11package ai.devchat.plugin.completion.editor
22
33import ai.devchat.storage.CONFIG
4- import ai.devchat.storage.CompletionTriggerMode
5- import ai.devchat.storage.DevChatState
6- import com.intellij.openapi.application.invokeLater
4+ import com.intellij.openapi.application.ApplicationManager
5+ import com.intellij.openapi.application.ModalityState
76import com.intellij.openapi.components.service
87import com.intellij.openapi.diagnostic.Logger
98import com.intellij.openapi.editor.Editor
109import com.intellij.openapi.editor.event.*
1110import com.intellij.openapi.fileEditor.FileEditorManager
1211import com.intellij.openapi.fileEditor.FileEditorManagerEvent
1312import com.intellij.openapi.fileEditor.FileEditorManagerListener
13+ import kotlinx.coroutines.*
14+ import java.util.concurrent.atomic.AtomicLong
15+
16+ class Debouncer (private val debounceDelay : Long , private val scope : CoroutineScope ) {
17+ private val lastTimestamp = AtomicLong (0 )
18+
19+ fun debounce (action : suspend () -> Unit ): Job = scope.launch {
20+ val timestamp = System .currentTimeMillis()
21+ lastTimestamp.set(timestamp)
22+ delay(debounceDelay)
23+ if (timestamp == lastTimestamp.get()) {
24+ action()
25+ }
26+ }
27+ }
1428
1529class EditorListener : EditorFactoryListener {
16- private val logger = Logger .getInstance(EditorListener ::class .java)
17- private val disposers = mutableMapOf<Editor , () - > Unit > ()
30+ private val logger = Logger .getInstance(EditorListener ::class .java)
31+ private val disposers = mutableMapOf<Editor , () - > Unit > ()
32+ private val debouncer = Debouncer (300 , CoroutineScope (Dispatchers .Default ))
1833
19- override fun editorCreated (event : EditorFactoryEvent ) {
20- val editor = event.editor
21- val editorManager = editor.project?.let { FileEditorManager .getInstance(it) } ? : return
22- val completionProvider = service<CompletionProvider >()
23- val inlineCompletionService = service<InlineCompletionService >()
24- logger.debug(" EditorFactoryListener: editorCreated $event " )
34+ override fun editorCreated (event : EditorFactoryEvent ) {
35+ val editor = event.editor
36+ val editorManager = editor.project?.let { FileEditorManager .getInstance(it) } ? : return
37+ val completionProvider = service<CompletionProvider >()
38+ val inlineCompletionService = service<InlineCompletionService >()
39+ logger.debug(" EditorFactoryListener: editorCreated $event " )
2540
26- editor.caretModel.addCaretListener(object : CaretListener {
27- override fun caretPositionChanged (event : CaretEvent ) {
28- logger.debug(" CaretListener: caretPositionChanged $event " )
29- if (editorManager.selectedTextEditor == editor) {
30- inlineCompletionService.shownInlineCompletion?.let {
31- if (it.ongoing) return
32- }
33- completionProvider.ongoingCompletion.value.let {
34- if (it != null && it.editor == editor && it.offset == editor.caretModel.primaryCaret.offset) {
35- // keep ongoing completion
36- logger.debug(" Keep ongoing completion." )
37- } else {
38- completionProvider.clear()
41+ editor.caretModel.addCaretListener(object : CaretListener {
42+ override fun caretPositionChanged (event : CaretEvent ) {
43+ logger.debug(" CaretListener: caretPositionChanged $event " )
44+ if (editorManager.selectedTextEditor == editor) {
45+ inlineCompletionService.shownInlineCompletion?.let {
46+ if (it.ongoing) return
47+ }
48+ completionProvider.ongoingCompletion.value.let {
49+ if (it != null && it.editor == editor && it.offset == editor.caretModel.primaryCaret.offset) {
50+ logger.debug(" Keep ongoing completion." )
51+ } else {
52+ completionProvider.clear()
53+ }
54+ }
55+ }
56+ }
57+ })
58+
59+ val documentListener = object : DocumentListener {
60+ override fun documentChanged (event : DocumentEvent ) {
61+ logger.info(" DocumentListener: documentChanged $event " )
62+
63+ debouncer.debounce {
64+ ApplicationManager .getApplication().invokeLater({
65+ processDocumentChange(event, editor, editorManager, completionProvider, inlineCompletionService)
66+ }, ModalityState .defaultModalityState())
67+ }
3968 }
40- }
4169 }
42- }
43- })
70+ editor.document.addDocumentListener(documentListener)
4471
45- val documentListener = object : DocumentListener {
46- override fun documentChanged (event : DocumentEvent ) {
47- logger.debug(" DocumentListener: documentChanged $event " )
48- if (editorManager.selectedTextEditor == editor) {
49- val enabled = CONFIG [" complete_enable" ] as ? Boolean ? : false
50- if (enabled) {
51- inlineCompletionService.shownInlineCompletion?.let {
52- if (it.ongoing) {
53- logger.debug(" DocumentListener: documentChanged $event , but ongoing inline completion." )
54- return
55- }
72+ val messagesConnection = editor.project?.messageBus?.connect()
73+ messagesConnection?.subscribe(
74+ FileEditorManagerListener .FILE_EDITOR_MANAGER ,
75+ object : FileEditorManagerListener {
76+ override fun selectionChanged (event : FileEditorManagerEvent ) {
77+ logger.debug(" FileEditorManagerListener: selectionChanged." )
78+ completionProvider.clear()
79+ }
5680 }
81+ )
5782
83+ disposers[editor] = {
84+ editor.document.removeDocumentListener(documentListener)
85+ messagesConnection?.disconnect()
86+ }
87+ }
88+
89+ override fun editorReleased (event : EditorFactoryEvent ) {
90+ logger.debug(" EditorFactoryListener: editorReleased $event " )
91+ disposers[event.editor]?.invoke()
92+ disposers.remove(event.editor)
93+ }
5894
95+ private fun processDocumentChange (
96+ event : DocumentEvent ,
97+ editor : Editor ,
98+ editorManager : FileEditorManager ,
99+ completionProvider : CompletionProvider ,
100+ inlineCompletionService : InlineCompletionService
101+ ) {
102+ logger.info(" trigger processDocumentChange" )
103+ if (editorManager.selectedTextEditor == editor) {
104+ val enabled = CONFIG [" complete_enable" ] as ? Boolean ? : false
105+ if (enabled) {
106+ inlineCompletionService.shownInlineCompletion?.let {
107+ if (it.ongoing) {
108+ logger.info(" Ongoing inline completion, skipping." )
109+ return
110+ }
111+ }
59112
60- completionProvider.ongoingCompletion.value.let {
61- if (it != null && it.editor == editor && it.offset == editor.caretModel.primaryCaret.offset) {
62- // keep ongoing completion
63- logger.debug(" Keep ongoing completion." )
64- } else {
65- logger.debug(" DocumentListener: documentChanged $event , need to completion." )
66- invokeLater {
67- completionProvider.provideCompletion(editor, editor.caretModel.primaryCaret.offset)
113+ completionProvider.ongoingCompletion.value?.let {
114+ if (it.editor == editor && it.offset == editor.caretModel.primaryCaret.offset) {
115+ logger.info(" Keeping ongoing completion." )
116+ } else {
117+ logger.info(" Cancelling previous completion and providing new one." )
118+ completionProvider.clear()
119+ completionProvider.provideCompletion(editor, editor.caretModel.primaryCaret.offset)
120+ }
121+ } ? : run {
122+ logger.info(" Providing new completion." )
123+ completionProvider.provideCompletion(editor, editor.caretModel.primaryCaret.offset)
68124 }
69- }
125+ } else {
126+ logger.debug(" Completion is disabled." )
70127 }
71- } else {
72- logger.debug(" DocumentListener: documentChanged $event , but completion is disabled." )
73- }
74128 } else {
75- logger.debug(" DocumentListener: documentChanged $event , but not selected editor." )
76- }
77- }
78- }
79- editor.document.addDocumentListener(documentListener)
80-
81- val messagesConnection = editor.project?.messageBus?.connect()
82- messagesConnection?.subscribe(
83- FileEditorManagerListener .FILE_EDITOR_MANAGER ,
84- object : FileEditorManagerListener {
85- override fun selectionChanged (event : FileEditorManagerEvent ) {
86- logger.debug(" FileEditorManagerListener: selectionChanged." )
87- completionProvider.clear()
129+ logger.debug(" Not the selected editor." )
88130 }
89- }
90- )
91-
92- disposers[editor] = {
93- editor.document.removeDocumentListener(documentListener)
94- messagesConnection?.disconnect()
95131 }
96- }
97-
98- override fun editorReleased (event : EditorFactoryEvent ) {
99- logger.debug(" EditorFactoryListener: editorReleased $event " )
100- disposers[event.editor]?.invoke()
101- disposers.remove(event.editor)
102- }
103132}
0 commit comments