25KGF2W2ORVH3Z7RIBDBRKILMM624MNHIMN5QXNMZOWQ5DOJUCZAC 32SDOYNGPOAAMEQMZLZUE3XE6D5CFSZU6M3JDRERUVPAB4TLZOOAC GUD2J453E3UTRJZIEEUUJ3ZIGL72ETUXTO57XQIZ2Y3ZU3C2R55QC 6W6OJAUT3KLOXXKYR4GA3MG4ACLO2T4KX3SGD2KZDX6MYEZYTPHAC U45XPF3YKPXXRJ4MN24T2RV7GOL4FZKQSWX5P5I7WT4HC5XF4FHAC 5SFTBD4FW6GQPKRUJBAII5R2EZPDYG6CH57BIJD4IVZBE6JZB2ZQC NBG3GUEJJMWS2FRJDU26KRQLA6O2YK77PDAPBMD3SKWLDTK23BVAC if breaks == 0 then 1-- If the file ends with a newline, Vim doesn't count it as a new empty line-- unless it's preceded by another newline (e.g., "a\n\n" is 2 lines).else if pt.endsWithNewline then breakselse breaks + 1
-- In editor state, each newline creates a new line slot.-- Keep lineCount consistent with getLineRange/getLine and cursor rows.breaks + 1
/-- Flatten a tree into pieces in document order (iterative to avoid deep recursion). -/def toPieces (t : ViE.PieceTree) : Array ViE.Piece := Id.run dolet mut out : Array ViE.Piece := #[]let mut stack : List ViE.PieceTree := [t]while !stack.isEmpty domatch stack with| [] => pure ()| node :: rest =>stack := restmatch node with| ViE.PieceTree.empty => pure ()| ViE.PieceTree.leaf pieces _ _ =>out := out ++ pieces| ViE.PieceTree.internal children _ _ =>let mut i := children.sizewhile i > 0 dolet j := i - 1stack := children[j]! :: stacki := jreturn out
def testLengthAndLineCountConsistency : IO Unit := doIO.println "testLengthAndLineCountConsistency..."let mut pt := PieceTable.fromString ""for i in [0:1200] dolet len := pt.lengthlet pos := if len == 0 then 0 else (i * 17 + 3) % (len + 1)if i % 5 == 0 thenlet txt := if i % 2 == 0 then "\n" else s!"x{i}"pt := pt.insert pos txt poselse if i % 7 == 0 thenlet delLen := if len == 0 then 0 else min ((i % 4) + 1) (len - (pos % len))pt := pt.delete (if len == 0 then 0 else pos % len) delLen 0elselet txt := if i % 3 == 0 then "a\n" else "b"pt := pt.insert pos txt poslet rendered := pt.toStringlet renderedBytes := rendered.toUTF8.sizeif renderedBytes != pt.length thenthrow <| IO.userError s!"Length mismatch at step {i}: tree={pt.length} rendered={renderedBytes}"let expectedLines := expectedLineCountFromText renderedif pt.lineCount != expectedLines thenthrow <| IO.userError s!"Line count mismatch at step {i}: tree={pt.lineCount} expected={expectedLines}"let h := PieceTree.height pt.treeif h > 64 thenthrow <| IO.userError s!"Tree height unexpectedly large at step {i}: height={h}"IO.println "[PASS] length/line count remain consistent under mixed edits"
-- Scenario: Insert mode transition + single-char delete keeps line count and cursor in sync.let s := ViE.initialStatelet s ← runKeys s [Key.char 'i', Key.enter]let c1 := s.getCursorassert "Enter in insert creates second line" (s.getActiveBuffer.lineCount == 2)assert "Enter in insert moves cursor to next line" (c1.row.val == 1 && c1.col.val == 0)let s ← runKeys s [Key.esc]let c2 := s.getCursorassert "Esc from insert keeps valid cursor row" (c2.row.val < s.getActiveBuffer.lineCount)assert "Esc from insert keeps row/col at second line head" (c2.row.val == 1 && c2.col.val == 0)-- Re-enter insert and delete one char (the previous newline) via Backspace.let s ← runKeys s [Key.char 'i', Key.backspace]let c3 := s.getCursorassert "Backspace at BOL joins lines" (s.getActiveBuffer.lineCount == 1)assert "Backspace at BOL moves cursor to joined line end" (c3.row.val == 0 && c3.col.val == 0)-- In normal mode x at empty line is a no-op and must keep cursor valid.let s ← runKeys s [Key.esc, Key.char 'x']let c4 := s.getCursorassert "x on empty line keeps line count" (s.getActiveBuffer.lineCount == 1)assert "x on empty line keeps cursor in range" (c4.row.val < s.getActiveBuffer.lineCount)