From a30291d17c73f6cf67241392654a36b775d25107 Mon Sep 17 00:00:00 2001 From: mia Date: Sun, 7 Apr 2024 04:01:19 -0700 Subject: the first attempt --- src/main.rs | 213 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 src/main.rs (limited to 'src/main.rs') 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::>(); + + 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#"
"#);
+
+    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#""#)
+            }
+            print!("{section}");
+            if class.is_some() {
+                print!("");
+            }
+        }
+
+        line.clear();
+        if std::io::stdin().read_line(&mut line).unwrap() == 0 {
+            println!();
+            break;
+        }
+        if !line.ends_with('\n') {
+            line.push('\n');
+        }
+    }
+
+    print!("
"); +} -- cgit 1.4.1