Move view position and cursors into a new EditorState struct.

This commit is contained in:
Nathan Vegdahl 2025-04-13 12:40:16 +02:00
parent 31d468928c
commit 257ce5c1fe
8 changed files with 487 additions and 398 deletions

View File

@ -32,9 +32,8 @@ pub struct Editor {
// The dimensions and position of just the text view portion of the editor // The dimensions and position of just the text view portion of the editor
pub view_dim: (usize, usize), // (height, width) pub view_dim: (usize, usize), // (height, width)
// Indices into the mark sets of the buffer. // Index into the relevant editor state of the buffer.
pub v_msi: usize, // View position MarkSet index. pub state_idx: usize,
pub c_msi: usize, // Cursors MarkSet index.
} }
impl Editor { impl Editor {
@ -42,11 +41,8 @@ impl Editor {
pub fn new(buffer: Buffer, formatter: LineFormatter) -> Editor { pub fn new(buffer: Buffer, formatter: LineFormatter) -> Editor {
let mut buffer = buffer; let mut buffer = buffer;
// Create appropriate mark sets for view positions and cursors. // Create editor state in the buffer for this new editor.
let v_msi = buffer.add_mark_set(); let state_idx = buffer.add_editor_state();
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));
let mut ed = Editor { let mut ed = Editor {
buffer: buffer, buffer: buffer,
@ -56,8 +52,7 @@ impl Editor {
soft_tab_width: 4, soft_tab_width: 4,
editor_dim: (0, 0), editor_dim: (0, 0),
view_dim: (0, 0), view_dim: (0, 0),
v_msi: v_msi, state_idx: state_idx,
c_msi: c_msi,
}; };
ed.auto_detect_line_ending(); ed.auto_detect_line_ending();
@ -250,7 +245,7 @@ impl Editor {
// Find the first and last char index visible within the editor. // Find the first and last char index visible within the editor.
let c_first = self.formatter.set_horizontal( let c_first = self.formatter.set_horizontal(
&self.buffer.text, &self.buffer.text,
self.buffer.mark_sets[self.v_msi][0].head, self.buffer.editor_states[self.state_idx].view_position.head,
0, 0,
); );
let mut c_last = self.formatter.offset_vertical( 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); .set_horizontal(&self.buffer.text, c_last, self.view_dim.1);
// Adjust the view depending on where the cursor is // 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 { 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 { } else if cursor_head > c_last {
self.buffer.mark_sets[self.v_msi][0].head = self.formatter.offset_vertical( self.buffer.editor_states[self.state_idx].view_position.head = self
&self.buffer.text, .formatter
cursor_head, .offset_vertical(&self.buffer.text, cursor_head, -(self.view_dim.0 as isize));
-(self.view_dim.0 as isize),
);
} }
} }
pub fn insert_text_at_cursor(&mut self, text: &str) { pub fn insert_text_at_cursor(&mut self, text: &str) {
// TODO: handle multiple cursors. // 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. // Do the edit.
self.buffer.edit((range.start, range.end), text); self.buffer.edit((range.start, range.end), text);
// Adjust cursor position. // Adjust cursor position.
let len = text.len(); let len = text.len();
self.buffer.mark_sets[self.c_msi][0].head = range.start + len; self.buffer.editor_states[self.state_idx].cursors[0].head = range.start + len;
self.buffer.mark_sets[self.c_msi][0].tail = range.start + len; self.buffer.editor_states[self.state_idx].cursors[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].hh_pos = None;
// Adjust view // Adjust view
self.move_view_to_cursor(); self.move_view_to_cursor();
@ -294,7 +291,7 @@ impl Editor {
pub fn insert_tab_at_cursor(&mut self) { pub fn insert_tab_at_cursor(&mut self) {
// TODO: handle multiple cursors. // 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 { if self.soft_tabs {
// Figure out how many spaces to insert // Figure out how many spaces to insert
@ -314,16 +311,16 @@ impl Editor {
.edit((range.start, range.end), space_strs[space_count]); .edit((range.start, range.end), space_strs[space_count]);
// Adjust cursor position. // Adjust cursor position.
self.buffer.mark_sets[self.c_msi][0].head = range.start + space_count; self.buffer.editor_states[self.state_idx].cursors[0].head = range.start + space_count;
self.buffer.mark_sets[self.c_msi][0].tail = range.start + space_count; self.buffer.editor_states[self.state_idx].cursors[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].hh_pos = None;
} else { } else {
self.buffer.edit((range.start, range.end), "\t"); self.buffer.edit((range.start, range.end), "\t");
// Adjust cursor position. // Adjust cursor position.
self.buffer.mark_sets[self.c_msi][0].head = range.start + 1; self.buffer.editor_states[self.state_idx].cursors[0].head = range.start + 1;
self.buffer.mark_sets[self.c_msi][0].tail = range.start + 1; self.buffer.editor_states[self.state_idx].cursors[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].hh_pos = None;
} }
// Adjust view // Adjust view
@ -332,7 +329,10 @@ impl Editor {
pub fn remove_text_behind_cursor(&mut self, grapheme_count: usize) { pub fn remove_text_behind_cursor(&mut self, grapheme_count: usize) {
// TODO: handle multiple cursors. // 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(); let range = mark.range();
// Do nothing if there's nothing to delete. // 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) { pub fn remove_text_in_front_of_cursor(&mut self, grapheme_count: usize) {
// TODO: handle multiple cursors. // 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(); let range = mark.range();
// Do nothing if there's nothing to delete. // Do nothing if there's nothing to delete.
@ -372,7 +375,10 @@ impl Editor {
pub fn remove_text_inside_cursor(&mut self) { pub fn remove_text_inside_cursor(&mut self) {
// TODO: handle multiple cursors. // 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(); let range = mark.range();
if range.start < range.end { if range.start < range.end {
@ -384,8 +390,10 @@ impl Editor {
} }
pub fn cursor_to_beginning_of_buffer(&mut self) { pub fn cursor_to_beginning_of_buffer(&mut self) {
self.buffer.mark_sets[self.c_msi].clear(); self.buffer.editor_states[self.state_idx].cursors.clear();
self.buffer.mark_sets[self.c_msi].add_mark(Mark::new(0, 0)); self.buffer.editor_states[self.state_idx]
.cursors
.add_mark(Mark::new(0, 0));
// Adjust view. // Adjust view.
self.move_view_to_cursor(); self.move_view_to_cursor();
@ -394,39 +402,45 @@ impl Editor {
pub fn cursor_to_end_of_buffer(&mut self) { pub fn cursor_to_end_of_buffer(&mut self) {
let end = self.buffer.text.len(); let end = self.buffer.text.len();
self.buffer.mark_sets[self.c_msi].clear(); self.buffer.editor_states[self.state_idx].cursors.clear();
self.buffer.mark_sets[self.c_msi].add_mark(Mark::new(end, end)); self.buffer.editor_states[self.state_idx]
.cursors
.add_mark(Mark::new(end, end));
// 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) {
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.head = nth_prev_grapheme_boundary(&self.buffer.text.slice(..), mark.head, n);
mark.tail = mark.head; mark.tail = mark.head;
mark.hh_pos = None; 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 // 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) {
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.head = nth_next_grapheme_boundary(&self.buffer.text.slice(..), mark.head, n);
mark.tail = mark.head; mark.tail = mark.head;
mark.hh_pos = None; 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 // 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) {
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 { if mark.hh_pos == None {
mark.hh_pos = Some(self.formatter.get_horizontal(&self.buffer.text, mark.head)); mark.hh_pos = Some(self.formatter.get_horizontal(&self.buffer.text, mark.head));
} }
@ -454,14 +468,16 @@ impl Editor {
mark.tail = temp_index; 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 // Adjust view
self.move_view_to_cursor(); self.move_view_to_cursor();
} }
pub fn cursor_down(&mut self, n: usize) { 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 { if mark.hh_pos == None {
mark.hh_pos = Some(self.formatter.get_horizontal(&self.buffer.text, mark.head)); mark.hh_pos = Some(self.formatter.get_horizontal(&self.buffer.text, mark.head));
} }
@ -489,7 +505,9 @@ impl Editor {
mark.tail = temp_index; 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 // Adjust view
self.move_view_to_cursor(); self.move_view_to_cursor();
@ -497,9 +515,10 @@ impl Editor {
pub fn page_up(&mut self) { pub fn page_up(&mut self) {
let move_amount = self.view_dim.0 - max(self.view_dim.0 / 8, 1); 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.editor_states[self.state_idx].view_position.head =
self.formatter.offset_vertical(
&self.buffer.text, &self.buffer.text,
self.buffer.mark_sets[self.v_msi][0].head, self.buffer.editor_states[self.state_idx].view_position.head,
-1 * move_amount as isize, -1 * move_amount as isize,
); );
@ -511,9 +530,10 @@ impl Editor {
pub fn page_down(&mut self) { pub fn page_down(&mut self) {
let move_amount = self.view_dim.0 - max(self.view_dim.0 / 8, 1); 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.editor_states[self.state_idx].view_position.head =
self.formatter.offset_vertical(
&self.buffer.text, &self.buffer.text,
self.buffer.mark_sets[self.v_msi][0].head, self.buffer.editor_states[self.state_idx].view_position.head,
move_amount as isize, move_amount as isize,
); );
@ -524,12 +544,15 @@ impl Editor {
} }
pub fn jump_to_line(&mut self, n: usize) { pub fn jump_to_line(&mut self, n: usize) {
self.buffer.mark_sets[self.c_msi].reduce_to_main(); self.buffer.editor_states[self.state_idx]
if self.buffer.mark_sets[self.c_msi][0].hh_pos == None { .cursors
self.buffer.mark_sets[self.c_msi][0].hh_pos = Some( .reduce_to_main();
self.formatter if self.buffer.editor_states[self.state_idx].cursors[0].hh_pos == None {
.get_horizontal(&self.buffer.text, self.buffer.mark_sets[self.c_msi][0].head), 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 let pos = self
@ -539,11 +562,13 @@ impl Editor {
let pos = self.formatter.set_horizontal( let pos = self.formatter.set_horizontal(
&self.buffer.text, &self.buffer.text,
pos, 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.editor_states[self.state_idx].cursors[0].head = pos;
self.buffer.mark_sets[self.c_msi][0].tail = pos; self.buffer.editor_states[self.state_idx].cursors[0].tail = pos;
// Adjust view // Adjust view
self.move_view_to_cursor(); self.move_view_to_cursor();

View File

@ -501,7 +501,11 @@ impl TermUI {
// TODO: use view instead of cursor for calculation if there is more // TODO: use view instead of cursor for calculation if there is more
// than one cursor. // than one cursor.
let percentage: usize = if editor.buffer.text.len() > 0 { 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)) / (editor.buffer.text.len() as f32))
* 100.0) as usize * 100.0) as usize
} else { } else {
@ -540,8 +544,10 @@ impl TermUI {
} }
fn draw_editor_text(&self, editor: &Editor, c1: (usize, usize), c2: (usize, usize)) { 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 view_pos = editor.buffer.editor_states[editor.state_idx]
let cursors = &editor.buffer.mark_sets[editor.c_msi]; .view_position
.head;
let cursors = &editor.buffer.editor_states[editor.state_idx].cursors;
// Calculate all the starting info // Calculate all the starting info
let gutter_width = editor.editor_dim.1 - editor.view_dim.1; let gutter_width = editor.editor_dim.1 - editor.view_dim.1;

View File

@ -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.

View File

@ -7,7 +7,11 @@ use std::{
use ropey::{LineType, Rope}; 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. /// The line type that should be used as the standard line metric in a buffer.
pub const BUFLINE: LineType = LineType::LF_CR; pub const BUFLINE: LineType = LineType::LF_CR;
@ -26,10 +30,9 @@ pub enum BufferPath {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Buffer { pub struct Buffer {
pub text: Rope, // The actual text content. pub text: Rope, // The actual text content.
pub mark_sets: Vec<MarkSet>, // MarkSets for cursors.
pub path: BufferPath, pub path: BufferPath,
pub edits_since_saved: i64, // Tracks whether this buffer is out of sync with the last save. pub edits_since_saved: i64, // Tracks whether this buffer is out of sync with the last save.
pub editor_states: Vec<EditorState>,
history: History, history: History,
} }
@ -40,7 +43,7 @@ impl Buffer {
path: path, path: path,
edits_since_saved: 0, edits_since_saved: 0,
text: text, text: text,
mark_sets: Vec::new(), editor_states: Vec::new(),
history: History::new(), history: History::new(),
} }
} }
@ -91,14 +94,13 @@ impl Buffer {
// Build the change. // Build the change.
let change = Change::from_edit(start, &Cow::from(self.text.slice(start..end)), text); let change = Change::from_edit(start, &Cow::from(self.text.slice(start..end)), text);
// Update mark sets. // Update editor states.
for mark_set in self.mark_sets.iter_mut() { for context in self.editor_states.iter_mut() {
change.apply_to_marks(mark_set); context.apply_change(&change);
mark_set.make_consistent();
} }
// Apply the edit to the document. // Apply the edit to the document.
change.apply(&mut self.text); self.text.apply_change(&change);
// Update undo stack. // Update undo stack.
self.history.push_edit(change); self.history.push_edit(change);
@ -113,14 +115,13 @@ impl Buffer {
if let Some(change) = self.history.undo() { if let Some(change) = self.history.undo() {
self.edits_since_saved = self.edits_since_saved.wrapping_sub(1); self.edits_since_saved = self.edits_since_saved.wrapping_sub(1);
// Update mark sets. // Update editor states.
for mark_set in self.mark_sets.iter_mut() { for context in self.editor_states.iter_mut() {
change.apply_inverse_to_marks(mark_set); context.apply_change_inverse(change);
mark_set.make_consistent();
} }
// Apply the edit to the document. // Apply the edit to the document.
change.apply_inverse(&mut self.text); self.text.apply_change_inverse(change);
return Some(change); return Some(change);
} else { } else {
@ -137,14 +138,13 @@ impl Buffer {
if let Some(change) = self.history.redo() { if let Some(change) = self.history.redo() {
self.edits_since_saved = self.edits_since_saved.wrapping_add(1); self.edits_since_saved = self.edits_since_saved.wrapping_add(1);
// Update mark sets. // Update editor states.
for mark_set in self.mark_sets.iter_mut() { for context in self.editor_states.iter_mut() {
change.apply_to_marks(mark_set); context.apply_change(change);
mark_set.make_consistent();
} }
// Apply the edit to the document. // Apply the edit to the document.
change.apply(&mut self.text); self.text.apply_change(change);
return Some(change); return Some(change);
} else { } else {
@ -152,9 +152,9 @@ impl Buffer {
} }
} }
/// Creates a new empty mark set, and returns the set index. /// Creates a new editor state for the buffer, and returns its index.
pub fn add_mark_set(&mut self) -> usize { pub fn add_editor_state(&mut self) -> usize {
self.mark_sets.push(MarkSet::new()); self.editor_states.push(EditorState::new());
return self.mark_sets.len() - 1; return self.editor_states.len() - 1;
} }
} }

View File

@ -1,9 +1,7 @@
use ropey::Rope; use ropey::Rope;
use crate::marks::MarkSet;
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
enum Op { pub enum Op {
Retain(usize), // In bytes. Retain(usize), // In bytes.
Replace { Replace {
// These both represent strings, and are byte-index ranges into // These both represent strings, and are byte-index ranges into
@ -17,7 +15,7 @@ enum Op {
impl Op { impl Op {
/// The length of the string segment this Op represents *before* /// The length of the string segment this Op represents *before*
/// its application. In bytes. /// its application. In bytes.
fn len_pre(&self) -> usize { pub fn len_pre(&self) -> usize {
match self { match self {
Op::Retain(byte_count) => *byte_count, Op::Retain(byte_count) => *byte_count,
Op::Replace { old, .. } => old.1 - old.0, Op::Replace { old, .. } => old.1 - old.0,
@ -26,7 +24,7 @@ impl Op {
/// The length of the string segment this Op represents *after* /// The length of the string segment this Op represents *after*
/// its application. In bytes. /// its application. In bytes.
fn len_post(&self) -> usize { pub fn len_post(&self) -> usize {
match self { match self {
Op::Retain(byte_count) => *byte_count, Op::Retain(byte_count) => *byte_count,
Op::Replace { new, .. } => new.1 - new.0, Op::Replace { new, .. } => new.1 - new.0,
@ -37,8 +35,8 @@ impl Op {
/// A reversable set of edits treated as an atomic unit. /// A reversable set of edits treated as an atomic unit.
#[derive(Clone)] #[derive(Clone)]
pub struct Change { pub struct Change {
ops: Vec<Op>, pub ops: Vec<Op>,
buffer: String, pub buffer: String,
} }
impl Change { impl Change {
@ -320,168 +318,6 @@ impl Change {
inv 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. /// Pushes a retain op onto the change operations.
@ -566,142 +402,72 @@ impl std::fmt::Debug for Change {
} }
} }
#[cfg(test)] pub trait ApplyChange {
mod tests { /// Applies a Change.
use super::*; fn apply_change(&mut self, change: &Change);
use crate::marks::{Mark, MarkSet};
#[test] /// Applies the inverse of a Change.
fn apply_to_marks_01() { ///
let change = Change::from_edit(5, "", "hi"); /// 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(); impl ApplyChange for Rope {
mark_set.add_mark(Mark::new(4, 4)); fn apply_change(&mut self, change: &Change) {
mark_set.add_mark(Mark::new(5, 5)); let mut i = 0;
mark_set.add_mark(Mark::new(6, 6)); 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); if !old.is_empty() {
assert_eq!(mark_set.marks[0].head, 4); debug_assert_eq!(self.slice(i..(i + old.len())), old);
assert_eq!(mark_set.marks[0].tail, 4); self.remove(i..(i + old.len()));
assert_eq!(mark_set.marks[1].head, 7); }
assert_eq!(mark_set.marks[1].tail, 7); if !new.is_empty() {
assert_eq!(mark_set.marks[2].head, 8); self.insert(i, new);
assert_eq!(mark_set.marks[2].tail, 8);
} }
#[test] i = i + new.len();
fn apply_to_marks_02() { }
let change = Change { }
ops: vec![ }
Op::Retain(5), debug_assert!(i <= self.len());
}
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 { Op::Replace {
old: (0, 0), old: old_range,
new: (0, 1), new: new_range,
}, } => {
Op::Replace { // Swap old and new, to do the inverse.
old: (0, 0), let old = &change.buffer[new_range.0..new_range.1];
new: (1, 2), let new = &change.buffer[old_range.0..old_range.1];
},
],
buffer: "hi".into(),
};
let mut mark_set = MarkSet::new(); if !old.is_empty() {
mark_set.add_mark(Mark::new(4, 4)); debug_assert_eq!(self.slice(i..(i + old.len())), old);
mark_set.add_mark(Mark::new(5, 5)); self.remove(i..(i + old.len()));
mark_set.add_mark(Mark::new(6, 6)); }
if !new.is_empty() {
change.apply_to_marks(&mut mark_set); self.insert(i, new);
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] i = i + new.len();
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);
} }
debug_assert!(i <= self.len());
#[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);
} }
} }

View File

@ -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. /// Contains all state that's unique to a buffer-editor pair.
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Editor { pub struct EditorState {
open_buffers: Vec<Buffer>, 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);
}
} }

View File

@ -1,3 +1,5 @@
use crate::change::{ApplyChange, Change};
/// A mark on a piece of text, useful for representing cursors, selections, and /// A mark on a piece of text, useful for representing cursors, selections, and
/// general positions within a piece of text. /// 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. /// 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<usize> for MarkSet { impl std::ops::Index<usize> for MarkSet {
type Output = Mark; type Output = Mark;
@ -252,3 +368,143 @@ impl std::ops::IndexMut<usize> for MarkSet {
&mut self.marks[index] &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);
}
}

View File

@ -6,7 +6,7 @@ use proptest::test_runner::Config;
use ropey::Rope; 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) { fn make_edit_list_valid(edits: &mut Vec<(usize, usize, String)>, start_text: &str) {
edits.sort_by_key(|e| e.0); edits.sort_by_key(|e| e.0);
@ -45,7 +45,7 @@ proptest! {
(e.0, &start_text[e.0..(e.0+e.1)], &e.2[..]) (e.0, &start_text[e.0..(e.0+e.1)], &e.2[..])
})); }));
let mut r1 = Rope::from_str(start_text); let mut r1 = Rope::from_str(start_text);
change1.apply(&mut r1); r1.apply_change(&change1);
let text1: String = r1.clone().into(); let text1: String = r1.clone().into();
// Make edits2 valid and compute the post-edits2 string. // 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| { let change2 = Change::from_ordered_edit_set(edits2.iter().map(|e| {
(e.0, &text1[e.0..(e.0+e.1)], &e.2[..]) (e.0, &text1[e.0..(e.0+e.1)], &e.2[..])
})); }));
change2.apply(&mut r1); r1.apply_change(&change2);
// Do the test! // Do the test!
let change3 = change1.compose(&change2); let change3 = change1.compose(&change2);
let mut r2 = Rope::from_str(start_text); let mut r2 = Rope::from_str(start_text);
change3.apply(&mut r2); r2.apply_change(&change3);
assert_eq!(r1, r2); assert_eq!(r1, r2);
} }