From faf492ecb6ad9b734175ffc00856e71b24f190ec Mon Sep 17 00:00:00 2001 From: Lorenz Bateman Date: Mon, 19 Feb 2024 08:54:52 +0100 Subject: [PATCH 1/4] Fix compatibility issues + bugs 1) From LSP spec 3.16 to 3.17, dropping the location attribute from the response to `workspace/symbol` is only permitted if the client capability `workspace.symbol.resolveSupport` is advertized. Without this setting, the `location` attribute should be present. 2) `findDeclarationCursorSite` constructs a location using a non-uri path in the uri property. This leads to document symbol rename for example to not work. 3) THe stdio backend for logging accidentally overrides the stdout backend with the one meant for stderr. --- .../kotlin/org/javacs/kt/Configuration.kt | 10 ++++++ .../org/javacs/kt/KotlinLanguageServer.kt | 4 +++ .../org/javacs/kt/KotlinWorkspaceService.kt | 2 +- .../kotlin/org/javacs/kt/symbols/Symbols.kt | 33 ++++++++++++++----- .../src/main/kotlin/org/javacs/kt/Logger.kt | 3 +- 5 files changed, 41 insertions(+), 11 deletions(-) diff --git a/server/src/main/kotlin/org/javacs/kt/Configuration.kt b/server/src/main/kotlin/org/javacs/kt/Configuration.kt index 13774c38e..284e9a439 100644 --- a/server/src/main/kotlin/org/javacs/kt/Configuration.kt +++ b/server/src/main/kotlin/org/javacs/kt/Configuration.kt @@ -39,6 +39,15 @@ public data class CompilerConfiguration( val jvm: JVMConfiguration = JVMConfiguration() ) +public data class SymbolResolveSupport( + val enabled: Boolean = false, + val properties: List = emptyList() +) + +public data class WorkspaceConfiguration( + var symbolResolveSupport: SymbolResolveSupport = SymbolResolveSupport() +) + public data class IndexingConfiguration( /** Whether an index of global symbols should be built in the background. */ var enabled: Boolean = true @@ -108,4 +117,5 @@ public data class Configuration( val externalSources: ExternalSourcesConfiguration = ExternalSourcesConfiguration(), val inlayHints: InlayHintsConfiguration = InlayHintsConfiguration(), val formatting: FormattingConfiguration = FormattingConfiguration(), + val workspace: WorkspaceConfiguration = WorkspaceConfiguration() ) diff --git a/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt b/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt index 29998dc3a..65d223007 100644 --- a/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt +++ b/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt @@ -108,6 +108,10 @@ class KotlinLanguageServer( serverCapabilities.renameProvider = Either.forRight(RenameOptions(false)) } + config.workspace.symbolResolveSupport = clientCapabilities?.workspace?.symbol?.resolveSupport?.properties?.let { properties -> + if (properties.size > 0) SymbolResolveSupport(true, properties) else null + } ?: SymbolResolveSupport(false, emptyList()) + @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..c610e9e82 100644 --- a/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt +++ b/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt @@ -189,7 +189,7 @@ class KotlinWorkspaceService( @Suppress("DEPRECATION") override fun symbol(params: WorkspaceSymbolParams): CompletableFuture, List>> { - val result = workspaceSymbols(params.query, sp) + val result = workspaceSymbols(!config.workspace.symbolResolveSupport.enabled, params.query, sp) return CompletableFuture.completedFuture(Either.forRight(result)) } 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..5b8567d0f 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 @@ -33,10 +34,10 @@ private fun doDocumentSymbols(element: PsiElement): List { } ?: children } -fun workspaceSymbols(query: String, sp: SourcePath): List = +fun workspaceSymbols(locationRequired: Boolean, query: String, sp: SourcePath): List = doWorkspaceSymbols(sp) .filter { containsCharactersInOrder(it.name!!, query, false) } - .mapNotNull(::workspaceSymbol) + .mapNotNull(workspaceSymbol(locationRequired)) .toList() private fun doWorkspaceSymbols(sp: SourcePath): Sequence = @@ -56,10 +57,18 @@ 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(locationRequired: Boolean): (KtNamedDeclaration) -> WorkspaceSymbol? { + return { d -> + d.name?.let { name -> + val location: Either? = if (locationRequired) { + location(d)?.let { l -> Either.forLeft(l) } + } else { + Either.forRight(workspaceSymbolLocation(d)) + } - 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 = @@ -73,10 +82,18 @@ 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() +private fun location(d: KtNamedDeclaration): Location? { + val uri = d.containingFile.toPath().toUri().toString() + val (content, textRange) = try { d.containingFile?.text to d.nameIdentifier?.textRange } catch (e: Exception) { null to null } + return if (content != null && textRange != null) { + Location(uri, range(content, textRange)) + } else { + null + } +} +private fun workspaceSymbolLocation(d: KtNamedDeclaration): WorkspaceSymbolLocation { + val uri = d.containingFile.toPath().toUri().toString() return WorkspaceSymbolLocation(uri) } 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 { From 2ae12529b90301a02d8037473729c12ce84f45ba Mon Sep 17 00:00:00 2001 From: Lorenz Bateman Date: Tue, 20 Feb 2024 08:25:35 +0100 Subject: [PATCH 2/4] Fallback to declaration text range Not all named declarations have a name identifier. In those cases fallback to using the range that encloses the whole named declaration rather than returning nothing. --- .../kotlin/org/javacs/kt/symbols/Symbols.kt | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) 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 5b8567d0f..9b8aa2d46 100644 --- a/server/src/main/kotlin/org/javacs/kt/symbols/Symbols.kt +++ b/server/src/main/kotlin/org/javacs/kt/symbols/Symbols.kt @@ -12,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 @@ -82,20 +82,22 @@ private fun symbolKind(d: KtNamedDeclaration): SymbolKind = else -> throw IllegalArgumentException("Unexpected symbol $d") } -private fun location(d: KtNamedDeclaration): Location? { - val uri = d.containingFile.toPath().toUri().toString() - val (content, textRange) = try { d.containingFile?.text to d.nameIdentifier?.textRange } catch (e: Exception) { null to null } - return if (content != null && textRange != null) { - Location(uri, range(content, textRange)) - } else { +private fun location(d: KtNamedDeclaration): Location? = + try { + val content = d.containingFile?.text + val locationInContent = (d.nameIdentifier?.textRange ?: d.textRange) + if (content != null && locationInContent != null) { + Location(d.containingFile.toURIString(), range(content, locationInContent)) + } else { + null + } + } catch (e: Exception) { null } -} -private fun workspaceSymbolLocation(d: KtNamedDeclaration): WorkspaceSymbolLocation { - val uri = d.containingFile.toPath().toUri().toString() - return WorkspaceSymbolLocation(uri) -} + +private fun workspaceSymbolLocation(d: KtNamedDeclaration): WorkspaceSymbolLocation = + WorkspaceSymbolLocation(d.containingFile.toURIString()) private fun symbolContainer(d: KtNamedDeclaration): String? = d.parents From 1abe6bf5dcdc1576574027496998bd9ac9e872be Mon Sep 17 00:00:00 2001 From: Lorenz Bateman Date: Wed, 13 Mar 2024 09:03:51 +0100 Subject: [PATCH 3/4] Fix detekt violations --- .../org/javacs/kt/KotlinLanguageServer.kt | 9 ++++-- .../kotlin/org/javacs/kt/symbols/Symbols.kt | 29 +++++++------------ 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt b/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt index 65d223007..950b01085 100644 --- a/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt +++ b/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt @@ -108,9 +108,7 @@ class KotlinLanguageServer( serverCapabilities.renameProvider = Either.forRight(RenameOptions(false)) } - config.workspace.symbolResolveSupport = clientCapabilities?.workspace?.symbol?.resolveSupport?.properties?.let { properties -> - if (properties.size > 0) SymbolResolveSupport(true, properties) else null - } ?: SymbolResolveSupport(false, emptyList()) + config.workspace.symbolResolveSupport = clientHasWorkspaceSymbolResolveSupport(clientCapabilities) @Suppress("DEPRECATION") val folders = params.workspaceFolders?.takeIf { it.isNotEmpty() } @@ -145,6 +143,11 @@ class KotlinLanguageServer( InitializeResult(serverCapabilities, serverInfo) } + private fun clientHasWorkspaceSymbolResolveSupport(clientCapabilities: ClientCapabilities) = + clientCapabilities?.workspace?.symbol?.resolveSupport?.properties?.let { properties -> + if (properties.size > 0) SymbolResolveSupport(true, properties) else null + } ?: SymbolResolveSupport(false, emptyList()) + private fun connectLoggingBackend() { val backend: (LogMessage) -> Unit = { client.logMessage(MessageParams().apply { 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 9b8aa2d46..0f88496b3 100644 --- a/server/src/main/kotlin/org/javacs/kt/symbols/Symbols.kt +++ b/server/src/main/kotlin/org/javacs/kt/symbols/Symbols.kt @@ -58,12 +58,22 @@ private fun pickImportantElements(node: PsiElement, includeLocals: Boolean): KtN } private fun workspaceSymbol(locationRequired: Boolean): (KtNamedDeclaration) -> WorkspaceSymbol? { + fun location(d: KtNamedDeclaration): Location? { + val content = d.containingFile?.text + val locationInContent = (d.nameIdentifier?.textRange ?: d.textRange) + return if (content != null && locationInContent != null) { + Location(d.containingFile.toURIString(), range(content, locationInContent)) + } else { + null + } + } + return { d -> d.name?.let { name -> val location: Either? = if (locationRequired) { location(d)?.let { l -> Either.forLeft(l) } } else { - Either.forRight(workspaceSymbolLocation(d)) + Either.forRight(WorkspaceSymbolLocation(d.containingFile.toURIString())) } location?.let { WorkspaceSymbol(name, symbolKind(d), it, symbolContainer(d)) } @@ -82,23 +92,6 @@ private fun symbolKind(d: KtNamedDeclaration): SymbolKind = else -> throw IllegalArgumentException("Unexpected symbol $d") } -private fun location(d: KtNamedDeclaration): Location? = - try { - val content = d.containingFile?.text - val locationInContent = (d.nameIdentifier?.textRange ?: d.textRange) - if (content != null && locationInContent != null) { - Location(d.containingFile.toURIString(), range(content, locationInContent)) - } else { - null - } - } catch (e: Exception) { - null - } - - -private fun workspaceSymbolLocation(d: KtNamedDeclaration): WorkspaceSymbolLocation = - WorkspaceSymbolLocation(d.containingFile.toURIString()) - private fun symbolContainer(d: KtNamedDeclaration): String? = d.parents .filterIsInstance() From fb2550bf01e6335915fd03031a0eb6ee5730fc6a Mon Sep 17 00:00:00 2001 From: Lorenz Bateman Date: Thu, 4 Apr 2024 15:45:13 +0200 Subject: [PATCH 4/4] Requested changes 1) pass client capabilities into KotlinWorkspaceService 2) Moved SymbolResolveSupport to a new file and added extension property to ClientCapabilities for it 3) Added a test for the location fetching required by the symbol resolve client capability --- .../kotlin/org/javacs/kt/Configuration.kt | 12 +------ .../org/javacs/kt/KotlinLanguageServer.kt | 7 +--- .../org/javacs/kt/KotlinWorkspaceService.kt | 8 ++++- .../javacs/kt/symbols/SymbolResolveSupport.kt | 13 +++++++ .../kotlin/org/javacs/kt/symbols/Symbols.kt | 35 ++++++++----------- .../org/javacs/kt/WorkspaceSymbolsTest.kt | 29 +++++++++++++++ 6 files changed, 65 insertions(+), 39 deletions(-) create mode 100644 server/src/main/kotlin/org/javacs/kt/symbols/SymbolResolveSupport.kt diff --git a/server/src/main/kotlin/org/javacs/kt/Configuration.kt b/server/src/main/kotlin/org/javacs/kt/Configuration.kt index 284e9a439..f5188e7c0 100644 --- a/server/src/main/kotlin/org/javacs/kt/Configuration.kt +++ b/server/src/main/kotlin/org/javacs/kt/Configuration.kt @@ -39,15 +39,6 @@ public data class CompilerConfiguration( val jvm: JVMConfiguration = JVMConfiguration() ) -public data class SymbolResolveSupport( - val enabled: Boolean = false, - val properties: List = emptyList() -) - -public data class WorkspaceConfiguration( - var symbolResolveSupport: SymbolResolveSupport = SymbolResolveSupport() -) - public data class IndexingConfiguration( /** Whether an index of global symbols should be built in the background. */ var enabled: Boolean = true @@ -116,6 +107,5 @@ public data class Configuration( val indexing: IndexingConfiguration = IndexingConfiguration(), val externalSources: ExternalSourcesConfiguration = ExternalSourcesConfiguration(), val inlayHints: InlayHintsConfiguration = InlayHintsConfiguration(), - val formatting: FormattingConfiguration = FormattingConfiguration(), - val workspace: WorkspaceConfiguration = WorkspaceConfiguration() + 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 950b01085..54605ef09 100644 --- a/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt +++ b/server/src/main/kotlin/org/javacs/kt/KotlinLanguageServer.kt @@ -108,7 +108,7 @@ class KotlinLanguageServer( serverCapabilities.renameProvider = Either.forRight(RenameOptions(false)) } - config.workspace.symbolResolveSupport = clientHasWorkspaceSymbolResolveSupport(clientCapabilities) + workspaces.initialize(clientCapabilities) @Suppress("DEPRECATION") val folders = params.workspaceFolders?.takeIf { it.isNotEmpty() } @@ -143,11 +143,6 @@ class KotlinLanguageServer( InitializeResult(serverCapabilities, serverInfo) } - private fun clientHasWorkspaceSymbolResolveSupport(clientCapabilities: ClientCapabilities) = - clientCapabilities?.workspace?.symbol?.resolveSupport?.properties?.let { properties -> - if (properties.size > 0) SymbolResolveSupport(true, properties) else null - } ?: SymbolResolveSupport(false, emptyList()) - private fun connectLoggingBackend() { val backend: (LogMessage) -> Unit = { client.logMessage(MessageParams().apply { diff --git a/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt b/server/src/main/kotlin/org/javacs/kt/KotlinWorkspaceService.kt index c610e9e82..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(!config.workspace.symbolResolveSupport.enabled, 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 0f88496b3..4c760c609 100644 --- a/server/src/main/kotlin/org/javacs/kt/symbols/Symbols.kt +++ b/server/src/main/kotlin/org/javacs/kt/symbols/Symbols.kt @@ -34,10 +34,10 @@ private fun doDocumentSymbols(element: PsiElement): List { } ?: children } -fun workspaceSymbols(locationRequired: Boolean, query: String, sp: SourcePath): List = +fun workspaceSymbols(query: String, sp: SourcePath, locationRequired: Boolean): List = doWorkspaceSymbols(sp) .filter { containsCharactersInOrder(it.name!!, query, false) } - .mapNotNull(workspaceSymbol(locationRequired)) + .mapNotNull { workspaceSymbol(it, locationRequired) } .toList() private fun doWorkspaceSymbols(sp: SourcePath): Sequence = @@ -57,29 +57,22 @@ private fun pickImportantElements(node: PsiElement, includeLocals: Boolean): KtN else -> null } -private fun workspaceSymbol(locationRequired: Boolean): (KtNamedDeclaration) -> WorkspaceSymbol? { - fun location(d: KtNamedDeclaration): Location? { - val content = d.containingFile?.text - val locationInContent = (d.nameIdentifier?.textRange ?: d.textRange) - return if (content != null && locationInContent != null) { - Location(d.containingFile.toURIString(), range(content, locationInContent)) - } else { - null - } - } - - return { d -> - d.name?.let { name -> - val location: Either? = if (locationRequired) { - location(d)?.let { l -> Either.forLeft(l) } +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 { - Either.forRight(WorkspaceSymbolLocation(d.containingFile.toURIString())) + null } - - location?.let { WorkspaceSymbol(name, symbolKind(d), it, symbolContainer(d)) } + } else { + d.containingFile?.let { Either.forRight(WorkspaceSymbolLocation(it.toURIString())) } } + + location?.let { WorkspaceSymbol(name, symbolKind(d), it, symbolContainer(d)) } } -} private fun symbolKind(d: KtNamedDeclaration): SymbolKind = when (d) { 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) + } }