Skip to content

Commit

Permalink
Render line number from a starting number
Browse files Browse the repository at this point in the history
  • Loading branch information
hongquan committed Mar 31, 2024
1 parent bdd13cc commit 0e3cecd
Show file tree
Hide file tree
Showing 7 changed files with 25 additions and 117 deletions.
11 changes: 0 additions & 11 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ field_names = "0.2.0"
fluent-bundle = "0.15.2"
fluent-templates = "0.8.0"
fred = { version = "8.0.2", features = ["tracing"] }
htmlize = "1.0.5"
http = "1.0.0"
indexmap = { version = "2.0.0", features = ["serde"] }
libpassgen = "1.0.3"
Expand Down
18 changes: 11 additions & 7 deletions minijinja/base.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
// Our old <code> element will be replaced by the one created by Shiki,
// we need to keep old class names and copy to the new one.
function getShikiOpt(lang, classes) {
function getShikiOpt(lang, classes, startLine) {
return {
lang,
theme: 'one-dark-pro',
Expand All @@ -94,6 +94,8 @@
{
code(node) {
classes.forEach((c) => this.addClassToHast(node, c))
const style = node.properties.style || ''
node.properties.style = style + `--start-line: ${startLine};`
},
pre(node) {
const className = classes.includes('q-with-lineno') ? 'py-4' : 'p-4'
Expand All @@ -108,23 +110,25 @@
document.addEventListener('alpine:init', () => {
Alpine.data('need_highlight', () => ({
code: '',
classes: [],
origClasses: [],
startLine: 1,
lang: 'text',
init() {
const codeElm = this.$refs.orig_code;
const code = codeElm.textContent
this.code = code.trim()
let classes = Array.from(codeElm.classList.values())
let className = classes.find((c) => c.startsWith('language-'))
const classes = Array.from(codeElm.classList.values())
const className = classes.find((c) => c.startsWith('language-'))
if (className) {
this.lang = className.split('-')[1]
}
this.classes = classes
this.origClasses = classes
codeElm.dataset.startLine && (this.startLine = parseInt(codeElm.dataset.startLine))
},
async highlight() {
const lang = this.lang
const classes = this.classes
const opts = getShikiOpt(lang, classes)
const classes = this.origClasses
const opts = getShikiOpt(lang, classes, this.startLine)
const html = await codeToHtml(this.code, opts)
return html
}
Expand Down
3 changes: 0 additions & 3 deletions src/consts.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
use syntect::html::ClassStyle;

pub const DB_NAME: &str = "quanweb";
pub const DEFAULT_PAGE_SIZE: u8 = 10;
pub const STATIC_URL: &str = "/static";
pub const UNCATEGORIZED_URL: &str = "/category/_uncategorized/";
pub const SYNTECT_CLASS_STYLE: ClassStyle = ClassStyle::SpacedPrefixed { prefix: "st-" };
pub const KEY_LANG: &str = "lang";
pub const DEFAULT_LANG: &str = "en";
pub const ALPINE_HIGHLIGHTING_APP: &str = "need_highlight";
Expand Down
2 changes: 2 additions & 0 deletions src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,8 @@ pub fn create_shape_element<N: ToString>(name: N, cardinality: Cardinality) -> S
}
}

/// Codefence options. Follow slidev syntax.
/// For example: {lines:true, start_line:4}
#[derive(Debug, Deserialize, SmartDefault)]
#[serde(default)]
pub struct CodeFenceOptions {
Expand Down
105 changes: 11 additions & 94 deletions src/utils/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,109 +7,30 @@ use comrak::{
markdown_to_html_with_plugins, ExtensionOptionsBuilder, Options, PluginsBuilder,
RenderOptionsBuilder, RenderPluginsBuilder,
};
use htmlize::escape_text;
use serde_json5;
use syntect::html::ClassedHTMLGenerator;
use syntect::parsing::{SyntaxReference, SyntaxSet};
use syntect::util::LinesWithEndings;

use crate::consts::{
ALPINE_HIGHLIGHTING_APP, ALPINE_ORIG_CODE_ELM, ATTR_CODEFENCE_EXTRA, SYNTECT_CLASS_STYLE,
};
use crate::consts::{ALPINE_HIGHLIGHTING_APP, ALPINE_ORIG_CODE_ELM, ATTR_CODEFENCE_EXTRA};
use crate::types::CodeFenceOptions;

pub struct CssSyntectAdapter {
syntax_set: SyntaxSet,
}

#[allow(dead_code)]
impl CssSyntectAdapter {
pub fn new() -> Self {
Self {
syntax_set: two_face::syntax::extra_newlines(),
}
}

fn highlight_html(
&self,
code: &str,
syntax: &SyntaxReference,
) -> Result<String, syntect::Error> {
let mut html_generator = ClassedHTMLGenerator::new_with_class_style(
syntax,
&self.syntax_set,
SYNTECT_CLASS_STYLE,
);
for line in LinesWithEndings::from(code) {
html_generator.parse_html_for_line_which_includes_newline(line)?;
}
Ok(html_generator.finalize())
}
}

impl SyntaxHighlighterAdapter for CssSyntectAdapter {
fn write_highlighted(
&self,
output: &mut dyn Write,
lang: Option<&str>,
code: &str,
) -> std::io::Result<()> {
let fallback_syntax = "Plain Text";
let lang: &str = match lang {
Some(l) if !l.is_empty() => l,
_ => fallback_syntax,
};
let syntax = self
.syntax_set
.find_syntax_by_token(lang)
.unwrap_or_else(|| {
self.syntax_set
.find_syntax_by_first_line(code)
.unwrap_or_else(|| self.syntax_set.find_syntax_plain_text())
});

match self.highlight_html(code, syntax) {
Ok(highlighted_code) => output.write_all(highlighted_code.as_bytes()),
Err(_) => output.write_all(code.as_bytes()),
}
}

fn write_pre_tag(
&self,
output: &mut dyn Write,
attributes: HashMap<String, String>,
) -> std::io::Result<()> {
html::write_opening_tag(output, "pre", attributes)
}

fn write_code_tag(
&self,
output: &mut dyn Write,
attributes: HashMap<String, String>,
) -> std::io::Result<()> {
html::write_opening_tag(output, "code", attributes)
}
}

// A simple adapter that defers highlighting job to the client side
pub struct JSHighlightAdapter;
pub struct JsHighlightAdapter;

impl SyntaxHighlighterAdapter for JSHighlightAdapter {
impl SyntaxHighlighterAdapter for JsHighlightAdapter {
fn write_highlighted(
&self,
output: &mut dyn Write,
_lang: Option<&str>,
code: &str,
) -> std::io::Result<()> {
let code = escape_text(code);
output.write_all(code.as_bytes())
html::escape(output, code.as_bytes())
}

fn write_pre_tag(
&self,
output: &mut dyn Write,
mut attributes: HashMap<String, String>,
) -> std::io::Result<()> {
// Adding HTML classes which are needed by our AlpineJS app
let classname = " q-need-highlight not-prose p-0";
if let Some(class) = attributes.get_mut("class") {
class.push_str(classname)
Expand All @@ -126,9 +47,9 @@ impl SyntaxHighlighterAdapter for JSHighlightAdapter {
output: &mut dyn Write,
mut attributes: HashMap<String, String>,
) -> std::io::Result<()> {
// Adding HTML classes which are needed by our AlpineJS app
tracing::info!("Attributes for code: {:?}", attributes);
let mut class_names = vec!["q-code"];
let mut styles = vec![];
if let Some(info_string) = attributes.get(ATTR_CODEFENCE_EXTRA) {
tracing::info!("Attempt to parse: {}", info_string);
let codefence_opts: CodeFenceOptions = serde_json5::from_str(info_string.as_str())
Expand All @@ -137,21 +58,17 @@ impl SyntaxHighlighterAdapter for JSHighlightAdapter {
if codefence_opts.lines {
class_names.push("q-with-lineno")
}
styles.push(format!("--line-start={}", codefence_opts.start_line));
attributes.insert(
"data-start-line".to_string(),
format!("{}", codefence_opts.start_line),
);
};
let extra_class = format!(" {}", class_names.join(" "));
if let Some(class) = attributes.get_mut("class") {
class.push_str(&extra_class)
} else {
attributes.insert("class".to_string(), extra_class);
};
if let Some(style) = attributes.get("style") {
styles.extend(style.split(';').map(String::from));
}
if !styles.is_empty() {
let style_s = styles.join(" ");
attributes.insert("style".to_string(), style_s);
}
attributes.insert("x-ref".to_string(), ALPINE_ORIG_CODE_ELM.to_string());
html::write_opening_tag(output, "code", attributes)
}
Expand All @@ -172,7 +89,7 @@ pub fn markdown_to_html(markdown: &str) -> String {
render,
..Default::default()
};
let adapter = JSHighlightAdapter;
let adapter = JsHighlightAdapter;
let render = RenderPluginsBuilder::default()
.codefence_syntax_highlighter(Some(&adapter))
.build()
Expand Down
2 changes: 1 addition & 1 deletion static/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ article .entry-content.front h2 {

/* Code line numbering */
.q-need-highlight code.q-with-lineno {
counter-reset: step;
counter-reset: step calc(var(--start-line, 1) - 1);
counter-increment: step 0;
}

Expand Down

0 comments on commit 0e3cecd

Please sign in to comment.