From 36e079d12b6c83be81f6a09e7790d5960ae94f4b Mon Sep 17 00:00:00 2001 From: Alexey Alekhin Date: Thu, 2 Nov 2017 04:07:28 +0100 Subject: [PATCH 1/4] Added onNotification handler for the LSP CommandChannel --- .../sbt/internal/server/LanguageServerProtocol.scala | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala index 5c18b926b..47ac40aee 100644 --- a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala @@ -34,6 +34,15 @@ private[sbt] trait LanguageServerProtocol extends CommandChannel { protected def log: Logger protected def onSettingQuery(execId: Option[String], req: Q): Unit + protected def onNotification(notification: JsonRpcNotificationMessage): Unit = { + log.debug(s"onNotification: $notification") + notification.method match { + case "textDocument/didSave" => + append(Exec(";compile; collectAnalyses", None, Some(CommandSource(name)))) + case _ => () + } + } + protected def onRequestMessage(request: JsonRpcRequestMessage): Unit = { import sbt.internal.langserver.codec.JsonProtocol._ import internalJsonProtocol._ @@ -57,8 +66,6 @@ private[sbt] trait LanguageServerProtocol extends CommandChannel { setInitialized(true) append(Exec(s"collectAnalyses", Some(request.id), Some(CommandSource(name)))) langRespond(InitializeResult(serverCapabilities), Option(request.id)) - case "textDocument/didSave" => - append(Exec(";compile; collectAnalyses", Some(request.id), Some(CommandSource(name)))) case "textDocument/definition" => import scala.concurrent.ExecutionContext.Implicits.global Definition.lspDefinition(json, request.id, CommandSource(name), log) From c6898213831da9ab66185b7074b2762c5ed47555 Mon Sep 17 00:00:00 2001 From: Alexey Alekhin Date: Thu, 2 Nov 2017 04:08:14 +0100 Subject: [PATCH 2/4] Added deserialization for NotificationMessage and used it in handleBody --- .../scala/sbt/internal/server/NetworkChannel.scala | 12 ++++++++++-- .../main/scala/sbt/protocol/Serialization.scala | 14 ++++++++++---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala index 26b489347..d385965f4 100644 --- a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala +++ b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala @@ -166,8 +166,8 @@ final class NetworkChannel(val name: String, def handleBody(chunk: Vector[Byte]): Unit = { if (isLanguageServerProtocol) { - Serialization.deserializeJsonRequest(chunk) match { - case Right(req) => + Serialization.deserializeJsonMessage(chunk) match { + case Right(Right(req)) => try { onRequestMessage(req) } catch { @@ -175,6 +175,14 @@ final class NetworkChannel(val name: String, log.debug(s"sending error: $code: $message") langError(Option(req.id), code, message) } + case Right(Left(ntf)) => + try { + onNotification(ntf) + } catch { + case LangServerError(code, message) => + log.debug(s"sending error: $code: $message") + langError(None, code, message) // new id? + } case Left(errorDesc) => val msg = s"Got invalid chunk from client (${new String(chunk.toArray, "UTF-8")}): " + errorDesc langError(None, ErrorCodes.ParseError, msg) diff --git a/protocol/src/main/scala/sbt/protocol/Serialization.scala b/protocol/src/main/scala/sbt/protocol/Serialization.scala index 2a6e174b4..c193dec81 100644 --- a/protocol/src/main/scala/sbt/protocol/Serialization.scala +++ b/protocol/src/main/scala/sbt/protocol/Serialization.scala @@ -143,15 +143,21 @@ object Serialization { } } - private[sbt] def deserializeJsonRequest( - bytes: Seq[Byte]): Either[String, JsonRpcRequestMessage] = { + private[sbt] def deserializeJsonMessage(bytes: Seq[Byte]) + : Either[String, Either[JsonRpcNotificationMessage, JsonRpcRequestMessage]] = { val buffer = ByteBuffer.wrap(bytes.toArray) Parser.parseFromByteBuffer(buffer) match { case Success(json) => import sbt.internal.protocol.codec.JsonRPCProtocol._ Converter.fromJson[JsonRpcRequestMessage](json) match { - case Success(msg) => Right(msg) - case Failure(e) => throw e + case Success(request) if (request.id.nonEmpty) => Right(Right(request)) + case Failure(e) => throw e + case _ => { + Converter.fromJson[JsonRpcNotificationMessage](json) match { + case Success(notification) => Right(Left(notification)) + case Failure(e) => throw e + } + } } case Failure(e) => Left(s"Parse error: ${e.getMessage}") From e4dd090d0cf3c46f46602cabe83bb331e15a4c24 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 29 Nov 2017 00:51:16 -0500 Subject: [PATCH 3/4] Add debug log on unhandled notification --- .../main/scala/sbt/internal/server/LanguageServerProtocol.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala index 47ac40aee..ce1131d0f 100644 --- a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala @@ -39,7 +39,7 @@ private[sbt] trait LanguageServerProtocol extends CommandChannel { notification.method match { case "textDocument/didSave" => append(Exec(";compile; collectAnalyses", None, Some(CommandSource(name)))) - case _ => () + case u => log.debug(s"Unhandled notification received: $u") } } From 6a996378c713bddf7afc61e8aac9949c88c365f7 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Wed, 29 Nov 2017 01:00:46 -0500 Subject: [PATCH 4/4] return Either[String, JsonRpcMessage] --- .../main/scala/sbt/internal/server/NetworkChannel.scala | 7 +++++-- protocol/src/main/scala/sbt/protocol/Serialization.scala | 8 ++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala index d385965f4..7c27063de 100644 --- a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala +++ b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala @@ -18,6 +18,7 @@ import sbt.protocol._ import sbt.internal.langserver.ErrorCodes import sbt.internal.util.{ ObjectEvent, StringEvent } import sbt.internal.util.codec.JValueFormats +import sbt.internal.protocol.{ JsonRpcRequestMessage, JsonRpcNotificationMessage } import sbt.util.Logger final class NetworkChannel(val name: String, @@ -167,7 +168,7 @@ final class NetworkChannel(val name: String, def handleBody(chunk: Vector[Byte]): Unit = { if (isLanguageServerProtocol) { Serialization.deserializeJsonMessage(chunk) match { - case Right(Right(req)) => + case Right(req: JsonRpcRequestMessage) => try { onRequestMessage(req) } catch { @@ -175,7 +176,7 @@ final class NetworkChannel(val name: String, log.debug(s"sending error: $code: $message") langError(Option(req.id), code, message) } - case Right(Left(ntf)) => + case Right(ntf: JsonRpcNotificationMessage) => try { onNotification(ntf) } catch { @@ -183,6 +184,8 @@ final class NetworkChannel(val name: String, log.debug(s"sending error: $code: $message") langError(None, code, message) // new id? } + case Right(msg) => + log.debug(s"Unhandled message: $msg") case Left(errorDesc) => val msg = s"Got invalid chunk from client (${new String(chunk.toArray, "UTF-8")}): " + errorDesc langError(None, ErrorCodes.ParseError, msg) diff --git a/protocol/src/main/scala/sbt/protocol/Serialization.scala b/protocol/src/main/scala/sbt/protocol/Serialization.scala index c193dec81..75b9e7c83 100644 --- a/protocol/src/main/scala/sbt/protocol/Serialization.scala +++ b/protocol/src/main/scala/sbt/protocol/Serialization.scala @@ -15,6 +15,7 @@ import java.nio.ByteBuffer import scala.util.{ Success, Failure } import sbt.internal.util.StringEvent import sbt.internal.protocol.{ + JsonRpcMessage, JsonRpcRequestMessage, JsonRpcResponseMessage, JsonRpcNotificationMessage @@ -143,18 +144,17 @@ object Serialization { } } - private[sbt] def deserializeJsonMessage(bytes: Seq[Byte]) - : Either[String, Either[JsonRpcNotificationMessage, JsonRpcRequestMessage]] = { + private[sbt] def deserializeJsonMessage(bytes: Seq[Byte]): Either[String, JsonRpcMessage] = { val buffer = ByteBuffer.wrap(bytes.toArray) Parser.parseFromByteBuffer(buffer) match { case Success(json) => import sbt.internal.protocol.codec.JsonRPCProtocol._ Converter.fromJson[JsonRpcRequestMessage](json) match { - case Success(request) if (request.id.nonEmpty) => Right(Right(request)) + case Success(request) if (request.id.nonEmpty) => Right(request) case Failure(e) => throw e case _ => { Converter.fromJson[JsonRpcNotificationMessage](json) match { - case Success(notification) => Right(Left(notification)) + case Success(notification) => Right(notification) case Failure(e) => throw e } }