Skip to content

Commit c1dcb38

Browse files
committed
Replace client-side highlight.js by server-side syntect highlighting
This is setup to only support the same languages we targeted before: Rust, Markdown and TOML.
1 parent f4bf393 commit c1dcb38

28 files changed

+398
-1529
lines changed

.gitmodules

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[submodule "assets/syntaxes/Packages"]
2+
path = assets/syntaxes/Packages
3+
url = https://github.com/sublimehq/Packages
4+
[submodule "assets/syntaxes/Extras/TOML"]
5+
path = assets/syntaxes/Extras/TOML
6+
url = https://github.com/jasonwilliams/sublime_toml_highlighting

Cargo.lock

Lines changed: 72 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ backtrace = "0.3.61"
4444
failure = "0.1.8"
4545
thiserror = "1.0.26"
4646
comrak = { version = "0.14.0", default-features = false }
47+
syntect = { version = "5.0.0", default-features = false, features = ["parsing", "html", "dump-load", "regex-fancy"] }
4748
toml = "0.5"
4849
schemamama = "0.3"
4950
schemamama_postgres = "0.3"
@@ -128,6 +129,7 @@ walkdir = "2"
128129
anyhow = { version = "1.0.42", features = ["backtrace"] }
129130
grass = { version = "0.11.0", default-features = false }
130131
once_cell = { version = "1.4.0", features = ["parking_lot"] }
132+
syntect = { version = "5.0.0", default-features = false, features = ["parsing", "dump-create", "yaml-load", "regex-fancy"] }
131133

132134
[[bench]]
133135
name = "compression"

assets/syntaxes/Extras/TOML

Submodule TOML added at ed38438

assets/syntaxes/Packages

Submodule Packages added at 7d9ed80

build.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ mod tracked {
3636
Ok(())
3737
}
3838

