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
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,9 +515,10 @@ 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.editor_states[self.state_idx].view_position.head =
self.formatter.offset_vertical(
&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,
);
@ -511,9 +530,10 @@ 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.editor_states[self.state_idx].view_position.head =
self.formatter.offset_vertical(
&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,
);
@ -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();

View File

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

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 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;
@ -26,10 +30,9 @@ pub enum BufferPath {
#[derive(Debug, Clone)]
pub struct Buffer {
pub text: Rope, // The actual text content.
pub mark_sets: Vec<MarkSet>, // MarkSets for cursors.
pub path: BufferPath,
pub edits_since_saved: i64, // Tracks whether this buffer is out of sync with the last save.
pub editor_states: Vec<EditorState>,
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;
}
}

View File

@ -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<Op>,
buffer: String,
pub ops: Vec<Op>,
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");
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_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);
/// 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);
}
#[test]
fn apply_to_marks_02() {
let change = Change {
ops: vec![
Op::Retain(5),
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];
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());
}
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));
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);
}
#[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());
}
}

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.
#[derive(Debug)]
pub struct Editor {
open_buffers: Vec<Buffer>,
/// 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);
}
}

View File

@ -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<usize> for MarkSet {
type Output = Mark;
@ -252,3 +368,143 @@ impl std::ops::IndexMut<usize> 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);
}
}

View File

@ -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);
}