Add a JS/TS debug locator (#31769)

With this, a semi-working debug session is possible from the JS/TS
gutter tasks:


https://github.com/user-attachments/assets/8db6ed29-b44a-4314-ae8b-a8213291bffc

For now, available in debug builds only as a base to improve on later on
the DAP front.

Release Notes:

- N/A

---------

Co-authored-by: Piotr Osiewicz <peterosiewicz@gmail.com>
This commit is contained in:
Kirill Bulatov 2025-05-30 20:15:42 +03:00 committed by GitHub
parent 0ee900e8fb
commit 1e83022f03
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 95 additions and 13 deletions

View file

@ -298,6 +298,7 @@ pub async fn download_adapter_from_github(
response.status().to_string()
);
delegate.output_to_console("Download complete".to_owned());
match file_type {
DownloadedFileType::GzipTar => {
let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));

View file

@ -434,7 +434,7 @@ impl TransportDelegate {
.with_context(|| "reading a message from server")?
== 0
{
anyhow::bail!("debugger reader stream closed");
anyhow::bail!("debugger reader stream closed, last string output: '{buffer}'");
};
if buffer == "\r\n" {

View file

@ -26,7 +26,7 @@ impl JsDebugAdapter {
delegate: &Arc<dyn DapDelegate>,
) -> Result<AdapterVersion> {
let release = latest_github_release(
&format!("{}/{}", "microsoft", Self::ADAPTER_NPM_NAME),
&format!("microsoft/{}", Self::ADAPTER_NPM_NAME),
true,
false,
delegate.http_client(),
@ -449,6 +449,8 @@ impl DebugAdapter for JsDebugAdapter {
delegate.as_ref(),
)
.await?;
} else {
delegate.output_to_console(format!("{} debug adapter is up to date", self.name()));
}
}

View file

@ -168,8 +168,9 @@ impl ContextProvider for TypeScriptContextProvider {
command: TYPESCRIPT_RUNNER_VARIABLE.template_value(),
args: vec![
TYPESCRIPT_JEST_TASK_VARIABLE.template_value(),
VariableName::File.template_value(),
VariableName::RelativeFile.template_value(),
],
cwd: Some(VariableName::WorktreeRoot.template_value()),
..TaskTemplate::default()
});
task_templates.0.push(TaskTemplate {
@ -183,13 +184,14 @@ impl ContextProvider for TypeScriptContextProvider {
TYPESCRIPT_JEST_TASK_VARIABLE.template_value(),
"--testNamePattern".to_owned(),
format!("\"{}\"", VariableName::Symbol.template_value()),
VariableName::File.template_value(),
VariableName::RelativeFile.template_value(),
],
tags: vec![
"ts-test".to_owned(),
"js-test".to_owned(),
"tsx-test".to_owned(),
],
cwd: Some(VariableName::WorktreeRoot.template_value()),
..TaskTemplate::default()
});
@ -203,8 +205,9 @@ impl ContextProvider for TypeScriptContextProvider {
args: vec![
TYPESCRIPT_VITEST_TASK_VARIABLE.template_value(),
"run".to_owned(),
VariableName::File.template_value(),
VariableName::RelativeFile.template_value(),
],
cwd: Some(VariableName::WorktreeRoot.template_value()),
..TaskTemplate::default()
});
task_templates.0.push(TaskTemplate {
@ -219,13 +222,14 @@ impl ContextProvider for TypeScriptContextProvider {
"run".to_owned(),
"--testNamePattern".to_owned(),
format!("\"{}\"", VariableName::Symbol.template_value()),
VariableName::File.template_value(),
VariableName::RelativeFile.template_value(),
],
tags: vec![
"ts-test".to_owned(),
"js-test".to_owned(),
"tsx-test".to_owned(),
],
cwd: Some(VariableName::WorktreeRoot.template_value()),
..TaskTemplate::default()
});
@ -238,8 +242,9 @@ impl ContextProvider for TypeScriptContextProvider {
command: TYPESCRIPT_RUNNER_VARIABLE.template_value(),
args: vec![
TYPESCRIPT_MOCHA_TASK_VARIABLE.template_value(),
VariableName::File.template_value(),
VariableName::RelativeFile.template_value(),
],
cwd: Some(VariableName::WorktreeRoot.template_value()),
..TaskTemplate::default()
});
task_templates.0.push(TaskTemplate {
@ -253,13 +258,14 @@ impl ContextProvider for TypeScriptContextProvider {
TYPESCRIPT_MOCHA_TASK_VARIABLE.template_value(),
"--grep".to_owned(),
format!("\"{}\"", VariableName::Symbol.template_value()),
VariableName::File.template_value(),
VariableName::RelativeFile.template_value(),
],
tags: vec![
"ts-test".to_owned(),
"js-test".to_owned(),
"tsx-test".to_owned(),
],
cwd: Some(VariableName::WorktreeRoot.template_value()),
..TaskTemplate::default()
});
@ -272,8 +278,9 @@ impl ContextProvider for TypeScriptContextProvider {
command: TYPESCRIPT_RUNNER_VARIABLE.template_value(),
args: vec![
TYPESCRIPT_JASMINE_TASK_VARIABLE.template_value(),
VariableName::File.template_value(),
VariableName::RelativeFile.template_value(),
],
cwd: Some(VariableName::WorktreeRoot.template_value()),
..TaskTemplate::default()
});
task_templates.0.push(TaskTemplate {
@ -286,13 +293,14 @@ impl ContextProvider for TypeScriptContextProvider {
args: vec![
TYPESCRIPT_JASMINE_TASK_VARIABLE.template_value(),
format!("--filter={}", VariableName::Symbol.template_value()),
VariableName::File.template_value(),
VariableName::RelativeFile.template_value(),
],
tags: vec![
"ts-test".to_owned(),
"js-test".to_owned(),
"tsx-test".to_owned(),
],
cwd: Some(VariableName::WorktreeRoot.template_value()),
..TaskTemplate::default()
});
@ -313,6 +321,7 @@ impl ContextProvider for TypeScriptContextProvider {
package_json_script.template_value(),
],
tags: vec!["package-script".into()],
cwd: Some(VariableName::WorktreeRoot.template_value()),
..TaskTemplate::default()
});
}

