SynEdit: Start paint-token-breaker for future bidi support

git-svn-id: trunk@38950 -
This commit is contained in:
martin 2012-10-02 14:16:07 +00:00
parent f50a958fad
commit 5f293d23ae
2 changed files with 663 additions and 216 deletions

View File

@ -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 }

View File

@ -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;