Compare commits

...

12 Commits

Author SHA1 Message Date
marvim
047da25d1c docs: update version.c 2025-08-30 03:18:19 +00:00
zeertzjq
4edeaaa6e2 vim-patch:partial:9.1.1708: tests: various tests can be improved (#35548)
Problem:  tests: various tests can be improved
Solution: Use string interpolation to concatenate strings in
          test_winfixbuf, check for specific errors in assert_fails()
          (Yegappan Lakshmanan)

closes: vim/vim#18151

97ea879b9b

Cherry-pick Test_file_perm.vim changes from patch 9.0.{0363,0611}.
Skip Test_colornames_assignment_and_unassignment().

Co-authored-by: Yegappan Lakshmanan <yegappan@yahoo.com>
2025-08-30 08:55:48 +08:00
zeertzjq
6eebf30a39 vim-patch:7239d95: runtime(vim): Update base syntax, match :defer command argument (#35547)
closes: vim/vim#18159

7239d95bf2

Co-authored-by: Doug Kearns <dougkearns@gmail.com>
2025-08-30 08:15:01 +08:00
zeertzjq
8a2587be23 Merge pull request #35546 from zeertzjq/vim-a07a2f4
vim-patch:a07a2f4: runtime(astro): catch json_decode() error when parsing tsconfig.json
2025-08-30 08:14:15 +08:00
zeertzjq
834e1181da Merge pull request #35545 from zeertzjq/vim-9.1.1687
vim-patch:9.1.{1687,1693,1697,1713}: m4 ft detection updates
2025-08-30 07:22:13 +08:00
zeertzjq
68a2e0ef78 vim-patch:9.1.1713: filetype: fvwm2m4 files are no longer detected
Problem:  filetype: fvwm2m4 files are no longer recognized
          (after 9.1.1687).
Solution: Add a special case in m4 filetype detection (zeertzjq).

closes: vim/vim#18146

5355e81868

Co-authored-by: Damien Lejay <damien@lejay.be>
2025-08-30 06:57:09 +08:00
zeertzjq
61712cbc3a vim-patch:9.1.1697: tests: no test for aclocal.m4
Problem:  tests: no test for aclocal.m4
          (after v9.1.1693)
Solution: Add a test that aclocal.m4 is detected as config filetype

related: vim/vim#18065

f5670a1596

Co-authored-by: Christian Brabandt <cb@256bit.org>
2025-08-30 06:57:09 +08:00
zeertzjq
8a1afac653 vim-patch:9.1.1693: tests: test_filetype fails in shadow dir
Problem:  tests: test_filetype fails in shadow dir
          (after v9.1.9.1.1687)
Solution: Use a custom test that does not rely on configure.ac
          being existing in the upper directory tree.

de6e560150

Co-authored-by: Christian Brabandt <cb@256bit.org>
2025-08-30 06:57:09 +08:00
zeertzjq
dab31a3637 vim-patch:9.1.1687: filetype: autoconf filetype not always correct
Problem:  filetype: autoconf filetype not always correct
Solution: Detect aclocal.m4 as config filetype, detect configure.ac as
          config filetype, fall back to POSIX m4 (Damien Lejay).

closes: vim/vim#18065

2b55474f0a

Co-authored-by: Damien Lejay <damien@lejay.be>
2025-08-30 06:57:08 +08:00
bfredl
772f1966a3 Merge pull request #31400 from vanaigr/decor-provider-range
feat(decor): add range-based highlighting
2025-08-29 10:33:15 +02:00
vanaigr
5edbabdbec perf: add on_range in treesitter highlighting 2025-08-28 08:22:38 -05:00
vanaigr
118e7e7111 test: add treesitter long lines benchmark 2025-08-28 08:22:38 -05:00
34 changed files with 1124 additions and 392 deletions

View File

@@ -3290,11 +3290,12 @@ nvim_set_decoration_provider({ns_id}, {opts})
Note: this function should not be called often. Rather, the callbacks
themselves can be used to throttle unneeded callbacks. the `on_start`
callback can return `false` to disable the provider until the next redraw.
Similarly, return `false` in `on_win` will skip the `on_line` calls for
that window (but any extmarks set in `on_win` will still be used). A
plugin managing multiple sources of decoration should ideally only set one
provider, and merge the sources internally. You can use multiple `ns_id`
for the extmarks set/modified inside the callback anyway.
Similarly, return `false` in `on_win` will skip the `on_line` and
`on_range` calls for that window (but any extmarks set in `on_win` will
still be used). A plugin managing multiple sources of decoration should
ideally only set one provider, and merge the sources internally. You can
use multiple `ns_id` for the extmarks set/modified inside the callback
anyway.
Note: doing anything other than setting extmarks is considered
experimental. Doing things like changing options are not explicitly
@@ -3302,8 +3303,8 @@ nvim_set_decoration_provider({ns_id}, {opts})
consumption). Doing `vim.rpcnotify` should be OK, but `vim.rpcrequest` is
quite dubious for the moment.
Note: It is not allowed to remove or update extmarks in `on_line`
callbacks.
Note: It is not allowed to remove or update extmarks in `on_line` or
`on_range` callbacks.
Attributes: ~
Lua |vim.api| only
@@ -3326,6 +3327,14 @@ nvim_set_decoration_provider({ns_id}, {opts})
• on_line: called for each buffer line being redrawn. (The
interaction with fold lines is subject to change) >
["line", winid, bufnr, row]
<
• on_range: called for each buffer range being redrawn. Range
is end-exclusive and may span multiple lines. Range bounds
point to the first byte of a character. An end position of
the form (lnum, 0), including (number of lines, 0), is
valid and indicates that EOL of the preceding line is
included. >
["range", winid, bufnr, begin_row, begin_col, end_row, end_col]
<
• on_end: called at the end of a redraw cycle >
["end", tick]

View File

@@ -51,7 +51,7 @@ These changes may require adaptations in your config or plugins.
API
todo
Decoration provider has `on_range()` callback.
BUILD
@@ -118,6 +118,7 @@ TREESITTER
`metadata[capture_id].offset`. The offset will be applied in
|vim.treesitter.get_range()|, which should be preferred over reading
metadata directly for retrieving node ranges.
• |Query:iter_captures()| supports specifying starting and ending columns.
TUI

View File

@@ -1522,7 +1522,7 @@ them to parse text. See |vim.treesitter.query.parse()| for a working example.
• {has_combined_injections} (`boolean`) whether the query contains
combined injections
• {query} (`TSQuery`) userdata query object
• {iter_captures} (`fun(self: vim.treesitter.Query, node: TSNode, source: integer|string, start: integer?, stop: integer?, opts: table?): fun(end_line: integer?): integer, TSNode, vim.treesitter.query.TSMetadata, TSQueryMatch, TSTree`)
• {iter_captures} (`fun(self: vim.treesitter.Query, node: TSNode, source: integer|string, start_row: integer?, end_row: integer?, opts: table?): fun(end_line: integer?, end_col: integer?): integer, TSNode, vim.treesitter.query.TSMetadata, TSQueryMatch, TSTree`)
See |Query:iter_captures()|.
• {iter_matches} (`fun(self: vim.treesitter.Query, node: TSNode, source: integer|string, start: integer?, stop: integer?, opts: table?): fun(): integer, table<integer, TSNode[]>, vim.treesitter.query.TSMetadata, TSTree`)
See |Query:iter_matches()|.
@@ -1693,7 +1693,7 @@ parse({lang}, {query}) *vim.treesitter.query.parse()*
• |vim.treesitter.query.get()|
*Query:iter_captures()*
Query:iter_captures({node}, {source}, {start}, {stop}, {opts})
Query:iter_captures({node}, {source}, {start_row}, {end_row}, {opts})
Iterates over all captures from all matches in {node}.
{source} is required if the query contains predicates; then the caller
@@ -1725,22 +1725,26 @@ Query:iter_captures({node}, {source}, {start}, {stop}, {opts})
contained predicates.
Parameters: ~
• {node} (`TSNode`) under which the search will occur
• {source} (`integer|string`) Source buffer or string to extract text
from
• {start} (`integer?`) Starting line for the search. Defaults to
`node:start()`.
• {stop} (`integer?`) Stopping line for the search (end-exclusive).
Defaults to `node:end_()`.
• {opts} (`table?`) Optional keyword arguments:
• max_start_depth (integer) if non-zero, sets the maximum
start depth for each match. This is used to prevent
traversing too deep into a tree.
• match_limit (integer) Set the maximum number of
in-progress matches (Default: 256).
• {node} (`TSNode`) under which the search will occur
• {source} (`integer|string`) Source buffer or string to extract
text from
• {start_row} (`integer?`) Starting line for the search. Defaults to
`node:start()`.
• {end_row} (`integer?`) Stopping line for the search (end-inclusive,
unless `stop_col` is provided). Defaults to
`node:end_()`.
• {opts} (`table?`) Optional keyword arguments:
• max_start_depth (integer) if non-zero, sets the maximum
start depth for each match. This is used to prevent
traversing too deep into a tree.
• match_limit (integer) Set the maximum number of
in-progress matches (Default: 256).
• start_col (integer) Starting column for the search.
• end_col (integer) Stopping column for the search
(end-exclusive).
Return: ~
(`fun(end_line: integer?): integer, TSNode, vim.treesitter.query.TSMetadata, TSQueryMatch, TSTree`)
(`fun(end_line: integer?, end_col: integer?): integer, TSNode, vim.treesitter.query.TSMetadata, TSQueryMatch, TSTree`)
capture id, capture node, metadata, match, tree
*Query:iter_matches()*

View File

@@ -3,6 +3,7 @@
" Maintainer: Romain Lafourcade <romainlafourcade@gmail.com>
" Last Change: 2024 Apr 21
" 2024 May 24 by Riley Bruins <ribru17@gmail.com> ('commentstring')
" 2025 Aug 29 by Vim project, add try/catch around json_decode(), #18141
if exists("b:did_ftplugin")
finish
@@ -52,13 +53,19 @@ function! s:CollectPathsFromConfig() abort
endif
endif
let paths_from_config = config_json
try
let paths_from_config = config_json
\ ->readfile()
\ ->filter({ _, val -> val =~ '^\s*[\[\]{}"0-9]' })
\ ->join()
\ ->json_decode()
\ ->get('compilerOptions', {})
\ ->get('paths', {})
catch /^Vim\%((\a\+)\)\=:E491:/ " invalid json
let paths_from_config = {}
catch /^Vim\%((\a\+)\)\=:E474:/ " invalid json in Nvim
let paths_from_config = {}
endtry
if !empty(paths_from_config)
let b:astro_paths = paths_from_config

View File

@@ -2128,7 +2128,7 @@ function vim.api.nvim_set_current_win(window) end
--- Note: this function should not be called often. Rather, the callbacks
--- themselves can be used to throttle unneeded callbacks. the `on_start`
--- callback can return `false` to disable the provider until the next redraw.
--- Similarly, return `false` in `on_win` will skip the `on_line` calls
--- Similarly, return `false` in `on_win` will skip the `on_line` and `on_range` calls
--- for that window (but any extmarks set in `on_win` will still be used).
--- A plugin managing multiple sources of decoration should ideally only set
--- one provider, and merge the sources internally. You can use multiple `ns_id`
@@ -2140,7 +2140,7 @@ function vim.api.nvim_set_current_win(window) end
--- Doing `vim.rpcnotify` should be OK, but `vim.rpcrequest` is quite dubious
--- for the moment.
---
--- Note: It is not allowed to remove or update extmarks in `on_line` callbacks.
--- Note: It is not allowed to remove or update extmarks in `on_line` or `on_range` callbacks.
---
--- @param ns_id integer Namespace id from `nvim_create_namespace()`
--- @param opts vim.api.keyset.set_decoration_provider Table of callbacks:
@@ -2162,6 +2162,14 @@ function vim.api.nvim_set_current_win(window) end
--- ```
--- ["line", winid, bufnr, row]
--- ```
--- - on_range: called for each buffer range being redrawn.
--- Range is end-exclusive and may span multiple lines. Range
--- bounds point to the first byte of a character. An end position
--- of the form (lnum, 0), including (number of lines, 0), is valid
--- and indicates that EOL of the preceding line is included.
--- ```
--- ["range", winid, bufnr, begin_row, begin_col, end_row, end_col]
--- ```
--- - on_end: called at the end of a redraw cycle
--- ```
--- ["end", tick]

View File

@@ -382,6 +382,7 @@ error('Cannot require a meta file')
--- @field on_buf? fun(_: "buf", bufnr: integer, tick: integer)
--- @field on_win? fun(_: "win", winid: integer, bufnr: integer, toprow: integer, botrow: integer): boolean?
--- @field on_line? fun(_: "line", winid: integer, bufnr: integer, row: integer): boolean?
--- @field on_range? fun(_: "range", winid: integer, bufnr: integer, start_row: integer, start_col: integer, end_row: integer, end_col: integer): boolean?
--- @field on_end? fun(_: "end", tick: integer)
--- @field _on_hl_def? fun(_: "hl_def")
--- @field _on_spell_nav? fun(_: "spell_nav")

View File

@@ -734,13 +734,10 @@ local extension = {
luau = 'luau',
lrc = 'lyrics',
m = detect.m,
at = 'm4',
at = 'config',
mc = detect.mc,
quake = 'm3quake',
m4 = function(path, _bufnr)
local pathl = path:lower()
return not (pathl:find('html%.m4$') or pathl:find('fvwm2rc')) and 'm4' or nil
end,
m4 = detect.m4,
eml = 'mail',
mk = detect.make,
mak = detect.make,
@@ -2493,7 +2490,6 @@ local pattern = {
end
end,
['^hg%-editor%-.*%.txt$'] = 'hgcommit',
['%.html%.m4$'] = 'htmlm4',
['^JAM.*%.'] = starsetf('jam'),
['^Prl.*%.'] = starsetf('jam'),
['^${HOME}/.*/Code/User/.*%.json$'] = 'jsonc',

View File

@@ -688,10 +688,7 @@ function M.fvwm_v1(_, _)
end
--- @type vim.filetype.mapfn
function M.fvwm_v2(path, _)
if fn.fnamemodify(path, ':e') == 'm4' then
return 'fvwm2m4'
end
function M.fvwm_v2(_, _)
return 'fvwm', function(bufnr)
vim.b[bufnr].fvwm_version = 2
end
@@ -1026,6 +1023,50 @@ function M.m(_, bufnr)
end
end
--- For files ending in *.m4, distinguish:
--- *.html.m4 files
--- - *fvwm2rc*.m4 files
--- files in the Autoconf M4 dialect
--- files in POSIX M4
--- @type vim.filetype.mapfn
function M.m4(path, bufnr)
local fname = fn.fnamemodify(path, ':t')
path = fn.fnamemodify(path, ':p:h')
if fname:find('html%.m4$') then
return 'htmlm4'
end
if fname:find('fvwm2rc') then
return 'fvwm2m4'
end
-- Canonical Autoconf file
if fname == 'aclocal.m4' then
return 'config'
end
-- Repo heuristic for Autoconf M4 (nearby configure.ac)
if
fn.filereadable(path .. '/../configure.ac') ~= 0
or fn.filereadable(path .. '/configure.ac') ~= 0
then
return 'config'
end
-- Content heuristic for Autoconf M4 (scan first ~200 lines)
-- Signals:
-- - Autoconf macro prefixes: AC_/AM_/AS_/AU_/AT_
for _, line in ipairs(getlines(bufnr, 1, 200)) do
if line:find('^%s*A[CMSUT]_') then
return 'config'
end
end
-- Default to POSIX M4
return 'm4'
end
--- @param contents string[]
--- @return string?
local function m4(contents)

View File

@@ -80,8 +80,6 @@ function TSQueryCursor:next_match() end
--- @param node TSNode
--- @param query TSQuery
--- @param start integer?
--- @param stop integer?
--- @param opts? { max_start_depth?: integer, match_limit?: integer}
--- @param opts? { start_row: integer, start_col: integer, end_row: integer, end_col: integer, max_start_depth?: integer, match_limit?: integer }
--- @return TSQueryCursor
function vim._create_ts_querycursor(node, query, start, stop, opts) end
function vim._create_ts_querycursor(node, query, opts) end

View File

@@ -1,10 +1,11 @@
local api = vim.api
local query = vim.treesitter.query
local Range = require('vim.treesitter._range')
local cmp_lt = Range.cmp_pos.lt
local ns = api.nvim_create_namespace('nvim.treesitter.highlighter')
---@alias vim.treesitter.highlighter.Iter fun(end_line: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata, TSQueryMatch
---@alias vim.treesitter.highlighter.Iter fun(end_line: integer|nil, end_col: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata, TSQueryMatch, TSTree
---@class (private) vim.treesitter.highlighter.Query
---@field private _query vim.treesitter.Query?
@@ -57,6 +58,7 @@ end
---@class (private) vim.treesitter.highlighter.State
---@field tstree TSTree
---@field next_row integer
---@field next_col integer
---@field iter vim.treesitter.highlighter.Iter?
---@field highlighter_query vim.treesitter.highlighter.Query
---@field prev_marks MarkInfo[]
@@ -233,6 +235,7 @@ function TSHighlighter:prepare_highlight_states(win, srow, erow)
table.insert(self._highlight_states[win], {
tstree = tstree,
next_row = 0,
next_col = 0,
iter = nil,
highlighter_query = hl_query,
prev_marks = {},
@@ -331,27 +334,36 @@ end
---Queues the remainder if the mark continues after the line.
---@param m MarkInfo
---@param buf integer
---@param line integer
---@param range_start_row integer
---@param range_start_col integer
---@param range_end_row integer
---@param range_end_col integer
---@param next_marks MarkInfo[]
local function add_mark(m, buf, line, next_marks)
local function add_mark(
m,
buf,
range_start_row,
range_start_col,
range_end_row,
range_end_col,
next_marks
)
local cur_start_l = m.start_line
local cur_start_c = m.start_col
if cur_start_l < line then
cur_start_l = line
cur_start_c = 0
if cmp_lt(cur_start_l, cur_start_c, range_start_row, range_start_col) then
cur_start_l = range_start_row
cur_start_c = range_start_col
end
local cur_opts = m.opts
if cur_opts.end_line >= line + 1 then
if cmp_lt(range_end_row, range_end_col, cur_opts.end_line, cur_opts.end_col) then
cur_opts = vim.deepcopy(cur_opts, true)
cur_opts.end_line = line + 1
cur_opts.end_col = 0
cur_opts.end_line = range_end_row
cur_opts.end_col = range_end_col
table.insert(next_marks, m)
end
local empty = cur_opts.end_line < cur_start_l
or (cur_opts.end_line == cur_start_l and cur_opts.end_col <= cur_start_c)
if cur_start_l <= line and not empty then
if cmp_lt(cur_start_l, cur_start_c, cur_opts.end_line, cur_opts.end_col) then
api.nvim_buf_set_extmark(buf, ns, cur_start_l, cur_start_c, cur_opts)
end
end
@@ -359,17 +371,44 @@ end
---@param self vim.treesitter.highlighter
---@param win integer
---@param buf integer
---@param line integer
---@param range_start_row integer
---@param range_start_col integer
---@param range_end_row integer
---@param range_end_col integer
---@param on_spell boolean
---@param on_conceal boolean
local function on_line_impl(self, win, buf, line, on_spell, on_conceal)
self._conceal_checked[line] = self._conceal_line and true or nil
local function on_range_impl(
self,
win,
buf,
range_start_row,
range_start_col,
range_end_row,
range_end_col,
on_spell,
on_conceal
)
if self._conceal_line then
range_start_col = 0
if range_end_col ~= 0 then
range_end_row = range_end_row + 1
range_end_col = 0
end
end
for i = range_start_row, range_end_row - 1 do
self._conceal_checked[i] = self._conceal_line or nil
end
self:for_each_highlight_state(win, function(state)
local root_node = state.tstree:root()
local root_start_row, _, root_end_row, _ = root_node:range()
---@type { [1]: integer, [2]: integer, [3]: integer, [4]: integer }
local root_range = { root_node:range() }
-- Only consider trees that contain this line
if root_start_row > line or root_end_row < line then
if
not Range.intercepts(
root_range,
{ range_start_row, range_start_col, range_end_row, range_end_col }
)
then
return
end
@@ -378,85 +417,114 @@ local function on_line_impl(self, win, buf, line, on_spell, on_conceal)
local next_marks = {}
for _, mark in ipairs(state.prev_marks) do
add_mark(mark, buf, line, next_marks)
add_mark(
mark,
buf,
range_start_row,
range_start_col,
range_end_row,
range_end_col,
next_marks
)
end
if state.iter == nil or state.next_row < line then
local next_row = state.next_row
local next_col = state.next_col
if state.iter == nil or cmp_lt(next_row, next_col, range_start_row, range_start_col) then
-- Mainly used to skip over folds
-- TODO(lewis6991): Creating a new iterator loses the cached predicate results for query
-- matches. Move this logic inside iter_captures() so we can maintain the cache.
state.iter =
state.highlighter_query:query():iter_captures(root_node, self.bufnr, line, root_end_row + 1)
state.iter = state.highlighter_query:query():iter_captures(
root_node,
self.bufnr,
range_start_row,
root_range[3],
{ start_col = range_start_col, end_col = root_range[4] }
)
end
local captures = state.highlighter_query:query().captures
while line >= state.next_row do
local capture, node, metadata, match = state.iter(line)
local outer_range = { root_end_row + 1, 0, root_end_row + 1, 0 }
if node then
outer_range = vim.treesitter.get_range(node, buf, metadata and metadata[capture])
while cmp_lt(next_row, next_col, range_end_row, range_end_col) do
local capture, node, metadata, match = state.iter(range_end_row, range_end_col)
if not node then
next_row = math.huge
next_col = math.huge
break
end
local outer_range = vim.treesitter.get_range(node, buf, metadata and metadata[capture])
if cmp_lt(next_row, next_col, outer_range[1], outer_range[2]) then
next_row = outer_range[1]
next_col = outer_range[2]
end
if not capture then
break
end
local outer_range_start_row = outer_range[1]
for _, range in ipairs(tree_region) do
local intersection = Range.intersection(range, outer_range)
if intersection then
local start_row, start_col, end_row, end_col = Range.unpack4(intersection)
if capture then
local hl = state.highlighter_query:get_hl_from_capture(capture)
local hl = state.highlighter_query:get_hl_from_capture(capture)
local capture_name = captures[capture]
local capture_name = captures[capture]
local spell, spell_pri_offset = get_spell(capture_name)
local spell, spell_pri_offset = get_spell(capture_name)
-- The "priority" attribute can be set at the pattern level or on a particular capture
local priority = (
tonumber(metadata.priority or metadata[capture] and metadata[capture].priority)
or vim.hl.priorities.treesitter
) + spell_pri_offset
-- The "priority" attribute can be set at the pattern level or on a particular capture
local priority = (
tonumber(metadata.priority or metadata[capture] and metadata[capture].priority)
or vim.hl.priorities.treesitter
) + spell_pri_offset
-- The "conceal" attribute can be set at the pattern level or on a particular capture
local conceal = metadata.conceal or metadata[capture] and metadata[capture].conceal
-- The "conceal" attribute can be set at the pattern level or on a particular capture
local conceal = metadata.conceal or metadata[capture] and metadata[capture].conceal
local url = get_url(match, buf, capture, metadata)
local url = get_url(match, buf, capture, metadata)
if hl and end_row >= line and not on_conceal and (not on_spell or spell ~= nil) then
local opts = {
end_line = end_row,
end_col = end_col,
hl_group = hl,
ephemeral = true,
priority = priority,
conceal = conceal,
spell = spell,
url = url,
}
local mark = { start_line = start_row, start_col = start_col, opts = opts }
add_mark(mark, buf, line, next_marks)
end
if hl and not on_conceal and (not on_spell or spell ~= nil) then
local opts = {
end_line = end_row,
end_col = end_col,
hl_group = hl,
ephemeral = true,
priority = priority,
conceal = conceal,
spell = spell,
url = url,
}
local mark = { start_line = start_row, start_col = start_col, opts = opts }
add_mark(
mark,
buf,
range_start_row,
range_start_col,
range_end_row,
range_end_col,
next_marks
)
end
if
(metadata.conceal_lines or metadata[capture] and metadata[capture].conceal_lines)
and #api.nvim_buf_get_extmarks(buf, ns, { start_row, 0 }, { start_row, 0 }, {}) == 0
then
api.nvim_buf_set_extmark(buf, ns, start_row, 0, {
end_line = end_row,
conceal_lines = '',
})
end
if
(metadata.conceal_lines or metadata[capture] and metadata[capture].conceal_lines)
and #api.nvim_buf_get_extmarks(buf, ns, { start_row, 0 }, { start_row, 0 }, {}) == 0
then
api.nvim_buf_set_extmark(buf, ns, start_row, 0, {
end_line = end_row,
conceal_lines = '',
})
end
end
end
if outer_range_start_row > line then
state.next_row = outer_range_start_row
end
end
state.next_row = next_row
state.next_col = next_col
state.prev_marks = next_marks
end)
end
@@ -464,14 +532,17 @@ end
---@private
---@param win integer
---@param buf integer
---@param line integer
function TSHighlighter._on_line(_, win, buf, line, _)
---@param br integer
---@param bc integer
---@param er integer
---@param ec integer
function TSHighlighter._on_range(_, win, buf, br, bc, er, ec, _)
local self = TSHighlighter.active[buf]
if not self then
return
end
on_line_impl(self, win, buf, line, false, false)
on_range_impl(self, win, buf, br, bc, er, ec, false, false)
end
---@private
@@ -490,9 +561,7 @@ function TSHighlighter._on_spell_nav(_, win, buf, srow, _, erow, _)
local highlight_states = self._highlight_states[win]
self:prepare_highlight_states(win, srow, erow)
for row = srow, erow do
on_line_impl(self, win, buf, row, true, false)
end
on_range_impl(self, win, buf, srow, 0, erow, 0, true, false)
self._highlight_states[win] = highlight_states
end
@@ -510,7 +579,7 @@ function TSHighlighter._on_conceal_line(_, win, buf, row)
local highlight_states = self._highlight_states[win]
self.tree:parse({ row, row })
self:prepare_highlight_states(win, row, row)
on_line_impl(self, win, buf, row, false, true)
on_range_impl(self, win, buf, row, 0, row + 1, 0, false, true)
self._highlight_states[win] = highlight_states
end
@@ -554,7 +623,7 @@ end
api.nvim_set_decoration_provider(ns, {
on_win = TSHighlighter._on_win,
on_line = TSHighlighter._on_line,
on_range = TSHighlighter._on_range,
_on_spell_nav = TSHighlighter._on_spell_nav,
_on_conceal_line = TSHighlighter._on_conceal_line,
})

View File

@@ -4,6 +4,7 @@
local api = vim.api
local language = require('vim.treesitter.language')
local memoize = vim.func._memoize
local cmp_ge = require('vim.treesitter._range').cmp_pos.ge
local MODELINE_FORMAT = '^;+%s*inherits%s*:?%s*([a-z_,()]+)%s*$'
local EXTENDS_FORMAT = '^;+%s*extends%s*$'
@@ -951,18 +952,20 @@ end
---
---@param node TSNode under which the search will occur
---@param source (integer|string) Source buffer or string to extract text from
---@param start? integer Starting line for the search. Defaults to `node:start()`.
---@param stop? integer Stopping line for the search (end-exclusive). Defaults to `node:end_()`.
---@param start_row? integer Starting line for the search. Defaults to `node:start()`.
---@param end_row? integer Stopping line for the search (end-inclusive, unless `stop_col` is provided). Defaults to `node:end_()`.
---@param opts? table Optional keyword arguments:
--- - max_start_depth (integer) if non-zero, sets the maximum start depth
--- for each match. This is used to prevent traversing too deep into a tree.
--- - match_limit (integer) Set the maximum number of in-progress matches (Default: 256).
--- - start_col (integer) Starting column for the search.
--- - end_col (integer) Stopping column for the search (end-exclusive).
---
---@return (fun(end_line: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata, TSQueryMatch, TSTree):
---@return (fun(end_line: integer|nil, end_col: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata, TSQueryMatch, TSTree):
--- capture id, capture node, metadata, match, tree
---
---@note Captures are only returned if the query pattern of a specific capture contained predicates.
function Query:iter_captures(node, source, start, stop, opts)
function Query:iter_captures(node, source, start_row, end_row, opts)
opts = opts or {}
opts.match_limit = opts.match_limit or 256
@@ -970,17 +973,24 @@ function Query:iter_captures(node, source, start, stop, opts)
source = api.nvim_get_current_buf()
end
start, stop = value_or_node_range(start, stop, node)
start_row, end_row = value_or_node_range(start_row, end_row, node)
local tree = node:tree()
local cursor = vim._create_ts_querycursor(node, self.query, start, stop, opts)
local cursor = vim._create_ts_querycursor(node, self.query, {
start_row = start_row,
start_col = opts.start_col or 0,
end_row = end_row,
end_col = opts.end_col or 0,
max_start_depth = opts.max_start_depth,
match_limit = opts.match_limit or 256,
})
-- For faster checks that a match is not in the cache.
local highest_cached_match_id = -1
---@type table<integer, vim.treesitter.query.TSMetadata>
local match_cache = {}
local function iter(end_line)
local function iter(end_line, end_col)
local capture, captured_node, match = cursor:next_capture()
if not capture then
@@ -1005,9 +1015,22 @@ function Query:iter_captures(node, source, start, stop, opts)
local predicates = processed_pattern.predicates
if not self:_match_predicates(predicates, pattern_i, captures, source) then
cursor:remove_match(match_id)
if end_line and captured_node:range() > end_line then
local row, col = captured_node:range()
local outside = false
if end_line then
if end_col then
outside = cmp_ge(row, col, end_line, end_col)
else
outside = row > end_line
end
end
if outside then
return nil, captured_node, nil, nil
end
return iter(end_line) -- tail call: try next match
end
@@ -1072,7 +1095,14 @@ function Query:iter_matches(node, source, start, stop, opts)
start, stop = value_or_node_range(start, stop, node)
local tree = node:tree()
local cursor = vim._create_ts_querycursor(node, self.query, start, stop, opts)
local cursor = vim._create_ts_querycursor(node, self.query, {
start_row = start,
start_col = 0,
end_row = stop,
end_col = 0,
max_start_depth = opts.max_start_depth,
match_limit = opts.match_limit or 256,
})
local function iter()
local match = cursor:next_match()

View File

@@ -232,7 +232,7 @@ syn match vimNumber '\<0z\%(\x\x\)\+\%(\.\%(\x\x\)\+\)*' skipwhite nextgroup=@vi
syn case match
" All vimCommands are contained by vimIsCommand. {{{2
syn cluster vimCmdList contains=vimAbb,vimAddress,vimAutocmd,vimAugroup,vimBehave,vimCall,vimCatch,vimCommandModifier,vimConst,vimDoautocmd,vimDebuggreedy,vimDef,vimDefFold,vimDelcommand,vimDelFunction,@vimEcho,vimElse,vimEnddef,vimEndfunction,vimEndif,vimEval,vimExecute,vimIsCommand,vimExtCmd,vimExFilter,vimExMark,vimFiletype,vimFor,vimFunction,vimFunctionFold,vimGrep,vimGrepAdd,vimGlobal,vimHelpgrep,vimHighlight,vimImport,vimLet,vimLoadkeymap,vimLockvar,vimMake,vimMap,vimMark,vimMatch,vimNotFunc,vimNormal,vimProfdel,vimProfile,vimRedir,vimSet,vimSleep,vimSort,vimSyntax,vimSynColor,vimSynLink,vimThrow,vimUniq,vimUnlet,vimUnlockvar,vimUnmap,vimUserCmd,vimVimgrep,vimVimgrepadd,vimMenu,vimMenutranslate,@vim9CmdList,@vimExUserCmdList,vimLua,vimMzScheme,vimPerl,vimPython,vimPython3,vimPythonX,vimRuby,vimTcl
syn cluster vimCmdList contains=vimAbb,vimAddress,vimAutocmd,vimAugroup,vimBehave,vimCall,vimCatch,vimCommandModifier,vimConst,vimDoautocmd,vimDebuggreedy,vimDef,vimDefFold,vimDefer,vimDelcommand,vimDelFunction,@vimEcho,vimElse,vimEnddef,vimEndfunction,vimEndif,vimEval,vimExecute,vimIsCommand,vimExtCmd,vimExFilter,vimExMark,vimFiletype,vimFor,vimFunction,vimFunctionFold,vimGrep,vimGrepAdd,vimGlobal,vimHelpgrep,vimHighlight,vimImport,vimLet,vimLoadkeymap,vimLockvar,vimMake,vimMap,vimMark,vimMatch,vimNotFunc,vimNormal,vimProfdel,vimProfile,vimRedir,vimSet,vimSleep,vimSort,vimSyntax,vimSynColor,vimSynLink,vimThrow,vimUniq,vimUnlet,vimUnlockvar,vimUnmap,vimUserCmd,vimVimgrep,vimVimgrepadd,vimMenu,vimMenutranslate,@vim9CmdList,@vimExUserCmdList,vimLua,vimMzScheme,vimPerl,vimPython,vimPython3,vimPythonX,vimRuby,vimTcl
syn cluster vim9CmdList contains=vim9Abstract,vim9Class,vim9Const,vim9Enum,vim9Export,vim9Final,vim9For,vim9Interface,vim9Type,vim9Var
syn match vimCmdSep "\\\@1<!|" skipwhite nextgroup=@vimCmdList,vimSubst1,@vimFunc
syn match vimCmdSep ":\+" skipwhite nextgroup=@vimCmdList,vimSubst1
@@ -300,6 +300,10 @@ syn match vimCall "\<call\=\>" skipwhite nextgroup=vimVar,@vimFunc
" TODO: special-cased until generalised range/count support is implemented
syn match vimDebuggreedy "\<0\=debugg\%[reedy]\>" contains=vimCount
" Defer {{{2
" =====
syn match vimDefer "\<defer\=\>" skipwhite nextgroup=@vimFunc
" Exception Handling {{{2
syn keyword vimThrow th[row] skipwhite nextgroup=@vimExprList
syn keyword vimCatch cat[ch] skipwhite nextgroup=vimCatchPattern
@@ -2240,6 +2244,7 @@ if !exists("skip_vim_syntax_inits")
hi def link vimDef vimCommand
hi def link vimDefBang vimBang
hi def link vimDefComment vim9Comment
hi def link vimDefer vimCommand
hi def link vimDefParam vimVar
hi def link vimDelcommand vimCommand
hi def link vimDelcommandAttr vimUserCmdAttr

View File

@@ -1004,7 +1004,7 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
/// Note: this function should not be called often. Rather, the callbacks
/// themselves can be used to throttle unneeded callbacks. the `on_start`
/// callback can return `false` to disable the provider until the next redraw.
/// Similarly, return `false` in `on_win` will skip the `on_line` calls
/// Similarly, return `false` in `on_win` will skip the `on_line` and `on_range` calls
/// for that window (but any extmarks set in `on_win` will still be used).
/// A plugin managing multiple sources of decoration should ideally only set
/// one provider, and merge the sources internally. You can use multiple `ns_id`
@@ -1016,7 +1016,7 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
/// Doing `vim.rpcnotify` should be OK, but `vim.rpcrequest` is quite dubious
/// for the moment.
///
/// Note: It is not allowed to remove or update extmarks in `on_line` callbacks.
/// Note: It is not allowed to remove or update extmarks in `on_line` or `on_range` callbacks.
///
/// @param ns_id Namespace id from |nvim_create_namespace()|
/// @param opts Table of callbacks:
@@ -1038,6 +1038,14 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
/// ```
/// ["line", winid, bufnr, row]
/// ```
/// - on_range: called for each buffer range being redrawn.
/// Range is end-exclusive and may span multiple lines. Range
/// bounds point to the first byte of a character. An end position
/// of the form (lnum, 0), including (number of lines, 0), is valid
/// and indicates that EOL of the preceding line is included.
/// ```
/// ["range", winid, bufnr, begin_row, begin_col, end_row, end_col]
/// ```
/// - on_end: called at the end of a redraw cycle
/// ```
/// ["end", tick]
@@ -1061,6 +1069,7 @@ void nvim_set_decoration_provider(Integer ns_id, Dict(set_decoration_provider) *
{ "on_buf", &opts->on_buf, &p->redraw_buf },
{ "on_win", &opts->on_win, &p->redraw_win },
{ "on_line", &opts->on_line, &p->redraw_line },
{ "on_range", &opts->on_range, &p->redraw_range },
{ "on_end", &opts->on_end, &p->redraw_end },
{ "_on_hl_def", &opts->_on_hl_def, &p->hl_def },
{ "_on_spell_nav", &opts->_on_spell_nav, &p->spell_nav },

View File

@@ -18,6 +18,8 @@ typedef struct {
LuaRefOf(("win" _, Integer winid, Integer bufnr, Integer toprow, Integer botrow),
*Boolean) on_win;
LuaRefOf(("line" _, Integer winid, Integer bufnr, Integer row), *Boolean) on_line;
LuaRefOf(("range" _, Integer winid, Integer bufnr, Integer start_row, Integer start_col,
Integer end_row, Integer end_col), *Boolean) on_range;
LuaRefOf(("end" _, Integer tick)) on_end;
LuaRefOf(("hl_def" _)) _on_hl_def;
LuaRefOf(("spell_nav" _)) _on_spell_nav;

View File

@@ -513,7 +513,7 @@ static void decor_state_pack(DecorState *state)
state->future_begin = fut_beg;
}
bool decor_redraw_line(win_T *wp, int row, DecorState *state)
void decor_redraw_line(win_T *wp, int row, DecorState *state)
{
decor_state_pack(state);
@@ -527,7 +527,11 @@ bool decor_redraw_line(win_T *wp, int row, DecorState *state)
state->row = row;
state->col_until = -1;
state->eol_col = -1;
}
// Checks if there are (likely) more decorations on the current line.
bool decor_has_more_decorations(DecorState *state, int row)
{
if (state->current_end != 0 || state->future_begin != (int)kv_size(state->ranges_i)) {
return true;
}

View File

@@ -149,6 +149,7 @@ typedef struct {
LuaRef redraw_buf;
LuaRef redraw_win;
LuaRef redraw_line;
LuaRef redraw_range;
LuaRef redraw_end;
LuaRef hl_def;
LuaRef spell_nav;

View File

@@ -25,7 +25,7 @@ static kvec_t(DecorProvider) decor_providers = KV_INITIAL_VALUE;
#define DECORATION_PROVIDER_INIT(ns_id) (DecorProvider) \
{ ns_id, kDecorProviderDisabled, LUA_NOREF, LUA_NOREF, \
LUA_NOREF, LUA_NOREF, LUA_NOREF, \
LUA_NOREF, LUA_NOREF, LUA_NOREF, LUA_NOREF, \
LUA_NOREF, LUA_NOREF, -1, false, false, 0 }
static void decor_provider_error(DecorProvider *provider, const char *name, const char *msg)
@@ -189,6 +189,30 @@ void decor_providers_invoke_line(win_T *wp, int row)
decor_state.running_decor_provider = false;
}
void decor_providers_invoke_range(win_T *wp, int start_row, int start_col, int end_row, int end_col)
{
decor_state.running_decor_provider = true;
for (size_t i = 0; i < kv_size(decor_providers); i++) {
DecorProvider *p = &kv_A(decor_providers, i);
if (p->state == kDecorProviderActive && p->redraw_range != LUA_NOREF) {
MAXSIZE_TEMP_ARRAY(args, 6);
ADD_C(args, WINDOW_OBJ(wp->handle));
ADD_C(args, BUFFER_OBJ(wp->w_buffer->handle));
ADD_C(args, INTEGER_OBJ(start_row));
ADD_C(args, INTEGER_OBJ(start_col));
ADD_C(args, INTEGER_OBJ(end_row));
ADD_C(args, INTEGER_OBJ(end_col));
if (!decor_provider_invoke((int)i, "range", p->redraw_range, args, true)) {
// return 'false' or error: skip rest of this window
kv_A(decor_providers, i).state = kDecorProviderWinDisabled;
}
hl_check_ns();
}
}
decor_state.running_decor_provider = false;
}
/// For each provider invoke the 'buf' callback for a given buffer.
///
/// @param buf Buffer
@@ -272,6 +296,7 @@ void decor_provider_clear(DecorProvider *p)
NLUA_CLEAR_REF(p->redraw_buf);
NLUA_CLEAR_REF(p->redraw_win);
NLUA_CLEAR_REF(p->redraw_line);
NLUA_CLEAR_REF(p->redraw_range);
NLUA_CLEAR_REF(p->redraw_end);
NLUA_CLEAR_REF(p->spell_nav);
NLUA_CLEAR_REF(p->conceal_line);

View File

@@ -1142,6 +1142,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
// Not drawing text when line is concealed or drawing filler lines beyond last line.
const bool draw_text = !concealed && (lnum != buf->b_ml.ml_line_count + 1);
int decor_provider_end_col;
bool check_decor_providers = false;
if (col_rows == 0 && draw_text) {
// To speed up the loop below, set extra_check when there is linebreak,
// trailing white space and/or syntax processing to be done.
@@ -1163,14 +1166,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
}
}
decor_providers_invoke_line(wp, lnum - 1); // may invalidate wp->w_virtcol
validate_virtcol(wp);
has_decor = decor_redraw_line(wp, lnum - 1, &decor_state);
if (has_decor) {
extra_check = true;
}
check_decor_providers = true;
// Check for columns to display for 'colorcolumn'.
wlv.color_cols = wp->w_buffer->terminal ? NULL : wp->w_p_cc_cols;
@@ -1466,22 +1462,22 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
// 'nowrap' or 'wrap' and a single line that doesn't fit: Advance to the
// first character to be displayed.
const int start_col = wp->w_p_wrap
? (startrow == 0 ? wp->w_skipcol : 0)
: wp->w_leftcol;
const int start_vcol = wp->w_p_wrap
? (startrow == 0 ? wp->w_skipcol : 0)
: wp->w_leftcol;
if (has_foldtext) {
wlv.vcol = start_col;
} else if (start_col > 0 && col_rows == 0) {
wlv.vcol = start_vcol;
} else if (start_vcol > 0 && col_rows == 0) {
char *prev_ptr = ptr;
CharSize cs = { 0 };
CharsizeArg csarg;
CSType cstype = init_charsize_arg(&csarg, wp, lnum, line);
csarg.max_head_vcol = start_col;
csarg.max_head_vcol = start_vcol;
int vcol = wlv.vcol;
StrCharInfo ci = utf_ptr2StrCharInfo(ptr);
while (vcol < start_col && *ci.ptr != NUL) {
while (vcol < start_vcol && *ci.ptr != NUL) {
cs = win_charsize(cstype, vcol, ci.ptr, ci.chr.value, &csarg);
vcol += cs.width;
prev_ptr = ci.ptr;
@@ -1518,23 +1514,23 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
// - the visual mode is active, or
// - drawing a fold
// the end of the line may be before the start of the displayed part.
if (wlv.vcol < start_col && (wp->w_p_cuc
|| wlv.color_cols
|| virtual_active(wp)
|| (VIsual_active && wp->w_buffer == curwin->w_buffer)
|| has_fold)) {
wlv.vcol = start_col;
if (wlv.vcol < start_vcol && (wp->w_p_cuc
|| wlv.color_cols
|| virtual_active(wp)
|| (VIsual_active && wp->w_buffer == curwin->w_buffer)
|| has_fold)) {
wlv.vcol = start_vcol;
}
// Handle a character that's not completely on the screen: Put ptr at
// that character but skip the first few screen characters.
if (wlv.vcol > start_col) {
if (wlv.vcol > start_vcol) {
wlv.vcol -= charsize;
ptr = prev_ptr;
}
if (start_col > wlv.vcol) {
wlv.skip_cells = start_col - wlv.vcol - head;
if (start_vcol > wlv.vcol) {
wlv.skip_cells = start_vcol - wlv.vcol - head;
}
// Adjust for when the inverted text is before the screen,
@@ -1588,6 +1584,23 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
}
}
if (check_decor_providers) {
int const col = (int)(ptr - line);
decor_provider_end_col = decor_providers_setup(endrow - startrow,
start_vcol == 0,
lnum,
col,
wp);
line = ml_get_buf(wp->w_buffer, lnum);
ptr = line + col;
}
decor_redraw_line(wp, lnum - 1, &decor_state);
if (!has_decor && decor_has_more_decorations(&decor_state, lnum - 1)) {
has_decor = true;
extra_check = true;
}
// Correct highlighting for cursor that can't be disabled.
// Avoids having to check this for each character.
if (wlv.fromcol >= 0) {
@@ -1642,6 +1655,18 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
bool did_decrement_ptr = false;
// Get next chunk of extmark highlights if previous approximation was smaller than needed.
if (check_decor_providers && (int)(ptr - line) >= decor_provider_end_col) {
int const col = (int)(ptr - line);
decor_provider_end_col = invoke_range_next(wp, lnum, col, 100);
line = ml_get_buf(wp->w_buffer, lnum);
ptr = line + col;
if (!has_decor && decor_has_more_decorations(&decor_state, lnum - 1)) {
has_decor = true;
extra_check = true;
}
}
// Skip this quickly when working on the text.
if (draw_cols) {
if (cul_screenline) {
@@ -2740,7 +2765,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
// Highlight 'cursorcolumn' & 'colorcolumn' past end of the line.
// check if line ends before left margin
wlv.vcol = MAX(wlv.vcol, start_col + wlv.col - win_col_off(wp));
wlv.vcol = MAX(wlv.vcol, start_vcol + wlv.col - win_col_off(wp));
// Get rid of the boguscols now, we want to draw until the right
// edge for 'cursorcolumn'.
wlv.col -= wlv.boguscols;
@@ -2762,7 +2787,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
if (((wp->w_p_cuc
&& wp->w_virtcol >= vcol_hlc(wlv) - eol_hl_off
&& wp->w_virtcol < view_width * (ptrdiff_t)(wlv.row - startrow + 1) + start_col
&& wp->w_virtcol < view_width * (ptrdiff_t)(wlv.row - startrow + 1) + start_vcol
&& lnum != wp->w_cursor.lnum)
|| wlv.color_cols || wlv.line_attr_lowprio || wlv.line_attr
|| wlv.diff_hlf != 0 || wp->w_buffer->terminal)) {
@@ -3180,3 +3205,50 @@ static void wlv_put_linebuf(win_T *wp, const winlinevars_T *wlv, int endcol, boo
ScreenGrid *g = grid_adjust(grid, &row, &coloff);
grid_put_linebuf(g, row, coloff, startcol, endcol, clear_width, bg_attr, 0, wlv->vcol - 1, flags);
}
static int decor_providers_setup(int rows_to_draw, bool draw_from_line_start, linenr_T lnum,
colnr_T col, win_T *wp)
{
// Approximate the number of bytes that will be drawn.
// Assume we're dealing with 1-cell ascii and ignore
// the effects of 'linebreak', 'breakindent', etc.
int rem_vcols;
if (wp->w_p_wrap) {
int width = wp->w_view_width - win_col_off(wp);
int width2 = width + win_col_off2(wp);
int first_row_width = draw_from_line_start ? width : width2;
rem_vcols = first_row_width + (rows_to_draw - 1) * width2;
} else {
rem_vcols = wp->w_view_height - win_col_off(wp);
}
// Call it here since we need to invalidate the line pointer anyway.
decor_providers_invoke_line(wp, lnum - 1);
validate_virtcol(wp);
return invoke_range_next(wp, lnum, col, rem_vcols + 1);
}
/// @return New begin column, or INT_MAX.
static int invoke_range_next(win_T *wp, int lnum, colnr_T begin_col, colnr_T col_off)
{
char const *const line = ml_get_buf(wp->w_buffer, lnum);
int const line_len = ml_get_buf_len(wp->w_buffer, lnum);
col_off = MAX(col_off, 1);
colnr_T new_col;
if (col_off <= line_len - begin_col) {
int end_col = begin_col + col_off;
end_col += mb_off_next(line, line + end_col);
decor_providers_invoke_range(wp, lnum - 1, begin_col, lnum - 1, end_col);
validate_virtcol(wp);
new_col = end_col;
} else {
decor_providers_invoke_range(wp, lnum - 1, begin_col, lnum, 0);
validate_virtcol(wp);
new_col = INT_MAX;
}
return new_col;
}

View File

@@ -1361,34 +1361,46 @@ static int tslua_push_querycursor(lua_State *L)
TSQuery *query = query_check(L, 2);
TSQueryCursor *cursor = ts_query_cursor_new();
if (lua_gettop(L) >= 3 && !lua_isnil(L, 3)) {
luaL_argcheck(L, lua_istable(L, 3), 3, "table expected");
}
lua_getfield(L, 3, "start_row");
uint32_t start_row = (uint32_t)luaL_checkinteger(L, -1);
lua_pop(L, 1);
lua_getfield(L, 3, "start_col");
uint32_t start_col = (uint32_t)luaL_checkinteger(L, -1);
lua_pop(L, 1);
lua_getfield(L, 3, "end_row");
uint32_t end_row = (uint32_t)luaL_checkinteger(L, -1);
lua_pop(L, 1);
lua_getfield(L, 3, "end_col");
uint32_t end_col = (uint32_t)luaL_checkinteger(L, -1);
lua_pop(L, 1);
ts_query_cursor_set_point_range(cursor, (TSPoint){ start_row, start_col },
(TSPoint){ end_row, end_col });
lua_getfield(L, 3, "max_start_depth");
if (!lua_isnil(L, -1)) {
uint32_t max_start_depth = (uint32_t)luaL_checkinteger(L, -1);
ts_query_cursor_set_max_start_depth(cursor, max_start_depth);
}
lua_pop(L, 1);
lua_getfield(L, 3, "match_limit");
if (!lua_isnil(L, -1)) {
uint32_t match_limit = (uint32_t)luaL_checkinteger(L, -1);
ts_query_cursor_set_match_limit(cursor, match_limit);
}
lua_pop(L, 1);
ts_query_cursor_exec(cursor, query, node);
if (lua_gettop(L) >= 3) {
uint32_t start = (uint32_t)luaL_checkinteger(L, 3);
uint32_t end = lua_gettop(L) >= 4 ? (uint32_t)luaL_checkinteger(L, 4) : MAXLNUM;
ts_query_cursor_set_point_range(cursor, (TSPoint){ start, 0 }, (TSPoint){ end, 0 });
}
if (lua_gettop(L) >= 5 && !lua_isnil(L, 5)) {
luaL_argcheck(L, lua_istable(L, 5), 5, "table expected");
lua_pushnil(L); // [dict, ..., nil]
while (lua_next(L, 5)) {
// [dict, ..., key, value]
if (lua_type(L, -2) == LUA_TSTRING) {
char *k = (char *)lua_tostring(L, -2);
if (strequal("max_start_depth", k)) {
uint32_t max_start_depth = (uint32_t)lua_tointeger(L, -1);
ts_query_cursor_set_max_start_depth(cursor, max_start_depth);
} else if (strequal("match_limit", k)) {
uint32_t match_limit = (uint32_t)lua_tointeger(L, -1);
ts_query_cursor_set_match_limit(cursor, match_limit);
}
}
// pop the value; lua_next will pop the key.
lua_pop(L, 1); // [dict, ..., key]
}
}
TSQueryCursor **ud = lua_newuserdata(L, sizeof(*ud)); // [node, query, ..., udata]
*ud = cursor;
lua_getfield(L, LUA_REGISTRYINDEX, TS_META_QUERYCURSOR); // [node, query, ..., udata, meta]

View File

@@ -110,7 +110,7 @@ static const int included_patches[] = {
2374,
2373,
2372,
// 2371,
2371,
2370,
2369,
2368,

View File

@@ -8,7 +8,7 @@ describe('decor perf', function()
it('can handle long lines', function()
Screen.new(100, 101)
local result = exec_lua [==[
local result = exec_lua(function()
local ephemeral_pattern = {
{ 0, 4, 'Comment', 11 },
{ 0, 3, 'Keyword', 12 },
@@ -61,7 +61,7 @@ describe('decor perf', function()
return true
end,
on_line = function()
add_pattern(ephemeral_pattern, true)
add_pattern(ephemeral_pattern, true)
end,
})
@@ -69,16 +69,16 @@ describe('decor perf', function()
local total = {}
local provider = {}
for i = 1, 100 do
for _ = 1, 100 do
local tic = vim.uv.hrtime()
vim.cmd'redraw!'
vim.cmd 'redraw!'
local toc = vim.uv.hrtime()
table.insert(total, toc - tic)
table.insert(provider, pe - ps)
end
return { total, provider }
]==]
end)
local total, provider = unpack(result)
table.sort(total)
@@ -137,4 +137,39 @@ describe('decor perf', function()
)
print('\nTotal ' .. res)
end)
it('can handle long lines with treesitter highlighting', function()
Screen.new(100, 51)
local result = exec_lua(function()
local long_line = 'local a = { ' .. ('a = 5, '):rep(2000) .. '}'
vim.api.nvim_buf_set_lines(0, 0, 0, false, { long_line })
vim.api.nvim_win_set_cursor(0, { 1, 0 })
vim.treesitter.start(0, 'lua')
local total = {}
for _ = 1, 50 do
local tic = vim.uv.hrtime()
vim.cmd 'redraw!'
local toc = vim.uv.hrtime()
table.insert(total, toc - tic)
end
return { total }
end)
local total = unpack(result)
table.sort(total)
local ms = 1 / 1000000
local res = string.format(
'min, 25%%, median, 75%%, max:\n\t%0.1fms,\t%0.1fms,\t%0.1fms,\t%0.1fms,\t%0.1fms',
total[1] * ms,
total[1 + math.floor(#total * 0.25)] * ms,
total[1 + math.floor(#total * 0.5)] * ms,
total[1 + math.floor(#total * 0.75)] * ms,
total[#total] * ms
)
print('\nTotal ' .. res)
end)
end)

View File

@@ -1,10 +1,11 @@
local n = require('test.functional.testnvim')()
local Screen = require('test.functional.ui.screen')
local clear = n.clear
local exec_lua = n.exec_lua
describe('treesitter perf', function()
setup(function()
before_each(function()
clear()
end)
@@ -47,4 +48,144 @@ describe('treesitter perf', function()
return vim.uv.hrtime() - start
]]
end)
local function test_long_line(_pos, _wrap, _line, grid)
local screen = Screen.new(20, 11)
local result = exec_lua(function(...)
local pos, wrap, line = ...
vim.api.nvim_buf_set_lines(0, 0, 0, false, { line })
vim.api.nvim_win_set_cursor(0, pos)
vim.api.nvim_set_option_value('wrap', wrap, { win = 0 })
vim.treesitter.start(0, 'lua')
local total = {}
for _ = 1, 100 do
local tic = vim.uv.hrtime()
vim.cmd 'redraw!'
local toc = vim.uv.hrtime()
table.insert(total, toc - tic)
end
return { total }
end, _pos, _wrap, _line)
screen:expect({ grid = grid or '' })
local total = unpack(result)
table.sort(total)
local ms = 1 / 1000000
local res = string.format(
'min, 25%%, median, 75%%, max:\n\t%0.2fms,\t%0.2fms,\t%0.2fms,\t%0.2fms,\t%0.2fms',
total[1] * ms,
total[1 + math.floor(#total * 0.25)] * ms,
total[1 + math.floor(#total * 0.5)] * ms,
total[1 + math.floor(#total * 0.75)] * ms,
total[#total] * ms
)
print('\nTotal ' .. res)
end
local long_line = 'local a = { ' .. ('a = 5, '):rep(500) .. '}'
it('can redraw the beginning of a long line with wrapping', function()
local grid = [[
{15:^local} {25:a} {15:=} {16:{} {25:a} {15:=} {26:5}{16:,} {25:a}|
{15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} |
{25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,}|
{25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}|
{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} |
{26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=}|
{26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} |
{15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a}|
{15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} |
{25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,}|
|
]]
test_long_line({ 1, 0 }, true, long_line, grid)
end)
it('can redraw the middle of a long line with wrapping', function()
local grid = [[
{1:<<<}{26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} |
{25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,}|
{25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}|
{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} |
{26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=}|
{26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} |
{15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a}|
{15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} |
{25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,}|
{25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a}^ {15:=} {26:5}|
|
]]
test_long_line({ 1, math.floor(#long_line / 2) }, true, long_line, grid)
end)
it('can redraw the end of a long line with wrapping', function()
local grid = [[
{1:<<<}{25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=}|
{26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} |
{15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a}|
{15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} |
{25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,}|
{25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}|
{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} |
{26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=}|
{26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {25:a} |
{15:=} {26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {16:^}} |
|
]]
test_long_line({ 1, #long_line - 1 }, true, long_line, grid)
end)
it('can redraw the beginning of a long line without wrapping', function()
local grid = [[
{15:^local} {25:a} {15:=} {16:{} {25:a} {15:=} {26:5}{16:,} {25:a}|
|
{1:~ }|*8
|
]]
test_long_line({ 1, 0 }, false, long_line, grid)
end)
it('can redraw the middle of a long line without wrapping', function()
local grid = [[
{16:,} {25:a} {15:=} {26:5}{16:,} {25:a}^ {15:=} {26:5}{16:,} {25:a} {15:=} |
|
{1:~ }|*8
|
]]
test_long_line({ 1, math.floor(#long_line / 2) }, false, long_line, grid)
end)
it('can redraw the end of a long line without wrapping', function()
local grid = [[
{26:5}{16:,} {25:a} {15:=} {26:5}{16:,} {16:^}} |
|
{1:~ }|*8
|
]]
test_long_line({ 1, #long_line - 1 }, false, long_line, grid)
end)
local long_line_mb = 'local a = { ' .. ('À = 5, '):rep(500) .. '}'
it('can redraw the middle of a long line with multibyte characters', function()
local grid = [[
{1:<<<}{26:5}{16:,} {25:À} {15:=} {26:5}{16:,} {25:À} {15:=} {26:5}{16:,} |
{25:À} {15:=} {26:5}{16:,} {25:À} {15:=} {26:5}{16:,} {25:À} {15:=} {26:5}{16:,}|
{25:À} {15:=} {26:5}{16:,} {25:À} {15:=} {26:5}{16:,} {25:À} {15:=} {26:5}|
{16:,} {25:À} {15:=} {26:5}{16:,} {25:À} {15:=} {26:5}{16:,} {25:À} {15:=} |
{26:5}{16:,} {25:À} {15:=} {26:5}{16:,} {25:À} {15:=} {26:5}{16:,} {25:À} {15:=}|
{26:5}{16:,} {25:À} {15:=} {26:5}{16:,} {25:À} {15:=} {26:5}{16:,} {25:À} |
{15:=} {26:5}{16:,} {25:À} {15:=} {26:5}{16:,} {25:À} {15:=} {26:5}{16:,} {25:À}|
{15:=} {26:5}{16:,} {25:À} {15:=} {26:5}{16:,} {25:À} {15:=} {26:5}{16:,} |
{25:À} {15:=} {26:5}{16:,} {25:À} {15:=} {26:5}{16:,} {25:À} {15:=} {26:5}{16:,}|
{25:À} {15:=} {26:5}{16:,} {25:À} {15:=} {26:5}{16:,} {25:À}^ {15:=} {26:5}|
|
]]
test_long_line({ 1, math.floor(#long_line_mb / 2) }, true, long_line_mb, grid)
end)
end)

View File

@@ -747,6 +747,47 @@ void ui_refresh(void)
eq({ { 1, 10, 1, 13 } }, ret)
end)
it('iter_captures supports columns', function()
local txt = table.concat({
'int aaa = 1, bbb = 2;',
'int foo = 1, bar = 2;',
'int baz = 3, qux = 4;',
'int ccc = 1, ddd = 2;',
}, '\n')
local function test(opts)
local parser = vim.treesitter.get_string_parser(txt, 'c')
local nodes = {}
local query = vim.treesitter.query.parse('c', '((identifier) @foo)')
local root = assert(parser:parse()[1]:root())
local iter = query:iter_captures(root, txt, 1, 2, opts)
while true do
local capture, node = iter()
if not capture then
break
end
table.insert(nodes, { node:range() })
end
return nodes
end
local ret
ret = exec_lua(test, { start_col = 7, end_col = 13 })
eq({ { 1, 13, 1, 16 }, { 2, 4, 2, 7 } }, ret)
ret = exec_lua(test, { start_col = 7 })
eq({ { 1, 13, 1, 16 } }, ret)
ret = exec_lua(test, { end_col = 13 })
eq({ { 1, 4, 1, 7 }, { 1, 13, 1, 16 }, { 2, 4, 2, 7 } }, ret)
ret = exec_lua(test, {})
eq({ { 1, 4, 1, 7 }, { 1, 13, 1, 16 } }, ret)
end)
it('fails to load queries', function()
local function test(exp, cquery)
eq(exp, pcall_err(exec_lua, "vim.treesitter.query.parse('c', ...)", cquery))

View File

@@ -28,39 +28,43 @@ local function setup_provider(code)
]]) .. [[
api.nvim_set_decoration_provider(_G.ns1, {
on_start = on_do; on_buf = on_do;
on_win = on_do; on_line = on_do;
on_win = on_do; on_line = on_do; on_range = on_do;
on_end = on_do; _on_spell_nav = on_do;
})
return _G.ns1
]])
end
local function setup_screen(screen)
screen:set_default_attr_ids {
[1] = { bold = true, foreground = Screen.colors.Blue },
[2] = { foreground = Screen.colors.Grey100, background = Screen.colors.Red },
[3] = { foreground = Screen.colors.Brown },
[4] = { foreground = Screen.colors.Blue1 },
[5] = { foreground = Screen.colors.Magenta },
[6] = { bold = true, foreground = Screen.colors.Brown },
[7] = { background = Screen.colors.Gray90 },
[8] = { bold = true, reverse = true },
[9] = { reverse = true },
[10] = { italic = true, background = Screen.colors.Magenta },
[11] = { foreground = Screen.colors.Red, background = tonumber('0x005028') },
[12] = { foreground = tonumber('0x990000') },
[13] = { background = Screen.colors.LightBlue },
[14] = { background = Screen.colors.WebGray, foreground = Screen.colors.DarkBlue },
[15] = { special = Screen.colors.Blue, undercurl = true },
[16] = { special = Screen.colors.Red, undercurl = true },
[17] = { foreground = Screen.colors.Red },
[18] = { bold = true, foreground = Screen.colors.SeaGreen },
[19] = { bold = true },
}
end
describe('decorations providers', function()
local screen ---@type test.functional.ui.screen
before_each(function()
clear()
screen = Screen.new(40, 8)
screen:set_default_attr_ids {
[1] = { bold = true, foreground = Screen.colors.Blue },
[2] = { foreground = Screen.colors.Grey100, background = Screen.colors.Red },
[3] = { foreground = Screen.colors.Brown },
[4] = { foreground = Screen.colors.Blue1 },
[5] = { foreground = Screen.colors.Magenta },
[6] = { bold = true, foreground = Screen.colors.Brown },
[7] = { background = Screen.colors.Gray90 },
[8] = { bold = true, reverse = true },
[9] = { reverse = true },
[10] = { italic = true, background = Screen.colors.Magenta },
[11] = { foreground = Screen.colors.Red, background = tonumber('0x005028') },
[12] = { foreground = tonumber('0x990000') },
[13] = { background = Screen.colors.LightBlue },
[14] = { background = Screen.colors.WebGray, foreground = Screen.colors.DarkBlue },
[15] = { special = Screen.colors.Blue, undercurl = true },
[16] = { special = Screen.colors.Red, undercurl = true },
[17] = { foreground = Screen.colors.Red },
[18] = { bold = true, foreground = Screen.colors.SeaGreen },
[19] = { bold = true },
}
setup_screen(screen)
end)
local mulholland = [[
@@ -110,12 +114,19 @@ describe('decorations providers', function()
{ 'start', 4 },
{ 'win', 1000, 1, 0, 6 },
{ 'line', 1000, 1, 0 },
{ 'range', 1000, 1, 0, 0, 1, 0 },
{ 'line', 1000, 1, 1 },
{ 'range', 1000, 1, 1, 0, 2, 0 },
{ 'line', 1000, 1, 2 },
{ 'range', 1000, 1, 2, 0, 3, 0 },
{ 'line', 1000, 1, 3 },
{ 'range', 1000, 1, 3, 0, 4, 0 },
{ 'line', 1000, 1, 4 },
{ 'range', 1000, 1, 4, 0, 5, 0 },
{ 'line', 1000, 1, 5 },
{ 'range', 1000, 1, 5, 0, 6, 0 },
{ 'line', 1000, 1, 6 },
{ 'range', 1000, 1, 6, 0, 7, 0 },
{ 'end', 4 },
}
@@ -137,6 +148,7 @@ describe('decorations providers', function()
{ 'buf', 1, 5 },
{ 'win', 1000, 1, 0, 6 },
{ 'line', 1000, 1, 6 },
{ 'range', 1000, 1, 6, 0, 7, 0 },
{ 'end', 5 },
}
end)
@@ -206,9 +218,13 @@ describe('decorations providers', function()
{ 'start', 5 },
{ 'win', 1000, 1, 0, 3 },
{ 'line', 1000, 1, 0 },
{ 'range', 1000, 1, 0, 0, 1, 0 },
{ 'line', 1000, 1, 1 },
{ 'range', 1000, 1, 1, 0, 2, 0 },
{ 'line', 1000, 1, 2 },
{ 'range', 1000, 1, 2, 0, 3, 0 },
{ 'line', 1000, 1, 3 },
{ 'range', 1000, 1, 3, 0, 4, 0 },
{ 'end', 5 },
}
@@ -806,6 +822,126 @@ describe('decorations providers', function()
]])
end)
it('on_range is invoked on all visible characters', function()
clear()
screen = Screen.new(20, 4)
setup_screen(screen)
local function record()
exec_lua(function()
_G.p_min = { math.huge, math.huge }
_G.p_max = { -math.huge, -math.huge }
function _G.pos_gt(a, b)
return a[1] > b[1] or (a[1] == b[1] and a[2] > b[2])
end
function _G.pos_lt(a, b)
return a[1] < b[1] or (a[1] == b[1] and a[2] < b[2])
end
end)
setup_provider [[
local function on_do(kind, _, bufnr, br, bc, er, ec)
if kind == 'range' then
local b = { br, bc }
local e = { er, ec }
if _G.pos_gt(_G.p_min, b) then
_G.p_min = b
end
if _G.pos_lt(_G.p_max, e) then
_G.p_max = e
end
end
end
]]
end
local function check(min, max)
local p_min = exec_lua('return _G.p_min')
assert(
p_min[1] < min[1] or (p_min[1] == min[1] and p_min[2] <= min[2]),
'minimum position ' .. vim.inspect(p_min) .. ' should be before the first char'
)
local p_max = exec_lua('return _G.p_max')
assert(
p_max[1] > max[1] or (p_max[1] == max[1] and p_max[2] >= max[2]),
'maximum position ' .. vim.inspect(p_max) .. ' should be on or after the last char'
)
end
-- Multiple lines.
exec_lua([[
local lines = { ('a'):rep(40), ('b'):rep(40), ('c'):rep(40) }
vim.api.nvim_buf_set_lines(0, 0, -1, true, lines)
vim.api.nvim_win_set_cursor(0, { 2, 0 })
]])
record()
screen:expect([[
^bbbbbbbbbbbbbbbbbbbb|
bbbbbbbbbbbbbbbbbbbb|
ccccccccccccccccc{1:@@@}|
|
]])
check({ 1, 0 }, { 2, 21 })
-- One long line.
exec_lua([[
local lines = { ('a'):rep(100) }
vim.api.nvim_buf_set_lines(0, 0, -1, true, lines)
vim.api.nvim_win_set_cursor(0, { 1, 70 })
]])
record()
screen:expect([[
{1:<<<}aaaaaaaaaaaaaaaaa|
aaaaaaaaaaaaaaaaaaaa|
aaaaaaaaaa^aaaaaaaaaa|
|
]])
check({ 0, 20 }, { 0, 81 })
-- Multibyte characters.
exec_lua([[
local lines = { ('\195\162'):rep(100) }
vim.api.nvim_buf_set_lines(0, 0, -1, true, lines)
vim.api.nvim_win_set_cursor(0, { 1, 70 * 2 })
]])
record()
screen:expect([[
{1:<<<}âââââââââââââââââ|
ââââââââââââââââââââ|
ââââââââââ^ââââââââââ|
|
]])
check({ 0, 20 * 2 }, { 0, 81 * 2 })
-- Tabs.
exec_lua([[
local lines = { 'a' .. ('\t'):rep(100) }
vim.api.nvim_buf_set_lines(0, 0, -1, true, lines)
vim.api.nvim_win_set_cursor(0, { 1, 39 })
]])
record()
screen:expect([[
{1:<<<} |
|
^ |
|
]])
check({ 0, 33 }, { 0, 94 })
-- One long line without wrapping.
command('set nowrap')
exec_lua([[
local lines = { ('a'):rep(50) .. ('b'):rep(50) }
vim.api.nvim_buf_set_lines(0, 0, -1, true, lines)
vim.api.nvim_win_set_cursor(0, { 1, 50 })
]])
record()
screen:expect([[
aaaaaaaaaa^bbbbbbbbbb|
{1:~ }|*2
|
]])
check({ 0, 40 }, { 0, 60 })
end)
it('can add new providers during redraw #26652', function()
setup_provider [[
local ns = api.nvim_create_namespace('test_no_add')

View File

@@ -22,7 +22,7 @@ func Test_equal()
call assert_false(base.method == instance.other)
call assert_false([base.method] == [instance.other])
call assert_fails('echo base.method > instance.method')
call assert_fails('echo base.method > instance.method', 'E694: Invalid operation for Funcrefs')
" Nvim doesn't have null functions
" call assert_equal(0, test_null_function() == function('min'))
" call assert_equal(1, test_null_function() == test_null_function())

View File

@@ -1,30 +1,29 @@
" Test getting and setting file permissions.
func Test_file_perm()
call assert_equal('', getfperm('Xtest'))
call assert_equal(0, 'Xtest'->setfperm('r--------'))
call assert_equal('', getfperm('XtestPerm'))
call assert_equal(0, 'XtestPerm'->setfperm('r--------'))
call writefile(['one'], 'Xtest')
call assert_true(len('Xtest'->getfperm()) == 9)
call writefile(['one'], 'XtestPerm', 'D')
call assert_true(len('XtestPerm'->getfperm()) == 9)
call assert_equal(1, setfperm('Xtest', 'rwx------'))
call assert_equal(1, setfperm('XtestPerm', 'rwx------'))
if has('win32')
call assert_equal('rw-rw-rw-', getfperm('Xtest'))
call assert_equal('rw-rw-rw-', getfperm('XtestPerm'))
else
call assert_equal('rwx------', getfperm('Xtest'))
call assert_equal('rwx------', getfperm('XtestPerm'))
endif
call assert_equal(1, setfperm('Xtest', 'r--r--r--'))
call assert_equal('r--r--r--', getfperm('Xtest'))
call assert_equal(1, setfperm('XtestPerm', 'r--r--r--'))
call assert_equal('r--r--r--', getfperm('XtestPerm'))
call assert_fails("setfperm('Xtest', '---')")
call assert_fails("call setfperm('XtestPerm', '---')", 'E475: Invalid argument: ---')
call assert_equal(1, setfperm('Xtest', 'rwx------'))
call delete('Xtest')
call assert_equal(1, setfperm('XtestPerm', 'rwx------'))
call assert_fails("call setfperm(['Xfile'], 'rw-rw-rw-')", 'E730:')
call assert_fails("call setfperm('Xfile', [])", 'E730:')
call assert_fails("call setfperm('Xfile', 'rwxrwxrwxrw')", 'E475:')
call assert_fails("call setfperm(['Xpermfile'], 'rw-rw-rw-')", 'E730:')
call assert_fails("call setfperm('Xpermfile', [])", 'E730:')
call assert_fails("call setfperm('Xpermfile', 'rwxrwxrwxrw')", 'E475:')
endfunc
" vim: shiftwidth=2 sts=2 expandtab

View File

@@ -1,5 +1,10 @@
" Test :setfiletype
" Make VIMRUNTIME and &rtp absolute.
" Otherwise, a :lcd inside a test would break the relative ../../runtime path.
let $VIMRUNTIME = fnamemodify($VIMRUNTIME, ':p')
let &rtp = join(map(split(&rtp, ','), 'fnamemodify(v:val, ":p")'), ',')
func Test_backup_strip()
filetype on
let fname = 'Xdetect.js~~~~~~~~~~~'
@@ -186,7 +191,7 @@ func s:GetFilenameChecks() abort
\ 'coco': ['file.atg'],
\ 'conaryrecipe': ['file.recipe'],
\ 'conf': ['auto.master', 'file.conf', 'texdoc.cnf', '.x11vncrc', '.chktexrc', '.ripgreprc', 'ripgreprc', 'file.ctags'],
\ 'config': ['configure.in', 'configure.ac', '/etc/hostname.file', 'any/etc/hostname.file'],
\ 'config': ['/etc/hostname.file', 'any/etc/hostname.file', 'configure.in', 'configure.ac', 'file.at', 'aclocal.m4'],
\ 'confini': ['pacman.conf', 'paru.conf', 'mpv.conf', 'any/.aws/config', 'any/.aws/credentials', 'file.nmconnection',
\ 'any/.gnuradio/grc.conf', 'any/gnuradio/config.conf', 'any/gnuradio/conf.d/modtool.conf'],
\ 'context': ['tex/context/any/file.tex', 'file.mkii', 'file.mkiv', 'file.mkvi', 'file.mkxl', 'file.mklx'],
@@ -303,7 +308,8 @@ func s:GetFilenameChecks() abort
\ 'fstab': ['fstab', 'mtab'],
\ 'func': ['file.fc'],
\ 'fusion': ['file.fusion'],
\ 'fvwm': ['/.fvwm/file', 'any/.fvwm/file'],
\ 'fvwm': ['/.fvwm/file', 'any/.fvwm/file', '.fvwmrc', 'foo.fvwmrc', 'fvwmrc.foo', '.fvwm2rc', 'foo.fvwm2rc', 'fvwm2rc.foo', 'foo.fvwm95.hook', 'fvwm95.foo.hook'],
\ 'fvwm2m4': ['.fvwm2rc.m4', 'foo.fvwm2rc.m4', 'fvwm2rc.foo.m4'],
\ 'gdb': ['.gdbinit', 'gdbinit', '.cuda-gdbinit', 'cuda-gdbinit', 'file.gdb', '.config/gdbearlyinit', '.gdbearlyinit'],
\ 'gdmo': ['file.mo', 'file.gdmo'],
\ 'gdresource': ['file.tscn', 'file.tres'],
@@ -463,7 +469,7 @@ func s:GetFilenameChecks() abort
\ 'any/m17n-db/file.ali', 'any/m17n-db/file.cs', 'any/m17n-db/file.dir', 'any/m17n-db/FLT/file.flt', 'any/m17n-db/file.fst', 'any/m17n-db/LANGDATA/file.lnm', 'any/m17n-db/file.mic', 'any/m17n-db/MIM/file.mim', 'any/m17n-db/file.tbl'],
\ 'm3build': ['m3makefile', 'm3overrides'],
\ 'm3quake': ['file.quake', 'cm3.cfg'],
\ 'm4': ['file.at', '.m4_history'],
\ 'm4': ['.m4_history'],
\ 'mail': ['snd.123', '.letter', '.letter.123', '.followup', '.article', '.article.123', 'pico.123', 'mutt-xx-xxx', 'muttng-xx-xxx', 'ae123.txt', 'file.eml', 'reportbug-file'],
\ 'mailaliases': ['/etc/mail/aliases', '/etc/aliases', 'any/etc/aliases', 'any/etc/mail/aliases'],
\ 'mailcap': ['.mailcap', 'mailcap'],
@@ -1146,6 +1152,43 @@ endfunc
" Keep sorted.
"""""""""""""""""""""""""""""""""""""""""""""""""
" Since dist#ft#FTm4() looks around for configure.ac
" the test needs to isolate itself in a fresh temporary project tree,
" so that no configure.ac from another test (or from the repo root)
" accidentally influences detection.
func Test_autoconf_file()
filetype on
" Make a fresh sandbox far away from any configure.ac
let save_cwd = getcwd()
call mkdir('Xproj_autoconf/a/b', 'p')
execute 'lcd Xproj_autoconf/a/b'
try
call writefile(['AC_CHECK_HEADERS([stdio.h])'], 'foo.m4', 'D')
split foo.m4
call assert_equal('config', &filetype)
bwipe!
call writefile(['AS_IF([true], [:])'], 'bar.m4', 'D')
split bar.m4
call assert_equal('config', &filetype)
bwipe!
call writefile(['AC_INIT([foo],[1.0])'], 'configure.ac')
call mkdir('m4', 'p')
call writefile([], 'm4/empty.m4', 'D')
split m4/empty.m4
call assert_equal('config', &filetype)
bwipe!
finally
call delete('m4', 'rf')
call delete('configure.ac')
execute 'lcd' fnameescape(save_cwd)
call delete('Xproj_autoconf', 'rf')
filetype off
endtry
endfunc
func Test_bas_file()
filetype on
@@ -1891,6 +1934,30 @@ func Test_m_file()
filetype off
endfunc
" Since dist#ft#FTm4() looks around for configure.ac
" the test needs to isolate itself in a fresh temporary project tree,
" so that no configure.ac from another test (or from the repo root)
" accidentally influences detection.
func Test_m4_file()
filetype on
let save_cwd = getcwd()
" Make a fresh sandbox far away from any configure.ac
call mkdir('Xsandbox/level1/level2', 'p')
execute 'lcd Xsandbox/level1/level2'
try
call writefile(["define(`FOO', `bar')", "FOO"], 'plain.m4', 'D')
split plain.m4
call assert_equal('m4', &filetype)
bwipe!
finally
execute 'lcd' fnameescape(save_cwd)
call delete('Xsandbox', 'rf')
filetype off
endtry
endfunc
func Test_mod_file()
filetype on
@@ -3047,4 +3114,23 @@ func Test_diff_format()
filetype off
endfunc
func Test_m4_format()
filetype on
call mkdir('Xm4', 'D')
cd Xm4
call writefile([''], 'alocal.m4', 'D')
split alocal.m4
call assert_equal('m4', &filetype)
bwipe!
" an accompanying configure.ac in the current directory changes the filetype
call writefile([''], 'configure.ac')
split alocal.m4
call assert_equal('config', &filetype)
bwipe!
cd -
filetype off
endfunc
" vim: shiftwidth=2 sts=2 expandtab

View File

@@ -21,7 +21,7 @@ function Test_hide()
new Xf1
set modified
call assert_fails('edit Xf2')
call assert_fails('edit Xf2', 'E37: No write since last change (add ! to override)')
bwipeout! Xf1
new Xf1

View File

@@ -1156,7 +1156,7 @@ func Test_mkvimrc()
" set pastetoggle=<F5>
set wildchar=<F6>
set wildcharm=<F7>
call assert_fails('mkvimrc Xtestvimrc')
call assert_fails('mkvimrc Xtestvimrc', 'E189: "Xtestvimrc" exists')
mkvimrc! Xtestvimrc
" call assert_notequal(-1, index(readfile('Xtestvimrc'), 'set pastetoggle=<F5>'))
call assert_notequal(-1, index(readfile('Xtestvimrc'), 'set wildchar=<F6>'))

View File

@@ -176,7 +176,7 @@ func Test_signcolumn()
call assert_equal("auto", &signcolumn)
set signcolumn=yes
set signcolumn=no
call assert_fails('set signcolumn=nope')
call assert_fails('set signcolumn=nope', 'E474: Invalid argument: signcolumn=nope')
endfunc
func Test_filetype_valid()

View File

@@ -220,7 +220,7 @@ func Test_undo_del_chars()
call BackOne('3-456')
call BackOne('23-456')
call BackOne('123-456')
call assert_fails("BackOne('123-456')")
call assert_fails("BackOne('123-456')", "E492: Not an editor command: BackOne('123-456')")
:" Delete three other characters and go back in time with g-
call feedkeys('$x', 'xt')
@@ -236,7 +236,7 @@ func Test_undo_del_chars()
call BackOne('3-456')
call BackOne('23-456')
call BackOne('123-456')
call assert_fails("BackOne('123-456')")
call assert_fails("BackOne('123-456')", "E492: Not an editor command: BackOne('123-456')")
normal 10g+
call assert_equal('123-', getline(1))

View File

@@ -387,14 +387,14 @@ func Test_vartabs_shiftwidth()
endfunc
func Test_vartabs_failures()
call assert_fails('set vts=8,')
call assert_fails('set vsts=8,')
call assert_fails('set vts=8,,8')
call assert_fails('set vsts=8,,8')
call assert_fails('set vts=8,,8,')
call assert_fails('set vsts=8,,8,')
call assert_fails('set vts=,8')
call assert_fails('set vsts=,8')
call assert_fails('set vts=8,', 'E475: Invalid argument: 8,')
call assert_fails('set vsts=8,', 'E475: Invalid argument: 8,')
call assert_fails('set vts=8,,8', 'E474: Invalid argument: vts=8,,8')
call assert_fails('set vsts=8,,8', 'E474: Invalid argument: vsts=8,,8')
call assert_fails('set vts=8,,8,', 'E474: Invalid argument: vts=8,,8,')
call assert_fails('set vsts=8,,8,', 'E474: Invalid argument: vsts=8,,8,')
call assert_fails('set vts=,8', 'E474: Invalid argument: vts=,8')
call assert_fails('set vsts=,8', 'E474: Invalid argument: vsts=,8')
endfunc
func Test_vartabs_reset()

View File

@@ -77,7 +77,7 @@ func s:make_buffer_trio()
edit! third
let l:third = bufnr()
execute ":buffer! " . l:second
exe $":buffer! {l:second}"
return [l:first, l:second, l:third]
endfunc
@@ -180,11 +180,11 @@ endfunc
" Create a quickfix with at least 2 entries that are in the current 'winfixbuf' window.
func s:make_quickfix_windows()
let [l:current, _] = s:make_simple_quickfix()
execute "buffer! " . l:current
exe $"buffer! {l:current}"
split
let l:first_window = win_getid()
execute "normal \<C-w>j"
exe "normal \<C-w>j"
let l:winfix_window = win_getid()
" Open the quickfix in a separate split and go to it
@@ -210,7 +210,7 @@ func s:set_quickfix_by_buffer(buffer)
let l:index = 1 " quickfix indices start at 1
for l:entry in getqflist()
if l:entry["bufnr"] == a:buffer
execute l:index . "cc"
exe $"{l:index} cc"
return
endif
@@ -218,7 +218,7 @@ func s:set_quickfix_by_buffer(buffer)
let l:index += 1
endfor
echoerr 'No quickfix entry matching "' . a:buffer . '" could be found.'
echoerr $'No quickfix entry matching {a:buffer} could be found.'
endfunc
" Fail to call :Next on a 'winfixbuf' window unless :Next! is used.
@@ -253,7 +253,7 @@ func Test_argdo_choose_available_window()
split
let l:nowinfixbuf_window = win_getid()
" Move to the 'winfixbuf' window now
execute "normal \<C-w>j"
exe "normal \<C-w>j"
let l:winfixbuf_window = win_getid()
let l:expected_windows = s:get_windows_count()
@@ -274,7 +274,7 @@ func Test_argdo_make_new_window()
argdo echo ''
call assert_notequal(l:current, win_getid())
call assert_equal(l:last, bufnr())
execute "normal \<C-w>j"
exe "normal \<C-w>j"
call assert_equal(l:first, bufnr())
call assert_equal(l:current_windows + 1, s:get_windows_count())
endfunc
@@ -313,7 +313,7 @@ func Test_arglocal()
let l:other = s:make_buffer_pairs()
let l:current = bufnr()
argglobal! other
execute "buffer! " . l:current
exe $"buffer! {l:current}"
call assert_fails("arglocal other", "E1513:")
call assert_equal(l:current, bufnr())
@@ -422,9 +422,9 @@ func Test_bmodified()
let l:other = s:make_buffer_pairs()
let l:current = bufnr()
execute "buffer! " . l:other
exe $"buffer! {l:other}"
set modified
execute "buffer! " . l:current
exe $"buffer! {l:current}"
call assert_fails("bmodified", "E1513:")
call assert_equal(l:current, bufnr())
@@ -535,7 +535,7 @@ func Test_bufdo_choose_available_window()
split
let l:nowinfixbuf_window = win_getid()
" Move to the 'winfixbuf' window now
execute "normal \<C-w>j"
exe "normal \<C-w>j"
let l:winfixbuf_window = win_getid()
let l:current = bufnr()
@@ -554,14 +554,14 @@ func Test_bufdo_make_new_window()
call s:reset_all_buffers()
let [l:first, l:last] = s:make_buffers_list()
execute "buffer! " . l:first
exe $"buffer! {l:first}"
let l:current = win_getid()
let l:current_windows = s:get_windows_count()
bufdo echo ''
call assert_notequal(l:current, win_getid())
call assert_equal(l:last, bufnr())
execute "normal \<C-w>j"
exe "normal \<C-w>j"
call assert_equal(l:first, bufnr())
call assert_equal(l:current_windows + 1, s:get_windows_count())
endfunc
@@ -573,10 +573,10 @@ func Test_buffer()
let l:other = s:make_buffer_pairs()
let l:current = bufnr()
call assert_fails("buffer " . l:other, "E1513:")
call assert_fails($"buffer {l:other}", "E1513:")
call assert_equal(l:current, bufnr())
execute "buffer! " . l:other
exe $"buffer! {l:other}"
call assert_equal(l:other, bufnr())
endfunc
@@ -587,10 +587,10 @@ func Test_buffer_same_buffer()
call s:make_buffer_pairs()
let l:current = bufnr()
execute "buffer " . l:current
exe $"buffer {l:current}"
call assert_equal(l:current, bufnr())
execute "buffer! " . l:current
exe $"buffer! {l:current}"
call assert_equal(l:current, bufnr())
endfunc
@@ -640,7 +640,7 @@ func Test_caddexpr()
let l:file_path = tempname()
call writefile(["Error - bad-thing-found"], l:file_path, 'D')
execute "edit " . l:file_path
exe $"edit {l:file_path}"
let l:file_buffer = bufnr()
let l:current = bufnr()
@@ -651,9 +651,9 @@ func Test_caddexpr()
set winfixbuf
execute "buffer! " . l:file_buffer
exe $"buffer! {l:file_buffer}"
execute 'caddexpr expand("%") .. ":" .. line(".") .. ":" .. getline(".")'
exe 'caddexpr expand("%") .. ":" .. line(".") .. ":" .. getline(".")'
call assert_equal(l:current, bufnr())
endfunc
@@ -664,7 +664,7 @@ func Test_cbuffer()
let l:file_path = tempname()
call writefile(["first.unittest:1:Error - bad-thing-found"], l:file_path, 'D')
execute "edit " . l:file_path
exe $"edit {l:file_path}"
let l:file_buffer = bufnr()
let l:current = bufnr()
@@ -675,12 +675,12 @@ func Test_cbuffer()
set winfixbuf
execute "buffer! " . l:file_buffer
exe $"buffer! {file_buffer}"
call assert_fails("cbuffer " . l:file_buffer)
call assert_equal(l:current, bufnr())
call assert_fails($"cbuffer {file_buffer}", "E1513: Cannot switch buffer. 'winfixbuf' is enabled")
call assert_equal(current, bufnr())
execute "cbuffer! " . l:file_buffer
exe $"cbuffer! {file_buffer}"
call assert_equal("first.unittest", expand("%:t"))
endfunc
@@ -715,7 +715,7 @@ func Test_cdo_choose_available_window()
call s:reset_all_buffers()
let [l:current, l:last] = s:make_simple_quickfix()
execute "buffer! " . l:current
exe $"buffer! {l:current}"
" Make a split window that is 'nowinfixbuf' but make it the second-to-last
" window so that :cdo will first try the 'winfixbuf' window, pass over it,
@@ -729,7 +729,7 @@ func Test_cdo_choose_available_window()
split
let l:nowinfixbuf_window = win_getid()
" Move to the 'winfixbuf' window now
execute "normal \<C-w>j"
exe "normal \<C-w>j"
let l:winfixbuf_window = win_getid()
let l:expected_windows = s:get_windows_count()
@@ -737,7 +737,7 @@ func Test_cdo_choose_available_window()
call assert_equal(l:nowinfixbuf_window, win_getid())
call assert_equal(l:last, bufnr())
execute "normal \<C-w>j"
exe "normal \<C-w>j"
call assert_equal(l:current, bufnr())
call assert_equal(l:expected_windows, s:get_windows_count())
endfunc
@@ -748,7 +748,7 @@ func Test_cdo_make_new_window()
call s:reset_all_buffers()
let [l:current_buffer, l:last] = s:make_simple_quickfix()
execute "buffer! " . l:current_buffer
exe $"buffer! {l:current_buffer}"
let l:current_window = win_getid()
let l:current_windows = s:get_windows_count()
@@ -756,7 +756,7 @@ func Test_cdo_make_new_window()
cdo echo ''
call assert_notequal(l:current_window, win_getid())
call assert_equal(l:last, bufnr())
execute "normal \<C-w>j"
exe "normal \<C-w>j"
call assert_equal(l:current_buffer, bufnr())
call assert_equal(l:current_windows + 1, s:get_windows_count())
endfunc
@@ -766,17 +766,17 @@ func Test_cexpr()
CheckFeature quickfix
call s:reset_all_buffers()
let l:file = tempname()
let l:entry = '["' . l:file . ':1:bar"]'
let l:current = bufnr()
let file = tempname()
let entry = $'["{file}:1:bar"]'
let current = bufnr()
set winfixbuf
call assert_fails("cexpr " . l:entry)
call assert_equal(l:current, bufnr())
call assert_fails($"cexpr {entry}", "E1513: Cannot switch buffer. 'winfixbuf' is enabled")
call assert_equal(current, bufnr())
execute "cexpr! " . l:entry
call assert_equal(fnamemodify(l:file, ":t"), expand("%:t"))
exe $"cexpr! {entry}"
call assert_equal(fnamemodify(file, ":t"), expand("%:t"))
endfunc
" Call :cfdo and choose the next available 'nowinfixbuf' window.
@@ -785,7 +785,7 @@ func Test_cfdo_choose_available_window()
call s:reset_all_buffers()
let [l:current, l:last] = s:make_simple_quickfix()
execute "buffer! " . l:current
exe $"buffer! {l:current}"
" Make a split window that is 'nowinfixbuf' but make it the second-to-last
" window so that :cfdo will first try the 'winfixbuf' window, pass over it,
@@ -799,7 +799,7 @@ func Test_cfdo_choose_available_window()
split
let l:nowinfixbuf_window = win_getid()
" Move to the 'winfixbuf' window now
execute "normal \<C-w>j"
exe "normal \<C-w>j"
let l:winfixbuf_window = win_getid()
let l:expected_windows = s:get_windows_count()
@@ -807,7 +807,7 @@ func Test_cfdo_choose_available_window()
call assert_equal(l:nowinfixbuf_window, win_getid())
call assert_equal(l:last, bufnr())
execute "normal \<C-w>j"
exe "normal \<C-w>j"
call assert_equal(l:current, bufnr())
call assert_equal(l:expected_windows, s:get_windows_count())
endfunc
@@ -818,7 +818,7 @@ func Test_cfdo_make_new_window()
call s:reset_all_buffers()
let [l:current_buffer, l:last] = s:make_simple_quickfix()
execute "buffer! " . l:current_buffer
exe $"buffer! {l:current_buffer}"
let l:current_window = win_getid()
let l:current_windows = s:get_windows_count()
@@ -826,7 +826,7 @@ func Test_cfdo_make_new_window()
cfdo echo ''
call assert_notequal(l:current_window, win_getid())
call assert_equal(l:last, bufnr())
execute "normal \<C-w>j"
exe "normal \<C-w>j"
call assert_equal(l:current_buffer, bufnr())
call assert_equal(l:current_windows + 1, s:get_windows_count())
endfunc
@@ -839,26 +839,26 @@ func Test_cfile()
edit first.unittest
call append(0, ["some-search-term bad-thing-found"])
write
let l:first = bufnr()
let first = bufnr()
edit! second.unittest
call append(0, ["some-search-term"])
write
let l:file = tempname()
call writefile(["first.unittest:1:Error - bad-thing-found was detected"], l:file)
let file = tempname()
call writefile(["first.unittest:1:Error - bad-thing-found was detected"], file)
let l:current = bufnr()
let current = bufnr()
set winfixbuf
call assert_fails(":cfile " . l:file)
call assert_equal(l:current, bufnr())
call assert_fails($":cfile {file}", "E1513: Cannot switch buffer. 'winfixbuf' is enabled")
call assert_equal(current, bufnr())
execute ":cfile! " . l:file
call assert_equal(l:first, bufnr())
exe $":cfile! {file}"
call assert_equal(first, bufnr())
call delete(l:file)
call delete(file)
call delete("first.unittest")
call delete("second.unittest")
endfunc
@@ -932,7 +932,7 @@ func Test_cnext_no_previous_window()
call s:reset_all_buffers()
let [l:current, _] = s:make_simple_quickfix()
execute "buffer! " . l:current
exe $"buffer! {l:current}"
let l:expected = s:get_windows_count()
@@ -1058,7 +1058,7 @@ func Test_ctrl_w_f()
call setline(1, l:file_name)
let l:current_windows = s:get_windows_count()
execute "normal \<C-w>f"
exe "normal \<C-w>f"
call assert_equal(l:current_windows + 1, s:get_windows_count())
@@ -1069,9 +1069,9 @@ endfunc
func Test_djump()
call s:reset_all_buffers()
let l:include_file = tempname() . ".h"
let l:include_file = tempname() .. ".h"
call writefile(["min(1, 12);",
\ '#include "' . l:include_file . '"'
\ $'#include "{l:include_file}"'
\ ],
\ "main.c")
call writefile(["#define min(X, Y) ((X) < (Y) ? (X) : (Y))"], l:include_file)
@@ -1135,23 +1135,23 @@ func Test_edit_different_buffer_on_disk_and_relative_path_to_disk()
let l:file_on_disk = tempname()
let l:directory_on_disk1 = fnamemodify(l:file_on_disk, ":p:h")
let l:name = fnamemodify(l:file_on_disk, ":t")
execute "edit " . l:file_on_disk
exe $"edit {l:file_on_disk}"
write!
let l:directory_on_disk2 = l:directory_on_disk1 . "_something_else"
let l:directory_on_disk2 = l:directory_on_disk1 .. "_something_else"
if !isdirectory(l:directory_on_disk2)
call mkdir(l:directory_on_disk2)
endif
execute "cd " . l:directory_on_disk2
execute "edit " l:name
exe $"cd {l:directory_on_disk2}"
exe $"edit {l:name}"
let l:current = bufnr()
call assert_equal(l:current, bufnr())
set winfixbuf
call assert_fails("edit " . l:file_on_disk, "E1513:")
call assert_fails($"edit {l:file_on_disk}", "E1513:")
call assert_equal(l:current, bufnr())
call delete(l:directory_on_disk1)
@@ -1174,26 +1174,26 @@ func Test_edit_different_buffer_on_disk_and_relative_path_to_memory()
let l:file_on_disk = tempname()
let l:directory_on_disk1 = fnamemodify(l:file_on_disk, ":p:h")
let l:name = fnamemodify(l:file_on_disk, ":t")
execute "edit " . l:file_on_disk
exe $"edit {l:file_on_disk}"
write!
let l:directory_on_disk2 = l:directory_on_disk1 . "_something_else"
let l:directory_on_disk2 = l:directory_on_disk1 .. "_something_else"
if !isdirectory(l:directory_on_disk2)
call mkdir(l:directory_on_disk2)
endif
execute "cd " . l:directory_on_disk2
execute "edit " l:name
execute "cd " . l:directory_on_disk1
execute "edit " l:file_on_disk
execute "cd " . l:directory_on_disk2
exe $"cd {l:directory_on_disk2}"
exe $"edit {l:name}"
exe $"cd {l:directory_on_disk1}"
exe $"edit {l:file_on_disk}"
exe $"cd {l:directory_on_disk2}"
let l:current = bufnr()
call assert_equal(l:current, bufnr())
set winfixbuf
call assert_fails("edit " . l:name, "E1513:")
call assert_fails($"edit {l:name}", "E1513:")
call assert_equal(l:current, bufnr())
call delete(l:directory_on_disk1)
@@ -1252,12 +1252,12 @@ func Test_edit_same_buffer_on_disk_absolute_path()
call writefile([], file, 'D')
let file = fnamemodify(file, ':p')
let current = bufnr()
execute "edit " . file
exe $"edit {file}"
write!
call assert_equal(current, bufnr())
set winfixbuf
execute "edit " file
exe $"edit {file}"
call assert_equal(current, bufnr())
set nowinfixbuf
@@ -1304,17 +1304,17 @@ func Test_find()
let l:name = fnamemodify(l:file, ":p:t")
let l:original_path = &path
execute "set path=" . l:directory
exe $"set path={l:directory}"
set winfixbuf
call assert_fails("execute 'find " . l:name . "'", "E1513:")
call assert_fails($"exe 'find {l:name}'", "E1513:")
call assert_equal(l:current, bufnr())
execute "find! " . l:name
exe $"find! {l:name}"
call assert_equal(l:file, expand("%:p"))
execute "set path=" . l:original_path
exe $"set path={l:original_path}"
endfunc
" Fail :first but :first! is allowed
@@ -1357,7 +1357,7 @@ func Test_grep()
call assert_fails("silent! grep some-search-term *.unittest", "E1513:")
call assert_equal(l:current, bufnr())
execute "edit! " . l:first
exe $"edit! {l:first}"
silent! grep! some-search-term *.unittest
call assert_notequal(l:first, bufnr())
@@ -1371,9 +1371,9 @@ endfunc
func Test_ijump()
call s:reset_all_buffers()
let l:include_file = tempname() . ".h"
let l:include_file = tempname() .. ".h"
call writefile([
\ '#include "' . l:include_file . '"'
\ $'#include "{l:include_file}"'
\ ],
\ "main.c", 'D')
call writefile(["#define min(X, Y) ((X) < (Y) ? (X) : (Y))"], l:include_file, 'D')
@@ -1463,7 +1463,7 @@ func Test_laddexpr()
let l:file_path = tempname()
call writefile(["Error - bad-thing-found"], l:file_path, 'D')
execute "edit " . l:file_path
exe $"edit {l:file_path}"
let l:file_buffer = bufnr()
let l:current = bufnr()
@@ -1474,9 +1474,9 @@ func Test_laddexpr()
set winfixbuf
execute "buffer! " . l:file_buffer
exe $"buffer! {l:file_buffer}"
execute 'laddexpr expand("%") .. ":" .. line(".") .. ":" .. getline(".")'
exe 'laddexpr expand("%") .. ":" .. line(".") .. ":" .. getline(".")'
call assert_equal(l:current, bufnr())
endfunc
@@ -1501,7 +1501,7 @@ func Test_lbuffer()
let l:file_path = tempname()
call writefile(["first.unittest:1:Error - bad-thing-found"], l:file_path, 'D')
execute "edit " . l:file_path
exe $"edit {l:file_path}"
let l:file_buffer = bufnr()
let l:current = bufnr()
@@ -1512,12 +1512,12 @@ func Test_lbuffer()
set winfixbuf
execute "buffer! " . l:file_buffer
exe $"buffer! {file_buffer}"
call assert_fails("lbuffer " . l:file_buffer)
call assert_equal(l:current, bufnr())
call assert_fails($"lbuffer {file_buffer}", "E1513: Cannot switch buffer. 'winfixbuf' is enabled")
call assert_equal(current, bufnr())
execute "lbuffer! " . l:file_buffer
exe $"lbuffer! {file_buffer}"
call assert_equal("first.unittest", expand("%:t"))
endfunc
@@ -1529,9 +1529,9 @@ func Test_ldo()
let [l:first, l:middle, l:last] = s:make_simple_location_list()
lnext!
call assert_fails('execute "ldo buffer ' . l:first . '"', "E1513:")
call assert_fails($'exe "ldo buffer {l:first}"', "E1513:")
call assert_equal(l:middle, bufnr())
execute "ldo! buffer " . l:first
exe $"ldo! buffer {l:first}"
call assert_notequal(l:last, bufnr())
endfunc
@@ -1540,17 +1540,17 @@ func Test_lexpr()
CheckFeature quickfix
call s:reset_all_buffers()
let l:file = tempname()
let l:entry = '["' . l:file . ':1:bar"]'
let l:current = bufnr()
let file = tempname()
let entry = $'["{file}:1:bar"]'
let current = bufnr()
set winfixbuf
call assert_fails("lexpr " . l:entry)
call assert_equal(l:current, bufnr())
call assert_fails($"lexpr {entry}", "E1513: Cannot switch buffer. 'winfixbuf' is enabled")
call assert_equal(current, bufnr())
execute "lexpr! " . l:entry
call assert_equal(fnamemodify(l:file, ":t"), expand("%:t"))
exe $"lexpr! {entry}"
call assert_equal(fnamemodify(file, ":t"), expand("%:t"))
endfunc
" Fail :lfdo but :lfdo! is allowed
@@ -1558,13 +1558,13 @@ func Test_lfdo()
CheckFeature quickfix
call s:reset_all_buffers()
let [l:first, l:middle, l:last] = s:make_simple_location_list()
let [first, middle, last] = s:make_simple_location_list()
lnext!
call assert_fails('execute "lfdo buffer ' . l:first . '"', "E1513:")
call assert_equal(l:middle, bufnr())
execute "lfdo! buffer " . l:first
call assert_notequal(l:last, bufnr())
call assert_fails('exe "lfdo buffer ' .. first .. '"', "E1513:")
call assert_equal(middle, bufnr())
exe $"lfdo! buffer {first}"
call assert_notequal(last, bufnr())
endfunc
" Fail :lfile but :lfile! is allowed
@@ -1588,10 +1588,10 @@ func Test_lfile()
set winfixbuf
call assert_fails(":lfile " . l:file)
call assert_fails($":lfile {l:file}", "E1513: Cannot switch buffer. 'winfixbuf' is enabled")
call assert_equal(l:current, bufnr())
execute ":lfile! " . l:file
exe $":lfile! {l:file}"
call assert_equal(l:first, bufnr())
call delete("first.unittest")
@@ -1606,15 +1606,15 @@ func Test_ll()
let [l:first, l:middle, l:last] = s:make_simple_location_list()
lopen
lfirst!
execute "normal \<C-w>j"
exe "normal \<C-w>j"
normal j
call assert_fails(".ll", "E1513:")
execute "normal \<C-w>k"
exe "normal \<C-w>k"
call assert_equal(l:first, bufnr())
execute "normal \<C-w>j"
exe "normal \<C-w>j"
.ll!
execute "normal \<C-w>k"
exe "normal \<C-w>k"
call assert_equal(l:middle, bufnr())
endfunc
@@ -1737,7 +1737,7 @@ func Test_ltag()
call writefile(["one", "two", "three"], "Xfile", 'D')
call writefile(["one"], "Xother", 'D')
edit Xother
execute "normal \<C-]>"
exe "normal \<C-]>"
set winfixbuf
@@ -1765,10 +1765,10 @@ func Test_lua_command()
set winfixbuf
call assert_fails('lua vim.cmd("buffer " .. ' . l:previous . ')')
call assert_fails($'lua vim.cmd("buffer " .. {l:previous})')
call assert_equal(l:current, bufnr())
execute 'lua vim.cmd("buffer! " .. ' . l:previous . ')'
exe $'lua vim.cmd("buffer! " .. {l:previous})'
call assert_equal(l:previous, bufnr())
endfunc
@@ -1829,7 +1829,7 @@ func Test_lvimgrepadd()
buffer! winfix.unittest
call assert_fails("lvimgrepadd /some-search-term/ *.unittest")
call assert_fails("lvimgrepadd /some-search-term/ *.unittest", "E1513: Cannot switch buffer. 'winfixbuf' is enabled")
call assert_equal(l:current, bufnr())
lvimgrepadd! /some-search-term/ *.unittest
@@ -1846,9 +1846,9 @@ func Test_marks_mappings_fail()
let l:other = s:make_buffer_pairs()
let l:current = bufnr()
execute "buffer! " . l:other
exe $"buffer! {l:other}"
normal mA
execute "buffer! " . l:current
exe $"buffer! {l:current}"
normal mB
call assert_fails("normal `A", "E1513:")
@@ -1982,7 +1982,7 @@ func Test_normal_g_rightmouse()
call writefile(["one", "two", "three"], "Xfile", 'D')
call writefile(["one"], "Xother", 'D')
edit Xother
execute "normal \<C-]>"
exe "normal \<C-]>"
set winfixbuf
@@ -2033,7 +2033,7 @@ func Test_normal_ctrl_rightmouse()
call writefile(["one", "two", "three"], "Xfile", 'D')
call writefile(["one"], "Xother", 'D')
edit Xother
execute "normal \<C-]>"
exe "normal \<C-]>"
set winfixbuf
@@ -2059,7 +2059,7 @@ func Test_normal_ctrl_t()
call writefile(["one", "two", "three"], "Xfile", 'D')
call writefile(["one"], "Xother", 'D')
edit Xother
execute "normal \<C-]>"
exe "normal \<C-]>"
set winfixbuf
@@ -2108,12 +2108,12 @@ func Test_normal_ctrl_i_pass()
" Go up another line
normal m`
normal k
execute "normal \<C-o>"
exe "normal \<C-o>"
set winfixbuf
let l:line = getcurpos()[1]
execute "normal 1\<C-i>"
exe "normal 1\<C-i>"
call assert_notequal(l:line, getcurpos()[1])
endfunc
@@ -2157,7 +2157,7 @@ func Test_normal_ctrl_o_pass()
set winfixbuf
execute "normal \<C-o>"
exe "normal \<C-o>"
call assert_equal(l:current, bufnr())
endfunc
@@ -2202,7 +2202,7 @@ func Test_normal_ctrl_w_ctrl_square_bracket_right()
set winfixbuf
let l:current_windows = s:get_windows_count()
execute "normal \<C-w>\<C-]>"
exe "normal \<C-w>\<C-]>"
call assert_equal(l:current_windows + 1, s:get_windows_count())
set tags&
@@ -2225,7 +2225,7 @@ func Test_normal_ctrl_w_g_ctrl_square_bracket_right()
set winfixbuf
let l:current_windows = s:get_windows_count()
execute "normal \<C-w>g\<C-]>"
exe "normal \<C-w>g\<C-]>"
call assert_equal(l:current_windows + 1, s:get_windows_count())
set tags&
@@ -2343,9 +2343,9 @@ endfunc
func Test_normal_square_bracket_left_ctrl_d()
call s:reset_all_buffers()
let l:include_file = tempname() . ".h"
let l:include_file = tempname() .. ".h"
call writefile(["min(1, 12);",
\ '#include "' . l:include_file . '"'
\ $'#include "{l:include_file}"'
\ ],
\ "main.c", 'D')
call writefile(["#define min(X, Y) ((X) < (Y) ? (X) : (Y))"], l:include_file, 'D')
@@ -2361,7 +2361,7 @@ func Test_normal_square_bracket_left_ctrl_d()
set nowinfixbuf
execute "normal [\<C-d>"
exe "normal [\<C-d>"
call assert_notequal(l:current, bufnr())
endfunc
@@ -2369,9 +2369,9 @@ endfunc
func Test_normal_square_bracket_right_ctrl_d()
call s:reset_all_buffers()
let l:include_file = tempname() . ".h"
let l:include_file = tempname() .. ".h"
call writefile(["min(1, 12);",
\ '#include "' . l:include_file . '"'
\ $'#include "{l:include_file}"'
\ ],
\ "main.c", 'D')
call writefile(["#define min(X, Y) ((X) < (Y) ? (X) : (Y))"], l:include_file, 'D')
@@ -2386,7 +2386,7 @@ func Test_normal_square_bracket_right_ctrl_d()
set nowinfixbuf
execute "normal ]\<C-d>"
exe "normal ]\<C-d>"
call assert_notequal(l:current, bufnr())
endfunc
@@ -2394,8 +2394,8 @@ endfunc
func Test_normal_square_bracket_left_ctrl_i()
call s:reset_all_buffers()
let l:include_file = tempname() . ".h"
call writefile(['#include "' . l:include_file . '"',
let l:include_file = tempname() .. ".h"
call writefile([$'#include "{l:include_file}"',
\ "min(1, 12);",
\ ],
\ "main.c", 'D')
@@ -2416,7 +2416,7 @@ func Test_normal_square_bracket_left_ctrl_i()
set nowinfixbuf
execute "normal [\<C-i>"
exe "normal [\<C-i>"
call assert_notequal(l:current, bufnr())
set define&
@@ -2428,9 +2428,9 @@ endfunc
func Test_normal_square_bracket_right_ctrl_i()
call s:reset_all_buffers()
let l:include_file = tempname() . ".h"
let l:include_file = tempname() .. ".h"
call writefile(["min(1, 12);",
\ '#include "' . l:include_file . '"'
\ $'#include "{l:include_file}"'
\ ],
\ "main.c", 'D')
call writefile(["#define min(X, Y) ((X) < (Y) ? (X) : (Y))"], l:include_file, 'D')
@@ -2449,7 +2449,7 @@ func Test_normal_square_bracket_right_ctrl_i()
set nowinfixbuf
execute "normal ]\<C-i>"
exe "normal ]\<C-i>"
call assert_notequal(l:current, bufnr())
set define&
@@ -2541,7 +2541,7 @@ func Test_pedit()
pedit other
execute "normal \<C-w>w"
exe "normal \<C-w>w"
call assert_equal(l:other, bufnr())
endfunc
@@ -2551,9 +2551,9 @@ func Test_pbuffer()
let l:other = s:make_buffer_pairs()
exe 'pbuffer ' . l:other
exe $'pbuffer {l:other}'
execute "normal \<C-w>w"
exe "normal \<C-w>w"
call assert_equal(l:other, bufnr())
endfunc
@@ -2738,7 +2738,7 @@ func Test_remap_key_pass()
" Disallow <C-^> by default but allow it if the command does something else
nnoremap <C-^> :echo "hello!"
execute "normal \<C-^>"
exe "normal \<C-^>"
call assert_equal(l:current, bufnr())
nunmap <C-^>
@@ -2817,7 +2817,7 @@ func Test_split_window()
call s:reset_all_buffers()
split
execute "normal \<C-w>j"
exe "normal \<C-w>j"
set winfixbuf
@@ -2845,7 +2845,7 @@ func Test_tNext()
edit Xother
tag thesame
execute "normal \<C-^>"
exe "normal \<C-^>"
tnext!
set winfixbuf
@@ -2878,7 +2878,7 @@ func Test_tabdo_choose_available_window()
split
let l:nowinfixbuf_window = win_getid()
" Move to the 'winfixbuf' window now
execute "normal \<C-w>j"
exe "normal \<C-w>j"
let l:winfixbuf_window = win_getid()
let l:expected_windows = s:get_windows_count()
@@ -2893,7 +2893,7 @@ func Test_tabdo_make_new_window()
call s:reset_all_buffers()
let [l:first, _] = s:make_buffers_list()
execute "buffer! " . l:first
exe $"buffer! {l:first}"
let l:current = win_getid()
let l:current_windows = s:get_windows_count()
@@ -2901,7 +2901,7 @@ func Test_tabdo_make_new_window()
tabdo echo ''
call assert_notequal(l:current, win_getid())
call assert_equal(l:first, bufnr())
execute "normal \<C-w>j"
exe "normal \<C-w>j"
call assert_equal(l:first, bufnr())
call assert_equal(l:current_windows + 1, s:get_windows_count())
endfunc
@@ -3033,7 +3033,7 @@ func Test_tnext()
edit Xother
tag thesame
execute "normal \<C-^>"
exe "normal \<C-^>"
set winfixbuf
@@ -3064,7 +3064,7 @@ func Test_tprevious()
edit Xother
tag thesame
execute "normal \<C-^>"
exe "normal \<C-^>"
tnext!
set winfixbuf
@@ -3130,7 +3130,7 @@ func Test_vimgrep()
buffer! winfix.unittest
call assert_fails("vimgrep /some-search-term/ *.unittest")
call assert_fails("vimgrep /some-search-term/ *.unittest", "E1513: Cannot switch buffer. 'winfixbuf' is enabled")
call assert_equal(l:current, bufnr())
" Don't error and also do swap to the first match because ! was included
@@ -3165,7 +3165,7 @@ func Test_vimgrepadd()
buffer! winfix.unittest
call assert_fails("vimgrepadd /some-search-term/ *.unittest")
call assert_fails("vimgrepadd /some-search-term/ *.unittest", "E1513: Cannot switch buffer. 'winfixbuf' is enabled")
call assert_equal(l:current, bufnr())
vimgrepadd! /some-search-term/ *.unittest
@@ -3210,10 +3210,10 @@ func Test_windo()
windo echo ''
call assert_equal(l:current_window, win_getid())
call assert_fails('execute "windo buffer ' . l:current_buffer . '"', "E1513:")
call assert_fails($'exe "windo buffer {l:current_buffer}"', "E1513:")
call assert_equal(l:current_window, win_getid())
execute "windo buffer! " . l:current_buffer
exe $"windo buffer! {l:current_buffer}"
call assert_equal(l:current_window, win_getid())
endfunc
@@ -3262,7 +3262,7 @@ func Test_quickfix_switchbuf_invalid_prevwin()
set switchbuf=uselast
split
copen
execute winnr('#') 'quit'
exe winnr('#') 'quit'
call assert_equal(2, winnr('$'))
cnext " Would've triggered a null pointer member access

View File

@@ -175,7 +175,7 @@ func Test_writefile_autowrite()
next
call assert_equal(['aaa'], readfile('Xa'))
call setline(1, 'bbb')
call assert_fails('edit XX')
call assert_fails('edit XX', 'E37: No write since last change (add ! to override)')
call assert_false(filereadable('Xb'))
set autowriteall