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:
Nathan Vegdahl 2015-01-18 15:11:49 -08:00
parent 30ad0c2ea4
commit ac784077f0
4 changed files with 159 additions and 47 deletions

View File

@ -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
View 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])
}
}

View File

@ -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) {

View File

@ -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) {