/ide/searchfrm.pas

http://github.com/graemeg/lazarus · Pascal · 1096 lines · 1050 code · 12 blank · 34 comment · 0 complexity · b2dd6df88b1facadea7296ccfef9604f MD5 · raw file

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