Running and debugging
Metals implements the Debug Adapter Protocol, which can be used by the editor to communicate with JVM to run and debug code. Alternatively, Metals is also able to provide editors with all the information needed to run the code (this is currently supported in run code lenses for main classes).
How to add support for debugging or running in my editor?
There are two main ways to add support for debugging depending on the capabilities exposed by the client.
Via code lenses
If you want to use DAP the editor needs to handle two commands in its language
client extension:
metals-run-session-start
and
metals-debug-session-start
.
These commands should get executed automatically by the LSP client once the user
activates a code lens. The difference between them is that the former ignores
all breakpoints being set while the latter respects them. The procedure of
starting the run/debug session is as follows:
Then we can request the debug adapter URI from the metals server using the
debug-adapter-start
command.
Starting a Debug Adapter Protocol session might take some time, since it needs
to set up all the neccesary utilities for debugging. Metals also provides a
shellCommand
field, which will be present in the command attached to the run
main methods code lenses. This field can be used to simply run the process
quickly without the debugging capabilities.
If you can't or won't support DAP, you can use the runProvider
instead of
debugProvider
option in the initialization options sent from the editor to the
Metals server. This will make sure that only the run
code lense show up with
the needed shellCommand
field.
Via explicit main or test commands
Apart from using code lenses, users can start a debug session by executing the
debug-adapter-start
command with any of following params:
- for an explicit main class
{
"mainClass": "com.foo.App",
"buildTarget": "foo",
"args": ["bar"],
"jvmOptions": ["-Dpropert=123"],
"env": { "RETRY": "TRUE" },
"envFile": ".env"
}
- for an explicit test class
{
"testClass": "com.foo.FooSuite",
"buildTarget": "foo"
}
buildTarget
is an optional parameter, which might be useful if there are
identically named classes in different modules. A URI will be returned that can
be used by the DAP client.
envFile
is an optional parameter, which allows you to specify a path to a
.env
file with additional environment variables. The path can be either
absolute or relative to your project workspace. The parser supports single line
as well as multi-line quoted values (without value substitution). Any variables
defined in the env
object take precedence over those from the .env
file.
Here's an example of a supported .env
file:
# single line values
key1=value 1
key2='value 2' # ignored inline comment
key3="value 3"
# multi-line values
key4='line 1
line 2'
key5="line 1
line 2"
# export statements
export key6=value 6
# comma delimiter
key7:value 6
# keys cannot contain dots or dashes
a.b.key8=value 8 # will be ignored
a-b-key9=value 9 # will be ignored
- for Metals discovery
This option works a bit different than the other two param shapes as you don't
specify a test or main class, but rather a runType
of either "run"
,
"runOrTestFile"
, "testFile"
, or "testTarget"
and a file URI representing
your current location. "run"
will automatically find any main method in the
build target that belongs to the URI that was sent in. If multiple are found,
you will be given the choice of which to run. "runOrTestFile"
will try to find
a main or test class in your current file and run them. The "testFile"
option
will check for any test classes in your current file and run them. Similarly,
"testTarget"
will run all test classes found in the build target that the URI
belongs to. The "args"
, "jvmOptions"
, "env"
, and "envFile"
are all valid
keys that can be sent as well with the same format as above.
{
"path": "file:///path/to/my/file.scala",
"runType": "testTarget"
}
Instead of debug-adapter-start
if you only want to get data about the command
to run you can use discover-jvm-run-command
, which takes the same json as
above, but instead of starting the DAP session it will return the
DebugSessionParams
object containing targets that this main class can be run
for along with the data about the main class which will take form of:
{
"targets": ["id1"],
"dataKind": "scala-main-class",
"data": {
"class": "Foo",
"arguments": [],
"jvmOptions": [],
"environmentVariables": [],
"shellCommand": "java ..."
}
}
where shellCommand
will be the exact command to run if you want to run it on
your own without DAP.
Wiring it all together
No matter which method you use, you still need to connect the debug adapter extension specific to you editor using the aforementioned URI and let it drive the run/debug session. For reference, take a look at the vscode implementation
and how it is wired up together
Supported Testing Frameworks
NOTE: While Metals detects test suites for most of existing testing frameworks, support for recognizing individual tests is more limited. Metals supports the current set of test frameworks when it comes to individual test discovery:
- Junit
- MUnit
- Scalatest
- Weaver Test
Debugging the connection
Create the following trace files to spy on incoming/outgoing JSON communication between the debug server and editor.
# macOS
touch ~/Library/Caches/org.scalameta.metals/dap-server.trace.json
touch ~/Library/Caches/org.scalameta.metals/dap-client.trace.json
# Linux
touch ~/.cache/metals/dap-server.trace.json
touch ~/.cache/metals/dap-client.trace.json