aboutsummaryrefslogtreecommitdiff
path: root/runtime
diff options
context:
space:
mode:
Diffstat (limited to 'runtime')
-rw-r--r--runtime/autoload/man.vim2
-rw-r--r--runtime/autoload/xmlformat.vim164
-rw-r--r--runtime/compiler/xmllint.vim18
-rw-r--r--runtime/doc/options.txt1
-rw-r--r--runtime/doc/treesitter.txt13
-rw-r--r--runtime/filetype.vim15
-rw-r--r--runtime/indent/xml.vim15
-rw-r--r--runtime/lua/vim/treesitter.lua141
-rw-r--r--runtime/lua/vim/treesitter/highlighter.lua239
-rw-r--r--runtime/lua/vim/treesitter/languagetree.lua454
-rw-r--r--runtime/syntax/gitconfig.vim12
-rw-r--r--runtime/syntax/xml.vim43
12 files changed, 822 insertions, 295 deletions
diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim
index dab88fde23..5008b8cfaf 100644
--- a/runtime/autoload/man.vim
+++ b/runtime/autoload/man.vim
@@ -13,8 +13,6 @@ function! man#init() abort
try
" Check for -l support.
call s:get_page(s:get_path('', 'man'))
- catch /E145:/
- " Ignore the error in restricted mode
catch /command error .*/
let s:localfile_arg = v:false
endtry
diff --git a/runtime/autoload/xmlformat.vim b/runtime/autoload/xmlformat.vim
index ea89401977..712e6f7351 100644
--- a/runtime/autoload/xmlformat.vim
+++ b/runtime/autoload/xmlformat.vim
@@ -1,6 +1,6 @@
" Vim plugin for formatting XML
-" Last Change: Thu, 07 Dec 2018
-" Version: 0.1
+" Last Change: 2020 Jan 06
+" Version: 0.3
" Author: Christian Brabandt <cb@256bit.org>
" Repository: https://github.com/chrisbra/vim-xml-ftplugin
" License: VIM License
@@ -15,51 +15,92 @@ let s:keepcpo = &cpo
set cpo&vim
" Main function: Format the input {{{1
-func! xmlformat#Format()
+func! xmlformat#Format() abort
" only allow reformatting through the gq command
" (e.g. Vim is in normal mode)
if mode() != 'n'
" do not fall back to internal formatting
return 0
endif
+ let count_orig = v:count
let sw = shiftwidth()
let prev = prevnonblank(v:lnum-1)
let s:indent = indent(prev)/sw
let result = []
let lastitem = prev ? getline(prev) : ''
let is_xml_decl = 0
- " split on `<`, but don't split on very first opening <
- for item in split(join(getline(v:lnum, (v:lnum + v:count - 1))), '.\@<=[>]\zs')
- if s:EndTag(item)
- let s:indent = s:DecreaseIndent()
- call add(result, s:Indent(item))
- elseif s:EmptyTag(lastitem)
- call add(result, s:Indent(item))
- elseif s:StartTag(lastitem) && s:IsTag(item)
- let s:indent += 1
- call add(result, s:Indent(item))
- else
- if !s:IsTag(item)
- " Simply split on '<'
- let t=split(item, '.<\@=\zs')
- let s:indent+=1
- call add(result, s:Indent(t[0]))
- let s:indent = s:DecreaseIndent()
- call add(result, s:Indent(t[1]))
- else
+ " go through every line, but don't join all content together and join it
+ " back. We might lose empty lines
+ let list = getline(v:lnum, (v:lnum + count_orig - 1))
+ let current = 0
+ for line in list
+ " Keep empty input lines?
+ if empty(line)
+ call add(result, '')
+ continue
+ elseif line !~# '<[/]\?[^>]*>'
+ let nextmatch = match(list, '<[/]\?[^>]*>', current)
+ if nextmatch > -1
+ let line .= ' '. join(list[(current + 1):(nextmatch-1)], " ")
+ call remove(list, current+1, nextmatch-1)
+ endif
+ endif
+ " split on `>`, but don't split on very first opening <
+ " this means, items can be like ['<tag>', 'tag content</tag>']
+ for item in split(line, '.\@<=[>]\zs')
+ if s:EndTag(item)
+ call s:DecreaseIndent()
+ call add(result, s:Indent(item))
+ elseif s:EmptyTag(lastitem)
call add(result, s:Indent(item))
+ elseif s:StartTag(lastitem) && s:IsTag(item)
+ let s:indent += 1
+ call add(result, s:Indent(item))
+ else
+ if !s:IsTag(item)
+ " Simply split on '<', if there is one,
+ " but reformat according to &textwidth
+ let t=split(item, '.<\@=\zs')
+
+ " if the content fits well within a single line, add it there
+ " so that the output looks like this:
+ "
+ " <foobar>1</foobar>
+ if s:TagContent(lastitem) is# s:TagContent(t[1]) && strlen(result[-1]) + strlen(item) <= s:Textwidth()
+ let result[-1] .= item
+ let lastitem = t[1]
+ continue
+ endif
+ " t should only contain 2 items, but just be safe here
+ if s:IsTag(lastitem)
+ let s:indent+=1
+ endif
+ let result+=s:FormatContent([t[0]])
+ if s:EndTag(t[1])
+ call s:DecreaseIndent()
+ endif
+ "for y in t[1:]
+ let result+=s:FormatContent(t[1:])
+ "endfor
+ else
+ call add(result, s:Indent(item))
+ endif
endif
- endif
- let lastitem = item
- endfor
+ let lastitem = item
+ endfor
+ let current += 1
+ endfor
- if !empty(result)
- exe v:lnum. ",". (v:lnum + v:count - 1). 'd'
+ if !empty(result)
+ let lastprevline = getline(v:lnum + count_orig)
+ let delete_lastline = v:lnum + count_orig - 1 == line('$')
+ exe v:lnum. ",". (v:lnum + count_orig - 1). 'd'
call append(v:lnum - 1, result)
" Might need to remove the last line, if it became empty because of the
" append() call
let last = v:lnum + len(result)
- if getline(last) is ''
+ " do not use empty(), it returns true for `empty(0)`
+ if getline(last) is '' && lastprevline is '' && delete_lastline
exe last. 'd'
endif
endif
@@ -68,15 +109,15 @@ func! xmlformat#Format()
return 0
endfunc
" Check if given tag is XML Declaration header {{{1
-func! s:IsXMLDecl(tag)
+func! s:IsXMLDecl(tag) abort
return a:tag =~? '^\s*<?xml\s\?\%(version="[^"]*"\)\?\s\?\%(encoding="[^"]*"\)\? ?>\s*$'
endfunc
" Return tag indented by current level {{{1
-func! s:Indent(item)
+func! s:Indent(item) abort
return repeat(' ', shiftwidth()*s:indent). s:Trim(a:item)
endfu
" Return item trimmed from leading whitespace {{{1
-func! s:Trim(item)
+func! s:Trim(item) abort
if exists('*trim')
return trim(a:item)
else
@@ -84,30 +125,77 @@ func! s:Trim(item)
endif
endfunc
" Check if tag is a new opening tag <tag> {{{1
-func! s:StartTag(tag)
+func! s:StartTag(tag) abort
let is_comment = s:IsComment(a:tag)
return a:tag =~? '^\s*<[^/?]' && !is_comment
endfunc
-func! s:IsComment(tag)
+" Check if tag is a Comment start {{{1
+func! s:IsComment(tag) abort
return a:tag =~? '<!--'
endfunc
" Remove one level of indentation {{{1
-func! s:DecreaseIndent()
- return (s:indent > 0 ? s:indent - 1 : 0)
+func! s:DecreaseIndent() abort
+ let s:indent = (s:indent > 0 ? s:indent - 1 : 0)
endfunc
" Check if tag is a closing tag </tag> {{{1
-func! s:EndTag(tag)
+func! s:EndTag(tag) abort
return a:tag =~? '^\s*</'
endfunc
" Check that the tag is actually a tag and not {{{1
" something like "foobar</foobar>"
-func! s:IsTag(tag)
+func! s:IsTag(tag) abort
return s:Trim(a:tag)[0] == '<'
endfunc
" Check if tag is empty <tag/> {{{1
-func! s:EmptyTag(tag)
+func! s:EmptyTag(tag) abort
return a:tag =~ '/>\s*$'
endfunc
+func! s:TagContent(tag) abort "{{{1
+ " Return content of a tag
+ return substitute(a:tag, '^\s*<[/]\?\([^>]*\)>\s*$', '\1', '')
+endfunc
+func! s:Textwidth() abort "{{{1
+ " return textwidth (or 80 if not set)
+ return &textwidth == 0 ? 80 : &textwidth
+endfunc
+" Format input line according to textwidth {{{1
+func! s:FormatContent(list) abort
+ let result=[]
+ let limit = s:Textwidth()
+ let column=0
+ let idx = -1
+ let add_indent = 0
+ let cnt = 0
+ for item in a:list
+ for word in split(item, '\s\+\S\+\zs')
+ if match(word, '^\s\+$') > -1
+ " skip empty words
+ continue
+ endif
+ let column += strdisplaywidth(word, column)
+ if match(word, "^\\s*\n\\+\\s*$") > -1
+ call add(result, '')
+ let idx += 1
+ let column = 0
+ let add_indent = 1
+ elseif column > limit || cnt == 0
+ let add = s:Indent(s:Trim(word))
+ call add(result, add)
+ let column = strdisplaywidth(add)
+ let idx += 1
+ else
+ if add_indent
+ let result[idx] = s:Indent(s:Trim(word))
+ else
+ let result[idx] .= ' '. s:Trim(word)
+ endif
+ let add_indent = 0
+ endif
+ let cnt += 1
+ endfor
+ endfor
+ return result
+endfunc
" Restoration And Modelines: {{{1
let &cpo= s:keepcpo
unlet s:keepcpo
diff --git a/runtime/compiler/xmllint.vim b/runtime/compiler/xmllint.vim
index 96cfa55383..79d38b4d14 100644
--- a/runtime/compiler/xmllint.vim
+++ b/runtime/compiler/xmllint.vim
@@ -1,7 +1,7 @@
" Vim compiler file
-" Compiler: xmllint
+" Compiler: Libxml2 Command-Line Tool
" Maintainer: Doug Kearns <dougkearns@gmail.com>
-" Last Change: 2013 Jul 8
+" Last Change: 2020 Jul 30
if exists("current_compiler")
finish
@@ -13,14 +13,16 @@ if exists(":CompilerSet") != 2 " older Vim always used :setlocal
endif
let s:cpo_save = &cpo
-set cpo-=C
+set cpo&vim
CompilerSet makeprg=xmllint\ --valid\ --noout
-
-CompilerSet errorformat=%+E%f:%l:\ %.%#\ error\ :\ %m,
- \%+W%f:%l:\ %.%#\ warning\ :\ %m,
- \%-Z%p^,
- \%-G%.%#
+CompilerSet errorformat=%E%f:%l:\ %.%#\ error\ :\ %m,
+ \%W%f:%l:\ %.%#\ warning\ :\ %m,
+ \%-Z%p^,
+ \%C%.%#,
+ \%terror:\ %m,
+ \%tarning:\ %m,
+ \%-G%.%#
let &cpo = s:cpo_save
unlet s:cpo_save
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index b83d2c4484..6c42dd6739 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -5166,6 +5166,7 @@ A jump table for the options with a short description can be found at |Q_op|.
It is allowed to give an argument to the command, e.g. "csh -f".
See |option-backslash| about including spaces and backslashes.
Environment variables are expanded |:set_env|.
+
If the name of the shell contains a space, you might need to enclose
it in quotes. Example: >
:set shell=\"c:\program\ files\unix\sh.exe\"\ -f
diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt
index 58cd535e98..b6a238f158 100644
--- a/runtime/doc/treesitter.txt
+++ b/runtime/doc/treesitter.txt
@@ -59,15 +59,16 @@ shouldn't be done directly in the change callback anyway as they will be very
frequent. Rather a plugin that does any kind of analysis on a tree should use
a timer to throttle too frequent updates.
-tsparser:set_included_ranges({ranges}) *tsparser:set_included_ranges()*
- Changes the ranges the parser should consider. This is used for
- language injection. {ranges} should be of the form (all zero-based): >
+tsparser:set_included_regions({region_list}) *tsparser:set_included_regions()*
+ Changes the regions the parser should consider. This is used for
+ language injection. {region_list} should be of the form (all zero-based): >
{
- {start_node, end_node},
+ {node1, node2},
...
}
<
- NOTE: `start_node` and `end_node` are both inclusive.
+ `node1` and `node2` are both considered part of the same region and
+ will be parsed together with the parser in the same context.
Tree methods *lua-treesitter-tree*
@@ -253,7 +254,7 @@ Here is a list of built-in predicates :
`lua-match?` *ts-predicate-lua-match?*
This will match the same way than |match?| but using lua
regexes.
-
+
`contains?` *ts-predicate-contains?*
Will check if any of the following arguments appears in the
text corresponding to the node : >
diff --git a/runtime/filetype.vim b/runtime/filetype.vim
index 4e617052a9..4e54bcaefd 100644
--- a/runtime/filetype.vim
+++ b/runtime/filetype.vim
@@ -692,12 +692,21 @@ au BufNewFile,BufRead .gtkrc,gtkrc setf gtkrc
au BufNewFile,BufRead *.haml setf haml
" Hamster Classic | Playground files
-au BufNewFile,BufRead *.hsc,*.hsm setf hamster
+au BufNewFile,BufRead *.hsm setf hamster
+au BufNewFile,BufRead *.hsc
+ \ if match(join(getline(1,10), "\n"), '\%(^\|\n\)\s*\%({-#\_s*LANGUAGE\>\|\<module\>\)') != -1 |
+ \ setf haskell |
+ \ else |
+ \ setf hamster |
+ \ endif
" Haskell
au BufNewFile,BufRead *.hs,*.hs-boot setf haskell
au BufNewFile,BufRead *.lhs setf lhaskell
au BufNewFile,BufRead *.chs setf chaskell
+au BufNewFile,BufRead cabal.project setf cabalproject
+au BufNewFile,BufRead $HOME/.cabal/config setf cabalconfig
+au BufNewFile,BufRead cabal.config setf cabalconfig
" Haste
au BufNewFile,BufRead *.ht setf haste
@@ -2042,12 +2051,14 @@ au BufNewFile,BufRead bzr_log.* setf bzr
" Bazel build file
if !has("fname_case")
- au BufNewFile,BufRead *.BUILD,BUILD setf bzl
+ au BufNewFile,BufRead *.BUILD,BUILD setf bzl
endif
" BIND zone
au BufNewFile,BufRead */named/db.*,*/bind/db.* call s:StarSetf('bindzone')
+au BufNewFile,BufRead cabal.project.* call s:StarSetf('cabalproject')
+
" Calendar
au BufNewFile,BufRead */.calendar/*,
\*/share/calendar/*/calendar.*,*/share/calendar/calendar.*
diff --git a/runtime/indent/xml.vim b/runtime/indent/xml.vim
index 883af98563..413a3ddb53 100644
--- a/runtime/indent/xml.vim
+++ b/runtime/indent/xml.vim
@@ -1,9 +1,10 @@
-" Language: xml
-" Repository: https://github.com/chrisbra/vim-xml-ftplugin
-" Last Changed: July 27, 2019
-" Maintainer: Christian Brabandt <cb@256bit.org>
-" Previous Maintainer: Johannes Zellner <johannes@zellner.org>
+" Language: XML
+" Maintainer: Christian Brabandt <cb@256bit.org>
+" Repository: https://github.com/chrisbra/vim-xml-ftplugin
+" Previous Maintainer: Johannes Zellner <johannes@zellner.org>
+" Last Changed: 2019 Dec 02
" Last Change:
+" 20191202 - Handle docbk filetype
" 20190726 - Correctly handle non-tagged data
" 20190204 - correctly handle wrap tags
" https://github.com/chrisbra/vim-xml-ftplugin/issues/5
@@ -134,7 +135,7 @@ fun! XmlIndentGet(lnum, use_syntax_check)
if syn_name_end =~ 'Comment' && syn_name_start =~ 'Comment'
return <SID>XmlIndentComment(a:lnum)
- elseif empty(syn_name_start) && empty(syn_name_end)
+ elseif empty(syn_name_start) && empty(syn_name_end) && a:use_syntax_check
" non-xml tag content: use indent from 'autoindent'
return pind + shiftwidth()
endif
@@ -148,7 +149,7 @@ endfun
func! <SID>IsXMLContinuation(line)
" Checks, whether or not the line matches a start-of-tag
- return a:line !~ '^\s*<'
+ return a:line !~ '^\s*<' && &ft is# 'xml'
endfunc
func! <SID>HasNoTagEnd(line)
diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
index 19ef148afc..6886f0c178 100644
--- a/runtime/lua/vim/treesitter.lua
+++ b/runtime/lua/vim/treesitter.lua
@@ -1,98 +1,13 @@
local a = vim.api
local query = require'vim.treesitter.query'
local language = require'vim.treesitter.language'
+local LanguageTree = require'vim.treesitter.languagetree'
-- TODO(bfredl): currently we retain parsers for the lifetime of the buffer.
-- Consider use weak references to release parser if all plugins are done with
-- it.
local parsers = {}
-local Parser = {}
-Parser.__index = Parser
-
---- Parses the buffer if needed and returns a tree.
---
--- Calling this will call the on_changedtree callbacks if the tree has changed.
---
--- @returns An up to date tree
--- @returns If the tree changed with this call, the changed ranges
-function Parser:parse()
- if self.valid then
- return self._tree_immutable
- end
- local changes
-
- self._tree, changes = self._parser:parse(self._tree, self:input_source())
-
- self._tree_immutable = self._tree:copy()
-
- self.valid = true
-
- if not vim.tbl_isempty(changes) then
- for _, cb in ipairs(self.changedtree_cbs) do
- cb(changes)
- end
- end
-
- return self._tree_immutable, changes
-end
-
-function Parser:input_source()
- return self.bufnr or self.str
-end
-
-function Parser:_on_bytes(bufnr, changed_tick,
- start_row, start_col, start_byte,
- old_row, old_col, old_byte,
- new_row, new_col, new_byte)
- local old_end_col = old_col + ((old_row == 0) and start_col or 0)
- local new_end_col = new_col + ((new_row == 0) and start_col or 0)
- self._tree:edit(start_byte,start_byte+old_byte,start_byte+new_byte,
- start_row, start_col,
- start_row+old_row, old_end_col,
- start_row+new_row, new_end_col)
- self.valid = false
-
- for _, cb in ipairs(self.bytes_cbs) do
- cb(bufnr, changed_tick,
- start_row, start_col, start_byte,
- old_row, old_col, old_byte,
- new_row, new_col, new_byte)
- end
-end
-
---- Registers callbacks for the parser
--- @param cbs An `nvim_buf_attach`-like table argument with the following keys :
--- `on_bytes` : see `nvim_buf_attach`, but this will be called _after_ the parsers callback.
--- `on_changedtree` : a callback that will be called everytime the tree has syntactical changes.
--- it will only be passed one argument, that is a table of the ranges (as node ranges) that
--- changed.
-function Parser:register_cbs(cbs)
- if not cbs then return end
-
- if cbs.on_changedtree then
- table.insert(self.changedtree_cbs, cbs.on_changedtree)
- end
-
- if cbs.on_bytes then
- table.insert(self.bytes_cbs, cbs.on_bytes)
- end
-end
-
---- Sets the included ranges for the current parser
---
--- @param ranges A table of nodes that will be used as the ranges the parser should include.
-function Parser:set_included_ranges(ranges)
- self._parser:set_included_ranges(ranges)
- -- The buffer will need to be parsed again later
- self.valid = false
-end
-
---- Gets the included ranges for the parsers
-function Parser:included_ranges()
- return self._parser:included_ranges()
-end
-
local M = vim.tbl_extend("error", query, language)
setmetatable(M, {
@@ -113,9 +28,9 @@ setmetatable(M, {
-- It is not recommended to use this, use vim.treesitter.get_parser() instead.
--
-- @param bufnr The buffer the parser will be tied to
--- @param lang The language of the parser.
--- @param id The id the parser will have
-function M._create_parser(bufnr, lang, id)
+-- @param lang The language of the parser
+-- @param opts Options to pass to the language tree
+function M._create_parser(bufnr, lang, opts)
language.require_language(lang)
if bufnr == 0 then
bufnr = a.nvim_get_current_buf()
@@ -123,25 +38,22 @@ function M._create_parser(bufnr, lang, id)
vim.fn.bufload(bufnr)
- local self = setmetatable({bufnr=bufnr, lang=lang, valid=false}, Parser)
- self._parser = vim._create_ts_parser(lang)
- self.changedtree_cbs = {}
- self.bytes_cbs = {}
- self:parse()
- -- TODO(bfredl): use weakref to self, so that the parser is free'd is no plugin is
- -- using it.
+ local self = LanguageTree.new(bufnr, lang, opts)
+
local function bytes_cb(_, ...)
- return self:_on_bytes(...)
+ self:_on_bytes(...)
end
- local detach_cb = nil
- if id ~= nil then
- detach_cb = function()
- if parsers[id] == self then
- parsers[id] = nil
- end
+
+ local function detach_cb()
+ if parsers[bufnr] == self then
+ parsers[bufnr] = nil
end
end
+
a.nvim_buf_attach(self.bufnr, false, {on_bytes=bytes_cb, on_detach=detach_cb})
+
+ self:parse()
+
return self
end
@@ -152,39 +64,36 @@ end
--
-- @param bufnr The buffer the parser should be tied to
-- @param ft The filetype of this parser
--- @param buf_attach_cbs See Parser:register_cbs
+-- @param opts Options object to pass to the parser
--
-- @returns The parser
-function M.get_parser(bufnr, lang, buf_attach_cbs)
+function M.get_parser(bufnr, lang, opts)
+ opts = opts or {}
+
if bufnr == nil or bufnr == 0 then
bufnr = a.nvim_get_current_buf()
end
if lang == nil then
lang = a.nvim_buf_get_option(bufnr, "filetype")
end
- local id = tostring(bufnr)..'_'..lang
- if parsers[id] == nil then
- parsers[id] = M._create_parser(bufnr, lang, id)
+ if parsers[bufnr] == nil then
+ parsers[bufnr] = M._create_parser(bufnr, lang, opts)
end
- parsers[id]:register_cbs(buf_attach_cbs)
+ parsers[bufnr]:register_cbs(opts.buf_attach_cbs)
- return parsers[id]
+ return parsers[bufnr]
end
-function M.get_string_parser(str, lang)
+function M.get_string_parser(str, lang, opts)
vim.validate {
str = { str, 'string' },
lang = { lang, 'string' }
}
language.require_language(lang)
- local self = setmetatable({str=str, lang=lang, valid=false}, Parser)
- self._parser = vim._create_ts_parser(lang)
- self:parse()
-
- return self
+ return LanguageTree.new(str, lang, opts)
end
return M
diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua
index 6714bb6354..60db7f24cf 100644
--- a/runtime/lua/vim/treesitter/highlighter.lua
+++ b/runtime/lua/vim/treesitter/highlighter.lua
@@ -1,4 +1,5 @@
local a = vim.api
+local query = require"vim.treesitter.query"
-- support reload for quick experimentation
local TSHighlighter = rawget(vim.treesitter, 'TSHighlighter') or {}
@@ -6,6 +7,9 @@ TSHighlighter.__index = TSHighlighter
TSHighlighter.active = TSHighlighter.active or {}
+local TSHighlighterQuery = {}
+TSHighlighterQuery.__index = TSHighlighterQuery
+
local ns = a.nvim_create_namespace("treesitter/highlighter")
-- These are conventions defined by nvim-treesitter, though it
@@ -56,27 +60,83 @@ TSHighlighter.hl_map = {
["include"] = "Include",
}
-function TSHighlighter.new(parser, query)
+local function is_highlight_name(capture_name)
+ local firstc = string.sub(capture_name, 1, 1)
+ return firstc ~= string.lower(firstc)
+end
+
+function TSHighlighterQuery.new(lang, query_string)
+ local self = setmetatable({}, { __index = TSHighlighterQuery })
+
+ self.hl_cache = setmetatable({}, {
+ __index = function(table, capture)
+ local hl = self:get_hl_from_capture(capture)
+ rawset(table, capture, hl)
+
+ return hl
+ end
+ })
+
+ if query_string then
+ self._query = query.parse_query(lang, query_string)
+ else
+ self._query = query.get_query(lang, "highlights")
+ end
+
+ return self
+end
+
+function TSHighlighterQuery:query()
+ return self._query
+end
+
+function TSHighlighterQuery:get_hl_from_capture(capture)
+ local name = self._query.captures[capture]
+
+ if is_highlight_name(name) then
+ -- From "Normal.left" only keep "Normal"
+ return vim.split(name, '.', true)[1]
+ else
+ -- Default to false to avoid recomputing
+ local hl = TSHighlighter.hl_map[name]
+ return hl and a.nvim_get_hl_id_by_name(hl) or 0
+ end
+end
+
+function TSHighlighter.new(tree, opts)
local self = setmetatable({}, TSHighlighter)
- self.parser = parser
- parser:register_cbs {
- on_changedtree = function(...) self:on_changedtree(...) end
+ if type(tree:source()) ~= "number" then
+ error("TSHighlighter can not be used with a string parser source.")
+ end
+
+ opts = opts or {}
+ self.tree = tree
+ tree:register_cbs {
+ on_changedtree = function(...) self:on_changedtree(...) end,
+ on_bytes = function(...) self:on_bytes(...) end
}
- self:set_query(query)
+ self.bufnr = tree:source()
self.edit_count = 0
self.redraw_count = 0
self.line_count = {}
- self.root = self.parser:parse():root()
- a.nvim_buf_set_option(self.buf, "syntax", "")
-
- -- TODO(bfredl): can has multiple highlighters per buffer????
- if not TSHighlighter.active[parser.bufnr] then
- TSHighlighter.active[parser.bufnr] = {}
+ -- A map of highlight states.
+ -- This state is kept during rendering across each line update.
+ self._highlight_states = {}
+ self._queries = {}
+
+ -- Queries for a specific language can be overridden by a custom
+ -- string query... if one is not provided it will be looked up by file.
+ if opts.queries then
+ for lang, query_string in pairs(opts.queries) do
+ self._queries[lang] = TSHighlighterQuery.new(lang, query_string)
+ end
end
- TSHighlighter.active[parser.bufnr][parser.lang] = self
+ a.nvim_buf_set_option(self.bufnr, "syntax", "")
+
+ TSHighlighter.active[self.bufnr] = self
-- Tricky: if syntax hasn't been enabled, we need to reload color scheme
-- but use synload.vim rather than syntax.vim to not enable
@@ -85,119 +145,112 @@ function TSHighlighter.new(parser, query)
if vim.g.syntax_on ~= 1 then
vim.api.nvim_command("runtime! syntax/synload.vim")
end
+
+ self.tree:parse()
+
return self
end
-local function is_highlight_name(capture_name)
- local firstc = string.sub(capture_name, 1, 1)
- return firstc ~= string.lower(firstc)
+function TSHighlighter:destroy()
+ if TSHighlighter.active[self.bufnr] then
+ TSHighlighter.active[self.bufnr] = nil
+ end
end
-function TSHighlighter:get_hl_from_capture(capture)
+function TSHighlighter:get_highlight_state(tstree)
+ if not self._highlight_states[tstree] then
+ self._highlight_states[tstree] = {
+ next_row = 0,
+ iter = nil
+ }
+ end
- local name = self.query.captures[capture]
+ return self._highlight_states[tstree]
+end
- if is_highlight_name(name) then
- -- From "Normal.left" only keep "Normal"
- return vim.split(name, '.', true)[1]
- else
- -- Default to false to avoid recomputing
- local hl = TSHighlighter.hl_map[name]
- return hl and a.nvim_get_hl_id_by_name(hl) or 0
- end
+function TSHighlighter:reset_highlight_state()
+ self._highlight_states = {}
+end
+
+function TSHighlighter:on_bytes(_, _, start_row, _, _, _, _, _, new_end)
+ a.nvim__buf_redraw_range(self.bufnr, start_row, start_row + new_end + 1)
end
function TSHighlighter:on_changedtree(changes)
for _, ch in ipairs(changes or {}) do
- a.nvim__buf_redraw_range(self.buf, ch[1], ch[3]+1)
+ a.nvim__buf_redraw_range(self.bufnr, ch[1], ch[3]+1)
end
end
-function TSHighlighter:set_query(query)
- if type(query) == "string" then
- query = vim.treesitter.parse_query(self.parser.lang, query)
+function TSHighlighter:get_query(lang)
+ if not self._queries[lang] then
+ self._queries[lang] = TSHighlighterQuery.new(lang)
end
- self.query = query
+ return self._queries[lang]
+end
- self.hl_cache = setmetatable({}, {
- __index = function(table, capture)
- local hl = self:get_hl_from_capture(capture)
- rawset(table, capture, hl)
+local function on_line_impl(self, buf, line)
+ self.tree:for_each_tree(function(tstree, tree)
+ if not tstree then return end
- return hl
+ local root_node = tstree:root()
+ local root_start_row, _, root_end_row, _ = root_node:range()
+
+ -- Only worry about trees within the line range
+ if root_start_row > line or root_end_row < line then return end
+
+ local state = self:get_highlight_state(tstree)
+ local highlighter_query = self:get_query(tree:lang())
+
+ if state.iter == nil then
+ state.iter = highlighter_query:query():iter_captures(root_node, self.bufnr, line, root_end_row + 1)
end
- })
- a.nvim__buf_redraw_range(self.parser.bufnr, 0, a.nvim_buf_line_count(self.parser.bufnr))
-end
+ while line >= state.next_row do
+ local capture, node = state.iter()
-local function iter_active_tshl(buf, fn)
- for _, hl in pairs(TSHighlighter.active[buf] or {}) do
- fn(hl)
- end
-end
+ if capture == nil then break end
-local function on_line_impl(self, buf, line)
- if self.root == nil then
- return -- parser bought the farm already
- end
+ local start_row, start_col, end_row, end_col = node:range()
+ local hl = highlighter_query.hl_cache[capture]
- if self.iter == nil then
- self.iter = self.query:iter_captures(self.root,buf,line,self.botline)
- end
- while line >= self.nextrow do
- local capture, node = self.iter()
- if capture == nil then
- break
+ if hl and end_row >= line then
+ a.nvim_buf_set_extmark(buf, ns, start_row, start_col,
+ { end_line = end_row, end_col = end_col,
+ hl_group = hl,
+ ephemeral = true
+ })
+ end
+ if start_row > line then
+ state.next_row = start_row
+ end
end
- local start_row, start_col, end_row, end_col = node:range()
- local hl = self.hl_cache[capture]
- if hl and end_row >= line then
- a.nvim_buf_set_extmark(buf, ns, start_row, start_col,
- { end_line = end_row, end_col = end_col,
- hl_group = hl,
- ephemeral = true,
- })
- end
- if start_row > line then
- self.nextrow = start_row
- end
- end
+ end, true)
end
-function TSHighlighter._on_line(_, _win, buf, line, highlighter)
- -- on_line is only called when this is non-nil
- if highlighter then
- on_line_impl(highlighter, buf, line)
- else
- iter_active_tshl(buf, function(self)
- on_line_impl(self, buf, line)
- end)
- end
+function TSHighlighter._on_line(_, _win, buf, line, _)
+ local self = TSHighlighter.active[buf]
+ if not self then return end
+
+ on_line_impl(self, buf, line)
end
function TSHighlighter._on_buf(_, buf)
- iter_active_tshl(buf, function(self)
- if self then
- local tree = self.parser:parse()
- self.root = (tree and tree:root()) or nil
- end
- end)
+ local self = TSHighlighter.active[buf]
+ if self then
+ self.tree:parse()
+ end
end
-function TSHighlighter._on_win(_, _win, buf, _topline, botline)
- iter_active_tshl(buf, function(self)
- if not self then
- return false
- end
+function TSHighlighter._on_win(_, _win, buf, _topline)
+ local self = TSHighlighter.active[buf]
+ if not self then
+ return false
+ end
- self.iter = nil
- self.nextrow = 0
- self.botline = botline
- self.redraw_count = self.redraw_count + 1
- return true
- end)
+ self:reset_highlight_state()
+ self.redraw_count = self.redraw_count + 1
return true
end
diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
new file mode 100644
index 0000000000..70e2ac4c62
--- /dev/null
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -0,0 +1,454 @@
+local a = vim.api
+local query = require'vim.treesitter.query'
+local language = require'vim.treesitter.language'
+
+local LanguageTree = {}
+LanguageTree.__index = LanguageTree
+
+-- Represents a single treesitter parser for a language.
+-- The language can contain child languages with in it's range,
+-- hence the tree.
+--
+-- @param source Can be a bufnr or a string of text to parse
+-- @param lang The language this tree represents
+-- @param opts Options table
+-- @param opts.queries A table of language to injection query strings
+-- This is useful for overridding the built in runtime file
+-- searching for the injection language query per language.
+function LanguageTree.new(source, lang, opts)
+ language.require_language(lang)
+ opts = opts or {}
+
+ local custom_queries = opts.queries or {}
+ local self = setmetatable({
+ _source=source,
+ _lang=lang,
+ _children = {},
+ _regions = {},
+ _trees = {},
+ _opts = opts,
+ _injection_query = custom_queries[lang]
+ and query.parse_query(lang, custom_queries[lang])
+ or query.get_query(lang, "injections"),
+ _valid = false,
+ _parser = vim._create_ts_parser(lang),
+ _callbacks = {
+ changedtree = {},
+ bytes = {},
+ child_added = {},
+ child_removed = {}
+ },
+ }, LanguageTree)
+
+
+ return self
+end
+
+-- Invalidates this parser and all it's children
+function LanguageTree:invalidate()
+ self._valid = false
+
+ for _, child in ipairs(self._children) do
+ child:invalidate()
+ end
+end
+
+-- Returns all trees this language tree contains.
+-- Does not include child languages.
+function LanguageTree:trees()
+ return self._trees
+end
+
+-- Gets the language of this tree layer.
+function LanguageTree:lang()
+ return self._lang
+end
+
+-- Determines whether this tree is valid.
+-- If the tree is invalid, `parse()` must be called
+-- to get the an updated tree.
+function LanguageTree:is_valid()
+ return self._valid
+end
+
+-- Returns a map of language to child tree.
+function LanguageTree:children()
+ return self._children
+end
+
+-- Returns the source content of the language tree (bufnr or string).
+function LanguageTree:source()
+ return self._source
+end
+
+-- Parses all defined regions using a treesitter parser
+-- for the language this tree represents.
+-- This will run the injection query for this language to
+-- determine if any child languages should be created.
+function LanguageTree:parse()
+ if self._valid then
+ return self._trees
+ end
+
+ local parser = self._parser
+ local changes = {}
+
+ local old_trees = self._trees
+ self._trees = {}
+
+ -- If there are no ranges, set to an empty list
+ -- so the included ranges in the parser ar cleared.
+ if self._regions and #self._regions > 0 then
+ for i, ranges in ipairs(self._regions) do
+ local old_tree = old_trees[i]
+ parser:set_included_ranges(ranges)
+
+ local tree, tree_changes = parser:parse(old_tree, self._source)
+
+ table.insert(self._trees, tree)
+ vim.list_extend(changes, tree_changes)
+ end
+ else
+ local tree, tree_changes = parser:parse(old_trees[1], self._source)
+
+ table.insert(self._trees, tree)
+ vim.list_extend(changes, tree_changes)
+ end
+
+ local injections_by_lang = self:_get_injections()
+ local seen_langs = {}
+
+ for lang, injection_ranges in pairs(injections_by_lang) do
+ local child = self._children[lang]
+
+ if not child then
+ child = self:add_child(lang)
+ end
+
+ child:set_included_regions(injection_ranges)
+
+ local _, child_changes = child:parse()
+
+ -- Propagate any child changes so they are included in the
+ -- the change list for the callback.
+ if child_changes then
+ vim.list_extend(changes, child_changes)
+ end
+
+ seen_langs[lang] = true
+ end
+
+ for lang, _ in pairs(self._children) do
+ if not seen_langs[lang] then
+ self:remove_child(lang)
+ end
+ end
+
+ self._valid = true
+
+ self:_do_callback('changedtree', changes)
+ return self._trees, changes
+end
+
+-- Invokes the callback for each LanguageTree and it's children recursively
+-- @param fn The function to invoke. This is invoked with arguments (tree: LanguageTree, lang: string)
+-- @param include_self Whether to include the invoking tree in the results.
+function LanguageTree:for_each_child(fn, include_self)
+ if include_self then
+ fn(self, self._lang)
+ end
+
+ for _, child in pairs(self._children) do
+ child:for_each_child(fn, true)
+ end
+end
+
+-- Invokes the callback for each treesitter trees recursively.
+-- Note, this includes the invoking language tree's trees as well.
+-- @param fn The callback to invoke. The callback is invoked with arguments
+-- (tree: TSTree, languageTree: LanguageTree)
+function LanguageTree:for_each_tree(fn)
+ for _, tree in ipairs(self._trees) do
+ fn(tree, self)
+ end
+
+ for _, child in pairs(self._children) do
+ child:for_each_tree(fn)
+ end
+end
+
+-- Adds a child language to this tree.
+-- If the language already exists as a child, it will first be removed.
+-- @param lang The language to add.
+function LanguageTree:add_child(lang)
+ if self._children[lang] then
+ self:remove_child(lang)
+ end
+
+ self._children[lang] = LanguageTree.new(self._source, lang, self._opts)
+
+ self:invalidate()
+ self:_do_callback('child_added', self._children[lang])
+
+ return self._children[lang]
+end
+
+-- Removes a child language from this tree.
+-- @param lang The language to remove.
+function LanguageTree:remove_child(lang)
+ local child = self._children[lang]
+
+ if child then
+ self._children[lang] = nil
+ child:destroy()
+ self:invalidate()
+ self:_do_callback('child_removed', child)
+ end
+end
+
+-- Destroys this language tree and all it's children.
+-- Any cleanup logic should be performed here.
+-- Note, this DOES NOT remove this tree from a parent.
+-- `remove_child` must be called on the parent to remove it.
+function LanguageTree:destroy()
+ -- Cleanup here
+ for _, child in ipairs(self._children) do
+ child:destroy()
+ end
+end
+
+-- Sets the included regions that should be parsed by this parser.
+-- A region is a set of nodes and/or ranges that will be parsed in the same context.
+--
+-- For example, `{ { node1 }, { node2} }` is two separate regions.
+-- This will be parsed by the parser in two different contexts... thus resulting
+-- in two separate trees.
+--
+-- `{ { node1, node2 } }` is a single region consisting of two nodes.
+-- This will be parsed by the parser in a single context... thus resulting
+-- in a single tree.
+--
+-- This allows for embedded languages to be parsed together across different
+-- nodes, which is useful for templating languages like ERB and EJS.
+--
+-- Note, this call invalidates the tree and requires it to be parsed again.
+--
+-- @param regions A list of regions this tree should manange and parse.
+function LanguageTree:set_included_regions(regions)
+ -- Transform the tables from 4 element long to 6 element long (with byte offset)
+ for _, region in ipairs(regions) do
+ for i, range in ipairs(region) do
+ if type(range) == "table" and #range == 4 then
+ -- TODO(vigoux): I don't think string parsers are useful for now
+ if type(self._source) == "number" then
+ local start_row, start_col, end_row, end_col = unpack(range)
+ -- Easy case, this is a buffer parser
+ -- TODO(vigoux): proper byte computation here, and account for EOL ?
+ local start_byte = a.nvim_buf_get_offset(self.bufnr, start_row) + start_col
+ local end_byte = a.nvim_buf_get_offset(self.bufnr, end_row) + end_col
+
+ region[i] = { start_row, start_col, start_byte, end_row, end_col, end_byte }
+ end
+ end
+ end
+ end
+
+ self._regions = regions
+ -- Trees are no longer valid now that we have changed regions.
+ -- TODO(vigoux,steelsojka): Look into doing this smarter so we can use some of the
+ -- old trees for incremental parsing. Currently, this only
+ -- effects injected languages.
+ self._trees = {}
+ self:invalidate()
+end
+
+-- Gets the set of included regions
+function LanguageTree:included_regions()
+ return self._regions
+end
+
+-- Gets language injection points by language.
+-- This is where most of the injection processing occurs.
+-- TODO: Allow for an offset predicate to tailor the injection range
+-- instead of using the entire nodes range.
+-- @private
+function LanguageTree:_get_injections()
+ if not self._injection_query then return {} end
+
+ local injections = {}
+
+ for tree_index, tree in ipairs(self._trees) do
+ local root_node = tree:root()
+ local start_line, _, end_line, _ = root_node:range()
+
+ for pattern, match in self._injection_query:iter_matches(root_node, self._source, start_line, end_line+1) do
+ local lang = nil
+ local injection_node = nil
+ local combined = false
+
+ -- You can specify the content and language together
+ -- using a tag with the language, for example
+ -- @javascript
+ for id, node in pairs(match) do
+ local name = self._injection_query.captures[id]
+ -- TODO add a way to offset the content passed to the parser.
+ -- Needed to shave off leading quotes and things of that nature.
+
+ -- Lang should override any other language tag
+ if name == "language" then
+ lang = query.get_node_text(node, self._source)
+ elseif name == "combined" then
+ combined = true
+ elseif name == "content" then
+ injection_node = node
+ -- Ignore any tags that start with "_"
+ -- Allows for other tags to be used in matches
+ elseif string.sub(name, 1, 1) ~= "_" then
+ if lang == nil then
+ lang = name
+ end
+
+ if not injection_node then
+ injection_node = node
+ end
+ end
+ end
+
+ -- Each tree index should be isolated from the other nodes.
+ if not injections[tree_index] then
+ injections[tree_index] = {}
+ end
+
+ if not injections[tree_index][lang] then
+ injections[tree_index][lang] = {}
+ end
+
+ -- Key by pattern so we can either combine each node to parse in the same
+ -- context or treat each node independently.
+ if not injections[tree_index][lang][pattern] then
+ injections[tree_index][lang][pattern] = { combined = combined, nodes = {} }
+ end
+
+ table.insert(injections[tree_index][lang][pattern].nodes, injection_node)
+ end
+ end
+
+ local result = {}
+
+ -- Generate a map by lang of node lists.
+ -- Each list is a set of ranges that should be parsed
+ -- together.
+ for _, lang_map in ipairs(injections) do
+ for lang, patterns in pairs(lang_map) do
+ if not result[lang] then
+ result[lang] = {}
+ end
+
+ for _, entry in pairs(patterns) do
+ if entry.combined then
+ table.insert(result[lang], entry.nodes)
+ else
+ for _, node in ipairs(entry.nodes) do
+ table.insert(result[lang], {node})
+ end
+ end
+ end
+ end
+ end
+
+ return result
+end
+
+function LanguageTree:_do_callback(cb_name, ...)
+ for _, cb in ipairs(self._callbacks[cb_name]) do
+ cb(...)
+ end
+end
+
+function LanguageTree:_on_bytes(bufnr, changed_tick,
+ start_row, start_col, start_byte,
+ old_row, old_col, old_byte,
+ new_row, new_col, new_byte)
+ self:invalidate()
+
+ local old_end_col = old_col + ((old_row == 0) and start_col or 0)
+ local new_end_col = new_col + ((new_row == 0) and start_col or 0)
+
+ -- Edit all trees recursively, together BEFORE emitting a bytes callback.
+ -- In most cases this callback should only be called from the root tree.
+ self:for_each_tree(function(tree)
+ tree:edit(start_byte,start_byte+old_byte,start_byte+new_byte,
+ start_row, start_col,
+ start_row+old_row, old_end_col,
+ start_row+new_row, new_end_col)
+ end)
+
+ self:_do_callback('bytes', bufnr, changed_tick,
+ start_row, start_col, start_byte,
+ old_row, old_col, old_byte,
+ new_row, new_col, new_byte)
+end
+
+--- Registers callbacks for the parser
+-- @param cbs An `nvim_buf_attach`-like table argument with the following keys :
+-- `on_bytes` : see `nvim_buf_attach`, but this will be called _after_ the parsers callback.
+-- `on_changedtree` : a callback that will be called everytime the tree has syntactical changes.
+-- it will only be passed one argument, that is a table of the ranges (as node ranges) that
+-- changed.
+-- `on_child_added` : emitted when a child is added to the tree.
+-- `on_child_removed` : emitted when a child is remvoed from the tree.
+function LanguageTree:register_cbs(cbs)
+ if not cbs then return end
+
+ if cbs.on_changedtree then
+ table.insert(self._callbacks.changedtree, cbs.on_changedtree)
+ end
+
+ if cbs.on_bytes then
+ table.insert(self._callbacks.bytes, cbs.on_bytes)
+ end
+
+ if cbs.on_child_added then
+ table.insert(self._callbacks.child_added, cbs.on_child_added)
+ end
+
+ if cbs.on_child_removed then
+ table.insert(self._callbacks.child_removed, cbs.on_child_removed)
+ end
+end
+
+local function region_contains(region, range)
+ for _, node in ipairs(region) do
+ local start_row, start_col, end_row, end_col = node:range()
+ local start_fits = start_row < range[1] or (start_row == range[1] and start_col <= range[2])
+ local end_fits = end_row > range[3] or (end_row == range[3] and end_col >= range[4])
+
+ if start_fits and end_fits then
+ return true
+ end
+ end
+
+ return false
+end
+
+function LanguageTree:contains(range)
+ for _, region in pairs(self._region) do
+ if region_contains(region, range) then
+ return true
+ end
+ end
+
+ return false
+end
+
+function LanguageTree:language_for_range(range)
+ for _, child in pairs(self._children) do
+ if child:contains(range) then
+ return child:node_for_range(range)
+ end
+ end
+
+ return self
+end
+
+return LanguageTree
diff --git a/runtime/syntax/gitconfig.vim b/runtime/syntax/gitconfig.vim
index c6c56f77b6..e5eaf10f94 100644
--- a/runtime/syntax/gitconfig.vim
+++ b/runtime/syntax/gitconfig.vim
@@ -2,24 +2,22 @@
" Language: git config file
" Maintainer: Tim Pope <vimNOSPAM@tpope.org>
" Filenames: gitconfig, .gitconfig, *.git/config
-" Last Change: 2010 May 21
+" Last Change: 2019 Dec 05
if exists("b:current_syntax")
finish
endif
-setlocal iskeyword+=-
-setlocal iskeyword-=_
syn case ignore
syn sync minlines=10
-syn match gitconfigComment "[#;].*"
+syn match gitconfigComment "[#;].*" contains=@Spell
syn match gitconfigSection "\%(^\s*\)\@<=\[[a-z0-9.-]\+\]"
syn match gitconfigSection '\%(^\s*\)\@<=\[[a-z0-9.-]\+ \+\"\%([^\\"]\|\\.\)*"\]'
-syn match gitconfigVariable "\%(^\s*\)\@<=\a\k*\%(\s*\%([=#;]\|$\)\)\@=" nextgroup=gitconfigAssignment skipwhite
+syn match gitconfigVariable "\%(^\s*\)\@<=\a[a-z0-9-]*\%(\s*\%([=#;]\|$\)\)\@=" nextgroup=gitconfigAssignment skipwhite
syn region gitconfigAssignment matchgroup=gitconfigNone start=+=\s*+ skip=+\\+ end=+\s*$+ contained contains=gitconfigBoolean,gitconfigNumber,gitConfigString,gitConfigEscape,gitConfigError,gitconfigComment keepend
syn keyword gitconfigBoolean true false yes no contained
-syn match gitconfigNumber "\d\+" contained
+syn match gitconfigNumber "\<\d\+\>" contained
syn region gitconfigString matchgroup=gitconfigDelim start=+"+ skip=+\\+ end=+"+ matchgroup=gitconfigError end=+[^\\"]\%#\@!$+ contained contains=gitconfigEscape,gitconfigEscapeError
syn match gitconfigError +\\.+ contained
syn match gitconfigEscape +\\[\\"ntb]+ contained
@@ -32,7 +30,7 @@ hi def link gitconfigBoolean Boolean
hi def link gitconfigNumber Number
hi def link gitconfigString String
hi def link gitconfigDelim Delimiter
-hi def link gitconfigEscape Delimiter
+hi def link gitconfigEscape Special
hi def link gitconfigError Error
let b:current_syntax = "gitconfig"
diff --git a/runtime/syntax/xml.vim b/runtime/syntax/xml.vim
index 7b503abf49..7c9791a7cc 100644
--- a/runtime/syntax/xml.vim
+++ b/runtime/syntax/xml.vim
@@ -1,11 +1,15 @@
" Vim syntax file
-" Language: XML
-" Maintainer: Johannes Zellner <johannes@zellner.org>
-" Author and previous maintainer:
-" Paul Siegmann <pauls@euronet.nl>
-" Last Change: 2013 Jun 07
+" Language: XML
+" Maintainer: Christian Brabandt <cb@256bit.org>
+" Repository: https://github.com/chrisbra/vim-xml-ftplugin
+" Previous Maintainer: Johannes Zellner <johannes@zellner.org>
+" Author: Paul Siegmann <pauls@euronet.nl>
+" Last Changed: Nov 03, 2019
" Filenames: *.xml
-" $Id: xml.vim,v 1.3 2006/04/11 21:32:00 vimboss Exp $
+" Last Change:
+" 20190923 - Fix xmlEndTag to match xmlTag (vim/vim#884)
+" 20190924 - Fix xmlAttribute property (amadeus/vim-xml@d8ce1c946)
+" 20191103 - Enable spell checking globally
" CONFIGURATION:
" syntax folding can be turned on by
@@ -49,6 +53,12 @@ set cpo&vim
syn case match
+" Allow spell checking in tag values,
+" there is no syntax region for that,
+" so enable spell checking in top-level elements
+" <tag>This text is spell checked</tag>
+syn spell toplevel
+
" mark illegal characters
syn match xmlError "[<&]"
@@ -81,7 +91,7 @@ syn match xmlEqual +=+ display
" ^^^^^^^^^^^^^
"
syn match xmlAttrib
- \ +[-'"<]\@1<!\<[a-zA-Z:_][-.0-9a-zA-Z:_]*\>\%(['">]\@!\|$\)+
+ \ +[-'"<]\@1<!\<[a-zA-Z:_][-.0-9a-zA-Z:_]*\>\%(['"]\@!\|$\)+
\ contained
\ contains=xmlAttribPunct,@xmlAttribHook
\ display
@@ -122,7 +132,7 @@ endif
" ^^^
"
syn match xmlTagName
- \ +<\@1<=[^ /!?<>"']\++
+ \ +\%(<\|</\)\@2<=[^ /!?<>"']\++
\ contained
\ contains=xmlNamespace,xmlAttribPunct,@xmlTagHook
\ display
@@ -157,11 +167,11 @@ if exists('g:xml_syntax_folding')
" </tag>
" ^^^^^^
"
- syn match xmlEndTag
- \ +</[^ /!?<>"']\+>+
+ syn region xmlEndTag
+ \ matchgroup=xmlTag start=+</[^ /!?<>"']\@=+
+ \ matchgroup=xmlTag end=+>+
\ contained
- \ contains=xmlNamespace,xmlAttribPunct,@xmlTagHook
-
+ \ contains=xmlTagName,xmlNamespace,xmlAttribPunct,@xmlTagHook
" tag elements with syntax-folding.
" NOTE: NO HIGHLIGHTING -- highlighting is done by contained elements
@@ -181,7 +191,7 @@ if exists('g:xml_syntax_folding')
\ start=+<\z([^ /!?<>"']\+\)+
\ skip=+<!--\_.\{-}-->+
\ end=+</\z1\_\s\{-}>+
- \ matchgroup=xmlEndTag end=+/>+
+ \ end=+/>+
\ fold
\ contains=xmlTag,xmlEndTag,xmlCdata,xmlRegion,xmlComment,xmlEntity,xmlProcessing,@xmlRegionHook,@Spell
\ keepend
@@ -198,9 +208,10 @@ else
\ matchgroup=xmlTag end=+>+
\ contains=xmlError,xmlTagName,xmlAttrib,xmlEqual,xmlString,@xmlStartTagHook
- syn match xmlEndTag
- \ +</[^ /!?<>"']\+>+
- \ contains=xmlNamespace,xmlAttribPunct,@xmlTagHook
+ syn region xmlEndTag
+ \ matchgroup=xmlTag start=+</[^ /!?<>"']\@=+
+ \ matchgroup=xmlTag end=+>+
+ \ contains=xmlTagName,xmlNamespace,xmlAttribPunct,@xmlTagHook
endif