A Dive into Configuring Metals
As of this last Metals release, it's now 100% possible to fully configure Metals without any need to pass in server properties. Depending on your editor of choice, the process to configure Metals may be completely abstracted away. You simply click install, wait a bit, and start coding. In this post I'd like to talk a bit about the progression of how Metals was originally configured fully with server properties and how it can now be fully configured via the client, which in LSP terms is your editor. This can serve both as a guide for those client extension maintainers out there and also those curious at how Metals correctly works for all the various editors.
The first configuration
Looking back to the Fall of 2018, you see a giant glimpse of Metals becoming
what it is today when looking at a giant commit by
@olafurpg with the title Implement pretty
basic language server and build
client..
It's a pretty fascinating commit to look at if you're interested in the
beginnings of Metals, but I want to focus in on a specific file that still exists
today, which is the
MetalsServerConfig.scala.
In this file you see the first configuration options that existed for Metals.
You see things like isLogShowMessage
to ensure users were correctly getting
status messages instead of everything just going into the logs. (This was also
before
metals/status
existed which is used today for a better status experience in Metals). You also
see other options like isHttpEnabled
for Metals to start the Doctor for those
that needed an HTTP client interface, or even an icons
setting to ensure
things looked nice and matched your client. At this point, instead of just
having the user specify every one of these when they bootstrapped the server, a
metals.client
property was introduced that we could give editors a set of
defaults. Here is an example for the first settings for Vim and Metals using the
vim-lsc plugin:
System.getProperty("metals.client", "unknown") match {
case "vim-lsc" =>
MetalsServerConfig().copy(
isExtensionsEnabled = false,
fileWatcher = FileWatcherConfig.auto,
isLogStatusBar = true,
isNoInitialized = true,
isHttpEnabled = true,
icons = Icons.unicode
)
...
The property would then be set when the user would bootstrap Metals. This
started out as a manual process for almost all the editors utilizing
Coursier. This still actually remains a
valid way to configure Metals, although not recommended if your client supports
setting InitializationOptions
in the
initalize
request. It's also almost the identical process that happens behind the scenes
when client extensions like
metals-vscode,
coc-metals, and
metals-sublime bootstrap the
server for you. For example, here is how you would manually do this:
coursier bootstrap \
--java-opt -Xss4m \
--java-opt -Xms100m \
--java-opt -Dmetals.client=emacs \
org.scalameta:metals_2.12:0.9.2 \
-r bintray:scalacenter/releases \
-r sonatype:snapshots \
-o /usr/local/bin/metals-emacs -f
In the above example, you would then get the defaults specified in
MetalsServerConfig.scala
for emacs
. Again, when the process is automated
it's very similar, and you can see this if you poke around the
fetchAndLaunchMetals
function in the VS-Code extension. You can see how the path to Coursier is
grabbed, your JAVA_HOME
is captured, and how we get some extra
variables/properties to call Metals with.
User configuration
Apart from server properties, it was also necessary for users to be able to
easily change a setting, even while in the editor. For example, we have a
current setting metals.superMethodLensesEnabled
which when enabled will
display a code lens that when invoked will either go to the parent class
containing the definition of the method or symbol or display the full method
hierarchy allowing you to choose where to go.
Here is an example of what this looks like in Vim:
This feature is actually turned off by default since in very large code bases
you may experience a lag. So if a user wanted to turn this on, it wouldn't be a
great user experience to have to re-bootstrap the server to enable this feature.
This is where the User Configuration comes into play by being able to change a
configuration value and notify the server via
workspace/didChangeConfiguration
.
This can fully happen for most of the user configuration values without any need
to restart the server. You can see the first configuration options added this
way in this
commit
where the ability to define your JAVA_HOME
was added. With now allowing for
user configurations in Metals, this allowed for an even more customized
experience.
Experimental
Being able to customize the server with properties and allowing users to pass in
some configuration values worked great. However, once Metals started creating
LSP extensions for functionality that wasn't supported fully just by LSP, then a
way was needed for the client to express that it supported these extensions.
This is when Metals started to use the
ClientCapabilities.experimental
field which the client needed to declare support the extension. You can see the
first inklings of this when the Tree View
Protocol was
introduced here in this
commit.
This then continued to be further expanded as we introduced more extensions.
As it became easier for various clients to set this, we slowly started to
migrate other options that could
only be previously set via server properties to
ClientCapabilities.experimental
. So settings like which format you'd like the
Doctor to return could now be set directly by the client without need to
bootstrap the server with a specific property. This allowed for much easier
configuration than was previously had.
InitializationOptions
Once it was clear that configuring Metals via the client was desirable, a closer
look was taken at InitializationOptions
that can be passed in during the
initialize
request. Since any value is able to be passed in this way, a decision was made
to fully migrate all the possible settings that were previously set as server
properties (except a select few that we'll touch on later), and also move all
the of settings that could be set under experimental
to
InitializationOptions
as well. This ultimately allows for clients to fully
configured Metals via InitializationOptions
without the need to set any server
properties. In theory this also meant that you could not use the same Metals
executable for VS Code, Vim, or Emacs since the server is fully being configured
by the client itself. The current settings that can be passed in and their
defaults are explained in detail here on the
website,
but the interface is as follows:
interface MetalsInitializationOptions {
compilerOptions?: CompilerInitializationOptions;
debuggingProvider?: boolean;
decorationProvider?: boolean;
didFocusProvider?: boolean;
doctorProvider?: "json" | "html";
executeClientCommandProvider?: boolean;
globSyntax?: "vscode" | "uri";
icons?: "vscode" | "octicons" | "atom" | "unicode";
inputBoxProvider?: boolean;
isExitOnShutdown?: boolean;
isHttpEnabled?: boolean;
openFilesOnRenameProvider?: boolean;
quickPickProvider?: boolean;
renameFileThreshold?: number;
slowTaskProvider?: boolean;
statusBarProvider?: "on" | "off" | "log-message" | "show-message";
treeViewProvider?: boolean;
openNewWindowProvider?: boolean;
}
interface CompilerInitializationOptions {
completionCommand?: string;
isCompletionItemDetailEnabled?: boolean;
isCompletionItemDocumentationEnabled?: boolean;
isCompletionItemResolve?: boolean;
isHoverDocumentationEnabled?: boolean;
isSignatureHelpDocumentationEnabled?: boolean;
overrideDefFormat?: "ascii" | "unicode";
parameterHintsCommand?: string;
snippetAutoIndent?: boolean;
}
You'll notice that this allows for a much finer grained configuration if the
client chooses to set certain values. Everything from whether or not the Scala
Presentation Compiler should populate the SignatureHelp.documentation
to
whether or not the editor supports opening a new window after using the
metals.new-scala-project
command can now be easily configured. Fully
configuring Metals through InitializationOptions
is now the recommended way to
configure Metals.
Are there still server properties?
While all of the old server properties still exist for Metals, it's no longer recommended to use them to configure Metals. However, there are still a few server properties that remain only server properties since they are not meant to be widely used, and aren't exactly recommended to use for the average user. You can see an up to date list of these here on the website and what functionality they provide.
Conclusion
As of Metals 0.9.2 it's fully possibly for all clients to use a default
bootstrapped Metals that can fully be configured via InitializationOptions
.
There is a freshly updated Integrating a new
editor section on
the website to help explain how to exactly configure a client for usage with
Metals. As always, don't hesitate to reach out on any of the various channels
located in the footer or submit an issue to either improve documentation or to
log a bug. Also as a reminder, there is a separate repo for
metals-feature-requests.
Happy coding with Metals!