memory_view.rs

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