From 7f5fdbb5fdd54b96162fc6cfc78e4efb1c8c56e9 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 11 Jun 2026 22:54:43 -0700 Subject: [PATCH] Fix deleting a numbered-list item dropping following list items When a single newline was deleted to merge two lines, RichSpanManager did not remap spans living entirely below the joined line, so they kept stale line indices and were dropped. Deleting a middle ordered-list item therefore removed every following item from the list. Decrement those spans' line indices to follow the upward line shift. Fixes #18 Co-Authored-By: Claude Opus 4.8 --- .../texteditor/state/RichSpanManager.kt | 12 ++++++++++++ .../markdown/OrderedListSerializationTest.kt | 15 +++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/ComposeTextEditor/src/commonMain/kotlin/com/darkrockstudios/texteditor/state/RichSpanManager.kt b/ComposeTextEditor/src/commonMain/kotlin/com/darkrockstudios/texteditor/state/RichSpanManager.kt index a6cc6a9..fe62a87 100644 --- a/ComposeTextEditor/src/commonMain/kotlin/com/darkrockstudios/texteditor/state/RichSpanManager.kt +++ b/ComposeTextEditor/src/commonMain/kotlin/com/darkrockstudios/texteditor/state/RichSpanManager.kt @@ -313,6 +313,18 @@ class RichSpanManager( (end.line == deletionPoint.line && end.char <= deletionPoint.char) -> { updatedSpans.add(span) } + // Span is entirely below the joined line — the deleted newline pulls + // every following line up by one, so decrement its line index. + start.line > nextLineStart.line -> { + updatedSpans.add( + span.copy( + range = TextEditorRange( + start = CharLineOffset(start.line - 1, start.char), + end = CharLineOffset(end.line - 1, end.char) + ) + ) + ) + } // Span is entirely on the second line start.line == nextLineStart.line -> { val newStart = CharLineOffset( diff --git a/ComposeTextEditor/src/desktopTest/kotlin/markdown/OrderedListSerializationTest.kt b/ComposeTextEditor/src/desktopTest/kotlin/markdown/OrderedListSerializationTest.kt index 16e09ff..c25785a 100644 --- a/ComposeTextEditor/src/desktopTest/kotlin/markdown/OrderedListSerializationTest.kt +++ b/ComposeTextEditor/src/desktopTest/kotlin/markdown/OrderedListSerializationTest.kt @@ -245,6 +245,21 @@ class OrderedListSerializationTest { assertEquals("1. onetwo", extension.exportAsMarkdown()) } + @Test + fun `deleting a middle ordered-list item keeps following items in the list`() = runTest { + val extension = createMarkdownExtension() + extension.importMarkdown("1. one\n2. two\n3. three\n4. four") + + val state = extension.editorState + // Merge item 2 into item 1 via a single-newline delete (backspace at col 0 + // of a same-block line). Items 3 and 4 must keep their spans and renumber. + state.cursor.updatePosition(CharLineOffset(1, 0)) + state.backspaceAtCursor() + + assertEquals(listOf(0, 1, 2), extension.orderedLines()) + assertEquals("1. onetwo\n2. three\n3. four", extension.exportAsMarkdown()) + } + @Test fun `enter on empty ordered-list item exits the list`() = runTest { val extension = createMarkdownExtension()