Skip to content

Commit

Permalink
Merge pull request trishume#296 from quasicomputational/css-class-nam…
Browse files Browse the repository at this point in the history
…espaced

html: allow a configurable prefix on CSS class names.
  • Loading branch information
trishume committed May 29, 2020
2 parents b2360ea + 1336aa0 commit f444f60
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 17 deletions.
12 changes: 6 additions & 6 deletions examples/synhtml-css-classes.rs
Expand Up @@ -10,8 +10,8 @@
//! You can open the html with a web browser and change between light and dark
//! mode.
use syntect::highlighting::ThemeSet;
use syntect::html::css_for_theme;
use syntect::html::ClassedHTMLGenerator;
use syntect::html::css_for_theme_with_class_style;
use syntect::html::{ClassedHTMLGenerator, ClassStyle};
use syntect::parsing::SyntaxSet;

use std::fs::File;
Expand Down Expand Up @@ -42,7 +42,7 @@ fn main() {
}";

let sr_rs = ss.find_syntax_by_extension("rs").unwrap();
let mut rs_html_generator = ClassedHTMLGenerator::new(&sr_rs, &ss);
let mut rs_html_generator = ClassedHTMLGenerator::new_with_class_style(&sr_rs, &ss, ClassStyle::Spaced);
for line in code_rs.lines() {
rs_html_generator.parse_html_for_line(&line);
}
Expand All @@ -60,7 +60,7 @@ int main() {
}";

let sr_cpp = ss.find_syntax_by_extension("cpp").unwrap();
let mut cpp_html_generator = ClassedHTMLGenerator::new(&sr_cpp, &ss);
let mut cpp_html_generator = ClassedHTMLGenerator::new_with_class_style(&sr_cpp, &ss, ClassStyle::Spaced);
for line in code_cpp.lines() {
cpp_html_generator.parse_html_for_line(&line);
}
Expand Down Expand Up @@ -104,15 +104,15 @@ int main() {
let css_dark_file = File::create(Path::new("theme-dark.css"))?;
let mut css_dark_writer = BufWriter::new(&css_dark_file);

let css_dark = css_for_theme(dark_theme);
let css_dark = css_for_theme_with_class_style(dark_theme, ClassStyle::Spaced);
writeln!(css_dark_writer, "{}", css_dark)?;

// create light color scheme css
let light_theme = &ts.themes["Solarized (light)"];
let css_light_file = File::create(Path::new("theme-light.css"))?;
let mut css_light_writer = BufWriter::new(&css_light_file);

let css_light = css_for_theme(light_theme);
let css_light = css_for_theme_with_class_style(light_theme, ClassStyle::Spaced);
writeln!(css_light_writer, "{}", css_light)?;

Ok(())
Expand Down
92 changes: 81 additions & 11 deletions src/html.rs
Expand Up @@ -20,7 +20,7 @@ use std::path::Path;
/// # Example
///
/// ```
/// use syntect::html::ClassedHTMLGenerator;
/// use syntect::html::{ClassedHTMLGenerator, ClassStyle};
/// use syntect::parsing::SyntaxSet;
///
/// let current_code = r#"
Expand All @@ -31,7 +31,7 @@ use std::path::Path;
///
/// let syntax_set = SyntaxSet::load_defaults_newlines();
/// let syntax = syntax_set.find_syntax_by_name("R").unwrap();
/// let mut html_generator = ClassedHTMLGenerator::new(&syntax, &syntax_set);
/// let mut html_generator = ClassedHTMLGenerator::new_with_class_style(&syntax, &syntax_set, ClassStyle::Spaced);
/// for line in current_code.lines() {
/// html_generator.parse_html_for_line(&line);
/// }
Expand All @@ -42,10 +42,16 @@ pub struct ClassedHTMLGenerator<'a> {
open_spans: isize,
parse_state: ParseState,
html: String,
style: ClassStyle,
}

impl<'a> ClassedHTMLGenerator<'a> {
#[deprecated(since="4.2.0", note="Please use `new_with_class_style` instead")]
pub fn new(syntax_reference: &'a SyntaxReference, syntax_set: &'a SyntaxSet) -> ClassedHTMLGenerator<'a> {
Self::new_with_class_style(syntax_reference, syntax_set, ClassStyle::Spaced)
}

pub fn new_with_class_style(syntax_reference: &'a SyntaxReference, syntax_set: &'a SyntaxSet, style: ClassStyle) -> ClassedHTMLGenerator<'a> {
let parse_state = ParseState::new(syntax_reference);
let open_spans = 0;
let html = String::new();
Expand All @@ -54,6 +60,7 @@ impl<'a> ClassedHTMLGenerator<'a> {
open_spans,
parse_state,
html,
style,
}
}

Expand All @@ -63,7 +70,8 @@ impl<'a> ClassedHTMLGenerator<'a> {
let (formatted_line, delta) = tokens_to_classed_spans(
line,
parsed_line.as_slice(),
ClassStyle::Spaced);
self.style,
);
self.open_spans += delta;
self.html.push_str(formatted_line.as_str());
// retain newline
Expand All @@ -79,17 +87,29 @@ impl<'a> ClassedHTMLGenerator<'a> {
}
}

#[deprecated(since="4.2.0", note="Please use `css_for_theme_with_class_style` instead.")]
pub fn css_for_theme(theme: &Theme) -> String {
css_for_theme_with_class_style(theme, ClassStyle::Spaced)
}

