SynEdit: partly implement handling of space followed by combining mark. Issue #41228

This commit is contained in:
Martin 2024-11-24 18:01:09 +01:00
parent 79be6943b5
commit ed45ec5224
7 changed files with 90 additions and 55 deletions

View File

@ -35,6 +35,7 @@ type
ExpandedExtraBytes: Integer; // tab and space expansion
HasTabs: Boolean; // ExtraWidth may still be 0
HasDoubleWidth: Boolean;
NeedsEto: Boolean;
NextPos: TLazSynDisplayTokenBound; // Next toxen, may be BIDI
NextRtlInfo: TLazSynDisplayRtlInfo;
@ -463,6 +464,7 @@ begin
ATokenInfo.ExpandedExtraBytes := 0;
ATokenInfo.HasTabs := False;
ATokenInfo.HasDoubleWidth := False; // TODO: True, but needs charwidth for painter
ATokenInfo.NeedsEto := False;
end
else begin
if ATokenInfo.NextRtlInfo.IsRtl <> FCurMarkupNextRtlInfo.IsRtl then begin
@ -740,7 +742,7 @@ var
PrevLogicIdx, PrevPhysPos: Integer;
PhysTokenStop: Integer;
TabExtra: Integer;
HasTabs, HasDouble: Boolean;
HasTabs, HasDouble, NeedsEto: Boolean;
begin
ATokenInfo.Attr := nil;
while True do begin
@ -762,6 +764,7 @@ begin
LogicEnd := LogicIdx + FCurViewToken.TokenLength;
//assert(GetCharWidthData(LogicIdx)<>0, 'GetNextHighlighterTokenFromView: Token starts with char');
NeedsEto := False;
case FCurViewinRTL of
False: // Left To Right
begin
@ -812,8 +815,13 @@ begin
else
if j > 1 then
HasDouble := True;
if c = ' ' then
if c = ' ' then begin
inc(TabExtra, FSpaceExtraByteCount);
{$IfDef WINDOWS}
if not NeedsEto then
NeedsEto := IsCombiningCodePoint(FCurViewToken.TokenStart + i + 1);
{$ENDIF}
end;
end;
repeat
@ -851,6 +859,7 @@ begin
ATokenInfo.ExpandedExtraBytes := TabExtra;
ATokenInfo.HasTabs := HasTabs;
ATokenInfo.HasDoubleWidth := HasDouble;
ATokenInfo.NeedsEto := NeedsEto;
assert(ATokenInfo.StartPos.Offset >= 0, 'FCurViewScannerPos.Offset >= 0');
assert(ATokenInfo.EndPos.Offset <= 0, 'FCurViewToken.EndPos.Offset <= 0');
@ -937,8 +946,13 @@ begin
else
if j > 1 then
HasDouble := True;
if c = ' ' then
if c = ' ' then begin
inc(TabExtra, FSpaceExtraByteCount);
{$IfDef WINDOWS}
if not NeedsEto then
NeedsEto := IsCombiningCodePoint(FCurViewToken.TokenStart + i + 1);
{$ENDIF}
end;
end;
repeat
@ -979,6 +993,7 @@ begin
ATokenInfo.ExpandedExtraBytes := TabExtra;
ATokenInfo.HasTabs := HasTabs;
ATokenInfo.HasDoubleWidth := HasDouble;
ATokenInfo.NeedsEto := NeedsEto;
assert(ATokenInfo.StartPos.Offset >= 0, 'FCurViewScannerPos.Offset >= 0');
assert(ATokenInfo.EndPos.Offset <= 0, 'FCurViewToken.EndPos.Offset <= 0');
@ -1634,7 +1649,7 @@ var
end;
NeedExpansion := (ATokenInfo.ExpandedExtraBytes > 0) or (ATokenInfo.HasTabs);
NeedTransform := FTextDrawer.NeedsEto or ATokenInfo.HasDoubleWidth or NeedExpansion
NeedTransform := FTextDrawer.NeedsEto or ATokenInfo.HasDoubleWidth or ATokenInfo.NeedsEto or NeedExpansion
{$IFDEF Windows} or ATokenInfo.RtlInfo.IsRtl {$ENDIF}
;
Len := ATokenInfo.Tk.TokenLength;
@ -1663,7 +1678,7 @@ var
end;
// Prepare FETOBuf
if FTextDrawer.NeedsEto or ATokenInfo.HasDoubleWidth
if FTextDrawer.NeedsEto or ATokenInfo.HasDoubleWidth or ATokenInfo.NeedsEto
{$IFDEF Windows} or ATokenInfo.RtlInfo.IsRtl {$ENDIF} // RTL may have script with ligature
then begin
FEtoBuf := FTextDrawer.Eto;
@ -1705,7 +1720,9 @@ var
if FetoBuf <> nil then FEtoBuf.EtoData[e] := c;
inc(e);
end;
if (vscTabAtLast in FVisibleSpecialChars) and ((pl-1)^=' ') and (j < CWLen) then begin
if (vscTabAtLast in FVisibleSpecialChars) and ((pl-1)^=' ') and (j < CWLen) and
(not IsCombiningCodePoint(pt+1))
then begin
(pl-1)^ := #194;
pl^ := #187; inc(pl);
if FetoBuf <> nil then FEtoBuf.EtoData[e] := c;
@ -1713,7 +1730,9 @@ var
end;
end;
' ': begin
if (vscSpace in FVisibleSpecialChars) and (j < CWLen) then begin
if (vscSpace in FVisibleSpecialChars) and (j < CWLen) and
(not IsCombiningCodePoint(pt+1))
then begin
pl^ := #194; inc(pl);
pl^ := #183; inc(pl);
end

