unit ce_search; {$I ce_defines.inc} interface uses Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls, Menus, StdCtrls, actnList, Buttons, SynEdit, SynEditSearch, SynEditTypes, ce_common, ce_mru, ce_widget, ce_synmemo, ce_interfaces, ce_observer, ce_writableComponent, ce_dialogs, ce_sharedres, SynEditTextBuffer; type // TCESearchWidget persistents settings TCESearchOptions = class(TWritableLfmTextComponent) private fPrompt: boolean; fFromCur: boolean; fRegex: boolean; fCaseSens:boolean; fBackWard: boolean; fWholeWord: boolean; fMrSearches: TStringList; fMrReplacements: TStringList; procedure setMrSearches(value: TStringList); procedure setMrReplacements(value: TStringList); published property prompt: boolean read fPrompt write fPrompt; property fromCursor: boolean read fFromCur write fFromCur; property regex: boolean read fRegex write fRegex; property caseSensistive: boolean read fCaseSens write fCaseSens; property backward: boolean read fBackWard write fBackWard; property wholeWord: boolean read fWholeWord write fWholeWord; property recentSearches: TStringList read fMrSearches write setMrSearches; property recentReplacements: TStringList read fMrReplacements write setMrReplacements; public procedure afterLoad; override; constructor create(aOwner: TComponent); override; destructor destroy; override; procedure assign(source: TPersistent); override; procedure assignTo(target: TPersistent); override; end; { TCESearchWidget } TCESearchWidget = class(TCEWidget, ICEDocumentObserver, ICEProjectObserver) btnAllScope: TBitBtn; btnFind: TBitBtn; btnFindAll: TBitBtn; btnReplace: TBitBtn; btnReplaceAll: TBitBtn; cbToFind: TComboBox; cbReplaceWth: TComboBox; chkEnableRep: TCheckBox; chkPrompt: TCheckBox; chkRegex: TCheckBox; chkWWord: TCheckBox; chkBack: TCheckBox; chkFromCur: TCheckBox; chkCaseSens: TCheckBox; grpOpts: TGroupBox; imgList: TImageList; Panel1: TPanel; Panel2: TPanel; procedure btnAllScopeClick(Sender: TObject); procedure cbReplaceWthChange(Sender: TObject); procedure cbToFindChange(Sender: TObject); procedure chkEnableRepChange(Sender: TObject); private fDoc: TCESynMemo; fToFind: string; fReplaceWth: string; fActReplaceNext: TAction; fActFindNext: TAction; fActReplaceAll: TAction; fActFindAll: TAction; fSearchMru, fReplaceMru: TCEMruList; fCancelAll: boolean; fHasSearched: boolean; fHasRestarted: boolean; fProj: ICECommonProject; fAllInProj: boolean; function getOptions: TSynSearchOptions; procedure actReplaceAllExecute(sender: TObject); procedure replaceEvent(Sender: TObject; const ASearch, AReplace: string; Line, Column: integer; var ReplaceAction: TSynReplaceAction); // procedure projNew(project: ICECommonProject); procedure projChanged(project: ICECommonProject); procedure projClosing(project: ICECommonProject); procedure projFocused(project: ICECommonProject); procedure projCompiling(project: ICECommonProject); procedure projCompiled(project: ICECommonProject; success: boolean); // procedure docNew(document: TCESynMemo); procedure docClosing(document: TCESynMemo); procedure docFocused(document: TCESynMemo); procedure docChanged(document: TCESynMemo); // procedure findAll(const filename: string; lines: TStrings); protected procedure updateImperative; override; public constructor Create(aOwner: TComponent); override; destructor Destroy; override; // procedure actFindNextExecute(sender: TObject); procedure actReplaceNextExecute(sender: TObject); procedure actFindAllExecute(sender: TObject); end; implementation {$R *.lfm} const OptsFname = 'search.txt'; {$REGION TCESearchOptions ------------------------------------------------------} constructor TCESearchOptions.create(aOwner: TComponent); begin inherited; fMrReplacements := TStringList.Create; fMrSearches := TStringList.Create; end; destructor TCESearchOptions.destroy; begin fMrSearches.Free; fMrReplacements.Free; inherited; end; procedure TCESearchOptions.assign(source: TPersistent); var widg: TCESearchWidget; begin if source is TCESearchWidget then begin widg := TCESearchWidget(source); fMrSearches.Assign(widg.fSearchMru); fMrReplacements.Assign(widg.fReplaceMru); fPrompt := widg.chkPrompt.Checked; fBackWard := widg.chkBack.Checked; fCaseSens := widg.chkCaseSens.Checked; fRegex := widg.chkRegex.Checked; fFromCur := widg.chkFromCur.Checked; fWholeWord := widg.chkWWord.Checked; end else inherited; end; procedure TCESearchOptions.assignTo(target: TPersistent); var widg: TCESearchWidget; begin if target is TCESearchWidget then begin widg := TCESearchWidget(target); widg.cbToFind.Items.Assign(fMrSearches); widg.fSearchMru.Assign(fMrSearches); widg.cbReplaceWth.Items.Assign(fMrReplacements); widg.fReplaceMru.Assign(fMrReplacements); widg.chkPrompt.Checked := fPrompt; widg.chkBack.Checked := fBackWard; widg.chkCaseSens.Checked:= fCaseSens; widg.chkRegex.Checked := fRegex; widg.chkFromCur.Checked := fFromCur; widg.chkWWord.Checked := fWholeWord; end else inherited; end; procedure TCESearchOptions.setMrSearches(value: TStringList); begin fMrSearches.Assign(value); end; procedure TCESearchOptions.setMrReplacements(value: TStringList); begin fMrReplacements.Assign(value); end; procedure TCESearchOptions.afterLoad; var i: integer; begin inherited; for i := fMrReplacements.Count-1 downto 0 do if fMrReplacements[i].length > 128 then fMrReplacements.Delete(i); for i := fMrSearches.Count-1 downto 0 do if fMrSearches[i].length > 128 then fMrSearches.Delete(i); end; {$ENDREGION} {$REGION Standard Comp/Obj------------------------------------------------------} constructor TCESearchWidget.Create(aOwner: TComponent); var fname: string; begin inherited; toolbarVisible:=false; fActFindNext := TAction.Create(self); fActFindNext.Caption := 'Find'; fActFindNext.OnExecute := @actFindNextExecute; fActFindAll := TAction.Create(self); fActFindAll.Caption := 'Find all'; fActFindAll.OnExecute := @actFindAllExecute; fActReplaceNext := TAction.Create(self); fActReplaceNext.Caption := 'Replace'; fActReplaceNext.OnExecute := @actReplaceNextExecute; fActReplaceAll := TAction.Create(self); fActReplaceAll.Caption := 'Replace all'; fActReplaceAll.OnExecute := @actReplaceAllExecute; // fSearchMru := TCEMruList.Create; fReplaceMru:= TCEMruList.Create; // fname := getCoeditDocPath + OptsFname; if fname.fileExists then with TCESearchOptions.create(nil) do try loadFromFile(fname); assignTo(self); finally free; end; // btnFind.Action := fActFindNext; btnReplace.Action := fActReplaceNext; btnReplaceAll.Action := fActReplaceAll; btnFindAll.Action := fActFindAll; AssignPng(btnAllScope, 'DOCUMENT'); updateImperative; // EntitiesConnector.addObserver(self); end; destructor TCESearchWidget.Destroy; begin with TCESearchOptions.create(nil) do try assign(self); saveToFile(getCoeditDocPath + OptsFname); finally free; end; // EntitiesConnector.removeObserver(self); fSearchMru.Free; fReplaceMru.Free; inherited; end; {$ENDREGION} {$REGION ICEContextualActions---------------------------------------------------} function TCESearchWidget.getOptions: TSynSearchOptions; begin result := []; if chkRegex.Checked then result += [ssoRegExpr]; if chkWWord.Checked then result += [ssoWholeWord]; if chkBack.Checked then result += [ssoBackwards]; if chkCaseSens.Checked then result += [ssoMatchCase]; if chkPrompt.Checked then result += [ssoPrompt]; end; function dlgReplaceAll: TModalResult; const Btns = [mbYes, mbNo, mbYesToAll, mbNoToAll]; begin exit( MessageDlg('Coedit', 'Replace this match ?', mtConfirmation, Btns, '')); end; procedure TCESearchWidget.replaceEvent(Sender: TObject; const ASearch, AReplace: string; Line, Column: integer; var ReplaceAction: TSynReplaceAction); begin case dlgReplaceAll of mrYes: ReplaceAction := raReplace; mrNo: ReplaceAction := raSkip; mrYesToAll: ReplaceAction := raReplaceAll; mrCancel, mrClose, mrNoToAll: begin ReplaceAction := raCancel; fCancelAll := true; end; end; end; procedure TCESearchWidget.actFindAllExecute(sender: TObject); var i: integer; lst: TSynEditStringList; fnm: string; begin if fDoc.isNil and not fAllInProj then exit; if (fProj = nil) and fAllInProj then exit; // fSearchMru.Insert(0,fToFind); cbToFind.Items.Assign(fSearchMru); // if fAllInProj then begin lst := TSynEditStringList.Create; try for i := 0 to fProj.sourcesCount-1 do begin fnm := fProj.sourceAbsolute(i); lst.LoadFromFile(fnm); findAll(fnm, lst); end; finally lst.Free; end; end else findAll(fDoc.fileName, fDoc.Lines); end; procedure TCESearchWidget.findAll(const filename: string; lines: TStrings); var search: TSynEditSearch; options: TSynSearchOptions; start, stop: TPoint; startf, stopf: TPoint; msgs: ICEMessagesDisplay; msg: string; fmt: string; i: integer; res: array of TPoint = nil; begin search := TSynEditSearch.Create; try options := getOptions; search.Sensitive := ssoMatchCase in options; search.Whole := ssoWholeWord in options; search.RegularExpressions:= ssoRegExpr in options; search.Pattern:=fToFind; start := Point(1,1); stop := Point(high(integer), lines.Count); while search.FindNextOne(lines, start, stop, startf, stopf) do begin setLength(res, length(res) + 1); res[high(res)].X := startf.X; res[high(res)].Y := startf.Y; start := stopf; end; msgs := getMessageDisplay; msg := format('%d result(s) for the pattern <%s> in %s', [length(res), fToFind, filename]); msgs.message(msg, nil, amcMisc, amkInf); fmt := fileName + '(%d,%d): "%s"'; for i := 0 to high(res) do begin msg := format(fmt, [res[i].Y, res[i].X, Trim(lines[res[i].Y-1])]); msgs.message(msg, nil, amcMisc, amkInf); end; finally search.free; end; end; procedure TCESearchWidget.actFindNextExecute(sender: TObject); begin if fDoc.isNil then exit; // fSearchMru.Insert(0,fToFind); cbToFind.Items.Assign(fSearchMru); // if not chkFromCur.Checked then begin if chkBack.Checked then fDoc.CaretXY := Point(high(Integer), high(Integer)) else begin if not fHasRestarted then fDoc.CaretXY := Point(0,0); fHasRestarted := true; end; end else if fHasSearched then begin if chkBack.Checked then fDoc.CaretX := fDoc.CaretX - 1 else fDoc.CaretX := fDoc.CaretX + fToFind.length; end; if fDoc.SearchReplace(fToFind, '', getOptions) = 0 then dlgOkInfo('the expression cannot be found') else begin fHasSearched := true; fHasRestarted := false; chkFromCur.Checked := true; end; updateImperative; end; procedure TCESearchWidget.actReplaceNextExecute(sender: TObject); begin if fDoc.isNil then exit; // fSearchMru.Insert(0, fToFind); fReplaceMru.Insert(0, fReplaceWth); cbToFind.Items.Assign(fSearchMru); cbReplaceWth.Items.Assign(fReplaceMru); // if chkPrompt.Checked then fDoc.OnReplaceText := @replaceEvent; if not chkFromCur.Checked then begin if chkBack.Checked then fDoc.CaretXY := Point(high(Integer), high(Integer)) else fDoc.CaretXY := Point(0,0); end else if fHasSearched then begin if chkBack.Checked then fDoc.CaretX := fDoc.CaretX - 1 else fDoc.CaretX := fDoc.CaretX + fToFind.length; end; if fDoc.SearchReplace(fToFind, fReplaceWth, getOptions + [ssoReplace]) <> 0 then fHasSearched := true; fDoc.OnReplaceText := nil; updateImperative; end; procedure TCESearchWidget.actReplaceAllExecute(sender: TObject); var opts: TSynSearchOptions; begin if fDoc.isNil then exit; cbReplaceWth.Items.Assign(fReplaceMru); opts := getOptions + [ssoReplace]; opts -= [ssoBackwards]; // fSearchMru.Insert(0, fToFind); fReplaceMru.Insert(0, fReplaceWth); if chkPrompt.Checked then fDoc.OnReplaceText := @replaceEvent; fDoc.CaretXY := Point(0,0); while(true) do begin if fDoc.SearchReplace(fToFind, fReplaceWth, opts) = 0 then break; if fCancelAll then begin fCancelAll := false; break; end; end; fDoc.OnReplaceText := nil; updateImperative; end; {$ENDREGION} {$REGION ICEProjectObserver ----------------------------------------------------} procedure TCESearchWidget.projNew(project: ICECommonProject); begin fProj := project; updateImperative; end; procedure TCESearchWidget.projChanged(project: ICECommonProject); begin end; procedure TCESearchWidget.projClosing(project: ICECommonProject); begin if fProj = project then fProj := nil; updateImperative; end; procedure TCESearchWidget.projFocused(project: ICECommonProject); begin fProj := project; updateImperative; end; procedure TCESearchWidget.projCompiling(project: ICECommonProject); begin end; procedure TCESearchWidget.projCompiled(project: ICECommonProject; success: boolean); begin end; {$ENDREGION} {$REGION ICEDocumentObserver ---------------------------------------------------} procedure TCESearchWidget.docNew(document: TCESynMemo); begin fDoc := document; updateImperative; end; procedure TCESearchWidget.docClosing(document: TCESynMemo); begin if fDoc = document then fDoc := nil; updateImperative; end; procedure TCESearchWidget.docFocused(document: TCESynMemo); begin if fDoc = document then exit; fDoc := document; updateImperative; end; procedure TCESearchWidget.docChanged(document: TCESynMemo); begin end; {$ENDREGION} {$REGION Misc. -----------------------------------------------------------------} procedure TCESearchWidget.cbToFindChange(Sender: TObject); begin if Updating then exit; fToFind := cbToFind.Text; fHasSearched := false; updateImperative; end; procedure TCESearchWidget.chkEnableRepChange(Sender: TObject); begin if Updating then exit; updateImperative; end; procedure TCESearchWidget.cbReplaceWthChange(Sender: TObject); begin if Updating then exit; fReplaceWth := cbReplaceWth.Text; fHasSearched := false; updateImperative; end; procedure TCESearchWidget.btnAllScopeClick(Sender: TObject); begin fAllInProj := not fAllInProj; if fAllInProj then begin AssignPng(btnAllScope, 'DOCUMENT_ALL'); btnAllScope.Hint := 'all project sources'; end else begin AssignPng(btnAllScope, 'DOCUMENT'); btnAllScope.Hint := 'selected source'; end; updateImperative; end; procedure TCESearchWidget.updateImperative; var canAll: boolean; hasTxt: boolean; begin canAll := ((fDoc.isNotNil and not fAllInProj) or (fAllInProj and (fProj <> nil))); hasTxt := fToFind.isNotEmpty and not fToFind.isBlank; btnFind.Enabled := fDoc.isNotNil and hasTxt; btnFindAll.Enabled := canAll and hasTxt; btnReplace.Enabled := fDoc.isNotNil and chkEnableRep.Checked and fToFind.isNotEmpty; btnReplaceAll.Enabled := btnReplace.Enabled; cbReplaceWth.Enabled := fDoc.isNotNil and chkEnableRep.Checked; cbToFind.Enabled := canAll or fDoc.isNotNil; end; {$ENDREGION} initialization RegisterClasses([TCESearchOptions]); end.