diff options
35 files changed, 1900 insertions, 1401 deletions
diff --git a/.gitignore b/.gitignore index b7f710d1d7..6e8cbd0321 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ compile_commands.json /dist/ /.deps/ /tmp/ +/.clangd/ *.mo .*.sw? diff --git a/ci/build.ps1 b/ci/build.ps1 index 49fd61abf6..244b4766b2 100644 --- a/ci/build.ps1 +++ b/ci/build.ps1 @@ -114,6 +114,11 @@ if (-Not (Test-Path -PathType Leaf "$env:TREE_SITTER_DIR\bin\c.dll")) { exit 1 } +if ($compiler -eq 'MSVC') { + # Required for LuaRocks (https://github.com/luarocks/luarocks/issues/1039#issuecomment-507296940). + $env:VCINSTALLDIR = "C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.16.27023/" +} + function convertToCmakeArgs($vars) { return $vars.GetEnumerator() | foreach { "-D$($_.Key)=$($_.Value)" } } diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim index a9256638e1..36f42c0003 100644 --- a/runtime/autoload/man.vim +++ b/runtime/autoload/man.vim @@ -64,33 +64,20 @@ function! man#open_page(count, count1, mods, ...) abort return endtry - call s:push_tag() - let bufname = 'man://'.name.(empty(sect)?'':'('.sect.')') - + let [l:buf, l:save_tfu] = [bufnr(), &tagfunc] try - set eventignore+=BufReadCmd + set tagfunc=man#goto_tag + let l:target = l:name . '(' . l:sect . ')' if a:mods !~# 'tab' && s:find_man() - execute 'silent keepalt edit' fnameescape(bufname) + execute 'silent keepalt tag' l:target else - execute 'silent keepalt' a:mods 'split' fnameescape(bufname) + execute 'silent keepalt' a:mods 'stag' l:target endif finally - set eventignore-=BufReadCmd - endtry - - try - let page = s:get_page(path) - catch - if a:mods =~# 'tab' || !s:find_man() - " a new window was opened - close - endif - call s:error(v:exception) - return + call setbufvar(l:buf, '&tagfunc', l:save_tfu) endtry let b:man_sect = sect - call s:put_page(page) endfunction function! man#read_page(ref) abort @@ -163,6 +150,7 @@ endfunction function! s:put_page(page) abort setlocal modifiable setlocal noreadonly + setlocal noswapfile silent keepjumps %delete _ silent put =a:page while getline(1) =~# '^\s*$' @@ -254,24 +242,6 @@ function! s:verify_exists(sect, name) abort return s:extract_sect_and_name_path(path) + [path] endfunction -let s:tag_stack = [] - -function! s:push_tag() abort - let s:tag_stack += [{ - \ 'buf': bufnr('%'), - \ 'lnum': line('.'), - \ 'col': col('.'), - \ }] -endfunction - -function! man#pop_tag() abort - if !empty(s:tag_stack) - let tag = remove(s:tag_stack, -1) - execute 'silent' tag['buf'].'buffer' - call cursor(tag['lnum'], tag['col']) - endif -endfunction - " extracts the name and sect out of 'path/name.sect' function! s:extract_sect_and_name_path(path) abort let tail = fnamemodify(a:path, ':t') @@ -356,14 +326,18 @@ function! man#complete(arg_lead, cmd_line, cursor_pos) abort return s:complete(sect, sect, name) endfunction -function! s:complete(sect, psect, name) abort +function! s:get_paths(sect, name) abort try let mandirs = join(split(s:system(['man', s:find_arg]), ':\|\n'), ',') catch call s:error(v:exception) return endtry - let pages = globpath(mandirs,'man?/'.a:name.'*.'.a:sect.'*', 0, 1) + return globpath(mandirs,'man?/'.a:name.'*.'.a:sect.'*', 0, 1) +endfunction + +function! s:complete(sect, psect, name) abort + let pages = s:get_paths(a:sect, a:name) " We remove duplicates in case the same manpage in different languages was found. return uniq(sort(map(pages, 's:format_candidate(v:val, a:psect)'), 'i')) endfunction @@ -402,4 +376,27 @@ function! man#init_pager() abort endif endfunction +function! man#goto_tag(pattern, flags, info) abort + let [l:sect, l:name] = man#extract_sect_and_name_ref(a:pattern) + + let l:paths = s:get_paths(l:sect, l:name) + let l:structured = [] + + for l:path in l:paths + let l:n = s:extract_sect_and_name_path(l:path)[1] + let l:structured += [{ 'name': l:n, 'path': l:path }] + endfor + + " sort by relevance - exact matches first, then the previous order + call sort(l:structured, { a, b -> a.name ==? l:name ? -1 : b.name ==? l:name ? 1 : 0 }) + + return map(l:structured, { + \ _, entry -> { + \ 'name': entry.name, + \ 'filename': 'man://' . entry.path, + \ 'cmd': '1' + \ } + \ }) +endfunction + call s:init() diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 4ed0a6aba0..57a72e6173 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -462,7 +462,7 @@ We can get a mark by its id: > We can get all marks in a buffer for our namespace (or by a range): > - echo nvim_buf_get_extmarks(0, g:mark_ns, 0, -1, -1) + echo nvim_buf_get_extmarks(0, g:mark_ns, 0, -1, {}) => [[1, 0, 2]] Deleting all text surrounding an extmark does not remove the extmark. To @@ -1516,6 +1516,13 @@ nvim_buf_attach({buffer}, {send_buffer}, {opts}) *nvim_buf_attach()* Activates buffer-update events on a channel, or as Lua callbacks. + Example (Lua): capture buffer updates in a global `events` variable (use "print(vim.inspect(events))" to see its + contents): > + events = {} + vim.api.nvim_buf_attach(0, false, { + on_lines=function(...) table.insert(events, {...}) end}) +< + Parameters: ~ {buffer} Buffer handle, or 0 for current buffer {send_buffer} True if the initial notification should @@ -1804,21 +1811,22 @@ nvim_buf_get_extmarks({buffer}, {ns_id}, {start}, {end}, {opts}) range ends can be specified as (row, col) tuples, as well as extmark ids in the same namespace. In addition, 0 and -1 works as shorthands for (0,0) and (-1,-1) respectively, so that all - marks in the buffer can be quieried as: + marks in the buffer can be queried as: - all_marks = nvim_buf_get_extmarks(0, my_ns, 0, -1, -1) + all_marks = nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) If end is a lower position than start, then the range will be - traversed backwards. This is mostly used with limited amount, - to be able to get the first marks prior to a given position. + traversed backwards. This is mostly useful with limited + amount, to be able to get the first marks prior to a given + position. Parameters: ~ {buffer} The buffer handle {ns_id} An id returned previously from nvim_create_namespace - {lower} One of: extmark id, (row, col) or 0, -1 for + {start} One of: extmark id, (row, col) or 0, -1 for buffer ends - {upper} One of: extmark id, (row, col) or 0, -1 for + {end} One of: extmark id, (row, col) or 0, -1 for buffer ends {opts} additional options. Supports the keys: • amount: Maximum number of marks to return @@ -1845,7 +1853,7 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {id}, {line}, {col}, {opts}) {ns_id} a identifier returned previously with nvim_create_namespace {id} The extmark's id or 0 to create a new mark. - {row} The row to set the extmark to. + {line} The row to set the extmark to. {col} The column to set the extmark to. {opts} Optional parameters. Currently not used. diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 79bf81dc0e..84a893a205 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1738,7 +1738,7 @@ v:lnum Line number for the 'foldexpr' |fold-expr|, 'formatexpr' and |sandbox|. *v:lua* *lua-variable* -v:lua Prefix for calling lua functions from expressions. +v:lua Prefix for calling Lua functions from expressions. See |v:lua-call| for more information. *v:mouse_win* *mouse_win-variable* diff --git a/runtime/doc/help.txt b/runtime/doc/help.txt index 284cd26583..6090fa96bb 100644 --- a/runtime/doc/help.txt +++ b/runtime/doc/help.txt @@ -129,6 +129,7 @@ Advanced editing ~ |autocmd.txt| automatically executing commands on an event |eval.txt| expression evaluation, conditional commands |fold.txt| hide (fold) ranges of lines +|lua.txt| Lua API Special issues ~ |print.txt| printing @@ -157,7 +158,6 @@ GUI ~ Interfaces ~ |if_cscop.txt| using Cscope with Vim -|if_lua.txt| Lua interface |if_pyth.txt| Python interface |if_ruby.txt| Ruby interface |sign.txt| debugging signs diff --git a/runtime/doc/if_lua.txt b/runtime/doc/if_lua.txt index 911197acd4..34bcf0f039 100644 --- a/runtime/doc/if_lua.txt +++ b/runtime/doc/if_lua.txt @@ -1,941 +1,8 @@ -*if_lua.txt* Nvim - NVIM REFERENCE MANUAL + NVIM REFERENCE MANUAL - -Lua engine *lua* *Lua* - - Type |gO| to see the table of contents. - -============================================================================== -Introduction *lua-intro* - -The Lua 5.1 language is builtin and always available. Try this command to get -an idea of what lurks beneath: > - - :lua print(vim.inspect(package.loaded)) - -Nvim includes a "standard library" |lua-stdlib| for Lua. It complements the -"editor stdlib" (|functions| and Ex commands) and the |API|, all of which can -be used from Lua code. - -Module conflicts are resolved by "last wins". For example if both of these -are on 'runtimepath': - runtime/lua/foo.lua - ~/.config/nvim/lua/foo.lua -then `require('foo')` loads "~/.config/nvim/lua/foo.lua", and -"runtime/lua/foo.lua" is not used. See |lua-require| to understand how Nvim -finds and loads Lua modules. The conventions are similar to VimL plugins, -with some extra features. See |lua-require-example| for a walkthrough. - -============================================================================== -Importing Lua modules *lua-require* - -Nvim automatically adjusts `package.path` and `package.cpath` according to -effective 'runtimepath' value. Adjustment happens whenever 'runtimepath' is -changed. `package.path` is adjusted by simply appending `/lua/?.lua` and -`/lua/?/init.lua` to each directory from 'runtimepath' (`/` is actually the -first character of `package.config`). - -Similarly to `package.path`, modified directories from 'runtimepath' are also -added to `package.cpath`. In this case, instead of appending `/lua/?.lua` and -`/lua/?/init.lua` to each runtimepath, all unique `?`-containing suffixes of -the existing `package.cpath` are used. Example: - -1. Given that - - 'runtimepath' contains `/foo/bar,/xxx;yyy/baz,/abc`; - - initial (defined at compile-time or derived from - `$LUA_CPATH`/`$LUA_INIT`) `package.cpath` contains - `./?.so;/def/ghi/a?d/j/g.elf;/def/?.so`. -2. It finds `?`-containing suffixes `/?.so`, `/a?d/j/g.elf` and `/?.so`, in - order: parts of the path starting from the first path component containing - question mark and preceding path separator. -3. The suffix of `/def/?.so`, namely `/?.so` is not unique, as it’s the same - as the suffix of the first path from `package.path` (i.e. `./?.so`). Which - leaves `/?.so` and `/a?d/j/g.elf`, in this order. -4. 'runtimepath' has three paths: `/foo/bar`, `/xxx;yyy/baz` and `/abc`. The - second one contains semicolon which is a paths separator so it is out, - leaving only `/foo/bar` and `/abc`, in order. -5. The cartesian product of paths from 4. and suffixes from 3. is taken, - giving four variants. In each variant `/lua` path segment is inserted - between path and suffix, leaving - - - `/foo/bar/lua/?.so` - - `/foo/bar/lua/a?d/j/g.elf` - - `/abc/lua/?.so` - - `/abc/lua/a?d/j/g.elf` - -6. New paths are prepended to the original `package.cpath`. - -The result will look like this: - - `/foo/bar,/xxx;yyy/baz,/abc` ('runtimepath') - × `./?.so;/def/ghi/a?d/j/g.elf;/def/?.so` (`package.cpath`) - - = `/foo/bar/lua/?.so;/foo/bar/lua/a?d/j/g.elf;/abc/lua/?.so;/abc/lua/a?d/j/g.elf;./?.so;/def/ghi/a?d/j/g.elf;/def/?.so` - -Note: - -- To track 'runtimepath' updates, paths added at previous update are - remembered and removed at the next update, while all paths derived from the - new 'runtimepath' are prepended as described above. This allows removing - paths when path is removed from 'runtimepath', adding paths when they are - added and reordering `package.path`/`package.cpath` content if 'runtimepath' - was reordered. - -- Although adjustments happen automatically, Nvim does not track current - values of `package.path` or `package.cpath`. If you happen to delete some - paths from there you can set 'runtimepath' to trigger an update: > - let &runtimepath = &runtimepath - -- Skipping paths from 'runtimepath' which contain semicolons applies both to - `package.path` and `package.cpath`. Given that there are some badly written - plugins using shell which will not work with paths containing semicolons it - is better to not have them in 'runtimepath' at all. - ------------------------------------------------------------------------------- -LUA PLUGIN EXAMPLE *lua-require-example* - -The following example plugin adds a command `:MakeCharBlob` which transforms -current buffer into a long `unsigned char` array. Lua contains transformation -function in a module `lua/charblob.lua` which is imported in -`autoload/charblob.vim` (`require("charblob")`). Example plugin is supposed -to be put into any directory from 'runtimepath', e.g. `~/.config/nvim` (in -this case `lua/charblob.lua` means `~/.config/nvim/lua/charblob.lua`). - -autoload/charblob.vim: > - - function charblob#encode_buffer() - call setline(1, luaeval( - \ 'require("charblob").encode(unpack(_A))', - \ [getline(1, '$'), &textwidth, ' '])) - endfunction - -plugin/charblob.vim: > - - if exists('g:charblob_loaded') - finish - endif - let g:charblob_loaded = 1 - - command MakeCharBlob :call charblob#encode_buffer() - -lua/charblob.lua: > - - local function charblob_bytes_iter(lines) - local init_s = { - next_line_idx = 1, - next_byte_idx = 1, - lines = lines, - } - local function next(s, _) - if lines[s.next_line_idx] == nil then - return nil - end - if s.next_byte_idx > #(lines[s.next_line_idx]) then - s.next_line_idx = s.next_line_idx + 1 - s.next_byte_idx = 1 - return ('\n'):byte() - end - local ret = lines[s.next_line_idx]:byte(s.next_byte_idx) - if ret == ('\n'):byte() then - ret = 0 -- See :h NL-used-for-NUL. - end - s.next_byte_idx = s.next_byte_idx + 1 - return ret - end - return next, init_s, nil - end - - local function charblob_encode(lines, textwidth, indent) - local ret = { - 'const unsigned char blob[] = {', - indent, - } - for byte in charblob_bytes_iter(lines) do - -- .- space + number (width 3) + comma - if #(ret[#ret]) + 5 > textwidth then - ret[#ret + 1] = indent - else - ret[#ret] = ret[#ret] .. ' ' - end - ret[#ret] = ret[#ret] .. (('%3u,'):format(byte)) - end - ret[#ret + 1] = '};' - return ret - end - - return { - bytes_iter = charblob_bytes_iter, - encode = charblob_encode, - } - -============================================================================== -Commands *lua-commands* - - *:lua* -:[range]lua {chunk} - Execute Lua chunk {chunk}. - -Examples: -> - :lua vim.api.nvim_command('echo "Hello, Nvim!"') -< -To see the Lua version: > - :lua print(_VERSION) - -To see the LuaJIT version: > - :lua print(jit.version) -< - -:[range]lua << [endmarker] -{script} -{endmarker} - Execute Lua script {script}. Useful for including Lua - code in Vim scripts. - -The {endmarker} must NOT be preceded by any white space. - -If [endmarker] is omitted from after the "<<", a dot '.' must be used after -{script}, like for the |:append| and |:insert| commands. - -Example: -> - function! CurrentLineInfo() - lua << EOF - local linenr = vim.api.nvim_win_get_cursor(0)[1] - local curline = vim.api.nvim_buf_get_lines( - 0, linenr, linenr + 1, false)[1] - print(string.format("Current line [%d] has %d bytes", - linenr, #curline)) - EOF - endfunction - -Note that the `local` variables will disappear when block finishes. This is -not the case for globals. - - *:luado* -:[range]luado {body} Execute Lua function "function (line, linenr) {body} - end" for each line in the [range], with the function - argument being set to the text of each line in turn, - without a trailing <EOL>, and the current line number. - If the value returned by the function is a string it - becomes the text of the line in the current turn. The - default for [range] is the whole file: "1,$". - -Examples: -> - :luado return string.format("%s\t%d", line:reverse(), #line) - - :lua require"lpeg" - :lua -- balanced parenthesis grammar: - :lua bp = lpeg.P{ "(" * ((1 - lpeg.S"()") + lpeg.V(1))^0 * ")" } - :luado if bp:match(line) then return "-->\t" .. line end -< - - *:luafile* -:[range]luafile {file} - Execute Lua script in {file}. - The whole argument is used as a single file name. - -Examples: -> - :luafile script.lua - :luafile % -< - -All these commands execute a Lua chunk from either the command line (:lua and -:luado) or a file (:luafile) with the given line [range]. Similarly to the Lua -interpreter, each chunk has its own scope and so only global variables are -shared between command calls. All Lua default libraries are available. In -addition, Lua "print" function has its output redirected to the Nvim message -area, with arguments separated by a white space instead of a tab. - -Lua uses the "vim" module (see |lua-vim|) to issue commands to Nvim. However, -procedures that alter buffer content, open new buffers, and change cursor -position are restricted when the command is executed in the |sandbox|. - - -============================================================================== -luaeval() *lua-eval* *luaeval()* - -The (dual) equivalent of "vim.eval" for passing Lua values to Nvim is -"luaeval". "luaeval" takes an expression string and an optional argument used -for _A inside expression and returns the result of the expression. It is -semantically equivalent in Lua to: -> - local chunkheader = "local _A = select(1, ...) return " - function luaeval (expstr, arg) - local chunk = assert(loadstring(chunkheader .. expstr, "luaeval")) - return chunk(arg) -- return typval - end - -Lua nils, numbers, strings, tables and booleans are converted to their -respective VimL types. An error is thrown if conversion of any other Lua types -is attempted. - -The magic global "_A" contains the second argument to luaeval(). - -Example: > - :echo luaeval('_A[1] + _A[2]', [40, 2]) - 42 - :echo luaeval('string.match(_A, "[a-z]+")', 'XYXfoo123') - foo - -Lua tables are used as both dictionaries and lists, so it is impossible to -determine whether empty table is meant to be empty list or empty dictionary. -Additionally Lua does not have integer numbers. To distinguish between these -cases there is the following agreement: - -0. Empty table is empty list. -1. Table with N incrementally growing integral numbers, starting from 1 and - ending with N is considered to be a list. -2. Table with string keys, none of which contains NUL byte, is considered to - be a dictionary. -3. Table with string keys, at least one of which contains NUL byte, is also - considered to be a dictionary, but this time it is converted to - a |msgpack-special-map|. - *lua-special-tbl* -4. Table with `vim.type_idx` key may be a dictionary, a list or floating-point - value: - - `{[vim.type_idx]=vim.types.float, [vim.val_idx]=1}` is converted to - a floating-point 1.0. Note that by default integral Lua numbers are - converted to |Number|s, non-integral are converted to |Float|s. This - variant allows integral |Float|s. - - `{[vim.type_idx]=vim.types.dictionary}` is converted to an empty - dictionary, `{[vim.type_idx]=vim.types.dictionary, [42]=1, a=2}` is - converted to a dictionary `{'a': 42}`: non-string keys are ignored. - Without `vim.type_idx` key tables with keys not fitting in 1., 2. or 3. - are errors. - - `{[vim.type_idx]=vim.types.list}` is converted to an empty list. As well - as `{[vim.type_idx]=vim.types.list, [42]=1}`: integral keys that do not - form a 1-step sequence from 1 to N are ignored, as well as all - non-integral keys. - -Examples: > - - :echo luaeval('math.pi') - :function Rand(x,y) " random uniform between x and y - : return luaeval('(_A.y-_A.x)*math.random()+_A.x', {'x':a:x,'y':a:y}) - : endfunction - :echo Rand(1,10) - -Note: second argument to `luaeval` undergoes VimL to Lua conversion -("marshalled"), so changes to Lua containers do not affect values in VimL. -Return value is also always converted. When converting, -|msgpack-special-dict|s are treated specially. - -============================================================================== -v:lua function calls *v:lua-call* - -The special prefix `v:lua` can be used in vimL expressions to call lua -functions which are global or nested inside global tables. The expression -`v:lua.func(arg1, arg2)` is equivalent to executing the lua code -`return func(...)` where the args have been converted to lua values. In addition -`v:lua.somemod.func(args)` will work like `return somemod.func(...)` . - -`v:lua` can also be used in function options like 'omnifunc'. As an -example, consider the following lua implementation of an omnifunc: > - - function mymod.omnifunc(findstart, base) - if findstart == 1 then - return 0 - else - return {'stuff', 'steam', 'strange things'} - end - end - vim.api.nvim_buf_set_option(0, 'omnifunc', 'v:lua.mymod.omnifunc') - -A limitation is that the plugin module ("mymod" in this case) must -be made available as a global. - -Note: `v:lua` without a call is not allowed in a vimL expression. Funcrefs -to lua functions cannot be created. The following are errors: > - - let g:Myvar = v:lua.myfunc - call SomeFunc(v:lua.mycallback) - let g:foo = v:lua - let g:foo = v:['lua'] - -============================================================================== -Lua standard modules *lua-stdlib* - -The Nvim Lua "standard library" (stdlib) is the `vim` module, which exposes -various functions and sub-modules. It is always loaded, thus require("vim") -is unnecessary. - -You can peek at the module properties: > - - :lua print(vim.inspect(vim)) - -Result is something like this: > - - { - _os_proc_children = <function 1>, - _os_proc_info = <function 2>, - ... - api = { - nvim__id = <function 5>, - nvim__id_array = <function 6>, - ... - }, - deepcopy = <function 106>, - gsplit = <function 107>, - ... - } - -To find documentation on e.g. the "deepcopy" function: > - - :help vim.deepcopy() - -Note that underscore-prefixed functions (e.g. "_os_proc_children") are -internal/private and must not be used by plugins. - ------------------------------------------------------------------------------- -VIM.LOOP *lua-loop* *vim.loop* - -`vim.loop` exposes all features of the Nvim event-loop. This is a low-level -API that provides functionality for networking, filesystem, and process -management. Try this command to see available functions: > - - :lua print(vim.inspect(vim.loop)) - -Reference: http://docs.libuv.org -Examples: https://github.com/luvit/luv/tree/master/examples - - *E5560* *lua-loop-callbacks* -It is an error to directly invoke `vim.api` functions (except |api-fast|) in -`vim.loop` callbacks. For example, this is an error: > - - local timer = vim.loop.new_timer() - timer:start(1000, 0, function() - vim.api.nvim_command('echomsg "test"') - end) - -To avoid the error use |vim.schedule_wrap()| to defer the callback: > - - local timer = vim.loop.new_timer() - timer:start(1000, 0, vim.schedule_wrap(function() - vim.api.nvim_command('echomsg "test"') - end)) - -Example: repeating timer - 1. Save this code to a file. - 2. Execute it with ":luafile %". > - - -- Create a timer handle (implementation detail: uv_timer_t). - local timer = vim.loop.new_timer() - local i = 0 - -- Waits 1000ms, then repeats every 750ms until timer:close(). - timer:start(1000, 750, function() - print('timer invoked! i='..tostring(i)) - if i > 4 then - timer:close() -- Always close handles to avoid leaks. - end - i = i + 1 - end) - print('sleeping'); - - -Example: File-change detection *file-change-detect* - 1. Save this code to a file. - 2. Execute it with ":luafile %". - 3. Use ":Watch %" to watch any file. - 4. Try editing the file from another text editor. - 5. Observe that the file reloads in Nvim (because on_change() calls - |:checktime|). > - - local w = vim.loop.new_fs_event() - local function on_change(err, fname, status) - -- Do work... - vim.api.nvim_command('checktime') - -- Debounce: stop/start. - w:stop() - watch_file(fname) - end - function watch_file(fname) - local fullpath = vim.api.nvim_call_function( - 'fnamemodify', {fname, ':p'}) - w:start(fullpath, {}, vim.schedule_wrap(function(...) - on_change(...) end)) - end - vim.api.nvim_command( - "command! -nargs=1 Watch call luaeval('watch_file(_A)', expand('<args>'))") - - -Example: TCP echo-server *tcp-server* - 1. Save this code to a file. - 2. Execute it with ":luafile %". - 3. Note the port number. - 4. Connect from any TCP client (e.g. "nc 0.0.0.0 36795"): > - - local function create_server(host, port, on_connect) - local server = vim.loop.new_tcp() - server:bind(host, port) - server:listen(128, function(err) - assert(not err, err) -- Check for errors. - local sock = vim.loop.new_tcp() - server:accept(sock) -- Accept client connection. - on_connect(sock) -- Start reading messages. - end) - return server - end - local server = create_server('0.0.0.0', 0, function(sock) - sock:read_start(function(err, chunk) - assert(not err, err) -- Check for errors. - if chunk then - sock:write(chunk) -- Echo received messages to the channel. - else -- EOF (stream closed). - sock:close() -- Always close handles to avoid leaks. - end - end) - end) - print('TCP echo-server listening on port: '..server:getsockname().port) - ------------------------------------------------------------------------------- -VIM.TREESITTER *lua-treesitter* - -Nvim integrates the tree-sitter library for incremental parsing of buffers. - -Currently Nvim does not provide the tree-sitter parsers, instead these must -be built separately, for instance using the tree-sitter utility. -The parser is loaded into nvim using > - - vim.treesitter.add_language("/path/to/c_parser.so", "c") - -<Create a parser for a buffer and a given language (if another plugin uses the -same buffer/language combination, it will be safely reused). Use > - - parser = vim.treesitter.get_parser(bufnr, lang) - -<`bufnr=0` can be used for current buffer. `lang` will default to 'filetype' (this -doesn't work yet for some filetypes like "cpp") Currently, the parser will be -retained for the lifetime of a buffer but this is subject to change. A plugin -should keep a reference to the parser object as long as it wants incremental -updates. - -Whenever you need to access the current syntax tree, parse the buffer: > - - tstree = parser:parse() - -<This will return an immutable tree that represents the current state of the -buffer. When the plugin wants to access the state after a (possible) edit -it should call `parse()` again. If the buffer wasn't edited, the same tree will -be returned again without extra work. If the buffer was parsed before, -incremental parsing will be done of the changed parts. - -NB: to use the parser directly inside a |nvim_buf_attach| Lua callback, you must -call `get_parser()` before you register your callback. But preferably parsing -shouldn't be done directly in the change callback anyway as they will be very -frequent. Rather a plugin that does any kind of analysis on a tree should use -a timer to throttle too frequent updates. - -Tree methods *lua-treesitter-tree* - -tstree:root() *tstree:root()* - Return the root node of this tree. - - -Node methods *lua-treesitter-node* - -tsnode:parent() *tsnode:parent()* - Get the node's immediate parent. - -tsnode:child_count() *tsnode:child_count()* - Get the node's number of children. - -tsnode:child(N) *tsnode:child()* - Get the node's child at the given index, where zero represents the - first child. - -tsnode:named_child_count() *tsnode:named_child_count()* - Get the node's number of named children. - -tsnode:named_child(N) *tsnode:named_child()* - Get the node's named child at the given index, where zero represents - the first named child. - -tsnode:start() *tsnode:start()* - Get the node's start position. Return three values: the row, column - and total byte count (all zero-based). - -tsnode:end_() *tsnode:end_()* - Get the node's end position. Return three values: the row, column - and total byte count (all zero-based). - -tsnode:range() *tsnode:range()* - Get the range of the node. Return four values: the row, column - of the start position, then the row, column of the end position. - -tsnode:type() *tsnode:type()* - Get the node's type as a string. - -tsnode:symbol() *tsnode:symbol()* - Get the node's type as a numerical id. - -tsnode:named() *tsnode:named()* - Check if the node is named. Named nodes correspond to named rules in - the grammar, whereas anonymous nodes correspond to string literals - in the grammar. - -tsnode:missing() *tsnode:missing()* - Check if the node is missing. Missing nodes are inserted by the - parser in order to recover from certain kinds of syntax errors. - -tsnode:has_error() *tsnode:has_error()* - Check if the node is a syntax error or contains any syntax errors. - -tsnode:sexpr() *tsnode:sexpr()* - Get an S-expression representing the node as a string. - -tsnode:descendant_for_range(start_row, start_col, end_row, end_col) - *tsnode:descendant_for_range()* - Get the smallest node within this node that spans the given range of - (row, column) positions - -tsnode:named_descendant_for_range(start_row, start_col, end_row, end_col) - *tsnode:named_descendant_for_range()* - Get the smallest named node within this node that spans the given - range of (row, column) positions - ------------------------------------------------------------------------------- -VIM *lua-builtin* - -vim.api.{func}({...}) *vim.api* - Invokes Nvim |API| function {func} with arguments {...}. - Example: call the "nvim_get_current_line()" API function: > - print(tostring(vim.api.nvim_get_current_line())) - -vim.call({func}, {...}) *vim.call()* - Invokes |vim-function| or |user-function| {func} with arguments {...}. - See also |vim.fn|. Equivalent to: > - vim.fn[func]({...}) - -vim.in_fast_event() *vim.in_fast_event()* - Returns true if the code is executing as part of a "fast" event - handler, where most of the API is disabled. These are low-level events - (e.g. |lua-loop-callbacks|) which can be invoked whenever Nvim polls - for input. When this is `false` most API functions are callable (but - may be subject to other restrictions such as |textlock|). - -vim.NIL *vim.NIL* - Special value used to represent NIL in msgpack-rpc and |v:null| in - vimL interaction, and similar cases. Lua `nil` cannot be used as - part of a lua table representing a Dictionary or Array, as it - is equivalent to a missing value: `{"foo", nil}` is the same as - `{"foo"}` - -vim.rpcnotify({channel}, {method}[, {args}...]) *vim.rpcnotify()* - Sends {event} to {channel} via |RPC| and returns immediately. - If {channel} is 0, the event is broadcast to all channels. - - This function also works in a fast callback |lua-loop-callbacks|. - -vim.rpcrequest({channel}, {method}[, {args}...]) *vim.rpcrequest()* - Sends a request to {channel} to invoke {method} via - |RPC| and blocks until a response is received. - - Note: NIL values as part of the return value is represented as - |vim.NIL| special value - -vim.stricmp({a}, {b}) *vim.stricmp()* - Compares strings case-insensitively. Returns 0, 1 or -1 if strings - are equal, {a} is greater than {b} or {a} is lesser than {b}, - respectively. - -vim.str_utfindex({str}[, {index}]) *vim.str_utfindex()* - Convert byte index to UTF-32 and UTF-16 indicies. If {index} is not - supplied, the length of the string is used. All indicies are zero-based. - Returns two values: the UTF-32 and UTF-16 indicies respectively. - - Embedded NUL bytes are treated as terminating the string. Invalid - UTF-8 bytes, and embedded surrogates are counted as one code - point each. An {index} in the middle of a UTF-8 sequence is rounded - upwards to the end of that sequence. - -vim.str_byteindex({str}, {index}[, {use_utf16}]) *vim.str_byteindex()* - Convert UTF-32 or UTF-16 {index} to byte index. If {use_utf16} is not - supplied, it defaults to false (use UTF-32). Returns the byte index. - - Invalid UTF-8 and NUL is treated like by |vim.str_byteindex()|. An {index} - in the middle of a UTF-16 sequence is rounded upwards to the end of that - sequence. - -vim.schedule({callback}) *vim.schedule()* - Schedules {callback} to be invoked soon by the main event-loop. Useful - to avoid |textlock| or other temporary restrictions. - -vim.fn.{func}({...}) *vim.fn* - Invokes |vim-function| or |user-function| {func} with arguments {...}. - To call autoload functions, use the syntax: > - vim.fn['some#function']({...}) -< - Unlike vim.api.|nvim_call_function| this converts directly between Vim - objects and Lua objects. If the Vim function returns a float, it will - be represented directly as a Lua number. Empty lists and dictionaries - both are represented by an empty table. - - Note: |v:null| values as part of the return value is represented as - |vim.NIL| special value - - Note: vim.fn keys are generated lazily, thus `pairs(vim.fn)` only - enumerates functions that were called at least once. - -vim.type_idx *vim.type_idx* - Type index for use in |lua-special-tbl|. Specifying one of the - values from |vim.types| allows typing the empty table (it is - unclear whether empty Lua table represents empty list or empty array) - and forcing integral numbers to be |Float|. See |lua-special-tbl| for - more details. - -vim.val_idx *vim.val_idx* - Value index for tables representing |Float|s. A table representing - floating-point value 1.0 looks like this: > - { - [vim.type_idx] = vim.types.float, - [vim.val_idx] = 1.0, - } -< See also |vim.type_idx| and |lua-special-tbl|. - -vim.types *vim.types* - Table with possible values for |vim.type_idx|. Contains two sets - of key-value pairs: first maps possible values for |vim.type_idx| - to human-readable strings, second maps human-readable type names to - values for |vim.type_idx|. Currently contains pairs for `float`, - `array` and `dictionary` types. - - Note: one must expect that values corresponding to `vim.types.float`, - `vim.types.array` and `vim.types.dictionary` fall under only two - following assumptions: - 1. Value may serve both as a key and as a value in a table. Given the - properties of Lua tables this basically means “value is not `nil`”. - 2. For each value in `vim.types` table `vim.types[vim.types[value]]` - is the same as `value`. - No other restrictions are put on types, and it is not guaranteed that - values corresponding to `vim.types.float`, `vim.types.array` and - `vim.types.dictionary` will not change or that `vim.types` table will - only contain values for these three types. +Moved to |lua.txt| ============================================================================== -Lua module: vim *lua-vim* - -inspect({object}, {options}) *vim.inspect()* - Return a human-readable representation of the given object. - - See also: ~ - https://github.com/kikito/inspect.lua - https://github.com/mpeterv/vinspect - -paste({lines}, {phase}) *vim.paste()* - Paste handler, invoked by |nvim_paste()| when a conforming UI - (such as the |TUI|) pastes text into the editor. - - Example: To remove ANSI color codes when pasting: > - - vim.paste = (function() - local overridden = vim.paste - return function(lines, phase) - for i,line in ipairs(lines) do - -- Scrub ANSI color codes from paste input. - lines[i] = line:gsub('\27%[[0-9;mK]+', '') - end - overridden(lines, phase) - end - end)() -< - - Parameters: ~ - {lines} |readfile()|-style list of lines to paste. - |channel-lines| - {phase} -1: "non-streaming" paste: the call contains all - lines. If paste is "streamed", `phase` indicates the stream state: - • 1: starts the paste (exactly once) - • 2: continues the paste (zero or more times) - • 3: ends the paste (exactly once) - - Return: ~ - false if client should cancel the paste. - - See also: ~ - |paste| - -schedule_wrap({cb}) *vim.schedule_wrap()* - Defers callback `cb` until the Nvim API is safe to call. - - See also: ~ - |lua-loop-callbacks| - |vim.schedule()| - |vim.in_fast_event()| - - - - -deepcopy({orig}) *vim.deepcopy()* - Returns a deep copy of the given object. Non-table objects are - copied as in a typical Lua assignment, whereas table objects - are copied recursively. - - Parameters: ~ - {orig} Table to copy - - Return: ~ - New table of copied keys and (nested) values. - -gsplit({s}, {sep}, {plain}) *vim.gsplit()* - Splits a string at each instance of a separator. - - Parameters: ~ - {s} String to split - {sep} Separator string or pattern - {plain} If `true` use `sep` literally (passed to - String.find) - - Return: ~ - Iterator over the split components - - See also: ~ - |vim.split()| - https://www.lua.org/pil/20.2.html - http://lua-users.org/wiki/StringLibraryTutorial - -split({s}, {sep}, {plain}) *vim.split()* - Splits a string at each instance of a separator. - - Examples: > - split(":aa::b:", ":") --> {'','aa','','bb',''} - split("axaby", "ab?") --> {'','x','y'} - split(x*yz*o, "*", true) --> {'x','yz','o'} -< - - Parameters: ~ - {s} String to split - {sep} Separator string or pattern - {plain} If `true` use `sep` literally (passed to - String.find) - - Return: ~ - List-like table of the split components. - - See also: ~ - |vim.gsplit()| - -tbl_contains({t}, {value}) *vim.tbl_contains()* - Checks if a list-like (vector) table contains `value` . - - Parameters: ~ - {t} Table to check - {value} Value to compare - - Return: ~ - true if `t` contains `value` - -tbl_extend({behavior}, {...}) *vim.tbl_extend()* - Merges two or more map-like tables. - - Parameters: ~ - {behavior} Decides what to do if a key is found in more - than one map: - • "error": raise an error - • "keep": use value from the leftmost map - • "force": use value from the rightmost map - {...} Two or more map-like tables. - - See also: ~ - |extend()| - -tbl_flatten({t}) *vim.tbl_flatten()* - Creates a copy of a list-like table such that any nested - tables are "unrolled" and appended to the result. - - Parameters: ~ - {t} List-like table - - Return: ~ - Flattened copy of the given list-like table. - -trim({s}) *vim.trim()* - Trim whitespace (Lua pattern "%s") from both sides of a - string. - - Parameters: ~ - {s} String to trim - - Return: ~ - String with whitespace removed from its beginning and end - - See also: ~ - https://www.lua.org/pil/20.2.html - -pesc({s}) *vim.pesc()* - Escapes magic chars in a Lua pattern string. - - Parameters: ~ - {s} String to escape - - Return: ~ - %-escaped pattern string - - See also: ~ - https://github.com/rxi/lume - -validate({opt}) *vim.validate()* - Validates a parameter specification (types and values). - - Usage example: > - - function user.new(name, age, hobbies) - vim.validate{ - name={name, 'string'}, - age={age, 'number'}, - hobbies={hobbies, 'table'}, - } - ... - end -< - - Examples with explicit argument values (can be run directly): > - - vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}} - => NOP (success) -< -> - vim.validate{arg1={1, 'table'}} - => error('arg1: expected table, got number') -< -> - vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}} - => error('arg1: expected even number, got 3') -< - - Parameters: ~ - {opt} Map of parameter names to validations. Each key is - a parameter name; each value is a tuple in one of - these forms: - 1. (arg_value, type_name, optional) - • arg_value: argument value - • type_name: string type name, one of: ("table", - "t", "string", "s", "number", "n", "boolean", - "b", "function", "f", "nil", "thread", - "userdata") - • optional: (optional) boolean, if true, `nil` - is valid - - 2. (arg_value, fn, msg) - • arg_value: argument value - • fn: any function accepting one argument, - returns true if and only if the argument is - valid - • msg: (optional) error string if validation - fails - -is_callable({f}) *vim.is_callable()* - Returns true if object `f` can be called as a function. - - Parameters: ~ - {f} Any object - - Return: ~ - true if `f` is callable, else false - - vim:tw=78:ts=8:ft=help:norl: + vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt new file mode 100644 index 0000000000..edcf246295 --- /dev/null +++ b/runtime/doc/lua.txt @@ -0,0 +1,994 @@ +*lua.txt* Nvim + + + NVIM REFERENCE MANUAL + + +Lua engine *lua* *Lua* + + Type |gO| to see the table of contents. + +============================================================================== +Introduction *lua-intro* + +The Lua 5.1 language is builtin and always available. Try this command to get +an idea of what lurks beneath: > + + :lua print(vim.inspect(package.loaded)) + +Nvim includes a "standard library" |lua-stdlib| for Lua. It complements the +"editor stdlib" (|functions| and Ex commands) and the |API|, all of which can +be used from Lua code. + +Module conflicts are resolved by "last wins". For example if both of these +are on 'runtimepath': + runtime/lua/foo.lua + ~/.config/nvim/lua/foo.lua +then `require('foo')` loads "~/.config/nvim/lua/foo.lua", and +"runtime/lua/foo.lua" is not used. See |lua-require| to understand how Nvim +finds and loads Lua modules. The conventions are similar to VimL plugins, +with some extra features. See |lua-require-example| for a walkthrough. + +============================================================================== +Importing Lua modules *lua-require* + + *lua-package-path* +Nvim automatically adjusts `package.path` and `package.cpath` according to +effective 'runtimepath' value. Adjustment happens whenever 'runtimepath' is +changed. `package.path` is adjusted by simply appending `/lua/?.lua` and +`/lua/?/init.lua` to each directory from 'runtimepath' (`/` is actually the +first character of `package.config`). + +Similarly to `package.path`, modified directories from 'runtimepath' are also +added to `package.cpath`. In this case, instead of appending `/lua/?.lua` and +`/lua/?/init.lua` to each runtimepath, all unique `?`-containing suffixes of +the existing `package.cpath` are used. Example: + +1. Given that + - 'runtimepath' contains `/foo/bar,/xxx;yyy/baz,/abc`; + - initial (defined at compile-time or derived from + `$LUA_CPATH`/`$LUA_INIT`) `package.cpath` contains + `./?.so;/def/ghi/a?d/j/g.elf;/def/?.so`. +2. It finds `?`-containing suffixes `/?.so`, `/a?d/j/g.elf` and `/?.so`, in + order: parts of the path starting from the first path component containing + question mark and preceding path separator. +3. The suffix of `/def/?.so`, namely `/?.so` is not unique, as it’s the same + as the suffix of the first path from `package.path` (i.e. `./?.so`). Which + leaves `/?.so` and `/a?d/j/g.elf`, in this order. +4. 'runtimepath' has three paths: `/foo/bar`, `/xxx;yyy/baz` and `/abc`. The + second one contains semicolon which is a paths separator so it is out, + leaving only `/foo/bar` and `/abc`, in order. +5. The cartesian product of paths from 4. and suffixes from 3. is taken, + giving four variants. In each variant `/lua` path segment is inserted + between path and suffix, leaving + + - `/foo/bar/lua/?.so` + - `/foo/bar/lua/a?d/j/g.elf` + - `/abc/lua/?.so` + - `/abc/lua/a?d/j/g.elf` + +6. New paths are prepended to the original `package.cpath`. + +The result will look like this: + + `/foo/bar,/xxx;yyy/baz,/abc` ('runtimepath') + × `./?.so;/def/ghi/a?d/j/g.elf;/def/?.so` (`package.cpath`) + + = `/foo/bar/lua/?.so;/foo/bar/lua/a?d/j/g.elf;/abc/lua/?.so;/abc/lua/a?d/j/g.elf;./?.so;/def/ghi/a?d/j/g.elf;/def/?.so` + +Note: + +- To track 'runtimepath' updates, paths added at previous update are + remembered and removed at the next update, while all paths derived from the + new 'runtimepath' are prepended as described above. This allows removing + paths when path is removed from 'runtimepath', adding paths when they are + added and reordering `package.path`/`package.cpath` content if 'runtimepath' + was reordered. + +- Although adjustments happen automatically, Nvim does not track current + values of `package.path` or `package.cpath`. If you happen to delete some + paths from there you can set 'runtimepath' to trigger an update: > + let &runtimepath = &runtimepath + +- Skipping paths from 'runtimepath' which contain semicolons applies both to + `package.path` and `package.cpath`. Given that there are some badly written + plugins using shell which will not work with paths containing semicolons it + is better to not have them in 'runtimepath' at all. + +------------------------------------------------------------------------------ +LUA PLUGIN EXAMPLE *lua-require-example* + +The following example plugin adds a command `:MakeCharBlob` which transforms +current buffer into a long `unsigned char` array. Lua contains transformation +function in a module `lua/charblob.lua` which is imported in +`autoload/charblob.vim` (`require("charblob")`). Example plugin is supposed +to be put into any directory from 'runtimepath', e.g. `~/.config/nvim` (in +this case `lua/charblob.lua` means `~/.config/nvim/lua/charblob.lua`). + +autoload/charblob.vim: > + + function charblob#encode_buffer() + call setline(1, luaeval( + \ 'require("charblob").encode(unpack(_A))', + \ [getline(1, '$'), &textwidth, ' '])) + endfunction + +plugin/charblob.vim: > + + if exists('g:charblob_loaded') + finish + endif + let g:charblob_loaded = 1 + + command MakeCharBlob :call charblob#encode_buffer() + +lua/charblob.lua: > + + local function charblob_bytes_iter(lines) + local init_s = { + next_line_idx = 1, + next_byte_idx = 1, + lines = lines, + } + local function next(s, _) + if lines[s.next_line_idx] == nil then + return nil + end + if s.next_byte_idx > #(lines[s.next_line_idx]) then + s.next_line_idx = s.next_line_idx + 1 + s.next_byte_idx = 1 + return ('\n'):byte() + end + local ret = lines[s.next_line_idx]:byte(s.next_byte_idx) + if ret == ('\n'):byte() then + ret = 0 -- See :h NL-used-for-NUL. + end + s.next_byte_idx = s.next_byte_idx + 1 + return ret + end + return next, init_s, nil + end + + local function charblob_encode(lines, textwidth, indent) + local ret = { + 'const unsigned char blob[] = {', + indent, + } + for byte in charblob_bytes_iter(lines) do + -- .- space + number (width 3) + comma + if #(ret[#ret]) + 5 > textwidth then + ret[#ret + 1] = indent + else + ret[#ret] = ret[#ret] .. ' ' + end + ret[#ret] = ret[#ret] .. (('%3u,'):format(byte)) + end + ret[#ret + 1] = '};' + return ret + end + + return { + bytes_iter = charblob_bytes_iter, + encode = charblob_encode, + } + +============================================================================== +Commands *lua-commands* + +These commands execute a Lua chunk from either the command line (:lua, :luado) +or a file (:luafile) on the given line [range]. As always in Lua, each chunk +has its own scope (closure), so only global variables are shared between +command calls. The |lua-stdlib| modules, user modules, and anything else on +|lua-package-path| are available. + +The Lua print() function redirects its output to the Nvim message area, with +arguments separated by " " (space) instead of "\t" (tab). + + *:lua* +:[range]lua {chunk} + Executes Lua chunk {chunk}. + + Examples: > + :lua vim.api.nvim_command('echo "Hello, Nvim!"') +< To see the Lua version: > + :lua print(_VERSION) +< To see the LuaJIT version: > + :lua print(jit.version) +< + *:lua-heredoc* +:[range]lua << [endmarker] +{script} +{endmarker} + Executes Lua script {script} from within Vimscript. + {endmarker} must NOT be preceded by whitespace. You + can omit [endmarker] after the "<<" and use a dot "." + after {script} (similar to |:append|, |:insert|). + + Example: + > + function! CurrentLineInfo() + lua << EOF + local linenr = vim.api.nvim_win_get_cursor(0)[1] + local curline = vim.api.nvim_buf_get_lines( + 0, linenr, linenr + 1, false)[1] + print(string.format("Current line [%d] has %d bytes", + linenr, #curline)) + EOF + endfunction + +< Note that the `local` variables will disappear when + the block finishes. But not globals. + + *:luado* +:[range]luado {body} Executes Lua chunk "function(line, linenr) {body} end" + for each buffer line in [range], where `line` is the + current line text (without <EOL>), and `linenr` is the + current line number. If the function returns a string + that becomes the text of the corresponding buffer + line. Default [range] is the whole file: "1,$". + + Examples: + > + :luado return string.format("%s\t%d", line:reverse(), #line) + + :lua require"lpeg" + :lua -- balanced parenthesis grammar: + :lua bp = lpeg.P{ "(" * ((1 - lpeg.S"()") + lpeg.V(1))^0 * ")" } + :luado if bp:match(line) then return "-->\t" .. line end +< + + *:luafile* +:[range]luafile {file} + Execute Lua script in {file}. + The whole argument is used as a single file name. + + Examples: + > + :luafile script.lua + :luafile % +< + +============================================================================== +luaeval() *lua-eval* *luaeval()* + +The (dual) equivalent of "vim.eval" for passing Lua values to Nvim is +"luaeval". "luaeval" takes an expression string and an optional argument used +for _A inside expression and returns the result of the expression. It is +semantically equivalent in Lua to: +> + local chunkheader = "local _A = select(1, ...) return " + function luaeval (expstr, arg) + local chunk = assert(loadstring(chunkheader .. expstr, "luaeval")) + return chunk(arg) -- return typval + end + +Lua nils, numbers, strings, tables and booleans are converted to their +respective VimL types. An error is thrown if conversion of any other Lua types +is attempted. + +The magic global "_A" contains the second argument to luaeval(). + +Example: > + :echo luaeval('_A[1] + _A[2]', [40, 2]) + 42 + :echo luaeval('string.match(_A, "[a-z]+")', 'XYXfoo123') + foo + +Lua tables are used as both dictionaries and lists, so it is impossible to +determine whether empty table is meant to be empty list or empty dictionary. +Additionally Lua does not have integer numbers. To distinguish between these +cases there is the following agreement: + +0. Empty table is empty list. +1. Table with N incrementally growing integral numbers, starting from 1 and + ending with N is considered to be a list. +2. Table with string keys, none of which contains NUL byte, is considered to + be a dictionary. +3. Table with string keys, at least one of which contains NUL byte, is also + considered to be a dictionary, but this time it is converted to + a |msgpack-special-map|. + *lua-special-tbl* +4. Table with `vim.type_idx` key may be a dictionary, a list or floating-point + value: + - `{[vim.type_idx]=vim.types.float, [vim.val_idx]=1}` is converted to + a floating-point 1.0. Note that by default integral Lua numbers are + converted to |Number|s, non-integral are converted to |Float|s. This + variant allows integral |Float|s. + - `{[vim.type_idx]=vim.types.dictionary}` is converted to an empty + dictionary, `{[vim.type_idx]=vim.types.dictionary, [42]=1, a=2}` is + converted to a dictionary `{'a': 42}`: non-string keys are ignored. + Without `vim.type_idx` key tables with keys not fitting in 1., 2. or 3. + are errors. + - `{[vim.type_idx]=vim.types.list}` is converted to an empty list. As well + as `{[vim.type_idx]=vim.types.list, [42]=1}`: integral keys that do not + form a 1-step sequence from 1 to N are ignored, as well as all + non-integral keys. + +Examples: > + + :echo luaeval('math.pi') + :function Rand(x,y) " random uniform between x and y + : return luaeval('(_A.y-_A.x)*math.random()+_A.x', {'x':a:x,'y':a:y}) + : endfunction + :echo Rand(1,10) + +Note: second argument to `luaeval` undergoes VimL to Lua conversion +("marshalled"), so changes to Lua containers do not affect values in VimL. +Return value is also always converted. When converting, +|msgpack-special-dict|s are treated specially. + +============================================================================== +Vimscript v:lua interface *v:lua-call* + +From Vimscript the special `v:lua` prefix can be used to call Lua functions +which are global or accessible from global tables. The expression > + v:lua.func(arg1, arg2) +is equivalent to the Lua chunk > + return func(...) +where the args are converted to Lua values. The expression > + v:lua.somemod.func(args) +is equivalent to the Lua chunk > + return somemod.func(...) + +You can use `v:lua` in "func" options like 'tagfunc', 'omnifunc', etc. +For example consider the following Lua omnifunc handler: > + + function mymod.omnifunc(findstart, base) + if findstart == 1 then + return 0 + else + return {'stuff', 'steam', 'strange things'} + end + end + vim.api.nvim_buf_set_option(0, 'omnifunc', 'v:lua.mymod.omnifunc') + +Note: the module ("mymod" in the above example) must be a Lua global. + +Note: `v:lua` without a call is not allowed in a Vimscript expression: +|Funcref|s cannot represent Lua functions. The following are errors: > + + let g:Myvar = v:lua.myfunc " Error + call SomeFunc(v:lua.mycallback) " Error + let g:foo = v:lua " Error + let g:foo = v:['lua'] " Error + + +============================================================================== +Lua standard modules *lua-stdlib* + +The Nvim Lua "standard library" (stdlib) is the `vim` module, which exposes +various functions and sub-modules. It is always loaded, thus require("vim") +is unnecessary. + +You can peek at the module properties: > + + :lua print(vim.inspect(vim)) + +Result is something like this: > + + { + _os_proc_children = <function 1>, + _os_proc_info = <function 2>, + ... + api = { + nvim__id = <function 5>, + nvim__id_array = <function 6>, + ... + }, + deepcopy = <function 106>, + gsplit = <function 107>, + ... + } + +To find documentation on e.g. the "deepcopy" function: > + + :help vim.deepcopy() + +Note that underscore-prefixed functions (e.g. "_os_proc_children") are +internal/private and must not be used by plugins. + +------------------------------------------------------------------------------ +VIM.LOOP *lua-loop* *vim.loop* + +`vim.loop` exposes all features of the Nvim event-loop. This is a low-level +API that provides functionality for networking, filesystem, and process +management. Try this command to see available functions: > + + :lua print(vim.inspect(vim.loop)) + +Reference: http://docs.libuv.org +Examples: https://github.com/luvit/luv/tree/master/examples + + *E5560* *lua-loop-callbacks* +It is an error to directly invoke `vim.api` functions (except |api-fast|) in +`vim.loop` callbacks. For example, this is an error: > + + local timer = vim.loop.new_timer() + timer:start(1000, 0, function() + vim.api.nvim_command('echomsg "test"') + end) + +To avoid the error use |vim.schedule_wrap()| to defer the callback: > + + local timer = vim.loop.new_timer() + timer:start(1000, 0, vim.schedule_wrap(function() + vim.api.nvim_command('echomsg "test"') + end)) + +Example: repeating timer + 1. Save this code to a file. + 2. Execute it with ":luafile %". > + + -- Create a timer handle (implementation detail: uv_timer_t). + local timer = vim.loop.new_timer() + local i = 0 + -- Waits 1000ms, then repeats every 750ms until timer:close(). + timer:start(1000, 750, function() + print('timer invoked! i='..tostring(i)) + if i > 4 then + timer:close() -- Always close handles to avoid leaks. + end + i = i + 1 + end) + print('sleeping'); + + +Example: File-change detection *watch-file* + 1. Save this code to a file. + 2. Execute it with ":luafile %". + 3. Use ":Watch %" to watch any file. + 4. Try editing the file from another text editor. + 5. Observe that the file reloads in Nvim (because on_change() calls + |:checktime|). > + + local w = vim.loop.new_fs_event() + local function on_change(err, fname, status) + -- Do work... + vim.api.nvim_command('checktime') + -- Debounce: stop/start. + w:stop() + watch_file(fname) + end + function watch_file(fname) + local fullpath = vim.api.nvim_call_function( + 'fnamemodify', {fname, ':p'}) + w:start(fullpath, {}, vim.schedule_wrap(function(...) + on_change(...) end)) + end + vim.api.nvim_command( + "command! -nargs=1 Watch call luaeval('watch_file(_A)', expand('<args>'))") + + +Example: TCP echo-server *tcp-server* + 1. Save this code to a file. + 2. Execute it with ":luafile %". + 3. Note the port number. + 4. Connect from any TCP client (e.g. "nc 0.0.0.0 36795"): > + + local function create_server(host, port, on_connect) + local server = vim.loop.new_tcp() + server:bind(host, port) + server:listen(128, function(err) + assert(not err, err) -- Check for errors. + local sock = vim.loop.new_tcp() + server:accept(sock) -- Accept client connection. + on_connect(sock) -- Start reading messages. + end) + return server + end + local server = create_server('0.0.0.0', 0, function(sock) + sock:read_start(function(err, chunk) + assert(not err, err) -- Check for errors. + if chunk then + sock:write(chunk) -- Echo received messages to the channel. + else -- EOF (stream closed). + sock:close() -- Always close handles to avoid leaks. + end + end) + end) + print('TCP echo-server listening on port: '..server:getsockname().port) + +------------------------------------------------------------------------------ +VIM.TREESITTER *lua-treesitter* + +Nvim integrates the tree-sitter library for incremental parsing of buffers. + +Currently Nvim does not provide the tree-sitter parsers, instead these must +be built separately, for instance using the tree-sitter utility. +The parser is loaded into nvim using > + + vim.treesitter.add_language("/path/to/c_parser.so", "c") + +<Create a parser for a buffer and a given language (if another plugin uses the +same buffer/language combination, it will be safely reused). Use > + + parser = vim.treesitter.get_parser(bufnr, lang) + +<`bufnr=0` can be used for current buffer. `lang` will default to 'filetype' (this +doesn't work yet for some filetypes like "cpp") Currently, the parser will be +retained for the lifetime of a buffer but this is subject to change. A plugin +should keep a reference to the parser object as long as it wants incremental +updates. + +Whenever you need to access the current syntax tree, parse the buffer: > + + tstree = parser:parse() + +<This will return an immutable tree that represents the current state of the +buffer. When the plugin wants to access the state after a (possible) edit +it should call `parse()` again. If the buffer wasn't edited, the same tree will +be returned again without extra work. If the buffer was parsed before, +incremental parsing will be done of the changed parts. + +NB: to use the parser directly inside a |nvim_buf_attach| Lua callback, you must +call `get_parser()` before you register your callback. But preferably parsing +shouldn't be done directly in the change callback anyway as they will be very +frequent. Rather a plugin that does any kind of analysis on a tree should use +a timer to throttle too frequent updates. + +Tree methods *lua-treesitter-tree* + +tstree:root() *tstree:root()* + Return the root node of this tree. + + +Node methods *lua-treesitter-node* + +tsnode:parent() *tsnode:parent()* + Get the node's immediate parent. + +tsnode:child_count() *tsnode:child_count()* + Get the node's number of children. + +tsnode:child(N) *tsnode:child()* + Get the node's child at the given index, where zero represents the + first child. + +tsnode:named_child_count() *tsnode:named_child_count()* + Get the node's number of named children. + +tsnode:named_child(N) *tsnode:named_child()* + Get the node's named child at the given index, where zero represents + the first named child. + +tsnode:start() *tsnode:start()* + Get the node's start position. Return three values: the row, column + and total byte count (all zero-based). + +tsnode:end_() *tsnode:end_()* + Get the node's end position. Return three values: the row, column + and total byte count (all zero-based). + +tsnode:range() *tsnode:range()* + Get the range of the node. Return four values: the row, column + of the start position, then the row, column of the end position. + +tsnode:type() *tsnode:type()* + Get the node's type as a string. + +tsnode:symbol() *tsnode:symbol()* + Get the node's type as a numerical id. + +tsnode:named() *tsnode:named()* + Check if the node is named. Named nodes correspond to named rules in + the grammar, whereas anonymous nodes correspond to string literals + in the grammar. + +tsnode:missing() *tsnode:missing()* + Check if the node is missing. Missing nodes are inserted by the + parser in order to recover from certain kinds of syntax errors. + +tsnode:has_error() *tsnode:has_error()* + Check if the node is a syntax error or contains any syntax errors. + +tsnode:sexpr() *tsnode:sexpr()* + Get an S-expression representing the node as a string. + +tsnode:descendant_for_range(start_row, start_col, end_row, end_col) + *tsnode:descendant_for_range()* + Get the smallest node within this node that spans the given range of + (row, column) positions + +tsnode:named_descendant_for_range(start_row, start_col, end_row, end_col) + *tsnode:named_descendant_for_range()* + Get the smallest named node within this node that spans the given + range of (row, column) positions + +------------------------------------------------------------------------------ +VIM *lua-builtin* + +vim.api.{func}({...}) *vim.api* + Invokes Nvim |API| function {func} with arguments {...}. + Example: call the "nvim_get_current_line()" API function: > + print(tostring(vim.api.nvim_get_current_line())) + +vim.call({func}, {...}) *vim.call()* + Invokes |vim-function| or |user-function| {func} with arguments {...}. + See also |vim.fn|. Equivalent to: > + vim.fn[func]({...}) + +vim.in_fast_event() *vim.in_fast_event()* + Returns true if the code is executing as part of a "fast" event + handler, where most of the API is disabled. These are low-level events + (e.g. |lua-loop-callbacks|) which can be invoked whenever Nvim polls + for input. When this is `false` most API functions are callable (but + may be subject to other restrictions such as |textlock|). + +vim.NIL *vim.NIL* + Special value used to represent NIL in msgpack-rpc and |v:null| in + vimL interaction, and similar cases. Lua `nil` cannot be used as + part of a lua table representing a Dictionary or Array, as it + is equivalent to a missing value: `{"foo", nil}` is the same as + `{"foo"}` + +vim.rpcnotify({channel}, {method}[, {args}...]) *vim.rpcnotify()* + Sends {event} to {channel} via |RPC| and returns immediately. + If {channel} is 0, the event is broadcast to all channels. + + This function also works in a fast callback |lua-loop-callbacks|. + +vim.rpcrequest({channel}, {method}[, {args}...]) *vim.rpcrequest()* + Sends a request to {channel} to invoke {method} via + |RPC| and blocks until a response is received. + + Note: NIL values as part of the return value is represented as + |vim.NIL| special value + +vim.stricmp({a}, {b}) *vim.stricmp()* + Compares strings case-insensitively. Returns 0, 1 or -1 if strings + are equal, {a} is greater than {b} or {a} is lesser than {b}, + respectively. + +vim.str_utfindex({str}[, {index}]) *vim.str_utfindex()* + Convert byte index to UTF-32 and UTF-16 indicies. If {index} is not + supplied, the length of the string is used. All indicies are zero-based. + Returns two values: the UTF-32 and UTF-16 indicies respectively. + + Embedded NUL bytes are treated as terminating the string. Invalid + UTF-8 bytes, and embedded surrogates are counted as one code + point each. An {index} in the middle of a UTF-8 sequence is rounded + upwards to the end of that sequence. + +vim.str_byteindex({str}, {index}[, {use_utf16}]) *vim.str_byteindex()* + Convert UTF-32 or UTF-16 {index} to byte index. If {use_utf16} is not + supplied, it defaults to false (use UTF-32). Returns the byte index. + + Invalid UTF-8 and NUL is treated like by |vim.str_byteindex()|. An {index} + in the middle of a UTF-16 sequence is rounded upwards to the end of that + sequence. + +vim.schedule({callback}) *vim.schedule()* + Schedules {callback} to be invoked soon by the main event-loop. Useful + to avoid |textlock| or other temporary restrictions. + +vim.fn.{func}({...}) *vim.fn* + Invokes |vim-function| or |user-function| {func} with arguments {...}. + To call autoload functions, use the syntax: > + vim.fn['some#function']({...}) +< + Unlike vim.api.|nvim_call_function| this converts directly between Vim + objects and Lua objects. If the Vim function returns a float, it will + be represented directly as a Lua number. Empty lists and dictionaries + both are represented by an empty table. + + Note: |v:null| values as part of the return value is represented as + |vim.NIL| special value + + Note: vim.fn keys are generated lazily, thus `pairs(vim.fn)` only + enumerates functions that were called at least once. + +vim.type_idx *vim.type_idx* + Type index for use in |lua-special-tbl|. Specifying one of the + values from |vim.types| allows typing the empty table (it is + unclear whether empty Lua table represents empty list or empty array) + and forcing integral numbers to be |Float|. See |lua-special-tbl| for + more details. + +vim.val_idx *vim.val_idx* + Value index for tables representing |Float|s. A table representing + floating-point value 1.0 looks like this: > + { + [vim.type_idx] = vim.types.float, + [vim.val_idx] = 1.0, + } +< See also |vim.type_idx| and |lua-special-tbl|. + +vim.types *vim.types* + Table with possible values for |vim.type_idx|. Contains two sets + of key-value pairs: first maps possible values for |vim.type_idx| + to human-readable strings, second maps human-readable type names to + values for |vim.type_idx|. Currently contains pairs for `float`, + `array` and `dictionary` types. + + Note: one must expect that values corresponding to `vim.types.float`, + `vim.types.array` and `vim.types.dictionary` fall under only two + following assumptions: + 1. Value may serve both as a key and as a value in a table. Given the + properties of Lua tables this basically means “value is not `nil`”. + 2. For each value in `vim.types` table `vim.types[vim.types[value]]` + is the same as `value`. + No other restrictions are put on types, and it is not guaranteed that + values corresponding to `vim.types.float`, `vim.types.array` and + `vim.types.dictionary` will not change or that `vim.types` table will + only contain values for these three types. + +============================================================================== +Lua module: vim *lua-vim* + +inspect({object}, {options}) *vim.inspect()* + Return a human-readable representation of the given object. + + See also: ~ + https://github.com/kikito/inspect.lua + https://github.com/mpeterv/vinspect + +paste({lines}, {phase}) *vim.paste()* + Paste handler, invoked by |nvim_paste()| when a conforming UI + (such as the |TUI|) pastes text into the editor. + + Example: To remove ANSI color codes when pasting: > + + vim.paste = (function(overridden) + return function(lines, phase) + for i,line in ipairs(lines) do + -- Scrub ANSI color codes from paste input. + lines[i] = line:gsub('\27%[[0-9;mK]+', '') + end + overridden(lines, phase) + end + end)(vim.paste) +< + + Parameters: ~ + {lines} |readfile()|-style list of lines to paste. + |channel-lines| + {phase} -1: "non-streaming" paste: the call contains all + lines. If paste is "streamed", `phase` indicates the stream state: + • 1: starts the paste (exactly once) + • 2: continues the paste (zero or more times) + • 3: ends the paste (exactly once) + + Return: ~ + false if client should cancel the paste. + + See also: ~ + |paste| + +schedule_wrap({cb}) *vim.schedule_wrap()* + Defers callback `cb` until the Nvim API is safe to call. + + See also: ~ + |lua-loop-callbacks| + |vim.schedule()| + |vim.in_fast_event()| + + + + +deepcopy({orig}) *vim.deepcopy()* + Returns a deep copy of the given object. Non-table objects are + copied as in a typical Lua assignment, whereas table objects + are copied recursively. + + Parameters: ~ + {orig} Table to copy + + Return: ~ + New table of copied keys and (nested) values. + +gsplit({s}, {sep}, {plain}) *vim.gsplit()* + Splits a string at each instance of a separator. + + Parameters: ~ + {s} String to split + {sep} Separator string or pattern + {plain} If `true` use `sep` literally (passed to + String.find) + + Return: ~ + Iterator over the split components + + See also: ~ + |vim.split()| + https://www.lua.org/pil/20.2.html + http://lua-users.org/wiki/StringLibraryTutorial + +split({s}, {sep}, {plain}) *vim.split()* + Splits a string at each instance of a separator. + + Examples: > + split(":aa::b:", ":") --> {'','aa','','bb',''} + split("axaby", "ab?") --> {'','x','y'} + split(x*yz*o, "*", true) --> {'x','yz','o'} +< + + Parameters: ~ + {s} String to split + {sep} Separator string or pattern + {plain} If `true` use `sep` literally (passed to + String.find) + + Return: ~ + List-like table of the split components. + + See also: ~ + |vim.gsplit()| + +tbl_keys({t}) *vim.tbl_keys()* + Return a list of all keys used in a table. However, the order + of the return table of keys is not guaranteed. + + Parameters: ~ + {t} Table + + Return: ~ + list of keys + + See also: ~ + Fromhttps://github.com/premake/premake-core/blob/master/src/base/table.lua + +tbl_values({t}) *vim.tbl_values()* + Return a list of all values used in a table. However, the + order of the return table of values is not guaranteed. + + Parameters: ~ + {t} Table + + Return: ~ + list of values + +tbl_contains({t}, {value}) *vim.tbl_contains()* + Checks if a list-like (vector) table contains `value` . + + Parameters: ~ + {t} Table to check + {value} Value to compare + + Return: ~ + true if `t` contains `value` + +tbl_isempty({t}) *vim.tbl_isempty()* + See also: ~ + Fromhttps://github.com/premake/premake-core/blob/master/src/base/table.lua@paramt Table to check + +tbl_extend({behavior}, {...}) *vim.tbl_extend()* + Merges two or more map-like tables. + + Parameters: ~ + {behavior} Decides what to do if a key is found in more + than one map: + • "error": raise an error + • "keep": use value from the leftmost map + • "force": use value from the rightmost map + {...} Two or more map-like tables. + + See also: ~ + |extend()| + +deep_equal({a}, {b}) *vim.deep_equal()* + TODO: Documentation + +tbl_add_reverse_lookup({o}) *vim.tbl_add_reverse_lookup()* + Add the reverse lookup values to an existing table. For + example: `tbl_add_reverse_lookup { A = 1 } == { [1] = 'A', A = + 1 }` + + Parameters: ~ + {o} table The table to add the reverse to. + +list_extend({dst}, {src}) *vim.list_extend()* + Extends a list-like table with the values of another list-like + table. + + Parameters: ~ + {dst} The list which will be modified and appended to. + {src} The list from which values will be inserted. + + See also: ~ + |extend()| + +tbl_flatten({t}) *vim.tbl_flatten()* + Creates a copy of a list-like table such that any nested + tables are "unrolled" and appended to the result. + + Parameters: ~ + {t} List-like table + + Return: ~ + Flattened copy of the given list-like table. + + See also: ~ + Fromhttps://github.com/premake/premake-core/blob/master/src/base/table.lua + +tbl_islist({t}) *vim.tbl_islist()* + Table + + Return: ~ + true: A non-empty array, false: A non-empty table, nil: An + empty table + +trim({s}) *vim.trim()* + Trim whitespace (Lua pattern "%s") from both sides of a + string. + + Parameters: ~ + {s} String to trim + + Return: ~ + String with whitespace removed from its beginning and end + + See also: ~ + https://www.lua.org/pil/20.2.html + +pesc({s}) *vim.pesc()* + Escapes magic chars in a Lua pattern string. + + Parameters: ~ + {s} String to escape + + Return: ~ + %-escaped pattern string + + See also: ~ + https://github.com/rxi/lume + +validate({opt}) *vim.validate()* + Validates a parameter specification (types and values). + + Usage example: > + + function user.new(name, age, hobbies) + vim.validate{ + name={name, 'string'}, + age={age, 'number'}, + hobbies={hobbies, 'table'}, + } + ... + end +< + + Examples with explicit argument values (can be run directly): > + + vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}} + => NOP (success) +< +> + vim.validate{arg1={1, 'table'}} + => error('arg1: expected table, got number') +< +> + vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}} + => error('arg1: expected even number, got 3') +< + + Parameters: ~ + {opt} Map of parameter names to validations. Each key is + a parameter name; each value is a tuple in one of + these forms: + 1. (arg_value, type_name, optional) + • arg_value: argument value + • type_name: string type name, one of: ("table", + "t", "string", "s", "number", "n", "boolean", + "b", "function", "f", "nil", "thread", + "userdata") + • optional: (optional) boolean, if true, `nil` + is valid + + 2. (arg_value, fn, msg) + • arg_value: argument value + • fn: any function accepting one argument, + returns true if and only if the argument is + valid + • msg: (optional) error string if validation + fails + +is_callable({f}) *vim.is_callable()* + Returns true if object `f` can be called as a function. + + Parameters: ~ + {f} Any object + + Return: ~ + true if `f` is callable, else false + + vim:tw=78:ts=8:ft=help:norl: diff --git a/runtime/doc/msgpack_rpc.txt b/runtime/doc/msgpack_rpc.txt index f5d42dfeb2..5368cf0f4f 100644 --- a/runtime/doc/msgpack_rpc.txt +++ b/runtime/doc/msgpack_rpc.txt @@ -1,7 +1,8 @@ - NVIM REFERENCE MANUAL by Thiago de Arruda - - + NVIM REFERENCE MANUAL This document was merged into |api.txt| and |develop.txt|. + +============================================================================== + vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index e64ff1b12c..386fcdf8c0 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -843,6 +843,14 @@ A jump table for the options with a short description can be found at |Q_op|. name, precede it with a backslash. - To include a comma in a directory name precede it with a backslash. - A directory name may end in an '/'. + - For Unix and Win32, if a directory ends in two path separators "//", + the swap file name will be built from the complete path to the file + with all path separators changed to percent '%' signs. This will + ensure file name uniqueness in the backup directory. + On Win32, it is also possible to end with "\\". However, When a + separating comma is following, you must use "//", since "\\" will + include the comma in the file name. Therefore it is recommended to + use '//', instead of '\\'. - Environment variables are expanded |:set_env|. - Careful with '\' characters, type one before a space, type two to get one in the option (see |option-backslash|), for example: > @@ -1992,12 +2000,14 @@ A jump table for the options with a short description can be found at |Q_op|. - A directory starting with "./" (or ".\" for Windows) means to put the swap file relative to where the edited file is. The leading "." is replaced with the path name of the edited file. - - For Unix and Win32, if a directory ends in two path separators "//" - or "\\", the swap file name will be built from the complete path to - the file with all path separators substituted to percent '%' signs. - This will ensure file name uniqueness in the preserve directory. - On Win32, when a separating comma is following, you must use "//", - since "\\" will include the comma in the file name. + - For Unix and Win32, if a directory ends in two path separators "//", + the swap file name will be built from the complete path to the file + with all path separators substituted to percent '%' signs. This will + ensure file name uniqueness in the preserve directory. + On Win32, it is also possible to end with "\\". However, When a + separating comma is following, you must use "//", since "\\" will + include the comma in the file name. Therefore it is recommended to + use '//', instead of '\\'. - Spaces after the comma are ignored, other spaces are considered part of the directory name. To have a space at the start of a directory name, precede it with a backslash. @@ -6642,22 +6652,18 @@ A jump table for the options with a short description can be found at |Q_op|. *'wildmenu'* *'wmnu'* *'nowildmenu'* *'nowmnu'* 'wildmenu' 'wmnu' boolean (default on) global - When 'wildmenu' is on, command-line completion operates in an enhanced - mode. On pressing 'wildchar' (usually <Tab>) to invoke completion, - the possible matches are shown just above the command line, with the - first match highlighted (overwriting the status line, if there is - one). Keys that show the previous/next match, such as <Tab> or - CTRL-P/CTRL-N, cause the highlight to move to the appropriate match. - When 'wildmode' is used, "wildmenu" mode is used where "full" is - specified. "longest" and "list" do not start "wildmenu" mode. - You can check the current mode with |wildmenumode()|. - If there are more matches than can fit in the line, a ">" is shown on - the right and/or a "<" is shown on the left. The status line scrolls - as needed. - The "wildmenu" mode is abandoned when a key is hit that is not used - for selecting a completion. - While the "wildmenu" is active the following keys have special - meanings: + Enables "enhanced mode" of command-line completion. When user hits + <Tab> (or 'wildchar') to invoke completion, the possible matches are + shown in a menu just above the command-line (see 'wildoptions'), with + the first match highlighted (overwriting the statusline). Keys that + show the previous/next match (<Tab>/CTRL-P/CTRL-N) highlight the + match. + 'wildmode' must specify "full": "longest" and "list" do not start + 'wildmenu' mode. You can check the current mode with |wildmenumode()|. + The menu is canceled when a key is hit that is not used for selecting + a completion. + + While the menu is active these keys have special meanings: <Left> <Right> - select previous/next match (like CTRL-P/CTRL-N) <Down> - in filename/menu name completion: move into a @@ -6667,15 +6673,12 @@ A jump table for the options with a short description can be found at |Q_op|. <Up> - in filename/menu name completion: move up into parent directory or parent menu. - This makes the menus accessible from the console |console-menus|. - - If you prefer the <Left> and <Right> keys to move the cursor instead - of selecting a different match, use this: > + If you want <Left> and <Right> to move the cursor instead of selecting + a different match, use this: > :cnoremap <Left> <Space><BS><Left> :cnoremap <Right> <Space><BS><Right> < - The "WildMenu" highlighting is used for displaying the current match - |hl-WildMenu|. + |hl-WildMenu| highlights the current match. *'wildmode'* *'wim'* 'wildmode' 'wim' string (default: "full") diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 45a94bb961..4267aefbbf 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -296,7 +296,7 @@ coerced to strings. See |id()| for more details, currently it uses |c_CTRL-R| pasting a non-special register into |cmdline| omits the last <CR>. -Lua interface (|if_lua.txt|): +Lua interface (|lua.txt|): - `:lua print("a\0b")` will print `a^@b`, like with `:echomsg "a\nb"` . In Vim that prints `a` and `b` on separate lines, exactly like @@ -307,15 +307,15 @@ Lua interface (|if_lua.txt|): - Lua package.path and package.cpath are automatically updated according to 'runtimepath': |lua-require|. -|input()| and |inputdialog()| support for each other’s features (return on -cancel and completion respectively) via dictionary argument (replaces all -other arguments if used). - -|input()| and |inputdialog()| support user-defined cmdline highlighting. - Commands: |:doautocmd| does not warn about "No matching autocommands". +Functions: + |input()| and |inputdialog()| support for each other’s features (return on + cancel and completion respectively) via dictionary argument (replaces all + other arguments if used). + |input()| and |inputdialog()| support user-defined cmdline highlighting. + Highlight groups: |hl-ColorColumn|, |hl-CursorColumn| are lower priority than most other groups @@ -399,10 +399,10 @@ VimL (Vim script) compatibility: Some legacy Vim features are not implemented: -- |if_py|: *python-bindeval* *python-Function* are not supported -- |if_lua|: the `vim` object is missing some legacy methods -- *if_perl* +- |if_lua|: Nvim Lua API is not compatible with Vim's "if_lua" - *if_mzscheme* +- *if_perl* +- |if_py|: *python-bindeval* *python-Function* are not supported - *if_tcl* ============================================================================== @@ -524,4 +524,4 @@ TUI: always uses 7-bit control sequences. ============================================================================== - vim:tw=78:ts=8:sw=2:noet:ft=help:norl: + vim:tw=78:ts=8:sw=2:et:ft=help:norl: diff --git a/runtime/ftplugin/man.vim b/runtime/ftplugin/man.vim index 6c7f095f62..b3b23833ba 100644 --- a/runtime/ftplugin/man.vim +++ b/runtime/ftplugin/man.vim @@ -20,13 +20,13 @@ setlocal wrap breakindent linebreak setlocal nonumber norelativenumber setlocal foldcolumn=0 colorcolumn=0 nolist nofoldenable +setlocal tagfunc=man#goto_tag + if !exists('g:no_plugin_maps') && !exists('g:no_man_maps') nnoremap <silent> <buffer> j gj nnoremap <silent> <buffer> k gk nnoremap <silent> <buffer> gO :call man#show_toc()<CR> - nnoremap <silent> <buffer> <C-]> :Man<CR> nnoremap <silent> <buffer> K :Man<CR> - nnoremap <silent> <buffer> <C-T> :call man#pop_tag()<CR> if 1 == bufnr('%') || s:pager nnoremap <silent> <buffer> <nowait> q :lclose<CR>:q<CR> else diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index b2dc1315dd..5a0b320f4c 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -24,7 +24,6 @@ local lsp = { -- format_rpc_error = lsp_rpc.format_rpc_error; } --- TODO consider whether 'eol' or 'fixeol' should change the nvim_buf_get_lines that send. -- TODO improve handling of scratch buffers with LSP attached. local function err_message(...) @@ -181,6 +180,14 @@ local function validate_client_config(config) } end +local function buf_get_full_text(bufnr) + local text = table.concat(nvim_buf_get_lines(bufnr, 0, -1, true), '\n') + if nvim_buf_get_option(bufnr, 'eol') then + text = text .. '\n' + end + return text +end + local function text_document_did_open_handler(bufnr, client) if not client.resolved_capabilities.text_document_open_close then return @@ -194,7 +201,7 @@ local function text_document_did_open_handler(bufnr, client) uri = vim.uri_from_bufnr(bufnr); -- TODO make sure our filetypes are compatible with languageId names. languageId = nvim_buf_get_option(bufnr, 'filetype'); - text = table.concat(nvim_buf_get_lines(bufnr, 0, -1, false), '\n'); + text = buf_get_full_text(bufnr); } } client.notify('textDocument/didOpen', params) @@ -557,7 +564,7 @@ do end) local full_changes = once(function() return { - text = table.concat(nvim_buf_get_lines(bufnr, 0, -1, false), "\n"); + text = buf_get_full_text(bufnr); }; end) local uri = vim.uri_from_bufnr(bufnr) diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py index 8d0cf54828..4d71d5e15e 100755 --- a/scripts/gen_vimdoc.py +++ b/scripts/gen_vimdoc.py @@ -85,7 +85,7 @@ CONFIG = { 'append_only': [], }, 'lua': { - 'filename': 'if_lua.txt', + 'filename': 'lua.txt', 'section_start_token': '*lua-vim*', 'section_order': [ 'vim.lua', diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 9ec96840d1..a5f8b0974e 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -106,6 +106,14 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err) /// Activates buffer-update events on a channel, or as Lua callbacks. /// +/// Example (Lua): capture buffer updates in a global `events` variable +/// (use "print(vim.inspect(events))" to see its contents): +/// <pre> +/// events = {} +/// vim.api.nvim_buf_attach(0, false, { +/// on_lines=function(...) table.insert(events, {...}) end}) +/// </pre> +/// /// @see |nvim_buf_detach()| /// @see |api-buffer-updates-lua| /// @@ -1041,18 +1049,18 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, /// range ends can be specified as (row, col) tuples, as well as extmark /// ids in the same namespace. In addition, 0 and -1 works as shorthands /// for (0,0) and (-1,-1) respectively, so that all marks in the buffer can be -/// quieried as: +/// queried as: /// -/// all_marks = nvim_buf_get_extmarks(0, my_ns, 0, -1, -1) +/// all_marks = nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) /// /// If end is a lower position than start, then the range will be traversed -/// backwards. This is mostly used with limited amount, to be able to get the +/// backwards. This is mostly useful with limited amount, to be able to get the /// first marks prior to a given position. /// /// @param buffer The buffer handle /// @param ns_id An id returned previously from nvim_create_namespace -/// @param lower One of: extmark id, (row, col) or 0, -1 for buffer ends -/// @param upper One of: extmark id, (row, col) or 0, -1 for buffer ends +/// @param start One of: extmark id, (row, col) or 0, -1 for buffer ends +/// @param end One of: extmark id, (row, col) or 0, -1 for buffer ends /// @param opts additional options. Supports the keys: /// - amount: Maximum number of marks to return /// @param[out] err Details of an error that may have occurred @@ -1153,7 +1161,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, /// @param buffer The buffer handle /// @param ns_id a identifier returned previously with nvim_create_namespace /// @param id The extmark's id or 0 to create a new mark. -/// @param row The row to set the extmark to. +/// @param line The row to set the extmark to. /// @param col The column to set the extmark to. /// @param opts Optional parameters. Currently not used. /// @param[out] err Details of an error that may have occurred diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 25b6502b19..cd0f3f4b9d 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -3056,7 +3056,9 @@ static void ins_compl_clear(void) XFREE_CLEAR(compl_orig_text); compl_enter_selects = false; // clear v:completed_item - set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc()); + dict_T *const d = tv_dict_alloc(); + d->dv_lock = VAR_FIXED; + set_vim_var_dict(VV_COMPLETED_ITEM, d); } /// Check that Insert completion is active. @@ -4313,7 +4315,9 @@ static void ins_compl_delete(void) // causes flicker, thus we can't do that. changed_cline_bef_curs(); // clear v:completed_item - set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc()); + dict_T *const d = tv_dict_alloc(); + d->dv_lock = VAR_FIXED; + set_vim_var_dict(VV_COMPLETED_ITEM, d); } // Insert the new text being completed. @@ -4335,6 +4339,7 @@ static dict_T *ins_compl_dict_alloc(compl_T *match) { // { word, abbr, menu, kind, info } dict_T *dict = tv_dict_alloc(); + dict->dv_lock = VAR_FIXED; tv_dict_add_str( dict, S_LEN("word"), (const char *)EMPTY_IF_NULL(match->cp_str)); diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 6a8bea28a7..641edf4610 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -140,6 +140,31 @@ struct dbg_stuff { except_T *current_exception; }; +typedef struct { + // parsed results + exarg_T *eap; + char_u *parsed_upto; // local we've parsed up to so far + char_u *cmd; // start of command + char_u *after_modifier; + + // errors + char_u *errormsg; + + // globals that need to be updated + cmdmod_T cmdmod; + int sandbox; + int msg_silent; + int emsg_silent; + bool ex_pressedreturn; + long p_verbose; + + // other side-effects + bool set_eventignore; + long verbose_save; + int save_msg_silent; + int did_esilent; + bool did_sandbox; +} parse_state_T; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_docmd.c.generated.h" @@ -1218,69 +1243,57 @@ static char_u *skip_colon_white(const char_u *p, bool skipleadingwhite) return (char_u *)p; } -/* - * Execute one Ex command. - * - * If 'sourcing' is TRUE, the command will be included in the error message. - * - * 1. skip comment lines and leading space - * 2. handle command modifiers - * 3. skip over the range to find the command - * 4. parse the range - * 5. parse the command - * 6. parse arguments - * 7. switch on command name - * - * Note: "fgetline" can be NULL. - * - * This function may be called recursively! - */ -static char_u * do_one_cmd(char_u **cmdlinep, - int flags, - struct condstack *cstack, - LineGetter fgetline, - void *cookie /* argument for fgetline() */ - ) +static void parse_state_to_global(const parse_state_T *parse_state) { - char_u *p; - linenr_T lnum; - long n; - char_u *errormsg = NULL; /* error message */ - exarg_T ea; /* Ex command arguments */ - long verbose_save = -1; - int save_msg_scroll = msg_scroll; - int save_msg_silent = -1; - int did_esilent = 0; - int did_sandbox = FALSE; - cmdmod_T save_cmdmod; - const int save_reg_executing = reg_executing; - char_u *cmd; - int address_count = 1; + cmdmod = parse_state->cmdmod; + sandbox = parse_state->sandbox; + msg_silent = parse_state->msg_silent; + emsg_silent = parse_state->emsg_silent; + ex_pressedreturn = parse_state->ex_pressedreturn; + p_verbose = parse_state->p_verbose; - memset(&ea, 0, sizeof(ea)); - ea.line1 = 1; - ea.line2 = 1; - ex_nesting_level++; + if (parse_state->set_eventignore) { + set_string_option_direct( + (char_u *)"ei", -1, (char_u *)"all", OPT_FREE, SID_NONE); + } +} - /* When the last file has not been edited :q has to be typed twice. */ - if (quitmore - /* avoid that a function call in 'statusline' does this */ - && !getline_equal(fgetline, cookie, get_func_line) - /* avoid that an autocommand, e.g. QuitPre, does this */ - && !getline_equal(fgetline, cookie, getnextac) - ) - --quitmore; +static void parse_state_from_global(parse_state_T *parse_state) +{ + memset(parse_state, 0, sizeof(*parse_state)); + parse_state->cmdmod = cmdmod; + parse_state->sandbox = sandbox; + parse_state->msg_silent = msg_silent; + parse_state->emsg_silent = emsg_silent; + parse_state->ex_pressedreturn = ex_pressedreturn; + parse_state->p_verbose = p_verbose; +} - /* - * Reset browse, confirm, etc.. They are restored when returning, for - * recursive calls. - */ - save_cmdmod = cmdmod; - memset(&cmdmod, 0, sizeof(cmdmod)); +// +// Parse one Ex command. +// +// This has no side-effects, except for modifying parameters +// passed in by pointer. +// +// The `out` should be zeroed, and its `ea` member initialised, +// before calling this function. +// +static bool parse_one_cmd( + char_u **cmdlinep, + parse_state_T *const out, + LineGetter fgetline, + void *fgetline_cookie) +{ + exarg_T ea = { + .line1 = 1, + .line2 = 1, + }; + *out->eap = ea; - /* "#!anything" is handled like a comment. */ - if ((*cmdlinep)[0] == '#' && (*cmdlinep)[1] == '!') - goto doend; + // "#!anything" is handled like a comment. + if ((*cmdlinep)[0] == '#' && (*cmdlinep)[1] == '!') { + return false; + } /* * Repeat until no more command modifiers are found. @@ -1290,70 +1303,76 @@ static char_u * do_one_cmd(char_u **cmdlinep, /* * 1. Skip comment lines and leading white space and colons. */ - while (*ea.cmd == ' ' || *ea.cmd == '\t' || *ea.cmd == ':') - ++ea.cmd; + while (*ea.cmd == ' ' + || *ea.cmd == '\t' + || *ea.cmd == ':') { + ea.cmd++; + } - /* in ex mode, an empty line works like :+ */ + // in ex mode, an empty line works like :+ if (*ea.cmd == NUL && exmode_active - && (getline_equal(fgetline, cookie, getexmodeline) - || getline_equal(fgetline, cookie, getexline)) + && (getline_equal(fgetline, fgetline_cookie, getexmodeline) + || getline_equal(fgetline, fgetline_cookie, getexline)) && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { ea.cmd = (char_u *)"+"; - ex_pressedreturn = true; + out->ex_pressedreturn = true; } - /* ignore comment and empty lines */ - if (*ea.cmd == '"') - goto doend; + // ignore comment and empty lines + if (*ea.cmd == '"') { + return false; + } if (*ea.cmd == NUL) { - ex_pressedreturn = true; - goto doend; + out->ex_pressedreturn = true; + return false; } /* * 2. Handle command modifiers. */ - p = skip_range(ea.cmd, NULL); + char_u *p = skip_range(ea.cmd, NULL); switch (*p) { - /* When adding an entry, also modify cmd_exists(). */ + // When adding an entry, also modify cmd_exists(). case 'a': if (!checkforcmd(&ea.cmd, "aboveleft", 3)) break; - cmdmod.split |= WSP_ABOVE; + out->cmdmod.split |= WSP_ABOVE; continue; case 'b': if (checkforcmd(&ea.cmd, "belowright", 3)) { - cmdmod.split |= WSP_BELOW; + out->cmdmod.split |= WSP_BELOW; continue; } if (checkforcmd(&ea.cmd, "browse", 3)) { - cmdmod.browse = true; + out->cmdmod.browse = true; continue; } - if (!checkforcmd(&ea.cmd, "botright", 2)) + if (!checkforcmd(&ea.cmd, "botright", 2)) { break; - cmdmod.split |= WSP_BOT; + } + out->cmdmod.split |= WSP_BOT; continue; case 'c': if (!checkforcmd(&ea.cmd, "confirm", 4)) break; - cmdmod.confirm = true; + out->cmdmod.confirm = true; continue; case 'k': if (checkforcmd(&ea.cmd, "keepmarks", 3)) { - cmdmod.keepmarks = true; + out->cmdmod.keepmarks = true; continue; } if (checkforcmd(&ea.cmd, "keepalt", 5)) { - cmdmod.keepalt = true; + out->cmdmod.keepalt = true; continue; } if (checkforcmd(&ea.cmd, "keeppatterns", 5)) { - cmdmod.keeppatterns = true; + out->cmdmod.keeppatterns = true; continue; } - if (!checkforcmd(&ea.cmd, "keepjumps", 5)) + if (!checkforcmd(&ea.cmd, "keepjumps", 5)) { break; - cmdmod.keepjumps = true; + } + out->cmdmod.keepjumps = true; continue; case 'f': { // only accept ":filter {pat} cmd" @@ -1363,7 +1382,7 @@ static char_u * do_one_cmd(char_u **cmdlinep, break; } if (*p == '!') { - cmdmod.filter_force = true; + out->cmdmod.filter_force = true; p = skipwhite(p + 1); if (*p == NUL || ends_excmd(*p)) { break; @@ -1373,134 +1392,217 @@ static char_u * do_one_cmd(char_u **cmdlinep, if (p == NULL || *p == NUL) { break; } - cmdmod.filter_regmatch.regprog = vim_regcomp(reg_pat, RE_MAGIC); - if (cmdmod.filter_regmatch.regprog == NULL) { + out->cmdmod.filter_regmatch.regprog = vim_regcomp(reg_pat, RE_MAGIC); + if (out->cmdmod.filter_regmatch.regprog == NULL) { break; } ea.cmd = p; continue; } - /* ":hide" and ":hide | cmd" are not modifiers */ + // ":hide" and ":hide | cmd" are not modifiers case 'h': if (p != ea.cmd || !checkforcmd(&p, "hide", 3) || *p == NUL || ends_excmd(*p)) break; ea.cmd = p; - cmdmod.hide = true; + out->cmdmod.hide = true; continue; case 'l': if (checkforcmd(&ea.cmd, "lockmarks", 3)) { - cmdmod.lockmarks = true; + out->cmdmod.lockmarks = true; continue; } - if (!checkforcmd(&ea.cmd, "leftabove", 5)) + if (!checkforcmd(&ea.cmd, "leftabove", 5)) { break; - cmdmod.split |= WSP_ABOVE; + } + out->cmdmod.split |= WSP_ABOVE; continue; case 'n': if (checkforcmd(&ea.cmd, "noautocmd", 3)) { - if (cmdmod.save_ei == NULL) { - /* Set 'eventignore' to "all". Restore the - * existing option value later. */ - cmdmod.save_ei = vim_strsave(p_ei); - set_string_option_direct( - (char_u *)"ei", -1, (char_u *)"all", OPT_FREE, SID_NONE); + if (out->cmdmod.save_ei == NULL) { + // Set 'eventignore' to "all". Restore the + // existing option value later. + out->cmdmod.save_ei = vim_strsave(p_ei); + out->set_eventignore = true; } continue; } if (!checkforcmd(&ea.cmd, "noswapfile", 3)) { break; } - cmdmod.noswapfile = true; + out->cmdmod.noswapfile = true; continue; case 'r': if (!checkforcmd(&ea.cmd, "rightbelow", 6)) break; - cmdmod.split |= WSP_BELOW; + out->cmdmod.split |= WSP_BELOW; continue; case 's': if (checkforcmd(&ea.cmd, "sandbox", 3)) { - if (!did_sandbox) - ++sandbox; - did_sandbox = TRUE; + if (!out->did_sandbox) { + out->sandbox++; + } + out->did_sandbox = true; continue; } - if (!checkforcmd(&ea.cmd, "silent", 3)) + if (!checkforcmd(&ea.cmd, "silent", 3)) { break; - if (save_msg_silent == -1) - save_msg_silent = msg_silent; - ++msg_silent; + } + if (out->save_msg_silent == -1) { + out->save_msg_silent = out->msg_silent; + } + out->msg_silent++; if (*ea.cmd == '!' && !ascii_iswhite(ea.cmd[-1])) { - /* ":silent!", but not "silent !cmd" */ + // ":silent!", but not "silent !cmd" ea.cmd = skipwhite(ea.cmd + 1); - ++emsg_silent; - ++did_esilent; + out->emsg_silent++; + out->did_esilent++; } continue; case 't': if (checkforcmd(&p, "tab", 3)) { - long tabnr = get_address(&ea, &ea.cmd, ADDR_TABS, ea.skip, false, 1); + long tabnr = get_address( + &ea, &ea.cmd, ADDR_TABS, ea.skip, false, 1); + if (tabnr == MAXLNUM) { - cmdmod.tab = tabpage_index(curtab) + 1; + out->cmdmod.tab = tabpage_index(curtab) + 1; } else { if (tabnr < 0 || tabnr > LAST_TAB_NR) { - errormsg = (char_u *)_(e_invrange); - goto doend; + out->errormsg = (char_u *)_(e_invrange); + return false; } - cmdmod.tab = tabnr + 1; + out->cmdmod.tab = tabnr + 1; } ea.cmd = p; continue; } - if (!checkforcmd(&ea.cmd, "topleft", 2)) + if (!checkforcmd(&ea.cmd, "topleft", 2)) { break; - cmdmod.split |= WSP_TOP; + } + out->cmdmod.split |= WSP_TOP; continue; case 'u': if (!checkforcmd(&ea.cmd, "unsilent", 3)) break; - if (save_msg_silent == -1) - save_msg_silent = msg_silent; - msg_silent = 0; + if (out->save_msg_silent == -1) { + out->save_msg_silent = out->msg_silent; + } + out->msg_silent = 0; continue; case 'v': if (checkforcmd(&ea.cmd, "vertical", 4)) { - cmdmod.split |= WSP_VERT; + out->cmdmod.split |= WSP_VERT; continue; } if (!checkforcmd(&p, "verbose", 4)) break; - if (verbose_save < 0) - verbose_save = p_verbose; - if (ascii_isdigit(*ea.cmd)) - p_verbose = atoi((char *)ea.cmd); - else - p_verbose = 1; + if (out->verbose_save < 0) { + out->verbose_save = out->p_verbose; + } + if (ascii_isdigit(*ea.cmd)) { + out->p_verbose = atoi((char *)ea.cmd); + } else { + out->p_verbose = 1; + } ea.cmd = p; continue; } break; } - char_u *after_modifier = ea.cmd; - - ea.skip = (did_emsg - || got_int - || current_exception - || (cstack->cs_idx >= 0 - && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE))); + out->after_modifier = ea.cmd; // 3. Skip over the range to find the command. Let "p" point to after it. // // We need the command to know what kind of range it uses. - cmd = ea.cmd; + out->cmd = ea.cmd; ea.cmd = skip_range(ea.cmd, NULL); if (*ea.cmd == '*') { ea.cmd = skipwhite(ea.cmd + 1); } - p = find_command(&ea, NULL); + out->parsed_upto = find_command(&ea, NULL); + + *out->eap = ea; + + return true; +} + +/* + * Execute one Ex command. + * + * If 'sourcing' is TRUE, the command will be included in the error message. + * + * 1. skip comment lines and leading space + * 2. handle command modifiers + * 3. skip over the range to find the command + * 4. parse the range + * 5. parse the command + * 6. parse arguments + * 7. switch on command name + * + * Note: "fgetline" can be NULL. + * + * This function may be called recursively! + */ +static char_u * do_one_cmd(char_u **cmdlinep, + int flags, + struct condstack *cstack, + LineGetter fgetline, + void *cookie /* argument for fgetline() */ + ) +{ + char_u *p; + linenr_T lnum; + long n; + char_u *errormsg = NULL; // error message + exarg_T ea; + int save_msg_scroll = msg_scroll; + parse_state_T parsed; + cmdmod_T save_cmdmod; + const int save_reg_executing = reg_executing; + + ex_nesting_level++; + + /* When the last file has not been edited :q has to be typed twice. */ + if (quitmore + /* avoid that a function call in 'statusline' does this */ + && !getline_equal(fgetline, cookie, get_func_line) + /* avoid that an autocommand, e.g. QuitPre, does this */ + && !getline_equal(fgetline, cookie, getnextac) + ) + --quitmore; + + /* + * Reset browse, confirm, etc.. They are restored when returning, for + * recursive calls. + */ + save_cmdmod = cmdmod; + memset(&cmdmod, 0, sizeof(cmdmod)); + + parse_state_from_global(&parsed); + parsed.eap = &ea; + parsed.verbose_save = -1; + parsed.save_msg_silent = -1; + parsed.did_esilent = 0; + parsed.did_sandbox = false; + bool parse_success = parse_one_cmd(cmdlinep, &parsed, fgetline, cookie); + parse_state_to_global(&parsed); + + // Update locals from parse_one_cmd() + errormsg = parsed.errormsg; + p = parsed.parsed_upto; + + if (!parse_success) { + goto doend; + } + + ea.skip = (did_emsg + || got_int + || current_exception + || (cstack->cs_idx >= 0 + && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE))); // Count this line for profiling if skip is TRUE. if (do_profiling == PROF_YES @@ -1571,148 +1673,9 @@ static char_u * do_one_cmd(char_u **cmdlinep, } } - /* repeat for all ',' or ';' separated addresses */ - ea.cmd = cmd; - for (;; ) { - ea.line1 = ea.line2; - switch (ea.addr_type) { - case ADDR_LINES: - // default is current line number - ea.line2 = curwin->w_cursor.lnum; - break; - case ADDR_WINDOWS: - ea.line2 = CURRENT_WIN_NR; - break; - case ADDR_ARGUMENTS: - ea.line2 = curwin->w_arg_idx + 1; - if (ea.line2 > ARGCOUNT) { - ea.line2 = ARGCOUNT; - } - break; - case ADDR_LOADED_BUFFERS: - case ADDR_BUFFERS: - ea.line2 = curbuf->b_fnum; - break; - case ADDR_TABS: - ea.line2 = CURRENT_TAB_NR; - break; - case ADDR_TABS_RELATIVE: - ea.line2 = 1; - break; - case ADDR_QUICKFIX: - ea.line2 = qf_get_cur_valid_idx(&ea); - break; - } - ea.cmd = skipwhite(ea.cmd); - lnum = get_address(&ea, &ea.cmd, ea.addr_type, ea.skip, - ea.addr_count == 0, address_count++); - if (ea.cmd == NULL) { // error detected - goto doend; - } - if (lnum == MAXLNUM) { - if (*ea.cmd == '%') { /* '%' - all lines */ - ++ea.cmd; - switch (ea.addr_type) { - case ADDR_LINES: - ea.line1 = 1; - ea.line2 = curbuf->b_ml.ml_line_count; - break; - case ADDR_LOADED_BUFFERS: { - buf_T *buf = firstbuf; - while (buf->b_next != NULL && buf->b_ml.ml_mfp == NULL) { - buf = buf->b_next; - } - ea.line1 = buf->b_fnum; - buf = lastbuf; - while (buf->b_prev != NULL && buf->b_ml.ml_mfp == NULL) { - buf = buf->b_prev; - } - ea.line2 = buf->b_fnum; - break; - } - case ADDR_BUFFERS: - ea.line1 = firstbuf->b_fnum; - ea.line2 = lastbuf->b_fnum; - break; - case ADDR_WINDOWS: - case ADDR_TABS: - if (IS_USER_CMDIDX(ea.cmdidx)) { - ea.line1 = 1; - ea.line2 = - ea.addr_type == ADDR_WINDOWS ? LAST_WIN_NR : LAST_TAB_NR; - } else { - // there is no Vim command which uses '%' and - // ADDR_WINDOWS or ADDR_TABS - errormsg = (char_u *)_(e_invrange); - goto doend; - } - break; - case ADDR_TABS_RELATIVE: - errormsg = (char_u *)_(e_invrange); - goto doend; - break; - case ADDR_ARGUMENTS: - if (ARGCOUNT == 0) { - ea.line1 = ea.line2 = 0; - } else { - ea.line1 = 1; - ea.line2 = ARGCOUNT; - } - break; - case ADDR_QUICKFIX: - ea.line1 = 1; - ea.line2 = qf_get_size(&ea); - if (ea.line2 == 0) { - ea.line2 = 1; - } - break; - } - ++ea.addr_count; - } - /* '*' - visual area */ - else if (*ea.cmd == '*') { - pos_T *fp; - - if (ea.addr_type != ADDR_LINES) { - errormsg = (char_u *)_(e_invrange); - goto doend; - } - - ++ea.cmd; - if (!ea.skip) { - fp = getmark('<', FALSE); - if (check_mark(fp) == FAIL) - goto doend; - ea.line1 = fp->lnum; - fp = getmark('>', FALSE); - if (check_mark(fp) == FAIL) - goto doend; - ea.line2 = fp->lnum; - ++ea.addr_count; - } - } - } else - ea.line2 = lnum; - ea.addr_count++; - - if (*ea.cmd == ';') { - if (!ea.skip) { - curwin->w_cursor.lnum = ea.line2; - // don't leave the cursor on an illegal line or column - check_cursor(); - } - } else if (*ea.cmd != ',') { - break; - } - ea.cmd++; - } - - /* One address given: set start and end lines */ - if (ea.addr_count == 1) { - ea.line1 = ea.line2; - /* ... but only implicit: really no address given */ - if (lnum == MAXLNUM) - ea.addr_count = 0; + ea.cmd = parsed.cmd; + if (parse_cmd_address(&ea, &errormsg) == FAIL) { + goto doend; } /* @@ -1791,8 +1754,8 @@ static char_u * do_one_cmd(char_u **cmdlinep, if (!(flags & DOCMD_VERBOSE)) { // If the modifier was parsed OK the error must be in the following // command - if (after_modifier != NULL) { - append_command(after_modifier); + if (parsed.after_modifier != NULL) { + append_command(parsed.after_modifier); } else { append_command(*cmdlinep); } @@ -2260,12 +2223,12 @@ static char_u * do_one_cmd(char_u **cmdlinep, // The :try command saves the emsg_silent flag, reset it here when // ":silent! try" was used, it should only apply to :try itself. - if (ea.cmdidx == CMD_try && did_esilent > 0) { - emsg_silent -= did_esilent; + if (ea.cmdidx == CMD_try && parsed.did_esilent > 0) { + emsg_silent -= parsed.did_esilent; if (emsg_silent < 0) { emsg_silent = 0; } - did_esilent = 0; + parsed.did_esilent = 0; } // 7. Execute the command. @@ -2331,8 +2294,9 @@ doend: ? cmdnames[(int)ea.cmdidx].cmd_name : (char_u *)NULL); - if (verbose_save >= 0) - p_verbose = verbose_save; + if (parsed.verbose_save >= 0) { + p_verbose = parsed.verbose_save; + } if (cmdmod.save_ei != NULL) { /* Restore 'eventignore' to the value before ":noautocmd". */ set_string_option_direct((char_u *)"ei", -1, cmdmod.save_ei, @@ -2347,16 +2311,18 @@ doend: cmdmod = save_cmdmod; reg_executing = save_reg_executing; - if (save_msg_silent != -1) { - /* messages could be enabled for a serious error, need to check if the - * counters don't become negative */ - if (!did_emsg || msg_silent > save_msg_silent) - msg_silent = save_msg_silent; - emsg_silent -= did_esilent; - if (emsg_silent < 0) + if (parsed.save_msg_silent != -1) { + // messages could be enabled for a serious error, need to check if the + // counters don't become negative + if (!did_emsg || msg_silent > parsed.save_msg_silent) { + msg_silent = parsed.save_msg_silent; + } + emsg_silent -= parsed.did_esilent; + if (emsg_silent < 0) { emsg_silent = 0; - /* Restore msg_scroll, it's set by file I/O commands, even when no - * message is actually displayed. */ + } + // Restore msg_scroll, it's set by file I/O commands, even when no + // message is actually displayed. msg_scroll = save_msg_scroll; /* "silent reg" or "silent echo x" inside "redir" leaves msg_col @@ -2365,8 +2331,9 @@ doend: msg_col = 0; } - if (did_sandbox) - --sandbox; + if (parsed.did_sandbox) { + sandbox--; + } if (ea.nextcmd && *ea.nextcmd == NUL) /* not really a next command */ ea.nextcmd = NULL; @@ -2376,6 +2343,160 @@ doend: return ea.nextcmd; } +// Parse the address range, if any, in "eap". +// Return FAIL and set "errormsg" or return OK. +int parse_cmd_address(exarg_T *eap, char_u **errormsg) + FUNC_ATTR_NONNULL_ALL +{ + int address_count = 1; + linenr_T lnum; + + // Repeat for all ',' or ';' separated addresses. + for (;;) { + eap->line1 = eap->line2; + switch (eap->addr_type) { + case ADDR_LINES: + // default is current line number + eap->line2 = curwin->w_cursor.lnum; + break; + case ADDR_WINDOWS: + eap->line2 = CURRENT_WIN_NR; + break; + case ADDR_ARGUMENTS: + eap->line2 = curwin->w_arg_idx + 1; + if (eap->line2 > ARGCOUNT) { + eap->line2 = ARGCOUNT; + } + break; + case ADDR_LOADED_BUFFERS: + case ADDR_BUFFERS: + eap->line2 = curbuf->b_fnum; + break; + case ADDR_TABS: + eap->line2 = CURRENT_TAB_NR; + break; + case ADDR_TABS_RELATIVE: + eap->line2 = 1; + break; + case ADDR_QUICKFIX: + eap->line2 = qf_get_cur_valid_idx(eap); + break; + } + eap->cmd = skipwhite(eap->cmd); + lnum = get_address(eap, &eap->cmd, eap->addr_type, eap->skip, + eap->addr_count == 0, address_count++); + if (eap->cmd == NULL) { // error detected + return FAIL; + } + if (lnum == MAXLNUM) { + if (*eap->cmd == '%') { // '%' - all lines + eap->cmd++; + switch (eap->addr_type) { + case ADDR_LINES: + eap->line1 = 1; + eap->line2 = curbuf->b_ml.ml_line_count; + break; + case ADDR_LOADED_BUFFERS: { + buf_T *buf = firstbuf; + + while (buf->b_next != NULL && buf->b_ml.ml_mfp == NULL) { + buf = buf->b_next; + } + eap->line1 = buf->b_fnum; + buf = lastbuf; + while (buf->b_prev != NULL && buf->b_ml.ml_mfp == NULL) { + buf = buf->b_prev; + } + eap->line2 = buf->b_fnum; + break; + } + case ADDR_BUFFERS: + eap->line1 = firstbuf->b_fnum; + eap->line2 = lastbuf->b_fnum; + break; + case ADDR_WINDOWS: + case ADDR_TABS: + if (IS_USER_CMDIDX(eap->cmdidx)) { + eap->line1 = 1; + eap->line2 = eap->addr_type == ADDR_WINDOWS + ? LAST_WIN_NR : LAST_TAB_NR; + } else { + // there is no Vim command which uses '%' and + // ADDR_WINDOWS or ADDR_TABS + *errormsg = (char_u *)_(e_invrange); + return FAIL; + } + break; + case ADDR_TABS_RELATIVE: + *errormsg = (char_u *)_(e_invrange); + return FAIL; + case ADDR_ARGUMENTS: + if (ARGCOUNT == 0) { + eap->line1 = eap->line2 = 0; + } else { + eap->line1 = 1; + eap->line2 = ARGCOUNT; + } + break; + case ADDR_QUICKFIX: + eap->line1 = 1; + eap->line2 = qf_get_size(eap); + if (eap->line2 == 0) { + eap->line2 = 1; + } + break; + } + eap->addr_count++; + } else if (*eap->cmd == '*') { + // '*' - visual area + if (eap->addr_type != ADDR_LINES) { + *errormsg = (char_u *)_(e_invrange); + return FAIL; + } + + eap->cmd++; + if (!eap->skip) { + pos_T *fp = getmark('<', false); + if (check_mark(fp) == FAIL) { + return FAIL; + } + eap->line1 = fp->lnum; + fp = getmark('>', false); + if (check_mark(fp) == FAIL) { + return FAIL; + } + eap->line2 = fp->lnum; + eap->addr_count++; + } + } + } else { + eap->line2 = lnum; + } + eap->addr_count++; + + if (*eap->cmd == ';') { + if (!eap->skip) { + curwin->w_cursor.lnum = eap->line2; + // don't leave the cursor on an illegal line or column + check_cursor(); + } + } else if (*eap->cmd != ',') { + break; + } + eap->cmd++; + } + + // One address given: set start and end lines. + if (eap->addr_count == 1) { + eap->line1 = eap->line2; + // ... but only implicit: really no address given + if (lnum == MAXLNUM) { + eap->addr_count = 0; + } + } + return OK; +} + /* * Check for an Ex command with optional tail. * If there is a match advance "pp" to the argument and return TRUE. @@ -3556,15 +3677,13 @@ const char * set_one_cmd_context( return NULL; } -/* - * skip a range specifier of the form: addr [,addr] [;addr] .. - * - * Backslashed delimiters after / or ? will be skipped, and commands will - * not be expanded between /'s and ?'s or after "'". - * - * Also skip white space and ":" characters. - * Returns the "cmd" pointer advanced to beyond the range. - */ +// Skip a range specifier of the form: addr [,addr] [;addr] .. +// +// Backslashed delimiters after / or ? will be skipped, and commands will +// not be expanded between /'s and ?'s or after "'". +// +// Also skip white space and ":" characters. +// Returns the "cmd" pointer advanced to beyond the range. char_u *skip_range( const char_u *cmd, int *ctx // pointer to xp_context or NULL diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 58e6b2ae92..fcf15638c7 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -2650,6 +2650,7 @@ buf_write( */ if (!(append && *p_pm == NUL) && !filtering && perm >= 0 && dobackup) { FileInfo file_info; + const bool no_prepend_dot = false; if ((bkc & BKC_YES) || append) { /* "yes" */ backup_copy = TRUE; @@ -2737,6 +2738,7 @@ buf_write( int some_error = false; char_u *dirp; char_u *rootname; + char_u *p; /* * Try to make the backup in each directory in the 'bdir' option. @@ -2756,6 +2758,17 @@ buf_write( * Isolate one directory name, using an entry in 'bdir'. */ (void)copy_option_part(&dirp, IObuff, IOSIZE, ","); + p = IObuff + STRLEN(IObuff); + if (after_pathsep((char *)IObuff, (char *)p) && p[-1] == p[-2]) { + // Ends with '//', Use Full path + if ((p = (char_u *)make_percent_swname((char *)IObuff, (char *)fname)) + != NULL) { + backup = (char_u *)modname((char *)p, (char *)backup_ext, + no_prepend_dot); + xfree(p); + } + } + rootname = get_file_in_dir(fname, IObuff); if (rootname == NULL) { some_error = TRUE; /* out of memory */ @@ -2764,10 +2777,14 @@ buf_write( FileInfo file_info_new; { - /* - * Make backup file name. - */ - backup = (char_u *)modname((char *)rootname, (char *)backup_ext, FALSE); + // + // Make the backup file name. + // + if (backup == NULL) { + backup = (char_u *)modname((char *)rootname, (char *)backup_ext, + no_prepend_dot); + } + if (backup == NULL) { xfree(rootname); some_error = TRUE; /* out of memory */ @@ -2893,12 +2910,26 @@ nobackup: * Isolate one directory name and make the backup file name. */ (void)copy_option_part(&dirp, IObuff, IOSIZE, ","); - rootname = get_file_in_dir(fname, IObuff); - if (rootname == NULL) - backup = NULL; - else { - backup = (char_u *)modname((char *)rootname, (char *)backup_ext, FALSE); - xfree(rootname); + p = IObuff + STRLEN(IObuff); + if (after_pathsep((char *)IObuff, (char *)p) && p[-1] == p[-2]) { + // path ends with '//', use full path + if ((p = (char_u *)make_percent_swname((char *)IObuff, (char *)fname)) + != NULL) { + backup = (char_u *)modname((char *)p, (char *)backup_ext, + no_prepend_dot); + xfree(p); + } + } + + if (backup == NULL) { + rootname = get_file_in_dir(fname, IObuff); + if (rootname == NULL) { + backup = NULL; + } else { + backup = (char_u *)modname((char *)rootname, (char *)backup_ext, + no_prepend_dot); + xfree(rootname); + } } if (backup != NULL) { diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index ce24d1716d..1665a55aff 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -167,8 +167,7 @@ end --- --- Example: To remove ANSI color codes when pasting: --- <pre> ---- vim.paste = (function() ---- local overridden = vim.paste +--- vim.paste = (function(overridden) --- return function(lines, phase) --- for i,line in ipairs(lines) do --- -- Scrub ANSI color codes from paste input. @@ -176,7 +175,7 @@ end --- end --- overridden(lines, phase) --- end ---- end)() +--- end)(vim.paste) --- </pre> --- --@see |paste| diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 454e9e46f4..e5070f23ff 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -296,17 +296,17 @@ pos_T *movechangelist(int count) * - NULL if there is no mark called 'c'. * - -1 if mark is in other file and jumped there (only if changefile is TRUE) */ -pos_T *getmark_buf(buf_T *buf, int c, int changefile) +pos_T *getmark_buf(buf_T *buf, int c, bool changefile) { return getmark_buf_fnum(buf, c, changefile, NULL); } -pos_T *getmark(int c, int changefile) +pos_T *getmark(int c, bool changefile) { return getmark_buf_fnum(curbuf, c, changefile, NULL); } -pos_T *getmark_buf_fnum(buf_T *buf, int c, int changefile, int *fnum) +pos_T *getmark_buf_fnum(buf_T *buf, int c, bool changefile, int *fnum) { pos_T *posp; pos_T *startp, *endp; diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 05cc62bb33..2824d57f49 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -1437,7 +1437,7 @@ recover_names ( * Append the full path to name with path separators made into percent * signs, to dir. An unnamed buffer is handled as "" (<currentdir>/"") */ -static char *make_percent_swname(const char *dir, char *name) +char *make_percent_swname(const char *dir, char *name) FUNC_ATTR_NONNULL_ARG(1) { char *d = NULL; diff --git a/src/nvim/search.c b/src/nvim/search.c index d396e7551b..a298f7333e 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -3808,8 +3808,9 @@ current_quote( } vis_bef_curs = lt(VIsual, curwin->w_cursor); + vis_empty = equalpos(VIsual, curwin->w_cursor); if (*p_sel == 'e') { - if (!vis_bef_curs) { + if (!vis_bef_curs && !vis_empty) { // VIsual needs to be start of Visual selection. pos_T t = curwin->w_cursor; @@ -3819,8 +3820,8 @@ current_quote( restore_vis_bef = true; } dec_cursor(); + vis_empty = equalpos(VIsual, curwin->w_cursor); } - vis_empty = equalpos(VIsual, curwin->w_cursor); } if (!vis_empty) { diff --git a/src/nvim/tag.c b/src/nvim/tag.c index a3967c70b5..9e8c05fb1e 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -65,6 +65,7 @@ typedef struct tag_pointers { char_u *tagkind_end; // end of tagkind char_u *user_data; // user_data string char_u *user_data_end; // end of user_data + linenr_T tagline; // "line:" value } tagptrs_T; /* @@ -2545,6 +2546,7 @@ parse_match( tagp->tagkind = NULL; tagp->user_data = NULL; + tagp->tagline = 0; tagp->command_end = NULL; if (retval == OK) { @@ -2564,6 +2566,8 @@ parse_match( tagp->tagkind = p + 5; } else if (STRNCMP(p, "user_data:", 10) == 0) { tagp->user_data = p + 10; + } else if (STRNCMP(p, "line:", 5) == 0) { + tagp->tagline = atoi((char *)p + 5); } if (tagp->tagkind != NULL && tagp->user_data != NULL) { break; @@ -2811,7 +2815,13 @@ static int jumpto_tag( p_ic = FALSE; /* don't ignore case now */ p_scs = FALSE; save_lnum = curwin->w_cursor.lnum; - curwin->w_cursor.lnum = 0; /* start search before first line */ + if (tagp.tagline > 0) { + // start search before line from "line:" field + curwin->w_cursor.lnum = tagp.tagline - 1; + } else { + // start search before first line + curwin->w_cursor.lnum = 0; + } if (do_search(NULL, pbuf[0], pbuf + 1, (long)1, search_options, NULL)) { retval = OK; diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim index 84f636077d..a5d83d6a25 100644 --- a/src/nvim/testdir/shared.vim +++ b/src/nvim/testdir/shared.vim @@ -252,6 +252,8 @@ func GetVimProg() endif endfunc +let g:valgrind_cnt = 1 + " Get the command to run Vim, with -u NONE and --headless arguments. " If there is an argument use it instead of "NONE". func GetVimCommand(...) @@ -267,6 +269,13 @@ func GetVimCommand(...) endif let cmd .= ' --headless -i NONE' let cmd = substitute(cmd, 'VIMRUNTIME=.*VIMRUNTIME;', '', '') + + " If using valgrind, make sure every run uses a different log file. + if cmd =~ 'valgrind.*--log-file=' + let cmd = substitute(cmd, '--log-file=\(^\s*\)', '--log-file=\1.' . g:valgrind_cnt, '') + let g:valgrind_cnt += 1 + endif + return cmd endfunc @@ -290,9 +299,6 @@ endfunc func RunVimPiped(before, after, arguments, pipecmd) let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log' let cmd = GetVimCommand() - if cmd == '' - return 0 - endif let args = '' if len(a:before) > 0 call writefile(a:before, 'Xbefore.vim') diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index f1274b01c8..5668f45dea 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -2,6 +2,7 @@ " This makes testing go faster, since Vim doesn't need to restart. source test_assign.vim +source test_backup.vim source test_behave.vim source test_cd.vim source test_changedtick.vim diff --git a/src/nvim/testdir/test_backup.vim b/src/nvim/testdir/test_backup.vim new file mode 100644 index 0000000000..fa10430613 --- /dev/null +++ b/src/nvim/testdir/test_backup.vim @@ -0,0 +1,58 @@ +" Tests for the backup function + +func Test_backup() + set backup backupdir=. + new + call setline(1, ['line1', 'line2']) + :f Xbackup.txt + :w! Xbackup.txt + " backup file is only created after + " writing a second time (before overwriting) + :w! Xbackup.txt + let l = readfile('Xbackup.txt~') + call assert_equal(['line1', 'line2'], l) + bw! + set backup&vim backupdir&vim + call delete('Xbackup.txt') + call delete('Xbackup.txt~') +endfunc + +func Test_backup2() + set backup backupdir=.// + new + call setline(1, ['line1', 'line2', 'line3']) + :f Xbackup.txt + :w! Xbackup.txt + " backup file is only created after + " writing a second time (before overwriting) + :w! Xbackup.txt + sp *Xbackup.txt~ + call assert_equal(['line1', 'line2', 'line3'], getline(1,'$')) + let f=expand('%') + call assert_match('src%nvim%testdir%Xbackup.txt\~', f) + bw! + bw! + call delete('Xbackup.txt') + call delete(f) + set backup&vim backupdir&vim +endfunc + +func Test_backup2_backupcopy() + set backup backupdir=.// backupcopy=yes + new + call setline(1, ['line1', 'line2', 'line3']) + :f Xbackup.txt + :w! Xbackup.txt + " backup file is only created after + " writing a second time (before overwriting) + :w! Xbackup.txt + sp *Xbackup.txt~ + call assert_equal(['line1', 'line2', 'line3'], getline(1,'$')) + let f=expand('%') + call assert_match('src%nvim%testdir%Xbackup.txt\~', f) + bw! + bw! + call delete('Xbackup.txt') + call delete(f) + set backup&vim backupdir&vim backupcopy&vim +endfunc diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index 9dce87774b..e2016d7927 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -320,6 +320,19 @@ func Test_zz_Numbers() \ ]) endfunc +" Affix flags +func Test_zz_affix_flags() + call LoadAffAndDic(g:test_data_aff10, g:test_data_dic10) + call RunGoodBad("drink drinkable drinkables drinktable drinkabletable", + \ "bad: drinks drinkstable drinkablestable", + \ ["drink", "drinkable", "drinkables", "table"], + \ [['bad', []], + \ ['drinks', ['drink']], + \ ['drinkstable', ['drinktable', 'drinkable', 'drink table']], + \ ['drinkablestable', ['drinkabletable', 'drinkables table', 'drinkable table']], + \ ]) +endfunc + function FirstSpellWord() call feedkeys("/^start:\n", 'tx') normal ]smm @@ -751,6 +764,21 @@ let g:test_data_dic9 = [ \"foo", \"bar", \ ] +let g:test_data_aff10 = [ + \"COMPOUNDRULE se", + \"COMPOUNDPERMITFLAG p", + \"", + \"SFX A Y 1", + \"SFX A 0 able/Mp .", + \"", + \"SFX M Y 1", + \"SFX M 0 s .", + \ ] +let g:test_data_dic10 = [ + \"1234", + \"drink/As", + \"table/e", + \ ] let g:test_data_aff_sal = [ \"SET ISO8859-1", \"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ", diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim index ce527a5e1d..f93af76f17 100644 --- a/src/nvim/testdir/test_tagjump.vim +++ b/src/nvim/testdir/test_tagjump.vim @@ -466,4 +466,28 @@ func Test_tag_line_toolong() let &verbose = old_vbs endfunc +func Test_tagline() + call writefile([ + \ 'provision Xtest.py /^ def provision(self, **kwargs):$/;" m line:1 language:Python class:Foo', + \ 'provision Xtest.py /^ def provision(self, **kwargs):$/;" m line:3 language:Python class:Bar', + \], 'Xtags') + call writefile([ + \ ' def provision(self, **kwargs):', + \ ' pass', + \ ' def provision(self, **kwargs):', + \ ' pass', + \], 'Xtest.py') + + set tags=Xtags + + 1tag provision + call assert_equal(line('.'), 1) + 2tag provision + call assert_equal(line('.'), 3) + + call delete('Xtags') + call delete('Xtest.py') + set tags& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_textobjects.vim b/src/nvim/testdir/test_textobjects.vim index 9194e0014d..448b2dc51c 100644 --- a/src/nvim/testdir/test_textobjects.vim +++ b/src/nvim/testdir/test_textobjects.vim @@ -48,6 +48,9 @@ func Test_quote_selection_selection_exclusive() set selection=exclusive exe "norm! fdvhi'y" call assert_equal('bcde', @") + let @"='dummy' + exe "norm! $gevi'y" + call assert_equal('bcde', @") set selection&vim bw! endfunc diff --git a/src/nvim/version.c b/src/nvim/version.c index db02279fa3..f678b743c2 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -68,9 +68,6 @@ NULL // clang-format off static const int included_patches[] = { - 2218, - 2207, - 2173, 1850, 1849, 1848, @@ -86,7 +83,7 @@ static const int included_patches[] = { 1838, 1837, 1836, - // 1835, + 1835, 1834, 1833, 1832, @@ -123,7 +120,7 @@ static const int included_patches[] = { 1801, 1800, 1799, - // 1798, + 1798, 1797, 1796, 1795, @@ -179,7 +176,7 @@ static const int included_patches[] = { // 1745, // 1744, // 1743, - // 1742, + 1742, 1741, 1740, 1739, @@ -196,10 +193,10 @@ static const int included_patches[] = { 1728, 1727, 1726, - // 1725, + 1725, 1724, 1723, - // 1722, + 1722, 1721, 1720, 1719, @@ -210,7 +207,7 @@ static const int included_patches[] = { 1714, 1713, // 1712, - // 1711, + 1711, 1710, 1709, 1708, @@ -236,7 +233,7 @@ static const int included_patches[] = { 1688, 1687, 1686, - // 1685, + 1685, 1684, 1683, 1682, @@ -255,8 +252,8 @@ static const int included_patches[] = { 1669, // 1668, 1667, - // 1666, - // 1665, + 1666, + 1665, 1664, 1663, 1662, @@ -297,12 +294,12 @@ static const int included_patches[] = { 1627, 1626, 1625, - // 1624, + 1624, 1623, 1622, 1621, 1620, - // 1619, + 1619, 1618, // 1617, // 1616, @@ -339,7 +336,7 @@ static const int included_patches[] = { 1585, 1584, 1583, - // 1582, + 1582, 1581, 1580, 1579, @@ -429,7 +426,7 @@ static const int included_patches[] = { // 1495, 1494, 1493, - // 1492, + 1492, // 1491, 1490, 1489, @@ -470,7 +467,7 @@ static const int included_patches[] = { // 1454, 1453, 1452, - // 1451, + 1451, 1450, // 1449, 1448, @@ -745,7 +742,7 @@ static const int included_patches[] = { 1179, 1178, 1177, - // 1176, + 1176, 1175, 1174, 1173, @@ -987,7 +984,7 @@ static const int included_patches[] = { 937, 936, 935, - // 934, + 934, 933, 932, 931, diff --git a/test/functional/core/fileio_spec.lua b/test/functional/core/fileio_spec.lua index e6bce85b8a..f4c476560d 100644 --- a/test/functional/core/fileio_spec.lua +++ b/test/functional/core/fileio_spec.lua @@ -9,9 +9,12 @@ local nvim_prog = helpers.nvim_prog local request = helpers.request local retry = helpers.retry local rmdir = helpers.rmdir +local mkdir = helpers.mkdir local sleep = helpers.sleep local read_file = helpers.read_file local trim = helpers.trim +local currentdir = helpers.funcs.getcwd +local iswin = helpers.iswin describe('fileio', function() before_each(function() @@ -24,6 +27,7 @@ describe('fileio', function() os.remove('Xtest_startup_file2') os.remove('Xtest_тест.md') rmdir('Xtest_startup_swapdir') + rmdir('Xtest_backupdir') end) it('fsync() codepaths #8304', function() @@ -88,6 +92,27 @@ describe('fileio', function() eq('foo', bar_contents); end) + it('backup with full path #11214', function() + clear() + mkdir('Xtest_backupdir') + command('set backup') + command('set backupdir=Xtest_backupdir//') + command('write Xtest_startup_file1') + feed('ifoo<esc>') + command('write') + feed('Abar<esc>') + command('write') + + -- Backup filename = fullpath, separators replaced with "%". + local backup_file_name = string.gsub(currentdir()..'/Xtest_startup_file1', + iswin() and '[:/\\]' or '/', '%%') .. '~' + local foo_contents = trim(read_file('Xtest_backupdir/'..backup_file_name)) + local foobar_contents = trim(read_file('Xtest_startup_file1')) + + eq('foobar', foobar_contents); + eq('foo', foo_contents); + end) + it('readfile() on multibyte filename #10586', function() clear() local text = { diff --git a/test/functional/fixtures/lsp-test-rpc-server.lua b/test/functional/fixtures/lsp-test-rpc-server.lua index 971e61b072..798883ced0 100644 --- a/test/functional/fixtures/lsp-test-rpc-server.lua +++ b/test/functional/fixtures/lsp-test-rpc-server.lua @@ -170,7 +170,7 @@ function tests.basic_check_buffer_open() expect_notification('textDocument/didOpen', { textDocument = { languageId = ""; - text = table.concat({"testing"; "123"}, "\n"); + text = table.concat({"testing"; "123"}, "\n") .. '\n'; uri = "file://"; version = 0; }; @@ -197,6 +197,42 @@ function tests.basic_check_buffer_open_and_change() expect_notification('textDocument/didOpen', { textDocument = { languageId = ""; + text = table.concat({"testing"; "123"}, "\n") .. '\n'; + uri = "file://"; + version = 0; + }; + }) + expect_notification('textDocument/didChange', { + textDocument = { + uri = "file://"; + version = 3; + }; + contentChanges = { + { text = table.concat({"testing"; "boop"}, "\n") .. '\n'; }; + } + }) + expect_notification("finish") + notify('finish') + end; + } +end + +function tests.basic_check_buffer_open_and_change_noeol() + skeleton { + on_init = function(params) + local expected_capabilities = protocol.make_client_capabilities() + assert_eq(params.capabilities, expected_capabilities) + return { + capabilities = { + textDocumentSync = protocol.TextDocumentSyncKind.Full; + } + } + end; + body = function() + notify('start') + expect_notification('textDocument/didOpen', { + textDocument = { + languageId = ""; text = table.concat({"testing"; "123"}, "\n"); uri = "file://"; version = 0; @@ -216,7 +252,6 @@ function tests.basic_check_buffer_open_and_change() end; } end - function tests.basic_check_buffer_open_and_change_multi() skeleton { on_init = function(params) @@ -233,7 +268,7 @@ function tests.basic_check_buffer_open_and_change_multi() expect_notification('textDocument/didOpen', { textDocument = { languageId = ""; - text = table.concat({"testing"; "123"}, "\n"); + text = table.concat({"testing"; "123"}, "\n") .. '\n'; uri = "file://"; version = 0; }; @@ -244,7 +279,7 @@ function tests.basic_check_buffer_open_and_change_multi() version = 3; }; contentChanges = { - { text = table.concat({"testing"; "321"}, "\n"); }; + { text = table.concat({"testing"; "321"}, "\n") .. '\n'; }; } }) expect_notification('textDocument/didChange', { @@ -253,7 +288,7 @@ function tests.basic_check_buffer_open_and_change_multi() version = 4; }; contentChanges = { - { text = table.concat({"testing"; "boop"}, "\n"); }; + { text = table.concat({"testing"; "boop"}, "\n") .. '\n'; }; } }) expect_notification("finish") @@ -278,7 +313,7 @@ function tests.basic_check_buffer_open_and_change_multi_and_close() expect_notification('textDocument/didOpen', { textDocument = { languageId = ""; - text = table.concat({"testing"; "123"}, "\n"); + text = table.concat({"testing"; "123"}, "\n") .. '\n'; uri = "file://"; version = 0; }; @@ -289,7 +324,7 @@ function tests.basic_check_buffer_open_and_change_multi_and_close() version = 3; }; contentChanges = { - { text = table.concat({"testing"; "321"}, "\n"); }; + { text = table.concat({"testing"; "321"}, "\n") .. '\n'; }; } }) expect_notification('textDocument/didChange', { @@ -298,7 +333,7 @@ function tests.basic_check_buffer_open_and_change_multi_and_close() version = 4; }; contentChanges = { - { text = table.concat({"testing"; "boop"}, "\n"); }; + { text = table.concat({"testing"; "boop"}, "\n") .. '\n'; }; } }) expect_notification('textDocument/didClose', { @@ -328,7 +363,7 @@ function tests.basic_check_buffer_open_and_change_incremental() expect_notification('textDocument/didOpen', { textDocument = { languageId = ""; - text = table.concat({"testing"; "123"}, "\n"); + text = table.concat({"testing"; "123"}, "\n") .. '\n'; uri = "file://"; version = 0; }; diff --git a/test/functional/plugin/lsp/lsp_spec.lua b/test/functional/plugin/lsp/lsp_spec.lua index cd0974b81c..c38c9b72ce 100644 --- a/test/functional/plugin/lsp/lsp_spec.lua +++ b/test/functional/plugin/lsp/lsp_spec.lua @@ -410,6 +410,54 @@ describe('Language Client API', function() } end) + it('should check the body and didChange full with noeol', function() + local expected_callbacks = { + {NIL, "shutdown", {}, 1}; + {NIL, "finish", {}, 1}; + {NIL, "start", {}, 1}; + } + local client + test_rpc_server { + test_name = "basic_check_buffer_open_and_change_noeol"; + on_setup = function() + exec_lua [[ + BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { + "testing"; + "123"; + }) + vim.api.nvim_buf_set_option(BUFFER, 'eol', false) + ]] + end; + on_init = function(_client) + client = _client + local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") + eq(full_kind, client.resolved_capabilities().text_document_did_change) + eq(true, client.resolved_capabilities().text_document_open_close) + exec_lua [[ + assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) + ]] + end; + on_exit = function(code, signal) + eq(0, code, "exit code") eq(0, signal, "exit signal") + end; + on_callback = function(err, method, params, client_id) + if method == 'start' then + exec_lua [[ + vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { + "boop"; + }) + ]] + client.notify('finish') + end + eq(table.remove(expected_callbacks), {err, method, params, client_id}, "expected callback") + if method == 'finish' then + client.stop() + end + end; + } + end) + -- TODO(askhan) we don't support full for now, so we can disable these tests. pending('should check the body and didChange incremental', function() local expected_callbacks = { diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index fdeb7f342f..7465d037ee 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -153,8 +153,8 @@ set(LUAJIT_SHA256 ad5077bd861241bf5e50ae4bf543d291c5fcffab95ccc3218401131f503e45 set(LUA_URL https://www.lua.org/ftp/lua-5.1.5.tar.gz) set(LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333) -set(LUAROCKS_URL https://github.com/luarocks/luarocks/archive/v2.4.4.tar.gz) -set(LUAROCKS_SHA256 9eb3d0738fd02ad8bf39bcedccac4e83e9b5fff2bcca247c3584b925b2075d9c) +set(LUAROCKS_URL https://github.com/luarocks/luarocks/archive/v3.2.1.tar.gz) +set(LUAROCKS_SHA256 0cab9f79311083f33e4d8f5a76021604f1d3f7141ce9a2ef1d8b717d92058370) set(UNIBILIUM_URL https://github.com/neovim/unibilium/archive/92d929f.tar.gz) set(UNIBILIUM_SHA256 29815283c654277ef77a3adcc8840db79ddbb20a0f0b0c8f648bd8cd49a02e4b) diff --git a/third-party/cmake/BuildLuarocks.cmake b/third-party/cmake/BuildLuarocks.cmake index 7551c52ecc..4b1b94a46b 100644 --- a/third-party/cmake/BuildLuarocks.cmake +++ b/third-party/cmake/BuildLuarocks.cmake @@ -52,13 +52,17 @@ if(NOT MSVC) set(LUAROCKS_BUILDARGS CC=${HOSTDEPS_C_COMPILER} LD=${HOSTDEPS_C_COMPILER}) endif() +# Lua version, used with rocks directories. +# Defaults to 5.1 for bundled LuaJIT/Lua. +set(LUA_VERSION "5.1") + if(UNIX OR (MINGW AND CMAKE_CROSSCOMPILING)) if(USE_BUNDLED_LUAJIT) list(APPEND LUAROCKS_OPTS --with-lua=${HOSTDEPS_INSTALL_DIR} --with-lua-include=${HOSTDEPS_INSTALL_DIR}/include/luajit-2.1 - --lua-suffix=jit) + --with-lua-interpreter=luajit) elseif(USE_BUNDLED_LUA) list(APPEND LUAROCKS_OPTS --with-lua=${HOSTDEPS_INSTALL_DIR}) @@ -66,9 +70,23 @@ if(UNIX OR (MINGW AND CMAKE_CROSSCOMPILING)) find_package(LuaJit) if(LUAJIT_FOUND) list(APPEND LUAROCKS_OPTS - --lua-version=5.1 --with-lua-include=${LUAJIT_INCLUDE_DIRS} - --lua-suffix=jit) + --with-lua-interpreter=luajit) + endif() + + # Get LUA_VERSION used with rocks output. + if(LUAJIT_FOUND) + set(LUA_EXE "luajit") + else() + set(LUA_EXE "lua") + endif() + execute_process( + COMMAND ${LUA_EXE} -e "print(string.sub(_VERSION, 5))" + OUTPUT_VARIABLE LUA_VERSION + ERROR_VARIABLE ERR + RESULT_VARIABLE RES) + if(NOT RES EQUAL 0) + message(FATAL_ERROR "Could not get LUA_VERSION with ${LUA_EXE}: ${ERR}") endif() endif() @@ -111,7 +129,7 @@ if(USE_BUNDLED_LUAJIT) elseif(USE_BUNDLED_LUA) add_dependencies(luarocks lua) endif() -set(ROCKS_DIR ${HOSTDEPS_LIB_DIR}/luarocks/rocks) +set(ROCKS_DIR ${HOSTDEPS_LIB_DIR}/luarocks/rocks-${LUA_VERSION}) # mpack add_custom_command(OUTPUT ${ROCKS_DIR}/mpack |