From 1bd5d41123a73fa8cebfb5e2bfcc722a8bf4d47d Mon Sep 17 00:00:00 2001 From: alice pellerin Date: Sat, 21 Mar 2026 01:53:55 -0500 Subject: [PATCH] mark offset --- src/action.rs | 20 +++++++++++++- src/buffer/mod.rs | 6 ++++- src/buffer/widget.rs | 63 +++++++++++++++++++++++--------------------- src/config.rs | 4 +++ src/main.rs | 1 + 5 files changed, 62 insertions(+), 32 deletions(-) diff --git a/src/action.rs b/src/action.rs index 8e7b2e1..5b8be38 100644 --- a/src/action.rs +++ b/src/action.rs @@ -1,4 +1,4 @@ -use std::{cmp::min, convert::identity, fs::File, io::Write, iter, mem::{replace, swap}}; +use std::{cmp::min, collections::hash_set::Entry, convert::identity, fs::File, io::Write, iter, mem::{replace, swap}}; use ratatui::{style::Stylize, text::Span}; use crate::{BYTES_PER_LINE, app::WindowSize, buffer::{Buffer, Mode, PartialAction}, cursor::Cursor, edit_action::EditAction}; @@ -83,6 +83,8 @@ pub enum Action { SplitSelectionsInto9s, JumpToSelectedOffset, + + ToggleMark, } // actions that act on the app as a whole, not just one buffer @@ -175,6 +177,8 @@ impl Buffer { Action::SplitSelectionsInto9s => self.split_selections_into_size(9), Action::JumpToSelectedOffset => self.jump_to_selected_offset(window_size), + + Action::ToggleMark => self.toggle_mark(), } None @@ -646,6 +650,20 @@ impl Buffer { 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(); }, + Entry::Vacant(vacant_entry) => vacant_entry.insert(), + } + + for cursor in &self.cursors { + match self.marks.entry(cursor.lower_bound()) { + Entry::Occupied(occupied_entry) => { occupied_entry.remove(); }, + Entry::Vacant(vacant_entry) => vacant_entry.insert(), + } + } + } } // helpers diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs index 4c764a5..9311555 100644 --- a/src/buffer/mod.rs +++ b/src/buffer/mod.rs @@ -1,5 +1,5 @@ use core::slice::GetDisjointMutIndex; -use std::{fs::File, io::Read, path::PathBuf}; +use std::{collections::HashSet, fs::File, io::Read, path::PathBuf}; use crossterm::event::KeyEvent; use ratatui::{style::Color, text::Span}; use crate::{action::AppAction, app::WindowSize, config::Config, cursor::Cursor, edit_action::EditAction}; @@ -16,6 +16,8 @@ pub struct Buffer { pub primary_cursor: Cursor, pub cursors: Vec, + pub marks: HashSet, + pub mode: Mode, pub partial_action: Option, pub partial_replace: Option, @@ -86,6 +88,8 @@ impl Buffer { primary_cursor: Cursor::default(), cursors: Vec::new(), + marks: HashSet::from([0, 4, 12, 15, 16, 17]), + mode: Mode::Normal, partial_action: None, partial_replace: None, diff --git a/src/buffer/widget.rs b/src/buffer/widget.rs index f455eca..2c8c0d4 100644 --- a/src/buffer/widget.rs +++ b/src/buffer/widget.rs @@ -76,7 +76,7 @@ mod address { pub fn render_address(address: usize) -> Span<'static> { Span { style: Style::new().fg(Color::Rgb(138, 187, 195)), - content: format!("{address:08x} ").into() + content: format!("{address:08x}").into() } } } @@ -103,15 +103,9 @@ mod hex { .iter() .copied() .zip((address..).step_by(BYTES_PER_CHUNK)) - .map(|(chunk, address)| self.render_chunk(address, &chunk).collect()) - .interleave( - (address..) - .step_by(BYTES_PER_CHUNK) - .take(CHUNKS_PER_LINE) - .skip(1) - .map(|address| vec![self.render_large_space_before(address)]) - ) - .flatten() + .flat_map(|(chunk, address)| { + self.render_chunk(address, &chunk).collect::>() + }) } pub fn render_partial_chunks( @@ -161,16 +155,19 @@ mod hex { bytes: &[u8; BYTES_PER_CHUNK] ) -> impl Iterator> { #[allow(unstable_name_collisions)] - bytes - .iter() - .copied() - .zip(address..) - .map(|(byte, address)| self.render_byte_at(address, byte)) - .interleave( - (address..) - .take(BYTES_PER_CHUNK) - .skip(1) - .map(|address| self.render_space_before(address)) + iter::once(self.render_large_space_before(address)) + .chain( + bytes + .iter() + .copied() + .zip(address..) + .map(|(byte, address)| self.render_byte_at(address, byte)) + .interleave( + (address..) + .take(BYTES_PER_CHUNK) + .skip(1) + .map(|address| self.render_space_before(address)) + ) ) } @@ -244,30 +241,36 @@ mod hex { } fn render_large_space_before(&self, address: usize) -> Span<'static> { + let span: Span = if self.marks.contains(&address) { + " →".into() + } else { + " ".into() + }; + if iter::once(&self.primary_cursor) .chain(&self.cursors) .any(|cursor| cursor.contains_space_before(address)) { - Span { - style: Style::new().bg(Color::select_grey()), - content: " ".into() - } + span.bg(Color::select_grey()) } else { - " ".into() + span } } fn render_space_before(&self, address: usize) -> Span<'static> { + let span: Span = if self.marks.contains(&address) { + "→".into() + } else { + " ".into() + }; + if iter::once(&self.primary_cursor) .chain(&self.cursors) .any(|cursor| cursor.contains_space_before(address)) { - Span { - style: Style::new().bg(Color::select_grey()), - content: " ".into() - } + span.bg(Color::select_grey()) } else { - " ".into() + span } } } diff --git a/src/config.rs b/src/config.rs index ea42a00..994a49f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -163,6 +163,8 @@ impl Default for Config { ("9".try_into().unwrap(), Action::SplitSelectionsInto9s), ("J".try_into().unwrap(), Action::JumpToSelectedOffset), + + ("m".try_into().unwrap(), Action::ToggleMark), ].into()), (Some(PartialAction::Goto), [ ("j".try_into().unwrap(), Action::GotoLineStart), @@ -234,6 +236,8 @@ impl Default for Config { ("9".try_into().unwrap(), Action::SplitSelectionsInto9s), ("J".try_into().unwrap(), Action::JumpToSelectedOffset), + + ("m".try_into().unwrap(), Action::ToggleMark), ].into()), (Some(PartialAction::Space), [ ("w".try_into().unwrap(), Action::Save), diff --git a/src/main.rs b/src/main.rs index 0403add..308360f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ #![allow(clippy::cast_possible_truncation)] #![feature(get_disjoint_mut_helpers)] #![feature(exact_bitshifts)] +#![feature(hash_set_entry)] use app::App;