diff --git a/build.gradle.kts b/build.gradle.kts index c00c9a63bb..4b3673e74c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -63,6 +63,7 @@ plugins { val platformVersion: String by project val platformType: String by project val javaVersion: String by project +val majorPlatformVersion = platformVersion.split(".").first() group = properties("pluginGroup")!! @@ -126,6 +127,10 @@ dependencies { instrumentationTools() pluginVerifier() testFramework(TestFrameworkType.Platform) + + if (majorPlatformVersion.toInt() >= 2024) { + bundledModule("intellij.platform.vcs.dvcs.impl") + } } implementation("org.commonmark:commonmark:0.22.0") @@ -586,6 +591,8 @@ tasks { test { dependsOn(project.tasks.getByPath("buildCody")) } sourceSets { + main { kotlin.srcDir("src/intellij${majorPlatformVersion}/kotlin") } + create("integrationTest") { kotlin.srcDir("src/integrationTest/kotlin") compileClasspath += main.get().output + configurations.testCompileClasspath.get() diff --git a/gradle.properties b/gradle.properties index 58cb45cc38..bd60f5e67e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,7 +19,7 @@ javaVersion=17 # See https://plugins.jetbrains.com/docs/intellij/kotlin.html#kotlin-standard-library for details. # suppress inspection "UnusedProperty" kotlin.stdlib.default.dependency=false -kotlin.daemon.jvmargs=-Xmx2g -Xms500m +kotlin.daemon.jvmargs=-Xmx4g -Xms500m # See https://docs.gradle.org/current/userguide/build_cache.html#sec:build_cache_configure nodeBinaries.commit=8755ae4c05fd476cd23f2972049111ba436c86d4 nodeBinaries.version=v20.12.2 diff --git a/src/main/kotlin/com/intellij/codeInsight/inline/completion/InlineCompletionEvent.kt b/src/intellij2023/kotlin/com/intellij/codeInsight/inline/completion/InlineCompletionEvent.kt similarity index 100% rename from src/main/kotlin/com/intellij/codeInsight/inline/completion/InlineCompletionEvent.kt rename to src/intellij2023/kotlin/com/intellij/codeInsight/inline/completion/InlineCompletionEvent.kt diff --git a/src/main/kotlin/com/intellij/codeInsight/inline/completion/InlineCompletionProviderID.kt b/src/intellij2023/kotlin/com/intellij/codeInsight/inline/completion/InlineCompletionProviderID.kt similarity index 100% rename from src/main/kotlin/com/intellij/codeInsight/inline/completion/InlineCompletionProviderID.kt rename to src/intellij2023/kotlin/com/intellij/codeInsight/inline/completion/InlineCompletionProviderID.kt diff --git a/src/main/kotlin/com/intellij/codeInsight/inline/completion/elements/InlineCompletionElement.kt b/src/intellij2023/kotlin/com/intellij/codeInsight/inline/completion/elements/InlineCompletionElement.kt similarity index 100% rename from src/main/kotlin/com/intellij/codeInsight/inline/completion/elements/InlineCompletionElement.kt rename to src/intellij2023/kotlin/com/intellij/codeInsight/inline/completion/elements/InlineCompletionElement.kt diff --git a/src/main/kotlin/com/intellij/codeInsight/inline/completion/elements/InlineCompletionGrayTextElement.kt b/src/intellij2023/kotlin/com/intellij/codeInsight/inline/completion/elements/InlineCompletionGrayTextElement.kt similarity index 100% rename from src/main/kotlin/com/intellij/codeInsight/inline/completion/elements/InlineCompletionGrayTextElement.kt rename to src/intellij2023/kotlin/com/intellij/codeInsight/inline/completion/elements/InlineCompletionGrayTextElement.kt diff --git a/src/intellij2023/kotlin/com/intellij/codeInsight/inline/completion/suggestion/InlineCompletionSuggestion.kt b/src/intellij2023/kotlin/com/intellij/codeInsight/inline/completion/suggestion/InlineCompletionSuggestion.kt new file mode 100644 index 0000000000..0d739beda8 --- /dev/null +++ b/src/intellij2023/kotlin/com/intellij/codeInsight/inline/completion/suggestion/InlineCompletionSuggestion.kt @@ -0,0 +1,34 @@ +package com.intellij.codeInsight.inline.completion.suggestion + +import com.intellij.codeInsight.inline.completion.elements.InlineCompletionElement +import com.intellij.openapi.util.UserDataHolderBase +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.FlowCollector + +interface InlineCompletionSuggestion { + + object Empty : InlineCompletionSuggestion {} +} + +interface InlineCompletionSingleSuggestion : InlineCompletionSuggestion { + + companion object { + + /** @see [InlineCompletionVariant.build] */ + fun build( + data: UserDataHolderBase = UserDataHolderBase(), + buildElements: + suspend FlowCollector.(data: UserDataHolderBase) -> Unit + ): InlineCompletionSingleSuggestion { + return object : InlineCompletionSingleSuggestion {} + } + + /** @see InlineCompletionVariant.build */ + fun build( + data: UserDataHolderBase = UserDataHolderBase(), + elements: Flow + ): InlineCompletionSingleSuggestion { + return object : InlineCompletionSingleSuggestion {} + } + } +} diff --git a/src/main/kotlin/com/sourcegraph/cody/autocomplete/CodyInlineCompletionProvider.kt b/src/intellij2023/kotlin/com/sourcegraph/cody/autocomplete/CodyInlineCompletionProvider.kt similarity index 94% rename from src/main/kotlin/com/sourcegraph/cody/autocomplete/CodyInlineCompletionProvider.kt rename to src/intellij2023/kotlin/com/sourcegraph/cody/autocomplete/CodyInlineCompletionProvider.kt index 88d13e9fb3..27a2fee8de 100644 --- a/src/main/kotlin/com/sourcegraph/cody/autocomplete/CodyInlineCompletionProvider.kt +++ b/src/intellij2023/kotlin/com/sourcegraph/cody/autocomplete/CodyInlineCompletionProvider.kt @@ -2,6 +2,8 @@ package com.sourcegraph.cody.autocomplete import com.intellij.codeInsight.inline.completion.* import com.intellij.codeInsight.inline.completion.elements.InlineCompletionGrayTextElement +import com.intellij.codeInsight.inline.completion.suggestion.InlineCompletionSingleSuggestion +import com.intellij.codeInsight.inline.completion.suggestion.InlineCompletionSuggestion import com.intellij.openapi.application.ApplicationInfo import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ReadAction @@ -36,9 +38,9 @@ class CodyInlineCompletionProvider : InlineCompletionProvider { suspend fun getSuggestion(request: InlineCompletionRequest): InlineCompletionSuggestion { ApplicationManager.getApplication().assertIsNonDispatchThread() val editor = request.editor - val project = editor.project ?: return InlineCompletionSuggestion.empty() + val project = editor.project ?: return InlineCompletionSuggestion.Empty if (!isImplicitAutocompleteEnabledForEditor(editor)) { - return InlineCompletionSuggestion.empty() + return InlineCompletionSuggestion.Empty } val lookupString: String? = null // todo: can we use this provider for lookups? @@ -56,9 +58,9 @@ class CodyInlineCompletionProvider : InlineCompletionProvider { val completions = fetchCompletions(project, editor, triggerKind, cancellationToken, lookupString) .completeOnTimeout(null, 1, TimeUnit.SECONDS) - .get() ?: return InlineCompletionSuggestion.empty() + .get() ?: return InlineCompletionSuggestion.Empty - return InlineCompletionSuggestion.withFlow { + return InlineCompletionSingleSuggestion.build { completions.items .firstNotNullOfOrNull { WriteCommandAction.runWriteCommandAction( diff --git a/src/intellij2024/kotlin/com/sourcegraph/cody/autocomplete/CodyInlineCompletionProvider.kt b/src/intellij2024/kotlin/com/sourcegraph/cody/autocomplete/CodyInlineCompletionProvider.kt new file mode 100644 index 0000000000..7425ce6d48 --- /dev/null +++ b/src/intellij2024/kotlin/com/sourcegraph/cody/autocomplete/CodyInlineCompletionProvider.kt @@ -0,0 +1,147 @@ +package com.sourcegraph.cody.autocomplete + +import com.intellij.codeInsight.inline.completion.* +import com.intellij.codeInsight.inline.completion.elements.InlineCompletionGrayTextElement +import com.intellij.codeInsight.inline.completion.suggestion.InlineCompletionSingleSuggestion +import com.intellij.codeInsight.inline.completion.suggestion.InlineCompletionSuggestion +import com.intellij.openapi.application.ApplicationInfo +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.ReadAction +import com.intellij.openapi.client.ClientSessionsManager +import com.intellij.openapi.command.WriteCommandAction +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.event.DocumentEvent +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.TextRange +import com.intellij.util.concurrency.annotations.RequiresReadLock +import com.sourcegraph.cody.agent.CodyAgentService +import com.sourcegraph.cody.agent.protocol.AutocompleteResult +import com.sourcegraph.cody.agent.protocol.CompletionItemParams +import com.sourcegraph.cody.statusbar.CodyStatusService.Companion.resetApplication +import com.sourcegraph.cody.vscode.CancellationToken +import com.sourcegraph.cody.vscode.InlineCompletionTriggerKind +import com.sourcegraph.cody.vscode.IntelliJTextDocument +import com.sourcegraph.config.ConfigUtil +import com.sourcegraph.utils.CodyEditorUtil.getTextRange +import com.sourcegraph.utils.CodyEditorUtil.isImplicitAutocompleteEnabledForEditor +import com.sourcegraph.utils.CodyFormatter +import java.util.concurrent.CompletableFuture +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicReference + +class CodyInlineCompletionProvider : InlineCompletionProvider { + private val logger = Logger.getInstance(CodyInlineCompletionProvider::class.java) + private val currentJob = AtomicReference(CancellationToken()) + override val id = InlineCompletionProviderID("Cody") + + override suspend fun getSuggestion(request: InlineCompletionRequest): InlineCompletionSuggestion { + ApplicationManager.getApplication().assertIsNonDispatchThread() + val editor = request.editor + val project = editor.project ?: return InlineCompletionSuggestion.Empty + if (!isImplicitAutocompleteEnabledForEditor(editor)) { + return InlineCompletionSuggestion.Empty + } + val lookupString: String? = null // todo: can we use this provider for lookups? + + cancelCurrentJob(project) + val cancellationToken = CancellationToken() + currentJob.set(cancellationToken) + + val triggerKind = + if (request.event is InlineCompletionEvent.DirectCall) { + InlineCompletionTriggerKind.INVOKE + } else { + InlineCompletionTriggerKind.AUTOMATIC + } + + val completions = + fetchCompletions(project, editor, triggerKind, cancellationToken, lookupString) + .completeOnTimeout(null, 1, TimeUnit.SECONDS) + .get() ?: return InlineCompletionSuggestion.Empty + + return InlineCompletionSingleSuggestion.build { + completions.items + .firstNotNullOfOrNull { + WriteCommandAction.runWriteCommandAction( + editor.project) { + val range = getTextRange(editor.document, it.range) + val originalText = editor.document.getText(range) + + val formattedCompletionText: String = + if (System.getProperty("cody.autocomplete.enableFormatting") == "false") { + it.insertText + } else { + CodyFormatter.formatStringBasedOnDocument( + it.insertText, project, editor.document, range, request.endOffset) + } + + val completionText = formattedCompletionText.removeSuffix(originalText) + if (completionText.trim().isBlank()) { + null + } else { + + CodyAgentService.withAgent(project) { agent -> + agent.server.completionSuggested(CompletionItemParams(it.id)) + } + + InlineCompletionGrayTextElement(completionText) + } + } + } + ?.let { emit(it) } + } + } + + @RequiresReadLock + private fun fetchCompletions( + project: Project, + editor: Editor, + triggerKind: InlineCompletionTriggerKind, + cancellationToken: CancellationToken, + lookupString: String?, + ): CompletableFuture { + val textDocument = IntelliJTextDocument(editor, project) + val offset = ReadAction.compute { editor.caretModel.offset } + val lineNumber = editor.document.getLineNumber(offset) + val caretPositionInLine = offset - editor.document.getLineStartOffset(lineNumber) + val originalText = editor.document.getText(TextRange(offset - caretPositionInLine, offset)) + + val result = CompletableFuture() + Utils.triggerAutocompleteAsync( + project, + editor, + offset, + textDocument, + triggerKind, + cancellationToken, + lookupString, + originalText, + logger) { autocompleteResult -> + result.complete(autocompleteResult) + } + return result + } + + private fun cancelCurrentJob(project: Project?) { + currentJob.get().abort() + project?.let { resetApplication(it) } + } + + override fun isEnabled(event: InlineCompletionEvent): Boolean { + return isEnabled() + } + + fun isEnabled(event: DocumentEvent): Boolean { + return isEnabled() + } + + private fun isEnabled(): Boolean { + val ideVersion = ApplicationInfo.getInstance().build.baselineVersion + val isRemoteDev = ClientSessionsManager.getAppSession()?.isRemote ?: false + return ideVersion >= 233 && + isRemoteDev && + ConfigUtil.isCodyEnabled() && + ConfigUtil.isCodyAutocompleteEnabled() + } +} diff --git a/src/main/kotlin/com/intellij/codeInsight/inline/completion/InlineCompletionSuggestion.kt b/src/main/kotlin/com/intellij/codeInsight/inline/completion/InlineCompletionSuggestion.kt deleted file mode 100644 index 23c99b3e15..0000000000 --- a/src/main/kotlin/com/intellij/codeInsight/inline/completion/InlineCompletionSuggestion.kt +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the -// Apache 2.0 license. -package com.intellij.codeInsight.inline.completion - -import com.intellij.codeInsight.inline.completion.elements.InlineCompletionElement -import com.intellij.openapi.util.UserDataHolderBase -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.FlowCollector -import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.flow - -/** - * Abstract class representing an inline completion suggestion. - * - * Provides the suggestion flow for generating only one suggestion. - * - * @see InlineCompletionElement - */ -abstract class InlineCompletionSuggestion : UserDataHolderBase() { - abstract val suggestionFlow: Flow - - class Default(override val suggestionFlow: Flow) : - InlineCompletionSuggestion() - - companion object { - fun empty(): InlineCompletionSuggestion = Default(emptyFlow()) - - fun withFlow( - buildSuggestion: suspend FlowCollector.() -> Unit - ): InlineCompletionSuggestion { - return Default(flow(buildSuggestion)) - } - } -}