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 crate::{
|
||||
history::{Edit, History},
|
||||
marks::MarkSet,
|
||||
};
|
||||
use crate::{history::History, marks::MarkSet, transaction::Transaction};
|
||||
|
||||
/// A path for an open text buffer.
|
||||
///
|
||||
|
@ -21,8 +18,8 @@ pub enum BufferPath {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct Buffer {
|
||||
pub path: BufferPath,
|
||||
pub is_dirty: bool, // Is this buffer currently out of sync with disk.
|
||||
pub text: Rope, // The actual text content.
|
||||
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 mark_sets: Vec<MarkSet>, // MarkSets for cursors, view positions, etc.
|
||||
history: History,
|
||||
}
|
||||
|
@ -31,19 +28,35 @@ impl Buffer {
|
|||
pub fn new(text: Rope, path: BufferPath) -> Buffer {
|
||||
Buffer {
|
||||
path: path,
|
||||
is_dirty: false,
|
||||
edits_since_saved: 0,
|
||||
text: text,
|
||||
mark_sets: Vec::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.
|
||||
///
|
||||
/// The range does not have to be ordered (i.e. the first component can be
|
||||
/// greater than the second).
|
||||
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.
|
||||
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)
|
||||
};
|
||||
|
||||
// Update undo stack.
|
||||
if char_idx_range.0 == char_idx_range.1 {
|
||||
// Fast-path for insertion-only edits.
|
||||
self.history.push_edit(Edit {
|
||||
char_idx: start,
|
||||
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(),
|
||||
});
|
||||
}
|
||||
// Build the transaction.
|
||||
let trans = Transaction::from_edit(
|
||||
self.text.char_to_byte(start),
|
||||
&Cow::from(self.text.slice(start..end)),
|
||||
text,
|
||||
);
|
||||
|
||||
// Update mark sets.
|
||||
let post_len = text.chars().count();
|
||||
for mark_set in self.mark_sets.iter_mut() {
|
||||
for mark in mark_set.iter_mut() {
|
||||
*mark = mark.edit((start, end), post_len);
|
||||
}
|
||||
|
||||
trans.apply_to_marks(mark_set);
|
||||
mark_set.make_consistent();
|
||||
}
|
||||
|
||||
// Do removal if needed.
|
||||
if start != end {
|
||||
self.text.remove(start..end);
|
||||
}
|
||||
// Apply the edit to the document.
|
||||
trans.apply(&mut self.text);
|
||||
|
||||
// Do insertion if needed.
|
||||
if !text.is_empty() {
|
||||
self.text.insert(start, text);
|
||||
}
|
||||
// Update undo stack.
|
||||
self.history.push_edit(trans);
|
||||
}
|
||||
|
||||
/// Un-does the last edit if there is one, and returns the range of the
|
||||
/// edited characters which can be used for e.g. placing a cursor or moving
|
||||
/// the view.
|
||||
/// Un-does the last edit if there is one, and returns the
|
||||
/// Transaction that was inverse-applied, in case it's needed for
|
||||
/// further processing.
|
||||
///
|
||||
/// Returns None if there is no edit to undo.
|
||||
pub fn undo(&mut self) -> Option<(usize, usize)> {
|
||||
if let Some(ed) = self.history.undo() {
|
||||
self.is_dirty = true;
|
||||
|
||||
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);
|
||||
pub fn undo(&mut self) -> Option<&Transaction> {
|
||||
if let Some(trans) = 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() {
|
||||
for mark in mark_set.iter_mut() {
|
||||
*mark = mark.edit((start, end), post_len);
|
||||
}
|
||||
|
||||
trans.apply_inverse_to_marks(mark_set);
|
||||
mark_set.make_consistent();
|
||||
}
|
||||
|
||||
// Do removal if needed.
|
||||
if start != end {
|
||||
self.text.remove(start..end);
|
||||
}
|
||||
// Apply the edit to the document.
|
||||
trans.apply_inverse(&mut self.text);
|
||||
|
||||
// Do insertion if needed.
|
||||
if !ed.from.is_empty() {
|
||||
self.text.insert(start, &ed.from);
|
||||
}
|
||||
|
||||
return Some((start, start + post_len));
|
||||
return Some(trans);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
/// Re-does the last edit if there is one, and returns the range of the
|
||||
/// edited characters which can be used for e.g. placing a cursor or moving
|
||||
/// the view.
|
||||
/// Re-does the last edit if there is one, and returns the
|
||||
/// Transaction that was applied, in case it's needed for further
|
||||
/// processing.
|
||||
///
|
||||
/// Returns None if there is no edit to redo.
|
||||
pub fn redo(&mut self) -> Option<(usize, usize)> {
|
||||
if let Some(ed) = self.history.redo() {
|
||||
self.is_dirty = true;
|
||||
|
||||
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);
|
||||
pub fn redo(&mut self) -> Option<&Transaction> {
|
||||
if let Some(trans) = 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() {
|
||||
for mark in mark_set.iter_mut() {
|
||||
*mark = mark.edit((start, end), post_len);
|
||||
}
|
||||
|
||||
trans.apply_to_marks(mark_set);
|
||||
mark_set.make_consistent();
|
||||
}
|
||||
|
||||
// Do removal if needed.
|
||||
if start != end {
|
||||
self.text.remove(start..end);
|
||||
}
|
||||
// Apply the edit to the document.
|
||||
trans.apply(&mut self.text);
|
||||
|
||||
// Do insertion if needed.
|
||||
if !ed.to.is_empty() {
|
||||
self.text.insert(start, &ed.to);
|
||||
}
|
||||
|
||||
return Some((start, start + post_len));
|
||||
return Some(trans);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use crate::transaction::Transaction;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct History {
|
||||
edits: Vec<Edit>,
|
||||
edits: Vec<Transaction>,
|
||||
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.push(edit);
|
||||
self.position += 1;
|
||||
}
|
||||
|
||||
pub fn undo(&mut self) -> Option<&Edit> {
|
||||
pub fn undo(&mut self) -> Option<&Transaction> {
|
||||
if self.position > 0 {
|
||||
self.position -= 1;
|
||||
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() {
|
||||
let edit = &self.edits[self.position];
|
||||
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
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn apply(&self, text: &mut Rope) {
|
||||
let mut i = 0;
|
||||
|
@ -327,8 +304,43 @@ impl Transaction {
|
|||
Op::Replace { old, new } => {
|
||||
let old = &self.buffer[old.0..old.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() {
|
||||
let old_char_len = old.chars().count();
|
||||
debug_assert_eq!(text.slice(char_i..(char_i + old_char_len)), old);
|
||||
|
@ -350,6 +362,15 @@ impl Transaction {
|
|||
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.
|
||||
|
|
Loading…
Reference in New Issue
Block a user