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 std::{cmp::min, convert::identity, fs::File, io::Write, iter, mem::{replace, swap}};
|
||||||
use crate::{BYTES_PER_LINE, app::WindowSize, buffer::{Buffer, Mode, PartialAction}, edit_action::EditAction};
|
use crate::{BYTES_PER_LINE, app::WindowSize, buffer::{Buffer, Mode, PartialAction}, cursor::Cursor, edit_action::EditAction};
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
@@ -60,6 +60,13 @@ pub enum Action {
|
|||||||
|
|
||||||
PreviousBuffer,
|
PreviousBuffer,
|
||||||
NextBuffer,
|
NextBuffer,
|
||||||
|
|
||||||
|
CopySelectionOnNextLine,
|
||||||
|
|
||||||
|
RotateSelectionsBackward,
|
||||||
|
RotateSelectionsForward,
|
||||||
|
|
||||||
|
KeepPrimarySelection,
|
||||||
}
|
}
|
||||||
|
|
||||||
// actions that act on the app as a whole, not just one buffer
|
// 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::PreviousBuffer => return Some(AppAction::PreviousBuffer),
|
||||||
Action::NextBuffer => return Some(AppAction::NextBuffer),
|
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
|
None
|
||||||
@@ -596,6 +610,70 @@ impl Buffer {
|
|||||||
self.time_traveling.unwrap_or(self.edit_history.len())
|
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
|
// helpers
|
||||||
|
|||||||
+1
-3
@@ -171,11 +171,9 @@ impl Buffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn combine_cursors_if_overlapping(&mut self) {
|
pub fn combine_cursors_if_overlapping(&mut self) {
|
||||||
if self.cursors.is_empty() { return; }
|
|
||||||
|
|
||||||
let mut index = 0;
|
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 &&
|
while index < self.cursors.len() - 1 &&
|
||||||
self.cursors[index].range().is_overlapping(
|
self.cursors[index].range().is_overlapping(
|
||||||
&self.cursors[index + 1].range())
|
&self.cursors[index + 1].range())
|
||||||
|
|||||||
+10
-3
@@ -221,20 +221,27 @@ mod hex {
|
|||||||
|
|
||||||
let span = SPAN_FOR_BYTE[byte as usize].clone();
|
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 {
|
let head_color = match self.mode {
|
||||||
Mode::Select => Color::Yellow,
|
Mode::Select => Color::Yellow,
|
||||||
_ => Color::Gray
|
_ => Color::Gray
|
||||||
};
|
};
|
||||||
|
|
||||||
match iter::once(&self.primary_cursor)
|
match place_in_cursor {
|
||||||
.chain(&self.cursors)
|
InCursor::Head => span.bg(head_color),
|
||||||
|
InCursor::Rest => span.bg(Color::select_grey()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match self.cursors
|
||||||
|
.iter()
|
||||||
.find_map(|cursor| cursor.contains(address))
|
.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()),
|
Some(InCursor::Rest) => span.bg(Color::select_grey()),
|
||||||
None => span,
|
None => span,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn render_large_space_before(&self, address: usize) -> Span<'static> {
|
fn render_large_space_before(&self, address: usize) -> Span<'static> {
|
||||||
if iter::once(&self.primary_cursor)
|
if iter::once(&self.primary_cursor)
|
||||||
|
|||||||
@@ -141,6 +141,13 @@ impl Default for Config {
|
|||||||
|
|
||||||
("C-j".try_into().unwrap(), Action::PreviousBuffer),
|
("C-j".try_into().unwrap(), Action::PreviousBuffer),
|
||||||
("C-l".try_into().unwrap(), Action::NextBuffer),
|
("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()),
|
].into()),
|
||||||
(Some(PartialAction::Goto), [
|
(Some(PartialAction::Goto), [
|
||||||
("j".try_into().unwrap(), Action::GotoLineStart),
|
("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::Undo),
|
||||||
("U".try_into().unwrap(), Action::Redo),
|
("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()),
|
].into()),
|
||||||
(Some(PartialAction::Space), [
|
(Some(PartialAction::Space), [
|
||||||
("w".try_into().unwrap(), Action::Save),
|
("w".try_into().unwrap(), Action::Save),
|
||||||
|
|||||||
Reference in New Issue
Block a user