diff --git a/.gitattributes b/.gitattributes index e63072a42c..ff7aa00e1b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4758,6 +4758,7 @@ components/synedit/test/testpaintcolormerging.pas svneol=native#text/pascal components/synedit/test/testsearch.pas svneol=native#text/pascal components/synedit/test/testsynbeautifier.pas svneol=native#text/pascal components/synedit/test/testsyncroedit.pas svneol=native#text/pascal +components/synedit/test/testsynmulticaret.pas svneol=native#text/pascal components/synedit/test/testsynselection.pas svneol=native#text/pascal components/synedit/test/testsynsharededits.pas svneol=native#text/pascal components/synedit/test/testsyntextarea.pas svneol=native#text/pascal diff --git a/components/synedit/synpluginmulticaret.pp b/components/synedit/synpluginmulticaret.pp index 63ae906f92..b917b306a3 100644 --- a/components/synedit/synpluginmulticaret.pp +++ b/components/synedit/synpluginmulticaret.pp @@ -2,7 +2,7 @@ unit SynPluginMultiCaret; {$mode objfpc}{$H+} -{$DEFINE SynMultiCaretAssert} +{off $DEFINE SynMultiCaretAssert} {off $DEFINE SynMultiCaretDebug} {$IfDef SynMultiCaretAssert} @@ -631,9 +631,9 @@ begin ) ) then - h := Result + h := Result // FCarets[Result] >= (x,y,o) else - l := Result + 1; + l := Result + 1; // FCarets[Result] < (x,y,o) Result := cardinal(l + h) div 2; end; cp := @FCarets[Result]; @@ -1148,7 +1148,6 @@ end; procedure TSynPluginMultiCaretList.SetCurrentCaretKeepX(AValue: Integer); begin FCurrenCaret^.KeepX := AValue; - AdjustAfterChange(FCurrenCaret); end; procedure TSynPluginMultiCaretList.AdjustAfterChange(ACaret: PCaretData); @@ -1169,37 +1168,42 @@ begin if (ACaret > FLowCaret) then begin NewCaretPos := ACaret - 1; + // Compare with previous Caret in list if (y <= NewCaretPos^.y) then begin x := ACaret^.x; if (y < NewCaretPos^.y) or (x <= NewCaretPos^.x) then begin o := ACaret^.offs; if (x < NewCaretPos^.x) or ( (x = NewCaretPos^.x) and (o <= NewCaretPos^.offs) ) then begin + // ACaret is <= previous Caret in list + // TODO: If equal, only check for merge 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) ) ) + ( (x < HelpCaretPos^.x) or ( (x = HelpCaretPos^.x) and (o <= HelpCaretPos^.offs) ) ) ) ) then begin + // ACaret is < pre-previous Caret in list NewCaretIdx := FindEqOrNextCaretRawIdx(x,y,o, FLowIndex, ToRawIndex(HelpCaretPos)); - if NewCaretIdx > FHighIndex then NewCaretIdx := FHighIndex; + Assert((NewCaretIdx >= FLowIndex) and (NewCaretIdx <= FHighIndex), 'caret idx in range'); NewCaretPos := @FCarets[NewCaretIdx]; end; if (y = NewCaretPos^.y) and (x = NewCaretPos^.x) and (o = NewCaretPos^.offs) then begin - if FMergeLock = 0 then + if FMergeLock = 0 then begin InternalRemoveCaretEx(ToRawIndex(ACaret), ToRawIndex(NewCaretPos)); - exit; + exit; + end; end; v := ACaret^; {$IfDef SynMultiCaretDebug} - debugln(SynMCaretDebug, ['TSynPluginMultiCaretList.AdjustAfterChange ', ToRawIndex(NewCaretPos), ' ',ToRawIndex(ACaret)]); + 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'); + assert(FBeforeNextCaret=nil, 'TSynPluginMultiCaretList.AdjustAfterChange: FBeforeNextCaret=nil Caret changed twice in same iteration'); FCurrenCaret := NewCaretPos; // move down case FIteratoreMode of mciUp: FBeforeNextCaret := ACaret; // continue at ACaret+1; @@ -1209,6 +1213,8 @@ begin inc(FIterationDoneCount); end; end; + + exit; end end; end; @@ -1216,29 +1222,46 @@ begin if (ACaret < FHighCaret) then begin NewCaretPos := ACaret + 1; + // Compare with next Caret in list if (y >= NewCaretPos^.y) then begin x := ACaret^.x; if (y > NewCaretPos^.y) or (x >= NewCaretPos^.x) then begin o := ACaret^.offs; if (x > NewCaretPos^.x) or ( (x = NewCaretPos^.x) and (o >= NewCaretPos^.offs) ) then begin + // ACaret is >= next Caret in list 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) ) ) + ( (x > HelpCaretPos^.x) or ( (x = HelpCaretPos^.x) and (o >= HelpCaretPos^.offs) ) ) ) ) then begin + // ACaret is > post-next Caret in list NewCaretIdx := FindEqOrNextCaretRawIdx(x,y,o, ToRawIndex(HelpCaretPos), FHighIndex); - if NewCaretIdx < FLowIndex then NewCaretIdx := FLowIndex; + Assert((NewCaretIdx >= FLowIndex + 1) and (NewCaretIdx <= FHighIndex + 1), 'caret idx in range'); + {$PUSH}{$R-} NewCaretPos := @FCarets[NewCaretIdx]; - end; + {$POP} + if (NewCaretIdx <= FHighIndex) then begin + if (y = NewCaretPos^.y) and (x = NewCaretPos^.x) and (o = NewCaretPos^.offs) then begin + if FMergeLock = 0 then begin + InternalRemoveCaretEx(ToRawIndex(ACaret), ToRawIndex(NewCaretPos)); + exit; + end; + end; + end; + dec(NewCaretPos); + end + + else + if (y = NewCaretPos^.y) and (x = NewCaretPos^.x) and (o = NewCaretPos^.offs) then begin + if FMergeLock = 0 then begin + InternalRemoveCaretEx(ToRawIndex(ACaret), ToRawIndex(NewCaretPos)); + exit; + end; + 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)]); @@ -1246,7 +1269,7 @@ begin Move((ACaret+1)^, ACaret^, Pointer(NewCaretPos)-Pointer(ACaret)); NewCaretPos^ := v; - assert(FBeforeNextCaret=nil, 'TSynPluginMultiCaretList.AdjustAfterChange: FBeforeNextCaret=nil'); + assert(FBeforeNextCaret=nil, 'TSynPluginMultiCaretList.AdjustAfterChange: FBeforeNextCaret=nil Caret changed twice in same iteration'); FCurrenCaret := NewCaretPos; // move down case FIteratoreMode of mciDown: FBeforeNextCaret := ACaret; // continue at ACaret-1; @@ -1919,6 +1942,8 @@ begin Command := ecNone; Handled := (Command <> ecNone) or IsStartOfCombo; + if IsStartOfCombo then + ComboKeyStrokes := FKeyStrokes; end; procedure TSynCustomPluginMultiCaret.RemoveCaretsInSelection; diff --git a/components/synedit/test/SynTest.lpi b/components/synedit/test/SynTest.lpi index 719e011d63..bcc1cfdf92 100644 --- a/components/synedit/test/SynTest.lpi +++ b/components/synedit/test/SynTest.lpi @@ -17,9 +17,6 @@ - - - @@ -51,7 +48,7 @@ - + @@ -161,6 +158,11 @@ + + + + + diff --git a/components/synedit/test/SynTest.lpr b/components/synedit/test/SynTest.lpr index 0640a4914e..a3e59624e7 100644 --- a/components/synedit/test/SynTest.lpr +++ b/components/synedit/test/SynTest.lpr @@ -4,7 +4,7 @@ program SynTest; uses Interfaces, Forms, GuiTestRunner, TestBase, TestBasicSynEdit, TestNavigation, - TestSynSelection, TestBlockIndent, TestBookMarks, TestSearch, + TestSynSelection, TestSynMultiCaret, TestBlockIndent, TestBookMarks, TestSearch, TestSynBeautifier, TestTrimSpace, TestSyncroEdit, TestSynTextArea, TestHighlightPas, TestHighlightXml, TestHighlightMulti, TestMarkupwordGroup, TestMarkupHighAll, TestFoldedView, TestSynSharedEdits, TestHighlighterLfm, diff --git a/components/synedit/test/testsynmulticaret.pas b/components/synedit/test/testsynmulticaret.pas new file mode 100644 index 0000000000..25ac478044 --- /dev/null +++ b/components/synedit/test/testsynmulticaret.pas @@ -0,0 +1,257 @@ +unit TestSynMultiCaret; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, fpcunit, testutils, testregistry, SynPluginMultiCaret, + SynEditTypes, LazLoggerBase; + +type + + { TTestSynMultCaret } + + TTestSynMultCaret = class(TTestCase) + published + procedure TestAdjustAfterChange; + end; + +implementation + +procedure TTestSynMultCaret.TestAdjustAfterChange; + procedure CreateList(var AList: TSynPluginMultiCaretList; ACount: Integer; AMergeLock: Boolean); + var + i: Integer; + begin + FreeAndNil(AList); + AList := TSynPluginMultiCaretList.Create; + if AMergeLock then + AList.IncMergeLock; + + for i := 1 to ACount do + AList.AddCaret(i * 10, 1, 0); + end; + + procedure AssertSorted(AName: String; AList: TSynPluginMultiCaretList; ACount: Integer; ADupValue: Integer = -1); + var + i: Integer; + begin + //for i := 0 to AList.Count-1 do DbgOut([AList.CaretFull[i].x, ' ']); debugln(''); + AssertEquals(AName + ' Count ', ACount, AList.Count); + for i := 1 to ACount-1 do + AssertTrue(AName + ' Ordered '+IntToStr(i), + (AList.CaretFull[i].x > AList.CaretFull[i-1].x) or + ( (ADupValue <> -1) and + (AList.CaretFull[i].x = AList.CaretFull[i-1].x) and (AList.CaretFull[i].x = ADupValue) ) + ); + end; + +var + TestList: TSynPluginMultiCaretList; + p: TLogCaretPoint; + Len, MoveIdx, MoveTo, i, L2: Integer; + s: String; +begin + for Len := 1 to 10 do + for MoveIdx := 0 to Len - 1 do + for MoveTo := 0 to Len + 1 do + try + TestList := nil; + s := Format('L: %d, From: %d, To: %d ', [Len, MoveIdx, MoveTo]); + //DebugLn('TestAdjustAfterChange '+s); + + L2 := Len; + If not( (MoveTo = MoveIdx+1) or (MoveTo = 0) or (MoveTo = Len + 1) ) then + dec(L2); + + // Modify, no dups + CreateList(TestList, Len, True); + + p := TestList.CaretFull[MoveIdx]; + p.X := MoveTo * 10 + 1; + TestList.CaretFull[MoveIdx] := p; + + AssertSorted('Modify, no dup '+s, TestList, Len); + + + + // Modify, with dups + CreateList(TestList, Len, True); + + p := TestList.CaretFull[MoveIdx]; + p.X := MoveTo * 10; + TestList.CaretFull[MoveIdx] := p; + + AssertSorted('Modify, with dup '+s, TestList, Len, MoveTo * 10); + + + + // Modify, with dups remove + CreateList(TestList, Len, False); + + p := TestList.CaretFull[MoveIdx]; + p.X := MoveTo * 10; + TestList.CaretFull[MoveIdx] := p; + + AssertSorted('Modify, with dup '+s, TestList, L2); + + + + // Iterator Up, no dups + CreateList(TestList, Len, True); + + TestList.StartIteratorAtFirst; + for i := 1 to Len do begin + AssertTrue('Iterator Up Continues '+s, TestList.IterateNextUp); + p := TestList.CurrentCaretFull; + AssertEquals('Iterator Ordered '+s+IntToStr(i), i * 10, TestList.CurrentCaretFull.x); + + if i-1 = MoveIdx then begin + p.X := MoveTo * 10 + 1; + TestList.CurrentCaretFull := p; + end; + end; + AssertTrue('Iterator Up Finished '+s, not TestList.IterateNextUp); + AssertSorted('Iterate up, no dup '+s, TestList, Len); + + TestList.StartIteratorAtFirst; // Iterate again / must get all entries + for i := 1 to Len do + AssertTrue('Iterator 2 Up Continues '+s, TestList.IterateNextUp); + AssertTrue('Iterator 2 Up Finished '+s, not TestList.IterateNextUp); + + + + // Iterator Up, with dups + CreateList(TestList, Len, True); + + TestList.StartIteratorAtFirst; + for i := 1 to Len do begin + AssertTrue('Iterator Up (dups) Continues '+s, TestList.IterateNextUp); + p := TestList.CurrentCaretFull; + AssertEquals('Iterator Ordered '+s+IntToStr(i), i * 10, TestList.CurrentCaretFull.x); + + if i-1 = MoveIdx then begin + p.X := MoveTo * 10; + TestList.CurrentCaretFull := p; + end; + + end; + AssertTrue('Iterator Up (dups) Finished '+s, not TestList.IterateNextUp); + AssertSorted('Iterate up (dups) '+s, TestList, Len, MoveTo * 10); + + TestList.StartIteratorAtFirst; // Iterate again / must get all entries + for i := 1 to Len do + AssertTrue('Iterator 2 Up (dups) Continues '+s, TestList.IterateNextUp); + AssertTrue('Iterator 2 Up (dups) Finished '+s, not TestList.IterateNextUp); + + +(* Not implemented + // Iterator Up, with dups remove + CreateList(TestList, Len, False); + + TestList.StartIteratorAtFirst; + for i := 1 to Len do begin + AssertTrue('Iterator Up (dups) Continues '+s, TestList.IterateNextUp); + p := TestList.CurrentCaretFull; + AssertEquals('Iterator Ordered '+s+IntToStr(i), i * 10, TestList.CurrentCaretFull.x); + + if i-1 = MoveIdx then begin + p.X := MoveTo * 10; + TestList.CurrentCaretFull := p; + end; + + end; + AssertTrue('Iterator Up (dups) Finished '+s, not TestList.IterateNextUp); + AssertSorted('Iterate up (dups) '+s, TestList, L2); + + TestList.StartIteratorAtFirst; // Iterate again / must get all entries + for i := 1 to L2 do + AssertTrue('Iterator 2 Up (dups) Continues '+s, TestList.IterateNextUp); + AssertTrue('Iterator 2 Up (dups) Finished '+s, not TestList.IterateNextUp); +*) + + + // Iterator Down, no dups + CreateList(TestList, Len, True); + + TestList.StartIteratorAtLast; + for i := Len downto 1 do begin + AssertTrue('Iterator Down Continues '+s, TestList.IterateNextDown); + p := TestList.CurrentCaretFull; + AssertEquals('Iterator Ordered '+s+IntToStr(i), i * 10, TestList.CurrentCaretFull.x); + + if i-1 = MoveIdx then begin + p.X := MoveTo * 10 + 1; + TestList.CurrentCaretFull := p; + end; + end; + AssertTrue('Iterator Down Finished '+s, not TestList.IterateNextDown); + AssertSorted('Iterate Down, no dup '+s, TestList, Len); + + TestList.StartIteratorAtLast; // Iterate again / must get all entries + for i := 1 to Len do + AssertTrue('Iterator 2 Down Continues '+s, TestList.IterateNextDown); + AssertTrue('Iterator 2 Down Finished '+s, not TestList.IterateNextDown); + + + + // Iterator Down, with dups + CreateList(TestList, Len, True); + + TestList.StartIteratorAtLast; + for i := Len downto 1 do begin + AssertTrue('Iterator Down (dups) Continues '+s, TestList.IterateNextDown); + p := TestList.CurrentCaretFull; + AssertEquals('Iterator Ordered '+s+IntToStr(i), i * 10, TestList.CurrentCaretFull.x); + + if i-1 = MoveIdx then begin + p.X := MoveTo * 10; + TestList.CurrentCaretFull := p; + end; + end; + AssertTrue('Iterator Down (dups) Finished '+s, not TestList.IterateNextDown); + AssertSorted('Iterate Down (dups) '+s, TestList, Len, MoveTo * 10); + + TestList.StartIteratorAtLast; // Iterate again / must get all entries + for i := 1 to Len do + AssertTrue('Iterator 2 Down (dups) Continues '+s, TestList.IterateNextDown); + AssertTrue('Iterator 2 Down (dups) Finished '+s, not TestList.IterateNextDown); + + +(* Not implemented + // Iterator Down, with dups remove + CreateList(TestList, Len, False); + + TestList.StartIteratorAtLast; + for i := Len downto 1 do begin + AssertTrue('Iterator Down (dups) Continues '+s, TestList.IterateNextDown); + p := TestList.CurrentCaretFull; + AssertEquals('Iterator Ordered '+s+IntToStr(i), i * 10, TestList.CurrentCaretFull.x); + + if i-1 = MoveIdx then begin + p.X := MoveTo * 10; + TestList.CurrentCaretFull := p; + end; + end; + AssertTrue('Iterator Down (dups) Finished '+s, not TestList.IterateNextDown); + AssertSorted('Iterate Down (dups) '+s, TestList, L2); + + TestList.StartIteratorAtLast; // Iterate again / must get all entries + for i := 1 to L2 do + AssertTrue('Iterator 2 Down (dups) Continues '+s, TestList.IterateNextDown); + AssertTrue('Iterator 2 Down (dups) Finished '+s, not TestList.IterateNextDown); +*) + + finally + TestList.Free; + end; +end; + + + +initialization + + RegisterTest(TTestSynMultCaret); +end. +