Debugger UI: Dynamic session contents (#28033)

Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...

---------

Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Anthony <anthony@zed.dev>
This commit is contained in:
Piotr Osiewicz 2025-04-07 23:22:09 +02:00 committed by GitHub
parent fdaf2a27bf
commit 22b937f27f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 663 additions and 537 deletions

View file

@ -30,9 +30,8 @@ use task::DebugTaskDefinition;
use terminal_view::terminal_panel::TerminalPanel;
use ui::{ContextMenu, Divider, DropdownMenu, Tooltip, prelude::*};
use workspace::{
Pane, Workspace,
Workspace,
dock::{DockPosition, Panel, PanelEvent},
pane,
};
pub enum DebugPanelEvent {
@ -55,11 +54,13 @@ pub enum DebugPanelEvent {
actions!(debug_panel, [ToggleFocus]);
pub struct DebugPanel {
size: Pixels,
pane: Entity<Pane>,
sessions: Vec<Entity<DebugSession>>,
active_session: Option<Entity<DebugSession>>,
/// This represents the last debug definition that was created in the new session modal
pub(crate) past_debug_definition: Option<DebugTaskDefinition>,
project: WeakEntity<Project>,
workspace: WeakEntity<Workspace>,
focus_handle: FocusHandle,
_subscriptions: Vec<Subscription>,
}
@ -72,36 +73,17 @@ impl DebugPanel {
cx.new(|cx| {
let project = workspace.project().clone();
let dap_store = project.read(cx).dap_store();
let pane = cx.new(|cx| {
let mut pane = Pane::new(
workspace.weak_handle(),
project.clone(),
Default::default(),
None,
gpui::NoAction.boxed_clone(),
window,
cx,
);
pane.set_can_split(None);
pane.set_can_navigate(true, cx);
pane.display_nav_history_buttons(None);
pane.set_should_display_tab_bar(|_window, _cx| false);
pane.set_close_pane_if_empty(true, cx);
pane
});
let _subscriptions = vec![
cx.observe(&pane, |_, _, cx| cx.notify()),
cx.subscribe_in(&pane, window, Self::handle_pane_event),
cx.subscribe_in(&dap_store, window, Self::handle_dap_store_event),
];
let _subscriptions =
vec![cx.subscribe_in(&dap_store, window, Self::handle_dap_store_event)];
let debug_panel = Self {
pane,
size: px(300.),
sessions: vec![],
active_session: None,
_subscriptions,
past_debug_definition: None,
focus_handle: cx.focus_handle(),
project: project.downgrade(),
workspace: workspace.weak_handle(),
};
@ -130,7 +112,7 @@ impl DebugPanel {
cx.observe(&debug_panel, |_, debug_panel, cx| {
let (has_active_session, supports_restart, support_step_back) = debug_panel
.update(cx, |this, cx| {
this.active_session(cx)
this.active_session()
.map(|item| {
let running = item.read(cx).mode().as_running().cloned();
@ -192,11 +174,8 @@ impl DebugPanel {
})
}
pub fn active_session(&self, cx: &App) -> Option<Entity<DebugSession>> {
self.pane
.read(cx)
.active_item()
.and_then(|panel| panel.downcast::<DebugSession>())
pub fn active_session(&self) -> Option<Entity<DebugSession>> {
self.active_session.clone()
}
pub fn debug_panel_items_by_client(
@ -204,10 +183,8 @@ impl DebugPanel {
client_id: &SessionId,
cx: &Context<Self>,
) -> Vec<Entity<DebugSession>> {
self.pane
.read(cx)
.items()
.filter_map(|item| item.downcast::<DebugSession>())
self.sessions
.iter()
.filter(|item| item.read(cx).session_id(cx) == Some(*client_id))
.map(|item| item.clone())
.collect()
@ -218,15 +195,14 @@ impl DebugPanel {
client_id: SessionId,
cx: &mut Context<Self>,
) -> Option<Entity<DebugSession>> {
self.pane
.read(cx)
.items()
.filter_map(|item| item.downcast::<DebugSession>())
self.sessions
.iter()
.find(|item| {
let item = item.read(cx);
item.session_id(cx) == Some(client_id)
})
.cloned()
}
fn handle_dap_store_event(
@ -248,10 +224,11 @@ impl DebugPanel {
return log::error!("Debug Panel out lived it's weak reference to Project");
};
if self.pane.read_with(cx, |pane, cx| {
pane.items_of_type::<DebugSession>()
.any(|item| item.read(cx).session_id(cx) == Some(*session_id))
}) {
if self
.sessions
.iter()
.any(|item| item.read(cx).session_id(cx) == Some(*session_id))
{
// We already have an item for this session.
return;
}
@ -264,11 +241,8 @@ impl DebugPanel {
cx,
);
self.pane.update(cx, |pane, cx| {
pane.add_item(Box::new(session_item), true, true, None, window, cx);
window.focus(&pane.focus_handle(cx));
cx.notify();
});
self.sessions.push(session_item.clone());
self.activate_session(session_item, window, cx);
}
dap_store::DapStoreEvent::RunInTerminal {
title,
@ -362,63 +336,9 @@ impl DebugPanel {
})
}
fn handle_pane_event(
&mut self,
_: &Entity<Pane>,
event: &pane::Event,
window: &mut Window,
cx: &mut Context<Self>,
) {
match event {
pane::Event::Remove { .. } => cx.emit(PanelEvent::Close),
pane::Event::ZoomIn => cx.emit(PanelEvent::ZoomIn),
pane::Event::ZoomOut => cx.emit(PanelEvent::ZoomOut),
pane::Event::AddItem { item } => {
self.workspace
.update(cx, |workspace, cx| {
item.added_to_pane(workspace, self.pane.clone(), window, cx)
})
.ok();
}
pane::Event::RemovedItem { item } => {
if let Some(debug_session) = item.downcast::<DebugSession>() {
debug_session.update(cx, |session, cx| {
session.shutdown(cx);
})
}
}
pane::Event::ActivateItem {
local: _,
focus_changed,
} => {
if *focus_changed {
if let Some(debug_session) = self
.pane
.read(cx)
.active_item()
.and_then(|item| item.downcast::<DebugSession>())
{
if let Some(running) = debug_session
.read_with(cx, |session, _| session.mode().as_running().cloned())
{
running.update(cx, |running, cx| {
running.go_to_selected_stack_frame(window, cx);
});
}
}
}
}
_ => {}
}
}
fn top_controls_strip(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<Div> {
let active_session = self
.pane
.read(cx)
.active_item()
.and_then(|item| item.downcast::<DebugSession>());
let active_session = self.active_session.clone();
Some(
h_flex()
.border_b_1()
@ -609,34 +529,29 @@ impl DebugPanel {
},
)
.when_some(active_session.as_ref(), |this, session| {
let pane = self.pane.downgrade();
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| {
let sessions = pane
.read_with(cx, |pane, _| {
pane.items().map(|item| item.boxed_clone()).collect()
})
.ok()
.unwrap_or_else(Vec::new);
for (index, item) in sessions.into_iter().enumerate() {
if let Some(session) = item.downcast::<DebugSession>() {
let pane = pane.clone();
this = this.entry(
session.read(cx).label(cx),
None,
move |window, cx| {
pane.update(cx, |pane, cx| {
pane.activate_item(
index, true, true, window, cx,
);
})
.ok();
},
);
}
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
}),
@ -680,6 +595,25 @@ impl DebugPanel {
),
)
}
fn activate_session(
&mut self,
session_item: Entity<DebugSession>,
window: &mut Window,
cx: &mut Context<Self>,
) {
debug_assert!(self.sessions.contains(&session_item));
session_item.focus_handle(cx).focus(window);
session_item.update(cx, |this, cx| {
if let Some(running) = this.mode().as_running() {
running.update(cx, |this, cx| {
this.go_to_selected_stack_frame(window, cx);
});
}
});
self.active_session = Some(session_item);
cx.notify();
}
}
impl EventEmitter<PanelEvent> for DebugPanel {}
@ -687,16 +621,12 @@ impl EventEmitter<DebugPanelEvent> for DebugPanel {}
impl EventEmitter<project::Event> for DebugPanel {}
impl Focusable for DebugPanel {
fn focus_handle(&self, cx: &App) -> FocusHandle {
self.pane.focus_handle(cx)
fn focus_handle(&self, _: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
impl Panel for DebugPanel {
fn pane(&self) -> Option<Entity<Pane>> {
Some(self.pane.clone())
}
fn persistent_name() -> &'static str {
"DebugPanel"
}
@ -753,7 +683,9 @@ impl Panel for DebugPanel {
impl Render for DebugPanel {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let has_sessions = self.pane.read(cx).items_len() > 0;
let has_sessions = self.sessions.len() > 0;
debug_assert_eq!(has_sessions, self.active_session.is_some());
v_flex()
.size_full()
.key_context("DebugPanel")
@ -761,7 +693,7 @@ impl Render for DebugPanel {
.track_focus(&self.focus_handle(cx))
.map(|this| {
if has_sessions {
this.child(self.pane.clone())
this.children(self.active_session.clone())
} else {
this.child(
v_flex()

View file

@ -52,7 +52,7 @@ pub fn init(cx: &mut App) {
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
panel
.active_session(cx)
.active_session()
.and_then(|session| session.read(cx).mode().as_running().cloned())
}) {
active_item.update(cx, |item, cx| item.pause_thread(cx))
@ -63,7 +63,7 @@ pub fn init(cx: &mut App) {
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
panel
.active_session(cx)
.active_session()
.and_then(|session| session.read(cx).mode().as_running().cloned())
}) {
active_item.update(cx, |item, cx| item.restart_session(cx))
@ -74,7 +74,7 @@ pub fn init(cx: &mut App) {
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
panel
.active_session(cx)
.active_session()
.and_then(|session| session.read(cx).mode().as_running().cloned())
}) {
active_item.update(cx, |item, cx| item.step_in(cx))
@ -85,7 +85,7 @@ pub fn init(cx: &mut App) {
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
panel
.active_session(cx)
.active_session()
.and_then(|session| session.read(cx).mode().as_running().cloned())
}) {
active_item.update(cx, |item, cx| item.step_over(cx))
@ -96,7 +96,7 @@ pub fn init(cx: &mut App) {
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
panel
.active_session(cx)
.active_session()
.and_then(|session| session.read(cx).mode().as_running().cloned())
}) {
active_item.update(cx, |item, cx| item.step_back(cx))
@ -107,7 +107,7 @@ pub fn init(cx: &mut App) {
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
panel
.active_session(cx)
.active_session()
.and_then(|session| session.read(cx).mode().as_running().cloned())
}) {
active_item.update(cx, |item, cx| item.stop_thread(cx))
@ -118,7 +118,7 @@ pub fn init(cx: &mut App) {
if let Some(debug_panel) = workspace.panel::<DebugPanel>(cx) {
if let Some(active_item) = debug_panel.read_with(cx, |panel, cx| {
panel
.active_session(cx)
.active_session()
.and_then(|session| session.read(cx).mode().as_running().cloned())
}) {
active_item.update(cx, |item, cx| item.toggle_ignore_breakpoints(cx))

View file

@ -43,14 +43,6 @@ pub enum DebugPanelItemEvent {
Stopped { go_to_stack_frame: bool },
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ThreadItem {
Console,
LoadedSource,
Modules,
Variables,
}
impl DebugSession {
pub(crate) fn running(
project: Entity<Project>,
@ -60,7 +52,15 @@ impl DebugSession {
window: &mut Window,
cx: &mut App,
) -> Entity<Self> {
let mode = cx.new(|cx| RunningState::new(session.clone(), workspace.clone(), window, cx));
let mode = cx.new(|cx| {
RunningState::new(
session.clone(),
project.clone(),
workspace.clone(),
window,
cx,
)
});
cx.new(|cx| Self {
_subscriptions: [cx.subscribe(&mode, |_, _, _, cx| {
@ -81,6 +81,7 @@ 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)),

View file

@ -4,47 +4,66 @@ mod module_list;
pub mod stack_frame_list;
pub mod variable_list;
use super::{DebugPanelItemEvent, ThreadItem};
use std::{any::Any, ops::ControlFlow, sync::Arc};
use super::DebugPanelItemEvent;
use collections::HashMap;
use console::Console;
use dap::{Capabilities, Thread, client::SessionId, debugger_settings::DebuggerSettings};
use gpui::{AppContext, Entity, EventEmitter, FocusHandle, Focusable, Subscription, WeakEntity};
use gpui::{
Action as _, AnyView, AppContext, Entity, EntityId, EventEmitter, FocusHandle, Focusable,
NoAction, Subscription, WeakEntity,
};
use loaded_source_list::LoadedSourceList;
use module_list::ModuleList;
use project::debugger::session::{Session, SessionEvent, ThreadId, ThreadStatus};
use project::{
Project,
debugger::session::{Session, SessionEvent, ThreadId, ThreadStatus},
};
use rpc::proto::ViewId;
use settings::Settings;
use stack_frame_list::StackFrameList;
use ui::{
ActiveTheme, AnyElement, App, Button, Context, ContextMenu, DropdownMenu, FluentBuilder,
Indicator, InteractiveElement, IntoElement, ParentElement, Render, SharedString,
StatefulInteractiveElement, Styled, Window, div, h_flex, v_flex,
App, Context, ContextMenu, DropdownMenu, InteractiveElement, IntoElement, ParentElement,
Render, SharedString, Styled, Window, div, h_flex, v_flex,
};
use util::ResultExt;
use variable_list::VariableList;
use workspace::Workspace;
use workspace::{
ActivePaneDecorator, DraggedTab, Item, Pane, PaneGroup, Workspace, move_item, pane::Event,
};
pub struct RunningState {
session: Entity<Session>,
thread_id: Option<ThreadId>,
console: Entity<console::Console>,
focus_handle: FocusHandle,
_remote_id: Option<ViewId>,
show_console_indicator: bool,
module_list: Entity<module_list::ModuleList>,
active_thread_item: ThreadItem,
workspace: WeakEntity<Workspace>,
session_id: SessionId,
variable_list: Entity<variable_list::VariableList>,
_subscriptions: Vec<Subscription>,
stack_frame_list: Entity<stack_frame_list::StackFrameList>,
loaded_source_list: Entity<loaded_source_list::LoadedSourceList>,
_module_list: Entity<module_list::ModuleList>,
_console: Entity<Console>,
panes: PaneGroup,
pane_close_subscriptions: HashMap<EntityId, Subscription>,
}
impl Render for RunningState {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let threads = self.session.update(cx, |this, cx| this.threads(cx));
self.select_current_thread(&threads, cx);
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let active = self.panes.panes().into_iter().next();
let x = if let Some(active) = active {
self.panes
.render(
None,
&ActivePaneDecorator::new(active, &self.workspace),
window,
cx,
)
.into_any_element()
} else {
div().into_any_element()
};
let thread_status = self
.thread_id
.map(|thread_id| self.session.read(cx).thread_status(thread_id))
@ -53,88 +72,185 @@ impl Render for RunningState {
self.variable_list.update(cx, |this, cx| {
this.disabled(thread_status != ThreadStatus::Stopped, cx);
});
let active_thread_item = &self.active_thread_item;
let capabilities = self.capabilities(cx);
h_flex()
.key_context("DebugPanelItem")
.track_focus(&self.focus_handle(cx))
v_flex()
.size_full()
.items_start()
.child(
v_flex().size_full().items_start().child(
h_flex()
.size_full()
.items_start()
.p_1()
.gap_4()
.child(self.stack_frame_list.clone()),
),
)
.child(
v_flex()
.border_l_1()
.border_color(cx.theme().colors().border_variant)
.size_full()
.items_start()
.child(
h_flex()
.border_b_1()
.w_full()
.border_color(cx.theme().colors().border_variant)
.child(self.render_entry_button(
&SharedString::from("Variables"),
ThreadItem::Variables,
cx,
))
.when(
capabilities.supports_modules_request.unwrap_or_default(),
|this| {
this.child(self.render_entry_button(
&SharedString::from("Modules"),
ThreadItem::Modules,
cx,
))
},
)
.when(
capabilities
.supports_loaded_sources_request
.unwrap_or_default(),
|this| {
this.child(self.render_entry_button(
&SharedString::from("Loaded Sources"),
ThreadItem::LoadedSource,
cx,
))
},
)
.child(self.render_entry_button(
&SharedString::from("Console"),
ThreadItem::Console,
cx,
)),
)
.when(*active_thread_item == ThreadItem::Variables, |this| {
this.child(self.variable_list.clone())
})
.when(*active_thread_item == ThreadItem::Modules, |this| {
this.size_full().child(self.module_list.clone())
})
.when(*active_thread_item == ThreadItem::LoadedSource, |this| {
this.size_full().child(self.loaded_source_list.clone())
})
.when(*active_thread_item == ThreadItem::Console, |this| {
this.child(self.console.clone())
}),
)
.key_context("DebugSessionItem")
.track_focus(&self.focus_handle(cx))
.child(h_flex().flex_1().child(x))
}
}
struct SubView {
inner: AnyView,
pane_focus_handle: FocusHandle,
tab_name: SharedString,
}
impl SubView {
fn new(
pane_focus_handle: FocusHandle,
view: AnyView,
tab_name: SharedString,
cx: &mut App,
) -> Entity<Self> {
cx.new(|_| Self {
tab_name,
inner: view,
pane_focus_handle,
})
}
}
impl Focusable for SubView {
fn focus_handle(&self, _: &App) -> FocusHandle {
self.pane_focus_handle.clone()
}
}
impl EventEmitter<()> for SubView {}
impl Item for SubView {
type Event = ();
fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
Some(self.tab_name.clone())
}
}
impl Render for SubView {
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
v_flex().size_full().child(self.inner.clone())
}
}
fn new_debugger_pane(
workspace: WeakEntity<Workspace>,
project: Entity<Project>,
window: &mut Window,
cx: &mut Context<RunningState>,
) -> Entity<Pane> {
let weak_running = cx.weak_entity();
let custom_drop_handle = {
let workspace = workspace.clone();
let project = project.downgrade();
let weak_running = weak_running.clone();
move |pane: &mut Pane, any: &dyn Any, window: &mut Window, cx: &mut Context<Pane>| {
let Some(tab) = any.downcast_ref::<DraggedTab>() else {
return ControlFlow::Break(());
};
let Some(project) = project.upgrade() else {
return ControlFlow::Break(());
};
let this_pane = cx.entity().clone();
let item = if tab.pane == this_pane {
pane.item_for_index(tab.ix)
} else {
tab.pane.read(cx).item_for_index(tab.ix)
};
let Some(item) = item.filter(|item| item.downcast::<SubView>().is_some()) else {
return ControlFlow::Break(());
};
let source = tab.pane.clone();
let item_id_to_move = item.item_id();
let Ok(new_split_pane) = pane
.drag_split_direction()
.map(|split_direction| {
weak_running.update(cx, |running, cx| {
let new_pane =
new_debugger_pane(workspace.clone(), project.clone(), window, cx);
let _previous_subscription = running.pane_close_subscriptions.insert(
new_pane.entity_id(),
cx.subscribe(&new_pane, RunningState::handle_pane_event),
);
debug_assert!(_previous_subscription.is_none());
running
.panes
.split(&this_pane, &new_pane, split_direction)?;
anyhow::Ok(new_pane)
})
})
.transpose()
else {
return ControlFlow::Break(());
};
match new_split_pane.transpose() {
// Source pane may be the one currently updated, so defer the move.
Ok(Some(new_pane)) => cx
.spawn_in(window, async move |_, cx| {
cx.update(|window, cx| {
move_item(
&source,
&new_pane,
item_id_to_move,
new_pane.read(cx).active_item_index(),
window,
cx,
);
})
.ok();
})
.detach(),
// If we drop into existing pane or current pane,
// regular pane drop handler will take care of it,
// using the right tab index for the operation.
Ok(None) => return ControlFlow::Continue(()),
err @ Err(_) => {
err.log_err();
return ControlFlow::Break(());
}
};
ControlFlow::Break(())
}
};
let ret = cx.new(move |cx| {
let mut pane = Pane::new(
workspace.clone(),
project.clone(),
Default::default(),
None,
NoAction.boxed_clone(),
window,
cx,
);
pane.set_can_split(Some(Arc::new(move |pane, dragged_item, _window, cx| {
if let Some(tab) = dragged_item.downcast_ref::<DraggedTab>() {
let is_current_pane = tab.pane == cx.entity();
let Some(can_drag_away) = weak_running
.update(cx, |running_state, _| {
let current_panes = running_state.panes.panes();
!current_panes.contains(&&tab.pane)
|| current_panes.len() > 1
|| (!is_current_pane || pane.items_len() > 1)
})
.ok()
else {
return false;
};
if can_drag_away {
let item = if is_current_pane {
pane.item_for_index(tab.ix)
} else {
tab.pane.read(cx).item_for_index(tab.ix)
};
if let Some(item) = item {
return item.downcast::<SubView>().is_some();
}
}
}
false
})));
pane.display_nav_history_buttons(None);
pane.set_custom_drop_handle(cx, custom_drop_handle);
pane
});
ret
}
impl RunningState {
pub fn new(
session: Entity<Session>,
project: Entity<Project>,
workspace: WeakEntity<Workspace>,
window: &mut Window,
cx: &mut Context<Self>,
@ -151,6 +267,7 @@ impl RunningState {
let module_list = cx.new(|cx| ModuleList::new(session.clone(), workspace.clone(), cx));
#[expect(unused)]
let loaded_source_list = cx.new(|cx| LoadedSourceList::new(session.clone(), cx));
let console = cx.new(|cx| {
@ -188,24 +305,116 @@ impl RunningState {
}),
];
let leftmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
leftmost_pane.update(cx, |this, cx| {
this.add_item(
Box::new(SubView::new(
this.focus_handle(cx),
stack_frame_list.clone().into(),
SharedString::new_static("Frames"),
cx,
)),
true,
false,
None,
window,
cx,
);
});
let center_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
center_pane.update(cx, |this, cx| {
this.add_item(
Box::new(SubView::new(
variable_list.focus_handle(cx),
variable_list.clone().into(),
SharedString::new_static("Variables"),
cx,
)),
true,
false,
None,
window,
cx,
);
this.add_item(
Box::new(SubView::new(
this.focus_handle(cx),
module_list.clone().into(),
SharedString::new_static("Modules"),
cx,
)),
true,
false,
None,
window,
cx,
);
});
let rightmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
rightmost_pane.update(cx, |this, cx| {
this.add_item(
Box::new(SubView::new(
this.focus_handle(cx),
console.clone().into(),
SharedString::new_static("Console"),
cx,
)),
true,
false,
None,
window,
cx,
);
});
let pane_close_subscriptions = HashMap::from_iter(
[&leftmost_pane, &center_pane, &rightmost_pane]
.into_iter()
.map(|entity| {
(
entity.entity_id(),
cx.subscribe(entity, Self::handle_pane_event),
)
}),
);
let group_root = workspace::PaneAxis::new(
gpui::Axis::Horizontal,
[leftmost_pane, center_pane, rightmost_pane]
.into_iter()
.map(workspace::Member::Pane)
.collect(),
);
let panes = PaneGroup::with_root(workspace::Member::Axis(group_root));
Self {
session,
console,
workspace,
module_list,
focus_handle,
variable_list,
_subscriptions,
thread_id: None,
_remote_id: None,
stack_frame_list,
loaded_source_list,
session_id,
show_console_indicator: false,
active_thread_item: ThreadItem::Variables,
panes,
_module_list: module_list,
_console: console,
pane_close_subscriptions,
}
}
fn handle_pane_event(
this: &mut RunningState,
source_pane: Entity<Pane>,
event: &Event,
cx: &mut Context<RunningState>,
) {
if let Event::Remove { .. } = event {
let _did_find_pane = this.panes.remove(&source_pane).is_ok();
debug_assert!(_did_find_pane);
cx.notify();
}
}
pub(crate) fn go_to_selected_stack_frame(&self, window: &Window, cx: &mut Context<Self>) {
if self.thread_id.is_some() {
self.stack_frame_list
@ -221,12 +430,6 @@ impl RunningState {
self.session_id
}
#[cfg(test)]
pub fn set_thread_item(&mut self, thread_item: ThreadItem, cx: &mut Context<Self>) {
self.active_thread_item = thread_item;
cx.notify()
}
#[cfg(test)]
pub fn stack_frame_list(&self) -> &Entity<StackFrameList> {
&self.stack_frame_list
@ -234,14 +437,31 @@ impl RunningState {
#[cfg(test)]
pub fn console(&self) -> &Entity<Console> {
&self.console
&self._console
}
#[cfg(test)]
pub(crate) fn module_list(&self) -> &Entity<ModuleList> {
&self.module_list
&self._module_list
}
#[cfg(test)]
pub(crate) fn activate_variable_list(&self, window: &mut Window, cx: &mut App) {
let (variable_list_position, pane) = self
.panes
.panes()
.into_iter()
.find_map(|pane| {
pane.read(cx)
.items_of_type::<SubView>()
.position(|view| view.read(cx).tab_name == *"Variables")
.map(|view| (view, pane))
})
.unwrap();
pane.update(cx, |this, cx| {
this.activate_item(variable_list_position, true, true, window, cx);
})
}
#[cfg(test)]
pub(crate) fn variable_list(&self) -> &Entity<VariableList> {
&self.variable_list
@ -292,41 +512,6 @@ impl RunningState {
cx.notify();
}
fn render_entry_button(
&self,
label: &SharedString,
thread_item: ThreadItem,
cx: &mut Context<Self>,
) -> AnyElement {
let has_indicator =
matches!(thread_item, ThreadItem::Console) && self.show_console_indicator;
div()
.id(label.clone())
.px_2()
.py_1()
.cursor_pointer()
.border_b_2()
.when(self.active_thread_item == thread_item, |this| {
this.border_color(cx.theme().colors().border)
})
.child(
h_flex()
.child(Button::new(label.clone(), label.clone()))
.when(has_indicator, |this| this.child(Indicator::dot())),
)
.on_click(cx.listener(move |this, _, _window, cx| {
this.active_thread_item = thread_item;
if matches!(this.active_thread_item, ThreadItem::Console) {
this.show_console_indicator = false;
}
cx.notify();
}))
.into_any_element()
}
pub fn continue_thread(&mut self, cx: &mut Context<Self>) {
let Some(thread_id) = self.thread_id else {
return;

View file

@ -687,6 +687,7 @@ impl VariableList {
.child(
ListItem::new(SharedString::from(format!("scope-{}", var_ref)))
.selectable(false)
.disabled(self.disabled)
.indent_level(state.depth + 1)
.indent_step_size(px(20.))
.always_show_disclosure_icon(true)
@ -695,7 +696,15 @@ impl VariableList {
let var_path = entry.path.clone();
cx.listener(move |this, _, _, cx| this.toggle_entry(&var_path, cx))
})
.child(div().text_ui(cx).w_full().child(scope.name.clone())),
.child(
div()
.text_ui(cx)
.w_full()
.when(self.disabled, |this| {
this.text_color(Color::Disabled.color(cx))
})
.child(scope.name.clone()),
),
)
.into_any()
}
@ -716,20 +725,27 @@ impl VariableList {
};
let syntax_color_for = |name| cx.theme().syntax().get(name).color;
let variable_name_color = match &dap
.presentation_hint
.as_ref()
.and_then(|hint| hint.kind.as_ref())
.unwrap_or(&VariablePresentationHintKind::Unknown)
{
VariablePresentationHintKind::Class
| VariablePresentationHintKind::BaseClass
| VariablePresentationHintKind::InnerClass
| VariablePresentationHintKind::MostDerivedClass => syntax_color_for("type"),
VariablePresentationHintKind::Data => syntax_color_for("variable"),
VariablePresentationHintKind::Unknown | _ => syntax_color_for("variable"),
let variable_name_color = if self.disabled {
Some(Color::Disabled.color(cx))
} else {
match &dap
.presentation_hint
.as_ref()
.and_then(|hint| hint.kind.as_ref())
.unwrap_or(&VariablePresentationHintKind::Unknown)
{
VariablePresentationHintKind::Class
| VariablePresentationHintKind::BaseClass
| VariablePresentationHintKind::InnerClass
| VariablePresentationHintKind::MostDerivedClass => syntax_color_for("type"),
VariablePresentationHintKind::Data => syntax_color_for("variable"),
VariablePresentationHintKind::Unknown | _ => syntax_color_for("variable"),
}
};
let variable_color = syntax_color_for("variable.special");
let variable_color = self
.disabled
.then(|| Color::Disabled.color(cx))
.or_else(|| syntax_color_for("variable.special"));
let var_ref = dap.variables_reference;
let colors = get_entry_color(cx);

View file

@ -76,7 +76,7 @@ pub fn active_debug_session_panel(
.update(cx, |workspace, _window, cx| {
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
debug_panel
.update(cx, |this, cx| this.active_session(cx))
.update(cx, |this, _| this.active_session())
.unwrap()
})
.unwrap()

View file

@ -101,10 +101,6 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp
.clone()
});
running_state.update(cx, |state, cx| {
state.set_thread_item(session::ThreadItem::Console, cx);
cx.refresh_windows();
});
cx.run_until_parked();
// assert we have output from before the thread stopped
@ -112,7 +108,7 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp
.update(cx, |workspace, _window, cx| {
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
let active_debug_session_panel = debug_panel
.update(cx, |this, cx| this.active_session(cx))
.update(cx, |this, _| this.active_session())
.unwrap();
assert_eq!(
@ -151,8 +147,7 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp
.await;
cx.run_until_parked();
running_state.update(cx, |state, cx| {
state.set_thread_item(session::ThreadItem::Console, cx);
running_state.update(cx, |_, cx| {
cx.refresh_windows();
});
cx.run_until_parked();
@ -162,7 +157,7 @@ async fn test_handle_output_event(executor: BackgroundExecutor, cx: &mut TestApp
.update(cx, |workspace, _window, cx| {
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
let active_session_panel = debug_panel
.update(cx, |this, cx| this.active_session(cx))
.update(cx, |this, _| this.active_session())
.unwrap();
assert_eq!(

View file

@ -85,9 +85,8 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test
workspace
.update(cx, |workspace, _window, cx| {
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
let active_session = debug_panel.update(cx, |debug_panel, cx| {
debug_panel.active_session(cx).unwrap()
});
let active_session =
debug_panel.update(cx, |debug_panel, _| debug_panel.active_session().unwrap());
let running_state = active_session.update(cx, |active_session, _| {
active_session
@ -98,9 +97,7 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test
});
debug_panel.update(cx, |this, cx| {
assert!(this.active_session(cx).is_some());
// we have one active session
assert_eq!(1, this.pane().unwrap().read(cx).items_len());
assert!(this.active_session().is_some());
assert!(running_state.read(cx).selected_thread_id().is_none());
});
})
@ -124,7 +121,7 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test
.update(cx, |workspace, _window, cx| {
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
let active_session = debug_panel
.update(cx, |this, cx| this.active_session(cx))
.update(cx, |this, _| this.active_session())
.unwrap();
let running_state = active_session.update(cx, |active_session, _| {
@ -135,11 +132,6 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test
.clone()
});
// we have one active session
assert_eq!(
1,
debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len())
);
assert_eq!(client.id(), running_state.read(cx).session_id());
assert_eq!(
ThreadId(1),
@ -162,7 +154,7 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
let active_session = debug_panel
.update(cx, |this, cx| this.active_session(cx))
.update(cx, |this, _| this.active_session())
.unwrap();
let running_state = active_session.update(cx, |active_session, _| {
@ -174,8 +166,7 @@ async fn test_basic_show_debug_panel(executor: BackgroundExecutor, cx: &mut Test
});
debug_panel.update(cx, |this, cx| {
assert!(this.active_session(cx).is_some());
assert_eq!(1, this.pane().unwrap().read(cx).items_len());
assert!(this.active_session().is_some());
assert_eq!(
ThreadId(1),
running_state.read(cx).selected_thread_id().unwrap()
@ -243,10 +234,8 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
.update(cx, |workspace, _window, cx| {
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
debug_panel.update(cx, |this, cx| {
assert!(this.active_session(cx).is_some());
// we have one active session
assert_eq!(1, this.pane().unwrap().read(cx).items_len());
debug_panel.update(cx, |this, _| {
assert!(this.active_session().is_some());
});
})
.unwrap();
@ -270,7 +259,7 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
.update(cx, |workspace, _window, cx| {
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
let active_session = debug_panel
.update(cx, |this, cx| this.active_session(cx))
.update(cx, |this, _| this.active_session())
.unwrap();
let running_state = active_session.update(cx, |active_session, _| {
@ -281,11 +270,6 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
.clone()
});
// we have one active session
assert_eq!(
1,
debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len())
);
assert_eq!(client.id(), active_session.read(cx).session_id(cx).unwrap());
assert_eq!(
ThreadId(1),
@ -312,7 +296,7 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
.update(cx, |workspace, _window, cx| {
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
let active_session = debug_panel
.update(cx, |this, cx| this.active_session(cx))
.update(cx, |this, _| this.active_session())
.unwrap();
let running_state = active_session.update(cx, |active_session, _| {
@ -323,11 +307,6 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
.clone()
});
// we have one active session
assert_eq!(
1,
debug_panel.update(cx, |this, cx| this.pane().unwrap().read(cx).items_len())
);
assert_eq!(client.id(), active_session.read(cx).session_id(cx).unwrap());
assert_eq!(
ThreadId(1),
@ -349,7 +328,7 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
.update(cx, |workspace, _window, cx| {
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
let active_session = debug_panel
.update(cx, |this, cx| this.active_session(cx))
.update(cx, |this, _| this.active_session())
.unwrap();
let running_state = active_session.update(cx, |active_session, _| {
@ -361,8 +340,7 @@ async fn test_we_can_only_have_one_panel_per_debug_session(
});
debug_panel.update(cx, |this, cx| {
assert!(this.active_session(cx).is_some());
assert_eq!(1, this.pane().unwrap().read(cx).items_len());
assert!(this.active_session().is_some());
assert_eq!(
ThreadId(1),
running_state.read(cx).selected_thread_id().unwrap()

View file

@ -1,6 +1,5 @@
use crate::{
debugger_panel::DebugPanel,
session::ThreadItem,
tests::{active_debug_session_panel, init_test, init_test_workspace},
};
use dap::{
@ -139,13 +138,7 @@ async fn test_module_list(executor: BackgroundExecutor, cx: &mut TestAppContext)
.clone()
});
assert!(
!called_modules.load(std::sync::atomic::Ordering::SeqCst),
"Request Modules shouldn't be called before it's needed"
);
running_state.update(cx, |state, cx| {
state.set_thread_item(ThreadItem::Modules, cx);
running_state.update(cx, |_, cx| {
cx.refresh_windows();
});
@ -157,9 +150,6 @@ async fn test_module_list(executor: BackgroundExecutor, cx: &mut TestAppContext)
);
active_debug_session_panel(workspace, cx).update(cx, |_, cx| {
running_state.update(cx, |state, cx| {
state.set_thread_item(ThreadItem::Modules, cx)
});
let actual_modules = running_state.update(cx, |state, cx| {
state.module_list().update(cx, |list, cx| list.modules(cx))
});

View file

@ -410,7 +410,7 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC
.update(cx, |workspace, _window, cx| {
let debug_panel = workspace.panel::<DebugPanel>(cx).unwrap();
let active_debug_panel_item = debug_panel
.update(cx, |this, cx| this.active_session(cx))
.update(cx, |this, _| this.active_session())
.unwrap();
active_debug_panel_item

View file

@ -207,7 +207,9 @@ async fn test_basic_fetch_initial_scope_and_variables(
.expect("Session should be running by this point")
.clone()
});
running_state.update_in(cx, |this, window, cx| {
this.activate_variable_list(window, cx);
});
cx.run_until_parked();
running_state.update(cx, |running_state, cx| {
@ -222,7 +224,6 @@ async fn test_basic_fetch_initial_scope_and_variables(
running_state
.variable_list()
.update(cx, |variable_list, _| {
assert_eq!(1, variable_list.scopes().len());
assert_eq!(scopes, variable_list.scopes());
assert_eq!(
vec![variables[0].clone(), variables[1].clone(),],
@ -480,7 +481,9 @@ async fn test_fetch_variables_for_multiple_scopes(
.expect("Session should be running by this point")
.clone()
});
running_state.update_in(cx, |this, window, cx| {
this.activate_variable_list(window, cx);
});
cx.run_until_parked();
running_state.update(cx, |running_state, cx| {
@ -797,7 +800,11 @@ async fn test_keyboard_navigation(executor: BackgroundExecutor, cx: &mut TestApp
variable_list.update(cx, |_, cx| cx.focus_self(window));
running
});
running_state.update_in(cx, |this, window, cx| {
this.activate_variable_list(window, cx);
});
cx.run_until_parked();
cx.dispatch_action(SelectFirst);
cx.dispatch_action(SelectFirst);
cx.run_until_parked();
@ -1541,16 +1548,13 @@ async fn test_variable_list_only_sends_requests_when_rendering(
})
.await;
let running_state = active_debug_session_panel(workspace, cx).update_in(cx, |item, _, cx| {
let running_state = active_debug_session_panel(workspace, cx).update_in(cx, |item, _, _| {
let state = item
.mode()
.as_running()
.expect("Session should be running by this point")
.clone();
state.update(cx, |state, cx| {
state.set_thread_item(crate::session::ThreadItem::Modules, cx)
});
state
});
@ -1577,9 +1581,10 @@ async fn test_variable_list_only_sends_requests_when_rendering(
assert!(!made_scopes_request.load(Ordering::SeqCst));
cx.focus_self(window);
running_state.set_thread_item(crate::session::ThreadItem::Variables, cx);
});
running_state.update_in(cx, |this, window, cx| {
this.activate_variable_list(window, cx);
});
cx.run_until_parked();
running_state.update(cx, |running_state, cx| {
@ -1893,7 +1898,9 @@ async fn test_it_fetches_scopes_variables_when_you_select_a_stack_frame(
.expect("Session should be running by this point")
.clone()
});
running_state.update_in(cx, |this, window, cx| {
this.activate_variable_list(window, cx);
});
cx.run_until_parked();
running_state.update(cx, |running_state, cx| {

View file

@ -1202,12 +1202,15 @@ impl Render for TerminalPanel {
self.workspace
.update(cx, |workspace, cx| {
registrar.size_full().child(self.center.render(
workspace.project(),
&HashMap::default(),
None,
&self.active_pane,
workspace.zoomed_item(),
workspace.app_state(),
&workspace::PaneRenderContext {
follower_states: &&HashMap::default(),
active_call: workspace.active_call(),
active_pane: &self.active_pane,
app_state: &workspace.app_state(),
project: workspace.project(),
workspace: &workspace.weak_handle(),
},
window,
cx,
))

View file

@ -8,8 +8,8 @@ use call::{ActiveCall, ParticipantLocation};
use client::proto::PeerId;
use collections::HashMap;
use gpui::{
Along, AnyView, AnyWeakView, Axis, Bounds, Context, Entity, IntoElement, MouseButton, Pixels,
Point, StyleRefinement, Window, point, size,
Along, AnyView, AnyWeakView, Axis, Bounds, Entity, Hsla, IntoElement, MouseButton, Pixels,
Point, StyleRefinement, WeakEntity, Window, point, size,
};
use parking_lot::Mutex;
use project::Project;
@ -124,26 +124,12 @@ impl PaneGroup {
pub fn render(
&self,
project: &Entity<Project>,
follower_states: &HashMap<PeerId, FollowerState>,
active_call: Option<&Entity<ActiveCall>>,
active_pane: &Entity<Pane>,
zoomed: Option<&AnyWeakView>,
app_state: &Arc<AppState>,
render_cx: &dyn PaneLeaderDecorator,
window: &mut Window,
cx: &mut Context<Workspace>,
cx: &mut App,
) -> impl IntoElement {
self.root.render(
project,
0,
follower_states,
active_call,
active_pane,
zoomed,
app_state,
window,
cx,
)
self.root.render(0, zoomed, render_cx, window, cx)
}
pub fn panes(&self) -> Vec<&Entity<Pane>> {
@ -195,6 +181,160 @@ pub enum Member {
Pane(Entity<Pane>),
}
#[derive(Clone, Copy)]
pub struct PaneRenderContext<'a> {
pub project: &'a Entity<Project>,
pub follower_states: &'a HashMap<PeerId, FollowerState>,
pub active_call: Option<&'a Entity<ActiveCall>>,
pub active_pane: &'a Entity<Pane>,
pub app_state: &'a Arc<AppState>,
pub workspace: &'a WeakEntity<Workspace>,
}
#[derive(Default)]
pub struct LeaderDecoration {
border: Option<Hsla>,
status_box: Option<AnyElement>,
}
pub trait PaneLeaderDecorator {
fn decorate(&self, pane: &Entity<Pane>, cx: &App) -> LeaderDecoration;
fn active_pane(&self) -> &Entity<Pane>;
fn workspace(&self) -> &WeakEntity<Workspace>;
}
pub struct ActivePaneDecorator<'a> {
active_pane: &'a Entity<Pane>,
workspace: &'a WeakEntity<Workspace>,
}
impl<'a> ActivePaneDecorator<'a> {
pub fn new(active_pane: &'a Entity<Pane>, workspace: &'a WeakEntity<Workspace>) -> Self {
Self {
active_pane,
workspace,
}
}
}
impl PaneLeaderDecorator for ActivePaneDecorator<'_> {
fn decorate(&self, _: &Entity<Pane>, _: &App) -> LeaderDecoration {
LeaderDecoration::default()
}
fn active_pane(&self) -> &Entity<Pane> {
self.active_pane
}
fn workspace(&self) -> &WeakEntity<Workspace> {
self.workspace
}
}
impl PaneLeaderDecorator for PaneRenderContext<'_> {
fn decorate(&self, pane: &Entity<Pane>, cx: &App) -> LeaderDecoration {
let follower_state = self.follower_states.iter().find_map(|(leader_id, state)| {
if state.center_pane == *pane {
Some((*leader_id, state))
} else {
None
}
});
let leader = follower_state.as_ref().and_then(|(leader_id, _)| {
let room = self.active_call?.read(cx).room()?.read(cx);
room.remote_participant_for_peer_id(*leader_id)
});
let Some(leader) = leader else {
return LeaderDecoration::default();
};
let is_in_unshared_view = follower_state.as_ref().map_or(false, |(_, state)| {
state
.active_view_id
.is_some_and(|view_id| !state.items_by_leader_view_id.contains_key(&view_id))
});
let is_in_panel = follower_state
.as_ref()
.map_or(false, |(_, state)| state.dock_pane.is_some());
let mut leader_join_data = None;
let leader_status_box = match leader.location {
ParticipantLocation::SharedProject {
project_id: leader_project_id,
} => {
if Some(leader_project_id) == self.project.read(cx).remote_id() {
is_in_unshared_view.then(|| {
Label::new(format!(
"{} is in an unshared pane",
leader.user.github_login
))
})
} else {
leader_join_data = Some((leader_project_id, leader.user.id));
Some(Label::new(format!(
"Follow {} to their active project",
leader.user.github_login,
)))
}
}
ParticipantLocation::UnsharedProject => Some(Label::new(format!(
"{} is viewing an unshared Zed project",
leader.user.github_login
))),
ParticipantLocation::External => Some(Label::new(format!(
"{} is viewing a window outside of Zed",
leader.user.github_login
))),
};
let mut leader_color = cx
.theme()
.players()
.color_for_participant(leader.participant_index.0)
.cursor;
if is_in_panel {
leader_color.fade_out(0.75);
} else {
leader_color.fade_out(0.3);
}
let status_box = leader_status_box.map(|status| {
div()
.absolute()
.w_96()
.bottom_3()
.right_3()
.elevation_2(cx)
.p_1()
.child(status)
.when_some(
leader_join_data,
|this, (leader_project_id, leader_user_id)| {
let app_state = self.app_state.clone();
this.cursor_pointer()
.on_mouse_down(MouseButton::Left, move |_, _, cx| {
crate::join_in_room_project(
leader_project_id,
leader_user_id,
app_state.clone(),
cx,
)
.detach_and_log_err(cx);
})
},
)
.into_any_element()
});
LeaderDecoration {
status_box,
border: Some(leader_color),
}
}
fn active_pane(&self) -> &Entity<Pane> {
self.active_pane
}
fn workspace(&self) -> &WeakEntity<Workspace> {
self.workspace
}
}
impl Member {
fn new_axis(old_pane: Entity<Pane>, new_pane: Entity<Pane>, direction: SplitDirection) -> Self {
use Axis::*;
@ -222,15 +362,11 @@ impl Member {
pub fn render(
&self,
project: &Entity<Project>,
basis: usize,
follower_states: &HashMap<PeerId, FollowerState>,
active_call: Option<&Entity<ActiveCall>>,
active_pane: &Entity<Pane>,
zoomed: Option<&AnyWeakView>,
app_state: &Arc<AppState>,
render_cx: &dyn PaneLeaderDecorator,
window: &mut Window,
cx: &mut Context<Workspace>,
cx: &mut App,
) -> impl IntoElement {
match self {
Member::Pane(pane) => {
@ -238,76 +374,7 @@ impl Member {
return div().into_any();
}
let follower_state = follower_states.iter().find_map(|(leader_id, state)| {
if state.center_pane == *pane {
Some((*leader_id, state))
} else {
None
}
});
let leader = follower_state.as_ref().and_then(|(leader_id, _)| {
let room = active_call?.read(cx).room()?.read(cx);
room.remote_participant_for_peer_id(*leader_id)
});
let is_in_unshared_view = follower_state.as_ref().map_or(false, |(_, state)| {
state.active_view_id.is_some_and(|view_id| {
!state.items_by_leader_view_id.contains_key(&view_id)
})
});
let is_in_panel = follower_state
.as_ref()
.map_or(false, |(_, state)| state.dock_pane.is_some());
let mut leader_border = None;
let mut leader_status_box = None;
let mut leader_join_data = None;
if let Some(leader) = &leader {
let mut leader_color = cx
.theme()
.players()
.color_for_participant(leader.participant_index.0)
.cursor;
if is_in_panel {
leader_color.fade_out(0.75);
} else {
leader_color.fade_out(0.3);
}
leader_border = Some(leader_color);
leader_status_box = match leader.location {
ParticipantLocation::SharedProject {
project_id: leader_project_id,
} => {
if Some(leader_project_id) == project.read(cx).remote_id() {
if is_in_unshared_view {
Some(Label::new(format!(
"{} is in an unshared pane",
leader.user.github_login
)))
} else {
None
}
} else {
leader_join_data = Some((leader_project_id, leader.user.id));
Some(Label::new(format!(
"Follow {} to their active project",
leader.user.github_login,
)))
}
}
ParticipantLocation::UnsharedProject => Some(Label::new(format!(
"{} is viewing an unshared Zed project",
leader.user.github_login
))),
ParticipantLocation::External => Some(Label::new(format!(
"{} is viewing a window outside of Zed",
leader.user.github_login
))),
};
}
let decoration = render_cx.decorate(pane, cx);
div()
.relative()
@ -317,7 +384,7 @@ impl Member {
AnyView::from(pane.clone())
.cached(StyleRefinement::default().v_flex().size_full()),
)
.when_some(leader_border, |this, color| {
.when_some(decoration.border, |this, color| {
this.child(
div()
.absolute()
@ -328,49 +395,11 @@ impl Member {
.border_color(color),
)
})
.when_some(leader_status_box, |this, status_box| {
this.child(
div()
.absolute()
.w_96()
.bottom_3()
.right_3()
.elevation_2(cx)
.p_1()
.child(status_box)
.when_some(
leader_join_data,
|this, (leader_project_id, leader_user_id)| {
this.cursor_pointer().on_mouse_down(
MouseButton::Left,
cx.listener(move |this, _, _, cx| {
crate::join_in_room_project(
leader_project_id,
leader_user_id,
this.app_state().clone(),
cx,
)
.detach_and_log_err(cx);
}),
)
},
),
)
})
.children(decoration.status_box)
.into_any()
}
Member::Axis(axis) => axis
.render(
project,
basis + 1,
follower_states,
active_call,
active_pane,
zoomed,
app_state,
window,
cx,
)
.render(basis + 1, zoomed, render_cx, window, cx)
.into_any(),
}
}
@ -671,15 +700,11 @@ impl PaneAxis {
fn render(
&self,
project: &Entity<Project>,
basis: usize,
follower_states: &HashMap<PeerId, FollowerState>,
active_call: Option<&Entity<ActiveCall>>,
active_pane: &Entity<Pane>,
zoomed: Option<&AnyWeakView>,
app_state: &Arc<AppState>,
render_cx: &dyn PaneLeaderDecorator,
window: &mut Window,
cx: &mut Context<Workspace>,
cx: &mut App,
) -> gpui::AnyElement {
debug_assert!(self.members.len() == self.flexes.lock().len());
let mut active_pane_ix = None;
@ -689,24 +714,14 @@ impl PaneAxis {
basis,
self.flexes.clone(),
self.bounding_boxes.clone(),
cx.entity().downgrade(),
render_cx.workspace().clone(),
)
.children(self.members.iter().enumerate().map(|(ix, member)| {
if matches!(member, Member::Pane(pane) if pane == active_pane) {
if matches!(member, Member::Pane(pane) if pane == render_cx.active_pane()) {
active_pane_ix = Some(ix);
}
member
.render(
project,
(basis + ix) * 10,
follower_states,
active_call,
active_pane,
zoomed,
app_state,
window,
cx,
)
.render((basis + ix) * 10, zoomed, render_cx, window, cx)
.into_any_element()
}))
.with_active_pane(active_pane_ix)

View file

@ -5561,12 +5561,16 @@ impl Render for Workspace {
this.child(p.border_r_1())
})
.child(self.center.render(
&self.project,
&self.follower_states,
self.active_call(),
&self.active_pane,
self.zoomed.as_ref(),
&self.app_state,
&PaneRenderContext {
follower_states:
&self.follower_states,
active_call: self.active_call(),
active_pane: &self.active_pane,
app_state: &self.app_state,
project: &self.project,
workspace: &self.weak_self,
},
window,
cx,
))