From 8783e0100de28ae66b6c85df299b7c3acbdacf79 Mon Sep 17 00:00:00 2001 From: mattias Date: Fri, 1 Sep 2006 14:38:07 +0000 Subject: [PATCH] IDE: Find in Files: implemented multi line pattern and replacement, gtk intf: improved z ordering with modal forms git-svn-id: trunk@9779 - --- components/codetools/basiccodetools.pas | 103 +++++++- components/codetools/sourcelog.pas | 8 +- ide/findrenameidentifier.pas | 1 + ide/frmsearch.pas | 302 +++++++++++------------- ide/ideprocs.pp | 14 ++ ide/searchresultview.pp | 185 +++++++++------ ide/uniteditor.pp | 2 +- lcl/forms.pp | 14 +- lcl/include/customform.inc | 8 +- lcl/include/screen.inc | 7 +- lcl/interfaces/gtk/gtkobject.inc | 21 +- 11 files changed, 403 insertions(+), 262 deletions(-) diff --git a/components/codetools/basiccodetools.pas b/components/codetools/basiccodetools.pas index f4122821bc..60fa2de7d0 100644 --- a/components/codetools/basiccodetools.pas +++ b/components/codetools/basiccodetools.pas @@ -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 (CurPosSrc) or (IsNonWordChar[PChar(CurPos-1)^])) + then begin + CmpSearch:=Search; + CmpSrc:=CurPos; + while (CmpSearchCmpSrc[1]) then + inc(CmpSrc,2) + else + inc(CmpSrc); + if (CmpSearch+1CmpSearch[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; diff --git a/components/codetools/sourcelog.pas b/components/codetools/sourcelog.pas index 0adce6cde5..bedab578ed 100644 --- a/components/codetools/sourcelog.pas +++ b/components/codetools/sourcelog.pas @@ -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; diff --git a/ide/findrenameidentifier.pas b/ide/findrenameidentifier.pas index e10ffc3341..4ce360fe4a 100644 --- a/ide/findrenameidentifier.pas +++ b/ide/findrenameidentifier.pas @@ -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); diff --git a/ide/frmsearch.pas b/ide/frmsearch.pas index 200178e2b3..2c31b2a216 100644 --- a/ide/frmsearch.pas +++ b/ide/frmsearch.pas @@ -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=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; diff --git a/ide/ideprocs.pp b/ide/ideprocs.pp index 940ec680a9..a41f639c2e 100644 --- a/ide/ideprocs.pp +++ b/ide/ideprocs.pp @@ -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; diff --git a/ide/searchresultview.pp b/ide/searchresultview.pp index c798388bfa..3f02206fb2 100644 --- a/ide/searchresultview.pp +++ b/ide/searchresultview.pp @@ -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} diff --git a/ide/uniteditor.pp b/ide/uniteditor.pp index 5a31d079c8..3d0e3f1712 100644 --- a/ide/uniteditor.pp +++ b/ide/uniteditor.pp @@ -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, diff --git a/lcl/forms.pp b/lcl/forms.pp index 978e0000c6..bb6340607f 100644 --- a/lcl/forms.pp +++ b/lcl/forms.pp @@ -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; diff --git a/lcl/include/customform.inc b/lcl/include/customform.inc index ad144d439f..bc6fa10077 100644 --- a/lcl/include/customform.inc +++ b/lcl/include/customform.inc @@ -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); diff --git a/lcl/include/screen.inc b/lcl/include/screen.inc index 5b4b828ffc..e6dc14f5e3 100644 --- a/lcl/include/screen.inc +++ b/lcl/include/screen.inc @@ -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; {------------------------------------------------------------------------------ diff --git a/lcl/interfaces/gtk/gtkobject.inc b/lcl/interfaces/gtk/gtkobject.inc index 50ce436511..6ad4f768eb 100644 --- a/lcl/interfaces/gtk/gtkobject.inc +++ b/lcl/interfaces/gtk/gtkobject.inc @@ -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