IDE.MainForm.FindReplaceHelper.pas 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. unit IDE.MainForm.FindReplaceHelper;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2025 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. Compiler form - Find & Replace helper which has the navigation helper as ancestor
  8. Not used by MainForm: it uses IDE.MainForm.FinalHelper instead
  9. }
  10. interface
  11. uses
  12. Dialogs,
  13. IDE.MainForm, IDE.MainForm.NavigationHelper;
  14. type
  15. TMainFormFindReplaceHelper = class helper(TMainFormNavigationHelper) for TMainForm
  16. procedure ShowFindDialog(const Down: Boolean);
  17. procedure ShowFindInFilesDialog;
  18. procedure DoFindNext(const Down: Boolean);
  19. procedure DoFindOrReplaceDialogFind(const Dialog: TFindDialog);
  20. procedure DoFindInFilesDialogFind;
  21. procedure UpdateFindResult(const FindResult: TFindResult; const ItemIndex: Integer;
  22. const NewLine, NewLineStartPos: Integer);
  23. function FindSetupDirectiveValue(const DirectiveName,
  24. DefaultValue: String): String; overload;
  25. function FindSetupDirectiveValue(const DirectiveName: String;
  26. DefaultValue: Boolean): Boolean; overload;
  27. procedure ShowReplaceDialog;
  28. procedure DoReplaceDialogReplace;
  29. { Private }
  30. procedure _InitializeFindText(Dlg: TFindDialog);
  31. procedure _FindNext(const ReverseDirection: Boolean);
  32. function _StoreAndTestLastFindOptions(const Dialog: TFindDialog): Boolean;
  33. function _TestLastFindOptions: Boolean;
  34. end;
  35. implementation
  36. uses
  37. Windows, Messages,
  38. Classes, SysUtils, StrUtils, Menus,
  39. ScintEdit,
  40. Shared.CommonFunc, Shared.CommonFunc.Vcl,
  41. IDE.Messages, IDE.HelperFunc, IDE.ScintStylerInnoSetup;
  42. const
  43. OldFindReplaceWndProcProp = 'OldFindReplaceWndProc';
  44. function FindReplaceWndProc(Wnd: HWND; Msg: Cardinal; WParam: WPARAM; LParam: LPARAM): LRESULT; stdcall;
  45. function CallDefWndProc: LRESULT;
  46. begin
  47. Result := CallWindowProc(Pointer(GetProp(Wnd, OldFindReplaceWndProcProp)), Wnd,
  48. Msg, WParam, LParam);
  49. end;
  50. begin
  51. case Msg of
  52. WM_MENUCHAR:
  53. if LoWord(wParam) = VK_RETURN then begin
  54. var hwndCtl := GetDlgItem(Wnd, idOk);
  55. if (hWndCtl <> 0) and IsWindowEnabled(hWndCtl) then
  56. PostMessage(Wnd, WM_COMMAND, MakeWParam(idOk, BN_CLICKED), Windows.LPARAM(hWndCtl));
  57. end;
  58. WM_NCDESTROY:
  59. begin
  60. Result := CallDefWndProc;
  61. RemoveProp(Wnd, OldFindReplaceWndProcProp);
  62. Exit;
  63. end;
  64. end;
  65. Result := CallDefWndProc;
  66. end;
  67. procedure ExecuteFindDialogAllowingAltEnter(const FindDialog: TFindDialog);
  68. begin
  69. var DoHook := FindDialog.Handle = 0;
  70. FindDialog.Execute;
  71. if DoHook then begin
  72. SetProp(FindDialog.Handle, OldFindReplaceWndProcProp, THandle(GetWindowLongPtr(FindDialog.Handle, GWLP_WNDPROC)));
  73. SetWindowLongPtr(FindDialog.Handle, GWLP_WNDPROC, LONG_PTR(@FindReplaceWndProc));
  74. end;
  75. end;
  76. { TMainFormFindReplaceHelper }
  77. procedure TMainFormFindReplaceHelper._InitializeFindText(Dlg: TFindDialog);
  78. var
  79. S: String;
  80. begin
  81. S := FActiveMemo.MainSelText;
  82. if (S <> '') and (Pos(#13, S) = 0) and (Pos(#10, S) = 0) then
  83. Dlg.FindText := S
  84. else
  85. Dlg.FindText := FLastFindText;
  86. end;
  87. procedure TMainFormFindReplaceHelper.ShowFindDialog(const Down: Boolean);
  88. begin
  89. ReplaceDialog.CloseDialog;
  90. if FindDialog.Handle = 0 then
  91. _InitializeFindText(FindDialog);
  92. if Down then
  93. FindDialog.Options := FindDialog.Options + [frDown]
  94. else
  95. FindDialog.Options := FindDialog.Options - [frDown];
  96. ExecuteFindDialogAllowingAltEnter(FindDialog);
  97. end;
  98. procedure TMainFormFindReplaceHelper.ShowFindInFilesDialog;
  99. begin
  100. _InitializeFindText(FindInFilesDialog);
  101. FindInFilesDialog.Execute;
  102. end;
  103. procedure TMainFormFindReplaceHelper.DoFindNext(const Down: Boolean);
  104. begin
  105. if FLastFindText = '' then
  106. ShowFindDialog(Down)
  107. else begin
  108. if Down then
  109. FLastFindOptions := FLastFindOptions + [frDown]
  110. else
  111. FLastFindOptions := FLastFindOptions - [frDown];
  112. FLastFindRegEx := FOptions.FindRegEx;
  113. if not _TestLastFindOptions then
  114. Exit;
  115. _FindNext(False);
  116. end;
  117. end;
  118. procedure TMainFormFindReplaceHelper._FindNext(const ReverseDirection: Boolean);
  119. var
  120. StartPos, EndPos: Integer;
  121. Range: TScintRange;
  122. begin
  123. var Down := frDown in FLastFindOptions;
  124. if ReverseDirection then
  125. Down := not Down;
  126. if Down then begin
  127. StartPos := FActiveMemo.Selection.EndPos;
  128. EndPos := FActiveMemo.RawTextLength;
  129. end
  130. else begin
  131. StartPos := FActiveMemo.Selection.StartPos;
  132. EndPos := 0;
  133. end;
  134. if FActiveMemo.FindText(StartPos, EndPos, FLastFindText,
  135. FindOptionsToSearchOptions(FLastFindOptions, FLastFindRegEx), Range) then
  136. FActiveMemo.SelectAndEnsureVisible(Range)
  137. else
  138. MsgBoxFmt('Cannot find "%s"', [FLastFindText], SCompilerFormCaption,
  139. mbInformation, MB_OK);
  140. end;
  141. function TMainFormFindReplaceHelper._StoreAndTestLastFindOptions(const Dialog: TFindDialog): Boolean;
  142. begin
  143. { TReplaceDialog is a subclass of TFindDialog must check for TReplaceDialog first }
  144. if Dialog is TReplaceDialog then begin
  145. with Dialog as TReplaceDialog do begin
  146. FLastFindOptions := Options;
  147. FLastFindText := FindText;
  148. end;
  149. end else begin
  150. with Dialog do begin
  151. FLastFindOptions := Options;
  152. FLastFindText := FindText;
  153. end;
  154. end;
  155. FLastFindRegEx := FOptions.FindRegEx;
  156. Result := _TestLastFindOptions;
  157. end;
  158. function TMainFormFindReplaceHelper._TestLastFindOptions;
  159. begin
  160. if FLastFindRegEx then begin
  161. Result := FActiveMemo.TestRegularExpression(FLastFindText);
  162. if not Result then
  163. MsgBoxFmt('Invalid regular expression "%s"', [FLastFindText], SCompilerFormCaption,
  164. mbError, MB_OK);
  165. end else
  166. Result := True;
  167. end;
  168. procedure TMainFormFindReplaceHelper.DoFindOrReplaceDialogFind(const Dialog: TFindDialog);
  169. begin
  170. if not _StoreAndTestLastFindOptions(Dialog) then
  171. Exit;
  172. if GetKeyState(VK_MENU) < 0 then begin
  173. { Alt+Enter was used to close the dialog }
  174. Dialog.CloseDialog;
  175. ESelectAllFindMatchesClick(Self); { Uses the copy made above }
  176. end else
  177. _FindNext(GetKeyState(VK_SHIFT) < 0);
  178. end;
  179. procedure TMainFormFindReplaceHelper.DoFindInFilesDialogFind;
  180. begin
  181. if not _StoreAndTestLastFindOptions(FindInFilesDialog) then
  182. Exit;
  183. FindResultsList.Clear;
  184. SendMessage(FindResultsList.Handle, LB_SETHORIZONTALEXTENT, 0, 0);
  185. FFindResults.Clear;
  186. var Hits := 0;
  187. var Files := 0;
  188. for var Memo in FFileMemos do begin
  189. if Memo.Used then begin
  190. var StartPos := 0;
  191. var EndPos := Memo.RawTextLength;
  192. var FileHits := 0;
  193. var Range: TScintRange;
  194. while (StartPos < EndPos) and
  195. Memo.FindText(StartPos, EndPos, FLastFindText,
  196. FindOptionsToSearchOptions(FLastFindOptions, FLastFindRegEx), Range) do begin
  197. { Also see UpdateFindResult }
  198. var Line := Memo.GetLineFromPosition(Range.StartPos);
  199. var Prefix := Format(' Line %d: ', [Line+1]);
  200. var FindResult := TFindResult.Create;
  201. FindResult.Filename := Memo.Filename;
  202. FindResult.Line := Line;
  203. FindResult.LineStartPos := Memo.GetPositionFromLine(Line);
  204. FindResult.Range := Range;
  205. FindResult.PrefixStringLength := Length(Prefix);
  206. FFindResults.Add(FindResult);
  207. FindResultsList.Items.AddObject(Prefix + Memo.Lines[Line], FindResult);
  208. Inc(FileHits);
  209. StartPos := Range.EndPos;
  210. end;
  211. Inc(Files);
  212. if FileHits > 0 then begin
  213. Inc(Hits, FileHits);
  214. FindResultsList.Items.Insert(FindResultsList.Count-FileHits, Format('%s (%d hits):', [Memo.Filename, FileHits]));
  215. end;
  216. end;
  217. end;
  218. FindResultsList.Items.Insert(0, Format('Find "%s" (%d hits in %d files)', [FindInFilesDialog.FindText, Hits, Files]));
  219. FindInFilesDialog.CloseDialog;
  220. OutputTabSet.TabIndex := tiFindResults;
  221. SetStatusPanelVisible(True);
  222. end;
  223. procedure TMainFormFindReplaceHelper.UpdateFindResult(const FindResult: TFindResult; const ItemIndex: Integer;
  224. const NewLine, NewLineStartPos: Integer);
  225. begin
  226. { Also see DoFindInFilesDialogFind }
  227. const OldPrefix = Format(' Line %d: ', [FindResult.Line+1]);
  228. FindResult.Line := NewLine;
  229. const NewPrefix = Format(' Line %d: ', [FindResult.Line+1]);
  230. FindResultsList.Items[ItemIndex] := NewPrefix + Copy(FindResultsList.Items[ItemIndex], Length(OldPrefix)+1, MaxInt);
  231. FindResult.PrefixStringLength := Length(NewPrefix);
  232. const PosChange = NewLineStartPos - FindResult.LineStartPos;
  233. FindResult.LineStartPos := NewLineStartPos;
  234. FindResult.Range.StartPos := FindResult.Range.StartPos + PosChange;
  235. FindResult.Range.EndPos := FindResult.Range.EndPos + PosChange;
  236. end;
  237. function TMainFormFindReplaceHelper.FindSetupDirectiveValue(const DirectiveName,
  238. DefaultValue: String): String;
  239. begin
  240. Result := DefaultValue;
  241. var Memo := FMainMemo; { This function only searches the main file }
  242. var StartPos := 0;
  243. var EndPos := Memo.RawTextLength;
  244. var Range: TScintRange;
  245. { We rely on the styler to identify [Setup] section lines, but we
  246. may be searching into areas that haven't been styled yet }
  247. Memo.StyleNeeded(EndPos);
  248. while (StartPos < EndPos) and
  249. Memo.FindText(StartPos, EndPos, DirectiveName, [sfoWholeWord], Range) do begin
  250. var Line := Memo.GetLineFromPosition(Range.StartPos);
  251. if FMemosStyler.GetSectionFromLineState(Memo.Lines.State[Line]) = scSetup then begin
  252. var LineValue := Memo.Lines[Line].Trim; { LineValue can't be empty }
  253. if LineValue[1] <> ';' then begin
  254. var LineParts := LineValue.Split(['=']);
  255. if (Length(LineParts) = 2) and SameText(LineParts[0].Trim, DirectiveName) then begin
  256. Result := LineParts[1].Trim;
  257. { If Result is surrounded in quotes, remove them, just like TSetupCompiler.SeparateDirective }
  258. if (Length(Result) >= 2) and
  259. (Result[1] = '"') and (Result[Length(Result)] = '"') then
  260. Result := Copy(Result, 2, Length(Result)-2);
  261. Exit; { Compiler doesn't allow a directive to be specified twice so we can exit now }
  262. end;
  263. end;
  264. end;
  265. StartPos := Range.EndPos;
  266. end;
  267. end;
  268. function TMainFormFindReplaceHelper.FindSetupDirectiveValue(const DirectiveName: String;
  269. DefaultValue: Boolean): Boolean;
  270. begin
  271. var Value := FindSetupDirectiveValue(DirectiveName, IfThen(DefaultValue, '1', '0'));
  272. if not TryStrToBoolean(Value, Result) then
  273. Result := DefaultValue;
  274. end;
  275. procedure TMainFormFindReplaceHelper.ShowReplaceDialog;
  276. begin
  277. FindDialog.CloseDialog;
  278. if ReplaceDialog.Handle = 0 then begin
  279. _InitializeFindText(ReplaceDialog);
  280. ReplaceDialog.ReplaceText := FLastReplaceText;
  281. end;
  282. ExecuteFindDialogAllowingAltEnter(ReplaceDialog);
  283. end;
  284. procedure TMainFormFindReplaceHelper.DoReplaceDialogReplace;
  285. begin
  286. if not _StoreAndTestLastFindOptions(ReplaceDialog) then
  287. Exit;
  288. FLastReplaceText := ReplaceDialog.ReplaceText;
  289. var ReplaceMode := RegExToReplaceMode(FLastFindRegEx);
  290. if frReplaceAll in FLastFindOptions then begin
  291. var ReplaceCount := 0;
  292. FActiveMemo.BeginUndoAction;
  293. try
  294. var Pos := 0;
  295. var Range: TScintRange;
  296. while FActiveMemo.FindText(Pos, FActiveMemo.RawTextLength, FLastFindText,
  297. FindOptionsToSearchOptions(FLastFindOptions, FLastFindRegEx), Range) do begin
  298. var NewRange := FActiveMemo.ReplaceTextRange(Range.StartPos, Range.EndPos, FLastReplaceText, ReplaceMode);
  299. Pos := NewRange.EndPos;
  300. Inc(ReplaceCount);
  301. end;
  302. finally
  303. FActiveMemo.EndUndoAction;
  304. end;
  305. if ReplaceCount = 0 then
  306. MsgBoxFmt('Cannot find "%s"', [FLastFindText], SCompilerFormCaption,
  307. mbInformation, MB_OK)
  308. else
  309. MsgBoxFmt('%d occurrence(s) replaced.', [ReplaceCount], SCompilerFormCaption,
  310. mbInformation, MB_OK);
  311. end
  312. else begin
  313. if FActiveMemo.MainSelTextEquals(FLastFindText, FindOptionsToSearchOptions(frMatchCase in FLastFindOptions, FLastFindRegEx)) then begin
  314. { Note: the MainSelTextEquals above performs a search so the replacement
  315. below is safe even if the user just enabled regex }
  316. FActiveMemo.ReplaceMainSelText(FLastReplaceText, ReplaceMode);
  317. end;
  318. _FindNext(GetKeyState(VK_SHIFT) < 0);
  319. end;
  320. end;
  321. end.