Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T05:38:30.722Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

memory_view.rs

935 lines · 35.9 KB · rust
1use std::{
2    cell::{LazyCell, RefCell, RefMut},
3    fmt::Write,
4    ops::RangeInclusive,
5    rc::Rc,
6    sync::{Arc, LazyLock},
7    time::Duration,
8};
9
10use editor::{Editor, EditorElement, EditorStyle};
11use gpui::{
12    Action, Along, AppContext, Axis, DismissEvent, DragMoveEvent, Empty, Entity, FocusHandle,
13    Focusable, ListHorizontalSizingBehavior, MouseButton, Point, ScrollStrategy, ScrollWheelEvent,
14    Subscription, Task, TextStyle, UniformList, UniformListScrollHandle, WeakEntity, actions,
15    anchored, deferred, uniform_list,
16};
17use notifications::status_toast::StatusToast;
18use project::debugger::{MemoryCell, dap_command::DataBreakpointContext, session::Session};
19use settings::Settings;
20use theme_settings::ThemeSettings;
21use ui::{
22    ContextMenu, Divider, DropdownMenu, PopoverMenuHandle, ScrollableHandle,
23    StatefulInteractiveElement, Tooltip, WithScrollbar, prelude::*,
24};
25use workspace::Workspace;
26
27use crate::{ToggleDataBreakpoint, session::running::stack_frame_list::StackFrameList};
28
29actions!(debugger, [GoToSelectedAddress]);
30
31pub(crate) struct MemoryView {
32    workspace: WeakEntity<Workspace>,
33    stack_frame_list: WeakEntity<StackFrameList>,
34    focus_handle: FocusHandle,
35    view_state_handle: ViewStateHandle,
36    query_editor: Entity<Editor>,
37    session: Entity<Session>,
38    width_picker_handle: PopoverMenuHandle<ContextMenu>,
39    is_writing_memory: bool,
40    open_context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
41}
42
43impl Focusable for MemoryView {
44    fn focus_handle(&self, _: &ui::App) -> FocusHandle {
45        self.focus_handle.clone()
46    }
47}
48#[derive(Clone, Debug)]
49struct Drag {
50    start_address: u64,
51    end_address: u64,
52}
53
54impl Drag {
55    fn contains(&self, address: u64) -> bool {
56        let range = self.memory_range();
57        range.contains(&address)
58    }
59
60    fn memory_range(&self) -> RangeInclusive<u64> {
61        if self.start_address < self.end_address {
62            self.start_address..=self.end_address
63        } else {
64            self.end_address..=self.start_address
65        }
66    }
67}
68#[derive(Clone, Debug)]
69enum SelectedMemoryRange {
70    DragUnderway(Drag),
71    DragComplete(Drag),
72}
73
74impl SelectedMemoryRange {
75    fn contains(&self, address: u64) -> bool {
76        match self {
77            SelectedMemoryRange::DragUnderway(drag) => drag.contains(address),
78            SelectedMemoryRange::DragComplete(drag) => drag.contains(address),
79        }
80    }
81    fn is_dragging(&self) -> bool {
82        matches!(self, SelectedMemoryRange::DragUnderway(_))
83    }
84    fn drag(&self) -> &Drag {
85        match self {
86            SelectedMemoryRange::DragUnderway(drag) => drag,
87            SelectedMemoryRange::DragComplete(drag) => drag,
88        }
89    }
90}
91
92#[derive(Clone)]
93struct ViewStateHandle(Rc<RefCell<ViewState>>);
94
95impl ViewStateHandle {
96    fn new(base_row: u64, line_width: ViewWidth) -> Self {
97        Self(Rc::new(RefCell::new(ViewState::new(base_row, line_width))))
98    }
99}
100
101#[derive(Clone)]
102struct ViewState {
103    /// Uppermost row index
104    base_row: u64,
105    /// How many cells per row do we have?
106    line_width: ViewWidth,
107    scroll_handle: UniformListScrollHandle,
108    selection: Option<SelectedMemoryRange>,
109}
110
111impl ViewState {
112    fn new(base_row: u64, line_width: ViewWidth) -> Self {
113        Self {
114            scroll_handle: UniformListScrollHandle::new(),
115            base_row,
116            line_width,
117            selection: None,
118        }
119    }
120    fn row_count(&self) -> u64 {
121        // 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
122        // middle ground for data size.
123        const PAGE_SIZE: u64 = 4096;
124        PAGE_SIZE / self.line_width.width as u64
125    }
126    fn schedule_scroll_down(&mut self) {
127        self.base_row = self.base_row.saturating_add(1)
128    }
129    fn schedule_scroll_up(&mut self) {
130        self.base_row = self.base_row.saturating_sub(1);
131    }
132
133    fn set_offset(&mut self, point: Point<Pixels>) {
134        if point.y >= -Pixels::ZERO {
135            self.schedule_scroll_up();
136        } else if point.y <= -self.scroll_handle.max_offset().y {
137            self.schedule_scroll_down();
138        }
139        self.scroll_handle.set_offset(point);
140    }
141}
142
143impl ScrollableHandle for ViewStateHandle {
144    fn max_offset(&self) -> gpui::Point<Pixels> {
145        self.0.borrow().scroll_handle.max_offset()
146    }
147
148    fn set_offset(&self, point: Point<Pixels>) {
149        self.0.borrow_mut().set_offset(point);
150    }
151
152    fn offset(&self) -> Point<Pixels> {
153        self.0.borrow().scroll_handle.offset()
154    }
155
156    fn viewport(&self) -> gpui::Bounds<Pixels> {
157        self.0.borrow().scroll_handle.viewport()
158    }
159}
160
161static HEX_BYTES_MEMOIZED: LazyLock<[SharedString; 256]> =
162    LazyLock::new(|| std::array::from_fn(|byte| SharedString::from(format!("{byte:02X}"))));
163static UNKNOWN_BYTE: SharedString = SharedString::new_static("??");
164
165impl MemoryView {
166    pub(crate) fn new(
167        session: Entity<Session>,
168        workspace: WeakEntity<Workspace>,
169        stack_frame_list: WeakEntity<StackFrameList>,
170        window: &mut Window,
171        cx: &mut Context<Self>,
172    ) -> Self {
173        let view_state_handle = ViewStateHandle::new(0, WIDTHS[4].clone());
174
175        let query_editor = cx.new(|cx| Editor::single_line(window, cx));
176
177        let mut this = Self {
178            workspace,
179            stack_frame_list,
180            focus_handle: cx.focus_handle(),
181            view_state_handle,
182            query_editor,
183            session,
184            width_picker_handle: Default::default(),
185            is_writing_memory: true,
186            open_context_menu: None,
187        };
188        this.change_query_bar_mode(false, window, cx);
189        cx.on_focus_out(&this.focus_handle, window, |this, _, window, cx| {
190            this.change_query_bar_mode(false, window, cx);
191            cx.notify();
192        })
193        .detach();
194        this
195    }
196
197    fn view_state(&self) -> RefMut<'_, ViewState> {
198        self.view_state_handle.0.borrow_mut()
199    }
200
201    fn render_memory(&self, cx: &mut Context<Self>) -> UniformList {
202        let weak = cx.weak_entity();
203        let session = self.session.clone();
204        let view_state = self.view_state_handle.0.borrow().clone();
205        uniform_list(
206            "debugger-memory-view",
207            view_state.row_count() as usize,
208            move |range, _, cx| {
209                let mut line_buffer = Vec::with_capacity(view_state.line_width.width as usize);
210                let memory_start =
211                    (view_state.base_row + range.start as u64) * view_state.line_width.width as u64;
212                let memory_end = (view_state.base_row + range.end as u64)
213                    * view_state.line_width.width as u64
214                    - 1;
215                let mut memory = session.update(cx, |this, cx| {
216                    this.read_memory(memory_start..=memory_end, cx)
217                });
218                let mut rows = Vec::with_capacity(range.end - range.start);
219                for ix in range {
220                    line_buffer.extend((&mut memory).take(view_state.line_width.width as usize));
221                    rows.push(render_single_memory_view_line(
222                        &line_buffer,
223                        ix as u64,
224                        weak.clone(),
225                        cx,
226                    ));
227                    line_buffer.clear();
228                }
229                rows
230            },
231        )
232        .track_scroll(&view_state.scroll_handle)
233        .with_horizontal_sizing_behavior(ListHorizontalSizingBehavior::Unconstrained)
234        .on_scroll_wheel(cx.listener(|this, evt: &ScrollWheelEvent, window, _| {
235            let mut view_state = this.view_state();
236            let delta = evt.delta.pixel_delta(window.line_height());
237            let current_offset = view_state.scroll_handle.offset();
238            view_state
239                .set_offset(current_offset.apply_along(Axis::Vertical, |offset| offset + delta.y));
240        }))
241    }
242    fn render_query_bar(&self, cx: &Context<Self>) -> impl IntoElement {
243        EditorElement::new(
244            &self.query_editor,
245            Self::editor_style(&self.query_editor, cx),
246        )
247    }
248    pub(super) fn go_to_memory_reference(
249        &mut self,
250        memory_reference: &str,
251        evaluate_name: Option<&str>,
252        stack_frame_id: Option<u64>,
253        cx: &mut Context<Self>,
254    ) {
255        use parse_int::parse;
256        let Ok(as_address) = parse::<u64>(memory_reference) else {
257            return;
258        };
259        let access_size = evaluate_name
260            .map(|typ| {
261                self.session.update(cx, |this, cx| {
262                    this.data_access_size(stack_frame_id, typ, cx)
263                })
264            })
265            .unwrap_or_else(|| Task::ready(None));
266        cx.spawn(async move |this, cx| {
267            let access_size = access_size.await.unwrap_or(1);
268            this.update(cx, |this, cx| {
269                this.view_state().selection = Some(SelectedMemoryRange::DragComplete(Drag {
270                    start_address: as_address,
271                    end_address: as_address + access_size - 1,
272                }));
273                this.jump_to_address(as_address, cx);
274            })
275            .ok();
276        })
277        .detach();
278    }
279
280    fn handle_memory_drag(&mut self, evt: &DragMoveEvent<Drag>) {
281        let mut view_state = self.view_state();
282        if !view_state
283            .selection
284            .as_ref()
285            .is_some_and(|selection| selection.is_dragging())
286        {
287            return;
288        }
289        let row_count = view_state.row_count();
290        debug_assert!(row_count > 1);
291        let scroll_handle = &view_state.scroll_handle;
292        let viewport = scroll_handle.viewport();
293
294        if viewport.bottom() < evt.event.position.y {
295            view_state.schedule_scroll_down();
296        } else if viewport.top() > evt.event.position.y {
297            view_state.schedule_scroll_up();
298        }
299    }
300
301    fn editor_style(editor: &Entity<Editor>, cx: &Context<Self>) -> EditorStyle {
302        let is_read_only = editor.read(cx).read_only(cx);
303        let settings = ThemeSettings::get_global(cx);
304        let theme = cx.theme();
305        let text_style = TextStyle {
306            color: if is_read_only {
307                theme.colors().text_muted
308            } else {
309                theme.colors().text
310            },
311            font_family: settings.buffer_font.family.clone(),
312            font_features: settings.buffer_font.features.clone(),
313            font_size: TextSize::Small.rems(cx).into(),
314            font_weight: settings.buffer_font.weight,
315
316            ..Default::default()
317        };
318        EditorStyle {
319            background: theme.colors().editor_background,
320            local_player: theme.players().local(),
321            text: text_style,
322            ..Default::default()
323        }
324    }
325
326    fn render_width_picker(&self, window: &mut Window, cx: &mut Context<Self>) -> DropdownMenu {
327        let weak = cx.weak_entity();
328        let selected_width = self.view_state().line_width.clone();
329        DropdownMenu::new(
330            "memory-view-width-picker",
331            selected_width.label.clone(),
332            ContextMenu::build(window, cx, |mut this, window, cx| {
333                for width in &WIDTHS {
334                    let weak = weak.clone();
335                    let width = width.clone();
336                    this = this.entry(width.label.clone(), None, move |_, cx| {
337                        _ = weak.update(cx, |this, _| {
338                            let mut view_state = this.view_state();
339                            // Convert base ix between 2 line widths to keep the shown memory address roughly the same.
340                            // All widths are powers of 2, so the conversion should be lossless.
341                            match view_state.line_width.width.cmp(&width.width) {
342                                std::cmp::Ordering::Less => {
343                                    // We're converting up.
344                                    let shift = width.width.trailing_zeros()
345                                        - view_state.line_width.width.trailing_zeros();
346                                    view_state.base_row >>= shift;
347                                }
348                                std::cmp::Ordering::Greater => {
349                                    // We're converting down.
350                                    let shift = view_state.line_width.width.trailing_zeros()
351                                        - width.width.trailing_zeros();
352                                    view_state.base_row <<= shift;
353                                }
354                                _ => {}
355                            }
356                            view_state.line_width = width.clone();
357                        });
358                    });
359                }
360                if let Some(ix) = WIDTHS
361                    .iter()
362                    .position(|width| width.width == selected_width.width)
363                {
364                    for _ in 0..=ix {
365                        this.select_next(&Default::default(), window, cx);
366                    }
367                }
368                this
369            }),
370        )
371        .style(ui::DropdownStyle::Outlined)
372        .handle(self.width_picker_handle.clone())
373        .attach(gpui::Anchor::BottomLeft)
374    }
375
376    fn page_down(&mut self, _: &menu::SelectLast, _: &mut Window, cx: &mut Context<Self>) {
377        let mut view_state = self.view_state();
378        view_state.base_row = view_state
379            .base_row
380            .overflowing_add(view_state.row_count())
381            .0;
382        cx.notify();
383    }
384    fn page_up(&mut self, _: &menu::SelectFirst, _: &mut Window, cx: &mut Context<Self>) {
385        let mut view_state = self.view_state();
386        view_state.base_row = view_state
387            .base_row
388            .overflowing_sub(view_state.row_count())
389            .0;
390        cx.notify();
391    }
392
393    fn change_query_bar_mode(
394        &mut self,
395        is_writing_memory: bool,
396        window: &mut Window,
397        cx: &mut Context<Self>,
398    ) {
399        if is_writing_memory == self.is_writing_memory {
400            return;
401        }
402        if !self.is_writing_memory {
403            self.query_editor.update(cx, |this, cx| {
404                this.clear(window, cx);
405                this.set_placeholder_text("Write to Selected Memory Range", window, cx);
406            });
407            self.is_writing_memory = true;
408            self.query_editor.focus_handle(cx).focus(window, cx);
409        } else {
410            self.query_editor.update(cx, |this, cx| {
411                this.clear(window, cx);
412                this.set_placeholder_text("Go to Memory Address / Expression", window, cx);
413            });
414            self.is_writing_memory = false;
415        }
416    }
417
418    fn toggle_data_breakpoint(
419        &mut self,
420        _: &crate::ToggleDataBreakpoint,
421        _: &mut Window,
422        cx: &mut Context<Self>,
423    ) {
424        let Some(SelectedMemoryRange::DragComplete(selection)) =
425            self.view_state().selection.clone()
426        else {
427            return;
428        };
429        let range = selection.memory_range();
430        let context = Arc::new(DataBreakpointContext::Address {
431            address: range.start().to_string(),
432            bytes: Some(*range.end() - *range.start()),
433        });
434
435        self.session.update(cx, |this, cx| {
436            let data_breakpoint_info = this.data_breakpoint_info(context.clone(), None, cx);
437            cx.spawn(async move |this, cx| {
438                if let Some(info) = data_breakpoint_info.await {
439                    let Some(data_id) = info.data_id else {
440                        return;
441                    };
442                    _ = this.update(cx, |this, cx| {
443                        this.create_data_breakpoint(
444                            context,
445                            data_id.clone(),
446                            dap::DataBreakpoint {
447                                data_id,
448                                access_type: None,
449                                condition: None,
450                                hit_condition: None,
451                            },
452                            cx,
453                        );
454                    });
455                }
456            })
457            .detach();
458        })
459    }
460
461    fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
462        let selection = self.view_state().selection.clone();
463        if let Some(SelectedMemoryRange::DragComplete(drag)) = selection {
464            // Go into memory writing mode.
465            if !self.is_writing_memory {
466                let should_return = self.session.update(cx, |session, cx| {
467                    if !session
468                        .capabilities()
469                        .supports_write_memory_request
470                        .unwrap_or_default()
471                    {
472                        let adapter_name = session.adapter();
473                        // We cannot write memory with this adapter.
474                        _ = self.workspace.update(cx, |this, cx| {
475                            this.toggle_status_toast(
476                                StatusToast::new(format!(
477                                    "Debug Adapter `{adapter_name}` does not support writing to memory"
478                                ), cx, |this, cx| {
479                                    cx.spawn(async move |this, cx| {
480                                        cx.background_executor().timer(Duration::from_secs(2)).await;
481                                        _ = this.update(cx, |_, cx| {
482                                            cx.emit(DismissEvent)
483                                        });
484                                    }).detach();
485                                    this.icon(Icon::new(IconName::XCircle).size(IconSize::Small).color(Color::Error))
486                                }),
487                                cx,
488                            );
489                        });
490                        true
491                    } else {
492                        false
493                    }
494                });
495                if should_return {
496                    return;
497                }
498
499                self.change_query_bar_mode(true, window, cx);
500            } else if self.query_editor.focus_handle(cx).is_focused(window) {
501                let mut text = self.query_editor.read(cx).text(cx);
502                if text.chars().any(|c| !c.is_ascii_hexdigit()) {
503                    // Interpret this text as a string and oh-so-conveniently convert it.
504                    text = text.bytes().map(|byte| format!("{:02x}", byte)).collect();
505                }
506                self.session.update(cx, |this, cx| {
507                    let range = drag.memory_range();
508
509                    if let Ok(as_hex) = hex::decode(text) {
510                        this.write_memory(*range.start(), &as_hex, cx);
511                    }
512                });
513                self.change_query_bar_mode(false, window, cx);
514            }
515
516            cx.notify();
517            return;
518        }
519        // Just change the currently viewed address.
520        if !self.query_editor.focus_handle(cx).is_focused(window) {
521            return;
522        }
523        self.jump_to_query_bar_address(cx);
524    }
525
526    fn jump_to_query_bar_address(&mut self, cx: &mut Context<Self>) {
527        use parse_int::parse;
528        let text = self.query_editor.read(cx).text(cx);
529
530        let Ok(as_address) = parse::<u64>(&text) else {
531            return self.jump_to_expression(text, cx);
532        };
533        self.jump_to_address(as_address, cx);
534    }
535
536    fn jump_to_address(&mut self, address: u64, cx: &mut Context<Self>) {
537        let mut view_state = self.view_state();
538        view_state.base_row = (address & !0xfff) / view_state.line_width.width as u64;
539        let line_ix = (address & 0xfff) / view_state.line_width.width as u64;
540        view_state
541            .scroll_handle
542            .scroll_to_item(line_ix as usize, ScrollStrategy::Center);
543        cx.notify();
544    }
545
546    fn jump_to_expression(&mut self, expr: String, cx: &mut Context<Self>) {
547        let Ok(selected_frame) = self
548            .stack_frame_list
549            .update(cx, |this, _| this.opened_stack_frame_id())
550        else {
551            return;
552        };
553        let expr = format!("?${{{expr}}}");
554        let reference = self.session.update(cx, |this, cx| {
555            this.memory_reference_of_expr(selected_frame, expr, cx)
556        });
557        cx.spawn(async move |this, cx| {
558            if let Some((reference, typ)) = reference.await {
559                _ = this.update(cx, |this, cx| {
560                    let sizeof_expr = if typ.as_ref().is_some_and(|t| {
561                        t.chars()
562                            .all(|c| c.is_whitespace() || c.is_alphabetic() || c == '*')
563                    }) {
564                        typ.as_deref()
565                    } else {
566                        None
567                    };
568                    this.go_to_memory_reference(&reference, sizeof_expr, selected_frame, cx);
569                });
570            }
571        })
572        .detach();
573    }
574
575    fn cancel(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
576        self.view_state().selection = None;
577        cx.notify();
578    }
579
580    /// Jump to memory pointed to by selected memory range.
581    fn go_to_address(
582        &mut self,
583        _: &GoToSelectedAddress,
584        window: &mut Window,
585        cx: &mut Context<Self>,
586    ) {
587        let Some(SelectedMemoryRange::DragComplete(drag)) = self.view_state().selection.clone()
588        else {
589            return;
590        };
591        let range = drag.memory_range();
592        let Some(memory): Option<Vec<u8>> = self.session.update(cx, |this, cx| {
593            this.read_memory(range, cx).map(|cell| cell.0).collect()
594        }) else {
595            return;
596        };
597        if memory.len() > 8 {
598            return;
599        }
600        let zeros_to_write = 8 - memory.len();
601        let mut acc = String::from("0x");
602        acc.extend(std::iter::repeat("00").take(zeros_to_write));
603        let as_query = memory.into_iter().rev().fold(acc, |mut acc, byte| {
604            _ = write!(&mut acc, "{:02x}", byte);
605            acc
606        });
607        self.query_editor.update(cx, |this, cx| {
608            this.set_text(as_query, window, cx);
609        });
610        self.jump_to_query_bar_address(cx);
611    }
612
613    fn deploy_memory_context_menu(
614        &mut self,
615        range: RangeInclusive<u64>,
616        position: Point<Pixels>,
617        window: &mut Window,
618        cx: &mut Context<Self>,
619    ) {
620        let session = self.session.clone();
621        let context_menu = ContextMenu::build(window, cx, |menu, _, cx| {
622            let range_too_large = range.end() - range.start() > std::mem::size_of::<u64>() as u64;
623            let caps = session.read(cx).capabilities();
624            let supports_data_breakpoints = caps.supports_data_breakpoints.unwrap_or_default()
625                && caps.supports_data_breakpoint_bytes.unwrap_or_default();
626            let memory_unreadable = LazyCell::new(|| {
627                session.update(cx, |this, cx| {
628                    this.read_memory(range.clone(), cx)
629                        .any(|cell| cell.0.is_none())
630                })
631            });
632
633            let mut menu = menu.action_disabled_when(
634                range_too_large || *memory_unreadable,
635                "Go To Selected Address",
636                GoToSelectedAddress.boxed_clone(),
637            );
638
639            if supports_data_breakpoints {
640                menu = menu.action_disabled_when(
641                    *memory_unreadable,
642                    "Set Data Breakpoint",
643                    ToggleDataBreakpoint { access_type: None }.boxed_clone(),
644                );
645            }
646            menu.context(self.focus_handle.clone())
647        });
648
649        cx.focus_view(&context_menu, window);
650        let subscription = cx.subscribe_in(
651            &context_menu,
652            window,
653            |this, _, _: &DismissEvent, window, cx| {
654                if this.open_context_menu.as_ref().is_some_and(|context_menu| {
655                    context_menu.0.focus_handle(cx).contains_focused(window, cx)
656                }) {
657                    cx.focus_self(window);
658                }
659                this.open_context_menu.take();
660                cx.notify();
661            },
662        );
663
664        self.open_context_menu = Some((context_menu, position, subscription));
665    }
666}
667
668#[derive(Clone)]
669struct ViewWidth {
670    width: u8,
671    label: SharedString,
672}
673
674impl ViewWidth {
675    const fn new(width: u8, label: &'static str) -> Self {
676        Self {
677            width,
678            label: SharedString::new_static(label),
679        }
680    }
681}
682
683static WIDTHS: [ViewWidth; 7] = [
684    ViewWidth::new(1, "1 byte"),
685    ViewWidth::new(2, "2 bytes"),
686    ViewWidth::new(4, "4 bytes"),
687    ViewWidth::new(8, "8 bytes"),
688    ViewWidth::new(16, "16 bytes"),
689    ViewWidth::new(32, "32 bytes"),
690    ViewWidth::new(64, "64 bytes"),
691];
692
693fn render_single_memory_view_line(
694    memory: &[MemoryCell],
695    ix: u64,
696    weak: gpui::WeakEntity<MemoryView>,
697    cx: &mut App,
698) -> AnyElement {
699    let Ok(view_state) = weak.update(cx, |this, _| this.view_state().clone()) else {
700        return div().into_any();
701    };
702    let base_address = (view_state.base_row + ix) * view_state.line_width.width as u64;
703
704    h_flex()
705        .id((
706            "memory-view-row-full",
707            ix * view_state.line_width.width as u64,
708        ))
709        .size_full()
710        .gap_x_2()
711        .child(
712            div()
713                .child(
714                    Label::new(format!("{:016X}", base_address))
715                        .buffer_font(cx)
716                        .size(ui::LabelSize::Small)
717                        .color(Color::Muted),
718                )
719                .px_1()
720                .border_r_1()
721                .border_color(Color::Muted.color(cx)),
722        )
723        .child(
724            h_flex()
725                .id((
726                    "memory-view-row-raw-memory",
727                    ix * view_state.line_width.width as u64,
728                ))
729                .px_1()
730                .children(memory.iter().enumerate().map(|(cell_ix, cell)| {
731                    let weak = weak.clone();
732                    div()
733                        .id(("memory-view-row-raw-memory-cell", cell_ix as u64))
734                        .px_0p5()
735                        .when_some(view_state.selection.as_ref(), |this, selection| {
736                            this.when(selection.contains(base_address + cell_ix as u64), |this| {
737                                let weak = weak.clone();
738
739                                this.bg(Color::Selected.color(cx).opacity(0.2)).when(
740                                    !selection.is_dragging(),
741                                    |this| {
742                                        let selection = selection.drag().memory_range();
743                                        this.on_mouse_down(
744                                            MouseButton::Right,
745                                            move |click, window, cx| {
746                                                _ = weak.update(cx, |this, cx| {
747                                                    this.deploy_memory_context_menu(
748                                                        selection.clone(),
749                                                        click.position,
750                                                        window,
751                                                        cx,
752                                                    )
753                                                });
754                                                cx.stop_propagation();
755                                            },
756                                        )
757                                    },
758                                )
759                            })
760                        })
761                        .child(
762                            Label::new(
763                                cell.0
764                                    .map(|val| HEX_BYTES_MEMOIZED[val as usize].clone())
765                                    .unwrap_or_else(|| UNKNOWN_BYTE.clone()),
766                            )
767                            .buffer_font(cx)
768                            .when(cell.0.is_none(), |this| this.color(Color::Muted))
769                            .size(ui::LabelSize::Small),
770                        )
771                        .on_drag(
772                            Drag {
773                                start_address: base_address + cell_ix as u64,
774                                end_address: base_address + cell_ix as u64,
775                            },
776                            {
777                                let weak = weak.clone();
778                                move |drag, _, _, cx| {
779                                    _ = weak.update(cx, |this, _| {
780                                        this.view_state().selection =
781                                            Some(SelectedMemoryRange::DragUnderway(drag.clone()));
782                                    });
783
784                                    cx.new(|_| Empty)
785                                }
786                            },
787                        )
788                        .on_drop({
789                            let weak = weak.clone();
790                            move |drag: &Drag, _, cx| {
791                                _ = weak.update(cx, |this, _| {
792                                    this.view_state().selection =
793                                        Some(SelectedMemoryRange::DragComplete(Drag {
794                                            start_address: drag.start_address,
795                                            end_address: base_address + cell_ix as u64,
796                                        }));
797                                });
798                            }
799                        })
800                        .drag_over(move |style, drag: &Drag, _, cx| {
801                            _ = weak.update(cx, |this, _| {
802                                this.view_state().selection =
803                                    Some(SelectedMemoryRange::DragUnderway(Drag {
804                                        start_address: drag.start_address,
805                                        end_address: base_address + cell_ix as u64,
806                                    }));
807                            });
808
809                            style
810                        })
811                })),
812        )
813        .child(
814            h_flex()
815                .id((
816                    "memory-view-row-ascii-memory",
817                    ix * view_state.line_width.width as u64,
818                ))
819                .h_full()
820                .px_1()
821                .mr_4()
822                // .gap_x_1p5()
823                .border_x_1()
824                .border_color(Color::Muted.color(cx))
825                .children(memory.iter().enumerate().map(|(ix, cell)| {
826                    let as_character = char::from(cell.0.unwrap_or(0));
827                    let as_visible = if as_character.is_ascii_graphic() {
828                        as_character
829                    } else {
830                        '·'
831                    };
832                    div()
833                        .px_0p5()
834                        .when_some(view_state.selection.as_ref(), |this, selection| {
835                            this.when(selection.contains(base_address + ix as u64), |this| {
836                                this.bg(Color::Selected.color(cx).opacity(0.2))
837                            })
838                        })
839                        .child(
840                            Label::new(format!("{as_visible}"))
841                                .buffer_font(cx)
842                                .when(cell.0.is_none(), |this| this.color(Color::Muted))
843                                .size(ui::LabelSize::Small),
844                        )
845                })),
846        )
847        .into_any()
848}
849
850impl Render for MemoryView {
851    fn render(&mut self, window: &mut ui::Window, cx: &mut ui::Context<Self>) -> impl IntoElement {
852        let (icon, tooltip_text) = if self.is_writing_memory {
853            (IconName::Pencil, "Edit Memory at a Selected Address")
854        } else {
855            (
856                IconName::LocationEdit,
857                "Change Address of Currently Viewed Memory",
858            )
859        };
860
861        v_flex()
862            .id("Memory-view")
863            .on_action(cx.listener(Self::cancel))
864            .on_action(cx.listener(Self::go_to_address))
865            .p_1()
866            .on_action(cx.listener(Self::confirm))
867            .on_action(cx.listener(Self::toggle_data_breakpoint))
868            .on_action(cx.listener(Self::page_down))
869            .on_action(cx.listener(Self::page_up))
870            .size_full()
871            .track_focus(&self.focus_handle)
872            .child(
873                h_flex()
874                    .w_full()
875                    .mb_1()
876                    .gap_1()
877                    .child(
878                        h_flex()
879                            .px_1()
880                            .h_6()
881                            .w_full()
882                            .rounded_sm()
883                            .gap_1()
884                            .border_1()
885                            .when_else(
886                                self.query_editor
887                                    .focus_handle(cx)
888                                    .contains_focused(window, cx),
889                                |this| this.border_color(cx.theme().colors().border_focused),
890                                |this| this.border_color(cx.theme().colors().border_variant),
891                            )
892                            .bg(cx.theme().colors().editor_background)
893                            .child(
894                                div()
895                                    .id("memory-view-editor-icon")
896                                    .child(Icon::new(icon).size(ui::IconSize::XSmall))
897                                    .tooltip(Tooltip::text(tooltip_text)),
898                            )
899                            .child(Divider::vertical())
900                            .child(self.render_query_bar(cx)),
901                    )
902                    .child(self.render_width_picker(window, cx)),
903            )
904            .child(Divider::horizontal())
905            .child(
906                v_flex()
907                    .size_full()
908                    .on_drag_move(cx.listener(|this, evt, _, _| {
909                        this.handle_memory_drag(evt);
910                    }))
911                    .child(self.render_memory(cx).size_full())
912                    .children(self.open_context_menu.as_ref().map(|(menu, position, _)| {
913                        deferred(
914                            anchored()
915                                .position(*position)
916                                .anchor(gpui::Anchor::TopLeft)
917                                .child(menu.clone()),
918                        )
919                        .with_priority(1)
920                    }))
921                    .custom_scrollbars(
922                        ui::Scrollbars::new(ui::ScrollAxes::Both)
923                            .tracked_scroll_handle(&self.view_state_handle)
924                            .with_track_along(
925                                ui::ScrollAxes::Both,
926                                cx.theme().colors().panel_background,
927                            )
928                            .tracked_entity(cx.entity_id()),
929                        window,
930                        cx,
931                    ),
932            )
933    }
934}
935
Served at tenant.openagents/omega Member data and write actions are omitted.