Switch other backend code over to use transactions.
This commit is contained in:
parent
696ecb732d
commit
3f3cf5dd3a
|
@ -1,11 +1,8 @@
|
||||||
use std::path::PathBuf;
|
use std::{borrow::Cow, path::PathBuf};
|
||||||
|
|
||||||
use ropey::Rope;
|
use ropey::Rope;
|
||||||
|
|
||||||
use crate::{
|
use crate::{history::History, marks::MarkSet, transaction::Transaction};
|
||||||
history::{Edit, History},
|
|
||||||
marks::MarkSet,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A path for an open text buffer.
|
/// A path for an open text buffer.
|
||||||
///
|
///
|
||||||
|
@ -21,8 +18,8 @@ pub enum BufferPath {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Buffer {
|
pub struct Buffer {
|
||||||
pub path: BufferPath,
|
pub path: BufferPath,
|
||||||
pub is_dirty: bool, // Is this buffer currently out of sync with disk.
|
pub edits_since_saved: i64, // Tracks whether this buffer is out of sync with the last save.
|
||||||
pub text: Rope, // The actual text content.
|
pub text: Rope, // The actual text content.
|
||||||
pub mark_sets: Vec<MarkSet>, // MarkSets for cursors, view positions, etc.
|
pub mark_sets: Vec<MarkSet>, // MarkSets for cursors, view positions, etc.
|
||||||
history: History,
|
history: History,
|
||||||
}
|
}
|
||||||
|
@ -31,19 +28,35 @@ impl Buffer {
|
||||||
pub fn new(text: Rope, path: BufferPath) -> Buffer {
|
pub fn new(text: Rope, path: BufferPath) -> Buffer {
|
||||||
Buffer {
|
Buffer {
|
||||||
path: path,
|
path: path,
|
||||||
is_dirty: false,
|
edits_since_saved: 0,
|
||||||
text: text,
|
text: text,
|
||||||
mark_sets: Vec::new(),
|
mark_sets: Vec::new(),
|
||||||
history: History::new(),
|
history: History::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_dirty(&self) -> bool {
|
||||||
|
self.edits_since_saved != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Saves the buffer to disk.
|
||||||
|
pub fn save(&mut self) {
|
||||||
|
self.edits_since_saved = 0;
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
/// Replaces the given range of chars with the given text.
|
/// Replaces the given range of chars with the given text.
|
||||||
///
|
///
|
||||||
/// The range does not have to be ordered (i.e. the first component can be
|
/// The range does not have to be ordered (i.e. the first component can be
|
||||||
/// greater than the second).
|
/// greater than the second).
|
||||||
pub fn edit(&mut self, char_idx_range: (usize, usize), text: &str) {
|
pub fn edit(&mut self, char_idx_range: (usize, usize), text: &str) {
|
||||||
self.is_dirty = true;
|
if self.edits_since_saved >= 0 {
|
||||||
|
self.edits_since_saved = self.edits_since_saved.wrapping_add(1);
|
||||||
|
} else {
|
||||||
|
// The undo state matching the last save is inaccessible, so
|
||||||
|
// set this to a similarly inaccessible number.
|
||||||
|
self.edits_since_saved = i64::MAX;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the range, properly ordered.
|
// Get the range, properly ordered.
|
||||||
let (start, end) = if char_idx_range.0 < char_idx_range.1 {
|
let (start, end) = if char_idx_range.0 < char_idx_range.1 {
|
||||||
|
@ -52,114 +65,69 @@ impl Buffer {
|
||||||
(char_idx_range.1, char_idx_range.0)
|
(char_idx_range.1, char_idx_range.0)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update undo stack.
|
// Build the transaction.
|
||||||
if char_idx_range.0 == char_idx_range.1 {
|
let trans = Transaction::from_edit(
|
||||||
// Fast-path for insertion-only edits.
|
self.text.char_to_byte(start),
|
||||||
self.history.push_edit(Edit {
|
&Cow::from(self.text.slice(start..end)),
|
||||||
char_idx: start,
|
text,
|
||||||
from: String::new(),
|
);
|
||||||
to: text.into(),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
self.history.push_edit(Edit {
|
|
||||||
char_idx: start,
|
|
||||||
from: self.text.slice(start..end).into(),
|
|
||||||
to: text.into(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update mark sets.
|
// Update mark sets.
|
||||||
let post_len = text.chars().count();
|
|
||||||
for mark_set in self.mark_sets.iter_mut() {
|
for mark_set in self.mark_sets.iter_mut() {
|
||||||
for mark in mark_set.iter_mut() {
|
trans.apply_to_marks(mark_set);
|
||||||
*mark = mark.edit((start, end), post_len);
|
|
||||||
}
|
|
||||||
|
|
||||||
mark_set.make_consistent();
|
mark_set.make_consistent();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do removal if needed.
|
// Apply the edit to the document.
|
||||||
if start != end {
|
trans.apply(&mut self.text);
|
||||||
self.text.remove(start..end);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do insertion if needed.
|
// Update undo stack.
|
||||||
if !text.is_empty() {
|
self.history.push_edit(trans);
|
||||||
self.text.insert(start, text);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Un-does the last edit if there is one, and returns the range of the
|
/// Un-does the last edit if there is one, and returns the
|
||||||
/// edited characters which can be used for e.g. placing a cursor or moving
|
/// Transaction that was inverse-applied, in case it's needed for
|
||||||
/// the view.
|
/// further processing.
|
||||||
///
|
///
|
||||||
/// Returns None if there is no edit to undo.
|
/// Returns None if there is no edit to undo.
|
||||||
pub fn undo(&mut self) -> Option<(usize, usize)> {
|
pub fn undo(&mut self) -> Option<&Transaction> {
|
||||||
if let Some(ed) = self.history.undo() {
|
if let Some(trans) = self.history.undo() {
|
||||||
self.is_dirty = true;
|
self.edits_since_saved = self.edits_since_saved.wrapping_sub(1);
|
||||||
|
|
||||||
let pre_len = ed.to.chars().count();
|
|
||||||
let post_len = ed.from.chars().count();
|
|
||||||
let (start, end) = (ed.char_idx, ed.char_idx + pre_len);
|
|
||||||
|
|
||||||
// Update mark sets.
|
// Update mark sets.
|
||||||
for mark_set in self.mark_sets.iter_mut() {
|
for mark_set in self.mark_sets.iter_mut() {
|
||||||
for mark in mark_set.iter_mut() {
|
trans.apply_inverse_to_marks(mark_set);
|
||||||
*mark = mark.edit((start, end), post_len);
|
|
||||||
}
|
|
||||||
|
|
||||||
mark_set.make_consistent();
|
mark_set.make_consistent();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do removal if needed.
|
// Apply the edit to the document.
|
||||||
if start != end {
|
trans.apply_inverse(&mut self.text);
|
||||||
self.text.remove(start..end);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do insertion if needed.
|
return Some(trans);
|
||||||
if !ed.from.is_empty() {
|
|
||||||
self.text.insert(start, &ed.from);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Some((start, start + post_len));
|
|
||||||
} else {
|
} else {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Re-does the last edit if there is one, and returns the range of the
|
/// Re-does the last edit if there is one, and returns the
|
||||||
/// edited characters which can be used for e.g. placing a cursor or moving
|
/// Transaction that was applied, in case it's needed for further
|
||||||
/// the view.
|
/// processing.
|
||||||
///
|
///
|
||||||
/// Returns None if there is no edit to redo.
|
/// Returns None if there is no edit to redo.
|
||||||
pub fn redo(&mut self) -> Option<(usize, usize)> {
|
pub fn redo(&mut self) -> Option<&Transaction> {
|
||||||
if let Some(ed) = self.history.redo() {
|
if let Some(trans) = self.history.redo() {
|
||||||
self.is_dirty = true;
|
self.edits_since_saved = self.edits_since_saved.wrapping_add(1);
|
||||||
|
|
||||||
let pre_len = ed.from.chars().count();
|
|
||||||
let post_len = ed.to.chars().count();
|
|
||||||
let (start, end) = (ed.char_idx, ed.char_idx + pre_len);
|
|
||||||
|
|
||||||
// Update mark sets.
|
// Update mark sets.
|
||||||
for mark_set in self.mark_sets.iter_mut() {
|
for mark_set in self.mark_sets.iter_mut() {
|
||||||
for mark in mark_set.iter_mut() {
|
trans.apply_to_marks(mark_set);
|
||||||
*mark = mark.edit((start, end), post_len);
|
|
||||||
}
|
|
||||||
|
|
||||||
mark_set.make_consistent();
|
mark_set.make_consistent();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do removal if needed.
|
// Apply the edit to the document.
|
||||||
if start != end {
|
trans.apply(&mut self.text);
|
||||||
self.text.remove(start..end);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do insertion if needed.
|
return Some(trans);
|
||||||
if !ed.to.is_empty() {
|
|
||||||
self.text.insert(start, &ed.to);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Some((start, start + post_len));
|
|
||||||
} else {
|
} else {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
use crate::transaction::Transaction;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct History {
|
pub struct History {
|
||||||
edits: Vec<Edit>,
|
edits: Vec<Transaction>,
|
||||||
position: usize, // Where we are in the history.
|
position: usize, // Where we are in the history.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,13 +14,13 @@ impl History {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_edit(&mut self, edit: Edit) {
|
pub fn push_edit(&mut self, edit: Transaction) {
|
||||||
self.edits.truncate(self.position);
|
self.edits.truncate(self.position);
|
||||||
self.edits.push(edit);
|
self.edits.push(edit);
|
||||||
self.position += 1;
|
self.position += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn undo(&mut self) -> Option<&Edit> {
|
pub fn undo(&mut self) -> Option<&Transaction> {
|
||||||
if self.position > 0 {
|
if self.position > 0 {
|
||||||
self.position -= 1;
|
self.position -= 1;
|
||||||
Some(&self.edits[self.position])
|
Some(&self.edits[self.position])
|
||||||
|
@ -27,7 +29,7 @@ impl History {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn redo(&mut self) -> Option<&Edit> {
|
pub fn redo(&mut self) -> Option<&Transaction> {
|
||||||
if self.position < self.edits.len() {
|
if self.position < self.edits.len() {
|
||||||
let edit = &self.edits[self.position];
|
let edit = &self.edits[self.position];
|
||||||
self.position += 1;
|
self.position += 1;
|
||||||
|
@ -37,10 +39,3 @@ impl History {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Edit {
|
|
||||||
pub char_idx: usize,
|
|
||||||
pub from: String,
|
|
||||||
pub to: String,
|
|
||||||
}
|
|
||||||
|
|
|
@ -293,29 +293,6 @@ impl Transaction {
|
||||||
trans
|
trans
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build a Transaction that is functionally identical to undoing
|
|
||||||
/// this Transaction.
|
|
||||||
///
|
|
||||||
/// Note: the resulting Transaction will losslessly reverse the
|
|
||||||
/// original Transaction on text content, but will be lossy when
|
|
||||||
/// applied to Marks.
|
|
||||||
#[must_use]
|
|
||||||
pub fn invert(&self) -> Transaction {
|
|
||||||
let mut inverted = self.clone();
|
|
||||||
for op in inverted.ops.iter_mut() {
|
|
||||||
match *op {
|
|
||||||
Op::Retain(_) => {} // Do nothing.
|
|
||||||
Op::Replace {
|
|
||||||
ref mut old,
|
|
||||||
ref mut new,
|
|
||||||
} => {
|
|
||||||
std::mem::swap(old, new);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
inverted
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Applies the Transaction to a Rope.
|
/// Applies the Transaction to a Rope.
|
||||||
pub fn apply(&self, text: &mut Rope) {
|
pub fn apply(&self, text: &mut Rope) {
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
|
@ -327,8 +304,43 @@ impl Transaction {
|
||||||
Op::Replace { old, new } => {
|
Op::Replace { old, new } => {
|
||||||
let old = &self.buffer[old.0..old.1];
|
let old = &self.buffer[old.0..old.1];
|
||||||
let new = &self.buffer[new.0..new.1];
|
let new = &self.buffer[new.0..new.1];
|
||||||
let char_i = text.byte_to_char(i);
|
|
||||||
|
|
||||||
|
let char_i = text.byte_to_char(i);
|
||||||
|
if !old.is_empty() {
|
||||||
|
let old_char_len = old.chars().count();
|
||||||
|
debug_assert_eq!(text.slice(char_i..(char_i + old_char_len)), old);
|
||||||
|
text.remove(char_i..(char_i + old_char_len));
|
||||||
|
}
|
||||||
|
if !new.is_empty() {
|
||||||
|
text.insert(char_i, new);
|
||||||
|
}
|
||||||
|
|
||||||
|
i = i + new.len();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug_assert!(i <= text.len_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies the inverse of the Transaction to a Rope.
|
||||||
|
///
|
||||||
|
/// This is an "undo" of the Transaction.
|
||||||
|
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];
|
||||||
|
|
||||||
|
let char_i = text.byte_to_char(i);
|
||||||
if !old.is_empty() {
|
if !old.is_empty() {
|
||||||
let old_char_len = old.chars().count();
|
let old_char_len = old.chars().count();
|
||||||
debug_assert_eq!(text.slice(char_i..(char_i + old_char_len)), old);
|
debug_assert_eq!(text.slice(char_i..(char_i + old_char_len)), old);
|
||||||
|
@ -350,6 +362,15 @@ impl Transaction {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Applies the inverse of the Transaction to a set of Marks.
|
||||||
|
///
|
||||||
|
/// This is essentially an "undo" of the Transaction. 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) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
//---------------------------------------------------------
|
//---------------------------------------------------------
|
||||||
|
|
||||||
/// Pushes a retain op onto the transaction operations.
|
/// Pushes a retain op onto the transaction operations.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user