diff --git a/components/synedit/lazsyntextarea.pp b/components/synedit/lazsyntextarea.pp index d4de236942..03eab0e4c1 100644 --- a/components/synedit/lazsyntextarea.pp +++ b/components/synedit/lazsyntextarea.pp @@ -1,6 +1,7 @@ unit LazSynTextArea; {$mode objfpc}{$H+} +{x $INLINE OFF} {off $DEFINE SynUseOldDrawer} @@ -25,6 +26,7 @@ type ExpandedExtraBytes: Integer; // tab and space expansion HasDoubleWidth: Boolean; Attr: TSynSelectedColor; + NextPhysicalStart: Integer; // 1 based - Next toxen, may be BIDI end; { TLazSynPaintTokenBreaker } @@ -32,20 +34,26 @@ type TLazSynPaintTokenBreaker = class private FBackgroundColor: TColor; - FDisplayView: TLazSynDisplayView; FForegroundColor: TColor; + FSpaceExtraByteCount: Integer; + FTabExtraByteCount: Integer; + FFirstCol, FLastCol: integer; // Physical + + FDisplayView: TLazSynDisplayView; FLinesView: TSynEditStrings; FMarkupManager: TSynEditMarkupManager; + FCharWidths: TPhysicalCharWidths; - FFirstCol, FLastCol: integer; + FCharWidthsLen: Integer; FCurTxtLineIdx : Integer; FCurViewToken: TLazSynDisplayTokenInfoEx; + FCurViewinRTL: Boolean; + FCurViewRtlPhysEnd: integer; + FCurViewRtlLogEnd: integer; FCurMarkupPhysPos, FNextMarkupPhysPos: Integer; // 1, -1 FMarkupTokenAttr: TSynSelectedColor; - FSpaceExtraByteCount: Integer; - FTabExtraByteCount: Integer; public constructor Create; destructor Destroy; override; @@ -55,7 +63,7 @@ type ); procedure SetHighlighterTokensLine(ALine: TLineIdx; out ARealLine: TLineIdx); function GetNextHighlighterTokenFromView(out ATokenInfo: TLazSynDisplayTokenInfoEx; - AMaxPhysEnd: Integer + APhysEnd: Integer ): Boolean; function GetNextHighlighterTokenEx(out ATokenInfo: TLazSynDisplayTokenInfoEx): Boolean; property CharWidths: TPhysicalCharWidths read FCharWidths; @@ -244,11 +252,14 @@ procedure TLazSynPaintTokenBreaker.SetHighlighterTokensLine(ALine: TLineIdx; out begin FDisplayView.SetHighlighterTokensLine(ALine, ARealLine); FCharWidths := FLinesView.GetPhysicalCharWidths(ARealLine); + FCharWidthsLen := Length(FCharWidths); FCurViewToken.Tk.TokenLength := 0; FCurViewToken.LogicalStart := 1; FCurViewToken.PhysicalStart := 1; + FCurViewToken.NextPhysicalStart := 1; FCurViewToken.PhysicalPaintStart := FFirstCol; + FCurViewinRTL := False; FCurMarkupPhysPos := FFirstCol; FNextMarkupPhysPos := -1; @@ -288,7 +299,7 @@ begin end; function TLazSynPaintTokenBreaker.GetNextHighlighterTokenFromView(out - ATokenInfo: TLazSynDisplayTokenInfoEx; AMaxPhysEnd: Integer): Boolean; + ATokenInfo: TLazSynDisplayTokenInfoEx; APhysEnd: Integer): Boolean; procedure InitSynAttr(var ATarget: TSynSelectedColor; ASource: TSynHighlighterAttributes; AnAttrStartX: Integer); @@ -312,119 +323,363 @@ function TLazSynPaintTokenBreaker.GetNextHighlighterTokenFromView(out ATarget.EndX := -1; //PhysicalStartPos + TokenCharLen - 1; end; + function MaybeFetchToken: Boolean; inline; + begin + Result := FCurViewToken.Tk.TokenLength > 0; + if Result or (FCurViewToken.Tk.TokenLength < 0) then exit; + while FCurViewToken.Tk.TokenLength = 0 do begin // Todo: is SyncroEd-test a zero size token is returned + Result := FDisplayView.GetNextHighlighterToken(FCurViewToken.Tk); + if not Result then begin + FCurViewToken.Tk.TokenLength := -1; + exit; + end; + // Todo: concatenate with next token, if possible (only, if reaching token end) + end; + // TODO: wait with attr, if skipping none painted + InitSynAttr(FCurViewToken.Attr, FCurViewToken.Tk.TokenAttr, FCurViewToken.PhysicalStart); + end; + + function GetCharWidthData(AIdx: Integer): TPhysicalCharWidth; inline; + begin + if AIdx >= FCharWidthsLen + then Result := 1 + else Result := FCharWidths[AIdx]; + end; + + Procedure AdjustCurTokenLogStart(ANewLogStart: Integer); inline; + // ANewLogStart = 1 based + var + j: integer; + begin + j := (ANewLogStart - FCurViewToken.LogicalStart); + FCurViewToken.Tk.TokenLength := FCurViewToken.Tk.TokenLength - j; + FCurViewToken.Tk.TokenStart := FCurViewToken.Tk.TokenStart + j; + FCurViewToken.LogicalStart := ANewLogStart; + end; + + procedure SkipLtrBeforeFirstCol(var ALogicIdx: integer; ALogicEnd: Integer); inline; + var + j: Integer; + pcw: TPhysicalCharWidth; + begin + if (FCurViewToken.PhysicalStart >= FFirstCol) then + exit; + + pcw := GetCharWidthData(ALogicIdx); + if (pcw and PCWFlagRTL <> 0) then exit; + + j := (pcw and PCWMask); + while (ALogicIdx < ALogicEnd) and (FCurViewToken.PhysicalStart + j <= FFirstCol) and + (pcw and PCWFlagRTL = 0) + do begin + inc(FCurViewToken.PhysicalStart, j); + repeat + inc(ALogicIdx); + until (ALogicIdx >= ALogicEnd) or + (ALogicIdx >= FCharWidthsLen) or ((FCharWidths[ALogicIdx] and PCWMask) <> 0); + + pcw := GetCharWidthData(ALogicIdx); + j := pcw and PCWMask; + end; + + if ALogicIdx <> FCurViewToken.LogicalStart - 1 then begin + AdjustCurTokenLogStart(ALogicIdx + 1); + assert(FCurViewToken.Tk.TokenLength >= 0, 'FCurViewToken.Tk.TokenLength > 0'); + end; + if FCurViewToken.PhysicalPaintStart < FFirstCol then + FCurViewToken.PhysicalPaintStart := FFirstCol; + end; + + procedure SkipRtlOffScreen(var ALogicIdx: integer; ALogicEnd: Integer); inline; + var + j: Integer; + pcw: TPhysicalCharWidth; + begin + if (FCurViewToken.PhysicalStart <= FFirstCol) then begin +// TODO: end, if FCurViewRtlPhysEnd >= FLastCol; + if ALogicIdx + FCurViewToken.Tk.TokenLength < FCurViewRtlLogEnd then begin + FCurViewToken.LogicalStart := FCurViewToken.LogicalStart + FCurViewToken.Tk.TokenLength; + FCurViewToken.Tk.TokenLength := 0; + end + else begin + j := FCurViewRtlLogEnd - ALogicIdx; + FCurViewToken.LogicalStart := FCurViewToken.LogicalStart + j; + FCurViewToken.Tk.TokenStart := FCurViewToken.Tk.TokenStart + j; + FCurViewToken.Tk.TokenLength := FCurViewToken.Tk.TokenLength - j; + ALogicIdx := ALogicIdx + j; + FCurViewToken.PhysicalStart := FCurViewRtlPhysEnd; + FCurViewToken.PhysicalPaintStart := FCurViewRtlPhysEnd; + assert(FCurViewToken.LogicalStart - 1 = FCurViewRtlLogEnd, 'SkipRtlOffScreen: FCurViewToken.LogicalStart = FCurViewRtlLogEnd'); + end; + exit; + end; + + if (FCurViewToken.PhysicalStart <= FLastCol) then + exit; + + pcw := GetCharWidthData(ALogicIdx); + if (pcw and PCWFlagRTL = 0) then exit; + + j := (pcw and PCWMask); + while (ALogicIdx < ALogicEnd) and (FCurViewToken.PhysicalStart - j >= FLastCol) and + (pcw and PCWFlagRTL <> 0) + do begin + dec(FCurViewToken.PhysicalStart, j); + repeat + inc(ALogicIdx); + until (ALogicIdx >= ALogicEnd) or + (ALogicIdx >= FCharWidthsLen) or ((FCharWidths[ALogicIdx] and PCWMask) <> 0); + + pcw := GetCharWidthData(ALogicIdx); + j := pcw and PCWMask; + end; + + if ALogicIdx <> FCurViewToken.LogicalStart - 1 then begin + AdjustCurTokenLogStart(ALogicIdx + 1); + assert(FCurViewToken.Tk.TokenLength >= 0, 'FCurViewToken.Tk.TokenLength > 0'); + end; + if FCurViewToken.PhysicalPaintStart > FLastCol then + FCurViewToken.PhysicalPaintStart := FLastCol; + end; + + procedure ChangeToRtl(ALogicIdx, ALogicEnd: Integer); + var + RtlRunPhysWidth, j: Integer; + pcw: TPhysicalCharWidth; + begin + pcw := GetCharWidthData(ALogicIdx); + + RtlRunPhysWidth := 0; + j := (pcw and PCWMask); + while (ALogicIdx < ALogicEnd) and (pcw and PCWFlagRTL <> 0) do begin + inc(RtlRunPhysWidth, j); + repeat + inc(ALogicIdx); + until (ALogicIdx >= ALogicEnd) or + (ALogicIdx >= FCharWidthsLen) or ((FCharWidths[ALogicIdx] and PCWMask) <> 0); + + pcw := GetCharWidthData(ALogicIdx); + j := pcw and PCWMask; + end; + + FCurViewinRTL := True; + FCurViewRTLLogEnd := ALogicIdx; + FCurViewRtlPhysEnd := FCurViewToken.PhysicalStart + RtlRunPhysWidth; + FCurViewToken.PhysicalStart := FCurViewRtlPhysEnd; + FCurViewToken.PhysicalPaintStart := FCurViewRtlPhysEnd; + end; + + function MaybeChangeToRtl(ALogicIdx, ALogicEnd: Integer): boolean; inline; + begin + Result := (GetCharWidthData(ALogicIdx) and PCWFlagRTL) <> 0; + if Result then + ChangeToRtl(ALogicIdx, ALogicEnd); + end; + + procedure ChangeToLtr(ALogicIdx, ALogicEnd: Integer); + begin + FCurViewinRTL := False; + FCurViewToken.PhysicalStart := FCurViewRtlPhysEnd; + FCurViewToken.PhysicalPaintStart := FCurViewRtlPhysEnd; + end; + + function MaybeChangeToLtr(ALogicIdx, ALogicEnd: Integer): boolean; inline; + begin + Result := (GetCharWidthData(ALogicIdx) and PCWFlagRTL) = 0; + if Result then + ChangeToLtr(ALogicIdx, ALogicEnd); + end; + var - i, j, CharWidthsLen: Integer; + i, j: Integer; + pcw: TPhysicalCharWidth; c: Char; LogicIdx, LogicEnd, PhysPos: Integer; PrevLogicIdx, PrevPhysPos: Integer; + PhysTokenStop: Integer; TabExtra: Integer; HasDouble: Boolean; begin - if (AMaxPhysEnd > FLastCol) or (AMaxPhysEnd < 0) then - AMaxPhysEnd := FLastCol; - - Result := AMaxPhysEnd > FCurViewToken.PhysicalPaintStart; - if not Result then exit; - while True do begin - if FCurViewToken.Tk.TokenLength = 0 then begin - Result := FDisplayView.GetNextHighlighterToken(FCurViewToken.Tk); - if not Result then exit; - if FCurViewToken.Tk.TokenLength = 0 then continue; // // Todo: is SyncroEd-test a zero size token is returned - InitSynAttr(FCurViewToken.Attr, FCurViewToken.Tk.TokenAttr, FCurViewToken.PhysicalStart); - // Todo: concatenate with next token, if possible - end; + Result := MaybeFetchToken; // Get token from View/Highlighter + if not Result then exit; - CharWidthsLen := Length(FCharWidths); - LogicIdx := FCurViewToken.LogicalStart - 1; + LogicIdx := FCurViewToken.LogicalStart - 1; LogicEnd := LogicIdx + FCurViewToken.Tk.TokenLength; + assert(GetCharWidthData(LogicIdx)<>0, 'GetNextHighlighterTokenFromView: Token starts with char'); - // SKip out of screen - while (LogicIdx < LogicEnd) and (FCurViewToken.PhysicalStart < FFirstCol) do begin - if LogicIdx >= CharWidthsLen - then j := 1 - else j := (FCharWidths[LogicIdx] and PCWMask); + case FCurViewinRTL of + False: // Left To Right + begin + SkipLtrBeforeFirstCol(LogicIdx, LogicEnd); // Skip out of screen + if FCurViewToken.Tk.TokenLength = 0 then + continue; // Get NEXT token - if FCurViewToken.PhysicalStart + j > FFirstCol then break; + if MaybeChangeToRtl(LogicIdx, LogicEnd) then + continue; - inc(FCurViewToken.PhysicalStart, j); - repeat - inc(LogicIdx); - until (LogicIdx >= CharWidthsLen) or ((FCharWidths[LogicIdx] and PCWMask) <> 0); + if APhysEnd > 0 + then PhysTokenStop := Min(FLastCol, APhysEnd) + else PhysTokenStop := FLastCol; + // TODO: APhysEnd should always allow some data. Compare with FLastCol? Assert for APhysEnd + Result := PhysTokenStop > FCurViewToken.PhysicalPaintStart; + if not Result then exit; + + // Find end according to PhysTokenStop + PhysPos := FCurViewToken.PhysicalStart; + PrevLogicIdx := LogicIdx; + PrevPhysPos := PhysPos; + HasDouble := False; + TabExtra := 0; // Extra bytes needed for expanded Tab/Space(utf8 visible space/dot) + i := 0; + + pcw := GetCharWidthData(LogicIdx); + while (LogicIdx < LogicEnd) and (PhysPos < PhysTokenStop) and + (pcw and PCWFlagRTL = 0) + do begin + j := pcw and PCWMask; + + PrevLogicIdx := LogicIdx; + PrevPhysPos := PhysPos; + inc(PhysPos, j); + if j <> 0 then begin + c := (FCurViewToken.Tk.TokenStart + i)^; + if c = #9 then + inc(TabExtra, j-1 + FTabExtraByteCount) + else + if j > 1 then + HasDouble := True; + if c = ' ' then + inc(TabExtra, FSpaceExtraByteCount); + end; + + repeat + inc(LogicIdx); + inc(i); + until (LogicIdx >= FCharWidthsLen) or + (LogicIdx >= LogicEnd) or ((FCharWidths[LogicIdx] and PCWMask) <> 0); + pcw := GetCharWidthData(LogicIdx); + end; + Assert(PhysPos > FCurViewToken.PhysicalStart, 'PhysPos > FCurViewToken.PhysicalStart'); + + ATokenInfo := FCurViewToken; + ATokenInfo.Tk.TokenLength := LogicIdx + 1 - ATokenInfo.LogicalStart; + ATokenInfo.LogicalEnd := LogicIdx + 1; + ATokenInfo.PhysicalEnd := PhysPos; + ATokenInfo.PhysicalPaintEnd := Min(PhysPos, PhysTokenStop); + ATokenInfo.ExpandedExtraBytes := TabExtra; + ATokenInfo.HasDoubleWidth := HasDouble; + + if PhysPos > PhysTokenStop then begin // Last char goes over paint boundary + LogicIdx := PrevLogicIdx; + PhysPos := PrevPhysPos; + end + else + PhysTokenStop := PhysPos; + AdjustCurTokenLogStart(LogicIdx + 1); + FCurViewToken.PhysicalStart := PhysPos; + FCurViewToken.PhysicalPaintStart := PhysTokenStop; + + assert(FCurViewToken.Tk.TokenLength >= 0, 'FCurViewToken.Tk.TokenLength >= 0'); + if FCurViewToken.Tk.TokenLength = 0 then + ATokenInfo.Attr.EndX := PhysPos-1; + + //MaybeFetchToken; + MaybeChangeToRtl(LogicIdx, LogicEnd); // get NextTokenPhysStart + + ATokenInfo.NextPhysicalStart := FCurViewToken.PhysicalStart; + + break; + end; // case FCurViewinRTL = False; + True: // Right To Left + begin + SkipRtlOffScreen(LogicIdx, LogicEnd); + if FCurViewToken.Tk.TokenLength = 0 then + continue; // Get NEXT token + + if MaybeChangeToLtr(LogicIdx, LogicEnd) then + continue; + + if APhysEnd >= FCurViewRtlPhysEnd + then PhysTokenStop := FFirstCol + else PhysTokenStop := Max(FFirstCol, APhysEnd); + // TODO: APhysEnd should always allow some data. Assert for APhysEnd + // FFirstCol must be less PPS. Otherwise it would have gone LTR +// Result := PhysTokenStop < FCurViewToken.PhysicalPaintStart; +// if not Result then exit; + + // Find end according to PhysTokenStop + PhysPos := FCurViewToken.PhysicalStart; + PrevLogicIdx := LogicIdx; + PrevPhysPos := PhysPos; + HasDouble := False; + TabExtra := 0; // Extra bytes needed for expanded Tab/Space(utf8 visible space/dot) + i := 0; + + pcw := GetCharWidthData(LogicIdx); + while (LogicIdx < LogicEnd) and (PhysPos > PhysTokenStop) and + (pcw and PCWFlagRTL <> 0) + do begin + j := pcw and PCWMask; + + PrevLogicIdx := LogicIdx; + PrevPhysPos := PhysPos; + dec(PhysPos, j); + if j <> 0 then begin + c := (FCurViewToken.Tk.TokenStart + i)^; + if c = #9 then + inc(TabExtra, j-1 + FTabExtraByteCount) + else + if j > 1 then + HasDouble := True; + if c = ' ' then + inc(TabExtra, FSpaceExtraByteCount); + end; + + repeat + inc(LogicIdx); + inc(i); + until (LogicIdx >= FCharWidthsLen) or + (LogicIdx >= LogicEnd) or ((FCharWidths[LogicIdx] and PCWMask) <> 0); + pcw := GetCharWidthData(LogicIdx); + end; + Assert(PhysPos < FCurViewToken.PhysicalStart, 'PhysPos > FCurViewToken.PhysicalStart'); + + ATokenInfo := FCurViewToken; + ATokenInfo.Tk.TokenLength := LogicIdx + 1 - ATokenInfo.LogicalStart; + ATokenInfo.LogicalEnd := LogicIdx + 1; + ATokenInfo.PhysicalEnd := ATokenInfo.PhysicalStart; + ATokenInfo.PhysicalPaintEnd := ATokenInfo.PhysicalPaintStart; + ATokenInfo.PhysicalStart := PhysPos; + ATokenInfo.PhysicalPaintStart := Max(PhysPos, PhysTokenStop); + ATokenInfo.ExpandedExtraBytes := TabExtra; + ATokenInfo.HasDoubleWidth := HasDouble; + + if PhysPos < PhysTokenStop then begin // Last char goes over paint boundary + LogicIdx := PrevLogicIdx; + PhysPos := PrevPhysPos; + end; + //else + // AMaxPhysEnd := PhysPos; + AdjustCurTokenLogStart(LogicIdx + 1); + FCurViewToken.PhysicalStart := PhysPos; + FCurViewToken.PhysicalPaintStart := Max(PhysPos, PhysTokenStop); + + assert(FCurViewToken.Tk.TokenLength >= 0, 'FCurViewToken.Tk.TokenLength >= 0'); + if FCurViewToken.Tk.TokenLength = 0 then + ATokenInfo.Attr.EndX := PhysPos-1; + + //MaybeFetchToken; + MaybeChangeToLtr(LogicIdx, LogicEnd); // get NextTokenPhysStart + + ATokenInfo.NextPhysicalStart := FCurViewToken.PhysicalStart; + + break; + end; // case FCurViewinRTL = True; end; - if LogicIdx <> FCurViewToken.LogicalStart - 1 then begin - j := (LogicIdx + 1 - FCurViewToken.LogicalStart); - FCurViewToken.Tk.TokenLength := FCurViewToken.Tk.TokenLength - j; - FCurViewToken.Tk.TokenStart := FCurViewToken.Tk.TokenStart + j; - FCurViewToken.LogicalStart := LogicIdx + 1; - if FCurViewToken.Tk.TokenLength = 0 then - continue; - assert(FCurViewToken.Tk.TokenLength > 0, 'FCurViewToken.Tk.TokenLength > 0'); - end; - - // Find end according to AMaxPhysEnd - LogicEnd := LogicIdx + FCurViewToken.Tk.TokenLength; - PhysPos := FCurViewToken.PhysicalStart; - PrevLogicIdx := LogicIdx; - PrevPhysPos := PhysPos; - HasDouble := False; - TabExtra := 0; // Extra bytes needed for expanded Tab/Space(utf8 visible space/dot) - i := 0; - - while (LogicIdx < LogicEnd) and (PhysPos < AMaxPhysEnd) do begin - if LogicIdx >= CharWidthsLen - then j := 1 - else j := (FCharWidths[LogicIdx] and PCWMask); - - PrevLogicIdx := LogicIdx; - PrevPhysPos := PhysPos; - inc(PhysPos, j); - if j <> 0 then begin - c := (FCurViewToken.Tk.TokenStart + i)^; - if c = #9 then - inc(TabExtra, j-1 + FTabExtraByteCount) - else - if j > 1 then - HasDouble := True; - if c = ' ' then - inc(TabExtra, FSpaceExtraByteCount); - end; - - repeat - inc(LogicIdx); - inc(i); - until (LogicIdx >= CharWidthsLen) or (LogicIdx >= LogicEnd) or ((FCharWidths[LogicIdx] and PCWMask) <> 0); - end; - Assert(PhysPos > FCurViewToken.PhysicalStart, 'PhysPos > FCurViewToken.PhysicalStart'); - - ATokenInfo := FCurViewToken; - ATokenInfo.Tk.TokenLength := LogicIdx + 1 - ATokenInfo.LogicalStart; - ATokenInfo.LogicalEnd := LogicIdx + 1; - ATokenInfo.PhysicalEnd := PhysPos; - ATokenInfo.PhysicalPaintEnd := Min(PhysPos, AMaxPhysEnd); - ATokenInfo.ExpandedExtraBytes := TabExtra; - ATokenInfo.HasDoubleWidth := HasDouble; - - if PhysPos > AMaxPhysEnd then begin // Last char goes over paint boundary - LogicIdx := PrevLogicIdx; - PhysPos := PrevPhysPos; - end - else - AMaxPhysEnd := PhysPos; - j := LogicIdx + 1 - FCurViewToken.LogicalStart; - FCurViewToken.Tk.TokenLength := FCurViewToken.Tk.TokenLength - j; - FCurViewToken.Tk.TokenStart := FCurViewToken.Tk.TokenStart + j; - FCurViewToken.LogicalStart := LogicIdx + 1; - FCurViewToken.PhysicalStart := PhysPos; - FCurViewToken.PhysicalPaintStart := AMaxPhysEnd; - assert(FCurViewToken.Tk.TokenLength >= 0, 'FCurViewToken.Tk.TokenLength >= 0'); - - if FCurViewToken.Tk.TokenLength = 0 then - ATokenInfo.Attr.EndX := PhysPos-1; - break; - end; + end; // while True end; { TLazSynSurfaceManager } diff --git a/components/synedit/test/testsyntextarea.pas b/components/synedit/test/testsyntextarea.pas index 9b5bde4bad..4175377174 100644 --- a/components/synedit/test/testsyntextarea.pas +++ b/components/synedit/test/testsyntextarea.pas @@ -1,12 +1,13 @@ unit TestSynTextArea; {$mode objfpc}{$H+} +{$INLINE OFF} interface uses - Classes, SysUtils, fpcunit, testregistry, TestBase, SynHighlighterPas, - LazSynTextArea, SynEditTypes; + Classes, SysUtils, fpcunit, testregistry, TestBase, LazSynTextArea, + SynEditTypes, SynEditMarkupBracket, SynEdit, SynHighlighterPosition, Graphics; type @@ -14,12 +15,13 @@ type TTestSynTextArea = class(TTestBase) private - FTheHighLighter: TSynPasSyn; + FTheHighLighter: TSynPositionHighlighter; + FtkRed, FtkGreen, FtkBlue, FtkYellow: TtkTokenKind; protected FTokenBreaker: TLazSynPaintTokenBreaker; procedure ReCreateEdit; reintroduce; - function CreateTheHighLighter: TSynPasSyn; + function CreateTheHighLighter: TSynPositionHighlighter; procedure SetUp; override; procedure TearDown; override; @@ -43,9 +45,13 @@ begin SynEdit.Highlighter := FTheHighLighter; end; -function TTestSynTextArea.CreateTheHighLighter: TSynPasSyn; +function TTestSynTextArea.CreateTheHighLighter: TSynPositionHighlighter; begin - Result := TSynPasSyn.Create(nil); + Result := TSynPositionHighlighter.Create(nil); + FtkRed := Result.CreateTokenID('red', clRed, clDefault, []); + FtkGreen := Result.CreateTokenID('green', clGreen, clDefault, []); + FtkBlue := Result.CreateTokenID('blue', clBlue, clDefault, []); + FtkYellow := Result.CreateTokenID('yellow', clYellow, clDefault, []); end; procedure TTestSynTextArea.SetUp; @@ -63,15 +69,20 @@ end; procedure TTestSynTextArea.SetRealLinesText; begin ReCreateEdit; - SetLines(['unit foo;', + SetLines(['unit foo;', // 1 'interface//', 'const', ' test =''abcDEF'';', - ' testa=''a あアア F'';', + ' testa=''a あアア F'';', // 5 ' testb=''aääDEF''; // föö bar', #9'i=123;', ' a'#9'=0;', - #9#9#9#9'end' + #9#9#9#9'end', + '', // 10 + 'شس', + 'شس ي', + 'ABشس يCD', + '' ]); end; @@ -80,6 +91,7 @@ var BaseName, Name: String; TkCnt: Integer; Token: TLazSynDisplayTokenInfoEx; + UseViewTokenOnly: Boolean; procedure TestToken(LStart, LEnd, PStart, PEnd, DStart, DEnd: Integer; AText: String); begin @@ -97,7 +109,7 @@ var var RLine: TLineIdx; begin - BaseName := Format('%s (Line=%d, F/L=%d-%d): ', [AName, ALine, AFirst, ALast]); + BaseName := Format('%s::%s (Line=%d, F/L=%d-%d): ', [BaseTestName, AName, ALine, AFirst, ALast]); Name := BaseName; TkCnt := 0; FTokenBreaker.Prepare(SynEdit.ViewedTextBuffer.DisplayView, @@ -115,7 +127,9 @@ var begin inc(TkCnt); Name := Format('%sL=%d (%d): ', [BaseName, APhysLimit, TkCnt]); - R := FTokenBreaker.GetNextHighlighterTokenFromView(Token, APhysLimit); + if UseViewTokenOnly + then R := FTokenBreaker.GetNextHighlighterTokenFromView(Token, APhysLimit) + else R := FTokenBreaker.GetNextHighlighterTokenEx(Token); AssertTrue(Name + 'Got Token', R); end; @@ -131,142 +145,320 @@ var begin inc(TkCnt); Name := Format('%sL=%d (%d): ', [BaseName, APhysLimit, TkCnt]); - R := FTokenBreaker.GetNextHighlighterTokenFromView(Token, APhysLimit); + if UseViewTokenOnly + then R := FTokenBreaker.GetNextHighlighterTokenFromView(Token, APhysLimit) + else R := FTokenBreaker.GetNextHighlighterTokenEx(Token); AssertFalse(Name + ' No further Token', R); end; begin + UseViewTokenOnly := True; SetRealLinesText; SynEdit.TabWidth := 4; - SynEdit.ViewedTextBuffer.DisplayView.InitHighlighterTokens(FTheHighLighter); + SynEdit.ViewedTextBuffer.DisplayView.InitHighlighterTokens(SynEdit.Highlighter); - {%region full line} - TestStart('Scan full line', 2, 1, 100, 2); + FTheHighLighter.AddToken(2-1, 9, FtkBlue); // interface + FTheHighLighter.AddToken(7-1, 1, FtkYellow); // #9 + FTheHighLighter.AddToken(7-1, 2, FtkGreen); // i + FTheHighLighter.AddToken(7-1, 3, FtkRed); // = + FTheHighLighter.AddToken(7-1, 6, FtkGreen); // 123 - TestNext(100, 1, 10, 1, 10, 1, 10, 'interface'); - TestNext(100, 10, 12, 10, 12, 10, 12, '//'); - TestEnd(100); + {%region LTR only } + PushBaseName('LTR-Only'); + {%region full line} + TestStart('Scan full line', 2, 1, 100, 2); + TestNext(100, 1, 10, 1, 10, 1, 10, 'interface'); + TestNext(100, 10, 12, 10, 12, 10, 12, '//'); + TestEnd(100); + + TestStart('Scan full line', 2, 1, 100, 2); + TestNext(-1, 1, 10, 1, 10, 1, 10, 'interface'); + TestNext(-1, 10, 12, 10, 12, 10, 12, '//'); + TestEnd(-1); + {%endregion} + + {%region cut off end of line} + TestStart('Cut off end', 2, 1, 5, 2); + + TestNext(100, 1, 5, 1, 5, 1, 5, 'inte'); + TestEnd(100); + {%endregion} + + {%region cut off start of line} + TestStart('Cut off start', 2, 3, 100, 2); + + TestNext(100, 3, 10, 3, 10, 3, 10, 'terface'); + TestNext(100, 10, 12, 10, 12, 10, 12, '//'); + TestEnd(100); + {%endregion} + + {%region cut off start of line} + TestStart('Cut off start 1 tok', 2, 10, 100, 2); + + TestNext(100, 10, 12, 10, 12, 10, 12, '//'); + TestEnd(100); + {%endregion} + + {%region cut off start of line} + TestStart('Cut off start 1.5 tok', 2, 11, 100, 2); + + TestNext(100, 11, 12, 11, 12, 11, 12, '/'); + TestEnd(100); + {%endregion} + + {%region cut off both} + TestStart('Cut off both', 2, 3, 10, 2); + + TestNext(100, 3, 10, 3, 10, 3, 10, 'terface'); + //TestNext(100, 10, 12, 10, 12, 10, 12, '//'); + TestEnd(100); + {%endregion} + + {%region cut off both - 2 token} + TestStart('Cut off both - 2 token', 2, 3, 11, 2); + + TestNext(100, 3, 10, 3, 10, 3, 10, 'terface'); + TestNext(100, 10, 11, 10, 11, 10, 11, '/'); + TestEnd(100); + {%endregion} + + {%region cut off both - 1 token, skip first} + TestStart('Cut off both - 1 token, skip 1st', 2, 10, 11, 2); + + TestNext(100, 10, 11, 10, 11, 10, 11, '/'); + TestEnd(100); + {%endregion} + + {%region 1 token 2 parts} + TestStart('1 token 2 parts', 2, 1, 100, 2); + + TestNext( 3, 1, 3, 1, 3, 1, 3, 'in'); + TestNext(100, 3, 10, 3, 10, 3, 10, 'terface'); + TestNext(100, 10, 12, 10, 12, 10, 12, '//'); + TestEnd(100); + {%endregion} + + // part chars/tabs + + {%region cut off PART of char} + TestStart('cut off PART of char (begin)', 7, 2, 100, 7); + + TestNext( 5, 1, 2, 1, 5, 2, 5, #9); + TestNext(100, 2, 3, 5, 6, 5, 6, 'i'); + {%endregion} + + {%region cut off PART of char} + TestStart('cut off PART of char (end)', 7, 1, 100, 7); + + TestNext( 3, 1, 2, 1, 5, 1, 3, #9); + TestNext(100, 1, 2, 1, 5, 3, 5, #9); + TestNext(100, 2, 3, 5, 6, 5, 6, 'i'); + {%endregion} + + {%region cut off PART of char} + TestStart('cut off PART of char (end) next-limit', 7, 1, 100, 7); + + TestNext( 3, 1, 2, 1, 5, 1, 3, #9); + TestNext( 5, 1, 2, 1, 5, 3, 5, #9); + TestNext(100, 2, 3, 5, 6, 5, 6, 'i'); + {%endregion} + + {%region cut off PART of char} + TestStart('cut off PART of char (both) continue', 7, 2, 100, 7); + + TestNext( 3, 1, 2, 1, 5, 2, 3, #9); + TestNext(100, 1, 2, 1, 5, 3, 5, #9); + TestNext(100, 2, 3, 5, 6, 5, 6, 'i'); + {%endregion} + + {%region cut off PART of char} + TestStart('cut off PART of char (both) global-limit', 7, 2, 3, 7); + + TestNext(100, 1, 2, 1, 5, 2, 3, #9); + TestEnd(100); + {%endregion} + + {%region cut off PART of char} + TestStart('cut off PART of char (both) next-limit', 7, 2, 100, 7); + + TestNext(3, 1, 2, 1, 5, 2, 3, #9); + TestEnd(3); + {%endregion} + + {%region cut tab in many} + TestStart('cut tab in many', 9, 2, 100, 9); + + TestNext( 3, 1, 2, 1, 5, 2, 3, #9); + TestNext( 6, 1, 3, 1, 9, 3, 6, #9#9); + TestNext( 11, 2, 4, 5, 13, 6, 11, #9#9); + TestNext( 13, 3, 4, 9, 13, 11, 13, #9); + TestNext( 15, 4, 5, 13, 17, 13, 15, #9); + {%endregion} + PopBaseName; {%endregion} - {%region cut off end of line} - TestStart('Cut off end', 2, 1, 5, 2); + SynEdit.ViewedTextBuffer.DisplayView.FinishHighlighterTokens; + SynEdit.Highlighter := nil; + SynEdit.ViewedTextBuffer.DisplayView.InitHighlighterTokens(SynEdit.Highlighter); + {%region RTL only } + PushBaseName('RTL-Only'); + {%region full line} + TestStart('Scan full line', 11, 1, 100, 11); + TestNext(100, 1, 5, 1, 3, 1, 3, 'شس'); + TestEnd(100); - TestNext(100, 1, 5, 1, 5, 1, 5, 'inte'); - TestEnd(100); + TestStart('Scan full line', 11, 1, 100, 11); + TestNext(-1, 1, 5, 1, 3, 1, 3, 'شس'); + TestEnd(-1); + + TestStart('Scan full line (2 words)', 12, 1, 100, 12); + TestNext(100, 1, 8, 1, 5, 1, 5, 'شس ي'); + TestEnd(100); + {%endregion} + + {%region part line} + // 1 char parts + TestStart('part line - begin', 12, 1, 2, 12); + TestNext(100, 6, 8, 1, 2, 1, 2, 'ي'); + TestEnd(100); + + TestStart('part line - mid', 12, 2, 3, 12); + TestNext(100, 5, 6, 2, 3, 2, 3, ' '); + TestEnd(100); + + TestStart('part line - mid', 12, 3, 4, 12); + TestNext(100, 3, 5, 3, 4, 3, 4, 'س'); + TestEnd(100); + + TestStart('part line - end', 12, 4, 5, 12); + TestNext(100, 1, 3, 4, 5, 4, 5, 'ش'); + TestEnd(100); + + // 2 char parts + TestStart('part line - begin(2)', 12, 1, 3, 12); + TestNext(100, 5, 8, 1, 3, 1, 3, ' ي'); + TestEnd(100); + + TestStart('part line - mid(2)', 12, 2, 4, 12); + TestNext(100, 3, 6, 2, 4, 2, 4, 'س '); + TestEnd(100); + + TestStart('part line - end(2)', 12, 3, 5, 12); + TestNext(100, 1, 5, 3, 5, 3, 5, 'شس'); + TestEnd(100); + + // 1 char parts, several chunks + TestStart('part line - begin', 12, 1, 100, 12); + TestNext(4, 1, 3, 4, 5, 4, 5, 'ش'); + TestNext(3, 3, 5, 3, 4, 3, 4, 'س'); + TestNext(2, 5, 6, 2, 3, 2, 3, ' '); + TestNext(1, 6, 8, 1, 2, 1, 2, 'ي'); + TestEnd(100); + + // 1 char parts, several chunks + TestStart('part line - begin', 12, 1, 100, 12); + TestNext(4, 1, 3, 4, 5, 4, 5, 'ش'); + TestNext(3, 3, 5, 3, 4, 3, 4, 'س'); + TestNext(0, 5, 8, 1, 3, 1, 3, ' ي'); + TestEnd(100); + + //TestStart('part line - begin', 12, 1, 100, 12); + //TestNext(2, 1, 6, 2, 5, 2, 5, 'شس '); + //TestNext(5, 1, 3, 4, 5, 4, 5, 'ي'); + //TestEnd(100); + // + //TestStart('part line - begin', 12, 1, 100, 12); + //TestNext( 2, 1, 6, 2, 5, 2, 5, 'شس '); + //TestNext(100, 1, 3, 4, 5, 4, 5, 'شس ي'); + //TestEnd(100); + + {%endregion} + + PopBaseName; {%endregion} - {%region cut off start of line} - TestStart('Cut off start', 2, 3, 100, 2); + {%region MIXED Rtl/Ltr } + PushBaseName('MIXED Rtl/Ltr'); + {%region full line} + TestStart('Scan full line', 13, 1, 100, 13); + TestNext(-1, 1, 3, 1, 3, 1, 3, 'AB'); + TestNext(-1, 3, 10, 3, 7, 3, 7, 'شس ي'); + TestNext(-1, 10, 12, 7, 9, 7, 9, 'CD'); + TestEnd(-1); - TestNext(100, 3, 10, 3, 10, 3, 10, 'terface'); - TestNext(100, 10, 12, 10, 12, 10, 12, '//'); - TestEnd(100); - {%endregion} + {%endregion} - {%region cut off start of line} - TestStart('Cut off start 1 tok', 2, 10, 100, 2); + {%region parts} + TestStart('Scan part line, cut at start', 13, 2, 100, 13); + TestNext(-1, 2, 3, 2, 3, 2, 3, 'B'); + TestNext(-1, 3, 10, 3, 7, 3, 7, 'شس ي'); + TestNext(-1, 10, 12, 7, 9, 7, 9, 'CD'); + TestEnd(-1); - TestNext(100, 10, 12, 10, 12, 10, 12, '//'); - TestEnd(100); - {%endregion} + TestStart('Scan part line, cut at start', 13, 3, 100, 13); + TestNext(-1, 3, 10, 3, 7, 3, 7, 'شس ي'); + TestNext(-1, 10, 12, 7, 9, 7, 9, 'CD'); + TestEnd(-1); - {%region cut off start of line} - TestStart('Cut off start 1.5 tok', 2, 11, 100, 2); + TestStart('Scan part line, cut at start', 13, 4, 100, 13); + TestNext(-1, 3, 8, 4, 7, 4, 7, 'شس '); + TestNext(-1, 10, 12, 7, 9, 7, 9, 'CD'); + TestEnd(-1); - TestNext(100, 11, 12, 11, 12, 11, 12, '/'); - TestEnd(100); - {%endregion} + TestStart('Scan part line, cut at start', 13, 6, 100, 13); + TestNext(-1, 3, 5, 6, 7, 6, 7, 'ش'); + TestNext(-1, 10, 12, 7, 9, 7, 9, 'CD'); + TestEnd(-1); - {%region cut off both} - TestStart('Cut off both', 2, 3, 10, 2); + TestStart('Scan part line, cut at start', 13, 7, 100, 13); + TestNext(-1, 10, 12, 7, 9, 7, 9, 'CD'); + TestEnd(-1); - TestNext(100, 3, 10, 3, 10, 3, 10, 'terface'); - //TestNext(100, 10, 12, 10, 12, 10, 12, '//'); - TestEnd(100); - {%endregion} + TestStart('Scan part line, cut at start', 13, 8, 100, 13); + TestNext(-1, 11, 12, 8, 9, 8, 9, 'D'); + TestEnd(-1); - {%region cut off both - 2 token} - TestStart('Cut off both - 2 token', 2, 3, 11, 2); + TestStart('Scan part line, cut at start', 13, 9, 100, 13); + TestEnd(-1); - TestNext(100, 3, 10, 3, 10, 3, 10, 'terface'); - TestNext(100, 10, 11, 10, 11, 10, 11, '/'); - TestEnd(100); - {%endregion} - {%region cut off both - 1 token, skip first} - TestStart('Cut off both - 1 token, skip 1st', 2, 10, 11, 2); + TestStart('Scan part line, cut at end', 13, 1, 8, 13); + TestNext(-1, 1, 3, 1, 3, 1, 3, 'AB'); + TestNext(-1, 3, 10, 3, 7, 3, 7, 'شس ي'); + TestNext(-1, 10, 11, 7, 8, 7, 8, 'C'); + TestEnd(-1); - TestNext(100, 10, 11, 10, 11, 10, 11, '/'); - TestEnd(100); - {%endregion} + TestStart('Scan part line, cut at end', 13, 1, 7, 13); + TestNext(-1, 1, 3, 1, 3, 1, 3, 'AB'); + TestNext(-1, 3, 10, 3, 7, 3, 7, 'شس ي'); + TestEnd(-1); - {%region 1 token 2 parts} - TestStart('1 token 2 parts', 2, 1, 100, 2); + TestStart('Scan part line, cut at end', 13, 1, 6, 13); + TestNext(-1, 1, 3, 1, 3, 1, 3, 'AB'); + TestNext(-1, 5, 10, 3, 6, 3, 6, 'س ي'); + TestEnd(-1); - TestNext( 3, 1, 3, 1, 3, 1, 3, 'in'); - TestNext(100, 3, 10, 3, 10, 3, 10, 'terface'); - TestNext(100, 10, 12, 10, 12, 10, 12, '//'); - TestEnd(100); - {%endregion} + TestStart('Scan part line, cut at end', 13, 1, 4, 13); + TestNext(-1, 1, 3, 1, 3, 1, 3, 'AB'); + TestNext(-1, 8, 10, 3, 4, 3, 4, 'ي'); + TestEnd(-1); - // part chars/tabs + TestStart('Scan part line, cut at end', 13, 1, 3, 13); + TestNext(-1, 1, 3, 1, 3, 1, 3, 'AB'); + TestEnd(-1); - {%region cut off PART of char} - TestStart('cut off PART of char (begin)', 7, 2, 100, 7); + TestStart('Scan part line, cut at end', 13, 1, 2, 13); + TestNext(-1, 1, 2, 1, 2, 1, 2, 'A'); + TestEnd(-1); - TestNext( 5, 1, 2, 1, 5, 2, 5, #9); - TestNext(100, 2, 3, 5, 6, 5, 6, 'i'); - {%endregion} - {%region cut off PART of char} - TestStart('cut off PART of char (end)', 7, 1, 100, 7); + {%endregion} - TestNext( 3, 1, 2, 1, 5, 1, 3, #9); - TestNext(100, 1, 2, 1, 5, 3, 5, #9); - TestNext(100, 2, 3, 5, 6, 5, 6, 'i'); - {%endregion} - - {%region cut off PART of char} - TestStart('cut off PART of char (end) next-limit', 7, 1, 100, 7); - - TestNext( 3, 1, 2, 1, 5, 1, 3, #9); - TestNext( 5, 1, 2, 1, 5, 3, 5, #9); - TestNext(100, 2, 3, 5, 6, 5, 6, 'i'); - {%endregion} - - {%region cut off PART of char} - TestStart('cut off PART of char (both) continue', 7, 2, 100, 7); - - TestNext( 3, 1, 2, 1, 5, 2, 3, #9); - TestNext(100, 1, 2, 1, 5, 3, 5, #9); - TestNext(100, 2, 3, 5, 6, 5, 6, 'i'); - {%endregion} - - {%region cut off PART of char} - TestStart('cut off PART of char (both) global-limit', 7, 2, 3, 7); - - TestNext(100, 1, 2, 1, 5, 2, 3, #9); - TestEnd(100); - {%endregion} - - {%region cut off PART of char} - TestStart('cut off PART of char (both) next-limit', 7, 2, 100, 7); - - TestNext(3, 1, 2, 1, 5, 2, 3, #9); - TestEnd(3); - {%endregion} - - {%region cut tab in many} - TestStart('cut tab in many', 9, 2, 100, 9); - - TestNext( 3, 1, 2, 1, 5, 2, 3, #9); - TestNext( 6, 1, 3, 1, 9, 3, 6, #9#9); - TestNext( 11, 2, 4, 5, 13, 6, 11, #9#9); - TestNext( 13, 3, 4, 9, 13, 11, 13, #9); - TestNext( 15, 4, 5, 13, 17, 13, 15, #9); + PopBaseName; {%endregion} + SynEdit.ViewedTextBuffer.DisplayView.FinishHighlighterTokens; end;