From 3a7b09bddf6c9e0669da1d71b07f91a1d90a24a1 Mon Sep 17 00:00:00 2001 From: tkadziolka Date: Wed, 11 Sep 2024 07:33:43 +0200 Subject: [PATCH] Shaped keyword swap for partial analysis --- build.gradle.kts | 2 +- .../highlights/internal/CodeAnalyzer.kt | 5 ++ .../highlights/internal/CodeComparator.kt | 16 ++++- .../snipme/highlights/model/CodeStructure.kt | 14 ++++ .../highlights/internal/CodeAnalyzerTest.kt | 70 +++++++++++++++++++ .../highlights/internal/CodeComparatorTest.kt | 44 ++++++++++++ 6 files changed, 149 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index bcb70c1..c00f902 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ plugins { } group = "dev.snipme" -version = "0.9.1" +version = "0.9.2" kotlin { // Android diff --git a/src/commonMain/kotlin/dev/snipme/highlights/internal/CodeAnalyzer.kt b/src/commonMain/kotlin/dev/snipme/highlights/internal/CodeAnalyzer.kt index 42e4ef3..dcae125 100644 --- a/src/commonMain/kotlin/dev/snipme/highlights/internal/CodeAnalyzer.kt +++ b/src/commonMain/kotlin/dev/snipme/highlights/internal/CodeAnalyzer.kt @@ -84,6 +84,11 @@ internal object CodeAnalyzer { codeSnapshot.structure - newStructure.move(lengthDifference) } + is CodeDifference.Swap -> { + val newStructure = analyzeForLanguage(difference.new, codeSnapshot.language) + codeSnapshot.structure.replace(difference.old, newStructure) + } + CodeDifference.None -> return codeSnapshot.structure } diff --git a/src/commonMain/kotlin/dev/snipme/highlights/internal/CodeComparator.kt b/src/commonMain/kotlin/dev/snipme/highlights/internal/CodeComparator.kt index 1298152..712ea59 100644 --- a/src/commonMain/kotlin/dev/snipme/highlights/internal/CodeComparator.kt +++ b/src/commonMain/kotlin/dev/snipme/highlights/internal/CodeComparator.kt @@ -5,6 +5,7 @@ private const val WORDS_DELIMITER = " " internal sealed class CodeDifference { data class Increase(val change: String) : CodeDifference() data class Decrease(val change: String) : CodeDifference() + data class Swap(val old: String, val new: String) : CodeDifference() object None : CodeDifference() } @@ -14,7 +15,20 @@ internal object CodeComparator { val updatedWords = updated.tokenize() return when { - currentWords.size == updatedWords.size -> CodeDifference.None + currentWords.size == updatedWords.size -> { + if (currentWords == updatedWords) { + CodeDifference.None + } else { + val different = updatedWords.filterIndexed { index, updated -> + updated != currentWords[index] + } + + CodeDifference.Swap( + old = currentWords.joinToString(WORDS_DELIMITER), + new = different.joinToString(WORDS_DELIMITER) + ) + } + } currentWords.size < updatedWords.size -> CodeDifference.Increase( findDifference( diff --git a/src/commonMain/kotlin/dev/snipme/highlights/model/CodeStructure.kt b/src/commonMain/kotlin/dev/snipme/highlights/model/CodeStructure.kt index 604dc88..c120a29 100644 --- a/src/commonMain/kotlin/dev/snipme/highlights/model/CodeStructure.kt +++ b/src/commonMain/kotlin/dev/snipme/highlights/model/CodeStructure.kt @@ -86,6 +86,20 @@ data class CodeStructure( incremental = true, ) + fun replace(old: PhraseLocation, new: CodeStructure): CodeStructure = + // TODO + CodeStructure( + marks = marks - old + new.marks, + punctuations = punctuations - old + new.punctuations, + keywords = keywords - old + new.keywords, + strings = strings - old + new.strings, + literals = literals - old + new.literals, + comments = comments - old + new.comments, + multilineComments = multilineComments - old + new.multilineComments, + annotations = annotations - old + new.annotations, + incremental = true, + ) + fun printStructure(code: String) { print("marks = ${marks.join(code)}") print("punctuations = ${punctuations.join(code)}") diff --git a/src/commonTest/kotlin/dev/snipme/highlights/internal/CodeAnalyzerTest.kt b/src/commonTest/kotlin/dev/snipme/highlights/internal/CodeAnalyzerTest.kt index 742a030..c0a6beb 100644 --- a/src/commonTest/kotlin/dev/snipme/highlights/internal/CodeAnalyzerTest.kt +++ b/src/commonTest/kotlin/dev/snipme/highlights/internal/CodeAnalyzerTest.kt @@ -1,5 +1,7 @@ package dev.snipme.highlights.internal +import dev.snipme.highlights.Highlights +import dev.snipme.highlights.model.ColorHighlight import dev.snipme.highlights.model.PhraseLocation import dev.snipme.highlights.model.SyntaxLanguage import kotlin.test.Test @@ -253,4 +255,72 @@ internal class CodeAnalyzerTest { result.annotations ) } + + + @Test + fun basic_getHighlights_location() { + val highlighter = Highlights.Builder() + .language(SyntaxLanguage.JAVASCRIPT) + .build() + + val code1 = "const foo = 'bar';"; + + highlighter.setCode(code1) + val highlights1 = highlighter.getHighlights().filterIsInstance().sortedBy { it.location.start } + highlights1.size shouldEqual 4 + highlights1[0].location shouldEqual PhraseLocation(start=0, end=5) + highlights1[1].location shouldEqual PhraseLocation(start=10, end=11) + highlights1[2].location shouldEqual PhraseLocation(start=12, end=17) + highlights1[3].location shouldEqual PhraseLocation(start=17, end=18) + + highlights1.map { it.location }.printResults(code1) + + val code2 = "const foo = 'barrr';" + + highlighter.setCode(code2) + val highlights2 = highlighter.getHighlights().filterIsInstance().sortedBy { it.location.start } + highlights2.map { it.location }.printResults(code2) + highlights2.size shouldEqual 4 + highlights2[0].location shouldEqual PhraseLocation(start=0, end=5) + highlights2[1].location shouldEqual PhraseLocation(start=10, end=11) + // FAILS HERE: + highlights2[2].location shouldEqual PhraseLocation(start=12, end=19) + highlights2[3].location shouldEqual PhraseLocation(start=19, end=20) + + } + + @Test + fun basic_getHighlights_location_alt() { + var highlighter = Highlights.Builder() + .language(SyntaxLanguage.JAVASCRIPT) + .build() + + highlighter = highlighter + .getBuilder() + .code("const foo = 'bar';") + .build() + + val highlights1 = highlighter.getHighlights().filterIsInstance().sortedBy { it.location.start } + highlights1.size shouldEqual 4 + highlights1[0].location shouldEqual PhraseLocation(start=0, end=5) + highlights1[1].location shouldEqual PhraseLocation(start=10, end=11) + highlights1[2].location shouldEqual PhraseLocation(start=12, end=17) + highlights1[3].location shouldEqual PhraseLocation(start=17, end=18) + + highlighter = highlighter + .getBuilder() + .code("const foo = 'barrr';") + .build() + val highlights2 = highlighter.getHighlights().filterIsInstance().sortedBy { it.location.start } + highlights2.size shouldEqual 4 + highlights2[0].location shouldEqual PhraseLocation(start=0, end=5) + highlights2[1].location shouldEqual PhraseLocation(start=10, end=11) + highlights2[2].location shouldEqual PhraseLocation(start=12, end=19) + highlights2[3].location shouldEqual PhraseLocation(start=19, end=20) + } + + private infix fun Any?.shouldEqual(expected: Any?) { + assertEquals(expected, this) + } + } \ No newline at end of file diff --git a/src/commonTest/kotlin/dev/snipme/highlights/internal/CodeComparatorTest.kt b/src/commonTest/kotlin/dev/snipme/highlights/internal/CodeComparatorTest.kt index 197b2ea..0bc4148 100644 --- a/src/commonTest/kotlin/dev/snipme/highlights/internal/CodeComparatorTest.kt +++ b/src/commonTest/kotlin/dev/snipme/highlights/internal/CodeComparatorTest.kt @@ -75,6 +75,50 @@ internal class CodeComparatorTest { assertEquals(CodeDifference.None, result) } + @Test + fun `Returns difference for the char change in token`() { + val currentCode = "const foo = 'bar';" + + val newCode = "const foo = 'baz';" + + val result = CodeComparator.difference(currentCode, newCode) + + assertEquals(CodeDifference.Swap("'bar'", "'baz'"), result) + } + + @Test + fun `Returns difference for the char change in token`() { + val currentCode = "const foo = 'bar';" + + val newCode = "co,st foo = 'bar';" + + val result = CodeComparator.difference(currentCode, newCode) + + assertEquals(CodeDifference.Swap("const", "co,st"), result) + } + + @Test + fun `Returns difference for the char addition in single token`() { + val currentCode = "const foo = 'bar';" + + val newCode = "const foo = 'barrr';" + + val result = CodeComparator.difference(currentCode, newCode) + + assertEquals(CodeDifference.Swap("'bar'", "'barrr'"), result) + } + + @Test + fun `Returns difference for the char subtraction in single token`() { + val currentCode = "const foo = 'barrr';" + + val newCode = "const foo = 'bar';" + + val result = CodeComparator.difference(currentCode, newCode) + + assertEquals(CodeDifference.Swap("'barrr'", "'bar'"), result) + } + @Test fun `Returns only difference for complex code change`() { val currentCode = """