mirror of https://github.com/sbt/sbt.git
separate BSP and LSP handlers + add bspWorkspace task
This commit is contained in:
parent
a415cd0cfc
commit
454ee61289
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue