@@ -2,6 +2,7 @@ package com.github.continuedev.continueintellijextension.autocomplete
2
2
3
3
import com.github.continuedev.continueintellijextension.services.ContinueExtensionSettings
4
4
import com.github.continuedev.continueintellijextension.services.ContinuePluginService
5
+ import com.github.continuedev.continueintellijextension.utils.toUriOrNull
5
6
import com.github.continuedev.continueintellijextension.utils.uuid
6
7
import com.intellij.injected.editor.VirtualFileWindow
7
8
import com.intellij.openapi.application.*
@@ -10,8 +11,10 @@ import com.intellij.openapi.components.ServiceManager
10
11
import com.intellij.openapi.components.service
11
12
import com.intellij.openapi.editor.Editor
12
13
import com.intellij.openapi.editor.InlayProperties
14
+ import com.intellij.openapi.editor.impl.EditorImpl
13
15
import com.intellij.openapi.fileEditor.FileDocumentManager
14
16
import com.intellij.openapi.project.Project
17
+ import com.intellij.openapi.util.TextRange
15
18
import com.intellij.openapi.wm.WindowManager
16
19
import com.intellij.psi.PsiDocumentManager
17
20
import com.intellij.psi.PsiElement
@@ -32,9 +35,24 @@ fun PsiElement.isInjectedText(): Boolean {
32
35
return false
33
36
}
34
37
38
+ fun Editor.addInlayElement (
39
+ lines : List <String >,
40
+ offset : Int ,
41
+ properties : InlayProperties
42
+ ) {
43
+ if (this is EditorImpl ) {
44
+ if (lines[0 ].isNotEmpty()) {
45
+ inlayModel.addInlineElement(offset, properties, ContinueInlayRenderer (listOf (lines[0 ])))
46
+ }
47
+ if (lines.size > 1 ) {
48
+ inlayModel.addBlockElement(offset, properties, ContinueInlayRenderer (lines.drop(1 )))
49
+ }
50
+ }
51
+ }
52
+
35
53
@Service(Service .Level .PROJECT )
36
54
class AutocompleteService (private val project : Project ) {
37
- var pendingCompletion: PendingCompletion ? = null ;
55
+ var pendingCompletion: PendingCompletion ? = null
38
56
private val autocompleteLookupListener = project.service<AutocompleteLookupListener >()
39
57
private var widget: AutocompleteSpinnerWidget ? = null
40
58
@@ -66,23 +84,23 @@ class AutocompleteService(private val project: Project) {
66
84
67
85
// Request a completion from the core
68
86
val virtualFile = FileDocumentManager .getInstance().getFile(editor.document)
87
+
88
+ val uri = virtualFile?.toUriOrNull() ? : return
89
+
90
+ val line = editor.caretModel.primaryCaret.logicalPosition.line
69
91
val column = editor.caretModel.primaryCaret.logicalPosition.column
70
92
val input = mapOf (
71
93
" completionId" to completionId,
72
- " filepath" to virtualFile?.url ,
94
+ " filepath" to uri ,
73
95
" pos" to mapOf (
74
- " line" to editor.caretModel.primaryCaret.logicalPosition. line,
96
+ " line" to line,
75
97
" character" to column
76
98
),
77
99
" recentlyEditedFiles" to emptyList<String >(),
78
100
" recentlyEditedRanges" to emptyList<String >(),
79
101
" clipboardText" to " "
80
102
)
81
103
82
- val lineStart = editor.document.getLineStartOffset(editor.caretModel.primaryCaret.logicalPosition.line)
83
- val lineEnd = editor.document.getLineEndOffset(editor.caretModel.primaryCaret.logicalPosition.line)
84
- val lineLength = lineEnd - lineStart
85
-
86
104
project.service<ContinuePluginService >().coreMessenger?.request(
87
105
" autocomplete/complete" ,
88
106
input,
@@ -95,9 +113,8 @@ class AutocompleteService(private val project: Project) {
95
113
val completion = completions[0 ].toString()
96
114
val finalTextToInsert = deduplicateCompletion(editor, offset, completion)
97
115
98
- if (shouldRenderCompletion(finalTextToInsert, column, lineLength , editor)) {
116
+ if (shouldRenderCompletion(finalTextToInsert, offset, line , editor)) {
99
117
renderCompletion(editor, offset, finalTextToInsert)
100
- pendingCompletion = PendingCompletion (editor, offset, completionId, finalTextToInsert)
101
118
// Hide auto-popup
102
119
// AutoPopupController.getInstance(project).cancelAllRequests()
103
120
}
@@ -106,13 +123,19 @@ class AutocompleteService(private val project: Project) {
106
123
)
107
124
}
108
125
109
- private fun shouldRenderCompletion (completion : String , column : Int , lineLength : Int , editor : Editor ): Boolean {
110
- if (completion.isEmpty()) {
126
+ private fun shouldRenderCompletion (completion : String , offset : Int , line : Int , editor : Editor ): Boolean {
127
+ if (completion.isEmpty() || runReadAction { offset != editor.caretModel.offset } ) {
111
128
return false
112
129
}
113
130
131
+ if (completion.lines().size == 1 ) {
132
+ return true
133
+ }
134
+
135
+ val endOffset = editor.document.getLineEndOffset(line)
136
+
114
137
// Do not render if completion is multi-line and caret is in middle of line
115
- return ! (completion.lines().size > 1 && column < lineLength )
138
+ return offset <= endOffset && editor.document.getText( TextRange (offset, endOffset)).isBlank( )
116
139
}
117
140
118
141
private fun deduplicateCompletion (editor : Editor , offset : Int , completion : String ): String {
@@ -126,9 +149,9 @@ class AutocompleteService(private val project: Project) {
126
149
127
150
val N = 10
128
151
var textAfterCursor = if (caretOffset + N <= document.textLength) {
129
- document.getText(com.intellij.openapi.util. TextRange (caretOffset, caretOffset + N ))
152
+ document.getText(TextRange (caretOffset, caretOffset + N ))
130
153
} else {
131
- document.getText(com.intellij.openapi.util. TextRange (caretOffset, document.textLength))
154
+ document.getText(TextRange (caretOffset, document.textLength))
132
155
}
133
156
134
157
// Avoid truncating the completion text when the text after the cursor is blank
@@ -171,19 +194,9 @@ class AutocompleteService(private val project: Project) {
171
194
properties.relatesToPrecedingText(true )
172
195
properties.disableSoftWrapping(true )
173
196
174
- if (completion.lines().size > 1 ) {
175
- editor.inlayModel.addBlockElement(
176
- offset,
177
- properties,
178
- ContinueMultilineCustomElementRenderer (editor, completion)
179
- )
180
- } else {
181
- editor.inlayModel.addInlineElement(
182
- offset,
183
- properties,
184
- ContinueCustomElementRenderer (editor, completion)
185
- )
186
- }
197
+ val lines = completion.lines()
198
+ pendingCompletion = pendingCompletion?.copy(text = lines.joinToString(" \n " ))
199
+ editor.addInlayElement(lines, offset, properties)
187
200
188
201
// val attributes = TextAttributes().apply {
189
202
// backgroundColor = JBColor.GREEN
@@ -211,7 +224,7 @@ class AutocompleteService(private val project: Project) {
211
224
({})
212
225
)
213
226
invokeLater {
214
- clearCompletions(editor)
227
+ clearCompletions(editor, completion )
215
228
}
216
229
}
217
230
@@ -274,23 +287,14 @@ class AutocompleteService(private val project: Project) {
274
287
project.service<ContinuePluginService >().coreMessenger?.request(" autocomplete/cancel" , null , null , ({}))
275
288
}
276
289
277
- fun clearCompletions (editor : Editor ) {
290
+ fun clearCompletions (editor : Editor , completion : PendingCompletion ? = pendingCompletion ) {
278
291
if (isInjectedFile(editor)) return
279
292
280
- if (pendingCompletion != null ) {
281
- cancelCompletion(pendingCompletion!! )
282
- pendingCompletion = null
283
- }
284
- editor.inlayModel.getInlineElementsInRange(0 , editor.document.textLength).forEach {
285
- if (it.renderer is ContinueCustomElementRenderer ) {
286
- it.dispose()
287
- }
288
- }
289
- editor.inlayModel.getBlockElementsInRange(0 , editor.document.textLength).forEach {
290
- if (it.renderer is ContinueMultilineCustomElementRenderer ) {
291
- it.dispose()
292
- }
293
+ if (completion != null ) {
294
+ cancelCompletion(completion)
295
+ if (completion.completionId == pendingCompletion?.completionId) pendingCompletion = null
293
296
}
297
+ disposeInlayRenderer(editor)
294
298
}
295
299
296
300
private fun isInjectedFile (editor : Editor ): Boolean {
@@ -306,13 +310,17 @@ class AutocompleteService(private val project: Project) {
306
310
fun hideCompletions (editor : Editor ) {
307
311
if (isInjectedFile(editor)) return
308
312
313
+ disposeInlayRenderer(editor)
314
+ }
315
+
316
+ private fun disposeInlayRenderer (editor : Editor ) {
309
317
editor.inlayModel.getInlineElementsInRange(0 , editor.document.textLength).forEach {
310
- if (it.renderer is ContinueCustomElementRenderer ) {
318
+ if (it.renderer is ContinueInlayRenderer ) {
311
319
it.dispose()
312
320
}
313
321
}
314
322
editor.inlayModel.getBlockElementsInRange(0 , editor.document.textLength).forEach {
315
- if (it.renderer is ContinueMultilineCustomElementRenderer ) {
323
+ if (it.renderer is ContinueInlayRenderer ) {
316
324
it.dispose()
317
325
}
318
326
}
0 commit comments