map positions through changes in O(N)

pull/7457/head
Pascal Kuthe 1 year ago committed by Blaž Hrastnik
parent 6c6923c39e
commit d491e234f4

@ -161,34 +161,35 @@ impl Range {
self.from() <= pos && pos < self.to() self.from() <= pos && pos < self.to()
} }
/// Map a range through a set of changes. Returns a new range representing the same position /// Map a range through a set of changes. Returns a new range representing
/// after the changes are applied. /// the same position after the changes are applied. Note that this
pub fn map(self, changes: &ChangeSet) -> Self { /// function runs in O(N) (N is number of changes) and can therefore
/// cause performance problems if run for a large number of ranges as the
/// complexity is then O(MN) (for multicuror M=N usually). Instead use
/// [Selection::map] or [ChangeSet::update_positions] instead
pub fn map(mut self, changes: &ChangeSet) -> Self {
use std::cmp::Ordering; use std::cmp::Ordering;
let (anchor, head) = match self.anchor.cmp(&self.head) { if changes.is_empty() {
Ordering::Equal => ( return self;
changes.map_pos(self.anchor, Assoc::After),
changes.map_pos(self.head, Assoc::After),
),
Ordering::Less => (
changes.map_pos(self.anchor, Assoc::After),
changes.map_pos(self.head, Assoc::Before),
),
Ordering::Greater => (
changes.map_pos(self.anchor, Assoc::Before),
changes.map_pos(self.head, Assoc::After),
),
};
// We want to return a new `Range` with `horiz == None` every time,
// even if the anchor and head haven't changed, because we don't
// know if the *visual* position hasn't changed due to
// character-width or grapheme changes earlier in the text.
Self {
anchor,
head,
old_visual_position: None,
} }
let positions_to_map = match self.anchor.cmp(&self.head) {
Ordering::Equal => [
(&mut self.anchor, Assoc::After),
(&mut self.head, Assoc::After),
],
Ordering::Less => [
(&mut self.anchor, Assoc::After),
(&mut self.head, Assoc::Before),
],
Ordering::Greater => [
(&mut self.head, Assoc::After),
(&mut self.anchor, Assoc::Before),
],
};
changes.update_positions(positions_to_map.into_iter());
self.old_visual_position = None;
self
} }
/// Extend the range to cover at least `from` `to`. /// Extend the range to cover at least `from` `to`.
@ -450,18 +451,44 @@ impl Selection {
/// Map selections over a set of changes. Useful for adjusting the selection position after /// Map selections over a set of changes. Useful for adjusting the selection position after
/// applying changes to a document. /// applying changes to a document.
pub fn map(self, changes: &ChangeSet) -> Self { pub fn map(mut self, changes: &ChangeSet) -> Self {
if changes.is_empty() { if changes.is_empty() {
return self; return self;
} }
self = self.map_no_normalize(changes);
// TODO: only normalize if needed (any ranges out of order)
self = self.normalize();
Self::new( self
self.ranges }
.into_iter()
.map(|range| range.map(changes)) /// Map selections over a set of changes. Useful for adjusting the selection position after
.collect(), /// applying changes to a document. Doesn't normalize the selection
self.primary_index, pub fn map_no_normalize(mut self, changes: &ChangeSet) -> Self {
) if changes.is_empty() {
return self;
}
let positions_to_map = self.ranges.iter_mut().flat_map(|range| {
use std::cmp::Ordering;
range.old_visual_position = None;
match range.anchor.cmp(&range.head) {
Ordering::Equal => [
(&mut range.anchor, Assoc::After),
(&mut range.head, Assoc::After),
],
Ordering::Less => [
(&mut range.anchor, Assoc::After),
(&mut range.head, Assoc::Before),
],
Ordering::Greater => [
(&mut range.head, Assoc::After),
(&mut range.anchor, Assoc::Before),
],
}
});
changes.update_positions(positions_to_map);
self
} }
pub fn ranges(&self) -> &[Range] { pub fn ranges(&self) -> &[Range] {

@ -1,7 +1,7 @@
use smallvec::SmallVec; use smallvec::SmallVec;
use crate::{Range, Rope, Selection, Tendril}; use crate::{Range, Rope, Selection, Tendril};
use std::borrow::Cow; use std::{borrow::Cow, iter::once};
/// (from, to, replacement) /// (from, to, replacement)
pub type Change = (usize, usize, Option<Tendril>); pub type Change = (usize, usize, Option<Tendril>);
@ -326,17 +326,33 @@ impl ChangeSet {
self.changes.is_empty() || self.changes == [Operation::Retain(self.len)] self.changes.is_empty() || self.changes == [Operation::Retain(self.len)]
} }
/// Map a position through the changes. /// Map a *sorted* list of positions through the changes.
/// ///
/// `assoc` indicates which size to associate the position with. `Before` will keep the /// This is equivalent to updating each position with `map_pos`:
/// position close to the character before, and will place it before insertions over that ///
/// range, or at that point. `After` will move it forward, placing it at the end of such /// ``` no-compile
/// insertions. /// for (pos, assoc) in positions {
pub fn map_pos(&self, pos: usize, assoc: Assoc) -> usize { /// *pos = changes.map_pos(*pos, assoc);
/// }
/// ```
/// However this function is significantly faster running in `O(N+M)` instead of `O(NM)`
pub fn update_positions<'a>(&self, positions: impl Iterator<Item = (&'a mut usize, Assoc)>) {
use Operation::*; use Operation::*;
let mut positions = positions.peekable();
macro_rules! map {
($map: expr) => {
loop {
let Some((pos, assoc)) = positions.peek_mut() else { return; };
let Some(new_pos) = $map(**pos, *assoc) else { break; };
**pos = new_pos;
positions.next();
}
};
}
let mut old_pos = 0; let mut old_pos = 0;
let mut new_pos = 0; let mut new_pos = 0;
let mut iter = self.changes.iter().peekable(); let mut iter = self.changes.iter().peekable();
while let Some(change) = iter.next() { while let Some(change) = iter.next() {
@ -348,16 +364,12 @@ impl ChangeSet {
match change { match change {
Retain(_) => { Retain(_) => {
if old_end > pos { map!(|pos, _| (old_end > pos).then_some(new_pos + (pos - old_pos)));
return new_pos + (pos - old_pos);
}
new_pos += len; new_pos += len;
} }
Delete(_) => { Delete(_) => {
// in range // in range
if old_end > pos { map!(|pos, _| (old_end > pos).then_some(new_pos));
return new_pos;
}
} }
Insert(s) => { Insert(s) => {
let ins = s.chars().count(); let ins = s.chars().count();
@ -368,26 +380,26 @@ impl ChangeSet {
old_end = old_pos + len; old_end = old_pos + len;
// in range of replaced text // in range of replaced text
if old_end > pos { map!(|pos, assoc| (old_end > pos).then(|| {
// at point or tracking before // at point or tracking before
if pos == old_pos || assoc == Assoc::Before { if pos == old_pos || assoc == Assoc::Before {
return new_pos; new_pos
} else { } else {
// place to end of insert // place to end of insert
return new_pos + ins; new_pos + ins
}
} }
}));
} else { } else {
// at insert point // at insert point
if old_pos == pos { map!(|pos, assoc| (old_pos == pos).then(|| {
// return position before inserted text // return position before inserted text
if assoc == Assoc::Before { if assoc == Assoc::Before {
return new_pos; new_pos
} else { } else {
// after text // after text
return new_pos + ins; new_pos + ins
}
} }
}));
} }
new_pos += ins; new_pos += ins;
@ -395,14 +407,21 @@ impl ChangeSet {
} }
old_pos = old_end; old_pos = old_end;
} }
map!(|pos, _| (old_pos == pos).then_some(new_pos));
let out_of_bounds: Vec<_> = positions.collect();
if pos > old_pos { panic!("Positions {out_of_bounds:?} are out of range for changeset len {old_pos}!",)
panic!(
"Position {} is out of range for changeset len {}!",
pos, old_pos
)
} }
new_pos
/// Map a position through the changes.
///
/// `assoc` indicates which side to associate the position with. `Before` will keep the
/// position close to the character before, and will place it before insertions over that
/// range, or at that point. `After` will move it forward, placing it at the end of such
/// insertions.
pub fn map_pos(&self, mut pos: usize, assoc: Assoc) -> usize {
self.update_positions(once((&mut pos, assoc)));
pos
} }
pub fn changes_iter(&self) -> ChangeIterator { pub fn changes_iter(&self) -> ChangeIterator {

@ -377,7 +377,7 @@ pub mod util {
.expect("transaction must be valid for primary selection"); .expect("transaction must be valid for primary selection");
let removed_text = text.slice(removed_start..removed_end); let removed_text = text.slice(removed_start..removed_end);
let (transaction, selection) = Transaction::change_by_selection_ignore_overlapping( let (transaction, mut selection) = Transaction::change_by_selection_ignore_overlapping(
doc, doc,
selection, selection,
|range| { |range| {
@ -420,6 +420,11 @@ pub mod util {
return transaction; return transaction;
} }
// Don't normalize to avoid merging/reording selections which would
// break the association between tabstops and selections. Most ranges
// will be replaced by tabstops anyways and the final selection will be
// normalized anyways
selection = selection.map_no_normalize(changes);
let mut mapped_selection = SmallVec::with_capacity(selection.len()); let mut mapped_selection = SmallVec::with_capacity(selection.len());
let mut mapped_primary_idx = 0; let mut mapped_primary_idx = 0;
let primary_range = selection.primary(); let primary_range = selection.primary();
@ -428,7 +433,6 @@ pub mod util {
mapped_primary_idx = mapped_selection.len() mapped_primary_idx = mapped_selection.len()
} }
let range = range.map(changes);
let tabstops = tabstops.first().filter(|tabstops| !tabstops.is_empty()); let tabstops = tabstops.first().filter(|tabstops| !tabstops.is_empty());
let Some(tabstops) = tabstops else{ let Some(tabstops) = tabstops else{
// no tabstop normal mapping // no tabstop normal mapping

@ -1121,21 +1121,28 @@ impl Document {
let changes = transaction.changes(); let changes = transaction.changes();
changes.update_positions(
self.diagnostics
.iter_mut()
.map(|diagnostic| (&mut diagnostic.range.start, Assoc::After)),
);
changes.update_positions(
self.diagnostics
.iter_mut()
.map(|diagnostic| (&mut diagnostic.range.end, Assoc::After)),
);
// map state.diagnostics over changes::map_pos too // map state.diagnostics over changes::map_pos too
for diagnostic in &mut self.diagnostics { for diagnostic in &mut self.diagnostics {
diagnostic.range.start = changes.map_pos(diagnostic.range.start, Assoc::After);
diagnostic.range.end = changes.map_pos(diagnostic.range.end, Assoc::After);
diagnostic.line = self.text.char_to_line(diagnostic.range.start); diagnostic.line = self.text.char_to_line(diagnostic.range.start);
} }
self.diagnostics
.sort_unstable_by_key(|diagnostic| diagnostic.range);
// Update the inlay hint annotations' positions, helping ensure they are displayed in the proper place // Update the inlay hint annotations' positions, helping ensure they are displayed in the proper place
let apply_inlay_hint_changes = |annotations: &mut Rc<[InlineAnnotation]>| { let apply_inlay_hint_changes = |annotations: &mut Rc<[InlineAnnotation]>| {
if let Some(data) = Rc::get_mut(annotations) { if let Some(data) = Rc::get_mut(annotations) {
for inline in data.iter_mut() { changes.update_positions(
inline.char_idx = changes.map_pos(inline.char_idx, Assoc::After); data.iter_mut()
} .map(|diagnostic| (&mut diagnostic.char_idx, Assoc::After)),
);
} }
}; };

Loading…
Cancel
Save