SynEdit: fix SyncroEdit for chars with different upper/lower length. Issue #41446

(cherry picked from commit 2ef9a0607c)
This commit is contained in:
Martin 2025-03-05 23:38:11 +01:00
parent 0c48c46503
commit 1b39a66408
4 changed files with 186 additions and 74 deletions

View File

@ -11,9 +11,6 @@
<UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/> <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/>
</SearchPaths> </SearchPaths>
<Other> <Other>
<ConfigFile>
<WriteConfigFilePath Value=""/>
</ConfigFile>
<CustomOptions Value="$(IDEBuildOptions)"/> <CustomOptions Value="$(IDEBuildOptions)"/>
</Other> </Other>
</CompilerOptions> </CompilerOptions>
@ -39,6 +36,10 @@ Additional licenses may be granted in each individual file. See the headers in e
<Filename Value="xregexpr_unicodedata.pas"/> <Filename Value="xregexpr_unicodedata.pas"/>
<UnitName Value="xregexpr_unicodedata"/> <UnitName Value="xregexpr_unicodedata"/>
</Item> </Item>
<Item>
<Filename Value="lazeditmiscprocs.pas"/>
<UnitName Value="lazeditmiscprocs"/>
</Item>
</Files> </Files>
<RequiredPkgs> <RequiredPkgs>
<Item> <Item>

View File

@ -8,7 +8,7 @@ unit LazEdit;
interface interface
uses uses
TextMateGrammar, xHyperLinksDecorator, xregexpr, xregexpr_unicodedata; TextMateGrammar, xHyperLinksDecorator, xregexpr, xregexpr_unicodedata, LazEditMiscProcs;
implementation implementation

View File

@ -0,0 +1,90 @@
{
*****************************************************************************
This file is part of the LazEdit package from the Lazarus IDE.
This content of this file is licensensed: Modified LGPL-2
Or at the users choice: Modified LGPL-3
See the file COPYING.modifiedLGPL.txt, included in the Lazarus distribution,
for details about the license.
Alternatively, the contents of this file may be used under the terms of the
Mozilla Public License Version 1.1 http://www.mozilla.org/MPL/
A copy used under either License can have the other Licenses removed from this
header. A note should be added that the original file is available with the
above choice of License.
*****************************************************************************
Written by Martin Friebe
}
unit LazEditMiscProcs;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils;
function IsCombiningCodePoint(const AChar: PChar): Boolean;
function CountChars(AText: PChar; AByteLen: integer): integer;
function CountBytes(AText: PChar; ACharLen: Integer; AMaxBytes: integer = high(Integer)): integer;
implementation
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 CountChars(AText: PChar; AByteLen: integer): integer;
var
b: Byte;
begin
Result := 0;
while AByteLen > 0 do begin
b := Byte(AText^);
if (b < 128) or
((b >= 192) and not IsCombiningCodePoint(AText))
then
inc(Result);
inc(AText);
dec(AByteLen);
end;
end;
function CountBytes(AText: PChar; ACharLen: Integer; AMaxBytes: integer): integer;
var
b: Byte;
begin
Result := 0;
while AMaxBytes > 0 do begin
b := Byte(AText^);
if b = 0 then
exit;
if (b < 128) or
((b >= 192) and not IsCombiningCodePoint(AText))
then begin
if ACharLen = 0 then
exit;
dec(ACharLen);
end;
inc(AText);
inc(Result);
dec(AMaxBytes);
end;
end;
end.

View File

