First steps towards multiple cursor support.

This commit is contained in:
Nathan Vegdahl 2015-01-13 00:03:16 -08:00
parent fc194bef09
commit 9a22812cf1
3 changed files with 207 additions and 118 deletions

View File

@ -44,7 +44,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 cursor: Cursor, pub cursors: Vec<Cursor>,
} }
@ -58,7 +58,7 @@ impl Editor {
dirty: false, dirty: false,
view_dim: (0, 0), view_dim: (0, 0),
view_pos: (0, 0), view_pos: (0, 0),
cursor: Cursor::new(), cursors: vec!(Cursor::new()),
} }
} }
@ -75,7 +75,7 @@ impl Editor {
dirty: false, dirty: false,
view_dim: (0, 0), view_dim: (0, 0),
view_pos: (0, 0), view_pos: (0, 0),
cursor: Cursor::new(), cursors: vec!(Cursor::new()),
}; };
ed.auto_detect_indentation_style(); ed.auto_detect_indentation_style();
@ -190,10 +190,12 @@ impl Editor {
pub fn undo(&mut self) { pub fn undo(&mut self) {
// TODO: handle multiple cursors properly
if let Some(pos) = self.buffer.undo() { if let Some(pos) = self.buffer.undo() {
self.cursor.range.0 = pos; self.cursors.truncate(1);
self.cursor.range.1 = pos; self.cursors[0].range.0 = pos;
self.cursor.update_vis_start(&(self.buffer)); self.cursors[0].range.1 = pos;
self.cursors[0].update_vis_start(&(self.buffer));
self.move_view_to_cursor(); self.move_view_to_cursor();
@ -203,10 +205,12 @@ impl Editor {
pub fn redo(&mut self) { pub fn redo(&mut self) {
// TODO: handle multiple cursors properly
if let Some(pos) = self.buffer.redo() { if let Some(pos) = self.buffer.redo() {
self.cursor.range.0 = pos; self.cursors.truncate(1);
self.cursor.range.1 = pos; self.cursors[0].range.0 = pos;
self.cursor.update_vis_start(&(self.buffer)); self.cursors[0].range.1 = pos;
self.cursors[0].update_vis_start(&(self.buffer));
self.move_view_to_cursor(); self.move_view_to_cursor();
@ -217,7 +221,10 @@ impl Editor {
/// Moves the editor's view the minimum amount to show the cursor /// Moves the editor's view the minimum amount to show the cursor
pub fn move_view_to_cursor(&mut self) { pub fn move_view_to_cursor(&mut self) {
let (v, h) = self.buffer.index_to_v2d(self.cursor.range.0); // TODO: handle multiple cursors properly. Should only move if
// there are no cursors currently in view, and should jump to
// the closest cursor.
let (v, h) = self.buffer.index_to_v2d(self.cursors[0].range.0);
// Horizontal // Horizontal
if h < self.view_pos.1 { if h < self.view_pos.1 {
@ -238,15 +245,21 @@ impl Editor {
pub fn insert_text_at_cursor(&mut self, text: &str) { pub fn insert_text_at_cursor(&mut self, text: &str) {
let str_len = grapheme_count(text); let str_len = grapheme_count(text);
let mut offset = 0;
// Insert text for c in self.cursors.as_mut_slice().iter_mut() {
self.buffer.insert_text(text, self.cursor.range.0); // Insert text
self.dirty = true; self.buffer.insert_text(text, c.range.0 + offset);
self.dirty = true;
// Move cursor
self.cursor.range.0 += str_len; // Move cursor
self.cursor.range.1 += str_len; c.range.0 += str_len + offset;
self.cursor.update_vis_start(&(self.buffer)); c.range.1 += str_len + offset;
c.update_vis_start(&(self.buffer));
// Update offset
offset += str_len;
}
// Adjust view // Adjust view
self.move_view_to_cursor(); self.move_view_to_cursor();
@ -254,21 +267,32 @@ impl Editor {
pub fn insert_tab_at_cursor(&mut self) { pub fn insert_tab_at_cursor(&mut self) {
if self.soft_tabs { if self.soft_tabs {
// Figure out how many spaces to insert let mut offset = 0;
let (_, vis_pos) = self.buffer.index_to_v2d(self.cursor.range.0);
let next_tab_stop = ((vis_pos / self.buffer.tab_width) + 1) * self.buffer.tab_width;
let space_count = min(next_tab_stop - vis_pos, 8);
for c in self.cursors.as_mut_slice().iter_mut() {
// Insert spaces // Update cursor with offset
let space_strs = ["", " ", " ", " ", " ", " ", " ", " ", " "]; c.range.0 += offset;
self.buffer.insert_text(space_strs[space_count], self.cursor.range.0); c.range.1 += offset;
self.dirty = true;
// Figure out how many spaces to insert
// Move cursor let (_, vis_pos) = self.buffer.index_to_v2d(c.range.0);
self.cursor.range.0 += space_count; let next_tab_stop = ((vis_pos / self.buffer.tab_width) + 1) * self.buffer.tab_width;
self.cursor.range.1 += space_count; let space_count = min(next_tab_stop - vis_pos, 8);
self.cursor.update_vis_start(&(self.buffer));
// Insert spaces
let space_strs = ["", " ", " ", " ", " ", " ", " ", " ", " "];
self.buffer.insert_text(space_strs[space_count], c.range.0);
self.dirty = true;
// Move cursor
c.range.0 += space_count;
c.range.1 += space_count;
c.update_vis_start(&(self.buffer));
// Update offset
offset += space_count;
}
// Adjust view // Adjust view
self.move_view_to_cursor(); self.move_view_to_cursor();
@ -289,64 +313,102 @@ impl Editor {
} }
pub fn remove_text_behind_cursor(&mut self, grapheme_count: usize) { pub fn remove_text_behind_cursor(&mut self, grapheme_count: usize) {
// Do nothing if there's nothing to delete. let mut offset = 0;
if self.cursor.range.0 == 0 {
return; for c in self.cursors.as_mut_slice().iter_mut() {
// Update cursor with offset
c.range.0 -= offset;
c.range.1 -= offset;
// Do nothing if there's nothing to delete.
if c.range.0 == 0 {
continue;
}
let len = min(c.range.0, grapheme_count);
// Remove text
self.buffer.remove_text_before(c.range.0, len);
self.dirty = true;
// Move cursor
c.range.0 -= len;
c.range.1 -= len;
c.update_vis_start(&(self.buffer));
// Update offset
offset -= len;
} }
let len = min(self.cursor.range.0, grapheme_count);
// Remove text
self.buffer.remove_text_before(self.cursor.range.0, len);
self.dirty = true;
// Move cursor
self.cursor.range.0 -= len;
self.cursor.range.1 -= len;
self.cursor.update_vis_start(&(self.buffer));
// 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) {
// Do nothing if there's nothing to delete. let mut offset = 0;
if self.cursor.range.0 == self.buffer.grapheme_count() {
return; for c in self.cursors.as_mut_slice().iter_mut() {
// Update cursor with offset
c.range.0 -= min(c.range.0, offset);
c.range.1 -= min(c.range.1, offset);
// Do nothing if there's nothing to delete.
if c.range.1 == self.buffer.grapheme_count() {
return;
}
let max_len = if self.buffer.grapheme_count() > c.range.1 {self.buffer.grapheme_count() - c.range.1} else {0};
let len = min(max_len, grapheme_count);
// Remove text
self.buffer.remove_text_after(c.range.1, len);
self.dirty = true;
// Move cursor
c.update_vis_start(&(self.buffer));
// Update offset
offset -= len;
} }
let max_len = if self.buffer.grapheme_count() > self.cursor.range.1 {self.buffer.grapheme_count() - self.cursor.range.1} else {0};
let len = min(max_len, grapheme_count);
// Remove text
self.buffer.remove_text_after(self.cursor.range.1, len);
self.dirty = true;
// Move cursor
self.cursor.update_vis_start(&(self.buffer));
// 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) {
// If selection, remove text let mut offset = 0;
if self.cursor.range.0 < self.cursor.range.1 {
self.buffer.remove_text_before(self.cursor.range.0, self.cursor.range.1 - self.cursor.range.0);
self.dirty = true;
}
// Move cursor for c in self.cursors.as_mut_slice().iter_mut() {
self.cursor.range.1 = self.cursor.range.0; // Update cursor with offset
self.cursor.update_vis_start(&(self.buffer)); c.range.0 -= min(c.range.0, offset);
c.range.1 -= min(c.range.1, offset);
// If selection, remove text
if c.range.0 < c.range.1 {
let len = c.range.1 - c.range.0;
self.buffer.remove_text_before(c.range.0, c.range.1 - c.range.0);
self.dirty = true;
// Move cursor
c.range.1 = c.range.0;
// Update offset
offset += len;
}
c.update_vis_start(&(self.buffer));
}
// 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.cursor.range = (0, 0); self.cursors = vec!(Cursor::new());
self.cursor.update_vis_start(&(self.buffer));
self.cursors[0].range = (0, 0);
self.cursors[0].update_vis_start(&(self.buffer));
// Adjust view // Adjust view
self.move_view_to_cursor(); self.move_view_to_cursor();
@ -354,51 +416,60 @@ 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.cursor.range = (end, end);
self.cursor.update_vis_start(&(self.buffer)); self.cursors = vec!(Cursor::new());
self.cursors[0].range = (end, end);
self.cursors[0].update_vis_start(&(self.buffer));
// Adjust view // Adjust view
self.move_view_to_cursor(); self.move_view_to_cursor();
} }
pub fn cursor_left(&mut self, n: usize) { pub fn cursor_left(&mut self, n: usize) {
if self.cursor.range.0 >= n { for c in self.cursors.as_mut_slice().iter_mut() {
self.cursor.range.0 -= n; if c.range.0 >= n {
c.range.0 -= n;
}
else {
c.range.0 = 0;
}
c.range.1 = c.range.0;
c.update_vis_start(&(self.buffer));
} }
else {
self.cursor.range.0 = 0;
}
self.cursor.range.1 = self.cursor.range.0;
self.cursor.update_vis_start(&(self.buffer));
// Adjust view // Adjust view
self.move_view_to_cursor(); self.move_view_to_cursor();
} }
pub fn cursor_right(&mut self, n: usize) { pub fn cursor_right(&mut self, n: usize) {
self.cursor.range.1 += n; for c in self.cursors.as_mut_slice().iter_mut() {
c.range.1 += n;
if self.cursor.range.1 > self.buffer.grapheme_count() {
self.cursor.range.1 = self.buffer.grapheme_count(); if c.range.1 > self.buffer.grapheme_count() {
c.range.1 = self.buffer.grapheme_count();
}
c.range.0 = c.range.1;
c.update_vis_start(&(self.buffer));
} }
self.cursor.range.0 = self.cursor.range.1;
self.cursor.update_vis_start(&(self.buffer));
// Adjust view // Adjust view
self.move_view_to_cursor(); self.move_view_to_cursor();
} }
pub fn cursor_up(&mut self, n: usize) { pub fn cursor_up(&mut self, n: usize) {
let (v, _) = self.buffer.index_to_v2d(self.cursor.range.0); for c in self.cursors.as_mut_slice().iter_mut() {
let (v, _) = self.buffer.index_to_v2d(c.range.0);
if v >= n {
self.cursor.range.0 = self.buffer.v2d_to_index((v - n, self.cursor.vis_start)); if v >= n {
self.cursor.range.1 = self.cursor.range.0; c.range.0 = self.buffer.v2d_to_index((v - n, c.vis_start));
} c.range.1 = c.range.0;
else { }
self.cursor_to_beginning_of_buffer(); else {
c.range = (0, 0);
c.update_vis_start(&(self.buffer));
}
} }
// Adjust view // Adjust view
@ -406,14 +477,18 @@ impl Editor {
} }
pub fn cursor_down(&mut self, n: usize) { pub fn cursor_down(&mut self, n: usize) {
let (v, _) = self.buffer.index_to_v2d(self.cursor.range.0); for c in self.cursors.as_mut_slice().iter_mut() {
let (v, _) = self.buffer.index_to_v2d(c.range.0);
if v < (self.buffer.line_count() - n) {
self.cursor.range.0 = self.buffer.v2d_to_index((v + n, self.cursor.vis_start)); if v < (self.buffer.line_count() - n) {
self.cursor.range.1 = self.cursor.range.0; c.range.0 = self.buffer.v2d_to_index((v + n, c.vis_start));
} c.range.1 = c.range.0;
else { }
self.cursor_to_end_of_buffer(); else {
let end = self.buffer.grapheme_count();
c.range = (end, end);
c.update_vis_start(&(self.buffer));
}
} }
// Adjust view // Adjust view
@ -441,7 +516,6 @@ impl Editor {
} }
pub fn page_down(&mut self) { pub fn page_down(&mut self) {
// TODO
let nlc = self.buffer.line_count() - 1; let nlc = self.buffer.line_count() - 1;
if self.view_pos.0 < nlc { if self.view_pos.0 < nlc {
@ -468,8 +542,9 @@ impl Editor {
pub fn jump_to_line(&mut self, n: usize) { pub fn jump_to_line(&mut self, n: usize) {
let pos = self.buffer.line_col_to_index((n, 0)); let pos = self.buffer.line_col_to_index((n, 0));
let (v, _) = self.buffer.index_to_v2d(pos); let (v, _) = self.buffer.index_to_v2d(pos);
self.cursor.range.0 = self.buffer.v2d_to_index((v, self.cursor.vis_start)); self.cursors.truncate(1);
self.cursor.range.1 = self.cursor.range.0; self.cursors[0].range.0 = self.buffer.v2d_to_index((v, self.cursors[0].vis_start));
self.cursors[0].range.1 = self.cursors[0].range.0;
// Adjust view // Adjust view
self.move_view_to_cursor(); self.move_view_to_cursor();

View File

@ -301,8 +301,10 @@ impl TermUI {
self.rb.print(c1.1 + 1, c1.0, rustbox::RB_NORMAL, foreground, background, name.as_slice()); self.rb.print(c1.1 + 1, c1.0, rustbox::RB_NORMAL, foreground, background, name.as_slice());
// Percentage position in document // Percentage position in document
// TODO: use view instead of cursor for calculation if there is more
// than one cursor.
let percentage: usize = if editor.buffer.grapheme_count() > 0 { let percentage: usize = if editor.buffer.grapheme_count() > 0 {
(((editor.cursor.range.0 as f32) / (editor.buffer.grapheme_count() as f32)) * 100.0) as usize (((editor.cursors[0].range.0 as f32) / (editor.buffer.grapheme_count() as f32)) * 100.0) as usize
} }
else { else {
100 100
@ -355,11 +357,22 @@ impl TermUI {
grapheme_index = editor.buffer.v2d_to_index((vis_line_num, vis_col_num)); grapheme_index = editor.buffer.v2d_to_index((vis_line_num, vis_col_num));
for (g, pos, width) in g_iter { for (g, pos, width) in g_iter {
print_col_num = pos - editor.view_pos.1; print_col_num = pos - editor.view_pos.1;
// Check if the character is within a cursor
let mut at_cursor = false;
for c in editor.cursors.as_slice().iter() {
if grapheme_index >= c.range.0 && grapheme_index <= c.range.1 {
at_cursor = true;
}
}
// Print to screen
if is_line_ending(g) { if is_line_ending(g) {
if grapheme_index == editor.cursor.range.0 { if at_cursor {
self.rb.print(print_col_num, print_line_num, rustbox::RB_NORMAL, Color::Black, Color::White, " "); self.rb.print(print_col_num, print_line_num, rustbox::RB_NORMAL, Color::Black, Color::White, " ");
} }
} }
@ -368,12 +381,12 @@ impl TermUI {
self.rb.print(i, print_line_num, rustbox::RB_NORMAL, Color::White, Color::Black, " "); self.rb.print(i, print_line_num, rustbox::RB_NORMAL, Color::White, Color::Black, " ");
} }
if grapheme_index == editor.cursor.range.0 { if at_cursor {
self.rb.print(print_col_num, print_line_num, rustbox::RB_NORMAL, Color::Black, Color::White, " "); self.rb.print(print_col_num, print_line_num, rustbox::RB_NORMAL, Color::Black, Color::White, " ");
} }
} }
else { else {
if grapheme_index == editor.cursor.range.0 { if at_cursor {
self.rb.print(print_col_num, print_line_num, rustbox::RB_NORMAL, Color::Black, Color::White, g); self.rb.print(print_col_num, print_line_num, rustbox::RB_NORMAL, Color::Black, Color::White, g);
} }
else { else {
@ -403,15 +416,17 @@ impl TermUI {
} }
} }
// Print cursor 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.
if editor.cursor.range.0 >= editor.buffer.grapheme_count() { for c in editor.cursors.as_slice().iter() {
let vis_cursor_pos = editor.buffer.index_to_v2d(editor.cursor.range.0); if c.range.0 >= editor.buffer.grapheme_count() {
if (vis_cursor_pos.0 >= editor.view_pos.0) && (vis_cursor_pos.1 >= editor.view_pos.1) { let vis_cursor_pos = editor.buffer.index_to_v2d(c.range.0);
let print_cursor_pos = (vis_cursor_pos.0 - editor.view_pos.0 + c1.0, vis_cursor_pos.1 - editor.view_pos.1 + c1.1); if (vis_cursor_pos.0 >= editor.view_pos.0) && (vis_cursor_pos.1 >= editor.view_pos.1) {
let print_cursor_pos = (vis_cursor_pos.0 - editor.view_pos.0 + c1.0, vis_cursor_pos.1 - editor.view_pos.1 + c1.1);
if print_cursor_pos.0 >= c1.0 && print_cursor_pos.0 <= c2.0 && print_cursor_pos.1 >= c1.1 && print_cursor_pos.1 <= c2.1 {
self.rb.print(print_cursor_pos.1, print_cursor_pos.0, rustbox::RB_NORMAL, Color::Black, Color::White, " "); if print_cursor_pos.0 >= c1.0 && print_cursor_pos.0 <= c2.0 && print_cursor_pos.1 >= c1.1 && print_cursor_pos.1 <= c2.1 {
self.rb.print(print_cursor_pos.1, print_cursor_pos.0, rustbox::RB_NORMAL, Color::Black, Color::White, " ");
}
} }
} }
} }

View File

@ -7,7 +7,6 @@
- Persistent infinite undo - Persistent infinite undo
- Multiple cursors - Multiple cursors
- "Projects" - "Projects"
- Move undo/redo functionality into Editor instead of Buffer
- Clean up text buffer interface: - Clean up text buffer interface:
- //Editing (these are undoable): - //Editing (these are undoable):