diff --git a/metals-bench/src/main/scala/bench/PcBenchmark.scala b/metals-bench/src/main/scala/bench/PcBenchmark.scala index 4e585bd47ae..88fc9e4ecb4 100644 --- a/metals-bench/src/main/scala/bench/PcBenchmark.scala +++ b/metals-bench/src/main/scala/bench/PcBenchmark.scala @@ -41,7 +41,7 @@ abstract class PcBenchmark { } def newPC(search: SymbolSearch = newSearch()): PresentationCompiler = { - new ScalaPresentationCompiler("root") + new ScalaPresentationCompiler() .withSearch(search) .newInstance("", classpath.asJava, Nil.asJava) } diff --git a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala index d82722b2c39..a624cf63026 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/Compilers.scala @@ -72,7 +72,6 @@ class Compilers( trees: Trees, mtagsResolver: MtagsResolver, sourceMapper: SourceMapper, - workspaceId: String, )(implicit ec: ExecutionContextExecutorService) extends Cancelable { val plugins = new CompilerPlugins() @@ -1016,7 +1015,7 @@ class Compilers( ): PresentationCompiler = { val pc: PresentationCompiler = mtags match { - case MtagsBinaries.BuildIn => new ScalaPresentationCompiler(workspaceId) + case MtagsBinaries.BuildIn => new ScalaPresentationCompiler() case artifacts: MtagsBinaries.Artifacts => embedded.presentationCompiler(artifacts, classpath) 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 666323561e3..9d2d7cf1245 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/MetalsLspService.scala @@ -248,7 +248,6 @@ class MetalsLspService( fileWatchFilter, params => { didChangeWatchedFiles(params) - initTreeView() }, ) ) @@ -262,11 +261,13 @@ class MetalsLspService( Cancelable.empty, ) ) - private def onBuildChanged = + + private val onBuildChanged = BatchedFunction.fromFuture[AbsolutePath, BuildChange]( onBuildChangedUnbatched ) - def pauseables: Pauseable = Pauseable.fromPausables( + + val pauseables: Pauseable = Pauseable.fromPausables( onBuildChanged :: parseTrees :: compilations.pauseables @@ -375,7 +376,6 @@ class MetalsLspService( time, report => { didCompileTarget(report) - initTreeView() compilers.didCompile(report) }, onBuildTargetDidCompile = { target => @@ -390,7 +390,6 @@ class MetalsLspService( }, onBuildTargetDidChangeFunc = params => { maybeQuickConnectToBuildServer(params) - initTreeView() }, ) @@ -626,7 +625,6 @@ class MetalsLspService( trees, mtagsResolver, sourceMapper, - folderId, ) ) @@ -738,7 +736,7 @@ class MetalsLspService( var httpServer: Option[MetalsHttpServer] = None - def treeView = + val treeView = new MetalsTreeFolderViewProvider( folderId, () => folder, @@ -782,15 +780,11 @@ class MetalsLspService( tables, doctor, () => { - val res = slowConnectToBuildServer(forceImport = true) - initTreeView() - res + slowConnectToBuildServer(forceImport = true) }, bspConnector, () => { - val res = quickConnectToBuildServer() - initTreeView() - res + quickConnectToBuildServer() }, ) private val findTextInJars: FindTextInDependencyJars = @@ -2091,6 +2085,7 @@ class MetalsLspService( case None => Future.successful(BuildChange.None) } + _ = initTreeView() } yield result) .recover { case NonFatal(e) => disconnectOldBuildServer() @@ -2276,9 +2271,7 @@ class MetalsLspService( ): Future[BuildChange] = { val isBuildChange = paths.exists(buildTools.isBuildRelated(folder, _)) if (isBuildChange) { - val res = slowConnectToBuildServer(forceImport = false) - initTreeView() - res + slowConnectToBuildServer(forceImport = false) } else { Future.successful(BuildChange.None) } 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 1cfce7cbb66..2b5b447766b 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/WorkspaceLspService.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/WorkspaceLspService.scala @@ -8,7 +8,6 @@ import scala.concurrent.ExecutionContextExecutorService import scala.concurrent.Future import scala.concurrent.Promise import scala.concurrent.duration.Duration -import scala.util.Success import scala.util.control.NonFatal import scala.meta.internal.builds.BuildTools @@ -120,6 +119,13 @@ class WorkspaceLspService( @volatile private var userConfig: UserConfiguration = initialUserConfig + val statusBar: StatusBar = new StatusBar( + languageClient, + time, + progressTicks, + clientConfig, + ) + private val shellRunner = register { new ShellRunner(languageClient, () => userConfig, time, statusBar) } @@ -127,40 +133,42 @@ class WorkspaceLspService( var focusedDocument: Option[AbsolutePath] = None private val recentlyFocusedFiles = new ActiveFiles(time) - val folderServices: List[MetalsLspService] = folders - .withFilter { case Folder(uri, _, _) => - !BuildTools.default(uri).isEmpty - } - .map { case Folder(uri, id, name) => - new MetalsLspService( - ec, - sh, - serverInputs, - languageClient, - initializeParams, - clientConfig, - () => userConfig, - statusBar, - () => focusedDocument, - shellRunner, - timerProvider, - initTreeView, - uri, - id, - name, - ) - } + private val timerProvider: TimerProvider = new TimerProvider(time) - assert(folderServices.nonEmpty) + val folderServices: List[MetalsLspService] = { + def createService(folder: Folder) = + folder match { + case Folder(uri, id, name) => + new MetalsLspService( + ec, + sh, + serverInputs, + languageClient, + initializeParams, + clientConfig, + () => userConfig, + statusBar, + () => focusedDocument, + shellRunner, + timerProvider, + initTreeView, + uri, + id, + name, + ) + } - val statusBar: StatusBar = new StatusBar( - languageClient, - time, - progressTicks, - clientConfig, - ) + val res = folders + .withFilter { case Folder(uri, _, _) => + !BuildTools.default(uri).isEmpty + } + .map(createService) - private val timerProvider: TimerProvider = new TimerProvider(time) + if (res.isEmpty) List(createService(folders.head)) + else res + } + + assert(folderServices.nonEmpty) val treeView: TreeViewProvider = if (clientConfig.isTreeViewProvider) { @@ -262,13 +270,8 @@ class WorkspaceLspService( def foreachSeq[A]( f: MetalsLspService => Future[A], ignoreValue: Boolean = false, - withF: Option[() => Unit] = None, ): CompletableFuture[Object] = { - val res0 = Future.sequence(folderServices.map(f)) - val res = withF match { - case Some(f) => res0.andThen { case Success(_) => f() } - case None => res0 - } + val res = Future.sequence(folderServices.map(f)) if (ignoreValue) res.ignoreValue.asJavaObject else res.asJavaObject } @@ -570,8 +573,6 @@ class WorkspaceLspService( } } - // def withTreeViewInit() {} - override def windowStateDidChange(params: WindowStateDidChangeParams): Unit = if (params.focused) { folderServices.foreach(_.unpause()) @@ -602,20 +603,18 @@ class WorkspaceLspService( foreachSeq(_.indexSources(), ignoreValue = true) case ServerCommands.RestartBuildServer() => folderServices.find(_.isBloop()).foreach(_.shutDownBloop()) - foreachSeq(_.autoConnectToBuildServer(), withF = Some(initTreeView)) + foreachSeq(_.autoConnectToBuildServer()) case ServerCommands.GenerateBspConfig() => foreachSeq( _.generateBspConfig(), ignoreValue = true, - withF = Some(initTreeView), ) case ServerCommands.ImportBuild() => foreachSeq( - _.slowConnectToBuildServer(forceImport = true), - withF = Some(initTreeView), + _.slowConnectToBuildServer(forceImport = true) ) case ServerCommands.ConnectBuildServer() => - foreachSeq(_.quickConnectToBuildServer(), withF = Some(initTreeView)) + foreachSeq(_.quickConnectToBuildServer()) case ServerCommands.DisconnectBuildServer() => foreachSeq(_.disconnectOldBuildServer(), ignoreValue = true) case ServerCommands.DecodeFile(uri) => @@ -650,7 +649,7 @@ class WorkspaceLspService( .asJava }.asJavaObject case ServerCommands.BspSwitch() => - foreachSeq(_.switchBspServer(), withF = Some(initTreeView)) + foreachSeq(_.switchBspServer()) case ServerCommands.OpenIssue() => Future .successful(Urls.openBrowser(githubNewIssueUrlCreator.buildUrl())) @@ -733,7 +732,7 @@ class WorkspaceLspService( folderServices.map(_.findMainClassAndItsBuildTarget(params)) ) .flatMap { list => - list.filter(_._2.isEmpty) match { + list.filter(_._2.nonEmpty) match { case (service, buildTargets) :: Nil => service.startMainClass(buildTargets, params) case list => @@ -773,7 +772,7 @@ class WorkspaceLspService( folderServices.map(_.findTestClassAndItsBuildTarget(params)) ) .flatMap { list => - list.filter(_._2.isEmpty) match { + list.filter(_._2.nonEmpty) match { case (service, buildTargets) :: Nil => service.startTestSuiteForResolved(buildTargets, params) case list => @@ -1038,7 +1037,6 @@ class WorkspaceLspService( Future // TODO:: we should probably have only one http server // and remove this ---v .sequence(folderServices.map(_.initialized(this))) - .andThen { case Success(_) => treeView.init() } .ignoreValue } } @@ -1115,6 +1113,7 @@ class WorkspaceLspService( } } else Future.unit } + } // TODO:: delete id, uri is enough case class Folder(uri: AbsolutePath, id: String, visibleName: Option[String]) diff --git a/metals/src/main/scala/scala/meta/internal/metals/logging/MetalsLogger.scala b/metals/src/main/scala/scala/meta/internal/metals/logging/MetalsLogger.scala index 55f89ada41c..50556a31d8b 100644 --- a/metals/src/main/scala/scala/meta/internal/metals/logging/MetalsLogger.scala +++ b/metals/src/main/scala/scala/meta/internal/metals/logging/MetalsLogger.scala @@ -1,5 +1,6 @@ package scala.meta.internal.metals.logging +import java.io.OutputStream import java.io.PrintStream import java.nio.file.Files import java.nio.file.StandardOpenOption @@ -45,29 +46,42 @@ object MetalsLogger { .replace() } - def redirectSystemOut(logfile: AbsolutePath): Unit = { - Files.createDirectories(logfile.toNIO.getParent) - val logStream = Files.newOutputStream( - logfile.toNIO, - StandardOpenOption.APPEND, - StandardOpenOption.CREATE, + def redirectSystemOut(logfile: AbsolutePath): Unit = redirectSystemOut( + List(logfile) + ) + + def redirectSystemOut(logfiles: List[AbsolutePath]): Unit = { + logfiles.foreach(logfile => + Files.createDirectories(logfile.toNIO.getParent) + ) + val logStreams = logfiles.map(logfile => + Files.newOutputStream( + logfile.toNIO, + StandardOpenOption.APPEND, + StandardOpenOption.CREATE, + ) ) - val out = new PrintStream(logStream) + val out = new PrintStream(new MutipleOutputsStream(logStreams)) System.setOut(out) System.setErr(out) - configureRootLogger(logfile) + configureRootLogger(logfiles) } - private def configureRootLogger(logfile: AbsolutePath): Unit = { - Logger.root - .clearModifiers() - .clearHandlers() - .withHandler( - writer = newFileWriter(logfile), - formatter = defaultFormat, - minimumLevel = Some(level), - modifiers = List(MetalsFilter()), - ) + private def configureRootLogger(logfile: List[AbsolutePath]): Unit = { + logfile + .foldLeft( + Logger.root + .clearModifiers() + .clearHandlers() + ) { (logger, logfile) => + logger + .withHandler( + writer = newFileWriter(logfile), + formatter = defaultFormat, + minimumLevel = Some(level), + modifiers = List(MetalsFilter()), + ) + } .withHandler( writer = LanguageClientLogger, formatter = MetalsLogger.defaultFormat, @@ -95,13 +109,13 @@ object MetalsLogger { } def setupLspLogger( - workspace: AbsolutePath, + folders: List[AbsolutePath], redirectSystemStreams: Boolean, ): Unit = { - val newLogFile = workspace.resolve(workspaceLogPath) - scribe.info(s"logging to file $newLogFile") + val newLogFiles = folders.map(_.resolve(workspaceLogPath)) + scribe.info(s"logging to files ${newLogFiles.mkString(",")}") if (redirectSystemStreams) { - redirectSystemOut(newLogFile) + redirectSystemOut(newLogFiles) } } @@ -119,3 +133,7 @@ object MetalsLogger { if (MetalsServerConfig.isTesting) silent else default } + +class MutipleOutputsStream(outputs: List[OutputStream]) extends OutputStream { + override def write(b: Int): Unit = outputs.foreach(_.write(b)) +} diff --git a/metals/src/main/scala/scala/meta/metals/MetalsLanguageServer.scala b/metals/src/main/scala/scala/meta/metals/MetalsLanguageServer.scala index f5c99dc0c1d..a8b679e59b4 100644 --- a/metals/src/main/scala/scala/meta/metals/MetalsLanguageServer.scala +++ b/metals/src/main/scala/scala/meta/metals/MetalsLanguageServer.scala @@ -155,7 +155,7 @@ class MetalsLanguageServer( val service = createService(List(Folder(root, "root", None)), params) setupJna() - MetalsLogger.setupLspLogger(root, redirectSystemOut) + MetalsLogger.setupLspLogger(List(root), redirectSystemOut) val clientInfo = Option(params.getClientInfo()).fold("") { info => s"for client ${info.getName()} ${Option(info.getVersion).getOrElse("")}" diff --git a/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala b/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala index 6abeb7bbdda..e890b66ac16 100644 --- a/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala +++ b/mtags/src/main/scala-2/scala/meta/internal/pc/ScalaPresentationCompiler.scala @@ -83,8 +83,8 @@ case class ScalaPresentationCompiler( ): PresentationCompiler = copy(config = config) - def this(workspaceId: String) = - this(buildTargetIdentifier = "", workspaceId = workspaceId) + def this() = + this(buildTargetIdentifier = "", workspaceId = "root") val compilerAccess = new ScalaCompilerAccess( diff --git a/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala b/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala index 0792f8c2316..dd6c4d4512a 100644 --- a/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala +++ b/mtags/src/main/scala-3/scala/meta/internal/pc/ScalaPresentationCompiler.scala @@ -53,7 +53,7 @@ case class ScalaPresentationCompiler( workspaceId: String, ) extends PresentationCompiler: - def this(workspaceId: String) = this("", Nil, Nil, workspaceId = workspaceId) + def this() = this("", Nil, Nil, workspaceId = "root") import InteractiveDriver.* diff --git a/tests/cross/src/main/scala/tests/BasePCSuite.scala b/tests/cross/src/main/scala/tests/BasePCSuite.scala index 79134d7c43d..672f4fb6b48 100644 --- a/tests/cross/src/main/scala/tests/BasePCSuite.scala +++ b/tests/cross/src/main/scala/tests/BasePCSuite.scala @@ -82,7 +82,7 @@ abstract class BasePCSuite extends BaseSuite { val scalacOpts = scalacOptions(myclasspath) - new ScalaPresentationCompiler("root") + new ScalaPresentationCompiler() .withSearch(search) .withConfiguration(config) .withExecutorService(executorService) diff --git a/tests/unit/src/main/scala/tests/TestingServer.scala b/tests/unit/src/main/scala/tests/TestingServer.scala index 6aeeda11011..b07cbd78fff 100644 --- a/tests/unit/src/main/scala/tests/TestingServer.scala +++ b/tests/unit/src/main/scala/tests/TestingServer.scala @@ -642,7 +642,7 @@ final case class TestingServer( scribe.info(s"Executing command [$command]") val args: java.util.List[Object] = params.map(_.toJson.asInstanceOf[Object]).asJava - + pprint.log(command) server.executeCommand(new ExecuteCommandParams(command, args)).asScala } @@ -699,6 +699,7 @@ final case class TestingServer( stoppageHandler: Stoppage.Handler = Stoppage.Handler.Continue, ): Future[TestDebugger] = { assertSystemExit(params) + pprint.log(params) executeCommandUnsafe(ServerCommands.StartDebugAdapter.id, Seq(params)) .collect { case DebugSession(_, uri) => TestDebugger(URI.create(uri), stoppageHandler) diff --git a/tests/unit/src/test/scala/tests/TreeViewLspSuite.scala b/tests/unit/src/test/scala/tests/TreeViewLspSuite.scala index 9d2fcab3c57..0cc090125db 100644 --- a/tests/unit/src/test/scala/tests/TreeViewLspSuite.scala +++ b/tests/unit/src/test/scala/tests/TreeViewLspSuite.scala @@ -96,29 +96,29 @@ class TreeViewLspSuite extends BaseLspSuite("tree-view") { |""".stripMargin, ) _ = server.assertTreeViewChildren( - s"projects:${server.buildTarget("a")}", + s"projects-root:${server.buildTarget("a")}", "", ) _ <- server.didOpen("a/src/main/scala/a/First.scala") _ <- server.didOpen("b/src/main/scala/b/Third.scala") _ = server.assertTreeViewChildren( - s"projects:${server.buildTarget("a")}", + s"projects-root:${server.buildTarget("a")}", """|_empty_/ - |a/ - |""".stripMargin, ) _ = server.assertTreeViewChildren( - s"projects:${server.buildTarget("a")}!/_empty_/", + s"projects-root:${server.buildTarget("a")}!/_empty_/", """|Zero class + |""".stripMargin, ) _ = server.assertTreeViewChildren( - s"projects:${server.buildTarget("a")}!/_empty_/Zero#", + s"projects-root:${server.buildTarget("a")}!/_empty_/Zero#", """|a val |""".stripMargin, ) _ = server.assertTreeViewChildren( - s"projects:${server.buildTarget("a")}!/a/", + s"projects-root:${server.buildTarget("a")}!/a/", """|First class - |First object |Second class - @@ -126,13 +126,13 @@ class TreeViewLspSuite extends BaseLspSuite("tree-view") { |""".stripMargin, ) _ = server.assertTreeViewChildren( - s"projects:${server.buildTarget("a")}!/a/First#", + s"projects-root:${server.buildTarget("a")}!/a/First#", """|a() method |b val |""".stripMargin, ) _ = server.assertTreeViewChildren( - s"projects:${server.buildTarget("a")}!/a/Second#", + s"projects-root:${server.buildTarget("a")}!/a/Second#", """|a() method |b val |c var @@ -159,21 +159,21 @@ class TreeViewLspSuite extends BaseLspSuite("tree-view") { ) _ = { server.assertTreeViewChildren( - s"libraries:${server.jar("sourcecode")}", + s"libraries-root:${server.jar("sourcecode")}", "sourcecode/ +", ) server.assertTreeViewChildren( - s"libraries:", + s"libraries-root:", expectedLibrariesString, ) server.assertTreeViewChildren( - s"libraries:${server.jar("scala-library")}!/scala/Some#", + s"libraries-root:${server.jar("scala-library")}!/scala/Some#", """|value val |get() method |""".stripMargin, ) server.assertTreeViewChildren( - s"libraries:${server.jar("lsp4j")}!/org/eclipse/lsp4j/FileChangeType#", + s"libraries-root:${server.jar("lsp4j")}!/org/eclipse/lsp4j/FileChangeType#", """|Created enum |Changed enum |Deleted enum @@ -184,12 +184,12 @@ class TreeViewLspSuite extends BaseLspSuite("tree-view") { |""".stripMargin, ) server.assertTreeViewChildren( - s"libraries:${server.jar("circe-core")}!/_root_/", + s"libraries-root:${server.jar("circe-core")}!/_root_/", """|io/ + |""".stripMargin, ) server.assertTreeViewChildren( - s"libraries:${server.jar("cats-core")}!/cats/instances/symbol/", + s"libraries-root:${server.jar("cats-core")}!/cats/instances/symbol/", """|package object |""".stripMargin, ) @@ -217,9 +217,9 @@ class TreeViewLspSuite extends BaseLspSuite("tree-view") { }, ), s"""|root - | Projects (0) - | Libraries (${expectedLibrariesCount}) - | Libraries (${expectedLibrariesCount}) + | Projects for root (0) + | Libraries for root (${expectedLibrariesCount}) + | Libraries for root (${expectedLibrariesCount}) | sourcecode_2.13-0.1.7.jar | sourcecode_2.13-0.1.7.jar | sourcecode/ @@ -270,9 +270,9 @@ class TreeViewLspSuite extends BaseLspSuite("tree-view") { }, ), s"""|root - | Projects (0) - | Libraries (${expectedLibrariesCount}) - | Libraries (${expectedLibrariesCount}) + | Projects for root (0) + | Libraries for root (${expectedLibrariesCount}) + | Libraries for root (${expectedLibrariesCount}) | org.eclipse.lsp4j-0.5.0.jar | org.eclipse.lsp4j.generator-0.5.0.jar | org.eclipse.lsp4j.jsonrpc-0.5.0.jar @@ -501,7 +501,7 @@ class TreeViewLspSuite extends BaseLspSuite("tree-view") { // Trigger a compilation of Second.scala _ <- server.didOpen("b/src/main/scala/b/Second.scala") _ = server.assertTreeViewChildren( - s"projects:${server.buildTarget("b")}", + s"projects-root:${server.buildTarget("b")}", "b/ +", ) @@ -517,7 +517,7 @@ class TreeViewLspSuite extends BaseLspSuite("tree-view") { // contains no `*.class` files yet. This is Bloop-specific behavior // and may be not an issue with other clients. _ = server.assertTreeViewChildren( - s"projects:${server.buildTarget("b")}", + s"projects-root:${server.buildTarget("b")}", "", ) @@ -529,7 +529,7 @@ class TreeViewLspSuite extends BaseLspSuite("tree-view") { // background compilation of project "b" has completed, even if it was a // no-op. _ = server.assertTreeViewChildren( - s"projects:${server.buildTarget("b")}", + s"projects-root:${server.buildTarget("b")}", "b/ +", )