Files
Pena/src-tauri/src/infrastructure/comrak_renderer.rs
T
2026-06-22 21:33:38 +02:00

109 lines
3.5 KiB
Rust

use comrak::plugins::syntect::SyntectAdapterBuilder;
use comrak::{markdown_to_html_with_plugins, Options, Plugins};
use syntect::highlighting::ThemeSet;
use syntect::html::{css_for_theme_with_class_style, ClassStyle};
use syntect::parsing::SyntaxDefinition;
use crate::domain::MarkdownRenderer;
const TYPESCRIPT_SYNTAX: &str =
include_str!("../../resources/syntaxes/TypeScript.sublime-syntax");
fn build_syntax_set() -> syntect::parsing::SyntaxSet {
let mut builder = syntect::parsing::SyntaxSet::load_defaults_nonewlines().into_builder();
if let Ok(def) = SyntaxDefinition::load_from_str(TYPESCRIPT_SYNTAX, true, None) {
builder.add(def);
}
builder.build()
}
pub struct ComrakRenderer;
pub struct ComrakPreviewRenderer;
impl MarkdownRenderer for ComrakRenderer {
fn render(&self, content: &str) -> String {
let adapter = SyntectAdapterBuilder::new()
.syntax_set(build_syntax_set())
.css()
.build();
let mut options = Options::default();
options.extension.table = true;
options.extension.strikethrough = true;
options.extension.autolink = true;
options.extension.tasklist = true;
let mut plugins = Plugins::default();
plugins.render.codefence_syntax_highlighter = Some(&adapter);
markdown_to_html_with_plugins(content, &options, &plugins)
}
}
impl MarkdownRenderer for ComrakPreviewRenderer {
fn render(&self, content: &str) -> String {
let adapter = SyntectAdapterBuilder::new()
.syntax_set(build_syntax_set())
.css()
.build();
let options = Options::default();
let mut plugins = Plugins::default();
plugins.render.codefence_syntax_highlighter = Some(&adapter);
markdown_to_html_with_plugins(content, &options, &plugins)
}
}
pub fn syntax_css_for_theme(syntect_theme_name: &str) -> String {
let ts = ThemeSet::load_defaults();
let theme = match ts.themes.get(syntect_theme_name) {
Some(t) => t,
None => return String::new(),
};
match css_for_theme_with_class_style(theme, ClassStyle::Spaced) {
Ok(css) => css.replace(".code {", ".syntax-highlighting {"),
Err(_) => String::new(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn render_outputs_syntax_highlighting_class() {
let r = ComrakRenderer;
let html = r.render("```rust\nfn main() {}\n```\n");
assert!(html.contains("syntax-highlighting"));
}
#[test]
fn render_outputs_classed_spans() {
let r = ComrakRenderer;
let html = r.render("```rust\nfn main() {}\n```\n");
assert!(html.contains("<span class="));
}
#[test]
fn typescript_syntax_is_highlighted() {
let r = ComrakRenderer;
let html = r.render("```typescript\ninterface Foo { id: string; }\n```\n");
assert!(html.contains("<span class="), "TypeScript should produce highlighted spans");
}
#[test]
fn syntax_css_known_theme_contains_wrapper() {
let css = syntax_css_for_theme("base16-ocean.dark");
assert!(!css.is_empty());
assert!(css.contains(".syntax-highlighting {"));
}
#[test]
fn syntax_css_known_theme_contains_token_rules() {
let css = syntax_css_for_theme("base16-ocean.dark");
assert!(css.contains("color:") || css.contains("color :"));
}
#[test]
fn syntax_css_unknown_theme_returns_empty() {
let css = syntax_css_for_theme("nonexistent-theme-xyz");
assert!(css.is_empty());
}
}