diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index 3a98830d44..86d60d2640 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -311,6 +311,31 @@ impl ActivityIndicator { }); } + if let Some(session) = self + .project + .read(cx) + .dap_store() + .read(cx) + .sessions() + .find(|s| !s.read(cx).is_started()) + { + return Some(Content { + icon: Some( + Icon::new(IconName::ArrowCircle) + .size(IconSize::Small) + .with_animation( + "arrow-circle", + Animation::new(Duration::from_secs(2)).repeat(), + |icon, delta| icon.transform(Transformation::rotate(percentage(delta))), + ) + .into_any_element(), + ), + message: format!("Debug: {}", session.read(cx).adapter()), + tooltip_message: Some(session.read(cx).label().to_string()), + on_click: None, + }); + } + let current_job = self .project .read(cx) diff --git a/crates/debugger_ui/src/dropdown_menus.rs b/crates/debugger_ui/src/dropdown_menus.rs index f6ab263026..27b6393172 100644 --- a/crates/debugger_ui/src/dropdown_menus.rs +++ b/crates/debugger_ui/src/dropdown_menus.rs @@ -1,4 +1,6 @@ -use gpui::Entity; +use std::time::Duration; + +use gpui::{Animation, AnimationExt as _, Entity, Transformation, percentage}; use project::debugger::session::{ThreadId, ThreadStatus}; use ui::{ContextMenu, DropdownMenu, DropdownStyle, Indicator, prelude::*}; @@ -23,31 +25,40 @@ impl DebugPanel { let sessions = self.sessions().clone(); let weak = cx.weak_entity(); let running_state = running_state.read(cx); - let label = if let Some(active_session) = active_session { + let label = if let Some(active_session) = active_session.clone() { active_session.read(cx).session(cx).read(cx).label() } else { SharedString::new_static("Unknown Session") }; let is_terminated = running_state.session().read(cx).is_terminated(); - let session_state_indicator = { - if is_terminated { - Some(Indicator::dot().color(Color::Error)) - } else { - match running_state.thread_status(cx).unwrap_or_default() { - project::debugger::session::ThreadStatus::Stopped => { - Some(Indicator::dot().color(Color::Conflict)) - } - _ => Some(Indicator::dot().color(Color::Success)), + let is_started = active_session + .is_some_and(|session| session.read(cx).session(cx).read(cx).is_started()); + + let session_state_indicator = if is_terminated { + Indicator::dot().color(Color::Error).into_any_element() + } else if !is_started { + Icon::new(IconName::ArrowCircle) + .size(IconSize::Small) + .color(Color::Muted) + .with_animation( + "arrow-circle", + Animation::new(Duration::from_secs(2)).repeat(), + |icon, delta| icon.transform(Transformation::rotate(percentage(delta))), + ) + .into_any_element() + } else { + match running_state.thread_status(cx).unwrap_or_default() { + ThreadStatus::Stopped => { + Indicator::dot().color(Color::Conflict).into_any_element() } + _ => Indicator::dot().color(Color::Success).into_any_element(), } }; let trigger = h_flex() .gap_2() - .when_some(session_state_indicator, |this, indicator| { - this.child(indicator) - }) + .child(session_state_indicator) .justify_between() .child( DebugPanel::dropdown_label(label) diff --git a/crates/debugger_ui/src/session/running/console.rs b/crates/debugger_ui/src/session/running/console.rs index bff7793ee4..4a996b8b60 100644 --- a/crates/debugger_ui/src/session/running/console.rs +++ b/crates/debugger_ui/src/session/running/console.rs @@ -110,7 +110,7 @@ impl Console { } fn is_running(&self, cx: &Context) -> bool { - self.session.read(cx).is_local() + self.session.read(cx).is_running() } fn handle_stack_frame_list_events( diff --git a/crates/project/src/debugger/session.rs b/crates/project/src/debugger/session.rs index 815bc553d2..0d846ae7e5 100644 --- a/crates/project/src/debugger/session.rs +++ b/crates/project/src/debugger/session.rs @@ -121,16 +121,17 @@ impl From for Thread { pub enum Mode { Building, - Running(LocalMode), + Running(RunningMode), } #[derive(Clone)] -pub struct LocalMode { +pub struct RunningMode { client: Arc, binary: DebugAdapterBinary, tmp_breakpoint: Option, worktree: WeakEntity, executor: BackgroundExecutor, + is_started: bool, } fn client_source(abs_path: &Path) -> dap::Source { @@ -148,7 +149,7 @@ fn client_source(abs_path: &Path) -> dap::Source { } } -impl LocalMode { +impl RunningMode { async fn new( session_id: SessionId, parent_session: Option>, @@ -181,6 +182,7 @@ impl LocalMode { tmp_breakpoint: None, binary, executor: cx.background_executor().clone(), + is_started: false, }) } @@ -373,7 +375,7 @@ impl LocalMode { capabilities: &Capabilities, initialized_rx: oneshot::Receiver<()>, dap_store: WeakEntity, - cx: &App, + cx: &mut Context, ) -> Task> { let raw = self.binary.request_args.clone(); @@ -405,7 +407,7 @@ impl LocalMode { let this = self.clone(); let worktree = self.worktree().clone(); let configuration_sequence = cx.spawn({ - async move |cx| { + async move |_, cx| { let breakpoint_store = dap_store.read_with(cx, |dap_store, _| dap_store.breakpoint_store().clone())?; initialized_rx.await?; @@ -453,9 +455,20 @@ impl LocalMode { } }); - cx.background_spawn(async move { - futures::future::try_join(launch, configuration_sequence).await?; - Ok(()) + let task = cx.background_spawn(futures::future::try_join(launch, configuration_sequence)); + + cx.spawn(async move |this, cx| { + task.await?; + + this.update(cx, |this, cx| { + if let Some(this) = this.as_running_mut() { + this.is_started = true; + cx.notify(); + } + }) + .ok(); + + anyhow::Ok(()) }) } @@ -704,7 +717,7 @@ impl Session { cx.subscribe(&breakpoint_store, |this, store, event, cx| match event { BreakpointStoreEvent::BreakpointsUpdated(path, reason) => { if let Some(local) = (!this.ignore_breakpoints) - .then(|| this.as_local_mut()) + .then(|| this.as_running_mut()) .flatten() { local @@ -714,7 +727,7 @@ impl Session { } BreakpointStoreEvent::BreakpointsCleared(paths) => { if let Some(local) = (!this.ignore_breakpoints) - .then(|| this.as_local_mut()) + .then(|| this.as_running_mut()) .flatten() { local.unset_breakpoints_from_paths(paths, cx).detach(); @@ -806,7 +819,7 @@ impl Session { let parent_session = self.parent_session.clone(); cx.spawn(async move |this, cx| { - let mode = LocalMode::new( + let mode = RunningMode::new( id, parent_session, worktree.downgrade(), @@ -906,18 +919,29 @@ impl Session { return tx; } - pub fn is_local(&self) -> bool { + pub fn is_started(&self) -> bool { + match &self.mode { + Mode::Building => false, + Mode::Running(running) => running.is_started, + } + } + + pub fn is_building(&self) -> bool { + matches!(self.mode, Mode::Building) + } + + pub fn is_running(&self) -> bool { matches!(self.mode, Mode::Running(_)) } - pub fn as_local_mut(&mut self) -> Option<&mut LocalMode> { + pub fn as_running_mut(&mut self) -> Option<&mut RunningMode> { match &mut self.mode { Mode::Running(local_mode) => Some(local_mode), Mode::Building => None, } } - pub fn as_local(&self) -> Option<&LocalMode> { + pub fn as_running(&self) -> Option<&RunningMode> { match &self.mode { Mode::Running(local_mode) => Some(local_mode), Mode::Building => None, @@ -1140,7 +1164,7 @@ impl Session { body: Option, cx: &mut Context, ) -> Task> { - let Some(local_session) = self.as_local() else { + let Some(local_session) = self.as_running() else { unreachable!("Cannot respond to remote client"); }; let client = local_session.client.clone(); @@ -1162,7 +1186,7 @@ impl Session { fn handle_stopped_event(&mut self, event: StoppedEvent, cx: &mut Context) { // todo(debugger): Find a clean way to get around the clone let breakpoint_store = self.breakpoint_store.clone(); - if let Some((local, path)) = self.as_local_mut().and_then(|local| { + if let Some((local, path)) = self.as_running_mut().and_then(|local| { let breakpoint = local.tmp_breakpoint.take()?; let path = breakpoint.path.clone(); Some((local, path)) @@ -1528,7 +1552,7 @@ impl Session { self.ignore_breakpoints = ignore; - if let Some(local) = self.as_local() { + if let Some(local) = self.as_running() { local.send_source_breakpoints(ignore, &self.breakpoint_store, cx) } else { // todo(debugger): We need to propagate this change to downstream sessions and send a message to upstream sessions @@ -1550,7 +1574,7 @@ impl Session { } fn send_exception_breakpoints(&mut self, cx: &App) { - if let Some(local) = self.as_local() { + if let Some(local) = self.as_running() { let exception_filters = self .exception_breakpoints .values()