Subversion Repositories fastphp

Rev

Rev 88 | 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. resourcestring
  112.   SBofReached = 'Begin of document reached.';
  113.   SEofReached = 'End of document reached.';
  114. begin
  115.   if direction = sdDefault then direction := GetDirection(dialog);
  116.  
  117.   if fEditor.SelAvail then
  118.   begin
  119.     if direction = sdForwards then
  120.     begin
  121.       fEditor.SelStart := fEditor.SelStart + 1;
  122.       fEditor.SelLength := 0;
  123.     end
  124.     else
  125.     begin
  126.       // Jump left to selection
  127.       fEditor.SelLength := 0;
  128.     end;
  129.   end;
  130.  
  131.   opt := [];
  132.   if frMatchCase in dialog.Options then Include(opt, ssoMatchCase);
  133.   if frWholeWord in dialog.Options then Include(opt, ssoWholeWord);
  134.   //if frReplace in dialog.Options then Include(opt, ssoReplace);
  135.   //if frReplaceAll in dialog.Options then Include(opt, ssoReplaceAll);
  136.   if direction = sdBackwards then Include(opt, ssoBackwards);
  137.   //Include(opt, ssoPrompt); // TODO: test. does not work?
  138.   //if fEditor.SelAvail then Include(opt, ssoSelectedOnly);  // TODO: doesn't work because when you search it selects something and then doesn't go any further
  139.   Exclude(opt, ssoEntireScope); // TODO: ok?
  140.  
  141.   found := fEditor.SearchReplace(dialog.FindText, '', opt) > 0;
  142.  
  143.   if not found then
  144.   begin
  145.     // TODO: If single replace was chosen, behave like Notepad and select the last replaced word
  146.     if direction = sdForwards then
  147.       MessageDlg(SEofReached, mtInformation, [mbOk], 0)
  148.     else
  149.       MessageDlg(SBofReached, mtInformation, [mbOk], 0);
  150.   end;
  151.  
  152.   if fAutofocus and fEditor.CanFocus then fEditor.SetFocus;
  153. end;
  154.  
  155. procedure TSynEditFindReplace.DoReplace(dialog: TReplaceDialog; direction: TFindDirection);
  156. var
  157.   opt: TSynSearchOptions;
  158.   numReplacements: integer;
  159.   bakSelLenght, bakSelStart, bakSizeOld: Integer;
  160. resourcestring
  161.   SReplaceAllDoneEntireScope = '%d replaced in entire scope.';
  162.   SReplaceAllDoneSelectionOnly = '%d replaced in selection.';
  163. begin
  164.   try
  165.     if direction = sdDefault then direction := GetDirection(dialog);
  166.  
  167.     opt := [];
  168.     if frMatchCase in dialog.Options then Include(opt, ssoMatchCase);
  169.     if frWholeWord in dialog.Options then Include(opt, ssoWholeWord);
  170.     if frReplace in dialog.Options then Include(opt, ssoReplace);
  171.     if frReplaceAll in dialog.Options then Include(opt, ssoReplaceAll);
  172.     if direction = sdBackwards then Include(opt, ssoBackwards);
  173.     Include(opt, ssoPrompt); // TODO: test. does not work?
  174.     if fEditor.SelAvail then
  175.     begin
  176.       Include(opt, ssoSelectedOnly);
  177.       Exclude(opt, ssoEntireScope);
  178.     end
  179.     else
  180.     begin
  181.       Include(opt, ssoEntireScope);
  182.       Exclude(opt, ssoSelectedOnly);
  183.     end;
  184.  
  185.     if not (ssoReplaceAll in opt) then
  186.     begin
  187.       if fEditor.SelLength = 0 then
  188.       begin
  189.         DoFind(dialog, sdForwards);
  190.         exit;
  191.       end;
  192.     end;
  193.  
  194.     fEditor.BeginUpdate; // For "replace all": avoid that the user sees how the program scrolls through the document and do replacements
  195.     fEditor.BeginUndoBlock; // For "replace all": avoid that every replacement gets their own undo step
  196.     try
  197.       // will be needed later
  198.       bakSelLenght := fEditor.SelLength;
  199.       bakSelStart := fEditor.SelStart;
  200.       bakSizeOld := Length(fEditor.Text);
  201.  
  202.       if (ssoReplaceAll in opt) and (ssoEntireScope in opt) then
  203.       begin
  204.         // Remember the selection start (we don't backup fEditor.SelStart, since the replacement might change the location)! by adding this character to the current cursor position
  205.         // We assume that character #1 will not be in a text file!
  206.         fEditor.SelLength := 0;
  207.         fEditor.SelText := chr(1);
  208.       end;
  209.  
  210.       numReplacements := fEditor.SearchReplace(dialog.FindText, dialog.ReplaceText, opt);
  211.  
  212.       // Restore position and selection after replacement
  213.       // TODO: The SelStart and SelLength were kept, but the scrollposition did not. What can we do?
  214.       if ssoReplaceAll in opt then
  215.       begin
  216.         if ssoEntireScope in opt then
  217.         begin
  218.           // Remove the temporary marker chr(1) and jump to that spot
  219.           fEditor.SelStart := AnsiPos(chr(1), fEditor.Text)-1;
  220.           fEditor.SelLength := 1;
  221.           fEditor.SelText := ''; // remove the chr(1) again
  222.           // restore initial select length
  223.           fEditor.SelLength := bakSelLenght;
  224.         end
  225.         else if (ssoSelectedOnly in opt) then
  226.         begin
  227.           // restore initial selection
  228.           fEditor.SelStart := bakSelStart;
  229.           fEditor.SelLength := bakSelLenght + (Length(fEditor.Text) - bakSizeOld); // length will be adjusted, depending if the replacement changed length
  230.         end;
  231.       end;
  232.     finally
  233.       fEditor.EndUndoBlock;
  234.       fEditor.EndUpdate;
  235.     end;
  236.  
  237.     if (ssoReplaceAll in opt) and (ssoEntireScope in opt) then
  238.     begin
  239.       ShowMessageFmt(SReplaceAllDoneEntireScope, [numReplacements]);
  240.     end
  241.     else if (ssoReplaceAll in opt) and (ssoSelectedOnly in opt) then
  242.     begin
  243.       ShowMessageFmt(SReplaceAllDoneSelectionOnly, [numReplacements]);
  244.     end
  245.     else
  246.     begin
  247.       DoFind(dialog, sdForwards);
  248.     end;
  249.   finally
  250.     if fAutofocus and fEditor.CanFocus then fEditor.SetFocus;
  251.   end;
  252. end;
  253.  
  254. procedure TSynEditFindReplace.OnFind(Sender: TObject);
  255. begin
  256.   DoFind(Sender as TFindDialog, sdDefault);
  257. end;
  258.  
  259. procedure TSynEditFindReplace.OnReplace(Sender: TObject);
  260. begin
  261.   DoReplace(Sender as TReplaceDialog, sdDefault);
  262. end;
  263.  
  264. procedure TSynEditFindReplace.FindExecute;
  265. begin
  266.   fFindDialog.Execute;
  267. end;
  268.  
  269. procedure TSynEditFindReplace.ReplaceExecute;
  270. begin
  271.   fReplaceDialog.Execute;
  272. end;
  273.  
  274. procedure TSynEditFindReplace.FindContinue;
  275. begin
  276.   if fFindDialog.FindText = '' then
  277.   begin
  278.     fFindDialog.Options := fFindDialog.Options + [frDown]; // Default direction: down
  279.     FindExecute;
  280.   end
  281.   else
  282.     DoFind(fFindDialog, sdDefault);
  283. end;
  284.  
  285. procedure TSynEditFindReplace.FindNext;
  286. begin
  287.   if fFindDialog.FindText = '' then
  288.   begin
  289.     fFindDialog.Options := fFindDialog.Options + [frDown];
  290.     FindExecute;
  291.   end
  292.   else
  293.     DoFind(fFindDialog, sdForwards);
  294. end;
  295.  
  296. procedure TSynEditFindReplace.FindPrev;
  297. begin
  298.   if fFindDialog.FindText = '' then
  299.   begin
  300.     fFindDialog.Options := fFindDialog.Options - [frDown];
  301.     FindExecute;
  302.   end
  303.   else
  304.     DoFind(fFindDialog, sdBackwards);
  305. end;
  306.  
  307. procedure TSynEditFindReplace.GoToLine(LineNumber: integer);
  308. var
  309.   currentLine: integer;
  310.   i: integer;
  311. begin
  312.   currentLine := 1;
  313.  
  314.   for i := 1 to fEditor.GetTextLen do
  315.   begin
  316.     if currentLine = LineNumber then
  317.     begin
  318.       fEditor.selStart := i;
  319.       fEditor.SetFocus;
  320.       Exit;
  321.     end
  322.     else if fEditor.Text = #$D then
  323.       inc(currentLine);
  324.   end;
  325. end;
  326.  
  327. procedure TSynEditFindReplace.CloseDialogs;
  328. begin
  329.   fFindDialog.CloseDialog;
  330.   fReplaceDialog.CloseDialog;
  331. end;
  332.  
  333. end.
  334.