935 lines · 35.0 KB · Rust
RawDownload
use std::{    cell::{LazyCell, RefCell, RefMut},    fmt::Write,    ops::RangeInclusive,    rc::Rc,    sync::{Arc, LazyLock},    time::Duration,}; use editor::{Editor, EditorElement, EditorStyle};use gpui::{    Action, Along, AppContext, Axis, DismissEvent, DragMoveEvent, Empty, Entity, FocusHandle,    Focusable, ListHorizontalSizingBehavior, MouseButton, Point, ScrollStrategy, ScrollWheelEvent,    Subscription, Task, TextStyle, UniformList, UniformListScrollHandle, WeakEntity, actions,    anchored, deferred, uniform_list,};use notifications::status_toast::{StatusToast, ToastIcon};use project::debugger::{MemoryCell, dap_command::DataBreakpointContext, session::Session};use settings::Settings;use theme_settings::ThemeSettings;use ui::{    ContextMenu, Divider, DropdownMenu, FluentBuilder, IntoElement, PopoverMenuHandle, Render,    ScrollableHandle, StatefulInteractiveElement, Tooltip, WithScrollbar, prelude::*,};use workspace::Workspace; use crate::{ToggleDataBreakpoint, session::running::stack_frame_list::StackFrameList}; actions!(debugger, [GoToSelectedAddress]); pub(crate) struct MemoryView {    workspace: WeakEntity<Workspace>,    stack_frame_list: WeakEntity<StackFrameList>,    focus_handle: FocusHandle,    view_state_handle: ViewStateHandle,    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 ViewStateHandle(Rc<RefCell<ViewState>>); impl ViewStateHandle {    fn new(base_row: u64, line_width: ViewWidth) -> Self {        Self(Rc::new(RefCell::new(ViewState::new(base_row, line_width))))    }} #[derive(Clone)]struct ViewState {    /// Uppermost row index    base_row: u64,    /// How many cells per row do we have?    line_width: ViewWidth,    scroll_handle: UniformListScrollHandle,    selection: Option<SelectedMemoryRange>,} impl ViewState {    fn new(base_row: u64, line_width: ViewWidth) -> Self {        Self {            scroll_handle: UniformListScrollHandle::new(),            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);    }     fn set_offset(&mut self, point: Point<Pixels>) {        if point.y >= -Pixels::ZERO {            self.schedule_scroll_up();        } else if point.y <= -self.scroll_handle.max_offset().y {            self.schedule_scroll_down();        }        self.scroll_handle.set_offset(point);    }} impl ScrollableHandle for ViewStateHandle {    fn max_offset(&self) -> gpui::Point<Pixels> {        self.0.borrow().scroll_handle.max_offset()    }     fn set_offset(&self, point: Point<Pixels>) {        self.0.borrow_mut().set_offset(point);    }     fn offset(&self) -> Point<Pixels> {        self.0.borrow().scroll_handle.offset()    }     fn viewport(&self) -> gpui::Bounds<Pixels> {        self.0.borrow().scroll_handle.viewport()    }} 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_handle = ViewStateHandle::new(0, WIDTHS[4].clone());         let query_editor = cx.new(|cx| Editor::single_line(window, cx));         let mut this = Self {            workspace,            stack_frame_list,            focus_handle: cx.focus_handle(),            view_state_handle,            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 view_state(&self) -> RefMut<'_, ViewState> {        self.view_state_handle.0.borrow_mut()    }     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_handle.0.borrow().clone();        uniform_list(            "debugger-memory-view",            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(&view_state.scroll_handle)        .with_horizontal_sizing_behavior(ListHorizontalSizingBehavior::Unconstrained)        .on_scroll_wheel(cx.listener(|this, evt: &ScrollWheelEvent, window, _| {            let mut view_state = this.view_state();            let delta = evt.delta.pixel_delta(window.line_height());            let current_offset = view_state.scroll_handle.offset();            view_state                .set_offset(current_offset.apply_along(Axis::Vertical, |offset| offset + 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>) {        let mut view_state = self.view_state();        if !view_state            .selection            .as_ref()            .is_some_and(|selection| selection.is_dragging())        {            return;        }        let row_count = view_state.row_count();        debug_assert!(row_count > 1);        let scroll_handle = &view_state.scroll_handle;        let viewport = scroll_handle.viewport();         if viewport.bottom() < evt.event.position.y {            view_state.schedule_scroll_down();        } else if viewport.top() > evt.event.position.y {            view_state.schedule_scroll_up();        }    }     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, _| {                            let mut view_state = this.view_state();                            // 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 view_state.line_width.width.cmp(&width.width) {                                std::cmp::Ordering::Less => {                                    // We're converting up.                                    let shift = width.width.trailing_zeros()                                        - view_state.line_width.width.trailing_zeros();                                    view_state.base_row >>= shift;                                }                                std::cmp::Ordering::Greater => {                                    // We're converting down.                                    let shift = view_state.line_width.width.trailing_zeros()                                        - width.width.trailing_zeros();                                    view_state.base_row <<= shift;                                }                                _ => {}                            }                            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>) {        let mut view_state = self.view_state();        view_state.base_row = view_state            .base_row            .overflowing_add(view_state.row_count())            .0;        cx.notify();    }    fn page_up(&mut self, _: &menu::SelectFirst, _: &mut Window, cx: &mut Context<Self>) {        let mut view_state = self.view_state();        view_state.base_row = view_state            .base_row            .overflowing_sub(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", window, cx);            });            self.is_writing_memory = true;            self.query_editor.focus_handle(cx).focus(window, cx);        } else {            self.query_editor.update(cx, |this, cx| {                this.clear(window, cx);                this.set_placeholder_text("Go to Memory Address / Expression", window, 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 else {                        return;                    };                    _ = this.update(cx, |this, cx| {                        this.create_data_breakpoint(                            context,                            data_id.clone(),                            dap::DataBreakpoint {                                data_id,                                access_type: None,                                condition: None,                                hit_condition: None,                            },                            cx,                        );                    });                }            })            .detach();        })    }     fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {        let selection = self.view_state().selection.clone();        if let Some(SelectedMemoryRange::DragComplete(drag)) = selection {            // Go into memory writing mode.            if !self.is_writing_memory {                let should_return = self.session.update(cx, |session, cx| {                    if !session                        .capabilities()                        .supports_write_memory_request                        .unwrap_or_default()                    {                        let adapter_name = session.adapter();                        // We cannot write memory with this adapter.                        _ = self.workspace.update(cx, |this, cx| {                            this.toggle_status_toast(                                StatusToast::new(format!(                                    "Debug Adapter `{adapter_name}` does not support writing to memory"                                ), cx, |this, cx| {                                    cx.spawn(async move |this, cx| {                                        cx.background_executor().timer(Duration::from_secs(2)).await;                                        _ = this.update(cx, |_, cx| {                                            cx.emit(DismissEvent)                                        });                                    }).detach();                                    this.icon(ToastIcon::new(IconName::XCircle).color(Color::Error))                                }),                                cx,                            );                        });                        true                    } else {                        false                    }                });                if should_return {                    return;                }                 self.change_query_bar_mode(true, window, cx);            } else if self.query_editor.focus_handle(cx).is_focused(window) {                let mut text = self.query_editor.read(cx).text(cx);                if text.chars().any(|c| !c.is_ascii_hexdigit()) {

Lines 1–500 of 935.