Move view position and cursors into a new EditorState struct.
This commit is contained in:
parent
31d468928c
commit
257ce5c1fe
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
8
sub_crates/backend/README.md
Normal file
8
sub_crates/backend/README.md
Normal 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.
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
let mut mark_set = MarkSet::new();
|
/// state (cursor positions, for example).
|
||||||
mark_set.add_mark(Mark::new(4, 4));
|
fn apply_change_inverse(&mut self, change: &Change);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
impl ApplyChange for Rope {
|
||||||
fn apply_to_marks_02() {
|
fn apply_change(&mut self, change: &Change) {
|
||||||
let change = Change {
|
let mut i = 0;
|
||||||
ops: vec![
|
for op in change.ops.iter() {
|
||||||
Op::Retain(5),
|
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 {
|
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();
|
debug_assert!(i <= self.len());
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user