1//! This module provides a syntax highlighter for Rust code.
2//! It is used by the `rustc --explain` command.
3//!
4//! The syntax highlighter uses `rustc_lexer`'s `tokenize`
5//! function to parse the Rust code into a `Vec` of tokens.
6//! The highlighter then highlights the tokens in the `Vec`,
7//! and writes the highlighted output to the buffer.
8use std::io::{self, Write};
910use anstyle::{AnsiColor, Color, Effects, Style};
11use rustc_lexer::{LiteralKind, strip_shebang, tokenize};
1213const PRIMITIVE_TYPES: &'static [&str] = &[
14"i8", "i16", "i32", "i64", "i128", "isize", // signed integers
15"u8", "u16", "u32", "u64", "u128", "usize", // unsigned integers
16"f32", "f64", // floating point
17"char", "bool", // others
18];
1920const KEYWORDS: &'static [&str] = &[
21"static", "struct", "super", "trait", "true", "type", "unsafe", "use", "where", "while", "as",
22"async", "await", "break", "const", "continue", "crate", "dyn", "else", "enum", "extern",
23"false", "fn", "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub",
24"ref",
25];
2627const STR_LITERAL_COLOR: AnsiColor = AnsiColor::Green;
28const OTHER_LITERAL_COLOR: AnsiColor = AnsiColor::BrightRed;
29const DERIVE_COLOR: AnsiColor = AnsiColor::BrightRed;
30const KEYWORD_COLOR: AnsiColor = AnsiColor::BrightMagenta;
31const TYPE_COLOR: AnsiColor = AnsiColor::Yellow;
32const FUNCTION_COLOR: AnsiColor = AnsiColor::Blue;
33const USE_COLOR: AnsiColor = AnsiColor::BrightMagenta;
34const PRIMITIVE_TYPE_COLOR: AnsiColor = AnsiColor::Cyan;
3536/// Highlight a Rust code string and write the highlighted
37/// output to the buffer. It serves as a wrapper around
38/// `Highlighter::highlight_rustc_lexer`. It is passed to
39/// `write_anstream_buf` in the `lib.rs` file.
40pub fn highlight(code: &str, buf: &mut Vec<u8>) -> io::Result<()> {
41let mut highlighter = Highlighter::default();
42highlighter.highlight_rustc_lexer(code, buf)
43}
4445/// A syntax highlighter for Rust code
46/// It is used by the `rustc --explain` command.
47#[derive(#[automatically_derived]
impl ::core::default::Default for Highlighter {
#[inline]
fn default() -> Highlighter {
Highlighter {
prev_was_special: ::core::default::Default::default(),
len_accum: ::core::default::Default::default(),
}
}
}Default)]
48pub struct Highlighter {
49/// Used to track if the previous token was a token
50 /// that warrants the next token to be colored differently
51 ///
52 /// For example, the keyword `fn` requires the next token
53 /// (the function name) to be colored differently.
54prev_was_special: bool,
55/// Used to track the length of tokens that have been
56 /// written so far. This is used to find the original
57 /// lexeme for a token from the code string.
58len_accum: usize,
59}
6061impl Highlighter {
62/// Create a new highlighter
63pub fn new() -> Self {
64Self::default()
65 }
6667/// Highlight a Rust code string and write the highlighted
68 /// output to the buffer.
69pub fn highlight_rustc_lexer(&mut self, code: &str, buf: &mut Vec<u8>) -> io::Result<()> {
70use rustc_lexer::TokenKind;
7172// Remove shebang from code string
73let stripped_idx = strip_shebang(code).unwrap_or(0);
74let stripped_code = &code[stripped_idx..];
75self.len_accum = stripped_idx;
76let len_accum = &mut self.len_accum;
77let tokens = tokenize(stripped_code, rustc_lexer::FrontmatterAllowed::No);
78for token in tokens {
79let len = token.len as usize;
80// If the previous token was a special token, and this token is
81 // not a whitespace token, then it should be colored differently
82let token_str = &code[*len_accum..*len_accum + len];
83if self.prev_was_special {
84if token_str != " " {
85self.prev_was_special = false;
86 }
87let style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Blue)));
88buf.write_fmt(format_args!("{0}{1}{0:#}", style, token_str))write!(buf, "{style}{token_str}{style:#}")?;
89*len_accum += len;
90continue;
91 }
92match token.kind {
93 TokenKind::Ident => {
94let mut style = Style::new();
95// Match if an identifier is a (well-known) keyword
96if KEYWORDS.contains(&token_str) {
97if token_str == "fn" {
98self.prev_was_special = true;
99 }
100 style = style.fg_color(Some(Color::Ansi(KEYWORD_COLOR)));
101 }
102// The `use` keyword is colored differently
103if #[allow(non_exhaustive_omitted_patterns)] match token_str {
"use" => true,
_ => false,
}matches!(token_str, "use") {
104 style = style.fg_color(Some(Color::Ansi(USE_COLOR)));
105 }
106// This heuristic test is to detect if the identifier is
107 // a function call. If it is, then the function identifier is
108 // colored differently.
109if code[*len_accum..*len_accum + len + 1].ends_with('(') {
110 style = style.fg_color(Some(Color::Ansi(FUNCTION_COLOR)));
111 }
112// The `derive` keyword is colored differently.
113if token_str == "derive" {
114 style = style.fg_color(Some(Color::Ansi(DERIVE_COLOR)));
115 }
116// This heuristic test is to detect if the identifier is
117 // a type. If it is, then the identifier is colored differently.
118if #[allow(non_exhaustive_omitted_patterns)] match token_str.chars().next().map(|c|
c.is_uppercase()) {
Some(true) => true,
_ => false,
}matches!(token_str.chars().next().map(|c| c.is_uppercase()), Some(true)) {
119 style = style.fg_color(Some(Color::Ansi(TYPE_COLOR)));
120 }
121// This if statement is to detect if the identifier is a primitive type.
122if PRIMITIVE_TYPES.contains(&token_str) {
123 style = style.fg_color(Some(Color::Ansi(PRIMITIVE_TYPE_COLOR)));
124 }
125buf.write_fmt(format_args!("{0}{1}{0:#}", style, token_str))write!(buf, "{style}{token_str}{style:#}")?;
126 }
127128// Color literals
129TokenKind::Literal { kind, suffix_start: _ } => {
130// Strings -> Green
131 // Chars -> Green
132 // Raw strings -> Green
133 // C strings -> Green
134 // Byte Strings -> Green
135 // Other literals -> Bright Red (Orage-esque)
136let style = match kind {
137 LiteralKind::Str { terminated: _ }
138 | LiteralKind::Char { terminated: _ }
139 | LiteralKind::RawStr { n_hashes: _ }
140 | LiteralKind::CStr { terminated: _ } => {
141 Style::new().fg_color(Some(Color::Ansi(STR_LITERAL_COLOR)))
142 }
143_ => Style::new().fg_color(Some(Color::Ansi(OTHER_LITERAL_COLOR))),
144 };
145buf.write_fmt(format_args!("{0}{1}{0:#}", style, token_str))write!(buf, "{style}{token_str}{style:#}")?;
146 }
147_ => {
148// All other tokens are dimmed
149let style = Style::new()
150 .fg_color(Some(Color::Ansi(AnsiColor::BrightWhite)))
151 .effects(Effects::DIMMED);
152buf.write_fmt(format_args!("{0}{1}{0:#}", style, token_str))write!(buf, "{style}{token_str}{style:#}")?;
153 }
154 }
155*len_accum += len;
156 }
157Ok(())
158 }
159}