From c0b01ec2c326e0cf968ea7e61796760e4c1a0f75 Mon Sep 17 00:00:00 2001 From: martin Date: Wed, 11 Mar 2015 18:27:30 +0000 Subject: [PATCH] SYnEdit: multi caret, paint - started log/phys position handling git-svn-id: trunk@48271 - --- components/synedit/syneditpointclasses.pas | 4 +- components/synedit/synpluginmulticaret.pp | 407 +++++++++--- components/synedit/test/testbase.pas | 35 + components/synedit/test/testmulticaret.pas | 711 ++++++++++++++++++--- 4 files changed, 974 insertions(+), 183 deletions(-) diff --git a/components/synedit/syneditpointclasses.pas b/components/synedit/syneditpointclasses.pas index caa1c53c35..b2bad8f6b7 100644 --- a/components/synedit/syneditpointclasses.pas +++ b/components/synedit/syneditpointclasses.pas @@ -619,8 +619,8 @@ end; function TSynEditBaseCaret.GetFullLogicalPos: TLogCaretPoint; begin ValidateBytePos; - Result.X := FLinePos; - Result.Y := FBytePos; + Result.Y := FLinePos; + Result.X := FBytePos; Result.Offs := FBytePosOffset; end; diff --git a/components/synedit/synpluginmulticaret.pp b/components/synedit/synpluginmulticaret.pp index 66785b0190..301c051774 100644 --- a/components/synedit/synpluginmulticaret.pp +++ b/components/synedit/synpluginmulticaret.pp @@ -84,22 +84,28 @@ type //TCaretFlag = (cfMainCaret, cfNoneVisual); //TCaretFlags = set of TCaretFlag; TCaretData = record - x, y: Integer; // logical + x, y, offs: Integer; // logical + KeepX: Integer; Flags: TCaretFlags; Visual: TSynPluginMultiCaretVisual; end; private FLowIndex, FHighIndex: Integer; FMainCaretIndex: Integer; + FMergeLock: Integer; FCarets: Array of TCaretData; - function FindEqOrNextCaretRawIdx(X, Y: Integer; LowIdx: integer = -1; HighIdx: integer = -1): Integer; + function FindEqOrNextCaretRawIdx(X, Y, Offs: Integer; LowIdx: integer = -1; HighIdx: integer = -1): Integer; function GetCaret(Index: Integer): TPoint; inline; + function GetCaretFull(Index: Integer): TLogCaretPoint; inline; + function GetCaretOffs(Index: Integer): Integer; inline; function GetCaretX(Index: Integer): Integer; inline; function GetCaretY(Index: Integer): Integer; inline; function GetFlags(Index: Integer): TCaretFlags; function GetMainCaretIndex: Integer; function GetVisual(Index: Integer): TSynPluginMultiCaretVisual; inline; procedure SetCaret(Index: Integer; AValue: TPoint); inline; + procedure SetCaretFull(Index: Integer; AValue: TLogCaretPoint); inline; + procedure SetCaretOffs(Index: Integer; AValue: Integer); inline; procedure SetCaretX(Index: Integer; AValue: Integer); inline; procedure SetCaretY(Index: Integer; AValue: Integer); inline; procedure SetVisual(Index: Integer; AValue: TSynPluginMultiCaretVisual); inline; @@ -109,16 +115,21 @@ type procedure AdjustAfterChange(RawIndex: Integer); public constructor Create; - function AddCaret(X, Y: Integer; flags: TCaretFlags = []): Integer; + function AddCaret(X, Y, Offs: Integer; flags: TCaretFlags = []): Integer; procedure RemoveCaret(Index: Integer); procedure Clear(AFreeVisual: Boolean = False); function Count: Integer; - function FindCaretIdx(X, Y: Integer): Integer; + function FindCaretIdx(X, Y, Offs: Integer): Integer; + function FindEqOrNextCaretIdx(X, Y, Offs: Integer; LowIdx: integer = -1; HighIdx: integer = -1): Integer; procedure AdjustAllAfterEdit(aLinePos, aBytePos, aCount, aLineBrkCnt: Integer); procedure FindAndRemoveMergedCarets; + procedure IncMergeLock; + procedure DecMergeLock; property Caret[Index: Integer]: TPoint read GetCaret write SetCaret; + property CaretFull[Index: Integer]: TLogCaretPoint read GetCaretFull write SetCaretFull; property CaretX[Index: Integer]: Integer read GetCaretX write SetCaretX; + property CaretOffs[Index: Integer]: Integer read GetCaretOffs write SetCaretOffs; property CaretY[Index: Integer]: Integer read GetCaretY write SetCaretY; property Visual[Index: Integer]: TSynPluginMultiCaretVisual read GetVisual write SetVisual; property Flags[Index: Integer]: TCaretFlags read GetFlags; @@ -162,7 +173,7 @@ type function CreateVisual: TSynPluginMultiCaretVisual; virtual; function GetVisual: TSynPluginMultiCaretVisual; protected - function AddCaret(X, Y: Integer; flags: TCaretFlags = []): Integer; + function AddCaret(X, Y, Offs: Integer; flags: TCaretFlags = []): Integer; procedure RemoveCaret(Index: Integer); procedure UpdateCaretsPos; procedure ClearCarets; @@ -225,7 +236,8 @@ type FKeyStrokes: TSynPluginMultiCaretKeyStrokes; FStateFlags: TSynPluginMultiCaretStateFlags; FMouseActions: TSynPluginMultiCaretMouseActions; - FSelY1, FSElY2, FSelX: Integer; + FSelY1, FSelY2, FSelX: Integer; + FColSelDoneY1, FColSelDoneY2, FColSelDonePhysX: Integer; procedure RemoveCaretsInSelection; procedure SetActiveMode(AValue: TSynPluginMultiCaretMode); @@ -233,6 +245,12 @@ type procedure SetDefaultMode(AValue: TSynPluginMultiCaretDefaultMode); procedure SetSkipCaretAtSel; protected + function LogPhysConvertor: TSynLogicalPhysicalConvertor; inline; + function PhysicalToLogical(AIndex, AColumn: Integer; out AColOffset: Integer; + ACharSide: TSynPhysCharSide= cspDefault; + AFlags: TSynLogPhysFlags = []): Integer; inline; + + procedure DoEditorRemoving(AValue: TCustomSynEdit); override; procedure DoEditorAdded(AValue: TCustomSynEdit); override; @@ -263,7 +281,7 @@ type public constructor Create(AOwner: TComponent); override; destructor Destroy; override; - procedure AddCaretAt(X, Y: Integer); + procedure AddCaretAtLogPos(X, Y, Offs: Integer); property MouseActions: TSynPluginMultiCaretMouseActions read FMouseActions; property KeyStrokes: TSynPluginMultiCaretKeyStrokes read FKeyStrokes; property EnableWithColumnSelection: Boolean read FEnableWithColumnSelection write FEnableWithColumnSelection default True; @@ -467,8 +485,8 @@ end; { TSynPluginMultiCaretList } -function TSynPluginMultiCaretList.FindEqOrNextCaretRawIdx(X, Y: Integer; LowIdx: integer; - HighIdx: integer): Integer; +function TSynPluginMultiCaretList.FindEqOrNextCaretRawIdx(X, Y, Offs: Integer; + LowIdx: integer; HighIdx: integer): Integer; var l, h: Integer; begin @@ -486,13 +504,24 @@ begin Result := (l + h) div 2; while (h > l) do begin - if (FCarets[Result].y > y) or ((FCarets[Result].y = y) and (FCarets[Result].x >= x)) then + if (FCarets[Result].y > y) or + ( (FCarets[Result].y = y) and + ( (FCarets[Result].x > x) or + ((FCarets[Result].x = x) and (FCarets[Result].offs >= Offs)) + ) + ) + then h := Result else l := Result + 1; Result := (l + h) div 2; end; - if (FCarets[Result].y < y) or ((FCarets[Result].y = y) and (FCarets[Result].x < x)) then + if (FCarets[Result].y < y) or + ( (FCarets[Result].y = y) and + (FCarets[Result].x < x) or + ((FCarets[Result].x = x) and (FCarets[Result].offs < Offs)) + ) + then inc(Result); end; @@ -504,6 +533,22 @@ begin Result.y := FCarets[Index].y; end; +function TSynPluginMultiCaretList.GetCaretFull(Index: Integer): TLogCaretPoint; +begin + Index := Index + FLowIndex; + assert((Index>=FLowIndex) and (Index <= FHighIndex), 'TSynPluginMultiCaretList.GetCaretX: (Index>=FLowIndex) and (Index <= FHighIndex)'); + Result.X := FCarets[Index].x; + Result.Y := FCarets[Index].y; + Result.Offs := FCarets[Index].offs; +end; + +function TSynPluginMultiCaretList.GetCaretOffs(Index: Integer): Integer; +begin + Index := Index + FLowIndex; + assert((Index>=FLowIndex) and (Index <= FHighIndex), 'TSynPluginMultiCaretList.GetCaretX: (Index>=FLowIndex) and (Index <= FHighIndex)'); + Result := FCarets[Index].offs; +end; + function TSynPluginMultiCaretList.GetCaretX(Index: Integer): Integer; begin Index := Index + FLowIndex; @@ -550,6 +595,27 @@ begin AdjustAfterChange(Index); end; +procedure TSynPluginMultiCaretList.SetCaretFull(Index: Integer; AValue: TLogCaretPoint); +begin + Index := Index + FLowIndex; + assert((Index>=FLowIndex) and (Index <= FHighIndex), 'TSynPluginMultiCaretList.SetCaretX: (Index>=FLowIndex) and (Index <= FHighIndex)'); + if (FCarets[Index].x = AValue.x) and (FCarets[Index].y = AValue.y) and (FCarets[Index].offs = AValue.Offs) then + exit; + FCarets[Index].x := AValue.X; + FCarets[Index].y := AValue.Y; + FCarets[Index].offs := AValue.Offs; + AdjustAfterChange(Index); +end; + +procedure TSynPluginMultiCaretList.SetCaretOffs(Index: Integer; AValue: Integer); +begin + Index := Index + FLowIndex; + assert((Index>=FLowIndex) and (Index <= FHighIndex), 'TSynPluginMultiCaretList.SetCaretX: (Index>=FLowIndex) and (Index <= FHighIndex)'); + if FCarets[Index].offs = AValue then exit; + FCarets[Index].offs := AValue; + AdjustAfterChange(Index); +end; + procedure TSynPluginMultiCaretList.SetCaretX(Index: Integer; AValue: Integer); begin Index := Index + FLowIndex; @@ -638,7 +704,8 @@ begin NewIdx := RawIndex-1; if (y = FCarets[NewIdx].y) and (x = FCarets[NewIdx].x) then begin - InternalRemoveCaretEx(RawIndex, NewIdx); + if FMergeLock = 0 then + InternalRemoveCaretEx(RawIndex, NewIdx); exit; end; v := FCarets[RawIndex]; @@ -660,7 +727,8 @@ debugln(SynMCaretDebug, ['TSynPluginMultiCaretList.AdjustAfterChange ', NewIdx, NewIdx := RawIndex+1; if (y = FCarets[NewIdx].y) and (x = FCarets[NewIdx].x) then begin - InternalRemoveCaretEx(RawIndex, NewIdx); + if FMergeLock = 0 then + InternalRemoveCaretEx(RawIndex, NewIdx); exit; end; v := FCarets[RawIndex]; @@ -679,16 +747,16 @@ begin FMainCaretIndex := -1; end; -function TSynPluginMultiCaretList.AddCaret(X, Y: Integer; flags: TCaretFlags): Integer; +function TSynPluginMultiCaretList.AddCaret(X, Y, Offs: Integer; flags: TCaretFlags): Integer; var NewCarets: Array of TCaretData; Len, AddLen, i, Middle: Integer; begin - Result := FindEqOrNextCaretRawIdx(x, y); + Result := FindEqOrNextCaretRawIdx(x, y, Offs); if Result < FLowIndex then Result := FLowIndex; - if ((Result <= FHighIndex) and (FCarets[Result].x = x) and (FCarets[Result].y = y)) and - not(cfAddDuplicate in flags) + if (Result <= FHighIndex) and (FCarets[Result].x = x) and (FCarets[Result].y = y) and + (FCarets[Result].offs = Offs) and not(cfAddDuplicate in flags) then begin FCarets[Result].Flags := flags - [cfMainCaret]; if cfMainCaret in flags then @@ -750,6 +818,7 @@ begin end; FCarets[Result].x := x; + FCarets[Result].offs := Offs; FCarets[Result].y := y; FCarets[Result].Visual := nil; FCarets[Result].Flags := flags - [cfMainCaret, cfAddDuplicate]; @@ -792,10 +861,24 @@ begin Result := FHighIndex - FLowIndex + 1; end; -function TSynPluginMultiCaretList.FindCaretIdx(X, Y: Integer): Integer; +function TSynPluginMultiCaretList.FindCaretIdx(X, Y, Offs: Integer): Integer; begin - Result := FindEqOrNextCaretRawIdx(x, y); - if (Result > FHighIndex) or (FCarets[Result].x <> x) or (FCarets[Result].y <> y) + Result := FindEqOrNextCaretRawIdx(x, y, offs); + if (Result > FHighIndex) or (FCarets[Result].x <> x) or (FCarets[Result].offs <> Offs) or + (FCarets[Result].y <> y) + then + Result := -1 + else + Result := Result - FLowIndex; +end; + +function TSynPluginMultiCaretList.FindEqOrNextCaretIdx(X, Y, Offs: Integer; LowIdx: integer; + HighIdx: integer): Integer; +begin + if LowIdx >= 0 then inc(LowIdx, FLowIndex); + if HighIdx >= 0 then inc(HighIdx, FLowIndex); + Result := FindEqOrNextCaretRawIdx(x, y, offs, LowIdx, HighIdx); + if (Result > FHighIndex) then Result := -1 else @@ -808,7 +891,7 @@ var i, j, lowest: Integer; begin if Count = 0 then exit; - lowest := FindEqOrNextCaretRawIdx(aBytePos, aLinePos); + lowest := FindEqOrNextCaretRawIdx(aBytePos, aLinePos, 0); if aLineBrkCnt = 0 then begin if aCount < 0 then begin @@ -903,6 +986,16 @@ begin end; end; +procedure TSynPluginMultiCaretList.IncMergeLock; +begin + inc(FMergeLock); +end; + +procedure TSynPluginMultiCaretList.DecMergeLock; +begin + dec(FMergeLock); +end; + { TSynPluginMultiCaretBase } procedure TSynPluginMultiCaretBase.DoBoundsChanged(Sender: TObject); @@ -931,7 +1024,7 @@ begin end; Carets.FindAndRemoveMergedCarets; - i := Carets.FindCaretIdx(CaretObj.BytePos, CaretObj.LinePos); + i := Carets.FindCaretIdx(CaretObj.BytePos, CaretObj.LinePos, CaretObj.BytePosOffset); if i >= 0 then Carets.RemoveCaret(i); end; @@ -1113,11 +1206,11 @@ begin Result := TLazSynSurfaceManager(PaintArea).TextArea; end; -function TSynPluginMultiCaretBase.AddCaret(X, Y: Integer; flags: TCaretFlags): Integer; +function TSynPluginMultiCaretBase.AddCaret(X, Y, Offs: Integer; flags: TCaretFlags): Integer; var y1, y2: Integer; begin - Result := Carets.AddCaret(x,y, flags); + Result := Carets.AddCaret(x,y, Offs, flags); if cfNoneVisual in flags then exit; @@ -1137,7 +1230,7 @@ begin if (y > 0) and (y1 <> y2) or (y=1) then begin if Carets.Visual[Result] = nil then Carets.Visual[Result] := GetVisual; - x := Editor.LogicalToPhysicalPos(Point(x, y)).x; + 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; end @@ -1152,7 +1245,7 @@ end; procedure TSynPluginMultiCaretBase.UpdateCaretsPos; var - i, x, y, w: Integer; + i, x, y, o, w: Integer; y1, y2: Integer; begin if plfDeferUpdateCaretsPos in FPaintLockFlags then exit; @@ -1174,6 +1267,7 @@ begin x := Carets.CaretX[i]; y := Carets.CaretY[i]; + o := Carets.CaretOffs[i]; y1 := Editor.RowToScreenRow(y); if (y1 < 0) or (y1 > w) then begin Carets.Visual[i] := nil; @@ -1186,9 +1280,13 @@ begin if (y1 <> y2) or (y=1) then begin if Carets.Visual[i] = nil then Carets.Visual[i] := GetVisual; - x := Editor.LogicalToPhysicalPos(Point(x, y)).x; + x := ViewedTextBuffer.LogPhysConvertor.LogicalToPhysical(ToIdx(y), x, o); Carets.Visual[i].DisplayPos := TextArea.RowColumnToPixels(Point(x, y1)); Carets.Visual[i].Visible := True; +//todo: remove if duplicate + // check if offs was adjusted + //if o <> Carets.CaretOffs[i] then + // Carets.CaretOffs[i] := o; end else Carets.Visual[i] := nil; @@ -1395,10 +1493,33 @@ procedure TSynCustomPluginMultiCaret.SetSkipCaretAtSel; begin Include(FStateFlags, sfSkipCaretsAtSelection); FSelY1 := SelectionObj.FirstLineBytePos.y; - FSElY2 := SelectionObj.LastLineBytePos.y; + FSelY2 := SelectionObj.LastLineBytePos.y; FSelX := SelectionObj.FirstLineBytePos.x; end; +function TSynCustomPluginMultiCaret.LogPhysConvertor: TSynLogicalPhysicalConvertor; +begin + Result := ViewedTextBuffer.LogPhysConvertor; +end; + +function TSynCustomPluginMultiCaret.PhysicalToLogical(AIndex, AColumn: Integer; out + AColOffset: Integer; ACharSide: TSynPhysCharSide; AFlags: TSynLogPhysFlags): Integer; +var + s: String; +begin + Result := LogPhysConvertor.PhysicalToLogical(AIndex, AColumn, AColOffset, ACharSide, AFlags); + if (AColOffset > 0) then begin + if (eoCaretSkipTab in Editor.Options2) then + AColOffset := 0 + else + begin + s := ViewedTextBuffer[AIndex]; + if (Result > Length(s)) or (s[Result] <> #9) then + AColOffset := 0; + end; + end; +end; + procedure TSynCustomPluginMultiCaret.DoEditorRemoving(AValue: TCustomSynEdit); begin if Editor <> nil then begin @@ -1436,7 +1557,7 @@ begin UpdateCaretsPos; inherited DoAfterDecPaintLock(Sender); - FStateFlags := FStateFlags - [sfExtendingColumnSel]; + Exclude(FStateFlags, sfExtendingColumnSel); end; procedure TSynCustomPluginMultiCaret.DoCleared; @@ -1444,6 +1565,9 @@ begin inherited DoCleared; FActiveMode := mcmNoCarets; Exclude(FStateFlags, sfCreateCaretAtCurrentPos); + FColSelDoneY1 := -1; + FColSelDoneY2 := -2; + FColSelDonePhysX := -1; end; procedure TSynCustomPluginMultiCaret.DoLinesEdited(Sender: TSynEditStrings; aLinePos, @@ -1454,9 +1578,12 @@ begin end; procedure TSynCustomPluginMultiCaret.DoCaretChanged(Sender: TObject); +var + p: TLogCaretPoint; begin if (sfCreateCaretAtCurrentPos in FStateFlags) then begin - AddCaret(CaretObj.OldLineBytePos.x, CaretObj.OldLinePos); + p := CaretObj.OldFullLogicalPos; + AddCaret(p.x, p.y, p.Offs); exclude(FStateFlags, sfCreateCaretAtCurrentPos); exit; end; @@ -1469,35 +1596,129 @@ begin end; procedure TSynCustomPluginMultiCaret.DoSelectionChanged(Sender: TObject); + procedure AddCarets(StartY, EndY, PhysX: Integer); + var + i, XLog, Offs: Integer; + CurCar: TLogCaretPoint; + begin + i:= -1; + CurCar.Y := -1; + while StartY <= EndY do begin + XLog := PhysicalToLogical(ToIdx(StartY), PhysX, Offs); + if StartY >= CurCar.Y then begin + i := Carets.FindEqOrNextCaretIdx(XLog, StartY, Offs, i+1); + if i >= 0 then + CurCar := Carets.CaretFull[i]; + end; + if (CurCar.x <> XLog) or (CurCar.Offs <> Offs) or (CurCar.y <> StartY) then + AddCaret(XLog, StartY, Offs); // TODO: pass "i-1" as KnowIndexOfCaretBefore (limit bin search) + inc(StartY); + end; + end; + procedure RemoveCarets(StartY, EndY, PhysX: Integer); + var + i, XLog, Offs: Integer; + begin + XLog := PhysicalToLogical(ToIdx(StartY), PhysX, Offs); + i := Carets.FindEqOrNextCaretIdx(XLog, StartY, Offs, i); + if i >= 0 then begin + while Carets.CaretY[i] <= EndY do begin + if (Carets.CaretX[i] = XLog) and (Carets.CaretOffs[i] = Offs) then + Carets.RemoveCaret(i) + else + inc(i); + if i >= CaretsCount then + break; + if StartY <> Carets.CaretY[i] then begin + StartY := Carets.CaretY[i]; + XLog := PhysicalToLogical(ToIdx(StartY), PhysX, Offs); + end; + end; + end; + end; var - i, x, y1, y2, y3: Integer; - c: TPoint; + i: Integer; + XPhys, XLog, Offs: Integer; + SelFirstY, SelLastY, CurY: Integer; + CurCaret: TLogCaretPoint; begin if (sfProcessingCmd in FStateFlags) then exit; - y1 := Editor.BlockBegin.y; - y2 := Editor.BlockEnd.y; - If not ((y1 <> y2) and (Editor.SelectionMode = smColumn) and EnableWithColumnSelection) then begin + SelFirstY := Editor.BlockBegin.y; + SelLastY := Editor.BlockEnd.y; + If not ((SelFirstY <> SelLastY) and (Editor.SelectionMode = smColumn) and EnableWithColumnSelection) then begin ClearCarets; exit; end; - x := Editor.LogicalCaretXY.x; - y3 := Editor.CaretY; - i := CaretsCount; - while i > 0 do begin - dec(i); - c := Carets.Caret[i]; - if (c.x <> x) or - (c.y < y1) or (c.y > y2) or (c.y = y3) - then - RemoveCaret(i); + + Include(FStateFlags, sfExtendingColumnSel); + if SelFirstY = CaretObj.LinePos then inc(SelFirstY); + if SelLastY = CaretObj.LinePos then dec(SelLastY); + + if (FColSelDoneY2 >= FColSelDoneY1) then begin + // Delete carets at top, that are no longer in selection + if SelFirstY > FColSelDoneY1 then begin + RemoveCarets(FColSelDoneY1, SelFirstY - 1, FColSelDonePhysX); + FColSelDoneY1 := SelFirstY; + end; + // Delete carets at bottom, that are no longer in selection + if SelLastY < FColSelDoneY2 then begin + RemoveCarets(SelLastY + 1, FColSelDoneY2, FColSelDonePhysX); + FColSelDoneY2 := SelLastY; + end; end; - for i := y1 to y2 do begin - if i= y3 then continue; - AddCaret(x, i); + XPhys := Editor.CaretX; + if (FColSelDoneY2 >= FColSelDoneY1) and (XPhys <> FColSelDonePhysX) then begin + // Move carets X + CurY := FColSelDoneY1; + XLog := PhysicalToLogical(ToIdx(CurY), FColSelDonePhysX, Offs); + i := Carets.FindEqOrNextCaretIdx(XLog, CurY, Offs); + if i >= 0 then begin + while True do begin + CurCaret := Carets.CaretFull[i]; + if CurCaret.Y > FColSelDoneY2 then + break; + if (CurCaret.X = XLog) and (CurCaret.Offs = Offs) then begin + CurCaret.X := PhysicalToLogical(ToIdx(CurCaret.Y), XPhys, CurCaret.Offs); + Carets.CaretFull[i] := CurCaret; + end; + inc(i); + if i >= CaretsCount then + break; + if CurY <> Carets.CaretY[i] then begin + CurY := Carets.CaretY[i]; + XLog := PhysicalToLogical(ToIdx(CurY), FColSelDonePhysX, Offs); + end; + end; + end; + FColSelDonePhysX := XPhys; end; + if (FColSelDoneY2 < FColSelDoneY1) then begin + // New Selection + AddCarets(SelFirstY, SelLastY, XPhys); + FColSelDoneY1 := SelFirstY; + FColSelDoneY2 := SelLastY; + FColSelDonePhysX := XPhys; + end + else + begin + // Extend + if SelFirstY < FColSelDoneY1 then begin + AddCarets(SelFirstY, FColSelDoneY1 - 1, FColSelDonePhysX); + FColSelDoneY1 := SelFirstY; + end; + if SelLastY > FColSelDoneY2 then begin + AddCarets(FColSelDoneY2 + 1, SelLastY, FColSelDonePhysX); + FColSelDoneY2 := SelLastY; + end; + end; + + i := Carets.FindCaretIdx(CaretObj.BytePos, CaretObj.LinePos, CaretObj.BytePosOffset); + if i >= 0 then + Carets.RemoveCaret(i); + if FActiveMode = mcmNoCarets then FActiveMode := DefaultColumnSelectMode; end; @@ -1529,19 +1750,19 @@ begin Handled := True; case Command of ecPluginMultiCaretSetCaret: begin - if Carets.FindCaretIdx(CaretObj.BytePos, CaretObj.LinePos) < 0 then + if Carets.FindCaretIdx(CaretObj.BytePos, CaretObj.LinePos, CaretObj.BytePosOffset) < 0 then include(FStateFlags, sfCreateCaretAtCurrentPos); FActiveMode := mcmAddingCarets; end; ecPluginMultiCaretUnsetCaret: begin exclude(FStateFlags, sfCreateCaretAtCurrentPos); - i := Carets.FindCaretIdx(CaretObj.BytePos, CaretObj.LinePos); + i := Carets.FindCaretIdx(CaretObj.BytePos, CaretObj.LinePos, CaretObj.BytePosOffset); if i >= 0 then RemoveCaret(i); FActiveMode := mcmAddingCarets; end; ecPluginMultiCaretToggleCaret: begin - i := Carets.FindCaretIdx(CaretObj.BytePos, CaretObj.LinePos); + i := Carets.FindCaretIdx(CaretObj.BytePos, CaretObj.LinePos, CaretObj.BytePosOffset); if (i > 0) or (sfCreateCaretAtCurrentPos in FStateFlags) then begin exclude(FStateFlags, sfCreateCaretAtCurrentPos); if i >= 0 then @@ -1552,7 +1773,11 @@ begin end; FActiveMode := mcmAddingCarets; end; - ecPluginMultiCaretClearAll: ClearCarets; + ecPluginMultiCaretClearAll: begin + ClearCarets; + if not SelectionObj.SelAvail then + SelectionObj.Clear; // clear invisibel selection + end; ecPluginMultiCaretModeCancelOnMove: FActiveMode := mcmCancelOnCaretMove; ecPluginMultiCaretModeMoveAll: FActiveMode := mcmMoveAllCarets; @@ -1573,7 +1798,7 @@ procedure TSynCustomPluginMultiCaret.ProcessAllSynCommand(Sender: TObject; After Handled := True; Editor.BeginUpdate(True); try - c := AddCaret(Editor.LogicalCaretXY.x, Editor.CaretY, [cfMainCaret, cfNoneVisual {, cfAddDuplicate}]); + c := AddCaret(Editor.LogicalCaretXY.x, Editor.CaretY, CaretObj.BytePosOffset, [cfMainCaret, cfNoneVisual {, cfAddDuplicate}]); // Execute Command at current caret pos Include(FStateFlags, sfProcessingMain); @@ -1587,13 +1812,13 @@ procedure TSynCustomPluginMultiCaret.ProcessAllSynCommand(Sender: TObject; After // Repeat command CaretObj.IncForcePastEOL; i := CaretsCount; - y := FSElY2; + y := FSelY2; while i > 0 do begin dec(i); if i = c then continue; p := Carets.Caret[i]; if y > p.y then y := p.y; - if (sfSkipCaretsAtSelection in FStateFlags) and (y >= FSElY1) and + if (sfSkipCaretsAtSelection in FStateFlags) and (y >= FSelY1) and (y = p.y) and (FSelX = p.x) then begin dec(y); @@ -1605,7 +1830,7 @@ procedure TSynCustomPluginMultiCaret.ProcessAllSynCommand(Sender: TObject; After CaretObj.DecForcePastEOL; if Carets.MainCaretIndex >= 0 then begin - Editor.LogicalCaretXY := Carets.Caret[Carets.MainCaretIndex]; + CaretObj.FullLogicalPos := Carets.CaretFull[Carets.MainCaretIndex]; RemoveCaret(Carets.MainCaretIndex); end else @@ -1617,31 +1842,51 @@ procedure TSynCustomPluginMultiCaret.ProcessAllSynCommand(Sender: TObject; After procedure ExecCaretMoveRepeated; var - i: Integer; - c, p: TPoint; + i,j: Integer; + c: TLogCaretPoint; begin Handled := True; Editor.BeginUpdate(True); + FCarets.IncMergeLock; try // Execute Command at current caret pos Include(FStateFlags, sfProcessingMain); Editor.CommandProcessor(Command, AChar, data, [hcfInit, hcfFinish]); - c := CaretObj.LineCharPos; + c := CaretObj.FullLogicalPos; Exclude(FStateFlags, sfProcessingMain); // Repeat command - // TODO left and up mavement loop 0 to count - i := CaretsCount; - while i > 0 do begin - dec(i); - p := Carets.Caret[i]; - Editor.LogicalCaretXY := p; - Editor.CommandProcessor(Command, AChar, nil, [hcfInit, hcfFinish]); - Carets.SetCaret(i, Editor.LogicalCaretXY); + // TODO lock sorting, and sort at end only + // TODO: ecPageTop ecLineTextStart moves both dir. + 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]; + Editor.CommandProcessor(Command, AChar, nil, [hcfInit, hcfFinish]); + Carets.CaretFull[i] := CaretObj.FullLogicalPos; + inc(i); + end; + end; + ecEditorTop, ecEditorBottom: ClearCarets; + else + begin + i := CaretsCount; + while i > 0 do begin + dec(i); + CaretObj.FullLogicalPos := Carets.CaretFull[i]; + Editor.CommandProcessor(Command, AChar, nil, [hcfInit, hcfFinish]); + Carets.CaretFull[i] := CaretObj.FullLogicalPos; + end; + end; end; finally - CaretObj.LineCharPos := c; + FCarets.DecMergeLock; + CaretObj.FullLogicalPos := c; MergeAndRemoveCarets; Editor.EndUpdate; end; @@ -1654,10 +1899,6 @@ procedure TSynCustomPluginMultiCaret.ProcessAllSynCommand(Sender: TObject; After FActiveMode := DefaultMode; end; - procedure HandleNewColSelection; - begin - end; - var ClipHelper: TSynClipboardStream; begin @@ -1665,8 +1906,9 @@ begin if (sfProcessingCmd in FStateFlags) or (CaretsCount = 0) then exit; if AfterProcessing then begin - if sfExtendingColumnSel in FStateFlags then - HandleNewColSelection; + //if sfExtendingColumnSel in FStateFlags then + // HandleNewColSelection; + Exclude(FStateFlags, sfExtendingColumnSel); UpdateCaretsPos; exit; end; @@ -1725,7 +1967,7 @@ begin // no indent for column mode, when multicaret Editor.BeginUpdate(True); try - AddCaret(Editor.LogicalCaretXY.x, Editor.CaretY, [cfMainCaret, cfNoneVisual, cfAddDuplicate]); + AddCaret(Editor.LogicalCaretXY.x, Editor.CaretY, CaretObj.BytePosOffset, [cfMainCaret, cfNoneVisual, cfAddDuplicate]); Editor.SelText := ''; if Carets.MainCaretIndex >= 0 then begin Editor.LogicalCaretXY := Carets.Caret[Carets.MainCaretIndex]; @@ -1792,15 +2034,18 @@ begin if AnAction.Command = emcPluginMultiCaretToggleCaret then begin Result := True; - i := Carets.FindCaretIdx(AnInfo.NewCaret.BytePos, AnInfo.NewCaret.LinePos); + i := Carets.FindCaretIdx(AnInfo.NewCaret.BytePos, AnInfo.NewCaret.LinePos, AnInfo.NewCaret.BytePosOffset); if i >= 0 then RemoveCaret(i) else if (AnInfo.NewCaret.BytePos <> CaretObj.BytePos) or (AnInfo.NewCaret.LinePos <> CaretObj.LinePos) then begin - AddCaret(AnInfo.NewCaret.BytePos, AnInfo.NewCaret.LinePos); - if FActiveMode = mcmNoCarets then - FActiveMode := DefaultMode; + AddCaret(AnInfo.NewCaret.BytePos, AnInfo.NewCaret.LinePos, AnInfo.NewCaret.BytePosOffset); end; + if CaretsCount > 0 then + FActiveMode := DefaultMode + else + FActiveMode := mcmNoCarets; + exclude(FStateFlags, sfCreateCaretAtCurrentPos); end; end; @@ -1831,9 +2076,9 @@ begin FreeAndNil(FKeyStrokes); end; -procedure TSynCustomPluginMultiCaret.AddCaretAt(X, Y: Integer); +procedure TSynCustomPluginMultiCaret.AddCaretAtLogPos(X, Y, Offs: Integer); begin - AddCaret(x, y); + AddCaret(x, y, Offs); if FActiveMode = mcmNoCarets then FActiveMode := FDefaultMode; end; diff --git a/components/synedit/test/testbase.pas b/components/synedit/test/testbase.pas index 52ec241797..f9202db561 100644 --- a/components/synedit/test/testbase.pas +++ b/components/synedit/test/testbase.pas @@ -103,6 +103,7 @@ type procedure TearDown; override; public procedure TestIsCaret(Name: String; X, Y: Integer); // logical caret + procedure TestIsCaret(Name: String; X, Y, Offs: Integer); // logical caret procedure TestIsCaretPhys(Name: String; X, Y: Integer); procedure TestIsCaretAndSel(Name: String; LogX1, LogY1, LogX2, LogY2: Integer); // logical caret procedure TestIsCaretAndSelBackward(Name: String; LogX1, LogY1, LogX2, LogY2: Integer); // logical caret @@ -124,6 +125,10 @@ type procedure TestIsCaretLogAndFullText(Name: String; X, Y: Integer; Text: String); // logical caret procedure TestIsCaretLogAndFullText(Name: String; X, Y: Integer; Lines: Array of String); // logical caret procedure TestIsCaretLogAndFullText(Name: String; X, Y: Integer; Lines: Array of String; Repl: Array of const); // logical caret + + procedure TestIsCaretLogAndFullText(Name: String; X, Y, Offs: Integer; Text: String); // logical caret + procedure TestIsCaretLogAndFullText(Name: String; X, Y, Offs: Integer; Lines: Array of String); // logical caret + procedure TestIsCaretLogAndFullText(Name: String; X, Y, Offs: Integer; Lines: Array of String; Repl: Array of const); // logical caret end; function MyDbg(t: String): String; @@ -272,6 +277,16 @@ begin Format('X/Y=(%d, %d)', [SynEdit.LogicalCaretXY.X, SynEdit.LogicalCaretXY.Y])); end; +procedure TTestBase.TestIsCaret(Name: String; X, Y, Offs: Integer); +begin + if (SynEdit.LogicalCaretXY.X <> X) or (SynEdit.LogicalCaretXY.Y <> Y) or + (SynEdit.CaretObj.BytePosOffset <> Offs) + then + TestFail(Name, 'IsCaret', + Format('X/Y=(%d, %d, %d)', [X, Y, Offs]), + Format('X/Y=(%d, %d, %d)', [SynEdit.LogicalCaretXY.X, SynEdit.LogicalCaretXY.Y, SynEdit.CaretObj.BytePosOffset])); +end; + procedure TTestBase.TestIsCaretPhys(Name: String; X, Y: Integer); begin if (SynEdit.CaretXY.X <> X) or (SynEdit.CaretXY.Y <> Y) then @@ -423,6 +438,26 @@ begin TestIsFullText(Name, Lines, Repl); end; +procedure TTestBase.TestIsCaretLogAndFullText(Name: String; X, Y, Offs: Integer; Text: String); +begin + TestIsCaret(Name, X, Y, Offs); + TestIsFullText(Name, Text); +end; + +procedure TTestBase.TestIsCaretLogAndFullText(Name: String; X, Y, Offs: Integer; + Lines: array of String); +begin + TestIsCaret(Name, X, Y, Offs); + TestIsFullText(Name, Lines); +end; + +procedure TTestBase.TestIsCaretLogAndFullText(Name: String; X, Y, Offs: Integer; + Lines: array of String; Repl: array of const); +begin + TestIsCaret(Name, X, Y, Offs); + TestIsFullText(Name, Lines, Repl); +end; + procedure TTestBase.TestFail(Name, Func, Expect, Got: String; Result: Boolean = False); begin if Result then exit; diff --git a/components/synedit/test/testmulticaret.pas b/components/synedit/test/testmulticaret.pas index 01b8f30f6b..bf510f340c 100644 --- a/components/synedit/test/testmulticaret.pas +++ b/components/synedit/test/testmulticaret.pas @@ -5,7 +5,7 @@ unit TestMultiCaret; interface uses - Classes, SysUtils, TestBase, SynEditKeyCmds, SynPluginMultiCaret, SynEdit, Clipbrd, + Classes, SysUtils, TestBase, SynEditKeyCmds, SynPluginMultiCaret, SynEdit, Clipbrd, Forms, testregistry; type @@ -20,14 +20,81 @@ type TTestMultiCaret = class(TTestBase) protected FMultiCaret: TSynPluginMultiCaretTest; + FOptAdd, FOptRemove: TSynEditorOptions; + FOpt2Add, FOpt2Remove: TSynEditorOptions2; + FEnableWithColumnSelection: Boolean; + FDefaultMode: TSynPluginMultiCaretDefaultMode; + FDefaultColumnSelectMode: TSynPluginMultiCaretDefaultMode; + + procedure SetUp; override; + + procedure SetCaretAndColumnSelect(X, Y, Down, Right: Integer); + procedure SetCaretsByKey(X, Y: Integer; CaretMoves: Array of Integer; EndMode: TSynPluginMultiCaretMode = mcmAddingCarets); // [ {SET}, Right,DOwn, {SET}, Right,DOwn,..] + procedure SetCaretsByKey(CaretMoves: Array of Integer; EndMode: TSynPluginMultiCaretMode = mcmAddingCarets); // [ {SET}, Right,DOwn, {SET}, Right,DOwn,..] + + procedure TestExtraCaretCount(AName: String; ExpCount: Integer); + 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 RunAndTest(AName: String; + cmds: Array of TSynEditorCommand; chars: array of String; + X, Y: Integer; ExpLines: Array of String + ); + procedure RunAndTest(AName: String; + cmds: Array of TSynEditorCommand; // no chars + X, Y: Integer; ExpLines: Array of String + ); + procedure RunAndTest(AName: String; + cmds: Array of TSynEditorCommand; chars: array of String; + X, Y, Offs: Integer; ExpLines: Array of String + ); + procedure RunAndTest(AName: String; + cmds: Array of TSynEditorCommand; // no chars + X, Y, Offs: Integer; ExpLines: Array of String + ); + + + procedure RunAndTest(AName: String; + cmds: Array of TSynEditorCommand; chars: array of String; + X, Y: Integer; ExpLines: Array of String; + ExpCount: Integer; ExpPos: array of integer // x, [offs,] y + ); + procedure RunAndTest(AName: String; + cmds: Array of TSynEditorCommand; // no chars + X, Y: Integer; ExpLines: Array of String; + ExpCount: Integer; ExpPos: array of integer // x, [offs,] y + ); + procedure RunAndTest(AName: String; + cmds: Array of TSynEditorCommand; chars: array of String; + X, Y, Offs: Integer; ExpLines: Array of String; + ExpCount: Integer; ExpPos: array of integer // x, [offs,] y + ); + procedure RunAndTest(AName: String; + cmds: Array of TSynEditorCommand; // no chars + X, Y, Offs: Integer; ExpLines: Array of String; + ExpCount: Integer; ExpPos: array of integer // x, [offs,] y + ); + + // y1/2 1-based + function DelCol(Lines: Array of String; y1,y2,X: Integer; Cnt: Integer = 1): TStringArray; + // ADel: y,x,cnt, y,x,cnt, .... 1-based + function DelCol(Lines: Array of String; ADel: Array of Integer): TStringArray; + + function TestText1: TStringArray; + function TestText1(DelY1, DelY2, DelX: Integer; DelCnt: Integer = 1): TStringArray; + function TestText1(ADel: Array of Integer): TStringArray; + function TestText2: TStringArray; + function TestText2(DelY1, DelY2, DelX: Integer; DelCnt: Integer = 1): TStringArray; + function TestText2(ADel: Array of Integer): TStringArray; public procedure ReCreateEdit; reintroduce; - procedure ReCreateEdit(ALines: TStringArray; AOpt: TSynEditorOptions2 = []; - AOptRemove: TSynEditorOptions2 = []); + procedure ReCreateEdit(ALines: TStringArray); procedure RunCmdSeq(cmds: Array of TSynEditorCommand; chars: array of String); published procedure CaretList; + procedure ColumnSelect; + procedure CursorMove; procedure Edit; procedure Delete; procedure ReplaceColSel; @@ -39,6 +106,19 @@ implementation { TTestMultiCaret } +procedure TTestMultiCaret.SetUp; +begin + FOptAdd := []; + FOptRemove := []; + FOpt2Add := []; + FOpt2Remove := []; + FEnableWithColumnSelection := True; + FDefaultMode := mcmMoveAllCarets; + FDefaultColumnSelectMode := mcmCancelOnCaretMove; + + inherited SetUp; +end; + procedure TTestMultiCaret.SetCaretAndColumnSelect(X, Y, Down, Right: Integer); var i: Integer; @@ -61,20 +141,232 @@ begin RunCmdSeq([ecColSelLeft], []); end; +procedure TTestMultiCaret.SetCaretsByKey(X, Y: Integer; CaretMoves: array of Integer; + EndMode: TSynPluginMultiCaretMode); +begin + SetCaret(X, Y); + SetCaretsByKey(CaretMoves, EndMode); +end; + +procedure TTestMultiCaret.SetCaretsByKey(CaretMoves: array of Integer; + EndMode: TSynPluginMultiCaretMode); +var + i, j: Integer; +begin + for i := 0 to (Length(CaretMoves) div 2) - 1 do begin + RunCmdSeq([ecPluginMultiCaretSetCaret], []); + if CaretMoves[i*2+1] > 0 then + for j := 1 to CaretMoves[i*2+1] do + RunCmdSeq([ecDown], []) + else + if CaretMoves[i*2+1] < 0 then + for j := 1 to -CaretMoves[i*2+1] do + RunCmdSeq([ecUp], []); + + if CaretMoves[i*2+0] > 0 then + for j := 1 to CaretMoves[i*2+0] do + RunCmdSeq([ecRight], []) + else + if CaretMoves[i*2+0] < 0 then + for j := 1 to -CaretMoves[i*2+0] do + RunCmdSeq([ecLeft], []); + end; + FMultiCaret.ActiveMode := EndMode; +end; + +procedure TTestMultiCaret.TestExtraCaretCount(AName: String; ExpCount: Integer); +begin + AssertEquals(BaseTestName+' '+AName + ' extra count', ExpCount, FMultiCaret.Carets.Count); +end; + +procedure TTestMultiCaret.TestExtraCaretPos(AName: String; ExpCount: Integer; + ExpPos: array of integer); +var + i: Integer; +begin + AssertEquals(BaseTestName+' '+AName + ' extra count', ExpCount, FMultiCaret.Carets.Count); + AssertEquals(BaseTestName+' '+AName + 'selftest',length(ExpPos), ExpCount*2); + for i := 0 to ExpCount - 1 do begin + AssertEquals(BaseTestName+' '+AName + ' extra pos x', ExpPos[i*2+0], FMultiCaret.Carets.CaretX[i]); + AssertEquals(BaseTestName+' '+AName + ' extra pos y', ExpPos[i*2+1], FMultiCaret.Carets.CaretY[i]); + end +end; + +procedure TTestMultiCaret.TestExtraCaretPosAndOffs(AName: String; ExpCount: Integer; + ExpPos: array of integer); +var + i: Integer; +begin + AssertEquals(BaseTestName+' '+AName + ' extra count', ExpCount, FMultiCaret.Carets.Count); + AssertEquals(BaseTestName+' '+AName + 'selftest',length(ExpPos), ExpCount*3); + for i := 0 to ExpCount - 1 do begin + AssertEquals(BaseTestName+' '+AName + ' extra pos x', ExpPos[i*3+0], FMultiCaret.Carets.CaretX[i]); + AssertEquals(BaseTestName+' '+AName + ' extra pos y', ExpPos[i*3+1], FMultiCaret.Carets.CaretY[i]); + AssertEquals(BaseTestName+' '+AName + ' extra pos O', ExpPos[i*3+2], FMultiCaret.Carets.CaretOffs[i]); + end +end; + +procedure TTestMultiCaret.RunAndTest(AName: String; cmds: array of TSynEditorCommand; + chars: array of String; X, Y: Integer; ExpLines: array of String); +begin + RunCmdSeq(cmds, chars); + TestIsCaretLogAndFullText(AName, X, Y, ExpLines); +end; + +procedure TTestMultiCaret.RunAndTest(AName: String; cmds: array of TSynEditorCommand; X, + Y: Integer; ExpLines: array of String); +begin + RunAndTest(AName, cmds, [], X, Y, ExpLines); +end; + +procedure TTestMultiCaret.RunAndTest(AName: String; cmds: array of TSynEditorCommand; + chars: array of String; X, Y, Offs: Integer; ExpLines: array of String); +begin + RunCmdSeq(cmds, chars); + TestIsCaretLogAndFullText(AName, X, Y, Offs, ExpLines); +end; + +procedure TTestMultiCaret.RunAndTest(AName: String; cmds: array of TSynEditorCommand; X, Y, + Offs: Integer; ExpLines: array of String); +begin + RunAndTest(AName, cmds, [], X, Y, Offs, ExpLines); +end; + +procedure TTestMultiCaret.RunAndTest(AName: String; cmds: array of TSynEditorCommand; + chars: array of String; X, Y: Integer; ExpLines: array of String; ExpCount: Integer; + ExpPos: array of integer); +begin + RunAndTest(AName, cmds, chars, X, Y, 0, ExpLines, ExpCount, ExpPos); +end; + +procedure TTestMultiCaret.RunAndTest(AName: String; cmds: array of TSynEditorCommand; X, + Y: Integer; ExpLines: array of String; ExpCount: Integer; ExpPos: array of integer); +begin + RunAndTest(AName, cmds, [], X, Y, ExpLines, ExpCount, ExpPos); +end; + +procedure TTestMultiCaret.RunAndTest(AName: String; cmds: array of TSynEditorCommand; + chars: array of String; X, Y, Offs: Integer; ExpLines: array of String; ExpCount: Integer; + ExpPos: array of integer); +begin + RunAndTest(AName, cmds, chars, X, Y, Offs, ExpLines); + if length(ExpPos) = 0 then + TestExtraCaretCount(AName, ExpCount) + else + if length(ExpPos) = ExpCount * 2 then + TestExtraCaretPos(AName, ExpCount, ExpPos) + else + if length(ExpPos) = ExpCount * 3 then + TestExtraCaretPosAndOffs(AName, ExpCount, ExpPos) + else + AssertTrue(BaseTestName+' '+AName + 'selftest CaretCOUNT <> pos-array-len', false); +end; + +procedure TTestMultiCaret.RunAndTest(AName: String; cmds: array of TSynEditorCommand; X, Y, + Offs: Integer; ExpLines: array of String; ExpCount: Integer; ExpPos: array of integer); +begin + RunAndTest(AName, cmds, [], X, Y, Offs, ExpLines, ExpCount, ExpPos); +end; + +function TTestMultiCaret.DelCol(Lines: array of String; y1, y2, X: Integer; + Cnt: Integer): TStringArray; +var + i: Integer; +begin + SetLength(Result, length(Lines)); + for i := 0 to high(Lines) do + Result[i] := Lines[i]; + for i := y1-1 to y2-1 do + system.Delete(Result[i], X, Cnt); +end; + +function TTestMultiCaret.DelCol(Lines: array of String; ADel: array of Integer): TStringArray; +var + i: Integer; +begin + SetLength(Result, length(Lines)); + for i := 0 to high(Lines) do + Result[i] := Lines[i]; + for i := 0 to (length(ADel) div 3) - 1 do + system.Delete(Result[ADel[i*3+0]-1], ADel[i*3+1], ADel[i*3+2]); +end; + +function TTestMultiCaret.TestText1: TStringArray; +begin + SetLength(Result, 8); + Result[0] := '1abc def gh'; + Result[1] := '2mno pqr st'; + Result[2] := '3ABC DEF GH'; + Result[3] := '4MNO PQR ST'; + Result[4] := '5xyz klm op'; + Result[5] := '6aA bB cC dD'; + Result[6] := '7mM nN oO pP'; + Result[7] := ''; +end; + +function TTestMultiCaret.TestText1(DelY1, DelY2, DelX: Integer; DelCnt: Integer): TStringArray; +begin + Result := DelCol(TestText1(), DelY1, DelY2, DelX, DelCnt); +end; + +function TTestMultiCaret.TestText1(ADel: array of Integer): TStringArray; +begin + Result := DelCol(TestText1(), ADel); +end; + +function TTestMultiCaret.TestText2: TStringArray; +begin + SetLength(Result, 18); + Result[0] := '1abc def gh'; + Result[1] := '2mno pqr st'; + Result[2] := '1abc def gh Oo xx 99'; + Result[3] := '2äöü pqr st Oo xx 99'; + Result[4] := '3ÄÖÜ DEF GH Oo xx 99'; + Result[5] := '4MNä PQR ST Oo xx 99'; + Result[6] := '5xyÜ klm op'; + Result[7] := '6aA b'#9#9'B cC dD'; + Result[8] := '7mM n'#9#9'N oO pP'; + Result[9] := '6aA b'#9#9'B cC dD'; + Result[10] := '1abc アアウ gh Oo xx 99'; + Result[11] := '2mno pqr アウ Oo xx 99'; + Result[12] := '3アアウアアウ GH Oo xx 99'; + Result[13] := '4Mアアウアアウ ST Oo xx 99'; + Result[14] := '5xyz klm op bB cC dD'; + Result[15] := '6a'#9#9#9#9'A'; + Result[16] := '7mM nN oO pP'; + Result[17] := ''; +end; + +function TTestMultiCaret.TestText2(DelY1, DelY2, DelX: Integer; DelCnt: Integer): TStringArray; +begin + Result := DelCol(TestText2(), DelY1, DelY2, DelX, DelCnt); +end; + +function TTestMultiCaret.TestText2(ADel: array of Integer): TStringArray; +begin + Result := DelCol(TestText2(), ADel); +end; + procedure TTestMultiCaret.ReCreateEdit; begin inherited; FMultiCaret := TSynPluginMultiCaretTest.Create(SynEdit); + + SynEdit.Options := SynEdit.Options - FOptRemove + FOptAdd; + SynEdit.Options2 := SynEdit.Options2 - FOpt2Remove + FOpt2Add; + + FMultiCaret.EnableWithColumnSelection := FEnableWithColumnSelection; + FMultiCaret.DefaultMode := FDefaultMode; + FMultiCaret.DefaultColumnSelectMode := FDefaultColumnSelectMode; + SynEdit.BlockIndent := 2; SynEdit.BlockTabIndent := 0; SynEdit.TabWidth := 4; end; -procedure TTestMultiCaret.ReCreateEdit(ALines: TStringArray; AOpt: TSynEditorOptions2; - AOptRemove: TSynEditorOptions2); +procedure TTestMultiCaret.ReCreateEdit(ALines: TStringArray); begin ReCreateEdit; - SynEdit.Options2 := SynEdit.Options2 - AOptRemove + AOpt; SetLines(ALines); end; @@ -91,6 +383,7 @@ begin inc(j); end; SynEdit.CommandProcessor(cmds[i], a, nil); + Application.ProcessMessages; end; end; @@ -103,14 +396,14 @@ procedure TTestMultiCaret.CaretList; c := TSynPluginMultiCaretList.Create; for i := 0 to high(a) do begin - c.AddCaret(1,a[i]); + c.AddCaret(1,a[i],0); for j := 1 to c.Count-1 do AssertTrue(Format(name+' Test %d %d', [i, j]), c.Caret[j].y > c.Caret[j-1].y); end; c.Clear; for i := 0 to high(a) do begin - k := c.AddCaret(1,a[i]); + k := c.AddCaret(1,a[i],0); AssertEquals(Format(name+' Test %d %d', [i, j]),a[i], c.Caret[k].y); for j := 1 to c.Count-1 do AssertTrue(Format(name+' Test %d %d', [i, j]), c.Caret[j].y > c.Caret[j-1].y); @@ -118,14 +411,14 @@ procedure TTestMultiCaret.CaretList; c.Clear; for i := 0 to high(a) do begin - c.AddCaret(1,a[i]); + c.AddCaret(1,a[i],0); end; for j := 1 to c.Count-1 do AssertTrue(Format(name+' Test %d %d', [i, j]), c.Caret[j].y > c.Caret[j-1].y); c.Clear; for i := high(a) downto 0 do begin - k := c.AddCaret(1,a[i]); + k := c.AddCaret(1,a[i],0); AssertEquals(Format(name+' Test %d %d', [i, j]),a[i], c.Caret[k].y); for j := 1 to c.Count-1 do AssertTrue(Format(name+' Test %d %d', [i, j]), c.Caret[j].y > c.Caret[j-1].y); @@ -136,7 +429,7 @@ procedure TTestMultiCaret.CaretList; for n := 0 to m do begin c.Clear; for i := 0 to m do begin - k := c.AddCaret(1,a[i]); + k := c.AddCaret(1,a[i],0); AssertEquals(Format(name+' Test %d %d', [i, j]),a[i], c.Caret[k].y); end; for j := 1 to c.Count-1 do @@ -197,8 +490,150 @@ begin TestSequenceEx('1', [1,2,3,4,-1]); end; +procedure TTestMultiCaret.ColumnSelect; +begin + PushBaseName('Simple 0 width col select ep/down'); + ReCreateEdit(TestText1); + SetCaret(3,3); + RunAndTest('', [ecColSelDown], 3,4, TestText1, 1, [3,3,0]); + RunAndTest('', [ecColSelDown], 3,5, TestText1, 2, [3,3,0, 3,4,0]); + RunAndTest('', [ecColSelUp], 3,4, TestText1, 1, [3,3,0]); + RunAndTest('', [ecColSelUp], 3,3, TestText1, 0, []); + RunAndTest('', [ecColSelUp], 3,2, TestText1, 1, [3,3,0]); + RunAndTest('', [ecColSelDown], 3,3, TestText1, 0, []); + RunAndTest('', [ecColSelDown], 3,4, TestText1, 1, [3,3,0]); + RunAndTest('', [ecColSelDown], 3,5, TestText1, 2, [3,3,0, 3,4,0]); + PopPushBaseName('column sel left/right'); + RunAndTest('', [ecColSelLeft], 2,5, TestText1, 2, [2,3,0, 2,4,0]); + RunAndTest('', [ecColSelRight], 3,5, TestText1, 2, [3,3,0, 3,4,0]); + RunAndTest('', [ecColSelRight], 4,5, TestText1, 2, [4,3,0, 4,4,0]); + RunAndTest('', [ecColSelRight], 5,5, TestText1, 2, [5,3,0, 5,4,0]); + PopPushBaseName('column sel, 2 width up/down'); + RunAndTest('', [ecColSelDown], 5,6, TestText1, 3, [5,3,0, 5,4,0, 5,5,0]); + RunAndTest('', [ecColSelUp], 5,5, TestText1, 2, [5,3,0, 5,4,0]); + RunAndTest('', [ecColSelUp], 5,4, TestText1, 1, [5,3,0]); + PopBaseName; + + PushBaseName('double width char'); + FOptAdd := [eoKeepCaretX]; + ReCreateEdit(TestText2); + // X at log pos 4 / phys 4 / 3 chars before + SetCaret(4,12); + // X goes to log pos 5 / phys 4 / 2 chars => 1 double width char + RunAndTest('', [ecColSelDown], 5,13, TestText2, 1, [4,12,0]); + // X goes to log pos 3 / phys 3 / 2 chars => pushed forward by following dbl-w char + RunAndTest('', [ecColSelDown], 3,14, TestText2, 2, [3,12,0, 2,13,0]); // 2,13 is pushed forward + // X goes to log pos 4 / phys 4 => keepcaretX // 1,14 is oout of line + RunAndTest('', [ecColSelDown], 4,15, TestText2, 3, [4,12,0, 5,13,0, 3,14,0]); + + // X goes to log pos 3 / phys 3 / 2 chars => pushed forward by following dbl-w char + RunAndTest('', [ecColSelUp], 3,14, TestText2, 2, [3,12,0, 2,13,0]); + // X goes to log pos 5 / phys 4 / 2 chars => 1 double width char + RunAndTest('', [ecColSelUp], 5,13, TestText2, 1, [4,12,0]); + + // X goes to log pos 3 / phys 3 / 2 chars => pushed forward by following dbl-w char + RunAndTest('', [ecColSelDown], 3,14, TestText2, 2, [3,12,0, 2,13,0]); // 2,13 is pushed forward + +end; + +procedure TTestMultiCaret.CursorMove; +begin + PushBaseName('eoScrollPastEol, eoCaretSkipTab'); + FOptAdd := [eoScrollPastEol]; + FOptRemove := []; + FOpt2Add := [eoCaretSkipTab]; + FOpt2Remove := []; + FDefaultColumnSelectMode := mcmMoveAllCarets; + + + PushBaseName('ecUp'); + ReCreateEdit(TestText1); + SetCaretAndColumnSelect(3,3, 1,0); + RunAndTest('Height 2', [ecUp], 3,3, TestText1, 1, [3,2,0]); + + ReCreateEdit(TestText1); + SetCaretAndColumnSelect(3,3, 2,0); + RunAndTest('Height 3', [ecUp], 3,4, TestText1, 2, [3,2,0, 3,3,0]); + + ReCreateEdit(TestText1); + SetCaretsByKey(3,3, [1,0, 1,0], mcmMoveAllCarets); + RunAndTest('Width 3', [ecUp], 5,2, TestText1, 2, [3,2,0, 4,2,0]); + PopBaseName; + + PushBaseName('ecUp'); + ReCreateEdit(TestText1); + SetCaretAndColumnSelect(3,3, 1,0); + RunAndTest('Height 2', [ecDown], 3,5, TestText1, 1, [3,4,0]); + + ReCreateEdit(TestText1); + SetCaretAndColumnSelect(3,3, 2,0); + RunAndTest('Height 3', [ecDown], 3,6, TestText1, 2, [3,4,0, 3,5,0]); + + ReCreateEdit(TestText1); + SetCaretsByKey(3,3, [1,0, 1,0], mcmMoveAllCarets); + RunAndTest('Width 3', [ecDown], 5,4, TestText1, 2, [3,4,0, 4,4,0]); + PopBaseName; + + PushBaseName('ecLeft'); + ReCreateEdit(TestText1); + SetCaretAndColumnSelect(3,3, 1,0); + RunAndTest('Height 2', [ecLeft], 2,4, TestText1, 1, [2,3,0]); + + ReCreateEdit(TestText1); + SetCaretAndColumnSelect(3,3, 2,0); + RunAndTest('Height 3', [ecLeft], 2,5, TestText1, 2, [2,3,0, 2,4,0]); + + ReCreateEdit(TestText1); + SetCaretsByKey(3,3, [1,0, 1,0], mcmMoveAllCarets); + RunAndTest('Width 3', [ecLeft], 4,3, TestText1, 2, [2,3,0, 3,3,0]); + PopBaseName; + + PushBaseName('ecRight'); + ReCreateEdit(TestText1); + SetCaretAndColumnSelect(3,3, 1,0); + RunAndTest('Height 2', [ecRight], 4,4, TestText1, 1, [4,3,0]); + + ReCreateEdit(TestText1); + SetCaretAndColumnSelect(3,3, 2,0); + RunAndTest('Height 3', [ecRight], 4,5, TestText1, 2, [4,3,0, 4,4,0]); + + ReCreateEdit(TestText1); + SetCaretsByKey(3,3, [1,0, 1,0], mcmMoveAllCarets); + RunAndTest('Width 3', [ecRight], 6,3, TestText1, 2, [4,3,0, 5,3,0]); + PopBaseName; + PopBaseName; + + PushBaseName('eoScrollPastEol, NO eoCaretSkipTab - move through tab'); + FOptAdd := [eoScrollPastEol]; + FOptRemove := []; + FOpt2Add := []; + FOpt2Remove := [eoCaretSkipTab]; + FDefaultColumnSelectMode := mcmMoveAllCarets; + + PushBaseName('ecRight'); + ReCreateEdit(TestText2); // tabw=4 + SetCaretAndColumnSelect(6,8, 2,0); // before both tabs + RunAndTest('Height 3', [ecRight], 6,10,1, TestText2, 2, [6,8,1, 6,9,1]); + RunAndTest('Height 3', [ecRight], 6,10,2, TestText2, 2, [6,8,2, 6,9,2]); + RunAndTest('Height 3', [ecRight], 7,10,0, TestText2, 2, [7,8,0, 7,9,0]); + RunAndTest('Height 3', [ecRight], 7,10,1, TestText2, 2, [7,8,1, 7,9,1]); + PopBaseName; + + PushBaseName('ecLeft'); + ReCreateEdit(TestText2); // tabw=4 + SetCaretAndColumnSelect(8,8, 2,0); // after both tabs + RunAndTest('Height 3', [ecLeft], 7,10,3, TestText2, 2, [7,8,3, 7,9,3]); + RunAndTest('Height 3', [ecLeft], 7,10,2, TestText2, 2, [7,8,2, 7,9,2]); + RunAndTest('Height 3', [ecLeft], 7,10,1, TestText2, 2, [7,8,1, 7,9,1]); + RunAndTest('Height 3', [ecLeft], 7,10,0, TestText2, 2, [7,8,0, 7,9,0]); + RunAndTest('Height 3', [ecLeft], 6,10,2, TestText2, 2, [6,8,2, 6,9,2]); + PopBaseName; + PopBaseName; + +end; + procedure TTestMultiCaret.Edit; - function TestText1: TStringArray; + function LocalText1: TStringArray; begin SetLength(Result, 8); Result[0] := '1'; @@ -210,7 +645,7 @@ procedure TTestMultiCaret.Edit; Result[6] := '7'; Result[7] := ''; end; - function TestText1A: TStringArray; + function LocalText1A: TStringArray; begin SetLength(Result, 8); Result[0] := '1'; @@ -222,7 +657,7 @@ procedure TTestMultiCaret.Edit; Result[6] := '7'; Result[7] := ''; end; - function TestText1Del: TStringArray; + function LocalText1Del: TStringArray; begin SetLength(Result, 3); Result[0] := '123456'; @@ -232,26 +667,26 @@ procedure TTestMultiCaret.Edit; begin ReCreateEdit; - SetLines(TestText1); + SetLines(LocalText1); SetCaretAndColumnSelect(1,2, 4,0); - TestIsCaretLogAndFullText('', 1, 6, TestText1); + TestIsCaretLogAndFullText('', 1, 6, LocalText1); RunCmdSeq([ecChar], ['A']); - TestIsCaretLogAndFullText('', 2, 6, TestText1A); + TestIsCaretLogAndFullText('', 2, 6, LocalText1A); RunCmdSeq([ecDeleteLastChar], []); - TestIsCaretLogAndFullText('', 1, 6, TestText1); + TestIsCaretLogAndFullText('', 1, 6, LocalText1); // 4 extra carets + main caret AssertEquals(BaseTestName+'', 4, FMultiCaret.Carets.Count); RunCmdSeq([ecDeleteLastChar], []); - TestIsCaretLogAndFullText('', 6, 1, TestText1Del); + TestIsCaretLogAndFullText('', 6, 1, LocalText1Del); // 4 extra carets + main caret AssertEquals(BaseTestName+'', 4, FMultiCaret.Carets.Count); RunCmdSeq([ecDeleteLastChar], []); - TestIsCaretLogAndFullText('', 1, 1, TestText1Del, [1, '6']); + TestIsCaretLogAndFullText('', 1, 1, LocalText1Del, [1, '6']); // NO extra carets AssertEquals(BaseTestName+'', 0, FMultiCaret.Carets.Count); @@ -260,7 +695,7 @@ begin end; procedure TTestMultiCaret.Delete; - function TestText1: TStringArray; + function LocalText1: TStringArray; begin SetLength(Result, 8); Result[0] := '1aA'; @@ -272,7 +707,7 @@ procedure TTestMultiCaret.Delete; Result[6] := '7gG'; Result[7] := ''; end; - function TestText1Del: TStringArray; + function LocalText1Del: TStringArray; begin SetLength(Result, 8); Result[0] := '1aA'; @@ -284,7 +719,7 @@ procedure TTestMultiCaret.Delete; Result[6] := '7gG'; Result[7] := ''; end; - function TestText1DelAndBS: TStringArray; + function LocalText1DelAndBS: TStringArray; begin SetLength(Result, 8); Result[0] := '1aA'; @@ -296,7 +731,7 @@ procedure TTestMultiCaret.Delete; Result[6] := '7gG'; Result[7] := ''; end; - function TestText1Del2: TStringArray; + function LocalText1Del2: TStringArray; begin SetLength(Result, 8); Result[0] := '1aA'; @@ -308,7 +743,7 @@ procedure TTestMultiCaret.Delete; Result[6] := '7gG'; Result[7] := ''; end; - function TestText1DelExtra: TStringArray; + function LocalText1DelExtra: TStringArray; begin SetLength(Result, 8); Result[0] := '1aA'; @@ -320,114 +755,190 @@ procedure TTestMultiCaret.Delete; Result[6] := '7gG'; Result[7] := ''; end; -var - Opt, OptRemove: TSynEditorOptions2; begin PushBaseName('NO eoPersistentBlock, HAS eoOverwriteBlock'); - Opt := [eoOverwriteBlock]; - OptRemove := [eoPersistentBlock]; + FOpt2Add := [eoOverwriteBlock]; + FOpt2Remove := [eoPersistentBlock]; PushBaseName('ecDeleteLastChar'); PushBaseName('ecDeleteLastChar - zero width sel'); - ReCreateEdit(TestText1, Opt, OptRemove); + ReCreateEdit(LocalText1); SetCaretAndColumnSelect(3,2, 4,0); RunCmdSeq([ecDeleteLastChar], []); - TestIsCaretLogAndFullText('', 2, 6, TestText1Del); + TestIsCaretLogAndFullText('', 2, 6, LocalText1Del); PopPushBaseName('ecDeleteLastChar - ONE width backward sel'); - ReCreateEdit(TestText1, Opt, OptRemove); + ReCreateEdit(LocalText1); SetCaretAndColumnSelect(3,2, 4,-1); RunCmdSeq([ecDeleteLastChar], []); - TestIsCaretLogAndFullText('', 2, 6, TestText1Del); + TestIsCaretLogAndFullText('', 2, 6, LocalText1Del); RunCmdSeq([ecDeleteLastChar], []); - TestIsCaretLogAndFullText('BS again', 1, 6, TestText1DelAndBS); + TestIsCaretLogAndFullText('BS again', 1, 6, LocalText1DelAndBS); PopPushBaseName('ecDeleteLastChar - ONE width sel'); - ReCreateEdit(TestText1, Opt, OptRemove); + ReCreateEdit(LocalText1); SetCaretAndColumnSelect(2,2, 4,1); RunCmdSeq([ecDeleteLastChar], []); - TestIsCaretLogAndFullText('', 2, 6, TestText1Del); + TestIsCaretLogAndFullText('', 2, 6, LocalText1Del); PopPushBaseName('ecDeleteLastChar - Two width sel'); - ReCreateEdit(TestText1, Opt, OptRemove); + ReCreateEdit(LocalText1); SetCaretAndColumnSelect(1,2, 4,2); RunCmdSeq([ecDeleteLastChar], []); - TestIsCaretLogAndFullText('', 1, 6, TestText1Del2); + TestIsCaretLogAndFullText('', 1, 6, LocalText1Del2); PopPushBaseName('ecDeleteLastChar - ONE width sel / extra caret'); - ReCreateEdit(TestText1, Opt, OptRemove); + ReCreateEdit(LocalText1); SetCaretAndColumnSelect(2,2, 4,1); - FMultiCaret.AddCaretAt(4,3); - FMultiCaret.AddCaretAt(2,4); + FMultiCaret.AddCaretAtLogPos(4,3,0); + FMultiCaret.AddCaretAtLogPos(2,4,0); RunCmdSeq([ecDeleteLastChar], []); - TestIsCaretLogAndFullText('', 2, 6, TestText1DelExtra); + TestIsCaretLogAndFullText('', 2, 6, LocalText1DelExtra); PopPushBaseName('ecDeleteChar'); PushBaseName('ecDeleteChar - zero width sel'); - ReCreateEdit(TestText1, Opt, OptRemove); + ReCreateEdit(TestText1); + SetCaretAndColumnSelect(2,2, 1,0); + RunAndTest('Height 2', [ecDeleteChar], 2, 3, TestText1(2,3, 2,1), 1, []); + RunAndTest('Height 2', [ecRight], 3, 3, TestText1(2,3, 2,1), 0, []); + RunAndTest('Height 2', [ecRight, ecUndo],2, 3, TestText1, 0, []); + + ReCreateEdit(TestText1); + SetCaretAndColumnSelect(2,2, 2,0); + RunAndTest('Height 3', [ecDeleteChar], 2, 4, TestText1(2,4, 2,1), 2, []); + RunAndTest('Height 3', [ecRight], 3, 4, TestText1(2,4, 2,1), 0, []); + RunAndTest('Height 2', [ecRight, ecUndo],2, 4, TestText1, 0, []); + + ReCreateEdit(TestText1); + SetCaretAndColumnSelect(2,2, 3,0); + RunAndTest('Height 4', [ecDeleteChar], 2, 5, TestText1(2,5, 2,1), 3, []); + RunAndTest('Height 4', [ecRight], 3, 5, TestText1(2,5, 2,1), 0, []); + RunAndTest('Height 2', [ecRight, ecUndo],2, 5, TestText1, 0, []); + + ReCreateEdit(TestText1); SetCaretAndColumnSelect(2,2, 4,0); - RunCmdSeq([ecDeleteChar], []); - TestIsCaretLogAndFullText('', 2, 6, TestText1Del); + RunAndTest('Height 5', [ecDeleteChar], 2, 6, TestText1(2,6, 2,1), 4, []); + RunAndTest('Height 5', [ecRight], 3, 6, TestText1(2,6, 2,1), 0, []); + RunAndTest('Height 2', [ecRight, ecUndo],2, 6, TestText1, 0, []); + + ReCreateEdit(TestText1); + SetCaretAndColumnSelect(2,2, 5,0); + RunAndTest('Height 5', [ecDeleteChar], 2, 7, TestText1(2,7, 2,1), 5, []); + RunAndTest('Height 5', [ecRight], 3, 7, TestText1(2,7, 2,1), 0, []); + RunAndTest('Height 2', [ecRight, ecUndo],2, 7, TestText1, 0, []); + + + ReCreateEdit(TestText1); + SetCaretAndColumnSelect(2,2, 4,0); + RunAndTest('', [ecDeleteChar], 2, 6, TestText1(2,6, 2,1), 4, []); + RunAndTest('', [ecRight], 3, 6, TestText1(2,6, 2,1), 0, []); + + + ReCreateEdit(TestText1); + SetCaretAndColumnSelect(1,2, 1,0); + RunAndTest('X=0, Height 2', [ecDeleteChar], 1, 3, TestText1(2,3, 1,1), 1, []); + RunAndTest('X=0, Height 2', [ecRight], 2, 3, TestText1(2,3, 1,1), 0, []); + RunAndTest('X=0, Height 2', [ecRight, ecUndo],1, 3, TestText1, 0, []); + + ReCreateEdit(TestText1); + SetCaretAndColumnSelect(1,2, 2,0); + RunAndTest('X=0, Height 3', [ecDeleteChar], 1, 4, TestText1(2,4, 1,1), 2, []); + RunAndTest('X=0, Height 3', [ecRight], 2, 4, TestText1(2,4, 1,1), 0, []); + RunAndTest('X=0, Height 3', [ecRight, ecUndo],1, 4, TestText1, 0, []); + + ReCreateEdit(TestText1); +FMultiCaret.DefaultColumnSelectMode := mcmMoveAllCarets; + SetCaretAndColumnSelect(1,2, 2,0); + RunAndTest('X=0, Height 3', [ecDeleteChar], 1, 4, TestText1(2,4, 1,1), 2, [1,2, 1,3]); + RunAndTest('X=0, Height 3', [ecRight], 2, 4, TestText1(2,4, 1,1), 2, [2,2, 2,3]); + RunAndTest('X=0, Height 3', [ecUndo],1, 4, TestText1, 0, []); PopPushBaseName('ecDeleteChar - ONE width backward sel'); - ReCreateEdit(TestText1, Opt, OptRemove); + ReCreateEdit(LocalText1); SetCaretAndColumnSelect(3,2, 4,-1); RunCmdSeq([ecDeleteChar], []); - TestIsCaretLogAndFullText('', 2, 6, TestText1Del); + TestIsCaretLogAndFullText('', 2, 6, LocalText1Del); PopPushBaseName('ecDeleteChar - ONE width sel'); - ReCreateEdit(TestText1, Opt, OptRemove); + ReCreateEdit(LocalText1); SetCaretAndColumnSelect(2,2, 4,1); RunCmdSeq([ecDeleteChar], []); - TestIsCaretLogAndFullText('', 2, 6, TestText1Del); + TestIsCaretLogAndFullText('', 2, 6, LocalText1Del); PopPushBaseName('ecDeleteChar - Two width sel'); - ReCreateEdit(TestText1, Opt, OptRemove); + ReCreateEdit(LocalText1); SetCaretAndColumnSelect(1,2, 4,2); RunCmdSeq([ecDeleteChar], []); - TestIsCaretLogAndFullText('', 1, 6, TestText1Del2); + TestIsCaretLogAndFullText('', 1, 6, LocalText1Del2); PopBaseName; PopBaseName; PopPushBaseName('NO eoPersistentBlock, NO eoOverwriteBlock'); - Opt := []; - OptRemove := [eoOverwriteBlock, eoPersistentBlock]; + FOpt2Add := []; + FOpt2Remove := [eoOverwriteBlock, eoPersistentBlock]; PushBaseName('ecDeleteLastChar'); PopPushBaseName('ecDeleteLastChar - Two width sel'); - ReCreateEdit(TestText1, Opt, OptRemove); + ReCreateEdit(LocalText1); SetCaretAndColumnSelect(1,2, 4,2); RunCmdSeq([ecDeleteLastChar], []); - TestIsCaretLogAndFullText('', 2, 6, TestText1Del); + TestIsCaretLogAndFullText('', 2, 6, LocalText1Del); PopPushBaseName('ecDeleteChar'); PopPushBaseName('ecDeleteChar - Two width backward sel'); - ReCreateEdit(TestText1, Opt, OptRemove); + ReCreateEdit(LocalText1); SetCaretAndColumnSelect(4,2, 4,-2); RunCmdSeq([ecDeleteChar], []); - TestIsCaretLogAndFullText('', 2, 6, TestText1Del); + TestIsCaretLogAndFullText('', 2, 6, LocalText1Del); PopBaseName; PopBaseName; PopPushBaseName('NO eoPersistentBlock, NO eoOverwriteBlock'); - Opt := [eoPersistentBlock]; - OptRemove := [eoOverwriteBlock]; + FOpt2Add := [eoPersistentBlock]; + FOpt2Remove := [eoOverwriteBlock]; PopPushBaseName('ecDeleteLastChar - Two width sel'); - ReCreateEdit(TestText1, Opt, OptRemove); + ReCreateEdit(LocalText1); SetCaretAndColumnSelect(1,2, 4,2); RunCmdSeq([ecDeleteLastChar], []); - TestIsCaretLogAndFullText('', 2, 6, TestText1Del); + TestIsCaretLogAndFullText('', 2, 6, LocalText1Del); PopBaseName; + + // Delete and merge caret + ReCreateEdit(TestText1); + SetCaretAndColumnSelect(4,2, 0,0); + SetCaretsByKey([2,0, 2,0, 2,0]); // 4carets / main caret at end + RunAndTest('', [ecDeleteLastChar], 6,2, TestText1([2,9,1, 2,7,1, 2,5,1, 2,3,1]), 3, [3,2,0, 4,2,0, 5,2,0]); + RunAndTest('', [ecDeleteLastChar], 2,2, TestText1([2,2,8]), 0, []); + + ReCreateEdit(TestText1); + SetCaretAndColumnSelect(10,2, 0,0); + SetCaretsByKey([-2,0, -2,0, -2,0]); // 4carets / main caret at start + RunAndTest('', [ecDeleteLastChar], 3,2, TestText1([2,9,1, 2,7,1, 2,5,1, 2,3,1]), 3, [4,2,0, 5,2,0, 6,2,0]); + RunAndTest('', [ecDeleteLastChar], 2,2, TestText1([2,2,8]), 0, []); + + ReCreateEdit(TestText1); + SetCaretAndColumnSelect(10,2, 0,0); + SetCaretsByKey([-2,0, -4,0, +2,0]); // 4carets / main caret in middle + RunAndTest('', [ecDeleteLastChar], 4,2, TestText1([2,9,1, 2,7,1, 2,5,1, 2,3,1]), 3, [3,2,0, 5,2,0, 6,2,0]); + RunAndTest('', [ecDeleteLastChar], 2,2, TestText1([2,2,8]), 0, []); + + ReCreateEdit(TestText1); + SetCaretAndColumnSelect(3,2, 0,0); + SetCaretsByKey([2,0, 2,0, 2,0]); // 4carets / main caret at end + RunAndTest('', [ecDeleteChar], 6,2, TestText1([2,9,1, 2,7,1, 2,5,1, 2,3,1]), 3, [3,2,0, 4,2,0, 5,2,0]); + RunAndTest('', [ecDeleteChar], 3,2, TestText1([2,3,8]), 0, []); + + end; procedure TTestMultiCaret.ReplaceColSel; - function TestText1: TStringArray; + function LocalText1: TStringArray; begin SetLength(Result, 8); Result[0] := '1aA'; @@ -439,7 +950,7 @@ procedure TTestMultiCaret.ReplaceColSel; Result[6] := '7gG'; Result[7] := ''; end; - function TestText1X: TStringArray; + function LocalText1X: TStringArray; begin SetLength(Result, 8); Result[0] := '1aA'; @@ -453,20 +964,20 @@ procedure TTestMultiCaret.ReplaceColSel; end; begin ReCreateEdit; - SetLines(TestText1); + SetLines(LocalText1); SetCaretAndColumnSelect(2,2, 4,1); - TestIsCaretLogAndFullText('', 3, 6, TestText1); + TestIsCaretLogAndFullText('', 3, 6, LocalText1); RunCmdSeq([ecChar], ['X']); - TestIsCaretLogAndFullText('', 3, 6, TestText1X); + TestIsCaretLogAndFullText('', 3, 6, LocalText1X); // 4 extra carets + main caret AssertEquals(BaseTestName+'', 4, FMultiCaret.Carets.Count); end; procedure TTestMultiCaret.TabKey; - function TestText1: TStringArray; + function LocalText1: TStringArray; begin SetLength(Result, 8); Result[0] := '1a'; @@ -478,7 +989,7 @@ procedure TTestMultiCaret.TabKey; Result[6] := '7g'; Result[7] := ''; end; - function TestText1Tab: TStringArray; + function LocalText1Tab: TStringArray; begin SetLength(Result, 8); Result[0] := '1a'; @@ -490,7 +1001,7 @@ procedure TTestMultiCaret.TabKey; Result[6] := '7g'; Result[7] := ''; end; - function TestText1Indent: TStringArray; + function LocalText1Indent: TStringArray; begin SetLength(Result, 8); Result[0] := '1a'; @@ -502,7 +1013,7 @@ procedure TTestMultiCaret.TabKey; Result[6] := '7g'; Result[7] := ''; end; - function TestText1IndentX: TStringArray; + function LocalText1IndentX: TStringArray; begin SetLength(Result, 8); Result[0] := '1a'; @@ -514,7 +1025,7 @@ procedure TTestMultiCaret.TabKey; Result[6] := '7g'; Result[7] := ''; end; - function TestText1TabOver: TStringArray; + function LocalText1TabOver: TStringArray; begin SetLength(Result, 8); Result[0] := '1a'; @@ -530,13 +1041,13 @@ begin PushBaseName('ZERO width selection -- WITH eoTabIndent'); ReCreateEdit; SynEdit.Options := SynEdit.Options + [eoTabIndent] - [eoTabsToSpaces, eoSmartTabs, eoTrimTrailingSpaces]; - SetLines(TestText1); + SetLines(LocalText1); SetCaretAndColumnSelect(2,2, 4,0); - TestIsCaretLogAndFullText('', 2, 6, TestText1); + TestIsCaretLogAndFullText('', 2, 6, LocalText1); RunCmdSeq([ecTab], []); - TestIsCaretLogAndFullText('', 3, 6, TestText1Tab); + TestIsCaretLogAndFullText('', 3, 6, LocalText1Tab); // 4 extra carets + main caret AssertEquals(BaseTestName+'', 4, FMultiCaret.Carets.Count); @@ -544,13 +1055,13 @@ begin PopPushBaseName('ONE width selection -- WITH eoTabIndent'); ReCreateEdit; SynEdit.Options := SynEdit.Options + [eoTabIndent] - [eoTabsToSpaces, eoSmartTabs, eoTrimTrailingSpaces]; - SetLines(TestText1); + SetLines(LocalText1); SetCaretAndColumnSelect(2,2, 4,1); - TestIsCaretLogAndFullText('', 3, 6, TestText1); + TestIsCaretLogAndFullText('', 3, 6, LocalText1); RunCmdSeq([ecTab], []); - TestIsCaretLogAndFullText('', 3, 6, TestText1TabOver); + TestIsCaretLogAndFullText('', 3, 6, LocalText1TabOver); // 4 extra carets + main caret AssertEquals(BaseTestName+'', 4, FMultiCaret.Carets.Count); @@ -559,13 +1070,13 @@ begin PopPushBaseName('ZERO width selection -- WITHOUT eoTabIndent'); ReCreateEdit; SynEdit.Options := SynEdit.Options - [eoTabIndent] - [eoTabsToSpaces, eoSmartTabs, eoTrimTrailingSpaces]; - SetLines(TestText1); + SetLines(LocalText1); SetCaretAndColumnSelect(2,2, 4,0); - TestIsCaretLogAndFullText('', 2, 6, TestText1); + TestIsCaretLogAndFullText('', 2, 6, LocalText1); RunCmdSeq([ecTab], []); - TestIsCaretLogAndFullText('', 3, 6, TestText1Tab); + TestIsCaretLogAndFullText('', 3, 6, LocalText1Tab); // 4 extra carets + main caret AssertEquals(BaseTestName+'', 4, FMultiCaret.Carets.Count); @@ -573,13 +1084,13 @@ begin PopPushBaseName('ONE width selection -- WITHOUT eoTabIndent'); ReCreateEdit; SynEdit.Options := SynEdit.Options - [eoTabIndent] - [eoTabsToSpaces, eoSmartTabs, eoTrimTrailingSpaces]; - SetLines(TestText1); + SetLines(LocalText1); SetCaretAndColumnSelect(2,2, 4,1); - TestIsCaretLogAndFullText('', 3, 6, TestText1); + TestIsCaretLogAndFullText('', 3, 6, LocalText1); RunCmdSeq([ecTab], []); - TestIsCaretLogAndFullText('', 3, 6, TestText1TabOver); + TestIsCaretLogAndFullText('', 3, 6, LocalText1TabOver); // 4 extra carets + main caret AssertEquals(BaseTestName+'', 4, FMultiCaret.Carets.Count); @@ -587,7 +1098,7 @@ begin end; procedure TTestMultiCaret.Paste; - function TestText1: TStringArray; + function LocalText1: TStringArray; begin SetLength(Result, 8); Result[0] := '1a'; @@ -599,7 +1110,7 @@ procedure TTestMultiCaret.Paste; Result[6] := '7g'; Result[7] := ''; end; - function TestText1PasteNorm: TStringArray; + function LocalText1PasteNorm: TStringArray; begin SetLength(Result, 8); Result[0] := '1a'; @@ -611,7 +1122,7 @@ procedure TTestMultiCaret.Paste; Result[6] := '7g'; Result[7] := ''; end; - function TestText1PasteNormOver: TStringArray; + function LocalText1PasteNormOver: TStringArray; begin SetLength(Result, 8); Result[0] := '1a'; @@ -623,7 +1134,7 @@ procedure TTestMultiCaret.Paste; Result[6] := '7g'; Result[7] := ''; end; - function TestText1PasteCol: TStringArray; + function LocalText1PasteCol: TStringArray; begin SetLength(Result, 8); Result[0] := '1a'; @@ -635,7 +1146,7 @@ procedure TTestMultiCaret.Paste; Result[6] := '72g'; Result[7] := ''; end; - function TestText1PasteColOver: TStringArray; + function LocalText1PasteColOver: TStringArray; begin SetLength(Result, 8); Result[0] := '1a'; @@ -651,16 +1162,16 @@ begin PushBaseName('ZERO width selection -- paste normal'); ReCreateEdit; SynEdit.Options := SynEdit.Options + [eoTabIndent] - [eoTabsToSpaces, eoSmartTabs, eoTrimTrailingSpaces]; - SetLines(TestText1); + SetLines(LocalText1); SetCaret(1,1); RunCmdSeq([ecSelRight, ecSelRight, ecCopy], []); // copy SetCaretAndColumnSelect(2,2, 4,0); - TestIsCaretLogAndFullText('', 2, 6, TestText1); + TestIsCaretLogAndFullText('', 2, 6, LocalText1); RunCmdSeq([ecPaste], []); - TestIsCaretLogAndFullText('', 4, 6, TestText1PasteNorm); + TestIsCaretLogAndFullText('', 4, 6, LocalText1PasteNorm); // 4 extra carets + main caret AssertEquals(BaseTestName+'', 4, FMultiCaret.Carets.Count); @@ -668,16 +1179,16 @@ begin PopPushBaseName('ONE width selection -- paste normal'); ReCreateEdit; SynEdit.Options := SynEdit.Options + [eoTabIndent] - [eoTabsToSpaces, eoSmartTabs, eoTrimTrailingSpaces]; - SetLines(TestText1); + SetLines(LocalText1); SetCaret(1,1); RunCmdSeq([ecSelRight, ecSelRight, ecCopy], []); // copy SetCaretAndColumnSelect(2,2, 4,1); - TestIsCaretLogAndFullText('', 3, 6, TestText1); + TestIsCaretLogAndFullText('', 3, 6, LocalText1); RunCmdSeq([ecPaste], []); - TestIsCaretLogAndFullText('', 4, 6, TestText1PasteNormOver); + TestIsCaretLogAndFullText('', 4, 6, LocalText1PasteNormOver); // 4 extra carets + main caret AssertEquals(BaseTestName+'', 4, FMultiCaret.Carets.Count); @@ -686,32 +1197,32 @@ begin PushBaseName('ZERO width selection -- paste column'); ReCreateEdit; SynEdit.Options := SynEdit.Options + [eoTabIndent] - [eoTabsToSpaces, eoSmartTabs, eoTrimTrailingSpaces]; - SetLines(TestText1); + SetLines(LocalText1); SetCaretAndColumnSelect(1,1, 1,1); RunCmdSeq([ecCopy], []); // copy SetCaretAndColumnSelect(2,2, 4,0); - TestIsCaretLogAndFullText('', 2, 6, TestText1); + TestIsCaretLogAndFullText('', 2, 6, LocalText1); RunCmdSeq([ecPaste], []); - TestIsCaretLogAndFullText('', 3, 7, TestText1PasteCol); + TestIsCaretLogAndFullText('', 3, 7, LocalText1PasteCol); AssertEquals(BaseTestName+'', 0, FMultiCaret.Carets.Count); PopPushBaseName('ONE width selection -- paste column'); ReCreateEdit; SynEdit.Options := SynEdit.Options + [eoTabIndent] - [eoTabsToSpaces, eoSmartTabs, eoTrimTrailingSpaces]; - SetLines(TestText1); + SetLines(LocalText1); SetCaretAndColumnSelect(1,1, 1,1); RunCmdSeq([ecCopy], []); // copy SetCaretAndColumnSelect(2,2, 4,1); - TestIsCaretLogAndFullText('', 3, 6, TestText1); + TestIsCaretLogAndFullText('', 3, 6, LocalText1); RunCmdSeq([ecPaste], []); - TestIsCaretLogAndFullText('', 3, 3, TestText1PasteColOver); + TestIsCaretLogAndFullText('', 3, 3, LocalText1PasteColOver); AssertEquals(BaseTestName+'', 0, FMultiCaret.Carets.Count); end;