diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8208dc76..167e4e5e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -49,5 +49,5 @@ org-jetbrains-kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gra [plugins] com-github-jk1-tcdeps = { id = "com.github.jk1.tcdeps", version = "1.6.2" } -com-jaredsburrows-license = { id = "com.jaredsburrows.license", version = "0.8.42" } +com-jaredsburrows-license = { id = "com.jaredsburrows.license", version = "0.9.8" } io-gitlab-arturbosch-detekt = { id = "io.gitlab.arturbosch.detekt", version = "1.22.0" } diff --git a/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt b/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt index e8da0ff9..79f2e71a 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.codeLensProvider = CodeLensOptions(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..d2323621 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.codelens.findCodeLenses class KotlinTextDocumentService( private val sf: SourceFiles, @@ -144,8 +145,14 @@ class KotlinTextDocumentService( )) } - override fun codeLens(params: CodeLensParams): CompletableFuture> { - TODO("not implemented") + override fun codeLens(params: CodeLensParams): CompletableFuture> = async.compute { + reportTime { + LOG.info("Finding code lenses in {}", describeURI(params.textDocument.uri)) + + val uri = parseURI(params.textDocument.uri) + val file = sp.currentVersion(uri) + return@compute findCodeLenses(file) + } } override fun rename(params: RenameParams) = async.compute { @@ -263,8 +270,66 @@ class KotlinTextDocumentService( } } - override fun resolveCodeLens(unresolved: CodeLens): CompletableFuture { - TODO("not implemented") + override fun resolveCodeLens(unresolved: CodeLens): CompletableFuture = async.compute { + reportTime { + LOG.info("Resolving code lens {}", unresolved.command?.command) + + val command = unresolved.command + if (command == null) { + return@compute unresolved + } + + val args = command.arguments as List<*> + if (args.size != 3) { + return@compute unresolved + } + + val uri = args[0] as String + val line = args[1] as Int + val character = args[2] as Int + + val file = sp.currentVersion(parseURI(uri)) + val content = sp.content(parseURI(uri)) + val offset = offset(content, line, character) + + when (command.command) { + "kotlin.showImplementations" -> { + val implementations = findImplementation(sp, sf, file, offset) + if (implementations.isNotEmpty()) { + unresolved.command = Command( + command.title, + command.command, + listOf(uri, line, character, implementations) + ) + } + } + "kotlin.showSubclasses" -> { + val implementations = findImplementation(sp, sf, file, offset) + if (implementations.isNotEmpty()) { + unresolved.command = Command( + command.title, + command.command, + listOf(uri, line, character, implementations) + ) + } + } + "kotlin.showReferences" -> { + val filePath = parseURI(uri).filePath + if (filePath != null) { + val references = findReferences(filePath, offset, sp) + if (references.isNotEmpty()) { + unresolved.command = Command( + command.title, + command.command, + listOf(uri, line, character, references) + ) + } + } + } + } + + return@compute unresolved + } } private fun describePosition(position: TextDocumentPositionParams): String { diff --git a/server/src/main/kotlin/org/javacs/kt/codelens/CodeLens.kt b/server/src/main/kotlin/org/javacs/kt/codelens/CodeLens.kt new file mode 100644 index 00000000..14e69af9 --- /dev/null +++ b/server/src/main/kotlin/org/javacs/kt/codelens/CodeLens.kt @@ -0,0 +1,64 @@ +package org.javacs.kt.codelens + +import org.eclipse.lsp4j.CodeLens +import org.eclipse.lsp4j.Command +import org.eclipse.lsp4j.Range +import org.javacs.kt.CompiledFile +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.psi.KtNamedFunction +import org.jetbrains.kotlin.resolve.BindingContext +import java.nio.file.Paths + +fun findCodeLenses(file: CompiledFile): List { + val codeLenses = mutableListOf() + val parsedFile = file.parse + val filePath = Paths.get(parsedFile.containingFile.virtualFile.path) + val uri = filePath.toUri().toString() + + // Add code lenses for classes and interfaces + parsedFile.declarations.filterIsInstance().forEach { ktClass -> + val classDesc = file.compile.get(BindingContext.CLASS, ktClass) + if (classDesc != null) { + when (classDesc.kind) { + ClassKind.INTERFACE -> { + // Add "Show Implementations" code lens for interfaces + location(ktClass)?.let { loc -> + codeLenses.add(CodeLens( + loc.range, + Command("Show Implementations", "kotlin.showImplementations", listOf(uri, loc.range.start.line, loc.range.start.character)), + null + )) + } + } + ClassKind.CLASS -> { + // Add "Show Subclasses" code lens for classes + location(ktClass)?.let { loc -> + codeLenses.add(CodeLens( + loc.range, + Command("Show Subclasses", "kotlin.showSubclasses", listOf(uri, loc.range.start.line, loc.range.start.character)), + null + )) + } + } + else -> {} + } + } + } + + // Add code lenses for functions + parsedFile.declarations.filterIsInstance().forEach { ktFunction -> + location(ktFunction)?.let { loc -> + codeLenses.add(CodeLens( + loc.range, + Command("Show References", "kotlin.showReferences", listOf(uri, loc.range.start.line, loc.range.start.character)), + null + )) + } + } + + return codeLenses +} \ No newline at end of file