IDE.IDEScintEdit.pas 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738
  1. unit IDE.IDEScintEdit;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2024 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. Compiler IDE's TScintEdit
  8. }
  9. interface
  10. uses
  11. Windows, Graphics, Classes, Menus, Generics.Collections,
  12. ScintInt, ScintEdit, ModernColors;
  13. const
  14. { Memo margin numbers }
  15. mmLineNumbers = 0;
  16. mmIcons = 1;
  17. mmChangeHistory = 2;
  18. mmFolding = 3;
  19. { Memo marker icon and line marker numbers }
  20. mmiHasEntry = 0; { grey dot }
  21. mmiEntryProcessed = 1; { green dot }
  22. mmiBreakpoint = 2; { stop sign }
  23. mmiBreakpointGood = 3; { stop sign + check }
  24. mmiBreakpointBad = 4; { stop sign + X }
  25. mmiStep = 5; { blue arrow }
  26. mmiBreakpointStep = 6; { blue arrow on top of a stop sign + check }
  27. mmiMask = $7F;
  28. mlmError = 10; { maroon line highlight }
  29. mlmBreakpointBad = 11; { ugly olive line highlight }
  30. mlmStep = 12; { blue line highlight }
  31. { Memo indicator numbers - Note: inSquiggly and inPendingSquiggly are 0 and 1
  32. in ScintStylerInnoSetup and must be first and second here. Also note: even
  33. though inSquiggly and inPendingSquiggly are exclusive we still need 2 indicators
  34. (instead of 1 indicator with 2 values) because inPendingSquiggly is always
  35. hidden and in inSquiggly is not. }
  36. minSquiggly = INDICATOR_CONTAINER;
  37. minPendingSquiggly = INDICATOR_CONTAINER+1;
  38. minWordAtCursorOccurrence = INDICATOR_CONTAINER+2;
  39. minSelTextOccurrence = INDICATOR_CONTAINER+3;
  40. minMax = minSelTextOccurrence;
  41. { Just some invalid value used to indicate an unknown/uninitialized compiler FileIndex value }
  42. UnknownCompilerFileIndex = -2;
  43. type
  44. TLineState = (lnUnknown, lnHasEntry, lnEntryProcessed); { Not related to TScintLineState }
  45. PLineStateArray = ^TLineStateArray;
  46. TLineStateArray = array[0..0] of TLineState;
  47. TSaveEncoding = (seAuto, seUTF8WithBOM, seUTF8WithoutBOM);
  48. TIDEScintIndicatorNumber = Low(TScintIndicatorNumber)..minMax;
  49. { Keymaps - Note: Scintilla's default keymap is the same or at least nearly
  50. the same as Visual Studio's }
  51. TIDEScintKeyMappingType = (kmtDefault, kmtVSCode);
  52. { Commands which require more than 1 parameterless SCI_XXXXX and need help
  53. from the container }
  54. TIDEScintComplexCommand = (ccNone, ccSelectNextOccurrence,
  55. ccSelectAllOccurrences, ccSelectAllFindMatches, ccSimplifySelection,
  56. ccUnfoldLine, ccFoldLine, ccToggleLinesComment, ccAddCursorUp,
  57. ccAddCursorDown, ccBraceMatch, ccAddCursorsToLineEnds);
  58. TIDEScintEdit = class(TScintEdit)
  59. private
  60. type
  61. TIDEScintComplexCommands = TDictionary<TShortCut, TIDEScintComplexCommand>;
  62. TIDEScintComplexCommandsReversed = TDictionary<TIDEScintComplexCommand, TShortCut>;
  63. var
  64. FKeyMappingType: TIDEScintKeyMappingType;
  65. FSmartHome: Boolean;
  66. FComplexCommands: TIDEScintComplexCommands;
  67. FComplexCommandsReversed: TIDEScintComplexCommandsReversed;
  68. FUseFolding: Boolean;
  69. FTheme: TTheme;
  70. FOpeningFile: Boolean;
  71. FUsed: Boolean; { The IDE only shows 1 memo at a time so can't use .Visible to check if a memo is used }
  72. FIndicatorCount: array[TIDEScintIndicatorNumber] of Integer;
  73. FIndicatorHash: array[TIDEScintIndicatorNumber] of String;
  74. procedure AddComplexCommand(const ShortCut: TShortCut;
  75. Command: TIDEScintComplexCommand; const AlternativeShortCut: Boolean = False);
  76. procedure SetUseFolding(const Value: Boolean);
  77. procedure SetKeyMappingType(const Value: TIDEScintKeyMappingType);
  78. procedure SetSmartHome(const Value: Boolean);
  79. procedure UpdateComplexCommands;
  80. procedure UpdateSmartHome;
  81. protected
  82. procedure CreateWnd; override;
  83. public
  84. constructor Create(AOwner: TComponent); override;
  85. destructor Destroy; override;
  86. property Theme: TTheme read FTheme write FTheme;
  87. property OpeningFile: Boolean read FOpeningFile write FOpeningFile;
  88. property Used: Boolean read FUsed write FUsed;
  89. function GetComplexCommand(const ShortCut: TShortCut): TIDEScintComplexCommand;
  90. function GetComplexCommandShortCut(const Command: TIDEScintComplexCommand): TShortCut;
  91. function GetRectExtendShiftState: TShiftState;
  92. procedure UpdateIndicators(const Ranges: TScintRangeList;
  93. const IndicatorNumber: TIDEScintIndicatorNumber);
  94. procedure UpdateWidthsAndSizes(const IconMarkersWidth,
  95. BaseChangeHistoryWidth, BaseFolderMarkersWidth, LeftBlankMarginWidth,
  96. RightBlankMarginWidth, SquigglyWidth, CaretWidth, WhiteSpaceSize: Integer);
  97. procedure UpdateThemeColorsAndStyleAttributes;
  98. published
  99. property KeyMappingType: TIDEScintKeyMappingType read FKeyMappingType write SetKeyMappingType default kmtDefault;
  100. property SmartHome: Boolean read FSmartHome write SetSmartHome default True;
  101. property UseFolding: Boolean read FUseFolding write SetUseFolding default True;
  102. end;
  103. TIDEScintFileEdit = class(TIDEScintEdit)
  104. private
  105. FBreakPoints: TList<Integer>;
  106. FCompilerFileIndex: Integer;
  107. FFilename: String;
  108. FFileLastWriteTime: TFileTime;
  109. FSaveEncoding: TSaveEncoding;
  110. public
  111. ErrorLine, ErrorCaretPosition: Integer;
  112. StepLine: Integer;
  113. LineState: PLineStateArray;
  114. LineStateCapacity, LineStateCount: Integer;
  115. constructor Create(AOwner: TComponent); override;
  116. destructor Destroy; override;
  117. property BreakPoints: TList<Integer> read FBreakPoints;
  118. property Filename: String read FFileName write FFilename;
  119. property CompilerFileIndex: Integer read FCompilerFileIndex write FCompilerFileIndex;
  120. property FileLastWriteTime: TFileTime read FFileLastWriteTime write FFileLastWriteTime;
  121. property SaveEncoding: TSaveEncoding read FSaveEncoding write FSaveEncoding;
  122. end;
  123. TIDEScintEditNavItem = record
  124. Memo: TIDEScintEdit;
  125. Line, Column, VirtualSpace: Integer;
  126. constructor Create(const AMemo: TIDEScintEdit);
  127. function EqualMemoAndLine(const ANavItem: TIDEScintEditNavItem): Boolean;
  128. procedure Invalidate;
  129. function Valid: Boolean;
  130. end;
  131. { Not using TStack since it lacks a way the keep a maximum amount of items by discarding the oldest }
  132. TIDEScintEditNavStack = class(TList<TIDEScintEditNavItem>)
  133. public
  134. function LinesDeleted(const AMemo: TIDEScintEdit; const FirstLine, LineCount: Integer): Boolean;
  135. procedure LinesInserted(const AMemo: TIDEScintEdit; const FirstLine, LineCount: Integer);
  136. procedure Optimize;
  137. function RemoveMemo(const AMemo: TIDEScintEdit): Boolean;
  138. function RemoveMemoBadLines(const AMemo: TIDEScintEdit): Boolean;
  139. end;
  140. TIDEScintEditNavStacks = class
  141. private
  142. FBackNavStack: TIDEScintEditNavStack;
  143. FForwardNavStack: TIDEScintEditNavStack;
  144. public
  145. constructor Create;
  146. destructor Destroy; override;
  147. function AddNewBackForJump(const OldNavItem, NewNavItem: TIDEScintEditNavItem): Boolean;
  148. procedure Clear;
  149. procedure Limit;
  150. function LinesDeleted(const AMemo: TIDEScintEdit; const FirstLine, LineCount: Integer): Boolean;
  151. procedure LinesInserted(const AMemo: TIDEScintEdit; const FirstLine, LineCount: Integer);
  152. function RemoveMemo(const AMemo: TIDEScintEdit): Boolean;
  153. function RemoveMemoBadLines(const AMemo: TIDEScintEdit): Boolean;
  154. property Back: TIDEScintEditNavStack read FBackNavStack;
  155. property Forward: TIDEScintEditNavStack read FForwardNavStack;
  156. end;
  157. implementation
  158. uses
  159. SysUtils, SHA256, ScintInt.InnoSetup;
  160. { TIDEScintEdit }
  161. constructor TIDEScintEdit.Create(AOwner: TComponent);
  162. begin
  163. inherited;
  164. FComplexCommands := TIDEScintComplexCommands.Create;
  165. FComplexCommandsReversed := TIDEScintComplexCommandsReversed.Create;
  166. FKeyMappingType := kmtDefault;
  167. FSmartHome := True;
  168. UpdateComplexCommands;
  169. FUseFolding := True;
  170. end;
  171. destructor TIDEScintEdit.Destroy;
  172. begin
  173. FComplexCommandsReversed.Free;
  174. FComplexCommands.Free;
  175. inherited;
  176. end;
  177. procedure TIDEScintEdit.CreateWnd;
  178. begin
  179. inherited;
  180. { Some notes about Scintilla versions:
  181. -What about using Calltips and SCN_DWELLSTART to show variable evalutions?
  182. -5.2.3: "Applications should move to SCI_GETTEXTRANGEFULL, SCI_FINDTEXTFULL,
  183. and SCI_FORMATRANGEFULL from their predecessors as they will be
  184. deprecated." So our use of SCI_GETTEXTRANGE and SCI_FORMATRANGE needs
  185. to be updated but that also means we should do many more changes to
  186. replace all the Integer positions with a 'TScintPosition = type
  187. NativeInt'. Does not actually change anything until there's a
  188. 64-bit build...
  189. Later SCI_GETSTYLEDTEXTFULL was also added but we don't use it at
  190. the time of writing. }
  191. Call(SCI_AUTOCSETAUTOHIDE, 0, 0);
  192. Call(SCI_AUTOCSETCANCELATSTART, 0, 0);
  193. Call(SCI_AUTOCSETDROPRESTOFWORD, 1, 0);
  194. Call(SCI_AUTOCSETIGNORECASE, 1, 0);
  195. Call(SCI_AUTOCSETOPTIONS, SC_AUTOCOMPLETE_FIXED_SIZE, 0); { Removes the ugly WS_THICKFRAME header at the cost of resizability }
  196. Call(SCI_AUTOCSETMAXHEIGHT, 12, 0);
  197. Call(SCI_AUTOCSETMINWIDTH, 50, 0);
  198. Call(SCI_AUTOCSETMAXWIDTH, 50, 0);
  199. { Same color as AutoComplete's border color, works well for both dark and light themes }
  200. var BorderColor := ColorToRGB(clWindowFrame);
  201. Call(SCI_CALLTIPSETFOREBORDER, BorderColor, BorderColor);
  202. Call(SCI_SETMULTIPLESELECTION, 1, 0);
  203. Call(SCI_SETADDITIONALSELECTIONTYPING, 1, 0);
  204. Call(SCI_SETMULTIPASTE, SC_MULTIPASTE_EACH, 0);
  205. Call(SCI_SETCOPYSEPARATOR, 0, LineEndingString);
  206. Call(SCI_SETUNDOSELECTIONHISTORY, SC_UNDO_SELECTION_HISTORY_ENABLED, 0);
  207. AssignCmdKey('Z', [ssShift, ssCtrl], SCI_REDO);
  208. Call(SCI_SETSCROLLWIDTH, 1024 * Call(SCI_TEXTWIDTH, 0, 'X'), 0);
  209. Call(SCI_INDICSETSTYLE, minSquiggly, INDIC_SQUIGGLE); { Overwritten by TCompForm.SyncEditorOptions }
  210. Call(SCI_INDICSETFORE, minSquiggly, clRed); { May be overwritten by UpdateThemeColorsAndStyleAttributes }
  211. Call(SCI_INDICSETSTYLE, minPendingSquiggly, INDIC_HIDDEN);
  212. Call(SCI_INDICSETSTYLE, minWordAtCursorOccurrence, INDIC_STRAIGHTBOX);
  213. Call(SCI_INDICSETFORE, minWordAtCursorOccurrence, clSilver); { May be overwritten by UpdateThemeColorsAndStyleAttributes }
  214. Call(SCI_INDICSETALPHA, minWordAtCursorOccurrence, SC_ALPHA_OPAQUE);
  215. Call(SCI_INDICSETOUTLINEALPHA, minWordAtCursorOccurrence, SC_ALPHA_OPAQUE);
  216. Call(SCI_INDICSETUNDER, minWordAtCursorOccurrence, 1);
  217. Call(SCI_INDICSETSTYLE, minSelTextOccurrence, INDIC_STRAIGHTBOX);
  218. Call(SCI_INDICSETFORE, minSelTextOccurrence, clSilver); { May be overwritten by UpdateThemeColorsAndStyleAttributes }
  219. Call(SCI_INDICSETALPHA, minSelTextOccurrence, SC_ALPHA_OPAQUE);
  220. Call(SCI_INDICSETOUTLINEALPHA, minSelTextOccurrence, SC_ALPHA_OPAQUE);
  221. Call(SCI_INDICSETUNDER, minSelTextOccurrence, 1);
  222. { Set up the gutter column with line numbers - avoid Scintilla's 'reverse arrow'
  223. cursor which is not a standard Windows cursor so is just confusing, especially
  224. because the line numbers are clickable to select lines. Note: width of the
  225. column is set up for us by TScintEdit.UpdateLineNumbersWidth. }
  226. Call(SCI_SETMARGINCURSORN, mmLineNumbers, SC_CURSORARROW);
  227. { Set up the gutter column with breakpoint etc symbols }
  228. Call(SCI_SETMARGINTYPEN, mmIcons, SC_MARGIN_SYMBOL);
  229. Call(SCI_SETMARGINMASKN, mmIcons, mmiMask);
  230. Call(SCI_SETMARGINSENSITIVEN, mmIcons, 1); { Makes it send SCN_MARGIN(RIGHT)CLICK instead of selecting lines }
  231. Call(SCI_SETMARGINCURSORN, mmIcons, SC_CURSORARROW);
  232. { Set up the gutter column with change history. Note: width of the column is
  233. set up by UpdateMarginsAndSquigglyAndCaretWidths. Also see
  234. https://scintilla.org/ChangeHistory.html }
  235. Call(SCI_SETMARGINTYPEN, mmChangeHistory, SC_MARGIN_SYMBOL);
  236. Call(SCI_SETMARGINMASKN, mmChangeHistory, SC_MASK_HISTORY);
  237. Call(SCI_SETMARGINCURSORN, mmChangeHistory, SC_CURSORARROW);
  238. { Set up the gutter column with folding markers. Note: width of the column is
  239. set up by UpdateMarginsAndSquigglyAndCaretWidths. }
  240. Call(SCI_SETMARGINTYPEN, mmFolding, SC_MARGIN_SYMBOL);
  241. Call(SCI_SETMARGINMASKN, mmFolding, LPARAM(SC_MASK_FOLDERS));
  242. Call(SCI_SETMARGINCURSORN, mmFolding, SC_CURSORARROW);
  243. Call(SCI_SETMARGINSENSITIVEN, mmFolding, 1);
  244. Call(SCI_SETAUTOMATICFOLD, SC_AUTOMATICFOLD_SHOW or SC_AUTOMATICFOLD_CLICK or SC_AUTOMATICFOLD_CHANGE, 0);
  245. Call(SCI_MARKERDEFINE, SC_MARKNUM_FOLDEROPEN, SC_MARK_ARROWDOWN);
  246. Call(SCI_MARKERDEFINE, SC_MARKNUM_FOLDER, SC_MARK_ARROW);
  247. Call(SCI_MARKERDEFINE, SC_MARKNUM_FOLDERSUB, SC_MARK_EMPTY);
  248. Call(SCI_MARKERDEFINE, SC_MARKNUM_FOLDERTAIL, SC_MARK_EMPTY);
  249. Call(SCI_MARKERDEFINE, SC_MARKNUM_FOLDEREND, SC_MARK_EMPTY);
  250. Call(SCI_MARKERDEFINE, SC_MARKNUM_FOLDEROPENMID, SC_MARK_EMPTY);
  251. Call(SCI_MARKERDEFINE, SC_MARKNUM_FOLDERMIDTAIL, SC_MARK_EMPTY);
  252. FoldFlags := [{sffLevelNumbers, }sffLineAfterContracted]; { sffLevelNumbers can be used to debug fold levels}
  253. { Set up the line markers }
  254. Call(SCI_MARKERDEFINE, mlmError, SC_MARK_BACKFORE);
  255. Call(SCI_MARKERSETFORE, mlmError, clWhite);
  256. Call(SCI_MARKERSETBACK, mlmError, clMaroon);
  257. Call(SCI_MARKERDEFINE, mlmBreakpointBad, SC_MARK_BACKFORE);
  258. Call(SCI_MARKERSETFORE, mlmBreakpointBad, clLime);
  259. Call(SCI_MARKERSETBACK, mlmBreakpointBad, clOlive);
  260. Call(SCI_MARKERDEFINE, mlmStep, SC_MARK_BACKFORE);
  261. Call(SCI_MARKERSETFORE, mlmStep, clWhite);
  262. Call(SCI_MARKERSETBACK, mlmStep, clBlue); { May be overwritten by UpdateThemeColorsAndStyleAttributes }
  263. end;
  264. procedure TIDEScintEdit.AddComplexCommand(const ShortCut: TShortCut;
  265. Command: TIDEScintComplexCommand; const AlternativeShortCut: Boolean);
  266. begin
  267. if Command = ccNone then
  268. raise Exception.Create('Command = ccNone');
  269. FComplexCommands.Add(ShortCut, Command);
  270. if not AlternativeShortCut then
  271. FComplexCommandsReversed.Add(Command, ShortCut);
  272. end;
  273. function TIDEScintEdit.GetComplexCommand(
  274. const ShortCut: TShortCut): TIDEScintComplexCommand;
  275. begin
  276. if not FComplexCommands.TryGetValue(ShortCut, Result) or
  277. (ReadOnly and (Result = ccToggleLinesComment)) then
  278. Result := ccNone;
  279. end;
  280. function TIDEScintEdit.GetComplexCommandShortCut(
  281. const Command: TIDEScintComplexCommand): TShortCut;
  282. begin
  283. Result := FComplexCommandsReversed[Command];
  284. end;
  285. function TIDEScintEdit.GetRectExtendShiftState: TShiftState;
  286. begin
  287. Result := [ssShift, ssAlt];
  288. if FKeyMappingType = kmtVSCode then
  289. Include(Result, ssCtrl);
  290. end;
  291. procedure TIDEScintEdit.SetKeyMappingType(
  292. const Value: TIDEScintKeyMappingType);
  293. begin
  294. if FKeyMappingType <> Value then begin
  295. FKeyMappingType := Value;
  296. Call(SCI_RESETALLCMDKEYS, Ord(FKeyMappingType = kmtVSCode), 0);
  297. if FKeyMappingType = kmtDefault then begin
  298. { Take some compatible improvements from the VSCode map }
  299. AssignCmdKey('C', [ssCtrl], SCI_COPYALLOWLINE);
  300. AssignCmdKey(SCK_INSERT, [ssCtrl], SCI_COPYALLOWLINE);
  301. AssignCmdKey('X', [ssCtrl], SCI_CUTALLOWLINE);
  302. AssignCmdKey(SCK_DELETE, [ssShift], SCI_CUTALLOWLINE);
  303. AssignCmdKey(SCK_UP, [ssAlt], SCI_MOVESELECTEDLINESUP);
  304. AssignCmdKey(SCK_DOWN, [ssAlt], SCI_MOVESELECTEDLINESDOWN);
  305. end;
  306. Call(SCI_SETMOUSEMAPPING, Ord(FKeyMappingType = kmtVSCode), 0);
  307. ClearCmdKey('/', [ssCtrl]); { Will be used by ccToggleLinesComment }
  308. ClearCmdKey('\', [ssCtrl]);
  309. if not FSmartHome then { Scintilla defaults to smart home (VCHOME*) }
  310. UpdateSmartHome;
  311. UpdateComplexCommands;
  312. end;
  313. end;
  314. procedure TIDEScintEdit.SetSmartHome(const Value: Boolean);
  315. begin
  316. if FSmartHome <> Value then begin
  317. FSmartHome := Value;
  318. UpdateSmartHome;
  319. end;
  320. end;
  321. procedure TIDEScintEdit.UpdateSmartHome;
  322. const
  323. Commands: array [Boolean] of array [0..2] of TScintCommand =
  324. ((SCI_HOME, SCI_HOMEEXTEND, SCI_HOMERECTEXTEND),
  325. (SCI_VCHOME, SCI_VCHOMEEXTEND, SCI_VCHOMERECTEXTEND));
  326. begin
  327. AssignCmdKey(SCK_HOME, [], Commands[FSmartHome][0]);
  328. AssignCmdKey(SCK_HOME, [ssShift], Commands[FSmartHome][1]);
  329. AssignCmdKey(SCK_HOME, GetRectExtendShiftState, Commands[FSmartHome][2]);
  330. end;
  331. procedure TIDEScintEdit.UpdateComplexCommands;
  332. begin
  333. FComplexCommands.Clear;
  334. FComplexCommandsReversed.Clear;
  335. { Normally VK_OEM_1 is ;, VK_OEM_6 is ], VK_OEM_4 is [, VK_OEM_2 is /, and VK_OEM_5 is \
  336. See CompFunc's NewShortcutToText for how it's is handled when they are different.
  337. Note: all VK_OEM shortcuts must have a menu item so the user can see what the
  338. shortcut is for their kayboard layout. }
  339. if FKeyMappingType = kmtVSCode then begin
  340. { Use freed Ctrl+D and Ctrl+Shift+L }
  341. AddComplexCommand(ShortCut(KeyToKeyCode('D'), [ssCtrl]), ccSelectNextOccurrence);
  342. AddComplexCommand(ShortCut(KeyToKeyCode('L'), [ssShift, ssCtrl]), ccSelectAllOccurrences);
  343. AddComplexCommand(ShortCut(VK_F2, [ssCtrl]), ccSelectAllOccurrences, True);
  344. end else begin
  345. AddComplexCommand(ShortCut(VK_OEM_PERIOD, [ssShift, ssAlt]), ccSelectNextOccurrence);
  346. AddComplexCommand(ShortCut(VK_OEM_1, [ssShift, ssAlt]), ccSelectAllOccurrences);
  347. end;
  348. AddComplexCommand(ShortCut(VK_RETURN, [ssAlt]), ccSelectAllFindMatches);
  349. AddComplexCommand(ShortCut(VK_ESCAPE, []), ccSimplifySelection);
  350. AddComplexCommand(ShortCut(VK_OEM_6, [ssShift, ssCtrl]), ccUnfoldLine);
  351. AddComplexCommand(ShortCut(VK_OEM_4, [ssShift, ssCtrl]), ccFoldLine);
  352. AddComplexCommand(ShortCut(VK_UP, [ssCtrl, ssAlt]), ccAddCursorUp);
  353. AddComplexCommand(ShortCut(VK_DOWN, [ssCtrl, ssAlt]), ccAddCursorDown);
  354. { Use freed Ctrl+/ }
  355. AddComplexCommand(ShortCut(VK_OEM_2, [ssCtrl]), ccToggleLinesComment); { Also see GetComplexCommand for ReadOnly check }
  356. AddComplexCommand(ShortCut(VK_OEM_5, [ssShift, ssCtrl]), ccBraceMatch);
  357. AddComplexCommand(ShortCut(KeyToKeyCode('I'), [ssShift, ssAlt]), ccAddCursorsToLineEnds);
  358. end;
  359. procedure TIDEScintEdit.SetUseFolding(const Value: Boolean);
  360. begin
  361. if FUseFolding <> Value then begin
  362. FUseFolding := Value;
  363. { If FUseFolding is True then caller must set the margin width using
  364. UpdateMarginsAndSquigglyAndCaretWidths else we set it to 0 now }
  365. if not FUseFolding then begin
  366. Call(SCI_FOLDALL, SC_FOLDACTION_EXPAND, 0);
  367. Call(SCI_SETMARGINWIDTHN, 3, 0);
  368. end;
  369. end;
  370. end;
  371. procedure TIDEScintEdit.UpdateIndicators(const Ranges: TScintRangeList;
  372. const IndicatorNumber: TIDEScintIndicatorNumber);
  373. function HashRanges(const Ranges: TScintRangeList): String;
  374. begin
  375. if Ranges.Count > 0 then begin
  376. var Context: TSHA256Context;
  377. SHA256Init(Context);
  378. for var Range in Ranges do
  379. SHA256Update(Context, Range, SizeOf(Range));
  380. Result := SHA256DigestToString(SHA256Final(Context));
  381. end else
  382. Result := '';
  383. end;
  384. begin
  385. var NewCount := Ranges.Count;
  386. var NewHash: String;
  387. var GotNewHash := False;
  388. var Update := NewCount <> FIndicatorCount[IndicatorNumber];
  389. if not Update and (NewCount <> 0) then begin
  390. NewHash := HashRanges(Ranges);
  391. GotNewHash := True;
  392. Update := NewHash <> FIndicatorHash[IndicatorNumber];
  393. end;
  394. if Update then begin
  395. Self.ClearIndicators(IndicatorNumber);
  396. for var Range in Ranges do
  397. Self.SetIndicators(Range.StartPos, Range.EndPos, IndicatorNumber, True);
  398. if not GotNewHash then
  399. NewHash := HashRanges(Ranges);
  400. FIndicatorCount[IndicatorNumber] := NewCount;
  401. FIndicatorHash[IndicatorNumber] := NewHash;
  402. end;
  403. end;
  404. procedure TIDEScintEdit.UpdateWidthsAndSizes(const IconMarkersWidth,
  405. BaseChangeHistoryWidth, BaseFolderMarkersWidth, LeftBlankMarginWidth,
  406. RightBlankMarginWidth, SquigglyWidth, CaretWidth, WhiteSpaceSize: Integer);
  407. begin
  408. Call(SCI_SETMARGINWIDTHN, mmIcons, IconMarkersWidth);
  409. var ChangeHistoryWidth: Integer;
  410. if ChangeHistory <> schDisabled then
  411. ChangeHistoryWidth := BaseChangeHistoryWidth
  412. else
  413. ChangeHistoryWidth := 0; { Current this is just the preprocessor output memo }
  414. Call(SCI_SETMARGINWIDTHN, mmChangeHistory, ChangeHistoryWidth);
  415. var FolderMarkersWidth: Integer;
  416. if FUseFolding then
  417. FolderMarkersWidth := BaseFolderMarkersWidth
  418. else
  419. FolderMarkersWidth := 0;
  420. Call(SCI_SETMARGINWIDTHN, mmFolding, FolderMarkersWidth);
  421. { Note: the first parameter is unused so the value '0' doesn't mean anything below }
  422. Call(SCI_SETMARGINLEFT, 0, LeftBlankMarginWidth);
  423. Call(SCI_SETMARGINRIGHT, 0, RightBlankMarginWidth);
  424. Call(SCI_INDICSETSTROKEWIDTH, minSquiggly, SquigglyWidth);
  425. Call(SCI_SETCARETWIDTH, CaretWidth, 0);
  426. Call(SCI_SETWHITESPACESIZE, WhiteSpaceSize, 0);
  427. end;
  428. procedure TIDEScintEdit.UpdateThemeColorsAndStyleAttributes;
  429. begin
  430. if FTheme <> nil then begin { Always True at the moment }
  431. Font.Color := FTheme.Colors[tcFore];
  432. Color := FTheme.Colors[tcBack];
  433. Call(SCI_SETELEMENTCOLOUR, SC_ELEMENT_LIST, FTheme.Colors[tcFore] or (SC_ALPHA_OPAQUE shl 24));
  434. Call(SCI_SETELEMENTCOLOUR, SC_ELEMENT_LIST_BACK, FTheme.Colors[tcIntelliBack] or (SC_ALPHA_OPAQUE shl 24));
  435. var Options := Call(SCI_AUTOCGETOPTIONS, 0, 0);
  436. if FTheme.Dark then
  437. Options := Options or SC_AUTOCOMPLETE_DARK_MODE
  438. else
  439. Options := Options and not SC_AUTOCOMPLETE_DARK_MODE;
  440. Call(SCI_AUTOCSETOPTIONS, Options, 0);
  441. Call(SCI_CALLTIPSETFORE, FTheme.Colors[tcFore], 0);
  442. Call(SCI_CALLTIPSETBACK, FTheme.Colors[tcIntelliBack], 0);
  443. Call(SCI_CALLTIPSETFOREHLT, FTheme.Colors[tcBlue], 0);
  444. var SelBackColor := FTheme.Colors[tcSelBack];
  445. Call(SCI_SETELEMENTCOLOUR, SC_ELEMENT_SELECTION_BACK, SelBackColor);
  446. Call(SCI_SETELEMENTCOLOUR, SC_ELEMENT_SELECTION_ADDITIONAL_BACK, SelBackColor);
  447. Call(SCI_SETELEMENTCOLOUR, SC_ELEMENT_SELECTION_SECONDARY_BACK, SelBackColor);
  448. Call(SCI_SETELEMENTCOLOUR, SC_ELEMENT_SELECTION_INACTIVE_BACK, SelBackColor);
  449. Call(SCI_SETELEMENTCOLOUR, SC_ELEMENT_SELECTION_INACTIVE_ADDITIONAL_BACK, SelBackColor);
  450. Call(SCI_SETELEMENTCOLOUR, SC_ELEMENT_WHITE_SPACE, FTheme.Colors[tcIndentGuideFore] or (SC_ALPHA_OPAQUE shl 24));
  451. Call(SCI_SETELEMENTCOLOUR, SC_ELEMENT_FOLD_LINE, FTheme.Colors[tcIndentGuideFore] or (70 shl 24));
  452. Call(SCI_SETFOLDMARGINCOLOUR, Ord(True), FTheme.Colors[tcBack]);
  453. Call(SCI_SETFOLDMARGINHICOLOUR, Ord(True), FTheme.Colors[tcBack]);
  454. Call(SCI_INDICSETFORE, minSquiggly, FTheme.Colors[tcRed]);
  455. Call(SCI_INDICSETFORE, minWordAtCursorOccurrence, FTheme.Colors[tcWordAtCursorOccurrenceBack]);
  456. Call(SCI_INDICSETFORE, minSelTextOccurrence, FTheme.Colors[tcSelTextOccurrenceBack]);
  457. Call(SCI_MARKERSETBACK, mlmStep, FTheme.Colors[tcBlue]);
  458. Call(SCI_MARKERSETFORE, SC_MARKNUM_HISTORY_REVERTED_TO_ORIGIN, FTheme.Colors[tcBlue]); { To reproduce: open a file, press enter, save, undo }
  459. Call(SCI_MARKERSETBACK, SC_MARKNUM_HISTORY_REVERTED_TO_ORIGIN, FTheme.Colors[tcBlue]);
  460. Call(SCI_MARKERSETFORE, SC_MARKNUM_HISTORY_SAVED, FTheme.Colors[tcGreen]);
  461. Call(SCI_MARKERSETBACK, SC_MARKNUM_HISTORY_SAVED, FTheme.Colors[tcGreen]);
  462. Call(SCI_MARKERSETFORE, SC_MARKNUM_HISTORY_MODIFIED, FTheme.Colors[tcReallyOrange]);
  463. Call(SCI_MARKERSETBACK, SC_MARKNUM_HISTORY_MODIFIED, FTheme.Colors[tcReallyOrange]);
  464. Call(SCI_MARKERSETFORE, SC_MARKNUM_HISTORY_REVERTED_TO_MODIFIED, FTheme.Colors[tcTeal]); { To reproduce: open a file, press space, press backspace, save, press enter, save, undo }
  465. Call(SCI_MARKERSETBACK, SC_MARKNUM_HISTORY_REVERTED_TO_MODIFIED, FTheme.Colors[tcTeal]);
  466. end;
  467. UpdateStyleAttributes;
  468. end;
  469. { TIDEScintFileEdit }
  470. constructor TIDEScintFileEdit.Create;
  471. begin
  472. inherited;
  473. FBreakPoints := TList<Integer>.Create;
  474. end;
  475. destructor TIDEScintFileEdit.Destroy;
  476. begin
  477. FBreakPoints.Free;
  478. inherited;
  479. end;
  480. { TIDEScintEditNavItem }
  481. constructor TIDEScintEditNavItem.Create(const AMemo: TIDEScintEdit);
  482. begin
  483. Memo := AMemo;
  484. Line := AMemo.CaretLine;
  485. Column := AMemo.CaretColumn;
  486. VirtualSpace := AMemo.CaretVirtualSpace;
  487. end;
  488. function TIDEScintEditNavItem.EqualMemoAndLine(
  489. const ANavItem: TIDEScintEditNavItem): Boolean;
  490. begin
  491. Result := (Memo = ANavItem.Memo) and (Line = ANavItem.Line);
  492. end;
  493. procedure TIDEScintEditNavItem.Invalidate;
  494. begin
  495. Memo := nil;
  496. end;
  497. function TIDEScintEditNavItem.Valid: Boolean;
  498. begin
  499. Result := (Memo <> nil) and (Line < Memo.Lines.Count); { Line check: see MemoLinesDeleted and RemoveMemoBadLinesFromNav }
  500. end;
  501. { TIDEScintEditNavStack }
  502. function TIDEScintEditNavStack.LinesDeleted(const AMemo: TIDEScintEdit;
  503. const FirstLine, LineCount: Integer): Boolean;
  504. begin
  505. Result := False;
  506. for var I := Count-1 downto 0 do begin
  507. var NavItem := Items[I];
  508. if NavItem.Memo = AMemo then begin
  509. var Line := NavItem.Line;
  510. if Line >= FirstLine then begin
  511. if Line < FirstLine + LineCount then begin
  512. Delete(I);
  513. Result := True;
  514. end else begin
  515. NavItem.Line := Line - LineCount;
  516. Items[I] := NavItem;
  517. end;
  518. end;
  519. end;
  520. end;
  521. if Result then
  522. Optimize;
  523. end;
  524. procedure TIDEScintEditNavStack.LinesInserted(const AMemo: TIDEScintEdit;
  525. const FirstLine, LineCount: Integer);
  526. begin
  527. for var I := 0 to Count-1 do begin
  528. var NavItem := Items[I];
  529. if NavItem.Memo = AMemo then begin
  530. var Line := NavItem.Line;
  531. if Line >= FirstLine then begin
  532. NavItem.Line := Line + LineCount;
  533. Items[I] := NavItem;
  534. end;
  535. end;
  536. end;
  537. end;
  538. procedure TIDEScintEditNavStack.Optimize;
  539. begin
  540. { Turn two entries for the same memo and line which are next to each other
  541. into one entry, ignoring column differences (like Visual Studio 2022)
  542. Note: doesn't yet look at CompForm's FCurrentNavItem to see if a stack's top
  543. item is the same so it doesnt optimize that situation atm }
  544. for var I := Count-1 downto 1 do
  545. if Items[I].EqualMemoAndLine(Items[I-1]) then
  546. Delete(I);
  547. end;
  548. function TIDEScintEditNavStack.RemoveMemo(
  549. const AMemo: TIDEScintEdit): Boolean;
  550. begin
  551. Result := False;
  552. for var I := Count-1 downto 0 do begin
  553. if Items[I].Memo = AMemo then begin
  554. Delete(I);
  555. Result := True;
  556. end;
  557. end;
  558. if Result then
  559. Optimize;
  560. end;
  561. function TIDEScintEditNavStack.RemoveMemoBadLines(
  562. const AMemo: TIDEScintEdit): Boolean;
  563. begin
  564. Result := False;
  565. var LastGoodLine := AMemo.Lines.Count-1;
  566. for var I := Count-1 downto 0 do begin
  567. if (Items[I].Memo = AMemo) and (Items[I].Line > LastGoodLine) then begin
  568. Delete(I);
  569. Result := True;
  570. end;
  571. end;
  572. if Result then
  573. Optimize;
  574. end;
  575. { TIDEScintEditNavStacks }
  576. constructor TIDEScintEditNavStacks.Create;
  577. begin
  578. inherited;
  579. FBackNavStack := TIDEScintEditNavStack.Create;
  580. FForwardNavStack := TIDEScintEditNavStack.Create;
  581. end;
  582. destructor TIDEScintEditNavStacks.Destroy;
  583. begin
  584. FForwardNavStack.Free;
  585. FBackNavStack.Free;
  586. inherited;
  587. end;
  588. function TIDEScintEditNavStacks.AddNewBackForJump(const OldNavItem,
  589. NewNavItem: TIDEScintEditNavItem): Boolean;
  590. begin
  591. { Want a new item when changing tabs or moving at least 11 lines at once,
  592. similar to Visual Studio 2022, see:
  593. https://learn.microsoft.com/en-us/archive/blogs/zainnab/navigate-backward-and-navigate-forward
  594. Note: not doing the other stuff listed in the article atm }
  595. Result := (OldNavItem.Memo <> NewNavItem.Memo) or
  596. (Abs(OldNavItem.Line - NewNavItem.Line) >= 11);
  597. if Result then begin
  598. FBackNavStack.Add(OldNavItem);
  599. Limit;
  600. end;
  601. end;
  602. procedure TIDEScintEditNavStacks.Clear;
  603. begin
  604. FBackNavStack.Clear;
  605. FForwardNavStack.Clear;
  606. end;
  607. procedure TIDEScintEditNavStacks.Limit;
  608. begin
  609. { The dropdown showing both stacks + the current nav item should show at most
  610. 16 items just like Visual Studio 2022 }
  611. if FBackNavStack.Count + FForwardNavStack.Count >= 15 then
  612. FBackNavStack.Delete(0);
  613. end;
  614. function TIDEScintEditNavStacks.LinesDeleted(const AMemo: TIDEScintEdit;
  615. const FirstLine, LineCount: Integer): Boolean;
  616. begin
  617. Result := FBackNavStack.LinesDeleted(AMemo, FirstLine, LineCount);
  618. Result := FForwardNavStack.LinesDeleted(AMemo, FirstLine, LineCount) or Result;
  619. end;
  620. procedure TIDEScintEditNavStacks.LinesInserted(const AMemo: TIDEScintEdit;
  621. const FirstLine, LineCount: Integer);
  622. begin
  623. FBackNavStack.LinesInserted(AMemo, FirstLine, LineCount);
  624. FForwardNavStack.LinesInserted(AMemo, FirstLine, LineCount);
  625. end;
  626. function TIDEScintEditNavStacks.RemoveMemo(
  627. const AMemo: TIDEScintEdit): Boolean;
  628. begin
  629. Result := FBackNavStack.RemoveMemo(AMemo);
  630. Result := FForwardNavStack.RemoveMemo(AMemo) or Result;
  631. end;
  632. function TIDEScintEditNavStacks.RemoveMemoBadLines(
  633. const AMemo: TIDEScintEdit): Boolean;
  634. begin
  635. Result := FBackNavStack.RemoveMemoBadLines(AMemo);
  636. Result := FForwardNavStack.RemoveMemoBadLines(AMemo) or Result;
  637. end;
  638. end.