Subversion Repositories fastphp

Rev

Rev 83 | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. unit FindReplace;
  2.  
  3.   (*
  4.   TSynSearchOption = (ssoMatchCase, ssoWholeWord, ssoBackwards,
  5.     ssoEntireScope, ssoSelectedOnly, ssoReplace, ssoReplaceAll, ssoPrompt);
  6.  
  7.   frDisableMatchCase
  8.   Disables (grays) the Match Case check box in a find dialog.
  9.   frDisableUpDown
  10.   Disables (grays) the Up and Down buttons, which determine the direction of the search.
  11.   frDisableWholeWord
  12.   Disables (grays) the Match Whole Word check box of find dialog.
  13.   frDown = not ssoBackwards
  14.   Selects the Down button by default when the dialog opens. If the frDown flags is off, Up is selected when the dialog opens. (By default, frDown is on.)
  15.   frFindNext
  16.   This flag is turned on when the user clicks the Find Next button and turned off when the dialog closes.
  17.   frHideMatchCase
  18.   Removes the Match Case check box from the dialog.
  19.   frHideWholeWord
  20.   Removes the Match Whole Word check box from the dialog.
  21.   frHideUpDown
  22.   Removes the Up and Down buttons from the dialog.
  23.   frMatchCase = ssoMatchCase
  24.   This flag is turned on (off) when the user selects (deselects) the Match Case check box. To select the check box by default when the dialog opens, set frMatchCase at design time.
  25.   frReplace = ssoReplace
  26.   Applies to TReplaceDialog only. This flag is set by the system to indicate that the application should replace the current occurrence (and only the current occurrence) of the FindText string with the ReplaceText string. Not used in search routines.
  27.   frReplaceAll = ssoReplaceAll
  28.   Applies to TReplaceDialog only. This flag is set by the system to indicate that the application should replace all occurrences of the FindText string with the ReplaceText string.
  29.   frShowHelp
  30.   Displays a Help button in the dialog.
  31.   frWholeWord = ssoWholeWord
  32.   This flag is turned on (off) when the user selects (deselects) the Match Whole Word check box. To select the check box by default when the dialog opens, set frWholeWord at design time.
  33.   *)
  34.  
  35. interface
  36.  
  37. uses
  38.   Windows, Messages, SysUtils, Classes, Dialogs, SynEdit, System.UITypes;
  39.  
  40. type
  41.   TSynEditFindReplace = class(TComponent)
  42.   private
  43.     fEditor: TSynEdit;
  44.     fReplaceDialog: TReplaceDialog;
  45.     fFindDialog: TFindDialog;
  46.     fAutofocus: boolean;
  47.   protected
  48.     type
  49.       TFindDirection = (sdDefault, sdForwards, sdBackwards);
  50.  
  51.     procedure OnFind(Sender: TObject); virtual;
  52.     procedure OnReplace(Sender: TObject); virtual;
  53.  
  54.     procedure DoFind(dialog: TFindDialog; direction: TFindDirection); overload;
  55.     procedure DoReplace(dialog: TReplaceDialog; direction: TFindDirection);
  56.  
  57.     function GetDirection(dialog: TFindDialog): TFindDirection;
  58.  
  59.   public
  60.     constructor Create(AOwner: TComponent); override;
  61.  
  62.     property FindDialog: TFindDialog read fFindDialog;
  63.     property ReplaceDialog: TReplaceDialog read fReplaceDialog;
  64.  
  65.     procedure CloseDialogs;
  66.  
  67.     procedure FindExecute;
  68.     procedure ReplaceExecute;
  69.  
  70.     procedure FindContinue;
  71.     procedure FindNext;
  72.     procedure FindPrev;
  73.  
  74.     procedure GoToLine(LineNumber: integer);
  75.  
  76.   published
  77.     property Editor: TSynEdit read fEditor write fEditor;
  78.     property Autofocus: boolean read fAutofocus write fAutofocus;
  79.   end;
  80.  
  81. implementation
  82.  
  83. uses
  84.   SynEditTypes;
  85.  
  86. constructor TSynEditFindReplace.Create(AOwner: TComponent);
  87. begin
  88.   inherited Create(AOwner);
  89.  
  90.   fFindDialog := TFindDialog.Create(Self);
  91.   fFindDialog.OnFind := OnFind;
  92.  
  93.   fReplaceDialog := TReplaceDialog.Create(Self);
  94.   fReplaceDialog.OnReplace := OnReplace;
  95.   fReplaceDialog.OnFind := OnFind;
  96.   fReplaceDialog.Options := fReplaceDialog.Options + [frHideWholeWord]; // TODO: currently not supported (see below)
  97. end;
  98.  
  99. function TSynEditFindReplace.GetDirection(dialog: TFindDialog): TFindDirection;
  100. begin
  101.   if frDown in dialog.Options then
  102.     result := sdForwards
  103.   else
  104.     result := sdBackwards;
  105. end;
  106.  
  107. procedure TSynEditFindReplace.DoFind(dialog: TFindDialog; direction: TFindDirection);
  108. var
  109.   opt: TSynSearchOptions;
  110.   found: boolean;
  111. begin
  112.   if direction = sdDefault then direction := GetDirection(dialog);
  113.  
  114.   if fEditor.SelAvail then
  115.   begin
  116.     if direction = sdForwards then
  117.     begin
  118.       fEditor.SelStart := fEditor.SelStart + 1;
  119.       fEditor.SelLength := 0;
  120.     end
  121.     else
  122.     begin
  123.       // Links von Selektion springen
  124.       fEditor.SelLength := 0;
  125.     end;
  126.   end;
  127.  
  128.   opt := [];
  129.   if frMatchCase in dialog.Options then Include(opt, ssoMatchCase);
  130.   if frWholeWord in dialog.Options then Include(opt, ssoWholeWord);
  131.   //if frReplace in dialog.Options then Include(opt, ssoReplace);
  132.   //if frReplaceAll in dialog.Options then Include(opt, ssoReplaceAll);
  133.   if direction = sdBackwards then Include(opt, ssoBackwards);
  134.   //Include(opt, ssoPrompt); // TODO: test. geht nicht?
  135.   //if fEditor.SelAvail then Include(opt, ssoSelectedOnly);  // TODO: geht nicht, weil er bei einer suche ja dann etwas selektirert und dann nicht weitergeht
  136.   Exclude(opt, ssoEntireScope); // TODO: ok?
  137.  
  138.   found := fEditor.SearchReplace(dialog.FindText, '', opt) > 0;
  139.  
  140.   if not found then
  141.   begin
  142.     // TODO: If single replace was chosen, behave like Notepad and select the last replaced word
  143.     if direction = sdForwards then
  144.       MessageDlg('End of document reached.', mtInformation, [mbOk], 0)
  145.     else
  146.       MessageDlg('Begin of document reached.', mtInformation, [mbOk], 0);
  147.   end;
  148.  
  149.   if fAutofocus and fEditor.CanFocus then fEditor.SetFocus;
  150. end;
  151.  
  152. procedure TSynEditFindReplace.DoReplace(dialog: TReplaceDialog; direction: TFindDirection);
  153. var
  154.   opt: TSynSearchOptions;
  155.   numReplacements: integer;
  156.   bakSelLenght: Integer;
  157.   stmp: string;
  158.   bakSelStart: Integer;
  159. begin
  160.   try
  161.     if direction = sdDefault then direction := GetDirection(dialog);
  162.  
  163.     opt := [];
  164.     if frMatchCase in dialog.Options then Include(opt, ssoMatchCase);
  165.     if frWholeWord in dialog.Options then Include(opt, ssoWholeWord);
  166.     if frReplace in dialog.Options then Include(opt, ssoReplace);
  167.     if frReplaceAll in dialog.Options then Include(opt, ssoReplaceAll);
  168.     if direction = sdBackwards then Include(opt, ssoBackwards);
  169.     Include(opt, ssoPrompt); // TODO: test. geht nicht?
  170.     if fEditor.SelAvail then Include(opt, ssoSelectedOnly);
  171.     Exclude(opt, ssoEntireScope); // TODO: ok?
  172.  
  173.     if not (ssoReplaceAll in opt) then
  174.     begin
  175.       if fEditor.SelLength = 0 then
  176.       begin
  177.         DoFind(dialog, sdForwards);
  178.         exit;
  179.       end;
  180.     end;
  181.  
  182.     fEditor.BeginUpdate; // TODO: geht nicht?
  183.     //fEditor.BeginUndoBlock;
  184.     try
  185.       bakSelLenght := 0; // avoid compiler warning
  186.       if ssoReplaceAll in opt then
  187.       begin
  188.         // Remember the selection length
  189.         bakSelLenght := fEditor.SelLength;
  190.         //bakSelStart := fEditor.SelStart;
  191.  
  192.         // Remember the selection start (we don't backup fEditor.SelStart, since the replacement might change the location)!
  193.         // We assume that character #1 will not be in a text file!
  194.         stmp := fEditor.Text;
  195.         Insert(chr(1), stmp, fEditor.SelStart+1);
  196.         //showmessage(inttostr(ord(stmp[fEditor.SelStart-1])));
  197.         //showmessage(inttostr(ord(stmp[fEditor.SelStart])));
  198.         //showmessage(inttostr(ord(stmp[fEditor.SelStart+1])));
  199.         fEditor.Text := stmp;
  200.  
  201.         // When the user presses "Replace all", then we want to replace from the very beginning!
  202.         fEditor.SelStart := 0;
  203.         fEditor.SelLength := 0;
  204.       end;
  205.  
  206.       numReplacements := fEditor.SearchReplace(dialog.FindText, dialog.ReplaceText, opt);
  207.  
  208.       if ssoReplaceAll in opt then
  209.       begin
  210.         stmp := fEditor.Text;
  211.         bakSelStart := AnsiPos(chr(1), stmp)-1;
  212.         Delete(stmp, bakSelStart+1, 1);
  213.         fEditor.Text := stmp;
  214.  
  215.         fEditor.SelStart := bakSelStart;
  216.         fEditor.SelLength := bakSelLenght;
  217.  
  218.         // TODO: The SelStart and SelLength were kept, but the scrollposition did not. What can we do?
  219.       end;
  220.     finally
  221.       //fEditor.EndUndoBlock;
  222.       fEditor.EndUpdate;
  223.     end;
  224.  
  225.     if not (ssoReplaceAll in opt) then
  226.     begin
  227.       DoFind(dialog, sdForwards);
  228.     end
  229.     else
  230.     begin
  231.       ShowMessageFmt('%d replaced.', [numReplacements]);
  232.     end;
  233.   finally
  234.     if fAutofocus and fEditor.CanFocus then fEditor.SetFocus;
  235.   end;
  236. end;
  237.  
  238. procedure TSynEditFindReplace.OnFind(Sender: TObject);
  239. begin
  240.   DoFind(Sender as TFindDialog, sdDefault);
  241. end;
  242.  
  243. procedure TSynEditFindReplace.OnReplace(Sender: TObject);
  244. begin
  245.   DoReplace(Sender as TReplaceDialog, sdDefault);
  246. end;
  247.  
  248. procedure TSynEditFindReplace.FindExecute;
  249. begin
  250.   fFindDialog.Execute;
  251. end;
  252.  
  253. procedure TSynEditFindReplace.ReplaceExecute;
  254. begin
  255.   fReplaceDialog.Execute;
  256. end;
  257.  
  258. procedure TSynEditFindReplace.FindContinue;
  259. begin
  260.   if fFindDialog.FindText = '' then
  261.   begin
  262.     fFindDialog.Options := fFindDialog.Options + [frDown]; // Default direction: down
  263.     FindExecute;
  264.   end
  265.   else
  266.     DoFind(fFindDialog, sdDefault);
  267. end;
  268.  
  269. procedure TSynEditFindReplace.FindNext;
  270. begin
  271.   if fFindDialog.FindText = '' then
  272.   begin
  273.     fFindDialog.Options := fFindDialog.Options + [frDown];
  274.     FindExecute;
  275.   end
  276.   else
  277.     DoFind(fFindDialog, sdForwards);
  278. end;
  279.  
  280. procedure TSynEditFindReplace.FindPrev;
  281. begin
  282.   if fFindDialog.FindText = '' then
  283.   begin
  284.     fFindDialog.Options := fFindDialog.Options - [frDown];
  285.     FindExecute;
  286.   end
  287.   else
  288.     DoFind(fFindDialog, sdBackwards);
  289. end;
  290.  
  291. procedure TSynEditFindReplace.GoToLine(LineNumber: integer);
  292. var
  293.   currentLine: integer;
  294.   i: integer;
  295. begin
  296.   currentLine := 1;
  297.  
  298.   for i := 1 to fEditor.GetTextLen do
  299.   begin
  300.     if currentLine = LineNumber then
  301.     begin
  302.       fEditor.selStart := i;
  303.       fEditor.SetFocus;
  304.       Exit;
  305.     end
  306.     else if fEditor.Text = #$D then
  307.       inc(currentLine);
  308.   end;
  309. end;
  310.  
  311. procedure TSynEditFindReplace.CloseDialogs;
  312. begin
  313.   fFindDialog.CloseDialog;
  314.   fReplaceDialog.CloseDialog;
  315. end;
  316.  
  317. end.
  318.