multi-cursor actions
This commit is contained in:
+80
-2
@@ -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<Cursor> = 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
|
||||
|
||||
+1
-3
@@ -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())
|
||||
|
||||
+10
-3
@@ -221,20 +221,27 @@ mod hex {
|
||||
|
||||
let span = SPAN_FOR_BYTE[byte as usize].clone();
|
||||
|
||||
if let Some(place_in_cursor) = self.primary_cursor.contains(address) {
|
||||
let head_color = match self.mode {
|
||||
Mode::Select => Color::Yellow,
|
||||
_ => Color::Gray
|
||||
};
|
||||
|
||||
match iter::once(&self.primary_cursor)
|
||||
.chain(&self.cursors)
|
||||
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.bg(head_color),
|
||||
Some(InCursor::Head) => span.on_gray(),
|
||||
Some(InCursor::Rest) => span.bg(Color::select_grey()),
|
||||
None => span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_large_space_before(&self, address: usize) -> Span<'static> {
|
||||
if iter::once(&self.primary_cursor)
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user