From ca720024809f7bc58c27795ba3907dd608f2acc5 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Mon, 20 Nov 2023 16:13:06 +0100 Subject: [PATCH] reconnect on `.bsp` change --- .../meta/internal/bsp/BspConnector.scala | 12 +++--- .../builds/BloopInstallProvider.scala | 4 +- .../scala/meta/internal/builds/BspOnly.scala | 12 +++++- .../meta/internal/builds/BuildTool.scala | 2 + .../meta/internal/builds/BuildTools.scala | 12 ++++-- .../scala/meta/internal/builds/Digest.scala | 17 ++++++++ .../scala/meta/internal/metals/Configs.scala | 1 + .../scala/meta/internal/metals/Indexer.scala | 39 ++++++++++++------- .../internal/metals/MetalsLspService.scala | 1 + 9 files changed, 73 insertions(+), 27 deletions(-) diff --git a/metals/src/main/scala/scala/meta/internal/bsp/BspConnector.scala b/metals/src/main/scala/scala/meta/internal/bsp/BspConnector.scala index 439aa19c85a..b0b29ee14ef 100644 --- a/metals/src/main/scala/scala/meta/internal/bsp/BspConnector.scala +++ b/metals/src/main/scala/scala/meta/internal/bsp/BspConnector.scala @@ -6,7 +6,6 @@ import scala.concurrent.ExecutionContext import scala.concurrent.Future import scala.meta.internal.bsp.BspConfigGenerationStatus._ -import scala.meta.internal.builds.BloopInstallProvider import scala.meta.internal.builds.BuildServerProvider import scala.meta.internal.builds.BuildTool import scala.meta.internal.builds.BuildTools @@ -47,7 +46,7 @@ class BspConnector( */ def resolve(buildTool: Option[BuildTool]): BspResolvedResult = { resolveExplicit().getOrElse { - if (buildTool.isInstanceOf[BloopInstallProvider] || buildTools.isBloop) + if (buildTool.exists(_.isBloopInstallProvider) || buildTools.isBloop) ResolvedBloop else bspServers.resolve() } @@ -179,9 +178,10 @@ class BspConnector( possibleBuildServerConn match { case None => Future.successful(None) case Some(buildServerConn) - if buildServerConn.isBloop && buildTool.exists( - _.isInstanceOf[SbtBuildTool] - ) => + if buildServerConn.isBloop && buildTool.exists { + case _: SbtBuildTool => true + case _ => false + } => // NOTE: (ckipp01) we special case this here since sbt bsp server // doesn't yet support metabuilds. So in the future when that // changes, re-work this and move the creation of this out above @@ -280,7 +280,7 @@ class BspConnector( if ( bloopPresent || buildTools .loadSupported() - .exists(_.isInstanceOf[BloopInstallProvider]) + .exists(_.isBloopInstallProvider) ) new BspConnectionDetails( BloopServers.name, diff --git a/metals/src/main/scala/scala/meta/internal/builds/BloopInstallProvider.scala b/metals/src/main/scala/scala/meta/internal/builds/BloopInstallProvider.scala index ccc4c5b12aa..c18e118046c 100644 --- a/metals/src/main/scala/scala/meta/internal/builds/BloopInstallProvider.scala +++ b/metals/src/main/scala/scala/meta/internal/builds/BloopInstallProvider.scala @@ -9,7 +9,7 @@ import scala.meta.io.AbsolutePath trait BloopInstallProvider extends BuildTool { /** - * Method used to generate the necesary .bloop files for the + * Method used to generate the necessary .bloop files for the * build tool. */ def bloopInstall( @@ -22,4 +22,6 @@ trait BloopInstallProvider extends BuildTool { * Args necessary for build tool to generate the .bloop files. */ def bloopInstallArgs(workspace: AbsolutePath): List[String] + + override val isBloopInstallProvider = true } diff --git a/metals/src/main/scala/scala/meta/internal/builds/BspOnly.scala b/metals/src/main/scala/scala/meta/internal/builds/BspOnly.scala index c175d8e23c7..fe6ef611789 100644 --- a/metals/src/main/scala/scala/meta/internal/builds/BspOnly.scala +++ b/metals/src/main/scala/scala/meta/internal/builds/BspOnly.scala @@ -1,5 +1,9 @@ package scala.meta.internal.builds +import java.security.MessageDigest + +import scala.meta.internal.builds.Digest +import scala.meta.internal.mtags.MD5 import scala.meta.io.AbsolutePath /** @@ -9,6 +13,12 @@ case class BspOnly( override val executableName: String, override val projectRoot: AbsolutePath, ) extends BuildTool { - override def digest(workspace: AbsolutePath): Option[String] = Some("OK") + override def digest(workspace: AbsolutePath): Option[String] = { + val digest = MessageDigest.getInstance("MD5") + val isSuccess = + Digest.digestJson(workspace.resolve(s"$executableName.json"), digest) + if (isSuccess) Some(MD5.bytesToHex(digest.digest())) + else None + } override val forcesBuildServer = true } diff --git a/metals/src/main/scala/scala/meta/internal/builds/BuildTool.scala b/metals/src/main/scala/scala/meta/internal/builds/BuildTool.scala index c6670036ee7..9dff270b338 100644 --- a/metals/src/main/scala/scala/meta/internal/builds/BuildTool.scala +++ b/metals/src/main/scala/scala/meta/internal/builds/BuildTool.scala @@ -24,6 +24,8 @@ trait BuildTool { val forcesBuildServer = false + val isBloopInstallProvider = false + } object BuildTool { diff --git a/metals/src/main/scala/scala/meta/internal/builds/BuildTools.scala b/metals/src/main/scala/scala/meta/internal/builds/BuildTools.scala index 13b891d309a..9d7c72a7cb0 100644 --- a/metals/src/main/scala/scala/meta/internal/builds/BuildTools.scala +++ b/metals/src/main/scala/scala/meta/internal/builds/BuildTools.scala @@ -130,16 +130,15 @@ final class BuildTools( .listFiles() .collect { case file - if file.isFile() && file.getName().endsWith(".json") && !knowBsps( - file.getName().stripSuffix(".json") - ) => + if file.isFile() && file.getName().endsWith(".json") && + !knownBsps(file.getName().stripSuffix(".json")) => BspOnly(file.getName().stripSuffix(".json"), root) } .toList } else Nil } - private def knowBsps = + private def knownBsps = Set(SbtBuildTool.name, MillBuildTool.name) ++ ScalaCli.names private def customProjectRoot = @@ -230,6 +229,11 @@ final class BuildTools( Some(MavenBuildTool.name) else if (isMill && MillBuildTool.isMillRelatedPath(path)) Some(MillBuildTool.name) + else if ( + path.isFile && path.filename.endsWith(".json") && + path.parent.filename == ".bsp" + ) + Some(path.filename.stripSuffix(".json")) else None } diff --git a/metals/src/main/scala/scala/meta/internal/builds/Digest.scala b/metals/src/main/scala/scala/meta/internal/builds/Digest.scala index 719e47dc3d5..bb2fc11a5b7 100644 --- a/metals/src/main/scala/scala/meta/internal/builds/Digest.scala +++ b/metals/src/main/scala/scala/meta/internal/builds/Digest.scala @@ -4,6 +4,8 @@ import java.nio.charset.StandardCharsets import java.nio.file.Files import java.security.MessageDigest +import scala.util.Success +import scala.util.Try import scala.util.control.NonFatal import scala.xml.Comment import scala.xml.Node @@ -15,6 +17,8 @@ import scala.meta.internal.mtags.MD5 import scala.meta.internal.parsing.Trees import scala.meta.io.AbsolutePath +import ujson.BytesRenderer + case class Digest( md5: String, status: Status, @@ -174,6 +178,19 @@ object Digest { false } } + + def digestJson( + file: AbsolutePath, + digest: MessageDigest, + ): Boolean = { + Try(ujson.read(file.readText)) match { + case Success(json) => + val bytes = json.transform(BytesRenderer()).toByteArray() + digest.update(bytes) + true + case _ => false + } + } } trait Digestable { diff --git a/metals/src/main/scala/scala/meta/internal/metals/Configs.scala b/metals/src/main/scala/scala/meta/internal/metals/Configs.scala index 4e9b040388c..ece48f7a172 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Configs.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Configs.scala @@ -40,6 +40,7 @@ object Configs { new FileSystemWatcher( Either.forLeft(s"$root/.metals/.reports/bloop/*/*") ), + new FileSystemWatcher(Either.forLeft(s"$root/.bsp/*.json")), ).asJava ) } diff --git a/metals/src/main/scala/scala/meta/internal/metals/Indexer.scala b/metals/src/main/scala/scala/meta/internal/metals/Indexer.scala index 57ddb7b2006..4ecf03eb904 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Indexer.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Indexer.scala @@ -19,6 +19,7 @@ import scala.meta.dialects._ import scala.meta.inputs.Input import scala.meta.internal.bsp.BspSession import scala.meta.internal.bsp.BuildChange +import scala.meta.internal.builds.BspOnly import scala.meta.internal.builds.BuildTool import scala.meta.internal.builds.BuildTools import scala.meta.internal.builds.Digest.Status @@ -88,25 +89,33 @@ final case class Indexer( buildTool: BuildTool, checksum: String, importBuild: BspSession => Future[Unit], + reconnectToBuildServer: () => Future[BuildChange], ): Future[BuildChange] = { def reloadAndIndex(session: BspSession): Future[BuildChange] = { workspaceReload().persistChecksumStatus(Status.Started, buildTool) - session - .workspaceReload() - .flatMap(_ => importBuild(session)) - .map { _ => - scribe.info("Correctly reloaded workspace") - profiledIndexWorkspace(doctorCheck) - workspaceReload().persistChecksumStatus(Status.Installed, buildTool) - BuildChange.Reloaded - } - .recoverWith { case NonFatal(e) => - scribe.error(s"Unable to reload workspace: ${e.getMessage()}") - workspaceReload().persistChecksumStatus(Status.Failed, buildTool) - languageClient.showMessage(Messages.ReloadProjectFailed) - Future.successful(BuildChange.Failed) - } + buildTool match { + case _: BspOnly => reconnectToBuildServer() + case _ => + session + .workspaceReload() + .flatMap(_ => importBuild(session)) + .map { _ => + scribe.info("Correctly reloaded workspace") + profiledIndexWorkspace(doctorCheck) + workspaceReload().persistChecksumStatus( + Status.Installed, + buildTool, + ) + BuildChange.Reloaded + } + .recoverWith { case NonFatal(e) => + scribe.error(s"Unable to reload workspace: ${e.getMessage()}") + workspaceReload().persistChecksumStatus(Status.Failed, buildTool) + languageClient.showMessage(Messages.ReloadProjectFailed) + Future.successful(BuildChange.Failed) + } + } } bspSession() match { 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 d52e544d83c..84437fc2a15 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala @@ -2153,6 +2153,7 @@ class MetalsLspService( found.buildTool, found.digest, importBuild, + quickConnectToBuildServer, ) case None => Future.successful(BuildChange.None)