on master help sign in
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.

commit2b64a64d7ea43e22ad82f97f2c118891b74c3199 authorPascal Kuthe <pascal.kuthe@semimod.de> dateThu 9 Mar 2023, 22:19 (4 years ago) parentcdec933
1 file changed+67
helix-core/src/transaction.rs+67
helix-core/src/transaction.rs+67
@@ -1,3 +1,5 @@
1+use smallvec::SmallVec;
2+
13use crate::{Range, Rope, Selection, Tendril};
24use std::borrow::Cow;
35
@@ -466,6 +468,33 @@ impl Transaction {
466468 self
467469 }
468470
471+ /// Generate a transaction from a set of potentially overlapping changes. The `change_ranges`
472+ /// iterator yield the range (of removed text) in the old document for each edit. If any change
473+ /// overlaps with a range overlaps with a previous range then that range is ignored.
474+ ///
475+ /// The `process_change` callback is called for each edit that is not ignored (in the order
476+ /// yielded by `changes`) and should return the new text that the associated range will be
477+ /// replaced with.
478+ ///
479+ /// To make this function more flexible the iterator can yield additional data for each change
480+ /// that is passed to `process_change`
481+ pub fn change_ignore_overlapping<T>(
482+ doc: &Rope,
483+ change_ranges: impl Iterator<Item = (usize, usize, T)>,
484+ mut process_change: impl FnMut(usize, usize, T) -> Option<Tendril>,
485+ ) -> Self {
486+ let mut last = 0;
487+ let changes = change_ranges.filter_map(|(from, to, data)| {
488+ if from < last {
489+ return None;
490+ }
491+ let tendril = process_change(from, to, data);
492+ last = to;
493+ Some((from, to, tendril))
494+ });
495+ Self::change(doc, changes)
496+ }
497+
469498 /// Generate a transaction from a set of changes.
470499 pub fn change<I>(doc: &Rope, changes: I) -> Self
471500 where
@@ -513,6 +542,44 @@ impl Transaction {
513542 Self::change(doc, selection.iter().map(f))
514543 }
515544
545+ pub fn change_by_selection_ignore_overlapping(
546+ doc: &Rope,
547+ selection: &Selection,
548+ mut change_range: impl FnMut(&Range) -> (usize, usize),
549+ mut create_tendril: impl FnMut(usize, usize) -> Option<Tendril>,
550+ ) -> (Transaction, Selection) {
551+ let mut last_selection_idx = None;
552+ let mut new_primary_idx = None;
553+ let mut ranges: SmallVec<[Range; 1]> = SmallVec::new();
554+ let process_change = |change_start, change_end, (idx, range): (usize, &Range)| {
555+ // update the primary idx
556+ if idx == selection.primary_index() {
557+ new_primary_idx = Some(idx);
558+ } else if new_primary_idx.is_none() {
559+ if idx > selection.primary_index() {
560+ new_primary_idx = last_selection_idx;
561+ } else {
562+ last_selection_idx = Some(idx);
563+ }
564+ }
565+ ranges.push(*range);
566+ create_tendril(change_start, change_end)
567+ };
568+ let transaction = Self::change_ignore_overlapping(
569+ doc,
570+ selection.iter().enumerate().map(|range| {
571+ let (change_start, change_end) = change_range(range.1);
572+ (change_start, change_end, range)
573+ }),
574+ process_change,
575+ );
576+
577+ (
578+ transaction,
579+ Selection::new(ranges, new_primary_idx.unwrap_or(0)),
580+ )
581+ }
582+
516583 /// Insert text at each selection head.
517584 pub fn insert(doc: &Rope, selection: &Selection, text: Tendril) -> Self {
518585 Self::change_by_selection(doc, selection, |range| {