diff --git a/src/action.rs b/src/action.rs index daea92f..e57b036 100644 --- a/src/action.rs +++ b/src/action.rs @@ -1,5 +1,5 @@ -use std::{cmp::min, fs::File, io::Write, mem::replace}; -use crate::{BYTES_PER_LINE, app::WindowSize, buffer::{Buffer, Mode, PartialAction}, edit_action::EditAction}; +use std::{cmp::min, convert::identity, fs::File, io::Write, iter, mem::{replace, swap}}; +use crate::{BYTES_PER_LINE, app::WindowSize, buffer::{Buffer, Mode, PartialAction}, cursor::Cursor, edit_action::EditAction}; #[derive(Clone, Copy)] pub enum Action { @@ -60,6 +60,13 @@ pub enum Action { PreviousBuffer, NextBuffer, + + CopySelectionOnNextLine, + + RotateSelectionsBackward, + RotateSelectionsForward, + + KeepPrimarySelection, } // actions that act on the app as a whole, not just one buffer @@ -131,6 +138,13 @@ impl Buffer { Action::PreviousBuffer => return Some(AppAction::PreviousBuffer), Action::NextBuffer => return Some(AppAction::NextBuffer), + + Action::CopySelectionOnNextLine => self.copy_selection_on_next_line(), + + Action::RotateSelectionsBackward => self.rotate_selections_backward(), + Action::RotateSelectionsForward => self.rotate_selections_forward(), + + Action::KeepPrimarySelection => self.keep_primary_selection(), } None @@ -596,6 +610,70 @@ impl Buffer { self.time_traveling.unwrap_or(self.edit_history.len()) ); } + + fn copy_selection_on_next_line(&mut self) { + let new_cursors: Vec = iter::once(&self.primary_cursor) + .chain(&self.cursors) + .filter_map(|cursor| { + let number_of_lines_tall = (cursor.upper_bound() - cursor.lower_bound()) / BYTES_PER_LINE; + let offset_to_add = (number_of_lines_tall + 1) * BYTES_PER_LINE; + + if cursor.lower_bound() + offset_to_add < self.contents.len() { + Some( + Cursor { + head: min(cursor.head + offset_to_add, self.max_contents_index()), + tail: min(cursor.tail + offset_to_add, self.max_contents_index()) + } + ) + } else { + None + } + }) + .collect(); + + self.cursors.extend(new_cursors); + self.cursors.sort_by_key(|cursor| cursor.head); + + self.combine_cursors_if_overlapping(); + } + + fn rotate_selections_backward(&mut self) { + if self.cursors.is_empty() { return; } + + let next_cursor_index = self.cursors + .binary_search_by_key(&self.primary_cursor.head, |cursor| cursor.head) + .unwrap_or_else(identity); + + + if next_cursor_index == 0 { + let cursor_count = self.cursors.len(); + swap(&mut self.primary_cursor, &mut self.cursors[cursor_count - 1]); + + self.cursors.sort_by_key(|cursor| cursor.head); + } else { + swap(&mut self.primary_cursor, &mut self.cursors[next_cursor_index - 1]); + } + } + + fn rotate_selections_forward(&mut self) { + if self.cursors.is_empty() { return; } + + let next_cursor_index = self.cursors + .binary_search_by_key(&self.primary_cursor.head, |cursor| cursor.head) + .unwrap_or_else(identity); + + if next_cursor_index == self.cursors.len() { + swap(&mut self.primary_cursor, &mut self.cursors[0]); + + self.cursors.sort_by_key(|cursor| cursor.head); + } else { + swap(&mut self.primary_cursor, &mut self.cursors[next_cursor_index]); + } + } + + fn keep_primary_selection(&mut self) { + self.cursors.clear(); + } } // helpers diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs index 4eb5ec6..f4b1024 100644 --- a/src/buffer/mod.rs +++ b/src/buffer/mod.rs @@ -171,11 +171,9 @@ impl Buffer { } pub fn combine_cursors_if_overlapping(&mut self) { - if self.cursors.is_empty() { return; } - let mut index = 0; - while index < self.cursors.len() - 1 { + while !self.cursors.is_empty() && index < self.cursors.len() - 1 { while index < self.cursors.len() - 1 && self.cursors[index].range().is_overlapping( &self.cursors[index + 1].range()) diff --git a/src/buffer/widget.rs b/src/buffer/widget.rs index 1c1422d..f455eca 100644 --- a/src/buffer/widget.rs +++ b/src/buffer/widget.rs @@ -221,18 +221,25 @@ mod hex { let span = SPAN_FOR_BYTE[byte as usize].clone(); - let head_color = match self.mode { - Mode::Select => Color::Yellow, - _ => Color::Gray - }; - - match iter::once(&self.primary_cursor) - .chain(&self.cursors) - .find_map(|cursor| cursor.contains(address)) - { - Some(InCursor::Head) => span.bg(head_color), - Some(InCursor::Rest) => span.bg(Color::select_grey()), - None => span, + if let Some(place_in_cursor) = self.primary_cursor.contains(address) { + let head_color = match self.mode { + Mode::Select => Color::Yellow, + _ => Color::Gray + }; + + match place_in_cursor { + InCursor::Head => span.bg(head_color), + InCursor::Rest => span.bg(Color::select_grey()), + } + } else { + match self.cursors + .iter() + .find_map(|cursor| cursor.contains(address)) + { + Some(InCursor::Head) => span.on_gray(), + Some(InCursor::Rest) => span.bg(Color::select_grey()), + None => span, + } } } diff --git a/src/config.rs b/src/config.rs index 75db82f..a1de155 100644 --- a/src/config.rs +++ b/src/config.rs @@ -141,6 +141,13 @@ impl Default for Config { ("C-j".try_into().unwrap(), Action::PreviousBuffer), ("C-l".try_into().unwrap(), Action::NextBuffer), + + ("C".try_into().unwrap(), Action::CopySelectionOnNextLine), + + ("(".try_into().unwrap(), Action::RotateSelectionsBackward), + (")".try_into().unwrap(), Action::RotateSelectionsForward), + + (",".try_into().unwrap(), Action::KeepPrimarySelection), ].into()), (Some(PartialAction::Goto), [ ("j".try_into().unwrap(), Action::GotoLineStart), @@ -191,6 +198,13 @@ impl Default for Config { ("u".try_into().unwrap(), Action::Undo), ("U".try_into().unwrap(), Action::Redo), + + ("C".try_into().unwrap(), Action::CopySelectionOnNextLine), + + ("(".try_into().unwrap(), Action::RotateSelectionsBackward), + (")".try_into().unwrap(), Action::RotateSelectionsForward), + + (",".try_into().unwrap(), Action::KeepPrimarySelection), ].into()), (Some(PartialAction::Space), [ ("w".try_into().unwrap(), Action::Save),