mirror of
https://github.com/neovim/neovim.git
synced 2026-07-31 12:49:11 +00:00
Merge pull request #41069 from zeertzjq/vim-b0e0b22
vim-patch: runtime file updates
This commit is contained in:
94
runtime/pack/dist/opt/old-zip/autoload/zip.vim
vendored
94
runtime/pack/dist/opt/old-zip/autoload/zip.vim
vendored
@@ -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
|
||||
|
||||
21
runtime/pack/dist/opt/old-zip/doc/pi_zip.txt
vendored
21
runtime/pack/dist/opt/old-zip/doc/pi_zip.txt
vendored
@@ -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
|
||||
|
||||
@@ -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:
|
||||
" 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 /* 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 <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
|
||||
" 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'
|
||||
|
||||
" 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
|
||||
" 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
|
||||
" 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 attribute method
|
||||
" shorthand bodies:
|
||||
" 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
|
||||
" 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=@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
|
||||
" 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
|
||||
|
||||
" 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 </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
|
||||
\markoComment,markoHtmlComment,markoCData,markoDeclaration,markoEscape,markoPlaceholder,
|
||||
\markoEntity,markoScriptlet,markoCloseName,markoCloseAnon,markoAttrTagName,
|
||||
\markoTagNameConditional,markoTagNameRepeat,markoTagNameException,
|
||||
\markoTagNameStatement,markoTagNameBuiltin
|
||||
|
||||
" 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:
|
||||
" 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
|
||||
\ 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
|
||||
|
||||
" 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
|
||||
" 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
|
||||
|
||||
@@ -87,7 +87,7 @@ func Test_zip_basic()
|
||||
"## Check opening zip when "unzip" program is missing
|
||||
let save_zip_unzipcmd = g:zip_unzipcmd
|
||||
let g:zip_unzipcmd = "/"
|
||||
call assert_match('unzip not available on your system', execute("e X.zip"))
|
||||
call assert_match('(zip#Browse) sorry, your system doesn''t appear to have the / program', execute("e X.zip"))
|
||||
|
||||
"## Check when "unzip" don't work
|
||||
if executable("false")
|
||||
|
||||
Reference in New Issue
Block a user