Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-core/src/transaction.rs')
| -rw-r--r-- | helix-core/src/transaction.rs | 187 |
1 files changed, 151 insertions, 36 deletions
diff --git a/helix-core/src/transaction.rs b/helix-core/src/transaction.rs index 37be2e2e..b419ccf8 100644 --- a/helix-core/src/transaction.rs +++ b/helix-core/src/transaction.rs @@ -361,7 +361,6 @@ impl ChangeSet { pos += s.chars().count(); } } - println!("=>\n{text}"); } true } @@ -521,9 +520,52 @@ impl ChangeSet { pos } - pub fn changes_iter(&self) -> ChangeIterator<'_> { + pub fn changes_iter(&self) -> ChangeIterator { ChangeIterator::new(self) } + + pub fn from_change(doc: &Rope, change: Change) -> Self { + Self::from_changes(doc, std::iter::once(change)) + } + + /// Generate a ChangeSet from a set of changes. + pub fn from_changes<I>(doc: &Rope, changes: I) -> Self + where + I: Iterator<Item = Change>, + { + let len = doc.len_chars(); + + let (lower, upper) = changes.size_hint(); + let size = upper.unwrap_or(lower); + let mut changeset = ChangeSet::with_capacity(2 * size + 1); // rough estimate + + let mut last = 0; + for (from, to, tendril) in changes { + // Verify ranges are ordered and not overlapping + debug_assert!(last <= from); + // Verify ranges are correct + debug_assert!( + from <= to, + "Edit end must end before it starts (should {from} <= {to})" + ); + + // Retain from last "to" to current "from" + changeset.retain(from - last); + let span = to - from; + match tendril { + Some(text) => { + changeset.insert(text); + changeset.delete(span); + } + None => changeset.delete(span), + } + last = to; + } + + changeset.retain(len - last); + + changeset + } } /// Transaction represents a single undoable unit of changes. Several changes can be grouped into @@ -617,38 +659,7 @@ impl Transaction { where I: Iterator<Item = Change>, { - let len = doc.len_chars(); - - let (lower, upper) = changes.size_hint(); - let size = upper.unwrap_or(lower); - let mut changeset = ChangeSet::with_capacity(2 * size + 1); // rough estimate - - let mut last = 0; - for (from, to, tendril) in changes { - // Verify ranges are ordered and not overlapping - debug_assert!(last <= from); - // Verify ranges are correct - debug_assert!( - from <= to, - "Edit end must end before it starts (should {from} <= {to})" - ); - - // Retain from last "to" to current "from" - changeset.retain(from - last); - let span = to - from; - match tendril { - Some(text) => { - changeset.insert(text); - changeset.delete(span); - } - None => changeset.delete(span), - } - last = to; - } - - changeset.retain(len - last); - - Self::from(changeset) + Self::from(ChangeSet::from_changes(doc, changes)) } /// Generate a transaction from a set of potentially overlapping deletions @@ -737,9 +748,60 @@ impl Transaction { ) } + /// Generate a transaction with a change per selection range, which + /// generates a new selection as well. Each range is operated upon by + /// the given function and can optionally produce a new range. If none + /// is returned by the function, that range is mapped through the change + /// as usual. + pub fn change_by_and_with_selection<F>(doc: &Rope, selection: &Selection, mut f: F) -> Self + where + F: FnMut(&Range) -> (Change, Option<Range>), + { + let mut end_ranges = SmallVec::with_capacity(selection.len()); + let mut offset = 0; + + let transaction = Transaction::change_by_selection(doc, selection, |start_range| { + let ((from, to, replacement), end_range) = f(start_range); + let mut change_size = to as isize - from as isize; + + if let Some(ref text) = replacement { + change_size = text.chars().count() as isize - change_size; + } else { + change_size = -change_size; + } + + let new_range = if let Some(end_range) = end_range { + end_range + } else { + let changeset = ChangeSet::from_change(doc, (from, to, replacement.clone())); + start_range.map(&changeset) + }; + + let offset_range = Range::new( + (new_range.anchor as isize + offset) as usize, + (new_range.head as isize + offset) as usize, + ); + + end_ranges.push(offset_range); + offset += change_size; + + log::trace!( + "from: {}, to: {}, replacement: {:?}, offset: {}", + from, + to, + replacement, + offset + ); + + (from, to, replacement) + }); + + transaction.with_selection(Selection::new(end_ranges, selection.primary_index())) + } + /// Generate a transaction with a deletion per selection range. /// Compared to using `change_by_selection` directly these ranges may overlap. - /// In that case they are merged + /// In that case they are merged. pub fn delete_by_selection<F>(doc: &Rope, selection: &Selection, f: F) -> Self where F: FnMut(&Range) -> Deletion, @@ -747,6 +809,59 @@ impl Transaction { Self::delete(doc, selection.iter().map(f)) } + /// Generate a transaction with a delete per selection range, which + /// generates a new selection as well. Each range is operated upon by + /// the given function and can optionally produce a new range. If none + /// is returned by the function, that range is mapped through the change + /// as usual. + /// + /// Compared to using `change_by_and_with_selection` directly these ranges + /// may overlap. In that case they are merged. + pub fn delete_by_and_with_selection<F>(doc: &Rope, selection: &Selection, mut f: F) -> Self + where + F: FnMut(&Range) -> (Deletion, Option<Range>), + { + let mut end_ranges = SmallVec::with_capacity(selection.len()); + let mut offset = 0; + let mut last = 0; + + let transaction = Transaction::delete_by_selection(doc, selection, |start_range| { + let ((from, to), end_range) = f(start_range); + + // must account for possibly overlapping deletes + let change_size = if last > from { to - last } else { to - from }; + + let new_range = if let Some(end_range) = end_range { + end_range + } else { + let changeset = ChangeSet::from_change(doc, (from, to, None)); + start_range.map(&changeset) + }; + + let offset_range = Range::new( + new_range.anchor.saturating_sub(offset), + new_range.head.saturating_sub(offset), + ); + + log::trace!( + "delete from: {}, to: {}, offset: {}, new_range: {:?}, offset_range: {:?}", + from, + to, + offset, + new_range, + offset_range + ); + + end_ranges.push(offset_range); + offset += change_size; + last = to; + + (from, to) + }); + + transaction.with_selection(Selection::new(end_ranges, selection.primary_index())) + } + /// Insert text at each selection head. pub fn insert(doc: &Rope, selection: &Selection, text: Tendril) -> Self { Self::change_by_selection(doc, selection, |range| { @@ -754,7 +869,7 @@ impl Transaction { }) } - pub fn changes_iter(&self) -> ChangeIterator<'_> { + pub fn changes_iter(&self) -> ChangeIterator { self.changes.changes_iter() } } |