diff options
37 files changed, 558 insertions, 127 deletions
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 4dba469ebe..07fbd669e5 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -58,6 +58,11 @@ aa4f9c5341f5280f16cce0630ea54b84eef717b3 6ff245732a5a8ab821598a38fb0c5805e6bd3779 abf758a2977c4e6cab4dfa217f56da853d85851c cb84f5ee530f0f32b92bed5b4ad41344e8b551aa +f98b8d2d44d289263b1a3b33b6a7f20644ef671c +544ef994df72c3cbe0dca6b856ce2dcbc5169767 +45fe4d11add933df76a2ea4bf52ce8904f4a778b +517f0cc634b985057da5b95cf4ad659ee456a77e +04f2f864e270e772c6326cefdf24947f0130e492 # typos d238b8f6003d34cae7f65ff7585b48a2cd9449fb diff --git a/MAINTAIN.md b/MAINTAIN.md index f8bcfa2acf..cc9e11af6c 100644 --- a/MAINTAIN.md +++ b/MAINTAIN.md @@ -225,6 +225,15 @@ https://github.com/neovim/neovim-backup trying to produce images that work in the broadest number of environments, and therefore want to use older releases. +### Special labels + +Some github labels are used to trigger certain jobs: + +* `backport release-x.y` - backport to release branch +* `ci-s390x` - enable s390x CI +* `needs:response` - Close PR after a certain amount of time if author doesn't + respond + See also -------- diff --git a/runtime/colors/vim.lua b/runtime/colors/vim.lua index 67f9ddd643..b4ede93357 100644 --- a/runtime/colors/vim.lua +++ b/runtime/colors/vim.lua @@ -231,7 +231,7 @@ if vim.o.background == 'light' then hi('SpellRare', { sp = 'Magenta', undercurl = true, ctermbg = 'LightMagenta' }) hi('TabLine', { bg = 'LightGrey', underline = true, ctermfg = 'Black', ctermbg = 'LightGrey', cterm = { underline = true } }) hi('Title', { fg = 'Magenta', bold = true, ctermfg = 'DarkMagenta' }) - hi('Visual', { bg = 'LightGrey', ctermbg = 'LightGrey' }) + hi('Visual', { bg = 'LightGrey', ctermfg = 'White', ctermbg = 'DarkGrey' }) hi('WarningMsg', { fg = 'Red', ctermfg = 'DarkRed' }) hi('Comment', { fg = 'Blue', ctermfg = 'DarkBlue' }) hi('Constant', { fg = 'Magenta', ctermfg = 'DarkRed' }) @@ -270,7 +270,7 @@ else hi('SpellRare', { sp = 'Magenta', undercurl = true, ctermbg = 'Magenta' }) hi('TabLine', { bg = 'DarkGrey', underline = true, ctermfg = 'White', ctermbg = 'DarkGrey', cterm = { underline = true } }) hi('Title', { fg = 'Magenta', bold = true, ctermfg = 'LightMagenta' }) - hi('Visual', { bg = 'DarkGrey', ctermbg = 'DarkGrey' }) + hi('Visual', { bg = '#575757', ctermfg = 'Black', ctermbg = 'Grey' }) hi('WarningMsg', { fg = 'Red', ctermfg = 'LightRed' }) hi('Comment', { fg = '#80a0ff', ctermfg = 'Cyan' }) hi('Constant', { fg = '#ffa0a0', ctermfg = 'Magenta' }) diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 90c9d0ccbb..1831b78e9e 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -3336,6 +3336,13 @@ nvim_tabpage_set_var({tabpage}, {name}, {value}) • {name} Variable name • {value} Variable value +nvim_tabpage_set_win({tabpage}, {win}) *nvim_tabpage_set_win()* + Sets the current window in a tabpage + + Parameters: ~ + • {tabpage} Tabpage handle, or 0 for current tabpage + • {win} Window handle, must already belong to {tabpage} + ============================================================================== Autocmd Functions *api-autocmd* diff --git a/runtime/doc/filetype.txt b/runtime/doc/filetype.txt index 29b929b8a5..97ebdf48c1 100644 --- a/runtime/doc/filetype.txt +++ b/runtime/doc/filetype.txt @@ -687,7 +687,7 @@ To disable this behavior, set the following variable in your vimrc: > QUERY *ft-query-plugin* -Linting of tree-sitter queries for installed parsers using +Linting of treesitter queries for installed parsers using |vim.treesitter.query.lint()| is enabled by default on `BufEnter` and `BufWrite`. To change the events that trigger linting, use >lua diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 7aa2fd5a6f..c2f5941a5c 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -619,7 +619,7 @@ vim.highlight.on_yank({opts}) *vim.highlight.on_yank()* vim.highlight.priorities *vim.highlight.priorities* Table with default priorities used for highlighting: • `syntax`: `50`, used for standard syntax highlighting - • `treesitter`: `100`, used for tree-sitter-based highlighting + • `treesitter`: `100`, used for treesitter-based highlighting • `semantic_tokens`: `125`, used for LSP semantic token highlighting • `diagnostics`: `150`, used for code analysis such as diagnostics • `user`: `200`, used for user-triggered highlights such as LSP document diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 40717f8ecf..3051b81189 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -304,6 +304,8 @@ The following new APIs and features were added. highlight attribute. The TUI will display URLs using the OSC 8 control sequence, enabling clickable text in supporting terminals. +• Added |nvim_tabpage_set_win()| to set the current window of a tabpage. + ============================================================================== CHANGED FEATURES *news-changed* diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index 0f4462b109..2755cd421b 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -17,7 +17,7 @@ changes. This documentation may also not fully reflect the latest changes. ============================================================================== PARSER FILES *treesitter-parsers* -Parsers are the heart of tree-sitter. They are libraries that tree-sitter will +Parsers are the heart of treesitter. They are libraries that treesitter will search for in the `parser` runtime directory. By default, Nvim bundles parsers for C, Lua, Vimscript, Vimdoc and Treesitter query files, but parsers can be installed via a plugin like https://github.com/nvim-treesitter/nvim-treesitter @@ -43,7 +43,7 @@ TREESITTER TREES *treesitter-tree* A "treesitter tree" represents the parsed contents of a buffer, which can be used to perform further analysis. It is a |userdata| reference to an object -held by the tree-sitter library. +held by the treesitter library. An instance `TSTree` of a treesitter tree supports the following methods. @@ -59,7 +59,7 @@ TREESITTER NODES *treesitter-node* A "treesitter node" represents one specific element of the parsed contents of a buffer, which can be captured by a |Query| for, e.g., highlighting. It is -a |userdata| reference to an object held by the tree-sitter library. +a |userdata| reference to an object held by the treesitter library. An instance `TSNode` of a treesitter node supports the following methods. @@ -563,7 +563,7 @@ Conceals specified in this way respect 'conceallevel'. *treesitter-highlight-priority* Treesitter uses |nvim_buf_set_extmark()| to set highlights with a default priority of 100. This enables plugins to set a highlighting priority lower or -higher than tree-sitter. It is also possible to change the priority of an +higher than treesitter. It is also possible to change the priority of an individual query pattern manually by setting its `"priority"` metadata attribute: >query @@ -624,17 +624,17 @@ associated with patterns: VIM.TREESITTER *lua-treesitter* The remainder of this document is a reference manual for the `vim.treesitter` -Lua module, which is the main interface for Nvim's tree-sitter integration. +Lua module, which is the main interface for Nvim's treesitter integration. Most of the following content is automatically generated from the function documentation. *vim.treesitter.language_version* -The latest parser ABI version that is supported by the bundled tree-sitter +The latest parser ABI version that is supported by the bundled treesitter library. *vim.treesitter.minimum_language_version* -The earliest parser ABI version that is supported by the bundled tree-sitter +The earliest parser ABI version that is supported by the bundled treesitter library. ============================================================================== diff --git a/runtime/ftplugin/query.lua b/runtime/ftplugin/query.lua index 964c221ad4..c75dc30430 100644 --- a/runtime/ftplugin/query.lua +++ b/runtime/ftplugin/query.lua @@ -1,5 +1,5 @@ -- Neovim filetype plugin file --- Language: Tree-sitter query +-- Language: Treesitter query -- Last Change: 2023 Aug 23 if vim.b.did_ftplugin == 1 then diff --git a/runtime/ftplugin/vim.vim b/runtime/ftplugin/vim.vim index 06369e8a82..f5dae0f94e 100644 --- a/runtime/ftplugin/vim.vim +++ b/runtime/ftplugin/vim.vim @@ -49,18 +49,17 @@ setlocal isk+=# " Use :help to lookup the keyword under the cursor with K. setlocal keywordprg=:help -" if "\n" .. getline(1, 10)->join("\n") =~# '\n\s*vim9\%[script]\>' -if "\n" .. join(getline(1, 10), "\n") =~# '\n\s*vim9\%[script]\>' - " Set 'comments' to format dashed lists in comments - setlocal com=sO:#\ -,mO:#\ \ ,eO:##,:# - " Comments starts with # in Vim9 script +" Comments starts with # in Vim9 script. We have to guess which one to use. +if "\n" .. getline(1, 10)->join("\n") =~# '\n\s*vim9\%[script]\>' setlocal commentstring=#%s else - setlocal com=sO:\"\ -,mO:\"\ \ ,eO:\"\",:\" - " Comments starts with a double quote in legacy script setlocal commentstring=\"%s endif +" Set 'comments' to format dashed lists in comments, both in Vim9 and legacy +" script. +setlocal com=sO:#\ -,mO:#\ \ ,eO:##,:#\\\ ,:#,sO:\"\ -,mO:\"\ \ ,eO:\"\",:\"\\\ ,:\" + " Format comments to be up to 78 characters long if &tw == 0 diff --git a/runtime/indent/query.lua b/runtime/indent/query.lua index c86948e95e..3261376d87 100644 --- a/runtime/indent/query.lua +++ b/runtime/indent/query.lua @@ -1,5 +1,5 @@ -- Neovim indent file --- Language: Tree-sitter query +-- Language: Treesitter query -- Last Change: 2022 Mar 29 -- it's a lisp! diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index f9fa364158..bedc218626 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -1952,6 +1952,12 @@ function vim.api.nvim_tabpage_list_wins(tabpage) end --- @param value any Variable value function vim.api.nvim_tabpage_set_var(tabpage, name, value) end +--- Sets the current window in a tabpage +--- +--- @param tabpage integer Tabpage handle, or 0 for current tabpage +--- @param win integer Window handle, must already belong to {tabpage} +function vim.api.nvim_tabpage_set_win(tabpage, win) end + --- Calls a function with window as temporary current window. --- --- @param window integer Window handle, or 0 for current window diff --git a/runtime/lua/vim/_meta/api_keysets.lua b/runtime/lua/vim/_meta/api_keysets.lua index 00d3ff5bb4..c9ef4e9b4f 100644 --- a/runtime/lua/vim/_meta/api_keysets.lua +++ b/runtime/lua/vim/_meta/api_keysets.lua @@ -274,6 +274,7 @@ error('Cannot require a meta file') --- @field ui_watched? boolean --- @field undo_restore? boolean --- @field url? string +--- @field _subpriority? integer --- @class vim.api.keyset.user_command --- @field addr? any diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 7e1803f640..fbcca2dcfd 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1909,7 +1909,7 @@ local pattern = { ['.*baseq[2-3]/.*%.cfg'] = 'quake', ['.*quake[1-3]/.*%.cfg'] = 'quake', ['.*id1/.*%.cfg'] = 'quake', - ['.*/queries/.*%.scm'] = 'query', -- tree-sitter queries (Neovim only) + ['.*/queries/.*%.scm'] = 'query', -- treesitter queries (Neovim only) ['.*,v'] = 'rcs', ['%.reminders.*'] = starsetf('remind'), ['[rR]akefile.*'] = starsetf('ruby'), diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua index fc2fd43c97..1d04f95d2d 100644 --- a/runtime/lua/vim/highlight.lua +++ b/runtime/lua/vim/highlight.lua @@ -26,7 +26,7 @@ local M = {} --- Table with default priorities used for highlighting: --- - `syntax`: `50`, used for standard syntax highlighting ---- - `treesitter`: `100`, used for tree-sitter-based highlighting +--- - `treesitter`: `100`, used for treesitter-based highlighting --- - `semantic_tokens`: `125`, used for LSP semantic token highlighting --- - `diagnostics`: `150`, used for code analysis such as diagnostics --- - `user`: `200`, used for user-triggered highlights such as LSP document diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua index 4816b873ba..62138c0edf 100644 --- a/runtime/lua/vim/lsp/inlay_hint.lua +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -6,7 +6,7 @@ local M = {} ---@class lsp.inlay_hint.bufstate ---@field version? integer ----@field client_hint? table<integer, table<integer, lsp.InlayHint[]>> client_id -> (lnum -> hints) +---@field client_hints? table<integer, table<integer, lsp.InlayHint[]>> client_id -> (lnum -> hints) ---@field applied table<integer, integer> Last version of hints applied to this line ---@field enabled boolean Whether inlay hints are enabled for this buffer ---@type table<integer, lsp.inlay_hint.bufstate> @@ -39,11 +39,11 @@ function M.on_inlayhint(err, result, ctx, _) if not bufstate or not bufstate.enabled then return end - if not (bufstate.client_hint and bufstate.version) then - bufstate.client_hint = vim.defaulttable() + if not (bufstate.client_hints and bufstate.version) then + bufstate.client_hints = vim.defaulttable() bufstate.version = ctx.version end - local hints_by_client = bufstate.client_hint + local hints_by_client = bufstate.client_hints local client = assert(vim.lsp.get_client_by_id(client_id)) local new_hints_by_lnum = vim.defaulttable() @@ -162,7 +162,7 @@ function M.get(filter) end local bufstate = bufstates[bufnr] - if not (bufstate and bufstate.client_hint) then + if not (bufstate and bufstate.client_hints) then return {} end @@ -185,7 +185,7 @@ function M.get(filter) --- @type vim.lsp.inlay_hint.get.ret[] local hints = {} for _, client in pairs(clients) do - local hints_by_lnum = bufstate.client_hint[client.id] + local hints_by_lnum = bufstate.client_hints[client.id] if hints_by_lnum then for lnum = range.start.line, range['end'].line do local line_hints = hints_by_lnum[lnum] or {} @@ -218,11 +218,11 @@ local function clear(bufnr) return end local bufstate = bufstates[bufnr] - local client_lens = (bufstate or {}).client_hint or {} + local client_lens = (bufstate or {}).client_hints or {} local client_ids = vim.tbl_keys(client_lens) --- @type integer[] for _, iter_client_id in ipairs(client_ids) do if bufstate then - bufstate.client_hint[iter_client_id] = {} + bufstate.client_hints[iter_client_id] = {} end end api.nvim_buf_clear_namespace(bufnr, namespace, 0, -1) @@ -319,7 +319,7 @@ api.nvim_set_decoration_provider(namespace, { if bufstate.version ~= util.buf_versions[bufnr] then return end - local hints_by_client = assert(bufstate.client_hint) + local hints_by_client = assert(bufstate.client_hints) for lnum = topline, botline do if bufstate.applied[lnum] ~= bufstate.version then diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index 399d0ef03e..5020dd87e7 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -14,7 +14,7 @@ local M = {} local TSTreeView = {} ---@class TSP.Node ----@field node TSNode Tree-sitter node +---@field node TSNode Treesitter node ---@field field string? Node field ---@field depth integer Depth of this node in the tree ---@field text string? Text displayed in the inspector for this node. Not computed until the diff --git a/runtime/syntax/query.lua b/runtime/syntax/query.lua index 8f40b1cb8a..1e129dbeff 100644 --- a/runtime/syntax/query.lua +++ b/runtime/syntax/query.lua @@ -1,5 +1,5 @@ -- Neovim syntax file --- Language: Tree-sitter query +-- Language: Treesitter query -- Last Change: 2022 Apr 13 -- it's a lisp! diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim index 71556b4848..298215b880 100644 --- a/runtime/syntax/vim.vim +++ b/runtime/syntax/vim.vim @@ -204,7 +204,7 @@ syn keyword vimAugroupKey contained aug[roup] " Operators: {{{2 " ========= -syn cluster vimOperGroup contains=vimEnvvar,vimFunc,vimFuncVar,vimOper,vimOperParen,vimNumber,vimString,vimType,vimRegister,vimContinue,vim9Comment,vimVar +syn cluster vimOperGroup contains=vimEnvvar,vimFunc,vimFuncVar,vimOper,vimOperParen,vimNumber,vimString,vimType,vimRegister,@vimContinue,vim9Comment,vimVar syn match vimOper "||\|&&\|[-+*/%.!]" skipwhite nextgroup=vimString,vimSpecFile syn match vimOper "\%#=1\(==\|!=\|>=\|<=\|=\~\|!\~\|>\|<\|=\|!\~#\)[?#]\{0,2}" skipwhite nextgroup=vimString,vimSpecFile syn match vimOper "\(\<is\|\<isnot\)[?#]\{0,2}\>" skipwhite nextgroup=vimString,vimSpecFile @@ -311,6 +311,12 @@ syn match vimString contained +"[^"]*\\$+ skipnl nextgroup=vimStringCont syn match vimStringCont contained +\(\\\\\|.\)\{-}[^\\]"+ syn match vimEscape contained "\\." +syn region vimString start=+$'+ end=+'+ skip=+''+ oneline contains=vimStringInterpolationBrace,vimStringInterpolationExpr +syn region vimString start=+$"+ end=+"+ oneline contains=@vimStringGroup,vimStringInterpolationBrace,vimStringInterpolationExpr +syn region vimStringInterpolationExpr matchgroup=vimOperParen start=+{+ end=+}+ oneline contains=vimFunc,vimFuncVar,vimOper,vimNotation,vimOperParen,vimString,vimVar +syn match vimStringInterpolationBrace "{{" +syn match vimStringInterpolationBrace "}}" + " Substitutions: {{{2 " ============= syn cluster vimSubstList contains=vimPatSep,vimPatRegion,vimPatSepErr,vimSubstTwoBS,vimSubstRange,vimNotation @@ -476,16 +482,22 @@ syn match vimNormCmds contained ".*$" " Syntax: {{{2 "======= -syn match vimGroupList contained "@\=[^ \t,]*" contains=vimGroupSpecial,vimPatSep -syn match vimGroupList contained "@\=[^ \t,]*," nextgroup=vimGroupList contains=vimGroupSpecial,vimPatSep +syn match vimGroupList contained "[^[:space:],]\+\%(\s*,\s*[^[:space:],]\+\)*" contains=vimGroupSpecial +syn region vimGroupList contained start=/^\s*["#]\\ \|^\s*\\\|[^[:space:],]\+\s*,/ skip=/\s*\n\s*\\\|\s*\n\s*["#]\\ \|^\s*\\\|^\s*["#]\\ / end=/[^[:space:],]\s*$\|[^[:space:],]\ze\s\+\w/ contains=@vimContinue,vimGroupSpecial syn keyword vimGroupSpecial contained ALL ALLBUT CONTAINED TOP + if !exists("g:vimsyn_noerror") && !exists("g:vimsyn_novimsynerror") syn match vimSynError contained "\i\+" syn match vimSynError contained "\i\+=" nextgroup=vimGroupList endif -syn match vimSynContains contained "\<contain\(s\|edin\)=" nextgroup=vimGroupList -syn match vimSynKeyContainedin contained "\<containedin=" nextgroup=vimGroupList -syn match vimSynNextgroup contained "nextgroup=" nextgroup=vimGroupList +syn match vimSynContains contained "\<contain\%(s\|edin\)=" skipwhite skipnl nextgroup=vimGroupList +syn match vimSynKeyContainedin contained "\<containedin=" skipwhite skipnl nextgroup=vimGroupList +syn match vimSynNextgroup contained "\<nextgroup=" skipwhite skipnl nextgroup=vimGroupList +if has("conceal") + " no whitespace allowed after '=' + syn match vimSynCchar contained "\<cchar=" nextgroup=vimSynCcharValue + syn match vimSynCcharValue contained "\S" +endif syn match vimSyntax "\<sy\%[ntax]\>" contains=vimCommand skipwhite nextgroup=vimSynType,vimComment,vim9Comment syn match vimAuSyntax contained "\s+sy\%[ntax]" contains=vimCommand skipwhite nextgroup=vimSynType,vimComment,vim9Comment @@ -503,9 +515,9 @@ syn keyword vimSynType contained clear skipwhite nextgroup=vimGroupList " Syntax: cluster {{{2 syn keyword vimSynType contained cluster skipwhite nextgroup=vimClusterName -syn region vimClusterName contained matchgroup=vimGroupName start="\h\w*" skip="\\\\\|\\|" matchgroup=vimSep end="$\||" contains=vimGroupAdd,vimGroupRem,vimSynContains,vimSynError -syn match vimGroupAdd contained "add=" nextgroup=vimGroupList -syn match vimGroupRem contained "remove=" nextgroup=vimGroupList +syn region vimClusterName contained keepend matchgroup=vimGroupName start="\h\w*\>" skip=+\\\\\|\\\|\n\s*\\\|\n\s*"\\ + matchgroup=vimCmdSep end="$\||" contains=@vimContinue,vimGroupAdd,vimGroupRem,vimSynContains,vimSynError +syn match vimGroupAdd contained keepend "\<add=" skipwhite skipnl nextgroup=vimGroupList +syn match vimGroupRem contained keepend "\<remove=" skipwhite skipnl nextgroup=vimGroupList syn cluster vimFuncBodyList add=vimSynType,vimGroupAdd,vimGroupRem " Syntax: foldlevel {{{2 @@ -525,35 +537,31 @@ syn keyword vimSynType contained include skipwhite nextgroup=vimGroupList syn cluster vimFuncBodyList add=vimSynType " Syntax: keyword {{{2 -syn cluster vimSynKeyGroup contains=vimSynNextgroup,vimSynKeyOpt,vimSynKeyContainedin +syn cluster vimSynKeyGroup contains=@vimContinue,vimSynCchar,vimSynNextgroup,vimSynKeyOpt,vimSynKeyContainedin syn keyword vimSynType contained keyword skipwhite nextgroup=vimSynKeyRegion -syn region vimSynKeyRegion contained oneline keepend matchgroup=vimGroupName start="\h\w*" skip="\\\\\|\\|" matchgroup=vimSep end="|\|$" contains=@vimSynKeyGroup +syn region vimSynKeyRegion contained keepend matchgroup=vimGroupName start="\h\w*\>" skip=+\\\\\|\\|\|\n\s*\\\|\n\s*"\\ + matchgroup=vimCmdSep end="|\|$" contains=@vimSynKeyGroup syn match vimSynKeyOpt contained "\%#=1\<\(conceal\|contained\|transparent\|skipempty\|skipwhite\|skipnl\)\>" syn cluster vimFuncBodyList add=vimSynType " Syntax: match {{{2 -syn cluster vimSynMtchGroup contains=vimMtchComment,vimSynContains,vimSynError,vimSynMtchOpt,vimSynNextgroup,vimSynRegPat,vimNotation,vim9Comment +syn cluster vimSynMtchGroup contains=@vimContinue,vimSynCchar,vimSynContains,vimSynError,vimSynMtchOpt,vimSynNextgroup,vimSynRegPat,vimNotation,vimMtchComment syn keyword vimSynType contained match skipwhite nextgroup=vimSynMatchRegion -syn region vimSynMatchRegion contained keepend matchgroup=vimGroupName start="\h\w*" matchgroup=vimSep end="|\|$" contains=@vimSynMtchGroup +syn region vimSynMatchRegion contained keepend matchgroup=vimGroupName start="\h\w*\>" skip=+\\\\\|\\|\|\n\s*\\\|\n\s*"\\ + matchgroup=vimCmdSep end="|\|$" contains=@vimSynMtchGroup syn match vimSynMtchOpt contained "\%#=1\<\(conceal\|transparent\|contained\|excludenl\|keepend\|skipempty\|skipwhite\|display\|extend\|skipnl\|fold\)\>" -if has("conceal") - syn match vimSynMtchOpt contained "\<cchar=" nextgroup=vimSynMtchCchar - syn match vimSynMtchCchar contained "\S" -endif syn cluster vimFuncBodyList add=vimSynMtchGroup " Syntax: off and on {{{2 syn keyword vimSynType contained enable list manual off on reset " Syntax: region {{{2 -syn cluster vimSynRegPatGroup contains=vimPatSep,vimNotPatSep,vimSynPatRange,vimSynNotPatRange,vimSubstSubstr,vimPatRegion,vimPatSepErr,vimNotation -syn cluster vimSynRegGroup contains=vimSynContains,vimSynNextgroup,vimSynRegOpt,vimSynReg,vimSynMtchGrp +syn cluster vimSynRegPatGroup contains=@vimContinue,vimPatSep,vimNotPatSep,vimSynPatRange,vimSynNotPatRange,vimSubstSubstr,vimPatRegion,vimPatSepErr,vimNotation +syn cluster vimSynRegGroup contains=@vimContinue,vimSynCchar,vimSynContains,vimSynNextgroup,vimSynRegOpt,vimSynReg,vimSynMtchGrp syn keyword vimSynType contained region skipwhite nextgroup=vimSynRegion -syn region vimSynRegion contained keepend matchgroup=vimGroupName start="\h\w*" skip="\\\\\|\\|" end="|\|$" contains=@vimSynRegGroup +syn region vimSynRegion contained keepend matchgroup=vimGroupName start="\h\w*" skip=+\\\\\|\\\|\n\s*\\\|\n\s*"\\ + end="|\|$" contains=@vimSynRegGroup syn match vimSynRegOpt contained "\%#=1\<\(conceal\(ends\)\=\|transparent\|contained\|excludenl\|skipempty\|skipwhite\|display\|keepend\|oneline\|extend\|skipnl\|fold\)\>" -syn match vimSynReg contained "\(start\|skip\|end\)="he=e-1 nextgroup=vimSynRegPat +syn match vimSynReg contained "\<\%(start\|skip\|end\)=" nextgroup=vimSynRegPat syn match vimSynMtchGrp contained "matchgroup=" nextgroup=vimGroup,vimHLGroup,vimOnlyHLGroup,nvimHLGroup -syn region vimSynRegPat contained extend start="\z([-`~!@#$%^&*_=+;:'",./?]\)" skip="\\\\\|\\\z1" end="\z1" contains=@vimSynRegPatGroup skipwhite nextgroup=vimSynPatMod,vimSynReg +syn region vimSynRegPat contained extend start="\z([-`~!@#$%^&*_=+;:'",./?]\)" skip=/\\\\\|\\\z1\|\n\s*\\\|\n\s*"\\ / end="\z1" contains=@vimSynRegPatGroup skipwhite nextgroup=vimSynPatMod,vimSynReg syn match vimSynPatMod contained "\%#=1\(hs\|ms\|me\|hs\|he\|rs\|re\)=[se]\([-+]\d\+\)\=" syn match vimSynPatMod contained "\%#=1\(hs\|ms\|me\|hs\|he\|rs\|re\)=[se]\([-+]\d\+\)\=," nextgroup=vimSynPatMod syn match vimSynPatMod contained "lc=\d\+" @@ -642,10 +650,14 @@ syn match vimCtrlChar "[--]" " Beginners - Patterns that involve ^ {{{2 " ========= -syn match vimLineComment +^[ \t:]*".*$+ contains=@vimCommentGroup,vimCommentString,vimCommentTitle +syn match vimLineComment +^[ \t:]*".*$+ contains=@vimCommentGroup,vimCommentString,vimCommentTitle,vimComment +syn match vimLineComment +^[ \t:]*"\("[^"]*"\|[^"]\)*$+ contains=@vimCommentGroup,vimCommentString,vimCommentTitle syn match vim9LineComment +^[ \t:]\+#.*$+ contains=@vimCommentGroup,vimCommentString,vimCommentTitle syn match vimCommentTitle '"\s*\%([sS]:\|\h\w*#\)\=\u\w*\(\s\+\u\w*\)*:'hs=s+1 contained contains=vimCommentTitleLeader,vimTodo,@vimCommentGroup -syn match vimContinue "^\s*\\" +" Note: Look-behind to work around nextgroup skipnl consuming leading whitespace and preventing a match +syn match vimContinue "^\s*\zs\\" +syn match vimContinueComment '^\s*\zs["#]\\ .*' contained +syn cluster vimContinue contains=vimContinue,vimContinueComment syn region vimString start="^\s*\\\z(['"]\)" skip='\\\\\|\\\z1' end="\z1" oneline keepend contains=@vimStringGroup,vimContinue syn match vimCommentTitleLeader '"\s\+'ms=s+1 contained @@ -889,6 +901,7 @@ if !exists("skip_vim_syntax_inits") hi def link vimCondHL vimCommand hi def link vimConst vimCommand hi def link vimContinue Special + hi def link vimContinueComment vimComment hi def link vimCtrlChar SpecialChar hi def link vimEchoHLNone vimGroup hi def link vimEchoHL vimCommand @@ -984,6 +997,7 @@ if !exists("skip_vim_syntax_inits") hi def link vimStringCont vimString hi def link vimString String hi def link vimStringEnd vimString + hi def link vimStringInterpolationBrace vimEscape hi def link vimSubst1 vimSubst hi def link vimSubstDelim Delimiter hi def link vimSubstFlags Special @@ -1004,6 +1018,8 @@ if !exists("skip_vim_syntax_inits") hi def link vimSynFoldMethod Type hi def link vimSynKeyContainedin vimSynContains hi def link vimSynKeyOpt vimSynOption + hi def link vimSynCchar vimSynOption + hi def link vimSynCcharValue Character hi def link vimSynMtchGrp vimSynOption hi def link vimSynMtchOpt vimSynOption hi def link vimSynNextgroup vimSynOption diff --git a/scripts/gen_help_html.lua b/scripts/gen_help_html.lua index 20174bab97..8bc7d99985 100644 --- a/scripts/gen_help_html.lua +++ b/scripts/gen_help_html.lua @@ -42,9 +42,14 @@ local spell_dict = { VimL = 'Vimscript', vimL = 'Vimscript', viml = 'Vimscript', + ['tree-sitter'] = 'treesitter', + ['Tree-sitter'] = 'Treesitter', } +--- specify the list of keywords to ignore (i.e. allow), or true to disable spell check completely. +--- @type table<string, true|string[]> local spell_ignore_files = { - ['backers.txt'] = 'true', + ['backers.txt'] = true, + ['news.txt'] = { 'tree-sitter' }, -- in news, may refer to the upstream "tree-sitter" library } local language = nil @@ -398,9 +403,15 @@ local function visit_validate(root, level, lang_tree, opt, stats) then local text_nopunct = vim.fn.trim(text, '.,', 0) -- Ignore some punctuation. local fname_basename = assert(vim.fs.basename(opt.fname)) - if spell_dict[text_nopunct] and not spell_ignore_files[fname_basename] then - invalid_spelling[text_nopunct] = invalid_spelling[text_nopunct] or {} - invalid_spelling[text_nopunct][fname_basename] = node_text(root:parent()) + if spell_dict[text_nopunct] then + local should_ignore = ( + spell_ignore_files[fname_basename] == true + or vim.tbl_contains(spell_ignore_files[fname_basename] --[[ @as string[] ]], text_nopunct) + ) + if not should_ignore then + invalid_spelling[text_nopunct] = invalid_spelling[text_nopunct] or {} + invalid_spelling[text_nopunct][fname_basename] = node_text(root:parent()) + end end elseif node_name == 'url' then local fixed_url, _ = fix_url(trim(text)) diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 27a4b7854f..4e84b41a02 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -748,20 +748,32 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer col2 = c; } + DecorPriority subpriority = DECOR_PRIORITY_BASE; + if (HAS_KEY(opts, set_extmark, _subpriority)) { + VALIDATE_RANGE((opts->_subpriority >= 0 && opts->_subpriority <= UINT16_MAX), + "_subpriority", { + goto error; + }); + subpriority = (DecorPriority)opts->_subpriority; + } + if (kv_size(virt_text.data.virt_text)) { - decor_range_add_virt(&decor_state, r, c, line2, col2, decor_put_vt(virt_text, NULL), true); + decor_range_add_virt(&decor_state, r, c, line2, col2, decor_put_vt(virt_text, NULL), true, + subpriority); } if (kv_size(virt_lines.data.virt_lines)) { - decor_range_add_virt(&decor_state, r, c, line2, col2, decor_put_vt(virt_lines, NULL), true); + decor_range_add_virt(&decor_state, r, c, line2, col2, decor_put_vt(virt_lines, NULL), true, + subpriority); } if (url != NULL) { DecorSignHighlight sh = DECOR_SIGN_HIGHLIGHT_INIT; sh.url = url; - decor_range_add_sh(&decor_state, r, c, line2, col2, &sh, true, 0, 0); + decor_range_add_sh(&decor_state, r, c, line2, col2, &sh, true, 0, 0, subpriority); } if (has_hl) { DecorSignHighlight sh = decor_sh_from_inline(hl); - decor_range_add_sh(&decor_state, r, c, line2, col2, &sh, true, (uint32_t)ns_id, id); + decor_range_add_sh(&decor_state, r, c, line2, col2, &sh, true, (uint32_t)ns_id, id, + subpriority); } } else { if (opts->ephemeral) { diff --git a/src/nvim/api/keysets_defs.h b/src/nvim/api/keysets_defs.h index 811f60f4d6..85896c1fa7 100644 --- a/src/nvim/api/keysets_defs.h +++ b/src/nvim/api/keysets_defs.h @@ -55,6 +55,8 @@ typedef struct { Boolean ui_watched; Boolean undo_restore; String url; + + Integer _subpriority; } Dict(set_extmark); typedef struct { diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c index 303f2ca817..fadc03b3e5 100644 --- a/src/nvim/api/tabpage.c +++ b/src/nvim/api/tabpage.c @@ -122,6 +122,37 @@ Window nvim_tabpage_get_win(Tabpage tabpage, Error *err) abort(); } +/// Sets the current window in a tabpage +/// +/// @param tabpage Tabpage handle, or 0 for current tabpage +/// @param win Window handle, must already belong to {tabpage} +/// @param[out] err Error details, if any +void nvim_tabpage_set_win(Tabpage tabpage, Window win, Error *err) + FUNC_API_SINCE(12) +{ + tabpage_T *tp = find_tab_by_handle(tabpage, err); + if (!tp) { + return; + } + + win_T *wp = find_window_by_handle(win, err); + if (!wp) { + return; + } + + if (!tabpage_win_valid(tp, wp)) { + api_set_error(err, kErrorTypeException, "Window does not belong to tabpage %d", tp->handle); + return; + } + + if (tp == curtab) { + win_enter(wp, true); + } else if (tp->tp_curwin != wp) { + tp->tp_prevwin = tp->tp_curwin; + tp->tp_curwin = wp; + } +} + /// Gets the tabpage number /// /// @param tabpage Tabpage handle, or 0 for current tabpage diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index d597d82ee2..00cb7272c0 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1183,6 +1183,32 @@ static int empty_curbuf(bool close_others, int forceit, int action) return retval; } +/// Remove every jump list entry referring to a given buffer. +/// This function will also adjust the current jump list index. +void buf_remove_from_jumplist(buf_T *deleted_buf) +{ + // Remove all jump list entries that match the deleted buffer. + for (int i = curwin->w_jumplistlen - 1; i >= 0; i--) { + buf_T *buf = buflist_findnr(curwin->w_jumplist[i].fmark.fnum); + + if (buf == deleted_buf) { + // Found an entry that we want to delete. + curwin->w_jumplistlen -= 1; + + // If the current jump list index behind the entry we want to + // delete, move it back by one. + if (curwin->w_jumplistidx > i && curwin->w_jumplistidx > 0) { + curwin->w_jumplistidx -= 1; + } + + // Actually remove the entry from the jump list. + for (int d = i; d < curwin->w_jumplistlen; d++) { + curwin->w_jumplist[d] = curwin->w_jumplist[d + 1]; + } + } + } +} + /// Implementation of the commands for the buffer list. /// /// action == DOBUF_GOTO go to specified buffer @@ -1205,6 +1231,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit) { buf_T *buf; buf_T *bp; + bool update_jumplist = true; bool unload = (action == DOBUF_UNLOAD || action == DOBUF_DEL || action == DOBUF_WIPE); @@ -1362,7 +1389,11 @@ int do_buffer(int action, int start, int dir, int count, int forceit) // If the buffer to be deleted is not the current one, delete it here. if (buf != curbuf) { + // Remove the buffer to be deleted from the jump list. + buf_remove_from_jumplist(buf); + close_windows(buf, false); + if (buf != curbuf && bufref_valid(&bufref) && buf->b_nwindows <= 0) { close_buffer(NULL, buf, action, false, false); } @@ -1382,40 +1413,53 @@ int do_buffer(int action, int start, int dir, int count, int forceit) if (au_new_curbuf.br_buf != NULL && bufref_valid(&au_new_curbuf)) { buf = au_new_curbuf.br_buf; } else if (curwin->w_jumplistlen > 0) { - int jumpidx = curwin->w_jumplistidx - 1; - if (jumpidx < 0) { - jumpidx = curwin->w_jumplistlen - 1; - } + // Remove the current buffer from the jump list. + buf_remove_from_jumplist(curbuf); + + // It's possible that we removed all jump list entries, in that case we need to try another + // approach + if (curwin->w_jumplistlen > 0) { + // If the index is the same as the length, the current position was not yet added to the jump + // list. So we can safely go back to the last entry and search from there. + if (curwin->w_jumplistidx == curwin->w_jumplistlen) { + curwin->w_jumplistidx = curwin->w_jumplistlen - 1; + } - forward = jumpidx; - while (jumpidx != curwin->w_jumplistidx) { - buf = buflist_findnr(curwin->w_jumplist[jumpidx].fmark.fnum); - if (buf != NULL) { - // Skip current and unlisted bufs. Also skip a quickfix - // buffer, it might be deleted soon. - if (buf == curbuf || !buf->b_p_bl || bt_quickfix(buf)) { - buf = NULL; - } else if (buf->b_ml.ml_mfp == NULL) { - // skip unloaded buf, but may keep it for later - if (bp == NULL) { - bp = buf; + int jumpidx = curwin->w_jumplistidx; + + forward = jumpidx; + do { + buf = buflist_findnr(curwin->w_jumplist[jumpidx].fmark.fnum); + + if (buf != NULL) { + // Skip unlisted bufs. Also skip a quickfix + // buffer, it might be deleted soon. + if (!buf->b_p_bl || bt_quickfix(buf)) { + buf = NULL; + } else if (buf->b_ml.ml_mfp == NULL) { + // skip unloaded buf, but may keep it for later + if (bp == NULL) { + bp = buf; + } + buf = NULL; } - buf = NULL; } - } - if (buf != NULL) { // found a valid buffer: stop searching - break; - } - // advance to older entry in jump list - if (!jumpidx && curwin->w_jumplistidx == curwin->w_jumplistlen) { - break; - } - if (--jumpidx < 0) { - jumpidx = curwin->w_jumplistlen - 1; - } - if (jumpidx == forward) { // List exhausted for sure - break; - } + if (buf != NULL) { // found a valid buffer: stop searching + curwin->w_jumplistidx = jumpidx; + update_jumplist = false; + break; + } + // advance to older entry in jump list + if (!jumpidx && curwin->w_jumplistidx == curwin->w_jumplistlen) { + break; + } + if (--jumpidx < 0) { + jumpidx = curwin->w_jumplistlen - 1; + } + if (jumpidx == forward) { // List exhausted for sure + break; + } + } while (jumpidx != curwin->w_jumplistidx); } } @@ -1511,7 +1555,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit) } // Go to the other buffer. - set_curbuf(buf, action); + set_curbuf(buf, action, update_jumplist); if (action == DOBUF_SPLIT) { RESET_BINDING(curwin); // reset 'scrollbind' and 'cursorbind' @@ -1533,14 +1577,17 @@ int do_buffer(int action, int start, int dir, int count, int forceit) /// DOBUF_UNLOAD unload it /// DOBUF_DEL delete it /// DOBUF_WIPE wipe it out -void set_curbuf(buf_T *buf, int action) +void set_curbuf(buf_T *buf, int action, bool update_jumplist) { buf_T *prevbuf; int unload = (action == DOBUF_UNLOAD || action == DOBUF_DEL || action == DOBUF_WIPE); OptInt old_tw = curbuf->b_p_tw; - setpcmark(); + if (update_jumplist) { + setpcmark(); + } + if ((cmdmod.cmod_flags & CMOD_KEEPALT) == 0) { curwin->w_alt_fnum = curbuf->b_fnum; // remember alternate file } @@ -3675,7 +3722,7 @@ void ex_buffer_all(exarg_T *eap) // Open the buffer in this window. swap_exists_action = SEA_DIALOG; - set_curbuf(buf, DOBUF_GOTO); + set_curbuf(buf, DOBUF_GOTO, false); if (!bufref_valid(&bufref)) { // Autocommands deleted the buffer. swap_exists_action = SEA_NONE; diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 79527176ab..24f97dd92c 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -713,7 +713,7 @@ struct file_buffer { // Measurements of the deleted or replaced region since the last update // event. Some consumers of buffer changes need to know the byte size (like - // tree-sitter) or the corresponding UTF-32/UTF-16 size (like LSP) of the + // treesitter) or the corresponding UTF-32/UTF-16 size (like LSP) of the // deleted text. size_t deleted_bytes; size_t deleted_bytes2; diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 755655856d..5358241644 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -449,18 +449,21 @@ static void decor_range_add_from_inline(DecorState *state, int start_row, int st if (decor.ext) { DecorVirtText *vt = decor.data.ext.vt; while (vt) { - decor_range_add_virt(state, start_row, start_col, end_row, end_col, vt, owned); + decor_range_add_virt(state, start_row, start_col, end_row, end_col, vt, owned, + DECOR_PRIORITY_BASE); vt = vt->next; } uint32_t idx = decor.data.ext.sh_idx; while (idx != DECOR_ID_INVALID) { DecorSignHighlight *sh = &kv_A(decor_items, idx); - decor_range_add_sh(state, start_row, start_col, end_row, end_col, sh, owned, ns, mark_id); + decor_range_add_sh(state, start_row, start_col, end_row, end_col, sh, owned, ns, mark_id, + DECOR_PRIORITY_BASE); idx = sh->next; } } else { DecorSignHighlight sh = decor_sh_from_inline(decor.data.hl); - decor_range_add_sh(state, start_row, start_col, end_row, end_col, &sh, owned, ns, mark_id); + decor_range_add_sh(state, start_row, start_col, end_row, end_col, &sh, owned, ns, mark_id, + DECOR_PRIORITY_BASE); } } @@ -470,7 +473,8 @@ static void decor_range_insert(DecorState *state, DecorRange range) size_t index; for (index = kv_size(state->active) - 1; index > 0; index--) { DecorRange item = kv_A(state->active, index - 1); - if (item.priority <= range.priority) { + if ((item.priority < range.priority) + || ((item.priority == range.priority) && (item.subpriority <= range.subpriority))) { break; } kv_A(state->active, index) = kv_A(state->active, index - 1); @@ -479,7 +483,7 @@ static void decor_range_insert(DecorState *state, DecorRange range) } void decor_range_add_virt(DecorState *state, int start_row, int start_col, int end_row, int end_col, - DecorVirtText *vt, bool owned) + DecorVirtText *vt, bool owned, DecorPriority subpriority) { bool is_lines = vt->flags & kVTIsLines; DecorRange range = { @@ -489,13 +493,15 @@ void decor_range_add_virt(DecorState *state, int start_row, int start_col, int e .attr_id = 0, .owned = owned, .priority = vt->priority, + .subpriority = subpriority, .draw_col = -10, }; decor_range_insert(state, range); } void decor_range_add_sh(DecorState *state, int start_row, int start_col, int end_row, int end_col, - DecorSignHighlight *sh, bool owned, uint32_t ns, uint32_t mark_id) + DecorSignHighlight *sh, bool owned, uint32_t ns, uint32_t mark_id, + DecorPriority subpriority) { if (sh->flags & kSHIsSign) { return; @@ -508,6 +514,7 @@ void decor_range_add_sh(DecorState *state, int start_row, int start_col, int end .attr_id = 0, .owned = owned, .priority = sh->priority, + .subpriority = subpriority, .draw_col = -10, }; diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h index b3ff737123..e70c588806 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -48,6 +48,8 @@ typedef struct { int attr_id; ///< cached lookup of inl.hl_id if it was a highlight bool owned; ///< ephemeral decoration, free memory immediately DecorPriority priority; + DecorPriority subpriority; ///< Secondary priority value used for ordering (#27131). + ///< Reflects the order of patterns/captures in the query file. DecorRangeKind kind; /// Screen column to draw the virtual text. /// When -1, the virtual text may be drawn after deciding where. diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 842f8a4297..484b3572ab 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -405,7 +405,7 @@ buf_found: // Open the changed buffer in the current window. if (buf != curbuf) { - set_curbuf(buf, unload ? DOBUF_UNLOAD : DOBUF_GOTO); + set_curbuf(buf, unload ? DOBUF_UNLOAD : DOBUF_GOTO, true); } theend: diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 03bc953368..d3411850fd 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1146,10 +1146,10 @@ static void gotchars(const uint8_t *chars, size_t len) maptick++; } -/// Record a <Nop> key. -void gotchars_nop(void) +/// Record an <Ignore> key. +void gotchars_ignore(void) { - uint8_t nop_buf[3] = { K_SPECIAL, KS_EXTRA, KE_NOP }; + uint8_t nop_buf[3] = { K_SPECIAL, KS_EXTRA, KE_IGNORE }; gotchars(nop_buf, 3); } @@ -2746,9 +2746,9 @@ static int vgetorpeek(bool advance) } if (timedout && c == ESC) { - // When recording there will be no timeout. Add a <Nop> after the ESC - // to avoid that it forms a key code with following characters. - gotchars_nop(); + // When recording there will be no timeout. Add an <Ignore> after the + // ESC to avoid that it forms a key code with following characters. + gotchars_ignore(); } vgetc_busy--; diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 8b62bff496..de17aabca2 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -1,5 +1,5 @@ -// lua bindings for tree-sitter. -// NB: this file mostly contains a generic lua interface for tree-sitter +// lua bindings for treesitter. +// NB: this file mostly contains a generic lua interface for treesitter // trees and nodes, and could be broken out as a reusable lua package #include <assert.h> diff --git a/src/nvim/normal.c b/src/nvim/normal.c index ed2b1437ec..8c388b4318 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -845,10 +845,10 @@ static void normal_get_additional_char(NormalState *s) no_mapping++; // Vim may be in a different mode when the user types the next key, // but when replaying a recording the next key is already in the - // typeahead buffer, so record a <Nop> before that to prevent the - // vpeekc() above from applying wrong mappings when replaying. + // typeahead buffer, so record an <Ignore> before that to prevent + // the vpeekc() above from applying wrong mappings when replaying. no_u_sync++; - gotchars_nop(); + gotchars_ignore(); no_u_sync--; } } diff --git a/src/nvim/window.c b/src/nvim/window.c index 112ac8bf3b..66169bcb74 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -1557,7 +1557,7 @@ bool win_valid(const win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT /// Check if "win" is a pointer to an existing window in tabpage "tp". /// /// @param win window to check -static bool tabpage_win_valid(const tabpage_T *tp, const win_T *win) +bool tabpage_win_valid(const tabpage_T *tp, const win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { if (win == NULL) { @@ -2553,6 +2553,7 @@ static bool close_last_window_tabpage(win_T *win, bool free_buf, tabpage_T *prev /// "abort_if_last" is passed to close_buffer(): abort closing if all other /// windows are closed. static void win_close_buffer(win_T *win, int action, bool abort_if_last) + FUNC_ATTR_NONNULL_ALL { // Free independent synblock before the buffer is freed. if (win->w_buffer != NULL) { @@ -2590,6 +2591,7 @@ static void win_close_buffer(win_T *win, int action, bool abort_if_last) // Called by :quit, :close, :xit, :wq and findtag(). // Returns FAIL when the window was not closed. int win_close(win_T *win, bool free_buf, bool force) + FUNC_ATTR_NONNULL_ALL { tabpage_T *prev_curtab = curtab; frame_T *win_frame = win->w_floating ? NULL : win->w_frame->fr_parent; @@ -2888,6 +2890,7 @@ static void do_autocmd_winclosed(win_T *win) // Caller must check if buffer is hidden and whether the tabline needs to be // updated. void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) + FUNC_ATTR_NONNULL_ALL { // Get here with win->w_buffer == NULL when win_close() detects the tab page // changed. @@ -2989,6 +2992,7 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) /// /// @return a pointer to the window that got the freed up space. static win_T *win_free_mem(win_T *win, int *dirp, tabpage_T *tp) + FUNC_ATTR_NONNULL_ARG(1) { win_T *wp; tabpage_T *win_tp = tp == NULL ? curtab : tp; @@ -3007,6 +3011,7 @@ static win_T *win_free_mem(win_T *win, int *dirp, tabpage_T *tp) wp = firstwin; } } else { + assert(tp != curtab); if (tabpage_win_valid(tp, tp->tp_prevwin) && tp->tp_prevwin != win) { wp = tp->tp_prevwin; } else { @@ -3079,7 +3084,10 @@ void win_free_all(void) /// /// @return a pointer to the window that got the freed up space. win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp) + FUNC_ATTR_NONNULL_ARG(1, 2) { + assert(tp == NULL || tp != curtab); + // If there is only one window there is nothing to remove. if (tp == NULL ? ONE_WINDOW : tp->tp_firstwin == tp->tp_lastwin) { return NULL; @@ -3226,7 +3234,10 @@ win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp) /// @return a pointer to the frame that will receive the empty screen space that /// is left over after "win" is closed. static frame_T *win_altframe(win_T *win, tabpage_T *tp) + FUNC_ATTR_NONNULL_ARG(1) { + assert(tp == NULL || tp != curtab); + if (tp == NULL ? ONE_WINDOW : tp->tp_firstwin == tp->tp_lastwin) { return alt_tabpage()->tp_curwin->w_frame; } @@ -3290,6 +3301,7 @@ static tabpage_T *alt_tabpage(void) // Find the left-upper window in frame "frp". win_T *frame2win(frame_T *frp) + FUNC_ATTR_NONNULL_ALL { while (frp->fr_win == NULL) { frp = frp->fr_child; @@ -5142,7 +5154,10 @@ void win_append(win_T *after, win_T *wp) /// /// @param tp tab page "win" is in, NULL for current void win_remove(win_T *wp, tabpage_T *tp) + FUNC_ATTR_NONNULL_ARG(1) { + assert(tp == NULL || tp != curtab); + if (wp->w_prev != NULL) { wp->w_prev->w_next = wp->w_next; } else if (tp == NULL) { diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua index 0a286965f2..2acfbfc949 100644 --- a/test/functional/api/extmark_spec.lua +++ b/test/functional/api/extmark_spec.lua @@ -1798,6 +1798,36 @@ describe('API/extmarks', function() eq(1, #extmarks) eq('https://example.com', extmarks[1][4].url) end) + + it('respects priority', function() + screen = Screen.new(15, 10) + screen:attach() + + set_extmark(ns, marks[1], 0, 0, { + hl_group = 'Comment', + end_col = 2, + priority = 20, + }) + + -- Extmark defined after first extmark but has lower priority, first extmark "wins" + set_extmark(ns, marks[2], 0, 0, { + hl_group = 'String', + end_col = 2, + priority = 10, + }) + + screen:expect { + grid = [[ + {1:12}34^5 | + {2:~ }|*8 + | + ]], + attr_ids = { + [1] = { foreground = Screen.colors.Blue1 }, + [2] = { foreground = Screen.colors.Blue1, bold = true }, + }, + } + end) end) describe('Extmarks buffer api with many marks', function() diff --git a/test/functional/api/tabpage_spec.lua b/test/functional/api/tabpage_spec.lua index 65b030fc60..36955c4ace 100644 --- a/test/functional/api/tabpage_spec.lua +++ b/test/functional/api/tabpage_spec.lua @@ -12,15 +12,19 @@ describe('api/tabpage', function() describe('list_wins and get_win', function() it('works', function() - helpers.command('tabnew') - helpers.command('vsplit') + command('tabnew') + command('vsplit') local tab1, tab2 = unpack(api.nvim_list_tabpages()) local win1, win2, win3 = unpack(api.nvim_list_wins()) eq({ win1 }, api.nvim_tabpage_list_wins(tab1)) + eq(win1, api.nvim_tabpage_get_win(tab1)) eq({ win2, win3 }, api.nvim_tabpage_list_wins(tab2)) eq(win2, api.nvim_tabpage_get_win(tab2)) api.nvim_set_current_win(win3) eq(win3, api.nvim_tabpage_get_win(tab2)) + command('tabprev') + eq(win1, api.nvim_tabpage_get_win(tab1)) + eq(win3, api.nvim_tabpage_get_win(tab2)) end) it('validates args', function() @@ -28,6 +32,62 @@ describe('api/tabpage', function() end) end) + describe('set_win', function() + it('works', function() + command('tabnew') + command('vsplit') + local tab1, tab2 = unpack(api.nvim_list_tabpages()) + local win1, win2, win3 = unpack(api.nvim_list_wins()) + eq({ win1 }, api.nvim_tabpage_list_wins(tab1)) + eq({ win2, win3 }, api.nvim_tabpage_list_wins(tab2)) + eq(win2, api.nvim_tabpage_get_win(tab2)) + api.nvim_tabpage_set_win(tab2, win3) + eq(win3, api.nvim_tabpage_get_win(tab2)) + end) + + it('works in non-current tabpages', function() + command('tabnew') + command('vsplit') + local tab1, tab2 = unpack(api.nvim_list_tabpages()) + local win1, win2, win3 = unpack(api.nvim_list_wins()) + eq({ win1 }, api.nvim_tabpage_list_wins(tab1)) + eq({ win2, win3 }, api.nvim_tabpage_list_wins(tab2)) + eq(win2, api.nvim_tabpage_get_win(tab2)) + eq(win2, api.nvim_get_current_win()) + + command('tabprev') + + eq(tab1, api.nvim_get_current_tabpage()) + + eq(win2, api.nvim_tabpage_get_win(tab2)) + api.nvim_tabpage_set_win(tab2, win3) + eq(win3, api.nvim_tabpage_get_win(tab2)) + + command('tabnext') + eq(win3, api.nvim_get_current_win()) + end) + + it('throws an error when the window does not belong to the tabpage', function() + command('tabnew') + command('vsplit') + local tab1, tab2 = unpack(api.nvim_list_tabpages()) + local win1, win2, win3 = unpack(api.nvim_list_wins()) + eq({ win1 }, api.nvim_tabpage_list_wins(tab1)) + eq({ win2, win3 }, api.nvim_tabpage_list_wins(tab2)) + eq(win2, api.nvim_get_current_win()) + + eq( + string.format('Window does not belong to tabpage %d', tab2), + pcall_err(api.nvim_tabpage_set_win, tab2, win1) + ) + + eq( + string.format('Window does not belong to tabpage %d', tab1), + pcall_err(api.nvim_tabpage_set_win, tab1, win3) + ) + end) + end) + describe('{get,set,del}_var', function() it('works', function() api.nvim_tabpage_set_var(0, 'lua', { 1, 2, { ['3'] = 1 } }) @@ -64,12 +124,12 @@ describe('api/tabpage', function() local tabs = api.nvim_list_tabpages() eq(1, api.nvim_tabpage_get_number(tabs[1])) - helpers.command('tabnew') + command('tabnew') local tab1, tab2 = unpack(api.nvim_list_tabpages()) eq(1, api.nvim_tabpage_get_number(tab1)) eq(2, api.nvim_tabpage_get_number(tab2)) - helpers.command('-tabmove') + command('-tabmove') eq(2, api.nvim_tabpage_get_number(tab1)) eq(1, api.nvim_tabpage_get_number(tab2)) end) @@ -77,11 +137,11 @@ describe('api/tabpage', function() describe('is_valid', function() it('works', function() - helpers.command('tabnew') + command('tabnew') local tab = api.nvim_list_tabpages()[2] api.nvim_set_current_tabpage(tab) ok(api.nvim_tabpage_is_valid(tab)) - helpers.command('tabclose') + command('tabclose') ok(not api.nvim_tabpage_is_valid(tab)) end) end) diff --git a/test/functional/editor/jump_spec.lua b/test/functional/editor/jump_spec.lua index 717284b7d1..fe03d82164 100644 --- a/test/functional/editor/jump_spec.lua +++ b/test/functional/editor/jump_spec.lua @@ -3,6 +3,7 @@ local Screen = require('test.functional.ui.screen') local clear = helpers.clear local command = helpers.command +local dedent = helpers.dedent local eq = helpers.eq local fn = helpers.fn local feed = helpers.feed @@ -192,6 +193,131 @@ describe("jumpoptions=stack behaves like 'tagstack'", function() end) end) +describe('buffer deletion', function() + local base_file = 'Xtest-functional-buffer-deletion' + local file1 = base_file .. '1' + local file2 = base_file .. '2' + local file3 = base_file .. '3' + local base_content = 'text' + local content1 = base_content .. '1' + local content2 = base_content .. '2' + local content3 = base_content .. '3' + + local function format_jumplist(input) + return dedent(input) + :gsub('%{file1%}', file1) + :gsub('%{file2%}', file2) + :gsub('%{file3%}', file3) + :gsub('%{content1%}', content1) + :gsub('%{content2%}', content2) + :gsub('%{content3%}', content3) + end + + before_each(function() + clear() + command('clearjumps') + + write_file(file1, content1, false, false) + write_file(file2, content2, false, false) + write_file(file3, content3, false, false) + + command('edit ' .. file1) + command('edit ' .. file2) + command('edit ' .. file3) + end) + + it('deletes jump list entries when the current buffer is deleted', function() + command('edit ' .. file1) + + eq( + format_jumplist([[ + jump line col file/text + 3 1 0 {content1} + 2 1 0 {file2} + 1 1 0 {file3} + >]]), + exec_capture('jumps') + ) + + command('bwipeout') + + eq( + format_jumplist([[ + jump line col file/text + 1 1 0 {file2} + > 0 1 0 {content3}]]), + exec_capture('jumps') + ) + end) + + it('deletes jump list entries when another buffer is deleted', function() + eq( + format_jumplist([[ + jump line col file/text + 2 1 0 {file1} + 1 1 0 {file2} + >]]), + exec_capture('jumps') + ) + + command('bwipeout ' .. file2) + + eq( + format_jumplist([[ + jump line col file/text + 1 1 0 {file1} + >]]), + exec_capture('jumps') + ) + end) + + it('sets the correct jump index when the current buffer is deleted', function() + feed('<C-O>') + + eq( + format_jumplist([[ + jump line col file/text + 1 1 0 {file1} + > 0 1 0 {content2} + 1 1 0 {file3}]]), + exec_capture('jumps') + ) + + command('bw') + + eq( + format_jumplist([[ + jump line col file/text + 1 1 0 {file1} + > 0 1 0 {content3}]]), + exec_capture('jumps') + ) + end) + + it('sets the correct jump index when the another buffer is deleted', function() + feed('<C-O>') + + eq( + format_jumplist([[ + jump line col file/text + 1 1 0 {file1} + > 0 1 0 {content2} + 1 1 0 {file3}]]), + exec_capture('jumps') + ) + + command('bwipeout ' .. file1) + + eq( + format_jumplist([[ + jump line col file/text + > 0 1 0 {content2} + 1 1 0 {file3}]]), + exec_capture('jumps') + ) + end) +end) + describe('jumpoptions=view', function() local file1 = 'Xtestfile-functional-editor-jumps' local file2 = 'Xtestfile-functional-editor-jumps-2' diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index 7b3533454c..cb804f056d 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -687,7 +687,7 @@ describe('decorations providers', function() ]]} end) - it('can add new providers during redraw #26652', function() + it('can add new providers during redraw #26652', function() setup_provider [[ local ns = api.nvim_create_namespace('test_no_add') function on_do(...) @@ -697,6 +697,47 @@ describe('decorations providers', function() helpers.assert_alive() end) + + it('supports subpriorities (order of definitions in a query file #27131)', function() + insert(mulholland) + setup_provider [[ + local test_ns = api.nvim_create_namespace('mulholland') + function on_do(event, ...) + if event == "line" then + local win, buf, line = ... + api.nvim_buf_set_extmark(buf, test_ns, line, 0, { + end_row = line + 1, + hl_eol = true, + hl_group = 'Comment', + ephemeral = true, + priority = 100, + _subpriority = 20, + }) + + -- This extmark is set last but has a lower subpriority, so the first extmark "wins" + api.nvim_buf_set_extmark(buf, test_ns, line, 0, { + end_row = line + 1, + hl_eol = true, + hl_group = 'String', + ephemeral = true, + priority = 100, + _subpriority = 10, + }) + end + end + ]] + + screen:expect{grid=[[ + {4:// just to see if there was an accident }| + {4:// on Mulholland Drive }| + {4:try_start(); }| + {4:bufref_T save_buf; }| + {4:switch_buffer(&save_buf, buf); }| + {4:posp = getmark(mark, false); }| + {4:restore_buffer(&save_buf);^ }| + | + ]]} + end) end) local example_text = [[ diff --git a/test/old/testdir/test_registers.vim b/test/old/testdir/test_registers.vim index bc90de806f..b570e745f1 100644 --- a/test/old/testdir/test_registers.vim +++ b/test/old/testdir/test_registers.vim @@ -829,6 +829,8 @@ func Test_replay_charsearch_omap() call timer_start(10, {-> feedkeys(",bar\<Esc>q", 't')}) call feedkeys('qrct[', 'xt!') call assert_equal(',bar[blah]', getline(1)) + call assert_equal("ct[\<Ignore>,bar\<Esc>", @r) + call assert_equal('ct[<Ignore>,bar<Esc>', keytrans(@r)) undo call assert_equal('foo[blah]', getline(1)) call feedkeys('@r', 'xt!') |