From 3173dcb69623df9aecd84346fd2f3e9cdd4805c5 Mon Sep 17 00:00:00 2001 From: MrMineO5 Date: Mon, 12 May 2025 09:48:26 +0200 Subject: [PATCH 1/2] Improve filtering for static vs non-static functions and fields --- .../completions/DotExpressionCompletion.kt | 10 +++++++- .../actions/completions/GenericCompletion.kt | 10 +++++++- .../org/kotlinlsp/index/db/Declaration.kt | 3 ++- .../kotlinlsp/index/queries/Completions.kt | 10 +------- .../org/kotlinlsp/index/worker/IndexKtFile.kt | 25 +++++++++++++------ 5 files changed, 38 insertions(+), 20 deletions(-) diff --git a/app/src/main/kotlin/org/kotlinlsp/actions/completions/DotExpressionCompletion.kt b/app/src/main/kotlin/org/kotlinlsp/actions/completions/DotExpressionCompletion.kt index d818c3c..d4dc5ea 100644 --- a/app/src/main/kotlin/org/kotlinlsp/actions/completions/DotExpressionCompletion.kt +++ b/app/src/main/kotlin/org/kotlinlsp/actions/completions/DotExpressionCompletion.kt @@ -42,7 +42,15 @@ fun autoCompletionDotExpression(ktFile: KtFile, offset: Int, index: Index, compl } val importInsertionPosition = StringUtil.offsetToLineColumn(ktFile.text, importInsertionOffset).let { Position(it.line, it.column) } - val completions = index.getCompletions(prefix, "", receiverType) // TODO: ThisRef + val completions = index.getCompletions(prefix) // TODO: ThisRef + .filter { + when (it) { + is Declaration.Class -> false + is Declaration.Function -> it.receiverFqName == receiverType + is Declaration.Field -> it.parentFqName == receiverType + is Declaration.EnumEntry -> false + } + } .mapNotNull { decl -> val additionalEdits = mutableListOf() diff --git a/app/src/main/kotlin/org/kotlinlsp/actions/completions/GenericCompletion.kt b/app/src/main/kotlin/org/kotlinlsp/actions/completions/GenericCompletion.kt index 0e4c8d9..31581d5 100644 --- a/app/src/main/kotlin/org/kotlinlsp/actions/completions/GenericCompletion.kt +++ b/app/src/main/kotlin/org/kotlinlsp/actions/completions/GenericCompletion.kt @@ -85,7 +85,15 @@ fun autoCompletionGeneric(ktFile: KtFile, offset: Int, index: Index, completingE } val importInsertionPosition = StringUtil.offsetToLineColumn(ktFile.text, importInsertionOffset).let { Position(it.line, it.column) } - val completions = index.getCompletions(prefix, "", "") // TODO: ThisRef + val completions = index.getCompletions(prefix) // TODO: ThisRef + .filter { + when (it) { + is Declaration.Class -> true + is Declaration.Field -> it.static + is Declaration.Function -> it.static + is Declaration.EnumEntry -> true + } + } .mapNotNull { decl -> val additionalEdits = mutableListOf() diff --git a/app/src/main/kotlin/org/kotlinlsp/index/db/Declaration.kt b/app/src/main/kotlin/org/kotlinlsp/index/db/Declaration.kt index fc5f880..0bd5506 100644 --- a/app/src/main/kotlin/org/kotlinlsp/index/db/Declaration.kt +++ b/app/src/main/kotlin/org/kotlinlsp/index/db/Declaration.kt @@ -2,7 +2,6 @@ package org.kotlinlsp.index.db import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import org.kotlinlsp.common.info import org.kotlinlsp.index.db.adapters.put @Serializable @@ -24,6 +23,7 @@ sealed class Declaration() { val returnType: String, val parentFqName: String, val receiverFqName: String, + val static: Boolean, ) : Declaration() { @Serializable data class Parameter( @@ -73,6 +73,7 @@ sealed class Declaration() { override val endOffset: Int, val type: String, val parentFqName: String, + val static: Boolean, ) : Declaration() } diff --git a/app/src/main/kotlin/org/kotlinlsp/index/queries/Completions.kt b/app/src/main/kotlin/org/kotlinlsp/index/queries/Completions.kt index 910abad..ee7bbff 100644 --- a/app/src/main/kotlin/org/kotlinlsp/index/queries/Completions.kt +++ b/app/src/main/kotlin/org/kotlinlsp/index/queries/Completions.kt @@ -7,15 +7,7 @@ import org.kotlinlsp.index.Index import org.kotlinlsp.index.db.Declaration @OptIn(ExperimentalSerializationApi::class) -fun Index.getCompletions(prefix: String, thisRef: String, receiver: String): Sequence = query { +fun Index.getCompletions(prefix: String): Sequence = query { it.declarationsDb.prefixSearch(prefix) .map { ProtoBuf.decodeFromByteArray(it.second) } - .filter { - when (it) { - is Declaration.Function -> it.receiverFqName == receiver || it.receiverFqName.isEmpty() - is Declaration.Class -> true - is Declaration.EnumEntry -> true - is Declaration.Field -> it.parentFqName == receiver || it.parentFqName.isEmpty() - } - } } diff --git a/app/src/main/kotlin/org/kotlinlsp/index/worker/IndexKtFile.kt b/app/src/main/kotlin/org/kotlinlsp/index/worker/IndexKtFile.kt index f915d9c..8c6e403 100644 --- a/app/src/main/kotlin/org/kotlinlsp/index/worker/IndexKtFile.kt +++ b/app/src/main/kotlin/org/kotlinlsp/index/worker/IndexKtFile.kt @@ -6,6 +6,7 @@ import org.jetbrains.kotlin.analysis.api.KaSession import org.jetbrains.kotlin.analysis.api.analyze import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.isAbstract +import org.jetbrains.kotlin.psi.psiUtil.isObjectLiteral import org.kotlinlsp.common.read import org.kotlinlsp.common.warn import org.kotlinlsp.index.db.* @@ -74,9 +75,13 @@ private fun KaSession.analyzeDeclaration(path: String, dcl: KtDeclaration): Decl return when (dcl) { is KtNamedFunction -> { - val parentFqName = if (dcl.parent is KtClassBody) { - (dcl.parent.parent as? KtClassOrObject)?.fqName?.asString() ?: "" - } else "" + // Local functions are not indexed, they are handled using the analysis API + if (dcl.isLocal) return null + + val container = if (dcl.parent is KtClassBody) { + dcl.parentOfType() + } else null + val parentFqName = container?.classSymbol?.defaultType?.toString() ?: "" Declaration.Function( name, @@ -92,7 +97,8 @@ private fun KaSession.analyzeDeclaration(path: String, dcl: KtDeclaration): Decl }, dcl.returnType.toString(), parentFqName, - dcl.receiverTypeReference?.type?.toString() ?: "" + dcl.receiverTypeReference?.type?.toString() ?: "", + container == null || container is KtObjectDeclaration ) } @@ -142,20 +148,22 @@ private fun KaSession.analyzeDeclaration(path: String, dcl: KtDeclaration): Decl startOffset, endOffset, dcl.returnType.toString(), - clazz.fqName?.asString() ?: "" + clazz.fqName?.asString() ?: "", + false, ) } is KtProperty -> { if (dcl.isLocal) return null - val clazz = dcl.parentOfType() ?: return Declaration.Field( + val clazz = dcl.parentOfType() ?: return Declaration.Field( name, dcl.fqName?.asString() ?: "", path, startOffset, endOffset, dcl.returnType.toString(), - "" + "", + true, ) Declaration.Field( @@ -165,7 +173,8 @@ private fun KaSession.analyzeDeclaration(path: String, dcl: KtDeclaration): Decl startOffset, endOffset, dcl.returnType.toString(), - clazz.fqName?.asString() ?: "" + clazz.fqName?.asString() ?: "", + clazz is KtObjectDeclaration ) } From 0c46fb0af3443c51e8b1a692a03b21803d83a23d Mon Sep 17 00:00:00 2001 From: MrMineO5 Date: Tue, 13 May 2025 13:24:30 +0200 Subject: [PATCH 2/2] Add extension properties and replace FqNames with ClassIds and CallableIds --- .../org/kotlinlsp/actions/Autocomplete.kt | 8 ++++ .../completions/DotExpressionCompletion.kt | 3 ++ .../kotlin/org/kotlinlsp/index/db/Database.kt | 2 +- .../org/kotlinlsp/index/db/Declaration.kt | 1 + .../org/kotlinlsp/index/worker/IndexKtFile.kt | 29 +++++++++--- app/test-projects/basic/test.kt | 45 +++++++++++++++++++ 6 files changed, 80 insertions(+), 8 deletions(-) create mode 100644 app/test-projects/basic/test.kt diff --git a/app/src/main/kotlin/org/kotlinlsp/actions/Autocomplete.kt b/app/src/main/kotlin/org/kotlinlsp/actions/Autocomplete.kt index 7749f6b..205e3a9 100644 --- a/app/src/main/kotlin/org/kotlinlsp/actions/Autocomplete.kt +++ b/app/src/main/kotlin/org/kotlinlsp/actions/Autocomplete.kt @@ -13,6 +13,7 @@ import org.jetbrains.kotlin.psi.KtNameReferenceExpression import org.jetbrains.kotlin.psi.KtValueArgumentList import org.kotlinlsp.actions.completions.autoCompletionDotExpression import org.kotlinlsp.actions.completions.autoCompletionGeneric +import org.kotlinlsp.common.info import org.kotlinlsp.index.Index import org.kotlinlsp.index.db.Declaration @@ -25,6 +26,8 @@ fun autocompleteAction(ktFile: KtFile, offset: Int, index: Index): List() ?: ktFile + info("Completing at $offset with prefix '$prefix', element type: ${completingElement.javaClass.simpleName}") + if (completingElement is KtNameReferenceExpression) { if (completingElement.parent is KtDotQualifiedExpression) { return completeDotQualified(ktFile, offset, index, completingElement.parent as KtDotQualifiedExpression, prefix) @@ -33,6 +36,11 @@ fun autocompleteAction(ktFile: KtFile, offset: Int, index: Index): List()?.fqName?.asString() ?: "" + dcl.parentOfType()?.getClassId()?.asString() ?: "" ) } @@ -129,7 +133,7 @@ private fun KaSession.analyzeDeclaration(path: String, dcl: KtDeclaration): Decl Declaration.Class( name, type, - dcl.fqName?.asString() ?: "", + dcl.getClassId()?.asString() ?: "", path, startOffset, endOffset @@ -140,40 +144,51 @@ private fun KaSession.analyzeDeclaration(path: String, dcl: KtDeclaration): Decl if (!dcl.hasValOrVar()) return null val constructor = dcl.parentOfType() ?: return null val clazz = constructor.parentOfType() ?: return null + val classId = clazz.getClassId() ?: return null + val callableId = CallableId(classId, dcl.nameAsSafeName) Declaration.Field( name, - dcl.fqName?.asString() ?: "", + callableId.toString(), path, startOffset, endOffset, dcl.returnType.toString(), - clazz.fqName?.asString() ?: "", + clazz.getClassId()?.asString() ?: "", + "", false, ) } is KtProperty -> { if (dcl.isLocal) return null + + val receiver = if (dcl.isExtensionDeclaration()) dcl.receiverTypeReference?.type?.toString() ?: "" else "" + val clazz = dcl.parentOfType() ?: return Declaration.Field( name, - dcl.fqName?.asString() ?: "", + CallableId(dcl.containingKtFile.packageFqName, dcl.nameAsSafeName).toString(), path, startOffset, endOffset, dcl.returnType.toString(), "", + receiver, true, ) + val classId = clazz.getClassId() ?: return null + val callableId = CallableId(classId, dcl.nameAsSafeName) + Declaration.Field( name, - dcl.fqName?.asString() ?: "", + callableId.toString(), path, startOffset, endOffset, dcl.returnType.toString(), - clazz.fqName?.asString() ?: "", + clazz.getClassId()?.asString() ?: "", + receiver, clazz is KtObjectDeclaration ) } diff --git a/app/test-projects/basic/test.kt b/app/test-projects/basic/test.kt new file mode 100644 index 0000000..8c42293 --- /dev/null +++ b/app/test-projects/basic/test.kt @@ -0,0 +1,45 @@ +package app.ultradev.divineprison.util + +import app.ultradev.divineprison.util.TestClass + +class TestClass( + val name: String, +) { + val test: String = "test" + + class TestContainedClass( + val innerTest: String = "innerTest" + ) + inner class TestInnerClass( + val innerTest: String = "innerTest" + ) + + companion object { + val testCompanion: String = "testCompanion" + + val String.test get() = this + "test" + } +} + +val GLOBAL_TEST = "test" + +object Test { + val test: String = "test" +} + +fun test() { + val testList = listOf(1, 2, 3, 4, 5) + val testClass = TestClass("test") + + testList.forEach { + println(it) + } + + println(testClass.name) + + TestClass.TestInnerClass + + for (i in 1..10) { + println(i) + } +} \ No newline at end of file