1use crate::span::Span;
2use colored::Colorize;
3use unicode_width::UnicodeWidthStr;
4
5#[derive(Debug, Clone)]
7pub struct Source<'str> {
8 pub input: &'str str,
9}
10
11impl<'str> Source<'str> {
12 pub fn new(input: &'str str) -> Self {
16 let trimmed = input.trim();
17 Self { input: trimmed }
18 }
19
20 pub fn highlight(&self, span: &Span) -> String {
24 let input = &self.input;
25 let pre = Self::escape(&input[..span.start]);
26 let tok = Self::escape(&input[span.start..span.end]);
27 let post = Self::escape(&input[span.end..]);
28 let line = format!("{}{}{}", pre, tok.red().bold(), post);
29
30 let caret = "^".green().bold();
31 let squiggly_len = UnicodeWidthStr::width(tok.as_str());
32 let caret_offset = UnicodeWidthStr::width(pre.as_str()) + caret.len();
33
34 format!(
35 "1 | {0}\n | {1: >2$}{3}",
36 line,
37 caret,
38 caret_offset,
39 "~".repeat(squiggly_len.saturating_sub(1)).green()
40 )
41 }
42
43 fn escape(s: &str) -> String {
44 let mut out = String::with_capacity(s.len());
45 for c in s.chars() {
46 match c {
47 '\n' => out.push_str("\\n"),
48 '\r' => out.push_str("\\r"),
49 other => out.push(other),
50 }
51 }
52 out
53 }
54}