diff --git a/Cargo.lock b/Cargo.lock index 87def3a5..1fbcb78f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -632,9 +632,9 @@ checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "comrak" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c93ab3577cca16b4a1d80a88c2e0cd8b6e969e51696f0bbb0d1dcb0157109832" +checksum = "d8c32ff8b21372fab0e9ecc4e42536055702dc5faa418362bffd1544f9d12637" dependencies = [ "caseless", "derive_builder", @@ -837,9 +837,9 @@ dependencies = [ [[package]] name = "deno_ast" -version = "0.42.0" +version = "0.43.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b08d11d9e4086b00d3428650e31153cf5896586411763cb88a6423ce5b18791" +checksum = "48d00b724e06d2081a141ec1155756a0b465d413d8e2a7515221f61d482eb2ee" dependencies = [ "base64 0.21.7", "deno_media_type", @@ -878,9 +878,9 @@ dependencies = [ [[package]] name = "deno_doc" -version = "0.155.0" +version = "0.157.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234aade73928fc0a2241ac54e8be33517fc02d177fbf38516e97716ff32b4464" +checksum = "8c674b0065e17685297f271f5e323b8886981e5e24c08564d55f26bad5f6e9fa" dependencies = [ "ammonia", "anyhow", @@ -899,24 +899,13 @@ dependencies = [ "serde", "serde_json", "termcolor", - "tree-sitter-bash", - "tree-sitter-css", - "tree-sitter-highlight", - "tree-sitter-html", - "tree-sitter-javascript", - "tree-sitter-json", - "tree-sitter-md", - "tree-sitter-regex", - "tree-sitter-rust", - "tree-sitter-typescript", - "tree-sitter-xml", ] [[package]] name = "deno_graph" -version = "0.83.3" +version = "0.84.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77163c46755676d8f793fc19e365537ba660a8db173cd1e02d21eb010c0b3cef" +checksum = "cd4f4a14aa069087be41c2998077b0453f0191747898f96e6343f700abfc2c18" dependencies = [ "anyhow", "async-trait", @@ -955,9 +944,9 @@ dependencies = [ [[package]] name = "deno_media_type" -version = "0.1.4" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8978229b82552bf8457a0125aa20863f023619cfc21ebb007b1e571d68fd85b" +checksum = "7fcf552fbdedbe81c89705349d7d2485c7051382b000dfddbdbf7fc25931cf83" dependencies = [ "data-url", "serde", @@ -2960,6 +2949,7 @@ dependencies = [ "flate2", "futures", "handlebars 5.1.2", + "html-escape", "hyper", "indexmap 2.5.0", "infer", @@ -2994,6 +2984,17 @@ dependencies = [ "tracing-futures", "tracing-opentelemetry", "tracing-subscriber", + "tree-sitter-bash", + "tree-sitter-css", + "tree-sitter-highlight", + "tree-sitter-html", + "tree-sitter-javascript", + "tree-sitter-json", + "tree-sitter-md", + "tree-sitter-regex", + "tree-sitter-rust", + "tree-sitter-typescript", + "tree-sitter-xml", "url", "urlencoding", "uuid", diff --git a/api/Cargo.toml b/api/Cargo.toml index b7a9e678..2201bd78 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -78,10 +78,10 @@ deno_semver = "0.5.2" flate2 = "1" thiserror = "1" async-tar = "0.4.2" -deno_graph = "0.83.3" -deno_ast = { version = "0.42.0", features = ["view"] } -deno_doc = { version = "0.155.0" } -comrak = { version = "0.28.0", default-features = false } +deno_graph = "0.84.1" +deno_ast = { version = "0.43.3", features = ["view"] } +deno_doc = { version = "0.157.0", features = ["comrak"] } +comrak = { version = "0.29.0", default-features = false } async-trait = "0.1.73" jsonwebkey = { version = "0.3.5", features = ["jsonwebtoken", "jwt-convert"] } jsonwebtoken = "8.3.0" @@ -96,6 +96,19 @@ sha1 = "0.10.6" infer = "0.15.0" x509-parser = { version = "0.15.1", features = ["verify"] } sitemap-rs = "0.2.1" +html-escape = "0.2.13" + +tree-sitter-highlight = "0.22.6" +tree-sitter-javascript = "0.21.4" +tree-sitter-typescript = "0.21.2" +tree-sitter-json = "0.21.0" +tree-sitter-regex = "0.21.0" +tree-sitter-css = "0.21.0" +tree-sitter-md = "0.2.3" +tree-sitter-rust = "0.21.2" +tree-sitter-html = "0.20.3" +tree-sitter-bash = "0.21.0" +tree-sitter-xml = "0.6.4" [dev-dependencies] flate2 = "1" diff --git a/api/src/analysis.rs b/api/src/analysis.rs index c8d5b833..930cd65d 100644 --- a/api/src/analysis.rs +++ b/api/src/analysis.rs @@ -21,7 +21,7 @@ use deno_graph::source::NullFileSystem; use deno_graph::BuildFastCheckTypeGraphOptions; use deno_graph::BuildOptions; use deno_graph::CapturingModuleAnalyzer; -use deno_graph::DefaultModuleParser; +use deno_graph::DefaultEsParser; use deno_graph::GraphKind; use deno_graph::ModuleGraph; use deno_graph::ModuleInfo; @@ -168,7 +168,7 @@ async fn analyze_package_inner( fast_check_cache: None, fast_check_dts: true, jsr_url_provider: &PassthroughJsrUrlProvider, - module_parser: Some(&module_analyzer.analyzer), + es_parser: Some(&module_analyzer.analyzer), resolver: Default::default(), npm_resolver: Default::default(), workspace_fast_check: WorkspaceFastCheckOption::Enabled(&workspace_members), @@ -595,7 +595,7 @@ async fn rebuild_npm_tarball_inner( fast_check_cache: Default::default(), fast_check_dts: true, jsr_url_provider: &PassthroughJsrUrlProvider, - module_parser: Some(&module_analyzer.analyzer), + es_parser: Some(&module_analyzer.analyzer), resolver: None, npm_resolver: None, workspace_fast_check: WorkspaceFastCheckOption::Enabled(&workspace_members), @@ -680,14 +680,14 @@ impl<'a> deno_graph::source::Loader for GcsLoader<'a> { } #[derive(Default)] -pub struct ModuleParser(DefaultModuleParser); +pub struct ModuleParser(DefaultEsParser); -impl deno_graph::ModuleParser for ModuleParser { - fn parse_module( +impl deno_graph::EsParser for ModuleParser { + fn parse_program( &self, options: deno_graph::ParseOptions, ) -> Result { - let source = self.0.parse_module(options)?; + let source = self.0.parse_program(options)?; if let Some(err) = source.diagnostics().first() { return Err(err.clone()); } @@ -817,9 +817,9 @@ fn check_for_banned_syntax( (line_number, column_number) }; - for i in parsed_source.module().body.iter() { + for i in parsed_source.program_ref().body() { match i { - ast::ModuleItem::ModuleDecl(n) => match n { + deno_ast::ModuleItemRef::ModuleDecl(n) => match n { ast::ModuleDecl::TsNamespaceExport(n) => { let (line, column) = line_col(&n.range()); return Err(PublishError::GlobalTypeAugmentation { @@ -894,7 +894,7 @@ fn check_for_banned_syntax( } _ => continue, }, - ast::ModuleItem::Stmt(n) => match n { + deno_ast::ModuleItemRef::Stmt(n) => match n { ast::Stmt::Decl(ast::Decl::TsModule(n)) => { if n.global { let (line, column) = line_col(&n.range()); diff --git a/api/src/api/package.rs b/api/src/api/package.rs index c793c767..197ba03f 100644 --- a/api/src/api/package.rs +++ b/api/src/api/package.rs @@ -1062,6 +1062,7 @@ pub async fn get_docs_handler( match docs { GeneratedDocsOutput::Docs(docs) => Ok(ApiPackageVersionDocs::Content { css: Cow::Borrowed(deno_doc::html::STYLESHEET), + comrak_css: Cow::Borrowed(deno_doc::html::comrak::COMRAK_STYLESHEET), script: Cow::Borrowed(deno_doc::html::SCRIPT_JS), breadcrumbs: docs.breadcrumbs, toc: docs.toc, @@ -1300,7 +1301,9 @@ pub async fn get_source_handler( let source = if let Some(file) = file { let size = file.len(); - let highlighter = deno_doc::html::setup_highlighter(true); + let highlighter = crate::tree_sitter::ComrakAdapter { + show_line_numbers: true, + }; let view = if let Ok(file) = String::from_utf8(file.to_vec()) { let mut out = vec![]; @@ -2644,6 +2647,7 @@ ggHohNAjhbzDaY2iBW/m3NC5dehGUP4T2GBo/cwGhg== ApiPackageVersionDocs::Content { version, css, + comrak_css: _, script: _, breadcrumbs, toc, @@ -2669,6 +2673,7 @@ ggHohNAjhbzDaY2iBW/m3NC5dehGUP4T2GBo/cwGhg== ApiPackageVersionDocs::Content { version, css, + comrak_css: _, script: _, breadcrumbs, toc, @@ -2698,6 +2703,7 @@ ggHohNAjhbzDaY2iBW/m3NC5dehGUP4T2GBo/cwGhg== ApiPackageVersionDocs::Content { version, css, + comrak_css: _, script: _, breadcrumbs, toc, @@ -2730,6 +2736,7 @@ ggHohNAjhbzDaY2iBW/m3NC5dehGUP4T2GBo/cwGhg== ApiPackageVersionDocs::Content { version, css, + comrak_css: _, script: _, breadcrumbs, toc, diff --git a/api/src/api/types.rs b/api/src/api/types.rs index 6eb7c1b7..aa17bd6e 100644 --- a/api/src/api/types.rs +++ b/api/src/api/types.rs @@ -586,9 +586,11 @@ pub struct ApiPackageVersion { #[serde(rename_all = "camelCase", tag = "kind")] #[allow(clippy::large_enum_variant)] pub enum ApiPackageVersionDocs { + #[serde(rename_all = "camelCase")] Content { version: ApiPackageVersion, css: Cow<'static, str>, + comrak_css: Cow<'static, str>, script: Cow<'static, str>, breadcrumbs: Option, toc: Option, diff --git a/api/src/docs.rs b/api/src/docs.rs index a2718af3..3365a344 100644 --- a/api/src/docs.rs +++ b/api/src/docs.rs @@ -132,7 +132,7 @@ fn get_url_rewriter( base: String, github_repository: Option, is_readme: bool, -) -> deno_doc::html::comrak_adapters::URLRewriter { +) -> deno_doc::html::comrak::URLRewriter { Arc::new(move |current_file, url| { if url.starts_with('#') || url.starts_with('/') { return url.to_string(); @@ -216,7 +216,7 @@ pub fn get_generate_ctx<'a>( let package_name = format!("@{scope}/{package}"); let url_rewriter_base = format!("/{package_name}/{version}"); - let mut generate_ctx = GenerateCtx::new( + GenerateCtx::new( deno_doc::html::GenerateOptions { package_name: Some(package_name), main_entrypoint, @@ -244,87 +244,37 @@ pub fn get_generate_ctx<'a>( }) .clone(), }), - usage_composer: Some(Rc::new(move |ctx, doc_nodes, url| { - let mut map = IndexMap::new(); - let scoped_name = format!("@{scope}/{package}"); - - let import = format!("\nImport symbol\n{}", deno_doc::html::usage_to_md(ctx, doc_nodes, &url)); - - if !runtime_compat.deno.is_some_and(|compat| !compat) { - map.insert( - UsageComposerEntry { - name: "Deno".to_string(), - icon: Some( - r#"deno logo"#.into(), - ), - }, - format!("Add Package\n```\ndeno add jsr:{scoped_name}\n```{import}\n---- OR ----\n\nImport directly with a jsr specifier\n{}\n", deno_doc::html::usage_to_md(ctx, doc_nodes, &format!("jsr:{url}"))), - ); - } - - if !runtime_compat.node.is_some_and(|compat| !compat) { - map.insert( - UsageComposerEntry { - name: "npm".to_string(), - icon: Some( - r#"npm logo"#.into(), - ), - }, - format!("Add Package\n```\nnpx jsr add {scoped_name}\n```{import}"), - ); - map.insert( - UsageComposerEntry { - name: "Yarn".to_string(), - icon: Some( - r#"yarn logo"#.into(), - ), - }, - format!("Add Package\n```\nyarn dlx jsr add {scoped_name}\n```{import}"), - ); - map.insert( - UsageComposerEntry { - name: "pnpm".to_string(), - icon: Some( - r#"pnpm logo"#.into(), - ), - }, - format!("Add Package\n```\npnpm dlx jsr add {scoped_name}\n```{import}"), - ); - } - - if !runtime_compat.bun.is_some_and(|compat| !compat) { - map.insert( - UsageComposerEntry { - name: "Bun".to_string(), - icon: Some( - r#"bun logo"#.into(), - ), - }, - format!("Add Package\n```\nbunx jsr add {scoped_name}\n```{import}"), - ); - } - - map + usage_composer: (Rc::new(DocUsageComposer { + runtime_compat, + scope, + package, })), rewrite_map: Some(rewrite_map), category_docs: None, disable_search: false, symbol_redirect_map: None, default_symbol_map: None, + markdown_renderer: deno_doc::html::comrak::create_renderer( + Some(Arc::new(super::tree_sitter::ComrakAdapter { + show_line_numbers: false, + })), + Some(Box::new(|ammonia| { + ammonia.add_allowed_classes("span", crate::tree_sitter::CLASSES); + })), + Some(get_url_rewriter( + url_rewriter_base, + github_repository, + has_readme, + )), + ), + markdown_stripper: Rc::new(deno_doc::html::comrak::strip), + head_inject: None, }, None, deno_doc::html::FileMode::Normal, doc_nodes_by_url, ) - .unwrap(); - - generate_ctx.url_rewriter = Some(get_url_rewriter( - url_rewriter_base, - github_repository, - has_readme, - )); - - generate_ctx + .unwrap() } #[allow(clippy::too_many_arguments)] @@ -916,24 +866,6 @@ impl HrefResolver for DocResolver { } } - fn resolve_usage(&self, current_resolve: UrlResolveKind) -> Option { - let (is_main, path) = current_resolve - .get_file() - .map(|short_path| (short_path.is_main, &*short_path.path)) - .unwrap_or((true, "")); - - Some(format!( - "@{}/{}{}", - self.scope, - self.package, - if is_main { - String::new() - } else { - format!("/{path}") - } - )) - } - fn resolve_source(&self, location: &Location) -> Option { let url = Url::parse(&location.filename).ok()?; Some(format!( @@ -955,6 +887,106 @@ impl HrefResolver for DocResolver { } } +struct DocUsageComposer { + runtime_compat: RuntimeCompat, + scope: ScopeName, + package: PackageName, +} + +impl deno_doc::html::UsageComposer for DocUsageComposer { + fn is_single_mode(&self) -> bool { + false + } + + fn compose( + &self, + doc_nodes: &[DocNodeWithContext], + current_resolve: UrlResolveKind, + usage_to_md: deno_doc::html::UsageToMd, + ) -> IndexMap { + let mut map = IndexMap::new(); + let scoped_name = format!("@{}/{}", self.scope, self.package); + + let (is_main, path) = current_resolve + .get_file() + .map(|short_path| (short_path.is_main, &*short_path.path)) + .unwrap_or((true, "")); + + let url = format!( + "@{}/{}{}", + self.scope, + self.package, + if is_main { + String::new() + } else { + format!("/{path}") + } + ); + + let import = format!( + "\nImport symbol\n{}", + usage_to_md(doc_nodes, &url, Some(self.package.as_str())) + ); + + if !self.runtime_compat.deno.is_some_and(|compat| !compat) { + map.insert( + UsageComposerEntry { + name: "Deno".to_string(), + icon: Some( + r#"deno logo"#.into(), + ), + }, + format!("Add Package\n```\ndeno add jsr:{scoped_name}\n```{import}\n---- OR ----\n\nImport directly with a jsr specifier\n{}\n", usage_to_md(doc_nodes, &format!("jsr:{url}"), Some(self.package.as_str()))), + ); + } + + if !self.runtime_compat.node.is_some_and(|compat| !compat) { + map.insert( + UsageComposerEntry { + name: "npm".to_string(), + icon: Some( + r#"npm logo"#.into(), + ), + }, + format!("Add Package\n```\nnpx jsr add {scoped_name}\n```{import}"), + ); + map.insert( + UsageComposerEntry { + name: "Yarn".to_string(), + icon: Some( + r#"yarn logo"#.into(), + ), + }, + format!("Add Package\n```\nyarn dlx jsr add {scoped_name}\n```{import}"), + ); + map.insert( + UsageComposerEntry { + name: "pnpm".to_string(), + icon: Some( + r#"pnpm logo"#.into(), + ), + }, + format!("Add Package\n```\npnpm dlx jsr add {scoped_name}\n```{import}"), + ); + } + + if !self.runtime_compat.bun.is_some_and(|compat| !compat) { + map.insert( + UsageComposerEntry { + name: "Bun".to_string(), + icon: Some( + r#"bun logo"# + .into(), + ), + }, + format!("Add Package\n```\nbunx jsr add {scoped_name}\n```{import}"), + ); + } + + map + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/api/src/main.rs b/api/src/main.rs index 51824171..17d796fd 100644 --- a/api/src/main.rs +++ b/api/src/main.rs @@ -25,6 +25,7 @@ mod tasks; mod token; mod traced_router; mod tracing; +mod tree_sitter; mod util; use crate::api::api_router; diff --git a/api/src/npm/emit.rs b/api/src/npm/emit.rs index cad3930e..5eb2bf67 100644 --- a/api/src/npm/emit.rs +++ b/api/src/npm/emit.rs @@ -3,7 +3,7 @@ use deno_ast::emit; use deno_ast::fold_program; use deno_ast::swc::visit::VisitMutWith; -use deno_ast::EmittedSourceBytes; +use deno_ast::EmittedSourceText; use deno_ast::ParsedSource; use deno_ast::SourceMap; use deno_ast::SourceMapOption; @@ -35,7 +35,7 @@ pub fn transpile_to_js( relative_import_specifier(target_specifier, source.specifier()); let source_map = SourceMap::single(file_name, source.text().to_string()); - let mut program = source.program_ref().clone(); + let mut program = source.program_ref().to_owned(); // needs to align with what's done internally in source map assert_eq!(1, source.range().start.as_byte_pos().0); @@ -65,10 +65,9 @@ pub fn transpile_to_js( source.diagnostics(), )?; - let EmittedSourceBytes { - mut source, - source_map, - } = emit(&program, &comments, &source_map, &emit_options)?; + let EmittedSourceText { text, source_map } = + emit((&program).into(), &comments, &source_map, &emit_options)?; + let mut source = text.into_bytes(); if let Some(last) = source.last() { if *last != b'\n' { @@ -79,7 +78,7 @@ pub fn transpile_to_js( source .extend(format!("//# sourceMappingURL={}.map", basename).into_bytes()); - Ok((source, source_map.unwrap())) + Ok((source, source_map.unwrap().into_bytes())) }) } @@ -114,10 +113,9 @@ pub fn transpile_to_dts( }; program.visit_mut_with(&mut import_rewrite_transformer); - let EmittedSourceBytes { - mut source, - source_map, - } = emit(&program, &comments, &source_map, &emit_options)?; + let EmittedSourceText { text, source_map } = + emit((&program).into(), &comments, &source_map, &emit_options)?; + let mut source = text.into_bytes(); if let Some(last) = source.last() { if *last != b'\n' { @@ -127,5 +125,5 @@ pub fn transpile_to_dts( source.extend(format!("//# sourceMappingURL={}.map", basename).into_bytes()); - Ok((source, source_map.unwrap())) + Ok((source, source_map.unwrap().into_bytes())) } diff --git a/api/src/npm/tarball.rs b/api/src/npm/tarball.rs index 8deac1e7..40914a5f 100644 --- a/api/src/npm/tarball.rs +++ b/api/src/npm/tarball.rs @@ -725,7 +725,7 @@ mod tests { fast_check_cache: Default::default(), fast_check_dts: true, jsr_url_provider: &PassthroughJsrUrlProvider, - module_parser: Some(&module_analyzer.analyzer), + es_parser: Some(&module_analyzer.analyzer), resolver: None, npm_resolver: None, workspace_fast_check: WorkspaceFastCheckOption::Enabled( diff --git a/api/src/publish.rs b/api/src/publish.rs index eab4b97f..98c6cffb 100644 --- a/api/src/publish.rs +++ b/api/src/publish.rs @@ -822,6 +822,7 @@ pub mod tests { HashMap::from_iter([( "/mod.ts".to_string(), ModuleInfo { + is_script: false, dependencies: vec![], ts_references: vec![], self_types_specifier: None, diff --git a/api/src/tree_sitter.rs b/api/src/tree_sitter.rs new file mode 100644 index 00000000..e84f23de --- /dev/null +++ b/api/src/tree_sitter.rs @@ -0,0 +1,397 @@ +// Copyright 2024 the JSR authors. All rights reserved. MIT license. +use std::collections::HashMap; +use std::io::Write; +use std::sync::OnceLock; + +use tree_sitter_highlight::Highlight; +use tree_sitter_highlight::HighlightConfiguration; + +pub struct ComrakAdapter { + pub show_line_numbers: bool, +} + +impl comrak::adapters::SyntaxHighlighterAdapter for ComrakAdapter { + fn write_highlighted( + &self, + output: &mut dyn Write, + lang: Option<&str>, + code: &str, + ) -> std::io::Result<()> { + let lang = lang.unwrap_or_default(); + let config = tree_sitter_language_cb(lang); + let source = code.as_bytes(); + if let Some(config) = config { + let mut highlighter = tree_sitter_highlight::Highlighter::new(); + // unsure why exactly, but without the closure it doesnt compile + // seems to be related to the static aspect of tree_sitter_language_cb + #[allow(clippy::redundant_closure)] + let res = highlighter + .highlight(config, source, None, |e| tree_sitter_language_cb(e)); + + match res { + Ok(highlighter) => { + let mut renderer = tree_sitter_highlight::HtmlRenderer::new(); + match renderer + .render(highlighter, source, &|highlight| classes(highlight)) + { + Ok(()) => { + let mut line_numbers = String::new(); + let mut lines = String::new(); + + for (i, line) in renderer.lines().enumerate() { + let n = i + 1; + + if self.show_line_numbers { + line_numbers.push_str(&format!( + r##"{n}"##, + )); + + lines.push_str(&format!( + r#""# + )); + } + + lines.push_str(line); + + if self.show_line_numbers { + lines.push_str(""); + } + } + + let html = if self.show_line_numbers { + format!( + r##"
{line_numbers}
{lines}
"## + ) + } else { + lines + }; + + return output.write_all(html.as_bytes()); + } + Err(err) => { + eprintln!("Error rendering code: {}", err); + } + }; + } + Err(err) => { + eprintln!("Error highlighting code: {}", err); + } + } + } + + comrak::html::escape(output, source) + } + + fn write_pre_tag( + &self, + output: &mut dyn Write, + attributes: HashMap, + ) -> std::io::Result<()> { + comrak::html::write_opening_tag(output, "pre", attributes) + } + + fn write_code_tag( + &self, + output: &mut dyn Write, + mut attributes: HashMap, + ) -> std::io::Result<()> { + if self.show_line_numbers { + attributes + .entry("class".into()) + .or_default() + .push_str(" !flex gap-2"); + } + comrak::html::write_opening_tag(output, "code", attributes) + } +} + +macro_rules! highlighter { + [$($name:literal -> $class:literal,)*] => { + /// The capture names to configure on the highlighter. If this is not + /// configured correctly, the highlighter will not work. + pub const CAPTURE_NAMES: &[&str] = &[$($name),*]; + const CLASSES_ATTRIBUTES: &[&str] = &[$(concat!("class=\"", $class, "\"")),*]; + pub const CLASSES: &[&str] = &[$($class),*]; + }; +} + +highlighter! [ + "attribute" -> "pl-c1", + "comment" -> "pl-c", + "constant.builtin" -> "pl-c1", + "constant" -> "pl-c1", + "constructor" -> "pl-v", + "embedded" -> "pl-s1", + "function" -> "pl-en", + "keyword" -> "pl-k", + "number" -> "pl-c1", + "operator" -> "pl-c1", + "property" -> "pl-c1", + "string" -> "pl-s", + "tag" -> "pl-ent", + "type" -> "pl-smi", + "variable.builtin" -> "pl-smi", +]; + +pub(crate) fn classes(highlight: Highlight) -> &'static [u8] { + CLASSES_ATTRIBUTES[highlight.0].as_bytes() +} + +pub fn tree_sitter_language_cb( + lang: &str, +) -> Option<&'static HighlightConfiguration> { + for lang in lang.split(',') { + let cfg = match lang.trim() { + "js" | "javascript" => tree_sitter_language_javascript(), + "jsx" => tree_sitter_language_jsx(), + "ts" | "typescript" => tree_sitter_language_typescript(), + "tsx" => tree_sitter_language_tsx(), + "json" | "jsonc" => tree_sitter_language_json(), + "css" => tree_sitter_language_css(), + "md" | "markdown" => tree_sitter_language_markdown(), + "xml" => tree_sitter_language_xml(), + "dtd" => tree_sitter_language_dtd(), + "regex" => tree_sitter_language_regex(), + "rs" | "rust" => tree_sitter_language_rust(), + "html" => tree_sitter_language_html(), + "sh" | "bash" => tree_sitter_language_bash(), + _ => continue, + }; + return Some(cfg); + } + None +} + +pub fn tree_sitter_language_javascript() -> &'static HighlightConfiguration { + static CONFIG: OnceLock = OnceLock::new(); + CONFIG.get_or_init(|| { + let mut config = HighlightConfiguration::new( + tree_sitter_javascript::language(), + "javascript", + tree_sitter_javascript::HIGHLIGHT_QUERY, + tree_sitter_javascript::INJECTIONS_QUERY, + tree_sitter_javascript::LOCALS_QUERY, + ) + .expect("failed to initialize tree_sitter_javascript highlighter"); + config.configure(CAPTURE_NAMES); + config + }) +} + +pub fn tree_sitter_language_jsx() -> &'static HighlightConfiguration { + static CONFIG: OnceLock = OnceLock::new(); + CONFIG.get_or_init(|| { + let mut config = HighlightConfiguration::new( + tree_sitter_javascript::language(), + "jsx", + format!( + "{} {}", + tree_sitter_javascript::HIGHLIGHT_QUERY, + tree_sitter_javascript::JSX_HIGHLIGHT_QUERY + ) + .leak(), + tree_sitter_javascript::INJECTIONS_QUERY, + tree_sitter_javascript::LOCALS_QUERY, + ) + .expect("failed to initialize tree_sitter_javascript highlighter"); + config.configure(CAPTURE_NAMES); + config + }) +} + +pub fn tree_sitter_language_typescript() -> &'static HighlightConfiguration { + static CONFIG: OnceLock = OnceLock::new(); + CONFIG.get_or_init(|| { + let mut config = HighlightConfiguration::new( + tree_sitter_typescript::language_typescript(), + "typescript", + format!( + "{} {}", + tree_sitter_javascript::HIGHLIGHT_QUERY, + tree_sitter_typescript::HIGHLIGHTS_QUERY + ) + .leak(), + tree_sitter_javascript::INJECTIONS_QUERY, + format!( + "{} {}", + tree_sitter_javascript::LOCALS_QUERY, + tree_sitter_typescript::LOCALS_QUERY + ) + .leak(), + ) + .expect("failed to initialize tree_sitter_typescript highlighter"); + config.configure(CAPTURE_NAMES); + config + }) +} + +pub fn tree_sitter_language_tsx() -> &'static HighlightConfiguration { + static CONFIG: OnceLock = OnceLock::new(); + CONFIG.get_or_init(|| { + let mut config = HighlightConfiguration::new( + tree_sitter_typescript::language_tsx(), + "tsx", + format!( + "{} {} {}", + tree_sitter_javascript::HIGHLIGHT_QUERY, + tree_sitter_javascript::JSX_HIGHLIGHT_QUERY, + tree_sitter_typescript::HIGHLIGHTS_QUERY, + ) + .leak(), + tree_sitter_javascript::INJECTIONS_QUERY, + format!( + "{} {}", + tree_sitter_javascript::LOCALS_QUERY, + tree_sitter_typescript::LOCALS_QUERY + ) + .leak(), + ) + .expect("failed to initialize tree_sitter_typescript highlighter"); + config.configure(CAPTURE_NAMES); + config + }) +} + +fn tree_sitter_language_json() -> &'static HighlightConfiguration { + static CONFIG: OnceLock = OnceLock::new(); + CONFIG.get_or_init(|| { + let mut config = HighlightConfiguration::new( + tree_sitter_json::language(), + "json", + tree_sitter_json::HIGHLIGHTS_QUERY, + "", + "", + ) + .expect("failed to initialize tree_sitter_json highlighter"); + config.configure(CAPTURE_NAMES); + config + }) +} + +fn tree_sitter_language_css() -> &'static HighlightConfiguration { + static CONFIG: OnceLock = OnceLock::new(); + CONFIG.get_or_init(|| { + let mut config = HighlightConfiguration::new( + tree_sitter_css::language(), + "css", + tree_sitter_css::HIGHLIGHTS_QUERY, + "", + "", + ) + .expect("failed to initialize tree_sitter_css highlighter"); + config.configure(CAPTURE_NAMES); + config + }) +} + +fn tree_sitter_language_markdown() -> &'static HighlightConfiguration { + static CONFIG: OnceLock = OnceLock::new(); + CONFIG.get_or_init(|| { + let mut config = HighlightConfiguration::new( + tree_sitter_md::language(), + "markdown", + tree_sitter_md::HIGHLIGHT_QUERY_BLOCK, + tree_sitter_md::INJECTION_QUERY_BLOCK, + "", + ) + .expect("failed to initialize tree_sitter_md highlighter"); + config.configure(CAPTURE_NAMES); + config + }) +} + +fn tree_sitter_language_xml() -> &'static HighlightConfiguration { + static CONFIG: OnceLock = OnceLock::new(); + CONFIG.get_or_init(|| { + let mut config = HighlightConfiguration::new( + tree_sitter_xml::language_xml(), + "xml", + tree_sitter_xml::XML_HIGHLIGHT_QUERY, + "", + "", + ) + .expect("failed to initialize tree_sitter_xml highlighter"); + config.configure(CAPTURE_NAMES); + config + }) +} + +fn tree_sitter_language_dtd() -> &'static HighlightConfiguration { + static CONFIG: OnceLock = OnceLock::new(); + CONFIG.get_or_init(|| { + let mut config = HighlightConfiguration::new( + tree_sitter_xml::language_dtd(), + "dtd", + tree_sitter_xml::DTD_HIGHLIGHT_QUERY, + "", + "", + ) + .expect("failed to initialize tree_sitter_dtd highlighter"); + config.configure(CAPTURE_NAMES); + config + }) +} + +fn tree_sitter_language_regex() -> &'static HighlightConfiguration { + static CONFIG: OnceLock = OnceLock::new(); + CONFIG.get_or_init(|| { + let mut config = HighlightConfiguration::new( + tree_sitter_regex::language(), + "regex", + tree_sitter_regex::HIGHLIGHTS_QUERY, + "", + "", + ) + .expect("failed to initialize tree_sitter_regex highlighter"); + config.configure(CAPTURE_NAMES); + config + }) +} + +fn tree_sitter_language_rust() -> &'static HighlightConfiguration { + static CONFIG: OnceLock = OnceLock::new(); + CONFIG.get_or_init(|| { + let mut config = HighlightConfiguration::new( + tree_sitter_rust::language(), + "rust", + tree_sitter_rust::HIGHLIGHTS_QUERY, + tree_sitter_rust::INJECTIONS_QUERY, + "", + ) + .expect("failed to initialize tree_sitter_rust highlighter"); + config.configure(CAPTURE_NAMES); + config + }) +} + +fn tree_sitter_language_html() -> &'static HighlightConfiguration { + static CONFIG: OnceLock = OnceLock::new(); + CONFIG.get_or_init(|| { + let mut config = HighlightConfiguration::new( + tree_sitter_html::language(), + "html", + tree_sitter_html::HIGHLIGHTS_QUERY, + tree_sitter_html::INJECTIONS_QUERY, + "", + ) + .expect("failed to initialize tree_sitter_html highlighter"); + config.configure(CAPTURE_NAMES); + config + }) +} + +fn tree_sitter_language_bash() -> &'static HighlightConfiguration { + static CONFIG: OnceLock = OnceLock::new(); + CONFIG.get_or_init(|| { + let mut config = HighlightConfiguration::new( + tree_sitter_bash::language(), + "bash", + tree_sitter_bash::HIGHLIGHT_QUERY, + "", + "", + ) + .expect("failed to initialize tree_sitter_bash highlighter"); + config.configure(CAPTURE_NAMES); + config + }) +} diff --git a/frontend/routes/package/(_components)/Docs.tsx b/frontend/routes/package/(_components)/Docs.tsx index 570318a2..868b3881 100644 --- a/frontend/routes/package/(_components)/Docs.tsx +++ b/frontend/routes/package/(_components)/Docs.tsx @@ -38,6 +38,7 @@ export function DocsView({ return (