1use crate::lua::async_io::{AsyncIOTask, runtime};
7use mlua::{Lua, Result, Table};
8use once_cell::sync::Lazy;
9use syntect::highlighting::ThemeSet;
10use syntect::html::{ClassStyle, ClassedHTMLGenerator};
11use syntect::parsing::SyntaxSet;
12
13static SYNTAX_SET: Lazy<SyntaxSet> = Lazy::new(SyntaxSet::load_defaults_newlines);
15static THEME_SET: Lazy<ThemeSet> = Lazy::new(ThemeSet::load_defaults);
16
17fn get_syntax_name(lang: &str) -> &str {
19 match lang.to_lowercase().as_str() {
20 "py" | "python" | "python3" => "Python",
21 "rs" | "rust" => "Rust",
22 "js" | "javascript" => "JavaScript",
23 "ts" | "typescript" => "TypeScript",
24 "c" => "C",
25 "cpp" | "c++" | "cxx" => "C++",
26 "h" | "hpp" => "C++",
27 "hs" | "haskell" => "Haskell",
28 "rb" | "ruby" => "Ruby",
29 "go" | "golang" => "Go",
30 "java" => "Java",
31 "sh" | "bash" | "shell" => "Bourne Again Shell (bash)",
32 "zsh" => "Bourne Again Shell (bash)",
33 "json" => "JSON",
34 "yaml" | "yml" => "YAML",
35 "toml" => "TOML",
36 "xml" => "XML",
37 "html" | "htm" => "HTML",
38 "css" => "CSS",
39 "sql" => "SQL",
40 "md" | "markdown" => "Markdown",
41 "lua" => "Lua",
42 "vim" => "VimL",
43 "dockerfile" => "Dockerfile",
44 "make" | "makefile" => "Makefile",
45 "tex" | "latex" => "LaTeX",
46 "r" => "R",
47 "scala" => "Scala",
48 "kotlin" | "kt" => "Kotlin",
49 "swift" => "Swift",
50 "objc" | "objective-c" => "Objective-C",
51 "php" => "PHP",
52 "perl" | "pl" => "Perl",
53 "elixir" | "ex" => "Elixir",
54 "erlang" | "erl" => "Erlang",
55 "clojure" | "clj" => "Clojure",
56 "lisp" | "el" => "Lisp",
57 "scheme" | "scm" => "Lisp",
58 "ocaml" | "ml" => "OCaml",
59 "fsharp" | "fs" => "F#",
60 "csharp" | "cs" => "C#",
61 "diff" | "patch" => "Diff",
62 "ini" | "conf" => "INI",
63 "nginx" => "nginx",
64 "asm" | "assembly" => "Assembly (x86_64)",
65 _ => lang,
66 }
67}
68
69pub fn highlight_code_sync(code: &str, language: &str) -> String {
72 let syntax_name = get_syntax_name(language);
73
74 let syntax = SYNTAX_SET
76 .find_syntax_by_name(syntax_name)
77 .or_else(|| SYNTAX_SET.find_syntax_by_extension(language))
78 .or_else(|| SYNTAX_SET.find_syntax_by_token(language))
79 .unwrap_or_else(|| SYNTAX_SET.find_syntax_plain_text());
80
81 let mut html_generator =
82 ClassedHTMLGenerator::new_with_class_style(syntax, &SYNTAX_SET, ClassStyle::Spaced);
83
84 for line in syntect::util::LinesWithEndings::from(code) {
85 let _ = html_generator.parse_html_for_line_which_includes_newline(line);
87 }
88
89 html_generator.finalize()
90}
91
92fn list_syntaxes() -> Vec<String> {
94 SYNTAX_SET
95 .syntaxes()
96 .iter()
97 .map(|s| s.name.clone())
98 .collect()
99}
100
101fn list_themes() -> Vec<String> {
103 THEME_SET.themes.keys().cloned().collect()
104}
105
106fn generate_theme_css(theme_name: &str) -> Option<String> {
108 let theme = THEME_SET.themes.get(theme_name)?;
109 syntect::html::css_for_theme_with_class_style(theme, ClassStyle::Spaced).ok()
110}
111
112pub fn create_module(lua: &Lua) -> Result<Table> {
114 let module = lua.create_table()?;
115
116 let highlight_fn = lua.create_function(|_, (code, language): (String, String)| {
118 let handle = runtime().spawn(async move {
119 let result = tokio::task::spawn_blocking(move || highlight_code_sync(&code, &language))
121 .await
122 .map_err(|e| format!("Highlight task panicked: {}", e))?;
123 Ok(result)
124 });
125
126 Ok(AsyncIOTask::from_string_handle(handle))
127 })?;
128 module.set("highlight", highlight_fn)?;
129
130 module.set("code", module.get::<mlua::Function>("highlight")?)?;
132
133 let highlight_sync_fn = lua.create_function(|_, (code, language): (String, String)| {
135 Ok(highlight_code_sync(&code, &language))
136 })?;
137 module.set("highlight_sync", highlight_sync_fn)?;
138
139 let syntaxes_fn = lua.create_function(|_, ()| Ok(list_syntaxes()))?;
141 module.set("syntaxes", syntaxes_fn)?;
142
143 let themes_fn = lua.create_function(|_, ()| Ok(list_themes()))?;
145 module.set("themes", themes_fn)?;
146
147 let theme_css_fn =
149 lua.create_function(|_, theme_name: String| Ok(generate_theme_css(&theme_name)))?;
150 module.set("theme_css", theme_css_fn)?;
151
152 Ok(module)
153}