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 {
22 let input = &self.input;
23 let pre = Self::escape(&input[..span.start]);
24 let tok = Self::escape(&input[span.start..span.end]);
25 let post = Self::escape(&input[span.end..]);
26 let line = format!("{}{}{}", pre, tok.red().bold(), post);
27
28 let caret = "^".green().bold();
29 let squiggly_len = UnicodeWidthStr::width(tok.as_str());
30 let caret_offset = UnicodeWidthStr::width(pre.as_str()) + caret.len();
31
32 format!(
33 "1 | {0}\n | {1: >2$}{3}",
34 line,
35 caret,
36 caret_offset,
37 "~".repeat(squiggly_len.saturating_sub(1)).green()
38 )
39 }
40
41 fn escape(s: &str) -> String {
42 let mut out = String::with_capacity(s.len());
43 for c in s.chars() {
44 match c {
45 '\n' => out.push_str("\\n"),
46 '\r' => out.push_str("\\r"),
47 other => out.push(other),
48 }
49 }
50 out
51 }
52}