From e10b821843e5aeb8da124ccab767494c28e8aa94 Mon Sep 17 00:00:00 2001 From: Andrey Zaytsev Date: Wed, 4 Jun 2025 17:34:14 +0200 Subject: [PATCH] [analyzer] move `lsp.protocol` to `community/fleet` to enable gradle dependency on it from fleet GitOrigin-RevId: 00d0fa952106348cd0ec9c2b48f3b34d2e1e28ee --- .idea/modules.xml | 1 + fleet/lsp.protocol/BUILD.bazel | 28 + fleet/lsp.protocol/fleet.lsp.protocol.iml | 37 + .../jetbrains/lsp/implementation/AsyncUtil.kt | 54 + .../jetbrains/lsp/implementation/LspClient.kt | 16 + .../lsp/implementation/LspException.kt | 24 + .../lsp/implementation/LspHandlers.kt | 100 ++ .../jetbrains/lsp/implementation/streaming.kt | 65 ++ .../jetbrains/lsp/implementation/tcpServer.kt | 163 +++ .../jetbrains/lsp/implementation/withLsp.kt | 320 ++++++ .../com/jetbrains/lsp/protocol/ApplyEdit.kt | 38 + .../lsp/protocol/ClientCapabilities.kt | 41 + .../ClientFileOperationsCapabilities.kt | 41 + .../lsp/protocol/ClientGeneralCapabilities.kt | 50 + .../lsp/protocol/ClientWindowCapabilities.kt | 38 + .../protocol/ClientWorkspaceCapabilities.kt | 105 ++ .../com/jetbrains/lsp/protocol/CodeAction.kt | 292 ++++++ .../com/jetbrains/lsp/protocol/Completion.kt | 691 +++++++++++++ .../com/jetbrains/lsp/protocol/Declaration.kt | 49 + .../com/jetbrains/lsp/protocol/Definition.kt | 44 + .../com/jetbrains/lsp/protocol/Diagnostics.kt | 356 +++++++ .../jetbrains/lsp/protocol/DocumentSync.kt | 232 +++++ .../lsp/protocol/EnumAsIntSerializer.kt | 23 + .../lsp/protocol/EnumAsNameSerializer.kt | 37 + .../jetbrains/lsp/protocol/ExecuteCommand.kt | 36 + .../main/com/jetbrains/lsp/protocol/Hover.kt | 36 + .../com/jetbrains/lsp/protocol/Initialize.kt | 126 +++ .../com/jetbrains/lsp/protocol/JsonRpc.kt | 183 ++++ .../main/com/jetbrains/lsp/protocol/LSP.kt | 945 ++++++++++++++++++ .../com/jetbrains/lsp/protocol/LogMessage.kt | 71 ++ .../com/jetbrains/lsp/protocol/LogTrace.kt | 22 + .../com/jetbrains/lsp/protocol/Notebooks.kt | 393 ++++++++ .../jetbrains/lsp/protocol/SemanticTokens.kt | 54 + .../lsp/protocol/ServerCapabilities.kt | 459 +++++++++ .../com/jetbrains/lsp/protocol/Shutdown.kt | 10 + .../TextDocumentClientCapabilities.kt | 211 ++++ .../com/jetbrains/lsp/protocol/Workspace.kt | 54 + .../com/jetbrains/lsp/protocol/references.kt | 28 + .../com/jetbrains/lsp/protocol/symbols.kt | 237 +++++ 39 files changed, 5710 insertions(+) create mode 100644 fleet/lsp.protocol/BUILD.bazel create mode 100644 fleet/lsp.protocol/fleet.lsp.protocol.iml create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/AsyncUtil.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/LspClient.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/LspException.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/LspHandlers.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/streaming.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/tcpServer.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/withLsp.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ApplyEdit.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ClientCapabilities.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ClientFileOperationsCapabilities.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ClientGeneralCapabilities.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ClientWindowCapabilities.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ClientWorkspaceCapabilities.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/CodeAction.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Completion.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Declaration.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Definition.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Diagnostics.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/DocumentSync.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/EnumAsIntSerializer.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/EnumAsNameSerializer.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ExecuteCommand.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Hover.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Initialize.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/JsonRpc.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/LSP.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/LogMessage.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/LogTrace.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Notebooks.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/SemanticTokens.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ServerCapabilities.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Shutdown.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/TextDocumentClientCapabilities.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Workspace.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/references.kt create mode 100644 fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/symbols.kt diff --git a/.idea/modules.xml b/.idea/modules.xml index 6318bc0504cd..939bd95736a1 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -6,6 +6,7 @@ + diff --git a/fleet/lsp.protocol/BUILD.bazel b/fleet/lsp.protocol/BUILD.bazel new file mode 100644 index 000000000000..7ea5e943e775 --- /dev/null +++ b/fleet/lsp.protocol/BUILD.bazel @@ -0,0 +1,28 @@ +### auto-generated section `build fleet.lsp.protocol` start +load("//build:compiler-options.bzl", "create_kotlinc_options") +load("@rules_jvm//:jvm.bzl", "jvm_library") + +create_kotlinc_options( + name = "custom", + context_receivers = True, + opt_in = [ + "kotlinx.coroutines.ExperimentalCoroutinesApi", + "kotlinx.serialization.ExperimentalSerializationApi", + ] +) + +jvm_library( + name = "lsp.protocol", + module_name = "fleet.lsp.protocol", + visibility = ["//visibility:public"], + srcs = glob(["src/main/**/*.kt", "src/main/**/*.java"], allow_empty = True, exclude = ["**/module-info.java"]), + kotlinc_opts = ":custom", + deps = [ + "@lib//:kotlin-stdlib", + "@lib//:kotlinx-coroutines-core", + "@lib//:kotlinx-serialization-core", + "@lib//:kotlinx-serialization-json", + "//fleet/util/core", + ] +) +### auto-generated section `build fleet.lsp.protocol` end \ No newline at end of file diff --git a/fleet/lsp.protocol/fleet.lsp.protocol.iml b/fleet/lsp.protocol/fleet.lsp.protocol.iml new file mode 100644 index 000000000000..7584b3276f5c --- /dev/null +++ b/fleet/lsp.protocol/fleet.lsp.protocol.iml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + $KOTLIN_BUNDLED$/lib/kotlinx-serialization-compiler-plugin.jar + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/AsyncUtil.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/AsyncUtil.kt new file mode 100644 index 000000000000..444daf8bd72e --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/AsyncUtil.kt @@ -0,0 +1,54 @@ +package com.jetbrains.lsp.implementation + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancelAndJoin +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.ReceiveChannel +import kotlinx.coroutines.channels.SendChannel +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.job + +internal suspend fun T.use(body: suspend CoroutineScope.(T) -> R): R { + return try { + coroutineScope { body(this@use) } + } finally { + cancelAndJoin() + } +} + +internal fun Channel.split(): Pair, ReceiveChannel> = this to this + +internal fun channels( + capacity: Int = Channel.RENDEZVOUS, + onBufferOverlow: BufferOverflow = BufferOverflow.SUSPEND, +): Pair, ReceiveChannel> = Channel(capacity, onBufferOverflow = onBufferOverlow).split() + +internal inline fun SendChannel.use(block: (SendChannel) -> R): R { + var cause: Throwable? = null + try { + return block(this) + } catch (ex: Throwable) { + cause = ex + throw ex + } finally { + close(cause) + } +} + +suspend fun withSupervisor(body: suspend CoroutineScope.(scope: CoroutineScope) -> T): T { + val context = currentCoroutineContext() + val supervisorJob = SupervisorJob(context.job) + return try { + coroutineScope { + body(CoroutineScope(context + supervisorJob)) + } + } + finally { + supervisorJob.cancelAndJoin() + } + } + \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/LspClient.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/LspClient.kt new file mode 100644 index 000000000000..2dfb21bc5971 --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/LspClient.kt @@ -0,0 +1,16 @@ +package com.jetbrains.lsp.implementation + +import com.jetbrains.lsp.protocol.NotificationType +import com.jetbrains.lsp.protocol.RequestType + +interface LspClient { + suspend fun request( + requestType: RequestType, + params: Params, + ): Result + + fun notify( + notificationType: NotificationType, + params: Params, + ) +} \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/LspException.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/LspException.kt new file mode 100644 index 000000000000..325e0a66379a --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/LspException.kt @@ -0,0 +1,24 @@ +package com.jetbrains.lsp.implementation + +import com.jetbrains.lsp.protocol.RequestType + +class LspException internal constructor( + message: String, + val errorCode: Int, + val payload: Any?, + cause: Throwable?, +) : Exception(message, cause) + +fun throwLspError( + requestType: RequestType<*, *, E>, + message: String, + data: E, + code: Int, + cause: Throwable? = null, +): Nothing = + throw LspException( + message = message, + errorCode = code, + payload = data, + cause = cause + ) diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/LspHandlers.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/LspHandlers.kt new file mode 100644 index 000000000000..c6aecf7b78b6 --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/LspHandlers.kt @@ -0,0 +1,100 @@ +package com.jetbrains.lsp.implementation + +import com.jetbrains.lsp.protocol.LSP +import com.jetbrains.lsp.protocol.LSP.ProgressNotificationType +import com.jetbrains.lsp.protocol.RequestType +import com.jetbrains.lsp.protocol.NotificationType +import com.jetbrains.lsp.protocol.ProgressParams +import com.jetbrains.lsp.protocol.WorkDoneProgress +import com.jetbrains.lsp.protocol.WorkDoneProgressParams +import kotlinx.coroutines.CoroutineScope +import kotlinx.serialization.KSerializer + +interface LspHandlers { + fun requestHandler( + requestTypeName: String, + ): LspRequestHandler<*, *, *>? + + fun notificationHandler( + notificationTypeName: String, + ): LspNotificationHandler<*>? +} + +interface LspHandlersBuilder { + fun request( + requestType: RequestType, + handler: suspend context(LspHandlerContext) CoroutineScope.(Request) -> Response, + ) + + fun notification( + notifcationType: NotificationType, + handler: suspend CoroutineScope.(Notification) -> Unit, + ) +} + + +class LspHandlerContext( + val lspClient: LspClient, +) + +fun

