debugger: Add spinners while session is starting up (#31548)
Release Notes: - Debugger Beta: Added a spinner to the debug panel when a session is starting up. --------- Co-authored-by: Remco Smits <djsmits12@gmail.com> Co-authored-by: Julia <julia@zed.dev>
This commit is contained in:
parent
384b11392a
commit
f9407db7d6
4 changed files with 93 additions and 33 deletions
|
@ -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
|
let current_job = self
|
||||||
.project
|
.project
|
||||||
.read(cx)
|
.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 project::debugger::session::{ThreadId, ThreadStatus};
|
||||||
use ui::{ContextMenu, DropdownMenu, DropdownStyle, Indicator, prelude::*};
|
use ui::{ContextMenu, DropdownMenu, DropdownStyle, Indicator, prelude::*};
|
||||||
|
|
||||||
|
@ -23,31 +25,40 @@ impl DebugPanel {
|
||||||
let sessions = self.sessions().clone();
|
let sessions = self.sessions().clone();
|
||||||
let weak = cx.weak_entity();
|
let weak = cx.weak_entity();
|
||||||
let running_state = running_state.read(cx);
|
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()
|
active_session.read(cx).session(cx).read(cx).label()
|
||||||
} else {
|
} else {
|
||||||
SharedString::new_static("Unknown Session")
|
SharedString::new_static("Unknown Session")
|
||||||
};
|
};
|
||||||
|
|
||||||
let is_terminated = running_state.session().read(cx).is_terminated();
|
let is_terminated = running_state.session().read(cx).is_terminated();
|
||||||
let session_state_indicator = {
|
let is_started = active_session
|
||||||
if is_terminated {
|
.is_some_and(|session| session.read(cx).session(cx).read(cx).is_started());
|
||||||
Some(Indicator::dot().color(Color::Error))
|
|
||||||
} else {
|
let session_state_indicator = if is_terminated {
|
||||||
match running_state.thread_status(cx).unwrap_or_default() {
|
Indicator::dot().color(Color::Error).into_any_element()
|
||||||
project::debugger::session::ThreadStatus::Stopped => {
|
} else if !is_started {
|
||||||
Some(Indicator::dot().color(Color::Conflict))
|
Icon::new(IconName::ArrowCircle)
|
||||||
}
|
.size(IconSize::Small)
|
||||||
_ => Some(Indicator::dot().color(Color::Success)),
|
.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()
|
let trigger = h_flex()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.when_some(session_state_indicator, |this, indicator| {
|
.child(session_state_indicator)
|
||||||
this.child(indicator)
|
|
||||||
})
|
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.child(
|
.child(
|
||||||
DebugPanel::dropdown_label(label)
|
DebugPanel::dropdown_label(label)
|
||||||
|
|
|
@ -110,7 +110,7 @@ impl Console {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_running(&self, cx: &Context<Self>) -> bool {
|
fn is_running(&self, cx: &Context<Self>) -> bool {
|
||||||
self.session.read(cx).is_local()
|
self.session.read(cx).is_running()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_stack_frame_list_events(
|
fn handle_stack_frame_list_events(
|
||||||
|
|
|
@ -121,16 +121,17 @@ impl From<dap::Thread> for Thread {
|
||||||
|
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
Building,
|
Building,
|
||||||
Running(LocalMode),
|
Running(RunningMode),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct LocalMode {
|
pub struct RunningMode {
|
||||||
client: Arc<DebugAdapterClient>,
|
client: Arc<DebugAdapterClient>,
|
||||||
binary: DebugAdapterBinary,
|
binary: DebugAdapterBinary,
|
||||||
tmp_breakpoint: Option<SourceBreakpoint>,
|
tmp_breakpoint: Option<SourceBreakpoint>,
|
||||||
worktree: WeakEntity<Worktree>,
|
worktree: WeakEntity<Worktree>,
|
||||||
executor: BackgroundExecutor,
|
executor: BackgroundExecutor,
|
||||||
|
is_started: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn client_source(abs_path: &Path) -> dap::Source {
|
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(
|
async fn new(
|
||||||
session_id: SessionId,
|
session_id: SessionId,
|
||||||
parent_session: Option<Entity<Session>>,
|
parent_session: Option<Entity<Session>>,
|
||||||
|
@ -181,6 +182,7 @@ impl LocalMode {
|
||||||
tmp_breakpoint: None,
|
tmp_breakpoint: None,
|
||||||
binary,
|
binary,
|
||||||
executor: cx.background_executor().clone(),
|
executor: cx.background_executor().clone(),
|
||||||
|
is_started: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -373,7 +375,7 @@ impl LocalMode {
|
||||||
capabilities: &Capabilities,
|
capabilities: &Capabilities,
|
||||||
initialized_rx: oneshot::Receiver<()>,
|
initialized_rx: oneshot::Receiver<()>,
|
||||||
dap_store: WeakEntity<DapStore>,
|
dap_store: WeakEntity<DapStore>,
|
||||||
cx: &App,
|
cx: &mut Context<Session>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
let raw = self.binary.request_args.clone();
|
let raw = self.binary.request_args.clone();
|
||||||
|
|
||||||
|
@ -405,7 +407,7 @@ impl LocalMode {
|
||||||
let this = self.clone();
|
let this = self.clone();
|
||||||
let worktree = self.worktree().clone();
|
let worktree = self.worktree().clone();
|
||||||
let configuration_sequence = cx.spawn({
|
let configuration_sequence = cx.spawn({
|
||||||
async move |cx| {
|
async move |_, cx| {
|
||||||
let breakpoint_store =
|
let breakpoint_store =
|
||||||
dap_store.read_with(cx, |dap_store, _| dap_store.breakpoint_store().clone())?;
|
dap_store.read_with(cx, |dap_store, _| dap_store.breakpoint_store().clone())?;
|
||||||
initialized_rx.await?;
|
initialized_rx.await?;
|
||||||
|
@ -453,9 +455,20 @@ impl LocalMode {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.background_spawn(async move {
|
let task = cx.background_spawn(futures::future::try_join(launch, configuration_sequence));
|
||||||
futures::future::try_join(launch, configuration_sequence).await?;
|
|
||||||
Ok(())
|
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 {
|
cx.subscribe(&breakpoint_store, |this, store, event, cx| match event {
|
||||||
BreakpointStoreEvent::BreakpointsUpdated(path, reason) => {
|
BreakpointStoreEvent::BreakpointsUpdated(path, reason) => {
|
||||||
if let Some(local) = (!this.ignore_breakpoints)
|
if let Some(local) = (!this.ignore_breakpoints)
|
||||||
.then(|| this.as_local_mut())
|
.then(|| this.as_running_mut())
|
||||||
.flatten()
|
.flatten()
|
||||||
{
|
{
|
||||||
local
|
local
|
||||||
|
@ -714,7 +727,7 @@ impl Session {
|
||||||
}
|
}
|
||||||
BreakpointStoreEvent::BreakpointsCleared(paths) => {
|
BreakpointStoreEvent::BreakpointsCleared(paths) => {
|
||||||
if let Some(local) = (!this.ignore_breakpoints)
|
if let Some(local) = (!this.ignore_breakpoints)
|
||||||
.then(|| this.as_local_mut())
|
.then(|| this.as_running_mut())
|
||||||
.flatten()
|
.flatten()
|
||||||
{
|
{
|
||||||
local.unset_breakpoints_from_paths(paths, cx).detach();
|
local.unset_breakpoints_from_paths(paths, cx).detach();
|
||||||
|
@ -806,7 +819,7 @@ impl Session {
|
||||||
let parent_session = self.parent_session.clone();
|
let parent_session = self.parent_session.clone();
|
||||||
|
|
||||||
cx.spawn(async move |this, cx| {
|
cx.spawn(async move |this, cx| {
|
||||||
let mode = LocalMode::new(
|
let mode = RunningMode::new(
|
||||||
id,
|
id,
|
||||||
parent_session,
|
parent_session,
|
||||||
worktree.downgrade(),
|
worktree.downgrade(),
|
||||||
|
@ -906,18 +919,29 @@ impl Session {
|
||||||
return tx;
|
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(_))
|
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 {
|
match &mut self.mode {
|
||||||
Mode::Running(local_mode) => Some(local_mode),
|
Mode::Running(local_mode) => Some(local_mode),
|
||||||
Mode::Building => None,
|
Mode::Building => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_local(&self) -> Option<&LocalMode> {
|
pub fn as_running(&self) -> Option<&RunningMode> {
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
Mode::Running(local_mode) => Some(local_mode),
|
Mode::Running(local_mode) => Some(local_mode),
|
||||||
Mode::Building => None,
|
Mode::Building => None,
|
||||||
|
@ -1140,7 +1164,7 @@ impl Session {
|
||||||
body: Option<serde_json::Value>,
|
body: Option<serde_json::Value>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
let Some(local_session) = self.as_local() else {
|
let Some(local_session) = self.as_running() else {
|
||||||
unreachable!("Cannot respond to remote client");
|
unreachable!("Cannot respond to remote client");
|
||||||
};
|
};
|
||||||
let client = local_session.client.clone();
|
let client = local_session.client.clone();
|
||||||
|
@ -1162,7 +1186,7 @@ impl Session {
|
||||||
fn handle_stopped_event(&mut self, event: StoppedEvent, cx: &mut Context<Self>) {
|
fn handle_stopped_event(&mut self, event: StoppedEvent, cx: &mut Context<Self>) {
|
||||||
// todo(debugger): Find a clean way to get around the clone
|
// todo(debugger): Find a clean way to get around the clone
|
||||||
let breakpoint_store = self.breakpoint_store.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 breakpoint = local.tmp_breakpoint.take()?;
|
||||||
let path = breakpoint.path.clone();
|
let path = breakpoint.path.clone();
|
||||||
Some((local, path))
|
Some((local, path))
|
||||||
|
@ -1528,7 +1552,7 @@ impl Session {
|
||||||
|
|
||||||
self.ignore_breakpoints = ignore;
|
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)
|
local.send_source_breakpoints(ignore, &self.breakpoint_store, cx)
|
||||||
} else {
|
} else {
|
||||||
// todo(debugger): We need to propagate this change to downstream sessions and send a message to upstream sessions
|
// 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) {
|
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
|
let exception_filters = self
|
||||||
.exception_breakpoints
|
.exception_breakpoints
|
||||||
.values()
|
.values()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue