diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Cargo.lock | 423 | ||||
-rw-r--r-- | Cargo.toml | 7 | ||||
-rw-r--r-- | src/main.rs | 213 |
4 files changed, 644 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..2209fa6 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,423 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cc" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cgit-syntect" +version = "0.1.0" +dependencies = [ + "syntect", +] + +[[package]] +name = "crc32fast" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "line-wrap" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd1bc4d24ad230d21fb898d1116b1801d7adfc449d42026475862ab48b11e70e" + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "onig" +version = "6.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" +dependencies = [ + "bitflags", + "libc", + "once_cell", + "onig_sys", +] + +[[package]] +name = "onig_sys" +version = "69.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "plist" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9d34169e64b3c7a80c8621a48adaf44e0cf62c78a9b25dd9dd35f1881a17cf9" +dependencies = [ + "base64", + "indexmap", + "line-wrap", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex-syntax" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "2.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syntect" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1" +dependencies = [ + "bincode", + "bitflags", + "flate2", + "fnv", + "once_cell", + "onig", + "plist", + "regex-syntax", + "serde", + "serde_derive", + "serde_json", + "thiserror", + "walkdir", + "yaml-rust", +] + +[[package]] +name = "thiserror" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..928f8ba --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "cgit-syntect" +version = "0.1.0" +edition = "2021" + +[dependencies] +syntect = { version = "5.2.0", default-features = false, features = ["parsing", "yaml-load", "regex-onig", "plist-load"] } 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>"); +} |