diff --git a/metals/src/main/scala/scala/meta/internal/metals/BspMetalsLspService.scala b/metals/src/main/scala/scala/meta/internal/metals/BspMetalsLspService.scala index e5ba41fc5f7..3fb888dec59 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/BspMetalsLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/BspMetalsLspService.scala @@ -83,6 +83,7 @@ class BspMetalsLspService( folder, folderVisibleName, headDoctor, + maxScalaCliServers = 3, ) { import serverInputs._ diff --git a/metals/src/main/scala/scala/meta/internal/metals/FallbackMetalsLspService.scala b/metals/src/main/scala/scala/meta/internal/metals/FallbackMetalsLspService.scala index 6d7576b567e..646c6031e5d 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/FallbackMetalsLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/FallbackMetalsLspService.scala @@ -52,6 +52,7 @@ class FallbackMetalsLspService( folder, folderVisibleName, headDoctor, + maxScalaCliServers = 10, ) { override protected def doctor: Doctor = 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 d9a8059df65..6735eb050ce 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala @@ -111,6 +111,7 @@ abstract class MetalsLspService( folder: AbsolutePath, folderVisibleName: Option[String], headDoctor: HeadDoctor, + maxScalaCliServers: Int, ) extends Folder(folder, folderVisibleName, isKnownMetalsProject = true) with Cancelable with TextDocumentService { @@ -866,6 +867,7 @@ abstract class MetalsLspService( .foreach(focusedDocumentBuildTarget.set) // unpublish diagnostic for dependencies interactiveSemanticdbs.didFocus(path) + scalaCli.didFocus(path) // Don't trigger compilation on didFocus events under cascade compilation // because save events already trigger compile in inverse dependencies. if (path.isDependencySource(folder)) { @@ -1601,6 +1603,7 @@ abstract class MetalsLspService( () => userConfig, parseTreesAndPublishDiags, buildTargets, + maxScalaCliServers, ) ) diff --git a/metals/src/main/scala/scala/meta/internal/metals/scalacli/ScalaCliServers.scala b/metals/src/main/scala/scala/meta/internal/metals/scalacli/ScalaCliServers.scala index 15cca422681..29ff09c6dbb 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/scalacli/ScalaCliServers.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/scalacli/ScalaCliServers.scala @@ -3,6 +3,7 @@ package scala.meta.internal.metals.scalacli import java.io.File import java.util.concurrent.atomic.AtomicReference +import scala.collection.immutable.Queue import scala.concurrent.ExecutionContextExecutorService import scala.concurrent.Future @@ -39,16 +40,17 @@ class ScalaCliServers( userConfig: () => UserConfiguration, parseTreesAndPublishDiags: Seq[AbsolutePath] => Future[Unit], buildTargets: BuildTargets, + maxServers: Int, )(implicit ec: ExecutionContextExecutorService) extends Cancelable { - private val serversRef: AtomicReference[Set[ScalaCli]] = new AtomicReference( - Set.empty - ) + + private val serversRef: AtomicReference[Queue[ScalaCli]] = + new AtomicReference(Queue.empty) private lazy val localScalaCli: Option[Seq[String]] = ScalaCli.localScalaCli(userConfig()) - def servers: List[ScalaCli] = serversRef.get().toList + def servers: Iterable[ScalaCli] = serversRef.get() def setupIDE(path: AbsolutePath): Future[Unit] = { localScalaCli @@ -96,17 +98,18 @@ class ScalaCliServers( .map(server => (server.lastImportedBuild, server.buildTargetsData)) .toList - def buildServers: List[BuildServerConnection] = servers.flatMap(_.buildServer) + def buildServers: Iterable[BuildServerConnection] = + servers.flatMap(_.buildServer) def cancel(): Unit = { - val servers = serversRef.getAndSet(Set.empty) + val servers = serversRef.getAndSet(Queue.empty) servers.foreach(_.cancel()) } def loaded(path: AbsolutePath): Boolean = servers.exists(_.path.toNIO.startsWith(path.toNIO)) - def paths: List[AbsolutePath] = servers.map(_.path) + def paths: Iterable[AbsolutePath] = servers.map(_.path) def start(path: AbsolutePath): Future[Unit] = { val scalaCli = @@ -128,21 +131,32 @@ class ScalaCliServers( val prevServers = serversRef.getAndUpdate { servers => if (servers.exists(_.path == path)) servers - else servers + scalaCli + else { + if (servers.size == maxServers) servers.drop(1) :+ scalaCli + else servers :+ scalaCli + } } - prevServers - .find(_.path == path) - .getOrElse { - buildTargets.addData(scalaCli.buildTargetsData) - scalaCli - } - .start() + val (newServer, outServer) = + prevServers + .find(_.path == path) + .map((_, None)) + .getOrElse { + buildTargets.addData(scalaCli.buildTargetsData) + val outServer = + Option.when(servers.size == 10)(prevServers.dequeue._1) + (scalaCli, outServer) + } + + for { + _ <- outServer.map(_.stop()).getOrElse(Future.unit) + _ <- newServer.start() + } yield () } def stop(): Future[Unit] = { - val servers = serversRef.getAndSet(Set.empty) - Future.sequence(servers.map(_.stop())).ignoreValue + val servers = serversRef.getAndSet(Queue.empty) + Future.sequence(servers.map(_.stop()).toSeq).ignoreValue } def stop(path: AbsolutePath): Future[Unit] = { @@ -156,4 +170,13 @@ class ScalaCliServers( .getOrElse(Future.successful(())) } + def didFocus(path: AbsolutePath): Queue[ScalaCli] = + serversRef.getAndUpdate { servers => + servers.find(server => path.startWith(server.path)) match { + case Some(foundServer) => + servers.filterNot(_ == foundServer) :+ foundServer + case None => servers + } + } + }