diff --git a/packages/delphi2010/heidisql.dpr b/packages/delphi2010/heidisql.dpr index ccafc7d7..ea665933 100644 --- a/packages/delphi2010/heidisql.dpr +++ b/packages/delphi2010/heidisql.dpr @@ -35,7 +35,8 @@ uses table_editor in '..\..\source\table_editor.pas' {frmTableEditor}, mysql_api in '..\..\source\mysql_api.pas', mysql_connection in '..\..\source\mysql_connection.pas', - trigger_editor in '..\..\source\trigger_editor.pas' {frmTriggerEditor: TFrame}; + trigger_editor in '..\..\source\trigger_editor.pas' {frmTriggerEditor: TFrame}, + searchreplace in '..\..\source\searchreplace.pas' {frmSearchReplace}; {$R ..\..\res\icon.RES} {$R ..\..\res\version.RES} diff --git a/packages/delphi2010/heidisql.dproj b/packages/delphi2010/heidisql.dproj index d56bf46b..2f120fac 100644 --- a/packages/delphi2010/heidisql.dproj +++ b/packages/delphi2010/heidisql.dproj @@ -195,6 +195,9 @@
frmTriggerEditor
TFrame + +
frmSearchReplace
+
icon.res
diff --git a/res/icons/find_again.png b/res/icons/find_again.png new file mode 100644 index 00000000..272fe6b8 Binary files /dev/null and b/res/icons/find_again.png differ diff --git a/source/const.inc b/source/const.inc index b55b023c..95225ac7 100644 --- a/source/const.inc +++ b/source/const.inc @@ -190,6 +190,8 @@ const DEFAULT_FILTERACTIVE = False; REGNAME_MULTI_INSTANCES = 'AllowMultipleInstances'; DEFAULT_MULTI_INSTANCES = True; + REGNAME_SEARCHTEXT = 'FindDialogSearchHistory'; + REGNAME_REPLACETEXT = 'FindDialogReplaceHistory'; REGNAME_FIELDCOLOR_NUMERIC = 'FieldColor_Numeric'; REGNAME_FIELDCOLOR_TEXT = 'FieldColor_Text'; diff --git a/source/main.dfm b/source/main.dfm index 0784aeea..6445b9b9 100644 --- a/source/main.dfm +++ b/source/main.dfm @@ -1263,6 +1263,7 @@ object MainForm: TMainForm Top = 0 Width = 411 Height = 96 + OnSearchNotFound = SynMemoQuerySearchNotFound SingleLineMode = False Align = alClient ActiveLineColor = clWindow @@ -1292,6 +1293,7 @@ object MainForm: TMainForm TabWidth = 3 WantTabs = True OnDropFiles = SynMemoQueryDropFiles + OnReplaceText = SynMemoQueryReplaceText OnStatusChange = SynMemoQueryStatusChange RemovedKeystrokes = < item @@ -1498,6 +1500,18 @@ object MainForm: TMainForm Action = actReformatSQL end end + object Search1: TMenuItem + Caption = 'Search' + object Findtext1: TMenuItem + Action = actQueryFind + end + object actQueryFindAgain1: TMenuItem + Action = actQueryFindAgain + end + object Replacetext1: TMenuItem + Action = actQueryReplace + end + end object Extra1: TMenuItem Tag = 19 Caption = '&Tools' @@ -2146,7 +2160,7 @@ object MainForm: TMainForm Hint = 'Find text ...' ImageIndex = 30 ShortCut = 16454 - OnExecute = actQueryFindExecute + OnExecute = actQueryFindReplaceExecute end object actQueryReplace: TAction Category = 'SQL' @@ -2155,7 +2169,14 @@ object MainForm: TMainForm Hint = 'Replace text ...' ImageIndex = 59 ShortCut = 16466 - OnExecute = actQueryReplaceExecute + OnExecute = actQueryFindReplaceExecute + end + object actQueryFindAgain: TAction + Category = 'SQL' + Caption = 'Find or replace again' + ImageIndex = 142 + ShortCut = 114 + OnExecute = actQueryFindAgainExecute end object actSetDelimiter: TAction Category = 'SQL' @@ -6231,23 +6252,42 @@ object MainForm: TMainForm 43F83D4D2738195C82C5B763381B5E31A3DD0479CBAF6D7E05EBBBC0DC883A01 4C91E3672618257B704396A99BE46D97C9623AF93A3F9224CD92FC8501FA8D75 FE4FCCAC61ACF6D72F62B02F294EFE3B9F0000000049454E44AE426082} + end + item + Background = clWindow + Name = 'find_again' + PngImage.Data = { + 89504E470D0A1A0A0000000D49484452000000100000001008060000001FF3FF + 610000001974455874536F667477617265005061696E742E4E45542076332E35 + 2E32D7EE6943000002C24944415478DAAD934B48547114C6BF3BB719535354A6 + 4143255F93AF468D5132CD3470229D5AD4226813650FA3400B1795B98841DB55 + D426B0D08A30717415F9487BA838D6989A9889927847677CA6F3721EF7D99D11 + DA45481D38FC17E7FFFDCEE17C1C02FF18C47F07E8EE3F31C5C5ED8EA1280AD5 + 1565C4A3C636C16E5BC7E8D0E074CB8B7AE55F013774F74C39D9D9314D2F9F43 + 1410E5D76F0BCA94746C389DE69A6BE7A3FF083877A54A484ECF84C36E436060 + 10DA9A1A3064E8250A355A2155B50F325900FADF77FADE5796B9D90B36BBD5B5 + BEBAC2138DFACEED635F870BC3C2E5ED2429C1EAF212BE8F8F826568F88036EB + 1A18991C494989703BD6306EEC455FF79B6A8665EED25EAF403C78DAB24D8C1F + A121C1B1C64F83FE69427725234A11069AE31887C32B655DEB98999D07EB5A83 + 529988BAEA0A8A63D904F12B47D43D6C900A024F8747C8F16D6C181CC789DD19 + B85D1B301A7A119F948CC43DA9605916C16A03ECDE35D49F1D11447180980C71 + B1F2A674D13CE75C5EB4C822E40A2C2D98E171BB60B7592D6E97B3542A0BA815 + BB95040505437B27123BC36230611E4673F924290278FF12B5274EF71D282CCE + 5767ED85D5E604356F41F3B3C7CD795785531058D0BC982C87A8F004A8A2F3F1 + 79F61DC6CD4674545A083F40A33DC9A6A46792A5A547A16F6D85D7E3C1887160 + E26025528BD3CE801378703C071E02166DF3080F56C030D38D91B9FE4D1B8B8E + 1C13545939F0015AF47A4824243E76BDA6F3AA24B2125519A89FD360C429589E + 01C331625D06454834FA673A370179451A213BF7100A0A0AD0D5D5099224D1DB + D30ED565078E675C12859C3F7D932CD84C88D81129761FC0DBC98E493F409D5B + 20F836CF88DED3B4D7EF82E831726E11BCDBE395D03C0D0F4B2351918EFDF11A + 0C9906D033D53546E990B1A5634AD3112BEAD8C3F20F533D5FA85AA8B77C8D71 + 3558E178526EAAE57EEB7E01DE445494B226E2DE0000000049454E44AE426082} end> PngOptions = [pngBlendOnDisabled, pngGrayscaleOnDisabled] Left = 104 Top = 232 Bitmap = {} end - object FindDialogQuery: TFindDialog - OnFind = FindDialogQueryFind - Left = 104 - Top = 200 - end - object ReplaceDialogQuery: TReplaceDialog - OnFind = ReplaceDialogQueryFind - OnReplace = ReplaceDialogQueryReplace - Left = 136 - Top = 200 - end object PopupQueryLoad: TPopupMenu Left = 104 Top = 64 @@ -6968,4 +7008,8 @@ object MainForm: TMainForm Left = 40 Top = 269 end + object SynEditRegexSearch1: TSynEditRegexSearch + Left = 104 + Top = 304 + end end diff --git a/source/main.pas b/source/main.pas index 00759818..09b008c8 100644 --- a/source/main.pas +++ b/source/main.pas @@ -13,12 +13,12 @@ uses Windows, SysUtils, Classes, Graphics, GraphUtil, Forms, Controls, Menus, StdCtrls, Dialogs, Buttons, Messages, ExtCtrls, ComCtrls, StdActns, ActnList, ImgList, ToolWin, Clipbrd, SynMemo, SynEdit, SynEditTypes, SynEditKeyCmds, VirtualTrees, DateUtils, PngImageList, - ShlObj, SynEditMiscClasses, SynEditSearch, SynCompletionProposal, SynEditHighlighter, + ShlObj, SynEditMiscClasses, SynEditSearch, SynEditRegexSearch, SynCompletionProposal, SynEditHighlighter, SynHighlighterSQL, Tabs, SynUnicode, SynRegExpr, WideStrUtils, ExtActns, CommCtrl, Contnrs, PngSpeedButton, Generics.Collections, routine_editor, trigger_editor, options, EditVar, helpers, createdatabase, table_editor, TableTools, View, Usermanager, SelectDBObject, connections, sqlhelp, mysql_connection, - mysql_api, insertfiles; + mysql_api, insertfiles, searchreplace; type @@ -182,8 +182,6 @@ type actQueryWordWrap: TAction; actQueryFind: TAction; actQueryReplace: TAction; - FindDialogQuery: TFindDialog; - ReplaceDialogQuery: TReplaceDialog; ToolBarQuery: TToolBar; btnExecuteQuery: TToolButton; btnLoadSQL: TToolButton; @@ -300,6 +298,7 @@ type OpenDialogSQLFile: TOpenDialog; SaveDialogSQLFile: TSaveDialog; SynEditSearch1: TSynEditSearch; + SynEditRegexSearch1: TSynEditRegexSearch; tabCommandStats: TTabSheet; ListCommandStats: TVirtualStringTree; QF13: TMenuItem; @@ -454,6 +453,11 @@ type menuFilterInsertFunction: TMenuItem; actBlobAsText: TAction; btnBlobAsText: TToolButton; + actQueryFindAgain: TAction; + Search1: TMenuItem; + Findtext1: TMenuItem; + actQueryFindAgain1: TMenuItem; + Replacetext1: TMenuItem; procedure refreshMonitorConfig; procedure loadWindowConfig; procedure saveWindowConfig; @@ -504,8 +508,7 @@ type procedure actNewWindowExecute(Sender: TObject); procedure actSessionManagerExecute(Sender: TObject); procedure actPreferencesExecute(Sender: TObject); - procedure actQueryFindExecute(Sender: TObject); - procedure actQueryReplaceExecute(Sender: TObject); + procedure actQueryFindReplaceExecute(Sender: TObject); procedure actQueryStopOnErrorsExecute(Sender: TObject); procedure actQueryWordWrapExecute(Sender: TObject); procedure actReadmeExecute(Sender: TObject); @@ -518,9 +521,6 @@ type procedure actSQLhelpExecute(Sender: TObject); procedure actUpdateCheckExecute(Sender: TObject); procedure actWebbrowse(Sender: TObject); - procedure FindDialogQueryFind(Sender: TObject); - procedure ReplaceDialogQueryFind(Sender: TObject); - procedure ReplaceDialogQueryReplace(Sender: TObject); procedure actCopyAsSQLExecute(Sender: TObject); procedure actSelectTreeBackgroundExecute(Sender: TObject); procedure popupQueryPopup(Sender: TObject); @@ -722,6 +722,10 @@ type NewNode: PVirtualNode; OldColumn, NewColumn: TColumnIndex; var Allowed: Boolean); procedure actBlobAsTextExecute(Sender: TObject); + procedure SynMemoQuerySearchNotFound(Sender: TObject; FindText: string); + procedure SynMemoQueryReplaceText(Sender: TObject; const ASearch, + AReplace: string; Line, Column: Integer; var Action: TSynReplaceAction); + procedure actQueryFindAgainExecute(Sender: TObject); private ReachedEOT: Boolean; FDelimiter: String; @@ -742,6 +746,7 @@ type FCmdlineFilenames: TStringlist; FCmdlineConnectionParams: TConnectionParameters; FCmdlineSessionName: String; + FSearchReplaceExecuted: Boolean; procedure ParseCommandLineParameters(Parameters: TStringlist); procedure SetDelimiter(Value: String); procedure DisplayRowCountStats(MatchingRows: Int64 = -1); @@ -782,6 +787,7 @@ type TableEditor: TfrmTableEditor; InsertFiles: TfrmInsertFiles; EditVariableForm: TfrmEditVariable; + SearchReplaceDialog: TfrmSearchReplace; // Virtual Tree data arrays VTRowDataListVariables, @@ -1091,6 +1097,7 @@ begin FreeAndNil(TableEditor); FreeAndNil(TriggerEditor); FreeAndNil(CreateDatabaseForm); + FreeAndNil(SearchReplaceDialog); // Close database connection DoDisconnect; @@ -2553,25 +2560,78 @@ begin end; -procedure TMainForm.actQueryFindExecute(Sender: TObject); +procedure TMainForm.actQueryFindReplaceExecute(Sender: TObject); +var + DlgResult: TModalResult; + Occurences: Integer; begin - // if something is selected search for that text - if ActiveQueryMemo.SelAvail then - FindDialogQuery.FindText := ActiveQueryMemo.SelText + // Display search + replace dialog + if not Assigned(SearchReplaceDialog) then + SearchReplaceDialog := TfrmSearchReplace.Create(Self); + SearchReplaceDialog.Editor := ActiveQueryMemo; + SearchReplaceDialog.chkReplace.Checked := Sender = actQueryReplace; + DlgResult := SearchReplaceDialog.ShowModal; + if SearchReplaceDialog.chkRegularExpression.Checked then + SearchReplaceDialog.Editor.SearchEngine := SynEditRegexSearch1 else - FindDialogQuery.FindText := ActiveQueryMemo.WordAtCursor; - FindDialogQuery.Execute; + SearchReplaceDialog.Editor.SearchEngine := SynEditSearch1; + ShowStatus('Searching ...'); + case DlgResult of + mrOK, mrAll: begin + Occurences := SearchReplaceDialog.Editor.SearchReplace( + SearchReplaceDialog.comboSearch.Text, + SearchReplaceDialog.comboReplace.Text, + SearchReplaceDialog.Options + ); + FSearchReplaceExecuted := True; // Helper for later F3 hits + if DlgResult = mrAll then + ShowStatus('Text "'+SearchReplaceDialog.comboSearch.Text+'" '+FormatNumber(Occurences)+' times replaced.', 0); + end; + mrCancel: Exit; + end; + ShowStatus(STATUS_MSG_READY); end; -procedure TMainForm.actQueryReplaceExecute(Sender: TObject); +procedure TMainForm.actQueryFindAgainExecute(Sender: TObject); begin - // if something is selected search for that text - if ActiveQueryMemo.SelAvail then - ReplaceDialogQuery.FindText := ActiveQueryMemo.SelText - else - ReplaceDialogQuery.FindText := ActiveQueryMemo.WordAtCursor; - ReplaceDialogQuery.Execute; + // F3 - search or replace again, using previous settings + if not FSearchReplaceExecuted then begin + // Display dialog if not done yet + actQueryFindReplaceExecute(Sender) + end else begin + SearchReplaceDialog.Editor := ActiveQueryMemo; + if SearchReplaceDialog.chkRegularExpression.Checked then + SearchReplaceDialog.Editor.SearchEngine := SynEditRegexSearch1 + else + SearchReplaceDialog.Editor.SearchEngine := SynEditSearch1; + SearchReplaceDialog.Editor.SearchReplace( + SearchReplaceDialog.comboSearch.Text, + SearchReplaceDialog.comboReplace.Text, + SearchReplaceDialog.Options + ); + end; +end; + + +procedure TMainForm.SynMemoQuerySearchNotFound(Sender: TObject; FindText: string); +begin + // No text found + ShowStatus('Text "'+sstr(FindText,50)+'" not found.', 0); + MessageBeep(MB_ICONASTERISK); +end; + + +procedure TMainForm.SynMemoQueryReplaceText(Sender: TObject; const ASearch, + AReplace: string; Line, Column: Integer; var Action: TSynReplaceAction); +begin + // Fires when "Replace all" in search dialog was pressed with activated "Prompt on replace" + case MessageDlg('Replace this occurrence of "'+sstr(ASearch, 100)+'"?', mtConfirmation, [mbYes, mbYesToAll, mbNo, mbCancel], 0) of + mrYes: Action := raReplace; + mrYesToAll: Action := raReplaceAll; + mrNo: Action := raSkip; + mrCancel: Action := raCancel; + end; end; @@ -2792,68 +2852,6 @@ begin end; -procedure TMainForm.FindDialogQueryFind(Sender: TObject); -var - Options: TSynSearchOptions; - Search: String; -begin - Search := FindDialogQuery.FindText; - Options := []; - if Sender is TReplaceDialog then - Include(Options, ssoEntireScope); - if not (frDown in FindDialogQuery.Options) then - Include(Options, ssoBackwards); - if frMatchCase in FindDialogQuery.Options then - Include(Options, ssoMatchCase); - if frWholeWord in FindDialogQuery.Options then - Include(Options, ssoWholeWord); - if ActiveQueryMemo.SearchReplace(Search, '', Options) = 0 then - begin - MessageBeep(MB_ICONASTERISK); - ShowStatus( 'SearchText ''' + Search + ''' not found!', 0); - end; -end; - - -procedure TMainForm.ReplaceDialogQueryFind(Sender: TObject); -begin - FindDialogQuery.FindText := ReplaceDialogQuery.FindText; - FindDialogQueryFind( ReplaceDialogQuery ); -end; - - -procedure TMainForm.ReplaceDialogQueryReplace(Sender: TObject); -var - Options: TSynSearchOptions; - Search: String; -begin - Search := ReplaceDialogQuery.FindText; - Options := [ssoEntireScope]; // Do replaces always on entire scope, because the standard-dialog lacks of a down/up-option - if frReplaceAll in ReplaceDialogQuery.Options then - Include( Options, ssoReplaceAll ); - if not (frDown in ReplaceDialogQuery.Options) then - Include(Options, ssoBackwards); - if frMatchCase in ReplaceDialogQuery.Options then - Include(Options, ssoMatchCase); - if frWholeWord in ReplaceDialogQuery.Options then - Include(Options, ssoWholeWord); - if frReplace in ReplaceDialogQuery.Options then // Replace instead of ReplaceAll is pressed - Include(Options, ssoReplace) - else - Include(Options, ssoReplaceAll); - if ActiveQueryMemo.SearchReplace( Search, ReplaceDialogQuery.ReplaceText, Options) = 0 then - begin - MessageBeep(MB_ICONASTERISK); - ShowStatus( 'SearchText ''' + Search + ''' not found!', 0); - if ssoBackwards in Options then - ActiveQueryMemo.BlockEnd := ActiveQueryMemo.BlockBegin - else - ActiveQueryMemo.BlockBegin := ActiveQueryMemo.BlockEnd; - ActiveQueryMemo.CaretXY := ActiveQueryMemo.BlockBegin; - end; -end; - - procedure TMainform.FillPopupQueryLoad; var i, j: Integer; @@ -3774,8 +3772,9 @@ begin actSaveSQLselection.Enabled := InQueryTab and HasSelection; actSaveSQLSnippet.Enabled := InQueryTab and NotEmpty; actSaveSQLSelectionSnippet.Enabled := InQueryTab and HasSelection; - actQueryFind.Enabled := InQueryTab and NotEmpty; - actQueryReplace.Enabled := InQueryTab and NotEmpty; + actQueryFind.Enabled := InQueryTab; + actQueryReplace.Enabled := InQueryTab; + actQueryFindAgain.Enabled := InQueryTab; // We need a pressed button which somehow does not work in conjunction with Enabled=False // actQueryStopOnErrors.Enabled := QueryTabActive; actQueryWordWrap.Enabled := InQueryTab; @@ -7873,8 +7872,6 @@ begin // Copy text from a focused control to clipboard Success := False; Control := Screen.ActiveControl; - // Do not handle Search/replace dialog - if not Control.Focused then Exit; DoCut := Sender = actCut; if Control is TCustomEdit then begin Edit := TCustomEdit(Control); @@ -7925,8 +7922,6 @@ begin // Paste text into the focused control Success := False; Control := Screen.ActiveControl; - // Do not handle Search/replace dialog - if not Control.Focused then Exit; if not Clipboard.HasFormat(CF_TEXT) then begin // Do nothing, we cannot paste a picture or so end else if Control is TCustomEdit then begin @@ -7969,8 +7964,6 @@ begin // Select all items, text or whatever Success := False; Control := Screen.ActiveControl; - // Do not handle Search/replace dialog - if not Control.Focused then Exit; if Control is TCustomEdit then begin TCustomEdit(Control).SelectAll; Success := True; @@ -8006,8 +7999,6 @@ begin // Invert selection in grids or listboxes Success := False; Control := Screen.ActiveControl; - // Do not handle Search/replace dialog - if not Control.Focused then Exit; if Control is TVirtualStringTree then begin Grid := TVirtualStringTree(Control); if toMultiSelect in Grid.TreeOptions.SelectionOptions then begin @@ -8316,13 +8307,14 @@ begin QueryTab.Memo.RightEdge := SynMemoQuery.RightEdge; QueryTab.Memo.WantTabs := SynMemoQuery.WantTabs; QueryTab.Memo.Highlighter := SynMemoQuery.Highlighter; - QueryTab.Memo.SearchEngine := SynMemoQuery.SearchEngine; QueryTab.Memo.Gutter.Assign(SynMemoQuery.Gutter); QueryTab.Memo.Font.Assign(SynMemoQuery.Font); QueryTab.Memo.ActiveLineColor := SynMemoQuery.ActiveLineColor; QueryTab.Memo.OnDragDrop := SynMemoQuery.OnDragDrop; QueryTab.Memo.OnDragOver := SynMemoQuery.OnDragOver; QueryTab.Memo.OnDropFiles := SynMemoQuery.OnDropFiles; + QueryTab.Memo.OnSearchNotFound := SynMemoQuery.OnSearchNotFound; + QueryTab.Memo.OnReplaceText := SynMemoQuery.OnReplaceText; QueryTab.Memo.OnStatusChange := SynMemoQuery.OnStatusChange; SynCompletionProposal.AddEditor(QueryTab.Memo); diff --git a/source/searchreplace.dfm b/source/searchreplace.dfm new file mode 100644 index 00000000..65e915ff --- /dev/null +++ b/source/searchreplace.dfm @@ -0,0 +1,180 @@ +object frmSearchReplace: TfrmSearchReplace + Left = 0 + Top = 0 + BorderStyle = bsDialog + Caption = 'Search and replace text' + ClientHeight = 236 + ClientWidth = 388 + Color = clBtnFace + Font.Charset = DEFAULT_CHARSET + Font.Color = clWindowText + Font.Height = -11 + Font.Name = 'Tahoma' + Font.Style = [] + OldCreateOrder = False + Position = poOwnerFormCenter + OnClose = FormClose + OnCreate = FormCreate + OnDestroy = FormDestroy + OnShow = FormShow + DesignSize = ( + 388 + 236) + PixelsPerInch = 96 + TextHeight = 13 + object lblSearch: TLabel + Left = 8 + Top = 11 + Width = 60 + Height = 13 + Caption = '&Text to find:' + FocusControl = comboSearch + end + object btnCancel: TButton + Left = 305 + Top = 203 + Width = 75 + Height = 25 + Anchors = [akRight, akBottom] + Cancel = True + Caption = 'Cancel' + ModalResult = 2 + TabOrder = 9 + end + object btnReplaceAll: TButton + Left = 224 + Top = 203 + Width = 75 + Height = 25 + Anchors = [akRight, akBottom] + Caption = 'Replace all' + ModalResult = 8 + TabOrder = 8 + end + object chkReplace: TCheckBox + Left = 8 + Top = 35 + Width = 73 + Height = 17 + Caption = 'Replace:' + TabOrder = 1 + OnClick = chkReplaceClick + end + object comboSearch: TComboBox + Left = 90 + Top = 8 + Width = 290 + Height = 21 + Anchors = [akLeft, akTop, akRight] + TabOrder = 0 + Text = 'comboSearch' + TextHint = 'Enter text to find ...' + OnExit = comboSearchReplaceExit + end + object comboReplace: TComboBox + Left = 90 + Top = 33 + Width = 290 + Height = 21 + Anchors = [akLeft, akTop, akRight] + Enabled = False + TabOrder = 2 + Text = 'comboReplace' + TextHint = 'Enter replacement pattern ...' + OnExit = comboSearchReplaceExit + end + object grpOptions: TGroupBox + Left = 8 + Top = 60 + Width = 372 + Height = 69 + Anchors = [akLeft, akTop, akRight] + Caption = 'Options' + TabOrder = 3 + object chkCaseSensitive: TCheckBox + Left = 12 + Top = 18 + Width = 130 + Height = 17 + Caption = 'Case sensitive' + TabOrder = 0 + end + object chkWholeWords: TCheckBox + Left = 12 + Top = 41 + Width = 130 + Height = 17 + Caption = 'Whole words' + TabOrder = 1 + end + object chkRegularExpression: TCheckBox + Left = 156 + Top = 18 + Width = 130 + Height = 17 + Caption = 'Regular expression' + ParentShowHint = False + ShowHint = True + TabOrder = 2 + OnClick = ValidateControls + end + object chkPromptOnReplace: TCheckBox + Left = 156 + Top = 41 + Width = 130 + Height = 17 + Caption = 'Prompt on replace' + TabOrder = 3 + end + end + object grpDirection: TRadioGroup + Left = 8 + Top = 135 + Width = 120 + Height = 62 + Anchors = [akLeft, akTop, akBottom] + Caption = 'Direction' + ItemIndex = 0 + Items.Strings = ( + '&Forward' + '&Backward') + TabOrder = 4 + end + object grpOrigin: TRadioGroup + Left = 134 + Top = 135 + Width = 120 + Height = 62 + Anchors = [akLeft, akTop, akBottom] + Caption = 'Origin' + ItemIndex = 0 + Items.Strings = ( + 'From cursor' + 'Entire scope') + TabOrder = 5 + end + object grpScope: TRadioGroup + Left = 260 + Top = 135 + Width = 120 + Height = 62 + Anchors = [akLeft, akTop, akRight, akBottom] + Caption = 'Scope' + ItemIndex = 0 + Items.Strings = ( + 'Global' + 'Selected text') + TabOrder = 6 + end + object btnOK: TButton + Left = 143 + Top = 203 + Width = 75 + Height = 25 + Anchors = [akRight, akBottom] + Caption = 'OK' + Default = True + ModalResult = 1 + TabOrder = 7 + end +end diff --git a/source/searchreplace.pas b/source/searchreplace.pas new file mode 100644 index 00000000..6c22ee8c --- /dev/null +++ b/source/searchreplace.pas @@ -0,0 +1,150 @@ +unit searchreplace; + +interface + +uses + Windows, Messages, SysUtils, Classes, Controls, Forms, Dialogs, StdCtrls, + ExtCtrls, SynMemo, SynEditSearch, SynEditTypes, SynEditMiscClasses; + +type + TfrmSearchReplace = class(TForm) + 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; + procedure ValidateControls(Sender: TObject); + procedure chkReplaceClick(Sender: TObject); + procedure FormShow(Sender: TObject); + procedure FormClose(Sender: TObject; var Action: TCloseAction); + procedure comboSearchReplaceExit(Sender: TObject); + procedure FormCreate(Sender: TObject); + procedure FormDestroy(Sender: TObject); + private + { Private declarations } + public + { Public declarations } + Editor: TSynMemo; + Options: TSynSearchOptions; + end; + + +implementation + +{$R *.dfm} + +uses main, helpers; + + +procedure TfrmSearchReplace.FormCreate(Sender: TObject); +begin + comboSearch.Items.Text := GetRegValue(REGNAME_SEARCHTEXT, ''); + comboReplace.Items.Text := GetRegValue(REGNAME_REPLACETEXT, ''); + comboSearch.Text := ''; + comboReplace.Text := ''; + if comboSearch.Items.Count > 0 then comboSearch.Text := comboSearch.Items[0]; + if comboReplace.Items.Count > 0 then comboReplace.Text := comboReplace.Items[0]; + + chkRegularExpression.Hint := 'Search patterns:'+CRLF+ + ' ^ Start of line'+CRLF+ + ' $ End of line'+CRLF+ + ' \w Any word character'+CRLF+ + ' \d Digit (0-9)'+CRLF+ + ' \s Whitespace'+CRLF+ + 'Replacement patterns:'+CRLF+ + ' $0 .. $n Callback parentheses' + ; +end; + + +procedure TfrmSearchReplace.FormShow(Sender: TObject); +var + SearchText: String; +begin + // Prefill search editor with selected text + if Editor.SelAvail then + SearchText := Editor.SelText + else + SearchText := Editor.WordAtCursor; + if SearchText <> '' then + comboSearch.Text := SearchText; + ValidateControls(Sender); + comboSearch.SetFocus; +end; + + +procedure TfrmSearchReplace.FormClose(Sender: TObject; var Action: TCloseAction); +begin + // Set SynEditSearch options, which the caller can use in Editor.SearchReplace() + 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); + +end; + + +procedure TfrmSearchReplace.FormDestroy(Sender: TObject); +begin + OpenRegistry; + Mainreg.WriteString(REGNAME_SEARCHTEXT, comboSearch.Items.Text); + Mainreg.WriteString(REGNAME_REPLACETEXT, comboReplace.Items.Text); +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; + chkPromptOnReplace.Enabled := chkReplace.Checked; + btnReplaceAll.Enabled := chkReplace.Checked; + if chkReplace.Checked then + btnOK.Caption := 'Replace' + else + btnOK.Caption := 'Find'; +end; + + +procedure TfrmSearchReplace.comboSearchReplaceExit(Sender: TObject); +var + Combo: TComboBox; + idx: Integer; +begin + // Store search or replace text history + Combo := Sender as TComboBox; + if Combo.Text = '' then + Exit; + idx := Combo.Items.IndexOf(Combo.Text); + if idx > -1 then + Combo.Items.Move(idx, 0) + else + Combo.Items.Insert(0, Combo.Text); + Combo.Text := Combo.Items[0]; +end; + + +end.