1
1
package dev.koding.copilot.completion
2
2
3
- import com.intellij.codeInsight.completion.*
3
+ import com.intellij.codeInsight.completion.CompletionContributor
4
+ import com.intellij.codeInsight.completion.CompletionParameters
5
+ import com.intellij.codeInsight.completion.CompletionResultSet
6
+ import com.intellij.codeInsight.completion.CompletionSorter
4
7
import com.intellij.codeInsight.lookup.LookupElementBuilder
5
- import com.intellij.notification.Notification
6
8
import com.intellij.notification.NotificationType
7
9
import com.intellij.openapi.progress.ProcessCanceledException
8
10
import com.intellij.openapi.progress.ProgressManager
9
11
import com.intellij.openapi.util.TextRange
12
+ import dev.koding.copilot.auth.handleLogin
10
13
import dev.koding.copilot.completion.api.CompletionRequest
11
14
import dev.koding.copilot.completion.api.CompletionResponse
12
15
import dev.koding.copilot.config.settings
13
16
import dev.koding.copilot.copilotIcon
17
+ import dev.koding.copilot.util.Notifications
14
18
import io.ktor.client.features.*
15
19
import kotlinx.coroutines.GlobalScope
16
20
import kotlinx.coroutines.launch
@@ -19,95 +23,79 @@ import kotlin.math.max
19
23
20
24
class CopilotCompletionContributor : CompletionContributor () {
21
25
22
- private var notified = false
23
-
24
26
override fun fillCompletionVariants (parameters : CompletionParameters , result : CompletionResultSet ) {
25
27
if (parameters.isAutoPopup) return
26
28
27
- if (settings.token == null ) {
28
- if (notified) return
29
- @Suppress(" DialogTitleCapitalization" )
30
- Notification (
31
- " Error Report" ,
32
- " GitHub Copilot" ,
33
- " You have not set a token for GitHub Copilot." ,
34
- NotificationType .ERROR
35
- ).notify(parameters.editor.project)
36
- return run { notified = true }
37
- }
29
+ if (settings.token?.isBlank() == true ) return Notifications .send(
30
+ " You have not set a token for GitHub Copilot." ,
31
+ type = NotificationType .ERROR ,
32
+ once = true ,
33
+ Notifications .NotificationAction (" Login" ) { handleLogin() })
38
34
35
+ val (prefix, suffix) = parameters.prefixSuffix
39
36
val prompt = """
40
37
// Language: ${parameters.originalFile.language.displayName}
41
38
// Path: ${parameters.originalFile.name}
42
39
${parameters.prompt}
43
40
""" .trimIndent()
44
41
45
- val (prefix, suffix) = parameters.prefixSuffix
42
+ val matcher = result.prefixMatcher
43
+ val set = result
44
+ .withPrefixMatcher(CopilotPrefixMatcher (matcher.cloneWithPrefix(matcher.prefix)))
45
+ .withRelevanceSorter(CompletionSorter .defaultSorter(parameters, matcher).weigh(CopilotWeigher ()))
46
46
47
+ // TODO: Fix freeze
47
48
var response: CompletionResponse ? = null
49
+ var errored = false
50
+
48
51
val job = GlobalScope .launch {
49
52
try {
50
53
response = CompletionRequest (prompt).send(settings.token!! )
51
54
} catch (e: ClientRequestException ) {
52
- if (! notified) {
53
- @Suppress(" DialogTitleCapitalization" )
54
- Notification (
55
- " Error Report" ,
56
- " GitHub Copilot" ,
57
- " Failed to fetch response. Is your copilot token valid?" ,
58
- NotificationType .ERROR
59
- ).notify(parameters.editor.project)
60
- notified = true
61
- }
62
-
63
- return @launch result.stopHere()
55
+ errored = true
56
+ return @launch Notifications .send(
57
+ " Failed to fetch response. Is your copilot token valid?" ,
58
+ type = NotificationType .ERROR ,
59
+ once = true ,
60
+ Notifications .NotificationAction (" Login" ) { handleLogin() })
64
61
}
65
62
}
66
63
67
- if (result.isStopped) return
68
64
while (response == null ) {
65
+ if (errored) return
69
66
try {
70
67
ProgressManager .getInstance().progressIndicator.checkCanceled()
71
68
Thread .sleep(10 )
72
69
} catch (e: ProcessCanceledException ) {
73
70
job.cancel()
74
- return result.stopHere()
71
+ return
75
72
}
76
73
}
77
74
78
75
val choices = response!! .choices.filter { it.text.isNotBlank() }
79
76
if (choices.isEmpty()) return
80
77
81
- val originalMatcher = result.prefixMatcher
82
- val set =
83
- result.withPrefixMatcher(CopilotPrefixMatcher (originalMatcher.cloneWithPrefix(originalMatcher.prefix)))
84
- .withRelevanceSorter(
85
- CompletionSorter .defaultSorter(parameters, originalMatcher)
86
- .weigh(CopilotWeigher ())
87
- )
88
-
89
78
set.restartCompletionOnAnyPrefixChange()
90
79
set.addAllElements(choices.map { choice ->
91
80
val completion = choice.text.removePrefix(prefix.trim()).removeSuffix(suffix.trim())
92
81
val insert = " $prefix${completion.trim()} \n "
93
82
94
- PrioritizedLookupElement .withPriority(
95
- LookupElementBuilder .create(choice, " " )
96
- .withInsertHandler { context, _ ->
97
- val caret = context.editor.caretModel
98
- val startOffset = caret.visualLineStart
99
- val endOffset = caret.visualLineEnd
100
-
101
- context.document.deleteString(startOffset, endOffset)
102
- context.document.insertString(startOffset, insert)
103
- caret.moveToOffset(startOffset + insert.length - 1 )
104
- }
105
- .withPresentableText(prefix.split(" ." ).last())
106
- .withTailText(completion, true )
107
- .withTypeText(" GitHub Copilot" )
108
- .withIcon(copilotIcon)
109
- .bold(), Double .MAX_VALUE
110
- )
83
+ LookupElementBuilder .create(choice, " " )
84
+ .withInsertHandler { context, _ ->
85
+ val caret = context.editor.caretModel
86
+ val startOffset = caret.visualLineStart
87
+ val endOffset = caret.visualLineEnd
88
+
89
+ context.document.deleteString(startOffset, endOffset)
90
+ context.document.insertString(startOffset, insert)
91
+ caret.moveToOffset(startOffset + insert.length - 1 )
92
+ }
93
+ .withPresentableText(prefix.split(" ." ).last().trim())
94
+ .withTailText(completion, true )
95
+ .withCaseSensitivity(false )
96
+ .withTypeText(" GitHub Copilot" )
97
+ .withIcon(copilotIcon)
98
+ .bold()
111
99
})
112
100
}
113
101
0 commit comments