39+
pub(crate) fn track_recursive(path: impl AsRef<Path>) -> Result<()> {
40+
for entry in walkdir::WalkDir::new(path) {
41+
track(entry?.path())?;
42+
}
43+
Ok(())
44+
}
45+
3946
pub(crate) fn read(path: impl AsRef<Path>) -> Result<Vec<u8>> {
4047
let path = path.as_ref();
4148
track(path)?;
@@ -72,6 +79,7 @@ fn main() -> Result<()> {
7279
write_git_version(out_dir)?;
7380
compile_sass(out_dir)?;
7481
write_known_targets(out_dir)?;
82+
compile_syntax(out_dir)?;
7583
Ok(())
7684
}
7785

@@ -175,3 +183,32 @@ fn write_known_targets(out_dir: &Path) -> Result<()> {
175183

176184
Ok(())
177185
}
186+
187+
fn compile_syntax(out_dir: &Path) -> Result<()> {
188+
use syntect::{dumps::dump_to_uncompressed_file, parsing::SyntaxSetBuilder};
189+
190+
fn tracked_add_from_folder(
191+
builder: &mut SyntaxSetBuilder,
192+
path: impl AsRef<Path>,
193+
) -> Result<()> {
194+
// There's no easy way to know exactly which files matter, so just track everything in the
195+
// folder
196+
tracked::track_recursive(&path)?;
197+
builder.add_from_folder(path, true)?;
198+
Ok(())
199+
}
200+
201+
let mut builder = SyntaxSetBuilder::new();
202+
builder.add_plain_text_syntax();
203+
tracked_add_from_folder(&mut builder, "assets/syntaxes/Packages/Rust/")?;
204+
// Some of the extended syntaxes fail to compile, so only load the primary markdown syntax
205+
tracked_add_from_folder(
206+
&mut builder,
207+
"assets/syntaxes/Packages/Markdown/Markdown.sublime-syntax",
208+
)?;
209+
tracked_add_from_folder(&mut builder, "assets/syntaxes/Extras/TOML/")?;
210+
211+
dump_to_uncompressed_file(&builder.build(), out_dir.join("syntect.packdump"))?;
212+
213+
Ok(())
214+
}

dockerfiles/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ COPY src src/
5050
RUN find src -name "*.rs" -exec touch {} \;
5151
COPY templates/style templates/style
5252
COPY vendor vendor/
53+
COPY assets assets/
5354

5455
RUN cargo build --release
5556

src/web/crate_details.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::{match_version, redirect_base, render_markdown, MatchSemver, MetaData};
1+
use super::{markdown, match_version, redirect_base, MatchSemver, MetaData};
22
use crate::utils::{get_correct_docsrs_style_file, report_error};
33
use crate::{
44
db::Pool,
@@ -69,7 +69,7 @@ where
6969
{
7070
markdown
7171
.as_ref()
72-
.map(|markdown| render_markdown(markdown))
72+
.map(|markdown| markdown::render(markdown))
7373
.serialize(serializer)
7474
}
7575

src/web/markdown.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
use crate::error::Result;
2+
use comrak::{
3+
adapters::SyntaxHighlighterAdapter, ComrakExtensionOptions, ComrakOptions, ComrakPlugins,
4+
ComrakRenderPlugins,
5+
};
6+
use once_cell::sync::Lazy;
7+
use std::collections::HashMap;
8+
use std::fmt::Write;
9+
10+
#[derive(Debug)]
11+
struct CodeAdapter;
12+
13+
impl SyntaxHighlighterAdapter for CodeAdapter {
14+
fn highlight(&self, lang: Option<&str>, code: &str) -> String {
15+
highlight_code(lang, code)
16+
}
17+
18+
fn build_pre_tag(&self, attributes: &HashMap<String, String>) -> String {
19+
build_opening_tag("pre", attributes)
20+
}
21+
22+
fn build_code_tag(&self, attributes: &HashMap<String, String>) -> String {
23+
build_opening_tag("code", attributes)
24+
}
25+
}
26+
27+
fn build_opening_tag(tag: &str, attributes: &HashMap<String, String>) -> String {
28+
let mut tag_parts = format!("<{tag}");
29+
for (attr, val) in attributes {
30+
write!(tag_parts, " {attr}=\"{val}\"").unwrap();
31+
}
32+
tag_parts.push('>');
33+
tag_parts
34+
}
35+
36+
pub fn try_highlight_code(lang: Option<&str>, code: &str) -> Result<String> {
37+
use syntect::{
38+
html::{ClassStyle, ClassedHTMLGenerator},
39+
parsing::SyntaxSet,
40+
util::LinesWithEndings,
41+
};
42+
43+
static SYNTAX_DATA: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/syntect.packdump"));
44+
static SYNTAXES: Lazy<SyntaxSet> = Lazy::new(|| {
45+
let syntaxes: SyntaxSet = syntect::dumps::from_uncompressed_data(SYNTAX_DATA).unwrap();
46+
let names = syntaxes
47+
.syntaxes()
48+
.iter()
49+
.map(|s| &s.name)
50+
.collect::<Vec<_>>();
51+
log::debug!("known syntaxes {names:?}");
52+
syntaxes
53+
});
54+
55+
let syntax = lang
56+
.and_then(|lang| SYNTAXES.find_syntax_by_token(lang))
57+
.or_else(|| SYNTAXES.find_syntax_by_first_line(code))
58+
.unwrap_or_else(|| SYNTAXES.find_syntax_plain_text());
59+
60+
log::trace!("Using syntax {:?} for language {lang:?}", syntax.name);
61+
62+
let mut html_generator = ClassedHTMLGenerator::new_with_class_style(
63+
syntax,
64+
&SYNTAXES,
65+
ClassStyle::SpacedPrefixed { prefix: "syntax-" },
66+
);
67+
68+
for line in LinesWithEndings::from(code) {
69+
html_generator.parse_html_for_line_which_includes_newline(line)?;
70+
}
71+
72+
Ok(html_generator.finalize())
73+
}
74+
75+
pub fn highlight_code(lang: Option<&str>, code: &str) -> String {
76+
match try_highlight_code(lang, code) {
77+
Ok(highlighted) => highlighted,
78+
Err(err) => {
79+
log::error!("failed while highlighting code: {err:?}");
80+
code.to_owned()
81+
}
82+
}
83+
}
84+
85+
/// Wrapper around the Markdown parser and renderer to render markdown
86+
pub(crate) fn render(text: &str) -> String {
87+
comrak::markdown_to_html_with_plugins(
88+
text,
89+
&ComrakOptions {
90+
extension: ComrakExtensionOptions {
91+
superscript: true,
92+
table: true,
93+
autolink: true,
94+
tasklist: true,
95+
strikethrough: true,
96+
..ComrakExtensionOptions::default()
97+
},
98+
..ComrakOptions::default()
99+
},
100+
&ComrakPlugins {
101+
render: ComrakRenderPlugins {
102+
codefence_syntax_highlighter: Some(&CodeAdapter),
103+
},
104+
},
105+
)
106+
}

src/web/mod.rs

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ mod error;
8181
mod extensions;
8282
mod features;
8383
mod file;
84+
mod markdown;
8485
pub(crate) mod metrics;
8586
mod releases;
8687
mod routes;
@@ -413,25 +414,6 @@ fn match_version(
413414
Err(Nope::VersionNotFound)
414415
}
415416

416-
/// Wrapper around the Markdown parser and renderer to render markdown
417-
fn render_markdown(text: &str) -> String {
418-
use comrak::{markdown_to_html, ComrakExtensionOptions, ComrakOptions};
419-
420-
let options = ComrakOptions {
421-
extension: ComrakExtensionOptions {
422-
superscript: true,
423-
table: true,
424-
autolink: true,
425-
tasklist: true,
426-
strikethrough: true,
427-
..ComrakExtensionOptions::default()
428-
},
429-
..ComrakOptions::default()
430-
};
431-
432-
markdown_to_html(text, &options)
433-
}
434-
435417
#[must_use = "`Server` blocks indefinitely when dropped"]
436418
pub struct Server {
437419
inner: Listening,

0 commit comments

Comments
 (0)