debugger: Tidy up dropdown menus (#30679)
Before  After  Release Notes: - N/A
This commit is contained in:
parent
4280bff10a
commit
dce6e96c16
6 changed files with 329 additions and 169 deletions
|
@ -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;
|
||||
|
|
186
crates/debugger_ui/src/dropdown_menus.rs
Normal file
186
crates/debugger_ui/src/dropdown_menus.rs
Normal file
|
@ -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()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue