debugger: Add close button and coloring to debug panel session's menu (#28310)
This PR adds colors to debug panel's session menu that indicate the state of each respective session. It also adds a close button to each entry. green - running yellow - stopped red - terminated/ended Release Notes: - N/A
This commit is contained in:
parent
ee7b1ec7f2
commit
1774cad933
6 changed files with 173 additions and 44 deletions
|
@ -12,8 +12,8 @@ use dap::{
|
|||
};
|
||||
use futures::{SinkExt as _, channel::mpsc};
|
||||
use gpui::{
|
||||
Action, App, AsyncWindowContext, Context, Entity, EventEmitter, FocusHandle, Focusable,
|
||||
Subscription, Task, WeakEntity, actions,
|
||||
Action, App, AsyncWindowContext, Context, Entity, EntityId, EventEmitter, FocusHandle,
|
||||
Focusable, Subscription, Task, WeakEntity, actions,
|
||||
};
|
||||
use project::{
|
||||
Project,
|
||||
|
@ -336,6 +336,95 @@ impl DebugPanel {
|
|||
})
|
||||
}
|
||||
|
||||
fn close_session(&mut self, entity_id: EntityId, cx: &mut Context<Self>) {
|
||||
let Some(session) = self
|
||||
.sessions
|
||||
.iter()
|
||||
.find(|other| entity_id == other.entity_id())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
session.update(cx, |session, cx| session.shutdown(cx));
|
||||
|
||||
self.sessions.retain(|other| entity_id != other.entity_id());
|
||||
|
||||
if let Some(active_session_id) = self
|
||||
.active_session
|
||||
.as_ref()
|
||||
.map(|session| session.entity_id())
|
||||
{
|
||||
if active_session_id == entity_id {
|
||||
self.active_session = self.sessions.first().cloned();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sessions_drop_down_menu(
|
||||
&self,
|
||||
active_session: &Entity<DebugSession>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> DropdownMenu {
|
||||
let sessions = self.sessions.clone();
|
||||
let weak = cx.weak_entity();
|
||||
let label = active_session.read(cx).label_element(cx);
|
||||
|
||||
DropdownMenu::new_with_element(
|
||||
"debugger-session-list",
|
||||
label,
|
||||
ContextMenu::build(window, cx, move |mut this, _, _| {
|
||||
for session in sessions.into_iter() {
|
||||
let weak_session = session.downgrade();
|
||||
let weak_id = weak_session.entity_id();
|
||||
|
||||
this = this.custom_entry(
|
||||
{
|
||||
let weak = weak.clone();
|
||||
move |_, cx| {
|
||||
weak_session
|
||||
.read_with(cx, |session, cx| {
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.child(session.label_element(cx))
|
||||
.child(
|
||||
IconButton::new(
|
||||
"close-debug-session",
|
||||
IconName::Close,
|
||||
)
|
||||
.icon_size(IconSize::Small)
|
||||
.on_click({
|
||||
let weak = weak.clone();
|
||||
move |_, _, cx| {
|
||||
weak.update(cx, |panel, cx| {
|
||||
panel.close_session(weak_id, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}),
|
||||
)
|
||||
.into_any_element()
|
||||
})
|
||||
.unwrap_or_else(|_| div().into_any_element())
|
||||
}
|
||||
},
|
||||
{
|
||||
let weak = weak.clone();
|
||||
move |window, cx| {
|
||||
weak.update(cx, |panel, cx| {
|
||||
panel.activate_session(session.clone(), window, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
this
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn top_controls_strip(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<Div> {
|
||||
let active_session = self.active_session.clone();
|
||||
|
||||
|
@ -529,34 +618,8 @@ impl DebugPanel {
|
|||
},
|
||||
)
|
||||
.when_some(active_session.as_ref(), |this, session| {
|
||||
let sessions = self.sessions.clone();
|
||||
let weak = cx.weak_entity();
|
||||
let label = session.read(cx).label(cx);
|
||||
this.child(DropdownMenu::new(
|
||||
"debugger-session-list",
|
||||
label,
|
||||
ContextMenu::build(window, cx, move |mut this, _, cx| {
|
||||
for item in sessions {
|
||||
let weak = weak.clone();
|
||||
this = this.entry(
|
||||
session.read(cx).label(cx),
|
||||
None,
|
||||
move |window, cx| {
|
||||
weak.update(cx, |panel, cx| {
|
||||
panel.activate_session(
|
||||
item.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
},
|
||||
);
|
||||
}
|
||||
this
|
||||
}),
|
||||
))
|
||||
.child(Divider::vertical())
|
||||
let context_menu = self.sessions_drop_down_menu(session, window, cx);
|
||||
this.child(context_menu).child(Divider::vertical())
|
||||
})
|
||||
.child(
|
||||
IconButton::new("debug-new-session", IconName::Plus)
|
||||
|
|
|
@ -7,7 +7,7 @@ use project::debugger::{dap_store::DapStore, session::Session};
|
|||
use project::worktree_store::WorktreeStore;
|
||||
use rpc::proto::{self, PeerId};
|
||||
use running::RunningState;
|
||||
use ui::prelude::*;
|
||||
use ui::{Indicator, prelude::*};
|
||||
use workspace::{
|
||||
FollowableItem, ViewId, Workspace,
|
||||
item::{self, Item},
|
||||
|
@ -81,7 +81,6 @@ impl DebugSession {
|
|||
}
|
||||
}
|
||||
|
||||
#[expect(unused)]
|
||||
pub(crate) fn shutdown(&mut self, cx: &mut Context<Self>) {
|
||||
match &self.mode {
|
||||
DebugSessionState::Running(state) => state.update(cx, |state, cx| state.shutdown(cx)),
|
||||
|
@ -108,6 +107,33 @@ impl DebugSession {
|
|||
.expect("Remote Debug Sessions are not implemented yet")
|
||||
.label()
|
||||
}
|
||||
|
||||
pub(crate) fn label_element(&self, cx: &App) -> AnyElement {
|
||||
let label = self.label(cx);
|
||||
|
||||
let (icon, color) = match &self.mode {
|
||||
DebugSessionState::Running(state) => {
|
||||
if state.read(cx).session().read(cx).is_terminated() {
|
||||
(Some(Indicator::dot().color(Color::Error)), Color::Error)
|
||||
} else {
|
||||
match state.read(cx).thread_status(cx).unwrap_or_default() {
|
||||
project::debugger::session::ThreadStatus::Stopped => (
|
||||
Some(Indicator::dot().color(Color::Conflict)),
|
||||
Color::Conflict,
|
||||
),
|
||||
_ => (Some(Indicator::dot().color(Color::Success)), Color::Success),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.when_some(icon, |this, indicator| this.child(indicator))
|
||||
.justify_between()
|
||||
.child(Label::new(label).color(color))
|
||||
.into_any_element()
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<DebugPanelItemEvent> for DebugSession {}
|
||||
|
|
|
@ -219,7 +219,7 @@ pub enum MarkdownEvent {
|
|||
Start(MarkdownTag),
|
||||
/// End of a tagged element.
|
||||
End(MarkdownTagEnd),
|
||||
/// Text that uses the associated range from the mardown source.
|
||||
/// Text that uses the associated range from the markdown source.
|
||||
Text,
|
||||
/// Text that differs from the markdown source - typically due to substitution of HTML entities
|
||||
/// and smart punctuation.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use super::{
|
||||
breakpoint_store::BreakpointStore,
|
||||
locator_store::LocatorStore,
|
||||
session::{self, Session},
|
||||
session::{self, Session, SessionStateEvent},
|
||||
};
|
||||
use crate::{ProjectEnvironment, debugger, worktree_store::WorktreeStore};
|
||||
use anyhow::{Result, anyhow};
|
||||
|
@ -869,6 +869,15 @@ fn create_new_session(
|
|||
}
|
||||
|
||||
this.update(cx, |_, cx| {
|
||||
cx.subscribe(
|
||||
&session,
|
||||
move |this: &mut DapStore, _, event: &SessionStateEvent, cx| match event {
|
||||
SessionStateEvent::Shutdown => {
|
||||
this.shutdown_session(session_id, cx).detach_and_log_err(cx);
|
||||
}
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
cx.emit(DapStoreEvent::DebugSessionInitialized(session_id));
|
||||
})?;
|
||||
|
||||
|
|
|
@ -832,7 +832,12 @@ pub enum SessionEvent {
|
|||
Threads,
|
||||
}
|
||||
|
||||
pub(crate) enum SessionStateEvent {
|
||||
Shutdown,
|
||||
}
|
||||
|
||||
impl EventEmitter<SessionEvent> for Session {}
|
||||
impl EventEmitter<SessionStateEvent> for Session {}
|
||||
|
||||
// local session will send breakpoint updates to DAP for all new breakpoints
|
||||
// remote side will only send breakpoint updates when it is a breakpoint created by that peer
|
||||
|
@ -1553,6 +1558,8 @@ impl Session {
|
|||
)
|
||||
};
|
||||
|
||||
cx.emit(SessionStateEvent::Shutdown);
|
||||
|
||||
cx.background_spawn(async move {
|
||||
let _ = task.await;
|
||||
})
|
||||
|
|
|
@ -2,10 +2,15 @@ use gpui::{ClickEvent, Corner, CursorStyle, Entity, MouseButton};
|
|||
|
||||
use crate::{ContextMenu, PopoverMenu, prelude::*};
|
||||
|
||||
enum LabelKind {
|
||||
Text(SharedString),
|
||||
Element(AnyElement),
|
||||
}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct DropdownMenu {
|
||||
id: ElementId,
|
||||
label: SharedString,
|
||||
label: LabelKind,
|
||||
menu: Entity<ContextMenu>,
|
||||
full_width: bool,
|
||||
disabled: bool,
|
||||
|
@ -19,7 +24,21 @@ impl DropdownMenu {
|
|||
) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
label: label.into(),
|
||||
label: LabelKind::Text(label.into()),
|
||||
menu,
|
||||
full_width: false,
|
||||
disabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_element(
|
||||
id: impl Into<ElementId>,
|
||||
label: AnyElement,
|
||||
menu: Entity<ContextMenu>,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
label: LabelKind::Element(label),
|
||||
menu,
|
||||
full_width: false,
|
||||
disabled: false,
|
||||
|
@ -55,7 +74,7 @@ impl RenderOnce for DropdownMenu {
|
|||
|
||||
#[derive(IntoElement)]
|
||||
struct DropdownMenuTrigger {
|
||||
label: SharedString,
|
||||
label: LabelKind,
|
||||
full_width: bool,
|
||||
selected: bool,
|
||||
disabled: bool,
|
||||
|
@ -64,9 +83,9 @@ struct DropdownMenuTrigger {
|
|||
}
|
||||
|
||||
impl DropdownMenuTrigger {
|
||||
pub fn new(label: impl Into<SharedString>) -> Self {
|
||||
pub fn new(label: LabelKind) -> Self {
|
||||
Self {
|
||||
label: label.into(),
|
||||
label,
|
||||
full_width: false,
|
||||
selected: false,
|
||||
disabled: false,
|
||||
|
@ -135,11 +154,16 @@ impl RenderOnce for DropdownMenuTrigger {
|
|||
el.cursor_pointer()
|
||||
}
|
||||
})
|
||||
.child(Label::new(self.label).color(if disabled {
|
||||
Color::Disabled
|
||||
} else {
|
||||
Color::Default
|
||||
}))
|
||||
.child(match self.label {
|
||||
LabelKind::Text(text) => Label::new(text)
|
||||
.color(if disabled {
|
||||
Color::Disabled
|
||||
} else {
|
||||
Color::Default
|
||||
})
|
||||
.into_any_element(),
|
||||
LabelKind::Element(element) => element,
|
||||
})
|
||||
.child(
|
||||
Icon::new(IconName::ChevronUpDown)
|
||||
.size(IconSize::XSmall)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue