Skip to content

Commit 3093eeb

Browse files
committed
refactor: completions
1 parent d64bc64 commit 3093eeb

File tree

10 files changed

+85
-90
lines changed

10 files changed

+85
-90
lines changed

app/src/main/kotlin/org/kotlinlsp/actions/autocomplete/Autocomplete.kt

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,22 @@ import org.eclipse.lsp4j.CompletionItem
66
import org.jetbrains.kotlin.psi.*
77
import org.kotlinlsp.index.Index
88

9-
fun autocompleteAction(ktFile: KtFile, offset: Int, index: Index): List<CompletionItem> {
10-
val leaf = ktFile.leavesAroundOffset(offset).asSequence()
11-
.toList().last().first
9+
fun autocompleteAction(ktFile: KtFile, offset: Int, index: Index): Sequence<CompletionItem> {
10+
val leaf = ktFile.leavesAroundOffset(offset).last().first
1211

1312
val prefix = leaf.text.substring(0, offset - leaf.textRange.startOffset)
1413
val completingElement = leaf.parentOfType<KtElement>() ?: ktFile
1514

1615
if (completingElement is KtNameReferenceExpression) {
17-
if (completingElement.parent is KtDotQualifiedExpression) {
18-
return autoCompletionDotExpression(ktFile, offset, index, completingElement.parent as KtDotQualifiedExpression, prefix)
16+
return if (completingElement.parent is KtDotQualifiedExpression) {
17+
autoCompletionDotExpression(ktFile, offset, index, completingElement.parent as KtDotQualifiedExpression, prefix)
1918
} else {
20-
return autoCompletionGeneric(ktFile, offset, index, completingElement, prefix)
19+
autoCompletionGeneric(ktFile, offset, index, completingElement, prefix)
2120
}
2221
}
2322

2423
if (completingElement is KtValueArgumentList) {
25-
return emptyList() // TODO: function call arguments
24+
return emptySequence() // TODO: function call arguments
2625
}
2726

2827
return autoCompletionGeneric(ktFile, offset, index, completingElement, prefix)

app/src/main/kotlin/org/kotlinlsp/actions/autocomplete/DeclarationExtensions.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,13 @@ fun Declaration.insertInfo(): Pair<String, InsertTextFormat> = when (this) {
4444

4545
is Declaration.Field -> name to InsertTextFormat.PlainText
4646
}
47+
48+
fun Sequence<Declaration>.filterMatchesReceiver(receiver: String): Sequence<Declaration> =
49+
filter {
50+
when (it) {
51+
is Declaration.Function -> it.receiverFqName == receiver || it.receiverFqName.isEmpty()
52+
is Declaration.Class -> true
53+
is Declaration.EnumEntry -> true
54+
is Declaration.Field -> it.parentFqName == receiver || it.parentFqName.isEmpty()
55+
}
56+
}
Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
package org.kotlinlsp.actions.autocomplete
22

3-
import com.intellij.openapi.util.text.StringUtil
43
import org.eclipse.lsp4j.CompletionItem
5-
import org.eclipse.lsp4j.Position
6-
import org.eclipse.lsp4j.TextEdit
74
import org.jetbrains.kotlin.analysis.api.analyze
85
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
96
import org.jetbrains.kotlin.psi.KtFile
10-
import org.jetbrains.kotlin.psi.KtImportDirective
117
import org.kotlinlsp.index.Index
128
import org.kotlinlsp.index.queries.getCompletions
139

@@ -17,24 +13,16 @@ fun autoCompletionDotExpression(
1713
index: Index,
1814
completingElement: KtDotQualifiedExpression,
1915
prefix: String
20-
): List<CompletionItem> {
16+
): Sequence<CompletionItem> {
2117
val receiverType = analyze(completingElement) {
2218
completingElement.receiverExpression.expressionType.toString()
2319
}
24-
val existingImports = ktFile.importList?.children?.filterIsInstance<KtImportDirective>() ?: emptyList()
25-
val (importInsertionOffset, newlineCount) = if (existingImports.isEmpty()) {
26-
ktFile.packageDirective?.textRange?.let { it.endOffset to 2 } ?: (ktFile.textRange.startOffset to 0)
27-
} else {
28-
existingImports.last().textRange.endOffset to 1
29-
}
30-
val importInsertionPosition =
31-
StringUtil.offsetToLineColumn(ktFile.text, importInsertionOffset).let { Position(it.line, it.column) }
3220

3321
return index
34-
.getCompletions(prefix, "", receiverType) // TODO: ThisRef
22+
.getCompletions(prefix) // TODO: ThisRef
23+
.filterMatchesReceiver(receiverType)
3524
.map { decl ->
3625
val (inserted, insertionType) = decl.insertInfo()
37-
3826
CompletionItem().apply {
3927
label = decl.name
4028
labelDetails = decl.details()
@@ -44,5 +32,4 @@ fun autoCompletionDotExpression(
4432
additionalTextEdits = emptyList()
4533
}
4634
}
47-
.toList()
4835
}

app/src/main/kotlin/org/kotlinlsp/actions/autocomplete/GenericCompletion.kt

Lines changed: 51 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,8 @@ import org.kotlinlsp.index.queries.getCompletions
3131

3232
private val newlines = arrayOf("", "\n", "\n\n")
3333

34-
fun autoCompletionGeneric(ktFile: KtFile, offset: Int, index: Index, completingElement: KtElement, prefix: String): List<CompletionItem> {
35-
val localVariableCompletions = fetchLocalCompletions(ktFile, offset, completingElement, prefix)
36-
34+
fun autoCompletionGeneric(ktFile: KtFile, offset: Int, index: Index, completingElement: KtElement, prefix: String): Sequence<CompletionItem> {
35+
// Get import list and where to add new imports
3736
val existingImports = ktFile.importList?.children?.filterIsInstance<KtImportDirective>() ?: emptyList()
3837
val (importInsertionOffset, newlineCount) = if (existingImports.isEmpty()) {
3938
ktFile.packageDirective?.textRange?.let { it.endOffset to 2 } ?: (ktFile.textRange.startOffset to 0)
@@ -43,11 +42,12 @@ fun autoCompletionGeneric(ktFile: KtFile, offset: Int, index: Index, completingE
4342
val importInsertionPosition =
4443
StringUtil.offsetToLineColumn(ktFile.text, importInsertionOffset).let { Position(it.line, it.column) }
4544

46-
val completions = index
47-
.getCompletions(prefix, "", "") // TODO: ThisRef
45+
val externalCompletions = index
46+
.getCompletions(prefix) // TODO: ThisRef
4847
.map { decl ->
4948
val additionalEdits = mutableListOf<TextEdit>()
5049

50+
// Add the import if not there yet
5151
if (decl is Declaration.Class) {
5252
val exists = existingImports.any {
5353
it.importedFqName?.asString() == decl.fqName
@@ -74,7 +74,9 @@ fun autoCompletionGeneric(ktFile: KtFile, offset: Int, index: Index, completingE
7474
}
7575
}
7676

77-
return localVariableCompletions + completions
77+
val localCompletions = fetchLocalCompletions(ktFile, offset, completingElement, prefix)
78+
79+
return localCompletions.plus(externalCompletions).asSequence()
7880
}
7981

8082
@OptIn(KaExperimentalApi::class)
@@ -83,55 +85,57 @@ private fun fetchLocalCompletions(
8385
offset: Int,
8486
completingElement: KtElement,
8587
prefix: String
86-
): List<CompletionItem> = analyze(completingElement) {
88+
): List<CompletionItem> = analyze(ktFile) {
8789
ktFile
8890
.scopeContext(completingElement)
8991
.scopes
92+
.asSequence()
9093
.filter { it.kind is KaScopeKind.LocalScope }
91-
.flatMap {
92-
it.scope.declarations.mapNotNull { decl ->
93-
if (!decl.name.toString().startsWith(prefix)) return@mapNotNull null
94-
val psi = decl.psi ?: return@mapNotNull null
95-
96-
// TODO: This is a hack to get the correct offset for function literals, can analysis tell us if a declaration is accessible?
97-
val declOffset = if (psi is KtFunctionLiteral) psi.textRange.startOffset else psi.textRange.endOffset
98-
if (declOffset >= offset) return@mapNotNull null
99-
100-
val detail = when (decl) {
101-
is KaVariableSymbol -> decl.returnType.render(
102-
KaTypeRendererForSource.WITH_SHORT_NAMES,
103-
Variance.INVARIANT
104-
)
94+
.flatMap { it.scope.declarations }
95+
.filter { it.name.toString().startsWith(prefix) }
96+
.mapNotNull { if (it.psi != null) Pair(it, it.psi!!) else null }
97+
.filter { (_, psi) ->
98+
// TODO: This is a hack to get the correct offset for function literals, can analysis tell us if a declaration is accessible?
99+
val declOffset =
100+
if (psi is KtFunctionLiteral) psi.textRange.startOffset else psi.textRange.endOffset
101+
declOffset < offset
102+
}
103+
.map { (decl, psi) ->
104+
val detail = when (decl) {
105+
is KaVariableSymbol -> decl.returnType.render(
106+
KaTypeRendererForSource.WITH_SHORT_NAMES,
107+
Variance.INVARIANT
108+
)
109+
110+
else -> "Missing ${decl.javaClass.simpleName}"
111+
}
105112

106-
else -> "Missing ${decl.javaClass.simpleName}"
113+
val preview = when (psi) {
114+
is KtProperty -> psi.text
115+
is KtParameter -> {
116+
if (psi.isLoopParameter) {
117+
val loop = psi.parentOfType<KtLoopExpression>()!!
118+
loop.text.replace(loop.body!!.text, "")
119+
} else psi.text
107120
}
108121

109-
val preview = when (psi) {
110-
is KtProperty -> psi.text
111-
is KtParameter -> {
112-
if (psi.isLoopParameter) {
113-
val loop = psi.parentOfType<KtLoopExpression>()!!
114-
loop.text.replace(loop.body!!.text, "")
115-
} else psi.text
116-
}
117-
118-
is KtFunctionLiteral -> decl.name // TODO: Show the function call containing the lambda?
119-
else -> "TODO: Preview for ${psi.javaClass.simpleName}"
120-
}
122+
is KtFunctionLiteral -> decl.name // TODO: Show the function call containing the lambda?
123+
else -> "TODO: Preview for ${psi.javaClass.simpleName}"
124+
}
121125

122-
CompletionItem().apply {
123-
label = decl.name.toString()
124-
labelDetails = CompletionItemLabelDetails().apply {
125-
this.detail = " $detail"
126-
description = ""
127-
}
128-
documentation = Either.forRight(
129-
MarkupContent("markdown", "```kotlin\n${preview}\n```")
130-
)
131-
kind = CompletionItemKind.Variable
132-
insertText = decl.name.toString()
133-
insertTextFormat = InsertTextFormat.PlainText
126+
CompletionItem().apply {
127+
label = decl.name.toString()
128+
labelDetails = CompletionItemLabelDetails().apply {
129+
this.detail = " $detail"
130+
description = ""
134131
}
135-
}.toList()
132+
documentation = Either.forRight(
133+
MarkupContent("markdown", "```kotlin\n${preview}\n```")
134+
)
135+
kind = CompletionItemKind.Variable
136+
insertText = decl.name.toString()
137+
insertTextFormat = InsertTextFormat.PlainText
138+
}
136139
}
140+
.toList()
137141
}

app/src/main/kotlin/org/kotlinlsp/analysis/AnalysisSession.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,6 @@ class AnalysisSession(private val notifier: AnalysisSessionNotifier, rootPath: S
282282
val ktFile = index.getOpenedKtFile(path) ?: return emptyList()
283283
val offset = position.toOffset(ktFile)
284284

285-
return autocompleteAction(ktFile, offset, index)
285+
return project.read { autocompleteAction(ktFile, offset, index) }.toList()
286286
}
287287
}

app/src/main/kotlin/org/kotlinlsp/index/db/adapters/DatabaseAdapter.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,19 @@ interface DatabaseAdapter {
99
fun putRawData(key: String, value: ByteArray)
1010
fun putRawData(values: Iterable<Pair<String, ByteArray>>)
1111
fun getRawData(key: String): ByteArray?
12-
fun prefixSearch(prefix: String): Sequence<Pair<String, ByteArray>>
12+
fun prefixSearchRaw(prefix: String): Sequence<Pair<String, ByteArray>>
1313
fun remove(key: String)
1414
fun remove(keys: Iterable<String>)
1515
fun close()
1616
fun deleteDb()
1717
}
1818

19+
@OptIn(ExperimentalSerializationApi::class)
20+
inline fun <reified T: Any> DatabaseAdapter.prefixSearch(key: String): Sequence<Pair<String, T>> {
21+
return prefixSearchRaw(key)
22+
.map { (key, value) -> Pair(key, ProtoBuf.decodeFromByteArray(value)) }
23+
}
24+
1925
@OptIn(ExperimentalSerializationApi::class)
2026
inline fun <reified T: Any> DatabaseAdapter.put(key: String, value: T) {
2127
putRawData(key, ProtoBuf.encodeToByteArray(value))

app/src/main/kotlin/org/kotlinlsp/index/db/adapters/RocksDBAdapter.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class RocksDBAdapter(private val path: Path): DatabaseAdapter {
3939
return data
4040
}
4141

42-
override fun prefixSearch(prefix: String): Sequence<Pair<String, ByteArray>> = sequence {
42+
override fun prefixSearchRaw(prefix: String): Sequence<Pair<String, ByteArray>> = sequence {
4343
val readOptions = ReadOptions().setPrefixSameAsStart(true)
4444

4545
readOptions.use {
Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,10 @@
11
package org.kotlinlsp.index.queries
22

3-
import kotlinx.serialization.ExperimentalSerializationApi
4-
import kotlinx.serialization.decodeFromByteArray
5-
import kotlinx.serialization.protobuf.ProtoBuf
63
import org.kotlinlsp.index.Index
74
import org.kotlinlsp.index.db.Declaration
5+
import org.kotlinlsp.index.db.adapters.prefixSearch
86

9-
@OptIn(ExperimentalSerializationApi::class)
10-
fun Index.getCompletions(prefix: String, thisRef: String, receiver: String): Sequence<Declaration> = query {
11-
it.declarationsDb.prefixSearch(prefix)
12-
.map { ProtoBuf.decodeFromByteArray<Declaration>(it.second) }
13-
.filter {
14-
when (it) {
15-
is Declaration.Function -> it.receiverFqName == receiver || it.receiverFqName.isEmpty()
16-
is Declaration.Class -> true
17-
is Declaration.EnumEntry -> true
18-
is Declaration.Field -> it.parentFqName == receiver || it.parentFqName.isEmpty()
19-
}
20-
}
7+
fun Index.getCompletions(prefix: String): Sequence<Declaration> = query {
8+
it.declarationsDb.prefixSearch<Declaration>(prefix)
9+
.map { (_, value) -> value }
2110
}

app/src/main/kotlin/org/kotlinlsp/index/queries/PackageExistsInSourceFiles.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ import org.jetbrains.kotlin.name.FqName
44
import org.kotlinlsp.index.Index
55

66
fun Index.packageExistsInSourceFiles(fqName: FqName): Boolean = query { db ->
7-
db.packagesDb.prefixSearch(fqName.asString()).iterator().hasNext()
7+
db.packagesDb.prefixSearchRaw(fqName.asString()).iterator().hasNext()
88
}

app/src/main/kotlin/org/kotlinlsp/index/queries/SubpackageNames.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package org.kotlinlsp.index.queries
33
import org.kotlinlsp.index.Index
44

55
fun Index.subpackageNames(fqName: String): Set<String> = query { db ->
6-
db.packagesDb.prefixSearch(fqName)
6+
db.packagesDb.prefixSearchRaw(fqName)
77
.filter { (key, _) -> key != fqName }
88
.map { (key, _) -> key.removePrefix("$fqName.").split(".") }
99
.filter { it.isNotEmpty() }

0 commit comments

Comments
 (0)