1 {
2 /***************************************************************************
3 SearchFrm.pas
4 -------------------
5
6 ***************************************************************************/
7
8 ***************************************************************************
9 * *
10 * This source is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 * This code is distributed in the hope that it will be useful, but *
16 * WITHOUT ANY WARRANTY; without even the implied warranty of *
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
18 * General Public License for more details. *
19 * *
20 * A copy of the GNU General Public License is available on the World *
21 * Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also *
22 * obtain it by writing to the Free Software Foundation, *
23 * Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA. *
24 * *
25 ***************************************************************************
26 }
27 unit SearchFrm;
28
29 {$mode objfpc}{$H+}
30
31 interface
32
33 uses
34 // RTL + FCL
35 Classes, SysUtils, types, RegExpr,
36 // LCL
37 LCLIntf, Forms, Controls, ComCtrls, Dialogs, ExtCtrls, StdCtrls, Buttons,
38 // CodeTools
39 SourceLog, KeywordFuncLists, BasicCodeTools, FileProcs,
40 // LazUtils
41 FileUtil, LazFileUtils, LazFileCache, LazTracer,
42 // IDEIntf
43 IDEWindowIntf, LazIDEIntf, SrcEditorIntf, IDEDialogs, ProjectGroupIntf,
44 // ide
45 LazarusIDEStrConsts, InputHistory, IDEProcs, SearchResultView, Project;
46
47 type
48
49 { TSearchProgressForm }
50
51 TSearchProgressForm = class(TForm)
52 btnCancel: TBitBtn;
53 MatchesLabel: TLABEL;
54 SearchingLabel: TLABEL;
55 SearchTextLabel: TLABEL;
56 lblMatches: TLABEL;
57 lblProgress: TLABEL;
58 lblSearchText: TLABEL;
59 Panel2: TPANEL;
60 procedure FormClose(Sender: TObject; var {%H-}CloseAction: TCloseAction);
61 procedure FormShow(Sender: TObject);
62 procedure OnAddMatch(const Filename: string; const StartPos, EndPos: TPoint;
63 const Lines: string);
64 procedure SearchFormCREATE(Sender: TObject);
65 procedure SearchFormDESTROY(Sender: TObject);
66 procedure btnAbortCLICK(Sender: TObject);
67 private
68 fFlags: TSrcEditSearchOptions;
69 fAbortString: string;
70 fMask: string;
71 fMatches: longint;
72 fPad: string;
73 FProgress: TIDESearchInTextProgress;
74 fPromptOnReplace: boolean;
75 fRecursive: boolean;
76 FReplaceText: string;
77 fResultsListUpdating: boolean;
78 fResultsList: TStrings;
79 fResultsWindow: TTabSheet;
80 fSearchFileList: TStringList;
81 fSearchFiles: boolean;
82 fSearchFor: String;
83 fDirectories: string;
84 fSearchOpen: boolean;
85 fSearchActive: boolean;
86 fSearchProject: boolean;
87 fSearchProjectGroup: boolean;
88 fAborting: boolean;
89 fLastUpdateProgress: DWORD;
90 fWasActive: boolean;
91 procedure DoFindInFiles(ADirectories: string);
92 procedure DoFindInSearchList;
93 procedure SetResultsList(const AValue: TStrings);
94 procedure UpdateMatches;
95 procedure UpdateProgress(FileName: string);
PadAndShortennull96 function PadAndShorten(FileName: string): string;
97 procedure SetOptions(TheOptions: TLazFindInFileSearchOptions);
GetOptionsnull98 function GetOptions: TLazFindInFileSearchOptions;
99 procedure SearchFile(const aFilename: string);
100 procedure SetFlag(Flag: TSrcEditSearchOption; AValue: boolean);
101 procedure DoSearchAndAddToSearchResults;
DoSearchnull102 function DoSearch: integer;
103 public
104 procedure DoSearchOpenFiles;
105 procedure DoSearchActiveFile;
106 procedure DoSearchDirs;
107 procedure DoSearchProject(AProject: TProject);
108 procedure DoSearchProjectGroup;
109 public
110 property SearchDirectories: string read fDirectories write fDirectories;
111 property SearchText: string read fSearchFor write fSearchFor;
112 property ReplaceText: string read FReplaceText write FReplaceText;
113 property SearchOptions: TLazFindInFileSearchOptions read GetOptions
114 write SetOptions;
115 property SearchFileList: TStringList read fSearchFileList
116 write fSearchFileList;
117 property ResultsList: TStrings read fResultsList write SetResultsList;
118 property SearchMask: string read fMask write fMask;
119 property Pad: string read fPad write fPad;
120 property ResultsWindow: TTabSheet read fResultsWindow write fResultsWindow;
121 property PromptOnReplace: boolean read fPromptOnReplace write fPromptOnReplace;// this is asked once and can be changed when prompting
122 property Progress: TIDESearchInTextProgress read FProgress;
123 end;
124
125 var
126 SearchProgressForm: TSearchProgressForm;
127
SearchInTextnull128 function SearchInText(const TheFileName: string;
129 var TheText: string;// if TheFileName='' then use TheText
130 SearchFor, ReplaceText: string;
131 Flags: TSrcEditSearchOptions; var Prompt: boolean;
132 Progress: TIDESearchInTextProgress = nil
133 ): TModalResult;
TrimLinesAndAdjustPosnull134 function TrimLinesAndAdjustPos(const Lines: string; var APosition: integer): string;
SearchInLinenull135 function SearchInLine(const SearchStr: string; SrcLog: TSourceLog;
136 LineNumber: integer; WholeWords: boolean; StartInLine: integer;
137 out MatchStartInLine: integer): boolean;
138
139
140 implementation
141
142 {$R *.lfm}
143
144 const
145 WordBreakChars = [#0..#31,'.', ',', ';', ':', '"', '''', '!', '?', '[', ']',
146 '(', ')', '{', '}', '^', '-', '=', '+', '*', '/', '\', '|', ' '];
147 WhiteSpaceChars = [' ',#10,#13,#9];
148
SearchInLinenull149 function SearchInLine(const SearchStr: string; SrcLog: TSourceLog;
150 LineNumber: integer; WholeWords: boolean; StartInLine: integer;
151 out MatchStartInLine: integer): boolean;
152 // search SearchStr in SrcLog line
153 // returns MatchStartInLine=1 for start of line
154 var
155 LineRange: TLineRange;
156 Src: String;
157 StartPos: PChar;
158 EndPos: PChar;
159 i: Integer;
160 SearchLen: Integer;
161 LineStartPos: PChar;
162 FirstChar: Char;
163 Found: Boolean;
164 CharInFront: PChar;
165 CharBehind: PChar;
166 begin
167 Result:=false;
168 if SearchStr='' then exit;
169 SrcLog.GetLineRange(LineNumber-1,LineRange);
170 Src:=SrcLog.Source;
171 SearchLen:=length(SearchStr);
172 LineStartPos:=@Src[LineRange.StartPos];
173 StartPos:=LineStartPos+StartInLine-1;
174 EndPos:=@Src[LineRange.EndPos-SearchLen+1];
175 FirstChar:=SearchStr[1];
176 while (StartPos<EndPos) do begin
177 if FirstChar=StartPos^ then begin
178 i:=1;
179 while (i<=SearchLen) and (StartPos[i-1]=SearchStr[i]) do
180 inc(i);
181 if i>SearchLen then begin
182 Found:=true;
183 MatchStartInLine:=StartPos-LineStartPos+1;
184 if WholeWords then begin
185 CharInFront:=StartPos-1;
186 CharBehind:=StartPos+SearchLen;
187 if ((MatchStartInLine=1)
188 or (CharInFront^ in WordBreakChars))
189 and ((StartPos+SearchLen=@Src[LineRange.EndPos])
190 or (CharBehind^ in WordBreakChars))
191 then begin
192 // word start and word end
193 end else begin
194 // not whole word
195 Found:=false;
196 end;
197 end;
198 if Found then begin
199 Result:=true;
200 exit;
201 end;
202 end;
203 end;
204 inc(StartPos);
205 end;
206 end;
207
TrimLinesAndAdjustPosnull208 function TrimLinesAndAdjustPos(const Lines: string;
209 var APosition: integer): string;
210 var
211 StartPos: Integer;
212 EndPos: Integer;
213 begin
214 if Lines='' then begin
215 Result:='';
216 exit;
217 end;
218 if LineEndCount(Lines)=0 then begin
219 StartPos:=1;
220 while (StartPos<=length(Lines)) and (Lines[StartPos] in WhiteSpaceChars) do
221 inc(StartPos);
222 if (APosition>0) and (StartPos>APosition) then
223 StartPos:=APosition;
224 EndPos:=length(Lines)+1;
225 while (EndPos>=StartPos) and (Lines[EndPos-1] in WhiteSpaceChars) do
226 dec(EndPos);
227 dec(APosition,StartPos-1);
228 Result:=copy(Lines,StartPos,EndPos-StartPos);
229 end else
230 Result:=Lines;
231 end;
232
SearchInTextnull233 function SearchInText(const TheFileName: string;
234 var TheText: string;// if TheFileName='' then use TheText
235 SearchFor, ReplaceText: string;
236 Flags: TSrcEditSearchOptions; var Prompt: boolean;
237 Progress: TIDESearchInTextProgress = nil
238 ): TModalResult;
239 var
240 OriginalFile: TSourceLog;// The original File being searched
241 CaseFile: TSourceLog; // The working File being searched
242 FoundStartPos: TPoint; // Position of match in line. 1 based.
243 FoundEndPos: TPoint;
244 ReplaceLineOffset: integer;// number of lines added/deleted by replacement.
245 LastReplaceLine: integer; // last changed line by replace. 1 based
246 LastReplaceColOffset: integer;// bytes added/deleted by replace in last line
247 TempSearch: string; // Temp Storage for the search string.
248 RE: TRegExpr;
249 Lines: String;
250
251 SrcEditValid: Boolean;// true if SrcEdit is valid
252 SrcEdit: TSourceEditorInterface;
253 PaintLockEnabled: Boolean;
254
255 ReplacedText: PChar;
256 ReplacedTextCapacity: integer;
257 ReplacedTextLength: integer;
258 ReplacedTextOriginalPos: integer;// 1-based. e.g. 2 bytes has been replaced => ReplacedTextOriginalPos=3.
259
260 procedure DoAbort;
261 begin
262 if Progress<>nil then
263 Progress.Abort:=true;
264 Result:=mrAbort;
265 end;
266
267 procedure ProcessMessages;
268 begin
269 if Application<>nil then Application.ProcessMessages;
270 if (Progress<>nil) and Progress.Abort then
271 Result:=mrAbort;
272 end;
273
FileIsOpenInSourceEditornull274 function FileIsOpenInSourceEditor: boolean;
275 begin
276 if not SrcEditValid then begin
277 if (TheFileName<>'') and (SourceEditorManagerIntf<>nil) then
278 SrcEdit:=SourceEditorManagerIntf.SourceEditorIntfWithFilename(TheFileName)
279 else
280 SrcEdit:=nil;
281 SrcEditValid:=true;
282 end;
283 Result:=SrcEdit<>nil;
284 end;
285
286 procedure GrowNewText(NewLength: integer);
287 var
288 NewCapacity: Integer;
289 begin
290 if NewLength<=ReplacedTextCapacity then exit;
291 // grow
292 // first double
293 NewCapacity:=ReplacedTextCapacity*2;
294 if NewLength>NewCapacity then begin
295 // double is not enough, use the original size as minimum
296 if NewCapacity<1 then
297 NewCapacity:=OriginalFile.SourceLength+1000;
298 if NewLength>NewCapacity then begin
299 // still not enough -> grow to new length
300 NewCapacity:=NewLength;
301 end;
302 end;
303 ReplacedTextCapacity:=NewCapacity;
304 ReAllocMem(ReplacedText,ReplacedTextCapacity);
305 end;
306
307 procedure EnablePaintLock;
308 begin
309 if (not PaintLockEnabled) and FileIsOpenInSourceEditor then begin
310 PaintLockEnabled:=true;
311 SrcEdit.BeginUpdate;
312 end;
313 end;
314
315 procedure DisablePaintLock;
316 begin
317 if PaintLockEnabled then
318 SrcEdit.EndUpdate;
319 PaintLockEnabled:=false;
320 end;
321
322 procedure EndLocks;
323 begin
324 DisablePaintLock;
325 SrcEditValid:=false;
326 end;
327
328 procedure DoReplaceLine;
329 var
330 AReplace: String;
331 Action: TSrcEditReplaceAction;
332 OriginalTextPos: integer; // 1-based
333 GapLength: Integer;
334 NewLength: Integer;
335 SrcEditPosValid: boolean;
336 SrcEditStartPos, SrcEditEndPos: TPoint;
337 aLastLineLength: integer;
338 aLineCount: integer;
339 i: integer;
340
341 procedure GetSrcEditPos;
342 begin
343 if not SrcEditPosValid then begin
344 SrcEditStartPos:=FoundStartPos;
345 SrcEditEndPos:=FoundEndPos;
346 // FoundStart/EndPos contain the original position
347 // add the changes due to replacement to SrcEditStart/EndPos
348 if SrcEditStartPos.Y=LastReplaceLine then
349 inc(SrcEditStartPos.X,LastReplaceColOffset);
350 if SrcEditStartPos.Y>=LastReplaceLine then
351 inc(SrcEditStartPos.Y,ReplaceLineOffset);
352 if SrcEditEndPos.Y=LastReplaceLine then
353 inc(SrcEditEndPos.X,LastReplaceColOffset);
354 if SrcEditEndPos.Y>=LastReplaceLine then
355 inc(SrcEditEndPos.Y,ReplaceLineOffset);
356 SrcEditPosValid:=true;
357 end;
358 end;
359
360 begin
361 // create replacement
362 AReplace:=ReplaceText;
363 if sesoRegExpr in Flags then
364 AReplace:=RE.Substitute(AReplace);
365 //DebugLn(['DoReplaceLine Replace with "',AReplace,'"']);
366
367 SrcEditPosValid:=false;
368
369 // ask the user
370 if Prompt and (TheFileName<>'') then begin
371 // open the place in the source editor
372 EndLocks;
373
374 // update windows
375 ProcessMessages;
376 if Result=mrAbort then exit;
377
378 GetSrcEditPos;
379 if LazarusIDE.DoOpenFileAndJumpToPos(TheFileName,SrcEditStartPos,
380 -1,-1,-1,[ofUseCache,ofDoNotLoadResource,ofVirtualFile,ofRegularFile])
381 <>mrOk then
382 begin
383 DoAbort;
384 exit;
385 end;
386 // select found text
387 if not FileIsOpenInSourceEditor then
388 RaiseGDBException('inconsistency');
389 SrcEdit.SelectText(SrcEditStartPos.Y,SrcEditStartPos.X,
390 SrcEditEndPos.Y,SrcEditEndPos.X);
391 SrcEdit.AskReplace(nil,SrcEdit.Selection,AReplace,
392 SrcEditStartPos.Y,SrcEditStartPos.X,Action);
393 case Action of
394 seraSkip: exit;
395 seraReplace: ;
396 seraReplaceAll: Prompt:=false;
397 else
398 DoAbort;
399 exit;
400 end;
401 end;
402
403 if FileIsOpenInSourceEditor then begin
404 // change text in source editor
405 EnablePaintLock;
406 GetSrcEditPos;
407 SrcEdit.SelectText(SrcEditStartPos.Y,SrcEditStartPos.X,
408 SrcEditEndPos.Y,SrcEditEndPos.X);
409 SrcEdit.Selection:=AReplace;
410
411 // count total replacements and adjust offsets
412 aLineCount:=LineEndCount(AReplace,aLastLineLength);
413 //debugln(['DoReplaceLine Replace="',dbgstr(AReplace),'" aLineCount=',aLineCount,' aLastLineLength=',aLastLineLength]);
414 if aLineCount>0 then begin
415 // replaced with multiple lines
416 LastReplaceColOffset:=aLastLineLength+1-FoundEndPos.X;
417 end else begin
418 if FoundStartPos.Y<>LastReplaceLine then
419 LastReplaceColOffset:=0;
420 // replaced with some words
421 if FoundStartPos.Y=FoundEndPos.Y then begin
422 // replaced some words with some words
423 inc(LastReplaceColOffset,
424 aLastLineLength-(FoundEndPos.X-FoundStartPos.X));
425 end else begin
426 // replaced several lines with some words
427 inc(LastReplaceColOffset,FoundStartPos.X+aLastLineLength-FoundEndPos.X);
428 end;
429 end;
430 LastReplaceLine:=FoundEndPos.Y;
431
432 Lines := '';
433 for i := SrcEditStartPos.Y to SrcEditStartPos.Y + aLineCount do
434 Lines := Lines + SrcEdit.Lines[i-1] + LineEnding;
435 Lines:=ChompOneLineEndAtEnd(Lines);
436 if (Progress<>nil)
437 and (Progress.OnAddMatch<>nil) then begin
438 Progress.OnAddMatch(TheFileName,
439 Point(FoundStartPos.x, FoundStartPos.y + ReplaceLineOffset),
440 SrcEdit.CursorTextXY,Lines);
441 end;
442
443 inc(ReplaceLineOffset,aLineCount-(FoundEndPos.Y-FoundStartPos.Y));
444 //DebugLn(['DoReplaceLine FoundStartPos=',dbgs(FoundStartPos),' FoundEndPos=',dbgs(FoundEndPos),' aLastLineLength=',aLastLineLength,' LastReplaceLine=',LastReplaceLine,' LastReplaceColOffset=',LastReplaceColOffset,' ReplaceLineOffset=',ReplaceLineOffset]);
445 end else begin
446 // change text in memory/disk
447 OriginalFile.LineColToPosition(FoundStartPos.Y,FoundStartPos.X,
448 OriginalTextPos);
449 GapLength:=OriginalTextPos-ReplacedTextOriginalPos;
450 NewLength:=ReplacedTextLength+GapLength+length(AReplace);
451 GrowNewText(NewLength);
452 // copy the text between the last replacement and this replacement
453 if GapLength>0 then begin
454 System.Move(OriginalFile.Source[ReplacedTextOriginalPos],
455 ReplacedText[ReplacedTextLength],GapLength);
456 inc(ReplacedTextLength,GapLength);
457 end;
458 // copy the replacement
459 if AReplace<>'' then begin
460 System.Move(AReplace[1],ReplacedText[ReplacedTextLength],length(AReplace));
461 inc(ReplacedTextLength,length(AReplace));
462 end;
463 // save original position behind found position
464 OriginalFile.LineColToPosition(FoundEndPos.Y,FoundEndPos.X,
465 ReplacedTextOriginalPos);
466
467 Lines:=copy(OriginalFile.GetLines(FoundStartPos.Y,FoundStartPos.Y), 1, FoundStartPos.X - 1) +
468 AReplace +
469 copy(OriginalFile.GetLines(FoundEndPos.Y,FoundEndPos.Y), FoundEndPos.x, MaxInt);
470 Lines:=ChompOneLineEndAtEnd(Lines);
471 aLineCount:=LineEndCount(AReplace,aLastLineLength);
472 if aLineCount = 0 then aLastLineLength := aLastLineLength + FoundStartPos.X;
473 if (Progress<>nil)
474 and (Progress.OnAddMatch<>nil) then begin
475 Progress.OnAddMatch(TheFileName,
476 Point(FoundStartPos.x, FoundStartPos.y + ReplaceLineOffset),
477 Point(aLastLineLength, FoundStartPos.Y + aLineCount + ReplaceLineOffset),
478 Lines);
479 end;
480
481 inc(ReplaceLineOffset,aLineCount-(FoundEndPos.Y-FoundStartPos.Y));
482 end;
483 end;
484
485 procedure CommitChanges;
486 var
487 GapLength: Integer;
488 NewLength: Integer;
489 NewText: string;
490 CurResult: TModalResult;
491 begin
492 EndLocks;
493 if (ReplacedText<>nil) then begin
494 if SearchInText<>mrAbort then begin
495 GapLength:=OriginalFile.SourceLength+1-ReplacedTextOriginalPos;
496 NewLength:=ReplacedTextLength+GapLength;
497 GrowNewText(NewLength);
498 // copy the text between the last and this replacement
499 if GapLength>0 then begin
500 System.Move(OriginalFile.Source[ReplacedTextOriginalPos],
501 ReplacedText[ReplacedTextLength],GapLength);
502 inc(ReplacedTextLength,GapLength);
503 end;
504 SetLength(NewText,ReplacedTextLength);
505 if NewText<>'' then
506 System.Move(ReplacedText[0],NewText[1],length(NewText));
507 if (TheFileName<>'') then begin
508 OriginalFile.Source:=NewText;
509 if (not OriginalFile.SaveToFile(TheFileName)) then begin
510 CurResult:=MessageDlg(lisCodeToolsDefsWriteError,
511 Format(lisErrorWritingFile, [TheFileName]),
512 mtError,[mbCancel,mbAbort],0);
513 if CurResult=mrAbort then DoAbort;
514 end;
515 end else begin
516 TheText:=NewText;
517 end;
518 end;
519 FreeMem(ReplacedText);
520 end;
521 end;
522
523 var
524 Found: Boolean;
525 Src: String;
526 NewMatchStartPos: PtrInt;
527 NewMatchEndPos: PtrInt;
528 begin
529 //debugln(['SearchInText TheFileName=',TheFileName,' SearchFor=',SearchFor,'" ReplaceText=',ReplaceText,'"']);
530
531 if (Progress<>nil) and Progress.Abort then exit(mrAbort);
532 Result:=mrOk;
533
534 OriginalFile:=nil;
535 CaseFile:=nil;
536 RE:=nil;
537 SrcEdit:=nil;
538 SrcEditValid:=false;
539 PaintLockEnabled:=false;
540 ReplacedText:=nil;
541 ReplacedTextCapacity:=0;
542 ReplacedTextLength:=0;
543 ReplacedTextOriginalPos:=1;
544
545 ReplaceLineOffset:=0;
546 LastReplaceLine:=0;
547 LastReplaceColOffset:=0;
548
549 try
550 FoundEndPos:= Point(0,0);
551 TempSearch:= SearchFor;
552
553 // load text (to save memory, do not use codetools cache system)
554 if FileIsOpenInSourceEditor then begin
555 OriginalFile:=TSourceLog.Create(SrcEdit.GetText(false));
556 end else if TheFileName<>'' then begin
557 OriginalFile:=TSourceLog.Create('');
558 OriginalFile.LoadFromFile(TheFileName);
559 end else begin
560 OriginalFile:=TSourceLog.Create(TheText);
561 end;
562 if OriginalFile.Source='' then exit;
563
564 CaseFile:=nil;
565
566 if sesoRegExpr in Flags then begin
567 // Setup the regular expression search engine
568 RE:=TRegExpr.Create;
569 RE.ModifierI:=not (sesoMatchCase in Flags);
570 RE.ModifierM:=true;
571 RE.ModifierS:=sesoMultiLine in Flags;
572 Src:=OriginalFile.Source;
573 if sesoWholeWord in Flags then
574 RE.Expression:='\b'+SearchFor+'\b'
575 else
576 RE.Expression:=SearchFor;
577 end else begin
578 // convert case if necessary
579 if not (sesoMatchCase in Flags) then begin
580 CaseFile:=TSourceLog.Create(UpperCaseStr(OriginalFile.Source));
581 TempSearch:=UpperCaseStr(TempSearch);
582 Src:=CaseFile.Source;
583 end else
584 Src:=OriginalFile.Source;
585 end;
586
587 //debugln(['TheFileName=',TheFileName,' len=',OriginalFile.SourceLength,' Cnt=',OriginalFile.LineCount,' TempSearch=',TempSearch]);
588
589 NewMatchEndPos:=1;
590 repeat
591 Found:=false;
592 if sesoRegExpr in Flags then begin
593 // search the text for regular expression
594 RE.InputString:=Src;
595 if RE.ExecPos(NewMatchEndPos) then begin
596 Found:=true;
597 NewMatchStartPos:=RE.MatchPos[0];
598 NewMatchEndPos:=NewMatchStartPos+RE.MatchLen[0];
599 end;
600 end else begin
601 // search for normal text
602 if SearchNextInText(PChar(TempSearch),length(TempSearch),
603 PChar(Src),length(Src),
604 NewMatchEndPos-1,NewMatchStartPos,NewMatchEndPos,
605 sesoWholeWord in Flags,sesoMultiLine in Flags)
606 then begin
607 Found:=true;
608 inc(NewMatchStartPos);
609 inc(NewMatchEndPos);
610 end;
611 end;
612
613 if Found then begin
614 // found => convert position, report and/or replace
615 OriginalFile.AbsoluteToLineCol(NewMatchStartPos,
616 FoundStartPos.Y,FoundStartPos.X);
617 OriginalFile.AbsoluteToLineCol(NewMatchEndPos,
618 FoundEndPos.Y,FoundEndPos.X);
619 //DebugLn(['SearchInText NewMatchStartPos=',NewMatchStartPos,' NewMatchEndPos=',NewMatchEndPos,' FoundStartPos=',dbgs(FoundStartPos),' FoundEndPos=',dbgs(FoundEndPos),' Found="',dbgstr(copy(Src,NewMatchStartPos,NewMatchEndPos-NewMatchStartPos)),'" Replace=',sesoReplace in Flags]);
620 if sesoReplace in Flags then begin
621 DoReplaceLine
622 end else begin
623 if (Progress<>nil)
624 and (Progress.OnAddMatch<>nil) then begin
625 Lines:=OriginalFile.GetLines(FoundStartPos.Y,FoundEndPos.Y);
626 Lines:=ChompOneLineEndAtEnd(Lines);
627 if (Progress<>nil)
628 and (Progress.OnAddMatch<>nil) then begin
629 Progress.OnAddMatch(TheFileName,FoundStartPos,FoundEndPos,Lines);
630 end;
631 end;
632 end;
633 end else begin
634 // not found
635 break;
636 end;
637
638 // check abort
639 if (Result=mrAbort) then begin
640 exit;
641 end;
642
643 until false;
644 finally
645 CommitChanges;
646 if OriginalFile=CaseFile then
647 CaseFile:=nil;
648 FreeAndNil(OriginalFile);
649 FreeAndNil(CaseFile);
650 FreeAndNil(RE);
651 end;
652 end;//SearchFile
653
654
655 { TSearchProgressForm }
656
657 procedure TSearchProgressForm.btnAbortCLICK(Sender: TObject);
658 begin
659 Progress.Abort:= true;
660 end;
661
662 procedure TSearchProgressForm.SearchFormCREATE(Sender: TObject);
MaxWidthnull663 Function MaxWidth(const Labs : array of TLabel) : integer;
664 var i,w : integer;
665 begin
666 Result:=0;
667 for i:=low(Labs) to high(Labs) do
668 begin
669 w:=Canvas.TextWidth(Labs[i].Caption);
670 if Result<w then
671 Result:=w;
672 end;
673 end;
674
675 var NewX : integer;
676 begin
677 //Set Defaults
678 MatchesLabel.Caption:=lissMatches;
679 SearchingLabel.Caption:=lissSearching;
680 SearchTextLabel.Caption:=lissSearchText;
681 NewX:=MatchesLabel.Left+MaxWidth([MatchesLabel,SearchingLabel,SearchTextLabel])+10;
682 lblMatches.Left:=NewX;
683 lblProgress.Left:=NewX;
684 lblSearchText.Left:=NewX;
685
686 Caption:=dlgSearchCaption;
687 btnCancel.Caption:=lisCancel;
688
689 fProgress:=TIDESearchInTextProgress.Create;
690 FProgress.OnAddMatch:=@OnAddMatch;
691
692 fFlags:=[];
693 fPromptOnReplace:=true;
694 fRecursive:= True;
695 Progress.Abort:= false;
696 fAbortString:= dlgSearchAbort;
697 fPad:= '...';
698 fSearchProject:= false;
699 fSearchProjectGroup:= false;
700 fSearchOpen:= false;
701 fSearchFiles:= false;
702 fWasActive:= false;
703 end;
704
705 procedure TSearchProgressForm.OnAddMatch(const Filename: string; const StartPos,
706 EndPos: TPoint; const Lines: string);
707 var
708 MatchLen: Integer;
709 TrimmedMatch: LongInt;
710 TrimmedLines: String;
711 LastLineLen: integer;
712 begin
713 LineEndCount(Lines,LastLineLen);
714 MatchLen:=length(Lines)-(LastLineLen+1-EndPos.X)-StartPos.X+1;
715 if MatchLen<1 then MatchLen:=1;
716 //DebugLn(['TSearchForm.OnAddMatch length(Lines)=',length(Lines),' LastLineLen=',LastLineLen,' MatchLen=',MatchLen]);
717 TrimmedMatch:=StartPos.X;
718 TrimmedLines:=TrimLinesAndAdjustPos(Lines,TrimmedMatch);
719 //DebugLn(['TSearchForm.OnAddMatch StartPos=',dbgs(StartPos),' EndPos=',dbgs(EndPos),' Lines="',Lines,'" Trimmed="',TrimmedLines,'" TrimmedMatch=',TrimmedMatch]);
720 SearchResultsView.AddMatch(fResultsWindow.PageIndex,FileName,StartPos,EndPos,
721 TrimmedLines, TrimmedMatch, MatchLen);
722 UpdateMatches;
723 end;
724
725 procedure TSearchProgressForm.FormClose(Sender: TObject; var CloseAction:
726 TCloseAction);
727 begin
728 fWasActive:= Active;
729 end;
730
731 procedure TSearchProgressForm.FormShow(Sender: TObject);
732 begin
733 fWasActive:= true;
734 end;
735
736 procedure TSearchProgressForm.SearchFormDESTROY(Sender: TObject);
737 begin
738 FreeAndNil(fProgress);
739 end;
740
741 procedure TSearchProgressForm.SetOptions(TheOptions: TLazFindInFileSearchOptions);
742 begin
743 SetFlag(sesoWholeWord,fifWholeWord in TheOptions);
744 SetFlag(sesoReplace,fifReplace in TheOptions);
745 SetFlag(sesoReplaceAll,fifReplaceAll in TheOptions);
746 SetFlag(sesoMatchCase,fifMatchCase in TheOptions);
747 SetFlag(sesoRegExpr,fifRegExpr in TheOptions);
748 SetFlag(sesoMultiLine,fifMultiLine in TheOptions);
749 fRecursive:= (fifIncludeSubDirs in TheOptions);
750 fSearchProject:= (fifSearchProject in TheOptions);
751 fSearchProjectGroup:= (fifSearchProjectGroup in TheOptions);
752 fSearchOpen:= (fifSearchOpen in TheOptions);
753 fSearchActive:= (fifSearchActive in TheOptions);
754 fSearchFiles:= (fifSearchDirectories in TheOptions);
755 end;//SetOptions
756
TSearchProgressForm.GetOptionsnull757 function TSearchProgressForm.GetOptions: TLazFindInFileSearchOptions;
758 begin
759 Result:=[];
760 if sesoWholeWord in fFlags then include(Result,fifWholeWord);
761 if sesoMatchCase in fFlags then include(Result,fifMatchCase);
762 if sesoReplace in fFlags then include(Result,fifReplace);
763 if sesoReplaceAll in fFlags then include(Result,fifReplaceAll);
764 if sesoRegExpr in fFlags then include(Result,fifRegExpr);
765 if sesoMultiLine in fFlags then include(Result,fifMultiLine);
766 if fRecursive then include(Result,fifIncludeSubDirs);
767 if fSearchProject then include(Result, fifSearchProject);
768 if fSearchProjectGroup then include(Result, fifSearchProjectGroup);
769 if fSearchOpen then include(Result,fifSearchOpen);
770 if fSearchActive then include(Result,fifSearchActive);
771 if fSearchFiles then include(Result,fifSearchDirectories);
772 end;//GetOptions
773
DoSearchnull774 function TSearchProgressForm.DoSearch: integer;
775 // Search in all files and then return the number of found items.
776 begin
777 Result:= 0;
778 PromptOnReplace:=true;
779 fAborting:=false;
780 Progress.Abort:=false;
781 lblSearchText.Caption:= fSearchFor;
782 fMatches:= 0;
783 if Assigned(fResultsList) then
784 begin
785 if not fResultsListUpdating then begin
786 fResultsList.BeginUpdate;
787 fResultsListUpdating:=true;
788 end;
789 try
790 if fSearchFiles or fSearchProjectGroup then
791 DoFindInFiles(fDirectories);
792 if fSearchProject or fSearchOpen or fSearchActive then
793 DoFindInSearchList;
794 if Assigned(fResultsList) then begin
795 Result:=fResultsList.Count; // Return the real item count.
796 if fResultsList.Count = 0 then // Add a note to the list if no items found.
797 fResultsList.Add(Format(lisUESearchStringNotFound,[dbgstr(fSearchFor)]));
798 end;
799 finally
800 if fResultsListUpdating then begin
801 fResultsListUpdating:=false;
802 fResultsList.EndUpdate;
803 end;
804 end;
805 end;//if
806 Close;
807 end;//DoSearch
808
809 type
810
811 { TLazFileSearcher }
812
813 TLazFileSearcher = class(TFileSearcher)
814 private
815 FParent: TSearchProgressForm;
816 procedure CheckAbort;
817 protected
818 procedure DoDirectoryEnter; override;
819 procedure DoDirectoryFound; override;
820 procedure DoFileFound; override;
821 public
822 constructor Create(AParent: TSearchProgressForm);
823 destructor Destroy; override;
824 end;
825
826 { TLazFileSearcher }
827
828 procedure TLazFileSearcher.CheckAbort;
829 begin
830 if FParent.Progress.Abort then
831 begin
832 if not FParent.FAborting then
833 begin
834 FParent.FAborting := True;
835 FParent.FResultsList.Insert(0, FParent.FAbortString);
836 end;
837
838 Stop;
839 end;
840 end;
841
842 procedure TLazFileSearcher.DoDirectoryEnter;
843 begin
844 CheckAbort;
845 end;
846
847 procedure TLazFileSearcher.DoDirectoryFound;
848 begin
849 CheckAbort;
850 end;
851
852 procedure TLazFileSearcher.DoFileFound;
853 var
854 F: String;
855 begin
856 F := FileName;
857 if FileIsTextCached(F) then
858 begin
859 FParent.UpdateProgress(F);
860 FParent.SearchFile(F);
861 end;
862 CheckAbort;
863 end;
864
865 constructor TLazFileSearcher.Create(AParent: TSearchProgressForm);
866 begin
867 inherited Create;
868 FParent := AParent;
869 end;
870
871 destructor TLazFileSearcher.Destroy;
872 begin
873 FParent:=nil;
874 inherited Destroy;
875 end;
876
877 { TSearchProgressForm }
878
879 procedure TSearchProgressForm.DoFindInFiles(ADirectories: string);
880 var
881 Searcher: TLazFileSearcher;
882 SearchPath: String;
883 p: Integer;
884 Dir: String;
885 begin
886 // if we have a list and a valid directory
887 SearchPath:='';
888 p:=1;
889 repeat
890 Dir:=GetNextDirectoryInSearchPath(ADirectories,p);
891 if Dir='' then break;
892 if DirPathExists(Dir) then
893 SearchPath:=MergeSearchPaths(SearchPath,Dir);
894 until false;
895 if SearchPath='' then
896 exit;
897 Searcher := TLazFileSearcher.Create(Self);
898 try
899 Searcher.Search(SearchPath, FMask, FRecursive);
900 finally
901 Searcher.Free;
902 end;
903 end;
904
905 procedure TSearchProgressForm.DoFindInSearchList;
906 var
907 i: integer;
908 begin
909 if Assigned(fSearchFileList) then
910 begin
911 for i:= 0 to fSearchFileList.Count -1 do
912 begin
913 UpdateProgress(fSearchFileList[i]);
914 SearchFile(fSearchFileList[i]);
915 end;
916 end;
917 end;
918
919 procedure TSearchProgressForm.SetResultsList(const AValue: TStrings);
920 begin
921 if fResultsList=AValue then exit;
922 if fResultsListUpdating then
923 begin
924 fResultsList.EndUpdate;
925 fResultsListUpdating:=false;
926 end;
927 fResultsList:=AValue;
928 end;
929
930 procedure TSearchProgressForm.UpdateMatches;
931 begin
932 inc(fMatches);
933 //DebugLn(['TSearchForm.UpdateMatches ',lblMatches.Caption]);
934 lblMatches.Caption:=IntToStr(fMatches);
935 end;
936
937 procedure TSearchProgressForm.UpdateProgress(FileName: string);
938 const
939 UpdateAfterTicks = 200; // update not more than 5 times per second
940 var
941 DisplayFileName: string;
942 ShorterFileName: String;
943 CurTick: DWORD;
944 begin
945 CurTick:=GetTickCount;
946 if Abs(int64(CurTick)-int64(fLastUpdateProgress))<UpdateAfterTicks then
947 exit;
948 fLastUpdateProgress:=CurTick;
949
950 DisplayFileName := FileName;
951 //DebugLn(['TSearchForm.UpdateProgress DisplayFileName="',dbgstr(DisplayFileName),'"']);
952 while (lblProgress.Left + lblProgress.Canvas.TextWidth(DisplayFileName)) > lblProgress.Parent.ClientWidth-12 do
953 begin
954 ShorterFileName:= PadAndShorten(DisplayFileName);
955 if ShorterFileName=DisplayFileName then break;
956 DisplayFileName:=ShorterFileName;
957 //DebugLn(['TSearchForm.UpdateProgress Padded DisplayFileName="',dbgstr(DisplayFileName),'"']);
958 end;
959 lblProgress.Caption := DisplayFileName;
960 Application.ProcessMessages;
961 end;
962
963 procedure TSearchProgressForm.SearchFile(const aFilename: string);
964 var
965 Src: String;
966 begin
967 fResultsList.BeginUpdate;
968 try
969 Src:='';
970 SearchInText(aFilename,Src,fSearchFor,FReplaceText,FFlags,
971 fPromptOnReplace,Progress);
972 finally
973 fResultsList.EndUpdate;
974 end;
975 end;
976
977 procedure TSearchProgressForm.SetFlag(Flag: TSrcEditSearchOption; AValue: boolean);
978 begin
979 if AValue then
980 Include(fFlags,Flag)
981 else
982 Exclude(fFlags,Flag);
983 end;
984
985 procedure TSearchProgressForm.DoSearchAndAddToSearchResults;
986 var
987 ListPage: TTabSheet;
988 Cnt: integer;
989 State: TIWGetFormState;
990 begin
991 Cnt:= 0;
992 LazarusIDE.DoShowSearchResultsView(iwgfShow);
993 ListPage:=SearchResultsView.AddSearch(SearchText,SearchText,
994 ReplaceText,SearchDirectories,SearchMask,SearchOptions);
995 try
996 (* BeginUpdate prevents ListPage from being closed,
997 other pages can still be closed or inserted, so PageIndex can change *)
998 SearchResultsView.BeginUpdate(ListPage.PageIndex);
999 ResultsList:= SearchResultsView.Items[ListPage.PageIndex];
1000 ResultsList.Clear;
1001 ResultsWindow:= ListPage;
1002 try
1003 Show; // floating window, not dockable
1004 Cnt:= DoSearch;
1005 except
1006 on E: ERegExpr do
1007 IDEMessageDialog(lisUEErrorInRegularExpression, E.Message,mtError,
1008 [mbCancel]);
1009 end;
1010 finally
1011 ListPage.Caption:= Format('%s (%d)',[ListPage.Caption,Cnt]);
1012 SearchResultsView.EndUpdate(ListPage.PageIndex);
1013 // show, but bring to front only if Search Progress dialog was active
1014 if fWasActive then
1015 State:=iwgfShowOnTop
1016 else
1017 State:=iwgfShow;
1018 LazarusIDE.DoShowSearchResultsView(State);
1019 end;
1020 end;
1021
1022 procedure TSearchProgressForm.DoSearchOpenFiles;
1023 var
1024 i: integer;
1025 TheFileList: TStringList;
1026 SrcEdit: TSourceEditorInterface;
1027 begin
1028 try
1029 TheFileList:= TStringList.Create;
1030 for i:= 0 to SourceEditorManagerIntf.UniqueSourceEditorCount -1 do
1031 begin
1032 //only if file exists on disk
1033 SrcEdit := SourceEditorManagerIntf.UniqueSourceEditors[i];
1034 if FilenameIsAbsolute(SrcEdit.FileName)
1035 and (not FileExistsCached(SrcEdit.FileName)) then
1036 continue;
1037 TheFileList.Add(SrcEdit.FileName);
1038 end;
1039 SearchFileList:= TheFileList;
1040 DoSearchAndAddToSearchResults;
1041 finally
1042 FreeAndNil(TheFileList);
1043 end;
1044 end;
1045
1046 procedure TSearchProgressForm.DoSearchActiveFile;
1047 var
1048 TheFileList: TStringList;
1049 begin
1050 try
1051 TheFileList:= TStringList.Create; // Add a single file to the list
1052 TheFileList.Add(SourceEditorManagerIntf.ActiveEditor.FileName);
1053 SearchFileList:= TheFileList;
1054 DoSearchAndAddToSearchResults;
1055 finally
1056 FreeAndNil(TheFileList);
1057 end;
1058 end;
1059
1060 procedure TSearchProgressForm.DoSearchDirs;
1061 begin
1062 SearchFileList:= Nil;
1063 DoSearchAndAddToSearchResults;
1064 end;
1065
1066 procedure TSearchProgressForm.DoSearchProject(AProject: TProject);
1067 var
1068 AnUnitInfo: TUnitInfo;
1069 TheFileList: TStringList;
1070 begin
1071 try
1072 TheFileList:= TStringList.Create;
1073 AnUnitInfo:=AProject.FirstPartOfProject;
1074 while AnUnitInfo<>nil do begin
1075 //Only if file exists on disk.
1076 if FilenameIsAbsolute(AnUnitInfo.FileName)
1077 and FileExistsCached(AnUnitInfo.FileName) then
1078 TheFileList.Add(AnUnitInfo.FileName);
1079 AnUnitInfo:=AnUnitInfo.NextPartOfProject;
1080 end;
1081 SearchFileList:= TheFileList;
1082 DoSearchAndAddToSearchResults;
1083 finally
1084 FreeAndNil(TheFileList);
1085 end;
1086 end;
1087
1088 procedure TSearchProgressForm.DoSearchProjectGroup;
1089 begin
1090 if (ProjectGroupManager=nil) or (ProjectGroupManager.CurrentProjectGroup=nil) then
1091 DoSearchProject(Project1)
1092 else begin
1093 SearchFileList:= Nil;
1094 SearchDirectories:=ProjectGroupManager.GetSrcPaths;
1095 DoSearchAndAddToSearchResults;
1096 end;
1097 end;
1098
PadAndShortennull1099 function TSearchProgressForm.PadAndShorten(FileName: string): string;
1100 var
1101 FoundAt: integer;
1102 begin
1103 FoundAt:= System.Pos(PathDelim,FileName);
1104 if FoundAt<1 then begin
1105 Result := Filename;
1106 end else begin
1107 Result:= fPad + copy(FileName,FoundAt+1,Length(FileName));
1108 end;
1109 end;//PadAndShorten
1110
1111 end.
1112
1113