[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
This commit is contained in:
Ludwig Valda Vasquez
2025-07-29 17:25:37 +00:00
committed by intellij-monorepo-bot
parent 98090b220c
commit c38b281fec
3 changed files with 57 additions and 38 deletions
@@ -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<T> {
interface LspHandlersBuilder {
fun <Request, Response, Error> request(
requestType: RequestType<Request, Response, Error>,
handler: suspend context(T) CoroutineScope.(Request) -> Response,
handler: suspend context(LspHandlerContext) CoroutineScope.(Request) -> Response,
)
fun <Notification> notification(
notifcationType: NotificationType<Notification>,
handler: suspend context(T) CoroutineScope.(Notification) -> Unit,
handler: suspend context(LspHandlerContext) CoroutineScope.(Notification) -> Unit,
)
}
@@ -61,31 +61,49 @@ fun LspClient.reportProgressMessage(
}
class LspRequestHandler<Params, Result, Error, Context>(
class LspRequestHandler<Params, Result, Error>(
val requestType: RequestType<Params, Result, Error>,
val handler: suspend context(Context) CoroutineScope.(Params) -> Result,
val handler: suspend context(LspHandlerContext) CoroutineScope.(Params) -> Result,
)
class LspNotificationHandler<Params, Context>(
class LspNotificationHandler<Params>(
val notificationType: NotificationType<Params>,
val handler: suspend context(Context) CoroutineScope.(Params) -> Unit,
val handler: suspend context(LspHandlerContext) CoroutineScope.(Params) -> Unit,
)
fun <T> lspHandlers(builder: LspHandlersBuilder<T>.() -> Unit): LspHandlers {
val requests = mutableMapOf<String, LspRequestHandler<*, *, *, T>>()
val notifications = mutableMapOf<String, LspNotificationHandler<*, T>>()
interface LspHandlersMiddleware {
fun <P, R, E> requestHandler(handler: LspRequestHandler<P, R, E>): LspRequestHandler<P, R, E>
object : LspHandlersBuilder<T> {
fun <P> notificationHandler(handler: LspNotificationHandler<P>): LspNotificationHandler<P>
companion object {
val IDENTITY: LspHandlersMiddleware = object : LspHandlersMiddleware {
override fun <P, R, E> requestHandler(handler: LspRequestHandler<P, R, E>): LspRequestHandler<P, R, E> {
return handler
}
override fun <P> notificationHandler(handler: LspNotificationHandler<P>): LspNotificationHandler<P> {
return handler
}
}
}
}
fun lspHandlers(builder: LspHandlersBuilder.() -> Unit): LspHandlers {
val requests = mutableMapOf<String, LspRequestHandler<*, *, *>>()
val notifications = mutableMapOf<String, LspNotificationHandler<*>>()
object : LspHandlersBuilder {
override fun <Request, Response, Error> request(
requestType: RequestType<Request, Response, Error>,
handler: suspend context(T) CoroutineScope.(Request) -> Response,
handler: suspend context(LspHandlerContext) CoroutineScope.(Request) -> Response,
) {
requests[requestType.method] = LspRequestHandler(requestType, handler)
}
override fun <Notification> notification(
notifcationType: NotificationType<Notification>,
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 <T> lspHandlers(builder: LspHandlersBuilder<T>.() -> 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]
}
}
@@ -126,7 +126,7 @@ suspend fun withBaseProtocolFraming(
}
fun main() {
val handler = lspHandlers<LspHandlerContext> {
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()
}
}
@@ -33,13 +33,13 @@ private class OutgoingRequest(
val requestType: RequestType<*, *, *>,
)
suspend fun <T> withLsp(
suspend fun withLsp(
incoming: ReceiveChannel<JsonElement>,
outgoing: SendChannel<JsonElement>,
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<StringOrInt, OutgoingRequest>()
@@ -87,7 +87,7 @@ suspend fun <T> withLsp(
}
}
val lspHandlerContext = createContext(lspClient)
val lspHandlerContext = LspHandlerContext(lspClient)
launch(createCoroutineContext(lspClient)) {
withSupervisor { supervisor ->
@@ -102,6 +102,7 @@ suspend fun <T> 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 <T> withLsp(
val deserializedParams = request.params?.let { params ->
LSP.json.decodeFromJsonElement(handler.requestType.paramsSerializer, params)
}
val result = (handler as LspRequestHandler<Any?, Any?, Any?, T>).handler(
val result = (handler as LspRequestHandler<Any?, Any?, Any?>).handler(
lspHandlerContext,
this,
deserializedParams
@@ -241,15 +242,16 @@ suspend fun <T> 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<Any?, T>).handler(lspHandlerContext, this, deserializedParams)
(handler as LspNotificationHandler<Any?>).handler(lspHandlerContext, this, deserializedParams)
}
}
}.onFailure { error ->
@@ -266,7 +268,7 @@ suspend fun <T> 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<LspHandlerContext> {
handlers = lspHandlers {
request(HelloRequestType) { str ->
"Hello, $str"
}
@@ -297,23 +300,21 @@ fun main() {
}
}
},
createContext = ::LspHandlerContext,
) { server ->
withLsp(
incoming = serverToClient,
outgoing = clientToServer,
handlers = lspHandlers<LspHandlerContext> {
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()