diff --git a/main/src/main/scala/sbt/internal/CommandExchange.scala b/main/src/main/scala/sbt/internal/CommandExchange.scala index 9605558a4..5d63c3329 100644 --- a/main/src/main/scala/sbt/internal/CommandExchange.scala +++ b/main/src/main/scala/sbt/internal/CommandExchange.scala @@ -23,9 +23,10 @@ import scala.util.{ Success, Failure } import sbt.io.syntax._ import sbt.io.Hash import sbt.internal.server._ -import sbt.internal.util.{ StringEvent, ObjectEvent, ConsoleOut, MainAppender } +import sbt.internal.langserver.{ LogMessageParams, MessageType } +import sbt.internal.util.{ StringEvent, ObjectEvent, MainAppender } import sbt.internal.util.codec.JValueFormats -import sbt.protocol.{ EventMessage, Serialization, ChannelAcceptedEvent } +import sbt.protocol.{ EventMessage, ExecStatusEvent } import sbt.util.{ Level, Logger, LogExchange } /** @@ -72,7 +73,7 @@ private[sbt] final class CommandExchange { def run(s: State): State = { consoleChannel match { - case Some(x) => // do nothing + case Some(_) => // do nothing case _ => val x = new ConsoleChannel("console0") consoleChannel = Some(x) @@ -116,7 +117,7 @@ private[sbt] final class CommandExchange { subscribe(channel) } server match { - case Some(x) => // do nothing + case Some(_) => // do nothing case _ => val portfile = (new File(".")).getAbsoluteFile / "project" / "target" / "active.json" val h = Hash.halfHashString(portfile.toURI.toString) @@ -147,13 +148,13 @@ private[sbt] final class CommandExchange { private[sbt] def notifyEvent[A: JsonFormat](method: String, params: A): Unit = { val toDel: ListBuffer[CommandChannel] = ListBuffer.empty channels.foreach { - case c: ConsoleChannel => + case _: ConsoleChannel => // c.publishEvent(event) case c: NetworkChannel => try { c.notifyEvent(method, params) } catch { - case e: SocketException => + case _: SocketException => toDel += c } } @@ -167,33 +168,48 @@ private[sbt] final class CommandExchange { } def publishEvent[A: JsonFormat](event: A): Unit = { + val broadcastStringMessage = true val toDel: ListBuffer[CommandChannel] = ListBuffer.empty + event match { case entry: StringEvent => - channels.foreach { + val params = toLogMessageParams(entry) + channels collect { case c: ConsoleChannel => - if (entry.channelName.isEmpty || entry.channelName == Some(c.name)) { + if (broadcastStringMessage) { c.publishEvent(event) + } else { + if (entry.channelName.isEmpty || entry.channelName == Some(c.name)) { + c.publishEvent(event) + } } case c: NetworkChannel => try { - if (entry.channelName == Some(c.name)) { - c.publishEvent(event) + // Note that language server's LogMessageParams does not hold the execid, + // so this is weaker than the StringMessage. We might want to double-send + // in case we have a better client that can utilize the knowledge. + import sbt.internal.langserver.codec.JsonProtocol._ + if (broadcastStringMessage) { + c.langNotify("window/logMessage", params) + } else { + if (entry.channelName == Some(c.name)) { + c.langNotify("window/logMessage", params) + } } } catch { - case e: SocketException => + case _: SocketException => toDel += c } } case _ => - channels.foreach { + channels collect { case c: ConsoleChannel => c.publishEvent(event) case c: NetworkChannel => try { c.publishEvent(event) } catch { - case e: SocketException => + case _: SocketException => toDel += c } } @@ -207,6 +223,10 @@ private[sbt] final class CommandExchange { } } + private[sbt] def toLogMessageParams(event: StringEvent): LogMessageParams = { + LogMessageParams(MessageType.fromLevelString(event.level), event.message) + } + /** * This publishes object events. The type information has been * erased because it went through logging. @@ -224,14 +244,14 @@ private[sbt] final class CommandExchange { JField("execId", JString(execId)) })): _* ) - channels.foreach { + channels collect { case c: ConsoleChannel => c.publishEvent(json) case c: NetworkChannel => try { c.publishObjectEvent(event) } catch { - case e: SocketException => + case _: SocketException => toDel += c } } @@ -249,23 +269,39 @@ private[sbt] final class CommandExchange { val toDel: ListBuffer[CommandChannel] = ListBuffer.empty event match { // Special treatment for ConsolePromptEvent since it's hand coded without codec. - case e: ConsolePromptEvent => + case entry: ConsolePromptEvent => channels collect { - case c: ConsoleChannel => c.publishEventMessage(e) + case c: ConsoleChannel => c.publishEventMessage(entry) } - case e: ConsoleUnpromptEvent => + case entry: ConsoleUnpromptEvent => channels collect { - case c: ConsoleChannel => c.publishEventMessage(e) + case c: ConsoleChannel => c.publishEventMessage(entry) + } + case entry: ExecStatusEvent => + channels collect { + case c: ConsoleChannel => + if (entry.channelName.isEmpty || entry.channelName == Some(c.name)) { + c.publishEventMessage(event) + } + case c: NetworkChannel => + try { + if (entry.channelName == Some(c.name)) { + c.publishEventMessage(event) + } + } catch { + case e: SocketException => + toDel += c + } } case _ => - channels.foreach { + channels collect { case c: ConsoleChannel => c.publishEventMessage(event) case c: NetworkChannel => try { c.publishEventMessage(event) } catch { - case e: SocketException => + case _: SocketException => toDel += c } } diff --git a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala index 4fb90f3a9..776078ae6 100644 --- a/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/LanguageServerProtocol.scala @@ -138,6 +138,14 @@ private[sbt] trait LanguageServerProtocol extends CommandChannel { publishBytes(bytes) } + def logMessage(level: String, message: String): Unit = { + import sbt.internal.langserver.codec.JsonProtocol._ + langNotify( + "window/logMessage", + LogMessageParams(MessageType.fromLevelString(level), message) + ) + } + private[sbt] lazy val serverCapabilities: ServerCapabilities = { ServerCapabilities(textDocumentSync = TextDocumentSyncOptions(true, 0, false, false, SaveOptions(false)), diff --git a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala index cee95034f..26b489347 100644 --- a/main/src/main/scala/sbt/internal/server/NetworkChannel.scala +++ b/main/src/main/scala/sbt/internal/server/NetworkChannel.scala @@ -16,7 +16,7 @@ import sjsonnew._ import scala.annotation.tailrec import sbt.protocol._ import sbt.internal.langserver.ErrorCodes -import sbt.internal.util.ObjectEvent +import sbt.internal.util.{ ObjectEvent, StringEvent } import sbt.internal.util.codec.JValueFormats import sbt.util.Logger @@ -227,7 +227,10 @@ final class NetworkChannel(val name: String, def publishEvent[A: JsonFormat](event: A, execId: Option[String]): Unit = { if (isLanguageServerProtocol) { - langRespond(event, execId) + event match { + case entry: StringEvent => logMessage(entry.level, entry.message) + case _ => langRespond(event, execId) + } } else { contentType match { case SbtX1Protocol => @@ -241,11 +244,19 @@ final class NetworkChannel(val name: String, def publishEvent[A: JsonFormat](event: A): Unit = publishEvent(event, None) def publishEventMessage(event: EventMessage): Unit = { - contentType match { - case SbtX1Protocol => - val bytes = Serialization.serializeEventMessage(event) - publishBytes(bytes, true) - case _ => + if (isLanguageServerProtocol) { + event match { + case entry: LogEvent => logMessage(entry.level, entry.message) + case entry: ExecStatusEvent => logMessage("debug", entry.status) + case _ => () + } + } else { + contentType match { + case SbtX1Protocol => + val bytes = Serialization.serializeEventMessage(event) + publishBytes(bytes, true) + case _ => () + } } } diff --git a/protocol/src/main/contraband-scala/sbt/internal/langserver/LogMessageParams.scala b/protocol/src/main/contraband-scala/sbt/internal/langserver/LogMessageParams.scala new file mode 100644 index 000000000..a13045afa --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/langserver/LogMessageParams.scala @@ -0,0 +1,38 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.langserver +final class LogMessageParams private ( + /** The message type. */ + val `type`: Long, + /** The actual message */ + val message: String) extends Serializable { + + + + override def equals(o: Any): Boolean = o match { + case x: LogMessageParams => (this.`type` == x.`type`) && (this.message == x.message) + case _ => false + } + override def hashCode: Int = { + 37 * (37 * (37 * (17 + "sbt.internal.langserver.LogMessageParams".##) + `type`.##) + message.##) + } + override def toString: String = { + "LogMessageParams(" + `type` + ", " + message + ")" + } + protected[this] def copy(`type`: Long = `type`, message: String = message): LogMessageParams = { + new LogMessageParams(`type`, message) + } + def withType(`type`: Long): LogMessageParams = { + copy(`type` = `type`) + } + def withMessage(message: String): LogMessageParams = { + copy(message = message) + } +} +object LogMessageParams { + + def apply(`type`: Long, message: String): LogMessageParams = new LogMessageParams(`type`, message) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/langserver/codec/JsonProtocol.scala b/protocol/src/main/contraband-scala/sbt/internal/langserver/codec/JsonProtocol.scala index a40a00bbe..f946496ec 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/langserver/codec/JsonProtocol.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/langserver/codec/JsonProtocol.scala @@ -16,6 +16,7 @@ trait JsonProtocol extends sjsonnew.BasicJsonProtocol with sbt.internal.langserver.codec.TextDocumentSyncOptionsFormats with sbt.internal.langserver.codec.ServerCapabilitiesFormats with sbt.internal.langserver.codec.InitializeResultFormats + with sbt.internal.langserver.codec.LogMessageParamsFormats with sbt.internal.langserver.codec.PublishDiagnosticsParamsFormats with sbt.internal.langserver.codec.SbtExecParamsFormats object JsonProtocol extends JsonProtocol \ No newline at end of file diff --git a/protocol/src/main/contraband-scala/sbt/internal/langserver/codec/LogMessageParamsFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/langserver/codec/LogMessageParamsFormats.scala new file mode 100644 index 000000000..2ffc92003 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/langserver/codec/LogMessageParamsFormats.scala @@ -0,0 +1,29 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.langserver.codec +import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError } +trait LogMessageParamsFormats { self: sjsonnew.BasicJsonProtocol => +implicit lazy val LogMessageParamsFormat: JsonFormat[sbt.internal.langserver.LogMessageParams] = new JsonFormat[sbt.internal.langserver.LogMessageParams] { + override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.langserver.LogMessageParams = { + jsOpt match { + case Some(js) => + unbuilder.beginObject(js) + val `type` = unbuilder.readField[Long]("type") + val message = unbuilder.readField[String]("message") + unbuilder.endObject() + sbt.internal.langserver.LogMessageParams(`type`, message) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.internal.langserver.LogMessageParams, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("type", obj.`type`) + builder.addField("message", obj.message) + builder.endObject() + } +} +} diff --git a/protocol/src/main/contraband/lsp.contra b/protocol/src/main/contraband/lsp.contra index 49853a2f4..18bd0a42c 100644 --- a/protocol/src/main/contraband/lsp.contra +++ b/protocol/src/main/contraband/lsp.contra @@ -98,6 +98,16 @@ type SaveOptions { includeText: Boolean } +# LogMessage Notification + +type LogMessageParams { + ## The message type. + type: Long! + + ## The actual message + message: String! +} + # Document # PublishDiagnostics Notification https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#textDocument_publishDiagnostics diff --git a/protocol/src/main/scala/sbt/internal/langserver/MessageType.scala b/protocol/src/main/scala/sbt/internal/langserver/MessageType.scala new file mode 100644 index 000000000..2d0aff8f1 --- /dev/null +++ b/protocol/src/main/scala/sbt/internal/langserver/MessageType.scala @@ -0,0 +1,34 @@ +/* + * sbt + * Copyright 2011 - 2017, Lightbend, Inc. + * Copyright 2008 - 2010, Mark Harrah + * Licensed under BSD-3-Clause license (see LICENSE) + */ + +package sbt +package internal +package langserver + +object MessageType { + + /** An error message. */ + val Error = 1L + + /** A warning message. */ + val Warning = 2L + + /** An information message. */ + val Info = 3L + + /** A log message. */ + val Log = 4L + + def fromLevelString(level: String): Long = { + level.toLowerCase match { + case "info" => Info + case "warn" => Warning + case "error" => Error + case _ => Log + } + } +}