View File

@ -43,7 +43,7 @@ uses
Classes, SysUtils,
LazStringUtils,
SynEditMiscClasses, LazSynEditText, SynEditPointClasses,
SynEditKeyCmds, SynEditTypes;
SynEditKeyCmds, SynEditTypes, SynEditMiscProcs;
type
@ -672,11 +672,7 @@ var
begin
p := PChar(Line);
if Assigned(p) then begin
Result := 0;
while p^ in [#1..#32] do begin
Inc(p);
Inc(Result);
end;
Result := CountLeadWhiteSpace(p);
if Physical and (Result>0) then
Result := FCurrentLines.LogicalToPhysicalCol(Line, -1, Result+1)-1; // TODO, Need the real index of the line
end else

View File

@ -5805,9 +5805,7 @@ begin
ALine:=FTheLinesView[FBlockSelection.StartLinePos - 1];
x2:=length(ALine)+1;
if not WithLeadSpaces then begin
x := FBlockSelection.StartBytePos;
while (x<length(ALine)) and (ALine[x] in [' ',#9]) do
inc(x);
x := CountLeadWhiteSpace(PChar(ALine)) + 1;
FBlockSelection.StartLineBytePos := Point(x,MinMax(Value.y, 1, FTheLinesView.Count));
while (x2 > x) and (ALine[X2-1] in [' ',#9]) do
dec(x2);
@ -7476,7 +7474,9 @@ begin
Temp := Clipboard.AsText;
Helper := SelText;
if (Temp <> '') and (not (Temp[Length(Temp)] in [#10,#13, #9, #32])) and
(not (Helper[1] in [#10,#13, #9, #32]))
not( (Helper[1] in [#10,#13, #9, #32]) and
( (Length(Helper) = 1) or (not IsCombiningCodePoint(PChar(Helper)+1)))
)
then
Temp := Temp + ' ';
Clipboard.AsText := Temp + Helper;
@ -9010,6 +9010,8 @@ begin
// scan over whitespaces
while (p^ in [#9, #32]) do
inc(p);
if IsCombiningCodePoint(p) then
dec(p);
i := LogicalToPhysicalCol(PrevLine, iLine, p-@PrevLine[1]+1) - CaretX;
end;
end;
@ -9321,7 +9323,7 @@ begin
j := 0;
for i := 1 to FBlockTabIndent do begin
i2 := fTabWidth;
while (i2 > 0) and (Line[j] = #32) do begin
while (i2 > 0) and IsSpaceChar(Line+j) do begin
dec(i2);
inc(j);
end;
@ -9537,9 +9539,7 @@ begin
s := FTheLinesView[CaretXY.Y - 1];
// search first non blank char pos
FirstNonBlank := 1;
while (FirstNonBlank <= length(s)) and (s[FirstNonBlank] in [#32, #9]) do
inc(FirstNonBlank);
FirstNonBlank := CountLeadWhiteSpace(PChar(s)) + 1;
if FirstNonBlank > length(s) then
FirstNonBlank := -1;
end else

View File

@ -543,15 +543,11 @@ function TSynEditMarkupFoldColors.GetFirstCharacterColumn(pIndex: Integer
): TColumnCacheEntry;
var
l: String;
s, p: Integer;
p: Integer;
begin
l := SynEdit.Lines[pIndex];
s := length(l);
p := 1;
while (p <= s)
//and (l[p] in [#9, #32, '/']) do inc(p);
and (l[p] in [#9, #32]) do inc(p);
if p > s then
p := CountLeadWhiteSpace(PChar(l)) + 1;
if p > length(l) then
exit(high(Result));
Result := SynEdit.LogicalToPhysicalPos(Point(p, toPos(pIndex))).x;
end;

View File

@ -27,7 +27,7 @@ interface
uses
Classes, SysUtils, Graphics, Controls,
SynEditMarkup, SynEditTypes, SynEditMiscClasses;
SynEditMarkup, SynEditTypes, SynEditMiscClasses, SynEditMiscProcs;
type
@ -87,9 +87,10 @@ end;
function TSynEditMarkupSpecialChar.IsSpecial(pos: Integer): Boolean;
begin
if (pos < 1) or (pos > Length(FCurLine)) then exit(False);
Result := ( (vscSpace in FVisibleSpecialChars) and (FCurLine[pos] in [' ']) ) or
( (FVisibleSpecialChars*[vscTabAtFirst, vscTabAtLast] <> []) and (FCurLine[pos] in [#9]) )
;
Result := ( ( (vscSpace in FVisibleSpecialChars) and (FCurLine[pos] in [' ']) ) or
( (FVisibleSpecialChars*[vscTabAtFirst, vscTabAtLast] <> []) and (FCurLine[pos] in [#9]) )
) and
(not IsCombiningCodePoint(PChar(FCurLine)+pos));
end;
constructor TSynEditMarkupSpecialChar.Create(ASynEdit : TSynEditBase);

View File

@ -82,6 +82,9 @@ function ToPos(AIdx: Integer): Integer; inline;
function YToIdx(APointWithYPos: TPoint): TPoint; inline;
function YToPos(APointWithYIdx: TPoint): TPoint; inline;
function IsCombiningCodePoint(const AChar: PChar): Boolean;
function IsSpaceChar(AText: PChar): Boolean; inline;
function CountLeadSpace(AText: PChar): integer; inline;
function CountLeadWhiteSpace(AText: PChar): integer; inline;
function CountBackwardWhiteSpace(AText: PChar; AStart: Integer): integer; inline;
function CountLeadWhiteSpace(AText: PChar; out AnHasTab: boolean): integer; inline;
@ -111,6 +114,38 @@ begin
inc(Result.Y);
end;
function IsCombiningCodePoint(const AChar: PChar): Boolean;
begin
Result := (
( (AChar[0] = #$CC) ) or // Combining Diacritical Marks (belongs to previos char) 0300-036F
( (AChar[0] = #$CD) and (AChar[1] in [#$80..#$AF]) ) or // Combining Diacritical Marks
( (AChar[0] = #$D8) and (AChar[1] in [#$90..#$9A]) ) or // Arabic 0610 (d890)..061A (d89a)
( (AChar[0] = #$D9) and (AChar[1] in [#$8b..#$9f, #$B0]) ) or // Arabic 064B (d98b)..065F (d99f) // 0670 (d9b0)
( (AChar[0] = #$DB) and (AChar[1] in [#$96..#$9C, #$9F..#$A4, #$A7..#$A8, #$AA..#$AD]) ) or // Arabic 06D6 (db96).. .. ..06EA (dbaa)
( (AChar[0] = #$E0) and (AChar[1] = #$A3) and (AChar[2] in [#$A4..#$BE]) ) or // Arabic 08E4 (e0a3a4) ..08FE (e0a3be)
( (AChar[0] = #$E1) and (AChar[1] = #$B7) ) or // Combining Diacritical Marks Supplement 1DC0-1DFF
( (AChar[0] = #$E2) and (AChar[1] = #$83) and (AChar[2] in [#$90..#$FF]) ) or // Combining Diacritical Marks for Symbols 20D0-20FF
( (AChar[0] = #$EF) and (AChar[1] = #$B8) and (AChar[2] in [#$A0..#$AF]) ) // Combining half Marks FE20-FE2F
);
end;
function IsSpaceChar(AText: PChar): Boolean;
begin
Result := (AText^ = ' ') and not IsCombiningCodePoint(AText);
end;
function CountLeadSpace(AText: PChar): integer;
var
Run : PChar;
begin
Run := AText;
while (Run^ in [' ']) do
Inc(Run);
Result := Run - AText;
if (Result > 0) and IsCombiningCodePoint(Run) then
dec(Result);
end;
function CountLeadWhiteSpace(AText: PChar): integer;
var
Run : PChar;
@ -119,6 +154,8 @@ begin
while (Run^ in [' ', #9]) do
Inc(Run);
Result := Run - AText;
if (Result > 0) and IsCombiningCodePoint(Run) then
dec(Result);
end;
function CountBackwardWhiteSpace(AText: PChar; AStart: Integer): integer;
@ -142,6 +179,8 @@ begin
while (Run^ in [' ', #9]) do
Inc(Run);
Result := Run - AText;
if (Result > 0) and IsCombiningCodePoint(Run) then
dec(Result);
end;
{* fontstyle utilities *}

View File

@ -206,7 +206,6 @@ type
procedure UndoEditLinesDelete(LogY, ACount: Integer);
procedure IncreaseTextChangeStamp;
procedure DoGetPhysicalCharWidths(Line: PChar; LineLen, Index: Integer; PWidths: PPhysicalCharWidth); override;
function LogicPosIsCombining(const AChar: PChar): Boolean; inline;
function GetDisplayView: TLazSynDisplayView; override;
@ -979,7 +978,7 @@ begin
#$80..#$BF:
PWidths^ := 0;
else
if LogicPosIsCombining(Line) then
if IsCombiningCodePoint(Line) then
PWidths^ := 0
else
PWidths^ := 1;
@ -1012,21 +1011,6 @@ begin
end;
function TSynEditStringList.LogicPosIsCombining(const AChar: PChar): Boolean;
begin
Result := (
( (AChar[0] = #$CC) ) or // Combining Diacritical Marks (belongs to previos char) 0300-036F
( (AChar[0] = #$CD) and (AChar[1] in [#$80..#$AF]) ) or // Combining Diacritical Marks
( (AChar[0] = #$D8) and (AChar[1] in [#$90..#$9A]) ) or // Arabic 0610 (d890)..061A (d89a)
( (AChar[0] = #$D9) and (AChar[1] in [#$8b..#$9f, #$B0]) ) or // Arabic 064B (d98b)..065F (d99f) // 0670 (d9b0)
( (AChar[0] = #$DB) and (AChar[1] in [#$96..#$9C, #$9F..#$A4, #$A7..#$A8, #$AA..#$AD]) ) or // Arabic 06D6 (db96).. .. ..06EA (dbaa)
( (AChar[0] = #$E0) and (AChar[1] = #$A3) and (AChar[2] in [#$A4..#$BE]) ) or // Arabic 08E4 (e0a3a4) ..08FE (e0a3be)
( (AChar[0] = #$E1) and (AChar[1] = #$B7) ) or // Combining Diacritical Marks Supplement 1DC0-1DFF
( (AChar[0] = #$E2) and (AChar[1] = #$83) and (AChar[2] in [#$90..#$FF]) ) or // Combining Diacritical Marks for Symbols 20D0-20FF
( (AChar[0] = #$EF) and (AChar[1] = #$B8) and (AChar[2] in [#$A0..#$AF]) ) // Combining half Marks FE20-FE2F
);
end;
function TSynEditStringList.GetDisplayView: TLazSynDisplayView;
begin
Result := FDisplayView;
@ -1240,7 +1224,7 @@ begin
while (Result < l) and (ACount > 0) do begin
inc(Result);
if (ALine[Result] in [#0..#127, #192..#255]) and
( (lpStopAtCodePoint in AFlags) or (not LogicPosIsCombining(@ALine[Result])) )
( (lpStopAtCodePoint in AFlags) or (not IsCombiningCodePoint(@ALine[Result])) )
then
dec(ACount);
end;
@ -1250,7 +1234,7 @@ begin
if (Result <= l) then
while (Result > 1) and
( (not(ALine[Result] in [#0..#127, #192..#255])) or
( (not(lpStopAtCodePoint in AFlags)) and LogicPosIsCombining(@ALine[Result]) )
( (not(lpStopAtCodePoint in AFlags)) and IsCombiningCodePoint(@ALine[Result]) )
)
do
dec(Result);
@ -1259,7 +1243,7 @@ begin
dec(Result);
if (Result > l) or (Result = 1) or
( (ALine[Result] in [#0..#127, #192..#255]) and
( (lpStopAtCodePoint in AFlags) or (not LogicPosIsCombining(@ALine[Result])) )
( (lpStopAtCodePoint in AFlags) or (not IsCombiningCodePoint(@ALine[Result])) )
)
then
inc(ACount);
@ -1278,7 +1262,7 @@ begin
if Result then
Result := (ALogicalPos = 1) or
(lpStopAtCodePoint in AFlags) or
(not LogicPosIsCombining(@ALine[ALogicalPos]));
(not IsCombiningCodePoint(@ALine[ALogicalPos]));
end;
function TSynEditStringList.LogicPosAdjustToChar(const ALine: String; ALogicalPos: integer;
@ -1292,7 +1276,7 @@ begin
while (Result <= length(ALine)) and
( (not(ALine[Result] in [#0..#127, #192..#255])) or
((Result <> 1) and
(not(lpStopAtCodePoint in AFlags)) and LogicPosIsCombining(@ALine[Result])
(not(lpStopAtCodePoint in AFlags)) and IsCombiningCodePoint(@ALine[Result])
)
)
do
@ -1305,7 +1289,7 @@ begin
while (Result > 1) and
( (not(ALine[Result] in [#0..#127, #192..#255])) or
( (not(lpStopAtCodePoint in AFlags)) and LogicPosIsCombining(@ALine[Result]) )
( (not(lpStopAtCodePoint in AFlags)) and IsCombiningCodePoint(@ALine[Result]) )
)
do
dec(Result);