Detailed changes
@@ -1,5 +1,6 @@
use crate::persistence::DebuggerPaneItem;
use crate::session::DebugSession;
+use crate::session::running::RunningState;
use crate::{
ClearAllBreakpoints, Continue, Detach, FocusBreakpointList, FocusConsole, FocusFrames,
FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables, Pause, Restart,
@@ -30,7 +31,7 @@ use settings::Settings;
use std::any::TypeId;
use std::sync::Arc;
use task::{DebugScenario, TaskContext};
-use ui::{ContextMenu, Divider, DropdownMenu, Tooltip, prelude::*};
+use ui::{ContextMenu, Divider, Tooltip, prelude::*};
use workspace::SplitDirection;
use workspace::{
Pane, Workspace,
@@ -87,7 +88,20 @@ impl DebugPanel {
})
}
- fn filter_action_types(&self, cx: &mut App) {
+ pub(crate) fn sessions(&self) -> Vec<Entity<DebugSession>> {
+ self.sessions.clone()
+ }
+
+ pub fn active_session(&self) -> Option<Entity<DebugSession>> {
+ self.active_session.clone()
+ }
+
+ pub(crate) fn running_state(&self, cx: &mut App) -> Option<Entity<RunningState>> {
+ self.active_session()
+ .map(|session| session.read(cx).running_state().clone())
+ }
+
+ pub(crate) fn filter_action_types(&self, cx: &mut App) {
let (has_active_session, supports_restart, support_step_back, status) = self
.active_session()
.map(|item| {
@@ -273,7 +287,7 @@ impl DebugPanel {
.detach_and_log_err(cx);
}
- async fn register_session(
+ pub(crate) async fn register_session(
this: WeakEntity<Self>,
session: Entity<Session>,
cx: &mut AsyncWindowContext,
@@ -342,7 +356,7 @@ impl DebugPanel {
Ok(debug_session)
}
- fn handle_restart_request(
+ pub(crate) fn handle_restart_request(
&mut self,
mut curr_session: Entity<Session>,
window: &mut Window,
@@ -416,11 +430,12 @@ impl DebugPanel {
.detach_and_log_err(cx);
}
- pub fn active_session(&self) -> Option<Entity<DebugSession>> {
- self.active_session.clone()
- }
-
- fn close_session(&mut self, entity_id: EntityId, window: &mut Window, cx: &mut Context<Self>) {
+ pub(crate) fn close_session(
+ &mut self,
+ entity_id: EntityId,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
let Some(session) = self
.sessions
.iter()
@@ -474,93 +489,8 @@ impl DebugPanel {
})
.detach();
}
- 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, _, cx| {
- let context_menu = cx.weak_entity();
- for session in sessions.into_iter() {
- let weak_session = session.downgrade();
- let weak_session_id = weak_session.entity_id();
-
- this = this.custom_entry(
- {
- let weak = weak.clone();
- let context_menu = context_menu.clone();
- move |_, cx| {
- weak_session
- .read_with(cx, |session, cx| {
- let context_menu = context_menu.clone();
- let id: SharedString =
- format!("debug-session-{}", session.session_id(cx).0)
- .into();
- h_flex()
- .w_full()
- .group(id.clone())
- .justify_between()
- .child(session.label_element(cx))
- .child(
- IconButton::new(
- "close-debug-session",
- IconName::Close,
- )
- .visible_on_hover(id.clone())
- .icon_size(IconSize::Small)
- .on_click({
- let weak = weak.clone();
- move |_, window, cx| {
- weak.update(cx, |panel, cx| {
- panel.close_session(
- weak_session_id,
- window,
- cx,
- );
- })
- .ok();
- context_menu
- .update(cx, |this, cx| {
- this.cancel(
- &Default::default(),
- window,
- 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 deploy_context_menu(
+ pub(crate) fn deploy_context_menu(
&mut self,
position: Point<Pixels>,
window: &mut Window,
@@ -611,7 +541,11 @@ impl DebugPanel {
}
}
- fn top_controls_strip(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<Div> {
+ pub(crate) fn top_controls_strip(
+ &mut self,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) -> Option<Div> {
let active_session = self.active_session.clone();
let focus_handle = self.focus_handle.clone();
let is_side = self.position(window, cx).axis() == gpui::Axis::Horizontal;
@@ -651,12 +585,12 @@ impl DebugPanel {
active_session
.as_ref()
.map(|session| session.read(cx).running_state()),
- |this, running_session| {
+ |this, running_state| {
let thread_status =
- running_session.read(cx).thread_status(cx).unwrap_or(
+ running_state.read(cx).thread_status(cx).unwrap_or(
project::debugger::session::ThreadStatus::Exited,
);
- let capabilities = running_session.read(cx).capabilities(cx);
+ let capabilities = running_state.read(cx).capabilities(cx);
this.map(|this| {
if thread_status == ThreadStatus::Running {
this.child(
@@ -667,7 +601,7 @@ impl DebugPanel {
.icon_size(IconSize::XSmall)
.shape(ui::IconButtonShape::Square)
.on_click(window.listener_for(
- &running_session,
+ &running_state,
|this, _, _window, cx| {
this.pause_thread(cx);
},
@@ -694,7 +628,7 @@ impl DebugPanel {
.icon_size(IconSize::XSmall)
.shape(ui::IconButtonShape::Square)
.on_click(window.listener_for(
- &running_session,
+ &running_state,
|this, _, _window, cx| this.continue_thread(cx),
))
.disabled(thread_status != ThreadStatus::Stopped)
@@ -718,7 +652,7 @@ impl DebugPanel {
.icon_size(IconSize::XSmall)
.shape(ui::IconButtonShape::Square)
.on_click(window.listener_for(
- &running_session,
+ &running_state,
|this, _, _window, cx| {
this.step_over(cx);
},
@@ -742,7 +676,7 @@ impl DebugPanel {
.icon_size(IconSize::XSmall)
.shape(ui::IconButtonShape::Square)
.on_click(window.listener_for(
- &running_session,
+ &running_state,
|this, _, _window, cx| {
this.step_out(cx);
},
@@ -769,7 +703,7 @@ impl DebugPanel {
.icon_size(IconSize::XSmall)
.shape(ui::IconButtonShape::Square)
.on_click(window.listener_for(
- &running_session,
+ &running_state,
|this, _, _window, cx| {
this.step_in(cx);
},
@@ -819,7 +753,7 @@ impl DebugPanel {
|| thread_status == ThreadStatus::Ended,
)
.on_click(window.listener_for(
- &running_session,
+ &running_state,
|this, _, _window, cx| {
this.toggle_ignore_breakpoints(cx);
},
@@ -842,7 +776,7 @@ impl DebugPanel {
IconButton::new("debug-restart", IconName::DebugRestart)
.icon_size(IconSize::XSmall)
.on_click(window.listener_for(
- &running_session,
+ &running_state,
|this, _, _window, cx| {
this.restart_session(cx);
},
@@ -864,7 +798,7 @@ impl DebugPanel {
IconButton::new("debug-stop", IconName::Power)
.icon_size(IconSize::XSmall)
.on_click(window.listener_for(
- &running_session,
+ &running_state,
|this, _, _window, cx| {
this.stop_thread(cx);
},
@@ -898,7 +832,7 @@ impl DebugPanel {
IconButton::new("debug-disconnect", IconName::DebugDetach)
.icon_size(IconSize::XSmall)
.on_click(window.listener_for(
- &running_session,
+ &running_state,
|this, _, _, cx| {
this.detach_client(cx);
},
@@ -932,30 +866,42 @@ impl DebugPanel {
.as_ref()
.map(|session| session.read(cx).running_state())
.cloned(),
- |this, session| {
- this.child(
- session.update(cx, |this, cx| {
- this.thread_dropdown(window, cx)
- }),
- )
+ |this, running_state| {
+ this.children({
+ let running_state = running_state.clone();
+ let threads =
+ running_state.update(cx, |running_state, cx| {
+ let session = running_state.session();
+ session
+ .update(cx, |session, cx| session.threads(cx))
+ });
+
+ self.render_thread_dropdown(
+ &running_state,
+ threads,
+ window,
+ cx,
+ )
+ })
.when(!is_side, |this| this.gap_2().child(Divider::vertical()))
},
),
)
.child(
h_flex()
- .when_some(active_session.as_ref(), |this, session| {
- let context_menu =
- self.sessions_drop_down_menu(session, window, cx);
- this.child(context_menu).gap_2().child(Divider::vertical())
- })
+ .children(self.render_session_menu(
+ self.active_session(),
+ self.running_state(cx),
+ window,
+ cx,
+ ))
.when(!is_side, |this| this.child(new_session_button())),
),
),
)
}
- fn activate_pane_in_direction(
+ pub(crate) fn activate_pane_in_direction(
&mut self,
direction: SplitDirection,
window: &mut Window,
@@ -970,7 +916,7 @@ impl DebugPanel {
}
}
- fn activate_item(
+ pub(crate) fn activate_item(
&mut self,
item: DebuggerPaneItem,
window: &mut Window,
@@ -985,7 +931,7 @@ impl DebugPanel {
}
}
- fn activate_session(
+ pub(crate) fn activate_session(
&mut self,
session_item: Entity<DebugSession>,
window: &mut Window,
@@ -13,6 +13,7 @@ use workspace::{ItemHandle, ShutdownDebugAdapters, Workspace};
pub mod attach_modal;
pub mod debugger_panel;
+mod dropdown_menus;
mod new_session_modal;
mod persistence;
pub(crate) mod session;
@@ -0,0 +1,186 @@
+use gpui::Entity;
+use project::debugger::session::{ThreadId, ThreadStatus};
+use ui::{ContextMenu, DropdownMenu, DropdownStyle, Indicator, prelude::*};
+
+use crate::{
+ debugger_panel::DebugPanel,
+ session::{DebugSession, running::RunningState},
+};
+
+impl DebugPanel {
+ fn dropdown_label(label: impl Into<SharedString>) -> Label {
+ Label::new(label).size(LabelSize::Small)
+ }
+
+ pub fn render_session_menu(
+ &mut self,
+ active_session: Option<Entity<DebugSession>>,
+ running_state: Option<Entity<RunningState>>,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) -> Option<impl IntoElement> {
+ if let Some(running_state) = running_state {
+ 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 {
+ 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 trigger = h_flex()
+ .gap_2()
+ .when_some(session_state_indicator, |this, indicator| {
+ this.child(indicator)
+ })
+ .justify_between()
+ .child(
+ DebugPanel::dropdown_label(label)
+ .when(is_terminated, |this| this.strikethrough()),
+ )
+ .into_any_element();
+
+ Some(
+ DropdownMenu::new_with_element(
+ "debugger-session-list",
+ trigger,
+ ContextMenu::build(window, cx, move |mut this, _, cx| {
+ let context_menu = cx.weak_entity();
+ for session in sessions.into_iter() {
+ let weak_session = session.downgrade();
+ let weak_session_id = weak_session.entity_id();
+
+ this = this.custom_entry(
+ {
+ let weak = weak.clone();
+ let context_menu = context_menu.clone();
+ move |_, cx| {
+ weak_session
+ .read_with(cx, |session, cx| {
+ let context_menu = context_menu.clone();
+ let id: SharedString = format!(
+ "debug-session-{}",
+ session.session_id(cx).0
+ )
+ .into();
+ h_flex()
+ .w_full()
+ .group(id.clone())
+ .justify_between()
+ .child(session.label_element(cx))
+ .child(
+ IconButton::new(
+ "close-debug-session",
+ IconName::Close,
+ )
+ .visible_on_hover(id.clone())
+ .icon_size(IconSize::Small)
+ .on_click({
+ let weak = weak.clone();
+ move |_, window, cx| {
+ weak.update(cx, |panel, cx| {
+ panel.close_session(
+ weak_session_id,
+ window,
+ cx,
+ );
+ })
+ .ok();
+ context_menu
+ .update(cx, |this, cx| {
+ this.cancel(
+ &Default::default(),
+ window,
+ 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
+ }),
+ )
+ .style(DropdownStyle::Ghost),
+ )
+ } else {
+ None
+ }
+ }
+
+ pub(crate) fn render_thread_dropdown(
+ &self,
+ running_state: &Entity<RunningState>,
+ threads: Vec<(dap::Thread, ThreadStatus)>,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) -> Option<DropdownMenu> {
+ let running_state = running_state.clone();
+ let running_state_read = running_state.read(cx);
+ let thread_id = running_state_read.thread_id();
+ let session = running_state_read.session();
+ let session_id = session.read(cx).session_id();
+ let session_terminated = session.read(cx).is_terminated();
+ let selected_thread_name = threads
+ .iter()
+ .find(|(thread, _)| thread_id.map(|id| id.0) == Some(thread.id))
+ .map(|(thread, _)| thread.name.clone());
+
+ if let Some(selected_thread_name) = selected_thread_name {
+ let trigger = DebugPanel::dropdown_label(selected_thread_name).into_any_element();
+ Some(
+ DropdownMenu::new_with_element(
+ ("thread-list", session_id.0),
+ trigger,
+ ContextMenu::build_eager(window, cx, move |mut this, _, _| {
+ for (thread, _) in threads {
+ let running_state = running_state.clone();
+ let thread_id = thread.id;
+ this = this.entry(thread.name, None, move |window, cx| {
+ running_state.update(cx, |running_state, cx| {
+ running_state.select_thread(ThreadId(thread_id), window, cx);
+ });
+ });
+ }
+ this
+ }),
+ )
+ .disabled(session_terminated)
+ .style(DropdownStyle::Ghost),
+ )
+ } else {
+ None
+ }
+ }
+}
@@ -1,7 +1,6 @@
pub mod running;
-use std::{cell::OnceCell, sync::OnceLock};
-
+use crate::{StackTraceView, debugger_panel::DebugPanel, persistence::SerializedLayout};
use dap::client::SessionId;
use gpui::{
App, Axis, Entity, EventEmitter, FocusHandle, Focusable, Subscription, Task, WeakEntity,
@@ -11,14 +10,13 @@ use project::debugger::session::Session;
use project::worktree_store::WorktreeStore;
use rpc::proto;
use running::RunningState;
+use std::{cell::OnceCell, sync::OnceLock};
use ui::{Indicator, prelude::*};
use workspace::{
CollaboratorId, FollowableItem, ViewId, Workspace,
item::{self, Item},
};
-use crate::{StackTraceView, debugger_panel::DebugPanel, persistence::SerializedLayout};
-
pub struct DebugSession {
remote_id: Option<workspace::ViewId>,
running_state: Entity<RunningState>,
@@ -159,7 +157,11 @@ impl DebugSession {
.gap_2()
.when_some(icon, |this, indicator| this.child(indicator))
.justify_between()
- .child(Label::new(label).when(is_terminated, |this| this.strikethrough()))
+ .child(
+ Label::new(label)
+ .size(LabelSize::Small)
+ .when(is_terminated, |this| this.strikethrough()),
+ )
.into_any_element()
}
}
@@ -43,11 +43,10 @@ use task::{
};
use terminal_view::TerminalView;
use ui::{
- ActiveTheme, AnyElement, App, ButtonCommon as _, Clickable as _, Context, ContextMenu,
- Disableable, DropdownMenu, FluentBuilder, IconButton, IconName, IconSize, InteractiveElement,
- IntoElement, Label, LabelCommon as _, ParentElement, Render, SharedString,
- StatefulInteractiveElement, Styled, Tab, Tooltip, VisibleOnHover, VisualContext, Window, div,
- h_flex, v_flex,
+ ActiveTheme, AnyElement, App, ButtonCommon as _, Clickable as _, Context, FluentBuilder,
+ IconButton, IconName, IconSize, InteractiveElement, IntoElement, Label, LabelCommon as _,
+ ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, Tab, Tooltip,
+ VisibleOnHover, VisualContext, Window, div, h_flex, v_flex,
};
use util::ResultExt;
use variable_list::VariableList;
@@ -78,6 +77,12 @@ pub struct RunningState {
_schedule_serialize: Option<Task<()>>,
}
+impl RunningState {
+ pub(crate) fn thread_id(&self) -> Option<ThreadId> {
+ self.thread_id
+ }
+}
+
impl Render for RunningState {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let zoomed_pane = self
@@ -515,7 +520,7 @@ impl Focusable for DebugTerminal {
}
impl RunningState {
- pub fn new(
+ pub(crate) fn new(
session: Entity<Session>,
project: Entity<Project>,
workspace: WeakEntity<Workspace>,
@@ -1311,7 +1316,12 @@ impl RunningState {
.map(|id| self.session().read(cx).thread_status(id))
}
- fn select_thread(&mut self, thread_id: ThreadId, window: &mut Window, cx: &mut Context<Self>) {
+ pub(crate) fn select_thread(
+ &mut self,
+ thread_id: ThreadId,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
if self.thread_id.is_some_and(|id| id == thread_id) {
return;
}
@@ -1448,38 +1458,6 @@ impl RunningState {
});
}
- pub(crate) fn thread_dropdown(
- &self,
- window: &mut Window,
- cx: &mut Context<'_, RunningState>,
- ) -> DropdownMenu {
- let state = cx.entity();
- let session_terminated = self.session.read(cx).is_terminated();
- let threads = self.session.update(cx, |this, cx| this.threads(cx));
- let selected_thread_name = threads
- .iter()
- .find(|(thread, _)| self.thread_id.map(|id| id.0) == Some(thread.id))
- .map(|(thread, _)| thread.name.clone())
- .unwrap_or("Threads".to_owned());
- DropdownMenu::new(
- ("thread-list", self.session_id.0),
- selected_thread_name,
- ContextMenu::build_eager(window, cx, move |mut this, _, _| {
- for (thread, _) in threads {
- let state = state.clone();
- let thread_id = thread.id;
- this = this.entry(thread.name, None, move |window, cx| {
- state.update(cx, |state, cx| {
- state.select_thread(ThreadId(thread_id), window, cx);
- });
- });
- }
- this
- }),
- )
- .disabled(session_terminated)
- }
-
fn default_pane_layout(
project: Entity<Project>,
workspace: &WeakEntity<Workspace>,
@@ -1,7 +1,14 @@
-use gpui::{ClickEvent, Corner, CursorStyle, Entity, MouseButton};
+use gpui::{ClickEvent, Corner, CursorStyle, Entity, Hsla, MouseButton};
use crate::{ContextMenu, PopoverMenu, prelude::*};
+#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum DropdownStyle {
+ #[default]
+ Solid,
+ Ghost,
+}
+
enum LabelKind {
Text(SharedString),
Element(AnyElement),
@@ -11,6 +18,7 @@ enum LabelKind {
pub struct DropdownMenu {
id: ElementId,
label: LabelKind,
+ style: DropdownStyle,
menu: Entity<ContextMenu>,
full_width: bool,
disabled: bool,
@@ -25,6 +33,7 @@ impl DropdownMenu {
Self {
id: id.into(),
label: LabelKind::Text(label.into()),
+ style: DropdownStyle::default(),
menu,
full_width: false,
disabled: false,
@@ -39,12 +48,18 @@ impl DropdownMenu {
Self {
id: id.into(),
label: LabelKind::Element(label),
+ style: DropdownStyle::default(),
menu,
full_width: false,
disabled: false,
}
}
+ pub fn style(mut self, style: DropdownStyle) -> Self {
+ self.style = style;
+ self
+ }
+
pub fn full_width(mut self, full_width: bool) -> Self {
self.full_width = full_width;
self
@@ -66,7 +81,8 @@ impl RenderOnce for DropdownMenu {
.trigger(
DropdownMenuTrigger::new(self.label)
.full_width(self.full_width)
- .disabled(self.disabled),
+ .disabled(self.disabled)
+ .style(self.style),
)
.attach(Corner::BottomLeft)
}
@@ -135,12 +151,35 @@ impl Component for DropdownMenu {
}
}
+#[derive(Debug, Clone, Copy)]
+pub struct DropdownTriggerStyle {
+ pub bg: Hsla,
+}
+
+impl DropdownTriggerStyle {
+ pub fn for_style(style: DropdownStyle, cx: &App) -> Self {
+ let colors = cx.theme().colors();
+
+ if style == DropdownStyle::Solid {
+ Self {
+ // why is this editor_background?
+ bg: colors.editor_background,
+ }
+ } else {
+ Self {
+ bg: colors.ghost_element_background,
+ }
+ }
+ }
+}
+
#[derive(IntoElement)]
struct DropdownMenuTrigger {
label: LabelKind,
full_width: bool,
selected: bool,
disabled: bool,
+ style: DropdownStyle,
cursor_style: CursorStyle,
on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
}
@@ -152,6 +191,7 @@ impl DropdownMenuTrigger {
full_width: false,
selected: false,
disabled: false,
+ style: DropdownStyle::default(),
cursor_style: CursorStyle::default(),
on_click: None,
}
@@ -161,6 +201,11 @@ impl DropdownMenuTrigger {
self.full_width = full_width;
self
}
+
+ pub fn style(mut self, style: DropdownStyle) -> Self {
+ self.style = style;
+ self
+ }
}
impl Disableable for DropdownMenuTrigger {
@@ -193,11 +238,13 @@ impl RenderOnce for DropdownMenuTrigger {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let disabled = self.disabled;
+ let style = DropdownTriggerStyle::for_style(self.style, cx);
+
h_flex()
.id("dropdown-menu-trigger")
.justify_between()
.rounded_sm()
- .bg(cx.theme().colors().editor_background)
+ .bg(style.bg)
.pl_2()
.pr_1p5()
.py_0p5()