feat: improved multi-line edits (#923)

* fix: llama.cpp copy task

* feat: next edit predictions

* feat: minor clean up
This commit is contained in:
Carl-Robert Linnupuu 2025-03-17 15:32:08 +00:00
parent 9382160fe4
commit 7fbae569b1
26 changed files with 382 additions and 364 deletions

View file

@ -24,6 +24,7 @@ fun environment(key: String) = providers.environmentVariable(key)
plugins {
id("codegpt.java-conventions")
alias(libs.plugins.changelog)
alias(libs.plugins.protobuf)
}
group = properties("pluginGroup").get()
@ -67,6 +68,9 @@ dependencies {
implementation(libs.jsoup)
implementation(libs.commons.text)
implementation(libs.jtokkit)
implementation(libs.grpc.protobuf)
implementation(libs.grpc.stub)
implementation(libs.grpc.netty.shaded)
testImplementation(kotlin("test"))
}
@ -162,3 +166,22 @@ tasks {
}
}
}
protobuf {
protoc {
artifact = libs.protobuf.protoc.get().toString()
}
plugins {
create("grpc") {
artifact = libs.protobuf.java.get().toString()
}
}
generateProtoTasks {
all()
.forEach {
it.plugins {
create("grpc")
}
}
}
}

View file

@ -20,6 +20,7 @@ import java.util.List;
public class TelemetryConfiguration extends CompositeConfiguration {
public static final String KEY_MODE = "ee.carlrobert.telemetry.mode";
public static final String KEY_COMPLETION_TELEMETRY_ENABLED = "ee.carlrobert.telemetry.completionStatisticsEnabled";
private static final SaveableFileConfiguration FILE = new SaveableFileConfiguration(
Directories.PATH.resolve("ee.carlrobert.intellij.telemetry"));
@ -50,6 +51,14 @@ public class TelemetryConfiguration extends CompositeConfiguration {
setMode(Mode.valueOf(enabled));
}
public boolean isCompletionTelemetryEnabled() {
return Boolean.parseBoolean(get(KEY_COMPLETION_TELEMETRY_ENABLED));
}
public void setCompletionTelemetryEnabled(boolean enabled) {
put(KEY_COMPLETION_TELEMETRY_ENABLED, String.valueOf(enabled));
}
public boolean isDebug() {
return getMode() == Mode.DEBUG;
}

View file

@ -21,19 +21,27 @@ import javax.swing.JPanel;
*/
public class TelemetryComponent {
private static final String DESCRIPTION =
private static final String USAGE_DESCRIPTION =
"Help ProxyAI improve its products by sending anonymous data about features and plugins used, "
+ "hardware and software configuration.<br/>"
+ "<br/>"
+ "Please note that this will not include personal data or any sensitive Information.<br/>"
+ "Please note that this will not include personal data or any sensitive Information.";
private static final String COMPLETION_DESCRIPTION =
"Help ProxyAI improve its products by sharing your code inputs and completions.<br/>"
+ "<br/>"
+ "The data sent complies with the <a href=\"https://tryproxy.io/privacy\">Privacy Policy</a>.";
private final JPanel panel;
private final JBCheckBox enabled = new JBCheckBox("Send usage statistics");
private final JBCheckBox usageStatisticsEnabled = new JBCheckBox("Send usage statistics");
private final JBCheckBox completionStatisticsEnabled = new JBCheckBox("Send completion statistics");
public TelemetryComponent() {
this.panel = FormBuilder.createFormBuilder()
.addComponent(createCommentedPanel(enabled, DESCRIPTION), 1)
.addComponent(
createCommentedPanel(usageStatisticsEnabled, USAGE_DESCRIPTION), 1)
.addComponent(
createCommentedPanel(completionStatisticsEnabled, COMPLETION_DESCRIPTION), 1)
.addComponentFillVertically(new JPanel(), 0)
.getPanel();
}
@ -50,15 +58,22 @@ public class TelemetryComponent {
}
public JComponent getPreferredFocusedComponent() {
return enabled;
return usageStatisticsEnabled;
}
public boolean isEnabled() {
return enabled.isSelected();
public boolean getUsageStatisticsEnabled() {
return usageStatisticsEnabled.isSelected();
}
public void setEnabled(boolean enabled) {
this.enabled.setSelected(enabled);
public void setUsageStatisticsEnabled(boolean usageStatisticsEnabled) {
this.usageStatisticsEnabled.setSelected(usageStatisticsEnabled);
}
public boolean getCompletionStatisticsEnabled() {
return completionStatisticsEnabled.isSelected();
}
public void setCompletionStatisticsEnabled(boolean completionStatisticsEnabled) {
this.completionStatisticsEnabled.setSelected(completionStatisticsEnabled);
}
}

View file

@ -53,13 +53,15 @@ public class TelemetryConfigurable implements SearchableConfigurable {
@Override
public boolean isModified() {
boolean modified = false;
modified |= (component.isEnabled() != configuration.isEnabled());
modified |= (component.getUsageStatisticsEnabled() != configuration.isEnabled());
modified |= (component.getCompletionStatisticsEnabled() != configuration.isCompletionTelemetryEnabled());
return modified;
}
@Override
public void apply() {
configuration.setEnabled(component.isEnabled());
configuration.setEnabled(component.getUsageStatisticsEnabled());
configuration.setCompletionTelemetryEnabled(component.getCompletionStatisticsEnabled());
try {
configuration.save();
} catch (IOException e) {
@ -69,7 +71,8 @@ public class TelemetryConfigurable implements SearchableConfigurable {
@Override
public void reset() {
component.setEnabled(configuration.isEnabled());
component.setUsageStatisticsEnabled(configuration.isEnabled());
component.setCompletionStatisticsEnabled(configuration.isCompletionTelemetryEnabled());
}
@Override

View file

@ -9,4 +9,8 @@
files="[\\/]src[\\/]main[\\/]java[\\/]ee[\\/]carlrobert[\\/]codegpt[\\/]telemetry"
checks="."/>
<suppress files="[\\/]src[\\/]main[\\/]java[\\/]grammar" checks="."/>
<suppress
files="[\\/]build[\\/]generated[\\/]source[\\/]proto[\\/]main"
checks="."/>
</suppressions>

View file

@ -15,6 +15,9 @@ kotlin = "2.0.0"
llm-client = "0.8.37"
okio = "3.9.0"
tree-sitter = "0.24.4"
grpc = "1.71.0"
protobuf = "3.25.1"
protobuf-plugin = "0.9.4"
[libraries]
analytics = { module = "com.rudderstack.sdk.java.analytics:analytics", version.ref = "analytics" }
@ -31,6 +34,12 @@ kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", v
llm-client = { module = "ee.carlrobert:llm-client", version.ref = "llm-client" }
okio = { module = "com.squareup.okio:okio", version.ref = "okio" }
tree-sitter = { module = "io.github.bonede:tree-sitter", version.ref = "tree-sitter" }
grpc-protobuf = { module = "io.grpc:grpc-protobuf", version.ref = "grpc" }
grpc-stub = { module = "io.grpc:grpc-stub", version.ref = "grpc" }
grpc-netty-shaded = { module = "io.grpc:grpc-netty-shaded", version.ref = "grpc" }
protobuf-protoc = { module = "com.google.protobuf:protoc", version.ref = "protobuf" }
protobuf-java = { module = "io.grpc:protoc-gen-grpc-java", version.ref = "grpc" }
[plugins]
changelog = { id = "org.jetbrains.changelog", version.ref = "changelog" }
protobuf = { id = "com.google.protobuf", version.ref = "protobuf-plugin" }

View file

@ -1,76 +0,0 @@
package ee.carlrobert.codegpt
import com.intellij.codeInsight.editorActions.CopyPastePreProcessor
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.RawText
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.CodeGptApiKey
import ee.carlrobert.codegpt.credentials.CredentialsStore.isCredentialSet
import ee.carlrobert.codegpt.predictions.PredictionService
import ee.carlrobert.codegpt.settings.GeneralSettings
import ee.carlrobert.codegpt.settings.service.ServiceType
import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class CodeGPTCopyPastePreProcessor : CopyPastePreProcessor {
private companion object {
const val MAX_TOKEN_LIMIT = 4096
const val FREE_TIER_TOKEN_LIMIT = 2048
private val logger = thisLogger()
}
override fun preprocessOnCopy(
file: PsiFile,
startOffsets: IntArray,
endOffsets: IntArray,
text: String
): String {
return text
}
override fun preprocessOnPaste(
project: Project,
file: PsiFile,
editor: Editor,
text: String,
rawText: RawText?
): String {
try {
displayPrediction(editor.document.text) {
service<PredictionService>().displayPastePrediction(editor, text)
}
} catch (e: Exception) {
logger.error("Error displaying paste prediction")
}
return text
}
private fun displayPrediction(documentText: String, handleDisplay: () -> Unit = {}) {
if (!isPredictionEnabled()) return
val currentTokens = getDocumentTokenCount(documentText)
if (currentTokens > MAX_TOKEN_LIMIT) return
if (!isCredentialSet(CodeGptApiKey) && currentTokens > FREE_TIER_TOKEN_LIMIT) return
CoroutineScope(Dispatchers.IO).launch {
handleDisplay()
}
}
private fun isPredictionEnabled(): Boolean =
GeneralSettings.getSelectedService() == ServiceType.CODEGPT &&
service<CodeGPTServiceSettings>().state.codeAssistantEnabled
private fun getDocumentTokenCount(text: String): Int =
EncodingManager.getInstance().countTokens(text)
}

View file

@ -8,8 +8,6 @@ import com.intellij.codeInsight.lookup.impl.LookupImpl
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.components.service
import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.CodeGptApiKey
import ee.carlrobert.codegpt.credentials.CredentialsStore.isCredentialSet
import ee.carlrobert.codegpt.predictions.PredictionService
import ee.carlrobert.codegpt.settings.GeneralSettings
import ee.carlrobert.codegpt.settings.service.ServiceType
@ -34,22 +32,14 @@ class CodeGPTLookupListener : LookupManagerListener {
override fun itemSelected(event: LookupEvent) {
val editor = newLookup.editor
val encodingManager = EncodingManager.getInstance()
if (GeneralSettings.getSelectedService() != ServiceType.CODEGPT
|| !service<CodeGPTServiceSettings>().state.codeAssistantEnabled
|| encodingManager.countTokens(editor.document.text) > 4096
|| !isCredentialSet(CodeGptApiKey) && encodingManager.countTokens(editor.document.text) > 2048
|| !service<CodeGPTServiceSettings>().state.nextEditsEnabled
) {
return
}
ApplicationManager.getApplication().executeOnPooledThread {
service<PredictionService>().displayLookupPrediction(
editor,
event,
beforeApply,
cursorOffset
)
service<PredictionService>().displayInlineDiff(editor)
}
}
})

View file

@ -14,11 +14,11 @@ abstract class CodeAssistantFeatureToggleAction(
override fun actionPerformed(e: AnActionEvent) {
val settings = service<CodeGPTServiceSettings>().state
settings.codeAssistantEnabled = enableFeatureAction
settings.nextEditsEnabled = enableFeatureAction
}
override fun update(e: AnActionEvent) {
val codeAssistantEnabled = service<CodeGPTServiceSettings>().state.codeAssistantEnabled
val codeAssistantEnabled = service<CodeGPTServiceSettings>().state.nextEditsEnabled
e.presentation.isVisible = GeneralSettings.getSelectedService() == CODEGPT
&& codeAssistantEnabled != enableFeatureAction
@ -30,6 +30,6 @@ abstract class CodeAssistantFeatureToggleAction(
}
}
class EnableCodeAssistantAction : CodeAssistantFeatureToggleAction(true)
class EnableNextEditsAction : CodeAssistantFeatureToggleAction(true)
class DisableCodeAssistantAction : CodeAssistantFeatureToggleAction(false)
class DisableNextEditsAction : CodeAssistantFeatureToggleAction(false)

View file

@ -37,7 +37,8 @@ abstract class CodeCompletionFeatureToggleActions(
override fun update(e: AnActionEvent) {
val selectedService = GeneralSettings.getSelectedService()
val codeCompletionEnabled =
e.project?.service<CodeCompletionService>()?.isCodeCompletionsEnabled(selectedService) ?: false
e.project?.service<CodeCompletionService>()?.isCodeCompletionsEnabled(selectedService)
?: false
e.presentation.isVisible = codeCompletionEnabled != enableFeatureAction
e.presentation.isEnabled = when (selectedService) {
CODEGPT,
@ -60,4 +61,4 @@ abstract class CodeCompletionFeatureToggleActions(
class EnableCompletionsAction : CodeCompletionFeatureToggleActions(true)
class DisableCompletionsAction : CodeCompletionFeatureToggleActions(false)
class DisableCompletionsAction : CodeCompletionFeatureToggleActions(false)

View file

@ -17,7 +17,6 @@ import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler
import com.intellij.psi.PsiDocumentManager
import com.intellij.util.concurrency.ThreadingAssertions
import ee.carlrobert.codegpt.CodeGPTKeys.REMAINING_EDITOR_COMPLETION
import ee.carlrobert.codegpt.EncodingManager
import ee.carlrobert.codegpt.predictions.PredictionService
import ee.carlrobert.codegpt.settings.GeneralSettings
import ee.carlrobert.codegpt.settings.service.ServiceType
@ -47,18 +46,13 @@ class CodeCompletionInsertAction :
)
}
val beforeApply = editor.document.text
InlineCompletion.getHandlerOrNull(editor)?.insert()
if (GeneralSettings.getSelectedService() == ServiceType.CODEGPT
&& service<CodeGPTServiceSettings>().state.codeAssistantEnabled
&& service<EncodingManager>().countTokens(editor.document.text) <= 4096) {
&& service<CodeGPTServiceSettings>().state.nextEditsEnabled
) {
ApplicationManager.getApplication().executeOnPooledThread {
service<PredictionService>().displayAutocompletePrediction(
editor,
textToInsert,
beforeApply
)
service<PredictionService>().displayInlineDiff(editor)
}
return
}

View file

@ -9,6 +9,7 @@ import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange
import ee.carlrobert.codegpt.CodeGPTKeys.REMAINING_EDITOR_COMPLETION
import ee.carlrobert.codegpt.codecompletions.edit.GrpcClientService
import ee.carlrobert.codegpt.settings.GeneralSettings
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings
import ee.carlrobert.codegpt.settings.service.ServiceType
@ -47,6 +48,13 @@ class DebouncedCodeCompletionProvider : DebouncedInlineCompletionProvider() {
get() = CodeCompletionProviderPresentation()
override suspend fun getSuggestionDebounced(request: InlineCompletionRequest): InlineCompletionSuggestion {
if (GeneralSettings.getSelectedService() == ServiceType.CODEGPT
&& service<CodeGPTServiceSettings>().state.nextEditsEnabled
) {
predictNextEdit(request)
return InlineCompletionSingleSuggestion.build(elements = emptyFlow())
}
return if (service<ConfigurationSettings>().state.codeCompletionSettings.multiLineEnabled) {
getMultiLineSuggestionDebounced(request)
} else {
@ -54,6 +62,18 @@ class DebouncedCodeCompletionProvider : DebouncedInlineCompletionProvider() {
}
}
private fun predictNextEdit(request: InlineCompletionRequest) {
val project = request.editor.project ?: return
try {
CompletionProgressNotifier.update(project, true)
project.service<GrpcClientService>().getNextEdit(request.editor)
} catch (ex: Exception) {
logger.error("Error communicating with server: ${ex.message}")
} finally {
CompletionProgressNotifier.update(project, false)
}
}
private fun getSingleLineSuggestionDebounced(request: InlineCompletionRequest): InlineCompletionSuggestion {
val editor = request.editor
val remainingCompletion = REMAINING_EDITOR_COMPLETION.get(editor) ?: ""
@ -85,7 +105,11 @@ class DebouncedCodeCompletionProvider : DebouncedInlineCompletionProvider() {
.getCodeCompletionAsync(
infillRequest,
CodeCompletionMultiLineEventListener(request) {
trySend(InlineCompletionGrayTextElement(it))
if (it.isEmpty() && service<CodeGPTServiceSettings>().state.nextEditsEnabled) {
predictNextEdit(request)
} else {
trySend(InlineCompletionGrayTextElement(it))
}
}
)
}
@ -106,10 +130,6 @@ class DebouncedCodeCompletionProvider : DebouncedInlineCompletionProvider() {
return InlineCompletionSuggestion.Default(emptyFlow())
}
if (LookupManager.getActiveLookup(request.editor) != null) {
return InlineCompletionSuggestion.Default(emptyFlow())
}
request.editor.project?.let {
CompletionProgressNotifier.update(it, true)
}
@ -140,7 +160,7 @@ class DebouncedCodeCompletionProvider : DebouncedInlineCompletionProvider() {
}
if (!codeCompletionsEnabled) {
return false
return selectedService == ServiceType.CODEGPT && service<CodeGPTServiceSettings>().state.nextEditsEnabled
}
if (LookupManager.getActiveLookup(event.toRequest()?.editor) != null) {

View file

@ -0,0 +1,179 @@
package ee.carlrobert.codegpt.codecompletions.edit
import com.intellij.notification.NotificationAction.createSimpleExpiring
import com.intellij.notification.NotificationType
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.jetbrains.rd.util.UUID
import ee.carlrobert.codegpt.credentials.CredentialsStore
import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.CodeGptApiKey
import ee.carlrobert.codegpt.predictions.CodeSuggestionDiffViewer
import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings
import ee.carlrobert.codegpt.telemetry.core.configuration.TelemetryConfiguration
import ee.carlrobert.codegpt.ui.OverlayUtil
import ee.carlrobert.codegpt.util.GitUtil
import ee.carlrobert.service.AcceptEditRequest
import ee.carlrobert.service.NextEditRequest
import ee.carlrobert.service.NextEditResponse
import ee.carlrobert.service.NextEditServiceImplGrpc
import io.grpc.*
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder
import io.grpc.stub.StreamObserver
import java.util.concurrent.Executor
import java.util.concurrent.TimeUnit
import kotlin.coroutines.cancellation.CancellationException
@Service(Service.Level.PROJECT)
class GrpcClientService(private val project: Project) : Disposable {
private var channel: ManagedChannel? = null
private var stub: NextEditServiceImplGrpc.NextEditServiceImplStub? = null
private var prevObserver: NextEditStreamObserver? = null
companion object {
private const val HOST = "grpc.tryproxy.io"
private const val PORT = 9090
private const val SHUTDOWN_TIMEOUT_SECONDS = 5L
private val logger = thisLogger()
}
@Synchronized
private fun ensureConnection() {
if (channel == null || channel?.isShutdown == true) {
try {
channel = NettyChannelBuilder.forAddress(HOST, PORT).build()
stub = NextEditServiceImplGrpc.newStub(channel)
.withCallCredentials(
ApiKeyCredentials(CredentialsStore.getCredential(CodeGptApiKey) ?: "")
)
logger.info("gRPC connection established")
} catch (e: Exception) {
logger.error("Failed to establish gRPC connection", e)
throw e
}
}
}
fun getNextEdit(editor: Editor, isManuallyOpened: Boolean = false) {
ensureConnection()
prevObserver?.onCompleted()
val request = NextEditRequest.newBuilder()
.setFileName(editor.virtualFile.name)
.setFileContent(editor.document.text)
.setGitDiff(GitUtil.getCurrentChanges(project) ?: "")
.setCursorPosition(runReadAction { editor.caretModel.offset })
.setEnableTelemetry(TelemetryConfiguration.getInstance().isCompletionTelemetryEnabled)
.build()
prevObserver = NextEditStreamObserver(editor, isManuallyOpened) {
dispose()
}
stub?.nextEdit(request, prevObserver)
}
class NextEditStreamObserver(
private val editor: Editor,
private val isManuallyOpened: Boolean,
private val onDispose: () -> Unit
) : StreamObserver<NextEditResponse> {
override fun onNext(response: NextEditResponse) {
runInEdt {
CodeSuggestionDiffViewer.displayInlineDiff(editor, response, isManuallyOpened)
}
}
override fun onError(ex: Throwable) {
if (ex is CancellationException) {
onCompleted()
return
}
try {
if (ex is StatusRuntimeException) {
if (ex.status.code == Status.Code.CANCELLED) {
onCompleted()
return
}
OverlayUtil.showNotification(
ex.status.description ?: ex.localizedMessage,
NotificationType.ERROR,
createSimpleExpiring("Disable multi-line edits") {
service<CodeGPTServiceSettings>().state.nextEditsEnabled =
false
})
} else {
logger.error("Something went wrong", ex)
}
} finally {
onDispose()
}
}
override fun onCompleted() {
}
}
fun acceptEdit(responseId: UUID, acceptedEdit: String) {
NextEditServiceImplGrpc
.newBlockingStub(channel)
.acceptEdit(
AcceptEditRequest.newBuilder()
.setResponseId(responseId.toString())
.setAcceptedEdit(acceptedEdit)
.build()
)
}
override fun dispose() {
channel?.let { ch ->
if (!ch.isShutdown) {
try {
ch.shutdown().awaitTermination(SHUTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS)
logger.info("gRPC connection closed")
} catch (e: InterruptedException) {
logger.warn("Interrupted while shutting down gRPC channel", e)
Thread.currentThread().interrupt()
} finally {
if (!ch.isTerminated) {
ch.shutdownNow()
}
channel = null
}
}
}
}
}
internal class ApiKeyCredentials(private val apiKey: String) : CallCredentials() {
companion object {
private val API_KEY_HEADER: Metadata.Key<String> =
Metadata.Key.of("x-api-key", Metadata.ASCII_STRING_MARSHALLER)
}
override fun applyRequestMetadata(
requestInfo: RequestInfo?,
executor: Executor,
metadataApplier: MetadataApplier
) {
executor.execute {
try {
val headers = Metadata()
headers.put(API_KEY_HEADER, apiKey)
metadataApplier.apply(headers)
} catch (e: Throwable) {
metadataApplier.fail(Status.UNAUTHENTICATED.withCause(e))
}
}
}
}

View file

@ -27,15 +27,19 @@ import com.intellij.openapi.util.*
import com.intellij.testFramework.LightVirtualFile
import com.intellij.ui.components.JBLabel
import com.intellij.ui.components.JBScrollPane
import com.intellij.util.application
import com.intellij.util.concurrency.annotations.RequiresEdt
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.components.BorderLayoutPanel
import ee.carlrobert.codegpt.CodeGPTBundle
import ee.carlrobert.codegpt.CodeGPTKeys
import ee.carlrobert.codegpt.codecompletions.edit.GrpcClientService
import ee.carlrobert.service.NextEditResponse
import java.awt.BorderLayout
import java.awt.Dimension
import java.awt.FlowLayout
import java.awt.Point
import java.util.*
import javax.swing.Box
import javax.swing.JComponent
import javax.swing.JPanel
@ -45,6 +49,7 @@ import kotlin.math.max
class CodeSuggestionDiffViewer(
request: DiffRequest,
private val responseId: UUID,
private val mainEditor: Editor,
private val isManuallyOpened: Boolean
) : UnifiedDiffViewer(MyDiffContext(mainEditor.project), request), Disposable {
@ -117,6 +122,10 @@ class CodeSuggestionDiffViewer(
if (changes.size == 1) {
popup.dispose()
}
application.executeOnPooledThread {
project?.service<GrpcClientService>()?.acceptEdit(responseId, change.toString())
}
}
fun isVisible(): Boolean {
@ -288,10 +297,11 @@ class CodeSuggestionDiffViewer(
@RequiresEdt
fun displayInlineDiff(
editor: Editor,
nextRevision: String,
nextEditResponse: NextEditResponse,
isManuallyOpened: Boolean = false
) {
if (editor.virtualFile == null || editor.isViewer) {
val nextRevision = nextEditResponse.nextRevision
if (editor.virtualFile == null || editor.isViewer || nextRevision.isEmpty()) {
return
}
@ -304,7 +314,12 @@ class CodeSuggestionDiffViewer(
}
val diffRequest = createSimpleDiffRequest(editor, nextRevision)
val diffViewer = CodeSuggestionDiffViewer(diffRequest, editor, isManuallyOpened)
val diffViewer = CodeSuggestionDiffViewer(
diffRequest,
UUID.fromString(nextEditResponse.id),
editor,
isManuallyOpened
)
editor.putUserData(CodeGPTKeys.EDITOR_PREDICTION_DIFF_VIEWER, diffViewer)
diffViewer.rediff(true)
}

View file

@ -1,76 +1,23 @@
package ee.carlrobert.codegpt.predictions
import com.intellij.codeInsight.lookup.LookupEvent
import com.intellij.diff.DiffManager
import com.intellij.notification.NotificationListener
import com.intellij.notification.NotificationType
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.testFramework.LightVirtualFile
import ee.carlrobert.codegpt.CodeGPTKeys
import ee.carlrobert.codegpt.CodeGPTKeys.PENDING_PREDICTION_CALL
import ee.carlrobert.codegpt.codecompletions.CompletionProgressNotifier
import ee.carlrobert.codegpt.completions.CompletionClientProvider
import ee.carlrobert.codegpt.conversations.ConversationsState
import ee.carlrobert.codegpt.settings.prompts.PromptsSettings
import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings
import ee.carlrobert.codegpt.ui.OverlayUtil
import ee.carlrobert.codegpt.ui.OverlayUtil.getDefaultNotification
import ee.carlrobert.codegpt.codecompletions.edit.GrpcClientService
import ee.carlrobert.codegpt.util.EditorDiffUtil.createDiffRequest
import ee.carlrobert.codegpt.util.EditorUtil
import ee.carlrobert.codegpt.util.GitUtil
import ee.carlrobert.llm.client.codegpt.request.prediction.AutocompletionPredictionRequest
import ee.carlrobert.llm.client.codegpt.request.prediction.DirectPredictionRequest
import ee.carlrobert.llm.client.codegpt.request.prediction.PastePredictionRequest
import ee.carlrobert.llm.client.codegpt.request.prediction.PredictionRequest
import ee.carlrobert.llm.client.codegpt.response.CodeGPTException
import ee.carlrobert.llm.client.codegpt.response.PredictionResponse
import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionStandardMessage
import okhttp3.Request
@Service
class PredictionService {
fun displayDirectPrediction(editor: Editor) {
val request = CompletionClientProvider.getCodeGPTClient()
.buildDirectPredictionRequest(createDirectPredictionRequest(editor))
displayInlineDiff(editor, request, true)
}
fun displayAutocompletePrediction(editor: Editor, textToInsert: String, beforeApply: String) {
val request = CompletionClientProvider.getCodeGPTClient()
.buildAutocompletionPredictionRequest(
createAutocompletePredictionRequest(editor, textToInsert, beforeApply)
)
displayInlineDiff(editor, request)
}
fun displayLookupPrediction(
editor: Editor,
event: LookupEvent,
beforeApply: String,
cursorOffset: Int
) {
val request = CompletionClientProvider.getCodeGPTClient()
.buildLookupPredictionRequest(
createAutocompletePredictionRequest(
editor,
event.item?.lookupString ?: "",
beforeApply,
cursorOffset
)
)
displayInlineDiff(editor, request)
}
fun displayPastePrediction(editor: Editor, pastedText: String) {
val request = CompletionClientProvider.getCodeGPTClient()
.buildPastePredictionRequest(createPastePredictionRequest(editor, pastedText))
displayInlineDiff(editor, request)
companion object {
private val logger = thisLogger()
}
fun acceptPrediction(editor: Editor) {
@ -81,102 +28,18 @@ class PredictionService {
}
}
private fun displayInlineDiff(
fun displayInlineDiff(
editor: Editor,
request: Request,
isManuallyOpened: Boolean = false
) {
val prediction = getPrediction(editor, request)
if (prediction != null && !prediction.nextRevision.isNullOrEmpty()) {
runInEdt {
CodeSuggestionDiffViewer.displayInlineDiff(
editor,
prediction.nextRevision,
isManuallyOpened
)
}
}
}
private fun getPrediction(editor: Editor, request: Request): PredictionResponse? {
editor.project?.let {
CompletionProgressNotifier.update(it, true)
}
val pendingCall = PENDING_PREDICTION_CALL.get(editor)
if (pendingCall != null) {
pendingCall.cancel()
return null
}
val project = editor.project ?: return
try {
val client = CompletionClientProvider.getCodeGPTClient()
val call = client.createNewCall(request)
PENDING_PREDICTION_CALL.set(editor, call)
return client.getPrediction(call)
} catch (e: CodeGPTException) {
OverlayUtil.notify(
getDefaultNotification(e.detail, NotificationType.ERROR).apply {
setListener(NotificationListener.UrlOpeningListener(true))
})
service<CodeGPTServiceSettings>().state.codeAssistantEnabled = false
return null
} catch (e: Exception) {
if (e.cause?.message != "Canceled") {
throw e
}
return null
CompletionProgressNotifier.update(project, true)
project.service<GrpcClientService>().getNextEdit(editor, isManuallyOpened)
} catch (ex: Exception) {
logger.error("Error communicating with server: ${ex.message}")
} finally {
PENDING_PREDICTION_CALL.set(editor, null)
editor.project?.let {
CompletionProgressNotifier.update(it, false)
}
}
}
private fun createAutocompletePredictionRequest(
editor: Editor,
textToInsert: String,
beforeApply: String,
cursorOffset: Int? = null,
): AutocompletionPredictionRequest {
val predictionRequest = AutocompletionPredictionRequest()
predictionRequest.appliedCompletion = textToInsert
predictionRequest.previousRevision = beforeApply
setDefaultParams(editor, predictionRequest, cursorOffset)
return predictionRequest
}
private fun createPastePredictionRequest(
editor: Editor,
pastedCode: String
): PastePredictionRequest {
val predictionRequest = PastePredictionRequest(pastedCode)
setDefaultParams(editor, predictionRequest)
return predictionRequest
}
private fun createDirectPredictionRequest(editor: Editor): DirectPredictionRequest {
val predictionRequest = DirectPredictionRequest()
setDefaultParams(editor, predictionRequest)
return predictionRequest
}
private fun setDefaultParams(editor: Editor, request: PredictionRequest, offset: Int? = null) {
val messages: MutableList<OpenAIChatCompletionStandardMessage> = mutableListOf()
ConversationsState.getInstance().currentConversation?.messages?.forEach {
messages.add(OpenAIChatCompletionStandardMessage("user", it.prompt))
messages.add(OpenAIChatCompletionStandardMessage("assistant", it.response))
}
request.apply {
currentRevision = runReadAction { editor.document.text }
customPrompt =
service<PromptsSettings>().state.coreActions.codeAssistant.instructions
cursorOffset = offset ?: runReadAction { editor.caretModel.offset }
gitChanges = GitUtil.getCurrentChanges(editor.project!!)
openFiles = EditorUtil.getOpenFiles(editor.project!!)
conversationMessages = messages.toList()
CompletionProgressNotifier.update(project, false)
}
}

View file

@ -12,9 +12,6 @@ import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.actionSystem.EditorAction
import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler
import ee.carlrobert.codegpt.CodeGPTKeys
import ee.carlrobert.codegpt.EncodingManager
import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.CodeGptApiKey
import ee.carlrobert.codegpt.credentials.CredentialsStore.isCredentialSet
import ee.carlrobert.codegpt.settings.GeneralSettings
import ee.carlrobert.codegpt.settings.service.ServiceType
import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings
@ -33,14 +30,14 @@ class TriggerCustomPredictionAction : EditorAction(Handler()), HintManagerImpl.A
return
}
if (!service<CodeGPTServiceSettings>().state.codeAssistantEnabled) {
if (!service<CodeGPTServiceSettings>().state.nextEditsEnabled) {
val notification = OverlayUtil.getDefaultNotification(
"Please enable Code Assistant before using this feature.",
"Please enable multi-line edits before using this feature.",
NotificationType.WARNING,
)
notification.addAction(object : AnAction("Enable Code Assistant") {
notification.addAction(object : AnAction("Enable Multi-Line Edits") {
override fun actionPerformed(e: AnActionEvent) {
service<CodeGPTServiceSettings>().state.codeAssistantEnabled = true
service<CodeGPTServiceSettings>().state.nextEditsEnabled = true
notification.hideBalloon()
}
})
@ -49,18 +46,8 @@ class TriggerCustomPredictionAction : EditorAction(Handler()), HintManagerImpl.A
return
}
val encodingManager = service<EncodingManager>()
if (!isCredentialSet(CodeGptApiKey) && encodingManager.countTokens(editor.document.text) > 2048) {
OverlayUtil.showNotification("The file exceeds the token limit of 2,048. Please upgrade your plan to access higher limits.")
return
}
if (encodingManager.countTokens(editor.document.text) > 4096) {
OverlayUtil.showNotification("The file exceeds the token limit of 4,096.")
return
}
ApplicationManager.getApplication().executeOnPooledThread {
service<PredictionService>().displayDirectPrediction(editor)
service<PredictionService>().displayInlineDiff(editor, true)
}
}

View file

@ -30,8 +30,6 @@ class PromptsSettingsState : BaseState() {
class CoreActionsState : BaseState() {
companion object {
val DEFAULT_CODE_ASSISTANT_PROMPT =
getResourceContent("/prompts/core/code-assistant.txt")
val DEFAULT_EDIT_CODE_PROMPT = getResourceContent("/prompts/core/edit-code.txt")
val DEFAULT_GENERATE_COMMIT_MESSAGE_PROMPT =
getResourceContent("/prompts/core/generate-commit-message.txt")
@ -43,11 +41,6 @@ class CoreActionsState : BaseState() {
getResourceContent("/prompts/core/review-changes.txt")
}
var codeAssistant by property(CoreActionPromptDetailsState().apply {
name = "Code Assistant"
code = "CODE_ASSISTANT"
instructions = DEFAULT_CODE_ASSISTANT_PROMPT
})
var editCode by property(CoreActionPromptDetailsState().apply {
name = "Edit Code"
code = "EDIT_CODE"

View file

@ -104,7 +104,6 @@ class PromptsForm {
val coreActionsFormState = getFormState<CoreActionPromptDetails>(coreActionsNode)
settings.coreActions.apply {
codeAssistant = coreActionsFormState[0].toState()
editCode = coreActionsFormState[1].toState()
fixCompileErrors = coreActionsFormState[2].toState()
generateCommitMessage = coreActionsFormState[3].toState()
@ -157,7 +156,6 @@ class PromptsForm {
val formState = getFormState<CoreActionPromptDetails>(coreActionsNode)
val stateActions = listOf(
settingsState.codeAssistant,
settingsState.editCode,
settingsState.fixCompileErrors,
settingsState.generateCommitMessage,
@ -213,7 +211,6 @@ class PromptsForm {
val settings = service<PromptsSettings>().state
listOf(
settings.coreActions.codeAssistant,
settings.coreActions.editCode,
settings.coreActions.fixCompileErrors,
settings.coreActions.generateCommitMessage,

View file

@ -11,7 +11,6 @@ import com.intellij.util.ui.components.BorderLayoutPanel
import ee.carlrobert.codegpt.settings.Placeholder
import ee.carlrobert.codegpt.settings.Placeholder.GIT_DIFF
import ee.carlrobert.codegpt.settings.prompts.CommitMessageTemplate
import ee.carlrobert.codegpt.settings.prompts.CoreActionsState.Companion.DEFAULT_CODE_ASSISTANT_PROMPT
import ee.carlrobert.codegpt.settings.prompts.CoreActionsState.Companion.DEFAULT_EDIT_CODE_PROMPT
import ee.carlrobert.codegpt.settings.prompts.CoreActionsState.Companion.DEFAULT_FIX_COMPILE_ERRORS_PROMPT
import ee.carlrobert.codegpt.settings.prompts.CoreActionsState.Companion.DEFAULT_GENERATE_COMMIT_MESSAGE_PROMPT
@ -30,26 +29,6 @@ class CoreActionsDetailsPanel : PromptDetailsPanel {
override fun create(details: CoreActionPromptDetails): JComponent {
val editorPanel = when (details.code) {
"CODE_ASSISTANT" -> CoreActionEditorPanel(
details,
DEFAULT_CODE_ASSISTANT_PROMPT,
buildString {
append("<p>Template for generating code assistant messages. Use the following placeholders to insert dynamic values:</p>\n")
append(
"<ul>${
listOf(
GIT_DIFF,
Placeholder.OPEN_FILES,
Placeholder.ACTIVE_CONVERSATION,
).joinToString("\n") {
"<li><strong>${it.name}</strong>: ${it.description}</li>"
}
}</ul>\n"
)
},
listOf("{GIT_DIFF}", "{OPEN_FILES}", "{ACTIVE_CONVERSATION}")
)
"EDIT_CODE" -> CoreActionEditorPanel(
details,
DEFAULT_EDIT_CODE_PROMPT,
@ -91,7 +70,6 @@ class CoreActionsDetailsPanel : PromptDetailsPanel {
init {
val settings = service<PromptsSettings>().state.coreActions
listOf(
settings.codeAssistant,
settings.editCode,
settings.fixCompileErrors,
settings.generateCommitMessage,

View file

@ -32,9 +32,9 @@ class CodeGPTServiceForm {
renderer = CustomComboBoxRenderer()
}
private val codeAssistantEnabledCheckBox = JBCheckBox(
CodeGPTBundle.get("shared.enableCodeAssistant"),
service<CodeGPTServiceSettings>().state.codeAssistantEnabled
private val enableNextEditsEnabledCheckBox = JBCheckBox(
"Enable multi-line edits",
service<CodeGPTServiceSettings>().state.nextEditsEnabled
)
private val codeCompletionsEnabledCheckBox = JBCheckBox(
@ -73,9 +73,9 @@ class CodeGPTServiceForm {
UIUtil.createComment("settingsConfigurable.service.codegpt.codeCompletionModel.comment")
)
.addVerticalGap(4)
.addComponent(codeAssistantEnabledCheckBox)
.addComponent(enableNextEditsEnabledCheckBox)
.addComponent(
UIUtil.createComment("settingsConfigurable.service.codegpt.enableCodeAssistant.comment", 90)
UIUtil.createComment("settingsConfigurable.service.codegpt.enableNextEdits.comment", 90)
)
.addVerticalGap(4)
.addComponent(codeCompletionsEnabledCheckBox)
@ -90,14 +90,14 @@ class CodeGPTServiceForm {
fun isModified() = service<CodeGPTServiceSettings>().state.run {
(chatCompletionModelComboBox.selectedItem as CodeGPTModel).code != chatCompletionSettings.model
|| (codeCompletionModelComboBox.selectedItem as CodeGPTModel).code != codeCompletionSettings.model
|| codeAssistantEnabledCheckBox.isSelected != codeAssistantEnabled
|| enableNextEditsEnabledCheckBox.isSelected != nextEditsEnabled
|| codeCompletionsEnabledCheckBox.isSelected != codeCompletionSettings.codeCompletionsEnabled
|| getApiKey() != getCredential(CodeGptApiKey)
}
fun applyChanges() {
service<CodeGPTServiceSettings>().state.run {
codeAssistantEnabled = codeAssistantEnabledCheckBox.isSelected
nextEditsEnabled = enableNextEditsEnabledCheckBox.isSelected
chatCompletionSettings.model =
(chatCompletionModelComboBox.selectedItem as CodeGPTModel).code
codeCompletionSettings.codeCompletionsEnabled =
@ -110,7 +110,7 @@ class CodeGPTServiceForm {
fun resetForm() {
service<CodeGPTServiceSettings>().state.run {
codeAssistantEnabledCheckBox.isSelected = codeAssistantEnabled
enableNextEditsEnabledCheckBox.isSelected = nextEditsEnabled
chatCompletionModelComboBox.selectedItem = chatCompletionSettings.model
codeCompletionModelComboBox.selectedItem = codeCompletionSettings.model
codeCompletionsEnabledCheckBox.isSelected =

View file

@ -13,7 +13,7 @@ class CodeGPTServiceSettings :
class CodeGPTServiceSettingsState : BaseState() {
var chatCompletionSettings by property(CodeGPTServiceChatCompletionSettingsState())
var codeCompletionSettings by property(CodeGPTServiceCodeCompletionSettingsState())
var codeAssistantEnabled by property(false)
var nextEditsEnabled by property(true)
}
class CodeGPTServiceChatCompletionSettingsState : BaseState() {

View file

@ -0,0 +1,29 @@
// src/main/proto/edit.proto
syntax = "proto3";
option java_multiple_files = true;
option java_package = "ee.carlrobert.service";
import "google/protobuf/empty.proto";
service NextEditServiceImpl {
rpc NextEdit (NextEditRequest) returns (stream NextEditResponse);
rpc AcceptEdit (AcceptEditRequest) returns (google.protobuf.Empty);
}
message NextEditRequest {
string file_content = 1;
string file_name = 2;
int32 cursor_position = 3;
string git_diff = 4;
bool enable_telemetry = 5;
}
message NextEditResponse {
string id = 1;
string next_revision = 2;
}
message AcceptEditRequest {
string response_id = 1;
string accepted_edit = 2;
}

View file

@ -238,16 +238,16 @@
<override-text place="popup" use-text-of-place="MainMenu"/>
</action>
<action
id="statusbar.enableCodeAssistant"
class="ee.carlrobert.codegpt.actions.EnableCodeAssistantAction">
id="statusbar.enableNextEdits"
class="ee.carlrobert.codegpt.actions.EnableNextEditsAction">
<keyboard-shortcut first-keystroke="ctrl shift alt a" keymap="$default"/>
<override-text place="MainMenu"/>
<override-text place="popup" use-text-of-place="MainMenu"/>
</action>
<action
id="statusbar.disableCodeAssistant"
class="ee.carlrobert.codegpt.actions.DisableCodeAssistantAction">
id="statusbar.disableNextEdits"
class="ee.carlrobert.codegpt.actions.DisableNextEditsAction">
<keyboard-shortcut first-keystroke="ctrl shift alt a" keymap="$default"/>
<override-text place="MainMenu"/>
@ -275,8 +275,8 @@
<separator/>
<reference id="statusbar.stopServer" />
<reference id="statusbar.startServer" />
<reference id="statusbar.disableCodeAssistant" />
<reference id="statusbar.enableCodeAssistant" />
<reference id="statusbar.disableNextEdits" />
<reference id="statusbar.enableNextEdits" />
<reference id="statusbar.disableCompletions" />
<reference id="statusbar.enableCompletions" />
</group>

View file

@ -25,12 +25,12 @@ action.statusbar.enableCompletions.MainMenu.text=Enable Completions
action.statusbar.disableCompletions.text=Disable Completions
action.statusbar.disableCompletions.description=Disable Code Completions
action.statusbar.disableCompletions.MainMenu.text=Disable Completions
action.statusbar.enableCodeAssistant.text=Enable Code Assistant
action.statusbar.enableCodeAssistant.description=Enable Code Assistant
action.statusbar.enableCodeAssistant.MainMenu.text=Enable Code Assistant
action.statusbar.disableCodeAssistant.text=Disable Code Assistant
action.statusbar.disableCodeAssistant.description=Disable Code Assistant
action.statusbar.disableCodeAssistant.MainMenu.text=Disable Code Assistant
action.statusbar.enableNextEdits.text=Enable Multi-Line Edits
action.statusbar.enableNextEdits.description=Enable Multi-Line Edits
action.statusbar.enableNextEdits.MainMenu.text=Enable Multi-Line Edits
action.statusbar.disableNextEdits.text=Disable Multi-Line Edits
action.statusbar.disableNextEdits.description=Disable Multi-Line Edits
action.statusbar.disableNextEdits.MainMenu.text=Disable Multi-Line Edits
action.compareWithOriginal.title=Compare with Original
action.applyDirectly.title=Auto Apply
action.explainGitCommit.title=Explain Commit with ProxyAI
@ -42,7 +42,7 @@ settingsConfigurable.service.label=Selected provider:
settingsConfigurable.service.codegpt.apiKey.comment=You can find the API key in your <a href="https://tryproxy.io/account">User settings</a>.
settingsConfigurable.service.codegpt.chatCompletionModel.comment=Choose a model optimized for conversational interactions, including assistance with general queries and explanations.
settingsConfigurable.service.codegpt.codeCompletionModel.comment=Choose a model tailored for code completion-related tasks.
settingsConfigurable.service.codegpt.enableCodeAssistant.comment=If checked, Code Assistant will suggest related code updates as you make changes.
settingsConfigurable.service.codegpt.enableNextEdits.comment=If checked, ProxyAI will suggest multi-line changes as you type.
settingsConfigurable.service.codegpt.enableCodeCompletion.comment=If checked, ProxyAI will suggest changes as you type.
settingsConfigurable.service.custom.openai.apiKey.comment=A secret value stored in the system's Keychain or KeePass, depending on your OS. This approach is recommended over storing the secret in the header as plain text.
settingsConfigurable.service.custom.openai.apiKey.provider.name=Custom provider name:
@ -280,7 +280,6 @@ imageAccordion.title=Attached image
shared.image=Image
shared.chatCompletions=Chat Completions
shared.codeCompletions=Code Completions
shared.enableCodeAssistant=<html>Enable Code Assistant <small><strong><sup style="color: green;">NEW</sup></strong></small></html>
codeCompletionsForm.enableFeatureText=Enable code completions
codeCompletionsForm.parseResponseAsChatCompletions=Parse response as Chat Completions
codeCompletionsForm.overrideFimTemplate.label=Use built-in FIM template

View file

@ -1,18 +0,0 @@
Recent changes made to the project:
<git_diff>
{GIT_DIFF}
</git_diff>
Currently open files:
<open_files>
{OPEN_FILES}
</open_files>
Previous chat history between the AI and user:
<active_conversation_history>
{ACTIVE_CONVERSATION}
</active_conversation_history>
Important Guidelines:
- Carefully follow the instructions marked with the @AI keyword and act accordingly. Then, delete the instruction.

View file

@ -18,6 +18,7 @@ class CodeCompletionServiceTest : IntegrationTest() {
fun `test code completion with ProxyAI provider`() {
useCodeGPTService()
service<CodeGPTServiceSettings>().state.nextEditsEnabled = false
service<ConfigurationSettings>().state.codeCompletionSettings.multiLineEnabled = false
myFixture.configureByText(
"CompletionTest.java",
@ -58,6 +59,7 @@ class CodeCompletionServiceTest : IntegrationTest() {
fun `test code completion with OpenAI provider`() {
useOpenAIService()
service<CodeGPTServiceSettings>().state.nextEditsEnabled = false
service<ConfigurationSettings>().state.codeCompletionSettings.multiLineEnabled = false
myFixture.configureByText(
"CompletionTest.java",
@ -98,6 +100,7 @@ class CodeCompletionServiceTest : IntegrationTest() {
fun `_test apply inline suggestions without initial following text`() {
useCodeGPTService()
service<CodeGPTServiceSettings>().state.nextEditsEnabled = false
service<ConfigurationSettings>().state.codeCompletionSettings.multiLineEnabled = false
myFixture.configureByText(
"CompletionTest.java",
@ -215,7 +218,7 @@ class CodeCompletionServiceTest : IntegrationTest() {
fun `_test apply inline suggestions with initial following text`() {
useCodeGPTService()
service<CodeGPTServiceSettings>().state.codeAssistantEnabled = false
service<CodeGPTServiceSettings>().state.nextEditsEnabled = false
service<ConfigurationSettings>().state.codeCompletionSettings.multiLineEnabled = false
myFixture.configureByText(
"CompletionTest.java",
@ -287,6 +290,7 @@ class CodeCompletionServiceTest : IntegrationTest() {
fun `test adjust completion line whitespaces`() {
useCodeGPTService()
service<CodeGPTServiceSettings>().state.nextEditsEnabled = false
service<ConfigurationSettings>().state.codeCompletionSettings.multiLineEnabled = false
myFixture.configureByText(
"CompletionTest.java",