Subversion Repositories fastphp

Compare Revisions

Regard whitespace Rev 21 → Rev 22

/trunk/FindReplace.pas
1,7 → 1,9
unit FindReplace;
 
// FindReplace.pas
// Source: http://www.tek-tips.com/viewthread.cfm?qid=160357
// Some changed by Daniel Marschall, especially to make it compatible with TSynEdit
// 18 Nov 2001 "bearsite4"
// Changes by Daniel Marschall, especially to make it compatible with TSynEdit
 
interface
 
18,6 → 20,8
the word can't be found but instead to set the state to frcEndReached to
let the replace function know it's reached the end of the text}
 
TReplaceFunc = function ( const S1, S2: string ): Integer;
 
TFindReplace = class(TComponent)
private
fEditor: TSynEdit; {the richedit or memo component to hook it up to}
24,9 → 28,6
fReplaceDialog: TReplaceDialog; {the replace dialog}
fFindDialog: TFindDialog; {the find dialog}
 
FindTextLength: integer; {the length of the text we want to find}
TextToFind: string; {the text to find}
 
FindActionOnEnd: FindReplaceCommunication; {the action the find function
should take when it reaches the end of the text while searching for the
word}
37,14 → 38,12
test multiple words in a single string as well.}
 
protected
StringComparison: function ( const S1, S2: string ): Integer;
{the function to use to compare 2 strings. Should be assigned
different values according to the search criteria}
procedure ProcessCriteria( Sender: TFindDialog );
{set some internals given the search criteria such as match case}
type
TFindDirection = (sdDefault, sdForwards, sdBackwards);
 
procedure FindForwards( Sender: TFindDialog; start, finish: integer );
{search through the editor in a forwards direction}
procedure FindBackwards( start, finish: integer );
procedure FindBackwards( Sender: TFindDialog; start, finish: integer );
{search through the editor in a backwards direction}
 
{defined event handlers}
53,36 → 52,38
 
{the centralised find/replace functions}
function TryAndMatch( Sender: TFindDialog; index, finish: integer ): boolean; virtual;
function TryAndReplace: boolean; virtual;
function TryAndReplace(dialog: TReplaceDialog): boolean; virtual;
 
procedure DoReplace; virtual;
procedure DoReplace(dialog: TReplaceDialog); virtual;
{the replace function that coordinates all the work}
procedure DoReplaceAll; virtual;
procedure DoReplaceAll(dialog: TReplaceDialog); virtual;
{the replace all function that coordinates all the work}
 
procedure DoFind(dialog: TFindDialog; direction: TFindDirection);
 
public
constructor Create( AOwner: TComponent); override;
 
property _FindDialog: TFindDialog read fFindDialog;
property _ReplaceDialog: TReplaceDialog read fReplaceDialog;
property FindDialog: TFindDialog read fFindDialog;
property ReplaceDialog: TReplaceDialog read fReplaceDialog;
 
procedure CloseDialogs;
 
