diff options
author | Pascal Kuthe | 2023-03-09 21:19:14 +0000 |
---|---|---|
committer | Blaž Hrastnik | 2023-03-10 07:54:17 +0000 |
commit | 2b64a64d7ea43e22ad82f97f2c118891b74c3199 (patch) | |
tree | f81846cf308e9b99192479830259b4a52d44ac1f /helix-core | |
parent | cdec933523560f71c665469adc409d7ac0e06171 (diff) |
Add API to create a Transaction from potentially overlapping changes
This commit adds new functions to `Transaction` that allow creating
edits that might potentially overlap. Any change that overlaps
previous changes is ignored. Furthermore, a utility method is added
that also drops selections associated with dropped changes (for
transactions that are created from a selection).
This is needed to avoid crashes when applying multicursor
autocompletions, as the edit from a previous cursor may overlap
with the next cursor/edit.
Diffstat (limited to 'helix-core')
-rw-r--r-- | helix-core/src/transaction.rs | 67 |
1 files changed, 67 insertions, 0 deletions
diff --git a/helix-core/src/transaction.rs b/helix-core/src/transaction.rs index d2f4de07..d8e581aa 100644 --- a/helix-core/src/transaction.rs +++ b/helix-core/src/transaction.rs @@ -1,3 +1,5 @@ +use smallvec::SmallVec; + use crate::{Range, Rope, Selection, Tendril}; use std::borrow::Cow; @@ -466,6 +468,33 @@ impl Transaction { self } + /// Generate a transaction from a set of potentially overlapping changes. The `change_ranges` + /// iterator yield the range (of removed text) in the old document for each edit. If any change + /// overlaps with a range overlaps with a previous range then that range is ignored. + /// + /// The `process_change` callback is called for each edit that is not ignored (in the order + /// yielded by `changes`) and should return the new text that the associated range will be + /// replaced with. + /// + /// To make this function more flexible the iterator can yield additional data for each change + /// that is passed to `process_change` + pub fn change_ignore_overlapping<T>( + doc: &Rope, + change_ranges: impl Iterator<Item = (usize, usize, T)>, + mut process_change: impl FnMut(usize, usize, T) -> Option<Tendril>, + ) -> Self { + let mut last = 0; + let changes = change_ranges.filter_map(|(from, to, data)| { + if from < last { + return None; + } + let tendril = process_change(from, to, data); + last = to; + Some((from, to, tendril)) + }); + Self::change(doc, changes) + } + /// Generate a transaction from a set of changes. pub fn change<I>(doc: &Rope, changes: I) -> Self where @@ -513,6 +542,44 @@ impl Transaction { Self::change(doc, selection.iter().map(f)) } + pub fn change_by_selection_ignore_overlapping( + doc: &Rope, + selection: &Selection, + mut change_range: impl FnMut(&Range) -> (usize, usize), + mut create_tendril: impl FnMut(usize, usize) -> Option<Tendril>, + ) -> (Transaction, Selection) { + let mut last_selection_idx = None; + let mut new_primary_idx = None; + let mut ranges: SmallVec<[Range; 1]> = SmallVec::new(); + let process_change = |change_start, change_end, (idx, range): (usize, &Range)| { + // update the primary idx + if idx == selection.primary_index() { + new_primary_idx = Some(idx); + } else if new_primary_idx.is_none() { + if idx > selection.primary_index() { + new_primary_idx = last_selection_idx; + } else { + last_selection_idx = Some(idx); + } + } + ranges.push(*range); + create_tendril(change_start, change_end) + }; + let transaction = Self::change_ignore_overlapping( + doc, + selection.iter().enumerate().map(|range| { + let (change_start, change_end) = change_range(range.1); + (change_start, change_end, range) + }), + process_change, + ); + + ( + transaction, + Selection::new(ranges, new_primary_idx.unwrap_or(0)), + ) + } + /// Insert text at each selection head. pub fn insert(doc: &Rope, selection: &Selection, text: Tendril) -> Self { Self::change_by_selection(doc, selection, |range| { |