scala.meta.jsonrpc.LanguageServer.scala Maven / Gradle / Ivy
The newest version!
package scala.meta.jsonrpc
import io.circe.Json
import io.circe.jawn.parseByteBuffer
import io.circe.syntax._
import java.nio.ByteBuffer
import monix.eval.Task
import monix.execution.Cancelable
import monix.execution.Scheduler
import monix.reactive.Observable
import scala.collection.concurrent.TrieMap
import scala.concurrent.Await
import scala.concurrent.duration.Duration
import scala.util.control.NonFatal
import scribe.LoggerSupport
final class LanguageServer(
in: Observable[BaseProtocolMessage],
client: LanguageClient,
services: Services,
requestScheduler: Scheduler,
logger: LoggerSupport
) {
private val activeClientRequests: TrieMap[Json, Cancelable] = TrieMap.empty
private val cancelNotification =
Service.notification[CancelParams]("$/cancelRequest", logger) {
new Service[CancelParams, Unit] {
def handle(params: CancelParams): Task[Unit] = {
val id = params.id
activeClientRequests.get(id) match {
case None =>
Task {
logger.warn(
s"Can't cancel request $id, no active request found."
)
Response.empty
}
case Some(request) =>
Task {
logger.info(s"Cancelling request $id")
request.cancel()
activeClientRequests.remove(id)
Response.cancelled(id)
}
}
}
}
}
private val handlersByMethodName: Map[String, NamedJsonRpcService] =
services.addService(cancelNotification).byMethodName
def handleValidMessage(message: Message): Task[Response] = message match {
case response: Response =>
Task {
client.clientRespond(response)
Response.empty
}
case Notification(method, _) =>
handlersByMethodName.get(method) match {
case None =>
Task {
// Can't respond to invalid notifications
logger.error(s"Unknown method '$method'")
Response.empty
}
case Some(handler) =>
handler
.handle(message)
.map {
case Response.Empty => Response.empty
case nonEmpty =>
logger.error(
s"Obtained non-empty response $nonEmpty for notification $message. " +
s"Expected Response.empty"
)
Response.empty
}
.onErrorRecover {
case NonFatal(e) =>
logger.error(s"Error handling notification $message", e)
Response.empty
}
}
case request @ Request(method, _, id) =>
handlersByMethodName.get(method) match {
case None =>
Task {
logger.info(s"Method not found '$method'")
Response.methodNotFound(method, id)
}
case Some(handler) =>
val response = handler.handle(request).onErrorRecover {
case NonFatal(e) =>
logger.error(s"Unhandled error handling request $request", e)
Response.internalError(e.getMessage, request.id)
}
val runningResponse = response.runAsync(requestScheduler)
activeClientRequests.put(request.id.asJson, runningResponse)
Task.fromFuture(runningResponse)
}
}
def handleMessage(message: BaseProtocolMessage): Task[Response] =
parseByteBuffer(ByteBuffer.wrap(message.content)) match {
case Left(err) => Task.now(Response.parseError(err.toString))
case Right(json) =>
json.as[Message] match {
case Left(err) => Task.now(Response.invalidRequest(err.toString))
case Right(msg) => handleValidMessage(msg)
}
}
def startTask: Task[Unit] =
in.foreachL { msg =>
handleMessage(msg)
.map(client.serverRespond)
.onErrorRecover {
case NonFatal(e) =>
logger.error("Unhandled error", e)
}
.runAsync(requestScheduler)
}
def listen(): Unit = {
val f = startTask.runAsync(requestScheduler)
logger.info("Listening....")
Await.result(f, Duration.Inf)
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy