use super::stack_frame_list::{StackFrameList, StackFrameListEvent};use dap::{ScopePresentationHint, StackFrameId, VariablePresentationHintKind, VariableReference};use editor::Editor;use gpui::{ Action, AnyElement, ClickEvent, ClipboardItem, Context, DismissEvent, Entity, FocusHandle, Focusable, Hsla, MouseButton, MouseDownEvent, Point, Stateful, Subscription, TextStyleRefinement, UniformListScrollHandle, actions, anchored, deferred, uniform_list,};use menu::{SelectFirst, SelectLast, SelectNext, SelectPrevious};use project::debugger::session::{Session, SessionEvent};use std::{collections::HashMap, ops::Range, sync::Arc};use ui::{ContextMenu, ListItem, Scrollbar, ScrollbarState, prelude::*};use util::debug_panic; actions!( variable_list, [ ExpandSelectedEntry, CollapseSelectedEntry, CopyVariableName, CopyVariableValue, EditVariable ]); #[derive(Debug, Copy, Clone, PartialEq, Eq)]pub(crate) struct EntryState { depth: usize, is_expanded: bool, has_children: bool, parent_reference: VariableReference,} #[derive(Debug, PartialEq, Eq, Hash, Clone)]pub(crate) struct EntryPath { pub leaf_name: Option<SharedString>, pub indices: Arc<[SharedString]>,} impl EntryPath { fn for_scope(scope_name: impl Into<SharedString>) -> Self { Self { leaf_name: Some(scope_name.into()), indices: Arc::new([]), } } fn with_name(&self, name: SharedString) -> Self { Self { leaf_name: Some(name), indices: self.indices.clone(), } } /// Create a new child of this variable path fn with_child(&self, name: SharedString) -> Self { Self { leaf_name: None, indices: self .indices .iter() .cloned() .chain(std::iter::once(name)) .collect(), } }} #[derive(Debug, Clone, PartialEq)]enum EntryKind { Variable(dap::Variable), Scope(dap::Scope),} impl EntryKind { fn as_variable(&self) -> Option<&dap::Variable> { match self { EntryKind::Variable(dap) => Some(dap), _ => None, } } fn as_scope(&self) -> Option<&dap::Scope> { match self { EntryKind::Scope(dap) => Some(dap), _ => None, } } #[allow(dead_code)] fn name(&self) -> &str { match self { EntryKind::Variable(dap) => &dap.name, EntryKind::Scope(dap) => &dap.name, } }} #[derive(Debug, Clone, PartialEq)]struct ListEntry { dap_kind: EntryKind, path: EntryPath,} impl ListEntry { fn as_variable(&self) -> Option<&dap::Variable> { self.dap_kind.as_variable() } fn as_scope(&self) -> Option<&dap::Scope> { self.dap_kind.as_scope() } fn item_id(&self) -> ElementId { use std::fmt::Write; let mut id = match &self.dap_kind { EntryKind::Variable(dap) => format!("variable-{}", dap.name), EntryKind::Scope(dap) => format!("scope-{}", dap.name), }; for name in self.path.indices.iter() { _ = write!(id, "-{}", name); } SharedString::from(id).into() } fn item_value_id(&self) -> ElementId { use std::fmt::Write; let mut id = match &self.dap_kind { EntryKind::Variable(dap) => format!("variable-{}", dap.name), EntryKind::Scope(dap) => format!("scope-{}", dap.name), }; for name in self.path.indices.iter() { _ = write!(id, "-{}", name); } _ = write!(id, "-value"); SharedString::from(id).into() }} pub struct VariableList { entries: Vec<ListEntry>, entry_states: HashMap<EntryPath, EntryState>, selected_stack_frame_id: Option<StackFrameId>, list_handle: UniformListScrollHandle, scrollbar_state: ScrollbarState, session: Entity<Session>, selection: Option<EntryPath>, open_context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>, focus_handle: FocusHandle, edited_path: Option<(EntryPath, Entity<Editor>)>, disabled: bool, _subscriptions: Vec<Subscription>,} impl VariableList { pub fn new( session: Entity<Session>, stack_frame_list: Entity<StackFrameList>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let focus_handle = cx.focus_handle(); let _subscriptions = vec![ cx.subscribe(&stack_frame_list, Self::handle_stack_frame_list_events), cx.subscribe(&session, |this, _, event, cx| match event { SessionEvent::Stopped(_) => { this.selection.take(); this.edited_path.take(); this.selected_stack_frame_id.take(); } SessionEvent::Variables => { this.build_entries(cx); } _ => {} }), cx.on_focus_out(&focus_handle, window, |this, _, _, cx| { this.edited_path.take(); cx.notify(); }), ]; let list_state = UniformListScrollHandle::default(); Self { scrollbar_state: ScrollbarState::new(list_state.clone()), list_handle: list_state, session, focus_handle, _subscriptions, selected_stack_frame_id: None, selection: None, open_context_menu: None, disabled: false, edited_path: None, entries: Default::default(), entry_states: Default::default(), } } pub(super) fn disabled(&mut self, disabled: bool, cx: &mut Context<Self>) { let old_disabled = std::mem::take(&mut self.disabled); self.disabled = disabled; if old_disabled != disabled { cx.notify(); } } pub(super) fn has_open_context_menu(&self) -> bool { self.open_context_menu.is_some() } fn build_entries(&mut self, cx: &mut Context<Self>) { let Some(stack_frame_id) = self.selected_stack_frame_id else { return; }; let mut entries = vec![]; let scopes: Vec<_> = self.session.update(cx, |session, cx| { session.scopes(stack_frame_id, cx).iter().cloned().collect() }); let mut contains_local_scope = false; let mut stack = scopes .into_iter() .rev() .filter(|scope| { if scope .presentation_hint .as_ref() .map(|hint| *hint == ScopePresentationHint::Locals) .unwrap_or(scope.name.to_lowercase().starts_with("local")) { contains_local_scope = true; } self.session.update(cx, |session, cx| { session.variables(scope.variables_reference, cx).len() > 0 }) }) .map(|scope| { ( scope.variables_reference, scope.variables_reference, EntryPath::for_scope(&scope.name), EntryKind::Scope(scope), ) }) .collect::<Vec<_>>(); let scopes_count = stack.len(); while let Some((container_reference, variables_reference, mut path, dap_kind)) = stack.pop() { match &dap_kind { EntryKind::Variable(dap) => path = path.with_name(dap.name.clone().into()), EntryKind::Scope(dap) => path = path.with_child(dap.name.clone().into()), } let var_state = self .entry_states .entry(path.clone()) .and_modify(|state| { state.parent_reference = container_reference; state.has_children = variables_reference != 0; }) .or_insert(EntryState { depth: path.indices.len(), is_expanded: dap_kind.as_scope().is_some_and(|scope| { (scopes_count == 1 && !contains_local_scope) || scope .presentation_hint .as_ref() .map(|hint| *hint == ScopePresentationHint::Locals) .unwrap_or(scope.name.to_lowercase().starts_with("local")) }), parent_reference: container_reference, has_children: variables_reference != 0, }); entries.push(ListEntry { dap_kind, path: path.clone(), }); if var_state.is_expanded { let children = self .session .update(cx, |session, cx| session.variables(variables_reference, cx)); stack.extend(children.into_iter().rev().map(|child| { ( variables_reference, child.variables_reference, path.with_child(child.name.clone().into()), EntryKind::Variable(child), ) })); } } self.entries = entries; cx.notify(); } fn handle_stack_frame_list_events( &mut self, _: Entity<StackFrameList>, event: &StackFrameListEvent, cx: &mut Context<Self>, ) { match event { StackFrameListEvent::SelectedStackFrameChanged(stack_frame_id) => { self.selected_stack_frame_id = Some(*stack_frame_id); self.build_entries(cx); } StackFrameListEvent::BuiltEntries => {} } } pub fn completion_variables(&self, _cx: &mut Context<Self>) -> Vec<dap::Variable> { self.entries .iter() .filter_map(|entry| match &entry.dap_kind { EntryKind::Variable(dap) => Some(dap.clone()), EntryKind::Scope(_) => None, }) .collect() } fn render_entries( &mut self, ix: Range<usize>, window: &mut Window, cx: &mut Context<Self>, ) -> Vec<AnyElement> { ix.into_iter() .filter_map(|ix| { let (entry, state) = self .entries .get(ix) .and_then(|entry| Some(entry).zip(self.entry_states.get(&entry.path)))?; match &entry.dap_kind { EntryKind::Variable(_) => Some(self.render_variable(entry, *state, window, cx)), EntryKind::Scope(_) => Some(self.render_scope(entry, *state, cx)), } }) .collect() } pub(crate) fn toggle_entry(&mut self, var_path: &EntryPath, cx: &mut Context<Self>) { let Some(entry) = self.entry_states.get_mut(var_path) else { log::error!("Could not find variable list entry state to toggle"); return; }; entry.is_expanded = !entry.is_expanded; self.build_entries(cx); } fn select_first(&mut self, _: &SelectFirst, window: &mut Window, cx: &mut Context<Self>) { self.cancel(&Default::default(), window, cx); if let Some(variable) = self.entries.first() { self.selection = Some(variable.path.clone()); self.build_entries(cx); } } fn select_last(&mut self, _: &SelectLast, window: &mut Window, cx: &mut Context<Self>) { self.cancel(&Default::default(), window, cx); if let Some(variable) = self.entries.last() { self.selection = Some(variable.path.clone()); self.build_entries(cx); } } fn select_prev(&mut self, _: &SelectPrevious, window: &mut Window, cx: &mut Context<Self>) { self.cancel(&Default::default(), window, cx); if let Some(selection) = &self.selection { let index = self.entries.iter().enumerate().find_map(|(ix, var)| { if &var.path == selection && ix > 0 { Some(ix.saturating_sub(1)) } else { None } }); if let Some(new_selection) = index.and_then(|ix| self.entries.get(ix).map(|var| var.path.clone())) { self.selection = Some(new_selection); self.build_entries(cx); } else { self.select_last(&SelectLast, window, cx); } } else { self.select_last(&SelectLast, window, cx); } } fn select_next(&mut self, _: &SelectNext, window: &mut Window, cx: &mut Context<Self>) { self.cancel(&Default::default(), window, cx); if let Some(selection) = &self.selection { let index = self.entries.iter().enumerate().find_map(|(ix, var)| { if &var.path == selection { Some(ix.saturating_add(1)) } else { None } }); if let Some(new_selection) = index.and_then(|ix| self.entries.get(ix).map(|var| var.path.clone())) { self.selection = Some(new_selection); self.build_entries(cx); } else { self.select_first(&SelectFirst, window, cx); } } else { self.select_first(&SelectFirst, window, cx); } } fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) { self.edited_path.take(); self.focus_handle.focus(window); cx.notify(); } fn confirm(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) { if let Some((var_path, editor)) = self.edited_path.take() { let Some(state) = self.entry_states.get(&var_path) else { return; }; let variables_reference = state.parent_reference; let Some(name) = var_path.leaf_name else { return; }; let value = editor.read(cx).text(cx); self.session.update(cx, |session, cx| { session.set_variable_value(variables_reference, name.into(), value, cx) }); } } fn collapse_selected_entry( &mut self, _: &CollapseSelectedEntry, window: &mut Window, cx: &mut Context<Self>, ) { if let Some(ref selected_entry) = self.selection { let Some(entry_state) = self.entry_states.get_mut(selected_entry) else { debug_panic!("Trying to toggle variable in variable list that has an no state"); return; }; if !entry_state.is_expanded || !entry_state.has_children { self.select_prev(&SelectPrevious, window, cx); } else { entry_state.is_expanded = false; self.build_entries(cx); } } } fn expand_selected_entry( &mut self, _: &ExpandSelectedEntry, window: &mut Window, cx: &mut Context<Self>, ) { if let Some(selected_entry) = &self.selection { let Some(entry_state) = self.entry_states.get_mut(selected_entry) else { debug_panic!("Trying to toggle variable in variable list that has an no state"); return; }; if entry_state.is_expanded || !entry_state.has_children { self.select_next(&SelectNext, window, cx); } else { entry_state.is_expanded = true; self.build_entries(cx); } } } fn deploy_variable_context_menu( &mut self, _variable: ListEntry, position: Point<Pixels>, window: &mut Window, cx: &mut Context<Self>, ) { let context_menu = ContextMenu::build(window, cx, |menu, _, _| { menu.action("Copy Name", CopyVariableName.boxed_clone()) .action("Copy Value", CopyVariableValue.boxed_clone())Lines 1–500 of 1020. Load more
use super::stack_frame_list::{StackFrameList, StackFrameListEvent}; use dap::{ScopePresentationHint, StackFrameId, VariablePresentationHintKind, VariableReference}; use editor::Editor; use gpui::{ Action, AnyElement, ClickEvent, ClipboardItem, Context, DismissEvent, Entity, FocusHandle, Focusable, Hsla, MouseButton, MouseDownEvent, Point, Stateful, Subscription, TextStyleRefinement, UniformListScrollHandle, actions, anchored, deferred, uniform_list, }; use menu::{SelectFirst, SelectLast, SelectNext, SelectPrevious}; use project::debugger::session::{Session, SessionEvent}; use std::{collections::HashMap, ops::Range, sync::Arc}; use ui::{ContextMenu, ListItem, Scrollbar, ScrollbarState, prelude::*}; use util::debug_panic; actions!( variable_list, [ ExpandSelectedEntry, CollapseSelectedEntry, CopyVariableName, CopyVariableValue, EditVariable ] ); #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub(crate) struct EntryState { depth: usize, is_expanded: bool, has_children: bool, parent_reference: VariableReference, } #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub(crate) struct EntryPath { pub leaf_name: Option<SharedString>, pub indices: Arc<[SharedString]>, } impl EntryPath { fn for_scope(scope_name: impl Into<SharedString>) -> Self { Self { leaf_name: Some(scope_name.into()), indices: Arc::new([]), } } fn with_name(&self, name: SharedString) -> Self { Self { leaf_name: Some(name), indices: self.indices.clone(), } } /// Create a new child of this variable path fn with_child(&self, name: SharedString) -> Self { Self { leaf_name: None, indices: self .indices .iter() .cloned() .chain(std::iter::once(name)) .collect(), } } } #[derive(Debug, Clone, PartialEq)] enum EntryKind { Variable(dap::Variable), Scope(dap::Scope), } impl EntryKind { fn as_variable(&self) -> Option<&dap::Variable> { match self { EntryKind::Variable(dap) => Some(dap), _ => None, } } fn as_scope(&self) -> Option<&dap::Scope> { match self { EntryKind::Scope(dap) => Some(dap), _ => None, } } #[allow(dead_code)] fn name(&self) -> &str { match self { EntryKind::Variable(dap) => &dap.name, EntryKind::Scope(dap) => &dap.name, } } } #[derive(Debug, Clone, PartialEq)] struct ListEntry { dap_kind: EntryKind, path: EntryPath, } impl ListEntry { fn as_variable(&self) -> Option<&dap::Variable> { self.dap_kind.as_variable() } fn as_scope(&self) -> Option<&dap::Scope> { self.dap_kind.as_scope() } fn item_id(&self) -> ElementId { use std::fmt::Write; let mut id = match &self.dap_kind { EntryKind::Variable(dap) => format!("variable-{}", dap.name), EntryKind::Scope(dap) => format!("scope-{}", dap.name), }; for name in self.path.indices.iter() { _ = write!(id, "-{}", name); } SharedString::from(id).into() } fn item_value_id(&self) -> ElementId { use std::fmt::Write; let mut id = match &self.dap_kind { EntryKind::Variable(dap) => format!("variable-{}", dap.name), EntryKind::Scope(dap) => format!("scope-{}", dap.name), }; for name in self.path.indices.iter() { _ = write!(id, "-{}", name); } _ = write!(id, "-value"); SharedString::from(id).into() } } pub struct VariableList { entries: Vec<ListEntry>, entry_states: HashMap<EntryPath, EntryState>, selected_stack_frame_id: Option<StackFrameId>, list_handle: UniformListScrollHandle, scrollbar_state: ScrollbarState, session: Entity<Session>, selection: Option<EntryPath>, open_context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>, focus_handle: FocusHandle, edited_path: Option<(EntryPath, Entity<Editor>)>, disabled: bool, _subscriptions: Vec<Subscription>, } impl VariableList { pub fn new( session: Entity<Session>, stack_frame_list: Entity<StackFrameList>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let focus_handle = cx.focus_handle(); let _subscriptions = vec![ cx.subscribe(&stack_frame_list, Self::handle_stack_frame_list_events), cx.subscribe(&session, |this, _, event, cx| match event { SessionEvent::Stopped(_) => { this.selection.take(); this.edited_path.take(); this.selected_stack_frame_id.take(); } SessionEvent::Variables => { this.build_entries(cx); } _ => {} }), cx.on_focus_out(&focus_handle, window, |this, _, _, cx| { this.edited_path.take(); cx.notify(); }), ]; let list_state = UniformListScrollHandle::default(); Self { scrollbar_state: ScrollbarState::new(list_state.clone()), list_handle: list_state, session, focus_handle, _subscriptions, selected_stack_frame_id: None, selection: None, open_context_menu: None, disabled: false, edited_path: None, entries: Default::default(), entry_states: Default::default(), } } pub(super) fn disabled(&mut self, disabled: bool, cx: &mut Context<Self>) { let old_disabled = std::mem::take(&mut self.disabled); self.disabled = disabled; if old_disabled != disabled { cx.notify(); } } pub(super) fn has_open_context_menu(&self) -> bool { self.open_context_menu.is_some() } fn build_entries(&mut self, cx: &mut Context<Self>) { let Some(stack_frame_id) = self.selected_stack_frame_id else { return; }; let mut entries = vec![]; let scopes: Vec<_> = self.session.update(cx, |session, cx| { session.scopes(stack_frame_id, cx).iter().cloned().collect() }); let mut contains_local_scope = false; let mut stack = scopes .into_iter() .rev() .filter(|scope| { if scope .presentation_hint .as_ref() .map(|hint| *hint == ScopePresentationHint::Locals) .unwrap_or(scope.name.to_lowercase().starts_with("local")) { contains_local_scope = true; } self.session.update(cx, |session, cx| { session.variables(scope.variables_reference, cx).len() > 0 }) }) .map(|scope| { ( scope.variables_reference, scope.variables_reference, EntryPath::for_scope(&scope.name), EntryKind::Scope(scope), ) }) .collect::<Vec<_>>(); let scopes_count = stack.len(); while let Some((container_reference, variables_reference, mut path, dap_kind)) = stack.pop() { match &dap_kind { EntryKind::Variable(dap) => path = path.with_name(dap.name.clone().into()), EntryKind::Scope(dap) => path = path.with_child(dap.name.clone().into()), } let var_state = self .entry_states .entry(path.clone()) .and_modify(|state| { state.parent_reference = container_reference; state.has_children = variables_reference != 0; }) .or_insert(EntryState { depth: path.indices.len(), is_expanded: dap_kind.as_scope().is_some_and(|scope| { (scopes_count == 1 && !contains_local_scope) || scope .presentation_hint .as_ref() .map(|hint| *hint == ScopePresentationHint::Locals) .unwrap_or(scope.name.to_lowercase().starts_with("local")) }), parent_reference: container_reference, has_children: variables_reference != 0, }); entries.push(ListEntry { dap_kind, path: path.clone(), }); if var_state.is_expanded { let children = self .session .update(cx, |session, cx| session.variables(variables_reference, cx)); stack.extend(children.into_iter().rev().map(|child| { ( variables_reference, child.variables_reference, path.with_child(child.name.clone().into()), EntryKind::Variable(child), ) })); } } self.entries = entries; cx.notify(); } fn handle_stack_frame_list_events( &mut self, _: Entity<StackFrameList>, event: &StackFrameListEvent, cx: &mut Context<Self>, ) { match event { StackFrameListEvent::SelectedStackFrameChanged(stack_frame_id) => { self.selected_stack_frame_id = Some(*stack_frame_id); self.build_entries(cx); } StackFrameListEvent::BuiltEntries => {} } } pub fn completion_variables(&self, _cx: &mut Context<Self>) -> Vec<dap::Variable> { self.entries .iter() .filter_map(|entry| match &entry.dap_kind { EntryKind::Variable(dap) => Some(dap.clone()), EntryKind::Scope(_) => None, }) .collect() } fn render_entries( &mut self, ix: Range<usize>, window: &mut Window, cx: &mut Context<Self>, ) -> Vec<AnyElement> { ix.into_iter() .filter_map(|ix| { let (entry, state) = self .entries .get(ix) .and_then(|entry| Some(entry).zip(self.entry_states.get(&entry.path)))?; match &entry.dap_kind { EntryKind::Variable(_) => Some(self.render_variable(entry, *state, window, cx)), EntryKind::Scope(_) => Some(self.render_scope(entry, *state, cx)), } }) .collect() } pub(crate) fn toggle_entry(&mut self, var_path: &EntryPath, cx: &mut Context<Self>) { let Some(entry) = self.entry_states.get_mut(var_path) else { log::error!("Could not find variable list entry state to toggle"); return; }; entry.is_expanded = !entry.is_expanded; self.build_entries(cx); } fn select_first(&mut self, _: &SelectFirst, window: &mut Window, cx: &mut Context<Self>) { self.cancel(&Default::default(), window, cx); if let Some(variable) = self.entries.first() { self.selection = Some(variable.path.clone()); self.build_entries(cx); } } fn select_last(&mut self, _: &SelectLast, window: &mut Window, cx: &mut Context<Self>) { self.cancel(&Default::default(), window, cx); if let Some(variable) = self.entries.last() { self.selection = Some(variable.path.clone()); self.build_entries(cx); } } fn select_prev(&mut self, _: &SelectPrevious, window: &mut Window, cx: &mut Context<Self>) { self.cancel(&Default::default(), window, cx); if let Some(selection) = &self.selection { let index = self.entries.iter().enumerate().find_map(|(ix, var)| { if &var.path == selection && ix > 0 { Some(ix.saturating_sub(1)) } else { None } }); if let Some(new_selection) = index.and_then(|ix| self.entries.get(ix).map(|var| var.path.clone())) { self.selection = Some(new_selection); self.build_entries(cx); } else { self.select_last(&SelectLast, window, cx); } } else { self.select_last(&SelectLast, window, cx); } } fn select_next(&mut self, _: &SelectNext, window: &mut Window, cx: &mut Context<Self>) { self.cancel(&Default::default(), window, cx); if let Some(selection) = &self.selection { let index = self.entries.iter().enumerate().find_map(|(ix, var)| { if &var.path == selection { Some(ix.saturating_add(1)) } else { None } }); if let Some(new_selection) = index.and_then(|ix| self.entries.get(ix).map(|var| var.path.clone())) { self.selection = Some(new_selection); self.build_entries(cx); } else { self.select_first(&SelectFirst, window, cx); } } else { self.select_first(&SelectFirst, window, cx); } } fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) { self.edited_path.take(); self.focus_handle.focus(window); cx.notify(); } fn confirm(&mut self, _: &menu::Confirm, _window: &mut Window, cx: &mut Context<Self>) { if let Some((var_path, editor)) = self.edited_path.take() { let Some(state) = self.entry_states.get(&var_path) else { return; }; let variables_reference = state.parent_reference; let Some(name) = var_path.leaf_name else { return; }; let value = editor.read(cx).text(cx); self.session.update(cx, |session, cx| { session.set_variable_value(variables_reference, name.into(), value, cx) }); } } fn collapse_selected_entry( &mut self, _: &CollapseSelectedEntry, window: &mut Window, cx: &mut Context<Self>, ) { if let Some(ref selected_entry) = self.selection { let Some(entry_state) = self.entry_states.get_mut(selected_entry) else { debug_panic!("Trying to toggle variable in variable list that has an no state"); return; }; if !entry_state.is_expanded || !entry_state.has_children { self.select_prev(&SelectPrevious, window, cx); } else { entry_state.is_expanded = false; self.build_entries(cx); } } } fn expand_selected_entry( &mut self, _: &ExpandSelectedEntry, window: &mut Window, cx: &mut Context<Self>, ) { if let Some(selected_entry) = &self.selection { let Some(entry_state) = self.entry_states.get_mut(selected_entry) else { debug_panic!("Trying to toggle variable in variable list that has an no state"); return; }; if entry_state.is_expanded || !entry_state.has_children { self.select_next(&SelectNext, window, cx); } else { entry_state.is_expanded = true; self.build_entries(cx); } } } fn deploy_variable_context_menu( &mut self, _variable: ListEntry, position: Point<Pixels>, window: &mut Window, cx: &mut Context<Self>, ) { let context_menu = ContextMenu::build(window, cx, |menu, _, _| { menu.action("Copy Name", CopyVariableName.boxed_clone()) .action("Copy Value", CopyVariableValue.boxed_clone())