1use crate::position::ByteOffset;
4use crate::text::Document;
5
6#[derive(Clone, Debug, PartialEq, Eq)]
7pub struct Edit {
8 pub start: ByteOffset,
9 pub end: ByteOffset,
10 pub text: String,
11}
12
13impl Edit {
14 pub fn new(start: ByteOffset, end: ByteOffset, text: impl Into<String>) -> Self {
15 Self {
16 start,
17 end,
18 text: text.into(),
19 }
20 }
21
22 fn is_noop(&self) -> bool {
23 self.start == self.end && self.text.is_empty()
24 }
25}
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
29pub enum Bias {
30 Before,
31 After,
32}
33
34#[derive(Clone, Debug, PartialEq, Eq)]
37pub struct ChangeSet {
38 base_len: ByteOffset,
39 edits: Vec<Edit>,
40}
41
42impl ChangeSet {
43 pub fn new(base_len: ByteOffset, mut edits: Vec<Edit>) -> Self {
47 edits.retain(|e| !e.is_noop());
48 edits.sort_by_key(|e| (e.start, e.end));
49 let mut out: Vec<Edit> = Vec::with_capacity(edits.len());
50 for e in edits {
51 debug_assert!(e.start <= e.end && e.end <= base_len);
52 match out.last_mut() {
53 Some(last) if e.start <= last.end => {
54 debug_assert!(
55 e.start == last.end || (last.text.is_empty() && e.text.is_empty()),
56 "overlapping edits that insert text: {last:?} {e:?}"
57 );
58 last.end = last.end.max(e.end);
59 last.text.push_str(&e.text);
60 }
61 _ => out.push(e),
62 }
63 }
64 Self {
65 base_len,
66 edits: out,
67 }
68 }
69
70 pub fn edits(&self) -> &[Edit] {
71 &self.edits
72 }
73
74 pub fn is_empty(&self) -> bool {
75 self.edits.is_empty()
76 }
77
78 pub fn base_len(&self) -> ByteOffset {
79 self.base_len
80 }
81
82 pub fn new_len(&self) -> ByteOffset {
83 let delta: isize = self
84 .edits
85 .iter()
86 .map(|e| e.text.len() as isize - (e.end.0 - e.start.0) as isize)
87 .sum();
88 ByteOffset((self.base_len.0 as isize + delta) as usize)
89 }
90
91 pub fn apply(&self, doc: &mut Document) {
93 debug_assert_eq!(doc.len(), self.base_len);
94 for e in self.edits.iter().rev() {
95 doc.replace(e.start..e.end, &e.text);
96 }
97 }
98
99 pub fn invert(&self, doc: &Document) -> ChangeSet {
101 let mut delta: isize = 0;
102 let mut edits = Vec::with_capacity(self.edits.len());
103 for e in &self.edits {
104 let start = ByteOffset((e.start.0 as isize + delta) as usize);
105 edits.push(Edit::new(
106 start,
107 ByteOffset(start.0 + e.text.len()),
108 doc.slice(e.start..e.end),
109 ));
110 delta += e.text.len() as isize - (e.end.0 - e.start.0) as isize;
111 }
112 ChangeSet {
113 base_len: self.new_len(),
114 edits,
115 }
116 }
117
118 pub fn map_position(&self, pos: ByteOffset, bias: Bias) -> ByteOffset {
126 let mut delta: isize = 0;
127 for e in &self.edits {
128 let new_start = (e.start.0 as isize + delta) as usize;
129 let new_end = new_start + e.text.len();
130 if pos < e.start {
131 break;
132 }
133 if pos <= e.end {
134 let after = if e.start == e.end {
135 bias == Bias::After
136 } else if pos == e.start {
137 false
138 } else if pos == e.end {
139 true
140 } else {
141 bias == Bias::After
142 };
143 return ByteOffset(if after { new_end } else { new_start });
144 }
145 delta += e.text.len() as isize - (e.end.0 - e.start.0) as isize;
146 }
147 ByteOffset((pos.0 as isize + delta) as usize)
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 fn b(n: usize) -> ByteOffset {
156 ByteOffset(n)
157 }
158
159 #[test]
160 fn apply_and_invert() {
161 let mut doc = Document::new("hello world");
162 let cs = ChangeSet::new(
163 doc.len(),
164 vec![Edit::new(b(6), b(11), "there"), Edit::new(b(0), b(5), "hi")],
165 );
166 let inv = cs.invert(&doc);
167 cs.apply(&mut doc);
168 assert_eq!(doc.to_string(), "hi there");
169 assert_eq!(cs.new_len(), doc.len());
170 inv.apply(&mut doc);
171 assert_eq!(doc.to_string(), "hello world");
172 }
173
174 #[test]
175 fn merges_touching_and_overlapping_deletes() {
176 let cs = ChangeSet::new(
177 b(10),
178 vec![
179 Edit::new(b(2), b(3), ""),
180 Edit::new(b(3), b(4), ""),
181 Edit::new(b(3), b(5), ""),
182 Edit::new(b(7), b(7), ""),
183 ],
184 );
185 assert_eq!(cs.edits(), &[Edit::new(b(2), b(5), "")]);
186 }
187
188 #[test]
189 fn map_position_bias() {
190 let cs = ChangeSet::new(
192 b(10),
193 vec![Edit::new(b(2), b(2), "XY"), Edit::new(b(5), b(7), "Z")],
194 );
195 let m = |p, bias| cs.map_position(b(p), bias).0;
196 assert_eq!(m(1, Bias::After), 1);
197 assert_eq!(m(2, Bias::Before), 2);
198 assert_eq!(m(2, Bias::After), 4);
199 assert_eq!(m(4, Bias::Before), 6);
200 assert_eq!(m(5, Bias::After), 7); assert_eq!(m(6, Bias::Before), 7);
202 assert_eq!(m(6, Bias::After), 8);
203 assert_eq!(m(7, Bias::Before), 8); assert_eq!(m(10, Bias::Before), 11);
205 }
206}