SYnEdit: multi caret, paint - fix home key

git-svn-id: trunk@48319 -
This commit is contained in:
martin 2015-03-12 23:34:59 +00:00
parent d48e548ada
commit 6bb6966509
2 changed files with 306 additions and 80 deletions

View File

@ -8,7 +8,7 @@ unit SynPluginMultiCaret;
{$IfDef SynMultiCaretAssert}
{$ASSERTIONS on}
{$ENDIF}
{ $INLINE off}
interface
uses
@ -74,7 +74,7 @@ type
property ScreenCaret[Index: Integer]: TSynPluginMultiCaretVisual read GetScreenCaret; default;
end;
TCaretFlag = (cfMainCaret, cfNoneVisual, cfAddDuplicate);
TCaretFlag = (cfMainCaret, cfNoneVisual, cfAddDuplicate, cfIterationDone);
TCaretFlags = set of TCaretFlag;
{ TSynPluginMultiCaretList }
@ -89,6 +89,7 @@ type
Flags: TCaretFlags;
Visual: TSynPluginMultiCaretVisual;
end;
PCaretData = ^TCaretData;
private
FLowIndex, FHighIndex: Integer;
FMainCaretIndex: Integer;
@ -114,7 +115,7 @@ type
function InternalRemoveCaretEx(RawIndex: Integer; AlternativeRawIndex: Integer = -1): Integer;
function InternalRemoveCaret(RawIndex: Integer): integer;
procedure AdjustAfterChange(RawIndex: Integer);
procedure AdjustAfterChange(RawIndex: Integer); inline;
public
constructor Create;
function AddCaret(X, Y, Offs: Integer; flags: TCaretFlags = []; PhysX: Integer = -1): Integer;
@ -137,6 +138,32 @@ type
property Visual[Index: Integer]: TSynPluginMultiCaretVisual read GetVisual write SetVisual;
property Flags[Index: Integer]: TCaretFlags read GetFlags;
property MainCaretIndex: Integer read GetMainCaretIndex;
private
FCurrenCaret, FBeforeNextCaret: PCaretData;
FIterationDoneCount: Integer;
FLowCaret, FHighCaret: PCaretData; // used in AdjustAfterChange
FIteratoreMode: (mciNone, mciUp, mciDown);
function GetCurrentCaretFull: TLogCaretPoint; inline;
function GetCurrentCaretKeepX: Integer; inline;
procedure SetCurrentCaretFull(AValue: TLogCaretPoint); inline;
procedure SetCurrentCaretKeepX(AValue: Integer); inline;
procedure AdjustAfterChange(ACaret: PCaretData);
public
// During iteration no calls to add/remove are allowed
procedure StartIteratorAtFirst; // valid after first call to IterateNextUp
function IterateNextUp: Boolean; inline;
procedure StartIteratorAtLast;
function IterateNextDown: Boolean; inline;
//procedure AbortIterator;
property CurrentCaretFull: TLogCaretPoint read GetCurrentCaretFull write SetCurrentCaretFull;
property CurrentCaretKeepX: Integer read GetCurrentCaretKeepX write SetCurrentCaretKeepX;
//property CurrentCaret: TPoint read GetCurrentCaret write SetCurrentCaret;
//property CurrentCaretX: Integer read GetCurrentCaretX write SetCurrentCaretX;
//property CurrentCaretOffs: Integer read GetCurrentCaretOffs write SetCurrentCaretOffs;
//property CurrentCaretY: Integer read GetCurrentCaretY write SetCurrentCaretY;
end;
{ TSynPluginMultiCaretBase }
@ -705,6 +732,7 @@ end;
function TSynPluginMultiCaretList.InternalRemoveCaret(RawIndex: Integer): integer;
begin
assert(FIteratoreMode=mciNone, 'TSynPluginMultiCaretList.AddCaret: FIteratoreMode=mciNone');
assert((RawIndex>=FLowIndex) and (RawIndex <= FHighIndex), 'TSynPluginMultiCaretList.InternalRemoveCaret: (RawIndex>=FLowIndex) and (RawIndex <= FHighIndex)');
Result := 0; // change to LowCaret .. RawIndex
@ -726,7 +754,7 @@ begin
inc(FLowIndex);
if RawIndex > FMainCaretIndex then
inc(FMainCaretIndex);
Result := 1;
Result := 1; // FLowIndex was increasde by 1;
end;
//debugln(SynMCaretDebug, ['TSynPluginMultiCaretList.InternalRemoveCaret ', RawIndex, ' , ', count]);
@ -734,58 +762,13 @@ end;
procedure TSynPluginMultiCaretList.AdjustAfterChange(RawIndex: Integer);
var
NewIdx, y, x: Integer;
NewIdx, y, x, o: Integer;
v: TCaretData;
begin
assert((RawIndex>=FLowIndex) and (RawIndex <= FHighIndex), 'TSynPluginMultiCaretList.AdjustAfterChange: (Index>=FLowIndex) and (Index <= FHighIndex)');
NewIdx := RawIndex;
y := FCarets[RawIndex].y;
x := FCarets[RawIndex].x;
if (RawIndex > FLowIndex) and
((y < FCarets[RawIndex-1].y) or ((y = FCarets[RawIndex-1].y) and (x <= FCarets[RawIndex-1].x)))
then begin
if (RawIndex-1 > FLowIndex) and
((y < FCarets[RawIndex-2].y) or ((y = FCarets[RawIndex-2].y) and (x < FCarets[RawIndex-2].x)))
then
NewIdx := FindEqOrNextCaretRawIdx(x,y, FLowIndex, RawIndex - 2)
else
NewIdx := RawIndex-1;
if (y = FCarets[NewIdx].y) and (x = FCarets[NewIdx].x) then begin
if FMergeLock = 0 then
InternalRemoveCaretEx(RawIndex, NewIdx);
exit;
end;
v := FCarets[RawIndex];
{$IfDef SynMultiCaretDebug}
debugln(SynMCaretDebug, ['TSynPluginMultiCaretList.AdjustAfterChange ', NewIdx, ' ',RawIndex]);
{$EndIf}
Move(FCarets[NewIdx], FCarets[NewIdx+1], (RawIndex-NewIdx) * SizeOf(FCarets[0]));
FCarets[NewIdx] := v;
end
else
if (RawIndex < FHighIndex) and
((y > FCarets[RawIndex+1].y) or ((y = FCarets[RawIndex+1].y) and (x >= FCarets[RawIndex+1].x)))
then begin
if (RawIndex+1 < FHighIndex) and
((y > FCarets[RawIndex+2].y) or ((y = FCarets[RawIndex+2].y) and (x > FCarets[RawIndex+2].x)))
then
NewIdx := FindEqOrNextCaretRawIdx(x,y, RawIndex + 2, FHighIndex)
else
NewIdx := RawIndex+1;
if (y = FCarets[NewIdx].y) and (x = FCarets[NewIdx].x) then begin
if FMergeLock = 0 then
InternalRemoveCaretEx(RawIndex, NewIdx);
exit;
end;
v := FCarets[RawIndex];
{$IfDef SynMultiCaretDebug}
debugln(SynMCaretDebug, ['TSynPluginMultiCaretList.AdjustAfterChange ', NewIdx, ' ',RawIndex]);
{$EndIf}
Move(FCarets[RawIndex+1], FCarets[RawIndex], (NewIdx-RawIndex) * SizeOf(FCarets[0]));
FCarets[NewIdx] := v;
end;
assert(FIteratoreMode=mciNone, 'TSynPluginMultiCaretList.AddCaret: FIteratoreMode=mciNone');
FLowCaret := @FCarets[FLowIndex];
FHighCaret := @FCarets[FHighIndex];
AdjustAfterChange(@FCarets[RawIndex]);
end;
constructor TSynPluginMultiCaretList.Create;
@ -801,6 +784,7 @@ var
NewCarets: Array of TCaretData;
Len, AddLen, i, Middle: Integer;
begin
assert(FIteratoreMode=mciNone, 'TSynPluginMultiCaretList.AddCaret: FIteratoreMode=mciNone');
Result := FindEqOrNextCaretRawIdx(x, y, Offs);
if Result < FLowIndex then
Result := FLowIndex;
@ -882,6 +866,7 @@ end;
procedure TSynPluginMultiCaretList.RemoveCaret(Index: Integer);
begin
assert(FIteratoreMode=mciNone, 'TSynPluginMultiCaretList.RemoveCaret: FIteratoreMode=mciNone');
InternalRemoveCaret(Index+FLowIndex);
end;
@ -889,6 +874,7 @@ procedure TSynPluginMultiCaretList.Clear(AFreeVisual: Boolean);
var
i: Integer;
begin
assert(FIteratoreMode=mciNone, 'TSynPluginMultiCaretList.Clear: FIteratoreMode=mciNone');
if AFreeVisual then
begin
for i := FLowIndex to FHighIndex do
@ -1047,6 +1033,208 @@ begin
dec(FMergeLock);
end;
function TSynPluginMultiCaretList.GetCurrentCaretFull: TLogCaretPoint;
begin
Result.X := FCurrenCaret^.x;
Result.Y := FCurrenCaret^.y;
Result.Offs := FCurrenCaret^.offs;
end;
function TSynPluginMultiCaretList.GetCurrentCaretKeepX: Integer;
begin
Result := FCurrenCaret^.KeepX;
end;
procedure TSynPluginMultiCaretList.SetCurrentCaretFull(AValue: TLogCaretPoint);
begin
FCurrenCaret^.x := AValue.X;
FCurrenCaret^.y := AValue.Y;
FCurrenCaret^.offs := AValue.Offs;
AdjustAfterChange(FCurrenCaret);
end;
procedure TSynPluginMultiCaretList.SetCurrentCaretKeepX(AValue: Integer);
begin
FCurrenCaret^.KeepX := AValue;
AdjustAfterChange(FCurrenCaret);
end;
procedure TSynPluginMultiCaretList.AdjustAfterChange(ACaret: PCaretData);
function ToRawIndex(C: PCaretData): Integer;
begin
Result := (C - @FCarets[0]) div SizeOf(FCarets[0]);
end;
var
NewCaretPos, HelpCaretPos: PCaretData;
NewCaretIdx, y, x, o: Integer;
v: TCaretData;
begin
assert((ACaret>=FLowCaret) and (ACaret <= FHighCaret) and (ACaret <> nil), 'TSynPluginMultiCaretList.AdjustAfterChange: (ACaret>=FLowCaret) and (ACaret <= FHighCaret)');
// if iterating then this must only be called with fcurrentcaret
assert((FIteratoreMode=mciNone) or ((ACaret = FCurrenCaret)), 'TSynPluginMultiCaretList.AdjustAfterChange: (FIteratoreMode=mciNone) or (ACaret = FCurrenCaret)');
y := ACaret^.y;
x := ACaret^.x;
o := ACaret^.offs;
if (ACaret > FLowCaret) then begin
NewCaretPos := ACaret - 1;
if (y < NewCaretPos^.y) or
( (y = NewCaretPos^.y) and
( (x < NewCaretPos^.x) or ( (x = NewCaretPos^.x) and (o <= NewCaretPos^.offs) ) )
)
then begin
HelpCaretPos := NewCaretPos - 1;
if (HelpCaretPos >= FLowCaret) and
( (y < HelpCaretPos^.y) or
( (y = HelpCaretPos^.y) and
( (x < HelpCaretPos^.x) or ( (x = HelpCaretPos^.x) and (o < HelpCaretPos^.offs) ) )
) )
then begin
NewCaretIdx := FindEqOrNextCaretRawIdx(x,y,o, FLowIndex, ToRawIndex(HelpCaretPos));
NewCaretPos := @FCarets[NewCaretIdx];
end;
if (y = NewCaretPos^.y) and (x = NewCaretPos^.x) and (o = NewCaretPos^.offs) then begin
if FMergeLock = 0 then
InternalRemoveCaretEx(ToRawIndex(ACaret), ToRawIndex(NewCaretPos));
exit;
end;
v := ACaret^;
{$IfDef SynMultiCaretDebug}
debugln(SynMCaretDebug, ['TSynPluginMultiCaretList.AdjustAfterChange ', ToRawIndex(NewCaretPos), ' ',ToRawIndex(ACaret)]);
{$EndIf}
Move(NewCaretPos^, (NewCaretPos+1)^, Pointer(ACaret)-Pointer(NewCaretPos));
NewCaretPos^ := v;
assert(FBeforeNextCaret=nil, 'TSynPluginMultiCaretList.AdjustAfterChange: FBeforeNextCaret=nil');
FCurrenCaret := NewCaretPos; // move down
case FIteratoreMode of
mciUp: FBeforeNextCaret := ACaret; // continue at ACaret+1;
mciDown: begin
FBeforeNextCaret := ACaret + 1; // continue at ACaret;
Include(FCurrenCaret^.Flags, cfIterationDone);
inc(FIterationDoneCount);
end;
end;
end
end
else
if (ACaret < FHighCaret) then begin
NewCaretPos := ACaret + 1;
if (y > NewCaretPos^.y) or
( (y = NewCaretPos^.y) and
( (x > NewCaretPos^.x) or ( (x = NewCaretPos^.x) and (o >= NewCaretPos^.offs) ) )
)
then begin
HelpCaretPos := NewCaretPos + 1;
if (HelpCaretPos <= FHighCaret) and
( (y > HelpCaretPos^.y) or
( (y = HelpCaretPos^.y) and
( (x > HelpCaretPos^.x) or ( (x = HelpCaretPos^.x) and (o > HelpCaretPos^.offs) ) )
) )
then begin
NewCaretIdx := FindEqOrNextCaretRawIdx(x,y,o, ToRawIndex(HelpCaretPos), FHighIndex);
NewCaretPos := @FCarets[NewCaretIdx];
end;
if (y = NewCaretPos^.y) and (x = NewCaretPos^.x) and (o = NewCaretPos^.offs) then begin
if FMergeLock = 0 then
InternalRemoveCaretEx(ToRawIndex(ACaret), ToRawIndex(NewCaretPos));
exit;
end;
v := ACaret^;
{$IfDef SynMultiCaretDebug}
debugln(SynMCaretDebug, ['TSynPluginMultiCaretList.AdjustAfterChange ', ToRawIndex(NewCaretPos), ' ',ToRawIndex(ACaret)]);
{$EndIf}
Move((ACaret+1)^, ACaret^, Pointer(NewCaretPos)-Pointer(ACaret));
NewCaretPos^ := v;
assert(FBeforeNextCaret=nil, 'TSynPluginMultiCaretList.AdjustAfterChange: FBeforeNextCaret=nil');
FCurrenCaret := NewCaretPos; // move down
case FIteratoreMode of
mciDown: FBeforeNextCaret := ACaret; // continue at ACaret-1;
mciUp: begin
FBeforeNextCaret := ACaret - 1; // continue at ACaret;
Include(FCurrenCaret^.Flags, cfIterationDone);
inc(FIterationDoneCount);
end;
end;
end;
end;
end;
procedure TSynPluginMultiCaretList.StartIteratorAtFirst;
begin
FBeforeNextCaret := nil;
if Length(FCarets) = 0 then begin
FLowCaret := nil;
FHighCaret := nil;
FCurrenCaret := nil;
exit;
end;
FLowCaret := @FCarets[FLowIndex];
FHighCaret := @FCarets[FHighIndex];
FCurrenCaret := FLowCaret - 1;
FIteratoreMode := mciUp;
end;
function TSynPluginMultiCaretList.IterateNextUp: Boolean;
begin
if FBeforeNextCaret <> nil then begin
FCurrenCaret := FBeforeNextCaret;
FBeforeNextCaret := nil;
end;
repeat
Result := FCurrenCaret < FHighCaret;
if not Result then begin
FIteratoreMode := mciNone;
assert(FIterationDoneCount = 0, 'TSynPluginMultiCaretList.IterateNextUp: FIterationDoneCount = 0');
exit;
end;
inc(FCurrenCaret);
if not(cfIterationDone in FCurrenCaret^.Flags) then
break;
Exclude(FCurrenCaret^.Flags, cfIterationDone);
dec(FIterationDoneCount);
until False;
end;
procedure TSynPluginMultiCaretList.StartIteratorAtLast;
begin
FBeforeNextCaret := nil;
if Length(FCarets) = 0 then begin
FLowCaret := nil;
FHighCaret := nil;
FCurrenCaret := nil;
exit;
end;
FLowCaret := @FCarets[FLowIndex];
FHighCaret := @FCarets[FHighIndex];
FCurrenCaret := FHighCaret + 1;
FIteratoreMode := mciDown;
end;
function TSynPluginMultiCaretList.IterateNextDown: Boolean;
begin
if FBeforeNextCaret <> nil then begin
FCurrenCaret := FBeforeNextCaret;
FBeforeNextCaret := nil;
end;
repeat
Result := FCurrenCaret > FLowCaret;
if not Result then begin
FIteratoreMode := mciNone;
assert(FIterationDoneCount = 0, 'TSynPluginMultiCaretList.IterateNextDown: FIterationDoneCount = 0');
exit;
end;
dec(FCurrenCaret);
if not(cfIterationDone in FCurrenCaret^.Flags) then
break;
Exclude(FCurrenCaret^.Flags, cfIterationDone);
dec(FIterationDoneCount);
until False;
end;
{ TSynPluginMultiCaretBase }
procedure TSynPluginMultiCaretBase.DoBoundsChanged(Sender: TObject);
@ -1271,9 +1459,7 @@ begin
exit;
end;
if (eoNoCaret in Editor.Options) or
not((eoPersistentCaret in Editor.Options) or Editor.Focused)
then begin
if (eoNoCaret in Editor.Options) then begin
Carets.Visual[Result] := nil;
exit;
end;
@ -1289,7 +1475,7 @@ begin
Carets.Visual[Result] := GetVisual;
x := ViewedTextBuffer.LogPhysConvertor.LogicalToPhysical(ToIdx(y), x, Offs); // TODO: check if offs was adjusted? But should not happen for NEW caret
Carets.Visual[Result].DisplayPos := TextArea.RowColumnToPixels(Point(x, y1));
Carets.Visual[Result].Visible := True;
Carets.Visual[Result].Visible := (eoPersistentCaret in Editor.Options) or Editor.Focused;
end
else
Carets.Visual[Result] := nil;
@ -1304,20 +1490,21 @@ procedure TSynPluginMultiCaretBase.UpdateCaretsPos;
var
i, x, y, o, w: Integer;
y1, y2: Integer;
vis: Boolean;
begin
if plfDeferUpdateCaretsPos in FPaintLockFlags then exit;
if FPaintLock > 0 then begin
include(FPaintLockFlags, plfUpdateCaretsPos);
exit;
end;
if (eoNoCaret in Editor.Options) or
not((eoPersistentCaret in Editor.Options) or Editor.Focused)
then begin
for i := 0 to FUsedList.Count - 1 do
FUsedList[i].Visible := False;
if (eoNoCaret in Editor.Options) then begin
for i := 0 to CaretsCount - 1 do
Carets.Visual[i] := nil;
exit;
end;
vis := (eoPersistentCaret in Editor.Options) or Editor.Focused;
w := Editor.LinesInWindow + 1;
for i := 0 to CaretsCount - 1 do begin
if cfNoneVisual in Carets.Flags[i] then continue;
@ -1339,7 +1526,7 @@ begin
Carets.Visual[i] := GetVisual;
x := ViewedTextBuffer.LogPhysConvertor.LogicalToPhysical(ToIdx(y), x, o);
Carets.Visual[i].DisplayPos := TextArea.RowColumnToPixels(Point(x, y1));
Carets.Visual[i].Visible := True;
Carets.Visual[i].Visible := vis;
//todo: remove if duplicate
// check if offs was adjusted
//if o <> Carets.CaretOffs[i] then
@ -1963,33 +2150,30 @@ procedure TSynCustomPluginMultiCaret.ProcessAllSynCommand(Sender: TObject; After
case Command of
ecLeft, ecUp, ecWordLeft, ecLineStart, ecPageUp, ecPageLeft,
ecPageTop, ecLineTextStart, ecWordEndLeft, ecHalfWordLeft:
begin;
i := 0;
j := CaretsCount;
while i < j do begin
CaretObj.FullLogicalPos := Carets.CaretFull[i];
k := Carets.CaretKeepX[i];
begin
Carets.StartIteratorAtFirst;
while Carets.IterateNextUp do begin
CaretObj.FullLogicalPos := Carets.CurrentCaretFull;
k := Carets.CurrentCaretKeepX;
if k > 0 then
CaretObj.KeepCaretXPos := k;
Editor.CommandProcessor(Command, AChar, nil, [hcfInit, hcfFinish]);
Carets.CaretFull[i] := CaretObj.FullLogicalPos;
Carets.CaretKeepX[i] := CaretObj.KeepCaretXPos;
inc(i);
Carets.CurrentCaretFull := CaretObj.FullLogicalPos;
Carets.CurrentCaretKeepX := CaretObj.KeepCaretXPos;
end;
end;
ecEditorTop, ecEditorBottom: ClearCarets;
else
begin
i := CaretsCount;
while i > 0 do begin
dec(i);
CaretObj.FullLogicalPos := Carets.CaretFull[i];
k := Carets.CaretKeepX[i];
Carets.StartIteratorAtLast;
while Carets.IterateNextDown do begin
CaretObj.FullLogicalPos := Carets.CurrentCaretFull;
k := Carets.CurrentCaretKeepX;
if k > 0 then
CaretObj.KeepCaretXPos := k;
Editor.CommandProcessor(Command, AChar, nil, [hcfInit, hcfFinish]);
Carets.CaretFull[i] := CaretObj.FullLogicalPos;
Carets.CaretKeepX[i] := CaretObj.KeepCaretXPos;
Carets.CurrentCaretFull := CaretObj.FullLogicalPos;
Carets.CurrentCaretKeepX := CaretObj.KeepCaretXPos;
end;
end;
end;

View File

@ -37,6 +37,9 @@ type
procedure TestExtraCaretPos(AName: String; ExpCount: Integer; ExpPos: array of integer); // x,y
procedure TestExtraCaretPosAndOffs(AName: String; ExpCount: Integer; ExpPos: array of integer); // x,y,offs
procedure TestExtraCaretPos(AName: String; X, Y: Integer; ExpCount: Integer; ExpPos: array of integer); // x,y
procedure TestExtraCaretPosAndOffs(AName: String; X, Y, Offs: Integer; ExpCount: Integer; ExpPos: array of integer); // x,y,offs
procedure RunAndTest(AName: String;
cmds: Array of TSynEditorCommand; chars: array of String;
X, Y: Integer; ExpLines: Array of String
@ -206,6 +209,20 @@ begin
end
end;
procedure TTestMultiCaret.TestExtraCaretPos(AName: String; X, Y: Integer; ExpCount: Integer;
ExpPos: array of integer);
begin
TestIsCaret(AName, X, Y);
TestExtraCaretPos(AName, ExpCount, ExpPos);
end;
procedure TTestMultiCaret.TestExtraCaretPosAndOffs(AName: String; X, Y, Offs: Integer;
ExpCount: Integer; ExpPos: array of integer);
begin
TestIsCaret(AName, X, Y, Offs);
TestExtraCaretPosAndOffs(AName, ExpCount, ExpPos);
end;
procedure TTestMultiCaret.RunAndTest(AName: String; cmds: array of TSynEditorCommand;
chars: array of String; X, Y: Integer; ExpLines: array of String);
begin
@ -537,6 +554,14 @@ begin
end;
procedure TTestMultiCaret.CursorMove;
function LocalText1: TStringArray;
begin
SetLength(Result, 4);
Result[0] := ' 123 ';
Result[1] := ' abc ';
Result[2] := ' abc ';
Result[3] := '';
end;
begin
PushBaseName('eoScrollPastEol, eoCaretSkipTab');
FOptAdd := [eoScrollPastEol];
@ -630,6 +655,23 @@ begin
PopBaseName;
PopBaseName;
// move through tab, but not double-widths
PushBaseName('ecLineStart swap to carets');
FOptAdd := [eoScrollPastEol, eoEnhanceHomeKey];
FOptRemove := [eoTrimTrailingSpaces];
FOpt2Add := [eoEnhanceEndKey];
FOpt2Remove := [];
ReCreateEdit(LocalText1);
SetCaretsByKey(1,1, [2,0, 0,1], mcmMoveAllCarets);
TestExtraCaretPosAndOffs('', 3,2,0, 2, [1,1,0, 3,1,0]);
RunAndTest('3 carets', [ecLineStart], 1,2,0, LocalText1, 2, [1,1,0, 5,1,0]);
RunAndTest('3 carets Right', [ecRight], 2,2,0, LocalText1, 2, [2,1,0, 6,1,0]);
RunAndTest('3 carets', [ecLineStart], 1,2,0, LocalText1, 2, [1,1,0, 5,1,0]);
RunAndTest('3 carets', [ecLineStart], 7,2,0, LocalText1, 2, [1,1,0, 5,1,0]);
PopBaseName;
end;
procedure TTestMultiCaret.Edit;