1use alloc::borrow::Cow;
2use alloc::string::String;
3use alloc::{vec, vec::Vec};
4use core::cmp::{max, min};
5use core::ops::Range;
6
7use crate::renderer::{LineAnnotation, LineAnnotationType, char_width, num_overlap};
8use crate::{Annotation, AnnotationKind, Patch};
9
10#[derive(Debug)]
11pub(crate) struct SourceMap<'a> {
12 lines: Vec<LineInfo<'a>>,
13 pub(crate) source: &'a str,
14}
15
16impl<'a> SourceMap<'a> {
17 pub(crate) fn new(source: &'a str, line_start: usize) -> Self {
18 if source.is_empty() {
21 return Self {
22 lines: vec![LineInfo {
23 line: "",
24 line_index: line_start,
25 start_byte: 0,
26 end_byte: 0,
27 end_line_size: 0,
28 }],
29 source,
30 };
31 }
32
33 let mut current_index = 0;
34
35 let mut mapping = vec![];
36 for (idx, (line, end_line)) in CursorLines::new(source).enumerate() {
37 let line_length = line.len();
38 let line_range = current_index..current_index + line_length;
39 let end_line_size = end_line.len();
40
41 mapping.push(LineInfo {
42 line,
43 line_index: line_start + idx,
44 start_byte: line_range.start,
45 end_byte: line_range.end + end_line_size,
46 end_line_size,
47 });
48
49 current_index += line_length + end_line_size;
50 }
51 Self {
52 lines: mapping,
53 source,
54 }
55 }
56
57 pub(crate) fn get_line(&self, idx: usize) -> Option<&'a str> {
58 self.lines
59 .iter()
60 .find(|l| l.line_index == idx)
61 .map(|info| info.line)
62 }
63
64 pub(crate) fn span_to_locations(&self, span: Range<usize>) -> (Loc, Loc) {
65 let start_info = self
66 .lines
67 .iter()
68 .find(|info| span.start >= info.start_byte && span.start < info.end_byte)
69 .unwrap_or(self.lines.last().unwrap());
70 let (mut start_char_pos, start_display_pos) = start_info.line
71 [0..(span.start - start_info.start_byte).min(start_info.line.len())]
72 .chars()
73 .fold((0, 0), |(char_pos, byte_pos), c| {
74 let display = char_width(c);
75 (char_pos + 1, byte_pos + display)
76 });
77 if (span.start - start_info.start_byte).saturating_sub(start_info.line.len()) > 0 {
79 start_char_pos += 1;
80 }
81 let start = Loc {
82 line: start_info.line_index,
83 char: start_char_pos,
84 display: start_display_pos,
85 byte: span.start,
86 };
87
88 if span.start == span.end {
89 return (start, start);
90 }
91
92 let end_info = self
93 .lines
94 .iter()
95 .find(|info| span.end >= info.start_byte && span.end < info.end_byte)
96 .unwrap_or(self.lines.last().unwrap());
97 let (end_char_pos, end_display_pos) = end_info.line
98 [0..(span.end - end_info.start_byte).min(end_info.line.len())]
99 .chars()
100 .fold((0, 0), |(char_pos, byte_pos), c| {
101 let display = char_width(c);
102 (char_pos + 1, byte_pos + display)
103 });
104
105 let mut end = Loc {
106 line: end_info.line_index,
107 char: end_char_pos,
108 display: end_display_pos,
109 byte: span.end,
110 };
111 if start.line != end.line && end.byte > end_info.end_byte - end_info.end_line_size {
112 end.char += 1;
113 end.display += 1;
114 }
115
116 (start, end)
117 }
118
119 pub(crate) fn span_to_snippet(&self, span: Range<usize>) -> Option<&str> {
120 self.source.get(span)
121 }
122
123 pub(crate) fn span_to_lines(&self, span: Range<usize>) -> Vec<&LineInfo<'a>> {
124 let mut lines = vec![];
125 let start = span.start;
126 let end = span.end;
127 for line_info in &self.lines {
128 if start >= line_info.end_byte {
129 continue;
130 }
131 if end < line_info.start_byte {
132 break;
133 }
134 lines.push(line_info);
135 }
136
137 if lines.is_empty() && !self.lines.is_empty() {
138 lines.push(self.lines.last().unwrap());
139 }
140
141 lines
142 }
143
144 pub(crate) fn annotated_lines(
145 &self,
146 annotations: Vec<Annotation<'a>>,
147 fold: bool,
148 ) -> (usize, Vec<AnnotatedLineInfo<'a>>) {
149 let source_len = self.source.len();
150 if let Some(bigger) = annotations.iter().find_map(|x| {
151 if source_len + 1 < x.span.end {
153 Some(&x.span)
154 } else {
155 None
156 }
157 }) {
158 panic!("Annotation range `{bigger:?}` is beyond the end of buffer `{source_len}`")
159 }
160
161 let mut annotated_line_infos = self
162 .lines
163 .iter()
164 .map(|info| AnnotatedLineInfo {
165 line: info.line,
166 line_index: info.line_index,
167 annotations: vec![],
168 keep: false,
169 })
170 .collect::<Vec<_>>();
171 let mut multiline_annotations = vec![];
172
173 for Annotation {
174 span,
175 label,
176 kind,
177 highlight_source,
178 } in annotations
179 {
180 let (lo, mut hi) = self.span_to_locations(span.clone());
181 if kind == AnnotationKind::Visible {
182 for line_idx in lo.line..=hi.line {
183 self.keep_line(&mut annotated_line_infos, line_idx);
184 }
185 continue;
186 }
187 if lo.display == hi.display && lo.line == hi.line {
194 hi.display += 1;
195 }
196
197 if lo.line == hi.line {
198 let line_ann = LineAnnotation {
199 start: lo,
200 end: hi,
201 kind,
202 label,
203 annotation_type: LineAnnotationType::Singleline,
204 highlight_source,
205 };
206 self.add_annotation_to_file(&mut annotated_line_infos, lo.line, line_ann);
207 } else {
208 multiline_annotations.push(MultilineAnnotation {
209 depth: 1,
210 start: lo,
211 end: hi,
212 kind,
213 label,
214 overlaps_exactly: false,
215 highlight_source,
216 });
217 }
218 }
219
220 let mut primary_spans = vec![];
221
222 multiline_annotations.sort_by_key(|ml| (ml.start.line, usize::MAX - ml.end.line));
224 for (outer_i, ann) in multiline_annotations.clone().into_iter().enumerate() {
225 if ann.kind.is_primary() {
226 primary_spans.push((ann.start, ann.end));
227 }
228 for (inner_i, a) in &mut multiline_annotations.iter_mut().enumerate() {
229 if !ann.same_span(a)
232 && num_overlap(ann.start.line, ann.end.line, a.start.line, a.end.line, true)
233 {
234 a.increase_depth();
235 } else if ann.same_span(a) && outer_i != inner_i {
236 a.overlaps_exactly = true;
237 } else {
238 if primary_spans
239 .iter()
240 .any(|(s, e)| a.start == *s && a.end == *e)
241 {
242 a.kind = AnnotationKind::Primary;
243 }
244 break;
245 }
246 }
247 }
248
249 let mut max_depth = 0; for ann in &multiline_annotations {
251 max_depth = max(max_depth, ann.depth);
252 }
253 for a in &mut multiline_annotations {
255 a.depth = max_depth - a.depth + 1;
256 }
257 for ann in multiline_annotations {
258 let mut end_ann = ann.as_end();
259 if ann.overlaps_exactly {
260 end_ann.annotation_type = LineAnnotationType::Singleline;
261 } else {
262 self.add_annotation_to_file(
285 &mut annotated_line_infos,
286 ann.start.line,
287 ann.as_start(),
288 );
289 let middle = min(ann.start.line + 4, ann.end.line);
294 let filter = |s: &str| {
298 let s = s.trim();
299 !(s.starts_with("//") && !(s.starts_with("///") || s.starts_with("//!")))
301 && !["", "{", "}", "(", ")", "[", "]"].contains(&s)
303 };
304 let until = (ann.start.line..middle)
305 .rev()
306 .filter_map(|line| self.get_line(line).map(|s| (line + 1, s)))
307 .find(|(_, s)| filter(s))
308 .map_or(ann.start.line, |(line, _)| line);
309 for line in ann.start.line + 1..until {
310 self.add_annotation_to_file(&mut annotated_line_infos, line, ann.as_line());
312 }
313 let line_end = ann.end.line - 1;
314 let end_is_empty = self.get_line(line_end).is_some_and(|s| !filter(s));
315 if middle < line_end && !end_is_empty {
316 self.add_annotation_to_file(&mut annotated_line_infos, line_end, ann.as_line());
317 }
318 }
319 self.add_annotation_to_file(&mut annotated_line_infos, end_ann.end.line, end_ann);
320 }
321
322 if fold {
323 annotated_line_infos.retain(|l| !l.annotations.is_empty() || l.keep);
324 }
325
326 (max_depth, annotated_line_infos)
327 }
328
329 fn add_annotation_to_file(
330 &self,
331 annotated_line_infos: &mut Vec<AnnotatedLineInfo<'a>>,
332 line_index: usize,
333 line_ann: LineAnnotation<'a>,
334 ) {
335 if let Some(line_info) = annotated_line_infos
336 .iter_mut()
337 .find(|line_info| line_info.line_index == line_index)
338 {
339 line_info.annotations.push(line_ann);
340 } else {
341 let info = self
342 .lines
343 .iter()
344 .find(|l| l.line_index == line_index)
345 .unwrap();
346 annotated_line_infos.push(AnnotatedLineInfo {
347 line: info.line,
348 line_index,
349 annotations: vec![line_ann],
350 keep: false,
351 });
352 annotated_line_infos.sort_by_key(|l| l.line_index);
353 }
354 }
355
356 fn keep_line(&self, annotated_line_infos: &mut Vec<AnnotatedLineInfo<'a>>, line_index: usize) {
357 if let Some(line_info) = annotated_line_infos
358 .iter_mut()
359 .find(|line_info| line_info.line_index == line_index)
360 {
361 line_info.keep = true;
362 } else {
363 let info = self
364 .lines
365 .iter()
366 .find(|l| l.line_index == line_index)
367 .unwrap();
368 annotated_line_infos.push(AnnotatedLineInfo {
369 line: info.line,
370 line_index,
371 annotations: vec![],
372 keep: true,
373 });
374 annotated_line_infos.sort_by_key(|l| l.line_index);
375 }
376 }
377
378 pub(crate) fn splice_lines<'b>(
379 &'a self,
380 mut patches: Vec<Patch<'b>>,
381 fold: bool,
382 ) -> Option<SplicedLines<'b>> {
383 fn push_trailing(buf: &mut String, line_opt: Option<&str>, lo: &Loc, hi_opt: Option<&Loc>) {
384 let (lo, hi_opt) = (lo.char, hi_opt.map(|hi| hi.char));
387 if let Some(line) = line_opt {
388 if let Some(lo) = line.char_indices().map(|(i, _)| i).nth(lo) {
389 let hi_opt = hi_opt.and_then(|hi| line.char_indices().map(|(i, _)| i).nth(hi));
391 match hi_opt {
392 Some(hi) if hi > lo => buf.push_str(&line[lo..hi]),
394 Some(_) => (),
395 None => buf.push_str(&line[lo..]),
397 }
398 }
399 if hi_opt.is_none() {
401 buf.push('\n');
402 }
403 }
404 }
405
406 let source_len = self.source.len();
407 if let Some(bigger) = patches.iter().find_map(|x| {
408 if source_len + 1 < x.span.end {
410 Some(&x.span)
411 } else {
412 None
413 }
414 }) {
415 panic!("Patch span `{bigger:?}` is beyond the end of buffer `{source_len}`")
416 }
417
418 patches.sort_by_key(|p| p.span.start);
421
422 let (lo, hi) = if fold {
424 let lo = patches.iter().map(|p| p.span.start).min()?;
425 let hi = patches.iter().map(|p| p.span.end).max()?;
426 (lo, hi)
427 } else {
428 (0, source_len)
429 };
430
431 let lines = self.span_to_lines(lo..hi);
432
433 let mut highlights = vec![];
434 let (mut prev_hi, _) = self.span_to_locations(lo..hi);
444 prev_hi.char = 0;
445 let mut prev_line = lines.first().map(|line| line.line);
446 let mut buf = String::new();
447
448 let trimmed_patches = patches
449 .into_iter()
450 .map(|part| part.trim_trivial_replacements(self.source))
454 .collect::<Vec<_>>();
455 let mut line_highlight = vec![];
456 let mut acc = 0;
459 for part in &trimmed_patches {
460 let (cur_lo, cur_hi) = self.span_to_locations(part.span.clone());
461 if prev_hi.line == cur_lo.line {
462 push_trailing(&mut buf, prev_line, &prev_hi, Some(&cur_lo));
463 } else {
464 acc = 0;
465 highlights.push(core::mem::take(&mut line_highlight));
466 push_trailing(&mut buf, prev_line, &prev_hi, None);
467 for idx in prev_hi.line + 1..(cur_lo.line) {
469 if let Some(line) = self.get_line(idx) {
470 buf.push_str(line.as_ref());
471 buf.push('\n');
472 highlights.push(core::mem::take(&mut line_highlight));
473 }
474 }
475 if let Some(cur_line) = self.get_line(cur_lo.line) {
476 let end = match cur_line.char_indices().nth(cur_lo.char) {
477 Some((i, _)) => i,
478 None => cur_line.len(),
479 };
480 buf.push_str(&cur_line[..end]);
481 }
482 }
483 let len: isize = part
485 .replacement
486 .split('\n')
487 .next()
488 .unwrap_or(&part.replacement)
489 .chars()
490 .map(|c| match c {
491 '\t' => 4,
492 _ => 1,
493 })
494 .sum();
495 line_highlight.push(SubstitutionHighlight {
496 start: (cur_lo.char as isize + acc) as usize,
497 end: (cur_lo.char as isize + acc + len) as usize,
498 });
499 buf.push_str(&part.replacement);
500 acc += len - (cur_hi.char as isize - cur_lo.char as isize);
505 prev_hi = cur_hi;
506 prev_line = self.get_line(prev_hi.line);
507 for line in part.replacement.split('\n').skip(1) {
508 acc = 0;
509 highlights.push(core::mem::take(&mut line_highlight));
510 let end: usize = line
511 .chars()
512 .map(|c| match c {
513 '\t' => 4,
514 _ => 1,
515 })
516 .sum();
517 line_highlight.push(SubstitutionHighlight { start: 0, end });
518 }
519 }
520 highlights.push(core::mem::take(&mut line_highlight));
521 if fold {
522 if !buf.ends_with('\n') {
524 push_trailing(&mut buf, prev_line, &prev_hi, None);
525 }
526 } else {
527 if let Some(snippet) = self.span_to_snippet(prev_hi.byte..source_len) {
529 buf.push_str(snippet);
530 for _ in snippet.matches('\n') {
531 highlights.push(core::mem::take(&mut line_highlight));
532 }
533 }
534 }
535 while buf.ends_with('\n') {
537 buf.pop();
538 }
539
540 let (bounding_lo, bounding_hi) = self.span_to_locations(lo..hi);
541 let line_count = bounding_hi.line.saturating_sub(bounding_lo.line) + 1;
542 let mut replaced_highlights: Vec<Vec<SubstitutionHighlight>> = vec![Vec::new(); line_count];
543 for part in &trimmed_patches {
544 let (cur_lo, cur_hi) = self.span_to_locations(part.span.clone());
545 for line in cur_lo.line..=cur_hi.line {
546 let start = if line == cur_lo.line { cur_lo.char } else { 0 };
547 let end = if line == cur_hi.line {
548 cur_hi.char
549 } else {
550 self.get_line(line).unwrap_or_default().chars().count()
551 };
552 replaced_highlights[line - bounding_lo.line]
553 .push(SubstitutionHighlight { start, end });
554 }
555 }
556
557 if highlights.iter().all(|parts| parts.is_empty()) {
558 None
559 } else {
560 Some((buf, trimmed_patches, highlights, replaced_highlights))
561 }
562 }
563}
564
565#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
566pub(crate) struct MultilineAnnotation<'a> {
567 pub depth: usize,
568 pub start: Loc,
569 pub end: Loc,
570 pub kind: AnnotationKind,
571 pub label: Option<Cow<'a, str>>,
572 pub overlaps_exactly: bool,
573 pub highlight_source: bool,
574}
575
576impl<'a> MultilineAnnotation<'a> {
577 pub(crate) fn increase_depth(&mut self) {
578 self.depth += 1;
579 }
580
581 pub(crate) fn same_span(&self, other: &MultilineAnnotation<'_>) -> bool {
583 self.start == other.start && self.end == other.end
584 }
585
586 pub(crate) fn as_start(&self) -> LineAnnotation<'a> {
587 LineAnnotation {
588 start: self.start,
589 end: Loc {
590 line: self.start.line,
591 char: self.start.char + 1,
592 display: self.start.display + 1,
593 byte: self.start.byte + 1,
594 },
595 kind: self.kind,
596 label: None,
597 annotation_type: LineAnnotationType::MultilineStart(self.depth),
598 highlight_source: self.highlight_source,
599 }
600 }
601
602 pub(crate) fn as_end(&self) -> LineAnnotation<'a> {
603 LineAnnotation {
604 start: Loc {
605 line: self.end.line,
606 char: self.end.char.saturating_sub(1),
607 display: self.end.display.saturating_sub(1),
608 byte: self.end.byte.saturating_sub(1),
609 },
610 end: self.end,
611 kind: self.kind,
612 label: self.label.clone(),
613 annotation_type: LineAnnotationType::MultilineEnd(self.depth),
614 highlight_source: self.highlight_source,
615 }
616 }
617
618 pub(crate) fn as_line(&self) -> LineAnnotation<'a> {
619 LineAnnotation {
620 start: Loc::default(),
621 end: Loc::default(),
622 kind: self.kind,
623 label: None,
624 annotation_type: LineAnnotationType::MultilineLine(self.depth),
625 highlight_source: self.highlight_source,
626 }
627 }
628}
629
630#[derive(Debug)]
631pub(crate) struct LineInfo<'a> {
632 pub(crate) line: &'a str,
633 pub(crate) line_index: usize,
634 pub(crate) start_byte: usize,
635 pub(crate) end_byte: usize,
636 end_line_size: usize,
637}
638
639#[derive(Debug)]
640pub(crate) struct AnnotatedLineInfo<'a> {
641 pub(crate) line: &'a str,
642 pub(crate) line_index: usize,
643 pub(crate) annotations: Vec<LineAnnotation<'a>>,
644 pub(crate) keep: bool,
645}
646
647#[derive(Clone, Copy, Debug, Default, PartialOrd, Ord, PartialEq, Eq)]
649pub(crate) struct Loc {
650 pub(crate) line: usize,
652 pub(crate) char: usize,
654 pub(crate) display: usize,
656 pub(crate) byte: usize,
658}
659
660struct CursorLines<'a>(&'a str);
661
662impl CursorLines<'_> {
663 fn new(src: &str) -> CursorLines<'_> {
664 CursorLines(src)
665 }
666}
667
668#[derive(Copy, Clone, Debug, PartialEq)]
669enum EndLine {
670 Eof,
671 Lf,
672 Crlf,
673}
674
675impl EndLine {
676 pub(crate) fn len(self) -> usize {
678 match self {
679 EndLine::Eof => 0,
680 EndLine::Lf => 1,
681 EndLine::Crlf => 2,
682 }
683 }
684}
685
686impl<'a> Iterator for CursorLines<'a> {
687 type Item = (&'a str, EndLine);
688
689 fn next(&mut self) -> Option<Self::Item> {
690 if self.0.is_empty() {
691 None
692 } else {
693 self.0
694 .find('\n')
695 .map(|x| {
696 let ret = if 0 < x {
697 if self.0.as_bytes()[x - 1] == b'\r' {
698 (&self.0[..x - 1], EndLine::Crlf)
699 } else {
700 (&self.0[..x], EndLine::Lf)
701 }
702 } else {
703 ("", EndLine::Lf)
704 };
705 self.0 = &self.0[x + 1..];
706 ret
707 })
708 .or_else(|| {
709 let ret = Some((self.0, EndLine::Eof));
710 self.0 = "";
711 ret
712 })
713 }
714 }
715}
716
717pub(crate) type SplicedLines<'a> = (
718 String,
719 Vec<TrimmedPatch<'a>>,
720 Vec<Vec<SubstitutionHighlight>>,
722 Vec<Vec<SubstitutionHighlight>>,
725);
726
727#[derive(Debug, Clone, Copy)]
730pub(crate) struct SubstitutionHighlight {
731 pub(crate) start: usize,
732 pub(crate) end: usize,
733}
734
735#[derive(Clone, Debug)]
736pub(crate) struct TrimmedPatch<'a> {
737 pub(crate) original_span: Range<usize>,
738 pub(crate) span: Range<usize>,
739 pub(crate) replacement: Cow<'a, str>,
740}
741
742impl<'a> TrimmedPatch<'a> {
743 pub(crate) fn is_addition(&self, sm: &SourceMap<'_>) -> bool {
744 !self.replacement.is_empty() && !self.replaces_meaningful_content(sm)
745 }
746
747 pub(crate) fn is_deletion(&self, sm: &SourceMap<'_>) -> bool {
748 self.replacement.trim().is_empty() && self.replaces_meaningful_content(sm)
749 }
750
751 pub(crate) fn is_replacement(&self, sm: &SourceMap<'_>) -> bool {
752 !self.replacement.is_empty() && self.replaces_meaningful_content(sm)
753 }
754
755 pub(crate) fn is_destructive_replacement(&self, sm: &SourceMap<'_>) -> bool {
760 self.is_replacement(sm)
761 && sm
762 .span_to_snippet(self.span.clone())
763 .is_none_or(|s| as_substr(s.trim(), self.replacement.trim()).is_none())
764 }
765
766 fn replaces_meaningful_content(&self, sm: &SourceMap<'_>) -> bool {
767 sm.span_to_snippet(self.span.clone())
768 .map_or(!self.span.is_empty(), |snippet| !snippet.trim().is_empty())
769 }
770}
771
772pub(crate) fn as_substr<'a>(
777 original: &'a str,
778 suggestion: &'a str,
779) -> Option<(usize, &'a str, usize)> {
780 if let Some(stripped) = suggestion.strip_prefix(original) {
781 Some((original.len(), stripped, 0))
782 } else if let Some(stripped) = suggestion.strip_suffix(original) {
783 Some((0, stripped, original.len()))
784 } else {
785 let common_prefix = original
786 .chars()
787 .zip(suggestion.chars())
788 .take_while(|(c1, c2)| c1 == c2)
789 .map(|(c, _)| c.len_utf8())
790 .sum();
791 let original = &original[common_prefix..];
792 let suggestion = &suggestion[common_prefix..];
793 if let Some(stripped) = suggestion.strip_suffix(original) {
794 let common_suffix = original.len();
795 Some((common_prefix, stripped, common_suffix))
796 } else {
797 None
798 }
799 }
800}