diff --git a/src/action.rs b/src/action.rs index 5131ab6..2798d38 100644 --- a/src/action.rs +++ b/src/action.rs @@ -1,4 +1,6 @@ use std::{cmp::min, 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}; #[derive(Clone, Copy)] @@ -69,6 +71,16 @@ pub enum Action { KeepPrimarySelection, RemovePrimarySelection, + + SplitSelectionsInto1s, + SplitSelectionsInto2s, + SplitSelectionsInto3s, + SplitSelectionsInto4s, + SplitSelectionsInto5s, + SplitSelectionsInto6s, + SplitSelectionsInto7s, + SplitSelectionsInto8s, + SplitSelectionsInto9s, } // actions that act on the app as a whole, not just one buffer @@ -149,6 +161,16 @@ impl Buffer { Action::KeepPrimarySelection => self.keep_primary_selection(), Action::RemovePrimarySelection => self.remove_primary_selection(), + + Action::SplitSelectionsInto1s => self.split_selections_into_size(1), + Action::SplitSelectionsInto2s => self.split_selections_into_size(2), + Action::SplitSelectionsInto3s => self.split_selections_into_size(3), + Action::SplitSelectionsInto4s => self.split_selections_into_size(4), + Action::SplitSelectionsInto5s => self.split_selections_into_size(5), + Action::SplitSelectionsInto6s => self.split_selections_into_size(6), + Action::SplitSelectionsInto7s => self.split_selections_into_size(7), + Action::SplitSelectionsInto8s => self.split_selections_into_size(8), + Action::SplitSelectionsInto9s => self.split_selections_into_size(9), } None @@ -560,6 +582,30 @@ impl Buffer { self.primary_cursor = self.cursors.remove(next_cursor_index); } } + + fn split_selections_into_size(&mut self, size: usize) { + if !iter::once(&self.primary_cursor) + .chain(&self.cursors) + .all(|cursor| cursor.len().is_multiple_of(size)) + { + self.alert_message = Span::from( + format!("not all selections are a multiple of {size} long") + ).red(); + return; + } + + let mut new_cursors = iter::once(self.primary_cursor) + .chain(self.cursors.iter().copied()) + .flat_map(|cursor| { + cursor + .range() + .step_by(size) + .map(|head| Cursor { head, tail: head + size - 1 }) + }); + + self.primary_cursor = new_cursors.next().unwrap(); + self.cursors = new_cursors.collect(); + } } // helpers diff --git a/src/app/mod.rs b/src/app/mod.rs index c4449c8..6887c22 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -14,8 +14,6 @@ pub struct App { pub window_size: WindowSize, pub should_quit: bool, - - pub logs: Vec, } #[derive(Clone, Copy)] @@ -50,8 +48,6 @@ impl App { }, should_quit: false, - - logs: Vec::new(), } } diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs index f4b1024..4c764a5 100644 --- a/src/buffer/mod.rs +++ b/src/buffer/mod.rs @@ -27,6 +27,8 @@ pub struct Buffer { pub time_traveling: Option, // the index *after* the last saved edit action pub last_saved_at: Option, + + pub logs: Vec, } #[derive(Clone, Copy, Hash, PartialEq, Eq)] @@ -93,6 +95,8 @@ impl Buffer { edit_history: Vec::new(), time_traveling: None, last_saved_at: Some(0), + + logs: Vec::new(), } } @@ -173,7 +177,7 @@ impl Buffer { pub fn combine_cursors_if_overlapping(&mut self) { let mut index = 0; - while !self.cursors.is_empty() && index < self.cursors.len() - 1 { + while !self.cursors.is_empty() && index < self.cursors.len() { while index < self.cursors.len() - 1 && self.cursors[index].range().is_overlapping( &self.cursors[index + 1].range()) diff --git a/src/config.rs b/src/config.rs index bf2fd8d..c0280f6 100644 --- a/src/config.rs +++ b/src/config.rs @@ -95,6 +95,7 @@ impl From for Keypress { } impl Default for Config { + #[allow(clippy::too_many_lines)] fn default() -> Self { [ (Mode::Normal, [ @@ -150,6 +151,16 @@ impl Default for Config { (",".try_into().unwrap(), Action::KeepPrimarySelection), ("A-,".try_into().unwrap(), Action::RemovePrimarySelection), + + ("1".try_into().unwrap(), Action::SplitSelectionsInto1s), + ("2".try_into().unwrap(), Action::SplitSelectionsInto2s), + ("3".try_into().unwrap(), Action::SplitSelectionsInto3s), + ("4".try_into().unwrap(), Action::SplitSelectionsInto4s), + ("5".try_into().unwrap(), Action::SplitSelectionsInto5s), + ("6".try_into().unwrap(), Action::SplitSelectionsInto6s), + ("7".try_into().unwrap(), Action::SplitSelectionsInto7s), + ("8".try_into().unwrap(), Action::SplitSelectionsInto8s), + ("9".try_into().unwrap(), Action::SplitSelectionsInto9s), ].into()), (Some(PartialAction::Goto), [ ("j".try_into().unwrap(), Action::GotoLineStart), @@ -209,6 +220,16 @@ impl Default for Config { (",".try_into().unwrap(), Action::KeepPrimarySelection), ("A-,".try_into().unwrap(), Action::RemovePrimarySelection), + + ("1".try_into().unwrap(), Action::SplitSelectionsInto1s), + ("2".try_into().unwrap(), Action::SplitSelectionsInto2s), + ("3".try_into().unwrap(), Action::SplitSelectionsInto3s), + ("4".try_into().unwrap(), Action::SplitSelectionsInto4s), + ("5".try_into().unwrap(), Action::SplitSelectionsInto5s), + ("6".try_into().unwrap(), Action::SplitSelectionsInto6s), + ("7".try_into().unwrap(), Action::SplitSelectionsInto7s), + ("8".try_into().unwrap(), Action::SplitSelectionsInto8s), + ("9".try_into().unwrap(), Action::SplitSelectionsInto9s), ].into()), (Some(PartialAction::Space), [ ("w".try_into().unwrap(), Action::Save), diff --git a/src/cursor.rs b/src/cursor.rs index d1ea78c..352590c 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -30,6 +30,10 @@ impl Cursor { self.lower_bound()..=self.upper_bound() } + pub fn len(&self) -> usize { + self.upper_bound() - self.lower_bound() + 1 + } + pub const fn contains(&self, index: usize) -> Option { if index == self.head { Some(InCursor::Head) diff --git a/src/main.rs b/src/main.rs index cb0f17e..3abd807 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,7 +20,10 @@ const BYTES_PER_CHUNK: usize = 4; const CHUNKS_PER_LINE: usize = BYTES_PER_LINE / BYTES_PER_CHUNK; // TODO: +// - J jump to offset under cursor +// - m mark offset // - search +// - s/A-k/A-K // - modifications // - insert/append // - mode @@ -36,18 +39,11 @@ const CHUNKS_PER_LINE: usize = BYTES_PER_LINE / BYTES_PER_CHUNK; // - jumplist // - y/p // - [/] to cycle view offset? -// - J jump to offset -// - under cursor? +// - gj jump to entered offset // future directions -// - switch between cursor size u8s/u16s/u32s/u64s? -// - +/- -// - multi-cursor -// - s/C -// - split selection by u8/16/32/etc // - 'views' for bytes (i8/16/etc u8/16/etc 20.12/8.4/etc) // - how to fit??! `-128` longer than `80` -// - mark offsets? // - utf8? // - diffing @@ -71,7 +67,7 @@ fn main() { // dbg!(app.edit_history); - for log in app.logs { + for log in app.buffers.iter().flat_map(|buffer| &buffer.logs) { println!("{log}"); } }