From 86a71bfe1e6fbdcc7e855e12494eb3af00d9bd0e Mon Sep 17 00:00:00 2001 From: martin Date: Tue, 29 Mar 2011 23:44:25 +0000 Subject: [PATCH] SynEdit: Cache char-width info (Logical<>Physical) / Cleanup git-svn-id: trunk@30074 - --- components/synedit/syncompletion.pas | 2 +- components/synedit/synedit.pp | 60 +++--- components/synedit/syneditpointclasses.pas | 2 +- components/synedit/synedittextbase.pas | 188 +++++++++++++++++- components/synedit/synedittextbuffer.pp | 7 +- components/synedit/synedittexttabexpander.pas | 14 ++ components/synedit/synedittexttrimmer.pas | 31 ++- components/synedit/test/testbasicsynedit.pas | 76 +++++++ 8 files changed, 342 insertions(+), 38 deletions(-) diff --git a/components/synedit/syncompletion.pas b/components/synedit/syncompletion.pas index d360d19a91..8b26422b06 100644 --- a/components/synedit/syncompletion.pas +++ b/components/synedit/syncompletion.pas @@ -1593,7 +1593,7 @@ begin with TCustomSynEdit(F.CurrentEditor) do begin BeginUndoBlock; BeginUpdate; - LogCaret:=PhysicalToLogicalPos(CaretXY); + LogCaret := LogicalCaretXY; NewBlockBegin:=LogCaret; CurLine:=Lines[NewBlockBegin.Y - 1]; while (NewBlockBegin.X>1) and (NewBlockBegin.X-1<=length(CurLine)) diff --git a/components/synedit/synedit.pp b/components/synedit/synedit.pp index 388cbbf223..62ea695cc0 100644 --- a/components/synedit/synedit.pp +++ b/components/synedit/synedit.pp @@ -344,9 +344,9 @@ type FBlockIndent: integer; FCaret: TSynEditCaret; FInternalCaret: TSynEditCaret; + FScreenCaret: TSynEditScreenCaret; FInternalBlockSelection: TSynEditSelection; FOnChangeUpdating: TChangeUpdatingEvent; - FScreenCaret: TSynEditScreenCaret; FMouseSelectionMode: TSynSelectionMode; fCtrlMouseActive: boolean; // deprecated since 0.9.29 fMarkupManager : TSynEditMarkupManager; @@ -866,6 +866,7 @@ type property CaretX: Integer read GetCaretX write SetCaretX; property CaretY: Integer read GetCaretY write SetCaretY; property CaretXY: TPoint read GetCaretXY write SetCaretXY;// screen position + property LogicalCaretXY: TPoint read GetLogicalCaretXY write SetLogicalCaretXY; property CharsInWindow: Integer read fCharsInWindow; property CharWidth: integer read fCharWidth; property Color default clWhite; @@ -873,7 +874,6 @@ type {$IFDEF SYN_LAZARUS} property CtrlMouseActive: boolean read fCtrlMouseActive; deprecated; // deprecated in 0.9.29 {$ENDIF} - property LogicalCaretXY: TPoint read GetLogicalCaretXY write SetLogicalCaretXY; {$IFDEF SYN_LAZARUS} property SelStart: Integer read GetSelStart write SetSelStart; property SelEnd: Integer read GetSelEnd write SetSelEnd; @@ -2169,12 +2169,13 @@ end; function TCustomSynEdit.GetLogicalCaretXY: TPoint; begin - Result:=PhysicalToLogicalPos(CaretXY); + Result := FCaret.LineBytePos; end; procedure TCustomSynEdit.SetLogicalCaretXY(const NewLogCaretXY: TPoint); begin - CaretXY:=LogicalToPhysicalPos(NewLogCaretXY); + FCaret.ChangeOnTouch; + FCaret.LineBytePos := NewLogCaretXY; end; procedure TCustomSynEdit.SetBeautifier(NewBeautifier: TSynCustomBeautifier); @@ -2935,7 +2936,7 @@ begin Inc(X, CharsInWindow); FCaret.LineCharPos := Point(X, C.Y); if (not(sfIsDragging in fStateFlags)) then - SetBlockEnd(PhysicalToLogicalPos(CaretXY)); + SetBlockEnd(LogicalCaretXY); end; if fScrollDeltaY <> 0 then begin if GetKeyState(VK_SHIFT) < 0 then @@ -2947,7 +2948,7 @@ begin else Y := TopLine; // scrolling up FCaret.LineCharPos := Point(C.X, Y); if (not(sfIsDragging in fStateFlags)) then - SetBlockEnd(PhysicalToLogicalPos(CaretXY)); + SetBlockEnd(LogicalCaretXY); end; finally DoDecPaintLock(Self); @@ -3003,8 +3004,8 @@ begin if sfWaitForDragging in fStateFlags then begin ComputeCaret(X, Y); - SetBlockBegin(PhysicalToLogicalPos(CaretXY)); - SetBlockEnd(PhysicalToLogicalPos(CaretXY)); + SetBlockBegin(LogicalCaretXY); + SetBlockEnd(LogicalCaretXY); Exclude(fStateFlags, sfWaitForDragging); end; @@ -4117,7 +4118,7 @@ end; procedure TCustomSynEdit.SelectWord; begin - SetWordBlock(PhysicalToLogicalPos(CaretXY)); + SetWordBlock(LogicalCaretXY); end; procedure TCustomSynEdit.SelectLine(WithLeadSpaces: Boolean = True); @@ -4153,12 +4154,12 @@ end; function TCustomSynEdit.GetCaretX : Integer; begin - Result:= fCaret.CharPos; + Result:= FCaret.CharPos; end; function TCustomSynEdit.GetCaretY : Integer; begin - Result:= fCaret.LinePos; + Result:= FCaret.LinePos; end; function TCustomSynEdit.GetCaretUndo: TSynEditUndoItem; @@ -4188,7 +4189,7 @@ end; function TCustomSynEdit.GetCaretXY: TPoint; begin - Result := Point(CaretX, CaretY); + Result := FCaret.LineCharPos; end; function TCustomSynEdit.GetFoldedCodeColor: TSynSelectedColor; @@ -4994,7 +4995,7 @@ begin FBlockSelection.EndLineBytePos := Point(x2, MinMax(Value.y, 1, FTheLinesView.Count)); end; FBlockSelection.ActiveSelectionMode := smNormal; - CaretXY := FTheLinesView.LogicalToPhysicalPos(FBlockSelection.EndLineBytePos); + LogicalCaretXY := FBlockSelection.EndLineBytePos; //DebugLn(' FFF2 ',Value.X,',',Value.Y,' BlockBegin=',BlockBegin.X,',',BlockBegin.Y,' BlockEnd=',BlockEnd.X,',',BlockEnd.Y); DoDecPaintLock(Self); end; @@ -5748,10 +5749,8 @@ begin FCaret.DecForcePastEOL; end; FCaret.LineCharPos := NewCaret; - BlockBegin := {$IFDEF SYN_LAZARUS}PhysicalToLogicalPos(NewCaret) - {$ELSE}NewCaret{$ENDIF}; - BlockEnd := {$IFDEF SYN_LAZARUS}PhysicalToLogicalPos(CaretXY) - {$ELSE}CaretXY{$ENDIF}; + BlockBegin := PhysicalToLogicalPos(NewCaret); + BlockEnd := LogicalCaretXY; finally InternalEndUndoBlock; end; @@ -6211,9 +6210,9 @@ begin CaretNew := PrevWordPos; if FFoldedLinesView.FoldedAtTextIndex[CaretNew.Y - 1] then begin CY := FindNextUnfoldedLine(CaretNew.Y, False); - CaretNew := LogicalToPhysicalPos(Point(1 + Length(FTheLinesView[CY-1]), CY)); + CaretNew := Point(1 + Length(FTheLinesView[CY-1]), CY); end; - FCaret.LineCharPos := CaretNew; + FCaret.LineBytePos := CaretNew; end; ecWordRight, ecSelWordRight, ecColSelWordRight: begin @@ -6235,7 +6234,7 @@ begin else begin Temp := LineText; Len := Length(Temp); - LogCaretXY:=PhysicalToLogicalPos(CaretXY); + LogCaretXY:=LogicalCaretXY; Caret := CaretXY; //debugln('ecDeleteLastChar B Temp="',DbgStr(Temp),'" CaretX=',dbgs(CaretX),' LogCaretXY=',dbgs(LogCaretXY)); if LogCaretXY.X > Len +1 @@ -6275,7 +6274,7 @@ begin else begin Temp := LineText; Len := Length(Temp); - LogCaretXY:=PhysicalToLogicalPos(CaretXY); + LogCaretXY:=LogicalCaretXY; if LogCaretXY.X <= Len then begin // delete char @@ -6306,7 +6305,7 @@ begin WP := Point(Len + 1, CaretY); if (WP.X <> CaretX) or (WP.Y <> CaretY) then begin FInternalBlockSelection.StartLineBytePos := PhysicalToLogicalPos(WP); - FInternalBlockSelection.EndLineBytePos := PhysicalToLogicalPos(CaretXY); + FInternalBlockSelection.EndLineBytePos := LogicalCaretXY; FInternalBlockSelection.ActiveSelectionMode := smNormal; FInternalBlockSelection.SetSelTextPrimitive(smNormal, nil); if Helper <> '' then @@ -6322,7 +6321,7 @@ begin WP := Point(1, CaretY); if (WP.X <> CaretX) or (WP.Y <> CaretY) then begin FInternalBlockSelection.StartLineBytePos := PhysicalToLogicalPos(WP); - FInternalBlockSelection.EndLineBytePos := PhysicalToLogicalPos(CaretXY); + FInternalBlockSelection.EndLineBytePos := LogicalCaretXY; FInternalBlockSelection.ActiveSelectionMode := smNormal; FInternalBlockSelection.SetSelTextPrimitive(smNormal, nil); CaretXY := WP; @@ -6348,7 +6347,7 @@ begin if SelAvail and (not FBlockSelection.Persistent) and (eoOverwriteBlock in fOptions2) then SetSelTextExternal(''); Temp := LineText; - LogCaretXY:=PhysicalToLogicalPos(CaretXY); + LogCaretXY:=LogicalCaretXY; Len := Length(Temp); if LogCaretXY.X > Len + 1 then LogCaretXY.X := Len + 1; @@ -6424,7 +6423,7 @@ begin // Insert a linebreak, but do not apply any other functionality (such as indent) if SelAvail and (not FBlockSelection.Persistent) and (eoOverwriteBlock in fOptions2) then SetSelTextExternal(''); - LogCaretXY:=PhysicalToLogicalPos(CaretXY); + LogCaretXY:=LogicalCaretXY; FTheLinesView.EditLineBreak(LogCaretXY.X, LogCaretXY.Y); CaretXY := Point(1, CaretY + 1); EnsureCursorPosVisible; @@ -6611,7 +6610,7 @@ begin if fInserting then Helper := ''; fUndoList.AddChange(crInsert, StartOfBlock, - PhysicalToLogicalPos(CaretXY), + LogicalCaretXY, Helper, smNormal); if CaretX >= LeftChar + fCharsInWindow then LeftChar := LeftChar + min(25, fCharsInWindow - 1); @@ -8266,8 +8265,9 @@ var end; inc(EndPt.X); SetCaretAndSelection(CaretXY, StartPt, EndPt); - end else if MoveCaret then - CaretXY := LogicalToPhysicalPos(Result) + end + else if MoveCaret then + LogicalCaretXY := Result; end; procedure DoFindMatchingQuote(q: char); @@ -8543,7 +8543,7 @@ var end; begin - LogCaret:=PhysicalToLogicalPos(CaretXY); + LogCaret:=LogicalCaretXY; CX := LogCaret.X; CY := LogCaret.Y; // valid line? @@ -8596,7 +8596,7 @@ var LogCaret: TPoint; DelSpaces : Boolean; begin - LogCaret:=PhysicalToLogicalPos(CaretXY); + LogCaret:=LogicalCaretXY; CX := LogCaret.X; CY := LogCaret.Y; // valid line? diff --git a/components/synedit/syneditpointclasses.pas b/components/synedit/syneditpointclasses.pas index 8849fbb24a..0522841e4e 100644 --- a/components/synedit/syneditpointclasses.pas +++ b/components/synedit/syneditpointclasses.pas @@ -716,7 +716,7 @@ end; constructor TSynEditSelection.Create(ALines : TSynEditStrings; aActOnLineChanges: Boolean); begin Inherited Create(ALines); - FInternalCaret := TSynEditCaret.Create; + FInternalCaret := TSynEditCaret.Create; // TODO: does not need FLines.AddEditHandler FInternalCaret.Lines := FLines; FActiveSelectionMode := smNormal; diff --git a/components/synedit/synedittextbase.pas b/components/synedit/synedittextbase.pas index 06f7896361..c281cb3a8b 100644 --- a/components/synedit/synedittextbase.pas +++ b/components/synedit/synedittextbase.pas @@ -129,6 +129,30 @@ type read GetStorageMems write SetStorageMems; default; end; + { TSynLogicalPhysicalConvertor } +const + SYN_LP_MIN_ALLOC = 1024; // Keep at least n*SizeOf(TPhysicalCharWidths) allocated +type + TSynLogicalPhysicalConvertor = class + private + FLines: TSynEditStrings; + FCurrentWidths: TPhysicalCharWidths; + FCurrentWidthsLen, FCurrentWidthsAlloc: Integer; + FCurrentLine: Integer; + FTextChangeStamp, FViewChangeStamp: Int64; + // TODOtab-width + procedure PrepareWidthsForLine(AIndex: Integer; AForce: Boolean = False); + public + constructor Create(ALines: TSynEditStrings); + destructor Destroy; override; + // Line is 0-based // Column is 1-based + function PhysicalToLogical(AIndex, AColumn: Integer): Integer; + function PhysicalToLogical(AIndex, AColumn: Integer; out AColOffset: Integer): Integer; + // ACharPos 1=before 1st char + function LogicalToPhysical(AIndex, ABytePos: Integer): Integer; + function LogicalToPhysical(AIndex, ABytePos: Integer; var AColOffset: Integer): Integer; + end; + { TSynEditStringsBase } TSynEditStringsBase = class(TStrings) @@ -147,6 +171,7 @@ type TSynEditStrings = class(TSynEditStringsBase) private FSenderUpdateCount: Integer; + FLogPhysConvertor :TSynLogicalPhysicalConvertor; protected FIsUtf8: Boolean; function GetIsUtf8 : Boolean; virtual; @@ -155,6 +180,8 @@ type function GetExpandedString(Index: integer): string; virtual; abstract; function GetLengthOfLongestLine: integer; virtual; abstract; procedure SetTextStr(const Value: string); override; + function GetTextChangeStamp: int64; virtual; abstract; + function GetViewChangeStamp: int64; virtual; function GetUndoList: TSynEditUndoList; virtual; abstract; function GetRedoList: TSynEditUndoList; virtual; abstract; @@ -172,6 +199,7 @@ type procedure DoGetPhysicalCharWidths(Line: PChar; LineLen, Index: Integer; PWidths: PPhysicalCharWidth); virtual; abstract; public constructor Create; + destructor Destroy; override; procedure BeginUpdate(Sender: TObject); overload; procedure EndUpdate(Sender: TObject); overload; function IsUpdating: Boolean; @@ -229,6 +257,8 @@ type property IsUndoing: Boolean read GetIsUndoing write SetIsUndoing; property IsRedoing: Boolean read GetIsRedoing write SetIsRedoing; public + property TextChangeStamp: int64 read GetTextChangeStamp; + property ViewChangeStamp: int64 read GetViewChangeStamp; // tabs-size, trailing-spaces, ... property ExpandedStrings[Index: integer]: string read GetExpandedString; property LengthOfLongestLine: integer read GetLengthOfLongestLine; property IsUtf8: Boolean read GetIsUtf8 write SetIsUtf8; @@ -242,6 +272,8 @@ type function GetIsUtf8 : Boolean; override; procedure SetIsUtf8(const AValue : Boolean); override; + function GetTextChangeStamp: int64; override; + function GetViewChangeStamp: int64; override; function GetRange(Index: Pointer): TSynManagedStorageMem; override; procedure PutRange(Index: Pointer; const ARange: TSynManagedStorageMem); override; @@ -430,6 +462,130 @@ begin raise ESynEditStorageMem.CreateFmt(SListIndexOutOfBounds, [Index]); end; +{ TSynLogicalPhysicalConvertor } + +procedure TSynLogicalPhysicalConvertor.PrepareWidthsForLine(AIndex: Integer; + AForce: Boolean); +var + LineLen: Integer; + Line: PChar; +begin + if (not AForce) and (FCurrentLine = AIndex) and + (FLines.TextChangeStamp = FTextChangeStamp) and (FLines.ViewChangeStamp = FViewChangeStamp) + then begin + //debugln(['**************** RE-USING widths: ', AIndex,' (',dbgs(Pointer(self)),')']); + exit; + end; + + if (AIndex < 0) or (AIndex >= FLines.Count) then begin + FCurrentWidthsLen := 0; + FViewChangeStamp := FLines.ViewChangeStamp; + FTextChangeStamp := FLines.TextChangeStamp; + exit; + end; + + Line := FLines.GetPChar(AIndex, LineLen); + if LineLen > FCurrentWidthsAlloc then begin + //debugln(['**************** COMPUTING widths (grow): ', AIndex,' (',dbgs(Pointer(self)),') old-alloc=', FCurrentWidthsAlloc, ' new-len=',LineLen]); + SetLength(FCurrentWidths, LineLen * SizeOf(TPhysicalCharWidth)); + FCurrentWidthsAlloc := LineLen; + end + else if FCurrentWidthsAlloc > Max(Max(LineLen, FCurrentWidthsLen)*4, SYN_LP_MIN_ALLOC) then begin + //debugln(['**************** COMPUTING widths (shrink): ', AIndex,' (',dbgs(Pointer(self)),') old-alloc=', FCurrentWidthsAlloc, ' new-len=',LineLen]); + FCurrentWidthsAlloc := Max(Max(LineLen, FCurrentWidthsLen), SYN_LP_MIN_ALLOC) ; + SetLength(FCurrentWidths, FCurrentWidthsAlloc * SizeOf(TPhysicalCharWidth)); + //end + //else begin + // debugln(['**************** COMPUTING widths: ', AIndex,' (',dbgs(Pointer(self)),') alloc=',FCurrentWidthsAlloc]); + end; + + FCurrentWidthsLen := LineLen; + if LineLen > 0 then + FLines.DoGetPhysicalCharWidths(Line, LineLen, AIndex, @FCurrentWidths[0]); + FViewChangeStamp := FLines.ViewChangeStamp; + FTextChangeStamp := FLines.TextChangeStamp; + FCurrentLine := AIndex; +end; + +constructor TSynLogicalPhysicalConvertor.Create(ALines: TSynEditStrings); +begin + FLines := ALines; + FCurrentLine := -1; + FCurrentWidths := nil; + FCurrentWidthsLen := 0; + FCurrentWidthsAlloc := 0; +end; + +destructor TSynLogicalPhysicalConvertor.Destroy; +begin + SetLength(FCurrentWidths, 0); + inherited Destroy; +end; + +function TSynLogicalPhysicalConvertor.PhysicalToLogical(AIndex, + AColumn: Integer): Integer; +var + ColOffs: Integer; +begin + Result := PhysicalToLogical(AIndex, AColumn, ColOffs); +end; + +function TSynLogicalPhysicalConvertor.PhysicalToLogical(AIndex, AColumn: Integer; + out AColOffset: Integer): Integer; +var + BytePos, ScreenPos, ScreenPosOld: integer; +begin + PrepareWidthsForLine(AIndex); + + ScreenPos := 1; + BytePos := 0; + while BytePos < FCurrentWidthsLen do begin + ScreenPosOld := ScreenPos; + ScreenPos := ScreenPos + FCurrentWidths[BytePos]; + inc(BytePos); + if ScreenPos > AColumn then begin + AColOffset := AColumn - ScreenPosOld; + exit(BytePos); + end; + end; + + // Column at/past end of line + AColOffset := 0; + Result := BytePos + 1 + AColumn - ScreenPos; +end; + +function TSynLogicalPhysicalConvertor.LogicalToPhysical(AIndex, + ABytePos: Integer): Integer; +var + ColOffs: Integer; +begin + ColOffs := 0; + Result := LogicalToPhysical(AIndex, ABytePos, ColOffs); +end; + +function TSynLogicalPhysicalConvertor.LogicalToPhysical(AIndex, ABytePos: Integer; + var AColOffset: Integer): Integer; +var + i: integer; + CharWidths: TPhysicalCharWidths; +begin + PrepareWidthsForLine(AIndex); + + dec(ABytePos); + if ABytePos >= FCurrentWidthsLen then begin + Result := 1 + ABytePos - FCurrentWidthsLen; + ABytePos := FCurrentWidthsLen; + AColOffset := 0; + end + else begin + AColOffset := Min(AColOffset, FCurrentWidths[ABytePos]-1); + Result := 1 + AColOffset; + end; + + for i := 0 to ABytePos - 1 do + Result := Result + FCurrentWidths[i]; +end; + { TSynEditStringsBase } function TSynEditStringsBase.GetPChar(ALineIndex: Integer): PChar; @@ -443,10 +599,17 @@ end; constructor TSynEditStrings.Create; begin + FLogPhysConvertor := TSynLogicalPhysicalConvertor.Create(self); inherited Create; IsUtf8 := True; end; +destructor TSynEditStrings.Destroy; +begin + FreeAndNil(FLogPhysConvertor); + inherited Destroy; +end; + procedure TSynEditStrings.BeginUpdate(Sender: TObject); begin if FSenderUpdateCount = 0 then @@ -579,6 +742,11 @@ begin end; end; +function TSynEditStrings.GetViewChangeStamp: int64; +begin + Result := 0; +end; + procedure TSynEditStrings.SetUpdateState(Updating: Boolean); begin // Update/check "FSenderUpdateCount", to avoid extra locking/unlocking @@ -591,8 +759,9 @@ end; function TSynEditStrings.LogicalToPhysicalPos(const p : TPoint) : TPoint; begin Result := p; - if Result.Y - 1 < Count then - Result.X:=LogicalToPhysicalCol(self[Result.Y - 1], Result.Y, Result.X); + Result.X := FLogPhysConvertor.LogicalToPhysical(p.y - 1, p.x); +// if Result.Y - 1 < Count then +// Result.X:=LogicalToPhysicalCol(self[Result.Y - 1], Result.Y, Result.X); end; function TSynEditStrings.LogicalToPhysicalCol(const Line : String; @@ -619,8 +788,9 @@ end; function TSynEditStrings.PhysicalToLogicalPos(const p : TPoint) : TPoint; begin Result := p; - if (Result.Y>=1) and (Result.Y <= Count) then - Result.X:=PhysicalToLogicalCol(self[Result.Y - 1], Result.Y - 1, Result.X); + Result.X := FLogPhysConvertor.PhysicalToLogical(p.y - 1, p.x); +// if (Result.Y>=1) and (Result.Y <= Count) then +// Result.X:=PhysicalToLogicalCol(self[Result.Y - 1], Result.Y - 1, Result.X); end; function TSynEditStrings.PhysicalToLogicalCol(const Line : string; @@ -728,6 +898,16 @@ begin FSynStrings.IsUtf8 := AValue; end; +function TSynEditStringsLinked.GetTextChangeStamp: int64; +begin + Result := fSynStrings.GetTextChangeStamp; +end; + +function TSynEditStringsLinked.GetViewChangeStamp: int64; +begin + Result := fSynStrings.GetViewChangeStamp; +end; + //Ranges function TSynEditStringsLinked.GetRange(Index: Pointer): TSynManagedStorageMem; begin diff --git a/components/synedit/synedittextbuffer.pp b/components/synedit/synedittextbuffer.pp index 128d60cf7b..b8986fe2e3 100644 --- a/components/synedit/synedittextbuffer.pp +++ b/components/synedit/synedittextbuffer.pp @@ -136,6 +136,7 @@ type protected function GetExpandedString(Index: integer): string; override; function GetLengthOfLongestLine: integer; override; + function GetTextChangeStamp: int64; override; function GetRedoList: TSynEditUndoList; override; function GetUndoList: TSynEditUndoList; override; @@ -201,7 +202,6 @@ type property Flags[Index: Integer]: TSynEditStringFlags read GetFlags write SetFlags; property Modified: Boolean read FModified write SetModified; - property TextChangeStamp: int64 read FTextChangeStamp; public property UndoList: TSynEditUndoList read GetUndoList write fUndoList; property RedoList: TSynEditUndoList read GetRedoList write fRedoList; @@ -642,6 +642,11 @@ begin Result := 0; end; +function TSynEditStringList.GetTextChangeStamp: int64; +begin + Result := FTextChangeStamp; +end; + function TSynEditStringList.GetRedoList: TSynEditUndoList; begin Result := fRedoList; diff --git a/components/synedit/synedittexttabexpander.pas b/components/synedit/synedittexttabexpander.pas index b883047cdf..3df352b5ef 100644 --- a/components/synedit/synedittexttabexpander.pas +++ b/components/synedit/synedittexttabexpander.pas @@ -58,11 +58,13 @@ type FTabData: TSynEditStringTabData; FLastLineHasTab: Boolean; // Last line, parsed by GetPhysicalCharWidths FLastLinePhysLen: Integer; + FViewChangeStamp: int64; procedure TextBufferChanged(Sender: TObject); procedure LineCountChanged(Sender: TSynEditStrings; AIndex, ACount : Integer); function ExpandedString(Index: integer): string; function ExpandedStringLength(Index: integer): Integer; protected + function GetViewChangeStamp: int64; override; function GetTabWidth : integer; procedure SetTabWidth(const AValue : integer); function GetExpandedString(Index: integer): string; override; @@ -170,6 +172,10 @@ var begin if FTabWidth = AValue then exit; + {$PUSH}{$Q-}{$R-} + FViewChangeStamp := FViewChangeStamp + 1; + {$POP} + FTabWidth := AValue; FIndexOfLongestLine := -1; for i := 0 to Count - 1 do @@ -177,6 +183,14 @@ begin FTabData[i] := LINE_LEN_UNKNOWN; end; +function TSynEditStringTabExpander.GetViewChangeStamp: int64; +begin + Result := inherited GetViewChangeStamp; + {$PUSH}{$Q-}{$R-} + Result := Result + FViewChangeStamp; + {$POP} +end; + procedure TSynEditStringTabExpander.TextBufferChanged(Sender: TObject); var Data: TSynEditStringTabData; diff --git a/components/synedit/synedittexttrimmer.pas b/components/synedit/synedittexttrimmer.pas index 637bb70f07..12b2a7e7fb 100644 --- a/components/synedit/synedittexttrimmer.pas +++ b/components/synedit/synedittexttrimmer.pas @@ -52,6 +52,7 @@ type fLockList : TStringList; FLineEdited: Boolean; FTempLineStringForPChar: String; // experimental; used by GetPChar; + FViewChangeStamp: int64; procedure DoCaretChanged(Sender : TObject); procedure ListCleared(Sender: TObject); Procedure LinesChanged(Sender: TSynEditStrings; AIndex, ACount : Integer); @@ -68,7 +69,9 @@ type procedure EditMoveToTrim(LogY, Len: Integer); procedure EditMoveFromTrim(LogY, Len: Integer); procedure UpdateLineText(LogY: Integer); + procedure IncViewChangeStamp; protected + function GetViewChangeStamp: int64; override; function GetExpandedString(Index: integer): string; override; function GetLengthOfLongestLine: integer; override; function Get(Index: integer): string; override; @@ -378,6 +381,7 @@ begin then begin if (fLineIndex <> TSynEditCaret(Sender).LinePos - 1) then begin {$IFDEF SynTrimDebug}debugln(['--- Trimmer -- CarteChnaged - Clearing 1 ', ' fLineIndex=', fLineIndex, ' fSpaces=',length(fSpaces), 'newCaretYPos=',TSynEditCaret(Sender).LinePos]);{$ENDIF} + if fSpaces <> '' then IncViewChangeStamp; fLineIndex := TSynEditCaret(Sender).LinePos - 1; fSpaces := ''; end; @@ -385,7 +389,7 @@ begin end; FIsTrimming := True; - SendNotification(senrLineChange, self, fLineIndex, 1); + IncViewChangeStamp; if (fLineIndex <> TSynEditCaret(Sender).LinePos - 1) or (FTrimType = settIgnoreAll) then begin @@ -393,6 +397,7 @@ begin CurUndoList.AppendToLastChange(TSynEditUndoTrimForget.Create(FLineIndex+1, FSpaces)); i := length(FSpaces); fSpaces := ''; + SendNotification(senrLineChange, self, fLineIndex, 1); SendNotification(senrEditAction, self, FLineIndex+1, 0, 1+length(fSynStrings[FLineIndex]), -i, ''); end else begin @@ -408,6 +413,7 @@ begin FSpaces := copy(FSpaces, 1, j); i := length(s); CurUndoList.AppendToLastChange(TSynEditUndoTrimForget.Create(FLineIndex+1, s)); + SendNotification(senrLineChange, self, fLineIndex, 1); SendNotification(senrEditAction, self, FLineIndex+1, 0, 1+length(fSynStrings[FLineIndex]) + length(FSpaces), -i, ''); end; @@ -419,6 +425,7 @@ end; procedure TSynEditStringTrimmingList.ListCleared(Sender: TObject); begin {$IFDEF SynTrimDebug}debugln(['--- Trimmer -- LIST CLEARED ', ' fLineIndex=', fLineIndex, ' fSpaces=',length(fSpaces) ]);{$ENDIF} + if fSpaces <> '' then IncViewChangeStamp; fLockList.Clear; fLineIndex:= -1; fSpaces := ''; @@ -445,6 +452,7 @@ var i, j: Integer; begin if (not fEnabled) then exit; + IncViewChangeStamp; if fLockCount > 0 then begin {$IFDEF SynTrimDebug}debugln(['--- Trimmer -- Lines Changed (ins/del) locked ', ' fLineIndex=', fLineIndex, ' fSpaces=',length(fSpaces) ]);{$ENDIF} for i := fLockList.Count-1 downto 0 do begin @@ -549,6 +557,7 @@ begin if Index <> fLineIndex then exit(''); if (fLineIndex < 0) or (fLineIndex >= fSynStrings.Count) or (fLineText <> fSynStrings[fLineIndex]) then begin + if fSpaces <> '' then IncViewChangeStamp; fSpaces:=''; fLineText:=''; end; @@ -578,6 +587,7 @@ var begin if (not fEnabled) then exit; FIsTrimming := True; + IncViewChangeStamp; {$IFDEF SynTrimDebug}debugln(['--- Trimmer -- TrimAfterLock', ' fLineIndex=', fLineIndex, ' fSpaces=',length(fSpaces), ' Index=', Index, ' LockList=',fLockList.CommaText]);{$ENDIF} i := fLockList.IndexOfObject(TObject(Pointer(PtrUInt(fLineIndex)))); if i >= 0 then begin @@ -760,6 +770,7 @@ begin copy(s,1, LogX - 1) + AText + copy(s, LogX, length(s)), fSynStrings.Strings[LogY - 1]); CurUndoList.AddChange(TSynEditUndoTrimInsert.Create(LogX, LogY, Length(AText))); + IncViewChangeStamp; end; function TSynEditStringTrimmingList.EditDeleteTrim(LogX, LogY, ByteLen: @@ -777,6 +788,7 @@ begin fSynStrings.Strings[LogY - 1]); if Result <> '' then CurUndoList.AddChange(TSynEditUndoTrimDelete.Create(LogX, LogY, Result)); + IncViewChangeStamp; end; procedure TSynEditStringTrimmingList.EditMoveToTrim(LogY, Len: Integer); @@ -792,6 +804,7 @@ begin StoreSpacesForLine(LogY - 1, s, t); fSynStrings[LogY - 1] := t; CurUndoList.AddChange(TSynEditUndoTrimMoveTo.Create(LogY, Len)); + IncViewChangeStamp; end; procedure TSynEditStringTrimmingList.EditMoveFromTrim(LogY, Len: Integer); @@ -807,6 +820,7 @@ begin StoreSpacesForLine(LogY - 1, s, t); fSynStrings[LogY - 1] := t; CurUndoList.AddChange(TSynEditUndoTrimMoveFrom.Create(LogY, Len)); + IncViewChangeStamp; end; procedure TSynEditStringTrimmingList.UpdateLineText(LogY: Integer); @@ -815,6 +829,21 @@ begin fLineText := fSynStrings[LogY - 1]; end; +procedure TSynEditStringTrimmingList.IncViewChangeStamp; +begin + {$PUSH}{$Q-}{$R-} + FViewChangeStamp := FViewChangeStamp + 1; + {$POP} +end; + +function TSynEditStringTrimmingList.GetViewChangeStamp: int64; +begin + Result := inherited GetViewChangeStamp; + {$PUSH}{$Q-}{$R-} + Result := Result + FViewChangeStamp; + {$POP} +end; + procedure TSynEditStringTrimmingList.EditInsert(LogX, LogY: Integer; AText: String); var t: String; diff --git a/components/synedit/test/testbasicsynedit.pas b/components/synedit/test/testbasicsynedit.pas index fd7a835c6e..5da92e3333 100644 --- a/components/synedit/test/testbasicsynedit.pas +++ b/components/synedit/test/testbasicsynedit.pas @@ -27,6 +27,7 @@ type published procedure TestEditEmpty; procedure TestEditTabs; + procedure TestEditPhysicalLogical; end; implementation @@ -173,6 +174,81 @@ begin end; +procedure TTestBasicSynEdit.TestEditPhysicalLogical; + + procedure TestPhysLog(name: string; y, x, expX: integer); + var gotX: Integer; + begin + name := name + ' y='+inttostr(y)+' x='+inttostr(x); + gotX := SynEdit.PhysicalToLogicalPos(Point(x, y)).x; + AssertEquals(name+' PhysicalToLogicalPos', expX, gotX); + gotX := SynEdit.PhysicalToLogicalCol(SynEdit.Lines[y-1], y-1, x); + AssertEquals(name+' PhysicalToLogicalCol', expX, gotX); + end; + + procedure TestLogPhys(name: string; y, x, expX: integer); + var gotX: Integer; + begin + name := name + ' y='+inttostr(y)+' x='+inttostr(x); + gotX := SynEdit.LogicalToPhysicalPos(Point(x, y)).x; + AssertEquals(name+' LogicalToPhysicalPos', expX, gotX); + gotX := SynEdit.LogicalToPhysicalCol(SynEdit.Lines[y-1], y-1, x); + AssertEquals(name+' LogicalToPhysicalCol', expX, gotX); + end; + +begin + ReCreateEdit; + SynEdit.TabWidth := 6; + + SetLines(['abc', ' ääX', #9'mn', 'abc'#9'de', #9'Xää.']); + + TestLogPhys('simple line (abc)', 1, 1, 1); + TestLogPhys('simple line (abc)', 1, 2, 2); + TestLogPhys('simple line (abc)', 1, 4, 4); + TestLogPhys('simple line (abc)', 1, 5, 5); + TestLogPhys('simple line (abc)', 1, 6, 6); + TestLogPhys('line with 2byte-char', 2, 1, 1); + TestLogPhys('line with 2byte-char', 2, 2, 2); + TestLogPhys('line with 2byte-char', 2, 4, 3); // after ae + TestLogPhys('line with 2byte-char', 2, 6, 4); + TestLogPhys('line with 2byte-char', 2, 7, 5); + TestLogPhys('line with 2byte-char', 2, 8, 6); + TestLogPhys('line with 2byte-char', 2, 11, 9); + TestLogPhys('line with tab (start)', 3, 1, 1); + TestLogPhys('line with tab (start)', 3, 2, 7); + TestLogPhys('line with tab (middle)', 4, 3, 3); + TestLogPhys('line with tab (middle)', 4, 4, 4); // before tab + TestLogPhys('line with tab (middle)', 4, 5, 7); // after tab + TestLogPhys('line with tab (middle)', 4, 6, 8); + TestLogPhys('line with tab (middle)', 4, 9, 11); + TestLogPhys('line with tab (start) + 2bc', 5, 1, 1); + TestLogPhys('line with tab (start) + 2bc', 5, 2, 7); + TestLogPhys('line with tab (start) + 2bc', 5, 3, 8); + TestLogPhys('line with tab (start) + 2bc', 5, 5, 9); + + TestPhysLog('simple line (abc)', 1, 1, 1); + TestPhysLog('simple line (abc)', 1, 2, 2); + TestPhysLog('simple line (abc)', 1, 4, 4); + TestPhysLog('simple line (abc)', 1, 5, 5); + TestPhysLog('simple line (abc)', 1, 6, 6); + TestPhysLog('line with 2byte-char', 2, 1, 1); + TestPhysLog('line with 2byte-char', 2, 2, 2); + TestPhysLog('line with 2byte-char', 2, 3, 4); + TestPhysLog('line with 2byte-char', 2, 4, 6); + TestPhysLog('line with 2byte-char', 2, 5, 7); + TestPhysLog('line with 2byte-char', 2, 6, 8); + TestPhysLog('line with 2byte-char', 2, 7, 9); + TestPhysLog('line with tab (start)', 3, 1, 1); + TestPhysLog('line with tab (start)', 3, 2, 1); + TestPhysLog('line with tab (start)', 3, 5, 1); + TestPhysLog('line with tab (start)', 3, 6, 1); + TestPhysLog('line with tab (start)', 3, 7, 2); + TestPhysLog('line with tab (start)', 3, 8, 3); + TestPhysLog('line with tab (start)', 3, 9, 4); + TestPhysLog('line with tab (start)', 3, 11, 6); + +end; + initialization