Skip to content

Commit 69d6201

Browse files
committed
added goToImplementation capability
1 parent 8418fb5 commit 69d6201

File tree

4 files changed

+89
-0
lines changed

4 files changed

+89
-0
lines changed

server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ class KotlinLanguageServer(
9393
serverCapabilities.documentRangeFormattingProvider = Either.forLeft(true)
9494
serverCapabilities.executeCommandProvider = ExecuteCommandOptions(ALL_COMMANDS)
9595
serverCapabilities.documentHighlightProvider = Either.forLeft(true)
96+
serverCapabilities.implementationProvider = Either.forLeft(true)
9697

9798
val storagePath = getStoragePath(params)
9899
databaseService.setup(storagePath)

server/src/main/kotlin/org/javacs/kt/KotlinTextDocumentService.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import java.io.Closeable
3434
import java.nio.file.Path
3535
import java.time.Duration
3636
import java.util.concurrent.CompletableFuture
37+
import org.javacs.kt.implementation.findImplementation
3738

3839
class KotlinTextDocumentService(
3940
private val sf: SourceFiles,
@@ -267,6 +268,20 @@ class KotlinTextDocumentService(
267268
TODO("not implemented")
268269
}
269270

271+
override fun implementation(params: ImplementationParams): CompletableFuture<Either<List<Location>, List<LocationLink>>> = async.compute {
272+
reportTime {
273+
LOG.info("Find implementation at {}", describePosition(params))
274+
275+
val (file, cursor) = recover(params, Recompile.NEVER) ?: return@compute Either.forLeft(emptyList())
276+
val implementations = findImplementation(sp, sf, file, cursor)
277+
if (implementations.isEmpty()) {
278+
noResult("No implementations found at ${describePosition(params)}", Either.forLeft(emptyList()))
279+
} else {
280+
Either.forLeft(implementations)
281+
}
282+
}
283+
}
284+
270285
private fun describePosition(position: TextDocumentPositionParams): String {
271286
return "${describeURI(position.textDocument.uri)} ${position.position.line + 1}:${position.position.character + 1}"
272287
}

server/src/main/kotlin/org/javacs/kt/SourceFiles.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import java.nio.file.FileSystems
2020
import java.nio.file.Files
2121
import java.nio.file.Path
2222
import java.nio.file.Paths
23+
import kotlin.streams.toList
2324

2425
private class SourceVersion(val content: String, val version: Int, val language: Language?, val isTemporary: Boolean)
2526

@@ -80,6 +81,14 @@ class SourceFiles(
8081
}
8182
}
8283

84+
fun getAllUris(): List<URI> {
85+
return workspaceRoots.flatMap { root ->
86+
Files.walk(root)
87+
.filter { it.toString().endsWith(".kt") || it.toString().endsWith(".kts") }
88+
.map { it.toUri() }.toList()
89+
}
90+
}
91+
8392
fun close(uri: URI) {
8493
if (uri in open) {
8594
open.remove(uri)
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package org.javacs.kt.implementation
2+
3+
import org.eclipse.lsp4j.Location
4+
import org.javacs.kt.CompiledFile
5+
import org.javacs.kt.SourceFiles
6+
import org.javacs.kt.SourcePath
7+
import org.javacs.kt.LOG
8+
import org.javacs.kt.position.location
9+
import org.jetbrains.kotlin.descriptors.ClassDescriptor
10+
import org.jetbrains.kotlin.descriptors.ClassKind
11+
import org.jetbrains.kotlin.psi.KtClassOrObject
12+
import org.jetbrains.kotlin.resolve.BindingContext
13+
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny
14+
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperInterfaces
15+
16+
17+
fun findImplementation(
18+
sp: SourcePath,
19+
sf: SourceFiles,
20+
file: CompiledFile,
21+
cursor: Int
22+
): List<Location> {
23+
val (_, target) = file.referenceExpressionAtPoint(cursor) ?: return emptyList()
24+
25+
LOG.info("Finding implementations for declaration descriptor {}", target)
26+
27+
return when (target) {
28+
is ClassDescriptor -> findImplementations(sp, sf, file, target)
29+
else -> emptyList()
30+
}
31+
}
32+
33+
private fun findImplementations(sp: SourcePath, sf: SourceFiles, file: CompiledFile, descriptor: ClassDescriptor): List<Location> {
34+
val implementations = mutableListOf<Location>()
35+
36+
// Get all Kotlin file URIs by scanning workspace roots
37+
val allUris = sf.getAllUris()
38+
if (descriptor.kind == ClassKind.INTERFACE) {
39+
// Find all classes that implement this interface
40+
allUris.forEach { uri ->
41+
val ktFile = sp.parsedFile(uri)
42+
ktFile.declarations.filterIsInstance<KtClassOrObject>().forEach { ktClass ->
43+
val classDesc = file.compile.get(BindingContext.CLASS, ktClass)
44+
if (classDesc != null && descriptor in classDesc.getSuperInterfaces()) {
45+
location(ktClass)?.let { implementations.add(it) }
46+
}
47+
}
48+
}
49+
} else if (descriptor.kind == ClassKind.CLASS) {
50+
// Find all subclasses
51+
allUris.forEach { uri ->
52+
val ktFile = sp.parsedFile(uri)
53+
ktFile.declarations.filterIsInstance<KtClassOrObject>().forEach { ktClass ->
54+
val classDesc = file.compile.get(BindingContext.CLASS, ktClass)
55+
if (classDesc?.getSuperClassOrAny() == descriptor) {
56+
location(ktClass)?.let { implementations.add(it) }
57+
}
58+
}
59+
}
60+
}
61+
62+
return implementations
63+
}
64+

0 commit comments

Comments
 (0)