diff --git a/src/editor.rs b/src/editor.rs index fde95a6..8a8f72d 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -44,7 +44,7 @@ pub struct Editor { pub view_pos: (usize, usize), // (line, col) // The editing cursor position - pub cursor: Cursor, + pub cursors: Vec, } @@ -58,7 +58,7 @@ impl Editor { dirty: false, view_dim: (0, 0), view_pos: (0, 0), - cursor: Cursor::new(), + cursors: vec!(Cursor::new()), } } @@ -75,7 +75,7 @@ impl Editor { dirty: false, view_dim: (0, 0), view_pos: (0, 0), - cursor: Cursor::new(), + cursors: vec!(Cursor::new()), }; ed.auto_detect_indentation_style(); @@ -190,10 +190,12 @@ impl Editor { pub fn undo(&mut self) { + // TODO: handle multiple cursors properly if let Some(pos) = self.buffer.undo() { - self.cursor.range.0 = pos; - self.cursor.range.1 = pos; - self.cursor.update_vis_start(&(self.buffer)); + self.cursors.truncate(1); + self.cursors[0].range.0 = pos; + self.cursors[0].range.1 = pos; + self.cursors[0].update_vis_start(&(self.buffer)); self.move_view_to_cursor(); @@ -203,10 +205,12 @@ impl Editor { pub fn redo(&mut self) { + // TODO: handle multiple cursors properly if let Some(pos) = self.buffer.redo() { - self.cursor.range.0 = pos; - self.cursor.range.1 = pos; - self.cursor.update_vis_start(&(self.buffer)); + self.cursors.truncate(1); + self.cursors[0].range.0 = pos; + self.cursors[0].range.1 = pos; + self.cursors[0].update_vis_start(&(self.buffer)); self.move_view_to_cursor(); @@ -217,7 +221,10 @@ impl Editor { /// Moves the editor's view the minimum amount to show the cursor 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 if h < self.view_pos.1 { @@ -238,15 +245,21 @@ impl Editor { pub fn insert_text_at_cursor(&mut self, text: &str) { let str_len = grapheme_count(text); + let mut offset = 0; - // Insert text - self.buffer.insert_text(text, self.cursor.range.0); - self.dirty = true; - - // Move cursor - self.cursor.range.0 += str_len; - self.cursor.range.1 += str_len; - self.cursor.update_vis_start(&(self.buffer)); + for c in self.cursors.as_mut_slice().iter_mut() { + // Insert text + self.buffer.insert_text(text, c.range.0 + offset); + self.dirty = true; + + // Move cursor + c.range.0 += str_len + offset; + c.range.1 += str_len + offset; + c.update_vis_start(&(self.buffer)); + + // Update offset + offset += str_len; + } // Adjust view self.move_view_to_cursor(); @@ -254,21 +267,32 @@ impl Editor { pub fn insert_tab_at_cursor(&mut self) { if self.soft_tabs { - // Figure out how many spaces to insert - 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); + let mut offset = 0; - - // Insert spaces - let space_strs = ["", " ", " ", " ", " ", " ", " ", " ", " "]; - self.buffer.insert_text(space_strs[space_count], self.cursor.range.0); - self.dirty = true; - - // Move cursor - self.cursor.range.0 += space_count; - self.cursor.range.1 += space_count; - self.cursor.update_vis_start(&(self.buffer)); + for c in self.cursors.as_mut_slice().iter_mut() { + // Update cursor with offset + c.range.0 += offset; + c.range.1 += offset; + + // Figure out how many spaces to insert + let (_, vis_pos) = self.buffer.index_to_v2d(c.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); + + + // 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 self.move_view_to_cursor(); @@ -289,64 +313,102 @@ impl Editor { } pub fn remove_text_behind_cursor(&mut self, grapheme_count: usize) { - // Do nothing if there's nothing to delete. - if self.cursor.range.0 == 0 { - return; + let mut offset = 0; + + 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 self.move_view_to_cursor(); } pub fn remove_text_in_front_of_cursor(&mut self, grapheme_count: usize) { - // Do nothing if there's nothing to delete. - if self.cursor.range.0 == self.buffer.grapheme_count() { - return; + let mut offset = 0; + + 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 self.move_view_to_cursor(); } pub fn remove_text_inside_cursor(&mut self) { - // If selection, remove text - 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; - } + let mut offset = 0; - // Move cursor - self.cursor.range.1 = self.cursor.range.0; - self.cursor.update_vis_start(&(self.buffer)); + 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); + + // 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 self.move_view_to_cursor(); } pub fn cursor_to_beginning_of_buffer(&mut self) { - self.cursor.range = (0, 0); - self.cursor.update_vis_start(&(self.buffer)); + self.cursors = vec!(Cursor::new()); + + self.cursors[0].range = (0, 0); + self.cursors[0].update_vis_start(&(self.buffer)); // Adjust view self.move_view_to_cursor(); @@ -354,51 +416,60 @@ impl Editor { pub fn cursor_to_end_of_buffer(&mut self) { 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 self.move_view_to_cursor(); } pub fn cursor_left(&mut self, n: usize) { - if self.cursor.range.0 >= n { - self.cursor.range.0 -= n; + for c in self.cursors.as_mut_slice().iter_mut() { + 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 self.move_view_to_cursor(); } pub fn cursor_right(&mut self, n: usize) { - self.cursor.range.1 += n; - - if self.cursor.range.1 > self.buffer.grapheme_count() { - self.cursor.range.1 = self.buffer.grapheme_count(); + for c in self.cursors.as_mut_slice().iter_mut() { + c.range.1 += n; + + 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 self.move_view_to_cursor(); } pub fn cursor_up(&mut self, n: usize) { - let (v, _) = self.buffer.index_to_v2d(self.cursor.range.0); - - if v >= n { - self.cursor.range.0 = self.buffer.v2d_to_index((v - n, self.cursor.vis_start)); - self.cursor.range.1 = self.cursor.range.0; - } - else { - self.cursor_to_beginning_of_buffer(); + for c in self.cursors.as_mut_slice().iter_mut() { + let (v, _) = self.buffer.index_to_v2d(c.range.0); + + if v >= n { + c.range.0 = self.buffer.v2d_to_index((v - n, c.vis_start)); + c.range.1 = c.range.0; + } + else { + c.range = (0, 0); + c.update_vis_start(&(self.buffer)); + } } // Adjust view @@ -406,14 +477,18 @@ impl Editor { } pub fn cursor_down(&mut self, n: usize) { - let (v, _) = self.buffer.index_to_v2d(self.cursor.range.0); - - if v < (self.buffer.line_count() - n) { - self.cursor.range.0 = self.buffer.v2d_to_index((v + n, self.cursor.vis_start)); - self.cursor.range.1 = self.cursor.range.0; - } - else { - self.cursor_to_end_of_buffer(); + 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) { + c.range.0 = self.buffer.v2d_to_index((v + n, c.vis_start)); + c.range.1 = c.range.0; + } + else { + let end = self.buffer.grapheme_count(); + c.range = (end, end); + c.update_vis_start(&(self.buffer)); + } } // Adjust view @@ -441,7 +516,6 @@ impl Editor { } pub fn page_down(&mut self) { - // TODO let nlc = self.buffer.line_count() - 1; if self.view_pos.0 < nlc { @@ -468,8 +542,9 @@ impl Editor { pub fn jump_to_line(&mut self, n: usize) { let pos = self.buffer.line_col_to_index((n, 0)); let (v, _) = self.buffer.index_to_v2d(pos); - self.cursor.range.0 = self.buffer.v2d_to_index((v, self.cursor.vis_start)); - self.cursor.range.1 = self.cursor.range.0; + self.cursors.truncate(1); + 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 self.move_view_to_cursor(); diff --git a/src/term_ui.rs b/src/term_ui.rs index f45920d..5443aa0 100644 --- a/src/term_ui.rs +++ b/src/term_ui.rs @@ -301,8 +301,10 @@ impl TermUI { self.rb.print(c1.1 + 1, c1.0, rustbox::RB_NORMAL, foreground, background, name.as_slice()); // 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 { - (((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 { 100 @@ -355,11 +357,22 @@ impl TermUI { grapheme_index = editor.buffer.v2d_to_index((vis_line_num, vis_col_num)); + + for (g, pos, width) in g_iter { 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 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, " "); } } @@ -368,12 +381,12 @@ impl TermUI { 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, " "); } } 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); } 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. - if editor.cursor.range.0 >= editor.buffer.grapheme_count() { - let vis_cursor_pos = editor.buffer.index_to_v2d(editor.cursor.range.0); - 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, " "); + for c in editor.cursors.as_slice().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) { + 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, " "); + } } } } diff --git a/todo.md b/todo.md index 5a1a197..eaaed77 100644 --- a/todo.md +++ b/todo.md @@ -7,7 +7,6 @@ - Persistent infinite undo - Multiple cursors - "Projects" -- Move undo/redo functionality into Editor instead of Buffer - Clean up text buffer interface: - //Editing (these are undoable):