🗝
summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.rs213
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>");
+}