diff options
Diffstat (limited to 'runtime/lua/vim')
-rw-r--r-- | runtime/lua/vim/_defaults.lua | 8 | ||||
-rw-r--r-- | runtime/lua/vim/_editor.lua | 2 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/api.lua | 8 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/api_keysets.lua | 1 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/vimfn.lua | 3 | ||||
-rw-r--r-- | runtime/lua/vim/_options.lua | 6 | ||||
-rw-r--r-- | runtime/lua/vim/filetype.lua | 13 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/completion.lua | 4 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/util.lua | 5 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter.lua | 13 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/_fold.lua | 12 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/_headings.lua | 144 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/_meta/tsnode.lua | 2 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/highlighter.lua | 7 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/language.lua | 1 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/languagetree.lua | 119 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/query.lua | 10 | ||||
-rw-r--r-- | runtime/lua/vim/vimhelp.lua | 71 |
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 |