From c38b281fecb1d08bccd421aa8a9c8d46871a23af Mon Sep 17 00:00:00 2001 From: Ludwig Valda Vasquez Date: Tue, 29 Jul 2025 17:34:58 +0200 Subject: [PATCH] [lsp] remove the Context generic in handlers The LSP library was earlier augmented to be generic over the handler context (the one that is supplied to request and notification handlers). However, this makes handlers potentially incompatible with each other and hinders composition. After a review it was decided to remove this generic parameter. GitOrigin-RevId: 8902b147f0027425e47b2188fe4d9781b77bbe07 --- .../lsp/implementation/LspHandlers.kt | 56 ++++++++++++------- .../lsp/implementation/protocolFraming.kt | 4 +- .../jetbrains/lsp/implementation/withLsp.kt | 35 ++++++------ 3 files changed, 57 insertions(+), 38 deletions(-) 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 index 0e2937cdf1a4..ef2fddf200ae 100644 --- a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/LspHandlers.kt +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/LspHandlers.kt @@ -12,27 +12,27 @@ import kotlinx.coroutines.CoroutineScope interface LspHandlers { fun requestHandler( requestTypeName: String, - ): LspRequestHandler<*, *, *, *>? + ): LspRequestHandler<*, *, *>? fun notificationHandler( notificationTypeName: String, - ): LspNotificationHandler<*, *>? + ): LspNotificationHandler<*>? object EMPTY : LspHandlers { - override fun requestHandler(requestTypeName: String): LspRequestHandler<*, *, *, *>? = null - override fun notificationHandler(notificationTypeName: String): LspNotificationHandler<*, *>? = null + override fun requestHandler(requestTypeName: String): LspRequestHandler<*, *, *>? = null + override fun notificationHandler(notificationTypeName: String): LspNotificationHandler<*>? = null } } -interface LspHandlersBuilder { +interface LspHandlersBuilder { fun request( requestType: RequestType, - handler: suspend context(T) CoroutineScope.(Request) -> Response, + handler: suspend context(LspHandlerContext) CoroutineScope.(Request) -> Response, ) fun notification( notifcationType: NotificationType, - handler: suspend context(T) CoroutineScope.(Notification) -> Unit, + handler: suspend context(LspHandlerContext) CoroutineScope.(Notification) -> Unit, ) } @@ -61,31 +61,49 @@ fun LspClient.reportProgressMessage( } -class LspRequestHandler( +class LspRequestHandler( val requestType: RequestType, - val handler: suspend context(Context) CoroutineScope.(Params) -> Result, + val handler: suspend context(LspHandlerContext) CoroutineScope.(Params) -> Result, ) -class LspNotificationHandler( +class LspNotificationHandler( val notificationType: NotificationType, - val handler: suspend context(Context) CoroutineScope.(Params) -> Unit, + val handler: suspend context(LspHandlerContext) CoroutineScope.(Params) -> Unit, ) -fun lspHandlers(builder: LspHandlersBuilder.() -> Unit): LspHandlers { - val requests = mutableMapOf>() - val notifications = mutableMapOf>() +interface LspHandlersMiddleware { + fun requestHandler(handler: LspRequestHandler): LspRequestHandler - object : LspHandlersBuilder { + fun

notificationHandler(handler: LspNotificationHandler

): LspNotificationHandler

+ + companion object { + val IDENTITY: LspHandlersMiddleware = object : LspHandlersMiddleware { + override fun requestHandler(handler: LspRequestHandler): LspRequestHandler { + return handler + } + + override fun

notificationHandler(handler: LspNotificationHandler

): LspNotificationHandler

{ + return handler + } + } + } +} + +fun lspHandlers(builder: LspHandlersBuilder.() -> Unit): LspHandlers { + val requests = mutableMapOf>() + val notifications = mutableMapOf>() + + object : LspHandlersBuilder { override fun request( requestType: RequestType, - handler: suspend context(T) CoroutineScope.(Request) -> Response, + handler: suspend context(LspHandlerContext) CoroutineScope.(Request) -> Response, ) { requests[requestType.method] = LspRequestHandler(requestType, handler) } override fun notification( notifcationType: NotificationType, - handler: suspend context(T) CoroutineScope.(Notification) -> Unit, + handler: suspend context(LspHandlerContext) CoroutineScope.(Notification) -> Unit, ) { notifications[notifcationType.method] = LspNotificationHandler(notifcationType, handler) } @@ -95,13 +113,13 @@ fun lspHandlers(builder: LspHandlersBuilder.() -> Unit): LspHandlers { @Suppress("UNCHECKED_CAST") override fun requestHandler( requestType: String, - ): LspRequestHandler<*, *, *, T>? = + ): LspRequestHandler<*, *, *>? = requests[requestType] @Suppress("UNCHECKED_CAST") override fun notificationHandler( notificationType: String, - ): LspNotificationHandler<*, T>? = + ): LspNotificationHandler<*>? = notifications[notificationType] } } \ No newline at end of file diff --git a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/protocolFraming.kt b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/protocolFraming.kt index 3f095cb46155..f946f783ff0a 100644 --- a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/protocolFraming.kt +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/protocolFraming.kt @@ -126,7 +126,7 @@ suspend fun withBaseProtocolFraming( } fun main() { - val handler = lspHandlers { + val handler = lspHandlers { request(Initialize) { initParams -> InitializeResult( capabilities = ServerCapabilities( @@ -148,7 +148,7 @@ fun main() { runBlocking(Dispatchers.Default) { tcpServer(TcpConnectionConfig.Server("127.0.0.1", 9999, isMultiClient = true)) { connection -> withBaseProtocolFraming(connection, exitSignal = null) { incoming, outgoing -> - withLsp(incoming, outgoing, handler, ::LspHandlerContext) { lsp -> + withLsp(incoming, outgoing, handler) { lsp -> awaitCancellation() } } 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 index 04f0906605f8..2ccd68987933 100644 --- a/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/withLsp.kt +++ b/fleet/lsp.protocol/src/main/com/jetbrains/lsp/implementation/withLsp.kt @@ -33,13 +33,13 @@ private class OutgoingRequest( val requestType: RequestType<*, *, *>, ) -suspend fun withLsp( +suspend fun withLsp( incoming: ReceiveChannel, outgoing: SendChannel, handlers: LspHandlers, - createContext: (LspClient) -> T, + middleware: LspHandlersMiddleware = LspHandlersMiddleware.IDENTITY, createCoroutineContext: (LspClient) -> CoroutineContext = { EmptyCoroutineContext }, - body: suspend CoroutineScope.(T) -> Unit, + body: suspend CoroutineScope.(LspClient) -> Unit, ) { coroutineScope { val outgoingRequests = ConcurrentHashMap() @@ -87,7 +87,7 @@ suspend fun withLsp( } } - val lspHandlerContext = createContext(lspClient) + val lspHandlerContext = LspHandlerContext(lspClient) launch(createCoroutineContext(lspClient)) { withSupervisor { supervisor -> @@ -102,6 +102,7 @@ suspend fun withLsp( val request = LSP.json.decodeFromJsonElement(RequestMessage.serializer(), jsonMessage) supervisor.launch(start = CoroutineStart.ATOMIC) { val maybeHandler = handlers.requestHandler(request.method) + ?.let { handler -> middleware.requestHandler(handler)} runCatching { val handler = requireNotNull(maybeHandler) { "no handler for request: ${request.method}" @@ -109,7 +110,7 @@ suspend fun withLsp( val deserializedParams = request.params?.let { params -> LSP.json.decodeFromJsonElement(handler.requestType.paramsSerializer, params) } - val result = (handler as LspRequestHandler).handler( + val result = (handler as LspRequestHandler).handler( lspHandlerContext, this, deserializedParams @@ -241,15 +242,16 @@ suspend fun withLsp( else -> runCatching { - when (val handler = handlers.notificationHandler(notification.method)) { + when (val originalHandler = handlers.notificationHandler(notification.method)) { null -> LOG.debug("no handler for notification: ${notification.method}") else -> { + val handler = middleware.notificationHandler(originalHandler) val deserializedParams = notification.params?.let { params -> LSP.json.decodeFromJsonElement(handler.notificationType.paramsSerializer, params) } - (handler as LspNotificationHandler).handler(lspHandlerContext, this, deserializedParams) + (handler as LspNotificationHandler).handler(lspHandlerContext, this, deserializedParams) } } }.onFailure { error -> @@ -266,7 +268,7 @@ suspend fun withLsp( } } }.use { - body(lspHandlerContext) + body(lspHandlerContext.lspClient) } } } @@ -278,10 +280,11 @@ fun main() { 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 { + handlers = lspHandlers { request(HelloRequestType) { str -> "Hello, $str" } @@ -297,23 +300,21 @@ fun main() { } } }, - createContext = ::LspHandlerContext, ) { server -> withLsp( incoming = serverToClient, outgoing = clientToServer, - handlers = lspHandlers { + handlers = lspHandlers { notification(PrintHelloNotification) { str -> println("client: $str") } }, - createContext = ::LspHandlerContext, - ) { context -> - println(context.lspClient.request(HelloRequestType, "World")) - context.lspClient.notify(PrintHelloNotification, "Hello World") - context.lspClient.notify(PrintHelloNotification, "Hello World") + ) { client -> + println(client.request(HelloRequestType, "World")) + client.notify(PrintHelloNotification, "Hello World") + client.notify(PrintHelloNotification, "Hello World") val hangingRequestJob = launch { - context.lspClient.request(HangRequestType, Unit) + client.request(HangRequestType, Unit) } delay(100) hangingRequestJob.cancel()