diff --git a/runtime/pack/dist/opt/old-zip/autoload/zip.vim b/runtime/pack/dist/opt/old-zip/autoload/zip.vim index 458b8a1f20..a1bf8fd0dc 100644 --- a/runtime/pack/dist/opt/old-zip/autoload/zip.vim +++ b/runtime/pack/dist/opt/old-zip/autoload/zip.vim @@ -25,6 +25,7 @@ " 2026 Apr 14 by Vim Project: Detect more path traversal attacks on Windows " 2026 Apr 15 by Vim Project: Detect more path traversal attacks on Windows " 2026 Jun 20 by Vim Project: Fix wrong escaping for the powershell calls +" 2026 Jul 25 by Vim Project: Improved Compatibility for powershell 5 and pwsh 7 " License: Vim License (see vim's :help license) " Copyright: Copyright (C) 2005-2019 Charles E. Campbell {{{1 " Permission is hereby granted to use and distribute this code, @@ -61,6 +62,9 @@ endif if !exists("g:zip_extractcmd") let g:zip_extractcmd= g:zip_unzipcmd endif +if !exists("g:zip_pwsh") + let g:zip_pwsh='' +endif " --------------------------------------------------------------------- " required early @@ -77,16 +81,50 @@ if !has('nvim-0.10') && v:version < 901 call s:Mess('WarningMsg', "***warning*** this version of zip needs vim 9.1 or later") finish endif -" sanity checks -if !executable(g:zip_unzipcmd) && &shell !~ 'pwsh' - call s:Mess('Error', "***error*** (zip#Browse) unzip not available on your system") - finish + +" --------------------------------------------------------------------- +" Compatibility checks +" PowerShell: {{{2 +" Existence of powershell always means a Windows OS. By default the +" `zip`/`unzip` is not installed on Windows, but the runtime of +" powershell support relative functionalities about zip compression +" and extraction. +" In the following implementations we'll use some powershell scripts +" to manipulate zip files. All these scripts should available to both +" Windows PowerShell 5 and pwsh 7+. + +let s:ps = '' +" order: `g:zip_pwsh` > `&shell`, with executable check +if &shell =~# 'powershell' || g:zip_pwsh =~# 'powershell' + if executable('powershell') + let s:ps = 'powershell' + endif endif -if !dist#vim#IsSafeExecutable('zip', g:zip_unzipcmd) && &shell !~ 'pwsh' - call s:Mess('Error', "Warning: NOT executing " .. g:zip_unzipcmd .. " from current directory!") - finish +if (empty(g:zip_pwsh) && &shell =~# 'pwsh') || g:zip_pwsh =~# 'pwsh' + if executable('pwsh') + let s:ps = 'pwsh' + endif endif +fun! s:isPS() + return !empty(s:ps) +endfun + +" --------------------------------------------------------------------- +" sanity checks +" s:SafeExecutable: {{{2 +fun! s:SafeExecutable(exe) + " fails when exe is a full path with spaces + let exe = substitute(a:exe, '\s\+.*$', '', '') + if !executable(exe) && !s:isPS() + return v:false + endif + if !dist#vim#IsSafeExecutable('zip', exe) && !s:isPS() + call s:Mess('Error', "Warning: NOT executing " .. a:exe .. " from current directory!") + return v:false + endif + return v:true +endfun " ---------------- " PowerShell: {{{1 " ---------------- @@ -94,8 +132,8 @@ endif function! s:TryExecGnuFallBackToPs(executable, gnu_func_call, ...) " Check that a gnu executable is available, run the gnu_func_call if so. If " the gnu executable is not available or if gnu_func_call fails, try - " ps_func_call if &shell =~ 'pwsh'. If all attempts fail, print errors. - " a:executable - one of (g:zip_zipcmd, g:zip_unzipcmd, g:zip_extractcmd) + " ps_func_call if `s:isPS()`. If all attempts fail, print errors. + " a:executable - (string) name of the executable program " a:gnu_func_call - (string) a gnu function call to execute " a:1 - (optional string) a PowerShell function call to execute. let failures = [] @@ -109,12 +147,12 @@ function! s:TryExecGnuFallBackToPs(executable, gnu_func_call, ...) else call add(failures, a:executable.' not available on your system') endif - if &shell =~ 'pwsh' && a:0 == 1 + if s:isPS() && a:0 == 1 try exe a:1 return catch - call add(failures, 'Fallback to PowerShell attempted but failed') + call add(failures, 'Fallback to PowerShell attempted but failed: '.a:1) endtry endif for msg in failures @@ -127,20 +165,22 @@ function! s:ZipBrowsePS(zipfile) " Browse the contents of a zip file using PowerShell's " Equivalent `unzip -Z1 -- zipfile` let cmds = [ + \ 'Add-Type -AssemblyName System.IO.Compression.FileSystem;', \ '$zip = [System.IO.Compression.ZipFile]::OpenRead(' . s:PSEscape(a:zipfile) . ');', \ '$zip.Entries | ForEach-Object { $_.FullName };', \ '$zip.Dispose()' \ ] - return 'pwsh -NoProfile -Command ' . s:Escape(join(cmds, ' '), 1) + return s:ps . ' -NoProfile -Command ' . s:Escape(join(cmds, ' '), 1) endfunction function! s:ZipReadPS(zipfile, fname, tempfile) " Read a filename within a zipped file to a temporary file. " Equivalent to `unzip -p -- zipfile fname > tempfile` - if &shell =~ 'pwsh' + if s:isPS() call s:Mess('WarningMsg', "***warning*** PowerShell can display, but cannot update, files in archive subfolders") endif let cmds = [ + \ 'Add-Type -AssemblyName System.IO.Compression.FileSystem;', \ '$zip = [System.IO.Compression.ZipFile]::OpenRead(' . s:PSEscape(a:zipfile) . ');', \ '$fileEntry = $zip.Entries | Where-Object { $_.FullName -eq ' . s:PSEscape(a:fname) . ' };', \ '$stream = $fileEntry.Open();', @@ -150,13 +190,13 @@ function! s:ZipReadPS(zipfile, fname, tempfile) \ '$stream.Close();', \ '$zip.Dispose()' \ ] - return 'pwsh -NoProfile -Command ' . s:Escape(join(cmds, ' ')) + return s:ps . ' -NoProfile -Command ' . s:Escape(join(cmds, ' ')) endfunction function! s:ZipUpdatePS(zipfile, fname) " Update a filename within a zipped file " Equivalent to `zip -u zipfile fname` - if &shell =~ 'pwsh' && a:fname =~ '/' + if s:isPS() && a:fname =~ '/' call s:Mess('Error', "***error*** PowerShell cannot update files in archive subfolders") return ':' endif @@ -171,6 +211,7 @@ function! s:ZipExtractFilePS(zipfile, fname) return ':' endif let cmds = [ + \ 'Add-Type -AssemblyName System.IO.Compression.FileSystem;', \ '$zip = [System.IO.Compression.ZipFile]::OpenRead(' . s:PSEscape(a:zipfile) . ');', \ '$fileEntry = $zip.Entries | Where-Object { $_.FullName -eq ' . s:PSEscape(a:fname) . ' };', \ '$stream = $fileEntry.Open();', @@ -180,7 +221,7 @@ function! s:ZipExtractFilePS(zipfile, fname) \ '$stream.Close();', \ '$zip.Dispose()' \ ] - return 'pwsh -NoProfile -Command ' . s:Escape(join(cmds, ' ')) + return s:ps . ' -NoProfile -Command ' . s:Escape(join(cmds, ' ')) endfunction function! s:ZipDeleteFilePS(zipfile, fname) @@ -193,7 +234,7 @@ function! s:ZipDeleteFilePS(zipfile, fname) \ 'if ($entry) { $entry.Delete(); $zip.Dispose() }', \ 'else { $zip.Dispose() }' \ ] - return 'pwsh -NoProfile -Command ' . s:Escape(join(cmds, ' ')) + return s:ps . ' -NoProfile -Command ' . s:Escape(join(cmds, ' ')) endfunction " ---------------- @@ -214,8 +255,8 @@ fun! zip#Browse(zipfile) defer s:RestoreOpts(dict) " sanity checks - if !executable(g:zip_unzipcmd) && &shell !~ 'pwsh' - call s:Mess('Error', "***error*** (zip#Browse) unzip not available on your system") + if !s:SafeExecutable(g:zip_unzipcmd) + call s:Mess('Error', "***error*** (zip#Browse) sorry, your system doesn't appear to have the ".g:zip_unzipcmd." program") return endif if !filereadable(a:zipfile) @@ -322,7 +363,7 @@ fun! zip#Read(fname,mode) endif let fname = fname->substitute('[', '[[]', 'g')->escape('?*\\') " sanity check - if !executable(substitute(g:zip_unzipcmd,'\s\+.*$','','')) && &shell !~ 'pwsh' + if !s:SafeExecutable(g:zip_unzipcmd) call s:Mess('Error', "***error*** (zip#Read) sorry, your system doesn't appear to have the ".g:zip_unzipcmd." program") return endif @@ -358,7 +399,7 @@ fun! zip#Write(fname) defer s:RestoreOpts(dict) " sanity checks - if !executable(substitute(g:zip_zipcmd,'\s\+.*$','','')) && &shell !~ 'pwsh' + if !s:SafeExecutable(g:zip_zipcmd) call s:Mess('Error', "***error*** (zip#Write) sorry, your system doesn't appear to have the ".g:zip_zipcmd." program") return endif @@ -442,7 +483,7 @@ fun! zip#Write(fname) let ps_cmd = s:ZipUpdatePS(zip, fname) let ps_cmd = 'call system(''' . substitute(ps_cmd, "'", "''", 'g') . ''')' call s:TryExecGnuFallBackToPs(g:zip_zipcmd, gnu_cmd, ps_cmd) - if &shell =~ 'pwsh' + if s:isPS() " Vim flashes 'creation in progress ...' from what I believe is the " ProgressAction stream of PowerShell. Unfortunately, this cannot be " suppressed (as of 250824) due to an open PowerShell issue. @@ -486,6 +527,13 @@ fun! zip#Extract() let dict = s:SetSaneOpts() defer s:RestoreOpts(dict) + + " sanity checks + if !s:SafeExecutable(g:zip_extractcmd) + call s:Mess('Error', "***error*** (zip#Extract) sorry, your system doesn't appear to have the ".g:zip_extractcmd." program") + return + endif + let fname= getline(".") " sanity check @@ -540,7 +588,7 @@ fun! zip#Extract() if v:shell_error != 0 call s:Mess('Error', "***error*** ".g:zip_extractcmd." ".b:zipfile." ".fname.": failed!") - elseif !filereadable(fname) && &shell !~ 'pwsh' + elseif !filereadable(fname) && !s:isPS() call s:Mess('Error', "***error*** attempted to extract ".fname." but it doesn't appear to be present!") else echomsg "***note*** successfully extracted ".fname diff --git a/runtime/pack/dist/opt/old-zip/doc/pi_zip.txt b/runtime/pack/dist/opt/old-zip/doc/pi_zip.txt index c13ba221bc..eff5a546b2 100644 --- a/runtime/pack/dist/opt/old-zip/doc/pi_zip.txt +++ b/runtime/pack/dist/opt/old-zip/doc/pi_zip.txt @@ -71,16 +71,22 @@ Copyright: Copyright (C) 2005-2015 Charles E Campbell *zip-copyright* "0": > let g:zip_exec=0 < - FALLBACK TO POWERSHELL CORE~ + FALLBACK TO POWERSHELL~ + *g:zip_pwsh* This plugin will first attempt to use the (more capable) GNU zip/unzip commands. If these commands are not available or fail, and the user is - using PowerShell Core (i.e., the 'shell' option matches "pwsh"), the - plugin will fall back to a PowerShell Core cmdlet. The PowerShell Core - cmdlets are limited: they cannot write or extract files within - subdirectories of a zip archive. The advantage, however, is that no - separate unzip binary needs to be installed. - + using PowerShell, the plugin will fall back to a PowerShell cmdlet. + The PowerShell cmdlets are limited: they cannot write or extract files + within subdirectories of a zip archive. The advantage, however, is that + no separate unzip binary needs to be installed. The 'shell' option will + be checked whether to choose "pwsh" or "powershell", or set `g:zip_pwsh`. + If both are available, this plugin will prefer pwsh. Default value of + "g:zip_pwsh" is an empty string, which would not overwrite the 'shell' + option configuration. This functionality is not turned on by default on + MS-Windows, since the default 'shell' is "cmd.exe". For example: > + let g:zip_pwsh='powershell' +< PREVENTING LOADING~ If for some reason you do not wish to use vim to examine zipped files, @@ -116,6 +122,7 @@ Copyright: Copyright (C) 2005-2015 Charles E Campbell *zip-copyright* ============================================================================== 4. History *zip-history* {{{1 unreleased: + Jul 25, 2026 * support both Windows PowerShell 5 and pwsh 7+ Sep 19, 2025 * support PowerShell Core Jul 12, 2025 * drop ../ on write to prevent path traversal attacks Mar 11, 2025 * handle filenames with leading '-' correctly diff --git a/runtime/syntax/marko.vim b/runtime/syntax/marko.vim index be589534c3..96d24970a8 100644 --- a/runtime/syntax/marko.vim +++ b/runtime/syntax/marko.vim @@ -1,21 +1,18 @@ " Vim syntax file " Language: Marko -" Maintainer: Brian Carbone +" Maintainer: Brian Carbone " 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: "

if you want, stop by

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: "
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: -" 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. -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 +" 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 -" Comments are recognized where the parser allows text to start; anchoring -" to the line start keeps "//" inside URLs as text: -" // note /* note */ https://example.com +" 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 +" markoTSDelim is deliberately unlinked: delimiters render plain. +syn cluster markoExpr contains=@markoTS,markoTSBraces,markoTSParens,markoTSBrackets +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

done // for now

plain +" No comment item may take "display": a match skipped during offscreen +" state computation exposes its text, and a " +syn region markoDeclaration start="" +syn region markoDeclaration start="" syn region markoCData start="" -" Declarations; "" may close with ">" or "?>": -" -syn region markoDeclaration start="" -syn region markoDeclaration start="" -" 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 +" p -- +" A block of text, html mode. +" 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 " 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 +" 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" +" 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\)\@!\|=\@1\|/>\)" end="$" - \ contains=@markoExpr syn region markoString contained start=+\z(["']\)+ skip=+\\\z1+ end=+\z1+ contains=@Spell -" Shorthands after the tag name: -" -syn match markoShorthandId contained "#[-[:alnum:]_$]\+" display -syn match markoShorthandClass contained "\.[-[:alnum:]_$]\+" display +" Regions so an interpolation can sit inside one (no keepend: the +" placeholder carries the region past its "}"): +" +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|, and attribute method -" shorthand bodies: +" Tag variables, arguments, |parameters|, and method bodies: " " >
"-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; \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"' + \ . ' 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 +" 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 -" Concise attribute groups may span lines: -" define [ -" size = "large" -" ] +" 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=@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="" \ contains=@markoTagNames,markoPlaceholder -" The name of a close tag whose "". -syn match markoCloseName "\%(". +" It stops before "${", so a dynamic close name's placeholder colors: +" +syn match markoCloseName + \ "/\@1<=\%(" display +" A dynamic close name's placeholder, chained so the closing ">" keeps +" its color: via the body's nextgroup, 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 "" takes its color directly. +syn match markoCloseAnon "\%(" display -" Everything html-mode content may contain. Statement tags and concise -" tags are absent: bare text inside an element body is prose. +" Statement and concise tags are absent: bare text in a body is prose. +" The close-name refinements keep 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 + \markoComment,markoHtmlComment,markoCData,markoDeclaration,markoEscape,markoPlaceholder, + \markoEntity,markoScriptlet,markoCloseName,markoCloseAnon,markoAttrTagName, + \markoTagNameConditional,markoTagNameRepeat,markoTagNameException, + \markoTagNameStatement,markoTagNameBuiltin -" Whether ", -" "=>" and spaced comparisons finds the structural ">", and a "/>" there -" means self-closing, so no body: +" Whether "" finds the structural one, +" and a "/>" there means self-closing, no body: " go(a > b) /> +" -" No body opens for html voids, parsed-text tags, bodiless core tags -" ( 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 ( +" takes no close tag; 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="" end=""' + \ . ' end="" end=""' \ . ' 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: +" heading <${tag}>body exe 'syn region markoDynamicBody' - \ . ' start=+<\ze\%(\$!\={\)\@=' . s:tag_end_ahead . '+' + \ . ' start=+<\ze\%(\%(\k\|:\)*\$!\={\)\@=' . s:tag_end_ahead . '+' \ . ' matchgroup=markoTagDelim' - \ . ' end=""' - \ . ' contains=@markoHtmlContent' + \ . ' end=""' + \ . ' 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, html mode. " -- -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 -" keepend stops an unterminated embedded construct at the close tag. -syn region markoStyle matchgroup=markoTagNameBuiltin - \ start=+<\%(html-\)\=style\>\_[^>]\{-,300}/\@1+ - \ end="" - \ 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: +"