@@ -39,6 +39,12 @@ impl Change {
42+#[derive(Copy, Clone, PartialEq, Eq)]
42// ChangeSpec = Change | ChangeSet | Vec<Change>
48// ChangeSpec = Change | ChangeSet | Vec<Change>
43// ChangeDesc as a ChangeSet without text: can't be applied, cheaper to store.
49// ChangeDesc as a ChangeSet without text: can't be applied, cheaper to store.
44// ChangeSet = ChangeDesc with Text
50// ChangeSet = ChangeDesc with Text
@@ -236,7 +242,82 @@ impl ChangeSet {
239− // iter over changes
245+ /// Map a position through the changes.
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
251+ pub fn map_pos(&self, pos: usize, assoc: Assoc) -> usize {
253+ let mut old_pos = 0;
254+ let mut new_pos = 0;
256+ let mut iter = self.changes.iter().peekable();
258+ while let Some(change) = iter.next() {
259+ let len = match change {
260+ Delete(i) | Retain(i) => *i,
263+ let old_end = old_pos + len;
268+ return new_pos + (pos - old_pos);
273+ // a subsequent ins means a replace, consume it
274+ let ins = if let Some(Insert(s)) = iter.peek() {
283+ // at point or tracking before
284+ if pos == old_pos || assoc == Assoc::Before {
287+ // place to end of delete
288+ return new_pos + ins;
295+ let ins = s.chars().count();
298+ // return position before inserted text
299+ if assoc == Assoc::Before {
303+ return new_pos + ins;
315+ "Position {} is out of range for changeset len {}!",
242// trait Transaction
323// trait Transaction
@@ -282,9 +363,48 @@ mod test {
282 // should probably return cloned text
363 // should probably return cloned text
283 a.compose(b).unwrap().apply(&mut text);
364 a.compose(b).unwrap().apply(&mut text);
285− unimplemented!("{:?}", text);
366+ // unimplemented!("{:?}", text);
289− fn map() {}
371+ fn map_pos() {
375+ let cs = ChangeSet {
376+ changes: vec![Retain(4), Insert("!!".into()), Retain(4)],
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
386+ let cs = ChangeSet {
387+ changes: vec![Retain(4), Delete(4), Retain(4)],
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
395+ // TODO: delete tracking
397+ // stays inbetween replacements
398+ let cs = ChangeSet {
401+ Insert("ab".into()),
403+ Insert("cd".into()),
407+ assert_eq!(cs.map_pos(2, Assoc::Before), 2);
408+ assert_eq!(cs.map_pos(2, Assoc::After), 2);