aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/CMakeLists.txt2
-rw-r--r--runtime/doc/cmdline.txt6
-rw-r--r--runtime/doc/lua.txt186
-rw-r--r--runtime/doc/map.txt16
-rw-r--r--runtime/doc/quickfix.txt7
-rw-r--r--runtime/doc/repeat.txt5
-rw-r--r--runtime/ftplugin/meson.vim1
-rw-r--r--runtime/ftplugin/systemverilog.vim35
-rw-r--r--runtime/indent/meson.vim1
-rw-r--r--runtime/lua/vim/F.lua7
-rw-r--r--runtime/lua/vim/_meta.lua631
-rw-r--r--runtime/lua/vim/lsp/diagnostic.lua1
-rw-r--r--runtime/lua/vim/lsp/handlers.lua51
-rw-r--r--runtime/lua/vim/lsp/util.lua152
-rw-r--r--runtime/pack/dist/opt/termdebug/plugin/termdebug.vim6
-rw-r--r--runtime/syntax/dts.vim54
-rw-r--r--runtime/syntax/lsp_markdown.vim4
-rw-r--r--runtime/syntax/meson.vim53
-rw-r--r--snap/snapcraft.yaml2
-rw-r--r--src/nvim/api/vim.c2
-rw-r--r--src/nvim/ex_docmd.c22
-rw-r--r--src/nvim/lua/vim.lua246
-rw-r--r--src/nvim/option.c4
-rw-r--r--src/nvim/regexp.c32
-rw-r--r--src/nvim/regexp_nfa.c59
-rw-r--r--src/nvim/testdir/test_regexp_utf8.vim46
-rw-r--r--src/nvim/testdir/test_search.vim19
-rw-r--r--src/nvim/testdir/test_writefile.vim21
-rw-r--r--test/functional/api/vim_spec.lua4
-rw-r--r--test/functional/lua/vim_spec.lua458
30 files changed, 1790 insertions, 343 deletions
diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt
index d66e04f5f2..4fdcfc0f33 100644
--- a/runtime/CMakeLists.txt
+++ b/runtime/CMakeLists.txt
@@ -111,7 +111,7 @@ if(NOT APPLE)
install_helper(
FILES ${CMAKE_CURRENT_SOURCE_DIR}/nvim.png
- DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pixmaps)
+ DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps)
endif()
file(GLOB_RECURSE RUNTIME_PROGRAMS
diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt
index ae43aeeb25..dcdc2384dc 100644
--- a/runtime/doc/cmdline.txt
+++ b/runtime/doc/cmdline.txt
@@ -759,13 +759,15 @@ three lines: >
3:d<CR> is translated into: .,.+2d<CR>
<
-Visual Mode and Range *v_:*
- *:star-visual-range*
+Visual Mode and Range
+ *v_:*
{Visual}: Starts a command-line with the Visual selected lines as a
range. The code `:'<,'>` is used for this range, which makes
it possible to select a similar line from the command-line
history for repeating a command on different Visually selected
lines.
+
+:* *:star* *:star-visual-range*
When Visual mode was already ended, a short way to use the
Visual area for a range is `:*`.
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index be01966d42..c3893d05c0 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -912,9 +912,167 @@ vim.env *vim.env*
print(vim.env.TERM)
<
- *lua-vim-options*
-From Lua you can work with editor |options| by reading and setting items in
-these Lua tables:
+ *lua-vim-options*
+ *lua-vim-opt*
+ *lua-vim-set*
+ *lua-vim-optlocal*
+ *lua-vim-setlocal*
+
+In vimL, there is a succint and simple way to set options. For more
+information, see |set-option|. In Lua, the corresponding method is `vim.opt`.
+
+`vim.opt` provides several conveniences for setting and controlling options
+from within Lua.
+
+ Examples: ~
+
+ To set a boolean toggle:
+ In vimL:
+ `set number`
+
+ In Lua:
+ `vim.opt.number = true`
+
+ To set an array of values:
+ In vimL:
+ `set wildignore=*.o,*.a,__pycache__`
+
+ In Lua, there are two ways you can do this now. One is very similar to
+ the vimL way:
+ `vim.opt.wildignore = '*.o,*.a,__pycache__'`
+
+ However, vim.opt also supports a more elegent way of setting
+ list-style options, but using lua tables:
+ `vim.opt.wildignore = { '*.o', '*.a', '__pycache__' }`
+
+ To replicate the behavior of |:set+=|, use: >
+
+ -- vim.opt supports appending options via the "+" operator
+ vim.opt.wildignore = vim.opt.wildignore + { "*.pyc", "node_modules" }
+
+ -- or using the `:append(...)` method
+ vim.opt.wildignore:append { "*.pyc", "node_modules" }
+<
+
+ To replicate the behavior of |:set^=|, use: >
+
+ -- vim.opt supports prepending options via the "^" operator
+ vim.opt.wildignore = vim.opt.wildignore ^ { "new_first_value" }
+
+ -- or using the `:prepend(...)` method
+ vim.opt.wildignore:prepend { "new_first_value" }
+<
+ To replicate the behavior of |:set-=|, use: >
+
+ -- vim.opt supports removing options via the "-" operator
+ vim.opt.wildignore = vim.opt.wildignore - { "node_modules" }
+
+ -- or using the `:remove(...)` method
+ vim.opt.wildignore:remove { "node_modules" }
+<
+ To set a map of values:
+ In vimL:
+ `set listchars=space:_,tab:>~`
+
+ In Lua:
+ `vim.opt.listchars = { space = '_', tab = '>~' }`
+
+
+In any of the above examples, to replicate the behavior |setlocal|, use
+`vim.opt_local`. Additionally, to replicate the behavior of |setglobal|, use
+`vim.opt_global`.
+ *vim.opt*
+
+|vim.opt| returns an Option object.
+
+For example: `local listchar_object = vim.opt.listchar`
+
+An `Option` has the following methods:
+
+
+ *vim.opt:get()*
+Option:get()
+
+ Returns a lua-representation of the option. Boolean, number and string
+ values will be returned in exactly the same fashion.
+
+ For values that are comma-separated lists, an array will be returned with
+ the values as entries in the array: >
+ vim.cmd [[set wildignore=*.pyc,*.o]]
+
+ print(vim.inspect(vim.opt.wildignore:get()))
+ -- { "*.pyc", "*.o", }
+
+ for _, ignore_pattern in ipairs(vim.opt.wildignore:get()) do
+ print("Will ignore:", ignore_pattern)
+ end
+ -- Will ignore: *.pyc
+ -- Will ignore: *.o
+<
+ For values that are comma-separated maps, a table will be returned with
+ the names as keys and the values as entries: >
+ vim.cmd [[set listchars=space:_,tab:>~]]
+
+ print(vim.inspect(vim.opt.listchars:get()))
+ -- { space = "_", tab = ">~", }
+
+ for char, representation in pairs(vim.opt.listchars:get()) do
+ print(char, "->", representation)
+ end
+<
+ For values that are lists of flags, a set will be returned with the flags
+ as keys and `true` as entries. >
+ vim.cmd [[set formatoptions=njtcroql]]
+
+ print(vim.inspect(vim.opt.formatoptions:get()))
+ -- { n = true, j = true, c = true, ... }
+
+ local format_opts = vim.opt.formatoptions:get()
+ if format_opts.j then
+ print("J is enabled!")
+ end
+<
+ *vim.opt:append()*
+Option:append(value)
+
+ Append a value to string-style options. See |:set+=|
+
+ These are equivalent:
+ `vim.opt.formatoptions:append('j')`
+ `vim.opt.formatoptions = vim.opt.formatoptions + 'j'`
+
+ *vim.opt:prepend()*
+Option:prepend(value)
+
+ Prepend a value to string-style options. See |:set^=|
+
+ These are equivalent:
+ `vim.opt.wildignore:prepend('*.o')`
+ `vim.opt.wildignore = vim.opt.wildignore ^ '*.o'`
+
+ *vim.opt:remove()*
+Option:remove(value)
+
+ Remove a value from string-style options. See |:set-=|
+
+ These are equivalent:
+ `vim.opt.wildignore:remove('*.pyc')`
+ `vim.opt.wildignore = vim.opt.wildignore - '*.pyc'`
+
+
+In general, using `vim.opt` will provide the expected result when the user is
+used to interacting with editor |options| via `set`. There are still times
+where the user may want to set particular options via a shorthand in Lua,
+which is where |vim.o|, |vim.bo|, |vim.wo|, and |vim.go| come into play.
+
+The behavior of |vim.o|, |vim.bo|, |vim.wo|, and |vim.go| is designed to
+follow that of |:set|, |:setlocal|, and |:setglobal| which can be seen in the
+table below:
+
+ lua command global_value local_value ~
+vim.o :set set set
+vim.bo/vim.wo :setlocal - set
+vim.go :setglobal set -
vim.o *vim.o*
Get or set editor options, like |:set|. Invalid key is an error.
@@ -922,14 +1080,36 @@ vim.o *vim.o*
vim.o.cmdheight = 4
print(vim.o.columns)
+
+vim.go *vim.go*
+ Get or set an |option|. Invalid key is an error.
+
+ This is a wrapper around |nvim_set_option()| and |nvim_get_option()|.
+
+ NOTE: This is different than |vim.o| because this ONLY sets the global
+ option, which generally produces confusing behavior for options with
+ |global-local| values.
+
+ Example: >
+ vim.go.cmdheight = 4
+<
+
vim.bo *vim.bo*
Get or set buffer-scoped |local-options|. Invalid key is an error.
+
+ This is a wrapper around |nvim_buf_set_option()| and
+ |nvim_buf_get_option()|.
+
Example: >
vim.bo.buflisted = true
print(vim.bo.comments)
vim.wo *vim.wo*
Get or set window-scoped |local-options|. Invalid key is an error.
+
+ This is a wrapper around |nvim_win_set_option()| and
+ |nvim_win_get_option()|.
+
Example: >
vim.wo.cursorcolumn = true
print(vim.wo.foldmarker)
diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt
index ee42edf154..77cbf7d9b7 100644
--- a/runtime/doc/map.txt
+++ b/runtime/doc/map.txt
@@ -360,6 +360,22 @@ Overview of which map command works in which mode. More details below.
:cmap :cnoremap :cunmap Command-line
:tmap :tnoremap :tunmap Terminal
+Same information in a table:
+ *map-table*
+ Mode | Norm | Ins | Cmd | Vis | Sel | Opr | Term | Lang | ~
+Command +------+-----+-----+-----+-----+-----+------+------+ ~
+[nore]map | yes | - | - | yes | yes | yes | - | - |
+n[nore]map | yes | - | - | - | - | - | - | - |
+[nore]map! | - | yes | yes | - | - | - | - | - |
+i[nore]map | - | yes | - | - | - | - | - | - |
+c[nore]map | - | - | yes | - | - | - | - | - |
+v[nore]map | - | - | - | yes | yes | - | - | - |
+x[nore]map | - | - | - | yes | - | - | - | - |
+s[nore]map | - | - | - | - | yes | - | - | - |
+o[nore]map | - | - | - | - | - | yes | - | - |
+t[nore]map | - | - | - | - | - | - | yes | - |
+l[nore]map | - | yes | yes | - | - | - | - | yes |
+
COMMANDS MODES ~
Normal Visual+Select Operator-pending ~
diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt
index c67e52bd42..a937cfee98 100644
--- a/runtime/doc/quickfix.txt
+++ b/runtime/doc/quickfix.txt
@@ -1014,13 +1014,6 @@ commands can be combined to create a NewGrep command: >
updated. With the [!] any changes in the current
buffer are abandoned.
- 'f' When the 'f' flag is specified, fuzzy string
- matching is used to find matching lines. In this
- case, {pattern} is treated as a literal string
- instead of a regular expression. See
- |matchfuzzy()| for more info about fuzzy
- matching.
-
|QuickFixCmdPre| and |QuickFixCmdPost| are triggered.
A file that is opened for matching may use a buffer
number, but it is reused if possible to avoid
diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt
index b237d70760..dd05084652 100644
--- a/runtime/doc/repeat.txt
+++ b/runtime/doc/repeat.txt
@@ -50,6 +50,11 @@ Multiple repeats *multi-repeat*
:[range]v[global]/{pattern}/[cmd]
Same as :g!.
+Example: >
+ :g/^Obsolete/d _
+Using the underscore after `:d` avoids clobbering registers or the clipboard.
+This also makes it faster.
+
Instead of the '/' which surrounds the {pattern}, you can use any other
single byte character, but not an alphabetic character, '\', '"' or '|'.
This is useful if you want to include a '/' in the search pattern or
diff --git a/runtime/ftplugin/meson.vim b/runtime/ftplugin/meson.vim
index e432ebf196..d48fa1dfd1 100644
--- a/runtime/ftplugin/meson.vim
+++ b/runtime/ftplugin/meson.vim
@@ -1,6 +1,7 @@
" Vim filetype plugin file
" Language: meson
" License: VIM License
+" Maintainer: Liam Beguin <liambeguin@gmail.com>
" Original Author: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
" Last Change: 2018 Nov 27
diff --git a/runtime/ftplugin/systemverilog.vim b/runtime/ftplugin/systemverilog.vim
index 4d0f565fcc..e350427022 100644
--- a/runtime/ftplugin/systemverilog.vim
+++ b/runtime/ftplugin/systemverilog.vim
@@ -1,7 +1,7 @@
" Vim filetype plugin file
" Language: SystemVerilog
" Maintainer: kocha <kocha.lsifrontend@gmail.com>
-" Last Change: 12-Aug-2013.
+" Last Change: 07-May-2021
if exists("b:did_ftplugin")
finish
@@ -9,3 +9,36 @@ endif
" Behaves just like Verilog
runtime! ftplugin/verilog.vim
+
+let s:cpo_save = &cpo
+set cpo&vim
+
+" Add SystemVerilog keywords for matchit plugin.
+if exists("loaded_matchit")
+ let b:match_words =
+ \ '\<begin\>:\<end\>,' .
+ \ '\<case\>\|\<casex\>\|\<casez\>:\<endcase\>,' .
+ \ '\<module\>:\<endmodule\>,' .
+ \ '\<if\>:`\@<!\<else\>,' .
+ \ '\<function\>:\<endfunction\>,' .
+ \ '`ifn\?def\>:`elsif\>:`else\>:`endif\>,' .
+ \ '\<task\>:\<endtask\>,' .
+ \ '\<specify\>:\<endspecify\>,' .
+ \ '\<config\>:\<endconfig\>,' .
+ \ '\<generate\>:\<endgenerate\>,' .
+ \ '\<fork\>:\<join\>\|\<join_any\>\|\<join_none\>,' .
+ \ '\<primitive\>:\<endprimitive\>,' .
+ \ '\<table\>:\<endtable\>,' .
+ \ '\<checker\>:\<endchecker\>,' .
+ \ '\<class\>:\<endclass\>,' .
+ \ '\<clocking\>:\<endclocking\>,' .
+ \ '\<gruop\>:\<endgruop\>,' .
+ \ '\<interface\>:\<endinterface\>,' .
+ \ '\<package\>:\<endpackage\>,' .
+ \ '\<program\>:\<endprogram\>,' .
+ \ '\<property\>:\<endproperty\>,' .
+ \ '\<sequence\>:\<endsequence\>'
+endif
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
diff --git a/runtime/indent/meson.vim b/runtime/indent/meson.vim
index f116781f74..549209ca33 100644
--- a/runtime/indent/meson.vim
+++ b/runtime/indent/meson.vim
@@ -2,6 +2,7 @@
" Language: Meson
" License: VIM License
" Maintainer: Nirbheek Chauhan <nirbheek.chauhan@gmail.com>
+" Liam Beguin <liambeguin@gmail.com>
" Original Authors: David Bustos <bustos@caltech.edu>
" Bram Moolenaar <Bram@vim.org>
" Last Change: 2019 Oct 18
diff --git a/runtime/lua/vim/F.lua b/runtime/lua/vim/F.lua
index 5887e978b9..7925ff6e44 100644
--- a/runtime/lua/vim/F.lua
+++ b/runtime/lua/vim/F.lua
@@ -20,5 +20,12 @@ function F.npcall(fn, ...)
return F.ok_or_nil(pcall(fn, ...))
end
+--- Wrap a function to return nil if it fails, otherwise the value
+function F.nil_wrap(fn)
+ return function(...)
+ return F.npcall(fn, ...)
+ end
+end
+
return F
diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua
new file mode 100644
index 0000000000..02d1154df4
--- /dev/null
+++ b/runtime/lua/vim/_meta.lua
@@ -0,0 +1,631 @@
+-- prevents luacheck from making lints for setting things on vim
+local vim = assert(vim)
+
+local a = vim.api
+local validate = vim.validate
+
+local SET_TYPES = setmetatable({
+ SET = 0,
+ LOCAL = 1,
+ GLOBAL = 2,
+}, { __index = error })
+
+local options_info = {}
+for _, v in pairs(a.nvim_get_all_options_info()) do
+ options_info[v.name] = v
+ if v.shortname ~= "" then options_info[v.shortname] = v end
+end
+
+local is_global_option = function(info) return info.scope == "global" end
+local is_buffer_option = function(info) return info.scope == "buf" end
+local is_window_option = function(info) return info.scope == "win" end
+
+local get_scoped_options = function(scope)
+ local result = {}
+ for name, option_info in pairs(options_info) do
+ if option_info.scope == scope then
+ result[name] = true
+ end
+ end
+
+ return result
+end
+
+local buf_options = get_scoped_options("buf")
+local glb_options = get_scoped_options("global")
+local win_options = get_scoped_options("win")
+
+local function make_meta_accessor(get, set, del, validator)
+ validator = validator or function() return true end
+
+ validate {
+ get = {get, 'f'};
+ set = {set, 'f'};
+ del = {del, 'f', true};
+ validator = {validator, 'f'};
+ }
+
+ local mt = {}
+ function mt:__newindex(k, v)
+ if not validator(k) then
+ return
+ end
+
+ if del and v == nil then
+ return del(k)
+ end
+ return set(k, v)
+ end
+ function mt:__index(k)
+ if not validator(k) then
+ return
+ end
+
+ return get(k)
+ end
+ return setmetatable({}, mt)
+end
+
+vim.env = make_meta_accessor(function(k)
+ local v = vim.fn.getenv(k)
+ if v == vim.NIL then
+ return nil
+ end
+ return v
+end, vim.fn.setenv)
+
+do -- buffer option accessor
+ local function new_buf_opt_accessor(bufnr)
+ local function get(k)
+ if bufnr == nil and type(k) == "number" then
+ return new_buf_opt_accessor(k)
+ end
+
+ return a.nvim_buf_get_option(bufnr or 0, k)
+ end
+
+ local function set(k, v)
+ return a.nvim_buf_set_option(bufnr or 0, k, v)
+ end
+
+ return make_meta_accessor(get, set, nil, function(k)
+ if type(k) == 'string' then
+ if win_options[k] then
+ error(string.format([['%s' is a window option, not a buffer option. See ":help %s"]], k, k))
+ elseif glb_options[k] then
+ error(string.format([['%s' is a global option, not a buffer option. See ":help %s"]], k, k))
+ end
+ end
+
+ return true
+ end)
+ end
+
+ vim.bo = new_buf_opt_accessor(nil)
+end
+
+do -- window option accessor
+ local function new_win_opt_accessor(winnr)
+ local function get(k)
+ if winnr == nil and type(k) == "number" then
+ return new_win_opt_accessor(k)
+ end
+ return a.nvim_win_get_option(winnr or 0, k)
+ end
+
+ local function set(k, v)
+ return a.nvim_win_set_option(winnr or 0, k, v)
+ end
+
+ return make_meta_accessor(get, set, nil, function(k)
+ if type(k) == 'string' then
+ if buf_options[k] then
+ error(string.format([['%s' is a buffer option, not a window option. See ":help %s"]], k, k))
+ elseif glb_options[k] then
+ error(string.format([['%s' is a global option, not a window option. See ":help %s"]], k, k))
+ end
+ end
+
+ return true
+ end)
+ end
+
+ vim.wo = new_win_opt_accessor(nil)
+end
+
+--[[
+Local window setter
+
+buffer options: does not get copied when split
+ nvim_set_option(buf_opt, value) -> sets the default for NEW buffers
+ this sets the hidden global default for buffer options
+
+ nvim_buf_set_option(...) -> sets the local value for the buffer
+
+ set opt=value, does BOTH global default AND buffer local value
+ setlocal opt=value, does ONLY buffer local value
+
+window options: gets copied
+ does not need to call nvim_set_option because nobody knows what the heck this does⸮
+ We call it anyway for more readable code.
+
+
+ Command global value local value
+ :set option=value set set
+ :setlocal option=value - set
+:setglobal option=value set -
+--]]
+local function set_scoped_option(k, v, set_type)
+ local info = options_info[k]
+
+ -- Don't let people do setlocal with global options.
+ -- That is a feature that doesn't make sense.
+ if set_type == SET_TYPES.LOCAL and is_global_option(info) then
+ error(string.format("Unable to setlocal option: '%s', which is a global option.", k))
+ end
+
+ -- Only `setlocal` skips setting the default/global value
+ -- This will more-or-less noop for window options, but that's OK
+ if set_type ~= SET_TYPES.LOCAL then
+ a.nvim_set_option(k, v)
+ end
+
+ if is_window_option(info) then
+ if set_type ~= SET_TYPES.GLOBAL then
+ a.nvim_win_set_option(0, k, v)
+ end
+ elseif is_buffer_option(info) then
+ if set_type == SET_TYPES.LOCAL
+ or (set_type == SET_TYPES.SET and not info.global_local) then
+ a.nvim_buf_set_option(0, k, v)
+ end
+ end
+end
+
+--[[
+Local window getter
+
+ Command global value local value
+ :set option? - display
+ :setlocal option? - display
+:setglobal option? display -
+--]]
+local function get_scoped_option(k, set_type)
+ local info = assert(options_info[k], "Must be a valid option: " .. tostring(k))
+
+ if set_type == SET_TYPES.GLOBAL or is_global_option(info) then
+ return a.nvim_get_option(k)
+ end
+
+ if is_buffer_option(info) then
+ local was_set, value = pcall(a.nvim_buf_get_option, 0, k)
+ if was_set then return value end
+
+ if info.global_local then
+ return a.nvim_get_option(k)
+ end
+
+ error("buf_get: This should not be able to happen, given my understanding of options // " .. k)
+ end
+
+ if is_window_option(info) then
+ if vim.api.nvim_get_option_info(k).was_set then
+ local was_set, value = pcall(a.nvim_win_get_option, 0, k)
+ if was_set then return value end
+ end
+
+ return a.nvim_get_option(k)
+ end
+
+ error("This fallback case should not be possible. " .. k)
+end
+
+-- vim global option
+-- this ONLY sets the global option. like `setglobal`
+vim.go = make_meta_accessor(a.nvim_get_option, a.nvim_set_option)
+
+-- vim `set` style options.
+-- it has no additional metamethod magic.
+vim.o = make_meta_accessor(
+ function(k) return get_scoped_option(k, SET_TYPES.SET) end,
+ function(k, v) return set_scoped_option(k, v, SET_TYPES.SET) end
+)
+
+---@brief [[
+--- vim.opt, vim.opt_local and vim.opt_global implementation
+---
+--- To be used as helpers for working with options within neovim.
+--- For information on how to use, see :help vim.opt
+---
+---@brief ]]
+
+--- Preserves the order and does not mutate the original list
+local remove_duplicate_values = function(t)
+ local result, seen = {}, {}
+ if type(t) == "function" then error(debug.traceback("asdf")) end
+ for _, v in ipairs(t) do
+ if not seen[v] then
+ table.insert(result, v)
+ end
+
+ seen[v] = true
+ end
+
+ return result
+end
+
+-- TODO(tjdevries): Improve option metadata so that this doesn't have to be hardcoded.
+-- Can be done in a separate PR.
+local key_value_options = {
+ fillchars = true,
+ listchars = true,
+ winhl = true,
+}
+
+---@class OptionType
+--- Option Type Enum
+local OptionTypes = setmetatable({
+ BOOLEAN = 0,
+ NUMBER = 1,
+ STRING = 2,
+ ARRAY = 3,
+ MAP = 4,
+ SET = 5,
+}, {
+ __index = function(_, k) error("Not a valid OptionType: " .. k) end,
+ __newindex = function(_, k) error("Cannot set a new OptionType: " .. k) end,
+})
+
+--- Convert a vimoption_T style dictionary to the correct OptionType associated with it.
+---@return OptionType
+local get_option_type = function(name, info)
+ if info.type == "boolean" then
+ return OptionTypes.BOOLEAN
+ elseif info.type == "number" then
+ return OptionTypes.NUMBER
+ elseif info.type == "string" then
+ if not info.commalist and not info.flaglist then
+ return OptionTypes.STRING
+ end
+
+ if key_value_options[name] then
+ assert(info.commalist, "Must be a comma list to use key:value style")
+ return OptionTypes.MAP
+ end
+
+ if info.flaglist then
+ return OptionTypes.SET
+ elseif info.commalist then
+ return OptionTypes.ARRAY
+ end
+
+ error("Fallthrough in OptionTypes")
+ else
+ error("Not a known info.type:" .. info.type)
+ end
+end
+
+
+--- Convert a lua value to a vimoption_T value
+local convert_value_to_vim = (function()
+ -- Map of functions to take a Lua style value and convert to vimoption_T style value.
+ -- Each function takes (info, lua_value) -> vim_value
+ local to_vim_value = {
+ [OptionTypes.BOOLEAN] = function(_, value) return value end,
+ [OptionTypes.NUMBER] = function(_, value) return value end,
+ [OptionTypes.STRING] = function(_, value) return value end,
+
+ [OptionTypes.SET] = function(_, value)
+ if type(value) == "string" then return value end
+ local result = ''
+ for k in pairs(value) do
+ result = result .. k
+ end
+
+ return result
+ end,
+
+ [OptionTypes.ARRAY] = function(_, value)
+ if type(value) == "string" then return value end
+ return table.concat(remove_duplicate_values(value), ",")
+ end,
+
+ [OptionTypes.MAP] = function(_, value)
+ if type(value) == "string" then return value end
+ if type(value) == "function" then error(debug.traceback("asdf")) end
+
+ local result = {}
+ for opt_key, opt_value in pairs(value) do
+ table.insert(result, string.format("%s:%s", opt_key, opt_value))
+ end
+
+ table.sort(result)
+ return table.concat(result, ",")
+ end,
+ }
+
+ return function(name, info, value)
+ return to_vim_value[get_option_type(name, info)](info, value)
+ end
+end)()
+
+--- Converts a vimoption_T style value to a Lua value
+local convert_value_to_lua = (function()
+ -- Map of OptionType to functions that take vimoption_T values and conver to lua values.
+ -- Each function takes (info, vim_value) -> lua_value
+ local to_lua_value = {
+ [OptionTypes.BOOLEAN] = function(_, value) return value end,
+ [OptionTypes.NUMBER] = function(_, value) return value end,
+ [OptionTypes.STRING] = function(_, value) return value end,
+
+ [OptionTypes.ARRAY] = function(_, value)
+ if type(value) == "table" then
+ value = remove_duplicate_values(value)
+ return value
+ end
+
+ return vim.split(value, ",")
+ end,
+
+ [OptionTypes.SET] = function(info, value)
+ if type(value) == "table" then return value end
+
+ assert(info.flaglist, "That is the only one I know how to handle")
+
+ local result = {}
+ for i = 1, #value do
+ result[value:sub(i, i)] = true
+ end
+
+ return result
+ end,
+
+ [OptionTypes.MAP] = function(info, raw_value)
+ if type(raw_value) == "table" then return raw_value end
+
+ assert(info.commalist, "Only commas are supported currently")
+
+ local result = {}
+
+ local comma_split = vim.split(raw_value, ",")
+ for _, key_value_str in ipairs(comma_split) do
+ local key, value = unpack(vim.split(key_value_str, ":"))
+ key = vim.trim(key)
+ value = vim.trim(value)
+
+ result[key] = value
+ end
+
+ return result
+ end,
+ }
+
+ return function(name, info, option_value)
+ return to_lua_value[get_option_type(name, info)](info, option_value)
+ end
+end)()
+
+--- Handles the mutation of various different values.
+local value_mutator = function(name, info, current, new, mutator)
+ return mutator[get_option_type(name, info)](current, new)
+end
+
+--- Handles the '^' operator
+local prepend_value = (function()
+ local methods = {
+ [OptionTypes.NUMBER] = function()
+ error("The '^' operator is not currently supported for")
+ end,
+
+ [OptionTypes.STRING] = function(left, right)
+ return right .. left
+ end,
+
+ [OptionTypes.ARRAY] = function(left, right)
+ for i = #right, 1, -1 do
+ table.insert(left, 1, right[i])
+ end
+
+ return left
+ end,
+
+ [OptionTypes.MAP] = function(left, right)
+ return vim.tbl_extend("force", left, right)
+ end,
+
+ [OptionTypes.SET] = function(left, right)
+ return vim.tbl_extend("force", left, right)
+ end,
+ }
+
+ return function(name, info, current, new)
+ return value_mutator(
+ name, info, convert_value_to_lua(name, info, current), convert_value_to_lua(name, info, new), methods
+ )
+ end
+end)()
+
+--- Handles the '+' operator
+local add_value = (function()
+ local methods = {
+ [OptionTypes.NUMBER] = function(left, right)
+ return left + right
+ end,
+
+ [OptionTypes.STRING] = function(left, right)
+ return left .. right
+ end,
+
+ [OptionTypes.ARRAY] = function(left, right)
+ for _, v in ipairs(right) do
+ table.insert(left, v)
+ end
+
+ return left
+ end,
+
+ [OptionTypes.MAP] = function(left, right)
+ return vim.tbl_extend("force", left, right)
+ end,
+
+ [OptionTypes.SET] = function(left, right)
+ return vim.tbl_extend("force", left, right)
+ end,
+ }
+
+ return function(name, info, current, new)
+ return value_mutator(
+ name, info, convert_value_to_lua(name, info, current), convert_value_to_lua(name, info, new), methods
+ )
+ end
+end)()
+
+--- Handles the '-' operator
+local remove_value = (function()
+ local remove_one_item = function(t, val)
+ if vim.tbl_islist(t) then
+ local remove_index = nil
+ for i, v in ipairs(t) do
+ if v == val then
+ remove_index = i
+ end
+ end
+
+ if remove_index then
+ table.remove(t, remove_index)
+ end
+ else
+ t[val] = nil
+ end
+ end
+
+ local methods = {
+ [OptionTypes.NUMBER] = function(left, right)
+ return left - right
+ end,
+
+ [OptionTypes.STRING] = function()
+ error("Subtraction not supported for strings.")
+ end,
+
+ [OptionTypes.ARRAY] = function(left, right)
+ if type(right) == "string" then
+ remove_one_item(left, right)
+ else
+ for _, v in ipairs(right) do
+ remove_one_item(left, v)
+ end
+ end
+
+ return left
+ end,
+
+ [OptionTypes.MAP] = function(left, right)
+ if type(right) == "string" then
+ left[right] = nil
+ else
+ for _, v in ipairs(right) do
+ left[v] = nil
+ end
+ end
+
+ return left
+ end,
+
+ [OptionTypes.SET] = function(left, right)
+ if type(right) == "string" then
+ left[right] = nil
+ else
+ for _, v in ipairs(right) do
+ left[v] = nil
+ end
+ end
+
+ return left
+ end,
+ }
+
+ return function(name, info, current, new)
+ return value_mutator(name, info, convert_value_to_lua(name, info, current), new, methods)
+ end
+end)()
+
+local create_option_metatable = function(set_type)
+ local set_mt, option_mt
+
+ local make_option = function(name, value)
+ local info = assert(options_info[name], "Not a valid option name: " .. name)
+
+ if type(value) == "table" and getmetatable(value) == option_mt then
+ assert(name == value._name, "must be the same value, otherwise that's weird.")
+
+ value = value._value
+ end
+
+ return setmetatable({
+ _name = name,
+ _value = value,
+ _info = info,
+ }, option_mt)
+ end
+
+ -- TODO(tjdevries): consider supporting `nil` for set to remove the local option.
+ -- vim.cmd [[set option<]]
+
+ option_mt = {
+ -- To set a value, instead use:
+ -- opt[my_option] = value
+ _set = function(self)
+ local value = convert_value_to_vim(self._name, self._info, self._value)
+ set_scoped_option(self._name, value, set_type)
+
+ return self
+ end,
+
+ get = function(self)
+ return convert_value_to_lua(self._name, self._info, self._value)
+ end,
+
+ append = function(self, right)
+ return self:__add(right):_set()
+ end,
+
+ __add = function(self, right)
+ return make_option(self._name, add_value(self._name, self._info, self._value, right))
+ end,
+
+ prepend = function(self, right)
+ return self:__pow(right):_set()
+ end,
+
+ __pow = function(self, right)
+ return make_option(self._name, prepend_value(self._name, self._info, self._value, right))
+ end,
+
+ remove = function(self, right)
+ return self:__sub(right):_set()
+ end,
+
+ __sub = function(self, right)
+ return make_option(self._name, remove_value(self._name, self._info, self._value, right))
+ end
+ }
+ option_mt.__index = option_mt
+
+ set_mt = {
+ __index = function(_, k)
+ return make_option(k, get_scoped_option(k, set_type))
+ end,
+
+ __newindex = function(_, k, v)
+ local opt = make_option(k, v)
+ opt:_set()
+ end,
+ }
+
+ return set_mt
+end
+
+vim.opt = setmetatable({}, create_option_metatable(SET_TYPES.SET))
+vim.opt_local = setmetatable({}, create_option_metatable(SET_TYPES.LOCAL))
+vim.opt_global = setmetatable({}, create_option_metatable(SET_TYPES.GLOBAL))
diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua
index 6f2f846a3b..6a7dc1bbb0 100644
--- a/runtime/lua/vim/lsp/diagnostic.lua
+++ b/runtime/lua/vim/lsp/diagnostic.lua
@@ -1151,6 +1151,7 @@ function M.show_line_diagnostics(opts, bufnr, line_nr, client_id)
end
end
+ opts.focus_id = "line_diagnostics"
local popup_bufnr, winnr = util.open_floating_preview(lines, 'plaintext', opts)
for i, hi in ipairs(highlights) do
local prefixlen, hiname = unpack(hi)
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
index 18155ceb7e..6ae54ea253 100644
--- a/runtime/lua/vim/lsp/handlers.lua
+++ b/runtime/lua/vim/lsp/handlers.lua
@@ -260,22 +260,18 @@ end
--- - See |vim.api.nvim_open_win()|
function M.hover(_, method, result, _, _, config)
config = config or {}
- local bufnr, winnr = util.focusable_float(method, function()
- if not (result and result.contents) then
- -- return { 'No information available' }
- return
- end
- local markdown_lines = util.convert_input_to_markdown_lines(result.contents)
- markdown_lines = util.trim_empty_lines(markdown_lines)
- if vim.tbl_isempty(markdown_lines) then
- -- return { 'No information available' }
- return
- end
- local bufnr, winnr = util.fancy_floating_markdown(markdown_lines, config)
- util.close_preview_autocmd({"CursorMoved", "BufHidden", "InsertCharPre"}, winnr)
- return bufnr, winnr
- end)
- return bufnr, winnr
+ config.focus_id = method
+ if not (result and result.contents) then
+ -- return { 'No information available' }
+ return
+ end
+ local markdown_lines = util.convert_input_to_markdown_lines(result.contents)
+ markdown_lines = util.trim_empty_lines(markdown_lines)
+ if vim.tbl_isempty(markdown_lines) then
+ -- return { 'No information available' }
+ return
+ end
+ return util.open_floating_preview(markdown_lines, "markdown", config)
end
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover
@@ -333,26 +329,21 @@ M['textDocument/implementation'] = location_handler
--- - See |vim.api.nvim_open_win()|
function M.signature_help(_, method, result, _, bufnr, config)
config = config or {}
+ config.focus_id = method
-- When use `autocmd CompleteDone <silent><buffer> lua vim.lsp.buf.signature_help()` to call signatureHelp handler
-- If the completion item doesn't have signatures It will make noise. Change to use `print` that can use `<silent>` to ignore
if not (result and result.signatures and result.signatures[1]) then
print('No signature help available')
return
end
- local p_bufnr, winnr = util.focusable_float(method, function()
- local ft = api.nvim_buf_get_option(bufnr, 'filetype')
- local lines = util.convert_signature_help_to_markdown_lines(result, ft)
- lines = util.trim_empty_lines(lines)
- if vim.tbl_isempty(lines) then
- print('No signature help available')
- return
- end
- local p_bufnr, p_winnr = util.fancy_floating_markdown(lines, config)
- util.close_preview_autocmd({"CursorMoved", "CursorMovedI", "BufHidden", "InsertCharPre"}, p_winnr)
-
- return p_bufnr, p_winnr
- end)
- return p_bufnr, winnr
+ local ft = api.nvim_buf_get_option(bufnr, 'filetype')
+ local lines = util.convert_signature_help_to_markdown_lines(result, ft)
+ lines = util.trim_empty_lines(lines)
+ if vim.tbl_isempty(lines) then
+ print('No signature help available')
+ return
+ end
+ return util.open_floating_preview(lines, "markdown", config)
end
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index 4df744a357..e16b02fa6c 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -975,6 +975,8 @@ function M.preview_location(location, opts)
-- An empty syntax is more common now with TreeSitter, since TS disables syntax.
syntax = api.nvim_buf_get_option(bufnr, 'filetype')
end
+ opts = opts or {}
+ opts.focus_id = "location"
return M.open_floating_preview(contents, syntax, opts)
end
@@ -997,7 +999,9 @@ end
---buffer id, the newly created window will be the new focus associated with
---the current buffer via the tag `unique_name`.
--@returns (pbufnr, pwinnr) if `fn()` has created a new window; nil otherwise
+---@deprecated please use open_floating_preview directly
function M.focusable_float(unique_name, fn)
+ vim.notify("focusable_float is deprecated. Please use open_floating_preview and pass focus_id = [unique_name] instead", vim.log.levels.WARN)
-- Go back to previous window if we are in a focusable one
if npcall(api.nvim_win_get_var, 0, unique_name) then
return api.nvim_command("wincmd p")
@@ -1026,10 +1030,10 @@ end
--@param fn (function) The return values of this function will be passed
---directly to |vim.lsp.util.open_floating_preview()|, in the case that a new
---floating window should be created
+---@deprecated please use open_floating_preview directly
function M.focusable_preview(unique_name, fn)
- return M.focusable_float(unique_name, function()
- return M.open_floating_preview(fn())
- end)
+ vim.notify("focusable_preview is deprecated. Please use open_floating_preview and pass focus_id = [unique_name] instead", vim.log.levels.WARN)
+ return M.open_floating_preview(fn(), {focus_id = unique_name})
end
--- Trims empty lines from input and pad top and bottom with empty lines
@@ -1061,13 +1065,20 @@ end
--- TODO: refactor to separate stripping/converting and make use of open_floating_preview
---
+--- @deprecated please use open_floating_preview directly
+function M.fancy_floating_markdown(contents, opts)
+ vim.notify("fancy_floating_markdown is deprecated. Please use open_floating_preview and pass focus_id = [unique_name] instead", vim.log.levels.WARN)
+ return M.open_floating_preview(contents, "markdown", opts)
+end
+
--- Converts markdown into syntax highlighted regions by stripping the code
--- blocks and converting them into highlighted code.
--- This will by default insert a blank line separator after those code block
--- regions to improve readability.
---- The result is shown in a floating preview.
+---
+--- This method configures the given buffer and returns the lines to set.
+---
+--- If you want to open a popup with fancy markdown, use `open_floating_preview` instead
---
---@param contents table of lines to show in window
---@param opts dictionary with optional fields
@@ -1082,7 +1093,7 @@ end
--- - pad_bottom number of lines to pad contents at bottom
--- - separator insert separator after code block
---@returns width,height size of float
-function M.fancy_floating_markdown(contents, opts)
+function M.stylize_markdown(bufnr, contents, opts)
validate {
contents = { contents, 't' };
opts = { opts, 't', true };
@@ -1154,23 +1165,13 @@ function M.fancy_floating_markdown(contents, opts)
end
end
- -- Make the floating window.
- local bufnr = api.nvim_create_buf(false, true)
- local winnr = api.nvim_open_win(bufnr, false, M.make_floating_popup_options(width, height, opts))
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, stripped)
- api.nvim_buf_set_option(bufnr, 'modifiable', false)
-
- -- Switch to the floating window to apply the syntax highlighting.
- -- This is because the syntax command doesn't accept a target.
- local cwin = vim.api.nvim_get_current_win()
- vim.api.nvim_set_current_win(winnr)
- api.nvim_win_set_option(winnr, 'conceallevel', 2)
- api.nvim_win_set_option(winnr, 'concealcursor', 'n')
-
- vim.cmd("ownsyntax lsp_markdown")
local idx = 1
--@private
+ -- keep track of syntaxes we already inlcuded.
+ -- no need to include the same syntax more than once
+ local langs = {}
local function apply_syntax_to_region(ft, start, finish)
if ft == "" then
vim.cmd(string.format("syntax region markdownCode start=+\\%%%dl+ end=+\\%%%dl+ keepend extend", start, finish + 1))
@@ -1179,21 +1180,37 @@ function M.fancy_floating_markdown(contents, opts)
local name = ft..idx
idx = idx + 1
local lang = "@"..ft:upper()
- -- HACK: reset current_syntax, since some syntax files like markdown won't load if it is already set
- pcall(vim.api.nvim_buf_del_var, bufnr, "current_syntax")
- -- TODO(ashkan): better validation before this.
- if not pcall(vim.cmd, string.format("syntax include %s syntax/%s.vim", lang, ft)) then
- return
+ if not langs[lang] then
+ -- HACK: reset current_syntax, since some syntax files like markdown won't load if it is already set
+ pcall(vim.api.nvim_buf_del_var, bufnr, "current_syntax")
+ -- TODO(ashkan): better validation before this.
+ if not pcall(vim.cmd, string.format("syntax include %s syntax/%s.vim", lang, ft)) then
+ return
+ end
+ langs[lang] = true
end
vim.cmd(string.format("syntax region %s start=+\\%%%dl+ end=+\\%%%dl+ contains=%s keepend", name, start, finish + 1, lang))
end
- for _, h in ipairs(highlights) do
- apply_syntax_to_region(h.ft, h.start, h.finish)
- end
+ -- needs to run in the buffer for the regions to work
+ api.nvim_buf_call(bufnr, function()
+ -- we need to apply lsp_markdown regions speperately, since otherwise
+ -- markdown regions can "bleed" through the other syntax regions
+ -- and mess up the formatting
+ local last = 1
+ for _, h in ipairs(highlights) do
+ if last < h.start then
+ apply_syntax_to_region("lsp_markdown", last, h.start - 1)
+ end
+ apply_syntax_to_region(h.ft, h.start, h.finish)
+ last = h.finish + 1
+ end
+ if last < #stripped then
+ apply_syntax_to_region("lsp_markdown", last, #stripped)
+ end
+ end)
- vim.api.nvim_set_current_win(cwin)
- return bufnr, winnr
+ return stripped
end
--- Creates autocommands to close a preview window when events happen.
@@ -1202,7 +1219,9 @@ end
--@param winnr (number) window id of preview window
--@see |autocmd-events|
function M.close_preview_autocmd(events, winnr)
- api.nvim_command("autocmd "..table.concat(events, ',').." <buffer> ++once lua pcall(vim.api.nvim_win_close, "..winnr..", true)")
+ if #events > 0 then
+ api.nvim_command("autocmd "..table.concat(events, ',').." <buffer> ++once lua pcall(vim.api.nvim_win_close, "..winnr..", true)")
+ end
end
--@internal
@@ -1287,13 +1306,16 @@ end
--@param opts dictionary with optional fields
-- - height of floating window
-- - width of floating window
--- - wrap_at character to wrap at for computing height
+-- - wrap boolean enable wrapping of long lines (defaults to true)
+-- - wrap_at character to wrap at for computing height when wrap is enabled
-- - max_width maximal width of floating window
-- - max_height maximal height of floating window
-- - pad_left number of columns to pad contents at left
-- - pad_right number of columns to pad contents at right
-- - pad_top number of lines to pad contents at top
-- - pad_bottom number of lines to pad contents at bottom
+-- - focus_id if a popup with this id is opened, then focus it
+-- - close_events list of events that closes the floating window
--@returns bufnr,winnr buffer and window number of the newly created floating
---preview window
function M.open_floating_preview(contents, syntax, opts)
@@ -1303,27 +1325,77 @@ function M.open_floating_preview(contents, syntax, opts)
opts = { opts, 't', true };
}
opts = opts or {}
+ opts.wrap = opts.wrap ~= false -- wrapping by default
+ opts.stylize_markdown = opts.stylize_markdown ~= false
+ opts.close_events = opts.close_events or {"CursorMoved", "CursorMovedI", "BufHidden", "InsertCharPre"}
+
+ local bufnr = api.nvim_get_current_buf()
+
+ -- check if this popup is focusable and we need to focus
+ if opts.focus_id then
+ -- Go back to previous window if we are in a focusable one
+ local current_winnr = api.nvim_get_current_win()
+ if npcall(api.nvim_win_get_var, current_winnr, opts.focus_id) then
+ api.nvim_command("wincmd p")
+ return bufnr, current_winnr
+ end
+ do
+ local win = find_window_by_var(opts.focus_id, bufnr)
+ if win and api.nvim_win_is_valid(win) and vim.fn.pumvisible() == 0 then
+ -- focus and return the existing buf, win
+ api.nvim_set_current_win(win)
+ api.nvim_command("stopinsert")
+ return api.nvim_win_get_buf(win), win
+ end
+ end
+ end
+
+ local floating_bufnr = api.nvim_create_buf(false, true)
+ local do_stylize = syntax == "markdown" and opts.stylize_markdown
+
-- Clean up input: trim empty lines from the end, pad
contents = M._trim(contents, opts)
+ if do_stylize then
+ -- applies the syntax and sets the lines to the buffer
+ contents = M.stylize_markdown(floating_bufnr, contents, opts)
+ else
+ if syntax then
+ api.nvim_buf_set_option(floating_bufnr, 'syntax', syntax)
+ end
+ api.nvim_buf_set_lines(floating_bufnr, 0, -1, true, contents)
+ end
+
-- Compute size of float needed to show (wrapped) lines
- opts.wrap_at = opts.wrap_at or (vim.wo["wrap"] and api.nvim_win_get_width(0))
+ if opts.wrap then
+ opts.wrap_at = opts.wrap_at or api.nvim_win_get_width(0)
+ else
+ opts.wrap_at = nil
+ end
local width, height = M._make_floating_popup_size(contents, opts)
- local floating_bufnr = api.nvim_create_buf(false, true)
- if syntax then
- api.nvim_buf_set_option(floating_bufnr, 'syntax', syntax)
- end
local float_option = M.make_floating_popup_options(width, height, opts)
local floating_winnr = api.nvim_open_win(floating_bufnr, false, float_option)
- if syntax == 'markdown' then
+ if do_stylize then
api.nvim_win_set_option(floating_winnr, 'conceallevel', 2)
+ api.nvim_win_set_option(floating_winnr, 'concealcursor', 'n')
end
- api.nvim_buf_set_lines(floating_bufnr, 0, -1, true, contents)
+ -- disable folding
+ api.nvim_win_set_option(floating_winnr, 'foldenable', false)
+ -- soft wrapping
+ api.nvim_win_set_option(floating_winnr, 'wrap', opts.wrap)
+
api.nvim_buf_set_option(floating_bufnr, 'modifiable', false)
api.nvim_buf_set_option(floating_bufnr, 'bufhidden', 'wipe')
- M.close_preview_autocmd({"CursorMoved", "CursorMovedI", "BufHidden", "BufLeave"}, floating_winnr)
+ api.nvim_buf_set_keymap(floating_bufnr, "n", "q", "<cmd>bdelete<cr>", {silent = true, noremap = true})
+ M.close_preview_autocmd(opts.close_events, floating_winnr)
+
+ -- save focus_id
+ if opts.focus_id then
+ api.nvim_win_set_var(floating_winnr, opts.focus_id, bufnr)
+ end
+
return floating_bufnr, floating_winnr
end
diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
index a62df1dcec..ae1274f81f 100644
--- a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
+++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
@@ -2,7 +2,7 @@
"
" Author: Bram Moolenaar
" Copyright: Vim license applies, see ":help license"
-" Last Change: 2021 Jan 03
+" Last Change: 2021 May 16
"
" WORK IN PROGRESS - Only the basics work
" Note: On MS-Windows you need a recent version of gdb. The one included with
@@ -246,7 +246,9 @@ func s:StartDebug_term(dict)
let s:gdbbuf = gdb_job_info['buffer']
let s:gdbwin = win_getid(winnr())
- " Set arguments to be run
+ " Set arguments to be run. First wait a bit to make detecting gdb a bit
+ " more reliable.
+ sleep 200m
if len(proc_args)
call chansend(s:gdb_job_id, 'set args ' . join(proc_args) . "\r")
endif
diff --git a/runtime/syntax/dts.vim b/runtime/syntax/dts.vim
index cd4bd776b7..d75b9c178a 100644
--- a/runtime/syntax/dts.vim
+++ b/runtime/syntax/dts.vim
@@ -1,7 +1,7 @@
" Vim syntax file
" Language: dts/dtsi (device tree files)
" Maintainer: Daniel Mack <vim@zonque.org>
-" Last Change: 2013 Oct 20
+" Last Change: 2021 May 15
if exists("b:current_syntax")
finish
@@ -9,7 +9,7 @@ endif
syntax region dtsComment start="/\*" end="\*/"
syntax match dtsReference "&[[:alpha:][:digit:]_]\+"
-syntax region dtsBinaryProperty start="\[" end="\]"
+syntax region dtsBinaryProperty start="\[" end="\]"
syntax match dtsStringProperty "\".*\""
syntax match dtsKeyword "/.\{-1,\}/"
syntax match dtsLabel "^[[:space:]]*[[:alpha:][:digit:]_]\+:"
@@ -18,6 +18,38 @@ syntax region dtsCellProperty start="<" end=">" contains=dtsReference,dtsBinar
syntax region dtsCommentInner start="/\*" end="\*/"
syntax match dtsCommentLine "//.*$"
+" Accept %: for # (C99)
+syn region cPreCondit start="^\s*\zs\(%:\|#\)\s*\(if\|ifdef\|ifndef\|elif\)\>" skip="\\$" end="$" keepend contains=cComment,cCommentL,cCppString,cCharacter,cCppParen,cParenError,cNumbers,cCommentError,cSpaceError
+syn match cPreConditMatch display "^\s*\zs\(%:\|#\)\s*\(else\|endif\)\>"
+if !exists("c_no_if0")
+ syn cluster cCppOutInGroup contains=cCppInIf,cCppInElse,cCppInElse2,cCppOutIf,cCppOutIf2,cCppOutElse,cCppInSkip,cCppOutSkip
+ syn region cCppOutWrapper start="^\s*\zs\(%:\|#\)\s*if\s\+0\+\s*\($\|//\|/\*\|&\)" end=".\@=\|$" contains=cCppOutIf,cCppOutElse,@NoSpell fold
+ syn region cCppOutIf contained start="0\+" matchgroup=cCppOutWrapper end="^\s*\(%:\|#\)\s*endif\>" contains=cCppOutIf2,cCppOutElse
+ if !exists("c_no_if0_fold")
+ syn region cCppOutIf2 contained matchgroup=cCppOutWrapper start="0\+" end="^\s*\(%:\|#\)\s*\(else\>\|elif\s\+\(0\+\s*\($\|//\|/\*\|&\)\)\@!\|endif\>\)"me=s-1 contains=cSpaceError,cCppOutSkip,@Spell fold
+ else
+ syn region cCppOutIf2 contained matchgroup=cCppOutWrapper start="0\+" end="^\s*\(%:\|#\)\s*\(else\>\|elif\s\+\(0\+\s*\($\|//\|/\*\|&\)\)\@!\|endif\>\)"me=s-1 contains=cSpaceError,cCppOutSkip,@Spell
+ endif
+ syn region cCppOutElse contained matchgroup=cCppOutWrapper start="^\s*\(%:\|#\)\s*\(else\|elif\)" end="^\s*\(%:\|#\)\s*endif\>"me=s-1 contains=TOP,cPreCondit
+ syn region cCppInWrapper start="^\s*\zs\(%:\|#\)\s*if\s\+0*[1-9]\d*\s*\($\|//\|/\*\||\)" end=".\@=\|$" contains=cCppInIf,cCppInElse fold
+ syn region cCppInIf contained matchgroup=cCppInWrapper start="\d\+" end="^\s*\(%:\|#\)\s*endif\>" contains=TOP,cPreCondit
+ if !exists("c_no_if0_fold")
+ syn region cCppInElse contained start="^\s*\(%:\|#\)\s*\(else\>\|elif\s\+\(0*[1-9]\d*\s*\($\|//\|/\*\||\)\)\@!\)" end=".\@=\|$" containedin=cCppInIf contains=cCppInElse2 fold
+ else
+ syn region cCppInElse contained start="^\s*\(%:\|#\)\s*\(else\>\|elif\s\+\(0*[1-9]\d*\s*\($\|//\|/\*\||\)\)\@!\)" end=".\@=\|$" containedin=cCppInIf contains=cCppInElse2
+ endif
+ syn region cCppInElse2 contained matchgroup=cCppInWrapper start="^\s*\(%:\|#\)\s*\(else\|elif\)\([^/]\|/[^/*]\)*" end="^\s*\(%:\|#\)\s*endif\>"me=s-1 contains=cSpaceError,cCppOutSkip,@Spell
+ syn region cCppOutSkip contained start="^\s*\(%:\|#\)\s*\(if\>\|ifdef\>\|ifndef\>\)" skip="\\$" end="^\s*\(%:\|#\)\s*endif\>" contains=cSpaceError,cCppOutSkip
+ syn region cCppInSkip contained matchgroup=cCppInWrapper start="^\s*\(%:\|#\)\s*\(if\s\+\(\d\+\s*\($\|//\|/\*\||\|&\)\)\@!\|ifdef\>\|ifndef\>\)" skip="\\$" end="^\s*\(%:\|#\)\s*endif\>" containedin=cCppOutElse,cCppInIf,cCppInSkip contains=TOP,cPreProc
+endif
+syn region cIncluded display contained start=+"+ skip=+\\\\\|\\"+ end=+"+
+syn match cIncluded display contained "<[^>]*>"
+syn match cInclude display "^\s*\zs\(%:\|#\)\s*include\>\s*["<]" contains=cIncluded
+"syn match cLineSkip "\\$"
+syn cluster cPreProcGroup contains=cPreCondit,cIncluded,cInclude,cDefine,cErrInParen,cErrInBracket,cUserLabel,cSpecial,cOctalZero,cCppOutWrapper,cCppInWrapper,@cCppOutInGroup,cFormat,cNumber,cFloat,cOctal,cOctalError,cNumbersCom,cString,cCommentSkip,cCommentString,cComment2String,@cCommentGroup,cCommentStartError,cParen,cBracket,cMulti,cBadBlock
+syn region cDefine start="^\s*\zs\(%:\|#\)\s*\(define\|undef\)\>" skip="\\$" end="$" keepend contains=ALLBUT,@cPreProcGroup,@Spell
+syn region cPreProc start="^\s*\zs\(%:\|#\)\s*\(pragma\>\|line\>\|warning\>\|warn\>\|error\>\)" skip="\\$" end="$" keepend contains=ALLBUT,@cPreProcGroup,@Spell
+
hi def link dtsCellProperty Number
hi def link dtsBinaryProperty Number
hi def link dtsStringProperty String
@@ -26,5 +58,21 @@ hi def link dtsLabel Label
hi def link dtsNode Structure
hi def link dtsReference Macro
hi def link dtsComment Comment
-hi def link dtsCommentInner Comment
+hi def link dtsCommentInner Comment
hi def link dtsCommentLine Comment
+
+hi def link cInclude Include
+hi def link cPreProc PreProc
+hi def link cDefine Macro
+hi def link cIncluded cString
+hi def link cString String
+
+hi def link cCppInWrapper cCppOutWrapper
+hi def link cCppOutWrapper cPreCondit
+hi def link cPreConditMatch cPreCondit
+hi def link cPreCondit PreCondit
+hi def link cCppOutSkip cCppOutIf2
+
+hi def link cCppInElse2 cCppOutIf2
+hi def link cCppOutIf2 cCppOut
+hi def link cCppOut Comment
diff --git a/runtime/syntax/lsp_markdown.vim b/runtime/syntax/lsp_markdown.vim
index d5c1414f01..d9b50be54c 100644
--- a/runtime/syntax/lsp_markdown.vim
+++ b/runtime/syntax/lsp_markdown.vim
@@ -4,7 +4,9 @@
" URL: http://neovim.io
" Remark: Uses markdown syntax file
-runtime! syntax/markdown.vim
+" always source the system included markdown instead of any other installed
+" markdown.vim syntax files
+execute 'source' expand('<sfile>:p:h') .. '/markdown.vim'
syn cluster mkdNonListItem add=mkdEscape,mkdNbsp
diff --git a/runtime/syntax/meson.vim b/runtime/syntax/meson.vim
index 78de2ab89e..7e6b565f19 100644
--- a/runtime/syntax/meson.vim
+++ b/runtime/syntax/meson.vim
@@ -2,6 +2,7 @@
" Language: Meson
" License: VIM License
" Maintainer: Nirbheek Chauhan <nirbheek.chauhan@gmail.com>
+" Liam Beguin <liambeguin@gmail.com>
" Last Change: 2019 Oct 18
" Credits: Zvezdan Petkovic <zpetkovic@acm.org>
" Neil Schemenauer <nas@meson.ca>
@@ -17,11 +18,7 @@
" let meson_space_error_highlight = 1
"
-" For version 5.x: Clear all syntax items.
-" For version 6.x: Quit when a syntax file was already loaded.
-if version < 600
- syntax clear
-elseif exists("b:current_syntax")
+if exists("b:current_syntax")
finish
endif
@@ -32,8 +29,9 @@ set cpo&vim
" http://mesonbuild.com/Syntax.html
syn keyword mesonConditional elif else if endif
-syn keyword mesonRepeat foreach endforeach
-syn keyword mesonOperator and not or
+syn keyword mesonRepeat foreach endforeach
+syn keyword mesonOperator and not or in
+syn keyword mesonStatement continue break
syn match mesonComment "#.*$" contains=mesonTodo,@Spell
syn keyword mesonTodo FIXME NOTE NOTES TODO XXX contained
@@ -117,10 +115,12 @@ syn keyword mesonBuiltin
\ subdir
\ subdir_done
\ subproject
+ \ summary
\ target_machine
\ test
\ vcs_tag
\ warning
+ \ range
if exists("meson_space_error_highlight")
" trailing whitespace
@@ -130,31 +130,20 @@ if exists("meson_space_error_highlight")
syn match mesonSpaceError display "\t\+ "
endif
-if version >= 508 || !exists("did_meson_syn_inits")
- if version <= 508
- let did_meson_syn_inits = 1
- command -nargs=+ HiLink hi link <args>
- else
- command -nargs=+ HiLink hi def link <args>
- endif
-
- " The default highlight links. Can be overridden later.
- HiLink mesonStatement Statement
- HiLink mesonConditional Conditional
- HiLink mesonRepeat Repeat
- HiLink mesonOperator Operator
- HiLink mesonComment Comment
- HiLink mesonTodo Todo
- HiLink mesonString String
- HiLink mesonEscape Special
- HiLink mesonNumber Number
- HiLink mesonBuiltin Function
- HiLink mesonConstant Number
- if exists("meson_space_error_highlight")
- HiLink mesonSpaceError Error
- endif
-
- delcommand HiLink
+" The default highlight links. Can be overridden later.
+hi def link mesonStatement Statement
+hi def link mesonConditional Conditional
+hi def link mesonRepeat Repeat
+hi def link mesonOperator Operator
+hi def link mesonComment Comment
+hi def link mesonTodo Todo
+hi def link mesonString String
+hi def link mesonEscape Special
+hi def link mesonNumber Number
+hi def link mesonBuiltin Function
+hi def link mesonConstant Number
+if exists("meson_space_error_higlight")
+ hi def link mesonSpaceError Error
endif
let b:current_syntax = "meson"
diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml
index 57bcb72d5d..fd57f2d5e4 100644
--- a/snap/snapcraft.yaml
+++ b/snap/snapcraft.yaml
@@ -69,7 +69,7 @@ parts:
# Fix Desktop file
sed -i 's|^Exec=nvim|Exec=/snap/bin/nvim.nvim|' ${SNAPCRAFT_PART_INSTALL}/usr/share/applications/nvim.desktop
sed -i 's|^TryExec=nvim|TryExec=/snap/bin/nvim.nvim|' ${SNAPCRAFT_PART_INSTALL}/usr/share/applications/nvim.desktop
- sed -i 's|^Icon=.*|Icon=${SNAP}/usr/share/pixmaps/nvim.png|' ${SNAPCRAFT_PART_INSTALL}/usr/share/applications/nvim.desktop
+ sed -i 's|^Icon=.*|Icon=${SNAP}/usr/share/icons/hicolor/128x128/apps/nvim.png|' ${SNAPCRAFT_PART_INSTALL}/usr/share/applications/nvim.desktop
build-packages:
- ninja-build
- libtool
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index e9a0b0df2e..a76cefe294 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -984,7 +984,7 @@ Dictionary nvim_get_all_options_info(Error *err)
/// Resulting dictionary has keys:
/// - name: Name of the option (like 'filetype')
/// - shortname: Shortened name of the option (like 'ft')
-/// - type: type of option ("string", "integer" or "boolean")
+/// - type: type of option ("string", "number" or "boolean")
/// - default: The default value for the option
/// - was_set: Whether the option was set.
///
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index c93f9fe6f2..29347def4c 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -5017,7 +5017,7 @@ char_u *check_nextcmd(char_u *p)
static int
check_more(
int message, // when FALSE check only, no messages
- int forceit
+ bool forceit
)
{
int n = ARGCOUNT - curwin->w_arg_idx - 1;
@@ -6340,7 +6340,7 @@ void not_exiting(void)
exiting = false;
}
-static bool before_quit_autocmds(win_T *wp, bool quit_all, int forceit)
+bool before_quit_autocmds(win_T *wp, bool quit_all, bool forceit)
{
apply_autocmds(EVENT_QUITPRE, NULL, NULL, false, wp->w_buffer);
@@ -6406,7 +6406,7 @@ static void ex_quit(exarg_T *eap)
return;
}
- // If there are more files or windows we won't exit.
+ // If there is only one relevant window we will exit.
if (check_more(false, eap->forceit) == OK && only_one_window()) {
exiting = true;
}
@@ -6749,7 +6749,7 @@ static void ex_stop(exarg_T *eap)
apply_autocmds(EVENT_VIMRESUME, NULL, NULL, false, NULL);
}
-// ":exit", ":xit" and ":wq": Write file and quite the current window.
+// ":exit", ":xit" and ":wq": Write file and quit the current window.
static void ex_exit(exarg_T *eap)
{
if (cmdwin_type != 0) {
@@ -6762,17 +6762,15 @@ static void ex_exit(exarg_T *eap)
return;
}
- if (before_quit_autocmds(curwin, false, eap->forceit)) {
- return;
- }
-
- // if more files or windows we won't exit
+ // we plan to exit if there is only one relevant window
if (check_more(false, eap->forceit) == OK && only_one_window()) {
exiting = true;
}
- if (((eap->cmdidx == CMD_wq
- || curbufIsChanged())
- && do_write(eap) == FAIL)
+ // Write the buffer for ":wq" or when it was changed.
+ // Trigger QuitPre and ExitPre.
+ // Check if we can exit now, after autocommands have changed things.
+ if (((eap->cmdidx == CMD_wq || curbufIsChanged()) && do_write(eap) == FAIL)
+ || before_quit_autocmds(curwin, false, eap->forceit)
|| check_more(true, eap->forceit) == FAIL
|| (only_one_window() && check_changed_any(eap->forceit, false))) {
not_exiting();
diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua
index bc0770da31..5c9c5103a7 100644
--- a/src/nvim/lua/vim.lua
+++ b/src/nvim/lua/vim.lua
@@ -39,6 +39,75 @@ assert(vim)
vim.inspect = package.loaded['vim.inspect']
assert(vim.inspect)
+local pathtrails = {}
+vim._so_trails = {}
+for s in (package.cpath..';'):gmatch('[^;]*;') do
+ s = s:sub(1, -2) -- Strip trailing semicolon
+ -- Find out path patterns. pathtrail should contain something like
+ -- /?.so, \?.dll. This allows not to bother determining what correct
+ -- suffixes are.
+ local pathtrail = s:match('[/\\][^/\\]*%?.*$')
+ if pathtrail and not pathtrails[pathtrail] then
+ pathtrails[pathtrail] = true
+ table.insert(vim._so_trails, pathtrail)
+ end
+end
+
+function vim._load_package(name)
+ local basename = name:gsub('%.', '/')
+ local paths = {"lua/"..basename..".lua", "lua/"..basename.."/init.lua"}
+ for _,path in ipairs(paths) do
+ local found = vim.api.nvim_get_runtime_file(path, false)
+ if #found > 0 then
+ local f, err = loadfile(found[1])
+ return f or error(err)
+ end
+ end
+
+ for _,trail in ipairs(vim._so_trails) do
+ local path = "lua"..trail:gsub('?', basename) -- so_trails contains a leading slash
+ local found = vim.api.nvim_get_runtime_file(path, false)
+ if #found > 0 then
+ -- Making function name in Lua 5.1 (see src/loadlib.c:mkfuncname) is
+ -- a) strip prefix up to and including the first dash, if any
+ -- b) replace all dots by underscores
+ -- c) prepend "luaopen_"
+ -- So "foo-bar.baz" should result in "luaopen_bar_baz"
+ local dash = name:find("-", 1, true)
+ local modname = dash and name:sub(dash + 1) or name
+ local f, err = package.loadlib(found[1], "luaopen_"..modname:gsub("%.", "_"))
+ return f or error(err)
+ end
+ end
+ return nil
+end
+
+table.insert(package.loaders, 1, vim._load_package)
+
+-- These are for loading runtime modules lazily since they aren't available in
+-- the nvim binary as specified in executor.c
+setmetatable(vim, {
+ __index = function(t, key)
+ if key == 'treesitter' then
+ t.treesitter = require('vim.treesitter')
+ return t.treesitter
+ elseif key == 'F' then
+ t.F = require('vim.F')
+ return t.F
+ elseif require('vim.uri')[key] ~= nil then
+ -- Expose all `vim.uri` functions on the `vim` module.
+ t[key] = require('vim.uri')[key]
+ return t[key]
+ elseif key == 'lsp' then
+ t.lsp = require('vim.lsp')
+ return t.lsp
+ elseif key == 'highlight' then
+ t.highlight = require('vim.highlight')
+ return t.highlight
+ end
+ end
+})
+
vim.log = {
levels = {
TRACE = 0;
@@ -105,51 +174,6 @@ function vim._os_proc_children(ppid)
return children
end
-local pathtrails = {}
-vim._so_trails = {}
-for s in (package.cpath..';'):gmatch('[^;]*;') do
- s = s:sub(1, -2) -- Strip trailing semicolon
- -- Find out path patterns. pathtrail should contain something like
- -- /?.so, \?.dll. This allows not to bother determining what correct
- -- suffixes are.
- local pathtrail = s:match('[/\\][^/\\]*%?.*$')
- if pathtrail and not pathtrails[pathtrail] then
- pathtrails[pathtrail] = true
- table.insert(vim._so_trails, pathtrail)
- end
-end
-
-function vim._load_package(name)
- local basename = name:gsub('%.', '/')
- local paths = {"lua/"..basename..".lua", "lua/"..basename.."/init.lua"}
- for _,path in ipairs(paths) do
- local found = vim.api.nvim_get_runtime_file(path, false)
- if #found > 0 then
- local f, err = loadfile(found[1])
- return f or error(err)
- end
- end
-
- for _,trail in ipairs(vim._so_trails) do
- local path = "lua"..trail:gsub('?', basename) -- so_trails contains a leading slash
- local found = vim.api.nvim_get_runtime_file(path, false)
- if #found > 0 then
- -- Making function name in Lua 5.1 (see src/loadlib.c:mkfuncname) is
- -- a) strip prefix up to and including the first dash, if any
- -- b) replace all dots by underscores
- -- c) prepend "luaopen_"
- -- So "foo-bar.baz" should result in "luaopen_bar_baz"
- local dash = name:find("-", 1, true)
- local modname = dash and name:sub(dash + 1) or name
- local f, err = package.loadlib(found[1], "luaopen_"..modname:gsub("%.", "_"))
- return f or error(err)
- end
- end
- return nil
-end
-
-table.insert(package.loaders, 1, vim._load_package)
-
-- TODO(ZyX-I): Create compatibility layer.
--- Return a human-readable representation of the given object.
@@ -282,32 +306,6 @@ vim.funcref = function(viml_func_name)
return vim.fn[viml_func_name]
end
--- These are for loading runtime modules lazily since they aren't available in
--- the nvim binary as specified in executor.c
-local function __index(t, key)
- if key == 'treesitter' then
- t.treesitter = require('vim.treesitter')
- return t.treesitter
- elseif require('vim.uri')[key] ~= nil then
- -- Expose all `vim.uri` functions on the `vim` module.
- t[key] = require('vim.uri')[key]
- return t[key]
- elseif key == 'lsp' then
- t.lsp = require('vim.lsp')
- return t.lsp
- elseif key == 'highlight' then
- t.highlight = require('vim.highlight')
- return t.highlight
- elseif key == 'F' then
- t.F = require('vim.F')
- return t.F
- end
-end
-
-setmetatable(vim, {
- __index = __index
-})
-
-- An easier alias for commands.
vim.cmd = function(command)
return vim.api.nvim_exec(command, false)
@@ -315,32 +313,8 @@ end
-- These are the vim.env/v/g/o/bo/wo variable magic accessors.
do
- local a = vim.api
local validate = vim.validate
- local function make_meta_accessor(get, set, del)
- validate {
- get = {get, 'f'};
- set = {set, 'f'};
- del = {del, 'f', true};
- }
- local mt = {}
- if del then
- function mt:__newindex(k, v)
- if v == nil then
- return del(k)
- end
- return set(k, v)
- end
- else
- function mt:__newindex(k, v)
- return set(k, v)
- end
- end
- function mt:__index(k)
- return get(k)
- end
- return setmetatable({}, mt)
- end
+
local function make_dict_accessor(scope)
validate {
scope = {scope, 's'};
@@ -354,88 +328,12 @@ do
end
return setmetatable({}, mt)
end
+
vim.g = make_dict_accessor('g')
vim.v = make_dict_accessor('v')
vim.b = make_dict_accessor('b')
vim.w = make_dict_accessor('w')
vim.t = make_dict_accessor('t')
- vim.o = make_meta_accessor(a.nvim_get_option, a.nvim_set_option)
-
- local function getenv(k)
- local v = vim.fn.getenv(k)
- if v == vim.NIL then
- return nil
- end
- return v
- end
- vim.env = make_meta_accessor(getenv, vim.fn.setenv)
- -- TODO(ashkan) if/when these are available from an API, generate them
- -- instead of hardcoding.
- local window_options = {
- arab = true; arabic = true; breakindent = true; breakindentopt = true;
- bri = true; briopt = true; cc = true; cocu = true;
- cole = true; colorcolumn = true; concealcursor = true; conceallevel = true;
- crb = true; cuc = true; cul = true; cursorbind = true;
- cursorcolumn = true; cursorline = true; diff = true; fcs = true;
- fdc = true; fde = true; fdi = true; fdl = true;
- fdm = true; fdn = true; fdt = true; fen = true;
- fillchars = true; fml = true; fmr = true; foldcolumn = true;
- foldenable = true; foldexpr = true; foldignore = true; foldlevel = true;
- foldmarker = true; foldmethod = true; foldminlines = true; foldnestmax = true;
- foldtext = true; lbr = true; lcs = true; linebreak = true;
- list = true; listchars = true; nu = true; number = true;
- numberwidth = true; nuw = true; previewwindow = true; pvw = true;
- relativenumber = true; rightleft = true; rightleftcmd = true; rl = true;
- rlc = true; rnu = true; scb = true; scl = true;
- scr = true; scroll = true; scrollbind = true; signcolumn = true;
- spell = true; statusline = true; stl = true; wfh = true;
- wfw = true; winbl = true; winblend = true; winfixheight = true;
- winfixwidth = true; winhighlight = true; winhl = true; wrap = true;
- }
-
- --@private
- local function new_buf_opt_accessor(bufnr)
- --@private
- local function get(k)
- if window_options[k] then
- return a.nvim_err_writeln(k.." is a window option, not a buffer option")
- end
- if bufnr == nil and type(k) == "number" then
- return new_buf_opt_accessor(k)
- end
- return a.nvim_buf_get_option(bufnr or 0, k)
- end
-
- --@private
- local function set(k, v)
- if window_options[k] then
- return a.nvim_err_writeln(k.." is a window option, not a buffer option")
- end
- return a.nvim_buf_set_option(bufnr or 0, k, v)
- end
-
- return make_meta_accessor(get, set)
- end
- vim.bo = new_buf_opt_accessor(nil)
-
- --@private
- local function new_win_opt_accessor(winnr)
-
- --@private
- local function get(k)
- if winnr == nil and type(k) == "number" then
- return new_win_opt_accessor(k)
- end
- return a.nvim_win_get_option(winnr or 0, k)
- end
-
- --@private
- local function set(k, v)
- return a.nvim_win_set_option(winnr or 0, k, v)
- end
- return make_meta_accessor(get, set)
- end
- vim.wo = new_win_opt_accessor(nil)
end
--- Get a table of lines with start, end columns for a region marked by two points
@@ -738,4 +636,6 @@ vim._expand_pat_get_parts = function(lua_string)
return parts, search_index
end
+pcall(require, 'vim._meta')
+
return module
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 1454af1a73..67fb78bcc8 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -4883,7 +4883,7 @@ int get_option_value_strict(char *name,
if (p->flags & P_STRING) {
*stringval = xstrdup(*(char **)(varp));
} else if (p->flags & P_NUM) {
- *numval = *(long *) varp;
+ *numval = *(long *)varp;
} else {
*numval = *(int *)varp;
}
@@ -7705,7 +7705,7 @@ Dictionary get_vimoption(String name, Error *err)
Dictionary get_all_vimoptions(void)
{
Dictionary retval = ARRAY_DICT_INIT;
- for (size_t i = 0; i < PARAM_COUNT; i++) {
+ for (size_t i = 0; options[i].fullname != NULL; i++) {
Dictionary opt_dict = vimoption2dict(&options[i]);
PUT(retval, options[i].fullname, DICTIONARY_OBJ(opt_dict));
}
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index e0cc25421a..accf9b0bb5 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -692,6 +692,7 @@ static char_u *regparse; ///< Input-scan pointer.
static int prevchr_len; ///< byte length of previous char
static int num_complex_braces; ///< Complex \{...} count
static int regnpar; ///< () count.
+static bool wants_nfa; ///< regex should use NFA engine
static int regnzpar; ///< \z() count.
static int re_has_z; ///< \z item detected
static char_u *regcode; ///< Code-emit pointer, or JUST_CALC_SIZE
@@ -3974,17 +3975,25 @@ static bool regmatch(
pos = getmark_buf(rex.reg_buf, mark, false);
if (pos == NULL // mark doesn't exist
- || pos->lnum <= 0 // mark isn't set in reg_buf
- || (pos->lnum == rex.lnum + rex.reg_firstlnum
- ? (pos->col == (colnr_T)(rex.input - rex.line)
- ? (cmp == '<' || cmp == '>')
- : (pos->col < (colnr_T)(rex.input - rex.line)
- ? cmp != '>'
- : cmp != '<'))
- : (pos->lnum < rex.lnum + rex.reg_firstlnum
- ? cmp != '>'
- : cmp != '<'))) {
+ || pos->lnum <= 0) { // mark isn't set in reg_buf
status = RA_NOMATCH;
+ } else {
+ const colnr_T pos_col = pos->lnum == rex.lnum + rex.reg_firstlnum
+ && pos->col == MAXCOL
+ ? (colnr_T)STRLEN(reg_getline(pos->lnum - rex.reg_firstlnum))
+ : pos->col;
+
+ if (pos->lnum == rex.lnum + rex.reg_firstlnum
+ ? (pos_col == (colnr_T)(rex.input - rex.line)
+ ? (cmp == '<' || cmp == '>')
+ : (pos_col < (colnr_T)(rex.input - rex.line)
+ ? cmp != '>'
+ : cmp != '<'))
+ : (pos->lnum < rex.lnum + rex.reg_firstlnum
+ ? cmp != '>'
+ : cmp != '<')) {
+ status = RA_NOMATCH;
+ }
}
}
break;
@@ -7240,7 +7249,7 @@ regprog_T *vim_regcomp(char_u *expr_arg, int re_flags)
// Check for error compiling regexp with initial engine.
if (prog == NULL) {
#ifdef BT_REGEXP_DEBUG_LOG
- // Debugging log for NFA.
+ // Debugging log for BT engine.
if (regexp_engine != BACKTRACKING_ENGINE) {
FILE *f = fopen(BT_REGEXP_DEBUG_LOG_NAME, "a");
if (f) {
@@ -7257,6 +7266,7 @@ regprog_T *vim_regcomp(char_u *expr_arg, int re_flags)
// But don't try if an error message was given.
if (regexp_engine == AUTOMATIC_ENGINE && !called_emsg) {
regexp_engine = BACKTRACKING_ENGINE;
+ report_re_switch(expr);
prog = bt_regengine.regcomp(expr, re_flags);
}
}
diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c
index 923db6422e..5047e0db03 100644
--- a/src/nvim/regexp_nfa.c
+++ b/src/nvim/regexp_nfa.c
@@ -328,6 +328,11 @@ static int *post_start; ///< holds the postfix form of r.e.
static int *post_end;
static int *post_ptr;
+// Set when the pattern should use the NFA engine.
+// E.g. [[:upper:]] only allows 8bit characters for BT engine,
+// while NFA engine handles multibyte characters correctly.
+static bool wants_nfa;
+
static int nstate; ///< Number of states in the NFA. Also used when executing.
static int istate; ///< Index in the state vector, used in alloc_state()
@@ -377,6 +382,7 @@ nfa_regcomp_start (
post_start = (int *)xmalloc(postfix_size);
post_ptr = post_start;
post_end = post_start + nstate_max;
+ wants_nfa = false;
rex.nfa_has_zend = false;
rex.nfa_has_backref = false;
@@ -1618,6 +1624,7 @@ collection:
EMIT(NFA_CLASS_GRAPH);
break;
case CLASS_LOWER:
+ wants_nfa = true;
EMIT(NFA_CLASS_LOWER);
break;
case CLASS_PRINT:
@@ -1630,6 +1637,7 @@ collection:
EMIT(NFA_CLASS_SPACE);
break;
case CLASS_UPPER:
+ wants_nfa = true;
EMIT(NFA_CLASS_UPPER);
break;
case CLASS_XDIGIT:
@@ -1998,10 +2006,17 @@ static int nfa_regpiece(void)
return OK;
}
- // The engine is very inefficient (uses too many states) when the maximum
- // is much larger than the minimum and when the maximum is large. Bail out
- // if we can use the other engine.
- if ((nfa_re_flags & RE_AUTO) && (maxval > 500 || maxval > minval + 200)) {
+ // The engine is very inefficient (uses too many states) when the
+ // maximum is much larger than the minimum and when the maximum is
+ // large. However, when maxval is MAX_LIMIT, it is okay, as this
+ // will emit NFA_STAR.
+ // Bail out if we can use the other engine, but only, when the
+ // pattern does not need the NFA engine like (e.g. [[:upper:]]\{2,\}
+ // does not work with with characters > 8 bit with the BT engine)
+ if ((nfa_re_flags & RE_AUTO)
+ && (maxval > 500 || maxval > minval + 200)
+ && (maxval != MAX_LIMIT && minval < 200)
+ && !wants_nfa) {
return FAIL;
}
@@ -6055,21 +6070,27 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
{
pos_T *pos = getmark_buf(rex.reg_buf, t->state->val, false);
- // Compare the mark position to the match position.
- result = (pos != NULL // mark doesn't exist
- && pos->lnum > 0 // mark isn't set in reg_buf
- && (pos->lnum == rex.lnum + rex.reg_firstlnum
- ? (pos->col == (colnr_T)(rex.input - rex.line)
- ? t->state->c == NFA_MARK
- : (pos->col < (colnr_T)(rex.input - rex.line)
- ? t->state->c == NFA_MARK_GT
- : t->state->c == NFA_MARK_LT))
- : (pos->lnum < rex.lnum + rex.reg_firstlnum
- ? t->state->c == NFA_MARK_GT
- : t->state->c == NFA_MARK_LT)));
- if (result) {
- add_here = true;
- add_state = t->state->out;
+ // Compare the mark position to the match position, if the mark
+ // exists and mark is set in reg_buf.
+ if (pos != NULL && pos->lnum > 0) {
+ const colnr_T pos_col = pos->lnum == rex.lnum + rex.reg_firstlnum
+ && pos->col == MAXCOL
+ ? (colnr_T)STRLEN(reg_getline(pos->lnum - rex.reg_firstlnum))
+ : pos->col;
+
+ result = pos->lnum == rex.lnum + rex.reg_firstlnum
+ ? (pos_col == (colnr_T)(rex.input - rex.line)
+ ? t->state->c == NFA_MARK
+ : (pos_col < (colnr_T)(rex.input - rex.line)
+ ? t->state->c == NFA_MARK_GT
+ : t->state->c == NFA_MARK_LT))
+ : (pos->lnum < rex.lnum + rex.reg_firstlnum
+ ? t->state->c == NFA_MARK_GT
+ : t->state->c == NFA_MARK_LT);
+ if (result) {
+ add_here = true;
+ add_state = t->state->out;
+ }
}
break;
}
diff --git a/src/nvim/testdir/test_regexp_utf8.vim b/src/nvim/testdir/test_regexp_utf8.vim
index 513780938e..d8d5797dcf 100644
--- a/src/nvim/testdir/test_regexp_utf8.vim
+++ b/src/nvim/testdir/test_regexp_utf8.vim
@@ -542,6 +542,52 @@ func Test_match_start_of_line_combining()
bwipe!
endfunc
+" Check that [[:upper:]] matches for automatic engine
+func Test_match_char_class_upper()
+ new
+ let _engine=&regexpengine
+
+ " Test 1: [[:upper:]]\{2,\}
+ set regexpengine=0
+ call setline(1, ['05. ПЕСНЯ О ГЕРОЯХ муз. А. Давиденко, М. Коваля и Б. Шехтера ...', '05. PJESNJA O GJEROJAKH mus. A. Davidjenko, M. Kovalja i B. Shjekhtjera ...'])
+ call cursor(1,1)
+ let search_cmd='norm /\<[[:upper:]]\{2,\}\>' .. "\<CR>"
+ exe search_cmd
+ call assert_equal(4, searchcount().total, 'TEST 1')
+ set regexpengine=1
+ exe search_cmd
+ call assert_equal(2, searchcount().total, 'TEST 1')
+ set regexpengine=2
+ exe search_cmd
+ call assert_equal(4, searchcount().total, 'TEST 1')
+
+ " Test 2: [[:upper:]].\+
+ let search_cmd='norm /\<[[:upper:]].\+\>' .. "\<CR>"
+ set regexpengine=0
+ exe search_cmd
+ call assert_equal(2, searchcount().total, 'TEST 2')
+ set regexpengine=1
+ exe search_cmd
+ call assert_equal(1, searchcount().total, 'TEST 2')
+ set regexpengine=2
+ exe search_cmd
+ call assert_equal(2, searchcount().total, 'TEST 2')
+
+ " Test 3: [[:lower:]]\+
+ let search_cmd='norm /\<[[:lower:]]\+\>' .. "\<CR>"
+ set regexpengine=0
+ exe search_cmd
+ call assert_equal(4, searchcount().total, 'TEST 3 lower')
+ set regexpengine=1
+ exe search_cmd
+ call assert_equal(2, searchcount().total, 'TEST 3 lower')
+ set regexpengine=2
+ exe search_cmd
+ call assert_equal(4, searchcount().total, 'TEST 3 lower')
+ " clean up
+ let &regexpengine=_engine
+ bwipe!
+endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim
index 75d42b986b..b391663e0f 100644
--- a/src/nvim/testdir/test_search.vim
+++ b/src/nvim/testdir/test_search.vim
@@ -1177,13 +1177,28 @@ func Test_look_behind()
bwipe!
endfunc
+func Test_search_visual_area_linewise()
+ new
+ call setline(1, ['aa', 'bb', 'cc'])
+ exe "normal 2GV\<Esc>"
+ for engine in [1, 2]
+ exe 'set regexpengine=' .. engine
+ exe "normal gg/\\%'<\<CR>>"
+ call assert_equal([0, 2, 1, 0, 1], getcurpos(), 'engine ' .. engine)
+ exe "normal gg/\\%'>\<CR>"
+ call assert_equal([0, 2, 2, 0, 2], getcurpos(), 'engine ' .. engine)
+ endfor
+
+ bwipe!
+ set regexpengine&
+endfunc
+
func Test_search_sentence()
new
" this used to cause a crash
- call assert_fails("/\\%')", 'E486')
- call assert_fails("/", 'E486')
/\%'(
/
+ bwipe
endfunc
" Test that there is no crash when there is a last search pattern but no last
diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim
index c7710ff198..6922e2185d 100644
--- a/src/nvim/testdir/test_writefile.vim
+++ b/src/nvim/testdir/test_writefile.vim
@@ -361,4 +361,25 @@ func Test_write_file_encoding()
%bw!
endfunc
+" Check that buffer is written before triggering QuitPre
+func Test_wq_quitpre_autocommand()
+ edit Xsomefile
+ call setline(1, 'hello')
+ split
+ let g:seq = []
+ augroup Testing
+ au QuitPre * call add(g:seq, 'QuitPre - ' .. (&modified ? 'modified' : 'not modified'))
+ au BufWritePost * call add(g:seq, 'written')
+ augroup END
+ wq
+ call assert_equal(['written', 'QuitPre - not modified'], g:seq)
+
+ augroup Testing
+ au!
+ augroup END
+ bwipe!
+ unlet g:seq
+ call delete('Xsomefile')
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index 6926022ee3..0c0f610401 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -1986,6 +1986,10 @@ describe('API', function()
eq(meths.get_option_info'winhighlight', options_info.winhighlight)
end)
+
+ it('should not crash when echoed', function()
+ meths.exec("echo nvim_get_all_options_info()", true)
+ end)
end)
describe('nvim_get_option_info', function()
diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua
index 6566da4807..08a0552a38 100644
--- a/test/functional/lua/vim_spec.lua
+++ b/test/functional/lua/vim_spec.lua
@@ -1113,6 +1113,464 @@ describe('lua stdlib', function()
eq(0, funcs.luaeval "vim.wo[1000].cole")
end)
+ describe('vim.opt', function()
+ -- TODO: We still need to write some tests for optlocal, opt and then getting the options
+ -- Probably could also do some stuff with getting things from viml side as well to confirm behavior is the same.
+
+ it('should allow setting number values', function()
+ local scrolloff = exec_lua [[
+ vim.opt.scrolloff = 10
+ return vim.o.scrolloff
+ ]]
+ eq(scrolloff, 10)
+ end)
+
+ pending('should handle STUPID window things', function()
+ local result = exec_lua [[
+ local result = {}
+
+ table.insert(result, vim.api.nvim_get_option('scrolloff'))
+ table.insert(result, vim.api.nvim_win_get_option(0, 'scrolloff'))
+
+ return result
+ ]]
+
+ eq({}, result)
+ end)
+
+ it('should allow setting tables', function()
+ local wildignore = exec_lua [[
+ vim.opt.wildignore = { 'hello', 'world' }
+ return vim.o.wildignore
+ ]]
+ eq(wildignore, "hello,world")
+ end)
+
+ it('should allow setting tables with shortnames', function()
+ local wildignore = exec_lua [[
+ vim.opt.wig = { 'hello', 'world' }
+ return vim.o.wildignore
+ ]]
+ eq(wildignore, "hello,world")
+ end)
+
+ it('should error when you attempt to set string values to numeric options', function()
+ local result = exec_lua [[
+ return {
+ pcall(function() vim.opt.textwidth = 'hello world' end)
+ }
+ ]]
+
+ eq(false, result[1])
+ end)
+
+ it('should error when you attempt to setlocal a global value', function()
+ local result = exec_lua [[
+ return pcall(function() vim.opt_local.clipboard = "hello" end)
+ ]]
+
+ eq(false, result)
+ end)
+
+ it('should allow you to set boolean values', function()
+ eq({true, false, true}, exec_lua [[
+ local results = {}
+
+ vim.opt.autoindent = true
+ table.insert(results, vim.bo.autoindent)
+
+ vim.opt.autoindent = false
+ table.insert(results, vim.bo.autoindent)
+
+ vim.opt.autoindent = not vim.opt.autoindent:get()
+ table.insert(results, vim.bo.autoindent)
+
+ return results
+ ]])
+ end)
+
+ it('should change current buffer values and defaults for global local values', function()
+ local result = exec_lua [[
+ local result = {}
+
+ vim.opt.makeprg = "global-local"
+ table.insert(result, vim.api.nvim_get_option('makeprg'))
+ table.insert(result, (pcall(vim.api.nvim_buf_get_option, 0, 'makeprg')))
+
+ vim.opt_local.mp = "only-local"
+ table.insert(result, vim.api.nvim_get_option('makeprg'))
+ table.insert(result, vim.api.nvim_buf_get_option(0, 'makeprg'))
+
+ vim.opt_global.makeprg = "only-global"
+ table.insert(result, vim.api.nvim_get_option('makeprg'))
+ table.insert(result, vim.api.nvim_buf_get_option(0, 'makeprg'))
+
+ vim.opt.makeprg = "global-local"
+ table.insert(result, vim.api.nvim_get_option('makeprg'))
+ table.insert(result, vim.api.nvim_buf_get_option(0, 'makeprg'))
+ return result
+ ]]
+
+ -- Set -> global & local
+ eq("global-local", result[1])
+ eq(false, result[2])
+
+ -- Setlocal -> only local
+ eq("global-local", result[3])
+ eq("only-local", result[4])
+
+ -- Setglobal -> only global
+ eq("only-global", result[5])
+ eq("only-local", result[6])
+
+ -- set -> doesn't override previously set value
+ eq("global-local", result[7])
+ eq("only-local", result[8])
+ end)
+
+ it('should allow all sorts of string manipulation', function()
+ eq({'hello', 'hello world', 'start hello world'}, exec_lua [[
+ local results = {}
+
+ vim.opt.makeprg = "hello"
+ table.insert(results, vim.o.makeprg)
+
+ vim.opt.makeprg = vim.opt.makeprg + " world"
+ table.insert(results, vim.o.makeprg)
+
+ vim.opt.makeprg = vim.opt.makeprg ^ "start "
+ table.insert(results, vim.o.makeprg)
+
+ return results
+ ]])
+ end)
+
+ describe('option:get()', function()
+ it('should work for boolean values', function()
+ eq(false, exec_lua [[
+ vim.opt.number = false
+ return vim.opt.number:get()
+ ]])
+ end)
+
+ it('should work for number values', function()
+ local tabstop = exec_lua[[
+ vim.opt.tabstop = 10
+ return vim.opt.tabstop:get()
+ ]]
+
+ eq(10, tabstop)
+ end)
+
+ it('should work for string values', function()
+ eq("hello world", exec_lua [[
+ vim.opt.makeprg = "hello world"
+ return vim.opt.makeprg:get()
+ ]])
+ end)
+
+ it('should work for set type flaglists', function()
+ local formatoptions = exec_lua [[
+ vim.opt.formatoptions = 'tcro'
+ return vim.opt.formatoptions:get()
+ ]]
+
+ eq(true, formatoptions.t)
+ eq(true, not formatoptions.q)
+ end)
+
+ it('should work for set type flaglists', function()
+ local formatoptions = exec_lua [[
+ vim.opt.formatoptions = { t = true, c = true, r = true, o = true }
+ return vim.opt.formatoptions:get()
+ ]]
+
+ eq(true, formatoptions.t)
+ eq(true, not formatoptions.q)
+ end)
+
+ it('should work for array list type options', function()
+ local wildignore = exec_lua [[
+ vim.opt.wildignore = "*.c,*.o,__pycache__"
+ return vim.opt.wildignore:get()
+ ]]
+
+ eq(3, #wildignore)
+ eq("*.c", wildignore[1])
+ end)
+
+ it('should work for key-value pair options', function()
+ local listchars = exec_lua [[
+ vim.opt.listchars = "tab:>~,space:_"
+ return vim.opt.listchars:get()
+ ]]
+
+ eq({
+ tab = ">~",
+ space = "_",
+ }, listchars)
+ end)
+
+ it('should allow you to add numeric options', function()
+ eq(16, exec_lua [[
+ vim.opt.tabstop = 12
+ vim.opt.tabstop = vim.opt.tabstop + 4
+ return vim.bo.tabstop
+ ]])
+ end)
+
+ it('should allow you to subtract numeric options', function()
+ eq(2, exec_lua [[
+ vim.opt.tabstop = 4
+ vim.opt.tabstop = vim.opt.tabstop - 2
+ return vim.bo.tabstop
+ ]])
+ end)
+ end)
+
+ describe('key:value style options', function()
+ it('should handle dictionary style', function()
+ local listchars = exec_lua [[
+ vim.opt.listchars = {
+ eol = "~",
+ space = ".",
+ }
+
+ return vim.o.listchars
+ ]]
+ eq("eol:~,space:.", listchars)
+ end)
+
+ it('should allow adding dictionary style', function()
+ local listchars = exec_lua [[
+ vim.opt.listchars = {
+ eol = "~",
+ space = ".",
+ }
+
+ vim.opt.listchars = vim.opt.listchars + { space = "-" }
+
+ return vim.o.listchars
+ ]]
+
+ eq("eol:~,space:-", listchars)
+ end)
+
+ it('should allow adding dictionary style', function()
+ local listchars = exec_lua [[
+ vim.opt.listchars = {
+ eol = "~",
+ space = ".",
+ }
+ vim.opt.listchars = vim.opt.listchars + { space = "-" } + { space = "_" }
+
+ return vim.o.listchars
+ ]]
+
+ eq("eol:~,space:_", listchars)
+ end)
+
+ it('should allow completely new keys', function()
+ local listchars = exec_lua [[
+ vim.opt.listchars = {
+ eol = "~",
+ space = ".",
+ }
+ vim.opt.listchars = vim.opt.listchars + { tab = ">>>" }
+
+ return vim.o.listchars
+ ]]
+
+ eq("eol:~,space:.,tab:>>>", listchars)
+ end)
+
+ it('should allow subtracting dictionary style', function()
+ local listchars = exec_lua [[
+ vim.opt.listchars = {
+ eol = "~",
+ space = ".",
+ }
+ vim.opt.listchars = vim.opt.listchars - "space"
+
+ return vim.o.listchars
+ ]]
+
+ eq("eol:~", listchars)
+ end)
+
+ it('should allow subtracting dictionary style', function()
+ local listchars = exec_lua [[
+ vim.opt.listchars = {
+ eol = "~",
+ space = ".",
+ }
+ vim.opt.listchars = vim.opt.listchars - "space" - "eol"
+
+ return vim.o.listchars
+ ]]
+
+ eq("", listchars)
+ end)
+
+ it('should allow subtracting dictionary style multiple times', function()
+ local listchars = exec_lua [[
+ vim.opt.listchars = {
+ eol = "~",
+ space = ".",
+ }
+ vim.opt.listchars = vim.opt.listchars - "space" - "space"
+
+ return vim.o.listchars
+ ]]
+
+ eq("eol:~", listchars)
+ end)
+
+ it('should allow adding a key:value string to a listchars', function()
+ local listchars = exec_lua [[
+ vim.opt.listchars = {
+ eol = "~",
+ space = ".",
+ }
+ vim.opt.listchars = vim.opt.listchars + "tab:>~"
+
+ return vim.o.listchars
+ ]]
+
+ eq("eol:~,space:.,tab:>~", listchars)
+ end)
+
+ it('should allow prepending a key:value string to a listchars', function()
+ local listchars = exec_lua [[
+ vim.opt.listchars = {
+ eol = "~",
+ space = ".",
+ }
+ vim.opt.listchars = vim.opt.listchars ^ "tab:>~"
+
+ return vim.o.listchars
+ ]]
+
+ eq("eol:~,space:.,tab:>~", listchars)
+ end)
+ end)
+
+ it('should automatically set when calling remove', function()
+ eq("foo,baz", exec_lua [[
+ vim.opt.wildignore = "foo,bar,baz"
+ vim.opt.wildignore:remove("bar")
+
+ return vim.o.wildignore
+ ]])
+ end)
+
+ it('should automatically set when calling remove with a table', function()
+ eq("foo", exec_lua [[
+ vim.opt.wildignore = "foo,bar,baz"
+ vim.opt.wildignore:remove { "bar", "baz" }
+
+ return vim.o.wildignore
+ ]])
+ end)
+
+ it('should automatically set when calling append', function()
+ eq("foo,bar,baz,bing", exec_lua [[
+ vim.opt.wildignore = "foo,bar,baz"
+ vim.opt.wildignore:append("bing")
+
+ return vim.o.wildignore
+ ]])
+ end)
+
+ it('should automatically set when calling append with a table', function()
+ eq("foo,bar,baz,bing,zap", exec_lua [[
+ vim.opt.wildignore = "foo,bar,baz"
+ vim.opt.wildignore:append { "bing", "zap" }
+
+ return vim.o.wildignore
+ ]])
+ end)
+
+ it('should allow adding tables', function()
+ local wildignore = exec_lua [[
+ vim.opt.wildignore = 'foo'
+ return vim.o.wildignore
+ ]]
+ eq(wildignore, 'foo')
+
+ wildignore = exec_lua [[
+ vim.opt.wildignore = vim.opt.wildignore + { 'bar', 'baz' }
+ return vim.o.wildignore
+ ]]
+ eq(wildignore, 'foo,bar,baz')
+ end)
+
+ it('should handle adding duplicates', function()
+ local wildignore = exec_lua [[
+ vim.opt.wildignore = 'foo'
+ return vim.o.wildignore
+ ]]
+ eq(wildignore, 'foo')
+
+ wildignore = exec_lua [[
+ vim.opt.wildignore = vim.opt.wildignore + { 'bar', 'baz' }
+ return vim.o.wildignore
+ ]]
+ eq(wildignore, 'foo,bar,baz')
+
+ wildignore = exec_lua [[
+ vim.opt.wildignore = vim.opt.wildignore + { 'bar', 'baz' }
+ return vim.o.wildignore
+ ]]
+ eq(wildignore, 'foo,bar,baz')
+ end)
+
+ it('should allow adding multiple times', function()
+ local wildignore = exec_lua [[
+ vim.opt.wildignore = 'foo'
+ vim.opt.wildignore = vim.opt.wildignore + 'bar' + 'baz'
+ return vim.o.wildignore
+ ]]
+ eq(wildignore, 'foo,bar,baz')
+ end)
+
+ it('should remove values when you use minus', function()
+ local wildignore = exec_lua [[
+ vim.opt.wildignore = 'foo'
+ return vim.o.wildignore
+ ]]
+ eq(wildignore, 'foo')
+
+ wildignore = exec_lua [[
+ vim.opt.wildignore = vim.opt.wildignore + { 'bar', 'baz' }
+ return vim.o.wildignore
+ ]]
+ eq(wildignore, 'foo,bar,baz')
+
+ wildignore = exec_lua [[
+ vim.opt.wildignore = vim.opt.wildignore - 'bar'
+ return vim.o.wildignore
+ ]]
+ eq(wildignore, 'foo,baz')
+ end)
+
+ it('should prepend values when using ^', function()
+ local wildignore = exec_lua [[
+ vim.opt.wildignore = 'foo'
+ vim.opt.wildignore = vim.opt.wildignore ^ 'first'
+ return vim.o.wildignore
+ ]]
+ eq('first,foo', wildignore)
+
+ wildignore = exec_lua [[
+ vim.opt.wildignore = vim.opt.wildignore ^ 'super_first'
+ return vim.o.wildignore
+ ]]
+ eq(wildignore, 'super_first,first,foo')
+ end)
+
+ end)
+
it('vim.cmd', function()
exec_lua [[
vim.cmd "autocmd BufNew * ++once lua BUF = vim.fn.expand('<abuf>')"