aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cirrus.yml1
-rw-r--r--cmake.deps/deps.txt4
-rw-r--r--runtime/autoload/rust.vim2
-rw-r--r--runtime/doc/builtin.txt13
-rw-r--r--runtime/doc/news.txt25
-rw-r--r--runtime/doc/treesitter.txt45
-rw-r--r--runtime/doc/usr_41.txt2
-rw-r--r--runtime/doc/vim_diff.txt2
-rw-r--r--runtime/ftplugin/nix.vim17
-rw-r--r--runtime/ftplugin/pymanifest.vim13
-rw-r--r--runtime/ftplugin/rust.vim2
-rw-r--r--runtime/ftplugin/toml.vim2
-rw-r--r--runtime/lua/vim/_meta/vimfn.lua14
-rw-r--r--runtime/lua/vim/lsp.lua2
-rw-r--r--runtime/lua/vim/treesitter/_fold.lua7
-rw-r--r--runtime/lua/vim/treesitter/_meta.lua7
-rw-r--r--runtime/lua/vim/treesitter/_range.lua9
-rw-r--r--runtime/lua/vim/treesitter/dev.lua2
-rw-r--r--runtime/lua/vim/treesitter/highlighter.lua24
-rw-r--r--runtime/lua/vim/treesitter/languagetree.lua238
-rw-r--r--runtime/lua/vim/treesitter/query.lua28
-rw-r--r--runtime/queries/markdown/injections.scm5
-rw-r--r--runtime/syntax/dosini.vim6
-rw-r--r--runtime/syntax/iss.vim56
-rw-r--r--runtime/syntax/pymanifest.vim44
-rwxr-xr-xscripts/vim-patch.sh42
-rw-r--r--src/nvim/api/vim.c2
-rw-r--r--src/nvim/autocmd.c3
-rw-r--r--src/nvim/autocmd.h5
-rw-r--r--src/nvim/buffer_defs.h1
-rw-r--r--src/nvim/change.c5
-rw-r--r--src/nvim/diff.c2
-rw-r--r--src/nvim/edit.c6
-rw-r--r--src/nvim/eval.lua17
-rw-r--r--src/nvim/ex_getln.c3
-rw-r--r--src/nvim/getchar.c14
-rw-r--r--src/nvim/keycodes.c10
-rw-r--r--src/nvim/lua/executor.c3
-rw-r--r--src/nvim/lua/treesitter.c26
-rw-r--r--src/nvim/main.c5
-rw-r--r--src/nvim/mapping.c37
-rw-r--r--src/nvim/menu.c2
-rw-r--r--src/nvim/normal.c52
-rw-r--r--src/nvim/quickfix.c106
-rw-r--r--src/nvim/undo.c28
-rw-r--r--src/nvim/usercmd.c2
-rw-r--r--src/nvim/window.c1
-rw-r--r--test/compat.lua12
-rw-r--r--test/functional/autocmd/cursormoved_spec.lua39
-rw-r--r--test/functional/core/startup_spec.lua13
-rw-r--r--test/functional/legacy/search_spec.lua42
-rw-r--r--test/functional/treesitter/parser_spec.lua101
-rw-r--r--test/functional/ui/linematch_spec.lua44
-rw-r--r--test/helpers.lua9
-rw-r--r--test/old/testdir/test_maparg.vim26
-rw-r--r--test/old/testdir/test_normal.vim38
-rw-r--r--test/old/testdir/test_quickfix.vim12
-rw-r--r--test/old/testdir/test_registers.vim26
-rw-r--r--test/old/testdir/test_search.vim27
-rw-r--r--test/old/testdir/test_undo.vim47
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