Merge pull request #3740 from laughedelic/wip/logmessage

LSP: improve logMessage notifications
This commit is contained in:
eugene yokota 2017-11-27 20:51:50 -05:00 committed by GitHub
commit e6165464ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 195 additions and 28 deletions

View File

@ -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
}
}

View File

@ -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)),

View File

@ -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 _ => ()
}
}
}

View File

@ -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)
}

View File

@ -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

View File

@ -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()
}
}
}

View File

@ -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

View File

@ -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
}
}
}