extensions: Yet another PR for debugger touchups (#32822)

We'll now clean up DAP locators for unloaded extensions and load schemas
proper

I can now load a custom Ruby extensions with all bells and whistles and
use it as my debugger.

Release Notes:

- N/A
This commit is contained in:
Piotr Osiewicz 2025-06-17 09:34:55 +02:00 committed by GitHub
parent d92d52b508
commit 0e794fa0ac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 262 additions and 45 deletions

View file

@ -50,20 +50,23 @@ impl DapRegistry {
let name = adapter.name(); let name = adapter.name();
let _previous_value = self.0.write().adapters.insert(name, adapter); let _previous_value = self.0.write().adapters.insert(name, adapter);
} }
pub fn add_locator(&self, locator: Arc<dyn DapLocator>) {
self.0.write().locators.insert(locator.name(), locator);
}
pub fn remove_adapter(&self, name: &str) {
self.0.write().adapters.remove(name);
}
pub fn remove_locator(&self, locator: &str) {
self.0.write().locators.remove(locator);
}
pub fn adapter_language(&self, adapter_name: &str) -> Option<LanguageName> { pub fn adapter_language(&self, adapter_name: &str) -> Option<LanguageName> {
self.adapter(adapter_name) self.adapter(adapter_name)
.and_then(|adapter| adapter.adapter_language_name()) .and_then(|adapter| adapter.adapter_language_name())
} }
pub fn add_locator(&self, locator: Arc<dyn DapLocator>) {
let _previous_value = self.0.write().locators.insert(locator.name(), locator);
debug_assert!(
_previous_value.is_none(),
"Attempted to insert a new debug locator when one is already registered"
);
}
pub async fn adapters_schema(&self) -> task::AdapterSchemas { pub async fn adapters_schema(&self) -> task::AdapterSchemas {
let mut schemas = AdapterSchemas(vec![]); let mut schemas = AdapterSchemas(vec![]);

View file

@ -1,7 +1,7 @@
mod extension_dap_adapter; mod extension_dap_adapter;
mod extension_locator_adapter; mod extension_locator_adapter;
use std::sync::Arc; use std::{path::Path, sync::Arc};
use dap::DapRegistry; use dap::DapRegistry;
use extension::{ExtensionDebugAdapterProviderProxy, ExtensionHostProxy}; use extension::{ExtensionDebugAdapterProviderProxy, ExtensionHostProxy};
@ -34,8 +34,11 @@ impl ExtensionDebugAdapterProviderProxy for DebugAdapterRegistryProxy {
&self, &self,
extension: Arc<dyn extension::Extension>, extension: Arc<dyn extension::Extension>,
debug_adapter_name: Arc<str>, debug_adapter_name: Arc<str>,
schema_path: &Path,
) { ) {
if let Some(adapter) = ExtensionDapAdapter::new(extension, debug_adapter_name).log_err() { if let Some(adapter) =
ExtensionDapAdapter::new(extension, debug_adapter_name, schema_path).log_err()
{
self.debug_adapter_registry.add_adapter(Arc::new(adapter)); self.debug_adapter_registry.add_adapter(Arc::new(adapter));
} }
} }
@ -51,4 +54,13 @@ impl ExtensionDebugAdapterProviderProxy for DebugAdapterRegistryProxy {
locator_name, locator_name,
))); )));
} }
fn unregister_debug_adapter(&self, debug_adapter_name: Arc<str>) {
self.debug_adapter_registry
.remove_adapter(&debug_adapter_name);
}
fn unregister_debug_locator(&self, locator_name: Arc<str>) {
self.debug_adapter_registry.remove_locator(&locator_name);
}
} }

View file

@ -26,11 +26,13 @@ impl ExtensionDapAdapter {
pub(crate) fn new( pub(crate) fn new(
extension: Arc<dyn extension::Extension>, extension: Arc<dyn extension::Extension>,
debug_adapter_name: Arc<str>, debug_adapter_name: Arc<str>,
schema_path: &Path,
) -> Result<Self> { ) -> Result<Self> {
let schema = std::fs::read_to_string(extension.path_from_extension( let schema = std::fs::read_to_string(&schema_path).with_context(|| {
&Path::new("debug_adapter_schemas").join(debug_adapter_name.as_ref()), format!(
)) "Failed to read debug adapter schema for {debug_adapter_name} (from path: `{schema_path:?}`)"
.with_context(|| format!("Failed to read debug adapter schema for {debug_adapter_name}"))?; )
})?;
let schema = serde_json::Value::from_str(&schema).with_context(|| { let schema = serde_json::Value::from_str(&schema).with_context(|| {
format!("Debug adapter schema for {debug_adapter_name} is not a valid JSON") format!("Debug adapter schema for {debug_adapter_name} is not a valid JSON")
})?; })?;

View file

@ -1,7 +1,7 @@
use crate::{ use crate::{
ExtensionLibraryKind, ExtensionManifest, GrammarManifestEntry, parse_wasm_extension_version, ExtensionLibraryKind, ExtensionManifest, GrammarManifestEntry, parse_wasm_extension_version,
}; };
use anyhow::{Context as _, Result, bail, ensure}; use anyhow::{Context as _, Result, bail};
use async_compression::futures::bufread::GzipDecoder; use async_compression::futures::bufread::GzipDecoder;
use async_tar::Archive; use async_tar::Archive;
use futures::io::BufReader; use futures::io::BufReader;
@ -98,21 +98,20 @@ impl ExtensionBuilder {
log::info!("compiled Rust extension {}", extension_dir.display()); log::info!("compiled Rust extension {}", extension_dir.display());
} }
let debug_adapters_dir = extension_dir.join("debug_adapter_schemas"); for (debug_adapter_name, meta) in &mut extension_manifest.debug_adapters {
if !extension_manifest.debug_adapters.is_empty() { let debug_adapter_relative_schema_path =
ensure!( meta.schema_path.clone().unwrap_or_else(|| {
debug_adapters_dir.exists(), Path::new("debug_adapter_schemas")
"Expected debug adapter schemas directory to exist" .join(Path::new(debug_adapter_name.as_ref()).with_extension("json"))
); });
} let debug_adapter_schema_path = extension_dir.join(debug_adapter_relative_schema_path);
for debug_adapter_name in &extension_manifest.debug_adapters {
let debug_adapter_schema_path = debug_adapters_dir.join(debug_adapter_name.as_ref());
let debug_adapter_schema = fs::read_to_string(&debug_adapter_schema_path) let debug_adapter_schema = fs::read_to_string(&debug_adapter_schema_path)
.with_context(|| { .with_context(|| {
format!("failed to read debug adapter schema for `{debug_adapter_name}`") format!("failed to read debug adapter schema for `{debug_adapter_name}` from `{debug_adapter_schema_path:?}`")
})?; })?;
_ = serde_json::Value::from_str(&debug_adapter_schema).with_context(|| { _ = serde_json::Value::from_str(&debug_adapter_schema).with_context(|| {
format!("Debug adapter schema for `{debug_adapter_name}` is not a valid JSON") format!("Debug adapter schema for `{debug_adapter_name}` (path: `{debug_adapter_schema_path:?}`) is not a valid JSON")
})?; })?;
} }
for (grammar_name, grammar_metadata) in &extension_manifest.grammars { for (grammar_name, grammar_metadata) in &extension_manifest.grammars {

View file

@ -1,4 +1,4 @@
use std::path::PathBuf; use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use anyhow::Result; use anyhow::Result;
@ -411,17 +411,29 @@ impl ExtensionIndexedDocsProviderProxy for ExtensionHostProxy {
} }
pub trait ExtensionDebugAdapterProviderProxy: Send + Sync + 'static { pub trait ExtensionDebugAdapterProviderProxy: Send + Sync + 'static {
fn register_debug_adapter(&self, extension: Arc<dyn Extension>, debug_adapter_name: Arc<str>); fn register_debug_adapter(
&self,
extension: Arc<dyn Extension>,
debug_adapter_name: Arc<str>,
schema_path: &Path,
);
fn register_debug_locator(&self, extension: Arc<dyn Extension>, locator_name: Arc<str>); fn register_debug_locator(&self, extension: Arc<dyn Extension>, locator_name: Arc<str>);
fn unregister_debug_adapter(&self, debug_adapter_name: Arc<str>);
fn unregister_debug_locator(&self, locator_name: Arc<str>);
} }
impl ExtensionDebugAdapterProviderProxy for ExtensionHostProxy { impl ExtensionDebugAdapterProviderProxy for ExtensionHostProxy {
fn register_debug_adapter(&self, extension: Arc<dyn Extension>, debug_adapter_name: Arc<str>) { fn register_debug_adapter(
&self,
extension: Arc<dyn Extension>,
debug_adapter_name: Arc<str>,
schema_path: &Path,
) {
let Some(proxy) = self.debug_adapter_provider_proxy.read().clone() else { let Some(proxy) = self.debug_adapter_provider_proxy.read().clone() else {
return; return;
}; };
proxy.register_debug_adapter(extension, debug_adapter_name) proxy.register_debug_adapter(extension, debug_adapter_name, schema_path)
} }
fn register_debug_locator(&self, extension: Arc<dyn Extension>, locator_name: Arc<str>) { fn register_debug_locator(&self, extension: Arc<dyn Extension>, locator_name: Arc<str>) {
@ -431,4 +443,18 @@ impl ExtensionDebugAdapterProviderProxy for ExtensionHostProxy {
proxy.register_debug_locator(extension, locator_name) proxy.register_debug_locator(extension, locator_name)
} }
fn unregister_debug_adapter(&self, debug_adapter_name: Arc<str>) {
let Some(proxy) = self.debug_adapter_provider_proxy.read().clone() else {
return;
};
proxy.unregister_debug_adapter(debug_adapter_name)
}
fn unregister_debug_locator(&self, locator_name: Arc<str>) {
let Some(proxy) = self.debug_adapter_provider_proxy.read().clone() else {
return;
};
proxy.unregister_debug_locator(locator_name)
}
} }

View file

@ -88,9 +88,9 @@ pub struct ExtensionManifest {
#[serde(default)] #[serde(default)]
pub capabilities: Vec<ExtensionCapability>, pub capabilities: Vec<ExtensionCapability>,
#[serde(default)] #[serde(default)]
pub debug_adapters: Vec<Arc<str>>, pub debug_adapters: BTreeMap<Arc<str>, DebugAdapterManifestEntry>,
#[serde(default)] #[serde(default)]
pub debug_locators: Vec<Arc<str>>, pub debug_locators: BTreeMap<Arc<str>, DebugLocatorManifestEntry>,
} }
impl ExtensionManifest { impl ExtensionManifest {
@ -210,6 +210,14 @@ pub struct SlashCommandManifestEntry {
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
pub struct IndexedDocsProviderEntry {} pub struct IndexedDocsProviderEntry {}
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
pub struct DebugAdapterManifestEntry {
pub schema_path: Option<PathBuf>,
}
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
pub struct DebugLocatorManifestEntry {}
impl ExtensionManifest { impl ExtensionManifest {
pub async fn load(fs: Arc<dyn Fs>, extension_dir: &Path) -> Result<Self> { pub async fn load(fs: Arc<dyn Fs>, extension_dir: &Path) -> Result<Self> {
let extension_name = extension_dir let extension_name = extension_dir
@ -278,8 +286,8 @@ fn manifest_from_old_manifest(
indexed_docs_providers: BTreeMap::default(), indexed_docs_providers: BTreeMap::default(),
snippets: None, snippets: None,
capabilities: Vec::new(), capabilities: Vec::new(),
debug_adapters: vec![], debug_adapters: Default::default(),
debug_locators: vec![], debug_locators: Default::default(),
} }
} }

View file

@ -20,9 +20,10 @@ pub use wit::{
make_file_executable, make_file_executable,
zed::extension::context_server::ContextServerConfiguration, zed::extension::context_server::ContextServerConfiguration,
zed::extension::dap::{ zed::extension::dap::{
AttachRequest, BuildTaskDefinition, BuildTaskDefinitionTemplatePayload, BuildTaskTemplate,
DebugAdapterBinary, DebugConfig, DebugRequest, DebugScenario, DebugTaskDefinition, DebugAdapterBinary, DebugConfig, DebugRequest, DebugScenario, DebugTaskDefinition,
StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest, TaskTemplate, LaunchRequest, StartDebuggingRequestArguments, StartDebuggingRequestArgumentsRequest,
TcpArguments, TcpArgumentsTemplate, resolve_tcp_template, TaskTemplate, TcpArguments, TcpArgumentsTemplate, resolve_tcp_template,
}, },
zed::extension::github::{ zed::extension::github::{
GithubRelease, GithubReleaseAsset, GithubReleaseOptions, github_release_by_tag_name, GithubRelease, GithubReleaseAsset, GithubReleaseOptions, github_release_by_tag_name,
@ -198,12 +199,15 @@ pub trait Extension: Send + Sync {
&mut self, &mut self,
_adapter_name: String, _adapter_name: String,
_config: DebugTaskDefinition, _config: DebugTaskDefinition,
_user_provided_path: Option<String>, _user_provided_debug_adapter_path: Option<String>,
_worktree: &Worktree, _worktree: &Worktree,
) -> Result<DebugAdapterBinary, String> { ) -> Result<DebugAdapterBinary, String> {
Err("`get_dap_binary` not implemented".to_string()) Err("`get_dap_binary` not implemented".to_string())
} }
/// Determines whether the specified adapter configuration should *launch* a new debuggee process
/// or *attach* to an existing one. This function should not perform any further validation (outside of determining the kind of a request).
/// This function should return an error when the kind cannot be determined (rather than fall back to a known default).
fn dap_request_kind( fn dap_request_kind(
&mut self, &mut self,
_adapter_name: String, _adapter_name: String,
@ -211,12 +215,31 @@ pub trait Extension: Send + Sync {
) -> Result<StartDebuggingRequestArgumentsRequest, String> { ) -> Result<StartDebuggingRequestArgumentsRequest, String> {
Err("`dap_request_kind` not implemented".to_string()) Err("`dap_request_kind` not implemented".to_string())
} }
fn dap_config_to_scenario( /// Converts a high-level definition of a debug scenario (originating in a new session UI) to a "low-level" configuration suitable for a particular adapter.
&mut self, ///
_adapter_name: DebugConfig, /// In layman's terms: given a program, list of arguments, current working directory and environment variables,
) -> Result<DebugScenario, String> { /// create a configuration that can be used to start a debug session.
fn dap_config_to_scenario(&mut self, _config: DebugConfig) -> Result<DebugScenario, String> {
Err("`dap_config_to_scenario` not implemented".to_string()) Err("`dap_config_to_scenario` not implemented".to_string())
} }
/// Locators are entities that convert a Zed task into a debug scenario.
///
/// They can be provided even by extensions that don't provide a debug adapter.
/// For all tasks applicable to a given buffer, Zed will query all locators to find one that can turn the task into a debug scenario.
/// A converted debug scenario can include a build task (it shouldn't contain any configuration in such case); a build task result will later
/// be resolved with [`Extension::run_dap_locator`].
///
/// To work through a real-world example, take a `cargo run` task and a hypothetical `cargo` locator:
/// 1. We may need to modify the task; in this case, it is problematic that `cargo run` spawns a binary. We should turn `cargo run` into a debug scenario with
/// `cargo build` task. This is the decision we make at `dap_locator_create_scenario` scope.
/// 2. Then, after the build task finishes, we will run `run_dap_locator` of the locator that produced the build task to find the program to be debugged. This function
/// should give us a debugger-agnostic configuration for launching a debug target (that we end up resolving with [`Extension::dap_config_to_scenario`]). It's almost as if the user
/// found the artifact path by themselves.
///
/// Note that you're not obliged to use build tasks with locators. Specifically, it is sufficient to provide a debug configuration directly in the return value of
/// `dap_locator_create_scenario` if you're able to do that. Make sure to not fill out `build` field in that case, as that will prevent Zed from running second phase of resolution in such case.
/// This might be of particular relevance to interpreted languages.
fn dap_locator_create_scenario( fn dap_locator_create_scenario(
&mut self, &mut self,
_locator_name: String, _locator_name: String,
@ -226,6 +249,9 @@ pub trait Extension: Send + Sync {
) -> Option<DebugScenario> { ) -> Option<DebugScenario> {
None None
} }
/// Runs the second phase of locator resolution.
/// See [`Extension::dap_locator_create_scenario`] for a hefty comment on locators.
fn run_dap_locator( fn run_dap_locator(
&mut self, &mut self,
_locator_name: String, _locator_name: String,

View file

@ -1149,6 +1149,12 @@ impl ExtensionStore {
for (server_id, _) in extension.manifest.context_servers.iter() { for (server_id, _) in extension.manifest.context_servers.iter() {
self.proxy.unregister_context_server(server_id.clone(), cx); self.proxy.unregister_context_server(server_id.clone(), cx);
} }
for (adapter, _) in extension.manifest.debug_adapters.iter() {
self.proxy.unregister_debug_adapter(adapter.clone());
}
for (locator, _) in extension.manifest.debug_locators.iter() {
self.proxy.unregister_debug_locator(locator.clone());
}
} }
self.wasm_extensions self.wasm_extensions
@ -1344,12 +1350,24 @@ impl ExtensionStore {
.register_indexed_docs_provider(extension.clone(), provider_id.clone()); .register_indexed_docs_provider(extension.clone(), provider_id.clone());
} }
for debug_adapter in &manifest.debug_adapters { for (debug_adapter, meta) in &manifest.debug_adapters {
this.proxy let mut path = root_dir.clone();
.register_debug_adapter(extension.clone(), debug_adapter.clone()); path.push(Path::new(manifest.id.as_ref()));
if let Some(schema_path) = &meta.schema_path {
path.push(schema_path);
} else {
path.push("debug_adapter_schemas");
path.push(Path::new(debug_adapter.as_ref()).with_extension("json"));
}
this.proxy.register_debug_adapter(
extension.clone(),
debug_adapter.clone(),
&path,
);
} }
for debug_adapter in &manifest.debug_adapters { for debug_adapter in manifest.debug_locators.keys() {
this.proxy this.proxy
.register_debug_locator(extension.clone(), debug_adapter.clone()); .register_debug_locator(extension.clone(), debug_adapter.clone());
} }

View file

@ -63,6 +63,7 @@
- [Installing Extensions](./extensions/installing-extensions.md) - [Installing Extensions](./extensions/installing-extensions.md)
- [Developing Extensions](./extensions/developing-extensions.md) - [Developing Extensions](./extensions/developing-extensions.md)
- [Language Extensions](./extensions/languages.md) - [Language Extensions](./extensions/languages.md)
- [Debugger Extensions](./extensions/debugger-extensions.md)
- [Theme Extensions](./extensions/themes.md) - [Theme Extensions](./extensions/themes.md)
- [Icon Theme Extensions](./extensions/icon-themes.md) - [Icon Theme Extensions](./extensions/icon-themes.md)
- [Slash Command Extensions](./extensions/slash-commands.md) - [Slash Command Extensions](./extensions/slash-commands.md)

View file

@ -29,6 +29,8 @@ Zed supports a variety of debug adapters for different programming languages out
These adapters enable Zed to provide a consistent debugging experience across multiple languages while leveraging the specific features and capabilities of each debugger. These adapters enable Zed to provide a consistent debugging experience across multiple languages while leveraging the specific features and capabilities of each debugger.
> Is your desired debugger not listed? You can contribute by adding support for your favorite language or debugger. Check out our [debugger extensions](extensions/debugger-extensions.md) documentation for more information.
## Getting Started ## Getting Started
For basic debugging, you can set up a new configuration by opening the `New Session Modal` either via the `debugger: start` (default: f4) or by clicking the plus icon at the top right of the debug panel. For basic debugging, you can set up a new configuration by opening the `New Session Modal` either via the `debugger: start` (default: f4) or by clicking the plus icon at the top right of the debug panel.

View file

@ -5,6 +5,7 @@ Zed lets you add new functionality using user-defined extensions.
- [Installing Extensions](./extensions/installing-extensions.md) - [Installing Extensions](./extensions/installing-extensions.md)
- [Developing Extensions](./extensions/developing-extensions.md) - [Developing Extensions](./extensions/developing-extensions.md)
- [Developing Language Extensions](./extensions/languages.md) - [Developing Language Extensions](./extensions/languages.md)
- [Developing Debugger Extensions](./extensions/debugger-extensions.md)
- [Developing Themes](./extensions/themes.md) - [Developing Themes](./extensions/themes.md)
- [Developing Icon Themes](./extensions/icon-themes.md) - [Developing Icon Themes](./extensions/icon-themes.md)
- [Developing Slash Commands](./extensions/slash-commands.md) - [Developing Slash Commands](./extensions/slash-commands.md)

View file

@ -0,0 +1,117 @@
# Debugger Extensions
[Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol) Servers can be exposed as extensions for use in the [debugger](../debugger.md).
## Defining Debugger Extensions
A given extension may provide one or more DAP servers.
Each DAP server must be registered in the `extension.toml`:
```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:
```rust
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_.
```rust
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.
```rust
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`:
```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`.
```rust
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](../debugger.md#build-tasks). If there is one, we will execute `run_dap_locator` after a build task is finished successfully.
```rust
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](https://zed.dev/extensions?filter=debug-adapters).
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](./developing-extensions.md#developing-an-extension-locally).

View file

@ -5,6 +5,7 @@
Extensions can add the following capabilities to Zed: Extensions can add the following capabilities to Zed:
- [Languages](./languages.md) - [Languages](./languages.md)
- [Debuggers](./debugger-extensions.md)
- [Themes](./themes.md) - [Themes](./themes.md)
- [Icon Themes](./icon-themes.md) - [Icon Themes](./icon-themes.md)
- [Slash Commands](./slash-commands.md) - [Slash Commands](./slash-commands.md)

View file

@ -27,6 +27,7 @@ line_comments = ["# "]
- `tab_size` defines the indentation/tab size used for this language (default is `4`). - `tab_size` defines the indentation/tab size used for this language (default is `4`).
- `hard_tabs` whether to indent with tabs (`true`) or spaces (`false`, the default). - `hard_tabs` whether to indent with tabs (`true`) or spaces (`false`, the default).
- `first_line_pattern` is a regular expression, that in addition to `path_suffixes` (above) or `file_types` in settings can be used to match files which should use this language. For example Zed uses this to identify Shell Scripts by matching the [shebangs lines](https://github.com/zed-industries/zed/blob/main/crates/languages/src/bash/config.toml) in the first line of a script. - `first_line_pattern` is a regular expression, that in addition to `path_suffixes` (above) or `file_types` in settings can be used to match files which should use this language. For example Zed uses this to identify Shell Scripts by matching the [shebangs lines](https://github.com/zed-industries/zed/blob/main/crates/languages/src/bash/config.toml) in the first line of a script.
- `debuggers` is an array of strings that are used to identify debuggers in the language. When launching a debugger's `New Process Modal`, Zed will order available debuggers by the order of entries in this array.
<!-- <!--
TBD: Document `language_name/config.toml` keys TBD: Document `language_name/config.toml` keys