vertically aligned code comments

This commit is contained in:
りき萌 2025-08-30 13:13:29 +02:00
parent ee0a95974b
commit 408b984266
6 changed files with 83 additions and 12 deletions

View file

@ -2,6 +2,7 @@
//! Made concrete to avoid generic hell, with added treehouse-specific features.
use std::fmt::Write;
use std::mem;
use std::ops::Range;
use codespan_reporting::diagnostic::Diagnostic;
@ -42,6 +43,7 @@ impl Renderer<'_> {
renderer: self,
raw: Raw::None,
code_block: None,
code_block_text: String::new(),
img_alt_text: 0,
list_tightness: vec![],
not_first_line: false,
@ -88,6 +90,7 @@ struct Writer<'a> {
raw: Raw,
code_block: Option<CodeBlock<'a>>,
code_block_text: String,
img_alt_text: usize,
list_tightness: Vec<bool>,
not_first_line: bool,
@ -445,6 +448,17 @@ impl<'a> Writer<'a> {
Container::CodeBlock { language } => {
let code_block = self.code_block.take().unwrap();
let rendered =
if let Some(syntax) = self.renderer.config.syntaxes.get(*language) {
let mut rendered = String::new();
highlight(&mut rendered, syntax, &self.code_block_text);
self.code_block_text.clear();
rendered
} else {
mem::take(&mut self.code_block_text)
};
out.push_str(&rendered);
out.push_str(match &code_block.kind {
CodeBlockKind::PlainText | CodeBlockKind::SyntaxHighlight => {
"</code></pre>"
@ -507,11 +521,8 @@ impl<'a> Writer<'a> {
Event::Str(s) => match self.raw {
Raw::None if self.img_alt_text > 0 => write_attr(s, out),
Raw::None => {
let syntax = self.code_block.as_ref().and_then(|code_block| {
self.renderer.config.syntaxes.get(code_block.language)
});
if let Some(syntax) = syntax {
highlight(out, syntax, s);
if self.code_block.is_some() {
self.code_block_text.push_str(s);
} else {
write_text(s, out);
}

View file

@ -15,6 +15,8 @@ use std::{collections::HashMap, fmt::Write};
use serde::{Deserialize, Serialize};
use crate::html::highlight::tokenize::Token;
use self::compiled::CompiledSyntax;
use super::EscapeHtml;
@ -82,13 +84,59 @@ pub struct Keyword {
pub only_replaces: Option<String>,
}
pub fn highlight(out: &mut String, syntax: &CompiledSyntax, code: &str) {
let tokens = syntax.tokenize(code);
fn write_tokens(
out: &mut String,
syntax: &CompiledSyntax,
code: &str,
tokens: impl Iterator<Item = Token>,
) {
for token in tokens {
let str = &code[token.range.clone()];
out.push_str("<span class=\"");
_ = write!(out, "{}", EscapeHtml(&syntax.token_names[token.id]));
out.push_str("\">");
_ = write!(out, "{}", EscapeHtml(&code[token.range]));
_ = write!(out, "{}", EscapeHtml(str));
out.push_str("</span>");
}
}
pub fn highlight(out: &mut String, syntax: &CompiledSyntax, code: &str) {
let tokens = syntax.tokenize(code);
let mut line = vec![];
let mut in_columns = false;
for token in tokens {
let str = &code[token.range.clone()];
line.push(token);
if str.ends_with('\n') {
let line_comment = if line.last().is_some_and(|token| {
Some(token.id) == syntax.comment_token_id
&& code[token.range.clone()].ends_with('\n')
}) {
line.pop()
} else {
None
};
if let Some(line_comment) = line_comment {
if !in_columns {
out.push_str("<th-comment-columns>");
in_columns = true;
}
out.push_str("<span>");
write_tokens(out, syntax, code, line.drain(..));
out.push_str("</span>");
write_tokens(out, syntax, code, [line_comment].into_iter());
} else {
if in_columns {
out.push_str("</th-comment-columns>");
in_columns = false;
}
write_tokens(out, syntax, code, line.drain(..));
}
}
}
}

View file

@ -17,6 +17,9 @@ pub struct CompiledSyntax {
pub patterns: Vec<CompiledPattern>,
pub keywords: HashMap<String, CompiledKeyword>,
/// If there is a token named "comment", this is its ID.
pub comment_token_id: Option<TokenId>,
}
#[derive(Debug, Clone)]
@ -111,9 +114,12 @@ pub fn compile_syntax(syntax: &Syntax) -> CompiledSyntax {
})
.collect();
let comment_token_id = token_names.iter().position(|name| name == "comment");
CompiledSyntax {
token_names,
patterns,
keywords,
comment_token_id,
}
}

View file

@ -75,7 +75,7 @@ main.doc {
--recursive-wght: 500;
--recursive-mono: 0.5; /* You didn't expect a proportional font being used for code, did you. */
font-size: 95%;
tab-size: 3;
tab-size: 3ch;
}
&.monospaced code {
@ -160,7 +160,7 @@ main.doc {
& code {
--recursive-wght: 520;
font-size: 90%;
tab-size: 2;
tab-size: 2ch;
}
}
}

View file

@ -1198,6 +1198,11 @@ th-literate-program[data-mode="output"] {
}
}
.th-syntax-highlighting th-comment-columns {
display: grid;
grid-template-columns: repeat(2, max-content);
}
.th-syntax-highlighting {
& .export {
text-decoration: underline dotted;

View file

@ -1,6 +1,6 @@
{
"patterns": [
{ "regex": "\\/\\/.*", "is": "comment" },
{ "regex": "\\/\\/.*\\n?", "is": "comment" },
{
"regex": "\\/\\*.*?\\*\\/",
"flags": ["dotMatchesNewline"],
@ -37,7 +37,8 @@
}
},
{ "regex": "[a-zA-Z_][a-zA-Z0-9_]*", "is": "identifier" },
{ "regex": "'[a-zA-Z_][a-zA-Z0-9_]*", "is": "literal" }
{ "regex": "'[a-zA-Z_][a-zA-Z0-9_]*", "is": "literal" },
{ "regex": "\n", "is": "" }
],
"keywords": {
"_": { "into": "keyword1" },