summaryrefslogtreecommitdiff
path: root/helix-core
diff options
context:
space:
mode:
authorPascal Kuthe2023-03-09 21:19:14 +0000
committerBlaž Hrastnik2023-03-10 07:54:17 +0000
commit2b64a64d7ea43e22ad82f97f2c118891b74c3199 (patch)
treef81846cf308e9b99192479830259b4a52d44ac1f /helix-core
parentcdec933523560f71c665469adc409d7ac0e06171 (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.rs67
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| {