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