From 7919bc30893d6fd46880b64eb86c6b036dd60db7 Mon Sep 17 00:00:00 2001 From: Remco Smits Date: Thu, 16 Jan 2025 21:35:38 +0100 Subject: [PATCH 01/22] WIP add toolchain for python Co-Authored-By: Anthony Eid <56899983+Anthony-Eid@users.noreply.github.com> --- Cargo.lock | 1 + crates/dap/Cargo.toml | 1 + crates/dap/src/adapters.rs | 2 ++ crates/dap_adapters/src/python.rs | 21 ++++----------------- crates/project/src/dap_store.rs | 23 ++++++++++++++--------- crates/project/src/project.rs | 23 ++++++++++++----------- 6 files changed, 34 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1661ddd85f4db3..c428e7173070ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3503,6 +3503,7 @@ dependencies = [ "futures 0.3.31", "gpui", "http_client", + "language", "log", "node_runtime", "parking_lot", diff --git a/crates/dap/Cargo.toml b/crates/dap/Cargo.toml index 9f1161c9589adc..26b26e27006e84 100644 --- a/crates/dap/Cargo.toml +++ b/crates/dap/Cargo.toml @@ -30,6 +30,7 @@ fs.workspace = true futures.workspace = true gpui.workspace = true http_client.workspace = true +language.workspace = true log.workspace = true node_runtime.workspace = true parking_lot.workspace = true diff --git a/crates/dap/src/adapters.rs b/crates/dap/src/adapters.rs index e7eee938b3ff03..f9b8cd7d7a1772 100644 --- a/crates/dap/src/adapters.rs +++ b/crates/dap/src/adapters.rs @@ -9,6 +9,7 @@ use async_trait::async_trait; use futures::io::BufReader; use gpui::SharedString; pub use http_client::{github::latest_github_release, HttpClient}; +use language::LanguageToolchainStore; use node_runtime::NodeRuntime; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -37,6 +38,7 @@ pub enum DapStatus { pub trait DapDelegate { fn http_client(&self) -> Option>; fn node_runtime(&self) -> Option; + fn toolchain(&self) -> Arc; fn fs(&self) -> Arc; fn updated_adapters(&self) -> Arc>>; fn update_status(&self, dap_name: DebugAdapterName, status: DapStatus); diff --git a/crates/dap_adapters/src/python.rs b/crates/dap_adapters/src/python.rs index d930383c1531f9..61c140c0ca0aeb 100644 --- a/crates/dap_adapters/src/python.rs +++ b/crates/dap_adapters/src/python.rs @@ -92,23 +92,10 @@ impl DebugAdapter for PythonDebugAdapter { .ok_or_else(|| anyhow!("Debugpy directory not found"))? }; - let python_cmds = [ - OsStr::new("python3"), - OsStr::new("python"), - OsStr::new("py"), - ]; - let python_path = python_cmds - .iter() - .filter_map(|cmd| { - delegate - .which(cmd) - .and_then(|path| path.to_str().map(|str| str.to_string())) - }) - .find(|_| true); - - let python_path = python_path.ok_or(anyhow!( - "Failed to start debugger because python couldn't be found in PATH" - ))?; + let python_path = delegate + .toolchain() + .active_toolchain(worktree_id, language_name, cx) + .await?; Ok(DebugAdapterBinary { command: python_path, diff --git a/crates/project/src/dap_store.rs b/crates/project/src/dap_store.rs index 65539492969f6d..a67d6c24b4d4e2 100644 --- a/crates/project/src/dap_store.rs +++ b/crates/project/src/dap_store.rs @@ -40,7 +40,7 @@ use gpui::{AsyncAppContext, Context, EventEmitter, Model, ModelContext, SharedSt use http_client::HttpClient; use language::{ proto::{deserialize_anchor, serialize_anchor as serialize_text_anchor}, - Buffer, BufferSnapshot, LanguageRegistry, LanguageServerBinaryStatus, + Buffer, BufferSnapshot, LanguageRegistry, LanguageServerBinaryStatus, LanguageToolchainStore, }; use lsp::LanguageServerName; use node_runtime::NodeRuntime; @@ -165,6 +165,7 @@ impl DapStore { fs: Arc, languages: Arc, environment: Model, + toolchain_store: Arc, cx: &mut ModelContext, ) -> Self { cx.on_app_quit(Self::shutdown_sessions).detach(); @@ -180,6 +181,7 @@ impl DapStore { Some(node_runtime.clone()), fs.clone(), languages.clone(), + toolchain_store, Task::ready(None).shared(), ), client_by_session: Default::default(), @@ -191,11 +193,7 @@ impl DapStore { } } - pub fn new_remote( - project_id: u64, - upstream_client: AnyProtoClient, - _: &mut ModelContext, - ) -> Self { + pub fn new_remote(project_id: u64, upstream_client: AnyProtoClient) -> Self { Self { mode: DapStoreMode::Remote(RemoteDapStore { upstream_client: Some(upstream_client), @@ -549,7 +547,7 @@ impl DapStore { })); let adapter_delegate = Arc::new(adapter_delegate); - let client_id = self.as_local().unwrap().next_client_id(); + let client_id = local_store.next_client_id(); cx.spawn(|this, mut cx| async move { let adapter = build_adapter(&config.kind).await?; @@ -2181,10 +2179,11 @@ impl SerializedBreakpoint { #[derive(Clone)] pub struct DapAdapterDelegate { fs: Arc, - http_client: Option>, + languages: Arc, node_runtime: Option, + http_client: Option>, + toolchain_store: Arc, updated_adapters: Arc>>, - languages: Arc, load_shell_env_task: Shared>>>, } @@ -2194,6 +2193,7 @@ impl DapAdapterDelegate { node_runtime: Option, fs: Arc, languages: Arc, + toolchain_store: Arc, load_shell_env_task: Shared>>>, ) -> Self { Self { @@ -2201,6 +2201,7 @@ impl DapAdapterDelegate { languages, http_client, node_runtime, + toolchain_store, load_shell_env_task, updated_adapters: Default::default(), } @@ -2253,4 +2254,8 @@ impl dap::adapters::DapDelegate for DapAdapterDelegate { let task = self.load_shell_env_task.clone(); task.await.unwrap_or_default() } + + fn toolchain(&self) -> Arc { + self.toolchain_store.clone() + } } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a27b8dfc5fc584..a2458b55c438e1 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -650,6 +650,15 @@ impl Project { let environment = ProjectEnvironment::new(&worktree_store, env, cx); + let toolchain_store = cx.new_model(|cx| { + ToolchainStore::local( + languages.clone(), + worktree_store.clone(), + environment.clone(), + cx, + ) + }); + let dap_store = cx.new_model(|cx| { DapStore::new_local( client.http_client(), @@ -657,6 +666,7 @@ impl Project { fs.clone(), languages.clone(), environment.clone(), + toolchain_store.read(cx).as_language_toolchain_store(), cx, ) }); @@ -681,15 +691,6 @@ impl Project { ) }); - let toolchain_store = cx.new_model(|cx| { - ToolchainStore::local( - languages.clone(), - worktree_store.clone(), - environment.clone(), - cx, - ) - }); - let task_store = cx.new_model(|cx| { TaskStore::local( fs.clone(), @@ -845,7 +846,7 @@ impl Project { cx.subscribe(&lsp_store, Self::on_lsp_store_event).detach(); let dap_store = - cx.new_model(|cx| DapStore::new_remote(SSH_PROJECT_ID, client.clone().into(), cx)); + cx.new_model(|_| DapStore::new_remote(SSH_PROJECT_ID, client.clone().into())); cx.subscribe(&ssh, Self::on_ssh_event).detach(); cx.observe(&ssh, |_, _, cx| cx.notify()).detach(); @@ -1013,7 +1014,7 @@ impl Project { let environment = cx.update(|cx| ProjectEnvironment::new(&worktree_store, None, cx))?; let dap_store = cx.new_model(|cx| { - let mut dap_store = DapStore::new_remote(remote_id, client.clone().into(), cx); + let mut dap_store = DapStore::new_remote(remote_id, client.clone().into()); dap_store.set_breakpoints_from_proto(response.payload.breakpoints, cx); dap_store.set_debug_sessions_from_proto(response.payload.debug_sessions, cx); From 7c55f4cc9dcfedd764fd33e5c34e5c66cc85b612 Mon Sep 17 00:00:00 2001 From: Anthony Eid Date: Thu, 16 Jan 2025 17:51:47 -0500 Subject: [PATCH 02/22] Integrate toolchain store with dap_store & use it for debugpy --- Cargo.lock | 1 + crates/collab/src/tests/debug_panel_tests.rs | 5 +++ crates/dap/src/adapters.rs | 8 +++- crates/dap_adapters/Cargo.toml | 1 + crates/dap_adapters/src/python.rs | 31 +++++++++++++-- crates/debugger_ui/src/tests/attach_modal.rs | 3 ++ crates/debugger_ui/src/tests/console.rs | 3 ++ .../debugger_ui/src/tests/debugger_panel.rs | 7 ++++ .../debugger_ui/src/tests/stack_frame_list.rs | 2 + crates/debugger_ui/src/tests/variable_list.rs | 3 ++ crates/project/src/dap_store.rs | 39 +++++++++++++++---- crates/project/src/project.rs | 14 ++++++- crates/remote_server/src/headless_project.rs | 19 ++++----- 13 files changed, 112 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c428e7173070ab..378dfeeea10c0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3536,6 +3536,7 @@ dependencies = [ "anyhow", "async-trait", "dap", + "language", "paths", "regex", "serde", diff --git a/crates/collab/src/tests/debug_panel_tests.rs b/crates/collab/src/tests/debug_panel_tests.rs index 1b35d217bf4cf3..aae713e0ea1c12 100644 --- a/crates/collab/src/tests/debug_panel_tests.rs +++ b/crates/collab/src/tests/debug_panel_tests.rs @@ -98,6 +98,7 @@ async fn test_debug_panel_item_opens_on_remote( cwd: None, initialize_args: None, }, + None, cx, ) }) @@ -210,6 +211,7 @@ async fn test_active_debug_panel_item_set_on_join_project( cwd: None, initialize_args: None, }, + None, cx, ) }) @@ -353,6 +355,7 @@ async fn test_debug_panel_remote_button_presses( cwd: None, initialize_args: None, }, + None, cx, ) }) @@ -716,6 +719,7 @@ async fn test_restart_stack_frame(cx_a: &mut TestAppContext, cx_b: &mut TestAppC cwd: None, initialize_args: None, }, + None, cx, ) }) @@ -901,6 +905,7 @@ async fn test_updated_breakpoints_send_to_dap( cwd: None, initialize_args: None, }, + None, cx, ) }) diff --git a/crates/dap/src/adapters.rs b/crates/dap/src/adapters.rs index f9b8cd7d7a1772..d9aa43aa004366 100644 --- a/crates/dap/src/adapters.rs +++ b/crates/dap/src/adapters.rs @@ -9,7 +9,7 @@ use async_trait::async_trait; use futures::io::BufReader; use gpui::SharedString; pub use http_client::{github::latest_github_release, HttpClient}; -use language::LanguageToolchainStore; +use language::{LanguageName, Toolchain}; use node_runtime::NodeRuntime; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -38,7 +38,7 @@ pub enum DapStatus { pub trait DapDelegate { fn http_client(&self) -> Option>; fn node_runtime(&self) -> Option; - fn toolchain(&self) -> Arc; + fn toolchain(&self, adapter_name: &DebugAdapterName) -> Option<&Toolchain>; fn fs(&self) -> Arc; fn updated_adapters(&self) -> Arc>>; fn update_status(&self, dap_name: DebugAdapterName, status: DapStatus); @@ -215,6 +215,10 @@ pub async fn fetch_latest_adapter_version_from_github( pub trait DebugAdapter: 'static + Send + Sync { fn name(&self) -> DebugAdapterName; + fn language_name(&self) -> Option { + None + } + async fn get_binary( &self, delegate: &dyn DapDelegate, diff --git a/crates/dap_adapters/Cargo.toml b/crates/dap_adapters/Cargo.toml index 1672050ed720ad..b0b1ea7e29a38e 100644 --- a/crates/dap_adapters/Cargo.toml +++ b/crates/dap_adapters/Cargo.toml @@ -23,6 +23,7 @@ doctest = false anyhow.workspace = true async-trait.workspace = true dap.workspace = true +language.workspace = true paths.workspace = true regex.workspace = true serde.workspace = true diff --git a/crates/dap_adapters/src/python.rs b/crates/dap_adapters/src/python.rs index 61c140c0ca0aeb..669ab5d5b8f566 100644 --- a/crates/dap_adapters/src/python.rs +++ b/crates/dap_adapters/src/python.rs @@ -1,4 +1,5 @@ use dap::transport::{TcpTransport, Transport}; +use language::LanguageName; use std::{ffi::OsStr, net::Ipv4Addr, path::PathBuf, sync::Arc}; use crate::*; @@ -12,6 +13,7 @@ pub(crate) struct PythonDebugAdapter { impl PythonDebugAdapter { const ADAPTER_NAME: &'static str = "debugpy"; const ADAPTER_PATH: &'static str = "src/debugpy/adapter"; + const LANGUAGE_NAME: &'static str = "Python"; pub(crate) async fn new(host: &TCPHost) -> Result { Ok(PythonDebugAdapter { @@ -28,6 +30,10 @@ impl DebugAdapter for PythonDebugAdapter { DebugAdapterName(Self::ADAPTER_NAME.into()) } + fn language_name(&self) -> Option { + Some(LanguageName::new(Self::LANGUAGE_NAME)) + } + fn transport(&self) -> Arc { Arc::new(TcpTransport::new(self.host, self.port, self.timeout)) } @@ -92,10 +98,27 @@ impl DebugAdapter for PythonDebugAdapter { .ok_or_else(|| anyhow!("Debugpy directory not found"))? }; - let python_path = delegate - .toolchain() - .active_toolchain(worktree_id, language_name, cx) - .await?; + let python_path = if let Some(toolchain) = delegate.toolchain(&self.name()) { + Some(toolchain.path.to_string()) + } else { + let python_cmds = [ + OsStr::new("python3"), + OsStr::new("python"), + OsStr::new("py"), + ]; + python_cmds + .iter() + .filter_map(|cmd| { + delegate + .which(cmd) + .and_then(|path| path.to_str().map(|str| str.to_string())) + }) + .find(|_| true) + }; + + let python_path = python_path.ok_or(anyhow!( + "Failed to start debugger because python couldn't be found in PATH or toolchain" + ))?; Ok(DebugAdapterBinary { command: python_path, diff --git a/crates/debugger_ui/src/tests/attach_modal.rs b/crates/debugger_ui/src/tests/attach_modal.rs index dfdbfd0bddf52d..aec29fcffe618d 100644 --- a/crates/debugger_ui/src/tests/attach_modal.rs +++ b/crates/debugger_ui/src/tests/attach_modal.rs @@ -37,6 +37,7 @@ async fn test_direct_attach_to_process(executor: BackgroundExecutor, cx: &mut Te cwd: None, initialize_args: None, }, + None, cx, ) }) @@ -117,6 +118,7 @@ async fn test_show_attach_modal_and_select_process( cwd: None, initialize_args: None, }, + None, cx, ) }) @@ -222,6 +224,7 @@ async fn test_shutdown_session_when_modal_is_dismissed( cwd: None, initialize_args: None, }, + None, cx, ) }) diff --git a/crates/debugger_ui/src/tests/console.rs b/crates/debugger_ui/src/tests/console.rs index 8e9cf6912e5a01..07a27acb211bf4 100644 --- a/crates/debugger_ui/src/tests/console.rs +++ b/crates/debugger_ui/src/tests/console.rs @@ -35,6 +35,7 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp cwd: None, initialize_args: None, }, + None, cx, ) }) @@ -196,6 +197,7 @@ async fn test_grouped_output(executor: BackgroundExecutor, cx: &mut TestAppConte cwd: None, initialize_args: None, }, + None, cx, ) }) @@ -499,6 +501,7 @@ async fn test_evaluate_expression(executor: BackgroundExecutor, cx: &mut TestApp cwd: None, initialize_args: None, }, + None, cx, ) }) diff --git a/crates/debugger_ui/src/tests/debugger_panel.rs b/crates/debugger_ui/src/tests/debugger_panel.rs index 32fa8168b8d5b6..a2b22a047876c5 100644 --- a/crates/debugger_ui/src/tests/debugger_panel.rs +++ b/crates/debugger_ui/src/tests/debugger_panel.rs @@ -41,6 +41,7 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test cwd: None, initialize_args: None, }, + None, cx, ) }) @@ -158,6 +159,7 @@ async fn test_we_can_only_have_one_panel_per_debug_thread( cwd: None, initialize_args: None, }, + None, cx, ) }) @@ -306,6 +308,7 @@ async fn test_client_can_open_multiple_thread_panels( cwd: None, initialize_args: None, }, + None, cx, ) }) @@ -456,6 +459,7 @@ async fn test_handle_successful_run_in_terminal_reverse_request( cwd: None, initialize_args: None, }, + None, cx, ) }) @@ -562,6 +566,7 @@ async fn test_handle_error_run_in_terminal_reverse_request( cwd: None, initialize_args: None, }, + None, cx, ) }) @@ -659,6 +664,7 @@ async fn test_handle_start_debugging_reverse_request( cwd: None, initialize_args: None, }, + None, cx, ) }) @@ -786,6 +792,7 @@ async fn test_debug_panel_item_thread_status_reset_on_failure( cwd: None, initialize_args: None, }, + None, cx, ) }) diff --git a/crates/debugger_ui/src/tests/stack_frame_list.rs b/crates/debugger_ui/src/tests/stack_frame_list.rs index 220cb4184eacd8..9802bbbd1972b0 100644 --- a/crates/debugger_ui/src/tests/stack_frame_list.rs +++ b/crates/debugger_ui/src/tests/stack_frame_list.rs @@ -60,6 +60,7 @@ async fn test_fetch_initial_stack_frames_and_go_to_stack_frame( cwd: None, initialize_args: None, }, + None, cx, ) }) @@ -224,6 +225,7 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC cwd: None, initialize_args: None, }, + None, cx, ) }) diff --git a/crates/debugger_ui/src/tests/variable_list.rs b/crates/debugger_ui/src/tests/variable_list.rs index 25a866d6072ef6..0bfd60606f41e5 100644 --- a/crates/debugger_ui/src/tests/variable_list.rs +++ b/crates/debugger_ui/src/tests/variable_list.rs @@ -56,6 +56,7 @@ async fn test_basic_fetch_initial_scope_and_variables( cwd: None, initialize_args: None, }, + None, cx, ) }) @@ -280,6 +281,7 @@ async fn test_fetch_variables_for_multiple_scopes( cwd: None, initialize_args: None, }, + None, cx, ) }) @@ -549,6 +551,7 @@ async fn test_keyboard_navigation(executor: BackgroundExecutor, cx: &mut TestApp cwd: None, initialize_args: None, }, + None, cx, ) }) diff --git a/crates/project/src/dap_store.rs b/crates/project/src/dap_store.rs index a67d6c24b4d4e2..545422c8435224 100644 --- a/crates/project/src/dap_store.rs +++ b/crates/project/src/dap_store.rs @@ -41,6 +41,7 @@ use http_client::HttpClient; use language::{ proto::{deserialize_anchor, serialize_anchor as serialize_text_anchor}, Buffer, BufferSnapshot, LanguageRegistry, LanguageServerBinaryStatus, LanguageToolchainStore, + Toolchain, }; use lsp::LanguageServerName; use node_runtime::NodeRuntime; @@ -93,6 +94,7 @@ pub struct LocalDapStore { next_session_id: AtomicUsize, delegate: DapAdapterDelegate, environment: Model, + toolchain_store: Arc, sessions: HashMap>, client_by_session: HashMap, } @@ -181,9 +183,9 @@ impl DapStore { Some(node_runtime.clone()), fs.clone(), languages.clone(), - toolchain_store, Task::ready(None).shared(), ), + toolchain_store, client_by_session: Default::default(), }), downstream_client: None, @@ -533,6 +535,7 @@ impl DapStore { fn start_client_internal( &mut self, session_id: DebugSessionId, + worktree_id: Option, config: DebugAdapterConfig, cx: &mut ModelContext, ) -> Task>> { @@ -545,12 +548,31 @@ impl DapStore { adapter_delegate.refresh_shell_env_task(local_store.environment.update(cx, |env, cx| { env.get_environment(None, worktree_abs_path, cx) })); - let adapter_delegate = Arc::new(adapter_delegate); let client_id = local_store.next_client_id(); + let toolchains = local_store.toolchain_store.clone(); cx.spawn(|this, mut cx| async move { let adapter = build_adapter(&config.kind).await?; + let name = adapter.name(); + + let toolchain = if let Some(worktree_id) = worktree_id { + if let Some(language_name) = adapter.language_name() { + toolchains + .active_toolchain(worktree_id, language_name, &mut cx) + .await + } else { + None + } + } else { + None + }; + + if let Some(toolchain) = toolchain { + adapter_delegate.toolchains.insert(name, toolchain); + } + + let adapter_delegate = Arc::new(adapter_delegate); if !adapter.supports_attach() && matches!(config.request, DebugRequestType::Attach(_)) { bail!("Debug adapter does not support `attach` request"); @@ -620,6 +642,7 @@ impl DapStore { pub fn start_debug_session( &mut self, config: DebugAdapterConfig, + worktree_id: Option, cx: &mut ModelContext, ) -> Task, Arc)>> { let Some(local_store) = self.as_local() else { @@ -627,7 +650,8 @@ impl DapStore { }; let session_id = local_store.next_session_id(); - let start_client_task = self.start_client_internal(session_id, config.clone(), cx); + let start_client_task = + self.start_client_internal(session_id, worktree_id, config.clone(), cx); cx.spawn(|this, mut cx| async move { let session = cx.new_model(|_| DebugSession::new(session_id, config))?; @@ -2182,7 +2206,7 @@ pub struct DapAdapterDelegate { languages: Arc, node_runtime: Option, http_client: Option>, - toolchain_store: Arc, + toolchains: HashMap, updated_adapters: Arc>>, load_shell_env_task: Shared>>>, } @@ -2193,7 +2217,6 @@ impl DapAdapterDelegate { node_runtime: Option, fs: Arc, languages: Arc, - toolchain_store: Arc, load_shell_env_task: Shared>>>, ) -> Self { Self { @@ -2201,7 +2224,7 @@ impl DapAdapterDelegate { languages, http_client, node_runtime, - toolchain_store, + toolchains: Default::default(), load_shell_env_task, updated_adapters: Default::default(), } @@ -2255,7 +2278,7 @@ impl dap::adapters::DapDelegate for DapAdapterDelegate { task.await.unwrap_or_default() } - fn toolchain(&self) -> Arc { - self.toolchain_store.clone() + fn toolchain(&self, adapter_name: &DebugAdapterName) -> Option<&Toolchain> { + self.toolchains.get(&adapter_name) } } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a2458b55c438e1..3938237e853a23 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1288,8 +1288,19 @@ impl Project { cx: &mut ModelContext, ) { if let Some(config) = debug_task.debug_adapter_config() { + let worktree_id = maybe!({ + Some( + self.find_worktree(config.cwd.clone()?.as_path(), cx)? + .0 + .read(cx) + .id(), + ) + }); + self.dap_store.update(cx, |store, cx| { - store.start_debug_session(config, cx).detach_and_log_err(cx); + store + .start_debug_session(config, worktree_id, cx) + .detach_and_log_err(cx); }); } } @@ -1297,6 +1308,7 @@ impl Project { /// Get all serialized breakpoints that belong to a buffer /// /// # Parameters + /// None, /// `buffer_id`: The buffer id to get serialized breakpoints of /// `cx`: The context of the editor /// diff --git a/crates/remote_server/src/headless_project.rs b/crates/remote_server/src/headless_project.rs index 631b1e4e9e9319..f14e54a817f54f 100644 --- a/crates/remote_server/src/headless_project.rs +++ b/crates/remote_server/src/headless_project.rs @@ -81,6 +81,15 @@ impl HeadlessProject { let environment = project::ProjectEnvironment::new(&worktree_store, None, cx); + let toolchain_store = cx.new_model(|cx| { + ToolchainStore::local( + languages.clone(), + worktree_store.clone(), + environment.clone(), + cx, + ) + }); + let dap_store = cx.new_model(|cx| { DapStore::new_local( http_client.clone(), @@ -88,6 +97,7 @@ impl HeadlessProject { fs.clone(), languages.clone(), environment.clone(), + toolchain_store.read(cx).as_language_toolchain_store(), cx, ) }); @@ -107,15 +117,6 @@ impl HeadlessProject { ) }); - let toolchain_store = cx.new_model(|cx| { - ToolchainStore::local( - languages.clone(), - worktree_store.clone(), - environment.clone(), - cx, - ) - }); - let task_store = cx.new_model(|cx| { let mut task_store = TaskStore::local( fs.clone(), From 03626e83035c6b335b3d7c01671d9cdf96820b72 Mon Sep 17 00:00:00 2001 From: Remco Smits Date: Tue, 21 Jan 2025 20:17:52 +0100 Subject: [PATCH 03/22] Add python attach implementation --- crates/dap_adapters/src/python.rs | 38 +++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/crates/dap_adapters/src/python.rs b/crates/dap_adapters/src/python.rs index 669ab5d5b8f566..874e4ac9ff6913 100644 --- a/crates/dap_adapters/src/python.rs +++ b/crates/dap_adapters/src/python.rs @@ -1,6 +1,11 @@ -use dap::transport::{TcpTransport, Transport}; +use dap::{ + transport::{TcpTransport, Transport}, + DebugRequestType, +}; use language::LanguageName; -use std::{ffi::OsStr, net::Ipv4Addr, path::PathBuf, sync::Arc}; +use regex::Regex; +use std::{collections::HashMap, ffi::OsStr, net::Ipv4Addr, path::PathBuf, sync::Arc}; +use sysinfo::{Pid, Process}; use crate::*; @@ -133,10 +138,39 @@ impl DebugAdapter for PythonDebugAdapter { } fn request_args(&self, config: &DebugAdapterConfig) -> Value { + let pid = if let DebugRequestType::Attach(attach_config) = &config.request { + attach_config.process_id + } else { + None + }; + json!({ + "request": match config.request { + DebugRequestType::Launch => "launch", + DebugRequestType::Attach(_) => "attach", + }, + "processId": pid, "program": config.program, "subProcess": true, "cwd": config.cwd, }) } + + fn supports_attach(&self) -> bool { + true + } + + fn attach_processes<'a>( + &self, + processes: &'a HashMap, + ) -> Option> { + let regex = Regex::new(r"(?i)^(?:python3|python|py)(?:$|\b)").unwrap(); + + Some( + processes + .iter() + .filter(|(_, process)| regex.is_match(&process.name().to_string_lossy())) + .collect::>(), + ) + } } From fa6aec3e07355370cddccaf3cb982e9c8ad18049 Mon Sep 17 00:00:00 2001 From: Remco Smits Date: Wed, 22 Jan 2025 00:03:38 +0100 Subject: [PATCH 04/22] Wip require worktree to start debug session --- Cargo.lock | 1 + crates/dap/src/adapters.rs | 32 +++-- crates/dap_adapters/Cargo.toml | 7 +- crates/dap_adapters/src/custom.rs | 3 + crates/dap_adapters/src/gdb.rs | 3 + crates/dap_adapters/src/go.rs | 5 +- crates/dap_adapters/src/javascript.rs | 7 +- crates/dap_adapters/src/lldb.rs | 3 + crates/dap_adapters/src/php.rs | 7 +- crates/dap_adapters/src/python.rs | 41 ++----- crates/debugger_ui/src/tests.rs | 6 +- crates/debugger_ui/src/tests/attach_modal.rs | 11 +- crates/debugger_ui/src/tests/console.rs | 11 +- .../debugger_ui/src/tests/debugger_panel.rs | 41 ++++--- .../debugger_ui/src/tests/stack_frame_list.rs | 10 +- crates/debugger_ui/src/tests/variable_list.rs | 11 +- crates/project/src/dap_store.rs | 111 ++++++++---------- crates/project/src/project.rs | 30 +++-- 18 files changed, 175 insertions(+), 165 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ac59d9d50f1bcf..6fd7f3da0833a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3615,6 +3615,7 @@ dependencies = [ "anyhow", "async-trait", "dap", + "gpui", "language", "paths", "regex", diff --git a/crates/dap/src/adapters.rs b/crates/dap/src/adapters.rs index d9aa43aa004366..16fdce011beafc 100644 --- a/crates/dap/src/adapters.rs +++ b/crates/dap/src/adapters.rs @@ -7,12 +7,13 @@ use async_compression::futures::bufread::GzipDecoder; use async_tar::Archive; use async_trait::async_trait; use futures::io::BufReader; -use gpui::SharedString; +use gpui::{AsyncAppContext, SharedString}; pub use http_client::{github::latest_github_release, HttpClient}; -use language::{LanguageName, Toolchain}; +use language::LanguageToolchainStore; use node_runtime::NodeRuntime; use serde::{Deserialize, Serialize}; use serde_json::Value; +use settings::WorktreeId; use smol::{self, fs::File, lock::Mutex}; use std::{ collections::{HashMap, HashSet}, @@ -36,9 +37,10 @@ pub enum DapStatus { #[async_trait(?Send)] pub trait DapDelegate { - fn http_client(&self) -> Option>; + fn worktree_id(&self) -> WorktreeId; + fn http_client(&self) -> Arc; fn node_runtime(&self) -> Option; - fn toolchain(&self, adapter_name: &DebugAdapterName) -> Option<&Toolchain>; + fn toolchain_store(&self) -> Arc; fn fs(&self) -> Arc; fn updated_adapters(&self) -> Arc>>; fn update_status(&self, dap_name: DebugAdapterName, status: DapStatus); @@ -137,10 +139,8 @@ pub async fn download_adapter_from_github( &github_version.url, ); - let http_client = delegate + let mut response = delegate .http_client() - .ok_or_else(|| anyhow!("Failed to download adapter: couldn't connect to GitHub"))?; - let mut response = http_client .get(&github_version.url, Default::default(), true) .await .context("Error downloading release")?; @@ -193,15 +193,11 @@ pub async fn fetch_latest_adapter_version_from_github( github_repo: GithubRepo, delegate: &dyn DapDelegate, ) -> Result { - let http_client = delegate - .http_client() - .ok_or_else(|| anyhow!("Failed to download adapter: couldn't connect to GitHub"))?; - let release = latest_github_release( &format!("{}/{}", github_repo.repo_owner, github_repo.repo_name), false, false, - http_client, + delegate.http_client(), ) .await?; @@ -215,15 +211,12 @@ pub async fn fetch_latest_adapter_version_from_github( pub trait DebugAdapter: 'static + Send + Sync { fn name(&self) -> DebugAdapterName; - fn language_name(&self) -> Option { - None - } - async fn get_binary( &self, delegate: &dyn DapDelegate, config: &DebugAdapterConfig, user_installed_path: Option, + cx: &mut AsyncAppContext, ) -> Result { if delegate .updated_adapters() @@ -234,7 +227,7 @@ pub trait DebugAdapter: 'static + Send + Sync { log::info!("Using cached debug adapter binary {}", self.name()); if let Some(binary) = self - .get_installed_binary(delegate, &config, user_installed_path.clone()) + .get_installed_binary(delegate, &config, user_installed_path.clone(), cx) .await .log_err() { @@ -264,7 +257,7 @@ pub trait DebugAdapter: 'static + Send + Sync { .insert(self.name()); } - self.get_installed_binary(delegate, &config, user_installed_path) + self.get_installed_binary(delegate, &config, user_installed_path, cx) .await } @@ -289,6 +282,7 @@ pub trait DebugAdapter: 'static + Send + Sync { delegate: &dyn DapDelegate, config: &DebugAdapterConfig, user_installed_path: Option, + cx: &mut AsyncAppContext, ) -> Result; /// Should return base configuration to make the debug adapter work @@ -337,6 +331,7 @@ impl DebugAdapter for FakeAdapter { _delegate: &dyn DapDelegate, _config: &DebugAdapterConfig, _user_installed_path: Option, + _cx: &mut AsyncAppContext, ) -> Result { Ok(DebugAdapterBinary { command: "command".into(), @@ -366,6 +361,7 @@ impl DebugAdapter for FakeAdapter { _delegate: &dyn DapDelegate, _config: &DebugAdapterConfig, _user_installed_path: Option, + _cx: &mut AsyncAppContext, ) -> Result { unimplemented!("get installed binary"); } diff --git a/crates/dap_adapters/Cargo.toml b/crates/dap_adapters/Cargo.toml index b0b1ea7e29a38e..885f377d5a3149 100644 --- a/crates/dap_adapters/Cargo.toml +++ b/crates/dap_adapters/Cargo.toml @@ -7,9 +7,10 @@ license = "GPL-3.0-or-later" [features] test-support = [ - "util/test-support", - "task/test-support", "dap/test-support", + "gpui/test-support", + "task/test-support", + "util/test-support", ] [lints] @@ -23,6 +24,7 @@ doctest = false anyhow.workspace = true async-trait.workspace = true dap.workspace = true +gpui.workspace = true language.workspace = true paths.workspace = true regex.workspace = true @@ -34,5 +36,6 @@ util.workspace = true [dev-dependencies] dap = { workspace = true, features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } task = { workspace = true, features = ["test-support"] } util = { workspace = true, features = ["test-support"] } diff --git a/crates/dap_adapters/src/custom.rs b/crates/dap_adapters/src/custom.rs index ed54a828f882f6..4c3e59f3a832db 100644 --- a/crates/dap_adapters/src/custom.rs +++ b/crates/dap_adapters/src/custom.rs @@ -1,6 +1,7 @@ use std::{ffi::OsString, path::PathBuf, sync::Arc}; use dap::transport::{StdioTransport, TcpTransport, Transport}; +use gpui::AsyncAppContext; use serde_json::Value; use task::DebugAdapterConfig; @@ -44,6 +45,7 @@ impl DebugAdapter for CustomDebugAdapter { _: &dyn DapDelegate, config: &DebugAdapterConfig, _: Option, + _: &mut AsyncAppContext, ) -> Result { Ok(DebugAdapterBinary { command: self.custom_args.command.clone(), @@ -70,6 +72,7 @@ impl DebugAdapter for CustomDebugAdapter { _: &dyn DapDelegate, _: &DebugAdapterConfig, _: Option, + _: &mut AsyncAppContext, ) -> Result { bail!("Custom debug adapters cannot be installed") } diff --git a/crates/dap_adapters/src/gdb.rs b/crates/dap_adapters/src/gdb.rs index ed97bcec505da1..9050022f62766b 100644 --- a/crates/dap_adapters/src/gdb.rs +++ b/crates/dap_adapters/src/gdb.rs @@ -3,6 +3,7 @@ use std::ffi::OsStr; use anyhow::Result; use async_trait::async_trait; use dap::transport::{StdioTransport, Transport}; +use gpui::AsyncAppContext; use task::DebugAdapterConfig; use crate::*; @@ -32,6 +33,7 @@ impl DebugAdapter for GdbDebugAdapter { delegate: &dyn DapDelegate, config: &DebugAdapterConfig, user_installed_path: Option, + _: &mut AsyncAppContext, ) -> Result { let user_setting_path = user_installed_path .filter(|p| p.exists()) @@ -74,6 +76,7 @@ impl DebugAdapter for GdbDebugAdapter { _: &dyn DapDelegate, _: &DebugAdapterConfig, _: Option, + _: &mut AsyncAppContext, ) -> Result { unimplemented!("GDB cannot be installed by Zed (yet)") } diff --git a/crates/dap_adapters/src/go.rs b/crates/dap_adapters/src/go.rs index b1e8b34c526dc7..1c56de473ea4d1 100644 --- a/crates/dap_adapters/src/go.rs +++ b/crates/dap_adapters/src/go.rs @@ -1,4 +1,5 @@ use dap::transport::{TcpTransport, Transport}; +use gpui::AsyncAppContext; use std::{ffi::OsStr, net::Ipv4Addr, path::PathBuf, sync::Arc}; use crate::*; @@ -37,8 +38,9 @@ impl DebugAdapter for GoDebugAdapter { delegate: &dyn DapDelegate, config: &DebugAdapterConfig, user_installed_path: Option, + cx: &mut AsyncAppContext, ) -> Result { - self.get_installed_binary(delegate, config, user_installed_path) + self.get_installed_binary(delegate, config, user_installed_path, cx) .await } @@ -69,6 +71,7 @@ impl DebugAdapter for GoDebugAdapter { delegate: &dyn DapDelegate, config: &DebugAdapterConfig, _: Option, + _: &mut AsyncAppContext, ) -> Result { let delve_path = delegate .which(OsStr::new("dlv")) diff --git a/crates/dap_adapters/src/javascript.rs b/crates/dap_adapters/src/javascript.rs index da425ba70553d1..c1e26683022e92 100644 --- a/crates/dap_adapters/src/javascript.rs +++ b/crates/dap_adapters/src/javascript.rs @@ -1,5 +1,6 @@ use adapters::latest_github_release; use dap::transport::{TcpTransport, Transport}; +use gpui::AsyncAppContext; use regex::Regex; use std::{collections::HashMap, net::Ipv4Addr, path::PathBuf, sync::Arc}; use sysinfo::{Pid, Process}; @@ -40,14 +41,11 @@ impl DebugAdapter for JsDebugAdapter { &self, delegate: &dyn DapDelegate, ) -> Result { - let http_client = delegate - .http_client() - .ok_or_else(|| anyhow!("Failed to download adapter: couldn't connect to GitHub"))?; let release = latest_github_release( &format!("{}/{}", "microsoft", Self::ADAPTER_NAME), true, false, - http_client, + delegate.http_client(), ) .await?; @@ -70,6 +68,7 @@ impl DebugAdapter for JsDebugAdapter { delegate: &dyn DapDelegate, config: &DebugAdapterConfig, user_installed_path: Option, + _: &mut AsyncAppContext, ) -> Result { let adapter_path = if let Some(user_installed_path) = user_installed_path { user_installed_path diff --git a/crates/dap_adapters/src/lldb.rs b/crates/dap_adapters/src/lldb.rs index d9281a0d546367..c828bf52c9ec6a 100644 --- a/crates/dap_adapters/src/lldb.rs +++ b/crates/dap_adapters/src/lldb.rs @@ -3,6 +3,7 @@ use std::{ffi::OsStr, path::PathBuf, sync::Arc}; use anyhow::Result; use async_trait::async_trait; use dap::transport::{StdioTransport, Transport}; +use gpui::AsyncAppContext; use task::DebugAdapterConfig; use crate::*; @@ -32,6 +33,7 @@ impl DebugAdapter for LldbDebugAdapter { delegate: &dyn DapDelegate, config: &DebugAdapterConfig, user_installed_path: Option, + _: &mut AsyncAppContext, ) -> Result { let lldb_dap_path = if cfg!(target_os = "macos") { util::command::new_smol_command("xcrun") @@ -76,6 +78,7 @@ impl DebugAdapter for LldbDebugAdapter { _: &dyn DapDelegate, _: &DebugAdapterConfig, _: Option, + _: &mut AsyncAppContext, ) -> Result { unimplemented!("LLDB debug adapter cannot be installed by Zed (yet)") } diff --git a/crates/dap_adapters/src/php.rs b/crates/dap_adapters/src/php.rs index 1cba2b4ce2fa38..b06f25af51a906 100644 --- a/crates/dap_adapters/src/php.rs +++ b/crates/dap_adapters/src/php.rs @@ -1,5 +1,6 @@ use adapters::latest_github_release; use dap::transport::{TcpTransport, Transport}; +use gpui::AsyncAppContext; use std::{net::Ipv4Addr, path::PathBuf, sync::Arc}; use crate::*; @@ -37,14 +38,11 @@ impl DebugAdapter for PhpDebugAdapter { &self, delegate: &dyn DapDelegate, ) -> Result { - let http_client = delegate - .http_client() - .ok_or_else(|| anyhow!("Failed to download adapter: couldn't connect to GitHub"))?; let release = latest_github_release( &format!("{}/{}", "xdebug", Self::ADAPTER_NAME), true, false, - http_client, + delegate.http_client(), ) .await?; @@ -67,6 +65,7 @@ impl DebugAdapter for PhpDebugAdapter { delegate: &dyn DapDelegate, config: &DebugAdapterConfig, user_installed_path: Option, + _: &mut AsyncAppContext, ) -> Result { let adapter_path = if let Some(user_installed_path) = user_installed_path { user_installed_path diff --git a/crates/dap_adapters/src/python.rs b/crates/dap_adapters/src/python.rs index 669ab5d5b8f566..b5e42648dc300e 100644 --- a/crates/dap_adapters/src/python.rs +++ b/crates/dap_adapters/src/python.rs @@ -1,6 +1,6 @@ use dap::transport::{TcpTransport, Transport}; -use language::LanguageName; -use std::{ffi::OsStr, net::Ipv4Addr, path::PathBuf, sync::Arc}; +use gpui::AsyncAppContext; +use std::{net::Ipv4Addr, path::PathBuf, sync::Arc}; use crate::*; @@ -30,10 +30,6 @@ impl DebugAdapter for PythonDebugAdapter { DebugAdapterName(Self::ADAPTER_NAME.into()) } - fn language_name(&self) -> Option { - Some(LanguageName::new(Self::LANGUAGE_NAME)) - } - fn transport(&self) -> Arc { Arc::new(TcpTransport::new(self.host, self.port, self.timeout)) } @@ -84,6 +80,7 @@ impl DebugAdapter for PythonDebugAdapter { delegate: &dyn DapDelegate, config: &DebugAdapterConfig, user_installed_path: Option, + cx: &mut AsyncAppContext, ) -> Result { let debugpy_dir = if let Some(user_installed_path) = user_installed_path { user_installed_path @@ -98,30 +95,18 @@ impl DebugAdapter for PythonDebugAdapter { .ok_or_else(|| anyhow!("Debugpy directory not found"))? }; - let python_path = if let Some(toolchain) = delegate.toolchain(&self.name()) { - Some(toolchain.path.to_string()) - } else { - let python_cmds = [ - OsStr::new("python3"), - OsStr::new("python"), - OsStr::new("py"), - ]; - python_cmds - .iter() - .filter_map(|cmd| { - delegate - .which(cmd) - .and_then(|path| path.to_str().map(|str| str.to_string())) - }) - .find(|_| true) - }; - - let python_path = python_path.ok_or(anyhow!( - "Failed to start debugger because python couldn't be found in PATH or toolchain" - ))?; + let toolchain = delegate + .toolchain_store() + .active_toolchain( + delegate.worktree_id(), + language::LanguageName::new(Self::LANGUAGE_NAME), + cx, + ) + .await + .ok_or(anyhow!("failed to find active toolchain for Python"))?; Ok(DebugAdapterBinary { - command: python_path, + command: toolchain.path.to_string(), arguments: Some(vec![ debugpy_dir.join(Self::ADAPTER_PATH).into(), format!("--port={}", self.port).into(), diff --git a/crates/debugger_ui/src/tests.rs b/crates/debugger_ui/src/tests.rs index 2ab4e9740da399..954f3efd8f0045 100644 --- a/crates/debugger_ui/src/tests.rs +++ b/crates/debugger_ui/src/tests.rs @@ -1,5 +1,5 @@ use gpui::{Model, TestAppContext, View, WindowHandle}; -use project::Project; +use project::{Project, Worktree}; use settings::SettingsStore; use terminal_view::terminal_panel::TerminalPanel; use workspace::Workspace; @@ -71,3 +71,7 @@ pub fn active_debug_panel_item( }) .unwrap() } + +pub fn worktree_from_project(project: &Model, cx: &mut TestAppContext) -> Model { + project.read_with(cx, |project, cx| project.worktrees(cx).next().unwrap()) +} diff --git a/crates/debugger_ui/src/tests/attach_modal.rs b/crates/debugger_ui/src/tests/attach_modal.rs index aec29fcffe618d..102ad4ea99b4f7 100644 --- a/crates/debugger_ui/src/tests/attach_modal.rs +++ b/crates/debugger_ui/src/tests/attach_modal.rs @@ -10,7 +10,7 @@ use std::sync::{ Arc, }; use task::AttachConfig; -use tests::{init_test, init_test_workspace}; +use tests::{init_test, init_test_workspace, worktree_from_project}; #[gpui::test] async fn test_direct_attach_to_process(executor: BackgroundExecutor, cx: &mut TestAppContext) { @@ -23,6 +23,7 @@ async fn test_direct_attach_to_process(executor: BackgroundExecutor, cx: &mut Te let project = Project::test(fs, [], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); + let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { project.dap_store().update(cx, |store, cx| { @@ -37,7 +38,7 @@ async fn test_direct_attach_to_process(executor: BackgroundExecutor, cx: &mut Te cwd: None, initialize_args: None, }, - None, + &worktree, cx, ) }) @@ -106,6 +107,7 @@ async fn test_show_attach_modal_and_select_process( let project = Project::test(fs, [], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); + let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { project.dap_store().update(cx, |store, cx| { @@ -118,7 +120,7 @@ async fn test_show_attach_modal_and_select_process( cwd: None, initialize_args: None, }, - None, + &worktree, cx, ) }) @@ -212,6 +214,7 @@ async fn test_shutdown_session_when_modal_is_dismissed( let project = Project::test(fs, [], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); + let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { project.dap_store().update(cx, |store, cx| { @@ -224,7 +227,7 @@ async fn test_shutdown_session_when_modal_is_dismissed( cwd: None, initialize_args: None, }, - None, + &worktree, cx, ) }) diff --git a/crates/debugger_ui/src/tests/console.rs b/crates/debugger_ui/src/tests/console.rs index 2e02320acfc94b..1f8ed523b50aa1 100644 --- a/crates/debugger_ui/src/tests/console.rs +++ b/crates/debugger_ui/src/tests/console.rs @@ -10,7 +10,7 @@ use std::sync::{ atomic::{AtomicBool, Ordering}, Arc, Mutex, }; -use tests::{active_debug_panel_item, init_test, init_test_workspace}; +use tests::{active_debug_panel_item, init_test, init_test_workspace, worktree_from_project}; use unindent::Unindent as _; use variable_list::{VariableContainer, VariableListEntry}; @@ -23,6 +23,7 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp let project = Project::test(fs, [], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); + let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { project.dap_store().update(cx, |store, cx| { @@ -35,7 +36,7 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp cwd: None, initialize_args: None, }, - None, + &worktree, cx, ) }) @@ -185,6 +186,7 @@ async fn test_grouped_output(executor: BackgroundExecutor, cx: &mut TestAppConte let project = Project::test(fs, [], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); + let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { project.dap_store().update(cx, |store, cx| { @@ -197,7 +199,7 @@ async fn test_grouped_output(executor: BackgroundExecutor, cx: &mut TestAppConte cwd: None, initialize_args: None, }, - None, + &worktree, cx, ) }) @@ -489,6 +491,7 @@ async fn test_evaluate_expression(executor: BackgroundExecutor, cx: &mut TestApp let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); + let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { project.dap_store().update(cx, |store, cx| { @@ -501,7 +504,7 @@ async fn test_evaluate_expression(executor: BackgroundExecutor, cx: &mut TestApp cwd: None, initialize_args: None, }, - None, + &worktree, cx, ) }) diff --git a/crates/debugger_ui/src/tests/debugger_panel.rs b/crates/debugger_ui/src/tests/debugger_panel.rs index a779992bd8094c..9dd62d4eb062c0 100644 --- a/crates/debugger_ui/src/tests/debugger_panel.rs +++ b/crates/debugger_ui/src/tests/debugger_panel.rs @@ -27,7 +27,7 @@ use std::{ }, }; use terminal_view::{terminal_panel::TerminalPanel, TerminalView}; -use tests::{active_debug_panel_item, init_test, init_test_workspace}; +use tests::{active_debug_panel_item, init_test, init_test_workspace, worktree_from_project}; use workspace::{dock::Panel, Item}; #[gpui::test] @@ -39,6 +39,7 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test let project = Project::test(fs, [], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); + let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { project.dap_store().update(cx, |store, cx| { @@ -51,7 +52,7 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test cwd: None, initialize_args: None, }, - None, + &worktree, cx, ) }) @@ -157,6 +158,7 @@ async fn test_we_can_only_have_one_panel_per_debug_thread( let project = Project::test(fs, [], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); + let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { project.dap_store().update(cx, |store, cx| { @@ -169,7 +171,7 @@ async fn test_we_can_only_have_one_panel_per_debug_thread( cwd: None, initialize_args: None, }, - None, + &worktree, cx, ) }) @@ -306,6 +308,7 @@ async fn test_client_can_open_multiple_thread_panels( let project = Project::test(fs, [], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); + let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { project.dap_store().update(cx, |store, cx| { @@ -318,7 +321,7 @@ async fn test_client_can_open_multiple_thread_panels( cwd: None, initialize_args: None, }, - None, + &worktree, cx, ) }) @@ -457,6 +460,7 @@ async fn test_handle_successful_run_in_terminal_reverse_request( let project = Project::test(fs, [], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); + let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { project.dap_store().update(cx, |store, cx| { @@ -469,7 +473,7 @@ async fn test_handle_successful_run_in_terminal_reverse_request( cwd: None, initialize_args: None, }, - None, + &worktree, cx, ) }) @@ -564,6 +568,7 @@ async fn test_handle_error_run_in_terminal_reverse_request( let project = Project::test(fs, [], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); + let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { project.dap_store().update(cx, |store, cx| { @@ -576,7 +581,7 @@ async fn test_handle_error_run_in_terminal_reverse_request( cwd: None, initialize_args: None, }, - None, + &worktree, cx, ) }) @@ -662,6 +667,7 @@ async fn test_handle_start_debugging_reverse_request( let project = Project::test(fs, [], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); + let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { project.dap_store().update(cx, |store, cx| { @@ -674,7 +680,7 @@ async fn test_handle_start_debugging_reverse_request( cwd: None, initialize_args: None, }, - None, + &worktree, cx, ) }) @@ -792,6 +798,7 @@ async fn test_debug_panel_item_thread_status_reset_on_failure( let project = Project::test(fs, [], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); + let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { project.dap_store().update(cx, |store, cx| { @@ -804,7 +811,7 @@ async fn test_debug_panel_item_thread_status_reset_on_failure( cwd: None, initialize_args: None, }, - None, + &worktree, cx, ) }) @@ -996,12 +1003,9 @@ async fn test_send_breakpoints_when_editor_has_been_saved( let project = Project::test(fs, ["/a".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); + let worktree = worktree_from_project(&project, cx); let worktree_id = workspace - .update(cx, |workspace, cx| { - workspace.project().update(cx, |project, cx| { - project.worktrees(cx).next().unwrap().read(cx).id() - }) - }) + .update(cx, |_, cx| worktree.read(cx).id()) .unwrap(); let task = project.update(cx, |project, cx| { @@ -1015,6 +1019,7 @@ async fn test_send_breakpoints_when_editor_has_been_saved( cwd: None, initialize_args: None, }, + &worktree, cx, ) }) @@ -1187,12 +1192,9 @@ async fn test_it_send_breakpoint_request_if_breakpoint_buffer_is_unopened( let project = Project::test(fs, ["/a".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); + let worktree = worktree_from_project(&project, cx); let worktree_id = workspace - .update(cx, |workspace, cx| { - workspace.project().update(cx, |project, cx| { - project.worktrees(cx).next().unwrap().read(cx).id() - }) - }) + .update(cx, |_, cx| worktree.read(cx).id()) .unwrap(); let task = project.update(cx, |project, cx| { @@ -1206,6 +1208,7 @@ async fn test_it_send_breakpoint_request_if_breakpoint_buffer_is_unopened( cwd: None, initialize_args: None, }, + &worktree, cx, ) }) @@ -1322,6 +1325,7 @@ async fn test_debug_session_is_shutdown_when_attach_and_launch_request_fails( let project = Project::test(fs, [], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); + let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { project.dap_store().update(cx, |store, cx| { @@ -1334,6 +1338,7 @@ async fn test_debug_session_is_shutdown_when_attach_and_launch_request_fails( cwd: None, initialize_args: None, }, + &worktree, cx, ) }) diff --git a/crates/debugger_ui/src/tests/stack_frame_list.rs b/crates/debugger_ui/src/tests/stack_frame_list.rs index e91e8c29fb3254..d26cb4274600dd 100644 --- a/crates/debugger_ui/src/tests/stack_frame_list.rs +++ b/crates/debugger_ui/src/tests/stack_frame_list.rs @@ -1,7 +1,7 @@ use crate::{ debugger_panel::DebugPanel, stack_frame_list::StackFrameEntry, - tests::{active_debug_panel_item, init_test, init_test_workspace}, + tests::{active_debug_panel_item, init_test, init_test_workspace, worktree_from_project}, }; use dap::{ requests::{Disconnect, Initialize, Launch, StackTrace}, @@ -49,6 +49,7 @@ async fn test_fetch_initial_stack_frames_and_go_to_stack_frame( let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); + let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { project.dap_store().update(cx, |store, cx| { @@ -61,7 +62,7 @@ async fn test_fetch_initial_stack_frames_and_go_to_stack_frame( cwd: None, initialize_args: None, }, - None, + &worktree, cx, ) }) @@ -214,6 +215,7 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); + let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { project.dap_store().update(cx, |store, cx| { @@ -226,7 +228,7 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC cwd: None, initialize_args: None, }, - None, + &worktree, cx, ) }) @@ -462,6 +464,7 @@ async fn test_collapsed_entries(executor: BackgroundExecutor, cx: &mut TestAppCo let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); + let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { project.dap_store().update(cx, |store, cx| { @@ -474,6 +477,7 @@ async fn test_collapsed_entries(executor: BackgroundExecutor, cx: &mut TestAppCo cwd: None, initialize_args: None, }, + &worktree, cx, ) }) diff --git a/crates/debugger_ui/src/tests/variable_list.rs b/crates/debugger_ui/src/tests/variable_list.rs index 0bfd60606f41e5..5d7b30b7fb09a4 100644 --- a/crates/debugger_ui/src/tests/variable_list.rs +++ b/crates/debugger_ui/src/tests/variable_list.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use crate::{ - tests::{active_debug_panel_item, init_test, init_test_workspace}, + tests::{active_debug_panel_item, init_test, init_test_workspace, worktree_from_project}, variable_list::{CollapseSelectedEntry, ExpandSelectedEntry, VariableContainer}, }; use collections::HashMap; @@ -44,6 +44,7 @@ async fn test_basic_fetch_initial_scope_and_variables( let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); + let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { project.dap_store().update(cx, |store, cx| { @@ -56,7 +57,7 @@ async fn test_basic_fetch_initial_scope_and_variables( cwd: None, initialize_args: None, }, - None, + &worktree, cx, ) }) @@ -269,6 +270,7 @@ async fn test_fetch_variables_for_multiple_scopes( let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); + let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { project.dap_store().update(cx, |store, cx| { @@ -281,7 +283,7 @@ async fn test_fetch_variables_for_multiple_scopes( cwd: None, initialize_args: None, }, - None, + &worktree, cx, ) }) @@ -539,6 +541,7 @@ async fn test_keyboard_navigation(executor: BackgroundExecutor, cx: &mut TestApp let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); + let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { project.dap_store().update(cx, |store, cx| { @@ -551,7 +554,7 @@ async fn test_keyboard_navigation(executor: BackgroundExecutor, cx: &mut TestApp cwd: None, initialize_args: None, }, - None, + &worktree, cx, ) }) diff --git a/crates/project/src/dap_store.rs b/crates/project/src/dap_store.rs index aee5ba5da5d4ee..f107ee1ad01099 100644 --- a/crates/project/src/dap_store.rs +++ b/crates/project/src/dap_store.rs @@ -35,7 +35,6 @@ use dap::{ use dap_adapters::build_adapter; use fs::Fs; use futures::future::Shared; -use futures::FutureExt; use gpui::{ AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, SharedString, Task, }; @@ -43,7 +42,6 @@ use http_client::HttpClient; use language::{ proto::{deserialize_anchor, serialize_anchor as serialize_text_anchor}, Buffer, BufferSnapshot, LanguageRegistry, LanguageServerBinaryStatus, LanguageToolchainStore, - Toolchain, }; use lsp::LanguageServerName; use node_runtime::NodeRuntime; @@ -68,6 +66,7 @@ use std::{ use task::{AttachConfig, DebugAdapterConfig, DebugRequestType}; use text::Point; use util::{merge_json_value_into, ResultExt as _}; +use worktree::Worktree; pub enum DapStoreEvent { DebugClientStarted((DebugSessionId, DebugAdapterClientId)), @@ -95,10 +94,13 @@ pub enum DapStoreMode { } pub struct LocalDapStore { + fs: Arc, + node_runtime: NodeRuntime, next_client_id: AtomicUsize, next_session_id: AtomicUsize, - delegate: DapAdapterDelegate, + http_client: Arc, environment: Model, + language_registry: Arc, toolchain_store: Arc, sessions: HashMap>, client_by_session: HashMap, @@ -186,7 +188,7 @@ impl DapStore { http_client: Arc, node_runtime: NodeRuntime, fs: Arc, - languages: Arc, + language_registry: Arc, environment: Model, toolchain_store: Arc, cx: &mut ModelContext, @@ -195,18 +197,15 @@ impl DapStore { Self { mode: DapStoreMode::Local(LocalDapStore { + fs, environment, + http_client, + node_runtime, + toolchain_store, + language_registry, sessions: HashMap::default(), next_client_id: Default::default(), next_session_id: Default::default(), - delegate: DapAdapterDelegate::new( - Some(http_client.clone()), - Some(node_runtime.clone()), - fs.clone(), - languages.clone(), - Task::ready(None).shared(), - ), - toolchain_store, client_by_session: Default::default(), }), downstream_client: None, @@ -673,7 +672,7 @@ impl DapStore { fn start_client_internal( &mut self, session_id: DebugSessionId, - worktree_id: Option, + worktree: &Model, config: DebugAdapterConfig, cx: &mut ModelContext, ) -> Task>> { @@ -681,36 +680,23 @@ impl DapStore { return Task::ready(Err(anyhow!("cannot start client on remote side"))); }; - let mut adapter_delegate = local_store.delegate.clone(); let worktree_abs_path = config.cwd.as_ref().map(|p| Arc::from(p.as_path())); - adapter_delegate.refresh_shell_env_task(local_store.environment.update(cx, |env, cx| { - env.get_environment(None, worktree_abs_path, cx) - })); + let delegate = Arc::new(DapAdapterDelegate::new( + local_store.fs.clone(), + worktree.read(cx).id(), + local_store.http_client.clone(), + Some(local_store.node_runtime.clone()), + local_store.language_registry.clone(), + local_store.toolchain_store.clone(), + local_store.environment.update(cx, |env, cx| { + env.get_environment(None, worktree_abs_path, cx) + }), + )); let client_id = local_store.next_client_id(); - let toolchains = local_store.toolchain_store.clone(); cx.spawn(|this, mut cx| async move { let adapter = build_adapter(&config.kind).await?; - let name = adapter.name(); - - let toolchain = if let Some(worktree_id) = worktree_id { - if let Some(language_name) = adapter.language_name() { - toolchains - .active_toolchain(worktree_id, language_name, &mut cx) - .await - } else { - None - } - } else { - None - }; - - if let Some(toolchain) = toolchain { - adapter_delegate.toolchains.insert(name, toolchain); - } - - let adapter_delegate = Arc::new(adapter_delegate); if !adapter.supports_attach() && matches!(config.request, DebugRequestType::Attach(_)) { bail!("Debug adapter does not support `attach` request"); @@ -726,11 +712,11 @@ impl DapStore { })?; let (adapter, binary) = match adapter - .get_binary(adapter_delegate.as_ref(), &config, binary) + .get_binary(delegate.as_ref(), &config, binary, &mut cx) .await { Err(error) => { - adapter_delegate.update_status( + delegate.update_status( adapter.name(), DapStatus::Failed { error: error.to_string(), @@ -740,9 +726,9 @@ impl DapStore { return Err(error); } Ok(mut binary) => { - adapter_delegate.update_status(adapter.name(), DapStatus::None); + delegate.update_status(adapter.name(), DapStatus::None); - let shell_env = adapter_delegate.shell_env().await; + let shell_env = delegate.shell_env().await; let mut envs = binary.envs.unwrap_or_default(); envs.extend(shell_env); binary.envs = Some(envs); @@ -780,7 +766,7 @@ impl DapStore { pub fn start_debug_session( &mut self, config: DebugAdapterConfig, - worktree_id: Option, + worktree: &Model, cx: &mut ModelContext, ) -> Task, Arc)>> { let Some(local_store) = self.as_local() else { @@ -789,7 +775,7 @@ impl DapStore { let session_id = local_store.next_session_id(); let start_client_task = - self.start_client_internal(session_id, worktree_id, config.clone(), cx); + self.start_client_internal(session_id, worktree, config.clone(), cx); cx.spawn(|this, mut cx| async move { let session = cx.new_model(|_| DebugSession::new_local(session_id, config))?; @@ -2390,44 +2376,45 @@ impl SerializedBreakpoint { #[derive(Clone)] pub struct DapAdapterDelegate { fs: Arc, - languages: Arc, + worktree_id: WorktreeId, + http_client: Arc, node_runtime: Option, - http_client: Option>, - toolchains: HashMap, + language_registry: Arc, + toolchain_store: Arc, updated_adapters: Arc>>, load_shell_env_task: Shared>>>, } impl DapAdapterDelegate { pub fn new( - http_client: Option>, - node_runtime: Option, fs: Arc, - languages: Arc, + worktree_id: WorktreeId, + http_client: Arc, + node_runtime: Option, + language_registry: Arc, + toolchain_store: Arc, load_shell_env_task: Shared>>>, ) -> Self { Self { fs, - languages, + worktree_id, http_client, node_runtime, - toolchains: Default::default(), + toolchain_store, + language_registry, load_shell_env_task, updated_adapters: Default::default(), } } - - pub(crate) fn refresh_shell_env_task( - &mut self, - load_shell_env_task: Shared>>>, - ) { - self.load_shell_env_task = load_shell_env_task; - } } #[async_trait(?Send)] impl dap::adapters::DapDelegate for DapAdapterDelegate { - fn http_client(&self) -> Option> { + fn worktree_id(&self) -> WorktreeId { + self.worktree_id.clone() + } + + fn http_client(&self) -> Arc { self.http_client.clone() } @@ -2452,7 +2439,7 @@ impl dap::adapters::DapDelegate for DapAdapterDelegate { DapStatus::CheckingForUpdate => LanguageServerBinaryStatus::CheckingForUpdate, }; - self.languages + self.language_registry .update_dap_status(LanguageServerName(name), status); } @@ -2465,7 +2452,7 @@ impl dap::adapters::DapDelegate for DapAdapterDelegate { task.await.unwrap_or_default() } - fn toolchain(&self, adapter_name: &DebugAdapterName) -> Option<&Toolchain> { - self.toolchains.get(&adapter_name) + fn toolchain_store(&self) -> Arc { + self.toolchain_store.clone() } } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 99a1a915e909fd..0d332839f19f45 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1309,20 +1309,26 @@ impl Project { cx: &mut ModelContext, ) { if let Some(config) = debug_task.debug_adapter_config() { - let worktree_id = maybe!({ - Some( - self.find_worktree(config.cwd.clone()?.as_path(), cx)? - .0 - .read(cx) - .id(), - ) + let worktree = maybe!({ + if let Some(cwd) = &config.cwd { + Some(self.find_worktree(cwd.as_path(), cx)?.0) + } else { + self.worktrees(cx).next() + } }); - self.dap_store.update(cx, |store, cx| { - store - .start_debug_session(config, worktree_id, cx) - .detach_and_log_err(cx); - }); + if let Some(worktree) = &worktree { + self.dap_store.update(cx, |store, cx| { + store + .start_debug_session(config, worktree, cx) + .detach_and_log_err(cx); + }); + } else { + cx.emit(Event::Toast { + notification_id: "dap".into(), + message: "Failed to find a worktree".into(), + }); + } } } From 3e1131afeac622dfde42391d1acfddd0a3f4e310 Mon Sep 17 00:00:00 2001 From: Remco Smits Date: Wed, 22 Jan 2025 11:01:25 +0100 Subject: [PATCH 05/22] Move start debug session to project for determing the worktree --- crates/collab/src/tests/debug_panel_tests.rs | 197 ++++++------- crates/debugger_ui/src/tests/attach_modal.rs | 84 +++--- crates/debugger_ui/src/tests/console.rs | 80 +++--- .../debugger_ui/src/tests/debugger_panel.rs | 258 ++++++++---------- .../debugger_ui/src/tests/stack_frame_list.rs | 80 +++--- crates/debugger_ui/src/tests/variable_list.rs | 80 +++--- crates/project/src/project.rs | 44 ++- crates/tasks_ui/src/modal.rs | 21 +- 8 files changed, 368 insertions(+), 476 deletions(-) diff --git a/crates/collab/src/tests/debug_panel_tests.rs b/crates/collab/src/tests/debug_panel_tests.rs index abe5daae104eae..391b9f3bd7ba7f 100644 --- a/crates/collab/src/tests/debug_panel_tests.rs +++ b/crates/collab/src/tests/debug_panel_tests.rs @@ -88,20 +88,17 @@ async fn test_debug_panel_item_opens_on_remote( add_debugger_panel(&workspace_b, cx_b).await; let task = project_a.update(cx_a, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - dap::DebugAdapterConfig { - label: "test config".into(), - kind: dap::DebugAdapterKind::Fake, - request: dap::DebugRequestType::Launch, - program: None, - cwd: None, - initialize_args: None, - }, - None, - cx, - ) - }) + project.start_debug_session( + dap::DebugAdapterConfig { + label: "test config".into(), + kind: dap::DebugAdapterKind::Fake, + request: dap::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); @@ -201,20 +198,17 @@ async fn test_active_debug_panel_item_set_on_join_project( add_debugger_panel(&workspace_a, cx_a).await; let task = project_a.update(cx_a, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - dap::DebugAdapterConfig { - label: "test config".into(), - kind: dap::DebugAdapterKind::Fake, - request: dap::DebugRequestType::Launch, - program: None, - cwd: None, - initialize_args: None, - }, - None, - cx, - ) - }) + project.start_debug_session( + dap::DebugAdapterConfig { + label: "test config".into(), + kind: dap::DebugAdapterKind::Fake, + request: dap::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); @@ -345,20 +339,17 @@ async fn test_debug_panel_remote_button_presses( add_debugger_panel(&workspace_b, cx_b).await; let task = project_a.update(cx_a, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - dap::DebugAdapterConfig { - label: "test config".into(), - kind: dap::DebugAdapterKind::Fake, - request: dap::DebugRequestType::Launch, - program: None, - cwd: None, - initialize_args: None, - }, - None, - cx, - ) - }) + project.start_debug_session( + dap::DebugAdapterConfig { + label: "test config".into(), + kind: dap::DebugAdapterKind::Fake, + request: dap::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (_, client) = task.await.unwrap(); @@ -709,20 +700,17 @@ async fn test_restart_stack_frame(cx_a: &mut TestAppContext, cx_b: &mut TestAppC add_debugger_panel(&workspace_b, cx_b).await; let task = project_a.update(cx_a, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - dap::DebugAdapterConfig { - label: "test config".into(), - kind: dap::DebugAdapterKind::Fake, - request: dap::DebugRequestType::Launch, - program: None, - cwd: None, - initialize_args: None, - }, - None, - cx, - ) - }) + project.start_debug_session( + dap::DebugAdapterConfig { + label: "test config".into(), + kind: dap::DebugAdapterKind::Fake, + request: dap::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); @@ -895,20 +883,17 @@ async fn test_updated_breakpoints_send_to_dap( add_debugger_panel(&workspace_b, cx_b).await; let task = project_a.update(cx_a, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - dap::DebugAdapterConfig { - label: "test config".into(), - kind: dap::DebugAdapterKind::Fake, - request: dap::DebugRequestType::Launch, - program: None, - cwd: None, - initialize_args: None, - }, - None, - cx, - ) - }) + project.start_debug_session( + dap::DebugAdapterConfig { + label: "test config".into(), + kind: dap::DebugAdapterKind::Fake, + request: dap::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); @@ -1149,19 +1134,17 @@ async fn test_module_list( add_debugger_panel(&workspace_b, cx_b).await; let task = project_a.update(cx_a, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - dap::DebugAdapterConfig { - label: "test config".into(), - kind: dap::DebugAdapterKind::Fake, - request: dap::DebugRequestType::Launch, - program: None, - cwd: None, - initialize_args: None, - }, - cx, - ) - }) + project.start_debug_session( + dap::DebugAdapterConfig { + label: "test config".into(), + kind: dap::DebugAdapterKind::Fake, + request: dap::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); @@ -1418,19 +1401,17 @@ async fn test_variable_list( add_debugger_panel(&workspace_b, cx_b).await; let task = project_a.update(cx_a, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - dap::DebugAdapterConfig { - label: "test config".into(), - kind: dap::DebugAdapterKind::Fake, - request: dap::DebugRequestType::Launch, - program: None, - cwd: None, - initialize_args: None, - }, - cx, - ) - }) + project.start_debug_session( + dap::DebugAdapterConfig { + label: "test config".into(), + kind: dap::DebugAdapterKind::Fake, + request: dap::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); @@ -1936,19 +1917,17 @@ async fn test_ignore_breakpoints( cx_b.run_until_parked(); let task = project_a.update(cx_a, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - dap::DebugAdapterConfig { - label: "test config".into(), - kind: dap::DebugAdapterKind::Fake, - request: dap::DebugRequestType::Launch, - program: None, - cwd: None, - initialize_args: None, - }, - cx, - ) - }) + project.start_debug_session( + dap::DebugAdapterConfig { + label: "test config".into(), + kind: dap::DebugAdapterKind::Fake, + request: dap::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); diff --git a/crates/debugger_ui/src/tests/attach_modal.rs b/crates/debugger_ui/src/tests/attach_modal.rs index 102ad4ea99b4f7..49bac3f66995e6 100644 --- a/crates/debugger_ui/src/tests/attach_modal.rs +++ b/crates/debugger_ui/src/tests/attach_modal.rs @@ -10,7 +10,7 @@ use std::sync::{ Arc, }; use task::AttachConfig; -use tests::{init_test, init_test_workspace, worktree_from_project}; +use tests::{init_test, init_test_workspace}; #[gpui::test] async fn test_direct_attach_to_process(executor: BackgroundExecutor, cx: &mut TestAppContext) { @@ -23,25 +23,21 @@ async fn test_direct_attach_to_process(executor: BackgroundExecutor, cx: &mut Te let project = Project::test(fs, [], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - task::DebugAdapterConfig { - label: "test config".into(), - kind: task::DebugAdapterKind::Fake, - request: task::DebugRequestType::Attach(AttachConfig { - process_id: Some(10), - }), - program: None, - cwd: None, - initialize_args: None, - }, - &worktree, - cx, - ) - }) + project.start_debug_session( + task::DebugAdapterConfig { + label: "test config".into(), + kind: task::DebugAdapterKind::Fake, + request: task::DebugRequestType::Attach(AttachConfig { + process_id: Some(10), + }), + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); @@ -107,23 +103,19 @@ async fn test_show_attach_modal_and_select_process( let project = Project::test(fs, [], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - task::DebugAdapterConfig { - label: "test config".into(), - kind: task::DebugAdapterKind::Fake, - request: task::DebugRequestType::Attach(AttachConfig { process_id: None }), - program: None, - cwd: None, - initialize_args: None, - }, - &worktree, - cx, - ) - }) + project.start_debug_session( + task::DebugAdapterConfig { + label: "test config".into(), + kind: task::DebugAdapterKind::Fake, + request: task::DebugRequestType::Attach(AttachConfig { process_id: None }), + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); @@ -214,23 +206,19 @@ async fn test_shutdown_session_when_modal_is_dismissed( let project = Project::test(fs, [], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - task::DebugAdapterConfig { - label: "test config".into(), - kind: task::DebugAdapterKind::Fake, - request: task::DebugRequestType::Attach(AttachConfig { process_id: None }), - program: None, - cwd: None, - initialize_args: None, - }, - &worktree, - cx, - ) - }) + project.start_debug_session( + task::DebugAdapterConfig { + label: "test config".into(), + kind: task::DebugAdapterKind::Fake, + request: task::DebugRequestType::Attach(AttachConfig { process_id: None }), + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); diff --git a/crates/debugger_ui/src/tests/console.rs b/crates/debugger_ui/src/tests/console.rs index 1f8ed523b50aa1..6827b06507bf2d 100644 --- a/crates/debugger_ui/src/tests/console.rs +++ b/crates/debugger_ui/src/tests/console.rs @@ -10,7 +10,7 @@ use std::sync::{ atomic::{AtomicBool, Ordering}, Arc, Mutex, }; -use tests::{active_debug_panel_item, init_test, init_test_workspace, worktree_from_project}; +use tests::{active_debug_panel_item, init_test, init_test_workspace}; use unindent::Unindent as _; use variable_list::{VariableContainer, VariableListEntry}; @@ -23,23 +23,19 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp let project = Project::test(fs, [], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - task::DebugAdapterConfig { - label: "test config".into(), - kind: task::DebugAdapterKind::Fake, - request: task::DebugRequestType::Launch, - program: None, - cwd: None, - initialize_args: None, - }, - &worktree, - cx, - ) - }) + project.start_debug_session( + task::DebugAdapterConfig { + label: "test config".into(), + kind: task::DebugAdapterKind::Fake, + request: task::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); @@ -186,23 +182,19 @@ async fn test_grouped_output(executor: BackgroundExecutor, cx: &mut TestAppConte let project = Project::test(fs, [], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - task::DebugAdapterConfig { - label: "test config".into(), - kind: task::DebugAdapterKind::Fake, - request: task::DebugRequestType::Launch, - program: None, - cwd: None, - initialize_args: None, - }, - &worktree, - cx, - ) - }) + project.start_debug_session( + task::DebugAdapterConfig { + label: "test config".into(), + kind: task::DebugAdapterKind::Fake, + request: task::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); @@ -491,23 +483,19 @@ async fn test_evaluate_expression(executor: BackgroundExecutor, cx: &mut TestApp let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - task::DebugAdapterConfig { - label: "test config".into(), - kind: task::DebugAdapterKind::Fake, - request: task::DebugRequestType::Launch, - program: None, - cwd: None, - initialize_args: None, - }, - &worktree, - cx, - ) - }) + project.start_debug_session( + task::DebugAdapterConfig { + label: "test config".into(), + kind: task::DebugAdapterKind::Fake, + request: task::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); diff --git a/crates/debugger_ui/src/tests/debugger_panel.rs b/crates/debugger_ui/src/tests/debugger_panel.rs index 9dd62d4eb062c0..08bfc4a97f09af 100644 --- a/crates/debugger_ui/src/tests/debugger_panel.rs +++ b/crates/debugger_ui/src/tests/debugger_panel.rs @@ -39,23 +39,19 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test let project = Project::test(fs, [], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - task::DebugAdapterConfig { - label: "test config".into(), - kind: task::DebugAdapterKind::Fake, - request: task::DebugRequestType::Launch, - program: None, - cwd: None, - initialize_args: None, - }, - &worktree, - cx, - ) - }) + project.start_debug_session( + task::DebugAdapterConfig { + label: "test config".into(), + kind: task::DebugAdapterKind::Fake, + request: task::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); @@ -158,23 +154,19 @@ async fn test_we_can_only_have_one_panel_per_debug_thread( let project = Project::test(fs, [], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - task::DebugAdapterConfig { - label: "test config".into(), - kind: task::DebugAdapterKind::Fake, - request: task::DebugRequestType::Launch, - program: None, - cwd: None, - initialize_args: None, - }, - &worktree, - cx, - ) - }) + project.start_debug_session( + task::DebugAdapterConfig { + label: "test config".into(), + kind: task::DebugAdapterKind::Fake, + request: task::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); @@ -308,23 +300,19 @@ async fn test_client_can_open_multiple_thread_panels( let project = Project::test(fs, [], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - task::DebugAdapterConfig { - label: "test config".into(), - kind: task::DebugAdapterKind::Fake, - request: task::DebugRequestType::Launch, - program: None, - cwd: None, - initialize_args: None, - }, - &worktree, - cx, - ) - }) + project.start_debug_session( + task::DebugAdapterConfig { + label: "test config".into(), + kind: task::DebugAdapterKind::Fake, + request: task::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); @@ -460,23 +448,19 @@ async fn test_handle_successful_run_in_terminal_reverse_request( let project = Project::test(fs, [], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - task::DebugAdapterConfig { - label: "test config".into(), - kind: task::DebugAdapterKind::Fake, - request: task::DebugRequestType::Launch, - program: None, - cwd: None, - initialize_args: None, - }, - &worktree, - cx, - ) - }) + project.start_debug_session( + task::DebugAdapterConfig { + label: "test config".into(), + kind: task::DebugAdapterKind::Fake, + request: task::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); @@ -568,23 +552,19 @@ async fn test_handle_error_run_in_terminal_reverse_request( let project = Project::test(fs, [], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - task::DebugAdapterConfig { - label: "test config".into(), - kind: task::DebugAdapterKind::Fake, - request: task::DebugRequestType::Launch, - program: None, - cwd: None, - initialize_args: None, - }, - &worktree, - cx, - ) - }) + project.start_debug_session( + task::DebugAdapterConfig { + label: "test config".into(), + kind: task::DebugAdapterKind::Fake, + request: task::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); @@ -667,23 +647,19 @@ async fn test_handle_start_debugging_reverse_request( let project = Project::test(fs, [], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - task::DebugAdapterConfig { - label: "test config".into(), - kind: task::DebugAdapterKind::Fake, - request: task::DebugRequestType::Launch, - program: None, - cwd: None, - initialize_args: None, - }, - &worktree, - cx, - ) - }) + project.start_debug_session( + task::DebugAdapterConfig { + label: "test config".into(), + kind: task::DebugAdapterKind::Fake, + request: task::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); @@ -798,23 +774,19 @@ async fn test_debug_panel_item_thread_status_reset_on_failure( let project = Project::test(fs, [], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - task::DebugAdapterConfig { - label: "test config".into(), - kind: task::DebugAdapterKind::Fake, - request: task::DebugRequestType::Launch, - program: None, - cwd: None, - initialize_args: None, - }, - &worktree, - cx, - ) - }) + project.start_debug_session( + task::DebugAdapterConfig { + label: "test config".into(), + kind: task::DebugAdapterKind::Fake, + request: task::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); @@ -1009,20 +981,17 @@ async fn test_send_breakpoints_when_editor_has_been_saved( .unwrap(); let task = project.update(cx, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - task::DebugAdapterConfig { - label: "test config".into(), - kind: task::DebugAdapterKind::Fake, - request: task::DebugRequestType::Launch, - program: None, - cwd: None, - initialize_args: None, - }, - &worktree, - cx, - ) - }) + project.start_debug_session( + task::DebugAdapterConfig { + label: "test config".into(), + kind: task::DebugAdapterKind::Fake, + request: task::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); @@ -1198,20 +1167,17 @@ async fn test_it_send_breakpoint_request_if_breakpoint_buffer_is_unopened( .unwrap(); let task = project.update(cx, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - task::DebugAdapterConfig { - label: "test config".into(), - kind: task::DebugAdapterKind::Fake, - request: task::DebugRequestType::Launch, - program: None, - cwd: None, - initialize_args: None, - }, - &worktree, - cx, - ) - }) + project.start_debug_session( + task::DebugAdapterConfig { + label: "test config".into(), + kind: task::DebugAdapterKind::Fake, + request: task::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); @@ -1325,23 +1291,19 @@ async fn test_debug_session_is_shutdown_when_attach_and_launch_request_fails( let project = Project::test(fs, [], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - task::DebugAdapterConfig { - label: "test config".into(), - kind: task::DebugAdapterKind::Fake, - request: task::DebugRequestType::Launch, - program: None, - cwd: None, - initialize_args: None, - }, - &worktree, - cx, - ) - }) + project.start_debug_session( + task::DebugAdapterConfig { + label: "test config".into(), + kind: task::DebugAdapterKind::Fake, + request: task::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); diff --git a/crates/debugger_ui/src/tests/stack_frame_list.rs b/crates/debugger_ui/src/tests/stack_frame_list.rs index d26cb4274600dd..f42461db64194f 100644 --- a/crates/debugger_ui/src/tests/stack_frame_list.rs +++ b/crates/debugger_ui/src/tests/stack_frame_list.rs @@ -1,7 +1,7 @@ use crate::{ debugger_panel::DebugPanel, stack_frame_list::StackFrameEntry, - tests::{active_debug_panel_item, init_test, init_test_workspace, worktree_from_project}, + tests::{active_debug_panel_item, init_test, init_test_workspace}, }; use dap::{ requests::{Disconnect, Initialize, Launch, StackTrace}, @@ -49,23 +49,19 @@ async fn test_fetch_initial_stack_frames_and_go_to_stack_frame( let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - task::DebugAdapterConfig { - label: "test config".into(), - kind: task::DebugAdapterKind::Fake, - request: task::DebugRequestType::Launch, - program: None, - cwd: None, - initialize_args: None, - }, - &worktree, - cx, - ) - }) + project.start_debug_session( + task::DebugAdapterConfig { + label: "test config".into(), + kind: task::DebugAdapterKind::Fake, + request: task::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); @@ -215,23 +211,19 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - task::DebugAdapterConfig { - label: "test config".into(), - kind: task::DebugAdapterKind::Fake, - request: task::DebugRequestType::Launch, - program: None, - cwd: None, - initialize_args: None, - }, - &worktree, - cx, - ) - }) + project.start_debug_session( + task::DebugAdapterConfig { + label: "test config".into(), + kind: task::DebugAdapterKind::Fake, + request: task::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); @@ -464,23 +456,19 @@ async fn test_collapsed_entries(executor: BackgroundExecutor, cx: &mut TestAppCo let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - task::DebugAdapterConfig { - label: "test config".into(), - kind: task::DebugAdapterKind::Fake, - request: task::DebugRequestType::Launch, - program: None, - cwd: None, - initialize_args: None, - }, - &worktree, - cx, - ) - }) + project.start_debug_session( + task::DebugAdapterConfig { + label: "test config".into(), + kind: task::DebugAdapterKind::Fake, + request: task::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); diff --git a/crates/debugger_ui/src/tests/variable_list.rs b/crates/debugger_ui/src/tests/variable_list.rs index 5d7b30b7fb09a4..59c32424556082 100644 --- a/crates/debugger_ui/src/tests/variable_list.rs +++ b/crates/debugger_ui/src/tests/variable_list.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use crate::{ - tests::{active_debug_panel_item, init_test, init_test_workspace, worktree_from_project}, + tests::{active_debug_panel_item, init_test, init_test_workspace}, variable_list::{CollapseSelectedEntry, ExpandSelectedEntry, VariableContainer}, }; use collections::HashMap; @@ -44,23 +44,19 @@ async fn test_basic_fetch_initial_scope_and_variables( let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - task::DebugAdapterConfig { - label: "test config".into(), - kind: task::DebugAdapterKind::Fake, - request: task::DebugRequestType::Launch, - program: None, - cwd: None, - initialize_args: None, - }, - &worktree, - cx, - ) - }) + project.start_debug_session( + task::DebugAdapterConfig { + label: "test config".into(), + kind: task::DebugAdapterKind::Fake, + request: task::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); @@ -270,23 +266,19 @@ async fn test_fetch_variables_for_multiple_scopes( let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - task::DebugAdapterConfig { - label: "test config".into(), - kind: task::DebugAdapterKind::Fake, - request: task::DebugRequestType::Launch, - program: None, - cwd: None, - initialize_args: None, - }, - &worktree, - cx, - ) - }) + project.start_debug_session( + task::DebugAdapterConfig { + label: "test config".into(), + kind: task::DebugAdapterKind::Fake, + request: task::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); @@ -541,23 +533,19 @@ async fn test_keyboard_navigation(executor: BackgroundExecutor, cx: &mut TestApp let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); - let worktree = worktree_from_project(&project, cx); let task = project.update(cx, |project, cx| { - project.dap_store().update(cx, |store, cx| { - store.start_debug_session( - task::DebugAdapterConfig { - label: "test config".into(), - kind: task::DebugAdapterKind::Fake, - request: task::DebugRequestType::Launch, - program: None, - cwd: None, - initialize_args: None, - }, - &worktree, - cx, - ) - }) + project.start_debug_session( + task::DebugAdapterConfig { + label: "test config".into(), + kind: task::DebugAdapterKind::Fake, + request: task::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) }); let (session, client) = task.await.unwrap(); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 0d332839f19f45..8f7ae15f0d73e1 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -37,7 +37,8 @@ use dap::{ client::{DebugAdapterClient, DebugAdapterClientId}, debugger_settings::DebuggerSettings, messages::Message, - session::DebugSessionId, + session::{DebugSession, DebugSessionId}, + DebugAdapterConfig, }; use collections::{BTreeSet, HashMap, HashSet}; @@ -1303,33 +1304,26 @@ impl Project { }) } - pub fn start_debug_adapter_client_from_task( + pub fn start_debug_session( &mut self, - debug_task: task::ResolvedTask, + config: DebugAdapterConfig, cx: &mut ModelContext, - ) { - if let Some(config) = debug_task.debug_adapter_config() { - let worktree = maybe!({ - if let Some(cwd) = &config.cwd { - Some(self.find_worktree(cwd.as_path(), cx)?.0) - } else { - self.worktrees(cx).next() - } - }); - - if let Some(worktree) = &worktree { - self.dap_store.update(cx, |store, cx| { - store - .start_debug_session(config, worktree, cx) - .detach_and_log_err(cx); - }); + ) -> Task, Arc)>> { + let worktree = maybe!({ + if let Some(cwd) = &config.cwd { + Some(self.find_worktree(cwd.as_path(), cx)?.0) } else { - cx.emit(Event::Toast { - notification_id: "dap".into(), - message: "Failed to find a worktree".into(), - }); + self.worktrees(cx).next() } - } + }); + + let Some(worktree) = &worktree else { + return Task::ready(Err(anyhow!("Failed to find a worktree"))); + }; + + self.dap_store.update(cx, |dap_store, cx| { + dap_store.start_debug_session(config, worktree, cx) + }) } /// Get all serialized breakpoints that belong to a buffer @@ -1337,9 +1331,7 @@ impl Project { /// # Parameters /// None, /// `buffer_id`: The buffer id to get serialized breakpoints of - /// `cx`: The context of the editor /// - /// # Return /// `None`: If the buffer associated with buffer id doesn't exist or this editor /// doesn't belong to a project /// diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs index b536ef9d859ddc..12a9b125a545bd 100644 --- a/crates/tasks_ui/src/modal.rs +++ b/crates/tasks_ui/src/modal.rs @@ -317,10 +317,13 @@ impl PickerDelegate for TasksModalDelegate { omit_history_entry, cx, ), - // This would allow users to access to debug history and other issues - TaskType::Debug(_) => workspace.project().update(cx, |project, cx| { - project.start_debug_adapter_client_from_task(task, cx) - }), + TaskType::Debug(debug_config) => { + workspace.project().update(cx, |project, cx| { + project + .start_debug_session(debug_config, cx) + .detach_and_log_err(cx); + }) + } }; }) .ok(); @@ -478,9 +481,13 @@ impl PickerDelegate for TasksModalDelegate { ), // TODO: Should create a schedule_resolved_debug_task function // This would allow users to access to debug history and other issues - TaskType::Debug(_) => workspace.project().update(cx, |project, cx| { - project.start_debug_adapter_client_from_task(task, cx) - }), + TaskType::Debug(debug_config) => { + workspace.project().update(cx, |project, cx| { + project + .start_debug_session(debug_config, cx) + .detach_and_log_err(cx); + }) + } }; }) .ok(); From f16a458ad988f451dd584ebdc8538125ad05a129 Mon Sep 17 00:00:00 2001 From: Remco Smits Date: Wed, 22 Jan 2025 11:07:52 +0100 Subject: [PATCH 06/22] Make all tests pass again --- crates/debugger_ui/src/tests/attach_modal.rs | 30 +++++- crates/debugger_ui/src/tests/console.rs | 20 +++- .../debugger_ui/src/tests/debugger_panel.rs | 94 ++++++++++++++++--- 3 files changed, 124 insertions(+), 20 deletions(-) diff --git a/crates/debugger_ui/src/tests/attach_modal.rs b/crates/debugger_ui/src/tests/attach_modal.rs index 49bac3f66995e6..8af6b07b882f43 100644 --- a/crates/debugger_ui/src/tests/attach_modal.rs +++ b/crates/debugger_ui/src/tests/attach_modal.rs @@ -20,7 +20,15 @@ async fn test_direct_attach_to_process(executor: BackgroundExecutor, cx: &mut Te let fs = FakeFs::new(executor.clone()); - let project = Project::test(fs, [], cx).await; + fs.insert_tree( + "/project", + json!({ + "main.rs": "First line\nSecond line\nThird line\nFourth line", + }), + ) + .await; + + let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); @@ -100,7 +108,15 @@ async fn test_show_attach_modal_and_select_process( let fs = FakeFs::new(executor.clone()); - let project = Project::test(fs, [], cx).await; + fs.insert_tree( + "/project", + json!({ + "main.rs": "First line\nSecond line\nThird line\nFourth line", + }), + ) + .await; + + let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); @@ -203,7 +219,15 @@ async fn test_shutdown_session_when_modal_is_dismissed( let fs = FakeFs::new(executor.clone()); - let project = Project::test(fs, [], cx).await; + fs.insert_tree( + "/project", + json!({ + "main.rs": "First line\nSecond line\nThird line\nFourth line", + }), + ) + .await; + + let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); diff --git a/crates/debugger_ui/src/tests/console.rs b/crates/debugger_ui/src/tests/console.rs index 6827b06507bf2d..3ae64700c274e6 100644 --- a/crates/debugger_ui/src/tests/console.rs +++ b/crates/debugger_ui/src/tests/console.rs @@ -20,7 +20,15 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp let fs = FakeFs::new(executor.clone()); - let project = Project::test(fs, [], cx).await; + fs.insert_tree( + "/project", + json!({ + "main.rs": "First line\nSecond line\nThird line\nFourth line", + }), + ) + .await; + + let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); @@ -179,7 +187,15 @@ async fn test_grouped_output(executor: BackgroundExecutor, cx: &mut TestAppConte let fs = FakeFs::new(executor.clone()); - let project = Project::test(fs, [], cx).await; + fs.insert_tree( + "/project", + json!({ + "main.rs": "First line\nSecond line\nThird line\nFourth line", + }), + ) + .await; + + let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); diff --git a/crates/debugger_ui/src/tests/debugger_panel.rs b/crates/debugger_ui/src/tests/debugger_panel.rs index 08bfc4a97f09af..62e397e2ec3de6 100644 --- a/crates/debugger_ui/src/tests/debugger_panel.rs +++ b/crates/debugger_ui/src/tests/debugger_panel.rs @@ -36,7 +36,15 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test let fs = FakeFs::new(executor.clone()); - let project = Project::test(fs, [], cx).await; + fs.insert_tree( + "/project", + json!({ + "main.rs": "First line\nSecond line\nThird line\nFourth line", + }), + ) + .await; + + let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); @@ -151,7 +159,15 @@ async fn test_we_can_only_have_one_panel_per_debug_thread( let fs = FakeFs::new(executor.clone()); - let project = Project::test(fs, [], cx).await; + fs.insert_tree( + "/project", + json!({ + "main.rs": "First line\nSecond line\nThird line\nFourth line", + }), + ) + .await; + + let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); @@ -297,7 +313,15 @@ async fn test_client_can_open_multiple_thread_panels( let fs = FakeFs::new(executor.clone()); - let project = Project::test(fs, [], cx).await; + fs.insert_tree( + "/project", + json!({ + "main.rs": "First line\nSecond line\nThird line\nFourth line", + }), + ) + .await; + + let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); @@ -445,7 +469,15 @@ async fn test_handle_successful_run_in_terminal_reverse_request( let fs = FakeFs::new(executor.clone()); - let project = Project::test(fs, [], cx).await; + fs.insert_tree( + "/project", + json!({ + "main.rs": "First line\nSecond line\nThird line\nFourth line", + }), + ) + .await; + + let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); @@ -549,7 +581,15 @@ async fn test_handle_error_run_in_terminal_reverse_request( let fs = FakeFs::new(executor.clone()); - let project = Project::test(fs, [], cx).await; + fs.insert_tree( + "/project", + json!({ + "main.rs": "First line\nSecond line\nThird line\nFourth line", + }), + ) + .await; + + let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); @@ -644,7 +684,15 @@ async fn test_handle_start_debugging_reverse_request( let fs = FakeFs::new(executor.clone()); - let project = Project::test(fs, [], cx).await; + fs.insert_tree( + "/project", + json!({ + "main.rs": "First line\nSecond line\nThird line\nFourth line", + }), + ) + .await; + + let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); @@ -771,7 +819,15 @@ async fn test_debug_panel_item_thread_status_reset_on_failure( let fs = FakeFs::new(executor.clone()); - let project = Project::test(fs, [], cx).await; + fs.insert_tree( + "/project", + json!({ + "main.rs": "First line\nSecond line\nThird line\nFourth line", + }), + ) + .await; + + let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); @@ -965,14 +1021,14 @@ async fn test_send_breakpoints_when_editor_has_been_saved( let fs = FakeFs::new(executor.clone()); fs.insert_tree( - "/a", + "/project", json!({ "main.rs": "First line\nSecond line\nThird line\nFourth line", }), ) .await; - let project = Project::test(fs, ["/a".as_ref()], cx).await; + let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); let worktree = worktree_from_project(&project, cx); @@ -1052,7 +1108,7 @@ async fn test_send_breakpoints_when_editor_has_been_saved( .on_request::({ let called_set_breakpoints = called_set_breakpoints.clone(); move |_, args| { - assert_eq!("/a/main.rs", args.source.path.unwrap()); + assert_eq!("/project/main.rs", args.source.path.unwrap()); assert_eq!( vec![SourceBreakpoint { line: 2, @@ -1092,7 +1148,7 @@ async fn test_send_breakpoints_when_editor_has_been_saved( .on_request::({ let called_set_breakpoints = called_set_breakpoints.clone(); move |_, args| { - assert_eq!("/a/main.rs", args.source.path.unwrap()); + assert_eq!("/project/main.rs", args.source.path.unwrap()); assert_eq!( vec![SourceBreakpoint { line: 3, @@ -1151,14 +1207,14 @@ async fn test_it_send_breakpoint_request_if_breakpoint_buffer_is_unopened( let fs = FakeFs::new(executor.clone()); fs.insert_tree( - "/a", + "/project", json!({ "main.rs": "First line\nSecond line\nThird line\nFourth line", }), ) .await; - let project = Project::test(fs, ["/a".as_ref()], cx).await; + let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); let worktree = worktree_from_project(&project, cx); @@ -1221,7 +1277,7 @@ async fn test_it_send_breakpoint_request_if_breakpoint_buffer_is_unopened( .on_request::({ let called_set_breakpoints = called_set_breakpoints.clone(); move |_, args| { - assert_eq!("/a/main.rs", args.source.path.unwrap()); + assert_eq!("/project/main.rs", args.source.path.unwrap()); assert_eq!( vec![SourceBreakpoint { line: 2, @@ -1288,7 +1344,15 @@ async fn test_debug_session_is_shutdown_when_attach_and_launch_request_fails( let fs = FakeFs::new(executor.clone()); - let project = Project::test(fs, [], cx).await; + fs.insert_tree( + "/project", + json!({ + "main.rs": "First line\nSecond line\nThird line\nFourth line", + }), + ) + .await; + + let project = Project::test(fs, ["/project".as_ref()], cx).await; let workspace = init_test_workspace(&project, cx).await; let cx = &mut VisualTestContext::from_window(*workspace, cx); From 402a1b3be06e828de13068295fbb6d105899646d Mon Sep 17 00:00:00 2001 From: Remco Smits Date: Wed, 22 Jan 2025 11:14:46 +0100 Subject: [PATCH 07/22] Make collab tests pass --- crates/collab/src/tests/debug_panel_tests.rs | 34 ++++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/crates/collab/src/tests/debug_panel_tests.rs b/crates/collab/src/tests/debug_panel_tests.rs index 391b9f3bd7ba7f..c90144b4e94230 100644 --- a/crates/collab/src/tests/debug_panel_tests.rs +++ b/crates/collab/src/tests/debug_panel_tests.rs @@ -65,7 +65,7 @@ async fn test_debug_panel_item_opens_on_remote( let active_call_a = cx_a.read(ActiveCall::global); let active_call_b = cx_b.read(ActiveCall::global); - let (project_a, _worktree_id) = client_a.build_local_project("/a", cx_a).await; + let (project_a, _worktree_id) = client_a.build_local_project("/project", cx_a).await; active_call_a .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx)) .await @@ -182,7 +182,7 @@ async fn test_active_debug_panel_item_set_on_join_project( let active_call_a = cx_a.read(ActiveCall::global); let active_call_b = cx_b.read(ActiveCall::global); - let (project_a, _worktree_id) = client_a.build_local_project("/a", cx_a).await; + let (project_a, _worktree_id) = client_a.build_local_project("/project", cx_a).await; active_call_a .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx)) .await @@ -316,7 +316,7 @@ async fn test_debug_panel_remote_button_presses( let active_call_a = cx_a.read(ActiveCall::global); let active_call_b = cx_b.read(ActiveCall::global); - let (project_a, _worktree_id) = client_a.build_local_project("/a", cx_a).await; + let (project_a, _worktree_id) = client_a.build_local_project("/project", cx_a).await; active_call_a .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx)) .await @@ -677,7 +677,7 @@ async fn test_restart_stack_frame(cx_a: &mut TestAppContext, cx_b: &mut TestAppC let active_call_a = cx_a.read(ActiveCall::global); let active_call_b = cx_b.read(ActiveCall::global); - let (project_a, _worktree_id) = client_a.build_local_project("/a", cx_a).await; + let (project_a, _worktree_id) = client_a.build_local_project("/project", cx_a).await; active_call_a .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx)) .await @@ -839,7 +839,7 @@ async fn test_updated_breakpoints_send_to_dap( client_a .fs() .insert_tree( - "/a", + "/project", json!({ "test.txt": "one\ntwo\nthree\nfour\nfive", }), @@ -855,7 +855,7 @@ async fn test_updated_breakpoints_send_to_dap( let active_call_a = cx_a.read(ActiveCall::global); let active_call_b = cx_b.read(ActiveCall::global); - let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; + let (project_a, worktree_id) = client_a.build_local_project("/project", cx_a).await; active_call_a .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx)) .await @@ -922,7 +922,7 @@ async fn test_updated_breakpoints_send_to_dap( .on_request::({ let called_set_breakpoints = called_set_breakpoints.clone(); move |_, args| { - assert_eq!("/a/test.txt", args.source.path.unwrap()); + assert_eq!("/project/test.txt", args.source.path.unwrap()); assert_eq!( vec![SourceBreakpoint { line: 3, @@ -996,7 +996,7 @@ async fn test_updated_breakpoints_send_to_dap( .on_request::({ let called_set_breakpoints = called_set_breakpoints.clone(); move |_, args| { - assert_eq!("/a/test.txt", args.source.path.unwrap()); + assert_eq!("/project/test.txt", args.source.path.unwrap()); assert!(args.breakpoints.unwrap().is_empty()); assert!(!args.source_modified.unwrap()); @@ -1029,7 +1029,7 @@ async fn test_updated_breakpoints_send_to_dap( .on_request::({ let called_set_breakpoints = called_set_breakpoints.clone(); move |_, args| { - assert_eq!("/a/test.txt", args.source.path.unwrap()); + assert_eq!("/project/test.txt", args.source.path.unwrap()); let mut breakpoints = args.breakpoints.unwrap(); breakpoints.sort_by_key(|b| b.line); assert_eq!( @@ -1111,7 +1111,7 @@ async fn test_module_list( let active_call_b = cx_b.read(ActiveCall::global); let active_call_c = cx_c.read(ActiveCall::global); - let (project_a, _worktree_id) = client_a.build_local_project("/a", cx_a).await; + let (project_a, _worktree_id) = client_a.build_local_project("/project", cx_a).await; active_call_a .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx)) .await @@ -1378,7 +1378,7 @@ async fn test_variable_list( let active_call_b = cx_b.read(ActiveCall::global); let active_call_c = cx_c.read(ActiveCall::global); - let (project_a, _worktree_id) = client_a.build_local_project("/a", cx_a).await; + let (project_a, _worktree_id) = client_a.build_local_project("/project", cx_a).await; active_call_a .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx)) .await @@ -1852,7 +1852,7 @@ async fn test_ignore_breakpoints( client_a .fs() .insert_tree( - "/a", + "/project", json!({ "test.txt": "one\ntwo\nthree\nfour\nfive", }), @@ -1870,7 +1870,7 @@ async fn test_ignore_breakpoints( let active_call_b = cx_b.read(ActiveCall::global); let active_call_c = cx_c.read(ActiveCall::global); - let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await; + let (project_a, worktree_id) = client_a.build_local_project("/project", cx_a).await; active_call_a .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx)) .await @@ -1947,7 +1947,7 @@ async fn test_ignore_breakpoints( .on_request::({ let called_set_breakpoints = called_set_breakpoints.clone(); move |_, args| { - assert_eq!("/a/test.txt", args.source.path.unwrap()); + assert_eq!("/project/test.txt", args.source.path.unwrap()); let mut actual_breakpoints = args.breakpoints.unwrap(); actual_breakpoints.sort_by_key(|b| b.line); @@ -2063,7 +2063,7 @@ async fn test_ignore_breakpoints( .on_request::({ let called_set_breakpoints = called_set_breakpoints.clone(); move |_, args| { - assert_eq!("/a/test.txt", args.source.path.unwrap()); + assert_eq!("/project/test.txt", args.source.path.unwrap()); assert_eq!(args.breakpoints, Some(vec![])); called_set_breakpoints.store(true, Ordering::SeqCst); @@ -2157,7 +2157,7 @@ async fn test_ignore_breakpoints( .on_request::({ let called_set_breakpoints = called_set_breakpoints.clone(); move |_, args| { - assert_eq!("/a/test.txt", args.source.path.unwrap()); + assert_eq!("/project/test.txt", args.source.path.unwrap()); let mut actual_breakpoints = args.breakpoints.unwrap(); actual_breakpoints.sort_by_key(|b| b.line); @@ -2245,7 +2245,7 @@ async fn test_ignore_breakpoints( .on_request::({ let called_set_breakpoints = called_set_breakpoints.clone(); move |_, args| { - assert_eq!("/a/test.txt", args.source.path.unwrap()); + assert_eq!("/project/test.txt", args.source.path.unwrap()); assert_eq!(args.breakpoints, Some(vec![])); called_set_breakpoints.store(true, Ordering::SeqCst); From a0b9bf52a1d948dfb244c4b7040576a34ec6f465 Mon Sep 17 00:00:00 2001 From: Remco Smits Date: Wed, 22 Jan 2025 11:20:07 +0100 Subject: [PATCH 08/22] Use notify instead of manual notification --- crates/project/src/dap_store.rs | 12 +----------- crates/tasks_ui/src/modal.rs | 8 +++++--- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/crates/project/src/dap_store.rs b/crates/project/src/dap_store.rs index f107ee1ad01099..1bbe22c00ce4b0 100644 --- a/crates/project/src/dap_store.rs +++ b/crates/project/src/dap_store.rs @@ -780,17 +780,7 @@ impl DapStore { cx.spawn(|this, mut cx| async move { let session = cx.new_model(|_| DebugSession::new_local(session_id, config))?; - let client = match start_client_task.await { - Ok(client) => client, - Err(error) => { - this.update(&mut cx, |_, cx| { - cx.emit(DapStoreEvent::Notification(error.to_string())); - }) - .log_err(); - - return Err(error); - } - }; + let client = start_client_task.await?; this.update(&mut cx, |store, cx| { session.update(cx, |session, cx| { diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs index 12a9b125a545bd..42029ced7cc109 100644 --- a/crates/tasks_ui/src/modal.rs +++ b/crates/tasks_ui/src/modal.rs @@ -17,7 +17,9 @@ use ui::{ WindowContext, }; use util::ResultExt; -use workspace::{tasks::schedule_resolved_task, ModalView, Workspace}; +use workspace::{ + notifications::NotifyTaskExt, tasks::schedule_resolved_task, ModalView, Workspace, +}; pub use zed_actions::{Rerun, Spawn}; /// A modal used to spawn new tasks. @@ -321,7 +323,7 @@ impl PickerDelegate for TasksModalDelegate { workspace.project().update(cx, |project, cx| { project .start_debug_session(debug_config, cx) - .detach_and_log_err(cx); + .detach_and_notify_err(cx); }) } }; @@ -485,7 +487,7 @@ impl PickerDelegate for TasksModalDelegate { workspace.project().update(cx, |project, cx| { project .start_debug_session(debug_config, cx) - .detach_and_log_err(cx); + .detach_and_notify_err(cx); }) } }; From 61469bb1679bc35d5d3bf0b93e5b7cfc94357c80 Mon Sep 17 00:00:00 2001 From: Remco Smits Date: Wed, 22 Jan 2025 11:20:17 +0100 Subject: [PATCH 09/22] Use reference instead of clone --- crates/project/src/dap_store.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/project/src/dap_store.rs b/crates/project/src/dap_store.rs index 1bbe22c00ce4b0..5d050b6aa56975 100644 --- a/crates/project/src/dap_store.rs +++ b/crates/project/src/dap_store.rs @@ -673,7 +673,7 @@ impl DapStore { &mut self, session_id: DebugSessionId, worktree: &Model, - config: DebugAdapterConfig, + config: &DebugAdapterConfig, cx: &mut ModelContext, ) -> Task>> { let Some(local_store) = self.as_local_mut() else { @@ -774,8 +774,7 @@ impl DapStore { }; let session_id = local_store.next_session_id(); - let start_client_task = - self.start_client_internal(session_id, worktree, config.clone(), cx); + let start_client_task = self.start_client_internal(session_id, worktree, &config, cx); cx.spawn(|this, mut cx| async move { let session = cx.new_model(|_| DebugSession::new_local(session_id, config))?; From 8ba15ae9087c28538a8357c3db4515d97c2a75ce Mon Sep 17 00:00:00 2001 From: Remco Smits Date: Wed, 22 Jan 2025 11:21:06 +0100 Subject: [PATCH 10/22] Revert "Use reference instead of clone" This reverts commit 61469bb1679bc35d5d3bf0b93e5b7cfc94357c80. --- crates/project/src/dap_store.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/project/src/dap_store.rs b/crates/project/src/dap_store.rs index 5d050b6aa56975..1bbe22c00ce4b0 100644 --- a/crates/project/src/dap_store.rs +++ b/crates/project/src/dap_store.rs @@ -673,7 +673,7 @@ impl DapStore { &mut self, session_id: DebugSessionId, worktree: &Model, - config: &DebugAdapterConfig, + config: DebugAdapterConfig, cx: &mut ModelContext, ) -> Task>> { let Some(local_store) = self.as_local_mut() else { @@ -774,7 +774,8 @@ impl DapStore { }; let session_id = local_store.next_session_id(); - let start_client_task = self.start_client_internal(session_id, worktree, &config, cx); + let start_client_task = + self.start_client_internal(session_id, worktree, config.clone(), cx); cx.spawn(|this, mut cx| async move { let session = cx.new_model(|_| DebugSession::new_local(session_id, config))?; From 898e6132da5374d902a72013b9ca65419ea231a2 Mon Sep 17 00:00:00 2001 From: Remco Smits Date: Wed, 22 Jan 2025 11:32:07 +0100 Subject: [PATCH 11/22] Revert "Use notify instead of manual notification" This reverts commit a0b9bf52a1d948dfb244c4b7040576a34ec6f465. --- crates/project/src/dap_store.rs | 12 +++++++++++- crates/tasks_ui/src/modal.rs | 8 +++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/crates/project/src/dap_store.rs b/crates/project/src/dap_store.rs index 1bbe22c00ce4b0..f107ee1ad01099 100644 --- a/crates/project/src/dap_store.rs +++ b/crates/project/src/dap_store.rs @@ -780,7 +780,17 @@ impl DapStore { cx.spawn(|this, mut cx| async move { let session = cx.new_model(|_| DebugSession::new_local(session_id, config))?; - let client = start_client_task.await?; + let client = match start_client_task.await { + Ok(client) => client, + Err(error) => { + this.update(&mut cx, |_, cx| { + cx.emit(DapStoreEvent::Notification(error.to_string())); + }) + .log_err(); + + return Err(error); + } + }; this.update(&mut cx, |store, cx| { session.update(cx, |session, cx| { diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs index 42029ced7cc109..12a9b125a545bd 100644 --- a/crates/tasks_ui/src/modal.rs +++ b/crates/tasks_ui/src/modal.rs @@ -17,9 +17,7 @@ use ui::{ WindowContext, }; use util::ResultExt; -use workspace::{ - notifications::NotifyTaskExt, tasks::schedule_resolved_task, ModalView, Workspace, -}; +use workspace::{tasks::schedule_resolved_task, ModalView, Workspace}; pub use zed_actions::{Rerun, Spawn}; /// A modal used to spawn new tasks. @@ -323,7 +321,7 @@ impl PickerDelegate for TasksModalDelegate { workspace.project().update(cx, |project, cx| { project .start_debug_session(debug_config, cx) - .detach_and_notify_err(cx); + .detach_and_log_err(cx); }) } }; @@ -487,7 +485,7 @@ impl PickerDelegate for TasksModalDelegate { workspace.project().update(cx, |project, cx| { project .start_debug_session(debug_config, cx) - .detach_and_notify_err(cx); + .detach_and_log_err(cx); }) } }; From 56c883d4dba4877826ea2185a8177fddefa0d054 Mon Sep 17 00:00:00 2001 From: Remco Smits Date: Wed, 22 Jan 2025 11:41:42 +0100 Subject: [PATCH 12/22] Revert debugger branch merge --- .../20221109000000_test_schema.sql | 1 - .../20250121181012_add_ignore_breakpoints.sql | 2 - crates/collab/src/db/queries/projects.rs | 39 -- crates/collab/src/db/tables/debug_clients.rs | 1 - crates/collab/src/rpc.rs | 29 -- crates/collab/src/tests/debug_panel_tests.rs | 490 ------------------ crates/dap/src/session.rs | 117 ++--- crates/debugger_tools/src/dap_log.rs | 41 +- crates/debugger_ui/src/debugger_panel.rs | 19 +- crates/debugger_ui/src/debugger_panel_item.rs | 6 - .../debugger_ui/src/tests/debugger_panel.rs | 4 +- crates/project/src/dap_store.rs | 233 ++------- crates/project/src/project.rs | 44 -- crates/proto/proto/zed.proto | 25 +- crates/proto/src/proto.rs | 6 - 15 files changed, 88 insertions(+), 969 deletions(-) delete mode 100644 crates/collab/migrations/20250121181012_add_ignore_breakpoints.sql diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index c36207514aed29..5a065878af4ab7 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -474,7 +474,6 @@ CREATE TABLE IF NOT EXISTS "debug_clients" ( project_id INTEGER NOT NULL, session_id BIGINT NOT NULL, capabilities INTEGER NOT NULL, - ignore_breakpoints BOOLEAN NOT NULL DEFAULT FALSE, PRIMARY KEY (id, project_id, session_id), FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE ); diff --git a/crates/collab/migrations/20250121181012_add_ignore_breakpoints.sql b/crates/collab/migrations/20250121181012_add_ignore_breakpoints.sql deleted file mode 100644 index e1e362c5cf84ad..00000000000000 --- a/crates/collab/migrations/20250121181012_add_ignore_breakpoints.sql +++ /dev/null @@ -1,2 +0,0 @@ - -ALTER TABLE debug_clients ADD COLUMN ignore_breakpoints BOOLEAN NOT NULL DEFAULT FALSE; diff --git a/crates/collab/src/db/queries/projects.rs b/crates/collab/src/db/queries/projects.rs index b25c754d83c7a6..da0d99432be96a 100644 --- a/crates/collab/src/db/queries/projects.rs +++ b/crates/collab/src/db/queries/projects.rs @@ -556,40 +556,6 @@ impl Database { .await } - pub async fn ignore_breakpoint_state( - &self, - connection_id: ConnectionId, - update: &proto::IgnoreBreakpointState, - ) -> Result>> { - let project_id = ProjectId::from_proto(update.project_id); - self.project_transaction(project_id, |tx| async move { - let debug_clients = debug_clients::Entity::find() - .filter( - Condition::all() - .add(debug_clients::Column::ProjectId.eq(project_id)) - .add(debug_clients::Column::SessionId.eq(update.session_id)), - ) - .all(&*tx) - .await?; - - for debug_client in debug_clients { - debug_clients::Entity::update(debug_clients::ActiveModel { - id: ActiveValue::Unchanged(debug_client.id), - project_id: ActiveValue::Unchanged(debug_client.project_id), - session_id: ActiveValue::Unchanged(debug_client.session_id), - capabilities: ActiveValue::Unchanged(debug_client.capabilities), - ignore_breakpoints: ActiveValue::Set(update.ignore), - }) - .exec(&*tx) - .await?; - } - - self.internal_project_connection_ids(project_id, connection_id, true, &tx) - .await - }) - .await - } - pub async fn update_debug_adapter( &self, connection_id: ConnectionId, @@ -681,7 +647,6 @@ impl Database { project_id: ActiveValue::Set(project_id), session_id: ActiveValue::Set(update.session_id as i64), capabilities: ActiveValue::Set(0), - ignore_breakpoints: ActiveValue::Set(false), }; new_debug_client.insert(&*tx).await?; } @@ -764,7 +729,6 @@ impl Database { project_id: ActiveValue::Set(project_id), session_id: ActiveValue::Set(update.session_id as i64), capabilities: ActiveValue::Set(0), - ignore_breakpoints: ActiveValue::Set(false), }; debug_client = Some(new_debug_client.insert(&*tx).await?); } @@ -778,7 +742,6 @@ impl Database { project_id: ActiveValue::Unchanged(debug_client.project_id), session_id: ActiveValue::Unchanged(debug_client.session_id), capabilities: ActiveValue::Set(debug_client.capabilities), - ignore_breakpoints: ActiveValue::Set(debug_client.ignore_breakpoints), }) .exec(&*tx) .await?; @@ -1123,7 +1086,6 @@ impl Database { for (session_id, clients) in debug_sessions.into_iter() { let mut debug_clients = Vec::default(); - let ignore_breakpoints = clients.iter().any(|debug| debug.ignore_breakpoints); // Temp solution until client -> session change for debug_client in clients.into_iter() { let debug_panel_items = debug_client @@ -1146,7 +1108,6 @@ impl Database { project_id, session_id: session_id as u64, clients: debug_clients, - ignore_breakpoints, }); } diff --git a/crates/collab/src/db/tables/debug_clients.rs b/crates/collab/src/db/tables/debug_clients.rs index 498b9ed7359091..02758acaa0c4fa 100644 --- a/crates/collab/src/db/tables/debug_clients.rs +++ b/crates/collab/src/db/tables/debug_clients.rs @@ -25,7 +25,6 @@ pub struct Model { pub session_id: i64, #[sea_orm(column_type = "Integer")] pub capabilities: i32, - pub ignore_breakpoints: bool, } impl Model { diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index fa116c8cc54938..cf7083a21c1a99 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -438,13 +438,6 @@ impl Server { .add_request_handler(forward_mutating_project_request::) .add_message_handler( broadcast_project_message_from_host::, - ) - .add_message_handler( - broadcast_project_message_from_host::, - ) - .add_message_handler(ignore_breakpoint_state) - .add_message_handler( - broadcast_project_message_from_host::, ); Arc::new(server) @@ -2162,28 +2155,6 @@ async fn shutdown_debug_client( Ok(()) } -async fn ignore_breakpoint_state( - request: proto::IgnoreBreakpointState, - session: Session, -) -> Result<()> { - let guest_connection_ids = session - .db() - .await - .ignore_breakpoint_state(session.connection_id, &request) - .await?; - - broadcast( - Some(session.connection_id), - guest_connection_ids.iter().copied(), - |connection_id| { - session - .peer - .forward_send(session.connection_id, connection_id, request.clone()) - }, - ); - Ok(()) -} - /// Notify other participants that a debug panel item has been updated async fn update_debug_adapter(request: proto::UpdateDebugAdapter, session: Session) -> Result<()> { let guest_connection_ids = session diff --git a/crates/collab/src/tests/debug_panel_tests.rs b/crates/collab/src/tests/debug_panel_tests.rs index c90144b4e94230..40c2a5af97a59e 100644 --- a/crates/collab/src/tests/debug_panel_tests.rs +++ b/crates/collab/src/tests/debug_panel_tests.rs @@ -1836,493 +1836,3 @@ async fn test_variable_list( shutdown_client.await.unwrap(); } - -#[gpui::test] -async fn test_ignore_breakpoints( - cx_a: &mut TestAppContext, - cx_b: &mut TestAppContext, - cx_c: &mut TestAppContext, -) { - let executor = cx_a.executor(); - let mut server = TestServer::start(executor.clone()).await; - let client_a = server.create_client(cx_a, "user_a").await; - let client_b = server.create_client(cx_b, "user_b").await; - let client_c = server.create_client(cx_c, "user_c").await; - - client_a - .fs() - .insert_tree( - "/project", - json!({ - "test.txt": "one\ntwo\nthree\nfour\nfive", - }), - ) - .await; - - init_test(cx_a); - init_test(cx_b); - init_test(cx_c); - - server - .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)]) - .await; - let active_call_a = cx_a.read(ActiveCall::global); - let active_call_b = cx_b.read(ActiveCall::global); - let active_call_c = cx_c.read(ActiveCall::global); - - let (project_a, worktree_id) = client_a.build_local_project("/project", cx_a).await; - active_call_a - .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx)) - .await - .unwrap(); - - let project_path = ProjectPath { - worktree_id, - path: Arc::from(Path::new(&"test.txt")), - }; - - let project_id = active_call_a - .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) - .await - .unwrap(); - let project_b = client_b.join_remote_project(project_id, cx_b).await; - active_call_b - .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx)) - .await - .unwrap(); - - let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a); - let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); - - add_debugger_panel(&workspace_a, cx_a).await; - add_debugger_panel(&workspace_b, cx_b).await; - - let local_editor = workspace_a - .update(cx_a, |workspace, cx| { - workspace.open_path(project_path.clone(), None, true, cx) - }) - .await - .unwrap() - .downcast::() - .unwrap(); - - local_editor.update(cx_a, |editor, cx| { - editor.move_down(&editor::actions::MoveDown, cx); - editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, cx); // Line 2 - editor.move_down(&editor::actions::MoveDown, cx); - editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, cx); // Line 3 - }); - - cx_a.run_until_parked(); - cx_b.run_until_parked(); - - let task = project_a.update(cx_a, |project, cx| { - project.start_debug_session( - dap::DebugAdapterConfig { - label: "test config".into(), - kind: dap::DebugAdapterKind::Fake, - request: dap::DebugRequestType::Launch, - program: None, - cwd: None, - initialize_args: None, - }, - cx, - ) - }); - - let (session, client) = task.await.unwrap(); - let client_id = client.id(); - - client - .on_request::(move |_, _| { - Ok(dap::Capabilities { - supports_configuration_done_request: Some(true), - ..Default::default() - }) - }) - .await; - - let called_set_breakpoints = Arc::new(AtomicBool::new(false)); - client - .on_request::({ - let called_set_breakpoints = called_set_breakpoints.clone(); - move |_, args| { - assert_eq!("/project/test.txt", args.source.path.unwrap()); - - let mut actual_breakpoints = args.breakpoints.unwrap(); - actual_breakpoints.sort_by_key(|b| b.line); - - let expected_breakpoints = vec![ - SourceBreakpoint { - line: 2, - column: None, - condition: None, - hit_condition: None, - log_message: None, - mode: None, - }, - SourceBreakpoint { - line: 3, - column: None, - condition: None, - hit_condition: None, - log_message: None, - mode: None, - }, - ]; - - assert_eq!(actual_breakpoints, expected_breakpoints); - - called_set_breakpoints.store(true, Ordering::SeqCst); - - Ok(dap::SetBreakpointsResponse { - breakpoints: Vec::default(), - }) - } - }) - .await; - - client.on_request::(move |_, _| Ok(())).await; - client - .on_request::(move |_, _| { - Ok(dap::StackTraceResponse { - stack_frames: Vec::default(), - total_frames: None, - }) - }) - .await; - - client.on_request::(move |_, _| Ok(())).await; - - client - .fake_event(dap::messages::Events::Initialized(Some( - dap::Capabilities { - supports_configuration_done_request: Some(true), - ..Default::default() - }, - ))) - .await; - - cx_a.run_until_parked(); - cx_b.run_until_parked(); - - assert!( - called_set_breakpoints.load(std::sync::atomic::Ordering::SeqCst), - "SetBreakpoint request must be called when starting debug session" - ); - - client - .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent { - reason: dap::StoppedEventReason::Pause, - description: None, - thread_id: Some(1), - preserve_focus_hint: None, - text: None, - all_threads_stopped: None, - hit_breakpoint_ids: None, - })) - .await; - - cx_a.run_until_parked(); - cx_b.run_until_parked(); - - let remote_debug_item = workspace_b.update(cx_b, |workspace, cx| { - let debug_panel = workspace.panel::(cx).unwrap(); - let active_debug_panel_item = debug_panel - .update(cx, |this, cx| this.active_debug_panel_item(cx)) - .unwrap(); - - assert_eq!( - 1, - debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len()) - ); - - let session_id = debug_panel.update(cx, |this, cx| { - this.dap_store() - .read(cx) - .as_remote() - .unwrap() - .session_by_client_id(&client.id()) - .unwrap() - .read(cx) - .id() - }); - - let breakpoints_ignored = active_debug_panel_item.read(cx).are_breakpoints_ignored(cx); - - assert_eq!(session_id, active_debug_panel_item.read(cx).session_id()); - assert_eq!(false, breakpoints_ignored); - assert_eq!(client.id(), active_debug_panel_item.read(cx).client_id()); - assert_eq!(1, active_debug_panel_item.read(cx).thread_id()); - active_debug_panel_item - }); - - called_set_breakpoints.store(false, Ordering::SeqCst); - - client - .on_request::({ - let called_set_breakpoints = called_set_breakpoints.clone(); - move |_, args| { - assert_eq!("/project/test.txt", args.source.path.unwrap()); - assert_eq!(args.breakpoints, Some(vec![])); - - called_set_breakpoints.store(true, Ordering::SeqCst); - - Ok(dap::SetBreakpointsResponse { - breakpoints: Vec::default(), - }) - } - }) - .await; - - let local_debug_item = workspace_a.update(cx_a, |workspace, cx| { - let debug_panel = workspace.panel::(cx).unwrap(); - let active_debug_panel_item = debug_panel - .update(cx, |this, cx| this.active_debug_panel_item(cx)) - .unwrap(); - - assert_eq!( - 1, - debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len()) - ); - - assert_eq!( - false, - active_debug_panel_item.read(cx).are_breakpoints_ignored(cx) - ); - assert_eq!(client.id(), active_debug_panel_item.read(cx).client_id()); - assert_eq!(1, active_debug_panel_item.read(cx).thread_id()); - - active_debug_panel_item - }); - - local_debug_item.update(cx_a, |item, cx| { - item.toggle_ignore_breakpoints(cx); // Set to true - assert_eq!(true, item.are_breakpoints_ignored(cx)); - }); - - cx_a.run_until_parked(); - cx_b.run_until_parked(); - - assert!( - called_set_breakpoints.load(std::sync::atomic::Ordering::SeqCst), - "SetBreakpoint request must be called to ignore breakpoints" - ); - - client - .on_request::({ - let called_set_breakpoints = called_set_breakpoints.clone(); - move |_, _args| { - called_set_breakpoints.store(true, Ordering::SeqCst); - - Ok(dap::SetBreakpointsResponse { - breakpoints: Vec::default(), - }) - } - }) - .await; - - let remote_editor = workspace_b - .update(cx_b, |workspace, cx| { - workspace.open_path(project_path.clone(), None, true, cx) - }) - .await - .unwrap() - .downcast::() - .unwrap(); - - called_set_breakpoints.store(false, std::sync::atomic::Ordering::SeqCst); - - remote_editor.update(cx_b, |editor, cx| { - editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, cx); // Line 1 - }); - - cx_a.run_until_parked(); - cx_b.run_until_parked(); - - assert!( - called_set_breakpoints.load(std::sync::atomic::Ordering::SeqCst), - "SetBreakpoint request be called whenever breakpoints are toggled but with not breakpoints" - ); - - remote_debug_item.update(cx_b, |debug_panel, cx| { - let breakpoints_ignored = debug_panel.are_breakpoints_ignored(cx); - - assert_eq!(true, breakpoints_ignored); - assert_eq!(client.id(), debug_panel.client_id()); - assert_eq!(1, debug_panel.thread_id()); - }); - - client - .on_request::({ - let called_set_breakpoints = called_set_breakpoints.clone(); - move |_, args| { - assert_eq!("/project/test.txt", args.source.path.unwrap()); - - let mut actual_breakpoints = args.breakpoints.unwrap(); - actual_breakpoints.sort_by_key(|b| b.line); - - let expected_breakpoints = vec![ - SourceBreakpoint { - line: 1, - column: None, - condition: None, - hit_condition: None, - log_message: None, - mode: None, - }, - SourceBreakpoint { - line: 2, - column: None, - condition: None, - hit_condition: None, - log_message: None, - mode: None, - }, - SourceBreakpoint { - line: 3, - column: None, - condition: None, - hit_condition: None, - log_message: None, - mode: None, - }, - ]; - - assert_eq!(actual_breakpoints, expected_breakpoints); - - called_set_breakpoints.store(true, Ordering::SeqCst); - - Ok(dap::SetBreakpointsResponse { - breakpoints: Vec::default(), - }) - } - }) - .await; - - let project_c = client_c.join_remote_project(project_id, cx_c).await; - active_call_c - .update(cx_c, |call, cx| call.set_location(Some(&project_c), cx)) - .await - .unwrap(); - - let (workspace_c, cx_c) = client_c.build_workspace(&project_c, cx_c); - add_debugger_panel(&workspace_c, cx_c).await; - - let last_join_remote_item = workspace_c.update(cx_c, |workspace, cx| { - let debug_panel = workspace.panel::(cx).unwrap(); - let active_debug_panel_item = debug_panel - .update(cx, |this, cx| this.active_debug_panel_item(cx)) - .unwrap(); - - let breakpoints_ignored = active_debug_panel_item.read(cx).are_breakpoints_ignored(cx); - - assert_eq!(true, breakpoints_ignored); - - assert_eq!( - 1, - debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len()) - ); - assert_eq!(client.id(), active_debug_panel_item.read(cx).client_id()); - assert_eq!(1, active_debug_panel_item.read(cx).thread_id()); - active_debug_panel_item - }); - - remote_debug_item.update(cx_b, |item, cx| { - item.toggle_ignore_breakpoints(cx); - }); - - cx_a.run_until_parked(); - cx_b.run_until_parked(); - cx_c.run_until_parked(); - - assert!( - called_set_breakpoints.load(std::sync::atomic::Ordering::SeqCst), - "SetBreakpoint request should be called to update breakpoints" - ); - - client - .on_request::({ - let called_set_breakpoints = called_set_breakpoints.clone(); - move |_, args| { - assert_eq!("/project/test.txt", args.source.path.unwrap()); - assert_eq!(args.breakpoints, Some(vec![])); - - called_set_breakpoints.store(true, Ordering::SeqCst); - - Ok(dap::SetBreakpointsResponse { - breakpoints: Vec::default(), - }) - } - }) - .await; - - local_debug_item.update(cx_a, |debug_panel_item, cx| { - assert_eq!( - false, - debug_panel_item.are_breakpoints_ignored(cx), - "Remote client set this to false" - ); - }); - - remote_debug_item.update(cx_b, |debug_panel_item, cx| { - assert_eq!( - false, - debug_panel_item.are_breakpoints_ignored(cx), - "Remote client set this to false" - ); - }); - - last_join_remote_item.update(cx_c, |debug_panel_item, cx| { - assert_eq!( - false, - debug_panel_item.are_breakpoints_ignored(cx), - "Remote client set this to false" - ); - }); - - let shutdown_client = project_a.update(cx_a, |project, cx| { - project.dap_store().update(cx, |dap_store, cx| { - dap_store.shutdown_session(&session.read(cx).id(), cx) - }) - }); - - shutdown_client.await.unwrap(); - - cx_a.run_until_parked(); - cx_b.run_until_parked(); - - project_b.update(cx_b, |project, cx| { - project.dap_store().update(cx, |dap_store, _cx| { - let sessions = dap_store.sessions().collect::>(); - - assert_eq!( - None, - dap_store.session_by_client_id(&client_id), - "No client_id to session mapping should exist after shutdown" - ); - assert_eq!( - 0, - sessions.len(), - "No sessions should be left after shutdown" - ); - }) - }); - - project_c.update(cx_c, |project, cx| { - project.dap_store().update(cx, |dap_store, _cx| { - let sessions = dap_store.sessions().collect::>(); - - assert_eq!( - None, - dap_store.session_by_client_id(&client_id), - "No client_id to session mapping should exist after shutdown" - ); - assert_eq!( - 0, - sessions.len(), - "No sessions should be left after shutdown" - ); - }) - }); -} diff --git a/crates/dap/src/session.rs b/crates/dap/src/session.rs index 745b0ec5244f53..77e07a76a526e0 100644 --- a/crates/dap/src/session.rs +++ b/crates/dap/src/session.rs @@ -19,37 +19,54 @@ impl DebugSessionId { } } -pub enum DebugSession { - Local(LocalDebugSession), - Remote(RemoteDebugSession), -} - -pub struct LocalDebugSession { +pub struct DebugSession { id: DebugSessionId, ignore_breakpoints: bool, configuration: DebugAdapterConfig, clients: HashMap>, } -impl LocalDebugSession { +impl DebugSession { + pub fn new(id: DebugSessionId, configuration: DebugAdapterConfig) -> Self { + Self { + id, + configuration, + ignore_breakpoints: false, + clients: HashMap::default(), + } + } + + pub fn id(&self) -> DebugSessionId { + self.id + } + + pub fn name(&self) -> String { + self.configuration.label.clone() + } + pub fn configuration(&self) -> &DebugAdapterConfig { &self.configuration } + pub fn ignore_breakpoints(&self) -> bool { + self.ignore_breakpoints + } + + pub fn set_ignore_breakpoints(&mut self, ignore: bool, cx: &mut ModelContext) { + self.ignore_breakpoints = ignore; + cx.notify(); + } + pub fn update_configuration( &mut self, f: impl FnOnce(&mut DebugAdapterConfig), - cx: &mut ModelContext, + cx: &mut ModelContext, ) { f(&mut self.configuration); cx.notify(); } - pub fn add_client( - &mut self, - client: Arc, - cx: &mut ModelContext, - ) { + pub fn add_client(&mut self, client: Arc, cx: &mut ModelContext) { self.clients.insert(client.id(), client); cx.notify(); } @@ -57,7 +74,7 @@ impl LocalDebugSession { pub fn remove_client( &mut self, client_id: &DebugAdapterClientId, - cx: &mut ModelContext, + cx: &mut ModelContext, ) -> Option> { let client = self.clients.remove(client_id); cx.notify(); @@ -84,76 +101,4 @@ impl LocalDebugSession { pub fn client_ids(&self) -> impl Iterator + '_ { self.clients.keys().cloned() } - - pub fn id(&self) -> DebugSessionId { - self.id - } -} - -pub struct RemoteDebugSession { - id: DebugSessionId, - ignore_breakpoints: bool, - label: String, -} - -impl DebugSession { - pub fn new_local(id: DebugSessionId, configuration: DebugAdapterConfig) -> Self { - Self::Local(LocalDebugSession { - id, - ignore_breakpoints: false, - configuration, - clients: HashMap::default(), - }) - } - - pub fn as_local(&self) -> Option<&LocalDebugSession> { - match self { - DebugSession::Local(local) => Some(local), - _ => None, - } - } - - pub fn as_local_mut(&mut self) -> Option<&mut LocalDebugSession> { - match self { - DebugSession::Local(local) => Some(local), - _ => None, - } - } - - pub fn new_remote(id: DebugSessionId, label: String, ignore_breakpoints: bool) -> Self { - Self::Remote(RemoteDebugSession { - id, - label: label.clone(), - ignore_breakpoints, - }) - } - - pub fn id(&self) -> DebugSessionId { - match self { - DebugSession::Local(local) => local.id, - DebugSession::Remote(remote) => remote.id, - } - } - - pub fn name(&self) -> String { - match self { - DebugSession::Local(local) => local.configuration.label.clone(), - DebugSession::Remote(remote) => remote.label.clone(), - } - } - - pub fn ignore_breakpoints(&self) -> bool { - match self { - DebugSession::Local(local) => local.ignore_breakpoints, - DebugSession::Remote(remote) => remote.ignore_breakpoints, - } - } - - pub fn set_ignore_breakpoints(&mut self, ignore: bool, cx: &mut ModelContext) { - match self { - DebugSession::Local(local) => local.ignore_breakpoints = ignore, - DebugSession::Remote(remote) => remote.ignore_breakpoints = ignore, - } - cx.notify(); - } } diff --git a/crates/debugger_tools/src/dap_log.rs b/crates/debugger_tools/src/dap_log.rs index b111ecb1f8bb93..c315a12384833c 100644 --- a/crates/debugger_tools/src/dap_log.rs +++ b/crates/debugger_tools/src/dap_log.rs @@ -580,28 +580,25 @@ impl DapLogView { .dap_store() .read(cx) .sessions() - .filter_map(|session| { - Some(DapMenuItem { - session_id: session.read(cx).id(), - session_name: session.read(cx).name(), - clients: { - let mut clients = session - .read(cx) - .as_local()? - .clients() - .map(|client| DapMenuSubItem { - client_id: client.id(), - client_name: client.adapter_id(), - has_adapter_logs: client.has_adapter_logs(), - selected_entry: self - .current_view - .map_or(LogKind::Adapter, |(_, kind)| kind), - }) - .collect::>(); - clients.sort_by_key(|item| item.client_id.0); - clients - }, - }) + .map(|session| DapMenuItem { + session_id: session.read(cx).id(), + session_name: session.read(cx).name(), + clients: { + let mut clients = session + .read(cx) + .clients() + .map(|client| DapMenuSubItem { + client_id: client.id(), + client_name: client.adapter_id(), + has_adapter_logs: client.has_adapter_logs(), + selected_entry: self + .current_view + .map_or(LogKind::Adapter, |(_, kind)| kind), + }) + .collect::>(); + clients.sort_by_key(|item| item.client_id.0); + clients + }, }) .collect::>(); menu_items.sort_by_key(|item| item.session_id.0); diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index 9d588cd06e8138..ec343930ef45e2 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -292,11 +292,6 @@ impl DebugPanel { &self.message_queue } - #[cfg(any(test, feature = "test-support"))] - pub fn dap_store(&self) -> Model { - self.dap_store.clone() - } - pub fn active_debug_panel_item( &self, cx: &mut ViewContext, @@ -570,19 +565,14 @@ impl DebugPanel { client_id: &DebugAdapterClientId, cx: &mut ViewContext, ) { - let Some(session) = self - .dap_store - .read(cx) - .session_by_id(session_id) - .and_then(|session| session.read(cx).as_local()) - else { + let Some(session) = self.dap_store.read(cx).session_by_id(session_id) else { return; }; let session_id = *session_id; let client_id = *client_id; let workspace = self.workspace.clone(); - let request_type = session.configuration().request.clone(); + let request_type = session.read(cx).configuration().request.clone(); cx.spawn(|this, mut cx| async move { let task = this.update(&mut cx, |this, cx| { this.dap_store.update(cx, |store, cx| { @@ -1040,11 +1030,6 @@ impl DebugPanel { ) }); - self.dap_store.update(cx, |dap_store, cx| { - dap_store.add_remote_session(session_id, None, cx); - dap_store.add_client_to_session(session_id, client_id); - }); - pane.add_item(Box::new(debug_panel_item.clone()), true, true, None, cx); debug_panel_item }); diff --git a/crates/debugger_ui/src/debugger_panel_item.rs b/crates/debugger_ui/src/debugger_panel_item.rs index a645578f3fda1f..35ee5957f41737 100644 --- a/crates/debugger_ui/src/debugger_panel_item.rs +++ b/crates/debugger_ui/src/debugger_panel_item.rs @@ -506,12 +506,6 @@ impl DebugPanelItem { &self.thread_state } - #[cfg(any(test, feature = "test-support"))] - pub fn are_breakpoints_ignored(&self, cx: &AppContext) -> bool { - self.dap_store - .read_with(cx, |dap, cx| dap.ignore_breakpoints(&self.session_id, cx)) - } - pub fn capabilities(&self, cx: &mut ViewContext) -> Capabilities { self.dap_store.read(cx).capabilities_by_id(&self.client_id) } diff --git a/crates/debugger_ui/src/tests/debugger_panel.rs b/crates/debugger_ui/src/tests/debugger_panel.rs index 62e397e2ec3de6..33915218083af8 100644 --- a/crates/debugger_ui/src/tests/debugger_panel.rs +++ b/crates/debugger_ui/src/tests/debugger_panel.rs @@ -749,7 +749,7 @@ async fn test_handle_start_debugging_reverse_request( cx.run_until_parked(); project.update(cx, |_, cx| { - assert_eq!(2, session.read(cx).as_local().unwrap().clients_len()); + assert_eq!(2, session.read(cx).clients_len()); }); assert!( send_response.load(std::sync::atomic::Ordering::SeqCst), @@ -759,8 +759,6 @@ async fn test_handle_start_debugging_reverse_request( let second_client = project.update(cx, |_, cx| { session .read(cx) - .as_local() - .unwrap() .client_by_id(&DebugAdapterClientId(1)) .unwrap() }); diff --git a/crates/project/src/dap_store.rs b/crates/project/src/dap_store.rs index f107ee1ad01099..046b9a55814936 100644 --- a/crates/project/src/dap_store.rs +++ b/crates/project/src/dap_store.rs @@ -128,22 +128,9 @@ impl LocalDapStore { pub struct RemoteDapStore { upstream_client: Option, upstream_project_id: u64, - sessions: HashMap>, - client_by_session: HashMap, event_queue: Option>, } -impl RemoteDapStore { - pub fn session_by_client_id( - &self, - client_id: &DebugAdapterClientId, - ) -> Option> { - self.client_by_session - .get(client_id) - .and_then(|session_id| self.sessions.get(session_id).cloned()) - } -} - pub struct DapStore { mode: DapStoreMode, downstream_client: Option<(AnyProtoClient, u64)>, @@ -166,8 +153,6 @@ impl DapStore { client.add_model_message_handler(DapStore::handle_synchronize_breakpoints); client.add_model_message_handler(DapStore::handle_update_debug_adapter); client.add_model_message_handler(DapStore::handle_update_thread_status); - client.add_model_message_handler(DapStore::handle_ignore_breakpoint_state); - client.add_model_message_handler(DapStore::handle_session_has_shutdown); client.add_model_request_handler(DapStore::handle_dap_command::); client.add_model_request_handler(DapStore::handle_dap_command::); @@ -181,7 +166,7 @@ impl DapStore { client.add_model_request_handler(DapStore::handle_dap_command::); client.add_model_request_handler(DapStore::handle_dap_command::); client.add_model_request_handler(DapStore::handle_dap_command::); - client.add_model_request_handler(DapStore::handle_shutdown_session_request); + client.add_model_request_handler(DapStore::handle_shutdown_session); } pub fn new_local( @@ -220,8 +205,6 @@ impl DapStore { mode: DapStoreMode::Remote(RemoteDapStore { upstream_client: Some(upstream_client), upstream_project_id: project_id, - sessions: Default::default(), - client_by_session: Default::default(), event_queue: Some(VecDeque::default()), }), downstream_client: None, @@ -280,91 +263,21 @@ impl DapStore { self.downstream_client.as_ref() } - pub fn add_remote_session( - &mut self, - session_id: DebugSessionId, - ignore: Option, - cx: &mut ModelContext, - ) { - match &mut self.mode { - DapStoreMode::Remote(remote) => { - remote - .sessions - .entry(session_id) - .or_insert(cx.new_model(|_| { - DebugSession::new_remote( - session_id, - "Remote-Debug".to_owned(), - ignore.unwrap_or(false), - ) - })); - } - _ => {} - } - } - - pub fn add_client_to_session( - &mut self, - session_id: DebugSessionId, - client_id: DebugAdapterClientId, - ) { - match &mut self.mode { - DapStoreMode::Local(local) => { - if local.sessions.contains_key(&session_id) { - local.client_by_session.insert(client_id, session_id); - } - } - DapStoreMode::Remote(remote) => { - if remote.sessions.contains_key(&session_id) { - remote.client_by_session.insert(client_id, session_id); - } - } - } - } - - pub fn remove_session(&mut self, session_id: DebugSessionId, cx: &mut ModelContext) { - match &mut self.mode { - DapStoreMode::Local(local) => { - if let Some(session) = local.sessions.remove(&session_id) { - for client_id in session - .read(cx) - .as_local() - .map(|local| local.client_ids()) - .expect("Local Dap can only have local sessions") - { - local.client_by_session.remove(&client_id); - } - } - } - DapStoreMode::Remote(remote) => { - remote.sessions.remove(&session_id); - remote.client_by_session.retain(|_, val| val != &session_id) - } - } - } - pub fn sessions(&self) -> impl Iterator> + '_ { - match &self.mode { - DapStoreMode::Local(local) => local.sessions.values().cloned(), - DapStoreMode::Remote(remote) => remote.sessions.values().cloned(), - } + self.as_local().unwrap().sessions.values().cloned() } pub fn session_by_id(&self, session_id: &DebugSessionId) -> Option> { - match &self.mode { - DapStoreMode::Local(local) => local.sessions.get(session_id).cloned(), - DapStoreMode::Remote(remote) => remote.sessions.get(session_id).cloned(), - } + self.as_local() + .and_then(|store| store.sessions.get(session_id).cloned()) } pub fn session_by_client_id( &self, client_id: &DebugAdapterClientId, ) -> Option> { - match &self.mode { - DapStoreMode::Local(local) => local.session_by_client_id(client_id), - DapStoreMode::Remote(remote) => remote.session_by_client_id(client_id), - } + self.as_local() + .and_then(|store| store.session_by_client_id(client_id)) } pub fn client_by_id( @@ -372,10 +285,10 @@ impl DapStore { client_id: &DebugAdapterClientId, cx: &ModelContext, ) -> Option<(Model, Arc)> { - let local_session = self.session_by_client_id(client_id)?; - let client = local_session.read(cx).as_local()?.client_by_id(client_id)?; + let session = self.session_by_client_id(client_id)?; + let client = session.read(cx).client_by_id(client_id)?; - Some((local_session, client)) + Some((session, client)) } pub fn capabilities_by_id(&self, client_id: &DebugAdapterClientId) -> Capabilities { @@ -458,50 +371,7 @@ impl DapStore { &self.breakpoints } - async fn handle_session_has_shutdown( - this: Model, - envelope: TypedEnvelope, - mut cx: AsyncAppContext, - ) -> Result<()> { - this.update(&mut cx, |this, cx| { - this.remove_session(DebugSessionId::from_proto(envelope.payload.session_id), cx); - })?; - - Ok(()) - } - - async fn handle_ignore_breakpoint_state( - this: Model, - envelope: TypedEnvelope, - mut cx: AsyncAppContext, - ) -> Result<()> { - let session_id = DebugSessionId::from_proto(envelope.payload.session_id); - - this.update(&mut cx, |this, cx| { - if let Some(session) = this.session_by_id(&session_id) { - session.update(cx, |session, cx| { - session.set_ignore_breakpoints(envelope.payload.ignore, cx) - }); - } - })?; - - Ok(()) - } - - pub fn set_ignore_breakpoints( - &mut self, - session_id: &DebugSessionId, - ignore: bool, - cx: &mut ModelContext, - ) { - if let Some(session) = self.session_by_id(session_id) { - session.update(cx, |session, cx| { - session.set_ignore_breakpoints(ignore, cx); - }); - } - } - - pub fn ignore_breakpoints(&self, session_id: &DebugSessionId, cx: &AppContext) -> bool { + pub fn ignore_breakpoints(&self, session_id: &DebugSessionId, cx: &ModelContext) -> bool { self.session_by_id(session_id) .map(|session| session.read(cx).ignore_breakpoints()) .unwrap_or_default() @@ -647,15 +517,13 @@ impl DapStore { let session = store.session_by_id(&session_id).unwrap(); session.update(cx, |session, cx| { - let local_session = session.as_local_mut().unwrap(); - - local_session.update_configuration( + session.update_configuration( |old_config| { *old_config = config.clone(); }, cx, ); - local_session.add_client(Arc::new(client), cx); + session.add_client(Arc::new(client), cx); }); // don't emit this event ourself in tests, so we can add request, @@ -778,7 +646,7 @@ impl DapStore { self.start_client_internal(session_id, worktree, config.clone(), cx); cx.spawn(|this, mut cx| async move { - let session = cx.new_model(|_| DebugSession::new_local(session_id, config))?; + let session = cx.new_model(|_| DebugSession::new(session_id, config))?; let client = match start_client_task.await { Ok(client) => client, @@ -794,10 +662,7 @@ impl DapStore { this.update(&mut cx, |store, cx| { session.update(cx, |session, cx| { - session - .as_local_mut() - .unwrap() - .add_client(client.clone(), cx); + session.add_client(client.clone(), cx); }); let client_id = client.id(); @@ -873,7 +738,7 @@ impl DapStore { ))); }; - let config = session.read(cx).as_local().unwrap().configuration(); + let config = session.read(cx).configuration(); let mut adapter_args = client.adapter().request_args(&config); if let Some(args) = config.initialize_args.clone() { merge_json_value_into(args, &mut adapter_args); @@ -920,7 +785,7 @@ impl DapStore { // comes in we send another `attach` request with the already selected PID // If we don't do this the user has to select the process twice if the adapter sends a `startDebugging` request session.update(cx, |session, cx| { - session.as_local_mut().unwrap().update_configuration( + session.update_configuration( |config| { config.request = DebugRequestType::Attach(task::AttachConfig { process_id: Some(process_id), @@ -930,7 +795,7 @@ impl DapStore { ); }); - let config = session.read(cx).as_local().unwrap().configuration(); + let config = session.read(cx).configuration(); let mut adapter_args = client.adapter().request_args(&config); if let Some(args) = config.initialize_args.clone() { @@ -1097,15 +962,8 @@ impl DapStore { ))); }; - let Some(config) = session - .read(cx) - .as_local() - .map(|session| session.configuration()) - else { - return Task::ready(Err(anyhow!("Cannot find debug session: {:?}", session_id))); - }; - let session_id = *session_id; + let config = session.read(cx).configuration().clone(); let request_args = args.unwrap_or_else(|| StartDebuggingRequestArguments { configuration: config.initialize_args.clone().unwrap_or_default(), @@ -1116,17 +974,17 @@ impl DapStore { }); // Merge the new configuration over the existing configuration - let mut initialize_args = config.initialize_args.clone().unwrap_or_default(); + let mut initialize_args = config.initialize_args.unwrap_or_default(); merge_json_value_into(request_args.configuration, &mut initialize_args); let new_config = DebugAdapterConfig { label: config.label.clone(), kind: config.kind.clone(), - request: match &request_args.request { + request: match request_args.request { StartDebuggingRequestArgumentsRequest::Launch => DebugRequestType::Launch, StartDebuggingRequestArgumentsRequest::Attach => DebugRequestType::Attach( - if let DebugRequestType::Attach(attach_config) = &config.request { - attach_config.clone() + if let DebugRequestType::Attach(attach_config) = config.request { + attach_config } else { AttachConfig::default() }, @@ -1664,27 +1522,11 @@ impl DapStore { return Task::ready(Err(anyhow!("Could not find session: {:?}", session_id))); }; - let Some(local_session) = session.read(cx).as_local() else { - return Task::ready(Err(anyhow!( - "Cannot shutdown session on remote side: {:?}", - session_id - ))); - }; - let mut tasks = Vec::new(); - for client in local_session.clients().collect::>() { + for client in session.read(cx).clients().collect::>() { tasks.push(self.shutdown_client(&session, client, cx)); } - if let Some((downstream_client, project_id)) = self.downstream_client.as_ref() { - downstream_client - .send(proto::DebuggerSessionEnded { - project_id: *project_id, - session_id: session_id.to_proto(), - }) - .log_err(); - } - cx.background_executor().spawn(async move { futures::future::join_all(tasks).await; Ok(()) @@ -1746,13 +1588,11 @@ impl DapStore { debug_sessions: Vec, cx: &mut ModelContext, ) { - for session in debug_sessions.into_iter() { - let session_id = DebugSessionId::from_proto(session.session_id); - let ignore_breakpoints = Some(session.ignore_breakpoints); - - self.add_remote_session(session_id, ignore_breakpoints, cx); - - for debug_client in session.clients { + for (session_id, debug_clients) in debug_sessions + .into_iter() + .map(|session| (session.session_id, session.clients)) + { + for debug_client in debug_clients { if let DapStoreMode::Remote(remote) = &mut self.mode { if let Some(queue) = &mut remote.event_queue { debug_client.debug_panel_items.into_iter().for_each(|item| { @@ -1761,13 +1601,9 @@ impl DapStore { } } - let client = DebugAdapterClientId::from_proto(debug_client.client_id); - - self.add_client_to_session(session_id, client); - self.update_capabilities_for_client( - &session_id, - &client, + &DebugSessionId::from_proto(session_id), + &DebugAdapterClientId::from_proto(debug_client.client_id), &dap::proto_conversions::capabilities_from_proto( &debug_client.capabilities.unwrap_or_default(), ), @@ -1804,7 +1640,7 @@ impl DapStore { cx.notify(); } - async fn handle_shutdown_session_request( + async fn handle_shutdown_session( this: Model, envelope: TypedEnvelope, mut cx: AsyncAppContext, @@ -2092,11 +1928,8 @@ impl DapStore { .collect::>(); let mut tasks = Vec::new(); - for session in local_store - .sessions - .values() - .filter_map(|session| session.read(cx).as_local()) - { + for session in local_store.sessions.values() { + let session = session.read(cx); let ignore_breakpoints = self.ignore_breakpoints(&session.id(), cx); for client in session.clients().collect::>() { tasks.push(self.send_breakpoints( diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 8f7ae15f0d73e1..74da6fb446b00a 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -634,8 +634,6 @@ impl Project { client.add_model_request_handler(WorktreeStore::handle_rename_project_entry); - client.add_model_message_handler(Self::handle_toggle_ignore_breakpoints); - WorktreeStore::init(&client); BufferStore::init(&client); LspStore::init(&client); @@ -1398,26 +1396,6 @@ impl Project { result } - async fn handle_toggle_ignore_breakpoints( - this: Model, - envelope: TypedEnvelope, - mut cx: AsyncAppContext, - ) -> Result<()> { - this.update(&mut cx, |project, cx| { - // Only the host should handle this message because the host - // handles direct communication with the debugger servers. - if let Some((_, _)) = project.dap_store.read(cx).downstream_client() { - project - .toggle_ignore_breakpoints( - &DebugSessionId::from_proto(envelope.payload.session_id), - &DebugAdapterClientId::from_proto(envelope.payload.client_id), - cx, - ) - .detach_and_log_err(cx); - } - }) - } - pub fn toggle_ignore_breakpoints( &self, session_id: &DebugSessionId, @@ -1425,30 +1403,8 @@ impl Project { cx: &mut ModelContext, ) -> Task> { let tasks = self.dap_store.update(cx, |store, cx| { - if let Some((upstream_client, project_id)) = store.upstream_client() { - upstream_client - .send(proto::ToggleIgnoreBreakpoints { - session_id: session_id.to_proto(), - client_id: client_id.to_proto(), - project_id, - }) - .log_err(); - - return Vec::new(); - } - store.toggle_ignore_breakpoints(session_id, cx); - if let Some((downstream_client, project_id)) = store.downstream_client() { - downstream_client - .send(proto::IgnoreBreakpointState { - session_id: session_id.to_proto(), - project_id: *project_id, - ignore: store.ignore_breakpoints(session_id, cx), - }) - .log_err(); - } - let mut tasks = Vec::new(); for (project_path, breakpoints) in store.breakpoints() { diff --git a/crates/proto/proto/zed.proto b/crates/proto/proto/zed.proto index b6e6690d601b3a..52426ac69e882d 100644 --- a/crates/proto/proto/zed.proto +++ b/crates/proto/proto/zed.proto @@ -331,10 +331,7 @@ message Envelope { UpdateThreadStatus update_thread_status = 310; VariablesRequest variables_request = 311; DapVariables dap_variables = 312; - DapRestartStackFrameRequest dap_restart_stack_frame_request = 313; - IgnoreBreakpointState ignore_breakpoint_state = 314; - ToggleIgnoreBreakpoints toggle_ignore_breakpoints = 315; - DebuggerSessionEnded debugger_session_ended = 316; // current max + DapRestartStackFrameRequest dap_restart_stack_frame_request = 313; // current max } reserved 87 to 88; @@ -2474,16 +2471,10 @@ enum BreakpointKind { Log = 1; } -message DebuggerSessionEnded { - uint64 project_id = 1; - uint64 session_id = 2; -} - message DebuggerSession { uint64 session_id = 1; uint64 project_id = 2; - bool ignore_breakpoints = 3; - repeated DebugClient clients = 4; + repeated DebugClient clients = 3; } message DebugClient { @@ -2718,18 +2709,6 @@ message DapShutdownSession { optional uint64 session_id = 2; // Shutdown all sessions if this is None } -message ToggleIgnoreBreakpoints { - uint64 project_id = 1; - uint64 client_id = 2; - uint64 session_id = 3; -} - -message IgnoreBreakpointState { - uint64 project_id = 1; - uint64 session_id = 2; - bool ignore = 3; -} - message DapNextRequest { uint64 project_id = 1; uint64 client_id = 2; diff --git a/crates/proto/src/proto.rs b/crates/proto/src/proto.rs index a79bcf08183941..0af3cbaf4cb548 100644 --- a/crates/proto/src/proto.rs +++ b/crates/proto/src/proto.rs @@ -397,9 +397,6 @@ messages!( (UsersResponse, Foreground), (VariablesRequest, Background), (DapVariables, Background), - (IgnoreBreakpointState, Background), - (ToggleIgnoreBreakpoints, Background), - (DebuggerSessionEnded, Background), ); request_messages!( @@ -647,9 +644,6 @@ entity_messages!( DapShutdownSession, UpdateThreadStatus, VariablesRequest, - IgnoreBreakpointState, - ToggleIgnoreBreakpoints, - DebuggerSessionEnded, ); entity_messages!( From 59a480dcff0a880231868b03fabac79bed7e9a28 Mon Sep 17 00:00:00 2001 From: Remco Smits Date: Wed, 22 Jan 2025 13:23:37 +0100 Subject: [PATCH 13/22] Revert "Revert debugger branch merge" This reverts commit 56c883d4dba4877826ea2185a8177fddefa0d054. --- .../20221109000000_test_schema.sql | 1 + .../20250121181012_add_ignore_breakpoints.sql | 2 + crates/collab/src/db/queries/projects.rs | 39 ++ crates/collab/src/db/tables/debug_clients.rs | 1 + crates/collab/src/rpc.rs | 29 ++ crates/collab/src/tests/debug_panel_tests.rs | 490 ++++++++++++++++++ crates/dap/src/session.rs | 117 +++-- crates/debugger_tools/src/dap_log.rs | 41 +- crates/debugger_ui/src/debugger_panel.rs | 19 +- crates/debugger_ui/src/debugger_panel_item.rs | 6 + .../debugger_ui/src/tests/debugger_panel.rs | 4 +- crates/project/src/dap_store.rs | 233 +++++++-- crates/project/src/project.rs | 44 ++ crates/proto/proto/zed.proto | 25 +- crates/proto/src/proto.rs | 6 + 15 files changed, 969 insertions(+), 88 deletions(-) create mode 100644 crates/collab/migrations/20250121181012_add_ignore_breakpoints.sql diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index 5a065878af4ab7..c36207514aed29 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -474,6 +474,7 @@ CREATE TABLE IF NOT EXISTS "debug_clients" ( project_id INTEGER NOT NULL, session_id BIGINT NOT NULL, capabilities INTEGER NOT NULL, + ignore_breakpoints BOOLEAN NOT NULL DEFAULT FALSE, PRIMARY KEY (id, project_id, session_id), FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE ); diff --git a/crates/collab/migrations/20250121181012_add_ignore_breakpoints.sql b/crates/collab/migrations/20250121181012_add_ignore_breakpoints.sql new file mode 100644 index 00000000000000..e1e362c5cf84ad --- /dev/null +++ b/crates/collab/migrations/20250121181012_add_ignore_breakpoints.sql @@ -0,0 +1,2 @@ + +ALTER TABLE debug_clients ADD COLUMN ignore_breakpoints BOOLEAN NOT NULL DEFAULT FALSE; diff --git a/crates/collab/src/db/queries/projects.rs b/crates/collab/src/db/queries/projects.rs index da0d99432be96a..b25c754d83c7a6 100644 --- a/crates/collab/src/db/queries/projects.rs +++ b/crates/collab/src/db/queries/projects.rs @@ -556,6 +556,40 @@ impl Database { .await } + pub async fn ignore_breakpoint_state( + &self, + connection_id: ConnectionId, + update: &proto::IgnoreBreakpointState, + ) -> Result>> { + let project_id = ProjectId::from_proto(update.project_id); + self.project_transaction(project_id, |tx| async move { + let debug_clients = debug_clients::Entity::find() + .filter( + Condition::all() + .add(debug_clients::Column::ProjectId.eq(project_id)) + .add(debug_clients::Column::SessionId.eq(update.session_id)), + ) + .all(&*tx) + .await?; + + for debug_client in debug_clients { + debug_clients::Entity::update(debug_clients::ActiveModel { + id: ActiveValue::Unchanged(debug_client.id), + project_id: ActiveValue::Unchanged(debug_client.project_id), + session_id: ActiveValue::Unchanged(debug_client.session_id), + capabilities: ActiveValue::Unchanged(debug_client.capabilities), + ignore_breakpoints: ActiveValue::Set(update.ignore), + }) + .exec(&*tx) + .await?; + } + + self.internal_project_connection_ids(project_id, connection_id, true, &tx) + .await + }) + .await + } + pub async fn update_debug_adapter( &self, connection_id: ConnectionId, @@ -647,6 +681,7 @@ impl Database { project_id: ActiveValue::Set(project_id), session_id: ActiveValue::Set(update.session_id as i64), capabilities: ActiveValue::Set(0), + ignore_breakpoints: ActiveValue::Set(false), }; new_debug_client.insert(&*tx).await?; } @@ -729,6 +764,7 @@ impl Database { project_id: ActiveValue::Set(project_id), session_id: ActiveValue::Set(update.session_id as i64), capabilities: ActiveValue::Set(0), + ignore_breakpoints: ActiveValue::Set(false), }; debug_client = Some(new_debug_client.insert(&*tx).await?); } @@ -742,6 +778,7 @@ impl Database { project_id: ActiveValue::Unchanged(debug_client.project_id), session_id: ActiveValue::Unchanged(debug_client.session_id), capabilities: ActiveValue::Set(debug_client.capabilities), + ignore_breakpoints: ActiveValue::Set(debug_client.ignore_breakpoints), }) .exec(&*tx) .await?; @@ -1086,6 +1123,7 @@ impl Database { for (session_id, clients) in debug_sessions.into_iter() { let mut debug_clients = Vec::default(); + let ignore_breakpoints = clients.iter().any(|debug| debug.ignore_breakpoints); // Temp solution until client -> session change for debug_client in clients.into_iter() { let debug_panel_items = debug_client @@ -1108,6 +1146,7 @@ impl Database { project_id, session_id: session_id as u64, clients: debug_clients, + ignore_breakpoints, }); } diff --git a/crates/collab/src/db/tables/debug_clients.rs b/crates/collab/src/db/tables/debug_clients.rs index 02758acaa0c4fa..498b9ed7359091 100644 --- a/crates/collab/src/db/tables/debug_clients.rs +++ b/crates/collab/src/db/tables/debug_clients.rs @@ -25,6 +25,7 @@ pub struct Model { pub session_id: i64, #[sea_orm(column_type = "Integer")] pub capabilities: i32, + pub ignore_breakpoints: bool, } impl Model { diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index cf7083a21c1a99..fa116c8cc54938 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -438,6 +438,13 @@ impl Server { .add_request_handler(forward_mutating_project_request::) .add_message_handler( broadcast_project_message_from_host::, + ) + .add_message_handler( + broadcast_project_message_from_host::, + ) + .add_message_handler(ignore_breakpoint_state) + .add_message_handler( + broadcast_project_message_from_host::, ); Arc::new(server) @@ -2155,6 +2162,28 @@ async fn shutdown_debug_client( Ok(()) } +async fn ignore_breakpoint_state( + request: proto::IgnoreBreakpointState, + session: Session, +) -> Result<()> { + let guest_connection_ids = session + .db() + .await + .ignore_breakpoint_state(session.connection_id, &request) + .await?; + + broadcast( + Some(session.connection_id), + guest_connection_ids.iter().copied(), + |connection_id| { + session + .peer + .forward_send(session.connection_id, connection_id, request.clone()) + }, + ); + Ok(()) +} + /// Notify other participants that a debug panel item has been updated async fn update_debug_adapter(request: proto::UpdateDebugAdapter, session: Session) -> Result<()> { let guest_connection_ids = session diff --git a/crates/collab/src/tests/debug_panel_tests.rs b/crates/collab/src/tests/debug_panel_tests.rs index 40c2a5af97a59e..c90144b4e94230 100644 --- a/crates/collab/src/tests/debug_panel_tests.rs +++ b/crates/collab/src/tests/debug_panel_tests.rs @@ -1836,3 +1836,493 @@ async fn test_variable_list( shutdown_client.await.unwrap(); } + +#[gpui::test] +async fn test_ignore_breakpoints( + cx_a: &mut TestAppContext, + cx_b: &mut TestAppContext, + cx_c: &mut TestAppContext, +) { + let executor = cx_a.executor(); + let mut server = TestServer::start(executor.clone()).await; + let client_a = server.create_client(cx_a, "user_a").await; + let client_b = server.create_client(cx_b, "user_b").await; + let client_c = server.create_client(cx_c, "user_c").await; + + client_a + .fs() + .insert_tree( + "/project", + json!({ + "test.txt": "one\ntwo\nthree\nfour\nfive", + }), + ) + .await; + + init_test(cx_a); + init_test(cx_b); + init_test(cx_c); + + server + .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)]) + .await; + let active_call_a = cx_a.read(ActiveCall::global); + let active_call_b = cx_b.read(ActiveCall::global); + let active_call_c = cx_c.read(ActiveCall::global); + + let (project_a, worktree_id) = client_a.build_local_project("/project", cx_a).await; + active_call_a + .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx)) + .await + .unwrap(); + + let project_path = ProjectPath { + worktree_id, + path: Arc::from(Path::new(&"test.txt")), + }; + + let project_id = active_call_a + .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx)) + .await + .unwrap(); + let project_b = client_b.join_remote_project(project_id, cx_b).await; + active_call_b + .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx)) + .await + .unwrap(); + + let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a); + let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b); + + add_debugger_panel(&workspace_a, cx_a).await; + add_debugger_panel(&workspace_b, cx_b).await; + + let local_editor = workspace_a + .update(cx_a, |workspace, cx| { + workspace.open_path(project_path.clone(), None, true, cx) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + + local_editor.update(cx_a, |editor, cx| { + editor.move_down(&editor::actions::MoveDown, cx); + editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, cx); // Line 2 + editor.move_down(&editor::actions::MoveDown, cx); + editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, cx); // Line 3 + }); + + cx_a.run_until_parked(); + cx_b.run_until_parked(); + + let task = project_a.update(cx_a, |project, cx| { + project.start_debug_session( + dap::DebugAdapterConfig { + label: "test config".into(), + kind: dap::DebugAdapterKind::Fake, + request: dap::DebugRequestType::Launch, + program: None, + cwd: None, + initialize_args: None, + }, + cx, + ) + }); + + let (session, client) = task.await.unwrap(); + let client_id = client.id(); + + client + .on_request::(move |_, _| { + Ok(dap::Capabilities { + supports_configuration_done_request: Some(true), + ..Default::default() + }) + }) + .await; + + let called_set_breakpoints = Arc::new(AtomicBool::new(false)); + client + .on_request::({ + let called_set_breakpoints = called_set_breakpoints.clone(); + move |_, args| { + assert_eq!("/project/test.txt", args.source.path.unwrap()); + + let mut actual_breakpoints = args.breakpoints.unwrap(); + actual_breakpoints.sort_by_key(|b| b.line); + + let expected_breakpoints = vec![ + SourceBreakpoint { + line: 2, + column: None, + condition: None, + hit_condition: None, + log_message: None, + mode: None, + }, + SourceBreakpoint { + line: 3, + column: None, + condition: None, + hit_condition: None, + log_message: None, + mode: None, + }, + ]; + + assert_eq!(actual_breakpoints, expected_breakpoints); + + called_set_breakpoints.store(true, Ordering::SeqCst); + + Ok(dap::SetBreakpointsResponse { + breakpoints: Vec::default(), + }) + } + }) + .await; + + client.on_request::(move |_, _| Ok(())).await; + client + .on_request::(move |_, _| { + Ok(dap::StackTraceResponse { + stack_frames: Vec::default(), + total_frames: None, + }) + }) + .await; + + client.on_request::(move |_, _| Ok(())).await; + + client + .fake_event(dap::messages::Events::Initialized(Some( + dap::Capabilities { + supports_configuration_done_request: Some(true), + ..Default::default() + }, + ))) + .await; + + cx_a.run_until_parked(); + cx_b.run_until_parked(); + + assert!( + called_set_breakpoints.load(std::sync::atomic::Ordering::SeqCst), + "SetBreakpoint request must be called when starting debug session" + ); + + client + .fake_event(dap::messages::Events::Stopped(dap::StoppedEvent { + reason: dap::StoppedEventReason::Pause, + description: None, + thread_id: Some(1), + preserve_focus_hint: None, + text: None, + all_threads_stopped: None, + hit_breakpoint_ids: None, + })) + .await; + + cx_a.run_until_parked(); + cx_b.run_until_parked(); + + let remote_debug_item = workspace_b.update(cx_b, |workspace, cx| { + let debug_panel = workspace.panel::(cx).unwrap(); + let active_debug_panel_item = debug_panel + .update(cx, |this, cx| this.active_debug_panel_item(cx)) + .unwrap(); + + assert_eq!( + 1, + debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len()) + ); + + let session_id = debug_panel.update(cx, |this, cx| { + this.dap_store() + .read(cx) + .as_remote() + .unwrap() + .session_by_client_id(&client.id()) + .unwrap() + .read(cx) + .id() + }); + + let breakpoints_ignored = active_debug_panel_item.read(cx).are_breakpoints_ignored(cx); + + assert_eq!(session_id, active_debug_panel_item.read(cx).session_id()); + assert_eq!(false, breakpoints_ignored); + assert_eq!(client.id(), active_debug_panel_item.read(cx).client_id()); + assert_eq!(1, active_debug_panel_item.read(cx).thread_id()); + active_debug_panel_item + }); + + called_set_breakpoints.store(false, Ordering::SeqCst); + + client + .on_request::({ + let called_set_breakpoints = called_set_breakpoints.clone(); + move |_, args| { + assert_eq!("/project/test.txt", args.source.path.unwrap()); + assert_eq!(args.breakpoints, Some(vec![])); + + called_set_breakpoints.store(true, Ordering::SeqCst); + + Ok(dap::SetBreakpointsResponse { + breakpoints: Vec::default(), + }) + } + }) + .await; + + let local_debug_item = workspace_a.update(cx_a, |workspace, cx| { + let debug_panel = workspace.panel::(cx).unwrap(); + let active_debug_panel_item = debug_panel + .update(cx, |this, cx| this.active_debug_panel_item(cx)) + .unwrap(); + + assert_eq!( + 1, + debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len()) + ); + + assert_eq!( + false, + active_debug_panel_item.read(cx).are_breakpoints_ignored(cx) + ); + assert_eq!(client.id(), active_debug_panel_item.read(cx).client_id()); + assert_eq!(1, active_debug_panel_item.read(cx).thread_id()); + + active_debug_panel_item + }); + + local_debug_item.update(cx_a, |item, cx| { + item.toggle_ignore_breakpoints(cx); // Set to true + assert_eq!(true, item.are_breakpoints_ignored(cx)); + }); + + cx_a.run_until_parked(); + cx_b.run_until_parked(); + + assert!( + called_set_breakpoints.load(std::sync::atomic::Ordering::SeqCst), + "SetBreakpoint request must be called to ignore breakpoints" + ); + + client + .on_request::({ + let called_set_breakpoints = called_set_breakpoints.clone(); + move |_, _args| { + called_set_breakpoints.store(true, Ordering::SeqCst); + + Ok(dap::SetBreakpointsResponse { + breakpoints: Vec::default(), + }) + } + }) + .await; + + let remote_editor = workspace_b + .update(cx_b, |workspace, cx| { + workspace.open_path(project_path.clone(), None, true, cx) + }) + .await + .unwrap() + .downcast::() + .unwrap(); + + called_set_breakpoints.store(false, std::sync::atomic::Ordering::SeqCst); + + remote_editor.update(cx_b, |editor, cx| { + editor.toggle_breakpoint(&editor::actions::ToggleBreakpoint, cx); // Line 1 + }); + + cx_a.run_until_parked(); + cx_b.run_until_parked(); + + assert!( + called_set_breakpoints.load(std::sync::atomic::Ordering::SeqCst), + "SetBreakpoint request be called whenever breakpoints are toggled but with not breakpoints" + ); + + remote_debug_item.update(cx_b, |debug_panel, cx| { + let breakpoints_ignored = debug_panel.are_breakpoints_ignored(cx); + + assert_eq!(true, breakpoints_ignored); + assert_eq!(client.id(), debug_panel.client_id()); + assert_eq!(1, debug_panel.thread_id()); + }); + + client + .on_request::({ + let called_set_breakpoints = called_set_breakpoints.clone(); + move |_, args| { + assert_eq!("/project/test.txt", args.source.path.unwrap()); + + let mut actual_breakpoints = args.breakpoints.unwrap(); + actual_breakpoints.sort_by_key(|b| b.line); + + let expected_breakpoints = vec![ + SourceBreakpoint { + line: 1, + column: None, + condition: None, + hit_condition: None, + log_message: None, + mode: None, + }, + SourceBreakpoint { + line: 2, + column: None, + condition: None, + hit_condition: None, + log_message: None, + mode: None, + }, + SourceBreakpoint { + line: 3, + column: None, + condition: None, + hit_condition: None, + log_message: None, + mode: None, + }, + ]; + + assert_eq!(actual_breakpoints, expected_breakpoints); + + called_set_breakpoints.store(true, Ordering::SeqCst); + + Ok(dap::SetBreakpointsResponse { + breakpoints: Vec::default(), + }) + } + }) + .await; + + let project_c = client_c.join_remote_project(project_id, cx_c).await; + active_call_c + .update(cx_c, |call, cx| call.set_location(Some(&project_c), cx)) + .await + .unwrap(); + + let (workspace_c, cx_c) = client_c.build_workspace(&project_c, cx_c); + add_debugger_panel(&workspace_c, cx_c).await; + + let last_join_remote_item = workspace_c.update(cx_c, |workspace, cx| { + let debug_panel = workspace.panel::(cx).unwrap(); + let active_debug_panel_item = debug_panel + .update(cx, |this, cx| this.active_debug_panel_item(cx)) + .unwrap(); + + let breakpoints_ignored = active_debug_panel_item.read(cx).are_breakpoints_ignored(cx); + + assert_eq!(true, breakpoints_ignored); + + assert_eq!( + 1, + debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len()) + ); + assert_eq!(client.id(), active_debug_panel_item.read(cx).client_id()); + assert_eq!(1, active_debug_panel_item.read(cx).thread_id()); + active_debug_panel_item + }); + + remote_debug_item.update(cx_b, |item, cx| { + item.toggle_ignore_breakpoints(cx); + }); + + cx_a.run_until_parked(); + cx_b.run_until_parked(); + cx_c.run_until_parked(); + + assert!( + called_set_breakpoints.load(std::sync::atomic::Ordering::SeqCst), + "SetBreakpoint request should be called to update breakpoints" + ); + + client + .on_request::({ + let called_set_breakpoints = called_set_breakpoints.clone(); + move |_, args| { + assert_eq!("/project/test.txt", args.source.path.unwrap()); + assert_eq!(args.breakpoints, Some(vec![])); + + called_set_breakpoints.store(true, Ordering::SeqCst); + + Ok(dap::SetBreakpointsResponse { + breakpoints: Vec::default(), + }) + } + }) + .await; + + local_debug_item.update(cx_a, |debug_panel_item, cx| { + assert_eq!( + false, + debug_panel_item.are_breakpoints_ignored(cx), + "Remote client set this to false" + ); + }); + + remote_debug_item.update(cx_b, |debug_panel_item, cx| { + assert_eq!( + false, + debug_panel_item.are_breakpoints_ignored(cx), + "Remote client set this to false" + ); + }); + + last_join_remote_item.update(cx_c, |debug_panel_item, cx| { + assert_eq!( + false, + debug_panel_item.are_breakpoints_ignored(cx), + "Remote client set this to false" + ); + }); + + let shutdown_client = project_a.update(cx_a, |project, cx| { + project.dap_store().update(cx, |dap_store, cx| { + dap_store.shutdown_session(&session.read(cx).id(), cx) + }) + }); + + shutdown_client.await.unwrap(); + + cx_a.run_until_parked(); + cx_b.run_until_parked(); + + project_b.update(cx_b, |project, cx| { + project.dap_store().update(cx, |dap_store, _cx| { + let sessions = dap_store.sessions().collect::>(); + + assert_eq!( + None, + dap_store.session_by_client_id(&client_id), + "No client_id to session mapping should exist after shutdown" + ); + assert_eq!( + 0, + sessions.len(), + "No sessions should be left after shutdown" + ); + }) + }); + + project_c.update(cx_c, |project, cx| { + project.dap_store().update(cx, |dap_store, _cx| { + let sessions = dap_store.sessions().collect::>(); + + assert_eq!( + None, + dap_store.session_by_client_id(&client_id), + "No client_id to session mapping should exist after shutdown" + ); + assert_eq!( + 0, + sessions.len(), + "No sessions should be left after shutdown" + ); + }) + }); +} diff --git a/crates/dap/src/session.rs b/crates/dap/src/session.rs index 77e07a76a526e0..745b0ec5244f53 100644 --- a/crates/dap/src/session.rs +++ b/crates/dap/src/session.rs @@ -19,54 +19,37 @@ impl DebugSessionId { } } -pub struct DebugSession { +pub enum DebugSession { + Local(LocalDebugSession), + Remote(RemoteDebugSession), +} + +pub struct LocalDebugSession { id: DebugSessionId, ignore_breakpoints: bool, configuration: DebugAdapterConfig, clients: HashMap>, } -impl DebugSession { - pub fn new(id: DebugSessionId, configuration: DebugAdapterConfig) -> Self { - Self { - id, - configuration, - ignore_breakpoints: false, - clients: HashMap::default(), - } - } - - pub fn id(&self) -> DebugSessionId { - self.id - } - - pub fn name(&self) -> String { - self.configuration.label.clone() - } - +impl LocalDebugSession { pub fn configuration(&self) -> &DebugAdapterConfig { &self.configuration } - pub fn ignore_breakpoints(&self) -> bool { - self.ignore_breakpoints - } - - pub fn set_ignore_breakpoints(&mut self, ignore: bool, cx: &mut ModelContext) { - self.ignore_breakpoints = ignore; - cx.notify(); - } - pub fn update_configuration( &mut self, f: impl FnOnce(&mut DebugAdapterConfig), - cx: &mut ModelContext, + cx: &mut ModelContext, ) { f(&mut self.configuration); cx.notify(); } - pub fn add_client(&mut self, client: Arc, cx: &mut ModelContext) { + pub fn add_client( + &mut self, + client: Arc, + cx: &mut ModelContext, + ) { self.clients.insert(client.id(), client); cx.notify(); } @@ -74,7 +57,7 @@ impl DebugSession { pub fn remove_client( &mut self, client_id: &DebugAdapterClientId, - cx: &mut ModelContext, + cx: &mut ModelContext, ) -> Option> { let client = self.clients.remove(client_id); cx.notify(); @@ -101,4 +84,76 @@ impl DebugSession { pub fn client_ids(&self) -> impl Iterator + '_ { self.clients.keys().cloned() } + + pub fn id(&self) -> DebugSessionId { + self.id + } +} + +pub struct RemoteDebugSession { + id: DebugSessionId, + ignore_breakpoints: bool, + label: String, +} + +impl DebugSession { + pub fn new_local(id: DebugSessionId, configuration: DebugAdapterConfig) -> Self { + Self::Local(LocalDebugSession { + id, + ignore_breakpoints: false, + configuration, + clients: HashMap::default(), + }) + } + + pub fn as_local(&self) -> Option<&LocalDebugSession> { + match self { + DebugSession::Local(local) => Some(local), + _ => None, + } + } + + pub fn as_local_mut(&mut self) -> Option<&mut LocalDebugSession> { + match self { + DebugSession::Local(local) => Some(local), + _ => None, + } + } + + pub fn new_remote(id: DebugSessionId, label: String, ignore_breakpoints: bool) -> Self { + Self::Remote(RemoteDebugSession { + id, + label: label.clone(), + ignore_breakpoints, + }) + } + + pub fn id(&self) -> DebugSessionId { + match self { + DebugSession::Local(local) => local.id, + DebugSession::Remote(remote) => remote.id, + } + } + + pub fn name(&self) -> String { + match self { + DebugSession::Local(local) => local.configuration.label.clone(), + DebugSession::Remote(remote) => remote.label.clone(), + } + } + + pub fn ignore_breakpoints(&self) -> bool { + match self { + DebugSession::Local(local) => local.ignore_breakpoints, + DebugSession::Remote(remote) => remote.ignore_breakpoints, + } + } + + pub fn set_ignore_breakpoints(&mut self, ignore: bool, cx: &mut ModelContext) { + match self { + DebugSession::Local(local) => local.ignore_breakpoints = ignore, + DebugSession::Remote(remote) => remote.ignore_breakpoints = ignore, + } + cx.notify(); + } } diff --git a/crates/debugger_tools/src/dap_log.rs b/crates/debugger_tools/src/dap_log.rs index c315a12384833c..b111ecb1f8bb93 100644 --- a/crates/debugger_tools/src/dap_log.rs +++ b/crates/debugger_tools/src/dap_log.rs @@ -580,25 +580,28 @@ impl DapLogView { .dap_store() .read(cx) .sessions() - .map(|session| DapMenuItem { - session_id: session.read(cx).id(), - session_name: session.read(cx).name(), - clients: { - let mut clients = session - .read(cx) - .clients() - .map(|client| DapMenuSubItem { - client_id: client.id(), - client_name: client.adapter_id(), - has_adapter_logs: client.has_adapter_logs(), - selected_entry: self - .current_view - .map_or(LogKind::Adapter, |(_, kind)| kind), - }) - .collect::>(); - clients.sort_by_key(|item| item.client_id.0); - clients - }, + .filter_map(|session| { + Some(DapMenuItem { + session_id: session.read(cx).id(), + session_name: session.read(cx).name(), + clients: { + let mut clients = session + .read(cx) + .as_local()? + .clients() + .map(|client| DapMenuSubItem { + client_id: client.id(), + client_name: client.adapter_id(), + has_adapter_logs: client.has_adapter_logs(), + selected_entry: self + .current_view + .map_or(LogKind::Adapter, |(_, kind)| kind), + }) + .collect::>(); + clients.sort_by_key(|item| item.client_id.0); + clients + }, + }) }) .collect::>(); menu_items.sort_by_key(|item| item.session_id.0); diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index ec343930ef45e2..9d588cd06e8138 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -292,6 +292,11 @@ impl DebugPanel { &self.message_queue } + #[cfg(any(test, feature = "test-support"))] + pub fn dap_store(&self) -> Model { + self.dap_store.clone() + } + pub fn active_debug_panel_item( &self, cx: &mut ViewContext, @@ -565,14 +570,19 @@ impl DebugPanel { client_id: &DebugAdapterClientId, cx: &mut ViewContext, ) { - let Some(session) = self.dap_store.read(cx).session_by_id(session_id) else { + let Some(session) = self + .dap_store + .read(cx) + .session_by_id(session_id) + .and_then(|session| session.read(cx).as_local()) + else { return; }; let session_id = *session_id; let client_id = *client_id; let workspace = self.workspace.clone(); - let request_type = session.read(cx).configuration().request.clone(); + let request_type = session.configuration().request.clone(); cx.spawn(|this, mut cx| async move { let task = this.update(&mut cx, |this, cx| { this.dap_store.update(cx, |store, cx| { @@ -1030,6 +1040,11 @@ impl DebugPanel { ) }); + self.dap_store.update(cx, |dap_store, cx| { + dap_store.add_remote_session(session_id, None, cx); + dap_store.add_client_to_session(session_id, client_id); + }); + pane.add_item(Box::new(debug_panel_item.clone()), true, true, None, cx); debug_panel_item }); diff --git a/crates/debugger_ui/src/debugger_panel_item.rs b/crates/debugger_ui/src/debugger_panel_item.rs index 35ee5957f41737..a645578f3fda1f 100644 --- a/crates/debugger_ui/src/debugger_panel_item.rs +++ b/crates/debugger_ui/src/debugger_panel_item.rs @@ -506,6 +506,12 @@ impl DebugPanelItem { &self.thread_state } + #[cfg(any(test, feature = "test-support"))] + pub fn are_breakpoints_ignored(&self, cx: &AppContext) -> bool { + self.dap_store + .read_with(cx, |dap, cx| dap.ignore_breakpoints(&self.session_id, cx)) + } + pub fn capabilities(&self, cx: &mut ViewContext) -> Capabilities { self.dap_store.read(cx).capabilities_by_id(&self.client_id) } diff --git a/crates/debugger_ui/src/tests/debugger_panel.rs b/crates/debugger_ui/src/tests/debugger_panel.rs index 33915218083af8..62e397e2ec3de6 100644 --- a/crates/debugger_ui/src/tests/debugger_panel.rs +++ b/crates/debugger_ui/src/tests/debugger_panel.rs @@ -749,7 +749,7 @@ async fn test_handle_start_debugging_reverse_request( cx.run_until_parked(); project.update(cx, |_, cx| { - assert_eq!(2, session.read(cx).clients_len()); + assert_eq!(2, session.read(cx).as_local().unwrap().clients_len()); }); assert!( send_response.load(std::sync::atomic::Ordering::SeqCst), @@ -759,6 +759,8 @@ async fn test_handle_start_debugging_reverse_request( let second_client = project.update(cx, |_, cx| { session .read(cx) + .as_local() + .unwrap() .client_by_id(&DebugAdapterClientId(1)) .unwrap() }); diff --git a/crates/project/src/dap_store.rs b/crates/project/src/dap_store.rs index 046b9a55814936..f107ee1ad01099 100644 --- a/crates/project/src/dap_store.rs +++ b/crates/project/src/dap_store.rs @@ -128,9 +128,22 @@ impl LocalDapStore { pub struct RemoteDapStore { upstream_client: Option, upstream_project_id: u64, + sessions: HashMap>, + client_by_session: HashMap, event_queue: Option>, } +impl RemoteDapStore { + pub fn session_by_client_id( + &self, + client_id: &DebugAdapterClientId, + ) -> Option> { + self.client_by_session + .get(client_id) + .and_then(|session_id| self.sessions.get(session_id).cloned()) + } +} + pub struct DapStore { mode: DapStoreMode, downstream_client: Option<(AnyProtoClient, u64)>, @@ -153,6 +166,8 @@ impl DapStore { client.add_model_message_handler(DapStore::handle_synchronize_breakpoints); client.add_model_message_handler(DapStore::handle_update_debug_adapter); client.add_model_message_handler(DapStore::handle_update_thread_status); + client.add_model_message_handler(DapStore::handle_ignore_breakpoint_state); + client.add_model_message_handler(DapStore::handle_session_has_shutdown); client.add_model_request_handler(DapStore::handle_dap_command::); client.add_model_request_handler(DapStore::handle_dap_command::); @@ -166,7 +181,7 @@ impl DapStore { client.add_model_request_handler(DapStore::handle_dap_command::); client.add_model_request_handler(DapStore::handle_dap_command::); client.add_model_request_handler(DapStore::handle_dap_command::); - client.add_model_request_handler(DapStore::handle_shutdown_session); + client.add_model_request_handler(DapStore::handle_shutdown_session_request); } pub fn new_local( @@ -205,6 +220,8 @@ impl DapStore { mode: DapStoreMode::Remote(RemoteDapStore { upstream_client: Some(upstream_client), upstream_project_id: project_id, + sessions: Default::default(), + client_by_session: Default::default(), event_queue: Some(VecDeque::default()), }), downstream_client: None, @@ -263,21 +280,91 @@ impl DapStore { self.downstream_client.as_ref() } + pub fn add_remote_session( + &mut self, + session_id: DebugSessionId, + ignore: Option, + cx: &mut ModelContext, + ) { + match &mut self.mode { + DapStoreMode::Remote(remote) => { + remote + .sessions + .entry(session_id) + .or_insert(cx.new_model(|_| { + DebugSession::new_remote( + session_id, + "Remote-Debug".to_owned(), + ignore.unwrap_or(false), + ) + })); + } + _ => {} + } + } + + pub fn add_client_to_session( + &mut self, + session_id: DebugSessionId, + client_id: DebugAdapterClientId, + ) { + match &mut self.mode { + DapStoreMode::Local(local) => { + if local.sessions.contains_key(&session_id) { + local.client_by_session.insert(client_id, session_id); + } + } + DapStoreMode::Remote(remote) => { + if remote.sessions.contains_key(&session_id) { + remote.client_by_session.insert(client_id, session_id); + } + } + } + } + + pub fn remove_session(&mut self, session_id: DebugSessionId, cx: &mut ModelContext) { + match &mut self.mode { + DapStoreMode::Local(local) => { + if let Some(session) = local.sessions.remove(&session_id) { + for client_id in session + .read(cx) + .as_local() + .map(|local| local.client_ids()) + .expect("Local Dap can only have local sessions") + { + local.client_by_session.remove(&client_id); + } + } + } + DapStoreMode::Remote(remote) => { + remote.sessions.remove(&session_id); + remote.client_by_session.retain(|_, val| val != &session_id) + } + } + } + pub fn sessions(&self) -> impl Iterator> + '_ { - self.as_local().unwrap().sessions.values().cloned() + match &self.mode { + DapStoreMode::Local(local) => local.sessions.values().cloned(), + DapStoreMode::Remote(remote) => remote.sessions.values().cloned(), + } } pub fn session_by_id(&self, session_id: &DebugSessionId) -> Option> { - self.as_local() - .and_then(|store| store.sessions.get(session_id).cloned()) + match &self.mode { + DapStoreMode::Local(local) => local.sessions.get(session_id).cloned(), + DapStoreMode::Remote(remote) => remote.sessions.get(session_id).cloned(), + } } pub fn session_by_client_id( &self, client_id: &DebugAdapterClientId, ) -> Option> { - self.as_local() - .and_then(|store| store.session_by_client_id(client_id)) + match &self.mode { + DapStoreMode::Local(local) => local.session_by_client_id(client_id), + DapStoreMode::Remote(remote) => remote.session_by_client_id(client_id), + } } pub fn client_by_id( @@ -285,10 +372,10 @@ impl DapStore { client_id: &DebugAdapterClientId, cx: &ModelContext, ) -> Option<(Model, Arc)> { - let session = self.session_by_client_id(client_id)?; - let client = session.read(cx).client_by_id(client_id)?; + let local_session = self.session_by_client_id(client_id)?; + let client = local_session.read(cx).as_local()?.client_by_id(client_id)?; - Some((session, client)) + Some((local_session, client)) } pub fn capabilities_by_id(&self, client_id: &DebugAdapterClientId) -> Capabilities { @@ -371,7 +458,50 @@ impl DapStore { &self.breakpoints } - pub fn ignore_breakpoints(&self, session_id: &DebugSessionId, cx: &ModelContext) -> bool { + async fn handle_session_has_shutdown( + this: Model, + envelope: TypedEnvelope, + mut cx: AsyncAppContext, + ) -> Result<()> { + this.update(&mut cx, |this, cx| { + this.remove_session(DebugSessionId::from_proto(envelope.payload.session_id), cx); + })?; + + Ok(()) + } + + async fn handle_ignore_breakpoint_state( + this: Model, + envelope: TypedEnvelope, + mut cx: AsyncAppContext, + ) -> Result<()> { + let session_id = DebugSessionId::from_proto(envelope.payload.session_id); + + this.update(&mut cx, |this, cx| { + if let Some(session) = this.session_by_id(&session_id) { + session.update(cx, |session, cx| { + session.set_ignore_breakpoints(envelope.payload.ignore, cx) + }); + } + })?; + + Ok(()) + } + + pub fn set_ignore_breakpoints( + &mut self, + session_id: &DebugSessionId, + ignore: bool, + cx: &mut ModelContext, + ) { + if let Some(session) = self.session_by_id(session_id) { + session.update(cx, |session, cx| { + session.set_ignore_breakpoints(ignore, cx); + }); + } + } + + pub fn ignore_breakpoints(&self, session_id: &DebugSessionId, cx: &AppContext) -> bool { self.session_by_id(session_id) .map(|session| session.read(cx).ignore_breakpoints()) .unwrap_or_default() @@ -517,13 +647,15 @@ impl DapStore { let session = store.session_by_id(&session_id).unwrap(); session.update(cx, |session, cx| { - session.update_configuration( + let local_session = session.as_local_mut().unwrap(); + + local_session.update_configuration( |old_config| { *old_config = config.clone(); }, cx, ); - session.add_client(Arc::new(client), cx); + local_session.add_client(Arc::new(client), cx); }); // don't emit this event ourself in tests, so we can add request, @@ -646,7 +778,7 @@ impl DapStore { self.start_client_internal(session_id, worktree, config.clone(), cx); cx.spawn(|this, mut cx| async move { - let session = cx.new_model(|_| DebugSession::new(session_id, config))?; + let session = cx.new_model(|_| DebugSession::new_local(session_id, config))?; let client = match start_client_task.await { Ok(client) => client, @@ -662,7 +794,10 @@ impl DapStore { this.update(&mut cx, |store, cx| { session.update(cx, |session, cx| { - session.add_client(client.clone(), cx); + session + .as_local_mut() + .unwrap() + .add_client(client.clone(), cx); }); let client_id = client.id(); @@ -738,7 +873,7 @@ impl DapStore { ))); }; - let config = session.read(cx).configuration(); + let config = session.read(cx).as_local().unwrap().configuration(); let mut adapter_args = client.adapter().request_args(&config); if let Some(args) = config.initialize_args.clone() { merge_json_value_into(args, &mut adapter_args); @@ -785,7 +920,7 @@ impl DapStore { // comes in we send another `attach` request with the already selected PID // If we don't do this the user has to select the process twice if the adapter sends a `startDebugging` request session.update(cx, |session, cx| { - session.update_configuration( + session.as_local_mut().unwrap().update_configuration( |config| { config.request = DebugRequestType::Attach(task::AttachConfig { process_id: Some(process_id), @@ -795,7 +930,7 @@ impl DapStore { ); }); - let config = session.read(cx).configuration(); + let config = session.read(cx).as_local().unwrap().configuration(); let mut adapter_args = client.adapter().request_args(&config); if let Some(args) = config.initialize_args.clone() { @@ -962,8 +1097,15 @@ impl DapStore { ))); }; + let Some(config) = session + .read(cx) + .as_local() + .map(|session| session.configuration()) + else { + return Task::ready(Err(anyhow!("Cannot find debug session: {:?}", session_id))); + }; + let session_id = *session_id; - let config = session.read(cx).configuration().clone(); let request_args = args.unwrap_or_else(|| StartDebuggingRequestArguments { configuration: config.initialize_args.clone().unwrap_or_default(), @@ -974,17 +1116,17 @@ impl DapStore { }); // Merge the new configuration over the existing configuration - let mut initialize_args = config.initialize_args.unwrap_or_default(); + let mut initialize_args = config.initialize_args.clone().unwrap_or_default(); merge_json_value_into(request_args.configuration, &mut initialize_args); let new_config = DebugAdapterConfig { label: config.label.clone(), kind: config.kind.clone(), - request: match request_args.request { + request: match &request_args.request { StartDebuggingRequestArgumentsRequest::Launch => DebugRequestType::Launch, StartDebuggingRequestArgumentsRequest::Attach => DebugRequestType::Attach( - if let DebugRequestType::Attach(attach_config) = config.request { - attach_config + if let DebugRequestType::Attach(attach_config) = &config.request { + attach_config.clone() } else { AttachConfig::default() }, @@ -1522,11 +1664,27 @@ impl DapStore { return Task::ready(Err(anyhow!("Could not find session: {:?}", session_id))); }; + let Some(local_session) = session.read(cx).as_local() else { + return Task::ready(Err(anyhow!( + "Cannot shutdown session on remote side: {:?}", + session_id + ))); + }; + let mut tasks = Vec::new(); - for client in session.read(cx).clients().collect::>() { + for client in local_session.clients().collect::>() { tasks.push(self.shutdown_client(&session, client, cx)); } + if let Some((downstream_client, project_id)) = self.downstream_client.as_ref() { + downstream_client + .send(proto::DebuggerSessionEnded { + project_id: *project_id, + session_id: session_id.to_proto(), + }) + .log_err(); + } + cx.background_executor().spawn(async move { futures::future::join_all(tasks).await; Ok(()) @@ -1588,11 +1746,13 @@ impl DapStore { debug_sessions: Vec, cx: &mut ModelContext, ) { - for (session_id, debug_clients) in debug_sessions - .into_iter() - .map(|session| (session.session_id, session.clients)) - { - for debug_client in debug_clients { + for session in debug_sessions.into_iter() { + let session_id = DebugSessionId::from_proto(session.session_id); + let ignore_breakpoints = Some(session.ignore_breakpoints); + + self.add_remote_session(session_id, ignore_breakpoints, cx); + + for debug_client in session.clients { if let DapStoreMode::Remote(remote) = &mut self.mode { if let Some(queue) = &mut remote.event_queue { debug_client.debug_panel_items.into_iter().for_each(|item| { @@ -1601,9 +1761,13 @@ impl DapStore { } } + let client = DebugAdapterClientId::from_proto(debug_client.client_id); + + self.add_client_to_session(session_id, client); + self.update_capabilities_for_client( - &DebugSessionId::from_proto(session_id), - &DebugAdapterClientId::from_proto(debug_client.client_id), + &session_id, + &client, &dap::proto_conversions::capabilities_from_proto( &debug_client.capabilities.unwrap_or_default(), ), @@ -1640,7 +1804,7 @@ impl DapStore { cx.notify(); } - async fn handle_shutdown_session( + async fn handle_shutdown_session_request( this: Model, envelope: TypedEnvelope, mut cx: AsyncAppContext, @@ -1928,8 +2092,11 @@ impl DapStore { .collect::>(); let mut tasks = Vec::new(); - for session in local_store.sessions.values() { - let session = session.read(cx); + for session in local_store + .sessions + .values() + .filter_map(|session| session.read(cx).as_local()) + { let ignore_breakpoints = self.ignore_breakpoints(&session.id(), cx); for client in session.clients().collect::>() { tasks.push(self.send_breakpoints( diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 74da6fb446b00a..8f7ae15f0d73e1 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -634,6 +634,8 @@ impl Project { client.add_model_request_handler(WorktreeStore::handle_rename_project_entry); + client.add_model_message_handler(Self::handle_toggle_ignore_breakpoints); + WorktreeStore::init(&client); BufferStore::init(&client); LspStore::init(&client); @@ -1396,6 +1398,26 @@ impl Project { result } + async fn handle_toggle_ignore_breakpoints( + this: Model, + envelope: TypedEnvelope, + mut cx: AsyncAppContext, + ) -> Result<()> { + this.update(&mut cx, |project, cx| { + // Only the host should handle this message because the host + // handles direct communication with the debugger servers. + if let Some((_, _)) = project.dap_store.read(cx).downstream_client() { + project + .toggle_ignore_breakpoints( + &DebugSessionId::from_proto(envelope.payload.session_id), + &DebugAdapterClientId::from_proto(envelope.payload.client_id), + cx, + ) + .detach_and_log_err(cx); + } + }) + } + pub fn toggle_ignore_breakpoints( &self, session_id: &DebugSessionId, @@ -1403,8 +1425,30 @@ impl Project { cx: &mut ModelContext, ) -> Task> { let tasks = self.dap_store.update(cx, |store, cx| { + if let Some((upstream_client, project_id)) = store.upstream_client() { + upstream_client + .send(proto::ToggleIgnoreBreakpoints { + session_id: session_id.to_proto(), + client_id: client_id.to_proto(), + project_id, + }) + .log_err(); + + return Vec::new(); + } + store.toggle_ignore_breakpoints(session_id, cx); + if let Some((downstream_client, project_id)) = store.downstream_client() { + downstream_client + .send(proto::IgnoreBreakpointState { + session_id: session_id.to_proto(), + project_id: *project_id, + ignore: store.ignore_breakpoints(session_id, cx), + }) + .log_err(); + } + let mut tasks = Vec::new(); for (project_path, breakpoints) in store.breakpoints() { diff --git a/crates/proto/proto/zed.proto b/crates/proto/proto/zed.proto index 52426ac69e882d..b6e6690d601b3a 100644 --- a/crates/proto/proto/zed.proto +++ b/crates/proto/proto/zed.proto @@ -331,7 +331,10 @@ message Envelope { UpdateThreadStatus update_thread_status = 310; VariablesRequest variables_request = 311; DapVariables dap_variables = 312; - DapRestartStackFrameRequest dap_restart_stack_frame_request = 313; // current max + DapRestartStackFrameRequest dap_restart_stack_frame_request = 313; + IgnoreBreakpointState ignore_breakpoint_state = 314; + ToggleIgnoreBreakpoints toggle_ignore_breakpoints = 315; + DebuggerSessionEnded debugger_session_ended = 316; // current max } reserved 87 to 88; @@ -2471,10 +2474,16 @@ enum BreakpointKind { Log = 1; } +message DebuggerSessionEnded { + uint64 project_id = 1; + uint64 session_id = 2; +} + message DebuggerSession { uint64 session_id = 1; uint64 project_id = 2; - repeated DebugClient clients = 3; + bool ignore_breakpoints = 3; + repeated DebugClient clients = 4; } message DebugClient { @@ -2709,6 +2718,18 @@ message DapShutdownSession { optional uint64 session_id = 2; // Shutdown all sessions if this is None } +message ToggleIgnoreBreakpoints { + uint64 project_id = 1; + uint64 client_id = 2; + uint64 session_id = 3; +} + +message IgnoreBreakpointState { + uint64 project_id = 1; + uint64 session_id = 2; + bool ignore = 3; +} + message DapNextRequest { uint64 project_id = 1; uint64 client_id = 2; diff --git a/crates/proto/src/proto.rs b/crates/proto/src/proto.rs index 0af3cbaf4cb548..a79bcf08183941 100644 --- a/crates/proto/src/proto.rs +++ b/crates/proto/src/proto.rs @@ -397,6 +397,9 @@ messages!( (UsersResponse, Foreground), (VariablesRequest, Background), (DapVariables, Background), + (IgnoreBreakpointState, Background), + (ToggleIgnoreBreakpoints, Background), + (DebuggerSessionEnded, Background), ); request_messages!( @@ -644,6 +647,9 @@ entity_messages!( DapShutdownSession, UpdateThreadStatus, VariablesRequest, + IgnoreBreakpointState, + ToggleIgnoreBreakpoints, + DebuggerSessionEnded, ); entity_messages!( From c4688fe4135316e49f15da376753c84f60ff4cfe Mon Sep 17 00:00:00 2001 From: Remco Smits Date: Wed, 22 Jan 2025 13:33:29 +0100 Subject: [PATCH 14/22] Clean up --- crates/dap/src/adapters.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/dap/src/adapters.rs b/crates/dap/src/adapters.rs index 16fdce011beafc..329dc25635a652 100644 --- a/crates/dap/src/adapters.rs +++ b/crates/dap/src/adapters.rs @@ -328,10 +328,10 @@ impl DebugAdapter for FakeAdapter { async fn get_binary( &self, - _delegate: &dyn DapDelegate, - _config: &DebugAdapterConfig, - _user_installed_path: Option, - _cx: &mut AsyncAppContext, + _: &dyn DapDelegate, + _: &DebugAdapterConfig, + _: Option, + _: &mut AsyncAppContext, ) -> Result { Ok(DebugAdapterBinary { command: "command".into(), @@ -358,10 +358,10 @@ impl DebugAdapter for FakeAdapter { async fn get_installed_binary( &self, - _delegate: &dyn DapDelegate, - _config: &DebugAdapterConfig, - _user_installed_path: Option, - _cx: &mut AsyncAppContext, + _: &dyn DapDelegate, + _: &DebugAdapterConfig, + _: Option, + _: &mut AsyncAppContext, ) -> Result { unimplemented!("get installed binary"); } From af696bb3ed92bf2b75d544b1289ff5f28700bbbc Mon Sep 17 00:00:00 2001 From: Remco Smits Date: Wed, 22 Jan 2025 13:44:02 +0100 Subject: [PATCH 15/22] Make node runtime required --- crates/dap/src/adapters.rs | 2 +- crates/dap_adapters/src/go.rs | 5 ++--- crates/dap_adapters/src/javascript.rs | 7 ++----- crates/dap_adapters/src/php.rs | 7 ++----- crates/project/src/dap_store.rs | 8 ++++---- 5 files changed, 11 insertions(+), 18 deletions(-) diff --git a/crates/dap/src/adapters.rs b/crates/dap/src/adapters.rs index 329dc25635a652..d98baa60017bec 100644 --- a/crates/dap/src/adapters.rs +++ b/crates/dap/src/adapters.rs @@ -39,7 +39,7 @@ pub enum DapStatus { pub trait DapDelegate { fn worktree_id(&self) -> WorktreeId; fn http_client(&self) -> Arc; - fn node_runtime(&self) -> Option; + fn node_runtime(&self) -> NodeRuntime; fn toolchain_store(&self) -> Arc; fn fs(&self) -> Arc; fn updated_adapters(&self) -> Arc>>; diff --git a/crates/dap_adapters/src/go.rs b/crates/dap_adapters/src/go.rs index 1c56de473ea4d1..691e1e0baea807 100644 --- a/crates/dap_adapters/src/go.rs +++ b/crates/dap_adapters/src/go.rs @@ -11,8 +11,7 @@ pub(crate) struct GoDebugAdapter { } impl GoDebugAdapter { - const _ADAPTER_NAME: &'static str = "delve"; - // const ADAPTER_PATH: &'static str = "src/debugpy/adapter"; + const ADAPTER_NAME: &'static str = "delve"; pub(crate) async fn new(host: &TCPHost) -> Result { Ok(GoDebugAdapter { @@ -26,7 +25,7 @@ impl GoDebugAdapter { #[async_trait(?Send)] impl DebugAdapter for GoDebugAdapter { fn name(&self) -> DebugAdapterName { - DebugAdapterName(Self::_ADAPTER_NAME.into()) + DebugAdapterName(Self::ADAPTER_NAME.into()) } fn transport(&self) -> Arc { diff --git a/crates/dap_adapters/src/javascript.rs b/crates/dap_adapters/src/javascript.rs index c1e26683022e92..899d908959dd60 100644 --- a/crates/dap_adapters/src/javascript.rs +++ b/crates/dap_adapters/src/javascript.rs @@ -84,12 +84,9 @@ impl DebugAdapter for JsDebugAdapter { .ok_or_else(|| anyhow!("Couldn't find JavaScript dap directory"))? }; - let node_runtime = delegate - .node_runtime() - .ok_or(anyhow!("Couldn't get npm runtime"))?; - Ok(DebugAdapterBinary { - command: node_runtime + command: delegate + .node_runtime() .binary_path() .await? .to_string_lossy() diff --git a/crates/dap_adapters/src/php.rs b/crates/dap_adapters/src/php.rs index b06f25af51a906..2768c4ab0d0cdf 100644 --- a/crates/dap_adapters/src/php.rs +++ b/crates/dap_adapters/src/php.rs @@ -81,12 +81,9 @@ impl DebugAdapter for PhpDebugAdapter { .ok_or_else(|| anyhow!("Couldn't find PHP dap directory"))? }; - let node_runtime = delegate - .node_runtime() - .ok_or(anyhow!("Couldn't get npm runtime"))?; - Ok(DebugAdapterBinary { - command: node_runtime + command: delegate + .node_runtime() .binary_path() .await? .to_string_lossy() diff --git a/crates/project/src/dap_store.rs b/crates/project/src/dap_store.rs index f107ee1ad01099..3b72c20a435d3b 100644 --- a/crates/project/src/dap_store.rs +++ b/crates/project/src/dap_store.rs @@ -684,8 +684,8 @@ impl DapStore { let delegate = Arc::new(DapAdapterDelegate::new( local_store.fs.clone(), worktree.read(cx).id(), + local_store.node_runtime.clone(), local_store.http_client.clone(), - Some(local_store.node_runtime.clone()), local_store.language_registry.clone(), local_store.toolchain_store.clone(), local_store.environment.update(cx, |env, cx| { @@ -2377,8 +2377,8 @@ impl SerializedBreakpoint { pub struct DapAdapterDelegate { fs: Arc, worktree_id: WorktreeId, + node_runtime: NodeRuntime, http_client: Arc, - node_runtime: Option, language_registry: Arc, toolchain_store: Arc, updated_adapters: Arc>>, @@ -2389,8 +2389,8 @@ impl DapAdapterDelegate { pub fn new( fs: Arc, worktree_id: WorktreeId, + node_runtime: NodeRuntime, http_client: Arc, - node_runtime: Option, language_registry: Arc, toolchain_store: Arc, load_shell_env_task: Shared>>>, @@ -2418,7 +2418,7 @@ impl dap::adapters::DapDelegate for DapAdapterDelegate { self.http_client.clone() } - fn node_runtime(&self) -> Option { + fn node_runtime(&self) -> NodeRuntime { self.node_runtime.clone() } From 201430720aacd60050ebf3a1f531c853c54bb6c5 Mon Sep 17 00:00:00 2001 From: Remco Smits Date: Wed, 22 Jan 2025 13:59:26 +0100 Subject: [PATCH 16/22] Pass worktree id into get_environment --- crates/project/src/dap_store.rs | 2 +- crates/project/src/project.rs | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/crates/project/src/dap_store.rs b/crates/project/src/dap_store.rs index 3b72c20a435d3b..3d45adceb0db85 100644 --- a/crates/project/src/dap_store.rs +++ b/crates/project/src/dap_store.rs @@ -689,7 +689,7 @@ impl DapStore { local_store.language_registry.clone(), local_store.toolchain_store.clone(), local_store.environment.update(cx, |env, cx| { - env.get_environment(None, worktree_abs_path, cx) + env.get_environment(Some(worktree.read(cx).id()), worktree_abs_path, cx) }), )); diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 8f7ae15f0d73e1..5d10aeddc0b267 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1327,16 +1327,6 @@ impl Project { } /// Get all serialized breakpoints that belong to a buffer - /// - /// # Parameters - /// None, - /// `buffer_id`: The buffer id to get serialized breakpoints of - /// - /// `None`: If the buffer associated with buffer id doesn't exist or this editor - /// doesn't belong to a project - /// - /// `(Path, Vec Date: Wed, 22 Jan 2025 14:34:31 +0100 Subject: [PATCH 17/22] Fix use the resolved debug adapter config --- crates/project/src/dap_store.rs | 30 +++++++++++++++--------------- crates/task/src/lib.rs | 2 +- crates/tasks_ui/src/modal.rs | 24 ++++++++++-------------- 3 files changed, 26 insertions(+), 30 deletions(-) diff --git a/crates/project/src/dap_store.rs b/crates/project/src/dap_store.rs index 3d45adceb0db85..d5cd6a0ea26313 100644 --- a/crates/project/src/dap_store.rs +++ b/crates/project/src/dap_store.rs @@ -672,7 +672,7 @@ impl DapStore { fn start_client_internal( &mut self, session_id: DebugSessionId, - worktree: &Model, + delegate: Arc, config: DebugAdapterConfig, cx: &mut ModelContext, ) -> Task>> { @@ -680,19 +680,6 @@ impl DapStore { return Task::ready(Err(anyhow!("cannot start client on remote side"))); }; - let worktree_abs_path = config.cwd.as_ref().map(|p| Arc::from(p.as_path())); - let delegate = Arc::new(DapAdapterDelegate::new( - local_store.fs.clone(), - worktree.read(cx).id(), - local_store.node_runtime.clone(), - local_store.http_client.clone(), - local_store.language_registry.clone(), - local_store.toolchain_store.clone(), - local_store.environment.update(cx, |env, cx| { - env.get_environment(Some(worktree.read(cx).id()), worktree_abs_path, cx) - }), - )); - let client_id = local_store.next_client_id(); cx.spawn(|this, mut cx| async move { @@ -773,9 +760,22 @@ impl DapStore { return Task::ready(Err(anyhow!("cannot start session on remote side"))); }; + let delegate = Arc::new(DapAdapterDelegate::new( + local_store.fs.clone(), + worktree.read(cx).id(), + local_store.node_runtime.clone(), + local_store.http_client.clone(), + local_store.language_registry.clone(), + local_store.toolchain_store.clone(), + local_store.environment.update(cx, |env, cx| { + let worktree = worktree.read(cx); + env.get_environment(Some(worktree.id()), Some(worktree.abs_path()), cx) + }), + )); + let session_id = local_store.next_session_id(); let start_client_task = - self.start_client_internal(session_id, worktree, config.clone(), cx); + self.start_client_internal(session_id, delegate, config.clone(), cx); cx.spawn(|this, mut cx| async move { let session = cx.new_model(|_| DebugSession::new_local(session_id, config))?; diff --git a/crates/task/src/lib.rs b/crates/task/src/lib.rs index 021e9b870bbda8..6afc06ce4242c9 100644 --- a/crates/task/src/lib.rs +++ b/crates/task/src/lib.rs @@ -102,7 +102,7 @@ impl ResolvedTask { } /// Get the configuration for the debug adapter that should be used for this task. - pub fn debug_adapter_config(&self) -> Option { + pub fn resolved_debug_adapter_config(&self) -> Option { match self.original_task.task_type.clone() { TaskType::Script => None, TaskType::Debug(mut adapter_config) => { diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs index 12a9b125a545bd..7ab1a2cfa45fe2 100644 --- a/crates/tasks_ui/src/modal.rs +++ b/crates/tasks_ui/src/modal.rs @@ -317,13 +317,11 @@ impl PickerDelegate for TasksModalDelegate { omit_history_entry, cx, ), - TaskType::Debug(debug_config) => { - workspace.project().update(cx, |project, cx| { - project - .start_debug_session(debug_config, cx) - .detach_and_log_err(cx); - }) - } + TaskType::Debug(_) => workspace.project().update(cx, |project, cx| { + project + .start_debug_session(task.resolved_debug_adapter_config().unwrap(), cx) + .detach_and_log_err(cx); + }), }; }) .ok(); @@ -481,13 +479,11 @@ impl PickerDelegate for TasksModalDelegate { ), // TODO: Should create a schedule_resolved_debug_task function // This would allow users to access to debug history and other issues - TaskType::Debug(debug_config) => { - workspace.project().update(cx, |project, cx| { - project - .start_debug_session(debug_config, cx) - .detach_and_log_err(cx); - }) - } + TaskType::Debug(_) => workspace.project().update(cx, |project, cx| { + project + .start_debug_session(task.resolved_debug_adapter_config().unwrap(), cx) + .detach_and_log_err(cx); + }), }; }) .ok(); From 0d86235ed4ad067e39e846d06bda054e22c96065 Mon Sep 17 00:00:00 2001 From: Remco Smits Date: Wed, 22 Jan 2025 15:07:03 +0100 Subject: [PATCH 18/22] Fix clippy --- crates/dap_adapters/src/python.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/dap_adapters/src/python.rs b/crates/dap_adapters/src/python.rs index b0cbf1c470b6a2..e0c1db464e6780 100644 --- a/crates/dap_adapters/src/python.rs +++ b/crates/dap_adapters/src/python.rs @@ -3,9 +3,8 @@ use dap::{ DebugRequestType, }; use gpui::AsyncAppContext; -use language::LanguageName; use regex::Regex; -use std::{collections::HashMap, ffi::OsStr, net::Ipv4Addr, path::PathBuf, sync::Arc}; +use std::{collections::HashMap, net::Ipv4Addr, path::PathBuf, sync::Arc}; use sysinfo::{Pid, Process}; use crate::*; From 9192ed70b0c8f5be61eedc3907a42a0e73132cce Mon Sep 17 00:00:00 2001 From: Remco Smits Date: Wed, 29 Jan 2025 10:55:58 +0100 Subject: [PATCH 19/22] Add fallback if toolchain could not be found to common binary names --- crates/dap_adapters/src/python.rs | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/crates/dap_adapters/src/python.rs b/crates/dap_adapters/src/python.rs index 883afc66ff72da..e44cadd22ef1e5 100644 --- a/crates/dap_adapters/src/python.rs +++ b/crates/dap_adapters/src/python.rs @@ -1,8 +1,7 @@ +use crate::*; use dap::transport::{TcpTransport, Transport}; use gpui::AsyncApp; -use std::{net::Ipv4Addr, path::PathBuf, sync::Arc}; - -use crate::*; +use std::{ffi::OsStr, net::Ipv4Addr, path::PathBuf, sync::Arc}; pub(crate) struct PythonDebugAdapter { port: u16, @@ -82,6 +81,8 @@ impl DebugAdapter for PythonDebugAdapter { user_installed_path: Option, cx: &mut AsyncApp, ) -> Result { + const BINARY_NAMES: [&str; 3] = ["python3", "python", "py"]; + let debugpy_dir = if let Some(user_installed_path) = user_installed_path { user_installed_path } else { @@ -102,11 +103,23 @@ impl DebugAdapter for PythonDebugAdapter { language::LanguageName::new(Self::LANGUAGE_NAME), cx, ) - .await - .ok_or(anyhow!("failed to find active toolchain for Python"))?; + .await; + + let python_path = if let Some(toolchain) = toolchain { + Some(toolchain.path.to_string()) + } else { + BINARY_NAMES + .iter() + .filter_map(|cmd| { + delegate + .which(OsStr::new(cmd)) + .map(|path| path.to_string_lossy().to_string()) + }) + .find(|_| true) + }; Ok(DebugAdapterBinary { - command: toolchain.path.to_string(), + command: python_path.ok_or(anyhow!("failed to find binary path for python"))?, arguments: Some(vec![ debugpy_dir.join(Self::ADAPTER_PATH).into(), format!("--port={}", self.port).into(), From 357019b6c2656d79321df72d3e66019b13a3d6c9 Mon Sep 17 00:00:00 2001 From: Remco Smits Date: Sat, 1 Feb 2025 16:59:43 +0100 Subject: [PATCH 20/22] Only consider process ids that are bigger then o --- crates/dap_adapters/src/python.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/dap_adapters/src/python.rs b/crates/dap_adapters/src/python.rs index f9766b07431d0e..711b594a9f5bbb 100644 --- a/crates/dap_adapters/src/python.rs +++ b/crates/dap_adapters/src/python.rs @@ -137,7 +137,7 @@ impl DebugAdapter for PythonDebugAdapter { fn request_args(&self, config: &DebugAdapterConfig) -> Value { let pid = if let DebugRequestType::Attach(attach_config) = &config.request { - attach_config.process_id + attach_config.process_id.filter(|pid| *pid > 0) } else { None }; From 8c04f95eeb8904c46ab7eda98fa2684f46d258db Mon Sep 17 00:00:00 2001 From: Remco Smits Date: Sat, 1 Feb 2025 17:00:15 +0100 Subject: [PATCH 21/22] Filter out all NULL values as python seem to only check if the key is inside the options --- crates/dap_adapters/src/python.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/dap_adapters/src/python.rs b/crates/dap_adapters/src/python.rs index 711b594a9f5bbb..314cf255dc242e 100644 --- a/crates/dap_adapters/src/python.rs +++ b/crates/dap_adapters/src/python.rs @@ -142,7 +142,7 @@ impl DebugAdapter for PythonDebugAdapter { None }; - json!({ + let mut config = json!({ "request": match config.request { DebugRequestType::Launch => "launch", DebugRequestType::Attach(_) => "attach", @@ -151,7 +151,13 @@ impl DebugAdapter for PythonDebugAdapter { "program": config.program, "subProcess": true, "cwd": config.cwd, - }) + }); + + if let Value::Object(config) = &mut config { + config.retain(|_, v| !v.is_null()); + } + + config } fn supports_attach(&self) -> bool { From b5305c6488e990a5f1ecd9fa7332018160505691 Mon Sep 17 00:00:00 2001 From: Remco Smits Date: Mon, 3 Feb 2025 10:22:36 +0100 Subject: [PATCH 22/22] Update debugger.md --- docs/src/debugger.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/debugger.md b/docs/src/debugger.md index 0e8809492698ff..b037b6093d1167 100644 --- a/docs/src/debugger.md +++ b/docs/src/debugger.md @@ -62,7 +62,7 @@ To create a custom debug configuration you have to create a `.zed/debug.json` fi ### Using Attach [WIP] -Only javascript and lldb supports starting a debug session using attach. +Only JavaScript, Python and lldb supports starting a debug session using attach. When using the attach request with a process ID the syntax is as follows: