]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
runtime(marko): improve syntax highlighting
authorBrian Carbone <brian@briancarbone.com>
Thu, 30 Jul 2026 18:32:16 +0000 (18:32 +0000)
committerChristian Brabandt <cb@256bit.org>
Thu, 30 Jul 2026 18:32:16 +0000 (18:32 +0000)
closes: #20874

Signed-off-by: Brian Carbone <brian@briancarbone.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
runtime/syntax/marko.vim
runtime/syntax/testdir/dumps/marko_00.dump
runtime/syntax/testdir/dumps/marko_01.dump
runtime/syntax/testdir/dumps/marko_02.dump
runtime/syntax/testdir/dumps/marko_concise_00.dump
runtime/syntax/testdir/dumps/marko_concise_01.dump
runtime/syntax/testdir/dumps/marko_concise_02.dump
runtime/syntax/testdir/input/marko.marko
runtime/syntax/testdir/input/marko_concise.marko

index be589534c39a0e572f5953d385265f9967e020b6..96d24970a825d1d7302c848b109a4fcbbd9df3f6 100644 (file)
@@ -1,21 +1,18 @@
 " Vim syntax file
 " Language:    Marko
-" Maintainer:  Brian Carbone <cordial.coffee1365@fastmail.com>
+" Maintainer:  Brian Carbone <brian@briancarbone.com>
 " URL:         https://markojs.com
 " Filenames:   *.marko
-" Last Change: 2026 Jul 28
+" Last Change: 2026 Jul 29
 
-" Marko interleaves HTML-like tags, TypeScript expressions and CSS style
-" blocks, in two modes: an HTML-like mode and an indentation-based concise
-" mode.  Mode is tracked structurally: the body of an html-mode element is
-" an html-mode region (bare text stays plain there), while everywhere else
-" is concise context, where a line-start name is a tag:
+" Marko interleaves HTML-like tags, TypeScript expressions and CSS in two
+" modes.  Mode is tracked structurally: element bodies and "--" text blocks
+" are html mode (bare text stays plain), everywhere else is concise, where
+" a line-start name is a tag:
 "   <p>if you want, stop by</p>      body text, plain
 "   my-tag size="large"              concise tag, highlighted
-"
-" Same-position priority goes to the item defined last, so definition order
-" is deliberate throughout; the load-bearing orderings are called out in
-" the comments below.
+" Same-position priority goes to the item defined last; definition order
+" is deliberate throughout.
 
 if !exists("main_syntax")
   if exists("b:current_syntax")
@@ -27,11 +24,11 @@ endif
 let s:cpo_save = &cpo
 set cpo&vim
 
+" Top level is mostly markup; regions holding prose opt into @Spell.
 syn spell toplevel
 
-" TypeScript for expressions and statement tags, CSS for style blocks.
-" Both includes mutate shared state ("syn case", "syn iskeyword", sync
-" rules, 'iskeyword'), all of it restored or restated afterwards.
+" Both includes mutate shared state ("syn case", "syn iskeyword", sync,
+" 'iskeyword'), restored or restated afterwards.
 let s:iskeyword_save = &l:iskeyword
 syn include @markoTS syntax/typescript.vim
 unlet! b:current_syntax
@@ -41,107 +38,190 @@ let &l:iskeyword = s:iskeyword_save
 unlet s:iskeyword_save
 syn case match
 
-" typescript.vim (yats) attaches type arguments via nextgroup after every
-" identifier, so an unspaced "<" in any expression opens a region hunting a
-" ">" that may never come:
+" A "\%#=1" prefix pins a pattern to the backtracking regexp engine: the
+" default NFA engine is much slower on this file's characteristic shapes
+" (a lookbehind or bounded repeat in front of an alternation).  Both
+" engines answer alike; only patterns measured faster carry the prefix.
+
+" yats attaches type arguments via nextgroup after every identifier, so an
+" unspaced "<" in any expression would hunt a ">" that may never come:
 "   <div count=i<3>       would swallow the rest of the file
-" Its block comment carries "extend", which would let one stray "/*" escape
-" every containing region below.  Both are redefined bounded; the cost is
-" generic-call coloring inside script blocks.
+" That group stays cleared.  The comment region replaces yats' block
+" comment, whose "extend" would escape every containing region below.
 silent! syn clear typescriptTypeArguments
 silent! syn clear typescriptComment
 syn region typescriptComment contained start="/\*" end="\*/" contains=@Spell,typescriptCommentTodo
 
