diff --git a/server/src/main/kotlin/org/javacs/kt/Configuration.kt b/server/src/main/kotlin/org/javacs/kt/Configuration.kt index 13774c38e..f5188e7c0 100644 --- a/server/src/main/kotlin/org/javacs/kt/Configuration.kt +++ b/server/src/main/kotlin/org/javacs/kt/Configuration.kt @@ -107,5 +107,5 @@ public data class Configuration( val indexing: IndexingConfiguration = IndexingConfiguration(), val externalSources: ExternalSourcesConfiguration = ExternalSourcesConfiguration(), val inlayHints: InlayHintsConfiguration = InlayHintsConfiguration(), - val formatting: FormattingConfiguration = FormattingConfiguration(), + val formatting: FormattingConfiguration = FormattingConfiguration() ) diff --git a/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt b/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt index 29998dc3a..54605ef09 100644 --- a/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt +++ b/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt @@ -108,6 +108,8 @@ class KotlinLanguageServer( serverCapabilities.renameProvider = Either.forRight(RenameOptions(false)) } + workspaces.initialize(clientCapabilities) + @Suppress("DEPRECATION") val folders = params.workspaceFolders?.takeIf { it.isNotEmpty() } ?: params.rootUri?.let(::WorkspaceFolder)?.let(::listOf) diff --git a/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt b/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt index 82ec771bd..8b5f1d8f8 100644 --- a/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt +++ b/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt @@ -16,6 +16,7 @@ import java.util.concurrent.CompletableFuture import com.google.gson.JsonElement import com.google.gson.Gson import com.google.gson.JsonObject +import org.javacs.kt.symbols.symbolResolveSupport class KotlinWorkspaceService( private val sf: SourceFiles, @@ -26,11 +27,16 @@ class KotlinWorkspaceService( ) : WorkspaceService, LanguageClientAware { private val gson = Gson() private var languageClient: LanguageClient? = null + private var clientCapabilities: ClientCapabilities? = null override fun connect(client: LanguageClient): Unit { languageClient = client } + fun initialize(clientCapabilities: ClientCapabilities) { + this.clientCapabilities = clientCapabilities + } + override fun executeCommand(params: ExecuteCommandParams): CompletableFuture { val args = params.arguments LOG.info("Executing command: {} with {}", params.command, params.arguments) @@ -189,7 +195,7 @@ class KotlinWorkspaceService( @Suppress("DEPRECATION") override fun symbol(params: WorkspaceSymbolParams): CompletableFuture, List>> { - val result = workspaceSymbols(params.query, sp) + val result = workspaceSymbols(params.query, sp, !clientCapabilities.symbolResolveSupport.enabled) return CompletableFuture.completedFuture(Either.forRight(result)) } diff --git a/server/src/main/kotlin/org/javacs/kt/symbols/SymbolResolveSupport.kt b/server/src/main/kotlin/org/javacs/kt/symbols/SymbolResolveSupport.kt new file mode 100644 index 000000000..42455c03e --- /dev/null +++ b/server/src/main/kotlin/org/javacs/kt/symbols/SymbolResolveSupport.kt @@ -0,0 +1,13 @@ +package org.javacs.kt.symbols + +import org.eclipse.lsp4j.ClientCapabilities + +data class SymbolResolveSupport( + val enabled: Boolean = false, + val properties: List = emptyList() +) + +val ClientCapabilities?.symbolResolveSupport + get() = this?.workspace?.symbol?.resolveSupport?.properties?.let { properties -> + if (properties.size > 0) SymbolResolveSupport(true, properties) else null + } ?: SymbolResolveSupport(false, emptyList()) diff --git a/server/src/main/kotlin/org/javacs/kt/symbols/Symbols.kt b/server/src/main/kotlin/org/javacs/kt/symbols/Symbols.kt index cee4dbe42..4c760c609 100644 --- a/server/src/main/kotlin/org/javacs/kt/symbols/Symbols.kt +++ b/server/src/main/kotlin/org/javacs/kt/symbols/Symbols.kt @@ -3,6 +3,7 @@ package org.javacs.kt.symbols import com.intellij.psi.PsiElement +import org.eclipse.lsp4j.Location import org.eclipse.lsp4j.SymbolInformation import org.eclipse.lsp4j.SymbolKind import org.eclipse.lsp4j.DocumentSymbol @@ -11,9 +12,9 @@ import org.eclipse.lsp4j.WorkspaceSymbolLocation import org.eclipse.lsp4j.jsonrpc.messages.Either import org.javacs.kt.SourcePath import org.javacs.kt.position.range +import org.javacs.kt.position.toURIString import org.javacs.kt.util.containsCharactersInOrder import org.javacs.kt.util.preOrderTraversal -import org.javacs.kt.util.toPath import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.parents @@ -33,10 +34,10 @@ private fun doDocumentSymbols(element: PsiElement): List { } ?: children } -fun workspaceSymbols(query: String, sp: SourcePath): List = +fun workspaceSymbols(query: String, sp: SourcePath, locationRequired: Boolean): List = doWorkspaceSymbols(sp) .filter { containsCharactersInOrder(it.name!!, query, false) } - .mapNotNull(::workspaceSymbol) + .mapNotNull { workspaceSymbol(it, locationRequired) } .toList() private fun doWorkspaceSymbols(sp: SourcePath): Sequence = @@ -56,11 +57,22 @@ private fun pickImportantElements(node: PsiElement, includeLocals: Boolean): KtN else -> null } -private fun workspaceSymbol(d: KtNamedDeclaration): WorkspaceSymbol? { - val name = d.name ?: return null +private fun workspaceSymbol(d: KtNamedDeclaration, locationRequired: Boolean): WorkspaceSymbol? = + d.name?.let { name -> + val location: Either? = if (locationRequired) { + val content = d.containingFile?.text + val locationInContent = (d.nameIdentifier?.textRange ?: d.textRange) + if (content != null && locationInContent != null) { + Either.forLeft(Location(d.containingFile.toURIString(), range(content, locationInContent))) + } else { + null + } + } else { + d.containingFile?.let { Either.forRight(WorkspaceSymbolLocation(it.toURIString())) } + } - return WorkspaceSymbol(name, symbolKind(d), Either.forRight(workspaceLocation(d)), symbolContainer(d)) -} + location?.let { WorkspaceSymbol(name, symbolKind(d), it, symbolContainer(d)) } + } private fun symbolKind(d: KtNamedDeclaration): SymbolKind = when (d) { @@ -73,13 +85,6 @@ private fun symbolKind(d: KtNamedDeclaration): SymbolKind = else -> throw IllegalArgumentException("Unexpected symbol $d") } -private fun workspaceLocation(d: KtNamedDeclaration): WorkspaceSymbolLocation { - val file = d.containingFile - val uri = file.toPath().toUri().toString() - - return WorkspaceSymbolLocation(uri) -} - private fun symbolContainer(d: KtNamedDeclaration): String? = d.parents .filterIsInstance() diff --git a/server/src/test/kotlin/org/javacs/kt/WorkspaceSymbolsTest.kt b/server/src/test/kotlin/org/javacs/kt/WorkspaceSymbolsTest.kt index 5c4ebf2fa..d23b24069 100644 --- a/server/src/test/kotlin/org/javacs/kt/WorkspaceSymbolsTest.kt +++ b/server/src/test/kotlin/org/javacs/kt/WorkspaceSymbolsTest.kt @@ -1,7 +1,12 @@ package org.javacs.kt +import org.eclipse.lsp4j.ClientCapabilities +import org.eclipse.lsp4j.SymbolCapabilities import org.eclipse.lsp4j.SymbolKind +import org.eclipse.lsp4j.WorkspaceClientCapabilities import org.eclipse.lsp4j.WorkspaceSymbolParams +import org.eclipse.lsp4j.WorkspaceSymbolResolveSupportCapabilities +import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.hasItem import org.hamcrest.Matchers.not import org.junit.Assert.assertThat @@ -21,4 +26,28 @@ class WorkspaceSymbolsTest : SingleFileTestFixture("symbols", "DocumentSymbols.k assertThat(all, not(hasItem("aConstructorArg"))) assertThat(all, not(hasItem("otherFileLocalVariable"))) } + + @Test fun `returns location information if resolve is not supported by the client`() { + languageServer.workspaceService.initialize(clientCapabilities(false)) + val found = languageServer.workspaceService.symbol(WorkspaceSymbolParams("")).get().right + assertThat(found.all { s -> s.location.isLeft }, equalTo(true)) + } + + @Test fun `returns no location information if resolve is supported by the client`() { + languageServer.workspaceService.initialize(clientCapabilities(true)) + val found = languageServer.workspaceService.symbol(WorkspaceSymbolParams("")).get().right + assertThat(found.all { s -> s.location.isRight }, equalTo(true)) + } + + private fun clientCapabilities(resolveSupported: Boolean): ClientCapabilities { + val properties = if (resolveSupported) listOf("location.range") else emptyList() + + val workspaceClientCapabilities = WorkspaceClientCapabilities() + val symbolCapabilities = SymbolCapabilities() + val workspaceSymbolResolveSupportCapabilities = WorkspaceSymbolResolveSupportCapabilities() + workspaceSymbolResolveSupportCapabilities.properties = properties + symbolCapabilities.resolveSupport = workspaceSymbolResolveSupportCapabilities + workspaceClientCapabilities.symbol = symbolCapabilities + return ClientCapabilities(workspaceClientCapabilities, null, null) + } } diff --git a/shared/src/main/kotlin/org/javacs/kt/Logger.kt b/shared/src/main/kotlin/org/javacs/kt/Logger.kt index 68359f27a..7280e490a 100644 --- a/shared/src/main/kotlin/org/javacs/kt/Logger.kt +++ b/shared/src/main/kotlin/org/javacs/kt/Logger.kt @@ -3,7 +3,6 @@ package org.javacs.kt import java.io.PrintWriter import java.io.StringWriter import java.util.* -import java.util.logging.Formatter import java.util.logging.LogRecord import java.util.logging.Handler import java.util.logging.Level @@ -136,7 +135,7 @@ class Logger { fun connectStdioBackend() { connectOutputBackend { println(it.formatted) } - connectOutputBackend { System.err.println(it.formatted) } + connectErrorBackend { System.err.println(it.formatted) } } private fun insertPlaceholders(msg: String, placeholders: Array): String {