diff --git a/metals/src/main/scala/scala/meta/internal/builds/SbtBuildTool.scala b/metals/src/main/scala/scala/meta/internal/builds/SbtBuildTool.scala index 61c36a479d9..a3205c54ea7 100644 --- a/metals/src/main/scala/scala/meta/internal/builds/SbtBuildTool.scala +++ b/metals/src/main/scala/scala/meta/internal/builds/SbtBuildTool.scala @@ -4,6 +4,8 @@ import java.nio.file.Files import java.nio.file.Path import java.util.Properties +import scala.concurrent.Future + import scala.meta.inputs.Input import scala.meta.internal.metals.MetalsEnrichments._ import scala.meta.internal.metals._ @@ -61,6 +63,21 @@ case class SbtBuildTool( composeArgs(bspConfigArgs, workspace, bspDir) } + def shutdownBspServer( + shellRunner: ShellRunner, + workspace: AbsolutePath, + ): Future[Int] = { + val shutdownArgs = + composeArgs(List("--client", "shutdown"), workspace, workspace.toNIO) + scribe.info(s"running ${shutdownArgs.mkString(" ")}") + shellRunner.run( + "Shutting down sbt server", + shutdownArgs, + workspace, + true, + ) + } + private def composeArgs( sbtArgs: List[String], workspace: AbsolutePath, diff --git a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala index 1905ff76a94..b01be5a413e 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala @@ -31,6 +31,7 @@ import scala.meta.internal.builds.BuildServerProvider import scala.meta.internal.builds.BuildTool import scala.meta.internal.builds.BuildToolSelector import scala.meta.internal.builds.BuildTools +import scala.meta.internal.builds.SbtBuildTool import scala.meta.internal.builds.ScalaCliBuildTool import scala.meta.internal.builds.ShellRunner import scala.meta.internal.builds.WorkspaceReload @@ -1583,9 +1584,45 @@ class MetalsLspService( indexer.indexWorkspaceSources(buildTargets.allWritableData) } - def shutDownBloop(): Boolean = bloopServers.shutdownServer() + def restartBspServer(): Future[Boolean] = { + def emitMessage(msg: String) = { + languageClient.showMessage(new MessageParams(MessageType.Warning, msg)) + } + // This is for `bloop` and `sbt`, for which `build/shutdown` doesn't shutdown the server. + val shutdownBsp = + bspSession match { + case Some(session) if session.main.isBloop => + Future.successful(bloopServers.shutdownServer()) + case Some(session) if session.main.isSbt => + for { + currentBuildTool <- supportedBuildTool + res <- currentBuildTool match { + case Some(sbt: SbtBuildTool) => + for { + _ <- disconnectOldBuildServer() + code <- sbt.shutdownBspServer(shellRunner, folder) + } yield code == 0 + case _ => Future.successful(false) + } + } yield res + case s => Future.successful(s.nonEmpty) + } - def isBloop(): Boolean = bspSession.exists(_.main.isBloop) + for { + didShutdown <- shutdownBsp + _ = if (!didShutdown) { + bspSession match { + case Some(session) => + emitMessage( + s"Could not shutdown ${session.main.name} server. Will try to reconnect." + ) + case None => + emitMessage("No build server connected. Will try to connect.") + } + } + _ <- autoConnectToBuildServer() + } yield didShutdown + } def decodeFile(uri: String): Future[DecoderResponse] = fileDecoderProvider.decodedFileContents(uri) @@ -2530,7 +2567,7 @@ class MetalsLspService( def resetWorkspace(): Future[Unit] = { if (buildTools.isBloop) { - shutDownBloop() + bloopServers.shutdownServer() } disconnectOldBuildServer() .map { _ => diff --git a/metals/src/main/scala/scala/meta/internal/metals/WorkspaceLspService.scala b/metals/src/main/scala/scala/meta/internal/metals/WorkspaceLspService.scala index 49d70e3fa9b..2c5f1073954 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/WorkspaceLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/WorkspaceLspService.scala @@ -639,8 +639,10 @@ class WorkspaceLspService( case ServerCommands.ScanWorkspaceSources() => foreachSeq(_.indexSources(), ignoreValue = true) case ServerCommands.RestartBuildServer() => - folderServices.find(_.isBloop()).foreach(_.shutDownBloop()) - foreachSeq(_.autoConnectToBuildServer()) + onCurrentFolder( + _.restartBspServer().ignoreValue, + "restart BSP server", + ).asJavaObject case ServerCommands.GenerateBspConfig() => onCurrentFolder( _.generateBspConfig(), diff --git a/tests/slow/src/test/scala/tests/sbt/SbtServerSuite.scala b/tests/slow/src/test/scala/tests/sbt/SbtServerSuite.scala index c06033905a8..96d3ce90ead 100644 --- a/tests/slow/src/test/scala/tests/sbt/SbtServerSuite.scala +++ b/tests/slow/src/test/scala/tests/sbt/SbtServerSuite.scala @@ -15,6 +15,11 @@ import scala.meta.io.AbsolutePath import ch.epfl.scala.bsp4j.DebugSessionParamsDataKind import ch.epfl.scala.bsp4j.ScalaMainClass +import scribe.LogRecord +import scribe.Logger +import scribe.output.LogOutput +import scribe.output.format.OutputFormat +import scribe.writer.Writer import tests.BaseImportSuite import tests.SbtBuildLayout import tests.SbtServerInitializer @@ -230,6 +235,40 @@ class SbtServerSuite } yield () } + test("restart-server") { + val buffer = new StringBuilder() + val initHandlers = Logger.root.handlers + val writer = new Writer { + def write( + record: LogRecord, + output: LogOutput, + outputFormat: OutputFormat, + ): Unit = buffer.append(output.plainText) + } + Logger.root.withHandler(writer = writer).replace() + cleanWorkspace() + for { + _ <- initialize( + s"""|/project/build.properties + |sbt.version=${V.sbtVersion} + |/build.sbt + |${SbtBuildLayout.commonSbtSettings} + |scalaVersion := "${V.scala213}" + |""".stripMargin + ) + _ = buffer.clear() + _ <- server.server.restartBspServer() + } yield { + val logs = buffer.result() + assert(logs.contains("sbt server started")) + initHandlers + .foldLeft(Logger.root.clearHandlers())((logger, handler) => + logger.withHandler(handler) + ) + .replace() + } + } + test("debug") { cleanWorkspace() val mainClass = new ScalaMainClass(