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. Load more
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,