Skip to content

Commit

Permalink
feat(inlay): use command listener
Browse files Browse the repository at this point in the history
  • Loading branch information
phodal committed Jul 22, 2023
1 parent e5c42bd commit fa46000
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 5 deletions.
110 changes: 110 additions & 0 deletions src/main/kotlin/cc/unitmesh/devti/editor/LLMCommandListener.kt
@@ -1,8 +1,118 @@
package cc.unitmesh.devti.editor

import com.intellij.openapi.command.CommandEvent
import com.intellij.openapi.command.CommandListener
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.VisualPosition
import com.intellij.openapi.editor.ex.DocumentEx
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicReference

private class UndoTransparentActionState(val editor: Editor, val modificationStamp: Long)
private class CommandEditorState(val modificationStamp: Long, val visualPosition: VisualPosition)

class LLMCommandListener(private val project: Project) : CommandListener {
private val activeCommands = AtomicInteger()
private val startedWithEditor = AtomicBoolean(false)
private val undoTransparentActionStamp = AtomicReference<UndoTransparentActionState?>()

override fun commandStarted(event: CommandEvent) {
if (activeCommands.getAndIncrement() > 0) {
LOG.debug("Skipping nested commandStarted. Event: $event")
return
}
val editor = getSelectedEditorSafely(project)
if (editor != null) {
startedWithEditor.set(true)
COMMAND_STATE_KEY[editor] =
createCommandState(editor)
} else {
startedWithEditor.set(false)
}
}


override fun commandFinished(event: CommandEvent) {
if (activeCommands.decrementAndGet() > 0) {
LOG.debug("Skipping nested commandFinished. Event: $event")
return
}

if (!startedWithEditor.get()) {
return
}
val editor = getSelectedEditorSafely(project) ?: return
val editorManager = LLMInlayManager.getInstance()
if (!editorManager.isAvailable(editor)) {
return
}

val commandStartState = COMMAND_STATE_KEY[editor] ?: return
val commandEndState = createCommandState(editor)
if (isDocumentModification(commandStartState, commandEndState)) {
LOG.debug("command modified document: " + event.commandName)
editorManager.editorModified(editor)
} else if (isCaretPositionChange(commandStartState, commandEndState)) {
editorManager.disposeInlays(editor, InlayDisposeContext.CaretChange)
}
}

override fun undoTransparentActionStarted() {
val editor = getSelectedEditorSafely(project)
undoTransparentActionStamp.set(if (editor != null) createUndoTransparentState(editor) else null)
}

override fun undoTransparentActionFinished() {
val currentEditorStamp = undoTransparentActionStamp.get()
undoTransparentActionStamp.set(null)
val editor = getSelectedEditorSafely(project)
if (editor == null || currentEditorStamp == null || editor !== currentEditorStamp.editor) {
return
}
if (getDocumentStamp(editor.document) == currentEditorStamp.modificationStamp) {
return
}
val editorManager = LLMInlayManager.getInstance()
if (editorManager.isAvailable(editor)) {
editorManager.editorModified(editor)
}
}

private fun getSelectedEditorSafely(project: Project): Editor? {
return try {
FileEditorManager.getInstance(project)?.selectedTextEditor
} catch (e: Exception) {
null
}
}

companion object {
private val LOG = Logger.getInstance(LLMCommandListener::class.java)
private val COMMAND_STATE_KEY = Key.create<CommandEditorState>("llm.commandState")
}

private fun createCommandState(editor: Editor): CommandEditorState {
return CommandEditorState(getDocumentStamp(editor.document), editor.caretModel.visualPosition)
}
private fun createUndoTransparentState(editor: Editor): UndoTransparentActionState {
return UndoTransparentActionState(editor, getDocumentStamp(editor.document))
}

private fun getDocumentStamp(document: Document): Long {
return if (document is DocumentEx) document.modificationSequence.toLong() else document.modificationStamp
}

private fun isDocumentModification(first: CommandEditorState, second: CommandEditorState): Boolean {
return first.modificationStamp != second.modificationStamp
}

private fun isCaretPositionChange(first: CommandEditorState, second: CommandEditorState): Boolean {
return first.visualPosition != second.visualPosition
}
}
11 changes: 6 additions & 5 deletions src/main/kotlin/cc/unitmesh/devti/editor/LLMInlayManagerImpl.kt
Expand Up @@ -19,7 +19,8 @@ import java.util.function.Consumer

class LLMInlayManagerImpl : LLMInlayManager {
companion object {
private val LOG = Logger.getInstance(LLMInlayManagerImpl::class.java)
private val logger = Logger.getInstance(LLMInlayManagerImpl::class.java)

val KEY_DOCUMENT_SAVE_VETO = Key.create<Boolean>("llm.docSaveVeto")
private val KEY_PROCESSING =
KeyWithDefaultValue.create("llm.processing", java.lang.Boolean.valueOf(false)) as Key<Boolean>
Expand Down Expand Up @@ -85,24 +86,24 @@ class LLMInlayManagerImpl : LLMInlayManager {
override fun editorModified(editor: Editor, changeOffset: Int) {
disposeInlays(editor, InlayDisposeContext.Typing)

requestCompletions(editor, changeOffset, Consumer { completion ->
requestCompletions(editor, changeOffset) { completion ->
if (completion.isNotEmpty()) {
applyCompletion(editor.project!!, editor)
}
})
}
}

@RequiresBackgroundThread
private fun requestCompletions(editor: Editor, changeOffset: Int, onFirstCompletion: Consumer<String>?) {
println(editor.document.text)
logger.info("Requesting completions for offset $changeOffset")
}

override fun editorModified(editor: Editor) {
editorModified(editor, editor.caretModel.offset)
}

private fun disposeInlays(renderers: List<LLMInlayRenderer>) {
LOG.debug("Disposing inlays: " + renderers.size)
logger.debug("Disposing inlays: " + renderers.size)
for (renderer in renderers) {
val inlay = renderer.getInlay()
if (inlay != null) {
Expand Down

0 comments on commit fa46000

Please sign in to comment.