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