procedure FindExecute;
{opens the find dialog}
procedure ReplaceExecute;
{opens the replace dialog}
procedure FindNext; overload;
{finds the next occurence of the character}
procedure FindNext( errorMessage: string ); overload;
{same as above except allows you to specify the message to display
if the user hasn't picked a search word}
 
procedure FindContinue;
procedure FindNext;
procedure FindPrev;
 
procedure GoToLine( LineNumber: integer );
procedure GetLineNumber( Position: Integer; var LineNumber, ColumnNumber: Integer );
{returns the line and column number the cursor is on in the editor}
 
published
property Editor: TSynEdit
read fEditor write fEditor;
property Editor: TSynEdit read fEditor write fEditor;
end;
 
(*
116,19 → 117,6
FindActionOnEnd := frcAlertUser;
end;
 
procedure TFindReplace.ProcessCriteria( Sender: TFindDialog );
begin
 
{assign a case sensitive or case insensitive string
comparison function to StringComparison depending
on the whether or not the user chose to match case.
The functions assigned are normal VCL functions.}
if frMatchCase in Sender.Options then
StringComparison := CompareStr
else StringComparison := CompareText;
 
end;
 
procedure TFindReplace.FindForwards( Sender: TFindDialog; start, finish: integer );
var
i: integer;
135,10 → 123,6
 
begin
 
{because we'll be using the length of the text to search for
often, we should calculate it here to save time}
FindTextLength := Length( TextToFind );
 
{to find the word we go through the text on a character by character
basis}
for i := start to finish do
148,10 → 132,10
 
end;
 
procedure TFindReplace.FindBackwards( start, finish: Integer );
procedure TFindReplace.FindBackwards( Sender: TFindDialog; start, finish: Integer );
{since only find has a (search) up option and replace doesn't
we don't have to worry about sender since only the onFind will
be calling thi function}
be calling this function}
 
var
i: integer;
159,12 → 143,10
begin
{See comments for findforward}
 
FindTextLength := Length( TextToFind );
 
{to find the word we go through the text on a character by character
basis but working backwards}
for i := finish downto start do
if TryAndMatch( fFindDialog, i, start ) then
if TryAndMatch( Sender, i, start ) then
Exit;
 
end;
174,11 → 156,30
var
StringToTest: string;
 
StringComparison: TReplaceFunc;
{the function to use to compare 2 strings. Should be assigned
different values according to the search criteria}
 
FindTextLength: integer;
 
resourcestring
S_CANT_BE_FOUND = '%s could not be found';
begin
FindTextLength := Length( Sender.FindText );
 
{create a new string to test against}
StringToTest := copy( fEditor.Text, index+1, FindTextLength );
 
if (StringComparison( StringToTest, TextToFind ) = 0) and
{assign a case sensitive or case insensitive string
comparison function to StringComparison depending
on the whether or not the user chose to match case.
The functions assigned are normal VCL functions.}
if frMatchCase in Sender.Options then
StringComparison := CompareStr
else
StringComparison := CompareText;
 
if (StringComparison( StringToTest, Sender.FindText ) = 0) and
TestWholeWord( Sender, copy( fEditor.Text, index, FindTextLength+2 ) ) then
{with TestWholeWord we pass the value index not index+1 so that it will also
get the previous character. We pass the value FindTextLenght+2 so it
196,7 → 197,7
{if we've tried the last character and we can't find it then
display a message saying so.}
else if (index = finish) and (FindActionOnEnd = frcAlertUser) then
ShowMessage( TextToFind + ' could not be found' )
ShowMessageFmt(S_CANT_BE_FOUND, [Sender.FindText])
{otherwise if the replace function requested us to keep quiet
about it then don't display the message to the user}
else if (index = finish) and (FindActionOnEnd = frcAlertReplace) then
205,14 → 206,19
Result := false; {didn't find it}
end;
 
procedure TFindReplace.OnFind( Sender: TObject );
procedure TFindReplace.DoFind(dialog: TFindDialog; direction: TFindDirection);
var
// highlightedText: pChar;
highlightedText: string;
 
begin
{handle all the user options}
ProcessCriteria( Sender as TFindDialog );
if direction = sdDefault then
begin
if frDown in dialog.Options then
direction := sdForwards
else
direction := sdBackwards;
end;
 
{check if there is already some highlighted text. If there is and
this text is the text to search for then it's probably been highlighted
229,11 → 235,12
highlightedText := fEditor.SelText;
 
{compare the two strings}
if StrIComp( PChar(highlightedText), pChar( fFindDialog.FindText ) ) = 0 then
if StrIComp( PChar(highlightedText), pChar( dialog.FindText ) ) = 0 then
begin
if frDown in (Sender as TFindDialog).Options then
if direction = sdForwards then
fEditor.selStart := fEditor.SelStart + fEditor.SelLength
else fEditor.selStart := fEditor.SelStart - 1;
else
fEditor.selStart := fEditor.SelStart - 1;
end;
 
(*
241,18 → 248,16
*)
end;
 
{set the text to find to the findtext field of the find dialog}
TextToFind := (Sender as TFindDialog).FindText;
 
{begin the search}
if frDown in (Sender as TFindDialog).Options then {the user choose to search down}
if direction = sdForwards then {the user choose to search down}
begin
{if the user has highlighted a block of text only search
within that block}
if fEditor.SelLength > 0 then
FindForwards( (Sender as TFindDialog), fEditor.selStart, fEditor.selStart + fEditor.selLength )
FindForwards( dialog, fEditor.selStart, fEditor.selStart + fEditor.selLength )
{otherwise search the whole of the text}
else FindForwards( (Sender as TFindDialog), fEditor.selStart, fEditor.GetTextLen );
else
FindForwards( dialog, fEditor.selStart, fEditor.GetTextLen );
end
else {the user chose to search up}
begin
259,33 → 264,41
{if the user has highlighted a block of text only search
within that block}
if fEditor.SelLength > 0 then
FindBackwards( fEditor.selStart, fEditor.selStart + fEditor.selLength )
FindBackwards( dialog, fEditor.selStart, fEditor.selStart + fEditor.selLength )
{otherwise search the whole of the text}
else FindBackwards( 0, fEditor.selStart );
else
FindBackwards( dialog, 0, fEditor.selStart );
end;
end;
 
procedure TFindReplace.OnFind(Sender: TObject);
var
FindDialog: TFindDialog;
begin
FindDialog := Sender as TFindDialog;
DoFind(FindDialog, sdDefault);
end;
 
procedure TFindReplace.OnReplace( Sender: TOBject );
procedure TFindReplace.OnReplace( Sender: TObject );
var
ReplaceDialog: TReplaceDialog;
begin
ProcessCriteria( fReplaceDialog );
ReplaceDialog := Sender as TReplaceDialog;
 
{set the action on end to alert the function not the user}
FindActionOnEnd := frcAlertReplace;
 
{set the text to find to the findtext field of the replace dialog}
TextToFind := fReplaceDialog.FindText;
 
{now replace the word}
if frReplace in fReplaceDialog.Options then
DoReplace
else DoReplaceAll;
if frReplace in ReplaceDialog.Options then
DoReplace(ReplaceDialog)
else
DoReplaceAll(ReplaceDialog);
 
{reset the action on end to alert the user}
FindActionOnEnd := frcAlertUser;
end;
 
procedure TFindReplace.DoReplace;
procedure TFindReplace.DoReplace(dialog: TReplaceDialog);
begin
 
{if the user has highlighted a block of text only replace
292,51 → 305,51
within that block}
if fEditor.SelLength > 0 then
begin
FindForwards( fReplaceDialog, fEditor.selStart, fEditor.selStart + fEditor.selLength );
TryAndReplace;
FindForwards( dialog, fEditor.selStart, fEditor.selStart + fEditor.selLength );
TryAndReplace(dialog);
end
{otherwise replace within the whole of the text}
else
begin
FindForwards( fReplaceDialog, fEditor.selStart, fEditor.GetTextLen );
TryAndReplace;
FindForwards( dialog, fEditor.selStart, fEditor.GetTextLen );
TryAndReplace(dialog);
end;
 
end;
 
procedure TFindReplace.DoReplaceAll;
procedure TFindReplace.DoReplaceAll(dialog: TReplaceDialog);
begin
{see comments for DoReplace}
 
if fEditor.SelLength > 0 then
begin
FindForwards( fReplaceDialog, fEditor.selStart, fEditor.selStart + fEditor.selLength );
FindForwards( dialog, fEditor.selStart, fEditor.selStart + fEditor.selLength );
{keep replacing until we reach the end of the text}
while FindActionOnEnd <> frcEndReached do
begin
{we enclose the TryAndReplace in a loop because there might be more
than one occurence of the word in the line}
while TryAndReplace do
FindForwards( fReplaceDialog, fEditor.selStart, fEditor.selStart + fEditor.selLength );
while TryAndReplace(dialog) do
FindForwards( dialog, fEditor.selStart, fEditor.selStart + fEditor.selLength );
 
FindForwards( fReplaceDialog, fEditor.selStart, fEditor.selStart + fEditor.selLength );
FindForwards( dialog, fEditor.selStart, fEditor.selStart + fEditor.selLength );
end;
end
else
begin
FindForwards( fReplaceDialog, fEditor.selStart, fEditor.GetTextLen );
FindForwards( dialog, fEditor.selStart, fEditor.GetTextLen );
while FindActionOnEnd <> frcEndReached do
begin
while TryAndReplace do
FindForwards( fReplaceDialog, fEditor.selStart, fEditor.GetTextLen );
while TryAndReplace(dialog) do
FindForwards( dialog, fEditor.selStart, fEditor.GetTextLen );
 
FindForwards( fReplaceDialog, fEditor.selStart, fEditor.GetTextLen );
FindForwards( dialog, fEditor.selStart, fEditor.GetTextLen );
end;
end;
 
end;
 
function TFindReplace.TryAndReplace: boolean;
function TFindReplace.TryAndReplace(dialog: TReplaceDialog): boolean;
{returns true if a replacement was made and false otherwise. This is
so a function can keep calling TryAndReplace until it returns false
since there might be more than one occurence of the word to replace
346,7 → 359,7
LineNumber, ColumnNumber: integer;
ReplacementString: string; {string used to replace the text}
 
OldSelStart: integer; {the position of the cursore prior to the text
OldSelStart: integer; {the position of the cursor prior to the text
being replaced}
 
 
378,7 → 391,7
{truncate the newline (#$A#$D) at the end of tempstring}
SetLength( ReplacementString, Length( ReplacementString )-2 );
{add the replacement text into tempstring}
Insert( fReplaceDialog.ReplaceText, ReplacementString, ColumnNumber+1 );
Insert( dialog.ReplaceText, ReplacementString, ColumnNumber+1 );
{remove the old string and add the new string into the editor}
fEditor.Lines.Delete( LineNumber );
fEditor.Lines.Insert( LineNumber, ReplacementString );
389,7 → 402,7
{reposition the cursor to the character after the last chracter in
the newly replacing text. This is mainly so we can locate multiple
occurences of the to-be-replaced text in the same line}
fEditor.SelStart := oldSelStart + length( fReplaceDialog.ReplaceText );
fEditor.SelStart := oldSelStart + length( dialog.ReplaceText );
end
end;
 
404,10 → 417,14
end;
 
function TFindReplace.TestWholeWord( Sender: TFindDialog; TestString: string ): boolean;
var
FindTextLength: integer;
begin
{assume it's not a whole word}
Result := false;
 
FindTextLength := Length( Sender.FindText );
 
{if the user didn't choose whole words only then basically
we don't care about it so return true}
if not (frWholeWord in Sender.Options) then
436,22 → 453,37
 
end;
 
procedure TFindReplace.FindNext;
procedure TFindReplace.FindContinue;
begin
FindNext( 'Please chose a search word' );
if fFindDialog.FindText = '' then
begin
fFindDialog.Options := fFindDialog.Options + [frDown]; // Default direction: down
FindExecute;
end
else
DoFind(fFindDialog, sdDefault);
end;
 
procedure TFindReplace.FindNext( errorMessage: string );
procedure TFindReplace.FindNext;
begin
 
if fFindDialog.FindText = '' then
begin
ShowMessage( errorMessage );
Exit;
fFindDialog.Options := fFindDialog.Options + [frDown];
FindExecute;
end
else
DoFind(fFindDialog, sdForwards);
end;
 
{I'm not sure if I should pass fFindDialog as sender}
OnFind( fFindDialog );
procedure TFindReplace.FindPrev;
begin
if fFindDialog.FindText = '' then
begin
fFindDialog.Options := fFindDialog.Options - [frDown];
FindExecute;
end
else
DoFind(fFindDialog, sdBackwards);
end;
 
procedure TFindReplace.GetLineNumber( Position: Integer; var LineNumber, ColumnNumber: integer );
501,6 → 533,12
 
end;
 
procedure TFindReplace.CloseDialogs;
begin
fFindDialog.CloseDialog;
fReplaceDialog.CloseDialog;
end;
 
(*
procedure Register;
begin