From 054fa1636a5c83411fbcf3d677aeff90a2a6f4a9 Mon Sep 17 00:00:00 2001 From: Matthias Vogelgesang Date: Sat, 4 Feb 2023 18:15:49 +0100 Subject: [PATCH] Use tree-painter for syntax highlighting --- .gitmodules | 3 + Cargo.lock | 242 +++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/errors.rs | 7 +- src/highlight.rs | 83 ++++++++------- src/pages.rs | 5 +- src/themes/style.css | 23 +--- templates/index.html | 10 +- tree-painter | 1 + 9 files changed, 300 insertions(+), 75 deletions(-) create mode 100644 .gitmodules create mode 160000 tree-painter diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..ff9d030 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "tree-painter"] + path = tree-painter + url = https://github.com/matze/tree-painter diff --git a/Cargo.lock b/Cargo.lock index ad10da0..43b5c7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1467,6 +1467,18 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772c1426ab886e7362aedf4abc9c0d1348a979517efedfc25862944d10137af0" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + [[package]] name = "toml_datetime" version = "0.6.1" @@ -1599,6 +1611,235 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "tree-painter" +version = "0.0.0" +dependencies = [ + "thiserror", + "toml", + "tree-sitter-c", + "tree-sitter-c-sharp", + "tree-sitter-cpp", + "tree-sitter-css", + "tree-sitter-dockerfile", + "tree-sitter-go", + "tree-sitter-haskell", + "tree-sitter-highlight", + "tree-sitter-java", + "tree-sitter-javascript", + "tree-sitter-json", + "tree-sitter-julia", + "tree-sitter-kotlin", + "tree-sitter-latex", + "tree-sitter-lua", + "tree-sitter-md", + "tree-sitter-nix", + "tree-sitter-ocaml", + "tree-sitter-python", + "tree-sitter-rust", + "tree-sitter-typescript", + "tree-sitter-zig", +] + +[[package]] +name = "tree-sitter" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad726ec26496bf4c083fff0f43d4eb3a2ad1bba305323af5ff91383c0b6ecac0" +dependencies = [ + "cc", + "regex", +] + +[[package]] +name = "tree-sitter" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4423c784fe11398ca91e505cdc71356b07b1a924fc8735cfab5333afe3e18bc" +dependencies = [ + "cc", + "regex", +] + +[[package]] +name = "tree-sitter-c" +version = "0.20.2" +dependencies = [ + "cc", + "tree-sitter 0.20.9", +] + +[[package]] +name = "tree-sitter-c-sharp" +version = "0.20.0" +dependencies = [ + "cc", + "tree-sitter 0.20.9", +] + +[[package]] +name = "tree-sitter-cpp" +version = "0.20.0" +dependencies = [ + "cc", + "tree-sitter 0.20.9", +] + +[[package]] +name = "tree-sitter-css" +version = "0.19.0" +dependencies = [ + "cc", + "tree-sitter 0.20.9", +] + +[[package]] +name = "tree-sitter-dockerfile" +version = "0.1.0" +dependencies = [ + "cc", + "tree-sitter 0.20.9", +] + +[[package]] +name = "tree-sitter-go" +version = "0.19.1" +dependencies = [ + "cc", + "tree-sitter 0.20.9", +] + +[[package]] +name = "tree-sitter-haskell" +version = "0.14.0" +dependencies = [ + "cc", + "tree-sitter 0.20.9", +] + +[[package]] +name = "tree-sitter-highlight" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "042342584c5a7a0b833d9fc4e2bdab3f9868ddc6c4b339a1e01451c6720868bc" +dependencies = [ + "regex", + "thiserror", + "tree-sitter 0.20.9", +] + +[[package]] +name = "tree-sitter-java" +version = "0.20.0" +dependencies = [ + "cc", + "tree-sitter 0.20.9", +] + +[[package]] +name = "tree-sitter-javascript" +version = "0.20.0" +dependencies = [ + "cc", + "tree-sitter 0.20.9", +] + +[[package]] +name = "tree-sitter-json" +version = "0.20.0" +dependencies = [ + "cc", + "tree-sitter 0.20.9", +] + +[[package]] +name = "tree-sitter-julia" +version = "0.19.0" +dependencies = [ + "cc", + "tree-sitter 0.19.5", +] + +[[package]] +name = "tree-sitter-kotlin" +version = "0.2.11" +dependencies = [ + "cc", + "tree-sitter 0.20.9", +] + +[[package]] +name = "tree-sitter-latex" +version = "0.3.0" +dependencies = [ + "cc", + "tree-sitter 0.20.9", +] + +[[package]] +name = "tree-sitter-lua" +version = "0.0.14" +dependencies = [ + "cc", + "tree-sitter 0.20.9", +] + +[[package]] +name = "tree-sitter-md" +version = "0.1.2" +dependencies = [ + "cc", + "tree-sitter 0.20.9", +] + +[[package]] +name = "tree-sitter-nix" +version = "0.0.1" +dependencies = [ + "cc", + "tree-sitter 0.20.9", +] + +[[package]] +name = "tree-sitter-ocaml" +version = "0.20.1" +dependencies = [ + "cc", + "tree-sitter 0.20.9", +] + +[[package]] +name = "tree-sitter-python" +version = "0.20.2" +dependencies = [ + "cc", + "tree-sitter 0.20.9", +] + +[[package]] +name = "tree-sitter-rust" +version = "0.20.3" +dependencies = [ + "cc", + "tree-sitter 0.20.9", +] + +[[package]] +name = "tree-sitter-typescript" +version = "0.20.2" +dependencies = [ + "cc", + "tree-sitter 0.20.9", +] + +[[package]] +name = "tree-sitter-zig" +version = "0.0.1" +dependencies = [ + "cc", + "tree-sitter 0.20.9", +] + [[package]] name = "try-lock" version = "0.2.4" @@ -1794,6 +2035,7 @@ dependencies = [ "tower-service", "tracing", "tracing-subscriber", + "tree-painter", "zstd", ] diff --git a/Cargo.toml b/Cargo.toml index dd11465..2a99160 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ tower = { version = "0", default-features = false } tower-http = { version = "0", features = ["compression-full", "limit", "timeout", "trace"] } tracing = "0" tracing-subscriber = "0" +tree-painter = { path = "tree-painter/lib" } zstd = "0.11" [dev-dependencies] diff --git a/src/errors.rs b/src/errors.rs index ae9f662..e83e701 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -24,9 +24,7 @@ pub enum Error { #[error("join error: {0}")] Join(#[from] tokio::task::JoinError), #[error("syntax highlighting error: {0}")] - SyntaxHighlighting(#[from] syntect::Error), - #[error("syntax parsing error: {0}")] - SyntaxParsing(#[from] syntect::parsing::ParsingError), + Highlighting(#[from] tree_painter::Error), #[error("time formatting error: {0}")] TimeFormatting(#[from] time::error::Format), #[error("could not parse cookie: {0}")] @@ -49,8 +47,7 @@ impl From for StatusCode { | Error::IntConversion(_) | Error::TimeFormatting(_) | Error::Migration(_) - | Error::SyntaxHighlighting(_) - | Error::SyntaxParsing(_) + | Error::Highlighting(_) | Error::Axum(_) => StatusCode::INTERNAL_SERVER_ERROR, Error::Delete => StatusCode::FORBIDDEN, } diff --git a/src/highlight.rs b/src/highlight.rs index e60b7b0..ae0f3e5 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -1,23 +1,20 @@ -use crate::errors::Error; +use crate::Error; use once_cell::sync::Lazy; -use std::io::Cursor; -use syntect::highlighting::ThemeSet; -use syntect::html::{css_for_theme_with_class_style, line_tokens_to_classed_spans, ClassStyle}; -use syntect::parsing::{ParseState, ScopeStack, SyntaxSet}; -use syntect::util::LinesWithEndings; +use std::sync::{Arc, Mutex}; +use tree_painter::{Lang, Renderer, Theme}; pub static DATA: Lazy = Lazy::new(|| { - let data = include_str!("themes/ayu-light.tmTheme"); - let light_theme = ThemeSet::load_from_reader(&mut Cursor::new(data)).unwrap(); - - let data = include_str!("themes/ayu-dark.tmTheme"); - let dark_theme = ThemeSet::load_from_reader(&mut Cursor::new(data)).unwrap(); + let light_theme = Theme::from_helix(&tree_painter::themes::CATPPUCCIN_LATTE).unwrap(); + let dark_theme = Theme::from_helix(&tree_painter::themes::CATPPUCCIN_MOCHA).unwrap(); + let renderer = Renderer::new(light_theme); + let light = renderer.css(); + let dark = Renderer::new(dark_theme).css(); Data { main: include_str!("themes/style.css"), - light: css_for_theme_with_class_style(&light_theme, ClassStyle::Spaced).unwrap(), - dark: css_for_theme_with_class_style(&dark_theme, ClassStyle::Spaced).unwrap(), - syntax_set: SyntaxSet::load_defaults_newlines(), + renderer: Arc::new(Mutex::new(renderer)), + light, + dark, } }); @@ -25,42 +22,44 @@ pub struct Data<'a> { pub main: &'a str, pub dark: String, pub light: String, - pub syntax_set: SyntaxSet, + pub renderer: Arc>, } -pub fn highlight(source: &str, ext: &str) -> Result { - let syntax_ref = DATA - .syntax_set - .find_syntax_by_extension(ext) - .unwrap_or_else(|| DATA.syntax_set.find_syntax_by_extension("txt").unwrap()); - - let mut parse_state = ParseState::new(syntax_ref); +fn highlight_real(text: &str, lang: Lang) -> Result { let mut html = String::from(""); - let mut scope_stack = ScopeStack::new(); - - for (mut line_number, line) in LinesWithEndings::from(source).enumerate() { - let parsed = parse_state.parse_line(line, &DATA.syntax_set)?; - let (formatted, delta) = line_tokens_to_classed_spans( - line, - parsed.as_slice(), - ClassStyle::Spaced, - &mut scope_stack, - ) - .unwrap(); + let mut renderer = DATA.renderer.lock().unwrap(); + for (mut line_number, line) in renderer.render(&lang, text.as_bytes())?.enumerate() { line_number += 1; - let formatted_str = formatted.as_str(); - let line_number = - format!(r#""#); - html.push_str(&line_number); - let line = format!(r#""); + html.push_str(&format!( + r#""# + )); + html.push_str(&format!(r#""#)); } html.push_str("
{line_number:>4}{formatted_str}"#); - html.push_str(&line); - html.push_str(&"".repeat(delta.max(0).try_into()?)); - html.push_str("
{line_number:>4}{line}
"); - Ok(html) } + +fn highlight_plain(text: &str) -> String { + let mut html = String::from(""); + + for (mut line_number, line) in text.lines().enumerate() { + line_number += 1; + html.push_str(&format!( + r#""# + )); + html.push_str(&format!(r#""#)); + } + + html.push_str("
{line_number:>4}{line}
"); + html +} + +pub fn highlight(text: &str, ext: &str) -> Result { + match Lang::from_extension(ext) { + Some(lang) => highlight_real(text, lang), + None => Ok(highlight_plain(text)), + } +} diff --git a/src/pages.rs b/src/pages.rs index 0e9218a..9314a2f 100644 --- a/src/pages.rs +++ b/src/pages.rs @@ -1,5 +1,4 @@ use crate::env; -use crate::highlight; use askama::Template; use axum::http::StatusCode; use std::default::Default; @@ -34,7 +33,7 @@ impl From for ErrorResponse<'_> { pub struct Index<'a> { title: &'a str, version: &'a str, - syntaxes: &'a [syntect::parsing::SyntaxReference], + infos: &'static [tree_painter::Info; 20], } impl<'a> Default for Index<'a> { @@ -42,7 +41,7 @@ impl<'a> Default for Index<'a> { Self { title: &env::TITLE, version: env::VERSION, - syntaxes: highlight::DATA.syntax_set.syntaxes(), + infos: &tree_painter::INFOS, } } } diff --git a/src/themes/style.css b/src/themes/style.css index 84f00cb..fd5f533 100644 --- a/src/themes/style.css +++ b/src/themes/style.css @@ -1,25 +1,9 @@ @import url("light.css") (prefers-color-scheme: light); @import url("dark.css") (prefers-color-scheme: dark); -@media (prefers-color-scheme: dark) { - body, textarea, select, select > option:hover { - background-color: #0f1419; - color: #e6e1cf; - } - - select { - color: #e6e1cf; - } -} - -@media (prefers-color-scheme: light) { - body, textarea, select, select > option:hover { - background-color: #fafafa; - } - - select { - color: #0f1419; - } +body, textarea, select, select > option:hover { + background-color: var(--tsc-main-bg-color); + color: var(--tsc-main-fg-color); } body { @@ -36,6 +20,7 @@ select { width: 100%; font-family: "JetBrains Mono", monospace; border: none; + color: var(--tsc-main-fg-color); } select > option:hover { diff --git a/templates/index.html b/templates/index.html index 7953d6c..bffa9ac 100644 --- a/templates/index.html +++ b/templates/index.html @@ -43,12 +43,10 @@
- + {%- for info in infos -%} + + {%- endfor -%}
diff --git a/tree-painter b/tree-painter new file mode 160000 index 0000000..7bcb129 --- /dev/null +++ b/tree-painter @@ -0,0 +1 @@ +Subproject commit 7bcb129b3dfd4c95faa570613c76687f7cee79e1