diff --git a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/DebouncedCodeCompletionProvider.kt b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/DebouncedCodeCompletionProvider.kt index 0bcac684..1482c133 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/DebouncedCodeCompletionProvider.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/codecompletions/DebouncedCodeCompletionProvider.kt @@ -18,7 +18,6 @@ import ee.carlrobert.codegpt.settings.service.custom.CustomServiceSettings import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings -import ee.carlrobert.codegpt.util.StringUtil.findCompletionParts import kotlinx.coroutines.channels.ProducerScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.channelFlow @@ -26,6 +25,7 @@ import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.launch import okhttp3.sse.EventSource import java.util.concurrent.atomic.AtomicReference +import kotlin.math.min import kotlin.time.Duration import kotlin.time.DurationUnit import kotlin.time.toDuration @@ -129,8 +129,8 @@ class DebouncedCodeCompletionProvider : DebouncedInlineCompletionProvider() { override fun onLineReceived(completionLine: String) { runInEdt { - val editorLineSuffix = editor.getLineSuffixAfterCaret() - if (editorLineSuffix.isEmpty()) { + var editorLineSuffix = editor.getLineSuffixAfterCaret() + if (editorLineSuffix.isBlank()) { trySend( CodeCompletionTextElement( completionLine, @@ -139,31 +139,46 @@ class DebouncedCodeCompletionProvider : DebouncedInlineCompletionProvider() { ) ) } else { - var prevStartOffset = infillRequest.caretOffset - val completionParts = - findCompletionParts(editorLineSuffix, completionLine.trimEnd()) + var caretShift = 0 - completionParts.forEach { (completionPart, offsetDelta) -> - val element = CodeCompletionTextElement( - completionPart, - infillRequest.caretOffset + offsetDelta, - TextRange.from(prevStartOffset + offsetDelta, completionPart.length), - offsetDelta, + // TODO: Handle other scenarios + val processedCompletion = + if (completionLine.startsWith(editorLineSuffix.first())) { + caretShift++ + editorLineSuffix = editorLineSuffix.substring(1) + completionLine.substring(1) + } else { + completionLine + } + + val completionWithRemovedSuffix = + processedCompletion.removeSuffix(editorLineSuffix) + + trySend( + CodeCompletionTextElement( + completionWithRemovedSuffix, + infillRequest.caretOffset + caretShift, + TextRange.from( + infillRequest.caretOffset + caretShift, + completionWithRemovedSuffix.length + ), + caretShift, completionLine ) - prevStartOffset += completionPart.length - - trySend(element) - } + ) } - } } } private fun Editor.getLineSuffixAfterCaret(): String { val lineEndOffset = document.getLineEndOffset(document.getLineNumber(caretModel.offset)) - return document.getText(TextRange(caretModel.offset, lineEndOffset)) + return document.getText( + TextRange( + caretModel.offset, + min(lineEndOffset + 1, document.textLength) + ) + ) } private fun sendNextSuggestion( diff --git a/src/main/kotlin/ee/carlrobert/codegpt/util/StringUtil.kt b/src/main/kotlin/ee/carlrobert/codegpt/util/StringUtil.kt index 7878cbaa..f89b7e67 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/util/StringUtil.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/util/StringUtil.kt @@ -1,7 +1,6 @@ package ee.carlrobert.codegpt.util import ai.grazie.nlp.utils.takeWhitespaces -import com.intellij.util.diff.Diff object StringUtil { @@ -23,44 +22,4 @@ object StringUtil { return completionLine } - - fun findCompletionParts( - editorLineSuffix: String, - completionLine: String - ): List> { - val nonOverlappingPart = findNonOverlappingPart(editorLineSuffix, completionLine) - if (nonOverlappingPart.length == completionLine.length) { - return listOf(Pair(completionLine, 0)) - } - - val result = ArrayList>() - val editorChars: IntArray = editorLineSuffix.chars().toArray() - val completionChars: IntArray = completionLine.chars().toArray() - val changes: List = - Diff.buildChanges(editorChars, completionChars)?.toList() ?: emptyList() - for (change in changes) { - val part = completionLine.substring(change.line1, change.line1 + change.inserted) - result.add(Pair(part, change.line0)) - } - - return result - } - - private fun findNonOverlappingPart( - editorLineSuffix: String, - completionLine: String - ): String { - var i = editorLineSuffix.length - 1 - var j = completionLine.length - 1 - while (i >= 0 && j >= 0 && editorLineSuffix[i] == completionLine[j]) { - i-- - j-- - } - - if (j >= 0) { - return completionLine.substring(0, j + 1) - } - - return "" - } } diff --git a/src/test/kotlin/ee/carlrobert/codegpt/util/StringUtilTest.kt b/src/test/kotlin/ee/carlrobert/codegpt/util/StringUtilTest.kt deleted file mode 100644 index 92d7db74..00000000 --- a/src/test/kotlin/ee/carlrobert/codegpt/util/StringUtilTest.kt +++ /dev/null @@ -1,54 +0,0 @@ -package ee.carlrobert.codegpt.util - -import ee.carlrobert.codegpt.util.StringUtil.findCompletionParts -import org.assertj.core.api.Assertions.assertThat -import org.junit.Test - -class StringUtilTest { - - @Test - fun `should parse completion without brackets and braces`() { - val completionLine = "root != null" - val editorLineSuffix = ") {\n" - - val result = findCompletionParts(editorLineSuffix, completionLine) - - assertThat(result[0].second).isEqualTo(0) - assertThat(result[0].first).isEqualTo("root != null") - } - - @Test - fun `should parse completion with closing bracket and brace into separate parts`() { - val completionLine = "root != null) {\n" - val editorLineSuffix = ")\n" - - val result = findCompletionParts(editorLineSuffix, completionLine) - - assertThat(result[0].second).isEqualTo(0) - assertThat(result[0].first).isEqualTo("root != null") - assertThat(result[1].second).isEqualTo(1) - assertThat(result[1].first).isEqualTo(" {") - } - - @Test - fun `should parse completion when editor suffix contains closing bracket and brace`() { - val completionLine = "root != null) {\n" - val editorLineSuffix = ") {\n" - - val result = findCompletionParts(editorLineSuffix, completionLine) - - assertThat(result[0].second).isEqualTo(0) - assertThat(result[0].first).isEqualTo("root != null") - } - - @Test - fun `should parse completion between opening and closing brackets`() { - val completionLine = "(root != null) {\n" - val editorLineSuffix = "() {\n" - - val result = findCompletionParts(editorLineSuffix, completionLine) - - assertThat(result[0].second).isEqualTo(1) - assertThat(result[0].first).isEqualTo("root != null") - } -} \ No newline at end of file