LspClient.reportProgress( + params: WorkDoneProgressParams, + progress: P, +) { + val token = params.workDoneToken ?: return + notify(ProgressNotificationType, ProgressParams(token, LSP.json.encodeToJsonElement(WorkDoneProgress.serializer(), progress))) +} + +fun LspClient.reportProgressMessage( + params: WorkDoneProgressParams, + message: String, +) { + val report = WorkDoneProgress.Report(message = message) + reportProgress(params, report) +} + + +class LspRequestHandler( + val requestType: RequestType, + val handler: suspend context(LspHandlerContext) CoroutineScope.(Params) -> Result, +) + +class LspNotificationHandler( + val notificationType: NotificationType, + val handler: suspend CoroutineScope.(Params) -> Unit, +) + +fun lspHandlers(builder: LspHandlersBuilder.() -> Unit): LspHandlers { + val requests = mutableMapOf>() + val notifications = mutableMapOf>() + + object : LspHandlersBuilder { + override fun request( + requestType: RequestType, + handler: suspend context(LspHandlerContext) CoroutineScope.(Request) -> Response, + ) { + requests[requestType.method] = LspRequestHandler(requestType, handler) + } + + override fun notification( + notifcationType: NotificationType, + handler: suspend CoroutineScope.(Notification) -> Unit, + ) { + notifications[notifcationType.method] = LspNotificationHandler(notifcationType, handler) + } + }.builder() + + return object : LspHandlers { + @Suppress("UNCHECKED_CAST") + override fun requestHandler( + requestType: String, + ): LspRequestHandler<*, *, *>? = + requests[requestType] + + @Suppress("UNCHECKED_CAST") + override fun notificationHandler( + notificationType: String, + ): LspNotificationHandler<*>? = + notifications[notificationType] + } +} \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/streaming.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/streaming.kt new file mode 100644 index 000000000000..27dd855b5ac5 --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/streaming.kt @@ -0,0 +1,65 @@ +package com.jetbrains.lsp.implementation + +import com.jetbrains.lsp.protocol.LSP +import com.jetbrains.lsp.protocol.LSP.ProgressNotificationType +import com.jetbrains.lsp.protocol.ProgressParams +import com.jetbrains.lsp.protocol.ProgressToken +import fleet.util.async.chunkedByTimeout +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.toList +import kotlinx.serialization.KSerializer +import kotlinx.serialization.builtins.ListSerializer + +/** + * Streams results using partial results protocol if possible, otherwise collects all results and returns them directly. + * + * @param lspClient The LSP client to send notifications to + * @param partialResultToken Token for partial results reporting, if null direct response will be used + * @param resultSerializer Serializer for the result type + * @return Empty list if streaming was used, otherwise collected results + */ +suspend fun Flow.streamResultsIfPossibleOrRespondDirectly( + lspClient: LspClient, + partialResultToken: ProgressToken?, + resultSerializer: KSerializer, +): List { + if (partialResultToken == null) { + return this.toList() + } else { + streamResults(lspClient, partialResultToken, resultSerializer) + // protocol requires returning an empty result when progress is used + return emptyList() + } +} + +/** + * Streams results to the LSP client using progress notifications. + * + * @param lspClient The LSP client to send notifications to + * @param partialResultToken Token for partial results reporting + * @param resultSerializer Serializer for the result type + */ +suspend fun Flow.streamResults( + lspClient: LspClient, + partialResultToken: ProgressToken, + resultSerializer: KSerializer, +) { + // `chunkedByTimeout` to ensure that we do not spam the client with too many progress notifications. + chunkedByTimeout(BUFFERING_TIME_BETWEEN_SENDING_BATCH_RESULTS_MS) + .collect { partialResult -> + if (partialResult.isEmpty()) return@collect + lspClient.notify( + ProgressNotificationType, + ProgressParams( + partialResultToken, + LSP.json.encodeToJsonElement(ListSerializer(resultSerializer), partialResult), + ) + ) + } +} + + +/** + * Default time interval in milliseconds between sending batched results to the client. + */ +private const val BUFFERING_TIME_BETWEEN_SENDING_BATCH_RESULTS_MS = 50L diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/tcpServer.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/tcpServer.kt new file mode 100644 index 000000000000..d070406a4222 --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/tcpServer.kt @@ -0,0 +1,163 @@ +package com.jetbrains.lsp.implementation + +import com.jetbrains.lsp.protocol.Initialize +import com.jetbrains.lsp.protocol.InitializeResult +import com.jetbrains.lsp.protocol.* +import fleet.util.logging.logger +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.channels.ReceiveChannel +import kotlinx.coroutines.channels.SendChannel +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.consumeEach +import kotlinx.serialization.json.JsonElement +import java.io.InputStream +import java.io.OutputStream +import java.io.ByteArrayOutputStream +import java.net.ServerSocket + +suspend fun tcpServer(port: Int = 0, server: suspend CoroutineScope.(InputStream, OutputStream) -> Unit) { + ServerSocket(port).use { serverSocket -> + LOG.info("Server is listening on port ${serverSocket.localPort}") + supervisorScope { + while (true) { + val clientSocket = runInterruptible(Dispatchers.IO) { + serverSocket.accept() + } + LOG.info("A new client connected ${clientSocket.inetAddress}:${clientSocket.port}") + launch(start = CoroutineStart.ATOMIC) { + clientSocket.use { + val input = clientSocket.getInputStream() + val output = clientSocket.getOutputStream() + coroutineScope { + server(input, output) + } + } + LOG.info("Client disconnected ${clientSocket.inetAddress}:${clientSocket.port}") + } + } + } + } +} + +private val LOG = logger() + +private fun InputStream.readLine(): String { + val buffer = ByteArrayOutputStream() + var prevChar: Int? = null + while (true) { + val currentChar = this.read() + if (currentChar == -1 || (prevChar == '\r'.code && currentChar == '\n'.code)) { + break + } + if (prevChar != null) { + buffer.write(prevChar) + } + prevChar = currentChar + } + return buffer.toString(Charsets.UTF_8).trim() +} + +private fun InputStream.readFrame(): JsonElement? { + var contentLength = -1 + var readSomething = false + while (true) { + val line = this.readLine() + if (line.isEmpty()) break + readSomething = true + val (key, value) = line.split(':').map { it.trim() } + if (key == "Content-Length") { + contentLength = value.toInt() + } + } + if (!readSomething) return null + if (contentLength == -1) throw IllegalStateException("Content-Length header not found") + val buf = readNBytes(contentLength) + return LSP.json.decodeFromString(JsonElement.serializer(), String(buf, Charsets.UTF_8)) +} + +private fun OutputStream.writeFrame(jsonElement: JsonElement) { + val str = LSP.json.encodeToString(JsonElement.serializer(), jsonElement) + val frameStr = buildString { + // protocol requires string length in bytes + val contentLengthInBytes = str.toByteArray(Charsets.UTF_8).size + append("Content-Length: $contentLengthInBytes\r\n") + append("Content-Type: application/json-rpc; charset=utf-8\r\n") + append("\r\n") + append(str) + } + write(frameStr.toByteArray(Charsets.UTF_8)) + flush() +} + +suspend fun withBaseProtocolFraming( + reader: InputStream, + writer: OutputStream, + body: suspend CoroutineScope.( + incoming: ReceiveChannel, + outgoing: SendChannel, + ) -> Unit, +) { + coroutineScope { + val (incomingSender, incomingReceiver) = channels() + val (outgoingSender, outgoingReceiver) = channels(Channel.UNLIMITED) + launch { + launch { + incomingSender.use { + while (true) { + val frame = runInterruptible(Dispatchers.IO) { + reader.readFrame() + } + // println("received frame $frame") + if (frame == null) { + incomingSender.close() + break + } + incomingSender.send(frame) + } + } + } + launch { + outgoingReceiver.consumeEach { frame -> + // println("sending frame $frame") + runInterruptible(Dispatchers.IO) { + writer.writeFrame(frame) + } + } + } + }.use { + body(incomingReceiver, outgoingSender) + } + } +} + +fun main() { + val handler = lspHandlers { + request(Initialize) { initParams -> + InitializeResult( + capabilities = ServerCapabilities( + textDocumentSync = TextDocumentSync(TextDocumentSyncKind.Incremental), + ), + serverInfo = InitializeResult.ServerInfo( + name = "IntelliJ Analyzer", + version = "1.0" + ), + ) + } + notification(DocumentSync.DidOpen) { didOpen -> + println("didOpen: $didOpen") + } + notification(DocumentSync.DidChange) { didChange -> + println("didChange: $didChange") + } + } + runBlocking(Dispatchers.Default) { + tcpServer(9999) { input, output -> + withBaseProtocolFraming(input, output) { incoming, outgoing -> + withLsp(incoming, outgoing, handler) { lsp -> + awaitCancellation() + } + } + } + } +} \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/withLsp.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/withLsp.kt new file mode 100644 index 000000000000..affd0f27ff97 --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/withLsp.kt @@ -0,0 +1,320 @@ +package com.jetbrains.lsp.implementation + +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.* +import kotlinx.serialization.json.* +import kotlinx.serialization.* +import com.jetbrains.lsp.protocol.* +import fleet.util.logging.logger +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.atomic.AtomicInteger +import kotlinx.serialization.builtins.serializer +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + +private class ProtocolViolation(message: String) : Exception(message) + +private fun isRequest(jsonMessage: JsonObject): Boolean = + jsonMessage.containsKey("id") && jsonMessage.containsKey("method") + +private fun isResponse(jsonMessage: JsonObject): Boolean = + jsonMessage.containsKey("id") && !jsonMessage.containsKey("method") + +private fun isNotification(jsonMessage: JsonObject): Boolean = + jsonMessage.containsKey("method") && !jsonMessage.containsKey("id") + +private class OutgoingRequest( + val deferred: CompletableDeferred, + val requestType: RequestType<*, *, *>, +) + +suspend fun withLsp( + incoming: ReceiveChannel, + outgoing: SendChannel, + handlers: LspHandlers, + createCoroutineContext: (LspClient) -> CoroutineContext = { EmptyCoroutineContext }, + body: suspend CoroutineScope.(LspClient) -> Unit, +) { + coroutineScope { + val outgoingRequests = ConcurrentHashMap() + val idGen = AtomicInteger(0) + val lspClient = object : LspClient { + override suspend fun request( + requestType: RequestType, + params: Params, + ): Result { + val id = StringOrInt(JsonPrimitive(idGen.incrementAndGet())) + val deferred = CompletableDeferred() + outgoingRequests[id] = OutgoingRequest(deferred, requestType) + val request = RequestMessage( + jsonrpc = "2.0", + id = id, + method = requestType.method, + params = LSP.json.encodeToJsonElement(requestType.paramsSerializer, params) + ) + outgoing.send(LSP.json.encodeToJsonElement(RequestMessage.serializer(), request)) + @Suppress("UNCHECKED_CAST") + return try { + deferred.await() as Result + } catch (c: CancellationException) { + notify(LSP.CancelNotificationType, CancelParams(id)) + outgoingRequests.remove(id) + throw c + } + } + + override fun notify( + notificationType: NotificationType, + params: Params, + ) { + val notification = NotificationMessage( + jsonrpc = "2.0", + method = notificationType.method, + params = LSP.json.encodeToJsonElement(notificationType.paramsSerializer, params) + ) + outgoing.trySend( + LSP.json.encodeToJsonElement( + NotificationMessage.serializer(), + notification + ) + ).getOrThrow() + } + } + + val lspHandlerContext = LspHandlerContext(lspClient) + + launch(createCoroutineContext(lspClient)) { + withSupervisor { supervisor -> + val incomingRequestsJobs = ConcurrentHashMap() + incoming.consumeEach { jsonMessage -> + when { + jsonMessage !is JsonObject || jsonMessage["jsonrpc"] != JsonPrimitive("2.0") -> { + throw ProtocolViolation("not json rpc message: $jsonMessage") + } + + isRequest(jsonMessage) -> { + val request = LSP.json.decodeFromJsonElement(RequestMessage.serializer(), jsonMessage) + supervisor.launch(start = CoroutineStart.ATOMIC) { + val maybeHandler = handlers.requestHandler(request.method) + runCatching { + val handler = requireNotNull(maybeHandler) { + "no handler for request: ${request.method}" + } + val deserializedParams = request.params?.let { params -> + LSP.json.decodeFromJsonElement(handler.requestType.paramsSerializer, params) + } + val result = (handler as LspRequestHandler).handler( + lspHandlerContext, + this, + deserializedParams + ) + LSP.json.encodeToJsonElement( + serializer = handler.requestType.resultSerializer as KSerializer, + value = result + ) + }.fold( + onSuccess = { result -> + ResponseMessage( + jsonrpc = "2.0", + id = request.id, + result = result, + error = null + ) + }, + onFailure = { x -> + val responseError = when (x) { + is CancellationException -> { + ResponseError( + code = ErrorCodes.RequestCancelled, + message = "cancelled", + ) + } + + is LspException -> { + ResponseError( + code = x.errorCode, + message = x.message ?: x::class.simpleName ?: "unknown error", + data = runCatching { + val errorSerializer = requireNotNull(maybeHandler) { + "we could not have caught LspException if we didn't find the handler" + }.requestType.errorSerializer as KSerializer + LSP.json.encodeToJsonElement( + serializer = errorSerializer, + value = x.payload + ) + }.getOrNull() + ) + } + + else -> { + LOG.error(x) + + ResponseError( + code = ErrorCodes.RequestFailed, + message = x.message ?: x::class.simpleName ?: "unknown error", + ) + } + } + ResponseMessage( + jsonrpc = "2.0", + id = request.id, + result = null, + error = responseError + ) + } + ).let { responseMessage -> + outgoing.send(LSP.json.encodeToJsonElement(ResponseMessage.serializer(), responseMessage)) + } + }.also { requestJob -> + incomingRequestsJobs.put(request.id, requestJob) + requestJob.invokeOnCompletion { + incomingRequestsJobs.remove(request.id) + } + } + } + + isResponse(jsonMessage) -> { + val response = LSP.json.decodeFromJsonElement(ResponseMessage.serializer(), jsonMessage) + when (val client = outgoingRequests.remove(response.id)) { + null -> { + // request was cancelled + } + + else -> { + client.deferred.let { deferred -> + when (val error = response.error) { + null -> { + val result = response.result?.let { result -> + runCatching { + LSP.json.decodeFromJsonElement( + client.requestType.resultSerializer as KSerializer, + result + ) + }.onFailure { error -> + if (error is CancellationException) throw error + LOG.error(error) + }.getOrNull() + } + deferred.complete(result) + } + + else -> { + deferred.completeExceptionally( + LspException( + message = error.message, + errorCode = error.code, + cause = null, + payload = error.data?.let { data -> + runCatching { + LSP.json.decodeFromJsonElement( + client.requestType.errorSerializer as KSerializer, + data + ) + }.onFailure { decodingError -> + if (decodingError is CancellationException) throw decodingError + LOG.error(decodingError) + }.getOrNull() + } + ) + ) + } + } + } + } + } + } + + isNotification(jsonMessage) -> { + //todo: separate queue for notifications + val notification = LSP.json.decodeFromJsonElement(NotificationMessage.serializer(), jsonMessage) + when { + notification.method == LSP.CancelNotificationType.method -> { + val params = LSP.json.decodeFromJsonElement(CancelParams.serializer(), notification.params!!) + incomingRequestsJobs.remove(params.id)?.cancel() + } + + else -> + runCatching { + when (val handler = handlers.notificationHandler(notification.method)) { + null -> + LOG.debug("no handler for notification: ${notification.method}") + + else -> { + val deserializedParams = notification.params?.let { params -> + LSP.json.decodeFromJsonElement(handler.notificationType.paramsSerializer, params) + } + (handler as LspNotificationHandler).handler(this, deserializedParams) + } + } + }.onFailure { error -> + if (error is CancellationException) throw error + LOG.error(error) + } + } + } + + else -> { + throw ProtocolViolation("not json rpc message: $jsonMessage") + } + } + } + } + }.use { + body(lspClient) + } + } +} + +fun main() { + runBlocking(Dispatchers.Default) { + val clientToServer = Channel(Channel.UNLIMITED) + val serverToClient = Channel(Channel.UNLIMITED) + val HelloRequestType = RequestType("hello", String.serializer(), String.serializer(), Unit.serializer()) + val HangRequestType = RequestType("hand", Unit.serializer(), Unit.serializer(), Unit.serializer()) + val PrintHelloNotification = NotificationType("printHello", String.serializer()) + withLsp( + incoming = clientToServer, + outgoing = serverToClient, + handlers = lspHandlers { + request(HelloRequestType) { str -> + "Hello, $str" + } + notification(PrintHelloNotification) { str -> + println("server: $str") + } + request(HangRequestType) { + try { + awaitCancellation() + } catch (c: CancellationException) { + println("cancelled by client") + throw c + } + } + } + ) { server -> + withLsp( + incoming = serverToClient, + outgoing = clientToServer, + handlers = lspHandlers { + notification(PrintHelloNotification) { str -> + println("client: $str") + } + } + ) { client -> + println(client.request(HelloRequestType, "World")) + client.notify(PrintHelloNotification, "Hello World") + server.notify(PrintHelloNotification, "Hello World") + val hangingRequestJob = launch { + client.request(HangRequestType, Unit) + } + delay(100) + hangingRequestJob.cancel() + println("request cancelled") + delay(100) + println("quitting") + } + } + } +} + +private val LOG = logger() \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ApplyEdit.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ApplyEdit.kt new file mode 100644 index 000000000000..2cda985a3ac9 --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ApplyEdit.kt @@ -0,0 +1,38 @@ +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.serializer + +@Serializable +data class ApplyWorkspaceEditParams( + val label: String?, + val edit: WorkspaceEdit, +) + +@Serializable +data class ApplyWorkspaceEditResult( + /** + * Indicates whether the edit was applied or not. + */ + val applied: Boolean, + + /** + * An optional textual description for why the edit was not applied. + * This may be used by the server for diagnostic logging or to provide + * a suitable error for a request that triggered the edit. + */ + val failureReason: String?, + + /** + * Depending on the client's failure handling strategy `failedChange` + * might contain the index of the change that failed. This property is + * only available if the client signals a `failureHandling` strategy + * in its client capabilities. + */ + val failedChanges: Int?, +) + +object ApplyEditRequests { + val ApplyEdit: RequestType = + RequestType("workspace/applyEdit", ApplyWorkspaceEditParams.serializer(), ApplyWorkspaceEditResult.serializer(), Unit.serializer()) +} \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ClientCapabilities.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ClientCapabilities.kt new file mode 100644 index 000000000000..b765db08c39a --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ClientCapabilities.kt @@ -0,0 +1,41 @@ +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement + +@Serializable +data class ClientCapabilities( + /** + * Workspace specific client capabilities. + */ + val workspace: ClientWorkspaceCapabilities? = null, + + /** + * Text document specific client capabilities. + */ + val textDocument: TextDocumentClientCapabilities? = null, + + /** + * Capabilities specific to the notebook document support. + * + * @since 3.17.0 + */ + val notebookDocument: NotebookDocumentClientCapabilities? = null, + + /** + * Window specific client capabilities. + */ + val window: ClientWindowCapabilities? = null, + + /** + * General client capabilities. + * + * @since 3.16.0 + */ + val general: ClientGeneralCapabilities? = null, + + /** + * Experimental client capabilities. + */ + val experimental: JsonElement? = null, +) \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ClientFileOperationsCapabilities.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ClientFileOperationsCapabilities.kt new file mode 100644 index 000000000000..d2f66b61bfec --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ClientFileOperationsCapabilities.kt @@ -0,0 +1,41 @@ +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.Serializable + +@Serializable +data class ClientFileOperationsCapabilities( + /** + * Whether the client supports dynamic registration for file requests/notifications. + */ + val dynamicRegistration: Boolean? = null, + + /** + * The client has support for sending didCreateFiles notifications. + */ + val didCreate: Boolean? = null, + + /** + * The client has support for sending willCreateFiles requests. + */ + val willCreate: Boolean? = null, + + /** + * The client has support for sending didRenameFiles notifications. + */ + val didRename: Boolean? = null, + + /** + * The client has support for sending willRenameFiles requests. + */ + val willRename: Boolean? = null, + + /** + * The client has support for sending didDeleteFiles notifications. + */ + val didDelete: Boolean? = null, + + /** + * The client has support for sending willDeleteFiles requests. + */ + val willDelete: Boolean? = null +) \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ClientGeneralCapabilities.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ClientGeneralCapabilities.kt new file mode 100644 index 000000000000..3d5abbea2c4a --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ClientGeneralCapabilities.kt @@ -0,0 +1,50 @@ +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.Serializable + +@Serializable +data class ClientGeneralCapabilities( + /** + * Client capability that signals how the client handles stale requests + * (e.g. a request for which the client will not process the response + * anymore since the information is outdated). + * + * @since 3.17.0 + */ + val staleRequestSupport: StaleRequestSupport? = null, + + /** + * Client capabilities specific to regular expressions. + * + * @since 3.16.0 + */ + val regularExpressions: RegularExpressionsClientCapabilities? = null, + + /** + * Client capabilities specific to the client's markdown parser. + * + * @since 3.16.0 + */ + val markdown: MarkdownClientCapabilities? = null, + + /** + * The position encodings supported by the client. Client and server + * have to agree on the same position encoding to ensure that offsets + * (e.g. character position in a line) are interpreted the same on both + * sides. + * + * To keep the protocol backwards compatible the following applies: if + * the value 'utf-16' is missing from the array of position encodings, + * servers can assume that the client supports UTF-16. UTF-16 is + * therefore a mandatory encoding. + * + * If omitted it defaults to ['utf-16']. + * + * Implementation considerations: since the conversion from one encoding + * into another requires the content of the file/line, the conversion + * is best done where the file is read which is usually on the server side. + * + * @since 3.17.0 + */ + val positionEncodings: List? = null +) \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ClientWindowCapabilities.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ClientWindowCapabilities.kt new file mode 100644 index 000000000000..7fc3be144bf5 --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ClientWindowCapabilities.kt @@ -0,0 +1,38 @@ +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement + +@Serializable +data class ClientWindowCapabilities( + /** + * It indicates whether the client supports server-initiated progress using + * the `window/workDoneProgress/create` request. + * + * The capability also controls whether the client supports handling + * of progress notifications. If set servers are allowed to report a + * `workDoneProgress` property in the request-specific server capabilities. + * + * @since 3.15.0 + */ + val workDoneProgress: Boolean? = null, + + /** + * Capabilities specific to the showMessage request. + * + * @since 3.16.0 + */ + val showMessage: ShowMessageRequestClientCapabilities? = null, + + /** + * Client capabilities for the show document request. + * + * @since 3.16.0 + */ + val showDocument: ShowDocumentClientCapabilities? = null +) + +typealias Unknown = JsonElement + +typealias ShowMessageRequestClientCapabilities = Unknown +typealias ShowDocumentClientCapabilities = Unknown \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ClientWorkspaceCapabilities.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ClientWorkspaceCapabilities.kt new file mode 100644 index 000000000000..d5b598e54793 --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ClientWorkspaceCapabilities.kt @@ -0,0 +1,105 @@ +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.Serializable + +@Serializable +data class ClientWorkspaceCapabilities( + /** + * The client supports applying batch edits + * to the workspace by supporting the request + * 'workspace/applyEdit' + */ + val applyEdit: Boolean? = null, + + /** + * Capabilities specific to `WorkspaceEdit`s + */ + val workspaceEdit: WorkspaceEditClientCapabilities? = null, + + /** + * Capabilities specific to the `workspace/didChangeConfiguration` notification. + */ + val didChangeConfiguration: DidChangeConfigurationClientCapabilities? = null, + + /** + * Capabilities specific to the `workspace/didChangeWatchedFiles` notification. + */ + val didChangeWatchedFiles: DidChangeWatchedFilesClientCapabilities? = null, + + /** + * Capabilities specific to the `workspace/symbol` request. + */ + val symbol: WorkspaceSymbolClientCapabilities? = null, + + /** + * Capabilities specific to the `workspace/executeCommand` request. + */ + val executeCommand: ExecuteCommandClientCapabilities? = null, + + /** + * The client has support for workspace folders. + * + * @since 3.6.0 + */ + val workspaceFolders: Boolean? = null, + + /** + * The client supports `workspace/configuration` requests. + * + * @since 3.6.0 + */ + val configuration: Boolean? = null, + + /** + * Capabilities specific to the semantic token requests scoped to the + * workspace. + * + * @since 3.16.0 + */ + val semanticTokens: SemanticTokensWorkspaceClientCapabilities? = null, + + /** + * Capabilities specific to the code lens requests scoped to the + * workspace. + * + * @since 3.16.0 + */ + val codeLens: CodeLensWorkspaceClientCapabilities? = null, + + /** + * The client has support for file requests/notifications. + * + * @since 3.16.0 + */ + val fileOperations: ClientFileOperationsCapabilities? = null, + + /** + * Client workspace capabilities specific to inline values. + * + * @since 3.17.0 + */ + val inlineValue: InlineValueWorkspaceClientCapabilities? = null, + + /** + * Client workspace capabilities specific to inlay hints. + * + * @since 3.17.0 + */ + val inlayHint: InlayHintWorkspaceClientCapabilities? = null, + + /** + * Client workspace capabilities specific to diagnostics. + * + * @since 3.17.0. + */ + val diagnostics: DiagnosticWorkspaceClientCapabilities? = null +) + +typealias DidChangeConfigurationClientCapabilities = Unknown +typealias DidChangeWatchedFilesClientCapabilities = Unknown +typealias WorkspaceSymbolClientCapabilities = Unknown +typealias ExecuteCommandClientCapabilities = Unknown +typealias SemanticTokensWorkspaceClientCapabilities = Unknown +typealias CodeLensWorkspaceClientCapabilities = Unknown +typealias InlineValueWorkspaceClientCapabilities = Unknown +typealias InlayHintWorkspaceClientCapabilities = Unknown \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/CodeAction.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/CodeAction.kt new file mode 100644 index 000000000000..f338e7779405 --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/CodeAction.kt @@ -0,0 +1,292 @@ +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.json.JsonElement + +@Serializable +data class CodeActionOptions( + /** + * CodeActionKinds that this server may return. + * + * The list of kinds may be generic, such as `CodeActionKind.Refactor`, + * or the server may list out every specific kind they provide. + */ + val codeActionKinds: List, + + /** + * The server provides support to resolve additional + * information for a code action. + * + * @since 3.16.0 + */ + val resolveProvider: Boolean?, + override val workDoneProgress: Boolean?, +) : WorkDoneProgressOptions + +/** + * The kind of a code action. + * + * Kinds are a hierarchical list of identifiers separated by `.`, + * e.g. `"refactor.extract.function"`. + * + * The set of kinds is open and client needs to announce the kinds it supports + * to the server during initialization. + */ +@Serializable(with = CodeActionKind.Serializer::class) +enum class CodeActionKind(val value: String) { + + /** + * Empty kind. + */ + Empty(""), + + /** + * Base kind for quickfix actions: "quickfix". + */ + QuickFix("quickfix"), + + /** + * Base kind for refactoring actions: "refactor". + */ + Refactor("refactor"), + + /** + * Base kind for refactoring extraction actions: "refactor.extract". + * + * Example extract actions: + * + * - Extract method + * - Extract function + * - Extract variable + * - Extract interface from class + * - ... + */ + RefactorExtract("refactor.extract"), + + /** + * Base kind for refactoring inline actions: "refactor.inline". + * + * Example inline actions: + * + * - Inline function + * - Inline variable + * - Inline constant + * - ... + */ + RefactorInline("refactor.inline"), + + /** + * Base kind for refactoring rewrite actions: "refactor.rewrite". + * + * Example rewrite actions: + * + * - Convert JavaScript function to class + * - Add or remove parameter + * - Encapsulate field + * - Make method static + * - Move method to base class + * - ... + */ + RefactorRewrite("refactor.rewrite"), + + /** + * Base kind for source actions: `source`. + * + * Source code actions apply to the entire file. + */ + Source("source"), + + /** + * Base kind for an organize imports source action: + * `source.organizeImports`. + */ + SourceOrganizeImports("source.organizeImports"), + + /** + * Base kind for a "fix all" source action: `source.fixAll`. + * + * "Fix all" actions automatically fix errors that have a clear fix that + * do not require user input. They should not suppress errors or perform + * unsafe fixes such as generating new types or classes. + * + * @since 3.17.0 + */ + SourceFixAll("source.fixAll"), + + ; + + class Serializer : EnumAsNameSerializer(CodeActionKind::class, CodeActionKind::value) +} + +/** + * Parameters for a code action request. + */ +@Serializable +data class CodeActionParams( + /** + * Params for the CodeActionRequest + */ + val textDocument: TextDocumentIdentifier, + + /** + * The range for which the command was invoked. + */ + val range: Range, + + /** + * Context carrying additional information. + */ + val context: CodeActionContext, + override val workDoneToken: ProgressToken?, + override val partialResultToken: ProgressToken?, +) : WorkDoneProgressParams, PartialResultParams { + fun shouldProvideKind(kind: CodeActionKind): Boolean = + context.only == null || kind in context.only +} + +/** + * Contains additional diagnostic information about the context in which + * a code action is run. + */ +@Serializable +data class CodeActionContext( + /** + * An array of diagnostics known on the client side overlapping the range + * provided to the `textDocument/codeAction` request. They are provided so + * that the server knows which errors are currently presented to the user + * for the given range. There is no guarantee that these accurately reflect + * the error state of the resource. The primary parameter + * to compute code actions is the provided range. + */ + val diagnostics: List, + + /** + * Requested kind of actions to return. + * + * Actions not of this kind are filtered out by the client before being + * shown. So servers can omit computing them. + */ + val only: List? = null, + + /** + * The reason why code actions were requested. + * + * @since 3.17.0 + */ + val triggerKind: CodeActionTriggerKind? = null +) + +@Serializable(with = CodeActionTriggerKind.Serializer::class) +enum class CodeActionTriggerKind(val value: Int) { + Invoked(1), + Automatic(2), + + ; + + class Serializer : EnumAsIntSerializer( + serialName = CodeActionTriggerKind::class.simpleName!!, + serialize = CodeActionTriggerKind::value, + deserialize = { CodeActionTriggerKind.entries[it - 1] }, + ) +} + +/** + * A code action represents a change that can be performed in code, e.g., to fix + * a problem or to refactor code. + * + * A CodeAction must set either `edit` and/or a `command`. If both are supplied, + * the `edit` is applied first, then the `command` is executed. + */ +@Serializable +data class CodeAction( + /** + * A short, human-readable, title for this code action. + */ + val title: String, + + /** + * The kind of the code action. + * + * Used to filter code actions. + */ + val kind: CodeActionKind? = null, + + /** + * The diagnostics that this code action resolves. + */ + val diagnostics: List? = null, + + /** + * Marks this as a preferred action. Preferred actions are used by the + * `auto fix` command and can be targeted by keybindings. + * + * A quick fix should be marked preferred if it properly addresses the + * underlying error. A refactoring should be marked preferred if it is the + * most reasonable choice of actions to take. + * + * @since 3.15.0 + */ + val isPreferred: Boolean? = null, + + /** + * Marks that the code action cannot currently be applied. + * + * Clients should follow the following guidelines regarding disabled code + * actions: + * + * - Disabled code actions are not shown in automatic lightbulbs code + * action menus. + * + * - Disabled actions are shown as faded out in the code action menu when + * the user requests a more specific type of code action, such as + * refactorings. + * + * - If the user has a keybinding that auto-applies a code action and only + * a disabled code action is returned, the client should show the user + * an error message with `reason` in the editor. + * + * @since 3.16.0 + */ + val disabled: Disabled? = null, + + /** + * The workspace edit this code action performs. + */ + val edit: WorkspaceEdit? = null, + + /** + * A command this code action executes. If a code action + * provides an edit and a command, first the edit is + * executed and then the command. + */ + val command: Command? = null, + + /** + * A data entry field that is preserved on a code action between + * a `textDocument/codeAction` and a `codeAction/resolve` request. + * + * @since 3.16.0 + */ + val data: JsonElement? = null +) { + /** + * Represents a disabled state for a CodeAction with a reason. + */ + @Serializable + data class Disabled( + /** + * Human-readable description of why the code action is currently disabled. + * + * This is displayed in the code actions UI. + */ + val reason: String + ) +} + + +object CodeActions { + val CodeActionRequest: RequestType, Unit> = + RequestType("textDocument/codeAction", CodeActionParams.serializer(), ListSerializer(CodeAction.serializer()), Unit.serializer()) +} diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Completion.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Completion.kt new file mode 100644 index 000000000000..863b1167d0e4 --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Completion.kt @@ -0,0 +1,691 @@ +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.* +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.json.JsonPrimitive + +@Serializable +data class CompletionClientCapabilities( + /** + * Whether completion supports dynamic registration. + */ + val dynamicRegistration: Boolean? = null, + + /** + * The client supports the following `CompletionItem` specific + * capabilities. + */ + val completionItem: CompletionItemCapabilities? = null, + + val completionItemKind: CompletionItemKindCapabilities? = null, + + /** + * The client supports to send additional context information for a + * `textDocument/completion` request. + */ + val contextSupport: Boolean? = null, + + /** + * The client's default when the completion item doesn't provide a + * `insertTextMode` property. + * + * @since 3.17.0 + */ + val insertTextMode: InsertTextMode? = null, + + /** + * The client supports the following `CompletionList` specific + * capabilities. + * + * @since 3.17.0 + */ + val completionList: CompletionListCapabilities? = null, +) { + @Serializable + data class CompletionItemCapabilities( + /** + * Client supports snippets as insert text. + * + * A snippet can define tab stops and placeholders with `$1`, `$2` + * and `${3:foo}`. `$0` defines the final tab stop, it defaults to + * the end of the snippet. Placeholders with equal identifiers are + * linked, that is typing in one will update others too. + */ + val snippetSupport: Boolean? = null, + + /** + * Client supports commit characters on a completion item. + */ + val commitCharactersSupport: Boolean? = null, + + /** + * Client supports the follow content formats for the documentation + * property. The order describes the preferred format of the client. + */ + val documentationFormat: List? = null, + + /** + * Client supports the deprecated property on a completion item. + */ + val deprecatedSupport: Boolean? = null, + + /** + * Client supports the preselect property on a completion item. + */ + val preselectSupport: Boolean? = null, + + /** + * Client supports the tag property on a completion item. Clients + * supporting tags have to handle unknown tags gracefully. Clients + * especially need to preserve unknown tags when sending a completion + * item back to the server in a resolve call. + * + * @since 3.15.0 + */ + val tagSupport: TagSupportCapabilities? = null, + + /** + * Client supports insert replace edit to control different behavior if + * a completion item is inserted in the text or should replace text. + * + * @since 3.16.0 + */ + val insertReplaceSupport: Boolean? = null, + + /** + * Indicates which properties a client can resolve lazily on a + * completion item. Before version 3.16.0 only the predefined properties + * `documentation` and `detail` could be resolved lazily. + * + * @since 3.16.0 + */ + val resolveSupport: ResolveSupportCapabilities? = null, + + /** + * The client supports the `insertTextMode` property on + * a completion item to override the whitespace handling mode + * as defined by the client (see `insertTextMode`). + * + * @since 3.16.0 + */ + val insertTextModeSupport: InsertTextModeSupportCapabilities? = null, + + /** + * The client has support for completion item label + * details (see also `CompletionItemLabelDetails`). + * + * @since 3.17.0 + */ + val labelDetailsSupport: Boolean? = null, + ) { + @Serializable + data class TagSupportCapabilities( + /** + * The tags supported by the client. + */ + val valueSet: List, + ) + + @Serializable + data class ResolveSupportCapabilities( + /** + * The properties that a client can resolve lazily. + */ + val properties: List, + ) + + @Serializable + data class InsertTextModeSupportCapabilities( + val valueSet: List, + ) + } + + @Serializable + data class CompletionItemKindCapabilities( + /** + * The completion item kind values the client supports. When this + * property exists the client also guarantees that it will + * handle values outside its set gracefully and falls back + * to a default value when unknown. + * + * If this property is not present the client only supports + * the completion items kinds from `Text` to `Reference` as defined in + * the initial version of the protocol. + */ + val valueSet: List? = null, + ) + + @Serializable + data class CompletionListCapabilities( + /** + * The client supports the following itemDefaults on + * a completion list. + * + * The value lists the supported property names of the + * `CompletionList.itemDefaults` object. If omitted + * no properties are supported. + * + * @since 3.17.0 + */ + val itemDefaults: List? = null, + ) +} + +@Serializable +sealed interface CompletionOptions { + /** + * The additional characters, beyond the defaults provided by the client (typically + * [a-zA-Z]), that should automatically trigger a completion request. For example + * `.` in JavaScript represents the beginning of an object property or method and is + * thus a good candidate for triggering a completion request. + * + * Most tools trigger a completion request automatically without explicitly + * requesting it using a keyboard shortcut (e.g. Ctrl+Space). Typically they + * do so when the user starts to type an identifier. For example if the user + * types `c` in a JavaScript file code complete will automatically pop up + * present `console` besides others as a completion item. Characters that + * make up identifiers don't need to be listed here. + */ + val triggerCharacters: List? + + /** + * The list of all possible characters that commit a completion. This field + * can be used if clients don't support individual commit characters per + * completion item. See client capability + * `completion.completionItem.commitCharactersSupport`. + * + * If a server provides both `allCommitCharacters` and commit characters on + * an individual completion item the ones on the completion item win. + * + * @since 3.2.0 + */ + val allCommitCharacters: List? + + /** + * The server provides support to resolve additional + * information for a completion item. + */ + val resolveProvider: Boolean? + + /** + * The server supports the following `CompletionItem` specific + * capabilities. + * + * @since 3.17.0 + */ + val completionItem: CompletionItemCapabilities? + + @Serializable + data class CompletionItemCapabilities( + /** + * The server has support for completion item label + * details (see also `CompletionItemLabelDetails`) when receiving + * a completion item in a resolve call. + * + * @since 3.17.0 + */ + val labelDetailsSupport: Boolean? = null, + ) +} + +@Serializable +data class CompletionRegistrationOptionsImpl( + override val triggerCharacters: List?, + override val allCommitCharacters: List?, + override val resolveProvider: Boolean?, + override val completionItem: CompletionOptions.CompletionItemCapabilities?, +) : CompletionOptions + +@Serializable +data class CompletionRegistrationOptions( + override val documentSelector: DocumentSelector?, + override val triggerCharacters: List?, + override val allCommitCharacters: List?, + override val resolveProvider: Boolean?, + override val completionItem: CompletionOptions.CompletionItemCapabilities?, +) : TextDocumentRegistrationOptions, CompletionOptions + +@Serializable +data class CompletionParams( + val textDocument: TextDocumentIdentifier, + val position: Position, + val workDoneToken: ProgressToken? = null, + val partialResultToken: ProgressToken? = null, + /** + * The completion context. This is only available if the client specifies + * to send this using the client capability + * `completion.contextSupport === true` + */ + val context: CompletionContext? = null, +) + +class CompletionTriggerKindSerializer : EnumAsIntSerializer( + serialName = "CompletionTriggerKind", + serialize = CompletionTriggerKind::value, + deserialize = { CompletionTriggerKind.values().get(it - 1) }, +) + +@Serializable(CompletionTriggerKindSerializer::class) +enum class CompletionTriggerKind(val value: Int) { + Invoked(1), + TriggerCharacter(2), + TriggerForIncompleteCompletions(3); +} + +@Serializable +data class CompletionContext( + /** + * How the completion was triggered. + */ + val triggerKind: CompletionTriggerKind, + + /** + * The trigger character (a single character) that has triggered code + * completion. Is null if `triggerKind` is not `TriggerCharacter`. + */ + val triggerCharacter: String? = null, +) + +@Serializable +data class CompletionList( + /** + * This list is not complete. Further typing should result in recomputing + * this list. + * + * Recomputed lists have all their items replaced (not appended) in the + * incomplete completion sessions. + */ + val isIncomplete: Boolean, + + /** + * In many cases the items of an actual completion result share the same + * value for properties like `commitCharacters` or the range of a text + * edit. A completion list can therefore define item defaults which will + * be used if a completion item itself doesn't specify the value. + * + * If a completion list specifies a default value and a completion item + * also specifies a corresponding value the one from the item is used. + * + * Servers are only allowed to return default values if the client + * signals support for this via the `completionList.itemDefaults` + * capability. + * + * @since 3.17.0 + */ + val itemDefaults: ItemDefaults? = null, + + /** + * The completion items. + */ + val items: List, +) { + + companion object { + val EMPTY_COMPLETE = CompletionList( + isIncomplete = false, + itemDefaults = null, + items = emptyList(), + ) + } + + @Serializable + data class ItemDefaults( + /** + * A default commit character set. + * + * @since 3.17.0 + */ + val commitCharacters: List? = null, + + /** + * A default edit range. + * + * @since 3.17.0 + */ + val editRange: EditRange? = null, + + /** + * A default insert text format. + * + * @since 3.17.0 + */ + val insertTextFormat: InsertTextFormat? = null, + + /** + * A default insert text mode. + * + * @since 3.17.0 + */ + val insertTextMode: InsertTextMode? = null, + + /** + * A default data value. + * + * @since 3.17.0 + */ + val data: JsonElement? = null, + ) + + @Serializable + data class EditRange( + val insert: Range, + val replace: Range, + ) +} + +/** + * A special text edit to provide an insert and a replace operation. + * + * @since 3.16.0 + */ +@Serializable +data class InsertReplaceEdit( + /** + * The string to be inserted. + */ + val newText: String, + /** + * The range if the insert is requested + */ + val insert: Range, + /** + * The range if the replace is requested. + */ + val replace: Range, +) + +class InsertTextFormatSerializer : EnumAsIntSerializer( + serialName = "CompletionTriggerKind", + serialize = InsertTextFormat::value, + deserialize = { InsertTextFormat.values().get(it - 1) }, +) + +@Serializable(InsertTextFormatSerializer::class) +enum class InsertTextFormat(val value: Int) { + PlainText(1), + Snippet(2); +} + +class CompletionItemTagSerializer : EnumAsIntSerializer( + serialName = "CompletionTriggerKind", + serialize = CompletionItemTag::value, + deserialize = { CompletionItemTag.values().get(it - 1) }, +) + +/** + * Completion item tags are extra annotations that tweak the rendering of a + * completion item. + * + * @since 3.15.0 + */ +@Serializable(CompletionItemTagSerializer::class) +enum class CompletionItemTag(val value: Int) { + /** + * Render a completion as obsolete, usually using a strike-out. + */ + Deprecated(1); +} + +class InsertTextModeSerializer : EnumAsIntSerializer( + serialName = "CompletionTriggerKind", + serialize = InsertTextMode::value, + deserialize = { InsertTextMode.values().get(it - 1) }, +) + +/** + * How whitespace and indentation is handled during completion + * item insertion. + * + * @since 3.16.0 + */ +@Serializable(InsertTextModeSerializer::class) +enum class InsertTextMode(val value: Int) { + /** + * The insertion or replace strings is taken as it is. If the + * value is multi line the lines below the cursor will be + * inserted using the indentation defined in the string value. + * The client will not apply any kind of adjustments to the + * string. + */ + AsIs(1), + + /** + * The editor adjusts leading whitespace of new lines so that + * they match the indentation up to the cursor of the line for + * which the item is accepted. + * + * Consider a line like this: <2tabs><3tabs>foo. Accepting a + * multi line completion item is indented using 2 tabs and all + * following lines inserted will be indented using 2 tabs as well. + */ + AdjustIndentation(2); +} + +@Serializable +data class CompletionItemLabelDetails( + /** + * An optional string which is rendered less prominently directly after + * [CompletionItem.label], without any spacing. Should be + * used for function signatures or type annotations. + * + * @since 3.17.0 + */ + val detail: String? = null, + + /** + * An optional string which is rendered less prominently after + * [CompletionItemLabelDetails.detail]. Should be used for fully qualified + * names or file paths. + * + * @since 3.17.0 + */ + val description: String? = null, +) + +@Serializable +data class CompletionItem( + /** + * The label of this completion item. + * + * The label property is also by default the text that + * is inserted when selecting this completion. + * + * If label details are provided the label itself should + * be an unqualified name of the completion item. + */ + val label: String, + + /** + * Additional details for the label + * + * @since 3.17.0 + */ + val labelDetails: CompletionItemLabelDetails? = null, + + /** + * The kind of this completion item. Based on the kind + * an icon is chosen by the editor. The standardized set + * of available values is defined in `CompletionItemKind`. + */ + val kind: CompletionItemKind? = null, + + /** + * Tags for this completion item. + * + * @since 3.15.0 + */ + val tags: List? = null, + + /** + * A human-readable string with additional information + * about this item, like type or symbol information. + */ + val detail: String? = null, + + /** + * A human-readable string that represents a doc-comment. + */ + val documentation: StringOrMarkupContent? = null, + + /** + * Indicates if this item is deprecated. + * + * @deprecated Use `tags` instead if supported. + */ + @Deprecated("Use `tags` instead if supported.") val deprecated: Boolean? = null, + + /** + * Select this item when showing. + * + * *Note* that only one completion item can be selected and that the + * tool / client decides which item that is. The rule is that the *first* + * item of those that match best is selected. + */ + val preselect: Boolean? = null, + + /** + * A string that should be used when comparing this item + * with other items. When omitted the label is used + * as the sort text for this item. + */ + val sortText: String? = null, + + /** + * A string that should be used when filtering a set of + * completion items. When omitted the label is used as the + * filter text for this item. + */ + val filterText: String? = null, + + /** + * A string that should be inserted into a document when selecting + * this completion. When omitted the label is used as the insert text + * for this item. + */ + val insertText: String? = null, + + /** + * The format of the insert text. + */ + val insertTextFormat: InsertTextFormat? = null, + + /** + * How whitespace and indentation is handled during completion + * item insertion. + * + * @since 3.16.0 + * @since 3.17.0 - support for `textDocument.completion.insertTextMode` + */ + val insertTextMode: InsertTextMode? = null, + + /** + * An edit which is applied to a document when selecting this completion. + */ + val textEdit: TextEditOrInsertReplaceEdit? = null, + + /** + * The edit text used if the completion item is part of a CompletionList. + * + * @since 3.17.0 + */ + val textEditText: String? = null, + + /** + * An optional array of additional text edits that are applied when + * selecting this completion. + */ + val additionalTextEdits: List? = null, + + /** + * An optional set of characters that when pressed while this completion is + * active will accept it first and then type that character. + */ + val commitCharacters: List? = null, + + /** + * An optional command that is executed *after* inserting this completion. + */ + val command: Command? = null, + + /** + * A data entry field that is preserved on a completion item between + * a completion and a completion resolve request. + */ + val data: JsonElement? = null, +) + +// todo: custom serializer needed +@Serializable +@JvmInline +value class TextEditOrInsertReplaceEdit private constructor(val edit: JsonElement) { + constructor(textEdit: TextEdit) : this(LSP.json.encodeToJsonElement(TextEdit.serializer(), textEdit)) + constructor(insertReplaceEdit: InsertReplaceEdit) : this( + LSP.json.encodeToJsonElement( + InsertReplaceEdit.serializer(), + insertReplaceEdit + ) + ) +} + +//todo: custom serializer needed +@Serializable +@JvmInline +value class StringOrMarkupContent private constructor(val content: JsonElement) { + constructor(content: String) : this(JsonPrimitive(content)) + constructor(content: MarkupContent) : this(LSP.json.encodeToJsonElement(MarkupContent.serializer(), content)) +} + +class CompletionItemKindSerializer : EnumAsIntSerializer( + serialName = "CompletionTriggerKind", + serialize = CompletionItemKind::kind, + deserialize = { CompletionItemKind.values().get(it - 1) }, +) + +/** + * The kind of a completion entry. + */ +@Serializable(CompletionItemKindSerializer::class) +enum class CompletionItemKind(val kind: Int) { + Text(1), + Method(2), + Function(3), + Constructor(4), + Field(5), + Variable(6), + Class(7), + Interface(8), + Module(9), + Property(10), + Unit(11), + Value(12), + Enum(13), + Keyword(14), + Snippet(15), + Color(16), + File(17), + Reference(18), + Folder(19), + EnumMember(20), + Constant(21), + Struct(22), + Event(23), + Operator(24), + TypeParameter(25); +} + +val CompletionRequestType: RequestType = + RequestType( + "textDocument/completion", + CompletionParams.serializer(), + CompletionList.serializer(), + Unit.serializer() + ) + +val CompletionResolveRequestType: RequestType = + RequestType( + "completionItem/resolve", + CompletionItem.serializer(), + CompletionItem.serializer(), + Unit.serializer() + ) \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Declaration.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Declaration.kt new file mode 100644 index 000000000000..bc7d2a7731b0 --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Declaration.kt @@ -0,0 +1,49 @@ +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.builtins.nullable +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject + +@Serializable +data class DeclarationClientCapabilities( + /** + * Whether declaration supports dynamic registration. If this is set to + * `true` the client supports the new `DeclarationRegistrationOptions` + * return value for the corresponding server capability as well. + */ + val dynamicRegistration: Boolean? = null, + + /** + * The client supports additional metadata in the form of declaration links. + */ + val linkSupport: Boolean? = null, +) + +interface DeclarationOptions : WorkDoneProgressOptions + +@Serializable +data class DeclarationRegistrationOptions( + override val workDoneProgress: Boolean?, + override val documentSelector: DocumentSelector?, + override val id: String?, +) : DeclarationOptions, + TextDocumentRegistrationOptions, + StaticRegistrationOptions + +@Serializable +data class DeclarationParams( + override val textDocument: TextDocumentIdentifier, + override val position: Position, + override val workDoneToken: ProgressToken?, + override val partialResultToken: ProgressToken?, +) : TextDocumentPositionParams, + WorkDoneProgressParams, + PartialResultParams + + +val DeclarationRequestType: RequestType, Unit> = + RequestType("textDocument/declaration", DeclarationParams.serializer(), ListSerializer(Location.serializer()), Unit.serializer()) \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Definition.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Definition.kt new file mode 100644 index 000000000000..a50c25681f62 --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Definition.kt @@ -0,0 +1,44 @@ +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.builtins.nullable +import kotlinx.serialization.builtins.serializer + +/** + * Client capabilities for the definition feature. + */ +@Serializable +data class DefinitionClientCapabilities( + /** + * Whether definition supports dynamic registration. + */ + val dynamicRegistration: Boolean? = null, + + /** + * The client supports additional metadata in the form of definition links. + * + * @since 3.14.0 + */ + val linkSupport: Boolean? = null +) + +interface DefinitionOptions : WorkDoneProgressOptions + +@Serializable +data class DefinitionRegistrationOptions( + override val documentSelector: DocumentSelector? = null, + override val workDoneProgress: Boolean? = null +) : TextDocumentRegistrationOptions, DefinitionOptions + + +@Serializable +data class DefinitionParams( + override val textDocument: TextDocumentIdentifier, + override val position: Position, + override val workDoneToken: ProgressToken?, + override val partialResultToken: ProgressToken? +) : WorkDoneProgressParams, PartialResultParams, TextDocumentPositionParams + +val DefinitionRequestType: RequestType/*TODO LocationLink should be here as more flexible*/, Unit> = + RequestType("textDocument/definition", DefinitionParams.serializer(), ListSerializer(Location.serializer()), Unit.serializer()) \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Diagnostics.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Diagnostics.kt new file mode 100644 index 000000000000..ca9fe3c441ed --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Diagnostics.kt @@ -0,0 +1,356 @@ +@file:Suppress("PublicApiImplicitType") + +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerialName +import kotlinx.serialization.builtins.nullable +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.json.JsonElement + +/** + * Represents a diagnostic, such as a compiler error or warning. Diagnostic objects are only valid in the scope of a resource. + */ +@Serializable +data class Diagnostic( + /** + * The range at which the message applies. + */ + val range: Range, + + /** + * The diagnostic's severity. To avoid interpretation mismatches when a + * server is used with different clients it is highly recommended that + * servers always provide a severity value. If omitted, it’s recommended + * for the client to interpret it as an Error severity. + */ + val severity: DiagnosticSeverity? = null, + + /** + * The diagnostic's code, which might appear in the user interface. + */ + val code: StringOrInt? = null, // integer or string equivalent + + /** + * An optional property to describe the error code. + * + * @since 3.16.0 + */ + val codeDescription: CodeDescription? = null, + + /** + * A human-readable string describing the source of this + * diagnostic, e.g. 'typescript' or 'super lint'. + */ + val source: String? = null, + + /** + * The diagnostic's message. + */ + val message: String, + + /** + * Additional metadata about the diagnostic. + * + * @since 3.15.0 + */ + val tags: List? = null, + + /** + * An array of related diagnostic information, e.g. when symbol-names within + * a scope collide all definitions can be marked via this property. + */ + val relatedInformation: List? = null, + + /** + * A data entry field that is preserved between a + * `textDocument/publishDiagnostics` notification and + * `textDocument/codeAction` request. + * + * @since 3.16.0 + */ + val data: JsonElement? = null, +) + +class DiagnosticSeveritySerializer : EnumAsIntSerializer( + serialName = "CompletionTriggerKind", + serialize = DiagnosticSeverity::value, + deserialize = { DiagnosticSeverity.entries.get(it - 1) }, +) + +@Serializable(DiagnosticSeveritySerializer::class) +enum class DiagnosticSeverity(val value: Int) { + Error(1), + Warning(2), + Information(3), + Hint(4) +} + +class DiagnosticTagSerializer : EnumAsIntSerializer( + serialName = "CompletionTriggerKind", + serialize = DiagnosticTag::value, + deserialize = { DiagnosticTag.entries.get(it - 1) }, +) + +/** + * The diagnostic tags. + * + * @since 3.15.0 + */ +@Serializable(DiagnosticTagSerializer::class) +enum class DiagnosticTag(val value: Int) { + /** + * Unused or unnecessary code. + * + * Clients are allowed to render diagnostics with this tag faded out + * instead of having an error squiggle. + */ + Unnecessary(1), + + /** + * Deprecated or obsolete code. + * + * Clients are allowed to rendered diagnostics with this tag strike through. + */ + Deprecated(2) +} + +/** + * Represents a related message and source code location for a diagnostic. + * This should be used to point to code locations that cause or are related to + * a diagnostics, e.g when duplicating a symbol in a scope. + */ +@Serializable +data class DiagnosticRelatedInformation( + /** + * The location of this related diagnostic information. + */ + val location: Location, + + /** + * The message of this related diagnostic information. + */ + val message: String, +) + +/** + * Structure to capture a description for an error code. + * + * @since 3.16.0 + */ +@Serializable +data class CodeDescription( + /** + * An URI to open with more information about the diagnostic error. + */ + val href: URI, +) + +@Serializable +data class PublishDiagnosticsClientCapabilities( + /** + * Whether the client accepts diagnostics with related information. + */ + val relatedInformation: Boolean? = null, + + /** + * Client supports the tag property to provide meta data about a diagnostic. + * Clients supporting tags have to handle unknown tags gracefully. + * + * @since 3.15.0 + */ + val tagSupport: TagSupport? = null, + + /** + * Whether the client interprets the version property of the + * `textDocument/publishDiagnostics` notification's parameter. + * + * @since 3.15.0 + */ + val versionSupport: Boolean? = null, + + /** + * Client supports a codeDescription property. + * + * @since 3.16.0 + */ + val codeDescriptionSupport: Boolean? = null, + + /** + * Whether code actions support the `data` property which is + * preserved between a `textDocument/publishDiagnostics` and + * `textDocument/codeAction` request. + * + * @since 3.16.0 + */ + val dataSupport: Boolean? = null, +) { + @Serializable + data class TagSupport( + /** + * The tags supported by the client. + */ + val valueSet: List, + ) +} + +@Serializable +data class PublishDiagnosticsParams( + /** + * The URI for which diagnostic information is reported. + */ + val uri: DocumentUri, + + /** + * The version number of the document the diagnostics are published for. + * + * Optional, since 3.15.0 + */ + val version: Int? = null, + + /** + * An array of diagnostic information items. + */ + val diagnostics: List, +) + +/** + * Client capabilities specific to diagnostic pull requests. + * + * @since 3.17.0 + */ +@Serializable +data class DiagnosticClientCapabilities( + /** + * Whether implementation supports dynamic registration. If this is set to + * `true` the client supports the new + * `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` + * return value for the corresponding server capability as well. + */ + val dynamicRegistration: Boolean? = null, + + /** + * Whether the client supports related documents for document diagnostic + * pulls. + */ + val relatedDocumentSupport: Boolean? = null, +) + +@Serializable +data class DiagnosticOptions( + /** + * An optional identifier under which the diagnostics are + * managed by the client. + */ + val identifier: String?, + /** + * Whether the language has inter file dependencies meaning that + * editing code in one file can result in a different diagnostic + * set in another file. Inter file dependencies are common for + * most programming languages and typically uncommon for linters. + */ + val interFileDependencies: Boolean?, + /** + * The server provides support for workspace diagnostics as well. + */ + val workspaceDiagnostics: Boolean?, + override val workDoneProgress: Boolean?, +) : WorkDoneProgressOptions + +/** + * Parameters of the document diagnostic request. + * + * @since 3.17.0 + */ +@Serializable +data class DocumentDiagnosticParams( + /** + * The text document. + */ + val textDocument: TextDocumentIdentifier, + + /** + * The additional identifier provided during registration. + */ + val identifier: String? = null, + + /** + * The result id of a previous response if provided. + */ + val previousResultId: String? = null, + + override val workDoneToken: ProgressToken?, + + override val partialResultToken: ProgressToken?, + + ) : WorkDoneProgressParams, PartialResultParams + +@Serializable +enum class DocumentDiagnosticReportKind { + @SerialName("full") + Full, + + @SerialName("unchanged") + Unchanged +} + +@Serializable +data class DocumentDiagnosticReport( + val kind: DocumentDiagnosticReportKind, + val resultId: String?, + val items: List?, + val relatedDocuments: Map?, +) { + companion object { + val EMPTY_FULL = DocumentDiagnosticReport( + DocumentDiagnosticReportKind.Full, + resultId = null, + items = emptyList(), + relatedDocuments = null, + ) + } +} + +@Serializable +data class DiagnosticServerCancellationData( + val retriggerRequest: Boolean, +) + +@Serializable +data class DiagnosticWorkspaceClientCapabilities( + /** + * Whether the client implementation supports a refresh request sent from + * the server to the client. + * + * Note that this event is global and will force the client to refresh all + * pulled diagnostics currently shown. It should be used with absolute care + * and is useful for situations where a server, for example, detects a project- + * wide change that requires such a calculation. + */ + val refreshSupport: Boolean? = null, +) + +object Diagnostics { + /** + * Diagnostics notifications are sent from the server to the client to signal results of validation runs. + * + * Diagnostics are “owned” by the server so it is the server’s responsibility to clear them if necessary. The following rule is used for VS Code servers that generate diagnostics: + * if a language is single file only (for example HTML) then diagnostics are cleared by the server when the file is closed. Please note that open / close events don’t necessarily reflect what the user sees in the user interface. + * These events are ownership events. So with the current version of the specification it is possible that problems are not cleared although the file is not visible in the user interface since the client has not closed the file yet. + * if a language has a project system (for example C#) diagnostics are not cleared when a file closes. When a project is opened all diagnostics for all files are recomputed (or read from a cache). + * When a file changes it is the server’s responsibility to re-compute diagnostics and push them to the client. If the computed set is empty it has to push the empty array to clear former diagnostics. + * Newly pushed diagnostics always replace previously pushed diagnostics. + * There is no merging that happens on the client side. + */ + val PublishDiagnosticsNotificationType: NotificationType = + NotificationType("textDocument/publishDiagnostics", PublishDiagnosticsParams.serializer()) + + val DocumentDiagnosticRequestType: RequestType = + RequestType( + "textDocument/diagnostic", DocumentDiagnosticParams.serializer(), DocumentDiagnosticReport.serializer(), + DiagnosticServerCancellationData.serializer().nullable + ) + + val Refresh: RequestType = + RequestType("textDocument/diagnostics/refresh", Unit.serializer(), Unit.serializer(), Unit.serializer()) +} \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/DocumentSync.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/DocumentSync.kt new file mode 100644 index 000000000000..5538816c2fdd --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/DocumentSync.kt @@ -0,0 +1,232 @@ +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.json.JsonElement + +@Serializable +data class TextDocumentSyncClientCapabilities( + /** + * Whether text document synchronization supports dynamic registration. + */ + val dynamicRegistration: Boolean? = null, + + /** + * The client supports sending will save notifications. + */ + val willSave: Boolean? = null, + + /** + * The client supports sending a will save request and + * waits for a response providing text edits which will + * be applied to the document before it is saved. + */ + val willSaveWaitUntil: Boolean? = null, + + /** + * The client supports did save notifications. + */ + val didSave: Boolean? = null +) + + +//todo: custom serializer +@Serializable +@JvmInline +value class TextDocumentSync(val value: JsonElement) { + constructor(kind: TextDocumentSyncKind) : this( + LSP.json.encodeToJsonElement( + TextDocumentSyncKind.serializer(), + kind + ) + ) +} // TextDocumentSyncOptions | TextDocumentSyncKind + +@Serializable +data class TextDocumentSyncOptions( + /** + * Open and close notifications are sent to the server. If omitted open + * close notifications should not be sent. + */ + val openClose: Boolean, + + /** + * Change notifications are sent to the server. See + * TextDocumentSyncKind.None, TextDocumentSyncKind.Full and + * TextDocumentSyncKind.Incremental. If omitted it defaults to + * TextDocumentSyncKind.None. + */ + val change: TextDocumentSyncKind?, + + /** + * If present will save notifications are sent to the server. If omitted + * the notification should not be sent. + */ + val willSave: Boolean?, + + /** + * If present will save wait until requests are sent to the server. If + * omitted the request should not be sent. + */ + val willSaveWaitUntil: Boolean?, + + /** + * If present save notifications are sent to the server. If omitted the + * notification should not be sent. + */ + val save: Boolean?, +) + +class TextDocumentSyncKindSerializer : EnumAsIntSerializer( + serialName = "CompletionTriggerKind", + serialize = TextDocumentSyncKind::value, + deserialize = { TextDocumentSyncKind.entries.get(it) }, +) + +@Serializable(TextDocumentSyncKindSerializer::class) // todo: custom serializer +enum class TextDocumentSyncKind(val value: Int) { + None(0), + Full(1), + Incremental(2); +} + +@Serializable +data class DidOpenTextDocumentParams( + /** + * The document that was opened. + */ + val textDocument: TextDocumentItem, +) + +@Serializable +data class DidChangeTextDocumentParams( + /** + * The document that did change. The version number points + * to the version after all provided content changes have + * been applied. + */ + val textDocument: TextDocumentIdentifier, + + /** + * The actual content changes. The content changes describe single state + * changes to the document. So if there are two content changes c1 (at + * array index 0) and c2 (at array index 1) for a document in state S then + * c1 moves the document from S to S' and c2 from S' to S''. So c1 is + * computed on the state S and c2 is computed on the state S'. + * + * To mirror the content of a document using change events use the following + * approach: + * - start with the same initial content + * - apply the 'textDocument/didChange' notifications in the order you + * receive them. + * - apply the `TextDocumentContentChangeEvent`s in a single notification + * in the order you receive them. + */ + val contentChanges: List, +) + +/** + * An event describing a change to a text document. If only a text is provided + * it is considered to be the full content of the document. + */ +@Serializable +data class TextDocumentContentChangeEvent( + /** + * The range of the document that changed. + */ + val range: Range?, + /** + * The optional length of the range that got replaced. + * + * @deprecated use range instead. + */ + val rangeLength: Int?, + /** + * The new text for the provided range. + * if range is absent, it's the new text of the document. + */ + val text: String, +) + +/** + * The parameters sent in a will save text document notification. + */ +@Serializable +data class WillSaveTextDocumentParams( + /** + * The document that will be saved. + */ + val textDocument: TextDocumentIdentifier, + + /** + * The 'TextDocumentSaveReason'. + */ + val reason: TextDocumentSaveReason, +) + +class TextDocumentSaveReasonSerializer : EnumAsIntSerializer( + serialName = "CompletionTriggerKind", + serialize = TextDocumentSaveReason::code, + deserialize = { TextDocumentSaveReason.entries.get(it - 1) }, +) + +/** + * Represents reasons why a text document is saved. + */ +@Serializable(TextDocumentSaveReasonSerializer::class) +enum class TextDocumentSaveReason(val code: Int) { + + /** + * Manually triggered, e.g. by the user pressing save, by starting + * debugging, or by an API call. + */ + Manual(1), + + /** + * Automatic after a delay. + */ + AfterDelay(2), + + /** + * When the editor lost focus. + */ + FocusOut(3) +} + +data class SaveOptions( + val includeText: Boolean? = null, +) + +@Serializable +data class DidSaveTextDocumentParams( + val textDocument: TextDocumentIdentifier, + val text: String? = null, +) + +@Serializable +data class DidCloseTextDocumentParams( + val textDocument: TextDocumentIdentifier, +) + +object DocumentSync { + val DidOpen: NotificationType = + NotificationType("textDocument/didOpen", DidOpenTextDocumentParams.serializer()) + + val DidChange: NotificationType = + NotificationType("textDocument/didChange", DidChangeTextDocumentParams.serializer()) + + val WillSaveWaitUntil: RequestType, Unit> = + RequestType( + "textDocument/willSaveWaitUntil", + WillSaveTextDocumentParams.serializer(), + ListSerializer(TextEdit.serializer()), + Unit.serializer() + ) + + val DidSave: NotificationType = + NotificationType("textDocument/didSave", DidSaveTextDocumentParams.serializer()) + + val DidClose: NotificationType = + NotificationType("textDocument/didClose", DidCloseTextDocumentParams.serializer()) +} \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/EnumAsIntSerializer.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/EnumAsIntSerializer.kt new file mode 100644 index 000000000000..dcc9d75ef3c1 --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/EnumAsIntSerializer.kt @@ -0,0 +1,23 @@ +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.descriptors.* + +open class EnumAsIntSerializer>( + serialName: String, + val serialize: (v: T) -> Int, + val deserialize: (v: Int) -> T +) : KSerializer { + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor(serialName, PrimitiveKind.INT) + + override fun serialize(encoder: Encoder, value: T) { + encoder.encodeInt(serialize(value)) + } + + override fun deserialize(decoder: Decoder): T { + val v = decoder.decodeInt() + return deserialize(v) + } +} \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/EnumAsNameSerializer.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/EnumAsNameSerializer.kt new file mode 100644 index 000000000000..e3ab08bd6d06 --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/EnumAsNameSerializer.kt @@ -0,0 +1,37 @@ +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlin.reflect.KClass +import kotlin.reflect.KProperty1 + +open class EnumAsNameSerializer>( + serialName: String, + val serialize: (v: T) -> String, + val deserialize: (v: String) -> T, +) : KSerializer { + + constructor( + enumClass: KClass, + field: KProperty1, + ) : this( + serialName = enumClass.simpleName!!, + serialize = { field.get(it) }, + deserialize = { name -> enumClass.java.enumConstants.first { field.get(it) == name } } + ) + + override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor(serialName, PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: T) { + encoder.encodeString(serialize(value)) + } + + override fun deserialize(decoder: Decoder): T { + val v = decoder.decodeString() + return deserialize(v) + } +} \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ExecuteCommand.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ExecuteCommand.kt new file mode 100644 index 000000000000..0fabd3cdaa1a --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ExecuteCommand.kt @@ -0,0 +1,36 @@ +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.json.JsonElement + +@Serializable +data class ExecuteCommandOptions( + val commands: List, +) + +/** + * Represents parameters for executing a command. + * Extends WorkDoneProgressParams. + */ +@Serializable +data class ExecuteCommandParams( + /** + * The identifier of the actual command handler. + */ + val command: String, + + /** + * Arguments that the command should be invoked with. + */ + val arguments: List? = null, + override val workDoneToken: ProgressToken?, +) : WorkDoneProgressParams + +object Commands { + val ExecuteCommand = RequestType( + "workspace/executeCommand", + ExecuteCommandParams.serializer(), + JsonElement.serializer(), Unit.serializer() + ) +} \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Hover.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Hover.kt new file mode 100644 index 000000000000..6f92bfd89ab6 --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Hover.kt @@ -0,0 +1,36 @@ +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.nullable +import kotlinx.serialization.builtins.serializer + +@Serializable +data class HoverParams( + override val textDocument: TextDocumentIdentifier, + override val position: Position, + override val workDoneToken: ProgressToken?, +) : TextDocumentPositionParams, WorkDoneProgressParams + + +/** + * The result of a hover request. + */ +@Serializable +data class Hover( + /** + * The hover's content + */ + val contents: MarkupContent, // actually, it's contents: `MarkedString | MarkedString[] | MarkupContent` but `MarkedString` is deprecated + + /** + * An optional range is a range inside a text document + * that is used to visualize a hover, e.g. by changing the background color. + */ + val range: Range?, +) + +/** + * The hover request is sent from the client to the server to request hover information at a given text document position. + */ +val HoverRequestType: RequestType = + RequestType("textDocument/hover", HoverParams.serializer(), Hover.serializer().nullable, Unit.serializer()) \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Initialize.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Initialize.kt new file mode 100644 index 000000000000..d3e2bcdee92d --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Initialize.kt @@ -0,0 +1,126 @@ +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.json.JsonElement + +val Initialize: RequestType = RequestType( + "initialize", + InitializeParams.serializer(), + InitializeResult.serializer(), + InitializeError.serializer() +) + +@Serializable +data class InitializeParams( + /** + * The process Id of the parent process that started the server. Is null if + * the process has not been started by another process. If the parent + * process is not alive then the server should exit (see exit notification) + * its process. + */ + val processId: Int?, + + /** + * Information about the client + * + * @since 3.15.0 + */ + val clientInfo: ClientInfo? = null, + + /** + * The locale the client is currently showing the user interface + * in. This must not necessarily be the locale of the operating + * system. + * + * Uses IETF language tags as the value's syntax + * (See https://en.wikipedia.org/wiki/IETF_language_tag) + * + * @since 3.16.0 + */ + val locale: String? = null, + + /** + * The rootPath of the workspace. Is null + * if no folder is open. + * + * @deprecated in favour of `rootUri`. + */ + @Deprecated("Use rootUri instead") + val rootPath: String? = null, + + /** + * The rootUri of the workspace. Is null if no + * folder is open. If both `rootPath` and `rootUri` are set + * `rootUri` wins. + * + * @deprecated in favour of `workspaceFolders` + */ + val rootUri: DocumentUri?, + + /** + * User provided initialization options. + */ + val initializationOptions: JsonElement? = null, + + /** + * The capabilities provided by the client (editor or tool) + */ + val capabilities: ClientCapabilities, + + /** + * The initial trace setting. If omitted trace is disabled ('off'). + */ + val trace: TraceValue? = null, + + /** + * The workspace folders configured in the client when the server starts. + * This property is only available if the client supports workspace folders. + * It can be `null` if the client supports workspace folders but none are + * configured. + * + * @since 3.6.0 + */ + val workspaceFolders: List? = null, + + override val workDoneToken: ProgressToken?, + + ) : WorkDoneProgressParams + +@Serializable +data class InitializeResult( + val capabilities: ServerCapabilities, + val serverInfo: ServerInfo? = null, +) { + @Serializable + data class ServerInfo( + val name: String, + val version: String? = null, + ) +} + +object InitializeErrorCodes { + + /** + * If the protocol version provided by the client can't be handled by + * the server. + * + * @deprecated This initialize error got replaced by client capabilities. + * There is no version handshake in version 3.0x + */ + const val UNKNOWN_PROTOCOL_VERSION: Int = 1 +} + +@Serializable +data class InitializeError( + /** + * Indicates whether the client executes the following retry logic: + * (1) Show the message provided by the ResponseError to the user + * (2) User selects retry or cancel + * (3) If user selected retry, the initialize method is sent again. + */ + val retry: Boolean, +) + +val ExitNotificationType: NotificationType = + NotificationType("exit", Unit.serializer()) \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/JsonRpc.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/JsonRpc.kt new file mode 100644 index 000000000000..b80874de2e52 --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/JsonRpc.kt @@ -0,0 +1,183 @@ +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonPrimitive + +data class Header( + val contentLenght: Int, + val contentType: String = "application/vscode-jsonrpc; charset=utf-8", +) + +const val Separator: String = "\r\n" + +sealed interface Message { + val jsonrpc: String +} + +@Serializable +@JvmInline +value class StringOrInt(val value: JsonPrimitive) { + companion object { + fun int(value: Int): StringOrInt = StringOrInt(JsonPrimitive(value)) + fun string(value: String): StringOrInt = StringOrInt(JsonPrimitive(value)) + } +} + +@Serializable +data class RequestMessage( + override val jsonrpc: String = "2.0", + val id: StringOrInt, + val method: String, + val params: JsonElement? = null, +) : Message + +@Serializable +data class ResponseMessage( + override val jsonrpc: String, + val id: StringOrInt, + val result: JsonElement? = null, + val error: ResponseError? = null, +) : Message + +@Serializable +data class ResponseError( + val code: Int, + val message: String, + val data: JsonElement? = null, +) + +object ErrorCodes { + // Defined by JSON-RPC + const val ParseError: Int = -32700 + const val InvalidRequest: Int = -32600 + const val MethodNotFound: Int = -32601 + const val InvalidParams: Int = -32602 + const val InternalError: Int = -32603 + + /** + * This is the start range of JSON-RPC reserved error codes. + * It doesn't denote a real error code. No LSP error codes should + * be defined between the start and end range. For backwards + * compatibility the `ServerNotInitialized` and the `UnknownErrorCode` + * are left in the range. + * + * @since 3.16.0 + */ + const val jsonrpcReservedErrorRangeStart: Int = -32099 + + /** @deprecated use jsonrpcReservedErrorRangeStart */ + const val serverErrorStart: Int = jsonrpcReservedErrorRangeStart + + /** + * Error code indicating that a server received a notification or + * request before the server has received the `initialize` request. + */ + const val ServerNotInitialized: Int = -32002 + const val UnknownErrorCode: Int = -32001 + + /** + * This is the end range of JSON-RPC reserved error codes. + * It doesn't denote a real error code. + * + * @since 3.16.0 + */ + const val jsonrpcReservedErrorRangeEnd: Int = -32000 + + /** @deprecated use jsonrpcReservedErrorRangeEnd */ + const val serverErrorEnd: Int = jsonrpcReservedErrorRangeEnd + + /** + * This is the start range of LSP reserved error codes. + * It doesn't denote a real error code. + * + * @since 3.16.0 + */ + const val lspReservedErrorRangeStart: Int = -32899 + + /** + * A request failed but it was syntactically correct, e.g the + * method name was known and the parameters were valid. The error + * message should contain human readable information about why + * the request failed. + * + * @since 3.17.0 + */ + const val RequestFailed: Int = -32803 + + /** + * The server cancelled the request. This error code should + * only be used for requests that explicitly support being + * server cancellable. + * + * @since 3.17.0 + */ + const val ServerCancelled: Int = -32802 + + /** + * The server detected that the content of a document got + * modified outside normal conditions. A server should + * NOT send this error code if it detects a content change + * in it unprocessed messages. The result even computed + * on an older state might still be useful for the client. + * + * If a client decides that a result is not of any use anymore + * the client should cancel the request. + */ + const val ContentModified: Int = -32801 + + /** + * The client has canceled a request and a server has detected + * the cancel. + */ + const val RequestCancelled: Int = -32800 + + /** + * This is the end range of LSP reserved error codes. + * It doesn't denote a real error code. + * + * @since 3.16.0 + */ + const val lspReservedErrorRangeEnd: Int = -32800 +} + +@Serializable +data class NotificationMessage( + override val jsonrpc: String, + val method: String, + val params: JsonElement? = null, +) : Message + +@Serializable +data class CancelParams( + val id: StringOrInt, +) + +@Serializable +@JvmInline +value class ProgressToken(val value: StringOrInt) { + companion object { + fun string(value: String): ProgressToken = ProgressToken(StringOrInt.string(value)) + fun int(value: Int): ProgressToken = ProgressToken(StringOrInt.int(value)) + } +} + +@Serializable +data class ProgressParams( + val token: ProgressToken, + val value: JsonElement, +) + +data class RequestType( + val method: String, + val paramsSerializer: KSerializer, + val resultSerializer: KSerializer, + val errorSerializer: KSerializer, +) + +data class NotificationType( + val method: String, + val paramsSerializer: KSerializer +) + \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/LSP.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/LSP.kt new file mode 100644 index 000000000000..a5ad60d02a2f --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/LSP.kt @@ -0,0 +1,945 @@ +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.Json +import java.nio.file.Path +import java.nio.file.Paths + + +/** + * URI following the URI specification, so special spaces (like spaces) are encoded. + * + * It may not correspond to URIs which are used inside IntelliJ + */ +@Serializable +@JvmInline +value class URI(val uri: String) { + init { + require(isValidUriString(uri)) { "Invalid URI: $uri" } + } + + /** + * Returns the URI's schema without schema delimiter (`://`) + */ + val scheme: String get() = asJavaUri().scheme + + /** + * Returns the file name + */ + val fileName: String get() = asJavaUri().path.substringAfterLast('/') + + /** + * Returns the file extension (without dot) if present + */ + val fileExtension: String? + get() { + val name = fileName + val dotIndex = name.lastIndexOf('.') + return if (dotIndex > 0) name.substring(dotIndex + 1) else null + } + + + fun asJavaUri(): java.net.URI = java.net.URI(uri) + + object Schemas { + const val FILE: String = "file" + const val JRT: String = "jrt" + const val JAR: String = "jar" + } + + companion object { + /** + * We need to have consistent URIs as they are used as keys in the analyzer + */ + fun isValidUriString(uriString: String): Boolean { + val javaUri = runCatching { java.net.URI(uriString) }.getOrNull() ?: return false + return javaUri.scheme != null + } + } +} + + +@Serializable +data class RegularExpressionsClientCapabilities( + val engine: String, + val version: String? = null, +) + +@Serializable +data class Position( + /** + * Line position in a document (zero-based). + */ + val line: Int, + + /** + * Character offset on a line in a document (zero-based). The meaning of this + * offset is determined by the negotiated `PositionEncodingKind`. + * + * If the character value is greater than the line length it defaults back + * to the line length. + */ + val character: Int, +) : Comparable { + + override fun toString(): String { + return "$line:$character" + } + + override fun compareTo(other: Position): Int { + return compareValuesBy(this, other, { it.line }, { it.character }) + } + + companion object { + val ZERO: Position = Position(0, 0) + + /** + * To avoid computing the line size, we use a very large number that will hopefully be reached by the line size. + * + * We do not use `Int.MAX_VALUE` directly to avoid possible overflows in operations on the client side. + */ + const val EOL_INDEX: Int = Int.MAX_VALUE / 4 + + fun lineStart(line: Int): Position = Position(line, 0) + + fun lineEnd(line: Int): Position = Position(line, EOL_INDEX) + } +} + +fun Position.offsetCharacter(offset: Int): Position = Position(line, character + offset) + +operator fun Position.plus(other: Position): Position = Position(line + other.line, character + other.character) + +operator fun Position.minus(other: Position): Position = Position(line - other.line, character - other.character) + +@Serializable +enum class PositionEncodingKind { + /** + * Character offsets count UTF-8 code units (e.g., bytes). + */ + @SerialName("utf-8") + UTF8, + + /** + * Character offsets count UTF-16 code units. + * + * This is the default and must always be supported + * by servers. + */ + @SerialName("utf-16") + UTF16, + + /** + * Character offsets count UTF-32 code units. + * + * Implementation note: these are the same as Unicode code points, + * so this `PositionEncodingKind` may also be used for an + * encoding-agnostic representation of character offsets. + */ + @SerialName("utf-32") + UTF32 +} + +@Serializable +data class Range( + /** + * The range's start position. + */ + val start: Position, + + /** + * The range's end position. + */ + val end: Position, +) { + override fun toString(): String { + return "[$start, $end]" + } + + /** + * Extends the range so it includes also all remaining characters in the last line including the line break + */ + fun toTheLineEndWithLineBreak(): Range = Range(start, Position(end.line + 1, 0)) + + companion object { + val BEGINNING: Range = Range(Position.ZERO, Position.ZERO) + + fun empty(position: Position): Range = Range(position, position) + + fun fromPositionTillLineEnd(from: Position): Range = Range(from, Position.lineEnd(from.line)) + + fun fromLineStartTillPosition(till: Position): Range = Range(Position.lineStart(till.line), till) + + fun fullLine(line: Int): Range = Range(Position.lineStart(line), Position.lineEnd(line)) + } +} + +fun Range.intersects(other: Range): Boolean = + start <= other.end && end >= other.start + +@Serializable +@JvmInline +value class DocumentUri(val uri: URI) + +@Serializable +data class TextDocumentItem( + /** + * The text document's URI. + */ + val uri: DocumentUri, + + /** + * The text document's language identifier. + */ + val languageId: String, + + /** + * The version number of this document (it will increase after each + * change, including undo/redo). + */ + val version: Int, + + /** + * The content of the opened text document. + */ + val text: String, +) + +@Serializable +data class TextDocumentIdentifier( + /** + * The text document's URI. + */ + val uri: DocumentUri, + /** + * The version number of this document. + * + * The version number of a document will increase after each change, + * including undo/redo. The number doesn't need to be consecutive. + */ + val version: Int? = null, +) + +interface TextDocumentPositionParams { + /** + * The text document. + */ + val textDocument: TextDocumentIdentifier + + /** + * The position inside the text document. + */ + val position: Position +} + +/** + * A document filter denotes a document through properties like language, scheme or pattern. + * An example is a filter that applies to TypeScript files on disk. + * Another example is a filter that applies to JSON files with name package.json: + * + * ```json + * { language: 'typescript', scheme: 'file' } + * { language: 'json', pattern: '** /package.json' } + * ``` + * + * Please note that for a document filter to be valid at least one of the properties for language, scheme, or pattern must be set. + * To keep the type definition simple all properties are marked as optional. + */ +@Serializable +data class DocumentFilter( + /** + * A language id, like `typescript`. + */ + val language: String? = null, + + /** + * A Uri scheme, like `file` or `untitled`. + */ + val scheme: String? = null, + + /** + * A glob pattern, like `*.{ts,js}`. + * + * Glob patterns can have the following syntax: + * - `*` to match one or more characters in a path segment + * - `?` to match on one character in a path segment + * - `**` to match any number of path segments, including none + * - `{}` to group sub patterns into an OR expression + * matches all TypeScript and JavaScript files) + * - `[]` to declare a range of characters to match in a path segment + * (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …) + * - `[!...]` to negate a range of characters to match in a path segment + * (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but + * not `example.0`) + */ + val pattern: String? = null, +) { + init { + require(language != null || scheme != null || pattern != null) { + "DocumentFilter must have at least one property set (language, scheme or pattern)" + } + } +} + +@Serializable +@JvmInline +value class DocumentSelector(val filters: List) { + constructor(vararg filters: DocumentFilter) : this(filters.toList()) +} + +@Serializable +data class TextEdit( + /** + * The range of the text document to be manipulated. To insert + * text into a document create a range where start == end. + */ + val range: Range, + + /** + * The string to be inserted. For delete operations use an + * empty string. + */ + val newText: String, + + /** + * The actual annotation identifier. + */ + val annotationId: ChangeAnnotationIdentifier? = null, +) + +@Serializable +@JvmInline +value class ChangeAnnotationIdentifier(val id: String) + +/** + * Additional information that describes document changes. + * + * @since 3.16.0 + */ +@Serializable +data class ChangeAnnotation( + /** + * A human-readable string describing the actual change. The string + * is rendered prominently in the user interface. + */ + val label: String, + + /** + * A flag which indicates that user confirmation is needed + * before applying the change. + */ + val needsConfirmation: Boolean? = null, + + /** + * A human-readable string which is rendered less prominently in + * the user interface. + */ + val description: String? = null, +) + +//todo: custom serializer is required +@Serializable +@JvmInline +value class DocumentChange(val change: JsonElement) // TextDocumentEdit | FileChange + +@Serializable +sealed interface FileChange + +@Serializable +data class TextDocumentEdit( + /** + * The text document to change. + */ + val textDocument: TextDocumentIdentifier, + + /** + * The edits to be applied. + * + * @since 3.16.0 - support for AnnotatedTextEdit. This is guarded by the + * client capability `workspace.workspaceEdit.changeAnnotationSupport` + */ + val edits: List, +) + +@Serializable +data class Location( + val uri: DocumentUri, + val range: Range, +) + +@Serializable +data class LocationLink( + /** + * Span of the origin of this link. + * + * Used as the underlined span for mouse interaction. Defaults to the word + * range at the mouse position. + */ + val originSelectionRange: Range? = null, + + /** + * The target resource identifier of this link. + */ + val targetUri: DocumentUri, + + /** + * The full target range of this link. If the target for example is a symbol + * then target range is the range enclosing this symbol not including + * leading/trailing whitespace but everything else like comments. This + * information is typically used to highlight the range in the editor. + */ + val targetRange: Range, + + /** + * The range that should be selected and revealed when this link is being + * followed, e.g the name of a function. Must be contained by the + * `targetRange`. See also `DocumentSymbol#range` + */ + val targetSelectionRange: Range, +) + +@Serializable +data class Command( + /** + * Title of the command, like `save`. + */ + val title: String, + + /** + * The identifier of the actual command handler. + */ + val command: String, + + /** + * Arguments that the command handler should be + * invoked with. + */ + val arguments: List? = null, +) + +@Serializable +enum class MarkupKind { + /** + * Plain text is supported as a content format + */ + @SerialName("plaintext") + PlainText, + + /** + * Markdown is supported as a content format + */ + @SerialName("markdown") + Markdown +} + +/** + * Describes the content type that a client supports in various + * result literals like `Hover`, `ParameterInfo` or `CompletionItem`. + * + * Please note that `MarkupKinds` must not start with a `$`. This kinds + * are reserved for internal usage. + */ +@Serializable +enum class MarkupKindType { + /** + * Plain text is supported as a content format + */ + @SerialName("plaintext") + + PlaintText, + + /** + * Markdown is supported as a content format + */ + @SerialName("markdown") + Markdown +} + +/** + * A `MarkupContent` literal represents a string value which content is + * interpreted base on its kind flag. Currently the protocol supports + * `plaintext` and `markdown` as markup kinds. + * + * If the kind is `markdown` then the value can contain fenced code blocks like + * in GitHub issues. + * + * Here is an example how such a string can be constructed using + * JavaScript / TypeScript: + * ```typescript + * let markdown: MarkdownContent = { + * kind: MarkupKind.Markdown, + * value: [ + * '# Header', + * 'Some text', + * '```typescript', + * 'someCode();', + * '```' + * ].join('\n') + * }; + * ``` + * + * *Please Note* that clients might sanitize the return markdown. A client could + * decide to remove HTML from the markdown to avoid script execution. + */ +@Serializable +data class MarkupContent( + /** + * The type of the Markup + */ + val kind: MarkupKindType, + + /** + * The content itself + */ + val value: String, +) + +@Serializable +data class MarkdownClientCapabilities( + /** + * The name of the parser. + */ + val parser: String, + + /** + * The version of the parser. + */ + val version: String? = null, + + /** + * A list of HTML tags that the client allows / supports in Markdown. + * + * @since 3.17.0 + */ + val allowedTags: List? = null, +) + +@Serializable +data class CreateFileOptions( + /** + * Overwrite existing file. Overwrite wins over `ignoreIfExists` + */ + val overwrite: Boolean? = null, + + /** + * Ignore if exists. + */ + val ignoreIfExists: Boolean? = null, +) + +@SerialName("create") +@Serializable +data class CreateFile( + /** + * The resource to create. + */ + val uri: DocumentUri, + + /** + * Additional options + */ + val options: CreateFileOptions? = null, + + /** + * An optional annotation identifier describing the operation. + * + * @since 3.16.0 + */ + val annotationId: ChangeAnnotationIdentifier? = null, +) : FileChange + +@Serializable +data class RenameFileOptions( + /** + * Overwrite target if existing. Overwrite wins over `ignoreIfExists` + */ + val overwrite: Boolean? = null, + + /** + * Ignores if target exists. + */ + val ignoreIfExists: Boolean? = null, +) + +@Serializable +@SerialName("rename") +data class RenameFile( + /** + * The old (existing) location. + */ + val oldUri: DocumentUri, + + /** + * The new location. + */ + val newUri: DocumentUri, + + /** + * Rename options. + */ + val options: RenameFileOptions? = null, + + /** + * An optional annotation identifier describing the operation. + * + * @since 3.16.0 + */ + val annotationId: ChangeAnnotationIdentifier? = null, +) : FileChange + +@Serializable +data class DeleteFileOptions( + /** + * Delete the content recursively if a folder is denoted. + */ + val recursive: Boolean? = null, + + /** + * Ignore the operation if the file doesn't exist. + */ + val ignoreIfNotExists: Boolean? = null, +) + +@Serializable +@SerialName("delete") +data class DeleteFile( + /** + * The file to delete. + */ + val uri: DocumentUri, + + /** + * Delete options. + */ + val options: DeleteFileOptions? = null, + + /** + * An optional annotation identifier describing the operation. + * + * @since 3.16.0 + */ + val annotationId: ChangeAnnotationIdentifier? = null, +) : FileChange + +@Serializable +data class WorkspaceEdit( + /** + * Holds changes to existing resources. + */ + val changes: Map>? = null, + + /** + * Depending on the client capability `workspace.workspaceEdit.resourceOperations`, + * document changes are either an array of `TextDocumentEdit`s to express changes + * to different text documents where each text document edit addresses a specific + * version of a text document, or it can contain `TextDocumentEdit`s mixed with + * create, rename and delete file / folder operations. + * + * Whether a client supports versioned document edits is expressed via + * `workspace.workspaceEdit.documentChanges` client capability. + * + * If a client neither supports `documentChanges` nor + * `workspace.workspaceEdit.resourceOperations`, only plain `TextEdit`s + * using the `changes` property are supported. + */ + val documentChanges: List? = null, // Use proper types or sealed classes for TextDocumentEdit, CreateFile, RenameFile, DeleteFile if defined elsewhere + + /** + * A map of change annotations that can be referenced in `AnnotatedTextEdit`s + * or create, rename and delete file / folder operations. + * + * Whether clients honor this property depends on the client capability + * `workspace.changeAnnotationSupport`. + * + * @since 3.16.0 + */ + val changeAnnotations: Map? = null, +) + +@Serializable +data class WorkspaceEditClientCapabilities( + /** + * The client supports versioned document changes in `WorkspaceEdit`s + */ + val documentChanges: Boolean? = null, + + /** + * The resource operations the client supports. Clients should at least + * support 'create', 'rename' and 'delete' files and folders. + * + * @since 3.13.0 + */ + val resourceOperations: List? = null, + + /** + * The failure handling strategy of a client if applying the workspace edit + * fails. + * + * @since 3.13.0 + */ + val failureHandling: FailureHandlingKind? = null, + + /** + * Whether the client normalizes line endings to the client specific + * setting. If set to `true`, the client will normalize line ending characters + * in a workspace edit to the client-specific newline character(s). + * + * @since 3.16.0 + */ + val normalizesLineEndings: Boolean? = null, + + /** + * Whether the client in general supports change annotations on text edits, + * create file, rename file, and delete file changes. + * + * @since 3.16.0 + */ + val changeAnnotationSupport: ChangeAnnotationSupport? = null, +) + +@Serializable +data class ChangeAnnotationSupport( + /** + * Whether the client groups edits with equal labels into tree nodes, + * for instance, all edits labeled with "Changes in Strings" would + * be a tree node. + */ + val groupsOnLabel: Boolean? = null, +) + +@Serializable +enum class ResourceOperationKind { + @SerialName("create") + Create, + + @SerialName("rename") + Rename, + + @SerialName("delete") + Delete +} + +@Serializable +enum class FailureHandlingKind { + /** + * Applying the workspace change is simply aborted if one of the changes + * provided fails. All operations executed before the failing operation + * stay executed. + */ + @SerialName("abort") + Abort, + + /** + * All operations are executed transactional. That means they either all + * succeed or no changes at all are applied to the workspace. + */ + @SerialName("transactional") + Transactional, + + /** + * If the workspace edit contains only textual file changes they are + * executed transactional. If resource changes (create, rename or delete + * file) are part of the change the failure handling strategy is abort. + */ + @SerialName("textOnlyTransactional") + TextOnlyTransactional, + + /** + * The client tries to undo the operations already executed. But there is no + * guarantee that this is succeeding. + */ + @SerialName("undo") + Undo +} + +// todo: use 'kind' as descriminant in serialization +@Serializable +sealed interface WorkDoneProgress { + @SerialName("begin") + @Serializable + data class Begin( + val title: String, + val cancellable: Boolean? = null, + val message: String? = null, + val percentage: Int? = null, + ) : WorkDoneProgress { + @Deprecated("Use `WorkDoneProgress.serializer()` instead to have a proper `kind` field set", level = DeprecationLevel.ERROR) + companion object + } + + @SerialName("report") + @Serializable + data class Report( + val cancellable: Boolean? = null, + val message: String? = null, + val percentage: Int? = null, + ) : WorkDoneProgress { + @Deprecated("Use `WorkDoneProgress.serializer()` instead to have a proper `kind` field set", level = DeprecationLevel.ERROR) + private companion object + } + + @SerialName("end") + @Serializable + data class End( + val message: String? = null, + ) : WorkDoneProgress { + @Deprecated("Use `WorkDoneProgress.serializer()` instead to have a proper `kind` field set", level = DeprecationLevel.ERROR) + private companion object + } +} + +interface WorkDoneProgressParams { + /** + * An optional token that a server can use to report work done progress. + */ + val workDoneToken: ProgressToken? +} + +interface WorkDoneProgressOptions { + val workDoneProgress: Boolean? +} + +interface PartialResultParams { + /** + * An optional token that a server can use to report partial results (e.g., streaming) to the client. + */ + val partialResultToken: ProgressToken? +} + +@Serializable +enum class TraceValue { + @SerialName("off") + Off, + + @SerialName("messages") + Messages, + + @SerialName("verbose") + Verbose +} + +@Serializable +data class ClientInfo( + /** + * The name of the client as defined by the client. + */ + val name: String, + + /** + * The client's version as defined by the client. + */ + val version: String? = null, +) + +@Serializable +data class StaleRequestSupport( + /** + * The client will actively cancel the request. + */ + val cancel: Boolean, + + /** + * The list of requests for which the client will retry the request if + * it receives a response with error code `ContentModified`. + */ + val retryOnContentModified: List, +) + +@Serializable +data class WorkspaceFolder( + /** + * The associated URI for this workspace folder. + */ + val uri: URI, + + /** + * The name of the workspace folder. Used to refer to this + * workspace folder in the user interface. + */ + val name: String, +) + +@Serializable +data class Registration( + val id: String, + val method: String, + val registerOptions: JsonElement? = null, +) + +@Serializable +data class RegistrationParams( + val registrations: List, +) + +/** + * Static registration options to be returned in the initialize request. + */ + +interface StaticRegistrationOptions { + /** + * The id used to register the request. The id can be used to deregister + * the request again. See also Registration#id. + */ + val id: String? +} + +/** + * General text document registration options. + */ +interface TextDocumentRegistrationOptions { + /** + * A document selector to identify the scope of the registration. If set to + * null the document selector provided on the client side will be used. + */ + val documentSelector: DocumentSelector? +} + +@Serializable +data class Unregistration( + /** + * The id used to unregister the request or notification. Usually an id + * provided during the register request. + */ + val id: String, + + /** + * The method / capability to unregister for. + */ + val method: String, +) + +@Serializable +data class UnregistrationParams( + /** + * This should correctly be named `unregistrations`. However changing this + * is a breaking change and needs to wait until we deliver a 4.x version + * of the specification. + */ + @SerialName("unregisterations") + val unregistrations: List, +) + +object LSP { + val json: Json = Json { + encodeDefaults = true + explicitNulls = false + ignoreUnknownKeys = true + isLenient = true + prettyPrint = true + classDiscriminator = "kind" + } + + val ProgressNotificationType: NotificationType = + NotificationType("$/progress", ProgressParams.serializer()) + + val CancelNotificationType: NotificationType = + NotificationType("$/cancelRequest", CancelParams.serializer()) + + val RegisterCapabilityNotificationType: NotificationType = + NotificationType("client/registerCapability", RegistrationParams.serializer()) + + val UnregisterCapabilityNotificationType: NotificationType = + NotificationType("client/unregisterCapability", UnregistrationParams.serializer()) +} \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/LogMessage.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/LogMessage.kt new file mode 100644 index 000000000000..4b8615fbb236 --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/LogMessage.kt @@ -0,0 +1,71 @@ +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.Serializable + +@Serializable +data class LogMessageParams( + /** + * The message type. See {@link MessageType} + */ + val type: MessageType, + + /** + * The actual message + */ + val message: String, +) + +@Serializable +data class ShowMessageParams( + /** + * The message type. See {@link MessageType} + */ + val type: MessageType, + + /** + * The actual message + */ + val message: String, +) + +@Serializable(with = MessageType.Serializer::class) +enum class MessageType(val value: Int) { + /** + * An error message. + */ + Error(1), + + /** + * A warning message. + */ + Warning(2), + + /** + * An information message. + */ + Info(3), + + /** + * A log message. + */ + Log(4), + + /** + * A debug message. + * + * @since 3.18.0 + * @proposed + */ + Debug(5), + + ; + + internal class Serializer : EnumAsIntSerializer( + serialName = MessageType::class.simpleName!!, + serialize = MessageType::value, + deserialize = { entries[it - 1] }, + ) +} + +val LogMessageNotification: NotificationType = NotificationType("window/logMessage", LogMessageParams.serializer()) +val ShowMessageNotification: NotificationType = NotificationType("window/showMessage", ShowMessageParams.serializer()) \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/LogTrace.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/LogTrace.kt new file mode 100644 index 000000000000..c61a1b47093b --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/LogTrace.kt @@ -0,0 +1,22 @@ +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.Serializable + +@Serializable +data class LogTraceParams( + /** + * The message to be logged. + */ + val message: String, + /** + * Additional information that can be computed if the `trace` configuration + * is set to `"verbose"` + */ + val verbose: String? +) + +val LogTraceNotificationType: NotificationType = + NotificationType("\$/logTrace", LogTraceParams.serializer()) + +val SetTraceNotificationType: NotificationType = + NotificationType("\$/setTrace", TraceValue.serializer()) \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Notebooks.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Notebooks.kt new file mode 100644 index 000000000000..1d1eb5da0700 --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Notebooks.kt @@ -0,0 +1,393 @@ +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.Serializable + +@Serializable +data class NotebookDocumentClientCapabilities( + /** + * Capabilities specific to notebook document synchronization + * + * @since 3.17.0 + */ + val synchronization: NotebookDocumentSyncClientCapabilities +) + +@Serializable +data class NotebookDocumentSyncClientCapabilities( + /** + * Whether implementation supports dynamic registration. If this is + * set to `true` the client supports the new + * `(NotebookDocumentSyncRegistrationOptions & NotebookDocumentSyncOptions)` + * return value for the corresponding server capability as well. + */ + val dynamicRegistration: Boolean? = null, + + /** + * The client supports sending execution summary data per cell. + */ + val executionSummarySupport: Boolean? = null +) + +/** + * A notebook document. + * + * @since 3.17.0 + */ +@Serializable +data class NotebookDocument( + /** + * The notebook document's URI. + */ + val uri: URI, + + /** + * The type of the notebook. + */ + val notebookType: String, + + /** + * The version number of this document (it will increase after each + * change, including undo/redo). + */ + val version: Int, + + /** + * Additional metadata stored with the notebook + * document. + */ + val metadata: JsonObject? = null, + + /** + * The cells of a notebook. + */ + val cells: List, +) + +/** + * A notebook cell. + * + * A cell's document URI must be unique across ALL notebook + * cells and can therefore be used to uniquely identify a + * notebook cell or the cell's text document. + * + * @since 3.17.0 + */ +@Serializable +data class NotebookCell( + /** + * The cell's kind + */ + val kind: NotebookCellKind, + + /** + * The URI of the cell's text document + * content. + */ + val document: DocumentUri, + + /** + * Additional metadata stored with the cell. + */ + val metadata: JsonObject? = null, + + /** + * Additional execution summary information + * if supported by the client. + */ + val executionSummary: ExecutionSummary? = null, +) + +class NotebookCellKindSerializer : EnumAsIntSerializer( + serialName = "CompletionTriggerKind", + serialize = NotebookCellKind::value, + deserialize = { NotebookCellKind.entries.get(it - 1) }, +) + +/** + * A notebook cell kind. + * + * @since 3.17.0 + */ +@Serializable +enum class NotebookCellKind(val value: Int) { + /** + * A markup-cell is formatted source that is used for display. + */ + Markup(1), + + /** + * A code-cell is source code. + */ + Code(2) +} + +/** + * Execution summary information. + */ +@Serializable +data class ExecutionSummary( + /** + * A strict monotonically increasing value + * indicating the execution order of a cell + * inside a notebook. + */ + val executionOrder: UInt, + + /** + * Whether the execution was successful or + * not if known by the client. + */ + val success: Boolean? = null, +) + +/** + * A notebook cell text document filter denotes a cell text + * document by different properties. + * + * @since 3.17.0 + */ +@Serializable +data class NotebookCellTextDocumentFilter( + /** + * A filter that matches against the notebook + * containing the notebook cell. If a string + * value is provided it matches against the + * notebook type. '*' matches every notebook. + */ + val notebook: StringOrNotebookDocumentFilter, + + /** + * A language id like `python`. + * + * Will be matched against the language id of the + * notebook cell document. '*' matches every language. + */ + val language: String? = null, +) + +/** + * A notebook document filter denotes a notebook document by + * different properties. + * + * @since 3.17.0 + */ +@Serializable +data class NotebookDocumentFilter( + val notebookType: String, + val scheme: String? = null, + val pattern: String? = null +) + +/** + * Represents a union type of String or NotebookDocumentFilter. + */ +@Serializable // todo: custom serializer +@JvmInline +value class StringOrNotebookDocumentFilter(val value: JsonElement) // String | NotebookDocumentFilter + +/** + * Options specific to a notebook plus its cells + * to be synced to the server. + * + * If a selector provides a notebook document + * filter but no cell selector all cells of a + * matching notebook document will be synced. + * + * If a selector provides no notebook document + * filter but only a cell selector all notebook + * documents that contain at least one matching + * cell will be synced. + * + * @since 3.17.0 + */ +@Serializable +data class NotebookDocumentSyncOptions( + /** + * The notebooks to be synced. + */ + val notebookSelector: List, + + /** + * Whether save notification should be forwarded to + * the server. Will only be honored if mode === `notebook`. + */ + val save: Boolean? = null, + override val id: String?, +) : StaticRegistrationOptions + +@Serializable +data class NotebookSelector( + /** + * The notebook to be synced. If a string value is provided, it matches + * against the notebook type. '*' matches every notebook. + */ + val notebook: StringOrNotebookDocumentFilter? = null, + + /** + * The cells of the matching notebook to be synced. + */ + val cells: List? +) + +@Serializable +data class Cell( + /** + * The language of the cell. + */ + val language: String, +) + +@Serializable +data class DidOpenNotebookDocumentParams( + /** + * The notebook document that got opened. + */ + val notebookDocument: NotebookDocument, + + /** + * The text documents that represent the content + * of a notebook cell. + */ + val cellTextDocuments: List, +) + +data class DidChangeNotebookDocumentParams( + /** + * The notebook document that did change. The version number points + * to the version after all provided changes have been applied. + */ + val notebookDocument: VersionedNotebookDocumentIdentifier, + + /** + * The actual changes to the notebook document. + * + * The change describes a single state change to the notebook document. + * So it moves a notebook document, its cells and its cell text document + * contents from state S to S'. + * + * To mirror the content of a notebook using change events use the + * following approach: + * - start with the same initial content + * - apply the 'notebookDocument/didChange' notifications in the order + * you receive them. + */ + val change: NotebookDocumentChangeEvent, +) + +data class DidSaveNotebookDocumentParams( + /** + * The notebook document that got saved. + */ + val notebookDocument: NotebookDocumentIdentifier, +) + +data class DidCloseNotebookDocumentParams( + /** + * The notebook document that got closed. + */ + val notebookDocument: NotebookDocumentIdentifier, + + /** + * The text documents that represent the content + * of a notebook cell that got closed. + */ + val cellTextDocuments: List, +) + +data class VersionedNotebookDocumentIdentifier( + /** + * The version number of this notebook document. + */ + val version: Int, + + /** + * The notebook document's URI. + */ + val uri: URI, +) + +data class NotebookDocumentChangeEvent( + /** + * The changed metadata if any. + */ + val metadata: JsonObject? = null, + + /** + * Changes to cells. + */ + val cells: NotebookCellChanges? = null, +) + +data class NotebookCellChanges( + /** + * Changes to the cell structure to add or + * remove cells. + */ + val structure: NotebookCellStructureChange? = null, + + /** + * Changes to notebook cells properties like its + * kind, execution summary or metadata. + */ + val data: List? = null, + + /** + * Changes to the text content of notebook cells. + */ + val textContent: List? = null, +) + +data class NotebookCellStructureChange( + /** + * The change to the cell array. + */ + val array: NotebookCellArrayChange, + + /** + * Additional opened cell text documents. + */ + val didOpen: List? = null, + + /** + * Additional closed cell text documents. + */ + val didClose: List? = null, +) + +data class NotebookCellArrayChange( + /** + * The start offset of the cell that changed. + */ + val start: UInt, + + /** + * The deleted cells. + */ + val deleteCount: UInt, + + /** + * The new cells, if any. + */ + val cells: List? = null, +) + +data class CellTextContentChange( + /** + * The document that changed. + */ + val document: TextDocumentIdentifier, + + /** + * The change events for the document's text. + */ + val changes: List, +) + +data class NotebookDocumentIdentifier( + /** + * The notebook document's URI. + */ + val uri: URI, +) \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/SemanticTokens.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/SemanticTokens.kt new file mode 100644 index 000000000000..60de0957e1ca --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/SemanticTokens.kt @@ -0,0 +1,54 @@ +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.NothingSerializer +import kotlinx.serialization.builtins.nullable + +@Serializable +data class SemanticTokensLegend( + /** + * The token types a server uses. + */ + val tokenTypes: List, + + /** + * The token modifiers a server uses. + */ + val tokenModifiers: List, +) + +@Serializable +data class SemanticTokensParams( + /** + * The text document. + */ + val textDocument: TextDocumentIdentifier, + override val workDoneToken: ProgressToken?, + override val partialResultToken: ProgressToken?, +) : WorkDoneProgressParams, PartialResultParams + + +@Serializable +data class SemanticTokens( + /** + * An optional result id. If provided and clients support delta updating + * the client will include the result id in the next semantic token request. + * A server can then instead of computing all semantic tokens again simply + * send a delta. + */ + val resultId: String?, + + /** + * The actual tokens. + */ + val data: List, +) + + +object SemanticTokensRequests { + val SemanticTokensFullRequest: RequestType = + RequestType( + "textDocument/semanticTokens/full", SemanticTokensParams.serializer(), SemanticTokens.serializer(), + NothingSerializer().nullable + ) +} \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ServerCapabilities.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ServerCapabilities.kt new file mode 100644 index 000000000000..d9d3f8c95593 --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/ServerCapabilities.kt @@ -0,0 +1,459 @@ +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonPrimitive + +@Serializable +@JvmInline +value class OrBoolean(val value: JsonElement) { + constructor(boolean: Boolean) : this(JsonPrimitive(boolean)) + + companion object { + inline fun of(value: T): OrBoolean { + val serializer = kotlinx.serialization.serializer() + val encoded = LSP.json.encodeToJsonElement(serializer, value) + return OrBoolean(encoded) + } + } +} + +@Serializable +data class ServerCapabilities( + /** + * The position encoding the server picked from the encodings offered + * by the client via the client capability `general.positionEncodings`. + * + * If the client didn't provide any position encodings the only valid + * value that a server can return is 'utf-16'. + * + * If omitted it defaults to 'utf-16'. + * + * @since 3.17.0 + */ + val positionEncoding: PositionEncodingKind? = null, + + /** + * Defines how text documents are synced. Is either a detailed structure + * defining each notification or for backwards compatibility the + * TextDocumentSyncKind number. If omitted it defaults to + * `TextDocumentSyncKind.None`. + */ + val textDocumentSync: TextDocumentSync? = null, // TextDocumentSyncOptions | TextDocumentSyncKind + + /** + * Defines how notebook documents are synced. + * + * @since 3.17.0 + */ + val notebookDocumentSync: NotebookDocumentSyncOptions? = null, // NotebookDocumentSyncOptions | NotebookDocumentSyncRegistrationOptions + + /** + * The server provides completion support. + */ + val completionProvider: CompletionOptions? = null, + + /** + * The server provides hover support. + */ + val hoverProvider: OrBoolean? = null, + + /** + * The server provides signature help support. + */ + val signatureHelpProvider: SignatureHelpOptions? = null, + + /** + * The server provides go to declaration support. + * + * @since 3.14.0 + */ + val declarationProvider: OrBoolean? = null, + + /** + * The server provides goto definition support. + */ + val definitionProvider: OrBoolean? = null, + + /** + * The server provides goto type definition support. + * + * @since 3.6.0 + */ + val typeDefinitionProvider: OrBoolean? = null, + + /** + * The server provides goto implementation support. + * + * @since 3.6.0 + */ + val implementationProvider: OrBoolean? = null, + + /** + * The server provides find references support. + */ + val referencesProvider: OrBoolean? = null, + + /** + * The server provides document highlight support. + */ + val documentHighlightProvider: OrBoolean? = null, + + /** + * The server provides document symbol support. + */ + val documentSymbolProvider: OrBoolean? = null, + + /** + * The server provides code actions. The `CodeActionOptions` return type is + * only valid if the client signals code action literal support via the + * property `textDocument.codeAction.codeActionLiteralSupport`. + */ + val codeActionProvider: OrBoolean? = null, + + /** + * The server provides code lens. + */ + val codeLensProvider: CodeLensOptions? = null, + + /** + * The server provides document link support. + */ + val documentLinkProvider: DocumentLinkOptions? = null, + + /** + * The server provides color provider support. + * + * @since 3.6.0 + */ + val colorProvider: OrBoolean? = null, + + /** + * The server provides document formatting. + */ + val documentFormattingProvider: OrBoolean? = null, + + /** + * The server provides document range formatting. + */ + val documentRangeFormattingProvider: OrBoolean? = null, + + /** + * The server provides document formatting on typing. + */ + val documentOnTypeFormattingProvider: DocumentOnTypeFormattingOptions? = null, + + /** + * The server provides rename support. RenameOptions may only be + * specified if the client states that it supports + * `prepareSupport` in its initial `initialize` request. + */ + val renameProvider: OrBoolean? = null, + + /** + * The server provides folding provider support. + * + * @since 3.10.0 + */ + val foldingRangeProvider: OrBoolean? = null, + + /** + * The server provides execute command support. + */ + val executeCommandProvider: ExecuteCommandOptions? = null, + + /** + * The server provides selection range support. + * + * @since 3.15.0 + */ + val selectionRangeProvider: OrBoolean? = null, + + /** + * The server provides linked editing range support. + * + * @since 3.16.0 + */ + val linkedEditingRangeProvider: OrBoolean? = null, + + /** + * The server provides call hierarchy support. + * + * @since 3.16.0 + */ + val callHierarchyProvider: OrBoolean? = null, + + /** + * The server provides semantic tokens support. + * + * @since 3.16.0 + */ + val semanticTokensProvider: SemanticTokensOptions? = null, // SemanticTokensOptions | SemanticTokensRegistrationOptions + + /** + * Whether server provides moniker support. + * + * @since 3.16.0 + */ + val monikerProvider: OrBoolean? = null, + + /** + * The server provides type hierarchy support. + * + * @since 3.17.0 + */ + val typeHierarchyProvider: OrBoolean? = null, + + /** + * The server provides inline values. + * + * @since 3.17.0 + */ + val inlineValueProvider: OrBoolean? = null, + + /** + * The server provides inlay hints. + * + * @since 3.17.0 + */ + val inlayHintProvider: OrBoolean? = null, + + /** + * The server has support for pull model diagnostics. + * + * @since 3.17.0 + */ + val diagnosticProvider: DiagnosticOptions? = null, + + /** + * The server provides workspace symbol support. + */ + val workspaceSymbolProvider: OrBoolean? = null, + + /** + * Workspace specific server capabilities + */ + val workspace: ServerWorkspaceCapabilities? = null, + + /** + * Experimental server capabilities. + */ + val experimental: JsonElement? = null, +) + + +typealias HoverOptions = Unknown +typealias SignatureHelpOptions = Unknown +typealias TypeDefinitionOptions = Unknown +typealias ImplementationOptions = Unknown +typealias DocumentHighlightOptions = Unknown +typealias DocumentSymbolOptions = Unknown +typealias CodeLensOptions = Unknown +typealias DocumentLinkOptions = Unknown +typealias DocumentColorOptions = Unknown +typealias DocumentFormattingOptions = Unknown +typealias DocumentRangeFormattingOptions = Unknown +typealias DocumentOnTypeFormattingOptions = Unknown +typealias RenameOptions = Unknown +typealias FoldingRangeOptions = Unknown +typealias SelectionRangeOptions = Unknown +typealias LinkedEditingRangeOptions = Unknown +typealias CallHierarchyOptions = Unknown +typealias SemanticTokensRegistrationOptions = Unknown +typealias MonikerOptions = Unknown +typealias TypeHierarchyOptions = Unknown +typealias InlineValueOptions = Unknown +typealias InlayHintOptions = Unknown + +@Serializable +data class SemanticTokensOptions( + /** + * The legend used by the server + */ + val legend: SemanticTokensLegend, + + /** + * Server supports providing semantic tokens for a specific range + * of a document. + */ + val range: Boolean?, + + /** + * Server supports providing semantic tokens for a full document. + */ + val full: Boolean?, +) + +@Serializable +data class ServerWorkspaceCapabilities( + /** + * The server supports workspace folder. + * + * @since 3.6.0 + */ + val workspaceFolders: WorkspaceFoldersServerCapabilities? = null, + + /** + * The server is interested in file notifications/requests. + * + * @since 3.16.0 + */ + val fileOperations: FileOperations? = null, +) + +@Serializable +data class WorkspaceFoldersServerCapabilities( + /** + * The server has support for workspace folders + */ + val supported: Boolean? = null, + + /** + * Whether the server wants to receive workspace folder + * change notifications. + * + * If a string is provided, the string is treated as an ID + * under which the notification is registered on the client + * side. The ID can be used to unregister for these events + * using the `client/unregisterCapability` request. + */ + val changeNotifications: JsonElement? = null // String or Boolean +) + +/** + * A pattern to describe in which file operation requests or notifications + * the server is interested in. + * + * @since 3.16.0 + */ +@Serializable +data class FileOperationPattern( + /** + * The glob pattern to match. Glob patterns can have the following syntax: + * - `*` to match one or more characters in a path segment + * - `?` to match on one character in a path segment + * - `**` to match any number of path segments, including none + * - `{}` to group sub patterns into an OR expression. + * matches all TypeScript and JavaScript files) + * - `[]` to declare a range of characters to match in a path segment + * (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …) + * - `[!...]` to negate a range of characters to match in a path segment + * (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but + * not `example.0`) + */ + val glob: String, + + /** + * Whether to match files or folders with this pattern. + * + * Matches both if undefined. + */ + val matches: FileOperationPatternKind? = null, + + /** + * Additional options used during matching. + */ + val options: FileOperationPatternOptions? = null +) + +/** + * Matching options for the file operation pattern. + * + * @since 3.16.0 + */ +@Serializable +data class FileOperationPatternOptions( + /** + * The pattern should be matched ignoring casing. + */ + val ignoreCase: Boolean? = null +) + +@Serializable +enum class FileOperationPatternKind { + /** + * The pattern matches a file only. + * + * @since 3.16.0 + */ + @SerialName("file") + FILE, + + /** + * The pattern matches a folder only. + * + * @since 3.16.0 + */ + @SerialName("folder") + FOLDER +} + +/** + * A filter to describe in which file operation requests or notifications + * the server is interested in. + * + * @since 3.16.0 + */ +@Serializable +data class FileOperationFilter( + /** + * A Uri like `file` or `untitled`. + */ + val scheme: String? = null, + + /** + * The actual file operation pattern. + */ + val pattern: FileOperationPattern +) + +/** + * The options to register for file operations. + * + * @since 3.16.0 + */ +@Serializable +data class FileOperationRegistrationOptions( + /** + * The actual filters. + */ + val filters: List +) + +@Serializable +data class FileOperations( + /** + * The server is interested in receiving didCreateFiles + * notifications. + */ + val didCreate: FileOperationRegistrationOptions? = null, + + /** + * The server is interested in receiving willCreateFiles requests. + */ + val willCreate: FileOperationRegistrationOptions? = null, + + /** + * The server is interested in receiving didRenameFiles + * notifications. + */ + val didRename: FileOperationRegistrationOptions? = null, + + /** + * The server is interested in receiving willRenameFiles requests. + */ + val willRename: FileOperationRegistrationOptions? = null, + + /** + * The server is interested in receiving didDeleteFiles file + * notifications. + */ + val didDelete: FileOperationRegistrationOptions? = null, + + /** + * The server is interested in receiving willDeleteFiles file + * requests. + */ + val willDelete: FileOperationRegistrationOptions? = null, +) \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Shutdown.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Shutdown.kt new file mode 100644 index 000000000000..768a9f96b002 --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Shutdown.kt @@ -0,0 +1,10 @@ +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.builtins.serializer + +val Shutdown: RequestType = RequestType( + "shutdown", + Unit.serializer(), + Unit.serializer(), + Unit.serializer() +) diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/TextDocumentClientCapabilities.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/TextDocumentClientCapabilities.kt new file mode 100644 index 000000000000..da013a30cba7 --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/TextDocumentClientCapabilities.kt @@ -0,0 +1,211 @@ +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.Serializable + + +@Serializable +data class TextDocumentClientCapabilities( + /** + * Capabilities specific to the `textDocument/synchronization` request. + */ + val synchronization: TextDocumentSyncClientCapabilities? = null, + + /** + * Capabilities specific to the `textDocument/completion` request. + */ + val completion: CompletionClientCapabilities? = null, + + /** + * Capabilities specific to the `textDocument/hover` request. + */ + val hover: HoverClientCapabilities? = null, + + /** + * Capabilities specific to the `textDocument/signatureHelp` request. + */ + val signatureHelp: SignatureHelpClientCapabilities? = null, + + /** + * Capabilities specific to the `textDocument/declaration` request. + * + * @since 3.14.0 + */ + val declaration: DeclarationClientCapabilities? = null, + + /** + * Capabilities specific to the `textDocument/definition` request. + */ + val definition: DefinitionClientCapabilities? = null, + + /** + * Capabilities specific to the `textDocument/typeDefinition` request. + * + * @since 3.6.0 + */ + val typeDefinition: TypeDefinitionClientCapabilities? = null, + + /** + * Capabilities specific to the `textDocument/implementation` request. + * + * @since 3.6.0 + */ + val implementation: ImplementationClientCapabilities? = null, + + /** + * Capabilities specific to the `textDocument/references` request. + */ + val references: ReferenceClientCapabilities? = null, + + /** + * Capabilities specific to the `textDocument/documentHighlight` request. + */ + val documentHighlight: DocumentHighlightClientCapabilities? = null, + + /** + * Capabilities specific to the `textDocument/documentSymbol` request. + */ + val documentSymbol: DocumentSymbolClientCapabilities? = null, + + /** + * Capabilities specific to the `textDocument/codeAction` request. + */ + val codeAction: CodeActionClientCapabilities? = null, + + /** + * Capabilities specific to the `textDocument/codeLens` request. + */ + val codeLens: CodeLensClientCapabilities? = null, + + /** + * Capabilities specific to the `textDocument/documentLink` request. + */ + val documentLink: DocumentLinkClientCapabilities? = null, + + /** + * Capabilities specific to the `textDocument/documentColor` and the + * `textDocument/colorPresentation` request. + * + * @since 3.6.0 + */ + val colorProvider: DocumentColorClientCapabilities? = null, + + /** + * Capabilities specific to the `textDocument/formatting` request. + */ + val formatting: DocumentFormattingClientCapabilities? = null, + + /** + * Capabilities specific to the `textDocument/rangeFormatting` request. + */ + val rangeFormatting: DocumentRangeFormattingClientCapabilities? = null, + + /** + * Capabilities specific to the `textDocument/onTypeFormatting` request. + */ + val onTypeFormatting: DocumentOnTypeFormattingClientCapabilities? = null, + + /** + * Capabilities specific to the `textDocument/rename` request. + */ + val rename: RenameClientCapabilities? = null, + + /** + * Capabilities specific to the `textDocument/publishDiagnostics` notification. + */ + val publishDiagnostics: PublishDiagnosticsClientCapabilities? = null, + + /** + * Capabilities specific to the `textDocument/foldingRange` request. + * + * @since 3.10.0 + */ + val foldingRange: FoldingRangeClientCapabilities? = null, + + /** + * Capabilities specific to the `textDocument/selectionRange` request. + * + * @since 3.15.0 + */ + val selectionRange: SelectionRangeClientCapabilities? = null, + + /** + * Capabilities specific to the `textDocument/linkedEditingRange` request. + * + * @since 3.16.0 + */ + val linkedEditingRange: LinkedEditingRangeClientCapabilities? = null, + + /** + * Capabilities specific to the various call hierarchy requests. + * + * @since 3.16.0 + */ + val callHierarchy: CallHierarchyClientCapabilities? = null, + + /** + * Capabilities specific to the various semantic token requests. + * + * @since 3.16.0 + */ + val semanticTokens: SemanticTokensClientCapabilities? = null, + + /** + * Capabilities specific to the `textDocument/moniker` request. + * + * @since 3.16.0 + */ + val moniker: MonikerClientCapabilities? = null, + + /** + * Capabilities specific to the various type hierarchy requests. + * + * @since 3.17.0 + */ + val typeHierarchy: TypeHierarchyClientCapabilities? = null, + + /** + * Capabilities specific to the `textDocument/inlineValue` request. + * + * @since 3.17.0 + */ + val inlineValue: InlineValueClientCapabilities? = null, + + /** + * Capabilities specific to the `textDocument/inlayHint` request. + * + * @since 3.17.0 + */ + val inlayHint: InlayHintClientCapabilities? = null, + + /** + * Capabilities specific to the diagnostic pull model. + * + * @since 3.17.0 + */ + val diagnostic: DiagnosticClientCapabilities? = null +) + +typealias HoverClientCapabilities = Unknown +typealias SignatureHelpClientCapabilities = Unknown +typealias TypeDefinitionClientCapabilities = Unknown +typealias ImplementationClientCapabilities = Unknown +typealias ReferenceClientCapabilities = Unknown +typealias DocumentHighlightClientCapabilities = Unknown +typealias DocumentSymbolClientCapabilities = Unknown +typealias CodeActionClientCapabilities = Unknown +typealias CodeLensClientCapabilities = Unknown +typealias DocumentLinkClientCapabilities = Unknown +typealias DocumentColorClientCapabilities = Unknown +typealias DocumentFormattingClientCapabilities = Unknown +typealias DocumentRangeFormattingClientCapabilities = Unknown +typealias DocumentOnTypeFormattingClientCapabilities = Unknown +typealias RenameClientCapabilities = Unknown +typealias FoldingRangeClientCapabilities = Unknown +typealias SelectionRangeClientCapabilities = Unknown +typealias LinkedEditingRangeClientCapabilities = Unknown +typealias CallHierarchyClientCapabilities = Unknown +typealias SemanticTokensClientCapabilities = Unknown +typealias MonikerClientCapabilities = Unknown +typealias TypeHierarchyClientCapabilities = Unknown +typealias InlineValueClientCapabilities = Unknown +typealias InlayHintClientCapabilities = Unknown \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Workspace.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Workspace.kt new file mode 100644 index 000000000000..db6a47066c78 --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/Workspace.kt @@ -0,0 +1,54 @@ +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.json.JsonElement + +@Serializable +data class DidChangeWorkspaceFoldersParams( + /** + * The actual workspace folder change event. + */ + val event: WorkspaceFoldersChangeEvent +) + +/** + * The workspace folder change event. + */ +@Serializable +data class WorkspaceFoldersChangeEvent( + /** + * The list of added workspace folders. + */ + val added: List, + + /** + * The list of removed workspace folders. + */ + val removed: List +) + +@Serializable +data class DidChangeConfigurationParams( + /** + * The actual changed settings + */ + val settings: JsonElement +) + +object Workspace { + val WorkspaceFolders: RequestType, Unit> = + RequestType("workspace/folders", Unit.serializer(), ListSerializer(WorkspaceFolder.serializer()), Unit.serializer()) + val DidChangeWorkspaceFolders: NotificationType = + NotificationType("workspace/didChangeWorkspaceFolders", DidChangeWorkspaceFoldersParams.serializer()) + val DidChangeConfiguration: NotificationType = + NotificationType("workspace/didChangeConfiguration", DidChangeConfigurationParams.serializer()) + + // val DidChangeWatchedFiles: NotificationType = + // NotificationType("workspace/didChangeWatchedFiles", DidChangeWatchedFilesParams.serializer()) + // val ExecuteCommand: RequestType = + // RequestType("workspace/executeCommand", ExecuteCommandParams.serializer(), ExecuteCommandResponse.serializer().nullable, ExecuteCommandError.serializer()) + // val Symbol: RequestType, Unit> = + // RequestType("workspace/symbol", SymbolParams.serializer(), ListSerializer(SymbolInformation.serializer()), Unit.serializer()) +} \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/references.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/references.kt new file mode 100644 index 000000000000..f81e61f5120f --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/references.kt @@ -0,0 +1,28 @@ +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.builtins.serializer + + +interface ReferenceOptions : WorkDoneProgressOptions + +@Serializable +data class ReferenceParams( + override val textDocument: TextDocumentIdentifier, + override val position: Position, + val context: ReferenceContext, + override val workDoneToken: ProgressToken?, + override val partialResultToken: ProgressToken?, +) : TextDocumentPositionParams, WorkDoneProgressParams, PartialResultParams + +@Serializable +data class ReferenceContext( + /** + * Include the declaration of the current symbol. + */ + val includeDeclaration: Boolean, +) + +val ReferenceRequestType: RequestType, Unit> = + RequestType("textDocument/references", ReferenceParams.serializer(), ListSerializer(Location.serializer()), Unit.serializer()) \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/symbols.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/symbols.kt new file mode 100644 index 000000000000..3a6717c49a14 --- /dev/null +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/protocol/symbols.kt @@ -0,0 +1,237 @@ +package com.jetbrains.lsp.protocol + +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.json.JsonElement + +@Serializable +data class WorkspaceSymbolOptions( + /** + * The server provides support to resolve additional + * information for a workspace symbol. + * + * @since 3.17.0 + */ + val resolveProvider: Boolean?, + override val workDoneProgress: Boolean?, +) : WorkDoneProgressOptions + +/** + * The parameters of a Workspace Symbol Request. + */ +@Serializable +data class WorkspaceSymbolParams( + /** + * A query string to filter symbols by. Clients may send an empty + * string here to request all symbols. + */ + val query: String, + override val partialResultToken: ProgressToken?, + override val workDoneToken: ProgressToken?, +) : WorkDoneProgressParams, PartialResultParams + + +@Serializable +data class DocumentSymbolParams( + /** + * The text document. + */ + val textDocument: TextDocumentIdentifier, + override val partialResultToken: ProgressToken?, + override val workDoneToken: ProgressToken? +): WorkDoneProgressParams, PartialResultParams + +/** + * A special workspace symbol that supports locations without a range + * + * @since 3.17.0 + */ +@Serializable +data class WorkspaceSymbol( + /** + * The name of this symbol. + */ + val name: String, + + /** + * The kind of this symbol. + */ + val kind: SymbolKind, + + /** + * Tags for this completion item. + */ + val tags: List?, + + /** + * The name of the symbol containing this symbol. This information is for + * user interface purposes (e.g. to render a qualifier in the user interface + * if necessary). It can't be used to re-infer a hierarchy for the document + * symbols. + */ + val containerName: String?, + + /** + * The location of this symbol. Whether a server is allowed to + * return a location without a range depends on the client + * capability `workspace.symbol.resolveSupport`. + * + * See also `SymbolInformation.location`. + */ + val location: Location, // TODO | { uri: DocumentUri }, + + /** + * A data entry field that is preserved on a workspace symbol between a + * workspace symbol request and a workspace symbol resolve request. + */ + val data: JsonElement?, +) + +/** + * Represents programming constructs like variables, classes, interfaces etc. + * that appear in a document. Document symbols can be hierarchical and they + * have two ranges: one that encloses its definition and one that points to its + * most interesting range, e.g. the range of an identifier. + */ +@Serializable +data class DocumentSymbol( + + /** + * The name of this symbol. Will be displayed in the user interface and + * therefore must not be an empty string or a string only consisting of + * white spaces. + */ + val name: String, + + /** + * More detail for this symbol, e.g the signature of a function. + */ + val detail: String?, + + /** + * The kind of this symbol. + */ + val kind: SymbolKind, + + /** + * Tags for this document symbol. + * + * @since 3.16.0 + */ + val tags: List?, + + /** + * Indicates if this symbol is deprecated. + * + * @deprecated Use tags instead + */ + val deprecated: Boolean?, + + /** + * The range enclosing this symbol not including leading/trailing whitespace + * but everything else like comments. This information is typically used to + * determine if the clients cursor is inside the symbol to reveal in the + * symbol in the UI. + */ + val range: Range, + + /** + * The range that should be selected and revealed when this symbol is being + * picked, e.g. the name of a function. Must be contained by the `range`. + */ + val selectionRange: Range, + + /** + * Children of this symbol, e.g. properties of a class. + */ + val children: List? +) + +/** + * A symbol kind. + */ +@Serializable(SymbolKind.Serializer::class) +enum class SymbolKind(val value: Int) { + File(1), + Module(2), + Namespace(3), + Package(4), + Class(5), + Method(6), + Property(7), + Field(8), + Constructor(9), + Enum(10), + Interface(11), + Function(12), + Variable(13), + Constant(14), + String(15), + Number(16), + Boolean(17), + Array(18), + Object(19), + Key(20), + Null(21), + EnumMember(22), + Struct(23), + Event(24), + Operator(25), + TypeParameter(26), + + ; + + class Serializer : EnumAsIntSerializer( + serialName = SymbolKind::class.simpleName!!, + serialize = SymbolKind::value, + deserialize = { SymbolKind.entries[it - 1] }, + ) +} + +/** + * Symbol tags are extra annotations that tweak the rendering of a symbol. + * + * @since 3.16 + */ +@Serializable(SymbolTag.Serializer::class) +enum class SymbolTag(val value: Int) { + /** + * Render a symbol as obsolete, usually using a strike-out. + */ + Deprecated(1), + + ; + + class Serializer : EnumAsIntSerializer( + serialName = SymbolTag::class.simpleName!!, + serialize = SymbolTag::value, + deserialize = { SymbolTag.entries[it - 1] }, + ) +} + + +object WorkspaceSymbolRequests { + val WorkspaceSymbolRequest: RequestType, Unit> = + RequestType( + "workspace/symbol", + WorkspaceSymbolParams.serializer(), + ListSerializer(WorkspaceSymbol.serializer()), + Unit.serializer(), + ) + + val WorkspaceSymbolResolveRequest: RequestType = + RequestType( + "workspace/symbol/resolve", + WorkspaceSymbolParams.serializer(), + WorkspaceSymbol.serializer(), + Unit.serializer(), + ) +} + +val DocumentSymbolRequest = RequestType( + "textDocument/documentSymbol", + DocumentSymbolParams.serializer(), + ListSerializer(DocumentSymbol.serializer()), + Unit.serializer(), +) \ No newline at end of file