Inno-Setup-issrc/Projects/Src/IDE.IDEScintEdit.pas

714 lines
28 KiB
ObjectPascal

unit IDE.IDEScintEdit;
{
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);
TIDEScintIndicatorNumber = 0..minMax;
{ Keymaps - Note: Scintilla's default keymap is the same or at least nearly
the same as Visual Studio's }
TIDEScintKeyMappingType = (kmtDefault, kmtVSCode);
{ Commands which require more than 1 parameterless SCI_XXXXX and need help
from the container }
TIDEScintComplexCommand = (ccNone, ccSelectNextOccurrence,
ccSelectAllOccurrences, ccSelectAllFindMatches, ccSimplifySelection,
ccUnfoldLine, ccFoldLine, ccToggleLinesComment, ccAddCursorUp,
ccAddCursorDown, ccBraceMatch, ccAddCursorsToLineEnds);
TIDEScintEdit = class(TScintEdit)
private
type
TIDEScintComplexCommands = TDictionary<TShortCut, TIDEScintComplexCommand>;
TIDEScintComplexCommandsReversed = TDictionary<TIDEScintComplexCommand, TShortCut>;
var
FKeyMappingType: TIDEScintKeyMappingType;
FComplexCommands: TIDEScintComplexCommands;
FComplexCommandsReversed: TIDEScintComplexCommandsReversed;
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[TIDEScintIndicatorNumber] of Integer;
FIndicatorHash: array[TIDEScintIndicatorNumber] of String;
procedure AddComplexCommand(const ShortCut: TShortCut;
Command: TIDEScintComplexCommand; const AlternativeShortCut: Boolean = False);
procedure SetUseFolding(const Value: Boolean);
procedure SetKeyMappingType(const Value: TIDEScintKeyMappingType);
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): TIDEScintComplexCommand;
function GetComplexCommandShortCut(const Command: TIDEScintComplexCommand): TShortCut;
function GetRectExtendShiftState(const Desired: Boolean): TShiftState;
procedure UpdateIndicators(const Ranges: TScintRangeList;
const IndicatorNumber: TIDEScintIndicatorNumber);
procedure UpdateWidthsAndSizes(const IconMarkersWidth,
BaseChangeHistoryWidth, BaseFolderMarkersWidth, LeftBlankMarginWidth,
RightBlankMarginWidth, SquigglyWidth, CaretWidth, WhiteSpaceSize: Integer);
procedure UpdateThemeColorsAndStyleAttributes;
published
property KeyMappingType: TIDEScintKeyMappingType read FKeyMappingType write SetKeyMappingType default kmtDefault;
property UseFolding: Boolean read FUseFolding write SetUseFolding default True;
end;
TIDEScintFileEdit = class(TIDEScintEdit)
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;
TIDEScintEditNavItem = record
Memo: TIDEScintEdit;
Line, Column, VirtualSpace: Integer;
constructor Create(const AMemo: TIDEScintEdit);
function EqualMemoAndLine(const ANavItem: TIDEScintEditNavItem): 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 }
TIDEScintEditNavStack = class(TList<TIDEScintEditNavItem>)
public
function LinesDeleted(const AMemo: TIDEScintEdit; const FirstLine, LineCount: Integer): Boolean;
procedure LinesInserted(const AMemo: TIDEScintEdit; const FirstLine, LineCount: Integer);
procedure Optimize;
function RemoveMemo(const AMemo: TIDEScintEdit): Boolean;
function RemoveMemoBadLines(const AMemo: TIDEScintEdit): Boolean;
end;
TIDEScintEditNavStacks = class
private
FBackNavStack: TIDEScintEditNavStack;
FForwardNavStack: TIDEScintEditNavStack;
public
constructor Create;
destructor Destroy; override;
function AddNewBackForJump(const OldNavItem, NewNavItem: TIDEScintEditNavItem): Boolean;
procedure Clear;
procedure Limit;
function LinesDeleted(const AMemo: TIDEScintEdit; const FirstLine, LineCount: Integer): Boolean;
procedure LinesInserted(const AMemo: TIDEScintEdit; const FirstLine, LineCount: Integer);
function RemoveMemo(const AMemo: TIDEScintEdit): Boolean;
function RemoveMemoBadLines(const AMemo: TIDEScintEdit): Boolean;
property Back: TIDEScintEditNavStack read FBackNavStack;
property Forward: TIDEScintEditNavStack read FForwardNavStack;
end;
implementation
uses
SysUtils, SHA256, ScintInt.InnoSetup;
{ TIDEScintEdit }
constructor TIDEScintEdit.Create(AOwner: TComponent);
begin
inherited;
FComplexCommands := TIDEScintComplexCommands.Create;
FComplexCommandsReversed := TIDEScintComplexCommandsReversed.Create;
FKeyMappingType := kmtDefault;
UpdateComplexCommands;
FUseFolding := True;
end;
destructor TIDEScintEdit.Destroy;
begin
FComplexCommandsReversed.Free;
FComplexCommands.Free;
inherited;
end;
procedure TIDEScintEdit.CreateWnd;
begin
inherited;
{ Some notes about Scintilla versions:
-What about using Calltips and SCN_DWELLSTART to show variable evalutions?
-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);
Call(SCI_SETCOPYSEPARATOR, 0, LineEndingString);
Call(SCI_SETUNDOSELECTIONHISTORY, 1, 0);
AssignCmdKey('Z', [ssShift, ssCtrl], SCI_REDO);
Call(SCI_SETSCROLLWIDTH, 1024 * Call(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 TIDEScintEdit.AddComplexCommand(const ShortCut: TShortCut;
Command: TIDEScintComplexCommand; 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 TIDEScintEdit.GetComplexCommand(
const ShortCut: TShortCut): TIDEScintComplexCommand;
begin
if not FComplexCommands.TryGetValue(ShortCut, Result) or
(ReadOnly and (Result = ccToggleLinesComment)) then
Result := ccNone;
end;
function TIDEScintEdit.GetComplexCommandShortCut(
const Command: TIDEScintComplexCommand): TShortCut;
begin
Result := FComplexCommandsReversed[Command];
end;
function TIDEScintEdit.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 TIDEScintEdit.SetKeyMappingType(
const Value: TIDEScintKeyMappingType);
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 TIDEScintEdit.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);
AddComplexCommand(ShortCut(KeyToKeyCode('I'), [ssShift, ssAlt]), ccAddCursorsToLineEnds);
end;
procedure TIDEScintEdit.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 TIDEScintEdit.UpdateIndicators(const Ranges: TScintRangeList;
const IndicatorNumber: TIDEScintIndicatorNumber);
function HashRanges(const Ranges: TScintRangeList): String;
begin
if Ranges.Count > 0 then begin
var Context: TSHA256Context;
SHA256Init(Context);
for var Range in Ranges do
SHA256Update(Context, Range, SizeOf(Range));
Result := SHA256DigestToString(SHA256Final(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 TIDEScintEdit.UpdateWidthsAndSizes(const IconMarkersWidth,
BaseChangeHistoryWidth, BaseFolderMarkersWidth, LeftBlankMarginWidth,
RightBlankMarginWidth, SquigglyWidth, CaretWidth, WhiteSpaceSize: 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);
Call(SCI_SETWHITESPACESIZE, WhiteSpaceSize, 0);
end;
procedure TIDEScintEdit.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_WHITE_SPACE, FTheme.Colors[tcIndentGuideFore] or (SC_ALPHA_OPAQUE shl 24));
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;
{ TIDEScintFileEdit }
constructor TIDEScintFileEdit.Create;
begin
inherited;
FBreakPoints := TList<Integer>.Create;
end;
destructor TIDEScintFileEdit.Destroy;
begin
FBreakPoints.Free;
inherited;
end;
{ TIDEScintEditNavItem }
constructor TIDEScintEditNavItem.Create(const AMemo: TIDEScintEdit);
begin
Memo := AMemo;
Line := AMemo.CaretLine;
Column := AMemo.CaretColumn;
VirtualSpace := AMemo.CaretVirtualSpace;
end;
function TIDEScintEditNavItem.EqualMemoAndLine(
const ANavItem: TIDEScintEditNavItem): Boolean;
begin
Result := (Memo = ANavItem.Memo) and (Line = ANavItem.Line);
end;
procedure TIDEScintEditNavItem.Invalidate;
begin
Memo := nil;
end;
function TIDEScintEditNavItem.Valid: Boolean;
begin
Result := (Memo <> nil) and (Line < Memo.Lines.Count); { Line check: see MemoLinesDeleted and RemoveMemoBadLinesFromNav }
end;
{ TIDEScintEditNavStack }
function TIDEScintEditNavStack.LinesDeleted(const AMemo: TIDEScintEdit;
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 TIDEScintEditNavStack.LinesInserted(const AMemo: TIDEScintEdit;
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 TIDEScintEditNavStack.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 TIDEScintEditNavStack.RemoveMemo(
const AMemo: TIDEScintEdit): 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 TIDEScintEditNavStack.RemoveMemoBadLines(
const AMemo: TIDEScintEdit): 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;
{ TIDEScintEditNavStacks }
constructor TIDEScintEditNavStacks.Create;
begin
inherited;
FBackNavStack := TIDEScintEditNavStack.Create;
FForwardNavStack := TIDEScintEditNavStack.Create;
end;
destructor TIDEScintEditNavStacks.Destroy;
begin
FForwardNavStack.Free;
FBackNavStack.Free;
inherited;
end;
function TIDEScintEditNavStacks.AddNewBackForJump(const OldNavItem,
NewNavItem: TIDEScintEditNavItem): 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 TIDEScintEditNavStacks.Clear;
begin
FBackNavStack.Clear;
FForwardNavStack.Clear;
end;
procedure TIDEScintEditNavStacks.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 TIDEScintEditNavStacks.LinesDeleted(const AMemo: TIDEScintEdit;
const FirstLine, LineCount: Integer): Boolean;
begin
Result := FBackNavStack.LinesDeleted(AMemo, FirstLine, LineCount);
Result := FForwardNavStack.LinesDeleted(AMemo, FirstLine, LineCount) or Result;
end;
procedure TIDEScintEditNavStacks.LinesInserted(const AMemo: TIDEScintEdit;
const FirstLine, LineCount: Integer);
begin
FBackNavStack.LinesInserted(AMemo, FirstLine, LineCount);
FForwardNavStack.LinesInserted(AMemo, FirstLine, LineCount);
end;
function TIDEScintEditNavStacks.RemoveMemo(
const AMemo: TIDEScintEdit): Boolean;
begin
Result := FBackNavStack.RemoveMemo(AMemo);
Result := FForwardNavStack.RemoveMemo(AMemo) or Result;
end;
function TIDEScintEditNavStacks.RemoveMemoBadLines(
const AMemo: TIDEScintEdit): Boolean;
begin
Result := FBackNavStack.RemoveMemoBadLines(AMemo);
Result := FForwardNavStack.RemoveMemoBadLines(AMemo) or Result;
end;
end.