diff --git a/crates/project/src/debugger/breakpoint_store.rs b/crates/project/src/debugger/breakpoint_store.rs index 4426e55feb2d934b61c9cbabaf12eae530d6d594..ee0511ca0151ad92fcb7c08a6123e65b46077678 100644 --- a/crates/project/src/debugger/breakpoint_store.rs +++ b/crates/project/src/debugger/breakpoint_store.rs @@ -12,7 +12,7 @@ use rpc::{ AnyProtoClient, TypedEnvelope, proto::{self}, }; -use std::{hash::Hash, ops::Range, path::Path, sync::Arc}; +use std::{hash::Hash, ops::Range, path::Path, sync::Arc, u32}; use text::{Point, PointUtf16}; use crate::{Project, ProjectPath, buffer_store::BufferStore, worktree_store::WorktreeStore}; diff --git a/crates/project/src/debugger/dap_store.rs b/crates/project/src/debugger/dap_store.rs index 049a9a72f9c7d5961427180f02905e482a71788c..61a9c317067128ce6c904b5dc1be2afaf52fb2d6 100644 --- a/crates/project/src/debugger/dap_store.rs +++ b/crates/project/src/debugger/dap_store.rs @@ -23,7 +23,7 @@ use futures::{ }; use gpui::{ App, AppContext, AsyncApp, BackgroundExecutor, Context, Entity, EventEmitter, SharedString, - Task, + Task, WeakEntity, }; use http_client::HttpClient; use language::{BinaryStatus, LanguageRegistry, LanguageToolchainStore}; @@ -320,7 +320,7 @@ impl DapStore { session.set_ignore_breakpoints(envelope.payload.ignore, cx) }) } else { - Task::ready(()) + Task::ready(HashMap::default()) } })? .await; @@ -350,6 +350,7 @@ impl DapStore { &mut self, binary: DebugAdapterBinary, config: DebugTaskDefinition, + worktree: WeakEntity, parent_session: Option>, cx: &mut Context, ) -> (SessionId, Task>>) { @@ -373,6 +374,7 @@ impl DapStore { let start_client_task = this.update(cx, |this, cx| { Session::local( this.breakpoint_store.clone(), + worktree.clone(), session_id, parent_session, binary, @@ -385,7 +387,7 @@ impl DapStore { let ret = this .update(cx, |_, cx| { - create_new_session(session_id, initialized_rx, start_client_task, cx) + create_new_session(session_id, initialized_rx, start_client_task, worktree, cx) })? .await; ret @@ -404,6 +406,16 @@ impl DapStore { return Task::ready(Err(anyhow!("Session not found"))); }; + let Some(worktree) = parent_session + .read(cx) + .as_local() + .map(|local| local.worktree().clone()) + else { + return Task::ready(Err(anyhow!( + "Cannot handle start debugging request from remote end" + ))); + }; + let args = serde_json::from_value::( request.arguments.unwrap_or_default(), ) @@ -413,7 +425,7 @@ impl DapStore { binary.request_args = args; let new_session_task = self - .new_session(binary, config, Some(parent_session.clone()), cx) + .new_session(binary, config, worktree, Some(parent_session.clone()), cx) .1; let request_seq = request.seq; @@ -742,6 +754,7 @@ fn create_new_session( session_id: SessionId, initialized_rx: oneshot::Receiver<()>, start_client_task: Task, anyhow::Error>>, + worktree: WeakEntity, cx: &mut Context, ) -> Task>> { let task = cx.spawn(async move |this, cx| { @@ -771,7 +784,7 @@ fn create_new_session( session .update(cx, |session, cx| { - session.initialize_sequence(initialized_rx, cx) + session.initialize_sequence(initialized_rx, this.clone(), cx) })? .await }; @@ -820,12 +833,13 @@ fn create_new_session( let task = curr_session.update(cx, |session, cx| session.shutdown(cx)); + let worktree = worktree.clone(); cx.spawn(async move |this, cx| { task.await; this.update(cx, |this, cx| { this.sessions.remove(&session_id); - this.new_session(binary, config, None, cx) + this.new_session(binary, config, worktree, None, cx) })? .1 .await?; diff --git a/crates/project/src/debugger/session.rs b/crates/project/src/debugger/session.rs index beaf5fd4e76f8775cbe57ea6f7bab47cac4d7945..ec9b0540bd5fcb1d2acbef471a33986511f85838 100644 --- a/crates/project/src/debugger/session.rs +++ b/crates/project/src/debugger/session.rs @@ -9,6 +9,7 @@ use super::dap_command::{ StepBackCommand, StepCommand, StepInCommand, StepOutCommand, TerminateCommand, TerminateThreadsCommand, ThreadsCommand, VariablesCommand, }; +use super::dap_store::DapStore; use anyhow::{Context as _, Result, anyhow}; use collections::{HashMap, HashSet, IndexMap, IndexSet}; use dap::adapters::DebugAdapterBinary; @@ -26,6 +27,7 @@ use gpui::{ App, AppContext, AsyncApp, BackgroundExecutor, Context, Entity, EventEmitter, SharedString, Task, WeakEntity, }; + use rpc::AnyProtoClient; use serde_json::{Value, json}; use smol::stream::StreamExt; @@ -42,6 +44,7 @@ use std::{ use task::DebugTaskDefinition; use text::{PointUtf16, ToPointUtf16}; use util::{ResultExt, merge_json_value_into}; +use worktree::Worktree; #[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd, Ord, Eq)] #[repr(transparent)] @@ -163,6 +166,7 @@ pub struct LocalMode { binary: DebugAdapterBinary, pub(crate) breakpoint_store: Entity, tmp_breakpoint: Option, + worktree: WeakEntity, } fn client_source(abs_path: &Path) -> dap::Source { @@ -184,6 +188,7 @@ impl LocalMode { fn new( session_id: SessionId, parent_session: Option>, + worktree: WeakEntity, breakpoint_store: Entity, config: DebugTaskDefinition, binary: DebugAdapterBinary, @@ -194,6 +199,7 @@ impl LocalMode { session_id, parent_session, breakpoint_store, + worktree, config, binary, messages_tx, @@ -206,6 +212,7 @@ impl LocalMode { session_id: SessionId, parent_session: Option>, breakpoint_store: Entity, + worktree: WeakEntity, config: DebugTaskDefinition, binary: DebugAdapterBinary, messages_tx: futures::channel::mpsc::UnboundedSender, @@ -240,6 +247,7 @@ impl LocalMode { let mut session = Self { client, breakpoint_store, + worktree, tmp_breakpoint: None, definition: config, binary, @@ -251,6 +259,10 @@ impl LocalMode { }) } + pub(crate) fn worktree(&self) -> &WeakEntity { + &self.worktree + } + fn unset_breakpoints_from_paths(&self, paths: &Vec>, cx: &mut App) -> Task<()> { let tasks: Vec<_> = paths .into_iter() @@ -335,7 +347,12 @@ impl LocalMode { }; self.request(arg, cx.background_executor().clone()) } - fn send_source_breakpoints(&self, ignore_breakpoints: bool, cx: &App) -> Task<()> { + + fn send_source_breakpoints( + &self, + ignore_breakpoints: bool, + cx: &App, + ) -> Task, anyhow::Error>> { let mut breakpoint_tasks = Vec::new(); let breakpoints = self .breakpoint_store @@ -352,26 +369,25 @@ impl LocalMode { .collect() }; - breakpoint_tasks.push(self.request( - dap_command::SetBreakpoints { - source: client_source(&path), - source_modified: Some(false), - breakpoints, - }, - cx.background_executor().clone(), - )); + breakpoint_tasks.push( + self.request( + dap_command::SetBreakpoints { + source: client_source(&path), + source_modified: Some(false), + breakpoints, + }, + cx.background_executor().clone(), + ) + .map(|result| result.map_err(|e| (path, e))), + ); } cx.background_spawn(async move { futures::future::join_all(breakpoint_tasks) .await - .iter() - .for_each(|res| match res { - Ok(_) => {} - Err(err) => { - log::warn!("Set breakpoints request failed: {}", err); - } - }); + .into_iter() + .filter_map(Result::err) + .collect::>() }) } @@ -389,6 +405,7 @@ impl LocalMode { &self, capabilities: &Capabilities, initialized_rx: oneshot::Receiver<()>, + dap_store: WeakEntity, cx: &App, ) -> Task> { let mut raw = self.binary.request_args.clone(); @@ -431,12 +448,40 @@ impl LocalMode { .unwrap_or_default(); let configuration_sequence = cx.spawn({ let this = self.clone(); + let worktree = self.worktree().clone(); async move |cx| { initialized_rx.await?; - // todo(debugger) figure out if we want to handle a breakpoint response error - // This will probably consist of letting a user know that breakpoints failed to be set - cx.update(|cx| this.send_source_breakpoints(false, cx))? + let errors_by_path = cx + .update(|cx| this.send_source_breakpoints(false, cx))? .await; + + dap_store.update(cx, |_, cx| { + let Some(worktree) = worktree.upgrade() else { + return; + }; + + for (path, error) in &errors_by_path { + log::error!("failed to set breakpoints for {path:?}: {error}"); + } + + if let Some(failed_path) = errors_by_path.keys().next() { + let failed_path = failed_path + .strip_prefix(worktree.read(cx).abs_path()) + .unwrap_or(failed_path) + .display(); + let message = format!( + "Failed to set breakpoints for {failed_path}{}", + match errors_by_path.len() { + 0 => unreachable!(), + 1 => "".into(), + 2 => " and 1 other path".into(), + n => format!(" and {} other paths", n - 1), + } + ); + cx.emit(super::dap_store::DapStoreEvent::Notification(message)); + } + })?; + cx.update(|cx| { this.send_exception_breakpoints( exception_filters, @@ -699,6 +744,7 @@ impl EventEmitter for Session {} impl Session { pub(crate) fn local( breakpoint_store: Entity, + worktree: WeakEntity, session_id: SessionId, parent_session: Option>, binary: DebugAdapterBinary, @@ -713,6 +759,7 @@ impl Session { let mode = LocalMode::new( session_id, parent_session.clone(), + worktree, breakpoint_store.clone(), config.clone(), binary, @@ -871,11 +918,12 @@ impl Session { pub(super) fn initialize_sequence( &mut self, initialize_rx: oneshot::Receiver<()>, + dap_store: WeakEntity, cx: &mut Context, ) -> Task> { match &self.mode { Mode::Local(local_mode) => { - local_mode.initialize_sequence(&self.capabilities, initialize_rx, cx) + local_mode.initialize_sequence(&self.capabilities, initialize_rx, dap_store, cx) } Mode::Remote(_) => Task::ready(Err(anyhow!("cannot initialize remote session"))), } @@ -1292,13 +1340,20 @@ impl Session { self.ignore_breakpoints } - pub fn toggle_ignore_breakpoints(&mut self, cx: &mut App) -> Task<()> { + pub fn toggle_ignore_breakpoints( + &mut self, + cx: &mut App, + ) -> Task, anyhow::Error>> { self.set_ignore_breakpoints(!self.ignore_breakpoints, cx) } - pub(crate) fn set_ignore_breakpoints(&mut self, ignore: bool, cx: &mut App) -> Task<()> { + pub(crate) fn set_ignore_breakpoints( + &mut self, + ignore: bool, + cx: &mut App, + ) -> Task, anyhow::Error>> { if self.ignore_breakpoints == ignore { - return Task::ready(()); + return Task::ready(HashMap::default()); } self.ignore_breakpoints = ignore; diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 218ba89333f738c848e1cba0d5c051827260b6b2..f339da9c29ff0bfd9039e3ac7d2627830109f8c7 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -1462,7 +1462,7 @@ impl Project { config: DebugTaskDefinition, cx: &mut Context, ) -> Task>> { - let Some(worktree) = self.worktrees(cx).next() else { + let Some(worktree) = self.worktrees(cx).find(|tree| tree.read(cx).is_visible()) else { return Task::ready(Err(anyhow!("Failed to find a worktree"))); }; @@ -1501,7 +1501,7 @@ impl Project { let ret = this .update(cx, |project, cx| { project.dap_store.update(cx, |dap_store, cx| { - dap_store.new_session(binary, config, None, cx) + dap_store.new_session(binary, config, worktree.downgrade(), None, cx) }) })? .1