Преглед изворни кода

Merge branch 'regexfindreplace'

Martijn Laan пре 1 година
родитељ
комит
7fce75a7c5

+ 120 - 59
Components/ScintEdit.pas

@@ -42,7 +42,7 @@ type
   TScintEditUpdate = (suContent, suSelection, suVScroll, suHScroll);
   TScintEditUpdates = set of TScintEditUpdate;
   TScintEditUpdateUIEvent = procedure(Sender: TObject; Updated: TScintEditUpdates) of object;
-  TScintFindOption = (sfoMatchCase, sfoWholeWord);
+  TScintFindOption = (sfoMatchCase, sfoWholeWord, sfoRegEx);
   TScintFindOptions = set of TScintFindOption;
   TScintFoldFlag = (sffLineBeforeExpanded, sffLineBeforeContracted,
     sffLineAfterExpanded, sffLineAfterContracted, sffLevelNumbers, sffLineState);
@@ -50,6 +50,7 @@ type
   TScintIndentationGuides = (sigNone, sigReal, sigLookForward, sigLookBoth);
   TScintKeyCode = type Word;
   TScintKeyDefinition = type Cardinal;
+  TScintReplaceMode = (srmNormal, srmMinimal, srmRegEx);
   TScintStyleByteIndicatorNumber = 0..1; { Could be increased to 0..StyleNumberUnusedBits-1 }
   TScintStyleByteIndicatorNumbers = set of TScintStyleByteIndicatorNumber;
   TScintIndicatorNumber = INDICATOR_CONTAINER..INDICATOR_MAX;
@@ -158,8 +159,8 @@ type
     function GetRawSelText: TScintRawString;
     function GetRawText: TScintRawString;
     function GetReadOnly: Boolean;
-    class function GetSearchFlags(const Options: TScintFindOptions): Integer; overload;
-    class function GetSearchFlags(const MatchCase: Boolean): Integer; overload;
+    class function GetReplaceTargetMessage(const ReplaceMode: TScintReplaceMode): Cardinal;
+    class function GetSearchFlags(const Options: TScintFindOptions): Integer;
     function GetSelection: TScintRange;
     function GetSelectionAnchorPosition(Selection: Integer): Integer;
     function GetSelectionAnchorVirtualSpace(Selection: Integer): Integer;
@@ -244,9 +245,10 @@ type
     procedure AssignCmdKey(const KeyCode: TScintKeyCode; const Shift: TShiftState;
       const Command: TScintCommand); overload;
     procedure BeginUndoAction;
-    function Call(Msg: Cardinal; WParam: Longint; LParam: Longint): Longint;
-    function CallStr(Msg: Cardinal; WParam: Longint;
-      const LParamStr: TScintRawString): Longint;
+    function Call(Msg: Cardinal; WParam: Longint; LParam: Longint): Longint; overload;
+    function Call(Msg: Cardinal; WParam: Longint; LParam: Longint; out WarnStatus: Integer): Longint; overload;
+    function Call(Msg: Cardinal; WParam: Longint; const LParamStr: TScintRawString): Longint; overload;
+    function Call(Msg: Cardinal; WParam: Longint; const LParamStr: TScintRawString; out WarnStatus: Integer): Longint; overload;
     procedure CancelAutoComplete;
     procedure CancelAutoCompleteAndCallTip;
     procedure CancelCallTip;
@@ -313,16 +315,23 @@ type
     function IsPositionInViewVertically(const Pos: Integer): Boolean;
     class function KeyCodeAndShiftToKeyDefinition(const KeyCode: TScintKeyCode;
       Shift: TShiftState): TScintKeyDefinition;
-    function MainSelTextEquals(const S: String; const MatchCase: Boolean): Boolean;
+    function MainSelTextEquals(const S: String;
+      const Options: TScintFindOptions): Boolean;
     class function KeyToKeyCode(const Key: AnsiChar): TScintKeyCode;
     procedure PasteFromClipboard;
-    function RawMainSelTextEquals(const S: TScintRawString; const MatchCase: Boolean): Boolean;
+    function RawMainSelTextEquals(const S: TScintRawString;
+      const Options: TScintFindOptions): Boolean;
     class function RawStringIsBlank(const S: TScintRawString): Boolean;
     procedure Redo;
     procedure RemoveAdditionalSelections;
+    function ReplaceMainSelText(const S: String;
+      const ReplaceMode: TScintReplaceMode = srmNormal): TScintRange;
+    function ReplaceRawMainSelText(const S: TScintRawString;
+      const ReplaceMode: TScintReplaceMode = srmNormal): TScintRange;
     function ReplaceRawTextRange(const StartPos, EndPos: Integer;
-      const S: TScintRawString): TScintRange;
-    function ReplaceTextRange(const StartPos, EndPos: Integer; const S: String): TScintRange;
+      const S: TScintRawString; const ReplaceMode: TScintReplaceMode = srmNormal): TScintRange;
+    function ReplaceTextRange(const StartPos, EndPos: Integer; const S: String;
+      const ReplaceMode: TScintReplaceMode = srmNormal): TScintRange;
     procedure RestyleLine(const Line: Integer);
     procedure ScrollCaretIntoView;
     procedure SelectAll;
@@ -353,6 +362,8 @@ type
     procedure ShowCallTip(const Pos: Integer; const Definition: AnsiString);
     procedure StyleNeeded(const EndPos: Integer);
     procedure SysColorChange(const Message: TMessage);
