@@ -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)
@@ -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)
@@ -121,16 +121,17 @@ impl From<dap::Thread> for Thread {
pub enum Mode {
Building,
- Running(LocalMode),
+ Running(RunningMode),
}
#[derive(Clone)]
-pub struct LocalMode {
+pub struct RunningMode {
client: Arc<DebugAdapterClient>,
binary: DebugAdapterBinary,
tmp_breakpoint: Option<SourceBreakpoint>,
worktree: WeakEntity<Worktree>,
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<Entity<Session>>,
@@ -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<DapStore>,
- cx: &App,
+ cx: &mut Context<Session>,
) -> Task<Result<()>> {
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<serde_json::Value>,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
- 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<Self>) {
// 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()