Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions app/src/main/kotlin/org/kotlinlsp/actions/Autocomplete.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -25,6 +26,8 @@ fun autocompleteAction(ktFile: KtFile, offset: Int, index: Index): List<Completi
val prefix = leaf.text.substring(0, offset - leaf.textRange.startOffset)
val completingElement = leaf.parentOfType<KtElement>() ?: 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)
Expand All @@ -33,6 +36,11 @@ fun autocompleteAction(ktFile: KtFile, offset: Int, index: Index): List<Completi
}
}

if (completingElement is KtDotQualifiedExpression) {
assert(prefix == ".")
return completeDotQualified(ktFile, offset, index, completingElement, "")
}

if (completingElement is KtValueArgumentList) {
return emptyList() // TODO: function call arguments
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.types.Variance
import org.kotlinlsp.actions.completionKind
import org.kotlinlsp.common.info
import org.kotlinlsp.index.Index
import org.kotlinlsp.index.db.Declaration
import org.kotlinlsp.index.queries.getCompletions
Expand All @@ -42,7 +43,17 @@ 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
info("Completing dot expression at $offset with prefix '$prefix' and receiver type '$receiverType'")

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<TextEdit>()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<TextEdit>()

Expand Down
2 changes: 1 addition & 1 deletion app/src/main/kotlin/org/kotlinlsp/index/db/Database.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import org.kotlinlsp.index.db.adapters.put
import java.io.File
import kotlin.io.path.absolutePathString

const val CURRENT_SCHEMA_VERSION = 4 // Increment on schema changes
const val CURRENT_SCHEMA_VERSION = 5 // Increment on schema changes
const val VERSION_KEY = "__version"

class Database(rootFolder: String) {
Expand Down
4 changes: 3 additions & 1 deletion app/src/main/kotlin/org/kotlinlsp/index/db/Declaration.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -24,6 +23,7 @@ sealed class Declaration() {
val returnType: String,
val parentFqName: String,
val receiverFqName: String,
val static: Boolean,
) : Declaration() {
@Serializable
data class Parameter(
Expand Down Expand Up @@ -73,6 +73,8 @@ sealed class Declaration() {
override val endOffset: Int,
val type: String,
val parentFqName: String,
val receiverFqName: String,
val static: Boolean,
) : Declaration()
}

Expand Down
10 changes: 1 addition & 9 deletions app/src/main/kotlin/org/kotlinlsp/index/queries/Completions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<Declaration> = query {
fun Index.getCompletions(prefix: String): Sequence<Declaration> = query {
it.declarationsDb.prefixSearch(prefix)
.map { ProtoBuf.decodeFromByteArray<Declaration>(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()
}
}
}
50 changes: 37 additions & 13 deletions app/src/main/kotlin/org/kotlinlsp/index/worker/IndexKtFile.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ import com.intellij.openapi.project.Project
import com.intellij.psi.util.parentOfType
import org.jetbrains.kotlin.analysis.api.KaSession
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.StandardClassIds
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.isAbstract
import org.jetbrains.kotlin.psi.psiUtil.isExtensionDeclaration
import org.jetbrains.kotlin.psi.psiUtil.isObjectLiteral
import org.kotlinlsp.common.read
import org.kotlinlsp.common.warn
import org.kotlinlsp.index.db.*
Expand Down Expand Up @@ -74,9 +79,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<KtClassOrObject>()
} else null
val parentFqName = container?.classSymbol?.defaultType?.toString() ?: ""

Declaration.Function(
name,
Expand All @@ -92,7 +101,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
)
}

Expand All @@ -104,7 +114,7 @@ private fun KaSession.analyzeDeclaration(path: String, dcl: KtDeclaration): Decl
path,
startOffset,
endOffset,
dcl.parentOfType<KtClass>()?.fqName?.asString() ?: ""
dcl.parentOfType<KtClass>()?.getClassId()?.asString() ?: ""
)
}

Expand All @@ -123,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
Expand All @@ -134,38 +144,52 @@ private fun KaSession.analyzeDeclaration(path: String, dcl: KtDeclaration): Decl
if (!dcl.hasValOrVar()) return null
val constructor = dcl.parentOfType<KtPrimaryConstructor>() ?: return null
val clazz = constructor.parentOfType<KtClass>() ?: 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 clazz = dcl.parentOfType<KtClass>() ?: return Declaration.Field(

val receiver = if (dcl.isExtensionDeclaration()) dcl.receiverTypeReference?.type?.toString() ?: "" else ""

val clazz = dcl.parentOfType<KtClassOrObject>() ?: 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
)
}

Expand Down
45 changes: 45 additions & 0 deletions app/test-projects/basic/test.kt
Original file line number Diff line number Diff line change
@@ -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)
}
}