1use crate::lossless::{ErrorInfo, Makefile, PositionedParseError};
8use crate::parse::Parse;
9use rowan::TextRange;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct TextEdit {
14 pub range: TextRange,
16 pub new_text: String,
18}
19
20impl TextEdit {
21 pub fn new(range: TextRange, new_text: String) -> Self {
23 Self { range, new_text }
24 }
25
26 fn delta(&self) -> i64 {
28 self.new_text.len() as i64 - u32::from(self.range.len()) as i64
29 }
30}
31
32pub fn apply_edit_to_text(old_text: &str, edit: &TextEdit) -> String {
34 let start: usize = u32::from(edit.range.start()) as usize;
35 let end: usize = u32::from(edit.range.end()) as usize;
36 let mut new = String::with_capacity(old_text.len().wrapping_add_signed(edit.delta() as isize));
37 new.push_str(&old_text[..start]);
38 new.push_str(&edit.new_text);
39 new.push_str(&old_text[end..]);
40 new
41}
42
43impl Parse<Makefile> {
44 pub fn apply_edit(&self, old_text: &str, edit: &TextEdit) -> (Self, String) {
78 let new_text = apply_edit_to_text(old_text, edit);
79 let delta = edit.delta();
80
81 let old_green = self.green();
83 let children: Vec<_> = old_green.children().map(|c| c.to_owned()).collect();
84
85 let mut child_ranges: Vec<TextRange> = Vec::with_capacity(children.len());
87 let mut offset = rowan::TextSize::from(0);
88 for child in &children {
89 let len = match child {
90 rowan::NodeOrToken::Node(n) => n.text_len(),
91 rowan::NodeOrToken::Token(t) => t.text_len(),
92 };
93 child_ranges.push(TextRange::new(offset, offset + len));
94 offset += len;
95 }
96
97 if children.is_empty() {
98 let new_parse = Parse::parse_makefile(&new_text);
100 return (new_parse, new_text);
101 }
102
103 let first_affected = child_ranges
105 .iter()
106 .position(|r| r.end() > edit.range.start())
107 .unwrap_or(children.len().saturating_sub(1));
108
109 let last_affected = child_ranges
110 .iter()
111 .rposition(|r| r.start() < edit.range.end())
112 .unwrap_or(first_affected);
113
114 let reparse_start = child_ranges[first_affected].start();
116 let reparse_end_old = child_ranges[last_affected].end();
117
118 let reparse_end_new =
122 rowan::TextSize::from((u32::from(reparse_end_old) as i64 + delta) as u32);
123
124 let reparse_region =
125 &new_text[u32::from(reparse_start) as usize..u32::from(reparse_end_new) as usize];
126
127 let reparsed = crate::lossless::parse(reparse_region, None);
129 let reparsed_root = reparsed.green_node;
130
131 let new_root = old_green.splice_children(
133 first_affected..last_affected + 1,
134 reparsed_root.children().map(|c| c.to_owned()),
135 );
136
137 let mut new_errors = Vec::new();
139 let mut new_positioned_errors = Vec::new();
140
141 for err in self.errors() {
143 let lines_before = old_text[..u32::from(reparse_start) as usize]
148 .matches('\n')
149 .count();
150 if err.line <= lines_before {
151 new_errors.push(err.clone());
152 }
153 }
154
155 let line_offset = old_text[..u32::from(reparse_start) as usize]
157 .matches('\n')
158 .count();
159 for err in &reparsed.errors {
160 new_errors.push(ErrorInfo {
161 message: err.message.clone(),
162 line: err.line + line_offset,
163 context: err.context.clone(),
164 });
165 }
166
167 let old_lines_in_region = old_text
169 [u32::from(reparse_start) as usize..u32::from(reparse_end_old) as usize]
170 .matches('\n')
171 .count();
172 let new_lines_in_region = reparse_region.matches('\n').count();
173 let line_delta = new_lines_in_region as i64 - old_lines_in_region as i64;
174 let lines_after_start = line_offset + old_lines_in_region;
175 for err in self.errors() {
176 if err.line > lines_after_start {
177 new_errors.push(ErrorInfo {
178 message: err.message.clone(),
179 line: (err.line as i64 + line_delta) as usize,
180 context: err.context.clone(),
181 });
182 }
183 }
184
185 for err in self.positioned_errors() {
187 if err.range.end() <= reparse_start {
188 new_positioned_errors.push(err.clone());
189 }
190 }
191
192 for err in &reparsed.positioned_errors {
194 new_positioned_errors.push(PositionedParseError {
195 message: err.message.clone(),
196 range: TextRange::new(
197 err.range.start() + reparse_start,
198 err.range.end() + reparse_start,
199 ),
200 code: err.code.clone(),
201 });
202 }
203
204 for err in self.positioned_errors() {
206 if err.range.start() >= reparse_end_old {
207 let shift = rowan::TextSize::from(delta.unsigned_abs() as u32);
208 let (new_start, new_end) = if delta >= 0 {
209 (err.range.start() + shift, err.range.end() + shift)
210 } else {
211 (err.range.start() - shift, err.range.end() - shift)
212 };
213 new_positioned_errors.push(PositionedParseError {
214 message: err.message.clone(),
215 range: TextRange::new(new_start, new_end),
216 code: err.code.clone(),
217 });
218 }
219 }
220
221 let new_parse = Parse::new(new_root, new_errors, new_positioned_errors);
222 (new_parse, new_text)
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229 use rowan::ast::AstNode;
230
231 #[test]
232 fn test_apply_edit_to_text() {
233 let old = "hello world";
234 let edit = TextEdit::new(TextRange::new(6.into(), 11.into()), "rust".to_string());
235 assert_eq!(apply_edit_to_text(old, &edit), "hello rust");
236 }
237
238 #[test]
239 fn test_apply_edit_to_text_insert() {
240 let old = "hello world";
241 let edit = TextEdit::new(TextRange::new(5.into(), 5.into()), " beautiful".to_string());
242 assert_eq!(apply_edit_to_text(old, &edit), "hello beautiful world");
243 }
244
245 #[test]
246 fn test_apply_edit_to_text_delete() {
247 let old = "hello world";
248 let edit = TextEdit::new(TextRange::new(5.into(), 11.into()), String::new());
249 assert_eq!(apply_edit_to_text(old, &edit), "hello");
250 }
251
252 #[test]
253 fn test_incremental_change_variable_value() {
254 let old_text = "VAR1 = old\nVAR2 = value\n";
255 let parse = Parse::parse_makefile(old_text);
256
257 let edit = TextEdit::new(TextRange::new(7.into(), 10.into()), "new".to_string());
259 let (new_parse, new_text) = parse.apply_edit(old_text, &edit);
260
261 assert_eq!(new_text, "VAR1 = new\nVAR2 = value\n");
262 let makefile = new_parse.tree();
263 let vars: Vec<_> = makefile.variable_definitions().collect();
264 assert_eq!(vars.len(), 2);
265 assert_eq!(vars[0].name(), Some("VAR1".to_string()));
266 assert_eq!(vars[0].raw_value(), Some("new".to_string()));
267 assert_eq!(vars[1].name(), Some("VAR2".to_string()));
268 assert_eq!(vars[1].raw_value(), Some("value".to_string()));
269 }
270
271 #[test]
272 fn test_incremental_change_rule_command() {
273 let old_text = "all:\n\techo hello\n";
274 let parse = Parse::parse_makefile(old_text);
275
276 let edit = TextEdit::new(TextRange::new(11.into(), 16.into()), "goodbye".to_string());
278 let (new_parse, new_text) = parse.apply_edit(old_text, &edit);
279
280 assert_eq!(new_text, "all:\n\techo goodbye\n");
281 let makefile = new_parse.tree();
282 assert_eq!(makefile.rules().count(), 1);
283 }
284
285 #[test]
286 fn test_incremental_insert_new_variable() {
287 let old_text = "VAR1 = one\nVAR2 = two\n";
288 let parse = Parse::parse_makefile(old_text);
289
290 let edit = TextEdit::new(
292 TextRange::new(11.into(), 11.into()),
293 "NEW = inserted\n".to_string(),
294 );
295 let (new_parse, new_text) = parse.apply_edit(old_text, &edit);
296
297 assert_eq!(new_text, "VAR1 = one\nNEW = inserted\nVAR2 = two\n");
298 let makefile = new_parse.tree();
299 let vars: Vec<_> = makefile.variable_definitions().collect();
300 assert_eq!(vars.len(), 3);
301 assert_eq!(vars[0].name(), Some("VAR1".to_string()));
302 assert_eq!(vars[1].name(), Some("NEW".to_string()));
303 assert_eq!(vars[2].name(), Some("VAR2".to_string()));
304 }
305
306 #[test]
307 fn test_incremental_delete_variable() {
308 let old_text = "VAR1 = one\nVAR2 = two\nVAR3 = three\n";
309 let parse = Parse::parse_makefile(old_text);
310
311 let edit = TextEdit::new(TextRange::new(11.into(), 22.into()), String::new());
313 let (new_parse, new_text) = parse.apply_edit(old_text, &edit);
314
315 assert_eq!(new_text, "VAR1 = one\nVAR3 = three\n");
316 let makefile = new_parse.tree();
317 let vars: Vec<_> = makefile.variable_definitions().collect();
318 assert_eq!(vars.len(), 2);
319 assert_eq!(vars[0].name(), Some("VAR1".to_string()));
320 assert_eq!(vars[1].name(), Some("VAR3".to_string()));
321 }
322
323 #[test]
324 fn test_incremental_edit_preserves_unaffected() {
325 let old_text = "VAR1 = one\nVAR2 = two\nVAR3 = three\n";
326 let parse = Parse::parse_makefile(old_text);
327
328 let edit = TextEdit::new(TextRange::new(18.into(), 21.into()), "TWO".to_string());
330 let (new_parse, new_text) = parse.apply_edit(old_text, &edit);
331
332 assert_eq!(new_text, "VAR1 = one\nVAR2 = TWO\nVAR3 = three\n");
333 let makefile = new_parse.tree();
334 let vars: Vec<_> = makefile.variable_definitions().collect();
335 assert_eq!(vars.len(), 3);
336
337 let old_children: Vec<_> = parse.green().children().map(|c| c.to_owned()).collect();
339 let new_children: Vec<_> = new_parse.green().children().map(|c| c.to_owned()).collect();
340
341 assert_eq!(
343 old_children[0], new_children[0],
344 "VAR1 green node should be identical (reused)"
345 );
346 }
347
348 #[test]
349 fn test_incremental_empty_file() {
350 let old_text = "";
351 let parse = Parse::parse_makefile(old_text);
352
353 let edit = TextEdit::new(
354 TextRange::new(0.into(), 0.into()),
355 "VAR = value\n".to_string(),
356 );
357 let (new_parse, new_text) = parse.apply_edit(old_text, &edit);
358
359 assert_eq!(new_text, "VAR = value\n");
360 let makefile = new_parse.tree();
361 assert_eq!(makefile.variable_definitions().count(), 1);
362 }
363
364 #[test]
365 fn test_incremental_change_variable_to_rule() {
366 let old_text = "target = value\n";
367 let parse = Parse::parse_makefile(old_text);
368
369 let edit = TextEdit::new(TextRange::new(7.into(), 14.into()), ":".to_string());
371 let (new_parse, new_text) = parse.apply_edit(old_text, &edit);
372
373 assert_eq!(new_text, "target :\n");
374 let makefile = new_parse.tree();
375 assert_eq!(makefile.variable_definitions().count(), 0);
376 assert_eq!(makefile.rules().count(), 1);
377 }
378
379 #[test]
380 fn test_incremental_matches_full_reparse() {
381 let old_text = "VAR1 = one\nall: dep\n\techo $(VAR1)\nVAR2 = two\n";
382 let parse = Parse::parse_makefile(old_text);
383
384 let edit = TextEdit::new(TextRange::new(26.into(), 30.into()), "VAR2".to_string());
386 let (incremental, new_text) = parse.apply_edit(old_text, &edit);
387 let full = Parse::parse_makefile(&new_text);
388
389 let inc_tree = incremental.tree();
391 let full_tree: Makefile = full.tree();
392 assert_eq!(
393 inc_tree.syntax().to_string(),
394 full_tree.syntax().to_string()
395 );
396 }
397
398 #[test]
399 fn test_incremental_edit_at_end() {
400 let old_text = "VAR = value\n";
401 let parse = Parse::parse_makefile(old_text);
402
403 let edit = TextEdit::new(
405 TextRange::new(12.into(), 12.into()),
406 "all:\n\techo done\n".to_string(),
407 );
408 let (new_parse, new_text) = parse.apply_edit(old_text, &edit);
409
410 assert_eq!(new_text, "VAR = value\nall:\n\techo done\n");
411 let makefile = new_parse.tree();
412 assert_eq!(makefile.variable_definitions().count(), 1);
413 assert_eq!(makefile.rules().count(), 1);
414 }
415
416 #[test]
417 fn test_incremental_positioned_errors_shifted() {
418 let old_text = "VAR1 = one\nVAR2 = two\n";
420 let parse = Parse::parse_makefile(old_text);
421 assert!(parse.ok());
422
423 let edit = TextEdit::new(
425 TextRange::new(11.into(), 11.into()),
426 "\tbad line\n".to_string(),
427 );
428 let (new_parse, new_text) = parse.apply_edit(old_text, &edit);
429 assert_eq!(new_text, "VAR1 = one\n\tbad line\nVAR2 = two\n");
430
431 let full = Parse::parse_makefile(&new_text);
433 assert_eq!(new_parse.errors().len(), full.errors().len());
434 }
435
436 #[test]
437 fn test_incremental_with_include() {
438 let old_text = "include foo.mk\nVAR = value\n";
439 let parse = Parse::parse_makefile(old_text);
440
441 let edit = TextEdit::new(TextRange::new(8.into(), 14.into()), "bar.mk".to_string());
443 let (new_parse, new_text) = parse.apply_edit(old_text, &edit);
444
445 assert_eq!(new_text, "include bar.mk\nVAR = value\n");
446 let makefile = new_parse.tree();
447 let includes: Vec<_> = makefile.includes().collect();
448 assert_eq!(includes.len(), 1);
449 assert_eq!(includes[0].path(), Some("bar.mk".to_string()));
450 assert_eq!(makefile.variable_definitions().count(), 1);
451 }
452
453 #[test]
454 fn test_incremental_with_conditional() {
455 let old_text = "ifdef DEBUG\nCFLAGS = -g\nendif\nVAR = value\n";
456 let parse = Parse::parse_makefile(old_text);
457
458 let edit = TextEdit::new(TextRange::new(21.into(), 23.into()), "-O2".to_string());
460 let (new_parse, new_text) = parse.apply_edit(old_text, &edit);
461
462 assert_eq!(new_text, "ifdef DEBUG\nCFLAGS = -O2\nendif\nVAR = value\n");
463 let makefile = new_parse.tree();
464 assert_eq!(makefile.conditionals().count(), 1);
465 assert_eq!(makefile.variable_definitions().count(), 2);
467 }
468
469 #[test]
470 fn test_incremental_multiple_edits_sequentially() {
471 let old_text = "VAR1 = one\nVAR2 = two\nVAR3 = three\n";
472 let parse = Parse::parse_makefile(old_text);
473
474 let edit1 = TextEdit::new(TextRange::new(7.into(), 10.into()), "ONE".to_string());
476 let (parse2, text2) = parse.apply_edit(old_text, &edit1);
477
478 let edit2 = TextEdit::new(TextRange::new(29.into(), 34.into()), "THREE".to_string());
480 let (parse3, text3) = parse2.apply_edit(&text2, &edit2);
481
482 assert_eq!(text3, "VAR1 = ONE\nVAR2 = two\nVAR3 = THREE\n");
483 let makefile = parse3.tree();
484 let vars: Vec<_> = makefile.variable_definitions().collect();
485 assert_eq!(vars[0].raw_value(), Some("ONE".to_string()));
486 assert_eq!(vars[1].raw_value(), Some("two".to_string()));
487 assert_eq!(vars[2].raw_value(), Some("THREE".to_string()));
488 }
489}