diff options
Diffstat (limited to 'runtime/lua/vim')
-rw-r--r-- | runtime/lua/vim/_editor.lua | 2 | ||||
-rw-r--r-- | runtime/lua/vim/_inspector.lua | 2 | ||||
-rw-r--r-- | runtime/lua/vim/filetype.lua | 7 | ||||
-rw-r--r-- | runtime/lua/vim/filetype/detect.lua | 22 | ||||
-rw-r--r-- | runtime/lua/vim/fs.lua | 18 | ||||
-rw-r--r-- | runtime/lua/vim/lsp.lua | 22 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/_watchfiles.lua | 17 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/buf.lua | 2 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/handlers.lua | 2 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/util.lua | 30 | ||||
-rw-r--r-- | runtime/lua/vim/shared.lua | 8 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter.lua | 10 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/_fold.lua | 4 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/_meta.lua | 18 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/_range.lua | 38 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/language.lua | 5 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/languagetree.lua | 73 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/query.lua | 45 | ||||
-rw-r--r-- | runtime/lua/vim/version.lua | 2 |
19 files changed, 234 insertions, 93 deletions
diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 20e813d77c..b26def5958 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -778,7 +778,7 @@ do -- some bugs, so fake the two-step dance for now. local matches - --- Omnifunc for completing lua values from from the runtime lua interpreter, + --- Omnifunc for completing lua values from the runtime lua interpreter, --- similar to the builtin completion for the `:lua` command. --- --- Activate using `set omnifunc=v:lua.vim.lua_omnifunc` in a lua buffer. diff --git a/runtime/lua/vim/_inspector.lua b/runtime/lua/vim/_inspector.lua index 2ebb7a7efd..ecd39c35bc 100644 --- a/runtime/lua/vim/_inspector.lua +++ b/runtime/lua/vim/_inspector.lua @@ -90,7 +90,7 @@ function vim.inspect_pos(bufnr, row, col, filter) nsmap[id] = name end - --- Convert an extmark tuple into a map-like table + --- Convert an extmark tuple into a table --- @private local function to_map(extmark) extmark = { diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 5c799b23f2..1b04666161 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -579,6 +579,7 @@ local extension = { ['json-patch'] = 'json', json5 = 'json5', jsonc = 'jsonc', + jsonl = 'jsonl', jsonnet = 'jsonnet', libsonnet = 'jsonnet', jsp = 'jsp', @@ -997,7 +998,9 @@ local extension = { spi = 'spyce', spy = 'spyce', tyc = 'sql', - typ = 'sql', + typ = function(path, bufnr) + return require('vim.filetype.detect').typ(bufnr) + end, pkb = 'sql', tyb = 'sql', pks = 'sql', @@ -1078,6 +1081,8 @@ local extension = { uit = 'uil', uil = 'uil', ungram = 'ungrammar', + usd = 'usd', + usda = 'usd', sba = 'vb', vb = 'vb', dsm = 'vb', diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index 74b01d569c..94106a3547 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -1322,6 +1322,28 @@ function M.txt(bufnr) end end +function M.typ(bufnr) + if vim.g.filetype_typ then + return vim.g.filetype_typ + end + + for _, line in ipairs(getlines(bufnr, 1, 200)) do + if + findany(line, { + '^CASE[%s]?=[%s]?SAME$', + '^CASE[%s]?=[%s]?LOWER$', + '^CASE[%s]?=[%s]?UPPER$', + '^CASE[%s]?=[%s]?OPPOSITE$', + '^TYPE%s', + }) + then + return 'sql' + end + end + + return 'typst' +end + -- Determine if a .v file is Verilog, V, or Coq function M.v(bufnr) if vim.fn.did_filetype() ~= 0 then diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index 2a51bde263..864ba495f1 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -72,8 +72,12 @@ function M.basename(file) return file:match('[/\\]$') and '' or (file:match('[^\\/]*$'):gsub('\\', '/')) end ----@private -local function join_paths(...) +--- Concatenate directories and/or file into a single path with normalization +--- (e.g., `"foo/"` and `"bar"` get joined to `"foo/bar"`) +--- +---@param ... string +---@return string +function M.joinpath(...) return (table.concat({ ... }, '/'):gsub('//+', '/')) end @@ -116,14 +120,14 @@ function M.dir(path, opts) local dirs = { { path, 1 } } while #dirs > 0 do local dir0, level = unpack(table.remove(dirs, 1)) - local dir = level == 1 and dir0 or join_paths(path, dir0) + local dir = level == 1 and dir0 or M.joinpath(path, dir0) local fs = vim.loop.fs_scandir(M.normalize(dir)) while fs do local name, t = vim.loop.fs_scandir_next(fs) if not name then break end - local f = level == 1 and name or join_paths(dir0, name) + local f = level == 1 and name or M.joinpath(dir0, name) coroutine.yield(f, t) if opts.depth @@ -230,7 +234,7 @@ function M.find(names, opts) local t = {} for name, type in M.dir(p) do if (not opts.type or opts.type == type) and names(name, p) then - table.insert(t, join_paths(p, name)) + table.insert(t, M.joinpath(p, name)) end end return t @@ -239,7 +243,7 @@ function M.find(names, opts) test = function(p) local t = {} for _, name in ipairs(names) do - local f = join_paths(p, name) + local f = M.joinpath(p, name) local stat = vim.loop.fs_stat(f) if stat and (not opts.type or opts.type == stat.type) then t[#t + 1] = f @@ -276,7 +280,7 @@ function M.find(names, opts) end for other, type_ in M.dir(dir) do - local f = join_paths(dir, other) + local f = M.joinpath(dir, other) if type(names) == 'function' then if (not opts.type or opts.type == type_) and names(other, dir) then if add(f) then diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 5c78bd7580..2e6ca7a0ac 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -8,12 +8,8 @@ local sync = require('vim.lsp.sync') local semantic_tokens = require('vim.lsp.semantic_tokens') local api = vim.api -local nvim_err_writeln, nvim_buf_get_lines, nvim_command, nvim_buf_get_option, nvim_exec_autocmds = - api.nvim_err_writeln, - api.nvim_buf_get_lines, - api.nvim_command, - api.nvim_buf_get_option, - api.nvim_exec_autocmds +local nvim_err_writeln, nvim_buf_get_lines, nvim_command, nvim_exec_autocmds = + api.nvim_err_writeln, api.nvim_buf_get_lines, api.nvim_command, api.nvim_exec_autocmds local uv = vim.loop local tbl_isempty, tbl_extend = vim.tbl_isempty, vim.tbl_extend local validate = vim.validate @@ -137,7 +133,7 @@ local format_line_ending = { ---@param bufnr (number) ---@return string local function buf_get_line_ending(bufnr) - return format_line_ending[nvim_buf_get_option(bufnr, 'fileformat')] or '\n' + return format_line_ending[vim.bo[bufnr].fileformat] or '\n' end local client_index = 0 @@ -319,7 +315,7 @@ end local function buf_get_full_text(bufnr) local line_ending = buf_get_line_ending(bufnr) local text = table.concat(nvim_buf_get_lines(bufnr, 0, -1, true), line_ending) - if nvim_buf_get_option(bufnr, 'eol') then + if vim.bo[bufnr].eol then text = text .. line_ending end return text @@ -709,7 +705,7 @@ local function text_document_did_open_handler(bufnr, client) if not api.nvim_buf_is_loaded(bufnr) then return end - local filetype = nvim_buf_get_option(bufnr, 'filetype') + local filetype = vim.bo[bufnr].filetype local params = { textDocument = { @@ -901,8 +897,8 @@ end --- Field `cmd` in {config} is required. --- ---@param config (table) Configuration for the server: ---- - cmd: (table|string|fun(dispatchers: table):table) command string or ---- list treated like |jobstart()|. The command must launch the language server +--- - cmd: (string[]|fun(dispatchers: table):table) command a list of +--- strings treated like |jobstart()|. The command must launch the language server --- process. `cmd` can also be a function that creates an RPC client. --- The function receives a dispatchers table and must return a table with the --- functions `request`, `notify`, `is_closing` and `terminate` @@ -913,7 +909,7 @@ end --- the `cmd` process. Not related to `root_dir`. --- --- - cmd_env: (table) Environment flags to pass to the LSP on ---- spawn. Must be specified using a map-like table. +--- spawn. Must be specified using a table. --- Non-string values are coerced to string. --- Example: --- <pre> @@ -2177,7 +2173,7 @@ end --- --- Currently only supports a single client. This can be set via --- `setlocal formatexpr=v:lua.vim.lsp.formatexpr()` but will typically or in `on_attach` ---- via ``vim.api.nvim_buf_set_option(bufnr, 'formatexpr', 'v:lua.vim.lsp.formatexpr(#{timeout_ms:250})')``. +--- via ``vim.bo[bufnr].formatexpr = 'v:lua.vim.lsp.formatexpr(#{timeout_ms:250})'``. --- ---@param opts table options for customizing the formatting expression which takes the --- following optional keys: diff --git a/runtime/lua/vim/lsp/_watchfiles.lua b/runtime/lua/vim/lsp/_watchfiles.lua index 533a955925..cf2c57db1f 100644 --- a/runtime/lua/vim/lsp/_watchfiles.lua +++ b/runtime/lua/vim/lsp/_watchfiles.lua @@ -193,21 +193,27 @@ local to_lsp_change_type = { function M.register(reg, ctx) local client_id = ctx.client_id local client = vim.lsp.get_client_by_id(client_id) - if not client.workspace_folders then + if + -- Ill-behaved servers may not honor the client capability and try to register + -- anyway, so ignore requests when the user has opted out of the feature. + not client.config.capabilities.workspace.didChangeWatchedFiles.dynamicRegistration + or not client.workspace_folders + then return end local watch_regs = {} for _, w in ipairs(reg.registerOptions.watchers) do + local relative_pattern = false local glob_patterns = {} if type(w.globPattern) == 'string' then for _, folder in ipairs(client.workspace_folders) do table.insert(glob_patterns, { baseUri = folder.uri, pattern = w.globPattern }) end else + relative_pattern = true table.insert(glob_patterns, w.globPattern) end for _, glob_pattern in ipairs(glob_patterns) do - local pattern = parse(glob_pattern.pattern) local base_dir = nil if type(glob_pattern.baseUri) == 'string' then base_dir = glob_pattern.baseUri @@ -216,9 +222,16 @@ function M.register(reg, ctx) end assert(base_dir, "couldn't identify root of watch") base_dir = vim.uri_to_fname(base_dir) + local kind = w.kind or protocol.WatchKind.Create + protocol.WatchKind.Change + protocol.WatchKind.Delete + local pattern = glob_pattern.pattern + if relative_pattern then + pattern = base_dir .. '/' .. pattern + end + pattern = parse(pattern) + table.insert(watch_regs, { base_dir = base_dir, pattern = pattern, diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 3d9011656f..a307dea673 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -31,7 +31,9 @@ end --- ready. --- ---@returns `true` if server responds. +---@deprecated function M.server_ready() + vim.deprecate('vim.lsp.buf.server_ready', nil, '0.10.0') return not not vim.lsp.buf_notify(0, 'window/progress', {}) end diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 71bef43bc1..8e926c4644 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -454,7 +454,7 @@ function M.signature_help(_, result, ctx, config) local client = vim.lsp.get_client_by_id(ctx.client_id) local triggers = vim.tbl_get(client.server_capabilities, 'signatureHelpProvider', 'triggerCharacters') - local ft = api.nvim_buf_get_option(ctx.bufnr, 'filetype') + local ft = vim.bo[ctx.bufnr].filetype local lines, hl = util.convert_signature_help_to_markdown_lines(result, ft, triggers) lines = util.trim_empty_lines(lines) if vim.tbl_isempty(lines) then diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 8274361f6d..9fffc845b1 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -401,7 +401,7 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) if not api.nvim_buf_is_loaded(bufnr) then vim.fn.bufload(bufnr) end - api.nvim_buf_set_option(bufnr, 'buflisted', true) + vim.bo[bufnr].buflisted = true -- Fix reversed range and indexing each text_edits local index = 0 @@ -530,11 +530,7 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) -- Remove final line if needed local fix_eol = has_eol_text_edit - fix_eol = fix_eol - and ( - api.nvim_buf_get_option(bufnr, 'eol') - or (api.nvim_buf_get_option(bufnr, 'fixeol') and not api.nvim_buf_get_option(bufnr, 'binary')) - ) + fix_eol = fix_eol and (vim.bo[bufnr].eol or (vim.bo[bufnr].fixeol and not vim.bo[bufnr].binary)) fix_eol = fix_eol and get_line(bufnr, max - 1) == '' if fix_eol then api.nvim_buf_set_lines(bufnr, -2, -1, false, {}) @@ -1076,7 +1072,7 @@ function M.make_floating_popup_options(width, height, opts) local wincol = opts.relative == 'mouse' and vim.fn.getmousepos().column or vim.fn.wincol() - if wincol + width + (opts.offset_x or 0) <= api.nvim_get_option('columns') then + if wincol + width + (opts.offset_x or 0) <= vim.o.columns then anchor = anchor .. 'W' col = 0 else @@ -1142,7 +1138,7 @@ function M.show_document(location, offset_encoding, opts) or focus and api.nvim_get_current_win() or create_window_without_focus() - api.nvim_buf_set_option(bufnr, 'buflisted', true) + vim.bo[bufnr].buflisted = true api.nvim_win_set_buf(win, bufnr) if focus then api.nvim_set_current_win(win) @@ -1201,12 +1197,12 @@ function M.preview_location(location, opts) end local range = location.targetRange or location.range local contents = api.nvim_buf_get_lines(bufnr, range.start.line, range['end'].line + 1, false) - local syntax = api.nvim_buf_get_option(bufnr, 'syntax') + local syntax = vim.bo[bufnr].syntax if syntax == '' then -- When no syntax is set, we use filetype as fallback. This might not result -- in a valid syntax definition. See also ft detection in stylize_markdown. -- An empty syntax is more common now with TreeSitter, since TS disables syntax. - syntax = api.nvim_buf_get_option(bufnr, 'filetype') + syntax = vim.bo[bufnr].filetype end opts = opts or {} opts.focus_id = 'location' @@ -1665,7 +1661,7 @@ function M.open_floating_preview(contents, syntax, opts) contents = M.stylize_markdown(floating_bufnr, contents, opts) else if syntax then - api.nvim_buf_set_option(floating_bufnr, 'syntax', syntax) + vim.bo[floating_bufnr].syntax = syntax end api.nvim_buf_set_lines(floating_bufnr, 0, -1, true, contents) end @@ -1681,16 +1677,16 @@ function M.open_floating_preview(contents, syntax, opts) local float_option = M.make_floating_popup_options(width, height, opts) local floating_winnr = api.nvim_open_win(floating_bufnr, false, float_option) if do_stylize then - api.nvim_win_set_option(floating_winnr, 'conceallevel', 2) - api.nvim_win_set_option(floating_winnr, 'concealcursor', 'n') + vim.wo[floating_winnr].conceallevel = 2 + vim.wo[floating_winnr].concealcursor = 'n' end -- disable folding - api.nvim_win_set_option(floating_winnr, 'foldenable', false) + vim.wo[floating_winnr].foldenable = false -- soft wrapping - api.nvim_win_set_option(floating_winnr, 'wrap', opts.wrap) + vim.wo[floating_winnr].wrap = opts.wrap - api.nvim_buf_set_option(floating_bufnr, 'modifiable', false) - api.nvim_buf_set_option(floating_bufnr, 'bufhidden', 'wipe') + vim.bo[floating_bufnr].modifiable = false + vim.bo[floating_bufnr].bufhidden = 'wipe' api.nvim_buf_set_keymap( floating_bufnr, 'n', diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index a55deb1415..4f230c4412 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -361,7 +361,7 @@ local function tbl_extend(behavior, deep_extend, ...) return ret end ---- Merges two or more map-like tables. +--- Merges two or more tables. --- ---@see |extend()| --- @@ -369,13 +369,13 @@ end --- - "error": raise an error --- - "keep": use value from the leftmost map --- - "force": use value from the rightmost map ----@param ... table Two or more map-like tables +---@param ... table Two or more tables ---@return table Merged table function vim.tbl_extend(behavior, ...) return tbl_extend(behavior, false, ...) end ---- Merges recursively two or more map-like tables. +--- Merges recursively two or more tables. --- ---@see |vim.tbl_extend()| --- @@ -385,7 +385,7 @@ end --- - "error": raise an error --- - "keep": use value from the leftmost map --- - "force": use value from the rightmost map ----@param ... T2 Two or more map-like tables +---@param ... T2 Two or more tables ---@return T1|T2 (table) Merged table function vim.tbl_deep_extend(behavior, ...) return tbl_extend(behavior, true, ...) diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index d1f5996768..12fbe1654f 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -136,16 +136,6 @@ function M.get_parser(bufnr, lang, opts) return parsers[bufnr] end ----@package ----@param bufnr (integer|nil) Buffer number ----@return boolean -function M._has_parser(bufnr) - if bufnr == nil or bufnr == 0 then - bufnr = api.nvim_get_current_buf() - end - return parsers[bufnr] ~= nil -end - --- Returns a string parser --- ---@param str string Text to parse diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index f6425d7cb9..a8f8c7967e 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -301,7 +301,8 @@ function M.foldexpr(lnum) lnum = lnum or vim.v.lnum local bufnr = api.nvim_get_current_buf() - if not ts._has_parser(bufnr) or not lnum then + local parser = vim.F.npcall(ts.get_parser, bufnr) + if not parser then return '0' end @@ -309,7 +310,6 @@ function M.foldexpr(lnum) foldinfos[bufnr] = FoldInfo.new() get_folds_levels(bufnr, foldinfos[bufnr]) - local parser = ts.get_parser(bufnr) parser:register_cbs({ on_changedtree = function(tree_changes) on_changedtree(bufnr, foldinfos[bufnr], tree_changes) diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua index 4d0f43d030..9ca4b560c6 100644 --- a/runtime/lua/vim/treesitter/_meta.lua +++ b/runtime/lua/vim/treesitter/_meta.lua @@ -31,17 +31,21 @@ local TSNode = {} ---@param query userdata ---@param captures true ----@param start integer ----@param end_ integer +---@param start? integer +---@param end_? integer +---@param opts? table ---@return fun(): integer, TSNode, any -function TSNode:_rawquery(query, captures, start, end_) end +function TSNode:_rawquery(query, captures, start, end_, opts) end ---@param query userdata ---@param captures false ----@param start integer ----@param end_ integer +---@param start? integer +---@param end_? integer +---@param opts? table ---@return fun(): string, any -function TSNode:_rawquery(query, captures, start, end_) end +function TSNode:_rawquery(query, captures, start, end_, opts) end + +---@alias TSLoggerCallback fun(logtype: 'parse'|'lex', msg: string) ---@class TSParser ---@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: boolean?): TSTree, integer[] @@ -50,6 +54,8 @@ function TSNode:_rawquery(query, captures, start, end_) end ---@field set_included_ranges fun(self: TSParser, ranges: Range6[]) ---@field set_timeout fun(self: TSParser, timeout: integer) ---@field timeout fun(self: TSParser): integer +---@field _set_logger fun(self: TSParser, lex: boolean, parse: boolean, cb: TSLoggerCallback) +---@field _logger fun(self: TSParser): TSLoggerCallback ---@class TSTree ---@field root fun(self: TSTree): TSNode diff --git a/runtime/lua/vim/treesitter/_range.lua b/runtime/lua/vim/treesitter/_range.lua index f4db5016ac..35081c6400 100644 --- a/runtime/lua/vim/treesitter/_range.lua +++ b/runtime/lua/vim/treesitter/_range.lua @@ -143,6 +143,29 @@ function M.contains(r1, r2) return true end +--- @param source integer|string +--- @param index integer +--- @return integer +local function get_offset(source, index) + if index == 0 then + return 0 + end + + if type(source) == 'number' then + return api.nvim_buf_get_offset(source, index) + end + + local byte = 0 + local next_offset = source:gmatch('()\n') + local line = 1 + while line <= index do + byte = next_offset() --[[@as integer]] + line = line + 1 + end + + return byte +end + ---@private ---@param source integer|string ---@param range Range @@ -152,19 +175,10 @@ function M.add_bytes(source, range) return range --[[@as Range6]] end - local start_row, start_col, end_row, end_col = range[1], range[2], range[3], range[4] - local start_byte = 0 - local end_byte = 0 + local start_row, start_col, end_row, end_col = M.unpack4(range) -- TODO(vigoux): proper byte computation here, and account for EOL ? - if type(source) == 'number' then - -- Easy case, this is a buffer parser - start_byte = api.nvim_buf_get_offset(source, start_row) + start_col - end_byte = api.nvim_buf_get_offset(source, end_row) + end_col - elseif type(source) == 'string' then - -- string parser, single `\n` delimited string - start_byte = vim.fn.byteidx(source, start_col) - end_byte = vim.fn.byteidx(source, end_col) - end + local start_byte = get_offset(source, start_row) + start_col + local end_byte = get_offset(source, end_row) + end_col return { start_row, start_col, start_byte, end_row, end_col, end_byte } end diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua index b616d4d70b..08c297c9ad 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -27,6 +27,11 @@ function M.get_lang(filetype) if filetype == '' then return end + if ft_to_lang[filetype] then + return ft_to_lang[filetype] + end + -- support subfiletypes like html.glimmer + filetype = vim.split(filetype, '.', { plain = true })[1] return ft_to_lang[filetype] end diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 1adf6759fa..6c780f33c4 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -31,8 +31,17 @@ --- 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. +--- + +-- Debugging: +-- +-- vim.g.__ts_debug levels: +-- - 1. Messages from languagetree.lua +-- - 2. Parse messages from treesitter +-- - 2. Lex messages from treesitter +-- +-- Log file can be found in stdpath('log')/treesitter.log -local api = vim.api local query = require('vim.treesitter.query') local language = require('vim.treesitter.language') local Range = require('vim.treesitter._range') @@ -75,6 +84,8 @@ local TSCallbackNames = { ---@field private _source (integer|string) Buffer or string to parse ---@field private _trees TSTree[] Reference to parsed tree (one for each language) ---@field private _valid boolean|table<integer,boolean> If the parsed tree is valid +---@field private _logger? fun(logtype: string, msg: string) +---@field private _logfile? file* local LanguageTree = {} ---@class LanguageTreeOpts @@ -114,6 +125,10 @@ function LanguageTree.new(source, lang, opts) _callbacks_rec = {}, }, LanguageTree) + if vim.g.__ts_debug and type(vim.g.__ts_debug) == 'number' then + self:_set_logger() + end + for _, name in pairs(TSCallbackNames) do self._callbacks[name] = {} self._callbacks_rec[name] = {} @@ -122,6 +137,33 @@ function LanguageTree.new(source, lang, opts) return self end +function LanguageTree:_set_logger() + local source = self:source() + source = type(source) == 'string' and 'text' or tostring(source) + + local lang = self:lang() + + local logfilename = vim.fs._join_paths(vim.fn.stdpath('log'), 'treesitter.log') + + local logfile, openerr = io.open(logfilename, 'a+') + + if not logfile or openerr then + error(string.format('Could not open file (%s) for logging: %s', logfilename, openerr)) + return + end + + self._logfile = logfile + + self._logger = function(logtype, msg) + self._logfile:write(string.format('%s:%s:(%s) %s\n', source, lang, logtype, msg)) + self._logfile:flush() + end + + local log_lex = vim.g.__ts_debug >= 3 + local log_parse = vim.g.__ts_debug >= 2 + self._parser:_set_logger(log_lex, log_parse, self._logger) +end + ---@private ---Measure execution time of a function ---@generic R1, R2, R3 @@ -139,7 +181,11 @@ end ---@private ---@vararg any function LanguageTree:_log(...) - if vim.g.__ts_debug == nil then + if not self._logger then + return + end + + if not vim.g.__ts_debug or vim.g.__ts_debug < 1 then return end @@ -150,19 +196,17 @@ function LanguageTree:_log(...) local info = debug.getinfo(2, 'nl') local nregions = #self:included_regions() - local prefix = - string.format('%s:%d: [%s:%d] ', info.name, info.currentline, self:lang(), nregions) + local prefix = string.format('%s:%d: (#regions=%d) ', info.name, info.currentline, nregions) - api.nvim_out_write(prefix) + local msg = { prefix } for _, x in ipairs(args) do if type(x) == 'string' then - api.nvim_out_write(x) + msg[#msg + 1] = x else - api.nvim_out_write(vim.inspect(x, { newline = ' ', indent = '' })) + msg[#msg + 1] = vim.inspect(x, { newline = ' ', indent = '' }) end - api.nvim_out_write(' ') end - api.nvim_out_write('\n') + self._logger('nvim', table.concat(msg, ' ')) end --- Invalidates this parser and all its children @@ -876,16 +920,23 @@ end function LanguageTree:_on_detach(...) self:invalidate(true) self:_do_callback('detach', ...) + if self._logfile then + self._logger('nvim', 'detaching') + self._logger = nil + self._logfile:close() + end end --- Registers callbacks for the |LanguageTree|. ---@param cbs table An |nvim_buf_attach()|-like table argument with the following handlers: --- - `on_bytes` : see |nvim_buf_attach()|, but this will be called _after_ the parsers callback. --- - `on_changedtree` : a callback that will be called every time the tree has syntactical changes. ---- It will only be passed one argument, which is a table of the ranges (as node ranges) that ---- changed. +--- It will be passed two arguments: a table of the ranges (as node ranges) that +--- changed and the changed tree. --- - `on_child_added` : emitted when a child is added to the tree. --- - `on_child_removed` : emitted when a child is removed from the tree. +--- - `on_detach` : emitted when the buffer is detached, see |nvim_buf_detach_event|. +--- Takes one argument, the number of the buffer. --- @param recursive? boolean Apply callbacks recursively for all children. Any new children will --- also inherit the callbacks. function LanguageTree:register_cbs(cbs, recursive) diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 93841bb31e..73b561c777 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -382,6 +382,39 @@ local predicate_handlers = { return string_set[node_text] end, + + ['has-ancestor?'] = function(match, _, _, predicate) + local node = match[predicate[2]] + if not node then + return true + end + + local ancestor_types = {} + for _, type in ipairs({ unpack(predicate, 3) }) do + ancestor_types[type] = true + end + + node = node:parent() + while node do + if ancestor_types[node:type()] then + return true + end + node = node:parent() + end + return false + end, + + ['has-parent?'] = function(match, _, _, predicate) + local node = match[predicate[2]] + if not node then + return true + end + + if vim.list_contains({ unpack(predicate, 3) }, node:parent():type()) then + return true + end + return false + end, } -- As we provide lua-match? also expose vim-match? @@ -609,10 +642,10 @@ end --- --- {source} is needed if the query contains predicates; then the caller --- must ensure to use a freshly parsed tree consistent with the current ---- text of the buffer (if relevant). {start_row} and {end_row} can be used to limit +--- text of the buffer (if relevant). {start} and {stop} can be used to limit --- matches inside a row range (this is typically used with root node --- as the {node}, i.e., to get syntax highlight matches in the current ---- viewport). When omitted, the {start} and {end} row values are used from the given node. +--- viewport). When omitted, the {start} and {stop} row values are used from the given node. --- --- The iterator returns three values: a numeric id identifying the capture, --- the captured node, and metadata from any directives processing the match. @@ -686,16 +719,20 @@ end ---@param source (integer|string) Source buffer or string to search ---@param start integer Starting line for the search ---@param stop integer Stopping line for the search (end-exclusive) +---@param opts table|nil Options: +--- - max_start_depth (integer) if non-zero, sets the maximum start depth +--- for each match. This is used to prevent traversing too deep into a tree. +--- Requires treesitter >= 0.20.9. --- ---@return (fun(): integer, table<integer,TSNode>, table): pattern id, match, metadata -function Query:iter_matches(node, source, start, stop) +function Query:iter_matches(node, source, start, stop, opts) if type(source) == 'number' and source == 0 then source = api.nvim_get_current_buf() end start, stop = value_or_node_range(start, stop, node) - local raw_iter = node:_rawquery(self.query, false, start, stop) + local raw_iter = node:_rawquery(self.query, false, start, stop, opts) ---@cast raw_iter fun(): string, any local function iter() local pattern, match = raw_iter() diff --git a/runtime/lua/vim/version.lua b/runtime/lua/vim/version.lua index 3aacf3d4e0..ebe8f4e053 100644 --- a/runtime/lua/vim/version.lua +++ b/runtime/lua/vim/version.lua @@ -333,7 +333,7 @@ local function create_err_msg(v) return string.format('invalid version: %s (%s)', tostring(v), type(v)) end ---- Parses and compares two version version objects (the result of |vim.version.parse()|, or +--- Parses and compares two version objects (the result of |vim.version.parse()|, or --- specified literally as a `{major, minor, patch}` tuple, e.g. `{1, 0, 3}`). --- --- Example: |