1use perl_position_tracking::{Position, Range};
7
8#[derive(Debug, Clone, PartialEq)]
10pub struct Edit {
11 pub start_byte: usize,
13 pub old_end_byte: usize,
15 pub new_end_byte: usize,
17 pub start_position: Position,
19 pub old_end_position: Position,
21 pub new_end_position: Position,
23}
24
25impl Edit {
26 pub fn new(
28 start_byte: usize,
29 old_end_byte: usize,
30 new_end_byte: usize,
31 start_position: Position,
32 old_end_position: Position,
33 new_end_position: Position,
34 ) -> Self {
35 Edit {
36 start_byte,
37 old_end_byte,
38 new_end_byte,
39 start_position,
40 old_end_position,
41 new_end_position,
42 }
43 }
44
45 pub fn byte_shift(&self) -> isize {
47 self.new_end_byte as isize - self.old_end_byte as isize
48 }
49
50 pub fn line_shift(&self) -> i32 {
52 self.new_end_position.line as i32 - self.old_end_position.line as i32
53 }
54
55 pub fn affects_byte(&self, byte: usize) -> bool {
57 byte >= self.start_byte
58 }
59
60 pub fn overlaps_range(&self, range: &Range) -> bool {
62 range.start.byte < self.old_end_byte && range.end.byte > self.start_byte
63 }
64
65 pub fn apply_to_position(&self, pos: Position) -> Option<Position> {
67 if pos.byte < self.start_byte {
68 Some(pos)
70 } else if pos.byte >= self.old_end_byte {
71 Some(Position {
73 byte: (pos.byte as isize + self.byte_shift()) as usize,
74 line: (pos.line as i32 + self.line_shift()) as u32,
75 column: if pos.line == self.old_end_position.line {
76 let col_shift =
78 self.new_end_position.column as i32 - self.old_end_position.column as i32;
79 (pos.column as i32 + col_shift) as u32
80 } else {
81 pos.column
83 },
84 })
85 } else {
86 None
88 }
89 }
90
91 pub fn apply_to_range(&self, range: &Range) -> Option<Range> {
93 let new_start = self.apply_to_position(range.start)?;
94 let new_end = self.apply_to_position(range.end)?;
95 Some(Range::new(new_start, new_end))
96 }
97}
98
99#[derive(Debug, Clone, Default)]
101pub struct EditSet {
102 pub(crate) edits: Vec<Edit>,
103}
104
105impl EditSet {
106 pub fn new() -> Self {
108 EditSet { edits: Vec::new() }
109 }
110
111 pub fn add(&mut self, edit: Edit) {
113 let pos = self
115 .edits
116 .iter()
117 .position(|e| e.start_byte > edit.start_byte)
118 .unwrap_or(self.edits.len());
119 self.edits.insert(pos, edit);
120 }
121
122 pub fn apply_to_position(&self, mut pos: Position) -> Option<Position> {
124 for edit in &self.edits {
125 pos = edit.apply_to_position(pos)?;
126 }
127 Some(pos)
128 }
129
130 pub fn apply_to_range(&self, mut range: Range) -> Option<Range> {
132 for edit in &self.edits {
133 range = edit.apply_to_range(&range)?;
134 }
135 Some(range)
136 }
137
138 pub fn len(&self) -> usize {
140 self.edits.len()
141 }
142
143 pub fn is_empty(&self) -> bool {
145 self.edits.is_empty()
146 }
147
148 pub fn edits(&self) -> &[Edit] {
150 &self.edits
151 }
152
153 pub fn affects_range(&self, range: &Range) -> bool {
155 self.edits.iter().any(|edit| edit.overlaps_range(range))
156 }
157
158 pub fn byte_shift_at(&self, byte: usize) -> isize {
160 self.edits
161 .iter()
162 .filter(|edit| edit.old_end_byte <= byte)
163 .map(|edit| edit.byte_shift())
164 .sum()
165 }
166
167 pub fn affected_ranges(&self) -> Vec<Range> {
169 self.edits
170 .iter()
171 .map(|edit| Range::new(edit.start_position, edit.old_end_position))
172 .collect()
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179 use perl_tdd_support::must_some;
180
181 #[test]
182 fn test_simple_edit() {
183 let edit = Edit::new(
185 10,
186 15,
187 17,
188 Position::new(10, 2, 5),
189 Position::new(15, 2, 10),
190 Position::new(17, 2, 12),
191 );
192
193 assert_eq!(edit.byte_shift(), 2);
194 assert_eq!(edit.line_shift(), 0);
195
196 let pos = Position::new(5, 1, 5);
198 assert_eq!(edit.apply_to_position(pos), Some(pos));
199
200 let pos = Position::new(20, 2, 15);
202 let new_pos = must_some(edit.apply_to_position(pos));
203 assert_eq!(new_pos.byte, 22);
204 assert_eq!(new_pos.column, 17);
205 }
206
207 #[test]
208 fn test_multiline_edit() {
209 let edit = Edit::new(
211 10,
212 30,
213 20,
214 Position::new(10, 2, 5),
215 Position::new(30, 4, 10),
216 Position::new(20, 2, 15),
217 );
218
219 assert_eq!(edit.byte_shift(), -10);
220 assert_eq!(edit.line_shift(), -2);
221
222 let pos = Position::new(50, 6, 5);
224 let new_pos = must_some(edit.apply_to_position(pos));
225 assert_eq!(new_pos.byte, 40);
226 assert_eq!(new_pos.line, 4);
227 assert_eq!(new_pos.column, 5);
228 }
229
230 #[test]
231 fn test_edit_set() {
232 let mut edits = EditSet::new();
233
234 edits.add(Edit::new(
236 10,
237 15,
238 17,
239 Position::new(10, 2, 5),
240 Position::new(15, 2, 10),
241 Position::new(17, 2, 12),
242 ));
243
244 edits.add(Edit::new(
245 30,
246 35,
247 40,
248 Position::new(30, 3, 5),
249 Position::new(35, 3, 10),
250 Position::new(40, 3, 15),
251 ));
252
253 assert_eq!(edits.byte_shift_at(50), 7); }
256}