+    function TestRegularExpression(const S: String): Boolean;
+    function TestRawRegularExpression(const S: TScintRawString): Boolean;
     procedure Undo;
     procedure UpdateStyleAttributes;
     function WordAtCursor: String;
@@ -637,6 +648,13 @@ begin
 end;
 
 function TScintEdit.Call(Msg: Cardinal; WParam: Longint; LParam: Longint): Longint;
+begin
+  var Dummy: Integer;
+  Result := Call(Msg, WParam, LParam, Dummy);
+end;
+
+function TScintEdit.Call(Msg: Cardinal; WParam: Longint; LParam: Longint;
+  out WarnStatus: Integer): Longint;
 begin
   HandleNeeded;
   if FDirectPtr = nil then
@@ -649,15 +667,25 @@ begin
   if ErrorStatus <> 0 then begin
     var Dummy: Integer;
     FDirectStatusFunction(FDirectPtr, SCI_SETSTATUS, 0, 0, Dummy);
-    ErrorFmt('Error status %d returned after Call(%u, %d, %d) = %d',
-      [ErrorStatus, Msg, WParam, LParam, Result]);
+    if ErrorStatus < SC_STATUS_WARN_START then
+      ErrorFmt('Error status %d returned after Call(%u, %d, %d) = %d',
+        [ErrorStatus, Msg, WParam, LParam, Result]);
   end;
+
+  WarnStatus := ErrorStatus;
 end;
 
-function TScintEdit.CallStr(Msg: Cardinal; WParam: Longint;
+function TScintEdit.Call(Msg: Cardinal; WParam: Longint;
   const LParamStr: TScintRawString): Longint;
 begin
-  Result := Call(Msg, WParam, LPARAM(PAnsiChar(LParamStr)));
+  var Dummy: Integer;
+  Result := Call(Msg, WParam, LParamStr, Dummy);
+end;
+
+function TScintEdit.Call(Msg: Cardinal; WParam: Longint;
+  const LParamStr: TScintRawString; out WarnStatus: Integer): Longint;
+begin
+  Result := Call(Msg, WParam, LPARAM(PAnsiChar(LParamStr)), WarnStatus);
 end;
 
 procedure TScintEdit.CancelAutoComplete;
@@ -890,7 +918,7 @@ function TScintEdit.FindRawText(const StartPos, EndPos: Integer;
 begin
   SetTarget(StartPos, EndPos);
   Call(SCI_SETSEARCHFLAGS, GetSearchFlags(Options), 0);
-  Result := Call(SCI_SEARCHINTARGET, Length(S), LPARAM(PAnsiChar(S))) >= 0;
+  Result := Call(SCI_SEARCHINTARGET, Length(S), S) >= 0;
   if Result then
     MatchRange := GetTarget;
 end;
@@ -1231,6 +1259,18 @@ begin
   Result := Call(SCI_GETREADONLY, 0, 0) <> 0;
 end;
 
+class function TScintEdit.GetReplaceTargetMessage(
+  const ReplaceMode: TScintReplaceMode): Cardinal;
+begin
+  case ReplaceMode of
+    srmNormal: Result := SCI_REPLACETARGET;
+    srmMinimal: Result := SCI_REPLACETARGETMINIMAL;
+    srmRegEx: Result := SCI_REPLACETARGETRE;
+  else
+    raise GetErrorException('Unknown ReplaceMode');
+  end;
+end;
+
 class function TScintEdit.GetSearchFlags(const Options: TScintFindOptions): Integer;
 begin
   Result := 0;
@@ -1238,14 +1278,8 @@ begin
     Result := Result or SCFIND_MATCHCASE;
   if sfoWholeWord in Options then
     Result := Result or SCFIND_WHOLEWORD;
-end;
-
-class function TScintEdit.GetSearchFlags(const MatchCase: Boolean): Integer;
-begin
-  if MatchCase then
-    Result := GetSearchFlags([sfoMatchCase])
-  else
-    Result := GetSearchFlags([]);
+  if sfoRegEx in Options then
+    Result := Result or (SCFIND_REGEXP or SCFIND_CXX11REGEX);
 end;
 
 function TScintEdit.GetSelection: TScintRange;
@@ -1407,9 +1441,9 @@ begin
 end;
 
 function TScintEdit.MainSelTextEquals(const S: String;
-  const MatchCase: Boolean): Boolean;
+  const Options: TScintFindOptions): Boolean;
 begin
-  Result := RawMainSelTextEquals(ConvertStringToRawString(S), MatchCase);
+  Result := RawMainSelTextEquals(ConvertStringToRawString(S), Options);
 end;
 
 procedure TScintEdit.Notification(AComponent: TComponent; Operation: TOperation);
@@ -1487,12 +1521,12 @@ begin
 end;
 
 function TScintEdit.RawMainSelTextEquals(const S: TScintRawString;
-  const MatchCase: Boolean): Boolean;
+  const Options: TScintFindOptions): Boolean;
 begin
   Call(SCI_TARGETFROMSELECTION, 0, 0);
-  Call(SCI_SETSEARCHFLAGS, GetSearchFlags(MatchCase), 0);
+  Call(SCI_SETSEARCHFLAGS, GetSearchFlags(Options), 0);
   Result := False;
-  if Call(SCI_SEARCHINTARGET, Length(S), LPARAM(PAnsiChar(S))) >= 0 then begin
+  if Call(SCI_SEARCHINTARGET, Length(S), S) >= 0 then begin
     var Target := GetTarget;
     var Sel := GetSelection;
     if (Target.StartPos = Sel.StartPos) and (Target.EndPos = Sel.EndPos) then
