From 7eb8c84c6e096c0b3c64d15eef3159693ffb2116 Mon Sep 17 00:00:00 2001 From: alice pellerin Date: Thu, 30 Apr 2026 12:23:28 -0500 Subject: [PATCH] drag with mouse to select bytes --- src/app.rs | 144 +++++++++++++++++++++++++++++++++++--------------- src/buffer.rs | 20 +++++-- src/main.rs | 13 +++-- 3 files changed, 124 insertions(+), 53 deletions(-) diff --git a/src/app.rs b/src/app.rs index d5b43b9..e3a5c80 100644 --- a/src/app.rs +++ b/src/app.rs @@ -19,6 +19,8 @@ pub struct App { pub should_quit: bool, + pub is_dragging_mouse: bool, + pub logs: Vec, } @@ -111,19 +113,23 @@ impl App { should_quit: false, + is_dragging_mouse: false, + logs: Vec::new(), } } - pub fn handle_events(&mut self, terminal: &mut DefaultTerminal) { - self.handle_event(terminal); + pub fn handle_events(&mut self, terminal: &mut DefaultTerminal) -> bool { + let mut should_redraw = self.handle_event(terminal); while event::poll(Duration::ZERO).unwrap() { - self.handle_event(terminal); + should_redraw |= self.handle_event(terminal); } + + should_redraw } - pub fn handle_event(&mut self, terminal: &mut DefaultTerminal) { + pub fn handle_event(&mut self, terminal: &mut DefaultTerminal) -> bool { let event = event::read() .inspect_err(|error| { #[cfg(target_os = "macos")] { @@ -140,20 +146,24 @@ impl App { }) .unwrap(); + // self.logs.push(format!("{event:?}")); + match event { Event::Resize(_, height) => { self.window_size.rows = height as usize; self.buffers[self.current_buffer_index] .clamp_screen_to_primary_cursor(self.window_size); + + true } Event::Key(key_event) => self.handle_key(key_event, terminal), Event::Mouse(mouse_event) => self.handle_mouse(mouse_event), - _ => {} + _ => false } } - fn handle_key(&mut self, key_event: KeyEvent, terminal: &mut DefaultTerminal) { + fn handle_key(&mut self, key_event: KeyEvent, terminal: &mut DefaultTerminal) -> bool { if key_event.modifiers == KeyModifiers::CONTROL && key_event.code == KeyCode::Char('c') { @@ -163,7 +173,7 @@ impl App { exit(130); } - let maybe_app_action = self.buffers[self.current_buffer_index].handle_key( + let (maybe_app_action, should_redraw) = self.buffers[self.current_buffer_index].handle_key( key_event, &self.config, &self.primary_cursor_register, @@ -182,62 +192,110 @@ impl App { AppAction::Yank => self.yank(), } } + + should_redraw || maybe_app_action.is_some() } - fn handle_mouse(&mut self, mouse_event: MouseEvent) { - let tab_bar_rows = usize::from(self.buffers.len() > 1); + fn handle_mouse(&mut self, mouse_event: MouseEvent) -> bool { + let position = self.mouse_event_position(mouse_event); let current_buffer = &mut self.buffers[self.current_buffer_index]; match mouse_event.kind { MouseEventKind::Down(_) => { - let byte_column = match mouse_event.column { - 10..=11 => Some(0), - 13..=14 => Some(1), - 16..=17 => Some(2), - 19..=20 => Some(3), - - 23..=24 => Some(4), - 26..=27 => Some(5), - 29..=30 => Some(6), - 32..=33 => Some(7), - - 36..=37 => Some(8), - 39..=40 => Some(9), - 42..=43 => Some(10), - 45..=46 => Some(11), - - 49..=50 => Some(12), - 52..=53 => Some(13), - 55..=56 => Some(14), - 58..=59 => Some(15), - - _ => None, - }; - - - if let Some(byte_column) = byte_column && - mouse_event.row as usize - tab_bar_rows < self.window_size.hex_rows() - { - current_buffer.primary_cursor = Cursor::at( - current_buffer.scroll_position + - (mouse_event.row as usize - tab_bar_rows) * BYTES_PER_LINE + - byte_column - ); + if let Some(position) = position { + current_buffer.primary_cursor = Cursor::at(position); current_buffer.cursors.clear(); current_buffer.clamp_screen_to_primary_cursor(self.window_size); + self.is_dragging_mouse = true; } + + true + }, + MouseEventKind::Drag(_) if self.is_dragging_mouse => { + if let Some(position) = position { + current_buffer.primary_cursor.head = position; + current_buffer.clamp_screen_to_primary_cursor(self.window_size); + } + + true + }, + MouseEventKind::Up(_) if self.is_dragging_mouse => { + if let Some(position) = position { + current_buffer.primary_cursor.head = position; + current_buffer.clamp_screen_to_primary_cursor(self.window_size); + } + + self.is_dragging_mouse = false; + + true }, MouseEventKind::ScrollDown => { for _ in 0..3 { current_buffer.scroll_down(self.window_size); } + + true }, MouseEventKind::ScrollUp => { for _ in 0..3 { current_buffer.scroll_up(self.window_size); } + + true }, - _ => (), + _ => false, } } + + fn mouse_event_position(&self, mouse_event: MouseEvent) -> Option { + let tab_bar_rows = usize::from(self.buffers.len() > 1); + + if usize::from(mouse_event.row) - tab_bar_rows >= self.window_size.hex_rows() { + return None; + } + + let current_buffer = &self.buffers[self.current_buffer_index]; + + let byte_column = match mouse_event.column { + 10..=11 => Some(0), + 13..=14 => Some(1), + 16..=17 => Some(2), + 19..=20 => Some(3), + + 23..=24 => Some(4), + 26..=27 => Some(5), + 29..=30 => Some(6), + 32..=33 => Some(7), + + 36..=37 => Some(8), + 39..=40 => Some(9), + 42..=43 => Some(10), + 45..=46 => Some(11), + + 49..=50 => Some(12), + 52..=53 => Some(13), + 55..=56 => Some(14), + 58..=59 => Some(15), + + _ => None, + }; + + byte_column.map(|byte_column| { + current_buffer.scroll_position + + (mouse_event.row as usize - tab_bar_rows) * BYTES_PER_LINE + + byte_column + }) + + // if let Some(byte_column) = byte_column && + // mouse_event.row as usize - tab_bar_rows < self.window_size.hex_rows() + // { + // Some( + // current_buffer.scroll_position + + // (mouse_event.row as usize - tab_bar_rows) * BYTES_PER_LINE + + // byte_column + // ) + // } else { + // None + // } + } } diff --git a/src/buffer.rs b/src/buffer.rs index 7f0cbe3..df30445 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -121,13 +121,15 @@ impl Buffer { primary_cursor_register: &[u8], other_cursor_registers: &[Vec], window_size: WindowSize - ) -> Option { + ) -> (Option, bool) { + let mut should_redraw = !self.alert_message.content.is_empty(); self.alert_message = "".into(); // self.logs.push(format!("{event:?}")); let app_action = match self.partial_action { Some(PartialAction::Replace) => { self.handle_replace(event, window_size); + should_redraw = true; None }, Some(PartialAction::Repeat) => { @@ -138,9 +140,14 @@ impl Buffer { other_cursor_registers, window_size ); + should_redraw = true; None }, - _ => self.handle_other_modes(event, config, window_size), + _ => { + let (app_action, redraw) = self.handle_other_modes(event, config, window_size); + should_redraw |= redraw; + app_action + }, }; assert!(self.scroll_position.is_multiple_of(BYTES_PER_LINE)); @@ -154,7 +161,7 @@ impl Buffer { debug_assert!(self.cursors.is_sorted_by_key(|cursor| cursor.head)); - app_action + (app_action, should_redraw) } fn handle_replace(&mut self, event: KeyEvent, window_size: WindowSize) { @@ -190,17 +197,20 @@ impl Buffer { event: KeyEvent, config: &Config, window_size: WindowSize - ) -> Option { + ) -> (Option, bool) { use Action::*; let mut result = None; let should_reset_partial = self.partial_action.is_some(); + let mut should_redraw = should_reset_partial; if let Some(mode_config) = config.0.get(&self.mode) && let Some(keybinds) = mode_config.0.get(&self.partial_action) && let Some(action) = keybinds.0.get(&event.into()) { + should_redraw = true; + if action.clears_popups() { self.popups.clear(); } @@ -232,7 +242,7 @@ impl Buffer { self.partial_action = None; } - result + (result, should_redraw) } fn handle_repeat( diff --git a/src/main.rs b/src/main.rs index 5cbdca9..ab61efd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,7 +33,6 @@ const LINES_OF_PADDING: usize = 5; const BYTES_OF_PADDING: usize = LINES_OF_PADDING * BYTES_PER_LINE; // TODO: -// - click and drag selection // - `go` goto entered offset // - search // - `/` hex, `A-/` ascii @@ -90,12 +89,16 @@ fn main() { crossterm::terminal::enable_raw_mode().unwrap(); terminal.backend_mut().queue(EnableMouseCapture).unwrap(); + let mut should_redraw = true; + while !app.should_quit { - terminal.draw(|frame| { - frame.render_widget(&app, frame.area()); - }).unwrap(); + if should_redraw { + terminal.draw(|frame| { + frame.render_widget(&app, frame.area()); + }).unwrap(); + } - app.handle_events(&mut terminal); + should_redraw = app.handle_events(&mut terminal); } terminal.backend_mut().queue(DisableMouseCapture).unwrap();