diff --git a/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt b/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt index e8da0ff9..47bb5b70 100644 --- a/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt +++ b/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt @@ -93,6 +93,7 @@ class KotlinLanguageServer( serverCapabilities.documentRangeFormattingProvider = Either.forLeft(true) serverCapabilities.executeCommandProvider = ExecuteCommandOptions(ALL_COMMANDS) serverCapabilities.documentHighlightProvider = Either.forLeft(true) + serverCapabilities.implementationProvider = Either.forLeft(true) val storagePath = getStoragePath(params) databaseService.setup(storagePath) diff --git a/server/src/main/kotlin/org/javacs/kt/KotlinTextDocumentService.kt b/server/src/main/kotlin/org/javacs/kt/KotlinTextDocumentService.kt index 2ec1e522..307c77b3 100644 --- a/server/src/main/kotlin/org/javacs/kt/KotlinTextDocumentService.kt +++ b/server/src/main/kotlin/org/javacs/kt/KotlinTextDocumentService.kt @@ -34,6 +34,7 @@ import java.io.Closeable import java.nio.file.Path import java.time.Duration import java.util.concurrent.CompletableFuture +import org.javacs.kt.implementation.findImplementation class KotlinTextDocumentService( private val sf: SourceFiles, @@ -267,6 +268,20 @@ class KotlinTextDocumentService( TODO("not implemented") } + override fun implementation(params: ImplementationParams): CompletableFuture, List>> = async.compute { + reportTime { + LOG.info("Find implementation at {}", describePosition(params)) + + val (file, cursor) = recover(params, Recompile.NEVER) ?: return@compute Either.forLeft(emptyList()) + val implementations = findImplementation(sp, sf, file, cursor) + if (implementations.isEmpty()) { + noResult("No implementations found at ${describePosition(params)}", Either.forLeft(emptyList())) + } else { + Either.forLeft(implementations) + } + } + } + private fun describePosition(position: TextDocumentPositionParams): String { return "${describeURI(position.textDocument.uri)} ${position.position.line + 1}:${position.position.character + 1}" } diff --git a/server/src/main/kotlin/org/javacs/kt/SourceFiles.kt b/server/src/main/kotlin/org/javacs/kt/SourceFiles.kt index d7bb8496..ee26d69a 100644 --- a/server/src/main/kotlin/org/javacs/kt/SourceFiles.kt +++ b/server/src/main/kotlin/org/javacs/kt/SourceFiles.kt @@ -20,6 +20,7 @@ import java.nio.file.FileSystems import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths +import kotlin.streams.toList private class SourceVersion(val content: String, val version: Int, val language: Language?, val isTemporary: Boolean) @@ -80,6 +81,14 @@ class SourceFiles( } } + fun getAllUris(): List { + return workspaceRoots.flatMap { root -> + Files.walk(root) + .filter { it.toString().endsWith(".kt") || it.toString().endsWith(".kts") } + .map { it.toUri() }.toList() + } + } + fun close(uri: URI) { if (uri in open) { open.remove(uri) diff --git a/server/src/main/kotlin/org/javacs/kt/implementation/FindImplementation.kt b/server/src/main/kotlin/org/javacs/kt/implementation/FindImplementation.kt new file mode 100644 index 00000000..b5dea927 --- /dev/null +++ b/server/src/main/kotlin/org/javacs/kt/implementation/FindImplementation.kt @@ -0,0 +1,64 @@ +package org.javacs.kt.implementation + +import org.eclipse.lsp4j.Location +import org.javacs.kt.CompiledFile +import org.javacs.kt.SourceFiles +import org.javacs.kt.SourcePath +import org.javacs.kt.LOG +import org.javacs.kt.position.location +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.ClassKind +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny +import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperInterfaces + + +fun findImplementation( + sp: SourcePath, + sf: SourceFiles, + file: CompiledFile, + cursor: Int +): List { + val (_, target) = file.referenceExpressionAtPoint(cursor) ?: return emptyList() + + LOG.info("Finding implementations for declaration descriptor {}", target) + + return when (target) { + is ClassDescriptor -> findImplementations(sp, sf, file, target) + else -> emptyList() + } +} + +private fun findImplementations(sp: SourcePath, sf: SourceFiles, file: CompiledFile, descriptor: ClassDescriptor): List { + val implementations = mutableListOf() + + // Get all Kotlin file URIs by scanning workspace roots + val allUris = sf.getAllUris() + if (descriptor.kind == ClassKind.INTERFACE) { + // Find all classes that implement this interface + allUris.forEach { uri -> + val ktFile = sp.parsedFile(uri) + ktFile.declarations.filterIsInstance().forEach { ktClass -> + val classDesc = file.compile.get(BindingContext.CLASS, ktClass) + if (classDesc != null && descriptor in classDesc.getSuperInterfaces()) { + location(ktClass)?.let { implementations.add(it) } + } + } + } + } else if (descriptor.kind == ClassKind.CLASS) { + // Find all subclasses + allUris.forEach { uri -> + val ktFile = sp.parsedFile(uri) + ktFile.declarations.filterIsInstance().forEach { ktClass -> + val classDesc = file.compile.get(BindingContext.CLASS, ktClass) + if (classDesc?.getSuperClassOrAny() == descriptor) { + location(ktClass)?.let { implementations.add(it) } + } + } + } + } + + return implementations +} +