From 257ce5c1fe23b70623ab053f471dffade299f2c9 Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Sun, 13 Apr 2025 12:40:16 +0200 Subject: [PATCH] Move view position and cursors into a new EditorState struct. --- src/editor/mod.rs | 151 +++++++----- src/term_ui/mod.rs | 12 +- sub_crates/backend/README.md | 8 + sub_crates/backend/src/buffer.rs | 48 ++-- sub_crates/backend/src/change.rs | 362 +++++------------------------ sub_crates/backend/src/editor.rs | 40 +++- sub_crates/backend/src/marks.rs | 256 ++++++++++++++++++++ sub_crates/backend/tests/change.rs | 8 +- 8 files changed, 487 insertions(+), 398 deletions(-) create mode 100644 sub_crates/backend/README.md diff --git a/src/editor/mod.rs b/src/editor/mod.rs index caae6be..1b911a7 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -32,9 +32,8 @@ pub struct Editor { // The dimensions and position of just the text view portion of the editor pub view_dim: (usize, usize), // (height, width) - // Indices into the mark sets of the buffer. - pub v_msi: usize, // View position MarkSet index. - pub c_msi: usize, // Cursors MarkSet index. + // Index into the relevant editor state of the buffer. + pub state_idx: usize, } impl Editor { @@ -42,11 +41,8 @@ impl Editor { pub fn new(buffer: Buffer, formatter: LineFormatter) -> Editor { let mut buffer = buffer; - // Create appropriate mark sets for view positions and cursors. - let v_msi = buffer.add_mark_set(); - let c_msi = buffer.add_mark_set(); - buffer.mark_sets[v_msi].add_mark(Mark::new(0, 0)); - buffer.mark_sets[c_msi].add_mark(Mark::new(0, 0)); + // Create editor state in the buffer for this new editor. + let state_idx = buffer.add_editor_state(); let mut ed = Editor { buffer: buffer, @@ -56,8 +52,7 @@ impl Editor { soft_tab_width: 4, editor_dim: (0, 0), view_dim: (0, 0), - v_msi: v_msi, - c_msi: c_msi, + state_idx: state_idx, }; ed.auto_detect_line_ending(); @@ -250,7 +245,7 @@ impl Editor { // Find the first and last char index visible within the editor. let c_first = self.formatter.set_horizontal( &self.buffer.text, - self.buffer.mark_sets[self.v_msi][0].head, + self.buffer.editor_states[self.state_idx].view_position.head, 0, ); let mut c_last = self.formatter.offset_vertical( @@ -263,30 +258,32 @@ impl Editor { .set_horizontal(&self.buffer.text, c_last, self.view_dim.1); // Adjust the view depending on where the cursor is - let cursor_head = self.buffer.mark_sets[self.c_msi].main().unwrap().head; + let cursor_head = self.buffer.editor_states[self.state_idx] + .cursors + .main() + .unwrap() + .head; if cursor_head < c_first { - self.buffer.mark_sets[self.v_msi][0].head = cursor_head; + self.buffer.editor_states[self.state_idx].view_position.head = cursor_head; } else if cursor_head > c_last { - self.buffer.mark_sets[self.v_msi][0].head = self.formatter.offset_vertical( - &self.buffer.text, - cursor_head, - -(self.view_dim.0 as isize), - ); + self.buffer.editor_states[self.state_idx].view_position.head = self + .formatter + .offset_vertical(&self.buffer.text, cursor_head, -(self.view_dim.0 as isize)); } } pub fn insert_text_at_cursor(&mut self, text: &str) { // TODO: handle multiple cursors. - let range = self.buffer.mark_sets[self.c_msi][0].range(); + let range = self.buffer.editor_states[self.state_idx].cursors[0].range(); // Do the edit. self.buffer.edit((range.start, range.end), text); // Adjust cursor position. let len = text.len(); - self.buffer.mark_sets[self.c_msi][0].head = range.start + len; - self.buffer.mark_sets[self.c_msi][0].tail = range.start + len; - self.buffer.mark_sets[self.c_msi][0].hh_pos = None; + self.buffer.editor_states[self.state_idx].cursors[0].head = range.start + len; + self.buffer.editor_states[self.state_idx].cursors[0].tail = range.start + len; + self.buffer.editor_states[self.state_idx].cursors[0].hh_pos = None; // Adjust view self.move_view_to_cursor(); @@ -294,7 +291,7 @@ impl Editor { pub fn insert_tab_at_cursor(&mut self) { // TODO: handle multiple cursors. - let range = self.buffer.mark_sets[self.c_msi][0].range(); + let range = self.buffer.editor_states[self.state_idx].cursors[0].range(); if self.soft_tabs { // Figure out how many spaces to insert @@ -314,16 +311,16 @@ impl Editor { .edit((range.start, range.end), space_strs[space_count]); // Adjust cursor position. - self.buffer.mark_sets[self.c_msi][0].head = range.start + space_count; - self.buffer.mark_sets[self.c_msi][0].tail = range.start + space_count; - self.buffer.mark_sets[self.c_msi][0].hh_pos = None; + self.buffer.editor_states[self.state_idx].cursors[0].head = range.start + space_count; + self.buffer.editor_states[self.state_idx].cursors[0].tail = range.start + space_count; + self.buffer.editor_states[self.state_idx].cursors[0].hh_pos = None; } else { self.buffer.edit((range.start, range.end), "\t"); // Adjust cursor position. - self.buffer.mark_sets[self.c_msi][0].head = range.start + 1; - self.buffer.mark_sets[self.c_msi][0].tail = range.start + 1; - self.buffer.mark_sets[self.c_msi][0].hh_pos = None; + self.buffer.editor_states[self.state_idx].cursors[0].head = range.start + 1; + self.buffer.editor_states[self.state_idx].cursors[0].tail = range.start + 1; + self.buffer.editor_states[self.state_idx].cursors[0].hh_pos = None; } // Adjust view @@ -332,7 +329,10 @@ impl Editor { pub fn remove_text_behind_cursor(&mut self, grapheme_count: usize) { // TODO: handle multiple cursors. - let mark = self.buffer.mark_sets[self.c_msi].main().unwrap(); + let mark = self.buffer.editor_states[self.state_idx] + .cursors + .main() + .unwrap(); let range = mark.range(); // Do nothing if there's nothing to delete. @@ -352,7 +352,10 @@ impl Editor { pub fn remove_text_in_front_of_cursor(&mut self, grapheme_count: usize) { // TODO: handle multiple cursors. - let mark = self.buffer.mark_sets[self.c_msi].main().unwrap(); + let mark = self.buffer.editor_states[self.state_idx] + .cursors + .main() + .unwrap(); let range = mark.range(); // Do nothing if there's nothing to delete. @@ -372,7 +375,10 @@ impl Editor { pub fn remove_text_inside_cursor(&mut self) { // TODO: handle multiple cursors. - let mark = self.buffer.mark_sets[self.c_msi].main().unwrap(); + let mark = self.buffer.editor_states[self.state_idx] + .cursors + .main() + .unwrap(); let range = mark.range(); if range.start < range.end { @@ -384,8 +390,10 @@ impl Editor { } pub fn cursor_to_beginning_of_buffer(&mut self) { - self.buffer.mark_sets[self.c_msi].clear(); - self.buffer.mark_sets[self.c_msi].add_mark(Mark::new(0, 0)); + self.buffer.editor_states[self.state_idx].cursors.clear(); + self.buffer.editor_states[self.state_idx] + .cursors + .add_mark(Mark::new(0, 0)); // Adjust view. self.move_view_to_cursor(); @@ -394,39 +402,45 @@ impl Editor { pub fn cursor_to_end_of_buffer(&mut self) { let end = self.buffer.text.len(); - self.buffer.mark_sets[self.c_msi].clear(); - self.buffer.mark_sets[self.c_msi].add_mark(Mark::new(end, end)); + self.buffer.editor_states[self.state_idx].cursors.clear(); + self.buffer.editor_states[self.state_idx] + .cursors + .add_mark(Mark::new(end, end)); // Adjust view. self.move_view_to_cursor(); } pub fn cursor_left(&mut self, n: usize) { - for mark in self.buffer.mark_sets[self.c_msi].iter_mut() { + for mark in self.buffer.editor_states[self.state_idx].cursors.iter_mut() { mark.head = nth_prev_grapheme_boundary(&self.buffer.text.slice(..), mark.head, n); mark.tail = mark.head; mark.hh_pos = None; } - self.buffer.mark_sets[self.c_msi].make_consistent(); + self.buffer.editor_states[self.state_idx] + .cursors + .make_consistent(); // Adjust view self.move_view_to_cursor(); } pub fn cursor_right(&mut self, n: usize) { - for mark in self.buffer.mark_sets[self.c_msi].iter_mut() { + for mark in self.buffer.editor_states[self.state_idx].cursors.iter_mut() { mark.head = nth_next_grapheme_boundary(&self.buffer.text.slice(..), mark.head, n); mark.tail = mark.head; mark.hh_pos = None; } - self.buffer.mark_sets[self.c_msi].make_consistent(); + self.buffer.editor_states[self.state_idx] + .cursors + .make_consistent(); // Adjust view self.move_view_to_cursor(); } pub fn cursor_up(&mut self, n: usize) { - for mark in self.buffer.mark_sets[self.c_msi].iter_mut() { + for mark in self.buffer.editor_states[self.state_idx].cursors.iter_mut() { if mark.hh_pos == None { mark.hh_pos = Some(self.formatter.get_horizontal(&self.buffer.text, mark.head)); } @@ -454,14 +468,16 @@ impl Editor { mark.tail = temp_index; } } - self.buffer.mark_sets[self.c_msi].make_consistent(); + self.buffer.editor_states[self.state_idx] + .cursors + .make_consistent(); // Adjust view self.move_view_to_cursor(); } pub fn cursor_down(&mut self, n: usize) { - for mark in self.buffer.mark_sets[self.c_msi].iter_mut() { + for mark in self.buffer.editor_states[self.state_idx].cursors.iter_mut() { if mark.hh_pos == None { mark.hh_pos = Some(self.formatter.get_horizontal(&self.buffer.text, mark.head)); } @@ -489,7 +505,9 @@ impl Editor { mark.tail = temp_index; } } - self.buffer.mark_sets[self.c_msi].make_consistent(); + self.buffer.editor_states[self.state_idx] + .cursors + .make_consistent(); // Adjust view self.move_view_to_cursor(); @@ -497,11 +515,12 @@ impl Editor { pub fn page_up(&mut self) { let move_amount = self.view_dim.0 - max(self.view_dim.0 / 8, 1); - self.buffer.mark_sets[self.v_msi][0].head = self.formatter.offset_vertical( - &self.buffer.text, - self.buffer.mark_sets[self.v_msi][0].head, - -1 * move_amount as isize, - ); + self.buffer.editor_states[self.state_idx].view_position.head = + self.formatter.offset_vertical( + &self.buffer.text, + self.buffer.editor_states[self.state_idx].view_position.head, + -1 * move_amount as isize, + ); self.cursor_up(move_amount); @@ -511,11 +530,12 @@ impl Editor { pub fn page_down(&mut self) { let move_amount = self.view_dim.0 - max(self.view_dim.0 / 8, 1); - self.buffer.mark_sets[self.v_msi][0].head = self.formatter.offset_vertical( - &self.buffer.text, - self.buffer.mark_sets[self.v_msi][0].head, - move_amount as isize, - ); + self.buffer.editor_states[self.state_idx].view_position.head = + self.formatter.offset_vertical( + &self.buffer.text, + self.buffer.editor_states[self.state_idx].view_position.head, + move_amount as isize, + ); self.cursor_down(move_amount); @@ -524,12 +544,15 @@ impl Editor { } pub fn jump_to_line(&mut self, n: usize) { - self.buffer.mark_sets[self.c_msi].reduce_to_main(); - if self.buffer.mark_sets[self.c_msi][0].hh_pos == None { - self.buffer.mark_sets[self.c_msi][0].hh_pos = Some( - self.formatter - .get_horizontal(&self.buffer.text, self.buffer.mark_sets[self.c_msi][0].head), - ); + self.buffer.editor_states[self.state_idx] + .cursors + .reduce_to_main(); + if self.buffer.editor_states[self.state_idx].cursors[0].hh_pos == None { + self.buffer.editor_states[self.state_idx].cursors[0].hh_pos = + Some(self.formatter.get_horizontal( + &self.buffer.text, + self.buffer.editor_states[self.state_idx].cursors[0].head, + )); } let pos = self @@ -539,11 +562,13 @@ impl Editor { let pos = self.formatter.set_horizontal( &self.buffer.text, pos, - self.buffer.mark_sets[self.c_msi][0].hh_pos.unwrap(), + self.buffer.editor_states[self.state_idx].cursors[0] + .hh_pos + .unwrap(), ); - self.buffer.mark_sets[self.c_msi][0].head = pos; - self.buffer.mark_sets[self.c_msi][0].tail = pos; + self.buffer.editor_states[self.state_idx].cursors[0].head = pos; + self.buffer.editor_states[self.state_idx].cursors[0].tail = pos; // Adjust view self.move_view_to_cursor(); diff --git a/src/term_ui/mod.rs b/src/term_ui/mod.rs index 50a8ca5..54a4dc4 100644 --- a/src/term_ui/mod.rs +++ b/src/term_ui/mod.rs @@ -501,7 +501,11 @@ impl TermUI { // TODO: use view instead of cursor for calculation if there is more // than one cursor. let percentage: usize = if editor.buffer.text.len() > 0 { - (((editor.buffer.mark_sets[editor.c_msi].main().unwrap().head as f32) + (((editor.buffer.editor_states[editor.state_idx] + .cursors + .main() + .unwrap() + .head as f32) / (editor.buffer.text.len() as f32)) * 100.0) as usize } else { @@ -540,8 +544,10 @@ impl TermUI { } fn draw_editor_text(&self, editor: &Editor, c1: (usize, usize), c2: (usize, usize)) { - let view_pos = editor.buffer.mark_sets[editor.v_msi][0].head; - let cursors = &editor.buffer.mark_sets[editor.c_msi]; + let view_pos = editor.buffer.editor_states[editor.state_idx] + .view_position + .head; + let cursors = &editor.buffer.editor_states[editor.state_idx].cursors; // Calculate all the starting info let gutter_width = editor.editor_dim.1 - editor.view_dim.1; diff --git a/sub_crates/backend/README.md b/sub_crates/backend/README.md new file mode 100644 index 0000000..7a30188 --- /dev/null +++ b/sub_crates/backend/README.md @@ -0,0 +1,8 @@ +# Vocab + +- Buffer: a text buffer. Doesn't have to correspond to an on-disk text file, can also be in-memory scratch buffers. +- Editor: an open view into a text buffer. +- EditorState: the view position and cursors/selections of an editor for a particular buffer. +- Mark: an abstract representation of a range in the text. Used for representing cursors and selections, among other things. +- Change: a record of simultaneous edits to a buffer. Includes data to both apply and undo the edits. +- History: a history of changes made to a buffer. Used for undo. diff --git a/sub_crates/backend/src/buffer.rs b/sub_crates/backend/src/buffer.rs index b149312..9c0ac3e 100644 --- a/sub_crates/backend/src/buffer.rs +++ b/sub_crates/backend/src/buffer.rs @@ -7,7 +7,11 @@ use std::{ use ropey::{LineType, Rope}; -use crate::{change::Change, history::History, marks::MarkSet}; +use crate::{ + change::{ApplyChange, Change}, + editor::EditorState, + history::History, +}; /// The line type that should be used as the standard line metric in a buffer. pub const BUFLINE: LineType = LineType::LF_CR; @@ -25,11 +29,10 @@ pub enum BufferPath { /// An open text buffer, currently being edited. #[derive(Debug, Clone)] pub struct Buffer { - pub text: Rope, // The actual text content. - pub mark_sets: Vec, // MarkSets for cursors. - + pub text: Rope, // The actual text content. pub path: BufferPath, pub edits_since_saved: i64, // Tracks whether this buffer is out of sync with the last save. + pub editor_states: Vec, history: History, } @@ -40,7 +43,7 @@ impl Buffer { path: path, edits_since_saved: 0, text: text, - mark_sets: Vec::new(), + editor_states: Vec::new(), history: History::new(), } } @@ -91,14 +94,13 @@ impl Buffer { // Build the change. let change = Change::from_edit(start, &Cow::from(self.text.slice(start..end)), text); - // Update mark sets. - for mark_set in self.mark_sets.iter_mut() { - change.apply_to_marks(mark_set); - mark_set.make_consistent(); + // Update editor states. + for context in self.editor_states.iter_mut() { + context.apply_change(&change); } // Apply the edit to the document. - change.apply(&mut self.text); + self.text.apply_change(&change); // Update undo stack. self.history.push_edit(change); @@ -113,14 +115,13 @@ impl Buffer { if let Some(change) = self.history.undo() { self.edits_since_saved = self.edits_since_saved.wrapping_sub(1); - // Update mark sets. - for mark_set in self.mark_sets.iter_mut() { - change.apply_inverse_to_marks(mark_set); - mark_set.make_consistent(); + // Update editor states. + for context in self.editor_states.iter_mut() { + context.apply_change_inverse(change); } // Apply the edit to the document. - change.apply_inverse(&mut self.text); + self.text.apply_change_inverse(change); return Some(change); } else { @@ -137,14 +138,13 @@ impl Buffer { if let Some(change) = self.history.redo() { self.edits_since_saved = self.edits_since_saved.wrapping_add(1); - // Update mark sets. - for mark_set in self.mark_sets.iter_mut() { - change.apply_to_marks(mark_set); - mark_set.make_consistent(); + // Update editor states. + for context in self.editor_states.iter_mut() { + context.apply_change(change); } // Apply the edit to the document. - change.apply(&mut self.text); + self.text.apply_change(change); return Some(change); } else { @@ -152,9 +152,9 @@ impl Buffer { } } - /// Creates a new empty mark set, and returns the set index. - pub fn add_mark_set(&mut self) -> usize { - self.mark_sets.push(MarkSet::new()); - return self.mark_sets.len() - 1; + /// Creates a new editor state for the buffer, and returns its index. + pub fn add_editor_state(&mut self) -> usize { + self.editor_states.push(EditorState::new()); + return self.editor_states.len() - 1; } } diff --git a/sub_crates/backend/src/change.rs b/sub_crates/backend/src/change.rs index 3fce2e1..82f252d 100644 --- a/sub_crates/backend/src/change.rs +++ b/sub_crates/backend/src/change.rs @@ -1,9 +1,7 @@ use ropey::Rope; -use crate::marks::MarkSet; - #[derive(Debug, Clone, Copy)] -enum Op { +pub enum Op { Retain(usize), // In bytes. Replace { // These both represent strings, and are byte-index ranges into @@ -17,7 +15,7 @@ enum Op { impl Op { /// The length of the string segment this Op represents *before* /// its application. In bytes. - fn len_pre(&self) -> usize { + pub fn len_pre(&self) -> usize { match self { Op::Retain(byte_count) => *byte_count, Op::Replace { old, .. } => old.1 - old.0, @@ -26,7 +24,7 @@ impl Op { /// The length of the string segment this Op represents *after* /// its application. In bytes. - fn len_post(&self) -> usize { + pub fn len_post(&self) -> usize { match self { Op::Retain(byte_count) => *byte_count, Op::Replace { new, .. } => new.1 - new.0, @@ -37,8 +35,8 @@ impl Op { /// A reversable set of edits treated as an atomic unit. #[derive(Clone)] pub struct Change { - ops: Vec, - buffer: String, + pub ops: Vec, + pub buffer: String, } impl Change { @@ -320,168 +318,6 @@ impl Change { inv } - /// Applies the Change to a Rope. - pub fn apply(&self, text: &mut Rope) { - let mut i = 0; - for op in self.ops.iter() { - match op { - Op::Retain(byte_count) => { - i += byte_count; - } - Op::Replace { old, new } => { - let old = &self.buffer[old.0..old.1]; - let new = &self.buffer[new.0..new.1]; - - if !old.is_empty() { - debug_assert_eq!(text.slice(i..(i + old.len())), old); - text.remove(i..(i + old.len())); - } - if !new.is_empty() { - text.insert(i, new); - } - - i = i + new.len(); - } - } - } - debug_assert!(i <= text.len()); - } - - /// Applies the inverse of the Change to a Rope. - /// - /// This is an "undo" of the Change. - pub fn apply_inverse(&self, text: &mut Rope) { - let mut i = 0; - for op in self.ops.iter() { - match op { - Op::Retain(byte_count) => { - i += byte_count; - } - Op::Replace { - old: old_range, - new: new_range, - } => { - // Swap old and new, to do the inverse. - let old = &self.buffer[new_range.0..new_range.1]; - let new = &self.buffer[old_range.0..old_range.1]; - - if !old.is_empty() { - debug_assert_eq!(text.slice(i..(i + old.len())), old); - text.remove(i..(i + old.len())); - } - if !new.is_empty() { - text.insert(i, new); - } - - i = i + new.len(); - } - } - } - debug_assert!(i <= text.len()); - } - - /// Applies the Change to a set of Marks. - pub fn apply_to_marks(&self, marks: &mut MarkSet) { - marks.make_consistent(); - - // We do this in two passes: first heads, then tails. - for tail in 0..=1 { - let mut mark_idx = 0; - let mut byte_idx_old = 0; - let mut byte_idx_new = 0; - for op in self.ops.iter() { - let old_len = op.len_pre(); - let new_len = op.len_post(); - - while mark_idx < marks.len() { - let idx = if tail == 1 { - &mut marks[mark_idx].tail - } else { - &mut marks[mark_idx].head - }; - - if *idx < byte_idx_old { - mark_idx += 1; - } else if *idx < (byte_idx_old + old_len) { - *idx = *idx + byte_idx_new - byte_idx_old; - *idx = (*idx).min(byte_idx_new + new_len); - mark_idx += 1; - } else { - break; - } - } - - byte_idx_old += old_len; - byte_idx_new += new_len; - } - - while mark_idx < marks.len() { - let idx = if tail == 1 { - &mut marks[mark_idx].tail - } else { - &mut marks[mark_idx].head - }; - *idx = *idx + byte_idx_new - byte_idx_old; - mark_idx += 1; - } - } - - marks.make_consistent(); - } - - /// Applies the inverse of the Change to a set of Marks. - /// - /// This is essentially an "undo" of the Change. However, on - /// marks this is a lossy operation and is not guaranteed to return - /// the marks to their original count and position. - pub fn apply_inverse_to_marks(&self, marks: &mut MarkSet) { - marks.make_consistent(); - - // We do this in two passes: first heads, then tails. - for tail in 0..=1 { - let mut mark_idx = 0; - let mut byte_idx_old = 0; - let mut byte_idx_new = 0; - for op in self.ops.iter() { - let old_len = op.len_post(); - let new_len = op.len_pre(); - - while mark_idx < marks.len() { - let idx = if tail == 1 { - &mut marks[mark_idx].tail - } else { - &mut marks[mark_idx].head - }; - - if *idx < byte_idx_old { - mark_idx += 1; - } else if *idx < (byte_idx_old + old_len) { - *idx = *idx + byte_idx_new - byte_idx_old; - *idx = (*idx).min(byte_idx_new + new_len); - mark_idx += 1; - } else { - break; - } - } - - byte_idx_old += old_len; - byte_idx_new += new_len; - } - - while mark_idx < marks.len() { - let idx = if tail == 1 { - &mut marks[mark_idx].tail - } else { - &mut marks[mark_idx].head - }; - *idx = *idx + byte_idx_new - byte_idx_old; - mark_idx += 1; - } - } - - marks.make_consistent(); - } - //--------------------------------------------------------- /// Pushes a retain op onto the change operations. @@ -566,142 +402,72 @@ impl std::fmt::Debug for Change { } } -#[cfg(test)] -mod tests { - use super::*; - use crate::marks::{Mark, MarkSet}; +pub trait ApplyChange { + /// Applies a Change. + fn apply_change(&mut self, change: &Change); - #[test] - fn apply_to_marks_01() { - let change = Change::from_edit(5, "", "hi"); + /// Applies the inverse of a Change. + /// + /// More-or-less an "undo", although for some types applying a change + /// followed by applying it's inverse may not result in exactly the same + /// state (cursor positions, for example). + fn apply_change_inverse(&mut self, change: &Change); +} - let mut mark_set = MarkSet::new(); - mark_set.add_mark(Mark::new(4, 4)); - mark_set.add_mark(Mark::new(5, 5)); - mark_set.add_mark(Mark::new(6, 6)); +impl ApplyChange for Rope { + fn apply_change(&mut self, change: &Change) { + let mut i = 0; + for op in change.ops.iter() { + match op { + Op::Retain(byte_count) => { + i += byte_count; + } + Op::Replace { old, new } => { + let old = &change.buffer[old.0..old.1]; + let new = &change.buffer[new.0..new.1]; - change.apply_to_marks(&mut mark_set); - assert_eq!(mark_set.marks[0].head, 4); - assert_eq!(mark_set.marks[0].tail, 4); - assert_eq!(mark_set.marks[1].head, 7); - assert_eq!(mark_set.marks[1].tail, 7); - assert_eq!(mark_set.marks[2].head, 8); - assert_eq!(mark_set.marks[2].tail, 8); + if !old.is_empty() { + debug_assert_eq!(self.slice(i..(i + old.len())), old); + self.remove(i..(i + old.len())); + } + if !new.is_empty() { + self.insert(i, new); + } + + i = i + new.len(); + } + } + } + debug_assert!(i <= self.len()); } - #[test] - fn apply_to_marks_02() { - let change = Change { - ops: vec![ - Op::Retain(5), + fn apply_change_inverse(&mut self, change: &Change) { + let mut i = 0; + for op in change.ops.iter() { + match op { + Op::Retain(byte_count) => { + i += byte_count; + } Op::Replace { - old: (0, 0), - new: (0, 1), - }, - Op::Replace { - old: (0, 0), - new: (1, 2), - }, - ], - buffer: "hi".into(), - }; + old: old_range, + new: new_range, + } => { + // Swap old and new, to do the inverse. + let old = &change.buffer[new_range.0..new_range.1]; + let new = &change.buffer[old_range.0..old_range.1]; - let mut mark_set = MarkSet::new(); - mark_set.add_mark(Mark::new(4, 4)); - mark_set.add_mark(Mark::new(5, 5)); - mark_set.add_mark(Mark::new(6, 6)); + if !old.is_empty() { + debug_assert_eq!(self.slice(i..(i + old.len())), old); + self.remove(i..(i + old.len())); + } + if !new.is_empty() { + self.insert(i, new); + } - change.apply_to_marks(&mut mark_set); - assert_eq!(mark_set.marks[0].head, 4); - assert_eq!(mark_set.marks[0].tail, 4); - assert_eq!(mark_set.marks[1].head, 7); - assert_eq!(mark_set.marks[1].tail, 7); - assert_eq!(mark_set.marks[2].head, 8); - assert_eq!(mark_set.marks[2].tail, 8); - } - - #[test] - fn apply_to_marks_03() { - let change = Change::from_edit(5, "hi", ""); - - let mut mark_set = MarkSet::new(); - mark_set.add_mark(Mark::new(4, 4)); - mark_set.add_mark(Mark::new(6, 6)); - mark_set.add_mark(Mark::new(8, 8)); - - change.apply_to_marks(&mut mark_set); - assert_eq!(mark_set.marks[0].head, 4); - assert_eq!(mark_set.marks[0].tail, 4); - assert_eq!(mark_set.marks[1].head, 5); - assert_eq!(mark_set.marks[1].tail, 5); - assert_eq!(mark_set.marks[2].head, 6); - assert_eq!(mark_set.marks[2].tail, 6); - } - - #[test] - fn apply_to_marks_04() { - let change = Change { - ops: vec![ - Op::Retain(5), - Op::Replace { - old: (0, 1), - new: (0, 0), - }, - Op::Replace { - old: (1, 2), - new: (0, 0), - }, - ], - buffer: "hi".into(), - }; - - let mut mark_set = MarkSet::new(); - mark_set.add_mark(Mark::new(4, 4)); - mark_set.add_mark(Mark::new(6, 6)); - mark_set.add_mark(Mark::new(8, 8)); - - change.apply_to_marks(&mut mark_set); - assert_eq!(mark_set.marks[0].head, 4); - assert_eq!(mark_set.marks[0].tail, 4); - assert_eq!(mark_set.marks[1].head, 5); - assert_eq!(mark_set.marks[1].tail, 5); - assert_eq!(mark_set.marks[2].head, 6); - assert_eq!(mark_set.marks[2].tail, 6); - } - - #[test] - fn apply_inverse_to_marks_01() { - let change = Change::from_edit(5, "hi", ""); - - let mut mark_set = MarkSet::new(); - mark_set.add_mark(Mark::new(4, 4)); - mark_set.add_mark(Mark::new(5, 5)); - mark_set.add_mark(Mark::new(6, 6)); - - change.apply_inverse_to_marks(&mut mark_set); - assert_eq!(mark_set.marks[0].head, 4); - assert_eq!(mark_set.marks[0].tail, 4); - assert_eq!(mark_set.marks[1].head, 7); - assert_eq!(mark_set.marks[1].tail, 7); - assert_eq!(mark_set.marks[2].head, 8); - assert_eq!(mark_set.marks[2].tail, 8); - } - - #[test] - fn apply_inverse_to_marks_02() { - let change = Change::from_edit(5, "", "hi"); - - let mut mark_set = MarkSet::new(); - mark_set.add_mark(Mark::new(4, 4)); - mark_set.add_mark(Mark::new(6, 6)); - mark_set.add_mark(Mark::new(8, 8)); - - change.apply_inverse_to_marks(&mut mark_set); - assert_eq!(mark_set.marks[0].head, 4); - assert_eq!(mark_set.marks[0].tail, 4); - assert_eq!(mark_set.marks[1].head, 5); - assert_eq!(mark_set.marks[1].tail, 5); - assert_eq!(mark_set.marks[2].head, 6); - assert_eq!(mark_set.marks[2].tail, 6); + i = i + new.len(); + } + } + } + debug_assert!(i <= self.len()); } } diff --git a/sub_crates/backend/src/editor.rs b/sub_crates/backend/src/editor.rs index 859c9c4..8def2f3 100644 --- a/sub_crates/backend/src/editor.rs +++ b/sub_crates/backend/src/editor.rs @@ -1,9 +1,37 @@ -use crate::buffer::Buffer; +use crate::{ + change::{ApplyChange, Change}, + marks::{Mark, MarkSet}, +}; -/// A struct holding the current editor state. +/// The state of an editor with respect to a particular buffer. /// -/// The Editor represents all currently open buffers available for editing. -#[derive(Debug)] -pub struct Editor { - open_buffers: Vec, +/// Contains all state that's unique to a buffer-editor pair. +#[derive(Debug, Clone)] +pub struct EditorState { + pub view_position: Mark, + pub cursors: MarkSet, +} + +impl EditorState { + pub fn new() -> Self { + let mut cursors = MarkSet::new(); + cursors.add_mark(Mark::new(0, 0)); + + EditorState { + view_position: Mark::new(0, 0), + cursors: cursors, + } + } +} + +impl ApplyChange for EditorState { + fn apply_change(&mut self, change: &Change) { + self.view_position.apply_change(change); + self.cursors.apply_change(change); + } + + fn apply_change_inverse(&mut self, change: &Change) { + self.view_position.apply_change_inverse(change); + self.cursors.apply_change_inverse(change); + } } diff --git a/sub_crates/backend/src/marks.rs b/sub_crates/backend/src/marks.rs index c80f8ad..c0c8926 100644 --- a/sub_crates/backend/src/marks.rs +++ b/sub_crates/backend/src/marks.rs @@ -1,3 +1,5 @@ +use crate::change::{ApplyChange, Change}; + /// A mark on a piece of text, useful for representing cursors, selections, and /// general positions within a piece of text. /// @@ -101,6 +103,22 @@ impl Mark { } } +impl ApplyChange for Mark { + fn apply_change(&mut self, change: &Change) { + let mut mark_set = MarkSet::new(); + mark_set.add_mark(*self); + mark_set.apply_change(change); + *self = mark_set[0]; + } + + fn apply_change_inverse(&mut self, change: &Change) { + let mut mark_set = MarkSet::new(); + mark_set.add_mark(*self); + mark_set.apply_change_inverse(change); + *self = mark_set[0]; + } +} + //---------------------------------------------------------------------- /// A set of disjoint Marks, sorted by position in the text. @@ -239,6 +257,104 @@ impl MarkSet { } } +impl ApplyChange for MarkSet { + fn apply_change(&mut self, change: &Change) { + self.make_consistent(); + + // We do this in two passes: first heads, then tails. + for tail in 0..=1 { + let mut mark_idx = 0; + let mut byte_idx_old = 0; + let mut byte_idx_new = 0; + for op in change.ops.iter() { + let old_len = op.len_pre(); + let new_len = op.len_post(); + + while mark_idx < self.len() { + let idx = if tail == 1 { + &mut self[mark_idx].tail + } else { + &mut self[mark_idx].head + }; + + if *idx < byte_idx_old { + mark_idx += 1; + } else if *idx < (byte_idx_old + old_len) { + *idx = *idx + byte_idx_new - byte_idx_old; + *idx = (*idx).min(byte_idx_new + new_len); + mark_idx += 1; + } else { + break; + } + } + + byte_idx_old += old_len; + byte_idx_new += new_len; + } + + while mark_idx < self.len() { + let idx = if tail == 1 { + &mut self[mark_idx].tail + } else { + &mut self[mark_idx].head + }; + *idx = *idx + byte_idx_new - byte_idx_old; + mark_idx += 1; + } + } + + self.make_consistent(); + } + + fn apply_change_inverse(&mut self, change: &Change) { + self.make_consistent(); + + // We do this in two passes: first heads, then tails. + for tail in 0..=1 { + let mut mark_idx = 0; + let mut byte_idx_old = 0; + let mut byte_idx_new = 0; + for op in change.ops.iter() { + let old_len = op.len_post(); + let new_len = op.len_pre(); + + while mark_idx < self.len() { + let idx = if tail == 1 { + &mut self[mark_idx].tail + } else { + &mut self[mark_idx].head + }; + + if *idx < byte_idx_old { + mark_idx += 1; + } else if *idx < (byte_idx_old + old_len) { + *idx = *idx + byte_idx_new - byte_idx_old; + *idx = (*idx).min(byte_idx_new + new_len); + mark_idx += 1; + } else { + break; + } + } + + byte_idx_old += old_len; + byte_idx_new += new_len; + } + + while mark_idx < self.len() { + let idx = if tail == 1 { + &mut self[mark_idx].tail + } else { + &mut self[mark_idx].head + }; + *idx = *idx + byte_idx_new - byte_idx_old; + mark_idx += 1; + } + } + + self.make_consistent(); + } +} + impl std::ops::Index for MarkSet { type Output = Mark; @@ -252,3 +368,143 @@ impl std::ops::IndexMut for MarkSet { &mut self.marks[index] } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::change::Op; + + #[test] + fn apply_to_marks_01() { + let change = Change::from_edit(5, "", "hi"); + + let mut mark_set = MarkSet::new(); + mark_set.add_mark(Mark::new(4, 4)); + mark_set.add_mark(Mark::new(5, 5)); + mark_set.add_mark(Mark::new(6, 6)); + + mark_set.apply_change(&change); + assert_eq!(mark_set.marks[0].head, 4); + assert_eq!(mark_set.marks[0].tail, 4); + assert_eq!(mark_set.marks[1].head, 7); + assert_eq!(mark_set.marks[1].tail, 7); + assert_eq!(mark_set.marks[2].head, 8); + assert_eq!(mark_set.marks[2].tail, 8); + } + + #[test] + fn apply_to_marks_02() { + let change = Change { + ops: vec![ + Op::Retain(5), + Op::Replace { + old: (0, 0), + new: (0, 1), + }, + Op::Replace { + old: (0, 0), + new: (1, 2), + }, + ], + buffer: "hi".into(), + }; + + let mut mark_set = MarkSet::new(); + mark_set.add_mark(Mark::new(4, 4)); + mark_set.add_mark(Mark::new(5, 5)); + mark_set.add_mark(Mark::new(6, 6)); + + mark_set.apply_change(&change); + assert_eq!(mark_set.marks[0].head, 4); + assert_eq!(mark_set.marks[0].tail, 4); + assert_eq!(mark_set.marks[1].head, 7); + assert_eq!(mark_set.marks[1].tail, 7); + assert_eq!(mark_set.marks[2].head, 8); + assert_eq!(mark_set.marks[2].tail, 8); + } + + #[test] + fn apply_to_marks_03() { + let change = Change::from_edit(5, "hi", ""); + + let mut mark_set = MarkSet::new(); + mark_set.add_mark(Mark::new(4, 4)); + mark_set.add_mark(Mark::new(6, 6)); + mark_set.add_mark(Mark::new(8, 8)); + + mark_set.apply_change(&change); + assert_eq!(mark_set.marks[0].head, 4); + assert_eq!(mark_set.marks[0].tail, 4); + assert_eq!(mark_set.marks[1].head, 5); + assert_eq!(mark_set.marks[1].tail, 5); + assert_eq!(mark_set.marks[2].head, 6); + assert_eq!(mark_set.marks[2].tail, 6); + } + + #[test] + fn apply_to_marks_04() { + let change = Change { + ops: vec![ + Op::Retain(5), + Op::Replace { + old: (0, 1), + new: (0, 0), + }, + Op::Replace { + old: (1, 2), + new: (0, 0), + }, + ], + buffer: "hi".into(), + }; + + let mut mark_set = MarkSet::new(); + mark_set.add_mark(Mark::new(4, 4)); + mark_set.add_mark(Mark::new(6, 6)); + mark_set.add_mark(Mark::new(8, 8)); + + mark_set.apply_change(&change); + assert_eq!(mark_set.marks[0].head, 4); + assert_eq!(mark_set.marks[0].tail, 4); + assert_eq!(mark_set.marks[1].head, 5); + assert_eq!(mark_set.marks[1].tail, 5); + assert_eq!(mark_set.marks[2].head, 6); + assert_eq!(mark_set.marks[2].tail, 6); + } + + #[test] + fn apply_inverse_to_marks_01() { + let change = Change::from_edit(5, "hi", ""); + + let mut mark_set = MarkSet::new(); + mark_set.add_mark(Mark::new(4, 4)); + mark_set.add_mark(Mark::new(5, 5)); + mark_set.add_mark(Mark::new(6, 6)); + + mark_set.apply_change_inverse(&change); + assert_eq!(mark_set.marks[0].head, 4); + assert_eq!(mark_set.marks[0].tail, 4); + assert_eq!(mark_set.marks[1].head, 7); + assert_eq!(mark_set.marks[1].tail, 7); + assert_eq!(mark_set.marks[2].head, 8); + assert_eq!(mark_set.marks[2].tail, 8); + } + + #[test] + fn apply_inverse_to_marks_02() { + let change = Change::from_edit(5, "", "hi"); + + let mut mark_set = MarkSet::new(); + mark_set.add_mark(Mark::new(4, 4)); + mark_set.add_mark(Mark::new(6, 6)); + mark_set.add_mark(Mark::new(8, 8)); + + mark_set.apply_change_inverse(&change); + assert_eq!(mark_set.marks[0].head, 4); + assert_eq!(mark_set.marks[0].tail, 4); + assert_eq!(mark_set.marks[1].head, 5); + assert_eq!(mark_set.marks[1].tail, 5); + assert_eq!(mark_set.marks[2].head, 6); + assert_eq!(mark_set.marks[2].tail, 6); + } +} diff --git a/sub_crates/backend/tests/change.rs b/sub_crates/backend/tests/change.rs index db43301..6eced6a 100644 --- a/sub_crates/backend/tests/change.rs +++ b/sub_crates/backend/tests/change.rs @@ -6,7 +6,7 @@ use proptest::test_runner::Config; use ropey::Rope; -use backend::change::Change; +use backend::change::{ApplyChange, Change}; fn make_edit_list_valid(edits: &mut Vec<(usize, usize, String)>, start_text: &str) { edits.sort_by_key(|e| e.0); @@ -45,7 +45,7 @@ proptest! { (e.0, &start_text[e.0..(e.0+e.1)], &e.2[..]) })); let mut r1 = Rope::from_str(start_text); - change1.apply(&mut r1); + r1.apply_change(&change1); let text1: String = r1.clone().into(); // Make edits2 valid and compute the post-edits2 string. @@ -53,12 +53,12 @@ proptest! { let change2 = Change::from_ordered_edit_set(edits2.iter().map(|e| { (e.0, &text1[e.0..(e.0+e.1)], &e.2[..]) })); - change2.apply(&mut r1); + r1.apply_change(&change2); // Do the test! let change3 = change1.compose(&change2); let mut r2 = Rope::from_str(start_text); - change3.apply(&mut r2); + r2.apply_change(&change3); assert_eq!(r1, r2); }