@ -28,7 +28,7 @@ interface
uses uses
Classes, SysUtils, Graphics, StrUtils, Classes, SysUtils, Graphics, StrUtils,
SynEditMiscClasses, SynEdit, SynEditMarkup, SynEditMiscProcs, LazSynEditText, SynEditMiscClasses, SynEdit, SynEditMarkup, SynEditMiscProcs, LazSynEditText,
SynEditTextTrimmer, SynEditKeyCmds, SynEditTextBase, LazUTF8; SynEditTextTrimmer, SynEditKeyCmds, SynEditTextBase, LazUTF8, LazEditMiscProcs;
type type
@ -171,7 +171,7 @@ type
TSynPluginSyncronizedEditChangeAction = record TSynPluginSyncronizedEditChangeAction = record
CellIndex: Integer; CellIndex: Integer;
cLinePos, cBytePos, Count, LineBrkCount: Integer; cLinePos, cCharPos, Count, LineBrkCount: Integer;
Text: String; Text: String;
end; end;
@ -184,7 +184,7 @@ type
function GetItems(Index: Integer): TSynPluginSyncronizedEditChangeAction; function GetItems(Index: Integer): TSynPluginSyncronizedEditChangeAction;
public public
procedure Clear; procedure Clear;
procedure Add(aCellIndex, aLinePos, aBytePos, aCount, aLineBrkCnt: Integer; procedure Add(aCellIndex, aLinePos, aCharPos, aCount, aLineBrkCnt: Integer;
aText: String); aText: String);
property Count: Integer read FCount; property Count: Integer read FCount;
property Items[Index: Integer]: TSynPluginSyncronizedEditChangeAction property Items[Index: Integer]: TSynPluginSyncronizedEditChangeAction
@ -836,15 +836,15 @@ begin
FCount := 0; FCount := 0;
end; end;
procedure TSynPluginSyncronizedEditChangeList.Add(aCellIndex, aLinePos, aBytePos, procedure TSynPluginSyncronizedEditChangeList.Add(aCellIndex, aLinePos, aCharPos, aCount,
aCount, aLineBrkCnt: Integer; aText: String); aLineBrkCnt: Integer; aText: String);
begin begin
if length(FList) <= FCount then if length(FList) <= FCount then
SetLength(FList, FCount + 4); SetLength(FList, FCount + 4);
FList[FCount].CellIndex := aCellIndex; FList[FCount].CellIndex := aCellIndex;
FList[FCount].cLinePos := aLinePos; FList[FCount].cLinePos := aLinePos;
FList[FCount].cBytePos := aBytePos; FList[FCount].cCharPos := aCharPos;
FList[FCount].Count := aCount; FList[FCount].Count := aCount;
FList[FCount].LineBrkCount := aLineBrkCnt; FList[FCount].LineBrkCount := aLineBrkCnt;
FList[FCount].Text := aText; FList[FCount].Text := aText;
@ -1180,6 +1180,7 @@ var
edit: Boolean; edit: Boolean;
CellAtPos, CellCnt: Integer; CellAtPos, CellCnt: Integer;
LastCellEndPoint, CurCellStartPos: TPoint; LastCellEndPoint, CurCellStartPos: TPoint;
Line: PChar;
begin begin
if not Active then begin if not Active then begin
if PreActive then DoPreActiveEdit(aBytePos, aLinePos, aCount, aLineBrkCnt, IsUndoing or IsRedoing); if PreActive then DoPreActiveEdit(aBytePos, aLinePos, aCount, aLineBrkCnt, IsUndoing or IsRedoing);
@ -1249,11 +1250,16 @@ begin
(CompareCarets(Pos, FCells[CellAtPos].LogEnd) >= 0) (CompareCarets(Pos, FCells[CellAtPos].LogEnd) >= 0)
then begin then begin
CurCell := FCells[CellAtPos]; CurCell := FCells[CellAtPos];
Line := ViewedTextBuffer.GetPChar(Pos.Y-1, i);
Pos.Y := Pos.Y - CurCell.LogStart.y; Pos.Y := Pos.Y - CurCell.LogStart.y;
if Pos.y = 0 then if Pos.y = 0 then begin
Pos.X := Pos.X - CurCell.LogStart.x Pos.X := Pos.X - CurCell.LogStart.x;
else Pos.X := CountChars(Line+CurCell.LogStart.x-1, Pos.X);
end
else begin
dec(Pos.x); dec(Pos.x);
Pos.X := CountChars(Line, Pos.X);
end;
FChangeList.Add(CellAtPos, Pos.Y, Pos.X, aCount, aLineBrkCnt, aText); FChangeList.Add(CellAtPos, Pos.Y, Pos.X, aCount, aLineBrkCnt, aText);
end; end;
@ -1267,8 +1273,10 @@ procedure TSynPluginSyncronizedEditBase.ApplyChangeList;
var var
Action: TSynPluginSyncronizedEditChangeAction; Action: TSynPluginSyncronizedEditChangeAction;
a, i: Integer; a, i: Integer;
Group, Y2, X2, CurCell, LastUndoCurCell: Integer; Group, Y2, X2, CurCell, LastUndoCurCell, Len: Integer;
Cell: TSynPluginSyncronizedEditCell; Cell: TSynPluginSyncronizedEditCell;
Line: PChar;
XStart: Integer;
begin begin
LastUndoCurCell := -1; LastUndoCurCell := -1;
if FDependsOnCurrentCell then begin if FDependsOnCurrentCell then begin
@ -1296,37 +1304,34 @@ begin
continue; continue;
FCurrentCell := i; // direct access / markup does not need to know FCurrentCell := i; // direct access / markup does not need to know
if Cell.LogStart.Y = Cell.LogEnd.Y then Y2 := Cell.LogStart.Y + Action.cLinePos;
X2 := Cell.LogStart.X + Action.cBytePos if (Y2 <= Cell.LogEnd.Y) then begin
else Line := ViewedTextBuffer.GetPChar(Y2-1, Len);
X2 := 1 + Action.cBytePos; XStart := Cell.LogStart.X;
if (Cell.LogStart.Y + Action.cLinePos < Cell.LogEnd.Y) or
( (Cell.LogStart.Y + Action.cLinePos = Cell.LogEnd.Y) and
(X2 <= Cell.LogEnd.X) )
then begin
Y2 := Cell.LogStart.Y + Action.cLinePos;
if Action.cLinePos = 0 then if Action.cLinePos = 0 then
X2 := Cell.LogStart.X + Action.cBytePos X2 := XStart + CountBytes(Line + XStart - 1, Action.cCharPos, Len - XStart + 1)
else else
X2 := 1 + Action.cBytePos; X2 := 1 + CountBytes(Line, Action.cCharPos, Len);
if Action.LineBrkCount = -1 then if (Y2 < Cell.LogEnd.Y) or (X2 <= Cell.LogEnd.X) then begin
ViewedTextBuffer.EditLineJoin(Y2) if Action.LineBrkCount = -1 then
else ViewedTextBuffer.EditLineJoin(Y2)
if Action.LineBrkCount < -1 then else
ViewedTextBuffer.EditLinesDelete(Y2, -Action.LineBrkCount) if Action.LineBrkCount < -1 then
else ViewedTextBuffer.EditLinesDelete(Y2, -Action.LineBrkCount)
if Action.LineBrkCount = 1 then else
ViewedTextBuffer.EditLineBreak(X2, Y2) if Action.LineBrkCount = 1 then
else ViewedTextBuffer.EditLineBreak(X2, Y2)
if Action.LineBrkCount > 1 then else
ViewedTextBuffer.EditLinesInsert(Y2, Action.LineBrkCount) if Action.LineBrkCount > 1 then
else ViewedTextBuffer.EditLinesInsert(Y2, Action.LineBrkCount)
if Action.Count < 0 then else
ViewedTextBuffer.EditDelete(X2, Y2, -Action.Count) if Action.Count < 0 then
else ViewedTextBuffer.EditDelete(X2, Y2, -Action.Count)
if Action.Count > 0 then else
ViewedTextBuffer.EditInsert(X2, Y2, Action.Text); if Action.Count > 0 then
ViewedTextBuffer.EditInsert(X2, Y2, Action.Text);
end;
end; end;
end; end;
@ -1887,22 +1892,36 @@ begin
end; end;
procedure TSynPluginCustomSyncroEdit.AddGroupFromSelection(AScanMode: TSynPluginSyncroScanMode); procedure TSynPluginCustomSyncroEdit.AddGroupFromSelection(AScanMode: TSynPluginSyncroScanMode);
function FindNextStart(AText: String; AStartPos, AMaxPos: TPoint): TPoint; type
TPointEx = record Pnt: TPoint; Len: integer; end;
function FindNextStart(AText: String; AStartPos, AMaxPos: TPoint): TPointEx;
var var
l: String; l2, l: String;
x2: LongInt;
begin begin
Result.Y := -1; Result.Pnt.Y := -1;
repeat repeat
l := TextBuffer[ToIdx(AStartPos.Y)]; l := TextBuffer[ToIdx(AStartPos.Y)];
if AScanMode in [spssNoCase, spssCtxNoCase] then l2 := l;
if AScanMode in [spssNoCase, spssCtxNoCase] then begin
l := Utf8LowerCase(l); l := Utf8LowerCase(l);
Result.X := PosEx(AText, l, AStartPos.X); if AStartPos.X > 1 then
if (Result.X > 0) and AStartPos.X := CountBytes(PChar(l), CountChars(PChar(l2), AStartPos.X - 1), Length(l)) + 1;
end;
Result.Pnt.X := PosEx(AText, l, AStartPos.X);
if (Result.Pnt.X > 0) and
( (AStartPos.Y < AMaxPos.Y) or ( (AStartPos.Y < AMaxPos.Y) or
((AStartPos.y = AMaxPos.y) and (Result.x + Length(AText) <= AMaxPos.X)) ((AStartPos.y = AMaxPos.y) and (Result.Pnt.x + Length(AText) <= AMaxPos.X))
) )
then begin then begin
Result.Y := AStartPos.Y; Result.Pnt.Y := AStartPos.Y;
Result.Len := Length(AText);
if AScanMode in [spssNoCase, spssCtxNoCase] then begin
x2 := Result.Pnt.X;
Result.Pnt.X := CountBytes(PChar(l2), CountChars(PChar(l), Result.Pnt.X - 1), Length(l2)) + 1;
Result.Len := CountBytes(PChar(l2)+x2-1, CountChars(PChar(AText), Length(AText)), Length(l2)-x2+1);
end;
exit; exit;
end; end;
AStartPos.X := 1; AStartPos.X := 1;
@ -1910,7 +1929,7 @@ procedure TSynPluginCustomSyncroEdit.AddGroupFromSelection(AScanMode: TSynPlugin
until AStartPos.y > AMaxPos.Y; until AStartPos.y > AMaxPos.Y;
end; end;
function FindNextStart(AText: String; AStartPos, AMaxPos: TPoint; AScope: integer): TPoint; function FindNextStart(AText: String; AStartPos, AMaxPos: TPoint; AScope: integer): TPointEx;
var var
tt: Integer; tt: Integer;
begin begin
@ -1918,13 +1937,13 @@ procedure TSynPluginCustomSyncroEdit.AddGroupFromSelection(AScanMode: TSynPlugin
Result := FindNextStart(AText, AStartPos, AMaxPos); Result := FindNextStart(AText, AStartPos, AMaxPos);
if not (AScanMode in [spssCtxNoCase, spssCtxWithCase]) then if not (AScanMode in [spssCtxNoCase, spssCtxWithCase]) then
exit; exit;
if Result.Y < 0 then if Result.Pnt.Y < 0 then
exit; exit;
TSynEdit(FriendEdit).GetHighlighterAttriAtRowColEx(Result, tt, True); TSynEdit(FriendEdit).GetHighlighterAttriAtRowColEx(Result.Pnt, tt, True);
if tt = AScope then if tt = AScope then
exit; exit;
AStartPos := Result; AStartPos := Result.Pnt;
AStartPos.x := AStartPos.x + Length(AText); AStartPos.x := AStartPos.x + Result.Len;
until False; until False;
end; end;
@ -1953,10 +1972,11 @@ var
end; end;
var var
BndCell: TSynPluginSyncronizedEditCell; BndCell: TSynPluginSyncronizedEditCell;
t: String; SelTxt: String;
p: TPoint; p: TPoint;
Fnd, FndEnd, Fnd2, FndEnd2: TPoint; Fnd, Fnd2: TPointEx;
Ctx, i: Integer; FndEnd, FndEnd2: TPoint;
Ctx, i, SelTxtCharLen: Integer;
begin begin
if (not FriendEdit.SelAvail) or (FriendEdit.BlockBegin.y <> FriendEdit.BlockEnd.Y) then if (not FriendEdit.SelAvail) or (FriendEdit.BlockBegin.y <> FriendEdit.BlockEnd.Y) then
exit; exit;
@ -1974,32 +1994,33 @@ begin
if AScanMode in [spssCtxNoCase, spssCtxWithCase] then if AScanMode in [spssCtxNoCase, spssCtxWithCase] then
TSynEdit(FriendEdit).GetHighlighterAttriAtRowColEx(FriendEdit.BlockBegin, Ctx, False); TSynEdit(FriendEdit).GetHighlighterAttriAtRowColEx(FriendEdit.BlockBegin, Ctx, False);
t := FriendEdit.SelText; SelTxt := FriendEdit.SelText;
// SelTxtCharLen := CountChars(PChar(SelTxt), Length(SelTxt));
if AScanMode in [spssNoCase, spssCtxNoCase] then if AScanMode in [spssNoCase, spssCtxNoCase] then
t := UTF8LowerCase(t); SelTxt := UTF8LowerCase(SelTxt);
p := BndCell.LogStart; p := BndCell.LogStart;
Fnd := FindNextStart(t, p, BndCell.LogEnd, Ctx); Fnd := FindNextStart(SelTxt, p, BndCell.LogEnd, Ctx);
FndEnd := Fnd; FndEnd := Fnd.Pnt;
inc(FndEnd.X, Length(t)); inc(FndEnd.X, Fnd.Len);
if (Fnd.Y < 0) then if (Fnd.Pnt.Y < 0) then
exit; exit;
p := FndEnd; p := FndEnd;
Fnd2 := FindNextStart(t, p, BndCell.LogEnd, Ctx); Fnd2 := FindNextStart(SelTxt, p, BndCell.LogEnd, Ctx);
FndEnd2 := Fnd2; FndEnd2 := Fnd2.Pnt;
inc(FndEnd2.X, Length(t)); inc(FndEnd2.X, Fnd2.Len);
if (Fnd2.Y < 0) then if (Fnd2.Pnt.Y < 0) then
exit; exit;
AddFndCell(Fnd, FndEnd, True); AddFndCell(Fnd.Pnt, FndEnd, True);
while Fnd2.y >= 0 do begin while Fnd2.Pnt.y >= 0 do begin
AddFndCell(Fnd2, FndEnd2); AddFndCell(Fnd2.Pnt, FndEnd2);
p := FndEnd2; p := FndEnd2;
Fnd2 := FindNextStart(t, p, BndCell.LogEnd, Ctx); Fnd2 := FindNextStart(SelTxt, p, BndCell.LogEnd, Ctx);
FndEnd2 := Fnd2; FndEnd2 := Fnd2.Pnt;
inc(FndEnd2.X, Length(t)); inc(FndEnd2.X, Fnd2.Len);
end; end;
if DidDelete then if DidDelete then