From 4c59ad77a82ed73ada79d828162e17bf037d42a1 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Thu, 15 Feb 2024 12:53:54 +0100 Subject: [PATCH 1/7] feat: add setting to choose preferred build server --- .../meta/internal/builds/BuildTool.scala | 6 ++ .../internal/builds/ScalaCliBuildTool.scala | 4 +- .../internal/metals/MetalsLspService.scala | 40 +++++++---- .../internal/metals/UserConfiguration.scala | 21 ++++++ .../scala/tests/PreferredBuildServer.scala | 71 +++++++++++++++++++ .../scala/tests/BuildServerInitializer.scala | 2 +- 6 files changed, 129 insertions(+), 15 deletions(-) create mode 100644 tests/slow/src/test/scala/tests/PreferredBuildServer.scala 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 13bcc38bac6..83720a511f9 100644 --- a/metals/src/main/scala/scala/meta/internal/builds/BuildTool.scala +++ b/metals/src/main/scala/scala/meta/internal/builds/BuildTool.scala @@ -34,6 +34,12 @@ trait BuildTool { */ def buildServerName = executableName + def possibleBuildServerNames: List[String] = List(buildServerName) + + def isBspGenerated(workspace: AbsolutePath): Boolean = + possibleBuildServerNames + .map(name => workspace.resolve(".bsp").resolve(s"$name.json")) + .exists(_.isFile) } object BuildTool { diff --git a/metals/src/main/scala/scala/meta/internal/builds/ScalaCliBuildTool.scala b/metals/src/main/scala/scala/meta/internal/builds/ScalaCliBuildTool.scala index 5fbd6013164..8604062d545 100644 --- a/metals/src/main/scala/scala/meta/internal/builds/ScalaCliBuildTool.scala +++ b/metals/src/main/scala/scala/meta/internal/builds/ScalaCliBuildTool.scala @@ -61,13 +61,13 @@ class ScalaCliBuildTool( override val forcesBuildServer = true - def isBspGenerated(workspace: AbsolutePath): Boolean = - ScalaCliBuildTool.pathsToScalaCliBsp(workspace).exists(_.isFile) + override def possibleBuildServerNames = ScalaCli.names.toList } object ScalaCliBuildTool { def name = "scala-cli" + def pathsToScalaCliBsp(root: AbsolutePath): List[AbsolutePath] = ScalaCli.names.toList.map(name => root.resolve(".bsp").resolve(s"$name.json") 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 3cdc8853d73..d7abe8e0011 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala @@ -2091,23 +2091,25 @@ class MetalsLspService( buildTool: Option[BuildTool.Found], chosenBuildServer: Option[String], ): Future[BuildChange] = { - val isBloopOrEmpty = chosenBuildServer.isEmpty || chosenBuildServer.exists( + val preferredList = + userConfig.preferredBuildServes.map(_.trim().toLowerCase()) + def isPreferredAboveBloop(buildServer: String) = { + val idx = preferredList.indexOf(buildServer) + if (idx >= 0) { + val bloopIdx = preferredList.indexOf("bloop") + bloopIdx < 0 || bloopIdx > idx + } else false + } + def isBloop = chosenBuildServer.exists( _ == BloopServers.name ) + buildTool match { case Some(BuildTool.Found(buildTool: BloopInstallProvider, digest)) - if isBloopOrEmpty => + if isBloop || (chosenBuildServer.isEmpty && !isPreferredAboveBloop( + buildTool.buildServerName + )) => slowConnectToBloopServer(forceImport, buildTool, digest) - case Some(BuildTool.Found(buildTool: ScalaCliBuildTool, _)) - if !buildTool.isBspGenerated(folder) => - tables.buildServers.chooseServer(buildTool.buildServerName) - buildTool - .generateBspConfig( - folder, - args => bspConfigGenerator.runUnconditionally(buildTool, args), - statusBar, - ) - .flatMap(_ => quickConnectToBuildServer()) // If there is no .bazelbsp present, we ask user to write bsp config // After that, we should fall into the last case and index workspace case Some(BuildTool.Found(_: BazelBuildTool, _)) @@ -2122,12 +2124,26 @@ class MetalsLspService( forceImport, ) .flatMap(_ => quickConnectToBuildServer()) + case Some(BuildTool.Found(buildTool: BuildServerProvider, _)) + if !buildTool.isBspGenerated(folder) => + tables.buildServers.chooseServer(buildTool.buildServerName) + buildTool + .generateBspConfig( + folder, + args => bspConfigGenerator.runUnconditionally(buildTool, args), + statusBar, + ) + .flatMap(_ => quickConnectToBuildServer()) case Some(BuildTool.Found(buildTool, _)) if !chosenBuildServer.exists( _ == buildTool.buildServerName ) && buildTool.forcesBuildServer => tables.buildServers.chooseServer(buildTool.buildServerName) quickConnectToBuildServer() + case Some(BuildTool.Found(buildTool: BloopInstallProvider, _)) + if chosenBuildServer.isEmpty => + tables.buildServers.chooseServer(buildTool.buildServerName) + quickConnectToBuildServer() case Some(found) => indexer.reloadWorkspaceAndIndex( forceImport, diff --git a/metals/src/main/scala/scala/meta/internal/metals/UserConfiguration.scala b/metals/src/main/scala/scala/meta/internal/metals/UserConfiguration.scala index c5ed01feb87..7d34bf5d818 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/UserConfiguration.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/UserConfiguration.scala @@ -56,6 +56,7 @@ case class UserConfiguration( verboseCompilation: Boolean = false, automaticImportBuild: AutoImportBuildKind = AutoImportBuildKind.Off, scalaCliLauncher: Option[String] = None, + preferredBuildServes: List[String] = Nil, ) { def currentBloopVersion: String = @@ -349,6 +350,19 @@ object UserConfiguration { |only automatically import a build when a project is first opened, "all" will automate |build imports after subsequent changes as well.""".stripMargin, ), + UserConfigurationOption( + "preferred-build-servers", + "[]", + """["sbt"]""", + "List of preferred build servers.", + """|If multiple build servers available, this list provides an order, + |that overrides the default partial order, in which the build server should be chosen. + |The default partial order is: + |1. `bloop` + |2. other build servers (asks user) + |3. fallback `scala-cli` (if no build server found) + |""".stripMargin, + ), ) def fromJson( @@ -570,6 +584,11 @@ object UserConfiguration { case _ => AutoImportBuildKind.Off } + val scalaCliLauncher = getStringKey("scala-cli-launcher") + + val preferredBuildServes = + getStringListKey("preferred-build-servers").getOrElse(Nil) + if (errors.isEmpty) { Right( UserConfiguration( @@ -602,6 +621,8 @@ object UserConfiguration { customProjectRoot, verboseCompilation, autoImportBuilds, + scalaCliLauncher, + preferredBuildServes, ) ) } else { diff --git a/tests/slow/src/test/scala/tests/PreferredBuildServer.scala b/tests/slow/src/test/scala/tests/PreferredBuildServer.scala new file mode 100644 index 00000000000..453a11e8799 --- /dev/null +++ b/tests/slow/src/test/scala/tests/PreferredBuildServer.scala @@ -0,0 +1,71 @@ +package tests + +import scala.meta.internal.metals.UserConfiguration +import scala.meta.internal.metals.{BuildInfo => V} + +class PreferredBuildServer extends BaseLspSuite("preferred-build-server") { + override def userConfig: UserConfiguration = + super.userConfig.copy(preferredBuildServes = List("sbt")) + + test("start-sbt-when-preferred-no-bsp") { + cleanWorkspace() + + val fileLayout = + s"""|/project/build.properties + |sbt.version=${V.sbtVersion} + |/build.sbt + |${SbtBuildLayout.commonSbtSettings} + |ThisBuild / scalaVersion := "${V.scala213}" + |val a = project.in(file("a")) + |/a/src/main/scala/a/A.scala + |package a + |object A { + | val a = 1 + |} + |""".stripMargin + FileLayout.fromString(fileLayout, workspace) + + for { + _ <- server.initialize() + _ <- server.initialized() + _ <- server.server.buildServerPromise.future + _ = assertNoDiff( + server.server.tables.buildServers.selectedServer().get, + "sbt", + ) + _ = assert(server.server.bspSession.exists(_.main.isSbt)) + } yield () + } + + test("start-sbt-when-preferred-with-bsp") { + cleanWorkspace() + + val fileLayout = + s"""|/project/build.properties + |sbt.version=${V.sbtVersion} + |/build.sbt + |${SbtBuildLayout.commonSbtSettings} + |ThisBuild / scalaVersion := "${V.scala213}" + |val a = project.in(file("a")) + |/a/src/main/scala/a/A.scala + |package a + |object A { + | val a = 1 + |} + |""".stripMargin + + FileLayout.fromString(fileLayout, workspace) + SbtServerInitializer.generateBspConfig(workspace, V.sbtVersion) + + for { + _ <- server.initialize() + _ <- server.initialized() + _ <- server.server.buildServerPromise.future + _ = assertNoDiff( + server.server.tables.buildServers.selectedServer().get, + "sbt", + ) + _ = assert(server.server.bspSession.exists(_.main.isSbt)) + } yield () + } +} diff --git a/tests/unit/src/main/scala/tests/BuildServerInitializer.scala b/tests/unit/src/main/scala/tests/BuildServerInitializer.scala index c41a036620d..b521fafcecf 100644 --- a/tests/unit/src/main/scala/tests/BuildServerInitializer.scala +++ b/tests/unit/src/main/scala/tests/BuildServerInitializer.scala @@ -133,7 +133,7 @@ object SbtServerInitializer extends BuildServerInitializer { } } - private def generateBspConfig( + def generateBspConfig( workspace: AbsolutePath, sbtVersion: String, ): Unit = { From dcb090ae8c73e77529127c1146b7a820e1c49ff7 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Fri, 16 Feb 2024 14:15:39 +0100 Subject: [PATCH 2/7] ask if should import --- .../meta/internal/builds/BloopInstall.scala | 5 +-- .../scala/meta/internal/metals/Messages.scala | 25 +++++++++++ .../internal/metals/MetalsLspService.scala | 43 +++++++++++++++---- .../internal/metals/UserConfiguration.scala | 3 ++ .../scala/tests/PreferredBuildServer.scala | 17 ++++++-- 5 files changed, 77 insertions(+), 16 deletions(-) diff --git a/metals/src/main/scala/scala/meta/internal/builds/BloopInstall.scala b/metals/src/main/scala/scala/meta/internal/builds/BloopInstall.scala index dc0f92c42f0..7061fe8d752 100644 --- a/metals/src/main/scala/scala/meta/internal/builds/BloopInstall.scala +++ b/metals/src/main/scala/scala/meta/internal/builds/BloopInstall.scala @@ -7,7 +7,6 @@ import scala.concurrent.ExecutionContext import scala.concurrent.Future import scala.meta.internal.builds.Digest.Status -import scala.meta.internal.metals.AutoImportBuildKind import scala.meta.internal.metals.BuildInfo import scala.meta.internal.metals.Confirmation import scala.meta.internal.metals.Messages._ @@ -133,9 +132,7 @@ final class BloopInstall( scribe.info(s"skipping build import with status '${result.name}'") Future.successful(result) case _ => - if ( - userConfig().automaticImportBuild == AutoImportBuildKind.Initial || userConfig().automaticImportBuild == AutoImportBuildKind.All - ) { + if (userConfig().shouldAutoImportNewProject) { runUnconditionally(buildTool, isImportInProcess) } else { scribe.debug("Awaiting user response...") diff --git a/metals/src/main/scala/scala/meta/internal/metals/Messages.scala b/metals/src/main/scala/scala/meta/internal/metals/Messages.scala index 6dca10da430..074641ccd64 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Messages.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Messages.scala @@ -189,6 +189,31 @@ object Messages { } } + object GenerateBspAndConnect { + def yes = new MessageActionItem("Connect") + + def notNow: MessageActionItem = Messages.notNow + + def params( + buildToolName: String, + buildServerName: String, + ): ShowMessageRequestParams = { + val params = new ShowMessageRequestParams() + params.setMessage( + s"New $buildToolName workspace detected, would you like connect to $buildServerName build server?" + ) + params.setType(MessageType.Info) + params.setActions( + List( + yes, + notNow, + dontShowAgain, + ).asJava + ) + params + } + } + object MainClass { val message = "Multiple main classes found. Which would you like to run?" } 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 d7abe8e0011..ae90dd60092 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala @@ -2126,14 +2126,28 @@ class MetalsLspService( .flatMap(_ => quickConnectToBuildServer()) case Some(BuildTool.Found(buildTool: BuildServerProvider, _)) if !buildTool.isBspGenerated(folder) => - tables.buildServers.chooseServer(buildTool.buildServerName) - buildTool - .generateBspConfig( - folder, - args => bspConfigGenerator.runUnconditionally(buildTool, args), - statusBar, - ) - .flatMap(_ => quickConnectToBuildServer()) + val notification = tables.dismissedNotifications.ImportChanges + if (userConfig.shouldAutoImportNewProject) { + generateBspAndConnect(buildTool) + } else if (notification.isDismissed) { + Future.successful(BuildChange.None) + } else { + scribe.debug("Awaiting user response...") + languageClient + .showMessageRequest( + Messages.GenerateBspAndConnect + .params(buildTool.executableName, buildTool.buildServerName) + ) + .asScala + .flatMap { item => + if (item == Messages.dontShowAgain) { + notification.dismissForever() + Future.successful(BuildChange.None) + } else if (item == Messages.GenerateBspAndConnect.yes) { + generateBspAndConnect(buildTool) + } else Future.successful(BuildChange.None) + } + } case Some(BuildTool.Found(buildTool, _)) if !chosenBuildServer.exists( _ == buildTool.buildServerName @@ -2157,6 +2171,19 @@ class MetalsLspService( } } + def generateBspAndConnect( + buildTool: BuildServerProvider + ): Future[BuildChange] = { + tables.buildServers.chooseServer(buildTool.buildServerName) + buildTool + .generateBspConfig( + folder, + args => bspConfigGenerator.runUnconditionally(buildTool, args), + statusBar, + ) + .flatMap(_ => quickConnectToBuildServer()) + } + /** * If there is no auto-connectable build server and no supported build tool is found * we assume it's a scala-cli project. diff --git a/metals/src/main/scala/scala/meta/internal/metals/UserConfiguration.scala b/metals/src/main/scala/scala/meta/internal/metals/UserConfiguration.scala index 7d34bf5d818..b4686eecbc9 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/UserConfiguration.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/UserConfiguration.scala @@ -59,6 +59,9 @@ case class UserConfiguration( preferredBuildServes: List[String] = Nil, ) { + def shouldAutoImportNewProject: Boolean = + automaticImportBuild != AutoImportBuildKind.Off + def currentBloopVersion: String = bloopVersion.getOrElse(BuildInfo.bloopVersion) diff --git a/tests/slow/src/test/scala/tests/PreferredBuildServer.scala b/tests/slow/src/test/scala/tests/PreferredBuildServer.scala index 453a11e8799..d96e4b25ef2 100644 --- a/tests/slow/src/test/scala/tests/PreferredBuildServer.scala +++ b/tests/slow/src/test/scala/tests/PreferredBuildServer.scala @@ -1,5 +1,6 @@ package tests +import scala.meta.internal.metals.Messages import scala.meta.internal.metals.UserConfiguration import scala.meta.internal.metals.{BuildInfo => V} @@ -10,6 +11,15 @@ class PreferredBuildServer extends BaseLspSuite("preferred-build-server") { test("start-sbt-when-preferred-no-bsp") { cleanWorkspace() + val importMessage = + Messages.GenerateBspAndConnect.params("sbt", "sbt").getMessage() + + client.showMessageRequestHandler = msg => { + if (msg.getMessage() == importMessage) + Some(Messages.GenerateBspAndConnect.notNow) + else None + } + val fileLayout = s"""|/project/build.properties |sbt.version=${V.sbtVersion} @@ -28,12 +38,11 @@ class PreferredBuildServer extends BaseLspSuite("preferred-build-server") { for { _ <- server.initialize() _ <- server.initialized() - _ <- server.server.buildServerPromise.future _ = assertNoDiff( - server.server.tables.buildServers.selectedServer().get, - "sbt", + client.workspaceMessageRequests, + importMessage, ) - _ = assert(server.server.bspSession.exists(_.main.isSbt)) + _ = assert(server.server.bspSession.isEmpty) } yield () } From 418060d3e735f7d20182d7db7952ba9ce4a43c6f Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Tue, 20 Feb 2024 10:38:04 +0100 Subject: [PATCH 3/7] make scala-cli auto connect --- .../scala/scala/meta/internal/metals/MetalsLspService.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ae90dd60092..7423d5e598b 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala @@ -2127,7 +2127,7 @@ class MetalsLspService( case Some(BuildTool.Found(buildTool: BuildServerProvider, _)) if !buildTool.isBspGenerated(folder) => val notification = tables.dismissedNotifications.ImportChanges - if (userConfig.shouldAutoImportNewProject) { + if (userConfig.shouldAutoImportNewProject || buildTool.forcesBuildServer) { generateBspAndConnect(buildTool) } else if (notification.isDismissed) { Future.successful(BuildChange.None) From c7ee595f153556ccf0b964ec28255dd537846a73 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Tue, 20 Feb 2024 11:38:55 +0100 Subject: [PATCH 4/7] change `preferredBuildServes` to `defaultBSPToBuildTool` option --- .../internal/metals/MetalsLspService.scala | 113 +++++++++--------- .../internal/metals/UserConfiguration.scala | 24 ++-- .../scala/tests/PreferredBuildServer.scala | 2 +- .../scala/tests/scalacli/ScalaCliSuite.scala | 1 + 4 files changed, 66 insertions(+), 74 deletions(-) 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 7423d5e598b..8eee24ad33a 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala @@ -901,22 +901,11 @@ class MetalsLspService( buildTools.initialize() for { found <- supportedBuildTool() - chosenBuildServer = found match { - case Some(BuildTool.Found(buildServer, _)) - if buildServer.forcesBuildServer => - tables.buildServers.chooseServer(buildServer.buildServerName) - Some(buildServer.buildServerName) - case _ => tables.buildServers.selectedServer() - } _ <- Future .sequence( List( quickConnectToBuildServer(), - slowConnectToBuildServer( - forceImport = false, - found, - chosenBuildServer, - ), + slowConnectToBuildServer(forceImport = false, found), ) ) } yield () @@ -2078,38 +2067,29 @@ class MetalsLspService( forceImport: Boolean ): Future[BuildChange] = for { buildTool <- supportedBuildTool() - chosenBuildServer = tables.buildServers.selectedServer() buildChange <- slowConnectToBuildServer( forceImport, buildTool, - chosenBuildServer, ) } yield buildChange def slowConnectToBuildServer( forceImport: Boolean, buildTool: Option[BuildTool.Found], - chosenBuildServer: Option[String], ): Future[BuildChange] = { - val preferredList = - userConfig.preferredBuildServes.map(_.trim().toLowerCase()) - def isPreferredAboveBloop(buildServer: String) = { - val idx = preferredList.indexOf(buildServer) - if (idx >= 0) { - val bloopIdx = preferredList.indexOf("bloop") - bloopIdx < 0 || bloopIdx > idx - } else false - } - def isBloop = chosenBuildServer.exists( + val chosenBuildServer = tables.buildServers.selectedServer() + + def isBloopOrEmpty = chosenBuildServer.exists( _ == BloopServers.name - ) + ) || chosenBuildServer.isEmpty + + def useBuildToolBsp(buildTool: BuildServerProvider) = + buildTool match { + case _: BloopInstallProvider => userConfig.defaultBSPToBuildTool + case _ => true + } buildTool match { - case Some(BuildTool.Found(buildTool: BloopInstallProvider, digest)) - if isBloop || (chosenBuildServer.isEmpty && !isPreferredAboveBloop( - buildTool.buildServerName - )) => - slowConnectToBloopServer(forceImport, buildTool, digest) // If there is no .bazelbsp present, we ask user to write bsp config // After that, we should fall into the last case and index workspace case Some(BuildTool.Found(_: BazelBuildTool, _)) @@ -2125,39 +2105,22 @@ class MetalsLspService( ) .flatMap(_ => quickConnectToBuildServer()) case Some(BuildTool.Found(buildTool: BuildServerProvider, _)) - if !buildTool.isBspGenerated(folder) => - val notification = tables.dismissedNotifications.ImportChanges - if (userConfig.shouldAutoImportNewProject || buildTool.forcesBuildServer) { - generateBspAndConnect(buildTool) - } else if (notification.isDismissed) { - Future.successful(BuildChange.None) - } else { - scribe.debug("Awaiting user response...") - languageClient - .showMessageRequest( - Messages.GenerateBspAndConnect - .params(buildTool.executableName, buildTool.buildServerName) - ) - .asScala - .flatMap { item => - if (item == Messages.dontShowAgain) { - notification.dismissForever() - Future.successful(BuildChange.None) - } else if (item == Messages.GenerateBspAndConnect.yes) { - generateBspAndConnect(buildTool) - } else Future.successful(BuildChange.None) - } - } + if chosenBuildServer.isEmpty && useBuildToolBsp(buildTool) => + slowConnectToBuildToolBsp(buildTool) + case Some(BuildTool.Found(buildTool: BloopInstallProvider, digest)) + if isBloopOrEmpty => + slowConnectToBloopServer(forceImport, buildTool, digest) case Some(BuildTool.Found(buildTool, _)) if !chosenBuildServer.exists( _ == buildTool.buildServerName ) && buildTool.forcesBuildServer => tables.buildServers.chooseServer(buildTool.buildServerName) quickConnectToBuildServer() - case Some(BuildTool.Found(buildTool: BloopInstallProvider, _)) - if chosenBuildServer.isEmpty => - tables.buildServers.chooseServer(buildTool.buildServerName) - quickConnectToBuildServer() + // can happen if the user manually deletes `.bsp` + case Some(BuildTool.Found(buildTool: BuildServerProvider, _)) + if chosenBuildServer.contains(buildTool.buildServerName) && !buildTool + .isBspGenerated(folder) => + generateBspAndConnect(buildTool) case Some(found) => indexer.reloadWorkspaceAndIndex( forceImport, @@ -2171,7 +2134,39 @@ class MetalsLspService( } } - def generateBspAndConnect( + private def slowConnectToBuildToolBsp( + buildTool: BuildServerProvider + ) = { + val notification = tables.dismissedNotifications.ImportChanges + if (buildTool.isBspGenerated(folder)) { + tables.buildServers.chooseServer(buildTool.buildServerName) + quickConnectToBuildServer() + } else if ( + userConfig.shouldAutoImportNewProject || buildTool.forcesBuildServer + ) { + generateBspAndConnect(buildTool) + } else if (notification.isDismissed) { + Future.successful(BuildChange.None) + } else { + scribe.debug("Awaiting user response...") + languageClient + .showMessageRequest( + Messages.GenerateBspAndConnect + .params(buildTool.executableName, buildTool.buildServerName) + ) + .asScala + .flatMap { item => + if (item == Messages.dontShowAgain) { + notification.dismissForever() + Future.successful(BuildChange.None) + } else if (item == Messages.GenerateBspAndConnect.yes) { + generateBspAndConnect(buildTool) + } else Future.successful(BuildChange.None) + } + } + } + + private def generateBspAndConnect( buildTool: BuildServerProvider ): Future[BuildChange] = { tables.buildServers.chooseServer(buildTool.buildServerName) diff --git a/metals/src/main/scala/scala/meta/internal/metals/UserConfiguration.scala b/metals/src/main/scala/scala/meta/internal/metals/UserConfiguration.scala index b4686eecbc9..e24da5bcd73 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/UserConfiguration.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/UserConfiguration.scala @@ -56,7 +56,7 @@ case class UserConfiguration( verboseCompilation: Boolean = false, automaticImportBuild: AutoImportBuildKind = AutoImportBuildKind.Off, scalaCliLauncher: Option[String] = None, - preferredBuildServes: List[String] = Nil, + defaultBSPToBuildTool: Boolean = false, ) { def shouldAutoImportNewProject: Boolean = @@ -354,16 +354,12 @@ object UserConfiguration { |build imports after subsequent changes as well.""".stripMargin, ), UserConfigurationOption( - "preferred-build-servers", - "[]", - """["sbt"]""", - "List of preferred build servers.", - """|If multiple build servers available, this list provides an order, - |that overrides the default partial order, in which the build server should be chosen. - |The default partial order is: - |1. `bloop` - |2. other build servers (asks user) - |3. fallback `scala-cli` (if no build server found) + "default-bsp-to-build-tool", + "false", + "true", + "If used build server should default to build tool.", + """|If used build server should default to the one provided by the build tool + |instead of the default Bloop. |""".stripMargin, ), ) @@ -589,8 +585,8 @@ object UserConfiguration { val scalaCliLauncher = getStringKey("scala-cli-launcher") - val preferredBuildServes = - getStringListKey("preferred-build-servers").getOrElse(Nil) + val defaultBSPToBuildTool = + getBooleanKey("default-bsp-to-build-tool").getOrElse(false) if (errors.isEmpty) { Right( @@ -625,7 +621,7 @@ object UserConfiguration { verboseCompilation, autoImportBuilds, scalaCliLauncher, - preferredBuildServes, + defaultBSPToBuildTool, ) ) } else { diff --git a/tests/slow/src/test/scala/tests/PreferredBuildServer.scala b/tests/slow/src/test/scala/tests/PreferredBuildServer.scala index d96e4b25ef2..5c9ce4ff6d7 100644 --- a/tests/slow/src/test/scala/tests/PreferredBuildServer.scala +++ b/tests/slow/src/test/scala/tests/PreferredBuildServer.scala @@ -6,7 +6,7 @@ import scala.meta.internal.metals.{BuildInfo => V} class PreferredBuildServer extends BaseLspSuite("preferred-build-server") { override def userConfig: UserConfiguration = - super.userConfig.copy(preferredBuildServes = List("sbt")) + super.userConfig.copy(defaultBSPToBuildTool = true) test("start-sbt-when-preferred-no-bsp") { cleanWorkspace() diff --git a/tests/slow/src/test/scala/tests/scalacli/ScalaCliSuite.scala b/tests/slow/src/test/scala/tests/scalacli/ScalaCliSuite.scala index ea776826057..3d44ae7d828 100644 --- a/tests/slow/src/test/scala/tests/scalacli/ScalaCliSuite.scala +++ b/tests/slow/src/test/scala/tests/scalacli/ScalaCliSuite.scala @@ -312,6 +312,7 @@ class ScalaCliSuite extends BaseScalaCliSuite(V.scala3) { } test("inner") { + cleanWorkspace() for { _ <- scalaCliInitialize(useBsp = false)( s"""|/inner/project.scala From aa74d1e6b7f20ebae99f527bf68c63ed6f9791e9 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Tue, 20 Feb 2024 17:13:06 +0100 Subject: [PATCH 5/7] choose server before quickConnect --- .../internal/metals/MetalsLspService.scala | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) 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 8eee24ad33a..88ad5377aa1 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala @@ -901,11 +901,22 @@ class MetalsLspService( buildTools.initialize() for { found <- supportedBuildTool() + chosenBuildServer = found match { + case Some(BuildTool.Found(buildServer, _)) + if buildServer.forcesBuildServer => + tables.buildServers.chooseServer(buildServer.buildServerName) + Some(buildServer.buildServerName) + case _ => tables.buildServers.selectedServer() + } _ <- Future .sequence( List( quickConnectToBuildServer(), - slowConnectToBuildServer(forceImport = false, found), + slowConnectToBuildServer( + forceImport = false, + found, + chosenBuildServer, + ), ) ) } yield () @@ -2067,18 +2078,19 @@ class MetalsLspService( forceImport: Boolean ): Future[BuildChange] = for { buildTool <- supportedBuildTool() + chosenBuildServer = tables.buildServers.selectedServer() buildChange <- slowConnectToBuildServer( forceImport, buildTool, + chosenBuildServer, ) } yield buildChange def slowConnectToBuildServer( forceImport: Boolean, buildTool: Option[BuildTool.Found], + chosenBuildServer: Option[String], ): Future[BuildChange] = { - val chosenBuildServer = tables.buildServers.selectedServer() - def isBloopOrEmpty = chosenBuildServer.exists( _ == BloopServers.name ) || chosenBuildServer.isEmpty @@ -2116,7 +2128,6 @@ class MetalsLspService( ) && buildTool.forcesBuildServer => tables.buildServers.chooseServer(buildTool.buildServerName) quickConnectToBuildServer() - // can happen if the user manually deletes `.bsp` case Some(BuildTool.Found(buildTool: BuildServerProvider, _)) if chosenBuildServer.contains(buildTool.buildServerName) && !buildTool .isBspGenerated(folder) => From fbf7933d1c2160a2166f55ea32b52a9fd73133da Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Thu, 22 Feb 2024 11:03:05 +0100 Subject: [PATCH 6/7] fix grammar issues --- .../main/scala/scala/meta/internal/metals/Messages.scala | 2 +- .../scala/meta/internal/metals/UserConfiguration.scala | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/metals/src/main/scala/scala/meta/internal/metals/Messages.scala b/metals/src/main/scala/scala/meta/internal/metals/Messages.scala index 074641ccd64..4514dffffa0 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Messages.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Messages.scala @@ -200,7 +200,7 @@ object Messages { ): ShowMessageRequestParams = { val params = new ShowMessageRequestParams() params.setMessage( - s"New $buildToolName workspace detected, would you like connect to $buildServerName build server?" + s"New $buildToolName workspace detected, would you like connect to the $buildServerName build server?" ) params.setType(MessageType.Info) params.setActions( diff --git a/metals/src/main/scala/scala/meta/internal/metals/UserConfiguration.scala b/metals/src/main/scala/scala/meta/internal/metals/UserConfiguration.scala index e24da5bcd73..709e4616de1 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/UserConfiguration.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/UserConfiguration.scala @@ -357,9 +357,9 @@ object UserConfiguration { "default-bsp-to-build-tool", "false", "true", - "If used build server should default to build tool.", - """|If used build server should default to the one provided by the build tool - |instead of the default Bloop. + "Default to using build tool as your build server.", + """|If your build tool can also serve as a build server, + |default to using it instead of Bloop. |""".stripMargin, ), ) From 26deddfda39d303d1a65c973d14b9463e4f7a553 Mon Sep 17 00:00:00 2001 From: Katarzyna Marek Date: Thu, 22 Feb 2024 12:43:13 +0100 Subject: [PATCH 7/7] rename `defaultBSPToBuildTool` to `defaultBspToBuildTool` --- .../scala/scala/meta/internal/metals/MetalsLspService.scala | 2 +- .../scala/meta/internal/metals/UserConfiguration.scala | 6 +++--- tests/slow/src/test/scala/tests/PreferredBuildServer.scala | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) 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 88ad5377aa1..35e9cd6c360 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala @@ -2097,7 +2097,7 @@ class MetalsLspService( def useBuildToolBsp(buildTool: BuildServerProvider) = buildTool match { - case _: BloopInstallProvider => userConfig.defaultBSPToBuildTool + case _: BloopInstallProvider => userConfig.defaultBspToBuildTool case _ => true } diff --git a/metals/src/main/scala/scala/meta/internal/metals/UserConfiguration.scala b/metals/src/main/scala/scala/meta/internal/metals/UserConfiguration.scala index 709e4616de1..62ba6667c71 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/UserConfiguration.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/UserConfiguration.scala @@ -56,7 +56,7 @@ case class UserConfiguration( verboseCompilation: Boolean = false, automaticImportBuild: AutoImportBuildKind = AutoImportBuildKind.Off, scalaCliLauncher: Option[String] = None, - defaultBSPToBuildTool: Boolean = false, + defaultBspToBuildTool: Boolean = false, ) { def shouldAutoImportNewProject: Boolean = @@ -585,7 +585,7 @@ object UserConfiguration { val scalaCliLauncher = getStringKey("scala-cli-launcher") - val defaultBSPToBuildTool = + val defaultBspToBuildTool = getBooleanKey("default-bsp-to-build-tool").getOrElse(false) if (errors.isEmpty) { @@ -621,7 +621,7 @@ object UserConfiguration { verboseCompilation, autoImportBuilds, scalaCliLauncher, - defaultBSPToBuildTool, + defaultBspToBuildTool, ) ) } else { diff --git a/tests/slow/src/test/scala/tests/PreferredBuildServer.scala b/tests/slow/src/test/scala/tests/PreferredBuildServer.scala index 5c9ce4ff6d7..f95ca2a10e7 100644 --- a/tests/slow/src/test/scala/tests/PreferredBuildServer.scala +++ b/tests/slow/src/test/scala/tests/PreferredBuildServer.scala @@ -6,7 +6,7 @@ import scala.meta.internal.metals.{BuildInfo => V} class PreferredBuildServer extends BaseLspSuite("preferred-build-server") { override def userConfig: UserConfiguration = - super.userConfig.copy(defaultBSPToBuildTool = true) + super.userConfig.copy(defaultBspToBuildTool = true) test("start-sbt-when-preferred-no-bsp") { cleanWorkspace()