aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua/vim')
-rw-r--r--runtime/lua/vim/_defaults.lua8
-rw-r--r--runtime/lua/vim/_editor.lua2
-rw-r--r--runtime/lua/vim/_meta/api.lua8
-rw-r--r--runtime/lua/vim/_meta/api_keysets.lua1
-rw-r--r--runtime/lua/vim/_meta/vimfn.lua3
-rw-r--r--runtime/lua/vim/_options.lua6
-rw-r--r--runtime/lua/vim/filetype.lua13
-rw-r--r--runtime/lua/vim/lsp/completion.lua4
-rw-r--r--runtime/lua/vim/lsp/util.lua5
-rw-r--r--runtime/lua/vim/treesitter.lua13
-rw-r--r--runtime/lua/vim/treesitter/_fold.lua12
-rw-r--r--runtime/lua/vim/treesitter/_headings.lua144
-rw-r--r--runtime/lua/vim/treesitter/_meta/tsnode.lua2
-rw-r--r--runtime/lua/vim/treesitter/highlighter.lua7
-rw-r--r--runtime/lua/vim/treesitter/language.lua1
-rw-r--r--runtime/lua/vim/treesitter/languagetree.lua119
-rw-r--r--runtime/lua/vim/treesitter/query.lua10
-rw-r--r--runtime/lua/vim/vimhelp.lua71
18 files changed, 287 insertions, 142 deletions
diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua
index 6c8ef0ad9f..c2e4e76dd6 100644
--- a/runtime/lua/vim/_defaults.lua
+++ b/runtime/lua/vim/_defaults.lua
@@ -24,6 +24,14 @@ do
vim.api.nvim_create_user_command('EditQuery', function(cmd)
vim.treesitter.query.edit(cmd.fargs[1])
end, { desc = 'Edit treesitter query', nargs = '?' })
+
+ vim.api.nvim_create_user_command('Open', function(cmd)
+ vim.ui.open(cmd.fargs[1])
+ end, {
+ desc = 'Open file with system default handler. See :help vim.ui.open()',
+ nargs = 1,
+ complete = 'file',
+ })
end
--- Default mappings
diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua
index a77ea9bb91..94168bea5d 100644
--- a/runtime/lua/vim/_editor.lua
+++ b/runtime/lua/vim/_editor.lua
@@ -56,7 +56,7 @@ vim._extra = {
inspect_pos = true,
}
---- @private
+--- @nodoc
vim.log = {
--- @enum vim.log.levels
levels = {
diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua
index 3d10729d23..a6ffb43146 100644
--- a/runtime/lua/vim/_meta/api.lua
+++ b/runtime/lua/vim/_meta/api.lua
@@ -638,10 +638,14 @@ function vim.api.nvim_buf_line_count(buffer) end
--- placed below the buffer line containing the mark.
---
--- - virt_lines_above: place virtual lines above instead.
---- - virt_lines_leftcol: Place extmarks in the leftmost
+--- - virt_lines_leftcol: Place virtual lines in the leftmost
--- column of the window, bypassing
--- sign and number columns.
----
+--- - virt_lines_overflow: controls how to handle virtual lines wider
+--- than the window. Currently takes the one of the following values:
+--- - "trunc": truncate virtual lines on the right (default).
+--- - "scroll": virtual lines can scroll horizontally with 'nowrap',
+--- otherwise the same as "trunc".
--- - ephemeral : for use with `nvim_set_decoration_provider()`
--- callbacks. The mark will only be used for the current
--- redraw cycle, and not be permantently stored in the
diff --git a/runtime/lua/vim/_meta/api_keysets.lua b/runtime/lua/vim/_meta/api_keysets.lua
index 4d0665872b..a66e373851 100644
--- a/runtime/lua/vim/_meta/api_keysets.lua
+++ b/runtime/lua/vim/_meta/api_keysets.lua
@@ -258,6 +258,7 @@ error('Cannot require a meta file')
--- @field virt_lines? any[]
--- @field virt_lines_above? boolean
--- @field virt_lines_leftcol? boolean
+--- @field virt_lines_overflow? string
--- @field strict? boolean
--- @field sign_text? string
--- @field sign_hl_group? integer|string
diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua
index f4c395ce39..4970a3023b 100644
--- a/runtime/lua/vim/_meta/vimfn.lua
+++ b/runtime/lua/vim/_meta/vimfn.lua
@@ -1928,7 +1928,8 @@ function vim.fn.expandcmd(string, options) end
--- When {expr3} is omitted then "force" is assumed.
---
--- {expr1} is changed when {expr2} is not empty. If necessary
---- make a copy of {expr1} first.
+--- make a copy of {expr1} first or use |extendnew()| to return a
+--- new List/Dictionary.
--- {expr2} remains unchanged.
--- When {expr1} is locked and {expr2} is not empty the operation
--- fails.
diff --git a/runtime/lua/vim/_options.lua b/runtime/lua/vim/_options.lua
index 973ad87ee8..e98b95f837 100644
--- a/runtime/lua/vim/_options.lua
+++ b/runtime/lua/vim/_options.lua
@@ -922,11 +922,11 @@ function Option:prepend(value) end -- luacheck: no unused
---@diagnostic disable-next-line:unused-local used for gen_vimdoc
function Option:remove(value) end -- luacheck: no unused
----@private
+--- @nodoc
vim.opt = create_option_accessor()
----@private
+--- @nodoc
vim.opt_local = create_option_accessor('local')
----@private
+--- @nodoc
vim.opt_global = create_option_accessor('global')
diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index 6974b6508d..66ee45587a 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -1802,6 +1802,7 @@ local filename = {
Vagrantfile = 'ruby',
['smb.conf'] = 'samba',
['.lips_repl_history'] = 'scheme',
+ ['.guile'] = 'scheme',
screenrc = 'screen',
['.screenrc'] = 'screen',
['/etc/sensors3.conf'] = 'sensors',
@@ -2150,11 +2151,6 @@ local pattern = {
['/usr/.*/gnupg/options%.skel$'] = 'gpg',
['/usr/share/upstart/.*%.conf$'] = 'upstart',
['/usr/share/upstart/.*%.override$'] = 'upstart',
- ['/usr/share/X11/xkb/compat/'] = detect_xkb,
- ['/usr/share/X11/xkb/geometry/'] = detect_xkb,
- ['/usr/share/X11/xkb/keycodes/'] = detect_xkb,
- ['/usr/share/X11/xkb/symbols/'] = detect_xkb,
- ['/usr/share/X11/xkb/types/'] = detect_xkb,
},
['/var/'] = {
['/var/backups/group%.bak$'] = 'group',
@@ -2329,6 +2325,13 @@ local pattern = {
['^Neomuttrc'] = detect_neomuttrc,
['%.neomuttdebug'] = 'neomuttlog',
},
+ ['/%.?xkb/'] = {
+ ['/%.?xkb/compat/'] = detect_xkb,
+ ['/%.?xkb/geometry/'] = detect_xkb,
+ ['/%.?xkb/keycodes/'] = detect_xkb,
+ ['/%.?xkb/symbols/'] = detect_xkb,
+ ['/%.?xkb/types/'] = detect_xkb,
+ },
['^%.'] = {
['^%.cshrc'] = detect.csh,
['^%.login'] = detect.csh,
diff --git a/runtime/lua/vim/lsp/completion.lua b/runtime/lua/vim/lsp/completion.lua
index d99b1ffd0d..1466dcf438 100644
--- a/runtime/lua/vim/lsp/completion.lua
+++ b/runtime/lua/vim/lsp/completion.lua
@@ -132,7 +132,7 @@ end
--- @return string
local function get_completion_word(item, prefix, match)
if item.insertTextFormat == protocol.InsertTextFormat.Snippet then
- if item.textEdit then
+ if item.textEdit or (item.insertText and item.insertText ~= '') then
-- Use label instead of text if text has different starting characters.
-- label is used as abbr (=displayed), but word is used for filtering
-- This is required for things like postfix completion.
@@ -154,8 +154,6 @@ local function get_completion_word(item, prefix, match)
else
return word
end
- elseif item.insertText and item.insertText ~= '' then
- return parse_snippet(item.insertText)
else
return item.label
end
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index 9e84e27205..905b9822ba 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -1417,6 +1417,11 @@ function M._make_floating_popup_size(contents, opts)
-- make sure borders are always inside the screen
width = math.min(width, screen_width - border_width)
+ -- Make sure that the width is large enough to fit the title.
+ if opts.title then
+ width = math.max(width, vim.fn.strdisplaywidth(opts.title))
+ end
+
if wrap_at then
wrap_at = math.min(wrap_at, width)
end
diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
index 10638e10d8..44372415fd 100644
--- a/runtime/lua/vim/treesitter.lua
+++ b/runtime/lua/vim/treesitter.lua
@@ -34,8 +34,6 @@ M.minimum_language_version = vim._ts_get_minimum_language_version()
function M._create_parser(bufnr, lang, opts)
bufnr = vim._resolve_bufnr(bufnr)
- vim.fn.bufload(bufnr)
-
local self = LanguageTree.new(bufnr, lang, opts)
local function bytes_cb(_, ...)
@@ -102,6 +100,9 @@ function M.get_parser(bufnr, lang, opts)
return nil, err_msg
end
elseif parsers[bufnr] == nil or parsers[bufnr]:lang() ~= lang then
+ if not api.nvim_buf_is_loaded(bufnr) then
+ error(('Buffer %s must be loaded to create parser'):format(bufnr))
+ end
local parser = vim.F.npcall(M._create_parser, bufnr, lang, opts)
if not parser then
local err_msg =
@@ -415,6 +416,14 @@ end
---@param lang string? Language of the parser (default: from buffer filetype)
function M.start(bufnr, lang)
bufnr = vim._resolve_bufnr(bufnr)
+ -- Ensure buffer is loaded. `:edit` over `bufload()` to show swapfile prompt.
+ if not api.nvim_buf_is_loaded(bufnr) then
+ if api.nvim_buf_get_name(bufnr) ~= '' then
+ pcall(api.nvim_buf_call, bufnr, vim.cmd.edit)
+ else
+ vim.fn.bufload(bufnr)
+ end
+ end
local parser = assert(M.get_parser(bufnr, lang, { error = false }))
M.highlighter.new(parser)
end
diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua
index 38318347a7..1064004320 100644
--- a/runtime/lua/vim/treesitter/_fold.lua
+++ b/runtime/lua/vim/treesitter/_fold.lua
@@ -75,7 +75,15 @@ local function compute_folds_levels(bufnr, info, srow, erow, callback)
erow = erow or api.nvim_buf_line_count(bufnr)
local parser = info.parser
- if not parser then
+ if
+ not parser
+ -- Parsing an empty buffer results in problems with the parsing state,
+ -- resulting in both a broken highlighter and foldexpr.
+ or api.nvim_buf_line_count(bufnr) == 1
+ and api.nvim_buf_call(bufnr, function()
+ return vim.fn.line2byte(1) <= 0
+ end)
+ then
return
end
@@ -380,7 +388,7 @@ function M.foldexpr(lnum)
if not foldinfos[bufnr] then
foldinfos[bufnr] = FoldInfo.new(bufnr)
- api.nvim_create_autocmd({ 'BufUnload', 'VimEnter' }, {
+ api.nvim_create_autocmd({ 'BufUnload', 'VimEnter', 'FileType' }, {
buffer = bufnr,
once = true,
callback = function()
diff --git a/runtime/lua/vim/treesitter/_headings.lua b/runtime/lua/vim/treesitter/_headings.lua
new file mode 100644
index 0000000000..4e8833c93a
--- /dev/null
+++ b/runtime/lua/vim/treesitter/_headings.lua
@@ -0,0 +1,144 @@
+local ts = vim.treesitter
+local api = vim.api
+
+--- Treesitter-based navigation functions for headings
+local M = {}
+
+-- TODO(clason): use runtimepath queries (for other languages)
+local heading_queries = {
+ vimdoc = [[
+ (h1 (heading) @h1)
+ (h2 (heading) @h2)
+ (h3 (heading) @h3)
+ (column_heading (heading) @h4)
+ ]],
+ markdown = [[
+ (setext_heading
+ heading_content: (_) @h1
+ (setext_h1_underline))
+ (setext_heading
+ heading_content: (_) @h2
+ (setext_h2_underline))
+ (atx_heading
+ (atx_h1_marker)
+ heading_content: (_) @h1)
+ (atx_heading
+ (atx_h2_marker)
+ heading_content: (_) @h2)
+ (atx_heading
+ (atx_h3_marker)
+ heading_content: (_) @h3)
+ (atx_heading
+ (atx_h4_marker)
+ heading_content: (_) @h4)
+ (atx_heading
+ (atx_h5_marker)
+ heading_content: (_) @h5)
+ (atx_heading
+ (atx_h6_marker)
+ heading_content: (_) @h6)
+ ]],
+}
+
+local function hash_tick(bufnr)
+ return tostring(vim.b[bufnr].changedtick)
+end
+
+---@class TS.Heading
+---@field bufnr integer
+---@field lnum integer
+---@field text string
+---@field level integer
+
+--- Extract headings from buffer
+--- @param bufnr integer buffer to extract headings from
+--- @return TS.Heading[]
+local get_headings = vim.func._memoize(hash_tick, function(bufnr)
+ local lang = ts.language.get_lang(vim.bo[bufnr].filetype)
+ if not lang then
+ return {}
+ end
+ local parser = assert(ts.get_parser(bufnr, lang, { error = false }))
+ local query = ts.query.parse(lang, heading_queries[lang])
+ local root = parser:parse()[1]:root()
+ local headings = {}
+ for id, node, _, _ in query:iter_captures(root, bufnr) do
+ local text = ts.get_node_text(node, bufnr)
+ local row, col = node:start()
+ --- why can't you just be normal?!
+ local skip ---@type boolean|integer
+ if lang == 'vimdoc' then
+ -- only column_headings at col 1 are headings, otherwise it's code examples
+ skip = (id == 4 and col > 0)
+ -- ignore tabular material
+ or (id == 4 and (text:find('\t') or text:find(' ')))
+ -- ignore tag-only headings
+ or (node:child_count() == 1 and node:child(0):type() == 'tag')
+ end
+ if not skip then
+ table.insert(headings, {
+ bufnr = bufnr,
+ lnum = row + 1,
+ text = text,
+ level = id,
+ })
+ end
+ end
+ return headings
+end)
+
+--- Show a table of contents for the help buffer in a loclist
+function M.show_toc()
+ local bufnr = api.nvim_get_current_buf()
+ local headings = get_headings(bufnr)
+ if #headings == 0 then
+ return
+ end
+ -- add indentation for nicer list formatting
+ for _, heading in pairs(headings) do
+ if heading.level > 2 then
+ heading.text = '  ' .. heading.text
+ end
+ if heading.level > 4 then
+ heading.text = '  ' .. heading.text
+ end
+ end
+ vim.fn.setloclist(0, headings, ' ')
+ vim.fn.setloclist(0, {}, 'a', { title = 'Help TOC' })
+ vim.cmd.lopen()
+end
+
+--- Jump to section
+--- @param opts table jump options
+--- - count integer direction to jump (>0 forward, <0 backward)
+--- - level integer only consider headings up to level
+--- todo(clason): support count
+function M.jump(opts)
+ local bufnr = api.nvim_get_current_buf()
+ local headings = get_headings(bufnr)
+ if #headings == 0 then
+ return
+ end
+
+ local winid = api.nvim_get_current_win()
+ local curpos = vim.fn.getcurpos(winid)[2] --[[@as integer]]
+ local maxlevel = opts.level or 6
+
+ if opts.count > 0 then
+ for _, heading in ipairs(headings) do
+ if heading.lnum > curpos and heading.level <= maxlevel then
+ api.nvim_win_set_cursor(winid, { heading.lnum, 0 })
+ return
+ end
+ end
+ elseif opts.count < 0 then
+ for i = #headings, 1, -1 do
+ if headings[i].lnum < curpos and headings[i].level <= maxlevel then
+ api.nvim_win_set_cursor(winid, { headings[i].lnum, 0 })
+ return
+ end
+ end
+ end
+end
+
+return M
diff --git a/runtime/lua/vim/treesitter/_meta/tsnode.lua b/runtime/lua/vim/treesitter/_meta/tsnode.lua
index 552905c3f0..2f9d7f214a 100644
--- a/runtime/lua/vim/treesitter/_meta/tsnode.lua
+++ b/runtime/lua/vim/treesitter/_meta/tsnode.lua
@@ -43,7 +43,7 @@ function TSNode:prev_named_sibling() end
--- @return fun(): TSNode, string
function TSNode:iter_children() end
---- Returns a table of the nodes corresponding to the {name} field.
+--- Returns a list of all the node's children that have the given field name.
--- @param name string
--- @return TSNode[]
function TSNode:field(name) end
diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua
index 6dd47811bd..475a1f0aa5 100644
--- a/runtime/lua/vim/treesitter/highlighter.lua
+++ b/runtime/lua/vim/treesitter/highlighter.lua
@@ -232,7 +232,12 @@ end
---@return vim.treesitter.highlighter.Query
function TSHighlighter:get_query(lang)
if not self._queries[lang] then
- self._queries[lang] = TSHighlighterQuery.new(lang)
+ local success, result = pcall(TSHighlighterQuery.new, lang)
+ if not success then
+ self:destroy()
+ error(result)
+ end
+ self._queries[lang] = result
end
return self._queries[lang]
diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua
index 16d19bfc5a..38d309a102 100644
--- a/runtime/lua/vim/treesitter/language.lua
+++ b/runtime/lua/vim/treesitter/language.lua
@@ -5,6 +5,7 @@ local M = {}
---@type table<string,string>
local ft_to_lang = {
help = 'vimdoc',
+ checkhealth = 'vimdoc',
}
--- Returns the filetypes for which a parser named {lang} is used.
diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
index d8db489d54..f2e745ec65 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -46,6 +46,9 @@ local Range = require('vim.treesitter._range')
local default_parse_timeout_ms = 3
+---@type Range2
+local entire_document_range = { 0, math.huge }
+
---@alias TSCallbackName
---| 'changedtree'
---| 'bytes'
@@ -77,7 +80,7 @@ local TSCallbackNames = {
---@field package _callbacks_rec table<TSCallbackName,function[]> Callback handlers (recursive)
---@field private _children table<string,vim.treesitter.LanguageTree> Injected languages
---@field private _injection_query vim.treesitter.Query Queries defining injected languages
----@field private _injections_processed boolean
+---@field private _processed_injection_range Range? Range for which injections have been processed
---@field private _opts table Options
---@field private _parser TSParser Parser for language
---Table of regions for which the tree is currently running an async parse
@@ -95,6 +98,7 @@ local TSCallbackNames = {
---@field private _trees table<integer, TSTree> Reference to parsed tree (one for each language).
---Each key is the index of region, which is synced with _regions and _valid.
---@field private _valid_regions table<integer,true> Set of valid region IDs.
+---@field private _num_valid_regions integer Number of valid regions
---@field private _is_entirely_valid boolean Whether the entire tree (excluding children) is valid.
---@field private _logger? fun(logtype: string, msg: string)
---@field private _logfile? file*
@@ -136,8 +140,9 @@ function LanguageTree.new(source, lang, opts)
_opts = opts,
_injection_query = injections[lang] and query.parse(lang, injections[lang])
or query.get(lang, 'injections'),
- _injections_processed = false,
+ _processed_injection_range = nil,
_valid_regions = {},
+ _num_valid_regions = 0,
_num_regions = 1,
_is_entirely_valid = false,
_parser = vim._create_ts_parser(lang),
@@ -246,6 +251,7 @@ end
---@param reload boolean|nil
function LanguageTree:invalidate(reload)
self._valid_regions = {}
+ self._num_valid_regions = 0
self._is_entirely_valid = false
self._parser:reset()
@@ -331,7 +337,10 @@ function LanguageTree:is_valid(exclude_children, range)
end
if not exclude_children then
- if not self._injections_processed then
+ if
+ not self._processed_injection_range
+ or not Range.contains(self._processed_injection_range, range or entire_document_range)
+ then
return false
end
@@ -363,7 +372,6 @@ end
--- @return Range6[] changes
--- @return integer no_regions_parsed
--- @return number total_parse_time
---- @return boolean finished whether async parsing still needs time
function LanguageTree:_parse_regions(range, thread_state)
local changes = {}
local no_regions_parsed = 0
@@ -388,12 +396,14 @@ function LanguageTree:_parse_regions(range, thread_state)
if tree then
break
end
- coroutine.yield(changes, no_regions_parsed, total_parse_time, false)
+ coroutine.yield(self._trees, false)
parse_time, tree, tree_changes =
tcall(self._parser.parse, self._parser, self._trees[i], self._source, true)
end
+ self:_subtract_time(thread_state, parse_time)
+
self:_do_callback('changedtree', tree_changes, tree)
self._trees[i] = tree
vim.list_extend(changes, tree_changes)
@@ -401,24 +411,22 @@ function LanguageTree:_parse_regions(range, thread_state)
total_parse_time = total_parse_time + parse_time
no_regions_parsed = no_regions_parsed + 1
self._valid_regions[i] = true
+ self._num_valid_regions = self._num_valid_regions + 1
- -- _valid_regions can have holes, but that is okay because this equality is only true when it
- -- has no holes (meaning all regions are valid)
- if #self._valid_regions == self._num_regions then
+ if self._num_valid_regions == self._num_regions then
self._is_entirely_valid = true
end
end
end
- return changes, no_regions_parsed, total_parse_time, true
+ return changes, no_regions_parsed, total_parse_time
end
--- @private
---- @return number
-function LanguageTree:_add_injections()
+--- @param injections_by_lang table<string, Range6[][]>
+function LanguageTree:_add_injections(injections_by_lang)
local seen_langs = {} ---@type table<string,boolean>
- local query_time, injections_by_lang = tcall(self._get_injections, self)
for lang, injection_regions in pairs(injections_by_lang) do
local has_lang = pcall(language.add, lang)
@@ -442,8 +450,6 @@ function LanguageTree:_add_injections()
self:remove_child(lang)
end
end
-
- return query_time
end
--- @param range boolean|Range?
@@ -567,6 +573,15 @@ function LanguageTree:parse(range, on_parse)
return trees
end
+---@param thread_state ParserThreadState
+---@param time integer
+function LanguageTree:_subtract_time(thread_state, time)
+ thread_state.timeout = thread_state.timeout and math.max(thread_state.timeout - time, 0)
+ if thread_state.timeout == 0 then
+ coroutine.yield(self._trees, false)
+ end
+end
+
--- @private
--- @param range boolean|Range|nil
--- @param thread_state ParserThreadState
@@ -587,28 +602,27 @@ function LanguageTree:_parse(range, thread_state)
-- At least 1 region is invalid
if not self:is_valid(true, type(range) == 'table' and range or nil) then
- ---@type fun(self: vim.treesitter.LanguageTree, range: boolean|Range?, thread_state: ParserThreadState): Range6[], integer, number, boolean
- local parse_regions = coroutine.wrap(self._parse_regions)
- while true do
- local is_finished
- changes, no_regions_parsed, total_parse_time, is_finished =
- parse_regions(self, range, thread_state)
- thread_state.timeout = thread_state.timeout
- and math.max(thread_state.timeout - total_parse_time, 0)
- if is_finished then
- break
- end
- coroutine.yield(self._trees, false)
- end
+ changes, no_regions_parsed, total_parse_time = self:_parse_regions(range, thread_state)
+
-- Need to run injections when we parsed something
if no_regions_parsed > 0 then
- self._injections_processed = false
+ self._processed_injection_range = nil
end
end
- if not self._injections_processed and range then
- query_time = self:_add_injections()
- self._injections_processed = true
+ if
+ range
+ and not (
+ self._processed_injection_range
+ and Range.contains(
+ self._processed_injection_range,
+ range ~= true and range or entire_document_range
+ )
+ )
+ then
+ local injections_by_lang = self:_get_injections(range, thread_state)
+ local time = tcall(self._add_injections, self, injections_by_lang)
+ self:_subtract_time(thread_state, time)
end
self:_log({
@@ -620,21 +634,7 @@ function LanguageTree:_parse(range, thread_state)
})
for _, child in pairs(self._children) do
- if thread_state.timeout == 0 then
- coroutine.yield(self._trees, false)
- end
-
- ---@type fun(): table<integer, TSTree>, boolean
- local parse = coroutine.wrap(child._parse)
-
- while true do
- local ctime, _, child_finished = tcall(parse, child, range, thread_state)
- if child_finished then
- thread_state.timeout = thread_state.timeout and math.max(thread_state.timeout - ctime, 0)
- break
- end
- coroutine.yield(self._trees, child_finished)
- end
+ child:_parse(range, thread_state)
end
return self._trees, true
@@ -745,6 +745,7 @@ function LanguageTree:_iter_regions(fn)
-- just by checking the length of _valid_regions.
self._valid_regions[i] = fn(i, region) and true or nil
if not self._valid_regions[i] then
+ self._num_valid_regions = self._num_valid_regions - 1
self:_log(function()
return 'invalidating region', i, region_tostr(region)
end)
@@ -983,18 +984,29 @@ end
--- TODO: Allow for an offset predicate to tailor the injection range
--- instead of using the entire nodes range.
--- @private
+--- @param range Range|true
+--- @param thread_state ParserThreadState
--- @return table<string, Range6[][]>
-function LanguageTree:_get_injections()
+function LanguageTree:_get_injections(range, thread_state)
if not self._injection_query or #self._injection_query.captures == 0 then
+ self._processed_injection_range = entire_document_range
return {}
end
---@type table<integer,vim.treesitter.languagetree.Injection>
local injections = {}
+ local start = vim.uv.hrtime()
+
+ local full_scan = range == true or self._injection_query.has_combined_injections
for index, tree in pairs(self._trees) do
local root_node = tree:root()
- local start_line, _, end_line, _ = root_node:range()
+ local start_line, end_line ---@type integer, integer
+ if full_scan then
+ start_line, _, end_line = root_node:range()
+ else
+ start_line, _, end_line = Range.unpack4(range --[[@as Range]])
+ end
for pattern, match, metadata in
self._injection_query:iter_matches(root_node, self._source, start_line, end_line + 1)
@@ -1005,6 +1017,11 @@ function LanguageTree:_get_injections()
else
self:_log('match from injection query failed for pattern', pattern)
end
+
+ -- Check the current function duration against the timeout, if it exists.
+ local current_time = vim.uv.hrtime()
+ self:_subtract_time(thread_state, (current_time - start) / 1000000)
+ start = current_time
end
end
@@ -1031,6 +1048,12 @@ function LanguageTree:_get_injections()
end
end
+ if full_scan then
+ self._processed_injection_range = entire_document_range
+ else
+ self._processed_injection_range = range --[[@as Range]]
+ end
+
return result
end
diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua
index 10fb82e533..d26aa8e604 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -30,9 +30,11 @@ end
--- Splits the query patterns into predicates and directives.
---@param patterns table<integer, (integer|string)[][]>
---@return table<integer, vim.treesitter.query.ProcessedPattern>
+---@return boolean
local function process_patterns(patterns)
---@type table<integer, vim.treesitter.query.ProcessedPattern>
local processed_patterns = {}
+ local has_combined = false
for k, pattern_list in pairs(patterns) do
---@type vim.treesitter.query.ProcessedPredicate[]
@@ -47,6 +49,9 @@ local function process_patterns(patterns)
if is_directive(pred_name) then
table.insert(directives, pattern)
+ if vim.deep_equal(pattern, { 'set!', 'injection.combined' }) then
+ has_combined = true
+ end
else
local should_match = true
if pred_name:match('^not%-') then
@@ -60,7 +65,7 @@ local function process_patterns(patterns)
processed_patterns[k] = { predicates = predicates, directives = directives }
end
- return processed_patterns
+ return processed_patterns, has_combined
end
---@nodoc
@@ -71,6 +76,7 @@ end
---@field captures string[] list of (unique) capture names defined in query
---@field info vim.treesitter.QueryInfo query context (e.g. captures, predicates, directives)
---@field query TSQuery userdata query object
+---@field has_combined_injections boolean whether the query contains combined injections
---@field private _processed_patterns table<integer, vim.treesitter.query.ProcessedPattern>
local Query = {}
Query.__index = Query
@@ -90,7 +96,7 @@ function Query.new(lang, ts_query)
patterns = query_info.patterns,
}
self.captures = self.info.captures
- self._processed_patterns = process_patterns(self.info.patterns)
+ self._processed_patterns, self.has_combined_injections = process_patterns(self.info.patterns)
return self
end
diff --git a/runtime/lua/vim/vimhelp.lua b/runtime/lua/vim/vimhelp.lua
deleted file mode 100644
index a494d311b1..0000000000
--- a/runtime/lua/vim/vimhelp.lua
+++ /dev/null
@@ -1,71 +0,0 @@
--- Extra functionality for displaying Vim help.
-
-local M = {}
-
---- Apply current colorscheme to lists of default highlight groups
----
---- Note: {patterns} is assumed to be sorted by occurrence in the file.
---- @param patterns {start:string,stop:string,match:string}[]
-function M.highlight_groups(patterns)
- local ns = vim.api.nvim_create_namespace('nvim.vimhelp')
- vim.api.nvim_buf_clear_namespace(0, ns, 0, -1)
-
- local save_cursor = vim.fn.getcurpos()
-
- for _, pat in pairs(patterns) do
- local start_lnum = vim.fn.search(pat.start, 'c')
- local end_lnum = vim.fn.search(pat.stop)
- if start_lnum == 0 or end_lnum == 0 then
- break
- end
-
- for lnum = start_lnum, end_lnum do
- local word = vim.api.nvim_buf_get_lines(0, lnum - 1, lnum, true)[1]:match(pat.match)
- if vim.fn.hlexists(word) ~= 0 then
- vim.api.nvim_buf_set_extmark(0, ns, lnum - 1, 0, { end_col = #word, hl_group = word })
- end
- end
- end
-
- vim.fn.setpos('.', save_cursor)
-end
-
---- Show a table of contents for the help buffer in a loclist
-function M.show_toc()
- local bufnr = vim.api.nvim_get_current_buf()
- local parser = assert(vim.treesitter.get_parser(bufnr, 'vimdoc', { error = false }))
- local query = vim.treesitter.query.parse(
- parser:lang(),
- [[
- (h1 (heading) @h1)
- (h2 (heading) @h2)
- (h3 (heading) @h3)
- (column_heading (heading) @h4)
- ]]
- )
- local root = parser:parse()[1]:root()
- local headings = {}
- for id, node, _, _ in query:iter_captures(root, bufnr) do
- local text = vim.treesitter.get_node_text(node, bufnr)
- local capture = query.captures[id]
- local row, col = node:start()
- -- only column_headings at col 1 are headings, otherwise it's code examples
- local is_code = (capture == 'h4' and col > 0)
- -- ignore tabular material
- local is_table = (capture == 'h4' and (text:find('\t') or text:find(' ')))
- -- ignore tag-only headings
- local is_tag = node:child_count() == 1 and node:child(0):type() == 'tag'
- if not (is_code or is_table or is_tag) then
- table.insert(headings, {
- bufnr = bufnr,
- lnum = row + 1,
- text = (capture == 'h3' or capture == 'h4') and '  ' .. text or text,
- })
- end
- end
- vim.fn.setloclist(0, headings, ' ')
- vim.fn.setloclist(0, {}, 'a', { title = 'Help TOC' })
- vim.cmd.lopen()
-end
-
-return M