kind_report/report/
code.rs

1use fxhash::{FxHashMap, FxHashSet};
2use kind_span::{Pos, SyntaxCtxIndex};
3use std::{collections::hash_map::Iter, fmt::Display};
4use unicode_width::UnicodeWidthStr;
5
6use crate::data::Marker;
7
8/// The line guide is useful to locate some positions inside the source
9/// code by using the index instead of line and column information.
10pub struct LineGuide(Vec<usize>);
11
12pub struct FileMarkers(pub Vec<Marker>);
13
14/// This structure contains all markers sorted by lines and column for each
15/// one of the files.
16pub struct SortedMarkers(FxHashMap<SyntaxCtxIndex, FileMarkers>);
17
18impl SortedMarkers {
19    pub fn is_empty(&self) -> bool {
20        self.0.is_empty()
21    }
22
23    pub fn iter(&self) -> Iter<SyntaxCtxIndex, FileMarkers> {
24        self.0.iter()
25    }
26}
27
28#[derive(Clone, Copy)]
29pub struct Point {
30    pub line: usize,
31    pub column: usize,
32}
33
34impl Display for Point {
35    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36        write!(f, "{}:{}", self.line + 1, self.column + 1)
37    }
38}
39
40pub struct Spaces {
41    pub width: usize,
42    pub tabs: usize,
43}
44
45impl LineGuide {
46    pub fn get(code: &str) -> LineGuide {
47        let mut guide = Vec::new();
48        let mut size = 0;
49        for chr in code.chars() {
50            size += chr.len_utf8();
51            if chr == '\n' {
52                guide.push(size);
53            }
54        }
55        guide.push(code.len());
56        LineGuide(guide)
57    }
58
59    pub fn find(&self, pos: Pos) -> Point {
60        for i in 0..self.0.len() {
61            if self.0[i] > pos.index as usize {
62                return Point {
63                    line: i,
64                    column: pos.index as usize - (if i == 0 { 0 } else { self.0[i - 1] }),
65                };
66            }
67        }
68        let line = self.0.len() - 1;
69        Point {
70            line,
71            column: pos.index as usize - (if line == 0 { 0 } else { self.0[line - 1] }),
72        }
73    }
74
75    pub fn len(&self) -> usize {
76        self.0.len()
77    }
78}
79
80pub fn count_width(str: &str) -> Spaces {
81    Spaces {
82        width: UnicodeWidthStr::width(str),
83        tabs: str.chars().filter(|x| *x == '\t').count(),
84    }
85}
86
87pub fn group_markers(markers: &[Marker]) -> SortedMarkers {
88    let mut file_group = FxHashMap::default();
89
90    for marker in markers {
91        let group = file_group
92            .entry(marker.position.ctx)
93            .or_insert_with(Vec::new);
94        group.push(marker.clone())
95    }
96
97    for group in file_group.values_mut() {
98        group.sort_by(|x, y| x.position.start.cmp(&y.position.end));
99    }
100
101    SortedMarkers(
102        file_group
103            .into_iter()
104            .map(|(x, y)| (x, FileMarkers(y)))
105            .collect(),
106    )
107}
108
109pub fn group_marker_lines<'a>(
110    guide: &'a LineGuide,
111    markers: &'a FileMarkers,
112) -> (
113    FxHashSet<usize>,
114    FxHashMap<usize, Vec<(Point, Point, &'a Marker)>>,
115    Vec<(Point, Point, &'a Marker)>,
116) {
117    let mut lines_set = FxHashSet::default();
118    let mut markers_by_line: FxHashMap<usize, Vec<(Point, Point, &Marker)>> = FxHashMap::default();
119    let mut multi_line_markers: Vec<(Point, Point, &Marker)> = Vec::new();
120
121    for marker in &markers.0 {
122        let start = guide.find(marker.position.start);
123        let end = guide.find(marker.position.end);
124
125        if let Some(row) = markers_by_line.get_mut(&start.line) {
126            row.push((start, end, marker))
127        } else {
128            markers_by_line.insert(start.line, vec![(start, end, marker)]);
129        }
130
131        if end.line != start.line {
132            multi_line_markers.push((start, end, marker));
133        } else if marker.main {
134            // Just to make errors a little bit better
135            let start = start.line.saturating_sub(1);
136            let end = if start + 2 >= guide.len() {
137                guide.len() - 1
138            } else {
139                start + 2
140            };
141            for i in start..=end {
142                lines_set.insert(i);
143            }
144        }
145
146        if end.line - start.line <= 3 {
147            for i in start.line..=end.line {
148                lines_set.insert(i);
149            }
150        } else {
151            lines_set.insert(start.line);
152            lines_set.insert(end.line);
153        }
154    }
155
156    (lines_set, markers_by_line, multi_line_markers)
157}