Skip to content

Instantly share code, notes, and snippets.

@rauschma
Last active April 14, 2023 02:00
Show Gist options
  • Save rauschma/f36aabe3ac21e8a54a9a54e872d29198 to your computer and use it in GitHub Desktop.
Save rauschma/f36aabe3ac21e8a54a9a54e872d29198 to your computer and use it in GitHub Desktop.
use fancy_regex::{Captures, Regex};
use itertools::Itertools;
use std::{borrow::Cow, collections::HashMap};
pub type EscapingFn = Box<dyn Sync + 'static + Fn(&str) -> Cow<str>>;
pub fn new_escaping_fn<const N: usize>(
char_to_escaped: [(&'static str, &'static str); N],
) -> EscapingFn {
let map = HashMap::from(char_to_escaped);
let re_chars = Regex::new(&map.keys().map(|k| fancy_regex::escape(k)).join("|")).unwrap();
Box::new(move |str: &str| {
re_chars.replace_all(str, |cap: &Captures| {
let matched_str = cap.get(0).unwrap().as_str();
map.get(matched_str).unwrap()
})
})
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
lazy_static! {
static ref ESCAPE_HTML: EscapingFn = new_escaping_fn([
("\"", "&quot;"),
("'", "&apos;"),
("<", "&lt;"),
(">", "&gt;"),
("&", "&amp;"),
("`", "&#96;"),
]);
}
#[test]
fn test_escaping() {
assert_eq!(
ESCAPE_HTML("<hello & goodbye>"),
"&lt;hello &amp; goodbye&gt;"
);
}
}
// Illustrating the bugs of the *current* implementation
use syntect::{
easy::HighlightLines,
highlighting::{Style, ThemeSet},
html::{styled_line_to_highlighted_html, IncludeBackground},
parsing::SyntaxSet,
util::as_latex_escaped,
};
pub const THEME_NAME: &str = "Solarized (light)";
#[test]
fn test_escaping_backslashes() {
let syntax_set = SyntaxSet::load_defaults_newlines();
let theme_set = ThemeSet::load_defaults();
let syntax = syntax_set.find_syntax_by_extension("js").unwrap();
let mut h = HighlightLines::new(syntax, &theme_set.themes[THEME_NAME]);
let line = format!("/\\n/\n");
let ranges: Vec<(Style, &str)> = h.highlight_line(&line, &syntax_set).unwrap();
assert_eq!(
as_latex_escaped(&ranges),
r"\textcolor[RGB]{131,150,148}{/}\textcolor[RGB]{220,47,50}{\\n}\textcolor[RGB]{131,150,148}{/}"
);
}
#[test]
fn test_colors_in_latex() {
let syntax_set = SyntaxSet::load_defaults_newlines();
let theme_set = ThemeSet::load_defaults();
let syntax = syntax_set.find_syntax_by_extension("js").unwrap();
let mut h = HighlightLines::new(syntax, &theme_set.themes[THEME_NAME]);
let line = format!("function\n");
let ranges: Vec<(Style, &str)> = h.highlight_line(&line, &syntax_set).unwrap();
// Color is OK
assert_eq!(
styled_line_to_highlighted_html(&ranges, IncludeBackground::No).unwrap(),
"<span style=\"color:#268bd2;\">function\n</span>"
);
// Color is wrong: it should be {38,139,210}
assert_eq!(
as_latex_escaped(&ranges),
r"\textcolor[RGB]{38,210,139}{function}"
);
assert_eq!(format!("{:x}{:x}{:x}", 38, 210, 139), "26d28b");
}
use crate::util::escaping_fn::{new_escaping_fn, EscapingFn};
use fancy_regex::Regex;
use std::fmt::Write;
use syntect::highlighting::Style;
lazy_static! {
pub static ref ESCAPE_VERBATIM: EscapingFn =
new_escaping_fn([(r"\", r"\textbackslash{}"), (r"{", r"\{"), (r"}", r"\}"),]);
}
pub fn as_latex_escaped(v: &[(Style, &str)]) -> String {
let mut s: String = String::new();
let mut prev_style: Option<Style> = None;
fn textcolor(style: &Style, first: bool) -> String {
format!(
"{}\\textcolor[RGB]{{{},{},{}}}{{",
if first { "" } else { "}" },
style.foreground.r,
style.foreground.g,
style.foreground.b,
)
}
for &(style, text) in v.iter() {
if let Some(ps) = prev_style {
match text {
" " => {
s.push(' ');
continue;
}
"\n" => continue,
_ => (),
}
if style != ps {
write!(s, "{}", textcolor(&style, false)).unwrap();
}
} else {
write!(s, "{}", textcolor(&style, true)).unwrap();
}
write!(s, "{}", ESCAPE_VERBATIM(text)).unwrap();
prev_style = Some(style);
}
s.push('}');
s
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment