From 01b91deec7b6113ae728ac3f7a55f64f57ddb3e0 Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Thu, 30 Nov 2023 07:04:20 -0600 Subject: fix(treesitter): fix parens stacking in inspector display (#26304) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When first opened, the tree-sitter inspector traverses all of the nodes in the buffer to calculate an array of nodes. This traversal is done only once, and _all_ nodes (both named and anonymous) are included. Toggling anonymous nodes in the inspector only changes how the tree is drawn in the buffer, but does not affect the underlying data structure at all. When the buffer is traversed and the list of nodes is calculated, we don't know whether or not anonymous nodes will be displayed in the inspector or not. Thus, we cannot determine during traversal where to put closing parentheses. Instead, this must be done when drawing. When we draw, the tree structure has been flatted into a single array, so we lose parent-child relationships that would otherwise make determining the number of closing parentheses straightforward. However, we can instead rely on the fact that a delta between the depth of a node and the depth of the successive node _must_ mean that more closing parentheses are required: (foo (bar) (baz) ↑ │ └ (bar) and (baz) have different depths, so (bar) must have an extra closing parenthesis This does not depend on whether or not anonymous nodes are displayed and so works in both cases. --- runtime/lua/vim/treesitter/dev.lua | 103 ++++++++++++++++++------------------- 1 file changed, 50 insertions(+), 53 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index 69ddc9b558..aad639d85a 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -14,16 +14,12 @@ local M = {} local TSTreeView = {} ---@class TSP.Node ----@field id integer Node id ----@field text string Node text ----@field named boolean True if this is a named (non-anonymous) node ----@field depth integer Depth of the node within the tree ----@field lnum integer Beginning line number of this node in the source buffer ----@field col integer Beginning column number of this node in the source buffer ----@field end_lnum integer Final line number of this node in the source buffer ----@field end_col integer Final column number of this node in the source buffer +---@field node TSNode Tree-sitter node +---@field field string? Node field +---@field depth integer Depth of this node in the tree +---@field text string? Text displayed in the inspector for this node. Not computed until the +--- inspector is drawn. ---@field lang string Source language of this node ----@field root TSNode ---@class TSP.Injection ---@field lang string Source language of this injection @@ -54,37 +50,14 @@ local function traverse(node, depth, lang, injections, tree) end for child, field in node:iter_children() do - local type = child:type() - local lnum, col, end_lnum, end_col = child:range() - local named = child:named() - local text ---@type string - if named then - if field then - text = string.format('%s: (%s', field, type) - else - text = string.format('(%s', type) - end - else - text = string.format('"%s"', type:gsub('\n', '\\n'):gsub('"', '\\"')) - end - table.insert(tree, { - id = child:id(), - text = text, - named = named, + node = child, + field = field, depth = depth, - lnum = lnum, - col = col, - end_lnum = end_lnum, - end_col = end_col, lang = lang, }) traverse(child, depth + 1, lang, injections, tree) - - if named then - tree[#tree].text = string.format('%s)', tree[#tree].text) - end end return tree @@ -132,7 +105,7 @@ function TSTreeView:new(bufnr, lang) local named = {} ---@type TSP.Node[] for _, v in ipairs(nodes) do - if v.named then + if v.node:named() then named[#named + 1] = v end end @@ -213,7 +186,7 @@ local function set_inspector_cursor(treeview, lang, source_buf, inspect_buf, ins local cursor_node_id = cursor_node:id() for i, v in treeview:iter() do - if v.id == cursor_node_id then + if v.node:id() == cursor_node_id then local start = v.depth * treeview.opts.indent ---@type integer local end_col = start + #v.text api.nvim_buf_set_extmark(inspect_buf, treeview.ns, i - 1, start, { @@ -228,6 +201,8 @@ end --- Write the contents of this View into {bufnr}. --- +--- Calling this function computes the text that is displayed for each node. +--- ---@param bufnr integer Buffer number to write into. ---@package function TSTreeView:draw(bufnr) @@ -235,13 +210,35 @@ function TSTreeView:draw(bufnr) local lines = {} ---@type string[] local lang_hl_marks = {} ---@type table[] - for _, item in self:iter() do - local range_str = get_range_str(item.lnum, item.col, item.end_lnum, item.end_col) + for i, item in self:iter() do + local range_str = get_range_str(item.node:range()) local lang_str = self.opts.lang and string.format(' %s', item.lang) or '' + + local text ---@type string + if item.node:named() then + if item.field then + text = string.format('%s: (%s', item.field, item.node:type()) + else + text = string.format('(%s', item.node:type()) + end + else + text = string.format('"%s"', item.node:type():gsub('\n', '\\n'):gsub('"', '\\"')) + end + + local next = self:get(i + 1) + if not next or next.depth <= item.depth then + local parens = item.depth - (next and next.depth or 0) + (item.node:named() and 1 or 0) + if parens > 0 then + text = string.format('%s%s', text, string.rep(')', parens)) + end + end + + item.text = text + local line = string.format( '%s%s ; %s%s', string.rep(' ', item.depth * self.opts.indent), - item.text, + text, range_str, lang_str ) @@ -253,7 +250,7 @@ function TSTreeView:draw(bufnr) } end - lines[#lines + 1] = line + lines[i] = line end api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) @@ -364,9 +361,9 @@ function M.inspect_tree(opts) desc = 'Jump to the node under the cursor in the source buffer', callback = function() local row = api.nvim_win_get_cursor(w)[1] - local pos = treeview:get(row) + local lnum, col = treeview:get(row).node:start() api.nvim_set_current_win(win) - api.nvim_win_set_cursor(win, { pos.lnum + 1, pos.col }) + api.nvim_win_set_cursor(win, { lnum + 1, col }) end, }) api.nvim_buf_set_keymap(b, 'n', 'a', '', { @@ -374,7 +371,7 @@ function M.inspect_tree(opts) callback = function() local row, col = unpack(api.nvim_win_get_cursor(w)) ---@type integer, integer local curnode = treeview:get(row) - while curnode and not curnode.named do + while curnode and not curnode.node:named() do row = row - 1 curnode = treeview:get(row) end @@ -386,9 +383,9 @@ function M.inspect_tree(opts) return end - local id = curnode.id + local id = curnode.node:id() for i, node in treeview:iter() do - if node.id == id then + if node.node:id() == id then api.nvim_win_set_cursor(w, { i, col }) break end @@ -424,20 +421,20 @@ function M.inspect_tree(opts) api.nvim_buf_clear_namespace(buf, treeview.ns, 0, -1) local row = api.nvim_win_get_cursor(w)[1] - local pos = treeview:get(row) - api.nvim_buf_set_extmark(buf, treeview.ns, pos.lnum, pos.col, { - end_row = pos.end_lnum, - end_col = math.max(0, pos.end_col), + local lnum, col, end_lnum, end_col = treeview:get(row).node:range() + api.nvim_buf_set_extmark(buf, treeview.ns, lnum, col, { + end_row = end_lnum, + end_col = math.max(0, end_col), hl_group = 'Visual', }) local topline, botline = vim.fn.line('w0', win), vim.fn.line('w$', win) -- Move the cursor if highlighted range is completely out of view - if pos.lnum < topline and pos.end_lnum < topline then - api.nvim_win_set_cursor(win, { pos.end_lnum + 1, 0 }) - elseif pos.lnum > botline and pos.end_lnum > botline then - api.nvim_win_set_cursor(win, { pos.lnum + 1, 0 }) + if lnum < topline and end_lnum < topline then + api.nvim_win_set_cursor(win, { end_lnum + 1, 0 }) + elseif lnum > botline and end_lnum > botline then + api.nvim_win_set_cursor(win, { lnum + 1, 0 }) end end, }) -- cgit From f5573fba3d460ddf479826b7404e568cc825ed57 Mon Sep 17 00:00:00 2001 From: Pham Huy Hoang Date: Fri, 1 Dec 2023 01:37:42 +0900 Subject: fix: Remove nested for_each_tree in TSTreeView (#26328) Problem: `LanguageTree:for_each_tree` calls itself for child nodes, so when we calls `for_each_tree` inside `for_each_tree`, this quickly leads to exponential tree calls. Solution: Use `pairs(child:trees())` directly in this case, as we don't need the extra callback for each children, this is already handled from the outer `for_each_tree` call --- runtime/lua/vim/treesitter/dev.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index aad639d85a..870761b7c7 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -87,17 +87,17 @@ function TSTreeView:new(bufnr, lang) parser:for_each_tree(function(parent_tree, parent_ltree) local parent = parent_tree:root() for _, child in pairs(parent_ltree:children()) do - child:for_each_tree(function(tree, ltree) + for _, tree in pairs(child:trees()) do local r = tree:root() local node = assert(parent:named_descendant_for_range(r:range())) local id = node:id() if not injections[id] or r:byte_length() > injections[id].root:byte_length() then injections[id] = { - lang = ltree:lang(), + lang = child:lang(), root = r, } end - end) + end end end) -- cgit From 307d5bcc7940d14f7d17e7c2de795ebed7b0f00c Mon Sep 17 00:00:00 2001 From: ObserverOfTime Date: Thu, 30 Nov 2023 19:00:33 +0200 Subject: vim-patch:9.0.2137: Can't detect angular & mustache filetypes Problem: Can't detect angular & mustache filetypes Solution: Detect *.mustache as Mustache filetype; detect *.component.html as html.angular filetype closes: vim/vim#13594 https://github.com/vim/vim/commit/7bed263c343c62129c5d8f51796895a28db1b312 --- runtime/lua/vim/filetype.lua | 2 ++ 1 file changed, 2 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index c6200f16bb..2bbc545fc4 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -695,6 +695,7 @@ local extension = { msql = 'msql', mu = 'mupad', mush = 'mush', + mustache = 'mustache', mysql = 'mysql', n1ql = 'n1ql', nql = 'n1ql', @@ -1716,6 +1717,7 @@ local pattern = { ['.*/etc/host%.conf'] = 'hostconf', ['.*/etc/hosts%.deny'] = 'hostsaccess', ['.*/etc/hosts%.allow'] = 'hostsaccess', + ['.*%.component%.html'] = 'html.angular', ['.*%.html%.m4'] = 'htmlm4', ['.*/%.i3/config'] = 'i3config', ['.*/i3/config'] = 'i3config', -- cgit From b32b5b2711ed338f4944c983a247f80c3163f87c Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Thu, 30 Nov 2023 16:17:38 -0600 Subject: fix: use BEL to terminate OSC 11 request (#26335) --- runtime/lua/vim/_defaults.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index cc872dea83..c3bb36fc36 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -297,7 +297,7 @@ do end, }) - io.stdout:write('\027]11;?\027\\') + io.stdout:write('\027]11;?\007') timer:start(1000, 0, function() -- No response received. Delete the autocommand -- cgit From fedbf32250ba303ee06c751e798c28c5a3f05862 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 2 Dec 2023 09:42:16 +0800 Subject: vim-patch:9.0.2139: html.angular ft is problematic (#26357) Problem: html.angular ft is problematic Solution: partly revert v9.0.2137 The html.angular filetype causes issues and does not trigger FileType autocommands for the html or angular filetypes. So let's roll back that particular change and detect this only as html file related: https://github.com/vim/vim/pull/13594#issuecomment-1834465890 closes: vim/vim#13604 https://github.com/vim/vim/commit/4f3480c94358e3b938594fc3574477e780525cd7 Co-authored-by: Christian Brabandt --- runtime/lua/vim/filetype.lua | 1 - 1 file changed, 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 2bbc545fc4..60882d4432 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1717,7 +1717,6 @@ local pattern = { ['.*/etc/host%.conf'] = 'hostconf', ['.*/etc/hosts%.deny'] = 'hostsaccess', ['.*/etc/hosts%.allow'] = 'hostsaccess', - ['.*%.component%.html'] = 'html.angular', ['.*%.html%.m4'] = 'htmlm4', ['.*/%.i3/config'] = 'i3config', ['.*/i3/config'] = 'i3config', -- cgit From cf612c64b0fc87c399bc5c72735335c5e73d6de1 Mon Sep 17 00:00:00 2001 From: Jongwook Choi Date: Mon, 4 Dec 2023 04:00:49 -0500 Subject: fix(treesitter): allow passing lang to vim.treesitter.get_node() now correctly takes opts.lang (#26360) PROBLEM: `vim.treesitter.get_node()` does not recognize the `lang` in the option table. This option was used in somewhere else, for instance, `vim.treesitter.dev` (for `inspect_tree`) but was never implemented. SOLUTION: Make `get_node()` correctly use `opts.lang` when getting a treesitter parser. --- runtime/lua/vim/treesitter.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index e7a66c00b2..7b795380fe 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -362,6 +362,7 @@ end --- - bufnr integer|nil Buffer number (nil or 0 for current buffer) --- - pos table|nil 0-indexed (row, col) tuple. Defaults to cursor position in the --- current window. Required if {bufnr} is not the current buffer +--- - lang string|nil Parser language. (default: from buffer filetype) --- - ignore_injections boolean Ignore injected languages (default true) --- ---@return TSNode | nil Node at the given position @@ -392,7 +393,7 @@ function M.get_node(opts) local ts_range = { row, col, row, col } - local root_lang_tree = M.get_parser(bufnr) + local root_lang_tree = M.get_parser(bufnr, opts.lang) if not root_lang_tree then return end -- cgit From c9828200ac99c0f0769c587ddc060045ab3a9ad5 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Tue, 5 Dec 2023 09:24:28 +0100 Subject: vim-patch:9.0.2148: Vim does not detect pacman.log file Problem: Vim does not detect pacman.log file Solution: Detect pacmanlogs and add syntax highlighting pacman.log is a filetype common to Arch Liux and related distributions. Add some simple syntax highlighting for the pacmanlog filetype. closes: vim/vim#13618 https://github.com/vim/vim/commit/1e5d66408ef85c750a5af03bbf5cc19b5de7a6bc Co-authored-by: Ronan Pigott --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 60882d4432..e64923ab54 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1394,6 +1394,7 @@ local filename = { octaverc = 'octave', ['octave.conf'] = 'octave', opam = 'opam', + ['pacman.log'] = 'pacmanlog', ['/etc/pam.conf'] = 'pamconf', ['pam_env.conf'] = 'pamenv', ['.pam_environment'] = 'pamenv', -- cgit From 0b74ad0a641f28d9d3da5353e98372d87078bd9d Mon Sep 17 00:00:00 2001 From: Riccardo Mazzarini Date: Tue, 5 Dec 2023 12:33:57 +0100 Subject: refactor(api): complete conversion from `Dictionary` to `Dict(opts)` (#26365) --- runtime/lua/vim/_meta/api.lua | 20 ++++++++++---------- runtime/lua/vim/_meta/api_keysets.lua | 22 ++++++++++++++++++++++ 2 files changed, 32 insertions(+), 10 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 49269ba631..4ad0a2e791 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -151,7 +151,7 @@ function vim.api.nvim_buf_add_highlight(buffer, ns_id, hl_group, line, col_start --- `nvim_buf_lines_event`. Else the first notification --- will be `nvim_buf_changedtick_event`. Not for Lua --- callbacks. ---- @param opts table Optional parameters. +--- @param opts vim.api.keyset.buf_attach Optional parameters. --- • on_lines: Lua callback invoked on change. Return `true` to detach. Args: --- • the string "lines" --- • buffer handle @@ -284,7 +284,7 @@ function vim.api.nvim_buf_del_var(buffer, name) end --- Deletes the buffer. See `:bwipeout` --- --- @param buffer integer Buffer handle, or 0 for current buffer ---- @param opts table Optional parameters. Keys: +--- @param opts vim.api.keyset.buf_delete Optional parameters. Keys: --- • force: Force deletion and ignore unsaved changes. --- • unload: Unloaded only, do not delete. See `:bunload` function vim.api.nvim_buf_delete(buffer, opts) end @@ -307,7 +307,7 @@ function vim.api.nvim_buf_get_commands(buffer, opts) end --- @param buffer integer Buffer handle, or 0 for current buffer --- @param ns_id integer Namespace id from `nvim_create_namespace()` --- @param id integer Extmark id ---- @param opts table Optional parameters. Keys: +--- @param opts vim.api.keyset.get_extmark Optional parameters. Keys: --- • details: Whether to include the details dict --- • hl_name: Whether to include highlight group name instead --- of id, true if omitted @@ -440,7 +440,7 @@ function vim.api.nvim_buf_get_option(buffer, name) end --- @param start_col integer Starting column (byte offset) on first line --- @param end_row integer Last line index, inclusive --- @param end_col integer Ending column (byte offset) on last line, exclusive ---- @param opts table Optional parameters. Currently unused. +--- @param opts vim.api.keyset.empty Optional parameters. Currently unused. --- @return string[] function vim.api.nvim_buf_get_text(buffer, start_row, start_col, end_row, end_col, opts) end @@ -626,7 +626,7 @@ function vim.api.nvim_buf_set_lines(buffer, start, end_, strict_indexing, replac --- @param name string Mark name --- @param line integer Line number --- @param col integer Column/row number ---- @param opts table Optional parameters. Reserved for future use. +--- @param opts vim.api.keyset.empty Optional parameters. Reserved for future use. --- @return boolean function vim.api.nvim_buf_set_mark(buffer, name, line, col, opts) end @@ -675,7 +675,7 @@ function vim.api.nvim_buf_set_var(buffer, name, value) end --- @param src_id integer --- @param line integer --- @param chunks any[] ---- @param opts table +--- @param opts vim.api.keyset.empty --- @return integer function vim.api.nvim_buf_set_virtual_text(buffer, src_id, line, chunks, opts) end @@ -1241,7 +1241,7 @@ function vim.api.nvim_get_keymap(mode) end --- Marks are (1,0)-indexed. `api-indexing` --- --- @param name string Mark name ---- @param opts table Optional parameters. Reserved for future use. +--- @param opts vim.api.keyset.empty Optional parameters. Reserved for future use. --- @return any[] function vim.api.nvim_get_mark(name, opts) end @@ -1439,7 +1439,7 @@ function vim.api.nvim_notify(msg, log_level, opts) end --- virtual terminal having the intended size. --- --- @param buffer integer the buffer to use (expected to be empty) ---- @param opts table Optional parameters. +--- @param opts vim.api.keyset.open_term Optional parameters. --- • on_input: Lua callback for input sent, i e keypresses in --- terminal mode. Note: keypresses are sent raw as they would --- be to the pty master end. For instance, a carriage return @@ -1602,7 +1602,7 @@ function vim.api.nvim_out_write(str) end --- Doesn't check the validity of command arguments. --- --- @param str string Command line string to parse. Cannot contain "\n". ---- @param opts table Optional parameters. Reserved for future use. +--- @param opts vim.api.keyset.empty Optional parameters. Reserved for future use. --- @return table function vim.api.nvim_parse_cmd(str, opts) end @@ -1685,7 +1685,7 @@ function vim.api.nvim_replace_termcodes(str, from_part, do_lt, special) end --- inserted in the buffer. Ignored for `cmdline-completion`. --- @param finish boolean Finish the completion and dismiss the popup menu. Implies --- {insert}. ---- @param opts table Optional parameters. Reserved for future use. +--- @param opts vim.api.keyset.empty Optional parameters. Reserved for future use. function vim.api.nvim_select_popupmenu_item(item, insert, finish, opts) end --- Sets the current buffer. diff --git a/runtime/lua/vim/_meta/api_keysets.lua b/runtime/lua/vim/_meta/api_keysets.lua index f69e5a92c7..6a3e574455 100644 --- a/runtime/lua/vim/_meta/api_keysets.lua +++ b/runtime/lua/vim/_meta/api_keysets.lua @@ -3,6 +3,19 @@ -- DO NOT EDIT error('Cannot require a meta file') +--- @class vim.api.keyset.buf_attach +--- @field on_lines? function +--- @field on_bytes? function +--- @field on_changedtick? function +--- @field on_detach? function +--- @field on_reload? function +--- @field utf_sizes? boolean +--- @field preview? boolean + +--- @class vim.api.keyset.buf_delete +--- @field force? boolean +--- @field unload? boolean + --- @class vim.api.keyset.clear_autocmds --- @field buffer? integer --- @field event? any @@ -74,6 +87,8 @@ error('Cannot require a meta file') --- @class vim.api.keyset.echo_opts --- @field verbose? boolean +--- @class vim.api.keyset.empty + --- @class vim.api.keyset.eval_statusline --- @field winid? integer --- @field maxwidth? integer @@ -124,6 +139,10 @@ error('Cannot require a meta file') --- @class vim.api.keyset.get_commands --- @field builtin? boolean +--- @class vim.api.keyset.get_extmark +--- @field details? boolean +--- @field hl_name? boolean + --- @class vim.api.keyset.get_extmarks --- @field limit? integer --- @field details? boolean @@ -196,6 +215,9 @@ error('Cannot require a meta file') --- @field desc? string --- @field replace_keycodes? boolean +--- @class vim.api.keyset.open_term +--- @field on_input? function + --- @class vim.api.keyset.option --- @field scope? string --- @field win? integer -- cgit From 5b40a1c09dda83275784053b325ad16626fc55f2 Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Tue, 5 Dec 2023 18:35:22 -0800 Subject: feat(lua): implement Iter:join() (#26416) --- runtime/lua/vim/iter.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index 874bdfb437..e9c2b66bf2 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -356,6 +356,18 @@ function ListIter.totable(self) return self._table end +--- Collect the iterator into a delimited string. +--- +--- Each element in the iterator is joined into a string separated by {delim}. +--- +--- Consumes the iterator. +--- +--- @param delim string Delimiter +--- @return string +function Iter.join(self, delim) + return table.concat(self:totable(), delim) +end + --- Folds ("reduces") an iterator into a single value. --- --- Examples: -- cgit From e057b38e7037808b3593fb1035794595b4e4a45e Mon Sep 17 00:00:00 2001 From: Emanuel Date: Wed, 6 Dec 2023 16:56:04 +0100 Subject: fix(json): allow objects with empty keys #25564 Problem: Empty string is a valid JSON key, but json_decode() treats an object with empty key as ":help msgpack-special-dict". #20757 :echo json_decode('{"": "1"}') {'_TYPE': [], '_VAL': [['', '1']]} Note: vim returns `{'': '1'}`. Solution: Allow empty string as an object key. Note that we still (currently) disallow empty keys in object_to_vim() (since 7c01d5ff9286d262097484c680e3a4eab49e2911): https://github.com/neovim/neovim/blob/f64e4b43e1191ff30d902730f752875aa55682ce/src/nvim/api/private/converter.c#L333-L334 Fix #20757 Co-authored-by: Justin M. Keyes --- runtime/lua/vim/_meta/vimfn.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index 05e5b2b871..7234b813b6 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -4678,8 +4678,7 @@ function vim.fn.join(list, sep) end --- Vim value. In the following cases it will output --- |msgpack-special-dict|: --- 1. Dictionary contains duplicate key. ---- 2. Dictionary contains empty key. ---- 3. String contains NUL byte. Two special dictionaries: for +--- 2. String contains NUL byte. Two special dictionaries: for --- dictionary and for string will be emitted in case string --- with NUL byte was a dictionary key. --- @@ -5922,7 +5921,6 @@ function vim.fn.msgpackdump(list, type) end --- are binary strings). --- 2. String with NUL byte inside. --- 3. Duplicate key. ---- 4. Empty key. --- ext |List| with two values: first is a signed integer --- representing extension type. Second is --- |readfile()|-style list of strings. -- cgit From 2613ba5000d4c0d9b15e2eec2d2b97615575925e Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Tue, 5 Dec 2023 10:01:32 -0800 Subject: feat(defaults): enable 'termguicolors' by default when supported by terminal Enable 'termguicolors' automatically when Nvim can detect that truecolor is supported by the host terminal. If $COLORTERM is set to "truecolor" or "24bit", or the terminal's terminfo entry contains capabilities for Tc, RGB, or setrgbf and setrgbb, then we assume that the terminal supports truecolor. Otherwise, the terminal is queried (using both XTGETTCAP and SGR + DECRQSS). If the terminal's response to these queries (if any) indicates that it supports truecolor, then 'termguicolors' is enabled. --- runtime/lua/vim/_defaults.lua | 255 +++++++++++++++++++++++++++----------- runtime/lua/vim/_meta.lua | 1 + runtime/lua/vim/_meta/options.lua | 4 + runtime/lua/vim/termcap.lua | 52 ++++++-- 4 files changed, 227 insertions(+), 85 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index c3bb36fc36..b73681be04 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -165,91 +165,92 @@ do }) end ---- Guess value of 'background' based on terminal color. ---- ---- We write Operating System Command (OSC) 11 to the terminal to request the ---- terminal's background color. We then wait for a response. If the response ---- matches `rgba:RRRR/GGGG/BBBB/AAAA` where R, G, B, and A are hex digits, then ---- compute the luminance[1] of the RGB color and classify it as light/dark ---- accordingly. Note that the color components may have anywhere from one to ---- four hex digits, and require scaling accordingly as values out of 4, 8, 12, ---- or 16 bits. Also note the A(lpha) component is optional, and is parsed but ---- ignored in the calculations. ---- ---- [1] https://en.wikipedia.org/wiki/Luma_%28video%29 -do - --- Parse a string of hex characters as a color. - --- - --- The string can contain 1 to 4 hex characters. The returned value is - --- between 0.0 and 1.0 (inclusive) representing the intensity of the color. - --- - --- For instance, if only a single hex char "a" is used, then this function - --- returns 0.625 (10 / 16), while a value of "aa" would return 0.664 (170 / - --- 256). - --- - --- @param c string Color as a string of hex chars - --- @return number? Intensity of the color - local function parsecolor(c) - if #c == 0 or #c > 4 then - return nil - end - - local val = tonumber(c, 16) - if not val then - return nil - end - - local max = tonumber(string.rep('f', #c), 16) - return val / max +-- Only do the following when the TUI is attached +local tty = nil +for _, ui in ipairs(vim.api.nvim_list_uis()) do + if ui.chan == 1 and ui.stdout_tty then + tty = ui + break end +end - --- Parse an OSC 11 response - --- - --- Either of the two formats below are accepted: - --- - --- OSC 11 ; rgb:// - --- - --- or - --- - --- OSC 11 ; rgba:/// +if tty then + --- Guess value of 'background' based on terminal color. --- - --- where + --- We write Operating System Command (OSC) 11 to the terminal to request the + --- terminal's background color. We then wait for a response. If the response + --- matches `rgba:RRRR/GGGG/BBBB/AAAA` where R, G, B, and A are hex digits, then + --- compute the luminance[1] of the RGB color and classify it as light/dark + --- accordingly. Note that the color components may have anywhere from one to + --- four hex digits, and require scaling accordingly as values out of 4, 8, 12, + --- or 16 bits. Also note the A(lpha) component is optional, and is parsed but + --- ignored in the calculations. --- - --- , , , := h | hh | hhh | hhhh - --- - --- The alpha component is ignored, if present. - --- - --- @param resp string OSC 11 response - --- @return string? Red component - --- @return string? Green component - --- @return string? Blue component - local function parseosc11(resp) - local r, g, b - r, g, b = resp:match('^\027%]11;rgb:(%x+)/(%x+)/(%x+)$') - if not r and not g and not b then - local a - r, g, b, a = resp:match('^\027%]11;rgba:(%x+)/(%x+)/(%x+)/(%x+)$') - if not a or #a > 4 then - return nil, nil, nil + --- [1] https://en.wikipedia.org/wiki/Luma_%28video%29 + do + --- Parse a string of hex characters as a color. + --- + --- The string can contain 1 to 4 hex characters. The returned value is + --- between 0.0 and 1.0 (inclusive) representing the intensity of the color. + --- + --- For instance, if only a single hex char "a" is used, then this function + --- returns 0.625 (10 / 16), while a value of "aa" would return 0.664 (170 / + --- 256). + --- + --- @param c string Color as a string of hex chars + --- @return number? Intensity of the color + local function parsecolor(c) + if #c == 0 or #c > 4 then + return nil end - end - if r and g and b and #r <= 4 and #g <= 4 and #b <= 4 then - return r, g, b + local val = tonumber(c, 16) + if not val then + return nil + end + + local max = tonumber(string.rep('f', #c), 16) + return val / max end - return nil, nil, nil - end + --- Parse an OSC 11 response + --- + --- Either of the two formats below are accepted: + --- + --- OSC 11 ; rgb:// + --- + --- or + --- + --- OSC 11 ; rgba:/// + --- + --- where + --- + --- , , , := h | hh | hhh | hhhh + --- + --- The alpha component is ignored, if present. + --- + --- @param resp string OSC 11 response + --- @return string? Red component + --- @return string? Green component + --- @return string? Blue component + local function parseosc11(resp) + local r, g, b + r, g, b = resp:match('^\027%]11;rgb:(%x+)/(%x+)/(%x+)$') + if not r and not g and not b then + local a + r, g, b, a = resp:match('^\027%]11;rgba:(%x+)/(%x+)/(%x+)/(%x+)$') + if not a or #a > 4 then + return nil, nil, nil + end + end + + if r and g and b and #r <= 4 and #g <= 4 and #b <= 4 then + return r, g, b + end - local tty = false - for _, ui in ipairs(vim.api.nvim_list_uis()) do - if ui.chan == 1 and ui.stdout_tty then - tty = true - break + return nil, nil, nil end - end - if tty then local timer = assert(vim.uv.new_timer()) ---@param bg string New value of the 'background' option @@ -300,7 +301,7 @@ do io.stdout:write('\027]11;?\007') timer:start(1000, 0, function() - -- No response received. Delete the autocommand + -- Delete the autocommand if no response was received vim.schedule(function() -- Suppress error if autocommand has already been deleted pcall(vim.api.nvim_del_autocmd, id) @@ -311,4 +312,108 @@ do end end) end + + --- If the TUI (term_has_truecolor) was able to determine that the host + --- terminal supports truecolor, enable 'termguicolors'. Otherwise, query the + --- terminal (using both XTGETTCAP and SGR + DECRQSS). If the terminal's + --- response indicates that it does support truecolor enable 'termguicolors', + --- but only if the user has not already disabled it. + do + if tty.rgb then + -- The TUI was able to determine truecolor support + vim.o.termguicolors = true + else + --- Enable 'termguicolors', but only if it was not already set by the user. + local function settgc() + if not vim.api.nvim_get_option_info2('termguicolors', {}).was_set then + vim.o.termguicolors = true + end + end + + local caps = {} ---@type table + require('vim.termcap').query({ 'Tc', 'RGB', 'setrgbf', 'setrgbb' }, function(cap, found) + if not found then + return + end + + caps[cap] = true + if caps.Tc or caps.RGB or (caps.setrgbf and caps.setrgbb) then + settgc() + end + end) + + local timer = assert(vim.uv.new_timer()) + + -- Arbitrary colors to set in the SGR sequence + local r = 1 + local g = 2 + local b = 3 + + local id = vim.api.nvim_create_autocmd('TermResponse', { + nested = true, + callback = function(args) + local resp = args.data ---@type string + local decrqss = resp:match('^\027P1%$r([%d;:]+)m$') + + if decrqss then + -- The DECRQSS SGR response first contains attributes separated by + -- semicolons, followed by the SGR itself with parameters separated + -- by colons. Some terminals include "0" in the attribute list + -- unconditionally; others do not. Our SGR sequence did not set any + -- attributes, so there should be no attributes in the list. + local attrs = vim.split(decrqss, ';') + if #attrs ~= 1 and (#attrs ~= 2 or attrs[1] ~= '0') then + return true + end + + -- The returned SGR sequence should begin with 48:2 + local sgr = attrs[#attrs]:match('^48:2:([%d:]+)$') + if not sgr then + return true + end + + -- The remaining elements of the SGR sequence should be the 3 colors + -- we set. Some terminals also include an additional parameter + -- (which can even be empty!), so handle those cases as well + local params = vim.split(sgr, ':') + if #params ~= 3 and (#params ~= 4 or (params[1] ~= '' and params[1] ~= '1')) then + return true + end + + if + tonumber(params[#params - 2]) == r + and tonumber(params[#params - 1]) == g + and tonumber(params[#params]) == b + then + settgc() + end + + return true + end + end, + }) + + -- Write SGR followed by DECRQSS. This sets the background color then + -- immediately asks the terminal what the background color is. If the + -- terminal responds to the DECRQSS with the same SGR sequence that we + -- sent then the terminal supports truecolor. + local decrqss = '\027P$qm\027\\' + if os.getenv('TMUX') then + decrqss = string.format('\027Ptmux;%s\027\\', decrqss:gsub('\027', '\027\027')) + end + io.stdout:write(string.format('\027[48;2;%d;%d;%dm%s', r, g, b, decrqss)) + + timer:start(1000, 0, function() + -- Delete the autocommand if no response was received + vim.schedule(function() + -- Suppress error if autocommand has already been deleted + pcall(vim.api.nvim_del_autocmd, id) + end) + + if not timer:is_closing() then + timer:close() + end + end) + end + end end diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua index e3b99f6b3d..bb9ed722e2 100644 --- a/runtime/lua/vim/_meta.lua +++ b/runtime/lua/vim/_meta.lua @@ -20,6 +20,7 @@ vim.lsp = require('vim.lsp') vim.re = require('vim.re') vim.secure = require('vim.secure') vim.snippet = require('vim.snippet') +vim.text = require('vim.text') vim.treesitter = require('vim.treesitter') vim.ui = require('vim.ui') vim.version = require('vim.version') diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index d2bdab4d28..5e65ca6b1b 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -6941,6 +6941,10 @@ vim.go.tbidi = vim.go.termbidi --- attributes instead of "cterm" attributes. `guifg` --- Requires an ISO-8613-3 compatible terminal. --- +--- Nvim will automatically attempt to determine if the host terminal +--- supports 24-bit color and will enable this option if it does +--- (unless explicitly disabled by the user). +--- --- @type boolean vim.o.termguicolors = false vim.o.tgc = vim.o.termguicolors diff --git a/runtime/lua/vim/termcap.lua b/runtime/lua/vim/termcap.lua index 862cc52149..b88d9ac9ba 100644 --- a/runtime/lua/vim/termcap.lua +++ b/runtime/lua/vim/termcap.lua @@ -12,7 +12,10 @@ local M = {} --- emulator supports the XTGETTCAP sequence. --- --- @param caps string|table A terminal capability or list of capabilities to query ---- @param cb function(cap:string, seq:string) Function to call when a response is received +--- @param cb function(cap:string, found:bool, seq:string?) Callback function which is called for +--- each capability in {caps}. {found} is set to true if the capability was found or false +--- otherwise. {seq} is the control sequence for the capability if found, or nil for +--- boolean capabilities. function M.query(caps, cb) vim.validate({ caps = { caps, { 'string', 'table' } }, @@ -23,21 +26,33 @@ function M.query(caps, cb) caps = { caps } end - local count = #caps + local pending = {} ---@type table + for _, v in ipairs(caps) do + pending[v] = true + end + + local timer = assert(vim.uv.new_timer()) - vim.api.nvim_create_autocmd('TermResponse', { + local id = vim.api.nvim_create_autocmd('TermResponse', { callback = function(args) local resp = args.data ---@type string - local k, v = resp:match('^\027P1%+r(%x+)=(%x+)$') - if k and v then + local k, rest = resp:match('^\027P1%+r(%x+)(.*)$') + if k and rest then local cap = vim.text.hexdecode(k) - local seq = - vim.text.hexdecode(v):gsub('\\E', '\027'):gsub('%%p%d', ''):gsub('\\(%d+)', string.char) + local seq ---@type string? + if rest:match('^=%x+$') then + seq = vim.text + .hexdecode(rest:sub(2)) + :gsub('\\E', '\027') + :gsub('%%p%d', '') + :gsub('\\(%d+)', string.char) + end + + cb(cap, true, seq) - cb(cap, seq) + pending[cap] = nil - count = count - 1 - if count == 0 then + if next(pending) == nil then return true end end @@ -57,6 +72,23 @@ function M.query(caps, cb) end io.stdout:write(query) + + timer:start(1000, 0, function() + -- Delete the autocommand if no response was received + vim.schedule(function() + -- Suppress error if autocommand has already been deleted + pcall(vim.api.nvim_del_autocmd, id) + + -- Call the callback for all capabilities that were not found + for k in pairs(pending) do + cb(k, false, nil) + end + end) + + if not timer:is_closing() then + timer:close() + end + end) end return M -- cgit From a1bdf2852d6d848e5b4522f9a66d53e9296c1872 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Fri, 8 Dec 2023 12:23:46 -0800 Subject: fix(snippet): remove misleading comment about TM_FILENAME_BASE (#26465) --- runtime/lua/vim/snippet.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/snippet.lua b/runtime/lua/vim/snippet.lua index 32a8ea0b0d..ac25fb6a2d 100644 --- a/runtime/lua/vim/snippet.lua +++ b/runtime/lua/vim/snippet.lua @@ -42,9 +42,7 @@ local function resolve_variable(var, default) elseif var == 'TM_FILENAME' then return expand_or_default('%:t') elseif var == 'TM_FILENAME_BASE' then - -- Not using '%:t:r' since we want to remove all extensions. - local filename_base = expand_or_default('%:t'):gsub('%.[^%.]*$', '') - return filename_base + return expand_or_default('%:t:r') elseif var == 'TM_DIRECTORY' then return expand_or_default('%:p:h:t') elseif var == 'TM_FILEPATH' then -- cgit From 3692fd4c873a2cd7ad69eb09765eed2993570c49 Mon Sep 17 00:00:00 2001 From: Jongwook Choi Date: Mon, 11 Dec 2023 04:10:00 -0500 Subject: feat(gen_lsp.lua): validate CLI args #26514 - Improve CLI argument parsing, rejects invalid argument and commands as early as possible. Also prints USAGE in the command line. - No longer allows `--`, use `--out ` instead. - Print a little bit of verbose messages to better know what's going on rather than remaining silent at all times. - Add type annotation `gen_lsp._opt` to avoid type warnings. --- runtime/lua/vim/lsp/_meta/protocol.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/_meta/protocol.lua b/runtime/lua/vim/lsp/_meta/protocol.lua index 72b0f00f65..979dad84fd 100644 --- a/runtime/lua/vim/lsp/_meta/protocol.lua +++ b/runtime/lua/vim/lsp/_meta/protocol.lua @@ -1,7 +1,7 @@ --[[ This file is autogenerated from scripts/gen_lsp.lua Regenerate: -nvim -l scripts/gen_lsp.lua gen --version 3.18 --runtime/lua/vim/lsp/_meta/protocol.lua +nvim -l scripts/gen_lsp.lua gen --version 3.18 --out runtime/lua/vim/lsp/_meta/protocol.lua --]] ---@meta -- cgit From 7c6f9690f74eea5ae922f1d0db808de61ef61ab0 Mon Sep 17 00:00:00 2001 From: Jaehwang Jung Date: Sat, 9 Dec 2023 15:21:08 +0900 Subject: fix(treesitter): fix indexing in foldexpr --- runtime/lua/vim/treesitter/_fold.lua | 43 ++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 21 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 5c1cc06908..d5626d0391 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -5,8 +5,8 @@ local Range = require('vim.treesitter._range') local api = vim.api ---@class TS.FoldInfo ----@field levels table ----@field levels0 table +---@field levels string[] the foldexpr value for each line +---@field levels0 integer[] the raw fold levels ---@field private start_counts table ---@field private stop_counts table local FoldInfo = {} @@ -24,13 +24,13 @@ end ---@package ---@param srow integer ----@param erow integer +---@param erow integer 0-indexed, exclusive function FoldInfo:invalidate_range(srow, erow) - for i = srow, erow do - self.start_counts[i + 1] = nil - self.stop_counts[i + 1] = nil - self.levels0[i + 1] = nil - self.levels[i + 1] = nil + for i = srow + 1, erow do + self.start_counts[i] = nil + self.stop_counts[i] = nil + self.levels0[i] = nil + self.levels[i] = nil end end @@ -55,7 +55,7 @@ end ---@package ---@param srow integer ----@param erow integer +---@param erow integer 0-indexed, exclusive function FoldInfo:remove_range(srow, erow) list_remove(self.levels, srow + 1, erow) list_remove(self.levels0, srow + 1, erow) @@ -91,7 +91,7 @@ end ---@package ---@param srow integer ----@param erow integer +---@param erow integer 0-indexed, exclusive function FoldInfo:add_range(srow, erow) list_insert(self.levels, srow + 1, erow, '-1') list_insert(self.levels0, srow + 1, erow, -1) @@ -140,10 +140,10 @@ end --- TODO(lewis6991): Handle this generally --- --- @param bufnr integer ---- @param erow integer? +--- @param erow integer? 0-indexed, exclusive --- @return integer local function normalise_erow(bufnr, erow) - local max_erow = api.nvim_buf_line_count(bufnr) - 1 + local max_erow = api.nvim_buf_line_count(bufnr) return math.min(erow or max_erow, max_erow) end @@ -152,7 +152,7 @@ end ---@param bufnr integer ---@param info TS.FoldInfo ---@param srow integer? ----@param erow integer? +---@param erow integer? 0-indexed, exclusive ---@param parse_injections? boolean local function get_folds_levels(bufnr, info, srow, erow, parse_injections) srow = srow or 0 @@ -173,10 +173,7 @@ local function get_folds_levels(bufnr, info, srow, erow, parse_injections) return end - -- erow in query is end-exclusive - local q_erow = erow and erow + 1 or -1 - - for id, node, metadata in query:iter_captures(tree:root(), bufnr, srow, q_erow) do + for id, node, metadata in query:iter_captures(tree:root(), bufnr, srow, erow) do if query.captures[id] == 'fold' then local range = ts.get_range(node, bufnr, metadata[id]) local start, _, stop, stop_col = Range.unpack4(range) @@ -205,7 +202,7 @@ local function get_folds_levels(bufnr, info, srow, erow, parse_injections) local current_level = info.levels0[srow] or 0 -- We now have the list of fold opening and closing, fill the gaps and mark where fold start - for lnum = srow + 1, erow + 1 do + for lnum = srow + 1, erow do local last_trimmed_level = trim_level(current_level) current_level = current_level + info:get_start(lnum) info.levels0[lnum] = current_level @@ -296,7 +293,10 @@ end local function on_changedtree(bufnr, foldinfo, tree_changes) schedule_if_loaded(bufnr, function() for _, change in ipairs(tree_changes) do - local srow, _, erow = Range.unpack4(change) + local srow, _, erow, ecol = Range.unpack4(change) + if ecol > 0 then + erow = erow + 1 + end get_folds_levels(bufnr, foldinfo, srow, erow) end if #tree_changes > 0 then @@ -311,8 +311,9 @@ end ---@param old_row integer ---@param new_row integer local function on_bytes(bufnr, foldinfo, start_row, old_row, new_row) - local end_row_old = start_row + old_row - local end_row_new = start_row + new_row + -- extend the end to fully include the range + local end_row_old = start_row + old_row + 1 + local end_row_new = start_row + new_row + 1 if new_row ~= old_row then if new_row < old_row then -- cgit From 6f75facb9d755e2e3a10a9a1d6d258a12578dc14 Mon Sep 17 00:00:00 2001 From: Jaehwang Jung Date: Sun, 10 Dec 2023 22:18:48 +0900 Subject: fix(treesitter): improve vim.treesitter.foldexpr * Collect on_bytes and flush at the invocation of the scheduled callback to take account of commands that triggers multiple on_bytes. * More accurately track movement of folds so that foldexpr returns reasonable values even when the scheduled computation is not run yet. * Start computing folds from the line above (+ foldminlines) the changed lines to handle the folds that are removed due to the size limit. * Shrink folds that end at the line at which another fold starts to assign proper level to that line. * Use level '=' for lines that are not computed yet. --- runtime/lua/vim/treesitter/_fold.lua | 184 +++++++++++++++++++++-------------- 1 file changed, 110 insertions(+), 74 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index d5626d0391..735627d29f 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -5,35 +5,20 @@ local Range = require('vim.treesitter._range') local api = vim.api ---@class TS.FoldInfo ----@field levels string[] the foldexpr value for each line +---@field levels string[] the foldexpr result for each line ---@field levels0 integer[] the raw fold levels ----@field private start_counts table ----@field private stop_counts table +---@field edits? {[1]: integer, [2]: integer} line range edited since the last invocation of the callback scheduled in on_bytes. 0-indexed, end-exclusive. local FoldInfo = {} FoldInfo.__index = FoldInfo ---@private function FoldInfo.new() return setmetatable({ - start_counts = {}, - stop_counts = {}, levels0 = {}, levels = {}, }, FoldInfo) end ----@package ----@param srow integer ----@param erow integer 0-indexed, exclusive -function FoldInfo:invalidate_range(srow, erow) - for i = srow + 1, erow do - self.start_counts[i] = nil - self.stop_counts[i] = nil - self.levels0[i] = nil - self.levels[i] = nil - end -end - --- Efficiently remove items from middle of a list a list. --- --- Calling table.remove() in a loop will re-index the tail of the table on @@ -59,8 +44,6 @@ end function FoldInfo:remove_range(srow, erow) list_remove(self.levels, srow + 1, erow) list_remove(self.levels0, srow + 1, erow) - list_remove(self.start_counts, srow + 1, erow) - list_remove(self.stop_counts, srow + 1, erow) end --- Efficiently insert items into the middle of a list. @@ -93,44 +76,35 @@ end ---@param srow integer ---@param erow integer 0-indexed, exclusive function FoldInfo:add_range(srow, erow) - list_insert(self.levels, srow + 1, erow, '-1') + list_insert(self.levels, srow + 1, erow, '=') list_insert(self.levels0, srow + 1, erow, -1) - list_insert(self.start_counts, srow + 1, erow, nil) - list_insert(self.stop_counts, srow + 1, erow, nil) -end - ----@package ----@param lnum integer -function FoldInfo:add_start(lnum) - self.start_counts[lnum] = (self.start_counts[lnum] or 0) + 1 end ---@package ----@param lnum integer -function FoldInfo:add_stop(lnum) - self.stop_counts[lnum] = (self.stop_counts[lnum] or 0) + 1 -end - ----@package ----@param lnum integer ----@return integer -function FoldInfo:get_start(lnum) - return self.start_counts[lnum] or 0 +---@param srow integer +---@param erow_old integer +---@param erow_new integer 0-indexed, exclusive +function FoldInfo:edit_range(srow, erow_old, erow_new) + if self.edits then + self.edits[1] = math.min(srow, self.edits[1]) + if erow_old <= self.edits[2] then + self.edits[2] = self.edits[2] + (erow_new - erow_old) + end + self.edits[2] = math.max(self.edits[2], erow_new) + else + self.edits = { srow, erow_new } + end end ---@package ----@param lnum integer ----@return integer -function FoldInfo:get_stop(lnum) - return self.stop_counts[lnum] or 0 -end - -local function trim_level(level) - local max_fold_level = vim.wo.foldnestmax - if level > max_fold_level then - return max_fold_level +---@return integer? srow +---@return integer? erow 0-indexed, exclusive +function FoldInfo:flush_edit() + if self.edits then + local srow, erow = self.edits[1], self.edits[2] + self.edits = nil + return srow, erow end - return level end --- If a parser doesn't have any ranges explicitly set, treesitter will @@ -158,22 +132,24 @@ local function get_folds_levels(bufnr, info, srow, erow, parse_injections) srow = srow or 0 erow = normalise_erow(bufnr, erow) - info:invalidate_range(srow, erow) - - local prev_start = -1 - local prev_stop = -1 - local parser = ts.get_parser(bufnr) parser:parse(parse_injections and { srow, erow } or nil) + local enter_counts = {} ---@type table + local leave_counts = {} ---@type table + local prev_start = -1 + local prev_stop = -1 + parser:for_each_tree(function(tree, ltree) local query = ts.query.get(ltree:lang(), 'folds') if not query then return end - for id, node, metadata in query:iter_captures(tree:root(), bufnr, srow, erow) do + -- Collect folds starting from srow - 1, because we should first subtract the folds that end at + -- srow - 1 from the level of srow - 1 to get accurate level of srow. + for id, node, metadata in query:iter_captures(tree:root(), bufnr, math.max(srow - 1, 0), erow) do if query.captures[id] == 'fold' then local range = ts.get_range(node, bufnr, metadata[id]) local start, _, stop, stop_col = Range.unpack4(range) @@ -190,8 +166,8 @@ local function get_folds_levels(bufnr, info, srow, erow, parse_injections) if fold_length > vim.wo.foldminlines and not (start == prev_start and stop == prev_stop) then - info:add_start(start + 1) - info:add_stop(stop + 1) + enter_counts[start + 1] = (enter_counts[start + 1] or 0) + 1 + leave_counts[stop + 1] = (leave_counts[stop + 1] or 0) + 1 prev_start = start prev_stop = stop end @@ -199,16 +175,15 @@ local function get_folds_levels(bufnr, info, srow, erow, parse_injections) end end) - local current_level = info.levels0[srow] or 0 + local nestmax = vim.wo.foldnestmax + local level0_prev = info.levels0[srow] or 0 + local leave_prev = leave_counts[srow] or 0 -- We now have the list of fold opening and closing, fill the gaps and mark where fold start for lnum = srow + 1, erow do - local last_trimmed_level = trim_level(current_level) - current_level = current_level + info:get_start(lnum) - info.levels0[lnum] = current_level - - local trimmed_level = trim_level(current_level) - current_level = current_level - info:get_stop(lnum) + local enter_line = enter_counts[lnum] or 0 + local leave_line = leave_counts[lnum] or 0 + local level0 = level0_prev - leave_prev + enter_line -- Determine if it's the start/end of a fold -- NB: vim's fold-expr interface does not have a mechanism to indicate that @@ -216,14 +191,36 @@ local function get_folds_levels(bufnr, info, srow, erow, parse_injections) -- ( \n ( \n )) \n (( \n ) \n ) -- versus -- ( \n ( \n ) \n ( \n ) \n ) - -- If it did have such a mechanism, (trimmed_level - last_trimmed_level) + -- Both are represented by ['>1', '>2', '2', '>2', '2', '1'], and + -- vim interprets as the second case. + -- If it did have such a mechanism, (clamped - clamped_prev) -- would be the correct number of starts to pass on. + local adjusted = level0 ---@type integer local prefix = '' - if trimmed_level - last_trimmed_level > 0 then + if enter_line > 0 then prefix = '>' + if leave_line > 0 then + -- If this line ends a fold f1 and starts a fold f2, then move f1's end to the previous line + -- so that f2 gets the correct level on this line. This may reduce the size of f1 below + -- foldminlines, but we don't handle it for simplicity. + adjusted = level0 - leave_line + leave_line = 0 + end + end + + -- Clamp at foldnestmax. + local clamped = adjusted + if adjusted > nestmax then + prefix = '' + clamped = nestmax end - info.levels[lnum] = prefix .. tostring(trimmed_level) + -- Record the "real" level, so that it can be used as "base" of later get_folds_levels(). + info.levels0[lnum] = adjusted + info.levels[lnum] = prefix .. tostring(clamped) + + leave_prev = leave_line + level0_prev = adjusted end end @@ -297,7 +294,8 @@ local function on_changedtree(bufnr, foldinfo, tree_changes) if ecol > 0 then erow = erow + 1 end - get_folds_levels(bufnr, foldinfo, srow, erow) + -- Start from `srow - foldminlines`, because this edit may have shrunken the fold below limit. + get_folds_levels(bufnr, foldinfo, math.max(srow - vim.wo.foldminlines, 0), erow) end if #tree_changes > 0 then foldupdate(bufnr) @@ -309,20 +307,46 @@ end ---@param foldinfo TS.FoldInfo ---@param start_row integer ---@param old_row integer +---@param old_col integer ---@param new_row integer -local function on_bytes(bufnr, foldinfo, start_row, old_row, new_row) +---@param new_col integer +local function on_bytes(bufnr, foldinfo, start_row, start_col, old_row, old_col, new_row, new_col) -- extend the end to fully include the range local end_row_old = start_row + old_row + 1 local end_row_new = start_row + new_row + 1 if new_row ~= old_row then + -- foldexpr can be evaluated before the scheduled callback is invoked. So it may observe the + -- outdated levels, which may spuriously open the folds that didn't change. So we should shift + -- folds as accurately as possible. For this to be perfectly accurate, we should track the + -- actual TSNodes that account for each fold, and compare the node's range with the edited + -- range. But for simplicity, we just check whether the start row is completely removed (e.g., + -- `dd`) or shifted (e.g., `o`). if new_row < old_row then - foldinfo:remove_range(end_row_new, end_row_old) + if start_col == 0 and new_row == 0 and new_col == 0 then + foldinfo:remove_range(start_row, start_row + (end_row_old - end_row_new)) + else + foldinfo:remove_range(end_row_new, end_row_old) + end else - foldinfo:add_range(start_row, end_row_new) + if start_col == 0 and old_row == 0 and old_col == 0 then + foldinfo:add_range(start_row, start_row + (end_row_new - end_row_old)) + else + foldinfo:add_range(end_row_old, end_row_new) + end end + foldinfo:edit_range(start_row, end_row_old, end_row_new) + + -- This callback must not use on_bytes arguments, because they can be outdated when the callback + -- is invoked. For example, `J` with non-zero count triggers multiple on_bytes before executing + -- the scheduled callback. So we should collect the edits. schedule_if_loaded(bufnr, function() - get_folds_levels(bufnr, foldinfo, start_row, end_row_new) + local srow, erow = foldinfo:flush_edit() + if not srow then + return + end + -- Start from `srow - foldminlines`, because this edit may have shrunken the fold below limit. + get_folds_levels(bufnr, foldinfo, math.max(srow - vim.wo.foldminlines, 0), erow) foldupdate(bufnr) end) end @@ -349,8 +373,8 @@ function M.foldexpr(lnum) on_changedtree(bufnr, foldinfos[bufnr], tree_changes) end, - on_bytes = function(_, _, start_row, _, _, old_row, _, _, new_row, _, _) - on_bytes(bufnr, foldinfos[bufnr], start_row, old_row, new_row) + on_bytes = function(_, _, start_row, start_col, _, old_row, old_col, _, new_row, new_col, _) + on_bytes(bufnr, foldinfos[bufnr], start_row, start_col, old_row, old_col, new_row, new_col) end, on_detach = function() @@ -362,6 +386,18 @@ function M.foldexpr(lnum) return foldinfos[bufnr].levels[lnum] or '0' end +api.nvim_create_autocmd('OptionSet', { + pattern = { 'foldminlines', 'foldnestmax' }, + desc = 'Refresh treesitter folds', + callback = function() + for _, bufnr in ipairs(vim.tbl_keys(foldinfos)) do + foldinfos[bufnr] = FoldInfo.new() + get_folds_levels(bufnr, foldinfos[bufnr]) + foldupdate(bufnr) + end + end, +}) + ---@package ---@return { [1]: string, [2]: string[] }[]|string function M.foldtext() -- cgit From 69ffbb76c237fcbba24de80f1b5346d92642e800 Mon Sep 17 00:00:00 2001 From: Will Hopkins Date: Tue, 12 Dec 2023 12:27:24 -0800 Subject: feat(iter): add `Iter.take` (#26525) --- runtime/lua/vim/iter.lua | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index e9c2b66bf2..8e602c406a 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -592,6 +592,41 @@ function ListIter.rfind(self, f) -- luacheck: no unused args self._head = self._tail end +--- Transforms an iterator to yield only the first n values. +--- +--- Example: +--- +--- ```lua +--- local it = vim.iter({ 1, 2, 3, 4 }):take(2) +--- it:next() +--- -- 1 +--- it:next() +--- -- 2 +--- it:next() +--- -- nil +--- ``` +--- +---@param n integer +---@return Iter +function Iter.take(self, n) + local next = self.next + local i = 0 + self.next = function() + if i < n then + i = i + 1 + return next(self) + end + end + return self +end + +---@private +function ListIter.take(self, n) + local inc = self._head < self._tail and 1 or -1 + self._tail = math.min(self._tail, self._head + n * inc) + return self +end + --- "Pops" a value from a |list-iterator| (gets the last value and decrements the tail). --- --- Example: -- cgit From e527842211188a5c069ef4f4759aa291353b707f Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Wed, 13 Dec 2023 08:14:30 -0600 Subject: fix(termcap): only call callback for requested capabilities (#26546) If multiple XTGETTCAP requests are active at once (for example, for requesting the Ms capability and truecolor capabilities), then the TermResponse autocommand may fire for capabilities that were not requested. Instead, make sure that the provided callback is only called for capabilities that were actually requested. --- runtime/lua/vim/termcap.lua | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/termcap.lua b/runtime/lua/vim/termcap.lua index b88d9ac9ba..e48657b3c2 100644 --- a/runtime/lua/vim/termcap.lua +++ b/runtime/lua/vim/termcap.lua @@ -39,6 +39,12 @@ function M.query(caps, cb) local k, rest = resp:match('^\027P1%+r(%x+)(.*)$') if k and rest then local cap = vim.text.hexdecode(k) + if not pending[cap] then + -- Received a response for a capability we didn't request. This can happen if there are + -- multiple concurrent XTGETTCAP requests + return + end + local seq ---@type string? if rest:match('^=%x+$') then seq = vim.text -- cgit From 8122470f8310ae34bcd5e436e8474f9255eb16f2 Mon Sep 17 00:00:00 2001 From: Raphael Date: Wed, 13 Dec 2023 22:19:53 +0800 Subject: refactor(diagnostic): set sign by using extmark (#26193) after sign implementation refactor by using extmark, we can use `nvim_buf_set_extmark` to set diagnostic sign instead use `sign_define` --- runtime/lua/vim/diagnostic.lua | 61 +++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 34 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 99448982b4..729156584f 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -221,30 +221,6 @@ local underline_highlight_map = make_highlight_map('Underline') local floating_highlight_map = make_highlight_map('Floating') local sign_highlight_map = make_highlight_map('Sign') ----@private -local define_default_signs = (function() - local signs_defined = false - return function() - if signs_defined then - return - end -