1 use crate::{Line, Operation, OperationType, Update};
2
3 /// Line cache struct to work with xi update protocol.
4 #[derive(Clone, Debug, Default)]
5 pub struct LineCache {
6 invalid_before: u64,
7 lines: Vec<Line>,
8 invalid_after: u64,
9 }
10
11 impl LineCache {
12 /// Retrieve the number of invalid lines before
13 /// the start of the line cache.
before(&self) -> u6414 pub fn before(&self) -> u64 {
15 self.invalid_before
16 }
17
18 /// Retrieve the number of invalid lines after
19 /// the line cache.
after(&self) -> u6420 pub fn after(&self) -> u64 {
21 self.invalid_after
22 }
23
24 /// Retrieve all lines in the cache.
lines(&self) -> &Vec<Line>25 pub fn lines(&self) -> &Vec<Line> {
26 &self.lines
27 }
28
29 /// Retrieve the total height of the linecache
height(&self) -> u6430 pub fn height(&self) -> u64 {
31 self.before() + self.lines.len() as u64 + self.after()
32 }
33
34 /// Handle an xi-core update.
update(&mut self, update: Update)35 pub fn update(&mut self, update: Update) {
36 debug!("line cache before update: {:?}", self);
37 debug!(
38 "operations to be applied to the line cache: {:?}",
39 &update.operations
40 );
41 let LineCache {
42 ref mut lines,
43 ref mut invalid_before,
44 ref mut invalid_after,
45 } = *self;
46 let helper = UpdateHelper {
47 old_lines: lines,
48 old_invalid_before: invalid_before,
49 old_invalid_after: invalid_after,
50 new_lines: Vec::new(),
51 new_invalid_before: 0,
52 new_invalid_after: 0,
53 };
54 helper.update(update.operations);
55 }
56
is_empty(&self) -> bool57 pub fn is_empty(&self) -> bool {
58 self.lines.is_empty()
59 }
60 }
61
62 #[derive(Debug)]
63 struct UpdateHelper<'a, 'b, 'c> {
64 old_lines: &'a mut Vec<Line>,
65 old_invalid_before: &'b mut u64,
66 old_invalid_after: &'c mut u64,
67 new_lines: Vec<Line>,
68 new_invalid_before: u64,
69 new_invalid_after: u64,
70 }
71
72 impl<'a, 'b, 'c> UpdateHelper<'a, 'b, 'c> {
apply_copy(&mut self, nb_lines: u64, first_line_num: Option<u64>)73 fn apply_copy(&mut self, nb_lines: u64, first_line_num: Option<u64>) {
74 debug!("copying {} lines", nb_lines);
75 let UpdateHelper {
76 ref mut old_lines,
77 ref mut old_invalid_before,
78 ref mut old_invalid_after,
79 ref mut new_lines,
80 ref mut new_invalid_before,
81 ref mut new_invalid_after,
82 ..
83 } = *self;
84
85 // The number of lines left to copy
86 let mut nb_lines = nb_lines;
87
88 // STEP 1: Handle the invalid lines that precede the valid ones
89 // ------------------------------------------------------------
90
91 if **old_invalid_before >= nb_lines {
92 // case 1: there are more (or equal) invalid lines than lines to copy
93
94 // decrement old_invalid_lines by nb_lines
95 **old_invalid_before -= nb_lines;
96
97 // and increment new_invalid_lines by the same amount
98 *new_invalid_before += nb_lines;
99
100 // there is no more line to copy so we're done
101 return;
102 } else if **old_invalid_after > 0 {
103 // case 2: there are more lines to copy than invalid lines
104
105 // decrement the nb of lines to copy by the number of invalid lines
106 nb_lines -= **old_invalid_before;
107
108 // increment new_invalid_lines by the same amount
109 *new_invalid_before += **old_invalid_before;
110
111 // we don't have any invalid lines left
112 **old_invalid_before = 0;
113 }
114
115 // STEP 2: Handle the valid lines
116 // ------------------------------------------------------------
117
118 let nb_valid_lines = old_lines.len();
119 let range;
120
121 if nb_lines <= (nb_valid_lines as u64) {
122 // case 1: the are more (or equal) valid lines than lines to copy
123
124 // the range of lines to copy: from the start to nb_lines - 1;
125 range = 0..nb_lines as usize;
126
127 // after the copy, we won't have any line remaining to copy
128 nb_lines = 0;
129 } else {
130 // case 2: there are more lines to copy than valid lines
131
132 // we copy all the valid lines
133 range = 0..nb_valid_lines;
134
135 // after the operation we'll have (nb_lines - nb_valid_lines) left to copy
136 nb_lines -= nb_valid_lines as u64;
137 }
138
139 // we'll only apply the copy if there actually are valid lines to copy
140 if nb_valid_lines > 0 {
141 let diff = if let Some(new_first_line_num) = first_line_num {
142 // find the first "real" line (ie non-wrapped), and
143 // compute the difference between its line number and
144 // its *new* line number, given by the "copy"
145 // operation. This will be used to update the line
146 // number for all the lines we copy.
147 old_lines
148 .iter()
149 .find_map(|line| {
150 line.line_num
151 .map(|num| new_first_line_num as i64 - num as i64)
152 })
153 .unwrap_or(0)
154 } else {
155 // if the "copy" operation does not specify a new line
156 // number, just set the diff to 0
157 0
158 };
159
160 let copied_lines = old_lines.drain(range).map(|mut line| {
161 line.line_num = line
162 .line_num
163 .map(|line_num| (line_num as i64 + diff) as u64);
164 line
165 });
166 new_lines.extend(copied_lines);
167 }
168
169 // if there are no more lines to copy we're done
170 if nb_lines == 0 {
171 return;
172 }
173
174 // STEP 3: Handle the remaining invalid lines
175 // ------------------------------------------------------------
176
177 // We should have at least enought invalid lines to copy, otherwise it indicates there's a
178 // problem, and we panic.
179 if **old_invalid_after >= nb_lines {
180 **old_invalid_after -= nb_lines;
181 *new_invalid_after += nb_lines;
182 } else {
183 error!(
184 "{} lines left to copy, but only {} lines in the old cache",
185 nb_lines, **old_invalid_after
186 );
187 panic!("cache update failed");
188 }
189 }
190
apply_skip(&mut self, nb_lines: u64)191 fn apply_skip(&mut self, nb_lines: u64) {
192 debug!("skipping {} lines", nb_lines);
193
194 let UpdateHelper {
195 ref mut old_lines,
196 ref mut old_invalid_before,
197 ref mut old_invalid_after,
198 ..
199 } = *self;
200
201 let mut nb_lines = nb_lines;
202
203 // Skip invalid lines that comes before the valid ones.
204 if **old_invalid_before > nb_lines {
205 **old_invalid_before -= nb_lines;
206 return;
207 } else if **old_invalid_before > 0 {
208 nb_lines -= **old_invalid_before;
209 **old_invalid_before = 0;
210 }
211
212 // Skip the valid lines
213 let nb_valid_lines = old_lines.len();
214 if nb_lines < nb_valid_lines as u64 {
215 old_lines.drain(0..nb_lines as usize).last();
216 return;
217 } else {
218 old_lines.drain(..).last();
219 nb_lines -= nb_valid_lines as u64;
220 }
221
222 // Skip the remaining invalid lines
223 if **old_invalid_after >= nb_lines {
224 **old_invalid_after -= nb_lines;
225 return;
226 }
227
228 error!(
229 "{} lines left to skip, but only {} lines in the old cache",
230 nb_lines, **old_invalid_after
231 );
232 panic!("cache update failed");
233 }
234
apply_invalidate(&mut self, nb_lines: u64)235 fn apply_invalidate(&mut self, nb_lines: u64) {
236 debug!("invalidating {} lines", nb_lines);
237 if self.new_lines.is_empty() {
238 self.new_invalid_before += nb_lines;
239 } else {
240 self.new_invalid_after += nb_lines;
241 }
242 }
243
apply_insert(&mut self, mut lines: Vec<Line>)244 fn apply_insert(&mut self, mut lines: Vec<Line>) {
245 debug!("inserting {} lines", lines.len());
246 self.new_lines.extend(lines.drain(..).map(|mut line| {
247 trim_new_line(&mut line.text);
248 line
249 }));
250 }
251
apply_update(&mut self, nb_lines: u64, lines: Vec<Line>)252 fn apply_update(&mut self, nb_lines: u64, lines: Vec<Line>) {
253 debug!("updating {} lines", nb_lines);
254 let UpdateHelper {
255 ref mut old_lines,
256 ref mut new_lines,
257 ..
258 } = *self;
259 if nb_lines > old_lines.len() as u64 {
260 error!(
261 "{} lines to update, but only {} lines in cache",
262 nb_lines,
263 old_lines.len()
264 );
265 panic!("failed to update the cache");
266 }
267 new_lines.extend(
268 old_lines
269 .drain(0..nb_lines as usize)
270 .zip(lines.into_iter())
271 .map(|(mut old_line, update)| {
272 old_line.cursor = update.cursor;
273 old_line.styles = update.styles;
274 old_line
275 }),
276 )
277 }
278
update(mut self, operations: Vec<Operation>)279 fn update(mut self, operations: Vec<Operation>) {
280 trace!("updating the line cache");
281 trace!("cache state before: {:?}", &self);
282 trace!("operations to be applied: {:?}", &operations);
283 for op in operations {
284 debug!("operation: {:?}", &op);
285 debug!("cache helper before operation {:?}", &self);
286 match op.operation_type {
287 OperationType::Copy_ => (&mut self).apply_copy(op.nb_lines, op.line_num),
288 OperationType::Skip => (&mut self).apply_skip(op.nb_lines),
289 OperationType::Invalidate => (&mut self).apply_invalidate(op.nb_lines),
290 OperationType::Insert => (&mut self).apply_insert(op.lines),
291 OperationType::Update => (&mut self).apply_update(op.nb_lines, op.lines),
292 }
293 debug!("cache helper after operation {:?}", &self);
294 }
295 *self.old_lines = self.new_lines;
296 *self.old_invalid_before = self.new_invalid_before;
297 *self.old_invalid_after = self.new_invalid_after;
298 }
299 }
300
trim_new_line(text: &mut String)301 fn trim_new_line(text: &mut String) {
302 if let Some('\n') = text.chars().last() {
303 text.pop();
304 }
305 }
306