Skip to content

Commit

Permalink
Add API to create a Transaction from potentially overlapping changes
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
pascalkuthe authored and archseer committed Mar 10, 2023
1 parent cdec933 commit 2b64a64
Showing 1 changed file with 67 additions and 0 deletions.
67 changes: 67 additions & 0 deletions helix-core/src/transaction.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use smallvec::SmallVec;

use crate::{Range, Rope, Selection, Tendril};
use std::borrow::Cow;

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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| {
Expand Down

0 comments on commit 2b64a64

Please sign in to comment.