home
NOTE: Under Construction - I'm in the middle of upgrading my site and lots of stuff is kinda broken. Please forgive the mess.

Syntax Highlighting With Line Numbering And Classes In Rust

January 2024

This is how I'm doing syntax highlighting in Rust.

This is the main way I'm doing it where instead of adding the styles inline it adds classes.

[] Uses classes instead of inline styles

[] Trim leading empty lines, but not white space before the first character on the first line

[] Trim trailing empty lines

[] Provide HTML that can have line numbers added via CSS

[] Fall back to plain-text if the requested language isn't found

[] link to: 2fbld7l3 for generating stylesheets

Code
```cargo
[dependencies]
regex = "1.10.4"
syntect = "5.2.0"
```

use regex::Regex;
use syntect::html::{ClassedHTMLGenerator, ClassStyle};
use syntect::parsing::SyntaxSet;
use syntect::util::LinesWithEndings;

fn main() {
  let code = r#"


   fn main() {
  println!(`Hello, World
    alfa
    bravo
  `)
}
"#;

  let lang = "rust";
  let output = highlight_code(code, lang);
  println!("{}", output)
}

fn highlight_code(code: &str, lang: &str) -> String {
  let syntax_set = SyntaxSet::load_defaults_newlines();
  let syntax = syntax_set.find_syntax_by_token(&lang).unwrap_or_else(|| syntax_set.find_syntax_plain_text());
  let mut html_generator = ClassedHTMLGenerator::new_with_class_style(syntax, &syntax_set, ClassStyle::Spaced);
  for line in LinesWithEndings::from(&trim_empty_lines(code)) {
      let _ = html_generator.parse_html_for_line_which_includes_newline(line);
  }
  let initial_html = html_generator.finalize();
  let output_html: Vec<_> = initial_html.lines()
    .map(|line| 
        format!(r#"<span class="line-marker"></span>{}"#, line))
    .collect();
    output_html.join("\n")
}

fn trim_empty_lines(source: &str) -> String {
  let re = Regex::new(r"\S").unwrap();
  let trimmed_front = source.split("\n")
    .fold(
      "".to_string(), |acc, l|
      {
        if !acc.is_empty() {
          acc + l + "\n"
        } else  {
          if re.is_match(l) {
            l.to_string() + "\n"
          } else {
            acc
          }
        }
      });
  trimmed_front.trim_end().to_string()
}
Results
   fn main() {
  println!(`Hello, World
    alfa
    bravo
  `)
}

   fn main() {
  println!(`Hello, World
    alfa
    bravo
  `)
}
═══ § ═══

Footnotes And References

  • See this for how to output the styelsheet: