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