From 823e186acd768d5c3dbaf61b2e1cdbd9afb78d9a Mon Sep 17 00:00:00 2001 From: alice pellerin Date: Sat, 21 Mar 2026 04:48:17 -0500 Subject: [PATCH] jump relative to mark --- src/action.rs | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/app/mod.rs | 4 ++++ src/config.rs | 11 +++++----- src/main.rs | 12 ++++++---- 4 files changed, 76 insertions(+), 10 deletions(-) diff --git a/src/action.rs b/src/action.rs index 23dde22..c525a83 100644 --- a/src/action.rs +++ b/src/action.rs @@ -83,6 +83,7 @@ pub enum Action { SplitSelectionsInto9s, JumpToSelectedOffset, + JumpToSelectedOffsetRelativeToMark, ToggleMark, @@ -181,6 +182,7 @@ impl Buffer { Action::SplitSelectionsInto9s => self.split_selections_into_size(9), Action::JumpToSelectedOffset => self.jump_to_selected_offset(window_size), + Action::JumpToSelectedOffsetRelativeToMark => self.jump_to_selected_offset_relative_to_mark(window_size), Action::ToggleMark => self.toggle_mark(), @@ -688,6 +690,54 @@ impl Buffer { self.clamp_screen_to_primary_cursor(window_size); } + fn jump_to_selected_offset_relative_to_mark(&mut self, window_size: WindowSize) { + let mut sorted_marks: Vec<_> = self.marks.iter().copied().collect(); + sorted_marks.sort_unstable(); + + if !iter::once(&self.primary_cursor) + .chain(&self.cursors) + .all(|cursor| { + bytes_as_nat(&self.contents[cursor.range()]) + .map(|offset| mark_before(cursor.lower_bound(), &sorted_marks) + offset) + .is_some_and(|offset| offset < self.contents.len()) + }) + { + if self.cursors.is_empty() { + self.alert_message = Span::from( + "selection is not a valid offset" + ).red(); + } else { + self.alert_message = Span::from( + "not all selections are valid offsets" + ).red(); + } + return; + } + + self.primary_cursor = Cursor::at( + bytes_as_nat(&self.contents[self.primary_cursor.range()]) + .map(|offset| { + mark_before(self.primary_cursor.lower_bound(), &sorted_marks) + offset + }) + .unwrap() + ); + + for cursor in &mut self.cursors { + *cursor = Cursor::at( + bytes_as_nat(&self.contents[cursor.range()]) + .map(|offset| { + mark_before(cursor.lower_bound(), &sorted_marks) + offset + }) + .unwrap() + ); + } + + self.cursors.sort_by_key(|cursor| cursor.head); + + self.combine_cursors_if_overlapping(); + self.clamp_screen_to_primary_cursor(window_size); + } + fn toggle_mark(&mut self) { match self.marks.entry(self.primary_cursor.lower_bound()) { Entry::Occupied(occupied_entry) => { occupied_entry.remove(); }, @@ -748,3 +798,12 @@ fn bytes_as_nat(bytes: &[u8]) -> Option { Some(result.shl_exact(8)? | (byte as usize)) }) } + +// or 0 if no mark is before +fn mark_before(offset: usize, sorted_marks: &[usize]) -> usize { + match sorted_marks.binary_search(&offset) { + Ok(_) => offset, + Err(0) => 0, + Err(mark_after_index) => sorted_marks[mark_after_index - 1], + } +} diff --git a/src/app/mod.rs b/src/app/mod.rs index 438ec32..6dc0810 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -14,6 +14,8 @@ pub struct App { pub window_size: WindowSize, pub should_quit: bool, + + pub logs: Vec, } #[derive(Clone, Copy)] @@ -53,6 +55,8 @@ impl App { window_size, should_quit: false, + + logs: Vec::new(), } } diff --git a/src/config.rs b/src/config.rs index dbbaebd..8930024 100644 --- a/src/config.rs +++ b/src/config.rs @@ -86,10 +86,7 @@ impl From for Keypress { fn from(event: KeyEvent) -> Self { Self { code: event.code, - modifiers: match event.modifiers { - KeyModifiers::SHIFT => KeyModifiers::NONE, - x => x, - }, + modifiers: event.modifiers.difference(KeyModifiers::SHIFT), } } } @@ -162,7 +159,8 @@ impl Default for Config { ("8".try_into().unwrap(), Action::SplitSelectionsInto8s), ("9".try_into().unwrap(), Action::SplitSelectionsInto9s), - ("J".try_into().unwrap(), Action::JumpToSelectedOffset), + ("J".try_into().unwrap(), Action::JumpToSelectedOffsetRelativeToMark), + ("A-J".try_into().unwrap(), Action::JumpToSelectedOffset), ("m".try_into().unwrap(), Action::ToggleMark), ].into()), @@ -240,7 +238,8 @@ impl Default for Config { ("8".try_into().unwrap(), Action::SplitSelectionsInto8s), ("9".try_into().unwrap(), Action::SplitSelectionsInto9s), - ("J".try_into().unwrap(), Action::JumpToSelectedOffset), + ("J".try_into().unwrap(), Action::JumpToSelectedOffsetRelativeToMark), + ("A-J".try_into().unwrap(), Action::JumpToSelectedOffset), ("m".try_into().unwrap(), Action::ToggleMark), ].into()), diff --git a/src/main.rs b/src/main.rs index cd1bf5e..60a6c38 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,10 +26,8 @@ const LINES_OF_PADDING: usize = 5; const BYTES_OF_PADDING: usize = LINES_OF_PADDING * BYTES_PER_LINE; // TODO: -// - zz/zt/zb // - resizing can move the cursor off the screen -// - constant for 5 lines of padding -// - pad in clamp function, always +// - tab bar overflow // - search // - s/A-k/A-K // - C-a/C-x @@ -49,11 +47,13 @@ const BYTES_OF_PADDING: usize = LINES_OF_PADDING * BYTES_PER_LINE; // - [/] to cycle view offset? // - gj jump to entered offset // - repeat X times (dec and hex) -// - jump relative to last marker? +// - from register?? +// - extend to mark (tm?) // future directions // - 'views' for bytes (i8/16/etc u8/16/etc 20.12/8.4/etc) // - how to fit??! `-128` longer than `80` +// - popup for different readings for the selected bytes // - utf8? // - diffing @@ -79,6 +79,10 @@ fn main() { // dbg!(app.edit_history); + for log in app.logs { + println!("{log}"); + } + for log in app.buffers.iter().flat_map(|buffer| &buffer.logs) { println!("{log}"); }