1
- package org.kotlinlsp.actions.completions
1
+ package org.kotlinlsp.actions.autocomplete
2
2
3
3
import com.intellij.openapi.util.text.StringUtil
4
4
import com.intellij.psi.util.parentOfType
@@ -25,26 +25,84 @@ import org.jetbrains.kotlin.psi.KtLoopExpression
25
25
import org.jetbrains.kotlin.psi.KtParameter
26
26
import org.jetbrains.kotlin.psi.KtProperty
27
27
import org.jetbrains.kotlin.types.Variance
28
- import org.kotlinlsp.actions.completionKind
29
28
import org.kotlinlsp.index.Index
30
29
import org.kotlinlsp.index.db.Declaration
31
30
import org.kotlinlsp.index.queries.getCompletions
32
31
33
32
private val newlines = arrayOf(" " , " \n " , " \n\n " )
34
- @OptIn( KaExperimentalApi :: class )
33
+
35
34
fun autoCompletionGeneric (ktFile : KtFile , offset : Int , index : Index , completingElement : KtElement , prefix : String ): List <CompletionItem > {
36
- val localVariableCompletions: List <CompletionItem > = analyze(completingElement) {
37
- ktFile.scopeContext(completingElement).scopes.flatMap {
38
- if (it.kind !is KaScopeKind .LocalScope ) return @flatMap emptyList()
35
+ val localVariableCompletions = fetchLocalCompletions(ktFile, offset, completingElement, prefix)
36
+
37
+ val existingImports = ktFile.importList?.children?.filterIsInstance<KtImportDirective >() ? : emptyList()
38
+ val (importInsertionOffset, newlineCount) = if (existingImports.isEmpty()) {
39
+ ktFile.packageDirective?.textRange?.let { it.endOffset to 2 } ? : (ktFile.textRange.startOffset to 0 )
40
+ } else {
41
+ existingImports.last().textRange.endOffset to 1
42
+ }
43
+ val importInsertionPosition =
44
+ StringUtil .offsetToLineColumn(ktFile.text, importInsertionOffset).let { Position (it.line, it.column) }
45
+
46
+ val completions = index
47
+ .getCompletions(prefix, " " , " " ) // TODO: ThisRef
48
+ .map { decl ->
49
+ val additionalEdits = mutableListOf<TextEdit >()
50
+
51
+ if (decl is Declaration .Class ) {
52
+ val exists = existingImports.any {
53
+ it.importedFqName?.asString() == decl.fqName
54
+ }
55
+ if (! exists) {
56
+ val importText = " import ${decl.fqName} "
57
+ val edit = TextEdit ().apply {
58
+ range = Range (importInsertionPosition, importInsertionPosition)
59
+ newText = " ${newlines[newlineCount]}$importText "
60
+ }
61
+ additionalEdits.add(edit)
62
+ }
63
+ }
64
+
65
+ val (inserted, insertionType) = decl.insertInfo()
66
+
67
+ CompletionItem ().apply {
68
+ label = decl.name
69
+ labelDetails = decl.details()
70
+ kind = decl.completionKind()
71
+ insertText = inserted
72
+ insertTextFormat = insertionType
73
+ additionalTextEdits = additionalEdits
74
+ }
75
+ }
76
+
77
+ return localVariableCompletions + completions
78
+ }
79
+
80
+ @OptIn(KaExperimentalApi ::class )
81
+ private fun fetchLocalCompletions (
82
+ ktFile : KtFile ,
83
+ offset : Int ,
84
+ completingElement : KtElement ,
85
+ prefix : String
86
+ ): List <CompletionItem > = analyze(completingElement) {
87
+ ktFile
88
+ .scopeContext(completingElement)
89
+ .scopes
90
+ .filter { it.kind is KaScopeKind .LocalScope }
91
+ .flatMap {
39
92
it.scope.declarations.mapNotNull { decl ->
40
93
if (! decl.name.toString().startsWith(prefix)) return @mapNotNull null
41
94
val psi = decl.psi ? : return @mapNotNull null
95
+
42
96
// TODO: This is a hack to get the correct offset for function literals, can analysis tell us if a declaration is accessible?
43
97
val declOffset = if (psi is KtFunctionLiteral ) psi.textRange.startOffset else psi.textRange.endOffset
44
98
if (declOffset >= offset) return @mapNotNull null
45
99
46
100
val detail = when (decl) {
47
- is KaVariableSymbol -> decl.returnType.render(KaTypeRendererForSource .WITH_SHORT_NAMES , Variance .INVARIANT )
101
+ is KaVariableSymbol -> decl.returnType.render(
102
+ KaTypeRendererForSource .WITH_SHORT_NAMES ,
103
+ Variance .INVARIANT
104
+ )
105
+
48
106
else -> " Missing ${decl.javaClass.simpleName} "
49
107
}
50
108
@@ -56,6 +114,7 @@ fun autoCompletionGeneric(ktFile: KtFile, offset: Int, index: Index, completingE
56
114
loop.text.replace(loop.body!! .text, " " )
57
115
} else psi.text
58
116
}
117
+
59
118
is KtFunctionLiteral -> decl.name // TODO: Show the function call containing the lambda?
60
119
else -> " TODO: Preview for ${psi.javaClass.simpleName} "
61
120
}
@@ -75,66 +134,4 @@ fun autoCompletionGeneric(ktFile: KtFile, offset: Int, index: Index, completingE
75
134
}
76
135
}.toList()
77
136
}
78
- }
79
-
80
- val existingImports = ktFile.importList?.children?.filterIsInstance<KtImportDirective >() ? : emptyList()
81
- val (importInsertionOffset, newlineCount) = if (existingImports.isEmpty()) {
82
- ktFile.packageDirective?.textRange?.let { it.endOffset to 2 } ? : (ktFile.textRange.startOffset to 0 )
83
- } else {
84
- existingImports.last().textRange.endOffset to 1
85
- }
86
- val importInsertionPosition = StringUtil .offsetToLineColumn(ktFile.text, importInsertionOffset).let { Position (it.line, it.column) }
87
-
88
- val completions = index.getCompletions(prefix, " " , " " ) // TODO: ThisRef
89
- .mapNotNull { decl ->
90
- val additionalEdits = mutableListOf<TextEdit >()
91
-
92
- if (decl is Declaration .Class ) {
93
- val exists = existingImports.any {
94
- it.importedFqName?.asString() == decl.fqName
95
- }
96
- if (! exists) {
97
- val importText = " import ${decl.fqName} "
98
- val edit = TextEdit ().apply {
99
- range = Range (importInsertionPosition, importInsertionPosition)
100
- newText = " ${newlines[newlineCount]}$importText "
101
- }
102
- additionalEdits.add(edit)
103
- }
104
- }
105
-
106
- val detail = when (decl) {
107
- is Declaration .Class -> CompletionItemLabelDetails ().apply {
108
- detail = " (${decl.fqName} )"
109
- }
110
- is Declaration .EnumEntry -> CompletionItemLabelDetails ().apply {
111
- detail = " : ${decl.enumFqName} "
112
- }
113
- is Declaration .Function -> CompletionItemLabelDetails ().apply {
114
- detail = " (${decl.parameters.joinToString(" , " ) { param -> " ${param.name} : ${param.type} " }} ): ${decl.returnType} (${decl.fqName} )"
115
- }
116
- is Declaration .Field -> CompletionItemLabelDetails ().apply {
117
- detail = " : ${decl.type} (${decl.fqName} )"
118
- }
119
- }
120
-
121
- val (inserted, insertionType) = when (decl) {
122
- is Declaration .Class -> decl.name to InsertTextFormat .PlainText
123
- is Declaration .EnumEntry -> " ${decl.enumFqName.substringAfterLast(' .' )} .${decl.name} " to InsertTextFormat .PlainText
124
- is Declaration .Function -> " ${decl.name} (${
125
- decl.parameters.mapIndexed { index, param -> " \$ {${index+ 1 } :${param.name} }" }.joinToString(" , " )
126
- } )" to InsertTextFormat .Snippet
127
- is Declaration .Field -> decl.name to InsertTextFormat .PlainText
128
- }
129
-
130
- CompletionItem ().apply {
131
- label = decl.name
132
- labelDetails = detail
133
- kind = decl.completionKind()
134
- insertText = inserted
135
- insertTextFormat = insertionType
136
- additionalTextEdits = additionalEdits
137
- }
138
- }
139
- return localVariableCompletions + completions
140
137
}
0 commit comments