on master help sign in
changeset: Introduce map_pos.
commite52e848fd72f2d7fdc9ebe1c8f7bf215d85a02ee authorBlaž Hrastnik <blaz@mxxn.io> dateThu 28 May 2020, 14:46 (6 years ago) parentb5c3881
1 file changed+123−3
helix-core/src/transaction.rs+123−3
helix-core/src/transaction.rs+123−3
@@ -39,6 +39,12 @@ impl Change {
3939 }
4040}
4141
42+#[derive(Copy, Clone, PartialEq, Eq)]
43+pub enum Assoc {
44+ Before,
45+ After,
46+}
47+
4248// ChangeSpec = Change | ChangeSet | Vec<Change>
4349// ChangeDesc as a ChangeSet without text: can't be applied, cheaper to store.
4450// ChangeSet = ChangeDesc with Text
@@ -236,7 +242,82 @@ impl ChangeSet {
236242 }
237243 }
238244
239− // iter over changes
245+ /// Map a position through the changes.
246+ ///
247+ /// `assoc` indicates which size to associate the position with. `Before` will keep the
248+ /// position close to the character before, and will place it before insertions over that
249+ /// range, or at that point. `After` will move it forward, placing it at the end of such
250+ /// insertions.
251+ pub fn map_pos(&self, pos: usize, assoc: Assoc) -> usize {
252+ use Change::*;
253+ let mut old_pos = 0;
254+ let mut new_pos = 0;
255+
256+ let mut iter = self.changes.iter().peekable();
257+
258+ while let Some(change) = iter.next() {
259+ let len = match change {
260+ Delete(i) | Retain(i) => *i,
261+ Insert(_) => 0,
262+ };
263+ let old_end = old_pos + len;
264+
265+ match change {
266+ Retain(_len) => {
267+ if old_end > pos {
268+ return new_pos + (pos - old_pos);
269+ }
270+ new_pos += len;
271+ }
272+ Delete(_len) => {
273+ // a subsequent ins means a replace, consume it
274+ let ins = if let Some(Insert(s)) = iter.peek() {
275+ iter.next();
276+ s.chars().count()
277+ } else {
278+ 0
279+ };
280+
281+ // in range
282+ if old_end > pos {
283+ // at point or tracking before
284+ if pos == old_pos || assoc == Assoc::Before {
285+ return new_pos;
286+ } else {
287+ // place to end of delete
288+ return new_pos + ins;
289+ }
290+ }
291+
292+ new_pos += ins;
293+ }
294+ Insert(s) => {
295+ let ins = s.chars().count();
296+ // at insert point
297+ if old_pos == pos {
298+ // return position before inserted text
299+ if assoc == Assoc::Before {
300+ return new_pos;
301+ } else {
302+ // after text
303+ return new_pos + ins;
304+ }
305+ }
306+
307+ new_pos += ins;
308+ }
309+ }
310+ old_pos = old_end;
311+ }
312+
313+ if pos > old_pos {
314+ panic!(
315+ "Position {} is out of range for changeset len {}!",
316+ pos, old_pos
317+ )
318+ }
319+ new_pos
320+ }
240321}
241322
242323// trait Transaction
@@ -282,9 +363,48 @@ mod test {
282363 // should probably return cloned text
283364 a.compose(b).unwrap().apply(&mut text);
284365
285− unimplemented!("{:?}", text);
366+ // unimplemented!("{:?}", text);
367+ // TODO: assert
286368 }
287369
288370 #[test]
289− fn map() {}
371+ fn map_pos() {
372+ use Change::*;
373+
374+ // maps inserts
375+ let cs = ChangeSet {
376+ changes: vec![Retain(4), Insert("!!".into()), Retain(4)],
377+ len: 8,
378+ };
379+
380+ assert_eq!(cs.map_pos(0, Assoc::Before), 0); // before insert region
381+ assert_eq!(cs.map_pos(4, Assoc::Before), 4); // at insert, track before
382+ assert_eq!(cs.map_pos(4, Assoc::After), 6); // at insert, track after
383+ assert_eq!(cs.map_pos(5, Assoc::Before), 7); // after insert region
384+
385+ // maps deletes
386+ let cs = ChangeSet {
387+ changes: vec![Retain(4), Delete(4), Retain(4)],
388+ len: 12,
389+ };
390+ assert_eq!(cs.map_pos(0, Assoc::Before), 0); // at start
391+ assert_eq!(cs.map_pos(4, Assoc::Before), 4); // before a delete
392+ assert_eq!(cs.map_pos(5, Assoc::Before), 4); // inside a delete
393+ assert_eq!(cs.map_pos(5, Assoc::After), 4); // inside a delete
394+
395+ // TODO: delete tracking
396+
397+ // stays inbetween replacements
398+ let cs = ChangeSet {
399+ changes: vec![
400+ Delete(2),
401+ Insert("ab".into()),
402+ Delete(2),
403+ Insert("cd".into()),
404+ ],
405+ len: 4,
406+ };
407+ assert_eq!(cs.map_pos(2, Assoc::Before), 2);
408+ assert_eq!(cs.map_pos(2, Assoc::After), 2);
409+ }
290410}