IDE.IDEScintEdit.pas 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740
  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 = 0..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(const Desired: Boolean): 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(
  286. const Desired: Boolean): TShiftState;
  287. begin
  288. Result := [ssShift, ssAlt];
  289. if ((FKeyMappingType = kmtVSCode) and Desired) or
  290. ((FKeyMappingType <> kmtVSCode) and not Desired) then
  291. Include(Result, ssCtrl);
  292. end;
  293. procedure TIDEScintEdit.SetKeyMappingType(
  294. const Value: TIDEScintKeyMappingType);
  295. begin
  296. if FKeyMappingType <> Value then begin
  297. FKeyMappingType := Value;
  298. Call(SCI_RESETALLCMDKEYS, Ord(FKeyMappingType = kmtVSCode), 0);
  299. if FKeyMappingType = kmtDefault then begin
  300. { Take some compatible improvements from the VSCode map }
  301. AssignCmdKey('C', [ssCtrl], SCI_COPYALLOWLINE);
  302. AssignCmdKey(SCK_INSERT, [ssCtrl], SCI_COPYALLOWLINE);
  303. AssignCmdKey('X', [ssCtrl], SCI_CUTALLOWLINE);
  304. AssignCmdKey(SCK_DELETE, [ssShift], SCI_CUTALLOWLINE);
  305. AssignCmdKey(SCK_UP, [ssAlt], SCI_MOVESELECTEDLINESUP);
  306. AssignCmdKey(SCK_DOWN, [ssAlt], SCI_MOVESELECTEDLINESDOWN);
  307. end;
  308. Call(SCI_SETMOUSEMAPPING, Ord(FKeyMappingType = kmtVSCode), 0);
  309. ClearCmdKey('/', [ssCtrl]); { Will be used by ccToggleLinesComment }
  310. ClearCmdKey('\', [ssCtrl]);
  311. if not FSmartHome then { Scintilla defaults to smart home (VCHOME*) }
  312. UpdateSmartHome;
  313. UpdateComplexCommands;
  314. end;
  315. end;
  316. procedure TIDEScintEdit.SetSmartHome(const Value: Boolean);
  317. begin
  318. if FSmartHome <> Value then begin
  319. FSmartHome := Value;
  320. UpdateSmartHome;
  321. end;
  322. end;
  323. procedure TIDEScintEdit.UpdateSmartHome;
  324. const
  325. Commands: array [Boolean] of array [0..2] of TScintCommand =
  326. ((SCI_HOME, SCI_HOMEEXTEND, SCI_HOMERECTEXTEND),
  327. (SCI_VCHOME, SCI_VCHOMEEXTEND, SCI_VCHOMERECTEXTEND));
  328. begin
  329. AssignCmdKey(SCK_HOME, [], Commands[FSmartHome][0]);
  330. AssignCmdKey(SCK_HOME, [ssShift], Commands[FSmartHome][1]);
  331. AssignCmdKey(SCK_HOME, GetRectExtendShiftState(True), Commands[FSmartHome][2]);
  332. end;
  333. procedure TIDEScintEdit.UpdateComplexCommands;
  334. begin
  335. FComplexCommands.Clear;
  336. FComplexCommandsReversed.Clear;
  337. { Normally VK_OEM_1 is ;, VK_OEM_6 is ], VK_OEM_4 is [, VK_OEM_2 is /, and VK_OEM_5 is \
  338. See CompFunc's NewShortcutToText for how it's is handled when they are different.
  339. Note: all VK_OEM shortcuts must have a menu item so the user can see what the
  340. shortcut is for their kayboard layout. }
  341. if FKeyMappingType = kmtVSCode then begin
  342. { Use freed Ctrl+D and Ctrl+Shift+L }
  343. AddComplexCommand(ShortCut(KeyToKeyCode('D'), [ssCtrl]), ccSelectNextOccurrence);
  344. AddComplexCommand(ShortCut(KeyToKeyCode('L'), [ssShift, ssCtrl]), ccSelectAllOccurrences);
  345. AddComplexCommand(ShortCut(VK_F2, [ssCtrl]), ccSelectAllOccurrences, True);
  346. end else begin
  347. AddComplexCommand(ShortCut(VK_OEM_PERIOD, [ssShift, ssAlt]), ccSelectNextOccurrence);
  348. AddComplexCommand(ShortCut(VK_OEM_1, [ssShift, ssAlt]), ccSelectAllOccurrences);
  349. end;
  350. AddComplexCommand(ShortCut(VK_RETURN, [ssAlt]), ccSelectAllFindMatches);
  351. AddComplexCommand(ShortCut(VK_ESCAPE, []), ccSimplifySelection);
  352. AddComplexCommand(ShortCut(VK_OEM_6, [ssShift, ssCtrl]), ccUnfoldLine);
  353. AddComplexCommand(ShortCut(VK_OEM_4, [ssShift, ssCtrl]), ccFoldLine);
  354. AddComplexCommand(ShortCut(VK_UP, [ssCtrl, ssAlt]), ccAddCursorUp);
  355. AddComplexCommand(ShortCut(VK_DOWN, [ssCtrl, ssAlt]), ccAddCursorDown);
  356. { Use freed Ctrl+/ }
  357. AddComplexCommand(ShortCut(VK_OEM_2, [ssCtrl]), ccToggleLinesComment); { Also see GetComplexCommand for ReadOnly check }
  358. AddComplexCommand(ShortCut(VK_OEM_5, [ssShift, ssCtrl]), ccBraceMatch);
  359. AddComplexCommand(ShortCut(KeyToKeyCode('I'), [ssShift, ssAlt]), ccAddCursorsToLineEnds);
  360. end;
  361. procedure TIDEScintEdit.SetUseFolding(const Value: Boolean);
  362. begin
  363. if FUseFolding <> Value then begin
  364. FUseFolding := Value;
  365. { If FUseFolding is True then caller must set the margin width using
  366. UpdateMarginsAndSquigglyAndCaretWidths else we set it to 0 now }
  367. if not FUseFolding then begin
  368. Call(SCI_FOLDALL, SC_FOLDACTION_EXPAND, 0);
  369. Call(SCI_SETMARGINWIDTHN, 3, 0);
  370. end;
  371. end;
  372. end;
  373. procedure TIDEScintEdit.UpdateIndicators(const Ranges: TScintRangeList;
  374. const IndicatorNumber: TIDEScintIndicatorNumber);
  375. function HashRanges(const Ranges: TScintRangeList): String;
  376. begin
  377. if Ranges.Count > 0 then begin
  378. var Context: TSHA256Context;
  379. SHA256Init(Context);
  380. for var Range in Ranges do
  381. SHA256Update(Context, Range, SizeOf(Range));
  382. Result := SHA256DigestToString(SHA256Final(Context));
  383. end else
  384. Result := '';
  385. end;
  386. begin
  387. var NewCount := Ranges.Count;
  388. var NewHash: String;
  389. var GotNewHash := False;
  390. var Update := NewCount <> FIndicatorCount[IndicatorNumber];
  391. if not Update and (NewCount <> 0) then begin
  392. NewHash := HashRanges(Ranges);
  393. GotNewHash := True;
  394. Update := NewHash <> FIndicatorHash[IndicatorNumber];
  395. end;
  396. if Update then begin
  397. Self.ClearIndicators(IndicatorNumber);
  398. for var Range in Ranges do
  399. Self.SetIndicators(Range.StartPos, Range.EndPos, IndicatorNumber, True);
  400. if not GotNewHash then
  401. NewHash := HashRanges(Ranges);
  402. FIndicatorCount[IndicatorNumber] := NewCount;
  403. FIndicatorHash[IndicatorNumber] := NewHash;
  404. end;
  405. end;
  406. procedure TIDEScintEdit.UpdateWidthsAndSizes(const IconMarkersWidth,
  407. BaseChangeHistoryWidth, BaseFolderMarkersWidth, LeftBlankMarginWidth,
  408. RightBlankMarginWidth, SquigglyWidth, CaretWidth, WhiteSpaceSize: Integer);
  409. begin
  410. Call(SCI_SETMARGINWIDTHN, mmIcons, IconMarkersWidth);
  411. var ChangeHistoryWidth: Integer;
  412. if ChangeHistory <> schDisabled then
  413. ChangeHistoryWidth := BaseChangeHistoryWidth
  414. else
  415. ChangeHistoryWidth := 0; { Current this is just the preprocessor output memo }
  416. Call(SCI_SETMARGINWIDTHN, mmChangeHistory, ChangeHistoryWidth);
  417. var FolderMarkersWidth: Integer;
  418. if FUseFolding then
  419. FolderMarkersWidth := BaseFolderMarkersWidth
  420. else
  421. FolderMarkersWidth := 0;
  422. Call(SCI_SETMARGINWIDTHN, mmFolding, FolderMarkersWidth);
  423. { Note: the first parameter is unused so the value '0' doesn't mean anything below }
  424. Call(SCI_SETMARGINLEFT, 0, LeftBlankMarginWidth);
  425. Call(SCI_SETMARGINRIGHT, 0, RightBlankMarginWidth);
  426. Call(SCI_INDICSETSTROKEWIDTH, minSquiggly, SquigglyWidth);
  427. Call(SCI_SETCARETWIDTH, CaretWidth, 0);
  428. Call(SCI_SETWHITESPACESIZE, WhiteSpaceSize, 0);
  429. end;
  430. procedure TIDEScintEdit.UpdateThemeColorsAndStyleAttributes;
  431. begin
  432. if FTheme <> nil then begin { Always True at the moment }
  433. Font.Color := FTheme.Colors[tcFore];
  434. Color := FTheme.Colors[tcBack];
  435. Call(SCI_SETELEMENTCOLOUR, SC_ELEMENT_LIST, FTheme.Colors[tcFore] or (SC_ALPHA_OPAQUE shl 24));
  436. Call(SCI_SETELEMENTCOLOUR, SC_ELEMENT_LIST_BACK, FTheme.Colors[tcIntelliBack] or (SC_ALPHA_OPAQUE shl 24));
  437. var Options := Call(SCI_AUTOCGETOPTIONS, 0, 0);
  438. if FTheme.Dark then
  439. Options := Options or SC_AUTOCOMPLETE_DARK_MODE
  440. else
  441. Options := Options and not SC_AUTOCOMPLETE_DARK_MODE;
  442. Call(SCI_AUTOCSETOPTIONS, Options, 0);
  443. Call(SCI_CALLTIPSETFORE, FTheme.Colors[tcFore], 0);
  444. Call(SCI_CALLTIPSETBACK, FTheme.Colors[tcIntelliBack], 0);
  445. Call(SCI_CALLTIPSETFOREHLT, FTheme.Colors[tcBlue], 0);
  446. var SelBackColor := FTheme.Colors[tcSelBack];
  447. Call(SCI_SETELEMENTCOLOUR, SC_ELEMENT_SELECTION_BACK, SelBackColor);
  448. Call(SCI_SETELEMENTCOLOUR, SC_ELEMENT_SELECTION_ADDITIONAL_BACK, SelBackColor);
  449. Call(SCI_SETELEMENTCOLOUR, SC_ELEMENT_SELECTION_SECONDARY_BACK, SelBackColor);
  450. Call(SCI_SETELEMENTCOLOUR, SC_ELEMENT_SELECTION_INACTIVE_BACK, SelBackColor);
  451. Call(SCI_SETELEMENTCOLOUR, SC_ELEMENT_SELECTION_INACTIVE_ADDITIONAL_BACK, SelBackColor);
  452. Call(SCI_SETELEMENTCOLOUR, SC_ELEMENT_WHITE_SPACE, FTheme.Colors[tcIndentGuideFore] or (SC_ALPHA_OPAQUE shl 24));
  453. Call(SCI_SETELEMENTCOLOUR, SC_ELEMENT_FOLD_LINE, FTheme.Colors[tcIndentGuideFore] or (70 shl 24));
  454. Call(SCI_SETFOLDMARGINCOLOUR, Ord(True), FTheme.Colors[tcBack]);
  455. Call(SCI_SETFOLDMARGINHICOLOUR, Ord(True), FTheme.Colors[tcBack]);
  456. Call(SCI_INDICSETFORE, minSquiggly, FTheme.Colors[tcRed]);
  457. Call(SCI_INDICSETFORE, minWordAtCursorOccurrence, FTheme.Colors[tcWordAtCursorOccurrenceBack]);
  458. Call(SCI_INDICSETFORE, minSelTextOccurrence, FTheme.Colors[tcSelTextOccurrenceBack]);
  459. Call(SCI_MARKERSETBACK, mlmStep, FTheme.Colors[tcBlue]);
  460. Call(SCI_MARKERSETFORE, SC_MARKNUM_HISTORY_REVERTED_TO_ORIGIN, FTheme.Colors[tcBlue]); { To reproduce: open a file, press enter, save, undo }
  461. Call(SCI_MARKERSETBACK, SC_MARKNUM_HISTORY_REVERTED_TO_ORIGIN, FTheme.Colors[tcBlue]);
  462. Call(SCI_MARKERSETFORE, SC_MARKNUM_HISTORY_SAVED, FTheme.Colors[tcGreen]);
  463. Call(SCI_MARKERSETBACK, SC_MARKNUM_HISTORY_SAVED, FTheme.Colors[tcGreen]);
  464. Call(SCI_MARKERSETFORE, SC_MARKNUM_HISTORY_MODIFIED, FTheme.Colors[tcReallyOrange]);
  465. Call(SCI_MARKERSETBACK, SC_MARKNUM_HISTORY_MODIFIED, FTheme.Colors[tcReallyOrange]);
  466. 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 }
  467. Call(SCI_MARKERSETBACK, SC_MARKNUM_HISTORY_REVERTED_TO_MODIFIED, FTheme.Colors[tcTeal]);
  468. end;
  469. UpdateStyleAttributes;
  470. end;
  471. { TIDEScintFileEdit }
  472. constructor TIDEScintFileEdit.Create;
  473. begin
  474. inherited;
  475. FBreakPoints := TList<Integer>.Create;
  476. end;
  477. destructor TIDEScintFileEdit.Destroy;
  478. begin
  479. FBreakPoints.Free;
  480. inherited;
  481. end;
  482. { TIDEScintEditNavItem }
  483. constructor TIDEScintEditNavItem.Create(const AMemo: TIDEScintEdit);
  484. begin
  485. Memo := AMemo;
  486. Line := AMemo.CaretLine;
  487. Column := AMemo.CaretColumn;
  488. VirtualSpace := AMemo.CaretVirtualSpace;
  489. end;
  490. function TIDEScintEditNavItem.EqualMemoAndLine(
  491. const ANavItem: TIDEScintEditNavItem): Boolean;
  492. begin
  493. Result := (Memo = ANavItem.Memo) and (Line = ANavItem.Line);
  494. end;
  495. procedure TIDEScintEditNavItem.Invalidate;
  496. begin
  497. Memo := nil;
  498. end;
  499. function TIDEScintEditNavItem.Valid: Boolean;
  500. begin
  501. Result := (Memo <> nil) and (Line < Memo.Lines.Count); { Line check: see MemoLinesDeleted and RemoveMemoBadLinesFromNav }
  502. end;
  503. { TIDEScintEditNavStack }
  504. function TIDEScintEditNavStack.LinesDeleted(const AMemo: TIDEScintEdit;
  505. const FirstLine, LineCount: Integer): Boolean;
  506. begin
  507. Result := False;
  508. for var I := Count-1 downto 0 do begin
  509. var NavItem := Items[I];
  510. if NavItem.Memo = AMemo then begin
  511. var Line := NavItem.Line;
  512. if Line >= FirstLine then begin
  513. if Line < FirstLine + LineCount then begin
  514. Delete(I);
  515. Result := True;
  516. end else begin
  517. NavItem.Line := Line - LineCount;
  518. Items[I] := NavItem;
  519. end;
  520. end;
  521. end;
  522. end;
  523. if Result then
  524. Optimize;
  525. end;
  526. procedure TIDEScintEditNavStack.LinesInserted(const AMemo: TIDEScintEdit;
  527. const FirstLine, LineCount: Integer);
  528. begin
  529. for var I := 0 to Count-1 do begin
  530. var NavItem := Items[I];
  531. if NavItem.Memo = AMemo then begin
  532. var Line := NavItem.Line;
  533. if Line >= FirstLine then begin
  534. NavItem.Line := Line + LineCount;
  535. Items[I] := NavItem;
  536. end;
  537. end;
  538. end;
  539. end;
  540. procedure TIDEScintEditNavStack.Optimize;
  541. begin
  542. { Turn two entries for the same memo and line which are next to each other
  543. into one entry, ignoring column differences (like Visual Studio 2022)
  544. Note: doesn't yet look at CompForm's FCurrentNavItem to see if a stack's top
  545. item is the same so it doesnt optimize that situation atm }
  546. for var I := Count-1 downto 1 do
  547. if Items[I].EqualMemoAndLine(Items[I-1]) then
  548. Delete(I);
  549. end;
  550. function TIDEScintEditNavStack.RemoveMemo(
  551. const AMemo: TIDEScintEdit): Boolean;
  552. begin
  553. Result := False;
  554. for var I := Count-1 downto 0 do begin
  555. if Items[I].Memo = AMemo then begin
  556. Delete(I);
  557. Result := True;
  558. end;
  559. end;
  560. if Result then
  561. Optimize;
  562. end;
  563. function TIDEScintEditNavStack.RemoveMemoBadLines(
  564. const AMemo: TIDEScintEdit): Boolean;
  565. begin
  566. Result := False;
  567. var LastGoodLine := AMemo.Lines.Count-1;
  568. for var I := Count-1 downto 0 do begin
  569. if (Items[I].Memo = AMemo) and (Items[I].Line > LastGoodLine) then begin
  570. Delete(I);
  571. Result := True;
  572. end;
  573. end;
  574. if Result then
  575. Optimize;
  576. end;
  577. { TIDEScintEditNavStacks }
  578. constructor TIDEScintEditNavStacks.Create;
  579. begin
  580. inherited;
  581. FBackNavStack := TIDEScintEditNavStack.Create;
  582. FForwardNavStack := TIDEScintEditNavStack.Create;
  583. end;
  584. destructor TIDEScintEditNavStacks.Destroy;
  585. begin
  586. FForwardNavStack.Free;
  587. FBackNavStack.Free;
  588. inherited;
  589. end;
  590. function TIDEScintEditNavStacks.AddNewBackForJump(const OldNavItem,
  591. NewNavItem: TIDEScintEditNavItem): Boolean;
  592. begin
  593. { Want a new item when changing tabs or moving at least 11 lines at once,
  594. similar to Visual Studio 2022, see:
  595. https://learn.microsoft.com/en-us/archive/blogs/zainnab/navigate-backward-and-navigate-forward
  596. Note: not doing the other stuff listed in the article atm }
  597. Result := (OldNavItem.Memo <> NewNavItem.Memo) or
  598. (Abs(OldNavItem.Line - NewNavItem.Line) >= 11);
  599. if Result then begin
  600. FBackNavStack.Add(OldNavItem);
  601. Limit;
  602. end;
  603. end;
  604. procedure TIDEScintEditNavStacks.Clear;
  605. begin
  606. FBackNavStack.Clear;
  607. FForwardNavStack.Clear;
  608. end;
  609. procedure TIDEScintEditNavStacks.Limit;
  610. begin
  611. { The dropdown showing both stacks + the current nav item should show at most
  612. 16 items just like Visual Studio 2022 }
  613. if FBackNavStack.Count + FForwardNavStack.Count >= 15 then
  614. FBackNavStack.Delete(0);
  615. end;
  616. function TIDEScintEditNavStacks.LinesDeleted(const AMemo: TIDEScintEdit;
  617. const FirstLine, LineCount: Integer): Boolean;
  618. begin
  619. Result := FBackNavStack.LinesDeleted(AMemo, FirstLine, LineCount);
  620. Result := FForwardNavStack.LinesDeleted(AMemo, FirstLine, LineCount) or Result;
  621. end;
  622. procedure TIDEScintEditNavStacks.LinesInserted(const AMemo: TIDEScintEdit;
  623. const FirstLine, LineCount: Integer);
  624. begin
  625. FBackNavStack.LinesInserted(AMemo, FirstLine, LineCount);
  626. FForwardNavStack.LinesInserted(AMemo, FirstLine, LineCount);
  627. end;
  628. function TIDEScintEditNavStacks.RemoveMemo(
  629. const AMemo: TIDEScintEdit): Boolean;
  630. begin
  631. Result := FBackNavStack.RemoveMemo(AMemo);
  632. Result := FForwardNavStack.RemoveMemo(AMemo) or Result;
  633. end;
  634. function TIDEScintEditNavStacks.RemoveMemoBadLines(
  635. const AMemo: TIDEScintEdit): Boolean;
  636. begin
  637. Result := FBackNavStack.RemoveMemoBadLines(AMemo);
  638. Result := FForwardNavStack.RemoveMemoBadLines(AMemo) or Result;
  639. end;
  640. end.