diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index 0ad7d744..183eebe5 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -4,6 +4,6 @@
-
+
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
index 352d191f..55d7285f 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -30,8 +30,8 @@ repositories {
// Read more: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html
intellij {
// version.set("LATEST-EAP-SNAPSHOT")
- version.set("2022.3.1")
- type.set("PC") // Target IDE Platform
+ version.set("2024.1")
+ type.set("IU") // Target IDE Platform
plugins.set(listOf(
"Git4Idea",
@@ -51,8 +51,8 @@ tasks {
}
patchPluginXml {
- sinceBuild.set("223")
- untilBuild.set("241.*")
+ sinceBuild.set("241")
+ untilBuild.set("242.*")
}
signPlugin {
diff --git a/refact_lsp b/refact_lsp
index 2c0a9c7b..88d050b1 100644
--- a/refact_lsp
+++ b/refact_lsp
@@ -1 +1 @@
-v0.7.2
+main
\ No newline at end of file
diff --git a/src/main/kotlin/com/smallcloud/refactai/Initializer.kt b/src/main/kotlin/com/smallcloud/refactai/Initializer.kt
index 249a834e..219000a4 100644
--- a/src/main/kotlin/com/smallcloud/refactai/Initializer.kt
+++ b/src/main/kotlin/com/smallcloud/refactai/Initializer.kt
@@ -2,8 +2,11 @@ package com.smallcloud.refactai
import com.intellij.ide.plugins.PluginInstaller
import com.intellij.openapi.Disposable
+import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger
+import com.intellij.openapi.keymap.Keymap
+import com.intellij.openapi.keymap.KeymapManagerListener
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.openapi.startup.StartupActivity
@@ -11,6 +14,7 @@ import com.smallcloud.refactai.account.LoginStateService
import com.smallcloud.refactai.account.login
import com.smallcloud.refactai.io.ConnectivityManager
import com.smallcloud.refactai.io.InferenceGlobalContext
+import com.smallcloud.refactai.listeners.ACTION_ID_
import com.smallcloud.refactai.listeners.UninstallListener
import com.smallcloud.refactai.lsp.LSPProcessHolder
import com.smallcloud.refactai.lsp.lspProjectInitialize
@@ -20,6 +24,7 @@ import com.smallcloud.refactai.settings.AppSettingsState
import com.smallcloud.refactai.settings.settingsStartup
import com.smallcloud.refactai.statistic.UsageStats
import com.smallcloud.refactai.struct.DeploymentMode
+import org.jetbrains.annotations.NonNls
import java.util.concurrent.atomic.AtomicBoolean
@@ -51,6 +56,45 @@ class Initializer : StartupActivity, Disposable {
PluginInstaller.addStateListener(UninstallListener())
UpdateChecker.instance
LSPProcessHolder.instance.startup()
+
+ ApplicationManager.getApplication()
+ .messageBus
+ .connect(PluginState.instance)
+ .subscribe(KeymapManagerListener.TOPIC, object : KeymapManagerListener {
+ override fun shortcutsChanged(
+ keymap: Keymap,
+ actionIds: @NonNls MutableCollection,
+ fromSettings: Boolean
+ ) {
+ if (Thread.currentThread().stackTrace.count { it.className.startsWith("com.smallcloud.refactai.Initializer") } > 1) {
+ return
+ }
+ for (id in actionIds) {
+ if (!listOf(IdeActions.ACTION_INSERT_INLINE_COMPLETION, ACTION_ID_).contains(id)) {
+ continue
+ }
+ val shortcuts = keymap.getShortcuts(id)
+ if (id == IdeActions.ACTION_INSERT_INLINE_COMPLETION) {
+ keymap.removeAllActionShortcuts(ACTION_ID_)
+ for (shortcut in shortcuts) {
+ keymap.addShortcut(
+ ACTION_ID_,
+ shortcut
+ )
+ }
+ } else if (id == ACTION_ID_) {
+ keymap.removeAllActionShortcuts(IdeActions.ACTION_INSERT_INLINE_COMPLETION)
+ for (shortcut in shortcuts) {
+ keymap.addShortcut(
+ IdeActions.ACTION_INSERT_INLINE_COMPLETION,
+ shortcut
+ )
+ }
+ }
+ }
+ }
+ })
+
}
PrivacyService.instance.projectOpened(project)
lspProjectInitialize(ProjectRootManager.getInstance(project).contentRoots.map { it.toString() })
diff --git a/src/main/kotlin/com/smallcloud/refactai/modes/completion/structs/Completion.kt b/src/main/kotlin/com/smallcloud/refactai/codecompletion/Completion.kt
similarity index 85%
rename from src/main/kotlin/com/smallcloud/refactai/modes/completion/structs/Completion.kt
rename to src/main/kotlin/com/smallcloud/refactai/codecompletion/Completion.kt
index a04cf68e..a9f2971e 100644
--- a/src/main/kotlin/com/smallcloud/refactai/modes/completion/structs/Completion.kt
+++ b/src/main/kotlin/com/smallcloud/refactai/codecompletion/Completion.kt
@@ -1,4 +1,4 @@
-package com.smallcloud.refactai.modes.completion.structs
+package com.smallcloud.refactai.codecompletion
data class Completion(
diff --git a/src/main/kotlin/com/smallcloud/refactai/modes/completion/CompletionTracker.kt b/src/main/kotlin/com/smallcloud/refactai/codecompletion/CompletionTracker.kt
similarity index 94%
rename from src/main/kotlin/com/smallcloud/refactai/modes/completion/CompletionTracker.kt
rename to src/main/kotlin/com/smallcloud/refactai/codecompletion/CompletionTracker.kt
index f5e9baea..bf78a847 100644
--- a/src/main/kotlin/com/smallcloud/refactai/modes/completion/CompletionTracker.kt
+++ b/src/main/kotlin/com/smallcloud/refactai/codecompletion/CompletionTracker.kt
@@ -1,4 +1,4 @@
-package com.smallcloud.refactai.modes.completion
+package com.smallcloud.refactai.codecompletion
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.Key
diff --git a/src/main/kotlin/com/smallcloud/refactai/modes/EditorTextState.kt b/src/main/kotlin/com/smallcloud/refactai/codecompletion/EditorTextState.kt
similarity index 97%
rename from src/main/kotlin/com/smallcloud/refactai/modes/EditorTextState.kt
rename to src/main/kotlin/com/smallcloud/refactai/codecompletion/EditorTextState.kt
index d8e7add2..23aa60f7 100644
--- a/src/main/kotlin/com/smallcloud/refactai/modes/EditorTextState.kt
+++ b/src/main/kotlin/com/smallcloud/refactai/codecompletion/EditorTextState.kt
@@ -1,4 +1,4 @@
-package com.smallcloud.refactai.modes
+package com.smallcloud.refactai.codecompletion
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.Editor
diff --git a/src/main/kotlin/com/smallcloud/refactai/codecompletion/InlineCompletionGrayTextElement.kt b/src/main/kotlin/com/smallcloud/refactai/codecompletion/InlineCompletionGrayTextElement.kt
new file mode 100644
index 00000000..2f79a6c5
--- /dev/null
+++ b/src/main/kotlin/com/smallcloud/refactai/codecompletion/InlineCompletionGrayTextElement.kt
@@ -0,0 +1,167 @@
+package com.smallcloud.refactai.codecompletion
+
+import com.intellij.codeInsight.inline.completion.InlineCompletionFontUtils
+import com.intellij.codeInsight.inline.completion.elements.InlineCompletionElement
+import com.intellij.openapi.actionSystem.IdeActions
+import com.intellij.openapi.editor.Editor
+import com.intellij.openapi.editor.EditorCustomElementRenderer
+import com.intellij.openapi.editor.Inlay
+import com.intellij.openapi.editor.VisualPosition
+import com.intellij.openapi.editor.ex.util.EditorActionAvailabilityHint
+import com.intellij.openapi.editor.ex.util.addActionAvailabilityHint
+import com.intellij.openapi.editor.markup.TextAttributes
+import com.intellij.openapi.util.Disposer
+import org.jetbrains.annotations.ApiStatus
+import java.awt.Graphics
+import java.awt.Rectangle
+
+
+// delete this file ASAP
+
+fun String.formatBeforeRendering(editor: Editor): String {
+ val tabSize = editor.settings.getTabSize(editor.project)
+ val tab = " ".repeat(tabSize)
+ return replace("\t", tab)
+}
+
+class InlineBlockElementRenderer(private val editor: Editor, lines: List) : EditorCustomElementRenderer {
+
+ private val font = InlineCompletionFontUtils.font(editor)
+ private val width = editor
+ .contentComponent
+ .getFontMetrics(font)
+ .stringWidth(lines.maxBy { it.length })
+
+ val lines = lines.map { it.formatBeforeRendering(editor) }
+
+ override fun calcWidthInPixels(inlay: Inlay<*>) = width
+
+ override fun calcHeightInPixels(inlay: Inlay<*>) = editor.lineHeight * lines.size
+
+ override fun paint(inlay: Inlay<*>, g: Graphics, targetRegion: Rectangle, textAttributes: TextAttributes) {
+ g.color = InlineCompletionFontUtils.color(editor)
+ g.font = font
+ lines.forEachIndexed { i, it -> g.drawString(it, 0, targetRegion.y + editor.ascent + i * editor.lineHeight) }
+ }
+}
+
+class InlineSuffixRenderer(private val editor: Editor, suffix: String) : EditorCustomElementRenderer {
+ private val font = InlineCompletionFontUtils.font(editor)
+ private val width = editor.contentComponent.getFontMetrics(font).stringWidth(suffix)
+
+ val suffix = suffix.formatBeforeRendering(editor)
+
+ override fun calcWidthInPixels(inlay: Inlay<*>): Int = width
+ override fun calcHeightInPixels(inlay: Inlay<*>): Int {
+ return editor.contentComponent.getFontMetrics(font).height
+ }
+
+ override fun paint(inlay: Inlay<*>, g: Graphics, targetRegion: Rectangle, textAttributes: TextAttributes) {
+ g.color = InlineCompletionFontUtils.color(editor)
+ g.font = font
+ g.drawString(suffix, targetRegion.x, targetRegion.y + editor.ascent)
+ }
+}
+
+
+class InlineCompletionGrayTextElementCustom(override val text: String, private val delta: Int = 0) :
+ InlineCompletionElement {
+
+ override fun toPresentable(): InlineCompletionElement.Presentable = Presentable(this, delta)
+
+ open class Presentable(override val element: InlineCompletionElement, val delta: Int = 0) :
+ InlineCompletionElement.Presentable {
+ private var suffixInlay: Inlay<*>? = null
+ private var blockInlay: Inlay<*>? = null
+
+ override fun isVisible(): Boolean = suffixInlay != null || blockInlay != null
+
+ /**
+ * Temporal workaround for an internal plugin. **Should not be used.**
+ */
+ @ApiStatus.Internal
+ @ApiStatus.Experimental
+ protected open fun getText(): String = element.text
+
+ override fun render(editor: Editor, offset: Int) {
+ val text = getText()
+ if (text.isEmpty()) return
+ val lines = text.lines()
+ renderSuffix(editor, lines, offset - delta)
+ if (lines.size > 1) {
+ renderBlock(lines.drop(1), editor, offset)
+ }
+ }
+
+ override fun getBounds(): Rectangle? {
+ val bounds = suffixInlay?.bounds?.let { Rectangle(it) }
+ blockInlay?.bounds?.let { bounds?.add(Rectangle(it)) }
+ return bounds
+ }
+
+ override fun startOffset(): Int? = suffixInlay?.offset
+ override fun endOffset(): Int? = suffixInlay?.offset
+
+ override fun dispose() {
+ blockInlay?.also(Disposer::dispose)
+ blockInlay = null
+ suffixInlay?.also(Disposer::dispose)
+ suffixInlay = null
+ }
+
+ private fun renderSuffix(editor: Editor, lines: List, offset: Int) {
+ // The following is a hacky solution to the effect described in ML-977
+ // ML-1781 Inline completion renders on the left to the caret after moving it
+ editor.forceLeanLeft()
+
+ val line = lines.first()
+ if (line.isEmpty()) {
+ suffixInlay =
+ editor.inlayModel.addInlineElement(editor.caretModel.offset, object : EditorCustomElementRenderer {
+ override fun calcWidthInPixels(inlay: Inlay<*>) = 1
+ override fun calcHeightInPixels(inlay: Inlay<*>) = 1
+ override fun paint(
+ inlay: Inlay<*>,
+ g: Graphics,
+ targetRegion: Rectangle,
+ textAttributes: TextAttributes
+ ) {
+ }
+ })
+ return
+ }
+ editor.inlayModel.execute(true) {
+ val element = editor.inlayModel.addInlineElement(offset, true, InlineSuffixRenderer(editor, line))
+ ?: return@execute
+ element.addActionAvailabilityHint(
+ EditorActionAvailabilityHint(
+ IdeActions.ACTION_INSERT_INLINE_COMPLETION,
+ EditorActionAvailabilityHint.AvailabilityCondition.CaretOnStart,
+ )
+ )
+ suffixInlay = element
+ }
+ }
+
+ private fun renderBlock(
+ lines: List,
+ editor: Editor,
+ offset: Int
+ ) {
+ val element = editor.inlayModel.addBlockElement(
+ offset, true, false, 1,
+ InlineBlockElementRenderer(editor, lines)
+ ) ?: return
+
+ blockInlay = element
+ }
+
+ private fun Editor.forceLeanLeft() {
+ val visualPosition = caretModel.visualPosition
+ if (visualPosition.leansRight) {
+ val leftLeaningPosition = VisualPosition(visualPosition.line, visualPosition.column, false)
+ caretModel.moveToVisualPosition(leftLeaningPosition)
+ }
+ }
+ }
+}
diff --git a/src/main/kotlin/com/smallcloud/refactai/codecompletion/RefactAICompletionProvider.kt b/src/main/kotlin/com/smallcloud/refactai/codecompletion/RefactAICompletionProvider.kt
new file mode 100644
index 00000000..46e25829
--- /dev/null
+++ b/src/main/kotlin/com/smallcloud/refactai/codecompletion/RefactAICompletionProvider.kt
@@ -0,0 +1,277 @@
+@file:Suppress("UnstableApiUsage")
+
+package com.smallcloud.refactai.codecompletion
+
+import com.intellij.codeInsight.inline.completion.*
+import com.intellij.codeInsight.inline.completion.elements.InlineCompletionElement
+import com.intellij.codeInsight.inline.completion.elements.InlineCompletionElementManipulator
+import com.intellij.codeInsight.inline.completion.elements.InlineCompletionGrayTextElement
+import com.intellij.codeInsight.inline.completion.elements.InlineCompletionSkipTextElement
+import com.intellij.codeInsight.inline.completion.suggestion.InlineCompletionSingleSuggestion
+import com.intellij.codeInsight.inline.completion.suggestion.InlineCompletionSuggestionUpdateManager
+import com.intellij.codeInsight.inline.completion.suggestion.InlineCompletionSuggestionUpdateManager.UpdateResult.Changed
+import com.intellij.codeInsight.inline.completion.suggestion.InlineCompletionSuggestionUpdateManager.UpdateResult.Invalidated
+import com.intellij.codeInsight.inline.completion.suggestion.InlineCompletionVariant
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.application.runReadAction
+import com.intellij.openapi.diagnostic.Logger
+import com.intellij.openapi.editor.Document
+import com.intellij.openapi.fileEditor.FileDocumentManager
+import com.intellij.openapi.project.Project
+import com.intellij.util.application
+import com.smallcloud.refactai.io.ConnectionStatus
+import com.smallcloud.refactai.io.streamedInferenceFetch
+import com.smallcloud.refactai.privacy.Privacy
+import com.smallcloud.refactai.privacy.PrivacyService
+import com.smallcloud.refactai.statistic.UsageStatistic
+import com.smallcloud.refactai.struct.SMCRequest
+import com.smallcloud.refactai.utils.getExtension
+import dev.gitlive.difflib.DiffUtils
+import dev.gitlive.difflib.patch.DeltaType
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.channelFlow
+import kotlinx.coroutines.launch
+import kotlin.io.path.Path
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
+import com.smallcloud.refactai.io.InferenceGlobalContext.Companion.instance as InferenceGlobalContext
+
+private class Default : InlineCompletionSuggestionUpdateManager.Adapter {
+ override fun onDocumentChange(
+ event: InlineCompletionEvent.DocumentChange,
+ variant: InlineCompletionVariant.Snapshot
+ ): InlineCompletionSuggestionUpdateManager.UpdateResult {
+ if (!isValidTyping(event.typing, variant)) {
+ return Invalidated
+ }
+ val truncated = truncateFirstSymbol(variant.elements) ?: return Invalidated
+ return Changed(variant.copy(elements = truncated))
+ }
+
+ private fun isValidTyping(typing: TypingEvent, variant: InlineCompletionVariant.Snapshot): Boolean {
+ if (typing !is TypingEvent.OneSymbol) {
+ return false
+ }
+ val fragment = typing.typed
+ val textToInsert = variant.elements.joinToString("") { it.text }
+ return textToInsert.startsWith(fragment)
+ }
+
+ private fun truncateFirstSymbol(elements: List): List? {
+ val newFirstElementIndex = elements.indexOfFirst { it.text.isNotEmpty() }
+ check(newFirstElementIndex >= 0)
+ val firstElement = elements[newFirstElementIndex]
+ val manipulator = InlineCompletionElementManipulator.getApplicable(firstElement) ?: return null
+ val newFirstElement = manipulator.truncateFirstSymbol(firstElement)
+ return listOfNotNull(newFirstElement) + elements.drop(newFirstElementIndex + 1)
+ }
+
+ override fun onLookupEvent(
+ event: InlineCompletionEvent.InlineLookupEvent,
+ variant: InlineCompletionVariant.Snapshot
+ ): InlineCompletionSuggestionUpdateManager.UpdateResult {
+ return super.onLookupEvent(event, variant)
+ }
+
+ override fun onDirectCall(
+ event: InlineCompletionEvent.DirectCall,
+ variant: InlineCompletionVariant.Snapshot
+ ): InlineCompletionSuggestionUpdateManager.UpdateResult = Invalidated
+}
+
+private class InsertHandler : DefaultInlineCompletionInsertHandler() {
+ override fun afterInsertion(
+ environment: InlineCompletionInsertEnvironment,
+ elements: List
+ ) {
+ super.afterInsertion(environment, elements)
+ application.invokeLater { // Very important to make it in another EDT call
+ InlineCompletion.getHandlerOrNull(environment.editor)?.invokeEvent(
+ RefactAIContinuousEvent(
+ environment.editor, environment.editor.caretModel.offset
+ )
+ )
+ }
+ }
+}
+
+private val specialSymbolsRegex = "^[:\\s\\t\\n\\r(){},.\"'\\];]*\$".toRegex()
+
+class RefactAICompletionProvider : DebouncedInlineCompletionProvider() {
+ private val logger = Logger.getInstance("inlineCompletion")
+
+ override val id: InlineCompletionProviderID = InlineCompletionProviderID("Refact.ai")
+ override val suggestionUpdateManager: InlineCompletionSuggestionUpdateManager = Default()
+ override val insertHandler: InlineCompletionInsertHandler = InsertHandler()
+ private var lastRequestId: String = ""
+ private var temperatureCounter = 0
+
+ override suspend fun getDebounceDelay(request: InlineCompletionRequest): Duration {
+ val force = request.event is InlineCompletionEvent.DirectCall
+ if (!force) {
+ val debounceMs = CompletionTracker.calcDebounceTime(request.editor)
+ CompletionTracker.updateLastCompletionRequestTime(request.editor)
+ return debounceMs.milliseconds
+ } else {
+ return 0.milliseconds
+ }
+ }
+
+ override fun restartOn(event: InlineCompletionEvent): Boolean = false
+
+ private fun getActiveFile(document: Document, project: Project?): String? {
+ val projectPath = project?.basePath ?: return null
+ val file = FileDocumentManager.getInstance().getFile(document) ?: return null
+ return Path(file.path).toUri().toString().replace(Path(projectPath).toUri().toString(), "")
+ }
+
+ private class Context(val request: SMCRequest, val editorState: EditorTextState, val force: Boolean = false)
+
+
+ private fun makeContext(request: InlineCompletionRequest): Context? {
+ val fileName = getActiveFile(request.document, request.editor.project) ?: return null
+ if (PrivacyService.instance.getPrivacy(FileDocumentManager.getInstance().getFile(request.document))
+ == Privacy.DISABLED && !InferenceGlobalContext.isSelfHosted
+ ) return null
+ if (InferenceGlobalContext.status == ConnectionStatus.DISCONNECTED) return null
+ val editor = request.editor
+ val logicalPos = editor.caretModel.logicalPosition
+ var text = editor.document.text
+ var offset = -1
+ ApplicationManager.getApplication().runReadAction {
+ offset = editor.caretModel.offset
+ }
+
+ val currentLine = text.substring(
+ editor.document.getLineStartOffset(logicalPos.line),
+ editor.document.getLineEndOffset(logicalPos.line)
+ )
+ val rightOfCursor = text.substring(
+ offset,
+ editor.document.getLineEndOffset(logicalPos.line)
+ )
+
+ if (!rightOfCursor.matches(specialSymbolsRegex)) return null
+
+ val isMultiline = currentLine.all { it == ' ' || it == '\t' }
+ var pos = 0
+ if (isMultiline) {
+ val startOffset = editor.document.getLineStartOffset(logicalPos.line)
+ val endOffset = editor.document.getLineEndOffset(logicalPos.line)
+ text = text.removeRange(startOffset, endOffset)
+ } else {
+ pos = offset - editor.document.getLineStartOffset(logicalPos.line)
+ }
+ val force = request.event is InlineCompletionEvent.DirectCall
+ val state = runReadAction {
+ EditorTextState(
+ editor,
+ editor.document.modificationStamp,
+ editor.caretModel.offset
+ )
+ }
+
+ if (!state.isValid()) return null
+ val stat = UsageStatistic(scope = "completion", extension = getExtension(fileName))
+ val request = RequestCreator.create(
+ fileName, text, logicalPos.line, pos,
+ stat, stream = false, multiline = isMultiline
+ ) ?: return null
+ return Context(request, state, force = force)
+ }
+
+ override suspend fun getSuggestionDebounced(request: InlineCompletionRequest): InlineCompletionSingleSuggestion =
+ InlineCompletionSingleSuggestion.build(request, channelFlow {
+ val context = makeContext(request) ?: return@channelFlow
+ InferenceGlobalContext.status = ConnectionStatus.PENDING
+ if (context.force) {
+ context.request.body.parameters.maxNewTokens = 50
+ context.request.body.noCache = true
+ temperatureCounter++
+ } else {
+ temperatureCounter = 0
+ }
+ if (temperatureCounter > 1) {
+ context.request.body.parameters.temperature = 0.6F
+ }
+ lastRequestId = context.request.id
+
+ streamedInferenceFetch(context.request, dataReceiveEnded = {
+ InferenceGlobalContext.status = ConnectionStatus.CONNECTED
+ InferenceGlobalContext.lastErrorMsg = null
+ }) { prediction ->
+ val choice = prediction.choices.first()
+ if (lastRequestId != prediction.requestId) {
+ return@streamedInferenceFetch
+ }
+ val completion = Completion(
+ context.request.body.inputs.sources.values.toList().first(),
+ offset = context.editorState.offset,
+ multiline = context.request.body.inputs.multiline,
+ createdTs = prediction.created,
+ isFromCache = prediction.cached,
+ snippetTelemetryId = prediction.snippetTelemetryId
+ )
+ completion.updateCompletion(choice.delta)
+ CoroutineScope(Dispatchers.Default).launch {
+ val elems = if (completion.multiline) {
+ getMultilineElements(completion, context.editorState)
+ } else {
+ getSingleLineElements(completion, context.editorState)
+ }
+
+ elems.forEach {
+ send(it)
+ delay(2)
+ }
+ }
+ }
+ awaitClose()
+
+ })
+
+ private fun getSingleLineElements(
+ completionData: Completion,
+ editorState: EditorTextState
+ ): List {
+ val res = mutableListOf()
+
+ val currentLine = completionData.originalText.substring(completionData.offset)
+ .substringBefore('\n', "")
+ val patch = DiffUtils.diff(currentLine.toList(), completionData.completion.toList())
+ var previousPosition = 0
+ for (delta in patch.getDeltas()) {
+ if (delta.type != DeltaType.INSERT) {
+ continue
+ }
+ val blockText = delta.target.lines?.joinToString("") ?: ""
+
+ val previousSymbols = currentLine.substring(previousPosition, delta.source.position)
+ if (previousSymbols.isNotEmpty()) {
+ res.add(InlineCompletionSkipTextElement(previousSymbols))
+ }
+
+ res.add(InlineCompletionGrayTextElement(blockText))
+ previousPosition = delta.source.position
+ }
+ val previousSymbols = currentLine.substring(previousPosition)
+ if (previousSymbols.isNotEmpty()) {
+ res.add(InlineCompletionSkipTextElement(previousSymbols))
+ }
+ return res
+ }
+
+ private fun getMultilineElements(
+ completionData: Completion,
+ editorState: EditorTextState
+ ): List {
+ return listOf(InlineCompletionGrayTextElementCustom(completionData.completion, (editorState.offsetByCurrentLine)))
+ }
+
+ override fun isEnabled(event: InlineCompletionEvent): Boolean {
+ return InferenceGlobalContext.useAutoCompletion || event is InlineCompletionEvent.DirectCall
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/smallcloud/refactai/codecompletion/RefactAIContinuousEvent.kt b/src/main/kotlin/com/smallcloud/refactai/codecompletion/RefactAIContinuousEvent.kt
new file mode 100644
index 00000000..00fb7f5d
--- /dev/null
+++ b/src/main/kotlin/com/smallcloud/refactai/codecompletion/RefactAIContinuousEvent.kt
@@ -0,0 +1,42 @@
+package com.smallcloud.refactai.codecompletion
+
+import com.intellij.codeInsight.inline.completion.InlineCompletionEvent
+import com.intellij.codeInsight.inline.completion.InlineCompletionRequest
+import com.intellij.openapi.application.runReadAction
+import com.intellij.openapi.editor.Editor
+import com.intellij.openapi.project.Project
+import com.intellij.psi.PsiDocumentManager
+import com.intellij.psi.PsiFile
+import com.intellij.psi.impl.source.PsiFileImpl
+import com.intellij.psi.util.PsiUtilBase
+import com.intellij.util.concurrency.annotations.RequiresBlockingContext
+
+class RefactAIContinuousEvent(val editor: Editor, val offset: Int) : InlineCompletionEvent {
+ override fun toRequest(): InlineCompletionRequest? {
+ val project = editor.project ?: return null
+ val file = getPsiFile(editor, project) ?: return null
+ return InlineCompletionRequest(this, file, editor, editor.document, offset, offset)
+ }
+}
+
+@RequiresBlockingContext
+private fun getPsiFile(editor: Editor, project: Project): PsiFile? {
+ return runReadAction {
+ val file =
+ PsiDocumentManager.getInstance(project).getPsiFile(editor.document) ?: return@runReadAction null
+ // * [PsiUtilBase] takes into account injected [PsiFile] (like in Jupyter Notebooks)
+ // * However, it loads a file into the memory, which is expensive
+ // * Some tests forbid loading a file when tearing down
+ // * On tearing down, Lookup Cancellation happens, which causes the event
+ // * Existence of [treeElement] guarantees that it's in the memory
+ if (file.isLoadedInMemory()) {
+ PsiUtilBase.getPsiFileInEditor(editor, project)
+ } else {
+ file
+ }
+ }
+}
+
+private fun PsiFile.isLoadedInMemory(): Boolean {
+ return (this as? PsiFileImpl)?.treeElement != null
+}
diff --git a/src/main/kotlin/com/smallcloud/refactai/codecompletion/RefactInlineCompletionDocumentListener.kt b/src/main/kotlin/com/smallcloud/refactai/codecompletion/RefactInlineCompletionDocumentListener.kt
new file mode 100644
index 00000000..41f894e8
--- /dev/null
+++ b/src/main/kotlin/com/smallcloud/refactai/codecompletion/RefactInlineCompletionDocumentListener.kt
@@ -0,0 +1,48 @@
+package com.smallcloud.refactai.codecompletion
+
+import com.intellij.codeInsight.inline.completion.InlineCompletion
+import com.intellij.codeInsight.inline.completion.logs.InlineCompletionUsageTracker.ShownEvents.FinishType
+import com.intellij.codeInsight.inline.completion.session.InlineCompletionContext
+import com.intellij.codeWithMe.ClientId
+import com.intellij.codeWithMe.isCurrent
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.editor.ClientEditorManager
+import com.intellij.openapi.editor.Document
+import com.intellij.openapi.editor.Editor
+import com.intellij.openapi.editor.EditorFactory
+import com.intellij.openapi.editor.event.BulkAwareDocumentListener
+import com.intellij.openapi.editor.event.DocumentEvent
+import com.intellij.util.application
+
+class RefactInlineCompletionDocumentListener : BulkAwareDocumentListener {
+ override fun documentChangedNonBulk(event: DocumentEvent) {
+ val editor = getActiveEditor(event.document) ?: return
+ if (!(ClientEditorManager.getClientId(editor) ?: ClientId.localId).isCurrent()) {
+ hideInlineCompletion(editor, FinishType.DOCUMENT_CHANGED)
+ return
+ }
+
+ val handler = InlineCompletion.getHandlerOrNull(editor)
+ application.invokeLater {
+ handler?.invokeEvent(
+ RefactAIContinuousEvent(
+ editor, editor.caretModel.offset
+ )
+ )
+ }
+ }
+
+
+ private fun getActiveEditor(document: Document): Editor? {
+ if (!ApplicationManager.getApplication().isDispatchThread) {
+ return null
+ }
+ return EditorFactory.getInstance().getEditors(document).firstOrNull()
+ }
+}
+
+
+private fun hideInlineCompletion(editor: Editor, finishType: FinishType) {
+ val context = InlineCompletionContext.getOrNull(editor) ?: return
+ InlineCompletion.getHandlerOrNull(editor)?.hide(context, finishType)
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/smallcloud/refactai/modes/completion/prompt/RequestCreator.kt b/src/main/kotlin/com/smallcloud/refactai/codecompletion/RequestCreator.kt
similarity index 89%
rename from src/main/kotlin/com/smallcloud/refactai/modes/completion/prompt/RequestCreator.kt
rename to src/main/kotlin/com/smallcloud/refactai/codecompletion/RequestCreator.kt
index 857e0e8b..d699a5cf 100644
--- a/src/main/kotlin/com/smallcloud/refactai/modes/completion/prompt/RequestCreator.kt
+++ b/src/main/kotlin/com/smallcloud/refactai/codecompletion/RequestCreator.kt
@@ -1,4 +1,4 @@
-package com.smallcloud.refactai.modes.completion.prompt
+package com.smallcloud.refactai.codecompletion
import com.smallcloud.refactai.Resources
import com.smallcloud.refactai.statistic.UsageStatistic
@@ -14,9 +14,6 @@ object RequestCreator {
fileName: String, text: String,
line: Int, column: Int,
stat: UsageStatistic,
- intent: String, functionName: String,
- promptInfo: List,
- model: String,
stream: Boolean = true,
multiline: Boolean = false
): SMCRequest? {
diff --git a/src/main/kotlin/com/smallcloud/refactai/io/InferenceGlobalContext.kt b/src/main/kotlin/com/smallcloud/refactai/io/InferenceGlobalContext.kt
index 337ef85c..73e4dc38 100644
--- a/src/main/kotlin/com/smallcloud/refactai/io/InferenceGlobalContext.kt
+++ b/src/main/kotlin/com/smallcloud/refactai/io/InferenceGlobalContext.kt
@@ -185,6 +185,24 @@ class InferenceGlobalContext : Disposable {
return deploymentMode == DeploymentMode.SELF_HOSTED
}
+ var astIsEnabled: Boolean
+ get() = AppSettingsState.astIsEnabled
+ set(newValue) {
+ if (newValue == astIsEnabled) return
+ messageBus
+ .syncPublisher(InferenceGlobalContextChangedNotifier.TOPIC)
+ .astFlagChanged(newValue)
+ }
+
+ var vecdbIsEnabled: Boolean
+ get() = AppSettingsState.vecdbIsEnabled
+ set(newValue) {
+ if (newValue == vecdbIsEnabled) return
+ messageBus
+ .syncPublisher(InferenceGlobalContextChangedNotifier.TOPIC)
+ .vecdbFlagChanged(newValue)
+ }
+
fun makeRequest(requestData: SMCRequestBody): SMCRequest? {
val apiKey = AccountManager.apiKey
// if (apiKey.isNullOrEmpty() && isCloud) return null
diff --git a/src/main/kotlin/com/smallcloud/refactai/io/InferenceGlobalContextChangedNotifier.kt b/src/main/kotlin/com/smallcloud/refactai/io/InferenceGlobalContextChangedNotifier.kt
index c49cf676..97f0ef43 100644
--- a/src/main/kotlin/com/smallcloud/refactai/io/InferenceGlobalContextChangedNotifier.kt
+++ b/src/main/kotlin/com/smallcloud/refactai/io/InferenceGlobalContextChangedNotifier.kt
@@ -14,6 +14,8 @@ interface InferenceGlobalContextChangedNotifier {
fun useMultipleFilesCompletionChanged(newValue: Boolean) {}
fun developerModeEnabledChanged(newValue: Boolean) {}
fun deploymentModeChanged(newMode: DeploymentMode) {}
+ fun astFlagChanged(newValue: Boolean) {}
+ fun vecdbFlagChanged(newValue: Boolean) {}
companion object {
val TOPIC = Topic.create(
diff --git a/src/main/kotlin/com/smallcloud/refactai/listeners/AcceptAction.kt b/src/main/kotlin/com/smallcloud/refactai/listeners/AcceptAction.kt
index e5b00410..85db301f 100644
--- a/src/main/kotlin/com/smallcloud/refactai/listeners/AcceptAction.kt
+++ b/src/main/kotlin/com/smallcloud/refactai/listeners/AcceptAction.kt
@@ -2,18 +2,21 @@ package com.smallcloud.refactai.listeners
import com.intellij.codeInsight.hint.HintManagerImpl.ActionToIgnore
+import com.intellij.codeInsight.inline.completion.InlineCompletion
+import com.intellij.codeInsight.inline.completion.session.InlineCompletionContext
import com.intellij.openapi.actionSystem.DataContext
-import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
+import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.editor.actionSystem.EditorAction
import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler
import com.smallcloud.refactai.Resources
-import com.smallcloud.refactai.modes.ModeProvider
+import com.smallcloud.refactai.codecompletion.InlineCompletionGrayTextElementCustom
-class TabPressedAction :
- EditorAction(InlineCompletionHandler()),
- ActionToIgnore { val ACTION_ID = "TabPressedAction"
+const val ACTION_ID_ = "TabPressedAction"
+
+class TabPressedAction : EditorAction(InlineCompletionHandler()), ActionToIgnore {
+ val ACTION_ID = ACTION_ID_
init {
this.templatePresentation.icon = Resources.Icons.LOGO_RED_16x16
@@ -21,9 +24,12 @@ class TabPressedAction :
class InlineCompletionHandler : EditorWriteActionHandler() {
override fun executeWriteAction(editor: Editor, caret: Caret?, dataContext: DataContext) {
- Logger.getInstance("TabPressedAction").debug("executeWriteAction")
- val provider = ModeProvider.getOrCreateModeProvider(editor)
- provider.onTabPressed(editor, caret, dataContext)
+ InlineCompletion.getHandlerOrNull(editor)?.insert()
+ val currentOffset = editor.caretModel.offset
+ val logicalPosition = editor.caretModel.logicalPosition
+ val offset = editor.logicalPositionToOffset(LogicalPosition(logicalPosition.line + 1, 0))
+
+ editor.document.replaceString(currentOffset, offset, "")
}
override fun isEnabledForCaret(
@@ -31,7 +37,11 @@ class TabPressedAction :
caret: Caret,
dataContext: DataContext
): Boolean {
- return ModeProvider.getOrCreateModeProvider(editor).modeInActiveState()
+ val ctx = InlineCompletionContext.getOrNull(editor) ?: return false
+ if (ctx.state.elements.size != 1) return false
+ val elem = ctx.state.elements.first()
+ if (elem !is InlineCompletionGrayTextElementCustom.Presentable) return false
+ return elem.delta == caret.logicalPosition.column
}
}
}
diff --git a/src/main/kotlin/com/smallcloud/refactai/listeners/CancelAction.kt b/src/main/kotlin/com/smallcloud/refactai/listeners/CancelAction.kt
deleted file mode 100644
index d2c0c7aa..00000000
--- a/src/main/kotlin/com/smallcloud/refactai/listeners/CancelAction.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.smallcloud.refactai.listeners
-
-
-import com.intellij.codeInsight.hint.HintManagerImpl.ActionToIgnore
-import com.intellij.openapi.actionSystem.DataContext
-import com.intellij.openapi.diagnostic.Logger
-import com.intellij.openapi.editor.Caret
-import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.editor.actionSystem.EditorAction
-import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler
-import com.smallcloud.refactai.Resources
-import com.smallcloud.refactai.modes.ModeProvider
-
-class CancelPressedAction :
- EditorAction(InlineCompletionHandler()),
- ActionToIgnore {
- val ACTION_ID = "CancelPressedAction"
-
- init {
- this.templatePresentation.icon = Resources.Icons.LOGO_RED_16x16
- }
-
- class InlineCompletionHandler : EditorWriteActionHandler() {
- override fun executeWriteAction(editor: Editor, caret: Caret?, dataContext: DataContext) {
- Logger.getInstance("CancelPressedAction").debug("executeWriteAction")
- val provider = ModeProvider.getOrCreateModeProvider(editor)
- provider.onEscPressed(editor, caret, dataContext)
- }
-
- override fun isEnabledForCaret(
- editor: Editor,
- caret: Caret,
- dataContext: DataContext
- ): Boolean {
- return ModeProvider.getOrCreateModeProvider(editor).modeInActiveState()
- }
- }
-}
diff --git a/src/main/kotlin/com/smallcloud/refactai/listeners/CancelActionPromoter.kt b/src/main/kotlin/com/smallcloud/refactai/listeners/CancelActionPromoter.kt
deleted file mode 100644
index 16231548..00000000
--- a/src/main/kotlin/com/smallcloud/refactai/listeners/CancelActionPromoter.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.smallcloud.refactai.listeners
-
-import com.intellij.openapi.actionSystem.ActionPromoter
-import com.intellij.openapi.actionSystem.AnAction
-import com.intellij.openapi.actionSystem.CommonDataKeys
-import com.intellij.openapi.actionSystem.DataContext
-import com.intellij.openapi.editor.Editor
-
-class CancelActionsPromoter : ActionPromoter {
- private fun getEditor(dataContext: DataContext): Editor? {
- return CommonDataKeys.EDITOR.getData(dataContext)
- }
- override fun promote(actions: MutableList, context: DataContext): MutableList {
- if (getEditor(context) == null)
- return actions.toMutableList()
- return actions.filterIsInstance().toMutableList()
- }
-}
\ No newline at end of file
diff --git a/src/main/kotlin/com/smallcloud/refactai/listeners/DocumentListener.kt b/src/main/kotlin/com/smallcloud/refactai/listeners/DocumentListener.kt
deleted file mode 100644
index c0fa5b83..00000000
--- a/src/main/kotlin/com/smallcloud/refactai/listeners/DocumentListener.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-package com.smallcloud.refactai.listeners
-
-import com.intellij.openapi.Disposable
-import com.intellij.openapi.application.ApplicationManager
-import com.intellij.openapi.diagnostic.Logger
-import com.intellij.openapi.editor.Document
-import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.editor.EditorFactory
-import com.intellij.openapi.editor.event.BulkAwareDocumentListener
-import com.intellij.openapi.editor.event.DocumentEvent
-import com.smallcloud.refactai.modes.ModeProvider
-import com.smallcloud.refactai.io.InferenceGlobalContext.Companion.instance as InferenceGlobalContext
-
-
-class DocumentListener : BulkAwareDocumentListener, Disposable {
- override fun beforeDocumentChangeNonBulk(event: DocumentEvent) {
- Logger.getInstance("DocumentListener").debug("beforeDocumentChangeNonBulk")
- val editor = getActiveEditor(event.document) ?: return
- val provider = ModeProvider.getOrCreateModeProvider(editor)
- provider.beforeDocumentChangeNonBulk(event, editor)
- }
-
- override fun documentChangedNonBulk(event: DocumentEvent) {
- Logger.getInstance("DocumentListener").debug("documentChangedNonBulk")
- if (!InferenceGlobalContext.useAutoCompletion) return
- val editor = getActiveEditor(event.document) ?: return
- val provider = ModeProvider.getOrCreateModeProvider(editor)
- provider.onTextChange(event, editor, false)
- }
-
- private fun getActiveEditor(document: Document): Editor? {
- if (!ApplicationManager.getApplication().isDispatchThread) {
- return null
- }
- return EditorFactory.getInstance().getEditors(document).firstOrNull()
- }
-
- override fun dispose() {}
-}
diff --git a/src/main/kotlin/com/smallcloud/refactai/listeners/ForceCompletionAction.kt b/src/main/kotlin/com/smallcloud/refactai/listeners/ForceCompletionAction.kt
index 28a0d558..43dabdd1 100644
--- a/src/main/kotlin/com/smallcloud/refactai/listeners/ForceCompletionAction.kt
+++ b/src/main/kotlin/com/smallcloud/refactai/listeners/ForceCompletionAction.kt
@@ -2,38 +2,16 @@ package com.smallcloud.refactai.listeners
import com.intellij.codeInsight.hint.HintManagerImpl.ActionToIgnore
-import com.intellij.openapi.actionSystem.DataContext
-import com.intellij.openapi.diagnostic.Logger
-import com.intellij.openapi.editor.Caret
-import com.intellij.openapi.editor.Editor
+import com.intellij.codeInsight.inline.completion.CallInlineCompletionAction
import com.intellij.openapi.editor.actionSystem.EditorAction
-import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler
import com.smallcloud.refactai.Resources
-import com.smallcloud.refactai.modes.ModeProvider
class ForceCompletionAction :
- EditorAction(InlineCompletionHandler()),
+ EditorAction(CallInlineCompletionAction.CallInlineCompletionHandler()),
ActionToIgnore {
val ACTION_ID = "ForceCompletionAction"
init {
this.templatePresentation.icon = Resources.Icons.LOGO_RED_16x16
}
-
- class InlineCompletionHandler : EditorWriteActionHandler() {
- override fun executeWriteAction(editor: Editor, caret: Caret?, dataContext: DataContext) {
- Logger.getInstance("ForceCompletionAction").debug("executeWriteAction")
- val provider = ModeProvider.getOrCreateModeProvider(editor)
- provider.beforeDocumentChangeNonBulk(null, editor)
- provider.onTextChange(null, editor, true)
- }
-
- override fun isEnabledForCaret(
- editor: Editor,
- caret: Caret,
- dataContext: DataContext
- ): Boolean {
- return true //InferenceGlobalContext.useForceCompletion
- }
- }
}
diff --git a/src/main/kotlin/com/smallcloud/refactai/listeners/ForceCompletionActionPromoter.kt b/src/main/kotlin/com/smallcloud/refactai/listeners/ForceCompletionActionPromoter.kt
index 21ac6f2d..1858f533 100644
--- a/src/main/kotlin/com/smallcloud/refactai/listeners/ForceCompletionActionPromoter.kt
+++ b/src/main/kotlin/com/smallcloud/refactai/listeners/ForceCompletionActionPromoter.kt
@@ -1,21 +1,18 @@
package com.smallcloud.refactai.listeners
+import com.intellij.codeInsight.inline.completion.session.InlineCompletionContext
import com.intellij.openapi.actionSystem.ActionPromoter
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.DataContext
-import com.intellij.openapi.editor.Editor
-import com.smallcloud.refactai.modes.ModeProvider
class ForceCompletionActionPromoter : ActionPromoter {
- private fun getEditor(dataContext: DataContext): Editor? {
- return CommonDataKeys.EDITOR.getData(dataContext)
- }
- override fun promote(actions: MutableList, context: DataContext): MutableList {
- val editor: Editor = getEditor(context) ?: return actions.toMutableList()
- val modeProvider = ModeProvider.getOrCreateModeProvider(editor)
- if (modeProvider.isInCompletionMode())
- return actions.filterIsInstance().toMutableList()
- return actions.toMutableList()
+ override fun promote(actions: MutableList, context: DataContext): List {
+ val editor = CommonDataKeys.EDITOR.getData(context) ?: return emptyList()
+
+ if (InlineCompletionContext.getOrNull(editor) == null) {
+ return emptyList()
+ }
+ return actions.filterIsInstance()
}
}
\ No newline at end of file
diff --git a/src/main/kotlin/com/smallcloud/refactai/listeners/GlobalCaretListener.kt b/src/main/kotlin/com/smallcloud/refactai/listeners/GlobalCaretListener.kt
deleted file mode 100644
index a22f9b69..00000000
--- a/src/main/kotlin/com/smallcloud/refactai/listeners/GlobalCaretListener.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.smallcloud.refactai.listeners
-
-import com.intellij.openapi.diagnostic.Logger
-import com.intellij.openapi.editor.event.CaretEvent
-import com.intellij.openapi.editor.event.CaretListener
-import com.smallcloud.refactai.modes.ModeProvider
-
-class GlobalCaretListener : CaretListener {
- override fun caretPositionChanged(event: CaretEvent) {
- Logger.getInstance("CaretListener").debug("caretPositionChanged")
- val provider = ModeProvider.getOrCreateModeProvider(event.editor)
- provider.onCaretChange(event)
- }
-}
diff --git a/src/main/kotlin/com/smallcloud/refactai/listeners/GlobalFocusListener.kt b/src/main/kotlin/com/smallcloud/refactai/listeners/GlobalFocusListener.kt
deleted file mode 100644
index 75837cdc..00000000
--- a/src/main/kotlin/com/smallcloud/refactai/listeners/GlobalFocusListener.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.smallcloud.refactai.listeners
-
-import com.intellij.openapi.diagnostic.Logger
-import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.editor.ex.FocusChangeListener
-import com.smallcloud.refactai.modes.ModeProvider
-
-class GlobalFocusListener : FocusChangeListener {
- override fun focusGained(editor: Editor) {
- Logger.getInstance("FocusListener").debug("focusGained")
- val provider = ModeProvider.getOrCreateModeProvider(editor)
- provider.focusGained()
- }
-
- override fun focusLost(editor: Editor) {
- Logger.getInstance("FocusListener").debug("focusLost")
- val provider = ModeProvider.getOrCreateModeProvider(editor)
- provider.focusLost()
- }
-}
diff --git a/src/main/kotlin/com/smallcloud/refactai/lsp/LSPConfig.kt b/src/main/kotlin/com/smallcloud/refactai/lsp/LSPConfig.kt
index 0ae3d830..d9435d01 100644
--- a/src/main/kotlin/com/smallcloud/refactai/lsp/LSPConfig.kt
+++ b/src/main/kotlin/com/smallcloud/refactai/lsp/LSPConfig.kt
@@ -8,7 +8,9 @@ data class LSPConfig(
var apiKey: String? = null,
var clientVersion: String? = null,
var useTelemetry: Boolean = false,
- var deployment: DeploymentMode = DeploymentMode.CLOUD
+ var deployment: DeploymentMode = DeploymentMode.CLOUD,
+ var ast: Boolean = false,
+ var vecdb: Boolean = false
) {
fun toArgs(): List {
val params = mutableListOf()
@@ -31,6 +33,12 @@ data class LSPConfig(
if (useTelemetry) {
params.add("--basic-telemetry")
}
+ if (ast) {
+ params.add("--ast")
+ }
+ if (vecdb) {
+ params.add("--vecdb")
+ }
return params
}
diff --git a/src/main/kotlin/com/smallcloud/refactai/lsp/LSPProcessHolder.kt b/src/main/kotlin/com/smallcloud/refactai/lsp/LSPProcessHolder.kt
index 365d70bf..0be629cf 100644
--- a/src/main/kotlin/com/smallcloud/refactai/lsp/LSPProcessHolder.kt
+++ b/src/main/kotlin/com/smallcloud/refactai/lsp/LSPProcessHolder.kt
@@ -90,6 +90,16 @@ class LSPProcessHolder: Disposable {
settingsChanged()
}
}
+ override fun astFlagChanged(newValue: Boolean) {
+ AppExecutorUtil.getAppScheduledExecutorService().submit {
+ settingsChanged()
+ }
+ }
+ override fun vecdbFlagChanged(newValue: Boolean) {
+ AppExecutorUtil.getAppScheduledExecutorService().submit {
+ settingsChanged()
+ }
+ }
})
Companion::class.java.getResourceAsStream(
@@ -136,7 +146,9 @@ class LSPProcessHolder: Disposable {
port = (32000..32199).random(),
clientVersion = "${Resources.client}-${Resources.version}/${Resources.jbBuildVersion}",
useTelemetry = true,
- deployment = InferenceGlobalContext.deploymentMode
+ deployment = InferenceGlobalContext.deploymentMode,
+ ast = InferenceGlobalContext.astIsEnabled,
+ vecdb = InferenceGlobalContext.vecdbIsEnabled,
)
startProcess(newConfig)
}
diff --git a/src/main/kotlin/com/smallcloud/refactai/modes/EventAdapter.kt b/src/main/kotlin/com/smallcloud/refactai/modes/EventAdapter.kt
deleted file mode 100644
index 1ea64bda..00000000
--- a/src/main/kotlin/com/smallcloud/refactai/modes/EventAdapter.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-package com.smallcloud.refactai.modes
-
-import com.smallcloud.refactai.modes.completion.structs.DocumentEventExtra
-
-object EventAdapter {
- private fun bracketsAdapter(
- beforeText: List,
- afterText: List
- ): Pair?> {
- if (beforeText.size != 2 || afterText.size != 2) {
- return false to null
- }
-
- val startAutocompleteStrings = setOf("(", "\"", "{", "[", "'", "\"")
- val endAutocompleteStrings = setOf(")", "\"", "\'", "}", "]", "'''", "\"\"\"")
- val startToStopSymbols = mapOf(
- "(" to setOf(")"), "{" to setOf("}"), "[" to setOf("]"),
- "'" to setOf("'", "'''"), "\"" to setOf("\"", "\"\"\"")
- )
-
- val firstEventFragment = afterText[beforeText.size - 2].event?.newFragment.toString()
- val secondEventFragment = afterText[beforeText.size - 1].event?.newFragment.toString()
-
- if (firstEventFragment.isEmpty() || firstEventFragment !in startAutocompleteStrings) {
- return false to null
- }
- if (secondEventFragment.isEmpty() || secondEventFragment !in endAutocompleteStrings) {
- return false to null
- }
- if (secondEventFragment !in startToStopSymbols.getValue(firstEventFragment)) {
- return false to null
- }
-
- return true to (beforeText.last() to afterText.last().copy(
- offsetCorrection = -1
- ))
- }
-
- fun eventProcess(beforeText: List, afterText: List)
- : Pair {
- if (beforeText.isNotEmpty() && afterText.isEmpty()) {
- return beforeText.last() to null
- }
-
- if (afterText.last().force) {
- return beforeText.last() to afterText.last()
- }
-
- val (succeed, events) = bracketsAdapter(beforeText, afterText)
- if (succeed && events != null) {
- return events
- }
-
- return beforeText.last() to afterText.last()
- }
-}
\ No newline at end of file
diff --git a/src/main/kotlin/com/smallcloud/refactai/modes/Mode.kt b/src/main/kotlin/com/smallcloud/refactai/modes/Mode.kt
deleted file mode 100644
index 74514056..00000000
--- a/src/main/kotlin/com/smallcloud/refactai/modes/Mode.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.smallcloud.refactai.modes
-
-
-import com.intellij.openapi.actionSystem.DataContext
-import com.intellij.openapi.editor.Caret
-import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.editor.event.CaretEvent
-import com.smallcloud.refactai.modes.completion.structs.DocumentEventExtra
-
-interface Mode {
- var needToRender: Boolean
- fun beforeDocumentChangeNonBulk(event: DocumentEventExtra)
- fun onTextChange(event: DocumentEventExtra)
- fun onTabPressed(editor: Editor, caret: Caret?, dataContext: DataContext)
- fun onEscPressed(editor: Editor, caret: Caret?, dataContext: DataContext)
- fun onCaretChange(event: CaretEvent)
- fun isInActiveState(): Boolean
- fun show()
- fun hide()
- fun cleanup(editor: Editor)
-}
diff --git a/src/main/kotlin/com/smallcloud/refactai/modes/ModeProvider.kt b/src/main/kotlin/com/smallcloud/refactai/modes/ModeProvider.kt
deleted file mode 100644
index 1ccace24..00000000
--- a/src/main/kotlin/com/smallcloud/refactai/modes/ModeProvider.kt
+++ /dev/null
@@ -1,167 +0,0 @@
-package com.smallcloud.refactai.modes
-
-import com.intellij.codeInsight.completion.CompletionUtil.DUMMY_IDENTIFIER
-import com.intellij.openapi.Disposable
-import com.intellij.openapi.actionSystem.DataContext
-import com.intellij.openapi.application.ApplicationManager
-import com.intellij.openapi.editor.Caret
-import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.editor.event.CaretEvent
-import com.intellij.openapi.editor.event.DocumentEvent
-import com.intellij.openapi.editor.ex.EditorEx
-import com.intellij.util.ObjectUtils
-import com.intellij.util.concurrency.AppExecutorUtil
-import com.intellij.util.messages.MessageBus
-import com.intellij.util.xmlb.annotations.Transient
-import com.jetbrains.rd.util.getOrCreate
-import com.smallcloud.refactai.PluginState
-import com.smallcloud.refactai.io.ConnectionStatus
-import com.smallcloud.refactai.io.InferenceGlobalContextChangedNotifier
-import com.smallcloud.refactai.listeners.GlobalCaretListener
-import com.smallcloud.refactai.listeners.GlobalFocusListener
-import com.smallcloud.refactai.modes.completion.CompletionMode
-import com.smallcloud.refactai.modes.completion.structs.DocumentEventExtra
-import com.smallcloud.refactai.statistic.UsageStatistic
-import com.smallcloud.refactai.statistic.UsageStats
-import java.lang.System.currentTimeMillis
-import java.lang.System.identityHashCode
-import java.util.concurrent.ConcurrentLinkedQueue
-import java.util.concurrent.TimeUnit
-import com.smallcloud.refactai.io.InferenceGlobalContext.Companion.instance as InferenceGlobalContext
-
-
-enum class ModeType {
- Completion,
-}
-
-
-class ModeProvider(
- private val editor: Editor,
- private val modes: Map = mapOf(
- ModeType.Completion to CompletionMode(),
- ),
- private var activeMode: Mode? = null,
- private val pluginState: PluginState = PluginState.instance,
-) : Disposable, InferenceGlobalContextChangedNotifier {
-
- @Transient
- private val messageBus: MessageBus = ApplicationManager.getApplication().messageBus
- private val beforeTextChangeEventsQueue: ConcurrentLinkedQueue = ConcurrentLinkedQueue()
- private val onTextChangeEventsQueue: ConcurrentLinkedQueue = ConcurrentLinkedQueue()
- private val eventDebounceScheduler = AppExecutorUtil.createBoundedScheduledExecutorService(
- "SMCEventDebounceScheduler", 1
- )
- private val stats: UsageStats
- get() = ApplicationManager.getApplication().getService(UsageStats::class.java)
-
- init {
- activeMode = modes[ModeType.Completion]
- messageBus.connect(this).subscribe(
- InferenceGlobalContextChangedNotifier.TOPIC, this
- )
- eventDebounceScheduler.scheduleWithFixedDelay({
- checkAndSendEvents()
- }, 0, 2, TimeUnit.MILLISECONDS)
- }
-
- private fun checkAndSendEvents() {
- if (beforeTextChangeEventsQueue.isEmpty()) {
- return
- }
-
- val now = currentTimeMillis()
- val oldestBeforeTextEvent = beforeTextChangeEventsQueue.first()
- if (now - oldestBeforeTextEvent.ts < 20) {
- return
- }
-
- val beforeEvents: MutableList = mutableListOf()
- val onTextEvents: MutableList = mutableListOf()
- beforeTextChangeEventsQueue.forEach { beforeEvents.add(it) }
- onTextChangeEventsQueue.forEach { onTextEvents.add(it) }
- beforeTextChangeEventsQueue.clear()
- onTextChangeEventsQueue.clear()
-
- val (beforeEvent, afterEvent) = EventAdapter.eventProcess(beforeEvents, onTextEvents)
-
- try {
- beforeEvent?.let { activeMode?.beforeDocumentChangeNonBulk(it) }
- afterEvent?.let { activeMode?.onTextChange(it) }
- } catch (e: Exception) { InferenceGlobalContext.status = ConnectionStatus.ERROR
- InferenceGlobalContext.lastErrorMsg = e.message
- stats.addStatistic(
- false, UsageStatistic("uncaught exceptions"), "none",
- e.toString()
- )
- }
- }
-
- fun modeInActiveState(): Boolean = activeMode?.isInActiveState() == true
-
- fun isInCompletionMode(): Boolean =
- activeMode === modes[ModeType.Completion]
- fun getCompletionMode(): Mode = modes[ModeType.Completion]!!
-
- fun switchMode(newMode: ModeType = ModeType.Completion) {
- if (activeMode == modes[newMode]) return
- activeMode?.cleanup(editor)
- activeMode = modes[newMode]
- }
-
- fun beforeDocumentChangeNonBulk(event: DocumentEvent?, editor: Editor) {
- if (event?.newFragment.toString() == DUMMY_IDENTIFIER) return
- beforeTextChangeEventsQueue.add(DocumentEventExtra(event, editor, currentTimeMillis()))
- }
-
- fun onTextChange(event: DocumentEvent?, editor: Editor, force: Boolean) {
- if (event?.newFragment.toString() == DUMMY_IDENTIFIER) return
- onTextChangeEventsQueue.add(DocumentEventExtra(event, editor, currentTimeMillis(), force))
- }
-
- fun onCaretChange(event: CaretEvent) {
- activeMode?.onCaretChange(event)
- }
-
- fun focusGained() {}
-
- fun focusLost() {}
-
- fun onTabPressed(editor: Editor, caret: Caret?, dataContext: DataContext) {
- activeMode?.onTabPressed(editor, caret, dataContext)
- }
-
- fun onEscPressed(editor: Editor, caret: Caret?, dataContext: DataContext) {
- activeMode?.onEscPressed(editor, caret, dataContext)
- }
-
- override fun dispose() {
- }
-
- companion object {
- private const val MAX_EDITORS: Int = 8
- private var modeProviders: LinkedHashMap = linkedMapOf()
- private var providersToTs: LinkedHashMap = linkedMapOf()
-
- fun getOrCreateModeProvider(editor: Editor): ModeProvider {
- val hashId = identityHashCode(editor)
- if (modeProviders.size > MAX_EDITORS) {
- val toRemove = providersToTs.minByOrNull { it.value }?.key
- providersToTs.remove(toRemove)
- modeProviders.remove(toRemove)
- }
- return modeProviders.getOrCreate(hashId) {
- val modeProvider = ModeProvider(editor)
- providersToTs[hashId] = currentTimeMillis()
- editor.caretModel.addCaretListener(GlobalCaretListener())
- ObjectUtils.consumeIfCast(editor, EditorEx::class.java) {
- try {
- it.addFocusListener(GlobalFocusListener(), modeProvider)
- } catch (e: UnsupportedOperationException) {
- // nothing
- }
- }
- modeProvider
- }
- }
- }
-}
diff --git a/src/main/kotlin/com/smallcloud/refactai/modes/completion/CompletionLookupListener.kt b/src/main/kotlin/com/smallcloud/refactai/modes/completion/CompletionLookupListener.kt
deleted file mode 100644
index 7a6956e7..00000000
--- a/src/main/kotlin/com/smallcloud/refactai/modes/completion/CompletionLookupListener.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-package com.smallcloud.refactai.modes.completion
-
-import com.intellij.codeInsight.lookup.LookupEvent
-import com.intellij.codeInsight.lookup.LookupListener
-import com.intellij.openapi.diagnostic.Logger
-import com.intellij.openapi.editor.Editor
-import com.jetbrains.rd.util.getOrCreate
-import com.smallcloud.refactai.modes.Mode
-import com.smallcloud.refactai.modes.ModeProvider
-
-class CompletionLookupListener(editor: Editor): LookupListener {
- private val modeProvider: ModeProvider = ModeProvider.getOrCreateModeProvider(editor)
- private val completionMode: Mode = modeProvider.getCompletionMode()
- private val logger = Logger.getInstance("CompletionLookupListener")
-
- override fun lookupShown(event: LookupEvent) {
- logger.info("lookupShown")
- completionMode.hide()
- completionMode.needToRender = false
- }
-
- override fun beforeItemSelected(event: LookupEvent): Boolean {
- logger.info("beforeItemSelected")
- return true
- }
-
- override fun itemSelected(event: LookupEvent) {
- logger.info("itemSelected")
- completionMode.needToRender = true
- }
-
- override fun lookupCanceled(event: LookupEvent) {
- logger.info("lookupCanceled")
- completionMode.show()
- completionMode.needToRender = true
- }
-
- override fun currentItemChanged(event: LookupEvent) {
- logger.info("currentItemChanged")
- }
-
- override fun uiRefreshed() {
- logger.info("uiRefreshed")
- }
-
- override fun focusDegreeChanged() {
- logger.info("focusDegreeChanged")
- }
-
- companion object {
- private const val MAX_EDITORS: Int = 8
- private var lookupListeners: LinkedHashMap = linkedMapOf()
- private var providersToTs: LinkedHashMap = linkedMapOf()
-
- fun getOrCreate(editor: Editor): CompletionLookupListener {
- val hashId = System.identityHashCode(editor)
- if (lookupListeners.size > MAX_EDITORS) {
- val toRemove = providersToTs.minByOrNull { it.value }?.key
- providersToTs.remove(toRemove)
- lookupListeners.remove(toRemove)
- }
- return lookupListeners.getOrCreate(hashId) {
- val listener = CompletionLookupListener(editor)
- providersToTs[hashId] = System.currentTimeMillis()
- listener
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/main/kotlin/com/smallcloud/refactai/modes/completion/CompletionMode.kt b/src/main/kotlin/com/smallcloud/refactai/modes/completion/CompletionMode.kt
deleted file mode 100644
index 10b480b3..00000000
--- a/src/main/kotlin/com/smallcloud/refactai/modes/completion/CompletionMode.kt
+++ /dev/null
@@ -1,366 +0,0 @@
-package com.smallcloud.refactai.modes.completion
-
-import com.intellij.openapi.actionSystem.DataContext
-import com.intellij.openapi.application.ApplicationManager
-import com.intellij.openapi.diagnostic.Logger
-import com.intellij.openapi.editor.*
-import com.intellij.openapi.editor.event.CaretEvent
-import com.intellij.openapi.editor.event.CaretListener
-import com.intellij.openapi.editor.event.DocumentEvent
-import com.intellij.openapi.fileEditor.FileDocumentManager
-import com.intellij.openapi.project.Project
-import com.intellij.util.concurrency.AppExecutorUtil
-import com.smallcloud.refactai.Resources
-import com.smallcloud.refactai.io.ConnectionStatus
-import com.smallcloud.refactai.io.streamedInferenceFetch
-import com.smallcloud.refactai.modes.EditorTextState
-import com.smallcloud.refactai.modes.Mode
-import com.smallcloud.refactai.modes.completion.prompt.PromptInfo
-import com.smallcloud.refactai.modes.completion.prompt.RequestCreator
-import com.smallcloud.refactai.modes.completion.renderer.AsyncCompletionLayout
-import com.smallcloud.refactai.modes.completion.structs.Completion
-import com.smallcloud.refactai.modes.completion.structs.DocumentEventExtra
-import com.smallcloud.refactai.privacy.Privacy
-import com.smallcloud.refactai.statistic.UsageStatistic
-import com.smallcloud.refactai.struct.SMCRequest
-import com.smallcloud.refactai.utils.getExtension
-import java.io.InterruptedIOException
-import java.util.concurrent.CancellationException
-import java.util.concurrent.ExecutionException
-import java.util.concurrent.Future
-import java.util.concurrent.TimeUnit
-import kotlin.io.path.Path
-import com.smallcloud.refactai.io.InferenceGlobalContext.Companion.instance as InferenceGlobalContext
-import com.smallcloud.refactai.privacy.PrivacyService.Companion.instance as PrivacyService
-import com.smallcloud.refactai.statistic.UsageStats.Companion.instance as UsageStats
-
-private val specialSymbolsRegex = "^[:\\s\\t\\n\\r(){},.\"'\\];]*\$".toRegex()
-class CompletionMode(
- override var needToRender: Boolean = true
-) : Mode, CaretListener {
- private val scope: String = "completion"
- private val app = ApplicationManager.getApplication()
- private val scheduler = AppExecutorUtil.createBoundedScheduledExecutorService("SMCCompletionScheduler", 1)
- private var processTask: Future<*>? = null
- private var lastRequestId: String = ""
- private var requestTask: Future<*>? = null
- private var completionLayout: AsyncCompletionLayout? = null
- private val logger = Logger.getInstance("StreamedCompletionMode")
- private var completionInProgress: Boolean = false
- private var temperatureCounter: Int = 0
-
-
- override fun beforeDocumentChangeNonBulk(event: DocumentEventExtra) {
- event.editor.caretModel.removeCaretListener(this)
- event.editor.caretModel.addCaretListener(this)
- cancelOrClose()
- }
-
- override fun onTextChange(event: DocumentEventExtra) {
- if (completionInProgress) return
- val fileName = getActiveFile(event.editor.document, event.editor.project) ?: return
- if (PrivacyService.getPrivacy(FileDocumentManager.getInstance().getFile(event.editor.document))
- == Privacy.DISABLED && !InferenceGlobalContext.isSelfHosted) return
- if (InferenceGlobalContext.status == ConnectionStatus.DISCONNECTED) return
- var maybeState: EditorTextState? = null
- val debounceMs: Long
- val editor = event.editor
- val logicalPos = event.editor.caretModel.logicalPosition
- var text = editor.document.text
- var offset = -1
- ApplicationManager.getApplication().runReadAction {
- offset = editor.caretModel.offset
- }
-
- val currentLine = text.substring(editor.document.getLineStartOffset(logicalPos.line),
- editor.document.getLineEndOffset(logicalPos.line))
- val rightOfCursor = text.substring(offset,
- editor.document.getLineEndOffset(logicalPos.line))
-
- if (!rightOfCursor.matches(specialSymbolsRegex)) return
-
- val isMultiline = currentLine.all { it == ' ' || it == '\t' }
- var pos = 0
- if (isMultiline) {
- val startOffset = editor.document.getLineStartOffset(logicalPos.line)
- val endOffset = editor.document.getLineEndOffset(logicalPos.line)
- text = text.removeRange(startOffset, endOffset)
- } else {
- pos = offset - editor.document.getLineStartOffset(logicalPos.line)
- }
-
- if (!event.force) {
- val docEvent = event.event ?: return
- if (docEvent.offset + docEvent.newLength > editor.document.text.length) return
- if (docEvent.newLength + docEvent.oldLength <= 0) return
- maybeState = EditorTextState(
- editor,
- editor.document.modificationStamp,
- docEvent.offset + docEvent.newLength + event.offsetCorrection
- )
-
- if (shouldIgnoreChange(docEvent, editor, maybeState.offset)) {
- return
- }
-
- debounceMs = CompletionTracker.calcDebounceTime(editor)
- CompletionTracker.updateLastCompletionRequestTime(editor)
- logger.debug("Debounce time: $debounceMs")
- } else {
- app.invokeAndWait {
- maybeState = EditorTextState(
- editor,
- editor.document.modificationStamp,
- editor.caretModel.offset
- )
- }
- debounceMs = 0
- }
-
- val state = maybeState ?: return
- if (!state.isValid()) return
-
- val promptInfo: List = listOf()
- val stat = UsageStatistic(scope, extension = getExtension(fileName))
- val request = RequestCreator.create(
- fileName, text, logicalPos.line, pos,
- stat, "Infill", "infill", promptInfo,
- stream = false, model = InferenceGlobalContext.model ?: Resources.defaultModel,
- multiline=isMultiline
- ) ?: return
-
- processTask = scheduler.schedule({
- process(request, state, event.force, )
- }, debounceMs, TimeUnit.MILLISECONDS)
- }
-
- private fun renderCompletion(
- editor: Editor,
- state: EditorTextState,
- completionData: Completion,
- animation: Boolean
- ) {
- var modificationStamp: Long = state.modificationStamp
- var offset: Int = state.offset
- app.invokeAndWait {
- modificationStamp = editor.document.modificationStamp
- offset = editor.caretModel.offset
- }
- val invalidStamp = state.modificationStamp != modificationStamp
- val invalidOffset = state.offset != offset
- if (invalidStamp || invalidOffset) {
- logger.info("Completion is dropped: invalidStamp || invalidOffset")
- logger.info(
- "state_offset: ${state.offset}," +
- " state_modificationStamp: ${state.modificationStamp}"
- )
- logger.info(
- "editor_offset: $offset, editor_modificationStamp: $modificationStamp"
- )
- return
- }
- if (processTask == null) {
- logger.info("Completion is dropped: there is no active processTask is left")
- return
- }
- logger.info(
- "Completion rendering: offset: ${state.offset}," +
- " modificationStamp: ${state.modificationStamp}"
- )
- logger.info("Completion data: ${completionData.completion}")
- try {
- if (completionLayout == null) {
- completionLayout = AsyncCompletionLayout(editor)
- }
- completionLayout?.also {
- it.update(completionData, state, needToRender, animation)
- }
- } catch (ex: Exception) {
- logger.warn("Exception while rendering completion", ex)
- logger.debug("Exception while rendering completion cancelOrClose request")
- cancelOrClose()
- }
- }
-
- override fun hide() {
- if (!isInActiveState()) return
- scheduler.submit {
- try {
- processTask?.get()
- } catch (_: CancellationException) {
- } finally {
- completionLayout?.hide()
- }
- }
- }
-
- override fun show() {
- if (isInActiveState()) return
- scheduler.submit {
- try {
- processTask?.get()
- } catch (_: CancellationException) {
- } finally {
- completionLayout?.show()
- }
- }
- }
-
- private fun process(
- request: SMCRequest,
- editorState: EditorTextState,
- force: Boolean,
- ) {
- InferenceGlobalContext.status = ConnectionStatus.PENDING
- completionInProgress = true
- if (force) {
- request.body.parameters.maxNewTokens = 50
- request.body.noCache = true
- temperatureCounter++
- } else {
- temperatureCounter = 0
- }
- if (temperatureCounter > 1) {
- request.body.parameters.temperature = 0.6F
- }
- if (requestTask != null && requestTask!!.isDone && requestTask!!.isCancelled ) {
- requestTask!!.cancel(true)
- }
- lastRequestId = request.id
- streamedInferenceFetch(request, dataReceiveEnded = {
- InferenceGlobalContext.status = ConnectionStatus.CONNECTED
- InferenceGlobalContext.lastErrorMsg = null
- }) { prediction ->
- val choice = prediction.choices.first()
- if (lastRequestId != prediction.requestId) {
- completionLayout?.dispose()
- completionLayout = null
- return@streamedInferenceFetch
- }
-
- val completion: Completion = if (completionLayout?.lastCompletionData == null ||
- completionLayout?.lastCompletionData?.createdTs != prediction.created) {
- Completion(request.body.inputs.sources.values.toList().first(),
- offset = editorState.offset,
- multiline = request.body.inputs.multiline,
- createdTs = prediction.created,
- isFromCache = prediction.cached,
- snippetTelemetryId = prediction.snippetTelemetryId
- )
- } else {
- completionLayout!!.lastCompletionData!!
- }
- if (completion.snippetTelemetryId == null) {
- completion.snippetTelemetryId = prediction.snippetTelemetryId
- }
- if ((!completionInProgress)
- || (choice.delta.isEmpty() && !choice.finishReason.isNullOrEmpty())) {
- return@streamedInferenceFetch
- }
- completion.updateCompletion(choice.delta)
- synchronized(this) {
- renderCompletion(
- editorState.editor, editorState, completion, !prediction.cached
- )
- }
- }?.also {
- try {
- requestTask = it.get()
- requestTask!!.get()
- logger.debug("Completion request finished")
- completionInProgress = false
- } catch (e: InterruptedException) {
- handleInterruptedException(requestTask, editorState.editor)
- } catch (e: InterruptedIOException) {
- handleInterruptedException(requestTask, editorState.editor)
- } catch (e: ExecutionException) {
- cancelOrClose()
- catchNetExceptions(e.cause)
- } catch (e: Exception) {
- InferenceGlobalContext.status = ConnectionStatus.ERROR
- InferenceGlobalContext.lastErrorMsg = e.message
- cancelOrClose()
- logger.warn("Exception while completion request processing", e)
- }
- }
- }
-
- private fun handleInterruptedException(requestFuture: Future<*>?, editor: Editor) {
- InferenceGlobalContext.status = ConnectionStatus.CONNECTED
- requestFuture?.cancel(true)
- cancelOrClose()
- logger.debug("lastReqJob abort")
- }
-
- private fun catchNetExceptions(e: Throwable?) {
- InferenceGlobalContext.status = ConnectionStatus.ERROR
- InferenceGlobalContext.lastErrorMsg = e?.message
- logger.warn("Exception while completion request processing", e)
- }
-
- override fun onTabPressed(editor: Editor, caret: Caret?, dataContext: DataContext) {
- completionLayout?.apply {
- applyPreview(caret ?: editor.caretModel.currentCaret)
- lastCompletionData?.snippetTelemetryId?.let {
- UsageStats.snippetAccepted(it)
- }
- dispose()
- }
- completionLayout = null
- }
-
- override fun onEscPressed(editor: Editor, caret: Caret?, dataContext: DataContext) {
- cancelOrClose()
- }
-
- override fun onCaretChange(event: CaretEvent) {
- }
-
- override fun caretPositionChanged(event: CaretEvent) {
- cancelOrClose()
- }
-
- override fun isInActiveState(): Boolean = completionLayout != null && completionLayout!!.rendered && needToRender
-
- override fun cleanup(editor: Editor) {
- cancelOrClose()
- }
-
- private fun shouldIgnoreChange(event: DocumentEvent?, editor: Editor, offset: Int): Boolean {
- if (event == null) return false
- val document = event.document
-
- if (editor.editorKind != EditorKind.MAIN_EDITOR && !app.isUnitTestMode) {
- return true
- }
- if (!EditorModificationUtil.checkModificationAllowed(editor)
- || document.getRangeGuard(offset, offset) != null
- ) {
- document.fireReadOnlyModificationAttempt()
- return true
- }
- return false
- }
-
- private fun getActiveFile(document: Document, project: Project?): String? {
- val projectPath = project?.basePath ?: return null
- val file = FileDocumentManager.getInstance().getFile(document) ?: return null
- return Path(file.path).toUri().toString().replace(Path(projectPath).toUri().toString(), "")
- }
-
- private fun cancelOrClose() {
- try {
- processTask?.cancel(true)
- processTask?.get()
- } catch (_: CancellationException) {
- } finally {
- if (InferenceGlobalContext.status != ConnectionStatus.DISCONNECTED) {
- InferenceGlobalContext.status = ConnectionStatus.CONNECTED
- }
- completionInProgress = false
- processTask = null
- completionLayout?.dispose()
- completionLayout = null
- }
- }
-}
diff --git a/src/main/kotlin/com/smallcloud/refactai/modes/completion/CompletionState.kt b/src/main/kotlin/com/smallcloud/refactai/modes/completion/CompletionState.kt
deleted file mode 100644
index 2215c182..00000000
--- a/src/main/kotlin/com/smallcloud/refactai/modes/completion/CompletionState.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-package com.smallcloud.refactai.modes.completion
-
-import com.intellij.openapi.diagnostic.Logger
-import com.smallcloud.refactai.modes.EditorTextState
-import com.smallcloud.refactai.modes.completion.structs.Completion
-import java.util.regex.Pattern
-import kotlin.math.abs
-
-
-class CompletionState(
- private var editorState: EditorTextState,
- private val filterRightFromCursor: Boolean = true,
- private val force: Boolean = false
-) {
- private val maxTextSize: Long = 180 * 1024
- private val rightOfCursorSpecialChar = Pattern.compile("^[:\\s\\t\\n\\r),.\"'\\]]*\$")
-
- var multiline: Boolean = true
- private val logger = Logger.getInstance("CompletionUtils")
-
- @Suppress("RedundantSetter")
- var readyForCompletion: Boolean = false
- private set(value) {
- field = value
- }
-
- init {
- run {
- if (!force && filterRightFromCursor) {
- val rightOfCursor = editorState.currentLine.substring(editorState.offsetByCurrentLine)
- val rightOfCursorHasOnlySpecialChars = rightOfCursorSpecialChar.matcher(rightOfCursor).matches()
- if (!rightOfCursorHasOnlySpecialChars) {
- logger.info("There are no special characters in the $rightOfCursor")
- return@run
- }
- }
- val leftOfCursor = editorState.currentLine.substring(0, editorState.offsetByCurrentLine)
- multiline = leftOfCursor.replace(" ", "").replace("\t", "").isEmpty()
- multiline = multiline || force
- val requestedText = editorState.document.text
- if (!force && requestedText.length > maxTextSize) return@run
- readyForCompletion = true
- }
- }
-
- fun makeCompletion(
- headIndex: Int,
- completion: String,
- tailIndex: Int,
- finishReason: String
- ): Completion {
- val requestedText = editorState.document.text
- val textCurrentLine = editorState.currentLine
-
- val lines = completion.split('\n')
- var firstLine = lines.first()
-
- var headIndexUpdated = headIndex
- var leftSymbolsToRemove = 0
- if (editorState.currentLineIsEmptySymbols() && firstLine.isNotEmpty()) {
- val firstNonNonEmptyIndex = firstLine.indexOfFirst { it !in setOf(' ', '\t') }
- val diff = firstNonNonEmptyIndex - textCurrentLine.length
- firstLine = firstLine.substring(if (diff <= 0) firstNonNonEmptyIndex else diff)
- leftSymbolsToRemove = if (diff < 0) abs(diff) else 0
- headIndexUpdated = headIndex + textCurrentLine.length
- }
-
- // remove it ASAP after fix in scratchpad
- val addedNewLine = requestedText.last() != '\n'
-
- val currentMultiline = multiline && lines.size > 1
- val startIndex: Int = minOf(requestedText.length, headIndexUpdated)
- logger.info("Finish reason: $finishReason, firstLine: $firstLine")
- val endIndex = if (finishReason == "ins-stoptoken" && completion.isEmpty() && tailIndex == 0) {
- startIndex
- } else {
- requestedText.length - tailIndex + if (addedNewLine) 1 else 0
- }
-
- val editedCompletion = if (currentMultiline) {
- firstLine + lines.subList(1, lines.size).joinToString("\n", prefix = "\n")
- } else {
- firstLine
- }
- return Completion(
- originalText = requestedText,
- completion = editedCompletion,
-// currentLinesAreEqual = false,
- multiline = currentMultiline,
- offset = startIndex,
-// firstLineEndIndex = endIndex,
-// endIndex = maxOf(endIndex, startIndex),
-// createdTs = System.currentTimeMillis(),
-// leftSymbolsToRemove = leftSymbolsToRemove,
- )
- }
-}
diff --git a/src/main/kotlin/com/smallcloud/refactai/modes/completion/PopupCompletionContributor.kt b/src/main/kotlin/com/smallcloud/refactai/modes/completion/PopupCompletionContributor.kt
deleted file mode 100644
index 704d010d..00000000
--- a/src/main/kotlin/com/smallcloud/refactai/modes/completion/PopupCompletionContributor.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.smallcloud.refactai.modes.completion
-
-import com.intellij.codeInsight.completion.CompletionContributor
-import com.intellij.codeInsight.completion.CompletionParameters
-import com.intellij.codeInsight.completion.CompletionResultSet
-import com.intellij.codeInsight.lookup.LookupListener
-import com.intellij.codeInsight.lookup.LookupManager
-import com.intellij.openapi.editor.EditorKind
-import com.smallcloud.refactai.modes.ModeProvider
-
-class PopupCompletionContributor: CompletionContributor() {
- override fun fillCompletionVariants(
- parameters: CompletionParameters, resultSet: CompletionResultSet
- ) {
- val provider = ModeProvider.getOrCreateModeProvider(parameters.editor)
-
- if (parameters.editor.editorKind != EditorKind.MAIN_EDITOR) {
- return
- }
- if (provider.isInCompletionMode()) {
- val listener = CompletionLookupListener.getOrCreate(parameters.editor)
- registerLookupListener(parameters, listener)
- }
- }
-
- private fun registerLookupListener(
- parameters: CompletionParameters, lookupListener: LookupListener
- ) {
- val lookupEx = LookupManager.getActiveLookup(parameters.editor) ?: return
- lookupEx.removeLookupListener(lookupListener)
- lookupEx.addLookupListener(lookupListener)
- }
-}
diff --git a/src/main/kotlin/com/smallcloud/refactai/modes/completion/prompt/FilesCollector.kt b/src/main/kotlin/com/smallcloud/refactai/modes/completion/prompt/FilesCollector.kt
deleted file mode 100644
index 1a67fcea..00000000
--- a/src/main/kotlin/com/smallcloud/refactai/modes/completion/prompt/FilesCollector.kt
+++ /dev/null
@@ -1,117 +0,0 @@
-package com.smallcloud.refactai.modes.completion.prompt
-
-import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.editor.ex.EditorEx
-import com.intellij.openapi.editor.ex.FocusChangeListener
-import com.intellij.openapi.fileEditor.FileEditor
-import com.intellij.openapi.fileEditor.FileEditorManager
-import com.intellij.openapi.fileEditor.FileEditorManagerListener
-import com.intellij.openapi.fileEditor.impl.text.PsiAwareTextEditorImpl
-import com.intellij.openapi.module.ModuleManager
-import com.intellij.openapi.project.Project
-import com.intellij.openapi.roots.ModuleRootManager
-import com.intellij.openapi.util.Key
-import com.intellij.openapi.vfs.VirtualFile
-import com.intellij.psi.search.FilenameIndex
-import com.intellij.psi.search.ProjectScope
-import com.intellij.util.ObjectUtils
-import com.smallcloud.refactai.privacy.Privacy
-import com.smallcloud.refactai.privacy.PrivacyService
-import java.nio.file.Path
-import kotlin.io.path.relativeTo
-import com.smallcloud.refactai.io.InferenceGlobalContext.Companion.instance as InferenceGlobalContext
-import kotlin.io.path.Path as makePath
-
-data class FileInformationEntry(
- val file: VirtualFile,
- var lastEditorShown: Long,
- val projectRelativeFilePath: String,
- private val fileEditorManager: FileEditorManager,
-) {
- val lastUpdatedTs: Long
- get() = file.timeStamp
-
- fun getEditor(): FileEditor? = fileEditorManager.getSelectedEditor(file)
- fun isOpened(): Boolean = fileEditorManager.isFileOpen(file)
-}
-
-class FilesCollector(
- private val project: Project
-) : FileEditorManagerListener {
- private val fileEditorManager: FileEditorManager = FileEditorManager.getInstance(project)
- private var filesInformation: LinkedHashSet = linkedSetOf()
- private val projectPaths: List =
- ModuleManager.getInstance(project).modules
- .map {
- ModuleRootManager.getInstance(it)
- }
- .map {
- it.sourceRoots.map {
- makePath(it.path)
- }
- }
- .flatten()
-
- init {
- FilenameIndex.getAllFilenames(project).mapNotNull {
- FilenameIndex.getVirtualFilesByName(it, ProjectScope.getProjectScope(project)).find { virtualFile ->
- virtualFile.isValid && virtualFile.exists() && virtualFile.isInLocalFileSystem && !virtualFile.isDirectory
- }
- }.forEach {
- makeEntry(it, projectPaths)
- }
-
- project.messageBus.connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, this)
- }
-
- private fun makeEntry(file: VirtualFile, projectPaths: List) {
- val maybeInfo = filesInformation.find { info -> file == info.file }
- if (maybeInfo != null) {
- return
- }
-
- val filePath = makePath(file.path)
- val relativeFilePath: String = if (projectPaths.isNotEmpty()) {
- projectPaths.map { filePath.relativeTo(it).toString() }.minBy { it.length }
- } else {
- filePath.toString()
- }
- val entry = FileInformationEntry(file, 0, relativeFilePath, fileEditorManager)
- filesInformation.add(entry)
- entry.getEditor()?.let { editor ->
- if (entry.isOpened()) {
- entry.lastEditorShown = System.currentTimeMillis()
- }
-
- ObjectUtils.consumeIfCast((editor as PsiAwareTextEditorImpl).editor, EditorEx::class.java) {
- it?.addFocusListener(object : FocusChangeListener {
- override fun focusGained(editor: Editor) {
- entry.lastEditorShown = System.currentTimeMillis()
- }
- })
- }
- }
- }
-
- override fun fileOpened(source: FileEditorManager, file: VirtualFile) {
- makeEntry(file, projectPaths)
- }
-
- fun collect(): List {
- return filesInformation.filter {
- it.file.isValid && it.file.exists() && (PrivacyService.instance.getPrivacy(it.file) != Privacy.DISABLED
- || InferenceGlobalContext.isSelfHosted)
- }
- }
-
- companion object {
- private val FILES_COLLECTORS = Key.create("FILES_COLLECTORS")
-
- fun getInstance(project: Project): FilesCollector {
- if (!FILES_COLLECTORS.isIn(project)) {
- FILES_COLLECTORS[project] = FilesCollector(project)
- }
- return FILES_COLLECTORS[project]
- }
- }
-}
diff --git a/src/main/kotlin/com/smallcloud/refactai/modes/completion/prompt/PromptCooker.kt b/src/main/kotlin/com/smallcloud/refactai/modes/completion/prompt/PromptCooker.kt
deleted file mode 100644
index 836a9ed3..00000000
--- a/src/main/kotlin/com/smallcloud/refactai/modes/completion/prompt/PromptCooker.kt
+++ /dev/null
@@ -1,99 +0,0 @@
-package com.smallcloud.refactai.modes.completion.prompt
-
-import com.intellij.openapi.fileEditor.FileDocumentManager
-import com.intellij.openapi.vfs.VirtualFile
-import com.smallcloud.refactai.modes.EditorTextState
-import info.debatty.java.stringsimilarity.Jaccard
-
-data class PromptInfo(
- val fileName: String,
- val textLines: List,
- val prompt: String,
- val distance: Double,
- val fileInfo: FileInformationEntry,
-) {
- val text = textLines.joinToString("\n")
- fun cursors(): Pair {
- val start = text.indexOf(prompt)
- val end = start + prompt.length
- return start to end
- }
-}
-
-object PromptCooker {
- private const val windowSize: Int = 40
- private val simAlg = Jaccard()
-
- fun cook(
- currentFileEditorHelper: EditorTextState,
- files: List,
- mostImportantFilesMaxCount: Int,
- lessImportantFilesMaxCount: Int,
- maxFileSize: Int
- ): List {
- val currentFile = FileDocumentManager.getInstance().getFile(currentFileEditorHelper.document)
- val currentExt = currentFile?.extension
- val mostImportantFiles = files
- .filter { it.file != currentFile }
- .filter { it.isOpened() }
- .sortedByDescending { it.lastEditorShown }
- .take(mostImportantFilesMaxCount)
- val lessImportantFiles = files
- .filter { it.file != currentFile }
- .filter { !mostImportantFiles.contains(it) }
- .sortedByDescending { it.lastUpdatedTs }
- .take(lessImportantFilesMaxCount)
- val filteredFiles = mostImportantFiles + lessImportantFiles
-
- val topIdx = minOf(
- currentFileEditorHelper.currentLineNumber,
- windowSize / 2
- )
- val botIdx = minOf(
- currentFileEditorHelper.lines.size - currentFileEditorHelper.currentLineNumber,
- windowSize / 2
- )
- val currentText = currentFileEditorHelper.lines.subList(
- currentFileEditorHelper.currentLineNumber - topIdx,
- botIdx + currentFileEditorHelper.currentLineNumber
- ).joinToString("\n")
- return compareOtherFiles(currentText, filteredFiles, currentExt, maxFileSize)
- }
-
- private fun compareOtherFiles(
- currentText: String,
- files: List,
- currentExt: String?,
- maxFileSize: Int,
- ): List {
- return files
- .filter { it.file.extension == currentExt && it.file.length < maxFileSize }
- .map { info -> info to getLinesFromFile(info.file) }
- .filter { it.second != null }
- .mapNotNull { (info, lines) ->
- lines!!
- .windowed(windowSize, step = windowSize / 2, partialWindows = true) {
- it.joinToString("\n")
- }
- .map {
- PromptInfo(
- fileName = info.projectRelativeFilePath,
- textLines = lines,
- prompt = it,
- distance = simAlg.distance(it, currentText),
- fileInfo = info
- )
- }
- .minByOrNull { it.distance }
- }
- }
-
- private fun getLinesFromFile(file: VirtualFile): List? {
- val reader = file.inputStream.bufferedReader()
- return try {
- reader.use { it.readLines() }
- } catch (e: Exception) {
- null
- }
- }
-}
diff --git a/src/main/kotlin/com/smallcloud/refactai/modes/completion/renderer/BlockRenderer.kt b/src/main/kotlin/com/smallcloud/refactai/modes/completion/renderer/BlockRenderer.kt
deleted file mode 100644
index c946b2c6..00000000
--- a/src/main/kotlin/com/smallcloud/refactai/modes/completion/renderer/BlockRenderer.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-package com.smallcloud.refactai.modes.completion.renderer
-
-import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.editor.EditorCustomElementRenderer
-import com.intellij.openapi.editor.Inlay
-import com.intellij.openapi.editor.markup.TextAttributes
-import java.awt.Color
-import java.awt.Graphics
-import java.awt.Rectangle
-
-
-class AsyncBlockElementRenderer(
- initialBlockText: List,
- private val editor: Editor,
- private val deprecated: Boolean
-) : EditorCustomElementRenderer {
- private var color: Color? = null
- var blockText: List = initialBlockText
- set(value) {
- synchronized(this) {
- field = value
- }
- }
- get() {
- return field.map {
- it.replace("\t", " ".repeat(editor.settings.getTabSize(editor.project)))
- }
- }
-
- override fun calcWidthInPixels(inlay: Inlay<*>): Int {
- synchronized(this) {
- val line = blockText.maxByOrNull { it.length }
- val width = editor.contentComponent
- .getFontMetrics(RenderHelper.getFont(editor, deprecated)).stringWidth(line!!)
- return maxOf(width, 1)
- }
- }
-
- override fun calcHeightInPixels(inlay: Inlay<*>): Int {
- synchronized(this) {
- return maxOf(editor.lineHeight * blockText.size, 1)
- }
- }
-
- override fun paint(
- inlay: Inlay<*>,
- g: Graphics,
- targetRegion: Rectangle,
- textAttributes: TextAttributes
- ) {
- synchronized(this) {
- color = color ?: RenderHelper.color
- g.color = color
- g.font = RenderHelper.getFont(editor, deprecated)
-
- blockText.withIndex().forEach { (i, line) ->
- g.drawString(
- line,
- 0,
- targetRegion.y + i * editor.lineHeight + editor.ascent
- )
- }
- }
- }
-}
diff --git a/src/main/kotlin/com/smallcloud/refactai/modes/completion/renderer/CompletionLayout.kt b/src/main/kotlin/com/smallcloud/refactai/modes/completion/renderer/CompletionLayout.kt
deleted file mode 100644
index 281c753a..00000000
--- a/src/main/kotlin/com/smallcloud/refactai/modes/completion/renderer/CompletionLayout.kt
+++ /dev/null
@@ -1,259 +0,0 @@
-package com.smallcloud.refactai.modes.completion.renderer
-
-import com.intellij.openapi.Disposable
-import com.intellij.openapi.application.ApplicationManager
-import com.intellij.openapi.application.runWriteAction
-import com.intellij.openapi.diagnostic.Logger
-import com.intellij.openapi.editor.Caret
-import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.editor.markup.RangeHighlighter
-import com.intellij.openapi.util.Disposer
-import com.intellij.psi.PsiDocumentManager
-import com.intellij.util.concurrency.AppExecutorUtil
-import com.smallcloud.refactai.modes.EditorTextState
-import com.smallcloud.refactai.modes.completion.structs.Completion
-import dev.gitlive.difflib.DiffUtils
-import dev.gitlive.difflib.patch.DeltaType
-import java.util.concurrent.Future
-import kotlin.math.min
-
-class AsyncCompletionLayout(
- private val editor: Editor
-) : Disposable {
- private val renderChunkSize: Int = 1
- private val renderChunkTimeoutMs: Long = 2
- private var inlayer: AsyncInlayer? = null
- private var blockEvents: Boolean = false
- private var isUpdating: Boolean = true
- private var updateTask: Future<*>? = null
- private val scheduler = AppExecutorUtil.createBoundedScheduledExecutorService("SMCAsyncCompletionLayout", 1)
- var rendered: Boolean = false
- private var needToRender: Boolean = true
- private var highlighted: Boolean = false
- var lastCompletionData: Completion? = null
- private var rangeHighlighters: MutableList = mutableListOf()
-
- override fun dispose() {
- dispose(true)
- }
-
- fun dispose(needGet: Boolean = true) {
- stopUpdate(needGet)
- ApplicationManager.getApplication().invokeLater {
- rendered = false
- needToRender = false
- highlighted = false
- blockEvents = false
- rangeHighlighters.forEach { editor.markupModel.removeHighlighter(it) }
- rangeHighlighters.clear()
- inlayer?.dispose()
- }
- }
-
- private fun stopUpdate(needGet: Boolean = true) {
- synchronized(this) {
- if (!scheduler.isShutdown) {
- scheduler.shutdownNow()
- }
- isUpdating = false
- needToRender = false
- if (updateTask == null) return
- if (needGet && (!updateTask?.isCancelled!! || !updateTask?.isDone!!)) {
- updateTask!!.get()
- }
- updateTask = null
- }
- }
-
- fun update(
- completionData: Completion,
- editorState: EditorTextState,
- needToRender: Boolean,
- animation: Boolean
- ): Future<*>? {
- if (lastCompletionData != null && completionData.createdTs != lastCompletionData?.createdTs) return null
- if (!isUpdating) return null
- updateTask = scheduler.submit {
- lastCompletionData = completionData
- try {
- blockEvents = true
- editor.document.startGuardedBlockChecking()
-
- if (inlayer == null) {
- inlayer = AsyncInlayer(editor)
- }
-
- if (completionData.multiline) {
- renderAndUpdateMultilineState(completionData, editorState, needToRender, animation)
- } else {
- renderAndUpdateState(completionData, editorState, needToRender, animation)
- }
- } catch (ex: Exception) {
- dispose(false)
- throw ex
- } finally {
- editor.document.stopGuardedBlockChecking()
- blockEvents = false
- }
- }
- return updateTask
- }
-
- private fun renderAndUpdateState(
- completionData: Completion,
- editorState: EditorTextState,
- needToRender: Boolean,
- animation: Boolean) {
- inlayer?.let { inlayer ->
- val currentLine = completionData.originalText.substring(completionData.offset)
- .substringBefore('\n', "")
- val patch = DiffUtils.diff(currentLine.toList(), completionData.completion.toList())
- for (delta in patch.getDeltas()) {
- if (delta.type != DeltaType.INSERT) { continue }
- val currentOffset = editorState.offset + delta.source.position
- var blockText = delta.target.lines?.joinToString("") ?: ""
- val currentText = inlayer.getText(currentOffset) ?: ""
- if (blockText.startsWith(currentText)) {
- blockText = blockText.substring(currentText.length)
- }
-
- inlayer.setText(currentOffset, currentText + blockText)
- val text = blockText
-
- if (needToRender) {
- if (animation) {
- for (ch in text.chunked(renderChunkSize)) {
- if (!this.needToRender) {
- return@let
- }
- ApplicationManager.getApplication().invokeLater({
- inlayer.addText(currentOffset, ch)
- }, { !this.needToRender })
- Thread.sleep(renderChunkTimeoutMs)
- }
- } else {
- ApplicationManager.getApplication().invokeLater({
- inlayer.addText(currentOffset, text)
- }, { !this.needToRender })
- }
- rendered = true
- } else {
- inlayer.addTextWithoutRendering(currentOffset, text)
- }
- }
- }
- }
-
- private fun countSpacesBeforeFirstSymbol(string: String): Int {
- var count = 0
- for (char in string) {
- if (char.isWhitespace()) {
- count++
- } else {
- break
- }
- }
- return count
- }
-
- private fun renderAndUpdateMultilineState(
- completionData: Completion,
- editorState: EditorTextState,
- needToRender: Boolean,
- animation: Boolean) {
- inlayer?.let { inlayer ->
- var blockText = lastCompletionData!!.completion
- val trimLen = min(editorState.offsetByCurrentLine, countSpacesBeforeFirstSymbol(blockText))
- val offset = editorState.offset - (editorState.offsetByCurrentLine - trimLen)
- blockText = blockText.substring(trimLen)
- val currentText = inlayer.getText(offset) ?: ""
- if (blockText.startsWith(currentText)) {
- blockText = blockText.substring(currentText.length)
- }
- inlayer.setText(offset, completionData.completion)
- val text = blockText
-
- if (needToRender) {
- if (animation) {
- for (ch in text.chunked(renderChunkSize)) {
- if (!this.needToRender) {
- return@let
- }
- ApplicationManager.getApplication().invokeLater({
- inlayer.addText(offset, ch)
- }, { !this.needToRender })
- Thread.sleep(renderChunkTimeoutMs)
- }
- } else {
- ApplicationManager.getApplication().invokeLater({
- inlayer.addText(offset, text)
- }, { !this.needToRender })
- }
- rendered = true
- } else {
- inlayer.addTextWithoutRendering(offset, text)
- }
-
- }
- }
-
- fun applyPreview(caret: Caret?) {
- caret ?: return
- val project = editor.project ?: return
- try {
- hide()
- stopUpdate()
- runWriteAction {
- PsiDocumentManager.getInstance(project).getPsiFile(editor.document) ?: return@runWriteAction
- applyPreviewInternal()
- }
- } catch (e: Throwable) {
- Logger.getInstance(javaClass).warn("Failed in the processes of accepting completion", e)
- } finally {
- Disposer.dispose(this)
- }
- }
-
- private fun applyPreviewInternal() {
- lastCompletionData?.let { completion ->
- var startIndex = completion.offset
- if (completion.multiline) {
- val line = editor.document.getLineNumber(completion.offset)
- startIndex -= (editor.document.getLineEndOffset(line) - editor.document.getLineStartOffset(line))
- }
- var endIndex = completion.offset
- if (!completion.multiline) {
- val currentLine = completion.originalText.substring(startIndex)
- .substringBefore('\n', "")
- endIndex = completion.offset + currentLine.length
- }
- editor.document.replaceString(startIndex, endIndex, completion.completion)
- editor.caretModel.moveToOffset(startIndex + completion.completion.length)
- }
- }
-
- fun hide() {
- ApplicationManager.getApplication().invokeLater {
- if (!rendered) return@invokeLater
- rendered = false
- blockEvents = false
- rangeHighlighters.forEach { editor.markupModel.removeHighlighter(it) }
- rangeHighlighters.clear()
- highlighted = false
-// inlayers.forEach { it.value.hide() }
- inlayer?.hide()
-// inlayers.lastOrNull()?.hide()
- }
- }
-
- fun show() {
- ApplicationManager.getApplication().invokeLater {
- if (rendered) return@invokeLater
-// lastCompletionData?.let { highlight(it) }
-// inlayers.forEach { it.value.show () }
- inlayer?.show()
-// inlayers.lastOrNull()?.show()
- rendered = true
- }
- }
-}
diff --git a/src/main/kotlin/com/smallcloud/refactai/modes/completion/renderer/Inlayer.kt b/src/main/kotlin/com/smallcloud/refactai/modes/completion/renderer/Inlayer.kt
deleted file mode 100644
index e2c899ef..00000000
--- a/src/main/kotlin/com/smallcloud/refactai/modes/completion/renderer/Inlayer.kt
+++ /dev/null
@@ -1,162 +0,0 @@
-package com.smallcloud.refactai.modes.completion.renderer
-
-import com.intellij.openapi.Disposable
-import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.editor.Inlay
-import com.intellij.openapi.util.Disposer
-
-class AsyncInlayer(
- val editor: Editor,
-) : Disposable {
- private var lineRenderers: MutableMap = mutableMapOf()
- private var lineInlays: MutableMap> = mutableMapOf()
- private var blockRenderer: AsyncBlockElementRenderer? = null
- private var blockInlay: Inlay<*>? = null
- private var collectedTexts: MutableMap = mutableMapOf()
- private var disposed: Boolean = false
- private var hidden: Boolean = false
- private var realTexts: MutableMap = mutableMapOf()
-
- override fun dispose() {
- synchronized(this) {
- disposed = true
- lineInlays.forEach {
- it.value.dispose()
- }
- lineInlays.clear()
- blockInlay?.let {
- it.dispose()
- blockInlay = null
- }
- }
- }
-
- fun hide() {
- synchronized(this) {
- if (disposed) return
- hidden = true
- lineInlays.forEach {
- it.value.dispose()
- }
- lineInlays.clear()
- blockInlay?.let {
- it.dispose()
- blockInlay = null
- }
- }
- }
-
- fun show() {
- synchronized(this) {
- if (disposed) return
- hidden = false
-// addText(initialOffset, "")
- lineInlays.forEach {
- it.value.update()
- }
-// lineInlay?.update()
- blockInlay?.update()
- }
- }
-
- private fun createLine(offset: Int, initialText: String) {
- lineRenderers[offset] = AsyncLineRenderer(initialText, editor, false)
- val lineInlay = editor
- .inlayModel
- .addInlineElement(offset, true, lineRenderers[offset]!!)
- ?.also { Disposer.register(this, it) }
- if (lineInlay != null) {
- lineInlays[offset] = lineInlay
- }
- }
-
- private fun createBlock(offset: Int, initialLines: List) {
- blockRenderer = AsyncBlockElementRenderer(initialLines, editor, false)
- blockInlay = editor
- .inlayModel
- .addBlockElement(offset, false, false, 1, blockRenderer!!)
- ?.also { Disposer.register(this, it) }
- }
-
- fun getText(offset: Int): String? {
- synchronized(this) {
- return realTexts[offset]
- }
- }
-
- fun setText(offset: Int, text: String) {
- synchronized(this) {
- realTexts[offset] = text
- }
- }
-
- fun addText(offset: Int, text: String) {
- if (disposed) { dispose() }
- synchronized(this) {
- if (!collectedTexts.containsKey(offset)) { collectedTexts[offset] = "" }
- collectedTexts[offset] += text
- val lines = collectedTexts[offset]!!.split('\n')
-
- if (lineInlays[offset] == null && !disposed) {
- createLine(offset, lines[0])
- } else {
- lineRenderers[offset]?.text = lines[0]
- lineInlays.forEach {
- it.value.update()
- }
- }
-
- if (lines.size > 1) {
- val subList = lines.subList(1, lines.size)
- val notEmpty = subList.any { it.isNotEmpty() }
- if (notEmpty) {
- if (blockInlay == null && !disposed) {
- createBlock(offset, subList)
- } else {
- blockRenderer?.blockText = subList
- blockInlay?.update()
- }
- }
- }
- }
- }
-
- fun addTextWithoutRendering(offset: Int, text: String) {
- synchronized(this) {
- if (disposed) return
- collectedTexts[offset] += text
- }
- }
-
- fun addTextToLast(text: String) {
- synchronized(this) {
- val entry = realTexts.maxBy { it.key }
- addText(entry.key, text)
- }
- }
-
- fun addTextWithoutRenderingToLast(text: String) {
- synchronized(this) {
- val entry = realTexts.maxBy { it.key }
- addTextWithoutRendering(entry.key, text)
- }
- }
-
- fun getLastText(): String? {
- synchronized(this) {
-// try {
- val entry = realTexts.maxBy { it.key }
- return getText(entry.key)
-// } catch (e: Exception) {
-// throw e
-// }
- }
- }
-
- fun setLastText(realBlockText: String) {
- synchronized(this) {
- val entry = realTexts.maxBy { it.key }
- setText(entry.key, realBlockText)
- }
- }
-}
\ No newline at end of file
diff --git a/src/main/kotlin/com/smallcloud/refactai/modes/completion/renderer/LineRenderer.kt b/src/main/kotlin/com/smallcloud/refactai/modes/completion/renderer/LineRenderer.kt
deleted file mode 100644
index 7b46ff98..00000000
--- a/src/main/kotlin/com/smallcloud/refactai/modes/completion/renderer/LineRenderer.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.smallcloud.refactai.modes.completion.renderer
-
-import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.editor.EditorCustomElementRenderer
-import com.intellij.openapi.editor.Inlay
-import com.intellij.openapi.editor.markup.TextAttributes
-import java.awt.Color
-import java.awt.Graphics
-import java.awt.Rectangle
-
-class AsyncLineRenderer(
- initialText: String,
- private val editor: Editor,
- private val deprecated: Boolean
-) : EditorCustomElementRenderer {
- private var color: Color? = null
- var text: String = initialText
- set(value) {
- synchronized(this) {
- field = value
- }
- }
- get() {
- return field.replace("\t", " ".repeat(editor.settings.getTabSize(editor.project)))
- }
-
- override fun calcWidthInPixels(inlay: Inlay<*>): Int {
- synchronized(this) {
- val width = editor.contentComponent
- .getFontMetrics(RenderHelper.getFont(editor, deprecated)).stringWidth(text)
-
- return maxOf(width, 1)
- }
- }
-
- override fun paint(
- inlay: Inlay<*>,
- g: Graphics,
- targetRegion: Rectangle,
- textAttributes: TextAttributes
- ) {
- synchronized(this) {
- color = color ?: RenderHelper.color
- g.color = color
- g.font = RenderHelper.getFont(editor, deprecated)
- g.drawString(text, targetRegion.x, targetRegion.y + editor.ascent)
- }
- }
-}
diff --git a/src/main/kotlin/com/smallcloud/refactai/modes/completion/renderer/RenderHelper.kt b/src/main/kotlin/com/smallcloud/refactai/modes/completion/renderer/RenderHelper.kt
deleted file mode 100644
index 689c8e55..00000000
--- a/src/main/kotlin/com/smallcloud/refactai/modes/completion/renderer/RenderHelper.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.smallcloud.refactai.modes.completion.renderer
-
-import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.editor.colors.EditorFontType
-import com.intellij.ui.JBColor
-import java.awt.Color
-import java.awt.Font
-import java.awt.font.TextAttribute
-
-object RenderHelper {
- fun getFont(editor: Editor, deprecated: Boolean): Font {
- val font = editor.colorsScheme.getFont(EditorFontType.ITALIC)
- if (!deprecated) {
- return font
- }
- val attributes: MutableMap = HashMap(font.attributes)
- attributes[TextAttribute.STRIKETHROUGH] = TextAttribute.STRIKETHROUGH_ON
- return Font(attributes)
- }
-
- val color: Color
- get() {
- return JBColor.GRAY
- }
-}
diff --git a/src/main/kotlin/com/smallcloud/refactai/modes/completion/structs/DocumentEventExtra.kt b/src/main/kotlin/com/smallcloud/refactai/modes/completion/structs/DocumentEventExtra.kt
deleted file mode 100644
index 4a48cbc8..00000000
--- a/src/main/kotlin/com/smallcloud/refactai/modes/completion/structs/DocumentEventExtra.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.smallcloud.refactai.modes.completion.structs
-
-import com.intellij.openapi.editor.Editor
-import com.intellij.openapi.editor.event.DocumentEvent
-import java.lang.System.currentTimeMillis
-
-data class DocumentEventExtra(
- val event: DocumentEvent?,
- val editor: Editor,
- val ts: Long,
- val force: Boolean = false,
- val offsetCorrection: Int = 0
-) {
- companion object {
- fun empty(editor: Editor): DocumentEventExtra {
- return DocumentEventExtra(
- null, editor, currentTimeMillis()
- )
- }
- }
-}
diff --git a/src/main/kotlin/com/smallcloud/refactai/modes/completion/structs/StringUtils.kt b/src/main/kotlin/com/smallcloud/refactai/modes/completion/structs/StringUtils.kt
deleted file mode 100644
index 9a9d6f5c..00000000
--- a/src/main/kotlin/com/smallcloud/refactai/modes/completion/structs/StringUtils.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.smallcloud.refactai.modes.completion.structs
-
-
-fun String.getChar(index: Int): Char? {
- if (isEmpty()) return null
- return if (index < 0) {
- this[length + index]
- } else {
- this[index]
- }
-}
\ No newline at end of file
diff --git a/src/main/kotlin/com/smallcloud/refactai/settings/AppSettingsComponent.kt b/src/main/kotlin/com/smallcloud/refactai/settings/AppSettingsComponent.kt
index 5d5e10d9..4398be2c 100644
--- a/src/main/kotlin/com/smallcloud/refactai/settings/AppSettingsComponent.kt
+++ b/src/main/kotlin/com/smallcloud/refactai/settings/AppSettingsComponent.kt
@@ -33,6 +33,8 @@ class AppSettingsComponent {
myXDebugLSPPortLabel.isVisible = true
myStagingVersionText.isVisible = true
myStagingVersionLabel.isVisible = true
+ astCheckbox.isVisible = true
+ vecdbCheckbox.isVisible = true
}
}
@@ -57,6 +59,13 @@ class AppSettingsComponent {
}
private val defaultSystemPromptTextArea = JBTextArea()
+ private val astCheckbox = JCheckBox("AST").apply {
+ isVisible = false
+ }
+ private val vecdbCheckbox = JCheckBox("VECDB").apply {
+ isVisible = false
+ }
+
init {
mainPanel = FormBuilder.createFormBuilder().run {
@@ -94,6 +103,8 @@ class AppSettingsComponent {
addComponent(developerModeCheckBox, UIUtil.LARGE_VGAP)
addLabeledComponent(myXDebugLSPPortLabel, myXDebugLSPPort, UIUtil.LARGE_VGAP)
addLabeledComponent(myStagingVersionLabel, myStagingVersionText, UIUtil.LARGE_VGAP)
+ addComponent(astCheckbox, UIUtil.LARGE_VGAP)
+ addComponent(vecdbCheckbox, UIUtil.LARGE_VGAP)
addComponentFillVertically(JPanel(), 0)
}.panel
@@ -105,6 +116,7 @@ class AppSettingsComponent {
val preferredFocusedComponent: JComponent
get() = myTokenText
+
var tokenText: String
get() = myTokenText.text
set(newText) {
@@ -136,6 +148,18 @@ class AppSettingsComponent {
set(newVal) {
developerModeCheckBox.isSelected = newVal
}
+
+ var astIsEnabled: Boolean
+ get() = astCheckbox.isSelected
+ set(newVal) {
+ astCheckbox.isSelected = newVal
+ }
+ var vecdbIsEnabled: Boolean
+ get() = vecdbCheckbox.isSelected
+ set(newVal) {
+ vecdbCheckbox.isSelected = newVal
+ }
+
var xDebugLSPPort: Int?
get() =
try {
diff --git a/src/main/kotlin/com/smallcloud/refactai/settings/AppSettingsConfigurable.kt b/src/main/kotlin/com/smallcloud/refactai/settings/AppSettingsConfigurable.kt
index b6baec61..0e34f7ef 100644
--- a/src/main/kotlin/com/smallcloud/refactai/settings/AppSettingsConfigurable.kt
+++ b/src/main/kotlin/com/smallcloud/refactai/settings/AppSettingsConfigurable.kt
@@ -70,6 +70,9 @@ class AppSettingsConfigurable : Configurable {
modified = modified || mySettingsComponent!!.stagingVersion != InferenceGlobalContext.stagingVersion
modified = modified || mySettingsComponent!!.defaultSystemPrompt != AppSettingsState.instance.defaultSystemPrompt
+ modified = modified || mySettingsComponent!!.astIsEnabled != InferenceGlobalContext.astIsEnabled
+ modified = modified || mySettingsComponent!!.vecdbIsEnabled != InferenceGlobalContext.vecdbIsEnabled
+
return modified
}
@@ -83,6 +86,8 @@ class AppSettingsConfigurable : Configurable {
InferenceGlobalContext.stagingVersion = mySettingsComponent!!.stagingVersion
LSPProcessHolder.xDebugLSPPort = mySettingsComponent!!.xDebugLSPPort
AppSettingsState.instance.defaultSystemPrompt = mySettingsComponent!!.defaultSystemPrompt
+ InferenceGlobalContext.astIsEnabled = mySettingsComponent!!.astIsEnabled
+ InferenceGlobalContext.vecdbIsEnabled = mySettingsComponent!!.vecdbIsEnabled
}
override fun reset() {
@@ -93,6 +98,8 @@ class AppSettingsConfigurable : Configurable {
mySettingsComponent!!.stagingVersion = InferenceGlobalContext.stagingVersion
mySettingsComponent!!.xDebugLSPPort = LSPProcessHolder.xDebugLSPPort
mySettingsComponent!!.defaultSystemPrompt = AppSettingsState.instance.defaultSystemPrompt
+ mySettingsComponent!!.astIsEnabled = InferenceGlobalContext.astIsEnabled
+ mySettingsComponent!!.vecdbIsEnabled = InferenceGlobalContext.vecdbIsEnabled
}
override fun disposeUIResources() {
diff --git a/src/main/kotlin/com/smallcloud/refactai/settings/AppSettingsState.kt b/src/main/kotlin/com/smallcloud/refactai/settings/AppSettingsState.kt
index 3e8e3008..61023bf6 100644
--- a/src/main/kotlin/com/smallcloud/refactai/settings/AppSettingsState.kt
+++ b/src/main/kotlin/com/smallcloud/refactai/settings/AppSettingsState.kt
@@ -46,6 +46,8 @@ class AppSettingsState : PersistentStateComponent {
var stagingVersion: String = ""
var rateUsNotification: Boolean = false
var defaultSystemPrompt: String = ""
+ var astIsEnabled: Boolean = false
+ var vecdbIsEnabled: Boolean = false
@Transient
private val messageBus: MessageBus = ApplicationManager.getApplication().messageBus
@@ -96,6 +98,13 @@ class AppSettingsState : PersistentStateComponent {
override fun developerModeEnabledChanged(newValue: Boolean) {
instance.developerModeEnabled = newValue
}
+
+ override fun astFlagChanged(newValue: Boolean) {
+ instance.astIsEnabled = newValue
+ }
+ override fun vecdbFlagChanged(newValue: Boolean) {
+ instance.vecdbIsEnabled = newValue
+ }
})
messageBus
.connect(PluginState.instance)
diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml
index 368984a6..7494114a 100644
--- a/src/main/resources/META-INF/plugin.xml
+++ b/src/main/resources/META-INF/plugin.xml
@@ -107,14 +107,11 @@ integrated into a single package that follows your privacy settings.
-
+
-
-
+
description="Refact AI Accept Inline Completion">
-
-
-
-
-
-