transaction: Use builder methods to generate compact changesets.

pull/8/head
Blaž Hrastnik 4 years ago
parent 19fb4ed835
commit b4312c9492

@ -451,7 +451,7 @@ impl LanguageLayer {
Delete(i) | Retain(i) => *i, Delete(i) | Retain(i) => *i,
Insert(_) => 0, Insert(_) => 0,
}; };
let old_end = old_pos + len; let mut old_end = old_pos + len;
match change { match change {
Retain(_) => { Retain(_) => {
@ -467,23 +467,6 @@ impl LanguageLayer {
// let line_start_byte = line_to_byte() // let line_start_byte = line_to_byte()
// Position::new(line, line_start_byte - byte) // Position::new(line, line_start_byte - byte)
// a subsequent ins means a replace, consume it
if let Some(Insert(s)) = iter.peek() {
iter.next();
let ins = s.chars().count();
// replacement
edits.push(tree_sitter::InputEdit {
start_byte, // old_pos to byte
old_end_byte, // old_end to byte
new_end_byte: start_byte + s.len(), // old_pos to byte + s.len()
start_position, // old pos to coords
old_end_position, // old_end to coords
new_end_position: traverse(start_position, s), // old pos + chars, newlines matter too (iter over)
});
new_pos += ins;
} else {
// deletion // deletion
edits.push(tree_sitter::InputEdit { edits.push(tree_sitter::InputEdit {
start_byte, // old_pos to byte start_byte, // old_pos to byte
@ -493,13 +476,29 @@ impl LanguageLayer {
old_end_position, // old_end to coords old_end_position, // old_end to coords
new_end_position: start_position, // old pos to coords new_end_position: start_position, // old pos to coords
}); });
};
} }
Insert(s) => { Insert(s) => {
let (start_byte, start_position) = point_at_pos(&old_text, old_pos); let (start_byte, start_position) = point_at_pos(&old_text, old_pos);
let ins = s.chars().count(); let ins = s.chars().count();
// a subsequent delete means a replace, consume it
if let Some(Delete(len)) = iter.peek() {
old_end = old_pos + len;
let (old_end_byte, old_end_position) = point_at_pos(&old_text, old_end);
iter.next();
// replacement
edits.push(tree_sitter::InputEdit {
start_byte, // old_pos to byte
old_end_byte, // old_end to byte
new_end_byte: start_byte + s.len(), // old_pos to byte + s.len()
start_position, // old pos to coords
old_end_position, // old_end to coords
new_end_position: traverse(start_position, s), // old pos + chars, newlines matter too (iter over)
});
} else {
// insert // insert
edits.push(tree_sitter::InputEdit { edits.push(tree_sitter::InputEdit {
start_byte, // old_pos to byte start_byte, // old_pos to byte
@ -509,6 +508,7 @@ impl LanguageLayer {
old_end_position: start_position, // same old_end_position: start_position, // same
new_end_position: traverse(start_position, s), // old pos + chars, newlines matter too (iter over) new_end_position: traverse(start_position, s), // old pos + chars, newlines matter too (iter over)
}); });
}
new_pos += ins; new_pos += ins;
} }

@ -63,6 +63,56 @@ impl ChangeSet {
len len
} }
// Changeset builder operations: delete/insert/retain
fn delete(&mut self, n: usize) {
use Operation::*;
if n == 0 {
return;
}
if let Some(Delete(count)) = self.changes.last_mut() {
*count += n;
} else {
self.changes.push(Delete(n));
}
}
fn insert(&mut self, fragment: Tendril) {
use Operation::*;
if fragment.is_empty() {
return;
}
let new_last = match self.changes.as_mut_slice() {
[.., Insert(prev)] => {
prev.push_tendril(&fragment);
return;
}
[.., Insert(prev), Delete(_)] => {
prev.push_tendril(&fragment);
return;
}
[.., last @ Delete(_)] => std::mem::replace(last, Insert(fragment)),
_ => Insert(fragment),
};
self.changes.push(new_last);
}
fn retain(&mut self, n: usize) {
use Operation::*;
if n == 0 {
return;
}
if let Some(Retain(count)) = self.changes.last_mut() {
*count += n;
} else {
self.changes.push(Retain(n));
}
}
/// Combine two changesets together. /// Combine two changesets together.
/// In other words, If `this` goes `docA` → `docB` and `other` represents `docB` → `docC`, the /// In other words, If `this` goes `docA` → `docB` and `other` represents `docB` → `docC`, the
/// returned value will represent the change `docA` → `docC`. /// returned value will represent the change `docA` → `docC`.
@ -77,7 +127,10 @@ impl ChangeSet {
let mut head_a = changes_a.next(); let mut head_a = changes_a.next();
let mut head_b = changes_b.next(); let mut head_b = changes_b.next();
let mut changes: Vec<Operation> = Vec::with_capacity(len); // TODO: max(a, b), shrink_to_fit() afterwards let mut changes = Self {
len: self.len,
changes: Vec::with_capacity(len), // TODO: max(a, b), shrink_to_fit() afterwards
};
loop { loop {
use std::cmp::Ordering; use std::cmp::Ordering;
@ -88,37 +141,31 @@ impl ChangeSet {
break; break;
} }
// deletion in A // deletion in A
(Some(change @ Delete(..)), b) => { (Some(Delete(i)), b) => {
changes.push(change); changes.delete(i);
head_a = changes_a.next(); head_a = changes_a.next();
head_b = b; head_b = b;
} }
// insertion in B // insertion in B
(a, Some(Insert(current))) => { (a, Some(Insert(current))) => {
// merge onto previous insert if possible changes.insert(current);
// TODO: do these as operations on a changeset
if let Some(Insert(prev)) = changes.last_mut() {
prev.push_tendril(&current);
} else {
changes.push(Insert(current));
}
head_a = a; head_a = a;
head_b = changes_b.next(); head_b = changes_b.next();
} }
(None, _) | (_, None) => return unreachable!(), (None, _) | (_, None) => return unreachable!(),
(Some(Retain(i)), Some(Retain(j))) => match i.cmp(&j) { (Some(Retain(i)), Some(Retain(j))) => match i.cmp(&j) {
Ordering::Less => { Ordering::Less => {
changes.push(Retain(i)); changes.retain(i);
head_a = changes_a.next(); head_a = changes_a.next();
head_b = Some(Retain(j - i)); head_b = Some(Retain(j - i));
} }
Ordering::Equal => { Ordering::Equal => {
changes.push(Retain(i)); changes.retain(i);
head_a = changes_a.next(); head_a = changes_a.next();
head_b = changes_b.next(); head_b = changes_b.next();
} }
Ordering::Greater => { Ordering::Greater => {
changes.push(Retain(j)); changes.retain(j);
head_a = Some(Retain(i - j)); head_a = Some(Retain(i - j));
head_b = changes_b.next(); head_b = changes_b.next();
} }
@ -147,12 +194,12 @@ impl ChangeSet {
let len = s.chars().count(); let len = s.chars().count();
match len.cmp(&j) { match len.cmp(&j) {
Ordering::Less => { Ordering::Less => {
changes.push(Insert(s)); changes.insert(s);
head_a = changes_a.next(); head_a = changes_a.next();
head_b = Some(Retain(j - len)); head_b = Some(Retain(j - len));
} }
Ordering::Equal => { Ordering::Equal => {
changes.push(Insert(s)); changes.insert(s);
head_a = changes_a.next(); head_a = changes_a.next();
head_b = changes_b.next(); head_b = changes_b.next();
} }
@ -160,7 +207,7 @@ impl ChangeSet {
// figure out the byte index of the truncated string end // figure out the byte index of the truncated string end
let (pos, _) = s.char_indices().nth(j).unwrap(); let (pos, _) = s.char_indices().nth(j).unwrap();
let pos = pos as u32; let pos = pos as u32;
changes.push(Insert(s.subtendril(0, pos))); changes.insert(s.subtendril(0, pos));
head_a = Some(Insert(s.subtendril(pos, s.len() as u32 - pos))); head_a = Some(Insert(s.subtendril(pos, s.len() as u32 - pos)));
head_b = changes_b.next(); head_b = changes_b.next();
} }
@ -168,17 +215,17 @@ impl ChangeSet {
} }
(Some(Retain(i)), Some(Delete(j))) => match i.cmp(&j) { (Some(Retain(i)), Some(Delete(j))) => match i.cmp(&j) {
Ordering::Less => { Ordering::Less => {
changes.push(Delete(i)); changes.delete(i);
head_a = changes_a.next(); head_a = changes_a.next();
head_b = Some(Delete(j - i)); head_b = Some(Delete(j - i));
} }
Ordering::Equal => { Ordering::Equal => {
changes.push(Delete(j)); changes.delete(j);
head_a = changes_a.next(); head_a = changes_a.next();
head_b = changes_b.next(); head_b = changes_b.next();
} }
Ordering::Greater => { Ordering::Greater => {
changes.push(Delete(j)); changes.delete(j);
head_a = Some(Retain(i - j)); head_a = Some(Retain(i - j));
head_b = changes_b.next(); head_b = changes_b.next();
} }
@ -186,10 +233,7 @@ impl ChangeSet {
}; };
} }
Self { changes
len: self.len,
changes,
}
} }
/// Given another change set starting in the same document, maps this /// Given another change set starting in the same document, maps this
@ -421,37 +465,29 @@ impl Transaction {
I: IntoIterator<Item = Change> + ExactSizeIterator, I: IntoIterator<Item = Change> + ExactSizeIterator,
{ {
let len = state.doc.len_chars(); let len = state.doc.len_chars();
let mut acc = Vec::with_capacity(2 * changes.len() + 1); let acc = Vec::with_capacity(2 * changes.len() + 1);
let mut changeset = ChangeSet { changes: acc, len };
// TODO: verify ranges are ordered and not overlapping or change will panic. // TODO: verify ranges are ordered and not overlapping or change will panic.
let mut last = 0; let mut last = 0;
for (from, to, tendril) in changes { for (from, to, tendril) in changes {
// Retain from last "to" to current "from" // Retain from last "to" to current "from"
if from - last > 0 { changeset.retain(from - last);
acc.push(Operation::Retain(from - last));
}
let span = to - from; let span = to - from;
match tendril { match tendril {
Some(text) => { Some(text) => {
if span > 0 { changeset.delete(span);
acc.push(Operation::Delete(span)); changeset.insert(text);
} }
acc.push(Operation::Insert(text)); None => changeset.delete(span),
}
None if span > 0 => acc.push(Operation::Delete(span)),
// empty delete is useless
None => (),
} }
last = to; last = to;
} }
let span = len - last; changeset.retain(len - last);
if span > 0 {
acc.push(Operation::Retain(span));
}
Self::from(ChangeSet { changes: acc, len }) Self::from(changeset)
} }
/// Generate a transaction with a change per selection range. /// Generate a transaction with a change per selection range.
@ -591,7 +627,7 @@ mod test {
} }
#[test] #[test]
fn insert_composition() { fn optimized_composition() {
let mut state = State::new("".into()); let mut state = State::new("".into());
let t1 = Transaction::insert(&state, Tendril::from_char('h')); let t1 = Transaction::insert(&state, Tendril::from_char('h'));
t1.apply(&mut state); t1.apply(&mut state);
@ -620,5 +656,6 @@ mod test {
use Operation::*; use Operation::*;
assert_eq!(changes.changes, &[Insert("hello".into())]); assert_eq!(changes.changes, &[Insert("hello".into())]);
// instead of insert h, insert e, insert l, insert l, insert o
} }
} }

Loading…
Cancel
Save