diff options
60 files changed, 999 insertions, 379 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index 530095c40f..76f15852c4 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -9,7 +9,6 @@ freebsd_task: image_family: freebsd-13-1 timeout_in: 30m install_script: - - pkg update -f - pkg install -y cmake gmake ninja unzip wget gettext python git build_deps_script: - gmake deps diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt index c3d5c8050b..3260a9cb97 100644 --- a/cmake.deps/deps.txt +++ b/cmake.deps/deps.txt @@ -4,8 +4,8 @@ LIBUV_SHA256 7aa66be3413ae10605e1f5c9ae934504ffe317ef68ea16fdaa83e23905c681bd MSGPACK_URL https://github.com/msgpack/msgpack-c/releases/download/c-6.0.0/msgpack-c-6.0.0.tar.gz MSGPACK_SHA256 3654f5e2c652dc52e0a993e270bb57d5702b262703f03771c152bba51602aeba -LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/8635cbabf3094c4d8bd00578c7d812bea87bb2d3.tar.gz -LUAJIT_SHA256 835035b244c3dc3d3d19bdd5ac623af90b84207e6330fb78f9fa51d6e200d760 +LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/72efc42ef2258086a9cb797c676e2916b0a9e7e1.tar.gz +LUAJIT_SHA256 940b2afd480d0a6365fcae11415117b016e485f2bf8a68c7a12534b9ab42d35a LUA_URL https://www.lua.org/ftp/lua-5.1.5.tar.gz LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333 diff --git a/runtime/autoload/rust.vim b/runtime/autoload/rust.vim index 34a3b41773..4230332fa7 100644 --- a/runtime/autoload/rust.vim +++ b/runtime/autoload/rust.vim @@ -1,4 +1,4 @@ -" Author: Kevin Ballard +" Author: Lily Ballard " Description: Helper functions for Rust commands/mappings " Last Modified: May 27, 2014 " For bugs, patches and license go to https://github.com/rust-lang/rust.vim diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index e678a8d804..d05f488ed5 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -2734,6 +2734,9 @@ getqflist([{what}]) *getqflist()* text description of the error type type of the error, 'E', '1', etc. valid |TRUE|: recognized error message + user_data + custom data associated with the item, can be + any type. When there is no error list or it's empty, an empty list is returned. Quickfix list entries with a non-existing buffer @@ -6353,6 +6356,9 @@ setqflist({list} [, {action} [, {what}]]) *setqflist()* text description of the error type single-character error type, 'E', 'W', etc. valid recognized error message + user_data + custom data associated with the item, can be + any type. The "col", "vcol", "nr", "type" and "text" entries are optional. Either "lnum" or "pattern" entry can be used to @@ -8171,9 +8177,10 @@ undofile({name}) *undofile()* buffer without a file name will not write an undo file. Useful in combination with |:wundo| and |:rundo|. -undotree() *undotree()* - Return the current state of the undo tree in a dictionary with - the following items: +undotree([{buf}]) *undotree()* + Return the current state of the undo tree for the current + buffer, or for a specific buffer if {buf} is given. The + result is a dictionary with the following items: "seq_last" The highest undo sequence number used. "seq_cur" The sequence number of the current position in the undo tree. This differs from "seq_last" diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 179cdfef25..e9c493479c 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -61,6 +61,10 @@ The following changes may require adaptations in user config or plugins. spaces (but paths themselves may contain spaces now). • |'directory'| will no longer remove a `>` at the start of the option. +• |LanguageTree:parse()| will no longer parse injections by default and + now requires an explicit range argument to be passed. If injections are + required, provide an explicit range via `parser:parse({ start_row, end_row })`. + ============================================================================== NEW FEATURES *news-features* @@ -69,6 +73,9 @@ The following new APIs and features were added. • Performance: • 'diffopt' "linematch" scoring algorithm now favours larger and less groups https://github.com/neovim/neovim/pull/23611 + • Treesitter highlighting now parses injections incrementally during + screen redraws only for the line range being rendered. This significantly + improves performance in large files with many injections. • |vim.iter()| provides a generic iterator interface for tables and Lua iterators |for-in|. @@ -77,15 +84,9 @@ The following new APIs and features were added. • Added |vim.keycode()| for translating keycodes in a string. -• Added |vim.treesitter.query.omnifunc()| for treesitter query files (set by - default). - • |'smoothscroll'| option to scroll by screen line rather than by text line when |'wrap'| is set. -• |Query:iter_matches()| now has the ability to set the maximum start depth - for matches. - • Added inline virtual text support to |nvim_buf_set_extmark()|. • The terminal buffer now supports reflow (wrapped lines adapt when the buffer @@ -120,8 +121,16 @@ The following new APIs and features were added. `client.supports_method(<method>)`. It considers both the dynamic capabilities and static `server_capabilities`. -• Bundled treesitter parser and queries (highlight, folds) for Markdown, - Python, and Bash. +• Treesitter + • Bundled parsers and queries (highlight, folds) for Markdown, Python, and + Bash. + • Added |vim.treesitter.query.omnifunc()| for treesitter query files (set by + default). + • |Query:iter_matches()| now has the ability to set the maximum start depth + for matches. + • `@injection.language` now has smarter resolution and will now fallback to language aliases and/or attempt lower case variants of the text. + language via aliases (e.g., filetype) registered via + `vim.treesitter.language.register`. • |vim.ui.open()| opens URIs using the system default handler (macOS `open`, Windows `explorer`, Linux `xdg-open`, etc.) diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index 7c7be5f214..f3e697807f 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -311,19 +311,7 @@ The following directives are built in: {capture_id} Example: >query - (#inject-language! @_lang) -< - `inject-language!` *treesitter-directive-inject-language!* - Set the injection language from the node text, interpreted first as a - language name, then (if a parser is not found) a filetype. Custom - aliases can be added via |vim.treesitter.language.register()|. This - will set a new `metadata[capture_id]['injection.language']`. - - Parameters: ~ - {capture_id} - - Example: >query - (#inject-language! @_lang) + (#trim! @fold) < Further directives can be added via |vim.treesitter.query.add_directive()|. Use |vim.treesitter.query.list_directives()| to list all available directives. @@ -1032,13 +1020,6 @@ set({lang}, {query_name}, {text}) *vim.treesitter.query.set()* ============================================================================== -Lua module: vim.treesitter.highlighter *lua-treesitter-highlighter* - -TSHighlighter:destroy() *TSHighlighter:destroy()* - Removes all internal references to the highlighter. - - -============================================================================== Lua module: vim.treesitter.languagetree *lua-treesitter-languagetree* @@ -1065,7 +1046,7 @@ Whenever you need to access the current syntax tree, parse the buffer: >lua - local tree = parser:parse() + local tree = parser:parse({ start_row, end_row }) < @@ -1124,7 +1105,7 @@ LanguageTree:included_regions() *LanguageTree:included_regions()* Gets the set of included regions Return: ~ - integer[][] + Range6[][] LanguageTree:invalidate({reload}) *LanguageTree:invalidate()* Invalidates this parser and all its children @@ -1167,10 +1148,22 @@ LanguageTree:named_node_for_range({range}, {opts}) Return: ~ |TSNode| | nil Found node -LanguageTree:parse() *LanguageTree:parse()* - Parses all defined regions using a treesitter parser for the language this - tree represents. This will run the injection query for this language to - determine if any child languages should be created. +LanguageTree:parse({range}) *LanguageTree:parse()* + Recursively parse all regions in the language tree using + |treesitter-parsers| for the corresponding languages and run injection + queries on the parsed trees to determine whether child trees should be + created and parsed. + + Any region with empty range (`{}`, typically only the root tree) is always + parsed; otherwise (typically injections) only if it intersects {range} (or + if {range} is `true`). + + Parameters: ~ + • {range} boolean|Range|nil: Parse this range in the parser's source. + Set to `true` to run a complete parse of the source (Note: + Can be slow!) Set to `false|nil` to only parse regions with + empty ranges (typically only the root tree without + injections). Return: ~ TSTree[] diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index e1bb6c4af1..e6fcfa2fc6 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -1092,7 +1092,7 @@ Various: *various-functions* libcallnr() idem, returning a number undofile() get the name of the undo file - undotree() return the state of the undo tree + undotree() return the state of the undo tree for a buffer shiftwidth() effective value of 'shiftwidth' diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 07b4572a27..d288f31828 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -457,7 +457,7 @@ coerced to strings. See |id()| for more details, currently it uses |c_CTRL-R| pasting a non-special register into |cmdline| omits the last <CR>. -|CursorMoved| always triggers when moving between windows. +|CursorMoved| triggers when moving between windows. Lua interface (|lua.txt|): diff --git a/runtime/ftplugin/nix.vim b/runtime/ftplugin/nix.vim new file mode 100644 index 0000000000..d417cc7805 --- /dev/null +++ b/runtime/ftplugin/nix.vim @@ -0,0 +1,17 @@ +" Vim filetype plugin +" Language: nix +" Maintainer: Keith Smiley <keithbsmiley@gmail.com> +" Last Change: 2023 Jul 22 + +" Only do this when not done yet for this buffer +if exists("b:did_ftplugin") + finish +endif + +" Don't load another plugin for this buffer +let b:did_ftplugin = 1 + +let b:undo_ftplugin = "setl commentstring< comments<" + +setlocal comments=:# +setlocal commentstring=#\ %s diff --git a/runtime/ftplugin/pymanifest.vim b/runtime/ftplugin/pymanifest.vim new file mode 100644 index 0000000000..a77e956ae5 --- /dev/null +++ b/runtime/ftplugin/pymanifest.vim @@ -0,0 +1,13 @@ +" Vim filetype plugin +" Language: PyPA manifest +" Maintainer: ObserverOfTime <chronobserver@disroot.org> +" Last Change: 2023 Aug 08 + +if exists('b:did_ftplugin') + finish +endif +let b:did_ftplugin = 1 + +setl comments=:# commentstring=#\ %s + +let b:undo_ftplugin = 'setl com< cms<' diff --git a/runtime/ftplugin/rust.vim b/runtime/ftplugin/rust.vim index 7efca5985b..ececcced22 100644 --- a/runtime/ftplugin/rust.vim +++ b/runtime/ftplugin/rust.vim @@ -1,7 +1,7 @@ " Language: Rust " Description: Vim ftplugin for Rust " Maintainer: Chris Morgan <me@chrismorgan.info> -" Maintainer: Kevin Ballard <kevin@sb.org> +" Maintainer: Lily Ballard <lily@ballards.net> " Last Change: June 08, 2016 " For bugs, patches and license go to https://github.com/rust-lang/rust.vim diff --git a/runtime/ftplugin/toml.vim b/runtime/ftplugin/toml.vim index 1ef09a16e3..6bd79b1c0a 100644 --- a/runtime/ftplugin/toml.vim +++ b/runtime/ftplugin/toml.vim @@ -2,7 +2,7 @@ " Language: TOML " Homepage: https://github.com/cespare/vim-toml " Maintainer: Aman Verma -" Author: Kevin Ballard <kevin@sb.org> +" Author: Lily Ballard <lily@ballards.net> " Last Change: Sep 21, 2021 if exists('b:did_ftplugin') diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index 14aae99971..d5d9229ffb 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -3309,6 +3309,9 @@ function vim.fn.getpos(expr) end --- text description of the error --- type type of the error, 'E', '1', etc. --- valid |TRUE|: recognized error message +--- user_data +--- custom data associated with the item, can be +--- any type. --- --- When there is no error list or it's empty, an empty list is --- returned. Quickfix list entries with a non-existing buffer @@ -7595,6 +7598,9 @@ function vim.fn.setpos(expr, list) end --- text description of the error --- type single-character error type, 'E', 'W', etc. --- valid recognized error message +--- user_data +--- custom data associated with the item, can be +--- any type. --- --- The "col", "vcol", "nr", "type" and "text" entries are --- optional. Either "lnum" or "pattern" entry can be used to @@ -9733,8 +9739,9 @@ function vim.fn.type(expr) end --- @return string function vim.fn.undofile(name) end ---- Return the current state of the undo tree in a dictionary with ---- the following items: +--- Return the current state of the undo tree for the current +--- buffer, or for a specific buffer if {buf} is given. The +--- result is a dictionary with the following items: --- "seq_last" The highest undo sequence number used. --- "seq_cur" The sequence number of the current position in --- the undo tree. This differs from "seq_last" @@ -9775,8 +9782,9 @@ function vim.fn.undofile(name) end --- blocks. Each item may again have an "alt" --- item. --- +--- @param buf? any --- @return any -function vim.fn.undotree() end +function vim.fn.undotree(buf) end --- Remove second and succeeding copies of repeated adjacent --- {list} items in-place. Returns {list}. If you want a list diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 0c4290d067..2a16bafbfc 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -2262,7 +2262,7 @@ end local function adjust_start_col(lnum, line, items, encoding) local min_start_char = nil for _, item in pairs(items) do - if item.filterText == nil and item.textEdit and item.textEdit.range.start.line == lnum - 1 then + if item.textEdit and item.textEdit.range.start.line == lnum - 1 then if min_start_char and min_start_char ~= item.textEdit.range.start.character then return nil end diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index a02d0a584d..912a6e8a9f 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -147,11 +147,14 @@ local function normalise_erow(bufnr, erow) return math.min(erow or max_erow, max_erow) end +-- TODO(lewis6991): Setup a decor provider so injections folds can be parsed +-- as the window is redrawn ---@param bufnr integer ---@param info TS.FoldInfo ---@param srow integer? ---@param erow integer? -local function get_folds_levels(bufnr, info, srow, erow) +---@param parse_injections? boolean +local function get_folds_levels(bufnr, info, srow, erow, parse_injections) srow = srow or 0 erow = normalise_erow(bufnr, erow) @@ -162,7 +165,7 @@ local function get_folds_levels(bufnr, info, srow, erow) local parser = ts.get_parser(bufnr) - parser:parse() + parser:parse(parse_injections and { srow, erow } or nil) parser:for_each_tree(function(tree, ltree) local query = ts.query.get(ltree:lang(), 'folds') diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua index 36b1a9bbf8..d8babc9402 100644 --- a/runtime/lua/vim/treesitter/_meta.lua +++ b/runtime/lua/vim/treesitter/_meta.lua @@ -1,6 +1,6 @@ ---@meta ----@class TSNode +---@class TSNode: userdata ---@field id fun(self: TSNode): integer ---@field tree fun(self: TSNode): TSTree ---@field range fun(self: TSNode, include_bytes: false?): integer, integer, integer, integer @@ -51,7 +51,7 @@ function TSNode:_rawquery(query, captures, start, end_, opts) end ---@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: boolean?): TSTree, integer[] ---@field reset fun(self: TSParser) ---@field included_ranges fun(self: TSParser, include_bytes: boolean?): integer[] ----@field set_included_ranges fun(self: TSParser, ranges: Range6[]) +---@field set_included_ranges fun(self: TSParser, ranges: (Range6|TSNode)[]) ---@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) @@ -61,7 +61,8 @@ function TSNode:_rawquery(query, captures, start, end_, opts) end ---@field root fun(self: TSTree): TSNode ---@field edit fun(self: TSTree, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _:integer) ---@field copy fun(self: TSTree): TSTree ----@field included_ranges fun(self: TSTree, include_bytes: boolean?): integer[] +---@field included_ranges fun(self: TSTree, include_bytes: true): Range6[] +---@field included_ranges fun(self: TSTree, include_bytes: false): Range4[] ---@return integer vim._ts_get_language_version = function() end diff --git a/runtime/lua/vim/treesitter/_range.lua b/runtime/lua/vim/treesitter/_range.lua index 35081c6400..8d727c3c52 100644 --- a/runtime/lua/vim/treesitter/_range.lua +++ b/runtime/lua/vim/treesitter/_range.lua @@ -2,6 +2,10 @@ local api = vim.api local M = {} +---@class Range2 +---@field [1] integer start row +---@field [2] integer end row + ---@class Range4 ---@field [1] integer start row ---@field [2] integer start column @@ -16,7 +20,7 @@ local M = {} ---@field [5] integer end column ---@field [6] integer end bytes ----@alias Range Range4|Range6 +---@alias Range Range2|Range4|Range6 ---@private ---@param a_row integer @@ -111,6 +115,9 @@ end ---@param r Range ---@return integer, integer, integer, integer function M.unpack4(r) + if #r == 2 then + return r[1], 0, r[2], 0 + end local off_1 = #r == 6 and 1 or 0 return r[1], r[2], r[3 + off_1], r[4 + off_1] end diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index e94e8f08dc..f7625eb94b 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -99,7 +99,7 @@ function TSTreeView:new(bufnr, lang) -- For each child tree (injected language), find the root of the tree and locate the node within -- the primary tree that contains that root. Add a mapping from the node in the primary tree to -- the root in the child tree to the {injections} table. - local root = parser:parse()[1]:root() + local root = parser:parse(true)[1]:root() local injections = {} ---@type table<integer,table> parser:for_each_child(function(child, lang_) child:for_each_tree(function(tree) diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index f8ec5b175d..56b075b723 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -1,5 +1,6 @@ local api = vim.api local query = vim.treesitter.query +local Range = require('vim.treesitter._range') ---@alias TSHlIter fun(): integer, TSNode, TSMetadata @@ -14,6 +15,7 @@ local query = vim.treesitter.query ---@field _highlight_states table<TSTree,TSHighlightState> ---@field _queries table<string,TSHighlighterQuery> ---@field tree LanguageTree +---@field redraw_count integer local TSHighlighter = rawget(vim.treesitter, 'TSHighlighter') or {} TSHighlighter.__index = TSHighlighter @@ -139,6 +141,7 @@ function TSHighlighter.new(tree, opts) return self end +--- @nodoc --- Removes all internal references to the highlighter function TSHighlighter:destroy() if TSHighlighter.active[self.bufnr] then @@ -186,7 +189,7 @@ function TSHighlighter:on_detach() end ---@package ----@param changes Range6[][] +---@param changes Range6[] function TSHighlighter:on_changedtree(changes) for _, ch in ipairs(changes) do api.nvim__buf_redraw_range(self.bufnr, ch[1], ch[4] + 1) @@ -245,7 +248,7 @@ local function on_line_impl(self, buf, line, is_spell_nav) end local range = vim.treesitter.get_range(node, buf, metadata[capture]) - local start_row, start_col, _, end_row, end_col, _ = unpack(range) + local start_row, start_col, end_row, end_col = Range.unpack4(range) local hl = highlighter_query.hl_cache[capture] local capture_name = highlighter_query:query().captures[capture] @@ -310,31 +313,22 @@ function TSHighlighter._on_spell_nav(_, _, buf, srow, _, erow, _) end ---@private ----@param buf integer -function TSHighlighter._on_buf(_, buf) - local self = TSHighlighter.active[buf] - if self then - self.tree:parse() - end -end - ----@private ---@param _win integer ---@param buf integer ----@param _topline integer -function TSHighlighter._on_win(_, _win, buf, _topline) +---@param topline integer +---@param botline integer +function TSHighlighter._on_win(_, _win, buf, topline, botline) local self = TSHighlighter.active[buf] if not self then return false end - + self.tree:parse({ topline, botline }) self:reset_highlight_state() self.redraw_count = self.redraw_count + 1 return true end api.nvim_set_decoration_provider(ns, { - on_buf = TSHighlighter._on_buf, on_win = TSHighlighter._on_win, on_line = TSHighlighter._on_line, _on_spell_nav = TSHighlighter._on_spell_nav, diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 4b2628609a..b4c9027794 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -18,7 +18,7 @@ --- Whenever you need to access the current syntax tree, parse the buffer: --- --- <pre>lua ---- local tree = parser:parse() +--- local tree = parser:parse({ start_row, end_row }) --- </pre> --- --- This returns a table of immutable |treesitter-tree| objects representing the current state of @@ -74,6 +74,7 @@ local TSCallbackNames = { ---@field package _callbacks_rec table<TSCallbackName,function[]> Callback handlers (recursive) ---@field private _children table<string,LanguageTree> Injected languages ---@field private _injection_query Query Queries defining injected languages +---@field private _injections_processed boolean ---@field private _opts table Options ---@field private _parser TSParser Parser for language ---@field private _has_regions boolean @@ -115,7 +116,9 @@ function LanguageTree.new(source, lang, opts) end local injections = opts.injections or {} - local self = setmetatable({ + + --- @type LanguageTree + local self = { _source = source, _lang = lang, _children = {}, @@ -123,14 +126,19 @@ function LanguageTree.new(source, lang, opts) _opts = opts, _injection_query = injections[lang] and query.parse(lang, injections[lang]) or query.get(lang, 'injections'), + _has_regions = false, + _injections_processed = false, _valid = false, _parser = vim._create_ts_parser(lang), _callbacks = {}, _callbacks_rec = {}, - }, LanguageTree) + } + + setmetatable(self, LanguageTree) if vim.g.__ts_debug and type(vim.g.__ts_debug) == 'number' then self:_set_logger() + self:_log('START') end for _, name in pairs(TSCallbackNames) do @@ -141,12 +149,14 @@ function LanguageTree.new(source, lang, opts) return self end +--- @private function LanguageTree:_set_logger() local source = self:source() source = type(source) == 'string' and 'text' or tostring(source) local lang = self:lang() + vim.fn.mkdir(vim.fn.stdpath('log'), 'p') local logfilename = vim.fs.joinpath(vim.fn.stdpath('log'), 'treesitter.log') local logfile, openerr = io.open(logfilename, 'a+') @@ -171,7 +181,7 @@ end ---Measure execution time of a function ---@generic R1, R2, R3 ---@param f fun(): R1, R2, R2 ----@return integer, R1, R2, R3 +---@return number, R1, R2, R3 local function tcall(f, ...) local start = vim.uv.hrtime() ---@diagnostic disable-next-line @@ -199,7 +209,8 @@ function LanguageTree:_log(...) local info = debug.getinfo(2, 'nl') local nregions = #self:included_regions() - local prefix = string.format('%s:%d: (#regions=%d) ', info.name, info.currentline, nregions) + local prefix = + string.format('%s:%d: (#regions=%d) ', info.name or '???', info.currentline or 0, nregions) local msg = { prefix } for _, x in ipairs(args) do @@ -219,7 +230,7 @@ function LanguageTree:invalidate(reload) -- buffer was reloaded, reparse all trees if reload then - for _, t in ipairs(self._trees) do + for _, t in pairs(self._trees) do self:_do_callback('changedtree', t:included_ranges(true), t) end self._trees = {} @@ -250,14 +261,18 @@ function LanguageTree:is_valid(exclude_children) local valid = self._valid if type(valid) == 'table' then - for _, v in ipairs(valid) do - if not v then + for i = 1, #self:included_regions() do + if not valid[i] then return false end end end if not exclude_children then + if not self._injections_processed then + return false + end + for _, child in pairs(self._children) do if not child:is_valid(exclude_children) then return false @@ -265,9 +280,12 @@ function LanguageTree:is_valid(exclude_children) end end - assert(type(valid) == 'boolean') + if type(valid) == 'boolean' then + return valid + end - return valid + self._valid = true + return true end --- Returns a map of language to child tree. @@ -280,47 +298,72 @@ function LanguageTree:source() return self._source end ---- Parses all defined regions using a treesitter parser ---- for the language this tree represents. ---- This will run the injection query for this language to ---- determine if any child languages should be created. ---- ----@return TSTree[] -function LanguageTree:parse() - if self:is_valid() then - self:_log('valid') - return self._trees +--- @param region Range6[] +--- @param range? boolean|Range +--- @return boolean +local function intercepts_region(region, range) + if #region == 0 then + return true end - local changes = {} + if range == nil then + return false + end - -- Collect some stats - local regions_parsed = 0 + if type(range) == 'boolean' then + return range + end + + for _, r in ipairs(region) do + if Range.intercepts(r, range) then + return true + end + end + + return false +end + +--- @private +--- @param range boolean|Range? +--- @return integer[] changes +--- @return integer no_regions_parsed +--- @return number total_parse_time +function LanguageTree:_parse_regions(range) + local changes = {} + local no_regions_parsed = 0 local total_parse_time = 0 - --- At least 1 region is invalid - if not self:is_valid(true) then - -- If there are no ranges, set to an empty list - -- so the included ranges in the parser are cleared. - for i, ranges in ipairs(self:included_regions()) do - if not self._valid or not self._valid[i] then - self._parser:set_included_ranges(ranges) - local parse_time, tree, tree_changes = - tcall(self._parser.parse, self._parser, self._trees[i], self._source, true) - - -- Pass ranges if this is an initial parse - local cb_changes = self._trees[i] and tree_changes or tree:included_ranges(true) - - self:_do_callback('changedtree', cb_changes, tree) - self._trees[i] = tree - vim.list_extend(changes, tree_changes) - - total_parse_time = total_parse_time + parse_time - regions_parsed = regions_parsed + 1 - end + if type(self._valid) ~= 'table' then + self._valid = {} + end + + -- If there are no ranges, set to an empty list + -- so the included ranges in the parser are cleared. + for i, ranges in pairs(self:included_regions()) do + if not self._valid[i] and intercepts_region(ranges, range) then + self._parser:set_included_ranges(ranges) + local parse_time, tree, tree_changes = + tcall(self._parser.parse, self._parser, self._trees[i], self._source, true) + + -- Pass ranges if this is an initial parse + local cb_changes = self._trees[i] and tree_changes or tree:included_ranges(true) + + self:_do_callback('changedtree', cb_changes, tree) + self._trees[i] = tree + vim.list_extend(changes, tree_changes) + + total_parse_time = total_parse_time + parse_time + no_regions_parsed = no_regions_parsed + 1 + self._valid[i] = true end end + return changes, no_regions_parsed, total_parse_time +end + +--- @private +--- @return number +function LanguageTree:_add_injections() local seen_langs = {} ---@type table<string,boolean> local query_time, injections_by_lang = tcall(self._get_injections, self) @@ -348,19 +391,60 @@ function LanguageTree:parse() end end + return query_time +end + +--- Recursively parse all regions in the language tree using |treesitter-parsers| +--- for the corresponding languages and run injection queries on the parsed trees +--- to determine whether child trees should be created and parsed. +--- +--- Any region with empty range (`{}`, typically only the root tree) is always parsed; +--- otherwise (typically injections) only if it intersects {range} (or if {range} is `true`). +--- +--- @param range boolean|Range|nil: Parse this range in the parser's source. +--- Set to `true` to run a complete parse of the source (Note: Can be slow!) +--- Set to `false|nil` to only parse regions with empty ranges (typically +--- only the root tree without injections). +--- @return TSTree[] +function LanguageTree:parse(range) + if self:is_valid() then + self:_log('valid') + return self._trees + end + + local changes --- @type Range6? + + -- Collect some stats + local no_regions_parsed = 0 + local query_time = 0 + local total_parse_time = 0 + + --- At least 1 region is invalid + if not self:is_valid(true) then + changes, no_regions_parsed, total_parse_time = self:_parse_regions(range) + -- Need to run injections when we parsed something + if no_regions_parsed > 0 then + self._injections_processed = false + end + end + + if not self._injections_processed and range ~= false and range ~= nil then + query_time = self:_add_injections() + self._injections_processed = true + end + self:_log({ - changes = changes, - regions_parsed = regions_parsed, + changes = changes and #changes > 0 and changes or nil, + regions_parsed = no_regions_parsed, parse_time = total_parse_time, query_time = query_time, + range = range, }) self:for_each_child(function(child) - child:parse() + child:parse(range) end) - self._valid = true - return self._trees end @@ -384,7 +468,7 @@ end --- ---@param fn fun(tree: TSTree, ltree: LanguageTree) function LanguageTree:for_each_tree(fn) - for _, tree in ipairs(self._trees) do + for _, tree in pairs(self._trees) do fn(tree, self) end @@ -466,18 +550,17 @@ function LanguageTree:_iter_regions(fn) return end - if type(self._valid) ~= 'table' then + local was_valid = type(self._valid) ~= 'table' + + if was_valid then + self:_log('was valid', self._valid) self._valid = {} end local all_valid = true for i, region in ipairs(self:included_regions()) do - if self._valid[i] == nil then - self._valid[i] = true - end - - if self._valid[i] then + if was_valid or self._valid[i] then self._valid[i] = fn(i, region) if not self._valid[i] then self:_log(function() @@ -521,6 +604,8 @@ function LanguageTree:set_included_regions(new_regions) for i, range in ipairs(region) do if type(range) == 'table' and #range == 4 then region[i] = Range.add_bytes(self._source, range) + elseif type(range) == 'userdata' then + region[i] = { range:range(true) } end end end @@ -542,7 +627,7 @@ function LanguageTree:set_included_regions(new_regions) end ---Gets the set of included regions ----@return integer[][] +---@return Range6[][] function LanguageTree:included_regions() if self._regions then return self._regions @@ -581,7 +666,7 @@ local function get_node_ranges(node, source, metadata, include_children) -- We are excluding children so we need to mask out their ranges for i = 0, child_count - 1 do - local child = node:named_child(i) + local child = assert(node:named_child(i)) local c_srow, c_scol, c_sbyte, c_erow, c_ecol, c_ebyte = child:range(true) if c_srow > srow or c_scol > scol then ranges[#ranges + 1] = { srow, scol, sbyte, c_srow, c_scol, c_sbyte } @@ -635,6 +720,29 @@ local function add_injection(t, tree_index, pattern, lang, combined, ranges) table.insert(t[tree_index][lang][pattern].regions, ranges) end +-- TODO(clason): replace by refactored `ts.has_parser` API (without registering) +---@param lang string parser name +---@return boolean # true if parser for {lang} exists on rtp +local has_parser = function(lang) + return vim._ts_has_language(lang) + or #vim.api.nvim_get_runtime_file('parser/' .. lang .. '.*', false) > 0 +end + +--- Return parser name for language (if exists) or filetype (if registered and exists) +--- +---@param alias string language or filetype name +---@return string? # resolved parser name +local function resolve_lang(alias) + if has_parser(alias) then + return alias + end + + local lang = vim.treesitter.language.get_lang(alias) + if lang and has_parser(lang) then + return lang + end +end + ---@private --- Extract injections according to: --- https://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection @@ -649,10 +757,10 @@ function LanguageTree:_get_injection(match, metadata) for id, node in pairs(match) do local name = self._injection_query.captures[id] - -- Lang should override any other language tag if name == 'injection.language' then - lang = vim.treesitter.get_node_text(node, self._source, { metadata = metadata[id] }) + local text = vim.treesitter.get_node_text(node, self._source, { metadata = metadata[id] }) + lang = resolve_lang(text) or resolve_lang(text:lower()) elseif name == 'injection.content' then ranges = get_node_ranges(node, self._source, metadata[id], include_children) end @@ -726,8 +834,8 @@ end --- --- TODO: Allow for an offset predicate to tailor the injection range --- instead of using the entire nodes range. ----@private ----@return table<string, Range6[][]> +--- @private +--- @return table<string, Range6[][]> function LanguageTree:_get_injections() if not self._injection_query then return {} @@ -736,7 +844,7 @@ function LanguageTree:_get_injections() ---@type table<integer,TSInjection> local injections = {} - for tree_index, tree in ipairs(self._trees) do + for index, tree in pairs(self._trees) do local root_node = tree:root() local start_line, _, end_line, _ = root_node:range() @@ -748,7 +856,7 @@ function LanguageTree:_get_injections() -- TODO(lewis6991): remove after 0.9 (#20434) lang, combined, ranges = self:_get_injection_deprecated(match, metadata) end - add_injection(injections, tree_index, pattern, lang, combined, ranges) + add_injection(injections, index, pattern, lang, combined, ranges) end end @@ -771,7 +879,7 @@ function LanguageTree:_get_injections() end, entry.regions) table.insert(result[lang], regions) else - for _, ranges in ipairs(entry.regions) do + for _, ranges in pairs(entry.regions) do table.insert(result[lang], ranges) end end @@ -805,7 +913,7 @@ function LanguageTree:_edit( end_row_new, end_col_new ) - for _, tree in ipairs(self._trees) do + for _, tree in pairs(self._trees) do tree:edit( start_byte, end_byte_old, diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 3b7e74c0cf..3093657313 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -435,6 +435,7 @@ predicate_handlers['vim-match?'] = predicate_handlers['match?'] ---@class TSMetadata ---@field range? Range +---@field conceal? string ---@field [integer] TSMetadata ---@field [string] integer|string @@ -541,33 +542,6 @@ local directive_handlers = { metadata.range = { start_row, start_col, end_row, end_col } end end, - -- Set injection language from node text, interpreted first as language and then as filetype - -- Example: (#inject-language! @_lang) - ['inject-language!'] = function(match, _, bufnr, pred, metadata) - local id = pred[2] - local node = match[id] - if not node then - return - end - - -- TODO(clason): replace by refactored `ts.has_parser` API - local has_parser = function(lang) - return vim._ts_has_language(lang) - or #vim.api.nvim_get_runtime_file('parser/' .. lang .. '.*', false) > 0 - end - - local alias = vim.treesitter.get_node_text(node, bufnr, { metadata = metadata[id] }) - if not alias then - return - elseif has_parser(alias) then - metadata['injection.language'] = alias - else - local lang = vim.treesitter.language.get_lang(alias) - if lang and has_parser(lang) then - metadata['injection.language'] = lang - end - end - end, } --- Adds a new predicate to be used in queries diff --git a/runtime/queries/markdown/injections.scm b/runtime/queries/markdown/injections.scm index 0bead6f4ac..fda7036830 100644 --- a/runtime/queries/markdown/injections.scm +++ b/runtime/queries/markdown/injections.scm @@ -1,8 +1,7 @@ (fenced_code_block (info_string - (language) @_lang) - (code_fence_content) @injection.content - (#inject-language! @_lang)) + (language) @injection.language) + (code_fence_content) @injection.content) ((html_block) @injection.content (#set! injection.language "html") diff --git a/runtime/syntax/dosini.vim b/runtime/syntax/dosini.vim index cf42819bcd..751a12f4b2 100644 --- a/runtime/syntax/dosini.vim +++ b/runtime/syntax/dosini.vim @@ -1,12 +1,12 @@ " Vim syntax file " Language: Configuration File (ini file) for MSDOS/MS Windows -" Version: 2.2 +" Version: 2.3 " Original Author: Sean M. McKee <mckee@misslink.net> " Previous Maintainer: Nima Talebi <nima@it.net.au> " Current Maintainer: Hong Xu <hong@topbug.net> " Homepage: http://www.vim.org/scripts/script.php?script_id=3747 " Repository: https://github.com/xuhdev/syntax-dosini.vim -" Last Change: 2018 Sep 11 +" Last Change: 2023 Jun 27 " quit when a syntax file was already loaded @@ -24,6 +24,8 @@ syn match dosiniNumber "=\zs\s*\d*\.\d\+\s*$" syn match dosiniNumber "=\zs\s*\d\+e[+-]\=\d\+\s*$" syn region dosiniHeader start="^\s*\[" end="\]" syn match dosiniComment "^[#;].*$" +syn region dosiniSection start="\s*\[.*\]" end="\ze\s*\[.*\]" fold + \ contains=dosiniLabel,dosiniValue,dosiniNumber,dosiniHeader,dosiniComment " Define the default highlighting. " Only when an item doesn't have highlighting yet diff --git a/runtime/syntax/iss.vim b/runtime/syntax/iss.vim index 34bb698368..212c0f6dbe 100644 --- a/runtime/syntax/iss.vim +++ b/runtime/syntax/iss.vim @@ -2,10 +2,9 @@ " Language: Inno Setup File (iss file) and My InnoSetup extension " Maintainer: Jason Mills (jmills@cs.mun.ca) " Previous Maintainer: Dominique Stéphan (dominique@mggen.com) -" Last Change: 2021 Aug 30 +" Last Change: 2023 Jan 26 " " Todo: -" - The parameter String: is matched as flag string (because of case ignore). " - Pascal scripting syntax is not recognized. " - Embedded double quotes confuse string matches. e.g. "asfd""asfa" @@ -17,6 +16,9 @@ endif " shut case off syn case ignore +" match keywords with colon +syn iskeyword @,48-57,_,192-255,: + " Preprocessor syn region issPreProc start="^\s*#" end="$" @@ -30,25 +32,25 @@ syn match issDirective "^[^=]\+=" syn match issURL "http[s]\=:\/\/.*$" " Parameters used for any section. -" syn match issParam"[^: ]\+:" -syn match issParam "Name:" -syn match issParam "MinVersion:\|OnlyBelowVersion:\|Languages:" -syn match issParam "Source:\|DestDir:\|DestName:\|CopyMode:" -syn match issParam "Attribs:\|Permissions:\|FontInstall:\|Flags:" -syn match issParam "FileName:\|Parameters:\|WorkingDir:\|HotKey:\|Comment:" -syn match issParam "IconFilename:\|IconIndex:" -syn match issParam "Section:\|Key:\|String:" -syn match issParam "Root:\|SubKey:\|ValueType:\|ValueName:\|ValueData:" -syn match issParam "RunOnceId:" -syn match issParam "Type:\|Excludes:" -syn match issParam "Components:\|Description:\|GroupDescription:\|Types:\|ExtraDiskSpaceRequired:" -syn match issParam "StatusMsg:\|RunOnceId:\|Tasks:" -syn match issParam "MessagesFile:\|LicenseFile:\|InfoBeforeFile:\|InfoAfterFile:" +" syn match issParam "[^: ]\+:" +syn keyword issParam Name: +syn keyword issParam MinVersion: OnlyBelowVersion: Languages: +syn keyword issParam Source: DestDir: DestName: CopyMode: ExternalSize: +syn keyword issParam Attribs: Permissions: FontInstall: Flags: +syn keyword issParam FileName: Parameters: WorkingDir: HotKey: Comment: +syn keyword issParam IconFilename: IconIndex: +syn keyword issParam Section: Key: String: +syn keyword issParam Root: SubKey: ValueType: ValueName: ValueData: +syn keyword issParam RunOnceId: +syn keyword issParam Type: Excludes: +syn keyword issParam Components: Description: GroupDescription: Types: ExtraDiskSpaceRequired: +syn keyword issParam StatusMsg: RunOnceId: Tasks: +syn keyword issParam MessagesFile: LicenseFile: InfoBeforeFile: InfoAfterFile: syn match issComment "^\s*;.*$" contains=@Spell " folder constant -syn match issFolder "{[^{]*}" contains=@NoSpell +syn match issFolder "{\@1<!{[^{]*}" contains=@NoSpell " string syn region issString start=+"+ end=+"+ contains=issFolder,@Spell @@ -61,16 +63,16 @@ syn keyword issFilesCopyMode normal onlyifdoesntexist alwaysoverwrite alwaysskip syn keyword issFilesAttribs readonly hidden system syn keyword issFilesPermissions full modify readexec syn keyword issFilesFlags allowunsafefiles comparetimestampalso confirmoverwrite deleteafterinstall -syn keyword issFilesFlags dontcopy dontverifychecksum external fontisnttruetype ignoreversion -syn keyword issFilesFlags isreadme onlyifdestfileexists onlyifdoesntexist overwritereadonly +syn keyword issFilesFlags dontcopy dontverifychecksum external fontisnttruetype ignoreversion +syn keyword issFilesFlags isreadme onlyifdestfileexists onlyifdoesntexist overwritereadonly syn keyword issFilesFlags promptifolder recursesubdirs regserver regtypelib restartreplace -syn keyword issFilesFlags sharedfile skipifsourcedoesntexist sortfilesbyextension touch +syn keyword issFilesFlags sharedfile skipifsourcedoesntexist sortfilesbyextension touch syn keyword issFilesFlags uninsremovereadonly uninsrestartdelete uninsneveruninstall -syn keyword issFilesFlags replacesameversion nocompression noencryption noregerror +syn keyword issFilesFlags replacesameversion setntfscompression nocompression noencryption noregerror " [Icons] -syn keyword issIconsFlags closeonexit createonlyiffileexists dontcloseonexit +syn keyword issIconsFlags closeonexit createonlyiffileexists dontcloseonexit syn keyword issIconsFlags runmaximized runminimized uninsneveruninstall useapppaths " [INI] @@ -79,13 +81,13 @@ syn keyword issINIFlags createkeyifdoesntexist uninsdeleteentry uninsdeletesecti " [Registry] syn keyword issRegRootKey HKCR HKCU HKLM HKU HKCC syn keyword issRegValueType none string expandsz multisz dword binary -syn keyword issRegFlags createvalueifdoesntexist deletekey deletevalue dontcreatekey -syn keyword issRegFlags preservestringtype noerror uninsclearvalue +syn keyword issRegFlags createvalueifdoesntexist deletekey deletevalue dontcreatekey +syn keyword issRegFlags preservestringtype noerror uninsclearvalue syn keyword issRegFlags uninsdeletekey uninsdeletekeyifempty uninsdeletevalue " [Run] and [UninstallRun] syn keyword issRunFlags hidewizard nowait postinstall runhidden runmaximized -syn keyword issRunFlags runminimized shellexec skipifdoesntexist skipifnotsilent +syn keyword issRunFlags runminimized shellexec skipifdoesntexist skipifnotsilent syn keyword issRunFlags skipifsilent unchecked waituntilidle " [Types] @@ -98,7 +100,7 @@ syn keyword issComponentsFlags dontinheritcheck exclusive fixed restart disablen syn keyword issInstallDeleteType files filesandordirs dirifempty " [Tasks] -syn keyword issTasksFlags checkedonce dontinheritcheck exclusive restart unchecked +syn keyword issTasksFlags checkedonce dontinheritcheck exclusive restart unchecked " Define the default highlighting. @@ -112,7 +114,7 @@ hi def link issParam Type hi def link issFolder Special hi def link issString String hi def link issURL Include -hi def link issPreProc PreProc +hi def link issPreProc PreProc hi def link issDirsFlags Keyword hi def link issFilesCopyMode Keyword diff --git a/runtime/syntax/pymanifest.vim b/runtime/syntax/pymanifest.vim new file mode 100644 index 0000000000..54295c45dc --- /dev/null +++ b/runtime/syntax/pymanifest.vim @@ -0,0 +1,44 @@ +" Vim syntax file +" Language: PyPA manifest +" Maintainer: ObserverOfTime <chronobserver@disroot.org> +" Filenames: MANIFEST.in +" Last Change: 2023 Aug 12 + +if exists('b:current_syntax') + finish +endif + +let s:cpo_save = &cpoptions +set cpoptions&vim + +syn iskeyword @,- + +" Comments +syn keyword pymanifestTodo contained TODO FIXME XXX +syn match pymanifestComment /\\\@1<!#.*/ contains=pymanifestTodo + +" Commands +syn keyword pymanifestCommand + \ include exclude + \ recursive-include resursive-exclude + \ global-include global-exclude + \ graft prune + +" Globs & character ranges +syn match pymanifestGlob /\*\|\*\*\|?/ +syn match pymanifestRange /\\\@1<!\[.\{-}\]/ + +" Line break +syn match pymanifestLinebreak /\\$\|\\\ze\s\+#/ + +hi def link pymanifestCommand Keyword +hi def link pymanifestComment Comment +hi def link pymanifestGlob SpecialChar +hi def link pymanifestLinebreak SpecialKey +hi def link pymanifestRange Special +hi def link pymanifestTodo Todo + +let b:current_syntax = 'pymanifest' + +let &cpoptions = s:cpo_save +unlet s:cpo_save diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index 7af055640b..94ecc23413 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -119,10 +119,10 @@ get_vim_sources() { } commit_message() { - if [[ -n "$vim_tag" ]]; then - printf '%s\n\n%s\n\n%s' "${vim_message}" "${vim_commit_url}" "${vim_coauthor}" + if [[ "${vim_message}" == "vim-patch:${vim_version}:"* ]]; then + printf '%s\n\n%s\n\n%s' "${vim_message}" "${vim_commit_url}" "${vim_coauthors}" else - printf 'vim-patch:%s\n\n%s\n\n%s\n\n%s' "$vim_version" "$vim_message" "$vim_commit_url" "$vim_coauthor" + printf 'vim-patch:%s\n\n%s\n\n%s\n\n%s' "$vim_version" "$vim_message" "$vim_commit_url" "$vim_coauthors" fi } @@ -175,7 +175,13 @@ assign_commit_details() { vim_commit_url="https://github.com/vim/vim/commit/${vim_commit}" vim_message="$(git -C "${VIM_SOURCE_DIR}" log -1 --pretty='format:%B' "${vim_commit}" \ | sed -e 's/\(#[0-9]\{1,\}\)/vim\/vim\1/g')" - vim_coauthor="$(git -C "${VIM_SOURCE_DIR}" log -1 --pretty='format:Co-authored-by: %an <%ae>' "${vim_commit}")" + local vim_coauthor0 + vim_coauthor0="$(git -C "${VIM_SOURCE_DIR}" log -1 --pretty='format:Co-authored-by: %an <%ae>' "${vim_commit}")" + # Extract co-authors from the commit message. + vim_coauthors="$(echo "${vim_message}" | (grep -E '^Co-authored-by: ' || true) | (grep -Fxv "${vim_coauthor0}" || true))" + vim_coauthors="$(echo "${vim_coauthor0}"; echo "${vim_coauthors}")" + # Remove Co-authored-by and Signed-off-by lines from the commit message. + vim_message="$(echo "${vim_message}" | sed -e '/^\(Co-authored\|Signed-off\)-by: /d')" if [[ ${munge_commit_line} == "true" ]]; then # Remove first line of commit message. vim_message="$(echo "${vim_message}" | sed -e '1s/^patch /vim-patch:/')" @@ -235,43 +241,47 @@ preprocess_patch() { "$file" > "$file".tmp && mv "$file".tmp "$file" # Rename evalfunc.c to eval/funcs.c - LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/evalfunc\.c/\1\/eval\/funcs\.c/g' \ + LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/evalfunc\.c/\1\/eval\/funcs.c/g' \ "$file" > "$file".tmp && mv "$file".tmp "$file" # Rename evalvars.c to eval/vars.c - LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/evalvars\.c/\1\/eval\/vars\.c/g' \ + LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/evalvars\.c/\1\/eval\/vars.c/g' \ "$file" > "$file".tmp && mv "$file".tmp "$file" # Rename userfunc.c to eval/userfunc.c - LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/userfunc\.c/\1\/eval\/userfunc\.c/g' \ + LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/userfunc\.c/\1\/eval\/userfunc.c/g' \ "$file" > "$file".tmp && mv "$file".tmp "$file" # Rename evalbuffer.c to eval/buffer.c - LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/evalbuffer\.c/\1\/eval\/buffer\.c/g' \ + LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/evalbuffer\.c/\1\/eval\/buffer.c/g' \ "$file" > "$file".tmp && mv "$file".tmp "$file" # Rename evalwindow.c to eval/window.c - LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/evalwindow\.c/\1\/eval\/window\.c/g' \ + LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/evalwindow\.c/\1\/eval\/window.c/g' \ "$file" > "$file".tmp && mv "$file".tmp "$file" # Rename map.c to mapping.c - LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/map\(\.[ch]\)/\1\/mapping\2/g' \ + LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/map\.c/\1\/mapping.c/g' \ "$file" > "$file".tmp && mv "$file".tmp "$file" # Rename profiler.c to profile.c - LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/profiler\(\.[ch]\)/\1\/profile\2/g' \ + LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/profiler\.c/\1\/profile.c/g' \ "$file" > "$file".tmp && mv "$file".tmp "$file" # Rename scriptfile.c to runtime.c - LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/scriptfile\(\.[ch]\)/\1\/runtime\2/g' \ + LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/scriptfile\.c/\1\/runtime.c/g' \ "$file" > "$file".tmp && mv "$file".tmp "$file" # Rename session.c to ex_session.c - LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/session\(\.[ch]\)/\1\/ex_session\2/g' \ + LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/session\.c/\1\/ex_session.c/g' \ "$file" > "$file".tmp && mv "$file".tmp "$file" # Rename highlight.c to highlight_group.c - LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/highlight\(\.[ch]\)/\1\/highlight_group\2/g' \ + LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/highlight\.c/\1\/highlight_group.c/g' \ + "$file" > "$file".tmp && mv "$file".tmp "$file" + + # Rename locale.c to os/lang.c + LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/locale\.c/\1\/os\/lang.c/g' \ "$file" > "$file".tmp && mv "$file".tmp "$file" # Rename keymap.h to keycodes.h @@ -283,11 +293,11 @@ preprocess_patch() { "$file" > "$file".tmp && mv "$file".tmp "$file" # Rename test_urls.vim to check_urls.vim - LC_ALL=C sed -e 's@\( [ab]\)/runtime/doc/test\(_urls\.vim\)@\1/scripts/check\2@g' \ + LC_ALL=C sed -e 's/\( [ab]\)\/runtime\/doc\/test\(_urls\.vim\)/\1\/scripts\/check\2/g' \ "$file" > "$file".tmp && mv "$file".tmp "$file" # Rename path to check_colors.vim - LC_ALL=C sed -e 's@\( [ab]/runtime\)/colors/\(tools/check_colors\.vim\)@\1/\2@g' \ + LC_ALL=C sed -e 's/\( [ab]\/runtime\)\/colors\/\(tools\/check_colors\.vim\)/\1\/\2/g' \ "$file" > "$file".tmp && mv "$file".tmp "$file" } diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 22fe69e447..4179ae40b8 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -431,7 +431,7 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, Bool } char *ptr = NULL; - replace_termcodes(str.data, str.size, &ptr, flags, NULL, CPO_TO_CPO_FLAGS); + replace_termcodes(str.data, str.size, &ptr, 0, flags, NULL, CPO_TO_CPO_FLAGS); return cstr_as_string(ptr); } diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index a8c5d00383..c43a59cbb3 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -1030,7 +1030,8 @@ int autocmd_register(int64_t id, event_T event, const char *pat, int patlen, int // If the event is CursorMoved, update the last cursor position // position to avoid immediately triggering the autocommand if (event == EVENT_CURSORMOVED && !has_event(EVENT_CURSORMOVED)) { - curwin->w_last_cursormoved = curwin->w_cursor; + last_cursormoved_win = curwin; + last_cursormoved = curwin->w_cursor; } // Initialize the fields checked by the WinScrolled and diff --git a/src/nvim/autocmd.h b/src/nvim/autocmd.h index 9e6c534581..b3de57311e 100644 --- a/src/nvim/autocmd.h +++ b/src/nvim/autocmd.h @@ -80,6 +80,11 @@ typedef kvec_t(AutoCmd) AutoCmdVec; // apply_autocmds_group. EXTERN bool au_did_filetype INIT(= false); +/// For CursorMoved event +EXTERN win_T *last_cursormoved_win INIT(= NULL); +/// For CursorMoved event, only used when last_cursormoved_win == curwin +EXTERN pos_T last_cursormoved INIT(= { 0, 0, 0 }); + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "autocmd.h.generated.h" #endif diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 5708274848..ca1f791d28 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -1101,7 +1101,6 @@ struct window_S { ///< can be different from w_cursor.lnum ///< for closed folds. linenr_T w_last_cursorline; ///< where last 'cursorline' was drawn - pos_T w_last_cursormoved; ///< for CursorMoved event // the next seven are used to update the visual part char w_old_visual_mode; ///< last known VIsual_mode diff --git a/src/nvim/change.c b/src/nvim/change.c index 932de727b5..599e319dde 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -361,9 +361,10 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T } // when the cursor line is changed always trigger CursorMoved - if (lnum <= curwin->w_cursor.lnum + if (last_cursormoved_win == curwin + && lnum <= curwin->w_cursor.lnum && lnume + (xtra < 0 ? -xtra : xtra) > curwin->w_cursor.lnum) { - curwin->w_last_cursormoved.lnum = 0; + last_cursormoved.lnum = 0; } } diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 64e47cbeb8..1f8d21220b 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -2384,7 +2384,7 @@ void diff_set_topline(win_T *fromwin, win_T *towin) towin->w_topline = lnum + (dp->df_lnum[toidx] - dp->df_lnum[fromidx]); if (lnum >= dp->df_lnum[fromidx]) { - if (diff_flags & DIFF_LINEMATCH) { + if (dp->is_linematched) { calculate_topfill_and_topline(fromidx, toidx, fromwin->w_topline, fromwin->w_topfill, &towin->w_topfill, &towin->w_topline); } else { diff --git a/src/nvim/edit.c b/src/nvim/edit.c index e44c49ad0b..b8d2eca810 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -1287,7 +1287,8 @@ void ins_redraw(bool ready) // Trigger CursorMoved if the cursor moved. Not when the popup menu is // visible, the command might delete it. if (ready && has_event(EVENT_CURSORMOVEDI) - && !equalpos(curwin->w_last_cursormoved, curwin->w_cursor) + && (last_cursormoved_win != curwin + || !equalpos(last_cursormoved, curwin->w_cursor)) && !pum_visible()) { // Need to update the screen first, to make sure syntax // highlighting is correct after making a change (e.g., inserting @@ -1300,7 +1301,8 @@ void ins_redraw(bool ready) // getcurpos() update_curswant(); ins_apply_autocmds(EVENT_CURSORMOVEDI); - curwin->w_last_cursormoved = curwin->w_cursor; + last_cursormoved_win = curwin; + last_cursormoved = curwin->w_cursor; } // Trigger TextChangedI if changedtick differs. diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 4e6688a4a1..1e53014715 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -4105,6 +4105,9 @@ M.funcs = { text description of the error type type of the error, 'E', '1', etc. valid |TRUE|: recognized error message + user_data + custom data associated with the item, can be + any type. When there is no error list or it's empty, an empty list is returned. Quickfix list entries with a non-existing buffer @@ -9121,6 +9124,9 @@ M.funcs = { text description of the error type single-character error type, 'E', 'W', etc. valid recognized error message + user_data + custom data associated with the item, can be + any type. The "col", "vcol", "nr", "type" and "text" entries are optional. Either "lnum" or "pattern" entry can be used to @@ -11620,9 +11626,12 @@ M.funcs = { signature = 'undofile({name})', }, undotree = { + args = { 0, 1 }, + base = 1, desc = [=[ - Return the current state of the undo tree in a dictionary with - the following items: + Return the current state of the undo tree for the current + buffer, or for a specific buffer if {buf} is given. The + result is a dictionary with the following items: "seq_last" The highest undo sequence number used. "seq_cur" The sequence number of the current position in the undo tree. This differs from "seq_last" @@ -11664,8 +11673,8 @@ M.funcs = { item. ]=], name = 'undotree', - params = {}, - signature = 'undotree()', + params = { { 'buf', 'any' } }, + signature = 'undotree([{buf}])', }, uniq = { args = { 1, 3 }, diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 87e45cbb66..17c17e60ce 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -82,6 +82,7 @@ static unsigned last_prompt_id = 0; typedef struct { colnr_T vs_curswant; colnr_T vs_leftcol; + colnr_T vs_skipcol; linenr_T vs_topline; int vs_topfill; linenr_T vs_botline; @@ -208,6 +209,7 @@ static void save_viewstate(win_T *wp, viewstate_T *vs) { vs->vs_curswant = wp->w_curswant; vs->vs_leftcol = wp->w_leftcol; + vs->vs_skipcol = wp->w_skipcol; vs->vs_topline = wp->w_topline; vs->vs_topfill = wp->w_topfill; vs->vs_botline = wp->w_botline; @@ -219,6 +221,7 @@ static void restore_viewstate(win_T *wp, viewstate_T *vs) { wp->w_curswant = vs->vs_curswant; wp->w_leftcol = vs->vs_leftcol; + wp->w_skipcol = vs->vs_skipcol; wp->w_topline = vs->vs_topline; wp->w_topfill = vs->vs_topfill; wp->w_botline = vs->vs_botline; diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 5c1366c5b2..2e584e7cff 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1147,6 +1147,13 @@ static void gotchars(const uint8_t *chars, size_t len) maptick++; } +/// Record a <Nop> key. +void gotchars_nop(void) +{ + uint8_t nop_buf[3] = { K_SPECIAL, KS_EXTRA, KE_NOP }; + gotchars(nop_buf, 3); +} + /// Undo the last gotchars() for "len" bytes. To be used when putting a typed /// character back into the typeahead buffer, thus gotchars() will be called /// again. @@ -2745,14 +2752,9 @@ static int vgetorpeek(bool advance) } if (timedout && c == ESC) { - uint8_t nop_buf[3]; - // When recording there will be no timeout. Add a <Nop> after the ESC // to avoid that it forms a key code with following characters. - nop_buf[0] = K_SPECIAL; - nop_buf[1] = KS_EXTRA; - nop_buf[2] = KE_NOP; - gotchars(nop_buf, 3); + gotchars_nop(); } vgetc_busy--; diff --git a/src/nvim/keycodes.c b/src/nvim/keycodes.c index 34442ae5c4..6c64a2ca4a 100644 --- a/src/nvim/keycodes.c +++ b/src/nvim/keycodes.c @@ -873,6 +873,7 @@ int get_mouse_button(int code, bool *is_click, bool *is_drag) /// If `*bufp` is non-NULL, it will be used directly, /// and is assumed to be 128 bytes long (enough for transcoding LHS of mapping), /// and will be set to NULL in case of failure. +/// @param[in] sid_arg Script ID to use for <SID>, or 0 to use current_sctx /// @param[in] flags REPTERM_FROM_PART see above /// REPTERM_DO_LT also translate <lt> /// REPTERM_NO_SPECIAL do not accept <key> notation @@ -882,7 +883,8 @@ int get_mouse_button(int code, bool *is_click, bool *is_drag) /// /// @return The same as what `*bufp` is set to. char *replace_termcodes(const char *const from, const size_t from_len, char **const bufp, - const int flags, bool *const did_simplify, const int cpo_flags) + const scid_T sid_arg, const int flags, bool *const did_simplify, + const int cpo_flags) FUNC_ATTR_NONNULL_ARG(1, 3) { ssize_t i; @@ -916,15 +918,15 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co // Replace <SID> by K_SNR <script-nr> _. // (room: 5 * 6 = 30 bytes; needed: 3 + <nr> + 1 <= 14) if (end - src >= 4 && STRNICMP(src, "<SID>", 5) == 0) { - if (current_sctx.sc_sid <= 0) { + if (sid_arg < 0 || (sid_arg == 0 && current_sctx.sc_sid <= 0)) { emsg(_(e_usingsid)); } else { + const scid_T sid = sid_arg != 0 ? sid_arg : current_sctx.sc_sid; src += 5; result[dlen++] = (char)K_SPECIAL; result[dlen++] = (char)KS_EXTRA; result[dlen++] = KE_SNR; - snprintf(result + dlen, buf_len - dlen, "%" PRId64, - (int64_t)current_sctx.sc_sid); + snprintf(result + dlen, buf_len - dlen, "%" PRId64, (int64_t)sid); dlen += strlen(result + dlen); result[dlen++] = '_'; continue; diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 9215926434..459c69f385 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -1302,6 +1302,9 @@ LuaRef nlua_ref(lua_State *lstate, nlua_ref_state_t *ref_state, int index) return ref; } +// TODO(lewis6991): Currently cannot be run in __gc metamethods as they are +// invoked in lua_close() which can be invoked after the ref_markers map is +// destroyed in nlua_common_free_all_mem. LuaRef nlua_ref_global(lua_State *lstate, int index) { return nlua_ref(lstate, nlua_global_refs, index); diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 66a75f8d40..1e559316dd 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -325,6 +325,17 @@ static TSParser **parser_check(lua_State *L, uint16_t index) return luaL_checkudata(L, index, TS_META_PARSER); } +static void logger_gc(TSLogger logger) +{ + if (!logger.log) { + return; + } + + TSLuaLoggerOpts *opts = (TSLuaLoggerOpts *)logger.payload; + luaL_unref(opts->lstate, LUA_REGISTRYINDEX, opts->cb); + xfree(opts); +} + static int parser_gc(lua_State *L) { TSParser **p = parser_check(L, 1); @@ -332,12 +343,7 @@ static int parser_gc(lua_State *L) return 0; } - TSLogger logger = ts_parser_logger(*p); - if (logger.log) { - TSLuaLoggerOpts *opts = (TSLuaLoggerOpts *)logger.payload; - xfree(opts); - } - + logger_gc(ts_parser_logger(*p)); ts_parser_delete(*p); return 0; } @@ -698,7 +704,7 @@ static void logger_cb(void *payload, TSLogType logtype, const char *s) lua_State *lstate = opts->lstate; - nlua_pushref(lstate, opts->cb); + lua_rawgeti(lstate, LUA_REGISTRYINDEX, opts->cb); lua_pushstring(lstate, logtype == TSLogTypeParse ? "parse" : "lex"); lua_pushstring(lstate, s); if (lua_pcall(lstate, 2, 0, 0)) { @@ -726,11 +732,13 @@ static int parser_set_logger(lua_State *L) } TSLuaLoggerOpts *opts = xmalloc(sizeof(TSLuaLoggerOpts)); + lua_pushvalue(L, 4); + LuaRef ref = luaL_ref(L, LUA_REGISTRYINDEX); *opts = (TSLuaLoggerOpts){ .lex = lua_toboolean(L, 2), .parse = lua_toboolean(L, 3), - .cb = nlua_ref_global(L, 4), + .cb = ref, .lstate = L }; @@ -753,7 +761,7 @@ static int parser_get_logger(lua_State *L) TSLogger logger = ts_parser_logger(*p); if (logger.log) { TSLuaLoggerOpts *opts = (TSLuaLoggerOpts *)logger.payload; - nlua_pushref(L, opts->cb); + lua_rawgeti(L, LUA_REGISTRYINDEX, opts->cb); } else { lua_pushnil(L); } diff --git a/src/nvim/main.c b/src/nvim/main.c index 377b804661..d9ca82784f 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -921,6 +921,11 @@ static void remote_request(mparm_T *params, int remote_args, char *server_addr, os_errmsg(connect_error); os_errmsg("\n"); os_exit(1); + } else if (strequal(server_addr, os_getenv("NVIM"))) { + os_errmsg("Cannot attach UI of :terminal child to its parent. "); + os_errmsg("(Unset $NVIM to skip this check)"); + os_errmsg("\n"); + os_exit(1); } ui_client_channel_id = chan; diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c index 0cb94e6f5b..f2732184db 100644 --- a/src/nvim/mapping.c +++ b/src/nvim/mapping.c @@ -279,16 +279,16 @@ static bool set_maparg_lhs_rhs(const char *const orig_lhs, const size_t orig_lhs bool did_simplify = false; const int flags = REPTERM_FROM_PART | REPTERM_DO_LT; char *bufarg = lhs_buf; - char *replaced = replace_termcodes(orig_lhs, orig_lhs_len, &bufarg, flags, &did_simplify, - cpo_flags); + char *replaced = replace_termcodes(orig_lhs, orig_lhs_len, &bufarg, 0, + flags, &did_simplify, cpo_flags); if (replaced == NULL) { return false; } mapargs->lhs_len = strlen(replaced); xstrlcpy(mapargs->lhs, replaced, sizeof(mapargs->lhs)); if (did_simplify) { - replaced = replace_termcodes(orig_lhs, orig_lhs_len, &bufarg, flags | REPTERM_NO_SIMPLIFY, - NULL, cpo_flags); + replaced = replace_termcodes(orig_lhs, orig_lhs_len, &bufarg, 0, + flags | REPTERM_NO_SIMPLIFY, NULL, cpo_flags); if (replaced == NULL) { return false; } @@ -298,14 +298,15 @@ static bool set_maparg_lhs_rhs(const char *const orig_lhs, const size_t orig_lhs mapargs->alt_lhs_len = 0; } - set_maparg_rhs(orig_rhs, orig_rhs_len, rhs_lua, cpo_flags, mapargs); + set_maparg_rhs(orig_rhs, orig_rhs_len, rhs_lua, 0, cpo_flags, mapargs); return true; } /// @see set_maparg_lhs_rhs static void set_maparg_rhs(const char *const orig_rhs, const size_t orig_rhs_len, - const LuaRef rhs_lua, const int cpo_flags, MapArguments *const mapargs) + const LuaRef rhs_lua, const scid_T sid, const int cpo_flags, + MapArguments *const mapargs) { mapargs->rhs_lua = rhs_lua; @@ -319,8 +320,8 @@ static void set_maparg_rhs(const char *const orig_rhs, const size_t orig_rhs_len mapargs->rhs_is_noop = true; } else { char *rhs_buf = NULL; - char *replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf, REPTERM_DO_LT, NULL, - cpo_flags); + char *replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf, sid, + REPTERM_DO_LT, NULL, cpo_flags); mapargs->rhs_len = strlen(replaced); // NB: replace_termcodes may produce an empty string even if orig_rhs is non-empty // (e.g. a single ^V, see :h map-empty-rhs) @@ -1079,9 +1080,8 @@ bool map_to_exists(const char *const str, const char *const modechars, const boo int retval; char *buf = NULL; - const char *const rhs = replace_termcodes(str, strlen(str), - &buf, REPTERM_DO_LT, - NULL, CPO_TO_CPO_FLAGS); + const char *const rhs = replace_termcodes(str, strlen(str), &buf, 0, + REPTERM_DO_LT, NULL, CPO_TO_CPO_FLAGS); #define MAPMODE(mode, modechars, chr, modeflags) \ do { \ @@ -1657,7 +1657,7 @@ char *eval_map_expr(mapblock_T *mp, int c) char *res = NULL; if (replace_keycodes) { - replace_termcodes(p, strlen(p), &res, REPTERM_DO_LT, NULL, CPO_TO_CPO_FLAGS); + replace_termcodes(p, strlen(p), &res, 0, REPTERM_DO_LT, NULL, CPO_TO_CPO_FLAGS); } else { // Escape K_SPECIAL in the result to be able to use the string as typeahead. res = vim_strsave_escape_ks(p); @@ -2157,8 +2157,8 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) const int flags = REPTERM_FROM_PART | REPTERM_DO_LT; const int mode = get_map_mode((char **)&which, 0); - char *keys_simplified = replace_termcodes(keys, strlen(keys), &keys_buf, flags, &did_simplify, - CPO_TO_CPO_FLAGS); + char *keys_simplified = replace_termcodes(keys, strlen(keys), &keys_buf, 0, + flags, &did_simplify, CPO_TO_CPO_FLAGS); mapblock_T *mp = NULL; int buffer_local; LuaRef rhs_lua; @@ -2167,10 +2167,8 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) if (did_simplify) { // When the lhs is being simplified the not-simplified keys are // preferred for printing, like in do_map(). - (void)replace_termcodes(keys, - strlen(keys), - &alt_keys_buf, flags | REPTERM_NO_SIMPLIFY, NULL, - CPO_TO_CPO_FLAGS); + (void)replace_termcodes(keys, strlen(keys), &alt_keys_buf, 0, + flags | REPTERM_NO_SIMPLIFY, NULL, CPO_TO_CPO_FLAGS); rhs = check_map(alt_keys_buf, mode, exact, false, abbr, &mp, &buffer_local, &rhs_lua); } @@ -2252,12 +2250,13 @@ void f_mapset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) .replace_keycodes = tv_dict_get_number(d, "replace_keycodes") != 0, .desc = tv_dict_get_string(d, "desc", false), }; - set_maparg_rhs(orig_rhs, strlen(orig_rhs), rhs_lua, CPO_TO_CPO_FLAGS, &args); scid_T sid = (scid_T)tv_dict_get_number(d, "sid"); linenr_T lnum = (linenr_T)tv_dict_get_number(d, "lnum"); bool buffer = tv_dict_get_number(d, "buffer") != 0; // mode from the dict is not used + set_maparg_rhs(orig_rhs, strlen(orig_rhs), rhs_lua, sid, CPO_TO_CPO_FLAGS, &args); + mapblock_T **map_table = buffer ? curbuf->b_maphash : maphash; mapblock_T **abbr_table = buffer ? &curbuf->b_first_abbr : &first_abbr; diff --git a/src/nvim/menu.c b/src/nvim/menu.c index 898e3ddd27..1d35c97b39 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -232,7 +232,7 @@ void ex_menu(exarg_T *eap) map_buf = NULL; // Menu tips are plain text. } else { map_buf = NULL; - map_to = replace_termcodes(map_to, strlen(map_to), &map_buf, + map_to = replace_termcodes(map_to, strlen(map_to), &map_buf, 0, REPTERM_DO_LT, NULL, CPO_TO_CPO_FLAGS); } menuarg.modes = modes; diff --git a/src/nvim/normal.c b/src/nvim/normal.c index c5538fb7dd..edfc62ae17 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -807,25 +807,32 @@ static void normal_get_additional_char(NormalState *s) } } - // When getting a text character and the next character is a - // multi-byte character, it could be a composing character. - // However, don't wait for it to arrive. Also, do enable mapping, - // because if it's put back with vungetc() it's too late to apply - // mapping. - no_mapping--; - while (lang && (s->c = vpeekc()) > 0 - && (s->c >= 0x100 || MB_BYTE2LEN(vpeekc()) > 1)) { - s->c = plain_vgetc(); - if (!utf_iscomposing(s->c)) { - vungetc(s->c); // it wasn't, put it back - break; - } else if (s->ca.ncharC1 == 0) { - s->ca.ncharC1 = s->c; - } else { - s->ca.ncharC2 = s->c; + if (lang) { + // When getting a text character and the next character is a + // multi-byte character, it could be a composing character. + // However, don't wait for it to arrive. Also, do enable mapping, + // because if it's put back with vungetc() it's too late to apply + // mapping. + no_mapping--; + while (lang && (s->c = vpeekc()) > 0 + && (s->c >= 0x100 || MB_BYTE2LEN(vpeekc()) > 1)) { + s->c = plain_vgetc(); + if (!utf_iscomposing(s->c)) { + vungetc(s->c); // it wasn't, put it back + break; + } else if (s->ca.ncharC1 == 0) { + s->ca.ncharC1 = s->c; + } else { + s->ca.ncharC2 = s->c; + } } + no_mapping++; + // Vim may be in a different mode when the user types the next key, + // but when replaying a recording the next key is already in the + // typeahead buffer, so record a <Nop> before that to prevent the + // vpeekc() above from applying wrong mappings when replaying. + gotchars_nop(); } - no_mapping++; } no_mapping--; allow_keys--; @@ -1260,9 +1267,11 @@ static void normal_check_cursor_moved(NormalState *s) { // Trigger CursorMoved if the cursor moved. if (!finish_op && has_event(EVENT_CURSORMOVED) - && !equalpos(curwin->w_last_cursormoved, curwin->w_cursor)) { + && (last_cursormoved_win != curwin + || !equalpos(last_cursormoved, curwin->w_cursor))) { apply_autocmds(EVENT_CURSORMOVED, NULL, NULL, false, curbuf); - curwin->w_last_cursormoved = curwin->w_cursor; + last_cursormoved_win = curwin; + last_cursormoved = curwin->w_cursor; } } @@ -4506,7 +4515,7 @@ static void nv_replace(cmdarg_T *cap) } // get another character - if (cap->nchar == Ctrl_V) { + if (cap->nchar == Ctrl_V || cap->nchar == Ctrl_Q) { had_ctrl_v = Ctrl_V; cap->nchar = get_literal(false); // Don't redo a multibyte character with CTRL-V. @@ -4733,7 +4742,8 @@ static void nv_vreplace(cmdarg_T *cap) if (!MODIFIABLE(curbuf)) { emsg(_(e_modifiable)); } else { - if (cap->extra_char == Ctrl_V) { // get another character + if (cap->extra_char == Ctrl_V || cap->extra_char == Ctrl_Q) { + // get another character cap->extra_char = get_literal(false); } if (cap->extra_char < ' ') { diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index d42e0ed24f..00a9dad1fe 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -80,14 +80,14 @@ struct qfline_S { int qf_col; ///< column where the error occurred int qf_end_col; ///< column when the error has range or zero int qf_nr; ///< error number - char *qf_module; ///< module name for this error - char *qf_pattern; ///< search pattern for the error - char *qf_text; ///< description of the error - char qf_viscol; ///< set to true if qf_col and qf_end_col is - // screen column - char qf_cleared; ///< set to true if line has been deleted - char qf_type; ///< type of the error (mostly 'E'); 1 for :helpgrep - char qf_valid; ///< valid error message detected + char *qf_module; ///< module name for this error + char *qf_pattern; ///< search pattern for the error + char *qf_text; ///< description of the error + char qf_viscol; ///< set to true if qf_col and qf_end_col is screen column + char qf_cleared; ///< set to true if line has been deleted + char qf_type; ///< type of the error (mostly 'E'); 1 for :helpgrep + typval_T qf_user_data; ///< custom user data associated with this item + char qf_valid; ///< valid error message detected }; // There is a stack of error lists. @@ -109,18 +109,19 @@ typedef enum { /// created using setqflist()/setloclist() with a title and/or user context /// information and entries can be added later using setqflist()/setloclist(). typedef struct qf_list_S { - unsigned qf_id; ///< Unique identifier for this list + unsigned qf_id; ///< Unique identifier for this list qfltype_T qfl_type; - qfline_T *qf_start; ///< pointer to the first error - qfline_T *qf_last; ///< pointer to the last error - qfline_T *qf_ptr; ///< pointer to the current error - int qf_count; ///< number of errors (0 means empty list) - int qf_index; ///< current index in the error list - int qf_nonevalid; ///< true if not a single valid entry found - char *qf_title; ///< title derived from the command that created - ///< the error list or set by setqflist - typval_T *qf_ctx; ///< context set by setqflist/setloclist - Callback qf_qftf_cb; ///< 'quickfixtextfunc' callback function + qfline_T *qf_start; ///< pointer to the first error + qfline_T *qf_last; ///< pointer to the last error + qfline_T *qf_ptr; ///< pointer to the current error + int qf_count; ///< number of errors (0 means empty list) + int qf_index; ///< current index in the error list + bool qf_nonevalid; ///< true if not a single valid entry found + bool qf_has_user_data; ///< true if at least one item has user_data attached + char *qf_title; ///< title derived from the command that created + ///< the error list or set by setqflist + typval_T *qf_ctx; ///< context set by setqflist/setloclist + Callback qf_qftf_cb; ///< 'quickfixtextfunc' callback function struct dir_stack_T *qf_dir_stack; char *qf_directory; @@ -226,6 +227,7 @@ typedef struct { char *pattern; int enr; char type; + typval_T *user_data; bool valid; } qffields_T; @@ -351,6 +353,7 @@ static int qf_init_process_nextline(qf_list_T *qfl, efm_T *fmt_first, qfstate_T fields->pattern, fields->enr, fields->type, + fields->user_data, fields->valid); } @@ -1281,6 +1284,7 @@ static void qf_new_list(qf_info_T *qi, const char *qf_title) qf_store_title(qfl, qf_title); qfl->qfl_type = qi->qfl_type; qfl->qf_id = ++last_qf_id; + qfl->qf_has_user_data = false; } /// Parse the match for filename ('%f') pattern in regmatch. @@ -1836,12 +1840,14 @@ void check_quickfix_busy(void) /// @param pattern search pattern /// @param nr error number /// @param type type character +/// @param user_data custom user data or NULL /// @param valid valid entry /// /// @return QF_OK on success or QF_FAIL on failure. static int qf_add_entry(qf_list_T *qfl, char *dir, char *fname, char *module, int bufnum, char *mesg, linenr_T lnum, linenr_T end_lnum, int col, int end_col, - char vis_col, char *pattern, int nr, char type, char valid) + char vis_col, char *pattern, int nr, char type, typval_T *user_data, + char valid) { qfline_T *qfp = xmalloc(sizeof(qfline_T)); @@ -1862,6 +1868,12 @@ static int qf_add_entry(qf_list_T *qfl, char *dir, char *fname, char *module, in qfp->qf_col = col; qfp->qf_end_col = end_col; qfp->qf_viscol = vis_col; + if (user_data == NULL || user_data->v_type == VAR_UNKNOWN) { + qfp->qf_user_data.v_type = VAR_UNKNOWN; + } else { + tv_copy(user_data, &qfp->qf_user_data); + qfl->qf_has_user_data = true; + } if (pattern == NULL || *pattern == NUL) { qfp->qf_pattern = NULL; } else { @@ -1997,6 +2009,7 @@ static int copy_loclist_entries(const qf_list_T *from_qfl, qf_list_T *to_qfl) from_qfp->qf_pattern, from_qfp->qf_nr, 0, + &from_qfp->qf_user_data, from_qfp->qf_valid) == QF_FAIL) { return FAIL; } @@ -2022,6 +2035,7 @@ static int copy_loclist(qf_list_T *from_qfl, qf_list_T *to_qfl) // Some of the fields are populated by qf_add_entry() to_qfl->qfl_type = from_qfl->qfl_type; to_qfl->qf_nonevalid = from_qfl->qf_nonevalid; + to_qfl->qf_has_user_data = from_qfl->qf_has_user_data; to_qfl->qf_count = 0; to_qfl->qf_index = 0; to_qfl->qf_start = NULL; @@ -3374,6 +3388,7 @@ static void qf_free_items(qf_list_T *qfl) xfree(qfp->qf_module); xfree(qfp->qf_text); xfree(qfp->qf_pattern); + tv_clear(&qfp->qf_user_data); stop = (qfp == qfpnext); xfree(qfp); if (stop) { @@ -5239,6 +5254,7 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp NULL, // search pattern 0, // nr 0, // type + NULL, // user_data true) // valid == QF_FAIL) { got_int = true; @@ -5282,6 +5298,7 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp NULL, // search pattern 0, // nr 0, // type + NULL, // user_data true) // valid == QF_FAIL) { got_int = true; @@ -5809,6 +5826,8 @@ static int get_qfline_items(qfline_T *qfp, list_T *list) == FAIL) || (tv_dict_add_str(dict, S_LEN("text"), (qfp->qf_text == NULL ? "" : qfp->qf_text)) == FAIL) || (tv_dict_add_str(dict, S_LEN("type"), buf) == FAIL) + || (qfp->qf_user_data.v_type != VAR_UNKNOWN + && tv_dict_add_tv(dict, S_LEN("user_data"), &qfp->qf_user_data) == FAIL) || (tv_dict_add_nr(dict, S_LEN("valid"), (varnumber_T)qfp->qf_valid) == FAIL)) { // tv_dict_add* fail only if key already exist, but this is a newly // allocated dictionary which is thus guaranteed to have no existing keys. @@ -6288,8 +6307,7 @@ static int qf_setprop_qftf(qf_list_T *qfl, dictitem_T *di) /// Add a new quickfix entry to list at 'qf_idx' in the stack 'qi' from the /// items in the dict 'd'. If it is a valid error entry, then set 'valid_entry' /// to true. -static int qf_add_entry_from_dict(qf_list_T *qfl, const dict_T *d, bool first_entry, - bool *valid_entry) +static int qf_add_entry_from_dict(qf_list_T *qfl, dict_T *d, bool first_entry, bool *valid_entry) FUNC_ATTR_NONNULL_ALL { static bool did_bufnr_emsg; @@ -6313,6 +6331,9 @@ static int qf_add_entry_from_dict(qf_list_T *qfl, const dict_T *d, bool first_en if (text == NULL) { text = xcalloc(1, 1); } + typval_T user_data = { .v_type = VAR_UNKNOWN }; + tv_dict_get_tv(d, "user_data", &user_data); + bool valid = true; if ((filename == NULL && bufnum == 0) || (lnum == 0 && pattern == NULL)) { @@ -6349,12 +6370,14 @@ static int qf_add_entry_from_dict(qf_list_T *qfl, const dict_T *d, bool first_en pattern, // search pattern nr, type == NULL ? NUL : *type, + &user_data, valid); xfree(filename); xfree(module); xfree(pattern); xfree(text); + tv_clear(&user_data); if (valid) { *valid_entry = true; @@ -6390,13 +6413,12 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, char *title, continue; // Skip non-dict items. } - const dict_T *const d = TV_LIST_ITEM_TV(li)->vval.v_dict; + dict_T *const d = TV_LIST_ITEM_TV(li)->vval.v_dict; if (d == NULL) { continue; } - retval = qf_add_entry_from_dict(qfl, d, li == tv_list_first(list), - &valid_entry); + retval = qf_add_entry_from_dict(qfl, d, li == tv_list_first(list), &valid_entry); if (retval == QF_FAIL) { break; } @@ -6734,6 +6756,27 @@ int set_errorlist(win_T *wp, list_T *list, int action, char *title, dict_T *what return retval; } +static bool mark_quickfix_user_data(qf_info_T *qi, int copyID) +{ + bool abort = false; + for (int i = 0; i < LISTCOUNT && !abort; i++) { + qf_list_T *qfl = &qi->qf_lists[i]; + if (!qfl->qf_has_user_data) { + continue; + } + qfline_T *qfp; + int j; + FOR_ALL_QFL_ITEMS(qfl, qfp, j) { + typval_T *user_data = &qfp->qf_user_data; + if (user_data != NULL && user_data->v_type != VAR_NUMBER + && user_data->v_type != VAR_STRING && user_data->v_type != VAR_FLOAT) { + abort = abort || set_ref_in_item(user_data, copyID, NULL, NULL); + } + } + } + return abort; +} + /// Mark the quickfix context and callback function as in use for all the lists /// in a quickfix stack. static bool mark_quickfix_ctx(qf_info_T *qi, int copyID) @@ -6763,6 +6806,11 @@ bool set_ref_in_quickfix(int copyID) return abort; } + abort = mark_quickfix_user_data(&ql_info, copyID); + if (abort) { + return abort; + } + abort = set_ref_in_callback(&qftf_cb, copyID, NULL, NULL); if (abort) { return abort; @@ -6774,6 +6822,11 @@ bool set_ref_in_quickfix(int copyID) if (abort) { return abort; } + + abort = mark_quickfix_user_data(win->w_llist, copyID); + if (abort) { + return abort; + } } if (IS_LL_WINDOW(win) && (win->w_llist_ref->qf_refcount == 1)) { @@ -7054,7 +7107,8 @@ static void hgr_search_file(qf_list_T *qfl, char *fname, regmatch_T *p_regmatch) NULL, // search pattern 0, // nr 1, // type - true) // valid + NULL, // user_data + true) // valid == QF_FAIL) { got_int = true; if (line != IObuff) { diff --git a/src/nvim/undo.c b/src/nvim/undo.c index b324b777a6..695cf81f73 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -93,6 +93,7 @@ #include "nvim/cursor.h" #include "nvim/drawscreen.h" #include "nvim/edit.h" +#include "nvim/eval/funcs.h" #include "nvim/eval/typval.h" #include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds_defs.h" @@ -3118,7 +3119,7 @@ bool curbufIsChanged(void) /// @param[in] first_uhp Undo blocks list to start with. /// /// @return [allocated] List with a representation of undo blocks. -static list_T *u_eval_tree(const u_header_T *const first_uhp) +static list_T *u_eval_tree(buf_T *const buf, const u_header_T *const first_uhp) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET { list_T *const list = tv_list_alloc(kListLenMayKnow); @@ -3127,10 +3128,10 @@ static list_T *u_eval_tree(const u_header_T *const first_uhp) dict_T *const dict = tv_dict_alloc(); tv_dict_add_nr(dict, S_LEN("seq"), (varnumber_T)uhp->uh_seq); tv_dict_add_nr(dict, S_LEN("time"), (varnumber_T)uhp->uh_time); - if (uhp == curbuf->b_u_newhead) { + if (uhp == buf->b_u_newhead) { tv_dict_add_nr(dict, S_LEN("newhead"), 1); } - if (uhp == curbuf->b_u_curhead) { + if (uhp == buf->b_u_curhead) { tv_dict_add_nr(dict, S_LEN("curhead"), 1); } if (uhp->uh_save_nr > 0) { @@ -3139,7 +3140,7 @@ static list_T *u_eval_tree(const u_header_T *const first_uhp) if (uhp->uh_alt_next.ptr != NULL) { // Recursive call to add alternate undo tree. - tv_dict_add_list(dict, S_LEN("alt"), u_eval_tree(uhp->uh_alt_next.ptr)); + tv_dict_add_list(dict, S_LEN("alt"), u_eval_tree(buf, uhp->uh_alt_next.ptr)); } tv_list_append_dict(list, dict); @@ -3167,21 +3168,24 @@ void f_undofile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } } -/// "undotree()" function +/// "undotree(expr)" function void f_undotree(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { + typval_T *const tv = &argvars[0]; + buf_T *const buf = tv->v_type == VAR_UNKNOWN ? curbuf : tv_get_buf_from_arg(tv); + tv_dict_alloc_ret(rettv); dict_T *dict = rettv->vval.v_dict; - tv_dict_add_nr(dict, S_LEN("synced"), (varnumber_T)curbuf->b_u_synced); - tv_dict_add_nr(dict, S_LEN("seq_last"), (varnumber_T)curbuf->b_u_seq_last); - tv_dict_add_nr(dict, S_LEN("save_last"), (varnumber_T)curbuf->b_u_save_nr_last); - tv_dict_add_nr(dict, S_LEN("seq_cur"), (varnumber_T)curbuf->b_u_seq_cur); - tv_dict_add_nr(dict, S_LEN("time_cur"), (varnumber_T)curbuf->b_u_time_cur); - tv_dict_add_nr(dict, S_LEN("save_cur"), (varnumber_T)curbuf->b_u_save_nr_cur); + tv_dict_add_nr(dict, S_LEN("synced"), (varnumber_T)buf->b_u_synced); + tv_dict_add_nr(dict, S_LEN("seq_last"), (varnumber_T)buf->b_u_seq_last); + tv_dict_add_nr(dict, S_LEN("save_last"), (varnumber_T)buf->b_u_save_nr_last); + tv_dict_add_nr(dict, S_LEN("seq_cur"), (varnumber_T)buf->b_u_seq_cur); + tv_dict_add_nr(dict, S_LEN("time_cur"), (varnumber_T)buf->b_u_time_cur); + tv_dict_add_nr(dict, S_LEN("save_cur"), (varnumber_T)buf->b_u_save_nr_cur); - tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(curbuf->b_u_oldhead)); + tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(buf, buf->b_u_oldhead)); } // Given the buffer, Return the undo header. If none is set, set one first. diff --git a/src/nvim/usercmd.c b/src/nvim/usercmd.c index 41a8ad2d97..65720342ce 100644 --- a/src/nvim/usercmd.c +++ b/src/nvim/usercmd.c @@ -876,7 +876,7 @@ int uc_add_command(char *name, size_t name_len, const char *rep, uint32_t argt, char *rep_buf = NULL; garray_T *gap; - replace_termcodes(rep, strlen(rep), &rep_buf, 0, NULL, CPO_TO_CPO_FLAGS); + replace_termcodes(rep, strlen(rep), &rep_buf, 0, 0, NULL, CPO_TO_CPO_FLAGS); if (rep_buf == NULL) { // Can't replace termcodes - try using the string as is rep_buf = xstrdup(rep); diff --git a/src/nvim/window.c b/src/nvim/window.c index d6d677de3f..c475169261 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -4943,7 +4943,6 @@ static void win_enter_ext(win_T *const wp, const int flags) if (other_buffer) { apply_autocmds(EVENT_BUFENTER, NULL, NULL, false, curbuf); } - curwin->w_last_cursormoved.lnum = 0; } maketitle(); diff --git a/test/compat.lua b/test/compat.lua deleted file mode 100644 index 2c9786d491..0000000000 --- a/test/compat.lua +++ /dev/null @@ -1,12 +0,0 @@ --- Lua 5.1 forward-compatibility layer. --- For background see https://github.com/neovim/neovim/pull/9280 --- --- Reference the lua-compat-5.2 project for hints: --- https://github.com/keplerproject/lua-compat-5.2/blob/c164c8f339b95451b572d6b4b4d11e944dc7169d/compat52/mstrict.lua --- https://github.com/keplerproject/lua-compat-5.2/blob/c164c8f339b95451b572d6b4b4d11e944dc7169d/tests/test.lua - -local lua_version = _VERSION:sub(-3) - -if lua_version >= '5.2' then - unpack = table.unpack -- luacheck: ignore 121 143 -end diff --git a/test/functional/autocmd/cursormoved_spec.lua b/test/functional/autocmd/cursormoved_spec.lua index 64b63c3205..854e14b088 100644 --- a/test/functional/autocmd/cursormoved_spec.lua +++ b/test/functional/autocmd/cursormoved_spec.lua @@ -3,7 +3,7 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local eq = helpers.eq local eval = helpers.eval -local funcs = helpers.funcs +local meths = helpers.meths local source = helpers.source local command = helpers.command @@ -12,10 +12,10 @@ describe('CursorMoved', function() it('is triggered after BufEnter when changing or splitting windows #11878 #12031', function() source([[ - call setline(1, 'foo') - let g:log = [] - autocmd BufEnter * let g:log += ['BufEnter' .. expand("<abuf>")] - autocmd CursorMoved * let g:log += ['CursorMoved' .. expand("<abuf>")] + call setline(1, 'foo') + let g:log = [] + autocmd BufEnter * let g:log += ['BufEnter' .. expand("<abuf>")] + autocmd CursorMoved * let g:log += ['CursorMoved' .. expand("<abuf>")] ]]) eq({}, eval('g:log')) command('new') @@ -24,23 +24,34 @@ describe('CursorMoved', function() eq({'BufEnter2', 'CursorMoved2', 'BufEnter1', 'CursorMoved1'}, eval('g:log')) end) + it('is not triggered by temporarily switching window', function() + source([[ + let g:cursormoved = 0 + vnew + autocmd CursorMoved * let g:cursormoved += 1 + ]]) + command('wincmd w | wincmd p') + eq(0, eval('g:cursormoved')) + end) + it("is not triggered by functions that don't change the window", function() source([[ - let g:cursormoved = 0 - let g:buf = bufnr('%') - vsplit foo - autocmd CursorMoved * let g:cursormoved += 1 - call nvim_buf_set_lines(g:buf, 0, -1, v:true, ['aaa']) + let g:cursormoved = 0 + let g:buf = bufnr('%') + vsplit foo + autocmd CursorMoved * let g:cursormoved += 1 ]]) - eq({'aaa'}, funcs.nvim_buf_get_lines(eval('g:buf'), 0, -1, true)) + meths.buf_set_lines(eval('g:buf'), 0, -1, true, {'aaa'}) + eq(0, eval('g:cursormoved')) + eq({'aaa'}, meths.buf_get_lines(eval('g:buf'), 0, -1, true)) eq(0, eval('g:cursormoved')) end) it("is not triggered by cursor movement prior to first CursorMoved instantiation", function() source([[ - let g:cursormoved = 0 - autocmd! CursorMoved - autocmd CursorMoved * let g:cursormoved += 1 + let g:cursormoved = 0 + autocmd! CursorMoved + autocmd CursorMoved * let g:cursormoved += 1 ]]) eq(0, eval('g:cursormoved')) end) diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index fb30ddebb9..94ec3d4907 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -41,6 +41,19 @@ describe('startup', function() ok(string.find(alter_slashes(meths.get_option_value('runtimepath', {})), funcs.stdpath('config'), 1, true) == nil) end) + it('prevents remote UI infinite loop', function() + clear() + local screen + screen = Screen.new(84, 3) + screen:attach() + funcs.termopen({ nvim_prog, '-u', 'NONE', '--server', eval('v:servername'), '--remote-ui' }) + screen:expect([[ + ^Cannot attach UI of :terminal child to its parent. (Unset $NVIM to skip this check) | + | + | + ]]) + end) + it('--startuptime', function() local testfile = 'Xtest_startuptime' finally(function() diff --git a/test/functional/legacy/search_spec.lua b/test/functional/legacy/search_spec.lua index 4228940eda..25620f5262 100644 --- a/test/functional/legacy/search_spec.lua +++ b/test/functional/legacy/search_spec.lua @@ -642,7 +642,7 @@ describe('search cmdline', function() end) -- oldtest: Test_incsearch_substitute_dump2() - it('detects empty pattern properly vim-patch:8.2.2295', function() + it('incsearch detects empty pattern properly vim-patch:8.2.2295', function() screen:try_resize(70, 6) exec([[ set incsearch hlsearch scrolloff=0 @@ -675,6 +675,46 @@ describe('search cmdline', function() :1,5s/\v|^ | ]]) end) + + -- oldtest: Test_incsearch_restore_view() + it('incsearch restores viewport', function() + screen:try_resize(20, 6) + exec([[ + set incsearch nohlsearch + setlocal scrolloff=0 smoothscroll + call setline(1, [join(range(25), ' '), '', '', '', '', 'xxx']) + call feedkeys("2\<C-E>", 't') + ]]) + local s = [[ + {tilde:<<<} 18 19 20 21 22 2| + ^3 24 | + | + | + | + | + ]] + screen:expect(s) + feed('/xx') + screen:expect([[ + | + | + | + | + {inc:xx}x | + /xx^ | + ]]) + feed('x') + screen:expect([[ + | + | + | + | + {inc:xxx} | + /xxx^ | + ]]) + feed('<Esc>') + screen:expect(s) + end) end) describe('Search highlight', function() diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua index da84f435c9..ae3b0483c5 100644 --- a/test/functional/treesitter/parser_spec.lua +++ b/test/functional/treesitter/parser_spec.lua @@ -1,6 +1,7 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear +local dedent = helpers.dedent local eq = helpers.eq local insert = helpers.insert local exec_lua = helpers.exec_lua @@ -8,10 +9,13 @@ local pcall_err = helpers.pcall_err local feed = helpers.feed local is_os = helpers.is_os -before_each(clear) - describe('treesitter parser API', function() - clear() + before_each(function() + clear() + exec_lua[[ + vim.g.__ts_debug = 1 + ]] + end) it('parses buffer', function() insert([[ @@ -502,22 +506,12 @@ end]] local root = parser:parse()[1]:root() parser:set_included_regions({{root:child(0)}}) parser:invalidate() - return { parser:parse()[1]:root():range() } + return { parser:parse(true)[1]:root():range() } ]] eq({0, 0, 18, 1}, res2) - local range = exec_lua [[ - local res = {} - for _, region in ipairs(parser:included_regions()) do - for _, node in ipairs(region) do - table.insert(res, {node:range()}) - end - end - return res - ]] - - eq(range, { { 0, 0, 18, 1 } }) + eq({ { { 0, 0, 0, 18, 1, 512 } } }, exec_lua [[ return parser:included_regions() ]]) local range_tbl = exec_lua [[ parser:set_included_regions { { { 0, 0, 17, 1 } } } @@ -542,7 +536,7 @@ end]] parser:set_included_regions({nodes}) - local root = parser:parse()[1]:root() + local root = parser:parse(true)[1]:root() local res = {} for i=0,(root:named_child_count() - 1) do @@ -641,6 +635,7 @@ int x = INT_MAX; parser = vim.treesitter.get_parser(0, "c", { injections = { c = "(preproc_def (preproc_arg) @c) (preproc_function_def value: (preproc_arg) @c)"}}) + parser:parse(true) ]]) eq("table", exec_lua("return type(parser:children().c)")) @@ -673,6 +668,7 @@ int x = INT_MAX; parser = vim.treesitter.get_parser(0, "c", { injections = { c = "(preproc_def (preproc_arg) @c @combined) (preproc_function_def value: (preproc_arg) @c @combined)"}}) + parser:parse(true) ]]) eq("table", exec_lua("return type(parser:children().c)")) @@ -713,6 +709,7 @@ int x = INT_MAX; injections = { c = "(preproc_def ((preproc_arg) @_c (#inject-clang! @_c)))" .. "(preproc_function_def value: ((preproc_arg) @_a (#inject-clang! @_a)))"}}) + parser:parse(true) ]=]) eq("table", exec_lua("return type(parser:children().c)")) @@ -760,6 +757,7 @@ int x = INT_MAX; parser = vim.treesitter.get_parser(0, "c", { injections = { c = "(preproc_def ((preproc_arg) @c (#offset! @c 0 2 0 -1))) (preproc_function_def value: (preproc_arg) @c)"}}) + parser:parse(true) ]]) eq("table", exec_lua("return type(parser:children().c)")) @@ -783,7 +781,7 @@ int x = INT_MAX; return list ]] - eq({ 'gsub!', 'inject-language!', 'offset!', 'set!', 'trim!' }, res_list) + eq({ 'gsub!', 'offset!', 'set!', 'trim!' }, res_list) end) end) end) @@ -800,6 +798,7 @@ int x = INT_MAX; local result = exec_lua([[ parser = vim.treesitter.get_parser(0, "c", { injections = { c = "(preproc_def (preproc_arg) @c)"}}) + parser:parse(true) local sub_tree = parser:language_for_range({1, 18, 1, 19}) @@ -951,7 +950,7 @@ int x = INT_MAX; local r = exec_lua([[ local parser = vim.treesitter.get_string_parser(..., 'lua') - parser:parse() + parser:parse(true) local ranges = {} parser:for_each_tree(function(tstree, tree) ranges[tree:lang()] = { tstree:root():range(true) } @@ -997,7 +996,7 @@ int x = INT_MAX; vimdoc = "((codeblock (language) @injection.language (code) @injection.content))" } }) - parser1:parse() + parser1:parse(true) ]] eq(0, exec_lua("return #vim.tbl_keys(parser1:children())")) @@ -1008,7 +1007,7 @@ int x = INT_MAX; vimdoc = "((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))" } }) - parser2:parse() + parser2:parse(true) ]] eq(1, exec_lua("return #vim.tbl_keys(parser2:children())")) @@ -1016,4 +1015,66 @@ int x = INT_MAX; end) + it("parsers injections incrementally", function() + insert(dedent[[ + >lua + local a = {} + < + + >lua + local b = {} + < + + >lua + local c = {} + < + + >lua + local d = {} + < + + >lua + local e = {} + < + + >lua + local f = {} + < + + >lua + local g = {} + < + ]]) + + exec_lua [[ + parser = require('vim.treesitter.languagetree').new(0, "vimdoc", { + injections = { + vimdoc = "((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))" + } + }) + ]] + + --- Do not parse injections by default + eq(0, exec_lua [[ + parser:parse() + return #vim.tbl_keys(parser:children()) + ]]) + + --- Only parse injections between lines 0, 2 + eq(1, exec_lua [[ + parser:parse({0, 2}) + return #parser:children().lua:trees() + ]]) + + eq(2, exec_lua [[ + parser:parse({2, 6}) + return #parser:children().lua:trees() + ]]) + + eq(7, exec_lua [[ + parser:parse(true) + return #parser:children().lua:trees() + ]]) + end) + end) diff --git a/test/functional/ui/linematch_spec.lua b/test/functional/ui/linematch_spec.lua index 76197bc7e0..ef47ea7ed0 100644 --- a/test/functional/ui/linematch_spec.lua +++ b/test/functional/ui/linematch_spec.lua @@ -1178,4 +1178,48 @@ describe('regressions', function() helpers.curbufmeths.set_lines(0, -1, false, { string.rep('a', 1010)..'world' }) helpers.exec 'windo diffthis' end) + + it("properly computes filler lines for hunks bigger than linematch limit", function() + clear() + feed(':set diffopt+=linematch:10<cr>') + screen = Screen.new(100, 20) + screen:attach() + local lines = {} + for i = 0, 29 do + lines[#lines + 1] = tostring(i) + end + helpers.curbufmeths.set_lines(0, -1, false, lines) + helpers.exec 'vnew' + helpers.curbufmeths.set_lines(0, -1, false, { '00', '29' }) + helpers.exec 'windo diffthis' + feed('<C-e>') + screen:expect{grid=[[ + {1: }{2:------------------------------------------------}│{1: }{3:^1 }| + {1: }{2:------------------------------------------------}│{1: }{3:2 }| + {1: }{2:------------------------------------------------}│{1: }{3:3 }| + {1: }{2:------------------------------------------------}│{1: }{3:4 }| + {1: }{2:------------------------------------------------}│{1: }{3:5 }| + {1: }{2:------------------------------------------------}│{1: }{3:6 }| + {1: }{2:------------------------------------------------}│{1: }{3:7 }| + {1: }{2:------------------------------------------------}│{1: }{3:8 }| + {1: }{2:------------------------------------------------}│{1: }{3:9 }| + {1: }{2:------------------------------------------------}│{1: }{3:10 }| + {1: }{2:------------------------------------------------}│{1: }{3:11 }| + {1: }{2:------------------------------------------------}│{1: }{3:12 }| + {1: }{2:------------------------------------------------}│{1: }{3:13 }| + {1: }{2:------------------------------------------------}│{1: }{3:14 }| + {1: }{2:------------------------------------------------}│{1: }{3:15 }| + {1: }{2:------------------------------------------------}│{1: }{3:16 }| + {1: }{2:------------------------------------------------}│{1: }{3:17 }| + {1: }29 │{1: }{3:18 }| + {4:[No Name] [+] }{5:[No Name] [+] }| + | + ]], attr_ids={ + [1] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.Grey}; + [2] = {bold = true, background = Screen.colors.LightCyan, foreground = Screen.colors.Blue1}; + [3] = {background = Screen.colors.LightBlue}; + [4] = {reverse = true}; + [5] = {reverse = true, bold = true}; + }} + end) end) diff --git a/test/helpers.lua b/test/helpers.lua index 8f06311a3c..51114611ab 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -1,4 +1,3 @@ -require('test.compat') local shared = vim local assert = require('luassert') local busted = require('busted') @@ -570,21 +569,23 @@ function module.concat_tables(...) end --- @param str string ---- @param leave_indent? boolean +--- @param leave_indent? integer --- @return string function module.dedent(str, leave_indent) -- find minimum common indent across lines - local indent = nil + local indent --- @type string? for line in str:gmatch('[^\n]+') do local line_indent = line:match('^%s+') or '' if indent == nil or #line_indent < #indent then indent = line_indent end end - if indent == nil or #indent == 0 then + + if not indent or #indent == 0 then -- no minimum common indent return str end + local left_indent = (' '):rep(leave_indent or 0) -- create a pattern for the indent indent = indent:gsub('%s', '[ \t]') diff --git a/test/old/testdir/test_maparg.vim b/test/old/testdir/test_maparg.vim index 12670671dd..1837511990 100644 --- a/test/old/testdir/test_maparg.vim +++ b/test/old/testdir/test_maparg.vim @@ -322,8 +322,32 @@ func Test_map_restore() nunmap <C-B> endfunc -" Test restoring the script context of a mapping +" Test restoring an <SID> mapping func Test_map_restore_sid() + func RestoreMap() + const d = maparg('<CR>', 'i', v:false, v:true) + iunmap <buffer> <CR> + call mapset('i', v:false, d) + endfunc + + let mapscript =<< trim [CODE] + inoremap <silent><buffer> <SID>Return <C-R>=42<CR> + inoremap <script><buffer> <CR> <CR><SID>Return + [CODE] + call writefile(mapscript, 'Xmapscript', 'D') + + new + source Xmapscript + inoremap <buffer> <C-B> <Cmd>call RestoreMap()<CR> + call feedkeys("i\<CR>\<*C-B>\<CR>", 'xt') + call assert_equal(['', '42', '42'], getline(1, '$')) + + bwipe! + delfunc RestoreMap +endfunc + +" Test restoring a mapping with a negative script ID +func Test_map_restore_negative_sid() let after =<< trim [CODE] call assert_equal("\tLast set from --cmd argument", \ execute('verbose nmap ,n')->trim()->split("\n")[-1]) diff --git a/test/old/testdir/test_normal.vim b/test/old/testdir/test_normal.vim index 23baebb78c..c8e78dcf93 100644 --- a/test/old/testdir/test_normal.vim +++ b/test/old/testdir/test_normal.vim @@ -4011,4 +4011,42 @@ func Test_normal_j_below_botline() call StopVimInTerminal(buf) endfunc +" Test for r (replace) command with CTRL_V and CTRL_Q +func Test_normal_r_ctrl_v_cmd() + new + call append(0, 'This is a simple test: abcd') + exe "norm! 1gg$r\<C-V>\<C-V>" + call assert_equal(['This is a simple test: abc', ''], getline(1,'$')) + exe "norm! 1gg$hr\<C-Q>\<C-Q>" + call assert_equal(['This is a simple test: ab', ''], getline(1,'$')) + exe "norm! 1gg$2hr\<C-V>x7e" + call assert_equal(['This is a simple test: a~', ''], getline(1,'$')) + exe "norm! 1gg$3hr\<C-Q>x7e" + call assert_equal(['This is a simple test: ~~', ''], getline(1,'$')) + + if &encoding == 'utf-8' + exe "norm! 1gg$4hr\<C-V>u20ac" + call assert_equal(['This is a simple test:€~~', ''], getline(1,'$')) + exe "norm! 1gg$5hr\<C-Q>u20ac" + call assert_equal(['This is a simple test€€~~', ''], getline(1,'$')) + exe "norm! 1gg0R\<C-V>xff WAS \<esc>" + call assert_equal(['ÿ WAS a simple test€€~~', ''], getline(1,'$')) + exe "norm! 1gg0elR\<C-Q>xffNOT\<esc>" + call assert_equal(['ÿ WASÿNOT simple test€€~~', ''], getline(1,'$')) + endif + + call setline(1, 'This is a simple test: abcd') + exe "norm! 1gg$gr\<C-V>\<C-V>" + call assert_equal(['This is a simple test: abc', ''], getline(1,'$')) + exe "norm! 1gg$hgr\<C-Q>\<C-Q>" + call assert_equal(['This is a simple test: ab ', ''], getline(1,'$')) + exe "norm! 1gg$2hgr\<C-V>x7e" + call assert_equal(['This is a simple test: a~ ', ''], getline(1,'$')) + exe "norm! 1gg$3hgr\<C-Q>x7e" + call assert_equal(['This is a simple test: ~~ ', ''], getline(1,'$')) + + " clean up + bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_quickfix.vim b/test/old/testdir/test_quickfix.vim index 6378ee8770..e61da49584 100644 --- a/test/old/testdir/test_quickfix.vim +++ b/test/old/testdir/test_quickfix.vim @@ -1649,13 +1649,23 @@ func SetXlistTests(cchar, bnum) call s:setup_commands(a:cchar) call g:Xsetlist([{'bufnr': a:bnum, 'lnum': 1}, - \ {'bufnr': a:bnum, 'lnum': 2, 'end_lnum': 3, 'col': 4, 'end_col': 5}]) + \ {'bufnr': a:bnum, 'lnum': 2, 'end_lnum': 3, 'col': 4, 'end_col': 5, 'user_data': {'6': [7, 8]}}]) let l = g:Xgetlist() call assert_equal(2, len(l)) call assert_equal(2, l[1].lnum) call assert_equal(3, l[1].end_lnum) call assert_equal(4, l[1].col) call assert_equal(5, l[1].end_col) + call assert_equal({'6': [7, 8]}, l[1].user_data) + + " Test that user_data is garbage collected + call g:Xsetlist([{'user_data': ['high', 5]}, + \ {'user_data': {'this': [7, 'eight'], 'is': ['a', 'dictionary']}}]) + call test_garbagecollect_now() + let l = g:Xgetlist() + call assert_equal(2, len(l)) + call assert_equal(['high', 5], l[0].user_data) + call assert_equal({'this': [7, 'eight'], 'is': ['a', 'dictionary']}, l[1].user_data) Xnext call g:Xsetlist([{'bufnr': a:bnum, 'lnum': 3}], 'a') diff --git a/test/old/testdir/test_registers.vim b/test/old/testdir/test_registers.vim index 70dac535b4..01f9507916 100644 --- a/test/old/testdir/test_registers.vim +++ b/test/old/testdir/test_registers.vim @@ -758,8 +758,9 @@ func Test_record_in_select_mode() bwipe! endfunc -" mapping that ends macro recording should be removed from recorded macro +" A mapping that ends recording should be removed from the recorded register. func Test_end_record_using_mapping() + new call setline(1, 'aaa') nnoremap s q call feedkeys('safas', 'tx') @@ -779,7 +780,10 @@ func Test_end_record_using_mapping() bwipe! endfunc +" Starting a new recording should work immediately after replaying a recording +" that ends with a <Nop> mapping or a character search. func Test_end_reg_executing() + new nnoremap s <Nop> let @a = 's' call feedkeys("@aqaq\<Esc>", 'tx') @@ -797,5 +801,25 @@ func Test_end_reg_executing() bwipe! endfunc +" An operator-pending mode mapping shouldn't be applied to keys typed in +" Insert mode immediately after a character search when replaying. +func Test_replay_charsearch_omap() + CheckFeature timers + + new + call setline(1, 'foo[blah]') + onoremap , k + call timer_start(10, {-> feedkeys(",bar\<Esc>q", 't')}) + call feedkeys('qrct[', 'xt!') + call assert_equal(',bar[blah]', getline(1)) + undo + call assert_equal('foo[blah]', getline(1)) + call feedkeys('@r', 'xt!') + call assert_equal(',bar[blah]', getline(1)) + + ounmap , + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_search.vim b/test/old/testdir/test_search.vim index 37909d0afe..4a92ae34e4 100644 --- a/test/old/testdir/test_search.vim +++ b/test/old/testdir/test_search.vim @@ -1996,7 +1996,7 @@ func Test_incsearch_substitute_dump2() \ 'endfor', \ 'call setline(5, "abc|def")', \ '3', - \ ], 'Xis_subst_script2') + \ ], 'Xis_subst_script2', 'D') let buf = RunVimInTerminal('-S Xis_subst_script2', {'rows': 9, 'cols': 70}) call term_sendkeys(buf, ':%s/\vabc|') @@ -2011,7 +2011,30 @@ func Test_incsearch_substitute_dump2() call StopVimInTerminal(buf) - call delete('Xis_subst_script2') +endfunc + +func Test_incsearch_restore_view() + CheckOption incsearch + CheckScreendump + + let lines =<< trim [CODE] + set incsearch nohlsearch + setlocal scrolloff=0 smoothscroll + call setline(1, [join(range(25), ' '), '', '', '', '', 'xxx']) + call feedkeys("2\<C-E>", 't') + [CODE] + call writefile(lines, 'Xincsearch_restore_view', 'D') + let buf = RunVimInTerminal('-S Xincsearch_restore_view', {'rows': 6, 'cols': 20}) + + call VerifyScreenDump(buf, 'Test_incsearch_restore_view_01', {}) + call term_sendkeys(buf, '/xx') + call VerifyScreenDump(buf, 'Test_incsearch_restore_view_02', {}) + call term_sendkeys(buf, 'x') + call VerifyScreenDump(buf, 'Test_incsearch_restore_view_03', {}) + call term_sendkeys(buf, "\<Esc>") + call VerifyScreenDump(buf, 'Test_incsearch_restore_view_01', {}) + + call StopVimInTerminal(buf) endfunc func Test_pattern_is_uppercase_smartcase() diff --git a/test/old/testdir/test_undo.vim b/test/old/testdir/test_undo.vim index 4678a51d60..08a0ba4c39 100644 --- a/test/old/testdir/test_undo.vim +++ b/test/old/testdir/test_undo.vim @@ -93,6 +93,53 @@ func FillBuffer() endfor endfunc +func Test_undotree_bufnr() + new + let buf1 = bufnr() + + normal! Aabc + set ul=100 + + " Save undo tree without bufnr as ground truth for buffer 1 + let d1 = undotree() + + new + let buf2 = bufnr() + + normal! Adef + set ul=100 + + normal! Aghi + set ul=100 + + " Save undo tree without bufnr as ground truth for buffer 2 + let d2 = undotree() + + " Check undotree() with bufnr argument + let d = undotree(buf1) + call assert_equal(d1, d) + call assert_notequal(d2, d) + + let d = undotree(buf2) + call assert_notequal(d1, d) + call assert_equal(d2, d) + + " Switch buffers and check again + wincmd p + + let d = undotree(buf1) + call assert_equal(d1, d) + + let d = undotree(buf2) + call assert_notequal(d1, d) + call assert_equal(d2, d) + + " Drop created windows + set ul& + new + only! +endfunc + func Test_global_local_undolevels() new one set undolevels=5 |