1666 lines · 57.4 KB · Rust
RawDownload
use crate::session::running::{RunningState, memory_view::MemoryView}; use super::stack_frame_list::{StackFrameList, StackFrameListEvent};use dap::{    ScopePresentationHint, StackFrameId, VariablePresentationHint, VariablePresentationHintKind,    VariableReference,};use editor::Editor;use gpui::{    Action, AnyElement, ClickEvent, ClipboardItem, Context, DismissEvent, Empty, Entity,    FocusHandle, Focusable, Hsla, MouseDownEvent, Point, Subscription, TaskExt,    TextStyleRefinement, UniformListScrollHandle, WeakEntity, actions, anchored, deferred,    uniform_list,};use itertools::Itertools;use menu::{SelectFirst, SelectLast, SelectNext, SelectPrevious};use project::debugger::{    dap_command::DataBreakpointContext,    session::{Session, SessionEvent, Watcher},};use std::{collections::HashMap, ops::Range, sync::Arc};use ui::{ContextMenu, ListItem, ScrollAxes, ScrollableHandle, Tooltip, WithScrollbar, prelude::*};use util::{debug_panic, maybe}; static INDENT_STEP_SIZE: Pixels = px(10.0); actions!(    variable_list,    [        /// Expands the selected variable entry to show its children.        ExpandSelectedEntry,        /// Collapses the selected variable entry to hide its children.        CollapseSelectedEntry,        /// Copies the variable name to the clipboard.        CopyVariableName,        /// Copies the variable value to the clipboard.        CopyVariableValue,        /// Edits the value of the selected variable.        EditVariable,        /// Adds the selected variable to the watch list.        AddWatch,        /// Removes the selected variable from the watch list.        RemoveWatch,        /// Jump to variable's memory location.        GoToMemory,    ]); #[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_watcher(expression: impl Into<SharedString>) -> Self {        Self {            leaf_name: Some(expression.into()),            indices: Arc::new([]),        }    }     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 DapEntry {    Watcher(Watcher),    Variable(dap::Variable),    Scope(dap::Scope),} impl DapEntry {    fn as_watcher(&self) -> Option<&Watcher> {        match self {            DapEntry::Watcher(watcher) => Some(watcher),            _ => None,        }    }     fn as_variable(&self) -> Option<&dap::Variable> {        match self {            DapEntry::Variable(dap) => Some(dap),            _ => None,        }    }     fn as_scope(&self) -> Option<&dap::Scope> {        match self {            DapEntry::Scope(dap) => Some(dap),            _ => None,        }    }     #[cfg(test)]    fn name(&self) -> &str {        match self {            DapEntry::Watcher(watcher) => &watcher.expression,            DapEntry::Variable(dap) => &dap.name,            DapEntry::Scope(dap) => &dap.name,        }    }} #[derive(Debug, Clone, PartialEq)]struct ListEntry {    entry: DapEntry,    path: EntryPath,} impl ListEntry {    fn as_watcher(&self) -> Option<&Watcher> {        self.entry.as_watcher()    }     fn as_variable(&self) -> Option<&dap::Variable> {        self.entry.as_variable()    }     fn as_scope(&self) -> Option<&dap::Scope> {        self.entry.as_scope()    }     fn item_id(&self) -> ElementId {        use std::fmt::Write;        let mut id = match &self.entry {            DapEntry::Watcher(watcher) => format!("watcher-{}", watcher.expression),            DapEntry::Variable(dap) => format!("variable-{}", dap.name),            DapEntry::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.entry {            DapEntry::Watcher(watcher) => format!("watcher-{}", watcher.expression),            DapEntry::Variable(dap) => format!("variable-{}", dap.name),            DapEntry::Scope(dap) => format!("scope-{}", dap.name),        };        for name in self.path.indices.iter() {            _ = write!(id, "-{}", name);        }        _ = write!(id, "-value");        SharedString::from(id).into()    }} struct VariableColor {    name: Option<Hsla>,    value: Option<Hsla>,} pub struct VariableList {    entries: Vec<ListEntry>,    max_width_index: Option<usize>,    entry_states: HashMap<EntryPath, EntryState>,    selected_stack_frame_id: Option<StackFrameId>,    list_handle: UniformListScrollHandle,    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,    memory_view: Entity<MemoryView>,    weak_running: WeakEntity<RunningState>,    _subscriptions: Vec<Subscription>,} impl VariableList {    pub(crate) fn new(        session: Entity<Session>,        stack_frame_list: Entity<StackFrameList>,        memory_view: Entity<MemoryView>,        weak_running: WeakEntity<RunningState>,        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::HistoricSnapshotSelected => {                    this.selection.take();                    this.edited_path.take();                    this.selected_stack_frame_id.take();                    this.build_entries(cx);                }                SessionEvent::Stopped(_) => {                    this.selection.take();                    this.edited_path.take();                    this.selected_stack_frame_id.take();                }                SessionEvent::Variables | SessionEvent::Watchers => {                    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 {            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(),            max_width_index: None,            entry_states: Default::default(),            weak_running,            memory_view,        }    }     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).to_vec()        });         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;                }                 scope.expensive                    || self.session.update(cx, |session, cx| {                        !session.variables(scope.variables_reference, cx).is_empty()                    })            })            .map(|scope| {                (                    scope.variables_reference,                    scope.variables_reference,                    EntryPath::for_scope(&scope.name),                    DapEntry::Scope(scope),                )            })            .collect::<Vec<_>>();         let watches = self.session.read(cx).watchers().clone();        stack.extend(            watches                .into_values()                .map(|watcher| {                    (                        watcher.variables_reference,                        watcher.variables_reference,                        EntryPath::for_watcher(watcher.expression.clone()),                        DapEntry::Watcher(watcher),                    )                })                .collect::<Vec<_>>(),        );         let scopes_count = stack.len();         while let Some((container_reference, variables_reference, mut path, dap_kind)) = stack.pop()        {            match &dap_kind {                DapEntry::Watcher(watcher) => path = path.with_child(watcher.expression.clone()),                DapEntry::Variable(dap) => path = path.with_name(dap.name.clone().into()),                DapEntry::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| {                        !scope.expensive                            && ((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 {                entry: 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()),                        DapEntry::Variable(child),                    )                }));            }        }         self.entries = entries;         let text_pixels = ui::TextSize::Default.pixels(cx).to_f64() as f32;        let indent_size = INDENT_STEP_SIZE.to_f64() as f32;         self.max_width_index = self            .entries            .iter()            .map(|entry| match &entry.entry {                DapEntry::Scope(scope) => scope.name.len() as f32 * text_pixels,                DapEntry::Variable(variable) => {                    (variable.value.len() + variable.name.len()) as f32 * text_pixels                        + (entry.path.indices.len() as f32 * indent_size)                }                DapEntry::Watcher(watcher) => {                    (watcher.value.len() + watcher.expression.len()) as f32 * text_pixels                        + (entry.path.indices.len() as f32 * indent_size)                }            })            .position_max_by(|left, right| left.total_cmp(right));         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.session.update(cx, |session, cx| {                    session.refresh_watchers(*stack_frame_id, cx);                });                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.entry {                DapEntry::Variable(dap) => Some(dap.clone()),                DapEntry::Scope(_) | DapEntry::Watcher { .. } => 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.entry {                    DapEntry::Watcher { .. } => {                        Some(self.render_watcher(entry, *state, window, cx))                    }                    DapEntry::Variable(_) => Some(self.render_variable(entry, *state, window, cx)),                    DapEntry::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);

Lines 1–500 of 1666.