-" Everything an embedded TypeScript expression may contain.  The bracket
-" pairs keep an outer region open across nested delimiters and shield the
-" spaces inside groupings from attribute-value termination:
+" yats claims call arguments, bodies, indexing, arrays, templates and
+" parenthesized expressions via nextgroup, so the markoTS* pairs below
+" never see those delimiters.  Each row is yats' own definition plus
+" "contained extend", the one difference from upstream: a multiline group
+" keeps its containing attribute value open, and a bracketed one shields
+" the spaces inside, as the parser's group tracking does:
+"   count=Math.max(0, tiers.findIndex((t) => t.featured))
+"   pair=raw as [string, string]
+for s:def in [
+      \ 'typescriptBlock matchgroup=typescriptBraces start=/{/ end=/}/ contains=@typescriptStatement,@typescriptComments fold',
+      \ 'typescriptFuncCallArg matchgroup=typescriptParens start=/(/ end=/)/ contains=@typescriptValue,@typescriptComments,typescriptCastKeyword nextgroup=@typescriptSymbols,typescriptDotNotation skipwhite skipempty skipnl',
+      \ 'typescriptArray matchgroup=typescriptBraces start=/\[/ end=/]/ contains=@typescriptValue,@typescriptComments,typescriptCastKeyword nextgroup=@typescriptSymbols,typescriptDotNotation skipwhite skipempty fold',
+      \ 'typescriptTemplate start=/`/ skip=/\\\\\|\\`\|\n/ end=/`\|$/ contains=typescriptTemplateSubstitution,typescriptSpecial,@Spell nextgroup=@typescriptSymbols skipwhite skipempty',
+      \ 'typescriptObjectLiteral matchgroup=typescriptBraces start=/{/ end=/}/ contains=@typescriptComments,typescriptObjectLabel,typescriptStringProperty,typescriptComputedPropertyName,typescriptObjectAsyncKeyword,typescriptTernary,typescriptCastKeyword fold',
+      \ 'typescriptObjectType matchgroup=typescriptBraces start=/{/ end=/}/ contains=@typescriptTypeMember,typescriptEndColons,@typescriptComments,typescriptAccessibilityModifier,typescriptReadonlyModifier nextgroup=@typescriptTypeOperator skipwhite skipnl fold',
+      \ 'typescriptParenExp matchgroup=typescriptParens start=/(/ end=/)/ contains=@typescriptComments,@typescriptValue,typescriptCastKeyword nextgroup=@typescriptSymbols skipwhite skipempty',
+      \ 'typescriptIndexExpr matchgroup=typescriptProperty start=/\[/ end=/]/ contains=@typescriptValue,typescriptCastKeyword nextgroup=@typescriptSymbols,typescriptDotNotation,typescriptFuncCallArg skipwhite skipempty',
+      \ 'typescriptCall matchgroup=typescriptParens start=/(/ end=/)/ contains=typescriptDecorator,@typescriptParameterList,@typescriptComments nextgroup=typescriptTypeAnnotation,typescriptBlock skipwhite skipnl',
+      \ 'typescriptTupleType matchgroup=typescriptBraces start=/\[/ end=/\]/ contains=@typescriptType,@typescriptComments,typescriptRestOrSpread,typescriptTupleLable skipwhite',
+      \ 'typescriptParenthesizedType matchgroup=typescriptParens start=/(/ end=/)/ contains=@typescriptType nextgroup=@typescriptTypeOperator skipwhite skipempty fold',
+      \ 'typescriptFuncType matchgroup=typescriptParens start=/(\(\k\+:\|)\)\@=/ end=/)\s*=>/me=e-2 contains=@typescriptParameterList nextgroup=typescriptFuncTypeArrow skipwhite skipnl oneline',
+      \ 'typescriptStringLiteralType start=/\z(["'']\)/ skip=/\\\\\|\\\z1\|\\\n/ end=/\z1\|$/ nextgroup=typescriptUnion skipwhite skipempty',
+      \ 'typescriptTemplateLiteralType start=/`/ skip=/\\\\\|\\`\|\n/ end=/`\|$/ contains=typescriptTemplateSubstitutionType nextgroup=typescriptTypeOperator skipwhite skipempty',
+      \ ]
+  silent! exe 'syn clear ' . matchstr(s:def, '^\S\+')
+  exe 'syn region ' . s:def . ' contained extend'
+endfor
+unlet s:def
+" yats' own arrow-function definitions, restated to stay last-defined at
+" a "(": params eaten as a parenthesized expression turn the arrow's "{"
+" body into an object literal, whose ternary region has no closing bound:
+"   f((x) => { value = x ?? other; })
+syntax match   typescriptArrowFuncDef          contained /\K\k*\s*=>/
+      \ contains=typescriptArrowFuncArg,typescriptArrowFunc
+      \ nextgroup=@typescriptExpression,typescriptBlock
+      \ skipwhite skipempty
+syntax match   typescriptArrowFuncDef          contained /(\%(\_[^()]\+\|(\_[^()]*)\)*)\_s*=>/
+      \ contains=typescriptArrowFuncArg,typescriptArrowFunc,@typescriptCallSignature
+      \ nextgroup=@typescriptExpression,typescriptBlock
+      \ skipwhite skipempty
+syntax region  typescriptArrowFuncDef          contained start=/(\%(\_[^()]\+\|(\_[^()]*)\)*):/ matchgroup=typescriptArrowFunc end=/=>/
+      \ contains=typescriptArrowFuncArg,typescriptTypeAnnotation,@typescriptCallSignature
+      \ nextgroup=@typescriptExpression,typescriptBlock
+      \ skipwhite skipempty keepend
+
+" The bracket pairs keep an outer region open across nested delimiters
+" and shield the spaces inside groupings from value termination:
 "   a={ b: { c: 1 } } d=2
-" Without the matchgroup each region would re-match at its own start
-" character through its own contains and consume the closing delimiter.
-" markoTSDelim is deliberately unlinked: the delimiters render as plain
-" text, like brackets in a TypeScript buffer.
+" markoTSDelim is deliberately unlinked: delimiters render plain.
 syn cluster markoExpr contains=@markoTS,markoTSBraces,markoTSParens,markoTSBrackets
-syn region markoTSBraces contained transparent matchgroup=markoTSDelim
-      \ start="{" end="}"
-      \ contains=@markoExpr extend
-syn region markoTSParens contained transparent matchgroup=markoTSDelim
-      \ start="(" end=")"
-      \ contains=@markoExpr extend
-syn region markoTSBrackets contained transparent matchgroup=markoTSDelim
-      \ start="\[" end="]"
-      \ contains=@markoExpr extend
-
-" Comments are recognized where the parser allows text to start; anchoring
-" to the line start keeps "//" inside URLs as text:
-"   <!-- note -->    // note    /* note */    https://example.com
+for [s:pair, s:open, s:close] in [["Braces", '{', '}'], ["Parens", '(', ')'], ["Brackets", '\[', ']']]
+  exe 'syn region markoTS' . s:pair . ' contained transparent matchgroup=markoTSDelim'
+        \ . ' start="' . s:open . '" end="' . s:close . '"'
+        \ . ' contains=@markoExpr extend'
+endfor
+unlet s:pair s:open s:close
+
+" html-mode "//" and "/* */" comments open wherever whitespace precedes
+" the slash (a URL's "//" follows ":", so it stays plain); concise context
+" takes only the line-anchored forms, defined second so they win at a line
+" start and keep their fold and indent handling:
+"   // note    <p>done // for now</p>    <a href="https://x.co">plain</a>
+" No comment item may take "display": a match skipped during offscreen
+" state computation exposes its text, and a "<tag" inside a comment would
+" open a body whose highlighting depends on scroll position.
+syn match markoHtmlComment "\%(^\|\s\)\@1<=//.*$" contains=@Spell
+syn region markoHtmlComment start="\%(^\|\s\)\@1<=/\*" end="\*/" contains=@Spell fold
 syn region markoComment start="<!--" end="-->" contains=@Spell fold
-syn match markoComment "^\s*//.*$" contains=@Spell display
+syn match markoComment "^\s*//.*$" contains=@Spell
 syn region markoComment start="^\s*/\*" end="\*/" contains=@Spell fold
-syn region markoTagComment contained start="/\*" end="\*/"
+syn region markoTagComment contained start="/\*" end="\*/" extend
 syn match markoTagComment contained "//.*"
 
+" Declarations before CDATA: both open at "<!" and the later wins:
+"   <!doctype html>    <?xml version="1.0"?>    <![CDATA[ raw ]]>
+syn region markoDeclaration start="<!\(--\)\@!" end=">"
+syn region markoDeclaration start="<?" end=">"
 syn region markoCData start="<!\[CDATA\[" end="]]>"
-" Declarations; "<?...>" may close with ">" or "?>":
-"   <!doctype html>    <?xml version="1.0"?>
-syn region markoDeclaration start="<!\%(--\|\[CDATA\)\@!" end=">"
-syn region markoDeclaration start="<?" end="?\=>"
 
-" Placeholders interpolate into text, and backslashes escape pairwise:
+" Placeholders interpolate into text; backslashes escape pairwise:
 "   ${user.name}    $!{rawHtml}    \${literal}    \\${live}
-" The containedin lets them apply inside style rules and property values.
-" markoEscape must never take "display": it exists to suppress
-" markoPlaceholder, so skipping it during state computation would change
-" what matches.
-syn match markoEscape "\%(\\\\\)\+\ze\$!\={"
-syn match markoEscape "\%(\\\\\)*\\\$!\={"
+" markoEscape must never take "display" (it exists to suppress
+" markoPlaceholder); it consumes an escaped "$", or stops before a "${"
+" an even backslash run leaves live.
+syn match markoEscape "\%(\\\\\)*\%(\\\$!\={\|\\\\\ze\$!\={\)"
 syn region markoPlaceholder matchgroup=markoPlaceholderDelim
       \ start="\$!\={" end="}"
-      \ contains=@markoExpr containedin=cssDefinition,cssAttrRegion extend
+      \ contains=@markoExpr extend
+" The no-"extend" variant for keepend parsed-text bodies and the CSS
+" contexts: an unterminated "\${" must stop at the container's close.
+syn region markoPlaceholderText contained matchgroup=markoPlaceholderDelim
+      \ start="\$!\={" end="}"
+      \ contains=@markoExpr
+      \ containedin=cssDefinition,cssAttrRegion,cssStringQ,cssStringQQ
 
 syn match markoEntity "&\%(#\d\+\|#[xX]\x\+\|\w\+\);" display
 
-" Scriptlets; the whitespace after "$" is required, so a line-start
-" placeholder is not a scriptlet:
-"   $ count += 1
-"   $ { let total = 0; }
-"   ${count}                     placeholder
+" Scriptlets; the whitespace after "$" keeps line-start placeholders out.
+" No keepend: an open bracket continues, as the parser's group tracking
+" does.  The "$ {" block form is second, so it wins where both start:
+"   $ count += 1        $ { let total = 0; }
+"   $ const rows = [
+"       "a",
+"     ]
 syn region markoScriptlet matchgroup=markoScriptletDelim
-      \ start="^\s*\$\s\+{\@!" end="$"
-      \ contains=@markoExpr keepend
+      \ start="^\s*\$\s\+" end="$"
+      \ contains=@markoExpr
 syn region markoScriptlet matchgroup=markoScriptletDelim
       \ start="^\s*\$\s\+{" end="};\="
       \ contains=@markoExpr
 
-" "--" marks one line of text in concise mode:
+" "--" marks the rest of the line as text; at end of line it opens an
+" indented block instead:
 "   p -- some text
-syn match markoBlockDelim "\%(^\|\s\)\@1<=--\+\ze\%(\s\|$\)" display
-
-" Attributes.  An unquoted value ends at whitespace (quoted strings and
-" bracket pairs shield theirs), does not start at the "=" of an arrow, and
-" continues across " => " so arrow values stay whole:
-"   size="large"    total:=sum    ...spread    onClick=(e) => handle(e)
-" The attr name must not match the "${" of a dynamic tag name.
-syn match markoAttrName contained "\%(\$!\={\)\@![[:alnum:]_$@][-[:alnum:]_$.]*" display
+"   p --
+"     A block of text, <b>html mode</b>.
+" The inline form is a one-line html region, so later dash runs on the
+" line sit inside it and are never delimiters (the parser's rule), and
+" keepend bounds what the line opens.  Both forms stand down on a line
+" whose first non-blank is "<" (html content, where dashes are text).
+" They overlap at a trailing "--": the nextgroup form is second and wins,
+" and must not take "display".  A bare "--" line belongs to the
+" later-defined markoTextDelimBlock.
+syn region markoInlineText oneline keepend matchgroup=markoBlockDelim
+      \ start="\%#=1\%(^\s*<.\{-}\)\@200<!\%(^\|\s\)\@1<=--\+\ze\s" end="$"
+      \ contains=@markoHtmlContent
+syn match markoBlockDelim
+      \ "\%#=1\%(^\s*<.\{-}\)\@200<!\%(^\|\s\)\@1<=--\+\s*$"
+      \ nextgroup=markoTextIndent skipnl
+" The shared block body: its indent is the first following line's leading
+" whitespace run, blank lines included (the parser's rule); an empty run
+" leaves only the column-0 delimiter close, the dedent end being a
+" negative lookahead on an empty match.
+let s:indent_block = ' start="^\z(\s*\)\ze\%(\S\|$\)"'
+      \ . ' matchgroup=markoBlockDelim end="^\z1--\+\s*$"'
+      \ . ' matchgroup=NONE end="^\%(\z1\)\@!\ze\s*\S"'
+exe 'syn region markoTextIndent contained' . s:indent_block
+      \ . ' contains=@markoHtmlContent'
+
+" A name may hold ":" (not the ":=" of a bound value) and any keyword
+" character, and must not match a dynamic tag name's "${":
+"   size="large"    total:=sum    xml:lang="es"    <spinner café="ok"/>
+" An unquoted value ends at whitespace or the mode's terminators, except
+" that the parser continues across whitespace flanked by an operator:
+"   if=tag === "select"    content=a.desc ?? b.desc    onClick=(e) => go(e)
+syn match markoAttrName contained "\%#=1\%(\$!\={\)\@![[:alnum:]_$@]\%(\k\|[$.]\|:=\@!\)*" display
 syn match markoSpread contained "\.\.\." display
-syn region markoAttrValue contained matchgroup=markoOperator
-      \ start=":\==>\@!\s*" end="\ze\%(\%(=>\)\@2<!\s\%(\s*=>\)\@!\|=\@1<!>\|/>\)" end="$"
-      \ contains=@markoExpr
 syn region markoString contained start=+\z(["']\)+ skip=+\\\z1+ end=+\z1+ contains=@Spell
 
-" Shorthands after the tag name:
-"   <div.card#main>
-syn match markoShorthandId contained "#[-[:alnum:]_$]\+" display
-syn match markoShorthandClass contained "\.[-[:alnum:]_$]\+" display
-
-" Tag variables, arguments, |parameters|, <type args> and attribute method
-" shorthand bodies:
+" Regions so an interpolation can sit inside one (no keepend: the
+" placeholder carries the region past its "}"):
+"   <div.card#main>    <div#item-${id}>
+syn region markoShorthandId contained oneline
+      \ start="#[-[:alnum:]_$]\@=" end="[-[:alnum:]_$]\@!"
+      \ contains=markoPlaceholder
+syn region markoShorthandClass contained oneline
+      \ start="\.[-[:alnum:]_$]\@=" end="[-[:alnum:]_$]\@!"
+      \ contains=markoPlaceholder
+
+" Tag variables, arguments, |parameters|, <type args> and method bodies:
 "   <let/count=1/>    <for|item, i| of=list>    <while(cond)>
 "   <generic-list<string>>    <button onClick(e) { handle(e) }>
-" Params and type args must hug the preceding name (the "\k" follows the
-" "syn iskeyword" set below, which is what admits hyphenated names) so a
-" "|" or "<" inside an expression cannot open them, and both are oneline
-" so an unmatched delimiter cannot run past its line.
-syn match markoTagVar contained "/\%([[:alnum:]_$]\+\|\ze\s*[{[]\)" display
+" Params and type args hug the preceding name, so a "|" or "<" inside an
+" expression cannot open them; params and the plain type-args form are
+" oneline so an unmatched delimiter stops at its line.
+syn match markoTagVar contained "/\%([[:alnum:]_$]\+\|\ze\s*[{[]\)"
+      \ nextgroup=markoTagVarDestructure skipwhite display
+syn region markoTagVarDestructure contained matchgroup=markoTagDelim
+      \ start="\[" end="]"
+      \ contains=@markoExpr extend
 syn region markoArgs contained matchgroup=markoTagDelim
       \ start="(" end=")"
       \ contains=@markoExpr extend
@@ -150,48 +230,122 @@ syn region markoParams contained oneline matchgroup=markoTagDelim
       \ contains=@markoExpr
 syn region markoTypeArgs contained oneline matchgroup=markoTagDelim
       \ start="\%(\k\|\$\)\@4<=<" end=">"
-      \ contains=@markoExpr,markoTypeArgs
+      \ contains=@typescriptType,markoTypeArgs
+" The braced form spans lines: the object type bounds it, and the parser
+" reads "<{" after a name in tag scope as type args too.
+syn region markoTypeArgs contained matchgroup=markoTagDelim
+      \ start="\%(\k\|\$\)\@4<=<\ze\s*{" end=">"
+      \ contains=@typescriptType,markoTypeArgs extend
 syn region markoMethodBody contained matchgroup=markoTagDelim
       \ start="{" end="}"
       \ contains=@markoExpr extend
 
+" The three value variants differ only in terminators: html ends at ","
+" "/>" or the tag's ">"; concise at "," ";" or whitespace-led "--" (">"
+" is a comparison there); grouped at "," or "]".  Defined after markoArgs
+" so a "...spread" value wins at "(".  keepend bounds foreign TypeScript
+" at the value's end; the markoTS* and yats groups carry extend and punch
+" through.  A value survives its line end when either side of the break
+" is an operator in html mode (the lookahead crosses blank lines), only
+" a trailing one in concise:
+"   <a href=translated
+"        ? localizeUrl($global.url.href, { locale }).href
+"        : baseHref>
+" s:val_nocont, the trailing set, mirrors the parser: "=>" and ">>"
+" continue but a bare ">" ends (operator only outside type position,
+" untrackable, and honoring it swallowed "</div>"-style line ends;
+" mid-line "x > 1" continues via the break end's own guard); single
+" "+"/"-" continue, postfix "i++" ends; unary keywords count unless a
+" word character precedes ("a-new Date()" stays whole), binary and type
+" keywords only after whitespace ("class=step-in" ends); a trailing "."
+" continues when a word character follows, across blank lines.  A "/"
+" counts only when spaces flank it (ahead of the break it is ambiguous
+" with "/>"), and the break demands a non-space before the space, so a
+" continuation line's indent cannot end the value it continues:
+"   ratio=total / count max=10
+"   total=base +
+"     tax
+let s:val_nocont = '[&*^:=!<%|?~]\@1<!\%(=>\|>>\)\@2<!\%(+\@1<!+\|-\@1<!-\)\@1<!\%(\s/\)\@2<!'
+      \ . '\%([0-9A-Za-z_$.]\@1<!\%(async\|await\|class\|function\|new\|typeof\|void\)\|\s\%(as\|extends\|instanceof\|in\|satisfies\|asserts\|infer\|is\|keyof\|readonly\|unique\)\)\@12<!'
+      \ . '\%(\.\@1<=\_s*[0-9A-Za-z_$]\)\@!'
+" Cheap zero-width lead tests keep the wide lookbehinds off most columns.
+" s:val_last matches after the line's last non-blank, so trailing spaces
+" and blank lines never host a value end.
+let s:val_last = '\%#=1\%(\s*$\)\@=' . s:val_nocont . '\S\@1<='
+let s:val_break = '\%#=1\s\@=>\@1<!' . s:val_nocont . '\S\@1<=\s'
+let s:val_op = '[&*^!<%|~+?:={(]\|\.\%(\_s*\w\)\@=\|\<\%(as\|extends\|instanceof\|in\|satisfies\)\%(\_s\+[^:,=/>; \t]\)\@='
+let s:val_head = ' contained matchgroup=markoOperator start=":\==>\@!\s*"'
+      \ . ' matchgroup=NONE start="\.\@1<=\%(\.\.\.\)\@3<="'
+let s:val_tail = ' contains=@markoExpr keepend extend'
+exe 'syn region markoAttrValue' . s:val_head
+      \ . ' end="\ze," end="\ze/>" end="=\@1<!\ze>"'
+      \ . ' end="' . s:val_last . '\ze\s*\n\%(\_s*\%(' . s:val_op . '\|-\|/[/*>]\@!\)\)\@!"'
+      \ . ' end="' . s:val_break . '\%(\_s*\%(' . s:val_op . '\|-\|/[/*>]\@!\)\)\@!"' . s:val_tail
+exe 'syn region markoAttrValueConcise' . s:val_head
+      \ . ' end="\ze[,;]" end="' . s:val_last . '"'
+      \ . ' end="' . s:val_break . '\%(\s*\%(' . s:val_op . '\|--\@!\|/[/*]\@!\|>\)\)\@!"' . s:val_tail
+exe 'syn region markoAttrValueInGroup' . s:val_head
+      \ . ' end="\ze[,\]]" end="' . s:val_last . '"'
+      \ . ' end="' . s:val_break . '\%(\s*\%(' . s:val_op . '\|-\|/[/*]\@!\|>\)\)\@!"' . s:val_tail
+unlet s:val_nocont s:val_last s:val_break s:val_op s:val_head s:val_tail
+
 syn cluster markoTagStuff contains=markoTagVar,markoShorthandId,markoShorthandClass,
-      \markoAttrName,markoAttrValue,markoString,markoArgs,markoParams,markoTypeArgs,
+      \markoAttrName,markoString,markoArgs,markoParams,markoTypeArgs,
       \markoMethodBody,markoSpread,markoTagComment,markoPlaceholder
-syn cluster markoTagNames contains=markoTagName,markoAttrTagName,markoTagNameConditional,
-      \markoTagNameRepeat,markoTagNameException,markoTagNameStatement,markoTagNameBuiltin
-
-" Concise attribute groups may span lines:
-"   define [
-"     size = "large"
-"   ]
+" The name groups themselves are defined near the end of the file; a
+" cluster may name them before they exist.
+syn cluster markoTagNames contains=markoTagName,markoTagNameTail,markoAttrTagName,
+      \markoTagNameConditional,markoTagNameRepeat,markoTagNameException,
+      \markoTagNameStatement,markoTagNameBuiltin
+
+" Attribute groups may span lines.  The lookbehind keeps a spread's array
+" literal out; that "[" opens a value, not a group:
+"   define [size = "large"]    my-tag ...[1, 2]
 syn region markoAttrGroup contained matchgroup=markoTagDelim
-      \ start="\[" end="]"
-      \ contains=@markoTagStuff extend
+      \ start="\%(\.\.\.\)\@3<!\[" end="]"
+      \ contains=@markoTagStuff,markoAttrValueInGroup extend
 
-" One name list per highlight class, shared by the concise regions here
-" and the tag-name matches below.
+" One list per highlight class.  The builtin list is factored by first
+" letter; no name is a prefix of another, so grouping changes nothing.
 let s:tag_name_classes = [
       \ ["Conditional", 'if\|else-if\|else'],
       \ ["Repeat", 'for\|while'],
       \ ["Exception", 'try'],
       \ ["Statement", 'await\|return'],
-      \ ["Builtin", 'let\|const\|effect\|lifecycle\|id\|log\|debug\|context\|define\|macro\|html-comment'],
+      \ ["Builtin", 'l\%(et\|ifecycle\|og\)\|c\%(onst\|ontext\)\|d\%(ebug\|efine\)\|s\%(tyle\|cript\)\|effect\|id\|macro\|textarea\|html-\%(comment\|style\|script\)'],
       \ ]
 
-" Concise mode: a line-start name is a tag.  The generic rule is defined
-" first, so the builtin classes, "@" attribute tags and the statement tags
-" below all win over it for their names.  keepend bounds each region to
-" its line; brackets, method bodies and attr groups carry extend, which
-" deliberately punches through for multiline constructs.
-let s:concise_tag_end = ' matchgroup=markoTagDelim end=";" end="$" end="\s\zs\ze--\%(\s\|$\)"'
-      \ . ' contains=@markoTagStuff,markoAttrGroup keepend'
+" Concise mode: a line-start name is a tag; so are "${tag}", ".class" and
+" "#id".  The generic rule is first, so class names, "@" tags and the
+" statement tags win over it.  A tag ends at its line unless a comma
+" trails or leads the continuation; comments and blank lines may sit
+" between, and a blank line never hosts the end.  Brackets, method bodies
+" and attr groups carry extend, punching through keepend for multiline
+" constructs:
+"   link
+"     ,rel="canonical"
+"     // a comment between continued lines
+"     ,href=baseHref
+"   input type="text",
+"     value=name
+" Whitespace-led dashes end the tag and start text.  That end begins at
+" the first dash with the space as lookbehind: the space is also the
+" value's own break-end, and an end starting inside the value would be
+" suppressed by its extend:
+"   p ---- some text
+let s:concise_tag_end = ' matchgroup=markoTagDelim end=";"'
+      \ . ' end="\%#=1\%(,\s*\)\@9<!\%(^\s\{0,60}\)\@61<!\ze\n\%(\%(\s*\%(//.*\|/\*\_.\{-,200}\*/\s*\)\=\n\)\{0,20}\s*,\)\@!"'
+      \ . ' end="\s\@1<=\ze-\{2,}\%(\s\|$\)"'
+      \ . ' contains=@markoTagStuff,markoAttrGroup,markoAttrValueConcise,markoTagNameTail keepend'
 exe 'syn region markoConciseTag matchgroup=markoTagName'
-      \ . ' start="^\s*\zs[[:alnum:]][-[:alnum:]_$:]*\%([-[:alnum:]_$:]\)\@!"'
+      \ . ' start="^\s*\zs\%(-\@!\k\|\$[{ \t]\@!\)\%(\k\|:\|\${\@!\)*"'
+      \ . s:concise_tag_end
+exe 'syn region markoConciseTag'
+      \ . ' start="^\s*\ze\%(\$!\={\|[.#][-[:alnum:]_$]\)"'
       \ . s:concise_tag_end
 for [s:class_suffix, s:name_pattern] in s:tag_name_classes
   exe 'syn region markoConciseTag matchgroup=markoTagName' . s:class_suffix
-        \ . ' start="^\s*\zs\%(' . s:name_pattern . '\)\%([-[:alnum:]_$:]\)\@!"'
+        \ . ' start="^\s*\zs\%(' . s:name_pattern . '\)\%(\k\|\$\|:=\@!\)\@!"'
         \ . s:concise_tag_end
 endfor
 exe 'syn region markoConciseTag matchgroup=markoAttrTagName'
@@ -199,151 +353,190 @@ exe 'syn region markoConciseTag matchgroup=markoAttrTagName'
       \ . s:concise_tag_end
 unlet s:concise_tag_end
 
-" Statement tags contain one TypeScript statement.  static/server/client
-" are Marko keywords; import/export/class take their color from the
-" TypeScript include.  A statement may span lines through an open brace or
-" class body, so an unterminated "/*" runs on, as it would in a TypeScript
-" buffer.  Defined after the concise regions so these names win there.
+" One TypeScript statement each.  static/server/client are Marko keywords;
+" import/export/class take the include's colors.  Defined after the
+" concise regions so these names win there.
 syn region markoStatement matchgroup=markoStatementKeyword
       \ start="^\s*\zs\%(static\|server\|client\)\>"
-      \ end="$" contains=@markoExpr
-syn region markoStatement
-      \ start="^\s*\ze\%(import\|export\|class\)\>"
+      \ matchgroup=NONE start="^\s*\ze\%(import\|export\|class\)\>"
       \ end="$" contains=@markoExpr
 
 " A tag name may be empty (<.card>), dynamic (<${tag}>) or an attribute
-" tag (<@body>); the ">" of an arrow does not end the tag.
+" tag (<@body>); an arrow's ">" does not end the tag.
 syn region markoTag matchgroup=markoTagDelim
-      \ start="<\ze[^/ \t!?<>=]" end="/>" end="=\@1<!>"
-      \ contains=@markoTagStuff,@markoTagNames
+      \ start="<\ze[^/ \t!?<>=]" end="/>" end="\%#=1=\@1<!>"
+      \ contains=@markoTagStuff,@markoTagNames,markoAttrValue
+" Recovers a close tag no body end consumed, eg a stray one in concise
+" context.
 syn region markoCloseTag matchgroup=markoTagDelim
       \ start="</" end=">"
       \ contains=@markoTagNames,markoPlaceholder
 
-" The name of a close tag whose "</" was consumed by an element body's end
-" match below, plus its trailing ">".
-syn match markoCloseName "\%(</\)\@2<=@\=\%(\k\|[$:]\)\+\s*" nextgroup=markoCloseGt display
+" The name of a close tag whose "</" a body end consumed, plus its ">".
+" It stops before "${", so a dynamic close name's placeholder colors:
+"   </${tag}>
+syn match markoCloseName
+      \ "/\@1<=\%(</\)\@2<=\%(\k\|:\|\$\%(!\={\)\@!\)\+\s*"
+      \ nextgroup=markoCloseGt,markoClosePlaceholder display
 syn match markoCloseGt contained ">" display
-
-" Everything html-mode content may contain.  Statement tags and concise
-" tags are absent: bare text inside an element body is prose.
+" A dynamic close name's placeholder, chained so the closing ">" keeps
+" its color: </${tag}> via the body's nextgroup, </h${size}> via the
+" close name's.
+syn region markoClosePlaceholder contained matchgroup=markoPlaceholderDelim
+      \ start="\$!\={" end="}"
+      \ contains=@markoExpr
+      \ nextgroup=markoCloseGt,markoCloseTail
+syn match markoCloseTail contained "\%(\k\|:\)\+\s*"
+      \ nextgroup=markoCloseGt,markoClosePlaceholder display
+" The anonymous form (</>): a body end consumed the "</" and there is no
+" close name to carry the nextgroup, so the ">" takes its color directly.
+syn match markoCloseAnon "\%(</\)\@2<=>" display
+
+" Statement and concise tags are absent: bare text in a body is prose.
+" The close-name refinements keep </if> Conditional at any depth; generic
+" markoTagName stays out or it would outrank markoCloseName.
 syn cluster markoHtmlContent contains=markoTag,markoHtmlBody,markoDynamicBody,
       \markoStyle,markoScript,markoTextareaTag,markoHtmlCommentTag,
-      \markoComment,markoCData,markoDeclaration,markoEscape,markoPlaceholder,
-      \markoEntity,markoScriptlet,markoCloseName
-
-" Whether "<name" begins an element body turns on how its open tag ends:
-" an atomic (no-backtrack) run over quoted strings, (args), <type args>,
-" "=>" and spaced comparisons finds the structural ">", and a "/>" there
-" means self-closing, so no body:
+      \markoComment,markoHtmlComment,markoCData,markoDeclaration,markoEscape,markoPlaceholder,
+      \markoEntity,markoScriptlet,markoCloseName,markoCloseAnon,markoAttrTagName,
+      \markoTagNameConditional,markoTagNameRepeat,markoTagNameException,
+      \markoTagNameStatement,markoTagNameBuiltin
+
+" Whether "<name" begins a body turns on how its open tag ends: an atomic
+" run over the groupings that may hold a ">" finds the structural one,
+" and a "/>" there means self-closing, no body:
 "   <my-tag onClick=() => go(a > b) />
+"   <div onClick() { hidden = a > b }/>
+" Nothing else hides a ">" (the parser ends an open tag at any ">" not
+" preceded by "="), and the repeat counts bound the scan so a stray "<"
+" costs a fixed lookahead; past them no body opens.
 let s:tag_end_ahead = '\%(\%(\%(<\%([^<>]\|<\%([^<>]\|<[^<>]\{-,40}>\)\{-,60}>\)\{-,100}>'
       \ . '\|"[^"]\{-,200}"\|' . "'[^']\\{-,200}'"
-      \ . '\|([^()]\{-,200})\|=>\|[ \t]>[ \t]\@=\|\_[^>]\)\{0,400}\)\@>/\@1<!=\@1<!>\)\@='
+      \ . '\|`[^`]\{-,200}`'
+      \ . '\|(\%(\_[^()]\|(\%(\_[^()]\|([^()]\{-,60})\)\{-,100})\)\{-,300})'
+      \ . '\|{\%(\_[^{}]\|{\_[^{}]\{-,100}}\)\{-,300}}'
+      \ . '\|=>\|\_[^>]\)\{0,800}\)\@>/\@1<!=\@1<!>\)\@='
 
 " Element bodies: html mode from a non-void, non-self-closing open tag to
-" its matching close.  \z1 pairs the names, and the end consumes only
-" "</" so an inner same-name close cannot end the outer bodies
-" (markoCloseName colors the rest):
+" its matching close.  \z1 pairs the names; the end consumes only "</" so
+" an inner same-name close cannot end the outer body:
 "   <div><div>nested</div>still the outer body</div>
-" No body opens for html voids, parsed-text tags, bodiless core tags
-" (<let/x=1> takes no close tag) or "@" attribute tags (those auto-close
-" at their parent, which no name pairing can track).  Defined after
-" markoTag so open tags still overlay inside bodies, and after the
-" concise regions so html mode wins at a line-start "<".  A mismatched or
-" missing close leaves its body open for the rest of the buffer, as an
-" unclosed tag does in html.vim.
+" The excluded names are the parser's void and text lists (<let/x=1>
+" takes no close tag; <effect> is in neither and opens a body).  Defined
+" after markoTag and the concise regions so a body wins at "<"; a missing
+" close leaves the body open, as in html.vim.
 exe 'syn region markoHtmlBody'
       \ . ' start=+<\ze\%(\%(area\|base\|br\|col\|embed\|hr\|img\|input\|link\|meta\|param'
       \ .   '\|source\|track\|wbr\|style\|script\|textarea\|html-comment\|html-style'
-      \ .   '\|html-script\|let\|const\|id\|log\|debug\|effect\|lifecycle\|return\)'
-      \ .   '\%(\k\|[$:]\)\@!\)\@!'
+      \ .   '\|html-script\|let\|const\|id\|log\|debug\|lifecycle\|return\)'
+      \ .   '\%(\k\|\$\|:=\@!\)\@!\)\@!'
       \ .   '\z(\%(\k\|[$:]\)\+\)\%(\_s\|[/>|(<.#=]\)\@=' . s:tag_end_ahead . '+'
       \ . ' matchgroup=markoTagDelim'
-      \ . ' end="</\ze\z1\%(\k\|[$:]\)\@!\s*>" end="</\ze\s*>"'
+      \ . ' end="</\ze\z1\s*>" end="</\ze\s*>"'
       \ . ' contains=@markoHtmlContent'
+" A dynamic name -- any mix of literal characters and "${...}"
+" placeholders -- cannot be paired, so a dynamic body closes at the first
+" close tag of dynamic shape, regardless of nesting:
+"   <h${size}>heading</h${size}>    <${tag}>body</${tag}>
 exe 'syn region markoDynamicBody'
-      \ . ' start=+<\ze\%(\$!\={\)\@=' . s:tag_end_ahead . '+'
+      \ . ' start=+<\ze\%(\%(\k\|:\)*\$!\={\)\@=' . s:tag_end_ahead . '+'
       \ . ' matchgroup=markoTagDelim'
-      \ . ' end="</\ze\%(\$!\={\_[^}]*}\)\=\s*>"'
-      \ . ' contains=@markoHtmlContent'
+      \ . ' end="</\ze\%(\%(\k\|:\)*\%(\$!\={\_[^{}]*\%({\_[^{}]*}\_[^{}]*\)\{0,3}}\%(\k\|:\)*\)\+\)\=\s*>"'
+      \ . ' contains=@markoHtmlContent nextgroup=markoClosePlaceholder'
 unlet s:tag_end_ahead
 
-" Delimited text blocks in concise mode contain html-mode content.  An
-" indented block closes at a matching delimiter line or at any line
-" indented less than its opener; a column-0 block closes only at its
-" matching delimiter:
+" Standalone delimited blocks hold html content, like markoTextIndent.
+" A block closes at its exact delimiter -- same indent, same dash count,
+" longer or shorter runs are text -- or on any dedent; a column-0 block
+" only at its delimiter (the empty \z1 kills the dedent end).  Defined
+" after the markoBlockDelim forms so a bare "--" line opens a block:
 "   --
 "   A block of text, <b>html mode</b>.
 "   --
-syn region markoTextBlock matchgroup=markoBlockDelim
-      \ start="^\z(\s\+\)--\+\s*$"
-      \ end="^\z1--\+\s*$" end="^\%(\z1\)\@!\ze\s*\S"
-      \ contains=@markoHtmlContent
-syn region markoTextBlock matchgroup=markoBlockDelim
-      \ start="^--\+\s*$"
-      \ end="^--\+\s*$"
+syn region markoTextDelimBlock matchgroup=markoBlockDelim
+      \ start="^\z(\s*\)\z(--\+\)\s*$"
+      \ end="^\z1\z2\s*$" end="^\%(\z1\)\@!\ze\s*\S"
       \ contains=@markoHtmlContent
 
-" Parsed-text tags: bodies contain only text and placeholders (style and
-" script embed CSS and TypeScript, a textarea body is plain text, an
-" html-comment body is comment text).  Defined after the generic tag and
-" body regions to win at the same "<".  The "/\@1<!" keeps self-closing
-" open tags, which have no body, out:
-"   <script src="x" />
-" keepend stops an unterminated embedded construct at the close tag.
-syn region markoStyle matchgroup=markoTagNameBuiltin
-      \ start=+<\%(html-\)\=style\>\_[^>]\{-,300}/\@1<!=\@1<!>+
-      \ end="</\%(html-\)\=style\s*>"
-      \ contains=@markoCSS,markoPlaceholder keepend fold
+" Parsed-text tags: bodies hold only text and placeholders (style/script
+" embed CSS and TypeScript).  Each region consumes only its "<"; the
+" markoParsedOpen overlay colors the open tag normally:
+"   <style/styles>    <script src="x" async>
+" Defined after the generic tag and body regions to win at "<".  The
+" "/\@1<!" keeps self-closing tags out; keepend stops an unterminated
+" embedded construct at the close tag.
+syn region markoParsedOpen contained matchgroup=markoTagDelim
+      \ start="<\ze\%(html-\%(style\|script\|comment\)\|style\|script\|textarea\)\>\%(:=\@!\|\$\)\@!"
+      \ matchgroup=markoTagDelim end="/\@1<!=\@1<!>"
+      \ contains=@markoTagStuff,@markoTagNames,markoAttrValue
+" The bounded lookahead for the open tag's own ">", shared by all four.
+let s:open_end_ahead = '\%(\%("[^"]\{-,200}"\|''[^'']\{-,200}''\|`[^`]\{-,200}`\|=>\|\_[^>]\)\{-,300}/\@1<!=\@1<!>\)\@='
+for [s:name, s:pat, s:body] in [
+      \ ['markoStyle', '\%(html-\)\=style', '@markoCSS,markoPlaceholderText,markoParsedOpen keepend fold'],
+      \ ['markoScript', '\%(html-\)\=script', '@markoTS,markoPlaceholderText,markoParsedOpen keepend fold'],
+      \ ['markoTextareaTag', 'textarea', 'markoPlaceholderText,markoEntity,@Spell,markoParsedOpen keepend'],
+      \ ['markoHtmlCommentTag', 'html-comment', 'markoPlaceholderText,@Spell,markoParsedOpen keepend'],
+      \ ]
+  exe 'syn region ' . s:name
+        \ . ' start=+<\ze' . s:pat . '\>\%(:=\@!\|\$\)\@!' . s:open_end_ahead . '+'
+        \ . ' matchgroup=markoTagDelim end="</\ze' . s:pat . '\s*>"'
+        \ . ' contains=' . s:body
+endfor
+unlet s:name s:pat s:body
 syn region markoStyle matchgroup=markoTagNameBuiltin
       \ start="^\s*style\%([./][^ \t{]*\)\=\s*{" end="}"
-      \ contains=@markoCSS,markoPlaceholder fold
-syn region markoScript matchgroup=markoTagNameBuiltin
-      \ start=+<\%(html-\)\=script\>\_[^>]\{-,300}/\@1<!=\@1<!>+
-      \ end="</\%(html-\)\=script\s*>"
-      \ contains=@markoTS,markoPlaceholder keepend fold
-syn region markoTextareaTag matchgroup=markoTagNameBuiltin
-      \ start=+<textarea\>\_[^>]\{-,300}/\@1<!=\@1<!>+
-      \ end="</textarea\s*>"
-      \ contains=markoPlaceholder,markoEntity,@Spell keepend
-syn region markoHtmlCommentTag matchgroup=markoTagNameBuiltin
-      \ start=+<html-comment\>\_[^>]\{-,300}/\@1<!=\@1<!>+
-      \ end="</html-comment\s*>"
-      \ contains=markoPlaceholder,@Spell keepend
-
-" Tag names inside "<...>"/"</...>" (concise names are colored by the
-" region matchgroups above).  The generic name is defined after the
-" attribute name, so it wins right after "<"; the class-specific names are
-" defined after the generic one, so they win over it.
-syn match markoTagName contained "\%(</\=\)\@2<=[[:alnum:]][-[:alnum:]_$:]*" display
-syn match markoAttrTagName contained "\%(</\=\)\@2<=@[-[:alnum:]_$:]\+" display
+      \ contains=@markoCSS,markoPlaceholderText fold
+" A trailing "--" on a concise style tag opens a delimited block of CSS;
+" the lookbehind claims only the dashes, winning over the generic
+" trailing-"--" form.  script works the same way with TypeScript:
+"   style/styles --
+"     .root { color: red }
+"   --
+for [s:kind, s:body] in [["Style", '@markoCSS'], ["Script", '@markoTS']]
+  exe 'syn match marko' . s:kind . 'Delim'
+        \ . ' "\%#=1\%(^\s*' . tolower(s:kind) . '\%(\k\|[$:]\)\@![^;{]*\)\@200<=--\+\s*$"'
+        \ . ' nextgroup=marko' . s:kind . 'Text skipnl'
+  exe 'syn region marko' . s:kind . 'Text contained' . s:indent_block
+        \ . ' contains=' . s:body . ',markoPlaceholderText keepend fold'
+endfor
+unlet s:kind s:body s:indent_block
+unlet s:open_end_ahead
+
+" Names inside "<...>"/"</...>": a name stops before "${" and
+" markoTagNameTail resumes after "}"; the tail's lookbehind demands the
+" placeholder, so a method body's "}" cannot start one:
+"   <h${size}>    <div onClick() { go() }disabled>   "disabled" is an attr
+" Generic after attr name, class names after generic: later wins.
+syn match markoTagName contained "\%(</\=\)\@2<=\%(-\@!\k\|\$[{ \t]\@!\)\%(\k\|:\|\${\@!\)*" display
+syn match markoTagNameTail contained
+      \ "}\@1<=\%(\$!\={[^{}]*}\)\@120<=\%(\k\|:\|\${\@!\)\+" display
+syn match markoAttrTagName contained "\%#=1\%(</\=\)\@2<=@[-[:alnum:]_$:]\+" display
 " The un-contained variants refine markoCloseName after a body's end has
 " consumed the "</", so builtin and "@" close names keep their classes.
-syn match markoAttrTagName "\%(</\)\@2<=@\%(\k\|[$:]\)\+\s*" nextgroup=markoCloseGt display
+syn match markoAttrTagName "\%#=1\%(</\)\@2<=@\%(\k\|[$:]\)\+\s*" nextgroup=markoCloseGt display
 for [s:class_suffix, s:name_pattern] in s:tag_name_classes
   exe 'syn match markoTagName' . s:class_suffix
-        \ . ' contained "\%(</\=\)\@2<=\%(' . s:name_pattern . '\)\%([-[:alnum:]_$]\)\@!" display'
+        \ . ' contained "\%#=1\%(</\=\)\@2<=\%(' . s:name_pattern . '\)\%(\k\|\$\|:=\@!\)\@!" display'
   exe 'syn match markoTagName' . s:class_suffix
-        \ . ' "\%(</\)\@2<=\%(' . s:name_pattern . '\)\%(\k\|[$:]\)\@!\s*"'
+        \ . ' "\%#=1\%(</\)\@2<=\%(' . s:name_pattern . '\)\%(\k\|[$:]\)\@!\s*"'
         \ . ' nextgroup=markoCloseGt display'
 endfor
 unlet s:tag_name_classes s:class_suffix s:name_pattern
 
-" The includes install their own sync rules; clear them and restate the
-" keyword characters ("syn sync clear" clears "syn iskeyword" too).  "-"
-" keeps css.vim's hyphenated value keywords whole:
-"   font-family: sans-serif
-" fromstart is the only safe strategy, since element bodies, concise
-" regions and style/script bodies are all unbounded or line-anchored; a
-" cold parse of a 5000 line file stays well under a second on current
-" hardware.
+" The includes install sync rules; clear them and restate the keyword set
+" ("syn sync clear" clears "syn iskeyword" too; "-" keeps css.vim's
+" font-family whole).  fromstart is the only safe strategy -- bodies and
+" blocks are unbounded -- and a 5000-line cold parse stays well under a
+" second.
 syn sync clear
 syn sync fromstart
 syn iskeyword @,48-57,_,192-255,-
 
+" Scoping regions (tags, bodies, brackets, values) take no link: they
+" bound contents that carry their own colors.
 hi def link markoComment Comment
 hi def link markoTagComment Comment
+hi def link markoHtmlComment Comment
 hi def link markoHtmlCommentTag Comment
 hi def link markoDeclaration PreProc
 hi def link markoCData String
@@ -355,8 +548,11 @@ hi def link markoScriptletDelim Special
 hi def link markoBlockDelim Special
 hi def link markoTagDelim Special
 hi def link markoTagName Statement
+hi def link markoTagNameTail Statement
 hi def link markoCloseName Statement
+hi def link markoCloseTail Statement
 hi def link markoCloseGt Special
+hi def link markoCloseAnon Special
 hi def link markoAttrTagName Identifier
 hi def link markoTagNameConditional Conditional
 hi def link markoTagNameRepeat Repeat
@@ -365,6 +561,8 @@ hi def link markoTagNameStatement Statement
 hi def link markoTagNameBuiltin Macro
 hi def link markoShorthandId Constant
 hi def link markoShorthandClass Identifier
+hi def link markoStyleDelim Special
+hi def link markoScriptDelim Special
 hi def link markoTagVar Identifier
 hi def link markoAttrName Type
 hi def link markoString String
index becbe3883da2375cb70524148260ac621b34f2c7..7ad248d2ec07a111bb175f1c815b01e19f5da175 100644 (file)
@@ -3,18 +3,18 @@
 |i+0#af5f00255&|m|p|o|r|t| +0#0000000&|{+0#00e0e07&| +0#0000000&|f|o|r|m|a|t|P|r|i|c|e| |}+0#00e0e07&| +0#0000000&|f+0#af5f00255&|r|o|m| +0#0000000&|"+0#e000002&|.|/|u|t|i|l|"| +0#0000000&@38
 |s+0#af5f00255&|t|a|t|i|c| +0#0000000&|c+0#af5f00255&|o|n|s|t| +0#0000000&|V|E|R|S|I|O|N| |=| |1+0#e000002&| +0#0000000&@50
 @75
-|<+0#e000e06&|s|t|y|l|e|>| +0#0000000&@67
+|<+0#e000e06&|s|t|y|l|e|/+0#00e0e07&|s|t|y|l|e|s|>+0#e000e06&| +0#0000000&@60
 @2|.+0#00e0e07&|p|r|i|c|e| +0#0000000&|{+0#00e0e07&| +0#0000000&|c+0#00e0003&|o|l|o|r|:+0#0000000&| |g+0#e000002&|r|e@1|n| +0#0000000&|}+0#00e0e07&| +0#0000000&@49
 |<+0#e000e06&|/|s|t|y|l|e|>| +0#0000000&@66
-@75
 |<+0#e000e06&|l|e|t|/+0#00e0e07&|c|o|u|n|t|=+0#af5f00255&|1+0#e000002&|/+0#e000e06&|>| +0#0000000&@60
-|<+0#e000e06&|c|o|n|s|t|/+0#00e0e07&|{+0#e000e06&| +0#0000000&|i|t|e|m|s| |}+0#e000e06&|=+0#af5f00255&|i+0#0000000&|n|p|u|t|/+0#e000e06&|>| +0#0000000&@50
-@75
-|<+0#e000e06&|d+0#af5f00255&|i|v|.+0#00e0e07&|c|a|r|d|#+0#e000002&|m|a|i|n| +0#0000000&|c+0#00e0003&|l|a|s@1|=+0#af5f00255&|"+0#e000002&|c|a|r|t|"| +0#0000000&|d+0#00e0003&|a|t|a|-|t|o|t|a|l|:+0#af5f00255&|=|t+0#0000000&|o|t|a|l| |.+0#af5f00255&@2|r+0#00e0003&|e|s|t|>+0#e000e06&| +0#0000000&@20
-@2|<+0#e000e06&|i+0#af5f00255&|f|=|c+0#0000000&|o|u|n|t|>+0#e000e06&| +0#0000000&@62
-@4|<+0#e000e06&|m+0#af5f00255&|y|-|b|u|t@1|o|n| +0#0000000&|s+0#00e0003&|i|z|e|=+0#af5f00255&|"+0#e000002&|l|a|r|g|e|"| +0#0000000&|o+0#00e0003&|n|C|l|i|c|k|=+0#af5f00255&|(+0#0000000&|)| |=+0#00e0003&|>| +0#0000000&|b|u|y|(|)|>+0#e000e06&|B+0#0000000&|u|y| |$+0#e000e06&|{|c+0#0000000&|o|u|n|t| |+| |1+0#e000002&|}+0#e000e06&|<|/|m+0#af5f00255&|y|-|b|u|t@1|o|n
-|>+0#e000e06&| +0#0000000&@73
+|<+0#e000e06&|c|o|n|s|t|/+0#00e0e07&|[+0#e000e06&|w+0#0000000&|i|d|t|h|,| |,| |.@2|d|i|m|s|]+0#e000e06&|=+0#af5f00255&|b+0#0000000&|o|x|/+0#e000e06&|>| +0#0000000&@43
+|<+0#e000e06&|d+0#af5f00255&|i|v|.+0#00e0e07&|c|a|r|d|#+0#e000002&|m|a|i|n| +0#0000000&|c+0#00e0003&|l|a|s@1|=+0#af5f00255&|"+0#e000002&|c|a|r|t|"| +0#0000000&|t+0#00e0003&|o|t|a|l|:+0#af5f00255&|=|s+0#0000000&|u|m| |.+0#af5f00255&@2|r+0#0000000&|e|s|t| |.+0#af5f00255&@2|(+0#0000000&|a| |?| |b| |:| |c|)|>+0#e000e06&| +0#0000000&@12
+@2|<+0#e000e06&|i+0#af5f00255&|f|=|t+0#0000000&|a|g| |=@2| |"+0#e000002&|s|e|l|e|c|t|"| +0#0000000&||@1| |c|o|u|n|t| |>+0#e000e06&| +0#0000000&|0|>| @38
+@4|<+0#e000e06&|m+0#af5f00255&|y|-|b|u|t@1|o|n| +0#0000000&|s+0#00e0003&|i|z|e|=+0#af5f00255&|"+0#e000002&|l|g|"| +0#0000000&|o+0#00e0003&|n|C|l|i|c|k|=+0#af5f00255&|(+0#0000000&|e|)| |=+0#00e0003&|>| +0#0000000&|b|u|y|(|e+0#e000e06&|)+0#0000000&|>+0#e000e06&|$|{|c+0#0000000&|o|u|n|t| |+| |1+0#e000002&|}+0#e000e06&|<|/|m+0#af5f00255&|y|-|b|u|t@1|o|n|>+0#e000e06&| +0#0000000&@3
 @2|<+0#e000e06&|/|i+0#af5f00255&|f|>+0#e000e06&| +0#0000000&@67
+@2|<+0#e000e06&|e+0#af5f00255&|l|s|e|-|i|f|=|f+0#0000000&|a|l@1|b|a|c|k| |?@1| |c|h|o|i|c|e|s|/+0#e000e06&|>| +0#0000000&@42
 @2|<+0#e000e06&|f+0#af5f00255&|o|r||+0#e000e06&|i+0#0000000&|t|e|m|,| |i|n|d|e|x||+0#e000e06&| +0#0000000&|o+0#00e0003&|f|=+0#af5f00255&|i+0#0000000&|t|e|m|s|>+0#e000e06&| +0#0000000&@45
 @4|<+0#e000e06&|s+0#af5f00255&|p|a|n|>+0#e000e06&|$|{|f+0#0000000&|o|r|m|a|t|P|r|i|c|e|(|i+0#e000e06&|t|e|m|.+0#0000000&|p+0#e000e06&|r|i|c|e|)+0#0000000&|}+0#e000e06&|<|/|s+0#af5f00255&|p|a|n|>+0#e000e06&| +0#0000000&@31
+@2|<+0#e000e06&|/|f+0#af5f00255&|o|r|>+0#e000e06&| +0#0000000&@66
+@2|<+0#e000e06&|g+0#af5f00255&|e|n|e|r|i|c|-|l|i|s|t|<+0#e000e06&|C+0#00e0e07&|o|n|f|i|g|<+0#e000e06&|s+0#00e0003&|t|r|i|n|g|>+0#e000e06&@1| +0#0000000&|a+0#00e0003&|=+0#af5f00255&|1+0#e000002&|/+0#e000e06&|>| +0#0000000&@37
 @57|1|,|1| @10|T|o|p| 
index fc2493b8f4f3041eb67d85f7a2f8c0769c8bde19..dbe0c74eef61df2372490830f576b0cbf446523c 100644 (file)
@@ -1,20 +1,20 @@
-| +0&#ffffff0@1|<+0#e000e06&|i+0#af5f00255&|f|=|c+0#0000000&|o|u|n|t|>+0#e000e06&| +0#0000000&@62
-@4|<+0#e000e06&|m+0#af5f00255&|y|-|b|u|t@1|o|n| +0#0000000&|s+0#00e0003&|i|z|e|=+0#af5f00255&|"+0#e000002&|l|a|r|g|e|"| +0#0000000&|o+0#00e0003&|n|C|l|i|c|k|=+0#af5f00255&|(+0#0000000&|)| |=+0#00e0003&|>| +0#0000000&|b|u|y|(|)|>+0#e000e06&|B+0#0000000&|u|y| |$+0#e000e06&|{|c+0#0000000&|o|u|n|t| |+| |1+0#e000002&|}+0#e000e06&|<|/|m+0#af5f00255&|y|-|b|u|t@1|o|n
-|>+0#e000e06&| +0#0000000&@73
-@2|<+0#e000e06&|/|i+0#af5f00255&|f|>+0#e000e06&| +0#0000000&@67
+| +0&#ffffff0@1|<+0#e000e06&|/|i+0#af5f00255&|f|>+0#e000e06&| +0#0000000&@67
+@2|<+0#e000e06&|e+0#af5f00255&|l|s|e|-|i|f|=|f+0#0000000&|a|l@1|b|a|c|k| |?@1| |c|h|o|i|c|e|s|/+0#e000e06&|>| +0#0000000&@42
 @2|<+0#e000e06&|f+0#af5f00255&|o|r||+0#e000e06&|i+0#0000000&|t|e|m|,| |i|n|d|e|x||+0#e000e06&| +0#0000000&|o+0#00e0003&|f|=+0#af5f00255&|i+0#0000000&|t|e|m|s|>+0#e000e06&| +0#0000000&@45
-@4><+0#e000e06&|s+0#af5f00255&|p|a|n|>+0#e000e06&|$|{|f+0#0000000&|o|r|m|a|t|P|r|i|c|e|(|i+0#e000e06&|t|e|m|.+0#0000000&|p+0#e000e06&|r|i|c|e|)+0#0000000&|}+0#e000e06&|<|/|s+0#af5f00255&|p|a|n|>+0#e000e06&| +0#0000000&@31
+@4|<+0#e000e06&|s+0#af5f00255&|p|a|n|>+0#e000e06&|$|{|f+0#0000000&|o|r|m|a|t|P|r|i|c|e|(|i+0#e000e06&|t|e|m|.+0#0000000&|p+0#e000e06&|r|i|c|e|)+0#0000000&|}+0#e000e06&|<|/|s+0#af5f00255&|p|a|n|>+0#e000e06&| +0#0000000&@31
 @2|<+0#e000e06&|/|f+0#af5f00255&|o|r|>+0#e000e06&| +0#0000000&@66
-@2|<+0#e000e06&|g+0#af5f00255&|e|n|e|r|i|c|-|l|i|s|t|<+0#e000e06&|s+0#0000000&|t|r|i|n|g|>+0#e000e06&| +0#0000000&|a+0#00e0003&|=+0#af5f00255&|1+0#e000002&|/+0#e000e06&|>| +0#0000000&@45
-@2|<+0#e000e06&|$|{|d+0#0000000&|y|n|a|m|i|c|T|a|g|}+0#e000e06&| +0#0000000&|t+0#00e0003&|i|t|l|e|=+0#af5f00255&|"+0#e000002&|d|y|n|a|m|i|c|"|>+0#e000e06&|b+0#0000000&|o|d|y|<+0#e000e06&|/|>+0#0000000&| @34
-@2|<+0#e000e06&|@+0#00e0e07&|f|o@1|t|e|r|>+0#e000e06&|s+0#0000000&|l|o|t| |c|o|n|t|e|n|t|<|/|@+0#af5f00255&|f|o@1|t|e|r|>+0#e000e06&| +0#0000000&@41
+@2><+0#e000e06&|g+0#af5f00255&|e|n|e|r|i|c|-|l|i|s|t|<+0#e000e06&|C+0#00e0e07&|o|n|f|i|g|<+0#e000e06&|s+0#00e0003&|t|r|i|n|g|>+0#e000e06&@1| +0#0000000&|a+0#00e0003&|=+0#af5f00255&|1+0#e000002&|/+0#e000e06&|>| +0#0000000&@37
+@2|<+0#e000e06&|$|{|d+0#0000000&|y|n|a|m|i|c|T|a|g|}+0#e000e06&| +0#0000000&|t+0#00e0003&|i|t|l|e|=+0#af5f00255&|"+0#e000002&|d|y|n|a|m|i|c|"|>+0#e000e06&|b+0#0000000&|o|d|y|<+0#e000e06&|/|$|{|d+0#0000000&|y|n|a|m|i|c|T|a|g|}+0#e000e06&|>| +0#0000000&@21
 |<+0#e000e06&|/|d+0#af5f00255&|i|v|>+0#e000e06&| +0#0000000&@68
-@75
-|<+0#e000e06&|d+0#af5f00255&|i|v| +0#0000000&@70
-@2|c+0#00e0003&|l|a|s@1|=+0#af5f00255&|"+0#e000002&|m|u|l|t|i|l|i|n|e|"| +0#0000000&@55
-@2|d+0#00e0003&|a|t|a|-|x|=+0#af5f00255&|1+0#e000002&| +0#0000000&@64
-|>+0#e000e06&|t+0#0000000&|e|x|t| |w|i|t|h| |&+0#e000e06&|a|m|p|;| +0#0000000&|e|n|t|i|t|y| |a|n|d| |\+0#e000e06&|$|{|a+0#0000000&|n| |e|s|c|a|p|e|d| |p|l|a|c|e|h|o|l|d|e|r|}|<+0#e000e06&|/|d+0#af5f00255&|i|v|>+0#e000e06&| +0#0000000&@14
-@75
-|<+0#e000e06&|t|e|x|t|a|r|e|a| |r|o|w|s|=|3|>|l+0#0000000&|i|t|e|r|a|l| |<|b|>|t|e|x|t|<|/|b|>| |w|i|t|h| |$+0#e000e06&|{|c+0#0000000&|o|u|n|t|}+0#e000e06&|<|/|t|e|x|t|a|r|e|a|>| +0#0000000&@13
-|<+0#e000e06&|h|t|m|l|-|c|o|m@1|e|n|t|>|r+0#0000e05&|e|n|d|e|r|e|d| |<|i|>|c|o|m@1|e|n|t|<|/|i|>| |b|o|d|y|<+0#e000e06&|/|h|t|m|l|-|c|o|m@1|e|n|t|>| +0#0000000&@17
-@57|1|8|,|5| @9|7|6|%| 
+|<+0#e000e06&|l+0#af5f00255&|i|n|k| +0#0000000&@69
+@2|r+0#00e0003&|e|l|=+0#af5f00255&|"+0#e000002&|c|a|n|o|n|i|c|a|l|"| +0#0000000&@57
+@2|h+0#00e0003&|r|e|f|=+0#af5f00255&|t+0#0000000&|r|a|n|s|l|a|t|e|d| @57
+@4|?| |l|o|c|a|l|i|z|e|U|r|l|(|u+0#e000e06&|r|l|,| |{+0#00e0e07&| +0#0000000&|l+0#af5f00255&|o|c|a|l|e| |}+0#00e0e07&|)+0#0000000&|.|h+0#af5f00255&|r|e|f| +0#0000000&@35
+@4|:| |b|a|s|e|H|r|e|f| @60
+|>+0#e000e06&| +0#0000000&@73
+|<+0#e000e06&|a+0#af5f00255&|u|d|i|o|-|p|l|a|y|e|r| +0#0000000&|r+0#00e0003&|a|t|i|o|=+0#af5f00255&|t+0#0000000&|o|t|a|l| |/| |c|o|u|n|t| |v+0#00e0003&|o|l|u|m|e|=+0#af5f00255&|M+0#00e0003&|a|t|h|.+0#0000000&|m+0#af5f00255&|a|x|(+0#0000000&| @24
+| +0#e000e06&@1|0+0#e000002&|,+0#e000e06&| +0#0000000&@70
+| +0#e000e06&@1|s|e|t@1|i|n|g|s|.+0#0000000&|f+0#af5f00255&|i|n|d|I|n|d|e|x|(+0#0000000&@1|s+0#e000e06&|)+0#0000000&| |=+0#00e0003&|>| +0#e000e06&|s|.+0#0000000&|a+0#e000e06&|c|t|i|v|e|)+0#0000000&|,+0#e000e06&| +0#0000000&@36
+|)|/+0#e000e06&|>| +0#0000000&@71
+|<+0#e000e06&|h+0#af5f00255&|$+0#e000e06&|{|s+0#0000000&|i|z|e|}+0#e000e06&| +0#0000000&|i+0#00e0003&|d|:|s|c|o|p|e|d|>+0#e000e06&|h+0#0000000&|e|a|d|i|n|g|<+0#e000e06&|/|>| +0#0000000&@44
+@57|1|9|,|3| @9|4@1|%| 
index a9c90cd698f75abb75882b75007de9ab7167cb5e..945a3cda50ef74a773afe38e55b6a13ca9198eab 100644 (file)
@@ -1,20 +1,20 @@
-|<+0#e000e06#ffffff0|h|t|m|l|-|c|o|m@1|e|n|t|>|r+0#0000e05&|e|n|d|e|r|e|d| |<|i|>|c|o|m@1|e|n|t|<|/|i|>| |b|o|d|y|<+0#e000e06&|/|h|t|m|l|-|c|o|m@1|e|n|t|>| +0#0000000&@17
-@75
+|<+0#e000e06#ffffff0|h+0#af5f00255&|$+0#e000e06&|{|s+0#0000000&|i|z|e|}+0#e000e06&| +0#0000000&|i+0#00e0003&|d|:|s|c|o|p|e|d|>+0#e000e06&|h+0#0000000&|e|a|d|i|n|g|<+0#e000e06&|/|>| +0#0000000&@44
+|<+0#e000e06&|$|{|"+0#e000002&|h|e|l@1|o|"|}+0#e000e06&|-+0#af5f00255&|w|o|r|l|d|/+0#e000e06&|>| +0#0000000&@55
+|<+0#e000e06&|b+0#af5f00255&|a|d|g|e|#+0#e000002&|i|t|e|m|-|$+0#e000e06&|{|i+0#0000000&|d|}+0#e000e06&|.+0#00e0e07&|t|o|n|e|-|$+0#e000e06&|{|k+0#0000000&|i|n|d|}+0#e000e06&| +0#0000000&|c+0#00e0003&|a|f|é|=+0#af5f00255&|"+0#e000002&|f|a|ç|a|d|e|"| +0#0000000&|x+0#00e0003&|=+0#af5f00255&|`+0#e000002&|a|$+0#af5f00255&|{|`+0#e000002&|b| |$+0#af5f00255&|{|c+0#0000000&|}+0#af5f00255&|`+0#e000002&|}+0#af5f00255&|d+0#e000002&|`|/+0#e000e06&|>| +0#0000000&@10
+|<+0#e000e06&|f+0#af5f00255&|o|r|m| +0#0000000&|o+0#00e0003&|n|S|u|b|m|i|t|<+0#e000e06&|T+0#00e0e07&|>+0#e000e06&|(|e+0#0000000&|)+0#e000e06&| +0#0000000&|{+0#e000e06&| +0#0000000&|g|o|<|T|>|(|e|)| |}+0#e000e06&| +0#0000000&|a+0#00e0003&|c|t|i|o|n|(+0#e000e06&|a+0#0000000&|,| |b|)+0#e000e06&| +0#0000000&|m+0#00e0003&|s|g|=+0#af5f00255&|"+0#e000002&|s|a|y| |\+0#e000e06&|"|h+0#e000002&|i|\+0#e000e06&|"|"+0#e000002&|/+0#e000e06&|>| +0#0000000&@9
+|<+0#e000e06&|e|f@1|e|c|t|(|)| +0#0000000&|{+0#e000e06&| +0#0000000&|c|l|e|a|n|u|p|(|)| |}+0#e000e06&|>|b+0#0000000&|o|d|y| |p|r|o|s|e|<+0#e000e06&|/|e|f@1|e|c|t|>| +0#0000000&@31
+><+0#e000e06&|a+0#af5f00255&|w|a|i|t|(+0#e000e06&|p+0#0000000&|r|o|m|i|s|e|)+0#e000e06&|>| +0#0000000&@58
+@2|<+0#e000e06&|@+0#00e0e07&|t|h|e|n||+0#e000e06&|r+0#0000000&|e|s|u|l|t||+0#e000e06&|>|$|{|r+0#0000000&|e|s|u|l|t|}+0#e000e06&|<+0#0000000&|/|@+0#00e0e07&|t|h|e|n|>+0#e000e06&| +0#0000000&@40
+|<+0#e000e06&|/|a+0#af5f00255&|w|a|i|t|>+0#e000e06&| +0#0000000&@66
+|<+0#e000e06&|m|a|c|r|o| +0#0000000&|r+0#00e0003&|e|n|d|e|r|P|r|i|c|e||+0#e000e06&|a+0#0000000&|m|o|u|n|t||+0#e000e06&|>|$|{|f+0#0000000&|o|r|m|a|t|P|r|i|c|e|(|a+0#e000e06&|m|o|u|n|t|)+0#0000000&|}+0#e000e06&|<|/|m|a|c|r|o|>| +0#0000000&@17
+|<+0#e000e06&|d+0#af5f00255&|i|v| +0#0000000&@70
+@2|c+0#00e0003&|l|a|s@1|=+0#af5f00255&|"+0#e000002&|m|u|l|t|i|l|i|n|e|"| +0#0000000&@55
+@2|d+0#00e0003&|a|t|a|-|x|=+0#af5f00255&|1+0#e000002&| +0#0000000&@64
+|>+0#e000e06&|t+0#0000000&|e|x|t| |w|i|t|h| |&+0#e000e06&|a|m|p|;| +0#0000000&|e|n|t|i|t|y| |a|n|d| |\+0#e000e06&|$|{|a+0#0000000&|n| |e|s|c|a|p|e|d| |p|l|a|c|e|h|o|l|d|e|r|}|<+0#e000e06&|/|d+0#af5f00255&|i|v|>+0#e000e06&| +0#0000000&@14
+|<+0#e000e06&|t|e|x|t|a|r|e|a| +0#0000000&|r+0#00e0003&|o|w|s|=+0#af5f00255&|3+0#e000002&|>+0#e000e06&|l+0#0000000&|i|t|e|r|a|l| |<|b|>|t|e|x|t|<|/|b|>| |w|i|t|h| |$+0#e000e06&|{|c+0#0000000&|o|u|n|t|}+0#e000e06&|<|/|t|e|x|t|a|r|e|a|>| +0#0000000&@13
 |<+0#e000e06&|s|c|r|i|p|t|>| +0#0000000&@66
 @2|c+0#af5f00255&|o|n|s|t| +0#0000000&|e|l| |=| |d+0#00e0003&|o|c|u|m|e|n|t|.+0#0000000&|q+0#af5f00255&|u|e|r|y|S|e|l|e|c|t|o|r|(+0#0000000&|"+0#e000002&|.|p|r|i|c|e|"|)+0#0000000&|;| @28
-><+0#e000e06&|/|s|c|r|i|p|t|>| +0#0000000&@65
+|<+0#e000e06&|/|s|c|r|i|p|t|>| +0#0000000&@65
 |~+0#4040ff13&| @73
 |~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-| +0#0000000&@56|3|5|,|1| @9|B|o|t| 
+| +0#0000000&@56|3|7|,|1| @9|B|o|t| 
index 3dcec6ce316e62eab9e8cff227b61b39bc7abbce..e3db1d6b580eceddec8454aff69e4740600ea0c6 100644 (file)
@@ -1,20 +1,20 @@
->/+0#0000e05#ffffff0@1| |c|o|n|c|i|s|e| |m|o|d|e|:| |c|u|s|t|o|m| |t|a|g|s|,| |b|u|i|l|t|i|n|s|,| |s|t|a|t|e|m|e|n|t|s|,| |s|c|r|i|p|t|l|e|t|s|,| |t|e|x|t| |b|l|o|c|k|s
+>/+0#0000e05#ffffff0@1| |c|o|n|c|i|s|e| |m|o|d|e|:| |c|u|s|t|o|m| |t|a|g|s|,| |b|u|i|l|t|i|n|s|,| |s|t|a|t|e|m|e|n|t|s|,| |s|c|r|i|p|t|l|e|t|s|,| |b|l|o|c|k|s| +0#0000000&@4
 |i+0#af5f00255&|m|p|o|r|t| +0#0000000&|{+0#00e0e07&| +0#0000000&|f|e|t|c|h|I|t|e|m|s| |}+0#00e0e07&| +0#0000000&|f+0#af5f00255&|r|o|m| +0#0000000&|"+0#e000002&|.|/|a|p|i|"| +0#0000000&@40
-@75
-|$+0#e000e06&| |c+0#af5f00255&|o|n|s|t| +0#0000000&|d|o|u|b|l|e|d| |=| |i|n|p|u|t|.|c+0#af5f00255&|o|u|n|t| +0#0000000&|*| |2+0#e000002&|;+0#0000000&| @40
-@75
-|l+0#e000e06&|e|t|/+0#00e0e07&|b|a|s|k|e|t|=+0#af5f00255&|[+0#0000000&|]| @61
-|l+0#e000e06&|e|t|<|n+0#0000000&|u|m|b|e|r|>+0#e000e06&|/+0#00e0e07&|t|y|p|e|d|=+0#af5f00255&|0+0#e000002&| +0#0000000&@55
-@75
-|c+0#af5f00255&|o|n|t|a|i|n|e|r| +0#0000000&|f+0#00e0003&|l|u|i|d| +0#0000000&@59
-@2|s+0#af5f00255&|e|c|t|i|o|n|-|h|e|a|d|i|n|g| +0#0000000&|a+0#00e0003&|s|=+0#af5f00255&|"+0#e000002&|h|2|"| +0#0000000&|-+0#e000e06&@1| +0#0000000&|E|v|e|r|y|t|h|i|n|g| |p|a|r|t|n|e|r|s|h|i|p|s| |n|e@1|d| @17
-@2|b+0#af5f00255&|e|n|t|o| +0#0000000&|c+0#00e0003&|o|l|s|=+0#af5f00255&|4+0#e000002&| +0#0000000&@60
-@4|b+0#af5f00255&|e|n|t|o|-|c|e|l@1| +0#0000000&|[+0#e000e06&| +0#0000000&@58
-@6|f+0#00e0003&|e|a|t|u|r|e|d| +0#0000000&@60
-@6|c+0#00e0003&|o|l|S|p|a|n|=+0#af5f00255&|2+0#e000002&| +0#0000000&@59
-@6|t+0#00e0003&|i|t|l|e|=+0#af5f00255&|"+0#e000002&|O|n|e| |p|l|a|t|f|o|r|m|"| +0#0000000&@48
-@4|]+0#e000e06&| +0#0000000&@69
-@6|p+0#af5f00255&| +0#0000000&|-+0#e000e06&@1| +0#0000000&|O|n|b|o|a|r|d|i|n|g| |a|n|d| |e|n|a|b|l|e|m|e|n|t|,| |u|n|i|f|i|e|d|.| @28
-@75
-|i+0#af5f00255&|f|=|i+0#0000000&|n|p|u|t|.|r+0#af5f00255&|e|a|d|y| +0#0000000&@60
+|$+0#e000e06&| |c+0#af5f00255&|o|n|s|t| +0#0000000&|p|e|r|s|o|n| |=| |{+0#00e0e07&| +0#0000000&@56
+@2|n+0#af5f00255&|a|m|e|:+0#0000000&| |"+0#e000002&|F|r|a|n|k|"|,+0#0000000&| @58
+|}+0#00e0e07&| +0#0000000&@73
+|l+0#e000e06&|e|t|/+0#00e0e07&|[+0#e000e06&|h+0#0000000&|,| |i|,| |,| |.@2|j|]+0#e000e06&|=+0#af5f00255&|[+0#0000000&|1+0#e000002&|,+0#0000000&| |2+0#e000002&|,+0#0000000&| |3+0#e000002&|,+0#0000000&| |4+0#e000002&|,+0#0000000&| |5+0#e000002&|]+0#0000000&| |a+0#e000e06&|s| +0#0000000&|c+0#00e0e07&|o|n|s|t| +0#0000000&@31
+|c+0#e000e06&|o|n|s|t|/+0#00e0e07&|i|s|L|o|c|a|l|i|z|e|d|=+0#af5f00255&|l+0#0000000&|o|c|a|l|e| |!|=@1| |b|a|s|e|L|o|c|a|l|e| @35
+|c+0#e000e06&|o|n|s|t|/+0#00e0e07&|n|o|d|e|s|=+0#af5f00255&|i+0#0000000&|n|p|u|t|.|n|o|d|e|s| |?@1| @48
+|(|t+0#af5f00255&|y|p|e|o|f| +0#0000000&|p|a|r|t|s| |=@2| |"+0#e000002&|f|u|n|c|t|i|o|n|"| +0#0000000&@46
+@2|?| |b|u|i|l|d|N|o|d|e|s|(|p+0#e000e06&|a|r|t|s|(+0#0000000&|i+0#e000e06&|n|p|u|t|.+0#0000000&|i+0#e000e06&|n|p|u|t|s|)+0#0000000&@1| @39
+@2|:| |u+0#e000002&|n|d|e|f|i|n|e|d|)+0#0000000&| @60
+|b+0#af5f00255&|e|n|t|o|-|c|e|l@1| +0#0000000&|[+0#e000e06&| +0#0000000&@62
+@2|f+0#00e0003&|e|a|t|u|r|e|d| +0#0000000&@64
+@2|c+0#00e0003&|o|l|S|p|a|n|=+0#af5f00255&|2+0#e000002&| +0#0000000&@63
+@2|t+0#00e0003&|i|t|l|e|=+0#af5f00255&|h+0#0000000&|e|a|d|.|t+0#af5f00255&|i|t|l|e| +0#0000000&|?@1| |"+0#e000002&|O|n|e| |p|l|a|t|f|o|r|m|"| +0#0000000&@38
+|]+0#e000e06&| +0#0000000&@73
+|l+0#af5f00255&|i|n|k| +0#0000000&@70
+@2|,|r+0#00e0003&|e|l|=+0#af5f00255&|"+0#e000002&|a|l|t|e|r|n|a|t|e|"| +0#0000000&@56
+@2|/+0#0000e05&@1| |a| |c|o|m@1|e|n|t| |m|a|y| |s|i|t| |b|e|t|w|e@1|n| |c|o|n|t|i|n|u|e|d| |a|t@1|r|i|b|u|t|e|s| +0#0000000&@23
 @57|1|,|1| @10|T|o|p| 
index fb80cfb9f468bad1242f9bbec9f8b2ecff000f6c..c64e36b1c59aeb15a3f72ae939819468624bd893 100644 (file)
@@ -1,20 +1,20 @@
-| +0&#ffffff0@5|c+0#00e0003&|o|l|S|p|a|n|=+0#af5f00255&|2+0#e000002&| +0#0000000&@59
-@6|t+0#00e0003&|i|t|l|e|=+0#af5f00255&|"+0#e000002&|O|n|e| |p|l|a|t|f|o|r|m|"| +0#0000000&@48
-@4|]+0#e000e06&| +0#0000000&@69
-@6|p+0#af5f00255&| +0#0000000&|-+0#e000e06&@1| +0#0000000&|O|n|b|o|a|r|d|i|n|g| |a|n|d| |e|n|a|b|l|e|m|e|n|t|,| |u|n|i|f|i|e|d|.| @28
-@75
->i+0#af5f00255&|f|=|i+0#0000000&|n|p|u|t|.|r+0#af5f00255&|e|a|d|y| +0#0000000&@60
+| +0&#ffffff0@1|c+0#00e0003&|o|l|S|p|a|n|=+0#af5f00255&|2+0#e000002&| +0#0000000&@63
+@2|t+0#00e0003&|i|t|l|e|=+0#af5f00255&|h+0#0000000&|e|a|d|.|t+0#af5f00255&|i|t|l|e| +0#0000000&|?@1| |"+0#e000002&|O|n|e| |p|l|a|t|f|o|r|m|"| +0#0000000&@38
+|]+0#e000e06&| +0#0000000&@73
+|l+0#af5f00255&|i|n|k| +0#0000000&@70
+@2|,|r+0#00e0003&|e|l|=+0#af5f00255&|"+0#e000002&|a|l|t|e|r|n|a|t|e|"| +0#0000000&@56
+@2>/+0#0000e05&@1| |a| |c|o|m@1|e|n|t| |m|a|y| |s|i|t| |b|e|t|w|e@1|n| |c|o|n|t|i|n|u|e|d| |a|t@1|r|i|b|u|t|e|s| +0#0000000&@23
+@2|,|h+0#00e0003&|r|e|f|=+0#af5f00255&|(+0#0000000&| @65
+@4|t|r|a|n|s|l|a|t|e|d| |?| |l|o|c|a|l|i|z|e|U|r|l|(|u+0#e000e06&|r|l|,| |{+0#00e0e07&| +0#0000000&|l+0#af5f00255&|o|c|a|l|e| |}+0#00e0e07&|)+0#0000000&|.|h+0#af5f00255&|r|e|f| +0#0000000&|:| |b|a|s|e|H|r|e|f| @13
+@2|)| @71
+@2|,|o+0#00e0003&|n|L|o|a|d|(+0#e000e06&|e+0#0000000&|)+0#e000e06&| +0#0000000&|{+0#e000e06&| +0#0000000&@60
+@4|r|e|a|d|y| |=| |t+0#e000002&|r|u|e|;+0#0000000&| @57
+@2|}+0#e000e06&| +0#0000000&@71
+|i+0#af5f00255&|n|p|u|t| +0#0000000&|t+0#00e0003&|y|p|e|=+0#af5f00255&|"+0#e000002&|t|e|x|t|"|,+0#0000000&| @56
+@2|v+0#00e0003&|a|l|u|e|=+0#af5f00255&|n+0#00e0003&|a|m|e| +0#0000000&|c+0#00e0003&|h|e|c|k|e|d|:+0#af5f00255&|=|m+0#0000000&|o|d|e| |=@2| |"+0#e000002&|e|d|i|t|"| +0#0000000&@37
+|i+0#af5f00255&|f|=|i+0#0000000&|n|p|u|t|.|r+0#af5f00255&|e|a|d|y| +0#0000000&@60
 @2|f+0#af5f00255&|o|r||+0#e000e06&|i+0#0000000&|t|e|m|,| |i||+0#e000e06&| +0#0000000&|o+0#00e0003&|f|=+0#af5f00255&|f+0#0000000&|e|t|c|h|I|t|e|m|s|(|)| @44
-@4|l+0#e000e06&|o|g| +0#0000000&|"+0#e000002&|i|t|e|m| |l|o|a|d|e|d|"| +0#0000000&@53
-@4|-+0#e000e06&@1| +0#0000000&|P|r|i|c|e|:| |$+0#e000e06&|{|i+0#0000000&|t|e|m|.|p|r|i|c|e|}+0#e000e06&| +0#0000000&@47
-@2|w+0#af5f00255&|h|i|l|e|(+0#e000e06&|i+0#0000000&|<|1+0#e000002&|0|)+0#e000e06&| +0#0000000&@61
-@4|-+0#e000e06&@1| +0#0000000&|c|o|u|n|t|i|n|g| @59
-|t+0#af5f00255&|r|y| +0#0000000&@71
-@2|a+0#af5f00255&|w|a|i|t|(+0#e000e06&|p+0#0000000&|r|o|m|i|s|e|)+0#e000e06&| +0#0000000&@58
-|e+0#af5f00255&|l|s|e| +0#0000000&@70
-@2|r+0#af5f00255&|e|t|u|r|n|=|n+0#e000002&|u|l@1| +0#0000000&@61
-|@+0#00e0e07&|t|h|e|n||+0#e000e06&|r+0#0000000&|e|s|u|l|t||+0#e000e06&| +0#0000000&@61
-@2|-+0#e000e06&@1| +0#0000000&|r|e|s|o|l|v|e|d| @61
-@75
-|s+0#e000e06&|t|y|l|e|.|s|c|s@1| |{| +0#0000000&@62
-@57|1|9|,|1| @9|5|4|%| 
+@2|w+0#af5f00255&|h|i|l|e|(+0#e000e06&|i+0#0000000&| |<| |1+0#e000002&|0|)+0#e000e06&| +0#0000000&@59
+@4|m+0#af5f00255&|y|-|t|a|g| +0#0000000&|.+0#af5f00255&@2|[+0#0000000&|1+0#e000002&|,+0#0000000&| |2+0#e000002&|]+0#0000000&| |.+0#af5f00255&@2|r+0#0000000&|e|s|t| |t+0#00e0003&|o|t|a|l|=+0#af5f00255&|b+0#0000000&|a|s|e| |+| @33
+@6|t|a|x| @65
+@57|1|9|,|3| @9|4@1|%| 
index 2c1356382e1d3ab482f20e34be029118493acb1c..899864c3b6f71bcabe2e0b327bc3643896c48b7d 100644 (file)
@@ -1,20 +1,20 @@
-|s+0#e000e06#ffffff0|t|y|l|e|.|s|c|s@1| |{| +0#0000000&@62
+| +0&#ffffff0@5|t|a|x| @65
+@2|a+0#af5f00255&|w|a|i|t|(+0#e000e06&|p+0#0000000&|r|o|m|i|s|e|)+0#e000e06&| +0#0000000&@58
+@2|r+0#af5f00255&|e|t|u|r|n|=|x+0#0000000&| |i+0#af5f00255&|n|s|t|a|n|c|e|o|f| +0#0000000&|B| @51
+|@+0#00e0e07&|t|h|e|n||+0#e000e06&|r+0#0000000&|e|s|u|l|t||+0#e000e06&| +0#0000000&|-+0#e000e06&@1| +0#0000000&|r|e|s|o|l|v|e|d| @49
+|$+0#e000e06&|{|`+0#e000002&|b|u|t@1|o|n|`|}+0#e000e06&| +0#0000000&|t+0#00e0003&|y|p|e|=+0#af5f00255&|"+0#e000002&|b|u|t@1|o|n|"| +0#0000000&|c+0#00e0003&|l|a|s@1|=+0#af5f00255&|s+0#0000000&|t|y|l|e|s|.|b|u|t@1|o|n| |-+0#e000e06&@1| +0#0000000&|P|r|e|s@1| |m|e| @17
+>h+0#af5f00255&|$+0#e000e06&|{|s+0#0000000&|i|z|e|}+0#e000e06&| +0#0000000&|-+0#e000e06&@1| +0#0000000&|D|y|n|a|m|i|c| |h|e|a|d|i|n|g| @47
+|.+0#00e0e07&|h|e|r|o|-|c|o|p|y|#+0#e000002&|m|a|i|n|/+0#00e0e07&|$|e|l| +0#0000000&|-+0#e000e06&@1| +0#0000000&|S|h|o|r|t|h|a|n|d| |t|a|g| @38
+|s+0#e000e06&|t|y|l|e|/+0#00e0e07&|s|t|y|l|e|s| +0#0000000&|-+0#e000e06&@1| +0#0000000&@59
 @2|.+0#00e0e07&|b|a|s|k|e|t| +0#0000000&|{+0#00e0e07&| +0#0000000&|c+0#00e0003&|o|l|o|r|:+0#0000000&| |r+0#e000002&|e|d| +0#0000000&|}+0#00e0e07&| +0#0000000&@50
-|}+0#e000e06&| +0#0000000&@73
-@75
+| +0#e000e06&@1|-@1| +0#0000000&@70
+|p+0#af5f00255&| +0#0000000&|-+0#e000e06&@1| +0#0000000&@70
+@2|A|n| |i|n|d|e|n|t|e|d| |t|e|x|t| |b|l|o|c|k| |r|u|n|s| |u|n|t|i|l| |d|e|d|e|n|t|,| |w|i|t|h| |<+0#e000e06&|b+0#af5f00255&|>+0#e000e06&|h+0#0000000&|t|m|l|<+0#e000e06&|/|b+0#af5f00255&|>+0#e000e06&|.+0#0000000&| @13
+|p+0#af5f00255&| +0#0000000&|-+0#e000e06&@3| +0#0000000&|t|e|x|t| |a|f|t|e|r| |a| |l|o|n|g|e|r| |d|a|s|h| |r|u|n| @39
 |-+0#e000e06&@1| +0#0000000&@72
->A| |d|e|l|i|m|i|t|e|d| |b|l|o|c|k| |i|s| |h|t|m|l| |m|o|d|e|,| |s|o| |b|a|r|e| |w|o|r|d|s| |a|r|e| |p|r|o|s|e|.| @18
-|<+0#e000e06&|b+0#af5f00255&|>+0#e000e06&|b+0#0000000&|o|l|d| |$+0#e000e06&|{|d+0#0000000&|o|u|b|l|e|d|}+0#e000e06&|<|/|b+0#af5f00255&|>+0#e000e06&| +0#0000000&@52
+|A| |d|e|l|i|m|i|t|e|d| |b|l|o|c|k| |i|s| |h|t|m|l| |m|o|d|e|:| |<+0#e000e06&|b+0#af5f00255&|>+0#e000e06&|b+0#0000000&|o|l|d| |$+0#e000e06&|{|p+0#0000000&|e|r|s|o|n|.|n+0#af5f00255&|a|m|e|}+0#e000e06&|<|/|b+0#af5f00255&|>+0#e000e06&| +0#0000000&|a|n|d| |b|a|r|e| |p|r|o|s|e|.| 
 |-+0#e000e06&@1| +0#0000000&@72
-@75
-|<+0#e000e06&|f+0#af5f00255&|o@1|t|e|r|>+0#e000e06&| +0#0000000&@66
-|i|f| |y|o|u| |r|e|a|d| |t|h|i|s| |i|n|s|i|d|e| |a|n| |e|l|e|m|e|n|t| |b|o|d|y|,| |i|t| |s|t|a|y|s| |p|l|a|i|n| |p|r|o|s|e| @13
-|<+0#e000e06&|/|f+0#af5f00255&|o@1|t|e|r|>+0#e000e06&| +0#0000000&@65
+|<+0#e000e06&|f+0#af5f00255&|o@1|t|e|r|>+0#e000e06&|e+0#0000000&|l|e|m|e|n|t| |b|o|d|i|e|s| |s|t|a|y| |h|t|m|l| |m|o|d|e|,| |s|o| |t|h|i|s| |i|s| |p|l|a|i|n| |p|r|o|s|e|<+0#e000e06&|/|f+0#af5f00255&|o@1|t|e|r|>+0#e000e06&| +0#0000000&@4
 |~+0#4040ff13&| @73
 |~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
-|~| @73
 | +0#0000000&@56|3|7|,|1| @9|B|o|t| 
index 1281ce0bdcd0110c9cfda7b8177ded8b2d804b62..1be6727dfef982154fc8dcf831fb726e6ef3d4a1 100644 (file)
@@ -3,33 +3,46 @@
 import { formatPrice } from "./util"
 static const VERSION = 1
 
-<style>
+<style/styles>
   .price { color: green }
 </style>
-
 <let/count=1/>
-<const/{ items }=input/>
-
-<div.card#main class="cart" data-total:=total ...rest>
-  <if=count>
-    <my-button size="large" onClick=() => buy()>Buy ${count + 1}</my-button>
+<const/[width, , ...dims]=box/>
+<div.card#main class="cart" total:=sum ...rest ...(a ? b : c)>
+  <if=tag === "select" || count > 0>
+    <my-button size="lg" onClick=(e) => buy(e)>${count + 1}</my-button>
   </if>
+  <else-if=fallback ?? choices/>
   <for|item, index| of=items>
     <span>${formatPrice(item.price)}</span>
   </for>
-  <generic-list<string> a=1/>
-  <${dynamicTag} title="dynamic">body</>
-  <@footer>slot content</@footer>
+  <generic-list<Config<string>> a=1/>
+  <${dynamicTag} title="dynamic">body</${dynamicTag}>
 </div>
-
+<link
+  rel="canonical"
+  href=translated
+    ? localizeUrl(url, { locale }).href
+    : baseHref
+>
+<audio-player ratio=total / count volume=Math.max(
+  0,
+  settings.findIndex((s) => s.active),
+)/>
+<h${size} id:scoped>heading</>
+<${"hello"}-world/>
+<badge#item-${id}.tone-${kind} café="façade" x=`a${`b ${c}`}d`/>
+<form onSubmit<T>(e) { go<T>(e) } action(a, b) msg="say \"hi\""/>
+<effect() { cleanup() }>body prose</effect>
+<await(promise)>
+  <@then|result|>${result}</@then>
+</await>
+<macro renderPrice|amount|>${formatPrice(amount)}</macro>
 <div
   class="multiline"
   data-x=1
 >text with &amp; entity and \${an escaped placeholder}</div>
-
 <textarea rows=3>literal <b>text</b> with ${count}</textarea>
-<html-comment>rendered <i>comment</i> body</html-comment>
-
 <script>
   const el = document.querySelector(".price");
 </script>
index d8994cb46a2a88f9f113e0383014a76650846626..23987244f02f9c0693e09ac8206801f9259a14d3 100644 (file)
@@ -1,43 +1,48 @@
-// concise mode: custom tags, builtins, statements, scriptlets, text blocks
+// concise mode: custom tags, builtins, statements, scriptlets, blocks
 import { fetchItems } from "./api"
-
-$ const doubled = input.count * 2;
-
-let/basket=[]
-let<number>/typed=0
-
-container fluid
-  section-heading as="h2" -- Everything partnerships need
-  bento cols=4
-    bento-cell [
-      featured
-      colSpan=2
-      title="One platform"
-    ]
-      p -- Onboarding and enablement, unified.
-
+$ const person = {
+  name: "Frank",
+}
+let/[h, i, , ...j]=[1, 2, 3, 4, 5] as const
+const/isLocalized=locale !== baseLocale
+const/nodes=input.nodes ??
+(typeof parts === "function"
+  ? buildNodes(parts(input.inputs))
+  : undefined)
+bento-cell [
+  featured
+  colSpan=2
+  title=head.title ?? "One platform"
+]
+link
+  ,rel="alternate"
+  // a comment may sit between continued attributes
+  ,href=(
+    translated ? localizeUrl(url, { locale }).href : baseHref
+  )
+  ,onLoad(e) {
+    ready = true;
+  }
+input type="text",
+  value=name checked:=mode === "edit"
 if=input.ready
   for|item, i| of=fetchItems()
-    log "item loaded"
-    -- Price: ${item.price}
-  while(i<10)
-    -- counting
-try
+  while(i < 10)
+    my-tag ...[1, 2] ...rest total=base +
+      tax
   await(promise)
-else
-  return=null
-@then|result|
-  -- resolved
-
-style.scss {
+  return=x instanceof B
+@then|result| -- resolved
+${`button`} type="button" class=styles.button -- Press me
+h${size} -- Dynamic heading
+.hero-copy#main/$el -- Shorthand tag
+style/styles --
   .basket { color: red }
-}
-
+  --
+p --
+  An indented text block runs until dedent, with <b>html</b>.
+p ---- text after a longer dash run
 --
-A delimited block is html mode, so bare words are prose.
-<b>bold ${doubled}</b>
+A delimited block is html mode: <b>bold ${person.name}</b> and bare prose.
 --
-
-<footer>
-if you read this inside an element body, it stays plain prose
-</footer>
+<footer>element bodies stay html mode, so this is plain prose</footer>