SynEdit: added adjusting column selection in shared edit / fixed, prevent column-sel-bounds from becoming invalid (bytepos in middle of utf8 char)

This commit is contained in:
Martin 2024-10-04 01:41:26 +02:00
parent 08f140fe19
commit 1bd921e445
3 changed files with 134 additions and 22 deletions

View File

@ -2137,6 +2137,18 @@ procedure TSynEditSelection.DoLinesEdited(Sender: TSynEditStrings; aLinePos,
end;
end;
procedure AdjustColumnX;
var
p: TPoint;
begin
p := AdjustPoint(Point(FStartBytePos, FStartLinePos), True);
FStartBytePos := p.x;
FStartLinePos := p.y;
p := AdjustPoint(Point(FEndBytePos, FEndLinePos), True);
FEndBytePos := p.x;
FEndLinePos := p.y;
end;
var
empty, back: Boolean;
begin
@ -2146,22 +2158,22 @@ begin
if FPersistent or (FPersistentLock > 0) or
((FCaret <> nil) and (not FCaret.Locked))
then begin
if FActiveSelectionMode <> smColumn then begin // TODO: adjust ypos, height in smColumn mode
empty := (FStartBytePos = FEndBytePos) and (FStartLinePos = FEndLinePos);
back := IsBackwardSel;
AdjustStartLineBytePos(AdjustPoint(StartLineBytePos, not back));
if empty then
EndLineBytePos := StartLineBytePos
else
EndLineBytePos := AdjustPoint(EndLineBytePos, back);
end;
// Todo: Change Lines in smColumn
empty := (FStartBytePos = FEndBytePos) and (FStartLinePos = FEndLinePos);
back := IsBackwardSel;
AdjustStartLineBytePos(AdjustPoint(StartLineBytePos, not back));
if empty then
EndLineBytePos := StartLineBytePos
else
EndLineBytePos := AdjustPoint(EndLineBytePos, back);
end
else begin
// Change the Selection, if change was made by owning SynEdit (Caret.Locked)
// Clear the Selection, if change was made by owning SynEdit (Caret.Locked)
// (InternalSelection has no Caret)
if (FCaret <> nil) and (FCaret.Locked) then
StartLineBytePos := FCaret.LineBytePos;
if (FCaret <> nil) and (FCaret.Locked) then begin
if FActiveSelectionMode = smColumn then
AdjustColumnX; // prevent SelAvail from accessing invalid x in phys/log
StartLineBytePos := StartLineBytePos; // caret may not yet be up to date
end;
end;
end;

View File

@ -35,6 +35,8 @@ type
);
procedure SimulatePaintText;
procedure InvalidateLines(FirstLine, LastLine: integer); reintroduce;
procedure TestDoIncForeignPaintLock(Sender: TObject);
procedure TestDoDecForeignPaintLock(Sender: TObject);
property ViewedTextBuffer;
property TextBuffer;
property TextView; // foldedview
@ -66,6 +68,7 @@ type
procedure SetClipBoardText(const AValue: String);
protected
FSynEdit : TTestSynEdit;
FSharedSynEdit : TTestSynEdit;
function LinesToText(Lines: Array of String; Separator: String = LineEnding;
SeparatorAtEnd: Boolean = False): String;
(* Relpl,must be an alteration of LineNum, LineText+
@ -78,6 +81,7 @@ type
function LinesReplaceText(Lines: Array of String; Repl: Array of const): String;
protected
procedure ReCreateEdit;
function GetSharedSynEdit: TTestSynEdit;
procedure SetSynEditHeight(Lines: Integer; PartLinePixel: Integer = 3);
procedure SetSynEditWidth(Chars: Integer; PartCharPixel: Integer = 2);
procedure SetLines(Lines: Array of String);
@ -107,6 +111,7 @@ type
procedure IncFixedBaseTestNames;
procedure DecFixedBaseTestNames;
property SynEdit: TTestSynEdit read FSynEdit;
property SharedSynEdit: TTestSynEdit read GetSharedSynEdit;
property Form: TForm read FForm;
procedure ClearClipBoard;
property ClipBoardText: String read GetClipBoardText write SetClipBoardText;
@ -267,6 +272,16 @@ begin
inherited;
end;
procedure TTestSynEdit.TestDoIncForeignPaintLock(Sender: TObject);
begin
DoIncForeignPaintLock(Sender);
end;
procedure TTestSynEdit.TestDoDecForeignPaintLock(Sender: TObject);
begin
DoDecForeignPaintLock(Sender);
end;
{ TTestBase }
procedure TTestBase.SetUp;
@ -288,6 +303,7 @@ procedure TTestBase.TearDown;
begin
inherited TearDown;
Clipboard.Close;
FreeAndNil(FSharedSynEdit);
FreeAndNil(FSynEdit);
FreeAndNil(FForm);
end;
@ -603,6 +619,7 @@ end;
procedure TTestBase.ReCreateEdit;
begin
FreeAndNil(FSharedSynEdit);
FreeAndNil(FSynEdit);
FSynEdit := TTestSynEdit.Create(FScroll);
FSynEdit.Parent := FForm;
@ -612,6 +629,24 @@ begin
FSynEdit.Height := 250; // FSynEdit.Font.Height * 20 + 2;
end;
function TTestBase.GetSharedSynEdit: TTestSynEdit;
begin
Result := FSharedSynEdit;
if Result <> nil then
exit;
FSharedSynEdit := TTestSynEdit.Create(FScroll);
FSharedSynEdit.Parent := FForm;
FSharedSynEdit.Top := 0;
FSharedSynEdit.Left := 0;
FSharedSynEdit.Width:= 500;
FSharedSynEdit.Height := 250; // FSharedSynEdit.Font.Height * 20 + 2;
FSharedSynEdit.ShareOptions := [eosShareMarks];
FSharedSynEdit.ShareTextBufferFrom(FSynEdit);
Result := FSharedSynEdit;
end;
procedure TTestBase.SetSynEditHeight(Lines: Integer; PartLinePixel: Integer);
begin
FSynEdit.Height := FSynEdit.LineHeight * Lines + PartLinePixel +

View File

@ -1553,11 +1553,18 @@ var
function GetTheText: TStringArray;
begin
SetLength(Result, 4);
SetLength(Result, 11);
Result[0] := ' ABC def';
Result[1] := 'XYZ 123';
Result[2] := '';
Result[3] := '';
Result[4] := ' ÄÖÜäöü';
Result[5] := ' abcüüü';
Result[6] := ' cde';
Result[7] := ' ÄÖÜäöü';
Result[8] := ' abcüüü';
Result[9] := ' cde';
Result[10] := '';
end;
procedure DoTest(AName: String;
@ -1592,35 +1599,60 @@ var
SelBX, SelBY, SelEX, SelEY,
TextX, TextY, TextX2, TextY2,
ExpBx, ExpBy, ExpEx, ExpEy: Integer;
aFlags: TSynEditTextFlags = []; aCaretMode: TSynCaretAdjustMode = scamIgnore);
aFlags: TSynEditTextFlags = []; aCaretMode: TSynCaretAdjustMode = scamIgnore;
ASelMode: TSynSelectionMode = smNormal;
AnUseShared: boolean = False // simulate edit from shared editor
);
var
s: Boolean;
p, p2: TPoint;
Syn: TTestSynEdit;
begin
PopPushBaseName(AName);
SetLines(TheText);
if Length(TheText) = 0 then SynEdit.Lines.Clear;
SetCaretAndSel(SelBx, SelBy, SelEx,SelEy);
SetCaretAndSel(SelBx, SelBy, SelEx,SelEy, ASelMode=smColumn, ASelMode);
s := SynEdit.SelAvail;
p := Point(Textx,TextY);
p2 := Point(Textx2,TextY2);
SynEdit.SetTextBetweenPoints(p, p2, TheInsert, aFlags, aCaretMode);
debugln(['Caret ', dbgs(SynEdit.CaretXY), ' Sel ', dbgs(SynEdit.BlockBegin), ' ',dbgs(SynEdit.BlockEnd)]);
if AnUseShared
then Syn := SharedSynEdit
else Syn := SynEdit;
TestIsBlock('After Replace', ExpBx,ExpBy, ExpEx,ExpEy);
Syn.SetTextBetweenPoints(p, p2, TheInsert, aFlags, aCaretMode);
debugln(['Caret ', dbgs(Syn.CaretXY), ' Sel ', dbgs(Syn.BlockBegin), ' ',dbgs(Syn.BlockEnd)]);
SynEdit.Undo;
if ExpBx < 0 then
TestIsNoBlock('After Replace - no')
else
TestIsBlock('After Replace', ExpBx,ExpBy, ExpEx,ExpEy);
Syn.Undo;
if s
then TestIsBlock('After Undo', SelBx, SelBy, SelEx,SelEy)
else TestIsNoBlock('After Undo');
SynEdit.Redo;
Syn.Redo;
TestIsBlock('After Redo', ExpBx,ExpBy, ExpEx,ExpEy);
end;
function IncIf(AVal: Integer; ABool: Boolean): integer;
begin
Result := AVal;
if ABool then inc(Result);
end;
function DecIf(AVal: Integer; ABool: Boolean): integer;
begin
Result := AVal;
if ABool then dec(Result);
end;
const
CL1: array[5..10] of integer = ( 6 {Üä}, 4 {cü}, 5 {}, 6 {Üä}, 4 {cü}, 5 {} );
CL2: array[5..10] of integer = ( 10 {Üä}, 7 {cü}, 8 {}, 10 {Üä}, 7 {cü}, 8 {} );
var
p: TPoint;
y1, y2: Integer;
begin
PushBaseName('');
TheText := GetTheText;
@ -1755,6 +1787,39 @@ begin
DoTest('setExtendBlock - BlockEnd B', 2,1, 6,1, 6,1, 7,1, 2,1, 6,1, [setExtendBlock], scamAdjust); // so caret will not destroy selection
// Currently selection will be cleared // TODO
// ÖÜ bc
for y1 := 5 to 10 do
for y2 := 5 to 10 do
begin
TheInsert := 'X';
//DoTest('column ', y1,CL1[y1], y2,CL2[y2], 1,y1, 1,y1, -7,5, 7,7, [setPersistentBlock], scamAdjust, smColumn, sh);
DoTest('column ', CL1[y1],y1, CL2[y2],y2, 1,y1, 1,y1, -7,5, 7,7, [], scamAdjust, smColumn, False);
DoTest('column ', CL2[y1],y1, CL1[y2],y2, 1,y1, 1,y1, -7,5, 7,7, [], scamAdjust, smColumn, False);
DoTest('column ', CL1[y1],y1, CL2[y2],y2, 1,y2, 1,y2, -7,5, 7,7, [], scamAdjust, smColumn, False);
DoTest('column ', CL2[y1],y1, CL1[y2],y2, 1,y2, 1,y2, -7,5, 7,7, [], scamAdjust, smColumn, False);
DoTest('column ', CL1[y1],y1, CL2[y2],y2, 1,y1, 1,y1, IncIf(CL1[y1],y1=y1),y1, IncIf(CL2[y2],y2=y1),y2, [], scamAdjust, smColumn, True);
DoTest('column ', CL2[y1],y1, CL1[y2],y2, 1,y1, 1,y1, IncIf(CL2[y1],y1=y1),y1, IncIf(CL1[y2],y2=y1),y2, [], scamAdjust, smColumn, True);
DoTest('column ', CL1[y1],y1, CL2[y2],y2, 1,y2, 1,y2, IncIf(CL1[y1],y1=y2),y1, IncIf(CL2[y2],y2=y2),y2, [], scamAdjust, smColumn, True);
DoTest('column ', CL2[y1],y1, CL1[y2],y2, 1,y2, 1,y2, IncIf(CL2[y1],y1=y2),y1, IncIf(CL1[y2],y2=y2),y2, [], scamAdjust, smColumn, True);
TheInsert := '';
DoTest('column ', CL1[y1],y1, CL2[y2],y2, 1,y1, 2,y1, -7,5, 7,7, [], scamAdjust, smColumn, False);
DoTest('column ', CL2[y1],y1, CL1[y2],y2, 1,y1, 2,y1, -7,5, 7,7, [], scamAdjust, smColumn, False);
DoTest('column ', CL1[y1],y1, CL2[y2],y2, 1,y2, 2,y2, -7,5, 7,7, [], scamAdjust, smColumn, False);
DoTest('column ', CL2[y1],y1, CL1[y2],y2, 1,y2, 2,y2, -7,5, 7,7, [], scamAdjust, smColumn, False);
DoTest('column ', CL1[y1],y1, CL2[y2],y2, 1,y1, 2,y1, DecIf(CL1[y1],y1=y1),y1, DecIf(CL2[y2],y2=y1),y2, [], scamAdjust, smColumn, True);
DoTest('column ', CL2[y1],y1, CL1[y2],y2, 1,y1, 2,y1, DecIf(CL2[y1],y1=y1),y1, DecIf(CL1[y2],y2=y1),y2, [], scamAdjust, smColumn, True);
DoTest('column ', CL1[y1],y1, CL2[y2],y2, 1,y2, 2,y2, DecIf(CL1[y1],y1=y2),y1, DecIf(CL2[y2],y2=y2),y2, [], scamAdjust, smColumn, True);
DoTest('column ', CL2[y1],y1, CL1[y2],y2, 1,y2, 2,y2, DecIf(CL2[y1],y1=y2),y1, DecIf(CL1[y2],y2=y2),y2, [], scamAdjust, smColumn, True);
// no selection / check caret / with without adjust
end;
TheText := nil; // empty text
TheInsert := 'X';
PopPushBaseName('syn clear');