separate BSP and LSP handlers + add bspWorkspace task

This commit is contained in:
Adrien Piquerez 2020-05-04 16:14:14 +02:00
parent a415cd0cfc
commit 454ee61289
8 changed files with 283 additions and 304 deletions

View File

@ -12,9 +12,8 @@ package server
import sjsonnew.JsonFormat
import sbt.internal.protocol._
import sbt.util.Logger
import sbt.protocol.{ SettingQuery => Q, CompletionParams => CP }
import sbt.protocol.{ CompletionParams => CP, SettingQuery => Q }
import sbt.internal.langserver.{ CancelRequestParams => CRP }
import sbt.internal.bsp._
/**
* ServerHandler allows plugins to extend sbt server.
@ -65,6 +64,7 @@ trait ServerCallback {
def jsonRpcRespondError(execId: Option[String], code: Long, message: String): Unit
def jsonRpcNotify[A: JsonFormat](method: String, params: A): Unit
def appendExec(exec: Exec): Boolean
def appendExec(commandLine: String, requestId: Option[String]): Boolean
def log: Logger
def name: String
@ -74,6 +74,4 @@ trait ServerCallback {
private[sbt] def onSettingQuery(execId: Option[String], req: Q): Unit
private[sbt] def onCompletionRequest(execId: Option[String], cp: CP): Unit
private[sbt] def onCancellationRequest(execId: Option[String], crp: CRP): Unit
private[sbt] def onBspInitialize(execId: Option[String], param: InitializeBuildParams): Unit
private[sbt] def onBspBuildTargets(execId: Option[String]): Unit
}

View File

@ -199,9 +199,10 @@ object Defaults extends BuildCommon {
},
fileConverter := MappedFileConverter(rootPaths.value, allowMachinePath.value),
fullServerHandlers := {
(Vector(LanguageServerProtocol.handler(fileConverter.value))
++ serverHandlers.value
++ Vector(ServerHandler.fallback))
Seq(
LanguageServerProtocol.handler(fileConverter.value),
BuildServerProtocol.handler(sbtVersion.value)
) ++ serverHandlers.value :+ ServerHandler.fallback
},
uncachedStamper := Stamps.uncachedStamps(fileConverter.value),
reusableStamper := Stamps.timeWrapLibraryStamps(uncachedStamper.value, fileConverter.value),

View File

@ -338,7 +338,10 @@ object Keys {
val internalDependencyConfigurations = settingKey[Seq[(ProjectRef, Set[String])]]("The project configurations that this configuration depends on")
val closeClassLoaders = settingKey[Boolean]("Close classloaders in run and test when the task completes.").withRank(DSetting)
val allowZombieClassLoaders = settingKey[Boolean]("Allow a classloader that has previously been closed by `run` or `test` to continue loading classes.")
val buildTargetIdentifier = settingKey[BuildTargetIdentifier]("Id for BSP build target.").withRank(DSetting)
val bspWorkspace = taskKey[Map[BuildTargetIdentifier, Scope]]("Mapping of BSP build targets to sbt scopes").withRank(DTask)
val bspWorkspaceBuildTargets = taskKey[Seq[BuildTarget]]("List all the BSP build targets").withRank(DTask)
val bspBuildTarget = settingKey[BuildTarget]("Description of the BSP build target").withRank(DSetting)
val bspBuildTargetSources = inputKey[Unit]("").withRank(DTask)
val bspBuildTargetSourcesItem = taskKey[SourcesItem]("").withRank(DTask)

View File

@ -242,7 +242,7 @@ object BuiltinCommands {
act,
continuous,
clearCaches,
) ++ allBasicCommands ++ server.BuildServerProtocol.commands
) ++ allBasicCommands
def DefaultBootCommands: Seq[String] =
WriteSbtVersion :: LoadProject :: NotifyUsersAboutShell :: s"$IfLast $Shell" :: Nil

View File

@ -9,9 +9,7 @@ package sbt
import sbt.internal.{ Load, LoadedBuildUnit }
import sbt.internal.util.{ AttributeKey, Dag, Types }
import sbt.librarymanagement.{ Configuration, ConfigRef }
import sbt.librarymanagement.{ ConfigRef, Configuration }
import Types.const
import Def.Initialize
import java.net.URI
@ -23,6 +21,16 @@ object ScopeFilter {
type ConfigurationFilter = AxisFilter[ConfigKey]
type TaskFilter = AxisFilter[AttributeKey[_]]
/**
* Construct a Scope filter from a sequence of individual scopes.
*/
def in(scopes: Seq[Scope]): ScopeFilter = {
val scopeSet = scopes.toSet
new ScopeFilter {
override private[ScopeFilter] def apply(data: Data): Scope => Boolean = scopeSet.contains
}
}
/**
* Constructs a Scope filter from filters for the individual axes.
* If a project filter is not supplied, the enclosing project is selected.

View File

@ -11,32 +11,51 @@ package server
import java.net.URI
import sbt.internal.bsp._
import sbt.internal.util.complete.DefaultParsers
import sbt.librarymanagement.{ Configuration, Configurations }
import Configurations.{ Compile, Test }
import sbt.SlashSyntax0._
import sbt.BuildSyntax._
import scala.collection.mutable
import sbt.Def._
import sbt.Keys._
import sbt.ScopeFilter.Make._
import sbt.SlashSyntax0._
import sbt.internal.bsp._
import sbt.internal.langserver.ErrorCodes
import sbt.internal.protocol.JsonRpcRequestMessage
import sbt.librarymanagement.Configuration
import sbt.librarymanagement.Configurations.{ Compile, Test }
import sbt.std.TaskExtra._
import sjsonnew.shaded.scalajson.ast.unsafe.JValue
import sjsonnew.support.scalajson.unsafe.Converter
import Def._
import Keys._
import ScopeFilter.Make._
object BuildServerProtocol {
private[sbt] val idMap: mutable.Map[BuildTargetIdentifier, (ProjectRef, Configuration)] =
mutable.Map.empty
def commands: List[Command] = List()
import sbt.internal.bsp.codec.JsonProtocol._
private val bspVersion = "2.0.0-M5"
private val capabilities = BuildClientCapabilities(languageIds = Vector("scala"))
lazy val globalSettings: Seq[Def.Setting[_]] = Seq(
bspWorkspace := Def.taskDyn {
val structure = Keys.buildStructure.value
val scopes: Seq[Scope] = structure.allProjectRefs.flatMap { ref =>
Seq(Scope.Global.in(ref, Compile), Scope.Global.in(ref, Test))
}
scopes
.map(scope => (scope / Keys.buildTargetIdentifier).toTask)
.joinWith(tasks => joinTasks(tasks).join.map(_.zip(scopes).toMap))
}.value,
bspWorkspaceBuildTargets := Def.taskDyn {
val workspace = Keys.bspWorkspace.value
val state = Keys.state.value
val allTargets = ScopeFilter.in(workspace.values.toSeq)
Def.task {
val buildTargets = Keys.bspBuildTarget.all(allTargets).value.toVector
state.respondEvent(WorkspaceBuildTargetsResult(buildTargets))
buildTargets
}
}.value,
// https://github.com/build-server-protocol/build-server-protocol/blob/master/docs/specification.md#build-target-sources-request
bspBuildTargetSources := (Def.inputTaskDyn {
import DefaultParsers._
bspBuildTargetSources := Def.inputTaskDyn {
val s = state.value
val args: Seq[String] = spaceDelimited("<arg>").parsed
val filter = toScopeFilter(args)
val workspace = bspWorkspace.value
val targets = spaceDelimited().parsed.map(uri => BuildTargetIdentifier(URI.create(uri)))
val filter = ScopeFilter.in(targets.map(workspace))
// run the worker task concurrently
Def.task {
import sbt.internal.bsp.codec.JsonProtocol._
@ -44,12 +63,13 @@ object BuildServerProtocol {
val result = SourcesResult(items.toVector)
s.respondEvent(result)
}
}).evaluated,
}.evaluated,
bspBuildTargetSources / aggregate := false,
bspBuildTargetCompile := Def.inputTaskDyn {
val s: State = state.value
val args: Seq[String] = spaceDelimited().parsed
val filter = toScopeFilter(args)
val workspace = bspWorkspace.value
val targets = spaceDelimited().parsed.map(uri => BuildTargetIdentifier(URI.create(uri)))
val filter = ScopeFilter.in(targets.map(workspace))
Def.task {
import sbt.internal.bsp.codec.JsonProtocol._
val statusCode = Keys.bspBuildTargetCompileItem.all(filter).value.max
@ -57,19 +77,17 @@ object BuildServerProtocol {
}
}.evaluated,
bspBuildTargetCompile / aggregate := false,
bspBuildTargetScalacOptions := (Def.inputTaskDyn {
import DefaultParsers._
bspBuildTargetScalacOptions := Def.inputTaskDyn {
val s = state.value
val args: Seq[String] = spaceDelimited("<arg>").parsed
val filter = toScopeFilter(args)
// run the worker task concurrently
val workspace = bspWorkspace.value
val targets = spaceDelimited().parsed.map(uri => BuildTargetIdentifier(URI.create(uri)))
val filter = ScopeFilter.in(targets.map(workspace))
Def.task {
import sbt.internal.bsp.codec.JsonProtocol._
val items = bspBuildTargetScalacOptionsItem.all(filter).value
val result = ScalacOptionsResult(items.toVector)
s.respondEvent(result)
}
}).evaluated,
}.evaluated,
bspBuildTargetScalacOptions / aggregate := false
)
@ -86,10 +104,10 @@ object BuildServerProtocol {
val dirs = unmanagedSourceDirectories.value
val managed = managedSources.value
val items = (dirs.toVector map { dir =>
SourceItem(dir.toURI, SourceItemKind.Directory, false)
SourceItem(dir.toURI, SourceItemKind.Directory, generated = false)
}) ++
(managed.toVector map { x =>
SourceItem(x.toURI, SourceItemKind.File, true)
SourceItem(x.toURI, SourceItemKind.File, generated = true)
})
SourcesItem(id, items)
},
@ -103,6 +121,56 @@ object BuildServerProtocol {
)
)
def handler(sbtVersion: String): ServerHandler = ServerHandler { callback =>
ServerIntent(
{
case r: JsonRpcRequestMessage if r.method == "build/initialize" =>
val _ = Converter.fromJson[InitializeBuildParams](json(r)).get
val response = InitializeBuildResult("sbt", sbtVersion, bspVersion, capabilities, None)
callback.jsonRpcRespond(response, Some(r.id)); ()
case r: JsonRpcRequestMessage if r.method == "workspace/buildTargets" =>
val _ = callback.appendExec(Keys.bspWorkspaceBuildTargets.key.toString, None)
case r: JsonRpcRequestMessage if r.method == "build/shutdown" =>
()
case r: JsonRpcRequestMessage if r.method == "build/exit" =>
val _ = callback.appendExec("shutdown", None)
case r: JsonRpcRequestMessage if r.method == "buildTarget/sources" =>
import sbt.internal.bsp.codec.JsonProtocol._
val param = Converter.fromJson[SourcesParams](json(r)).get
val targets = param.targets.map(_.uri).mkString(" ")
val command = Keys.bspBuildTargetSources.key
val _ = callback.appendExec(s"$command $targets", None)
case r if r.method == "buildTarget/compile" =>
val param = Converter.fromJson[CompileParams](json(r)).get
callback.log.info(param.toString)
val targets = param.targets.map(_.uri).mkString(" ")
val command = Keys.bspBuildTargetCompile.key
val _ = callback.appendExec(s"$command $targets", None)
case r: JsonRpcRequestMessage if r.method == "buildTarget/scalacOptions" =>
import sbt.internal.bsp.codec.JsonProtocol._
val param = Converter.fromJson[ScalacOptionsParams](json(r)).get
val targets = param.targets.map(_.uri).mkString(" ")
val command = Keys.bspBuildTargetScalacOptions.key
val _ = callback.appendExec(s"$command $targets", None)
},
PartialFunction.empty
)
}
private def json(r: JsonRpcRequestMessage): JValue =
r.params.getOrElse(
throw LangServerError(
ErrorCodes.InvalidParams,
s"param is expected on '${r.method}' method."
)
)
private def bspBuildTargetSetting: Def.Initialize[BuildTarget] = Def.settingDyn {
import sbt.internal.bsp.codec.JsonProtocol._
val buildTargetIdentifier = Keys.buildTargetIdentifier.value
@ -157,13 +225,11 @@ object BuildServerProtocol {
}
}
def toScopeFilter(args: Seq[String]): ScopeFilter = {
val filters = args map { arg =>
val id = BuildTargetIdentifier(new URI(arg))
val pair = idMap(id)
ScopeFilter(inProjects(pair._1), inConfigurations(pair._2))
}
filters.tail.foldLeft(filters.head) { _ || _ }
def scopeFilter(
targets: Seq[BuildTargetIdentifier],
workspace: Map[BuildTargetIdentifier, Scope]
): ScopeFilter = {
ScopeFilter.in(targets.map(workspace))
}
def toId(ref: ProjectReference, config: Configuration): BuildTargetIdentifier =
@ -191,41 +257,3 @@ object BuildServerProtocol {
} yield depId
}
}
// This is mixed into NetworkChannel
trait BuildServerImpl { self: LanguageServerProtocol with NetworkChannel =>
protected def structure: BuildStructure
protected def getSetting[A](key: SettingKey[A]): Option[A]
protected def setting[A](key: SettingKey[A]): A
protected def onBspInitialize(execId: Option[String], param: InitializeBuildParams): Unit = {
import sbt.internal.bsp.codec.JsonProtocol._
val bspVersion = "2.0.0-M5"
// https://microsoft.github.io/language-server-protocol/specification#textDocumentItem
val languageIds = Vector("scala")
val sbtV = setting(Keys.sbtVersion)
val response =
InitializeBuildResult("sbt", sbtV, bspVersion, BuildClientCapabilities(languageIds), None)
respondResult(response, execId)
}
/**
* https://github.com/build-server-protocol/build-server-protocol/blob/master/docs/specification.md#workspace-build-targets-request
*/
protected def onBspBuildTargets(execId: Option[String]): Unit = {
import sbt.internal.bsp.codec.JsonProtocol._
val buildTargets = structure.allProjectRefs.flatMap { ref =>
val compileTarget = getSetting(ref / Compile / Keys.bspBuildTarget).get
val testTarget = getSetting(ref / Test / Keys.bspBuildTarget).get
BuildServerProtocol.idMap ++= Seq(
compileTarget.id -> ((ref, Compile)),
testTarget.id -> ((ref, Test))
)
Seq(compileTarget, testTarget)
}
respondResult(
WorkspaceBuildTargetsResult(buildTargets.toVector),
execId
)
}
}

