From bfc67a267145ae9c960eac25d6ea56036ebee8a1 Mon Sep 17 00:00:00 2001 From: alice pellerin Date: Wed, 18 Mar 2026 04:12:41 -0500 Subject: [PATCH] add extend motions, color cursor in select mode --- src/action.rs | 65 +++++++++++++++++++++++++++++++++++++++++++++++ src/app/widget.rs | 9 +++++-- src/config.rs | 14 +++++----- src/cursor.rs | 30 ++++++++++++++++++++++ 4 files changed, 109 insertions(+), 9 deletions(-) diff --git a/src/action.rs b/src/action.rs index fcc61f0..7da24bd 100644 --- a/src/action.rs +++ b/src/action.rs @@ -18,6 +18,11 @@ pub enum Action { MoveByteLeft, MoveByteRight, + ExtendByteUp, + ExtendByteDown, + ExtendByteLeft, + ExtendByteRight, + GotoLineStart, GotoLineEnd, GotoFileStart, @@ -36,6 +41,10 @@ pub enum Action { MoveNextWordEnd, MovePreviousWordStart, + ExtendNextWordStart, + ExtendNextWordEnd, + ExtendPreviousWordStart, + CollapseSelection, ExtendLineBelow, @@ -61,6 +70,11 @@ impl App { Action::MoveByteLeft => self.move_byte_left(), Action::MoveByteRight => self.move_byte_right(), + Action::ExtendByteUp => self.extend_byte_up(), + Action::ExtendByteDown => self.extend_byte_down(), + Action::ExtendByteLeft => self.extend_byte_left(), + Action::ExtendByteRight => self.extend_byte_right(), + Action::GotoLineStart => self.goto_line_start(), Action::GotoLineEnd => self.goto_line_end(), Action::GotoFileStart => self.goto_file_start(), @@ -79,6 +93,10 @@ impl App { Action::MoveNextWordEnd => self.move_next_word_end(), Action::MovePreviousWordStart => self.move_previous_word_start(), + Action::ExtendNextWordStart => self.extend_next_word_start(), + Action::ExtendNextWordEnd => self.extend_next_word_end(), + Action::ExtendPreviousWordStart => self.extend_previous_word_start(), + Action::CollapseSelection => self.collapse_selection(), Action::ExtendLineBelow => self.extend_line_below(), @@ -148,6 +166,34 @@ impl App { } } + const fn extend_byte_up(&mut self) { + if self.cursor.head >= BYTES_PER_LINE { + self.cursor.head -= BYTES_PER_LINE; + self.clamp_screen_to_cursor(); + } + } + + const fn extend_byte_down(&mut self) { + if self.contents.len() - 1 - self.cursor.head >= BYTES_PER_LINE { + self.cursor.head += BYTES_PER_LINE; + self.clamp_screen_to_cursor(); + } + } + + const fn extend_byte_left(&mut self) { + if self.cursor.head >= 1 { + self.cursor.head -= 1; + self.clamp_screen_to_cursor(); + } + } + + const fn extend_byte_right(&mut self) { + if self.contents.len() - 1 - self.cursor.head >= 1 { + self.cursor.head += 1; + self.clamp_screen_to_cursor(); + } + } + const fn goto_line_start(&mut self) { self.cursor.head -= self.cursor.head % BYTES_PER_LINE; self.cursor.collapse(); @@ -240,6 +286,21 @@ impl App { self.clamp_screen_to_cursor(); } + fn extend_next_word_start(&mut self) { + self.cursor.extend_to_next_word(self.contents.len() - 1); + self.clamp_screen_to_cursor(); + } + + fn extend_next_word_end(&mut self) { + self.cursor.extend_to_next_end(self.contents.len() - 1); + self.clamp_screen_to_cursor(); + } + + const fn extend_previous_word_start(&mut self) { + self.cursor.extend_to_previous_beginning(); + self.clamp_screen_to_cursor(); + } + const fn collapse_selection(&mut self) { self.cursor.collapse(); } @@ -285,6 +346,10 @@ impl App { data: self.contents[self.cursor.range()].into() } ); + + if self.mode == Mode::Select { + self.mode = Mode::Normal; + } } } diff --git a/src/app/widget.rs b/src/app/widget.rs index bc801ff..68fa3d8 100644 --- a/src/app/widget.rs +++ b/src/app/widget.rs @@ -78,7 +78,7 @@ mod hex { use itertools::{Itertools, repeat_n}; use ratatui::{style::{Color, Style, Stylize}, text::Span}; - use crate::{BYTES_PER_CHUNK, BYTES_PER_LINE, CHUNKS_PER_LINE, app::App, cardinality::HasCardinality, cursor::InCursor, empty_span::empty_span, custom_greys::CustomGreys}; + use crate::{BYTES_PER_CHUNK, BYTES_PER_LINE, CHUNKS_PER_LINE, app::{App, Mode}, cardinality::HasCardinality, cursor::InCursor, custom_greys::CustomGreys, empty_span::empty_span}; impl App { pub fn render_chunks( @@ -194,8 +194,13 @@ mod hex { let span = SPAN_FOR_BYTE[byte as usize].clone(); + let head_color = match self.mode { + Mode::Select => Color::Yellow, + _ => Color::Gray + }; + match self.cursor.contains(address) { - Some(InCursor::Head) => span.bg(Color::Gray), + Some(InCursor::Head) => span.bg(head_color), Some(InCursor::Rest) => span.bg(Color::select_grey()), None => span, } diff --git a/src/config.rs b/src/config.rs index f98a622..173e49f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -151,10 +151,10 @@ impl Default for Config { ("z".try_into().unwrap(), Action::ZPartial), ("r".try_into().unwrap(), Action::RPartial), - // ("i".try_into().unwrap(), Action::ExtendByteUp), - // ("k".try_into().unwrap(), Action::ExtendByteDown), - // ("j".try_into().unwrap(), Action::ExtendByteLeft), - // ("l".try_into().unwrap(), Action::ExtendByteRight), + ("i".try_into().unwrap(), Action::ExtendByteUp), + ("k".try_into().unwrap(), Action::ExtendByteDown), + ("j".try_into().unwrap(), Action::ExtendByteLeft), + ("l".try_into().unwrap(), Action::ExtendByteRight), ("C-e".try_into().unwrap(), Action::ScrollDown), ("C-y".try_into().unwrap(), Action::ScrollUp), @@ -165,9 +165,9 @@ impl Default for Config { ("C-f".try_into().unwrap(), Action::PageDown), ("C-b".try_into().unwrap(), Action::PageUp), - // ("w".try_into().unwrap(), Action::ExtendNextWordStart), - // ("e".try_into().unwrap(), Action::ExtendNextWordEnd), - // ("b".try_into().unwrap(), Action::ExtendPreviousWordStart), + ("w".try_into().unwrap(), Action::ExtendNextWordStart), + ("e".try_into().unwrap(), Action::ExtendNextWordEnd), + ("b".try_into().unwrap(), Action::ExtendPreviousWordStart), (";".try_into().unwrap(), Action::CollapseSelection), diff --git a/src/cursor.rs b/src/cursor.rs index 1fb9fca..5ebabe8 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -83,6 +83,36 @@ impl Cursor { self.head -= self.head % 4; } } + + pub fn extend_to_next_word(&mut self, max: usize) { + if self.head == max { return } + + if self.head.is_multiple_of(4) { // at the beginning of a word + self.head = (self.head + 4).min(max); + } else { + self.head = self.head.next_multiple_of(4).min(max); + } + } + + pub fn extend_to_next_end(&mut self, max: usize) { + if self.head == max { return } + + if self.head % 4 == 3 { // at the end of a word + self.head = (self.head + 4).min(max); + } else { + self.head = ((self.head + 1).next_multiple_of(4) - 1).min(max); + } + } + + pub const fn extend_to_previous_beginning(&mut self) { + if self.head == 0 { return } + + if self.head.is_multiple_of(4) { // at the beginning of a word + self.head -= 4; + } else { + self.head -= self.head % 4; + } + } } mod tests {