Detailed changes
@@ -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()
@@ -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))
@@ -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)),
@@ -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);
});
+ v_flex()
+ .size_full()
+ .key_context("DebugSessionItem")
+ .track_focus(&self.focus_handle(cx))
+ .child(h_flex().flex_1().child(x))
+ }
+}
- let active_thread_item = &self.active_thread_item;
+struct SubView {
+ inner: AnyView,
+ pane_focus_handle: FocusHandle,
+ tab_name: SharedString,
+}
- let capabilities = self.capabilities(cx);
- h_flex()
- .key_context("DebugPanelItem")
- .track_focus(&self.focus_handle(cx))
- .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())
+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)
})
- .when(*active_thread_item == ThreadItem::Modules, |this| {
- this.size_full().child(self.module_list.clone())
+ })
+ .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();
})
- .when(*active_thread_item == ThreadItem::LoadedSource, |this| {
- this.size_full().child(self.loaded_source_list.clone())
+ .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)
})
- .when(*active_thread_item == ThreadItem::Console, |this| {
- this.child(self.console.clone())
- }),
- )
- }
+ .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, ¢er_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;
@@ -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);
@@ -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()
@@ -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!(
@@ -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()
@@ -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))
});
@@ -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
@@ -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| {
@@ -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,
))
@@ -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)
@@ -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,
))