From 1d348ca5f8a5acc96f645dc75e387aa74fc66dd8 Mon Sep 17 00:00:00 2001 From: alice pellerin Date: Tue, 17 Mar 2026 02:36:08 -0500 Subject: [PATCH] add G motion and up/down/left/right --- src/app/mod.rs | 62 ++++++++++++++++++++++++++++++++++++-------------- src/cursor.rs | 7 +++--- src/main.rs | 10 ++++++++ 3 files changed, 58 insertions(+), 21 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index d31065a..73ccf1d 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -40,6 +40,11 @@ impl App { } } + // in bytes + const fn screen_size(&self) -> usize { + self.window_rows * BYTES_PER_LINE + } + #[allow(clippy::too_many_lines)] pub fn handle_events(&mut self) { #[allow(clippy::collapsible_match)] @@ -56,44 +61,55 @@ impl App { self.scroll_position + BYTES_PER_LINE, self.contents.len() - (5 * BYTES_PER_LINE) ); - self.cursor.clamp(self.scroll_position, self.window_rows); + self.cursor.clamp(self.scroll_position, self.screen_size()); } Event::Key(key_event) if key_event.modifiers.contains(KeyModifiers::CONTROL) && key_event.code == KeyCode::Char('y') => { self.scroll_position = self.scroll_position.saturating_sub(BYTES_PER_LINE); - self.cursor.clamp(self.scroll_position, self.window_rows); + self.cursor.clamp(self.scroll_position, self.screen_size()); } Event::Key(key_event) if key_event.modifiers.contains(KeyModifiers::CONTROL) && key_event.code == KeyCode::Char('d') => { self.scroll_position = min( - self.scroll_position + ((self.window_rows / 2) * BYTES_PER_LINE), + self.scroll_position + self.screen_size() / 2, self.contents.len() - (5 * BYTES_PER_LINE) ); - self.cursor.clamp(self.scroll_position, self.window_rows); + self.cursor.clamp(self.scroll_position, self.screen_size()); } + // TODO: for up/down, keep cursor at same relative position on screen Event::Key(key_event) if key_event.modifiers.contains(KeyModifiers::CONTROL) && key_event.code == KeyCode::Char('u') => { self.scroll_position = self.scroll_position.saturating_sub( - (self.window_rows / 2) * BYTES_PER_LINE + self.screen_size() / 2 ); - self.cursor.clamp(self.scroll_position, self.window_rows); + self.cursor.clamp(self.scroll_position, self.screen_size()); } Event::Key(key_event) if key_event.modifiers.contains(KeyModifiers::CONTROL) && key_event.code == KeyCode::Char('f') => { self.scroll_position = min( - self.scroll_position + (self.window_rows * BYTES_PER_LINE), + self.scroll_position + self.screen_size(), self.contents.len() - (5 * BYTES_PER_LINE) ); - self.cursor.clamp(self.scroll_position, self.window_rows); + self.cursor.clamp(self.scroll_position, self.screen_size()); } Event::Key(key_event) if key_event.modifiers.contains(KeyModifiers::CONTROL) && key_event.code == KeyCode::Char('b') => { self.scroll_position = self.scroll_position.saturating_sub( - self.window_rows * BYTES_PER_LINE + self.screen_size() ); - self.cursor.clamp(self.scroll_position, self.window_rows); + self.cursor.clamp(self.scroll_position, self.screen_size()); } - Event::Key(key_event) if key_event.code == KeyCode::Char('i') => { + // Event::Key(key_event) if key_event.code == KeyCode::Char('g') => { + // } + Event::Key(key_event) if key_event.code == KeyCode::Char('G') => { + self.cursor.head = previous_multiple_of(BYTES_PER_LINE, self.contents.len()) + + (self.cursor.head % BYTES_PER_LINE); + + self.cursor.collapse(); + self.clamp_screen_to_cursor(); + } + Event::Key(key_event) if key_event.code == KeyCode::Char('i') || + key_event.code == KeyCode::Up => { if self.cursor.head >= BYTES_PER_LINE { self.cursor.head -= BYTES_PER_LINE; self.cursor.collapse(); @@ -101,7 +117,8 @@ impl App { self.clamp_screen_to_cursor(); } } - Event::Key(key_event) if key_event.code == KeyCode::Char('j') => { + Event::Key(key_event) if key_event.code == KeyCode::Char('j') || + key_event.code == KeyCode::Left => { if self.cursor.head >= 1 { self.cursor.head -= 1; self.cursor.collapse(); @@ -109,7 +126,8 @@ impl App { self.clamp_screen_to_cursor(); } } - Event::Key(key_event) if key_event.code == KeyCode::Char('k') => { + Event::Key(key_event) if key_event.code == KeyCode::Char('k') || + key_event.code == KeyCode::Down => { if self.contents.len() - 1 - self.cursor.head >= BYTES_PER_LINE { self.cursor.head += BYTES_PER_LINE; self.cursor.collapse(); @@ -117,7 +135,8 @@ impl App { self.clamp_screen_to_cursor(); } } - Event::Key(key_event) if key_event.code == KeyCode::Char('l') => { + Event::Key(key_event) if key_event.code == KeyCode::Char('l') || + key_event.code == KeyCode::Right => { if self.contents.len() - 1 - self.cursor.head >= 1 { self.cursor.head += 1; self.cursor.collapse(); @@ -174,9 +193,18 @@ impl App { const fn clamp_screen_to_cursor(&mut self) { if self.cursor.head < self.scroll_position { - self.scroll_position -= BYTES_PER_LINE; - } else if self.cursor.head > self.scroll_position + (self.window_rows * BYTES_PER_LINE) - 1 { - self.scroll_position += BYTES_PER_LINE; + self.scroll_position -= (self.scroll_position - self.cursor.head).next_multiple_of(BYTES_PER_LINE); + } else if self.cursor.head > self.scroll_position + self.screen_size() - 1 { + let screen_edge_offset_to_cursor = self.cursor.head - (self.scroll_position + self.screen_size() - 1); + self.scroll_position += screen_edge_offset_to_cursor.next_multiple_of(BYTES_PER_LINE); } } } + +const fn previous_multiple_of(multiple: usize, number: usize) -> usize { + if number == 0 { + 0 + } else { + (number - 1) - ((number - 1) % multiple) + } +} diff --git a/src/cursor.rs b/src/cursor.rs index 3a449d3..bda4be7 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -1,5 +1,3 @@ -use crate::BYTES_PER_LINE; - #[derive(Debug, Default, PartialEq, Eq)] pub struct Cursor { pub head: usize, @@ -33,8 +31,9 @@ impl Cursor { self.tail = self.head; } - pub fn clamp(&mut self, scroll_position: usize, window_rows: usize) { - let max_row = scroll_position + window_rows * BYTES_PER_LINE - 1; + // TODO: in visual mode, should only clamp head + pub fn clamp(&mut self, scroll_position: usize, screen_size: usize) { + let max_row = scroll_position + screen_size - 1; self.head = self.head.clamp(scroll_position, max_row); self.tail = self.tail.clamp(scroll_position, max_row); diff --git a/src/main.rs b/src/main.rs index 9dd127d..11c28b7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,6 +13,16 @@ const BYTES_PER_LINE: usize = 0x10; const BYTES_PER_CHUNK: usize = 4; const CHUNKS_PER_LINE: usize = BYTES_PER_LINE / BYTES_PER_CHUNK; +// TODO: +// - modes +// - g/v/z +// - modifications +// - insert/append +// - replace +// - replace-and-keep-going +// - delete +// - change + fn main() { let mut app = App::init(); let mut terminal = ratatui::init();