split selections by size

jump to offset under cursor
This commit is contained in:
alice pellerin
2026-03-21 01:09:03 -05:00
parent 414b545a5c
commit 05d3a8a293
6 changed files with 81 additions and 14 deletions
+46
View File
@@ -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
-4
View File
@@ -14,8 +14,6 @@ pub struct App {
pub window_size: WindowSize,
pub should_quit: bool,
pub logs: Vec<String>,
}
#[derive(Clone, Copy)]
@@ -50,8 +48,6 @@ impl App {
},
should_quit: false,
logs: Vec::new(),
}
}
+5 -1
View File
@@ -27,6 +27,8 @@ pub struct Buffer {
pub time_traveling: Option<usize>,
// the index *after* the last saved edit action
pub last_saved_at: Option<usize>,
pub logs: Vec<String>,
}
#[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())
+21
View File
@@ -95,6 +95,7 @@ impl From<KeyEvent> 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),
+4
View File
@@ -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<InCursor> {
if index == self.head {
Some(InCursor::Head)
+5 -9
View File
@@ -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}");
}
}