diff options
108 files changed, 2262 insertions, 1433 deletions
diff --git a/.editorconfig b/.editorconfig index ac902ecec5..c0b80fdf47 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,10 +7,6 @@ end_of_line = lf insert_final_newline = true charset = utf_8 -[runtime/doc/*.txt] +[{Makefile,**/Makefile,runtime/doc/*.txt}] indent_style = tab indent_size = 8 - -[Makefile] -indent_style = tab -tab_width = 4 diff --git a/busted/outputHandlers/TAP.lua b/busted/outputHandlers/TAP.lua index ff93a7cc75..612e633576 100644 --- a/busted/outputHandlers/TAP.lua +++ b/busted/outputHandlers/TAP.lua @@ -1,7 +1,8 @@ --- TODO(jkeyes): remove this and use the upstream version as soon as it is --- available in a release of busted. +-- TODO(jkeyes): Use the upstream version when busted releases it. (But how to +-- inject our call to global_helpers.read_nvim_log() ?) local pretty = require 'pl.pretty' +local global_helpers = require('test.helpers') return function(options) local busted = require 'busted' @@ -18,6 +19,7 @@ return function(options) end handler.suiteEnd = function() + io.write(global_helpers.read_nvim_log()) print('1..' .. counter) io.flush() return nil, true diff --git a/busted/outputHandlers/nvim.lua b/busted/outputHandlers/nvim.lua index b612ead070..d137300a7e 100644 --- a/busted/outputHandlers/nvim.lua +++ b/busted/outputHandlers/nvim.lua @@ -1,6 +1,7 @@ local s = require 'say' local pretty = require 'pl.pretty' local term = require 'term' +local global_helpers = require('test.helpers') local colors @@ -200,6 +201,7 @@ return function(options) local tests = (testCount == 1 and 'test' or 'tests') local files = (fileCount == 1 and 'file' or 'files') io.write(globalTeardown) + io.write(global_helpers.read_nvim_log()) io.write(suiteEndString:format(testCount, tests, fileCount, files, elapsedTime_ms)) io.write(getSummaryString()) io.flush() diff --git a/ci/common/build.sh b/ci/common/build.sh index f4313578c9..7c27d61586 100644 --- a/ci/common/build.sh +++ b/ci/common/build.sh @@ -7,7 +7,7 @@ _stat() { } top_make() { - echo '================================================================================' + printf '%78s\n' | tr ' ' '=' # Travis has 1.5 virtual cores according to: # http://docs.travis-ci.com/user/speeding-up-the-build/#Paralellizing-your-build-on-one-VM ninja "$@" diff --git a/ci/common/suite.sh b/ci/common/suite.sh index d3fbcd1eda..8feb642547 100644 --- a/ci/common/suite.sh +++ b/ci/common/suite.sh @@ -39,13 +39,6 @@ enter_suite() { exit_suite() { set +x - if test -f "$NVIM_LOG_FILE" ; then - printf "===============================================================================\n" - printf "NVIM_LOG_FILE: $NVIM_LOG_FILE\n" - cat "$NVIM_LOG_FILE" 2>/dev/null || printf '(empty)' - printf "\n" - rm -rf "$NVIM_LOG_FILE" - fi travis_fold end "${NVIM_TEST_CURRENT_SUITE}" if test $FAILED -ne 0 ; then echo "Suite ${NVIM_TEST_CURRENT_SUITE} failed, summary:" diff --git a/codecov.yml b/codecov.yml index 481eea89ee..dbcb0c16ca 100644 --- a/codecov.yml +++ b/codecov.yml @@ -14,7 +14,7 @@ coverage: status: project: yes patch: yes - changes: no + changes: yes parsers: gcov: diff --git a/runtime/autoload/health/nvim.vim b/runtime/autoload/health/nvim.vim index d09a714719..93660d05dc 100644 --- a/runtime/autoload/health/nvim.vim +++ b/runtime/autoload/health/nvim.vim @@ -4,6 +4,14 @@ function! s:check_config() abort let ok = v:true call health#report_start('Configuration') + let vimrc = empty($MYVIMRC) ? stdpath('config').'/init.vim' : $MYVIMRC + if !filereadable(vimrc) + let ok = v:false + let has_vim = filereadable(expand('~/.vimrc')) + call health#report_warn('Missing user config file: '.vimrc, + \[ has_vim ? ':help nvim-from-vim' : ':help init.vim' ]) + endif + " If $VIM is empty we don't care. Else make sure it is valid. if !empty($VIM) && !filereadable($VIM.'/runtime/doc/nvim.txt') let ok = v:false diff --git a/runtime/autoload/remote/host.vim b/runtime/autoload/remote/host.vim index dfaab7d246..6266b312bd 100644 --- a/runtime/autoload/remote/host.vim +++ b/runtime/autoload/remote/host.vim @@ -114,7 +114,7 @@ function! s:RegistrationCommands(host) abort let host_id = a:host.'-registration-clone' call remote#host#RegisterClone(host_id, a:host) let pattern = s:plugin_patterns[a:host] - let paths = globpath(&rtp, 'rplugin/'.a:host.'/'.pattern, 0, 1) + let paths = globpath(&rtp, 'rplugin/'.a:host.'/'.pattern, 1, 1) let paths = map(paths, 'tr(resolve(v:val),"\\","/")') " Normalize slashes #4795 let paths = uniq(sort(paths)) if empty(paths) diff --git a/runtime/doc/Makefile b/runtime/doc/Makefile index dabbcd08d9..18d32c0820 100644 --- a/runtime/doc/Makefile +++ b/runtime/doc/Makefile @@ -13,7 +13,7 @@ HTMLS = $(DOCS:.txt=.html) .SUFFIXES: .c .o .txt .html # Awk version of .txt to .html conversion. -html: noerrors $(HTMLS) +html: noerrors vimindex.html $(HTMLS) @if test -f errors.log; then cat errors.log; fi noerrors: diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index c260101e92..9fabc1cf8b 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -85,7 +85,7 @@ Buffer update events *api-buffer-updates* API clients can "attach" to Nvim buffers to subscribe to buffer update events. This is similar to |TextChanged| but more powerful and granular. -Call |nvim_buf_attach| to receive these events on the channel: +Call |nvim_buf_attach()| to receive these events on the channel: *nvim_buf_lines_event* nvim_buf_lines_event[{buf}, {changedtick}, {firstline}, {lastline}, {linedata}, {more}] @@ -150,7 +150,7 @@ nvim_buf_detach_event[{buf}] *nvim_buf_detach_event* EXAMPLE ~ -Calling |nvim_buf_attach| with send_buffer=true on an empty buffer, emits: > +Calling |nvim_buf_attach()| with send_buffer=true on an empty buffer, emits: > nvim_buf_lines_event[{buf}, {changedtick}, 0, 0, [""], v:false] User adds two lines to the buffer, emits: > @@ -189,12 +189,11 @@ Another use case are plugins that show output in an append-only buffer, and want to add highlights to the outputs. Highlight data cannot be preserved on writing and loading a buffer to file, nor in undo/redo cycles. -Highlights are registered using the |nvim_buf_add_highlight| function, see the -generated API documentation for details. If an external highlighter plugin is -adding a large number of highlights in a batch, performance can be improved by -calling |nvim_buf_add_highlight| as an asynchronous notification, after first -(synchronously) reqesting a source id. Here is an example using wrapper -functions in the python client: +Highlights are registered using the |nvim_buf_add_highlight()| function. If an +external highlighter plugin wants to add many highlights in a batch, +performance can be improved by calling |nvim_buf_add_highlight()| as an +asynchronous notification, after first (synchronously) reqesting a source id. +Example using the Nvim python-client: > src = vim.new_highlight_source() @@ -207,10 +206,10 @@ functions in the python client: buf.clear_highlight(src) < If the highlights don't need to be deleted or updated, just pass -1 as -src_id (this is the default in python). |nvim_buf_clear_highlight| can be used -to clear highlights from a specific source, in a specific line range or the -entire buffer by passing in the line range 0, -1 (the latter is the default -in python as used above). +src_id (this is the default in python). Use |nvim_buf_clear_highlight()| to +clear highlights from a specific source, in a specific line range or the +entire buffer by passing in the line range 0, -1 (the latter is the default in +python as used above). An example of calling the api from vimscript: > @@ -655,14 +654,14 @@ nvim_get_chan_info({chan}) *nvim_get_chan_info()* stderr of this Nvim instance "socket" TCP/IP socket or named pipe "job" job with communication over its stdio - "mode" how data received on the channel is interpreted "bytes" send and recieve raw bytes "terminal" a |terminal| instance interprets ASCII sequences "rpc" |RPC| communication on the channel is active "pty" Name of pseudoterminal, if one is used (optional). On a POSIX system, this will be a device path like /dev/pts/1. Even if the name is unknown, the key will still be present to indicate a pty is used. This is currently the case when using winpty on windows. "buffer" buffer with connected |terminal| instance (optional) "client" information about the client on the other end of the RPC channel, if it has added it using |nvim_set_client_info|. (optional) + "mode" how data received on the channel is interpreted "bytes" send and recieve raw bytes "terminal" a |terminal| instance interprets ASCII sequences "rpc" |RPC| communication on the channel is active "pty" Name of pseudoterminal, if one is used (optional). On a POSIX system, this will be a device path like /dev/pts/1. Even if the name is unknown, the key will still be present to indicate a pty is used. This is currently the case when using winpty on windows. "buffer" buffer with connected |terminal| instance (optional) "client" information about the client on the other end of the RPC channel, if it has added it using |nvim_set_client_info()|. (optional) nvim_list_chans() *nvim_list_chans()* Get information about all open channels. Return: ~ Array of Dictionaries, each describing a channel with the - format specified at |nvim_get_chan_info|. + format specified at |nvim_get_chan_info()|. nvim_call_atomic({calls}) *nvim_call_atomic()* Calls many API methods atomically. diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 5b2e226fbb..24bcb13e6e 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -4,7 +4,7 @@ VIM REFERENCE MANUAL by Bram Moolenaar -Automatic commands *autocommand* +Automatic commands *autocmd* *autocommand* For a basic explanation, see section |40.3| in the user manual. @@ -494,14 +494,14 @@ ChanInfo State of channel changed, for instance the client of a RPC channel described itself. Sets these |v:event| keys: info - See |nvim_get_chan_info| for the format of the - info Dictionary. + See |nvim_get_chan_info()| for the format of + the info Dictionary. *ChanOpen* ChanOpen Just after a channel was opened. Sets these |v:event| keys: info - See |nvim_get_chan_info| for the format of the - info Dictionary. + See |nvim_get_chan_info()| for the format of + the info Dictionary. *CmdUndefined* CmdUndefined When a user command is used but it isn't defined. Useful for defining a command only diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt index 9610d7359f..f3dd0bd6df 100644 --- a/runtime/doc/change.txt +++ b/runtime/doc/change.txt @@ -1135,7 +1135,7 @@ There are ten types of registers: *registers* *E354* 5. three read-only registers ":, "., "% 6. alternate buffer register "# 7. the expression register "= -8. The selection and drop registers "*, "+ and "~ +8. The selection registers "* and "+ 9. The black hole register "_ 10. Last search pattern register "/ @@ -1236,7 +1236,7 @@ If the "= register is used for the "p" command, the String is split up at <NL> characters. If the String ends in a <NL>, it is regarded as a linewise register. -8. Selection and drop registers "*, "+ and "~ +8. Selection registers "* and "+ Use these registers for storing and retrieving the selected text for the GUI. See |quotestar| and |quoteplus|. When the clipboard is not available or not working, the unnamed register is used instead. For Unix systems and Mac OS X, diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt index c46a9fc2d8..e72799c65b 100644 --- a/runtime/doc/cmdline.txt +++ b/runtime/doc/cmdline.txt @@ -4,8 +4,8 @@ VIM REFERENCE MANUAL by Bram Moolenaar - *Cmdline-mode* *Command-line-mode* -Command-line mode *Cmdline* *Command-line* *mode-cmdline* *:* + *Cmdline-mode* *Command-line-mode* *Cmdline* +Command-line mode *cmdline* *Command-line* *mode-cmdline* *:* Command-line mode is used to enter Ex commands (":"), search patterns ("/" and "?"), and filter commands ("!"). @@ -163,12 +163,14 @@ CTRL-R CTRL-F *c_CTRL-R_CTRL-F* *c_<C-R>_<C-F>* CTRL-R CTRL-P *c_CTRL-R_CTRL-P* *c_<C-R>_<C-P>* CTRL-R CTRL-W *c_CTRL-R_CTRL-W* *c_<C-R>_<C-W>* CTRL-R CTRL-A *c_CTRL-R_CTRL-A* *c_<C-R>_<C-A>* +CTRL-R CTRL-L *c_CTRL-R_CTRL-L* *c_<C-R>_<C-L>* Insert the object under the cursor: CTRL-F the Filename under the cursor CTRL-P the Filename under the cursor, expanded with 'path' as in |gf| CTRL-W the Word under the cursor CTRL-A the WORD under the cursor; see |WORD| + CTRL-L the line under the cursor When 'incsearch' is set the cursor position at the end of the currently displayed match is used. With CTRL-W the part of @@ -176,8 +178,8 @@ CTRL-R CTRL-A *c_CTRL-R_CTRL-A* *c_<C-R>_<C-A>* *c_CTRL-R_CTRL-R* *c_<C-R>_<C-R>* *c_CTRL-R_CTRL-O* *c_<C-R>_<C-O>* -CTRL-R CTRL-R {0-9a-z"%#:-=. CTRL-F CTRL-P CTRL-W CTRL-A} -CTRL-R CTRL-O {0-9a-z"%#:-=. CTRL-F CTRL-P CTRL-W CTRL-A} +CTRL-R CTRL-R {0-9a-z"%#:-=. CTRL-F CTRL-P CTRL-W CTRL-A CTRL-L} +CTRL-R CTRL-O {0-9a-z"%#:-=. CTRL-F CTRL-P CTRL-W CTRL-A CTRL-L} Insert register or object under the cursor. Works like |c_CTRL-R| but inserts the text literally. For example, if register a contains "xy^Hz" (where ^H is a backspace), @@ -786,6 +788,11 @@ Also see |`=|. Note: these are typed literally, they are not special keys! <cword> is replaced with the word under the cursor (like |star|) <cWORD> is replaced with the WORD under the cursor (see |WORD|) + <cexpr> is replaced with the word under the cursor, including more + to form a C expression. E.g., when the cursor is on "arg" + of "ptr->arg" then the result is "ptr->arg"; when the + cursor is on "]" of "list[idx]" then the result is + "list[idx]". This is used for |v:beval_text|. <cfile> is replaced with the path name under the cursor (like what |gf| uses) <afile> When executing autocommands, is replaced with the file name diff --git a/runtime/doc/diff.txt b/runtime/doc/diff.txt index c6c827a748..50b53bcd3e 100644 --- a/runtime/doc/diff.txt +++ b/runtime/doc/diff.txt @@ -5,7 +5,7 @@ *diff* *diff-mode* -This file describes the |+diff| feature: Showing differences between two to +This file describes the diff feature: Showing differences between two to eight versions of the same file. The basics are explained in section |08.7| of the user manual. diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt index 5939cb8a8b..5aa09503cd 100644 --- a/runtime/doc/editing.txt +++ b/runtime/doc/editing.txt @@ -1225,9 +1225,6 @@ working directory, which in turn takes precedence over the global working directory. If a local working directory (tab or window) does not exist, the next-higher scope in the hierarchy applies. -Commands for changing the working directory can be suffixed with a bang "!" -(e.g. |:cd!|) which is ignored, for compatibility with Vim. - *:cd* *E747* *E472* :cd[!] On non-Unix systems: Print the current directory name. On Unix systems: Change the current directory diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index cfc3b70443..e51ecbd688 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1528,7 +1528,7 @@ v:event Dictionary of event data for the current |autocommand|. Valid KEY DESCRIPTION ~ abort Whether the event triggered during an aborting condition (e.g. |c_Esc| or - |c_CTRL-c| for |CmdlineLeave|). + |c_CTRL-C| for |CmdlineLeave|). cmdlevel Level of cmdline. cmdtype Type of cmdline, |cmdline-char|. cwd Current working directory. @@ -2352,6 +2352,7 @@ win_getid([{win} [, {tab}]]) Number get |window-ID| for {win} in {tab} win_gotoid({expr}) Number go to |window-ID| {expr} win_id2tabwin({expr}) List get tab and window nr from |window-ID| win_id2win({expr}) Number get window nr from |window-ID| +win_screenpos({nr}) List get screen position of window {nr} winbufnr({nr}) Number buffer number of window {nr} wincol() Number window column of the cursor winheight({nr}) Number height of window {nr} @@ -3229,8 +3230,7 @@ executable({expr}) *executable()* On Windows it only checks if the file exists and is not a directory, not if it's really executable. On Windows an executable in the same directory as Vim is - always found. Since this directory is added to $PATH it - should also work to execute it |win32-PATH|. + always found (it is added to $PATH at |startup|). The result is a Number: 1 exists 0 does not exist @@ -4079,6 +4079,7 @@ getcompletion({pat}, {type} [, {filtered}]) *getcompletion()* specifies what for. The following completion types are supported: + arglist file names in argument list augroup autocmd groups buffer buffer names behave :behave suboptions @@ -4099,6 +4100,7 @@ getcompletion({pat}, {type} [, {filtered}]) *getcompletion()* highlight highlight groups history :history suboptions locale locale names (as output of locale -a) + mapclear buffer argument mapping mapping name menu menus messages |:messages| suboptions @@ -4471,8 +4473,10 @@ getwininfo([{winid}]) *getwininfo()* variables a reference to the dictionary with window-local variables width window width + wincol leftmost screen column of the window winid |window-ID| winnr window number + winrow topmost screen column of the window To obtain all window-local variables use: > gettabwinvar({tabnr}, {winnr}, '&') @@ -4804,7 +4808,7 @@ input({opts}) where hl_start_col is the first highlighted column, hl_end_col is the last highlighted column (+ 1!), - hl_group is |:hl| group used for highlighting. + hl_group is |:hi| group used for highlighting. *E5403* *E5404* *E5405* *E5406* Both hl_start_col and hl_end_col + 1 must point to the start of the multibyte character (highlighting must not break @@ -5009,7 +5013,7 @@ jobstart({cmd}[, {opts}]) *jobstart()* was used) to send data to stdin and |chanclose()| to close stdio streams without stopping the job explicitly. - See |job-control| and |rpc|. + See |job-control| and |RPC|. NOTE: on Windows if {cmd} is a List: - cmd[0] must be an executable (not a "built-in"). If it is @@ -5051,7 +5055,7 @@ jobstart({cmd}[, {opts}]) *jobstart()* - The channel ID on success - 0 on invalid arguments - -1 if {cmd}[0] is not executable. - See |job-control|, |channels|, and |msgpack-rpc| for more information. + See also |job-control|, |channel|, |msgpack-rpc|. jobstop({id}) *jobstop()* Stop |job-id| {id} by sending SIGTERM to the job process. If @@ -6385,11 +6389,9 @@ rpcstart({prog}[, {argv}]) {Nvim} *rpcstart()* :let id = jobstart(['prog', 'arg1', 'arg2'], {'rpc': v:true}) rpcstop({channel}) {Nvim} *rpcstop()* - Deprecated. This function was used to stop a job with |rpc| - channel, and additionally closed rpc sockets. Instead use - |jobstop()| to stop any job, and |chanclose|(id, "rpc") to close - rpc communication without stopping the job. Use |chanclose|(id) - to close any socket. + Deprecated. Instead use |jobstop()| to stop any job, and + chanclose(id, "rpc") to close RPC communication without + stopping the job. Use chanclose(id) to close any socket. screenattr({row}, {col}) *screenattr()* Like |screenchar()|, but return the attribute. This is a rather @@ -8181,6 +8183,14 @@ win_id2win({expr}) *win_id2win()* Return the window number of window with ID {expr}. Return 0 if the window cannot be found in the current tabpage. +win_screenpos({nr}) *win_screenpos()* + Return the screen position of window {nr} as a list with two + numbers: [row, col]. The first window always has position + [1, 1]. + {nr} can be the window number or the |window-ID|. + Return [0, 0] if the window cannot be found in the current + tabpage. + *winbufnr()* winbufnr({nr}) The result is a Number, which is the number of the buffer associated with window {nr}. {nr} can be the window number or @@ -8875,9 +8885,6 @@ Also note that if you have two script files, and one calls a function in the other and vice versa, before the used function is defined, it won't work. Avoid using the autoload functionality at the toplevel. -Hint: If you distribute a bunch of scripts you can pack them together with the -|vimball| utility. Also read the user manual |distribute-script|. - ============================================================================== 6. Curly braces names *curly-braces-names* @@ -10749,7 +10756,7 @@ Group Default link Colored expression ~ |expr-entry| *hl-NvimColon* Delimiter `:` in |dict| literal -*hl-NvimComma* Delimiter `,` in |dict|/|list| +*hl-NvimComma* Delimiter `,` in |dict| or |list| literal or |expr-function| *hl-NvimArrow* Delimiter `->` in |lambda| diff --git a/runtime/doc/if_pyth.txt b/runtime/doc/if_pyth.txt index 8940e69092..6769dd87e8 100644 --- a/runtime/doc/if_pyth.txt +++ b/runtime/doc/if_pyth.txt @@ -4,7 +4,7 @@ VIM REFERENCE MANUAL by Paul Moore -The Python Interface to Vim *python* *Python* +The Python Interface to Vim *if_pyth* *python* *Python* See |provider-python| for more information. {Nvim} diff --git a/runtime/doc/if_ruby.txt b/runtime/doc/if_ruby.txt index ace304c083..d9dbc685d7 100644 --- a/runtime/doc/if_ruby.txt +++ b/runtime/doc/if_ruby.txt @@ -3,7 +3,7 @@ VIM REFERENCE MANUAL by Shugo Maeda -The Ruby Interface to Vim *ruby* *Ruby* +The Ruby Interface to Vim *if_ruby* *ruby* *Ruby* *E266* *E267* *E268* *E269* *E270* *E271* *E272* *E273* diff --git a/runtime/doc/intro.txt b/runtime/doc/intro.txt index 5c63d9e5e2..443cce98e1 100644 --- a/runtime/doc/intro.txt +++ b/runtime/doc/intro.txt @@ -439,7 +439,7 @@ notation meaning equivalent decimal value(s) ~ <k0> - <k9> keypad 0 to 9 *keypad-0* *keypad-9* <S-...> shift-key *shift* *<S-* <C-...> control-key *control* *ctrl* *<C-* -<M-...> alt-key or meta-key *META* *meta* *alt* *<M-* +<M-...> alt-key or meta-key *META* *ALT* *<M-* <A-...> same as <M-...> *<A-* <D-...> command-key or "super" key *<D-* ----------------------------------------------------------------------- diff --git a/runtime/doc/makehtml.awk b/runtime/doc/makehtml.awk index 216bb5fac1..6e93c01c54 100644 --- a/runtime/doc/makehtml.awk +++ b/runtime/doc/makehtml.awk @@ -135,11 +135,11 @@ NR == 1 { nf=split(FILENAME,f,".") # common case - Latin1 print "<META HTTP-EQUIV=\"Content-type\" content=\"text/html; charset=ISO-8859-1\">"; } - print "<TITLE>Vim documentation: " f[1] "</TITLE>"; + print "<TITLE>Nvim documentation: " f[1] "</TITLE>"; print "</HEAD>"; print "<BODY BGCOLOR=\"#ffffff\">"; - print "<H1>Vim documentation: " f[1] "</H1>"; + print "<H1>Nvim documentation: " f[1] "</H1>"; print "<A NAME=\"top\"></A>"; if ( FILENAME != "help.txt" ) { print "<A HREF=\"index.html\">main help file</A>\n"; diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt index 804b7410f6..d630bf5652 100644 --- a/runtime/doc/map.txt +++ b/runtime/doc/map.txt @@ -1208,6 +1208,7 @@ By default, the arguments of user defined commands do not undergo completion. However, by specifying one or the other of the following attributes, argument completion can be enabled: + -complete=arglist file names in argument list -complete=augroup autocmd groups -complete=buffer buffer names -complete=behave :behave suboptions @@ -1227,6 +1228,7 @@ completion can be enabled: -complete=highlight highlight groups -complete=history :history suboptions -complete=locale locale names (as output of locale -a) + -complete=mapclear buffer argument -complete=mapping mapping name -complete=menu menus -complete=messages |:messages| suboptions diff --git a/runtime/doc/msgpack_rpc.txt b/runtime/doc/msgpack_rpc.txt index a29569f049..862aa7b750 100644 --- a/runtime/doc/msgpack_rpc.txt +++ b/runtime/doc/msgpack_rpc.txt @@ -61,18 +61,15 @@ To get a formatted dump of the API using python (requires the `pyyaml` and 3. Connecting *rpc-connecting* See |channel-intro|, for various ways to open a channel. Most of the channel -opening functions take an `rpc` key in the options dictionary, to enable rpc. +opening functions take an `rpc` key in the options dictionary, to enable RPC. -Additionally, rpc channels can be opened by other processes connecting to +Additionally, RPC channels can be opened by other processes connecting to TCP/IP sockets or named pipes listened to by nvim. -An rpc socket is automatically created with each instance. The socket - location is stored in |v:servername|. By default this is a named pipe -with an automatically generated address. See |XXX|. - -To make Nvim listen on a TCP/IP socket instead, specify |--listen|: > +Nvim creates a default RPC socket at |startup|, given by |v:servername|. To +start with a TCP/IP socket instead, use |--listen| with a TCP-style address: > nvim --listen 127.0.0.1:6666 -<Also, more sockets and named pipes can be listened on using |serverstart()|. +Additional sockets and named pipes can be started with |serverstart()|. Note that localhost TCP sockets are generally less secure than named pipes, and can lead to vunerabilities like remote code execution. diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index cdec599a74..fe2fbfc039 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -728,13 +728,6 @@ A jump table for the options with a short description can be found at |Q_op|. < Vim will guess the value. In the GUI this should work correctly, in other cases Vim might not be able to guess the right value. - When the |t_RB| option is set, Vim will use it to request the background - color from the terminal. If the returned RGB value is dark/light and - 'background' is not dark/light, 'background' will be set and the - screen is redrawn. This may have side effects, make t_BG empty in - your .vimrc if you suspect this problem. The response to |t_RB| can - be found in |v:termrbgresp|. - When starting the GUI, the default value for 'background' will be "light". When the value is not set in the gvimrc, and Vim detects that the background is actually quite dark, 'background' is set to @@ -1216,8 +1209,9 @@ A jump table for the options with a short description can be found at |Q_op|. *'channel'* 'channel' number (default: 0) local to buffer - |Channel| connected to the buffer. Currently only used by - |terminal-emulator|. Is 0 if no terminal is open. Cannot be changed. + |channel| connected to the buffer, or 0 if no channel is connected. + In a |:terminal| buffer this is the terminal channel. + Read-only. *'charconvert'* *'ccv'* *E202* *E214* *E513* 'charconvert' 'ccv' string (default "") @@ -2041,8 +2035,8 @@ A jump table for the options with a short description can be found at |Q_op|. instead of using ^C and ~C. msgsep When showing messages longer than 'cmdheight', only scroll the message lines, not the entire screen. The - separator line is decorated by |MsgSeparator| and the - "msgsep" flag of 'fillchars'. + separator line is decorated by |hl-MsgSeparator| and + the "msgsep" flag of 'fillchars'. When neither "lastline" nor "truncate" is included, a last line that doesn't fit is replaced with "@" lines. @@ -6767,8 +6761,8 @@ A jump table for the options with a short description can be found at |Q_op|. syntax highlighting (use |:ownsyntax| for that). Highlights of vertical separators are determined by the window to the - left of the separator. The highlight of a tabpage in |tabline| is - determined by the last-focused window of the tabpage. Highlights of + left of the separator. The 'tabline' highlight of a tabpage is + decided by the last-focused window of the tabpage. Highlights of the popupmenu are determined by the current window. Highlights in the message area cannot be overridden. diff --git a/runtime/doc/pi_msgpack.txt b/runtime/doc/pi_msgpack.txt index e9f77e70b0..2484a13878 100644 --- a/runtime/doc/pi_msgpack.txt +++ b/runtime/doc/pi_msgpack.txt @@ -49,7 +49,7 @@ does not check whether argument matches its description. *{msgpack-value}* Either |msgpack-special-dict| or a regular value, but not function reference. -*{msgpack-integer}* Any value for which |msgpack#type| will return +*{msgpack-integer}* Any value for which |msgpack#type()| will return "integer". *{msgpack-special-int}* |msgpack-special-dict| representing integer. diff --git a/runtime/doc/pi_netrw.txt b/runtime/doc/pi_netrw.txt index 3e19f0b4af..b41629c0f0 100644 --- a/runtime/doc/pi_netrw.txt +++ b/runtime/doc/pi_netrw.txt @@ -3719,8 +3719,6 @@ netrw: or http://vim.sourceforge.net/scripts/script.php?script_id=120 - Decho.vim is provided as a "vimball"; see |vimball-intro|. - 2. Edit the <netrw.vim> file by typing: > vim netrw.vim diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index 24fb87a6b4..c9ce2b9dc1 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -408,9 +408,6 @@ accordingly. Vim proceeds in this order: Windows $XDG_CONFIG_HOME/nvim/init.vim (default for $XDG_CONFIG_HOME is ~/AppData/Local) - The files are searched in the order specified above and only the first - one that is found is read. - RECOMMENDATION: Put all your Vim configuration stuff in the $HOME/.config/nvim/ directory. That makes it easy to copy it to another system. diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index 094b280697..6a73061aca 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -5189,7 +5189,7 @@ To test your color setup, a file has been included in the Vim distribution. To use it, execute this command: > :runtime syntax/colortest.vim -Nvim uses |256-color| and |true-color| terminal capabilities whereever possible. +Nvim uses 256-color and |true-color| terminal capabilities whereever possible. ============================================================================== 18. When syntax is slow *:syntime* diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt index 51af11a2cd..42ce7a5edf 100644 --- a/runtime/doc/ui.txt +++ b/runtime/doc/ui.txt @@ -18,17 +18,17 @@ the grid ("externalized"). *ui-options* After connecting to Nvim (usually a spawned, embedded instance) use the -|nvim_ui_attach| API method to tell Nvim that your program wants to draw the +|nvim_ui_attach()| API method to tell Nvim that your program wants to draw the Nvim screen grid with a size of width × height cells. `options` must be a dictionary with these (optional) keys: - `rgb` Decides the color format. |ui-rgb| + `rgb` Decides the color format. *ui-rgb* Set true (default) for 24-bit RGB colors. Set false for terminal colors (max of 256). *ui-ext-options* `ext_popupmenu` Externalize the popupmenu. |ui-popupmenu| `ext_tabline` Externalize the tabline. |ui-tabline| `ext_cmdline` Externalize the cmdline. |ui-cmdline| - `ext_wildmenu` Externalize the wildmenu. |ui-ext-wildmenu| + `ext_wildmenu` Externalize the wildmenu. |ui-wildmenu| `ext_newgrid` Use new revision of the grid events. |ui-newgrid| `ext_hlstate` Use detailed highlight state. |ui-hlstate| @@ -150,7 +150,7 @@ Global Events *ui-global* user input. This could be indicated to the user by hiding the cursor. ["suspend"] - |:suspend| command or |Ctrl-Z| mapping is used. A terminal client (or other + |:suspend| command or |CTRL-Z| mapping is used. A terminal client (or other client where it makes sense) could suspend itself. Other clients can safely ignore it. @@ -407,7 +407,7 @@ with the following possible keys: "ui": A builtin ui highlight. "syntax": highlight applied to a buffer by a syntax declaration or other runtime/plugin functionallity such as - |nvim_buf_add_highlight| + |nvim_buf_add_highlight()| "terminal": highlight from a process running in a |terminal-emulator|. Contains no futher semantic information. `ui_name`: Name of the builtin highlight. See |highlight-groups| for @@ -417,7 +417,7 @@ with the following possible keys: `id`: Unique numeric id representing this item. Note: "ui" items will have both `ui_name` and `hi_name` present. These can -differ, because the builtin group was linked to another group |hi-link| , or +differ, because the builtin group was linked to another group |:hi-link| , or because 'winhighlight' was used. UI items will be transmitted, even if the highlight group is cleared, so `ui_name` can always be used to reliably identify screen elements, even if no attributes have been applied. @@ -480,7 +480,7 @@ Only sent if `ext_cmdline` option is set in |ui-options| typing `<c-r>=` at the command line prompt. The `level` field is used to distinguish different command lines active at the same time. The first invoked command line has level 1, the next recursively-invoked - prompt has level 2. A command line invoked from the |cmd-line-window| + prompt has level 2. A command line invoked from the |cmdline-window| has a higher level than than the edited command line. ["cmdline_pos", pos, level] diff --git a/runtime/doc/usr_05.txt b/runtime/doc/usr_05.txt index d1491e6b31..2485f106b4 100644 --- a/runtime/doc/usr_05.txt +++ b/runtime/doc/usr_05.txt @@ -323,8 +323,6 @@ Where can you find plugins? - They are sometimes posted in a Vim |maillist|. - You could write one yourself, see |write-plugin|. -Some plugins come as a vimball archive, see |vimball|. - USING A GLOBAL PLUGIN diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index 51d8143440..7978074550 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -2506,9 +2506,6 @@ Vim scripts can be used on any system. There might not be a tar or gzip command. If you want to pack files together and/or compress them the "zip" utility is recommended. -For utmost portability use Vim itself to pack scripts together. This can be -done with the Vimball utility. See |vimball|. - ============================================================================== Next chapter: |usr_42.txt| Add new menus diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt index 2a9a181b7a..9412899ea3 100644 --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -388,7 +388,6 @@ m *+writebackup* |'writebackup'| is default on m *+xim* X input method |xim| *+xfontset* X fontset support |xfontset| *+xpm* pixmap support -m *+xpm_w32* Win32 GUI only: pixmap support |w32-xpm-support| */dyn* *E370* *E448* To some of the features "/dyn" is added when the diff --git a/runtime/doc/vi_diff.txt b/runtime/doc/vi_diff.txt index 139cd3749a..bf90fe1754 100644 --- a/runtime/doc/vi_diff.txt +++ b/runtime/doc/vi_diff.txt @@ -322,7 +322,7 @@ Scripts and Expressions. |expression| Debugging and profiling are supported. |debug-scripts| |profile| If this is not enough, an interface is provided to |Python|. -Viminfo. |viminfo-file| +Viminfo. The command-line history, marks and registers can be stored in a file that is read on startup. This can be used to repeat a search command or command-line command after exiting and restarting Vim. It is also diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index ef0b54fa33..4081f4c239 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -111,7 +111,7 @@ ARCHITECTURE ~ External plugins run in separate processes. |remote-plugin| This improves stability and allows those plugins to work without blocking the editor. Even -"legacy" Python and Ruby plugins which use the old Vim interfaces (|if_py| and +"legacy" Python and Ruby plugins which use the old Vim interfaces (|if_pyth|, |if_ruby|) run out-of-process. Platform and I/O facilities are built upon libuv. Nvim benefits from libuv @@ -182,7 +182,7 @@ Options: 'display' flag `msgsep` to minimize scrolling when showing messages 'guicursor' works in the terminal 'fillchars' flags: `msgsep` (see 'display' above) - and `eob` for |EndOfBuffer| marker + and `eob` for |hl-EndOfBuffer| marker 'inccommand' shows interactive results for |:substitute|-like commands 'scrollback' 'statusline' supports unlimited alignment sections @@ -245,7 +245,7 @@ makes things faster. |:terminal| output is never throttled. 4. Stringifyed infinite and NaN values now use |str2float()| and can be evaled back. 5. (internal) Trying to print or stringify VAR_UNKNOWN in Vim results in - nothing, |E908|, in Neovim it is internal error. + nothing, E908, in Nvim it is internal error. |json_decode()| behaviour changed: 1. It may output |msgpack-special-dict|. @@ -287,12 +287,11 @@ coerced to strings. See |id()| for more details, currently it uses Lua interface (|if_lua.txt|): -- `:lua print("a\0b")` will print `a^@b`, like with `:echomsg "a\nb"` . In Vim +- `: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 `:lua print("a\nb")` . -- `:lua error('TEST')` will print “TEST” as the error in Vim and “E5105: Error - while calling lua chunk: [string "<VimL compiled string>"]:1: TEST” in - Neovim. +- `:lua error('TEST')` emits the error “E5105: Error while calling lua chunk: + [string "<VimL compiled string>"]:1: TEST”, whereas Vim emits only “TEST”. - Lua has direct access to Nvim |API| via `vim.api`. - Lua package.path and package.cpath are automatically updated according to 'runtimepath': |lua-require|. @@ -347,7 +346,7 @@ TUI: only 8 colours plus bright foreground on Linux VTs. Vim combines what is in its |builtin-terms| with what it reads from terminfo, - and has a |ttybuiltin| setting to control how that combination works. Nvim + and has a 'ttybuiltin' setting to control how that combination works. Nvim uses one or the other, it does not attempt to merge the two. VimL (Vim script) compatibility: diff --git a/runtime/doc/visual.txt b/runtime/doc/visual.txt index 176ce562d8..1731bbd030 100644 --- a/runtime/doc/visual.txt +++ b/runtime/doc/visual.txt @@ -70,10 +70,7 @@ position. selected. *CTRL-V* *blockwise-visual* -[count]CTRL-V Start Visual mode blockwise. Note: Under Windows - CTRL-V could be mapped to paste text, it doesn't work - to start Visual mode then, see |CTRL-V-alternative|. - [count] is used as with `v` above. +[count]CTRL-V Start Visual mode blockwise. If you use <Esc>, click the left mouse button or use any command that does a jump to another buffer while in Visual mode, the highlighting stops @@ -302,8 +299,8 @@ Visual-block Insert *v_b_I* With a blockwise selection, I{string}<ESC> will insert {string} at the start of block on every line of the block, provided that the line extends into the block. Thus lines that are short will remain unmodified. TABs are split to -retain visual columns. -See |v_b_I_example|. +retain visual columns. Works only for adding text to a line, not for +deletions. See |v_b_I_example|. Visual-block Append *v_b_A* With a blockwise selection, A{string}<ESC> will append {string} to the end of @@ -319,6 +316,7 @@ See |v_b_A_example|. Note: "I" and "A" behave differently for lines that don't extend into the selected block. This was done intentionally, so that you can do it the way you want. +Works only for adding text to a line, not for deletions. Visual-block change *v_b_c* All selected text in the block will be replaced by the same text string. When diff --git a/runtime/optwin.vim b/runtime/optwin.vim index 81eb3f11fd..83c509b7cb 100644 --- a/runtime/optwin.vim +++ b/runtime/optwin.vim @@ -4,17 +4,14 @@ " Last Change: 2017 Oct 19 " If there already is an option window, jump to that one. -if bufwinnr("option-window") > 0 - let s:thiswin = winnr() - while 1 - if @% == "option-window" +let buf = bufnr('option-window') +if buf >= 0 + let winids = win_findbuf(buf) + if len(winids) > 0 + if win_gotoid(winids[0]) == 1 finish endif - wincmd w - if s:thiswin == winnr() - break - endif - endwhile + endif endif " Make sure the '<' flag is not included in 'cpoptions', otherwise <CR> would @@ -141,8 +138,8 @@ while exists("b:current_syntax") && b:current_syntax == "help" endif endwhile -" Open the window -new option-window +" Open the window. $OPTWIN_CMD is set to "tab" for ":tab options". +exe $OPTWIN_CMD . ' new option-window' setlocal ts=15 tw=0 noro buftype=nofile " Insert help and a "set" command for each option. diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index 6dd411a34e..bba76ffa97 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -85,12 +85,13 @@ get_vim_sources() { git clone https://github.com/vim/vim.git "${VIM_SOURCE_DIR}" cd "${VIM_SOURCE_DIR}" else - if [[ ! -d "${VIM_SOURCE_DIR}/.git" ]]; then + cd "${VIM_SOURCE_DIR}" + if ! [ -d ".git" ] \ + && ! [ "$(git rev-parse --show-toplevel)" = "${VIM_SOURCE_DIR}" ]; then msg_err "${VIM_SOURCE_DIR} does not appear to be a git repository." echo " Please remove it and try again." exit 1 fi - cd "${VIM_SOURCE_DIR}" echo "Updating Vim sources: ${VIM_SOURCE_DIR}" git pull && msg_ok "Updated Vim sources." || @@ -202,7 +203,7 @@ get_vimpatch() { printf "Pre-processing patch...\n" preprocess_patch "${NVIM_SOURCE_DIR}/${patch_file}" - msg_ok "Saved patch to '${NVIM_SOURCE_DIR}/${patch_file}'.\n" + msg_ok "Saved patch to '${NVIM_SOURCE_DIR}/${patch_file}'." } stage_patch() { diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 37d34c5843..76e3927820 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -501,10 +501,8 @@ static void remote_ui_flush(UI *ui) } } -static void remote_ui_cmdline_show(UI *ui, Array args) +static Array translate_contents(UI *ui, Array contents) { - Array new_args = ARRAY_DICT_INIT; - Array contents = args.items[0].data.array; Array new_contents = ARRAY_DICT_INIT; for (size_t i = 0; i < contents.size; i++) { Array item = contents.items[i].data.array; @@ -519,23 +517,48 @@ static void remote_ui_cmdline_show(UI *ui, Array args) ADD(new_item, copy_object(item.items[1])); ADD(new_contents, ARRAY_OBJ(new_item)); } - ADD(new_args, ARRAY_OBJ(new_contents)); + return new_contents; +} + +static Array translate_firstarg(UI *ui, Array args) +{ + Array new_args = ARRAY_DICT_INIT; + Array contents = args.items[0].data.array; + + ADD(new_args, ARRAY_OBJ(translate_contents(ui, contents))); for (size_t i = 1; i < args.size; i++) { ADD(new_args, copy_object(args.items[i])); } - push_call(ui, "cmdline_show", new_args); + return new_args; } static void remote_ui_event(UI *ui, char *name, Array args, bool *args_consumed) { if (!ui->ui_ext[kUINewgrid]) { - // the representation of cmdline_show changed, translate back + // the representation of highlights in cmdline changed, translate back + // never consumes args if (strequal(name, "cmdline_show")) { - remote_ui_cmdline_show(ui, args); - // never consumes args + Array new_args = translate_firstarg(ui, args); + push_call(ui, name, new_args); + return; + } else if (strequal(name, "cmdline_block_show")) { + Array new_args = ARRAY_DICT_INIT; + Array block = args.items[0].data.array; + Array new_block = ARRAY_DICT_INIT; + for (size_t i = 0; i < block.size; i++) { + ADD(new_block, + ARRAY_OBJ(translate_contents(ui, block.items[i].data.array))); + } + ADD(new_args, ARRAY_OBJ(new_block)); + push_call(ui, name, new_args); + return; + } else if (strequal(name, "cmdline_block_append")) { + Array new_args = translate_firstarg(ui, args); + push_call(ui, name, new_args); return; } } + Array my_args = ARRAY_DICT_INIT; // Objects are currently single-reference // make a copy, but only if necessary diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 1ffae8ef43..e78b8c776d 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -1083,7 +1083,7 @@ void nvim_set_client_info(uint64_t channel_id, String name, /// - "buffer" buffer with connected |terminal| instance (optional) /// - "client" information about the client on the other end of the /// RPC channel, if it has added it using -/// |nvim_set_client_info|. (optional) +/// |nvim_set_client_info()|. (optional) /// Dictionary nvim_get_chan_info(Integer chan, Error *err) FUNC_API_SINCE(4) @@ -1097,7 +1097,7 @@ Dictionary nvim_get_chan_info(Integer chan, Error *err) /// Get information about all open channels. /// /// @returns Array of Dictionaries, each describing a channel with -/// the format specified at |nvim_get_chan_info|. +/// the format specified at |nvim_get_chan_info()|. Array nvim_list_chans(void) FUNC_API_SINCE(4) { diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 00d472b4c8..64569c294b 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -2659,9 +2659,8 @@ buf_T *setaltfname(char_u *ffname, char_u *sfname, linenr_T lnum) * Get alternate file name for current window. * Return NULL if there isn't any, and give error message if requested. */ -char_u * -getaltfname ( - int errmsg /* give error message */ +char_u * getaltfname( + bool errmsg // give error message ) { char_u *fname; diff --git a/src/nvim/charset.c b/src/nvim/charset.c index f1b3be6b46..4e8bb3b0d7 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -30,7 +30,7 @@ #include "nvim/state.h" #include "nvim/strings.h" #include "nvim/path.h" - +#include "nvim/cursor.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "charset.c.generated.h" @@ -1465,6 +1465,18 @@ char_u *skipwhite(const char_u *q) return (char_u *)p; } +// getwhitecols: return the number of whitespace +// columns (bytes) at the start of a given line +intptr_t getwhitecols_curline(void) +{ + return getwhitecols(get_cursor_line_ptr()); +} + +intptr_t getwhitecols(const char_u *p) +{ + return skipwhite(p) - p; +} + /// Skip over digits /// /// @param[in] q String to skip digits in. diff --git a/src/nvim/edit.c b/src/nvim/edit.c index c8061d82e4..a20661bb16 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -4503,7 +4503,7 @@ static int ins_complete(int c, bool enable_pum) * first non_blank in the line, if it is not a wordchar * include it to get a better pattern, but then we don't * want the "\\<" prefix, check it bellow */ - compl_col = (colnr_T)(skipwhite(line) - line); + compl_col = (colnr_T)getwhitecols(line); compl_startpos.col = compl_col; compl_startpos.lnum = curwin->w_cursor.lnum; compl_cont_status &= ~CONT_SOL; /* clear SOL if present */ @@ -4615,7 +4615,7 @@ static int ins_complete(int c, bool enable_pum) } } } else if (CTRL_X_MODE_LINE_OR_EVAL(ctrl_x_mode)) { - compl_col = (colnr_T)(skipwhite(line) - line); + compl_col = (colnr_T)getwhitecols(line); compl_length = (int)curs_col - (int)compl_col; if (compl_length < 0) /* cursor in indent: empty pattern */ compl_length = 0; @@ -6909,7 +6909,7 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) p = look + STRLEN(look); if ((try_match || try_match_word) && curwin->w_cursor.col >= (colnr_T)(p - look)) { - int match = FALSE; + bool match = false; if (keytyped == KEY_COMPLETE) { char_u *s; @@ -6934,29 +6934,30 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) && (icase ? mb_strnicmp(s, look, (size_t)(p - look)) : STRNCMP(s, look, p - look)) == 0) - match = TRUE; - } else - /* TODO: multi-byte */ - if (keytyped == (int)p[-1] || (icase && keytyped < 256 - && TOLOWER_LOC(keytyped) == - TOLOWER_LOC((int)p[-1]))) { - line = get_cursor_pos_ptr(); - assert(p >= look && (uintmax_t)(p - look) <= SIZE_MAX); - if ((curwin->w_cursor.col == (colnr_T)(p - look) - || !vim_iswordc(line[-(p - look) - 1])) - && (icase - ? mb_strnicmp(line - (p - look), look, (size_t)(p - look)) - : STRNCMP(line - (p - look), look, p - look)) - == 0) - match = TRUE; + match = true; + } else { + // TODO(@brammool): multi-byte + if (keytyped == (int)p[-1] + || (icase && keytyped < 256 + && TOLOWER_LOC(keytyped) == TOLOWER_LOC((int)p[-1]))) { + line = get_cursor_pos_ptr(); + assert(p >= look && (uintmax_t)(p - look) <= SIZE_MAX); + if ((curwin->w_cursor.col == (colnr_T)(p - look) + || !vim_iswordc(line[-(p - look) - 1])) + && (icase + ? mb_strnicmp(line - (p - look), look, (size_t)(p - look)) + : STRNCMP(line - (p - look), look, p - look)) == 0) { + match = true; + } + } } if (match && try_match_word && !try_match) { /* "0=word": Check if there are only blanks before the * word. */ - line = get_cursor_line_ptr(); - if ((int)(skipwhite(line) - line) != - (int)(curwin->w_cursor.col - (p - look))) - match = FALSE; + if (getwhitecols(line) != + (int)(curwin->w_cursor.col - (p - look))) { + match = false; + } } if (match) { return true; diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 86f57ee5a2..e65a4d489c 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -1210,8 +1210,12 @@ int call_vim_function( if (str_arg_only) { len = 0; } else { - // Recognize a number argument, the others must be strings. + // Recognize a number argument, the others must be strings. A dash + // is a string too. vim_str2nr(argv[i], NULL, &len, STR2NR_ALL, &n, NULL, 0); + if (len == 1 && *argv[i] == '-') { + len = 0; + } } if (len != 0 && len == (int)STRLEN(argv[i])) { argvars[i].v_type = VAR_NUMBER; @@ -10262,8 +10266,10 @@ static dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr) tv_dict_add_nr(dict, S_LEN("winnr"), winnr); tv_dict_add_nr(dict, S_LEN("winid"), wp->handle); tv_dict_add_nr(dict, S_LEN("height"), wp->w_height); + tv_dict_add_nr(dict, S_LEN("winrow"), wp->w_winrow); tv_dict_add_nr(dict, S_LEN("width"), wp->w_width); tv_dict_add_nr(dict, S_LEN("bufnr"), wp->w_buffer->b_fnum); + tv_dict_add_nr(dict, S_LEN("wincol"), wp->w_wincol); tv_dict_add_nr(dict, S_LEN("quickfix"), bt_quickfix(wp->w_buffer)); tv_dict_add_nr(dict, S_LEN("loclist"), @@ -10310,6 +10316,15 @@ static void f_getwininfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } +// "win_screenpos()" function +static void f_win_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_list_alloc_ret(rettv, 2); + const win_T *const wp = find_win_by_nr(&argvars[0], NULL); + tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_winrow + 1); + tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_wincol + 1); +} + /* * "getwinposx()" function */ @@ -10594,6 +10609,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) #ifdef HAVE_ACL "acl", #endif + "autochdir", "arabic", "autocmd", "browsefilter", diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 23959f348a..328f46443f 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -338,6 +338,7 @@ return { win_gotoid={args=1}, win_id2tabwin={args=1}, win_id2win={args=1}, + win_screenpos={args=1}, winbufnr={args=1}, wincol={}, winheight={args=1}, diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 4dcecae9d8..c9eccfa6b0 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -1595,15 +1595,16 @@ void ex_file(exarg_T *eap) } if (*eap->arg != NUL || eap->addr_count == 1) { - if (rename_buffer(eap->arg) == FAIL) + if (rename_buffer(eap->arg) == FAIL) { return; + } + redraw_tabline = true; } - if (!shortmess(SHM_FILEINFO)) { - // print full file name if :cd used + // print file name if no argument or 'F' is not in 'shortmess' + if (*eap->arg == NUL || !shortmess(SHM_FILEINFO)) { fileinfo(false, false, eap->forceit); } - redraw_tabline = true; } /* diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 120278d3fb..ab24b63110 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -2254,6 +2254,15 @@ static int alist_add_list(int count, char_u **files, int after) } } +// Function given to ExpandGeneric() to obtain the possible arguments of the +// argedit and argdelete commands. +char_u *get_arglist_name(expand_T *xp FUNC_ATTR_UNUSED, int idx) +{ + if (idx >= ARGCOUNT) { + return NULL; + } + return alist_name(&ARGLIST[idx]); +} /// ":compiler[!] {name}" void ex_compiler(exarg_T *eap) @@ -2719,6 +2728,7 @@ void ex_packadd(exarg_T *eap) /// ":options" void ex_options(exarg_T *eap) { + vim_setenv("OPTWIN_CMD", cmdmod.tab ? "tab" : ""); cmd_source((char_u *)SYS_OPTWIN_FILE, NULL); } diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index c31242f2ac..5a92a85c97 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -2665,8 +2665,8 @@ const char * set_one_cmd_context( size_t len = 0; exarg_T ea; int context = EXPAND_NOTHING; - int forceit = false; - int usefilter = false; // Filter instead of file name. + bool forceit = false; + bool usefilter = false; // Filter instead of file name. ExpandInit(xp); xp->xp_pattern = (char_u *)buff; @@ -2786,9 +2786,9 @@ const char * set_one_cmd_context( xp->xp_context = EXPAND_NOTHING; /* Default now that we're past command */ - if (*p == '!') { /* forced commands */ - forceit = TRUE; - ++p; + if (*p == '!') { // forced commands + forceit = true; + p++; } /* @@ -2813,10 +2813,10 @@ const char * set_one_cmd_context( } if (ea.cmdidx == CMD_read) { - usefilter = forceit; /* :r! filter if forced */ - if (*arg == '!') { /* :r !filter */ - ++arg; - usefilter = TRUE; + usefilter = forceit; // :r! filter if forced + if (*arg == '!') { // :r !filter + arg++; + usefilter = true; } } @@ -2978,7 +2978,7 @@ const char * set_one_cmd_context( // A full match ~user<Tab> will be replaced by user's home // directory i.e. something like ~user<Tab> -> /home/user/ if (*p == NUL && p > (const char *)xp->xp_pattern + 1 - && match_user(xp->xp_pattern + 1) == 1) { + && match_user(xp->xp_pattern + 1) >= 1) { xp->xp_context = EXPAND_USER; ++xp->xp_pattern; } @@ -3350,6 +3350,19 @@ const char * set_one_cmd_context( case CMD_xunmap: return (const char *)set_context_in_map_cmd( xp, (char_u *)cmd, (char_u *)arg, forceit, false, true, ea.cmdidx); + case CMD_mapclear: + case CMD_nmapclear: + case CMD_vmapclear: + case CMD_omapclear: + case CMD_imapclear: + case CMD_cmapclear: + case CMD_lmapclear: + case CMD_smapclear: + case CMD_xmapclear: + xp->xp_context = EXPAND_MAPCLEAR; + xp->xp_pattern = (char_u *)arg; + break; + case CMD_abbreviate: case CMD_noreabbrev: case CMD_cabbrev: case CMD_cnoreabbrev: case CMD_iabbrev: case CMD_inoreabbrev: @@ -3441,6 +3454,13 @@ const char * set_one_cmd_context( xp->xp_pattern = (char_u *)arg; break; + case CMD_argdelete: + while ((xp->xp_pattern = vim_strchr((const char_u *)arg, ' ')) != NULL) { + arg = (const char *)(xp->xp_pattern + 1); + } + xp->xp_context = EXPAND_ARGLIST; + xp->xp_pattern = (char_u *)arg; + break; default: break; @@ -4846,6 +4866,7 @@ static struct { */ static const char *command_complete[] = { + [EXPAND_ARGLIST] = "arglist", [EXPAND_AUGROUP] = "augroup", [EXPAND_BEHAVE] = "behave", [EXPAND_BUFFERS] = "buffer", @@ -4870,6 +4891,7 @@ static const char *command_complete[] = #ifdef HAVE_WORKING_LIBINTL [EXPAND_LOCALES] = "locale", #endif + [EXPAND_MAPCLEAR] = "mapclear", [EXPAND_MAPPINGS] = "mapping", [EXPAND_MENUS] = "menu", [EXPAND_MESSAGES] = "messages", @@ -7555,10 +7577,11 @@ static void ex_bang(exarg_T *eap) */ static void ex_undo(exarg_T *eap) { - if (eap->addr_count == 1) /* :undo 123 */ - undo_time(eap->line2, FALSE, FALSE, TRUE); - else + if (eap->addr_count == 1) { // :undo 123 + undo_time(eap->line2, false, false, true); + } else { u_undo(1); + } } static void ex_wundo(exarg_T *eap) @@ -7591,8 +7614,8 @@ static void ex_redo(exarg_T *eap) static void ex_later(exarg_T *eap) { long count = 0; - int sec = FALSE; - int file = FALSE; + bool sec = false; + bool file = false; char_u *p = eap->arg; if (*p == NUL) @@ -7600,11 +7623,11 @@ static void ex_later(exarg_T *eap) else if (isdigit(*p)) { count = getdigits_long(&p); switch (*p) { - case 's': ++p; sec = TRUE; break; - case 'm': ++p; sec = TRUE; count *= 60; break; - case 'h': ++p; sec = TRUE; count *= 60 * 60; break; - case 'd': ++p; sec = TRUE; count *= 24 * 60 * 60; break; - case 'f': ++p; file = TRUE; break; + case 's': ++p; sec = true; break; + case 'm': ++p; sec = true; count *= 60; break; + case 'h': ++p; sec = true; count *= 60 * 60; break; + case 'd': ++p; sec = true; count *= 24 * 60 * 60; break; + case 'f': ++p; file = true; break; } } @@ -7612,7 +7635,7 @@ static void ex_later(exarg_T *eap) EMSG2(_(e_invarg2), eap->arg); else undo_time(eap->cmdidx == CMD_earlier ? -count : count, - sec, file, FALSE); + sec, file, false); } /* @@ -8131,6 +8154,10 @@ static void ex_normal(exarg_T *eap) static void ex_startinsert(exarg_T *eap) { if (eap->forceit) { + // cursor line can be zero on startup + if (!curwin->w_cursor.lnum) { + curwin->w_cursor.lnum = 1; + } coladvance((colnr_T)MAXCOL); curwin->w_curswant = MAXCOL; curwin->w_set_curswant = FALSE; @@ -8370,23 +8397,25 @@ ssize_t find_cmdline_var(const char_u *src, size_t *usedlen) "%", #define SPEC_PERC 0 "#", -#define SPEC_HASH 1 - "<cword>", /* cursor word */ -#define SPEC_CWORD 2 - "<cWORD>", /* cursor WORD */ -#define SPEC_CCWORD 3 - "<cfile>", /* cursor path name */ -#define SPEC_CFILE 4 - "<sfile>", /* ":so" file name */ -#define SPEC_SFILE 5 - "<slnum>", /* ":so" file line number */ -#define SPEC_SLNUM 6 - "<afile>", /* autocommand file name */ -# define SPEC_AFILE 7 - "<abuf>", /* autocommand buffer number */ -# define SPEC_ABUF 8 - "<amatch>", /* autocommand match name */ -# define SPEC_AMATCH 9 +#define SPEC_HASH (SPEC_PERC + 1) + "<cword>", // cursor word +#define SPEC_CWORD (SPEC_HASH + 1) + "<cWORD>", // cursor WORD +#define SPEC_CCWORD (SPEC_CWORD + 1) + "<cexpr>", // expr under cursor +#define SPEC_CEXPR (SPEC_CCWORD + 1) + "<cfile>", // cursor path name +#define SPEC_CFILE (SPEC_CEXPR + 1) + "<sfile>", // ":so" file name +#define SPEC_SFILE (SPEC_CFILE + 1) + "<slnum>", // ":so" file line number +#define SPEC_SLNUM (SPEC_SFILE + 1) + "<afile>", // autocommand file name +#define SPEC_AFILE (SPEC_SLNUM + 1) + "<abuf>", // autocommand buffer number +#define SPEC_ABUF (SPEC_AFILE + 1) + "<amatch>", // autocommand match name +#define SPEC_AMATCH (SPEC_ABUF + 1) }; for (size_t i = 0; i < ARRAY_SIZE(spec_str); ++i) { @@ -8467,10 +8496,16 @@ eval_vars ( /* * word or WORD under cursor */ - if (spec_idx == SPEC_CWORD || spec_idx == SPEC_CCWORD) { - resultlen = find_ident_under_cursor(&result, (spec_idx == SPEC_CWORD - ? (FIND_IDENT|FIND_STRING) - : FIND_STRING)); + if (spec_idx == SPEC_CWORD + || spec_idx == SPEC_CCWORD + || spec_idx == SPEC_CEXPR) { + resultlen = find_ident_under_cursor( + &result, + spec_idx == SPEC_CWORD + ? (FIND_IDENT | FIND_STRING) + : (spec_idx == SPEC_CEXPR + ? (FIND_IDENT | FIND_STRING | FIND_EVAL) + : FIND_STRING)); if (resultlen == 0) { *errormsg = (char_u *)""; return NULL; @@ -8507,9 +8542,13 @@ eval_vars ( if (*s == '<') /* "#<99" uses v:oldfiles */ ++s; i = getdigits_int(&s); - *usedlen = (size_t)(s - src); /* length of what we expand */ + if (s == src + 2 && src[1] == '-') { + // just a minus sign, don't skip over it + s--; + } + *usedlen = (size_t)(s - src); // length of what we expand - if (src[1] == '<') { + if (src[1] == '<' && i != 0) { if (*usedlen < 2) { /* Should we give an error message for #<text? */ *usedlen = 1; @@ -8522,6 +8561,9 @@ eval_vars ( return NULL; } } else { + if (i == 0 && src[1] == '<' && *usedlen > 1) { + *usedlen = 1; + } buf = buflist_findnr(i); if (buf == NULL) { *errormsg = (char_u *)_( @@ -9655,6 +9697,14 @@ char_u *get_messages_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) return NULL; } +char_u *get_mapclear_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) +{ + if (idx == 0) { + return (char_u *)"<buffer>"; + } + return NULL; +} + static TriState filetype_detect = kNone; static TriState filetype_plugin = kNone; static TriState filetype_indent = kNone; diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index b4509f9e92..5c02c4c4d6 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -177,10 +177,6 @@ typedef struct command_line_state { int break_ctrl_c; expand_T xpc; long *b_im_ptr; - // Everything that may work recursively should save and restore the - // current command line in save_ccline. That includes update_screen(), a - // custom status line may invoke ":normal". - struct cmdline_info save_ccline; } CommandLineState; typedef struct cmdline_info CmdlineInfo; @@ -225,11 +221,19 @@ static bool need_cursor_update = false; # include "ex_getln.c.generated.h" #endif -static int cmd_hkmap = 0; /* Hebrew mapping during command line */ +static int cmd_hkmap = 0; // Hebrew mapping during command line +static int cmd_fkmap = 0; // Farsi mapping during command line -static int cmd_fkmap = 0; /* Farsi mapping during command line */ +/// Internal entry point for cmdline mode. +/// +/// caller must use save_cmdline and restore_cmdline. Best is to use +/// getcmdline or getcmdline_prompt, instead of calling this directly. static uint8_t *command_line_enter(int firstc, long count, int indent) { + // can be invoked recursively, identify each level + static int cmdline_level = 0; + cmdline_level++; + CommandLineState state, *s = &state; memset(s, 0, sizeof(CommandLineState)); s->firstc = firstc; @@ -257,7 +261,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) } ccline.prompt_id = last_prompt_id++; - ccline.level++; + ccline.level = cmdline_level; ccline.overstrike = false; // always start in insert mode clearpos(&s->match_end); s->save_cursor = curwin->w_cursor; // may be restored later @@ -489,19 +493,14 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) sb_text_end_cmdline(); - { - char_u *p = ccline.cmdbuff; - - // Make ccline empty, getcmdline() may try to use it. - ccline.cmdbuff = NULL; + char_u *p = ccline.cmdbuff; - if (ui_is_external(kUICmdline)) { - ccline.redraw_state = kCmdRedrawNone; - ui_call_cmdline_hide(ccline.level); - } - ccline.level--; - return p; + if (ui_is_external(kUICmdline)) { + ui_call_cmdline_hide(ccline.level); } + + cmdline_level--; + return p; } static int command_line_check(VimState *state) @@ -641,9 +640,7 @@ static int command_line_execute(VimState *state, int key) p_ls = save_p_ls; p_wmh = save_p_wmh; last_status(false); - save_cmdline(&s->save_ccline); update_screen(VALID); // redraw the screen NOW - restore_cmdline(&s->save_ccline); redrawcmd(); save_p_ls = -1; wild_menu_showing = 0; @@ -814,18 +811,17 @@ static int command_line_execute(VimState *state, int key) new_cmdpos = ccline.cmdpos; } - save_cmdline(&s->save_ccline); s->c = get_expr_register(); - restore_cmdline(&s->save_ccline); if (s->c == '=') { // Need to save and restore ccline. And set "textlock" // to avoid nasty things like going to another buffer when // evaluating an expression. - save_cmdline(&s->save_ccline); - ++textlock; + CmdlineInfo save_ccline; + save_cmdline(&save_ccline); + textlock++; p = get_expr_line(); - --textlock; - restore_cmdline(&s->save_ccline); + textlock--; + restore_cmdline(&save_ccline); if (p != NULL) { len = (int)STRLEN(p); @@ -1358,9 +1354,10 @@ static int command_line_handle_key(CommandLineState *s) beep_flush(); s->c = ESC; } else { - save_cmdline(&s->save_ccline); + CmdlineInfo save_ccline; + save_cmdline(&save_ccline); s->c = get_expr_register(); - restore_cmdline(&s->save_ccline); + restore_cmdline(&save_ccline); } } @@ -1892,9 +1889,7 @@ static int command_line_changed(CommandLineState *s) curwin->w_redr_status = true; } - save_cmdline(&s->save_ccline); update_screen(SOME_VALID); - restore_cmdline(&s->save_ccline); restore_last_search_pattern(); // Leave it at the end to make CTRL-R CTRL-W work. @@ -1989,7 +1984,14 @@ getcmdline ( int indent // indent for inside conditionals ) { - return command_line_enter(firstc, count, indent); + // Be prepared for situations where cmdline can be invoked recursively. + // That includes cmd mappings, event handlers, as well as update_screen() + // (custom status line eval), which all may invoke ":normal :". + CmdlineInfo save_ccline; + save_cmdline(&save_ccline); + char_u *retval = command_line_enter(firstc, count, indent); + restore_cmdline(&save_ccline); + return retval; } /// Get a command line with a prompt @@ -2013,7 +2015,7 @@ char *getcmdline_prompt(const char firstc, const char *const prompt, { const int msg_col_save = msg_col; - struct cmdline_info save_ccline; + CmdlineInfo save_ccline; save_cmdline(&save_ccline); ccline.prompt_id = last_prompt_id++; @@ -2027,7 +2029,7 @@ char *getcmdline_prompt(const char firstc, const char *const prompt, int msg_silent_saved = msg_silent; msg_silent = 0; - char *const ret = (char *)getcmdline(firstc, 1L, 0); + char *const ret = (char *)command_line_enter(firstc, 1L, 0); restore_cmdline(&save_ccline); msg_silent = msg_silent_saved; @@ -2169,6 +2171,7 @@ getexline ( /* When executing a register, remove ':' that's in front of each line. */ if (exec_from_reg && vpeekc() == ':') (void)vgetc(); + return getcmdline(c, 1L, indent); } @@ -2976,7 +2979,7 @@ void ui_ext_cmdline_block_append(int indent, const char *line) memcpy(buf + indent, line, strlen(line)); // -V575 Array item = ARRAY_DICT_INIT; - ADD(item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT)); + ADD(item, INTEGER_OBJ(0)); ADD(item, STRING_OBJ(cstr_as_string(buf))); Array content = ARRAY_DICT_INIT; ADD(content, ARRAY_OBJ(item)); @@ -3008,16 +3011,16 @@ void cmdline_screen_cleared(void) } int prev_level = ccline.level-1; - CmdlineInfo *prev_ccline = ccline.prev_ccline; - while (prev_level > 0 && prev_ccline) { - if (prev_ccline->level == prev_level) { + CmdlineInfo *line = ccline.prev_ccline; + while (prev_level > 0 && line) { + if (line->level == prev_level) { // don't redraw a cmdline already shown in the cmdline window if (prev_level != cmdwin_level) { - prev_ccline->redraw_state = kCmdRedrawAll; + line->redraw_state = kCmdRedrawAll; } prev_level--; } - prev_ccline = prev_ccline->prev_ccline; + line = line->prev_ccline; } need_cursor_update = true; @@ -3230,6 +3233,7 @@ static void save_cmdline(struct cmdline_info *ccp) ccline.cmdprompt = NULL; ccline.xpc = NULL; ccline.special_char = NUL; + ccline.level = 0; } /* @@ -3272,17 +3276,18 @@ void restore_cmdline_alloc(char_u *p) /// @returns FAIL for failure, OK otherwise static bool cmdline_paste(int regname, bool literally, bool remcr) { - long i; char_u *arg; char_u *p; - int allocated; + bool allocated; struct cmdline_info save_ccline; /* check for valid regname; also accept special characters for CTRL-R in * the command line */ if (regname != Ctrl_F && regname != Ctrl_P && regname != Ctrl_W - && regname != Ctrl_A && !valid_yank_reg(regname, false)) + && regname != Ctrl_A && regname != Ctrl_L + && !valid_yank_reg(regname, false)) { return FAIL; + } /* A register containing CTRL-R can cause an endless loop. Allow using * CTRL-C to break the loop. */ @@ -3294,9 +3299,9 @@ static bool cmdline_paste(int regname, bool literally, bool remcr) /* Need to save and restore ccline. And set "textlock" to avoid nasty * things like going to another buffer when evaluating an expression. */ save_cmdline(&save_ccline); - ++textlock; - i = get_spec_reg(regname, &arg, &allocated, TRUE); - --textlock; + textlock++; + const bool i = get_spec_reg(regname, &arg, &allocated, true); + textlock--; restore_cmdline(&save_ccline); if (i) { @@ -4742,6 +4747,7 @@ ExpandFromContext ( } tab[] = { { EXPAND_COMMANDS, get_command_name, false, true }, { EXPAND_BEHAVE, get_behave_arg, true, true }, + { EXPAND_MAPCLEAR, get_mapclear_arg, true, true }, { EXPAND_MESSAGES, get_messages_arg, true, true }, { EXPAND_HISTORY, get_history_arg, true, true }, { EXPAND_USER_COMMANDS, get_user_commands, false, true }, @@ -4769,6 +4775,7 @@ ExpandFromContext ( #endif { EXPAND_ENV_VARS, get_env_name, true, true }, { EXPAND_USER, get_users, true, false }, + { EXPAND_ARGLIST, get_arglist_name, true, false }, }; int i; diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c index 2a215f854f..f8ce6200d7 100644 --- a/src/nvim/indent_c.c +++ b/src/nvim/indent_c.c @@ -75,11 +75,12 @@ find_start_comment ( /* XXX */ /// Find the start of a comment or raw string, not knowing if we are in a /// comment or raw string right now. /// Search starts at w_cursor.lnum and goes backwards. +/// If is_raw is given and returns start of raw_string, sets it to true. /// /// @returns NULL when not inside a comment or raw string. /// /// @note "CORS" -> Comment Or Raw String -static pos_T *ind_find_start_CORS(void) +static pos_T *ind_find_start_CORS(linenr_T *is_raw) { // XXX static pos_T comment_pos_copy; @@ -96,6 +97,9 @@ static pos_T *ind_find_start_CORS(void) // If comment_pos is before rs_pos the raw string is inside the comment. // If rs_pos is before comment_pos the comment is inside the raw string. if (comment_pos == NULL || (rs_pos != NULL && lt(*rs_pos, *comment_pos))) { + if (is_raw != NULL && rs_pos != NULL) { + *is_raw = rs_pos->lnum; + } return rs_pos; } return comment_pos; @@ -384,8 +388,9 @@ int cin_islabel(void) * it. */ curwin->w_cursor.col = 0; - if ((trypos = ind_find_start_CORS()) != NULL) /* XXX */ + if ((trypos = ind_find_start_CORS(NULL)) != NULL) { // XXX curwin->w_cursor = *trypos; + } line = get_cursor_line_ptr(); if (cin_ispreproc(line)) /* ignore #defines, #if, etc. */ @@ -1401,10 +1406,12 @@ static pos_T *find_start_brace(void) pos = NULL; /* ignore the { if it's in a // or / * * / comment */ if ((colnr_T)cin_skip2pos(trypos) == trypos->col - && (pos = ind_find_start_CORS()) == NULL) /* XXX */ + && (pos = ind_find_start_CORS(NULL)) == NULL) { // XXX break; - if (pos != NULL) + } + if (pos != NULL) { curwin->w_cursor.lnum = pos->lnum; + } } curwin->w_cursor = cursor_save; return trypos; @@ -1443,7 +1450,7 @@ retry: pos_copy = *trypos; /* copy trypos, findmatch will change it */ trypos = &pos_copy; curwin->w_cursor = *trypos; - if ((trypos_wk = ind_find_start_CORS()) != NULL) { /* XXX */ + if ((trypos_wk = ind_find_start_CORS(NULL)) != NULL) { // XXX ind_maxp_wk = ind_maxparen - (int)(cursor_save.lnum - trypos_wk->lnum); if (ind_maxp_wk > 0) { @@ -1793,6 +1800,7 @@ int get_c_indent(void) int cont_amount = 0; /* amount for continuation line */ int original_line_islabel; int added_to_amount = 0; + linenr_T raw_string_start = 0; cpp_baseclass_cache_T cache_cpp_baseclass = { false, { MAXLNUM, 0 } }; /* make a copy, value is changed below */ @@ -2059,8 +2067,8 @@ int get_c_indent(void) } curwin->w_cursor.lnum = lnum; - /* Skip a comment or raw string. XXX */ - if ((trypos = ind_find_start_CORS()) != NULL) { + // Skip a comment or raw string. XXX + if ((trypos = ind_find_start_CORS(NULL)) != NULL) { lnum = trypos->lnum + 1; continue; } @@ -2443,7 +2451,7 @@ int get_c_indent(void) * If we're in a comment or raw string now, skip to * the start of it. */ - trypos = ind_find_start_CORS(); + trypos = ind_find_start_CORS(NULL); if (trypos != NULL) { curwin->w_cursor.lnum = trypos->lnum + 1; curwin->w_cursor.col = 0; @@ -2552,7 +2560,7 @@ int get_c_indent(void) /* If we're in a comment or raw string now, skip * to the start of it. */ - trypos = ind_find_start_CORS(); + trypos = ind_find_start_CORS(NULL); if (trypos != NULL) { curwin->w_cursor.lnum = trypos->lnum + 1; curwin->w_cursor.col = 0; @@ -2581,11 +2589,10 @@ int get_c_indent(void) break; } - /* - * If we're in a comment or raw string now, skip to the start - * of it. - */ /* XXX */ - if ((trypos = ind_find_start_CORS()) != NULL) { + // If we're in a comment or raw string now, skip to the start + // of it. + // XXX + if ((trypos = ind_find_start_CORS(&raw_string_start)) != NULL) { curwin->w_cursor.lnum = trypos->lnum + 1; curwin->w_cursor.col = 0; continue; @@ -3096,7 +3103,8 @@ int get_c_indent(void) } if (lookfor != LOOKFOR_TERM && lookfor != LOOKFOR_JS_KEY - && lookfor != LOOKFOR_COMMA) { + && lookfor != LOOKFOR_COMMA + && raw_string_start != curwin->w_cursor.lnum) { lookfor = LOOKFOR_UNTERM; } } @@ -3351,11 +3359,10 @@ term_again: l = get_cursor_line_ptr(); - /* - * If we're in a comment or raw string now, skip to the start - * of it. - */ /* XXX */ - if ((trypos = ind_find_start_CORS()) != NULL) { + // If we're in a comment or raw string now, skip to the start + // of it. + // XXX + if ((trypos = ind_find_start_CORS(NULL)) != NULL) { curwin->w_cursor.lnum = trypos->lnum + 1; curwin->w_cursor.col = 0; continue; diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index e1bbb03d3f..47f40da72e 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -13,7 +13,7 @@ local function _os_proc_info(pid) if pid == nil or pid <= 0 or type(pid) ~= 'number' then error('invalid pid') end - local cmd = { 'ps', '-p', pid, '-o', 'ucomm=', } + local cmd = { 'ps', '-p', pid, '-o', 'comm=', } local err, name = _system(cmd) if 1 == err and string.gsub(name, '%s*', '') == '' then return {} -- Process not found. @@ -23,7 +23,7 @@ local function _os_proc_info(pid) end local _, ppid = _system({ 'ps', '-p', pid, '-o', 'ppid=', }) -- Remove trailing whitespace. - name = string.gsub(name, '%s+$', '') + name = string.gsub(string.gsub(name, '%s+$', ''), '^.*/', '') ppid = string.gsub(ppid, '%s+$', '') ppid = tonumber(ppid) == nil and -1 or tonumber(ppid) return { diff --git a/src/nvim/main.c b/src/nvim/main.c index 96c2168bca..af7c194edc 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -605,9 +605,14 @@ void getout(int exitval) buf_T *buf = wp->w_buffer; if (buf_get_changedtick(buf) != -1) { + bufref_T bufref; + + set_bufref(&bufref, buf); apply_autocmds(EVENT_BUFWINLEAVE, buf->b_fname, buf->b_fname, false, buf); - buf_set_changedtick(buf, -1); // note that we did it already + if (bufref_valid(&bufref)) { + buf_set_changedtick(buf, -1); // note that we did it already + } // start all over, autocommands may mess up the lists next_tp = first_tabpage; break; diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 6831e48719..61d944eb75 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -3955,7 +3955,7 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp) /* Don't count the last line break if 'noeol' and ('bin' or * 'nofixeol'). */ if ((!buf->b_p_fixeol || buf->b_p_bin) && !buf->b_p_eol - && buf->b_ml.ml_line_count == lnum) { + && lnum > buf->b_ml.ml_line_count) { size -= ffdos + 1; } } diff --git a/src/nvim/memory.c b/src/nvim/memory.c index d3d0968a5c..8789075c44 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -619,7 +619,6 @@ void free_all_mem(void) /* Obviously named calls. */ free_all_autocmds(); - free_all_options(); free_all_marks(); alist_clear(&global_alist); free_homedir(); @@ -657,6 +656,9 @@ void free_all_mem(void) /* Destroy all windows. Must come before freeing buffers. */ win_free_all(); + // Free all option values. Must come after closing windows. + free_all_options(); + free_cmdline_buf(); /* Clear registers. */ diff --git a/src/nvim/message.c b/src/nvim/message.c index 947cd0735e..4b0824c90f 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -610,7 +610,7 @@ static bool emsgfv(const char *fmt, va_list ap) /// detected when fuzzing vim. void iemsg(const char *s) { - msg((char_u *)s); + emsg((char_u *)s); #ifdef ABORT_ON_INTERNAL_ERROR abort(); #endif diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index 08d1df7f08..50cc9dae02 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -884,8 +884,7 @@ open_line ( && curbuf->b_p_lisp && curbuf->b_p_ai) { fixthisline(get_lisp_indent); - p = get_cursor_line_ptr(); - ai_col = (colnr_T)(skipwhite(p) - p); + ai_col = (colnr_T)getwhitecols_curline(); } /* * May do indenting after opening a new line. @@ -898,8 +897,7 @@ open_line ( ? KEY_OPEN_FORW : KEY_OPEN_BACK, ' ', linewhite(curwin->w_cursor.lnum))) { do_c_expr_indent(); - p = get_cursor_line_ptr(); - ai_col = (colnr_T)(skipwhite(p) - p); + ai_col = (colnr_T)getwhitecols_curline(); } if (vreplace_mode != 0) State = vreplace_mode; @@ -1607,11 +1605,19 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) char_u *oldp = ml_get(lnum); colnr_T oldlen = (colnr_T)STRLEN(oldp); - /* - * Can't do anything when the cursor is on the NUL after the line. - */ - if (col >= oldlen) + // Can't do anything when the cursor is on the NUL after the line. + if (col >= oldlen) { + return FAIL; + } + // If "count" is zero there is nothing to do. + if (count == 0) { + return OK; + } + // If "count" is negative the caller must be doing something wrong. + if (count < 1) { + IEMSGN("E950: Invalid count for del_bytes(): %ld", count); return FAIL; + } /* If 'delcombine' is set and deleting (less than) one character, only * delete the last combining character. */ diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 3244d83a93..bb4aea0986 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -505,6 +505,11 @@ end: static void unsubscribe(Channel *channel, char *event) { char *event_string = pmap_get(cstr_t)(event_strings, event); + if (!event_string) { + WLOG("RPC: ch %" PRIu64 ": tried to unsubscribe unknown event '%s'", + channel->id, event); + return; + } pmap_del(cstr_t)(channel->rpc.subscribed_events, event_string); map_foreach_value(channels, channel, { diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 8fdc660b68..0bf93ee001 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -2991,6 +2991,43 @@ void reset_VIsual(void) } } +// Check for a balloon-eval special item to include when searching for an +// identifier. When "dir" is BACKWARD "ptr[-1]" must be valid! +// Returns true if the character at "*ptr" should be included. +// "dir" is FORWARD or BACKWARD, the direction of searching. +// "*colp" is in/decremented if "ptr[-dir]" should also be included. +// "bnp" points to a counter for square brackets. +static bool find_is_eval_item( + const char_u *const ptr, + int *const colp, + int *const bnp, + const int dir) +{ + // Accept everything inside []. + if ((*ptr == ']' && dir == BACKWARD) || (*ptr == '[' && dir == FORWARD)) { + *bnp += 1; + } + if (*bnp > 0) { + if ((*ptr == '[' && dir == BACKWARD) || (*ptr == ']' && dir == FORWARD)) { + *bnp -= 1; + } + return true; + } + + // skip over "s.var" + if (*ptr == '.') { + return true; + } + + // two-character item: s->var + if (ptr[dir == BACKWARD ? 0 : 1] == '>' + && ptr[dir == BACKWARD ? -1 : 0] == '-') { + *colp += dir; + return true; + } + return false; +} + /* * Find the identifier under or to the right of the cursor. * "find_type" can have one of three values: @@ -3031,6 +3068,7 @@ size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol, int this_class = 0; int prev_class; int prevcol; + int bn = 0; // bracket nesting /* * if i == 0: try to find an identifier @@ -3042,30 +3080,40 @@ size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol, * 1. skip to start of identifier/string */ col = startcol; - if (has_mbyte) { - while (ptr[col] != NUL) { - this_class = mb_get_class(ptr + col); - if (this_class != 0 && (i == 1 || this_class != 1)) - break; - col += (*mb_ptr2len)(ptr + col); + while (ptr[col] != NUL) { + // Stop at a ']' to evaluate "a[x]". + if ((find_type & FIND_EVAL) && ptr[col] == ']') { + break; } - } else - while (ptr[col] != NUL - && (i == 0 ? !vim_iswordc(ptr[col]) : ascii_iswhite(ptr[col])) - ) - ++col; + this_class = mb_get_class(ptr + col); + if (this_class != 0 && (i == 1 || this_class != 1)) { + break; + } + col += utfc_ptr2len(ptr + col); + } + // When starting on a ']' count it, so that we include the '['. + bn = ptr[col] == ']'; // // 2. Back up to start of identifier/string. // // Remember class of character under cursor. - this_class = mb_get_class(ptr + col); + if ((find_type & FIND_EVAL) && ptr[col] == ']') { + this_class = mb_get_class((char_u *)"a"); + } else { + this_class = mb_get_class(ptr + col); + } while (col > 0 && this_class != 0) { prevcol = col - 1 - utf_head_off(ptr, ptr + col - 1); prev_class = mb_get_class(ptr + prevcol); if (this_class != prev_class - && (i == 0 || prev_class == 0 || (find_type & FIND_IDENT))) { + && (i == 0 + || prev_class == 0 + || (find_type & FIND_IDENT)) + && (!(find_type & FIND_EVAL) + || prevcol == 0 + || !find_is_eval_item(ptr + prevcol, &prevcol, &bn, BACKWARD))) { break; } col = prevcol; @@ -3096,21 +3144,20 @@ size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol, /* * 3. Find the end if the identifier/string. */ + bn = 0; + startcol -= col; col = 0; - if (has_mbyte) { - /* Search for point of changing multibyte character class. */ - this_class = mb_get_class(ptr); - while (ptr[col] != NUL - && ((i == 0 ? mb_get_class(ptr + col) == this_class - : mb_get_class(ptr + col) != 0) - )) - col += (*mb_ptr2len)(ptr + col); - } else - while ((i == 0 ? vim_iswordc(ptr[col]) - : (ptr[col] != NUL && !ascii_iswhite(ptr[col]))) - ) { - ++col; - } + // Search for point of changing multibyte character class. + this_class = mb_get_class(ptr); + while (ptr[col] != NUL + && ((i == 0 + ? mb_get_class(ptr + col) == this_class + : mb_get_class(ptr + col) != 0) + || ((find_type & FIND_EVAL) + && col <= (int)startcol + && find_is_eval_item(ptr + col, &col, &bn, FORWARD)))) { + col += utfc_ptr2len(ptr + col); + } assert(col >= 0); return (size_t)col; diff --git a/src/nvim/ops.c b/src/nvim/ops.c index a3fd6e11fd..23948df769 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -1110,7 +1110,7 @@ int insert_reg( ) { int retval = OK; - int allocated; + bool allocated; /* * It is possible to get into an endless loop by having CTRL-R a in @@ -1187,82 +1187,92 @@ static void stuffescaped(const char *arg, int literally) } } -/* - * If "regname" is a special register, return TRUE and store a pointer to its - * value in "argp". - */ -int get_spec_reg( +// If "regname" is a special register, return true and store a pointer to its +// value in "argp". +bool get_spec_reg( int regname, char_u **argp, - int *allocated, /* return: TRUE when value was allocated */ - int errmsg /* give error message when failing */ + bool *allocated, // return: true when value was allocated + bool errmsg // give error message when failing ) { size_t cnt; *argp = NULL; - *allocated = FALSE; + *allocated = false; switch (regname) { case '%': /* file name */ if (errmsg) check_fname(); /* will give emsg if not set */ *argp = curbuf->b_fname; - return TRUE; + return true; - case '#': /* alternate file name */ - *argp = getaltfname(errmsg); /* may give emsg if not set */ - return TRUE; + case '#': // alternate file name + *argp = getaltfname(errmsg); // may give emsg if not set + return true; case '=': /* result of expression */ *argp = get_expr_line(); - *allocated = TRUE; - return TRUE; + *allocated = true; + return true; case ':': /* last command line */ if (last_cmdline == NULL && errmsg) EMSG(_(e_nolastcmd)); *argp = last_cmdline; - return TRUE; + return true; case '/': /* last search-pattern */ if (last_search_pat() == NULL && errmsg) EMSG(_(e_noprevre)); *argp = last_search_pat(); - return TRUE; + return true; case '.': /* last inserted text */ *argp = get_last_insert_save(); - *allocated = TRUE; - if (*argp == NULL && errmsg) + *allocated = true; + if (*argp == NULL && errmsg) { EMSG(_(e_noinstext)); - return TRUE; + } + return true; - case Ctrl_F: /* Filename under cursor */ - case Ctrl_P: /* Path under cursor, expand via "path" */ - if (!errmsg) - return FALSE; - *argp = file_name_at_cursor(FNAME_MESS | FNAME_HYP - | (regname == Ctrl_P ? FNAME_EXP : 0), 1L, NULL); - *allocated = TRUE; - return TRUE; + case Ctrl_F: // Filename under cursor + case Ctrl_P: // Path under cursor, expand via "path" + if (!errmsg) { + return false; + } + *argp = file_name_at_cursor( + FNAME_MESS | FNAME_HYP | (regname == Ctrl_P ? FNAME_EXP : 0), + 1L, NULL); + *allocated = true; + return true; - case Ctrl_W: /* word under cursor */ - case Ctrl_A: /* WORD (mnemonic All) under cursor */ - if (!errmsg) - return FALSE; + case Ctrl_W: // word under cursor + case Ctrl_A: // WORD (mnemonic All) under cursor + if (!errmsg) { + return false; + } cnt = find_ident_under_cursor(argp, (regname == Ctrl_W ? (FIND_IDENT|FIND_STRING) : FIND_STRING)); *argp = cnt ? vim_strnsave(*argp, cnt) : NULL; - *allocated = TRUE; - return TRUE; + *allocated = true; + return true; + + case Ctrl_L: // Line under cursor + if (!errmsg) { + return false; + } + + *argp = ml_get_buf(curwin->w_buffer, curwin->w_cursor.lnum, false); + return true; case '_': /* black hole: always empty */ *argp = (char_u *)""; - return TRUE; + return true; } - return FALSE; + return false; } /// Paste a yank register into the command line. @@ -2004,6 +2014,7 @@ void op_insert(oparg_T *oap, long count1) { long ins_len, pre_textlen = 0; char_u *firstline, *ins_text; + colnr_T ind_pre = 0; struct block_def bd; int i; pos_T t1; @@ -2034,9 +2045,13 @@ void op_insert(oparg_T *oap, long count1) } // Get the info about the block before entering the text block_prep(oap, &bd, oap->start.lnum, true); + // Get indent information + ind_pre = (colnr_T)getwhitecols_curline(); firstline = ml_get(oap->start.lnum) + bd.textcol; - if (oap->op_type == OP_APPEND) + + if (oap->op_type == OP_APPEND) { firstline += bd.textlen; + } pre_textlen = (long)STRLEN(firstline); } @@ -2089,10 +2104,23 @@ void op_insert(oparg_T *oap, long count1) if (oap->motion_type == kMTBlockWise) { struct block_def bd2; - - /* The user may have moved the cursor before inserting something, try - * to adjust the block for that. */ - if (oap->start.lnum == curbuf->b_op_start_orig.lnum && !bd.is_MAX) { + bool did_indent = false; + + // if indent kicked in, the firstline might have changed + // but only do that, if the indent actually increased + const colnr_T ind_post = (colnr_T)getwhitecols_curline(); + if (curbuf->b_op_start.col > ind_pre && ind_post > ind_pre) { + bd.textcol += ind_post - ind_pre; + bd.start_vcol += ind_post - ind_pre; + did_indent = true; + } + + // The user may have moved the cursor before inserting something, try + // to adjust the block for that. But only do it, if the difference + // does not come from indent kicking in. + if (oap->start.lnum == curbuf->b_op_start_orig.lnum + && !bd.is_MAX + && !did_indent) { if (oap->op_type == OP_INSERT && oap->start.col + oap->start.coladd != curbuf->b_op_start_orig.col + curbuf->b_op_start_orig.coladd) { @@ -2206,7 +2234,7 @@ int op_change(oparg_T *oap) } firstline = ml_get(oap->start.lnum); pre_textlen = (long)STRLEN(firstline); - pre_indent = (long)(skipwhite(firstline) - firstline); + pre_indent = (long)getwhitecols(firstline); bd.textcol = curwin->w_cursor.col; } @@ -2227,7 +2255,7 @@ int op_change(oparg_T *oap) // the indent, exclude that indent change from the inserted text. firstline = ml_get(oap->start.lnum); if (bd.textcol > (colnr_T)pre_indent) { - long new_indent = (long)(skipwhite(firstline) - firstline); + long new_indent = (long)getwhitecols(firstline); pre_textlen += new_indent - pre_indent; bd.textcol += (colnr_T)(new_indent - pre_indent); @@ -2651,7 +2679,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) int lendiff = 0; pos_T old_pos; char_u *insert_string = NULL; - int allocated = FALSE; + bool allocated = false; long cnt; if (flags & PUT_FIXINDENT) @@ -2747,9 +2775,10 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) * For special registers '%' (file name), '#' (alternate file name) and * ':' (last command line), etc. we have to create a fake yank register. */ - if (get_spec_reg(regname, &insert_string, &allocated, TRUE)) { - if (insert_string == NULL) + if (get_spec_reg(regname, &insert_string, &allocated, true)) { + if (insert_string == NULL) { return; + } } if (!curbuf->terminal) { @@ -4108,10 +4137,9 @@ format_lines ( if (next_leader_len > 0) { (void)del_bytes(next_leader_len, false, false); mark_col_adjust(curwin->w_cursor.lnum, (colnr_T)0, 0L, - (long)-next_leader_len); - } else if (second_indent > 0) { /* the "leader" for FO_Q_SECOND */ - char_u *p = get_cursor_line_ptr(); - int indent = (int)(skipwhite(p) - p); + (long)-next_leader_len); + } else if (second_indent > 0) { // the "leader" for FO_Q_SECOND + int indent = (int)getwhitecols_curline(); if (indent > 0) { (void)del_bytes(indent, FALSE, FALSE); @@ -4906,10 +4934,11 @@ void *get_reg_contents(int regname, int flags) return NULL; char_u *retval; - int allocated; - if (get_spec_reg(regname, &retval, &allocated, FALSE)) { - if (retval == NULL) + bool allocated; + if (get_spec_reg(regname, &retval, &allocated, false)) { + if (retval == NULL) { return NULL; + } if (allocated) { return get_reg_wrap_one_line(retval, flags); } diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 7cd11e5c31..f8bec30988 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -1771,10 +1771,9 @@ return { { full_name='printheader', abbreviation='pheader', type='string', scope={'global'}, - gettext=true, vi_def=true, varname='p_header', - defaults={if_true={vi=N_("%<%f%h%m%=Page %N")}} + defaults={if_true={vi="%<%f%h%m%=Page %N"}} }, { full_name='printmbcharset', abbreviation='pmbcs', diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index 6997156d4c..2f90d0bc9e 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -457,12 +457,15 @@ void expand_env_esc(char_u *restrict srcp, } else if ((src[0] == ' ' || src[0] == ',') && !one) { at_start = true; } - *dst++ = *src++; - --dstlen; + if (dstlen > 0) { + *dst++ = *src++; + dstlen--; - if (prefix != NULL && src - prefix_len >= srcp - && STRNCMP(src - prefix_len, prefix, prefix_len) == 0) { - at_start = true; + if (prefix != NULL + && src - prefix_len >= srcp + && STRNCMP(src - prefix_len, prefix, prefix_len) == 0) { + at_start = true; + } } } } diff --git a/src/nvim/screen.c b/src/nvim/screen.c index b79e8f4546..5a43f1b619 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -149,7 +149,7 @@ void redraw_later(int type) void redraw_win_later(win_T *wp, int type) { - if (wp->w_redr_type < type) { + if (!exiting && wp->w_redr_type < type) { wp->w_redr_type = type; if (type >= NOT_VALID) wp->w_lines_valid = 0; @@ -1448,7 +1448,11 @@ static void win_update(win_T *wp) wp->w_lines[idx].wl_lnum = lnum; wp->w_lines[idx].wl_valid = true; - if (row > wp->w_height) { // past end of screen + + // Past end of the window or end of the screen. Note that after + // resizing wp->w_height may be end up too big. That's a problem + // elsewhere, but prevent a crash here. + if (row > wp->w_height || row + wp->w_winrow >= Rows) { // we may need the size of that too long line later on if (dollar_vcol == -1) { wp->w_lines[idx].wl_size = plines_win(wp, lnum, true); @@ -2460,9 +2464,10 @@ win_line ( ptr = line; if (has_spell) { - /* For checking first word with a capital skip white space. */ - if (cap_col == 0) - cap_col = (int)(skipwhite(line) - line); + // For checking first word with a capital skip white space. + if (cap_col == 0) { + cap_col = (int)getwhitecols(line); + } /* To be able to spell-check over line boundaries copy the end of the * current line into nextline[]. Above the start of the next line was diff --git a/src/nvim/search.c b/src/nvim/search.c index 97975c037b..9280bb6fa4 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -521,7 +521,7 @@ int searchit( buffer without a window! */ buf_T *buf, pos_T *pos, - int dir, + Direction dir, char_u *pat, long count, int options, @@ -570,8 +570,12 @@ int searchit( && pos->lnum >= 1 && pos->lnum <= buf->b_ml.ml_line_count && pos->col < MAXCOL - 2) { // Watch out for the "col" being MAXCOL - 2, used in a closed fold. - ptr = ml_get_buf(buf, pos->lnum, false) + pos->col; - start_char_len = *ptr == NUL ? 1 : (*mb_ptr2len)(ptr); + ptr = ml_get_buf(buf, pos->lnum, false); + if ((int)STRLEN(ptr) <= pos->col) { + start_char_len = 1; + } else { + start_char_len = utfc_ptr2len(ptr + pos->col); + } } else { start_char_len = 1; } @@ -1846,7 +1850,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) } else { /* Searching backwards */ /* * A comment may contain / * or / /, it may also start or end - * with / * /. Ignore a / * after / /. + * with / * /. Ignore a / * after / / and after *. */ if (pos.col == 0) continue; @@ -1871,6 +1875,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) } } else if ( linep[pos.col - 1] == '/' && linep[pos.col] == '*' + && (pos.col == 1 || linep[pos.col - 2] != '*') && (int)pos.col < comment_col) { count++; match_pos = pos; @@ -2203,21 +2208,17 @@ showmatch( } } -/* - * findsent(dir, count) - Find the start of the next sentence in direction - * "dir" Sentences are supposed to end in ".", "!" or "?" followed by white - * space or a line break. Also stop at an empty line. - * Return OK if the next sentence was found. - */ -int findsent(int dir, long count) +// Find the start of the next sentence, searching in the direction specified +// by the "dir" argument. The cursor is positioned on the start of the next +// sentence when found. If the next sentence is found, return OK. Return FAIL +// otherwise. See ":h sentence" for the precise definition of a "sentence" +// text object. +int findsent(Direction dir, long count) { pos_T pos, tpos; int c; int (*func)(pos_T *); - int startlnum; - int noskip = FALSE; /* do not skip blanks */ - int cpo_J; - int found_dot; + bool noskip = false; // do not skip blanks pos = curwin->w_cursor; if (dir == FORWARD) @@ -2251,30 +2252,30 @@ int findsent(int dir, long count) decl(&pos); } - // go back to the previous non-blank char - found_dot = false; - while ((c = gchar_pos(&pos)) == ' ' || c == '\t' - || (dir == BACKWARD - && vim_strchr((char_u *)".!?)]\"'", c) != NULL)) { - if (vim_strchr((char_u *)".!?", c) != NULL) { - /* Only skip over a '.', '!' and '?' once. */ - if (found_dot) - break; - found_dot = TRUE; + // go back to the previous non-white non-punctuation character + bool found_dot = false; + while (c = gchar_pos(&pos), ascii_iswhite(c) + || vim_strchr((char_u *)".!?)]\"'", c) != NULL) { + tpos = pos; + if (decl(&tpos) == -1 || (LINEEMPTY(tpos.lnum) && dir == FORWARD)) { + break; } - if (decl(&pos) == -1) { + if (found_dot) { break; } - // when going forward: Stop in front of empty line - if (LINEEMPTY(pos.lnum) && dir == FORWARD) { - incl(&pos); - goto found; + if (vim_strchr((char_u *) ".!?", c) != NULL) { + found_dot = true; + } + if (vim_strchr((char_u *) ")]\"'", c) != NULL + && vim_strchr((char_u *) ".!?)]\"'", gchar_pos(&tpos)) == NULL) { + break; } + decl(&pos); } - /* remember the line where the search started */ - startlnum = pos.lnum; - cpo_J = vim_strchr(p_cpo, CPO_ENDOFSENT) != NULL; + // remember the line where the search started + const int startlnum = pos.lnum; + const bool cpo_J = vim_strchr(p_cpo, CPO_ENDOFSENT) != NULL; for (;; ) { /* find end of sentence */ c = gchar_pos(&pos); @@ -2302,7 +2303,7 @@ int findsent(int dir, long count) if ((*func)(&pos) == -1) { if (count) return FAIL; - noskip = TRUE; + noskip = true; break; } } @@ -3296,7 +3297,7 @@ int current_tagblock( oparg_T *oap, long count_arg, - int include /* TRUE == include white space */ + bool include // true == include white space ) { long count = count_arg; @@ -3310,7 +3311,7 @@ current_tagblock( char_u *cp; int len; int r; - int do_include = include; + bool do_include = include; bool save_p_ws = p_ws; int retval = FAIL; int is_inclusive = true; @@ -3436,10 +3437,12 @@ again: } curwin->w_cursor = end_pos; - /* If we now have the same text as before reset "do_include" and try - * again. */ - if (equalpos(start_pos, old_start) && equalpos(end_pos, old_end)) { - do_include = TRUE; + // If we are in Visual mode and now have the same text as before set + // "do_include" and try again. + if (VIsual_active + && equalpos(start_pos, old_start) + && equalpos(end_pos, old_end)) { + do_include = true; curwin->w_cursor = old_start; count = count_arg; goto again; @@ -3948,8 +3951,10 @@ current_search( if (VIsual_active && *p_sel == 'e' && lt(VIsual, curwin->w_cursor)) dec_cursor(); - pos_T orig_pos; /* position of the cursor at beginning */ - pos_T pos; /* position after the pattern */ + pos_T orig_pos; // position of the cursor at beginning + pos_T first_match; // position of first match + pos_T pos; // position after the pattern + int result; // result of various function calls if (VIsual_active) { orig_pos = pos = curwin->w_cursor; @@ -3964,8 +3969,9 @@ current_search( orig_pos = pos = curwin->w_cursor; } - // Is the pattern is zero-width? - int one_char = is_one_char(spats[last_idx].pat, true, &curwin->w_cursor); + // Is the pattern is zero-width?, this time, don't care about the direction + int one_char = is_one_char(spats[last_idx].pat, true, &curwin->w_cursor, + FORWARD); if (one_char == -1) { p_ws = old_p_ws; return FAIL; /* pattern not found */ @@ -3983,9 +3989,9 @@ current_search( if (!dir && !one_char) flags = SEARCH_END; - int result = searchit(curwin, curbuf, &pos, (dir ? FORWARD : BACKWARD), - spats[last_idx].pat, i ? count : 1, - SEARCH_KEEP | flags, RE_SEARCH, 0, NULL); + result = searchit(curwin, curbuf, &pos, (dir ? FORWARD : BACKWARD), + spats[last_idx].pat, i ? count : 1, + SEARCH_KEEP | flags, RE_SEARCH, 0, NULL); /* First search may fail, but then start searching from the * beginning of the file (cursor might be on the search match) @@ -4007,15 +4013,19 @@ current_search( ml_get(curwin->w_buffer->b_ml.ml_line_count)); } } + if (i == 0) { + first_match = pos; + } p_ws = old_p_ws; } - int flags = forward ? SEARCH_END : 0; + const int flags = forward ? SEARCH_END : SEARCH_START; pos_T start_pos = pos; + const Direction direction = forward ? FORWARD : BACKWARD; // Check again from the current cursor position, // since the next match might actually be only one char wide - one_char = is_one_char(spats[last_idx].pat, false, &pos); + one_char = is_one_char(spats[last_idx].pat, false, &pos, direction); if (one_char < 0) { // search failed, abort return FAIL; @@ -4023,9 +4033,26 @@ current_search( /* move to match, except for zero-width matches, in which case, we are * already on the next match */ - if (!one_char) - searchit(curwin, curbuf, &pos, (forward ? FORWARD : BACKWARD), - spats[last_idx].pat, 0L, flags | SEARCH_KEEP, RE_SEARCH, 0, NULL); + if (!one_char) { + p_ws = false; + for (int i = 0; i < 2; i++) { + result = searchit(curwin, curbuf, &pos, direction, + spats[last_idx].pat, 0L, flags | SEARCH_KEEP, RE_SEARCH, + 0, NULL); + // Search successfull, break out from the loop + if (result) { + break; + } + // search failed, try again from the last search position match + pos = first_match; + } + } + + p_ws = old_p_ws; + // not found + if (!result) { + return FAIL; + } if (!VIsual_active) VIsual = start_pos; @@ -4059,8 +4086,10 @@ current_search( /// Check if the pattern is one character long or zero-width. /// If move is true, check from the beginning of the buffer, /// else from position "cur". +/// "direction" is FORWARD or BACKWARD. /// Returns TRUE, FALSE or -1 for failure. -static int is_one_char(char_u *pattern, bool move, pos_T *cur) +static int is_one_char(char_u *pattern, bool move, pos_T *cur, + Direction direction) { regmmatch_T regmatch; int nmatched = 0; @@ -4087,7 +4116,7 @@ static int is_one_char(char_u *pattern, bool move, pos_T *cur) // accept a match at the cursor position flag = SEARCH_START; } - if (searchit(curwin, curbuf, &pos, FORWARD, pattern, 1, + if (searchit(curwin, curbuf, &pos, direction, pattern, 1, SEARCH_KEEP + flag, RE_SEARCH, 0, NULL) != FAIL) { // Zero-width pattern should match somewhere, then we can check if // start and end are in the same position. @@ -4099,7 +4128,9 @@ static int is_one_char(char_u *pattern, bool move, pos_T *cur) if (!nmatched) { break; } - } while (regmatch.startpos[0].col < pos.col); + } while (direction == FORWARD + ? regmatch.startpos[0].col < pos.col + : regmatch.startpos[0].col > pos.col); if (!called_emsg) { result = (nmatched != 0 @@ -4599,7 +4630,7 @@ search_line: if (depth == -1) { // match in current file if (l_g_do_tagpreview != 0) { - if (!GETFILE_SUCCESS(getfile(0, curwin_save->w_buffer->b_fname, + if (!GETFILE_SUCCESS(getfile(curwin_save->w_buffer->b_fnum, NULL, NULL, true, lnum, false))) { break; // failed to jump to file } @@ -4607,6 +4638,7 @@ search_line: setpcmark(); } curwin->w_cursor.lnum = lnum; + check_cursor(); } else { if (!GETFILE_SUCCESS(getfile(0, files[depth].name, NULL, true, files[depth].lnum, false))) { diff --git a/src/nvim/search.h b/src/nvim/search.h index cb50742990..cb094aab8c 100644 --- a/src/nvim/search.h +++ b/src/nvim/search.h @@ -4,7 +4,7 @@ #include <stdbool.h> #include <stdint.h> -#include "nvim/types.h" +#include "nvim/vim.h" #include "nvim/buffer_defs.h" #include "nvim/eval/typval.h" #include "nvim/normal.h" diff --git a/src/nvim/spell.c b/src/nvim/spell.c index f72c47bd15..0ac1dd95e2 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -1399,13 +1399,14 @@ spell_move_to ( capcol = 0; // For checking first word with a capital skip white space. - if (capcol == 0) - capcol = (int)(skipwhite(line) - line); - else if (curline && wp == curwin) { + if (capcol == 0) { + capcol = (int)getwhitecols(line); + } else if (curline && wp == curwin) { // For spellbadword(): check if first word needs a capital. - col = (int)(skipwhite(line) - line); - if (check_need_cap(lnum, col)) + col = (int)getwhitecols(line); + if (check_need_cap(lnum, col)) { capcol = col; + } // Need to get the line again, may have looked at the previous // one. @@ -2977,7 +2978,7 @@ static bool check_need_cap(linenr_T lnum, colnr_T col) line = get_cursor_line_ptr(); endcol = 0; - if ((int)(skipwhite(line) - line) >= (int)col) { + if (getwhitecols(line) >= (int)col) { // At start of line, check if previous line is empty or sentence // ends there. if (lnum == 1) diff --git a/src/nvim/strings.c b/src/nvim/strings.c index f24de72743..17c4a75a64 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -1217,6 +1217,7 @@ int vim_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap, str_arg_l = 3; zero_padding = 0; } else { + // Regular float number format[0] = '%'; size_t l = 1; if (force_sign) { @@ -1241,7 +1242,6 @@ int vim_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap, format[l] = (char)(fmt_spec == 'F' ? 'f' : fmt_spec); format[l + 1] = NUL; - // Regular float number str_arg_l = (size_t)snprintf(tmp, sizeof(tmp), format, f); assert(str_arg_l < sizeof(tmp)); diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 1e3dc04049..10cbd91e36 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -14,117 +14,42 @@ export NVIM_PRG := $(NVIM_PRG) export TMPDIR := $(abspath ../../../Xtest-tmpdir) SCRIPTS_DEFAULT = \ - test14.out \ - test24.out \ - test37.out \ - test42.out \ - test48.out \ - test52.out \ - test64.out \ + test14.out \ + test24.out \ + test37.out \ + test42.out \ + test48.out \ + test52.out \ + test64.out \ ifneq ($(OS),Windows_NT) SCRIPTS_DEFAULTS := $(SCRIPTS_DEFAULT) \ - test17.out \ - test49.out \ + test17.out \ + test49.out \ endif SCRIPTS ?= $(SCRIPTS_DEFAULT) # Tests using runtest.vim. -# Keep test_alot*.res as the last one, sort the others. -NEW_TESTS ?= \ - test_arabic.res \ - test_autocmd.res \ - test_bufwintabinfo.res \ - test_changedtick.res \ - test_charsearch.res \ - test_cindent.res \ - test_clientserver.res \ - test_close_count.res \ - test_cmdline.res \ - test_command_count.res \ - test_cscope.res \ - test_curswant.res \ - test_digraph.res \ - test_edit.res \ - test_erasebackword.res \ - test_exists.res \ - test_diffmode.res \ - test_farsi.res \ - test_file_size.res \ - test_filter_map.res \ - test_find_complete.res \ - test_fixeol.res \ - test_findfile.res \ - test_fnameescape.res \ - test_fold.res \ - test_ga.res \ - test_getvar.res \ - test_glob2regpat.res \ - test_gf.res \ - test_gn.res \ - test_hardcopy.res \ - test_help_tagjump.res \ - test_hide.res \ - test_highlight.res \ - test_history.res \ - test_hlsearch.res \ - test_increment.res \ - test_increment_dbcs.res \ - test_ins_complete.res \ - test_lambda.res \ - test_langmap.res \ - test_let.res \ - test_lineending.res \ - test_listdict.res \ - test_listchars.res \ - test_makeencoding.res \ - test_marks.res \ - test_match.res \ - test_matchadd_conceal.res \ - test_mksession.res \ - test_nested_function.res \ - test_normal.res \ - test_number.res \ - test_options.res \ - test_profile.res \ - test_put.res \ - test_python2.res \ - test_python3.res \ - test_quickfix.res \ - test_quotestar.res \ - test_recover.res \ - test_registers.res \ - test_retab.res \ - test_scrollbind.res \ - test_search.res \ - test_signs.res \ - test_smartindent.res \ - test_spell.res \ - test_stat.res \ - test_startup.res \ - test_substitute.res \ - test_swap.res \ - test_syntax.res \ - test_system.res \ - test_tab.res \ - test_tabpage.res \ - test_textobjects.res \ - test_timers.res \ - test_undo.res \ - test_usercommands.res \ - test_user_func.res \ - test_vimscript.res \ - test_visual.res \ - test_winbuf_close.res \ - test_window_id.res \ - test_windows_home.res \ - test_wordcount.res \ - test_writefile.res \ - test_alot_latin.res \ - test_alot_utf8.res \ - test_alot.res +NEW_TESTS_ALOT := test_alot_utf8 test_alot +NEW_TESTS_IN_ALOT := $(shell sed '/^source/ s/^source //;s/\.vim$$//' test_alot*.vim) +# Ignored tests. +# test_alot_latin1: Nvim does not allow setting encoding. +# test_arglist: ported to Lua, but kept for easier merging. +# test_autochdir: ported to Lua, but kept for easier merging. +# test_eval_func: used as include in old-style test (test_eval.in). +# test_listlbr: Nvim does not allow setting encoding. +# test_largefile: uses too much resources to run on CI. +NEW_TESTS_IGNORE := $(NEW_TESTS_IN_ALOT) $(NEW_TESTS_ALOT) \ + test_alot_latin \ + test_arglist \ + test_autochdir \ + test_eval_func \ + test_listlbr \ + test_largefile \ + +NEW_TESTS = $(addsuffix .res,$(sort $(filter-out $(NEW_TESTS_IGNORE),$(basename $(notdir $(wildcard test_*.vim))))) $(NEW_TESTS_ALOT)) SCRIPTS_GUI := test16.out @@ -218,6 +143,7 @@ test1.out: .gdbinit test1.in @/bin/sh runnvim.sh --oldesttest $(ROOT) $(NVIM_PRG) $* $(RUN_VIM) $*.in @rm -rf X* test.ok viminfo +# Explicit dependencies. test49.out: test49.vim nolog: diff --git a/src/nvim/testdir/test_blockedit.vim b/src/nvim/testdir/test_blockedit.vim new file mode 100644 index 0000000000..4a8d59952e --- /dev/null +++ b/src/nvim/testdir/test_blockedit.vim @@ -0,0 +1,20 @@ +" Test for block inserting +" +" TODO: rewrite test39.in into this new style test + +func Test_blockinsert_indent() + new + filetype plugin indent on + setlocal sw=2 et ft=vim + call setline(1, ['let a=[', ' ''eins'',', ' ''zwei'',', ' ''drei'']']) + call cursor(2, 3) + exe "norm! \<c-v>2jI\\ \<esc>" + call assert_equal(['let a=[', ' \ ''eins'',', ' \ ''zwei'',', ' \ ''drei'']'], + \ getline(1,'$')) + " reset to sane state + filetype off + bwipe! +endfunc + + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_bufwintabinfo.vim b/src/nvim/testdir/test_bufwintabinfo.vim index a592cd7b11..a6b4524cc0 100644 --- a/src/nvim/testdir/test_bufwintabinfo.vim +++ b/src/nvim/testdir/test_bufwintabinfo.vim @@ -39,17 +39,35 @@ function Test_getbufwintabinfo() let w2_id = win_getid() tabnew | let w3_id = win_getid() new | let w4_id = win_getid() - new | let w5_id = win_getid() + vert new | let w5_id = win_getid() call setwinvar(0, 'signal', 'green') tabfirst let winlist = getwininfo() call assert_equal(5, len(winlist)) + call assert_equal(winwidth(1), winlist[0].width) + call assert_equal(0, winlist[0].wincol) + let tablineheight = winlist[0].winrow == 1 ? 1 : 0 + call assert_equal(tablineheight, winlist[0].winrow) " tabline adds one + call assert_equal(winbufnr(2), winlist[1].bufnr) call assert_equal(winheight(2), winlist[1].height) + call assert_equal(0, winlist[1].wincol) + call assert_equal(tablineheight + winheight(1) + 1, winlist[1].winrow) + call assert_equal(1, winlist[2].winnr) + call assert_equal(tablineheight, winlist[2].winrow) + call assert_equal(0, winlist[2].wincol) + + call assert_equal(winlist[2].width + 1, winlist[3].wincol) + call assert_equal(0, winlist[4].wincol) + + call assert_equal(1, winlist[0].tabnr) + call assert_equal(1, winlist[1].tabnr) + call assert_equal(2, winlist[2].tabnr) call assert_equal(2, winlist[3].tabnr) + call assert_equal(2, winlist[4].tabnr) + call assert_equal('green', winlist[2].variables.signal) - call assert_equal(winwidth(1), winlist[0].width) call assert_equal(w4_id, winlist[3].winid) let winfo = getwininfo(w5_id)[0] call assert_equal(2, winfo.tabnr) diff --git a/src/nvim/testdir/test_cd.vim b/src/nvim/testdir/test_cd.vim index e573419bd0..770ed55b8d 100644 --- a/src/nvim/testdir/test_cd.vim +++ b/src/nvim/testdir/test_cd.vim @@ -8,6 +8,60 @@ endfunc func Test_cd_up_and_down() let path = getcwd() cd .. + call assert_notequal(path, getcwd()) exe 'cd ' . path call assert_equal(path, getcwd()) endfunc + +func Test_cd_no_arg() + if has('unix') + " Test that cd without argument goes to $HOME directory on Unix systems. + let path = getcwd() + cd + call assert_equal($HOME, getcwd()) + call assert_notequal(path, getcwd()) + exe 'cd ' . path + call assert_equal(path, getcwd()) + else + " Test that cd without argument echoes cwd on non-Unix systems. + call assert_match(getcwd(), execute('cd')) + endif +endfunc + +func Test_cd_minus() + " Test the :cd - goes back to the previous directory. + let path = getcwd() + cd .. + let path_dotdot = getcwd() + call assert_notequal(path, path_dotdot) + cd - + call assert_equal(path, getcwd()) + cd - + call assert_equal(path_dotdot, getcwd()) + cd - + call assert_equal(path, getcwd()) +endfunc + +func Test_cd_with_cpo_chdir() + e Xfoo + call setline(1, 'foo') + let path = getcwd() + " set cpo+=. + + " :cd should fail when buffer is modified and 'cpo' contains dot. + " call assert_fails('cd ..', 'E747:') + call assert_equal(path, getcwd()) + + " :cd with exclamation mark should succeed. + cd! .. + call assert_notequal(path, getcwd()) + + " :cd should succeed when buffer has been written. + w! + exe 'cd ' . path + call assert_equal(path, getcwd()) + + call delete('Xfoo') + set cpo& + bw! +endfunc diff --git a/src/nvim/testdir/test_cindent.vim b/src/nvim/testdir/test_cindent.vim index 444c4c4109..7c2c5e341c 100644 --- a/src/nvim/testdir/test_cindent.vim +++ b/src/nvim/testdir/test_cindent.vim @@ -68,9 +68,38 @@ func Test_cino_extern_c() call assert_equal(pair[2], getline(len(lines) + 1), 'Failed for "' . string(lines) . '"') endfor + bwipe! +endfunc +func Test_cindent_rawstring() + new + setl cindent + call feedkeys("i" . + \ "int main() {\<CR>" . + \ "R\"(\<CR>" . + \ ")\";\<CR>" . + \ "statement;\<Esc>", "x") + call assert_equal("\tstatement;", getline(line('.'))) + bw! +endfunc - bwipe! +func Test_cindent_expr() + new + func! MyIndentFunction() + return v:lnum == 1 ? shiftwidth() : 0 + endfunc + setl expandtab sw=8 indentkeys+=; indentexpr=MyIndentFunction() + call setline(1, ['var_a = something()', 'b = something()']) + call cursor(1, 1) + call feedkeys("^\<c-v>j$A;\<esc>", 'tnix') + call assert_equal([' var_a = something();', 'b = something();'], getline(1, '$')) + + %d + call setline(1, [' var_a = something()', ' b = something()']) + call cursor(1, 1) + call feedkeys("^\<c-v>j$A;\<esc>", 'tnix') + call assert_equal([' var_a = something();', ' b = something()'], getline(1, '$')) + bw! endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 5a43838218..c302948ba3 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -137,6 +137,11 @@ func Test_getcompletion() let l = getcompletion('v:notexists', 'var') call assert_equal([], l) + args a.c b.c + let l = getcompletion('', 'arglist') + call assert_equal(['a.c', 'b.c'], l) + %argdelete + let l = getcompletion('', 'augroup') call assert_true(index(l, 'END') >= 0) let l = getcompletion('blahblah', 'augroup') @@ -222,6 +227,11 @@ func Test_getcompletion() let l = getcompletion('not', 'messages') call assert_equal([], l) + let l = getcompletion('', 'mapclear') + call assert_true(index(l, '<buffer>') >= 0) + let l = getcompletion('not', 'mapclear') + call assert_equal([], l) + if has('cscope') let l = getcompletion('', 'cscope') let cmds = ['add', 'find', 'help', 'kill', 'reset', 'show'] @@ -311,6 +321,9 @@ func Test_paste_in_cmdline() call feedkeys("ft:aaa \<C-R>\<C-F> bbb\<C-B>\"\<CR>", 'tx') call assert_equal('"aaa /tmp/some bbb', @:) + call feedkeys(":aaa \<C-R>\<C-L> bbb\<C-B>\"\<CR>", 'tx') + call assert_equal('"aaa '.getline(1).' bbb', @:) + set incsearch call feedkeys("fy:aaa veryl\<C-R>\<C-W> bbb\<C-B>\"\<CR>", 'tx') call assert_equal('"aaa verylongword bbb', @:) @@ -375,6 +388,27 @@ func Test_cmdline_complete_user_cmd() delcommand Foo endfunc +func Test_cmdline_write_alternatefile() + new + call setline('.', ['one', 'two']) + f foo.txt + new + f #-A + call assert_equal('foo.txt-A', expand('%')) + f #<-B.txt + call assert_equal('foo-B.txt', expand('%')) + f %< + call assert_equal('foo-B', expand('%')) + new + call assert_fails('f #<', 'E95') + bw! + f foo-B.txt + f %<-A + call assert_equal('foo-B-A', expand('%')) + bw! + bw! +endfunc + " using a leading backslash here set cpo+=C @@ -430,6 +464,22 @@ func Test_getcmdtype() cunmap <F6> endfunc +func Test_getcmdwintype() + call feedkeys("q/:let a = getcmdwintype()\<CR>:q\<CR>", 'x!') + call assert_equal('/', a) + + call feedkeys("q?:let a = getcmdwintype()\<CR>:q\<CR>", 'x!') + call assert_equal('?', a) + + call feedkeys("q::let a = getcmdwintype()\<CR>:q\<CR>", 'x!') + call assert_equal(':', a) + + call feedkeys(":\<C-F>:let a = getcmdwintype()\<CR>:q\<CR>", 'x!') + call assert_equal(':', a) + + call assert_equal('', getcmdwintype()) +endfunc + func Test_verbosefile() set verbosefile=Xlog echomsg 'foo' @@ -440,4 +490,25 @@ func Test_verbosefile() call delete('Xlog') endfunc +func Test_setcmdpos() + func InsertTextAtPos(text, pos) + call assert_equal(0, setcmdpos(a:pos)) + return a:text + endfunc + + " setcmdpos() with position in the middle of the command line. + call feedkeys(":\"12\<C-R>=InsertTextAtPos('a', 3)\<CR>b\<CR>", 'xt') + call assert_equal('"1ab2', @:) + + call feedkeys(":\"12\<C-R>\<C-R>=InsertTextAtPos('a', 3)\<CR>b\<CR>", 'xt') + call assert_equal('"1b2a', @:) + + " setcmdpos() with position beyond the end of the command line. + call feedkeys(":\"12\<C-B>\<C-R>=InsertTextAtPos('a', 10)\<CR>b\<CR>", 'xt') + call assert_equal('"12ab', @:) + + " setcmdpos() returns 1 when not editing the command line. + call assert_equal(1, setcmdpos(3)) +endfunc + set cpo& diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index e2a035b0b2..8fde63b55f 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -701,6 +701,7 @@ endfunc func Test_byte2line_line2byte() new + set endofline call setline(1, ['a', 'bc', 'd']) set fileformat=unix @@ -721,7 +722,16 @@ func Test_byte2line_line2byte() call assert_equal([-1, -1, 1, 4, 8, 11, -1], \ map(range(-1, 5), 'line2byte(v:val)')) - set fileformat& + bw! + set noendofline nofixendofline + normal a- + for ff in ["unix", "mac", "dos"] + let &fileformat = ff + call assert_equal(1, line2byte(1)) + call assert_equal(2, line2byte(2)) " line2byte(line("$") + 1) is the buffer size plus one (as per :help line2byte). + endfor + + set endofline& fixendofline& fileformat& bw! endfunc diff --git a/src/nvim/testdir/test_gf.vim b/src/nvim/testdir/test_gf.vim index ef1bf1075b..c352379697 100644 --- a/src/nvim/testdir/test_gf.vim +++ b/src/nvim/testdir/test_gf.vim @@ -7,7 +7,8 @@ func Test_gf_url() \ "first test for URL://machine.name/tmp/vimtest2a and other text", \ "second test for URL://machine.name/tmp/vimtest2b. And other text", \ "third test for URL:\\\\machine.name\\vimtest2c and other text", - \ "fourth test for URL:\\\\machine.name\\tmp\\vimtest2d, and other text" + \ "fourth test for URL:\\\\machine.name\\tmp\\vimtest2d, and other text", + \ "fifth test for URL://machine.name/tmp?q=vim&opt=yes and other text", \ ]) call cursor(1,1) call search("^first") @@ -28,6 +29,10 @@ func Test_gf_url() call search("URL") call assert_equal("URL:\\\\machine.name\\tmp\\vimtest2d", expand("<cfile>")) + call search("^fifth") + call search("URL") + call assert_equal("URL://machine.name/tmp?q=vim&opt=yes", expand("<cfile>")) + set isf&vim enew! endfunc diff --git a/src/nvim/testdir/test_gn.vim b/src/nvim/testdir/test_gn.vim index b2a2937d88..405425a42b 100644 --- a/src/nvim/testdir/test_gn.vim +++ b/src/nvim/testdir/test_gn.vim @@ -5,51 +5,51 @@ func Test_gn_command() noautocmd new " replace a single char by itsself quoted: call setline('.', 'abc x def x ghi x jkl') - let @/='x' + let @/ = 'x' exe "norm! cgn'x'\<esc>.." call assert_equal("abc 'x' def 'x' ghi 'x' jkl", getline('.')) sil! %d_ " simple search match call setline('.', 'foobar') - let @/='foobar' + let @/ = 'foobar' exe "norm! gncsearchmatch" call assert_equal('searchmatch', getline('.')) sil! %d _ " replace a multi-line match call setline('.', ['', 'one', 'two']) - let @/='one\_s*two\_s' + let @/ = 'one\_s*two\_s' exe "norm! gnceins\<CR>zwei" call assert_equal(['','eins','zwei'], getline(1,'$')) sil! %d _ " test count argument call setline('.', ['', 'abcdx | abcdx | abcdx']) - let @/='[a]bcdx' + let @/ = '[a]bcdx' exe "norm! 2gnd" call assert_equal(['','abcdx | | abcdx'], getline(1,'$')) sil! %d _ " join lines call setline('.', ['join ', 'lines']) - let @/='$' + let @/ = '$' exe "norm! 0gnd" call assert_equal(['join lines'], getline(1,'$')) sil! %d _ " zero-width match call setline('.', ['', 'zero width pattern']) - let @/='\>\zs' + let @/ = '\>\zs' exe "norm! 0gnd" call assert_equal(['', 'zerowidth pattern'], getline(1,'$')) sil! %d _ " delete first and last chars call setline('.', ['delete first and last chars']) - let @/='^' + let @/ = '^' exe "norm! 0gnd$" - let @/='\zs' + let @/ = '\zs' exe "norm! gnd" call assert_equal(['elete first and last char'], getline(1,'$')) sil! %d _ @@ -62,14 +62,14 @@ func Test_gn_command() " backwards search call setline('.', ['my very excellent mother just served us nachos']) - let @/='mother' + let @/ = 'mother' exe "norm! $cgNmongoose" call assert_equal(['my very excellent mongoose just served us nachos'], getline(1,'$')) sil! %d _ " search for single char call setline('.', ['','for (i=0; i<=10; i++)']) - let @/='i' + let @/ = 'i' exe "norm! cgnj" call assert_equal(['','for (j=0; i<=10; i++)'], getline(1,'$')) sil! %d _ @@ -77,28 +77,28 @@ func Test_gn_command() " search hex char call setline('.', ['','Y']) set noignorecase - let @/='\%x59' + let @/ = '\%x59' exe "norm! gnd" call assert_equal(['',''], getline(1,'$')) sil! %d _ " test repeating gdn call setline('.', ['', '1', 'Johnny', '2', 'Johnny', '3']) - let @/='Johnny' + let @/ = 'Johnny' exe "norm! dgn." call assert_equal(['','1', '', '2', '', '3'], getline(1,'$')) sil! %d _ " test repeating gUgn call setline('.', ['', '1', 'Depp', '2', 'Depp', '3']) - let @/='Depp' + let @/ = 'Depp' exe "norm! gUgn." call assert_equal(['', '1', 'DEPP', '2', 'DEPP', '3'], getline(1,'$')) sil! %d _ " test using look-ahead assertions call setline('.', ['a:10', '', 'a:1', '', 'a:20']) - let @/='a:0\@!\zs\d\+' + let @/ = 'a:0\@!\zs\d\+' exe "norm! 2nygno\<esc>p" call assert_equal(['a:10', '', 'a:1', '1', '', 'a:20'], getline(1,'$')) sil! %d _ @@ -111,6 +111,24 @@ func Test_gn_command() call assert_equal(['foo baz'], getline(1,'$')) sil! %d_ + " search upwards with nowrapscan set + call setline('.', ['foo', 'bar', 'foo', 'baz']) + set nowrapscan + let @/ = 'foo' + $ + norm! dgN + call assert_equal(['foo', 'bar', '', 'baz'], getline(1,'$')) + sil! %d_ + + " search using the \zs atom + call setline(1, [' nnoremap', '' , 'nnoremap']) + set wrapscan&vim + let @/ = '\_s\zsnnoremap' + $ + norm! cgnmatch + call assert_equal([' nnoremap', '', 'match'], getline(1,'$')) + sil! %d_ + set wrapscan&vim set belloff&vim endfu diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim index c307e33cbf..5ff63e58ba 100644 --- a/src/nvim/testdir/test_ins_complete.vim +++ b/src/nvim/testdir/test_ins_complete.vim @@ -217,3 +217,22 @@ function Test_CompleteDoneList() let s:called_completedone = 0 au! CompleteDone endfunc + +func Test_omni_dash() + func Omni(findstart, base) + if a:findstart + return 5 + else + echom a:base + return ['-help', '-v'] + endif + endfunc + set omnifunc=Omni + new + exe "normal Gofind -\<C-x>\<C-o>" + call assert_equal("\n-\nmatch 1 of 2", execute(':2mess')) + + bwipe! + delfunc Omni + set omnifunc= +endfunc diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index 4c63bd1f71..d07b3fdbce 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -392,10 +392,31 @@ func! Test_normal10_expand() call setline(1, ['1', 'ifooar,,cbar']) 2 norm! $ - let a=expand('<cword>') - let b=expand('<cWORD>') - call assert_equal('cbar', a) - call assert_equal('ifooar,,cbar', b) + call assert_equal('cbar', expand('<cword>')) + call assert_equal('ifooar,,cbar', expand('<cWORD>')) + + call setline(1, ['prx = list[idx];']) + 1 + let expected = ['', 'prx', 'prx', 'prx', + \ 'list', 'list', 'list', 'list', 'list', 'list', 'list', + \ 'idx', 'idx', 'idx', 'idx', + \ 'list[idx]', + \ '];', + \ ] + for i in range(1, 16) + exe 'norm ' . i . '|' + call assert_equal(expected[i], expand('<cexpr>'), 'i == ' . i) + endfor + + if executable('echo') + " Test expand(`...`) i.e. backticks command expansion. + " MS-Windows has a trailing space. + call assert_match('^abcde *$', expand('`echo abcde`')) + endif + + " Test expand(`=...`) i.e. backticks expression expansion + call assert_equal('5', expand('`=2+3`')) + " clean up bw! endfunc @@ -1536,12 +1557,12 @@ fun! Test_normal29_brace() \ 'the ''{'' flag is in ''cpoptions'' then ''{'' in the first column is used as a', \ 'paragraph boundary |posix|.', \ '{', - \ 'This is no paragaraph', + \ 'This is no paragraph', \ 'unless the ''{'' is set', \ 'in ''cpoptions''', \ '}', \ '.IP', - \ 'The nroff macros IP seperates a paragraph', + \ 'The nroff macros IP separates a paragraph', \ 'That means, it must be a ''.''', \ 'followed by IP', \ '.LPIt does not matter, if afterwards some', @@ -1556,7 +1577,7 @@ fun! Test_normal29_brace() 1 norm! 0d2} call assert_equal(['.IP', - \ 'The nroff macros IP seperates a paragraph', 'That means, it must be a ''.''', 'followed by IP', + \ 'The nroff macros IP separates a paragraph', 'That means, it must be a ''.''', 'followed by IP', \ '.LPIt does not matter, if afterwards some', 'more characters follow.', '.SHAlso section boundaries from the nroff', \ 'macros terminate a paragraph. That means', 'a character like this:', '.NH', 'End of text here', ''], getline(1,'$')) norm! 0d} @@ -1576,21 +1597,21 @@ fun! Test_normal29_brace() " set cpo+={ " 1 " norm! 0d2} - " call assert_equal(['{', 'This is no paragaraph', 'unless the ''{'' is set', 'in ''cpoptions''', '}', - " \ '.IP', 'The nroff macros IP seperates a paragraph', 'That means, it must be a ''.''', + " call assert_equal(['{', 'This is no paragraph', 'unless the ''{'' is set', 'in ''cpoptions''', '}', + " \ '.IP', 'The nroff macros IP separates a paragraph', 'That means, it must be a ''.''', " \ 'followed by IP', '.LPIt does not matter, if afterwards some', 'more characters follow.', " \ '.SHAlso section boundaries from the nroff', 'macros terminate a paragraph. That means', " \ 'a character like this:', '.NH', 'End of text here', ''], getline(1,'$')) " $ " norm! d} - " call assert_equal(['{', 'This is no paragaraph', 'unless the ''{'' is set', 'in ''cpoptions''', '}', - " \ '.IP', 'The nroff macros IP seperates a paragraph', 'That means, it must be a ''.''', + " call assert_equal(['{', 'This is no paragraph', 'unless the ''{'' is set', 'in ''cpoptions''', '}', + " \ '.IP', 'The nroff macros IP separates a paragraph', 'That means, it must be a ''.''', " \ 'followed by IP', '.LPIt does not matter, if afterwards some', 'more characters follow.', " \ '.SHAlso section boundaries from the nroff', 'macros terminate a paragraph. That means', " \ 'a character like this:', '.NH', 'End of text here', ''], getline(1,'$')) " norm! gg} " norm! d5} - " call assert_equal(['{', 'This is no paragaraph', 'unless the ''{'' is set', 'in ''cpoptions''', '}', ''], getline(1,'$')) + " call assert_equal(['{', 'This is no paragraph', 'unless the ''{'' is set', 'in ''cpoptions''', '}', ''], getline(1,'$')) " clean up set cpo-={ @@ -1809,11 +1830,6 @@ fun! Test_normal33_g_cmd2() call assert_equal(15, col('.')) call assert_equal('l', getreg(0)) - " Test for g Ctrl-G - set ff=unix - let a=execute(":norm! g\<c-g>") - call assert_match('Col 15 of 43; Line 2 of 2; Word 2 of 2; Byte 16 of 45', a) - " Test for gI norm! gIfoo call assert_equal(['', 'fooabcdefghijk lmno0123456789AMNOPQRSTUVWXYZ'], getline(1,'$')) @@ -1832,6 +1848,81 @@ fun! Test_normal33_g_cmd2() bw! endfunc +func! Test_g_ctrl_g() + new + + let a = execute(":norm! g\<c-g>") + call assert_equal("\n--No lines in buffer--", a) + + call setline(1, ['first line', 'second line']) + + " Test g CTRL-g with dos, mac and unix file type. + norm! gojll + set ff=dos + let a = execute(":norm! g\<c-g>") + call assert_equal("\nCol 3 of 11; Line 2 of 2; Word 3 of 4; Byte 15 of 25", a) + + set ff=mac + let a = execute(":norm! g\<c-g>") + call assert_equal("\nCol 3 of 11; Line 2 of 2; Word 3 of 4; Byte 14 of 23", a) + + set ff=unix + let a = execute(":norm! g\<c-g>") + call assert_equal("\nCol 3 of 11; Line 2 of 2; Word 3 of 4; Byte 14 of 23", a) + + " Test g CTRL-g in visual mode (v) + let a = execute(":norm! gojllvlg\<c-g>") + call assert_equal("\nSelected 1 of 2 Lines; 1 of 4 Words; 2 of 23 Bytes", a) + + " Test g CTRL-g in visual mode (CTRL-V) with end col > start col + let a = execute(":norm! \<Esc>gojll\<C-V>kllg\<c-g>") + call assert_equal("\nSelected 3 Cols; 2 of 2 Lines; 2 of 4 Words; 6 of 23 Bytes", a) + + " Test g_CTRL-g in visual mode (CTRL-V) with end col < start col + let a = execute(":norm! \<Esc>goll\<C-V>jhhg\<c-g>") + call assert_equal("\nSelected 3 Cols; 2 of 2 Lines; 2 of 4 Words; 6 of 23 Bytes", a) + + " Test g CTRL-g in visual mode (CTRL-V) with end_vcol being MAXCOL + let a = execute(":norm! \<Esc>gojll\<C-V>k$g\<c-g>") + call assert_equal("\nSelected 2 of 2 Lines; 4 of 4 Words; 17 of 23 Bytes", a) + + " There should be one byte less with noeol + set bin noeol + let a = execute(":norm! \<Esc>gog\<c-g>") + call assert_equal("\nCol 1 of 10; Line 1 of 2; Word 1 of 4; Char 1 of 23; Byte 1 of 22", a) + set bin & eol& + + if has('multi_byte') + call setline(1, ['Français', '日本語']) + + let a = execute(":norm! \<Esc>gojlg\<c-g>") + call assert_equal("\nCol 4-3 of 9-6; Line 2 of 2; Word 2 of 2; Char 11 of 13; Byte 16 of 20", a) + + let a = execute(":norm! \<Esc>gojvlg\<c-g>") + call assert_equal("\nSelected 1 of 2 Lines; 1 of 2 Words; 2 of 13 Chars; 6 of 20 Bytes", a) + + let a = execute(":norm! \<Esc>goll\<c-v>jlg\<c-g>") + call assert_equal("\nSelected 4 Cols; 2 of 2 Lines; 2 of 2 Words; 6 of 13 Chars; 11 of 20 Bytes", a) + + set fenc=utf8 bomb + let a = execute(":norm! \<Esc>gojlg\<c-g>") + call assert_equal("\nCol 4-3 of 9-6; Line 2 of 2; Word 2 of 2; Char 11 of 13; Byte 16 of 20(+3 for BOM)", a) + + set fenc=utf16 bomb + let a = execute(":norm! g\<c-g>") + call assert_equal("\nCol 4-3 of 9-6; Line 2 of 2; Word 2 of 2; Char 11 of 13; Byte 16 of 20(+2 for BOM)", a) + + set fenc=utf32 bomb + let a = execute(":norm! g\<c-g>") + call assert_equal("\nCol 4-3 of 9-6; Line 2 of 2; Word 2 of 2; Char 11 of 13; Byte 16 of 20(+4 for BOM)", a) + + set fenc& bomb& + endif + + set ff& + bwipe! +endfunc + fun! Test_normal34_g_cmd3() if !has("multi_byte") return @@ -2180,10 +2271,11 @@ func! Test_normal44_textobjects2() endfunc func! Test_normal45_drop() - if !has("dnd") + if !has('dnd') return endif - " basic test for :drop command + + " basic test for drag-n-drop " unfortunately, without a gui, we can't really test much here, " so simply test that ~p fails (which uses the drop register) new diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index f0aec42ae1..62d40f71af 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -338,4 +338,20 @@ func Test_copy_winopt() bnext call assert_equal(4,&numberwidth) bw! + + set hidden& +endfunc + +func Test_shortmess_F() + new + call assert_match('\[No Name\]', execute('file')) + set shortmess+=F + call assert_match('\[No Name\]', execute('file')) + call assert_match('^\s*$', execute('file foo')) + call assert_match('foo', execute('file')) + set shortmess-=F + call assert_match('bar', execute('file bar')) + call assert_match('bar', execute('file')) + set shortmess& + bwipe endfunc diff --git a/src/nvim/testdir/test_preview.vim b/src/nvim/testdir/test_preview.vim new file mode 100644 index 0000000000..91923fb1e9 --- /dev/null +++ b/src/nvim/testdir/test_preview.vim @@ -0,0 +1,13 @@ +" Tests for the preview window + +func Test_Psearch() + " this used to cause ml_get errors + help + let wincount = winnr('$') + 0f + ps. + call assert_equal(wincount + 1, winnr('$')) + pclose + call assert_equal(wincount, winnr('$')) + bwipe +endfunc diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim index 7b77402115..638c6802d4 100644 --- a/src/nvim/testdir/test_startup.vim +++ b/src/nvim/testdir/test_startup.vim @@ -264,3 +264,27 @@ func Test_default_term() call assert_match('nvim', out) let $TERM = save_term endfunc + +func Test_zzz_startinsert() + " Test :startinsert + call writefile(['123456'], 'Xtestout') + let after = [ + \ ':startinsert', + \ 'call feedkeys("foobar\<c-o>:wq\<cr>","t")' + \ ] + if RunVim([], after, 'Xtestout') + let lines = readfile('Xtestout') + call assert_equal(['foobar123456'], lines) + endif + " Test :startinsert! + call writefile(['123456'], 'Xtestout') + let after = [ + \ ':startinsert!', + \ 'call feedkeys("foobar\<c-o>:wq\<cr>","t")' + \ ] + if RunVim([], after, 'Xtestout') + let lines = readfile('Xtestout') + call assert_equal(['123456foobar'], lines) + endif + call delete('Xtestout') +endfunc diff --git a/src/nvim/testdir/test_tabpage.vim b/src/nvim/testdir/test_tabpage.vim index a2eec2cc11..add9b3d7cf 100644 --- a/src/nvim/testdir/test_tabpage.vim +++ b/src/nvim/testdir/test_tabpage.vim @@ -42,40 +42,38 @@ function Test_tabpage() call assert_true(t:val_num == 100 && t:val_str == 'SetTabVar test' && t:val_list == ['red', 'blue', 'green']) tabclose - if has('nvim') || has('gui') || has('clientserver') - " Test for ":tab drop exist-file" to keep current window. - sp test1 - tab drop test1 - call assert_true(tabpagenr('$') == 1 && winnr('$') == 2 && winnr() == 1) - close - " - " - " Test for ":tab drop new-file" to keep current window of tabpage 1. - split - tab drop newfile - call assert_true(tabpagenr('$') == 2 && tabpagewinnr(1, '$') == 2 && tabpagewinnr(1) == 1) - tabclose - q - " - " - " Test for ":tab drop multi-opend-file" to keep current tabpage and window. - new test1 - tabnew - new test1 - tab drop test1 - call assert_true(tabpagenr() == 2 && tabpagewinnr(2, '$') == 2 && tabpagewinnr(2) == 1) - tabclose - q - " - " - " Test for ":tab drop vertical-split-window" to jump test1 buffer - tabedit test1 - vnew - tabfirst - tab drop test1 - call assert_equal([2, 2, 2, 2], [tabpagenr('$'), tabpagenr(), tabpagewinnr(2, '$'), tabpagewinnr(2)]) - 1tabonly - endif + " Test for ":tab drop exist-file" to keep current window. + sp test1 + tab drop test1 + call assert_true(tabpagenr('$') == 1 && winnr('$') == 2 && winnr() == 1) + close + " + " + " Test for ":tab drop new-file" to keep current window of tabpage 1. + split + tab drop newfile + call assert_true(tabpagenr('$') == 2 && tabpagewinnr(1, '$') == 2 && tabpagewinnr(1) == 1) + tabclose + q + " + " + " Test for ":tab drop multi-opend-file" to keep current tabpage and window. + new test1 + tabnew + new test1 + tab drop test1 + call assert_true(tabpagenr() == 2 && tabpagewinnr(2, '$') == 2 && tabpagewinnr(2) == 1) + tabclose + q + " + " + " Test for ":tab drop vertical-split-window" to jump test1 buffer + tabedit test1 + vnew + tabfirst + tab drop test1 + call assert_equal([2, 2, 2, 2], [tabpagenr('$'), tabpagenr(), tabpagewinnr(2, '$'), tabpagewinnr(2)]) + 1tabonly " " for i in range(9) | tabnew | endfor diff --git a/src/nvim/testdir/test_textobjects.vim b/src/nvim/testdir/test_textobjects.vim index 684f197f5f..6a2f5044cc 100644 --- a/src/nvim/testdir/test_textobjects.vim +++ b/src/nvim/testdir/test_textobjects.vim @@ -121,6 +121,23 @@ func Test_string_html_objects() enew! endfunc +func Test_empty_html_tag() + new + call setline(1, '<div></div>') + normal 0citxxx + call assert_equal('<div>xxx</div>', getline(1)) + + call setline(1, '<div></div>') + normal 0f<cityyy + call assert_equal('<div>yyy</div>', getline(1)) + + call setline(1, '<div></div>') + normal 0f<vitsaaa + call assert_equal('aaa', getline(1)) + + bwipe! +endfunc + " Tests for match() and matchstr() func Test_match() call assert_equal("b", matchstr("abcd", ".", 0, 2)) @@ -152,3 +169,91 @@ func Test_match() call assert_equal(3 , match('abc', '\zs', 3, 1)) call assert_equal(-1, match('abc', '\zs', 4, 1)) endfunc + +" This was causing an illegal memory access +func Test_inner_tag() + new + norm ixxx + call feedkeys("v", 'xt') + insert +x +x +. + norm it + q! +endfunc + +func Test_sentence() + enew! + call setline(1, 'A sentence. A sentence? A sentence!') + + normal yis + call assert_equal('A sentence.', @") + normal yas + call assert_equal('A sentence. ', @") + + normal ) + + normal yis + call assert_equal('A sentence?', @") + normal yas + call assert_equal('A sentence? ', @") + + normal ) + + normal yis + call assert_equal('A sentence!', @") + normal yas + call assert_equal(' A sentence!', @") + + normal 0 + normal 2yis + call assert_equal('A sentence. ', @") + normal 3yis + call assert_equal('A sentence. A sentence?', @") + normal 2yas + call assert_equal('A sentence. A sentence? ', @") + + %delete _ +endfunc + +func Test_sentence_with_quotes() + enew! + call setline(1, 'A "sentence." A sentence.') + + normal yis + call assert_equal('A "sentence."', @") + normal yas + call assert_equal('A "sentence." ', @") + + normal ) + + normal yis + call assert_equal('A sentence.', @") + normal yas + call assert_equal(' A sentence.', @") + + %delete _ +endfunc + +func! Test_sentence_with_cursor_on_delimiter() + enew! + call setline(1, "A '([sentence.])' A sentence.") + + normal! 15|yis + call assert_equal("A '([sentence.])'", @") + normal! 15|yas + call assert_equal("A '([sentence.])' ", @") + + normal! 16|yis + call assert_equal("A '([sentence.])'", @") + normal! 16|yas + call assert_equal("A '([sentence.])' ", @") + + normal! 17|yis + call assert_equal("A '([sentence.])'", @") + normal! 17|yas + call assert_equal("A '([sentence.])' ", @") + + %delete _ +endfunc diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim index f31499607b..83ede1dc37 100644 --- a/src/nvim/testdir/test_undo.vim +++ b/src/nvim/testdir/test_undo.vim @@ -390,3 +390,47 @@ funct Test_undofile() set undodir& endfunc + +func Test_undo_0() + new + set ul=100 + normal i1 + undo + normal i2 + undo + normal i3 + + undo 0 + let d = undotree() + call assert_equal('', getline(1)) + call assert_equal(0, d.seq_cur) + + redo + let d = undotree() + call assert_equal('3', getline(1)) + call assert_equal(3, d.seq_cur) + + undo 2 + undo 0 + let d = undotree() + call assert_equal('', getline(1)) + call assert_equal(0, d.seq_cur) + + redo + let d = undotree() + call assert_equal('2', getline(1)) + call assert_equal(2, d.seq_cur) + + undo 1 + undo 0 + let d = undotree() + call assert_equal('', getline(1)) + call assert_equal(0, d.seq_cur) + + redo + let d = undotree() + call assert_equal('1', getline(1)) + call assert_equal(1, d.seq_cur) + + bwipe! +endfunc diff --git a/src/nvim/testdir/test_winbuf_close.vim b/src/nvim/testdir/test_winbuf_close.vim index ed64dd79b7..e4618610cd 100644 --- a/src/nvim/testdir/test_winbuf_close.vim +++ b/src/nvim/testdir/test_winbuf_close.vim @@ -122,3 +122,39 @@ func Test_winbuf_close() call delete('Xtest2') call delete('Xtest3') endfunc + +" Test that ":close" will respect 'winfixheight' when possible. +func Test_winfixheight_on_close() + set nosplitbelow nosplitright + + split | split | vsplit + + $wincmd w + setlocal winfixheight + let l:height = winheight(0) + + 3close + + call assert_equal(l:height, winheight(0)) + + %bwipeout! + setlocal nowinfixheight splitbelow& splitright& +endfunc + +" Test that ":close" will respect 'winfixwidth' when possible. +func Test_winfixwidth_on_close() + set nosplitbelow nosplitright + + vsplit | vsplit | split + + $wincmd w + setlocal winfixwidth + let l:width = winwidth(0) + + 3close + + call assert_equal(l:width, winwidth(0)) + + %bwipeout! + setlocal nowinfixwidth splitbelow& splitright& +endfunction diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index ad60c8d3c7..b3ab6957dc 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -374,6 +374,19 @@ func Test_equalalways_on_close() set equalalways& endfunc +func Test_win_screenpos() + call assert_equal(1, winnr('$')) + split + vsplit + 10wincmd _ + 30wincmd | + call assert_equal([1, 1], win_screenpos(1)) + call assert_equal([1, 32], win_screenpos(2)) + call assert_equal([12, 1], win_screenpos(3)) + call assert_equal([0, 0], win_screenpos(4)) + only +endfunc + func Test_window_jump_tag() help /iccf diff --git a/src/nvim/undo.c b/src/nvim/undo.c index f4eb50b3b5..e15b9ec796 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -122,7 +122,7 @@ static long u_newcount, u_oldcount; * When 'u' flag included in 'cpoptions', we behave like vi. Need to remember * the action that "u" should do. */ -static int undo_undoes = FALSE; +static bool undo_undoes = false; static int lastmark = 0; @@ -591,7 +591,7 @@ int u_savecommon(linenr_T top, linenr_T bot, linenr_T newbot, int reload) uep->ue_next = curbuf->b_u_newhead->uh_entry; curbuf->b_u_newhead->uh_entry = uep; curbuf->b_u_synced = false; - undo_undoes = FALSE; + undo_undoes = false; #ifdef U_DEBUG u_check(FALSE); @@ -1675,10 +1675,11 @@ void u_undo(int count) count = 1; } - if (vim_strchr(p_cpo, CPO_UNDO) == NULL) - undo_undoes = TRUE; - else + if (vim_strchr(p_cpo, CPO_UNDO) == NULL) { + undo_undoes = true; + } else { undo_undoes = !undo_undoes; + } u_doit(count, false, true); } @@ -1804,31 +1805,29 @@ static void u_doit(int startcount, bool quiet, bool do_buf_event) u_undo_end(undo_undoes, false, quiet); } -/* - * Undo or redo over the timeline. - * When "step" is negative go back in time, otherwise goes forward in time. - * When "sec" is FALSE make "step" steps, when "sec" is TRUE use "step" as - * seconds. - * When "file" is TRUE use "step" as a number of file writes. - * When "absolute" is TRUE use "step" as the sequence number to jump to. - * "sec" must be FALSE then. - */ -void undo_time(long step, int sec, int file, int absolute) +// Undo or redo over the timeline. +// When "step" is negative go back in time, otherwise goes forward in time. +// When "sec" is false make "step" steps, when "sec" is true use "step" as +// seconds. +// When "file" is true use "step" as a number of file writes. +// When "absolute" is true use "step" as the sequence number to jump to. +// "sec" must be false then. +void undo_time(long step, bool sec, bool file, bool absolute) { long target; long closest; long closest_start; long closest_seq = 0; long val; - u_header_T *uhp; + u_header_T *uhp = NULL; u_header_T *last; int mark; int nomark; int round; - int dosec = sec; - int dofile = file; - int above = FALSE; - int did_undo = TRUE; + bool dosec = sec; + bool dofile = file; + bool above = false; + bool did_undo = true; /* First make sure the current undoable change is synced. */ if (curbuf->b_u_synced == false) @@ -1842,13 +1841,7 @@ void undo_time(long step, int sec, int file, int absolute) /* "target" is the node below which we want to be. * Init "closest" to a value we can't reach. */ if (absolute) { - if (step == 0) { - // target 0 does not exist, got to 1 and above it. - target = 1; - above = true; - } else { - target = step; - } + target = step; closest = -1; } else { if (dosec) { @@ -1873,7 +1866,7 @@ void undo_time(long step, int sec, int file, int absolute) if (target <= 0) /* Go to before first write: before the oldest change. Use * the sequence number for that. */ - dofile = FALSE; + dofile = false; } else { /* Moving forward to a newer write. */ target = curbuf->b_u_save_nr_cur + step; @@ -1881,7 +1874,7 @@ void undo_time(long step, int sec, int file, int absolute) /* Go to after last write: after the latest change. Use * the sequence number for that. */ target = curbuf->b_u_seq_last + 1; - dofile = FALSE; + dofile = false; } } } else @@ -1906,6 +1899,11 @@ void undo_time(long step, int sec, int file, int absolute) closest_start = closest; closest_seq = curbuf->b_u_seq_cur; + // When "target" is 0; Back to origin. + if (target == 0) { + goto found; + } + /* * May do this twice: * 1. Search for "target", update "closest" to the best match found. @@ -2015,17 +2013,17 @@ void undo_time(long step, int sec, int file, int absolute) } target = closest_seq; - dosec = FALSE; - dofile = FALSE; - if (step < 0) - above = TRUE; /* stop above the header */ + dosec = false; + dofile = false; + if (step < 0) { + above = true; // stop above the header + } } - /* If we found it: Follow the path to go to where we want to be. */ - if (uhp != NULL) { - /* - * First go up the tree as much as needed. - */ +found: + // If we found it: Follow the path to go to where we want to be. + if (uhp != NULL || target == 0) { + // First go up the tree as much as needed. while (!got_int) { /* Do the change warning now, for the same reason as above. */ change_warning(0); @@ -2035,83 +2033,97 @@ void undo_time(long step, int sec, int file, int absolute) uhp = curbuf->b_u_newhead; else uhp = uhp->uh_next.ptr; - if (uhp == NULL || uhp->uh_walk != mark - || (uhp->uh_seq == target && !above)) + if (uhp == NULL + || (target > 0 && uhp->uh_walk != mark) + || (uhp->uh_seq == target && !above)) { break; + } curbuf->b_u_curhead = uhp; u_undoredo(true, true); - uhp->uh_walk = nomark; // don't go back down here + if (target > 0) { + uhp->uh_walk = nomark; // don't go back down here + } } - /* - * And now go down the tree (redo), branching off where needed. - */ - while (!got_int) { - /* Do the change warning now, for the same reason as above. */ - change_warning(0); + // When back to origin, redo is not needed. + if (target > 0) { + // And now go down the tree (redo), branching off where needed. + while (!got_int) { + // Do the change warning now, for the same reason as above. + change_warning(0); - uhp = curbuf->b_u_curhead; - if (uhp == NULL) - break; - - /* Go back to the first branch with a mark. */ - while (uhp->uh_alt_prev.ptr != NULL - && uhp->uh_alt_prev.ptr->uh_walk == mark) - uhp = uhp->uh_alt_prev.ptr; + uhp = curbuf->b_u_curhead; + if (uhp == NULL) { + break; + } - /* Find the last branch with a mark, that's the one. */ - last = uhp; - while (last->uh_alt_next.ptr != NULL - && last->uh_alt_next.ptr->uh_walk == mark) - last = last->uh_alt_next.ptr; - if (last != uhp) { - /* Make the used branch the first entry in the list of - * alternatives to make "u" and CTRL-R take this branch. */ - while (uhp->uh_alt_prev.ptr != NULL) + // Go back to the first branch with a mark. + while (uhp->uh_alt_prev.ptr != NULL + && uhp->uh_alt_prev.ptr->uh_walk == mark) { uhp = uhp->uh_alt_prev.ptr; - if (last->uh_alt_next.ptr != NULL) - last->uh_alt_next.ptr->uh_alt_prev.ptr = - last->uh_alt_prev.ptr; - last->uh_alt_prev.ptr->uh_alt_next.ptr = last->uh_alt_next.ptr; - last->uh_alt_prev.ptr = NULL; - last->uh_alt_next.ptr = uhp; - uhp->uh_alt_prev.ptr = last; - - if (curbuf->b_u_oldhead == uhp) - curbuf->b_u_oldhead = last; - uhp = last; - if (uhp->uh_next.ptr != NULL) - uhp->uh_next.ptr->uh_prev.ptr = uhp; - } - curbuf->b_u_curhead = uhp; + } - if (uhp->uh_walk != mark) - break; /* must have reached the target */ + // Find the last branch with a mark, that's the one. + last = uhp; + while (last->uh_alt_next.ptr != NULL + && last->uh_alt_next.ptr->uh_walk == mark) { + last = last->uh_alt_next.ptr; + } + if (last != uhp) { + // Make the used branch the first entry in the list of + // alternatives to make "u" and CTRL-R take this branch. + while (uhp->uh_alt_prev.ptr != NULL) { + uhp = uhp->uh_alt_prev.ptr; + } + if (last->uh_alt_next.ptr != NULL) { + last->uh_alt_next.ptr->uh_alt_prev.ptr = last->uh_alt_prev.ptr; + } + last->uh_alt_prev.ptr->uh_alt_next.ptr = last->uh_alt_next.ptr; + last->uh_alt_prev.ptr = NULL; + last->uh_alt_next.ptr = uhp; + uhp->uh_alt_prev.ptr = last; - /* Stop when going backwards in time and didn't find the exact - * header we were looking for. */ - if (uhp->uh_seq == target && above) { - curbuf->b_u_seq_cur = target - 1; - break; - } + if (curbuf->b_u_oldhead == uhp) { + curbuf->b_u_oldhead = last; + } + uhp = last; + if (uhp->uh_next.ptr != NULL) { + uhp->uh_next.ptr->uh_prev.ptr = uhp; + } + } + curbuf->b_u_curhead = uhp; - u_undoredo(false, true); + if (uhp->uh_walk != mark) { + break; // must have reached the target + } - /* Advance "curhead" to below the header we last used. If it - * becomes NULL then we need to set "newhead" to this leaf. */ - if (uhp->uh_prev.ptr == NULL) - curbuf->b_u_newhead = uhp; - curbuf->b_u_curhead = uhp->uh_prev.ptr; - did_undo = FALSE; + // Stop when going backwards in time and didn't find the exact + // header we were looking for. + if (uhp->uh_seq == target && above) { + curbuf->b_u_seq_cur = target - 1; + break; + } - if (uhp->uh_seq == target) /* found it! */ - break; + u_undoredo(false, true); - uhp = uhp->uh_prev.ptr; - if (uhp == NULL || uhp->uh_walk != mark) { - // Need to redo more but can't find it... - internal_error("undo_time()"); - break; + // Advance "curhead" to below the header we last used. If it + // becomes NULL then we need to set "newhead" to this leaf. + if (uhp->uh_prev.ptr == NULL) { + curbuf->b_u_newhead = uhp; + } + curbuf->b_u_curhead = uhp->uh_prev.ptr; + did_undo = false; + + if (uhp->uh_seq == target) { // found it! + break; + } + + uhp = uhp->uh_prev.ptr; + if (uhp == NULL || uhp->uh_walk != mark) { + // Need to redo more but can't find it... + internal_error("undo_time()"); + break; + } } } } @@ -2375,8 +2387,8 @@ static void u_undoredo(int undo, bool do_buf_event) /// Otherwise, report the number of changes (this may be incorrect /// in some cases, but it's better than nothing). static void u_undo_end( - int did_undo, ///< just did an undo - int absolute, ///< used ":undo N" + bool did_undo, ///< just did an undo + bool absolute, ///< used ":undo N" bool quiet) { char *msgstr; @@ -2416,13 +2428,15 @@ static void u_undo_end( /* For ":undo N" we prefer a "after #N" message. */ if (absolute && curbuf->b_u_curhead->uh_next.ptr != NULL) { uhp = curbuf->b_u_curhead->uh_next.ptr; - did_undo = FALSE; - } else if (did_undo) + did_undo = false; + } else if (did_undo) { uhp = curbuf->b_u_curhead; - else + } else { uhp = curbuf->b_u_curhead->uh_next.ptr; - } else + } + } else { uhp = curbuf->b_u_newhead; + } if (uhp == NULL) *msgbuf = NUL; diff --git a/src/nvim/vim.h b/src/nvim/vim.h index bddf092789..767936ecee 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -155,6 +155,8 @@ enum { EXPAND_USER_ADDR_TYPE, EXPAND_PACKADD, EXPAND_MESSAGES, + EXPAND_MAPCLEAR, + EXPAND_ARGLIST, EXPAND_CHECKHEALTH, }; diff --git a/src/nvim/window.c b/src/nvim/window.c index 9515b88248..055564c1e0 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -565,6 +565,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) int before; int minheight; int wmh1; + bool did_set_fraction = false; if (flags & WSP_TOP) oldwin = firstwin; @@ -729,6 +730,11 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) * 'winfixheight' window. Take them from a window above or below * instead, if possible. */ if (oldwin->w_p_wfh) { + // Set w_fraction now so that the cursor keeps the same relative + // vertical position using the old height. + set_fraction(oldwin); + did_set_fraction = true; + win_setheight_win(oldwin->w_height + new_size + STATUS_HEIGHT, oldwin); oldwin_height = oldwin->w_height; @@ -843,8 +849,9 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) /* Set w_fraction now so that the cursor keeps the same relative * vertical position. */ - if (oldwin->w_height > 0) + if (!did_set_fraction) { set_fraction(oldwin); + } wp->w_fraction = oldwin->w_fraction; if (flags & WSP_VERT) { @@ -2330,14 +2337,14 @@ winframe_remove ( return wp; } -/* - * Find out which frame is going to get the freed up space when "win" is - * closed. - * if 'splitbelow'/'splitleft' the space goes to the window above/left. - * if 'nosplitbelow'/'nosplitleft' the space goes to the window below/right. - * This makes opening a window and closing it immediately keep the same window - * layout. - */ +// Return a pointer to the frame that will receive the empty screen space that +// is left over after "win" is closed. +// +// If 'splitbelow' or 'splitright' is set, the space goes above or to the left +// by default. Otherwise, the free space goes below or to the right. The +// result is that opening a window and then immediately closing it will +// preserve the initial window layout. The 'wfh' and 'wfw' settings are +// respected when possible. static frame_T * win_altframe ( win_T *win, @@ -2345,20 +2352,40 @@ win_altframe ( ) { frame_T *frp; - int b; - if (tp == NULL ? ONE_WINDOW : tp->tp_firstwin == tp->tp_lastwin) - /* Last window in this tab page, will go to next tab page. */ + if (tp == NULL ? ONE_WINDOW : tp->tp_firstwin == tp->tp_lastwin) { return alt_tabpage()->tp_curwin->w_frame; + } frp = win->w_frame; - if (frp->fr_parent != NULL && frp->fr_parent->fr_layout == FR_ROW) - b = p_spr; - else - b = p_sb; - if ((!b && frp->fr_next != NULL) || frp->fr_prev == NULL) + + if (frp->fr_prev == NULL) { return frp->fr_next; - return frp->fr_prev; + } + if (frp->fr_next == NULL) { + return frp->fr_prev; + } + + frame_T *target_fr = frp->fr_next; + frame_T *other_fr = frp->fr_prev; + if (p_spr || p_sb) { + target_fr = frp->fr_prev; + other_fr = frp->fr_next; + } + + // If 'wfh' or 'wfw' is set for the target and not for the alternate + // window, reverse the selection. + if (frp->fr_parent != NULL && frp->fr_parent->fr_layout == FR_ROW) { + if (frame_fixed_width(target_fr) && !frame_fixed_width(other_fr)) { + target_fr = other_fr; + } + } else { + if (frame_fixed_height(target_fr) && !frame_fixed_height(other_fr)) { + target_fr = other_fr; + } + } + + return target_fr; } /* @@ -4791,10 +4818,13 @@ void win_drag_vsep_line(win_T *dragwin, int offset) #define FRACTION_MULT 16384L // Set wp->w_fraction for the current w_wrow and w_height. +// Has no effect when the window is less than two lines. void set_fraction(win_T *wp) { - wp->w_fraction = ((long)wp->w_wrow * FRACTION_MULT + wp->w_height / 2) + if (wp->w_height > 1) { + wp->w_fraction = ((long)wp->w_wrow * FRACTION_MULT + wp->w_height / 2) / (long)wp->w_height; + } } /* @@ -5110,6 +5140,8 @@ file_name_in_line ( { char_u *ptr; size_t len; + bool in_type = true; + bool is_url = false; /* * search forward for what could be the start of a file name @@ -5146,7 +5178,19 @@ file_name_in_line ( */ len = 0; while (vim_isfilec(ptr[len]) || (ptr[len] == '\\' && ptr[len + 1] == ' ') - || ((options & FNAME_HYP) && path_is_url((char *)ptr + len))) { + || ((options & FNAME_HYP) && path_is_url((char *)ptr + len)) + || (is_url && vim_strchr((char_u *)"?&=", ptr[len]) != NULL)) { + // After type:// we also include ?, & and = as valid characters, so that + // http://google.com?q=this&that=ok works. + if ((ptr[len] >= 'A' && ptr[len] <= 'Z') + || (ptr[len] >= 'a' && ptr[len] <= 'z')) { + if (in_type && path_is_url((char *)ptr + len + 1)) { + is_url = true; + } + } else { + in_type = false; + } + if (ptr[len] == '\\' && ptr[len + 1] == ' ') { // Skip over the "\" in "\ ". ++len; diff --git a/test/README.md b/test/README.md index 9b1f0d084a..e41936c4b6 100644 --- a/test/README.md +++ b/test/README.md @@ -3,7 +3,9 @@ Tests Tests are run by `/cmake/RunTests.cmake` file, using `busted`. -For some failures, `.nvimlog` (or `$NVIM_LOG_FILE`) may provide insight. +For some failures, `.nvimlog` (or `$NVIM_LOG_FILE`) may provide insight. + +Depending on the presence of binaries (e.g., `xclip`) some tests will be ignored. You must compile with libintl to prevent `E319: The command is not available in this version` errors. --- diff --git a/test/functional/api/server_notifications_spec.lua b/test/functional/api/server_notifications_spec.lua index 1d64ae7103..29cd38ef0d 100644 --- a/test/functional/api/server_notifications_spec.lua +++ b/test/functional/api/server_notifications_spec.lua @@ -65,4 +65,11 @@ describe('notify', function() eq(nest_level, act_nest_level) end) end) + + it('unsubscribe non-existing event #8745', function() + nvim('subscribe', 'event1') + nvim('unsubscribe', 'doesnotexist') + nvim('unsubscribe', 'event1') + eq(2, eval('1+1')) -- Still alive? + end) end) diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index 4a21444ee0..feb9c9aeaa 100644 --- a/test/functional/core/job_spec.lua +++ b/test/functional/core/job_spec.lua @@ -417,7 +417,7 @@ describe('jobs', function() \ }) ]]) - screen:expect("{2:E120: Using <SID> not in a script context: s:OnEvent}",nil,nil,nil,true) + screen:expect{any="{2:E120: Using <SID> not in a script context: s:OnEvent}"} end) it('does not repeat output with slow output handlers', function() diff --git a/test/functional/ex_cmds/cmd_map_spec.lua b/test/functional/ex_cmds/cmd_map_spec.lua index 77d025dcc7..acb76e382d 100644 --- a/test/functional/ex_cmds/cmd_map_spec.lua +++ b/test/functional/ex_cmds/cmd_map_spec.lua @@ -656,7 +656,6 @@ describe('mappings with <Cmd>', function() end) it('works in cmdline mode', function() - cmdmap('<F2>', 'call setcmdpos(2)') feed(':text<F3>') eq('c', eval('m')) -- didn't leave cmdline mode @@ -768,5 +767,32 @@ describe('mappings with <Cmd>', function() end) + it("doesn't crash when invoking cmdline mode recursively #8859", function() + cmdmap('<F2>', 'norm! :foo') + feed(':bar') + screen:expect([[ + some short lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :bar^ | + ]]) + + feed('<f2>x') + screen:expect([[ + some short lines | + of test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :barx^ | + ]]) + end) + end) diff --git a/test/functional/ex_cmds/ctrl_c_spec.lua b/test/functional/ex_cmds/ctrl_c_spec.lua index 8f76099f79..4c5383b712 100644 --- a/test/functional/ex_cmds/ctrl_c_spec.lua +++ b/test/functional/ex_cmds/ctrl_c_spec.lua @@ -43,7 +43,7 @@ describe("CTRL-C (mapped)", function() feed(":global/^/p<CR>") screen:sleep(ms) feed("<C-C>") - screen:expect([[Interrupt]], nil, nil, nil, true) + screen:expect{any="Interrupt"} end -- The test is time-sensitive. Try different sleep values. diff --git a/test/functional/legacy/003_cindent_spec.lua b/test/functional/legacy/003_cindent_spec.lua index 1cede8a7d7..061904c42f 100644 --- a/test/functional/legacy/003_cindent_spec.lua +++ b/test/functional/legacy/003_cindent_spec.lua @@ -4754,4 +4754,21 @@ describe('cindent', function() 4 /* end of define */]=]) end) + + it('* immediately follows comment / vim-patch 8.0.1291', function() + insert_([=[ + { + a = second/*bug*/*line; + }]=]) + + feed_command('set cin cino&') + feed_command('/a = second') + feed('ox') + + expect([=[ + { + a = second/*bug*/*line; + x + }]=]) + end) end) diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index c290031fbe..4d6b125f9f 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -207,18 +207,18 @@ describe('terminal buffer', function() feed_command('terminal') feed('<c-\\><c-n>') feed_command('confirm bdelete') - screen:expect('Close "term://', nil, true, nil, true) + screen:expect{any='Close "term://', attr_ignore=true} end) it('with &confirm', function() feed_command('terminal') feed('<c-\\><c-n>') feed_command('bdelete') - screen:expect('E89', nil, true, nil, true) + screen:expect{any='E89', attr_ignore=true} feed('<cr>') eq('terminal', eval('&buftype')) feed_command('set confirm | bdelete') - screen:expect('Close "term://', nil, true, nil, true) + screen:expect{any='Close "term://', attr_ignore=true} feed('y') neq('terminal', eval('&buftype')) end) @@ -242,7 +242,7 @@ describe('No heap-buffer-overflow when using', function() feed('$') -- Let termopen() modify the buffer feed_command('call termopen("echo")') - wait() + eq(2, eval('1+1')) -- check nvim still running feed_command('bdelete!') end) end) diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua index f98add41a0..dbee9bdb49 100644 --- a/test/functional/terminal/ex_terminal_spec.lua +++ b/test/functional/terminal/ex_terminal_spec.lua @@ -54,7 +54,7 @@ describe(':terminal', function() else feed_command([[terminal printf '\e[6n'; sleep 0.5 ]]) end - screen:expect('%^%[%[1;1R', nil, nil, nil, true) + screen:expect{any='%^%[%[1;1R'} end) it("in normal-mode :split does not move cursor", function() diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua index c665e64a80..5e36fea474 100644 --- a/test/functional/terminal/scrollback_spec.lua +++ b/test/functional/terminal/scrollback_spec.lua @@ -408,7 +408,7 @@ describe("'scrollback' option", function() else feed_data('for i in $(seq 1 30); do echo "line$i"; done\n') end - screen:expect('line30 ', nil, nil, nil, true) + screen:expect{any='line30 '} retry(nil, nil, function() expect_lines(7) end) screen:detach() @@ -426,7 +426,7 @@ describe("'scrollback' option", function() curbufmeths.set_option('scrollback', 200) -- Wait for prompt. - screen:expect('$', nil, nil, nil, true) + screen:expect{any='$'} wait() if iswin() then @@ -435,7 +435,7 @@ describe("'scrollback' option", function() feed_data('for i in $(seq 1 30); do echo "line$i"; done\n') end - screen:expect('line30 ', nil, nil, nil, true) + screen:expect{any='line30 '} retry(nil, nil, function() expect_lines(33, 2) end) curbufmeths.set_option('scrollback', 10) @@ -452,7 +452,7 @@ describe("'scrollback' option", function() feed_data('for i in $(seq 1 40); do echo "line$i"; done\n') end - screen:expect('line40 ', nil, nil, nil, true) + screen:expect{any='line40 '} retry(nil, nil, function() expect_lines(58) end) -- Verify off-screen state diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index c0404ff463..351038e521 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -400,7 +400,7 @@ describe('tui FocusGained/FocusLost', function() -- Exit cmdline-mode. Redraws from timers/events are blocked during -- cmdline-mode, so the buffer won't be updated until we exit cmdline-mode. feed_data('\n') - screen:expect('lost'..(' '):rep(46)..'\ngained', nil, nil, nil, true) + screen:expect{any='lost'..(' '):rep(46)..'\ngained'} end) end) @@ -740,7 +740,7 @@ describe("tui 'term' option", function() screen.timeout = 250 -- We want screen:expect() to fail quickly. retry(nil, 2 * full_timeout, function() -- Wait for TUI thread to set 'term'. feed_data(":echo 'term='.(&term)\n") - screen:expect('term='..term_expected, nil, nil, nil, true) + screen:expect{any='term='..term_expected} end) end diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua index b2fc008dba..bf8cae3a0b 100644 --- a/test/functional/ui/cmdline_spec.lua +++ b/test/functional/ui/cmdline_spec.lua @@ -1,23 +1,16 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') -local clear, feed, eq = helpers.clear, helpers.feed, helpers.eq +local clear, feed = helpers.clear, helpers.feed local source = helpers.source -local ok = helpers.ok local command = helpers.command -describe('external cmdline', function() +local function test_cmdline(newgrid) local screen - local last_level = 0 - local cmdline = {} - local block = nil - local wild_items = nil - local wild_selected = nil before_each(function() clear() - cmdline, block = {}, nil screen = Screen.new(25, 5) - screen:attach({rgb=true, ext_cmdline=true}) + screen:attach({rgb=true, ext_cmdline=true, ext_newgrid=newgrid}) screen:set_default_attr_ids({ [1] = {bold = true, foreground = Screen.colors.Blue1}, [2] = {reverse = true}, @@ -25,142 +18,73 @@ describe('external cmdline', function() [4] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, [5] = {bold = true, foreground = Screen.colors.SeaGreen4}, }) - screen:set_on_event_handler(function(name, data) - if name == "cmdline_show" then - local content, pos, firstc, prompt, indent, level = unpack(data) - ok(level > 0) - for _,item in ipairs(content) do - item[1] = screen:get_hl(item[1]) - end - cmdline[level] = {content=content, pos=pos, firstc=firstc, - prompt=prompt, indent=indent} - last_level = level - elseif name == "cmdline_hide" then - local level = data[1] - cmdline[level] = nil - elseif name == "cmdline_special_char" then - local char, shift, level = unpack(data) - cmdline[level].special = {char, shift} - elseif name == "cmdline_pos" then - local pos, level = unpack(data) - cmdline[level].pos = pos - elseif name == "cmdline_block_show" then - block = data[1] - elseif name == "cmdline_block_append" then - block[#block+1] = data[1] - elseif name == "cmdline_block_hide" then - block = nil - elseif name == "wildmenu_show" then - wild_items = data[1] - elseif name == "wildmenu_select" then - wild_selected = data[1] - elseif name == "wildmenu_hide" then - wild_items, wild_selected = nil, nil - end - end) end) after_each(function() screen:detach() end) - local function expect_cmdline(level, expected) - local attr_ids = screen._default_attr_ids - local attr_ignore = screen._default_attr_ignore - local actual = '' - for _, chunk in ipairs(cmdline[level] and cmdline[level].content or {}) do - local attrs, text = chunk[1], chunk[2] - if screen:_equal_attrs(attrs, {}) then - actual = actual..text - else - local attr_id = screen:_get_attr_id(attr_ids, attr_ignore, attrs) - actual = actual..'{' .. attr_id .. ':' .. text .. '}' - end - end - eq(expected, actual) - end - it('works', function() feed(':') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq(1, last_level) - --print(require('inspect')(cmdline)) - eq({{ - content = { { {}, "" } }, - firstc = ":", - indent = 0, - pos = 0, - prompt = "" - }}, cmdline) - end) + ]], cmdline={{ + firstc = ":", + content = {{""}}, + pos = 0, + }}} feed('sign') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "sign" } }, - firstc = ":", - indent = 0, - pos = 4, - prompt = "" - }}, cmdline) - end) + ]], cmdline={{ + firstc = ":", + content = {{"sign"}}, + pos = 4, + }}} feed('<Left>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "sign" } }, - firstc = ":", - indent = 0, - pos = 3, - prompt = "" - }}, cmdline) - end) + ]], cmdline={{ + firstc = ":", + content = {{"sign"}}, + pos = 3, + }}} feed('<bs>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "sin" } }, - firstc = ":", - indent = 0, - pos = 2, - prompt = "" - }}, cmdline) - end) + ]], cmdline={{ + firstc = ":", + content = {{"sin"}}, + pos = 2, + }}} feed('<Esc>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({}, cmdline) - end) + ]]} end) describe("redraws statusline on entering", function() @@ -170,28 +94,32 @@ describe('external cmdline', function() end) it('from normal mode', function() + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {3:n }| + | + ]]} + feed(':') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {3:c }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "" } }, - firstc = ":", - indent = 0, - pos = 0, - prompt = "" - }}, cmdline) - end) + ]], cmdline={{ + firstc = ":", + content = {{""}}, + pos = 0, + }}} end) it('but not with scrolled messages', function() screen:try_resize(50,10) feed(':echoerr doesnotexist<cr>') - screen:expect([[ + screen:expect{grid=[[ | {1:~ }| {1:~ }| @@ -202,9 +130,9 @@ describe('external cmdline', function() {4:E121: Undefined variable: doesnotexist} | {4:E15: Invalid expression: doesnotexist} | {5:Press ENTER or type command to continue}^ | - ]]) + ]]} feed(':echoerr doesnotexist<cr>') - screen:expect([[ + screen:expect{grid=[[ | {1:~ }| {1:~ }| @@ -215,10 +143,10 @@ describe('external cmdline', function() {4:E121: Undefined variable: doesnotexist} | {4:E15: Invalid expression: doesnotexist} | {5:Press ENTER or type command to continue}^ | - ]]) + ]]} feed(':echoerr doesnotexist<cr>') - screen:expect([[ + screen:expect{grid=[[ | {1:~ }| {3: }| @@ -229,10 +157,10 @@ describe('external cmdline', function() {4:E121: Undefined variable: doesnotexist} | {4:E15: Invalid expression: doesnotexist} | {5:Press ENTER or type command to continue}^ | - ]]) + ]]} feed('<cr>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| @@ -243,372 +171,314 @@ describe('external cmdline', function() {1:~ }| {3:n }| | - ]]) + ]]} end) end) it("works with input()", function() feed(':call input("input", "default")<cr>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "default" } }, - firstc = "", - indent = 0, - pos = 7, - prompt = "input" - }}, cmdline) - end) + ]], cmdline={{ + prompt = "input", + content = {{"default"}}, + pos = 7, + }}} + feed('<cr>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({}, cmdline) - end) - + ]]} end) it("works with special chars and nested cmdline", function() feed(':xx<c-r>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "xx" } }, - firstc = ":", - indent = 0, - pos = 2, - prompt = "", - special = {'"', true}, - }}, cmdline) - end) + ]], cmdline={{ + firstc = ":", + content = {{"xx"}}, + pos = 2, + special = {'"', true}, + }}} feed('=') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "xx" } }, - firstc = ":", - indent = 0, - pos = 2, - prompt = "", - special = {'"', true}, - },{ - content = { { {}, "" } }, - firstc = "=", - indent = 0, - pos = 0, - prompt = "", - }}, cmdline) - end) + ]], cmdline={{ + firstc = ":", + content = {{"xx"}}, + pos = 2, + special = {'"', true}, + }, { + firstc = "=", + content = {{""}}, + pos = 0, + }}} feed('1+2') local expectation = {{ - content = { { {}, "xx" } }, - firstc = ":", - indent = 0, - pos = 2, - prompt = "", - special = {'"', true}, - },{ - content = { - { {}, "1" }, - { {}, "+" }, - { {}, "2" }, - }, - firstc = "=", - indent = 0, - pos = 3, - prompt = "", - }} - screen:expect([[ + firstc = ":", + content = {{"xx"}}, + pos = 2, + special = {'"', true}, + }, { + firstc = "=", + content = {{"1"}, {"+"}, {"2"}}, + pos = 3, + }} + + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq(expectation, cmdline) - end) + ]], cmdline=expectation} -- erase information, so we check if it is retransmitted - cmdline = {} + -- TODO(bfredl): when we add a flag to screen:expect{} + -- to explicitly check redraw!, it should also do this + screen.cmdline = {} command("redraw!") - -- redraw! forgets cursor position. Be OK with that, as UI should indicate - -- focus is at external cmdline anyway. - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq(expectation, cmdline) - end) + ]], cmdline=expectation} feed('<cr>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "xx3" } }, - firstc = ":", - indent = 0, - pos = 3, - prompt = "", - }}, cmdline) - end) + ]], cmdline={{ + firstc = ":", + content = {{"xx3"}}, + pos = 3, + }}} feed('<esc>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({}, cmdline) - end) + ]]} end) it("works with function definitions", function() feed(':function Foo()<cr>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "" } }, - firstc = ":", - indent = 2, - pos = 0, - prompt = "", - }}, cmdline) - eq({ { { {}, 'function Foo()'} } }, block) - end) + ]], cmdline={{ + indent = 2, + firstc = ":", + content = {{""}}, + pos = 0, + }}, cmdline_block = { + {{'function Foo()'}}, + }} feed('line1<cr>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({ { { {}, 'function Foo()'} }, - { { {}, ' line1'} } }, block) - end) + ]], cmdline={{ + indent = 2, + firstc = ":", + content = {{""}}, + pos = 0, + }}, cmdline_block = { + {{'function Foo()'}}, + {{' line1'}}, + }} - block = {} + screen.cmdline_block = {} command("redraw!") - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({ { { {}, 'function Foo()'} }, - { { {}, ' line1'} } }, block) - end) + ]], cmdline={{ + indent = 2, + firstc = ":", + content = {{""}}, + pos = 0, + }}, cmdline_block = { + {{'function Foo()'}}, + {{' line1'}}, + }} feed('endfunction<cr>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq(nil, block) - end) + ]]} -- Try once more, to check buffer is reinitialized. #8007 feed(':function Bar()<cr>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "" } }, - firstc = ":", - indent = 2, - pos = 0, - prompt = "", - }}, cmdline) - eq({ { { {}, 'function Bar()'} } }, block) - end) + ]], cmdline={{ + indent = 2, + firstc = ":", + content = {{""}}, + pos = 0, + }}, cmdline_block = { + {{'function Bar()'}}, + }} feed('endfunction<cr>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq(nil, block) - end) + ]]} + end) it("works with cmdline window", function() feed(':make') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "make" } }, - firstc = ":", - indent = 0, - pos = 4, - prompt = "" - }}, cmdline) - end) + ]], cmdline={{ + firstc = ":", + content = {{"make"}}, + pos = 4, + }}} feed('<c-f>') - screen:expect([[ + screen:expect{grid=[[ | {2:[No Name] }| {1::}make^ | {3:[Command Line] }| | - ]], nil, nil, function() - eq({}, cmdline) - end) + ]]} -- nested cmdline feed(':yank') - screen:expect([[ + screen:expect{grid=[[ | {2:[No Name] }| {1::}make^ | {3:[Command Line] }| | - ]], nil, nil, function() - eq({nil, { - content = { { {}, "yank" } }, - firstc = ":", - indent = 0, - pos = 4, - prompt = "" - }}, cmdline) - end) + ]], cmdline={nil, { + firstc = ":", + content = {{"yank"}}, + pos = 4, + }}} - cmdline = {} + screen.cmdline = {} command("redraw!") - screen:expect([[ + screen:expect{grid=[[ | {2:[No Name] }| {1::}make^ | {3:[Command Line] }| | - ]], nil, nil, function() - eq({nil, { - content = { { {}, "yank" } }, - firstc = ":", - indent = 0, - pos = 4, - prompt = "" - }}, cmdline) - end) + ]], cmdline={nil, { + firstc = ":", + content = {{"yank"}}, + pos = 4, + }}} feed("<c-c>") - screen:expect([[ + screen:expect{grid=[[ | {2:[No Name] }| {1::}make^ | {3:[Command Line] }| | - ]], nil, nil, function() - eq({}, cmdline) - end) + ]]} feed("<c-c>") - screen:expect([[ + screen:expect{grid=[[ | {2:[No Name] }| {1::}make^ | {3:[Command Line] }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "make" } }, - firstc = ":", - indent = 0, - pos = 4, - prompt = "" - }}, cmdline) - end) + ]], cmdline={{ + firstc = ":", + content = {{"make"}}, + pos = 4, + }}} - cmdline = {} + screen.cmdline = {} command("redraw!") - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "make" } }, - firstc = ":", - indent = 0, - pos = 4, - prompt = "" - }}, cmdline) - end) + ]], cmdline={{ + firstc = ":", + content = {{"make"}}, + pos = 4, + }}} end) it('works with inputsecret()', function() feed(":call inputsecret('secret:')<cr>abc123") - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "******" } }, - firstc = "", - indent = 0, - pos = 6, - prompt = "secret:" - }}, cmdline) - end) + ]], cmdline={{ + prompt = "secret:", + content = {{"******"}}, + pos = 6, + }}} end) it('works with highlighted cmdline', function() @@ -648,15 +518,18 @@ describe('external cmdline', function() PE={bold = true, foreground = Screen.colors.SeaGreen4} }) feed('<f5>(a(b)a)') - screen:expect([[ + screen:expect{grid=[[ ^ | {EOB:~ }| {EOB:~ }| {EOB:~ }| | - ]], nil, nil, function() - expect_cmdline(1, '{RBP1:(}a{RBP2:(}b{RBP2:)}a{RBP1:)}') - end) + ]], cmdline={{ + prompt = '>', + content = {{'(', 'RBP1'}, {'a'}, {'(', 'RBP2'}, {'b'}, + { ')', 'RBP2'}, {'a'}, {')', 'RBP1'}}, + pos = 7, + }}} end) it('works together with ext_wildmenu', function() @@ -674,98 +547,73 @@ describe('external cmdline', function() screen:set_option('ext_wildmenu', true) feed(':sign <tab>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "sign define"} }, - firstc = ":", - indent = 0, - pos = 11, - prompt = "" - }}, cmdline) - eq(expected, wild_items) - eq(0, wild_selected) - end) + ]], cmdline={{ + firstc = ":", + content = {{"sign define"}}, + pos = 11, + }}, wildmenu_items=expected, wildmenu_pos=0} feed('<tab>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "sign jump"} }, - firstc = ":", - indent = 0, - pos = 9, - prompt = "" - }}, cmdline) - eq(expected, wild_items) - eq(1, wild_selected) - end) + ]], cmdline={{ + firstc = ":", + content = {{"sign jump"}}, + pos = 9, + }}, wildmenu_items=expected, wildmenu_pos=1} feed('<left><left>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "sign "} }, - firstc = ":", - indent = 0, - pos = 5, - prompt = "" - }}, cmdline) - eq(expected, wild_items) - eq(-1, wild_selected) - end) + ]], cmdline={{ + firstc = ":", + content = {{"sign "}}, + pos = 5, + }}, wildmenu_items=expected, wildmenu_pos=-1} feed('<right>') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "sign define"} }, - firstc = ":", - indent = 0, - pos = 11, - prompt = "" - }}, cmdline) - eq(expected, wild_items) - eq(0, wild_selected) - end) + ]], cmdline={{ + firstc = ":", + content = {{"sign define"}}, + pos = 11, + }}, wildmenu_items=expected, wildmenu_pos=0} feed('a') - screen:expect([[ + screen:expect{grid=[[ ^ | {1:~ }| {1:~ }| {1:~ }| | - ]], nil, nil, function() - eq({{ - content = { { {}, "sign definea"} }, - firstc = ":", - indent = 0, - pos = 12, - prompt = "" - }}, cmdline) - eq(nil, wild_items) - eq(nil, wild_selected) - end) + ]], cmdline={{ + firstc = ":", + content = {{"sign definea"}}, + pos = 12, + }}} end) -end) +end + +-- the representation of cmdline and cmdline_block contents changed with ext_newgrid +-- (which uses indexed highlights) so make sure to test both +describe('ui/ext_cmdline', function() test_cmdline(true) end) +describe('ui/ext_cmdline (legacy highlights)', function() test_cmdline(false) end) diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua index d1cddbe06a..3e0370db14 100644 --- a/test/functional/ui/cursor_spec.lua +++ b/test/functional/ui/cursor_spec.lua @@ -188,15 +188,15 @@ describe('ui/cursor', function() -- Event is published ONLY if the cursor style changed. screen._mode_info = nil command("echo 'test'") - screen:expect([[ + screen:expect{grid=[[ ^ | ~ | ~ | ~ | test | - ]], nil, nil, function() + ]], condition=function() eq(nil, screen._mode_info) - end) + end} -- Change the cursor style. helpers.command('hi Cursor guibg=DarkGray') diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua index 9cc697a4b6..6a17448582 100644 --- a/test/functional/ui/inccommand_spec.lua +++ b/test/functional/ui/inccommand_spec.lua @@ -745,19 +745,19 @@ describe(":substitute, inccommand=split", function() it("shows preview when cmd modifiers are present", function() -- one modifier feed(':keeppatterns %s/tw/to') - screen:expect([[{12:to}o lines]], nil, nil, nil, true) + screen:expect{any=[[{12:to}o lines]]} feed('<Esc>') - screen:expect([[two lines]], nil, nil, nil, true) + screen:expect{any=[[two lines]]} -- multiple modifiers feed(':keeppatterns silent %s/tw/to') - screen:expect([[{12:to}o lines]], nil, nil, nil, true) + screen:expect{any=[[{12:to}o lines]]} feed('<Esc>') - screen:expect([[two lines]], nil, nil, nil, true) + screen:expect{any=[[two lines]]} -- non-modifier prefix feed(':silent tabedit %s/tw/to') - screen:expect([[two lines]], nil, nil, nil, true) + screen:expect{any=[[two lines]]} feed('<Esc>') end) @@ -1222,19 +1222,19 @@ describe("inccommand=nosplit", function() it("shows preview when cmd modifiers are present", function() -- one modifier feed(':keeppatterns %s/tw/to') - screen:expect([[{12:to}o lines]], nil, nil, nil, true) + screen:expect{any=[[{12:to}o lines]]} feed('<Esc>') - screen:expect([[two lines]], nil, nil, nil, true) + screen:expect{any=[[two lines]]} -- multiple modifiers feed(':keeppatterns silent %s/tw/to') - screen:expect([[{12:to}o lines]], nil, nil, nil, true) + screen:expect{any=[[{12:to}o lines]]} feed('<Esc>') - screen:expect([[two lines]], nil, nil, nil, true) + screen:expect{any=[[two lines]]} -- non-modifier prefix feed(':silent tabedit %s/tw/to') - screen:expect([[two lines]], nil, nil, nil, true) + screen:expect{any=[[two lines]]} feed('<Esc>') end) diff --git a/test/functional/ui/mode_spec.lua b/test/functional/ui/mode_spec.lua index f0cedfeeb5..f6b3c1c3c9 100644 --- a/test/functional/ui/mode_spec.lua +++ b/test/functional/ui/mode_spec.lua @@ -21,207 +21,169 @@ describe('ui mode_change event', function() end) it('works in normal mode', function() - screen:expect([[ + screen:expect{grid=[[ ^ | {0:~ }| {0:~ }| | - ]],nil,nil,function () - eq("normal", screen.mode) - end) + ]], mode="normal"} feed('d') - screen:expect([[ + screen:expect{grid=[[ ^ | {0:~ }| {0:~ }| | - ]],nil,nil,function () - eq("operator", screen.mode) - end) + ]], mode="operator"} feed('<esc>') - screen:expect([[ + screen:expect{grid=[[ ^ | {0:~ }| {0:~ }| | - ]],nil,nil,function () - eq("normal", screen.mode) - end) + ]], mode="normal"} end) it('works in insert mode', function() feed('i') - screen:expect([[ + screen:expect{grid=[[ ^ | {0:~ }| {0:~ }| {2:-- INSERT --} | - ]],nil,nil,function () - eq("insert", screen.mode) - end) + ]], mode="insert"} feed('word<esc>') - screen:expect([[ + screen:expect{grid=[[ wor^d | {0:~ }| {0:~ }| | - ]], nil, nil, function () - eq("normal", screen.mode) - end) + ]], mode="normal"} command("set showmatch") eq(eval('&matchtime'), 5) -- tenths of seconds feed('a(stuff') - screen:expect([[ + screen:expect{grid=[[ word(stuff^ | {0:~ }| {0:~ }| {2:-- INSERT --} | - ]], nil, nil, function () - eq("insert", screen.mode) - end) + ]], mode="insert"} feed(')') - screen:expect([[ + screen:expect{grid=[[ word^(stuff) | {0:~ }| {0:~ }| {2:-- INSERT --} | - ]], nil, nil, function () - eq("showmatch", screen.mode) - end) + ]], mode="showmatch"} screen:sleep(400) - screen:expect([[ + screen:expect{grid=[[ word(stuff)^ | {0:~ }| {0:~ }| {2:-- INSERT --} | - ]], nil, nil, function () - eq("insert", screen.mode) - end) + ]], mode="insert"} end) it('works in replace mode', function() feed('R') - screen:expect([[ + screen:expect{grid=[[ ^ | {0:~ }| {0:~ }| {2:-- REPLACE --} | - ]], nil, nil, function () - eq("replace", screen.mode) - end) + ]], mode="replace"} feed('word<esc>') - screen:expect([[ + screen:expect{grid=[[ wor^d | {0:~ }| {0:~ }| | - ]], nil, nil, function () - eq("normal", screen.mode) - end) + ]], mode="normal"} end) it('works in cmdline mode', function() feed(':') - screen:expect([[ + screen:expect{grid=[[ | {0:~ }| {0:~ }| :^ | - ]],nil,nil,function () - eq("cmdline_normal", screen.mode) - end) + ]], mode="cmdline_normal"} feed('x<left>') - screen:expect([[ + screen:expect{grid=[[ | {0:~ }| {0:~ }| :^x | - ]],nil,nil,function () - eq("cmdline_insert", screen.mode) - end) + ]], mode="cmdline_insert"} feed('<insert>') - screen:expect([[ + screen:expect{grid=[[ | {0:~ }| {0:~ }| :^x | - ]],nil,nil,function () - eq("cmdline_replace", screen.mode) - end) + ]], mode="cmdline_replace"} feed('<right>') - screen:expect([[ + screen:expect{grid=[[ | {0:~ }| {0:~ }| :x^ | - ]],nil,nil,function () - eq("cmdline_normal", screen.mode) - end) + ]], mode="cmdline_normal"} feed('<esc>') - screen:expect([[ + screen:expect{grid=[[ ^ | {0:~ }| {0:~ }| | - ]],nil,nil,function () - eq("normal", screen.mode) - end) + ]], mode="normal"} end) - it('works in visal mode', function() + it('works in visual mode', function() insert("text") feed('v') - screen:expect([[ + screen:expect{grid=[[ tex^t | {0:~ }| {0:~ }| {2:-- VISUAL --} | - ]],nil,nil,function () - eq("visual", screen.mode) - end) + ]], mode="visual"} feed('<esc>') - screen:expect([[ + screen:expect{grid=[[ tex^t | {0:~ }| {0:~ }| | - ]],nil,nil,function () - eq("normal", screen.mode) - end) + ]], mode="normal"} command('set selection=exclusive') feed('v') - screen:expect([[ + screen:expect{grid=[[ tex^t | {0:~ }| {0:~ }| {2:-- VISUAL --} | - ]],nil,nil,function () - eq("visual_select", screen.mode) - end) + ]], mode="visual_select"} feed('<esc>') - screen:expect([[ + screen:expect{grid=[[ tex^t | {0:~ }| {0:~ }| | - ]],nil,nil,function () - eq("normal", screen.mode) - end) + ]], mode="normal"} end) end) diff --git a/test/functional/ui/output_spec.lua b/test/functional/ui/output_spec.lua index 93d8965cb1..0f076eac26 100644 --- a/test/functional/ui/output_spec.lua +++ b/test/functional/ui/output_spec.lua @@ -61,7 +61,7 @@ describe("shell command :!", function() ":!for i in $(seq 2 3000); do echo XXXXXXXXXX $i; done\n") -- If we observe any line starting with a dot, then throttling occurred. - screen:expect("\n.", nil, nil, nil, true) + screen:expect{any="\n."} -- Final chunk of output should always be displayed, never skipped. -- (Throttling is non-deterministic, this test is merely a sanity check.) @@ -92,7 +92,7 @@ describe("shell command :!", function() eq(2, eval('1+1')) -- Still alive? end) - it([[handles control codes]], function() + it('handles control codes', function() if iswin() then pending('missing printf', function() end) return @@ -112,14 +112,14 @@ describe("shell command :!", function() -- Print BELL control code. #4338 screen.bell = false feed([[:!printf '\007\007\007\007text'<CR>]]) - screen:expect([[ + screen:expect{grid=[[ ~ | :!printf '\007\007\007\007text' | text | Press ENTER or type command to continue^ | - ]], nil, nil, function() + ]], condition=function() eq(true, screen.bell) - end) + end} feed([[<CR>]]) -- Print BS control code. feed([[:echo system('printf ''\010\n''')<CR>]]) @@ -188,7 +188,7 @@ describe("shell command :!", function() it('handles binary and multibyte data', function() feed_command('!cat test/functional/fixtures/shell_data.txt') screen.bell = false - screen:expect([[ + screen:expect{grid=[[ | {1:~ }| {4: }| @@ -199,9 +199,9 @@ describe("shell command :!", function() t {2:<ff>} | | {3:Press ENTER or type command to continue}^ | - ]], nil, nil, function() + ]], condition=function() eq(true, screen.bell) - end) + end} end) it('handles multibyte sequences split over buffer boundaries', function() diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua new file mode 100644 index 0000000000..8c583c90fe --- /dev/null +++ b/test/functional/ui/popupmenu_spec.lua @@ -0,0 +1,91 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear, feed = helpers.clear, helpers.feed +local source = helpers.source + +describe('ui/ext_popupmenu', function() + local screen + before_each(function() + clear() + screen = Screen.new(60, 8) + screen:attach({rgb=true, ext_popupmenu=true}) + screen:set_default_attr_ids({ + [1] = {bold=true, foreground=Screen.colors.Blue}, + [2] = {bold = true}, + }) + end) + + it('works', function() + source([[ + function! TestComplete() abort + call complete(1, ['foo', 'bar', 'spam']) + return '' + endfunction + ]]) + local expected = { + {'foo', '', '', ''}, + {'bar', '', '', ''}, + {'spam', '', '', ''}, + } + feed('o<C-r>=TestComplete()<CR>') + screen:expect{grid=[[ + | + foo^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]], popupmenu={ + items=expected, + pos=0, + anchor={1,0}, + }} + + feed('<c-p>') + screen:expect{grid=[[ + | + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]], popupmenu={ + items=expected, + pos=-1, + anchor={1,0}, + }} + + -- down moves the selection in the menu, but does not insert anything + feed('<down><down>') + screen:expect{grid=[[ + | + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]], popupmenu={ + items=expected, + pos=1, + anchor={1,0}, + }} + + feed('<cr>') + screen:expect{grid=[[ + | + bar^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]} + end) +end) diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index 872a9d3200..18c0fe2c2a 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -71,6 +71,8 @@ -- To help write screen tests, see Screen:snapshot_util(). -- To debug screen tests, see Screen:redraw_debug(). +local global_helpers = require('test.helpers') +local shallowcopy = global_helpers.shallowcopy local helpers = require('test.functional.helpers')(nil) local request, run, uimeths = helpers.request, helpers.run, helpers.uimeths local eq = helpers.eq @@ -139,6 +141,11 @@ function Screen.new(width, height) suspended = false, mode = 'normal', options = {}, + popupmenu = nil, + cmdline = {}, + cmdline_block = {}, + wildmenu_items = nil, + wildmenu_selected = nil, _default_attr_ids = nil, _default_attr_ignore = nil, _mouse_enabled = true, @@ -193,32 +200,80 @@ function Screen:set_option(option, value) self._options[option] = value end --- Asserts that `expected` eventually matches the screen state. +-- Asserts that the screen state eventually matches an expected state -- --- expected: Expected screen state (string). Each line represents a screen +-- This function can either be called with the positional forms +-- +-- screen:expect(grid, [attr_ids, attr_ignore]) +-- screen:expect(condition) +-- +-- or to use additional arguments (or grid and condition at the same time) +-- the keyword form has to be used: +-- +-- screen:expect{grid=[[...]], cmdline={...}, condition=function() ... end} +-- +-- +-- grid: Expected screen state (string). Each line represents a screen -- row. Last character of each row (typically "|") is stripped. -- Common indentation is stripped. --- Used as `condition` if NOT a string; must be the ONLY arg then. -- attr_ids: Expected text attributes. Screen rows are transformed according -- to this table, as follows: each substring S composed of -- characters having the same attributes will be substituted by -- "{K:S}", where K is a key in `attr_ids`. Any unexpected -- attributes in the final state are an error. --- attr_ignore: Ignored text attributes, or `true` to ignore all. --- condition: Function asserting some arbitrary condition. --- any: true: Succeed if `expected` matches ANY screen line(s). --- false (default): `expected` must match screen exactly. -function Screen:expect(expected, attr_ids, attr_ignore, condition, any) +-- Use screen:set_default_attr_ids() to define attributes for many +-- expect() calls. +-- attr_ignore: Ignored text attributes, or `true` to ignore all. By default +-- nothing is ignored. +-- condition: Function asserting some arbitrary condition. Return value is +-- ignored, throw an error (use eq() or similar) to signal failure. +-- any: A string that should be present on any line of the screen. +-- mode: Expected mode as signaled by "mode_change" event +-- +-- The following keys should be used to expect the state of various ext_ +-- features. Note that an absent key will assert that the item is currently +-- NOT present on the screen, also when positional form is used. +-- +-- popupmenu: Expected ext_popupmenu state, +-- cmdline: Expected ext_cmdline state, as an array of cmdlines of +-- different level. +-- cmdline_block: Expected ext_cmdline block (for function definitions) +-- wildmenu_items: Expected items for ext_wildmenu +-- wildmenu_pos: Expected position for ext_wildmenu +function Screen:expect(expected, attr_ids, attr_ignore) + local grid, condition = nil, nil local expected_rows = {} - if type(expected) ~= "string" then - assert(not (attr_ids or attr_ignore or condition or any)) + if type(expected) == "table" then + assert(not (attr_ids ~= nil or attr_ignore ~= nil)) + local is_key = {grid=true, attr_ids=true, attr_ignore=true, condition=true, + any=true, mode=true, popupmenu=true, cmdline=true, + cmdline_block=true, wildmenu_items=true, wildmenu_pos=true} + for k, _ in pairs(expected) do + if not is_key[k] then + error("Screen:expect: Unknown keyword argument '"..k.."'") + end + end + grid = expected.grid + attr_ids = expected.attr_ids + attr_ignore = expected.attr_ignore + condition = expected.condition + assert(not (expected.any ~= nil and grid ~= nil)) + elseif type(expected) == "string" then + grid = expected + expected = {} + elseif type(expected) == "function" then + assert(not (attr_ids ~= nil or attr_ignore ~= nil)) condition = expected - expected = nil + expected = {} else + assert(false) + end + + if grid ~= nil then -- Remove the last line and dedent. Note that gsub returns more then one -- value. - expected = dedent(expected:gsub('\n[ ]+$', ''), 0) - for row in expected:gmatch('[^\n]+') do + grid = dedent(grid:gsub('\n[ ]+$', ''), 0) + for row in grid:gmatch('[^\n]+') do row = row:sub(1, #row - 1) -- Last char must be the screen delimiter. table.insert(expected_rows, row) end @@ -238,7 +293,7 @@ function Screen:expect(expected, attr_ids, attr_ignore, condition, any) end end - if expected and not any and self._height ~= #expected_rows then + if grid ~= nil and self._height ~= #expected_rows then return ("Expected screen state's row count(" .. #expected_rows .. ') differs from configured height(' .. self._height .. ') of Screen.') end @@ -253,18 +308,18 @@ function Screen:expect(expected, attr_ids, attr_ignore, condition, any) actual_rows[i] = self:_row_repr(self._rows[i], info, ignore) end - if expected == nil then - return - elseif any then - -- Search for `expected` anywhere in the screen lines. + if expected.any ~= nil then + -- Search for `any` anywhere in the screen lines. local actual_screen_str = table.concat(actual_rows, '\n') - if nil == string.find(actual_screen_str, expected) then + if nil == string.find(actual_screen_str, expected.any) then return ( 'Failed to match any screen lines.\n' - .. 'Expected (anywhere): "' .. expected .. '"\n' + .. 'Expected (anywhere): "' .. expected.any .. '"\n' .. 'Actual:\n |' .. table.concat(actual_rows, '|\n |') .. '|\n\n') end - else + end + + if grid ~= nil then -- `expected` must match the screen lines exactly. for i = 1, self._height do if expected_rows[i] ~= actual_rows[i] then @@ -284,6 +339,38 @@ screen:redraw_debug() to show all intermediate screen states. ]]) end end end + + -- Extension features. The default expectations should cover the case of + -- the ext_ feature being disabled, or the feature currently not activated + -- (for instance no external cmdline visible) + local expected_cmdline = expected.cmdline or {} + local actual_cmdline = {} + for i, entry in pairs(self.cmdline) do + entry = shallowcopy(entry) + entry.content = self:_chunks_repr(entry.content, info, ignore) + actual_cmdline[i] = entry + end + + local expected_block = expected.cmdline_block or {} + local actual_block = {} + for i, entry in ipairs(self.cmdline_block) do + actual_block[i] = self:_chunks_repr(entry, info, ignore) + end + + -- convert assertion errors into invalid screen state descriptions + local status, res = pcall(function() + eq(expected.popupmenu, self.popupmenu, "popupmenu") + eq(expected_cmdline, actual_cmdline, "cmdline") + eq(expected_block, actual_block, "cmdline_block") + eq(expected.wildmenu_items, self.wildmenu_items, "wildmenu_items") + eq(expected.wildmenu_pos, self.wildmenu_pos, "wildmenu_pos") + if expected.mode ~= nil then + eq(expected.mode, self.mode, "mode") + end + end) + if not status then + return tostring(res) + end end) end @@ -510,14 +597,6 @@ function Screen:_handle_hl_attr_define(id, rgb_attrs, cterm_attrs, info) self._new_attrs = true end -function Screen:get_hl(val) - if self._options.ext_newgrid then - return self._attr_table[val][1] - else - return val - end -end - function Screen:_handle_highlight_set(attrs) self._attrs = attrs end @@ -595,6 +674,63 @@ function Screen:_handle_option_set(name, value) self.options[name] = value end +function Screen:_handle_popupmenu_show(items, selected, row, col) + self.popupmenu = {items=items,pos=selected, anchor={row, col}} +end + +function Screen:_handle_popupmenu_select(selected) + self.popupmenu.pos = selected +end + +function Screen:_handle_popupmenu_hide() + self.popupmenu = nil +end + +function Screen:_handle_cmdline_show(content, pos, firstc, prompt, indent, level) + if firstc == '' then firstc = nil end + if prompt == '' then prompt = nil end + if indent == 0 then indent = nil end + self.cmdline[level] = {content=content, pos=pos, firstc=firstc, + prompt=prompt, indent=indent} +end + +function Screen:_handle_cmdline_hide(level) + self.cmdline[level] = nil +end + +function Screen:_handle_cmdline_special_char(char, shift, level) + -- cleared by next cmdline_show on the same level + self.cmdline[level].special = {char, shift} +end + +function Screen:_handle_cmdline_pos(pos, level) + self.cmdline[level].pos = pos +end + +function Screen:_handle_cmdline_block_show(block) + self.cmdline_block = block +end + +function Screen:_handle_cmdline_block_append(item) + self.cmdline_block[#self.cmdline_block+1] = item +end + +function Screen:_handle_cmdline_block_hide() + self.cmdline_block = {} +end + +function Screen:_handle_wildmenu_show(items) + self.wildmenu_items = items +end + +function Screen:_handle_wildmenu_select(pos) + self.wildmenu_pos = pos +end + +function Screen:_handle_wildmenu_hide() + self.wildmenu_items, self.wildmenu_pos = nil, nil +end + function Screen:_clear_block(top, bot, left, right) for i = top, bot do self:_clear_row_section(i, left, right) @@ -643,6 +779,21 @@ function Screen:_row_repr(row, attr_ids, attr_ignore) return table.concat(rv, '')--:gsub('%s+$', '') end +function Screen:_chunks_repr(chunks, attr_ids, attr_ignore) + local repr_chunks = {} + for i, chunk in ipairs(chunks) do + local hl, text = unpack(chunk) + local attrs + if self._options.ext_newgrid then + attrs = self._attr_table[hl][1] + else + attrs = hl + end + local attr_id = self:_get_attr_id(attr_ids, attr_ignore, attrs, hl) + repr_chunks[i] = {text, attr_id} + end + return repr_chunks +end function Screen:_current_screen() -- get a string that represents the current screen state(debugging helper) diff --git a/test/functional/ui/tabline_spec.lua b/test/functional/ui/tabline_spec.lua index e8271de0bf..dcab9f7ef4 100644 --- a/test/functional/ui/tabline_spec.lua +++ b/test/functional/ui/tabline_spec.lua @@ -28,27 +28,27 @@ describe('ui/ext_tabline', function() {tab = { id = 1 }, name = '[No Name]'}, {tab = { id = 2 }, name = 'another-tab'}, } - screen:expect([[ + screen:expect{grid=[[ ^ | ~ | ~ | ~ | | - ]], nil, nil, function() + ]], condition=function() eq({ id = 2 }, event_curtab) eq(expected_tabs, event_tabs) - end) + end} command("tabNext") - screen:expect([[ + screen:expect{grid=[[ ^ | ~ | ~ | ~ | | - ]], nil, nil, function() + ]], condition=function() eq({ id = 1 }, event_curtab) eq(expected_tabs, event_tabs) - end) + end} end) end) diff --git a/test/functional/ui/wildmode_spec.lua b/test/functional/ui/wildmode_spec.lua index b60d520ca0..72dbef9538 100644 --- a/test/functional/ui/wildmode_spec.lua +++ b/test/functional/ui/wildmode_spec.lua @@ -167,7 +167,7 @@ describe("'wildmenu'", function() screen:sleep(10) -- Flush -- Check only the last 2 lines, because the shell output is -- system-dependent. - screen:expect('! # & < = > @ > \n:!^', nil, nil, nil, true) + screen:expect{any='! # & < = > @ > \n:!^'} end) end) @@ -204,21 +204,11 @@ end) describe('ui/ext_wildmenu', function() local screen - local items, selected = nil, nil before_each(function() clear() screen = Screen.new(25, 5) screen:attach({rgb=true, ext_wildmenu=true}) - screen:set_on_event_handler(function(name, data) - if name == "wildmenu_show" then - items = data[1] - elseif name == "wildmenu_select" then - selected = data[1] - elseif name == "wildmenu_hide" then - items, selected = nil, nil - end - end) end) after_each(function() @@ -238,63 +228,48 @@ describe('ui/ext_wildmenu', function() command('set wildmode=full') command('set wildmenu') feed(':sign <tab>') - screen:expect([[ + screen:expect{grid=[[ | ~ | ~ | ~ | :sign define^ | - ]], nil, nil, function() - eq(expected, items) - eq(0, selected) - end) + ]], wildmenu_items=expected, wildmenu_pos=0} feed('<tab>') - screen:expect([[ + screen:expect{grid=[[ | ~ | ~ | ~ | :sign jump^ | - ]], nil, nil, function() - eq(expected, items) - eq(1, selected) - end) + ]], wildmenu_items=expected, wildmenu_pos=1} feed('<left><left>') - screen:expect([[ + screen:expect{grid=[[ | ~ | ~ | ~ | :sign ^ | - ]], nil, nil, function() - eq(expected, items) - eq(-1, selected) - end) + ]], wildmenu_items=expected, wildmenu_pos=-1} feed('<right>') - screen:expect([[ + screen:expect{grid=[[ | ~ | ~ | ~ | :sign define^ | - ]], nil, nil, function() - eq(expected, items) - eq(0, selected) - end) + ]], wildmenu_items=expected, wildmenu_pos=0} feed('a') - screen:expect([[ + screen:expect{grid=[[ | ~ | ~ | ~ | :sign definea^ | - ]], nil, nil, function() - eq(nil, items) - eq(nil, selected) - end) + ]]} end) end) diff --git a/test/functional/viml/completion_spec.lua b/test/functional/viml/completion_spec.lua index c14f7fc1a6..3222e5783d 100644 --- a/test/functional/viml/completion_spec.lua +++ b/test/functional/viml/completion_spec.lua @@ -873,107 +873,6 @@ describe('completion', function() {3:-- Keyword Local completion (^N^P) }{4:match 1 of 7} | ]]) end) -end) - -describe('ui/ext_popupmenu', function() - local screen - local items, selected, anchor - before_each(function() - clear() - screen = Screen.new(60, 8) - screen:attach({rgb=true, ext_popupmenu=true}) - screen:set_default_attr_ids({ - [1] = {bold=true, foreground=Screen.colors.Blue}, - [2] = {bold = true}, - }) - screen:set_on_event_handler(function(name, data) - if name == "popupmenu_show" then - local row, col - items, selected, row, col = unpack(data) - anchor = {row, col} - elseif name == "popupmenu_select" then - selected = data[1] - elseif name == "popupmenu_hide" then - items = nil - end - end) - end) - - it('works', function() - source([[ - function! TestComplete() abort - call complete(1, ['foo', 'bar', 'spam']) - return '' - endfunction - ]]) - local expected = { - {'foo', '', '', ''}, - {'bar', '', '', ''}, - {'spam', '', '', ''}, - } - feed('o<C-r>=TestComplete()<CR>') - screen:expect([[ - | - foo^ | - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {2:-- INSERT --} | - ]], nil, nil, function() - eq(expected, items) - eq(0, selected) - eq({1,0}, anchor) - end) - - feed('<c-p>') - screen:expect([[ - | - ^ | - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {2:-- INSERT --} | - ]], nil, nil, function() - eq(expected, items) - eq(-1, selected) - eq({1,0}, anchor) - end) - - -- down moves the selection in the menu, but does not insert anything - feed('<down><down>') - screen:expect([[ - | - ^ | - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {2:-- INSERT --} | - ]], nil, nil, function() - eq(expected, items) - eq(1, selected) - eq({1,0}, anchor) - end) - - feed('<cr>') - screen:expect([[ - | - bar^ | - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {1:~ }| - {2:-- INSERT --} | - ]], nil, nil, function() - eq(nil, items) -- popupmenu was hidden - end) - end) it('TextChangedP autocommand', function() curbufmeths.set_lines(0, 1, false, { 'foo', 'bar', 'foobar'}) diff --git a/test/helpers.lua b/test/helpers.lua index 66724f6a78..013fe60596 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -45,8 +45,8 @@ local check_logs_useless_lines = { ['See README_MISSING_SYSCALL_OR_IOCTL for guidance']=3, } -local function eq(expected, actual) - return assert.are.same(expected, actual) +local function eq(expected, actual, ctx) + return assert.are.same(expected, actual, ctx) end local function neq(expected, actual) return assert.are_not.same(expected, actual) @@ -136,7 +136,6 @@ local function check_logs() fd:close() os.remove(file) if #lines > 0 then - -- local out = os.getenv('TRAVIS_CI_BUILD') and io.stdout or io.stderr local out = io.stdout out:write(start_msg .. '\n') out:write('= ' .. table.concat(lines, '\n= ') .. '\n') @@ -674,6 +673,37 @@ local function write_file(name, text, no_dedent, append) file:close() end +local function isCI() + local is_travis = nil ~= os.getenv('TRAVIS') + local is_appveyor = nil ~= os.getenv('APPVEYOR') + local is_quickbuild = nil ~= os.getenv('PR_NUMBER') + return is_travis or is_appveyor or is_quickbuild +end + +-- Gets the contents of $NVIM_LOG_FILE for printing to the build log. +-- Also removes the file, if the current environment looks like CI. +local function read_nvim_log() + local logfile = os.getenv('NVIM_LOG_FILE') or '.nvimlog' + local logtext = read_file(logfile) + local lines = {} + for l in string.gmatch(logtext or '', "[^\n]+") do -- Split at newlines. + table.insert(lines, l) + end + local log = (('-'):rep(78)..'\n' + ..string.format('$NVIM_LOG_FILE: %s\n', logfile) + ..(logtext and (isCI() and '' or '(last 10 lines)\n') or '(empty)\n')) + local keep = (isCI() and #lines or math.min(10, #lines)) + local startidx = math.max(1, #lines - keep + 1) + for i = startidx, (startidx + keep - 1) do + log = log..lines[i]..'\n' + end + log = log..('-'):rep(78)..'\n' + if isCI() then + os.remove(logfile) + end + return log +end + local module = { REMOVE_THIS = REMOVE_THIS, argss_to_cmd = argss_to_cmd, @@ -703,9 +733,10 @@ local module = { popen_r = popen_r, popen_w = popen_w, read_file = read_file, + read_nvim_log = read_nvim_log, repeated_read_cmd = repeated_read_cmd, - sleep = sleep, shallowcopy = shallowcopy, + sleep = sleep, table_flatten = table_flatten, tmpname = tmpname, uname = uname, diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index 83d7b69b45..0905b70be2 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -154,8 +154,8 @@ set(GPERF_SHA256 588546b945bba4b70b6a3a616e80b4ab466e3f33024a352fc2198112cdbb3ae set(WINTOOLS_URL https://github.com/neovim/deps/raw/2f9acbecf06365c10baa3c0087f34a54c9c6f949/opt/win32tools.zip) set(WINTOOLS_SHA256 8bfce7e3a365721a027ce842f2ec1cf878f1726233c215c05964aac07300798c) -set(WINGUI_URL https://github.com/equalsraf/neovim-qt/releases/download/v0.2.8/neovim-qt.zip) -set(WINGUI_SHA256 260efc686423e2529360b6a45c8e241fbbf276c8de6b04d44f45ab5b6fe8df8f) +set(WINGUI_URL https://github.com/equalsraf/neovim-qt/releases/download/v0.2.10/neovim-qt.zip) +set(WINGUI_SHA256 97988a96994b6066ea2b5f035a633304d664b8a14c91e946c5833a6e3782fdbb) set(WIN32YANK_X86_URL https://github.com/equalsraf/win32yank/releases/download/v0.0.4/win32yank-x86.zip) set(WIN32YANK_X86_SHA256 62f34e5a46c5d4a7b3f3b512e1ff7b77fedd432f42581cbe825233a996eed62c) |