@@ -1522,19 +1556,45 @@ begin
   SetSingleSelection(CaretPos, AnchorPos);
 end;
 
+function TScintEdit.ReplaceMainSelText(const S: String;
+  const ReplaceMode: TScintReplaceMode): TScintRange;
+begin
+  ReplaceRawMainSelText(ConvertStringToRawString(S), ReplaceMode);
+end;
+
+function TScintEdit.ReplaceRawMainSelText(const S: TScintRawString;
+  const ReplaceMode: TScintReplaceMode): TScintRange;
+{ Replaces the main selection just like SetRawSelText/SCI_REPLACESEL but
+  without removing additional selections }
+begin
+  { First replace the selection }
+  Call(SCI_TARGETFROMSELECTION, 0, 0);
+  Call(GetReplaceTargetMessage(ReplaceMode), Length(S), S);
+  { Then make the main selection an empty selection at the end of the inserted
+    text, just like SCI_REPLACESEL }
+  var Pos := GetTarget.EndPos; { SCI_REPLACETARGET* updates the target }
+  var MainSel := MainSelection;
+  SetSelectionCaretPosition(MainSel, Pos);
+  SetSelectionAnchorPosition(MainSel, Pos);
+  { Finally call Editor::SetLastXChosen and scroll caret into view, also just
+    like SCI_REPLACESEL }
+  ChooseCaretX;
+  ScrollCaretIntoView;
+end;
+
 function TScintEdit.ReplaceRawTextRange(const StartPos, EndPos: Integer;
-  const S: TScintRawString): TScintRange;
+  const S: TScintRawString; const ReplaceMode: TScintReplaceMode): TScintRange;
 begin
   CheckPosRange(StartPos, EndPos);
   SetTarget(StartPos, EndPos);
-  Call(SCI_REPLACETARGETMINIMAL, Length(S), LPARAM(PAnsiChar(S)));
+  Call(GetReplaceTargetMessage(ReplaceMode), Length(S), S);
   Result := GetTarget;
 end;
 
 function TScintEdit.ReplaceTextRange(const StartPos, EndPos: Integer;
-  const S: String): TScintRange;
+  const S: String; const ReplaceMode: TScintReplaceMode): TScintRange;
 begin
-  Result := ReplaceRawTextRange(StartPos, EndPos, ConvertStringToRawString(S));
+  Result := ReplaceRawTextRange(StartPos, EndPos, ConvertStringToRawString(S), ReplaceMode);
 end;
 
 procedure TScintEdit.RestyleLine(const Line: Integer);
@@ -1616,7 +1676,7 @@ end;
 
 procedure TScintEdit.SetAutoCompleteFillupChars(const FillupChars: AnsiString);
 begin
-  CallStr(SCI_AUTOCSETFILLUPS, 0, FillupChars);
+  Call(SCI_AUTOCSETFILLUPS, 0, FillupChars);
 end;
 
 procedure TScintEdit.SetAutoCompleteFontName(const Value: String);
@@ -1637,7 +1697,7 @@ end;
 
 procedure TScintEdit.SetAutoCompleteSelectedItem(const S: TScintRawString);
 begin
-  CallStr(SCI_AUTOCSELECT, 0, S);
+  Call(SCI_AUTOCSELECT, 0, S);
 end;
 
 procedure TScintEdit.SetAutoCompleteSeparators(const Separator, TypeSeparator: AnsiChar);
@@ -1648,7 +1708,7 @@ end;
 
 procedure TScintEdit.SetAutoCompleteStopChars(const StopChars: AnsiString);
 begin
-  CallStr(SCI_AUTOCSTOPS, 0, StopChars);
+  Call(SCI_AUTOCSTOPS, 0, StopChars);
 end;
 
 procedure TScintEdit.SetBraceBadHighlighting(const Pos: Integer);
@@ -1828,28 +1888,14 @@ begin
 end;
 
 procedure TScintEdit.SetRawMainSelText(const Value: TScintRawString);
-{ Replaces the main selection just like SetRawSelText/SCI_REPLACESEL but
-  without removing additional selections }
 begin
