on master help sign in
map positions through changes in O(N)
d491e23Pascal Kuthe
helix-core/src/selection.rs+61−34
@@ -161,34 +161,35 @@ impl Range {
161 self.from() <= pos && pos < self.to()
161 self.from() <= pos && pos < self.to()
162 }
162 }
163
163
164− /// Map a range through a set of changes. Returns a new range representing the same position
164+ /// Map a range through a set of changes. Returns a new range representing
165− /// after the changes are applied.
165+ /// the same position after the changes are applied. Note that this
166+ /// function runs in O(N) (N is number of changes) and can therefore
167+ /// cause performance problems if run for a large number of ranges as the
168+ /// complexity is then O(MN) (for multicuror M=N usually). Instead use
169+ /// [Selection::map] or [ChangeSet::update_positions] instead
166− pub fn map(self, changes: &ChangeSet) -> Self {
170+ pub fn map(mut self, changes: &ChangeSet) -> Self {
167 use std::cmp::Ordering;
171 use std::cmp::Ordering;
168− let (anchor, head) = match self.anchor.cmp(&self.head) {
172+ if changes.is_empty() {
169− Ordering::Equal => (
173+ return self;
170− changes.map_pos(self.anchor, Assoc::After),
171− changes.map_pos(self.head, Assoc::After),
172− ),
173− Ordering::Less => (
174− changes.map_pos(self.anchor, Assoc::After),
175− changes.map_pos(self.head, Assoc::Before),
176− ),
177− Ordering::Greater => (
178− changes.map_pos(self.anchor, Assoc::Before),
179− changes.map_pos(self.head, Assoc::After),
180− ),
181− };
182−
183− // We want to return a new `Range` with `horiz == None` every time,
184− // even if the anchor and head haven't changed, because we don't
185− // know if the *visual* position hasn't changed due to
186− // character-width or grapheme changes earlier in the text.
187− Self {
188− anchor,
189− head,
190− old_visual_position: None,
191 }
174 }
175+
176+ let positions_to_map = match self.anchor.cmp(&self.head) {
177+ Ordering::Equal => [
178+ (&mut self.anchor, Assoc::After),
179+ (&mut self.head, Assoc::After),
180+ ],
181+ Ordering::Less => [
182+ (&mut self.anchor, Assoc::After),
183+ (&mut self.head, Assoc::Before),
184+ ],
185+ Ordering::Greater => [
186+ (&mut self.head, Assoc::After),
187+ (&mut self.anchor, Assoc::Before),
188+ ],
189+ };
190+ changes.update_positions(positions_to_map.into_iter());
191+ self.old_visual_position = None;
192+ self
192 }
193 }
193
194
194 /// Extend the range to cover at least `from` `to`.
195 /// Extend the range to cover at least `from` `to`.
@@ -450,18 +451,44 @@ impl Selection {
450
451
451 /// Map selections over a set of changes. Useful for adjusting the selection position after
452 /// Map selections over a set of changes. Useful for adjusting the selection position after
452 /// applying changes to a document.
453 /// applying changes to a document.
453− pub fn map(self, changes: &ChangeSet) -> Self {
454+ pub fn map(mut self, changes: &ChangeSet) -> Self {
454 if changes.is_empty() {
455 if changes.is_empty() {
455 return self;
456 return self;
456 }
457 }
458+ self = self.map_no_normalize(changes);
459+ // TODO: only normalize if needed (any ranges out of order)
460+ self = self.normalize();
457
461
458− Self::new(
462+ self
459− self.ranges
463+ }
460− .into_iter()
464+
461− .map(|range| range.map(changes))
465+ /// Map selections over a set of changes. Useful for adjusting the selection position after
462− .collect(),
466+ /// applying changes to a document. Doesn't normalize the selection
463− self.primary_index,
467+ pub fn map_no_normalize(mut self, changes: &ChangeSet) -> Self {
468+ if changes.is_empty() {
469+ return self;
470+ }
471+
472+ let positions_to_map = self.ranges.iter_mut().flat_map(|range| {
473+ use std::cmp::Ordering;
474+ range.old_visual_position = None;
475+ match range.anchor.cmp(&range.head) {
476+ Ordering::Equal => [
477+ (&mut range.anchor, Assoc::After),
478+ (&mut range.head, Assoc::After),
479+ ],
480+ Ordering::Less => [
481+ (&mut range.anchor, Assoc::After),
482+ (&mut range.head, Assoc::Before),
483+ ],
484+ Ordering::Greater => [
485+ (&mut range.head, Assoc::After),
486+ (&mut range.anchor, Assoc::Before),
487+ ],
488+ }
464− )
489+ });
490+ changes.update_positions(positions_to_map);
491+ self
465 }
492 }
466
493
467 pub fn ranges(&self) -> &[Range] {
494 pub fn ranges(&self) -> &[Range] {
helix-core/src/transaction.rs+48−29
@@ -1,7 +1,7 @@
1use smallvec::SmallVec;
1use smallvec::SmallVec;
2
2
3use crate::{Range, Rope, Selection, Tendril};
3use crate::{Range, Rope, Selection, Tendril};
4−use std::borrow::Cow;
4+use std::{borrow::Cow, iter::once};
5
5
6/// (from, to, replacement)
6/// (from, to, replacement)
7pub type Change = (usize, usize, Option<Tendril>);
7pub type Change = (usize, usize, Option<Tendril>);
@@ -326,17 +326,33 @@ impl ChangeSet {
326 self.changes.is_empty() || self.changes == [Operation::Retain(self.len)]
326 self.changes.is_empty() || self.changes == [Operation::Retain(self.len)]
327 }
327 }
328
328
329− /// Map a position through the changes.
329+ /// Map a *sorted* list of positions through the changes.
330 ///
330 ///
331− /// `assoc` indicates which size to associate the position with. `Before` will keep the
331+ /// This is equivalent to updating each position with `map_pos`:
332− /// position close to the character before, and will place it before insertions over that
332+ ///
333− /// range, or at that point. `After` will move it forward, placing it at the end of such
333+ /// ``` no-compile
334− /// insertions.
334+ /// for (pos, assoc) in positions {
335− pub fn map_pos(&self, pos: usize, assoc: Assoc) -> usize {
335+ /// *pos = changes.map_pos(*pos, assoc);
336+ /// }
337+ /// ```
338+ /// However this function is significantly faster running in `O(N+M)` instead of `O(NM)`
339+ pub fn update_positions<'a>(&self, positions: impl Iterator<Item = (&'a mut usize, Assoc)>) {
336 use Operation::*;
340 use Operation::*;
341+
342+ let mut positions = positions.peekable();
343+ macro_rules! map {
344+ ($map: expr) => {
345+ loop {
346+ let Some((pos, assoc)) = positions.peek_mut() else { return; };
347+ let Some(new_pos) = $map(**pos, *assoc) else { break; };
348+ **pos = new_pos;
349+ positions.next();
350+ }
351+ };
352+ }
353+
337 let mut old_pos = 0;
354 let mut old_pos = 0;
338 let mut new_pos = 0;
355 let mut new_pos = 0;
339−
340 let mut iter = self.changes.iter().peekable();
356 let mut iter = self.changes.iter().peekable();
341
357
342 while let Some(change) = iter.next() {
358 while let Some(change) = iter.next() {
@@ -348,16 +364,12 @@ impl ChangeSet {
348
364
349 match change {
365 match change {
350 Retain(_) => {
366 Retain(_) => {
351− if old_end > pos {
352− return new_pos + (pos - old_pos);
367+ map!(|pos, _| (old_end > pos).then_some(new_pos + (pos - old_pos)));
353− }
354 new_pos += len;
368 new_pos += len;
355 }
369 }
356 Delete(_) => {
370 Delete(_) => {
357 // in range
371 // in range
358− if old_end > pos {
372+ map!(|pos, _| (old_end > pos).then_some(new_pos));
359− return new_pos;
360− }
361 }
373 }
362 Insert(s) => {
374 Insert(s) => {
363 let ins = s.chars().count();
375 let ins = s.chars().count();
@@ -368,26 +380,26 @@ impl ChangeSet {
368
380
369 old_end = old_pos + len;
381 old_end = old_pos + len;
370 // in range of replaced text
382 // in range of replaced text
371− if old_end > pos {
383+ map!(|pos, assoc| (old_end > pos).then(|| {
372 // at point or tracking before
384 // at point or tracking before
373 if pos == old_pos || assoc == Assoc::Before {
385 if pos == old_pos || assoc == Assoc::Before {
374− return new_pos;
386+ new_pos
375 } else {
387 } else {
376 // place to end of insert
388 // place to end of insert
377− return new_pos + ins;
389+ new_pos + ins
378 }
390 }
379− }
391+ }));
380 } else {
392 } else {
381 // at insert point
393 // at insert point
382− if old_pos == pos {
394+ map!(|pos, assoc| (old_pos == pos).then(|| {
383 // return position before inserted text
395 // return position before inserted text
384 if assoc == Assoc::Before {
396 if assoc == Assoc::Before {
385− return new_pos;
397+ new_pos
386 } else {
398 } else {
387 // after text
399 // after text
388− return new_pos + ins;
400+ new_pos + ins
389 }
401 }
390− }
402+ }));
391 }
403 }
392
404
393 new_pos += ins;
405 new_pos += ins;
@@ -395,14 +407,21 @@ impl ChangeSet {
395 }
407 }
396 old_pos = old_end;
408 old_pos = old_end;
397 }
409 }
410+ map!(|pos, _| (old_pos == pos).then_some(new_pos));
411+ let out_of_bounds: Vec<_> = positions.collect();
398
412
399− if pos > old_pos {
400− panic!(
401− "Position {} is out of range for changeset len {}!",
413+ panic!("Positions {out_of_bounds:?} are out of range for changeset len {old_pos}!",)
402− pos, old_pos
414+ }
403− )
415+
404− }
416+ /// Map a position through the changes.
405− new_pos
417+ ///
418+ /// `assoc` indicates which side to associate the position with. `Before` will keep the
419+ /// position close to the character before, and will place it before insertions over that
420+ /// range, or at that point. `After` will move it forward, placing it at the end of such
421+ /// insertions.
422+ pub fn map_pos(&self, mut pos: usize, assoc: Assoc) -> usize {
423+ self.update_positions(once((&mut pos, assoc)));
424+ pos
406 }
425 }
407
426
408 pub fn changes_iter(&self) -> ChangeIterator {
427 pub fn changes_iter(&self) -> ChangeIterator {
helix-lsp/src/lib.rs+6−2
@@ -377,7 +377,7 @@ pub mod util {
377 .expect("transaction must be valid for primary selection");
377 .expect("transaction must be valid for primary selection");
378 let removed_text = text.slice(removed_start..removed_end);
378 let removed_text = text.slice(removed_start..removed_end);
379
379
380− let (transaction, selection) = Transaction::change_by_selection_ignore_overlapping(
380+ let (transaction, mut selection) = Transaction::change_by_selection_ignore_overlapping(
381 doc,
381 doc,
382 selection,
382 selection,
383 |range| {
383 |range| {
@@ -420,6 +420,11 @@ pub mod util {
420 return transaction;
420 return transaction;
421 }
421 }
422
422
423+ // Don't normalize to avoid merging/reording selections which would
424+ // break the association between tabstops and selections. Most ranges
425+ // will be replaced by tabstops anyways and the final selection will be
426+ // normalized anyways
427+ selection = selection.map_no_normalize(changes);
423 let mut mapped_selection = SmallVec::with_capacity(selection.len());
428 let mut mapped_selection = SmallVec::with_capacity(selection.len());
424 let mut mapped_primary_idx = 0;
429 let mut mapped_primary_idx = 0;
425 let primary_range = selection.primary();
430 let primary_range = selection.primary();
@@ -428,7 +433,6 @@ pub mod util {
428 mapped_primary_idx = mapped_selection.len()
433 mapped_primary_idx = mapped_selection.len()
429 }
434 }
430
435
431− let range = range.map(changes);
432 let tabstops = tabstops.first().filter(|tabstops| !tabstops.is_empty());
436 let tabstops = tabstops.first().filter(|tabstops| !tabstops.is_empty());
433 let Some(tabstops) = tabstops else{
437 let Some(tabstops) = tabstops else{
434 // no tabstop normal mapping
438 // no tabstop normal mapping
helix-view/src/document.rs+14−7
@@ -1121,21 +1121,28 @@ impl Document {
1121
1121
1122 let changes = transaction.changes();
1122 let changes = transaction.changes();
1123
1123
1124+ changes.update_positions(
1125+ self.diagnostics
1126+ .iter_mut()
1127+ .map(|diagnostic| (&mut diagnostic.range.start, Assoc::After)),
1128+ );
1129+ changes.update_positions(
1130+ self.diagnostics
1131+ .iter_mut()
1132+ .map(|diagnostic| (&mut diagnostic.range.end, Assoc::After)),
1133+ );
1124 // map state.diagnostics over changes::map_pos too
1134 // map state.diagnostics over changes::map_pos too
1125 for diagnostic in &mut self.diagnostics {
1135 for diagnostic in &mut self.diagnostics {
1126− diagnostic.range.start = changes.map_pos(diagnostic.range.start, Assoc::After);
1127− diagnostic.range.end = changes.map_pos(diagnostic.range.end, Assoc::After);
1128 diagnostic.line = self.text.char_to_line(diagnostic.range.start);
1136 diagnostic.line = self.text.char_to_line(diagnostic.range.start);
1129 }
1137 }
1130− self.diagnostics
1131− .sort_unstable_by_key(|diagnostic| diagnostic.range);
1132
1138
1133 // Update the inlay hint annotations' positions, helping ensure they are displayed in the proper place
1139 // Update the inlay hint annotations' positions, helping ensure they are displayed in the proper place
1134 let apply_inlay_hint_changes = |annotations: &mut Rc<[InlineAnnotation]>| {
1140 let apply_inlay_hint_changes = |annotations: &mut Rc<[InlineAnnotation]>| {
1135 if let Some(data) = Rc::get_mut(annotations) {
1141 if let Some(data) = Rc::get_mut(annotations) {
1142+ changes.update_positions(
1136− for inline in data.iter_mut() {
1143+ data.iter_mut()
1137− inline.char_idx = changes.map_pos(inline.char_idx, Assoc::After);
1144+ .map(|diagnostic| (&mut diagnostic.char_idx, Assoc::After)),
1138− }
1145+ );
1139 }
1146 }
1140 };
1147 };
1141
1148