984 lines · 37.3 KB · Rust
RawDownload
use std::{    cell::LazyCell,    fmt::Write,    ops::RangeInclusive,    sync::{Arc, LazyLock},    time::Duration,}; use editor::{Editor, EditorElement, EditorStyle};use gpui::{    Action, AppContext, DismissEvent, DragMoveEvent, Empty, Entity, FocusHandle, Focusable,    MouseButton, Point, ScrollStrategy, ScrollWheelEvent, Stateful, Subscription, Task, TextStyle,    UniformList, UniformListScrollHandle, WeakEntity, actions, anchored, deferred, point,    uniform_list,};use notifications::status_toast::{StatusToast, ToastIcon};use project::debugger::{MemoryCell, dap_command::DataBreakpointContext, session::Session};use settings::Settings;use theme::ThemeSettings;use ui::{    ActiveTheme, AnyElement, App, Color, Context, ContextMenu, Div, Divider, DropdownMenu, Element,    FluentBuilder, Icon, IconName, InteractiveElement, IntoElement, Label, LabelCommon,    ParentElement, Pixels, PopoverMenuHandle, Render, Scrollbar, ScrollbarState, SharedString,    StatefulInteractiveElement, Styled, TextSize, Tooltip, Window, div, h_flex, px, v_flex,};use util::ResultExt;use workspace::Workspace; use crate::{ToggleDataBreakpoint, session::running::stack_frame_list::StackFrameList}; actions!(debugger, [GoToSelectedAddress]); pub(crate) struct MemoryView {    workspace: WeakEntity<Workspace>,    scroll_handle: UniformListScrollHandle,    scroll_state: ScrollbarState,    show_scrollbar: bool,    stack_frame_list: WeakEntity<StackFrameList>,    hide_scrollbar_task: Option<Task<()>>,    focus_handle: FocusHandle,    view_state: ViewState,    query_editor: Entity<Editor>,    session: Entity<Session>,    width_picker_handle: PopoverMenuHandle<ContextMenu>,    is_writing_memory: bool,    open_context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,} impl Focusable for MemoryView {    fn focus_handle(&self, _: &ui::App) -> FocusHandle {        self.focus_handle.clone()    }}#[derive(Clone, Debug)]struct Drag {    start_address: u64,    end_address: u64,} impl Drag {    fn contains(&self, address: u64) -> bool {        let range = self.memory_range();        range.contains(&address)    }     fn memory_range(&self) -> RangeInclusive<u64> {        if self.start_address < self.end_address {            self.start_address..=self.end_address        } else {            self.end_address..=self.start_address        }    }}#[derive(Clone, Debug)]enum SelectedMemoryRange {    DragUnderway(Drag),    DragComplete(Drag),} impl SelectedMemoryRange {    fn contains(&self, address: u64) -> bool {        match self {            SelectedMemoryRange::DragUnderway(drag) => drag.contains(address),            SelectedMemoryRange::DragComplete(drag) => drag.contains(address),        }    }    fn is_dragging(&self) -> bool {        matches!(self, SelectedMemoryRange::DragUnderway(_))    }    fn drag(&self) -> &Drag {        match self {            SelectedMemoryRange::DragUnderway(drag) => drag,            SelectedMemoryRange::DragComplete(drag) => drag,        }    }} #[derive(Clone)]struct ViewState {    /// Uppermost row index    base_row: u64,    /// How many cells per row do we have?    line_width: ViewWidth,    selection: Option<SelectedMemoryRange>,} impl ViewState {    fn new(base_row: u64, line_width: ViewWidth) -> Self {        Self {            base_row,            line_width,            selection: None,        }    }    fn row_count(&self) -> u64 {        // This was picked fully arbitrarily. There's no incentive for us to care about page sizes other than the fact that it seems to be a good        // middle ground for data size.        const PAGE_SIZE: u64 = 4096;        PAGE_SIZE / self.line_width.width as u64    }    fn schedule_scroll_down(&mut self) {        self.base_row = self.base_row.saturating_add(1)    }    fn schedule_scroll_up(&mut self) {        self.base_row = self.base_row.saturating_sub(1);    }} struct ScrollbarDragging; static HEX_BYTES_MEMOIZED: LazyLock<[SharedString; 256]> =    LazyLock::new(|| std::array::from_fn(|byte| SharedString::from(format!("{byte:02X}"))));static UNKNOWN_BYTE: SharedString = SharedString::new_static("??");impl MemoryView {    pub(crate) fn new(        session: Entity<Session>,        workspace: WeakEntity<Workspace>,        stack_frame_list: WeakEntity<StackFrameList>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Self {        let view_state = ViewState::new(0, WIDTHS[4].clone());        let scroll_handle = UniformListScrollHandle::default();         let query_editor = cx.new(|cx| Editor::single_line(window, cx));         let scroll_state = ScrollbarState::new(scroll_handle.clone());        let mut this = Self {            workspace,            scroll_state,            scroll_handle,            stack_frame_list,            show_scrollbar: false,            hide_scrollbar_task: None,            focus_handle: cx.focus_handle(),            view_state,            query_editor,            session,            width_picker_handle: Default::default(),            is_writing_memory: true,            open_context_menu: None,        };        this.change_query_bar_mode(false, window, cx);        cx.on_focus_out(&this.focus_handle, window, |this, _, window, cx| {            this.change_query_bar_mode(false, window, cx);            cx.notify();        })        .detach();        this    }    fn hide_scrollbar(&mut self, window: &mut Window, cx: &mut Context<Self>) {        const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);        self.hide_scrollbar_task = Some(cx.spawn_in(window, async move |panel, cx| {            cx.background_executor()                .timer(SCROLLBAR_SHOW_INTERVAL)                .await;            panel                .update(cx, |panel, cx| {                    panel.show_scrollbar = false;                    cx.notify();                })                .log_err();        }))    }     fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Option<Stateful<Div>> {        if !(self.show_scrollbar || self.scroll_state.is_dragging()) {            return None;        }        Some(            div()                .occlude()                .id("memory-view-vertical-scrollbar")                .on_drag_move(cx.listener(|this, evt, _, cx| {                    let did_handle = this.handle_scroll_drag(evt);                    cx.notify();                    if did_handle {                        cx.stop_propagation()                    }                }))                .on_drag(ScrollbarDragging, |_, _, _, cx| cx.new(|_| Empty))                .on_hover(|_, _, cx| {                    cx.stop_propagation();                })                .on_any_mouse_down(|_, _, cx| {                    cx.stop_propagation();                })                .on_mouse_up(                    MouseButton::Left,                    cx.listener(|_, _, _, cx| {                        cx.stop_propagation();                    }),                )                .on_scroll_wheel(cx.listener(|_, _, _, cx| {                    cx.notify();                }))                .h_full()                .absolute()                .right_1()                .top_1()                .bottom_0()                .w(px(12.))                .cursor_default()                .children(Scrollbar::vertical(self.scroll_state.clone())),        )    }     fn render_memory(&self, cx: &mut Context<Self>) -> UniformList {        let weak = cx.weak_entity();        let session = self.session.clone();        let view_state = self.view_state.clone();        uniform_list(            "debugger-memory-view",            self.view_state.row_count() as usize,            move |range, _, cx| {                let mut line_buffer = Vec::with_capacity(view_state.line_width.width as usize);                let memory_start =                    (view_state.base_row + range.start as u64) * view_state.line_width.width as u64;                let memory_end = (view_state.base_row + range.end as u64)                    * view_state.line_width.width as u64                    - 1;                let mut memory = session.update(cx, |this, cx| {                    this.read_memory(memory_start..=memory_end, cx)                });                let mut rows = Vec::with_capacity(range.end - range.start);                for ix in range {                    line_buffer.extend((&mut memory).take(view_state.line_width.width as usize));                    rows.push(render_single_memory_view_line(                        &line_buffer,                        ix as u64,                        weak.clone(),                        cx,                    ));                    line_buffer.clear();                }                rows            },        )        .track_scroll(self.scroll_handle.clone())        .on_scroll_wheel(cx.listener(|this, evt: &ScrollWheelEvent, window, _| {            let delta = evt.delta.pixel_delta(window.line_height());            let scroll_handle = this.scroll_state.scroll_handle();            let size = scroll_handle.content_size();            let viewport = scroll_handle.viewport();            let current_offset = scroll_handle.offset();            let first_entry_offset_boundary = size.height / this.view_state.row_count() as f32;            let last_entry_offset_boundary = size.height - first_entry_offset_boundary;            if first_entry_offset_boundary + viewport.size.height > current_offset.y.abs() {                // The topmost entry is visible, hence if we're scrolling up, we need to load extra lines.                this.view_state.schedule_scroll_up();            } else if last_entry_offset_boundary < current_offset.y.abs() + viewport.size.height {                this.view_state.schedule_scroll_down();            }            scroll_handle.set_offset(current_offset + point(px(0.), delta.y));        }))    }    fn render_query_bar(&self, cx: &Context<Self>) -> impl IntoElement {        EditorElement::new(            &self.query_editor,            Self::editor_style(&self.query_editor, cx),        )    }    pub(super) fn go_to_memory_reference(        &mut self,        memory_reference: &str,        evaluate_name: Option<&str>,        stack_frame_id: Option<u64>,        cx: &mut Context<Self>,    ) {        use parse_int::parse;        let Ok(as_address) = parse::<u64>(&memory_reference) else {            return;        };        let access_size = evaluate_name            .map(|typ| {                self.session.update(cx, |this, cx| {                    this.data_access_size(stack_frame_id, typ, cx)                })            })            .unwrap_or_else(|| Task::ready(None));        cx.spawn(async move |this, cx| {            let access_size = access_size.await.unwrap_or(1);            this.update(cx, |this, cx| {                this.view_state.selection = Some(SelectedMemoryRange::DragComplete(Drag {                    start_address: as_address,                    end_address: as_address + access_size - 1,                }));                this.jump_to_address(as_address, cx);            })            .ok();        })        .detach();    }     fn handle_memory_drag(&mut self, evt: &DragMoveEvent<Drag>) {        if !self            .view_state            .selection            .as_ref()            .is_some_and(|selection| selection.is_dragging())        {            return;        }        let row_count = self.view_state.row_count();        debug_assert!(row_count > 1);        let scroll_handle = self.scroll_state.scroll_handle();        let viewport = scroll_handle.viewport();         if viewport.bottom() < evt.event.position.y {            self.view_state.schedule_scroll_down();        } else if viewport.top() > evt.event.position.y {            self.view_state.schedule_scroll_up();        }    }     fn handle_scroll_drag(&mut self, evt: &DragMoveEvent<ScrollbarDragging>) -> bool {        if !self.scroll_state.is_dragging() {            return false;        }        let row_count = self.view_state.row_count();        debug_assert!(row_count > 1);        let scroll_handle = self.scroll_state.scroll_handle();        let viewport = scroll_handle.viewport();         if viewport.bottom() < evt.event.position.y {            self.view_state.schedule_scroll_down();            true        } else if viewport.top() > evt.event.position.y {            self.view_state.schedule_scroll_up();            true        } else {            false        }    }     fn editor_style(editor: &Entity<Editor>, cx: &Context<Self>) -> EditorStyle {        let is_read_only = editor.read(cx).read_only(cx);        let settings = ThemeSettings::get_global(cx);        let theme = cx.theme();        let text_style = TextStyle {            color: if is_read_only {                theme.colors().text_muted            } else {                theme.colors().text            },            font_family: settings.buffer_font.family.clone(),            font_features: settings.buffer_font.features.clone(),            font_size: TextSize::Small.rems(cx).into(),            font_weight: settings.buffer_font.weight,             ..Default::default()        };        EditorStyle {            background: theme.colors().editor_background,            local_player: theme.players().local(),            text: text_style,            ..Default::default()        }    }     fn render_width_picker(&self, window: &mut Window, cx: &mut Context<Self>) -> DropdownMenu {        let weak = cx.weak_entity();        let selected_width = self.view_state.line_width.clone();        DropdownMenu::new(            "memory-view-width-picker",            selected_width.label.clone(),            ContextMenu::build(window, cx, |mut this, window, cx| {                for width in &WIDTHS {                    let weak = weak.clone();                    let width = width.clone();                    this = this.entry(width.label.clone(), None, move |_, cx| {                        _ = weak.update(cx, |this, _| {                            // Convert base ix between 2 line widths to keep the shown memory address roughly the same.                            // All widths are powers of 2, so the conversion should be lossless.                            match this.view_state.line_width.width.cmp(&width.width) {                                std::cmp::Ordering::Less => {                                    // We're converting up.                                    let shift = width.width.trailing_zeros()                                        - this.view_state.line_width.width.trailing_zeros();                                    this.view_state.base_row >>= shift;                                }                                std::cmp::Ordering::Greater => {                                    // We're converting down.                                    let shift = this.view_state.line_width.width.trailing_zeros()                                        - width.width.trailing_zeros();                                    this.view_state.base_row <<= shift;                                }                                _ => {}                            }                            this.view_state.line_width = width.clone();                        });                    });                }                if let Some(ix) = WIDTHS                    .iter()                    .position(|width| width.width == selected_width.width)                {                    for _ in 0..=ix {                        this.select_next(&Default::default(), window, cx);                    }                }                this            }),        )        .handle(self.width_picker_handle.clone())    }     fn page_down(&mut self, _: &menu::SelectLast, _: &mut Window, cx: &mut Context<Self>) {        self.view_state.base_row = self            .view_state            .base_row            .overflowing_add(self.view_state.row_count())            .0;        cx.notify();    }    fn page_up(&mut self, _: &menu::SelectFirst, _: &mut Window, cx: &mut Context<Self>) {        self.view_state.base_row = self            .view_state            .base_row            .overflowing_sub(self.view_state.row_count())            .0;        cx.notify();    }     fn change_query_bar_mode(        &mut self,        is_writing_memory: bool,        window: &mut Window,        cx: &mut Context<Self>,    ) {        if is_writing_memory == self.is_writing_memory {            return;        }        if !self.is_writing_memory {            self.query_editor.update(cx, |this, cx| {                this.clear(window, cx);                this.set_placeholder_text("Write to Selected Memory Range", cx);            });            self.is_writing_memory = true;            self.query_editor.focus_handle(cx).focus(window);        } else {            self.query_editor.update(cx, |this, cx| {                this.clear(window, cx);                this.set_placeholder_text("Go to Memory Address / Expression", cx);            });            self.is_writing_memory = false;        }    }     fn toggle_data_breakpoint(        &mut self,        _: &crate::ToggleDataBreakpoint,        _: &mut Window,        cx: &mut Context<Self>,    ) {        let Some(SelectedMemoryRange::DragComplete(selection)) = self.view_state.selection.clone()        else {            return;        };        let range = selection.memory_range();        let context = Arc::new(DataBreakpointContext::Address {            address: range.start().to_string(),            bytes: Some(*range.end() - *range.start()),        });         self.session.update(cx, |this, cx| {            let data_breakpoint_info = this.data_breakpoint_info(context.clone(), None, cx);            cx.spawn(async move |this, cx| {                if let Some(info) = data_breakpoint_info.await {                    let Some(data_id) = info.data_id.clone() else {                        return;                    };                    _ = this.update(cx, |this, cx| {                        this.create_data_breakpoint(                            context,                            data_id.clone(),                            dap::DataBreakpoint {                                data_id,                                access_type: None,                                condition: None,

Lines 1–500 of 984.