-  { First replace the selection }
-  Call(SCI_TARGETFROMSELECTION, 0, 0);
-  Call(SCI_REPLACETARGETMINIMAL, Length(Value), LPARAM(PAnsiChar(Value)));
-  { Then make the main selection an empty selection at the end of the inserted
-    text, just like SCI_REPLACESEL }
-  var Pos := GetTarget.EndPos; { SCI_REPLACETARGETMINIMAL updates the target }
-  var MainSel := MainSelection;
-  SetSelectionCaretPosition(MainSel, Pos);
-  SetSelectionAnchorPosition(MainSel, Pos);
-  { Finally call Editor::SetLastXChosen and scroll caret into view, also just
-    like SCI_REPLACESEL }
-  ChooseCaretX;
-  ScrollCaretIntoView;
+  ReplaceRawMainSelText(Value, srmMinimal);
 end;
 
 procedure TScintEdit.SetRawSelText(const Value: TScintRawString);
 { Replaces the main selection's text and *clears* additional selections }
 begin
-  Call(SCI_REPLACESEL, 0, LPARAM(PAnsiChar(Value)));
+  Call(SCI_REPLACESEL, 0, Value);
 end;
 
 procedure TScintEdit.SetRawText(const Value: TScintRawString);
@@ -2001,7 +2047,7 @@ begin
   FWordCharsAsSet := [];
   for var C in S do
     Include(FWordCharsAsSet, C);
-  CallStr(SCI_SETWORDCHARS, 0, S);
+  Call(SCI_SETWORDCHARS, 0, S);
 end;
 
 procedure TScintEdit.SetWordWrap(const Value: Boolean);
@@ -2020,13 +2066,13 @@ end;
 procedure TScintEdit.ShowAutoComplete(const CharsEntered: Integer;
   const WordList: AnsiString);
 begin
-  Call(SCI_AUTOCSHOW, CharsEntered, LPARAM(PAnsiChar(WordList)));
+  Call(SCI_AUTOCSHOW, CharsEntered, WordList);
 end;
 
 procedure TScintEdit.ShowCallTip(const Pos: Integer;
   const Definition: AnsiString);
 begin
-  Call(SCI_CALLTIPSHOW, Pos, LPARAM(PAnsiChar(Definition)));
+  Call(SCI_CALLTIPSHOW, Pos, Definition);
 end;
 
 procedure TScintEdit.StyleNeeded(const EndPos: Integer);
@@ -2132,7 +2178,7 @@ procedure TScintEdit.StyleNeeded(const EndPos: Integer);
       if HadStyleByteIndicators then
         for var I := 1 to N do
           FStyler.FStyleStr[I] := AnsiChar(Ord(FStyler.FStyleStr[I]) and StyleNumberMask);
-      Call(SCI_SETSTYLINGEX, Length(FStyler.FStyleStr), LPARAM(PAnsiChar(FStyler.FStyleStr)));
+      Call(SCI_SETSTYLINGEX, Length(FStyler.FStyleStr), FStyler.FStyleStr);
 
       FStyler.FStyleStr := '';
       FStyler.FText := '';
@@ -2193,7 +2239,7 @@ procedure TScintEdit.StyleNeeded(const EndPos: Integer);
     { Note: Using SCI_SETSTYLINGEX because it only redraws the part of the
       range that changed, whereas SCI_SETSTYLING redraws the entire range. }
     StyleStr := StringOfChar(AnsiChar(0), FLines.GetRawLineLengthWithEnding(Line));
-    Call(SCI_SETSTYLINGEX, Length(StyleStr), LPARAM(PAnsiChar(StyleStr)));
+    Call(SCI_SETSTYLINGEX, Length(StyleStr), StyleStr);
   end;
 
 var
@@ -2240,6 +2286,21 @@ begin
   ForwardMessage(Message);
 end;
 
+function TScintEdit.TestRawRegularExpression(const S: TScintRawString): Boolean;
+{ Example invalid regular expression: ( }
+begin
+  Call(SCI_SETTARGETRANGE, 0, 0);
+  Call(SCI_SETSEARCHFLAGS, GetSearchFlags([sfoRegEx]), 0);
+  var WarnStatus: Integer;
+  var Res := Call(SCI_SEARCHINTARGET, Length(S), S, WarnStatus);
+  Result := not ((Res = -1) and (WarnStatus = SC_STATUS_WARN_REGEX));
+end;
+
+function TScintEdit.TestRegularExpression(const S: String): Boolean;
+begin
+  Result := TestRawRegularExpression(ConvertStringToRawString(S));
+end;
+
 procedure TScintEdit.Undo;
 begin
   Call(SCI_UNDO, 0, 0);
@@ -2310,7 +2371,7 @@ begin
       end;
     end;
 
-    PixelWidth := 4 + CallStr(SCI_TEXTWIDTH, STYLE_LINENUMBER, AnsiString(Nines));
+    PixelWidth := 4 + Call(SCI_TEXTWIDTH, STYLE_LINENUMBER, AnsiString(Nines));
   end else
     PixelWidth := 0;
   
@@ -2325,7 +2386,7 @@ var
     const Attr: TScintStyleAttributes; const Force: Boolean);
   begin
     if Force or (Attr.FontName <> DefaultAttr.FontName) then
-      CallStr(SCI_STYLESETFONT, StyleNumber, AnsiString(Attr.FontName));
+      Call(SCI_STYLESETFONT, StyleNumber, AnsiString(Attr.FontName));
     if Force or (Attr.FontSize <> DefaultAttr.FontSize) then
       { Note: Scintilla doesn't support negative point sizes like the VCL }
       Call(SCI_STYLESETSIZE, StyleNumber, Abs(Attr.FontSize));
@@ -2659,7 +2720,7 @@ begin
   CheckIndexRange(Index);
   StartPos := FEdit.GetPositionFromLine(Index);
   EndPos := FEdit.GetLineEndPosition(Index);
-  FEdit.ReplaceRawTextRange(StartPos, EndPos, S);
+  FEdit.ReplaceRawTextRange(StartPos, EndPos, S, srmMinimal);
 end;
 
 procedure TScintEditStrings.SetText(Text: PChar);

BIN
Files/isscint.dll


+ 45 - 0
ISHelp/isetup.xml

@@ -85,6 +85,7 @@
     <contentstopic title="Setup Exit Codes" topic="setupexitcodes" />
     <contentstopic title="Uninstaller Exit Codes" topic="uninstexitcodes" />
     <contentstopic title="Compiler IDE Keyboard And Mouse Commands" topic="compformshortcuts" />
+    <contentstopic title="Compiler IDE Regular Expressions" topic="compformregex" />
     <contentstopic title="Miscellaneous Notes" topic="technotes" />
     <contentstopic title="Example Scripts" topic="examples" />
     <contentstopic title="Frequently Asked Questions" topic="faq" />
@@ -3369,6 +3370,50 @@ Filename: "{win}\MYPROG.INI"; Section: "InstallSettings"; Key: "InstallPath"; St
 
 
 
+<topic name="compformregex" title="Compiler IDE Regular Expressions">
+<keyword value="Compiler IDE Regular Expressions" />
+<keyword value="Regular Expressions" />
+<keyword value="Regex" />
+<body>
+
+<p>The Compiler IDE supports to use of regular expressions for all of its find and replace operations. This can be enabled and disabled using the <i>Use Regular Expressions</i> option to the <i>Edit</i> menu or its shortcut (Alt+R). Once enabled or disabled, this setting will be preserved across sessions.</p>
+
+<p>When used, regular expressions will only match ranges within a single line, never matching over multiple lines.</p>
+
+<p>Regular expressions must be written in the ECMAScript grammar, generally similar to the grammar used by JavaScript and the .NET languages. Invalid regular expressions will cause an error message.</p>
+
+<p>For more information about the grammar see:</p>
+<ul>
+<li><extlink href="https://cplusplus.com/reference/regex/ECMAScript/">https://cplusplus.com/reference/regex/ECMAScript/</extlink></li>
+<li><extlink href="https://learn.microsoft.com/en-us/cpp/standard-library/regular-expressions-cpp">https://learn.microsoft.com/en-us/cpp/standard-library/regular-expressions-cpp</extlink></li>
+<li><extlink href="https://regex101.com/r/fYE7S3/1">https://regex101.com/r/fYE7S3/1</extlink></li>
+</ul>
+
+<p>When replacing using regular expressions the replace string may contain the following special escape sequences:</p>
+
+<table>
+<tr><td><u>Escape Sequence</u></td><td><u>Meaning</u></td></tr>
+<tr>
+  <td>$1 through $9 or \1 through \9</td><td>Contents of the corresponding capture group</td>
+</tr>
+<tr>
+  <td>$&amp; or \0</td><td>Complete match contents</td>
+</tr>
+<tr>
+  <td>$$</td><td>A literal '$' character</td>
+</tr>
+<tr>
+  <td>\\, \a, \b, \f, \r, \n, \t, \v</td><td>A literal '\', '\a', '\b', etc. character</td>
+</tr>
+</table>
+
+<p>For example, if the search string was (Ex)(ample) and the replace string was $2$1, when applied to a script saying "Examples" this would modify the script to say "amplExs". Or if the replace string was $1\r\n$2 it would put a newline between "Ex" and "ample".</p>
+
+</body>
+</topic>
+
+
+
 <topic name="compilercmdline" title="Command Line Compiler Execution">
 <keyword value="Command Line Compiler Execution" />
 <keyword value="command line parameters" />

+ 5 - 0
Projects/Src/Compil32/CompForm.dfm

@@ -509,6 +509,11 @@ object CompileForm: TCompileForm
         ShortCut = 16456
         OnClick = EReplaceClick
       end
+      object EFindRegEx: TMenuItem
+        Caption = 'Use Regular E&xpressions'
+        ShortCut = 32850
+        OnClick = EFindRegExClick
+      end
       object N13: TMenuItem
         Caption = '-'
       end

+ 76 - 30
Projects/Src/Compil32/CompForm.pas

@@ -254,6 +254,7 @@ type
     EBraceMatch: TMenuItem;
     EFoldLine: TMenuItem;
     EUnfoldLine: TMenuItem;
+    EFindRegEx: TMenuItem;
     procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
     procedure FExitClick(Sender: TObject);
     procedure FOpenMainFileClick(Sender: TObject);
@@ -374,6 +375,7 @@ type
     procedure EToggleLinesCommentClick(Sender: TObject);
     procedure EBraceMatchClick(Sender: TObject);
     procedure EFoldOrUnfoldLineClick(Sender: TObject);
+    procedure EFindRegExClick(Sender: TObject);
   private
     { Private declarations }
     FMemos: TList<TCompScintEdit>;                      { FMemos[0] is the main memo and FMemos[1] the preprocessor output memo - also see MemosTabSet comment above }
@@ -408,6 +410,7 @@ type
       TabWidth: Integer;
       UseTabCharacter: Boolean;
       UseFolding: Boolean;
+      FindRegEx: Boolean;
       WordWrap: Boolean;
       AutoIndent: Boolean;
       IndentationGuides: Boolean;
@@ -448,6 +451,7 @@ type
     FDebuggerException: String;
     FRunParameters: String;
     FLastFindOptions: TFindOptions;
+    FLastFindRegEx: Boolean;
     FLastFindText: String;
     FLastReplaceText: String;
     FLastEvaluateConstantText: String;
@@ -567,7 +571,8 @@ type
     procedure SetStepLine(const AMemo: TCompScintFileEdit; ALine: Integer);
     procedure ShowOpenMainFileDialog(const Examples: Boolean);
     procedure StatusMessage(const Kind: TStatusMessageKind; const S: String);
-    procedure StoreLastFindOptions(Sender: TObject);
+    function StoreAndTestLastFindOptions(Sender: TObject): Boolean;
+    function TestLastFindOptions: Boolean;
     procedure SyncEditorOptions;
     function TabIndexToMemo(const ATabIndex, AMaxTabIndex: Integer): TCompScintEdit;
     function ToCurrentPPI(const XY: Integer): Integer;
@@ -800,6 +805,7 @@ constructor TCompileForm.Create(AOwner: TComponent);
       FOptions.TabWidth := Ini.ReadInteger('Options', 'TabWidth', 2);
       FOptions.UseTabCharacter := Ini.ReadBool('Options', 'UseTabCharacter', 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);
@@ -2764,6 +2770,7 @@ begin
   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;
@@ -2891,7 +2898,7 @@ begin
       if I = SelStartLine then // is this the first selected line?
         Inc(Selection.StartPos, Length(LongComment));
       Inc(Selection.EndPos, Length(LongComment)); // every iteration
-      AMemo.CallStr(SCI_INSERTTEXT, LineIndent, AMemo.ConvertStringToRawString(LongComment));
+      AMemo.Call(SCI_INSERTTEXT, LineIndent, AMemo.ConvertStringToRawString(LongComment));
     end;
     // after uncommenting selection may promote itself to the lines
     // before the first initially selected line;
@@ -2957,7 +2964,7 @@ begin
 
     while (StartPos < EndPos) and
           FActiveMemo.FindText(StartPos, EndPos, FLastFindText,
-            FindOptionsToSearchOptions(FLastFindOptions), FoundRange) do begin
+            FindOptionsToSearchOptions(FLastFindOptions, FLastFindRegEx), FoundRange) do begin
       if StartPos = 0 then
         FActiveMemo.SetSingleSelection(FoundRange.EndPos, FoundRange.StartPos)
       else
@@ -3025,7 +3032,7 @@ end;
 procedure TCompileForm.CloseTab(const TabIndex: Integer);
 begin
   var Memo := TabIndexToMemo(TabIndex, MemosTabSet.Tabs.Count-1);
-  var MemoWasActiveMemo:= Memo = FActiveMemo;
+  var MemoWasActiveMemo := Memo = FActiveMemo;
 
   MemosTabSet.Tabs.Delete(TabIndex); { This will not change MemosTabset.TabIndex }
   MemosTabSet.Hints.Delete(TabIndex);
@@ -3529,15 +3536,13 @@ begin
       FLastFindOptions := FLastFindOptions + [frDown]
     else
       FLastFindOptions := FLastFindOptions - [frDown];
+    FLastFindRegEx := FOptions.FindRegEx;
+    if not TestLastFindOptions then
+      Exit;
     FindNext(False);
   end;
 end;
 
-procedure TCompileForm.EFoldOrUnfoldLineClick(Sender: TObject);
-begin
-  FActiveMemo.FoldLine(FActiveMemo.CaretLine, Sender = EFoldLine);
-end;
-
 procedure TCompileForm.FindNext(const ReverseDirection: Boolean);
 var
   StartPos, EndPos: Integer;
@@ -3555,28 +3560,49 @@ begin
     EndPos := 0;
   end;
   if FActiveMemo.FindText(StartPos, EndPos, FLastFindText,
-     FindOptionsToSearchOptions(FLastFindOptions), Range) then
+     FindOptionsToSearchOptions(FLastFindOptions, FLastFindRegEx), Range) then
     FActiveMemo.SelectAndEnsureVisible(Range)
   else
     MsgBoxFmt('Cannot find "%s"', [FLastFindText], SCompilerFormCaption,
       mbInformation, MB_OK);
 end;
 
