diff --git a/source/searchreplace.lfm b/source/searchreplace.lfm index 6e6dfc1a..40362f3a 100644 --- a/source/searchreplace.lfm +++ b/source/searchreplace.lfm @@ -3,94 +3,109 @@ object frmSearchReplace: TfrmSearchReplace Height = 389 Top = 0 Width = 542 + AutoSize = True BorderIcons = [] Caption = 'Search and replace text' ClientHeight = 389 ClientWidth = 542 Color = clBtnFace - Constraints.MinHeight = 150 - Constraints.MinWidth = 250 + Constraints.MinHeight = 350 + Constraints.MinWidth = 500 DesignTimePPI = 120 Font.Color = clWindowText + Position = poOwnerFormCenter OnClose = FormClose OnCreate = FormCreate OnShow = FormShow - Position = poOwnerFormCenter object lblSearch: TLabel - AnchorSideTop.Control = comboSearchIn - AnchorSideTop.Side = asrBottom - Left = 10 + AnchorSideLeft.Control = lblSearchIn + AnchorSideTop.Control = comboSearch + AnchorSideTop.Side = asrCenter + Left = 6 Height = 20 - Top = 40 + Top = 44 Width = 78 - BorderSpacing.Around = 6 Caption = '&Text to find:' FocusControl = comboSearch end object lblReplaceHint: TLabel + AnchorSideLeft.Control = comboReplace AnchorSideTop.Control = comboReplace AnchorSideTop.Side = asrBottom - Left = 112 + Left = 109 Height = 20 - Top = 108 - Width = 324 - Anchors = [akTop, akLeft, akRight] - BorderSpacing.Around = 6 + Top = 102 + Width = 354 Caption = 'Replacement can have \n for new lines and \t for tabs' Enabled = False end object lblSearchIn: TLabel - AnchorSideTop.Control = Owner - Left = 10 + AnchorSideLeft.Control = Owner + AnchorSideTop.Control = comboSearchIn + AnchorSideTop.Side = asrCenter + Left = 6 Height = 20 - Top = 6 + Top = 10 Width = 63 - BorderSpacing.Around = 6 + BorderSpacing.Left = 6 Caption = 'Search in:' end object btnCancel: TButton - Left = 408 + AnchorSideRight.Control = Owner + AnchorSideRight.Side = asrBottom + AnchorSideBottom.Control = Owner + AnchorSideBottom.Side = asrBottom + Left = 411 Height = 31 - Top = 348 + Top = 352 Width = 125 Anchors = [akRight, akBottom] + BorderSpacing.Right = 6 + BorderSpacing.Bottom = 6 Cancel = True Caption = 'Cancel' ModalResult = 2 TabOrder = 12 end object btnReplaceAll: TButton - Left = 276 + AnchorSideRight.Control = btnCancel + AnchorSideBottom.Control = btnCancel + AnchorSideBottom.Side = asrCenter + Left = 280 Height = 31 - Top = 348 + Top = 351 Width = 125 Anchors = [akRight, akBottom] + BorderSpacing.Right = 6 Caption = 'Replace &all' ModalResult = 12 TabOrder = 11 OnClick = DoSearchReplace end object chkReplace: TCheckBox - AnchorSideTop.Control = comboSearch - AnchorSideTop.Side = asrBottom - Left = 10 + AnchorSideLeft.Control = lblSearch + AnchorSideTop.Control = comboReplace + AnchorSideTop.Side = asrCenter + Left = 6 Height = 24 - Top = 74 + Top = 76 Width = 77 - BorderSpacing.Around = 6 Caption = '&Replace:' TabOrder = 3 OnClick = chkReplaceClick end object comboSearch: TComboBox + AnchorSideLeft.Control = comboSearchIn AnchorSideTop.Control = comboSearchIn AnchorSideTop.Side = asrBottom - Left = 112 + AnchorSideRight.Control = btnSearchHints + Left = 109 Height = 28 Top = 40 - Width = 381 + Width = 390 Anchors = [akTop, akLeft, akRight] - BorderSpacing.Around = 6 + BorderSpacing.Top = 6 + BorderSpacing.Right = 6 ItemHeight = 20 TabOrder = 1 Text = 'comboSearch' @@ -98,14 +113,17 @@ object frmSearchReplace: TfrmSearchReplace OnExit = comboSearchReplaceExit end object comboReplace: TComboBox + AnchorSideLeft.Control = comboSearch AnchorSideTop.Control = comboSearch AnchorSideTop.Side = asrBottom - Left = 112 + AnchorSideRight.Control = btnReplaceHints + Left = 109 Height = 28 Top = 74 - Width = 381 + Width = 390 Anchors = [akTop, akLeft, akRight] - BorderSpacing.Around = 6 + BorderSpacing.Top = 6 + BorderSpacing.Right = 6 Enabled = False ItemHeight = 20 TabOrder = 4 @@ -114,40 +132,58 @@ object frmSearchReplace: TfrmSearchReplace OnExit = comboSearchReplaceExit end object grpOptions: TGroupBox + AnchorSideLeft.Control = Owner AnchorSideTop.Control = lblReplaceHint AnchorSideTop.Side = asrBottom - Left = 10 - Height = 86 - Top = 134 - Width = 522 + AnchorSideRight.Control = Owner + AnchorSideRight.Side = asrBottom + Left = 6 + Height = 91 + Top = 128 + Width = 530 Anchors = [akTop, akLeft, akRight] - BorderSpacing.Around = 6 + AutoSize = True + BorderSpacing.Left = 6 + BorderSpacing.Top = 6 + BorderSpacing.Right = 6 Caption = '&Options' - ClientHeight = 61 - ClientWidth = 518 + ClientHeight = 66 + ClientWidth = 526 ParentBackground = False TabOrder = 6 object chkCaseSensitive: TCheckBox - Left = 15 + AnchorSideLeft.Control = grpOptions + AnchorSideTop.Control = grpOptions + Left = 6 Height = 24 - Top = 0 + Top = 6 Width = 112 + BorderSpacing.Around = 6 Caption = 'Case sensitive' TabOrder = 0 end object chkWholeWords: TCheckBox - Left = 15 + AnchorSideLeft.Control = grpOptions + AnchorSideTop.Control = chkCaseSensitive + AnchorSideTop.Side = asrBottom + Left = 6 Height = 24 - Top = 29 + Top = 36 Width = 108 + BorderSpacing.Around = 6 Caption = 'Whole words' TabOrder = 1 end object chkRegularExpression: TCheckBox - Left = 230 + AnchorSideLeft.Control = chkCaseSensitive + AnchorSideLeft.Side = asrBottom + AnchorSideTop.Control = grpOptions + Left = 174 Height = 24 - Top = 0 + Top = 6 Width = 146 + BorderSpacing.Left = 50 + BorderSpacing.Around = 6 Caption = 'Regular expression' ParentShowHint = False ShowHint = True @@ -155,23 +191,33 @@ object frmSearchReplace: TfrmSearchReplace OnClick = ValidateControls end object chkPromptOnReplace: TCheckBox - Left = 230 + AnchorSideLeft.Control = chkRegularExpression + AnchorSideLeft.Side = asrCenter + AnchorSideTop.Control = chkRegularExpression + AnchorSideTop.Side = asrBottom + Left = 175 Height = 24 - Top = 29 + Top = 36 Width = 144 + BorderSpacing.Around = 6 Caption = 'Prompt on replace' TabOrder = 3 end end object grpDirection: TRadioGroup + AnchorSideLeft.Control = Owner AnchorSideTop.Control = grpOptions AnchorSideTop.Side = asrBottom - Left = 10 - Height = 114 - Top = 226 - Width = 150 + AnchorSideBottom.Control = btnOK + Left = 6 + Height = 119 + Top = 225 + Width = 170 Anchors = [akTop, akLeft, akBottom] AutoFill = True + BorderSpacing.Left = 6 + BorderSpacing.Top = 6 + BorderSpacing.Bottom = 6 Caption = '&Direction' ChildSizing.LeftRightSpacing = 6 ChildSizing.EnlargeHorizontal = crsHomogenousChildResize @@ -180,8 +226,8 @@ object frmSearchReplace: TfrmSearchReplace ChildSizing.ShrinkVertical = crsScaleChilds ChildSizing.Layout = cclLeftToRightThenTopToBottom ChildSizing.ControlsPerLine = 1 - ClientHeight = 89 - ClientWidth = 146 + ClientHeight = 94 + ClientWidth = 166 ItemIndex = 0 Items.Strings = ( '&Forward' @@ -191,14 +237,20 @@ object frmSearchReplace: TfrmSearchReplace TabOrder = 7 end object grpOrigin: TRadioGroup + AnchorSideLeft.Control = grpDirection + AnchorSideLeft.Side = asrBottom AnchorSideTop.Control = grpOptions AnchorSideTop.Side = asrBottom - Left = 168 - Height = 114 - Top = 226 - Width = 150 + AnchorSideBottom.Control = btnOK + Left = 182 + Height = 119 + Top = 225 + Width = 170 Anchors = [akTop, akLeft, akBottom] AutoFill = True + BorderSpacing.Left = 6 + BorderSpacing.Top = 6 + BorderSpacing.Bottom = 6 Caption = 'Or&igin' ChildSizing.LeftRightSpacing = 6 ChildSizing.EnlargeHorizontal = crsHomogenousChildResize @@ -207,8 +259,8 @@ object frmSearchReplace: TfrmSearchReplace ChildSizing.ShrinkVertical = crsScaleChilds ChildSizing.Layout = cclLeftToRightThenTopToBottom ChildSizing.ControlsPerLine = 1 - ClientHeight = 89 - ClientWidth = 146 + ClientHeight = 94 + ClientWidth = 166 ItemIndex = 1 Items.Strings = ( 'From cursor' @@ -218,14 +270,23 @@ object frmSearchReplace: TfrmSearchReplace TabOrder = 8 end object grpScope: TRadioGroup + AnchorSideLeft.Control = grpOrigin + AnchorSideLeft.Side = asrBottom AnchorSideTop.Control = grpOptions AnchorSideTop.Side = asrBottom - Left = 325 - Height = 114 - Top = 226 - Width = 207 + AnchorSideRight.Control = Owner + AnchorSideRight.Side = asrBottom + AnchorSideBottom.Control = btnOK + Left = 358 + Height = 119 + Top = 225 + Width = 178 Anchors = [akTop, akLeft, akRight, akBottom] AutoFill = True + BorderSpacing.Left = 6 + BorderSpacing.Top = 6 + BorderSpacing.Right = 6 + BorderSpacing.Bottom = 6 Caption = '&Scope' ChildSizing.LeftRightSpacing = 6 ChildSizing.EnlargeHorizontal = crsHomogenousChildResize @@ -234,8 +295,8 @@ object frmSearchReplace: TfrmSearchReplace ChildSizing.ShrinkVertical = crsScaleChilds ChildSizing.Layout = cclLeftToRightThenTopToBottom ChildSizing.ControlsPerLine = 1 - ClientHeight = 89 - ClientWidth = 203 + ClientHeight = 94 + ClientWidth = 174 ItemIndex = 0 Items.Strings = ( 'Global' @@ -245,11 +306,15 @@ object frmSearchReplace: TfrmSearchReplace TabOrder = 9 end object btnOK: TButton - Left = 143 + AnchorSideRight.Control = btnReplaceAll + AnchorSideBottom.Control = btnReplaceAll + AnchorSideBottom.Side = asrCenter + Left = 149 Height = 31 - Top = 348 + Top = 350 Width = 125 Anchors = [akRight, akBottom] + BorderSpacing.Right = 6 Caption = 'OK' Default = True ModalResult = 1 @@ -257,39 +322,49 @@ object frmSearchReplace: TfrmSearchReplace OnClick = DoSearchReplace end object comboSearchIn: TComboBox + AnchorSideLeft.Control = lblSearchIn + AnchorSideLeft.Side = asrBottom AnchorSideTop.Control = Owner - Left = 112 + AnchorSideRight.Control = Owner + AnchorSideRight.Side = asrBottom + Left = 109 Height = 28 Top = 6 - Width = 420 + Width = 427 Anchors = [akTop, akLeft, akRight] - BorderSpacing.Around = 6 + BorderSpacing.Left = 40 + BorderSpacing.Top = 6 + BorderSpacing.Right = 6 ItemHeight = 20 Style = csDropDownList TabOrder = 0 end object btnSearchHints: TButton - AnchorSideTop.Control = comboSearchIn - AnchorSideTop.Side = asrBottom - Left = 501 + AnchorSideTop.Control = comboSearch + AnchorSideTop.Side = asrCenter + AnchorSideRight.Control = Owner + AnchorSideRight.Side = asrBottom + Left = 505 Height = 31 - Top = 40 + Top = 39 Width = 31 Anchors = [akTop, akRight] - BorderSpacing.Around = 6 + BorderSpacing.Right = 6 Caption = '⏷' TabOrder = 2 OnClick = btnWithDropDownClick end object btnReplaceHints: TButton - AnchorSideTop.Control = comboSearch - AnchorSideTop.Side = asrBottom - Left = 501 + AnchorSideTop.Control = comboReplace + AnchorSideTop.Side = asrCenter + AnchorSideRight.Control = Owner + AnchorSideRight.Side = asrBottom + Left = 505 Height = 31 - Top = 74 + Top = 73 Width = 31 Anchors = [akTop, akRight] - BorderSpacing.Around = 6 + BorderSpacing.Right = 6 Caption = '⏷' Enabled = False TabOrder = 5 diff --git a/source/searchreplace.pas b/source/searchreplace.pas index f0896c2b..e5794e9b 100644 --- a/source/searchreplace.pas +++ b/source/searchreplace.pas @@ -1,531 +1,531 @@ -unit searchreplace; - -{$mode delphi}{$H+} - -interface - -uses - SysUtils, Classes, Controls, Forms, Dialogs, StdCtrls, - ExtCtrls, SynEdit, SynEditTypes, laz.VirtualTrees, RegExpr, - SynEditMiscClasses, SynEditSearch, extra_controls, - Menus, texteditor; - -type - TfrmSearchReplace = class(TExtForm) - btnCancel: TButton; - btnReplaceAll: TButton; - lblSearch: TLabel; - chkReplace: TCheckBox; - comboSearch: TComboBox; - comboReplace: TComboBox; - grpOptions: TGroupBox; - chkCaseSensitive: TCheckBox; - chkWholeWords: TCheckBox; - chkRegularExpression: TCheckBox; - chkPromptOnReplace: TCheckBox; - grpDirection: TRadioGroup; - grpOrigin: TRadioGroup; - grpScope: TRadioGroup; - btnOK: TButton; - lblReplaceHint: TLabel; - lblSearchIn: TLabel; - comboSearchIn: TComboBox; - btnSearchHints: TButton; - btnReplaceHints: TButton; - popupSearchHints: TPopupMenu; - popupReplaceHints: TPopupMenu; - procedure ValidateControls(Sender: TObject); - procedure chkReplaceClick(Sender: TObject); - procedure FormShow(Sender: TObject); - procedure comboSearchReplaceExit(Sender: TObject); - procedure FormCreate(Sender: TObject); - procedure DoSearchReplace(Sender: TObject); - procedure btnWithDropDownClick(Sender: TObject); - procedure menuHintClick(Sender: TObject); - procedure FormClose(Sender: TObject; var Action: TCloseAction); - private - { Private declarations } - procedure DoSearchReplaceText; - procedure DoSearchReplaceData; - function GetEditor: TSynEdit; - function GetGrid: TLazVirtualStringTree; - public - { Public declarations } - Options: TSynSearchOptions; - property Editor: TSynEdit read GetEditor; - property Grid: TLazVirtualStringTree read GetGrid; - end; - - -implementation - -{$R *.lfm} - -uses apphelpers, main; - - -procedure TfrmSearchReplace.FormCreate(Sender: TObject); - function AddItem(Menu: TPopupMenu; Code, Description, Example: String; IsRegEx: Boolean): TMenuItem; - begin - Result := TMenuItem.Create(Menu); - Result.Caption := Code + ' ' + _(Description); - Result.Hint := Example; - Result.OnClick := menuHintClick; - Result.Tag := Integer(IsRegEx); - Menu.Items.Add(Result); - end; -begin - HasSizeGrip := True; - comboSearch.Text := ''; - comboReplace.Text := ''; - - AddItem(popupSearchHints, '^', 'Start of line', '', True); - AddItem(popupSearchHints, '$', 'End of line', '', True); - AddItem(popupSearchHints, '.', 'Any character', '', True); - AddItem(popupSearchHints, '\w', 'Any word char', 'a-z A-Z 0-9 and _', True); - AddItem(popupSearchHints, '\W', 'Any non-word char', '', True); - AddItem(popupSearchHints, '\d', 'Digit', '0..9', True); - AddItem(popupSearchHints, '\D', 'Any char except digits', '', True); - AddItem(popupSearchHints, '\s', 'Whitespace', 'space, tab, carriage return, line feed, or form feed', True); - AddItem(popupSearchHints, '\S', 'Any char except whitespaces', '', True); - - AddItem(popupReplaceHints, '\n', 'New line', '', False); - AddItem(popupReplaceHints, '\t', 'Tabulator', '', False); - AddItem(popupReplaceHints, '$0', 'Entire matched text', '', True); - AddItem(popupReplaceHints, '$1', 'Text from first captured group', '', True); - AddItem(popupReplaceHints, '$2', 'Text from second captured group', '', True); - AddItem(popupReplaceHints, '$3', 'Text from third captured group', '', True); - AddItem(popupReplaceHints, '\l', 'Lowercase following char', 'aBCD', True); - AddItem(popupReplaceHints, '\L', 'Lowercase following block', 'abcd', True); - AddItem(popupReplaceHints, '\u', 'Uppercase following char', 'Abcd', True); - AddItem(popupReplaceHints, '\U', 'Uppercase following block', 'ABCD', True); - AddItem(popupReplaceHints, '\x', 'Hex code follows', '\x85', True); - -end; - - -procedure TfrmSearchReplace.FormShow(Sender: TObject); -var - SearchText, ItemLabel: String; - QueryMemo, AnySynMemo, UsedSynMemo: TSynEdit; - ResultGrid: TLazVirtualStringTree; - QueryTabOpen, IsGridTextEditor, IsEditorWritable: Boolean; - ActiveQueryTab: TQueryTab; -begin - // Populate "Search in" pulldown with grid and editor - comboSearchIn.Items.Clear; - ActiveQueryTab := MainForm.QueryTabs.ActiveTab; - QueryTabOpen := Assigned(ActiveQueryTab); - SearchText := ''; - UsedSynMemo := nil; - - QueryMemo := MainForm.QueryTabs.ActiveMemo; - AnySynMemo := MainForm.ActiveSynMemo(True); - if Assigned(AnySynMemo) then begin - IsEditorWritable := not AnySynMemo.ReadOnly; // Support views and procedure editors - IsGridTextEditor := GetParentForm(AnySynMemo) is TfrmTextEditor; // Support grid text editor, read-only or not - if IsEditorWritable or IsGridTextEditor then - UsedSynMemo := AnySynMemo; - end; - if not Assigned(UsedSynMemo) then begin - UsedSynMemo := QueryMemo; - end; - if Assigned(UsedSynMemo) then begin - if UsedSynMemo = QueryMemo then - ItemLabel := _('SQL editor') + ': ' + ActiveQueryTab.TabSheet.Caption - else - ItemLabel := GetParentForm(UsedSynMemo).Caption; - comboSearchIn.Items.AddObject(ItemLabel, UsedSynMemo); - if UsedSynMemo.Focused then - comboSearchIn.ItemIndex := comboSearchIn.Items.Count-1; - if UsedSynMemo.SelAvail then - SearchText := UsedSynMemo.SelText - else - SearchText := UsedSynMemo.GetWordAtRowCol(UsedSynMemo.CaretXY); - end; - - ResultGrid := MainForm.ActiveGrid; - if Assigned(ResultGrid) then begin - if QueryTabOpen then - ItemLabel := _('Result grid')+': '+ActiveQueryTab.tabsetQuery.Tabs[ActiveQueryTab.tabsetQuery.TabIndex] - else - ItemLabel := _('Data Grid'); - comboSearchIn.Items.AddObject(ItemLabel, ResultGrid); - if ResultGrid.Focused then - comboSearchIn.ItemIndex := comboSearchIn.Items.Count-1; - if Assigned(ResultGrid.FocusedNode) then - SearchText := ResultGrid.Text[ResultGrid.FocusedNode, ResultGrid.FocusedColumn]; - end; - - if (comboSearchIn.ItemIndex = -1) and (comboSearchIn.Items.Count > 0) then begin - comboSearchIn.ItemIndex := 0; - end; - - comboSearch.Items.Text := AppSettings.ReadString(asFindDialogSearchHistory); - comboReplace.Items.Text := AppSettings.ReadString(asFindDialogReplaceHistory); - // Prefill search editor with selected text - if SearchText <> '' then - comboSearch.Text := SearchText - else if comboSearch.Items.Count > 0 then - comboSearch.Text := comboSearch.Items[0]; - if comboReplace.Items.Count > 0 then - comboReplace.Text := comboReplace.Items[0]; - - ValidateControls(Sender); - comboSearch.SetFocus; -end; - - -procedure TfrmSearchReplace.FormClose(Sender: TObject; - var Action: TCloseAction); -begin - AppSettings.WriteString(asFindDialogSearchHistory, comboSearch.Items.Text); - AppSettings.WriteString(asFindDialogReplaceHistory, comboReplace.Items.Text); -end; - - -function TfrmSearchReplace.GetEditor: TSynEdit; -begin - // Return selected target object as editor - Result := nil; - if (comboSearchIn.ItemIndex > -1) and (comboSearchIn.Items.Objects[comboSearchIn.ItemIndex] is TSynEdit) then - Result := comboSearchIn.Items.Objects[comboSearchIn.ItemIndex] as TSynEdit; -end; - - -function TfrmSearchReplace.GetGrid: TLazVirtualStringTree; -var - o: TObject; -begin - // Return selected target object as grid - Result := nil; - if comboSearchIn.ItemIndex > -1 then begin - o := comboSearchIn.Items.Objects[comboSearchIn.ItemIndex]; - if (o <> nil) and (o is TLazVirtualStringTree) then - Result := o as TLazVirtualStringTree; - end; -end; - - -procedure TfrmSearchReplace.menuHintClick(Sender: TObject); -var - Item: TMenuItem; - Code: String; - Combo: TComboBox; -begin - // Search or replace hint menu item clicked - Item := Sender as TMenuItem; - Code := RegExprGetMatch('^(\S+)', StripHotkey(Item.Caption), 1); - if Item.GetParentMenu = popupSearchHints then - Combo := comboSearch - else - Combo := comboReplace; - // Do not overwrite user's text - Combo.SelLength := 0; - Combo.SelText := Code; - // Be sure to support regular expression if menu item is a regex pattern - if (Item.Tag = 1) and (not chkRegularExpression.Checked) then - chkRegularExpression.Checked := True; -end; - - -procedure TfrmSearchReplace.btnWithDropDownClick(Sender: TObject); -var - Menu: TPopupMenu; -begin - if Sender = btnSearchHints then - Menu := popupSearchHints - else - Menu := popupReplaceHints; - ShowPopup(Sender as TControl, Menu); -end; - - -procedure TfrmSearchReplace.chkReplaceClick(Sender: TObject); -begin - // Jump to replace editor - ValidateControls(Sender); - if comboReplace.Enabled then - ActiveControl := comboReplace; -end; - - -procedure TfrmSearchReplace.ValidateControls(Sender: TObject); -begin - // Enable or disable various controls - comboReplace.Enabled := chkReplace.Checked; - btnReplaceHints.Enabled := chkReplace.Checked; - chkPromptOnReplace.Enabled := chkReplace.Checked; - btnReplaceAll.Enabled := chkReplace.Checked; - lblReplaceHint.Enabled := chkReplace.Checked; - if chkReplace.Checked then - btnOK.Caption := _('Replace') - else - btnOK.Caption := _('Find'); -end; - - -procedure TfrmSearchReplace.comboSearchReplaceExit(Sender: TObject); -var - Combo: TComboBox; - i, idx: Integer; -begin - // Store search or replace text history - Combo := Sender as TComboBox; - if Combo.Text = '' then - Exit; - idx := -1; - for i:=0 to Combo.Items.Count-1 do begin - if Combo.Items[i] = Combo.Text then begin - idx := i; - break; - end; - end; - if idx > -1 then - Combo.Items.Move(idx, 0) - else - Combo.Items.Insert(0, Combo.Text); - Combo.Text := Combo.Items[0]; -end; - - -procedure TfrmSearchReplace.DoSearchReplace(Sender: TObject); -begin - // Set SynEditSearch options once when user hits the dialog button, - // not when "Find again" action was used - if (Sender = btnOK) or (Sender = btnReplaceAll) then begin - Options := []; - if chkReplace.Checked then Include(Options, ssoReplace); - if chkCaseSensitive.Checked then Include(Options, ssoMatchCase); - if chkWholeWords.Checked then Include(Options, ssoWholeWord); - if chkPromptOnReplace.Checked and chkPromptOnReplace.Enabled then Include(Options, ssoPrompt); - if grpDirection.ItemIndex = 1 then Include(Options, ssoBackwards); - if grpOrigin.ItemIndex = 1 then Include(Options, ssoEntireScope); - if grpScope.ItemIndex = 1 then Include(Options, ssoSelectedOnly); - if ModalResult = mrAll then Include(Options, ssoReplaceAll); - - // Work around multi line bug in SynEdit - if (ssoReplaceAll in Options) and (Pos('\n', comboReplace.Text) > 0) then - Include(Options, ssoBackwards); - end; - - if Assigned(Editor) then - DoSearchReplaceText - else if Assigned(Grid) then - DoSearchReplaceData - else - ErrorDialog(_('No area selected')); -end; - - -procedure TfrmSearchReplace.DoSearchReplaceText; -var - Occurences: Integer; - OldCaretXY: TPoint; - Replacement: String; -begin - {if chkRegularExpression.Checked then - Editor.SearchEngine := SynEditRegexSearch1 - else - Editor.SearchEngine := SynEditSearch1;} - - OldCaretXY := Editor.CaretXY; - Replacement := comboReplace.Text; - Replacement := StringReplace(Replacement, '\n', CRLF, [rfReplaceAll]); - Replacement := StringReplace(Replacement, '\t', #9, [rfReplaceAll]); - - Editor.BeginUpdate; - - MainForm.ShowStatusMsg(_('Searching ...')); - Occurences := -1; // So we can test whether an exception happened - try - Occurences := Editor.SearchReplace( - comboSearch.Text, - Replacement, - Options - ); - except - on E:Exception do begin - ErrorDialog(E.ClassName + ': ' + E.Message); - ModalResult := mrNone; - end; - end; - - Editor.EndUpdate; - MainForm.ShowStatusMsg; - - if Occurences > -1 then begin - if ssoReplaceAll in Options then begin - MessageDialog(f_('Text "%s" replaced %s times.', [comboSearch.Text, FormatNumber(Occurences)]), mtInformation, [mbOk]); - if Occurences = 0 then - ModalResult := mrNone; - end else begin - if (OldCaretXY.X = Editor.CaretX) and - (OldCaretXY.Y = Editor.CaretY) then begin - MessageDialog(f_('Text "%s" not found.', [comboSearch.Text]), mtInformation, [mbOk]); - ModalResult := mrNone; - end; - end; - end; -end; - - -procedure TfrmSearchReplace.DoSearchReplaceData; -var - Search, Replacement, CellText: String; - Node: PVirtualNode; - Column, StartAtCol: TColumnIndex; - Match, SelectedOnly, Backwards, UseRegEx: Boolean; - MatchCount, ReplaceCount: Integer; - rx: TRegExpr; - Prompt: TModalResult; - ReplaceFlags: TReplaceFlags; - NodeSelected: Boolean; -begin - // Data grid version of DoSearchReplaceText - MainForm.ShowStatusMsg(_('Searching ...')); - - Search := comboSearch.Text; - Replacement := comboReplace.Text; - Replacement := StringReplace(Replacement, '\n', CRLF, [rfReplaceAll]); - Replacement := StringReplace(Replacement, '\t', #9, [rfReplaceAll]); - SelectedOnly := ssoSelectedOnly in Options; - Backwards := ssoBackwards in Options; - if ssoReplaceAll in Options then - Prompt := mrYesToAll - else - Prompt := mrYes; - Match := False; - MatchCount := 0; - ReplaceCount := 0; - ReplaceFlags := [rfReplaceAll]; - if not (ssoMatchCase in Options) then - Include(ReplaceFlags, rfIgnoreCase); - NodeSelected := True; - - // Init regular expression - rx := TRegExpr.Create; - UseRegEx := (ssoWholeWord in Options) or chkRegularExpression.Checked; - if chkRegularExpression.Checked then - rx.Expression := Search - else // Still used for "whole word" search - rx.Expression := '\b'+QuoteRegExprMetaChars(Search)+'\b'; - rx.ModifierI := not (ssoMatchCase in Options); - - // Set start row and column with regard to "Entire scope" and "Forward/Backward" mode - if (ssoEntireScope in Options) or (not Assigned(Grid.FocusedNode)) then begin - if Backwards then - Node := GetPreviousNode(Grid, nil, SelectedOnly) - else - Node := GetNextNode(Grid, nil, SelectedOnly); - StartAtCol := InvalidColumn; - end else begin - Node := Grid.FocusedNode; - if Backwards then - StartAtCol := Grid.Header.Columns.GetPreviousVisibleColumn(Grid.FocusedColumn, True) - else - StartAtCol := Grid.Header.Columns.GetNextVisibleColumn(Grid.FocusedColumn, True); - // Advance to next row if focused column is the very last column - if StartAtCol = InvalidColumn then begin - if Backwards then - Node := GetPreviousNode(Grid, Node, SelectedOnly) - else - Node := GetNextNode(Grid, Node, SelectedOnly); - end; - end; - - while Assigned(Node) do begin - MainForm.AnyGridEnsureFullRow(Grid, Node); - - // Find the first column - if StartAtCol > InvalidColumn then begin - Column := StartAtCol; - StartAtCol := InvalidColumn; - end else begin - if Backwards then - Column := Grid.Header.Columns.GetLastVisibleColumn(True) - else - Column := Grid.Header.Columns.GetFirstVisibleColumn(True); - end; - - while Column >= 0 do begin - CellText := Grid.Text[Node, Column]; - - if UseRegEx then begin - Match := rx.Exec(CellText); - end else begin - if ssoMatchCase in Options then - Match := Pos(Search, CellText) > 0 - else - Match := Pos(LowerCase(Search), LowerCase(CellText)) > 0; - end; - - if Match then begin - Inc(MatchCount); - - // Set focus on node and column - NodeSelected := SelectNode(Grid, Node, not SelectedOnly); - if not NodeSelected then - Break; - Grid.FocusedColumn := Column; - - // Replace logic - if ssoReplace in Options then begin - if ssoPrompt in Options then begin - // Ask user - Prompt := MessageDialog(f_('Replace this occurrence of "%s"?', [Search]), - StrEllipsis(CellText, 500), - mtConfirmation, - [mbYes, mbYesToAll, mbNo, mbCancel]); - case Prompt of - mrYesToAll: begin - Exclude(Options, ssoPrompt); - Include(Options, ssoReplaceAll); - end; - mrCancel: Exclude(Options, ssoReplaceAll); - end; - end; - if Prompt in [mrYes, mrYesToAll] then begin - if UseRegEx then - Grid.Text[Node, Column] := rx.Replace(CellText, Replacement, chkRegularExpression.Checked) - else - Grid.Text[Node, Column] := StringReplace(CellText, Search, Replacement, ReplaceFlags); - Inc(ReplaceCount); - end; - end; - - if not (ssoReplaceAll in Options) then - Break; - end; - - // Advance to next column - if Backwards then - Column := Grid.Header.Columns.GetPreviousVisibleColumn(Column, True) - else - Column := Grid.Header.Columns.GetNextVisibleColumn(Column, True); - end; - - if Match and (not (ssoReplaceAll in Options)) then - Break; - if not NodeSelected then - Break; - - if Backwards then - Node := GetPreviousNode(Grid, Node, SelectedOnly) - else - Node := GetNextNode(Grid, Node, SelectedOnly); - end; - - if (ssoReplaceAll in Options) and (MatchCount > 0) then begin - MessageDialog(f_('Text "%s" %d times replaced.', [Search, ReplaceCount]), mtInformation, [mbOk]) - end; - if MatchCount = 0 then begin - MessageDialog(f_('Text "%s" not found.', [Search]), mtInformation, [mbOk]); - ModalResult := mrNone; - end; - - MainForm.ShowStatusMsg; -end; - - -end. +unit searchreplace; + +{$mode delphi}{$H+} + +interface + +uses + SysUtils, Classes, Controls, Forms, Dialogs, StdCtrls, + ExtCtrls, SynEdit, SynEditTypes, laz.VirtualTrees, RegExpr, + SynEditMiscClasses, SynEditSearch, extra_controls, + Menus, texteditor; + +type + TfrmSearchReplace = class(TExtForm) + btnCancel: TButton; + btnReplaceAll: TButton; + lblSearch: TLabel; + chkReplace: TCheckBox; + comboSearch: TComboBox; + comboReplace: TComboBox; + grpOptions: TGroupBox; + chkCaseSensitive: TCheckBox; + chkWholeWords: TCheckBox; + chkRegularExpression: TCheckBox; + chkPromptOnReplace: TCheckBox; + grpDirection: TRadioGroup; + grpOrigin: TRadioGroup; + grpScope: TRadioGroup; + btnOK: TButton; + lblReplaceHint: TLabel; + lblSearchIn: TLabel; + comboSearchIn: TComboBox; + btnSearchHints: TButton; + btnReplaceHints: TButton; + popupSearchHints: TPopupMenu; + popupReplaceHints: TPopupMenu; + procedure ValidateControls(Sender: TObject); + procedure chkReplaceClick(Sender: TObject); + procedure FormShow(Sender: TObject); + procedure comboSearchReplaceExit(Sender: TObject); + procedure FormCreate(Sender: TObject); + procedure DoSearchReplace(Sender: TObject); + procedure btnWithDropDownClick(Sender: TObject); + procedure menuHintClick(Sender: TObject); + procedure FormClose(Sender: TObject; var Action: TCloseAction); + private + { Private declarations } + procedure DoSearchReplaceText; + procedure DoSearchReplaceData; + function GetEditor: TSynEdit; + function GetGrid: TLazVirtualStringTree; + public + { Public declarations } + Options: TSynSearchOptions; + property Editor: TSynEdit read GetEditor; + property Grid: TLazVirtualStringTree read GetGrid; + end; + + +implementation + +{$R *.lfm} + +uses apphelpers, main; + + +procedure TfrmSearchReplace.FormCreate(Sender: TObject); + function AddItem(Menu: TPopupMenu; Code, Description, Example: String; IsRegEx: Boolean): TMenuItem; + begin + Result := TMenuItem.Create(Menu); + Result.Caption := Code + ' ' + _(Description); + Result.Hint := Example; + Result.OnClick := menuHintClick; + Result.Tag := Integer(IsRegEx); + Menu.Items.Add(Result); + end; +begin + HasSizeGrip := True; + comboSearch.Text := ''; + comboReplace.Text := ''; + + AddItem(popupSearchHints, '^', 'Start of line', '', True); + AddItem(popupSearchHints, '$', 'End of line', '', True); + AddItem(popupSearchHints, '.', 'Any character', '', True); + AddItem(popupSearchHints, '\w', 'Any word char', 'a-z A-Z 0-9 and _', True); + AddItem(popupSearchHints, '\W', 'Any non-word char', '', True); + AddItem(popupSearchHints, '\d', 'Digit', '0..9', True); + AddItem(popupSearchHints, '\D', 'Any char except digits', '', True); + AddItem(popupSearchHints, '\s', 'Whitespace', 'space, tab, carriage return, line feed, or form feed', True); + AddItem(popupSearchHints, '\S', 'Any char except whitespaces', '', True); + + AddItem(popupReplaceHints, '\n', 'New line', '', False); + AddItem(popupReplaceHints, '\t', 'Tabulator', '', False); + AddItem(popupReplaceHints, '$0', 'Entire matched text', '', True); + AddItem(popupReplaceHints, '$1', 'Text from first captured group', '', True); + AddItem(popupReplaceHints, '$2', 'Text from second captured group', '', True); + AddItem(popupReplaceHints, '$3', 'Text from third captured group', '', True); + AddItem(popupReplaceHints, '\l', 'Lowercase following char', 'aBCD', True); + AddItem(popupReplaceHints, '\L', 'Lowercase following block', 'abcd', True); + AddItem(popupReplaceHints, '\u', 'Uppercase following char', 'Abcd', True); + AddItem(popupReplaceHints, '\U', 'Uppercase following block', 'ABCD', True); + AddItem(popupReplaceHints, '\x', 'Hex code follows', '\x85', True); + +end; + + +procedure TfrmSearchReplace.FormShow(Sender: TObject); +var + SearchText, ItemLabel: String; + QueryMemo, AnySynMemo, UsedSynMemo: TSynEdit; + ResultGrid: TLazVirtualStringTree; + QueryTabOpen, IsGridTextEditor, IsEditorWritable: Boolean; + ActiveQueryTab: TQueryTab; +begin + // Populate "Search in" pulldown with grid and editor + comboSearchIn.Items.Clear; + ActiveQueryTab := MainForm.QueryTabs.ActiveTab; + QueryTabOpen := Assigned(ActiveQueryTab); + SearchText := ''; + UsedSynMemo := nil; + + QueryMemo := MainForm.QueryTabs.ActiveMemo; + AnySynMemo := MainForm.ActiveSynMemo(True); + if Assigned(AnySynMemo) then begin + IsEditorWritable := not AnySynMemo.ReadOnly; // Support views and procedure editors + IsGridTextEditor := GetParentForm(AnySynMemo) is TfrmTextEditor; // Support grid text editor, read-only or not + if IsEditorWritable or IsGridTextEditor then + UsedSynMemo := AnySynMemo; + end; + if not Assigned(UsedSynMemo) then begin + UsedSynMemo := QueryMemo; + end; + if Assigned(UsedSynMemo) then begin + if UsedSynMemo = QueryMemo then + ItemLabel := _('SQL editor') + ': ' + ActiveQueryTab.TabSheet.Caption + else + ItemLabel := GetParentForm(UsedSynMemo).Caption; + comboSearchIn.Items.AddObject(ItemLabel, UsedSynMemo); + if UsedSynMemo.Focused then + comboSearchIn.ItemIndex := comboSearchIn.Items.Count-1; + if UsedSynMemo.SelAvail then + SearchText := UsedSynMemo.SelText + else + SearchText := UsedSynMemo.GetWordAtRowCol(UsedSynMemo.CaretXY); + end; + + ResultGrid := MainForm.ActiveGrid; + if Assigned(ResultGrid) then begin + if QueryTabOpen then + ItemLabel := _('Result grid')+': '+ActiveQueryTab.tabsetQuery.Tabs[ActiveQueryTab.tabsetQuery.TabIndex] + else + ItemLabel := _('Data Grid'); + comboSearchIn.Items.AddObject(ItemLabel, ResultGrid); + if ResultGrid.Focused then + comboSearchIn.ItemIndex := comboSearchIn.Items.Count-1; + if Assigned(ResultGrid.FocusedNode) then + SearchText := ResultGrid.Text[ResultGrid.FocusedNode, ResultGrid.FocusedColumn]; + end; + + if (comboSearchIn.ItemIndex = -1) and (comboSearchIn.Items.Count > 0) then begin + comboSearchIn.ItemIndex := 0; + end; + + comboSearch.Items.Text := AppSettings.ReadString(asFindDialogSearchHistory); + comboReplace.Items.Text := AppSettings.ReadString(asFindDialogReplaceHistory); + // Prefill search editor with selected text + if SearchText <> '' then + comboSearch.Text := SearchText + else if comboSearch.Items.Count > 0 then + comboSearch.Text := comboSearch.Items[0]; + if comboReplace.Items.Count > 0 then + comboReplace.Text := comboReplace.Items[0]; + + ValidateControls(Sender); + comboSearch.SetFocus; +end; + + +procedure TfrmSearchReplace.FormClose(Sender: TObject; + var Action: TCloseAction); +begin + AppSettings.WriteString(asFindDialogSearchHistory, comboSearch.Items.Text); + AppSettings.WriteString(asFindDialogReplaceHistory, comboReplace.Items.Text); +end; + + +function TfrmSearchReplace.GetEditor: TSynEdit; +begin + // Return selected target object as editor + Result := nil; + if (comboSearchIn.ItemIndex > -1) and (comboSearchIn.Items.Objects[comboSearchIn.ItemIndex] is TSynEdit) then + Result := comboSearchIn.Items.Objects[comboSearchIn.ItemIndex] as TSynEdit; +end; + + +function TfrmSearchReplace.GetGrid: TLazVirtualStringTree; +var + o: TObject; +begin + // Return selected target object as grid + Result := nil; + if comboSearchIn.ItemIndex > -1 then begin + o := comboSearchIn.Items.Objects[comboSearchIn.ItemIndex]; + if (o <> nil) and (o is TLazVirtualStringTree) then + Result := o as TLazVirtualStringTree; + end; +end; + + +procedure TfrmSearchReplace.menuHintClick(Sender: TObject); +var + Item: TMenuItem; + Code: String; + Combo: TComboBox; +begin + // Search or replace hint menu item clicked + Item := Sender as TMenuItem; + Code := RegExprGetMatch('^(\S+)', StripHotkey(Item.Caption), 1); + if Item.GetParentMenu = popupSearchHints then + Combo := comboSearch + else + Combo := comboReplace; + // Do not overwrite user's text + Combo.SelLength := 0; + Combo.SelText := Code; + // Be sure to support regular expression if menu item is a regex pattern + if (Item.Tag = 1) and (not chkRegularExpression.Checked) then + chkRegularExpression.Checked := True; +end; + + +procedure TfrmSearchReplace.btnWithDropDownClick(Sender: TObject); +var + Menu: TPopupMenu; +begin + if Sender = btnSearchHints then + Menu := popupSearchHints + else + Menu := popupReplaceHints; + ShowPopup(Sender as TControl, Menu); +end; + + +procedure TfrmSearchReplace.chkReplaceClick(Sender: TObject); +begin + // Jump to replace editor + ValidateControls(Sender); + if comboReplace.Enabled then + ActiveControl := comboReplace; +end; + + +procedure TfrmSearchReplace.ValidateControls(Sender: TObject); +begin + // Enable or disable various controls + comboReplace.Enabled := chkReplace.Checked; + btnReplaceHints.Enabled := chkReplace.Checked; + chkPromptOnReplace.Enabled := chkReplace.Checked; + btnReplaceAll.Enabled := chkReplace.Checked; + lblReplaceHint.Enabled := chkReplace.Checked; + if chkReplace.Checked then + btnOK.Caption := _('Replace') + else + btnOK.Caption := _('Find'); +end; + + +procedure TfrmSearchReplace.comboSearchReplaceExit(Sender: TObject); +var + Combo: TComboBox; + i, idx: Integer; +begin + // Store search or replace text history + Combo := Sender as TComboBox; + if Combo.Text = '' then + Exit; + idx := -1; + for i:=0 to Combo.Items.Count-1 do begin + if Combo.Items[i] = Combo.Text then begin + idx := i; + break; + end; + end; + if idx > -1 then + Combo.Items.Move(idx, 0) + else + Combo.Items.Insert(0, Combo.Text); + Combo.Text := Combo.Items[0]; +end; + + +procedure TfrmSearchReplace.DoSearchReplace(Sender: TObject); +begin + // Set SynEditSearch options once when user hits the dialog button, + // not when "Find again" action was used + if (Sender = btnOK) or (Sender = btnReplaceAll) then begin + Options := []; + if chkReplace.Checked then Include(Options, ssoReplace); + if chkCaseSensitive.Checked then Include(Options, ssoMatchCase); + if chkWholeWords.Checked then Include(Options, ssoWholeWord); + if chkPromptOnReplace.Checked and chkPromptOnReplace.Enabled then Include(Options, ssoPrompt); + if grpDirection.ItemIndex = 1 then Include(Options, ssoBackwards); + if grpOrigin.ItemIndex = 1 then Include(Options, ssoEntireScope); + if grpScope.ItemIndex = 1 then Include(Options, ssoSelectedOnly); + if ModalResult = mrAll then Include(Options, ssoReplaceAll); + + // Work around multi line bug in SynEdit + if (ssoReplaceAll in Options) and (Pos('\n', comboReplace.Text) > 0) then + Include(Options, ssoBackwards); + end; + + if Assigned(Editor) then + DoSearchReplaceText + else if Assigned(Grid) then + DoSearchReplaceData + else + ErrorDialog(_('No area selected')); +end; + + +procedure TfrmSearchReplace.DoSearchReplaceText; +var + Occurences: Integer; + OldCaretXY: TPoint; + Replacement: String; +begin + {if chkRegularExpression.Checked then + Editor.SearchEngine := SynEditRegexSearch1 + else + Editor.SearchEngine := SynEditSearch1;} + + OldCaretXY := Editor.CaretXY; + Replacement := comboReplace.Text; + Replacement := StringReplace(Replacement, '\n', CRLF, [rfReplaceAll]); + Replacement := StringReplace(Replacement, '\t', #9, [rfReplaceAll]); + + Editor.BeginUpdate; + + MainForm.ShowStatusMsg(_('Searching ...')); + Occurences := -1; // So we can test whether an exception happened + try + Occurences := Editor.SearchReplace( + comboSearch.Text, + Replacement, + Options + ); + except + on E:Exception do begin + ErrorDialog(E.ClassName + ': ' + E.Message); + ModalResult := mrNone; + end; + end; + + Editor.EndUpdate; + MainForm.ShowStatusMsg; + + if Occurences > -1 then begin + if ssoReplaceAll in Options then begin + MessageDialog(f_('Text "%s" replaced %s times.', [comboSearch.Text, FormatNumber(Occurences)]), mtInformation, [mbOk]); + if Occurences = 0 then + ModalResult := mrNone; + end else begin + if (OldCaretXY.X = Editor.CaretX) and + (OldCaretXY.Y = Editor.CaretY) then begin + MessageDialog(f_('Text "%s" not found.', [comboSearch.Text]), mtInformation, [mbOk]); + ModalResult := mrNone; + end; + end; + end; +end; + + +procedure TfrmSearchReplace.DoSearchReplaceData; +var + Search, Replacement, CellText: String; + Node: PVirtualNode; + Column, StartAtCol: TColumnIndex; + Match, SelectedOnly, Backwards, UseRegEx: Boolean; + MatchCount, ReplaceCount: Integer; + rx: TRegExpr; + Prompt: TModalResult; + ReplaceFlags: TReplaceFlags; + NodeSelected: Boolean; +begin + // Data grid version of DoSearchReplaceText + MainForm.ShowStatusMsg(_('Searching ...')); + + Search := comboSearch.Text; + Replacement := comboReplace.Text; + Replacement := StringReplace(Replacement, '\n', CRLF, [rfReplaceAll]); + Replacement := StringReplace(Replacement, '\t', #9, [rfReplaceAll]); + SelectedOnly := ssoSelectedOnly in Options; + Backwards := ssoBackwards in Options; + if ssoReplaceAll in Options then + Prompt := mrYesToAll + else + Prompt := mrYes; + Match := False; + MatchCount := 0; + ReplaceCount := 0; + ReplaceFlags := [rfReplaceAll]; + if not (ssoMatchCase in Options) then + Include(ReplaceFlags, rfIgnoreCase); + NodeSelected := True; + + // Init regular expression + rx := TRegExpr.Create; + UseRegEx := (ssoWholeWord in Options) or chkRegularExpression.Checked; + if chkRegularExpression.Checked then + rx.Expression := Search + else // Still used for "whole word" search + rx.Expression := '\b'+QuoteRegExprMetaChars(Search)+'\b'; + rx.ModifierI := not (ssoMatchCase in Options); + + // Set start row and column with regard to "Entire scope" and "Forward/Backward" mode + if (ssoEntireScope in Options) or (not Assigned(Grid.FocusedNode)) then begin + if Backwards then + Node := GetPreviousNode(Grid, nil, SelectedOnly) + else + Node := GetNextNode(Grid, nil, SelectedOnly); + StartAtCol := InvalidColumn; + end else begin + Node := Grid.FocusedNode; + if Backwards then + StartAtCol := Grid.Header.Columns.GetPreviousVisibleColumn(Grid.FocusedColumn, True) + else + StartAtCol := Grid.Header.Columns.GetNextVisibleColumn(Grid.FocusedColumn, True); + // Advance to next row if focused column is the very last column + if StartAtCol = InvalidColumn then begin + if Backwards then + Node := GetPreviousNode(Grid, Node, SelectedOnly) + else + Node := GetNextNode(Grid, Node, SelectedOnly); + end; + end; + + while Assigned(Node) do begin + MainForm.AnyGridEnsureFullRow(Grid, Node); + + // Find the first column + if StartAtCol > InvalidColumn then begin + Column := StartAtCol; + StartAtCol := InvalidColumn; + end else begin + if Backwards then + Column := Grid.Header.Columns.GetLastVisibleColumn(True) + else + Column := Grid.Header.Columns.GetFirstVisibleColumn(True); + end; + + while Column >= 0 do begin + CellText := Grid.Text[Node, Column]; + + if UseRegEx then begin + Match := rx.Exec(CellText); + end else begin + if ssoMatchCase in Options then + Match := Pos(Search, CellText) > 0 + else + Match := Pos(LowerCase(Search), LowerCase(CellText)) > 0; + end; + + if Match then begin + Inc(MatchCount); + + // Set focus on node and column + NodeSelected := SelectNode(Grid, Node, not SelectedOnly); + if not NodeSelected then + Break; + Grid.FocusedColumn := Column; + + // Replace logic + if ssoReplace in Options then begin + if ssoPrompt in Options then begin + // Ask user + Prompt := MessageDialog(f_('Replace this occurrence of "%s"?', [Search]), + StrEllipsis(CellText, 500), + mtConfirmation, + [mbYes, mbYesToAll, mbNo, mbCancel]); + case Prompt of + mrYesToAll: begin + Exclude(Options, ssoPrompt); + Include(Options, ssoReplaceAll); + end; + mrCancel: Exclude(Options, ssoReplaceAll); + end; + end; + if Prompt in [mrYes, mrYesToAll] then begin + if UseRegEx then + Grid.Text[Node, Column] := rx.Replace(CellText, Replacement, chkRegularExpression.Checked) + else + Grid.Text[Node, Column] := StringReplace(CellText, Search, Replacement, ReplaceFlags); + Inc(ReplaceCount); + end; + end; + + if not (ssoReplaceAll in Options) then + Break; + end; + + // Advance to next column + if Backwards then + Column := Grid.Header.Columns.GetPreviousVisibleColumn(Column, True) + else + Column := Grid.Header.Columns.GetNextVisibleColumn(Column, True); + end; + + if Match and (not (ssoReplaceAll in Options)) then + Break; + if not NodeSelected then + Break; + + if Backwards then + Node := GetPreviousNode(Grid, Node, SelectedOnly) + else + Node := GetNextNode(Grid, Node, SelectedOnly); + end; + + if (ssoReplaceAll in Options) and (MatchCount > 0) then begin + MessageDialog(f_('Text "%s" %d times replaced.', [Search, ReplaceCount]), mtInformation, [mbOk]) + end; + if MatchCount = 0 then begin + MessageDialog(f_('Text "%s" not found.', [Search]), mtInformation, [mbOk]); + ModalResult := mrNone; + end; + + MainForm.ShowStatusMsg; +end; + + +end.