7991 lines
296 KiB
ObjectPascal
7991 lines
296 KiB
ObjectPascal
unit IDE.MainForm;
|
|
|
|
{
|
|
Inno Setup
|
|
Copyright (C) 1997-2025 Jordan Russell
|
|
Portions by Martijn Laan
|
|
For conditions of distribution and use, see LICENSE.TXT.
|
|
|
|
Compiler form
|
|
}
|
|
|
|
{x$DEFINE STATICCOMPILER}
|
|
{ For debugging purposes, remove the 'x' to have it link the compiler code into
|
|
this program and not depend on ISCmplr.dll. You will also need to add the
|
|
..\Components and Src folders to the Delphi Compiler Search path in the project
|
|
options. Also see ISCC's STATICCOMPILER and Compiler.Compile's STATICPREPROC. }
|
|
|
|
{$IFDEF STATICCOMPILER}
|
|
{$R ..\Res\ISCmplr.images.res}
|
|
{$ENDIF}
|
|
|
|
interface
|
|
|
|
uses
|
|
Windows, Messages, SysUtils, Classes, Contnrs, Graphics, Controls, Forms, Dialogs, CommDlg,
|
|
Generics.Collections, UIStateForm, StdCtrls, ExtCtrls, Menus, Buttons, ComCtrls, CommCtrl,
|
|
ScintInt, ScintEdit, IDE.ScintStylerInnoSetup, NewTabSet, ModernColors, IDE.IDEScintEdit,
|
|
Shared.DebugStruct, Shared.CompilerInt.Struct, NewUxTheme, ImageList, ImgList, ToolWin, IDE.HelperFunc,
|
|
VirtualImageList, BaseImageCollection;
|
|
|
|
const
|
|
WM_StartCommandLineCompile = WM_USER + $1000;
|
|
WM_StartCommandLineWizard = WM_USER + $1001;
|
|
WM_StartNormally = WM_USER + $1002;
|
|
|
|
type
|
|
PDebugEntryArray = ^TDebugEntryArray;
|
|
TDebugEntryArray = array[0..0] of TDebugEntry;
|
|
PVariableDebugEntryArray = ^TVariableDebugEntryArray;
|
|
TVariableDebugEntryArray = array[0..0] of TVariableDebugEntry;
|
|
TStepMode = (smRun, smStepInto, smStepOver, smStepOut, smRunToCursor);
|
|
TDebugTarget = (dtSetup, dtUninstall);
|
|
|
|
const
|
|
DebugTargetStrings: array[TDebugTarget] of String = ('Setup', 'Uninstall');
|
|
|
|
type
|
|
TStatusMessageKind = (smkStartEnd, smkNormal, smkWarning, smkError);
|
|
|
|
TIncludedFile = class
|
|
Filename: String;
|
|
CompilerFileIndex: Integer;
|
|
LastWriteTime: TFileTime;
|
|
HasLastWriteTime: Boolean;
|
|
Memo: TIDEScintFileEdit;
|
|
end;
|
|
|
|
TIncludedFiles = TObjectList<TIncludedFile>;
|
|
|
|
TFindResult = class
|
|
Filename: String;
|
|
Line, LineStartPos: Integer;
|
|
Range: TScintRange;
|
|
PrefixStringLength: Integer;
|
|
end;
|
|
|
|
TFindResults = TObjectList<TFindResult>;
|
|
|
|
TMenuBitmaps = TDictionary<TMenuItem, HBITMAP>;
|
|
TKeyMappedMenus = TDictionary<TShortCut, TToolButton>;
|
|
|
|
TCallTipState = record
|
|
StartCallTipWord: Integer;
|
|
FunctionDefinition: AnsiString;
|
|
BraceCount: Integer;
|
|
LastPosCallTip: Integer;
|
|
ClassOrRecordMember: Boolean;
|
|
CurrentCallTipWord: String;
|
|
CurrentCallTip: Integer;
|
|
MaxCallTips: Integer;
|
|
end;
|
|
|
|
TUpdatePanelMessage = class
|
|
Msg, ConfigIdent: String;
|
|
ConfigValue: Integer;
|
|
Color: TColor;
|
|
constructor Create(const AMsg, AConfigIdent: String; const AConfigValue: Integer; const AColor: TColor);
|
|
end;
|
|
|
|
TUpdatePanelMessages = TObjectList<TUpdatePanelMessage>;
|
|
|
|
TMainForm = class(TUIStateForm)
|
|
MainMenu1: TMainMenu;
|
|
FMenu: TMenuItem;
|
|
FNewMainFile: TMenuItem;
|
|
FOpenMainFile: TMenuItem;
|
|
FSave: TMenuItem;
|
|
FSaveMainFileAs: TMenuItem;
|
|
N1: TMenuItem;
|
|
BCompile: TMenuItem;
|
|
N2: TMenuItem;
|
|
FExit: TMenuItem;
|
|
EMenu: TMenuItem;
|
|
EUndo: TMenuItem;
|
|
N3: TMenuItem;
|
|
ECut: TMenuItem;
|
|
ECopy: TMenuItem;
|
|
EPaste: TMenuItem;
|
|
EDelete: TMenuItem;
|
|
N4: TMenuItem;
|
|
ESelectAll: TMenuItem;
|
|
VMenu: TMenuItem;
|
|
EFind: TMenuItem;
|
|
EFindNext: TMenuItem;
|
|
EReplace: TMenuItem;
|
|
HMenu: TMenuItem;
|
|
HDoc: TMenuItem;
|
|
HAbout: TMenuItem;
|
|
FRecent: TMenuItem;
|
|
FClearRecent: TMenuItem;
|
|
N6: TMenuItem;
|
|
VCompilerOutput: TMenuItem;
|
|
FindDialog: TFindDialog;
|
|
ReplaceDialog: TReplaceDialog;
|
|
StatusPanel: TPanel;
|
|
CompilerOutputList: TListBox;
|
|
SplitPanel: TPanel;
|
|
HWebsite: TMenuItem;
|
|
VToolbar: TMenuItem;
|
|
N7: TMenuItem;
|
|
TOptions: TMenuItem;
|
|
HFaq: TMenuItem;
|
|
StatusBar: TStatusBar;
|
|
BodyPanel: TPanel;
|
|
VStatusBar: TMenuItem;
|
|
ERedo: TMenuItem;
|
|
RMenu: TMenuItem;
|
|
RStepInto: TMenuItem;
|
|
RStepOver: TMenuItem;
|
|
N5: TMenuItem;
|
|
RRun: TMenuItem;
|
|
RRunToCursor: TMenuItem;
|
|
N10: TMenuItem;
|
|
REvaluate: TMenuItem;
|
|
CheckIfRunningTimer: TTimer;
|
|
RPause: TMenuItem;
|
|
RParameters: TMenuItem;
|
|
OutputListPopupMenu: TMenuItem;
|
|
POutputListCopy: TMenuItem;
|
|
HISPPSep: TMenuItem;
|
|
N12: TMenuItem;
|
|
BStopCompile: TMenuItem;
|
|
HISPPDoc: TMenuItem;
|
|
N13: TMenuItem;
|
|
EGoto: TMenuItem;
|
|
RTerminate: TMenuItem;
|
|
BMenu: TMenuItem;
|
|
BLowPriority: TMenuItem;
|
|
HDonate: TMenuItem;
|
|
N14: TMenuItem;
|
|
N15: TMenuItem;
|
|
RTargetSetup: TMenuItem;
|
|
RTargetUninstall: TMenuItem;
|
|
OutputTabSet: TNewTabSet;
|
|
DebugOutputList: TListBox;
|
|
VDebugOutput: TMenuItem;
|
|
VHide: TMenuItem;
|
|
N11: TMenuItem;
|
|
TMenu: TMenuItem;
|
|
TAddRemovePrograms: TMenuItem;
|
|
RToggleBreakPoint: TMenuItem;
|
|
RDeleteBreakPoints: TMenuItem;
|
|
HWhatsNew: TMenuItem;
|
|
TGenerateGUID: TMenuItem;
|
|
TSignTools: TMenuItem;
|
|
N16: TMenuItem;
|
|
HExamples: TMenuItem;
|
|
N17: TMenuItem;
|
|
BOpenOutputFolder: TMenuItem;
|
|
N8: TMenuItem;
|
|
VZoom: TMenuItem;
|
|
VZoomIn: TMenuItem;
|
|
VZoomOut: TMenuItem;
|
|
N9: TMenuItem;
|
|
VZoomReset: TMenuItem;
|
|
N18: TMenuItem;
|
|
N19: TMenuItem;
|
|
FSaveEncoding: TMenuItem;
|
|
FSaveEncodingAuto: TMenuItem;
|
|
FSaveEncodingUTF8WithBOM: TMenuItem;
|
|
ToolBar: TToolBar;
|
|
BackNavButton: TToolButton;
|
|
ForwardNavButton: TToolButton;
|
|
ToolButton1: TToolButton;
|
|
NewMainFileButton: TToolButton;
|
|
OpenMainFileButton: TToolButton;
|
|
SaveButton: TToolButton;
|
|
ToolButton2: TToolButton;
|
|
CompileButton: TToolButton;
|
|
StopCompileButton: TToolButton;
|
|
ToolButton3: TToolButton;
|
|
RunButton: TToolButton;
|
|
PauseButton: TToolButton;
|
|
ToolButton4: TToolButton;
|
|
TargetSetupButton: TToolButton;
|
|
TargetUninstallButton: TToolButton;
|
|
ToolButton5: TToolButton;
|
|
HelpButton: TToolButton;
|
|
Bevel1: TBevel;
|
|
TerminateButton: TToolButton;
|
|
ThemedToolbarVirtualImageList: TVirtualImageList;
|
|
LightToolbarVirtualImageList: TVirtualImageList;
|
|
POutputListSelectAll: TMenuItem;
|
|
DebugCallStackList: TListBox;
|
|
VDebugCallStack: TMenuItem;
|
|
TMsgBoxDesigner: TMenuItem;
|
|
TRegistryDesigner: TMenuItem;
|
|
ToolBarPanel: TPanel;
|
|
HMailingList: TMenuItem;
|
|
MemosTabSet: TNewTabSet; { First tab is the main memo, last tab is the preprocessor output memo }
|
|
FSaveAll: TMenuItem;
|
|
RStepOut: TMenuItem;
|
|
VNextTab: TMenuItem;
|
|
VPreviousTab: TMenuItem;
|
|
N20: TMenuItem;
|
|
HShortcutsDoc: TMenuItem;
|
|
HRegExDoc: TMenuItem;
|
|
N21: TMenuItem;
|
|
EFindPrevious: TMenuItem;
|
|
FindResultsList: TListBox;
|
|
VFindResults: TMenuItem;
|
|
EFindInFiles: TMenuItem;
|
|
FindInFilesDialog: TFindDialog;
|
|
FPrint: TMenuItem;
|
|
N22: TMenuItem;
|
|
PrintDialog: TPrintDialog;
|
|
FSaveEncodingUTF8WithoutBOM: TMenuItem;
|
|
TFilesDesigner: TMenuItem;
|
|
VCloseCurrentTab: TMenuItem;
|
|
VReopenTab: TMenuItem;
|
|
VReopenTabs: TMenuItem;
|
|
MemosTabSetPopupMenu: TMenuItem;
|
|
VCloseCurrentTab2: TMenuItem;
|
|
VReopenTab2: TMenuItem;
|
|
VReopenTabs2: TMenuItem;
|
|
NavPopupMenu: TMenuItem;
|
|
N23: TMenuItem;
|
|
ThemedMarkersAndACVirtualImageList: TVirtualImageList;
|
|
ESelectNextOccurrence: TMenuItem;
|
|
ESelectAllOccurrences: TMenuItem;
|
|
BreakPointsPopupMenu: TMenuItem;
|
|
RToggleBreakPoint2: TMenuItem;
|
|
RDeleteBreakPoints2: TMenuItem;
|
|
N24: TMenuItem;
|
|
VWordWrap: TMenuItem;
|
|
N25: TMenuItem;
|
|
ESelectAllFindMatches: TMenuItem;
|
|
EToggleLinesComment: TMenuItem;
|
|
EBraceMatch: TMenuItem;
|
|
EFoldLine: TMenuItem;
|
|
EUnfoldLine: TMenuItem;
|
|
EFindRegEx: TMenuItem;
|
|
UpdatePanel: TPanel;
|
|
UpdateLinkLabel: TLinkLabel;
|
|
UpdatePanelClosePaintBox: TPaintBox;
|
|
UpdatePanelDonateImage: TImage;
|
|
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
|
|
procedure FExitClick(Sender: TObject);
|
|
procedure FOpenMainFileClick(Sender: TObject);
|
|
procedure EUndoClick(Sender: TObject);
|
|
procedure EMenuClick(Sender: TObject);
|
|
procedure ECutClick(Sender: TObject);
|
|
procedure ECopyClick(Sender: TObject);
|
|
procedure EPasteClick(Sender: TObject);
|
|
procedure EDeleteClick(Sender: TObject);
|
|
procedure FSaveClick(Sender: TObject);
|
|
procedure ESelectAllClick(Sender: TObject);
|
|
procedure FNewMainFileClick(Sender: TObject);
|
|
procedure FNewMainFileUserWizardClick(Sender: TObject);
|
|
procedure HDocClick(Sender: TObject);
|
|
procedure BCompileClick(Sender: TObject);
|
|
procedure FMenuClick(Sender: TObject);
|
|
procedure FMRUClick(Sender: TObject);
|
|
procedure VCompilerOutputClick(Sender: TObject);
|
|
procedure HAboutClick(Sender: TObject);
|
|
procedure EFindClick(Sender: TObject);
|
|
procedure FindDialogFind(Sender: TObject);
|
|
procedure EReplaceClick(Sender: TObject);
|
|
procedure ReplaceDialogReplace(Sender: TObject);
|
|
procedure EFindNextOrPreviousClick(Sender: TObject);
|
|
procedure SplitPanelMouseMove(Sender: TObject; Shift: TShiftState; X,
|
|
Y: Integer);
|
|
procedure VMenuClick(Sender: TObject);
|
|
procedure HWebsiteClick(Sender: TObject);
|
|
procedure VToolbarClick(Sender: TObject);
|
|
procedure TOptionsClick(Sender: TObject);
|
|
procedure HFaqClick(Sender: TObject);
|
|
procedure HISPPDocClick(Sender: TObject);
|
|
procedure VStatusBarClick(Sender: TObject);
|
|
procedure ERedoClick(Sender: TObject);
|
|
procedure StatusBarResize(Sender: TObject);
|
|
procedure RStepIntoClick(Sender: TObject);
|
|
procedure RStepOverClick(Sender: TObject);
|
|
procedure RRunToCursorClick(Sender: TObject);
|
|
procedure RRunClick(Sender: TObject);
|
|
procedure REvaluateClick(Sender: TObject);
|
|
procedure CheckIfRunningTimerTimer(Sender: TObject);
|
|
procedure RPauseClick(Sender: TObject);
|
|
procedure RParametersClick(Sender: TObject);
|
|
procedure POutputListCopyClick(Sender: TObject);
|
|
procedure BStopCompileClick(Sender: TObject);
|
|
procedure EGotoClick(Sender: TObject);
|
|
procedure RTerminateClick(Sender: TObject);
|
|
procedure BMenuClick(Sender: TObject);
|
|
procedure BLowPriorityClick(Sender: TObject);
|
|
procedure StatusBarDrawPanel(StatusBar: TStatusBar;
|
|
Panel: TStatusPanel; const Rect: TRect);
|
|
procedure HDonateClick(Sender: TObject);
|
|
procedure RTargetClick(Sender: TObject);
|
|
procedure DebugOutputListDrawItem(Control: TWinControl; Index: Integer;
|
|
Rect: TRect; State: TOwnerDrawState);
|
|
procedure OutputTabSetClick(Sender: TObject);
|
|
procedure VHideClick(Sender: TObject);
|
|
procedure VDebugOutputClick(Sender: TObject);
|
|
procedure FormResize(Sender: TObject);
|
|
procedure TAddRemoveProgramsClick(Sender: TObject);
|
|
procedure RToggleBreakPointClick(Sender: TObject);
|
|
procedure RDeleteBreakPointsClick(Sender: TObject);
|
|
procedure HWhatsNewClick(Sender: TObject);
|
|
procedure TGenerateGUIDClick(Sender: TObject);
|
|
procedure TSignToolsClick(Sender: TObject);
|
|
procedure HExamplesClick(Sender: TObject);
|
|
procedure BOpenOutputFolderClick(Sender: TObject);
|
|
procedure FormKeyDown(Sender: TObject; var Key: Word;
|
|
Shift: TShiftState);
|
|
procedure VZoomInClick(Sender: TObject);
|
|
procedure VZoomOutClick(Sender: TObject);
|
|
procedure VZoomResetClick(Sender: TObject);
|
|
procedure FSaveEncodingItemClick(Sender: TObject);
|
|
procedure CompilerOutputListDrawItem(Control: TWinControl; Index: Integer;
|
|
Rect: TRect; State: TOwnerDrawState);
|
|
procedure FormAfterMonitorDpiChanged(Sender: TObject; OldDPI,
|
|
NewDPI: Integer);
|
|
procedure POutputListSelectAllClick(Sender: TObject);
|
|
procedure DebugCallStackListDrawItem(Control: TWinControl; Index: Integer; Rect: TRect;
|
|
State: TOwnerDrawState);
|
|
procedure VDebugCallStackClick(Sender: TObject);
|
|
procedure HMailingListClick(Sender: TObject);
|
|
procedure TMsgBoxDesignerClick(Sender: TObject);
|
|
procedure TRegistryDesignerClick(Sender: TObject);
|
|
procedure MemosTabSetClick(Sender: TObject);
|
|
procedure FSaveAllClick(Sender: TObject);
|
|
procedure RStepOutClick(Sender: TObject);
|
|
procedure TMenuClick(Sender: TObject);
|
|
procedure VNextTabClick(Sender: TObject);
|
|
procedure VPreviousTabClick(Sender: TObject);
|
|
procedure HShortcutsDocClick(Sender: TObject);
|
|
procedure HRegExDocClick(Sender: TObject);
|
|
procedure VFindResultsClick(Sender: TObject);
|
|
procedure EFindInFilesClick(Sender: TObject);
|
|
procedure FindInFilesDialogFind(Sender: TObject);
|
|
procedure FindResultsListDrawItem(Control: TWinControl; Index: Integer; Rect: TRect;
|
|
State: TOwnerDrawState);
|
|
procedure FindResultsListDblClick(Sender: TObject);
|
|
procedure FPrintClick(Sender: TObject);
|
|
procedure TFilesDesignerClick(Sender: TObject);
|
|
procedure VCloseCurrentTabClick(Sender: TObject);
|
|
procedure VReopenTabsClick(Sender: TObject);
|
|
procedure MemosTabSetPopupMenuClick(Sender: TObject);
|
|
procedure MemosTabSetOnCloseButtonClick(Sender: TObject; Index: Integer);
|
|
procedure StatusBarClick(Sender: TObject);
|
|
procedure SimpleMenuClick(Sender: TObject);
|
|
procedure OutputListKeyDown(Sender: TObject; var Key: Word;
|
|
Shift: TShiftState);
|
|
procedure RMenuClick(Sender: TObject);
|
|
procedure BackNavButtonClick(Sender: TObject);
|
|
procedure ForwardNavButtonClick(Sender: TObject);
|
|
procedure NavPopupMenuClick(Sender: TObject);
|
|
procedure ESelectNextOccurrenceClick(Sender: TObject);
|
|
procedure ESelectAllOccurrencesClick(Sender: TObject);
|
|
procedure BreakPointsPopupMenuClick(Sender: TObject);
|
|
procedure FClearRecentClick(Sender: TObject);
|
|
procedure VWordWrapClick(Sender: TObject);
|
|
procedure ESelectAllFindMatchesClick(Sender: TObject);
|
|
procedure EToggleLinesCommentClick(Sender: TObject);
|
|
procedure EBraceMatchClick(Sender: TObject);
|
|
procedure EFoldOrUnfoldLineClick(Sender: TObject);
|
|
procedure EFindRegExClick(Sender: TObject);
|
|
procedure UpdateLinkLabelLinkClick(Sender: TObject; const Link: string;
|
|
LinkType: TSysLinkType);
|
|
procedure UpdatePanelClosePaintBoxPaint(Sender: TObject);
|
|
procedure UpdatePanelClosePaintBoxClick(Sender: TObject);
|
|
procedure UpdatePanelDonateImageClick(Sender: TObject);
|
|
private
|
|
{ Private declarations }
|
|
FMemos: TList<TIDEScintEdit>; { FMemos[0] is the main memo and FMemos[1] the preprocessor output memo - also see MemosTabSet comment above }
|
|
FMainMemo: TIDEScintFileEdit; { Doesn't change }
|
|
FPreprocessorOutputMemo: TIDEScintEdit; { Doesn't change and is the only memo which isnt a TIDEScint*File*Edit}
|
|
FFileMemos: TList<TIDEScintFileEdit>; { All memos except FPreprocessorOutputMemo, including those without a tab }
|
|
FHiddenFiles: TStringList; { List of files which *do* use a memo but are hidden by the user and have no tab }
|
|
FActiveMemo: TIDEScintEdit; { Changes depending on user input }
|
|
FErrorMemo, FStepMemo: TIDEScintFileEdit; { These change depending on user input }
|
|
FMemosStyler: TInnoSetupStyler; { Single styler for all memos }
|
|
FCompilerVersion: PCompilerVersionInfo;
|
|
FMRUMainFilesMenuItems: array[0..MRUListMaxCount-1] of TMenuItem;
|
|
FMRUMainFilesList: TStringList;
|
|
FMRUParametersList: TStringList;
|
|
FOptions: record
|
|
ShowStartupForm: Boolean;
|
|
UseWizard: Boolean;
|
|
Autosave: Boolean;
|
|
MakeBackups: Boolean;
|
|
FullPathInTitleBar: Boolean;
|
|
UndoAfterSave: Boolean;
|
|
PauseOnDebuggerExceptions: Boolean;
|
|
RunAsDifferentUser: Boolean;
|
|
AutoAutoComplete: Boolean;
|
|
AutoCallTips: Boolean;
|
|
UseSyntaxHighlighting: Boolean;
|
|
ColorizeCompilerOutput: Boolean;
|
|
UnderlineErrors: Boolean;
|
|
HighlightWordAtCursorOccurrences: Boolean;
|
|
HighlightSelTextOccurrences: Boolean;
|
|
CursorPastEOL: Boolean;
|
|
TabWidth: Integer;
|
|
UseTabCharacter: Boolean;
|
|
ShowWhiteSpace: Boolean;
|
|
UseFolding: Boolean;
|
|
FindRegEx: Boolean;
|
|
WordWrap: Boolean;
|
|
AutoIndent: Boolean;
|
|
IndentationGuides: Boolean;
|
|
LowPriorityDuringCompile: Boolean;
|
|
GutterLineNumbers: Boolean;
|
|
KeyMappingType: TKeyMappingType;
|
|
MemoKeyMappingType: TIDEScintKeyMappingType;
|
|
ThemeType: TThemeType;
|
|
ShowPreprocessorOutput: Boolean;
|
|
OpenIncludedFiles: Boolean;
|
|
ShowCaretPosition: Boolean;
|
|
end;
|
|
FOptionsLoaded: Boolean;
|
|
FTheme: TTheme;
|
|
FSignTools: TStringList;
|
|
FFindResults: TFindResults;
|
|
FCompiling: Boolean;
|
|
FCompileWantAbort: Boolean;
|
|
FBecameIdle: Boolean;
|
|
FModifiedAnySinceLastCompile, FModifiedAnySinceLastCompileAndGo: Boolean;
|
|
FDebugEntries: PDebugEntryArray;
|
|
FDebugEntriesCount: Integer;
|
|
FVariableDebugEntries: PVariableDebugEntryArray;
|
|
FVariableDebugEntriesCount: Integer;
|
|
FCompiledCodeText: AnsiString;
|
|
FCompiledCodeDebugInfo: AnsiString;
|
|
FDebugClientWnd: HWND;
|
|
FProcessHandle, FDebugClientProcessHandle: THandle;
|
|
FDebugTarget: TDebugTarget;
|
|
FCompiledExe, FUninstExe, FTempDir: String;
|
|
FPreprocessorOutput: String;
|
|
FIncludedFiles: TIncludedFiles;
|
|
FDebugging: Boolean;
|
|
FStepMode: TStepMode;
|
|
FPaused, FPausedAtCodeLine: Boolean;
|
|
FRunToCursorPoint: TDebugEntry;
|
|
FReplyString: String;
|
|
FDebuggerException: String;
|
|
FRunParameters: String;
|
|
FLastFindOptions: TFindOptions;
|
|
FLastFindRegEx: Boolean;
|
|
FLastFindText: String;
|
|
FLastReplaceText: String;
|
|
FLastEvaluateConstantText: String;
|
|
FSavePriorityClass: DWORD;
|
|
FBuildAnimationFrame: Cardinal;
|
|
FLastAnimationTick: DWORD;
|
|
FProgress, FProgressMax: Cardinal;
|
|
FTaskbarProgressValue: Cardinal;
|
|
FProgressThemeData: HTHEME;
|
|
FMenuThemeData: HTHEME;
|
|
FToolbarThemeData: HTHEME;
|
|
FMenuDarkBackgroundBrush: TBrush;
|
|
FMenuDarkHotOrSelectedBrush: TBrush;
|
|
FDebugLogListTimestampsWidth: Integer;
|
|
FOnPendingSquiggly: Boolean;
|
|
FPendingSquigglyCaretPos: Integer;
|
|
FCallStackCount: Cardinal;
|
|
FDevMode, FDevNames: HGLOBAL;
|
|
FMenuImageList: TVirtualImageList;
|
|
FMenuBitmaps: TMenuBitmaps;
|
|
FMenuBitmapsSize: TSize;
|
|
FMenuBitmapsSourceImageCollection: TCustomImageCollection;
|
|
FSynchingZoom: Boolean;
|
|
FNavStacks: TIDEScintEditNavStacks;
|
|
FCurrentNavItem: TIDEScintEditNavItem;
|
|
FKeyMappedMenus: TKeyMappedMenus;
|
|
FBackNavButtonShortCut, FForwardNavButtonShortCut: TShortCut;
|
|
FBackNavButtonShortCut2, FForwardNavButtonShortCut2: TShortCut;
|
|
FIgnoreTabSetClick: Boolean;
|
|
FFirstTabSelectShortCut, FLastTabSelectShortCut: TShortCut;
|
|
FCompileShortCut2: TShortCut;
|
|
FCallTipState: TCallTipState;
|
|
FUpdatePanelMessages: TUpdatePanelMessages;
|
|
function AnyMemoHasBreakPoint: Boolean;
|
|
class procedure AppOnException(Sender: TObject; E: Exception);
|
|
procedure AppOnActivate(Sender: TObject);
|
|
class procedure AppOnGetActiveFormHandle(var AHandle: HWND);
|
|
procedure AppOnIdle(Sender: TObject; var Done: Boolean);
|
|
function AskToDetachDebugger: Boolean;
|
|
procedure BringToForeground;
|
|
procedure BuildAndSaveBreakPointLines(const AMemo: TIDEScintFileEdit);
|
|
procedure BuildAndSaveKnownIncludedAndHiddenFiles;
|
|
procedure CheckIfTerminated;
|
|
procedure ClearMRUMainFilesList;
|
|
procedure CloseTab(const TabIndex: Integer);
|
|
procedure CompileFile(AFilename: String; const ReadFromFile: Boolean);
|
|
procedure CompileIfNecessary;
|
|
function ConfirmCloseFile(const PromptToSave: Boolean): Boolean;
|
|
procedure DebuggingStopped(const WaitForTermination: Boolean);
|
|
procedure DebugLogMessage(const S: String);
|
|
procedure DebugShowCallStack(const CallStack: String; const CallStackCount: Cardinal);
|
|
function DestroyLineState(const AMemo: TIDEScintFileEdit): Boolean;
|
|
procedure DestroyDebugInfo;
|
|
procedure DetachDebugger;
|
|
function EvaluateConstant(const S: String; out Output: String): Integer;
|
|
function EvaluateVariableEntry(const DebugEntry: PVariableDebugEntry;
|
|
out Output: String): Integer;
|
|
procedure FindNext(const ReverseDirection: Boolean);
|
|
function FindSetupDirectiveValue(const DirectiveName,
|
|
DefaultValue: String): String; overload;
|
|
function FindSetupDirectiveValue(const DirectiveName: String;
|
|
DefaultValue: Boolean): Boolean; overload;
|
|
function FromCurrentPPI(const XY: Integer): Integer;
|
|
function GetBorderStyle: TFormBorderStyle;
|
|
procedure Go(AStepMode: TStepMode);
|
|
procedure HideError;
|
|
procedure InitializeFindText(Dlg: TFindDialog);
|
|
function InitializeFileMemo(const Memo: TIDEScintFileEdit; const PopupMenu: TPopupMenu): TIDEScintFileEdit;
|
|
function InitializeMainMemo(const Memo: TIDEScintFileEdit; const PopupMenu: TPopupMenu): TIDEScintFileEdit;
|
|
function InitializeMemoBase(const Memo: TIDEScintEdit; const PopupMenu: TPopupMenu): TIDEScintEdit;
|
|
function InitializeNonFileMemo(const Memo: TIDEScintEdit; const PopupMenu: TPopupMenu): TIDEScintEdit;
|
|
function InitiateAutoCompleteOrCallTipAllowedAtPos(const AMemo: TIDEScintEdit;
|
|
const WordStartLinePos, PositionBeforeWordStartPos: Integer): Boolean;
|
|
procedure InitiateAutoComplete(const Key: AnsiChar);
|
|
procedure UpdateCallTipFunctionDefinition(const Pos: Integer = -1);
|
|
procedure InitiateCallTip(const Key: AnsiChar);
|
|
procedure ContinueCallTip;
|
|
procedure InvalidateStatusPanel(const Index: Integer);
|
|
procedure LoadBreakPointLinesAndUpdateLineMarkers(const AMemo: TIDEScintFileEdit);
|
|
procedure LoadKnownIncludedAndHiddenFilesAndUpdateMemos(const AFilename: String);
|
|
procedure MemoCallTipArrowClick(Sender: TObject; const Up: Boolean);
|
|
procedure MemoChange(Sender: TObject; const Info: TScintEditChangeInfo);
|
|
procedure MemoCharAdded(Sender: TObject; Ch: AnsiChar);
|
|
procedure MainMemoDropFiles(Sender: TObject; X, Y: Integer; AFiles: TStrings);
|
|
procedure MemoHintShow(Sender: TObject; var Info: TScintHintInfo);
|
|
procedure MemoKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
|
|
procedure MemoKeyPress(Sender: TObject; var Key: Char);
|
|
procedure MemoLinesDeleted(Memo: TIDEScintFileEdit; FirstLine, Count, FirstAffectedLine: Integer);
|
|
procedure MemoLinesInserted(Memo: TIDEScintFileEdit; FirstLine, Count: integer);
|
|
procedure MemoMarginClick(Sender: TObject; MarginNumber: Integer;
|
|
Line: Integer);
|
|
procedure MemoMarginRightClick(Sender: TObject; MarginNumber: Integer;
|
|
Line: Integer);
|
|
procedure MemoModifiedChange(Sender: TObject);
|
|
function MemoToTabIndex(const AMemo: TIDEScintEdit): Integer;
|
|
procedure MemoUpdateUI(Sender: TObject; Updated: TScintEditUpdates);
|
|
procedure MemoZoom(Sender: TObject);
|
|
function MultipleSelectionPasteFromClipboard(const AMemo: TIDESCintEdit): Boolean;
|
|
procedure UpdateReopenTabMenu(const Menu: TMenuItem);
|
|
procedure ModifyMRUMainFilesList(const AFilename: String; const AddNewItem: Boolean);
|
|
procedure ModifyMRUParametersList(const AParameter: String; const AddNewItem: Boolean);
|
|
procedure MoveCaretAndActivateMemo(AMemo: TIDEScintEdit; const LineNumberOrPosition: Integer;
|
|
const AlwaysResetColumnEvenIfOnRequestedLineAlready: Boolean;
|
|
const IsPosition: Boolean = False; const PositionVirtualSpace: Integer = 0);
|
|
procedure NavItemClick(Sender: TObject);
|
|
procedure NewMainFile;
|
|
procedure NewMainFileUsingWizard;
|
|
procedure OpenFile(AMemo: TIDEScintFileEdit; AFilename: String; const MainMemoAddToRecentDocs: Boolean);
|
|
procedure OpenMRUMainFile(const AFilename: String);
|
|
procedure ParseDebugInfo(DebugInfo: Pointer);
|
|
procedure ReadMRUMainFilesList;
|
|
procedure ReadMRUParametersList;
|
|
procedure RemoveMemoFromNav(const AMemo: TIDEScintEdit);
|
|
procedure RemoveMemoBadLinesFromNav(const AMemo: TIDEScintEdit);
|
|
procedure ReopenTabClick(Sender: TObject);
|
|
procedure ReopenTabOrTabs(const HiddenFileIndex: Integer; const Activate: Boolean);
|
|
procedure ResetAllMemosLineState;
|
|
procedure StartProcess;
|
|
function SaveFile(const AMemo: TIDEScintFileEdit; const SaveAs: Boolean): Boolean;
|
|
procedure SetBorderStyle(Value: TFormBorderStyle);
|
|
procedure SetErrorLine(const AMemo: TIDEScintFileEdit; const ALine: Integer);
|
|
procedure SetStatusPanelVisible(const AVisible: Boolean);
|
|
procedure SetStepLine(const AMemo: TIDEScintFileEdit; ALine: Integer);
|
|
procedure ShowOpenMainFileDialog(const Examples: Boolean);
|
|
procedure StatusMessage(const Kind: TStatusMessageKind; const S: String);
|
|
function StoreAndTestLastFindOptions(Sender: TObject): Boolean;
|
|
function TestLastFindOptions: Boolean;
|
|
procedure SyncEditorOptions;
|
|
function TabIndexToMemo(const ATabIndex, AMaxTabIndex: Integer): TIDEScintEdit;
|
|
function ToCurrentPPI(const XY: Integer): Integer;
|
|
procedure ToggleBreakPoint(Line: Integer);
|
|
procedure UpdateAllMemoLineMarkers(const AMemo: TIDEScintFileEdit);
|
|
procedure UpdateAllMemosLineMarkers;
|
|
procedure UpdateBevel1Visibility;
|
|
procedure UpdateCaption;
|
|
procedure UpdateCaretPosPanelAndBackNavStack;
|
|
procedure UpdateCompileStatusPanels(const AProgress, AProgressMax: Cardinal;
|
|
const ASecondsRemaining: Integer; const ABytesCompressedPerSecond: Cardinal);
|
|
procedure UpdateEditModePanel;
|
|
procedure UpdateFindRegExUI;
|
|
procedure UpdateFindResult(const FindResult: TFindResult; const ItemIndex: Integer;
|
|
const NewLine, NewLineStartPos: Integer);
|
|
procedure UpdatePreprocMemos;
|
|
procedure UpdateLineMarkers(const AMemo: TIDEScintFileEdit; const Line: Integer);
|
|
procedure UpdateImages;
|
|
procedure UpdateMarginsAndAutoCompleteIcons;
|
|
procedure UpdateMarginsAndSquigglyAndCaretWidths;
|
|
procedure UpdateMemosTabSetVisibility;
|
|
procedure UpdateMenuBitmapsIfNeeded;
|
|
procedure UpdateModifiedPanel;
|
|
procedure UpdateNavButtons;
|
|
procedure UpdateNewMainFileButtons;
|
|
procedure UpdateOccurrenceIndicators(const AMemo: TIDEScintEdit);
|
|
procedure UpdateOutputTabSetListsItemHeightAndDebugTimeWidth;
|
|
procedure UpdateRunMenu;
|
|
procedure UpdateSaveMenuItemAndButton;
|
|
procedure UpdateTargetMenu;
|
|
procedure UpdateUpdatePanel;
|
|
procedure UpdateKeyMapping;
|
|
procedure UpdateTheme;
|
|
procedure UpdateThemeData(const Open: Boolean);
|
|
procedure ApplyMenuBitmaps(const ParentMenuItem: TMenuItem);
|
|
procedure UpdateStatusPanelHeight(H: Integer);
|
|
procedure WMAppCommand(var Message: TMessage); message WM_APPCOMMAND;
|
|
procedure WMCopyData(var Message: TWMCopyData); message WM_COPYDATA;
|
|
procedure WMDebuggerHello(var Message: TMessage); message WM_Debugger_Hello;
|
|
procedure WMDebuggerGoodbye(var Message: TMessage); message WM_Debugger_Goodbye;
|
|
procedure WMDebuggerQueryVersion(var Message: TMessage); message WM_Debugger_QueryVersion;
|
|
procedure GetMemoAndDebugEntryFromMessage(Kind, Index: Integer; var Memo: TIDEScintFileEdit;
|
|
var DebugEntry: PDebugEntry);
|
|
procedure DebuggerStepped(var Message: TMessage; const Intermediate: Boolean);
|
|
procedure WMDebuggerStepped(var Message: TMessage); message WM_Debugger_Stepped;
|
|
procedure WMDebuggerSteppedIntermediate(var Message: TMessage); message WM_Debugger_SteppedIntermediate;
|
|
procedure WMDebuggerException(var Message: TMessage); message WM_Debugger_Exception;
|
|
procedure WMDebuggerSetForegroundWindow(var Message: TMessage); message WM_Debugger_SetForegroundWindow;
|
|
procedure WMDebuggerCallStackCount(var Message: TMessage); message WM_Debugger_CallStackCount;
|
|
procedure WMStartCommandLineCompile(var Message: TMessage); message WM_StartCommandLineCompile;
|
|
procedure WMStartCommandLineWizard(var Message: TMessage); message WM_StartCommandLineWizard;
|
|
procedure WMStartNormally(var Message: TMessage); message WM_StartNormally;
|
|
procedure WMDPIChanged(var Message: TMessage); message WM_DPICHANGED;
|
|
procedure WMSettingChange(var Message: TMessage); message WM_SETTINGCHANGE;
|
|
procedure WMSysColorChange(var Message: TMessage); message WM_SYSCOLORCHANGE;
|
|
procedure WMThemeChanged(var Message: TMessage); message WM_THEMECHANGED;
|
|
procedure WMUAHDrawMenu(var Message: TMessage); message WM_UAHDRAWMENU;
|
|
procedure WMUAHDrawMenuItem(var Message: TMessage); message WM_UAHDRAWMENUITEM;
|
|
procedure UAHDrawMenuBottomLine;
|
|
procedure WMNCActivate(var Message: TMessage); message WM_NCACTIVATE;
|
|
procedure WMNCPaint(var Message: TMessage); message WM_NCPAINT;
|
|
protected
|
|
procedure WndProc(var Message: TMessage); override;
|
|
public
|
|
{ Public declarations }
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
function IsShortCut(var Message: TWMKey): Boolean; override;
|
|
published
|
|
property BorderStyle: TFormBorderStyle read GetBorderStyle write SetBorderStyle;
|
|
end;
|
|
|
|
var
|
|
MainForm: TMainForm;
|
|
CommandLineFilename, CommandLineWizardName: String;
|
|
CommandLineCompile: Boolean;
|
|
CommandLineWizard: Boolean;
|
|
|
|
implementation
|
|
|
|
uses
|
|
ActiveX, Clipbrd, ShellApi, ShlObj, IniFiles, Registry, Consts, Types, UITypes,
|
|
Math, StrUtils, WideStrUtils, TypInfo,
|
|
PathFunc, Shared.CommonFunc.Vcl, Shared.CommonFunc, Shared.FileClass, IDE.Messages, NewUxTheme.TmSchema, BrowseFunc,
|
|
IDE.HtmlHelpFunc, TaskbarProgressFunc, IDE.ImagesModule,
|
|
{$IFDEF STATICCOMPILER} Compiler.Compile, {$ENDIF}
|
|
IDE.OptionsForm, IDE.StartupForm, IDE.Wizard.WizardForm, IDE.SignToolsForm,
|
|
Shared.ConfigIniFile, Shared.SignToolsFunc, IDE.InputQueryComboForm, IDE.MsgBoxDesignerForm,
|
|
IDE.FilesDesignerForm, IDE.RegistryDesignerForm, IDE.Wizard.WizardFormRegistryHelper,
|
|
Shared.CompilerInt;
|
|
|
|
{$R *.DFM}
|
|
|
|
const
|
|
{ Memos }
|
|
MaxMemos = 22; { Includes the main and preprocessor output memos }
|
|
FirstIncludedFilesMemoIndex = 1; { This is an index into FFileMemos }
|
|
|
|
{ Status bar panel indexes }
|
|
spCaretPos = 0;
|
|
spModified = 1;
|
|
spEditMode = 2;
|
|
spFindRegEx = 3;
|
|
spHiddenFilesCount = 4;
|
|
spCompileIcon = 5;
|
|
spCompileProgress = 6;
|
|
spExtraStatus = 7;
|
|
|
|
{ Output tab set indexes }
|
|
tiCompilerOutput = 0;
|
|
tiDebugOutput = 1;
|
|
tiDebugCallStack = 2;
|
|
tiFindResults = 3;
|
|
|
|
LineStateGrowAmount = 4000;
|
|
|
|
{ TUpdatePanelMessage }
|
|
|
|
constructor TUpdatePanelMessage.Create(const AMsg, AConfigIdent: String;
|
|
const AConfigValue: Integer; const AColor: TColor);
|
|
begin
|
|
Msg := AMsg;
|
|
ConfigIdent := AConfigIdent;
|
|
ConfigValue := AConfigValue;
|
|
Color := AColor;
|
|
end;
|
|
|
|
{ TMainFormPopupMenu }
|
|
|
|
type
|
|
TMainFormPopupMenu = class(TPopupMenu)
|
|
private
|
|
FParentMenuItem: TMenuItem;
|
|
public
|
|
constructor Create(const AOwner: TComponent; const ParentMenuItem: TMenuItem); reintroduce; virtual;
|
|
procedure Popup(X, Y: Integer); override;
|
|
end;
|
|
|
|
constructor TMainFormPopupMenu.Create(const AOwner: TComponent; const ParentMenuItem: TMenuItem);
|
|
begin
|
|
inherited Create(AOwner);
|
|
FParentMenuItem := ParentMenuItem;
|
|
end;
|
|
|
|
procedure TMainFormPopupMenu.Popup(X, Y: Integer);
|
|
var
|
|
Form: TMainForm;
|
|
begin
|
|
{ Show the existing main menu's submenu }
|
|
Form := Owner as TMainForm;
|
|
var OldVisible := FParentMenuItem.Visible; { See ApplyMenuBitmaps }
|
|
FParentMenuItem.Visible := True;
|
|
try
|
|
TrackPopupMenu(FParentMenuItem.Handle, TPM_RIGHTBUTTON, X, Y, 0, Form.Handle, nil);
|
|
finally
|
|
FParentMenuItem.Visible := OldVisible;
|
|
end;
|
|
end;
|
|
|
|
{ TMainForm }
|
|
|
|
function TMainForm.InitializeMemoBase(const Memo: TIDEScintEdit; const PopupMenu: TPopupMenu): TIDEScintEdit;
|
|
begin
|
|
Memo.Align := alClient;
|
|
Memo.Font.Name := GetPreferredMemoFont; { Default font only, see ReadConfig }
|
|
Memo.Font.Size := 10;
|
|
Memo.ShowHint := True;
|
|
Memo.Styler := FMemosStyler;
|
|
Memo.PopupMenu := PopupMenu;
|
|
Memo.OnCallTipArrowClick := MemoCallTipArrowClick;
|
|
Memo.OnChange := MemoChange;
|
|
Memo.OnCharAdded := MemoCharAdded;
|
|
Memo.OnHintShow := MemoHintShow;
|
|
Memo.OnKeyDown := MemoKeyDown;
|
|
Memo.OnKeyPress := MemoKeyPress;
|
|
Memo.OnMarginClick := MemoMarginClick;
|
|
Memo.OnMarginRightClick := MemoMarginRightClick;
|
|
Memo.OnModifiedChange := MemoModifiedChange;
|
|
Memo.OnUpdateUI := MemoUpdateUI;
|
|
Memo.OnZoom := MemoZoom;
|
|
Memo.Parent := BodyPanel;
|
|
Memo.SetAutoCompleteSeparators(InnoSetupStylerWordListSeparator, InnoSetupStylerWordListTypeSeparator);
|
|
Memo.SetWordChars(Memo.GetDefaultWordChars+'#{}[]');
|
|
Memo.Theme := FTheme;
|
|
Memo.Visible := False;
|
|
Result := Memo;
|
|
end;
|
|
|
|
function TMainForm.InitializeFileMemo(const Memo: TIDEScintFileEdit; const PopupMenu: TPopupMenu): TIDEScintFileEdit;
|
|
begin
|
|
InitializeMemoBase(Memo, PopupMenu);
|
|
Memo.ChangeHistory := schMarkers;
|
|
Memo.CompilerFileIndex := UnknownCompilerFileIndex;
|
|
Memo.ErrorLine := -1;
|
|
Memo.StepLine := -1;
|
|
Result := Memo;
|
|
end;
|
|
|
|
function TMainForm.InitializeMainMemo(const Memo: TIDEScintFileEdit; const PopupMenu: TPopupMenu): TIDEScintFileEdit;
|
|
begin
|
|
InitializeFileMemo(Memo, PopupMenu);
|
|
Memo.AcceptDroppedFiles := True;
|
|
Memo.CompilerFileIndex := -1;
|
|
Memo.OnDropFiles := MainMemoDropFiles;
|
|
Memo.Used := True;
|
|
Result := Memo;
|
|
end;
|
|
|
|
function TMainForm.InitializeNonFileMemo(const Memo: TIDEScintEdit; const PopupMenu: TPopupMenu): TIDEScintEdit;
|
|
begin
|
|
InitializeMemoBase(Memo, PopupMenu);
|
|
Memo.ReadOnly := True;
|
|
Result := Memo;
|
|
end;
|
|
|
|
constructor TMainForm.Create(AOwner: TComponent);
|
|
|
|
procedure CheckUpdatePanelMessage(const Ini: TConfigIniFile; const ConfigIdent: String;
|
|
const ConfigValueDefault, ConfigValueMinimum: Integer; const Msg: String; const Color: TColor);
|
|
begin
|
|
var ConfigValue := Ini.ReadInteger('UpdatePanel', ConfigIdent, ConfigValueDefault);
|
|
if ConfigValue < ConfigValueMinimum then
|
|
FUpdatePanelMessages.Add(TUpdatePanelMessage.Create(Msg, ConfigIdent, ConfigValueMinimum, Color));
|
|
end;
|
|
|
|
procedure ReadConfig;
|
|
var
|
|
Ini: TConfigIniFile;
|
|
WindowPlacement: TWindowPlacement;
|
|
I: Integer;
|
|
Memo: TIDEScintEdit;
|
|
begin
|
|
Ini := TConfigIniFile.Create;
|
|
try
|
|
{ Menu check boxes state }
|
|
ToolbarPanel.Visible := Ini.ReadBool('Options', 'ShowToolbar', True);
|
|
StatusBar.Visible := Ini.ReadBool('Options', 'ShowStatusBar', True);
|
|
FOptions.LowPriorityDuringCompile := Ini.ReadBool('Options', 'LowPriorityDuringCompile', False);
|
|
|
|
{ Configuration options }
|
|
FOptions.ShowStartupForm := Ini.ReadBool('Options', 'ShowStartupForm', True);
|
|
FOptions.UseWizard := Ini.ReadBool('Options', 'UseWizard', True);
|
|
FOptions.Autosave := Ini.ReadBool('Options', 'Autosave', False);
|
|
FOptions.MakeBackups := Ini.ReadBool('Options', 'MakeBackups', False);
|
|
FOptions.FullPathInTitleBar := Ini.ReadBool('Options', 'FullPathInTitleBar', False);
|
|
FOptions.UndoAfterSave := Ini.ReadBool('Options', 'UndoAfterSave', True);
|
|
FOptions.PauseOnDebuggerExceptions := Ini.ReadBool('Options', 'PauseOnDebuggerExceptions', True);
|
|
FOptions.RunAsDifferentUser := Ini.ReadBool('Options', 'RunAsDifferentUser', False);
|
|
FOptions.AutoAutoComplete := Ini.ReadBool('Options', 'AutoComplete', True);
|
|
FOptions.AutoCallTips := Ini.ReadBool('Options', 'AutoCallTips', True);
|
|
FOptions.UseSyntaxHighlighting := Ini.ReadBool('Options', 'UseSynHigh', True);
|
|
FOptions.ColorizeCompilerOutput := Ini.ReadBool('Options', 'ColorizeCompilerOutput', True);
|
|
FOptions.UnderlineErrors := Ini.ReadBool('Options', 'UnderlineErrors', True);
|
|
FOptions.HighlightWordAtCursorOccurrences := Ini.ReadBool('Options', 'HighlightWordAtCursorOccurrences', False);
|
|
FOptions.HighlightSelTextOccurrences := Ini.ReadBool('Options', 'HighlightSelTextOccurrences', True);
|
|
FOptions.CursorPastEOL := Ini.ReadBool('Options', 'EditorCursorPastEOL', False);
|
|
FOptions.TabWidth := Ini.ReadInteger('Options', 'TabWidth', 2);
|
|
FOptions.UseTabCharacter := Ini.ReadBool('Options', 'UseTabCharacter', False);
|
|
FOptions.ShowWhiteSpace := Ini.ReadBool('Options', 'ShowWhiteSpace', False);
|
|
FOptions.UseFolding := Ini.ReadBool('Options', 'UseFolding', True);
|
|
FOptions.FindRegEx := Ini.ReadBool('Options', 'FindRegEx', False);
|
|
FOptions.WordWrap := Ini.ReadBool('Options', 'WordWrap', False);
|
|
FOptions.AutoIndent := Ini.ReadBool('Options', 'AutoIndent', True);
|
|
FOptions.IndentationGuides := Ini.ReadBool('Options', 'IndentationGuides', True);
|
|
FOptions.GutterLineNumbers := Ini.ReadBool('Options', 'GutterLineNumbers', False);
|
|
FOptions.ShowPreprocessorOutput := Ini.ReadBool('Options', 'ShowPreprocessorOutput', True);
|
|
FOptions.OpenIncludedFiles := Ini.ReadBool('Options', 'OpenIncludedFiles', True);
|
|
I := Ini.ReadInteger('Options', 'KeyMappingType', Ord(GetDefaultKeyMappingType));
|
|
if (I >= 0) and (I <= Ord(High(TKeyMappingType))) then
|
|
FOptions.KeyMappingType := TKeyMappingType(I);
|
|
I := Ini.ReadInteger('Options', 'MemoKeyMappingType', Ord(GetDefaultMemoKeyMappingType));
|
|
if (I >= 0) and (I <= Ord(High(TIDEScintKeyMappingType))) then
|
|
FOptions.MemoKeyMappingType := TIDEScintKeyMappingType(I);
|
|
I := Ini.ReadInteger('Options', 'ThemeType', Ord(GetDefaultThemeType));
|
|
if (I >= 0) and (I <= Ord(High(TThemeType))) then
|
|
FOptions.ThemeType := TThemeType(I);
|
|
FMainMemo.Font.Name := Ini.ReadString('Options', 'EditorFontName', FMainMemo.Font.Name);
|
|
FMainMemo.Font.Size := Ini.ReadInteger('Options', 'EditorFontSize', 10);
|
|
FMainMemo.Font.Charset := Ini.ReadInteger('Options', 'EditorFontCharset', FMainMemo.Font.Charset);
|
|
FMainMemo.Zoom := Ini.ReadInteger('Options', 'Zoom', 0); { MemoZoom will zoom the other memos }
|
|
for Memo in FMemos do
|
|
if Memo <> FMainMemo then
|
|
Memo.Font := FMainMemo.Font;
|
|
|
|
{ UpdatePanel visibility }
|
|
CheckUpdatePanelMessage(Ini, 'KnownVersion', 0, Integer(FCompilerVersion.BinVersion),
|
|
'Your version of Inno Setup has been updated! <a id="hwhatsnew">See what''s new</a>.',
|
|
$ABE3AB); //MGreen with HSL lightness changed from 40% to 78%
|
|
CheckUpdatePanelMessage(Ini, 'VSCodeMemoKeyMap', 0, 1,
|
|
'VS Code-style editor shortcuts added! Use the <a id="toptions-vscode">Editor Keys option</a> in Options dialog.',
|
|
$FFD399); //MBlue with HSL lightness changed from 42% to 80%
|
|
UpdateUpdatePanel;
|
|
|
|
{ Debug options }
|
|
FOptions.ShowCaretPosition := Ini.ReadBool('Options', 'ShowCaretPosition', False);
|
|
if FOptions.ShowCaretPosition then begin
|
|
StatusBar.Panels[spCaretPos].Width := MulDiv(StatusBar.Panels[spCaretPos].Width, 7, 2);
|
|
StatusBar.Panels[spCaretPos].Alignment := taLeftJustify;
|
|
end;
|
|
|
|
SyncEditorOptions;
|
|
UpdateNewMainFileButtons;
|
|
UpdateKeyMapping;
|
|
UpdateTheme;
|
|
UpdateFindRegExUI;
|
|
|
|
{ Window state }
|
|
WindowPlacement.length := SizeOf(WindowPlacement);
|
|
GetWindowPlacement(Handle, @WindowPlacement);
|
|
WindowPlacement.showCmd := SW_HIDE; { the form isn't Visible yet }
|
|
WindowPlacement.rcNormalPosition.Left := Ini.ReadInteger('State',
|
|
'WindowLeft', WindowPlacement.rcNormalPosition.Left);
|
|
WindowPlacement.rcNormalPosition.Top := Ini.ReadInteger('State',
|
|
'WindowTop', WindowPlacement.rcNormalPosition.Top);
|
|
WindowPlacement.rcNormalPosition.Right := Ini.ReadInteger('State',
|
|
'WindowRight', WindowPlacement.rcNormalPosition.Left + Width);
|
|
WindowPlacement.rcNormalPosition.Bottom := Ini.ReadInteger('State',
|
|
'WindowBottom', WindowPlacement.rcNormalPosition.Top + Height);
|
|
SetWindowPlacement(Handle, @WindowPlacement);
|
|
{ Note: Must set WindowState *after* calling SetWindowPlacement, since
|
|
TCustomForm.WMSize resets WindowState }
|
|
if Ini.ReadBool('State', 'WindowMaximized', False) then
|
|
WindowState := wsMaximized;
|
|
{ Note: Don't call UpdateStatusPanelHeight here since it clips to the
|
|
current form height, which hasn't been finalized yet }
|
|
|
|
{ StatusPanel height }
|
|
StatusPanel.Height := ToCurrentPPI(Ini.ReadInteger('State', 'StatusPanelHeight',
|
|
(10 * FromCurrentPPI(DebugOutputList.ItemHeight) + 4) + FromCurrentPPI(OutputTabSet.Height)));
|
|
finally
|
|
Ini.Free;
|
|
end;
|
|
FOptionsLoaded := True;
|
|
end;
|
|
|
|
var
|
|
I: Integer;
|
|
NewItem: TMenuItem;
|
|
PopupMenu: TPopupMenu;
|
|
Memo: TIDEScintEdit;
|
|
begin
|
|
inherited;
|
|
|
|
{$IFNDEF STATICCOMPILER}
|
|
FCompilerVersion := ISDllGetVersion;
|
|
{$ELSE}
|
|
FCompilerVersion := ISGetVersion;
|
|
{$ENDIF}
|
|
|
|
FModifiedAnySinceLastCompile := True;
|
|
|
|
InitFormFont(Self);
|
|
|
|
{ For some reason, if AutoScroll=False is set on the form Delphi ignores the
|
|
'poDefault' Position setting }
|
|
AutoScroll := False;
|
|
|
|
{ Append the shortcut key text to the Edit items. Don't actually set the
|
|
ShortCut property because we don't want the key combinations having an
|
|
effect when Memo doesn't have the focus. }
|
|
SetFakeShortCut(EUndo, Ord('Z'), [ssCtrl]);
|
|
SetFakeShortCut(ERedo, Ord('Y'), [ssCtrl]);
|
|
SetFakeShortCut(ECut, Ord('X'), [ssCtrl]);
|
|
SetFakeShortCut(ECopy, Ord('C'), [ssCtrl]);
|
|
SetFakeShortCut(EPaste, Ord('V'), [ssCtrl]);
|
|
SetFakeShortCut(ESelectAll, Ord('A'), [ssCtrl]);
|
|
SetFakeShortCut(EDelete, VK_DELETE, []);
|
|
SetFakeShortCutText(VZoomIn, SmkcCtrl + 'Num +'); { These zoom shortcuts are handled by Scintilla and only support the active memo, unlike the menu items which work on all memos }
|
|
SetFakeShortCutText(VZoomOut, SmkcCtrl + 'Num -');
|
|
SetFakeShortCutText(VZoomReset, SmkcCtrl + 'Num /');
|
|
{ Use fake Esc shortcut for Stop Compile so it doesn't conflict with the
|
|
editor's autocompletion list }
|
|
SetFakeShortCut(BStopCompile, VK_ESCAPE, []);
|
|
{ Use fake Ctrl+F4 shortcut for VCloseCurrentTab2 because VCloseCurrentTab
|
|
already has the real one }
|
|
SetFakeShortCut(VCloseCurrentTab2, VK_F4, [ssCtrl]);
|
|
{ Use fake Ctrl+C and Ctrl+A shortcuts for OutputListPopupMenu's items so they
|
|
don't conflict with the editor which also uses fake shortcuts for these }
|
|
SetFakeShortCut(POutputListCopy, Ord('C'), [ssCtrl]);
|
|
SetFakeShortCut(POutputListSelectAll, Ord('A'), [ssCtrl]);
|
|
{ Set real shortcut on TOptions which can't be set at design time }
|
|
TOptions.ShortCut := ShortCut(VK_OEM_COMMA, [ssCtrl]);
|
|
|
|
PopupMenu := TMainFormPopupMenu.Create(Self, EMenu);
|
|
|
|
FMemosStyler := TInnoSetupStyler.Create(Self);
|
|
FMemosStyler.ISPPInstalled := ISPPInstalled;
|
|
|
|
FTheme := TTheme.Create;
|
|
InitFormThemeInit(FTheme);
|
|
|
|
ToolBarPanel.ParentBackground := False;
|
|
UpdatePanel.ParentBackground := False;
|
|
UpdatePanelDonateImage.Hint := RemoveAccelChar(HDonate.Caption);
|
|
|
|
UpdateImages;
|
|
|
|
FMemos := TList<TIDEScintEdit>.Create;
|
|
FMainMemo := InitializeMainMemo(TIDEScintFileEdit.Create(Self), PopupMenu);
|
|
FMemos.Add(FMainMemo);
|
|
FPreprocessorOutputMemo := InitializeNonFileMemo(TIDEScintEdit.Create(Self), PopupMenu);
|
|
FMemos.Add(FPreprocessorOutputMemo);
|
|
for I := FMemos.Count to MaxMemos-1 do
|
|
FMemos.Add(InitializeFileMemo(TIDEScintFileEdit.Create(Self), PopupMenu));
|
|
FFileMemos := TList<TIDEScintFileEdit>.Create;
|
|
for Memo in FMemos do
|
|
if Memo is TIDEScintFileEdit then
|
|
FFileMemos.Add(TIDEScintFileEdit(Memo));
|
|
FHiddenFiles := TStringList.Create(dupError, True, True);
|
|
FActiveMemo := FMainMemo;
|
|
FActiveMemo.Visible := True;
|
|
FErrorMemo := FMainMemo;
|
|
FStepMemo := FMainMemo;
|
|
UpdateMarginsAndSquigglyAndCaretWidths;
|
|
|
|
FMemosStyler.Theme := FTheme;
|
|
|
|
MemosTabSet.PopupMenu := TMainFormPopupMenu.Create(Self, MemosTabSetPopupMenu);
|
|
FFirstTabSelectShortCut := ShortCut(Ord('1'), [ssCtrl]);
|
|
FLastTabSelectShortCut := ShortCut(Ord('9'), [ssCtrl]);
|
|
|
|
FNavStacks := TIDEScintEditNavStacks.Create;
|
|
UpdateNavButtons;
|
|
FCurrentNavItem.Invalidate;
|
|
|
|
BackNavButton.Style := tbsDropDown;
|
|
BackNavButton.DropdownMenu := TMainFormPopupMenu.Create(Self, NavPopupMenu);
|
|
|
|
PopupMenu := TMainFormPopupMenu.Create(Self, OutputListPopupMenu);
|
|
|
|
CompilerOutputList.PopupMenu := PopupMenu;
|
|
DebugOutputList.PopupMenu := PopupMenu;
|
|
DebugCallStackList.PopupMenu := PopupMenu;
|
|
FindResultsList.PopupMenu := PopupMenu;
|
|
|
|
UpdateOutputTabSetListsItemHeightAndDebugTimeWidth;
|
|
|
|
Application.HintShortPause := 0;
|
|
Application.OnException := AppOnException;
|
|
Application.OnActivate := AppOnActivate;
|
|
Application.OnIdle := AppOnIdle;
|
|
|
|
FMRUMainFilesList := TStringList.Create;
|
|
for I := 0 to High(FMRUMainFilesMenuItems) do begin
|
|
NewItem := TMenuItem.Create(Self);
|
|
NewItem.OnClick := FMRUClick;
|
|
FRecent.Insert(I, NewItem);
|
|
FMRUMainFilesMenuItems[I] := NewItem;
|
|
end;
|
|
FMRUParametersList := TStringList.Create;
|
|
|
|
FSignTools := TStringList.Create;
|
|
FFindResults := TFindResults.Create;
|
|
|
|
FIncludedFiles := TIncludedFiles.Create;
|
|
UpdatePreprocMemos;
|
|
|
|
FDebugTarget := dtSetup;
|
|
UpdateTargetMenu;
|
|
|
|
UpdateCaption;
|
|
|
|
FMenuDarkBackgroundBrush := TBrush.Create;
|
|
FMenuDarkHotOrSelectedBrush := TBrush.Create;
|
|
|
|
LightToolbarVirtualImageList.AutoFill := True;
|
|
ThemedMarkersAndACVirtualImageList.AutoFill := True;
|
|
|
|
UpdateThemeData(True);
|
|
|
|
FMenuBitmaps := TMenuBitmaps.Create;
|
|
FMenuBitmapsSize.cx := 0;
|
|
FMenuBitmapsSize.cy := 0;
|
|
|
|
FKeyMappedMenus := TKeyMappedMenus.Create;
|
|
|
|
FCallTipState.MaxCallTips := 1; { Just like SciTE 5.50 }
|
|
|
|
FUpdatePanelMessages := TUpdatePanelMessages.Create;
|
|
|
|
if CommandLineCompile then begin
|
|
ReadSignTools(FSignTools);
|
|
PostMessage(Handle, WM_StartCommandLineCompile, 0, 0)
|
|
end else if CommandLineWizard then begin
|
|
{ Stop Delphi from showing the compiler form }
|
|
Application.ShowMainForm := False;
|
|
{ Show wizard form later }
|
|
PostMessage(Handle, WM_StartCommandLineWizard, 0, 0);
|
|
end else begin
|
|
ReadConfig; { Calls UpdateTheme }
|
|
ReadSignTools(FSignTools);
|
|
PostMessage(Handle, WM_StartNormally, 0, 0);
|
|
end;
|
|
end;
|
|
|
|
destructor TMainForm.Destroy;
|
|
|
|
procedure SaveConfig;
|
|
var
|
|
Ini: TConfigIniFile;
|
|
WindowPlacement: TWindowPlacement;
|
|
begin
|
|
Ini := TConfigIniFile.Create;
|
|
try
|
|
{ Theme state - can change without opening the options }
|
|
Ini.WriteInteger('Options', 'ThemeType', Ord(FOptions.ThemeType)); { Also see TOptionsClick }
|
|
|
|
{ Menu check boxes state }
|
|
Ini.WriteBool('Options', 'ShowToolbar', ToolbarPanel.Visible);
|
|
Ini.WriteBool('Options', 'ShowStatusBar', StatusBar.Visible);
|
|
Ini.WriteBool('Options', 'LowPriorityDuringCompile', FOptions.LowPriorityDuringCompile);
|
|
|
|
{ Window state }
|
|
WindowPlacement.length := SizeOf(WindowPlacement);
|
|
GetWindowPlacement(Handle, @WindowPlacement);
|
|
Ini.WriteInteger('State', 'WindowLeft', WindowPlacement.rcNormalPosition.Left);
|
|
Ini.WriteInteger('State', 'WindowTop', WindowPlacement.rcNormalPosition.Top);
|
|
Ini.WriteInteger('State', 'WindowRight', WindowPlacement.rcNormalPosition.Right);
|
|
Ini.WriteInteger('State', 'WindowBottom', WindowPlacement.rcNormalPosition.Bottom);
|
|
{ The GetWindowPlacement docs claim that "flags" is always zero.
|
|
Fortunately, that's wrong. WPF_RESTORETOMAXIMIZED is set when the
|
|
window is either currently maximized, or currently minimized from a
|
|
previous maximized state. }
|
|
Ini.WriteBool('State', 'WindowMaximized', WindowPlacement.flags and WPF_RESTORETOMAXIMIZED <> 0);
|
|
Ini.WriteInteger('State', 'StatusPanelHeight', FromCurrentPPI(StatusPanel.Height));
|
|
|
|
{ Zoom state }
|
|
Ini.WriteInteger('Options', 'Zoom', FMainMemo.Zoom); { Only saves the main memo's zoom }
|
|
finally
|
|
Ini.Free;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
UpdateThemeData(False);
|
|
|
|
Application.OnActivate := nil;
|
|
Application.OnIdle := nil;
|
|
|
|
if FOptionsLoaded and not (CommandLineCompile or CommandLineWizard) then
|
|
SaveConfig;
|
|
|
|
if FDevMode <> 0 then
|
|
GlobalFree(FDevMode);
|
|
if FDevNames <> 0 then
|
|
GlobalFree(FDevNames);
|
|
|
|
FUpdatePanelMessages.Free;
|
|
FNavStacks.Free;
|
|
FKeyMappedMenus.Free;
|
|
FMenuBitmaps.Free;
|
|
FMenuDarkBackgroundBrush.Free;
|
|
FMenuDarkHotOrSelectedBrush.Free;
|
|
FTheme.Free;
|
|
DestroyDebugInfo;
|
|
FIncludedFiles.Free;
|
|
FFindResults.Free;
|
|
FSignTools.Free;
|
|
FMRUParametersList.Free;
|
|
FMRUMainFilesList.Free;
|
|
FFileMemos.Free;
|
|
FHiddenFiles.Free;
|
|
FMemos.Free;
|
|
|
|
inherited;
|
|
end;
|
|
|
|
function TMainForm.GetBorderStyle: TFormBorderStyle;
|
|
begin
|
|
Result := inherited BorderStyle;
|
|
end;
|
|
|
|
procedure TMainForm.SetBorderStyle(Value: TFormBorderStyle);
|
|
begin
|
|
{ Hack: To stop the Delphi IDE from adding Explicit* properties to the .dfm
|
|
file every time the unit is saved, we set BorderStyle=bsNone on the form.
|
|
At run-time, ignore that setting so that BorderStyle stays at the default
|
|
value, bsSizeable.
|
|
It would be simpler to change BorderStyle from bsNone to bsSizeable in the
|
|
form's constructor, but it doesn't quite work: when a form's handle is
|
|
created while BorderStyle=bsNone, Position=poDefault behaves like
|
|
poDefaultPosOnly (see TCustomForm.CreateParams). }
|
|
|
|
if Value <> bsNone then
|
|
inherited BorderStyle := Value;
|
|
end;
|
|
|
|
class procedure TMainForm.AppOnException(Sender: TObject; E: Exception);
|
|
begin
|
|
AppMessageBox(PChar(AddPeriod(E.Message)), SCompilerFormCaption,
|
|
MB_OK or MB_ICONSTOP);
|
|
end;
|
|
|
|
class procedure TMainForm.AppOnGetActiveFormHandle(var AHandle: HWND);
|
|
begin
|
|
{ As of Delphi 11.3, the default code in TApplication.GetActiveFormHandle
|
|
(which runs after this handler) calls GetActiveWindow, and if that returns
|
|
0, it calls GetLastActivePopup(Application.Handle).
|
|
The problem is that when the application isn't in the foreground,
|
|
GetActiveWindow returns 0, and when MainFormOnTaskBar=True, the
|
|
GetLastActivePopup call normally just returns Application.Handle (since
|
|
there are no popups owned by the application window).
|
|
So if the application calls Application.MessageBox while it isn't in the
|
|
foreground, that message box will be owned by Application.Handle, not by
|
|
the last-active window as it should be. That can lead to the message box
|
|
falling behind the main form in z-order.
|
|
To rectify that, when no window is active and MainFormOnTaskBar=True, we
|
|
fall back to returning the handle of the main form's last active popup,
|
|
which is the window that would be activated if the main form's taskbar
|
|
button were clicked. (If Application.Handle is active, we treat that the
|
|
same as no active window because Application.Handle shouldn't be the owner
|
|
of any windows when MainFormOnTaskBar=True.)
|
|
If there is no assigned main form or if MainFormOnTaskBar=False, then we
|
|
fall back to the default handling. }
|
|
|
|
if Application.MainFormOnTaskBar then begin
|
|
AHandle := GetActiveWindow;
|
|
if ((AHandle = 0) or (AHandle = Application.Handle)) and
|
|
Assigned(Application.MainForm) and
|
|
Application.MainForm.HandleAllocated then
|
|
AHandle := GetLastActivePopup(Application.MainFormHandle);
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.FormAfterMonitorDpiChanged(Sender: TObject; OldDPI,
|
|
NewDPI: Integer);
|
|
begin
|
|
UpdateImages;
|
|
UpdateMarginsAndAutoCompleteIcons;
|
|
UpdateMarginsAndSquigglyAndCaretWidths;
|
|
UpdateOutputTabSetListsItemHeightAndDebugTimeWidth;
|
|
UpdateStatusPanelHeight(StatusPanel.Height);
|
|
end;
|
|
|
|
procedure TMainForm.FormCloseQuery(Sender: TObject;
|
|
var CanClose: Boolean);
|
|
begin
|
|
if IsWindowEnabled(Handle) then
|
|
CanClose := ConfirmCloseFile(True)
|
|
else
|
|
{ CloseQuery is also called by the VCL when a WM_QUERYENDSESSION message
|
|
is received. Don't display message box if a modal dialog is already
|
|
displayed. }
|
|
CanClose := False;
|
|
end;
|
|
|
|
procedure TMainForm.FormKeyDown(Sender: TObject; var Key: Word;
|
|
Shift: TShiftState);
|
|
begin
|
|
var AShortCut := ShortCut(Key, Shift);
|
|
if (AShortCut = VK_ESCAPE) and BStopCompile.Enabled then begin
|
|
Key := 0; { Intentionally only done when BStopCompile is enabled to allow the memo to process it instead }
|
|
BStopCompileClick(Self)
|
|
end else if (AShortCut = FBackNavButtonShortCut) or
|
|
((FBackNavButtonShortCut2 <> 0) and (AShortCut = FBackNavButtonShortCut2)) then begin
|
|
Key := 0;
|
|
if BackNavButton.Enabled then
|
|
BackNavButtonClick(Self);
|
|
end else if (AShortCut = FForwardNavButtonShortCut) or
|
|
((FForwardNavButtonShortCut2 <> 0) and (AShortCut = FForwardNavButtonShortCut2)) then begin
|
|
Key := 0;
|
|
if ForwardNavButton.Enabled then
|
|
ForwardNavButtonClick(Self);
|
|
end else if (AShortCut >= FFirstTabSelectShortCut) and (AShortCut <= FLastTabSelectShortCut) then begin
|
|
Key := 0;
|
|
if MemosTabSet.Visible then begin
|
|
var TabIndex := AShortCut - FFirstTabSelectShortCut;
|
|
if TabIndex < 8 then begin
|
|
if TabIndex < MemosTabSet.Tabs.Count then
|
|
MemosTabSet.TabIndex := TabIndex;
|
|
end else { Ctrl+9 = Select last tab }
|
|
MemosTabSet.TabIndex := MemosTabSet.Tabs.Count-1;
|
|
end;
|
|
end else if AShortCut = FCompileShortCut2 then begin
|
|
Key := 0;
|
|
if BCompile.Enabled then
|
|
BCompileClick(Self);
|
|
end else if (Key = VK_F6) and not (ssAlt in Shift) then begin
|
|
{ Toggle focus between the active memo and the active bottom pane }
|
|
Key := 0;
|
|
if ActiveControl <> FActiveMemo then
|
|
ActiveControl := FActiveMemo
|
|
else if StatusPanel.Visible then begin
|
|
case OutputTabSet.TabIndex of
|
|
tiCompilerOutput: ActiveControl := CompilerOutputList;
|
|
tiDebugOutput: ActiveControl := DebugOutputList;
|
|
tiDebugCallStack: ActiveControl := DebugCallStackList;
|
|
tiFindResults: ActiveControl := FindResultsList;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.MemoKeyDown(Sender: TObject; var Key: Word;
|
|
Shift: TShiftState);
|
|
|
|
procedure SimplifySelection(const AMemo: TIDEScintEdit);
|
|
begin
|
|
{ The built in Esc (SCI_CANCEL) simply drops all additional selections
|
|
and does not empty the main selection, It doesn't matter if Esc is
|
|
pressed once or twice. Implement our own behaviour, same as VSCode.
|
|
Also see https://github.com/microsoft/vscode/issues/118835. }
|
|
if AMemo.SelectionCount > 1 then
|
|
AMemo.RemoveAdditionalSelections
|
|
else if not AMemo.SelEmpty then
|
|
AMemo.SetEmptySelection;
|
|
AMemo.ScrollCaretIntoView;
|
|
end;
|
|
|
|
procedure AddCursor(const AMemo: TIDEScintEdit; const Up: Boolean);
|
|
begin
|
|
{ Does not try to keep the main selection. }
|
|
|
|
var Selections: TScintCaretAndAnchorList := nil;
|
|
var VirtualSpaces: TScintCaretAndAnchorList := nil;
|
|
try
|
|
Selections := TScintCaretAndAnchorList.Create;
|
|
VirtualSpaces := TScintCaretAndAnchorList.Create;
|
|
{ Get all the virtual spaces as well before we start doing modifications }
|
|
AMemo.GetSelections(Selections, VirtualSpaces);
|
|
for var I := 0 to Selections.Count-1 do begin
|
|
var Selection := Selections[I];
|
|
var LineCaret := AMemo.GetLineFromPosition(Selection.CaretPos);
|
|
var LineAnchor := AMemo.GetLineFromPosition(Selection.AnchorPos);
|
|
if LineCaret = LineAnchor then begin
|
|
{ Add selection with same caret and anchor offsets one line up or down. }
|
|
var OtherLine := LineCaret + IfThen(Up, -1, 1);;
|
|
if (OtherLine < 0) or (OtherLine >= AMemo.Lines.Count) then
|
|
Continue { Already at the top or bottom, can't add }
|
|
else begin
|
|
var LineStartPos := AMemo.GetPositionFromLine(LineCaret);
|
|
var CaretCharacterCount := AMemo.GetCharacterCount(LineStartPos, Selection.CaretPos) + VirtualSpaces[I].CaretPos;
|
|
var AnchorCharacterCount := AMemo.GetCharacterCount(LineStartPos, Selection.AnchorPos) + VirtualSpaces[I].AnchorPos;
|
|
var OtherLineStart := AMemo.GetPositionFromLine(OtherLine);
|
|
var MaxCharacterCount := AMemo.GetCharacterCount(OtherLineStart, AMemo.GetLineEndPosition(OtherLine));
|
|
var NewCaretCharacterCount := CaretCharacterCount;
|
|
var NewCaretVirtualSpace := 0;
|
|
var NewAnchorCharacterCount := AnchorCharacterCount;
|
|
var NewAnchorVirtualSpace := 0;
|
|
if NewCaretCharacterCount > MaxCharacterCount then begin
|
|
NewCaretVirtualSpace := NewCaretCharacterCount - MaxCharacterCount;
|
|
NewCaretCharacterCount := MaxCharacterCount;
|
|
end;
|
|
if NewAnchorCharacterCount > MaxCharacterCount then begin
|
|
NewAnchorVirtualSpace := NewAnchorCharacterCount - MaxCharacterCount;
|
|
NewAnchorCharacterCount := MaxCharacterCount;
|
|
end;
|
|
var NewSelection: TScintCaretAndAnchor;
|
|
NewSelection.CaretPos := AMemo.GetPositionRelative(OtherLineStart, NewCaretCharacterCount);
|
|
NewSelection.AnchorPos := AMemo.GetPositionRelative(OtherLineStart, NewAnchorCharacterCount);
|
|
{ AddSelection trims selections except for the main selection so
|
|
we need to check that ourselves unfortunately. Not doing a check
|
|
gives a problem when you AddCursor two times starting with an
|
|
empty single selection. The result will be 4 cursors, with 2 of
|
|
them in the same place. The check below fixes this but not
|
|
other cases when there's only partial overlap and Scintilla still
|
|
behaves weird. The check also doesn't handle virtual space which
|
|
is why we ultimately don't set virtual space: it leads to duplicate
|
|
selections. }
|
|
var MainSelection := AMemo.Selection;
|
|
if not NewSelection.Range.Within(AMemo.Selection) then begin
|
|
AMemo.AddSelection(NewSelection.CaretPos, NewSelection.AnchorPos);
|
|
{ if svsUserAccessible in FActiveMemo.VirtualSpaceOptions then begin
|
|
var MainSel := AMemo.MainSelection;
|
|
AMemo.SelectionCaretVirtualSpace[MainSel] := NewCaretVirtualSpace;
|
|
AMemo.SelectionAnchorVirtualSpace[MainSel] := NewAnchorVirtualSpace;
|
|
end; }
|
|
end;
|
|
end;
|
|
end else begin
|
|
{ Extend multiline selection up or down. This is not the same as
|
|
LineExtendUp/Down because those can shrink instead of extend. }
|
|
var CaretBeforeAnchor := Selection.CaretPos < Selection.AnchorPos;
|
|
var Down := not Up;
|
|
var LineStartOrEnd, StartOrEndPos, VirtualSpace: Integer;
|
|
{ Does it start (when going up) or end (when going down) at the caret or the anchor? }
|
|
if (Up and CaretBeforeAnchor) or (Down and not CaretBeforeAnchor) then begin
|
|
LineStartOrEnd := LineCaret;
|
|
StartOrEndPos := Selection.CaretPos;
|
|
VirtualSpace := VirtualSpaces[I].CaretPos;
|
|
end else begin
|
|
LineStartOrEnd := LineAnchor;
|
|
StartOrEndPos := Selection.AnchorPos;
|
|
VirtualSpace := VirtualSpaces[I].AnchorPos;
|
|
end;
|
|
var NewStartOrEndPos: Integer;
|
|
var NewVirtualSpace := 0;
|
|
{ Go up or down one line or to the start or end of the document }
|
|
if (Up and (LineStartOrEnd > 0)) or (Down and (LineStartOrEnd < AMemo.Lines.Count-1)) then begin
|
|
var CharacterCount := AMemo.GetCharacterCount(AMemo.GetPositionFromLine(LineStartOrEnd), StartOrEndPos) + VirtualSpace;
|
|
var OtherLine := LineStartOrEnd + IfThen(Up, -1, 1);
|
|
var OtherLineStart := AMemo.GetPositionFromLine(OtherLine);
|
|
var MaxCharacterCount := AMemo.GetCharacterCount(OtherLineStart, AMemo.GetLineEndPosition(OtherLine));
|
|
var NewCharacterCount := CharacterCount;
|
|
if NewCharacterCount > MaxCharacterCount then begin
|
|
NewVirtualSpace := NewCharacterCount - MaxCharacterCount;
|
|
NewCharacterCount := MaxCharacterCount;
|
|
end;
|
|
NewStartOrEndPos := AMemo.GetPositionRelative(OtherLineStart, NewCharacterCount);
|
|
end else
|
|
NewStartOrEndPos := IfThen(Up, 0, AMemo.GetPositionFromLine(AMemo.Lines.Count));
|
|
{ Move the caret or the anchor up or down to extend the selection }
|
|
if (Up and CaretBeforeAnchor) or (Down and not CaretBeforeAnchor) then begin
|
|
AMemo.SelectionCaretPosition[I] := NewStartOrEndPos;
|
|
if svsUserAccessible in FActiveMemo.VirtualSpaceOptions then
|
|
AMemo.SelectionCaretVirtualSpace[I] := NewVirtualSpace;
|
|
end else begin
|
|
AMemo.SelectionAnchorPosition[I] := NewStartOrEndPos;
|
|
if svsUserAccessible in FActiveMemo.VirtualSpaceOptions then
|
|
AMemo.SelectionAnchorVirtualSpace[I] := NewVirtualSpace;
|
|
end;
|
|
end;
|
|
end;
|
|
finally
|
|
VirtualSpaces.Free;
|
|
Selections.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure AddCursorsToLineEnds(const AMemo: TIDEScintEdit);
|
|
begin
|
|
{ Does not try to keep the main selection. Otherwise behaves the same as
|
|
observed in Visual Studio Code, see comments. }
|
|
|
|
var Selections: TScintCaretAndAnchorList := nil;
|
|
var VirtualSpaces: TScintCaretAndAnchorList := nil;
|
|
try
|
|
Selections := TScintCaretAndAnchorList.Create;
|
|
VirtualSpaces := TScintCaretAndAnchorList.Create;
|
|
AMemo.GetSelections(Selections, VirtualSpaces);
|
|
|
|
{ First remove all empty selections }
|
|
for var I := Selections.Count-1 downto 0 do begin
|
|
var Selection := Selections[I];
|
|
var VirtualSpace := VirtualSpaces[I];
|
|
if (Selection.CaretPos + VirtualSpace.CaretPos) =
|
|
(Selection.AnchorPos + VirtualSpace.AnchorPos) then begin
|
|
Selections.Delete(I);
|
|
VirtualSpaces.Delete(I);
|
|
end;
|
|
end;
|
|
|
|
{ If all selections were empty do nothing }
|
|
if Selections.Count = 0 then
|
|
Exit;
|
|
|
|
{ Handle non empty selections }
|
|
for var I := Selections.Count-1 downto 0 do begin
|
|
var Selection := Selections[I];
|
|
var Line1 := AMemo.GetLineFromPosition(Selection.CaretPos);
|
|
var Line2 := AMemo.GetLineFromPosition(Selection.AnchorPos);
|
|
var SelSingleLine := Line1 = Line2;
|
|
if SelSingleLine then begin
|
|
{ Single line selections are updated into empty selection at end of selection }
|
|
var VirtualSpace := VirtualSpaces[I];
|
|
if Selection.CaretPos + VirtualSpace.CaretPos > Selection.AnchorPos + VirtualSpace.AnchorPos then begin
|
|
Selection.AnchorPos := Selection.CaretPos;
|
|
VirtualSpace.AnchorPos := VirtualSpace.CaretPos;
|
|
end else begin
|
|
Selection.CaretPos := Selection.AnchorPos;
|
|
VirtualSpace.CaretPos := VirtualSpace.AnchorPos;
|
|
end;
|
|
Selections[I] := Selection;
|
|
VirtualSpaces[I] := VirtualSpace;
|
|
end else begin
|
|
{ Multiline selections are replaced by empty selections at each end of line }
|
|
if Line1 > Line2 then begin
|
|
var TmpLine := Line1;
|
|
Line1 := Line2;
|
|
Line2 := TmpLine;
|
|
end;
|
|
{ Ignore last line if the selection doesn't really select anything on that line }
|
|
if Selection.Range.EndPos = AMemo.GetPositionFromLine(Line2) then
|
|
Dec(Line2);
|
|
for var Line := Line1 to Line2 do begin
|
|
Selection.CaretPos := AMemo.GetLineEndPosition(Line);
|
|
Selection.AnchorPos := Selection.CaretPos;
|
|
Selections.Add(Selection);
|
|
VirtualSpaces.Add(TScintCaretAndAnchor.Create(0, 0));
|
|
end;
|
|
Selections.Delete(I);
|
|
VirtualSpaces.Delete(I);
|
|
end;
|
|
end;
|
|
|
|
{ Send updated selections to memo }
|
|
for var I := 0 to Selections.Count-1 do begin
|
|
var Selection := Selections[I];
|
|
var VirtualSpace := VirtualSpaces[I];
|
|
if I = 0 then
|
|
AMemo.SetSingleSelection(Selection.CaretPos, Selection.AnchorPos)
|
|
else
|
|
AMemo.AddSelection(Selection.CaretPos, Selection.AnchorPos);
|
|
AMemo.SelectionCaretVirtualSpace[I] := VirtualSpaces[I].CaretPos;
|
|
AMemo.SelectionAnchorVirtualSpace[I] := VirtualSpaces[I].AnchorPos;
|
|
end;
|
|
finally
|
|
VirtualSpaces.Free;
|
|
Selections.Free;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
if (Key in [VK_LEFT, VK_RIGHT, VK_UP, VK_DOWN, VK_HOME, VK_END]) then begin
|
|
var Memo := Sender as TIDEScintEdit;
|
|
{ Make sure we don't break the special rectangular select shortcuts }
|
|
if Shift * [ssShift, ssAlt, ssCtrl] <> Memo.GetRectExtendShiftState(True) then begin
|
|
if Memo.SelectionMode in [ssmRectangular, ssmThinRectangular] then begin
|
|
{ Allow left/right/etc. navigation with rectangular selection, see
|
|
https://sourceforge.net/p/scintilla/feature-requests/1275/ and
|
|
https://sourceforge.net/p/scintilla/bugs/2412/#cb37
|
|
Notepad++ calls this "Enable Column Selection to Multi-editing" which
|
|
is on by default and in VSCode and VS it's also on by default. }
|
|
Memo.SelectionMode := ssmStream;
|
|
end;
|
|
end;
|
|
{ Key is not cleared to allow Scintilla to do the actual handling }
|
|
end;
|
|
|
|
if Key = VK_F1 then begin
|
|
Key := 0;
|
|
var HelpFile := GetHelpFile;
|
|
if Assigned(HtmlHelp) then begin
|
|
HtmlHelp(GetDesktopWindow, PChar(HelpFile), HH_DISPLAY_TOPIC, 0);
|
|
var S := FActiveMemo.WordAtCaret;
|
|
if S <> '' then begin
|
|
var KLink: THH_AKLINK;
|
|
FillChar(KLink, SizeOf(KLink), 0);
|
|
KLink.cbStruct := SizeOf(KLink);
|
|
KLink.pszKeywords := PChar(S);
|
|
KLink.fIndexOnFail := True;
|
|
HtmlHelp(GetDesktopWindow, PChar(HelpFile), HH_KEYWORD_LOOKUP, DWORD(@KLink));
|
|
end;
|
|
end;
|
|
end else if ((Key = Ord('V')) or (Key = VK_INSERT)) and (Shift * [ssShift, ssAlt, ssCtrl] = [ssCtrl]) then begin
|
|
if FActiveMemo.CanPaste then
|
|
if MultipleSelectionPasteFromClipboard(FActiveMemo) then
|
|
Key := 0;
|
|
end else if (Key = VK_SPACE) and (Shift * [ssShift, ssAlt, ssCtrl] = [ssShift, ssCtrl]) then begin
|
|
Key := 0;
|
|
{ Based on SciTE 5.50's SciTEBase::MenuCommand IDM_SHOWCALLTIP }
|
|
if FActiveMemo.CallTipActive then begin
|
|
FCallTipState.CurrentCallTip := IfThen(FCallTipState.CurrentCallTip + 1 = FCallTipState.MaxCallTips, 0, FCallTipState.CurrentCallTip + 1);
|
|
UpdateCallTipFunctionDefinition;
|
|
end else begin
|
|
FCallTipState.BraceCount := 1; { Missing in SciTE, see https://sourceforge.net/p/scintilla/bugs/2446/ }
|
|
InitiateCallTip(#0);
|
|
end;
|
|
end else begin
|
|
var AShortCut := ShortCut(Key, Shift);
|
|
{ Check if the memo keymap wants us to handle the shortcut but first check
|
|
the menu keymap didn't already claim the same shortcut. Other shortcuts
|
|
(which are always same and not set by the menu keymap) are assumed to
|
|
never conflict. }
|
|
if not FKeyMappedMenus.ContainsKey(AShortCut) then begin
|
|
var ComplexCommand := FActiveMemo.GetComplexCommand(AShortCut);
|
|
if ComplexCommand <> ccNone then begin
|
|
if Key <> VK_ESCAPE then { Allow Scintilla to see Esc }
|
|
Key := 0;
|
|
case ComplexCommand of
|
|
ccSelectNextOccurrence:
|
|
ESelectNextOccurrenceClick(Self);
|
|
ccSelectAllOccurrences:
|
|
ESelectAllOccurrencesClick(Self);
|
|
ccSelectAllFindMatches:
|
|
ESelectAllFindMatchesClick(Self);
|
|
ccFoldLine:
|
|
EFoldOrUnfoldLineClick(EFoldLine);
|
|
ccUnfoldLine:
|
|
EFoldOrUnfoldLineClick(EUnfoldLine);
|
|
ccSimplifySelection:
|
|
SimplifySelection(FActiveMemo);
|
|
ccToggleLinesComment:
|
|
EToggleLinesCommentClick(Self); //GetCompexCommand already checked ReadOnly for us
|
|
ccAddCursorUp, ccAddCursorDown:
|
|
AddCursor(FActiveMemo, ComplexCommand = ccAddCursorUp);
|
|
ccBraceMatch:
|
|
EBraceMatchClick(Self);
|
|
ccAddCursorsToLineEnds:
|
|
AddCursorsToLineEnds(FActiveMemo);
|
|
else
|
|
raise Exception.Create('Unknown ComplexCommand');
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.MemoKeyPress(Sender: TObject; var Key: Char);
|
|
begin
|
|
if ((Key = #9) or (Key = ' ')) and (GetKeyState(VK_CONTROL) < 0) then begin
|
|
{ About #9, as Wikipedia explains: "The most known and common tab is a
|
|
horizontal tabulation <..> and may be referred to as Ctrl+I." Ctrl+I is
|
|
(just like in Visual Studio Code) our alternative code completion character
|
|
because Ctrl+Space is used by the Chinese IME and Alt+Right is used for the
|
|
forward button. So that's why we handle #9 here. Doesn't mean Ctrl+Tab
|
|
doesn't work: it doesnt trigger KeyPress, even if it wasn't a menu
|
|
shortcut for Next Tab (which it is). }
|
|
InitiateAutoComplete(#0);
|
|
Key := #0;
|
|
end else if (Key <= #31) or (Key = #127) then begin
|
|
{ Prevent "control characters" from being entered in text. Don't need to be
|
|
concerned about #9 or #10 or #13 etc here. Based on Notepad++'s WM_CHAR
|
|
handling in ScintillaEditView.cpp.
|
|
Also don't need to be concerned about shortcuts like Ctrl+Shift+- which
|
|
equals #31. }
|
|
Key := #0
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.FormResize(Sender: TObject);
|
|
begin
|
|
{ Make sure the status panel's height is decreased if necessary in response
|
|
to the form's height decreasing }
|
|
if StatusPanel.Visible then
|
|
UpdateStatusPanelHeight(StatusPanel.Height);
|
|
end;
|
|
|
|
procedure TMainForm.WndProc(var Message: TMessage);
|
|
begin
|
|
{ Without this, the status bar's owner drawn panels sometimes get corrupted and show
|
|
menu items instead. See:
|
|
http://groups.google.com/group/borland.public.delphi.vcl.components.using/browse_thread/thread/e4cb6c3444c70714 }
|
|
with Message do
|
|
case Msg of
|
|
WM_DRAWITEM:
|
|
with PDrawItemStruct(Message.LParam)^ do
|
|
if (CtlType = ODT_MENU) and not IsMenu(hwndItem) then
|
|
CtlType := ODT_STATIC;
|
|
end;
|
|
inherited
|
|
end;
|
|
|
|
function TMainForm.IsShortCut(var Message: TWMKey): Boolean;
|
|
begin
|
|
{ Key messages are forwarded by the VCL to the main form for ShortCut
|
|
processing. In Delphi 5+, however, this happens even when a TFindDialog
|
|
is active, causing Ctrl+V/Esc/etc. to be intercepted by the main form.
|
|
Work around this by always returning False when not Active. }
|
|
if Active then
|
|
Result := inherited IsShortCut(Message)
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
procedure TMainForm.UpdateCaption;
|
|
var
|
|
NewCaption: String;
|
|
begin
|
|
if FMainMemo.Filename = '' then
|
|
NewCaption := GetFileTitle(FMainMemo.Filename)
|
|
else begin
|
|
if FOptions.FullPathInTitleBar then
|
|
NewCaption := FMainMemo.Filename
|
|
else
|
|
NewCaption := GetDisplayFilename(FMainMemo.Filename);
|
|
end;
|
|
NewCaption := NewCaption + ' - ' + SCompilerFormCaption + ' ' +
|
|
String(FCompilerVersion.Version);
|
|
if FCompiling then
|
|
NewCaption := NewCaption + ' [Compiling]'
|
|
else if FDebugging then begin
|
|
if not FPaused then
|
|
NewCaption := NewCaption + ' [Running]'
|
|
else
|
|
NewCaption := NewCaption + ' [Paused]';
|
|
end;
|
|
Caption := NewCaption;
|
|
if not CommandLineWizard then
|
|
Application.Title := NewCaption;
|
|
end;
|
|
|
|
procedure TMainForm.UpdateNewMainFileButtons;
|
|
begin
|
|
if FOptions.UseWizard then begin
|
|
FNewMainFile.Caption := '&New...';
|
|
FNewMainFile.OnClick := FNewMainFileUserWizardClick;
|
|
NewMainFileButton.OnClick := FNewMainFileUserWizardClick;
|
|
end else begin
|
|
FNewMainFile.Caption := '&New';
|
|
FNewMainFile.OnClick := FNewMainFileClick;
|
|
NewMainFileButton.OnClick := FNewMainFileClick;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.NewMainFile;
|
|
var
|
|
Memo: TIDEScintFileEdit;
|
|
begin
|
|
HideError;
|
|
FUninstExe := '';
|
|
if FDebugTarget <> dtSetup then begin
|
|
FDebugTarget := dtSetup;
|
|
UpdateTargetMenu;
|
|
end;
|
|
FHiddenFiles.Clear;
|
|
InvalidateStatusPanel(spHiddenFilesCount);
|
|
for Memo in FFileMemos do
|
|
if Memo.Used then
|
|
Memo.BreakPoints.Clear;
|
|
DestroyDebugInfo;
|
|
|
|
FMainMemo.Filename := '';
|
|
UpdateCaption;
|
|
FMainMemo.SaveEncoding := seUTF8WithoutBOM;
|
|
FMainMemo.Lines.Clear;
|
|
FModifiedAnySinceLastCompile := True;
|
|
FPreprocessorOutput := '';
|
|
FIncludedFiles.Clear;
|
|
UpdatePreprocMemos;
|
|
FMainMemo.ClearUndo;
|
|
|
|
FNavStacks.Clear;
|
|
UpdateNavButtons;
|
|
FCurrentNavItem.Invalidate;
|
|
end;
|
|
|
|
{ Breakpoints are preserved on a per-file basis }
|
|
procedure TMainForm.LoadBreakPointLinesAndUpdateLineMarkers(const AMemo: TIDEScintFileEdit);
|
|
begin
|
|
if AMemo.BreakPoints.Count <> 0 then
|
|
raise Exception.Create('AMemo.BreakPoints.Count <> 0'); { NewMainFile or OpenFile should have cleared these }
|
|
|
|
try
|
|
var HadSkippedBreakPoint := False;
|
|
var Strings := TStringList.Create;
|
|
try
|
|
LoadBreakPointLines(AMemo.FileName, Strings);
|
|
for var LineAsString in Strings do begin
|
|
var Line := LineAsString.ToInteger;
|
|
if Line < AMemo.Lines.Count then
|
|
AMemo.BreakPoints.Add(Line)
|
|
else
|
|
HadSkippedBreakPoint := True;
|
|
end;
|
|
finally
|
|
Strings.Free;
|
|
end;
|
|
for var Line in AMemo.BreakPoints do
|
|
UpdateLineMarkers(AMemo, Line);
|
|
|
|
{ If there were breakpoints beyond the end of file get rid of them so they
|
|
don't magically reappear on a reload of an externally edited and grown
|
|
file }
|
|
if HadSkippedBreakPoint then
|
|
BuildAndSaveBreakPointLines(AMemo);
|
|
except
|
|
{ Ignore any exceptions }
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.BuildAndSaveBreakPointLines(const AMemo: TIDEScintFileEdit);
|
|
begin
|
|
try
|
|
if AMemo.FileName <> '' then begin
|
|
var Strings := TStringList.Create;
|
|
try
|
|
for var Line in AMemo.BreakPoints do
|
|
Strings.Add(Line.ToString);
|
|
SaveBreakPointLines(AMemo.FileName, Strings);
|
|
finally
|
|
Strings.Free;
|
|
end;
|
|
end;
|
|
except
|
|
{ Handle exceptions locally; failure to save the breakpoint lines list should not be
|
|
a fatal error }
|
|
Application.HandleException(Self);
|
|
end;
|
|
end;
|
|
|
|
{ Known included and hidden files are preserved on a per-main-file basis }
|
|
procedure TMainForm.LoadKnownIncludedAndHiddenFilesAndUpdateMemos;
|
|
begin
|
|
if FIncludedFiles.Count <> 0 then
|
|
raise Exception.Create('FIncludedFiles.Count <> 0'); { NewMainFile should have cleared these }
|
|
|
|
try
|
|
if AFilename <> '' then begin
|
|
var Strings := TStringList.Create;
|
|
try
|
|
LoadKnownIncludedAndHiddenFiles(FMainMemo.FileName, Strings, FHiddenFiles);
|
|
if Strings.Count > 0 then begin
|
|
try
|
|
for var Filename in Strings do begin
|
|
var IncludedFile := TIncludedFile.Create;
|
|
IncludedFile.Filename := Filename;
|
|
IncludedFile.CompilerFileIndex := UnknownCompilerFileIndex;
|
|
IncludedFile.HasLastWriteTime := GetLastWriteTimeOfFile(IncludedFile.Filename,
|
|
@IncludedFile.LastWriteTime);
|
|
FIncludedFiles.Add(IncludedFile);
|
|
end;
|
|
finally
|
|
UpdatePreprocMemos;
|
|
end;
|
|
end;
|
|
finally
|
|
Strings.Free;
|
|
end;
|
|
end;
|
|
except
|
|
{ Ignore any exceptions }
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.BuildAndSaveKnownIncludedAndHiddenFiles;
|
|
begin
|
|
try
|
|
if FMainMemo.FileName <> '' then begin
|
|
var Strings := TStringList.Create;
|
|
try
|
|
for var IncludedFile in FIncludedFiles do
|
|
Strings.Add(IncludedFile.Filename);
|
|
SaveKnownIncludedAndHiddenFiles(FMainMemo.FileName, Strings, FHiddenFiles);
|
|
finally
|
|
Strings.Free;
|
|
end;
|
|
end;
|
|
except
|
|
{ Handle exceptions locally; failure to save the includes list should not be
|
|
a fatal error }
|
|
Application.HandleException(Self);
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.NewMainFileUsingWizard;
|
|
var
|
|
WizardForm: TWizardForm;
|
|
SaveEnabled: Boolean;
|
|
begin
|
|
WizardForm := TWizardForm.Create(Application);
|
|
try
|
|
SaveEnabled := Enabled;
|
|
if CommandLineWizard then begin
|
|
WizardForm.WizardName := CommandLineWizardName;
|
|
{ Must disable MainForm even though it isn't shown, otherwise
|
|
menu keyboard shortcuts (such as Ctrl+O) still work }
|
|
Enabled := False;
|
|
end;
|
|
try
|
|
if WizardForm.ShowModal <> mrOk then
|
|
Exit;
|
|
finally
|
|
Enabled := SaveEnabled;
|
|
end;
|
|
|
|
if CommandLineWizard then begin
|
|
SaveTextToFile(CommandLineFileName, WizardForm.ResultScript, seUTF8WithoutBOM);
|
|
end else begin
|
|
NewMainFile;
|
|
FMainMemo.Lines.Text := WizardForm.ResultScript;
|
|
FMainMemo.ClearUndo;
|
|
if WizardForm.Result = wrComplete then begin
|
|
FMainMemo.ForceModifiedState;
|
|
if MsgBox('Would you like to compile the new script now?', SCompilerFormCaption, mbConfirmation, MB_YESNO) = IDYES then
|
|
BCompileClick(Self);
|
|
end;
|
|
end;
|
|
finally
|
|
WizardForm.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.OpenFile(AMemo: TIDEScintFileEdit; AFilename: String;
|
|
const MainMemoAddToRecentDocs: Boolean);
|
|
|
|
function GetStreamSaveEncoding(const Stream: TStream): TSaveEncoding;
|
|
var
|
|
Buf: array[0..2] of Byte;
|
|
begin
|
|
Result := seAuto;
|
|
var StreamSize := Stream.Size;
|
|
var CappedSize: Integer;
|
|
if StreamSize > High(Integer) then
|
|
CappedSize := High(Integer)
|
|
else
|
|
CappedSize := Integer(StreamSize);
|
|
if (CappedSize >= SizeOf(Buf)) and (Stream.Read(Buf, SizeOf(Buf)) = SizeOf(Buf)) and
|
|
(Buf[0] = $EF) and (Buf[1] = $BB) and (Buf[2] = $BF) then
|
|
Result := seUTF8WithBOM
|
|
else begin
|
|
Stream.Seek(0, soFromBeginning);
|
|
var S: AnsiString;
|
|
SetLength(S, CappedSize);
|
|
SetLength(S, Stream.Read(S[1], CappedSize));
|
|
if DetectUTF8Encoding(S) in [etUSASCII, etUTF8] then
|
|
Result := seUTF8WithoutBOM;
|
|
end;
|
|
end;
|
|
|
|
function GetEncoding(const SaveEncoding: TSaveEncoding): TEncoding;
|
|
begin
|
|
if SaveEncoding in [seUTF8WithBOM, seUTF8WithoutBOM] then
|
|
Result := TEncoding.UTF8
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
var
|
|
Stream: TFileStream;
|
|
begin
|
|
AMemo.OpeningFile := True;
|
|
try
|
|
AFilename := PathExpand(AFilename);
|
|
var NameChange := PathCompare(AMemo.Filename, AFilename) <> 0;
|
|
|
|
Stream := TFileStream.Create(AFilename, fmOpenRead or fmShareDenyNone);
|
|
try
|
|
if AMemo = FMainMemo then
|
|
NewMainFile
|
|
else begin
|
|
AMemo.BreakPoints.Clear;
|
|
if DestroyLineState(AMemo) then
|
|
UpdateAllMemoLineMarkers(AMemo);
|
|
if NameChange then { Also see below the other case which needs to be done after load }
|
|
RemoveMemoFromNav(AMemo);
|
|
end;
|
|
GetFileTime(Stream.Handle, nil, nil, @AMemo.FileLastWriteTime);
|
|
AMemo.SaveEncoding := GetStreamSaveEncoding(Stream);
|
|
Stream.Seek(0, soFromBeginning);
|
|
AMemo.Lines.LoadFromStream(Stream, GetEncoding(AMemo.SaveEncoding));
|
|
if (AMemo <> FMainMemo) and not NameChange then
|
|
RemoveMemoBadLinesFromNav(AMemo);
|
|
finally
|
|
Stream.Free;
|
|
end;
|
|
AMemo.ClearUndo;
|
|
if AMemo = FMainMemo then begin
|
|
AMemo.Filename := AFilename;
|
|
UpdateCaption;
|
|
ModifyMRUMainFilesList(AFilename, True);
|
|
if MainMemoAddToRecentDocs then
|
|
AddFileToRecentDocs(AFilename);
|
|
LoadKnownIncludedAndHiddenFilesAndUpdateMemos(AFilename);
|
|
InvalidateStatusPanel(spHiddenFilesCount);
|
|
end;
|
|
LoadBreakPointLinesAndUpdateLineMarkers(AMemo);
|
|
finally
|
|
AMemo.OpeningFile := False;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.OpenMRUMainFile(const AFilename: String);
|
|
{ Same as OpenFile, but offers to remove the file from the MRU list if it
|
|
cannot be opened }
|
|
begin
|
|
try
|
|
OpenFile(FMainMemo, AFilename, True);
|
|
except
|
|
Application.HandleException(Self);
|
|
if MsgBoxFmt('There was an error opening the file. Remove it from the list?',
|
|
[AFilename], SCompilerFormCaption, mbError, MB_YESNO) = IDYES then begin
|
|
ModifyMRUMainFilesList(AFilename, False);
|
|
DeleteBreakPointLines(AFilename);
|
|
DeleteKnownIncludedAndHiddenFiles(AFilename);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TMainForm.SaveFile(const AMemo: TIDEScintFileEdit; const SaveAs: Boolean): Boolean;
|
|
|
|
procedure SaveMemoTo(const FN: String);
|
|
var
|
|
TempFN, BackupFN: String;
|
|
Buf: array[0..4095] of Char;
|
|
begin
|
|
{ Save to a temporary file; don't overwrite existing files in place. This
|
|
way, if the system crashes or the disk runs out of space during the save,
|
|
the existing file will still be intact. }
|
|
if GetTempFileName(PChar(PathExtractDir(FN)), 'iss', 0, Buf) = 0 then
|
|
raise Exception.CreateFmt('Error creating file (code %d). Could not save file',
|
|
[GetLastError]);
|
|
TempFN := Buf;
|
|
try
|
|
SaveTextToFile(TempFN, AMemo.Lines.Text, AMemo.SaveEncoding);
|
|
|
|
{ Back up existing file if needed }
|
|
if FOptions.MakeBackups and NewFileExists(FN) then begin
|
|
BackupFN := PathChangeExt(FN, '.~is');
|
|
DeleteFile(BackupFN);
|
|
if not RenameFile(FN, BackupFN) then
|
|
raise Exception.Create('Error creating backup file. Could not save file');
|
|
end;
|
|
|
|
{ Delete existing file }
|
|
if not DeleteFile(FN) and (GetLastError <> ERROR_FILE_NOT_FOUND) then
|
|
raise Exception.CreateFmt('Error removing existing file (code %d). Could not save file',
|
|
[GetLastError]);
|
|
except
|
|
DeleteFile(TempFN);
|
|
raise;
|
|
end;
|
|
{ Rename temporary file.
|
|
Note: This is outside the try..except because we already deleted the
|
|
existing file, and don't want the temp file also deleted in the unlikely
|
|
event that the rename fails. }
|
|
if not RenameFile(TempFN, FN) then
|
|
raise Exception.CreateFmt('Error renaming temporary file (code %d). Could not save file',
|
|
[GetLastError]);
|
|
GetLastWriteTimeOfFile(FN, @AMemo.FileLastWriteTime);
|
|
end;
|
|
|
|
var
|
|
FN: String;
|
|
begin
|
|
Result := False;
|
|
var OldName := AMemo.Filename;
|
|
if SaveAs or (AMemo.Filename = '') then begin
|
|
if AMemo <> FMainMemo then
|
|
raise Exception.Create('Internal error: AMemo <> FMainMemo');
|
|
FN := AMemo.Filename;
|
|
if not NewGetSaveFileName('', FN, '', SCompilerOpenFilter, 'iss', Handle) then Exit;
|
|
FN := PathExpand(FN);
|
|
SaveMemoTo(FN);
|
|
AMemo.Filename := FN;
|
|
UpdateCaption;
|
|
end else
|
|
SaveMemoTo(AMemo.Filename);
|
|
AMemo.SetSavePoint;
|
|
if not FOptions.UndoAfterSave then
|
|
AMemo.ClearUndo(False);
|
|
Result := True;
|
|
if AMemo = FMainMemo then begin
|
|
ModifyMRUMainFilesList(AMemo.Filename, True);
|
|
if PathCompare(AMemo.Filename, OldName) <> 0 then begin
|
|
if OldName <> '' then begin
|
|
DeleteBreakPointLines(OldName);
|
|
DeleteKnownIncludedAndHiddenFiles(OldName);
|
|
end;
|
|
BuildAndSaveBreakPointLines(AMemo);
|
|
BuildAndSaveKnownIncludedAndHiddenFiles;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TMainForm.ConfirmCloseFile(const PromptToSave: Boolean): Boolean;
|
|
|
|
function PromptToSaveMemo(const AMemo: TIDEScintFileEdit): Boolean;
|
|
var
|
|
FileTitle: String;
|
|
begin
|
|
Result := True;
|
|
if AMemo.Modified then begin
|
|
FileTitle := GetFileTitle(AMemo.Filename);
|
|
case MsgBox('The text in the ' + FileTitle + ' file has changed.'#13#10#13#10 +
|
|
'Do you want to save the changes?', SCompilerFormCaption, mbError,
|
|
MB_YESNOCANCEL) of
|
|
IDYES: Result := SaveFile(AMemo, False);
|
|
IDNO: ;
|
|
else
|
|
Result := False;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
Memo: TIDEScintFileEdit;
|
|
begin
|
|
if FCompiling then begin
|
|
MsgBox('Please stop the compile process before performing this command.',
|
|
SCompilerFormCaption, mbError, MB_OK);
|
|
Result := False;
|
|
Exit;
|
|
end;
|
|
if FDebugging and not AskToDetachDebugger then begin
|
|
Result := False;
|
|
Exit;
|
|
end;
|
|
Result := True;
|
|
if PromptToSave then begin
|
|
for Memo in FFileMemos do begin
|
|
if Memo.Used then begin
|
|
Result := PromptToSaveMemo(Memo);
|
|
if not Result then
|
|
Exit;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.ClearMRUMainFilesList;
|
|
begin
|
|
try
|
|
ClearMRUList(FMRUMainFilesList, 'ScriptFileHistoryNew');
|
|
except
|
|
{ Ignore any exceptions. }
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.ReadMRUMainFilesList;
|
|
begin
|
|
try
|
|
ReadMRUList(FMRUMainFilesList, 'ScriptFileHistoryNew', 'History');
|
|
except
|
|
{ Ignore any exceptions. }
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.ModifyMRUMainFilesList(const AFilename: String;
|
|
const AddNewItem: Boolean);
|
|
begin
|
|
{ Load most recent items first, just in case they've changed }
|
|
try
|
|
ReadMRUMainFilesList;
|
|
except
|
|
{ Ignore any exceptions. }
|
|
end;
|
|
try
|
|
ModifyMRUList(FMRUMainFilesList, 'ScriptFileHistoryNew', 'History', AFileName, AddNewItem, @PathCompare);
|
|
except
|
|
{ Handle exceptions locally; failure to save the MRU list should not be
|
|
a fatal error. }
|
|
Application.HandleException(Self);
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.ReadMRUParametersList;
|
|
begin
|
|
try
|
|
ReadMRUList(FMRUParametersList, 'ParametersHistory', 'History');
|
|
except
|
|
{ Ignore any exceptions. }
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.ModifyMRUParametersList(const AParameter: String;
|
|
const AddNewItem: Boolean);
|
|
begin
|
|
{ Load most recent items first, just in case they've changed }
|
|
try
|
|
ReadMRUParametersList;
|
|
except
|
|
{ Ignore any exceptions. }
|
|
end;
|
|
try
|
|
ModifyMRUList(FMRUParametersList, 'ParametersHistory', 'History', AParameter, AddNewItem, @CompareText);
|
|
except
|
|
{ Handle exceptions locally; failure to save the MRU list should not be
|
|
a fatal error. }
|
|
Application.HandleException(Self);
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.StatusMessage(const Kind: TStatusMessageKind; const S: String);
|
|
begin
|
|
AddLines(CompilerOutputList, S, TObject(Kind), False, alpNone, 0);
|
|
CompilerOutputList.Update;
|
|
end;
|
|
|
|
procedure TMainForm.DebugLogMessage(const S: String);
|
|
begin
|
|
AddLines(DebugOutputList, S, nil, True, alpTimestamp, FDebugLogListTimestampsWidth);
|
|
DebugOutputList.Update;
|
|
end;
|
|
|
|
procedure TMainForm.DebugShowCallStack(const CallStack: String; const CallStackCount: Cardinal);
|
|
begin
|
|
DebugCallStackList.Clear;
|
|
AddLines(DebugCallStackList, CallStack, nil, True, alpCountdown, FCallStackCount-1);
|
|
DebugCallStackList.Items.Insert(0, '*** [Code] Call Stack');
|
|
DebugCallStackList.Update;
|
|
end;
|
|
|
|
type
|
|
PAppData = ^TAppData;
|
|
TAppData = record
|
|
Form: TMainForm;
|
|
Filename: String;
|
|
Lines: TStringList;
|
|
CurLineNumber: Integer;
|
|
CurLine: String;
|
|
OutputExe: String;
|
|
DebugInfo: Pointer;
|
|
ErrorMsg: String;
|
|
ErrorFilename: String;
|
|
ErrorLine: Integer;
|
|
Aborted: Boolean;
|
|
end;
|
|
|
|
function CompilerCallbackProc(Code: Integer; var Data: TCompilerCallbackData;
|
|
AppData: Longint): Integer; stdcall;
|
|
|
|
procedure DecodeIncludedFilenames(P: PChar; const IncludedFiles: TIncludedFiles);
|
|
var
|
|
IncludedFile: TIncludedFile;
|
|
I: Integer;
|
|
begin
|
|
IncludedFiles.Clear;
|
|
if P = nil then
|
|
Exit;
|
|
I := 0;
|
|
while P^ <> #0 do begin
|
|
if not IsISPPBuiltins(P) then begin
|
|
IncludedFile := TIncludedFile.Create;
|
|
IncludedFile.Filename := GetCleanFileNameOfFile(P);
|
|
IncludedFile.CompilerFileIndex := I;
|
|
IncludedFile.HasLastWriteTime := GetLastWriteTimeOfFile(IncludedFile.Filename,
|
|
@IncludedFile.LastWriteTime);
|
|
IncludedFiles.Add(IncludedFile);
|
|
end;
|
|
Inc(P, StrLen(P) + 1);
|
|
Inc(I);
|
|
end;
|
|
end;
|
|
|
|
procedure CleanHiddenFiles(const IncludedFiles: TIncludedFiles; const HiddenFiles: TStringList);
|
|
var
|
|
HiddenFileIncluded: array of Boolean;
|
|
begin
|
|
if HiddenFiles.Count > 0 then begin
|
|
{ Clean previously hidden files which are no longer included }
|
|
if IncludedFiles.Count > 0 then begin
|
|
SetLength(HiddenFileIncluded, HiddenFiles.Count);
|
|
for var I := 0 to HiddenFiles.Count-1 do
|
|
HiddenFileIncluded[I] := False;
|
|
for var I := 0 to IncludedFiles.Count-1 do begin
|
|
var IncludedFile := IncludedFiles[I];
|
|
var HiddenFileIndex := HiddenFiles.IndexOf(IncludedFile.Filename);
|
|
if HiddenFileIndex <> -1 then
|
|
HiddenFileIncluded[HiddenFileIndex] := True;
|
|
end;
|
|
for var I := HiddenFiles.Count-1 downto 0 do
|
|
if not HiddenFileIncluded[I] then
|
|
HiddenFiles.Delete(I);
|
|
end else
|
|
HiddenFiles.Clear;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
Result := iscrSuccess;
|
|
with PAppData(AppData)^ do
|
|
case Code of
|
|
iscbReadScript:
|
|
begin
|
|
if Data.Reset then
|
|
CurLineNumber := 0;
|
|
if CurLineNumber < Lines.Count then begin
|
|
CurLine := Lines[CurLineNumber];
|
|
Data.LineRead := PChar(CurLine);
|
|
Inc(CurLineNumber);
|
|
end;
|
|
end;
|
|
iscbNotifyStatus:
|
|
if Data.Warning then
|
|
Form.StatusMessage(smkWarning, Data.StatusMsg)
|
|
else
|
|
Form.StatusMessage(smkNormal, Data.StatusMsg);
|
|
iscbNotifyIdle:
|
|
begin
|
|
Form.UpdateCompileStatusPanels(Data.CompressProgress,
|
|
Data.CompressProgressMax, Data.SecondsRemaining,
|
|
Data.BytesCompressedPerSecond);
|
|
{ We have to use HandleMessage instead of ProcessMessages so that
|
|
Application.Idle is called. Otherwise, Flat TSpeedButton's don't
|
|
react to the mouse being moved over them.
|
|
Unfortunately, HandleMessage by default calls WaitMessage. To avoid
|
|
this we have an Application.OnIdle handler which sets Done to False
|
|
while compiling is in progress - see AppOnIdle.
|
|
The GetQueueStatus check below is just an optimization; calling
|
|
HandleMessage when there are no messages to process wastes CPU. }
|
|
if GetQueueStatus(QS_ALLINPUT) <> 0 then begin
|
|
Form.FBecameIdle := False;
|
|
repeat
|
|
Application.HandleMessage;
|
|
{ AppOnIdle sets FBecameIdle to True when it's called, which
|
|
indicates HandleMessage didn't find any message to process }
|
|
until Form.FBecameIdle;
|
|
end;
|
|
if Form.FCompileWantAbort then
|
|
Result := iscrRequestAbort;
|
|
end;
|
|
iscbNotifyPreproc:
|
|
begin
|
|
Form.FPreprocessorOutput := TrimRight(Data.PreprocessedScript);
|
|
DecodeIncludedFilenames(Data.IncludedFilenames, Form.FIncludedFiles); { Also stores last write time }
|
|
CleanHiddenFiles(Form.FIncludedFiles, Form.FHiddenFiles);
|
|
Form.InvalidateStatusPanel(spHiddenFilesCount);
|
|
Form.BuildAndSaveKnownIncludedAndHiddenFiles;
|
|
end;
|
|
iscbNotifySuccess:
|
|
begin
|
|
OutputExe := Data.OutputExeFilename;
|
|
if Form.FCompilerVersion.BinVersion >= $3000001 then begin
|
|
DebugInfo := AllocMem(Data.DebugInfoSize);
|
|
Move(Data.DebugInfo^, DebugInfo^, Data.DebugInfoSize);
|
|
end else
|
|
DebugInfo := nil;
|
|
end;
|
|
iscbNotifyError:
|
|
begin
|
|
if Assigned(Data.ErrorMsg) then
|
|
ErrorMsg := Data.ErrorMsg
|
|
else
|
|
Aborted := True;
|
|
ErrorFilename := Data.ErrorFilename;
|
|
ErrorLine := Data.ErrorLine;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.CompileFile(AFilename: String; const ReadFromFile: Boolean);
|
|
|
|
function GetMemoFromErrorFilename(const ErrorFilename: String): TIDEScintFileEdit;
|
|
var
|
|
Memo: TIDEScintFileEdit;
|
|
begin
|
|
if ErrorFilename = '' then
|
|
Result := FMainMemo
|
|
else begin
|
|
if FOptions.OpenIncludedFiles then begin
|
|
for Memo in FFileMemos do begin
|
|
if Memo.Used and (PathCompare(Memo.Filename, ErrorFilename) = 0) then begin
|
|
Result := Memo;
|
|
Exit;
|
|
end;
|
|
end;
|
|
end;
|
|
Result := nil;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
SourcePath, S, Options: String;
|
|
Params: TCompileScriptParamsEx;
|
|
AppData: TAppData;
|
|
StartTime, ElapsedTime, ElapsedSeconds: DWORD;
|
|
I: Integer;
|
|
Memo: TIDEScintFileEdit;
|
|
OldActiveMemo: TIDEScintEdit;
|
|
begin
|
|
if FCompiling then begin
|
|
{ Shouldn't get here, but just in case... }
|
|
MsgBox('A compile is already in progress.', SCompilerFormCaption, mbError, MB_OK);
|
|
Abort;
|
|
end;
|
|
|
|
if not ReadFromFile then begin
|
|
if FOptions.OpenIncludedFiles then begin
|
|
{ Included files must always be saved since they're not read from the editor by the compiler }
|
|
for Memo in FFileMemos do begin
|
|
if (Memo <> FMainMemo) and Memo.Used and Memo.Modified then begin
|
|
if FOptions.Autosave then begin
|
|
if not SaveFile(Memo, False) then
|
|
Abort;
|
|
end else begin
|
|
case MsgBox('The text in the ' + Memo.Filename + ' file has changed and must be saved before compiling.'#13#10#13#10 +
|
|
'Save the changes and continue?', SCompilerFormCaption, mbError,
|
|
MB_YESNO) of
|
|
IDYES:
|
|
if not SaveFile(Memo, False) then
|
|
Abort;
|
|
else
|
|
Abort;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
{ Save main file if requested }
|
|
if FOptions.Autosave and FMainMemo.Modified then begin
|
|
if not SaveFile(FMainMemo, False) then
|
|
Abort;
|
|
end else if FMainMemo.Filename = '' then begin
|
|
case MsgBox('Would you like to save the script before compiling?' +
|
|
SNewLine2 + 'If you answer No, the compiled installation will be ' +
|
|
'placed under your My Documents folder by default.',
|
|
SCompilerFormCaption, mbConfirmation, MB_YESNOCANCEL) of
|
|
IDYES:
|
|
if not SaveFile(FMainMemo, False) then
|
|
Abort;
|
|
IDNO: ;
|
|
else
|
|
Abort;
|
|
end;
|
|
end;
|
|
AFilename := FMainMemo.Filename;
|
|
end; {else: Command line compile, AFilename already set. }
|
|
|
|
DestroyDebugInfo;
|
|
OldActiveMemo := FActiveMemo;
|
|
AppData.Lines := TStringList.Create;
|
|
try
|
|
FBuildAnimationFrame := 0;
|
|
FProgress := 0;
|
|
FProgressMax := 0;
|
|
FTaskbarProgressValue := 0;
|
|
|
|
FActiveMemo.CancelAutoCompleteAndCallTip;
|
|
FActiveMemo.Cursor := crAppStart;
|
|
FActiveMemo.SetCursorID(999); { hack to keep it from overriding Cursor }
|
|
CompilerOutputList.Cursor := crAppStart;
|
|
for Memo in FFileMemos do
|
|
Memo.ReadOnly := True;
|
|
UpdateEditModePanel;
|
|
HideError;
|
|
CompilerOutputList.Clear;
|
|
SendMessage(CompilerOutputList.Handle, LB_SETHORIZONTALEXTENT, 0, 0);
|
|
DebugOutputList.Clear;
|
|
SendMessage(DebugOutputList.Handle, LB_SETHORIZONTALEXTENT, 0, 0);
|
|
DebugCallStackList.Clear;
|
|
SendMessage(DebugCallStackList.Handle, LB_SETHORIZONTALEXTENT, 0, 0);
|
|
OutputTabSet.TabIndex := tiCompilerOutput;
|
|
SetStatusPanelVisible(True);
|
|
|
|
SourcePath := GetSourcePath(AFilename);
|
|
|
|
FillChar(Params, SizeOf(Params), 0);
|
|
Params.Size := SizeOf(Params);
|
|
Params.CompilerPath := nil;
|
|
Params.SourcePath := PChar(SourcePath);
|
|
Params.CallbackProc := CompilerCallbackProc;
|
|
Pointer(Params.AppData) := @AppData;
|
|
Options := '';
|
|
for I := 0 to FSignTools.Count-1 do
|
|
Options := Options + AddSignToolParam(FSignTools[I]);
|
|
Params.Options := PChar(Options);
|
|
|
|
AppData.Form := Self;
|
|
AppData.CurLineNumber := 0;
|
|
AppData.Aborted := False;
|
|
I := ReadScriptLines(AppData.Lines, ReadFromFile, AFilename, FMainMemo);
|
|
if I <> -1 then begin
|
|
if not ReadFromFile then begin
|
|
MoveCaretAndActivateMemo(FMainMemo, I, False);
|
|
SetErrorLine(FMainMemo, I);
|
|
end;
|
|
raise Exception.CreateFmt(SCompilerIllegalNullChar, [I + 1]);
|
|
end;
|
|
|
|
StartTime := GetTickCount;
|
|
StatusMessage(smkStartEnd, Format(SCompilerStatusStarting, [TimeToStr(Time)]));
|
|
StatusMessage(smkStartEnd, '');
|
|
FCompiling := True;
|
|
FCompileWantAbort := False;
|
|
UpdateRunMenu;
|
|
UpdateCaption;
|
|
SetLowPriority(FOptions.LowPriorityDuringCompile, FSavePriorityClass);
|
|
|
|
AppData.Filename := AFilename;
|
|
|
|
{$IFNDEF STATICCOMPILER}
|
|
if ISDllCompileScript(Params) <> isceNoError then begin
|
|
{$ELSE}
|
|
if ISCompileScript(Params, False) <> isceNoError then begin
|
|
{$ENDIF}
|
|
StatusMessage(smkError, SCompilerStatusErrorAborted);
|
|
if not ReadFromFile and (AppData.ErrorLine > 0) then begin
|
|
Memo := GetMemoFromErrorFilename(AppData.ErrorFilename);
|
|
if Memo <> nil then begin
|
|
{ Move the caret to the line number the error occurred on }
|
|
MoveCaretAndActivateMemo(Memo, AppData.ErrorLine - 1, False);
|
|
SetErrorLine(Memo, AppData.ErrorLine - 1);
|
|
end;
|
|
end;
|
|
if not AppData.Aborted then begin
|
|
S := '';
|
|
if AppData.ErrorFilename <> '' then
|
|
S := 'File: ' + AppData.ErrorFilename + SNewLine2;
|
|
if AppData.ErrorLine > 0 then
|
|
S := S + Format('Line %d:' + SNewLine, [AppData.ErrorLine]);
|
|
S := S + AppData.ErrorMsg;
|
|
SetAppTaskbarProgressState(tpsError);
|
|
MsgBox(S, 'Compiler Error', mbCriticalError, MB_OK)
|
|
end;
|
|
Abort;
|
|
end;
|
|
ElapsedTime := GetTickCount - StartTime;
|
|
ElapsedSeconds := ElapsedTime div 1000;
|
|
StatusMessage(smkStartEnd, Format(SCompilerStatusFinished, [TimeToStr(Time),
|
|
Format('%.2u%s%.2u%s%.3u', [ElapsedSeconds div 60, FormatSettings.TimeSeparator,
|
|
ElapsedSeconds mod 60, FormatSettings.DecimalSeparator, ElapsedTime mod 1000])]));
|
|
finally
|
|
AppData.Lines.Free;
|
|
FCompiling := False;
|
|
SetLowPriority(False, FSavePriorityClass);
|
|
OldActiveMemo.Cursor := crDefault;
|
|
OldActiveMemo.SetCursorID(SC_CURSORNORMAL);
|
|
CompilerOutputList.Cursor := crDefault;
|
|
for Memo in FFileMemos do
|
|
Memo.ReadOnly := False;
|
|
UpdateEditModePanel;
|
|
UpdateRunMenu;
|
|
UpdateCaption;
|
|
UpdatePreprocMemos;
|
|
if AppData.DebugInfo <> nil then begin
|
|
ParseDebugInfo(AppData.DebugInfo); { Must be called after UpdateIncludedFilesMemos }
|
|
FreeMem(AppData.DebugInfo);
|
|
end;
|
|
InvalidateStatusPanel(spCompileIcon);
|
|
InvalidateStatusPanel(spCompileProgress);
|
|
SetAppTaskbarProgressState(tpsNoProgress);
|
|
StatusBar.Panels[spExtraStatus].Text := '';
|
|
end;
|
|
FCompiledExe := AppData.OutputExe;
|
|
FModifiedAnySinceLastCompile := False;
|
|
FModifiedAnySinceLastCompileAndGo := False;
|
|
end;
|
|
|
|
procedure TMainForm.SyncEditorOptions;
|
|
const
|
|
SquigglyStyles: array[Boolean] of Integer = (INDIC_HIDDEN, INDIC_SQUIGGLE);
|
|
WhiteSpaceStyles: array[Boolean] of Integer = (SCWS_INVISIBLE, SCWS_VISIBLEALWAYS);
|
|
var
|
|
Memo: TIDEScintEdit;
|
|
begin
|
|
for Memo in FMemos do begin
|
|
Memo.UseStyleAttributes := FOptions.UseSyntaxHighlighting;
|
|
Memo.Call(SCI_INDICSETSTYLE, minSquiggly, SquigglyStyles[FOptions.UnderlineErrors]);
|
|
Memo.Call(SCI_SETVIEWWS, WhiteSpaceStyles[FOptions.ShowWhiteSpace], 0);
|
|
|
|
if FOptions.CursorPastEOL then
|
|
Memo.VirtualSpaceOptions := [svsRectangularSelection, svsUserAccessible, svsNoWrapLineStart]
|
|
else
|
|
Memo.VirtualSpaceOptions := [];
|
|
Memo.FillSelectionToEdge := FOptions.CursorPastEOL;
|
|
|
|
Memo.TabWidth := FOptions.TabWidth;
|
|
Memo.UseTabCharacter := FOptions.UseTabCharacter;
|
|
|
|
Memo.KeyMappingType := FOptions.MemoKeyMappingType;
|
|
if Memo = FMainMemo then begin
|
|
SetFakeShortCut(ESelectNextOccurrence, FMainMemo.GetComplexCommandShortCut(ccSelectNextOccurrence));
|
|
SetFakeShortCut(ESelectAllOccurrences, FMainMemo.GetComplexCommandShortCut(ccSelectAllOccurrences));
|
|
SetFakeShortCut(ESelectAllFindMatches, FMainMemo.GetComplexCommandShortCut(ccSelectAllFindMatches));
|
|
SetFakeShortCut(EFoldLine, FMainMemo.GetComplexCommandShortCut(ccFoldLine));
|
|
SetFakeShortCut(EUnfoldLine, FMainMemo.GetComplexCommandShortCut(ccUnfoldLine));
|
|
SetFakeShortCut(EToggleLinesComment, FMainMemo.GetComplexCommandShortCut(ccToggleLinesComment));
|
|
SetFakeShortCut(EBraceMatch, FMainMemo.GetComplexCommandShortCut(ccBraceMatch));
|
|
end;
|
|
|
|
Memo.UseFolding := FOptions.UseFolding;
|
|
Memo.WordWrap := FOptions.WordWrap;
|
|
|
|
if FOptions.IndentationGuides then
|
|
Memo.IndentationGuides := sigLookBoth
|
|
else
|
|
Memo.IndentationGuides := sigNone;
|
|
|
|
Memo.LineNumbers := FOptions.GutterLineNumbers;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.FMenuClick(Sender: TObject);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
FSaveMainFileAs.Enabled := FActiveMemo = FMainMemo;
|
|
FSaveEncoding.Enabled := FSave.Enabled; { FSave.Enabled is kept up-to-date by UpdateSaveMenuItemAndButton }
|
|
FSaveEncodingAuto.Checked := FSaveEncoding.Enabled and ((FActiveMemo as TIDEScintFileEdit).SaveEncoding = seAuto);
|
|
FSaveEncodingUTF8WithBOM.Checked := FSaveEncoding.Enabled and ((FActiveMemo as TIDEScintFileEdit).SaveEncoding = seUTF8WithBOM);
|
|
FSaveEncodingUTF8WithoutBOM.Checked := FSaveEncoding.Enabled and ((FActiveMemo as TIDEScintFileEdit).SaveEncoding = seUTF8WithoutBOM);
|
|
FSaveAll.Visible := FOptions.OpenIncludedFiles;
|
|
ReadMRUMainFilesList;
|
|
FRecent.Visible := FMRUMainFilesList.Count <> 0;
|
|
for I := 0 to High(FMRUMainFilesMenuItems) do
|
|
with FMRUMainFilesMenuItems[I] do begin
|
|
if I < FMRUMainFilesList.Count then begin
|
|
Visible := True;
|
|
Caption := '&' + IntToStr((I+1) mod 10) + ' ' + DoubleAmp(FMRUMainFilesList[I]);
|
|
end
|
|
else
|
|
Visible := False;
|
|
end;
|
|
|
|
ApplyMenuBitmaps(Sender as TMenuItem);
|
|
end;
|
|
|
|
procedure TMainForm.FNewMainFileClick(Sender: TObject);
|
|
begin
|
|
if ConfirmCloseFile(True) then
|
|
NewMainFile;
|
|
end;
|
|
|
|
procedure TMainForm.FNewMainFileUserWizardClick(Sender: TObject);
|
|
begin
|
|
if ConfirmCloseFile(True) then
|
|
NewMainFileUsingWizard;
|
|
end;
|
|
|
|
procedure TMainForm.ShowOpenMainFileDialog(const Examples: Boolean);
|
|
var
|
|
InitialDir, FileName: String;
|
|
begin
|
|
if Examples then begin
|
|
InitialDir := PathExtractPath(NewParamStr(0)) + 'Examples';
|
|
Filename := PathExtractPath(NewParamStr(0)) + 'Examples\Example1.iss';
|
|
end
|
|
else begin
|
|
InitialDir := PathExtractDir(FMainMemo.Filename);
|
|
Filename := '';
|
|
end;
|
|
if ConfirmCloseFile(True) then
|
|
if NewGetOpenFileName('', FileName, InitialDir, SCompilerOpenFilter, 'iss', Handle) then
|
|
OpenFile(FMainMemo, Filename, False);
|
|
end;
|
|
|
|
procedure TMainForm.FOpenMainFileClick(Sender: TObject);
|
|
begin
|
|
ShowOpenMainFileDialog(False);
|
|
end;
|
|
|
|
procedure TMainForm.FSaveClick(Sender: TObject);
|
|
begin
|
|
SaveFile((FActiveMemo as TIDEScintFileEdit), Sender = FSaveMainFileAs);
|
|
end;
|
|
|
|
procedure TMainForm.FSaveEncodingItemClick(Sender: TObject);
|
|
begin
|
|
var Memo := (FActiveMemo as TIDEScintFileEdit);
|
|
|
|
var OldSaveEncoding := Memo.SaveEncoding;
|
|
|
|
if Sender = FSaveEncodingUTF8WithBOM then
|
|
Memo.SaveEncoding := seUTF8WithBOM
|
|
else if Sender = FSaveEncodingUTF8WithoutBOM then
|
|
Memo.SaveEncoding := seUTF8WithoutBOM
|
|
else
|
|
Memo.SaveEncoding := seAuto;
|
|
|
|
if Memo.SaveEncoding <> OldSaveEncoding then
|
|
Memo.ForceModifiedState;
|
|
end;
|
|
|
|
procedure TMainForm.FSaveAllClick(Sender: TObject);
|
|
var
|
|
Memo: TIDEScintFileEdit;
|
|
begin
|
|
for Memo in FFileMemos do
|
|
if Memo.Used and Memo.Modified then
|
|
SaveFile(Memo, False);
|
|
end;
|
|
|
|
procedure TMainForm.FPrintClick(Sender: TObject);
|
|
|
|
procedure SetupNonDarkPrintStyler(var PrintStyler: TInnoSetupStyler; var PrintTheme: TTheme;
|
|
var OldStyler: TScintCustomStyler; var OldTheme: TTheme);
|
|
begin
|
|
{ Not the most pretty code, would ideally make a copy of FActiveMemo and print that instead or
|
|
somehow convince Scintilla to use different print styles but don't know of a good way to do
|
|
either. Using SC_PRINT_COLOURONWHITE doesn't help, this gives white on white in dark mode. }
|
|
PrintStyler := TInnoSetupStyler.Create(nil);
|
|
PrintTheme := TTheme.Create;
|
|
PrintStyler.ISPPInstalled := ISPPInstalled;
|
|
PrintStyler.Theme := PrintTheme;
|
|
if not FTheme.Dark then
|
|
PrintTheme.Typ := FTheme.Typ
|
|
else
|
|
PrintTheme.Typ := ttModernLight;
|
|
OldStyler := FActiveMemo.Styler;
|
|
OldTheme := FActiveMemo.Theme;
|
|
FActiveMemo.Styler := PrintStyler;
|
|
FActiveMemo.Theme := PrintTheme;
|
|
FActiveMemo.UpdateThemeColorsAndStyleAttributes;
|
|
end;
|
|
|
|
procedure DeinitPrintStyler(const PrintStyler: TInnoSetupStyler; const PrintTheme: TTheme;
|
|
const OldStyler: TScintCustomStyler; const OldTheme: TTheme);
|
|
begin
|
|
if (OldStyler <> nil) or (OldTheme <> nil) then begin
|
|
if OldStyler <> nil then
|
|
FActiveMemo.Styler := OldStyler;
|
|
if OldTheme <> nil then
|
|
FActiveMemo.Theme := OldTheme;
|
|
FActiveMemo.UpdateThemeColorsAndStyleAttributes;
|
|
end;
|
|
if PrintTheme <> FTheme then
|
|
PrintTheme.Free;
|
|
PrintStyler.Free;
|
|
end;
|
|
|
|
var
|
|
PrintStyler: TInnoSetupStyler;
|
|
OldStyler: TScintCustomStyler;
|
|
PrintTheme, OldTheme: TTheme;
|
|
PrintMemo: TIDEScintEdit;
|
|
HeaderMemo: TIDEScintFileEdit;
|
|
FileTitle, S: String;
|
|
pdlg: TPrintDlg;
|
|
hdc: Windows.HDC;
|
|
rectMargins, rectPhysMargins, rectSetup, rcw: TRect;
|
|
ptPage, ptDpi: TPoint;
|
|
headerLineHeight, footerLineHeight: Integer;
|
|
fontHeader, fontFooter: HFONT;
|
|
tm: TTextMetric;
|
|
di: TDocInfo;
|
|
lengthDoc, lengthDocMax, lengthPrinted: Integer;
|
|
frPrint: TScintRangeToFormat;
|
|
pageNum: Integer;
|
|
printPage: Boolean;
|
|
ta: UINT;
|
|
sHeader, sFooter: String;
|
|
pen, penOld: HPEN;
|
|
begin
|
|
if FActiveMemo is TIDEScintFileEdit then
|
|
HeaderMemo := TIDEScintFileEdit(FActiveMemo)
|
|
else
|
|
HeaderMemo := FMainMemo;
|
|
sHeader := HeaderMemo.Filename;
|
|
FileTitle := GetFileTitle(HeaderMemo.Filename);
|
|
if HeaderMemo <> FActiveMemo then begin
|
|
S := ' - ' + MemosTabSet.Tabs[MemoToTabIndex(FActiveMemo)];
|
|
sHeader := Format('%s %s', [sHeader, S]);
|
|
FileTitle := Format('%s %s', [FileTitle, S]);
|
|
end;
|
|
sHeader := Format('%s - %s', [sHeader, DateTimeToStr(Now())]);
|
|
|
|
{ Based on SciTE 5.50's SciTEWin::Print }
|
|
|
|
ZeroMemory(@pdlg, SizeOf(pdlg));
|
|
pdlg.lStructSize := SizeOf(pdlg);
|
|
pdlg.hwndOwner := Handle;
|
|
pdlg.hInstance := hInstance;
|
|
pdlg.Flags := PD_USEDEVMODECOPIES or PD_ALLPAGES or PD_RETURNDC;
|
|
pdlg.nFromPage := 1;
|
|
pdlg.nToPage := 1;
|
|
pdlg.nMinPage := 1;
|
|
pdlg.nMaxPage := $ffff; // We do not know how many pages in the document until the printer is selected and the paper size is known.
|
|
pdlg.nCopies := 1;
|
|
pdlg.hDC := 0;
|
|
pdlg.hDevMode := FDevMode;
|
|
pdlg.hDevNames := FDevNames;
|
|
|
|
// See if a range has been selected
|
|
var rangeSelection := FActiveMemo.Selection;
|
|
if rangeSelection.StartPos = rangeSelection.EndPos then
|
|
pdlg.Flags := pdlg.Flags or PD_NOSELECTION
|
|
else
|
|
pdlg.Flags := pdlg.Flags or PD_SELECTION;
|
|
|
|
if not PrintDlg(pdlg) then
|
|
Exit;
|
|
|
|
PrintStyler := nil;
|
|
PrintTheme := nil;
|
|
OldStyler := nil;
|
|
OldTheme := nil;
|
|
try
|
|
if FTheme.Dark then
|
|
SetupNonDarkPrintStyler(PrintStyler, PrintTheme, OldStyler, OldTheme)
|
|
else
|
|
PrintTheme := FTheme;
|
|
|
|
FDevMode := pdlg.hDevMode;
|
|
FDevNames := pdlg.hDevNames;
|
|
|
|
hdc := pdlg.hDC;
|
|
|
|
// Get printer resolution
|
|
ptDpi.x := GetDeviceCaps(hdc, LOGPIXELSX); // dpi in X direction
|
|
ptDpi.y := GetDeviceCaps(hdc, LOGPIXELSY); // dpi in Y direction
|
|
|
|
// Start by getting the physical page size (in device units).
|
|
ptPage.x := GetDeviceCaps(hdc, PHYSICALWIDTH); // device units
|
|
ptPage.y := GetDeviceCaps(hdc, PHYSICALHEIGHT); // device units
|
|
|
|
// Get the dimensions of the unprintable
|
|
// part of the page (in device units).
|
|
rectPhysMargins.left := GetDeviceCaps(hdc, PHYSICALOFFSETX);
|
|
rectPhysMargins.top := GetDeviceCaps(hdc, PHYSICALOFFSETY);
|
|
|
|
// To get the right and lower unprintable area,
|
|
// we take the entire width and height of the paper and
|
|
// subtract everything else.
|
|
rectPhysMargins.right := ptPage.x // total paper width
|
|
- GetDeviceCaps(hdc, HORZRES) // printable width
|
|
- rectPhysMargins.left; // left unprintable margin
|
|
|
|
rectPhysMargins.bottom := ptPage.y // total paper height
|
|
- GetDeviceCaps(hdc, VERTRES) // printable height
|
|
- rectPhysMargins.top; // right unprintable margin
|
|
|
|
// At this point, rectPhysMargins contains the widths of the
|
|
// unprintable regions on all four sides of the page in device units.
|
|
|
|
(*
|
|
// Take in account the page setup given by the user (if one value is not null)
|
|
if (pagesetupMargin.left != 0 || pagesetupMargin.right != 0 ||
|
|
pagesetupMargin.top != 0 || pagesetupMargin.bottom != 0) {
|
|
GUI::Rectangle rectSetup;
|
|
|
|
// Convert the hundredths of millimeters (HiMetric) or
|
|
// thousandths of inches (HiEnglish) margin values
|
|
// from the Page Setup dialog to device units.
|
|
// (There are 2540 hundredths of a mm in an inch.)
|
|
|
|
TCHAR localeInfo[3];
|
|
GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IMEASURE, localeInfo, 3);
|
|
|
|
if (localeInfo[0] == '0') { // Metric system. '1' is US System *)
|
|
rectSetup.left := MulDiv(500 {pagesetupMargin.left}, ptDpi.x, 2540);
|
|
rectSetup.top := MulDiv(500 {pagesetupMargin.top}, ptDpi.y, 2540);
|
|
rectSetup.right := MulDiv(500 {pagesetupMargin.right}, ptDpi.x, 2540);
|
|
rectSetup.bottom := MulDiv(500 {pagesetupMargin.bottom}, ptDpi.y, 2540);
|
|
(* } else {
|
|
rectSetup.left = MulDiv(pagesetupMargin.left, ptDpi.x, 1000);
|
|
rectSetup.top = MulDiv(pagesetupMargin.top, ptDpi.y, 1000);
|
|
rectSetup.right = MulDiv(pagesetupMargin.right, ptDpi.x, 1000);
|
|
rectSetup.bottom = MulDiv(pagesetupMargin.bottom, ptDpi.y, 1000);
|
|
} *)
|
|
|
|
// Don't reduce margins below the minimum printable area
|
|
rectMargins.left := Max(rectPhysMargins.left, rectSetup.left);
|
|
rectMargins.top := Max(rectPhysMargins.top, rectSetup.top);
|
|
rectMargins.right := Max(rectPhysMargins.right, rectSetup.right);
|
|
rectMargins.bottom := Max(rectPhysMargins.bottom, rectSetup.bottom);
|
|
(*
|
|
} else {
|
|
rectMargins := rectPhysMargins;
|
|
}
|
|
*)
|
|
|
|
// rectMargins now contains the values used to shrink the printable
|
|
// area of the page.
|
|
|
|
// Convert device coordinates into logical coordinates
|
|
DPtoLP(hdc, rectMargins, 2);
|
|
DPtoLP(hdc, rectPhysMargins, 2);
|
|
|
|
// Convert page size to logical units and we're done!
|
|
DPtoLP(hdc, ptPage, 1);
|
|
|
|
headerLineHeight := MulDiv(9, ptDpi.y, 72);
|
|
fontHeader := CreateFont(headerLineHeight, 0, 0, 0, FW_REGULAR, 1, 0, 0, 0, 0, 0, 0, 0, PChar(FActiveMemo.Font.Name));
|
|
SelectObject(hdc, fontHeader);
|
|
GetTextMetrics(hdc, &tm);
|
|
headerLineHeight := tm.tmHeight + tm.tmExternalLeading;
|
|
|
|
footerLineHeight := MulDiv(9, ptDpi.y, 72);
|
|
fontFooter := CreateFont(footerLineHeight, 0, 0, 0, FW_REGULAR, 0, 0, 0, 0, 0, 0, 0, 0, PChar(FActiveMemo.Font.Name));
|
|
SelectObject(hdc, fontFooter);
|
|
GetTextMetrics(hdc, &tm);
|
|
footerLineHeight := tm.tmHeight + tm.tmExternalLeading;
|
|
|
|
ZeroMemory(@di, SizeOf(di));
|
|
di.cbSize := SizeOf(di);
|
|
di.lpszDocName := PChar(FileTitle);
|
|
di.lpszOutput := nil;
|
|
di.lpszDatatype := nil;
|
|
di.fwType := 0;
|
|
|
|
if StartDoc(hdc, &di) < 0 then begin
|
|
DeleteDC(hdc);
|
|
DeleteObject(fontHeader);
|
|
DeleteObject(fontFooter);
|
|
MsgBox('Can not start printer document.', SCompilerFormCaption, mbError, MB_OK);
|
|
Exit;
|
|
end;
|
|
|
|
lengthDocMax := FActiveMemo.GetRawTextLength;
|
|
// PD_SELECTION -> requested to print selection.
|
|
lengthDoc := IfThen((pdlg.Flags and PD_SELECTION) <> 0, rangeSelection.EndPos, lengthDocMax);
|
|
lengthPrinted := IfThen((pdlg.Flags and PD_SELECTION) <> 0, rangeSelection.StartPos, 0);
|
|
|
|
// We must subtract the physical margins from the printable area
|
|
frPrint.hdc := hdc;
|
|
frPrint.hdcTarget := hdc;
|
|
frPrint.rc.left := rectMargins.left - rectPhysMargins.left;
|
|
frPrint.rc.top := rectMargins.top - rectPhysMargins.top;
|
|
frPrint.rc.right := ptPage.x - rectMargins.right - rectPhysMargins.left;
|
|
frPrint.rc.bottom := ptPage.y - rectMargins.bottom - rectPhysMargins.top;
|
|
frPrint.rcPage.left := 0;
|
|
frPrint.rcPage.top := 0;
|
|
frPrint.rcPage.right := ptPage.x - rectPhysMargins.left - rectPhysMargins.right - 1;
|
|
frPrint.rcPage.bottom := ptPage.y - rectPhysMargins.top - rectPhysMargins.bottom - 1;
|
|
frPrint.rc.top := frPrint.rc.top + headerLineHeight + headerLineHeight div 2;
|
|
frPrint.rc.bottom := frPrint.rc.bottom - (footerLineHeight + footerLineHeight div 2);
|
|
|
|
// Print each page
|
|
pageNum := 1;
|
|
|
|
while lengthPrinted < lengthDoc do begin
|
|
printPage := ((pdlg.Flags and PD_PAGENUMS) = 0) or
|
|
((pageNum >= pdlg.nFromPage) and (pageNum <= pdlg.nToPage));
|
|
|
|
sFooter := Format('- %d -', [pageNum]);
|
|
|
|
if printPage then begin
|
|
StartPage(hdc);
|
|
|
|
SetTextColor(hdc, PrintTheme.Colors[tcFore]);
|
|
SetBkColor(hdc, PrintTheme.Colors[tcBack]);
|
|
SelectObject(hdc, fontHeader);
|
|
ta := SetTextAlign(hdc, TA_BOTTOM);
|
|
rcw := Rect(frPrint.rc.left, frPrint.rc.top - headerLineHeight - headerLineHeight div 2,
|
|
frPrint.rc.right, frPrint.rc.top - headerLineHeight div 2);
|
|
rcw.bottom := rcw.top + headerLineHeight;
|
|
ExtTextOut(hdc, frPrint.rc.left + 5, frPrint.rc.top - headerLineHeight div 2,
|
|
ETO_OPAQUE, rcw, sHeader, Length(sHeader), nil);
|
|
SetTextAlign(hdc, ta);
|
|
pen := CreatePen(0, 1, GetTextColor(hdc));
|
|
penOld := SelectObject(hdc, pen);
|
|
MoveToEx(hdc, frPrint.rc.left, frPrint.rc.top - headerLineHeight div 4, nil);
|
|
LineTo(hdc, frPrint.rc.right, frPrint.rc.top - headerLineHeight div 4);
|
|
SelectObject(hdc, penOld);
|
|
DeleteObject(pen);
|
|
end;
|
|
|
|
frPrint.chrg.StartPos := lengthPrinted;
|
|
frPrint.chrg.EndPos := lengthDoc;
|
|
|
|
lengthPrinted := FActiveMemo.FormatRange(printPage, @frPrint);
|
|
|
|
if printPage then begin
|
|
SetTextColor(hdc, PrintTheme.Colors[tcFore]);
|
|
SetBkColor(hdc, PrintTheme.Colors[tcBack]);
|
|
SelectObject(hdc, fontFooter);
|
|
ta := SetTextAlign(hdc, TA_TOP);
|
|
rcw := Rect(frPrint.rc.left, frPrint.rc.bottom + footerLineHeight div 2,
|
|
frPrint.rc.right, frPrint.rc.bottom + footerLineHeight + footerLineHeight div 2);
|
|
ExtTextOut(hdc, frPrint.rc.left + 5, frPrint.rc.bottom + footerLineHeight div 2,
|
|
ETO_OPAQUE, rcw, sFooter, Length(sFooter), nil);
|
|
SetTextAlign(hdc, ta);
|
|
pen := CreatePen(0, 1, GetTextColor(hdc));
|
|
penOld := SelectObject(hdc, pen);
|
|
MoveToEx(hdc, frPrint.rc.left, frPrint.rc.bottom + footerLineHeight div 4, nil);
|
|
LineTo(hdc, frPrint.rc.right, frPrint.rc.bottom + footerLineHeight div 4);
|
|
SelectObject(hdc, penOld);
|
|
DeleteObject(pen);
|
|
|
|
EndPage(hdc);
|
|
end;
|
|
Inc(pageNum);
|
|
|
|
if ((pdlg.Flags and PD_PAGENUMS) <> 0) and (pageNum > pdlg.nToPage) then
|
|
Break;
|
|
end;
|
|
|
|
FActiveMemo.FormatRange(False, nil);
|
|
|
|
EndDoc(hdc);
|
|
DeleteDC(hdc);
|
|
DeleteObject(fontHeader);
|
|
DeleteObject(fontFooter);
|
|
finally
|
|
DeinitPrintStyler(PrintStyler, PrintTheme, OldStyler, OldTheme);
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.FClearRecentClick(Sender: TObject);
|
|
begin
|
|
if MsgBox('Are you sure you want to clear the list of recently opened files?',
|
|
SCompilerFormCaption, mbConfirmation, MB_YESNO or MB_DEFBUTTON2) <> IDNO then
|
|
ClearMRUMainFilesList;
|
|
end;
|
|
|
|
procedure TMainForm.FMRUClick(Sender: TObject);
|
|
var
|
|
I: Integer;
|
|
begin
|
|
if ConfirmCloseFile(True) then
|
|
for I := 0 to High(FMRUMainFilesMenuItems) do
|
|
if FMRUMainFilesMenuItems[I] = Sender then begin
|
|
OpenMRUMainFile(FMRUMainFilesList[I]);
|
|
Break;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.FExitClick(Sender: TObject);
|
|
begin
|
|
Close;
|
|
end;
|
|
|
|
procedure TMainForm.EMenuClick(Sender: TObject);
|
|
var
|
|
MemoHasFocus, MemoIsReadOnly: Boolean;
|
|
begin
|
|
MemoHasFocus := FActiveMemo.Focused;
|
|
MemoIsReadOnly := FActiveMemo.ReadOnly;
|
|
EUndo.Enabled := MemoHasFocus and FActiveMemo.CanUndo;
|
|
ERedo.Enabled := MemoHasFocus and FActiveMemo.CanRedo;
|
|
ECut.Enabled := MemoHasFocus and not MemoIsReadOnly and not FActiveMemo.SelEmpty;
|
|
ECopy.Enabled := MemoHasFocus and not FActiveMemo.SelEmpty;
|
|
EPaste.Enabled := MemoHasFocus and FActiveMemo.CanPaste;
|
|
EDelete.Enabled := MemoHasFocus and not FActiveMemo.SelEmpty;
|
|
ESelectAll.Enabled := MemoHasFocus;
|
|
ESelectNextOccurrence.Enabled := MemoHasFocus;
|
|
ESelectAllOccurrences.Enabled := MemoHasFocus;
|
|
ESelectAllFindMatches.Enabled := MemoHasFocus and (FLastFindText <> '');
|
|
EFind.Enabled := MemoHasFocus;
|
|
EFindNext.Enabled := MemoHasFocus;
|
|
EFindPrevious.Enabled := MemoHasFocus;
|
|
EReplace.Enabled := MemoHasFocus and not MemoIsReadOnly;
|
|
EFindRegEx.Checked := FOptions.FindRegEx;
|
|
EFoldLine.Visible := FOptions.UseFolding;
|
|
EFoldLine.Enabled := MemoHasFocus;
|
|
EUnfoldLine.Visible := EFoldLine.Visible;
|
|
EUnfoldLine.Enabled := EFoldLine.Enabled;
|
|
EGoto.Enabled := MemoHasFocus;
|
|
EToggleLinesComment.Enabled := not MemoIsReadOnly;
|
|
EBraceMatch.Enabled := MemoHasFocus;
|
|
|
|
ApplyMenuBitmaps(Sender as TMenuItem);
|
|
end;
|
|
|
|
procedure TMainForm.EUndoClick(Sender: TObject);
|
|
begin
|
|
FActiveMemo.Undo;
|
|
end;
|
|
|
|
procedure TMainForm.ERedoClick(Sender: TObject);
|
|
begin
|
|
FActiveMemo.Redo;
|
|
end;
|
|
|
|
procedure TMainForm.ECutClick(Sender: TObject);
|
|
begin
|
|
FActiveMemo.CutToClipboard;
|
|
end;
|
|
|
|
procedure TMainForm.ECopyClick(Sender: TObject);
|
|
begin
|
|
FActiveMemo.CopyToClipboard;
|
|
end;
|
|
|
|
function TMainForm.MultipleSelectionPasteFromClipboard(const AMemo: TIDEScintEdit): Boolean;
|
|
begin
|
|
{ Scintilla doesn't yet properly support multiple selection paste. Handle it
|
|
here, just like VS and VSCode do: if there's multiple selections and the paste
|
|
text has the same amount of lines then paste 1 line per selection. Do this even
|
|
if the paste text is marked as rectangular. Otherwise (so no match between
|
|
the selection count and the line count) paste all lines into each selection.
|
|
For the latter we don't need handling here: this is Scintilla's default
|
|
behaviour if SC_MULTIPASTE_EACH is on. }
|
|
Result := False;
|
|
var SelectionCount := AMemo.SelectionCount;
|
|
if SelectionCount > 1 then begin
|
|
var PasteLines := Clipboard.AsText.Replace(#13#10, #13).Split([#13, #10]);
|
|
if SelectionCount = Length(PasteLines) then begin
|
|
AMemo.BeginUndoAction;
|
|
try
|
|
for var I := 0 to SelectionCount-1 do begin
|
|
var StartPos := AMemo.SelectionStartPosition[I]; { Can't use AMemo.GetSelections because each paste can update other selections }
|
|
var EndPos := AMemo.SelectionEndPosition[I];
|
|
AMemo.ReplaceTextRange(StartPos, EndPos, PasteLines[I], srmMinimal);
|
|
{ Update the selection to an empty selection at the end of the inserted
|
|
text, just like ReplaceMainSelText }
|
|
var Pos := AMemo.Target.EndPos; { ReplaceTextRange updates the target }
|
|
AMemo.SelectionCaretPosition[I] := Pos;
|
|
AMemo.SelectionAnchorPosition[I] := Pos;
|
|
end;
|
|
{ Be like SCI_PASTE }
|
|
AMemo.ChooseCaretX;
|
|
AMemo.ScrollCaretIntoView;
|
|
finally
|
|
AMemo.EndUndoAction;
|
|
end;
|
|
Result := True;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.EPasteClick(Sender: TObject);
|
|
begin
|
|
if not MultipleSelectionPasteFromClipboard(FActiveMemo) then
|
|
FActiveMemo.PasteFromClipboard;
|
|
end;
|
|
|
|
procedure TMainForm.EDeleteClick(Sender: TObject);
|
|
begin
|
|
FActiveMemo.ClearSelection;
|
|
end;
|
|
|
|
procedure TMainForm.ESelectAllClick(Sender: TObject);
|
|
begin
|
|
FActiveMemo.SelectAll;
|
|
end;
|
|
|
|
procedure TMainForm.ESelectAllOccurrencesClick(Sender: TObject);
|
|
begin
|
|
{ Might be called even if ESelectAllOccurrences.Enabled would be False in EMenuClick }
|
|
if FActiveMemo.SelEmpty then begin
|
|
{ If the selection is empty then SelectAllOccurrences will actually just select
|
|
the word at caret which is not what we want, so preselect this word ourselves }
|
|
var Range := FActiveMemo.WordAtCaretRange;
|
|
if Range.StartPos <> Range.EndPos then
|
|
FActiveMemo.SetSingleSelection(Range.EndPos, Range.StartPos);
|
|
end;
|
|
FActiveMemo.SelectAllOccurrences([sfoMatchCase]);
|
|
end;
|
|
|
|
procedure TMainForm.ESelectNextOccurrenceClick(Sender: TObject);
|
|
begin
|
|
{ Might be called even if ESelectNextOccurrence.Enabled would be False in EMenuClick }
|
|
FActiveMemo.SelectNextOccurrence([sfoMatchCase]);
|
|
end;
|
|
|
|
procedure TMainForm.EToggleLinesCommentClick(Sender: TObject);
|
|
begin
|
|
var AMemo := FActiveMemo;
|
|
|
|
{ Based on SciTE 5.50's SciTEBase::StartBlockComment - only toggles comments
|
|
for the main selection }
|
|
|
|
var Selection := AMemo.Selection;
|
|
var CaretPosition := AMemo.CaretPosition;
|
|
// checking if caret is located in _beginning_ of selected block
|
|
var MoveCaret := CaretPosition < Selection.EndPos;
|
|
var SelStartLine := AMemo.GetLineFromPosition(Selection.StartPos);
|
|
var SelEndLine := AMemo.GetLineFromPosition(Selection.EndPos);
|
|
var Lines := SelEndLine - SelStartLine;
|
|
var FirstSelLineStart := AMemo.GetPositionFromLine(SelStartLine);
|
|
// "caret return" is part of the last selected line
|
|
if (Lines > 0) and (Selection.EndPos = AMemo.GetPositionFromLine(SelEndLine)) then
|
|
Dec(SelEndLine);
|
|
{ We rely on the styler to identify [Code] section lines, but we
|
|
may be searching into areas that haven't been styled yet }
|
|
AMemo.StyleNeeded(Selection.EndPos);
|
|
AMemo.BeginUndoAction;
|
|
try
|
|
var LastLongCommentLength := 0;
|
|
for var I := SelStartLine to SelEndLine do begin
|
|
var LineIndent := AMemo.GetLineIndentPosition(I);
|
|
var LineEnd := AMemo.GetLineEndPosition(I);
|
|
var LineBuf := AMemo.GetTextRange(LineIndent, LineEnd);
|
|
// empty lines are not commented
|
|
if LineBuf = '' then
|
|
Continue;
|
|
var Comment: String;
|
|
if LineBuf.StartsWith('//') or
|
|
(FMemosStyler.GetSectionFromLineState(AMemo.Lines.State[I]) = scCode) then
|
|
Comment := '//'
|
|
else
|
|
Comment := ';';
|
|
var LongComment := Comment + ' ';
|
|
LastLongCommentLength := Length(LongComment);
|
|
if LineBuf.StartsWith(Comment) then begin
|
|
var CommentLength := Length(Comment);
|
|
if LineBuf.StartsWith(LongComment) then begin
|
|
// Removing comment with space after it.
|
|
CommentLength := Length(LongComment);
|
|
end;
|
|
AMemo.Selection := TScintRange.Create(LineIndent, LineIndent + CommentLength);
|
|
AMemo.SelText := '';
|
|
if I = SelStartLine then // is this the first selected line?
|
|
Dec(Selection.StartPos, CommentLength);
|
|
Dec(Selection.EndPos, CommentLength); // every iteration
|
|
Continue;
|
|
end;
|
|
if I = SelStartLine then // is this the first selected line?
|
|
Inc(Selection.StartPos, Length(LongComment));
|
|
Inc(Selection.EndPos, Length(LongComment)); // every iteration
|
|
AMemo.Call(SCI_INSERTTEXT, LineIndent, AMemo.ConvertStringToRawString(LongComment));
|
|
end;
|
|
// after uncommenting selection may promote itself to the lines
|
|
// before the first initially selected line;
|
|
// another problem - if only comment symbol was selected;
|
|
if Selection.StartPos < FirstSelLineStart then begin
|
|
if Selection.StartPos >= Selection.EndPos - (LastLongCommentLength - 1) then
|
|
Selection.EndPos := FirstSelLineStart;
|
|
Selection.StartPos := FirstSelLineStart;
|
|
end;
|
|
if MoveCaret then begin
|
|
// moving caret to the beginning of selected block
|
|
AMemo.CaretPosition := Selection.EndPos;
|
|
AMemo.CaretPositionWithSelectFromAnchor := Selection.StartPos;
|
|
end else
|
|
AMemo.Selection := Selection;
|
|
finally
|
|
AMemo.EndUndoAction;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.EBraceMatchClick(Sender: TObject);
|
|
begin
|
|
var AMemo := FActiveMemo;
|
|
|
|
var Selections: TScintCaretAndAnchorList := nil;
|
|
var VirtualSpaces: TScintCaretAndAnchorList := nil;
|
|
try
|
|
Selections := TScintCaretAndAnchorList.Create;
|
|
VirtualSpaces := TScintCaretAndAnchorList.Create;
|
|
AMemo.GetSelections(Selections, VirtualSpaces);
|
|
for var I := 0 to Selections.Count-1 do begin
|
|
if VirtualSpaces[I].CaretPos = 0 then begin
|
|
var Pos := Selections[I].CaretPos;
|
|
var MatchPos := AMemo.GetPositionOfMatchingBrace(Pos);
|
|
if MatchPos = -1 then begin
|
|
Pos := AMemo.GetPositionBefore(Pos);
|
|
MatchPos := AMemo.GetPositionOfMatchingBrace(Pos)
|
|
end;
|
|
if MatchPos <> -1 then begin
|
|
AMemo.SelectionCaretPosition[I] := MatchPos;
|
|
AMemo.SelectionAnchorPosition[I] := MatchPos;
|
|
if I = 0 then
|
|
AMemo.ScrollCaretIntoView;
|
|
end;
|
|
end;
|
|
end;
|
|
finally
|
|
VirtualSpaces.Free;
|
|
Selections.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.ESelectAllFindMatchesClick(Sender: TObject);
|
|
begin
|
|
{ Might be called even if ESelectAllFindMatches.Enabled would be False in EMenuClick }
|
|
if FLastFindText <> '' then begin
|
|
var StartPos := 0;
|
|
var EndPos := FActiveMemo.RawTextLength;
|
|
var FoundRange: TScintRange;
|
|
var ClosestSelection := -1;
|
|
var ClosestSelectionDistance := 0; { Silence compiler }
|
|
var CaretPos := FActiveMemo.CaretPosition;
|
|
|
|
while (StartPos < EndPos) and
|
|
FActiveMemo.FindText(StartPos, EndPos, FLastFindText,
|
|
FindOptionsToSearchOptions(FLastFindOptions, FLastFindRegEx), FoundRange) do begin
|
|
if StartPos = 0 then
|
|
FActiveMemo.SetSingleSelection(FoundRange.EndPos, FoundRange.StartPos)
|
|
else
|
|
FActiveMemo.AddSelection(FoundRange.EndPos, FoundRange.StartPos);
|
|
|
|
var Distance := Abs(CaretPos-FoundRange.EndPos);
|
|
if (ClosestSelection = -1) or (Distance < ClosestSelectionDistance) then begin
|
|
ClosestSelection := FActiveMemo.SelectionCount-1;
|
|
ClosestSelectionDistance := Distance;
|
|
end;
|
|
|
|
StartPos := FoundRange.EndPos;
|
|
end;
|
|
if ClosestSelection <> -1 then begin
|
|
FActiveMemo.MainSelection := ClosestSelection;
|
|
FActiveMemo.ScrollCaretIntoView;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.VMenuClick(Sender: TObject);
|
|
begin
|
|
VZoomIn.Enabled := (FActiveMemo.Zoom < 20);
|
|
VZoomOut.Enabled := (FActiveMemo.Zoom > -10);
|
|
VZoomReset.Enabled := (FActiveMemo.Zoom <> 0);
|
|
VToolbar.Checked := ToolbarPanel.Visible;
|
|
VStatusBar.Checked := StatusBar.Visible;
|
|
VNextTab.Enabled := MemosTabSet.Visible and (MemosTabSet.Tabs.Count > 1);
|
|
VPreviousTab.Enabled := VNextTab.Enabled;
|
|
VCloseCurrentTab.Enabled := MemosTabSet.Visible and (FActiveMemo <> FMainMemo) and (FActiveMemo <> FPreprocessorOutputMemo);
|
|
VReopenTab.Visible := MemosTabSet.Visible and (FHiddenFiles.Count > 0);
|
|
if VReopenTab.Visible then
|
|
UpdateReopenTabMenu(VReopenTab);
|
|
VReopenTabs.Visible := VReopenTab.Visible;
|
|
VHide.Checked := not StatusPanel.Visible;
|
|
VCompilerOutput.Checked := StatusPanel.Visible and (OutputTabSet.TabIndex = tiCompilerOutput);
|
|
VDebugOutput.Checked := StatusPanel.Visible and (OutputTabSet.TabIndex = tiDebugOutput);
|
|
VDebugCallStack.Checked := StatusPanel.Visible and (OutputTabSet.TabIndex = tiDebugCallStack);
|
|
VFindResults.Checked := StatusPanel.Visible and (OutputTabSet.TabIndex = tiFindResults);
|
|
VWordWrap.Checked := FOptions.WordWrap;
|
|
|
|
ApplyMenuBitmaps(Sender as TMenuItem);
|
|
end;
|
|
|
|
procedure TMainForm.VNextTabClick(Sender: TObject);
|
|
var
|
|
NewTabIndex: Integer;
|
|
begin
|
|
NewTabIndex := MemosTabSet.TabIndex+1;
|
|
if NewTabIndex >= MemosTabSet.Tabs.Count then
|
|
NewTabIndex := 0;
|
|
MemosTabSet.TabIndex := NewTabIndex;
|
|
end;
|
|
|
|
procedure TMainForm.VPreviousTabClick(Sender: TObject);
|
|
var
|
|
NewTabIndex: Integer;
|
|
begin
|
|
NewTabIndex := MemosTabSet.TabIndex-1;
|
|
if NewTabIndex < 0 then
|
|
NewTabIndex := MemosTabSet.Tabs.Count-1;
|
|
MemosTabSet.TabIndex := NewTabIndex;
|
|
end;
|
|
|
|
procedure TMainForm.CloseTab(const TabIndex: Integer);
|
|
begin
|
|
var Memo := TabIndexToMemo(TabIndex, MemosTabSet.Tabs.Count-1);
|
|
var MemoWasActiveMemo := Memo = FActiveMemo;
|
|
|
|
MemosTabSet.Tabs.Delete(TabIndex); { This will not change MemosTabset.TabIndex }
|
|
MemosTabSet.Hints.Delete(TabIndex);
|
|
MemosTabSet.CloseButtons.Delete(TabIndex);
|
|
|
|
FHiddenFiles.Add((Memo as TIDEScintFileEdit).Filename);
|
|
InvalidateStatusPanel(spHiddenFilesCount);
|
|
BuildAndSaveKnownIncludedAndHiddenFiles;
|
|
|
|
{ Because MemosTabSet.Tabs and FHiddenFiles have both been updated now,
|
|
hereafter setting TabIndex will not select the memo we're closing
|
|
even if it's not hidden yet because TabIndexToMemo as called by
|
|
MemosTabSetClick will skip it }
|
|
|
|
if MemoWasActiveMemo then begin
|
|
{ Select next tab, except when we're already at the end. Avoiding flicker by
|
|
doing this before hiding old active memo. We do this in a dirty way by
|
|
clicking two tabs while making sure TabSetClick doesn't see the first
|
|
'fake' one. }
|
|
FIgnoreTabSetClick := True;
|
|
try
|
|
VNextTabClick(Self);
|
|
finally
|
|
FIgnoreTabSetClick := False;
|
|
end;
|
|
VPreviousTabClick(Self);
|
|
Memo.CancelAutoCompleteAndCallTip;
|
|
Memo.Visible := False;
|
|
end else if TabIndex < MemosTabset.TabIndex then
|
|
MemosTabSet.TabIndex := MemosTabset.TabIndex-1; { Reselect old selected tab }
|
|
end;
|
|
|
|
procedure TMainForm.VCloseCurrentTabClick(Sender: TObject);
|
|
begin
|
|
CloseTab(MemosTabSet.TabIndex);
|
|
end;
|
|
|
|
procedure TMainForm.ReopenTabOrTabs(const HiddenFileIndex: Integer;
|
|
const Activate: Boolean);
|
|
begin
|
|
var ReopenFilename: String;
|
|
if HiddenFileIndex >= 0 then begin
|
|
ReopenFilename := FHiddenFiles[HiddenFileIndex];
|
|
FHiddenFiles.Delete(HiddenFileIndex);
|
|
end else begin
|
|
ReopenFilename := FHiddenFiles[0];
|
|
FHiddenFiles.Clear;
|
|
end;
|
|
InvalidateStatusPanel(spHiddenFilesCount);
|
|
|
|
UpdatePreprocMemos;
|
|
BuildAndSaveKnownIncludedAndHiddenFiles;
|
|
|
|
{ Activate the memo if requested }
|
|
if Activate then begin
|
|
for var Memo in FFileMemos do begin
|
|
if Memo.Used and (PathCompare(Memo.Filename, ReopenFilename) = 0) then begin
|
|
MemosTabSet.TabIndex := MemoToTabIndex(memo);
|
|
Break;
|
|
end;
|
|
end
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.ReopenTabClick(Sender: TObject);
|
|
begin
|
|
ReopenTabOrTabs((Sender as TMenuItem).Tag, True);
|
|
end;
|
|
|
|
procedure TMainForm.VReopenTabsClick(Sender: TObject);
|
|
begin
|
|
ReopenTabOrTabs(-1, True);
|
|
end;
|
|
|
|
procedure TMainForm.VZoomInClick(Sender: TObject);
|
|
begin
|
|
FActiveMemo.ZoomIn; { MemoZoom will zoom the other memos }
|
|
end;
|
|
|
|
procedure TMainForm.VZoomOutClick(Sender: TObject);
|
|
begin
|
|
FActiveMemo.ZoomOut;
|
|
end;
|
|
|
|
procedure TMainForm.VZoomResetClick(Sender: TObject);
|
|
begin
|
|
FActiveMemo.Zoom := 0;
|
|
end;
|
|
|
|
procedure TMainForm.VToolbarClick(Sender: TObject);
|
|
begin
|
|
ToolbarPanel.Visible := not ToolbarPanel.Visible;
|
|
end;
|
|
|
|
procedure TMainForm.VStatusBarClick(Sender: TObject);
|
|
begin
|
|
StatusBar.Visible := not StatusBar.Visible;
|
|
end;
|
|
|
|
procedure TMainForm.VWordWrapClick(Sender: TObject);
|
|
begin
|
|
FOptions.WordWrap := not FOptions.WordWrap;
|
|
SyncEditorOptions;
|
|
var Ini := TConfigIniFile.Create;
|
|
try
|
|
Ini.WriteBool('Options', 'WordWrap', FOptions.WordWrap);
|
|
finally
|
|
Ini.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.SetStatusPanelVisible(const AVisible: Boolean);
|
|
var
|
|
CaretWasInView: Boolean;
|
|
begin
|
|
if StatusPanel.Visible <> AVisible then begin
|
|
CaretWasInView := FActiveMemo.IsPositionInViewVertically(FActiveMemo.CaretPosition);
|
|
if AVisible then begin
|
|
{ Ensure the status panel height isn't out of range before showing }
|
|
UpdateStatusPanelHeight(StatusPanel.Height);
|
|
SplitPanel.Top := ClientHeight;
|
|
StatusPanel.Top := ClientHeight;
|
|
end
|
|
else begin
|
|
if StatusPanel.ContainsControl(ActiveControl) then
|
|
ActiveControl := FActiveMemo;
|
|
end;
|
|
SplitPanel.Visible := AVisible;
|
|
StatusPanel.Visible := AVisible;
|
|
if AVisible and CaretWasInView then begin
|
|
{ If the caret was in view, make sure it still is }
|
|
FActiveMemo.ScrollCaretIntoView;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.VHideClick(Sender: TObject);
|
|
begin
|
|
SetStatusPanelVisible(False);
|
|
end;
|
|
|
|
procedure TMainForm.VCompilerOutputClick(Sender: TObject);
|
|
begin
|
|
OutputTabSet.TabIndex := tiCompilerOutput;
|
|
SetStatusPanelVisible(True);
|
|
end;
|
|
|
|
procedure TMainForm.VDebugOutputClick(Sender: TObject);
|
|
begin
|
|
OutputTabSet.TabIndex := tiDebugOutput;
|
|
SetStatusPanelVisible(True);
|
|
end;
|
|
|
|
procedure TMainForm.VDebugCallStackClick(Sender: TObject);
|
|
begin
|
|
OutputTabSet.TabIndex := tiDebugCallStack;
|
|
SetStatusPanelVisible(True);
|
|
end;
|
|
|
|
procedure TMainForm.VFindResultsClick(Sender: TObject);
|
|
begin
|
|
OutputTabSet.TabIndex := tiFindResults;
|
|
SetStatusPanelVisible(True);
|
|
end;
|
|
|
|
procedure TMainForm.BMenuClick(Sender: TObject);
|
|
begin
|
|
BLowPriority.Checked := FOptions.LowPriorityDuringCompile;
|
|
BOpenOutputFolder.Enabled := (FCompiledExe <> '');
|
|
|
|
ApplyMenuBitmaps(Sender as TMenuItem);
|
|
end;
|
|
|
|
procedure TMainForm.BCompileClick(Sender: TObject);
|
|
begin
|
|
CompileFile('', False);
|
|
end;
|
|
|
|
procedure TMainForm.BStopCompileClick(Sender: TObject);
|
|
begin
|
|
SetAppTaskbarProgressState(tpsPaused);
|
|
try
|
|
if MsgBox('Are you sure you want to abort the compile?', SCompilerFormCaption,
|
|
mbConfirmation, MB_YESNO or MB_DEFBUTTON2) <> IDNO then
|
|
FCompileWantAbort := True;
|
|
finally
|
|
SetAppTaskbarProgressState(tpsNormal);
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.BLowPriorityClick(Sender: TObject);
|
|
begin
|
|
FOptions.LowPriorityDuringCompile := not FOptions.LowPriorityDuringCompile;
|
|
{ If a compile is already in progress, change the priority now }
|
|
if FCompiling then
|
|
SetLowPriority(FOptions.LowPriorityDuringCompile, FSavePriorityClass);
|
|
end;
|
|
|
|
procedure TMainForm.BOpenOutputFolderClick(Sender: TObject);
|
|
begin
|
|
LaunchFileOrURL(AddBackslash(GetSystemWinDir) + 'explorer.exe',
|
|
Format('/select,"%s"', [FCompiledExe]));
|
|
end;
|
|
|
|
procedure TMainForm.HShortcutsDocClick(Sender: TObject);
|
|
begin
|
|
if Assigned(HtmlHelp) then
|
|
HtmlHelp(GetDesktopWindow, PChar(GetHelpFile), HH_DISPLAY_TOPIC, Cardinal(PChar('topic_compformshortcuts.htm')));
|
|
end;
|
|
|
|
procedure TMainForm.HRegExDocClick(Sender: TObject);
|
|
begin
|
|
if Assigned(HtmlHelp) then
|
|
HtmlHelp(GetDesktopWindow, PChar(GetHelpFile), HH_DISPLAY_TOPIC, Cardinal(PChar('topic_compformregex.htm')));
|
|
end;
|
|
|
|
procedure TMainForm.HDocClick(Sender: TObject);
|
|
begin
|
|
if Assigned(HtmlHelp) then
|
|
HtmlHelp(GetDesktopWindow, PChar(GetHelpFile), HH_DISPLAY_TOPIC, 0);
|
|
end;
|
|
|
|
procedure TMainForm.HExamplesClick(Sender: TObject);
|
|
begin
|
|
LaunchFileOrURL(PathExtractPath(NewParamStr(0)) + 'Examples');
|
|
end;
|
|
|
|
procedure TMainForm.HFaqClick(Sender: TObject);
|
|
begin
|
|
LaunchFileOrURL(PathExtractPath(NewParamStr(0)) + 'isfaq.url');
|
|
end;
|
|
|
|
procedure TMainForm.HWhatsNewClick(Sender: TObject);
|
|
begin
|
|
LaunchFileOrURL(PathExtractPath(NewParamStr(0)) + {$IFDEF DEBUG} '..\..\' + {$ENDIF} 'whatsnew.htm');
|
|
end;
|
|
|
|
procedure TMainForm.HWebsiteClick(Sender: TObject);
|
|
begin
|
|
LaunchFileOrURL('https://jrsoftware.org/isinfo.php');
|
|
end;
|
|
|
|
procedure TMainForm.HMailingListClick(Sender: TObject);
|
|
begin
|
|
OpenMailingListSite;
|
|
end;
|
|
|
|
procedure TMainForm.HISPPDocClick(Sender: TObject);
|
|
begin
|
|
if Assigned(HtmlHelp) then
|
|
HtmlHelp(GetDesktopWindow, PChar(GetHelpFile), HH_DISPLAY_TOPIC, Cardinal(PChar('topic_isppoverview.htm')));
|
|
end;
|
|
|
|
procedure TMainForm.HDonateClick(Sender: TObject);
|
|
begin
|
|
OpenDonateSite;
|
|
end;
|
|
|
|
procedure TMainForm.HAboutClick(Sender: TObject);
|
|
var
|
|
S: String;
|
|
begin
|
|
{ Removing the About box or modifying any existing text inside it is a
|
|
violation of the Inno Setup license agreement; see LICENSE.TXT.
|
|
However, adding additional lines to the About box is permitted, as long as
|
|
they are placed below the original copyright notice. }
|
|
S := FCompilerVersion.Title + ' Compiler version ' +
|
|
String(FCompilerVersion.Version) + SNewLine;
|
|
if FCompilerVersion.Title <> 'Inno Setup' then
|
|
S := S + (SNewLine + 'Based on Inno Setup' + SNewLine);
|
|
S := S + ('Copyright (C) 1997-2025 Jordan Russell' + SNewLine +
|
|
'Portions Copyright (C) 2000-2025 Martijn Laan' + SNewLine +
|
|
'All rights reserved.' + SNewLine2 +
|
|
'Inno Setup home page:' + SNewLine +
|
|
'https://www.innosetup.com/' + SNewLine2 +
|
|
'RemObjects Pascal Script home page:' + SNewLine +
|
|
'https://www.remobjects.com/ps' + SNewLine2 +
|
|
'Refer to LICENSE.TXT for conditions of distribution and use.');
|
|
MsgBox(S, 'About ' + FCompilerVersion.Title, mbInformation, MB_OK);
|
|
end;
|
|
|
|
procedure TMainForm.WMStartCommandLineCompile(var Message: TMessage);
|
|
var
|
|
Code: Integer;
|
|
begin
|
|
UpdateStatusPanelHeight(ClientHeight);
|
|
Code := 0;
|
|
try
|
|
try
|
|
CompileFile(CommandLineFilename, True);
|
|
except
|
|
Code := 2;
|
|
Application.HandleException(Self);
|
|
end;
|
|
finally
|
|
Halt(Code);
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.WMStartCommandLineWizard(var Message: TMessage);
|
|
var
|
|
Code: Integer;
|
|
begin
|
|
Code := 0;
|
|
try
|
|
try
|
|
NewMainFileUsingWizard;
|
|
except
|
|
Code := 2;
|
|
Application.HandleException(Self);
|
|
end;
|
|
finally
|
|
Halt(Code);
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.WMStartNormally(var Message: TMessage);
|
|
|
|
procedure ShowStartupForm;
|
|
var
|
|
StartupForm: TStartupForm;
|
|
Ini: TConfigIniFile;
|
|
begin
|
|
ReadMRUMainFilesList;
|
|
StartupForm := TStartupForm.Create(Application);
|
|
try
|
|
StartupForm.MRUFilesList := FMRUMainFilesList;
|
|
StartupForm.StartupCheck.Checked := not FOptions.ShowStartupForm;
|
|
if StartupForm.ShowModal = mrOK then begin
|
|
if FOptions.ShowStartupForm <> not StartupForm.StartupCheck.Checked then begin
|
|
FOptions.ShowStartupForm := not StartupForm.StartupCheck.Checked;
|
|
Ini := TConfigIniFile.Create;
|
|
try
|
|
Ini.WriteBool('Options', 'ShowStartupForm', FOptions.ShowStartupForm);
|
|
finally
|
|
Ini.Free;
|
|
end;
|
|
end;
|
|
case StartupForm.Result of
|
|
srEmpty:
|
|
FNewMainFileClick(Self);
|
|
srWizard:
|
|
FNewMainFileUserWizardClick(Self);
|
|
srOpenFile:
|
|
if ConfirmCloseFile(True) then
|
|
OpenMRUMainFile(StartupForm.ResultMainFileName);
|
|
srOpenDialog:
|
|
ShowOpenMainFileDialog(False);
|
|
srOpenDialogExamples:
|
|
ShowOpenMainFileDialog(True);
|
|
end;
|
|
end;
|
|
finally
|
|
StartupForm.Free;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
if CommandLineFilename = '' then begin
|
|
if FOptions.ShowStartupForm then
|
|
ShowStartupForm;
|
|
end else
|
|
OpenFile(FMainMemo, CommandLineFilename, False);
|
|
end;
|
|
|
|
procedure TMainForm.WMSysColorChange(var Message: TMessage);
|
|
begin
|
|
inherited;
|
|
for var Memo in FMemos do
|
|
Memo.SysColorChange(Message);
|
|
end;
|
|
|
|
procedure TMainForm.UpdateReopenTabMenu(const Menu: TMenuItem);
|
|
begin
|
|
Menu.Clear;
|
|
for var I := 0 to FHiddenFiles.Count-1 do begin
|
|
var MenuItem := TMenuItem.Create(Menu);
|
|
MenuItem.Caption := '&' + IntToStr((I+1) mod 10) + ' ' + DoubleAmp(PathExtractName(FHiddenFiles[I]));
|
|
MenuItem.Tag := I;
|
|
MenuItem.OnClick := ReopenTabClick;
|
|
Menu.Add(MenuItem);
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.MemosTabSetPopupMenuClick(Sender: TObject);
|
|
begin
|
|
{ Main and preprocessor memos can't be hidden }
|
|
VCloseCurrentTab2.Enabled := (FActiveMemo <> FMainMemo) and (FActiveMemo <> FPreprocessorOutputMemo);
|
|
|
|
VReopenTab2.Visible := FHiddenFiles.Count > 0;
|
|
if VReopenTab2.Visible then
|
|
UpdateReopenTabMenu(VReopenTab2);
|
|
VReopenTabs2.Visible := VReopenTab2.Visible;
|
|
|
|
ApplyMenuBitmaps(Sender as TMenuItem)
|
|
end;
|
|
|
|
procedure TMainForm.MemosTabSetClick(Sender: TObject);
|
|
begin
|
|
if FIgnoreTabSetClick then
|
|
Exit;
|
|
|
|
var NewActiveMemo := TabIndexToMemo(MemosTabSet.TabIndex, MemosTabSet.Tabs.Count-1);
|
|
if NewActiveMemo <> FActiveMemo then begin
|
|
{ Avoiding flicker by showing new before hiding old }
|
|
NewActiveMemo.Visible := True;
|
|
var OldActiveMemo := FActiveMemo;
|
|
FActiveMemo := NewActiveMemo;
|
|
ActiveControl := NewActiveMemo;
|
|
OldActiveMemo.CancelAutoCompleteAndCallTip;
|
|
OldActiveMemo.Visible := False;
|
|
|
|
UpdateSaveMenuItemAndButton;
|
|
UpdateRunMenu;
|
|
UpdateCaretPosPanelAndBackNavStack;
|
|
UpdateEditModePanel;
|
|
UpdateModifiedPanel;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.MemosTabSetOnCloseButtonClick(Sender: TObject; Index: Integer);
|
|
begin
|
|
CloseTab(Index);
|
|
end;
|
|
|
|
procedure TMainForm.InitializeFindText(Dlg: TFindDialog);
|
|
var
|
|
S: String;
|
|
begin
|
|
S := FActiveMemo.MainSelText;
|
|
if (S <> '') and (Pos(#13, S) = 0) and (Pos(#10, S) = 0) then
|
|
Dlg.FindText := S
|
|
else
|
|
Dlg.FindText := FLastFindText;
|
|
end;
|
|
|
|
const
|
|
OldFindReplaceWndProcProp = 'OldFindReplaceWndProc';
|
|
|
|
function FindReplaceWndProc(Wnd: HWND; Msg: Cardinal; WParam: WPARAM; LParam: LPARAM): LRESULT; stdcall;
|
|
|
|
function CallDefWndProc: LRESULT;
|
|
begin
|
|
Result := CallWindowProc(Pointer(GetProp(Wnd, OldFindReplaceWndProcProp)), Wnd,
|
|
Msg, WParam, LParam);
|
|
end;
|
|
|
|
begin
|
|
case Msg of
|
|
WM_MENUCHAR:
|
|
if LoWord(wParam) = VK_RETURN then begin
|
|
var hwndCtl := GetDlgItem(Wnd, idOk);
|
|
if (hWndCtl <> 0) and IsWindowEnabled(hWndCtl) then
|
|
PostMessage(Wnd, WM_COMMAND, MakeWParam(idOk, BN_CLICKED), Windows.LPARAM(hWndCtl));
|
|
end;
|
|
WM_NCDESTROY:
|
|
begin
|
|
Result := CallDefWndProc;
|
|
RemoveProp(Wnd, OldFindReplaceWndProcProp);
|
|
Exit;
|
|
end;
|
|
end;
|
|
Result := CallDefWndProc;
|
|
end;
|
|
|
|
procedure ExecuteFindDialogAllowingAltEnter(const FindDialog: TFindDialog);
|
|
begin
|
|
var DoHook := FindDialog.Handle = 0;
|
|
FindDialog.Execute;
|
|
if DoHook then begin
|
|
SetProp(FindDialog.Handle, OldFindReplaceWndProcProp, GetWindowLong(FindDialog.Handle, GWL_WNDPROC));
|
|
SetWindowLong(FindDialog.Handle, GWL_WNDPROC, IntPtr(@FindReplaceWndProc));
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.EFindClick(Sender: TObject);
|
|
begin
|
|
ReplaceDialog.CloseDialog;
|
|
if FindDialog.Handle = 0 then
|
|
InitializeFindText(FindDialog);
|
|
if (Sender = EFind) or (Sender = EFindNext) then
|
|
FindDialog.Options := FindDialog.Options + [frDown]
|
|
else
|
|
FindDialog.Options := FindDialog.Options - [frDown];
|
|
ExecuteFindDialogAllowingAltEnter(FindDialog);
|
|
end;
|
|
|
|
procedure TMainForm.EFindInFilesClick(Sender: TObject);
|
|
begin
|
|
InitializeFindText(FindInFilesDialog);
|
|
FindInFilesDialog.Execute;
|
|
end;
|
|
|
|
procedure TMainForm.EFindNextOrPreviousClick(Sender: TObject);
|
|
begin
|
|
if FLastFindText = '' then
|
|
EFindClick(Sender)
|
|
else begin
|
|
if Sender = EFindNext then
|
|
FLastFindOptions := FLastFindOptions + [frDown]
|
|
else
|
|
FLastFindOptions := FLastFindOptions - [frDown];
|
|
FLastFindRegEx := FOptions.FindRegEx;
|
|
if not TestLastFindOptions then
|
|
Exit;
|
|
FindNext(False);
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.FindNext(const ReverseDirection: Boolean);
|
|
var
|
|
StartPos, EndPos: Integer;
|
|
Range: TScintRange;
|
|
begin
|
|
var Down := frDown in FLastFindOptions;
|
|
if ReverseDirection then
|
|
Down := not Down;
|
|
if Down then begin
|
|
StartPos := FActiveMemo.Selection.EndPos;
|
|
EndPos := FActiveMemo.RawTextLength;
|
|
end
|
|
else begin
|
|
StartPos := FActiveMemo.Selection.StartPos;
|
|
EndPos := 0;
|
|
end;
|
|
if FActiveMemo.FindText(StartPos, EndPos, FLastFindText,
|
|
FindOptionsToSearchOptions(FLastFindOptions, FLastFindRegEx), Range) then
|
|
FActiveMemo.SelectAndEnsureVisible(Range)
|
|
else
|
|
MsgBoxFmt('Cannot find "%s"', [FLastFindText], SCompilerFormCaption,
|
|
mbInformation, MB_OK);
|
|
end;
|
|
|
|
function TMainForm.StoreAndTestLastFindOptions(Sender: TObject): Boolean;
|
|
begin
|
|
{ TReplaceDialog is a subclass of TFindDialog must check for TReplaceDialog first }
|
|
if Sender is TReplaceDialog then begin
|
|
with Sender as TReplaceDialog do begin
|
|
FLastFindOptions := Options;
|
|
FLastFindText := FindText;
|
|
end;
|
|
end else begin
|
|
with Sender as TFindDialog do begin
|
|
FLastFindOptions := Options;
|
|
FLastFindText := FindText;
|
|
end;
|
|
end;
|
|
FLastFindRegEx := FOptions.FindRegEx;
|
|
|
|
Result := TestLastFindOptions;
|
|
end;
|
|
|
|
function TMainForm.TestLastFindOptions;
|
|
begin
|
|
if FLastFindRegEx then begin
|
|
Result := FActiveMemo.TestRegularExpression(FLastFindText);
|
|
if not Result then
|
|
MsgBoxFmt('Invalid regular expression "%s"', [FLastFindText], SCompilerFormCaption,
|
|
mbError, MB_OK);
|
|
end else
|
|
Result := True;
|
|
end;
|
|
|
|
procedure TMainForm.FindDialogFind(Sender: TObject);
|
|
begin
|
|
{ This event handler is shared between FindDialog & ReplaceDialog }
|
|
|
|
if not StoreAndTestLastFindOptions(Sender) then
|
|
Exit;
|
|
|
|
if GetKeyState(VK_MENU) < 0 then begin
|
|
{ Alt+Enter was used to close the dialog }
|
|
(Sender as TFindDialog).CloseDialog;
|
|
ESelectAllFindMatchesClick(Self); { Uses the copy made above }
|
|
end else
|
|
FindNext(GetKeyState(VK_SHIFT) < 0);
|
|
end;
|
|
|
|
procedure TMainForm.FindInFilesDialogFind(Sender: TObject);
|
|
begin
|
|
if not StoreAndTestLastFindOptions(Sender) then
|
|
Exit;
|
|
|
|
FindResultsList.Clear;
|
|
SendMessage(FindResultsList.Handle, LB_SETHORIZONTALEXTENT, 0, 0);
|
|
FFindResults.Clear;
|
|
|
|
var Hits := 0;
|
|
var Files := 0;
|
|
|
|
for var Memo in FFileMemos do begin
|
|
if Memo.Used then begin
|
|
var StartPos := 0;
|
|
var EndPos := Memo.RawTextLength;
|
|
var FileHits := 0;
|
|
var Range: TScintRange;
|
|
while (StartPos < EndPos) and
|
|
Memo.FindText(StartPos, EndPos, FLastFindText,
|
|
FindOptionsToSearchOptions(FLastFindOptions, FLastFindRegEx), Range) do begin
|
|
{ Also see UpdateFindResult }
|
|
var Line := Memo.GetLineFromPosition(Range.StartPos);
|
|
var Prefix := Format(' Line %d: ', [Line+1]);
|
|
var FindResult := TFindResult.Create;
|
|
FindResult.Filename := Memo.Filename;
|
|
FindResult.Line := Line;
|
|
FindResult.LineStartPos := Memo.GetPositionFromLine(Line);
|
|
FindResult.Range := Range;
|
|
FindResult.PrefixStringLength := Length(Prefix);
|
|
FFindResults.Add(FindResult);
|
|
FindResultsList.Items.AddObject(Prefix + Memo.Lines[Line], FindResult);
|
|
Inc(FileHits);
|
|
StartPos := Range.EndPos;
|
|
end;
|
|
Inc(Files);
|
|
if FileHits > 0 then begin
|
|
Inc(Hits, FileHits);
|
|
FindResultsList.Items.Insert(FindResultsList.Count-FileHits, Format('%s (%d hits):', [Memo.Filename, FileHits]));
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
FindResultsList.Items.Insert(0, Format('Find "%s" (%d hits in %d files)', [FindInFilesDialog.FindText, Hits, Files]));
|
|
|
|
FindInFilesDialog.CloseDialog;
|
|
|
|
OutputTabSet.TabIndex := tiFindResults;
|
|
SetStatusPanelVisible(True);
|
|
end;
|
|
|
|
function TMainForm.FindSetupDirectiveValue(const DirectiveName,
|
|
DefaultValue: String): String;
|
|
begin
|
|
Result := DefaultValue;
|
|
|
|
var Memo := FMainMemo; { This function only searches the main file }
|
|
var StartPos := 0;
|
|
var EndPos := Memo.RawTextLength;
|
|
var Range: TScintRange;
|
|
|
|
{ We rely on the styler to identify [Setup] section lines, but we
|
|
may be searching into areas that haven't been styled yet }
|
|
Memo.StyleNeeded(EndPos);
|
|
|
|
while (StartPos < EndPos) and
|
|
Memo.FindText(StartPos, EndPos, DirectiveName, [sfoWholeWord], Range) do begin
|
|
var Line := Memo.GetLineFromPosition(Range.StartPos);
|
|
if FMemosStyler.GetSectionFromLineState(Memo.Lines.State[Line]) = scSetup then begin
|
|
var LineValue := Memo.Lines[Line].Trim; { LineValue can't be empty }
|
|
if LineValue[1] <> ';' then begin
|
|
var LineParts := LineValue.Split(['=']);
|
|
if (Length(LineParts) = 2) and SameText(LineParts[0].Trim, DirectiveName) then begin
|
|
Result := LineParts[1].Trim;
|
|
{ If Result is surrounded in quotes, remove them, just like TSetupCompiler.SeparateDirective }
|
|
if (Length(Result) >= 2) and
|
|
(Result[1] = '"') and (Result[Length(Result)] = '"') then
|
|
Result := Copy(Result, 2, Length(Result)-2);
|
|
Exit; { Compiler doesn't allow a directive to be specified twice so we can exit now }
|
|
end;
|
|
end;
|
|
end;
|
|
StartPos := Range.EndPos;
|
|
end;
|
|
end;
|
|
|
|
function TMainForm.FindSetupDirectiveValue(const DirectiveName: String;
|
|
DefaultValue: Boolean): Boolean;
|
|
begin
|
|
var Value := FindSetupDirectiveValue(DirectiveName, IfThen(DefaultValue, '1', '0'));
|
|
if not TryStrToBoolean(Value, Result) then
|
|
Result := DefaultValue;
|
|
end;
|
|
|
|
procedure TMainForm.EReplaceClick(Sender: TObject);
|
|
begin
|
|
FindDialog.CloseDialog;
|
|
if ReplaceDialog.Handle = 0 then begin
|
|
InitializeFindText(ReplaceDialog);
|
|
ReplaceDialog.ReplaceText := FLastReplaceText;
|
|
end;
|
|
ExecuteFindDialogAllowingAltEnter(ReplaceDialog);
|
|
end;
|
|
|
|
procedure TMainForm.ReplaceDialogReplace(Sender: TObject);
|
|
begin
|
|
if not StoreAndTestLastFindOptions(Sender) then
|
|
Exit;
|
|
|
|
FLastReplaceText := ReplaceDialog.ReplaceText;
|
|
var ReplaceMode := RegExToReplaceMode(FLastFindRegEx);
|
|
|
|
if frReplaceAll in FLastFindOptions then begin
|
|
var ReplaceCount := 0;
|
|
FActiveMemo.BeginUndoAction;
|
|
try
|
|
var Pos := 0;
|
|
var Range: TScintRange;
|
|
while FActiveMemo.FindText(Pos, FActiveMemo.RawTextLength, FLastFindText,
|
|
FindOptionsToSearchOptions(FLastFindOptions, FLastFindRegEx), Range) do begin
|
|
var NewRange := FActiveMemo.ReplaceTextRange(Range.StartPos, Range.EndPos, FLastReplaceText, ReplaceMode);
|
|
Pos := NewRange.EndPos;
|
|
Inc(ReplaceCount);
|
|
end;
|
|
finally
|
|
FActiveMemo.EndUndoAction;
|
|
end;
|
|
if ReplaceCount = 0 then
|
|
MsgBoxFmt('Cannot find "%s"', [FLastFindText], SCompilerFormCaption,
|
|
mbInformation, MB_OK)
|
|
else
|
|
MsgBoxFmt('%d occurrence(s) replaced.', [ReplaceCount], SCompilerFormCaption,
|
|
mbInformation, MB_OK);
|
|
end
|
|
else begin
|
|
if FActiveMemo.MainSelTextEquals(FLastFindText, FindOptionsToSearchOptions(frMatchCase in FLastFindOptions, FLastFindRegEx)) then begin
|
|
{ Note: the MainSelTextEquals above performs a search so the replacement
|
|
below is safe even if the user just enabled regex }
|
|
FActiveMemo.ReplaceMainSelText(FLastReplaceText, ReplaceMode);
|
|
end;
|
|
FindNext(GetKeyState(VK_SHIFT) < 0);
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.EFindRegExClick(Sender: TObject);
|
|
begin
|
|
{ If EFindRegEx uses Alt+R as the shortcut just like VSCode then also handle it like VSCode:
|
|
when the memo does not have the focus open the Run menu (also Alt+R) instead }
|
|
if not FActiveMemo.Focused and (EFindRegEx.ShortCut = ShortCut(Ord('R'), [ssAlt])) then
|
|
SendMessage(Handle, WM_SYSCOMMAND, SC_KEYMENU, Ord('r'))
|
|
else begin
|
|
FOptions.FindRegEx := not FOptions.FindRegEx;
|
|
UpdateFindRegExUI;
|
|
var Ini := TConfigIniFile.Create;
|
|
try
|
|
Ini.WriteBool('Options', 'FindRegEx', FOptions.FindRegEx);
|
|
finally
|
|
Ini.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.EFoldOrUnfoldLineClick(Sender: TObject);
|
|
begin
|
|
FActiveMemo.FoldLine(FActiveMemo.CaretLine, Sender = EFoldLine);
|
|
end;
|
|
|
|
procedure TMainForm.UpdateStatusPanelHeight(H: Integer);
|
|
var
|
|
MinHeight, MaxHeight: Integer;
|
|
begin
|
|
MinHeight := (3 * DebugOutputList.ItemHeight + ToCurrentPPI(4)) + OutputTabSet.Height;
|
|
MaxHeight := BodyPanel.ClientHeight - ToCurrentPPI(48) - SplitPanel.Height;
|
|
if H > MaxHeight then H := MaxHeight;
|
|
if H < MinHeight then H := MinHeight;
|
|
StatusPanel.Height := H;
|
|
end;
|
|
|
|
procedure TMainForm.UpdateOccurrenceIndicators(const AMemo: TIDEScintEdit);
|
|
|
|
procedure FindTextAndAddRanges(const AMemo: TIDEScintEdit;
|
|
const TextToFind: TScintRawString; const Options: TScintFindOptions;
|
|
const Selections, IndicatorRanges: TScintRangeList);
|
|
begin
|
|
if TScintEdit.RawStringIsBlank(TextToFind) then
|
|
Exit;
|
|
|
|
var StartPos := 0;
|
|
var EndPos := AMemo.RawTextLength;
|
|
var FoundRange: TScintRange;
|
|
|
|
while (StartPos < EndPos) and
|
|
AMemo.FindRawText(StartPos, EndPos, TextToFind, Options, FoundRange) do begin
|
|
StartPos := FoundRange.EndPos;
|
|
|
|
{ Don't add indicators on lines which have a line marker }
|
|
var Line := AMemo.GetLineFromPosition(FoundRange.StartPos);
|
|
var Markers := AMemo.GetMarkers(Line);
|
|
if Markers * [mlmError, mlmBreakpointBad, mlmStep] <> [] then
|
|
Continue;
|
|
|
|
{ Add indicator while making sure it does not overlap any regular selection
|
|
styling for either the main selection or any additional selection. Does
|
|
not account for an indicator overlapping more than 1 selection. }
|
|
var OverlappingSelection: TScintRange;
|
|
if Selections.Overlaps(FoundRange, OverlappingSelection) then begin
|
|
if FoundRange.StartPos < OverlappingSelection.StartPos then
|
|
IndicatorRanges.Add(TScintRange.Create(FoundRange.StartPos, OverlappingSelection.StartPos));
|
|
if FoundRange.EndPos > OverlappingSelection.EndPos then
|
|
IndicatorRanges.Add(TScintRange.Create(OverlappingSelection.EndPos, FoundRange.EndPos));
|
|
end else
|
|
IndicatorRanges.Add(FoundRange);
|
|
end;
|
|
end;
|
|
|
|
function HighlightAtCursorAllowed(const Word: TScintRawString): Boolean;
|
|
begin
|
|
const Section = FMemosStyler.GetSectionFromLineState(FActiveMemo.Lines.State[FActiveMemo.CaretLine]);
|
|
Result := FMemosStyler.HighlightAtCursorAllowed(Section, FActiveMemo.ConvertRawStringToString(Word));
|
|
end;
|
|
|
|
begin
|
|
{ Add occurrence indicators for the word at cursor if there's any and the
|
|
main selection is within this word. On top of those add occurrence indicators
|
|
for the main selected text if there's any. Don't do anything if the main
|
|
selection is not single line. All of these things are just like VSCode. }
|
|
|
|
var MainSelection: TScintRange;
|
|
var MainSelNotEmpty := AMemo.SelNotEmpty(MainSelection);
|
|
var MainSelSingleLine := AMemo.GetLineFromPosition(MainSelection.StartPos) =
|
|
AMemo.GetLineFromPosition(MainSelection.EndPos);
|
|
|
|
var IndicatorRanges: TScintRangeList := nil;
|
|
var Selections: TScintRangeList := nil;
|
|
try
|
|
IndicatorRanges := TScintRangeList.Create;
|
|
Selections := TScintRangeList.Create;
|
|
|
|
if FOptions.HighlightWordAtCursorOccurrences and (AMemo.CaretVirtualSpace = 0) and MainSelSingleLine then begin
|
|
var Word := AMemo.WordAtCaretRange;
|
|
if (Word.StartPos <> Word.EndPos) and MainSelection.Within(Word) then begin
|
|
var TextToIndicate := AMemo.GetRawTextRange(Word.StartPos, Word.EndPos);
|
|
if HighlightAtCursorAllowed(TextToIndicate) then begin
|
|
AMemo.GetSelections(Selections); { Gets any additional selections as well }
|
|
FindTextAndAddRanges(AMemo, TextToIndicate, [sfoMatchCase, sfoWholeWord], Selections, IndicatorRanges);
|
|
end;
|
|
end;
|
|
end;
|
|
AMemo.UpdateIndicators(IndicatorRanges, minWordAtCursorOccurrence);
|
|
|
|
IndicatorRanges.Clear;
|
|
if FOptions.HighlightSelTextOccurrences and MainSelNotEmpty and MainSelSingleLine then begin
|
|
var TextToIndicate := AMemo.RawMainSelText;
|
|
if Selections.Count = 0 then { If 0 then we didn't already call GetSelections above}
|
|
AMemo.GetSelections(Selections);
|
|
FindTextAndAddRanges(AMemo, TextToIndicate, [], Selections, IndicatorRanges);
|
|
end;
|
|
AMemo.UpdateIndicators(IndicatorRanges, minSelTextOccurrence);
|
|
finally
|
|
Selections.Free;
|
|
IndicatorRanges.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.UpdateImages;
|
|
{ Should be called at startup and after DPI changes }
|
|
begin
|
|
var WH := MulDiv(16, CurrentPPI, 96);
|
|
var Images := ImagesModule.LightToolBarImageCollection;
|
|
|
|
var Image := Images.GetSourceImage(Images.GetIndexByName('heart-filled'), WH, WH);
|
|
UpdatePanelDonateImage.Picture.Graphic:= Image;
|
|
end;
|
|
|
|
procedure TMainForm.UpdateOutputTabSetListsItemHeightAndDebugTimeWidth;
|
|
{ Should be called at startup and after DPI changes }
|
|
begin
|
|
CompilerOutputList.Canvas.Font.Assign(CompilerOutputList.Font);
|
|
CompilerOutputList.ItemHeight := CompilerOutputList.Canvas.TextHeight('0') + 1;
|
|
|
|
DebugOutputList.Canvas.Font.Assign(DebugOutputList.Font);
|
|
FDebugLogListTimestampsWidth := DebugOutputList.Canvas.TextWidth(Format('[00%s00%s00%s000] ', [FormatSettings.TimeSeparator, FormatSettings.TimeSeparator, FormatSettings.DecimalSeparator]));
|
|
DebugOutputList.ItemHeight := DebugOutputList.Canvas.TextHeight('0') + 1;
|
|
|
|
DebugCallStackList.Canvas.Font.Assign(DebugCallStackList.Font);
|
|
DebugCallStackList.ItemHeight := DebugCallStackList.Canvas.TextHeight('0') + 1;
|
|
|
|
FindResultsList.Canvas.Font.Assign(FindResultsList.Font);
|
|
FindResultsList.ItemHeight := FindResultsList.Canvas.TextHeight('0') + 1;
|
|
end;
|
|
|
|
type
|
|
TBitmapWithBits = class
|
|
Handle: HBITMAP;
|
|
pvBits: Pointer;
|
|
destructor Destroy; override;
|
|
end;
|
|
|
|
destructor TBitmapWithBits.Destroy;
|
|
begin
|
|
if Handle <> 0 then
|
|
DeleteObject(Handle);
|
|
inherited;
|
|
end;
|
|
|
|
procedure TMainForm.UpdateMarginsAndAutoCompleteIcons;
|
|
{ Should be called at startup and after theme and DPI changes }
|
|
|
|
type
|
|
TMarkerOrACBitmaps = TObjectDictionary<Integer, TBitmapWithBits>;
|
|
|
|
procedure SwapRedBlue(const pvBits: PByte; Width, Height: Integer);
|
|
begin
|
|
var pvPixel := pvBits;
|
|
var pvMax := pvBits + 4*Width*Height;
|
|
while pvPixel < pvMax do begin
|
|
var Tmp := PByte(pvPixel)^;
|
|
PByte(pvPixel)^ := PByte(pvPixel + 2)^;
|
|
PByte(pvPixel + 2)^ := Tmp;
|
|
Inc(pvPixel, 4);
|
|
end;
|
|
end;
|
|
|
|
procedure AddMarkerOrACBitmap(const MarkerOrACBitmaps: TMarkerOrACBitmaps; const DC: HDC; const BitmapInfo: TBitmapInfo;
|
|
const MarkerNumberOrACType: Integer; const BkBrush: TBrush; const ImageList: TVirtualImageList; const ImageName: String);
|
|
begin
|
|
{ Prepare a bitmap and select it }
|
|
var pvBits: Pointer;
|
|
var Bitmap := CreateDIBSection(DC, BitmapInfo, DIB_RGB_COLORS, pvBits, 0, 0);
|
|
var OldBitmap := SelectObject(DC, Bitmap);
|
|
|
|
{ Fill the entire bitmap to avoid any alpha so we don't have to worry about
|
|
whether will be premultiplied or not (it was in tests) when Scintilla wants
|
|
it without premultiplication }
|
|
var Width := BitmapInfo.bmiHeader.biWidth;
|
|
var Height := Abs(BitmapInfo.bmiHeader.biHeight);
|
|
var Rect := TRect.Create(0, 0, Width, Height);
|
|
FillRect(DC, Rect, BkBrush.Handle);
|
|
|
|
{ Draw the image - the result will be in pvBits }
|
|
if ImageList_Draw(ImageList.Handle, ImageList.GetIndexByName(ImageName), DC, 0, 0, ILD_TRANSPARENT) then begin
|
|
SwapRedBlue(pvBits, Width, Height); { Change pvBits from BGRA to RGBA like Scintilla wants }
|
|
var Bitmap2 := TBitmapWithBits.Create;
|
|
Bitmap2.Handle := Bitmap;
|
|
Bitmap2.pvBits := pvBits;
|
|
MarkerOrACBitmaps.Add(MarkerNumberOrACType, Bitmap2);
|
|
end else begin
|
|
SelectObject(DC, OldBitmap);
|
|
DeleteObject(Bitmap);
|
|
end;
|
|
end;
|
|
|
|
type
|
|
TMarkerNumberOrACType = TPair<Integer, String>;
|
|
|
|
function NNT(const MarkerNumberOrACType: Integer; const Name: String): TMarkerNumberOrACType;
|
|
begin
|
|
Result := TMarkerNumberOrACType.Create(MarkerNumberOrACType, Name); { This is a record so no need to free }
|
|
end;
|
|
|
|
begin
|
|
var ImageList := ThemedMarkersAndACVirtualImageList;
|
|
|
|
var DC := CreateCompatibleDC(0);
|
|
if DC <> 0 then begin
|
|
try
|
|
var MarkerBitmaps: TMarkerOrACBitmaps := nil;
|
|
var MarkerBkBrush: TBrush := nil;
|
|
var AutoCompleteBitmaps: TMarkerOrACBitmaps := nil;
|
|
var AutoCompleteBkBrush: TBrush := nil;
|
|
try
|
|
var BitmapInfo := CreateBitmapInfo(ImageList.Width, -ImageList.Height, 32); { This is a record so no need to free }
|
|
|
|
MarkerBitmaps := TMarkerOrACBitmaps.Create([doOwnsValues]);
|
|
MarkerBkBrush := TBrush.Create;
|
|
MarkerBkBrush.Color := FTheme.Colors[tcMarginBack];
|
|
|
|
var NamedMarkers := [
|
|
NNT(mmiHasEntry, 'markers\debug-stop-filled'),
|
|
NNT(mmiEntryProcessed, 'markers\debug-stop-filled_2'),
|
|
NNT(mmiBreakpoint, 'markers\debug-breakpoint-filled'),
|
|
NNT(mmiBreakpointBad, 'markers\debug-breakpoint-filled-cancel-2'),
|
|
NNT(mmiBreakpointGood, 'markers\debug-breakpoint-filled-ok-2'),
|
|
NNT(mmiStep, 'markers\symbol-arrow-right'),
|
|
NNT(mmiBreakpointStep, 'markers\debug-breakpoint-filled-ok2-symbol-arrow-right'),
|
|
NNT(SC_MARKNUM_FOLDER, 'markers\symbol-add'),
|
|
NNT(SC_MARKNUM_FOLDEROPEN, 'markers\symbol-remove')];
|
|
|
|
for var NamedMarker in NamedMarkers do
|
|
AddMarkerOrAcBitmap(MarkerBitmaps, DC, BitmapInfo, NamedMarker.Key, MarkerBkBrush, ImageList, NamedMarker.Value);
|
|
|
|
AutoCompleteBitmaps := TMarkerOrACBitmaps.Create([doOwnsValues]);
|
|
AutoCompleteBkBrush := TBrush.Create;
|
|
AutoCompleteBkBrush.Color := FTheme.Colors[tcIntelliBack];
|
|
|
|
var NamedTypes := [
|
|
NNT(awtSection, 'ac\structure-filled'),
|
|
NNT(awtParameter, 'ac\xml-filled'),
|
|
NNT(awtDirective, 'ac\xml-filled'),
|
|
NNT(awtFlag, 'ac\values'),
|
|
NNT(awtPreprocessorDirective, 'ac\symbol-hashtag'),
|
|
NNT(awtConstant, 'ac\constant-filled_2'),
|
|
NNT(awtScriptFunction, 'ac\method-filled'),
|
|
NNT(awtScriptType, 'ac\types'),
|
|
NNT(awtScriptVariable, 'ac\variables'),
|
|
NNT(awtScriptConstant, 'ac\constant-filled'),
|
|
NNT(awtScriptInterface, 'ac\interface-filled'),
|
|
NNT(awtScriptProperty, 'ac\properties-filled'),
|
|
NNT(awtScriptEvent, 'ac\event-filled'),
|
|
NNT(awtScriptKeyword, 'ac\list'),
|
|
NNT(awtScriptEnumValue, 'ac\constant-filled')];
|
|
|
|
for var NamedType in NamedTypes do
|
|
AddMarkerOrAcBitmap(AutoCompleteBitmaps, DC, BitmapInfo, NamedType.Key, AutoCompleteBkBrush, ImageList, NamedType.Value);
|
|
|
|
for var Memo in FMemos do begin
|
|
Memo.Call(SCI_RGBAIMAGESETWIDTH, ImageList.Width, 0);
|
|
Memo.Call(SCI_RGBAIMAGESETHEIGHT, ImageList.Height, 0);
|
|
for var MarkerBitmap in MarkerBitmaps do
|
|
Memo.Call(SCI_MARKERDEFINERGBAIMAGE, MarkerBitmap.Key, LPARAM(MarkerBitmap.Value.pvBits));
|
|
for var AutoCompleteBitmap in AutoCompleteBitmaps do
|
|
Memo.Call(SCI_REGISTERRGBAIMAGE, AutoCompleteBitmap.Key, LPARAM(AutoCompleteBitmap.Value.pvBits));
|
|
end;
|
|
finally
|
|
AutoCompleteBkBrush.Free;
|
|
AutoCompleteBitmaps.Free;
|
|
MarkerBkBrush.Free;
|
|
MarkerBitmaps.Free;
|
|
end;
|
|
finally
|
|
DeleteDC(DC);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.UpdateMarginsAndSquigglyAndCaretWidths;
|
|
{ Update the width of our two margins. Note: the width of the line numbers
|
|
margin is fully handled by TScintEdit. Should be called at startup and after
|
|
DPI change. }
|
|
begin
|
|
var IconMarkersWidth := ToCurrentPPI(18); { 3 pixel margin on both sides of the icon }
|
|
var BaseChangeHistoryWidth := ToCurrentPPI(6); { 6 = 2 pixel bar with 2 pixel margin on both sides because: "SC_MARK_BAR ... takes ... 1/3 of the margin width" }
|
|
var FolderMarkersWidth := ToCurrentPPI(14); { 1 pixel margin on boths side of the icon }
|
|
var LeftBlankMarginWidth := ToCurrentPPI(2); { 2 pixel margin between gutter and the main text }
|
|
var SquigglyWidth := ToCurrentPPI(100); { 100 = 1 pixel }
|
|
var CaretWidth := ToCurrentPPI(2);
|
|
var WhiteSpaceSize := CaretWidth;
|
|
|
|
for var Memo in FMemos do
|
|
Memo.UpdateWidthsAndSizes(IconMarkersWidth, BaseChangeHistoryWidth, FolderMarkersWidth,
|
|
LeftBlankMarginWidth, 0, SquigglyWidth, CaretWidth, WhiteSpaceSize);
|
|
end;
|
|
|
|
procedure TMainForm.SplitPanelMouseMove(Sender: TObject;
|
|
Shift: TShiftState; X, Y: Integer);
|
|
begin
|
|
if (ssLeft in Shift) and StatusPanel.Visible then begin
|
|
UpdateStatusPanelHeight(BodyPanel.ClientToScreen(Point(0, 0)).Y -
|
|
SplitPanel.ClientToScreen(Point(0, Y)).Y +
|
|
BodyPanel.ClientHeight - (SplitPanel.Height div 2));
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.SimpleMenuClick(Sender: TObject);
|
|
begin
|
|
ApplyMenuBitmaps(Sender as TMenuItem);
|
|
end;
|
|
|
|
procedure TMainForm.TMenuClick(Sender: TObject);
|
|
var
|
|
MemoIsReadOnly: Boolean;
|
|
begin
|
|
MemoIsReadOnly := FActiveMemo.ReadOnly;
|
|
TGenerateGUID.Enabled := not MemoIsReadOnly;
|
|
TMsgBoxDesigner.Enabled := not MemoIsReadOnly;
|
|
TFilesDesigner.Enabled := not MemoIsReadOnly;
|
|
TRegistryDesigner.Enabled := not MemoIsReadOnly;
|
|
|
|
ApplyMenuBitmaps(Sender as TMenuItem);
|
|
end;
|
|
|
|
procedure TMainForm.TAddRemoveProgramsClick(Sender: TObject);
|
|
begin
|
|
StartAddRemovePrograms;
|
|
end;
|
|
|
|
procedure TMainForm.TGenerateGUIDClick(Sender: TObject);
|
|
begin
|
|
if MsgBox('The generated GUID will be inserted into the editor at the cursor position. Continue?',
|
|
SCompilerFormCaption, mbConfirmation, MB_YESNO) = IDYES then
|
|
FActiveMemo.MainSelText := GenerateGuid;
|
|
end;
|
|
|
|
procedure TMainForm.TMsgBoxDesignerClick(Sender: TObject);
|
|
begin
|
|
if (FMemosStyler.GetSectionFromLineState(FActiveMemo.Lines.State[FActiveMemo.CaretLine]) <> scCode) and
|
|
(MsgBox('The generated Pascal script will be inserted into the editor at the cursor position, but the cursor is not in the [Code] section. Continue anyway?',
|
|
SCompilerFormCaption, mbConfirmation, MB_YESNO) = IDNO) then
|
|
Exit;
|
|
|
|
var MsgBoxForm := TMsgBoxDesignerForm.Create(Application);
|
|
try
|
|
if MsgBoxForm.ShowModal = mrOk then
|
|
FActiveMemo.MainSelText := MsgBoxForm.GetText(FOptions.TabWidth, FOptions.UseTabCharacter);
|
|
finally
|
|
MsgBoxForm.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.TRegistryDesignerClick(Sender: TObject);
|
|
begin
|
|
var RegistryDesignerForm := TRegistryDesignerForm.Create(Application);
|
|
try
|
|
var PrivilegesRequired := FindSetupDirectiveValue('PrivilegesRequired', 'admin');
|
|
var PrivilegesRequiredOverridesAllowed := FindSetupDirectiveValue('PrivilegesRequiredOverridesAllowed', '');
|
|
if PrivilegesRequiredOverridesAllowed = '' then begin
|
|
if SameText(PrivilegesRequired, 'admin') then
|
|
RegistryDesignerForm.PrivilegesRequired := prAdmin
|
|
else
|
|
RegistryDesignerForm.PrivilegesRequired := prLowest
|
|
end else
|
|
RegistryDesignerForm.PrivilegesRequired := prDynamic;
|
|
if RegistryDesignerForm.ShowModal = mrOk then
|
|
begin
|
|
FActiveMemo.CaretColumn := 0;
|
|
var Text := RegistryDesignerForm.Text;
|
|
if FMemosStyler.GetSectionFromLineState(FActiveMemo.Lines.State[FActiveMemo.CaretLine]) <> scRegistry then
|
|
Text := '[Registry]' + SNewLine + Text;
|
|
FActiveMemo.MainSelText := Text;
|
|
end;
|
|
finally
|
|
RegistryDesignerForm.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.TFilesDesignerClick(Sender: TObject);
|
|
begin
|
|
var FilesDesignerForm := TFilesDesignerForm.Create(Application);
|
|
try
|
|
FilesDesignerForm.CreateAppDir := FindSetupDirectiveValue('CreateAppDir', True);
|
|
if FilesDesignerForm.ShowModal = mrOk then begin
|
|
FActiveMemo.CaretColumn := 0;
|
|
var Text := FilesDesignerForm.Text;
|
|
if FMemosStyler.GetSectionFromLineState(FActiveMemo.Lines.State[FActiveMemo.CaretLine]) <> scFiles then
|
|
Text := '[Files]' + SNewLine + Text;
|
|
FActiveMemo.MainSelText := Text;
|
|
end;
|
|
finally
|
|
FilesDesignerForm.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.TSignToolsClick(Sender: TObject);
|
|
var
|
|
SignToolsForm: TSignToolsForm;
|
|
Ini: TConfigIniFile;
|
|
I: Integer;
|
|
begin
|
|
SignToolsForm := TSignToolsForm.Create(Application);
|
|
try
|
|
SignToolsForm.SignTools := FSignTools;
|
|
|
|
if SignToolsForm.ShowModal <> mrOK then
|
|
Exit;
|
|
|
|
FSignTools.Assign(SignToolsForm.SignTools);
|
|
|
|
{ Save new options }
|
|
Ini := TConfigIniFile.Create;
|
|
try
|
|
Ini.EraseSection('SignTools');
|
|
for I := 0 to FSignTools.Count-1 do
|
|
Ini.WriteString('SignTools', 'SignTool' + IntToStr(I), FSignTools[I]);
|
|
finally
|
|
Ini.Free;
|
|
end;
|
|
finally
|
|
SignToolsForm.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.TOptionsClick(Sender: TObject);
|
|
var
|
|
OptionsForm: TOptionsForm;
|
|
Ini: TConfigIniFile;
|
|
Memo: TIDEScintEdit;
|
|
begin
|
|
OptionsForm := TOptionsForm.Create(Application);
|
|
try
|
|
OptionsForm.StartupCheck.Checked := FOptions.ShowStartupForm;
|
|
OptionsForm.WizardCheck.Checked := FOptions.UseWizard;
|
|
OptionsForm.AutosaveCheck.Checked := FOptions.Autosave;
|
|
OptionsForm.BackupCheck.Checked := FOptions.MakeBackups;
|
|
OptionsForm.FullPathCheck.Checked := FOptions.FullPathInTitleBar;
|
|
OptionsForm.UndoAfterSaveCheck.Checked := FOptions.UndoAfterSave;
|
|
OptionsForm.PauseOnDebuggerExceptionsCheck.Checked := FOptions.PauseOnDebuggerExceptions;
|
|
OptionsForm.RunAsDifferentUserCheck.Checked := FOptions.RunAsDifferentUser;
|
|
OptionsForm.AutoAutoCompleteCheck.Checked := FOptions.AutoAutoComplete;
|
|
OptionsForm.UseSynHighCheck.Checked := FOptions.UseSyntaxHighlighting;
|
|
OptionsForm.ColorizeCompilerOutputCheck.Checked := FOptions.ColorizeCompilerOutput;
|
|
OptionsForm.UnderlineErrorsCheck.Checked := FOptions.UnderlineErrors;
|
|
OptionsForm.CursorPastEOLCheck.Checked := FOptions.CursorPastEOL;
|
|
OptionsForm.TabWidthEdit.Text := IntToStr(FOptions.TabWidth);
|
|
OptionsForm.UseTabCharacterCheck.Checked := FOptions.UseTabCharacter;
|
|
OptionsForm.ShowWhiteSpaceCheck.Checked := FOptions.ShowWhiteSpace;
|
|
OptionsForm.UseFoldingCheck.Checked := FOptions.UseFolding;
|
|
OptionsForm.AutoIndentCheck.Checked := FOptions.AutoIndent;
|
|
OptionsForm.IndentationGuidesCheck.Checked := FOptions.IndentationGuides;
|
|
OptionsForm.GutterLineNumbersCheck.Checked := FOptions.GutterLineNumbers;
|
|
OptionsForm.ShowPreprocessorOutputCheck.Checked := FOptions.ShowPreprocessorOutput;
|
|
OptionsForm.OpenIncludedFilesCheck.Checked := FOptions.OpenIncludedFiles;
|
|
OptionsForm.KeyMappingComboBox.ItemIndex := Ord(FOptions.KeyMappingType);
|
|
OptionsForm.MemoKeyMappingComboBox.ItemIndex := Ord(FOptions.MemoKeyMappingType);
|
|
OptionsForm.ThemeComboBox.ItemIndex := Ord(FOptions.ThemeType);
|
|
OptionsForm.FontPanel.Font.Assign(FMainMemo.Font);
|
|
OptionsForm.FontPanel.ParentBackground := False;
|
|
OptionsForm.FontPanel.Color := FMainMemo.Color;
|
|
OptionsForm.HighlightWordAtCursorOccurrencesCheck.Checked := FOptions.HighlightWordAtCursorOccurrences;
|
|
OptionsForm.HighlightSelTextOccurrencesCheck.Checked := FOptions.HighlightSelTextOccurrences;
|
|
|
|
if OptionsForm.ShowModal <> mrOK then
|
|
Exit;
|
|
|
|
FOptions.ShowStartupForm := OptionsForm.StartupCheck.Checked;
|
|
FOptions.UseWizard := OptionsForm.WizardCheck.Checked;
|
|
FOptions.Autosave := OptionsForm.AutosaveCheck.Checked;
|
|
FOptions.MakeBackups := OptionsForm.BackupCheck.Checked;
|
|
FOptions.FullPathInTitleBar := OptionsForm.FullPathCheck.Checked;
|
|
FOptions.UndoAfterSave := OptionsForm.UndoAfterSaveCheck.Checked;
|
|
FOptions.PauseOnDebuggerExceptions := OptionsForm.PauseOnDebuggerExceptionsCheck.Checked;
|
|
FOptions.RunAsDifferentUser := OptionsForm.RunAsDifferentUserCheck.Checked;
|
|
FOptions.AutoAutoComplete := OptionsForm.AutoAutoCompleteCheck.Checked;
|
|
FOptions.UseSyntaxHighlighting := OptionsForm.UseSynHighCheck.Checked;
|
|
FOptions.ColorizeCompilerOutput := OptionsForm.ColorizeCompilerOutputCheck.Checked;
|
|
FOptions.UnderlineErrors := OptionsForm.UnderlineErrorsCheck.Checked;
|
|
FOptions.CursorPastEOL := OptionsForm.CursorPastEOLCheck.Checked;
|
|
FOptions.TabWidth := StrToInt(OptionsForm.TabWidthEdit.Text);
|
|
FOptions.UseTabCharacter := OptionsForm.UseTabCharacterCheck.Checked;
|
|
FOptions.ShowWhiteSpace := OptionsForm.ShowWhiteSpaceCheck.Checked;
|
|
FOptions.UseFolding := OptionsForm.UseFoldingCheck.Checked;
|
|
FOptions.AutoIndent := OptionsForm.AutoIndentCheck.Checked;
|
|
FOptions.IndentationGuides := OptionsForm.IndentationGuidesCheck.Checked;
|
|
FOptions.GutterLineNumbers := OptionsForm.GutterLineNumbersCheck.Checked;
|
|
FOptions.ShowPreprocessorOutput := OptionsForm.ShowPreprocessorOutputCheck.Checked;
|
|
FOptions.OpenIncludedFiles := OptionsForm.OpenIncludedFilesCheck.Checked;
|
|
FOptions.KeyMappingType := TKeyMappingType(OptionsForm.KeyMappingComboBox.ItemIndex);
|
|
FOptions.MemoKeyMappingType := TIDEScintKeyMappingType(OptionsForm.MemoKeyMappingComboBox.ItemIndex);
|
|
FOptions.ThemeType := TThemeType(OptionsForm.ThemeComboBox.ItemIndex);
|
|
FOptions.HighlightWordAtCursorOccurrences := OptionsForm.HighlightWordAtCursorOccurrencesCheck.Checked;
|
|
FOptions.HighlightSelTextOccurrences := OptionsForm.HighlightSelTextOccurrencesCheck.Checked;
|
|
|
|
UpdateCaption;
|
|
UpdatePreprocMemos;
|
|
InvalidateStatusPanel(spHiddenFilesCount);
|
|
for Memo in FMemos do begin
|
|
{ Move caret to start of line to ensure it doesn't end up in the middle
|
|
of a double-byte character if the code page changes from SBCS to DBCS }
|
|
Memo.CaretLine := Memo.CaretLine;
|
|
Memo.Font.Assign(OptionsForm.FontPanel.Font);
|
|
end;
|
|
SyncEditorOptions;
|
|
UpdateMarginsAndSquigglyAndCaretWidths;
|
|
UpdateNewMainFileButtons;
|
|
UpdateOccurrenceIndicators(FActiveMemo);
|
|
UpdateKeyMapping;
|
|
UpdateTheme;
|
|
|
|
{ Save new options }
|
|
Ini := TConfigIniFile.Create;
|
|
try
|
|
Ini.WriteBool('Options', 'ShowStartupForm', FOptions.ShowStartupForm);
|
|
Ini.WriteBool('Options', 'UseWizard', FOptions.UseWizard);
|
|
Ini.WriteBool('Options', 'Autosave', FOptions.Autosave);
|
|
Ini.WriteBool('Options', 'MakeBackups', FOptions.MakeBackups);
|
|
Ini.WriteBool('Options', 'FullPathInTitleBar', FOptions.FullPathInTitleBar);
|
|
Ini.WriteBool('Options', 'UndoAfterSave', FOptions.UndoAfterSave);
|
|
Ini.WriteBool('Options', 'PauseOnDebuggerExceptions', FOptions.PauseOnDebuggerExceptions);
|
|
Ini.WriteBool('Options', 'RunAsDifferentUser', FOptions.RunAsDifferentUser);
|
|
Ini.WriteBool('Options', 'AutoComplete', FOptions.AutoAutoComplete);
|
|
Ini.WriteBool('Options', 'AutoCallTips', FOptions.AutoCallTips);
|
|
Ini.WriteBool('Options', 'UseSynHigh', FOptions.UseSyntaxHighlighting);
|
|
Ini.WriteBool('Options', 'ColorizeCompilerOutput', FOptions.ColorizeCompilerOutput);
|
|
Ini.WriteBool('Options', 'UnderlineErrors', FOptions.UnderlineErrors);
|
|
Ini.WriteBool('Options', 'HighlightWordAtCursorOccurrences', FOptions.HighlightWordAtCursorOccurrences);
|
|
Ini.WriteBool('Options', 'HighlightSelTextOccurrences', FOptions.HighlightSelTextOccurrences);
|
|
Ini.WriteBool('Options', 'EditorCursorPastEOL', FOptions.CursorPastEOL);
|
|
Ini.WriteInteger('Options', 'TabWidth', FOptions.TabWidth);
|
|
Ini.WriteBool('Options', 'UseTabCharacter', FOptions.UseTabCharacter);
|
|
Ini.WriteBool('Options', 'ShowWhiteSpace', FOptions.ShowWhiteSpace);
|
|
Ini.WriteBool('Options', 'UseFolding', FOptions.UseFolding);
|
|
Ini.WriteBool('Options', 'AutoIndent', FOptions.AutoIndent);
|
|
Ini.WriteBool('Options', 'IndentationGuides', FOptions.IndentationGuides);
|
|
Ini.WriteBool('Options', 'GutterLineNumbers', FOptions.GutterLineNumbers);
|
|
Ini.WriteBool('Options', 'ShowPreprocessorOutput', FOptions.ShowPreprocessorOutput);
|
|
Ini.WriteBool('Options', 'OpenIncludedFiles', FOptions.OpenIncludedFiles);
|
|
Ini.WriteInteger('Options', 'KeyMappingType', Ord(FOptions.KeyMappingType));
|
|
Ini.WriteInteger('Options', 'MemoKeyMappingType', Ord(FOptions.MemoKeyMappingType));
|
|
Ini.WriteInteger('Options', 'ThemeType', Ord(FOptions.ThemeType)); { Also see Destroy }
|
|
Ini.WriteString('Options', 'EditorFontName', FMainMemo.Font.Name);
|
|
Ini.WriteInteger('Options', 'EditorFontSize', FMainMemo.Font.Size);
|
|
Ini.WriteInteger('Options', 'EditorFontCharset', FMainMemo.Font.Charset);
|
|
finally
|
|
Ini.Free;
|
|
end;
|
|
finally
|
|
OptionsForm.Free;
|
|
end;
|
|
end;
|
|
|
|
{ Also see TabIndexToMemoIndex }
|
|
function TMainForm.MemoToTabIndex(const AMemo: TIDEScintEdit): Integer;
|
|
begin
|
|
if AMemo = FMainMemo then
|
|
Result := 0 { First tab displays the main memo }
|
|
else if AMemo = FPreprocessorOutputMemo then begin
|
|
if not FPreprocessorOutputMemo.Used then
|
|
raise Exception.Create('not FPreprocessorOutputMemo.Used');
|
|
Result := MemosTabSet.Tabs.Count-1 { Last tab displays the preprocessor output memo }
|
|
end else begin
|
|
Result := FFileMemos.IndexOf(AMemo as TIDEScintFileEdit); { Other tabs display include files which start second tab }
|
|
|
|
{ Filter memos explicitly hidden by the user }
|
|
for var MemoIndex := Result-1 downto 0 do
|
|
if FHiddenFiles.IndexOf(FFileMemos[MemoIndex].Filename) <> -1 then
|
|
Dec(Result);
|
|
end;
|
|
end;
|
|
|
|
{ Also see MemoToTabIndex }
|
|
function TMainForm.TabIndexToMemo(const ATabIndex, AMaxTabIndex: Integer): TIDEScintEdit;
|
|
begin
|
|
if ATabIndex = 0 then
|
|
Result := FMemos[0] { First tab displays the main memo which is FMemos[0] }
|
|
else if FPreprocessorOutputMemo.Used and (ATabIndex = AMaxTabIndex) then
|
|
Result := FMemos[1] { Last tab displays the preprocessor output memo which is FMemos[1] }
|
|
else begin
|
|
{ Only count memos not explicitly hidden by the user }
|
|
var TabIndex := 0;
|
|
for var MemoIndex := FirstIncludedFilesMemoIndex to FFileMemos.Count-1 do begin
|
|
if FHiddenFiles.IndexOf(FFileMemos[MemoIndex].Filename) = -1 then begin
|
|
Inc(TabIndex);
|
|
if TabIndex = ATabIndex then begin
|
|
Result := FMemos[MemoIndex + 1]; { Other tabs display include files which start at second tab but at FMemos[2] }
|
|
Exit;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
raise Exception.Create('TabIndexToMemo failed');
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.MoveCaretAndActivateMemo(AMemo: TIDEScintEdit; const LineNumberOrPosition: Integer;
|
|
const AlwaysResetColumnEvenIfOnRequestedLineAlready: Boolean; const IsPosition: Boolean;
|
|
const PositionVirtualSpace: Integer);
|
|
var
|
|
Pos: Integer;
|
|
begin
|
|
{ Reopen tab if needed }
|
|
if AMemo is TIDEScintFileEdit then begin
|
|
var FileName := (AMemo as TIDEScintFileEdit).Filename;
|
|
var HiddenFileIndex := FHiddenFiles.IndexOf(Filename);
|
|
if HiddenFileIndex <> -1 then begin
|
|
ReopenTabOrTabs(HiddenFileIndex, False);
|
|
{ The above call to ReopenTabOrTabs will currently lead to a call to UpdateIncludedFilesMemos which
|
|
sets up all the memos. Currently it will keep same memo for the reopened file but in case it no
|
|
longer does at some point: look it up again }
|
|
AMemo := nil;
|
|
for var Memo in FFileMemos do begin
|
|
if Memo.Used and (PathCompare(Memo.Filename, Filename) = 0) then begin
|
|
AMemo := Memo;
|
|
Break;
|
|
end;
|
|
end;
|
|
if AMemo = nil then
|
|
raise Exception.Create('AMemo MIA');
|
|
end;
|
|
end;
|
|
|
|
{ Move caret }
|
|
if IsPosition then
|
|
Pos := LineNumberOrPosition
|
|
else if AlwaysResetColumnEvenIfOnRequestedLineAlready or (AMemo.CaretLine <> LineNumberOrPosition) then
|
|
Pos := AMemo.GetPositionFromLine(LineNumberOrPosition)
|
|
else
|
|
Pos := AMemo.CaretPosition; { Not actually moving caret - it's already were we want it}
|
|
|
|
{ If the line is in a contracted section, expand it }
|
|
AMemo.EnsureLineVisible(AMemo.GetLineFromPosition(Pos));
|
|
|
|
{ If the line isn't in view, scroll so that it's in the center }
|
|
if not AMemo.IsPositionInViewVertically(Pos) then
|
|
AMemo.TopLine := AMemo.GetVisibleLineFromDocLine(AMemo.GetLineFromPosition(Pos)) -
|
|
(AMemo.LinesInWindow div 2);
|
|
|
|
AMemo.CaretPosition := Pos;
|
|
if IsPosition then
|
|
AMemo.CaretVirtualSpace := PositionVirtualSpace;
|
|
|
|
{ Activate memo }
|
|
MemosTabSet.TabIndex := MemoToTabIndex(AMemo); { This causes MemosTabSetClick to show the memo }
|
|
end;
|
|
|
|
procedure TMainForm.SetErrorLine(const AMemo: TIDEScintFileEdit; const ALine: Integer);
|
|
var
|
|
OldLine: Integer;
|
|
begin
|
|
if AMemo <> FErrorMemo then begin
|
|
SetErrorLine(FErrorMemo, -1);
|
|
FErrorMemo := AMemo;
|
|
end;
|
|
|
|
if FErrorMemo.ErrorLine <> ALine then begin
|
|
OldLine := FErrorMemo.ErrorLine;
|
|
FErrorMemo.ErrorLine := ALine;
|
|
if OldLine >= 0 then
|
|
UpdateLineMarkers(FErrorMemo, OldLine);
|
|
if FErrorMemo.ErrorLine >= 0 then begin
|
|
FErrorMemo.ErrorCaretPosition := FErrorMemo.CaretPosition;
|
|
UpdateLineMarkers(FErrorMemo, FErrorMemo.ErrorLine);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.SetStepLine(const AMemo: TIDEScintFileEdit; ALine: Integer);
|
|
var
|
|
OldLine: Integer;
|
|
begin
|
|
if AMemo <> FStepMemo then begin
|
|
SetStepLine(FStepMemo, -1);
|
|
FStepMemo := AMemo;
|
|
end;
|
|
|
|
if FStepMemo.StepLine <> ALine then begin
|
|
OldLine := FStepMemo.StepLine;
|
|
FStepMemo.StepLine := ALine;
|
|
if OldLine >= 0 then
|
|
UpdateLineMarkers(FStepMemo, OldLine);
|
|
if FStepMemo.StepLine >= 0 then
|
|
UpdateLineMarkers(FStepMemo, FStepMemo.StepLine);
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.HideError;
|
|
begin
|
|
SetErrorLine(FErrorMemo, -1);
|
|
if not FCompiling then
|
|
StatusBar.Panels[spExtraStatus].Text := '';
|
|
end;
|
|
|
|
procedure TMainForm.RemoveMemoFromNav(const AMemo: TIDEScintEdit);
|
|
begin
|
|
if FNavStacks.RemoveMemo(AMemo) then
|
|
UpdateNavButtons;
|
|
if FCurrentNavItem.Memo = AMemo then
|
|
FCurrentNavItem.Invalidate;
|
|
end;
|
|
|
|
procedure TMainForm.RemoveMemoBadLinesFromNav(const AMemo: TIDEScintEdit);
|
|
begin
|
|
if FNavStacks.RemoveMemoBadLines(AMemo) then
|
|
UpdateNavButtons;
|
|
{ We do NOT update FCurrentNav here so it might point to a line that's
|
|
deleted until next UpdateCaretPosPanelAndBackStack by UpdateMemoUI }
|
|
end;
|
|
|
|
procedure TMainForm.UpdateNavButtons;
|
|
begin
|
|
ForwardNavButton.Enabled := FNavStacks.Forward.Count > 0;
|
|
BackNavButton.Enabled := (FNavStacks.Back.Count > 0) or
|
|
ForwardNavButton.Enabled; { for the dropdown }
|
|
end;
|
|
|
|
procedure TMainForm.BackNavButtonClick(Sender: TObject);
|
|
begin
|
|
{ Delphi does not support BTNS_WHOLEDROPDOWN so we can't be like VS which
|
|
can have a disabled back nav button with an enabled dropdown. To avoid
|
|
always showing two dropdowns we keep the back button enabled when we need
|
|
the dropdown. So we need to check for this. }
|
|
if FNavStacks.Back.Count = 0 then begin
|
|
Beep;
|
|
Exit;
|
|
end;
|
|
|
|
FNavStacks.Forward.Add(FCurrentNavItem);
|
|
var NewNavItem := FNavStacks.Back.ExtractAt(FNavStacks.Back.Count-1);
|
|
UpdateNavButtons;
|
|
FCurrentNavItem := NewNavItem; { Must be done *before* moving }
|
|
MoveCaretAndActivateMemo(NewNavItem.Memo,
|
|
NewNavItem.Memo.GetPositionFromLineColumn(NewNavItem.Line, NewNavItem.Column), False, True, NewNavItem.VirtualSpace);
|
|
end;
|
|
|
|
procedure TMainForm.ForwardNavButtonClick(Sender: TObject);
|
|
begin
|
|
FNavStacks.Back.Add(FCurrentNavItem);
|
|
var NewNavItem := FNavStacks.Forward.ExtractAt(FNavStacks.Forward.Count-1);
|
|
UpdateNavButtons;
|
|
FCurrentNavItem := NewNavItem; { Must be done *before* moving }
|
|
MoveCaretAndActivateMemo(NewNavItem.Memo,
|
|
NewNavItem.Memo.GetPositionFromLineColumn(NewNavItem.Line, NewNavItem.Column), False, True, NewNavItem.VirtualSpace);
|
|
end;
|
|
|
|
procedure TMainForm.WMAppCommand(var Message: TMessage);
|
|
begin
|
|
var Command := GET_APPCOMMAND_LPARAM(Message.LParam);
|
|
|
|
if Command = APPCOMMAND_BROWSER_BACKWARD then begin
|
|
if BackNavButton.Enabled then
|
|
BackNavButton.Click;
|
|
Message.Result := 1;
|
|
end else if Command = APPCOMMAND_BROWSER_FORWARD then begin
|
|
if ForwardNavButton.Enabled then
|
|
ForwardNavButton.Click;
|
|
Message.Result := 1;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.NavItemClick(Sender: TObject);
|
|
begin
|
|
var MenuItem := Sender as TMenuItem;
|
|
var Clicks := Abs(MenuItem.Tag);
|
|
if Clicks > 0 then begin
|
|
var ButtonToClick: TToolButton;
|
|
if MenuItem.Tag > 0 then
|
|
ButtonToClick := ForwardNavButton
|
|
else
|
|
ButtonToClick := BackNavButton;
|
|
while Clicks > 0 do begin
|
|
if not ButtonToClick.Enabled then
|
|
raise Exception.Create('not ButtonToClick.Enabled');
|
|
ButtonToClick.Click;
|
|
Dec(Clicks);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.NavPopupMenuClick(Sender: TObject);
|
|
|
|
procedure AddNavItemToMenu(const NavItem: TIDEScintEditNavItem; const Checked: Boolean;
|
|
const ClicksNeeded: Integer; const Menu: TMenuItem);
|
|
begin
|
|
if NavItem.Line >= NavItem.Memo.Lines.Count then
|
|
raise Exception.Create('NavItem.Line >= NavItem.Memo.Lines.Count');
|
|
var LineInfo := NavItem.Memo.Lines[NavItem.Line];
|
|
if LineInfo.Trim = '' then
|
|
LineInfo := Format('Line %d', [NavItem.Line+1]);
|
|
|
|
var Caption: String;
|
|
if MemosTabSet.Visible then
|
|
Caption := Format('%s: %s', [MemosTabSet.Tabs[MemoToTabIndex(NavItem.Memo)], LineInfo])
|
|
else
|
|
Caption := LineInfo;
|
|
|
|
var MenuItem := TMenuItem.Create(Menu);
|
|
MenuItem.Caption := DoubleAmp(Caption);
|
|
MenuItem.Checked := Checked;
|
|
MenuItem.RadioItem := True;
|
|
MenuItem.Tag := ClicksNeeded;
|
|
MenuItem.OnClick := NavItemClick;
|
|
Menu.Add(MenuItem);
|
|
end;
|
|
|
|
begin
|
|
var Menu := Sender as TMenuItem;
|
|
|
|
Menu.Clear;
|
|
|
|
{ Setup dropdown. The result should end up being just like Visual Studio 2022
|
|
which means from top to bottom:
|
|
- Furthest (=oldest) forward item
|
|
- ...
|
|
- Closest (=next) forward item
|
|
- Current position in the active memo, checked
|
|
- Closest (=next) back item
|
|
- ...
|
|
- Furthest (=oldest) back item
|
|
The Tag parameter should be set to the amount of clicks needed to get to
|
|
the item, positive for forward and negative for back }
|
|
|
|
for var I := 0 to FNavStacks.Forward.Count-1 do
|
|
AddNavItemToMenu(FNavStacks.Forward[I], False, FNavStacks.Forward.Count-I, Menu);
|
|
AddNavItemToMenu(FCurrentNavItem, True, 0, Menu);
|
|
for var I := FNavStacks.Back.Count-1 downto 0 do
|
|
AddNavItemToMenu(FNavStacks.Back[I], False, -(FNavStacks.Back.Count-I), Menu);
|
|
end;
|
|
|
|
procedure TMainForm.UpdateCaretPosPanelAndBackNavStack;
|
|
begin
|
|
{ Update panel }
|
|
var Text := Format('%4d:%4d', [FActiveMemo.CaretLine + 1,
|
|
FActiveMemo.CaretColumnExpandedForTabs + 1]);
|
|
if FOptions.ShowCaretPosition then begin
|
|
const CaretPos = FActiveMemo.CaretPosition;
|
|
const Style = FActiveMemo.GetStyleAtPosition(CaretPos);
|
|
Text := Format('%s@%d+%d:%s', [Copy(GetEnumName(TypeInfo(TInnoSetupStylerStyle), Style), 3, MaxInt),
|
|
CaretPos, FActiveMemo.CaretVirtualSpace, Text]);
|
|
end;
|
|
StatusBar.Panels[spCaretPos].Text := Text;
|
|
|
|
{ Update NavStacks.Back if needed and remember new position }
|
|
var NewNavItem := TIDEScintEditNavItem.Create(FActiveMemo); { This is a record so no need to free }
|
|
if FCurrentNavItem.Valid and FNavStacks.AddNewBackForJump(FCurrentNavItem, NewNavItem) then
|
|
UpdateNavButtons;
|
|
FCurrentNavItem := NewNavItem;
|
|
end;
|
|
|
|
procedure TMainForm.UpdateEditModePanel;
|
|
const
|
|
InsertText: array[Boolean] of String = ('Overwrite', 'Insert');
|
|
begin
|
|
if FActiveMemo.ReadOnly then
|
|
StatusBar.Panels[spEditMode].Text := 'Read only'
|
|
else
|
|
StatusBar.Panels[spEditMode].Text := InsertText[FActiveMemo.InsertMode];
|
|
end;
|
|
|
|
procedure TMainForm.UpdateFindRegExUI;
|
|
const
|
|
FindRegExText: array[Boolean] of String = ('', '.*');
|
|
begin
|
|
StatusBar.Panels[spFindRegEx].Text := FindRegExText[FOptions.FindRegEx];
|
|
if FOptions.FindRegEx then begin
|
|
FindDialog.Options := FindDialog.Options + [frHideWholeWord];
|
|
ReplaceDialog.Options := ReplaceDialog.Options + [frHideWholeWord];
|
|
end else begin
|
|
FindDialog.Options := FindDialog.Options - [frHideWholeWord];
|
|
ReplaceDialog.Options := ReplaceDialog.Options - [frHideWholeWord];
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.UpdateMemosTabSetVisibility;
|
|
begin
|
|
MemosTabSet.Visible := FPreprocessorOutputMemo.Used or FFileMemos[FirstIncludedFilesMemoIndex].Used;
|
|
if not MemosTabSet.Visible then
|
|
MemosTabSet.TabIndex := 0; { For next time }
|
|
end;
|
|
|
|
procedure TMainForm.UpdateModifiedPanel;
|
|
begin
|
|
if FActiveMemo.Modified then
|
|
StatusBar.Panels[spModified].Text := 'Modified'
|
|
else
|
|
StatusBar.Panels[spModified].Text := '';
|
|
end;
|
|
|
|
procedure TMainForm.UpdatePreprocMemos;
|
|
|
|
procedure UpdatePreprocessorOutputMemo(const NewTabs, NewHints: TStringList;
|
|
const NewCloseButtons: TBoolList);
|
|
begin
|
|
if FOptions.ShowPreprocessorOutput and (FPreprocessorOutput <> '') and
|
|
(FMainMemo.Lines.Text.TrimRight <> FPreprocessorOutput) then begin
|
|
NewTabs.Add('Preprocessor Output');
|
|
NewHints.Add('');
|
|
NewCloseButtons.Add(False);
|
|
FPreprocessorOutputMemo.ReadOnly := False;
|
|
try
|
|
FPreprocessorOutputMemo.Lines.Text := FPreprocessorOutput;
|
|
FPreprocessorOutputMemo.ClearUndo;
|
|
finally
|
|
FPreprocessorOutputMemo.ReadOnly := True;
|
|
end;
|
|
FPreprocessorOutputMemo.Used := True;
|
|
end else begin
|
|
if FPreprocessorOutputMemo.Used then
|
|
RemoveMemoFromNav(FPreprocessorOutputMemo);
|
|
FPreprocessorOutputMemo.Used := False;
|
|
FPreprocessorOutputMemo.Visible := False;
|
|
end;
|
|
end;
|
|
|
|
procedure UpdateIncludedFilesMemos(const NewTabs, NewHints: TStringList;
|
|
const NewCloseButtons: TBoolList);
|
|
var
|
|
IncludedFile: TIncludedFile;
|
|
I: Integer;
|
|
begin
|
|
if FOptions.OpenIncludedFiles and (FIncludedFiles.Count > 0) then begin
|
|
var NextMemoIndex := FirstIncludedFilesMemoIndex;
|
|
var NextTabIndex := 1; { First tab displays the main memo }
|
|
for IncludedFile in FIncludedFiles do begin
|
|
IncludedFile.Memo := FFileMemos[NextMemoIndex];
|
|
try
|
|
if not IncludedFile.Memo.Used or
|
|
((PathCompare(IncludedFile.Memo.Filename, IncludedFile.Filename) <> 0) or
|
|
not IncludedFile.HasLastWriteTime or
|
|
(CompareFileTime(IncludedFile.Memo.FileLastWriteTime, IncludedFile.LastWriteTime) <> 0)) then begin
|
|
IncludedFile.Memo.Filename := IncludedFile.Filename;
|
|
IncludedFile.Memo.CompilerFileIndex := IncludedFile.CompilerFileIndex;
|
|
OpenFile(IncludedFile.Memo, IncludedFile.Filename, False); { Also updates FileLastWriteTime }
|
|
IncludedFile.Memo.Used := True;
|
|
end else begin
|
|
{ The memo assigned to the included file already has that file loaded
|
|
and is up-to-date so no call to OpenFile is needed. However, it could be
|
|
that CompilerFileIndex is not set yet. This happens if the initial
|
|
load was from the history loaded by LoadKnownIncludedAndHiddenFiles
|
|
and is followed by the user doing a compile. }
|
|
if IncludedFile.Memo.CompilerFileIndex = UnknownCompilerFileIndex then
|
|
IncludedFile.Memo.CompilerFileIndex := IncludedFile.CompilerFileIndex;
|
|
end;
|
|
|
|
if FHiddenFiles.IndexOf(IncludedFile.Filename) = -1 then begin
|
|
NewTabs.Insert(NextTabIndex, GetDisplayFilename(IncludedFile.Filename));
|
|
NewHints.Insert(NextTabIndex, GetFileTitle(IncludedFile.Filename));
|
|
NewCloseButtons.Insert(NextTabIndex, True);
|
|
Inc(NextTabIndex);
|
|
end;
|
|
|
|
Inc(NextMemoIndex);
|
|
if NextMemoIndex = FFileMemos.Count then
|
|
Break; { We're out of memos :( }
|
|
except on E: Exception do
|
|
begin
|
|
StatusMessage(smkWarning, 'Failed to open included file: ' + E.Message);
|
|
IncludedFile.Memo := nil;
|
|
end;
|
|
end;
|
|
end;
|
|
{ Hide any remaining memos }
|
|
for I := NextMemoIndex to FFileMemos.Count-1 do begin
|
|
FFileMemos[I].BreakPoints.Clear;
|
|
if FFileMemos[I].Used then
|
|
RemoveMemoFromNav(FFileMemos[I]);
|
|
FFileMemos[I].Used := False;
|
|
FFileMemos[I].Visible := False;
|
|
end;
|
|
end else begin
|
|
for I := FirstIncludedFilesMemoIndex to FFileMemos.Count-1 do begin
|
|
FFileMemos[I].BreakPoints.Clear;
|
|
if FFileMemos[I].Used then
|
|
RemoveMemoFromNav(FFileMemos[I]);
|
|
FFileMemos[I].Used := False;
|
|
FFileMemos[I].Visible := False;
|
|
end;
|
|
for IncludedFile in FIncludedFiles do
|
|
IncludedFile.Memo := nil;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
NewTabs, NewHints: TStringList;
|
|
NewCloseButtons: TBoolList;
|
|
I, SaveTabIndex: Integer;
|
|
SaveTabName: String;
|
|
begin
|
|
NewTabs := nil;
|
|
NewHints := nil;
|
|
NewCloseButtons := nil;
|
|
try
|
|
NewTabs := TStringList.Create;
|
|
NewTabs.Add(MemosTabSet.Tabs[0]); { 'Main Script' }
|
|
NewHints := TStringList.Create;
|
|
NewHints.Add(GetFileTitle(FMainMemo.Filename));
|
|
NewCloseButtons := TBoolList.Create;
|
|
NewCloseButtons.Add(False);
|
|
|
|
UpdatePreprocessorOutputMemo(NewTabs, NewHints, NewCloseButtons);
|
|
UpdateIncludedFilesMemos(NewTabs, NewHints, NewCloseButtons);
|
|
|
|
{ Set new tabs, try keep same file open }
|
|
SaveTabIndex := MemosTabSet.TabIndex;
|
|
SaveTabName := MemosTabSet.Tabs[MemosTabSet.TabIndex];
|
|
MemosTabSet.Tabs := NewTabs;
|
|
MemosTabSet.Hints := NewHints;
|
|
MemosTabSet.CloseButtons := NewCloseButtons;
|
|
I := MemosTabSet.Tabs.IndexOf(SaveTabName);
|
|
if I <> -1 then
|
|
MemosTabSet.TabIndex := I;
|
|
if MemosTabSet.TabIndex = SaveTabIndex then begin
|
|
{ If TabIndex stayed the same then the tabset won't perform a Click but we need this to make
|
|
sure the right memo is visible - so trigger it ourselves }
|
|
MemosTabSetClick(MemosTabSet);
|
|
end;
|
|
finally
|
|
NewCloseButtons.Free;
|
|
NewHints.Free;
|
|
NewTabs.Free;
|
|
end;
|
|
|
|
UpdateMemosTabSetVisibility;
|
|
UpdateBevel1Visibility;
|
|
end;
|
|
|
|
procedure TMainForm.MemoUpdateUI(Sender: TObject; Updated: TScintEditUpdates);
|
|
|
|
procedure UpdatePendingSquiggly(const AMemo: TIDEScintEdit);
|
|
var
|
|
Pos: Integer;
|
|
Value: Boolean;
|
|
begin
|
|
{ Check for the inPendingSquiggly indicator on either side of the caret }
|
|
Pos := AMemo.CaretPosition;
|
|
Value := False;
|
|
if AMemo.CaretVirtualSpace = 0 then begin
|
|
Value := AMemo.GetIndicatorAtPosition(minPendingSquiggly, Pos);
|
|
if not Value and (Pos > 0) then
|
|
Value := AMemo.GetIndicatorAtPosition(minPendingSquiggly, Pos-1);
|
|
end;
|
|
if FOnPendingSquiggly <> Value then begin
|
|
FOnPendingSquiggly := Value;
|
|
{ If caret has left a pending squiggly, force restyle of the line }
|
|
if not Value then begin
|
|
{ Stop reporting the caret position to the styler (until the next
|
|
Change event) so the token doesn't re-enter pending-squiggly state
|
|
if the caret comes back and something restyles the line }
|
|
AMemo.ReportCaretPositionToStyler := False;
|
|
AMemo.RestyleLine(AMemo.GetLineFromPosition(FPendingSquigglyCaretPos));
|
|
end;
|
|
end;
|
|
FPendingSquigglyCaretPos := Pos;
|
|
end;
|
|
|
|
procedure UpdateBraceHighlighting(const AMemo: TIDEScintEdit);
|
|
const
|
|
OpeningBraces: TSysCharSet = ['(', '[', '{', '<'];
|
|
ClosingBraces: TSysCharSet = [')', ']', '}', '>'];
|
|
|
|
function HighlightPos(const AMemo: TIDEScintEdit; const CaretPos: Integer;
|
|
const Before: Boolean; const Braces: TSysCharSet): Boolean;
|
|
begin
|
|
var Pos := CaretPos;
|
|
if Before then begin
|
|
if Pos > 0 then
|
|
Pos := AMemo.GetPositionBefore(Pos)
|
|
else
|
|
Exit(False);
|
|
end;
|
|
var C := AMemo.GetByteAtPosition(Pos);
|
|
Result := C in Braces;
|
|
if Result then begin
|
|
var MatchPos := AMemo.GetPositionOfMatchingBrace(Pos);
|
|
if MatchPos >= 0 then
|
|
AMemo.SetBraceHighlighting(Pos, MatchPos)
|
|
else begin
|
|
{ Found an unmatched brace: highlight it as bad unless it's an opening
|
|
brace and the caret is at the end of the line }
|
|
var CaretLineEndPos := AMemo.GetLineEndPosition(AMemo.CaretLine);
|
|
if (C in ClosingBraces) or (CaretPos <> CaretLineEndPos) then
|
|
AMemo.SetBraceBadHighlighting(Pos)
|
|
else
|
|
AMemo.SetBraceHighlighting(-1, -1);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
var Highlighted := False;
|
|
var Section := FMemosStyler.GetSectionFromLineState(AMemo.Lines.State[AMemo.CaretLine]);
|
|
if (Section <> scNone) and (AMemo.CaretVirtualSpace = 0) then begin
|
|
var Pos := AMemo.CaretPosition;
|
|
Highlighted := Highlighted or HighlightPos(AMemo, Pos, False, OpeningBraces);
|
|
Highlighted := Highlighted or HighlightPos(AMemo, Pos, False, ClosingBraces);
|
|
Highlighted := Highlighted or HighlightPos(AMemo, Pos, True, ClosingBraces);
|
|
Highlighted := Highlighted or HighlightPos(AMemo, Pos, True, OpeningBraces);
|
|
end;
|
|
if not Highlighted then
|
|
AMemo.SetBraceHighlighting(-1, -1);
|
|
end;
|
|
|
|
begin
|
|
if Updated * [suContent, suSelection] = [] then
|
|
Exit;
|
|
|
|
var Memo := Sender as TIDEScintEdit;
|
|
|
|
if (Memo = FErrorMemo) and ((FErrorMemo.ErrorLine < 0) or (FErrorMemo.CaretPosition <> FErrorMemo.ErrorCaretPosition)) then
|
|
HideError;
|
|
|
|
if Memo = FActiveMemo then begin
|
|
UpdateCaretPosPanelAndBackNavStack;
|
|
UpdateEditModePanel;
|
|
end;
|
|
|
|
UpdatePendingSquiggly(Memo);
|
|
UpdateBraceHighlighting(Memo);
|
|
UpdateOccurrenceIndicators(Memo);
|
|
end;
|
|
|
|
procedure TMainForm.MemoModifiedChange(Sender: TObject);
|
|
begin
|
|
if Sender = FActiveMemo then
|
|
UpdateModifiedPanel;
|
|
end;
|
|
|
|
procedure TMainForm.MemoCallTipArrowClick(Sender: TObject;
|
|
const Up: Boolean);
|
|
begin
|
|
{ Based on SciTE 5.50's SciTEBase::Notify SA::Notification::CallTipClick }
|
|
if Up and (FCallTipState.CurrentCallTip > 0) then begin
|
|
Dec(FCallTipState.CurrentCallTip);
|
|
UpdateCallTipFunctionDefinition;
|
|
end else if not Up and (FCallTipState.CurrentCallTip + 1 < FCallTipState.MaxCallTips) then begin
|
|
Inc(FCallTipState.CurrentCallTip);
|
|
UpdateCallTipFunctionDefinition;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.MemoChange(Sender: TObject; const Info: TScintEditChangeInfo);
|
|
|
|
procedure MemoLinesInsertedOrDeleted(Memo: TIDEScintFileEdit);
|
|
var
|
|
FirstAffectedLine, Line, LinePos: Integer;
|
|
begin
|
|
Line := Memo.GetLineFromPosition(Info.StartPos);
|
|
LinePos := Memo.GetPositionFromLine(Line);
|
|
FirstAffectedLine := Line;
|
|
{ If the deletion/insertion does not start on the first character of Line,
|
|
then we consider the first deleted/inserted line to be the following
|
|
line (Line+1). This way, if you press Del at the end of line 1, the dot
|
|
on line 2 is removed, while line 1's dot stays intact. }
|
|
if Info.StartPos > LinePos then
|
|
Inc(Line);
|
|
if Info.LinesDelta > 0 then
|
|
MemoLinesInserted(Memo, Line, Info.LinesDelta)
|
|
else
|
|
MemoLinesDeleted(Memo, Line, -Info.LinesDelta, FirstAffectedLine);
|
|
end;
|
|
|
|
var
|
|
Memo: TIDEScintFileEdit;
|
|
begin
|
|
if not (Sender is TIDEScintFileEdit) then
|
|
Exit;
|
|
|
|
Memo := TIDEScintFileEdit(Sender);
|
|
|
|
if Memo.OpeningFile then
|
|
Exit;
|
|
|
|
FModifiedAnySinceLastCompile := True;
|
|
if FDebugging then
|
|
FModifiedAnySinceLastCompileAndGo := True
|
|
else begin
|
|
{ Modified while not debugging or opening a file; free the debug info and clear the dots }
|
|
DestroyDebugInfo;
|
|
end;
|
|
|
|
if Info.LinesDelta <> 0 then
|
|
MemoLinesInsertedOrDeleted(Memo);
|
|
|
|
if Memo = FErrorMemo then begin
|
|
{ When the Delete key is pressed, the caret doesn't move, so reset
|
|
FErrorCaretPosition to ensure that OnUpdateUI calls HideError }
|
|
FErrorMemo.ErrorCaretPosition := -1;
|
|
end;
|
|
|
|
{ The change should trigger restyling. Allow the styler to see the current
|
|
caret position in case it wants to set a pending squiggly indicator. }
|
|
Memo.ReportCaretPositionToStyler := True;
|
|
end;
|
|
|
|
function TMainForm.InitiateAutoCompleteOrCallTipAllowedAtPos(const AMemo: TIDEScintEdit;
|
|
const WordStartLinePos, PositionBeforeWordStartPos: Integer): Boolean;
|
|
begin
|
|
Result := (PositionBeforeWordStartPos < WordStartLinePos) or
|
|
not FMemosStyler.IsCommentOrPascalStringStyle(AMemo.GetStyleAtPosition(PositionBeforeWordStartPos));
|
|
end;
|
|
|
|
procedure TMainForm.InitiateAutoComplete(const Key: AnsiChar);
|
|
|
|
function OnlyWhiteSpaceBeforeWord(const Memo: TIDEScintEdit; const LinePos, WordStartPos: Integer): Boolean;
|
|
var
|
|
I: Integer;
|
|
C: AnsiChar;
|
|
begin
|
|
{ Only allow autocompletion if no non-whitespace characters exist before the current word on the line }
|
|
I := WordStartPos;
|
|
Result := False;
|
|
while I > LinePos do begin
|
|
I := FActiveMemo.GetPositionBefore(I);
|
|
if I < LinePos then
|
|
Exit; { shouldn't get here }
|
|
C := FActiveMemo.GetByteAtPosition(I);
|
|
if C > ' ' then
|
|
Exit;
|
|
end;
|
|
Result := True;
|
|
end;
|
|
|
|
var
|
|
CaretPos, Line, LinePos, WordStartPos, WordEndPos, CharsBefore,
|
|
LangNamePos: Integer;
|
|
Section: TInnoSetupStylerSection;
|
|
IsParamSection: Boolean;
|
|
WordList: AnsiString;
|
|
FoundSemicolon, FoundFlagsOrType, FoundDot: Boolean;
|
|
C: AnsiChar;
|
|
begin
|
|
if FActiveMemo.AutoCompleteActive or FActiveMemo.ReadOnly then
|
|
Exit;
|
|
|
|
if Key = #0 then begin
|
|
{ If a character is typed then Scintilla will handle selections but
|
|
otherwise we should empty them and also make sure the caret is visible
|
|
before we start autocompletion }
|
|
FActiveMemo.SetEmptySelections;
|
|
FActiveMemo.ScrollCaretIntoView;
|
|
end;
|
|
|
|
CaretPos := FActiveMemo.CaretPosition;
|
|
Line := FActiveMemo.GetLineFromPosition(CaretPos);
|
|
LinePos := FActiveMemo.GetPositionFromLine(Line);
|
|
|
|
WordStartPos := FActiveMemo.GetWordStartPosition(CaretPos, True);
|
|
WordEndPos := FActiveMemo.GetWordEndPosition(CaretPos, True);
|
|
CharsBefore := CaretPos - WordStartPos;
|
|
|
|
{ Don't auto start autocompletion after a character is typed if there are any
|
|
word characters adjacent to the character }
|
|
if Key <> #0 then begin
|
|
if CharsBefore > 1 then
|
|
Exit;
|
|
if WordEndPos > CaretPos then
|
|
Exit;
|
|
end;
|
|
|
|
case FActiveMemo.GetByteAtPosition(WordStartPos) of
|
|
'#':
|
|
begin
|
|
if not OnlyWhiteSpaceBeforeWord(FActiveMemo, LinePos, WordStartPos) then
|
|
Exit;
|
|
WordList := FMemosStyler.ISPPDirectivesWordList;
|
|
FActiveMemo.SetAutoCompleteFillupChars(' ');
|
|
end;
|
|
'{':
|
|
begin
|
|
WordList := FMemosStyler.ConstantsWordList;
|
|
FActiveMemo.SetAutoCompleteFillupChars('\:');
|
|
end;
|
|
'[':
|
|
begin
|
|
if not OnlyWhiteSpaceBeforeWord(FActiveMemo, LinePos, WordStartPos) then
|
|
Exit;
|
|
WordList := FMemosStyler.SectionsWordList;
|
|
FActiveMemo.SetAutoCompleteFillupChars('');
|
|
end;
|
|
else
|
|
begin
|
|
Section := FMemosStyler.GetSectionFromLineState(FActiveMemo.Lines.State[Line]);
|
|
if Section = scCode then begin
|
|
{ Space can only initiate autocompletion after non whitespace }
|
|
if (Key = ' ') and OnlyWhiteSpaceBeforeWord(FActiveMemo, LinePos, WordStartPos) then
|
|
Exit;
|
|
|
|
var PositionBeforeWordStartPos := FActiveMemo.GetPositionBefore(WordStartPos);
|
|
if Key <> #0 then begin
|
|
FActiveMemo.StyleNeeded(PositionBeforeWordStartPos); { Make sure the typed character has been styled }
|
|
if not InitiateAutoCompleteOrCallTipAllowedAtPos(FActiveMemo, LinePos, PositionBeforeWordStartPos) then
|
|
Exit;
|
|
end;
|
|
|
|
WordList := '';
|
|
|
|
{ Autocomplete event functions if the current word on the line has
|
|
exactly 1 space before it which has the word 'function' or
|
|
'procedure' before it which has only whitespace before it }
|
|
if (PositionBeforeWordStartPos >= LinePos) and (FActiveMemo.GetByteAtPosition(PositionBeforeWordStartPos) <= ' ') then begin
|
|
var FunctionWordEndPos := PositionBeforeWordStartPos;
|
|
var FunctionWordStartPos := FActiveMemo.GetWordStartPosition(FunctionWordEndPos, True);
|
|
if OnlyWhiteSpaceBeforeWord(FActiveMemo, LinePos, FunctionWordStartPos) then begin
|
|
var FunctionWord := FActiveMemo.GetTextRange(FunctionWordStartPos, FunctionWordEndPos);
|
|
if SameText(FunctionWord, 'procedure') then
|
|
WordList := FMemosStyler.EventFunctionsWordList[True]
|
|
else if SameText(FunctionWord, 'function') then
|
|
WordList := FMemosStyler.EventFunctionsWordList[False];
|
|
if WordList <> '' then
|
|
FActiveMemo.SetAutoCompleteFillupChars('');
|
|
end;
|
|
end;
|
|
|
|
{ If no event function was found then autocomplete script functions,
|
|
types, etc if the current word has no dot before it }
|
|
if WordList = '' then begin
|
|
var ClassOrRecordMember := (PositionBeforeWordStartPos >= LinePos) and (FActiveMemo.GetByteAtPosition(PositionBeforeWordStartPos) = '.');
|
|
WordList := FMemosStyler.ScriptWordList[ClassOrRecordMember];
|
|
FActiveMemo.SetAutoCompleteFillupChars('');
|
|
end;
|
|
|
|
if WordList = '' then
|
|
Exit;
|
|
end else begin
|
|
IsParamSection := FMemosStyler.IsParamSection(Section);
|
|
|
|
{ Autocomplete if the current word on the line has only whitespace
|
|
before it, or else also: after the last ';' or after 'Flags:' or
|
|
'Type:' in parameterized sections }
|
|
FoundSemicolon := False;
|
|
FoundFlagsOrType := False;
|
|
FoundDot := False;
|
|
var I := WordStartPos;
|
|
while I > LinePos do begin
|
|
I := FActiveMemo.GetPositionBefore(I);
|
|
if I < LinePos then
|
|
Exit; { shouldn't get here }
|
|
C := FActiveMemo.GetByteAtPosition(I);
|
|
|
|
if IsParamSection and (C in [';', ':']) and
|
|
FMemosStyler.IsSymbolStyle(FActiveMemo.GetStyleAtPosition(I)) then begin { Make sure it's an stSymbol ';' or ':' and not one inside a quoted string }
|
|
FoundSemicolon := C = ';';
|
|
if not FoundSemicolon then begin
|
|
var ParameterWordEndPos := I;
|
|
var ParameterWordStartPos := FActiveMemo.GetWordStartPosition(ParameterWordEndPos, True);
|
|
var ParameterWord := FActiveMemo.GetTextRange(ParameterWordStartPos, ParameterWordEndPos);
|
|
FoundFlagsOrType := SameText(ParameterWord, 'Flags') or
|
|
((Section in [scInstallDelete, scUninstallDelete]) and SameText(ParameterWord, 'Type'));
|
|
end else
|
|
FoundFlagsOrType := False;
|
|
if FoundSemicolon or FoundFlagsOrType then
|
|
Break;
|
|
end;
|
|
if (Section = scLangOptions) and (C = '.') and not FoundDot then begin
|
|
{ Verify that a word (language name) precedes the '.', then check for
|
|
any non-whitespace characters before the word }
|
|
LangNamePos := FActiveMemo.GetWordStartPosition(I, True);
|
|
if LangNamePos >= I then
|
|
Exit;
|
|
I := LangNamePos;
|
|
FoundDot := True;
|
|
end else if C > ' ' then begin
|
|
if IsParamSection and not (Section in [scInstallDelete, scUninstallDelete]) and
|
|
(FMemosStyler.FlagsWordList[Section] <> '') then begin
|
|
{ Verify word before the current word (or before that when we get here again) is
|
|
a valid flag and if so, continue looking before it instead of stopping }
|
|
var FlagEndPos := FActiveMemo.GetWordEndPosition(I, True);
|
|
var FlagStartPos := FActiveMemo.GetWordStartPosition(I, True);
|
|
var FlagWord := FActiveMemo.GetTextRange(FlagStartPos, FlagEndPos);
|
|
if FMemosStyler.SectionHasFlag(Section, FlagWord) then
|
|
I := FlagStartPos
|
|
else
|
|
Exit;
|
|
end else
|
|
Exit;
|
|
end;
|
|
end;
|
|
{ Space can only initiate autocompletion after ';' or 'Flags:' or 'Type:' in parameterized sections }
|
|
if (Key = ' ') and not (FoundSemicolon or FoundFlagsOrType) then
|
|
Exit;
|
|
|
|
if FoundFlagsOrType then begin
|
|
WordList := FMemosStyler.FlagsWordList[Section];
|
|
if WordList = '' then
|
|
Exit;
|
|
FActiveMemo.SetAutoCompleteFillupChars(' ');
|
|
end else begin
|
|
WordList := FMemosStyler.KeywordsWordList[Section];
|
|
if WordList = '' then { CustomMessages }
|
|
Exit;
|
|
if IsParamSection then
|
|
FActiveMemo.SetAutoCompleteFillupChars(':')
|
|
else
|
|
FActiveMemo.SetAutoCompleteFillupChars('=');
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
FActiveMemo.ShowAutoComplete(CharsBefore, WordList);
|
|
end;
|
|
|
|
procedure TMainForm.UpdateCallTipFunctionDefinition(const Pos: Integer { = -1 });
|
|
begin
|
|
{ Based on SciTE 5.50's SciTEBase::FillFunctionDefinition }
|
|
|
|
if Pos > 0 then
|
|
FCallTipState.LastPosCallTip := Pos;
|
|
|
|
// Should get current api definition
|
|
var FunctionDefinition := FMemosStyler.GetScriptFunctionDefinition(FCallTipState.ClassOrRecordMember, FCallTipState.CurrentCallTipWord, FCallTipState.CurrentCallTip, FCallTipState.MaxCallTips);
|
|
if ((FCallTipState.MaxCallTips = 1) and FunctionDefinition.HasParams) or //if there's a single definition then only show if it has a parameter
|
|
(FCallTipState.MaxCallTips > 1) then begin //if there's multiple then show always just like MemoHintShow, so even the one without parameters if it exists
|
|
FCallTipState.FunctionDefinition := FunctionDefinition.ScriptFuncWithoutHeader;
|
|
if FCallTipState.MaxCallTips > 1 then
|
|
FCallTipState.FunctionDefinition := AnsiString(Format(#1'%d of %d'#2'%s', [FCallTipState.CurrentCallTip+1, FCallTipState.MaxCallTips, FCallTipState.FunctionDefinition]));
|
|
|
|
FActiveMemo.ShowCallTip(FCallTipState.LastPosCallTip - Length(FCallTipState.CurrentCallTipWord), FCallTipState.FunctionDefinition);
|
|
ContinueCallTip;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.InitiateCallTip(const Key: AnsiChar);
|
|
begin
|
|
var Pos := FActiveMemo.CaretPosition;
|
|
|
|
if (FMemosStyler.GetSectionFromLineState(FActiveMemo.Lines.State[FActiveMemo.GetLineFromPosition(Pos)]) <> scCode) or
|
|
((Key <> #0) and not InitiateAutoCompleteOrCallTipAllowedAtPos(FActiveMemo,
|
|
FActiveMemo.GetPositionFromLine(FActiveMemo.GetLineFromPosition(Pos)),
|
|
FActiveMemo.GetPositionBefore(Pos))) then
|
|
Exit;
|
|
|
|
{ Based on SciTE 5.50's SciTEBase::StartAutoComplete }
|
|
|
|
FCallTipState.CurrentCallTip := 0;
|
|
FCallTipState.CurrentCallTipWord := '';
|
|
var Line := FActiveMemo.CaretLineText;
|
|
var Current := FActiveMemo.CaretPositionInLine;
|
|
var CallTipWordCharacters := FActiveMemo.WordCharsAsSet;
|
|
|
|
{$ZEROBASEDSTRINGS ON}
|
|
repeat
|
|
var Braces := 0;
|
|
while ((Current > 0) and ((Braces <> 0) or not (Line[Current-1] = '('))) do begin
|
|
if Line[Current-1] = '(' then
|
|
Dec(Braces)
|
|
else if Line[Current-1] = ')' then
|
|
Inc(Braces);
|
|
Dec(Current);
|
|
Dec(Pos);
|
|
end;
|
|
if Current > 0 then begin
|
|
Dec(Current);
|
|
Dec(Pos);
|
|
end else
|
|
Break;
|
|
while (Current > 0) and (Line[Current-1] <= ' ') do begin
|
|
Dec(Current);
|
|
Dec(Pos);
|
|
end
|
|
until not ((Current > 0) and not CharInSet(Line[Current-1], CallTipWordCharacters));
|
|
{$ZEROBASEDSTRINGS OFF}
|
|
if Current <= 0 then
|
|
Exit;
|
|
|
|
FCallTipState.StartCallTipWord := Current - 1;
|
|
{$ZEROBASEDSTRINGS ON}
|
|
while (FCallTipState.StartCallTipWord > 0) and CharInSet(Line[FCallTipState.StartCallTipWord-1], CallTipWordCharacters) do
|
|
Dec(FCallTipState.StartCallTipWord);
|
|
FCallTipState.ClassOrRecordMember := (FCallTipState.StartCallTipWord > 0) and (Line[FCallTipState.StartCallTipWord-1] = '.');
|
|
{$ZEROBASEDSTRINGS OFF}
|
|
|
|
SetLength(Line, Current);
|
|
FCallTipState.CurrentCallTipWord := Line.Substring(FCallTipState.StartCallTipWord); { Substring is zero-based }
|
|
|
|
FCallTipState.FunctionDefinition := '';
|
|
UpdateCallTipFunctionDefinition(Pos);
|
|
end;
|
|
|
|
procedure TMainForm.ContinueCallTip;
|
|
begin
|
|
{ Based on SciTE 5.50's SciTEBase::ContinueCallTip }
|
|
|
|
var Line := FActiveMemo.CaretLineText;
|
|
var Current := FActiveMemo.CaretPositionInLine;
|
|
|
|
var Braces := 0;
|
|
var Commas := 0;
|
|
for var I := FCallTipState.StartCallTipWord to Current-1 do begin
|
|
{$ZEROBASEDSTRINGS ON}
|
|
if CharInSet(Line[I], ['(', '[']) then
|
|
Inc(Braces)
|
|
else if CharInSet(Line[I], [')', ']']) and (Braces > 0) then
|
|
Dec(Braces)
|
|
else if (Braces = 1) and (Line[I] = ',') then
|
|
Inc(Commas);
|
|
{$ZEROBASEDSTRINGS OFF}
|
|
end;
|
|
|
|
{$ZEROBASEDSTRINGS ON}
|
|
var StartHighlight := 0;
|
|
var FunctionDefinition := FCallTipState.FunctionDefinition;
|
|
var FunctionDefinitionLength := Length(FunctionDefinition);
|
|
while (StartHighlight < FunctionDefinitionLength) and not (FunctionDefinition[StartHighlight] = '(') do
|
|
Inc(StartHighlight);
|
|
if (StartHighlight < FunctionDefinitionLength) and (FunctionDefinition[StartHighlight] = '(') then
|
|
Inc(StartHighlight);
|
|
while (StartHighlight < FunctionDefinitionLength) and (Commas > 0) do begin
|
|
if FunctionDefinition[StartHighlight] in [',', ';'] then
|
|
Dec(Commas);
|
|
// If it reached the end of the argument list it means that the user typed in more
|
|
// arguments than the ones listed in the calltip
|
|
if FunctionDefinition[StartHighlight] = ')' then
|
|
Commas := 0
|
|
else
|
|
Inc(StartHighlight);
|
|
end;
|
|
if (StartHighlight < FunctionDefinitionLength) and (FunctionDefinition[StartHighlight] in [',', ';']) then
|
|
Inc(StartHighlight);
|
|
var EndHighlight := StartHighlight;
|
|
while (EndHighlight < FunctionDefinitionLength) and not (FunctionDefinition[EndHighlight] in [',', ';']) and not (FunctionDefinition[EndHighlight] = ')') do
|
|
Inc(EndHighlight);
|
|
{$ZEROBASEDSTRINGS OFF}
|
|
|
|
FActiveMemo.SetCallTipHighlight(StartHighlight, EndHighlight);
|
|
end;
|
|
|
|
procedure TMainForm.MemoCharAdded(Sender: TObject; Ch: AnsiChar);
|
|
|
|
function LineIsBlank(const Line: Integer): Boolean;
|
|
begin
|
|
var S := FActiveMemo.Lines.RawLines[Line];
|
|
Result := TScintEdit.RawStringIsBlank(S);
|
|
end;
|
|
|
|
var
|
|
NewLine, PreviousLine, NewIndent, PreviousIndent: Integer;
|
|
begin
|
|
if FOptions.AutoIndent and (Ch = FActiveMemo.LineEndingString[Length(FActiveMemo.LineEndingString)]) then begin
|
|
{ Add to the new line any (remaining) indentation from the previous line }
|
|
NewLine := FActiveMemo.CaretLine;
|
|
PreviousLine := NewLine-1;
|
|
if PreviousLine >= 0 then begin
|
|
NewIndent := FActiveMemo.GetLineIndentation(NewLine);
|
|
{ If no indentation was moved from the previous line to the new line
|
|
(i.e., there are no spaces/tabs directly to the right of the new
|
|
caret position), and the previous line is completely empty (0 length),
|
|
then use the indentation from the last line containing non-space
|
|
characters. }
|
|
if (NewIndent = 0) and (FActiveMemo.Lines.RawLineLengths[PreviousLine] = 0) then begin
|
|
Dec(PreviousLine);
|
|
while (PreviousLine >= 0) and LineIsBlank(PreviousLine) do
|
|
Dec(PreviousLine);
|
|
end;
|
|
if PreviousLine >= 0 then begin
|
|
PreviousIndent := FActiveMemo.GetLineIndentation(PreviousLine);
|
|
FActiveMemo.SetLineIndentation(NewLine, NewIndent + PreviousIndent);
|
|
FActiveMemo.CaretPosition := FActiveMemo.GetPositionFromLineExpandedColumn(NewLine,
|
|
PreviousIndent);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ Based on SciTE 5.50's SciTEBase::CharAdded but with an altered interaction
|
|
between calltips and autocomplete }
|
|
|
|
var DoAutoComplete := False;
|
|
|
|
if FActiveMemo.CallTipActive then begin
|
|
if Ch = ')' then begin
|
|
Dec(FCallTipState.BraceCount);
|
|
if FCallTipState.BraceCount < 1 then
|
|
FActiveMemo.CancelCallTip
|
|
else if FOptions.AutoCallTips then
|
|
InitiateCallTip(Ch);
|
|
end else if Ch = '(' then begin
|
|
Inc(FCallTipState.BraceCount);
|
|
if FOptions.AutoCallTips then
|
|
InitiateCallTip(Ch);
|
|
end else
|
|
ContinueCallTip;
|
|
end else if FActiveMemo.AutoCompleteActive then begin
|
|
if Ch = '(' then begin
|
|
Inc(FCallTipState.BraceCount);
|
|
if FOptions.AutoCallTips then begin
|
|
InitiateCallTip(Ch);
|
|
if not FActiveMemo.CallTipActive then begin
|
|
{ Normally the calltip activation means any active autocompletion gets
|
|
cancelled by Scintilla but if the current word has no call tip then
|
|
we should make sure ourselves that the added brace still cancels
|
|
the currently active autocompletion }
|
|
DoAutoComplete := True;
|
|
end;
|
|
end;
|
|
end else if Ch = ')' then
|
|
Dec(FCallTipState.BraceCount)
|
|
else
|
|
DoAutoComplete := True;
|
|
end else if Ch = '(' then begin
|
|
FCallTipState.BraceCount := 1;
|
|
if FOptions.AutoCallTips then
|
|
InitiateCallTip(Ch);
|
|
end else
|
|
DoAutoComplete := True;
|
|
|
|
if DoAutoComplete then begin
|
|
case Ch of
|
|
'A'..'Z', 'a'..'z', '_', '#', '{', '[', '<', '0'..'9':
|
|
if not FActiveMemo.AutoCompleteActive and FOptions.AutoAutoComplete and not (Ch in ['0'..'9']) then
|
|
InitiateAutoComplete(Ch);
|
|
else
|
|
var RestartAutoComplete := (Ch in [' ', '.']) and
|
|
(FOptions.AutoAutoComplete or FActiveMemo.AutoCompleteActive);
|
|
FActiveMemo.CancelAutoComplete;
|
|
if RestartAutoComplete then
|
|
InitiateAutoComplete(Ch);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.MemoHintShow(Sender: TObject; var Info: TScintHintInfo);
|
|
|
|
function GetCodeVariableDebugEntryFromFileLineCol(FileIndex, Line, Col: Integer; out DebugEntry: PVariableDebugEntry): Boolean;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
{ FVariableDebugEntries uses 1-based line and column numbers }
|
|
Inc(Line);
|
|
Inc(Col);
|
|
Result := False;
|
|
for I := 0 to FVariableDebugEntriesCount-1 do begin
|
|
if (FVariableDebugEntries[I].FileIndex = FileIndex) and
|
|
(FVariableDebugEntries[I].LineNumber = Line) and
|
|
(FVariableDebugEntries[I].Col = Col) then begin
|
|
DebugEntry := @FVariableDebugEntries[I];
|
|
Result := True;
|
|
Break;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function GetCodeColumnFromPosition(const Pos: Integer): Integer;
|
|
var
|
|
LinePos: Integer;
|
|
S: TScintRawString;
|
|
U: String;
|
|
begin
|
|
{ [Code] lines get converted from the editor's UTF-8 to UTF-16 Strings when
|
|
passed to the compiler. This can lead to column number discrepancies
|
|
between Scintilla and ROPS. This code simulates the conversion to try to
|
|
find out where ROPS thinks a Pos resides. }
|
|
LinePos := FActiveMemo.GetPositionFromLine(FActiveMemo.GetLineFromPosition(Pos));
|
|
S := FActiveMemo.GetRawTextRange(LinePos, Pos);
|
|
U := FActiveMemo.ConvertRawStringToString(S);
|
|
Result := Length(U);
|
|
end;
|
|
|
|
function FindVarOrFuncRange(const Pos: Integer): TScintRange;
|
|
begin
|
|
{ Note: The GetPositionAfter is needed so that when the mouse is over a '.'
|
|
between two words, it won't match the word to the left of the '.' }
|
|
FActiveMemo.SetDefaultWordChars;
|
|
Result.StartPos := FActiveMemo.GetWordStartPosition(FActiveMemo.GetPositionAfter(Pos), True);
|
|
Result.EndPos := FActiveMemo.GetWordEndPosition(Pos, True);
|
|
end;
|
|
|
|
function FindConstRange(const Pos: Integer): TScintRange;
|
|
var
|
|
BraceLevel, ConstStartPos, Line, LineEndPos, I: Integer;
|
|
C: AnsiChar;
|
|
begin
|
|
Result.StartPos := 0;
|
|
Result.EndPos := 0;
|
|
BraceLevel := 0;
|
|
ConstStartPos := -1;
|
|
Line := FActiveMemo.GetLineFromPosition(Pos);
|
|
LineEndPos := FActiveMemo.GetLineEndPosition(Line);
|
|
I := FActiveMemo.GetPositionFromLine(Line);
|
|
while I < LineEndPos do begin
|
|
if (I > Pos) and (BraceLevel = 0) then
|
|
Break;
|
|
C := FActiveMemo.GetByteAtPosition(I);
|
|
if C = '{' then begin
|
|
if FActiveMemo.GetByteAtPosition(I + 1) = '{' then
|
|
Inc(I)
|
|
else begin
|
|
if BraceLevel = 0 then
|
|
ConstStartPos := I;
|
|
Inc(BraceLevel);
|
|
end;
|
|
end
|
|
else if (C = '}') and (BraceLevel > 0) then begin
|
|
Dec(BraceLevel);
|
|
if (BraceLevel = 0) and (ConstStartPos <> -1) then begin
|
|
if (Pos >= ConstStartPos) and (Pos <= I) then begin
|
|
Result.StartPos := ConstStartPos;
|
|
Result.EndPos := I + 1;
|
|
Exit;
|
|
end;
|
|
ConstStartPos := -1;
|
|
end;
|
|
end;
|
|
I := FActiveMemo.GetPositionAfter(I);
|
|
end;
|
|
end;
|
|
|
|
procedure UpdateInfo(var Info: TScintHintInfo; const HintStr: String; const Range: TScintRange; const Memo: TIDEScintEdit);
|
|
begin
|
|
Info.HintStr := HintStr;
|
|
Info.CursorRect.TopLeft := Memo.GetPointFromPosition(Range.StartPos);
|
|
Info.CursorRect.BottomRight := Memo.GetPointFromPosition(Range.EndPos);
|
|
Info.CursorRect.Bottom := Info.CursorRect.Top + Memo.LineHeight;
|
|
Info.HideTimeout := High(Integer); { infinite }
|
|
end;
|
|
|
|
begin
|
|
var Pos := FActiveMemo.GetPositionFromPoint(Info.CursorPos, True, True);
|
|
if Pos < 0 then
|
|
Exit;
|
|
var Line := FActiveMemo.GetLineFromPosition(Pos);
|
|
|
|
{ Check if cursor is over a [Code] variable or function }
|
|
if FMemosStyler.GetSectionFromLineState(FActiveMemo.Lines.State[Line]) = scCode then begin
|
|
var VarOrFuncRange := FindVarOrFuncRange(Pos);
|
|
if VarOrFuncRange.EndPos > VarOrFuncRange.StartPos then begin
|
|
var HintStr := '';
|
|
var DebugEntry: PVariableDebugEntry;
|
|
if (FActiveMemo is TIDEScintFileEdit) and (FDebugClientWnd <> 0) and
|
|
GetCodeVariableDebugEntryFromFileLineCol((FActiveMemo as TIDEScintFileEdit).CompilerFileIndex,
|
|
Line, GetCodeColumnFromPosition(VarOrFuncRange.StartPos), DebugEntry) then begin
|
|
var Output: String;
|
|
case EvaluateVariableEntry(DebugEntry, Output) of
|
|
1: HintStr := Output;
|
|
2: HintStr := Output;
|
|
else
|
|
HintStr := 'Unknown error';
|
|
end;
|
|
end else begin
|
|
var ClassMember := False;
|
|
var Name := FActiveMemo.GetTextRange(VarOrFuncRange.StartPos, VarOrFuncRange.EndPos);
|
|
var Index := 0;
|
|
var Count: Integer;
|
|
var FunctionDefinition := FMemosStyler.GetScriptFunctionDefinition(ClassMember, Name, Index, Count);
|
|
if Count = 0 then begin
|
|
ClassMember := not ClassMember;
|
|
FunctionDefinition := FMemosStyler.GetScriptFunctionDefinition(ClassMember, Name, Index, Count);
|
|
end;
|
|
while Index < Count do begin
|
|
if Index <> 0 then
|
|
FunctionDefinition := FMemosStyler.GetScriptFunctionDefinition(ClassMember, Name, Index);
|
|
if HintStr <> '' then
|
|
HintStr := HintStr + #13;
|
|
if FunctionDefinition.WasFunction then
|
|
HintStr := HintStr + 'function '
|
|
else
|
|
HintStr := HintStr + 'procedure ';
|
|
HintStr := HintStr + String(FunctionDefinition.ScriptFuncWithoutHeader);
|
|
Inc(Index);
|
|
end;
|
|
end;
|
|
|
|
if HintStr <> '' then begin
|
|
UpdateInfo(Info, HintStr, VarOrFuncRange, FActiveMemo);
|
|
Exit;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
if FDebugClientWnd <> 0 then begin
|
|
{ Check if cursor is over a constant }
|
|
var ConstRange := FindConstRange(Pos);
|
|
if ConstRange.EndPos > ConstRange.StartPos then begin
|
|
var HintStr := FActiveMemo.GetTextRange(ConstRange.StartPos, ConstRange.EndPos);
|
|
var Output: String;
|
|
case EvaluateConstant(Info.HintStr, Output) of
|
|
1: HintStr := HintStr + ' = "' + Output + '"';
|
|
2: HintStr := HintStr + ' = Exception: ' + Output;
|
|
else
|
|
HintStr := HintStr + ' = Unknown error';
|
|
end;
|
|
UpdateInfo(Info, HintStr, ConstRange, FActiveMemo);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.MainMemoDropFiles(Sender: TObject; X, Y: Integer;
|
|
AFiles: TStrings);
|
|
begin
|
|
if (AFiles.Count > 0) and ConfirmCloseFile(True) then
|
|
OpenFile(FMainMemo, AFiles[0], True);
|
|
end;
|
|
|
|
procedure TMainForm.MemoZoom(Sender: TObject);
|
|
begin
|
|
if not FSynchingZoom then begin
|
|
FSynchingZoom := True;
|
|
try
|
|
for var Memo in FMemos do
|
|
if Memo <> Sender then
|
|
Memo.Zoom := (Sender as TScintEdit).Zoom;
|
|
finally
|
|
FSynchingZoom := False;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.StatusBarResize(Sender: TObject);
|
|
begin
|
|
{ Without this, on Windows XP with themes, the status bar's size grip gets
|
|
corrupted as the form is resized }
|
|
if StatusBar.HandleAllocated then
|
|
InvalidateRect(StatusBar.Handle, nil, True);
|
|
end;
|
|
|
|
procedure TMainForm.WMDebuggerQueryVersion(var Message: TMessage);
|
|
begin
|
|
Message.Result := FCompilerVersion.BinVersion;
|
|
end;
|
|
|
|
procedure TMainForm.WMDebuggerHello(var Message: TMessage);
|
|
var
|
|
PID: DWORD;
|
|
WantCodeText: Boolean;
|
|
begin
|
|
FDebugClientWnd := HWND(Message.WParam);
|
|
|
|
{ Save debug client process handle }
|
|
if FDebugClientProcessHandle <> 0 then begin
|
|
{ Shouldn't get here, but just in case, don't leak a handle }
|
|
CloseHandle(FDebugClientProcessHandle);
|
|
FDebugClientProcessHandle := 0;
|
|
end;
|
|
PID := 0;
|
|
if GetWindowThreadProcessId(FDebugClientWnd, @PID) <> 0 then
|
|
FDebugClientProcessHandle := OpenProcess(SYNCHRONIZE or PROCESS_TERMINATE,
|
|
False, PID);
|
|
|
|
WantCodeText := Bool(Message.LParam);
|
|
if WantCodeText then
|
|
SendCopyDataMessageStr(FDebugClientWnd, Handle, CD_DebugClient_CompiledCodeTextA, FCompiledCodeText);
|
|
SendCopyDataMessageStr(FDebugClientWnd, Handle, CD_DebugClient_CompiledCodeDebugInfoA, FCompiledCodeDebugInfo);
|
|
|
|
UpdateRunMenu;
|
|
end;
|
|
|
|
procedure TMainForm.WMDebuggerGoodbye(var Message: TMessage);
|
|
begin
|
|
ReplyMessage(0);
|
|
DebuggingStopped(True);
|
|
end;
|
|
|
|
procedure TMainForm.GetMemoAndDebugEntryFromMessage(Kind, Index: Integer; var Memo: TIDEScintFileEdit; var DebugEntry: PDebugEntry);
|
|
|
|
function GetMemoFromDebugEntryFileIndex(const FileIndex: Integer): TIDEScintFileEdit;
|
|
var
|
|
Memo: TIDEScintFileEdit;
|
|
begin
|
|
Result := nil;
|
|
if FOptions.OpenIncludedFiles then begin
|
|
for Memo in FFileMemos do begin
|
|
if Memo.Used and (Memo.CompilerFileIndex = FileIndex) then begin
|
|
Result := Memo;
|
|
Exit;
|
|
end;
|
|
end;
|
|
end else if FMainMemo.CompilerFileIndex = FileIndex then
|
|
Result := FMainMemo;
|
|
end;
|
|
|
|
var
|
|
I: Integer;
|
|
begin
|
|
for I := 0 to FDebugEntriesCount-1 do begin
|
|
if (FDebugEntries[I].Kind = Kind) and (FDebugEntries[I].Index = Index) then begin
|
|
Memo := GetMemoFromDebugEntryFileIndex(FDebugEntries[I].FileIndex);
|
|
DebugEntry := @FDebugEntries[I];
|
|
Exit;
|
|
end;
|
|
end;
|
|
Memo := nil;
|
|
DebugEntry := nil;
|
|
end;
|
|
|
|
procedure TMainForm.BringToForeground;
|
|
{ Brings our top window to the foreground. Called when pausing while
|
|
debugging. }
|
|
var
|
|
TopWindow: HWND;
|
|
begin
|
|
TopWindow := GetThreadTopWindow;
|
|
if TopWindow <> 0 then begin
|
|
{ First ask the debug client to call SetForegroundWindow() on our window.
|
|
If we don't do this then Windows (98/2000+) will prevent our window from
|
|
becoming activated if the debug client is currently in the foreground. }
|
|
SendMessage(FDebugClientWnd, WM_DebugClient_SetForegroundWindow,
|
|
WPARAM(TopWindow), 0);
|
|
{ Now call SetForegroundWindow() ourself. Why? When a remote thread calls
|
|
SetForegroundWindow(), the request is queued; the window doesn't actually
|
|
become active until the next time the window's thread checks the message
|
|
queue. This call causes the window to become active immediately. }
|
|
SetForegroundWindow(TopWindow);
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.DebuggerStepped(var Message: TMessage; const Intermediate: Boolean);
|
|
var
|
|
Memo: TIDEScintFileEdit;
|
|
DebugEntry: PDebugEntry;
|
|
LineNumber: Integer;
|
|
begin
|
|
GetMemoAndDebugEntryFromMessage(Message.WParam, Message.LParam, Memo, DebugEntry);
|
|
if (Memo = nil) or (DebugEntry = nil) then
|
|
Exit;
|
|
|
|
LineNumber := DebugEntry.LineNumber;
|
|
|
|
if LineNumber < 0 then { UninstExe has a DebugEntry but not a line number }
|
|
Exit;
|
|
|
|
if (LineNumber < Memo.LineStateCount) and
|
|
(Memo.LineState[LineNumber] <> lnEntryProcessed) then begin
|
|
Memo.LineState[LineNumber] := lnEntryProcessed;
|
|
UpdateLineMarkers(Memo, LineNumber);
|
|
end;
|
|
|
|
if (FStepMode = smStepOut) and DebugEntry.StepOutMarker then
|
|
FStepMode := smStepInto { Pause on next line }
|
|
else if (FStepMode = smStepInto) or
|
|
((FStepMode = smStepOver) and not Intermediate) or
|
|
((FStepMode = smRunToCursor) and
|
|
(FRunToCursorPoint.Kind = Integer(Message.WParam)) and
|
|
(FRunToCursorPoint.Index = Message.LParam)) or
|
|
(Memo.BreakPoints.IndexOf(LineNumber) <> -1) then begin
|
|
MoveCaretAndActivateMemo(Memo, LineNumber, True);
|
|
HideError;
|
|
SetStepLine(Memo, LineNumber);
|
|
BringToForeground;
|
|
{ Tell Setup to pause }
|
|
Message.Result := 1;
|
|
FPaused := True;
|
|
FPausedAtCodeLine := DebugEntry.Kind = Ord(deCodeLine);
|
|
UpdateRunMenu;
|
|
UpdateCaption;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.WMDebuggerStepped(var Message: TMessage);
|
|
begin
|
|
DebuggerStepped(Message, False);
|
|
end;
|
|
|
|
procedure TMainForm.WMDebuggerSteppedIntermediate(var Message: TMessage);
|
|
begin
|
|
DebuggerStepped(Message, True);
|
|
end;
|
|
|
|
procedure TMainForm.WMDPIChanged(var Message: TMessage);
|
|
begin
|
|
inherited;
|
|
for var Memo in FMemos do
|
|
Memo.DPIChanged(Message);
|
|
end;
|
|
|
|
procedure TMainForm.WMDebuggerException(var Message: TMessage);
|
|
var
|
|
Memo: TIDEScintFileEdit;
|
|
DebugEntry: PDebugEntry;
|
|
LineNumber: Integer;
|
|
S: String;
|
|
begin
|
|
if FOptions.PauseOnDebuggerExceptions then begin
|
|
GetMemoAndDebugEntryFromMessage(Message.WParam, Message.LParam, Memo, DebugEntry);
|
|
|
|
if DebugEntry <> nil then
|
|
LineNumber := DebugEntry.LineNumber
|
|
else
|
|
LineNumber := -1;
|
|
|
|
if (Memo <> nil) and (LineNumber >= 0) then begin
|
|
MoveCaretAndActivateMemo(Memo, LineNumber, True);
|
|
SetStepLine(Memo, -1);
|
|
SetErrorLine(Memo, LineNumber);
|
|
end;
|
|
|
|
BringToForeground;
|
|
{ Tell Setup to pause }
|
|
Message.Result := 1;
|
|
FPaused := True;
|
|
FPausedAtCodeLine := (DebugEntry <> nil) and (DebugEntry.Kind = Ord(deCodeLine));
|
|
UpdateRunMenu;
|
|
UpdateCaption;
|
|
|
|
ReplyMessage(Message.Result); { so that Setup enters a paused state now }
|
|
if LineNumber >= 0 then begin
|
|
S := Format('Line %d:' + SNewLine + '%s', [LineNumber + 1, AddPeriod(FDebuggerException)]);
|
|
if (Memo <> nil) and (Memo.Filename <> '') then
|
|
S := Memo.Filename + SNewLine2 + S;
|
|
MsgBox(S, 'Runtime Error', mbCriticalError, mb_Ok)
|
|
end else
|
|
MsgBox(AddPeriod(FDebuggerException), 'Runtime Error', mbCriticalError, mb_Ok);
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.WMDebuggerSetForegroundWindow(var Message: TMessage);
|
|
begin
|
|
SetForegroundWindow(HWND(Message.WParam));
|
|
end;
|
|
|
|
procedure TMainForm.WMDebuggerCallStackCount(var Message: TMessage);
|
|
begin
|
|
FCallStackCount := Message.WParam;
|
|
end;
|
|
|
|
procedure TMainForm.WMCopyData(var Message: TWMCopyData);
|
|
var
|
|
S: String;
|
|
begin
|
|
case Message.CopyDataStruct.dwData of
|
|
CD_Debugger_ReplyW: begin
|
|
FReplyString := '';
|
|
SetString(FReplyString, PChar(Message.CopyDataStruct.lpData),
|
|
Message.CopyDataStruct.cbData div SizeOf(Char));
|
|
Message.Result := 1;
|
|
end;
|
|
CD_Debugger_ExceptionW: begin
|
|
SetString(FDebuggerException, PChar(Message.CopyDataStruct.lpData),
|
|
Message.CopyDataStruct.cbData div SizeOf(Char));
|
|
Message.Result := 1;
|
|
end;
|
|
CD_Debugger_UninstExeW: begin
|
|
SetString(FUninstExe, PChar(Message.CopyDataStruct.lpData),
|
|
Message.CopyDataStruct.cbData div sizeOf(Char));
|
|
Message.Result := 1;
|
|
end;
|
|
CD_Debugger_LogMessageW: begin
|
|
SetString(S, PChar(Message.CopyDataStruct.lpData),
|
|
Message.CopyDataStruct.cbData div SizeOf(Char));
|
|
DebugLogMessage(S);
|
|
Message.Result := 1;
|
|
end;
|
|
CD_Debugger_TempDirW: begin
|
|
{ Paranoia: Store it in a local variable first. That way, if there's
|
|
a problem reading the string FTempDir will be left unmodified.
|
|
Gotta be extra careful when storing a path we'll be deleting. }
|
|
SetString(S, PChar(Message.CopyDataStruct.lpData),
|
|
Message.CopyDataStruct.cbData div SizeOf(Char));
|
|
{ Extreme paranoia: If there are any embedded nulls, discard it. }
|
|
if Pos(#0, S) <> 0 then
|
|
S := '';
|
|
FTempDir := S;
|
|
Message.Result := 1;
|
|
end;
|
|
CD_Debugger_CallStackW: begin
|
|
SetString(S, PChar(Message.CopyDataStruct.lpData),
|
|
Message.CopyDataStruct.cbData div SizeOf(Char));
|
|
DebugShowCallStack(S, FCallStackCount);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TMainForm.DestroyLineState(const AMemo: TIDEScintFileEdit): Boolean;
|
|
begin
|
|
if Assigned(AMemo.LineState) then begin
|
|
AMemo.LineStateCapacity := 0;
|
|
AMemo.LineStateCount := 0;
|
|
FreeMem(AMemo.LineState);
|
|
AMemo.LineState := nil;
|
|
Result := True;
|
|
end else
|
|
Result := False;
|
|
end;
|
|
|
|
procedure TMainForm.DestroyDebugInfo;
|
|
var
|
|
HadDebugInfo: Boolean;
|
|
Memo: TIDEScintFileEdit;
|
|
begin
|
|
HadDebugInfo := False;
|
|
for Memo in FFileMemos do
|
|
if DestroyLineState(Memo) then
|
|
HadDebugInfo := True;
|
|
|
|
FDebugEntriesCount := 0;
|
|
FreeMem(FDebugEntries);
|
|
FDebugEntries := nil;
|
|
|
|
FVariableDebugEntriesCount := 0;
|
|
FreeMem(FVariableDebugEntries);
|
|
FVariableDebugEntries := nil;
|
|
|
|
FCompiledCodeText := '';
|
|
FCompiledCodeDebugInfo := '';
|
|
|
|
{ Clear all dots and reset breakpoint icons (unless exiting; no point) }
|
|
if HadDebugInfo and not(csDestroying in ComponentState) then
|
|
UpdateAllMemosLineMarkers;
|
|
end;
|
|
|
|
var
|
|
PrevCompilerFileIndex: Integer;
|
|
PrevMemo: TIDEScintFileEdit;
|
|
|
|
procedure TMainForm.ParseDebugInfo(DebugInfo: Pointer);
|
|
|
|
function GetMemoFromCompilerFileIndex(const CompilerFileIndex: Integer): TIDEScintFileEdit;
|
|
var
|
|
Memo: TIDEScintFileEdit;
|
|
begin
|
|
if (PrevCompilerFileIndex <> CompilerFileIndex) then begin
|
|
PrevMemo := nil;
|
|
for Memo in FFileMemos do begin
|
|
if Memo.Used and (Memo.CompilerFileIndex = CompilerFileIndex) then begin
|
|
PrevMemo := Memo;
|
|
Break;
|
|
end;
|
|
end;
|
|
PrevCompilerFileIndex := CompilerFileIndex;
|
|
end;
|
|
Result := PrevMemo;
|
|
end;
|
|
|
|
{ This creates and fills the DebugEntries and Memo LineState arrays }
|
|
var
|
|
Header: PDebugInfoHeader;
|
|
Memo: TIDEScintFileEdit;
|
|
Size: Cardinal;
|
|
I: Integer;
|
|
begin
|
|
DestroyDebugInfo;
|
|
|
|
Header := DebugInfo;
|
|
if (Header.ID <> DebugInfoHeaderID) or
|
|
(Header.Version <> DebugInfoHeaderVersion) then
|
|
raise Exception.Create('Unrecognized debug info format');
|
|
|
|
try
|
|
for Memo in FFileMemos do begin
|
|
if Memo.Used then begin
|
|
I := Memo.Lines.Count;
|
|
Memo.LineState := AllocMem(SizeOf(TLineState) * (I + LineStateGrowAmount));
|
|
Memo.LineStateCapacity := I + LineStateGrowAmount;
|
|
Memo.LineStateCount := I;
|
|
end;
|
|
end;
|
|
|
|
Inc(Cardinal(DebugInfo), SizeOf(Header^));
|
|
|
|
FDebugEntriesCount := Header.DebugEntryCount;
|
|
Size := FDebugEntriesCount * SizeOf(TDebugEntry);
|
|
GetMem(FDebugEntries, Size);
|
|
Move(DebugInfo^, FDebugEntries^, Size);
|
|
for I := 0 to FDebugEntriesCount-1 do
|
|
Dec(FDebugEntries[I].LineNumber);
|
|
Inc(Cardinal(DebugInfo), Size);
|
|
|
|
FVariableDebugEntriesCount := Header.VariableDebugEntryCount;
|
|
Size := FVariableDebugEntriesCount * SizeOf(TVariableDebugEntry);
|
|
GetMem(FVariableDebugEntries, Size);
|
|
Move(DebugInfo^, FVariableDebugEntries^, Size);
|
|
Inc(Cardinal(DebugInfo), Size);
|
|
|
|
SetString(FCompiledCodeText, PAnsiChar(DebugInfo), Header.CompiledCodeTextLength);
|
|
Inc(Cardinal(DebugInfo), Header.CompiledCodeTextLength);
|
|
|
|
SetString(FCompiledCodeDebugInfo, PAnsiChar(DebugInfo), Header.CompiledCodeDebugInfoLength);
|
|
|
|
PrevCompilerFileIndex := UnknownCompilerFileIndex;
|
|
|
|
for I := 0 to FDebugEntriesCount-1 do begin
|
|
if FDebugEntries[I].LineNumber >= 0 then begin
|
|
Memo := GetMemoFromCompilerFileIndex(FDebugEntries[I].FileIndex);
|
|
if (Memo <> nil) and (FDebugEntries[I].LineNumber < Memo.LineStateCount) then begin
|
|
if Memo.LineState[FDebugEntries[I].LineNumber] = lnUnknown then
|
|
Memo.LineState[FDebugEntries[I].LineNumber] := lnHasEntry;
|
|
end;
|
|
end;
|
|
end;
|
|
UpdateAllMemosLineMarkers;
|
|
except
|
|
DestroyDebugInfo;
|
|
raise;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.ResetAllMemosLineState;
|
|
{ Changes green dots back to grey dots }
|
|
var
|
|
Memo: TIDEScintFileEdit;
|
|
I: Integer;
|
|
begin
|
|
for Memo in FFileMemos do begin
|
|
if Memo.Used and Assigned(Memo.LineState) then begin
|
|
for I := 0 to Memo.LineStateCount-1 do begin
|
|
if Memo.LineState[I] = lnEntryProcessed then begin
|
|
Memo.LineState[I] := lnHasEntry;
|
|
UpdateLineMarkers(Memo, I);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.CheckIfTerminated;
|
|
var
|
|
H: THandle;
|
|
begin
|
|
if FDebugging then begin
|
|
{ Check if the process hosting the debug client (e.g. Setup or the
|
|
uninstaller second phase) has terminated. If the debug client hasn't
|
|
connected yet, check the initial process (e.g. SetupLdr or the
|
|
uninstaller first phase) instead. }
|
|
if FDebugClientWnd <> 0 then
|
|
H := FDebugClientProcessHandle
|
|
else
|
|
H := FProcessHandle;
|
|
if WaitForSingleObject(H, 0) <> WAIT_TIMEOUT then
|
|
DebuggingStopped(True);
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.DebuggingStopped(const WaitForTermination: Boolean);
|
|
|
|
function GetExitCodeText: String;
|
|
var
|
|
ExitCode: DWORD;
|
|
begin
|
|
{ Note: When debugging an uninstall, this will get the exit code off of
|
|
the first phase process, since that's the exit code users will see when
|
|
running the uninstaller outside the debugger. }
|
|
case WaitForSingleObject(FProcessHandle, 0) of
|
|
WAIT_OBJECT_0:
|
|
begin
|
|
if GetExitCodeProcess(FProcessHandle, ExitCode) then begin
|
|
{ If the high bit is set, the process was killed uncleanly (e.g.
|
|
by a debugger). Show the exit code as hex in that case. }
|
|
if ExitCode and $80000000 <> 0 then
|
|
Result := Format(DebugTargetStrings[FDebugTarget] + ' exit code: 0x%.8x', [ExitCode])
|
|
else
|
|
Result := Format(DebugTargetStrings[FDebugTarget] + ' exit code: %u', [ExitCode]);
|
|
end
|
|
else
|
|
Result := 'Unable to get ' + DebugTargetStrings[FDebugTarget] + ' exit code (GetExitCodeProcess failed)';
|
|
end;
|
|
WAIT_TIMEOUT:
|
|
Result := DebugTargetStrings[FDebugTarget] + ' is still running; can''t get exit code';
|
|
else
|
|
Result := 'Unable to get ' + DebugTargetStrings[FDebugTarget] + ' exit code (WaitForSingleObject failed)';
|
|
end;
|
|
end;
|
|
|
|
var
|
|
ExitCodeText: String;
|
|
begin
|
|
if WaitForTermination then begin
|
|
{ Give the initial process time to fully terminate so we can successfully
|
|
get its exit code }
|
|
WaitForSingleObject(FProcessHandle, 5000);
|
|
end;
|
|
FDebugging := False;
|
|
FDebugClientWnd := 0;
|
|
ExitCodeText := GetExitCodeText;
|
|
if FDebugClientProcessHandle <> 0 then begin
|
|
CloseHandle(FDebugClientProcessHandle);
|
|
FDebugClientProcessHandle := 0;
|
|
end;
|
|
CloseHandle(FProcessHandle);
|
|
FProcessHandle := 0;
|
|
FTempDir := '';
|
|
CheckIfRunningTimer.Enabled := False;
|
|
HideError;
|
|
SetStepLine(FStepMemo, -1);
|
|
UpdateRunMenu;
|
|
UpdateCaption;
|
|
DebugLogMessage('*** ' + ExitCodeText);
|
|
StatusBar.Panels[spExtraStatus].Text := ' ' + ExitCodeText;
|
|
end;
|
|
|
|
procedure TMainForm.DetachDebugger;
|
|
begin
|
|
CheckIfTerminated;
|
|
if not FDebugging then Exit;
|
|
SendNotifyMessage(FDebugClientWnd, WM_DebugClient_Detach, 0, 0);
|
|
DebuggingStopped(False);
|
|
end;
|
|
|
|
function TMainForm.AskToDetachDebugger: Boolean;
|
|
begin
|
|
if FDebugClientWnd = 0 then begin
|
|
MsgBox('Please stop the running ' + DebugTargetStrings[FDebugTarget] + ' process before performing this command.',
|
|
SCompilerFormCaption, mbError, MB_OK);
|
|
Result := False;
|
|
end else if MsgBox('This command will detach the debugger from the running ' + DebugTargetStrings[FDebugTarget] + ' process. Continue?',
|
|
SCompilerFormCaption, mbError, MB_OKCANCEL) = IDOK then begin
|
|
DetachDebugger;
|
|
Result := True;
|
|
end else
|
|
Result := False;
|
|
end;
|
|
|
|
function TMainForm.AnyMemoHasBreakPoint: Boolean;
|
|
begin
|
|
{ Also see RDeleteBreakPointsClick }
|
|
for var Memo in FFileMemos do
|
|
if Memo.Used and (Memo.BreakPoints.Count > 0) then
|
|
Exit(True);
|
|
Result := False;
|
|
end;
|
|
|
|
procedure TMainForm.RMenuClick(Sender: TObject);
|
|
begin
|
|
RDeleteBreakPoints.Enabled := AnyMemoHasBreakPoint;
|
|
{ See UpdateRunMenu for other menu items }
|
|
|
|
ApplyMenuBitmaps(RMenu);
|
|
end;
|
|
|
|
procedure TMainForm.BreakPointsPopupMenuClick(Sender: TObject);
|
|
begin
|
|
RToggleBreakPoint2.Enabled := FActiveMemo is TIDEScintFileEdit;
|
|
RDeleteBreakPoints2.Enabled := AnyMemoHasBreakPoint;
|
|
{ Also see UpdateRunMenu }
|
|
|
|
ApplyMenuBitmaps(Sender as TMenuItem);
|
|
end;
|
|
|
|
{ Should always be called when one of the Enabled states would change because
|
|
other code depends on the states being correct always even if the user never
|
|
clicks the Run menu. This is unlike the other menus. Note: also updates
|
|
BCompile and BStopCompile from the Build menu. }
|
|
procedure TMainForm.UpdateRunMenu;
|
|
begin
|
|
CheckIfTerminated;
|
|
BCompile.Enabled := not FCompiling and not FDebugging;
|
|
CompileButton.Enabled := BCompile.Enabled;
|
|
BStopCompile.Enabled := FCompiling;
|
|
StopCompileButton.Enabled := BStopCompile.Enabled;
|
|
RRun.Enabled := not FCompiling and (not FDebugging or FPaused);
|
|
RunButton.Enabled := RRun.Enabled;
|
|
RPause.Enabled := FDebugging and not FPaused;
|
|
PauseButton.Enabled := RPause.Enabled;
|
|
RRunToCursor.Enabled := RRun.Enabled and (FActiveMemo is TIDEScintFileEdit);
|
|
RStepInto.Enabled := RRun.Enabled;
|
|
RStepOver.Enabled := RRun.Enabled;
|
|
RStepOut.Enabled := FPaused;
|
|
RToggleBreakPoint.Enabled := FActiveMemo is TIDEScintFileEdit;
|
|
RTerminate.Enabled := FDebugging and (FDebugClientWnd <> 0);
|
|
TerminateButton.Enabled := RTerminate.Enabled;
|
|
REvaluate.Enabled := FDebugging and (FDebugClientWnd <> 0);
|
|
{ See RMenuClick for other menu items and also see BreakPointsPopupMenuClick }
|
|
end;
|
|
|
|
procedure TMainForm.UpdateSaveMenuItemAndButton;
|
|
begin
|
|
FSave.Enabled := FActiveMemo is TIDEScintFileEdit;
|
|
SaveButton.Enabled := FSave.Enabled;
|
|
end;
|
|
|
|
procedure TMainForm.UpdateTargetMenu;
|
|
begin
|
|
if FDebugTarget = dtSetup then begin
|
|
RTargetSetup.Checked := True;
|
|
TargetSetupButton.Down := True;
|
|
end else begin
|
|
RTargetUninstall.Checked := True;
|
|
TargetUninstallButton.Down := True;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.UpdateKeyMapping;
|
|
|
|
type
|
|
TKeyMappedMenu = TPair<TMenuItem, TPair<TShortcut, TToolButton>>;
|
|
|
|
function KMM(const MenuItem: TMenuItem; const DelphiKey: Word; const DelphiShift: TShiftState;
|
|
const VisualStudioKey: Word; const VisualStudioShift: TShiftState;
|
|
const ToolButton: TToolButton = nil): TKeyMappedMenu;
|
|
begin
|
|
var AShortCut: TShortCut;
|
|
case FOptions.KeyMappingType of
|
|
kmtDelphi: AShortCut := ShortCut(DelphiKey, DelphiShift);
|
|
kmtVisualStudio: AShortCut := ShortCut(VisualStudioKey, VisualStudioShift);
|
|
else
|
|
raise Exception.Create('Unknown FOptions.KeyMappingType');
|
|
end;
|
|
|
|
Result := TKeyMappedMenu.Create(MenuItem, TPair<TShortcut, TToolButton>.Create(AShortcut, ToolButton)); { These are records so no need to free }
|
|
end;
|
|
|
|
begin
|
|
var KeyMappedMenus := [
|
|
KMM(EFindRegEx, Ord('R'), [ssCtrl, ssAlt], Ord('R'), [ssAlt]),
|
|
KMM(BCompile, VK_F9, [ssCtrl], Ord('B'), [ssCtrl], CompileButton), { Also FCompileShortCut2 below }
|
|
KMM(RRun, VK_F9, [], VK_F5, [], RunButton),
|
|
KMM(RRunToCursor, VK_F4, [], VK_F10, [ssCtrl]),
|
|
KMM(RStepInto, VK_F7, [], VK_F11, []),
|
|
KMM(RStepOver, VK_F8, [], VK_F10, []),
|
|
KMM(RStepOut, VK_F8, [ssShift], VK_F11, [ssShift]),
|
|
KMM(RToggleBreakPoint, VK_F5, [], VK_F9, []),
|
|
KMM(RDeleteBreakPoints, VK_F5, [ssShift, ssCtrl], VK_F9, [ssShift, ssCtrl]),
|
|
KMM(RTerminate, VK_F2, [ssCtrl], VK_F5, [ssShift], TerminateButton),
|
|
KMM(REvaluate, VK_F7, [ssCtrl], VK_F9, [ssShift])];
|
|
|
|
FKeyMappedMenus.Clear;
|
|
|
|
for var KeyMappedMenu in KeyMappedMenus do begin
|
|
var ShortCut := KeyMappedMenu.Value.Key;
|
|
var ToolButton := KeyMappedMenu.Value.Value;
|
|
KeyMappedMenu.Key.ShortCut := ShortCut;
|
|
if ToolButton <> nil then begin
|
|
var MenuItem := KeyMappedMenu.Key;
|
|
ToolButton.Hint := Format('%s (%s)', [RemoveAccelChar(MenuItem.Caption), NewShortCutToText(ShortCut)]);
|
|
end;
|
|
FKeyMappedMenus.Add(ShortCut, ToolButton);
|
|
end;
|
|
|
|
{ Set fake shortcuts on any duplicates of the above in popup menus }
|
|
|
|
SetFakeShortCut(RToggleBreakPoint2, RToggleBreakPoint.ShortCut);
|
|
SetFakeShortCut(RDeleteBreakPoints2, RDeleteBreakPoints.ShortCut);
|
|
|
|
{ Handle two special cases:
|
|
-The Nav buttons have no corresponding menu item and also no ShortCut property
|
|
so they need special handling
|
|
-Visual Studio and Delphi have separate Compile and Build shortcuts and the
|
|
Compile shortcut is displayed by the menu and is set above but we want to
|
|
allow the Build shortcuts as well for our single Build/Compile command }
|
|
|
|
FBackNavButtonShortCut := ShortCut(VK_LEFT, [ssAlt]);
|
|
FForwardNavButtonShortCut := ShortCut(VK_RIGHT, [ssAlt]);
|
|
|
|
case FOptions.KeyMappingType of
|
|
kmtDelphi:
|
|
begin
|
|
FBackNavButtonShortCut2 := 0;
|
|
FForwardNavButtonShortCut2 := 0;
|
|
FCompileShortCut2 := ShortCut(VK_F9, [ssShift]);
|
|
end;
|
|
kmtVisualStudio:
|
|
begin
|
|
FBackNavButtonShortCut2 := ShortCut(VK_OEM_MINUS, [ssCtrl]);
|
|
FForwardNavButtonShortCut2 := ShortCut(VK_OEM_MINUS, [ssCtrl, ssShift]);
|
|
FCompileShortCut2 := ShortCut(VK_F7, []);
|
|
end;
|
|
else
|
|
raise Exception.Create('Unknown FOptions.KeyMappingType');
|
|
end;
|
|
|
|
BackNavButton.Hint := Format('Back (%s)', [NewShortCutToText(FBackNavButtonShortCut)]);
|
|
FKeyMappedMenus.Add(FBackNavButtonShortCut, nil);
|
|
ForwardNavButton.Hint := Format('Forward (%s)', [NewShortCutToText(FForwardNavButtonShortCut)]);
|
|
FKeyMappedMenus.Add(FForwardNavButtonShortCut, nil);
|
|
end;
|
|
|
|
procedure TMainForm.UpdateTheme;
|
|
begin
|
|
FTheme.Typ := FOptions.ThemeType;
|
|
|
|
SetHelpFileDark(FTheme.Dark);
|
|
|
|
for var Memo in FMemos do begin
|
|
Memo.UpdateThemeColorsAndStyleAttributes;
|
|
SetControlWindowTheme(Memo, FTheme.Dark);
|
|
end;
|
|
|
|
InitFormTheme(Self);
|
|
ToolbarPanel.Color := FTheme.Colors[tcToolBack];
|
|
|
|
if FTheme.Dark then begin
|
|
ThemedToolbarVirtualImageList.ImageCollection := ImagesModule.DarkToolBarImageCollection;
|
|
ThemedMarkersAndACVirtualImageList.ImageCollection := ImagesModule.DarkMarkersAndACImageCollection;
|
|
end else begin
|
|
ThemedToolbarVirtualImageList.ImageCollection := ImagesModule.LightToolBarImageCollection;
|
|
ThemedMarkersAndACVirtualImageList.ImageCollection := ImagesModule.LightMarkersAndACImageCollection;
|
|
end;
|
|
|
|
UpdateBevel1Visibility;
|
|
UpdateMarginsAndAutoCompleteIcons;
|
|
|
|
SplitPanel.ParentBackground := False;
|
|
SplitPanel.Color := FTheme.Colors[tcSplitterBack];
|
|
|
|
if FTheme.Dark then begin
|
|
MemosTabSet.Theme := FTheme;
|
|
OutputTabSet.Theme := FTheme;
|
|
end else begin
|
|
MemosTabSet.Theme := nil;
|
|
OutputTabSet.Theme := nil;
|
|
end;
|
|
|
|
FMenuDarkBackgroundBrush.Color := FTheme.Colors[tcToolBack];
|
|
FMenuDarkHotOrSelectedBrush.Color := $2C2C2C; { Same as themed menu drawn by Windows 11, which is close to Colors[tcBack] }
|
|
|
|
DrawMenuBar(Handle);
|
|
|
|
{ SetPreferredAppMode doesn't work without FlushMenuThemes here: it would have
|
|
to be called before the form is created to have an effect without
|
|
FlushMenuThemes. So don't call SetPreferredAppMode if FlushMenuThemes is
|
|
missing. }
|
|
if Assigned(SetPreferredAppMode) and Assigned(FlushMenuThemes) then begin
|
|
FMenuImageList := ThemedToolbarVirtualImageList;
|
|
if FTheme.Dark then
|
|
SetPreferredAppMode(PAM_FORCEDARK)
|
|
else
|
|
SetPreferredAppMode(PAM_FORCELIGHT);
|
|
FlushMenuThemes;
|
|
end else
|
|
FMenuImageList := LightToolbarVirtualImageList;
|
|
end;
|
|
|
|
procedure TMainForm.UpdateThemeData(const Open: Boolean);
|
|
|
|
procedure CloseThemeDataIfNeeded(var ThemeData: HTHEME);
|
|
begin
|
|
if ThemeData <> 0 then begin
|
|
CloseThemeData(ThemeData);
|
|
ThemeData := 0;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
CloseThemeDataIfNeeded(FProgressThemeData);
|
|
CloseThemeDataIfNeeded(FMenuThemeData);
|
|
CloseThemeDataIfNeeded(FToolbarThemeData);
|
|
|
|
if Open and UseThemes then begin
|
|
FProgressThemeData := OpenThemeData(Handle, 'Progress');
|
|
FMenuThemeData := OpenThemeData(Handle, 'Menu');
|
|
FToolbarThemeData := OpenThemeData(Handle, 'Toolbar');
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.UpdateUpdatePanel;
|
|
begin
|
|
UpdatePanel.Visible := FUpdatePanelMessages.Count > 0;
|
|
if UpdatePanel.Visible then begin
|
|
var MessageToShowIndex := FUpdatePanelMessages.Count-1;
|
|
UpdateLinkLabel.Tag := MessageToShowIndex;
|
|
UpdateLinkLabel.Caption := FUpdatePanelMessages[MessageToShowIndex].Msg;
|
|
UpdatePanel.Color := FUpdatePanelMessages[MessageToShowIndex].Color;
|
|
end;
|
|
UpdateBevel1Visibility;
|
|
end;
|
|
|
|
procedure TMainForm.UpdateMenuBitmapsIfNeeded;
|
|
|
|
procedure AddMenuBitmap(const MenuBitmaps: TMenuBitmaps; const DC: HDC; const BitmapInfo: TBitmapInfo;
|
|
const MenuItem: TMenuItem; const ImageList: TVirtualImageList; const ImageIndex: Integer); overload;
|
|
begin
|
|
var pvBits: Pointer;
|
|
var Bitmap := CreateDIBSection(DC, bitmapInfo, DIB_RGB_COLORS, pvBits, 0, 0);
|
|
var OldBitmap := SelectObject(DC, Bitmap);
|
|
if ImageList_Draw(ImageList.Handle, ImageIndex, DC, 0, 0, ILD_TRANSPARENT) then
|
|
MenuBitmaps.Add(MenuItem, Bitmap)
|
|
else begin
|
|
SelectObject(DC, OldBitmap);
|
|
DeleteObject(Bitmap);
|
|
end;
|
|
end;
|
|
|
|
procedure AddMenuBitmap(const MenuBitmaps: TMenuBitmaps; const DC: HDC; const BitmapInfo: TBitmapInfo;
|
|
const MenuItem: TMenuItem; const ImageList: TVirtualImageList; const ImageName: String); overload;
|
|
begin
|
|
AddMenuBitmap(MenuBitmaps, DC, BitmapInfo, MenuItem, ImageList, ImageList.GetIndexByName(ImageName));
|
|
end;
|
|
|
|
type
|
|
TButtonedMenu = TPair<TMenuItem, TToolButton>;
|
|
TNamedMenu = TPair<TMenuItem, String>;
|
|
|
|
function BM(const MenuItem: TMenuItem; const ToolButton: TToolButton): TButtonedMenu;
|
|
begin
|
|
Result := TButtonedMenu.Create(MenuItem, ToolButton); { This is a record so no need to free }
|
|
end;
|
|
|
|
function NM(const MenuItem: TMenuItem; const Name: String): TNamedMenu;
|
|
begin
|
|
Result := TNamedMenu.Create(MenuItem, Name); { This is a record so no need to free }
|
|
end;
|
|
|
|
begin
|
|
{ This will create bitmaps for the current DPI using ImageList_Draw.
|
|
|
|
These draw perfectly even on Windows 7. Other techniques don't work because
|
|
they loose transparency or only look good on Windows 8 and later. Or they do
|
|
work but cause lots more VCL code to be run than just our simple CreateDIB+Draw
|
|
combo.
|
|
|
|
ApplyBitmaps will apply them to menu items using SetMenuItemInfo. The menu item
|
|
does not copy the bitmap so they should still be alive after ApplyBitmaps is done.
|
|
|
|
Depends on FMenuImageList to pick the best size icons for the current DPI
|
|
from the collection. }
|
|
|
|
var ImageList := FMenuImageList;
|
|
|
|
var NewSize: TSize;
|
|
NewSize.cx := ImageList.Width;
|
|
NewSize.cy := ImageList.Height;
|
|
if (NewSize.cx <> FMenuBitmapsSize.cx) or (NewSize.cy <> FMenuBitmapsSize.cy) or
|
|
(ImageList.ImageCollection <> FMenuBitmapsSourceImageCollection) then begin
|
|
|
|
{ Cleanup previous }
|
|
|
|
for var Bitmap in FMenuBitmaps.Values do
|
|
DeleteObject(Bitmap);
|
|
FMenuBitmaps.Clear;
|
|
|
|
{ Create }
|
|
|
|
var DC := CreateCompatibleDC(0);
|
|
if DC <> 0 then begin
|
|
try
|
|
var BitmapInfo := CreateBitmapInfo(NewSize.cx, NewSize.cy, 32);
|
|
|
|
var ButtonedMenus := [
|
|
BM(FNewMainFile, NewMainFileButton),
|
|
BM(FOpenMainFile, OpenMainFileButton),
|
|
BM(FSave, SaveButton),
|
|
BM(BCompile, CompileButton),
|
|
BM(BStopCompile, StopCompileButton),
|
|
BM(RRun, RunButton),
|
|
BM(RPause, PauseButton),
|
|
BM(RTerminate, TerminateButton),
|
|
BM(HDoc, HelpButton)];
|
|
|
|
for var ButtonedMenu in ButtonedMenus do
|
|
AddMenuBitmap(FMenuBitmaps, DC, BitmapInfo, ButtonedMenu.Key, ImageList, ButtonedMenu.Value.ImageIndex);
|
|
|
|
var NamedMenus := [
|
|
NM(FClearRecent, 'eraser'),
|
|
NM(FSaveMainFileAs, 'save-as-filled'),
|
|
NM(FSaveAll, 'save-all-filled'),
|
|
NM(FPrint, 'printer'),
|
|
NM(EUndo, 'command-undo-1'),
|
|
NM(ERedo, 'command-redo-1'),
|
|
NM(ECut, 'clipboard-cut'),
|
|
NM(ECopy, 'clipboard-copy'),
|
|
NM(POutputListCopy, 'clipboard-copy'),
|
|
NM(EPaste, 'clipboard-paste'),
|
|
NM(EDelete, 'symbol-cancel'),
|
|
NM(ESelectAll, 'select-all'),
|
|
NM(POutputListSelectAll, 'select-all'),
|
|
NM(EFind, 'find'),
|
|
NM(EFindInFiles, 'folder-open-filled-find'),
|
|
//NM(EFindNext, 'unused\find-arrow-right-2'),
|
|
//NM(EFindPrevious, 'unused\find-arrow-left-2'),
|
|
NM(EReplace, 'replace'),
|
|
NM(EFoldLine, 'symbol-remove'),
|
|
NM(EUnfoldLine, 'symbol-add'),
|
|
NM(VZoomIn, 'zoom-in'),
|
|
NM(VZoomOut, 'zoom-out'),
|
|
NM(VNextTab, 'control-tab-filled-arrow-right-2'),
|
|
NM(VPreviousTab, 'control-tab-filled-arrow-left-2'),
|
|
//NM(VCloseCurrentTab, 'unused\control-tab-filled-cancel-2'),
|
|
NM(VReopenTabs, 'control-tab-filled-redo-1'),
|
|
NM(VReopenTabs2, 'control-tab-filled-redo-1'),
|
|
NM(BOpenOutputFolder, 'folder-open-filled'),
|
|
NM(RParameters, 'control-edit'),
|
|
NM(RRunToCursor, 'debug-start-filled-arrow-right-2'),
|
|
NM(RStepInto, 'debug-step-into'),
|
|
NM(RStepOver, 'debug-step-over'),
|
|
NM(RStepOut, 'debug-step-out'),
|
|
NM(RToggleBreakPoint, 'debug-breakpoint-filled'),
|
|
NM(RToggleBreakPoint2, 'debug-breakpoint-filled'),
|
|
NM(RDeleteBreakPoints, 'debug-breakpoints-filled-eraser'),
|
|
NM(RDeleteBreakPoints2, 'debug-breakpoints-filled-eraser'),
|
|
NM(REvaluate, 'variables'),
|
|
NM(TAddRemovePrograms, 'application'),
|
|
NM(TGenerateGUID, 'tag-script-filled'),
|
|
NM(TFilesDesigner, 'documents-script-filled'),
|
|
NM(TRegistryDesigner, 'control-tree-script-filled'),
|
|
NM(TMsgBoxDesigner, 'comment-text-script-filled'),
|
|
NM(TSignTools, 'key-filled'),
|
|
NM(TOptions, 'gear-filled'),
|
|
NM(HDonate, 'heart-filled'),
|
|
NM(HMailingList, 'alert-filled'),
|
|
NM(HWhatsNew, 'announcement'),
|
|
NM(HWebsite, 'home'),
|
|
NM(HAbout, 'button-info')];
|
|
|
|
for var NamedMenu in NamedMenus do
|
|
AddMenuBitmap(FMenuBitmaps, DC, BitmapInfo, NamedMenu.Key, ImageList, NamedMenu.Value);
|
|
finally
|
|
DeleteDC(DC);
|
|
end;
|
|
end;
|
|
|
|
FMenuBitmapsSize := NewSize;
|
|
FMenuBitmapsSourceImageCollection := FMenuImageList.ImageCollection;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.ApplyMenuBitmaps(const ParentMenuItem: TMenuItem);
|
|
begin
|
|
UpdateMenuBitmapsIfNeeded;
|
|
|
|
{ Setting MainMenu1.ImageList or a menu item's .Bitmap to make a menu item
|
|
show a bitmap is not OK: it causes the entire menu to become owner drawn
|
|
which makes it looks different from native menus and additionally the trick
|
|
SetFakeShortCut uses doesn't work with owner drawn menus.
|
|
|
|
Instead UpdateMenuBitmapsIfNeeded has prepared images which can be applied
|
|
to native menu items using SetMenuItemInfo and MIIM_BITMAP - which is what we
|
|
do below.
|
|
|
|
A problem with this is that Delphi's TMenu likes to constantly recreate the
|
|
underlying native menu items, for example when updating the caption. Sometimes
|
|
it will even destroy and repopulate an entire menu because of a simple change
|
|
like setting the caption of a single item!
|
|
|
|
This means the result of our SetMenuItemInfo call (which Delphi doesn't know
|
|
about) will quickly become lost when Delphi recreates the menu item.
|
|
|
|
Fixing this in the OnChange event is not possible, this is event is more
|
|
than useless.
|
|
|
|
The solution is shown by TMenu.DispatchPopup: in reaction to WM_INITMENUPOPUP
|
|
it calls our Click events right before the menu is shown, giving us the
|
|
opportunity to call SetMenuItemInfo for the menu's items.
|
|
|
|
This works unless Delphi decides to destroy and repopulate the menu after
|
|
calling Click. Most amazingly it can do that indeed: it does this if the DPI
|
|
changed since the last popup or if a automatic hotkey change or line reduction
|
|
happens due to the menu's AutoHotkeys or AutoLineReduction properties. To make
|
|
things even worse: for the Run menu it does this each and every time it is
|
|
opened: this menu currently has a 'Step Out' item which has no shortcut but
|
|
also all its letters are taken by another item already. This confuses the
|
|
AutoHotkeys code, making it destroy and repopulate the entire menu over and
|
|
over because it erroneously thinks a hotkey changed.
|
|
|
|
To avoid this MainMenu1.AutoHotkeys was set to maManual since we have always
|
|
managed the hotkeys ourselves anyway and .AutoLineReduction was also set to
|
|
maManual and we now manage that ourselves as well.
|
|
|
|
This just leave an issue with the icons not appearing on the first popup after
|
|
a DPI change and this seems like a minor issue only.
|
|
|
|
For TPopupMenu: calling ApplyMenuBitmaps(PopupMenu.Items) does work but makes
|
|
the popup only show icons without text. This seems to be a limitiation of menus
|
|
created by CreatePopupMenu instead of CreateMenu. This is why our popups with
|
|
icons are all menu items popped using TMainFormPopupMenu. These menu items
|
|
are hidden in the main menu and temporarily shown on popup. Popping an always
|
|
hidden menu item (or a visible one as a child of a hidden parent) doesnt work. }
|
|
|
|
var mmi: TMenuItemInfo;
|
|
mmi.cbSize := SizeOf(mmi);
|
|
mmi.fMask := MIIM_BITMAP;
|
|
|
|
for var I := 0 to ParentMenuItem.Count-1 do begin
|
|
var MenuItem := ParentMenuItem.Items[I];
|
|
if MenuItem.Visible then begin
|
|
if FMenuBitmaps.TryGetValue(MenuItem, mmi.hbmpItem) then
|
|
SetMenuItemInfo(ParentMenuItem.Handle, MenuItem.Command, False, mmi);
|
|
if MenuItem.Count > 0 then
|
|
ApplyMenuBitmaps(MenuItem);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.StartProcess;
|
|
var
|
|
RunFilename, RunParameters, WorkingDir: String;
|
|
Info: TShellExecuteInfo;
|
|
SaveFocusWindow: HWND;
|
|
WindowList: Pointer;
|
|
ShellExecuteResult: BOOL;
|
|
ErrorCode: DWORD;
|
|
begin
|
|
if FDebugTarget = dtUninstall then begin
|
|
if FUninstExe = '' then
|
|
raise Exception.Create(SCompilerNeedUninstExe);
|
|
RunFilename := FUninstExe;
|
|
end else begin
|
|
if FCompiledExe = '' then
|
|
raise Exception.Create(SCompilerNeedCompiledExe);
|
|
RunFilename := FCompiledExe;
|
|
end;
|
|
RunParameters := Format('/DEBUGWND=$%x ', [Handle]) + FRunParameters;
|
|
|
|
ResetAllMemosLineState;
|
|
DebugOutputList.Clear;
|
|
SendMessage(DebugOutputList.Handle, LB_SETHORIZONTALEXTENT, 0, 0);
|
|
DebugCallStackList.Clear;
|
|
SendMessage(DebugCallStackList.Handle, LB_SETHORIZONTALEXTENT, 0, 0);
|
|
if not (OutputTabSet.TabIndex in [tiDebugOutput, tiDebugCallStack]) then
|
|
OutputTabSet.TabIndex := tiDebugOutput;
|
|
SetStatusPanelVisible(True);
|
|
|
|
FillChar(Info, SizeOf(Info), 0);
|
|
Info.cbSize := SizeOf(Info);
|
|
Info.fMask := SEE_MASK_FLAG_NO_UI or SEE_MASK_FLAG_DDEWAIT or
|
|
SEE_MASK_NOCLOSEPROCESS or SEE_MASK_NOZONECHECKS;
|
|
Info.Wnd := Handle;
|
|
if FOptions.RunAsDifferentUser then
|
|
Info.lpVerb := 'runas'
|
|
else
|
|
Info.lpVerb := 'open';
|
|
Info.lpFile := PChar(RunFilename);
|
|
Info.lpParameters := PChar(RunParameters);
|
|
WorkingDir := PathExtractDir(RunFilename);
|
|
Info.lpDirectory := PChar(WorkingDir);
|
|
Info.nShow := SW_SHOWNORMAL;
|
|
{ When the RunAsDifferentUser option is enabled, it's this process that
|
|
waits on the UAC dialog, not Setup(Ldr), so we need to disable windows to
|
|
prevent the user from clicking other things before the UAC dialog is
|
|
dismissed (which is definitely a possibility if the "Switch to the secure
|
|
desktop when prompting for elevation" setting is disabled in Group
|
|
Policy). }
|
|
SaveFocusWindow := GetFocus;
|
|
WindowList := DisableTaskWindows(Handle);
|
|
try
|
|
{ Also temporarily remove the focus since a disabled window's children can
|
|
still receive keystrokes. This is needed if Windows doesn't switch to
|
|
the secure desktop immediately and instead shows a flashing taskbar
|
|
button that the user must click (which happened on Windows Vista; I'm
|
|
unable to reproduce it on Windows 11). }
|
|
Windows.SetFocus(0);
|
|
ShellExecuteResult := ShellExecuteEx(@Info);
|
|
ErrorCode := GetLastError;
|
|
finally
|
|
EnableTaskWindows(WindowList);
|
|
Windows.SetFocus(SaveFocusWindow);
|
|
end;
|
|
if not ShellExecuteResult then begin
|
|
{ Don't display error message if user clicked Cancel at UAC dialog }
|
|
if ErrorCode = ERROR_CANCELLED then
|
|
Abort;
|
|
raise Exception.CreateFmt(SCompilerExecuteSetupError2, [RunFilename,
|
|
ErrorCode, Win32ErrorString(ErrorCode)]);
|
|
end;
|
|
FDebugging := True;
|
|
FPaused := False;
|
|
FProcessHandle := Info.hProcess;
|
|
CheckIfRunningTimer.Enabled := True;
|
|
UpdateRunMenu;
|
|
UpdateCaption;
|
|
DebugLogMessage('*** ' + DebugTargetStrings[FDebugTarget] + ' started');
|
|
end;
|
|
|
|
procedure TMainForm.CompileIfNecessary;
|
|
|
|
function UnopenedIncludedFileModifiedSinceLastCompile: Boolean;
|
|
var
|
|
IncludedFile: TIncludedFile;
|
|
NewTime: TFileTime;
|
|
begin
|
|
Result := False;
|
|
for IncludedFile in FIncludedFiles do begin
|
|
if (IncludedFile.Memo = nil) and IncludedFile.HasLastWriteTime and
|
|
GetLastWriteTimeOfFile(IncludedFile.Filename, @NewTime) and
|
|
(CompareFileTime(IncludedFile.LastWriteTime, NewTime) <> 0) then begin
|
|
Result := True;
|
|
Exit;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
CheckIfTerminated;
|
|
|
|
{ Display warning if the user modified the script while running - does not support unopened included files }
|
|
if FDebugging and FModifiedAnySinceLastCompileAndGo then begin
|
|
if MsgBox('The changes you made will not take effect until you ' +
|
|
're-compile.' + SNewLine2 + 'Continue running anyway?',
|
|
SCompilerFormCaption, mbError, MB_YESNO) <> IDYES then
|
|
Abort;
|
|
FModifiedAnySinceLastCompileAndGo := False;
|
|
{ The process may have terminated while the message box was up; check,
|
|
and if it has, we want to recompile below }
|
|
CheckIfTerminated;
|
|
end;
|
|
|
|
if not FDebugging and (FModifiedAnySinceLastCompile or UnopenedIncludedFileModifiedSinceLastCompile) then
|
|
CompileFile('', False);
|
|
end;
|
|
|
|
procedure TMainForm.Go(AStepMode: TStepMode);
|
|
begin
|
|
CompileIfNecessary;
|
|
FStepMode := AStepMode;
|
|
HideError;
|
|
SetStepLine(FStepMemo, -1);
|
|
if FDebugging then begin
|
|
if FPaused then begin
|
|
FPaused := False;
|
|
UpdateRunMenu;
|
|
UpdateCaption;
|
|
if DebugCallStackList.Items.Count > 0 then begin
|
|
DebugCallStackList.Clear;
|
|
SendMessage(DebugCallStackList.Handle, LB_SETHORIZONTALEXTENT, 0, 0);
|
|
DebugCallStackList.Update;
|
|
end;
|
|
{ Tell it to continue }
|
|
SendNotifyMessage(FDebugClientWnd, WM_DebugClient_Continue,
|
|
Ord(AStepMode = smStepOver), 0);
|
|
end;
|
|
end
|
|
else
|
|
StartProcess;
|
|
end;
|
|
|
|
function TMainForm.EvaluateConstant(const S: String;
|
|
out Output: String): Integer;
|
|
begin
|
|
{ This is about evaluating constants like 'app' and not [Code] variables }
|
|
FReplyString := '';
|
|
Result := SendCopyDataMessageStr(FDebugClientWnd, Handle,
|
|
CD_DebugClient_EvaluateConstantW, S);
|
|
if Result > 0 then
|
|
Output := FReplyString;
|
|
end;
|
|
|
|
function TMainForm.EvaluateVariableEntry(const DebugEntry: PVariableDebugEntry;
|
|
out Output: String): Integer;
|
|
begin
|
|
FReplyString := '';
|
|
Result := SendCopyDataMessage(FDebugClientWnd, Handle, CD_DebugClient_EvaluateVariableEntry,
|
|
DebugEntry, SizeOf(DebugEntry^));
|
|
if Result > 0 then
|
|
Output := FReplyString;
|
|
end;
|
|
|
|
procedure TMainForm.RRunClick(Sender: TObject);
|
|
begin
|
|
Go(smRun);
|
|
end;
|
|
|
|
procedure TMainForm.RParametersClick(Sender: TObject);
|
|
begin
|
|
ReadMRUParametersList;
|
|
InputQueryCombo('Run Parameters', 'Command line parameters for ' + DebugTargetStrings[dtSetup] +
|
|
' and ' + DebugTargetStrings[dtUninstall] + ':', FRunParameters, FMRUParametersList);
|
|
if FRunParameters <> '' then
|
|
ModifyMRUParametersList(FRunParameters, True);
|
|
end;
|
|
|
|
procedure TMainForm.RPauseClick(Sender: TObject);
|
|
begin
|
|
if FDebugging and not FPaused then begin
|
|
if FStepMode <> smStepInto then begin
|
|
FStepMode := smStepInto;
|
|
UpdateCaption;
|
|
end
|
|
else
|
|
MsgBox('A pause is already pending.', SCompilerFormCaption, mbError,
|
|
MB_OK);
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.RRunToCursorClick(Sender: TObject);
|
|
|
|
function GetDebugEntryFromMemoAndLineNumber(Memo: TIDEScintFileEdit; LineNumber: Integer;
|
|
var DebugEntry: TDebugEntry): Boolean;
|
|
var
|
|
I: Integer;
|
|
begin
|
|
Result := False;
|
|
for I := 0 to FDebugEntriesCount-1 do begin
|
|
if (FDebugEntries[I].FileIndex = Memo.CompilerFileIndex) and
|
|
(FDebugEntries[I].LineNumber = LineNumber) then begin
|
|
DebugEntry := FDebugEntries[I];
|
|
Result := True;
|
|
Break;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
CompileIfNecessary;
|
|
if not GetDebugEntryFromMemoAndLineNumber((FActiveMemo as TIDEScintFileEdit), FActiveMemo.CaretLine, FRunToCursorPoint) then begin
|
|
MsgBox('No code was generated for the current line.', SCompilerFormCaption,
|
|
mbError, MB_OK);
|
|
Exit;
|
|
end;
|
|
Go(smRunToCursor);
|
|
end;
|
|
|
|
procedure TMainForm.RStepIntoClick(Sender: TObject);
|
|
begin
|
|
Go(smStepInto);
|
|
end;
|
|
|
|
procedure TMainForm.RStepOutClick(Sender: TObject);
|
|
begin
|
|
if FPausedAtCodeLine then
|
|
Go(smStepOut)
|
|
else
|
|
Go(smStepInto);
|
|
end;
|
|
|
|
procedure TMainForm.RStepOverClick(Sender: TObject);
|
|
begin
|
|
Go(smStepOver);
|
|
end;
|
|
|
|
procedure TMainForm.RTerminateClick(Sender: TObject);
|
|
var
|
|
S, Dir: String;
|
|
begin
|
|
S := 'This will unconditionally terminate the running ' +
|
|
DebugTargetStrings[FDebugTarget] + ' process. Continue?';
|
|
|
|
if FDebugTarget = dtSetup then
|
|
S := S + #13#10#13#10'Note that if ' + DebugTargetStrings[FDebugTarget] + ' ' +
|
|
'is currently in the installation phase, any changes made to the ' +
|
|
'system thus far will not be undone, nor will uninstall data be written.';
|
|
|
|
if MsgBox(S, 'Terminate', mbConfirmation, MB_YESNO or MB_DEFBUTTON2) <> IDYES then
|
|
Exit;
|
|
CheckIfTerminated;
|
|
if FDebugging then begin
|
|
DebugLogMessage('*** Terminating process');
|
|
Win32Check(TerminateProcess(FDebugClientProcessHandle, 6));
|
|
if (WaitForSingleObject(FDebugClientProcessHandle, 5000) <> WAIT_TIMEOUT) and
|
|
(FTempDir <> '') then begin
|
|
Dir := FTempDir;
|
|
FTempDir := '';
|
|
DebugLogMessage('*** Removing left-over temporary directory: ' + Dir);
|
|
{ Sleep for a bit to allow files to be unlocked by Windows,
|
|
otherwise it fails intermittently (with Hyper-Threading, at least) }
|
|
Sleep(50);
|
|
if not DeleteDirTree(Dir) and DirExists(Dir) then
|
|
DebugLogMessage('*** Failed to remove temporary directory');
|
|
end;
|
|
DebuggingStopped(True);
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.REvaluateClick(Sender: TObject);
|
|
var
|
|
Output: String;
|
|
begin
|
|
if InputQuery('Evaluate', 'Constant to evaluate (e.g., "{app}"):',
|
|
FLastEvaluateConstantText) then begin
|
|
case EvaluateConstant(FLastEvaluateConstantText, Output) of
|
|
1: MsgBox(Output, 'Evaluate Result', mbInformation, MB_OK);
|
|
2: MsgBox(Output, 'Evaluate Error', mbError, MB_OK);
|
|
else
|
|
MsgBox('An unknown error occurred.', 'Evaluate Error', mbError, MB_OK);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.CheckIfRunningTimerTimer(Sender: TObject);
|
|
begin
|
|
{ In cases of normal Setup termination, we receive a WM_Debugger_Goodbye
|
|
message. But in case we don't get that, use a timer to periodically check
|
|
if the process is no longer running. }
|
|
CheckIfTerminated;
|
|
end;
|
|
|
|
procedure TMainForm.POutputListCopyClick(Sender: TObject);
|
|
var
|
|
ListBox: TListBox;
|
|
Text: String;
|
|
I: Integer;
|
|
begin
|
|
if CompilerOutputList.Visible then
|
|
ListBox := CompilerOutputList
|
|
else if DebugOutputList.Visible then
|
|
ListBox := DebugOutputList
|
|
else if DebugCallStackList.Visible then
|
|
ListBox := DebugCallStackList
|
|
else
|
|
ListBox := FindResultsList;
|
|
Text := '';
|
|
if ListBox.SelCount > 0 then begin
|
|
for I := 0 to ListBox.Items.Count-1 do begin
|
|
if ListBox.Selected[I] then begin
|
|
if Text <> '' then
|
|
Text := Text + SNewLine;
|
|
Text := Text + ListBox.Items[I];
|
|
end;
|
|
end;
|
|
end;
|
|
Clipboard.AsText := Text;
|
|
end;
|
|
|
|
procedure TMainForm.POutputListSelectAllClick(Sender: TObject);
|
|
var
|
|
ListBox: TListBox;
|
|
I: Integer;
|
|
begin
|
|
if CompilerOutputList.Visible then
|
|
ListBox := CompilerOutputList
|
|
else if DebugOutputList.Visible then
|
|
ListBox := DebugOutputList
|
|
else if DebugCallStackList.Visible then
|
|
ListBox := DebugCallStackList
|
|
else
|
|
ListBox := FindResultsList;
|
|
ListBox.Items.BeginUpdate;
|
|
try
|
|
for I := 0 to ListBox.Items.Count-1 do
|
|
ListBox.Selected[I] := True;
|
|
finally
|
|
ListBox.Items.EndUpdate;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.OutputListKeyDown(Sender: TObject; var Key: Word;
|
|
Shift: TShiftState);
|
|
begin
|
|
if Shift = [ssCtrl] then begin
|
|
if Key = Ord('C') then
|
|
POutputListCopyClick(Sender)
|
|
else if Key = Ord('A') then
|
|
POutputListSelectAllClick(Sender);
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.AppOnIdle(Sender: TObject; var Done: Boolean);
|
|
begin
|
|
{ For an explanation of this, see the comment where HandleMessage is called }
|
|
if FCompiling then
|
|
Done := False;
|
|
|
|
FBecameIdle := True;
|
|
end;
|
|
|
|
procedure TMainForm.EGotoClick(Sender: TObject);
|
|
var
|
|
S: String;
|
|
L: Integer;
|
|
begin
|
|
S := IntToStr(FActiveMemo.CaretLine + 1);
|
|
if InputQuery('Go to Line', 'Line number:', S) then begin
|
|
L := StrToIntDef(S, Low(L));
|
|
if L <> Low(L) then
|
|
FActiveMemo.CaretLine := L - 1;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.StatusBarClick(Sender: TObject);
|
|
begin
|
|
if MemosTabSet.Visible and (FHiddenFiles.Count > 0) then begin
|
|
var Point := SmallPointToPoint(TSmallPoint(GetMessagePos()));
|
|
var X := StatusBar.ScreenToClient(Point).X;
|
|
var W := 0;
|
|
for var I := 0 to StatusBar.Panels.Count-1 do begin
|
|
Inc(W, StatusBar.Panels[I].Width);
|
|
if X < W then begin
|
|
if I = spHiddenFilesCount then
|
|
(MemosTabSet.PopupMenu as TMainFormPopupMenu).Popup(Point.X, Point.Y);
|
|
Break;
|
|
end else if I = spHiddenFilesCount then
|
|
Break;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.StatusBarDrawPanel(StatusBar: TStatusBar;
|
|
Panel: TStatusPanel; const Rect: TRect);
|
|
const
|
|
TP_DROPDOWNBUTTONGLYPH = 7;
|
|
TS_NORMAL = 1;
|
|
begin
|
|
case Panel.Index of
|
|
spHiddenFilesCount:
|
|
if MemosTabSet.Visible and (FHiddenFiles.Count > 0) then begin
|
|
var RText := Rect;
|
|
if FToolbarThemeData <> 0 then begin
|
|
Dec(RText.Right, RText.Bottom - RText.Top);
|
|
var RGlyph := Rect;
|
|
RGlyph.Left := RText.Right; { RGlyph is now a square }
|
|
DrawThemeBackground(FToolbarThemeData, StatusBar.Canvas.Handle, TP_DROPDOWNBUTTONGLYPH, TS_NORMAL, RGlyph, nil);
|
|
end;
|
|
var S := Format('Tabs closed: %d', [FHiddenFiles.Count]);
|
|
StatusBar.Canvas.TextRect(RText, S, [tfCenter]);
|
|
end;
|
|
spCompileIcon:
|
|
if FCompiling then begin
|
|
var BuildImageList := ImagesModule.BuildImageList;
|
|
ImageList_Draw(BuildImageList.Handle, FBuildAnimationFrame, StatusBar.Canvas.Handle,
|
|
Rect.Left + ((Rect.Right - Rect.Left) - BuildImageList.Width) div 2,
|
|
Rect.Top + ((Rect.Bottom - Rect.Top) - BuildImageList.Height) div 2, ILD_NORMAL);
|
|
end;
|
|
spCompileProgress:
|
|
if FCompiling and (FProgressMax > 0) then begin
|
|
var R := Rect;
|
|
InflateRect(R, -2, -2);
|
|
if FProgressThemeData = 0 then begin
|
|
{ Border }
|
|
StatusBar.Canvas.Pen.Color := clBtnShadow;
|
|
StatusBar.Canvas.Brush.Style := bsClear;
|
|
StatusBar.Canvas.Rectangle(R);
|
|
InflateRect(R, -1, -1);
|
|
{ Filled part }
|
|
var SaveRight := R.Right;
|
|
R.Right := R.Left + MulDiv(FProgress, R.Right - R.Left,
|
|
FProgressMax);
|
|
StatusBar.Canvas.Brush.Color := clHighlight;
|
|
StatusBar.Canvas.FillRect(R);
|
|
{ Unfilled part }
|
|
R.Left := R.Right;
|
|
R.Right := SaveRight;
|
|
StatusBar.Canvas.Brush.Color := clBtnFace;
|
|
StatusBar.Canvas.FillRect(R);
|
|
end else begin
|
|
DrawThemeBackground(FProgressThemeData, StatusBar.Canvas.Handle,
|
|
PP_BAR, 0, R, nil);
|
|
{ PP_FILL drawing on Windows 11 (and probably 10) is bugged: when
|
|
the width of the green bar is less than ~25 pixels, the bar is
|
|
drawn over the left border. The same thing happens with
|
|
TProgressBar, so I don't think the API is being used incorrectly.
|
|
Work around the bug by passing a clipping rectangle that excludes
|
|
the left edge when running on Windows 10/11 only. (I don't know if
|
|
earlier versions need it, or if later versions will fix it.) }
|
|
var CR := R;
|
|
if (Win32MajorVersion = 10) and (Win32MinorVersion = 0) then
|
|
Inc(CR.Left); { does this need to be DPI-scaled? }
|
|
R.Right := R.Left + MulDiv(FProgress, R.Right - R.Left,
|
|
FProgressMax);
|
|
DrawThemeBackground(FProgressThemeData, StatusBar.Canvas.Handle,
|
|
PP_FILL, PBFS_NORMAL, R, @CR);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.InvalidateStatusPanel(const Index: Integer);
|
|
var
|
|
R: TRect;
|
|
begin
|
|
{ For some reason, the VCL doesn't offer a method for this... }
|
|
if SendMessage(StatusBar.Handle, SB_GETRECT, Index, LPARAM(@R)) <> 0 then begin
|
|
InflateRect(R, -1, -1);
|
|
InvalidateRect(StatusBar.Handle, @R, True);
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.UpdateCompileStatusPanels(const AProgress,
|
|
AProgressMax: Cardinal; const ASecondsRemaining: Integer;
|
|
const ABytesCompressedPerSecond: Cardinal);
|
|
begin
|
|
var CurTick := GetTickCount;
|
|
var LastTick := FLastAnimationTick;
|
|
FLastAnimationTick := CurTick;
|
|
|
|
{ Icon and text panels - updated every 500ms }
|
|
if CurTick div 500 <> LastTick div 500 then begin
|
|
InvalidateStatusPanel(spCompileIcon);
|
|
FBuildAnimationFrame := (FBuildAnimationFrame + 1) mod 4;
|
|
if ASecondsRemaining >= 0 then
|
|
StatusBar.Panels[spExtraStatus].Text := Format(
|
|
' Estimated time remaining: %.2d%s%.2d%s%.2d Average KB/sec: %.0n',
|
|
[(ASecondsRemaining div 60) div 60, FormatSettings.TimeSeparator,
|
|
(ASecondsRemaining div 60) mod 60, FormatSettings.TimeSeparator,
|
|
ASecondsRemaining mod 60, ABytesCompressedPerSecond / 1024])
|
|
else
|
|
StatusBar.Panels[spExtraStatus].Text := '';
|
|
end;
|
|
|
|
{ Progress panel and taskbar progress bar - updated every 100ms }
|
|
if (CurTick div 100 <> LastTick div 100) and
|
|
((FProgress <> AProgress) or (FProgressMax <> AProgressMax)) then begin
|
|
FProgress := AProgress;
|
|
FProgressMax := AProgressMax;
|
|
InvalidateStatusPanel(spCompileProgress);
|
|
|
|
{ The taskbar progress updates are slow (on Windows 11). Limiting the
|
|
range to 64 instead of 1024 improved compression KB/sec by about 4%
|
|
(9000 to 9400) when the rate limit above is disabled. }
|
|
var NewValue: Cardinal := 1; { must be at least 1 for progress bar to show }
|
|
if AProgressMax > 0 then begin
|
|
{ Not using MulDiv here to avoid rounding up }
|
|
NewValue := (AProgress * 64) div AProgressMax;
|
|
if NewValue = 0 then
|
|
NewValue := 1;
|
|
end;
|
|
{ Don't call the function if the value hasn't changed, just in case there's
|
|
a performance penalty. (There doesn't appear to be on Windows 11.) }
|
|
if FTaskbarProgressValue <> NewValue then begin
|
|
FTaskbarProgressValue := NewValue;
|
|
SetAppTaskbarProgressValue(NewValue, 64);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.WMSettingChange(var Message: TMessage);
|
|
begin
|
|
inherited;
|
|
if (FTheme.Typ <> ttClassic) and IsWindows10 and (Message.LParam <> 0) and (StrIComp(PChar(Message.LParam), 'ImmersiveColorSet') = 0) then begin
|
|
FOptions.ThemeType := GetDefaultThemeType;
|
|
UpdateTheme;
|
|
end;
|
|
for var Memo in FMemos do
|
|
Memo.SettingChange(Message);
|
|
end;
|
|
|
|
procedure TMainForm.WMThemeChanged(var Message: TMessage);
|
|
begin
|
|
{ Don't Run to Cursor into this function, it will interrupt up the theme change }
|
|
UpdateThemeData(True);
|
|
inherited;
|
|
end;
|
|
|
|
procedure TMainForm.WMUAHDrawMenu(var Message: TMessage);
|
|
begin
|
|
if FTheme.Dark then begin
|
|
var MenuBarInfo: TMenuBarInfo;
|
|
MenuBarInfo.cbSize := SizeOf(MenuBarInfo);
|
|
GetMenuBarInfo(Handle, Integer(OBJID_MENU), 0, MenuBarInfo);
|
|
|
|
var WindowRect: TRect;
|
|
GetWindowRect(Handle, WindowRect);
|
|
|
|
var Rect := MenuBarInfo.rcBar;
|
|
OffsetRect(Rect, -WindowRect.Left, -WindowRect.Top);
|
|
|
|
var UAHMenu := PUAHMenu(Message.lParam);
|
|
FillRect(UAHMenu.hdc, Rect, FMenuDarkBackgroundBrush.Handle);
|
|
end else
|
|
inherited;
|
|
end;
|
|
|
|
procedure TMainForm.WMUAHDrawMenuItem(var Message: TMessage);
|
|
const
|
|
ODS_NOACCEL = $100;
|
|
DTT_TEXTCOLOR = 1;
|
|
MENU_BARITEM = 8;
|
|
MBI_NORMAL = 1;
|
|
var
|
|
Buffer: array of Char;
|
|
begin
|
|
if FTheme.Dark then begin
|
|
var UAHDrawMenuItem := PUAHDrawMenuItem(Message.lParam);
|
|
|
|
var MenuItemInfo: TMenuItemInfo;
|
|
MenuItemInfo.cbSize := SizeOf(MenuItemInfo);
|
|
MenuItemInfo.fMask := MIIM_STRING;
|
|
MenuItemInfo.dwTypeData := nil;
|
|
GetMenuItemInfo(UAHDrawMenuItem.um.hmenu, UAHDrawMenuItem.umi.iPosition, True, MenuItemInfo);
|
|
Inc(MenuItemInfo.cch);
|
|
SetLength(Buffer, MenuItemInfo.cch);
|
|
MenuItemInfo.dwTypeData := @Buffer[0];
|
|
GetMenuItemInfo(UAHDrawMenuItem.um.hmenu, UAHDrawMenuItem.umi.iPosition, True, MenuItemInfo);
|
|
|
|
var dwFlags: DWORD := DT_CENTER or DT_SINGLELINE or DT_VCENTER;
|
|
if (UAHDrawMenuItem.dis.itemState and ODS_NOACCEL) <> 0 then
|
|
dwFlags := dwFlags or DT_HIDEPREFIX;
|
|
|
|
var Inactive := (UAHDrawMenuItem.dis.itemState and ODS_INACTIVE) <> 0;
|
|
|
|
var TextColor: TThemeColor;
|
|
if Inactive then
|
|
TextColor := tcMarginFore
|
|
else
|
|
TextColor := tcFore;
|
|
|
|
var opts: TDTTOpts;
|
|
opts.dwSize := SizeOf(opts);
|
|
opts.dwFlags := DTT_TEXTCOLOR;
|
|
opts.crText := FTheme.Colors[TextColor];
|
|
|
|
var Brush: HBrush;
|
|
{ ODS_HOTLIGHT can be set when the menu is inactive so we check Inactive as well. }
|
|
if not Inactive and ((UAHDrawMenuItem.dis.itemState and (ODS_HOTLIGHT or ODS_SELECTED)) <> 0) then
|
|
Brush := FMenuDarkHotOrSelectedBrush.Handle
|
|
else
|
|
Brush := FMenuDarkBackgroundBrush.Handle;
|
|
|
|
FillRect(UAHDrawMenuItem.um.hdc, UAHDrawMenuItem.dis.rcItem, Brush);
|
|
DrawThemeTextEx(FMenuThemeData, UAHDrawMenuItem.um.hdc, MENU_BARITEM, MBI_NORMAL, MenuItemInfo.dwTypeData, MenuItemInfo.cch, dwFlags, @UAHDrawMenuItem.dis.rcItem, opts);
|
|
end else
|
|
inherited;
|
|
end;
|
|
|
|
{ Should be removed if the main menu ever gets removed }
|
|
procedure TMainForm.UAHDrawMenuBottomLine;
|
|
begin
|
|
if FTheme.Dark then begin
|
|
var ClientRect: TRect;
|
|
Windows.GetClientRect(Handle, ClientRect);
|
|
MapWindowPoints(Handle, 0, ClientRect, 2);
|
|
|
|
var WindowRect: TRect;
|
|
GetWindowRect(Handle, WindowRect);
|
|
|
|
var Rect := ClientRect;
|
|
OffsetRect(Rect, -WindowRect.Left, -WindowRect.Top);
|
|
|
|
Rect.Bottom := Rect.Top;
|
|
Dec(Rect.Top);
|
|
|
|
var DC := GetWindowDC(Handle);
|
|
FillRect(DC, Rect, FMenuDarkBackgroundBrush.Handle);
|
|
ReleaseDC(Handle, DC);
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.WMNCActivate(var Message: TMessage);
|
|
begin
|
|
inherited;
|
|
UAHDrawMenuBottomLine;
|
|
end;
|
|
|
|
procedure TMainForm.WMNCPaint(var Message: TMessage);
|
|
begin
|
|
inherited;
|
|
UAHDrawMenuBottomLine;
|
|
end;
|
|
|
|
procedure TMainForm.RTargetClick(Sender: TObject);
|
|
var
|
|
NewTarget: TDebugTarget;
|
|
begin
|
|
if (Sender = RTargetSetup) or (Sender = TargetSetupButton) then
|
|
NewTarget := dtSetup
|
|
else
|
|
NewTarget := dtUninstall;
|
|
if (FDebugTarget <> NewTarget) and (not FDebugging or AskToDetachDebugger) then
|
|
FDebugTarget := NewTarget;
|
|
|
|
{ Update always even if the user decided not to switch so the states are restored }
|
|
UpdateTargetMenu;
|
|
end;
|
|
|
|
procedure TMainForm.AppOnActivate(Sender: TObject);
|
|
const
|
|
ReloadMessages: array[Boolean] of String = (
|
|
'The %s file has been modified outside of the source editor.' + SNewLine2 +
|
|
'Do you want to reload the file?',
|
|
'The %s file has been modified outside of the source editor. Changes have ' +
|
|
'also been made in the source editor.' + SNewLine2 + 'Do you want to ' +
|
|
'reload the file and lose the changes made in the source editor?');
|
|
var
|
|
Memo: TIDEScintFileEdit;
|
|
NewTime: TFileTime;
|
|
Changed: Boolean;
|
|
begin
|
|
for Memo in FFileMemos do begin
|
|
if (Memo.Filename = '') or not Memo.Used then
|
|
Continue;
|
|
|
|
{ See if the file has been modified outside the editor }
|
|
Changed := False;
|
|
if GetLastWriteTimeOfFile(Memo.Filename, @NewTime) then begin
|
|
if CompareFileTime(Memo.FileLastWriteTime, NewTime) <> 0 then begin
|
|
Memo.FileLastWriteTime := NewTime;
|
|
Changed := True;
|
|
end;
|
|
end;
|
|
|
|
{ If it has been, offer to reload it }
|
|
if Changed then begin
|
|
if IsWindowEnabled(Handle) then begin
|
|
if MsgBox(Format(ReloadMessages[Memo.Modified], [Memo.Filename]),
|
|
SCompilerFormCaption, mbConfirmation, MB_YESNO) = IDYES then
|
|
if ConfirmCloseFile(False) then begin
|
|
OpenFile(Memo, Memo.Filename, False);
|
|
if Memo = FMainMemo then
|
|
Break; { Reloading the main script will also reload all include files }
|
|
end;
|
|
end
|
|
else begin
|
|
{ When a modal dialog is up, don't offer to reload the file. Probably
|
|
not a good idea since the dialog might be manipulating the file. }
|
|
MsgBox('The ' + Memo.Filename + ' file has been modified outside ' +
|
|
'of the source editor. You might want to reload it.',
|
|
SCompilerFormCaption, mbInformation, MB_OK);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.CompilerOutputListDrawItem(Control: TWinControl;
|
|
Index: Integer; Rect: TRect; State: TOwnerDrawState);
|
|
const
|
|
ThemeColors: array [TStatusMessageKind] of TThemeColor = (tcGreen, tcFore, tcOrange, tcRed);
|
|
var
|
|
Canvas: TCanvas;
|
|
S: String;
|
|
StatusMessageKind: TStatusMessageKind;
|
|
begin
|
|
Canvas := CompilerOutputList.Canvas;
|
|
S := CompilerOutputList.Items[Index];
|
|
|
|
Canvas.FillRect(Rect);
|
|
Inc(Rect.Left, 2);
|
|
if FOptions.ColorizeCompilerOutput and not (odSelected in State) then begin
|
|
StatusMessageKind := TStatusMessageKind(CompilerOutputList.Items.Objects[Index]);
|
|
Canvas.Font.Color := FTheme.Colors[ThemeColors[StatusMessageKind]];
|
|
end;
|
|
Canvas.TextOut(Rect.Left, Rect.Top, S);
|
|
end;
|
|
|
|
procedure TMainForm.DebugOutputListDrawItem(Control: TWinControl;
|
|
Index: Integer; Rect: TRect; State: TOwnerDrawState);
|
|
var
|
|
Canvas: TCanvas;
|
|
S: String;
|
|
begin
|
|
Canvas := DebugOutputList.Canvas;
|
|
S := DebugOutputList.Items[Index];
|
|
|
|
Canvas.FillRect(Rect);
|
|
Inc(Rect.Left, 2);
|
|
if (S <> '') and (S[1] = #9) then
|
|
Canvas.TextOut(Rect.Left + FDebugLogListTimestampsWidth, Rect.Top, Copy(S, 2, Maxint))
|
|
else begin
|
|
if (Length(S) > 20) and (S[18] = '-') and (S[19] = '-') and (S[20] = ' ') then begin
|
|
{ Draw lines that begin with '-- ' (like '-- File entry --') in bold }
|
|
Canvas.TextOut(Rect.Left, Rect.Top, Copy(S, 1, 17));
|
|
Canvas.Font.Style := [fsBold];
|
|
Canvas.TextOut(Rect.Left + FDebugLogListTimestampsWidth, Rect.Top, Copy(S, 18, Maxint));
|
|
end else
|
|
Canvas.TextOut(Rect.Left, Rect.Top, S);
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.DebugCallStackListDrawItem(Control: TWinControl; Index: Integer; Rect: TRect;
|
|
State: TOwnerDrawState);
|
|
var
|
|
Canvas: TCanvas;
|
|
S: String;
|
|
begin
|
|
Canvas := DebugCallStackList.Canvas;
|
|
S := DebugCallStackList.Items[Index];
|
|
|
|
Canvas.FillRect(Rect);
|
|
Inc(Rect.Left, 2);
|
|
Canvas.TextOut(Rect.Left, Rect.Top, S);
|
|
end;
|
|
|
|
procedure TMainForm.FindResultsListDblClick(Sender: TObject);
|
|
var
|
|
FindResult: TFindResult;
|
|
Memo: TIDEScintFileEdit;
|
|
I: Integer;
|
|
begin
|
|
I := FindResultsList.ItemIndex;
|
|
if I <> -1 then begin
|
|
FindResult := FindResultsList.Items.Objects[I] as TFindResult;
|
|
if FindResult <> nil then begin
|
|
for Memo in FFileMemos do begin
|
|
if Memo.Used and (PathCompare(Memo.Filename, FindResult.Filename) = 0) then begin
|
|
MoveCaretAndActivateMemo(Memo, FindResult.Line, True);
|
|
Memo.SelectAndEnsureVisible(FindResult.Range);
|
|
ActiveControl := Memo;
|
|
Exit;
|
|
end;
|
|
end;
|
|
MsgBox('File not opened.', SCompilerFormCaption, mbError, MB_OK);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.FindResultsListDrawItem(Control: TWinControl; Index: Integer; Rect: TRect;
|
|
State: TOwnerDrawState);
|
|
var
|
|
Canvas: TCanvas;
|
|
S, S2: String;
|
|
FindResult: TFindResult;
|
|
StartI, EndI: Integer;
|
|
SaveColor: TColor;
|
|
begin
|
|
Canvas := FindResultsList.Canvas;
|
|
S := FindResultsList.Items[Index];
|
|
FindResult := FindResultsList.Items.Objects[Index] as TFindResult;
|
|
|
|
Canvas.FillRect(Rect);
|
|
Inc(Rect.Left, 2);
|
|
if FindResult = nil then begin
|
|
Canvas.Font.Style := [fsBold];
|
|
Canvas.TextOut(Rect.Left, Rect.Top, S);
|
|
end else if not (odSelected in State) then begin
|
|
StartI := FindResult.Range.StartPos - FindResult.LineStartPos + 1 + FindResult.PrefixStringLength;
|
|
EndI := FindResult.Range.EndPos - FindResult.LineStartPos + 1 + FindResult.PrefixStringLength;
|
|
if StartI > 1 then begin
|
|
Canvas.TextOut(Rect.Left, Rect.Top, Copy(S, 1, StartI-1));
|
|
Rect.Left := Canvas.PenPos.X;
|
|
end;
|
|
SaveColor := Canvas.Brush.Color;
|
|
if FTheme.Dark then
|
|
Canvas.Brush.Color := FTheme.Colors[tcRed]
|
|
else
|
|
Canvas.Brush.Color := FTheme.Colors[tcSelBack];
|
|
S2 := Copy(S, StartI, EndI-StartI);
|
|
Rect.Right := Rect.Left + Canvas.TextWidth(S2);
|
|
Canvas.TextRect(Rect, Rect.Left, Rect.Top, S2); { TextRect instead of TextOut to avoid a margin around the text }
|
|
if EndI <= Length(S) then begin
|
|
Canvas.Brush.Color := SaveColor;
|
|
S2 := Copy(S, EndI, MaxInt);
|
|
Rect.Left := Rect.Right;
|
|
Rect.Right := Rect.Left + Canvas.TextWidth(S2);
|
|
Canvas.TextRect(Rect, Rect.Left, Rect.Top, S2);
|
|
end;
|
|
end else
|
|
Canvas.TextOut(Rect.Left, Rect.Top, S)
|
|
end;
|
|
|
|
procedure TMainForm.OutputTabSetClick(Sender: TObject);
|
|
begin
|
|
case OutputTabSet.TabIndex of
|
|
tiCompilerOutput:
|
|
begin
|
|
CompilerOutputList.BringToFront;
|
|
CompilerOutputList.Visible := True;
|
|
DebugOutputList.Visible := False;
|
|
DebugCallStackList.Visible := False;
|
|
FindResultsList.Visible := False;
|
|
end;
|
|
tiDebugOutput:
|
|
begin
|
|
DebugOutputList.BringToFront;
|
|
DebugOutputList.Visible := True;
|
|
CompilerOutputList.Visible := False;
|
|
DebugCallStackList.Visible := False;
|
|
FindResultsList.Visible := False;
|
|
end;
|
|
tiDebugCallStack:
|
|
begin
|
|
DebugCallStackList.BringToFront;
|
|
DebugCallStackList.Visible := True;
|
|
CompilerOutputList.Visible := False;
|
|
DebugOutputList.Visible := False;
|
|
FindResultsList.Visible := False;
|
|
end;
|
|
tiFindResults:
|
|
begin
|
|
FindResultsList.BringToFront;
|
|
FindResultsList.Visible := True;
|
|
CompilerOutputList.Visible := False;
|
|
DebugOutputList.Visible := False;
|
|
DebugCallStackList.Visible := False;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.ToggleBreakPoint(Line: Integer);
|
|
var
|
|
Memo: TIDEScintFileEdit;
|
|
I: Integer;
|
|
begin
|
|
Memo := FActiveMemo as TIDEScintFileEdit;
|
|
I := Memo.BreakPoints.IndexOf(Line);
|
|
if I = -1 then
|
|
Memo.BreakPoints.Add(Line)
|
|
else
|
|
Memo.BreakPoints.Delete(I);
|
|
UpdateLineMarkers(Memo, Line);
|
|
BuildAndSaveBreakPointLines(Memo);
|
|
end;
|
|
|
|
procedure TMainForm.MemoMarginClick(Sender: TObject; MarginNumber: Integer;
|
|
Line: Integer);
|
|
begin
|
|
if (MarginNumber = 1) and RToggleBreakPoint.Enabled then
|
|
ToggleBreakPoint(Line);
|
|
end;
|
|
|
|
procedure TMainForm.MemoMarginRightClick(Sender: TObject; MarginNumber: Integer;
|
|
Line: Integer);
|
|
begin
|
|
if MarginNumber = 1 then begin
|
|
var Point := SmallPointToPoint(TSmallPoint(GetMessagePos()));
|
|
var PopupMenu := TMainFormPopupMenu.Create(Self, BreakPointsPopupMenu);
|
|
try
|
|
PopupMenu.Popup(Point.X, Point.Y);
|
|
finally
|
|
PopupMenu.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.RToggleBreakPointClick(Sender: TObject);
|
|
begin
|
|
ToggleBreakPoint(FActiveMemo.CaretLine);
|
|
end;
|
|
|
|
procedure TMainForm.RDeleteBreakPointsClick(Sender: TObject);
|
|
begin
|
|
{ Also see AnyMemoHasBreakPoint }
|
|
for var Memo in FFileMemos do begin
|
|
if Memo.Used and (Memo.BreakPoints.Count > 0) then begin
|
|
for var I := Memo.BreakPoints.Count-1 downto 0 do begin
|
|
var Line := Memo.BreakPoints[I];
|
|
Memo.BreakPoints.Delete(I);
|
|
UpdateLineMarkers(Memo, Line);
|
|
end;
|
|
BuildAndSaveBreakPointLines(Memo);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.UpdateFindResult(const FindResult: TFindResult; const ItemIndex: Integer;
|
|
const NewLine, NewLineStartPos: Integer);
|
|
begin
|
|
{ Also see FindInFilesDialogFind }
|
|
const OldPrefix = Format(' Line %d: ', [FindResult.Line+1]);
|
|
FindResult.Line := NewLine;
|
|
const NewPrefix = Format(' Line %d: ', [FindResult.Line+1]);
|
|
FindResultsList.Items[ItemIndex] := NewPrefix + Copy(FindResultsList.Items[ItemIndex], Length(OldPrefix)+1, MaxInt);
|
|
FindResult.PrefixStringLength := Length(NewPrefix);
|
|
const PosChange = NewLineStartPos - FindResult.LineStartPos;
|
|
FindResult.LineStartPos := NewLineStartPos;
|
|
FindResult.Range.StartPos := FindResult.Range.StartPos + PosChange;
|
|
FindResult.Range.EndPos := FindResult.Range.EndPos + PosChange;
|
|
end;
|
|
|
|
procedure TMainForm.MemoLinesInserted(Memo: TIDEScintFileEdit; FirstLine, Count: integer);
|
|
begin
|
|
for var I := 0 to FDebugEntriesCount-1 do
|
|
if (FDebugEntries[I].FileIndex = Memo.CompilerFileIndex) and
|
|
(FDebugEntries[I].LineNumber >= FirstLine) then
|
|
Inc(FDebugEntries[I].LineNumber, Count);
|
|
|
|
for var I := FindResultsList.Items.Count-1 downto 0 do begin
|
|
const FindResult = FindResultsList.Items.Objects[I] as TFindResult;
|
|
if FindResult <> nil then begin
|
|
if (PathCompare(FindResult.Filename, Memo.Filename) = 0) and
|
|
(FindResult.Line >= FirstLine) then begin
|
|
const NewLine = FindResult.Line + Count;
|
|
UpdateFindResult(FindResult, I, NewLine, Memo.GetPositionFromLine(NewLine));
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
if Assigned(Memo.LineState) and (FirstLine < Memo.LineStateCount) then begin
|
|
{ Grow FStateLine if necessary }
|
|
var GrowAmount := (Memo.LineStateCount + Count) - Memo.LineStateCapacity;
|
|
if GrowAmount > 0 then begin
|
|
if GrowAmount < LineStateGrowAmount then
|
|
GrowAmount := LineStateGrowAmount;
|
|
ReallocMem(Memo.LineState, SizeOf(TLineState) * (Memo.LineStateCapacity + GrowAmount));
|
|
Inc(Memo.LineStateCapacity, GrowAmount);
|
|
end;
|
|
{ Shift existing line states and clear the new ones }
|
|
for var I := Memo.LineStateCount-1 downto FirstLine do
|
|
Memo.LineState[I + Count] := Memo.LineState[I];
|
|
for var I := FirstLine to FirstLine + Count - 1 do
|
|
Memo.LineState[I] := lnUnknown;
|
|
Inc(Memo.LineStateCount, Count);
|
|
end;
|
|
|
|
if Memo.StepLine >= FirstLine then
|
|
Inc(Memo.StepLine, Count);
|
|
if Memo.ErrorLine >= FirstLine then
|
|
Inc(Memo.ErrorLine, Count);
|
|
|
|
var BreakPointsChanged := False;
|
|
for var I := 0 to Memo.BreakPoints.Count-1 do begin
|
|
const Line = Memo.BreakPoints[I];
|
|
if Line >= FirstLine then begin
|
|
Memo.BreakPoints[I] := Line + Count;
|
|
BreakPointsChanged := True;
|
|
end;
|
|
end;
|
|
if BreakPointsChanged then
|
|
BuildAndSaveBreakPointLines(Memo);
|
|
|
|
FNavStacks.LinesInserted(Memo, FirstLine, Count);
|
|
end;
|
|
|
|
procedure TMainForm.MemoLinesDeleted(Memo: TIDEScintFileEdit; FirstLine, Count,
|
|
FirstAffectedLine: Integer);
|
|
begin
|
|
for var I := 0 to FDebugEntriesCount-1 do begin
|
|
const DebugEntry: PDebugEntry = @FDebugEntries[I];
|
|
if (DebugEntry.FileIndex = Memo.CompilerFileIndex) and
|
|
(DebugEntry.LineNumber >= FirstLine) then begin
|
|
if DebugEntry.LineNumber < FirstLine + Count then
|
|
DebugEntry.LineNumber := -1
|
|
else
|
|
Dec(DebugEntry.LineNumber, Count);
|
|
end;
|
|
end;
|
|
|
|
for var I := FindResultsList.Items.Count-1 downto 0 do begin
|
|
const FindResult = FindResultsList.Items.Objects[I] as TFindResult;
|
|
if FindResult <> nil then begin
|
|
if (PathCompare(FindResult.Filename, Memo.Filename) = 0) and
|
|
(FindResult.Line >= FirstLine) then begin
|
|
if FindResult.Line < FirstLine + Count then
|
|
FindResultsList.Items.Delete(I)
|
|
else begin
|
|
const NewLine = FindResult.Line - Count;
|
|
UpdateFindResult(FindResult, I, NewLine, Memo.GetPositionFromLine(NewLine));
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
if Assigned(Memo.LineState) then begin
|
|
{ Shift existing line states }
|
|
if FirstLine < Memo.LineStateCount - Count then begin
|
|
for var I := FirstLine to Memo.LineStateCount - Count - 1 do
|
|
Memo.LineState[I] := Memo.LineState[I + Count];
|
|
Dec(Memo.LineStateCount, Count);
|
|
end
|
|
else begin
|
|
{ There's nothing to shift because the last line(s) were deleted, or
|
|
line(s) past FLineStateCount }
|
|
if Memo.LineStateCount > FirstLine then
|
|
Memo.LineStateCount := FirstLine;
|
|
end;
|
|
end;
|
|
|
|
if Memo.StepLine >= FirstLine then begin
|
|
if Memo.StepLine < FirstLine + Count then
|
|
Memo.StepLine := -1
|
|
else
|
|
Dec(Memo.StepLine, Count);
|
|
end;
|
|
if Memo.ErrorLine >= FirstLine then begin
|
|
if Memo.ErrorLine < FirstLine + Count then
|
|
Memo.ErrorLine := -1
|
|
else
|
|
Dec(Memo.ErrorLine, Count);
|
|
end;
|
|
|
|
var BreakPointsChanged := False;
|
|
for var I := Memo.BreakPoints.Count-1 downto 0 do begin
|
|
const Line = Memo.BreakPoints[I];
|
|
if Line >= FirstLine then begin
|
|
if Line < FirstLine + Count then begin
|
|
Memo.BreakPoints.Delete(I);
|
|
BreakPointsChanged := True;
|
|
end else begin
|
|
Memo.BreakPoints[I] := Line - Count;
|
|
BreakPointsChanged := True;
|
|
end;
|
|
end;
|
|
end;
|
|
if BreakPointsChanged then
|
|
BuildAndSaveBreakPointLines(Memo);
|
|
|
|
if FNavStacks.LinesDeleted(Memo, FirstLine, Count) then
|
|
UpdateNavButtons;
|
|
{ We do NOT update FCurrentNavItem here so it might point to a line that's
|
|
deleted until next UpdateCaretPosPanelAndBackStack by UpdateMemoUI }
|
|
|
|
{ When lines are deleted, Scintilla insists on moving all of the deleted
|
|
lines' markers to the line on which the deletion started
|
|
(FirstAffectedLine). This is bad for us as e.g. it can result in the line
|
|
having two conflicting markers (or two of the same marker). There's no
|
|
way to stop it from doing that, or to easily tell which markers came from
|
|
which lines, so we simply delete and re-create all markers on the line. }
|
|
UpdateLineMarkers(Memo, FirstAffectedLine);
|
|
end;
|
|
|
|
procedure TMainForm.UpdateLineMarkers(const AMemo: TIDEScintFileEdit; const Line: Integer);
|
|
var
|
|
NewMarker: Integer;
|
|
begin
|
|
if Line >= AMemo.Lines.Count then
|
|
Exit;
|
|
|
|
var StepLine := AMemo.StepLine = Line;
|
|
|
|
NewMarker := -1;
|
|
if AMemo.BreakPoints.IndexOf(Line) <> -1 then begin
|
|
if AMemo.LineState = nil then
|
|
NewMarker := mmiBreakpoint
|
|
else if (Line < AMemo.LineStateCount) and (AMemo.LineState[Line] <> lnUnknown) then
|
|
NewMarker := IfThen(StepLine, mmiBreakpointStep, mmiBreakpointGood)
|
|
else
|
|
NewMarker := mmiBreakpointBad;
|
|
end else if StepLine then
|
|
NewMarker := mmiStep
|
|
else begin
|
|
if Line < AMemo.LineStateCount then begin
|
|
case AMemo.LineState[Line] of
|
|
lnHasEntry: NewMarker := mmiHasEntry;
|
|
lnEntryProcessed: NewMarker := mmiEntryProcessed;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ Delete all markers on the line. To flush out any possible duplicates,
|
|
even the markers we'll be adding next are deleted. }
|
|
if AMemo.GetMarkers(Line) <> [] then
|
|
AMemo.DeleteAllMarkersOnLine(Line);
|
|
|
|
if NewMarker <> -1 then
|
|
AMemo.AddMarker(Line, NewMarker);
|
|
|
|
if StepLine then
|
|
AMemo.AddMarker(Line, mlmStep)
|
|
else if AMemo.ErrorLine = Line then
|
|
AMemo.AddMarker(Line, mlmError)
|
|
else if NewMarker = mmiBreakpointBad then
|
|
AMemo.AddMarker(Line, mlmBreakpointBad);
|
|
end;
|
|
|
|
procedure TMainForm.UpdateLinkLabelLinkClick(Sender: TObject;
|
|
const Link: string; LinkType: TSysLinkType);
|
|
begin
|
|
var Handled := True;
|
|
if (LinkType = sltID) and (Link = 'hwhatsnew') then
|
|
HWhatsNew.Click
|
|
else if (LinkType = sltID) and (Link = 'toptions-vscode') then begin
|
|
TOptionsForm.DropDownMemoKeyMappingComboBoxOnNextShow := True;
|
|
TOptions.Click
|
|
end else
|
|
Handled := False;
|
|
if Handled then
|
|
UpdatePanelClosePaintBoxClick(Sender);
|
|
end;
|
|
|
|
procedure TMainForm.UpdatePanelClosePaintBoxClick(Sender: TObject);
|
|
begin
|
|
var MessageToHideIndex := UpdateLinkLabel.Tag;
|
|
var Ini := TConfigIniFile.Create;
|
|
try
|
|
Ini.WriteInteger('UpdatePanel', FUpdatePanelMessages[MessageToHideIndex].ConfigIdent, FUpdatePanelMessages[MessageToHideIndex].ConfigValue);
|
|
finally
|
|
Ini.Free;
|
|
end;
|
|
FUpdatePanelMessages.Delete(MessageToHideIndex);
|
|
UpdateUpdatePanel;
|
|
end;
|
|
|
|
procedure TMainForm.UpdatePanelDonateImageClick(Sender: TObject);
|
|
begin
|
|
HDonate.Click;
|
|
end;
|
|
|
|
procedure TMainForm.UpdatePanelClosePaintBoxPaint(Sender: TObject);
|
|
const
|
|
MENU_SYSTEMCLOSE = 17;
|
|
MSYSC_NORMAL = 1;
|
|
begin
|
|
var Canvas := UpdatePanelClosePaintBox.Canvas;
|
|
var R := TRect.Create(0, 0, UpdatePanelClosePaintBox.Width, UpdatePanelClosePaintBox.Height);
|
|
if FMenuThemeData <> 0 then begin
|
|
var Offset := MulDiv(1, CurrentPPI, 96);
|
|
Inc(R.Left, Offset);
|
|
DrawThemeBackground(FMenuThemeData, Canvas.Handle, MENU_SYSTEMCLOSE, MSYSC_NORMAL, R, nil);
|
|
end else begin
|
|
InflateRect(R, -MulDiv(6, CurrentPPI, 96), -MulDiv(6, CurrentPPI, 96));
|
|
Canvas.Pen.Color := Canvas.Font.Color;
|
|
Canvas.MoveTo(R.Left, R.Top);
|
|
Canvas.LineTo(R.Right, R.Bottom);
|
|
Canvas.MoveTo(R.Left, R.Bottom-1);
|
|
Canvas.LineTo(R.Right, R.Top-1);
|
|
end;
|
|
end;
|
|
|
|
procedure TMainForm.UpdateAllMemoLineMarkers(const AMemo: TIDEScintFileEdit);
|
|
begin
|
|
for var Line := 0 to AMemo.Lines.Count-1 do
|
|
UpdateLineMarkers(AMemo, Line);
|
|
end;
|
|
|
|
procedure TMainForm.UpdateAllMemosLineMarkers;
|
|
begin
|
|
for var Memo in FFileMemos do
|
|
if Memo.Used then
|
|
UpdateAllMemoLineMarkers(Memo);
|
|
end;
|
|
|
|
procedure TMainForm.UpdateBevel1Visibility;
|
|
begin
|
|
{ Bevel1 is the line between the toolbar and memos when there's nothing in
|
|
between and the color of the toolbar and memo margins is the same }
|
|
Bevel1.Visible := (ToolBarPanel.Color = FTheme.Colors[tcMarginBack]) and
|
|
not UpdatePanel.Visible and not MemosTabSet.Visible;
|
|
end;
|
|
|
|
function TMainForm.ToCurrentPPI(const XY: Integer): Integer;
|
|
begin
|
|
Result := MulDiv(XY, CurrentPPI, 96);
|
|
end;
|
|
|
|
function TMainForm.FromCurrentPPI(const XY: Integer): Integer;
|
|
begin
|
|
Result := MulDiv(XY, 96, CurrentPPI);
|
|
end;
|
|
|
|
initialization
|
|
Application.OnGetActiveFormHandle := TMainForm.AppOnGetActiveFormHandle;
|
|
InitThemeLibrary;
|
|
InitHtmlHelpLibrary;
|
|
{ For ClearType support, try to make the default font Microsoft Sans Serif }
|
|
if DefFontData.Name = 'MS Sans Serif' then
|
|
DefFontData.Name := AnsiString(GetPreferredUIFont);
|
|
CoInitialize(nil);
|
|
finalization
|
|
CoUninitialize();
|
|
end.
|