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

@ -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,
))