drag with mouse to select bytes

This commit is contained in:
alice pellerin
2026-04-30 12:23:28 -05:00
parent e60e63cb16
commit 7eb8c84c6e
3 changed files with 124 additions and 53 deletions
+101 -43
View File
@@ -19,6 +19,8 @@ pub struct App {
pub should_quit: bool, pub should_quit: bool,
pub is_dragging_mouse: bool,
pub logs: Vec<String>, pub logs: Vec<String>,
} }
@@ -111,19 +113,23 @@ impl App {
should_quit: false, should_quit: false,
is_dragging_mouse: false,
logs: Vec::new(), logs: Vec::new(),
} }
} }
pub fn handle_events(&mut self, terminal: &mut DefaultTerminal) { pub fn handle_events(&mut self, terminal: &mut DefaultTerminal) -> bool {
self.handle_event(terminal); let mut should_redraw = self.handle_event(terminal);
while event::poll(Duration::ZERO).unwrap() { 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() let event = event::read()
.inspect_err(|error| { .inspect_err(|error| {
#[cfg(target_os = "macos")] { #[cfg(target_os = "macos")] {
@@ -140,20 +146,24 @@ impl App {
}) })
.unwrap(); .unwrap();
// self.logs.push(format!("{event:?}"));
match event { match event {
Event::Resize(_, height) => { Event::Resize(_, height) => {
self.window_size.rows = height as usize; self.window_size.rows = height as usize;
self.buffers[self.current_buffer_index] self.buffers[self.current_buffer_index]
.clamp_screen_to_primary_cursor(self.window_size); .clamp_screen_to_primary_cursor(self.window_size);
true
} }
Event::Key(key_event) => self.handle_key(key_event, terminal), Event::Key(key_event) => self.handle_key(key_event, terminal),
Event::Mouse(mouse_event) => self.handle_mouse(mouse_event), 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 && if key_event.modifiers == KeyModifiers::CONTROL &&
key_event.code == KeyCode::Char('c') key_event.code == KeyCode::Char('c')
{ {
@@ -163,7 +173,7 @@ impl App {
exit(130); 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, key_event,
&self.config, &self.config,
&self.primary_cursor_register, &self.primary_cursor_register,
@@ -182,62 +192,110 @@ impl App {
AppAction::Yank => self.yank(), AppAction::Yank => self.yank(),
} }
} }
should_redraw || maybe_app_action.is_some()
} }
fn handle_mouse(&mut self, mouse_event: MouseEvent) { fn handle_mouse(&mut self, mouse_event: MouseEvent) -> bool {
let tab_bar_rows = usize::from(self.buffers.len() > 1); let position = self.mouse_event_position(mouse_event);
let current_buffer = &mut self.buffers[self.current_buffer_index]; let current_buffer = &mut self.buffers[self.current_buffer_index];
match mouse_event.kind { match mouse_event.kind {
MouseEventKind::Down(_) => { MouseEventKind::Down(_) => {
let byte_column = match mouse_event.column { if let Some(position) = position {
10..=11 => Some(0), current_buffer.primary_cursor = Cursor::at(position);
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
);
current_buffer.cursors.clear(); current_buffer.cursors.clear();
current_buffer.clamp_screen_to_primary_cursor(self.window_size); 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 => { MouseEventKind::ScrollDown => {
for _ in 0..3 { for _ in 0..3 {
current_buffer.scroll_down(self.window_size); current_buffer.scroll_down(self.window_size);
} }
true
}, },
MouseEventKind::ScrollUp => { MouseEventKind::ScrollUp => {
for _ in 0..3 { for _ in 0..3 {
current_buffer.scroll_up(self.window_size); current_buffer.scroll_up(self.window_size);
} }
true
}, },
_ => (), _ => false,
} }
} }
fn mouse_event_position(&self, mouse_event: MouseEvent) -> Option<usize> {
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
// }
}
} }
+15 -5
View File
@@ -121,13 +121,15 @@ impl Buffer {
primary_cursor_register: &[u8], primary_cursor_register: &[u8],
other_cursor_registers: &[Vec<u8>], other_cursor_registers: &[Vec<u8>],
window_size: WindowSize window_size: WindowSize
) -> Option<AppAction> { ) -> (Option<AppAction>, bool) {
let mut should_redraw = !self.alert_message.content.is_empty();
self.alert_message = "".into(); self.alert_message = "".into();
// self.logs.push(format!("{event:?}")); // self.logs.push(format!("{event:?}"));
let app_action = match self.partial_action { let app_action = match self.partial_action {
Some(PartialAction::Replace) => { Some(PartialAction::Replace) => {
self.handle_replace(event, window_size); self.handle_replace(event, window_size);
should_redraw = true;
None None
}, },
Some(PartialAction::Repeat) => { Some(PartialAction::Repeat) => {
@@ -138,9 +140,14 @@ impl Buffer {
other_cursor_registers, other_cursor_registers,
window_size window_size
); );
should_redraw = true;
None 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)); 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)); 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) { fn handle_replace(&mut self, event: KeyEvent, window_size: WindowSize) {
@@ -190,17 +197,20 @@ impl Buffer {
event: KeyEvent, event: KeyEvent,
config: &Config, config: &Config,
window_size: WindowSize window_size: WindowSize
) -> Option<AppAction> { ) -> (Option<AppAction>, bool) {
use Action::*; use Action::*;
let mut result = None; let mut result = None;
let should_reset_partial = self.partial_action.is_some(); 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) && if let Some(mode_config) = config.0.get(&self.mode) &&
let Some(keybinds) = mode_config.0.get(&self.partial_action) && let Some(keybinds) = mode_config.0.get(&self.partial_action) &&
let Some(action) = keybinds.0.get(&event.into()) let Some(action) = keybinds.0.get(&event.into())
{ {
should_redraw = true;
if action.clears_popups() { if action.clears_popups() {
self.popups.clear(); self.popups.clear();
} }
@@ -232,7 +242,7 @@ impl Buffer {
self.partial_action = None; self.partial_action = None;
} }
result (result, should_redraw)
} }
fn handle_repeat( fn handle_repeat(
+8 -5
View File
@@ -33,7 +33,6 @@ const LINES_OF_PADDING: usize = 5;
const BYTES_OF_PADDING: usize = LINES_OF_PADDING * BYTES_PER_LINE; const BYTES_OF_PADDING: usize = LINES_OF_PADDING * BYTES_PER_LINE;
// TODO: // TODO:
// - click and drag selection
// - `go` goto entered offset // - `go` goto entered offset
// - search // - search
// - `/` hex, `A-/` ascii // - `/` hex, `A-/` ascii
@@ -90,12 +89,16 @@ fn main() {
crossterm::terminal::enable_raw_mode().unwrap(); crossterm::terminal::enable_raw_mode().unwrap();
terminal.backend_mut().queue(EnableMouseCapture).unwrap(); terminal.backend_mut().queue(EnableMouseCapture).unwrap();
let mut should_redraw = true;
while !app.should_quit { while !app.should_quit {
terminal.draw(|frame| { if should_redraw {
frame.render_widget(&app, frame.area()); terminal.draw(|frame| {
}).unwrap(); 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(); terminal.backend_mut().queue(DisableMouseCapture).unwrap();