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 font::Font;
use self::line::{Line, LineEnding};
use self::node::{BufferNode, BufferNodeGraphemeIter, BufferNodeLineIter};
use self::undo_stack::{UndoStack};
@ -24,6 +24,7 @@ pub struct Buffer {
undo_stack: UndoStack,
pub line_ending_type: LineEnding,
pub tab_width: usize,
pub font: Option<Font>,
}
@ -34,6 +35,7 @@ impl Buffer {
undo_stack: UndoStack::new(),
line_ending_type: LineEnding::LF,
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 files::{load_file_to_buffer, save_buffer_to_file};
use string_utils::grapheme_count;
use self::cursor::{Cursor, CursorSet};
/// 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;
}
}
mod cursor;
pub struct Editor {
@ -44,7 +21,7 @@ pub struct Editor {
pub view_pos: (usize, usize), // (line, col)
// The editing cursor position
pub cursors: Vec<Cursor>,
pub cursors: CursorSet,
}
@ -58,7 +35,7 @@ impl Editor {
dirty: false,
view_dim: (0, 0),
view_pos: (0, 0),
cursors: vec!(Cursor::new()),
cursors: CursorSet::new(),
}
}
@ -75,14 +52,15 @@ impl Editor {
dirty: false,
view_dim: (0, 0),
view_pos: (0, 0),
cursors: vec!(Cursor::new()),
cursors: CursorSet::new(),
};
// // For multiple-cursor testing
// ed.cursors.push(Cursor::new());
// ed.cursors[1].range.0 = 30;
// ed.cursors[1].range.1 = 30;
// ed.cursors[1].update_vis_start(&(ed.buffer));
// For multiple-cursor testing
let mut cur = Cursor::new();
cur.range.0 = 30;
cur.range.1 = 30;
cur.update_vis_start(&(ed.buffer));
ed.cursors.add_cursor(cur);
ed.auto_detect_indentation_style();
@ -206,6 +184,8 @@ impl Editor {
self.move_view_to_cursor();
self.dirty = true;
self.cursors.make_consistent();
}
}
@ -221,6 +201,8 @@ impl Editor {
self.move_view_to_cursor();
self.dirty = true;
self.cursors.make_consistent();
}
}
@ -250,10 +232,12 @@ impl Editor {
}
pub fn insert_text_at_cursor(&mut self, text: &str) {
self.cursors.make_consistent();
let str_len = grapheme_count(text);
let mut offset = 0;
for c in self.cursors.as_mut_slice().iter_mut() {
for c in self.cursors.iter_mut() {
// Insert text
self.buffer.insert_text(text, c.range.0 + offset);
self.dirty = true;
@ -272,10 +256,12 @@ impl Editor {
}
pub fn insert_tab_at_cursor(&mut self) {
self.cursors.make_consistent();
if self.soft_tabs {
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
c.range.0 += offset;
c.range.1 += offset;
@ -319,9 +305,11 @@ impl Editor {
}
pub fn remove_text_behind_cursor(&mut self, grapheme_count: usize) {
self.cursors.make_consistent();
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
c.range.0 -= offset;
c.range.1 -= offset;
@ -346,14 +334,18 @@ impl Editor {
offset += len;
}
self.cursors.make_consistent();
// Adjust view
self.move_view_to_cursor();
}
pub fn remove_text_in_front_of_cursor(&mut self, grapheme_count: usize) {
self.cursors.make_consistent();
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
c.range.0 -= min(c.range.0, offset);
c.range.1 -= min(c.range.1, offset);
@ -377,14 +369,18 @@ impl Editor {
offset += len;
}
self.cursors.make_consistent();
// Adjust view
self.move_view_to_cursor();
}
pub fn remove_text_inside_cursor(&mut self) {
self.cursors.make_consistent();
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
c.range.0 -= min(c.range.0, offset);
c.range.1 -= min(c.range.1, offset);
@ -406,12 +402,14 @@ impl Editor {
c.update_vis_start(&(self.buffer));
}
self.cursors.make_consistent();
// Adjust view
self.move_view_to_cursor();
}
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].update_vis_start(&(self.buffer));
@ -423,7 +421,7 @@ impl Editor {
pub fn cursor_to_end_of_buffer(&mut self) {
let end = self.buffer.grapheme_count();
self.cursors = vec!(Cursor::new());
self.cursors = CursorSet::new();
self.cursors[0].range = (end, end);
self.cursors[0].update_vis_start(&(self.buffer));
@ -432,7 +430,7 @@ impl Editor {
}
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 {
c.range.0 -= n;
}
@ -449,7 +447,7 @@ impl Editor {
}
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;
if c.range.1 > self.buffer.grapheme_count() {
@ -465,7 +463,7 @@ impl Editor {
}
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);
if v >= n {
@ -483,7 +481,7 @@ impl Editor {
}
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);
if v < (self.buffer.line_count() - n) {

View File

@ -364,7 +364,7 @@ impl TermUI {
// Check if the character is within a cursor
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 {
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
// already.
for c in editor.cursors.as_slice().iter() {
for c in editor.cursors.iter() {
if c.range.0 >= editor.buffer.grapheme_count() {
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) {