/// Create a complete CSS for a given theme. Can be used inline, or written to
/// a CSS file.
pub fn css_for_theme(theme: &Theme) -> String {
pub fn css_for_theme_with_class_style(theme: &Theme, style: ClassStyle) -> String {
let mut css = String::new();

css.push_str("/*\n");
let name = theme.name.clone().unwrap_or("unknown theme".to_string());
css.push_str(&format!(" * theme \"{}\" generated by syntect\n", name));
css.push_str(" */\n\n");

css.push_str(".code {\n");
match style {
ClassStyle::Spaced => {
css.push_str(".code {\n");
},
ClassStyle::SpacedPrefixed { prefix } => {
css.push_str(&format!(".{}code {{\n", prefix));
},
};
if let Some(fgc) = theme.settings.foreground {
css.push_str(&format!(" color: #{:02x}{:02x}{:02x};\n", fgc.r, fgc.g, fgc.b));
}
Expand All @@ -103,7 +123,8 @@ pub fn css_for_theme(theme: &Theme) -> String {
for scope_selector in &i.scope.selectors {
let scopes = scope_selector.extract_scopes();
for k in &scopes {
css.push_str(&format!(".{} {{\n", k));
scope_to_selector(&mut css, *k, style);
css.push_str(" {\n");

if let Some(fg) = i.style.foreground {
css.push_str(&format!(" color: #{:02x}{:02x}{:02x};\n", fg.r, fg.g, fg.b));
Expand Down Expand Up @@ -132,26 +153,62 @@ pub fn css_for_theme(theme: &Theme) -> String {
css
}

/// Only one style for now, I may add more class styles later.
/// Just here so I don't have to change the API
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[non_exhaustive]
pub enum ClassStyle {
/// The classes are the atoms of the scope separated by spaces
/// (e.g `source.php` becomes `source php`).
/// This isn't that fast since it has to use the scope repository
/// to look up scope names.
Spaced,
/// Like `Spaced`, but the given prefix will be prepended to all
/// classes. This is useful to prevent class name collisions, and
/// can ensure that the theme's CSS applies precisely to syntect's
/// output.
///
/// The prefix must be a valid CSS class name. To help ennforce
/// this invariant and prevent accidental foot-shooting, it must
/// be statically known. (If this requirement is onerous, please
/// file an issue; the HTML generator can also be forked
/// separately from the rest of syntect, as it only uses the
/// public API.)
SpacedPrefixed {
prefix: &'static str,
},
}

fn scope_to_classes(s: &mut String, scope: Scope, style: ClassStyle) {
assert!(style == ClassStyle::Spaced); // TODO more styles
let repo = SCOPE_REPO.lock().unwrap();
for i in 0..(scope.len()) {
let atom = scope.atom_at(i as usize);
let atom_s = repo.atom_str(atom);
if i != 0 {
s.push_str(" ")
}
match style {
ClassStyle::Spaced => {
},
ClassStyle::SpacedPrefixed { prefix } => {
s.push_str(&prefix);
},
}
s.push_str(atom_s);
}
}

fn scope_to_selector(s: &mut String, scope: Scope, style: ClassStyle) {
let repo = SCOPE_REPO.lock().unwrap();
for i in 0..(scope.len()) {
let atom = scope.atom_at(i as usize);
let atom_s = repo.atom_str(atom);
s.push_str(".");
match style {
ClassStyle::Spaced => {
},
ClassStyle::SpacedPrefixed { prefix } => {
s.push_str(&prefix);
},
}
s.push_str(atom_s);
}
}
Expand Down Expand Up @@ -459,14 +516,27 @@ mod tests {
let current_code = "x + y".to_string();
let syntax_set = SyntaxSet::load_defaults_newlines();
let syntax = syntax_set.find_syntax_by_name("R").unwrap();
let mut html_generator = ClassedHTMLGenerator::new(&syntax, &syntax_set);
let mut html_generator = ClassedHTMLGenerator::new_with_class_style(&syntax, &syntax_set, ClassStyle::Spaced);
for line in current_code.lines() {
html_generator.parse_html_for_line(&line);
}
let html = html_generator.finalize();
assert_eq!(html, "<span class=\"source r\">x <span class=\"keyword operator arithmetic r\">+</span> y\n</span>");
}

#[test]
fn test_classed_html_generator_prefixed() {
let current_code = "x + y".to_string();
let syntax_set = SyntaxSet::load_defaults_newlines();
let syntax = syntax_set.find_syntax_by_name("R").unwrap();
let mut html_generator = ClassedHTMLGenerator::new_with_class_style(&syntax, &syntax_set, ClassStyle::SpacedPrefixed { prefix: "foo-" });
for line in current_code.lines() {
html_generator.parse_html_for_line(&line);
}
let html = html_generator.finalize();
assert_eq!(html, "<span class=\"foo-source foo-r\">x <span class=\"foo-keyword foo-operator foo-arithmetic foo-r\">+</span> y\n</span>");
}

#[test]
fn test_classed_html_generator_no_empty_span() {
let code = "// Rust source
Expand All @@ -475,7 +545,7 @@ fn main() {
}";
let syntax_set = SyntaxSet::load_defaults_newlines();
let syntax = syntax_set.find_syntax_by_extension("rs").unwrap();
let mut html_generator = ClassedHTMLGenerator::new(&syntax, &syntax_set);
let mut html_generator = ClassedHTMLGenerator::new_with_class_style(&syntax, &syntax_set, ClassStyle::Spaced);
for line in code.lines() {
html_generator.parse_html_for_line(&line);
}
Expand Down

0 comments on commit f444f60

Please sign in to comment.