IDE: Find in Files: implemented multi line pattern and replacement, gtk intf: improved z ordering with modal forms

git-svn-id: trunk@9779 -
This commit is contained in:
mattias 2006-09-01 14:38:07 +00:00
parent b96a886dd8
commit 8783e0100d
11 changed files with 403 additions and 262 deletions

View File

@ -80,7 +80,7 @@ function FindNextIdentifier(const Source: string; StartPos, MaxPos: integer
// line/code ends
function LineEndCount(const Txt: string): integer;
function LineEndCount(const Txt: string; var LengthOfLastLine:integer): integer;
function LineEndCount(const Txt: string; out LengthOfLastLine:integer): integer;
function EmptyCodeLineCount(const Source: string; StartPos, EndPos: integer;
NestedComments: boolean): integer;
function PositionsInSameLine(const Source: string;
@ -97,6 +97,7 @@ function FindFirstLineEndInFrontOfInCode(const Source: string;
function FindFirstLineEndAfterInCode(const Source: string;
Position, MaxPosition: integer; NestedComments: boolean): integer;
function ChompLineEndsAtEnd(const s: string): string;
function ChompOneLineEndAtEnd(const s: string): string;
function TrimLineEnds(const s: string; TrimStart, TrimEnd: boolean): string;
// brackets
@ -140,6 +141,14 @@ function SplitStringConstant(const StringConstant: string;
procedure ImproveStringConstantStart(const ACode: string; var StartPos: integer);
procedure ImproveStringConstantEnd(const ACode: string; var EndPos: integer);
// search
function SearchNextInText(Search: PChar; SearchLen: PtrInt;
Src: PChar; SrcLen: PtrInt;
StartPos: PtrInt;// 0 based
out MatchStart, MatchEnd: PtrInt;// 0 based
WholeWords: boolean = false; MultiLine: boolean = false): boolean;
// files
type
@ -271,9 +280,10 @@ implementation
var
IsIDChar, // ['a'..'z','A'..'Z','0'..'9','_']
IsIDStartChar, // ['a'..'z','A'..'Z','_']
IsSpaceChar,
IsNumberChar,
IsHexNumberChar
IsSpaceChar, // [#0..#32];
IsNumberChar, // ['0'..'9']
IsHexNumberChar, // ['0'..'9','A'..'Z','a'..'z']
IsNonWordChar // [#0..#127]-IsIDChar
: array[char] of boolean;
function Min(i1, i2: integer): integer; inline;
@ -297,7 +307,7 @@ begin
Result:=false;
// find section
Position:=SearchCodeInSource(Source,Section,1,EndPos,false);
if Position<1 then exit;
if (Position<1) or (EndPos<1) then exit;
// search for include directives
repeat
Atom:=ReadNextPascalAtom(Source,Position,AtomStart);
@ -1574,7 +1584,7 @@ begin
end;
function LineEndCount(const Txt: string;
var LengthOfLastLine: integer): integer;
out LengthOfLastLine: integer): integer;
var i, LastLineEndPos: integer;
begin
i:=1;
@ -1915,6 +1925,21 @@ begin
SetLength(Result,EndPos-1);
end;
function ChompOneLineEndAtEnd(const s: string): string;
var
EndPos: Integer;
begin
Result:=s;
EndPos:=length(s)+1;
if (EndPos>1) and (s[EndPos-1] in [#10,#13]) then begin
dec(EndPos);
if (EndPos>1) and (s[EndPos-1] in [#10,#13]) and (s[EndPos]<>s[EndPos-1])
then
dec(EndPos);
SetLength(Result,EndPos-1);
end;
end;
function TrimLineEnds(const s: string; TrimStart, TrimEnd: boolean): string;
var
StartPos: Integer;
@ -3239,6 +3264,71 @@ begin
until AtomEndPos>=EndPos;
end;
function SearchNextInText(Search: PChar; SearchLen: PtrInt; Src: PChar;
SrcLen: PtrInt; StartPos: PtrInt; out MatchStart, MatchEnd: PtrInt;
WholeWords: boolean; MultiLine: boolean): boolean;
{ search Search in Src starting at StartPos.
MatchEnd will be the position of the first character after the found pattern.
if WholeWords then in front of MatchStart and behind MatchEnd will be
a non word character.
if MultiLine then newline characters are the same #13#10 = #10 = #13. }
var
EndSrc: PChar;
EndSearch: PChar;
FirstChar: Char;
CurPos: PChar;
CmpSearch: PChar;
CmpSrc: PChar;
begin
Result:=false;
MatchStart:=0;
MatchEnd:=0;
if (Search=nil) or (Src=nil) then exit;
EndSrc:=@Src[SrcLen];
EndSearch:=@Search[SearchLen];
FirstChar:=Search^;
CurPos:=@Src[StartPos];
while (CurPos<EndSrc) do begin
if (FirstChar=CurPos^)
and ((not WholeWords) or (CurPos>Src) or (IsNonWordChar[PChar(CurPos-1)^]))
then begin
CmpSearch:=Search;
CmpSrc:=CurPos;
while (CmpSearch<EndSearch) and (CmpSrc<EndSrc) do begin
if CmpSearch^=CmpSrc^ then begin
inc(CmpSearch);
inc(CmpSrc);
end else if MultiLine
and (CmpSrc^ in [#13,#10]) and (CmpSearch^ in [#13,#10]) then begin
if (CmpSrc+1<EndSrc) and (CmpSrc[1] in [#13,#10])
and (CmpSrc^<>CmpSrc[1]) then
inc(CmpSrc,2)
else
inc(CmpSrc);
if (CmpSearch+1<EndSearch) and (CmpSearch[1] in [#13,#10])
and (CmpSearch^<>CmpSearch[1]) then
inc(CmpSearch,2)
else
inc(CmpSearch);
end else begin
break;
end;
end;
if (CmpSearch=EndSearch)
and ((not WholeWords) or (CmpSrc=EndSrc) or (IsNonWordChar[CmpSrc^])) then
begin
// pattern found
Result:=true;
MatchStart:=CurPos-Src;
MatchEnd:=CmpSrc-Src;
exit;
end;
end;
inc(CurPos);
end;
end;
function GatherUnitFiles(const BaseDir, SearchPath,
Extensions: string; KeepDoubles, CaseInsensitive: boolean;
var TreeOfUnitFiles: TAVLTree): boolean;
@ -3580,6 +3670,7 @@ begin
IsSpaceChar[c]:=c in [#0..#32];
IsNumberChar[c]:=c in ['0'..'9'];
IsHexNumberChar[c]:=c in ['0'..'9','A'..'Z','a'..'z'];
IsNonWordChar[c]:=(c in [#0..#127]) and (not IsIDChar[c]);
end;
end;

View File

@ -129,8 +129,8 @@ type
property Source: string read FSource write SetSource;
property Modified: boolean read FModified write FModified;
// Line and Column begin at 1
procedure LineColToPosition(Line, Column: integer; var Position: integer);
procedure AbsoluteToLineCol(Position: integer; var Line, Column: integer);
procedure LineColToPosition(Line, Column: integer; out Position: integer);
procedure AbsoluteToLineCol(Position: integer; out Line, Column: integer);
procedure Insert(Pos: integer; const Txt: string);
procedure Delete(Pos, Len: integer);
procedure Replace(Pos, Len: integer; const Txt: string);
@ -576,7 +576,7 @@ begin
end;
procedure TSourceLog.LineColToPosition(Line, Column: integer;
var Position: integer);
out Position: integer);
begin
BuildLineRanges;
if (Line>=1) and (Line<=FLineCount) and (Column>=1) then begin
@ -602,7 +602,7 @@ begin
end;
procedure TSourceLog.AbsoluteToLineCol(Position: integer;
var Line, Column: integer);
out Line, Column: integer);
var l,r,m:integer;
begin
BuildLineRanges;

View File

@ -247,6 +247,7 @@ begin
SearchResultsView.AddMatch(SearchPageIndex,
CodePos^.Code.Filename,
Point(CodePos^.X,CodePos^.Y),
Point(CodePos^.X+length(Identifier),CodePos^.Y),
TrimmedLine,
CodePos^.X-TrimCnt, length(Identifier));
ANode:=TreeOfPCodeXYPosition.FindPrecessor(ANode);

View File

@ -35,7 +35,7 @@ uses
Classes, SysUtils, LCLProc, LResources, LCLType, LCLIntf, Forms, Controls,
Graphics, Dialogs, ExtCtrls, StdCtrls, Buttons, FileUtil,
// synedit, codetools
SynEditSearch, SynRegExpr, SourceLog, KeywordFuncLists,
SynEditSearch, SynRegExpr, SourceLog, KeywordFuncLists, BasicCodeTools,
// IDEIntf
LazIDEIntf, SrcEditorIntf,
// ide
@ -54,8 +54,8 @@ type
lblProgress: TLABEL;
lblSearchText: TLABEL;
Panel2: TPANEL;
procedure OnAddMatch(const Filename: string; const StartPos,
EndPos: TPoint; const Lines: string);
procedure OnAddMatch(const Filename: string; const StartPos, EndPos: TPoint;
const Lines: string);
procedure Panel2Click(Sender: TObject);
procedure SearchFormCREATE(Sender: TObject);
procedure SearchFormDESTROY(Sender: TObject);
@ -116,14 +116,10 @@ function SearchInText(const TheFileName: string;
Flags: TSrcEditSearchOptions; var Prompt: boolean;
Progress: TIDESearchInTextProgress = nil
): TModalResult;
function TrimLineAndAdjustPos(const Line: string; var APosition: integer): string;
function TrimLinesAndAdjustPos(const Lines: string; var APosition: integer): string;
function SearchInLine(const SearchStr: string; SrcLog: TSourceLog;
LineNumber: integer; WholeWords: boolean; StartInLine: integer;
out MatchStartInLine: integer): boolean;
function SearchNextInText(const SearchStr: string; Src: PChar;
SrcLen: PtrInt; WholeWords: boolean;
var MatchPos: PtrInt // 0 based
): boolean;
implementation
@ -193,88 +189,27 @@ begin
end;
end;
function SearchNextInText(const SearchStr: string; Src: PChar;
SrcLen: PtrInt; WholeWords: boolean;
var MatchPos: PtrInt): boolean;
// search SearchStr in Src
var
StartPos: PChar;
EndPos: PChar;
i: PtrInt;
SearchLen: PtrInt;
FirstChar: Char;
Found: Boolean;
CharInFront: PChar;
CharBehind: PChar;
FirstLineEnd: Integer;
IsMultiLinePattern: Boolean;
begin
Result:=false;
if SearchStr='' then exit;
if MatchPos<0 then MatchPos:=0;
SearchLen:=length(SearchStr);
FirstLineEnd:=1;
while (FirstLineEnd<=SearchLen) and (SearchStr[FirstLineEnd] in [#10,#13]) do
inc(FirstLineEnd);
if FirstLineEnd<=SearchLen then begin
IsMultiLinePattern:=true;
end else begin
IsMultiLinePattern:=false;
end;
StartPos:=@Src[MatchPos];
EndPos:=@Src[SrcLen];
FirstChar:=SearchStr[1];
while (StartPos<EndPos) do begin
if FirstChar=StartPos^ then begin
i:=1;
while (i<FirstLineEnd) and (StartPos[i-1]=SearchStr[i]) do
inc(i);
if i=FirstLineEnd then begin
// first line of pattern found
// TODO:
if IsMultiLinePattern then begin
end;
Found:=true;
MatchPos:=StartPos-Src+1;
if WholeWords then begin
CharInFront:=StartPos-1;
CharBehind:=StartPos+SearchLen;
if ((MatchPos=1)
or (CharInFront^ in WordBreakChars))
and ((CharBehind=EndPos)
or (CharBehind^ in WordBreakChars))
then begin
// word start and word end
end else begin
// not whole word
Found:=false;
end;
end;
if Found then begin
Result:=true;
exit;
end;
end;
end;
inc(StartPos);
end;
end;
function TrimLineAndAdjustPos(const Line: string; var APosition: integer): string;
function TrimLinesAndAdjustPos(const Lines: string;
var APosition: integer): string;
var
StartPos: Integer;
EndPos: Integer;
begin
StartPos:=1;
while (StartPos<=length(Line)) and (Line[StartPos] in WhiteSpaceChars) do
inc(StartPos);
EndPos:=length(Line)+1;
while (EndPos>=StartPos) and (Line[EndPos-1] in WhiteSpaceChars) do
dec(EndPos);
dec(APosition,StartPos-1);
Result:=copy(Line,StartPos,EndPos-StartPos);
if Lines='' then begin
Result:='';
exit;
end;
if LineEndCount(Lines)=0 then begin
StartPos:=1;
while (StartPos<=length(Lines)) and (Lines[StartPos] in WhiteSpaceChars) do
inc(StartPos);
EndPos:=length(Lines)+1;
while (EndPos>=StartPos) and (Lines[EndPos-1] in WhiteSpaceChars) do
dec(EndPos);
dec(APosition,StartPos-1);
Result:=copy(Lines,StartPos,EndPos-StartPos);
end else
Result:=Lines;
end;
function SearchInText(const TheFileName: string;
@ -288,12 +223,11 @@ var
CaseFile: TSourceLog; // The working File being searched
FoundStartPos: TPoint; // Position of match in line. 1 based.
FoundEndPos: TPoint;
CurLine: String;
CurLineReplaceOffset: integer; // e.g. if in the current line 'ABC'
// was replaced by 'a', then CurLineReplaceOffset is -2
ReplaceLineOffset: integer;// number of lines added/deleted by replacement.
LastReplaceLine: integer; // last changed line by replace. 1 based
LastReplaceColOffset: integer;// bytes added/deleted by replace in last line
TempSearch: string; // Temp Storage for the search string.
RE: TRegExpr;
SearchAllHitsInLine: boolean;
SrcEditValid: Boolean;// true if SrcEdit is valid
SrcEdit: TSourceEditorInterface;
@ -381,14 +315,24 @@ var
NewLength: Integer;
SrcEditPosValid: boolean;
SrcEditStartPos, SrcEditEndPos: TPoint;
aLastLineLength: integer;
aLineCount: integer;
procedure GetSrcEditPos;
begin
if not SrcEditPosValid then begin
SrcEditStartPos:=FoundStartPos;
SrcEditEndPos:=FoundEndPos;
inc(SrcEditStartPos.X,CurLineReplaceOffset);
inc(SrcEditEndPos.X,CurLineReplaceOffset);
// FoundStart/EndPos contain the original position
// add the changes due to replacement to SrcEditStart/EndPos
if SrcEditStartPos.Y=LastReplaceLine then
inc(SrcEditStartPos.X,LastReplaceColOffset);
if SrcEditStartPos.Y>=LastReplaceLine then
inc(SrcEditStartPos.Y,ReplaceLineOffset);
if SrcEditEndPos.Y=LastReplaceLine then
inc(SrcEditEndPos.X,LastReplaceColOffset);
if SrcEditEndPos.Y>=LastReplaceLine then
inc(SrcEditEndPos.Y,ReplaceLineOffset);
SrcEditPosValid:=true;
end;
end;
@ -405,6 +349,11 @@ var
if Prompt and (TheFileName<>'') then begin
// open the place in the source editor
EndLocks;
// update windows
ProcessMessages;
if Result=mrAbort then exit;
GetSrcEditPos;
if LazarusIDE.DoOpenFileAndJumpToPos(TheFileName,SrcEditStartPos,
-1,-1,[ofUseCache,ofDoNotLoadResource,ofVirtualFile,ofRegularFile])
@ -437,8 +386,28 @@ var
SrcEdit.SelectText(SrcEditStartPos.Y,SrcEditStartPos.X,
SrcEditEndPos.Y,SrcEditEndPos.X);
SrcEdit.Selection:=AReplace;
// adjust CurLine and FoundEndPos for next search
DebugLn('DoReplaceLine CurLine="',CurLine,'" FoundStartPos=',dbgs(FoundStartPos),' FoundEndPos=',dbgs(FoundEndPos));
// count total replacements and adjust offsets
aLineCount:=LineEndCount(AReplace,aLastLineLength);
if aLineCount>0 then begin
// replaced with multiple lines
LastReplaceColOffset:=aLastLineLength+1-FoundEndPos.X;
end else begin
if FoundStartPos.Y<>LastReplaceLine then
LastReplaceColOffset:=0;
// replaced with some words
if FoundStartPos.Y=FoundEndPos.Y then begin
// replaced some words with some words
inc(LastReplaceColOffset,
aLastLineLength-(FoundEndPos.X-FoundStartPos.X));
end else begin
// replaced several lines with some words
inc(LastReplaceColOffset,FoundStartPos.X+aLastLineLength-FoundEndPos.X);
end;
end;
LastReplaceLine:=FoundEndPos.Y;
inc(ReplaceLineOffset,aLineCount-(FoundEndPos.Y-FoundStartPos.Y));
//DebugLn(['DoReplaceLine FoundStartPos=',dbgs(FoundStartPos),' FoundEndPos=',dbgs(FoundEndPos),' aLastLineLength=',aLastLineLength,' LastReplaceLine=',LastReplaceLine,' LastReplaceColOffset=',LastReplaceColOffset,' ReplaceLineOffset=',ReplaceLineOffset]);
end else begin
// change text in memory/disk
OriginalFile.LineColToPosition(FoundStartPos.Y,FoundStartPos.X,
@ -461,8 +430,6 @@ var
OriginalFile.LineColToPosition(FoundEndPos.Y,FoundEndPos.X,
ReplacedTextOriginalPos);
end;
// adjust replace offset
inc(CurLineReplaceOffset,length(AReplace)-(FoundEndPos.X-FoundStartPos.X));
end;
procedure CommitChanges;
@ -504,10 +471,11 @@ var
end;
var
LastMatchStart: LongInt;
LastMatchEnd: Integer;
Found: Boolean;
Line: integer; // Loop Counter. 0 based.
Src: String;
NewMatchStartPos: PtrInt;
NewMatchEndPos: PtrInt;
Lines: String;
begin
if (Progress<>nil) and Progress.Abort then exit(mrAbort);
Result:=mrOk;
@ -515,7 +483,6 @@ begin
OriginalFile:=nil;
CaseFile:=nil;
RE:=nil;
SearchAllHitsInLine:=sesoReplace in Flags;
SrcEdit:=nil;
SrcEditValid:=false;
PaintLockEnabled:=false;
@ -524,6 +491,10 @@ begin
ReplacedTextLength:=0;
ReplacedTextOriginalPos:=1;
ReplaceLineOffset:=0;
LastReplaceLine:=0;
LastReplaceColOffset:=0;
try
FoundEndPos:= Point(0,0);
TempSearch:= SearchFor;
@ -547,79 +518,83 @@ begin
end;
if sesoRegExpr in Flags then begin
// Setup the regular expression search engine.
RE:= TRegExpr.Create;
with RE do begin
// Setup the regular expression search engine
RE:=TRegExpr.Create;
RE.ModifierI:=(sesoReplace in Flags) and (not (sesoMatchCase in Flags));
RE.ModifierM:= sesoMultiLine in Flags;
if (sesoReplace in Flags) then begin
Src:=OriginalFile.Source;
if sesoWholeWord in Flags then
Expression:= '\b'+SearchFor+'\b'
RE.Expression:= '\b'+SearchFor+'\b'
else
Expression:= SearchFor;
ModifierI:= not (sesoMatchCase in Flags);
ModifierM:= False; //for now
RE.Expression:= SearchFor;
end else begin
Src:=CaseFile.Source;
if sesoWholeWord in Flags then
RE.Expression:= '\b'+TempSearch+'\b'
else
RE.Expression:= TempSearch;
end;
end else begin
Src:=CaseFile.Source;
end;
//debugln(['TheFileName=',TheFileName,' len=',OriginalFile.SourceLength,' Cnt=',OriginalFile.LineCount,' TempSearch=',TempSearch]);
ProcessMessages;
CurLine:='';
for Line:=0 to OriginalFile.LineCount-1 do begin
if (Line and $ff)=0 then begin
EndLocks;
ProcessMessages;
NewMatchEndPos:=1;
repeat
Found:=false;
if sesoRegExpr in Flags then begin
// search the text for regular expression
RE.InputString:=Src;
if RE.ExecPos(NewMatchEndPos) then begin
Found:=true;
NewMatchStartPos:=RE.MatchPos[0];
NewMatchEndPos:=NewMatchStartPos+RE.MatchLen[0];
end;
end else begin
// search for normal text
if SearchNextInText(PChar(TempSearch),length(TempSearch),
PChar(Src),length(Src),
NewMatchEndPos-1,NewMatchStartPos,NewMatchEndPos,
sesoWholeWord in Flags,sesoMultiLine in Flags)
then begin
Found:=true;
inc(NewMatchStartPos);
inc(NewMatchEndPos);
end;
end;
FoundStartPos.X:=1;
FoundStartPos.Y:=Line+1;
FoundEndPos:=Point(0,0);
CurLineReplaceOffset:=0;
repeat
LastMatchStart:=FoundStartPos.X;
LastMatchEnd:=1;
// search
Found:=false;
if sesoRegExpr in Flags then begin
// search the line for regular expression
CurLine:=OriginalFile.GetLine(Line);
RE.InputString:=CurLine;
if RE.ExecPos(LastMatchEnd) then begin
Found:=true;
FoundStartPos.X:=RE.MatchPos[0]+LastMatchEnd-1;
FoundEndPos.X:=FoundStartPos.X+Re.MatchLen[0];
FoundEndPos.Y:=FoundStartPos.Y;
end;
if Found then begin
// found => convert position, report and/or replace
OriginalFile.AbsoluteToLineCol(NewMatchStartPos,
FoundStartPos.Y,FoundStartPos.X);
OriginalFile.AbsoluteToLineCol(NewMatchEndPos,
FoundEndPos.Y,FoundEndPos.X);
//DebugLn(['SearchInText NewMatchStartPos=',NewMatchStartPos,' NewMatchEndPos=',NewMatchEndPos,' FoundStartPos=',dbgs(FoundStartPos),' FoundEndPos=',dbgs(FoundEndPos),' Found="',dbgstr(copy(Src,NewMatchStartPos,NewMatchEndPos-NewMatchStartPos)),'"']);
if sesoReplace in Flags then begin
DoReplaceLine
end else begin
// search the line for text
Found:=SearchInLine(TempSearch,CaseFile,FoundStartPos.Y,
sesoWholeWord in Flags,LastMatchEnd,FoundStartPos.X);
if Found then begin
if (LastMatchStart=LastMatchEnd) then
CurLine:=OriginalFile.GetLine(FoundStartPos.Y-1);
FoundEndPos.X:=FoundStartPos.X+length(TempSearch);
FoundEndPos.Y:=FoundStartPos.Y;
if (Progress<>nil)
and (Progress.OnAddMatch<>nil) then begin
Lines:=OriginalFile.GetLines(FoundStartPos.Y,FoundEndPos.Y);
Lines:=ChompOneLineEndAtEnd(Lines);
Progress.OnAddMatch(TheFileName,FoundStartPos,FoundEndPos,Lines);
end;
end;
// add found place
if Found then begin
//DebugLn('TSearchForm.SearchFile CurLine="',CurLine,'" Found=',dbgs(Found),' FoundStartPos=',dbgs(FoundStartPos),' FoundEndPos=',dbgs(FoundEndPos),' Line=',dbgs(Line));
if sesoReplace in Flags then begin
DoReplaceLine
end else begin
if (Progress<>nil)
and (Progress.OnAddMatch<>nil) then
Progress.OnAddMatch(TheFileName,FoundStartPos,FoundEndPos,CurLine);
end;
end else
break;
until (Result=mrAbort) or (not SearchAllHitsInLine) or (FoundEndPos.X<1);
end else begin
// not found
break;
end;
// check abort
if (Result=mrAbort) then begin
exit;
end;
end;//for
ProcessMessages;
until false;
finally
CommitChanges;
if OriginalFile=CaseFile then
@ -672,15 +647,18 @@ procedure TSearchForm.OnAddMatch(const Filename: string; const StartPos,
var
MatchLen: Integer;
TrimmedMatch: LongInt;
TrimmedCurLine: String;
TrimmedLines: String;
LastLineLen: integer;
begin
TrimmedMatch:=StartPos.X;
TrimmedCurLine:=TrimLineAndAdjustPos(Lines,TrimmedMatch);
MatchLen:=EndPos.X-StartPos.X;
LineEndCount(Lines,LastLineLen);
MatchLen:=length(Lines)-(LastLineLen+1-EndPos.X)-StartPos.X+1;
if MatchLen<1 then MatchLen:=1;
//DebugLn(['TSearchForm.OnAddMatch length(Lines)=',length(Lines),' LastLineLen=',LastLineLen,' MatchLen=',MatchLen]);
TrimmedMatch:=StartPos.X;
TrimmedLines:=TrimLinesAndAdjustPos(Lines,TrimmedMatch);
//DebugLn(['TSearchForm.OnAddMatch StartPos=',dbgs(StartPos),' EndPos=',dbgs(EndPos),' Lines="',Lines,'"']);
SearchResultsView.AddMatch(fResultsWindow,FileName,StartPos,
TrimmedCurLine, TrimmedMatch, MatchLen);
SearchResultsView.AddMatch(fResultsWindow,FileName,StartPos,EndPos,
TrimmedLines, TrimmedMatch, MatchLen);
UpdateMatches;
end;

View File

@ -187,6 +187,7 @@ function SplitString(const s: string; Delimiter: char): TStrings;
procedure SplitString(const s: string; Delimiter: char; AddTo: TStrings;
ClearList: boolean = true);
function SpecialCharsToSpaces(const s: string): string;
function SpecialCharsToHex(const s: string): string;
function LineBreaksToDelimiter(const s: string; Delimiter: char): string;
function LineBreaksToSystemLineBreaks(const s: string): string;
function StringListToText(List: TStrings; const Delimiter: string;
@ -1515,6 +1516,19 @@ begin
Result:=Trim(Result);
end;
function SpecialCharsToHex(const s: string): string;
var
i: Integer;
begin
Result:=s;
if Result='' then exit;
for i:=length(Result) downto 1 do
if Result[i]<' ' then
Result:=copy(Result,1,i-1)
+'#'+Format('%d',[ord(Result[i])])
+copy(Result,i+1,length(Result));
end;
function LineBreaksToDelimiter(const s: string; Delimiter: char): string;
var
p: Integer;

View File

@ -40,24 +40,26 @@ uses
Classes, SysUtils, LCLProc, LResources, Forms, Controls, Graphics, Dialogs,
ComCtrls, ExtCtrls, StdCtrls, Buttons, LCLType,
IDEOptionDefs, LazarusIDEStrConsts, EnvironmentOpts, InputHistory,
FindInFilesDlg, Project, MainIntf;
IDEProcs, FindInFilesDlg, Project, MainIntf;
type
{ TLazSearchMatchPos }
TLazSearchMatchPos = class(TObject)
private
FFileEndPos: TPoint;
FFilename: string;
FFilePosition: TPoint;
FFileStartPos: TPoint;
fMatchStart: integer;
fMatchLen: integer;
FShownFilename: string;
FTheText: string;
public
property MatchStart: integer read fMatchStart write fMatchStart;
property MatchLen: integer read fMatchLen write fMatchLen;
property MatchStart: integer read fMatchStart write fMatchStart;// start in TheText
property MatchLen: integer read fMatchLen write fMatchLen; // length in TheText
property Filename: string read FFilename write FFilename;
property FilePosition: TPoint read FFilePosition write FFilePosition;
property FileStartPos: TPoint read FFileStartPos write FFileStartPos;
property FileEndPos: TPoint read FFileEndPos write FFileEndPos;
property TheText: string read FTheText write FTheText;
property ShownFilename: string read FShownFilename write FShownFilename;
end;//TLazSearchMatchPos
@ -85,7 +87,7 @@ type
{ TLazSearchResultLB }
TLazSearchResultLB = Class(TCustomListBox)
TLazSearchResultLB = class(TCustomListBox)
private
fSearchObject: TLazSearch;
FSkipped: integer;
@ -105,6 +107,9 @@ type
procedure EndUpdate;
procedure ShortenPaths;
procedure FreeObjects(var slItems: TStrings);
function BeautifyLine(const Filename: string; X, Y: integer;
const Line: string): string;
function BeautifyLine(SearchPos: TLazSearchMatchPos): string;
property BackUpStrings: TStrings read fBackUpStrings write fBackUpStrings;
property Filtered: Boolean read fFiltered write fFiltered;
property SearchInListPhrases: string read FSearchInListPhrases write FSearchInListPhrases;
@ -136,15 +141,16 @@ type
X, Y: Integer);
Procedure LazLBMouseWheel(Sender: TObject; Shift: TShiftState;
WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
procedure edSearchInListChange (Sender: TObject );
procedure edSearchInListChange(Sender: TObject );
procedure ResultsNoteBookPageChanged (Sender: TObject );
procedure bnForwardSearchClick (Sender: TObject );
procedure bnResetResultsClick (Sender: TObject );
procedure edSearchInListKeyDown (Sender: TObject; var Key: Word;
Shift: TShiftState );
procedure bnForwardSearchClick(Sender: TObject );
procedure bnResetResultsClick(Sender: TObject );
procedure edSearchInListKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState );
procedure bnFilterClick (Sender: TObject );
private
FMaxItems: integer;
function BeautifyPageName(const APageName: string): string;
function PageExists(const APageName: string): boolean;
function GetPageIndex(const APageName: string): integer;
function GetListBox(APageIndex: integer): TLazSearchResultLB;
@ -152,8 +158,7 @@ type
procedure ListBoxDoubleClicked(Sender: TObject);
procedure SetItems(Index: Integer; Value: TStrings);
function GetItems(Index: integer): TStrings;
fOnSelectionChanged: TNotifyEvent;
fListBoxFont: TFont;
fOnSelectionChanged: TNotifyEvent; fListBoxFont: TFont;
fMouseOverIndex: integer;
procedure SetMaxItems(const AValue: integer);
public
@ -168,12 +173,12 @@ type
function GetSelectedText: string;
function GetSelectedMatchPos: TLazSearchMatchPos;
procedure BringResultsToFront(const APageName: string);
procedure AddMatch(const AIndex: integer;
const Filename: string; const FilePosition: TPoint;
procedure AddMatch(const APageIndex: integer;
const Filename: string; const StartPos, EndPos: TPoint;
const TheText: string;
const MatchStart: integer; const MatchLen: integer);
procedure BeginUpdate(AIndex: integer);
procedure EndUpdate(AIndex: integer);
procedure BeginUpdate(APageIndex: integer);
procedure EndUpdate(APageIndex: integer);
procedure Parse_Search_Phrases(var slPhrases: TStrings);
property ListBoxFont: TFont read fListBoxFont write fListBoxFont;
property OnSelectionChanged: TNotifyEvent read fOnSelectionChanged
@ -190,19 +195,20 @@ implementation
{ TSearchResultsView }
const
SPACE = ' ';
MaxTextLen = 80;
function CopySearchMatchPos(var Src, Dest: TLazSearchMatchPos): Boolean;
begin
Result := False;
if ((Src = nil) or (Dest = nil)) then Exit;
Dest.MatchStart := Src.MatchStart;
Dest.MatchLen := Src.MatchLen;
Dest.Filename := Src.Filename;
Dest.FilePosition := Src.FilePosition;
Dest.TheText := Src.TheText;
Dest.ShownFilename := Dest.Filename;
Result := True;
Result := False;
if ((Src = nil) or (Dest = nil)) then Exit;
Dest.MatchStart := Src.MatchStart;
Dest.MatchLen := Src.MatchLen;
Dest.Filename := Src.Filename;
Dest.FileStartPos := Src.FileStartPos;
Dest.FileEndPos := Src.FileEndPos;
Dest.TheText := Src.TheText;
Dest.ShownFilename := Dest.Filename;
Result := True;
end;
procedure TSearchResultsView.Form1Create(Sender: TObject);
@ -441,8 +447,17 @@ begin
end;//End if Assigned(CurrentLB)
end;
procedure TSearchResultsView.AddMatch(const AIndex: integer;
const Filename: string; const FilePosition: TPoint;
function TSearchResultsView.BeautifyPageName(const APageName: string): string;
const
MaxPageName = 25;
begin
Result:=SpecialCharsToHex(APageName);
if UTF8Length(Result)>MaxPageName then
Result:=UTF8Copy(Result,1,15)+'...';
end;
procedure TSearchResultsView.AddMatch(const APageIndex: integer;
const Filename: string; const StartPos, EndPos: TPoint;
const TheText: string;
const MatchStart: integer; const MatchLen: integer);
var
@ -450,7 +465,7 @@ var
SearchPos: TLazSearchMatchPos;
ShownText: String;
begin
CurrentLB:= GetListBox(AIndex);
CurrentLB:=GetListBox(APageIndex);
if Assigned(CurrentLB) then
begin
if CurrentLB.UpdateState then begin
@ -465,16 +480,14 @@ begin
end;
end;
SearchPos:= TLazSearchMatchPos.Create;
SearchPos.MatchStart:= MatchStart;
SearchPos.MatchLen:= MatchLen;
SearchPos.MatchStart:=MatchStart;
SearchPos.MatchLen:=MatchLen;
SearchPos.Filename:=Filename;
SearchPos.FilePosition:=FilePosition;
SearchPos.FileStartPos:=StartPos;
SearchPos.FileEndPos:=EndPos;
SearchPos.TheText:=TheText;
SearchPos.ShownFilename:=SearchPos.Filename;
ShownText:=SearchPos.ShownFilename
+' ('+IntToStr(SearchPos.FilePosition.Y)
+','+IntToStr(SearchPos.FilePosition.X)+')'
+' '+SearchPos.TheText;
ShownText:=CurrentLB.BeautifyLine(SearchPos);
if CurrentLB.UpdateState then
CurrentLB.UpdateItems.AddObject(ShownText, SearchPos)
else
@ -488,20 +501,20 @@ begin
fListBoxFont.free;
end;//SearchResulstViewDestroy
Procedure TSearchResultsView.BeginUpdate(AIndex: integer);
Procedure TSearchResultsView.BeginUpdate(APageIndex: integer);
var
CurrentLB: TLazSearchResultLB;
begin
CurrentLB:= GetListBox(AIndex);
CurrentLB:= GetListBox(APageIndex);
if Assigned(CurrentLB) then
CurrentLB.BeginUpdate;
end;//BeginUpdate
procedure TSearchResultsView.EndUpdate(AIndex: integer);
procedure TSearchResultsView.EndUpdate(APageIndex: integer);
var
CurrentLB: TLazSearchResultLB;
begin
CurrentLB:= GetListBox(AIndex);
CurrentLB:= GetListBox(APageIndex);
if Assigned(CurrentLB) then
begin
CurrentLB.EndUpdate;
@ -625,14 +638,16 @@ end;
function TSearchResultsView.PageExists(const APageName: string): boolean;
var
i: integer;
CurPagename: String;
begin
result:= false;
Result:= false;
CurPagename:=BeautifyPageName(APageName);
for i:= 0 to ResultsNoteBook.Pages.Count - 1 do
begin
if (ResultsNoteBook.Pages[i] = APageName + SPACE) then
if (ResultsNoteBook.Pages[i] = CurPageName) then
begin
result:= true;
break;
Result:= true;
exit;
end;//if
end;//for
end;//PageExists
@ -646,8 +661,8 @@ begin
end;
end;
{Add Result will create a tab in the Results view window with an new
list box or focus an existing listbox and update it's searchoptions.}
{ Add Result will create a tab in the Results view window with an new
list box or focus an existing listbox and update it's searchoptions.}
function TSearchResultsView.AddSearch(const ResultsName: string;
const SearchText: string;
const ReplaceText: string;
@ -659,13 +674,16 @@ var
NewPage: LongInt;
i: integer;
SearchObj: TLazSearch;
NewPageName: String;
begin
result:= -1;
Result:= -1;
if Assigned(ResultsNoteBook) then
begin
With ResultsNoteBook do
NewPageName:=BeautifyPageName(ResultsName);
//DebugLn(['TSearchResultsView.AddSearch NewPageName=',dbgstr(NewPageName),' ResultsName="',dbgstr(ResultsName),'"']);
with ResultsNoteBook do
begin
i:= GetPageIndex(ResultsName);
i:= GetPageIndex(NewPageName);
if i>=0 then
begin
NewListBox:= GetListBox(i);
@ -677,7 +695,7 @@ begin
end//if
else
begin
NewPage:= Pages.Add(ResultsName + SPACE);
NewPage:= Pages.Add(NewPageName);
ResultsNoteBook.PageIndex:= NewPage;
ResultsNoteBook.Page[ResultsNoteBook.PageIndex].OnKeyDown := @ListBoxKeyDown;
if NewPage > -1 then
@ -719,7 +737,6 @@ begin
end;//if
end;//AddResult
procedure TSearchResultsView.LazLBShowHint(Sender: TObject;
HintInfo: PHintInfo);
var
@ -738,8 +755,8 @@ begin
MatchPos:= nil;
if MatchPos<>nil then
HintStr:=MatchPos.Filename
+' ('+IntToStr(MatchPos.FilePosition.Y)
+','+IntToStr(MatchPos.FilePosition.X)+')'
+' ('+IntToStr(MatchPos.FileStartPos.Y)
+','+IntToStr(MatchPos.FileStartPos.X)+')'
+' '+MatchPos.TheText
else
HintStr:=Items[fMouseOverIndex];
@ -756,12 +773,10 @@ var
FirstPart: string;
BoldPart: string;
LastPart: string;
BoldLen: integer;
TheText: string;
TheTop: integer;
MatchPos: TLazSearchMatchPos;
TextEnd: integer;
ShownMatchStart: LongInt;
begin
With Control as TLazSearchResultLB do
begin
@ -770,29 +785,33 @@ begin
MatchPos:= TLazSearchMatchPos(Items.Objects[Index])
else
MatchPos:= nil;
TheText:= Items[Index];
if Assigned(MatchPos) then
begin
TheTop:= ARect.Top;
BoldLen:= MatchPos.MatchLen;
ShownMatchStart:=length(TheText)-length(MatchPos.TheText)
+MatchPos.MatchStart;
FirstPart:= copy(TheText,1,ShownMatchStart - 1);
BoldPart:= copy(TheText,ShownMatchStart ,BoldLen);
LastPart:= copy(TheText, ShownMatchStart + BoldLen,
Length(TheText) - (ShownMatchStart + BoldLen) + 2);
FirstPart:=MatchPos.ShownFilename+' ('+IntToStr(MatchPos.FileStartPos.Y)
+','+IntToStr(MatchPos.FileStartPos.X)+') '
+SpecialCharsToHex(copy(MatchPos.TheText,1,MatchPos.MatchStart-1));
BoldPart:=SpecialCharsToHex(
copy(MatchPos.TheText,MatchPos.MatchStart,MatchPos.MatchLen));
LastPart:=SpecialCharsToHex(
copy(MatchPos.TheText, MatchPos.MatchStart+MatchPos.MatchLen,
Length(MatchPos.TheText)));
if UTF8Length(BoldPart)>MaxTextLen then
BoldPart:=UTF8Copy(BoldPart,1,MaxTextLen)+'...';
//DebugLn(['TSearchResultsView.ListboxDrawitem FirstPart="',FirstPart,'" BoldPart="',BoldPart,'" LastPart="',LastPart,'"']);
Canvas.TextOut(ARect.Left, TheTop, FirstPart);
TextEnd:= ARect.Left + Canvas.TextWidth(FirstPart);
Canvas.Font.Style:= Canvas.Font.Style + [fsBold];
{TODO: Find out why bold is 1 pixel off in gtk}
Canvas.TextOut(TextEnd, TheTop, BoldPart);
TextEnd:= TextEnd + Canvas.TextWidth(BoldPart);
Canvas.Font.Style:= Canvas.Font.Style - [fsBold];
Canvas.Font.Style:=Canvas.Font.Style - [fsBold];
Canvas.TextOut(TextEnd, TheTop, LastPart);
end//if
else
begin
TheText:=Items[Index];
Canvas.TextOut(ARect.Left, ARect.Top, TheText);
end;//else
end;//with
@ -822,7 +841,7 @@ begin
Result.y:= -1;
MatchPos:=GetSelectedMatchPos;
if MatchPos=nil then exit;
Result:=MatchPos.FilePosition;
Result:=MatchPos.FileStartPos;
end;//GetSourcePositon
{Returns The file name portion of a properly formated search result}
@ -893,13 +912,15 @@ end;
function TSearchResultsView.GetPageIndex(const APageName: string): integer;
var
i: integer;
CurPagename: String;
begin
result:= -1;
Result:= -1;
CurPagename:=BeautifyPageName(APageName);
for i:= 0 to ResultsNoteBook.Pages.Count - 1 do
begin
if (ResultsNoteBook.Pages[i] = APageName + SPACE) then
if (ResultsNoteBook.Pages[i] = CurPageName) then
begin
result:= i;
Result:= i;
break;
end;//if
end;//for
@ -1075,10 +1096,7 @@ begin
MatchPos:=TLazSearchMatchPos(AnObject);
MatchPos.ShownFilename:=copy(MatchPos.Filename,SharedLen+1,
length(MatchPos.Filename));
ShownText:=MatchPos.ShownFilename
+' ('+IntToStr(MatchPos.FilePosition.Y)
+','+IntToStr(MatchPos.FilePosition.X)+')'
+' '+MatchPos.TheText;
ShownText:=BeautifyLine(MatchPos);
SrcList[i]:=ShownText;
SrcList.Objects[i]:=MatchPos;
end;
@ -1096,6 +1114,25 @@ begin
end;//End for-loop
end;
function TLazSearchResultLB.BeautifyLine(const Filename: string; X, Y: integer;
const Line: string): string;
begin
Result:=SpecialCharsToHex(Line);
if UTF8Length(Result)>MaxTextLen then
Result:=UTF8Copy(Result,1,MaxTextLen)+'...';
Result:=Filename
+' ('+IntToStr(Y)
+','+IntToStr(X)+')'
+' '+Result;
end;
function TLazSearchResultLB.BeautifyLine(SearchPos: TLazSearchMatchPos
): string;
begin
Result:=BeautifyLine(SearchPos.ShownFilename,SearchPos.FileStartPos.X,
SearchPos.FileStartPos.Y,SearchPos.TheText);
end;
initialization
{$I searchresultview.lrs}

View File

@ -4235,7 +4235,7 @@ var
ListIndex: integer;
begin
ShowSearchResultsView;
ListIndex:=SearchResultsView.AddSearch(lisSearchFor+ASearchForm.SearchText,
ListIndex:=SearchResultsView.AddSearch(ASearchForm.SearchText,
ASearchForm.SearchText,
ASearchForm.ReplaceText,
ASearchForm.SearchDirectory,

View File

@ -725,6 +725,7 @@ type
procedure DestroyCursors;
function GetCursors(Index: Integer): HCURSOR;
function GetCustomFormCount: Integer;
function GetCustomFormZOrderCount: Integer;
function GetCustomForms(Index: Integer): TCustomForm;
function GetCustomFormsZOrdered(Index: Integer): TCustomForm;
function GetFonts : TStrings;
@ -758,19 +759,19 @@ type
procedure UpdateScreen;
// handler
procedure AddHandlerFormAdded(OnFormAdded: TScreenFormEvent;
AsLast: Boolean{$IFDEF HasDefaultValues}=true{$ENDIF});
AsLast: Boolean=true);
procedure RemoveHandlerFormAdded(OnFormAdded: TScreenFormEvent);
procedure AddHandlerRemoveForm(OnRemoveForm: TScreenFormEvent;
AsLast: Boolean{$IFDEF HasDefaultValues}=true{$ENDIF});
AsLast: Boolean=true);
procedure RemoveHandlerRemoveForm(OnRemoveForm: TScreenFormEvent);
procedure AddHandlerActiveControlChanged(
OnActiveControlChanged: TScreenControlEvent;
AsLast: Boolean{$IFDEF HasDefaultValues}=true{$ENDIF});
OnActiveControlChanged: TScreenControlEvent;
AsLast: Boolean=true);
procedure RemoveHandlerActiveControlChanged(
OnActiveControlChanged: TScreenControlEvent);
procedure AddHandlerActiveFormChanged(
OnActiveFormChanged: TScreenActiveFormChangedEvent;
AsLast: Boolean{$IFDEF HasDefaultValues}=true{$ENDIF});
OnActiveFormChanged: TScreenActiveFormChangedEvent;
AsLast: Boolean=true);
procedure RemoveHandlerActiveFormChanged(
OnActiveFormChanged: TScreenActiveFormChangedEvent);
procedure RemoveAllHandlersOfObject(AnObject: TObject); override;
@ -782,6 +783,7 @@ type
property Cursors[Index: Integer]: HCURSOR read GetCursors write SetCursors;
property CustomFormCount: Integer read GetCustomFormCount;
property CustomForms[Index: Integer]: TCustomForm read GetCustomForms;
property CustomFormZOrderCount: Integer read GetCustomFormZOrderCount;
property CustomFormsZOrdered[Index: Integer]: TCustomForm
read GetCustomFormsZOrdered;
property FocusedForm: TCustomForm read FFocusedForm;

View File

@ -804,12 +804,14 @@ procedure TCustomForm.SetZOrder(Topmost: Boolean);
begin
if Parent=nil then begin
if TopMost and HandleAllocated then begin
if (Screen.GetCurrentModalForm<>nil)
and (Screen.GetCurrentModalForm<>Self) then exit;
//TODO: call TWSCustomFormClass(Widgetset).SetZORder.
Screen.MoveFormToZFront(Self);
SetForegroundWindow(Handle);
end;
exit;
end;
inherited SetZOrder(Topmost);
end else
inherited SetZOrder(Topmost);
end;
procedure TCustomForm.SetParent(NewParent: TWinControl);

View File

@ -347,6 +347,11 @@ begin
Result:=FCustomForms.Count;
end;
function TScreen.GetCustomFormZOrderCount: Integer;
begin
Result:=FCustomFormsZOrdered.Count;
end;
{------------------------------------------------------------------------------
function TScreen.GetCustomForms(Index: Integer): TCustomForm;
------------------------------------------------------------------------------}
@ -467,8 +472,6 @@ begin
FCustomFormsZOrdered.Remove(AForm);
FFormList.Remove(AForm);
Application.UpdateVisible;
//if (FCustomForms.Count = 0) and (Application.FHintWindow <> nil) then
// Application.FHintWindow.ReleaseHandle;
end;
{------------------------------------------------------------------------------

View File

@ -709,6 +709,7 @@ begin
then begin
gdk_window_get_user_data(PGDKWindow(List^.Data), @Window);
if GtkWidgetIsA(PGtkWidget(Window), GTK_TYPE_WINDOW)
and gtk_widget_visible(PGtkWidget(Window))
then begin
// visible window found -> add to list
New(ATransientWindow);
@ -724,13 +725,20 @@ begin
else
ATransientWindow^.SortIndex:=-1;
ATransientWindow^.IsModal:=(ATransientWindow^.SortIndex>=0)
and (GTK_WIDGET_VISIBLE(PGtkWidget(Window)));
and (GTK_WIDGET_VISIBLE(PGtkWidget(Window)));
if not ATransientWindow^.IsModal then begin
if (LCLObject is TCustomForm)
and (TCustomForm(LCLObject).Parent=nil) then
ATransientWindow^.SortIndex:=
Screen.CustomFormIndex(TCustomForm(LCLObject));
Screen.CustomFormZIndex(TCustomForm(LCLObject));
end;
if ATransientWindow^.SortIndex<0 then begin
// this window has no form. Move it to the back.
ATransientWindow^.SortIndex:=Screen.CustomFormCount;
end;
//DebugLn(['TGtkWidgetSet.UpdateTransientWindows LCLObject=',DbgSName(LCLObject),' ATransientWindow^.SortIndex=',ATransientWindow^.SortIndex]);
if AllWindows=nil then AllWindows:=TFPList.Create;
AllWindows.Add(ATransientWindow);
end;
@ -739,9 +747,12 @@ begin
end;
if AllWindows=nil then exit;
//for i:=0 to SCreen.CustomFormZOrderCount-1 do
// DebugLn(['TGtkWidgetSet.UpdateTransientWindows i=',i,'/',SCreen.CustomFormZOrderCount,' ',DbgSName(SCreen.CustomFormsZOrdered[i])]);
// sort
// move all modal windows at the end of the window list
// move all modal windows to the end of the window list
i:=AllWindows.Count-1;
FirstModal:=AllWindows.Count;
while i>=0 do begin
@ -772,6 +783,8 @@ begin
// there are modal windows
// -> sort windows in z order and setup transient relationships
//DebugLn(['TGtkWidgetSet.UpdateTransientWindows ModalWindows=',AllWindows.Count-FirstModal,' NonModalWindows=',FirstModal]);
// sort modal windows (bubble sort)
for i:=FirstModal to AllWindows.Count-2 do begin
for j:=i+1 to AllWindows.Count-1 do begin