diff --git a/src/action.rs b/src/action.rs index d51f197..d81f356 100644 --- a/src/action.rs +++ b/src/action.rs @@ -37,6 +37,7 @@ pub enum BufferAction { Replace, Space, Repeat, + To, ScrollDown, ScrollUp, @@ -83,6 +84,10 @@ pub enum BufferAction { AlignViewCenter, AlignViewBottom, AlignViewTop, + + ExtendToMark, + ExtendToNull, + ExtendToFF, } impl From for Action { @@ -137,6 +142,7 @@ impl Buffer { BufferAction::Replace => self.replace(), BufferAction::Space => self.space(), BufferAction::Repeat => self.repeat(), + BufferAction::To => self.to(), BufferAction::ScrollDown => self.scroll_down(window_size), BufferAction::ScrollUp => self.scroll_up(window_size), @@ -148,7 +154,7 @@ impl Buffer { BufferAction::PageUp => self.page_up(window_size), BufferAction::CollapseSelection => self.collapse_selection(), - BufferAction::FlipSelections => self.flip_selection(), + BufferAction::FlipSelections => self.flip_selection(window_size), BufferAction::Delete => self.delete(window_size), @@ -183,6 +189,10 @@ impl Buffer { BufferAction::AlignViewCenter => self.align_view_center(window_size), BufferAction::AlignViewBottom => self.align_view_bottom(window_size), BufferAction::AlignViewTop => self.align_view_top(), + + BufferAction::ExtendToMark => self.extend_to_mark(window_size), + BufferAction::ExtendToNull => self.extend_to_null(window_size), + BufferAction::ExtendToFF => self.extend_to_FF(window_size), } } @@ -216,6 +226,10 @@ impl Buffer { self.partial_action = Some(PartialAction::Repeat); } + const fn to(&mut self) { + self.partial_action = Some(PartialAction::To); + } + pub fn scroll_down(&mut self, window_size: WindowSize) { if self.contents.len() <= BYTES_OF_PADDING { return; } @@ -348,12 +362,14 @@ impl Buffer { } } - fn flip_selection(&mut self) { + fn flip_selection(&mut self, window_size: WindowSize) { self.primary_cursor.flip(); for cursor in &mut self.cursors { cursor.flip(); } + + self.clamp_screen_to_primary_cursor(window_size); } fn delete(&mut self, window_size: WindowSize) { @@ -651,6 +667,84 @@ impl Buffer { .saturating_sub(self.primary_cursor.head % BYTES_PER_LINE) .saturating_sub(BYTES_OF_PADDING); } + + fn extend_to_mark(&mut self, window_size: WindowSize) { + let mut sorted_marks: Vec<_> = self.marks.iter().copied().collect(); + sorted_marks.sort_unstable(); + + let max_contents_index = self.max_contents_index(); + + let mark_after_primary = mark_after( + self.primary_cursor.head + 1, + &sorted_marks, + max_contents_index + ); + + self.primary_cursor.tail = self.primary_cursor.head; + self.primary_cursor.head = mark_after_primary - 1; + + for cursor in &mut self.cursors { + let mark_after_cursor = mark_after( + cursor.head + 1, + &sorted_marks, + max_contents_index + ); + + cursor.tail = cursor.head; + cursor.head = mark_after_cursor - 1; + } + + self.clamp_screen_to_primary_cursor(window_size); + } + + fn extend_to_null(&mut self, window_size: WindowSize) { + if let Some(null_offset_after_primary) = self.contents[self.primary_cursor.head..] + .iter() + .skip(2) + .position(|&byte| byte == 0) + { + self.primary_cursor.tail = self.primary_cursor.head; + self.primary_cursor.head += null_offset_after_primary + 1; + } + + for cursor in &mut self.cursors { + if let Some(null_offset_after_primary) = self.contents[cursor.head..] + .iter() + .skip(2) + .position(|&byte| byte == 0) + { + cursor.tail = cursor.head; + cursor.head += null_offset_after_primary + 1; + } + } + + self.clamp_screen_to_primary_cursor(window_size); + } + + #[allow(non_snake_case)] + fn extend_to_FF(&mut self, window_size: WindowSize) { + if let Some(null_offset_after_primary) = self.contents[self.primary_cursor.head..] + .iter() + .skip(2) + .position(|&byte| byte == 0xFF) + { + self.primary_cursor.tail = self.primary_cursor.head; + self.primary_cursor.head += null_offset_after_primary + 1; + } + + for cursor in &mut self.cursors { + if let Some(null_offset_after_primary) = self.contents[cursor.head..] + .iter() + .skip(2) + .position(|&byte| byte == 0xFF) + { + cursor.tail = cursor.head; + cursor.head += null_offset_after_primary + 1; + } + } + + self.clamp_screen_to_primary_cursor(window_size); + } } // helpers @@ -682,3 +776,23 @@ fn mark_before(offset: usize, sorted_marks: &[usize]) -> usize { Err(mark_after_index) => sorted_marks[mark_after_index - 1], } } + +// or end index if no mark is after +fn mark_after(offset: usize, sorted_marks: &[usize], max: usize) -> usize { + if sorted_marks.is_empty() { return max + 1; } + + match sorted_marks.binary_search(&offset) { + Ok(mark_before_index) => if mark_before_index == sorted_marks.len() - 1 { + max + 1 + } else { + sorted_marks[mark_before_index + 1] + }, + Err(mark_after_index) => { + if mark_after_index == sorted_marks.len() { + max + 1 + } else { + sorted_marks[mark_after_index] + } + }, + } +} diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs index 54599ca..fec54ac 100644 --- a/src/buffer/mod.rs +++ b/src/buffer/mod.rs @@ -40,7 +40,7 @@ pub enum Mode { #[derive(Clone, Copy, Hash, PartialEq, Eq)] pub enum PartialAction { - Goto, View, Replace, Space, Repeat + Goto, View, Replace, Space, Repeat, To } impl Mode { @@ -69,6 +69,7 @@ impl PartialAction { Self::Replace => "r", Self::Space => "␠", Self::Repeat => "×", + Self::To => "t", } } } diff --git a/src/config.rs b/src/config.rs index d2711e7..611ea80 100644 --- a/src/config.rs +++ b/src/config.rs @@ -107,6 +107,7 @@ impl Default for Config { ("r".try_into().unwrap(), BufferAction::Replace.into()), (" ".try_into().unwrap(), BufferAction::Space.into()), ("*".try_into().unwrap(), BufferAction::Repeat.into()), + ("t".try_into().unwrap(), BufferAction::To.into()), ("i".try_into().unwrap(), CursorAction::MoveByteUp.into()), ("k".try_into().unwrap(), CursorAction::MoveByteDown.into()), @@ -207,6 +208,11 @@ impl Default for Config { ("C".try_into().unwrap(), BufferAction::CopySelectionOnNextLine.into()), ].into()), + (Some(PartialAction::To), [ + ("m".try_into().unwrap(), BufferAction::ExtendToMark.into()), + ("0".try_into().unwrap(), BufferAction::ExtendToNull.into()), + ("f".try_into().unwrap(), BufferAction::ExtendToFF.into()), + ].into()), ].into()), (Mode::Select, [ (None, [ @@ -220,6 +226,7 @@ impl Default for Config { ("r".try_into().unwrap(), BufferAction::Replace.into()), (" ".try_into().unwrap(), BufferAction::Space.into()), ("*".try_into().unwrap(), BufferAction::Repeat.into()), + ("t".try_into().unwrap(), BufferAction::To.into()), ("i".try_into().unwrap(), CursorAction::ExtendByteUp.into()), ("k".try_into().unwrap(), CursorAction::ExtendByteDown.into()), @@ -309,6 +316,11 @@ impl Default for Config { ("C".try_into().unwrap(), BufferAction::CopySelectionOnNextLine.into()), ].into()), + (Some(PartialAction::To), [ + ("m".try_into().unwrap(), BufferAction::ExtendToMark.into()), + ("0".try_into().unwrap(), BufferAction::ExtendToNull.into()), + ("f".try_into().unwrap(), BufferAction::ExtendToFF.into()), + ].into()), ].into()) ].into() } diff --git a/src/main.rs b/src/main.rs index 301ac4c..d1a5fc3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,9 +26,6 @@ const LINES_OF_PADDING: usize = 5; const BYTES_OF_PADDING: usize = LINES_OF_PADDING * BYTES_PER_LINE; // TODO: -// - extend to mark (tm?) -// - t0 can be to next null -// - tf can be to next FF // - inspect selection // - resizing can move the cursor off the screen // - tab bar overflow