Debugger Extensions
Debug Adapter Protocol Servers can be exposed as extensions for use in the debugger.
Defining Debugger Extensions
A given extension may provide one or more DAP servers.
Each DAP server must be registered in the extension.toml
:
[debug-adapters.my-debug-adapter]
# Optional relative path to the JSON schema for the debug adapter configuration schema. Defaults to `debug_adapter_schemas/$DEBUG_ADAPTER_NAME_ID.json`.
# Note that while this field is optional, a schema is mandatory.
schema_path = "relative/path/to/schema.json"
Then, in the Rust code for your extension, implement the get_dap_binary
method on your extension:
#![allow(unused)] fn main() { impl zed::Extension for MyExtension { fn get_dap_binary( &mut self, adapter_name: String, config: DebugTaskDefinition, user_provided_debug_adapter_path: Option<String>, worktree: &Worktree, ) -> Result<DebugAdapterBinary, String>; } }
This method should return the command to start up a debug adapter protocol server, along with any arguments or environment variables necessary for it to function.
If you need to download the DAP server from an external source—like GitHub Releases or npm—you can also do that in this function. Make sure to check for updates only periodically, as this function is called whenever a user spawns a new debug session with your debug adapter.
You must also implement dap_request_kind
. This function is used to determine whether a given debug scenario will launch a new debuggee or attach to an existing one.
We also use it to determine that a given debug scenario requires running a locator.
#![allow(unused)] fn main() { impl zed::Extension for MyExtension { fn dap_request_kind( &mut self, _adapter_name: String, _config: Value, ) -> Result<StartDebuggingRequestArgumentsRequest, String>; } }
These two functions are sufficient to expose your debug adapter in debug.json
-based user workflows, but you should strongly consider implementing dap_config_to_scenario
as well.
#![allow(unused)] fn main() { impl zed::Extension for MyExtension { fn dap_config_to_scenario( &mut self, _adapter_name: DebugConfig, ) -> Result<DebugScenario, String>; } }
dap_config_to_scenario
is used when the user spawns a session via new session modal UI. At a high level, it takes a generic debug configuration (that isn't specific to any
debug adapter) and tries to turn it into a concrete debug scenario for your adapter.
Put another way, it is supposed to answer the question: "Given a program, a list of arguments, current working directory and environment variables, what would the configuration for spawning this debug adapter look like?".
Defining Debug Locators
Zed offers an automatic way to create debug scenarios with debug locators.
A locator locates the debug target and figures out how to spawn a debug session for it. Thanks to locators, we can automatically convert existing user tasks (e.g. cargo run
) and convert them into debug scenarios (e.g. cargo build
followed by spawning a debugger with target/debug/my_program
as the program to debug).
Your extension can define it's own debug locators even if it does not expose a debug adapter. We strongly recommend doing so when your extension already exposes language tasks, as it allows users to spawn a debug session without having to manually configure the debug adapter.
Locators can (but don't have to) be agnostic to the debug adapter they are used with. They are simply responsible for locating the debug target and figuring out how to spawn a debug session for it. This allows for a more flexible and extensible debugging experience.
Your extension can define one or more debug locators. Each debug locator must be registered in the extension.toml
:
[debug-locators.my-debug-locator]
Locators have two components.
First, each locator is ran on each available task to figure out if any of the available locators can provide a debug scenario for a given task. This is done by calling dap_locator_create_scenario
.
#![allow(unused)] fn main() { impl zed::Extension for MyExtension { fn dap_locator_create_scenario( &mut self, _locator_name: String, _build_task: TaskTemplate, _resolved_label: String, _debug_adapter_name: String, ) -> Option<DebugScenario>; } }
This function should return Some
debug scenario when that scenario defines a debugging counterpart to a given user task.
Note that a DebugScenario
can include a build task. If there is one, we will execute run_dap_locator
after a build task is finished successfully.
#![allow(unused)] fn main() { impl zed::Extension for MyExtension { fn run_dap_locator( &mut self, _locator_name: String, _build_task: TaskTemplate, ) -> Result<DebugRequest, String>; } }
run_dap_locator
is useful in case you cannot determine a build target deterministically. Some build systems may produce artifacts whose names are not known up-front.
Note however that you do not need to go through a 2-phase resolution; if you can determine the full debug configuration with just dap_locator_create_scenario
, you can omit build
property on a returned DebugScenario
. Please also note that your locator will be called with tasks it's unlikely to accept; thus you should take some effort to return None
early before performing any expensive operations.
Available Extensions
Check out all the DAP servers that have already been exposed as extensions on Zed's site.
We recommend taking a look at their repositories as a way to understand how they are generally created and structured.
Testing
To test your new Debug Adapter Protocol server extension, you can install it as a dev extension.