Split cursor code into separate file.
There is now a CursorSet type that handles multiple cursors, and makes it convenient to maintain the necessary invariants expected by the rest of the code for multi-cursor editing.
This commit is contained in:
parent
30ad0c2ea4
commit
ac784077f0
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
|
use font::Font;
|
||||||
use self::line::{Line, LineEnding};
|
use self::line::{Line, LineEnding};
|
||||||
use self::node::{BufferNode, BufferNodeGraphemeIter, BufferNodeLineIter};
|
use self::node::{BufferNode, BufferNodeGraphemeIter, BufferNodeLineIter};
|
||||||
use self::undo_stack::{UndoStack};
|
use self::undo_stack::{UndoStack};
|
||||||
|
@ -24,6 +24,7 @@ pub struct Buffer {
|
||||||
undo_stack: UndoStack,
|
undo_stack: UndoStack,
|
||||||
pub line_ending_type: LineEnding,
|
pub line_ending_type: LineEnding,
|
||||||
pub tab_width: usize,
|
pub tab_width: usize,
|
||||||
|
pub font: Option<Font>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,6 +35,7 @@ impl Buffer {
|
||||||
undo_stack: UndoStack::new(),
|
undo_stack: UndoStack::new(),
|
||||||
line_ending_type: LineEnding::LF,
|
line_ending_type: LineEnding::LF,
|
||||||
tab_width: 4,
|
tab_width: 4,
|
||||||
|
font: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
112
src/editor/cursor.rs
Normal file
112
src/editor/cursor.rs
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use std::slice::{Iter, IterMut};
|
||||||
|
use std::ops::{Index, IndexMut};
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
use buffer::Buffer;
|
||||||
|
|
||||||
|
|
||||||
|
/// A text cursor. Also represents selections when range.0 != range.1.
|
||||||
|
///
|
||||||
|
/// `range` is a pair of 1d grapheme indexes into the text.
|
||||||
|
///
|
||||||
|
/// `vis_start` is the visual 2d horizontal position of the cursor. This
|
||||||
|
/// doesn't affect editing operations at all, but is used for cursor movement.
|
||||||
|
#[derive(Copy)]
|
||||||
|
pub struct Cursor {
|
||||||
|
pub range: (usize, usize), // start, end
|
||||||
|
pub vis_start: usize, // start
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cursor {
|
||||||
|
pub fn new() -> Cursor {
|
||||||
|
Cursor {
|
||||||
|
range: (0, 0),
|
||||||
|
vis_start: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_vis_start(&mut self, buf: &Buffer) {
|
||||||
|
let (_, h) = buf.index_to_v2d(self.range.0);
|
||||||
|
self.vis_start = h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// A collection of cursors, managed to always be in a consistent
|
||||||
|
/// state for multi-cursor editing.
|
||||||
|
pub struct CursorSet {
|
||||||
|
cursors: Vec<Cursor>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl CursorSet {
|
||||||
|
pub fn new() -> CursorSet {
|
||||||
|
CursorSet {
|
||||||
|
cursors: vec!(Cursor::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_cursor(&mut self, cursor: Cursor) {
|
||||||
|
self.cursors.push(cursor);
|
||||||
|
self.make_consistent();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn truncate(&mut self, len: usize) {
|
||||||
|
self.cursors.truncate(len);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter<'a>(&'a self) -> Iter<'a, Cursor> {
|
||||||
|
self.cursors.as_slice().iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn iter_mut<'a>(&'a mut self) -> IterMut<'a, Cursor> {
|
||||||
|
self.cursors.as_mut_slice().iter_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_consistent(&mut self) {
|
||||||
|
// First, sort the cursors by starting position
|
||||||
|
self.cursors.sort_by(|a, b| {
|
||||||
|
if a.range.0 < b.range.0 {
|
||||||
|
Ordering::Less
|
||||||
|
}
|
||||||
|
else if a.range.0 > b.range.0 {
|
||||||
|
Ordering::Greater
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Ordering::Equal
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Next, merge overlapping cursors
|
||||||
|
let mut i = 0;
|
||||||
|
while i < (self.cursors.len()-1) {
|
||||||
|
if self.cursors[i].range.1 >= self.cursors[i+1].range.0 {
|
||||||
|
self.cursors[i].range.1 = self.cursors[i+1].range.1;
|
||||||
|
self.cursors.remove(i+1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Index<usize> for CursorSet {
|
||||||
|
type Output = Cursor;
|
||||||
|
|
||||||
|
fn index<'a>(&'a self, _index: &usize) -> &'a Cursor {
|
||||||
|
&(self.cursors[*_index])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl IndexMut<usize> for CursorSet {
|
||||||
|
type Output = Cursor;
|
||||||
|
|
||||||
|
fn index_mut<'a>(&'a mut self, _index: &usize) -> &'a mut Cursor {
|
||||||
|
&mut (self.cursors[*_index])
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,32 +5,9 @@ use std::path::Path;
|
||||||
use std::cmp::min;
|
use std::cmp::min;
|
||||||
use files::{load_file_to_buffer, save_buffer_to_file};
|
use files::{load_file_to_buffer, save_buffer_to_file};
|
||||||
use string_utils::grapheme_count;
|
use string_utils::grapheme_count;
|
||||||
|
use self::cursor::{Cursor, CursorSet};
|
||||||
|
|
||||||
|
mod cursor;
|
||||||
/// A text cursor. Also represents selections when range.0 != range.1.
|
|
||||||
///
|
|
||||||
/// `range` is a pair of 1d grapheme indexes into the text.
|
|
||||||
///
|
|
||||||
/// `vis_start` is the visual 2d horizontal position of the cursor. This
|
|
||||||
/// doesn't affect editing operations at all, but is used for cursor movement.
|
|
||||||
pub struct Cursor {
|
|
||||||
pub range: (usize, usize), // start, end
|
|
||||||
pub vis_start: usize, // start
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Cursor {
|
|
||||||
pub fn new() -> Cursor {
|
|
||||||
Cursor {
|
|
||||||
range: (0, 0),
|
|
||||||
vis_start: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_vis_start(&mut self, buf: &Buffer) {
|
|
||||||
let (_, h) = buf.index_to_v2d(self.range.0);
|
|
||||||
self.vis_start = h;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub struct Editor {
|
pub struct Editor {
|
||||||
|
@ -44,7 +21,7 @@ pub struct Editor {
|
||||||
pub view_pos: (usize, usize), // (line, col)
|
pub view_pos: (usize, usize), // (line, col)
|
||||||
|
|
||||||
// The editing cursor position
|
// The editing cursor position
|
||||||
pub cursors: Vec<Cursor>,
|
pub cursors: CursorSet,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -58,7 +35,7 @@ impl Editor {
|
||||||
dirty: false,
|
dirty: false,
|
||||||
view_dim: (0, 0),
|
view_dim: (0, 0),
|
||||||
view_pos: (0, 0),
|
view_pos: (0, 0),
|
||||||
cursors: vec!(Cursor::new()),
|
cursors: CursorSet::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,14 +52,15 @@ impl Editor {
|
||||||
dirty: false,
|
dirty: false,
|
||||||
view_dim: (0, 0),
|
view_dim: (0, 0),
|
||||||
view_pos: (0, 0),
|
view_pos: (0, 0),
|
||||||
cursors: vec!(Cursor::new()),
|
cursors: CursorSet::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// // For multiple-cursor testing
|
// For multiple-cursor testing
|
||||||
// ed.cursors.push(Cursor::new());
|
let mut cur = Cursor::new();
|
||||||
// ed.cursors[1].range.0 = 30;
|
cur.range.0 = 30;
|
||||||
// ed.cursors[1].range.1 = 30;
|
cur.range.1 = 30;
|
||||||
// ed.cursors[1].update_vis_start(&(ed.buffer));
|
cur.update_vis_start(&(ed.buffer));
|
||||||
|
ed.cursors.add_cursor(cur);
|
||||||
|
|
||||||
ed.auto_detect_indentation_style();
|
ed.auto_detect_indentation_style();
|
||||||
|
|
||||||
|
@ -206,6 +184,8 @@ impl Editor {
|
||||||
self.move_view_to_cursor();
|
self.move_view_to_cursor();
|
||||||
|
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
|
|
||||||
|
self.cursors.make_consistent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,6 +201,8 @@ impl Editor {
|
||||||
self.move_view_to_cursor();
|
self.move_view_to_cursor();
|
||||||
|
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
|
|
||||||
|
self.cursors.make_consistent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,10 +232,12 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_text_at_cursor(&mut self, text: &str) {
|
pub fn insert_text_at_cursor(&mut self, text: &str) {
|
||||||
|
self.cursors.make_consistent();
|
||||||
|
|
||||||
let str_len = grapheme_count(text);
|
let str_len = grapheme_count(text);
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
|
|
||||||
for c in self.cursors.as_mut_slice().iter_mut() {
|
for c in self.cursors.iter_mut() {
|
||||||
// Insert text
|
// Insert text
|
||||||
self.buffer.insert_text(text, c.range.0 + offset);
|
self.buffer.insert_text(text, c.range.0 + offset);
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
|
@ -272,10 +256,12 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_tab_at_cursor(&mut self) {
|
pub fn insert_tab_at_cursor(&mut self) {
|
||||||
|
self.cursors.make_consistent();
|
||||||
|
|
||||||
if self.soft_tabs {
|
if self.soft_tabs {
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
|
|
||||||
for c in self.cursors.as_mut_slice().iter_mut() {
|
for c in self.cursors.iter_mut() {
|
||||||
// Update cursor with offset
|
// Update cursor with offset
|
||||||
c.range.0 += offset;
|
c.range.0 += offset;
|
||||||
c.range.1 += offset;
|
c.range.1 += offset;
|
||||||
|
@ -319,9 +305,11 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_text_behind_cursor(&mut self, grapheme_count: usize) {
|
pub fn remove_text_behind_cursor(&mut self, grapheme_count: usize) {
|
||||||
|
self.cursors.make_consistent();
|
||||||
|
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
|
|
||||||
for c in self.cursors.as_mut_slice().iter_mut() {
|
for c in self.cursors.iter_mut() {
|
||||||
// Update cursor with offset
|
// Update cursor with offset
|
||||||
c.range.0 -= offset;
|
c.range.0 -= offset;
|
||||||
c.range.1 -= offset;
|
c.range.1 -= offset;
|
||||||
|
@ -346,14 +334,18 @@ impl Editor {
|
||||||
offset += len;
|
offset += len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.cursors.make_consistent();
|
||||||
|
|
||||||
// Adjust view
|
// Adjust view
|
||||||
self.move_view_to_cursor();
|
self.move_view_to_cursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_text_in_front_of_cursor(&mut self, grapheme_count: usize) {
|
pub fn remove_text_in_front_of_cursor(&mut self, grapheme_count: usize) {
|
||||||
|
self.cursors.make_consistent();
|
||||||
|
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
|
|
||||||
for c in self.cursors.as_mut_slice().iter_mut() {
|
for c in self.cursors.iter_mut() {
|
||||||
// Update cursor with offset
|
// Update cursor with offset
|
||||||
c.range.0 -= min(c.range.0, offset);
|
c.range.0 -= min(c.range.0, offset);
|
||||||
c.range.1 -= min(c.range.1, offset);
|
c.range.1 -= min(c.range.1, offset);
|
||||||
|
@ -377,14 +369,18 @@ impl Editor {
|
||||||
offset += len;
|
offset += len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.cursors.make_consistent();
|
||||||
|
|
||||||
// Adjust view
|
// Adjust view
|
||||||
self.move_view_to_cursor();
|
self.move_view_to_cursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_text_inside_cursor(&mut self) {
|
pub fn remove_text_inside_cursor(&mut self) {
|
||||||
|
self.cursors.make_consistent();
|
||||||
|
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
|
|
||||||
for c in self.cursors.as_mut_slice().iter_mut() {
|
for c in self.cursors.iter_mut() {
|
||||||
// Update cursor with offset
|
// Update cursor with offset
|
||||||
c.range.0 -= min(c.range.0, offset);
|
c.range.0 -= min(c.range.0, offset);
|
||||||
c.range.1 -= min(c.range.1, offset);
|
c.range.1 -= min(c.range.1, offset);
|
||||||
|
@ -406,12 +402,14 @@ impl Editor {
|
||||||
c.update_vis_start(&(self.buffer));
|
c.update_vis_start(&(self.buffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.cursors.make_consistent();
|
||||||
|
|
||||||
// Adjust view
|
// Adjust view
|
||||||
self.move_view_to_cursor();
|
self.move_view_to_cursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cursor_to_beginning_of_buffer(&mut self) {
|
pub fn cursor_to_beginning_of_buffer(&mut self) {
|
||||||
self.cursors = vec!(Cursor::new());
|
self.cursors = CursorSet::new();
|
||||||
|
|
||||||
self.cursors[0].range = (0, 0);
|
self.cursors[0].range = (0, 0);
|
||||||
self.cursors[0].update_vis_start(&(self.buffer));
|
self.cursors[0].update_vis_start(&(self.buffer));
|
||||||
|
@ -423,7 +421,7 @@ impl Editor {
|
||||||
pub fn cursor_to_end_of_buffer(&mut self) {
|
pub fn cursor_to_end_of_buffer(&mut self) {
|
||||||
let end = self.buffer.grapheme_count();
|
let end = self.buffer.grapheme_count();
|
||||||
|
|
||||||
self.cursors = vec!(Cursor::new());
|
self.cursors = CursorSet::new();
|
||||||
self.cursors[0].range = (end, end);
|
self.cursors[0].range = (end, end);
|
||||||
self.cursors[0].update_vis_start(&(self.buffer));
|
self.cursors[0].update_vis_start(&(self.buffer));
|
||||||
|
|
||||||
|
@ -432,7 +430,7 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cursor_left(&mut self, n: usize) {
|
pub fn cursor_left(&mut self, n: usize) {
|
||||||
for c in self.cursors.as_mut_slice().iter_mut() {
|
for c in self.cursors.iter_mut() {
|
||||||
if c.range.0 >= n {
|
if c.range.0 >= n {
|
||||||
c.range.0 -= n;
|
c.range.0 -= n;
|
||||||
}
|
}
|
||||||
|
@ -449,7 +447,7 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cursor_right(&mut self, n: usize) {
|
pub fn cursor_right(&mut self, n: usize) {
|
||||||
for c in self.cursors.as_mut_slice().iter_mut() {
|
for c in self.cursors.iter_mut() {
|
||||||
c.range.1 += n;
|
c.range.1 += n;
|
||||||
|
|
||||||
if c.range.1 > self.buffer.grapheme_count() {
|
if c.range.1 > self.buffer.grapheme_count() {
|
||||||
|
@ -465,7 +463,7 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cursor_up(&mut self, n: usize) {
|
pub fn cursor_up(&mut self, n: usize) {
|
||||||
for c in self.cursors.as_mut_slice().iter_mut() {
|
for c in self.cursors.iter_mut() {
|
||||||
let (v, _) = self.buffer.index_to_v2d(c.range.0);
|
let (v, _) = self.buffer.index_to_v2d(c.range.0);
|
||||||
|
|
||||||
if v >= n {
|
if v >= n {
|
||||||
|
@ -483,7 +481,7 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cursor_down(&mut self, n: usize) {
|
pub fn cursor_down(&mut self, n: usize) {
|
||||||
for c in self.cursors.as_mut_slice().iter_mut() {
|
for c in self.cursors.iter_mut() {
|
||||||
let (v, _) = self.buffer.index_to_v2d(c.range.0);
|
let (v, _) = self.buffer.index_to_v2d(c.range.0);
|
||||||
|
|
||||||
if v < (self.buffer.line_count() - n) {
|
if v < (self.buffer.line_count() - n) {
|
|
@ -364,7 +364,7 @@ impl TermUI {
|
||||||
|
|
||||||
// Check if the character is within a cursor
|
// Check if the character is within a cursor
|
||||||
let mut at_cursor = false;
|
let mut at_cursor = false;
|
||||||
for c in editor.cursors.as_slice().iter() {
|
for c in editor.cursors.iter() {
|
||||||
if grapheme_index >= c.range.0 && grapheme_index <= c.range.1 {
|
if grapheme_index >= c.range.0 && grapheme_index <= c.range.1 {
|
||||||
at_cursor = true;
|
at_cursor = true;
|
||||||
}
|
}
|
||||||
|
@ -418,7 +418,7 @@ impl TermUI {
|
||||||
|
|
||||||
// Print cursor(s) if it's at the end of the text, and thus wasn't printed
|
// Print cursor(s) if it's at the end of the text, and thus wasn't printed
|
||||||
// already.
|
// already.
|
||||||
for c in editor.cursors.as_slice().iter() {
|
for c in editor.cursors.iter() {
|
||||||
if c.range.0 >= editor.buffer.grapheme_count() {
|
if c.range.0 >= editor.buffer.grapheme_count() {
|
||||||
let vis_cursor_pos = editor.buffer.index_to_v2d(c.range.0);
|
let vis_cursor_pos = editor.buffer.index_to_v2d(c.range.0);
|
||||||
if (vis_cursor_pos.0 >= editor.view_pos.0) && (vis_cursor_pos.1 >= editor.view_pos.1) {
|
if (vis_cursor_pos.0 >= editor.view_pos.0) && (vis_cursor_pos.1 >= editor.view_pos.1) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user