View file

@ -103,8 +103,9 @@ impl DapStore {
ADD_LOCATORS.call_once(|| {
let registry = DapRegistry::global(cx);
registry.add_locator(Arc::new(locators::cargo::CargoLocator {}));
registry.add_locator(Arc::new(locators::python::PythonLocator));
registry.add_locator(Arc::new(locators::go::GoLocator {}));
registry.add_locator(Arc::new(locators::node::NodeLocator));
registry.add_locator(Arc::new(locators::python::PythonLocator));
});
client.add_entity_request_handler(Self::handle_run_debug_locator);
client.add_entity_request_handler(Self::handle_get_debug_adapter_binary);

View file

@ -1,3 +1,4 @@
pub(crate) mod cargo;
pub(crate) mod go;
pub(crate) mod node;
pub(crate) mod python;

View file

@ -0,0 +1,68 @@
use std::{borrow::Cow, path::Path};
use anyhow::{Result, bail};
use async_trait::async_trait;
use dap::{DapLocator, DebugRequest, adapters::DebugAdapterName};
use gpui::SharedString;
use task::{DebugScenario, SpawnInTerminal, TaskTemplate, VariableName};
pub(crate) struct NodeLocator;
const TYPESCRIPT_RUNNER_VARIABLE: VariableName =
VariableName::Custom(Cow::Borrowed("TYPESCRIPT_RUNNER"));
#[async_trait]
impl DapLocator for NodeLocator {
fn name(&self) -> SharedString {
SharedString::new_static("Node")
}
/// Determines whether this locator can generate debug target for given task.
fn create_scenario(
&self,
build_config: &TaskTemplate,
resolved_label: &str,
adapter: DebugAdapterName,
) -> Option<DebugScenario> {
// TODO(debugger) fix issues with `await` breakpoint step
if cfg!(not(debug_assertions)) {
return None;
}
if adapter.as_ref() != "JavaScript" {
return None;
}
if build_config.command != TYPESCRIPT_RUNNER_VARIABLE.template_value() {
return None;
}
let test_library = build_config.args.first()?;
let program_path = Path::new("$ZED_WORKTREE_ROOT")
.join("node_modules")
.join(".bin")
.join(test_library);
let args = build_config.args[1..].to_vec();
let config = serde_json::json!({
"request": "launch",
"type": "pwa-node",
"program": program_path,
"args": args,
"cwd": build_config.cwd.clone(),
"runtimeArgs": ["--inspect-brk"],
"console": "integratedTerminal",
});
Some(DebugScenario {
adapter: adapter.0,
label: resolved_label.to_string().into(),
build: None,
config,
tcp_connection: None,
})
}
async fn run(&self, _: SpawnInTerminal) -> Result<DebugRequest> {
bail!("Python locator should not require DapLocator::run to be ran");
}
}

View file

@ -1421,7 +1421,7 @@ impl Session {
));
return cx.spawn(async move |this, cx| {
this.update(cx, |this, cx| process_result(this, error, cx))
.log_err()
.ok()
.flatten()
});
}
@ -1430,7 +1430,7 @@ impl Session {
cx.spawn(async move |this, cx| {
let result = request.await;
this.update(cx, |this, cx| process_result(this, result, cx))
.log_err()
.ok()
.flatten()
})
}