diff options
Diffstat (limited to 'src/main.rs')
-rw-r--r-- | src/main.rs | 213 |
1 files changed, 213 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..74db5cd --- /dev/null +++ b/src/main.rs @@ -0,0 +1,213 @@ +use std::{path::PathBuf, str::FromStr}; + +use syntect::{ + dumps::{dump_to_uncompressed_file, from_uncompressed_dump_file}, + highlighting::{Color, FontStyle, ScopeSelectors, ThemeSet}, + parsing::{ParseState, ScopeStack, SyntaxSet, SyntaxSetBuilder}, +}; + +fn main() { + match std::env::var("CGIT_SYNTECT_MODE").as_deref().ok() { + Some("compile") => compile(), + Some("theme") => theme(), + None | Some("") | Some("highlight") => highlight(), + _ => panic!("unknown mode"), + } +} + +fn compile() { + let mut args = std::env::args(); + args.next(); // arg0 + let input: PathBuf = args.next().unwrap().into(); + let output: PathBuf = args.next().unwrap().into(); + let mut builder = SyntaxSetBuilder::new(); + builder.add_plain_text_syntax(); + builder.add_from_folder(input, true).unwrap(); + let set = builder.build(); + dump_to_uncompressed_file(&set, output).unwrap(); +} + +fn theme() { + let mut args = std::env::args(); + args.next(); // arg0 + let input: PathBuf = args.next().unwrap().into(); + let css_path: PathBuf = args.next().unwrap().into(); + let scopes_path: PathBuf = args.next().unwrap().into(); + + let theme = ThemeSet::get_theme(input).unwrap(); + let mut css_gen = Vec::new(); + let mut scopes_gen = Vec::new(); + + let mut global_css = String::new(); + global_css.push_str(".highlight {\n"); + if let Some(bg) = theme.settings.background { + global_css.push_str(" background-color: #"); + global_css.extend(hex_color(bg)); + global_css.push_str(";\n"); + } + global_css.push_str("}\n"); + css_gen.push(global_css); + + for (idx, item) in theme.scopes.into_iter().enumerate() { + let selectors_str = selectors_to_string(item.scope); + + let mut css = String::new(); + + css.push_str(&format!("/* {selectors_str} */\n")); + css.push_str(&format!(".hl-style{idx} {{\n")); + + if let Some(fg) = item.style.foreground { + css.push_str(" color: #"); + css.extend(hex_color(fg)); + css.push_str(";\n"); + } + + if let Some(bg) = item.style.background { + css.push_str(" background-color: #"); + css.extend(hex_color(bg)); + css.push_str(";\n"); + } + + if let Some(font) = item.style.font_style { + if font.contains(FontStyle::BOLD) { + css.push_str(" font-weight: bold;\n"); + } + if font.contains(FontStyle::UNDERLINE) { + css.push_str(" text-decoration: underline;\n"); + } + if font.contains(FontStyle::ITALIC) { + css.push_str(" font-style: italic;\n"); + } + } + + css.push_str("}\n"); + css_gen.push(css); + + scopes_gen.push(selectors_str); + } + + std::fs::write(css_path, css_gen.join("\n")).unwrap(); + std::fs::write(scopes_path, scopes_gen.join("\n")).unwrap(); +} + +fn selectors_to_string(selectors: ScopeSelectors) -> String { + let mut scopes = Vec::new(); + for scope in selectors.selectors { + let mut ser = String::new(); + ser.push_str(scope.path.to_string().trim()); + for exc in scope.excludes { + ser.push_str(" -"); + ser.push_str(exc.to_string().trim()); + } + scopes.push(ser); + } + scopes.join(", ") +} + +fn hex_color(color: Color) -> [char; 8] { + const HEX_DIGIT_LOOKUP: &[u8; 16] = b"0123456789abcdef"; + [ + HEX_DIGIT_LOOKUP[(color.r >> 4) as usize] as char, + HEX_DIGIT_LOOKUP[(color.r & 0xf) as usize] as char, + HEX_DIGIT_LOOKUP[(color.g >> 4) as usize] as char, + HEX_DIGIT_LOOKUP[(color.g & 0xf) as usize] as char, + HEX_DIGIT_LOOKUP[(color.b >> 4) as usize] as char, + HEX_DIGIT_LOOKUP[(color.b & 0xf) as usize] as char, + HEX_DIGIT_LOOKUP[(color.a >> 4) as usize] as char, + HEX_DIGIT_LOOKUP[(color.a & 0xf) as usize] as char, + ] +} + +fn highlight() { + let mut args = std::env::args(); + args.next(); // arg0 + let highlight_file: PathBuf = args.next().unwrap().into(); + + let syntax_dump = std::env::var("CGIT_SYNTECT_SYNTAXES") + .map(PathBuf::from) + .unwrap_or_else(|_| PathBuf::from("/usr/share/cgit-syntect/syntax.packdump")); + let set: SyntaxSet = from_uncompressed_dump_file(syntax_dump).unwrap(); + + let scope_dump = std::env::var("CGIT_SYNTECT_SCOPES") + .map(PathBuf::from) + .unwrap_or_else(|_| PathBuf::from("/usr/share/cgit-syntect/scopes")); + let scopes = std::fs::read_to_string(scope_dump) + .unwrap() + .lines() + .map(ScopeSelectors::from_str) + .map(|sels| sels.unwrap()) + .collect::<Vec<_>>(); + + let mut line = String::new(); + std::io::stdin().read_line(&mut line).unwrap(); + if !line.ends_with('\n') { + line.push('\n'); + } + + let file_name = highlight_file + .file_name() + .and_then(|n| n.to_str()) + .unwrap_or(""); + let extension = highlight_file + .extension() + .and_then(|x| x.to_str()) + .unwrap_or(""); + let syntax = set + .find_syntax_by_extension(file_name) + .or_else(|| set.find_syntax_by_extension(extension)) + .or_else(|| set.find_syntax_by_first_line(&line)) + .unwrap_or_else(|| set.find_syntax_plain_text()); + + print!(r#"<pre class="highlight"><code>"#); + + let mut parse = ParseState::new(syntax); + let mut stack = ScopeStack::new(); + loop { + let mut cursor = 0; + let ops = parse.parse_line(&line, &set).unwrap(); + let mut spans = Vec::new(); + + for (at, op) in ops { + if at != cursor { + let mut max_power = f64::NEG_INFINITY; + let mut selected = None; + for (idx, scope) in scopes.iter().enumerate() { + if let Some(power) = scope.does_match(stack.as_slice()) { + if power.0 > max_power { + max_power = power.0; + selected = Some(idx); + } + } + } + spans.push((&line[cursor..at], selected)); + } + stack.apply(&op).unwrap(); + cursor = at; + } + + if line.len() != cursor { + spans.push((&line[cursor..], None)); + } + + for (section, class) in spans { + if let Some(idx) = class { + print!(r#"<span class="hl-style{idx}">"#) + } + print!("{section}"); + if class.is_some() { + print!("</span>"); + } + } + + line.clear(); + if std::io::stdin().read_line(&mut line).unwrap() == 0 { + println!(); + break; + } + if !line.ends_with('\n') { + line.push('\n'); + } + } + + print!("</code></pre>"); +} |