-procedure TCompileForm.StoreLastFindOptions(Sender: TObject);
+function TCompileForm.StoreAndTestLastFindOptions(Sender: TObject): Boolean;
 begin
-  with Sender as TFindDialog do begin
-    FLastFindOptions := Options;
-    FLastFindText := FindText;
+  if Sender is TFindDialog then begin
+    with Sender as TFindDialog do begin
+      FLastFindOptions := Options;
+      FLastFindText := FindText;
+    end;
+  end else begin
+    with Sender as TReplaceDialog do begin
+      FLastFindOptions := Options;
+      FLastFindText := FindText;
+    end;
   end;
+  FLastFindRegEx := FOptions.FindRegEx;
+
+  Result := TestLastFindOptions;
+end;
+
+function TCompileForm.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 TCompileForm.FindDialogFind(Sender: TObject);
 begin
   { This event handler is shared between FindDialog & ReplaceDialog }
 
-  { Save a copy of the current text so that InitializeFindText doesn't
-    mess up the operation of Edit | Find Next }
-  StoreLastFindOptions(Sender);
+  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;
@@ -3587,7 +3613,8 @@ end;
 
 procedure TCompileForm.FindInFilesDialogFind(Sender: TObject);
 begin
-  StoreLastFindOptions(Sender);
+  if not StoreAndTestLastFindOptions(Sender) then
+    Exit;
 
   FindResultsList.Clear;
   SendMessage(FindResultsList.Handle, LB_SETHORIZONTALEXTENT, 0, 0);
@@ -3604,7 +3631,7 @@ begin
       var Range: TScintRange;
       while (StartPos < EndPos) and
             Memo.FindText(StartPos, EndPos, FLastFindText,
-              FindOptionsToSearchOptions(FLastFindOptions), Range) do begin
+              FindOptionsToSearchOptions(FLastFindOptions, FLastFindRegEx), Range) do begin
         var Line := Memo.GetLineFromPosition(Range.StartPos);
         var Prefix := Format('  Line %d: ', [Line+1]);
         var FindResult := TFindResult.Create;
@@ -3688,22 +3715,22 @@ begin
 end;
 
 procedure TCompileForm.ReplaceDialogReplace(Sender: TObject);
-var
-  ReplaceCount, Pos: Integer;
-  Range, NewRange: TScintRange;
 begin
-  FLastFindOptions := ReplaceDialog.Options;
-  FLastFindText := ReplaceDialog.FindText;
+  if not StoreAndTestLastFindOptions(Sender) then
+    Exit;
+
   FLastReplaceText := ReplaceDialog.ReplaceText;
+  var ReplaceMode := RegExToReplaceMode(FLastFindRegEx);
 
   if frReplaceAll in FLastFindOptions then begin
-    ReplaceCount := 0;
+    var ReplaceCount := 0;
     FActiveMemo.BeginUndoAction;
     try
