From 67dc63acece9010110e88458c56a4b10e648d40a Mon Sep 17 00:00:00 2001 From: alice pellerin Date: Tue, 17 Mar 2026 00:38:40 -0500 Subject: [PATCH] cursor/screen clamping --- src/app/mod.rs | 63 ++++++++++++++++++++++++++++++++++++++------------ src/cursor.rs | 9 ++++++++ 2 files changed, 57 insertions(+), 15 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 4feb1e7..d31065a 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -8,7 +8,7 @@ mod widget; #[derive(Debug)] pub struct App { pub contents: Vec, - pub window_rows: u16, + pub window_rows: usize, pub scroll_position: usize, pub cursor: Cursor, pub should_quit: bool @@ -33,7 +33,7 @@ impl App { Self { contents, - window_rows: window_size().unwrap().rows, + window_rows: window_size().unwrap().rows as usize, scroll_position: 0, cursor: Cursor::default(), should_quit: false @@ -45,7 +45,7 @@ impl App { #[allow(clippy::collapsible_match)] match event::read().unwrap() { Event::Resize(_, height) => { - self.window_rows = height; + self.window_rows = height as usize; } Event::Key(key_event) if key_event.code == KeyCode::Char('q') => { self.should_quit = true; @@ -56,89 +56,114 @@ impl App { self.scroll_position + BYTES_PER_LINE, self.contents.len() - (5 * BYTES_PER_LINE) ); + self.cursor.clamp(self.scroll_position, self.window_rows); } 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); } 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) as usize * BYTES_PER_LINE), + self.scroll_position + ((self.window_rows / 2) * BYTES_PER_LINE), self.contents.len() - (5 * BYTES_PER_LINE) ); + self.cursor.clamp(self.scroll_position, self.window_rows); } 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) as usize * BYTES_PER_LINE + (self.window_rows / 2) * BYTES_PER_LINE ); + self.cursor.clamp(self.scroll_position, self.window_rows); } 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 as usize * BYTES_PER_LINE), + self.scroll_position + (self.window_rows * BYTES_PER_LINE), self.contents.len() - (5 * BYTES_PER_LINE) ); + self.cursor.clamp(self.scroll_position, self.window_rows); } 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 as usize * BYTES_PER_LINE + self.window_rows * BYTES_PER_LINE ); + self.cursor.clamp(self.scroll_position, self.window_rows); } Event::Key(key_event) if key_event.code == KeyCode::Char('i') => { - if self.cursor.head >= 0x10 { - self.cursor.head -= 0x10; + if self.cursor.head >= BYTES_PER_LINE { + self.cursor.head -= BYTES_PER_LINE; self.cursor.collapse(); + + self.clamp_screen_to_cursor(); } } Event::Key(key_event) if key_event.code == KeyCode::Char('j') => { if self.cursor.head >= 1 { self.cursor.head -= 1; self.cursor.collapse(); + + self.clamp_screen_to_cursor(); } } Event::Key(key_event) if key_event.code == KeyCode::Char('k') => { - if self.contents.len() - 1 - self.cursor.head >= 0x10 { - self.cursor.head += 0x10; + if self.contents.len() - 1 - self.cursor.head >= BYTES_PER_LINE { + self.cursor.head += BYTES_PER_LINE; self.cursor.collapse(); + + self.clamp_screen_to_cursor(); } } Event::Key(key_event) if key_event.code == KeyCode::Char('l') => { if self.contents.len() - 1 - self.cursor.head >= 1 { self.cursor.head += 1; self.cursor.collapse(); + + self.clamp_screen_to_cursor(); } } Event::Key(key_event) if key_event.code == KeyCode::Char('I') => { - if self.cursor.head >= 0x10 { - self.cursor.head -= 0x10; + if self.cursor.head >= BYTES_PER_LINE { + self.cursor.head -= BYTES_PER_LINE; + + self.clamp_screen_to_cursor(); } } Event::Key(key_event) if key_event.code == KeyCode::Char('J') => { if self.cursor.head >= 1 { self.cursor.head -= 1; + + self.clamp_screen_to_cursor(); } } Event::Key(key_event) if key_event.code == KeyCode::Char('K') => { - if self.contents.len() - 1 - self.cursor.head >= 0x10 { - self.cursor.head += 0x10; + if self.contents.len() - 1 - self.cursor.head >= BYTES_PER_LINE { + self.cursor.head += BYTES_PER_LINE; + + self.clamp_screen_to_cursor(); } } Event::Key(key_event) if key_event.code == KeyCode::Char('L') => { if self.contents.len() - 1 - self.cursor.head >= 1 { self.cursor.head += 1; + + self.clamp_screen_to_cursor(); } } Event::Key(key_event) if key_event.code == KeyCode::Char('w') => { self.cursor.move_to_next_word(self.contents.len() - 1); + self.clamp_screen_to_cursor(); } Event::Key(key_event) if key_event.code == KeyCode::Char('e') => { self.cursor.move_to_next_end(self.contents.len() - 1); + self.clamp_screen_to_cursor(); } Event::Key(key_event) if key_event.code == KeyCode::Char('b') => { self.cursor.move_to_previous_beginning(); + self.clamp_screen_to_cursor(); } Event::Key(key_event) if key_event.code == KeyCode::Char(';') => { self.cursor.collapse(); @@ -146,4 +171,12 @@ 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; + } + } } diff --git a/src/cursor.rs b/src/cursor.rs index 93e2f1d..3a449d3 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -1,3 +1,5 @@ +use crate::BYTES_PER_LINE; + #[derive(Debug, Default, PartialEq, Eq)] pub struct Cursor { pub head: usize, @@ -31,6 +33,13 @@ 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; + + self.head = self.head.clamp(scroll_position, max_row); + self.tail = self.tail.clamp(scroll_position, max_row); + } + pub fn move_to_next_word(&mut self, max: usize) { if self.head == max { return }