diff --git a/ComposeTextEditor/src/commonMain/kotlin/com/darkrockstudios/texteditor/state/SpanManager.kt b/ComposeTextEditor/src/commonMain/kotlin/com/darkrockstudios/texteditor/state/SpanManager.kt index 22b0773..ffbec82 100644 --- a/ComposeTextEditor/src/commonMain/kotlin/com/darkrockstudios/texteditor/state/SpanManager.kt +++ b/ComposeTextEditor/src/commonMain/kotlin/com/darkrockstudios/texteditor/state/SpanManager.kt @@ -152,25 +152,27 @@ class SpanManager { private fun mergeSpans(spans: List): List { if (spans.isEmpty()) return emptyList() + // Group spans by style and merge within each group val result = mutableListOf() - val sorted = spans.sortedBy { it.start } - - var current = sorted.first() - var mergeCount = 0 - - for (i in 1 until sorted.size) { - val next = sorted[i] - if (current.overlaps(next) || current.isAdjacent(next)) { - current = current.merge(next) - mergeCount++ - } else { - result.add(current) - current = next + val spansByStyle = spans.groupBy { it.style } + + spansByStyle.forEach { (_, styleSpans) -> + val sorted = styleSpans.sortedBy { it.start } + var current = sorted.first() + var mergeCount = 0 + + for (i in 1 until sorted.size) { + val next = sorted[i] + if (current.overlaps(next) || current.isAdjacent(next)) { + current = current.merge(next) + mergeCount++ + } else { + result.add(current) + current = next + } } + result.add(current) } - result.add(current) - - //println("Style Spans Merged: $mergeCount") return result } diff --git a/ComposeTextEditor/src/desktopTest/kotlin/texteditmanager/TextEditManagerSpanMergeTest.kt b/ComposeTextEditor/src/desktopTest/kotlin/texteditmanager/TextEditManagerSpanMergeTest.kt index b45bce8..506e62f 100644 --- a/ComposeTextEditor/src/desktopTest/kotlin/texteditmanager/TextEditManagerSpanMergeTest.kt +++ b/ComposeTextEditor/src/desktopTest/kotlin/texteditmanager/TextEditManagerSpanMergeTest.kt @@ -4,9 +4,12 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextDecoration import com.darkrockstudios.texteditor.state.SpanManager import com.darkrockstudios.texteditor.utils.mergeAnnotatedStrings import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test @@ -297,4 +300,39 @@ class TextEditManagerSpanMergeTest { val blueSpan = result.spanStyles.first { it.start == 12 && it.end == 15 } assertEquals(Color.Blue, blueSpan.item.color) } + + @Test + fun `test insert character into overlapping styled text - bold and underline`() { + // Arrange - create text with overlapping bold and underline spans + val original = buildAnnotatedString { + append("Welcome") + addStyle(SpanStyle(fontWeight = FontWeight.Bold), 0, 7) // bold + addStyle(SpanStyle(textDecoration = TextDecoration.Underline), 0, 7) // underline + } + + // Simulate inserting "a" at position 3 (between "l" and "c") + val newText = buildAnnotatedString { + append("a") + addStyle(SpanStyle(fontWeight = FontWeight.Bold), 0, 1) // bold + addStyle(SpanStyle(textDecoration = TextDecoration.Underline), 0, 1) // underline + } + val insertionIndex = 3 + + // Act + val result = manager.mergeAnnotatedStrings(original, start = insertionIndex, newText = newText) + + // Assert + assertEquals("Welacome", result.text) + // Should have 2 spans: bold(0-8) and underline(0-8) + assertEquals(2, result.spanStyles.size) + + val boldSpan = result.spanStyles.find { it.item.fontWeight == FontWeight.Bold } + val underlineSpan = result.spanStyles.find { it.item.textDecoration == TextDecoration.Underline } + + assertNotNull(boldSpan) + assertNotNull(underlineSpan) + + boldSpan?.let { assertEquals(0, it.start); assertEquals(8, it.end) } + underlineSpan?.let { assertEquals(0, it.start); assertEquals(8, it.end) } + } }