typst: fix URL comment split and heading label highlighting
Build and Deploy Verso / deploy (push) Successful in 13m56s

- Add URL token (https://... / http://...) so '://' is never split into
  ':' + LineComment '//', preventing URLs from being styled as comments
- Stop headingTitleTokenizer before '<label>' patterns so labels at the
  end of headings get Label node styling instead of being consumed as
  heading title text
- Style URL nodes with t.url tag

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
claude
2026-06-09 17:38:32 +00:00
parent d7ca7b194d
commit f9fc0d9905
3 changed files with 24 additions and 1 deletions
@@ -77,6 +77,9 @@ export const TypstLanguage = LRLanguage.define({
'Strong/"*" Strong/StrongBody': t.strong,
'Emphasis/"_" Emphasis/EmphBody': t.emphasis,
// Bare URLs (https://... / http://...)
URL: t.url,
// Labels (<name>) and references (@name)
'Label/"<" Label/">" Label/LabelName': t.labelName,
'Ref/"@" Ref/RefName': t.labelName,
@@ -35,6 +35,8 @@ const DOT = 46 // .
const OPEN_PAREN = 40 // (
const COMMA = 44 // ,
const COLON = 58 // :
const OPEN_ANGLE = 60 // <
const CLOSE_ANGLE = 62 // >
const KEYWORDS = new Set([
'let', 'set', 'show', 'import', 'include',
@@ -87,6 +89,17 @@ export const headingTitleTokenizer = new ExternalTokenizer(
while (input.next !== -1 && input.next !== NEWLINE) {
if (input.next === SLASH &&
(input.peek(1) === SLASH || input.peek(1) === STAR)) break
// Stop before a trailing '<label>' so it is parsed as a Label node
// rather than being merged into the heading title text.
// Only stops when '<' is immediately followed by a valid label name and '>'.
if (input.next === OPEN_ANGLE) {
const ch = input.peek(1)
if (isAlpha(ch) || isDigit(ch) || ch === UNDERSCORE) {
let j = 2
while (isIdentTail(input.peek(j)) || input.peek(j) === DOT || input.peek(j) === COLON) j++
if (input.peek(j) === CLOSE_ANGLE) break
}
}
input.advance()
hasContent = true
}
@@ -27,6 +27,7 @@ item {
Label |
Ref |
Escape |
URL |
MarkupContent
}
@@ -218,6 +219,11 @@ Escape { "\\" EscapeChar }
("pt" | "mm" | "cm" | "in" | "em" | "rem" | "fr" | "deg" | "rad" | "%")?
}
// URL: bare https:// or http:// links in markup text. Matched as a single
// token so '://' is never split into ':' + LineComment '//…'. Stops at
// whitespace and angle brackets (labels use '<…>').
URL { ("https" | "http") "://" (![ \t\n<>])* }
// Regular markup: excludes all special-character starters plus whitespace
// (whitespace is handled by @skip). The '/' is excluded so that '//' and
// '/*' are not accidentally consumed as plain text.
@@ -241,7 +247,8 @@ Escape { "\\" EscapeChar }
// "[" > MarkupContent: ContentBlock callSuffix wins in merged code/markup states.
// CodeString > MarkupContent: '"' starts a string literal after a keyword.
// ":" > MarkupContent: keywordBody ':' wins over markup colon in code states.
@precedence { CodeBool EscapeChar CodeString "[" ":" "(" "." "]" "_" spaces MarkupContent }
// URL > MarkupContent: 'https://' / 'http://' wins over plain markup text.
@precedence { CodeBool EscapeChar CodeString URL "[" ":" "(" "." "]" "_" spaces MarkupContent }
}
@skip { spaces }