use anstyle::{AnsiColor, Color, Style};
use synoptic::{TokOpt, from_extension};
#[cfg(feature = "syntax-highlighting")]
pub(super) fn bash_token_style(kind: &str) -> Option<Style> {
match kind {
"function" => Some(
Style::new()
.fg_color(Some(Color::Ansi(AnsiColor::Blue)))
.dimmed(),
),
"keyword" => Some(
Style::new()
.fg_color(Some(Color::Ansi(AnsiColor::Magenta)))
.dimmed(),
),
"string" => Some(
Style::new()
.fg_color(Some(Color::Ansi(AnsiColor::Green)))
.dimmed(),
),
"operator" => Some(
Style::new()
.fg_color(Some(Color::Ansi(AnsiColor::Cyan)))
.dimmed(),
),
"property" => Some(
Style::new()
.fg_color(Some(Color::Ansi(AnsiColor::Yellow)))
.dimmed(),
),
"number" => Some(
Style::new()
.fg_color(Some(Color::Ansi(AnsiColor::Yellow)))
.dimmed(),
),
"constant" => Some(
Style::new()
.fg_color(Some(Color::Ansi(AnsiColor::Cyan)))
.dimmed(),
),
_ => None,
}
}
pub fn format_toml(content: &str) -> String {
let mut highlighter = from_extension("toml", 4).expect("synoptic supports TOML");
let gutter = super::GUTTER;
let dim = Style::new().dimmed();
let lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
highlighter.run(&lines);
let output_lines: Vec<String> = lines
.iter()
.enumerate()
.map(|(y, line)| {
let mut line_output = format!("{gutter} {gutter:#} ");
for token in highlighter.line(y, line) {
let (text, style) = match token {
TokOpt::Some(text, kind) => (text, toml_token_style(&kind)),
TokOpt::None(text) => (text, None),
};
if let Some(s) = style {
line_output.push_str(&format!("{s}{text}{s:#}"));
} else {
line_output.push_str(&format!("{dim}{text}{dim:#}"));
}
}
line_output
})
.collect();
output_lines.join("\n")
}
fn toml_token_style(kind: &str) -> Option<Style> {
match kind {
"string" => Some(
Style::new()
.fg_color(Some(Color::Ansi(AnsiColor::Green)))
.dimmed(),
),
"comment" => Some(Style::new().dimmed()),
"table" => Some(
Style::new()
.fg_color(Some(Color::Ansi(AnsiColor::Cyan)))
.dimmed(),
),
"boolean" | "digit" => Some(
Style::new()
.fg_color(Some(Color::Ansi(AnsiColor::Yellow)))
.dimmed(),
),
_ => None,
}
}
#[cfg(test)]
mod tests {
use insta::assert_snapshot;
use super::*;
#[test]
#[cfg(feature = "syntax-highlighting")]
fn test_bash_token_styles() {
let cases = [
("function", AnsiColor::Blue),
("keyword", AnsiColor::Magenta),
("string", AnsiColor::Green),
("operator", AnsiColor::Cyan),
("constant", AnsiColor::Cyan),
("property", AnsiColor::Yellow),
("number", AnsiColor::Yellow),
];
for (name, expected_color) in cases {
let style =
bash_token_style(name).unwrap_or_else(|| panic!("{name} should have a style"));
assert_eq!(
style.get_fg_color(),
Some(Color::Ansi(expected_color)),
"{name} should be {expected_color:?}"
);
}
assert!(bash_token_style("unknown").is_none());
assert!(bash_token_style("comment").is_none());
assert!(bash_token_style("embedded").is_none());
}
#[test]
fn test_toml_token_styles() {
let cases = [
("string", AnsiColor::Green),
("table", AnsiColor::Cyan),
("boolean", AnsiColor::Yellow),
("digit", AnsiColor::Yellow),
];
for (name, expected_color) in cases {
let style =
toml_token_style(name).unwrap_or_else(|| panic!("{name} should have a style"));
assert_eq!(
style.get_fg_color(),
Some(Color::Ansi(expected_color)),
"{name} should be {expected_color:?}"
);
}
assert!(toml_token_style("comment").is_some());
assert!(toml_token_style("unknown").is_none());
assert!(toml_token_style("key").is_none());
assert!(toml_token_style("operator").is_none());
}
#[test]
fn test_format_toml() {
assert_snapshot!(format_toml("[section]\nkey = \"value\""), @r#"
[107m [0m [2m[36m[section][0m
[107m [0m [2mkey = [0m[2m[32m"value"[0m
"#);
assert_snapshot!(
format_toml("[table]\nkey1 = \"value1\"\nkey2 = 42\n# comment\nkey3 = false"),
@r#"
[107m [0m [2m[36m[table][0m
[107m [0m [2mkey1 = [0m[2m[32m"value1"[0m
[107m [0m [2mkey2 = [0m[2m[33m42[0m
[107m [0m [2m# comment[0m
[107m [0m [2mkey3 = [0m[2m[33mfalse[0m
"#
);
assert_snapshot!(format_toml(""), @"");
}
#[test]
fn test_format_toml_has_styled_and_unstyled_text() {
use synoptic::{TokOpt, from_extension};
let content = "key = \"value\"";
let mut highlighter = from_extension("toml", 4).expect("synoptic supports TOML");
let lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
highlighter.run(&lines);
let mut has_styled = false;
let mut has_unstyled = false;
for (y, line) in lines.iter().enumerate() {
for token in highlighter.line(y, line) {
match token {
TokOpt::Some(_, kind) => {
if toml_token_style(&kind).is_some() {
has_styled = true;
}
}
TokOpt::None(_) => {
has_unstyled = true;
}
}
}
}
assert!(has_styled, "Should have at least one styled token");
assert!(
has_unstyled,
"Should have at least one unstyled text segment"
);
}
}