pub(crate) mod breakpoint_list;pub(crate) mod console;pub(crate) mod loaded_source_list;pub(crate) mod module_list;pub mod stack_frame_list;pub mod variable_list; use std::{any::Any, ops::ControlFlow, path::PathBuf, sync::Arc, time::Duration}; use crate::{ ToggleExpandItem, new_process_modal::resolve_path, persistence::{self, DebuggerPaneItem, SerializedLayout},}; use super::DebugPanelItemEvent;use anyhow::{Context as _, Result, anyhow};use breakpoint_list::BreakpointList;use collections::{HashMap, IndexMap};use console::Console;use dap::{ Capabilities, DapRegistry, RunInTerminalRequestArguments, Thread, adapters::{DebugAdapterName, DebugTaskDefinition}, client::SessionId, debugger_settings::DebuggerSettings,};use futures::{SinkExt, channel::mpsc};use gpui::{ Action as _, AnyView, AppContext, Axis, Entity, EntityId, EventEmitter, FocusHandle, Focusable, NoAction, Pixels, Point, Subscription, Task, WeakEntity,};use language::Buffer;use loaded_source_list::LoadedSourceList;use module_list::ModuleList;use project::{ Project, WorktreeId, debugger::session::{Session, SessionEvent, ThreadId, ThreadStatus}, terminals::TerminalKind,};use rpc::proto::ViewId;use serde_json::Value;use settings::Settings;use stack_frame_list::StackFrameList;use task::{ BuildTaskDefinition, DebugScenario, ShellBuilder, SpawnInTerminal, TaskContext, ZedDebugConfig, substitute_variables_in_str,};use terminal_view::TerminalView;use ui::{ ActiveTheme, AnyElement, App, ButtonCommon as _, Clickable as _, Context, FluentBuilder, IconButton, IconName, IconSize, InteractiveElement, IntoElement, Label, LabelCommon as _, ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, Tab, Tooltip, VisibleOnHover, VisualContext, Window, div, h_flex, v_flex,};use util::ResultExt;use variable_list::VariableList;use workspace::{ ActivePaneDecorator, DraggedTab, Item, ItemHandle, Member, Pane, PaneGroup, SplitDirection, Workspace, item::TabContentParams, move_item, pane::Event,}; pub struct RunningState { session: Entity<Session>, thread_id: Option<ThreadId>, focus_handle: FocusHandle, _remote_id: Option<ViewId>, workspace: WeakEntity<Workspace>, session_id: SessionId, variable_list: Entity<variable_list::VariableList>, _subscriptions: Vec<Subscription>, stack_frame_list: Entity<stack_frame_list::StackFrameList>, loaded_sources_list: Entity<LoadedSourceList>, pub debug_terminal: Entity<DebugTerminal>, module_list: Entity<module_list::ModuleList>, console: Entity<Console>, breakpoint_list: Entity<BreakpointList>, panes: PaneGroup, active_pane: Entity<Pane>, pane_close_subscriptions: HashMap<EntityId, Subscription>, dock_axis: Axis, _schedule_serialize: Option<Task<()>>,} impl RunningState { pub(crate) fn thread_id(&self) -> Option<ThreadId> { self.thread_id } pub(crate) fn active_pane(&self) -> &Entity<Pane> { &self.active_pane }} impl Render for RunningState { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { let zoomed_pane = self .panes .panes() .into_iter() .find(|pane| pane.read(cx).is_zoomed()); let active = self.panes.panes().into_iter().next(); let pane = if let Some(ref zoomed_pane) = zoomed_pane { zoomed_pane.update(cx, |pane, cx| pane.render(window, cx).into_any_element()) } else 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)) .unwrap_or(ThreadStatus::Exited); 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(pane)) }} pub(crate) struct SubView { inner: AnyView, item_focus_handle: FocusHandle, kind: DebuggerPaneItem, show_indicator: Box<dyn Fn(&App) -> bool>, hovered: bool,} impl SubView { pub(crate) fn new( item_focus_handle: FocusHandle, view: AnyView, kind: DebuggerPaneItem, show_indicator: Option<Box<dyn Fn(&App) -> bool>>, cx: &mut App, ) -> Entity<Self> { cx.new(|_| Self { kind, inner: view, item_focus_handle, show_indicator: show_indicator.unwrap_or(Box::new(|_| false)), hovered: false, }) } pub(crate) fn view_kind(&self) -> DebuggerPaneItem { self.kind }}impl Focusable for SubView { fn focus_handle(&self, _: &App) -> FocusHandle { self.item_focus_handle.clone() }}impl EventEmitter<()> for SubView {}impl Item for SubView { type Event = (); /// This is used to serialize debugger pane layouts /// A SharedString gets converted to a enum and back during serialization/deserialization. fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { self.kind.to_shared_string() } fn tab_tooltip_text(&self, _: &App) -> Option<SharedString> { Some(self.kind.tab_tooltip()) } fn tab_content( &self, params: workspace::item::TabContentParams, _: &Window, cx: &App, ) -> AnyElement { let label = Label::new(self.kind.to_shared_string()) .size(ui::LabelSize::Small) .color(params.text_color()) .line_height_style(ui::LineHeightStyle::UiLabel); if !params.selected && self.show_indicator.as_ref()(cx) { return h_flex() .justify_between() .child(ui::Indicator::dot()) .gap_2() .child(label) .into_any_element(); } label.into_any_element() }} impl Render for SubView { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { v_flex() .id(SharedString::from(format!( "subview-container-{}", self.kind.to_shared_string() ))) .on_hover(cx.listener(|this, hovered, _, cx| { this.hovered = *hovered; cx.notify(); })) .size_full() // Add border unconditionally to prevent layout shifts on focus changes. .border_1() .when(self.item_focus_handle.contains_focused(window, cx), |el| { el.border_color(cx.theme().colors().pane_focused_border) }) .child(self.inner.clone()) }} pub(crate) 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_in(&new_pane, window, 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(), true, 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, ); let focus_handle = pane.focus_handle(cx); pane.set_can_split(Some(Arc::new({ let weak_running = weak_running.clone(); 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 .read_with(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.set_can_toggle_zoom(false, cx); pane.display_nav_history_buttons(None); pane.set_custom_drop_handle(cx, custom_drop_handle); pane.set_should_display_tab_bar(|_, _| true); pane.set_render_tab_bar_buttons(cx, |_, _, _| (None, None)); pane.set_render_tab_bar(cx, { move |pane, window, cx| { let active_pane_item = pane.active_item(); let pane_group_id: SharedString = format!("pane-zoom-button-hover-{}", cx.entity_id()).into(); let is_hovered = active_pane_item.as_ref().map_or(false, |item| { item.downcast::<SubView>() .map_or(false, |this| this.read(cx).hovered) }); h_flex() .group(pane_group_id.clone()) .justify_between() .bg(cx.theme().colors().tab_bar_background) .border_b_1() .px_2() .border_color(cx.theme().colors().border) .track_focus(&focus_handle) .on_action(|_: &menu::Cancel, window, cx| { if cx.stop_active_drag(window) { return; } else { cx.propagate(); } }) .child( h_flex() .w_full() .gap_1() .h(Tab::container_height(cx)) .drag_over::<DraggedTab>(|bar, _, _, cx| { bar.bg(cx.theme().colors().drop_target_background) }) .on_drop(cx.listener( move |this, dragged_tab: &DraggedTab, window, cx| { this.drag_split_direction = None; this.handle_tab_drop(dragged_tab, this.items_len(), window, cx) }, )) .children(pane.items().enumerate().map(|(ix, item)| { let selected = active_pane_item .as_ref() .map_or(false, |active| active.item_id() == item.item_id()); let deemphasized = !pane.has_focus(window, cx); let item_ = item.boxed_clone(); div() .id(SharedString::from(format!( "debugger_tab_{}", item.item_id().as_u64() ))) .p_1() .rounded_md() .cursor_pointer() .when_some(item.tab_tooltip_text(cx), |this, tooltip| { this.tooltip(Tooltip::text(tooltip)) }) .map(|this| { let theme = cx.theme(); if selected { let color = theme.colors().tab_active_background; let color = if deemphasized { color.opacity(0.5) } else { color }; this.bg(color) } else { let hover_color = theme.colors().element_hover; this.hover(|style| style.bg(hover_color)) } }) .on_click(cx.listener(move |this, _, window, cx| { let index = this.index_for_item(&*item_); if let Some(index) = index { this.activate_item(index, true, true, window, cx); } })) .child(item.tab_content( TabContentParams { selected, deemphasized, ..Default::default() }, window, cx, )) .on_drop(cx.listener( move |this, dragged_tab: &DraggedTab, window, cx| { this.drag_split_direction = None; this.handle_tab_drop(dragged_tab, ix, window, cx) }, )) .on_drag( DraggedTab { item: item.boxed_clone(), pane: cx.entity().clone(), detail: 0, is_active: selected, ix, }, |tab, _, _, cx| cx.new(|_| tab.clone()), ) })), ) .child({ let zoomed = pane.is_zoomed(); div() .visible_on_hover(pane_group_id) .when(is_hovered, |this| this.visible()) .child( IconButton::new( SharedString::from(format!( "debug-toggle-zoom-{}", cx.entity_id() )), if zoomed { IconName::Minimize } else { IconName::Maximize }, ) .icon_size(IconSize::XSmall) .on_click(cx.listener(move |pane, _, _, cx| { let is_zoomed = pane.is_zoomed(); pane.set_zoomed(!is_zoomed, cx); cx.notify(); })) .tooltip({ let focus_handle = focus_handle.clone(); move |window, cx| { let zoomed_text = if zoomed { "Minimize" } else { "Expand" }; Tooltip::for_action_in( zoomed_text, &ToggleExpandItem, &focus_handle, window, cx, ) } }), ) }) .into_any_element() }Lines 1–500 of 1668. Load more
pub(crate) mod breakpoint_list; pub(crate) mod console; pub(crate) mod loaded_source_list; pub(crate) mod module_list; pub mod stack_frame_list; pub mod variable_list; use std::{any::Any, ops::ControlFlow, path::PathBuf, sync::Arc, time::Duration}; use crate::{ ToggleExpandItem, new_process_modal::resolve_path, persistence::{self, DebuggerPaneItem, SerializedLayout}, }; use super::DebugPanelItemEvent; use anyhow::{Context as _, Result, anyhow}; use breakpoint_list::BreakpointList; use collections::{HashMap, IndexMap}; use console::Console; use dap::{ Capabilities, DapRegistry, RunInTerminalRequestArguments, Thread, adapters::{DebugAdapterName, DebugTaskDefinition}, client::SessionId, debugger_settings::DebuggerSettings, }; use futures::{SinkExt, channel::mpsc}; use gpui::{ Action as _, AnyView, AppContext, Axis, Entity, EntityId, EventEmitter, FocusHandle, Focusable, NoAction, Pixels, Point, Subscription, Task, WeakEntity, }; use language::Buffer; use loaded_source_list::LoadedSourceList; use module_list::ModuleList; use project::{ Project, WorktreeId, debugger::session::{Session, SessionEvent, ThreadId, ThreadStatus}, terminals::TerminalKind, }; use rpc::proto::ViewId; use serde_json::Value; use settings::Settings; use stack_frame_list::StackFrameList; use task::{ BuildTaskDefinition, DebugScenario, ShellBuilder, SpawnInTerminal, TaskContext, ZedDebugConfig, substitute_variables_in_str, }; use terminal_view::TerminalView; use ui::{ ActiveTheme, AnyElement, App, ButtonCommon as _, Clickable as _, Context, FluentBuilder, IconButton, IconName, IconSize, InteractiveElement, IntoElement, Label, LabelCommon as _, ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, Tab, Tooltip, VisibleOnHover, VisualContext, Window, div, h_flex, v_flex, }; use util::ResultExt; use variable_list::VariableList; use workspace::{ ActivePaneDecorator, DraggedTab, Item, ItemHandle, Member, Pane, PaneGroup, SplitDirection, Workspace, item::TabContentParams, move_item, pane::Event, }; pub struct RunningState { session: Entity<Session>, thread_id: Option<ThreadId>, focus_handle: FocusHandle, _remote_id: Option<ViewId>, workspace: WeakEntity<Workspace>, session_id: SessionId, variable_list: Entity<variable_list::VariableList>, _subscriptions: Vec<Subscription>, stack_frame_list: Entity<stack_frame_list::StackFrameList>, loaded_sources_list: Entity<LoadedSourceList>, pub debug_terminal: Entity<DebugTerminal>, module_list: Entity<module_list::ModuleList>, console: Entity<Console>, breakpoint_list: Entity<BreakpointList>, panes: PaneGroup, active_pane: Entity<Pane>, pane_close_subscriptions: HashMap<EntityId, Subscription>, dock_axis: Axis, _schedule_serialize: Option<Task<()>>, } impl RunningState { pub(crate) fn thread_id(&self) -> Option<ThreadId> { self.thread_id } pub(crate) fn active_pane(&self) -> &Entity<Pane> { &self.active_pane } } impl Render for RunningState { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { let zoomed_pane = self .panes .panes() .into_iter() .find(|pane| pane.read(cx).is_zoomed()); let active = self.panes.panes().into_iter().next(); let pane = if let Some(ref zoomed_pane) = zoomed_pane { zoomed_pane.update(cx, |pane, cx| pane.render(window, cx).into_any_element()) } else 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)) .unwrap_or(ThreadStatus::Exited); 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(pane)) } } pub(crate) struct SubView { inner: AnyView, item_focus_handle: FocusHandle, kind: DebuggerPaneItem, show_indicator: Box<dyn Fn(&App) -> bool>, hovered: bool, } impl SubView { pub(crate) fn new( item_focus_handle: FocusHandle, view: AnyView, kind: DebuggerPaneItem, show_indicator: Option<Box<dyn Fn(&App) -> bool>>, cx: &mut App, ) -> Entity<Self> { cx.new(|_| Self { kind, inner: view, item_focus_handle, show_indicator: show_indicator.unwrap_or(Box::new(|_| false)), hovered: false, }) } pub(crate) fn view_kind(&self) -> DebuggerPaneItem { self.kind } } impl Focusable for SubView { fn focus_handle(&self, _: &App) -> FocusHandle { self.item_focus_handle.clone() } } impl EventEmitter<()> for SubView {} impl Item for SubView { type Event = (); /// This is used to serialize debugger pane layouts /// A SharedString gets converted to a enum and back during serialization/deserialization. fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString { self.kind.to_shared_string() } fn tab_tooltip_text(&self, _: &App) -> Option<SharedString> { Some(self.kind.tab_tooltip()) } fn tab_content( &self, params: workspace::item::TabContentParams, _: &Window, cx: &App, ) -> AnyElement { let label = Label::new(self.kind.to_shared_string()) .size(ui::LabelSize::Small) .color(params.text_color()) .line_height_style(ui::LineHeightStyle::UiLabel); if !params.selected && self.show_indicator.as_ref()(cx) { return h_flex() .justify_between() .child(ui::Indicator::dot()) .gap_2() .child(label) .into_any_element(); } label.into_any_element() } } impl Render for SubView { fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { v_flex() .id(SharedString::from(format!( "subview-container-{}", self.kind.to_shared_string() ))) .on_hover(cx.listener(|this, hovered, _, cx| { this.hovered = *hovered; cx.notify(); })) .size_full() // Add border unconditionally to prevent layout shifts on focus changes. .border_1() .when(self.item_focus_handle.contains_focused(window, cx), |el| { el.border_color(cx.theme().colors().pane_focused_border) }) .child(self.inner.clone()) } } pub(crate) 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_in(&new_pane, window, 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(), true, 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, ); let focus_handle = pane.focus_handle(cx); pane.set_can_split(Some(Arc::new({ let weak_running = weak_running.clone(); 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 .read_with(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.set_can_toggle_zoom(false, cx); pane.display_nav_history_buttons(None); pane.set_custom_drop_handle(cx, custom_drop_handle); pane.set_should_display_tab_bar(|_, _| true); pane.set_render_tab_bar_buttons(cx, |_, _, _| (None, None)); pane.set_render_tab_bar(cx, { move |pane, window, cx| { let active_pane_item = pane.active_item(); let pane_group_id: SharedString = format!("pane-zoom-button-hover-{}", cx.entity_id()).into(); let is_hovered = active_pane_item.as_ref().map_or(false, |item| { item.downcast::<SubView>() .map_or(false, |this| this.read(cx).hovered) }); h_flex() .group(pane_group_id.clone()) .justify_between() .bg(cx.theme().colors().tab_bar_background) .border_b_1() .px_2() .border_color(cx.theme().colors().border) .track_focus(&focus_handle) .on_action(|_: &menu::Cancel, window, cx| { if cx.stop_active_drag(window) { return; } else { cx.propagate(); } }) .child( h_flex() .w_full() .gap_1() .h(Tab::container_height(cx)) .drag_over::<DraggedTab>(|bar, _, _, cx| { bar.bg(cx.theme().colors().drop_target_background) }) .on_drop(cx.listener( move |this, dragged_tab: &DraggedTab, window, cx| { this.drag_split_direction = None; this.handle_tab_drop(dragged_tab, this.items_len(), window, cx) }, )) .children(pane.items().enumerate().map(|(ix, item)| { let selected = active_pane_item .as_ref() .map_or(false, |active| active.item_id() == item.item_id()); let deemphasized = !pane.has_focus(window, cx); let item_ = item.boxed_clone(); div() .id(SharedString::from(format!( "debugger_tab_{}", item.item_id().as_u64() ))) .p_1() .rounded_md() .cursor_pointer() .when_some(item.tab_tooltip_text(cx), |this, tooltip| { this.tooltip(Tooltip::text(tooltip)) }) .map(|this| { let theme = cx.theme(); if selected { let color = theme.colors().tab_active_background; let color = if deemphasized { color.opacity(0.5) } else { color }; this.bg(color) } else { let hover_color = theme.colors().element_hover; this.hover(|style| style.bg(hover_color)) } }) .on_click(cx.listener(move |this, _, window, cx| { let index = this.index_for_item(&*item_); if let Some(index) = index { this.activate_item(index, true, true, window, cx); } })) .child(item.tab_content( TabContentParams { selected, deemphasized, ..Default::default() }, window, cx, )) .on_drop(cx.listener( move |this, dragged_tab: &DraggedTab, window, cx| { this.drag_split_direction = None; this.handle_tab_drop(dragged_tab, ix, window, cx) }, )) .on_drag( DraggedTab { item: item.boxed_clone(), pane: cx.entity().clone(), detail: 0, is_active: selected, ix, }, |tab, _, _, cx| cx.new(|_| tab.clone()), ) })), ) .child({ let zoomed = pane.is_zoomed(); div() .visible_on_hover(pane_group_id) .when(is_hovered, |this| this.visible()) .child( IconButton::new( SharedString::from(format!( "debug-toggle-zoom-{}", cx.entity_id() )), if zoomed { IconName::Minimize } else { IconName::Maximize }, ) .icon_size(IconSize::XSmall) .on_click(cx.listener(move |pane, _, _, cx| { let is_zoomed = pane.is_zoomed(); pane.set_zoomed(!is_zoomed, cx); cx.notify(); })) .tooltip({ let focus_handle = focus_handle.clone(); move |window, cx| { let zoomed_text = if zoomed { "Minimize" } else { "Expand" }; Tooltip::for_action_in( zoomed_text, &ToggleExpandItem, &focus_handle, window, cx, ) } }), ) }) .into_any_element() }