Skip to content

Latest commit

 

History

History
120 lines (72 loc) · 9.63 KB

architecture.md

File metadata and controls

120 lines (72 loc) · 9.63 KB

Architecture of Metals

This document describes the high-level architecture of Metals following the philosophy of rust-analyzer's ARCHITECTURE.md

Other resources

Language Server

MetalsLanguageServer.scala is an entrypoint to Metals. This class is responsible for handling LSP lifecycle messages. Upon receiving an initialize request, an instance of WorkspaceLspService.scala containing a dedicated instance of MetalsLspService.scala for each workspace folder is created. Together they are responsible for handling other LSP requests.

LSP endpoints

In WorkspaceLspService.scala we implement all of the LSP endpoints. All endpoints we use are defined in the scala.meta.metals.lsp package in 3 files:

  • WorkspaceService.scala
  • TextDocumentService.scala
  • MetalsService.scala

For example, you will find the endpoint for textDocument/completion request in TextDocumentService.

@JsonRequest("textDocument/completion")
def completion(...) = ...

WorkspaceLspService keeps track of the currently opened workspace folders each treated as a separate Scala project. We keep an instance of MetalsLspService per workspace folder or a single instance for a single root project. The main purpose of WorkspaceLspService is redirecting requests/notifications to the correct instance of MetalsLspService.

MetalsLspService is the most important class of the project, it creates and manages many components, for instance:

  • private val compilers: Compilers = ...
  • private val codeLensProvider: CodeLensProvider = ...
  • private val diagnostics: Diagnostics = ...

Hacking in Metals usually starts with recognizing in which component one has to make a change to get something working.

Presentation compiler

Metals features are powered by presentation compilers, if you hit Compilers.scala it is the client of compilers.

mtags module is the Scala version specific module used to interact with the Scala presentation compiler using Java defined interfaces. You can find the interfaces under mtags-interface project.

For example, ScalaPresentationCompiler.java in mtags-interface is the interface for ScalaPresentationCompiler.scala under scala-2 and scala-3 directories of mtags module.

For more details

SemanticDB

Metals uses semanticdb for many features (Providers that receive the instance of Semanticdbs) such as references and renames. Semanticdb offers us information from the compiler that are written to disk and easily consumable. Metals consumes SemanticDBs in two ways:

  • FileSystemsSemanticdbs consumes SemanticDBs on disks
  • InteractiveSemanticdbs will generate SemanticDB on the fly using the presentation compiler via mtags, in case FileSystemsSemanticdbs failed or when the build tool doesn't compiler specific files such as dependency sources or sbt files.

In addition, classes extend SemanticdbFeatureProvider index the semanticdb symbols, that will be updated by SemanticdbIndexer.

Build Server

Metals communicates with build server such as bloop and sbt using Build Server Protocol.

BspConnector.scala manages the connections between Metals and build server, and BuildServerConnection.scala represents the API wrapper for the build server.

For more details about sbt's BSP support in Metals, see the blog post.

Worksheet

Worksheet support is provided by mdoc, which is able to typecheck and evaluate each line of the input. The main class responsible for worksheets is WorksheetProvider.scala. It is responsible for downloading mdoc instance for each Scala version that is supported and running the evaluation in the file input.

Later the evaluations are published using decoration extension or via additional Text Edits for editors that do not support decorations. This is done in the two classes implementing WorksheetPublisher.scala:

Formatting

FormattingProvider.scala takes care of how Metals handles textDocument/formatting. It uses scalafmt as a code formatter downloading dynamically using scalafmt-dynamic. We don't embed a specific version of scalafmt into Metals so that users can switch scalafmt's version using .scalafmt.conf.

Note that FormattingProvider doesn't handle textDocument/rangeFormatting.

Scalafix

Scalafix support is implemented in the ScalafixProvider.scala. The class uses scalafix API scalafix.interfaces.Scalafix to run the rules and get text edits, which are later changed to LSP TextEdits and applied to the file using WorkspaceEdit.

Metals downloads separate version of Scalafix for each binary version of Scala. It can also download additional dependencies for each of the used rules.

Debbugging

Debugging is handled by Debug Adapter Protocol, which is a complementary protocol to LSP.

The main code for debugging resides in scala.meta.internal.metals.debug package with DebugProvider.scala being the main entrypoint.

DebugProvider sets up the communication between the debug server process started by the build server and the client. This communication is handled in DebugProxy.scala which translates some of the messages in order to enrich them with the information from Metals itself.

You can find more information about DAP here

MtagsIndexer

MtagsIndexers are primarily designed for symbol indexing, generating approximate SemanticDB TextDocument instances based on syntax information. They are particularly useful when we are interested in symbol names and their locations, without requiring further information such as their types or document synthetics.

We use Mtags-generated SemanticDB instead of compiler-generated SemanticDB because there are times when we want to rely on a symbol index even if the compiler cannot generate SemanticDB: for example, with 3rd-party dependencies, non-compilable code, or not yet compiled code.

The endpoints is scala.meta.internal.mtags.Mtags, which dispatches to several MtagsIndexer implementations:

  • ScalaMtags parses the provided Scala file using Scalameta's parser.
    • To see which symbols ScalaMtags extracts, refer to the unit tests (MtagsSuite.scala and tests/unit/src/test/resources/mtags). p ScalaToplevelMtags tokenizes the given Scala file using Scalameta, and parses it on the Metals side with custom parser.
    • To enable fast indexing with a low memory footprint, nested symbols (such as functions and members defined in top-level classes, traits, and objects) are skipped since nested symbols are not of interest when performing symbol search.
    • See Fast goto definition with low memory footprint | Metals for more details.
    • The unit test (ScalaToplevelSuite.scala) is a good resource to see which symbols it extracts.
  • JavaMtags parses the given Java file using qdox.
  • JavaToplevelMtags tokenize and parses Java code in the same way as ScalaToplevelMtags. We use our own custom tokenizer and parser instead of qdox for fast indexing.