diff --git a/main/src/main/scala/sbt/Main.scala b/main/src/main/scala/sbt/Main.scala index 6c7dfd5f7..91984ff63 100644 --- a/main/src/main/scala/sbt/Main.scala +++ b/main/src/main/scala/sbt/Main.scala @@ -23,9 +23,8 @@ import sbt.internal.CommandStrings.BootCommand import sbt.internal._ import sbt.internal.client.BspClient import sbt.internal.inc.ScalaInstance -import sbt.internal.io.Retry import sbt.internal.nio.{ CheckBuildSources, FileTreeRepository } -import sbt.internal.server.NetworkChannel +import sbt.internal.server.{ BuildServerProtocol, NetworkChannel } import sbt.internal.util.Types.{ const, idFun } import sbt.internal.util._ import sbt.internal.util.complete.{ Parser, SizeParser } @@ -317,7 +316,10 @@ object BuiltinCommands { NetworkChannel.disconnect, waitCmd, promptChannel, - ) ++ allBasicCommands ++ ContinuousCommands.value + ) ++ + allBasicCommands ++ + ContinuousCommands.value ++ + BuildServerProtocol.commands def DefaultBootCommands: Seq[String] = WriteSbtVersion :: LoadProject :: NotifyUsersAboutShell :: s"$IfLast $Shell" :: Nil diff --git a/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala b/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala index 764e936fd..08600b896 100644 --- a/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala @@ -17,11 +17,13 @@ import sbt.Def._ import sbt.Keys._ import sbt.ScopeFilter.Make._ import sbt.SlashSyntax0._ +import sbt.StandardMain.exchange import sbt.internal.bsp._ import sbt.internal.langserver.ErrorCodes import sbt.internal.protocol.JsonRpcRequestMessage import sbt.librarymanagement.Configuration import sbt.util.Logger +import sjsonnew.shaded.scalajson.ast.unsafe.JNull import sjsonnew.shaded.scalajson.ast.unsafe.JValue import sjsonnew.support.scalajson.unsafe.Converter @@ -29,9 +31,44 @@ import scala.util.control.NonFatal object BuildServerProtocol { import sbt.internal.bsp.codec.JsonProtocol._ + private val capabilities = BuildServerCapabilities( CompileProvider(BuildServerConnection.languages), - dependencySourcesProvider = true + dependencySourcesProvider = true, + canReload = true + ) + + private val bspReload = "bspReload" + private val bspReloadFailed = "bspReloadFailed" + private val bspReloadSucceed = "bspReloadSucceed" + + lazy val commands: Seq[Command] = Seq( + Command.single(bspReload) { (state, reqId) => + import sbt.BasicCommandStrings._ + import sbt.internal.CommandStrings._ + val result = List( + StashOnFailure, + s"$OnFailure $bspReloadFailed $reqId", + LoadProjectImpl, + s"$bspReloadSucceed $reqId", + PopOnFailure, + FailureWall + ) ::: state + result + }, + Command.single(bspReloadFailed) { (state, reqId) => + exchange.respondError( + ErrorCodes.InternalError, + "reload failed", + Some(reqId), + state.source + ) + state + }, + Command.single(bspReloadSucceed) { (state, reqId) => + exchange.respondEvent(JNull, Some(reqId), state.source) + state + } ) lazy val globalSettings: Seq[Def.Setting[_]] = Seq( @@ -59,7 +96,6 @@ object BuildServerProtocol { val filter = ScopeFilter.in(targets.map(workspace)) // run the worker task concurrently Def.task { - import sbt.internal.bsp.codec.JsonProtocol._ val items = bspBuildTargetSourcesItem.all(filter).value val result = SourcesResult(items.toVector) s.respondEvent(result) @@ -86,7 +122,6 @@ object BuildServerProtocol { 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 s.respondEvent(BspCompileResult(None, statusCode)) } @@ -155,6 +190,9 @@ object BuildServerProtocol { case r: JsonRpcRequestMessage if r.method == "workspace/buildTargets" => val _ = callback.appendExec(Keys.bspWorkspaceBuildTargets.key.toString, Some(r.id)) + case r: JsonRpcRequestMessage if r.method == "workspace/reload" => + val _ = callback.appendExec(s"$bspReload ${r.id}", None) + case r: JsonRpcRequestMessage if r.method == "build/shutdown" => () diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/BuildServerCapabilities.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/BuildServerCapabilities.scala index d7bd59955..59b512820 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/bsp/BuildServerCapabilities.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/BuildServerCapabilities.scala @@ -8,25 +8,27 @@ package sbt.internal.bsp * @param compileProvider The languages the server supports compilation via method buildTarget/compile. * @param dependencySourcesProvider The server provides sources for library dependencies via method buildTarget/dependencySources + * @param canReload Reloading the workspace state through workspace/reload is supported */ final class BuildServerCapabilities private ( val compileProvider: Option[sbt.internal.bsp.CompileProvider], - val dependencySourcesProvider: Option[Boolean]) extends Serializable { + val dependencySourcesProvider: Option[Boolean], + val canReload: Option[Boolean]) extends Serializable { override def equals(o: Any): Boolean = o match { - case x: BuildServerCapabilities => (this.compileProvider == x.compileProvider) && (this.dependencySourcesProvider == x.dependencySourcesProvider) + case x: BuildServerCapabilities => (this.compileProvider == x.compileProvider) && (this.dependencySourcesProvider == x.dependencySourcesProvider) && (this.canReload == x.canReload) case _ => false } override def hashCode: Int = { - 37 * (37 * (37 * (17 + "sbt.internal.bsp.BuildServerCapabilities".##) + compileProvider.##) + dependencySourcesProvider.##) + 37 * (37 * (37 * (37 * (17 + "sbt.internal.bsp.BuildServerCapabilities".##) + compileProvider.##) + dependencySourcesProvider.##) + canReload.##) } override def toString: String = { - "BuildServerCapabilities(" + compileProvider + ", " + dependencySourcesProvider + ")" + "BuildServerCapabilities(" + compileProvider + ", " + dependencySourcesProvider + ", " + canReload + ")" } - private[this] def copy(compileProvider: Option[sbt.internal.bsp.CompileProvider] = compileProvider, dependencySourcesProvider: Option[Boolean] = dependencySourcesProvider): BuildServerCapabilities = { - new BuildServerCapabilities(compileProvider, dependencySourcesProvider) + private[this] def copy(compileProvider: Option[sbt.internal.bsp.CompileProvider] = compileProvider, dependencySourcesProvider: Option[Boolean] = dependencySourcesProvider, canReload: Option[Boolean] = canReload): BuildServerCapabilities = { + new BuildServerCapabilities(compileProvider, dependencySourcesProvider, canReload) } def withCompileProvider(compileProvider: Option[sbt.internal.bsp.CompileProvider]): BuildServerCapabilities = { copy(compileProvider = compileProvider) @@ -40,9 +42,15 @@ final class BuildServerCapabilities private ( def withDependencySourcesProvider(dependencySourcesProvider: Boolean): BuildServerCapabilities = { copy(dependencySourcesProvider = Option(dependencySourcesProvider)) } + def withCanReload(canReload: Option[Boolean]): BuildServerCapabilities = { + copy(canReload = canReload) + } + def withCanReload(canReload: Boolean): BuildServerCapabilities = { + copy(canReload = Option(canReload)) + } } object BuildServerCapabilities { - def apply(compileProvider: Option[sbt.internal.bsp.CompileProvider], dependencySourcesProvider: Option[Boolean]): BuildServerCapabilities = new BuildServerCapabilities(compileProvider, dependencySourcesProvider) - def apply(compileProvider: sbt.internal.bsp.CompileProvider, dependencySourcesProvider: Boolean): BuildServerCapabilities = new BuildServerCapabilities(Option(compileProvider), Option(dependencySourcesProvider)) + def apply(compileProvider: Option[sbt.internal.bsp.CompileProvider], dependencySourcesProvider: Option[Boolean], canReload: Option[Boolean]): BuildServerCapabilities = new BuildServerCapabilities(compileProvider, dependencySourcesProvider, canReload) + def apply(compileProvider: sbt.internal.bsp.CompileProvider, dependencySourcesProvider: Boolean, canReload: Boolean): BuildServerCapabilities = new BuildServerCapabilities(Option(compileProvider), Option(dependencySourcesProvider), Option(canReload)) } diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/BuildServerCapabilitiesFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/BuildServerCapabilitiesFormats.scala index ac8492132..360a19ecd 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/BuildServerCapabilitiesFormats.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/BuildServerCapabilitiesFormats.scala @@ -13,8 +13,9 @@ implicit lazy val BuildServerCapabilitiesFormat: JsonFormat[sbt.internal.bsp.Bui unbuilder.beginObject(__js) val compileProvider = unbuilder.readField[Option[sbt.internal.bsp.CompileProvider]]("compileProvider") val dependencySourcesProvider = unbuilder.readField[Option[Boolean]]("dependencySourcesProvider") + val canReload = unbuilder.readField[Option[Boolean]]("canReload") unbuilder.endObject() - sbt.internal.bsp.BuildServerCapabilities(compileProvider, dependencySourcesProvider) + sbt.internal.bsp.BuildServerCapabilities(compileProvider, dependencySourcesProvider, canReload) case None => deserializationError("Expected JsObject but found None") } @@ -23,6 +24,7 @@ implicit lazy val BuildServerCapabilitiesFormat: JsonFormat[sbt.internal.bsp.Bui builder.beginObject() builder.addField("compileProvider", obj.compileProvider) builder.addField("dependencySourcesProvider", obj.dependencySourcesProvider) + builder.addField("canReload", obj.canReload) builder.endObject() } } diff --git a/protocol/src/main/contraband/bsp.contra b/protocol/src/main/contraband/bsp.contra index 1732de2d9..7ab3a336d 100644 --- a/protocol/src/main/contraband/bsp.contra +++ b/protocol/src/main/contraband/bsp.contra @@ -186,6 +186,9 @@ type BuildServerCapabilities { # via method buildTarget/resources # resourcesProvider: Boolean + ## Reloading the workspace state through workspace/reload is supported + canReload: Boolean + # The server sends notifications to the client on build # target change events via buildTarget/didChange # buildTargetChangedProvider: Boolean diff --git a/server-test/src/test/scala/testpkg/BuildServerTest.scala b/server-test/src/test/scala/testpkg/BuildServerTest.scala index 9dbfbd0f0..fa594c670 100644 --- a/server-test/src/test/scala/testpkg/BuildServerTest.scala +++ b/server-test/src/test/scala/testpkg/BuildServerTest.scala @@ -74,6 +74,17 @@ object BuildServerTest extends AbstractServerTest { }) } + test("workspace/reload") { _ => + svr.sendJsonRpc( + """{ "jsonrpc": "2.0", "id": "15", "method": "workspace/reload"}""" + ) + assert(svr.waitForString(10.seconds) { s => + println(s) + (s contains """"id":"15"""") && + (s contains """"result":null""") + }) + } + def initializeRequest(): Unit = { svr.sendJsonRpc( """{ "jsonrpc": "2.0", "id": "10", "method": "build/initialize",