From 1e83022f0369f65269b27c4650a042b1e722c8e2 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 30 May 2025 20:15:42 +0300 Subject: [PATCH] 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 --- crates/dap/src/adapters.rs | 1 + crates/dap/src/transport.rs | 2 +- crates/dap_adapters/src/javascript.rs | 4 +- crates/languages/src/typescript.rs | 25 ++++--- crates/project/src/debugger/dap_store.rs | 3 +- crates/project/src/debugger/locators.rs | 1 + crates/project/src/debugger/locators/node.rs | 68 ++++++++++++++++++++ crates/project/src/debugger/session.rs | 4 +- 8 files changed, 95 insertions(+), 13 deletions(-) create mode 100644 crates/project/src/debugger/locators/node.rs diff --git a/crates/dap/src/adapters.rs b/crates/dap/src/adapters.rs index 38da0931f2..3319175920 100644 --- a/crates/dap/src/adapters.rs +++ b/crates/dap/src/adapters.rs @@ -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())); diff --git a/crates/dap/src/transport.rs b/crates/dap/src/transport.rs index c869364a94..ac51ca7195 100644 --- a/crates/dap/src/transport.rs +++ b/crates/dap/src/transport.rs @@ -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" { diff --git a/crates/dap_adapters/src/javascript.rs b/crates/dap_adapters/src/javascript.rs index 086bb84b65..74ef5feccf 100644 --- a/crates/dap_adapters/src/javascript.rs +++ b/crates/dap_adapters/src/javascript.rs @@ -26,7 +26,7 @@ impl JsDebugAdapter { delegate: &Arc, ) -> Result { 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())); } } diff --git a/crates/languages/src/typescript.rs b/crates/languages/src/typescript.rs index 9728ebbc55..2ed082ee24 100644 --- a/crates/languages/src/typescript.rs +++ b/crates/languages/src/typescript.rs @@ -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() }); } diff --git a/crates/project/src/debugger/dap_store.rs b/crates/project/src/debugger/dap_store.rs index 382efd108b..368b6da25c 100644 --- a/crates/project/src/debugger/dap_store.rs +++ b/crates/project/src/debugger/dap_store.rs @@ -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); diff --git a/crates/project/src/debugger/locators.rs b/crates/project/src/debugger/locators.rs index a845f1759c..2faa4c4ca9 100644 --- a/crates/project/src/debugger/locators.rs +++ b/crates/project/src/debugger/locators.rs @@ -1,3 +1,4 @@ pub(crate) mod cargo; pub(crate) mod go; +pub(crate) mod node; pub(crate) mod python; diff --git a/crates/project/src/debugger/locators/node.rs b/crates/project/src/debugger/locators/node.rs new file mode 100644 index 0000000000..9199abef91 --- /dev/null +++ b/crates/project/src/debugger/locators/node.rs @@ -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 { + // 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 { + bail!("Python locator should not require DapLocator::run to be ran"); + } +} diff --git a/crates/project/src/debugger/session.rs b/crates/project/src/debugger/session.rs index 0d846ae7e5..080eede5c3 100644 --- a/crates/project/src/debugger/session.rs +++ b/crates/project/src/debugger/session.rs @@ -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() }) }