-      Pos := 0;
+      var Pos := 0;
+      var Range: TScintRange;
       while FActiveMemo.FindText(Pos, FActiveMemo.RawTextLength, FLastFindText,
-         FindOptionsToSearchOptions(FLastFindOptions), Range) do begin
-        NewRange := FActiveMemo.ReplaceTextRange(Range.StartPos, Range.EndPos, FLastReplaceText);
+         FindOptionsToSearchOptions(FLastFindOptions, FLastFindRegEx), Range) do begin
+        var NewRange := FActiveMemo.ReplaceTextRange(Range.StartPos, Range.EndPos, FLastReplaceText, ReplaceMode);
         Pos := NewRange.EndPos;
         Inc(ReplaceCount);
       end;
@@ -3718,12 +3745,31 @@ begin
         mbInformation, MB_OK);
   end
   else begin
-    if FActiveMemo.MainSelTextEquals(FLastFindText, frMatchCase in FLastFindOptions) then
-      FActiveMemo.MainSelText := FLastReplaceText;
+    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 TCompileForm.EFindRegExClick(Sender: TObject);
+begin
+  FOptions.FindRegEx := not FOptions.FindRegEx;
+  var Ini := TConfigIniFile.Create;
+  try
+    Ini.WriteBool('Options', 'FindRegEx', FOptions.FindRegEx);
+  finally
+    Ini.Free;
+  end;
+end;
+
+procedure TCompileForm.EFoldOrUnfoldLineClick(Sender: TObject);
+begin
+  FActiveMemo.FoldLine(FActiveMemo.CaretLine, Sender = EFoldLine);
+end;
+
 procedure TCompileForm.UpdateStatusPanelHeight(H: Integer);
 var
   MinHeight, MaxHeight: Integer;

+ 27 - 2
Projects/Src/Compil32/CompFunc.pas