View File

@ -9,25 +9,30 @@ package sbt
package internal
package server
import sjsonnew.JsonFormat
import sjsonnew.support.scalajson.unsafe.Converter
import sbt.protocol.Serialization
import sbt.protocol.{ CompletionParams => CP, SettingQuery => Q }
import sbt.internal.langserver.{ CancelRequestParams => CRP }
import sbt.internal.bsp._
import sbt.internal.langserver._
import sbt.internal.protocol._
import sbt.internal.protocol.codec._
import sbt.internal.langserver._
import sbt.internal.util.ObjectEvent
import sbt.util.Logger
import scala.concurrent.ExecutionContext
import sbt.protocol.{ CompletionParams => CP, SettingQuery => Q }
import sjsonnew.shaded.scalajson.ast.unsafe.JValue
import sjsonnew.support.scalajson.unsafe.Converter
import xsbti.FileConverter
private[sbt] final case class LangServerError(code: Long, message: String)
extends Throwable(message)
private[sbt] object LanguageServerProtocol {
lazy val internalJsonProtocol = new InitializeOptionFormats with sjsonnew.BasicJsonProtocol {}
private val internalJsonProtocol = new sbt.internal.langserver.codec.JsonProtocol
with sbt.protocol.codec.JsonProtocol with sjsonnew.BasicJsonProtocol with InitializeOptionFormats
import internalJsonProtocol._
def json(r: JsonRpcRequestMessage): JValue =
r.params.getOrElse(
throw LangServerError(
ErrorCodes.InvalidParams,
s"param is expected on '${r.method}' method."
)
)
lazy val serverCapabilities: ServerCapabilities = {
ServerCapabilities(
@ -37,200 +42,54 @@ private[sbt] object LanguageServerProtocol {
)
}
def handler(converter: FileConverter): ServerHandler =
ServerHandler({
case callback: ServerCallback =>
import callback._
ServerIntent(
{
import sbt.internal.langserver.codec.JsonProtocol._
import internalJsonProtocol._
import sbt.internal.bsp.codec.JsonProtocol._
def json(r: JsonRpcRequestMessage) =
r.params.getOrElse(
throw LangServerError(
ErrorCodes.InvalidParams,
s"param is expected on '${r.method}' method."
)
def handler(converter: FileConverter): ServerHandler = ServerHandler { callback =>
import callback._
ServerIntent(
{
case r: JsonRpcRequestMessage if r.method == "initialize" =>
if (authOptions(ServerAuthentication.Token)) {
val param = Converter.fromJson[InitializeParams](json(r)).get
val optionJson = param.initializationOptions.getOrElse(
throw LangServerError(
ErrorCodes.InvalidParams,
"initializationOptions is expected on 'initialize' param."
)
)
val opt = Converter.fromJson[InitializeOption](optionJson).get
val token = opt.token.getOrElse(sys.error("'token' is missing."))
if (authenticate(token)) ()
else throw LangServerError(ErrorCodes.InvalidRequest, "invalid token")
} else ()
setInitialized(true)
appendExec("collectAnalyses", None)
jsonRpcRespond(InitializeResult(serverCapabilities), Some(r.id))
{
case r: JsonRpcRequestMessage if r.method == "textDocument/definition" =>
implicit val executionContext: ExecutionContext = StandardMain.executionContext
Definition.lspDefinition(json(r), r.id, CommandSource(name), converter, log)
()
case r: JsonRpcRequestMessage if r.method == "sbt/exec" =>
val param = Converter.fromJson[SbtExecParams](json(r)).get
appendExec(Exec(param.commandLine, Some(r.id), Some(CommandSource(name))))
()
case r: JsonRpcRequestMessage if r.method == "sbt/setting" =>
import sbt.protocol.codec.JsonProtocol._
val param = Converter.fromJson[Q](json(r)).get
onSettingQuery(Option(r.id), param)
case r: JsonRpcRequestMessage if r.method == "sbt/cancelRequest" =>
import sbt.protocol.codec.JsonProtocol._
val param = Converter.fromJson[CRP](json(r)).get
onCancellationRequest(Option(r.id), param)
case r: JsonRpcRequestMessage if r.method == "sbt/completion" =>
import sbt.protocol.codec.JsonProtocol._
val param = Converter.fromJson[CP](json(r)).get
onCompletionRequest(Option(r.id), param)
case r: JsonRpcRequestMessage if r.method == "build/initialize" =>
val param = Converter.fromJson[InitializeBuildParams](json(r)).get
onBspInitialize(Option(r.id), param)
case r: JsonRpcRequestMessage if r.method == "workspace/buildTargets" =>
onBspBuildTargets(Option(r.id))
case r: JsonRpcRequestMessage if r.method == "build/shutdown" =>
()
case r: JsonRpcRequestMessage if r.method == "build/exit" =>
appendExec(Exec("shutdown", Some(r.id), Some(CommandSource(name))))
()
case r: JsonRpcRequestMessage if r.method == "buildTarget/sources" =>
import sbt.internal.bsp.codec.JsonProtocol._
val param = Converter.fromJson[SourcesParams](json(r)).get
appendExec(
Exec(
s"""${Keys.bspBuildTargetSources.key} ${param.targets
.map(_.uri)
.mkString(" ")}""",
Option(r.id),
Some(CommandSource(name))
)
)
()
case r if r.method == "buildTarget/compile" =>
val param = Converter.fromJson[CompileParams](json(r)).get
log.info(param.toString)
val targetsUris = param.targets.map(_.uri).mkString(" ")
val key = Keys.bspBuildTargetCompile.key.toString
appendExec(
Exec(
s"$key $targetsUris",
Option(r.id),
Some(CommandSource(name))
)
)
()
case r: JsonRpcRequestMessage if r.method == "buildTarget/scalacOptions" =>
import sbt.internal.bsp.codec.JsonProtocol._
val param = Converter.fromJson[ScalacOptionsParams](json(r)).get
appendExec(
Exec(
s"""${Keys.bspBuildTargetScalacOptions.key} ${param.targets
.map(_.uri)
.mkString(" ")}""",
Option(r.id),
Some(CommandSource(name))
)
)
()
}
}, {
case n: JsonRpcNotificationMessage if n.method == "textDocument/didSave" =>
appendExec(Exec(";Test/compile; collectAnalyses", None, Some(CommandSource(name))))
()
}
)
})
}
case r: JsonRpcRequestMessage if r.method == "textDocument/definition" =>
val _ = Definition.lspDefinition(json(r), r.id, CommandSource(name), converter, log)(
StandardMain.executionContext
)
/** Implements Language Server Protocol <https://github.com/Microsoft/language-server-protocol>. */
private[sbt] trait LanguageServerProtocol { self: NetworkChannel =>
case r: JsonRpcRequestMessage if r.method == "sbt/exec" =>
val param = Converter.fromJson[SbtExecParams](json(r)).get
val _ = appendExec(param.commandLine, Some(r.id))
lazy val internalJsonProtocol = new InitializeOptionFormats with sjsonnew.BasicJsonProtocol {}
case r: JsonRpcRequestMessage if r.method == "sbt/setting" =>
val param = Converter.fromJson[Q](json(r)).get
onSettingQuery(Option(r.id), param)
protected def authenticate(token: String): Boolean
protected def authOptions: Set[ServerAuthentication]
protected def setInitialized(value: Boolean): Unit
protected def log: Logger
protected def onSettingQuery(execId: Option[String], req: Q): Unit
protected def onCompletionRequest(execId: Option[String], cp: CP): Unit
protected def onCancellationRequest(execId: Option[String], crp: CRP): Unit
protected def onBspInitialize(execId: Option[String], param: InitializeBuildParams): Unit
protected def onBspBuildTargets(execId: Option[String]): Unit
case r: JsonRpcRequestMessage if r.method == "sbt/cancelRequest" =>
val param = Converter.fromJson[CancelRequestParams](json(r)).get
onCancellationRequest(Option(r.id), param)
protected lazy val callbackImpl: ServerCallback = new ServerCallback {
def jsonRpcRespond[A: JsonFormat](event: A, execId: Option[String]): Unit =
self.respondResult(event, execId)
case r: JsonRpcRequestMessage if r.method == "sbt/completion" =>
import sbt.protocol.codec.JsonProtocol._
val param = Converter.fromJson[CP](json(r)).get
onCompletionRequest(Option(r.id), param)
def jsonRpcRespondError(execId: Option[String], code: Long, message: String): Unit =
self.respondError(code, message, execId)
def jsonRpcNotify[A: JsonFormat](method: String, params: A): Unit =
self.jsonRpcNotify(method, params)
def appendExec(exec: Exec): Boolean = self.append(exec)
def log: Logger = self.log
def name: String = self.name
private[sbt] def authOptions: Set[ServerAuthentication] = self.authOptions
private[sbt] def authenticate(token: String): Boolean = self.authenticate(token)
private[sbt] def setInitialized(value: Boolean): Unit = self.setInitialized(value)
private[sbt] def onSettingQuery(execId: Option[String], req: Q): Unit =
self.onSettingQuery(execId, req)
private[sbt] def onCompletionRequest(execId: Option[String], cp: CP): Unit =
self.onCompletionRequest(execId, cp)
private[sbt] def onCancellationRequest(execId: Option[String], crp: CancelRequestParams): Unit =
self.onCancellationRequest(execId, crp)
private[sbt] def onBspInitialize(execId: Option[String], param: InitializeBuildParams): Unit =
self.onBspInitialize(execId, param)
private[sbt] def onBspBuildTargets(execId: Option[String]): Unit =
self.onBspBuildTargets(execId)
}
/**
* This reacts to various events that happens inside sbt, sometime
* in response to the previous requests.
* The type information has been erased because it went through logging.
*/
protected def onObjectEvent(event: ObjectEvent[_]): Unit = {
// import sbt.internal.langserver.codec.JsonProtocol._
val msgContentType = event.contentType
msgContentType match {
// LanguageServerReporter sends PublishDiagnosticsParams
case "sbt.internal.langserver.PublishDiagnosticsParams" =>
// val p = event.message.asInstanceOf[PublishDiagnosticsParams]
// jsonRpcNotify("textDocument/publishDiagnostics", p)
case "xsbti.Problem" =>
() // ignore
case _ =>
// log.debug(event)
()
}
}
/** Respond back to Language Server's client. */
private[sbt] def jsonRpcRespond[A: JsonFormat](event: A, execId: String): Unit = {
val response =
JsonRpcResponseMessage("2.0", execId, Option(Converter.toJson[A](event).get), None)
val bytes = Serialization.serializeResponseMessage(response)
publishBytes(bytes)
}
/** Respond back to Language Server's client. */
private[sbt] def jsonRpcRespondError(
execId: String,
err: JsonRpcResponseError
): Unit = {
val m = JsonRpcResponseMessage("2.0", execId, None, Option(err))
val bytes = Serialization.serializeResponseMessage(m)
publishBytes(bytes)
}
/** Notify to Language Server's client. */
private[sbt] def jsonRpcNotify[A: JsonFormat](method: String, params: A): Unit = {
val m =
JsonRpcNotificationMessage("2.0", method, Option(Converter.toJson[A](params).get))
log.debug(s"jsonRpcNotify: $m")
val bytes = Serialization.serializeNotificationMessage(m)
publishBytes(bytes)
}
def logMessage(level: String, message: String): Unit = {
import sbt.internal.langserver.codec.JsonProtocol._
jsonRpcNotify(
"window/logMessage",
LogMessageParams(MessageType.fromLevelString(level), message)
}, {
case n: JsonRpcNotificationMessage if n.method == "textDocument/didSave" =>
val _ = appendExec(";Test/compile; collectAnalyses", None)
}
)
}
}

View File

@ -12,15 +12,16 @@ package server
import java.net.{ Socket, SocketTimeoutException }
import java.util.concurrent.atomic.AtomicBoolean
import sbt.internal.langserver.{ CancelRequestParams, ErrorCodes }
import sbt.internal.langserver.{ CancelRequestParams, ErrorCodes, LogMessageParams, MessageType }
import sbt.internal.protocol.{
JsonRpcNotificationMessage,
JsonRpcRequestMessage,
JsonRpcResponseError
JsonRpcResponseError,
JsonRpcResponseMessage
}
import sbt.internal.util.ObjectEvent
import sbt.internal.util.codec.JValueFormats
import sbt.internal.util.complete.Parser
import sbt.internal.util.ObjectEvent
import sbt.protocol._
import sbt.util.Logger
import sjsonnew._
@ -39,9 +40,7 @@ final class NetworkChannel(
instance: ServerInstance,
handlers: Seq[ServerHandler],
val log: Logger
) extends CommandChannel
with LanguageServerProtocol
with BuildServerImpl {
) extends CommandChannel { self =>
import NetworkChannel._
private val running = new AtomicBoolean(true)
@ -59,6 +58,34 @@ final class NetworkChannel(
private lazy val jsonFormat = new sjsonnew.BasicJsonProtocol with JValueFormats {}
private val pendingRequests: mutable.Map[String, JsonRpcRequestMessage] = mutable.Map()
private lazy val callback: ServerCallback = new ServerCallback {
def jsonRpcRespond[A: JsonFormat](event: A, execId: Option[String]): Unit =
self.respondResult(event, execId)
def jsonRpcRespondError(execId: Option[String], code: Long, message: String): Unit =
self.respondError(code, message, execId)
def jsonRpcNotify[A: JsonFormat](method: String, params: A): Unit =
self.jsonRpcNotify(method, params)
def appendExec(commandLine: String, execId: Option[String]): Boolean =
self.append(Exec(commandLine, execId, Some(CommandSource(name))))
def appendExec(exec: Exec): Boolean = self.append(exec)
def log: Logger = self.log
def name: String = self.name
private[sbt] def authOptions: Set[ServerAuthentication] = self.authOptions
private[sbt] def authenticate(token: String): Boolean = self.authenticate(token)
private[sbt] def setInitialized(value: Boolean): Unit = self.setInitialized(value)
private[sbt] def onSettingQuery(execId: Option[String], req: SettingQuery): Unit =
self.onSettingQuery(execId, req)
private[sbt] def onCompletionRequest(execId: Option[String], cp: CompletionParams): Unit =
self.onCompletionRequest(execId, cp)
private[sbt] def onCancellationRequest(execId: Option[String], crp: CancelRequestParams): Unit =
self.onCancellationRequest(execId, crp)
}
def setContentType(ct: String): Unit = synchronized { _contentType = ct }
def contentType: String = _contentType
@ -172,11 +199,11 @@ final class NetworkChannel(
}
private lazy val intents = {
val cb = callbackImpl
handlers.toVector map { h =>
h.handler(cb)
h.handler(callback)
}
}
lazy val onRequestMessage: PartialFunction[JsonRpcRequestMessage, Unit] =
intents.foldLeft(PartialFunction.empty[JsonRpcRequestMessage, Unit]) {
case (f, i) => f orElse i.onRequest
@ -396,18 +423,6 @@ final class NetworkChannel(
}
}
protected def getSetting[A](key: SettingKey[A]): Option[A] =
structure.data.get(key.scope, key.key)
protected def getTaskValue[A](key: TaskKey[A]): Option[Task[A]] =
structure.data.get(key.scope, key.key)
protected def setting[A](key: SettingKey[A]): A =
getSetting(key).getOrElse(sys.error(s"key ${Def.displayFull(key.scopedKey)} is not defined"))
protected def taskValue[A](key: TaskKey[A]): Task[A] =
getTaskValue(key).getOrElse(sys.error(s"key ${Def.displayFull(key.scopedKey)} is not defined"))
private def onExecCommand(cmd: ExecCommand) = {
if (initialized) {
append(
@ -535,6 +550,73 @@ final class NetworkChannel(
running.set(false)
out.close()
}
/**
* This reacts to various events that happens inside sbt, sometime
* in response to the previous requests.
* The type information has been erased because it went through logging.
*/
protected def onObjectEvent(event: ObjectEvent[_]): Unit = {
// import sbt.internal.langserver.codec.JsonProtocol._
val msgContentType = event.contentType
msgContentType match {
// LanguageServerReporter sends PublishDiagnosticsParams
case "sbt.internal.langserver.PublishDiagnosticsParams" =>
// val p = event.message.asInstanceOf[PublishDiagnosticsParams]
// jsonRpcNotify("textDocument/publishDiagnostics", p)
case "xsbti.Problem" =>
() // ignore
case _ =>
// log.debug(event)
()
}
}
/** Respond back to Language Server's client. */
private[sbt] def jsonRpcRespond[A: JsonFormat](event: A, execId: String): Unit = {
val m =
JsonRpcResponseMessage("2.0", execId, Option(Converter.toJson[A](event).get), None)
val bytes = Serialization.serializeResponseMessage(m)
publishBytes(bytes)
}
/** Respond back to Language Server's client. */
private[sbt] def jsonRpcRespondError[A: JsonFormat](
execId: String,
code: Long,
message: String,
data: A,
): Unit = {
val err = JsonRpcResponseError(code, message, Converter.toJson[A](data).get)
jsonRpcRespondError(execId, err)
}
private[sbt] def jsonRpcRespondError(
execId: String,
err: JsonRpcResponseError
): Unit = {
val m = JsonRpcResponseMessage("2.0", execId, None, Option(err))
val bytes = Serialization.serializeResponseMessage(m)
publishBytes(bytes)
}
/** Notify to Language Server's client. */
private[sbt] def jsonRpcNotify[A: JsonFormat](method: String, params: A): Unit = {
val m =
JsonRpcNotificationMessage("2.0", method, Option(Converter.toJson[A](params).get))
log.debug(s"jsonRpcNotify: $m")
val bytes = Serialization.serializeNotificationMessage(m)
publishBytes(bytes)
}
def logMessage(level: String, message: String): Unit = {
import sbt.internal.langserver.codec.JsonProtocol._
jsonRpcNotify(
"window/logMessage",
LogMessageParams(MessageType.fromLevelString(level), message)
)
}
}
object NetworkChannel {