turbo_tasks_fs/
source_context.rs

1use std::{borrow::Cow, cmp::Ordering, fmt::Display};
2
3pub enum SourceContextLine<'a> {
4    Context {
5        line: usize,
6        outside: Cow<'a, str>,
7    },
8    Start {
9        line: usize,
10        before: Cow<'a, str>,
11        inside: Cow<'a, str>,
12    },
13    End {
14        line: usize,
15        inside: Cow<'a, str>,
16        after: Cow<'a, str>,
17    },
18    StartAndEnd {
19        line: usize,
20        before: Cow<'a, str>,
21        inside: Cow<'a, str>,
22        after: Cow<'a, str>,
23    },
24    Inside {
25        line: usize,
26        inside: Cow<'a, str>,
27    },
28}
29
30impl Display for SourceContextLine<'_> {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        match self {
33            SourceContextLine::Context { line, outside } => {
34                writeln!(f, "{line:>6} | {outside}")
35            }
36            SourceContextLine::Start {
37                line,
38                before,
39                inside,
40            } => {
41                writeln!(
42                    f,
43                    "       | {}v{}",
44                    " ".repeat(before.len()),
45                    "-".repeat(inside.len()),
46                )?;
47                writeln!(f, "{line:>6} + {before}{inside}")
48            }
49            SourceContextLine::End {
50                line,
51                inside,
52                after,
53            } => {
54                writeln!(f, "{line:>6} + {inside}{after}")?;
55                writeln!(f, "       +{}^", "-".repeat(inside.len()))
56            }
57            SourceContextLine::StartAndEnd {
58                line,
59                before,
60                inside,
61                after,
62            } => {
63                if inside.len() >= 2 {
64                    writeln!(
65                        f,
66                        "       | {}v{}v",
67                        " ".repeat(before.len()),
68                        "-".repeat(inside.len() - 2)
69                    )?;
70                } else {
71                    writeln!(f, "       | {}v", " ".repeat(before.len()))?;
72                }
73                writeln!(f, "{line:>6} + {before}{inside}{after}")?;
74                if inside.len() >= 2 {
75                    writeln!(
76                        f,
77                        "       | {}^{}^",
78                        " ".repeat(before.len()),
79                        "-".repeat(inside.len() - 2),
80                    )
81                } else {
82                    writeln!(f, "       | {}^", " ".repeat(before.len()))
83                }
84            }
85            SourceContextLine::Inside { line, inside } => {
86                writeln!(f, "{line:>6} + {inside}")
87            }
88        }
89    }
90}
91
92pub struct SourceContextLines<'a>(pub Vec<SourceContextLine<'a>>);
93
94impl Display for SourceContextLines<'_> {
95    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96        for line in &self.0 {
97            write!(f, "{line}")?;
98        }
99        Ok(())
100    }
101}
102
103/// Compute the source context for a given range of lines, including selected
104/// ranges in these lines. (Lines are 0-indexed)
105pub fn get_source_context<'a>(
106    lines: impl Iterator<Item = &'a str>,
107    start_line: u32,
108    start_column: u32,
109    end_line: u32,
110    end_column: u32,
111) -> SourceContextLines<'a> {
112    let mut result = Vec::new();
113    let context_start = (start_line.saturating_sub(4)) as usize;
114    let context_end = (end_line + 4) as usize;
115    for (i, l) in lines.enumerate().take(context_end + 1).skip(context_start) {
116        let n = i + 1;
117        let i = i as u32;
118        fn safe_split_at(s: &str, i: u32) -> (&str, &str) {
119            let i = i as usize;
120            if i < s.len() {
121                s.split_at(s.floor_char_boundary(i))
122            } else {
123                (s, "")
124            }
125        }
126        fn limit_len(s: &str) -> Cow<'_, str> {
127            if s.len() < 200 {
128                return Cow::Borrowed(s);
129            }
130            let (a, b) = s.split_at(s.floor_char_boundary(98));
131            let (_, c) = b.split_at(b.ceil_char_boundary(b.len() - 99));
132            Cow::Owned(format!("{a}...{c}"))
133        }
134        match (i.cmp(&start_line), i.cmp(&end_line)) {
135            // outside
136            (Ordering::Less, _) | (_, Ordering::Greater) => {
137                result.push(SourceContextLine::Context {
138                    line: n,
139                    outside: limit_len(l),
140                });
141            }
142            // start line
143            (Ordering::Equal, Ordering::Less) => {
144                let (before, inside) = safe_split_at(l, start_column);
145                let before = limit_len(before);
146                let inside = limit_len(inside);
147                result.push(SourceContextLine::Start {
148                    line: n,
149                    before,
150                    inside,
151                });
152            }
153            // start and end line
154            (Ordering::Equal, Ordering::Equal) => {
155                let real_start = l.floor_char_boundary(start_column as usize) as u32;
156                let (before, temp) = safe_split_at(l, real_start);
157                let (inside, after) = safe_split_at(temp, end_column - real_start);
158                let before = limit_len(before);
159                let inside = limit_len(inside);
160                let after = limit_len(after);
161                result.push(SourceContextLine::StartAndEnd {
162                    line: n,
163                    before,
164                    inside,
165                    after,
166                });
167            }
168            // end line
169            (Ordering::Greater, Ordering::Equal) => {
170                let (inside, after) = safe_split_at(l, end_column);
171                let inside = limit_len(inside);
172                let after = limit_len(after);
173                result.push(SourceContextLine::End {
174                    line: n,
175                    inside,
176                    after,
177                });
178            }
179            // middle line
180            (Ordering::Greater, Ordering::Less) => {
181                result.push(SourceContextLine::Inside {
182                    line: n,
183                    inside: limit_len(l),
184                });
185            }
186        }
187    }
188    SourceContextLines(result)
189}