@@ -67,7 +67,11 @@ procedure AddLines(const ListBox: TListBox; const S: String; const AObject: TObj
 procedure SetLowPriority(ALowPriority: Boolean; var SavePriorityClass: DWORD);
 procedure SetHelpFileDark(const Dark: Boolean);
 function GetHelpFile: String;
-function FindOptionsToSearchOptions(const FindOptions: TFindOptions): TScintFindOptions;
+function FindOptionsToSearchOptions(const FindOptions: TFindOptions;
+  const RegEx: Boolean): TScintFindOptions; overload;
+function FindOptionsToSearchOptions(const MatchCase: Boolean;
+  const RegEx: Boolean): TScintFindOptions; overload;
+function RegExToReplaceMode(const RegEx: Boolean): TScintReplaceMode;
 procedure StartAddRemovePrograms;
 function GetSourcePath(const AFilename: String): String;
 function ReadScriptLines(const ALines: TStringList; const ReadFromFile: Boolean;
@@ -717,13 +721,34 @@ begin
   Result := Format('%sisetup%s.chm', [PathExtractPath(NewParamStr(0)), IfThen(HelpFileDark, '-dark', '')]);
 end;
 
-function FindOptionsToSearchOptions(const FindOptions: TFindOptions): TScintFindOptions;
+function FindOptionsToSearchOptions(const FindOptions: TFindOptions;
+  const RegEx: Boolean): TScintFindOptions;
 begin
   Result := [];
   if frMatchCase in FindOptions then
     Include(Result, sfoMatchCase);
   if frWholeWord in FindOptions then
     Include(Result, sfoWholeWord);
+  if RegEx then
+    Include(Result, sfoRegEx);
+end;
+
+function FindOptionsToSearchOptions(const MatchCase: Boolean;
+  const RegEx: Boolean): TScintFindOptions; overload;
+begin
+  Result := [];
+  if MatchCase then
+    Include(Result, sfoMatchCase);
+  if RegEx then
+    Include(Result, sfoRegEx);
+end;
+
+function RegExToReplaceMode(const RegEx: Boolean): TScintReplaceMode;
+begin
+  if RegEx then
+    Result := srmRegEx
+  else
+    Result := srmMinimal;
 end;
 
 procedure StartAddRemovePrograms;

+ 1 - 4
Projects/Src/Compil32/CompScintEdit.pas

@@ -201,9 +201,6 @@ begin
 
   { Some notes about Scintilla versions:
     -What about using Calltips and SCN_DWELLSTART to show variable evalutions?
-    -3.6.6: Investigate SCFIND_CXX11REGEX: C++ 11 <regex> support built by default.
-            Can be disabled by defining NO_CXX11_REGEX. Good (?) overview at:
-            https://cplusplus.com/reference/regex/ECMAScript/
     -5.2.3: "Applications should move to SCI_GETTEXTRANGEFULL, SCI_FINDTEXTFULL,
             and SCI_FORMATRANGEFULL from their predecessors as they will be
             deprecated." So our use of SCI_GETTEXTRANGE and SCI_FORMATRANGE needs
@@ -233,7 +230,7 @@ begin
 
   AssignCmdKey('Z', [ssShift, ssCtrl], SCI_REDO);
   
-  Call(SCI_SETSCROLLWIDTH, 1024 * CallStr(SCI_TEXTWIDTH, 0, 'X'), 0);
+  Call(SCI_SETSCROLLWIDTH, 1024 * Call(SCI_TEXTWIDTH, 0, 'X'), 0);
 
   Call(SCI_INDICSETSTYLE, minSquiggly, INDIC_SQUIGGLE); { Overwritten by TCompForm.SyncEditorOptions }
   Call(SCI_INDICSETFORE, minSquiggly, clRed); { May be overwritten by UpdateThemeColorsAndStyleAttributes }

+ 6 - 5
whatsnew.htm

@@ -39,7 +39,7 @@ For conditions of distribution and use, see <a href="files/is/license.txt">LICEN
 <ul>
   <li>Added new <i>Add Next Occurrence</i> menu item to the <i>Edit</i> menu to add the next occurrence of the current word or selected text as an additional selection (Shift+Alt+.).</li>
   <li>Added new <i>Select All Occurrences</i> menu item to the <i>Edit</i> menu to select all occurrences of the current word or selected text (Shift+Alt+;).</li>
-  <li>Added new <i>Select All Find Matches</i> menu item to the <i>Edit</i> menu to select all matches of the last find text (Alt+Enter).<br />Additionally, the Find (Ctrl+F) and Replace (Ctrl+H) dialogs now both support being closed by Alt+Enter to directly select all matches.</li>
+  <li>Added new <i>Select All Find Matches</i> menu item to the <i>Edit</i> menu to select all matches of the last find text (Alt+Enter).<br />Additionally, the <i>Find</i> (Ctrl+F) and <i>Replace</i> (Ctrl+H) dialogs now both support being closed by Alt+Enter to directly select all matches.</li>
   <li>Added shortcuts to add a cursor or selection up or down (Ctrl+Alt+Up and Ctrl+Alt+Down). For multi-line selections this extends the selection up or down and never shrinks.</li>
   <li>Added shortcuts to add a word or line as an additional selection (Ctrl+Double Click and Ctrl+Triple Click).</li>
   <li>Added shortcut to remove a selection by clicking it (Ctrl+Click).</li>
@@ -57,13 +57,15 @@ For conditions of distribution and use, see <a href="files/is/license.txt">LICEN
   <li>Added autocompletion support for all Pascal Scripting support functions, types, constants, etcetera. Existing option <i>Invoke autocompletion automatically</i> controls whether the autocompletion suggestions appear automatically or only when invoked manually by pressing Ctrl+Space or Ctrl+I.</li>
   <li>Added parameter hints and autocompletion support for all Pascal Scripting support class members and properties. Both always show all classes' members and properties instead of just those of the object's class.</li>
   <li>Added new <i>Enable section folding</i> option which allows you to temporarily hide sections while editing by clicking the new minus or plus icons in the editor's gutter or by using the new keyboard shortcuts (Ctrl+Shift+[ to fold and Ctrl+Shift+] to unfold) or menu items. Enabled by default.</li>
+  <li>Added new <i>Use Regular Expressions</i> option to the <i>Edit</i> menu to enable or disable the use of regular expressions for all find and replace operations and added a shortcut for it (Alt+R).</li>
   <li>The editor's gutter now shows change history to keep track of saved and unsaved modifications. Always enabled.</li>
   <li>The editor's font now defaults to Consolas if available, consistent with most other modern editors.</li>
   <li>The editor can now be scrolled horizontally instead of vertically by holding the Shift key while rotating the mouse wheel. Horizontal scroll wheels are now also supported.</li>
   <li>Cut (Ctrl+X or Shift+Delete) and Copy (Ctrl+C or Ctrl+Insert) now cut or copy the entire line if there's no selection, consistent with most other modern editors.</li>
-  <li>Added shortcuts to move selected lines up or down (Alt+Up and Alt+Down).</li>
-  <li>Added shortcut and menu item to toggle line comment (Ctrl+/).</li>
-  <li>Added shortcut and menu item to go to matching brace (Ctrl+Shift+\).</li>
+  <li>Added new shortcuts to move selected lines up or down (Alt+Up and Alt+Down).</li>
+  <li>Added new shortcut and menu item to the <i>Edit</i> menu to toggle line comment (Ctrl+/).</li>
+  <li>Added new shortcut and menu item to the <i>Edit</i> menu to go to matching brace (Ctrl+Shift+\).</li>
+  <li>Moved the <i>Word Wrap</i> option to the <i>View</i> menu and added a shortcut for it (Alt+Z).</li>
   <li>Added a right-click popup menu to the editor's gutter column for breakpoints.</li>
   <li>Added dark mode support to autocompletion lists and also added a minimum width.</li>
   <li>Improved brace highlighting.</li>
@@ -74,7 +76,6 @@ For conditions of distribution and use, see <a href="files/is/license.txt">LICEN
   <li>Shortcuts Alt+Left and Alt+Right now always navigate back and forward even if Visual Studio-style menu shortcuts have been activated.<br />Because of this Alt+Right can no longer be used to initiate auto complete, instead the existing Ctrl+Space or Ctrl+I alternatives must be used.</li>
   <li>Moved the list of recently opened files into a new <i>Open Recent</i> submenu of the <i>Files</i> menu.</li>
   <li>Added shortcuts to select a tab (Ctrl+1 through Ctrl+9).</li>
-  <li>Added new <i>Word Wrap</i> menu item to the <i>View</i> menu (Alt+Z).</li>
   <li>Added shortcut to the <i>Options</i> menu item in the <i>Tools</i> menu (Ctrl+,).</li>
 </ul>
 <p><span class="head2">Other changes</span></p>