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 sjsonnew.JsonFormat
|
||||||
import sbt.internal.protocol._
|
import sbt.internal.protocol._
|
||||||
import sbt.util.Logger
|
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.langserver.{ CancelRequestParams => CRP }
|
||||||
import sbt.internal.bsp._
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ServerHandler allows plugins to extend sbt server.
|
* ServerHandler allows plugins to extend sbt server.
|
||||||
|
|
@ -65,6 +64,7 @@ trait ServerCallback {
|
||||||
def jsonRpcRespondError(execId: Option[String], code: Long, message: String): Unit
|
def jsonRpcRespondError(execId: Option[String], code: Long, message: String): Unit
|
||||||
def jsonRpcNotify[A: JsonFormat](method: String, params: A): Unit
|
def jsonRpcNotify[A: JsonFormat](method: String, params: A): Unit
|
||||||
def appendExec(exec: Exec): Boolean
|
def appendExec(exec: Exec): Boolean
|
||||||
|
def appendExec(commandLine: String, requestId: Option[String]): Boolean
|
||||||
def log: Logger
|
def log: Logger
|
||||||
def name: String
|
def name: String
|
||||||
|
|
||||||
|
|
@ -74,6 +74,4 @@ trait ServerCallback {
|
||||||
private[sbt] def onSettingQuery(execId: Option[String], req: Q): Unit
|
private[sbt] def onSettingQuery(execId: Option[String], req: Q): Unit
|
||||||
private[sbt] def onCompletionRequest(execId: Option[String], cp: CP): Unit
|
private[sbt] def onCompletionRequest(execId: Option[String], cp: CP): Unit
|
||||||
private[sbt] def onCancellationRequest(execId: Option[String], crp: CRP): 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),
|
fileConverter := MappedFileConverter(rootPaths.value, allowMachinePath.value),
|
||||||
fullServerHandlers := {
|
fullServerHandlers := {
|
||||||
(Vector(LanguageServerProtocol.handler(fileConverter.value))
|
Seq(
|
||||||
++ serverHandlers.value
|
LanguageServerProtocol.handler(fileConverter.value),
|
||||||
++ Vector(ServerHandler.fallback))
|
BuildServerProtocol.handler(sbtVersion.value)
|
||||||
|
) ++ serverHandlers.value :+ ServerHandler.fallback
|
||||||
},
|
},
|
||||||
uncachedStamper := Stamps.uncachedStamps(fileConverter.value),
|
uncachedStamper := Stamps.uncachedStamps(fileConverter.value),
|
||||||
reusableStamper := Stamps.timeWrapLibraryStamps(uncachedStamper.value, 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 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 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 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 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 bspBuildTarget = settingKey[BuildTarget]("Description of the BSP build target").withRank(DSetting)
|
||||||
val bspBuildTargetSources = inputKey[Unit]("").withRank(DTask)
|
val bspBuildTargetSources = inputKey[Unit]("").withRank(DTask)
|
||||||
val bspBuildTargetSourcesItem = taskKey[SourcesItem]("").withRank(DTask)
|
val bspBuildTargetSourcesItem = taskKey[SourcesItem]("").withRank(DTask)
|
||||||
|
|
|
||||||
|
|
@ -242,7 +242,7 @@ object BuiltinCommands {
|
||||||
act,
|
act,
|
||||||
continuous,
|
continuous,
|
||||||
clearCaches,
|
clearCaches,
|
||||||
) ++ allBasicCommands ++ server.BuildServerProtocol.commands
|
) ++ allBasicCommands
|
||||||
|
|
||||||
def DefaultBootCommands: Seq[String] =
|
def DefaultBootCommands: Seq[String] =
|
||||||
WriteSbtVersion :: LoadProject :: NotifyUsersAboutShell :: s"$IfLast $Shell" :: Nil
|
WriteSbtVersion :: LoadProject :: NotifyUsersAboutShell :: s"$IfLast $Shell" :: Nil
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,7 @@ package sbt
|
||||||
|
|
||||||
import sbt.internal.{ Load, LoadedBuildUnit }
|
import sbt.internal.{ Load, LoadedBuildUnit }
|
||||||
import sbt.internal.util.{ AttributeKey, Dag, Types }
|
import sbt.internal.util.{ AttributeKey, Dag, Types }
|
||||||
|
import sbt.librarymanagement.{ ConfigRef, Configuration }
|
||||||
import sbt.librarymanagement.{ Configuration, ConfigRef }
|
|
||||||
|
|
||||||
import Types.const
|
import Types.const
|
||||||
import Def.Initialize
|
import Def.Initialize
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
@ -23,6 +21,16 @@ object ScopeFilter {
|
||||||
type ConfigurationFilter = AxisFilter[ConfigKey]
|
type ConfigurationFilter = AxisFilter[ConfigKey]
|
||||||
type TaskFilter = AxisFilter[AttributeKey[_]]
|
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.
|
* Constructs a Scope filter from filters for the individual axes.
|
||||||
* If a project filter is not supplied, the enclosing project is selected.
|
* If a project filter is not supplied, the enclosing project is selected.
|
||||||
|
|
|
||||||
|
|
@ -11,32 +11,51 @@ package server
|
||||||
|
|
||||||
import java.net.URI
|
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 sbt.BuildSyntax._
|
||||||
|
import sbt.Def._
|
||||||
import scala.collection.mutable
|
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 sjsonnew.support.scalajson.unsafe.Converter
|
||||||
import Def._
|
|
||||||
import Keys._
|
|
||||||
import ScopeFilter.Make._
|
|
||||||
|
|
||||||
object BuildServerProtocol {
|
object BuildServerProtocol {
|
||||||
private[sbt] val idMap: mutable.Map[BuildTargetIdentifier, (ProjectRef, Configuration)] =
|
import sbt.internal.bsp.codec.JsonProtocol._
|
||||||
mutable.Map.empty
|
private val bspVersion = "2.0.0-M5"
|
||||||
|
private val capabilities = BuildClientCapabilities(languageIds = Vector("scala"))
|
||||||
def commands: List[Command] = List()
|
|
||||||
|
|
||||||
lazy val globalSettings: Seq[Def.Setting[_]] = Seq(
|
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
|
// https://github.com/build-server-protocol/build-server-protocol/blob/master/docs/specification.md#build-target-sources-request
|
||||||
bspBuildTargetSources := (Def.inputTaskDyn {
|
bspBuildTargetSources := Def.inputTaskDyn {
|
||||||
import DefaultParsers._
|
|
||||||
val s = state.value
|
val s = state.value
|
||||||
val args: Seq[String] = spaceDelimited("<arg>").parsed
|
val workspace = bspWorkspace.value
|
||||||
val filter = toScopeFilter(args)
|
val targets = spaceDelimited().parsed.map(uri => BuildTargetIdentifier(URI.create(uri)))
|
||||||
|
val filter = ScopeFilter.in(targets.map(workspace))
|
||||||
// run the worker task concurrently
|
// run the worker task concurrently
|
||||||
Def.task {
|
Def.task {
|
||||||
import sbt.internal.bsp.codec.JsonProtocol._
|
import sbt.internal.bsp.codec.JsonProtocol._
|
||||||
|
|
@ -44,12 +63,13 @@ object BuildServerProtocol {
|
||||||
val result = SourcesResult(items.toVector)
|
val result = SourcesResult(items.toVector)
|
||||||
s.respondEvent(result)
|
s.respondEvent(result)
|
||||||
}
|
}
|
||||||
}).evaluated,
|
}.evaluated,
|
||||||
bspBuildTargetSources / aggregate := false,
|
bspBuildTargetSources / aggregate := false,
|
||||||
bspBuildTargetCompile := Def.inputTaskDyn {
|
bspBuildTargetCompile := Def.inputTaskDyn {
|
||||||
val s: State = state.value
|
val s: State = state.value
|
||||||
val args: Seq[String] = spaceDelimited().parsed
|
val workspace = bspWorkspace.value
|
||||||
val filter = toScopeFilter(args)
|
val targets = spaceDelimited().parsed.map(uri => BuildTargetIdentifier(URI.create(uri)))
|
||||||
|
val filter = ScopeFilter.in(targets.map(workspace))
|
||||||
Def.task {
|
Def.task {
|
||||||
import sbt.internal.bsp.codec.JsonProtocol._
|
import sbt.internal.bsp.codec.JsonProtocol._
|
||||||
val statusCode = Keys.bspBuildTargetCompileItem.all(filter).value.max
|
val statusCode = Keys.bspBuildTargetCompileItem.all(filter).value.max
|
||||||
|
|
@ -57,19 +77,17 @@ object BuildServerProtocol {
|
||||||
}
|
}
|
||||||
}.evaluated,
|
}.evaluated,
|
||||||
bspBuildTargetCompile / aggregate := false,
|
bspBuildTargetCompile / aggregate := false,
|
||||||
bspBuildTargetScalacOptions := (Def.inputTaskDyn {
|
bspBuildTargetScalacOptions := Def.inputTaskDyn {
|
||||||
import DefaultParsers._
|
|
||||||
val s = state.value
|
val s = state.value
|
||||||
val args: Seq[String] = spaceDelimited("<arg>").parsed
|
val workspace = bspWorkspace.value
|
||||||
val filter = toScopeFilter(args)
|
val targets = spaceDelimited().parsed.map(uri => BuildTargetIdentifier(URI.create(uri)))
|
||||||
// run the worker task concurrently
|
val filter = ScopeFilter.in(targets.map(workspace))
|
||||||
Def.task {
|
Def.task {
|
||||||
import sbt.internal.bsp.codec.JsonProtocol._
|
|
||||||
val items = bspBuildTargetScalacOptionsItem.all(filter).value
|
val items = bspBuildTargetScalacOptionsItem.all(filter).value
|
||||||
val result = ScalacOptionsResult(items.toVector)
|
val result = ScalacOptionsResult(items.toVector)
|
||||||
s.respondEvent(result)
|
s.respondEvent(result)
|
||||||
}
|
}
|
||||||
}).evaluated,
|
}.evaluated,
|
||||||
bspBuildTargetScalacOptions / aggregate := false
|
bspBuildTargetScalacOptions / aggregate := false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -86,10 +104,10 @@ object BuildServerProtocol {
|
||||||
val dirs = unmanagedSourceDirectories.value
|
val dirs = unmanagedSourceDirectories.value
|
||||||
val managed = managedSources.value
|
val managed = managedSources.value
|
||||||
val items = (dirs.toVector map { dir =>
|
val items = (dirs.toVector map { dir =>
|
||||||
SourceItem(dir.toURI, SourceItemKind.Directory, false)
|
SourceItem(dir.toURI, SourceItemKind.Directory, generated = false)
|
||||||
}) ++
|
}) ++
|
||||||
(managed.toVector map { x =>
|
(managed.toVector map { x =>
|
||||||
SourceItem(x.toURI, SourceItemKind.File, true)
|
SourceItem(x.toURI, SourceItemKind.File, generated = true)
|
||||||
})
|
})
|
||||||
SourcesItem(id, items)
|
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 {
|
private def bspBuildTargetSetting: Def.Initialize[BuildTarget] = Def.settingDyn {
|
||||||
import sbt.internal.bsp.codec.JsonProtocol._
|
import sbt.internal.bsp.codec.JsonProtocol._
|
||||||
val buildTargetIdentifier = Keys.buildTargetIdentifier.value
|
val buildTargetIdentifier = Keys.buildTargetIdentifier.value
|
||||||
|
|
@ -157,13 +225,11 @@ object BuildServerProtocol {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def toScopeFilter(args: Seq[String]): ScopeFilter = {
|
def scopeFilter(
|
||||||
val filters = args map { arg =>
|
targets: Seq[BuildTargetIdentifier],
|
||||||
val id = BuildTargetIdentifier(new URI(arg))
|
workspace: Map[BuildTargetIdentifier, Scope]
|
||||||
val pair = idMap(id)
|
): ScopeFilter = {
|
||||||
ScopeFilter(inProjects(pair._1), inConfigurations(pair._2))
|
ScopeFilter.in(targets.map(workspace))
|
||||||
}
|
|
||||||
filters.tail.foldLeft(filters.head) { _ || _ }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def toId(ref: ProjectReference, config: Configuration): BuildTargetIdentifier =
|
def toId(ref: ProjectReference, config: Configuration): BuildTargetIdentifier =
|
||||||
|
|
@ -191,41 +257,3 @@ object BuildServerProtocol {
|
||||||
} yield depId
|
} 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 internal
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import sjsonnew.JsonFormat
|
import sbt.internal.langserver._
|
||||||
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.protocol._
|
import sbt.internal.protocol._
|
||||||
import sbt.internal.protocol.codec._
|
import sbt.internal.protocol.codec._
|
||||||
import sbt.internal.langserver._
|
import sbt.protocol.{ CompletionParams => CP, SettingQuery => Q }
|
||||||
import sbt.internal.util.ObjectEvent
|
import sjsonnew.shaded.scalajson.ast.unsafe.JValue
|
||||||
import sbt.util.Logger
|
import sjsonnew.support.scalajson.unsafe.Converter
|
||||||
import scala.concurrent.ExecutionContext
|
|
||||||
import xsbti.FileConverter
|
import xsbti.FileConverter
|
||||||
|
|
||||||
private[sbt] final case class LangServerError(code: Long, message: String)
|
private[sbt] final case class LangServerError(code: Long, message: String)
|
||||||
extends Throwable(message)
|
extends Throwable(message)
|
||||||
|
|
||||||
private[sbt] object LanguageServerProtocol {
|
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 = {
|
lazy val serverCapabilities: ServerCapabilities = {
|
||||||
ServerCapabilities(
|
ServerCapabilities(
|
||||||
|
|
@ -37,200 +42,54 @@ private[sbt] object LanguageServerProtocol {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
def handler(converter: FileConverter): ServerHandler =
|
def handler(converter: FileConverter): ServerHandler = ServerHandler { callback =>
|
||||||
ServerHandler({
|
import callback._
|
||||||
case callback: ServerCallback =>
|
ServerIntent(
|
||||||
import callback._
|
{
|
||||||
ServerIntent(
|
case r: JsonRpcRequestMessage if r.method == "initialize" =>
|
||||||
{
|
if (authOptions(ServerAuthentication.Token)) {
|
||||||
import sbt.internal.langserver.codec.JsonProtocol._
|
val param = Converter.fromJson[InitializeParams](json(r)).get
|
||||||
import internalJsonProtocol._
|
val optionJson = param.initializationOptions.getOrElse(
|
||||||
import sbt.internal.bsp.codec.JsonProtocol._
|
throw LangServerError(
|
||||||
def json(r: JsonRpcRequestMessage) =
|
ErrorCodes.InvalidParams,
|
||||||
r.params.getOrElse(
|
"initializationOptions is expected on 'initialize' param."
|
||||||
throw LangServerError(
|
|
||||||
ErrorCodes.InvalidParams,
|
|
||||||
s"param is expected on '${r.method}' method."
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
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" =>
|
||||||
case r: JsonRpcRequestMessage if r.method == "textDocument/definition" =>
|
val _ = Definition.lspDefinition(json(r), r.id, CommandSource(name), converter, log)(
|
||||||
implicit val executionContext: ExecutionContext = StandardMain.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))))
|
|
||||||
()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Implements Language Server Protocol <https://github.com/Microsoft/language-server-protocol>. */
|
case r: JsonRpcRequestMessage if r.method == "sbt/exec" =>
|
||||||
private[sbt] trait LanguageServerProtocol { self: NetworkChannel =>
|
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
|
case r: JsonRpcRequestMessage if r.method == "sbt/cancelRequest" =>
|
||||||
protected def authOptions: Set[ServerAuthentication]
|
val param = Converter.fromJson[CancelRequestParams](json(r)).get
|
||||||
protected def setInitialized(value: Boolean): Unit
|
onCancellationRequest(Option(r.id), param)
|
||||||
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
|
|
||||||
|
|
||||||
protected lazy val callbackImpl: ServerCallback = new ServerCallback {
|
case r: JsonRpcRequestMessage if r.method == "sbt/completion" =>
|
||||||
def jsonRpcRespond[A: JsonFormat](event: A, execId: Option[String]): Unit =
|
import sbt.protocol.codec.JsonProtocol._
|
||||||
self.respondResult(event, execId)
|
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)
|
case n: JsonRpcNotificationMessage if n.method == "textDocument/didSave" =>
|
||||||
|
val _ = appendExec(";Test/compile; collectAnalyses", None)
|
||||||
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)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,15 +12,16 @@ package server
|
||||||
import java.net.{ Socket, SocketTimeoutException }
|
import java.net.{ Socket, SocketTimeoutException }
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
import sbt.internal.langserver.{ CancelRequestParams, ErrorCodes }
|
import sbt.internal.langserver.{ CancelRequestParams, ErrorCodes, LogMessageParams, MessageType }
|
||||||
import sbt.internal.protocol.{
|
import sbt.internal.protocol.{
|
||||||
JsonRpcNotificationMessage,
|
JsonRpcNotificationMessage,
|
||||||
JsonRpcRequestMessage,
|
JsonRpcRequestMessage,
|
||||||
JsonRpcResponseError
|
JsonRpcResponseError,
|
||||||
|
JsonRpcResponseMessage
|
||||||
}
|
}
|
||||||
|
import sbt.internal.util.ObjectEvent
|
||||||
import sbt.internal.util.codec.JValueFormats
|
import sbt.internal.util.codec.JValueFormats
|
||||||
import sbt.internal.util.complete.Parser
|
import sbt.internal.util.complete.Parser
|
||||||
import sbt.internal.util.ObjectEvent
|
|
||||||
import sbt.protocol._
|
import sbt.protocol._
|
||||||
import sbt.util.Logger
|
import sbt.util.Logger
|
||||||
import sjsonnew._
|
import sjsonnew._
|
||||||
|
|
@ -39,9 +40,7 @@ final class NetworkChannel(
|
||||||
instance: ServerInstance,
|
instance: ServerInstance,
|
||||||
handlers: Seq[ServerHandler],
|
handlers: Seq[ServerHandler],
|
||||||
val log: Logger
|
val log: Logger
|
||||||
) extends CommandChannel
|
) extends CommandChannel { self =>
|
||||||
with LanguageServerProtocol
|
|
||||||
with BuildServerImpl {
|
|
||||||
import NetworkChannel._
|
import NetworkChannel._
|
||||||
|
|
||||||
private val running = new AtomicBoolean(true)
|
private val running = new AtomicBoolean(true)
|
||||||
|
|
@ -59,6 +58,34 @@ final class NetworkChannel(
|
||||||
private lazy val jsonFormat = new sjsonnew.BasicJsonProtocol with JValueFormats {}
|
private lazy val jsonFormat = new sjsonnew.BasicJsonProtocol with JValueFormats {}
|
||||||
private val pendingRequests: mutable.Map[String, JsonRpcRequestMessage] = mutable.Map()
|
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 setContentType(ct: String): Unit = synchronized { _contentType = ct }
|
||||||
def contentType: String = _contentType
|
def contentType: String = _contentType
|
||||||
|
|
||||||
|
|
@ -172,11 +199,11 @@ final class NetworkChannel(
|
||||||
}
|
}
|
||||||
|
|
||||||
private lazy val intents = {
|
private lazy val intents = {
|
||||||
val cb = callbackImpl
|
|
||||||
handlers.toVector map { h =>
|
handlers.toVector map { h =>
|
||||||
h.handler(cb)
|
h.handler(callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy val onRequestMessage: PartialFunction[JsonRpcRequestMessage, Unit] =
|
lazy val onRequestMessage: PartialFunction[JsonRpcRequestMessage, Unit] =
|
||||||
intents.foldLeft(PartialFunction.empty[JsonRpcRequestMessage, Unit]) {
|
intents.foldLeft(PartialFunction.empty[JsonRpcRequestMessage, Unit]) {
|
||||||
case (f, i) => f orElse i.onRequest
|
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) = {
|
private def onExecCommand(cmd: ExecCommand) = {
|
||||||
if (initialized) {
|
if (initialized) {
|
||||||
append(
|
append(
|
||||||
|
|
@ -535,6 +550,73 @@ final class NetworkChannel(
|
||||||
running.set(false)
|
running.set(false)
|
||||||
out.close()
|
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 {
|
object NetworkChannel {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue