Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
reviews:
auto_review:
base_branches:
- main
- main-v2
16 changes: 8 additions & 8 deletions docs/contributors/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ To avoid repetition, common utilities of presentation compilers are in `mtags-sh
- `tests/input` - Example Scala code that is used as testing data for unit tests.

### Other modules
- `sbt-metals` - the sbt plugin used when users are using the BSP support from
sbt to ensure semanticDB is being produced by sbt.
- `sbt-metals` - an sbt plugin for integration with Metals through the Build Server Protocol.
It ensures that SemanticDB files are produced by sbt.
- `docs` - documentation markdown for the Metals website.
- `metals-docs` - methods used for generating documentation across multiple pages
in `docs`.
Expand Down Expand Up @@ -235,7 +235,7 @@ possible to investigate why test is failing manually.

## Cross tests

Tests for Scala 2 presenatation compiler, check common features such as hover, completions or signatures.
Tests for Scala 2 presentation compiler that check common features such as hover, completions, and signatures.

```sh
sbt
Expand Down Expand Up @@ -398,9 +398,9 @@ waiting for the debugger to connect:
## Updating build tool launcher/wrappers

Metals uses various wrappers or launchers for each build tool that it supports.
This makes sure that when your in a workspace for you build tool that metals is
able to correctly launch that build tool, even if it doesn't exist on the users
`$PATH`. You can see their usages in `<BuildToolName>BuildTool.scala`.
This ensures that Metals is able to launch the necessary build tool in any workspace,
even if the tool isn't present on the users `$PATH`.
You can see their usages in `<BuildToolName>BuildTool.scala`.

### Updating sbt-launcher

Expand All @@ -411,8 +411,8 @@ command:
cp "$(cs fetch org.scala-sbt:sbt-launch:<version>)" sbt-launch.jar
```

This will allow you to not have to do some of the manual steps with the launcher
properties file listed [here](https://github.com/sbt/launcher).
This will allow you to skip some of the manual steps with the launcher
properties file that are listed [here](https://github.com/sbt/launcher).

### Updating maven wrappers

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ class StandaloneMcpService(
initialUserConfig = initialUserConfig.getOrElse(UserConfiguration.default)
)

private val jarFileSystemCache: JarFileSystemCache = new JarFileSystemCache

private val uriMapper: WorkspaceURIMapper =
new WorkspaceURIMapper(() => Seq(projectMetalsLspService.folderUriMapper))

lazy val projectMetalsLspService = new ProjectMetalsLspService(
ec,
scheduledExecutor,
Expand All @@ -121,6 +126,8 @@ class StandaloneMcpService(
workDoneProgress,
maxScalaCliServers = 3,
moduleStatus,
jarFileSystemCache,
uriMapper,
)

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ object ClientCommands {
|""".stripMargin,
)

val LibraryFileSystemReady = new Command(
"metals-library-filesystem-ready",
"Library File System Ready",
"Notifies client that library file system is ready",
)

val RunDoctor = new ParametrizedCommand[String](
"metals-doctor-run",
"Run doctor",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ final class ClientConfiguration(
def isVirtualDocumentSupported(): Boolean =
initializationOptions.isVirtualDocumentSupported.getOrElse(false)

def isLibraryFileSystemSupported(): Boolean =
initializationOptions.isLibraryFileSystemSupported.getOrElse(false)

def icons(): Icons =
initializationOptions.icons
.map(Icons.fromString)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class FallbackMetalsLspService(
override val workDoneProgress: WorkDoneProgress,
bspStatus: BspStatus,
moduleStatus: ModuleStatus,
jarFileSystemCache: JarFileSystemCache,
uriMapper: WorkspaceURIMapper,
) extends MetalsLspService(
ec,
sh,
Expand All @@ -58,6 +60,8 @@ class FallbackMetalsLspService(
workDoneProgress,
maxScalaCliServers = 10,
moduleStatus,
jarFileSystemCache,
uriMapper,
) {

val buildServerPromise: Promise[Unit] = Promise.successful(())
Expand All @@ -68,6 +72,7 @@ class FallbackMetalsLspService(
folder,
compilers,
buildTargets,
uriMapper,
() => userConfig,
shellRunner,
optFileSystemSemanticdbs,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ final class FileDecoderProvider(
workspace: AbsolutePath,
compilers: Compilers,
buildTargets: BuildTargets,
uriMapper: URIMapper,
userConfig: () => UserConfiguration,
shellRunner: ShellRunner,
optFileSystemSemanticdbs: () => Option[FileSystemSemanticdbs],
Expand Down Expand Up @@ -169,6 +170,12 @@ final class FileDecoderProvider(
case "file" => decodeMetalsFile(uri)
case "metalsDecode" =>
decodedFileContents(uri.getSchemeSpecificPart())
case "metalsfs" =>
Try(uriMapper.convertToLocal(uriAsStr)) match {
case Success(local) => decodedFileContents(local)
case Failure(e) =>
Future.successful(DecoderResponse.failed(uri, e))
}
case _ =>
Future.successful(
DecoderResponse.failed(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ trait IndexProviders {
def referencesProvider: ReferenceProvider
def workspaceSymbols: WorkspaceSymbolProvider
def buildTargets: BuildTargets
def uriMapper: WorkspaceURIMapper
def folderUriMapper: FolderURIMapper
def lspFileSystemProvider: LSPFileSystemProvider
def semanticDBIndexer: SemanticdbIndexer
def fileWatcher: FileWatcher
def focusedDocument: Option[AbsolutePath]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,10 @@ case class Indexer(indexProviders: IndexProviders)(implicit rc: ReportContext) {
buildTool.importedBuild.dependencySources,
)
}
if (clientConfig.isLibraryFileSystemSupported()) {
folderUriMapper.rebuildIndexes()
lspFileSystemProvider.sendLibraryFileSystemReady()
}
// Schedule removal of unused toplevel symbols from cache
if (usedJars.nonEmpty)
sh.schedule(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ final case class InitializationOptions(
isHttpEnabled: Option[Boolean],
commandInHtmlFormat: Option[CommandHTMLFormat],
isVirtualDocumentSupported: Option[Boolean],
isLibraryFileSystemSupported: Option[Boolean],
openFilesOnRenameProvider: Option[Boolean],
quickPickProvider: Option[Boolean],
readClipboardProvider: Option[Boolean],
Expand Down Expand Up @@ -128,6 +129,7 @@ object InitializationOptions {
None,
None,
None,
None,
)

def from(
Expand Down Expand Up @@ -171,6 +173,8 @@ object InitializationOptions {
.flatMap(CommandHTMLFormat.fromString),
isVirtualDocumentSupported =
jsonObj.getBooleanOption("isVirtualDocumentSupported"),
isLibraryFileSystemSupported =
jsonObj.getBooleanOption("isLibraryFileSystemSupported"),
openFilesOnRenameProvider =
jsonObj.getBooleanOption("openFilesOnRenameProvider"),
quickPickProvider = jsonObj.getBooleanOption("quickPickProvider"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package scala.meta.internal.metals

import java.net.URI
import java.nio.file.FileSystem
import java.nio.file.FileSystemAlreadyExistsException
import java.nio.file.FileSystemNotFoundException
import java.nio.file.FileSystems

import scala.collection.mutable
import scala.util.control.NonFatal

import scala.meta.io.AbsolutePath

final case class FileSystemInfo(fs: FileSystem, fileUri: String)

final class JarFileSystemCache {

private val openFileSystems = mutable.Map.empty[URI, FileSystem]

/**
* Synchronized to avoid a race where two threads both call newFileSystem
* for the same archive.
*/
def open(localPath: AbsolutePath): FileSystemInfo = synchronized {
val fileUri = localPath.toNIO.toUri.toString.stripSuffix("/")
val zipURI = JarFileSystemCache.jarUriFor(localPath)
val fs = openFileSystems.getOrElseUpdate(zipURI, openFileSystem(zipURI))
FileSystemInfo(fs, fileUri)
}

private def openFileSystem(zipURI: URI): FileSystem =
try FileSystems.getFileSystem(zipURI)
catch {
case _: FileSystemNotFoundException =>
try
FileSystems
.newFileSystem(zipURI, new java.util.HashMap[String, Any])
catch {
case _: FileSystemAlreadyExistsException =>
FileSystems.getFileSystem(zipURI)
}
}

def closeObsolete(knownPaths: Set[AbsolutePath]): Unit = synchronized {
val knownUris = knownPaths.map(JarFileSystemCache.jarUriFor)
val obsoleteUris = openFileSystems.keysIterator.filterNot(knownUris).toList
obsoleteUris.foreach(close)
}

def close(zipURI: URI): Unit = synchronized {
openFileSystems.remove(zipURI).foreach { fs =>
try fs.close()
catch {
case NonFatal(e) =>
scribe.warn(s"Failed to close jar file system $zipURI", e)
}
}
}
}

object JarFileSystemCache {
def jarUriFor(localPath: AbsolutePath): URI = {
val fileUri = localPath.toNIO.toUri.toString.stripSuffix("/")
URI.create(s"jar:$fileUri")
}
}
Loading