aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--MAINTAIN.md10
-rw-r--r--runtime/autoload/health/nvim.vim11
-rw-r--r--runtime/autoload/health/provider.vim7
-rw-r--r--runtime/autoload/spellfile.vim14
-rw-r--r--runtime/doc/api.txt132
-rw-r--r--runtime/doc/debug.txt11
-rw-r--r--runtime/doc/deprecated.txt9
-rw-r--r--runtime/doc/develop.txt24
-rw-r--r--runtime/doc/eval.txt166
-rw-r--r--runtime/doc/intro.txt7
-rw-r--r--runtime/doc/msgpack_rpc.txt11
-rw-r--r--runtime/doc/nvim_terminal_emulator.txt29
-rw-r--r--runtime/doc/options.txt57
-rw-r--r--runtime/doc/quickfix.txt6
-rw-r--r--runtime/doc/repeat.txt18
-rw-r--r--runtime/doc/starting.txt100
-rw-r--r--runtime/doc/tagsrch.txt3
-rw-r--r--runtime/doc/ui.txt402
-rw-r--r--runtime/doc/usr_11.txt7
-rw-r--r--runtime/doc/usr_41.txt2
-rw-r--r--runtime/doc/various.txt57
-rw-r--r--runtime/filetype.vim7
-rw-r--r--runtime/indent/vim.vim18
-rw-r--r--runtime/nvim.appdata.xml11
-rw-r--r--runtime/pack/dist/opt/termdebug/plugin/termdebug.vim247
-rw-r--r--runtime/syntax/tutor.vim36
-rw-r--r--runtime/tutor/en/vim-01-beginner.tutor6
-rwxr-xr-xscripts/gen_vimdoc.py20
-rw-r--r--src/nvim/api/buffer.c57
-rw-r--r--src/nvim/api/private/helpers.c226
-rw-r--r--src/nvim/api/private/helpers.h1
-rw-r--r--src/nvim/api/ui.c31
-rw-r--r--src/nvim/api/vim.c66
-rw-r--r--src/nvim/buffer.c9
-rw-r--r--src/nvim/buffer_defs.h70
-rw-r--r--src/nvim/eval.c225
-rw-r--r--src/nvim/eval.lua6
-rw-r--r--src/nvim/eval/typval.c28
-rw-r--r--src/nvim/event/rstream.c10
-rw-r--r--src/nvim/event/stream.c10
-rw-r--r--src/nvim/event/stream.h3
-rw-r--r--src/nvim/ex_cmds.c41
-rw-r--r--src/nvim/ex_cmds2.c35
-rw-r--r--src/nvim/ex_docmd.c18
-rw-r--r--src/nvim/ex_getln.c9
-rw-r--r--src/nvim/file_search.c9
-rw-r--r--src/nvim/fileio.c190
-rw-r--r--src/nvim/getchar.c725
-rw-r--r--src/nvim/getchar.h34
-rw-r--r--src/nvim/globals.h1
-rw-r--r--src/nvim/main.c7
-rw-r--r--src/nvim/memline.c268
-rw-r--r--src/nvim/menu.c11
-rw-r--r--src/nvim/message.c15
-rw-r--r--src/nvim/misc1.c16
-rw-r--r--src/nvim/move.c4
-rw-r--r--src/nvim/normal.c3
-rw-r--r--src/nvim/ops.c12
-rw-r--r--src/nvim/option.c70
-rw-r--r--src/nvim/os/process.c6
-rw-r--r--src/nvim/os/stdpaths.c10
-rw-r--r--src/nvim/os/stdpaths_defs.h1
-rw-r--r--src/nvim/path.c9
-rw-r--r--src/nvim/popupmnu.c6
-rw-r--r--src/nvim/quickfix.c1490
-rw-r--r--src/nvim/regexp.c13
-rw-r--r--src/nvim/regexp_defs.h6
-rw-r--r--src/nvim/regexp_nfa.c6
-rw-r--r--src/nvim/screen.c54
-rw-r--r--src/nvim/shada.c2
-rw-r--r--src/nvim/spell.c15
-rw-r--r--src/nvim/spellfile.c8
-rw-r--r--src/nvim/syntax.c42
-rw-r--r--src/nvim/tag.c205
-rw-r--r--src/nvim/terminal.c25
-rw-r--r--src/nvim/testdir/Makefile9
-rw-r--r--src/nvim/testdir/test14.in94
-rw-r--r--src/nvim/testdir/test14.ok26
-rw-r--r--src/nvim/testdir/test16.in15
-rw-r--r--src/nvim/testdir/test16.ok2
-rw-r--r--src/nvim/testdir/test17.in126
-rw-r--r--src/nvim/testdir/test17.ok33
-rw-r--r--src/nvim/testdir/test17a.in3
-rw-r--r--src/nvim/testdir/test_autocmd.vim296
-rw-r--r--src/nvim/testdir/test_checkpath.vim113
-rw-r--r--src/nvim/testdir/test_debugger.vim232
-rw-r--r--src/nvim/testdir/test_edit.vim16
-rw-r--r--src/nvim/testdir/test_eval_stuff.vim28
-rw-r--r--src/nvim/testdir/test_filetype.vim5
-rw-r--r--src/nvim/testdir/test_findfile.vim1
-rw-r--r--src/nvim/testdir/test_gf.vim35
-rw-r--r--src/nvim/testdir/test_listchars.vim39
-rw-r--r--src/nvim/testdir/test_mapping.vim40
-rw-r--r--src/nvim/testdir/test_normal.vim95
-rw-r--r--src/nvim/testdir/test_quickfix.vim488
-rw-r--r--src/nvim/testdir/test_search.vim24
-rw-r--r--src/nvim/testdir/test_substitute.vim49
-rw-r--r--src/nvim/testdir/test_swap.vim163
-rw-r--r--src/nvim/testdir/test_syntax.vim34
-rw-r--r--src/nvim/testdir/test_tabpage.vim32
-rw-r--r--src/nvim/testdir/test_tagjump.vim101
-rw-r--r--src/nvim/testdir/test_taglist.vim25
-rw-r--r--src/nvim/testdir/test_undo.vim1
-rw-r--r--src/nvim/testdir/test_visual.vim24
-rw-r--r--src/nvim/tui/input.c8
-rw-r--r--src/nvim/tui/input.h3
-rw-r--r--src/nvim/tui/tui.c8
-rw-r--r--src/nvim/ui.c15
-rw-r--r--src/nvim/ui.h6
-rw-r--r--src/nvim/undo.c20
-rw-r--r--src/nvim/vim.h1
-rw-r--r--src/nvim/window.c48
-rw-r--r--test/functional/api/buffer_spec.lua35
-rw-r--r--test/functional/api/keymap_spec.lua505
-rw-r--r--test/functional/api/vim_spec.lua4
-rw-r--r--test/functional/autocmd/autocmd_spec.lua116
-rw-r--r--test/functional/core/channels_spec.lua8
-rw-r--r--test/functional/core/main_spec.lua12
-rw-r--r--test/functional/core/spellfile_spec.lua (renamed from test/functional/spell/spellfile_spec.lua)0
-rw-r--r--test/functional/core/startup_spec.lua24
-rw-r--r--test/functional/eval/api_functions_spec.lua2
-rw-r--r--test/functional/ex_cmds/oldfiles_spec.lua12
-rw-r--r--test/functional/ex_cmds/quickfix_commands_spec.lua10
-rw-r--r--test/functional/ex_cmds/wviminfo_spec.lua16
-rw-r--r--test/functional/helpers.lua72
-rw-r--r--test/functional/legacy/074_global_var_in_viminfo_spec.lua8
-rw-r--r--test/functional/legacy/assert_spec.lua9
-rw-r--r--test/functional/options/defaults_spec.lua58
-rw-r--r--test/functional/provider/clipboard_spec.lua (renamed from test/functional/clipboard/clipboard_provider_spec.lua)18
-rw-r--r--test/functional/terminal/ex_terminal_spec.lua9
-rw-r--r--test/functional/terminal/highlight_spec.lua50
-rw-r--r--test/functional/terminal/tui_spec.lua6
-rw-r--r--test/functional/ui/cmdline_highlight_spec.lua2
-rw-r--r--test/functional/ui/cmdline_spec.lua25
-rw-r--r--test/functional/ui/embed_spec.lua2
-rw-r--r--test/functional/ui/messages_spec.lua133
-rw-r--r--test/functional/ui/multigrid_spec.lua2
-rw-r--r--test/functional/ui/options_spec.lua3
-rw-r--r--test/functional/ui/screen.lua28
-rw-r--r--test/functional/viml/errorlist_spec.lua8
141 files changed, 6672 insertions, 2300 deletions
diff --git a/.gitignore b/.gitignore
index cf0a11804d..bf9928ce15 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
# Tools
.ropeproject/
+compile_commands.json
# Visual Studio
/.vs/
diff --git a/MAINTAIN.md b/MAINTAIN.md
index 55f4e7afc2..e102d7a94b 100644
--- a/MAINTAIN.md
+++ b/MAINTAIN.md
@@ -44,14 +44,16 @@ Release Policy
Release "often", but not "early".
The (unreleased) `master` branch is the "early" channel; it should not be
-released if it's not stable. Medium-risk changes may be merged to `master` if
+released if it's not stable. High-risk changes may be merged to `master` if
the next feature-release is not imminent.
-For maintenance releases, create a `release-x.y` branch. If the current stable
-release has a major bug:
+For maintenance releases, create a `release-x.y` branch. If the current release
+has a major bug:
1. Fix the bug on `master`.
2. Cherry-pick the fix to `release-x.y`.
-3. Cut a release from `release-x.y` (run `scripts/release.sh`).
+3. Cut a release from `release-x.y`.
+ - Run `./scripts/release.sh`
+ - Update (force-push) the remote `stable` tag.
See also: https://github.com/neovim/neovim/issues/862
diff --git a/runtime/autoload/health/nvim.vim b/runtime/autoload/health/nvim.vim
index efa3292801..8fcea2e941 100644
--- a/runtime/autoload/health/nvim.vim
+++ b/runtime/autoload/health/nvim.vim
@@ -170,6 +170,17 @@ function! s:check_tmux() abort
\ ["Set default-terminal in ~/.tmux.conf:\nset-option -g default-terminal \"screen-256color\"",
\ s:suggest_faq])
endif
+
+ " check for RGB capabilities
+ let info = system('tmux server-info')
+ let has_tc = stridx(info, " Tc: (flag) true") != -1
+ let has_rgb = stridx(info, " RGB: (flag) true") != -1
+ if !has_tc && !has_rgb
+ call health#report_warn(
+ \ "Neither Tc nor RGB capability set. True colors are disabled. |'termguicolors'| won't work properly.",
+ \ ["Put this in your ~/.tmux.conf and replace XXX by your $TERM outside of tmux:\nset-option -sa terminal-overrides ',XXX:RGB'",
+ \ "For older tmux versions use this instead:\nset-option -ga terminal-overrides ',XXX:Tc'"])
+ endif
endfunction
function! s:check_terminal() abort
diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim
index 8f364a2ace..29bbee4888 100644
--- a/runtime/autoload/health/provider.vim
+++ b/runtime/autoload/health/provider.vim
@@ -106,7 +106,8 @@ endfunction
" Fetch the contents of a URL.
function! s:download(url) abort
- if executable('curl')
+ let has_curl = executable('curl')
+ if has_curl && system(['curl', '-V']) =~# 'Protocols:.*https'
let rv = s:system(['curl', '-sL', a:url], '', 1, 1)
return s:shell_error ? 'curl error with '.a:url.': '.s:shell_error : rv
elseif executable('python')
@@ -124,7 +125,9 @@ function! s:download(url) abort
\ ? 'python urllib.request error: '.s:shell_error
\ : rv
endif
- return 'missing `curl` and `python`, cannot make pypi request'
+ return 'missing `curl` '
+ \ .(has_curl ? '(with HTTPS support) ' : '')
+ \ .'and `python`, cannot make web request'
endfunction
" Check for clipboard tools.
diff --git a/runtime/autoload/spellfile.vim b/runtime/autoload/spellfile.vim
index 9ec6091218..c0ef51cdfe 100644
--- a/runtime/autoload/spellfile.vim
+++ b/runtime/autoload/spellfile.vim
@@ -195,16 +195,6 @@ function! spellfile#GetDirChoices()
endfunc
function! spellfile#WritableSpellDir()
- " Always use the $XDG_DATA_HOME/nvim/site directory
- if exists('$XDG_DATA_HOME')
- return $XDG_DATA_HOME . "/nvim/site/spell"
- elseif !(has('win32') || has('win64'))
- return $HOME . "/.local/share/nvim/site/spell"
- endif
- for dir in split(&rtp, ',')
- if filewritable(dir) == 2
- return dir . "/spell"
- endif
- endfor
- return ''
+ " Always use the $XDG_DATA_HOME/…/site directory
+ return stdpath('data').'/site/spell'
endfunction
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index c8b9dd5fad..ed6e4905b4 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -46,7 +46,7 @@ version.api_prerelease Declares the API as unstable/unreleased >
(version.api_prerelease && fn.since == version.api_level)
functions API function signatures
ui_events UI event signatures |ui|
-ui_options Supported |ui-options|
+ui_options Supported |ui-option|s
{fn}.since API level where function {fn} was introduced
{fn}.deprecated_since API level where function {fn} was deprecated
types Custom handle types defined by Nvim
@@ -206,17 +206,15 @@ 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:
+
+Example using the Python API client (|pynvim|):
>
src = vim.new_highlight_source()
-
buf = vim.current.buffer
for i in range(5):
buf.add_highlight("String",i,0,-1,src_id=src)
-
- # some time later
-
- buf.clear_highlight(src)
+ # some time later ...
+ buf.clear_namespace(src)
<
If the highlights don't need to be deleted or updated, just pass -1 as
src_id (this is the default in python). Use |nvim_buf_clear_namespace()| to
@@ -224,13 +222,12 @@ 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: >
+Example using the API from Vimscript: >
call nvim_buf_set_lines(0, 0, 0, v:true, ["test text"])
let src = nvim_buf_add_highlight(0, 0, "String", 1, 0, 4)
call nvim_buf_add_highlight(0, src, "Identifier", 0, 5, -1)
-
- " later
+ " some time later ...
call nvim_buf_clear_namespace(0, src, 0, -1)
@@ -494,8 +491,6 @@ nvim_set_current_dir({dir}) *nvim_set_current_dir()*
nvim_get_current_line() *nvim_get_current_line()*
Gets the current line.
- Parameters: ~
-
Return: ~
Current line string
@@ -508,8 +503,6 @@ nvim_set_current_line({line}) *nvim_set_current_line()*
nvim_del_current_line() *nvim_del_current_line()*
Deletes the current line.
- Parameters: ~
-
nvim_get_var({name}) *nvim_get_var()*
Gets a global (g:) variable.
@@ -656,6 +649,7 @@ nvim_open_win({buffer}, {enter}, {config}) *nvim_open_win()*
For a general overview of floats, see |api-floatwin|.
Exactly one of `external` and `relative` must be specified.
+ The `width` and `height` of the new window must be specified.
With editor positioning row=0, col=0 refers to the top-left
corner of the screen-grid and row=Lines-1, Columns-1 refers to
@@ -697,7 +691,7 @@ nvim_open_win({buffer}, {enter}, {config}) *nvim_open_win()*
- `height` : window height (in character cells).
Minimum of 1.
- `width` : window width (in character cells).
- Minimum of 2.
+ Minimum of 1.
- `row` : row position. Screen cell height are
used as unit. Can be floating point.
- `col` : column position. Screen cell width is
@@ -769,10 +763,28 @@ nvim_unsubscribe({event}) *nvim_unsubscribe()*
{event} Event type string
nvim_get_color_by_name({name}) *nvim_get_color_by_name()*
- TODO: Documentation
+ Returns the 24-bit RGB value of a |nvim_get_color_map()| color
+ name or "#rrggbb" hexadecimal string.
+
+ Example: >
+ :echo nvim_get_color_by_name("Pink")
+ :echo nvim_get_color_by_name("#cbcbcb")
+<
+
+ Parameters: ~
+ {name} Color name or "#rrggbb" string
+
+ Return: ~
+ 24-bit RGB value, or -1 for invalid argument.
nvim_get_color_map() *nvim_get_color_map()*
- TODO: Documentation
+ Returns a map of color names and RGB values.
+
+ Keys are color names (e.g. "Aqua") and values are 24-bit RGB
+ color values (e.g. 65535).
+
+ Return: ~
+ Map of color names and RGB values.
nvim_get_mode() *nvim_get_mode()*
Gets the current mode. |mode()| "blocking" is true if Nvim is
@@ -795,6 +807,42 @@ nvim_get_keymap({mode}) *nvim_get_keymap()*
Array of maparg()-like dictionaries describing mappings.
The "buffer" key is always zero.
+nvim_set_keymap({mode}, {lhs}, {rhs}, {opts}) *nvim_set_keymap()*
+ Sets a global |mapping| for the given mode.
+
+ To set a buffer-local mapping, use |nvim_buf_set_keymap()|.
+
+ Unlike |:map|, leading/trailing whitespace is accepted as part
+ of the {lhs} or {rhs}. Empty {rhs} is |<Nop>|. |keycodes| are
+ replaced as usual.
+
+ Example: >
+ call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true})
+<
+
+ is equivalent to: >
+ nmap <nowait> <Space><NL> <Nop>
+<
+
+ Parameters: ~
+ {mode} Mode short-name (map command prefix: "n", "i",
+ "v", "x", …) or "!" for |:map!|, or empty string
+ for |:map|.
+ {lhs} Left-hand-side |{lhs}| of the mapping.
+ {rhs} Right-hand-side |{rhs}| of the mapping.
+ {opts} Optional parameters map. Accepts all
+ |:map-arguments| as keys excluding |<buffer>| but
+ including |noremap|. Values are Booleans. Unknown
+ key is an error.
+
+nvim_del_keymap({mode}, {lhs}) *nvim_del_keymap()*
+ Unmaps a global |mapping| for the given mode.
+
+ To unmap a buffer-local mapping, use |nvim_buf_del_keymap()|.
+
+ See also: ~
+ |nvim_set_keymap()|
+
nvim_get_commands({opts}) *nvim_get_commands()*
Gets a map of global (non-buffer-local) Ex commands.
@@ -1092,11 +1140,11 @@ nvim_list_uis() *nvim_list_uis()*
Return: ~
Array of UI dictionaries, each with these keys:
- - "height" requested height of the UI
- - "width" requested width of the UI
+ - "height" Requested height of the UI
+ - "width" Requested width of the UI
- "rgb" true if the UI uses RGB colors (false implies
|cterm-colors|)
- - "ext_..." Requested UI extensions, see |ui-options|
+ - "ext_..." Requested UI extensions, see |ui-option|
- "chan" Channel id of remote UI (not present for TUI)
nvim_get_proc_children({pid}) *nvim_get_proc_children()*
@@ -1161,7 +1209,7 @@ nvim_buf_line_count({buffer}) *nvim_buf_line_count()*
Line count, or 0 for unloaded buffer. |api-buffer|
nvim_buf_attach({buffer}, {send_buffer}, {opts}) *nvim_buf_attach()*
- Activate updates from this buffer to the current channel.
+ Activates buffer-update events on the channel.
Parameters: ~
{buffer} Buffer handle, or 0 for current buffer
@@ -1180,7 +1228,7 @@ nvim_buf_attach({buffer}, {send_buffer}, {opts}) *nvim_buf_attach()*
True.
nvim_buf_detach({buffer}) *nvim_buf_detach()*
- Deactivate updates from this buffer to the current channel.
+ Deactivates buffer-update events on the channel.
Parameters: ~
{buffer} Buffer handle, or 0 for current buffer
@@ -1284,6 +1332,25 @@ nvim_buf_get_keymap({buffer}, {mode}) *nvim_buf_get_keymap()*
Array of maparg()-like dictionaries describing mappings.
The "buffer" key holds the associated buffer handle.
+ *nvim_buf_set_keymap()*
+nvim_buf_set_keymap({buffer}, {mode}, {lhs}, {rhs}, {opts})
+ Sets a buffer-local |mapping| for the given mode.
+
+ Parameters: ~
+ {buffer} Buffer handle, or 0 for current buffer
+
+ See also: ~
+ |nvim_set_keymap()|
+
+nvim_buf_del_keymap({buffer}, {mode}, {lhs}) *nvim_buf_del_keymap()*
+ Unmaps a buffer-local |mapping| for the given mode.
+
+ Parameters: ~
+ {buffer} Buffer handle, or 0 for current buffer
+
+ See also: ~
+ |nvim_del_keymap()|
+
nvim_buf_get_commands({buffer}, {opts}) *nvim_buf_get_commands()*
Gets a map of buffer-local |user-commands|.
@@ -1738,10 +1805,27 @@ nvim_tabpage_is_valid({tabpage}) *nvim_tabpage_is_valid()*
UI Functions *api-ui*
nvim_ui_attach({width}, {height}, {options}) *nvim_ui_attach()*
- TODO: Documentation
+ Activates UI events on the channel.
+
+ Entry point of all UI clients. Allows |--embed| to continue
+ startup. Implies that the client is ready to show the UI. Adds
+ the client to the list of UIs. |nvim_list_uis()|
+
+ Note:
+ If multiple UI clients are attached, the global screen
+ dimensions degrade to the smallest client. E.g. if client
+ A requests 80x40 but client B requests 200x100, the global
+ screen has size 80x40.
+
+ Parameters: ~
+ {width} Requested screen columns
+ {height} Requested screen rows
+ {options} |ui-option| map
nvim_ui_detach() *nvim_ui_detach()*
- TODO: Documentation
+ Deactivates UI events on the channel.
+
+ Removes the client from the list of UIs. |nvim_list_uis()|
nvim_ui_try_resize({width}, {height}) *nvim_ui_try_resize()*
TODO: Documentation
diff --git a/runtime/doc/debug.txt b/runtime/doc/debug.txt
index 835b35b388..7b08e61655 100644
--- a/runtime/doc/debug.txt
+++ b/runtime/doc/debug.txt
@@ -76,15 +76,10 @@ matches the EXE (same date).
If you built the executable yourself with the Microsoft Visual C++ compiler,
then the PDB was built with the EXE.
-Alternatively, if you have the source files, you can import Make_ivc.mak into
-Visual Studio as a workspace. Then select a debug configuration, build and
-you can do all kinds of debugging (set breakpoints, watch variables, etc.).
-
If you have Visual Studio, use that instead of the VC Toolkit and WinDbg.
-For other compilers, you should always use the corresponding debugger: TD for
-a Vim executable compiled with the Borland compiler; gdb (see above
-|debug-gcc|) for the Cygwin and MinGW compilers.
+For other compilers, you should always use the corresponding debugger: gdb
+(see above |debug-gcc|) for the Cygwin and MinGW compilers.
*debug-vs2005*
@@ -112,7 +107,7 @@ line numbers. Double-click one of the lines and the Find Source dialog will
appear. Navigate to the directory where the Vim source is (if you have it.)
If you don't know how to debug this any further, follow the instructions
-at ":help bug-reports". Paste the call stack into the bug report.
+at ":help bug-report". Paste the call stack into the bug report.
If you have a non-free version of Visual Studio, you can save a minidump via
the Debug menu and send it with the bug report. A minidump is a small file
diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt
index 9e74efabe0..b56240353f 100644
--- a/runtime/doc/deprecated.txt
+++ b/runtime/doc/deprecated.txt
@@ -12,6 +12,9 @@ updated.
==============================================================================
+API ~
+*nvim_buf_clear_highlight()* Use |nvim_buf_clear_namespace()| instead.
+
Commands ~
*:rv*
*:rviminfo* Deprecated alias to |:rshada| command.
@@ -65,10 +68,8 @@ Options ~
*'viminfofile'* Deprecated alias to 'shadafile' option.
UI extensions~
-*ui-wildmenu* Use `ext_cmdline` and `ext_popupmenu` instead.
- Enabled by `ext_wildmenu` |ui-options|.
- If `ext_wildmenu` is set, these events are emitted for
- backwards-compatibility:
+*ui-wildmenu* Use |ui-cmdline| with |ui-popupmenu| instead. Enabled
+ by the `ext_wildmenu` |ui-option|. Emits these events:
["wildmenu_show", items]
["wildmenu_select", selected]
["wildmenu_hide"]
diff --git a/runtime/doc/develop.txt b/runtime/doc/develop.txt
index a6ba7ee9c5..843e23ee54 100644
--- a/runtime/doc/develop.txt
+++ b/runtime/doc/develop.txt
@@ -157,28 +157,32 @@ with a {thing} that groups functions under a common concept).
Use existing common {action} names if possible:
add Append to, or insert into, a collection
- get Get a thing (or subset of things by some query)
- set Set a thing
+ get Get a thing (or group of things by query)
+ set Set a thing (or group of things)
del Delete a thing (or group of things)
list Get all things
Use consistent names for {thing} in all API functions. E.g. a buffer is called
"buf" everywhere, not "buffer" in some places and "buf" in others.
-Example: `nvim_get_current_line` acts on the global editor state; the common
-{action} "get" is used but {thing} is omitted.
+Example:
+ `nvim_get_current_line` acts on the global editor state; the common
+ {action} "get" is used but {thing} is omitted.
-Example: `nvim_buf_add_highlight` acts on a `Buffer` object (the first
-parameter) and uses the common {action} "add".
+Example:
+ `nvim_buf_add_highlight` acts on a `Buffer` object (the first parameter)
+ and uses the common {action} "add".
-Example: `nvim_list_bufs` operates in a global context (first parameter is
-_not_ a Buffer). The common {action} "list" indicates that it lists all
-bufs (plural) in the global context.
+Example:
+ `nvim_list_bufs` operates in a global context (first parameter is not
+ a Buffer). The common {action} "list" indicates that it lists all bufs
+ (plural) in the global context.
Use this template to name new API events:
nvim_{thing}_{event}_event
-Example: `nvim_buf_changedtick_event`.
+Example:
+ `nvim_buf_changedtick_event`
API-CLIENT *dev-api-client*
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index d400df6b7d..8eca463c7f 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -1502,7 +1502,8 @@ v:dying Normally zero. When a deadly signal is caught it's set to
v:exiting Exit code, or |v:null| if not exiting. |VimLeave|
*v:errmsg* *errmsg-variable*
-v:errmsg Last given error message. It's allowed to set this variable.
+v:errmsg Last given error message.
+ Modifiable (can be set).
Example: >
:let v:errmsg = ""
:silent! next
@@ -1827,7 +1828,8 @@ v:shell_error Result of the last shell command. When non-zero, the last
:endif
<
*v:statusmsg* *statusmsg-variable*
-v:statusmsg Last given status message. It's allowed to set this variable.
+v:statusmsg Last given status message.
+ Modifiable (can be set).
*v:stderr* *stderr-variable*
v:stderr |channel-id| corresponding to stderr. The value is always 2;
@@ -1893,9 +1895,9 @@ v:termresponse The escape sequence returned by the terminal for the DA
v:testing Must be set before using `test_garbagecollect_now()`.
*v:this_session* *this_session-variable*
-v:this_session Full filename of the last loaded or saved session file. See
- |:mksession|. It is allowed to set this variable. When no
- session file has been saved, this variable is empty.
+v:this_session Full filename of the last loaded or saved session file.
+ Empty when no session file has been saved. See |:mksession|.
+ Modifiable (can be set).
*v:throwpoint* *throwpoint-variable*
v:throwpoint The point where the exception most recently caught and not
@@ -1922,22 +1924,20 @@ v:val Value of the current item of a |List| or |Dictionary|. Only
|filter()|. Read-only.
*v:version* *version-variable*
-v:version Version number of Vim: Major version number times 100 plus
- minor version number. Version 5.0 is 500. Version 5.1 (5.01)
- is 501. Read-only. "version" also works, for backwards
- compatibility.
- Use |has()| to check if a certain patch was included, e.g.: >
- if has("patch-7.4.123")
-< Note that patch numbers are specific to the version, thus both
- version 5.0 and 5.1 may have a patch 123, but these are
- completely different.
+v:version Vim version number: major version times 100 plus minor
+ version. Vim 5.0 is 500, Vim 5.1 is 501.
+ Read-only.
+ Use |has()| to check the Nvim (not Vim) version: >
+ :if has("nvim-0.2.1")
+<
*v:vim_did_enter* *vim_did_enter-variable*
-v:vim_did_enter Zero until most of startup is done. It is set to one just
- before |VimEnter| autocommands are triggered.
+v:vim_did_enter 0 during startup, 1 just before |VimEnter|.
+ Read-only.
*v:warningmsg* *warningmsg-variable*
-v:warningmsg Last given warning message. It's allowed to set this variable.
+v:warningmsg Last given warning message.
+ Modifiable (can be set).
*v:windowid* *windowid-variable*
v:windowid Application-specific window "handle" which may be set by any
@@ -1966,6 +1966,7 @@ argidx() Number current index in the argument list
arglistid([{winnr} [, {tabnr}]]) Number argument list id
argv({nr} [, {winid}]) String {nr} entry of the argument list
argv([-1, {winid}]) List the argument list
+assert_beeps({cmd}) none assert {cmd} causes a beep
assert_equal({exp}, {act} [, {msg}])
none assert {exp} is equal to {act}
assert_exception({error} [, {msg}])
@@ -2111,10 +2112,11 @@ gettabvar({nr}, {varname} [, {def}])
any variable {varname} in tab {nr} or {def}
gettabwinvar({tabnr}, {winnr}, {name} [, {def}])
any {name} in {winnr} in tab page {tabnr}
+gettagstack([{nr}]) Dict get the tag stack of window {nr}
getwininfo([{winid}]) List list of windows
getwinpos([{timeout}]) List X and Y coord in pixels of the Vim window
-getwinposx() Number X coord in pixels of GUI Vim window
-getwinposy() Number Y coord in pixels of GUI Vim window
+getwinposx() Number X coord in pixels of Vim window
+getwinposy() Number Y coord in pixels of Vim window
getwinvar({nr}, {varname} [, {def}])
any variable {varname} in window {nr}
glob({expr} [, {nosuf} [, {list} [, {alllinks}]]])
@@ -2276,6 +2278,8 @@ setreg({n}, {v}[, {opt}]) Number set register to value and type
settabvar({nr}, {varname}, {val}) set {varname} in tab page {nr} to {val}
settabwinvar({tabnr}, {winnr}, {varname}, {val}) set {varname} in window
{winnr} in tab page {tabnr} to {val}
+settagstack({nr}, {dict} [, {action}])
+ Number modify tag stack using {dict}
setwinvar({nr}, {varname}, {val}) set {varname} in window {nr} to {val}
sha256({string}) String SHA256 checksum of {string}
shellescape({string} [, {special}])
@@ -2320,6 +2324,8 @@ submatch({nr} [, {list}]) String or List
specific match in ":s" or substitute()
substitute({expr}, {pat}, {sub}, {flags})
String all {pat} in {expr} replaced with {sub}
+swapinfo({fname}) Dict information about swap file {fname}
+swapname({expr}) String swap file of buffer {expr}
synID({lnum}, {col}, {trans}) Number syntax ID at {lnum} and {col}
synIDattr({synID}, {what} [, {mode}])
String attribute {what} of syntax ID {synID}
@@ -2479,6 +2485,11 @@ argv([{nr} [, {winid}])
The {winid} argument specifies the window ID, see |argc()|.
+assert_beeps({cmd}) *assert_beeps()*
+ Run {cmd} and add an error message to |v:errors| if it does
+ NOT produce a beep or visual bell.
+ Also see |assert_fails()|.
+
*assert_equal()*
assert_equal({expected}, {actual}, [, {msg}])
When {expected} and {actual} are not equal an error message is
@@ -2511,6 +2522,8 @@ assert_fails({cmd} [, {error} [, {msg}]]) *assert_fails()*
Run {cmd} and add an error message to |v:errors| if it does
NOT produce an error.
When {error} is given it must match in |v:errmsg|.
+ Note that beeping is not considered an error, and some failing
+ commands only beep. Use |assert_beeps()| for those.
assert_false({actual} [, {msg}]) *assert_false()*
When {actual} is not false an error message is added to
@@ -4542,6 +4555,41 @@ gettabwinvar({tabnr}, {winnr}, {varname} [, {def}]) *gettabwinvar()*
To obtain all window-local variables use: >
gettabwinvar({tabnr}, {winnr}, '&')
+gettagstack([{nr}]) *gettagstack()*
+ The result is a Dict, which is the tag stack of window {nr}.
+ {nr} can be the window number or the |window-ID|.
+ When {nr} is not specified, the current window is used.
+ When window {nr} doesn't exist, an empty Dict is returned.
+
+ The returned dictionary contains the following entries:
+ curidx Current index in the stack. When at
+ top of the stack, set to (length + 1).
+ Index of bottom of the stack is 1.
+ items List of items in the stack. Each item
+ is a dictionary containing the
+ entries described below.
+ length Number of entries in the stack.
+
+ Each item in the stack is a dictionary with the following
+ entries:
+ bufnr buffer number of the current jump
+ from cursor position before the tag jump.
+ See |getpos()| for the format of the
+ returned list.
+ matchnr current matching tag number. Used when
+ multiple matching tags are found for a
+ name.
+ tagname name of the tag
+
+ See |tagstack| for more information about the tag stack.
+
+getwinpos([{timeout}]) *getwinpos()*
+ The result is a list with two numbers, the result of
+ getwinposx() and getwinposy() combined:
+ [x-pos, y-pos]
+ {timeout} can be used to specify how long to wait in msec for
+ a response from the terminal. When omitted 100 msec is used.
+
*getwinposx()*
getwinposx() The result is a Number, which is the X coordinate in pixels of
the left hand side of the GUI Vim window. The result will be
@@ -4674,8 +4722,8 @@ has({feature}) Returns 1 if {feature} is supported, 0 otherwise. The
{feature} argument is a feature name like "nvim-0.2.1" or
"win32", see below. See also |exists()|.
- Vim's compile-time feature names (prefixed with "+") are not
- supported because Nvim is always compiled with ALL possible
+ Vim's compile-time feature-names (prefixed with "+") are not
+ recognized because Nvim is always compiled with all possible
features. |feature-compile|
Feature names can be:
@@ -4704,14 +4752,12 @@ has({feature}) Returns 1 if {feature} is supported, 0 otherwise. The
wsl WSL (Windows Subsystem for Linux) system
*has-patch*
- 3. Vim patches. The "patch123" feature means that Vim patch
- 123 has been included. This does not check the Vim
- version, you could check |v:version| for that.
- Example: >
+ 3. Vim patch. For example the "patch123" feature means that
+ Vim patch 123 at the current |v:version| was included: >
:if v:version > 602 || v:version == 602 && has("patch148")
-< 5. Vim version. For example the "patch-7.4.237" feature means
- that the Vim version is 7.4.237 or later. >
+< 4. Vim version. For example the "patch-7.4.237" feature means
+ that Nvim is Vim-compatible to version 7.4.237 or later. >
:if has("patch-7.4.237")
@@ -7018,6 +7064,8 @@ setqflist({list} [, {action}[, {what}]]) *setqflist()*
buffer
filename name of a file; only used when "bufnr" is not
present or it is invalid.
+ module name of a module; if given it will be used in
+ quickfix error window instead of the filename
lnum line number in the file
pattern search pattern used to locate the error
col column number
@@ -7062,9 +7110,6 @@ setqflist({list} [, {action}[, {what}]]) *setqflist()*
freed. To add a new quickfix list at the end of the stack,
set "nr" in {what} to "$".
- If {title} is given, it will be used to set |w:quickfix_title|
- after opening the quickfix window.
-
If the optional {what} dictionary argument is supplied, then
only the items listed in {what} are set. The first {list}
argument is ignored. The following items can be specified in
@@ -7175,6 +7220,38 @@ settabwinvar({tabnr}, {winnr}, {varname}, {val}) *settabwinvar()*
:call settabwinvar(3, 2, "myvar", "foobar")
< This function is not available in the |sandbox|.
+settagstack({nr}, {dict} [, {action}]) *settagstack()*
+ Modify the tag stack of the window {nr} using {dict}.
+ {nr} can be the window number or the |window-ID|.
+
+ For a list of supported items in {dict}, refer to
+ |gettagstack()|
+ *E962*
+ If {action} is not present or is set to 'r', then the tag
+ stack is replaced. If {action} is set to 'a', then new entries
+ from {dict} are pushed onto the tag stack.
+
+ Returns zero for success, -1 for failure.
+
+ Examples:
+ Set current index of the tag stack to 4: >
+ call settagstack(1005, {'curidx' : 4})
+
+< Empty the tag stack of window 3: >
+ call settagstack(3, {'items' : []})
+
+< Push a new item onto the tag stack: >
+ let pos = [bufnr('myfile.txt'), 10, 1, 0]
+ let newtag = [{'tagname' : 'mytag', 'from' : pos}]
+ call settagstack(2, {'items' : newtag}, 'a')
+
+< Save and restore the tag stack: >
+ let stack = gettagstack(1003)
+ " do something else
+ call settagstack(1003, stack)
+ unlet stack
+<
+
setwinvar({nr}, {varname}, {val}) *setwinvar()*
Like |settabwinvar()| for the current tab page.
Examples: >
@@ -7745,6 +7822,31 @@ substitute({expr}, {pat}, {sub}, {flags}) *substitute()*
|submatch()| returns. Example: >
:echo substitute(s, '\(\x\x\)', {m -> '0x' . m[1]}, 'g')
+swapinfo({fname}) *swapinfo()*
+ The result is a dictionary, which holds information about the
+ swapfile {fname}. The available fields are:
+ version VIM version
+ user user name
+ host host name
+ fname original file name
+ pid PID of the VIM process that created the swap
+ file
+ mtime last modification time in seconds
+ inode Optional: INODE number of the file
+ dirty 1 if file was modified, 0 if not
+ In case of failure an "error" item is added with the reason:
+ Cannot open file: file not found or in accessible
+ Cannot read file: cannot read first block
+ Not a swap file: does not contain correct block ID
+ Magic number mismatch: Info in first block is invalid
+
+swapname({expr}) *swapname()*
+ The result is the swap file path of the buffer {expr}.
+ For the use of {expr}, see |bufname()| above.
+ If buffer {expr} is the current buffer, the result is equal to
+ |:swapname| (unless no swap file).
+ If buffer {expr} has no swap file, returns an empty string.
+
synID({lnum}, {col}, {trans}) *synID()*
The result is a Number, which is the syntax ID at the position
{lnum} and {col} in the current window.
@@ -7867,7 +7969,7 @@ system({cmd} [, {input}]) *system()* *E677*
items converted to NULs).
When {input} is given and is a valid buffer id, the content of
the buffer is written to the file line by line, each line
- terminated by a NL (and NUL where the text has NL).
+ terminated by NL (and NUL where the text has NL).
*E5677*
Note: system() cannot write to or read from backgrounded ("&")
shell commands, e.g.: >
@@ -7884,7 +7986,7 @@ system({cmd} [, {input}]) *system()* *E677*
The characters in 'shellquote' and 'shellxquote' may also
cause trouble.
- The result is a String. Example: >
+ Result is a String. Example: >
:let files = system("ls " . shellescape(expand('%:h')))
:let files = system('ls ' . expand('%:h:S'))
diff --git a/runtime/doc/intro.txt b/runtime/doc/intro.txt
index 1d2cca3073..1fb06e169c 100644
--- a/runtime/doc/intro.txt
+++ b/runtime/doc/intro.txt
@@ -58,7 +58,7 @@ For more information try one of these:
==============================================================================
Nvim on the interwebs *internet*
- *www* *WWW* *faq* *FAQ* *distribution* *download*
+ *www* *faq* *distribution* *download*
Nvim home page: https://neovim.io/
Nvim FAQ: https://github.com/neovim/neovim/wiki/FAQ
@@ -67,9 +67,10 @@ Nvim on the interwebs *internet*
Vim home page: https://www.vim.org/
-Bug reports: *bugs* *bug-reports* *bugreport.vim*
+ *bugs* *bug-report* *bugreport.vim* *feature-request*
-Report bugs on GitHub: https://github.com/neovim/neovim/issues
+Report bugs and request features here:
+https://github.com/neovim/neovim/issues
Be brief, yet complete. Always give a reproducible example and try to find
out which settings or other things trigger the bug.
diff --git a/runtime/doc/msgpack_rpc.txt b/runtime/doc/msgpack_rpc.txt
index 862aa7b750..91a4a3c267 100644
--- a/runtime/doc/msgpack_rpc.txt
+++ b/runtime/doc/msgpack_rpc.txt
@@ -114,17 +114,18 @@ You can also embed an Nvim instance via |jobstart()|, and communicate using
==============================================================================
4. Implementing API clients *rpc-api-client* *api-client*
-"API clients" wrap the Nvim API to provide idiomatic "SDKs" for their
-respective platforms (see |dev-jargon|). You can build a new API client for
-your favorite platform or programming language.
+API clients wrap the Nvim API to provide idiomatic "SDKs" for their respective
+platforms (see |jargon|). You can build a new API client for your favorite
+platform or programming language.
-Existing API clients are listed here:
+API clients are listed here:
https://github.com/neovim/neovim/wiki/Related-projects#api-clients
+ *pynvim*
The Python client is the reference implementation for API clients. It is
always up-to-date with the Nvim API, so its source code and test suite are
authoritative references.
- https://github.com/neovim/python-client
+ https://github.com/neovim/pynvim
API client implementation guidelines ~
diff --git a/runtime/doc/nvim_terminal_emulator.txt b/runtime/doc/nvim_terminal_emulator.txt
index af94c60629..cfaec12520 100644
--- a/runtime/doc/nvim_terminal_emulator.txt
+++ b/runtime/doc/nvim_terminal_emulator.txt
@@ -7,8 +7,8 @@
Terminal emulator *terminal* *terminal-emulator*
Nvim embeds a VT220/xterm terminal emulator based on libvterm. The terminal is
-presented as a special buffer type, asynchronously updated from the virtual
-terminal as data is received from the program connected to it.
+presented as a special 'buftype', asynchronously updated as data is received
+from the connected program.
Terminal buffers behave like normal buffers, except:
- With 'modifiable', lines can be edited but not deleted.
@@ -23,11 +23,11 @@ Terminal buffers behave like normal buffers, except:
==============================================================================
Start *terminal-start*
-There are 3 ways to create a terminal buffer:
+There are several ways to create a terminal buffer:
-- By invoking the |:terminal| ex command.
-- By calling the |termopen()| function.
-- By editing a file with a name matching `term://(.{-}//(\d+:)?)?\zs.*`.
+- Invoke the |:terminal| command.
+- Call the |termopen()| function.
+- Edit a file with a name matching `term://(.{-}//(\d+:)?)?\zs.*`.
For example:
>
:edit term://bash
@@ -98,14 +98,21 @@ global configuration.
- 'wrap' is disabled
You can change the defaults with a TermOpen autocommand: >
- au TermOpen * setlocal list
+ au TermOpen * setlocal list
TERMINAL COLORS ~
-The `{g,b}:terminal_color_$NUM` variables control the terminal color palette,
-where `$NUM` is the color index between 0 and 255 inclusive. This setting only
-affects UIs with RGB capabilities; for normal terminals the color index is
-just forwarded. The variables are read only during |TermOpen|.
+The `{g,b}:terminal_color_x` variables control the terminal color palette,
+where `x` is the color index between 0 and 255 inclusive. The variables are
+read during |TermOpen|. The value must be a color name or hexadecimal string.
+Example: >
+ let g:terminal_color_4 = '#ff0000'
+ let g:terminal_color_5 = 'green'
+Only works for RGB UIs (see 'termguicolors'); for 256-color terminals the
+color index is just forwarded.
+
+Editor highlighting (|syntax-highlighting|, |highlight-groups|, etc.) has
+higher precedence: it is applied after terminal colors are resolved.
==============================================================================
Status Variables *terminal-status*
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 171fff8547..cce9f681f9 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -4528,10 +4528,14 @@ A jump table for the options with a short description can be found at |Q_op|.
'redrawtime' 'rdt' number (default 2000)
global
Time in milliseconds for redrawing the display. Applies to
- 'hlsearch', 'inccommand' and |:match| highlighting.
+ 'hlsearch', 'inccommand', |:match| highlighting and syntax
+ highlighting.
When redrawing takes more than this many milliseconds no further
- matches will be highlighted. This is used to avoid that Vim hangs
- when using a very complicated pattern.
+ matches will be highlighted.
+ For syntax highlighting the time applies per window. When over the
+ limit syntax highlighting is disabled until |CTRL-L| is used.
+ This is used to avoid that Vim hangs when using a very complicated
+ pattern.
*'regexpengine'* *'re'*
'regexpengine' 're' number (default 0)
@@ -4659,12 +4663,11 @@ A jump table for the options with a short description can be found at |Q_op|.
:set rulerformat=%15(%c%V\ %p%%%)
<
*'runtimepath'* *'rtp'* *vimfiles*
-'runtimepath' 'rtp' string (default:
- Unix: "$XDG_CONFIG_HOME/nvim,
+'runtimepath' 'rtp' string (default: "$XDG_CONFIG_HOME/nvim,
$XDG_CONFIG_DIRS[1]/nvim,
$XDG_CONFIG_DIRS[2]/nvim,
- $XDG_DATA_HOME/nvim/site,
+ $XDG_DATA_HOME/nvim[-data]/site,
$XDG_DATA_DIRS[1]/nvim/site,
$XDG_DATA_DIRS[2]/nvim/site,
@@ -4672,14 +4675,13 @@ A jump table for the options with a short description can be found at |Q_op|.
$XDG_DATA_DIRS[2]/nvim/site/after,
$XDG_DATA_DIRS[1]/nvim/site/after,
- $XDG_DATA_HOME/nvim/site/after,
+ $XDG_DATA_HOME/nvim[-data]/site/after,
$XDG_CONFIG_DIRS[2]/nvim/after,
$XDG_CONFIG_DIRS[1]/nvim/after,
$XDG_CONFIG_HOME/nvim/after")
global
- This is a list of directories which will be searched for runtime
- files:
+ List of directories to be searched for these runtime files:
filetype.vim filetypes by file name |new-filetype|
scripts.vim filetypes by file contents |new-filetype-scripts|
autoload/ automatically loaded scripts |autoload-functions|
@@ -4701,19 +4703,20 @@ A jump table for the options with a short description can be found at |Q_op|.
And any other file searched for with the |:runtime| command.
- The defaults for most systems are setup to search these locations:
- 1. In your home directory (XDG_CONFIG_HOME defaults to $HOME/.config),
- for your personal preferences.
- 2. In directories which must contain configuration files according to
- XDG ($XDG_CONFIG_DIRS). This also contains preferences from system
- administrator (XDG_CONFIG_DIRS defaults to /etc/xdg).
- 3. In data home directory, for plugins installed by user.
- 4. In nvim/site subdirectories for each directory in $XDG_DATA_DIRS.
- This is for plugins which were installed by system administrator,
- but are not part of the Neovim distribution. XDG_DATA_DIRS defaults
- to /usr/local/share/:/usr/share/, so system administrators are
+ Defaults are setup to search these locations:
+ 1. Your home directory, for personal preferences.
+ Given by `stdpath("config")`. |$XDG_CONFIG_HOME|
+ 2. Directories which must contain configuration files according to
+ |xdg| ($XDG_CONFIG_DIRS, defaults to /etc/xdg). This also contains
+ preferences from system administrator.
+ 3. Data home directory, for plugins installed by user.
+ Given by `stdpath("data")`. |$XDG_DATA_HOME|
+ 4. nvim/site subdirectories for each directory in $XDG_DATA_DIRS.
+ This is for plugins which were installed by system administrator,
+ but are not part of the Nvim distribution. XDG_DATA_DIRS defaults
+ to /usr/local/share/:/usr/share/, so system administrators are
expected to install site plugins to /usr/share/nvim/site.
- 5. In $VIMRUNTIME, for files distributed with Neovim.
+ 5. $VIMRUNTIME, for files distributed with Neovim.
*after-directory*
6, 7, 8, 9. In after/ subdirectories of 1, 2, 3 and 4, with reverse
ordering. This is for preferences to overrule or add to the
@@ -5045,7 +5048,7 @@ A jump table for the options with a short description can be found at |Q_op|.
security reasons.
*'shadafile'* *'sdf'*
-'shadafile' 'vif' string (default: "")
+'shadafile' 'sdf' string (default: "")
global
When non-empty, overrides the file name used for |shada| (viminfo).
When equal to "NONE" no shada file will be read or written.
@@ -6128,9 +6131,9 @@ A jump table for the options with a short description can be found at |Q_op|.
*'termguicolors'* *'tgc'*
'termguicolors' 'tgc' boolean (default off)
global
- When on, uses |highlight-guifg| and |highlight-guibg| attributes in
- the terminal (thus using 24-bit color). Requires a ISO-8613-3
- compatible terminal.
+ Enables 24-bit RGB color in the |TUI|. Uses "gui" |:highlight|
+ attributes instead of "cterm" attributes. |highlight-guifg|
+ Requires an ISO-8613-3 compatible terminal.
*'terse'* *'noterse'*
'terse' boolean (default off)
@@ -6159,9 +6162,7 @@ A jump table for the options with a short description can be found at |Q_op|.
the file should contain words with similar meaning, separated by
non-keyword characters (white space is preferred). Maximum line
length is 510 bytes.
- To obtain a file to be used here, check out this ftp site:
- [Sorry this link doesn't work anymore, do you know the right one?]
- ftp://ftp.ox.ac.uk/pub/wordlists/ First get the README file.
+
To include a comma in a file name precede it with a backslash. Spaces
after a comma are ignored, otherwise spaces are included in the file
name. See |option-backslash| about using backslashes.
diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt
index 7aa81f612b..d20a91dc2d 100644
--- a/runtime/doc/quickfix.txt
+++ b/runtime/doc/quickfix.txt
@@ -1204,6 +1204,7 @@ you want to match case, add "\C" to the pattern |/\C|.
Basic items
%f file name (finds a string)
+ %o module name (finds a string)
%l line number (finds a number)
%c column number (finds a number representing character
column of the error, (1 <tab> == 1 character column))
@@ -1248,6 +1249,11 @@ conversion can be used to locate lines without a line number in the error
output. Like the output of the "grep" shell command.
When the pattern is present the line number will not be used.
+The "%o" conversion specifies the module name in quickfix entry. If present
+it will be used in quickfix error window instead of the filename. The module
+name is used only for displaying purposes, the file name is used when jumping
+to the file.
+
Changing directory
The following uppercase conversion characters specify the type of special
diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt
index 23ae3458ea..0a552a1309 100644
--- a/runtime/doc/repeat.txt
+++ b/runtime/doc/repeat.txt
@@ -432,6 +432,16 @@ flag when defining the function, it is not relevant when executing it. >
.
:endfunction
:set cpo-=C
+<
+ *line-continuation-comment*
+To add a comment in between the lines start with '\" '. Notice the space
+after the double quote. Example: >
+ let array = [
+ "\ first entry comment
+ \ 'first',
+ "\ second entry comment
+ \ 'second',
+ \ ]
Rationale:
Most programs work with a trailing backslash to indicate line
@@ -440,6 +450,14 @@ Rationale:
:map xx asdf\
< Therefore the unusual leading backslash is used.
+ Starting a comment in a continuation line results in all following
+ continuation lines to be part of the comment. Since it was like this
+ for a long time, when making it possible to add a comment halfway a
+ sequence of continuation lines, it was not possible to use \", since
+ that was a valid continuation line. Using '"\ ' comes closest, even
+ though it may look a bit weird. Requiring the space after the
+ backslash is to make it very unlikely this is a normal comment line.
+
==============================================================================
5. Using Vim packages *packages*
diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt
index a8ba64fda8..2a230d9449 100644
--- a/runtime/doc/starting.txt
+++ b/runtime/doc/starting.txt
@@ -195,34 +195,33 @@ argument.
-E Start Nvim in Ex mode |gQ|.
If stdin is not a TTY:
- -e reads stdin as Ex commands.
+ -e reads/executes stdin as Ex commands.
-E reads stdin as text (into buffer 1).
- *-es* *-Es*
--es *-s-ex* *silent-mode*
--Es Silent or batch mode: execute Ex commands from a file instead
- of a terminal. Special case of |-s| (which takes an argument
- while "-es" doesn't). Disables most prompts, messages,
- warnings and errors.
- Output of these commands is displayed (to stdout):
+-es *-es* *-Es* *-s-ex* *silent-mode*
+-Es Silent or batch mode. Special case of |-s| (which takes an
+ argument while "-es" doesn't). Disables most prompts,
+ messages, warnings and errors.
+
+ -es reads/executes stdin as Ex commands. >
+ printf "put ='foo'\n%%print\n" | nvim -es
+
+< -Es reads stdin as text (into buffer 1). Use |-c| or "+" to
+ send commands. >
+ printf "foo\n" | nvim -Es +"%print"
+
+< Output of these commands is displayed (to stdout):
:print
:list
:number
- :set to display option values.
- When 'verbose' is set messages are printed to stderr, e.g.: >
+ :set (to display option values)
+ When 'verbose' is set messages are printed to stderr. >
echo foo | nvim -V1 -es
-<
- User |init.vim| is skipped (unless given with |-u|).
- Swap file is skipped (like |-n|).
- |$TERM| is not used.
- If stdin is not a TTY:
- -es reads stdin as Ex commands.
- -Es reads stdin as text (into buffer 1).
+< User |init.vim| is skipped (unless given with |-u|).
+ Swap file is skipped (like |-n|).
+ User |shada| is loaded (unless "-i NONE" is given).
- Example: >
- printf "put ='foo'\n%%print\n" | nvim -es
-<
*-b*
-b Binary mode. File I/O will only recognize <NL> to separate
lines. The 'expandtab' option will be reset. The 'textwidth'
@@ -355,35 +354,33 @@ argument.
--embed Use stdin/stdout as a msgpack-RPC channel, so applications can
embed and control Nvim via the |rpc-api|.
- By default nvim will wait for the embedding process to call
- `nvim_ui_attach` before sourcing startup files and reading
- buffers. This is so that UI can show startup messages and
- possible swap file dialog for the first loaded file. The
- process can do requests before the `nvim_ui_attach`, for
- instance a `nvim_get_api_info` call so that UI features can be
- safely detected by the UI before attaching.
-
- See |ui-startup| for more information about UI startup.
-
- To embed nvim without using the UI protocol, `--headless` should
- be supplied together with `--embed`. Then initialization is
- performed without waiting for an UI. This is also equivalent
- to the following alternative: >
- nvim --headless --cmd "call stdioopen({'rpc': v:true})"
-<
- See also |channel-stdio|.
+ Waits for the client ("embedder") to call |nvim_ui_attach()|
+ before sourcing startup files and reading buffers, so that UIs
+ can deterministically handle (display) early messages,
+ dialogs, etc. The client can do other requests before
+ `nvim_ui_attach` (e.g. `nvim_get_api_info` for feature-detection).
+ During this pre-startup phase the user config is of course not
+ available (similar to `--cmd`).
+
+ Embedders _not_ using the UI protocol must pass |--headless|: >
+ nvim --embed --headless
+
+< Then startup will continue without waiting for `nvim_ui_attach`.
+ This is equivalent to: >
+ nvim --headless --cmd "call stdioopen({'rpc': v:true})"
+
+< See also: |ui-startup| |channel-stdio|
*--headless*
---headless Start nvim without an UI. The TUI is not used, so stdio
- can be used as an arbitrary communication channel.
- |channel-stdio| When used together with `--embed`, do not wait
- for the embedder to attach an UI.
+--headless Start without UI, and do not wait for `nvim_ui_attach`. The
+ builtin TUI is not used, so stdio works as an arbitrary
+ communication channel. |channel-stdio|
Also useful for scripting (tests) to see messages that would
not be printed by |-es|.
To detect if a UI is available, check if |nvim_list_uis()| is
- empty in or after |VimEnter|.
+ empty during or after |VimEnter|.
To read stdin as text, "-" must be given explicitly:
--headless cannot assume that stdin is just text. >
@@ -1084,15 +1081,16 @@ even if other entries (with known name/type/etc) are merged. |shada-merging|
SHADA FILE NAME *shada-file-name*
-- The default name of the ShaDa file is "$XDG_DATA_HOME/nvim/shada/main.shada"
- for Unix. Default for $XDG_DATA_HOME is ~/.local/share. |base-directories|
-- The 'n' flag in the 'shada' option can be used to specify another ShaDa
- file name |'shada'|.
-- The "-i" Vim argument can be used to set another file name, |-i|. When the
- file name given is "NONE" (all uppercase), no ShaDa file is ever read or
- written. Also not for the commands below!
-- The 'viminfofile' option can be used like the "-i" argument. In fact, the
- value form the "-i" argument is stored in the 'viminfofile' option.
+- Default name of the |shada| file is:
+ Unix: "$XDG_DATA_HOME/nvim/shada/main.shada"
+ Windows: "$XDG_DATA_HOME/nvim-data/shada/main.shada"
+ See also |base-directories|.
+- To choose a different file name you can use:
+ - The "n" flag in the 'shada' option.
+ - The |-i| startup argument. "NONE" means no shada file is ever read or
+ written. Also not for the commands below!
+ - The 'shadafile' option. The value from the "-i" argument (if any) is
+ stored in the 'shadafile' option.
- For the commands below, another file name can be given, overriding the
default and the name given with 'shada' or "-i" (unless it's NONE).
diff --git a/runtime/doc/tagsrch.txt b/runtime/doc/tagsrch.txt
index 08abcf2594..a4526a7f2c 100644
--- a/runtime/doc/tagsrch.txt
+++ b/runtime/doc/tagsrch.txt
@@ -173,6 +173,9 @@ commands explained above the tag stack will look like this:
1 1 main 1 harddisk2:text/vim/test
2 1 FuncB 59 harddisk2:text/vim/src/main.c
+The gettagstack() function returns the tag stack of a specified window. The
+settagstack() function modifies the tag stack of a window.
+
*E73*
When you try to use the tag stack while it doesn't contain anything you will
get an error message.
diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt
index 98476ea8fa..60d55bda61 100644
--- a/runtime/doc/ui.txt
+++ b/runtime/doc/ui.txt
@@ -11,70 +11,98 @@ Nvim UI protocol *ui*
==============================================================================
UI Events *ui-events*
-GUIs can be implemented as external processes communicating with Nvim over the
-RPC API. The default UI model consists of a terminal-like grid with a single,
-monospace font size. The UI can opt-in to have windows drawn on separate
-grids, as well as to have some elements (UI "widgets") be drawn by the UI
-itself rather than by nvim ("externalized").
-
-
- *ui-options*
-The |nvim_ui_attach()| API method is used to tell Nvim that your program wants to
-draw the Nvim screen grid with a size of width × height cells. This is typically
-done by an embedder, see |ui-startup| below for details, but an UI can also
-connect to a running nvim instance and invoke this method. `options` must be
-a dictionary with these (optional) keys:
- `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|
+UIs can be implemented as external client processes communicating with Nvim
+over the RPC API. The default UI model is a terminal-like grid with a single,
+monospace font. The UI can opt-in to have windows drawn on separate grids, and
+have some elements ("widgets") presented by the UI itself rather than by Nvim
+("externalized").
+
+ *ui-option*
+Call |nvim_ui_attach()| to tell Nvim that your program wants to draw the Nvim
+screen grid with a size of width × height cells. This is typically done by an
+embedder at startup (see |ui-startup|), but UIs can also connect to a running
+Nvim instance and invoke nvim_ui_attach(). The `options` parameter is a map
+with these (optional) keys:
+ *ui-rgb*
+ `rgb` Decides the color format.
+ true: (default) 24-bit RGB colors
+ false: Terminal colors (8-bit, max 256)
+ *ui-override*
+ `override` Decides how UI capabilities are resolved.
+ true: Enable requested UI capabilities, even
+ if not supported by all connected UIs
+ (including |TUI|).
+ false: (default) Disable UI capabilities not
+ supported by all connected UIs
+ (including TUI).
+ *ui-ext-options*
`ext_cmdline` Externalize the cmdline. |ui-cmdline|
- `ext_wildmenu` Externalize the wildmenu (deprecated). |ui-wildmenu|
+ `ext_hlstate` Detailed highlight state. |ui-hlstate|
+ Sets `ext_linegrid` implicitly.
+ `ext_linegrid` Line-based grid events. |ui-linegrid|
+ Deactivates |ui-grid-old| implicitly.
`ext_messages` Externalize messages. |ui-messages|
- `ext_linegrid` Use new revision of the grid events. |ui-linegrid|
- `ext_multigrid` Use per-window grid based events. |ui-multigrid|
- `ext_hlstate` Use detailed highlight state. |ui-hlstate|
+ Sets `ext_linegrid` and `ext_cmdline` implicitly.
+ `ext_multigrid` Per-window grid events. |ui-multigrid|
+ Sets `ext_linegrid` implicitly.
+ `ext_popupmenu` Externalize |popupmenu-completion| and
+ 'wildmenu'. |ui-popupmenu|
+ `ext_tabline` Externalize the tabline. |ui-tabline|
`ext_termcolors` Use external default colors.
-Specifying a non-existent option is an error. UIs can check the |api-metadata|
-`ui_options` key for supported options. Additionally Nvim (currently) requires
-that all connected UIs use the same set of widgets. Therefore the active
-widgets will be the intersection of the requested widget sets of all connected
-UIs. The "option_set" event announces which widgets actually are active.
-
-Nvim sends msgpack-rpc notifications to all attached UIs, with method name
-"redraw" and a single argument: an array (batch) of screen "update events".
-Each update event is itself an array whose first element is the event name and
-remaining elements are event-parameter tuples. This allows multiple events of
-the same kind to be sent in a row without the event name being repeated. This
-batching is mostly used for "grid_line", because each "grid_line" event puts
-contents in one grid line, but clients must be prepared for multiple argument
-sets being batched for all event kinds.
-
-Events must be handled in-order. A "flush" event is sent when nvim is done
-redrawing the entire screen (so that all windows have a consistent view of
-buffer state, options etc). Clients should be prepared that several "redraw"
-batches are sent before the entire screen has been redrawn, and only the last
-batch will end in "flush". The user should only see the final state when
-"flush" is sent, and not any intermediate state after processing only part of
-the batch array, nor after a batch not ending with "flush".
-
-By default, Nvim sends |ui-global| and |ui-grid-old| events; these suffice to
-implement a terminal-like interface. However there are two revisions of the
-grid part of the protocol. The newer revision |ui-linegrid|, enabled by
-`ext_linegrid` option, has a more effecient representation of text (especially
-highlighted text), and allows extensions that use multiple grids.
-
-The older revision is available and used by default only for backwards
-compatibility reasons. New UIs are strongly recommended to use |ui-linegrid|,
-as further protocol extensions require it. The |ui-multigrid| extension
-enables |ui-linegrid| implicitly.
-
-Nvim optionally sends screen elements "semantically" as structured events
-instead of raw grid-lines, controlled by |ui-ext-options|. The UI must present
-those elements itself; Nvim will not draw those elements on the grid.
+Specifying an unknown option is an error; UIs can check the |api-metadata|
+`ui_options` key for supported options.
+
+By default Nvim requires all connected UIs to support the same capabilities,
+thus the active capabilities are the intersection of those requested. UIs may
+specify |ui-override| to invert this behavior (useful for debugging). The
+"option_set" event announces which capabilities are active.
+
+Nvim sends RPC notifications to all attached UIs, with method name "redraw"
+and a single argument: an array (batch) of screen "update events". Each update
+event is itself an array whose first element is the event name and remaining
+elements are event-parameter tuples. Thus multiple events of the same kind can
+be sent contiguously without repeating the event name.
+
+Example of a typical "redraw" batch in a single RPC notification: >
+
+ ['notification', 'redraw',
+ [
+ ['grid_resize', [2, 77, 36]],
+ ['grid_line',
+ [2, 0, 0, [[' ' , 0, 77]]],
+ [2, 1, 0, [['~', 7], [' ', 7, 76]]],
+ [2, 9, 0, [['~', 7], [' ', 7, 76]]],
+ ...
+ [2, 35, 0, [['~', 7], [' ', 7, 76]]],
+ [1, 36, 0, [['[', 9], ['N'], ['o'], [' '], ['N'], ['a'], ['m'], ['e'], [']']]],
+ [1, 36, 9, [[' ', 9, 50]]],
+ [1, 36, 59, [['0', 9], [','], ['0'], ['-' ], ['1'], [' ', 9, 10], ['A'], ['l', 9, 2]]]
+ ],
+ ['msg_showmode', [[]]],
+ ['win_pos', [2, 1000, 0, 0, 77, 36]],
+ ['grid_cursor_goto', [2, 0, 0]],
+ ['flush', []]
+ ]
+ ]
+
+Events must be handled in-order. Nvim sends a "flush" event when it has
+completed a redraw of the entire screen (so all windows have a consistent view
+of buffer state, options, etc.). Multiple "redraw" batches may be sent before
+the entire screen has been redrawn, with "flush" following only the last
+batch. The user should only see the final state (when "flush" is sent), not
+any intermediate state while processing part of the batch array, nor after
+a batch not ending with "flush".
+
+By default, Nvim sends |ui-global| and |ui-grid-old| events (for backwards
+compatibility); these suffice to implement a terminal-like interface. However
+the new |ui-linegrid| represents text more efficiently (especially highlighted
+text), and allows UI capabilities requiring multiple grids. New UIs should
+implement |ui-linegrid| instead of |ui-grid-old|.
+
+Nvim optionally sends various screen elements "semantically" as structured
+events instead of raw grid-lines, as specified by |ui-ext-options|. The UI
+must present such elements itself, Nvim will not draw them on the grid.
Future versions of Nvim may add new update kinds and may append new parameters
to existing update kinds. Clients must be prepared to ignore such extensions,
@@ -83,40 +111,36 @@ for forward-compatibility. |api-contract|
==============================================================================
UI startup *ui-startup*
-Nvim defines a standard procedure for how an embedding UI should interact with
-the startup phase of Nvim. When spawning the nvim process, use the |--embed| flag
-but not the |--headless| flag. The started Nvim process will pause before loading
-startup files and reading buffers, and give the UI a chance to invoke requests
-to do early initialization. As soon as the UI invokes |nvim_ui_attach()|, the
-startup will continue.
+UI embedders (clients that start Nvim with |--embed| and later call
+|nvim_ui_attach()|) must start Nvim without |--headless|: >
+ nvim --embed
+Nvim will pause before loading startup files and reading buffers, so the UI
+has a chance to invoke requests and do early initialization. Startup will
+continue as soon as the UI invokes |nvim_ui_attach()|.
-A simple UI only need to do a single |nvim_ui_attach()| request and then
-be prepared to handle any UI event. A more featureful UI, which might need
-additional configuration of the nvim process, should use the following startup
+A simple UI only needs to do a single |nvim_ui_attach()| request and then
+prepare to handle any UI event. A more featureful UI, which might need
+additional configuration of the Nvim process, should use the following startup
procedure:
-1. Invoke |nvim_get_api_info()|, if this is needed to setup the client library
- and/or to get the list of supported UI extensions.
-2. At this time, any configuration that should be happen before init.vim
- loading should be done. Buffers and windows are not available at this
- point, but this could be used to set |g:| variables visible to init.vim
-3. If the UI wants to do additional setup after the init.vim file was loaded
- register an autocmd for VimEnter at this point: >
-
- nvim_command("autocmd VimEnter * call rpcrequest(1, 'vimenter')")
-
-<4. Now invoke |nvim_ui_attach()|. The UI will need to handle keyboard input
- at this point, as sourcing init.vim and loading buffers might lead to
- blocking prompts.
-5. If step 3 was used, nvim will send a blocking "vimenter" request to the
- UI. Inside this request handler, the UI can safely do any initialization
- before entering normal mode, for instance reading variables set by
- init.vim.
+1. Invoke |nvim_get_api_info()|, if needed to setup the client library and/or
+ to get the list of supported UI extensions.
+2. Do any configuration that should be happen before user config is loaded.
+ Buffers and windows are not available at this point, but this could be used
+ to set |g:| variables visible to init.vim
+3. If the UI wants to do additional setup after user config is loaded,
+ register a VimEnter autocmd: >
+ nvim_command("autocmd VimEnter * call rpcrequest(1, 'vimenter')")
+<4. Now invoke |nvim_ui_attach()|. The UI must handle user input by now:
+ sourcing init.vim and loading buffers might lead to blocking prompts.
+5. If step 3 was used, Nvim will send a blocking "vimenter" request to the UI.
+ Inside this request handler, the UI can safely do any initialization before
+ entering normal mode, for example reading variables set by init.vim.
==============================================================================
Global Events *ui-global*
-The following events will always be available, and describe global state of
+The following UI events are always emitted, and describe global state of
the editor.
["set_title", title]
@@ -189,7 +213,7 @@ the editor.
["mouse_off"]
Tells the client whether mouse support, as determined by |'mouse'|
option, is considered to be active in the current mode. This is mostly
- useful for a terminal frontend, or other situations where nvim mouse
+ useful for a terminal frontend, or other situations where Nvim mouse
would conflict with other usages of the mouse. It is safe for a client
to ignore this and always send mouse events.
@@ -218,20 +242,20 @@ the editor.
==============================================================================
Grid Events (line-based) *ui-linegrid*
-These events are used if `ext_linegrid` option is set (recommended for all new
-UIs). The biggest change compared to previous revision is to use a single
-event `grid_line` to update the contents of a screen line (where the old
-protocol used a combination of cursor, highlight and text events)
+Activated by the `ext_linegrid` |ui-option|. Recommended for all new UIs.
+Deactivates |ui-grid-old| implicitly.
+
+The biggest change compared to |ui-grid-old| is to use a single `grid_line`
+event to update the contents of a screen line (whereas the old protocol used
+a combination of cursor, highlight and text events)
Most of these events take a `grid` index as first parameter. Grid 1 is the
-global grid used by default for the entire editor screen state. Grids other
-than that will be defined by future extensions. Just activating the
-`ext_linegrid` option by itself will never cause any additional grids to be
-created. To enable per-window grids, `ext_multigrid` option should be set (see
-|ui-multigrid|).
+global grid used by default for the entire editor screen state. The
+`ext_linegrid` capability by itself will never cause any additional grids to
+be created; to enable per-window grids, activate |ui-multigrid|.
Highlight attribute groups are predefined. UIs should maintain a table to map
-numerical highlight `id`:s to the actual attributes.
+numerical highlight ids to the actual attributes.
["grid_resize", grid, width, height]
Resize a `grid`. If `grid` wasn't seen by the client before, a new grid is
@@ -242,16 +266,16 @@ numerical highlight `id`:s to the actual attributes.
special colors respectively. `cterm_fg` and `cterm_bg` specifies the
default color codes to use in a 256-color terminal.
- The rgb values will always be valid colors, by default. If no
+ The RGB values will always be valid colors, by default. If no
colors have been set, they will default to black and white, depending
on 'background'. By setting the `ext_termcolors` option, instead
- -1 will be used for unset colors. This is mostly useful for a
- TUI implementation, where using the terminal emulators builitn
- defaults are expected.
+ -1 will be used for unset colors. This is mostly useful for a TUI
+ implementation, where using the terminal builtin ("ANSI") defaults
+ are expected.
- Note: unlike the corresponding events in the first revision, the
- screen is not always cleared after sending this event. The GUI has to
- repaint the screen with changed background color itself.
+ Note: Unlike the corresponding |ui-grid-old| events, the screen is not
+ always cleared after sending this event. The UI must repaint the
+ screen with changed background color itself.
*ui-event-hl_attr_define*
["hl_attr_define", id, rgb_attr, cterm_attr, info]
@@ -275,7 +299,7 @@ numerical highlight `id`:s to the actual attributes.
All boolean keys default to false, and will only be sent when they
are true.
- Highlights are always transmitted both for both the rgb format and as
+ Highlights are always transmitted both for both the RGB format and as
terminal 256-color codes, as the `rgb_attr` and `cterm_attr` parameters
respectively. The |ui-rgb| option has no effect effect anymore.
Most external UIs will only need to store and use the `rgb_attr`
@@ -284,17 +308,17 @@ numerical highlight `id`:s to the actual attributes.
`id` 0 will always be used for the default highlight with colors defined
by `default_colors_set` and no styles applied.
- Note: `id`:s can be reused if Nvim's internal highlight table is full.
- In this case, Nvim will always issue redraws of screen cells that are
- affected by redefined `id`:s, so UIs do not need to keep track of this
+ Note: Nvim may reuse `id` values if its internal highlight table is full.
+ In that case Nvim will always issue redraws of screen cells that are
+ affected by redefined ids, so UIs do not need to keep track of this
themselves.
- `info` is an empty array per default, and will be used by the
- |ui-hlstate| extension explaned below.
+ `info` is an empty array by default, and will be used by the
+ |ui-hlstate| extension explained below.
*ui-event-grid_line*
["grid_line", grid, row, col_start, cells]
- Redraw a continous part of a `row` on a `grid`, starting at the column
+ Redraw a continuous part of a `row` on a `grid`, starting at the column
`col_start`. `cells` is an array of arrays each with 1 to 3 items:
`[text(, hl_id, repeat)]` . `text` is the UTF-8 text that should be put in
a cell, with the highlight `hl_id` defined by a previous `hl_attr_define`
@@ -372,10 +396,10 @@ numerical highlight `id`:s to the actual attributes.
part of handling the scroll event.
==============================================================================
-Legacy Grid Events (cell based) *ui-grid-old*
+Grid Events (cell-based) *ui-grid-old*
-This is an older representation of the screen grid, used if `ext_linegrid`
-option is not set. New UIs should use |ui-linegrid|.
+This is the legacy representation of the screen grid, emitted if |ui-linegrid|
+is not active. New UIs should implement |ui-linegrid| instead.
["resize", width, height]
The grid is resized to `width` and `height` cells.
@@ -407,7 +431,7 @@ option is not set. New UIs should use |ui-linegrid|.
updates. All boolean keys default to false.
`foreground`: foreground color.
- `background`: backround color.
+ `background`: background color.
`special`: color to use for underline and undercurl, when present.
`reverse`: reverse video. Foreground and background colors are
switched.
@@ -462,13 +486,13 @@ option is not set. New UIs should use |ui-linegrid|.
==============================================================================
Detailed highlight state Extension *ui-hlstate*
-Only sent if `ext_hlstate` option is set in |ui-options|. `ext_hlstate` implies
-`ext_linegrid`.
+Activated by the `ext_hlstate` |ui-option|.
+Activates |ui-linegrid| implicitly.
-By default, nvim will only describe grid cells using the final calculated
-higlight attributes, as described by the dict keys in |ui-event-highlight_set|.
+By default Nvim will only describe grid cells using the final calculated
+highlight attributes, as described by the dict keys in |ui-event-highlight_set|.
The `ext_hlstate` extension allows to the UI to also receive a semantic
-describtion of the higlights active in a cell. In this mode highlights will be
+description of the highlights active in a cell. In this mode highlights will be
predefined in a table, see |ui-event-hl_attr_define| and |ui-event-grid_line|.
The `info` parameter in `hl_attr_define` will contain a semantic description
of the highlights. As highlight groups can be combined, this will be an array
@@ -476,14 +500,13 @@ of items, with the item with highest priority last. Each item is a dictionary
with the following possible keys:
`kind`: always present. One of the following values:
- "ui": A builtin ui highlight.
- "syntax": highlight applied to a buffer by a syntax declaration or
- other runtime/plugin functionallity such as
+ "ui": Builtin UI highlight. |highlight-groups|
+ "syntax": Highlight applied to a buffer by a syntax declaration or
+ other runtime/plugin functionality such as
|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
- possible values. Only present for "ui".
+ Contains no further semantic information.
+ `ui_name`: Highlight name from |highlight-groups|. Only for "ui" kind.
`hi_name`: Name of the final |:highlight| group where the used
attributes are defined.
`id`: Unique numeric id representing this item.
@@ -497,30 +520,32 @@ screen elements, even if no attributes have been applied.
==============================================================================
Multigrid Events *ui-multigrid*
-Only sent if `ext_multigrid` option is set in |ui-options|. Enables the
-`ext_linegrid` extension implicitly.
+Activated by the `ext_multigrid` |ui-option|.
+Activates |ui-linegrid| implicitly.
-The multigrid extension gives the UIs more control over how windows are
-displayed. The UIs receive updates on a separate grid for each window. The UIs
-can set the grid size independently of how much space the window occupies on
-the global layout. This enables the UIs to set a different font size for each
-window if the UI so desires. The UI can also reserve space around the border
-of the window for its own elements, for instance scrollbars from the UI
-toolkit.
-
-By default, the grid size is handled by nvim and set to the outer grid size
-(i.e. the size of the window frame in nvim) whenever the split is created.
-Once a UI sets a grid size, nvim does not handle the size for that grid and
+See |ui-linegrid| for grid events.
+See |nvim_ui_try_resize_grid()| to request changing the grid size.
+See |nvim_input_mouse()| for sending mouse events to Nvim.
+
+The multigrid extension gives UIs more control over how windows are displayed:
+- UIs receive updates on a separate grid for each window.
+- UIs can set the grid size independently of how much space the window
+ occupies on the global layout. So the UI could use a different font size
+ per-window. Or reserve space around the border of the window for its own
+ elements, such as scrollbars from the UI toolkit.
+
+By default, the grid size is handled by Nvim and set to the outer grid size
+(i.e. the size of the window frame in Nvim) whenever the split is created.
+Once a UI sets a grid size, Nvim does not handle the size for that grid and
the UI must change the grid size whenever the outer size is changed. To
-delegate the handling of grid size back to nvim, the UIs should request the
-size (0, 0).
+delegate grid-size handling back to Nvim, request the size (0, 0).
A window can be hidden and redisplayed without its grid being deallocated.
This can happen multiple times for the same window, for instance when switching
tabs.
["win_pos", grid, win, start_row, start_col, width, height]
- Set the position and size of the grid in nvim (i.e. the outer grid
+ Set the position and size of the grid in Nvim (i.e. the outer grid
size). If the window was previously hidden, it should now be shown
again.
@@ -532,7 +557,7 @@ tabs.
["win_external_pos", grid, win]
Display or reconfigure external window `win`. The window should be
- displayed as a separate top-level window in the desktop envirionment,
+ displayed as a separate top-level window in the desktop environment,
or something similar.
["win_hide", grid]
@@ -551,14 +576,13 @@ tabs.
["win_close", grid]
Close the window.
-See |ui-linegrid| for grid events.
-See |nvim_ui_try_resize_grid| in |api-ui| to request changing the grid size.
-See |nvim_input_mouse| for sending mouse events to Nvim.
-
==============================================================================
Popupmenu Events *ui-popupmenu*
-Only sent if `ext_popupmenu` option is set in |ui-options|.
+Activated by the `ext_popupmenu` |ui-option|.
+
+This UI extension delegates presentation of the |popupmenu-completion| and
+command-line 'wildmenu'.
["popupmenu_show", items, selected, row, col, grid]
Show |popupmenu-completion|. `items` is an array of completion items
@@ -583,7 +607,7 @@ Only sent if `ext_popupmenu` option is set in |ui-options|.
==============================================================================
Tabline Events *ui-tabline*
-Only sent if `ext_tabline` option is set in |ui-options|
+Activated by the `ext_tabline` |ui-option|.
["tabline_update", curtab, tabs]
Tabline was updated. UIs should present this data in a custom tabline
@@ -594,10 +618,10 @@ Only sent if `ext_tabline` option is set in |ui-options|
==============================================================================
Cmdline Events *ui-cmdline*
-Only sent if `ext_cmdline` option is set in |ui-options|. To handle
-command-line completion (wildmenu), use |ui-popupmenu| events activated by
-|ext_popupmenu| option. (The `ext_wildmenu` option only exists for backwards
-compatibility).
+Activated by the `ext_cmdline` |ui-option|.
+
+This UI extension delegates presentation of the |cmdline| (except 'wildmenu').
+For command-line 'wildmenu' UI events, activate |ui-popupmenu|.
["cmdline_show", content, pos, firstc, prompt, indent, level]
content: List of [attrs, string]
@@ -653,47 +677,49 @@ compatibility).
Hide the block.
==============================================================================
-Message Events *ui-messages*
+Message/Dialog Events *ui-messages*
-Only sent if `ext_messages` option is set in |ui-options|. This option implies
-`ext_linegrid` and `ext_cmdline` also being set. |ui-linegrid| and |ui-cmdline| events
-will thus also be sent.
+Activated by the `ext_messages` |ui-option|.
+Activates |ui-linegrid| and |ui-cmdline| implicitly.
-This extension allows the UI to control the display of messages that otherwise
-would have been displayed in the message/cmdline area in the bottom of the
-screen.
+This UI extension delegates presentation of messages and dialogs. Messages
+that would otherwise render in the message/cmdline screen space, are emitted
+as UI events.
-Activating this extension means that Nvim will allocate no screen space for
-the cmdline or messages, and 'cmdheight' will be set to zero. Attempting to
-change 'cmdheight' will silently be ignored. |ui-cmdline| events will be used
-to represent the state of the cmdline.
+Nvim will not allocate screen space for the cmdline or messages, and
+'cmdheight' will be forced zero. Cmdline state is emitted as |ui-cmdline|
+events, which the UI must handle.
["msg_show", kind, content, replace_last]
Display a message to the user.
- `kind` will be one of the following
- `emsg`: Internal error message
- `echo`: temporary message from plugin (|:echo|)
- `echomsg`: ordinary message from plugin (|:echomsg|)
- `echoerr`: error message from plugin (|:echoerr|)
- `return_prompt`: |press-enter| prompt after a group of messages
- `quickfix`: Quickfix navigation message
- `kind` can also be the empty string. The message is then some internal
- informative or warning message, that hasn't yet been assigned a kind.
- New message kinds can be added in later versions; clients should
- handle messages of an unknown kind just like empty kind.
-
- `content` will be an array of `[attr_id, text_chunk]` tuples,
- building up the message text of chunks of different highlights.
- No extra spacing should be added between chunks, the `text_chunk` by
- itself should contain any necessary whitespace. Messages can contain
- line breaks `"\n"`.
-
- `replace_last` controls how multiple messages should be displayed.
- If `replace_last` is false, this message should be displayed together
- with all previous messages that are still visible. If `replace_last`
- is true, this message should replace the message in the most recent
- `msg_show` call, but any other visible message should still remain.
+ kind
+ Name indicating the message kind:
+ "" (empty) Unknown, report a |feature-request|
+ "confirm" |confirm()| or |:confirm| dialog
+ "confirm_sub" |:substitute| confirm dialog |:s_c|
+ "emsg" Error (|errors|, internal error, |:throw|, …)
+ "echo" |:echo| message
+ "echomsg" |:echomsg| message
+ "echoerr" |:echoerr| message
+ "return_prompt" |press-enter| prompt after a multiple messages
+ "quickfix" Quickfix navigation message
+ "wmsg" Warning ("search hit BOTTOM", |W10|, …)
+ New kinds may be added in the future; clients should treat unknown
+ kinds as the empty kind.
+
+ content
+ Array of `[attr_id, text_chunk]` tuples, building up the message
+ text of chunks of different highlights. No extra spacing should be
+ added between chunks, the `text_chunk` by itself contains any
+ necessary whitespace. Messages can contain line breaks "\n".
+
+ replace_last
+ Decides how multiple messages should be displayed:
+ false: Display the message together with all previous messages
+ that are still visible.
+ true: Replace the message in the most-recent `msg_show` call,
+ but any other visible message should still remain.
["msg_clear"]
Clear all messages currently displayed by "msg_show". (Messages sent
diff --git a/runtime/doc/usr_11.txt b/runtime/doc/usr_11.txt
index e5591ac1d1..42b564e962 100644
--- a/runtime/doc/usr_11.txt
+++ b/runtime/doc/usr_11.txt
@@ -205,6 +205,13 @@ something wrong. It may be one of these two situations.
NEWER than swap file! ~
+NOTE that in the following situation Vim knows the swap file is not useful and
+will automatically delete it:
+- The file is a valid swap file (Magic number is correct).
+- The flag that the file was modified is not set.
+- The process is not running.
+
+
UNREADABLE SWAP FILE
Sometimes the line
diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt
index 32923f6b25..53bdb5e8fe 100644
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -957,6 +957,8 @@ Various: *various-functions*
taglist() get list of matching tags
tagfiles() get a list of tags files
+ gettagstack() get the tag stack
+ settagstack() modify the tag stack
luaeval() evaluate Lua expression
py3eval() evaluate Python expression (|+python3|)
diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt
index 6f809af387..a0b2846b2b 100644
--- a/runtime/doc/various.txt
+++ b/runtime/doc/various.txt
@@ -235,35 +235,32 @@ g8 Print the hex values of the bytes used in the
*:!cmd* *:!* *E34*
:!{cmd} Execute {cmd} with 'shell'. See also |:terminal|.
- Any '!' in {cmd} is replaced with the previous
- external command (see also 'cpoptions'). But not when
- there is a backslash before the '!', then that
- backslash is removed. Example: ":!ls" followed by
+ The command runs in a non-interactive shell connected
+ to a pipe (not a terminal). Use |:terminal| to run an
+ interactive shell connected to a terminal.
+
+ Backgrounded ("&") commands must not write to stdout
+ or stderr, the streams are closed immediately. |E5677|
+ Use |jobstart()| instead. >
+ :call jobstart('foo', {'detach':1})
+<
+ Any "!" in {cmd} is replaced with the previous
+ external command (see also 'cpoptions'), unless
+ escaped by a backslash. Example: ":!ls" followed by
":!echo ! \! \\!" executes "echo ls ! \!".
- A '|' in {cmd} is passed to the shell, you cannot use
- it to append a Vim command. See |:bar|.
+ Any "|" in {cmd} is passed to the shell, you cannot
+ use it to append a Vim command. See |:bar|.
- If {cmd} contains "%" it is expanded to the current
- file name. Special characters are not escaped, use
- quotes to avoid their special meaning: >
+ Any "%" in {cmd} is expanded to the current file name.
+ Special characters are not escaped, use quotes or
+ |shellescape()|: >
:!ls "%"
-< If the file name contains a "$" single quotes might
- work better (but a single quote causes trouble): >
- :!ls '%'
-< This should always work, but it's more typing: >
:exe "!ls " . shellescape(expand("%"))
<
- A newline character ends {cmd}, what follows is
- interpreted as a following ":" command. However, if
- there is a backslash before the newline it is removed
- and {cmd} continues. It doesn't matter how many
- backslashes are before the newline, only one is
- removed.
-
- The command runs in a non-interactive shell connected
- to a pipe (not a terminal). Use |:terminal| to run an
- interactive shell connected to a terminal.
+ Newline character ends {cmd} unless a backslash
+ precedes the newline. What follows is interpreted as
+ another |:| command.
After the command has been executed, the timestamp and
size of the current file is checked |timestamp|.
@@ -273,15 +270,9 @@ g8 Print the hex values of the bytes used in the
data is lost, this only affects the display. The last
few lines are always displayed (never skipped).
- Vim redraws the screen after the command is finished,
- because it may have printed any text. This requires a
- hit-enter prompt, so that you can read any messages.
- To avoid this use: >
+ To avoid the hit-enter prompt use: >
:silent !{cmd}
-< The screen is not redrawn then, thus you have to use
- CTRL-L or ":redraw!" if the command did display
- something.
-
+<
*:!!*
:!! Repeat last ":!{cmd}".
@@ -289,10 +280,6 @@ g8 Print the hex values of the bytes used in the
:ve[rsion] Print editor version and build information.
See also |feature-compile|.
-:ve[rsion] {nr} Ignored. Previously used to check the version number
- of a .vimrc file. You can now use the ":if" command
- for version-dependent behavior.
-
*:redi* *:redir*
:redi[r][!] > {file} Redirect messages to file {file}. The messages which
are the output of commands are written to that file,
diff --git a/runtime/filetype.vim b/runtime/filetype.vim
index 1a08f5675f..a670f54898 100644
--- a/runtime/filetype.vim
+++ b/runtime/filetype.vim
@@ -707,8 +707,8 @@ au BufNewFile,BufRead *.erb,*.rhtml setf eruby
" HTML with M4
au BufNewFile,BufRead *.html.m4 setf htmlm4
-" HTML Cheetah template
-au BufNewFile,BufRead *.tmpl setf htmlcheetah
+" Some template. Used to be HTML Cheetah.
+au BufNewFile,BufRead *.tmpl setf template
" Host config
au BufNewFile,BufRead */etc/host.conf setf hostconf
@@ -1694,6 +1694,9 @@ au BufNewFile,BufReadPost *.tutor setf tutor
" TWIG files
au BufNewFile,BufReadPost *.twig setf twig
+" Typescript
+au BufNewFile,BufReadPost *.ts setf typescript
+
" Motif UIT/UIL files
au BufNewFile,BufRead *.uit,*.uil setf uil
diff --git a/runtime/indent/vim.vim b/runtime/indent/vim.vim
index 8ebfa12caf..cd735c3a3c 100644
--- a/runtime/indent/vim.vim
+++ b/runtime/indent/vim.vim
@@ -10,7 +10,7 @@ endif
let b:did_indent = 1
setlocal indentexpr=GetVimIndent()
-setlocal indentkeys+==end,=else,=cat,=fina,=END,0\\
+setlocal indentkeys+==end,=else,=cat,=fina,=END,0\\,0=\"\\\
let b:undo_indent = "setl indentkeys< indentexpr<"
@@ -31,15 +31,17 @@ function GetVimIndent()
endtry
endfunc
+let s:lineContPat = '^\s*\(\\\|"\\ \)'
+
function GetVimIndentIntern()
" Find a non-blank line above the current line.
let lnum = prevnonblank(v:lnum - 1)
- " If the current line doesn't start with '\' and below a line that starts
- " with '\', use the indent of the line above it.
+ " If the current line doesn't start with '\' or '"\ ' and below a line that
+ " starts with '\' or '"\ ', use the indent of the line above it.
let cur_text = getline(v:lnum)
- if cur_text !~ '^\s*\\'
- while lnum > 0 && getline(lnum) =~ '^\s*\\'
+ if cur_text !~ s:lineContPat
+ while lnum > 0 && getline(lnum) =~ s:lineContPat
let lnum = lnum - 1
endwhile
endif
@@ -51,10 +53,10 @@ function GetVimIndentIntern()
let prev_text = getline(lnum)
" Add a 'shiftwidth' after :if, :while, :try, :catch, :finally, :function
- " and :else. Add it three times for a line that starts with '\' after
- " a line that doesn't (or g:vim_indent_cont if it exists).
+ " and :else. Add it three times for a line that starts with '\' or '"\ '
+ " after a line that doesn't (or g:vim_indent_cont if it exists).
let ind = indent(lnum)
- if cur_text =~ '^\s*\\' && v:lnum > 1 && prev_text !~ '^\s*\\'
+ if cur_text =~ s:lineContPat && v:lnum > 1 && prev_text !~ s:lineContPat
if exists("g:vim_indent_cont")
let ind = ind + g:vim_indent_cont
else
diff --git a/runtime/nvim.appdata.xml b/runtime/nvim.appdata.xml
index 095e6c6fa8..fb409ff0b8 100644
--- a/runtime/nvim.appdata.xml
+++ b/runtime/nvim.appdata.xml
@@ -8,6 +8,7 @@
-->
<component type="desktop-application">
<id>nvim</id>
+ <translation type="gettext">nvim</translation>
<project_license>Apache-2.0</project_license>
<metadata_license>CC0-1.0</metadata_license>
<name>Neovim</name>
@@ -24,6 +25,16 @@
</screenshot>
</screenshots>
+ <releases>
+ <release date="2019-04-29" version="0.3.5"/>
+ <release date="2019-01-13" version="0.3.4"/>
+ <release date="2019-01-05" version="0.3.3"/>
+ <release date="2018-12-31" version="0.3.2"/>
+ <release date="2018-07-19" version="0.3.1"/>
+ <release date="2018-07-11" version="0.3.0"/>
+ </releases>
+
+ <content_rating type="oars-1.1"/>
<launchable type="desktop-id">nvim.desktop</launchable>
<url type="homepage">https://neovim.io/</url>
<url type="bugtracker">https://github.com/neovim/neovim/issues</url>
diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
index 506179297a..78b1ae8ce8 100644
--- a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
+++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
@@ -25,32 +25,44 @@ endif
command -nargs=* -complete=file Termdebug call s:StartDebug(<q-args>)
" Name of the gdb command, defaults to "gdb".
-if !exists('debugger')
- let debugger = 'gdb'
+if !exists('termdebugger')
+ let termdebugger = 'gdb'
endif
-" Sign used to highlight the line where the program has stopped.
-sign define debugPC linehl=debugPC
+let s:pc_id = 12
+let s:break_id = 13
+
if &background == 'light'
- hi debugPC term=reverse ctermbg=lightblue guibg=lightblue
+ hi default debugPC term=reverse ctermbg=lightblue guibg=lightblue
else
- hi debugPC term=reverse ctermbg=darkblue guibg=darkblue
+ hi default debugPC term=reverse ctermbg=darkblue guibg=darkblue
endif
-let s:pc_id = 12
+hi default debugBreakpoint term=reverse ctermbg=red guibg=red
func s:StartDebug(cmd)
let s:startwin = win_getid(winnr())
let s:startsigncolumn = &signcolumn
+ if exists('g:termdebug_wide') && &columns < g:termdebug_wide
+ let s:save_columns = &columns
+ let &columns = g:termdebug_wide
+ let vertical = 1
+ else
+ let s:save_columns = 0
+ let vertical = 0
+ endif
+
" Open a terminal window without a job, to run the debugged program
let s:ptybuf = term_start('NONE', {
\ 'term_name': 'gdb program',
+ \ 'vertical': vertical,
\ })
if s:ptybuf == 0
echoerr 'Failed to open the program terminal window'
return
endif
let pty = job_info(term_getjob(s:ptybuf))['tty_out']
+ let s:ptywin = win_getid(winnr())
" Create a hidden terminal window to communicate with gdb
let s:commbuf = term_start('NONE', {
@@ -66,7 +78,7 @@ func s:StartDebug(cmd)
let commpty = job_info(term_getjob(s:commbuf))['tty_out']
" Open a terminal window to run the debugger.
- let cmd = [g:debugger, '-tty', pty, a:cmd]
+ let cmd = [g:termdebugger, '-tty', pty, a:cmd]
echomsg 'executing "' . join(cmd) . '"'
let gdbbuf = term_start(cmd, {
\ 'exit_cb': function('s:EndDebug'),
@@ -78,15 +90,41 @@ func s:StartDebug(cmd)
exe 'bwipe! ' . s:commbuf
return
endif
+ let s:gdbwin = win_getid(winnr())
" Connect gdb to the communication pty, using the GDB/MI interface
call term_sendkeys(gdbbuf, 'new-ui mi ' . commpty . "\r")
+
+ " Sign used to highlight the line where the program has stopped.
+ " There can be only one.
+ sign define debugPC linehl=debugPC
+
+ " Sign used to indicate a breakpoint.
+ " Can be used multiple times.
+ sign define debugBreakpoint text=>> texthl=debugBreakpoint
+
+ " Install debugger commands in the text window.
+ call win_gotoid(s:startwin)
+ call s:InstallCommands()
+ call win_gotoid(s:gdbwin)
+
+ let s:breakpoints = {}
endfunc
func s:EndDebug(job, status)
exe 'bwipe! ' . s:ptybuf
exe 'bwipe! ' . s:commbuf
- call setwinvar(s:startwin, '&signcolumn', s:startsigncolumn)
+
+ let curwinid = win_getid(winnr())
+
+ call win_gotoid(s:startwin)
+ let &signcolumn = s:startsigncolumn
+ call s:DeleteCommands()
+
+ call win_gotoid(curwinid)
+ if s:save_columns > 0
+ let &columns = s:save_columns
+ endif
endfunc
" Handle a message received from gdb on the GDB/MI interface.
@@ -100,34 +138,175 @@ func s:CommOutput(chan, msg)
endif
if msg != ''
if msg =~ '^\*\(stopped\|running\)'
- let wid = win_getid(winnr())
-
- if win_gotoid(s:startwin)
- if msg =~ '^\*stopped'
- " TODO: proper parsing
- let fname = substitute(msg, '.*fullname="\([^"]*\)".*', '\1', '')
- let lnum = substitute(msg, '.*line="\([^"]*\)".*', '\1', '')
- if lnum =~ '^[0-9]*$'
- if expand('%:h') != fname
- if &modified
- " TODO: find existing window
- exe 'split ' . fnameescape(fname)
- let s:startwin = win_getid(winnr())
- else
- exe 'edit ' . fnameescape(fname)
- endif
- endif
- exe lnum
- exe 'sign place ' . s:pc_id . ' line=' . lnum . ' name=debugPC file=' . fnameescape(fname)
- setlocal signcolumn=yes
- endif
+ call s:HandleCursor(msg)
+ elseif msg =~ '^\^done,bkpt=' || msg =~ '^=breakpoint-created,'
+ call s:HandleNewBreakpoint(msg)
+ elseif msg =~ '^=breakpoint-deleted,'
+ call s:HandleBreakpointDelete(msg)
+ elseif msg =~ '^\^done,value='
+ call s:HandleEvaluate(msg)
+ elseif msg =~ '^\^error,msg='
+ call s:HandleError(msg)
+ endif
+ endif
+ endfor
+endfunc
+
+" Install commands in the current window to control the debugger.
+func s:InstallCommands()
+ command Break call s:SetBreakpoint()
+ command Delete call s:DeleteBreakpoint()
+ command Step call s:SendCommand('-exec-step')
+ command Over call s:SendCommand('-exec-next')
+ command Finish call s:SendCommand('-exec-finish')
+ command Continue call s:SendCommand('-exec-continue')
+ command -range -nargs=* Evaluate call s:Evaluate(<range>, <q-args>)
+ command Gdb call win_gotoid(s:gdbwin)
+ command Program call win_gotoid(s:ptywin)
+
+ " TODO: can the K mapping be restored?
+ nnoremap K :Evaluate<CR>
+endfunc
+
+" Delete installed debugger commands in the current window.
+func s:DeleteCommands()
+ delcommand Break
+ delcommand Delete
+ delcommand Step
+ delcommand Over
+ delcommand Finish
+ delcommand Continue
+ delcommand Evaluate
+ delcommand Gdb
+ delcommand Program
+
+ nunmap K
+ exe 'sign unplace ' . s:pc_id
+ for key in keys(s:breakpoints)
+ exe 'sign unplace ' . (s:break_id + key)
+ endfor
+ sign undefine debugPC
+ sign undefine debugBreakpoint
+ unlet s:breakpoints
+endfunc
+
+" :Break - Set a breakpoint at the cursor position.
+func s:SetBreakpoint()
+ call term_sendkeys(s:commbuf, '-break-insert --source '
+ \ . fnameescape(expand('%:p')) . ' --line ' . line('.') . "\r")
+endfunc
+
+" :Delete - Delete a breakpoint at the cursor position.
+func s:DeleteBreakpoint()
+ let fname = fnameescape(expand('%:p'))
+ let lnum = line('.')
+ for [key, val] in items(s:breakpoints)
+ if val['fname'] == fname && val['lnum'] == lnum
+ call term_sendkeys(s:commbuf, '-break-delete ' . key . "\r")
+ " Assume this always wors, the reply is simply "^done".
+ exe 'sign unplace ' . (s:break_id + key)
+ unlet s:breakpoints[key]
+ break
+ endif
+ endfor
+endfunc
+
+" :Next, :Continue, etc - send a command to gdb
+func s:SendCommand(cmd)
+ call term_sendkeys(s:commbuf, a:cmd . "\r")
+endfunc
+
+" :Evaluate - evaluate what is under the cursor
+func s:Evaluate(range, arg)
+ if a:arg != ''
+ let expr = a:arg
+ elseif a:range == 2
+ let pos = getcurpos()
+ let reg = getreg('v', 1, 1)
+ let regt = getregtype('v')
+ normal! gv"vy
+ let expr = @v
+ call setpos('.', pos)
+ call setreg('v', reg, regt)
+ else
+ let expr = expand('<cexpr>')
+ endif
+ call term_sendkeys(s:commbuf, '-data-evaluate-expression "' . expr . "\"\r")
+ let s:evalexpr = expr
+endfunc
+
+" Handle the result of data-evaluate-expression
+func s:HandleEvaluate(msg)
+ echomsg '"' . s:evalexpr . '": ' . substitute(a:msg, '.*value="\(.*\)"', '\1', '')
+endfunc
+
+" Handle an error.
+func s:HandleError(msg)
+ echoerr substitute(a:msg, '.*msg="\(.*\)"', '\1', '')
+endfunc
+
+" Handle stopping and running message from gdb.
+" Will update the sign that shows the current position.
+func s:HandleCursor(msg)
+ let wid = win_getid(winnr())
+
+ if win_gotoid(s:startwin)
+ let fname = substitute(a:msg, '.*fullname="\([^"]*\)".*', '\1', '')
+ if a:msg =~ '^\*stopped' && filereadable(fname)
+ let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '')
+ if lnum =~ '^[0-9]*$'
+ if expand('%:h') != fname
+ if &modified
+ " TODO: find existing window
+ exe 'split ' . fnameescape(fname)
+ let s:startwin = win_getid(winnr())
else
- exe 'sign unplace ' . s:pc_id
+ exe 'edit ' . fnameescape(fname)
endif
-
- call win_gotoid(wid)
endif
+ exe lnum
+ exe 'sign place ' . s:pc_id . ' line=' . lnum . ' name=debugPC file=' . fnameescape(fname)
+ setlocal signcolumn=yes
endif
+ else
+ exe 'sign unplace ' . s:pc_id
endif
- endfor
+
+ call win_gotoid(wid)
+ endif
+endfunc
+
+" Handle setting a breakpoint
+" Will update the sign that shows the breakpoint
+func s:HandleNewBreakpoint(msg)
+ let nr = substitute(a:msg, '.*number="\([0-9]\)*\".*', '\1', '') + 0
+ if nr == 0
+ return
+ endif
+
+ if has_key(s:breakpoints, nr)
+ let entry = s:breakpoints[nr]
+ else
+ let entry = {}
+ let s:breakpoints[nr] = entry
+ endif
+
+ let fname = substitute(a:msg, '.*fullname="\([^"]*\)".*', '\1', '')
+ let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '')
+
+ exe 'sign place ' . (s:break_id + nr) . ' line=' . lnum . ' name=debugBreakpoint file=' . fnameescape(fname)
+
+ let entry['fname'] = fname
+ let entry['lnum'] = lnum
+endfunc
+
+" Handle deleting a breakpoint
+" Will remove the sign that shows the breakpoint
+func s:HandleBreakpointDelete(msg)
+ let nr = substitute(a:msg, '.*id="\([0-9]*\)\".*', '\1', '') + 0
+ if nr == 0
+ return
+ endif
+ exe 'sign unplace ' . (s:break_id + nr)
+ unlet s:breakpoints[nr]
endfunc
diff --git a/runtime/syntax/tutor.vim b/runtime/syntax/tutor.vim
index cb101ee9a7..6305eef734 100644
--- a/runtime/syntax/tutor.vim
+++ b/runtime/syntax/tutor.vim
@@ -49,29 +49,29 @@ syn match tutorInlineType /{\(normal\|vim\)}/ contained conceal
syn match tutorInlineOK /✓/
syn match tutorInlineX /✗/
-hi! tutorLink cterm=underline gui=underline ctermfg=lightblue guifg=#0088ff
-hi! link tutorLinkBands Delimiter
-hi! link tutorLinkAnchor Underlined
-hi! link tutorInternalAnchor Underlined
-hi! link tutorURL tutorLink
-hi! link tutorEmail tutorLink
+hi def tutorLink cterm=underline gui=underline ctermfg=lightblue guifg=#0088ff
+hi def link tutorLinkBands Delimiter
+hi def link tutorLinkAnchor Underlined
+hi def link tutorInternalAnchor Underlined
+hi def link tutorURL tutorLink
+hi def link tutorEmail tutorLink
-hi! link tutorSection Title
-hi! link tutorSectionBullet Delimiter
+hi def link tutorSection Title
+hi def link tutorSectionBullet Delimiter
-hi! link tutorTOC Directory
+hi def link tutorTOC Directory
-hi! tutorMarks cterm=bold gui=bold
+hi def tutorMarks cterm=bold gui=bold
-hi! tutorEmphasis gui=italic cterm=italic
-hi! tutorBold gui=bold cterm=bold
+hi def tutorEmphasis gui=italic cterm=italic
+hi def tutorBold gui=bold cterm=bold
-hi! link tutorExpect Special
-hi! tutorOK ctermfg=green guifg=#00ff88 cterm=bold gui=bold
-hi! tutorX ctermfg=red guifg=#ff2000 cterm=bold gui=bold
-hi! link tutorInlineOK tutorOK
-hi! link tutorInlineX tutorX
+hi def link tutorExpect Special
+hi def tutorOK ctermfg=green guifg=#00ff88 cterm=bold gui=bold
+hi def tutorX ctermfg=red guifg=#ff2000 cterm=bold gui=bold
+hi def link tutorInlineOK tutorOK
+hi def link tutorInlineX tutorX
-hi! link tutorShellPrompt Delimiter
+hi def link tutorShellPrompt Delimiter
let b:current_syntax = "tutor"
diff --git a/runtime/tutor/en/vim-01-beginner.tutor b/runtime/tutor/en/vim-01-beginner.tutor
index 2b30ccb5f8..4e6154b24a 100644
--- a/runtime/tutor/en/vim-01-beginner.tutor
+++ b/runtime/tutor/en/vim-01-beginner.tutor
@@ -159,9 +159,9 @@ There is also some text missing here.
2. At the shell prompt type this command:
~~~ sh
- $ vim tutor
+ $ nvim tutor
~~~
- 'vim' is the command to start the Vim editor, 'tutor' is the name of
+ 'nvim' is the command to start the Nvim editor, 'tutor' is the name of
the file you wish to edit. Use a file that may be changed.
3. Insert and delete text as you learned in the previous lessons.
@@ -186,7 +186,7 @@ There is also some text missing here.
2. To start Vim from the shell prompt type:
~~~ sh
- $ vim FILENAME
+ $ nvim FILENAME
~~~
3. To exit Vim type: `<Esc>`{normal} `:q!`{vim} `<Enter>`{normal} to trash
diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py
index a62d18f02e..cdcab817ad 100755
--- a/scripts/gen_vimdoc.py
+++ b/scripts/gen_vimdoc.py
@@ -139,7 +139,7 @@ def is_blank(text):
return '' == clean_lines(text)
-def get_text(parent):
+def get_text(parent, preformatted=False):
"""Combine all text in a node."""
if parent.nodeType == parent.TEXT_NODE:
return parent.data
@@ -147,9 +147,9 @@ def get_text(parent):
out = ''
for node in parent.childNodes:
if node.nodeType == node.TEXT_NODE:
- out += clean_text(node.data)
+ out += node.data if preformatted else clean_text(node.data)
elif node.nodeType == node.ELEMENT_NODE:
- out += ' ' + get_text(node)
+ out += ' ' + get_text(node, preformatted)
return out
@@ -219,6 +219,14 @@ def doc_wrap(text, prefix='', width=70, func=False, indent=None):
return result
+def has_nonexcluded_params(nodes):
+ """Returns true if any of the given <parameterlist> elements has at least
+ one non-excluded item."""
+ for n in nodes:
+ if render_params(n) != '':
+ return True
+
+
def render_params(parent, width=62):
"""Renders Doxygen <parameterlist> tag as Vim help text."""
name_length = 0
@@ -263,6 +271,10 @@ def render_node(n, text, prefix='', indent='', width=62):
text += doc_wrap(n.data, indent=indent, width=width)
elif n.nodeName == 'computeroutput':
text += ' `{}` '.format(get_text(n))
+ elif n.nodeName == 'preformatted':
+ o = get_text(n, preformatted=True)
+ ensure_nl = '' if o[-1] == '\n' else '\n'
+ text += ' >{}{}\n<'.format(ensure_nl, o)
elif is_inline(n):
for c in n.childNodes:
text += render_node(c, text)
@@ -356,7 +368,7 @@ def render_para(parent, indent='', width=62):
chunks = [text]
# Generate text from the gathered items.
- if len(groups['params']) > 0:
+ if len(groups['params']) > 0 and has_nonexcluded_params(groups['params']):
chunks.append('\nParameters: ~')
for child in groups['params']:
chunks.append(render_params(child, width=width))
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index e27c85cd4f..9a5ffecad4 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -15,6 +15,7 @@
#include "nvim/buffer.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
+#include "nvim/getchar.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/misc1.h"
@@ -97,7 +98,7 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err)
return rv;
}
-/// Activate updates from this buffer to the current channel.
+/// Activates buffer-update events on the channel.
///
/// @param channel_id
/// @param buffer Buffer handle, or 0 for current buffer
@@ -106,7 +107,7 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err)
/// `nvim_buf_lines_event`. Otherwise, the first notification will be
/// a `nvim_buf_changedtick_event`
/// @param opts Optional parameters. Reserved for future use.
-/// @param[out] err Details of an error that may have occurred
+/// @param[out] err Error details, if any
/// @return False when updates couldn't be enabled because the buffer isn't
/// loaded or `opts` contained an invalid key; otherwise True.
Boolean nvim_buf_attach(uint64_t channel_id,
@@ -129,12 +130,12 @@ Boolean nvim_buf_attach(uint64_t channel_id,
return buf_updates_register(buf, channel_id, send_buffer);
}
-//
-/// Deactivate updates from this buffer to the current channel.
+
+/// Deactivates buffer-update events on the channel.
///
/// @param channel_id
/// @param buffer Buffer handle, or 0 for current buffer
-/// @param[out] err Details of an error that may have occurred
+/// @param[out] err Error details, if any
/// @return False when updates couldn't be disabled because the buffer
/// isn't loaded; otherwise True.
Boolean nvim_buf_detach(uint64_t channel_id,
@@ -474,6 +475,7 @@ void nvim_buf_set_lines(uint64_t channel_id,
false);
changed_lines((linenr_T)start, 0, (linenr_T)end, (long)extra, true);
+ fix_cursor((linenr_T)start, (linenr_T)end, (linenr_T)extra);
end:
for (size_t i = 0; i < new_len; i++) {
@@ -575,6 +577,31 @@ ArrayOf(Dictionary) nvim_buf_get_keymap(Buffer buffer, String mode, Error *err)
return keymap_array(mode, buf);
}
+/// Sets a buffer-local |mapping| for the given mode.
+///
+/// @see |nvim_set_keymap()|
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+void nvim_buf_set_keymap(Buffer buffer, String mode, String lhs, String rhs,
+ Dictionary opts, Error *err)
+ FUNC_API_SINCE(6)
+{
+ modify_keymap(buffer, false, mode, lhs, rhs, opts, err);
+}
+
+/// Unmaps a buffer-local |mapping| for the given mode.
+///
+/// @see |nvim_del_keymap()|
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+void nvim_buf_del_keymap(Buffer buffer, String mode, String lhs, Error *err)
+ FUNC_API_SINCE(6)
+{
+ String rhs = { .data = "", .size = 0 };
+ Dictionary opts = ARRAY_DICT_INIT;
+ modify_keymap(buffer, true, mode, lhs, rhs, opts, err);
+}
+
/// Gets a map of buffer-local |user-commands|.
///
/// @param buffer Buffer handle, or 0 for current buffer
@@ -1106,6 +1133,26 @@ free_exit:
return 0;
}
+// Check if deleting lines made the cursor position invalid.
+// Changed lines from `lo` to `hi`; added `extra` lines (negative if deleted).
+static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra)
+{
+ if (curwin->w_cursor.lnum >= lo) {
+ // Adjust cursor position if it's in/after the changed lines.
+ if (curwin->w_cursor.lnum >= hi) {
+ curwin->w_cursor.lnum += extra;
+ check_cursor_col();
+ } else if (extra < 0) {
+ curwin->w_cursor.lnum = lo;
+ check_cursor();
+ } else {
+ check_cursor_col();
+ }
+ changed_cline_bef_curs();
+ }
+ invalidate_botline();
+}
+
// Normalizes 0-based indexes to buffer line numbers
static int64_t normalize_index(buf_T *buf, int64_t index, bool *oob)
{
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index c2b382804d..521ec11906 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -744,6 +744,232 @@ String ga_take_string(garray_T *ga)
return str;
}
+/// Set, tweak, or remove a mapping in a mode. Acts as the implementation for
+/// functions like @ref nvim_buf_set_keymap.
+///
+/// Arguments are handled like @ref nvim_set_keymap unless noted.
+/// @param buffer Buffer handle for a specific buffer, or 0 for the current
+/// buffer, or -1 to signify global behavior ("all buffers")
+/// @param is_unmap When true, removes the mapping that matches {lhs}.
+void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs,
+ String rhs, Dictionary opts, Error *err)
+{
+ char *err_msg = NULL; // the error message to report, if any
+ char *err_arg = NULL; // argument for the error message format string
+ ErrorType err_type = kErrorTypeNone;
+
+ char_u *lhs_buf = NULL;
+ char_u *rhs_buf = NULL;
+
+ bool global = (buffer == -1);
+ if (global) {
+ buffer = 0;
+ }
+ buf_T *target_buf = find_buffer_by_handle(buffer, err);
+
+ MapArguments parsed_args;
+ memset(&parsed_args, 0, sizeof(parsed_args));
+ if (parse_keymap_opts(opts, &parsed_args, err)) {
+ goto fail_and_free;
+ }
+ parsed_args.buffer = !global;
+
+ set_maparg_lhs_rhs((char_u *)lhs.data, lhs.size,
+ (char_u *)rhs.data, rhs.size,
+ CPO_TO_CPO_FLAGS, &parsed_args);
+
+ if (parsed_args.lhs_len > MAXMAPLEN) {
+ err_msg = "LHS exceeds maximum map length: %s";
+ err_arg = lhs.data;
+ err_type = kErrorTypeValidation;
+ goto fail_with_message;
+ }
+
+ if (mode.size > 1) {
+ err_msg = "Shortname is too long: %s";
+ err_arg = mode.data;
+ err_type = kErrorTypeValidation;
+ goto fail_with_message;
+ }
+ int mode_val; // integer value of the mapping mode, to be passed to do_map()
+ char_u *p = (char_u *)((mode.size) ? mode.data : "m");
+ if (STRNCMP(p, "!", 2) == 0) {
+ mode_val = get_map_mode(&p, true); // mapmode-ic
+ } else {
+ mode_val = get_map_mode(&p, false);
+ if ((mode_val == VISUAL + SELECTMODE + NORMAL + OP_PENDING)
+ && mode.size > 0) {
+ // get_map_mode() treats unrecognized mode shortnames as ":map".
+ // This is an error unless the given shortname was empty string "".
+ err_msg = "Invalid mode shortname: \"%s\"";
+ err_arg = (char *)p;
+ err_type = kErrorTypeValidation;
+ goto fail_with_message;
+ }
+ }
+
+ if (parsed_args.lhs_len == 0) {
+ err_msg = "Invalid (empty) LHS";
+ err_arg = "";
+ err_type = kErrorTypeValidation;
+ goto fail_with_message;
+ }
+
+ bool is_noremap = parsed_args.noremap;
+ assert(!(is_unmap && is_noremap));
+
+ if (!is_unmap && (parsed_args.rhs_len == 0 && !parsed_args.rhs_is_noop)) {
+ if (rhs.size == 0) { // assume that the user wants RHS to be a <Nop>
+ parsed_args.rhs_is_noop = true;
+ } else {
+ // the given RHS was nonempty and not a <Nop>, but was parsed as if it
+ // were empty?
+ assert(false && "Failed to parse nonempty RHS!");
+ err_msg = "Parsing of nonempty RHS failed: %s";
+ err_arg = rhs.data;
+ err_type = kErrorTypeException;
+ goto fail_with_message;
+ }
+ } else if (is_unmap && parsed_args.rhs_len) {
+ err_msg = "Gave nonempty RHS in unmap command: %s";
+ err_arg = (char *)parsed_args.rhs;
+ err_type = kErrorTypeValidation;
+ goto fail_with_message;
+ }
+
+ // buf_do_map() reads noremap/unmap as its own argument.
+ int maptype_val = 0;
+ if (is_unmap) {
+ maptype_val = 1;
+ } else if (is_noremap) {
+ maptype_val = 2;
+ }
+
+ switch (buf_do_map(maptype_val, &parsed_args, mode_val, 0, target_buf)) {
+ case 0:
+ break;
+ case 1:
+ api_set_error(err, kErrorTypeException, (char *)e_invarg, 0);
+ goto fail_and_free;
+ case 2:
+ api_set_error(err, kErrorTypeException, (char *)e_nomap, 0);
+ goto fail_and_free;
+ case 5:
+ api_set_error(err, kErrorTypeException,
+ "E227: mapping already exists for %s", parsed_args.lhs);
+ goto fail_and_free;
+ default:
+ assert(false && "Unrecognized return code!");
+ goto fail_and_free;
+ } // switch
+
+ xfree(lhs_buf);
+ xfree(rhs_buf);
+ xfree(parsed_args.rhs);
+ xfree(parsed_args.orig_rhs);
+
+ return;
+
+fail_with_message:
+ api_set_error(err, err_type, err_msg, err_arg);
+
+fail_and_free:
+ xfree(lhs_buf);
+ xfree(rhs_buf);
+ xfree(parsed_args.rhs);
+ xfree(parsed_args.orig_rhs);
+ return;
+}
+
+/// Read in the given opts, setting corresponding flags in `out`.
+///
+/// @param opts A dictionary passed to @ref nvim_set_keymap or
+/// @ref nvim_buf_set_keymap.
+/// @param[out] out MapArguments object in which to set parsed
+/// |:map-arguments| flags.
+/// @param[out] err Error details, if any.
+///
+/// @returns Zero on success, nonzero on failure.
+Integer parse_keymap_opts(Dictionary opts, MapArguments *out, Error *err)
+{
+ char *err_msg = NULL; // the error message to report, if any
+ char *err_arg = NULL; // argument for the error message format string
+ ErrorType err_type = kErrorTypeNone;
+
+ out->buffer = false;
+ out->nowait = false;
+ out->silent = false;
+ out->script = false;
+ out->expr = false;
+ out->unique = false;
+
+ for (size_t i = 0; i < opts.size; i++) {
+ KeyValuePair *key_and_val = &opts.items[i];
+ char *optname = key_and_val->key.data;
+
+ if (key_and_val->value.type != kObjectTypeBoolean) {
+ err_msg = "Gave non-boolean value for an opt: %s";
+ err_arg = optname;
+ err_type = kErrorTypeValidation;
+ goto fail_with_message;
+ }
+
+ bool was_valid_opt = false;
+ switch (optname[0]) {
+ // note: strncmp up to and including the null terminator, so that
+ // "nowaitFoobar" won't match against "nowait"
+
+ // don't recognize 'buffer' as a key; user shouldn't provide <buffer>
+ // when calling nvim_set_keymap or nvim_buf_set_keymap, since it can be
+ // inferred from which function they called
+ case 'n':
+ if (STRNCMP(optname, "noremap", 8) == 0) {
+ was_valid_opt = true;
+ out->noremap = key_and_val->value.data.boolean;
+ } else if (STRNCMP(optname, "nowait", 7) == 0) {
+ was_valid_opt = true;
+ out->nowait = key_and_val->value.data.boolean;
+ }
+ break;
+ case 's':
+ if (STRNCMP(optname, "silent", 7) == 0) {
+ was_valid_opt = true;
+ out->silent = key_and_val->value.data.boolean;
+ } else if (STRNCMP(optname, "script", 7) == 0) {
+ was_valid_opt = true;
+ out->script = key_and_val->value.data.boolean;
+ }
+ break;
+ case 'e':
+ if (STRNCMP(optname, "expr", 5) == 0) {
+ was_valid_opt = true;
+ out->expr = key_and_val->value.data.boolean;
+ }
+ break;
+ case 'u':
+ if (STRNCMP(optname, "unique", 7) == 0) {
+ was_valid_opt = true;
+ out->unique = key_and_val->value.data.boolean;
+ }
+ break;
+ default:
+ break;
+ } // switch
+ if (!was_valid_opt) {
+ err_msg = "Invalid key: %s";
+ err_arg = optname;
+ err_type = kErrorTypeValidation;
+ goto fail_with_message;
+ }
+ } // for
+
+ return 0;
+
+fail_with_message:
+ api_set_error(err, err_type, err_msg, err_arg);
+ return 1;
+}
+
/// Collects `n` buffer lines into array `l`, optionally replacing newlines
/// with NUL.
///
diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index 0634764f13..cc74824402 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -5,6 +5,7 @@
#include "nvim/api/private/defs.h"
#include "nvim/vim.h"
+#include "nvim/getchar.h"
#include "nvim/memory.h"
#include "nvim/ex_eval.h"
#include "nvim/lib/kvec.h"
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index d50a91f261..4f28ea5af3 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -76,6 +76,21 @@ void remote_ui_wait_for_attach(void)
pmap_has(uint64_t)(connected_uis, CHAN_STDIO));
}
+/// Activates UI events on the channel.
+///
+/// Entry point of all UI clients. Allows |\-\-embed| to continue startup.
+/// Implies that the client is ready to show the UI. Adds the client to the
+/// list of UIs. |nvim_list_uis()|
+///
+/// @note If multiple UI clients are attached, the global screen dimensions
+/// degrade to the smallest client. E.g. if client A requests 80x40 but
+/// client B requests 200x100, the global screen has size 80x40.
+///
+/// @param channel_id
+/// @param width Requested screen columns
+/// @param height Requested screen rows
+/// @param options |ui-option| map
+/// @param[out] err Error details, if any
void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height,
Dictionary options, Error *err)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
@@ -95,6 +110,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height,
ui->width = (int)width;
ui->height = (int)height;
ui->rgb = true;
+ ui->override = false;
ui->grid_resize = remote_ui_grid_resize;
ui->grid_clear = remote_ui_grid_clear;
ui->grid_cursor_goto = remote_ui_grid_cursor_goto;
@@ -164,6 +180,12 @@ void ui_attach(uint64_t channel_id, Integer width, Integer height,
api_free_dictionary(opts);
}
+/// Deactivates UI events on the channel.
+///
+/// Removes the client from the list of UIs. |nvim_list_uis()|
+///
+/// @param channel_id
+/// @param[out] err Error details, if any
void nvim_ui_detach(uint64_t channel_id, Error *err)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
@@ -215,6 +237,15 @@ void nvim_ui_set_option(uint64_t channel_id, String name,
static void ui_set_option(UI *ui, bool init, String name, Object value,
Error *error)
{
+ if (strequal(name.data, "override")) {
+ if (value.type != kObjectTypeBoolean) {
+ api_set_error(error, kErrorTypeValidation, "override must be a Boolean");
+ return;
+ }
+ ui->override = value.data.boolean;
+ return;
+ }
+
if (strequal(name.data, "rgb")) {
if (value.type != kObjectTypeBoolean) {
api_set_error(error, kErrorTypeValidation, "rgb must be a Boolean");
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index f54ac5072c..b8c863704a 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -1198,12 +1198,29 @@ void nvim_unsubscribe(uint64_t channel_id, String event)
rpc_unsubscribe(channel_id, e);
}
+/// Returns the 24-bit RGB value of a |nvim_get_color_map()| color name or
+/// "#rrggbb" hexadecimal string.
+///
+/// Example:
+/// <pre>
+/// :echo nvim_get_color_by_name("Pink")
+/// :echo nvim_get_color_by_name("#cbcbcb")
+/// </pre>
+///
+/// @param name Color name or "#rrggbb" string
+/// @return 24-bit RGB value, or -1 for invalid argument.
Integer nvim_get_color_by_name(String name)
FUNC_API_SINCE(1)
{
return name_to_color((char_u *)name.data);
}
+/// Returns a map of color names and RGB values.
+///
+/// Keys are color names (e.g. "Aqua") and values are 24-bit RGB color values
+/// (e.g. 65535).
+///
+/// @return Map of color names and RGB values.
Dictionary nvim_get_color_map(void)
FUNC_API_SINCE(1)
{
@@ -1245,6 +1262,49 @@ ArrayOf(Dictionary) nvim_get_keymap(String mode)
return keymap_array(mode, NULL);
}
+/// Sets a global |mapping| for the given mode.
+///
+/// To set a buffer-local mapping, use |nvim_buf_set_keymap()|.
+///
+/// Unlike |:map|, leading/trailing whitespace is accepted as part of the {lhs}
+/// or {rhs}. Empty {rhs} is |<Nop>|. |keycodes| are replaced as usual.
+///
+/// Example:
+/// <pre>
+/// call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true})
+/// </pre>
+///
+/// is equivalent to:
+/// <pre>
+/// nmap <nowait> <Space><NL> <Nop>
+/// </pre>
+///
+/// @param mode Mode short-name (map command prefix: "n", "i", "v", "x", …)
+/// or "!" for |:map!|, or empty string for |:map|.
+/// @param lhs Left-hand-side |{lhs}| of the mapping.
+/// @param rhs Right-hand-side |{rhs}| of the mapping.
+/// @param opts Optional parameters map. Accepts all |:map-arguments|
+/// as keys excluding |<buffer>| but including |noremap|.
+/// Values are Booleans. Unknown key is an error.
+/// @param[out] err Error details, if any.
+void nvim_set_keymap(String mode, String lhs, String rhs,
+ Dictionary opts, Error *err)
+ FUNC_API_SINCE(6)
+{
+ modify_keymap(-1, false, mode, lhs, rhs, opts, err);
+}
+
+/// Unmaps a global |mapping| for the given mode.
+///
+/// To unmap a buffer-local mapping, use |nvim_buf_del_keymap()|.
+///
+/// @see |nvim_set_keymap()|
+void nvim_del_keymap(String mode, String lhs, Error *err)
+ FUNC_API_SINCE(6)
+{
+ nvim_buf_del_keymap(-1, mode, lhs, err);
+}
+
/// Gets a map of global (non-buffer-local) Ex commands.
///
/// Currently only |user-commands| are supported, not builtin Ex commands.
@@ -2031,10 +2091,10 @@ Dictionary nvim__stats(void)
/// Gets a list of dictionaries representing attached UIs.
///
/// @return Array of UI dictionaries, each with these keys:
-/// - "height" requested height of the UI
-/// - "width" requested width of the UI
+/// - "height" Requested height of the UI
+/// - "width" Requested width of the UI
/// - "rgb" true if the UI uses RGB colors (false implies |cterm-colors|)
-/// - "ext_..." Requested UI extensions, see |ui-options|
+/// - "ext_..." Requested UI extensions, see |ui-option|
/// - "chan" Channel id of remote UI (not present for TUI)
Array nvim_list_uis(void)
FUNC_API_SINCE(4)
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index d628c517bb..cdb226b94d 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -5112,14 +5112,15 @@ chk_modeline(
*e = NUL; // truncate the set command
if (*s != NUL) { // skip over an empty "::"
+ const int secure_save = secure;
save_SID = current_SID;
current_SID = SID_MODELINE;
// Make sure no risky things are executed as a side effect.
- secure++;
+ secure = 1;
retval = do_set(s, OPT_MODELINE | OPT_LOCAL | flags);
- secure--;
+ secure = secure_save;
current_SID = save_SID;
if (retval == FAIL) { // stop if error found
break;
@@ -5211,8 +5212,8 @@ char_u *buf_spname(buf_T *buf)
return (char_u *)_(msg_qflist);
}
}
- /* There is no _file_ when 'buftype' is "nofile", b_sfname
- * contains the name as specified by the user */
+ // There is no _file_ when 'buftype' is "nofile", b_sfname
+ // contains the name as specified by the user.
if (bt_nofile(buf)) {
if (buf->b_sfname != NULL) {
return buf->b_sfname;
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 2e6f24d9c4..5e28a7b513 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -388,24 +388,25 @@ typedef struct {
* a window may have its own instance.
*/
typedef struct {
- hashtab_T b_keywtab; /* syntax keywords hash table */
- hashtab_T b_keywtab_ic; /* idem, ignore case */
- int b_syn_error; /* TRUE when error occurred in HL */
- int b_syn_ic; /* ignore case for :syn cmds */
- int b_syn_spell; /* SYNSPL_ values */
- garray_T b_syn_patterns; /* table for syntax patterns */
- garray_T b_syn_clusters; /* table for syntax clusters */
- int b_spell_cluster_id; /* @Spell cluster ID or 0 */
- int b_nospell_cluster_id; /* @NoSpell cluster ID or 0 */
- int b_syn_containedin; /* TRUE when there is an item with a
- "containedin" argument */
- int b_syn_sync_flags; /* flags about how to sync */
- short b_syn_sync_id; /* group to sync on */
- long b_syn_sync_minlines; /* minimal sync lines offset */
- long b_syn_sync_maxlines; /* maximal sync lines offset */
- long b_syn_sync_linebreaks; /* offset for multi-line pattern */
- char_u *b_syn_linecont_pat; /* line continuation pattern */
- regprog_T *b_syn_linecont_prog; /* line continuation program */
+ hashtab_T b_keywtab; // syntax keywords hash table
+ hashtab_T b_keywtab_ic; // idem, ignore case
+ int b_syn_error; // TRUE when error occurred in HL
+ bool b_syn_slow; // true when 'redrawtime' reached
+ int b_syn_ic; // ignore case for :syn cmds
+ int b_syn_spell; // SYNSPL_ values
+ garray_T b_syn_patterns; // table for syntax patterns
+ garray_T b_syn_clusters; // table for syntax clusters
+ int b_spell_cluster_id; // @Spell cluster ID or 0
+ int b_nospell_cluster_id; // @NoSpell cluster ID or 0
+ int b_syn_containedin; // TRUE when there is an item with a
+ // "containedin" argument
+ int b_syn_sync_flags; // flags about how to sync
+ int16_t b_syn_sync_id; // group to sync on
+ long b_syn_sync_minlines; // minimal sync lines offset
+ long b_syn_sync_maxlines; // maximal sync lines offset
+ long b_syn_sync_linebreaks; // offset for multi-line pattern
+ char_u *b_syn_linecont_pat; // line continuation pattern
+ regprog_T *b_syn_linecont_prog; // line continuation program
syn_time_T b_syn_linecont_time;
int b_syn_linecont_ic; /* ignore-case flag for above */
int b_syn_topgrp; /* for ":syntax include" */
@@ -998,6 +999,16 @@ typedef struct {
.relative = 0, .external = false, \
.focusable = true })
+// Structure to store last cursor position and topline. Used by check_lnums()
+// and reset_lnums().
+typedef struct
+{
+ int w_topline_save; // original topline value
+ int w_topline_corr; // corrected topline value
+ pos_T w_cursor_save; // original cursor position
+ pos_T w_cursor_corr; // corrected cursor position
+} pos_save_T;
+
/// Structure which contains all information that belongs to a window.
///
/// All row numbers are relative to the start of the window, except w_winrow.
@@ -1090,17 +1101,18 @@ struct window_S {
colnr_T w_skipcol; /* starting column when a single line
doesn't fit in the window */
- /*
- * Layout of the window in the screen.
- * May need to add "msg_scrolled" to "w_winrow" in rare situations.
- */
- int w_winrow; /* first row of window in screen */
- int w_height; /* number of rows in window, excluding
- status/command line(s) */
- int w_status_height; /* number of status lines (0 or 1) */
- int w_wincol; /* Leftmost column of window in screen. */
- int w_width; /* Width of window, excluding separation. */
- int w_vsep_width; /* Number of separator columns (0 or 1). */
+ //
+ // Layout of the window in the screen.
+ // May need to add "msg_scrolled" to "w_winrow" in rare situations.
+ //
+ int w_winrow; // first row of window in screen
+ int w_height; // number of rows in window, excluding
+ // status/command line(s)
+ int w_status_height; // number of status lines (0 or 1)
+ int w_wincol; // Leftmost column of window in screen.
+ int w_width; // Width of window, excluding separation.
+ int w_vsep_width; // Number of separator columns (0 or 1).
+ pos_save_T w_save_cursor; // backup of cursor pos and topline
// inner size of window, which can be overridden by external UI
int w_height_inner;
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 9f88f1e393..bd61925b0d 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -3935,7 +3935,7 @@ static int eval6(char_u **arg, typval_T *rettv, int evaluate, int want_string)
int op;
varnumber_T n1, n2;
bool use_float = false;
- float_T f1 = 0, f2;
+ float_T f1 = 0, f2 = 0;
bool error = false;
/*
@@ -5221,7 +5221,7 @@ bool garbage_collect(bool testing)
(void)garbage_collect(testing);
}
} else if (p_verbose > 0) {
- verb_msg((char_u *)_(
+ verb_msg(_(
"Not enough memory to set references, garbage collection aborted!"));
}
#undef ABORTING
@@ -6877,6 +6877,27 @@ static void assert_equal_common(typval_T *argvars, assert_type_T atype)
}
}
+static void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *const cmd = tv_get_string_chk(&argvars[0]);
+ garray_T ga;
+
+ called_vim_beep = false;
+ suppress_errthrow = true;
+ emsg_silent = false;
+ do_cmdline_cmd(cmd);
+ if (!called_vim_beep) {
+ prepare_assert_error(&ga);
+ ga_concat(&ga, (const char_u *)"command did not beep: ");
+ ga_concat(&ga, (const char_u *)cmd);
+ assert_error(&ga);
+ ga_clear(&ga);
+ }
+
+ suppress_errthrow = false;
+ emsg_on_display = false;
+}
+
// "assert_equal(expected, actual[, msg])" function
static void f_assert_equal(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
@@ -10096,7 +10117,7 @@ static void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
// match added with matchaddpos()
for (i = 0; i < MAXPOSMATCH; i++) {
llpos_T *llpos;
- char buf[6];
+ char buf[30]; // use 30 to avoid compiler warning
llpos = &cur->pos.pos[i];
if (llpos->lnum == 0) {
@@ -10383,6 +10404,23 @@ static void f_gettabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
getwinvar(argvars, rettv, 1);
}
+// "gettagstack()" function
+static void f_gettagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ win_T *wp = curwin; // default is current window
+
+ tv_dict_alloc_ret(rettv);
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ wp = find_win_by_nr_or_id(&argvars[0]);
+ if (wp == NULL) {
+ return;
+ }
+ }
+
+ get_tagstack(wp, rettv->vval.v_dict);
+}
+
/// Returns information about a window as a dictionary.
static dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr)
{
@@ -10453,6 +10491,14 @@ static void f_win_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_wincol + 1);
}
+// "getwinpos({timeout})" function
+static void f_getwinpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_list_alloc_ret(rettv, 2);
+ tv_list_append_number(rettv->vval.v_list, -1);
+ tv_list_append_number(rettv->vval.v_list, -1);
+}
+
/*
* "getwinposx()" function
*/
@@ -11445,25 +11491,21 @@ static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr)
static garray_T ga_userinput = {0, 0, sizeof(tasave_T), 4, NULL};
-/*
- * "inputrestore()" function
- */
+/// "inputrestore()" function
static void f_inputrestore(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
if (!GA_EMPTY(&ga_userinput)) {
- --ga_userinput.ga_len;
+ ga_userinput.ga_len--;
restore_typeahead((tasave_T *)(ga_userinput.ga_data)
- + ga_userinput.ga_len);
- /* default return is zero == OK */
+ + ga_userinput.ga_len);
+ // default return is zero == OK
} else if (p_verbose > 1) {
- verb_msg((char_u *)_("called inputrestore() more often than inputsave()"));
- rettv->vval.v_number = 1; /* Failed */
+ verb_msg(_("called inputrestore() more often than inputsave()"));
+ rettv->vval.v_number = 1; // Failed
}
}
-/*
- * "inputsave()" function
- */
+/// "inputsave()" function
static void f_inputsave(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
// Add an entry to the stack of typeahead storage.
@@ -11471,9 +11513,7 @@ static void f_inputsave(typval_T *argvars, typval_T *rettv, FunPtr fptr)
save_typeahead(p);
}
-/*
- * "inputsecret()" function
- */
+/// "inputsecret()" function
static void f_inputsecret(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
cmdline_star++;
@@ -12840,33 +12880,36 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
char buf[NUMBUFLEN];
const char *const dir = tv_get_string_buf(&argvars[0], buf);
if (*dir == NUL) {
- rettv->vval.v_number = FAIL;
- } else {
- if (*path_tail((char_u *)dir) == NUL) {
- // Remove trailing slashes.
- *path_tail_with_sep((char_u *)dir) = NUL;
- }
+ return;
+ }
- if (argvars[1].v_type != VAR_UNKNOWN) {
- if (argvars[2].v_type != VAR_UNKNOWN) {
- prot = tv_get_number_chk(&argvars[2], NULL);
- }
- if (prot != -1 && strcmp(tv_get_string(&argvars[1]), "p") == 0) {
- char *failed_dir;
- int ret = os_mkdir_recurse(dir, prot, &failed_dir);
- if (ret != 0) {
- EMSG3(_(e_mkdir), failed_dir, os_strerror(ret));
- xfree(failed_dir);
- rettv->vval.v_number = FAIL;
- return;
- } else {
- rettv->vval.v_number = OK;
- return;
- }
+ if (*path_tail((char_u *)dir) == NUL) {
+ // Remove trailing slashes.
+ *path_tail_with_sep((char_u *)dir) = NUL;
+ }
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ prot = tv_get_number_chk(&argvars[2], NULL);
+ if (prot == -1) {
+ return;
+ }
+ }
+ if (strcmp(tv_get_string(&argvars[1]), "p") == 0) {
+ char *failed_dir;
+ int ret = os_mkdir_recurse(dir, prot, &failed_dir);
+ if (ret != 0) {
+ EMSG3(_(e_mkdir), failed_dir, os_strerror(ret));
+ xfree(failed_dir);
+ rettv->vval.v_number = FAIL;
+ return;
+ } else {
+ rettv->vval.v_number = OK;
+ return;
}
}
- rettv->vval.v_number = prot == -1 ? FAIL : vim_mkdir_emsg(dir, prot);
}
+ rettv->vval.v_number = vim_mkdir_emsg(dir, prot);
}
/// "mode()" function
@@ -14849,7 +14892,7 @@ static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv)
skip_args:
if (!title) {
- title = (wp ? "setloclist()" : "setqflist()");
+ title = (wp ? ":setloclist()" : ":setqflist()");
}
recursive++;
@@ -14925,7 +14968,7 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
// match from matchaddpos()
for (i = 1; i < 9; i++) {
- char buf[5];
+ char buf[30]; // use 30 to avoid compiler warning
snprintf(buf, sizeof(buf), "pos%d", i);
dictitem_T *const pos_di = tv_dict_find(d, buf, -1);
if (pos_di != NULL) {
@@ -15175,6 +15218,60 @@ static void f_settabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
setwinvar(argvars, rettv, 1);
}
+// "settagstack()" function
+static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ static char *e_invact2 = N_("E962: Invalid action: '%s'");
+ win_T *wp;
+ dict_T *d;
+ int action = 'r';
+
+ rettv->vval.v_number = -1;
+
+ // first argument: window number or id
+ wp = find_win_by_nr_or_id(&argvars[0]);
+ if (wp == NULL) {
+ return;
+ }
+
+ // second argument: dict with items to set in the tag stack
+ if (argvars[1].v_type != VAR_DICT) {
+ EMSG(_(e_dictreq));
+ return;
+ }
+ d = argvars[1].vval.v_dict;
+ if (d == NULL) {
+ return;
+ }
+
+ // third argument: action - 'a' for append and 'r' for replace.
+ // default is to replace the stack.
+ if (argvars[2].v_type == VAR_UNKNOWN) {
+ action = 'r';
+ } else if (argvars[2].v_type == VAR_STRING) {
+ const char *actstr;
+ actstr = tv_get_string_chk(&argvars[2]);
+ if (actstr == NULL) {
+ return;
+ }
+ if ((*actstr == 'r' || *actstr == 'a') && actstr[1] == NUL) {
+ action = *actstr;
+ } else {
+ EMSG2(_(e_invact2), actstr);
+ return;
+ }
+ } else {
+ EMSG(_(e_stringreq));
+ return;
+ }
+
+ if (set_tagstack(wp, d, action) == OK) {
+ rettv->vval.v_number = 0;
+ } else {
+ EMSG(_(e_listreq));
+ }
+}
+
/*
* "setwinvar()" function
*/
@@ -16394,6 +16491,27 @@ static void f_substitute(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
+/// "swapinfo(swap_filename)" function
+static void f_swapinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ tv_dict_alloc_ret(rettv);
+ get_b0_dict(tv_get_string(argvars), rettv->vval.v_dict);
+}
+
+/// "swapname(expr)" function
+static void f_swapname(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ rettv->v_type = VAR_STRING;
+ buf_T *buf = tv_get_buf(&argvars[0], false);
+ if (buf == NULL
+ || buf->b_ml.ml_mfp == NULL
+ || buf->b_ml.ml_mfp->mf_fname == NULL) {
+ rettv->vval.v_string = NULL;
+ } else {
+ rettv->vval.v_string = vim_strsave(buf->b_ml.ml_mfp->mf_fname);
+ }
+}
+
/// "synID(lnum, col, trans)" function
static void f_synID(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
@@ -18176,7 +18294,7 @@ pos_T *var2fpos(const typval_T *const tv, const int dollar_lnum,
* Return FAIL when conversion is not possible, doesn't check the position for
* validity.
*/
-static int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp)
+int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp)
{
list_T *l;
long i = 0;
@@ -19693,16 +19811,15 @@ void ex_echo(exarg_T *eap)
{
char_u *arg = eap->arg;
typval_T rettv;
- bool needclr = true;
bool atstart = true;
const int did_emsg_before = did_emsg;
if (eap->skip)
++emsg_skip;
while (*arg != NUL && *arg != '|' && *arg != '\n' && !got_int) {
- /* If eval1() causes an error message the text from the command may
- * still need to be cleared. E.g., "echo 22,44". */
- need_clr_eos = needclr;
+ // If eval1() causes an error message the text from the command may
+ // still need to be cleared. E.g., "echo 22,44".
+ need_clr_eos = true;
{
char_u *p = arg;
@@ -19746,14 +19863,14 @@ void ex_echo(exarg_T *eap)
}
eap->nextcmd = check_nextcmd(arg);
- if (eap->skip)
- --emsg_skip;
- else {
- /* remove text that may still be there from the command */
- if (needclr)
- msg_clr_eos();
- if (eap->cmdidx == CMD_echo)
+ if (eap->skip) {
+ emsg_skip--;
+ } else {
+ // remove text that may still be there from the command
+ msg_clr_eos();
+ if (eap->cmdidx == CMD_echo) {
msg_end();
+ }
}
}
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 311897b3cd..a7f8461fc3 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -25,6 +25,7 @@ return {
arglistid={args={0, 2}},
argv={args={0, 1}},
asin={args=1, func="float_op_wrapper", data="&asin"}, -- WJMc
+ assert_beeps={args={1, 2}},
assert_equal={args={2, 3}},
assert_exception={args={1, 2}},
assert_fails={args={1, 2}},
@@ -141,7 +142,9 @@ return {
gettabinfo={args={0, 1}},
gettabvar={args={2, 3}},
gettabwinvar={args={3, 4}},
+ gettagstack={args={0, 1}},
getwininfo={args={0, 1}},
+ getwinpos={args={0, 1}},
getwinposx={},
getwinposy={},
getwinvar={args={2, 3}},
@@ -267,6 +270,7 @@ return {
setreg={args={2, 3}},
settabvar={args=3},
settabwinvar={args=4},
+ settagstack={args={2, 3}},
setwinvar={args=3},
sha256={args=1},
shellescape={args={1, 2}},
@@ -299,6 +303,8 @@ return {
strwidth={args=1},
submatch={args={1, 2}},
substitute={args=4},
+ swapinfo={args={1}},
+ swapname={args={1}},
synID={args=3},
synIDattr={args={2, 3}},
synIDtrans={args=1},
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index 912aecafec..ffb46abfea 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -1656,12 +1656,7 @@ int tv_dict_add_special(dict_T *const d, const char *const key,
/// Add a string entry to dictionary
///
-/// @param[out] d Dictionary to add entry to.
-/// @param[in] key Key to add.
-/// @param[in] key_len Key length.
-/// @param[in] val String to add.
-///
-/// @return OK in case of success, FAIL when key already exists.
+/// @see tv_dict_add_allocated_str
int tv_dict_add_str(dict_T *const d,
const char *const key, const size_t key_len,
const char *const val)
@@ -1672,6 +1667,27 @@ int tv_dict_add_str(dict_T *const d,
/// Add a string entry to dictionary
///
+/// @param[out] d Dictionary to add entry to.
+/// @param[in] key Key to add.
+/// @param[in] key_len Key length.
+/// @param[in] val String to add. NULL adds empty string.
+/// @param[in] len Use this many bytes from `val`, or -1 for whole string.
+///
+/// @return OK in case of success, FAIL when key already exists.
+int tv_dict_add_str_len(dict_T *const d,
+ const char *const key, const size_t key_len,
+ char *const val, int len)
+ FUNC_ATTR_NONNULL_ARG(1, 2)
+{
+ char *s = val ? val : "";
+ if (val != NULL) {
+ s = (len < 0) ? xstrdup(val) : xstrndup(val, (size_t)len);
+ }
+ return tv_dict_add_allocated_str(d, key, key_len, s);
+}
+
+/// Add a string entry to dictionary
+///
/// Unlike tv_dict_add_str() saves val to the new dictionary item in place of
/// creating a new copy.
///
diff --git a/src/nvim/event/rstream.c b/src/nvim/event/rstream.c
index 6812b342bf..19907d2e45 100644
--- a/src/nvim/event/rstream.c
+++ b/src/nvim/event/rstream.c
@@ -21,16 +21,14 @@
#endif
void rstream_init_fd(Loop *loop, Stream *stream, int fd, size_t bufsize)
- FUNC_ATTR_NONNULL_ARG(1)
- FUNC_ATTR_NONNULL_ARG(2)
+ FUNC_ATTR_NONNULL_ARG(1, 2)
{
stream_init(loop, stream, fd, NULL);
rstream_init(stream, bufsize);
}
void rstream_init_stream(Stream *stream, uv_stream_t *uvstream, size_t bufsize)
- FUNC_ATTR_NONNULL_ARG(1)
- FUNC_ATTR_NONNULL_ARG(2)
+ FUNC_ATTR_NONNULL_ARG(1, 2)
{
stream_init(NULL, stream, -1, uvstream);
rstream_init(stream, bufsize);
@@ -138,6 +136,10 @@ static void read_cb(uv_stream_t *uvstream, ssize_t cnt, const uv_buf_t *buf)
}
// Called by the by the 'idle' handle to emulate a reading event
+//
+// Idle callbacks are invoked once per event loop:
+// - to perform some very low priority activity.
+// - to keep the loop "alive" (so there is always an event to process)
static void fread_idle_cb(uv_idle_t *handle)
{
uv_fs_t req;
diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c
index ba25b76ec7..70b1d890d6 100644
--- a/src/nvim/event/stream.c
+++ b/src/nvim/event/stream.c
@@ -53,9 +53,19 @@ void stream_init(Loop *loop, Stream *stream, int fd, uv_stream_t *uvstream)
stream->uv.idle.data = stream;
} else {
assert(type == UV_NAMED_PIPE || type == UV_TTY);
+#ifdef WIN32
+ if (type == UV_TTY) {
+ uv_tty_init(&loop->uv, &stream->uv.tty, fd, 0);
+ uv_tty_set_mode(&stream->uv.tty, UV_TTY_MODE_RAW);
+ stream->uvstream = STRUCT_CAST(uv_stream_t, &stream->uv.tty);
+ } else {
+#endif
uv_pipe_init(&loop->uv, &stream->uv.pipe, 0);
uv_pipe_open(&stream->uv.pipe, fd);
stream->uvstream = STRUCT_CAST(uv_stream_t, &stream->uv.pipe);
+#ifdef WIN32
+ }
+#endif
}
}
diff --git a/src/nvim/event/stream.h b/src/nvim/event/stream.h
index 694dab6568..a5c33a66a2 100644
--- a/src/nvim/event/stream.h
+++ b/src/nvim/event/stream.h
@@ -36,6 +36,9 @@ struct stream {
uv_pipe_t pipe;
uv_tcp_t tcp;
uv_idle_t idle;
+#ifdef WIN32
+ uv_tty_t tty;
+#endif
} uv;
uv_stream_t *uvstream;
uv_buf_t uvbuf;
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 150e13efd5..560f4e5df2 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -855,7 +855,7 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
return FAIL;
for (extra = 0, l = line1; l <= line2; l++) {
str = vim_strsave(ml_get(l + extra));
- ml_append(dest + l - line1, str, (colnr_T)0, FALSE);
+ ml_append(dest + l - line1, str, (colnr_T)0, false);
xfree(str);
if (dest < line1)
extra++;
@@ -914,9 +914,9 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
if (u_save(line1 + extra - 1, line2 + extra + 1) == FAIL)
return FAIL;
- for (l = line1; l <= line2; l++)
- ml_delete(line1 + extra, TRUE);
-
+ for (l = line1; l <= line2; l++) {
+ ml_delete(line1 + extra, true);
+ }
if (!global_busy && num_lines > p_report) {
if (num_lines == 1)
MSG(_("1 line moved"));
@@ -982,7 +982,7 @@ void ex_copy(linenr_T line1, linenr_T line2, linenr_T n)
/* need to use vim_strsave() because the line will be unlocked within
* ml_append() */
p = vim_strsave(ml_get(line1));
- ml_append(curwin->w_cursor.lnum, p, (colnr_T)0, FALSE);
+ ml_append(curwin->w_cursor.lnum, p, (colnr_T)0, false);
xfree(p);
/* situation 2: skip already copied lines */
@@ -2720,7 +2720,7 @@ static int append_indent = 0; /* autoindent for first line */
void ex_append(exarg_T *eap)
{
char_u *theline;
- int did_undo = FALSE;
+ bool did_undo = false;
linenr_T lnum = eap->line2;
int indent = 0;
char_u *p;
@@ -2808,16 +2808,16 @@ void ex_append(exarg_T *eap)
if (p[0] == NUL)
theline[0] = NUL;
- did_undo = TRUE;
- ml_append(lnum, theline, (colnr_T)0, FALSE);
+ did_undo = true;
+ ml_append(lnum, theline, (colnr_T)0, false);
appended_lines_mark(lnum + (empty ? 1 : 0), 1L);
xfree(theline);
++lnum;
if (empty) {
- ml_delete(2L, FALSE);
- empty = FALSE;
+ ml_delete(2L, false);
+ empty = 0;
}
}
State = NORMAL;
@@ -2862,7 +2862,7 @@ void ex_change(exarg_T *eap)
for (lnum = eap->line2; lnum >= eap->line1; --lnum) {
if (curbuf->b_ml.ml_flags & ML_EMPTY) /* nothing to delete */
break;
- ml_delete(eap->line1, FALSE);
+ ml_delete(eap->line1, false);
}
/* make sure the cursor is not beyond the end of the file now */
@@ -3692,10 +3692,9 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
i = msg_scroll;
msg_scroll = 0; /* truncate msg when
needed */
- msg_no_more = TRUE;
- /* write message same highlighting as for
- * wait_return */
- smsg_attr(HL_ATTR(HLF_R),
+ msg_no_more = true;
+ msg_ext_set_kind("confirm_sub");
+ smsg_attr(HL_ATTR(HLF_R), // Same highlight as wait_return().
_("replace with %s (y/n/a/q/l/^E/^Y)?"), sub);
msg_no_more = FALSE;
msg_scroll = i;
@@ -3989,8 +3988,9 @@ skip:
++lnum;
if (u_savedel(lnum, nmatch_tl) != OK)
break;
- for (i = 0; i < nmatch_tl; ++i)
- ml_delete(lnum, (int)FALSE);
+ for (i = 0; i < nmatch_tl; i++) {
+ ml_delete(lnum, false);
+ }
mark_adjust(lnum, lnum + nmatch_tl - 1,
(long)MAXLNUM, -nmatch_tl, false);
if (subflags.do_ask) {
@@ -5154,10 +5154,11 @@ void fix_help_buffer(void)
}
convert_setup(&vc, NULL, NULL);
- ml_append(lnum, cp, (colnr_T)0, FALSE);
- if (cp != IObuff)
+ ml_append(lnum, cp, (colnr_T)0, false);
+ if (cp != IObuff) {
xfree(cp);
- ++lnum;
+ }
+ lnum++;
}
fclose(fd);
}
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index 15b735dc9e..40ff29d4a8 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -1298,7 +1298,12 @@ void dialog_changed(buf_T *buf, bool checkall)
{
char_u buff[DIALOG_MSG_SIZE];
int ret;
- exarg_T ea;
+ // Init ea pseudo-structure, this is needed for the check_overwrite()
+ // function.
+ exarg_T ea = {
+ .append = false,
+ .forceit = false,
+ };
dialog_msg(buff, _("Save changes to \"%s\"?"), buf->b_fname);
if (checkall) {
@@ -1307,10 +1312,6 @@ void dialog_changed(buf_T *buf, bool checkall)
ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, buff, 1);
}
- // Init ea pseudo-structure, this is needed for the check_overwrite()
- // function.
- ea.append = ea.forceit = false;
-
if (ret == VIM_YES) {
if (buf->b_fname != NULL
&& check_overwrite(&ea,
@@ -3403,13 +3404,18 @@ char_u *getsourceline(int c, void *cookie, int indent)
// Get the next line and concatenate it when it starts with a
// backslash. We always need to read the next line, keep it in
// sp->nextline.
+ // Also check for a comment in between continuation lines: "\ .
sp->nextline = get_one_sourceline(sp);
- if (sp->nextline != NULL && *(p = skipwhite(sp->nextline)) == '\\') {
+ if (sp->nextline != NULL
+ && (*(p = skipwhite(sp->nextline)) == '\\'
+ || (p[0] == '"' && p[1] == '\\' && p[2] == ' '))) {
garray_T ga;
ga_init(&ga, (int)sizeof(char_u), 400);
ga_concat(&ga, line);
- ga_concat(&ga, p + 1);
+ if (*p == '\\') {
+ ga_concat(&ga, p + 1);
+ }
for (;; ) {
xfree(sp->nextline);
sp->nextline = get_one_sourceline(sp);
@@ -3417,15 +3423,16 @@ char_u *getsourceline(int c, void *cookie, int indent)
break;
}
p = skipwhite(sp->nextline);
- if (*p != '\\') {
+ if (*p == '\\') {
+ // Adjust the growsize to the current length to speed up
+ // concatenating many lines.
+ if (ga.ga_len > 400) {
+ ga_set_growsize(&ga, (ga.ga_len > 8000) ? 8000 : ga.ga_len);
+ }
+ ga_concat(&ga, p + 1);
+ } else if (p[0] != '"' || p[1] != '\\' || p[2] != ' ') {
break;
}
- // Adjust the growsize to the current length to speed up
- // concatenating many lines.
- if (ga.ga_len > 400) {
- ga_set_growsize(&ga, (ga.ga_len > 8000) ? 8000 : ga.ga_len);
- }
- ga_concat(&ga, p + 1);
}
ga_append(&ga, NUL);
xfree(line);
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 9fc047dde9..9c4a3f389a 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -839,9 +839,10 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
sourcing_lnum = current_exception->throw_lnum;
current_exception->throw_name = NULL;
- discard_current_exception(); /* uses IObuff if 'verbose' */
- suppress_errthrow = TRUE;
- force_abort = TRUE;
+ discard_current_exception(); // uses IObuff if 'verbose'
+ suppress_errthrow = true;
+ force_abort = true;
+ msg_ext_set_kind("emsg"); // kind=emsg for :throw, exceptions. #9993
if (messages != NULL) {
do {
@@ -4505,8 +4506,8 @@ static int get_tabpage_arg(exarg_T *eap)
tab_number = 0;
} else {
tab_number = eap->line2;
- if (!unaccept_arg0 && **eap->cmdlinep == '-') {
- --tab_number;
+ if (!unaccept_arg0 && *skipwhite(*eap->cmdlinep) == '-') {
+ tab_number--;
if (tab_number < unaccept_arg0) {
eap->errmsg = e_invarg;
}
@@ -7213,10 +7214,11 @@ static void ex_read(exarg_T *eap)
else
lnum = 1;
if (*ml_get(lnum) == NUL && u_savedel(lnum, 1L) == OK) {
- ml_delete(lnum, FALSE);
+ ml_delete(lnum, false);
if (curwin->w_cursor.lnum > 1
- && curwin->w_cursor.lnum >= lnum)
- --curwin->w_cursor.lnum;
+ && curwin->w_cursor.lnum >= lnum) {
+ curwin->w_cursor.lnum--;
+ }
deleted_lines_mark(lnum, 1L);
}
}
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 3de7204fde..b16023b0ec 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -366,7 +366,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
// redraw the statusline for statuslines that display the current mode
// using the mode() function.
- if (KeyTyped && msg_scrolled == 0) {
+ if (!cmd_silent && msg_scrolled == 0) {
curwin->w_redr_status = true;
redraw_statuslines();
}
@@ -1389,6 +1389,7 @@ static int command_line_handle_key(CommandLineState *s)
}
}
}
+ ccline.special_char = NUL;
redrawcmd();
return command_line_changed(s);
@@ -6109,9 +6110,9 @@ static int open_cmdwin(void)
do {
if (++i == hislen)
i = 0;
- if (history[histtype][i].hisstr != NULL)
- ml_append(lnum++, history[histtype][i].hisstr,
- (colnr_T)0, FALSE);
+ if (history[histtype][i].hisstr != NULL) {
+ ml_append(lnum++, history[histtype][i].hisstr, (colnr_T)0, false);
+ }
} while (i != hisidx[histtype]);
}
}
diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c
index e358f0218e..05611fb8ba 100644
--- a/src/nvim/file_search.c
+++ b/src/nvim/file_search.c
@@ -687,20 +687,24 @@ char_u *vim_findfile(void *search_ctx_arg)
if (!vim_isAbsName(stackp->ffs_fix_path)
&& search_ctx->ffsc_start_dir) {
if (STRLEN(search_ctx->ffsc_start_dir) + 1 >= MAXPATHL) {
+ ff_free_stack_element(stackp);
goto fail;
}
STRCPY(file_path, search_ctx->ffsc_start_dir);
if (!add_pathsep((char *)file_path)) {
+ ff_free_stack_element(stackp);
goto fail;
}
}
// append the fix part of the search path
if (STRLEN(file_path) + STRLEN(stackp->ffs_fix_path) + 1 >= MAXPATHL) {
+ ff_free_stack_element(stackp);
goto fail;
}
STRCAT(file_path, stackp->ffs_fix_path);
if (!add_pathsep((char *)file_path)) {
+ ff_free_stack_element(stackp);
goto fail;
}
@@ -715,6 +719,7 @@ char_u *vim_findfile(void *search_ctx_arg)
if (*p > 0) {
(*p)--;
if (len + 1 >= MAXPATHL) {
+ ff_free_stack_element(stackp);
goto fail;
}
file_path[len++] = '*';
@@ -743,6 +748,7 @@ char_u *vim_findfile(void *search_ctx_arg)
while (*rest_of_wildcards
&& !vim_ispathsep(*rest_of_wildcards)) {
if (len + 1 >= MAXPATHL) {
+ ff_free_stack_element(stackp);
goto fail;
}
file_path[len++] = *rest_of_wildcards++;
@@ -792,10 +798,12 @@ char_u *vim_findfile(void *search_ctx_arg)
// prepare the filename to be checked for existence below
if (STRLEN(stackp->ffs_filearray[i]) + 1
+ STRLEN(search_ctx->ffsc_file_to_search) >= MAXPATHL) {
+ ff_free_stack_element(stackp);
goto fail;
}
STRCPY(file_path, stackp->ffs_filearray[i]);
if (!add_pathsep((char *)file_path)) {
+ ff_free_stack_element(stackp);
goto fail;
}
STRCAT(file_path, search_ctx->ffsc_file_to_search);
@@ -964,7 +972,6 @@ char_u *vim_findfile(void *search_ctx_arg)
}
fail:
- ff_free_stack_element(stackp);
xfree(file_path);
return NULL;
}
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index b2840c9402..507bf3c032 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -11,6 +11,7 @@
#include <fcntl.h>
#include "nvim/vim.h"
+#include "nvim/api/private/handle.h"
#include "nvim/ascii.h"
#include "nvim/fileio.h"
#include "nvim/buffer.h"
@@ -47,6 +48,7 @@
#include "nvim/state.h"
#include "nvim/strings.h"
#include "nvim/ui.h"
+#include "nvim/ui_compositor.h"
#include "nvim/types.h"
#include "nvim/undo.h"
#include "nvim/window.h"
@@ -308,29 +310,30 @@ readfile (
#ifdef UNIX
int swap_mode = -1; /* protection bits for swap file */
#endif
- int fileformat = 0; /* end-of-line format */
- int keep_fileformat = FALSE;
+ int fileformat = 0; // end-of-line format
+ bool keep_fileformat = false;
+ FileInfo file_info;
int file_readonly;
linenr_T skip_count = 0;
linenr_T read_count = 0;
int msg_save = msg_scroll;
linenr_T read_no_eol_lnum = 0; // non-zero lnum when last line of
// last read was missing the eol
- int file_rewind = false;
+ bool file_rewind = false;
int can_retry;
- linenr_T conv_error = 0; /* line nr with conversion error */
- linenr_T illegal_byte = 0; /* line nr with illegal byte */
- int keep_dest_enc = FALSE; /* don't retry when char doesn't fit
- in destination encoding */
+ linenr_T conv_error = 0; // line nr with conversion error
+ linenr_T illegal_byte = 0; // line nr with illegal byte
+ bool keep_dest_enc = false; // don't retry when char doesn't fit
+ // in destination encoding
int bad_char_behavior = BAD_REPLACE;
/* BAD_KEEP, BAD_DROP or character to
* replace with */
char_u *tmpname = NULL; /* name of 'charconvert' output file */
int fio_flags = 0;
- char_u *fenc; /* fileencoding to use */
- int fenc_alloced; /* fenc_next is in allocated memory */
- char_u *fenc_next = NULL; /* next item in 'fencs' or NULL */
- int advance_fenc = FALSE;
+ char_u *fenc; // fileencoding to use
+ bool fenc_alloced; // fenc_next is in allocated memory
+ char_u *fenc_next = NULL; // next item in 'fencs' or NULL
+ bool advance_fenc = false;
long real_size = 0;
# ifdef USE_ICONV
iconv_t iconv_fd = (iconv_t)-1; /* descriptor for iconv() or -1 */
@@ -481,7 +484,6 @@ readfile (
if (newfile && !read_stdin && !read_buffer && !read_fifo) {
// Remember time of file.
- FileInfo file_info;
if (os_fileinfo((char *)fname, &file_info)) {
buf_store_file_info(curbuf, &file_info);
curbuf->b_mtime_read = curbuf->b_mtime;
@@ -627,13 +629,30 @@ readfile (
// Set swap file protection bits after creating it.
if (swap_mode > 0 && curbuf->b_ml.ml_mfp != NULL
&& curbuf->b_ml.ml_mfp->mf_fname != NULL) {
- (void)os_setperm((const char *)curbuf->b_ml.ml_mfp->mf_fname,
- (long)swap_mode);
+ const char *swap_fname = (const char *)curbuf->b_ml.ml_mfp->mf_fname;
+
+ // If the group-read bit is set but not the world-read bit, then
+ // the group must be equal to the group of the original file. If
+ // we can't make that happen then reset the group-read bit. This
+ // avoids making the swap file readable to more users when the
+ // primary group of the user is too permissive.
+ if ((swap_mode & 044) == 040) {
+ FileInfo swap_info;
+
+ if (os_fileinfo(swap_fname, &swap_info)
+ && file_info.stat.st_gid != swap_info.stat.st_gid
+ && os_fchown(curbuf->b_ml.ml_mfp->mf_fd, -1, file_info.stat.st_gid)
+ == -1) {
+ swap_mode &= 0600;
+ }
+ }
+
+ (void)os_setperm(swap_fname, swap_mode);
}
#endif
}
- /* If "Quit" selected at ATTENTION dialog, don't load the file */
+ // If "Quit" selected at ATTENTION dialog, don't load the file.
if (swap_exists_action == SEA_QUIT) {
if (!read_buffer && !read_stdin)
close(fd);
@@ -747,11 +766,11 @@ readfile (
*/
if (eap != NULL && eap->force_enc != 0) {
fenc = enc_canonize(eap->cmd + eap->force_enc);
- fenc_alloced = TRUE;
- keep_dest_enc = TRUE;
+ fenc_alloced = true;
+ keep_dest_enc = true;
} else if (curbuf->b_p_bin) {
- fenc = (char_u *)""; /* binary: don't convert */
- fenc_alloced = FALSE;
+ fenc = (char_u *)""; // binary: don't convert
+ fenc_alloced = false;
} else if (curbuf->b_help) {
// Help files are either utf-8 or latin1. Try utf-8 first, if this
// fails it must be latin1.
@@ -762,12 +781,12 @@ readfile (
fenc_alloced = false;
} else if (*p_fencs == NUL) {
- fenc = curbuf->b_p_fenc; /* use format from buffer */
- fenc_alloced = FALSE;
+ fenc = curbuf->b_p_fenc; // use format from buffer
+ fenc_alloced = false;
} else {
fenc_next = p_fencs; /* try items in 'fileencodings' */
fenc = next_fenc(&fenc_next);
- fenc_alloced = TRUE;
+ fenc_alloced = true;
}
/*
@@ -800,10 +819,11 @@ retry:
error = true;
goto failed;
}
- /* Delete the previously read lines. */
- while (lnum > from)
- ml_delete(lnum--, FALSE);
- file_rewind = FALSE;
+ // Delete the previously read lines.
+ while (lnum > from) {
+ ml_delete(lnum--, false);
+ }
+ file_rewind = false;
if (set_options) {
curbuf->b_p_bomb = FALSE;
curbuf->b_start_bomb = FALSE;
@@ -815,9 +835,9 @@ retry:
* When retrying with another "fenc" and the first time "fileformat"
* will be reset.
*/
- if (keep_fileformat)
- keep_fileformat = FALSE;
- else {
+ if (keep_fileformat) {
+ keep_fileformat = false;
+ } else {
if (eap != NULL && eap->force_ff != 0) {
fileformat = get_fileformat_force(curbuf, eap);
try_unix = try_dos = try_mac = FALSE;
@@ -841,7 +861,7 @@ retry:
/*
* Try the next entry in 'fileencodings'.
*/
- advance_fenc = FALSE;
+ advance_fenc = false;
if (eap != NULL && eap->force_enc != 0) {
/* Conversion given with "++cc=" wasn't possible, read
@@ -851,7 +871,7 @@ retry:
if (fenc_alloced)
xfree(fenc);
fenc = (char_u *)"";
- fenc_alloced = FALSE;
+ fenc_alloced = false;
} else {
if (fenc_alloced)
xfree(fenc);
@@ -860,7 +880,7 @@ retry:
fenc_alloced = (fenc_next != NULL);
} else {
fenc = (char_u *)"";
- fenc_alloced = FALSE;
+ fenc_alloced = false;
}
}
if (tmpname != NULL) {
@@ -926,8 +946,8 @@ retry:
if (tmpname == NULL) {
tmpname = readfile_charconvert(fname, fenc, &fd);
if (tmpname == NULL) {
- /* Conversion failed. Try another one. */
- advance_fenc = TRUE;
+ // Conversion failed. Try another one.
+ advance_fenc = true;
if (fd < 0) {
/* Re-opening the original file failed! */
EMSG(_("E202: Conversion made file unreadable!"));
@@ -945,7 +965,7 @@ retry:
) {
/* Conversion wanted but we can't.
* Try the next conversion in 'fileencodings' */
- advance_fenc = TRUE;
+ advance_fenc = true;
goto retry;
}
}
@@ -1180,14 +1200,14 @@ retry:
if (fio_flags == FIO_UCSBOM) {
if (ccname == NULL) {
- /* No BOM detected: retry with next encoding. */
- advance_fenc = TRUE;
+ // No BOM detected: retry with next encoding.
+ advance_fenc = true;
} else {
/* BOM detected: set "fenc" and jump back */
if (fenc_alloced)
xfree(fenc);
fenc = ccname;
- fenc_alloced = FALSE;
+ fenc_alloced = false;
}
/* retry reading without getting new bytes or rewinding */
skip_read = TRUE;
@@ -1512,9 +1532,9 @@ rewind_retry:
did_iconv = TRUE;
else
# endif
- /* use next item from 'fileencodings' */
- advance_fenc = TRUE;
- file_rewind = TRUE;
+ // use next item from 'fileencodings'
+ advance_fenc = true;
+ file_rewind = true;
goto retry;
}
}
@@ -1648,8 +1668,8 @@ rewind_retry:
fileformat = EOL_UNIX;
if (set_options)
set_fileformat(EOL_UNIX, OPT_LOCAL);
- file_rewind = TRUE;
- keep_fileformat = TRUE;
+ file_rewind = true;
+ keep_fileformat = true;
goto retry;
}
ff_error = EOL_DOS;
@@ -1760,8 +1780,8 @@ failed:
if (!recoverymode) {
/* need to delete the last line, which comes from the empty buffer */
if (newfile && wasempty && !(curbuf->b_ml.ml_flags & ML_EMPTY)) {
- ml_delete(curbuf->b_ml.ml_line_count, FALSE);
- --linecnt;
+ ml_delete(curbuf->b_ml.ml_line_count, false);
+ linecnt--;
}
linecnt = curbuf->b_ml.ml_line_count - linecnt;
if (filesize == 0)
@@ -4894,9 +4914,9 @@ static int move_lines(buf_T *frombuf, buf_T *tobuf)
/* Copy the lines in "frombuf" to "tobuf". */
curbuf = tobuf;
- for (lnum = 1; lnum <= frombuf->b_ml.ml_line_count; ++lnum) {
- p = vim_strsave(ml_get_buf(frombuf, lnum, FALSE));
- if (ml_append(lnum - 1, p, 0, FALSE) == FAIL) {
+ for (lnum = 1; lnum <= frombuf->b_ml.ml_line_count; lnum++) {
+ p = vim_strsave(ml_get_buf(frombuf, lnum, false));
+ if (ml_append(lnum - 1, p, 0, false) == FAIL) {
xfree(p);
retval = FAIL;
break;
@@ -4907,13 +4927,14 @@ static int move_lines(buf_T *frombuf, buf_T *tobuf)
/* Delete all the lines in "frombuf". */
if (retval != FAIL) {
curbuf = frombuf;
- for (lnum = curbuf->b_ml.ml_line_count; lnum > 0; --lnum)
- if (ml_delete(lnum, FALSE) == FAIL) {
- /* Oops! We could try putting back the saved lines, but that
- * might fail again... */
+ for (lnum = curbuf->b_ml.ml_line_count; lnum > 0; lnum--) {
+ if (ml_delete(lnum, false) == FAIL) {
+ // Oops! We could try putting back the saved lines, but that
+ // might fail again...
retval = FAIL;
break;
}
+ }
}
curbuf = tbuf;
@@ -6501,21 +6522,17 @@ bool check_nomodeline(char_u **argp)
return true;
}
-/*
- * Prepare for executing autocommands for (hidden) buffer "buf".
- * Search for a visible window containing the current buffer. If there isn't
- * one then use "aucmd_win".
- * Set "curbuf" and "curwin" to match "buf".
- */
-void
-aucmd_prepbuf (
- aco_save_T *aco, /* structure to save values in */
- buf_T *buf /* new curbuf */
-)
+/// Prepare for executing autocommands for (hidden) buffer `buf`.
+/// If the current buffer is not in any visible window, put it in a temporary
+/// floating window `aucmd_win`.
+/// Set `curbuf` and `curwin` to match `buf`.
+///
+/// @param aco structure to save values in
+/// @param buf new curbuf
+void aucmd_prepbuf(aco_save_T *aco, buf_T *buf)
{
- win_T *win;
- int save_ea;
- int save_acd;
+ win_T *win;
+ bool need_append = true; // Append `aucmd_win` to the window list.
/* Find a window that is for the new buffer */
if (buf == curbuf) { /* be quick when buf is curbuf */
@@ -6530,9 +6547,10 @@ aucmd_prepbuf (
}
}
- /* Allocate "aucmd_win" when needed. */
+ // Allocate the `aucmd_win` dummy floating window.
if (win == NULL && aucmd_win == NULL) {
win_alloc_aucmd_win();
+ need_append = false;
}
if (win == NULL && aucmd_win_used)
/* Strange recursive autocommand, fall back to using the current
@@ -6567,21 +6585,16 @@ aucmd_prepbuf (
aco->globaldir = globaldir;
globaldir = NULL;
-
- /* Split the current window, put the aucmd_win in the upper half.
- * We don't want the BufEnter or WinEnter autocommands. */
- block_autocmds();
- make_snapshot(SNAP_AUCMD_IDX);
- save_ea = p_ea;
- p_ea = false;
-
- /* Prevent chdir() call in win_enter_ext(), through do_autochdir(). */
- save_acd = p_acd;
+ block_autocmds(); // We don't want BufEnter/WinEnter autocommands.
+ if (need_append) {
+ win_append(lastwin, aucmd_win);
+ handle_register_window(aucmd_win);
+ win_config_float(aucmd_win, aucmd_win->w_float_config);
+ }
+ // Prevent chdir() call in win_enter_ext(), through do_autochdir()
+ int save_acd = p_acd;
p_acd = false;
-
- (void)win_split_ins(0, WSP_TOP, aucmd_win, 0);
- (void)win_comp_pos(); /* recompute window positions */
- p_ea = save_ea;
+ win_enter(aucmd_win, false);
p_acd = save_acd;
unblock_autocmds();
curwin = aucmd_win;
@@ -6597,8 +6610,6 @@ aucmd_prepbuf (
/// @param aco structure holding saved values
void aucmd_restbuf(aco_save_T *aco)
{
- int dummy;
-
if (aco->use_aucmd_win) {
curbuf->b_nwindows--;
// Find "aucmd_win", it can't be closed, but it may be in another tab page.
@@ -6617,9 +6628,14 @@ void aucmd_restbuf(aco_save_T *aco)
}
win_found:
- // Remove the window and frame from the tree of frames.
- (void)winframe_remove(curwin, &dummy, NULL);
win_remove(curwin, NULL);
+ handle_unregister_window(curwin);
+ if (curwin->w_grid.chars != NULL) {
+ ui_comp_remove_grid(&curwin->w_grid);
+ ui_call_win_hide(curwin->w_grid.handle);
+ grid_free(&curwin->w_grid);
+ }
+
aucmd_win_used = false;
last_status(false); // may need to remove last status line
@@ -6628,8 +6644,6 @@ win_found:
close_tabpage(curtab);
}
- restore_snapshot(SNAP_AUCMD_IDX, false);
- (void)win_comp_pos(); // recompute window positions
unblock_autocmds();
if (win_valid(aco->save_curwin)) {
@@ -7069,6 +7083,8 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io,
do_cmdline(NULL, getnextac, (void *)&patcmd,
DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT);
+ reset_lnums(); // restore cursor and topline, unless they were changed
+
if (eap != NULL) {
(void)set_cmdarg(NULL, save_cmdarg);
set_vim_var_nr(VV_CMDBANG, save_cmdbang);
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 0020b57482..f8c7dc613b 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -2502,286 +2502,334 @@ int fix_input_buffer(char_u *buf, int len)
return len;
}
-/*
- * map[!] : show all key mappings
- * map[!] {lhs} : show key mapping for {lhs}
- * map[!] {lhs} {rhs} : set key mapping for {lhs} to {rhs}
- * noremap[!] {lhs} {rhs} : same, but no remapping for {rhs}
- * unmap[!] {lhs} : remove key mapping for {lhs}
- * abbr : show all abbreviations
- * abbr {lhs} : show abbreviations for {lhs}
- * abbr {lhs} {rhs} : set abbreviation for {lhs} to {rhs}
- * noreabbr {lhs} {rhs} : same, but no remapping for {rhs}
- * unabbr {lhs} : remove abbreviation for {lhs}
- *
- * maptype: 0 for :map, 1 for :unmap, 2 for noremap.
- *
- * arg is pointer to any arguments. Note: arg cannot be a read-only string,
- * it will be modified.
- *
- * for :map mode is NORMAL + VISUAL + SELECTMODE + OP_PENDING
- * for :map! mode is INSERT + CMDLINE
- * for :cmap mode is CMDLINE
- * for :imap mode is INSERT
- * for :lmap mode is LANGMAP
- * for :nmap mode is NORMAL
- * for :vmap mode is VISUAL + SELECTMODE
- * for :xmap mode is VISUAL
- * for :smap mode is SELECTMODE
- * for :omap mode is OP_PENDING
- * for :tmap mode is TERM_FOCUS
- *
- * for :abbr mode is INSERT + CMDLINE
- * for :iabbr mode is INSERT
- * for :cabbr mode is CMDLINE
- *
- * Return 0 for success
- * 1 for invalid arguments
- * 2 for no match
- * 4 for out of mem (deprecated, WON'T HAPPEN)
- * 5 for entry not unique
- */
-int
-do_map (
- int maptype,
- char_u *arg,
- int mode,
- int abbrev /* not a mapping but an abbreviation */
-)
+/// Replace termcodes in the given LHS and RHS and store the results into the
+/// `lhs` and `rhs` of the given @ref MapArguments struct.
+///
+/// `rhs` and `orig_rhs` will both point to new allocated buffers. `orig_rhs`
+/// will hold a copy of the given `orig_rhs`.
+///
+/// The `*_len` variables will be set appropriately. If the length of
+/// the final `lhs` exceeds `MAXMAPLEN`, `lhs_len` will be set equal to the
+/// original larger length and `lhs` will be truncated.
+///
+/// If RHS is equal to "<Nop>", `rhs` will be the empty string, `rhs_len`
+/// will be zero, and `rhs_is_noop` will be set to true.
+///
+/// Any memory allocated by @ref replace_termcodes is freed before this function
+/// returns.
+///
+/// @param[in] orig_lhs Original mapping LHS, with characters to replace.
+/// @param[in] orig_lhs_len `strlen` of orig_lhs.
+/// @param[in] orig_rhs Original mapping RHS, with characters to replace.
+/// @param[in] orig_rhs_len `strlen` of orig_rhs.
+/// @param[in] cpo_flags See param docs for @ref replace_termcodes.
+/// @param[out] mapargs MapArguments struct holding the replaced strings.
+void set_maparg_lhs_rhs(const char_u *orig_lhs, const size_t orig_lhs_len,
+ const char_u *orig_rhs, const size_t orig_rhs_len,
+ int cpo_flags, MapArguments *mapargs)
{
- char_u *keys;
- mapblock_T *mp, **mpp;
- char_u *rhs;
- char_u *p;
- int n;
- int len = 0; /* init for GCC */
- int hasarg;
- int haskey;
- int did_it = FALSE;
- int did_local = FALSE;
- int round;
- char_u *keys_buf = NULL;
- char_u *arg_buf = NULL;
- int retval = 0;
- int do_backslash;
- int hash;
- int new_hash;
- mapblock_T **abbr_table;
- mapblock_T **map_table;
- bool unique = false;
- bool nowait = false;
- bool silent = false;
- bool expr = false;
- int noremap;
- char_u *orig_rhs;
+ char_u *lhs_buf = NULL;
+ char_u *rhs_buf = NULL;
- keys = arg;
- map_table = maphash;
- abbr_table = &first_abbr;
+ // If mapping has been given as ^V<C_UP> say, then replace the term codes
+ // with the appropriate two bytes. If it is a shifted special key, unshift
+ // it too, giving another two bytes.
+ //
+ // replace_termcodes() may move the result to allocated memory, which
+ // needs to be freed later (*lhs_buf and *rhs_buf).
+ // replace_termcodes() also removes CTRL-Vs and sometimes backslashes.
+ char_u *replaced = replace_termcodes(orig_lhs, orig_lhs_len, &lhs_buf,
+ true, true, true, cpo_flags);
+ mapargs->lhs_len = STRLEN(replaced);
+ xstrlcpy((char *)mapargs->lhs, (char *)replaced, sizeof(mapargs->lhs));
+
+ mapargs->orig_rhs_len = orig_rhs_len;
+ mapargs->orig_rhs = xcalloc(mapargs->orig_rhs_len + 1, sizeof(char_u));
+ xstrlcpy((char *)mapargs->orig_rhs, (char *)orig_rhs,
+ mapargs->orig_rhs_len + 1);
+
+ if (STRICMP(orig_rhs, "<nop>") == 0) { // "<Nop>" means nothing
+ mapargs->rhs = xcalloc(1, sizeof(char_u)); // single null-char
+ mapargs->rhs_len = 0;
+ mapargs->rhs_is_noop = true;
+ } else {
+ replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf,
+ false, true, true, cpo_flags);
+ mapargs->rhs_len = STRLEN(replaced);
+ mapargs->rhs_is_noop = false;
+ mapargs->rhs = xcalloc(mapargs->rhs_len + 1, sizeof(char_u));
+ xstrlcpy((char *)mapargs->rhs, (char *)replaced, mapargs->rhs_len + 1);
+ }
- /* For ":noremap" don't remap, otherwise do remap. */
- if (maptype == 2)
- noremap = REMAP_NONE;
- else
- noremap = REMAP_YES;
+ xfree(lhs_buf);
+ xfree(rhs_buf);
+}
- /* Accept <buffer>, <nowait>, <silent>, <expr> <script> and <unique> in
- * any order. */
- for (;; ) {
- /*
- * Check for "<buffer>": mapping local to buffer.
- */
- if (STRNCMP(keys, "<buffer>", 8) == 0) {
- keys = skipwhite(keys + 8);
- map_table = curbuf->b_maphash;
- abbr_table = &curbuf->b_first_abbr;
+/// Parse a string of |:map-arguments| into a @ref MapArguments struct.
+///
+/// Termcodes, backslashes, CTRL-V's, etc. inside the extracted {lhs} and
+/// {rhs} are replaced by @ref set_maparg_lhs_rhs.
+///
+/// rhs and orig_rhs in the returned mapargs will be set to null or a pointer
+/// to allocated memory and should be freed even on error.
+///
+/// @param[in] strargs String of map args, e.g. "<buffer> <expr><silent>".
+/// May contain leading or trailing whitespace.
+/// @param[in] is_unmap True, if strargs should be parsed like an |:unmap|
+/// command. |:unmap| commands interpret *all* text to the
+/// right of the last map argument as the {lhs} of the
+/// mapping, i.e. a literal ' ' character is treated like
+/// a "<space>", rather than separating the {lhs} from the
+/// {rhs}.
+/// @param[out] mapargs MapArguments struct holding all extracted argument
+/// values.
+/// @return 0 on success, 1 if invalid arguments are detected.
+int str_to_mapargs(const char_u *strargs, bool is_unmap, MapArguments *mapargs)
+{
+ const char_u *to_parse = strargs;
+ to_parse = skipwhite(to_parse);
+ MapArguments parsed_args; // copy these into mapargs "all at once" when done
+ memset(&parsed_args, 0, sizeof(parsed_args));
+
+ // Accept <buffer>, <nowait>, <silent>, <expr>, <script>, and <unique> in
+ // any order.
+ while (true) {
+ if (STRNCMP(to_parse, "<buffer>", 8) == 0) {
+ to_parse = skipwhite(to_parse + 8);
+ parsed_args.buffer = true;
continue;
}
- /*
- * Check for "<nowait>": don't wait for more characters.
- */
- if (STRNCMP(keys, "<nowait>", 8) == 0) {
- keys = skipwhite(keys + 8);
- nowait = true;
+ if (STRNCMP(to_parse, "<nowait>", 8) == 0) {
+ to_parse = skipwhite(to_parse + 8);
+ parsed_args.nowait = true;
continue;
}
- /*
- * Check for "<silent>": don't echo commands.
- */
- if (STRNCMP(keys, "<silent>", 8) == 0) {
- keys = skipwhite(keys + 8);
- silent = true;
+ if (STRNCMP(to_parse, "<silent>", 8) == 0) {
+ to_parse = skipwhite(to_parse + 8);
+ parsed_args.silent = true;
continue;
}
// Ignore obsolete "<special>" modifier.
- if (STRNCMP(keys, "<special>", 9) == 0) {
- keys = skipwhite(keys + 9);
+ if (STRNCMP(to_parse, "<special>", 9) == 0) {
+ to_parse = skipwhite(to_parse + 9);
continue;
}
- /*
- * Check for "<script>": remap script-local mappings only
- */
- if (STRNCMP(keys, "<script>", 8) == 0) {
- keys = skipwhite(keys + 8);
- noremap = REMAP_SCRIPT;
+ if (STRNCMP(to_parse, "<script>", 8) == 0) {
+ to_parse = skipwhite(to_parse + 8);
+ parsed_args.script = true;
continue;
}
- /*
- * Check for "<expr>": {rhs} is an expression.
- */
- if (STRNCMP(keys, "<expr>", 6) == 0) {
- keys = skipwhite(keys + 6);
- expr = true;
+ if (STRNCMP(to_parse, "<expr>", 6) == 0) {
+ to_parse = skipwhite(to_parse + 6);
+ parsed_args.expr = true;
continue;
}
- /*
- * Check for "<unique>": don't overwrite an existing mapping.
- */
- if (STRNCMP(keys, "<unique>", 8) == 0) {
- keys = skipwhite(keys + 8);
- unique = true;
+
+ if (STRNCMP(to_parse, "<unique>", 8) == 0) {
+ to_parse = skipwhite(to_parse + 8);
+ parsed_args.unique = true;
continue;
}
break;
}
- validate_maphash();
-
- /*
- * Find end of keys and skip CTRL-Vs (and backslashes) in it.
- * Accept backslash like CTRL-V when 'cpoptions' does not contain 'B'.
- * with :unmap white space is included in the keys, no argument possible.
- */
- p = keys;
- do_backslash = (vim_strchr(p_cpo, CPO_BSLASH) == NULL);
- while (*p && (maptype == 1 || !ascii_iswhite(*p))) {
- if ((p[0] == Ctrl_V || (do_backslash && p[0] == '\\')) && p[1] != NUL) {
- p++; // skip CTRL-V or backslash
+ // Find the next whitespace character, call that the end of {lhs}.
+ //
+ // If a character (e.g. whitespace) is immediately preceded by a CTRL-V,
+ // "scan past" that character, i.e. don't "terminate" LHS with that character
+ // if it's whitespace.
+ //
+ // Treat backslash like CTRL-V when 'cpoptions' does not contain 'B'.
+ //
+ // With :unmap, literal white space is included in the {lhs}; there is no
+ // separate {rhs}.
+ const char_u *lhs_end = to_parse;
+ bool do_backslash = (vim_strchr(p_cpo, CPO_BSLASH) == NULL);
+ while (*lhs_end && (is_unmap || !ascii_iswhite(*lhs_end))) {
+ if ((lhs_end[0] == Ctrl_V || (do_backslash && lhs_end[0] == '\\'))
+ && lhs_end[1] != NUL) {
+ lhs_end++; // skip CTRL-V or backslash
}
- p++;
+ lhs_end++;
+ }
+
+ // {lhs_end} is a pointer to the "terminating whitespace" after {lhs}.
+ // Use that to initialize {rhs_start}.
+ const char_u *rhs_start = skipwhite(lhs_end);
+
+ // Given {lhs} might be larger than MAXMAPLEN before replace_termcodes
+ // (e.g. "<Space>" is longer than ' '), so first copy into a buffer.
+ size_t orig_lhs_len = (size_t)(lhs_end - to_parse);
+ char_u *lhs_to_replace = xcalloc(orig_lhs_len + 1, sizeof(char_u));
+ xstrlcpy((char *)lhs_to_replace, (char *)to_parse, orig_lhs_len + 1);
+
+ size_t orig_rhs_len = STRLEN(rhs_start);
+ set_maparg_lhs_rhs(lhs_to_replace, orig_lhs_len,
+ rhs_start, orig_rhs_len,
+ CPO_TO_CPO_FLAGS, &parsed_args);
+
+ xfree(lhs_to_replace);
+
+ *mapargs = parsed_args;
+
+ if (parsed_args.lhs_len > MAXMAPLEN) {
+ return 1;
+ }
+ return 0;
+}
+
+/// Sets or removes a mapping or abbreviation in buffer `buf`.
+///
+/// @param maptype @see do_map
+/// @param args Fully parsed and "preprocessed" arguments for the
+/// (un)map/abbrev command. Termcodes should have already been
+/// replaced; whitespace, `<` and `>` signs, etc. in {lhs} and
+/// {rhs} are assumed to be literal components of the mapping.
+/// @param mode @see do_map
+/// @param is_abbrev @see do_map
+/// @param buf Target Buffer
+int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
+ buf_T *buf)
+{
+ mapblock_T *mp, **mpp;
+ char_u *p;
+ int n;
+ int len = 0; // init for GCC
+ int did_it = false;
+ int did_local = false;
+ int round;
+ int retval = 0;
+ int hash;
+ int new_hash;
+ mapblock_T **abbr_table;
+ mapblock_T **map_table;
+ int noremap;
+
+ map_table = maphash;
+ abbr_table = &first_abbr;
+
+ // For ":noremap" don't remap, otherwise do remap.
+ if (maptype == 2) {
+ noremap = REMAP_NONE;
+ } else {
+ noremap = REMAP_YES;
+ }
+
+ if (args->buffer) {
+ // If <buffer> was given, we'll be searching through the buffer's
+ // mappings/abbreviations, not the globals.
+ map_table = buf->b_maphash;
+ abbr_table = &buf->b_first_abbr;
}
- if (*p != NUL) {
- *p++ = NUL;
+ if (args->script) {
+ noremap = REMAP_SCRIPT;
}
- p = skipwhite(p);
- rhs = p;
- hasarg = (*rhs != NUL);
- haskey = (*keys != NUL);
+ validate_maphash();
+
+ bool has_lhs = (args->lhs[0] != NUL);
+ bool has_rhs = (args->rhs[0] != NUL) || args->rhs_is_noop;
- /* check for :unmap without argument */
- if (maptype == 1 && !haskey) {
+ // check for :unmap without argument
+ if (maptype == 1 && !has_lhs) {
retval = 1;
goto theend;
}
- // If mapping has been given as ^V<C_UP> say, then replace the term codes
- // with the appropriate two bytes. If it is a shifted special key, unshift
- // it too, giving another two bytes.
- // replace_termcodes() may move the result to allocated memory, which
- // needs to be freed later (*keys_buf and *arg_buf).
- // replace_termcodes() also removes CTRL-Vs and sometimes backslashes.
- if (haskey) {
- keys = replace_termcodes(keys, STRLEN(keys), &keys_buf, true, true, true,
- CPO_TO_CPO_FLAGS);
- }
- orig_rhs = rhs;
- if (hasarg) {
- if (STRICMP(rhs, "<nop>") == 0) { // "<Nop>" means nothing
- rhs = (char_u *)"";
- } else {
- rhs = replace_termcodes(rhs, STRLEN(rhs), &arg_buf, false, true, true,
- CPO_TO_CPO_FLAGS);
- }
- }
+ char_u *lhs = (char_u *)&args->lhs;
+ char_u *rhs = (char_u *)args->rhs;
+ char_u *orig_rhs = args->orig_rhs;
- //
// check arguments and translate function keys
- //
- if (haskey) {
- len = (int)STRLEN(keys);
- if (len > MAXMAPLEN) { /* maximum length of MAXMAPLEN chars */
+ if (has_lhs) {
+ len = (int)args->lhs_len;
+ if (len > MAXMAPLEN) {
retval = 1;
goto theend;
}
- if (abbrev && maptype != 1) {
- /*
- * If an abbreviation ends in a keyword character, the
- * rest must be all keyword-char or all non-keyword-char.
- * Otherwise we won't be able to find the start of it in a
- * vi-compatible way.
- */
+ if (is_abbrev && maptype != 1) {
+ //
+ // If an abbreviation ends in a keyword character, the
+ // rest must be all keyword-char or all non-keyword-char.
+ // Otherwise we won't be able to find the start of it in a
+ // vi-compatible way.
+ //
if (has_mbyte) {
int first, last;
int same = -1;
- first = vim_iswordp(keys);
+ first = vim_iswordp(lhs);
last = first;
- p = keys + (*mb_ptr2len)(keys);
+ p = lhs + (*mb_ptr2len)(lhs);
n = 1;
- while (p < keys + len) {
- ++n; /* nr of (multi-byte) chars */
- last = vim_iswordp(p); /* type of last char */
- if (same == -1 && last != first)
- same = n - 1; /* count of same char type */
+ while (p < lhs + len) {
+ n++; // nr of (multi-byte) chars
+ last = vim_iswordp(p); // type of last char
+ if (same == -1 && last != first) {
+ same = n - 1; // count of same char type
+ }
p += (*mb_ptr2len)(p);
}
if (last && n > 2 && same >= 0 && same < n - 1) {
retval = 1;
goto theend;
}
- } else if (vim_iswordc(keys[len - 1])) /* ends in keyword char */
- for (n = 0; n < len - 2; ++n)
- if (vim_iswordc(keys[n]) != vim_iswordc(keys[len - 2])) {
+ } else if (vim_iswordc(lhs[len - 1])) { // ends in keyword char
+ for (n = 0; n < len - 2; n++) {
+ if (vim_iswordc(lhs[n]) != vim_iswordc(lhs[len - 2])) {
retval = 1;
goto theend;
}
- /* An abbreviation cannot contain white space. */
- for (n = 0; n < len; ++n)
- if (ascii_iswhite(keys[n])) {
+ } // for
+ }
+ // An abbreviation cannot contain white space.
+ for (n = 0; n < len; n++) {
+ if (ascii_iswhite(lhs[n])) {
retval = 1;
goto theend;
}
+ } // for
}
}
- if (haskey && hasarg && abbrev) /* if we will add an abbreviation */
- no_abbr = FALSE; /* reset flag that indicates there are
- no abbreviations */
+ if (has_lhs && has_rhs && is_abbrev) { // if we will add an abbreviation,
+ no_abbr = false; // reset flag that indicates there are no abbreviations
+ }
- if (!haskey || (maptype != 1 && !hasarg))
+ if (!has_lhs || (maptype != 1 && !has_rhs)) {
msg_start();
+ }
- /*
- * Check if a new local mapping wasn't already defined globally.
- */
- if (map_table == curbuf->b_maphash && haskey && hasarg && maptype != 1) {
- /* need to loop over all global hash lists */
- for (hash = 0; hash < 256 && !got_int; ++hash) {
- if (abbrev) {
- if (hash != 0) /* there is only one abbreviation list */
+ // Check if a new local mapping wasn't already defined globally.
+ if (map_table == buf->b_maphash && has_lhs && has_rhs && maptype != 1) {
+ // need to loop over all global hash lists
+ for (hash = 0; hash < 256 && !got_int; hash++) {
+ if (is_abbrev) {
+ if (hash != 0) { // there is only one abbreviation list
break;
+ }
mp = first_abbr;
- } else
+ } else {
mp = maphash[hash];
+ }
for (; mp != NULL && !got_int; mp = mp->m_next) {
- /* check entries with the same mode */
+ // check entries with the same mode
if ((mp->m_mode & mode) != 0
&& mp->m_keylen == len
- && unique
- && STRNCMP(mp->m_keys, keys, (size_t)len) == 0) {
- if (abbrev)
+ && args->unique
+ && STRNCMP(mp->m_keys, lhs, (size_t)len) == 0) {
+ if (is_abbrev) {
EMSG2(_("E224: global abbreviation already exists for %s"),
- mp->m_keys);
- else
- EMSG2(_("E225: global mapping already exists for %s"),
- mp->m_keys);
+ mp->m_keys);
+ } else {
+ EMSG2(_("E225: global mapping already exists for %s"), mp->m_keys);
+ }
retval = 5;
goto theend;
}
@@ -2789,30 +2837,29 @@ do_map (
}
}
- /*
- * When listing global mappings, also list buffer-local ones here.
- */
- if (map_table != curbuf->b_maphash && !hasarg && maptype != 1) {
- /* need to loop over all global hash lists */
- for (hash = 0; hash < 256 && !got_int; ++hash) {
- if (abbrev) {
- if (hash != 0) /* there is only one abbreviation list */
+ // When listing global mappings, also list buffer-local ones here.
+ if (map_table != buf->b_maphash && !has_rhs && maptype != 1) {
+ // need to loop over all global hash lists
+ for (hash = 0; hash < 256 && !got_int; hash++) {
+ if (is_abbrev) {
+ if (hash != 0) { // there is only one abbreviation list
break;
- mp = curbuf->b_first_abbr;
- } else
- mp = curbuf->b_maphash[hash];
+ }
+ mp = buf->b_first_abbr;
+ } else {
+ mp = buf->b_maphash[hash];
+ }
for (; mp != NULL && !got_int; mp = mp->m_next) {
- /* check entries with the same mode */
+ // check entries with the same mode
if ((mp->m_mode & mode) != 0) {
- if (!haskey) { /* show all entries */
- showmap(mp, TRUE);
- did_local = TRUE;
+ if (!has_lhs) { // show all entries
+ showmap(mp, true);
+ did_local = true;
} else {
n = mp->m_keylen;
- if (STRNCMP(mp->m_keys, keys,
- (size_t)(n < len ? n : len)) == 0) {
- showmap(mp, TRUE);
- did_local = TRUE;
+ if (STRNCMP(mp->m_keys, lhs, (size_t)(n < len ? n : len)) == 0) {
+ showmap(mp, true);
+ did_local = true;
}
}
}
@@ -2820,103 +2867,98 @@ do_map (
}
}
- /*
- * Find an entry in the maphash[] list that matches.
- * For :unmap we may loop two times: once to try to unmap an entry with a
- * matching 'from' part, a second time, if the first fails, to unmap an
- * entry with a matching 'to' part. This was done to allow ":ab foo bar"
- * to be unmapped by typing ":unab foo", where "foo" will be replaced by
- * "bar" because of the abbreviation.
- */
+ // Find an entry in the maphash[] list that matches.
+ // For :unmap we may loop two times: once to try to unmap an entry with a
+ // matching 'from' part, a second time, if the first fails, to unmap an
+ // entry with a matching 'to' part. This was done to allow ":ab foo bar"
+ // to be unmapped by typing ":unab foo", where "foo" will be replaced by
+ // "bar" because of the abbreviation.
for (round = 0; (round == 0 || maptype == 1) && round <= 1
- && !did_it && !got_int; ++round) {
- /* need to loop over all hash lists */
- for (hash = 0; hash < 256 && !got_int; ++hash) {
- if (abbrev) {
- if (hash > 0) /* there is only one abbreviation list */
+ && !did_it && !got_int; round++) {
+ // need to loop over all hash lists
+ for (hash = 0; hash < 256 && !got_int; hash++) {
+ if (is_abbrev) {
+ if (hash > 0) { // there is only one abbreviation list
break;
+ }
mpp = abbr_table;
- } else
+ } else {
mpp = &(map_table[hash]);
+ }
for (mp = *mpp; mp != NULL && !got_int; mp = *mpp) {
-
- if (!(mp->m_mode & mode)) { /* skip entries with wrong mode */
+ if (!(mp->m_mode & mode)) { // skip entries with wrong mode
mpp = &(mp->m_next);
continue;
}
- if (!haskey) { /* show all entries */
+ if (!has_lhs) { // show all entries
showmap(mp, map_table != maphash);
- did_it = TRUE;
- } else { /* do we have a match? */
- if (round) { /* second round: Try unmap "rhs" string */
+ did_it = true;
+ } else { // do we have a match?
+ if (round) { // second round: Try unmap "rhs" string
n = (int)STRLEN(mp->m_str);
p = mp->m_str;
} else {
n = mp->m_keylen;
p = mp->m_keys;
}
- if (STRNCMP(p, keys, (size_t)(n < len ? n : len)) == 0) {
- if (maptype == 1) { /* delete entry */
- /* Only accept a full match. For abbreviations we
- * ignore trailing space when matching with the
- * "lhs", since an abbreviation can't have
- * trailing space. */
- if (n != len && (!abbrev || round || n > len
- || *skipwhite(keys + n) != NUL)) {
+ if (STRNCMP(p, lhs, (size_t)(n < len ? n : len)) == 0) {
+ if (maptype == 1) { // delete entry
+ // Only accept a full match. For abbreviations we
+ // ignore trailing space when matching with the
+ // "lhs", since an abbreviation can't have
+ // trailing space.
+ if (n != len && (!is_abbrev || round || n > len
+ || *skipwhite(lhs + n) != NUL)) {
mpp = &(mp->m_next);
continue;
}
- /*
- * We reset the indicated mode bits. If nothing is
- * left the entry is deleted below.
- */
+ // We reset the indicated mode bits. If nothing is
+ // left the entry is deleted below.
mp->m_mode &= ~mode;
- did_it = TRUE; /* remember we did something */
- } else if (!hasarg) { /* show matching entry */
+ did_it = true; // remember we did something
+ } else if (!has_rhs) { // show matching entry
showmap(mp, map_table != maphash);
- did_it = TRUE;
- } else if (n != len) { /* new entry is ambiguous */
+ did_it = true;
+ } else if (n != len) { // new entry is ambiguous
mpp = &(mp->m_next);
continue;
- } else if (unique) {
- if (abbrev)
- EMSG2(_("E226: abbreviation already exists for %s"),
- p);
- else
+ } else if (args->unique) {
+ if (is_abbrev) {
+ EMSG2(_("E226: abbreviation already exists for %s"), p);
+ } else {
EMSG2(_("E227: mapping already exists for %s"), p);
+ }
retval = 5;
goto theend;
- } else { /* new rhs for existing entry */
- mp->m_mode &= ~mode; /* remove mode bits */
- if (mp->m_mode == 0 && !did_it) { /* reuse entry */
+ } else { // new rhs for existing entry
+ mp->m_mode &= ~mode; // remove mode bits
+ if (mp->m_mode == 0 && !did_it) { // reuse entry
xfree(mp->m_str);
mp->m_str = vim_strsave(rhs);
xfree(mp->m_orig_str);
mp->m_orig_str = vim_strsave(orig_rhs);
mp->m_noremap = noremap;
- mp->m_nowait = nowait;
- mp->m_silent = silent;
+ mp->m_nowait = args->nowait;
+ mp->m_silent = args->silent;
mp->m_mode = mode;
- mp->m_expr = expr;
+ mp->m_expr = args->expr;
mp->m_script_ID = current_SID;
- did_it = TRUE;
+ did_it = true;
}
}
- if (mp->m_mode == 0) { // entry can be deleted
+ if (mp->m_mode == 0) { // entry can be deleted
mapblock_free(mpp);
- continue; // continue with *mpp
+ continue; // continue with *mpp
}
- /*
- * May need to put this entry into another hash list.
- */
+ // May need to put this entry into another hash list.
new_hash = MAP_HASH(mp->m_mode, mp->m_keys[0]);
- if (!abbrev && new_hash != hash) {
+ if (!is_abbrev && new_hash != hash) {
*mpp = mp->m_next;
mp->m_next = map_table[new_hash];
map_table[new_hash] = mp;
- continue; /* continue with *mpp */
+ continue; // continue with *mpp
}
}
}
@@ -2925,13 +2967,13 @@ do_map (
}
}
- if (maptype == 1) { /* delete entry */
+ if (maptype == 1) { // delete entry
if (!did_it) {
- retval = 2; /* no match */
- } else if (*keys == Ctrl_C) {
+ retval = 2; // no match
+ } else if (*lhs == Ctrl_C) {
// If CTRL-C has been unmapped, reuse it for Interrupting.
- if (map_table == curbuf->b_maphash) {
- curbuf->b_mapped_ctrl_c &= ~mode;
+ if (map_table == buf->b_maphash) {
+ buf->b_mapped_ctrl_c &= ~mode;
} else {
mapped_ctrl_c &= ~mode;
}
@@ -2939,48 +2981,46 @@ do_map (
goto theend;
}
- if (!haskey || !hasarg) { /* print entries */
- if (!did_it
- && !did_local
- ) {
- if (abbrev)
+ if (!has_lhs || !has_rhs) { // print entries
+ if (!did_it && !did_local) {
+ if (is_abbrev) {
MSG(_("No abbreviation found"));
- else
+ } else {
MSG(_("No mapping found"));
+ }
}
- goto theend; /* listing finished */
+ goto theend; // listing finished
}
- if (did_it) /* have added the new entry already */
+ if (did_it) { // have added the new entry already
goto theend;
+ }
- /*
- * Get here when adding a new entry to the maphash[] list or abbrlist.
- */
+ // Get here when adding a new entry to the maphash[] list or abbrlist.
mp = xmalloc(sizeof(mapblock_T));
// If CTRL-C has been mapped, don't always use it for Interrupting.
- if (*keys == Ctrl_C) {
- if (map_table == curbuf->b_maphash) {
- curbuf->b_mapped_ctrl_c |= mode;
+ if (*lhs == Ctrl_C) {
+ if (map_table == buf->b_maphash) {
+ buf->b_mapped_ctrl_c |= mode;
} else {
mapped_ctrl_c |= mode;
}
}
- mp->m_keys = vim_strsave(keys);
+ mp->m_keys = vim_strsave(lhs);
mp->m_str = vim_strsave(rhs);
mp->m_orig_str = vim_strsave(orig_rhs);
mp->m_keylen = (int)STRLEN(mp->m_keys);
mp->m_noremap = noremap;
- mp->m_nowait = nowait;
- mp->m_silent = silent;
+ mp->m_nowait = args->nowait;
+ mp->m_silent = args->silent;
mp->m_mode = mode;
- mp->m_expr = expr;
+ mp->m_expr = args->expr;
mp->m_script_ID = current_SID;
- /* add the new entry in front of the abbrlist or maphash[] list */
- if (abbrev) {
+ // add the new entry in front of the abbrlist or maphash[] list
+ if (is_abbrev) {
mp->m_next = *abbr_table;
*abbr_table = mp;
} else {
@@ -2990,11 +3030,80 @@ do_map (
}
theend:
- xfree(keys_buf);
- xfree(arg_buf);
return retval;
}
+
+/// Set or remove a mapping or an abbreviation in the current buffer, OR
+/// display (matching) mappings/abbreviations.
+///
+/// ```vim
+/// map[!] " show all key mappings
+/// map[!] {lhs} " show key mapping for {lhs}
+/// map[!] {lhs} {rhs} " set key mapping for {lhs} to {rhs}
+/// noremap[!] {lhs} {rhs} " same, but no remapping for {rhs}
+/// unmap[!] {lhs} " remove key mapping for {lhs}
+/// abbr " show all abbreviations
+/// abbr {lhs} " show abbreviations for {lhs}
+/// abbr {lhs} {rhs} " set abbreviation for {lhs} to {rhs}
+/// noreabbr {lhs} {rhs} " same, but no remapping for {rhs}
+/// unabbr {lhs} " remove abbreviation for {lhs}
+///
+/// for :map mode is NORMAL + VISUAL + SELECTMODE + OP_PENDING
+/// for :map! mode is INSERT + CMDLINE
+/// for :cmap mode is CMDLINE
+/// for :imap mode is INSERT
+/// for :lmap mode is LANGMAP
+/// for :nmap mode is NORMAL
+/// for :vmap mode is VISUAL + SELECTMODE
+/// for :xmap mode is VISUAL
+/// for :smap mode is SELECTMODE
+/// for :omap mode is OP_PENDING
+/// for :tmap mode is TERM_FOCUS
+///
+/// for :abbr mode is INSERT + CMDLINE
+/// for :iabbr mode is INSERT
+/// for :cabbr mode is CMDLINE
+/// ```
+///
+/// @param maptype 0 for |:map|, 1 for |:unmap|, 2 for |noremap|.
+/// @param arg C-string containing the arguments of the map/abbrev
+/// command, i.e. everything except the initial `:[X][nore]map`.
+/// - Cannot be a read-only string; it will be modified.
+/// @param mode Bitflags representing the mode in which to set the mapping.
+/// See @ref get_map_mode.
+/// @param is_abbrev True if setting an abbreviation, false otherwise.
+///
+/// @return 0 on success. On failure, will return one of the following:
+/// - 1 for invalid arguments
+/// - 2 for no match
+/// - 4 for out of mem (deprecated, WON'T HAPPEN)
+/// - 5 for entry not unique
+///
+int do_map(int maptype, char_u *arg, int mode, bool is_abbrev)
+{
+ MapArguments parsed_args;
+ int result = str_to_mapargs(arg, maptype == 1, &parsed_args);
+ switch (result) {
+ case 0:
+ break;
+ case 1:
+ result = 1; // invalid arguments
+ goto free_and_return;
+ default:
+ assert(false && "Unknown return code from str_to_mapargs!");
+ result = -1;
+ goto free_and_return;
+ } // switch
+
+ result = buf_do_map(maptype, &parsed_args, mode, is_abbrev, curbuf);
+
+free_and_return:
+ xfree(parsed_args.rhs);
+ xfree(parsed_args.orig_rhs);
+ return result;
+}
+
/*
* Delete one entry from the abbrlist or maphash[].
* "mpp" is a pointer to the m_next field of the PREVIOUS entry!
diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h
index 4f548d975a..a40ea7730a 100644
--- a/src/nvim/getchar.h
+++ b/src/nvim/getchar.h
@@ -5,6 +5,7 @@
#include "nvim/types.h"
#include "nvim/buffer_defs.h"
#include "nvim/ex_cmds_defs.h"
+#include "nvim/vim.h"
/// Values for "noremap" argument of ins_typebuf()
///
@@ -23,6 +24,39 @@ typedef enum {
FLUSH_INPUT // flush typebuf and inchar() input
} flush_buffers_T;
+/// All possible |:map-arguments| usable in a |:map| command.
+///
+/// The <special> argument has no effect on mappings and is excluded from this
+/// struct declaration. |noremap| is included, since it behaves like a map
+/// argument when used in a mapping.
+///
+/// @see mapblock_T
+struct map_arguments {
+ bool buffer;
+ bool expr;
+ bool noremap;
+ bool nowait;
+ bool script;
+ bool silent;
+ bool unique;
+
+ /// The {lhs} of the mapping.
+ ///
+ /// vim limits this to MAXMAPLEN characters, allowing us to use a static
+ /// buffer. Setting lhs_len to a value larger than MAXMAPLEN can signal
+ /// that {lhs} was too long and truncated.
+ char_u lhs[MAXMAPLEN + 1];
+ size_t lhs_len;
+
+ char_u *rhs; /// The {rhs} of the mapping.
+ size_t rhs_len;
+ bool rhs_is_noop; /// True when the {orig_rhs} is <nop>.
+
+ char_u *orig_rhs; /// The original text of the {rhs}.
+ size_t orig_rhs_len;
+};
+typedef struct map_arguments MapArguments;
+
#define KEYLEN_PART_KEY -1 /* keylen value for incomplete key-code */
#define KEYLEN_PART_MAP -2 /* keylen value for incomplete mapping */
#define KEYLEN_REMOVED 9999 /* keylen value for removed sequence */
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 79af4a8ce3..ec14ada3d2 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -227,6 +227,7 @@ EXTERN dict_T vimvardict; /* Dictionary with v: variables */
EXTERN dict_T globvardict; /* Dictionary with g: variables */
EXTERN int did_emsg; /* set by emsg() when the message
is displayed or thrown */
+EXTERN bool called_vim_beep; // set if vim_beep() is called
EXTERN int did_emsg_syntax; /* did_emsg set because of a
syntax error */
EXTERN int called_emsg; /* always set by emsg() */
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 27e2abe1ad..4e1c7dff57 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -1520,10 +1520,11 @@ static void create_windows(mparm_T *parmp)
dorewind = FALSE;
curbuf = curwin->w_buffer;
if (curbuf->b_ml.ml_mfp == NULL) {
- /* Set 'foldlevel' to 'foldlevelstart' if it's not negative. */
- if (p_fdls >= 0)
+ // Set 'foldlevel' to 'foldlevelstart' if it's not negative..
+ if (p_fdls >= 0) {
curwin->w_p_fdl = p_fdls;
- /* When getting the ATTENTION prompt here, use a dialog */
+ }
+ // When getting the ATTENTION prompt here, use a dialog.
swap_exists_action = SEA_DIALOG;
set_buflisted(TRUE);
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index 662eda3c7c..a071314453 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -71,6 +71,7 @@
#include "nvim/undo.h"
#include "nvim/window.h"
#include "nvim/os/os.h"
+#include "nvim/os/process.h"
#include "nvim/os/input.h"
#ifndef UNIX /* it's in os/unix_defs.h for Unix */
@@ -348,7 +349,7 @@ int ml_open(buf_T *buf)
/*
* Allocate first data block and create an empty line 1.
*/
- hp = ml_new_data(mfp, FALSE, 1);
+ hp = ml_new_data(mfp, false, 1);
if (hp->bh_bnum != 2) {
IEMSG(_("E298: Didn't get block nr 2?"));
goto error;
@@ -763,7 +764,7 @@ void ml_recover(void)
long error;
int cannot_open;
linenr_T line_count;
- int has_error;
+ bool has_error;
int idx;
int top;
int txt_start;
@@ -983,8 +984,9 @@ void ml_recover(void)
* Now that we are sure that the file is going to be recovered, clear the
* contents of the current buffer.
*/
- while (!(curbuf->b_ml.ml_flags & ML_EMPTY))
- ml_delete((linenr_T)1, FALSE);
+ while (!(curbuf->b_ml.ml_flags & ML_EMPTY)) {
+ ml_delete((linenr_T)1, false);
+ }
/*
* Try reading the original file to obtain the values of 'fileformat',
@@ -1033,8 +1035,8 @@ void ml_recover(void)
}
++error;
ml_append(lnum++, (char_u *)_("???MANY LINES MISSING"),
- (colnr_T)0, TRUE);
- } else { /* there is a block */
+ (colnr_T)0, true);
+ } else { // there is a block
pp = hp->bh_data;
if (pp->pb_id == PTR_ID) { /* it is a pointer block */
/* check line count when using pointer block first time */
@@ -1044,15 +1046,15 @@ void ml_recover(void)
if (line_count != 0) {
++error;
ml_append(lnum++, (char_u *)_("???LINE COUNT WRONG"),
- (colnr_T)0, TRUE);
+ (colnr_T)0, true);
}
}
if (pp->pb_count == 0) {
ml_append(lnum++, (char_u *)_("???EMPTY BLOCK"),
- (colnr_T)0, TRUE);
- ++error;
- } else if (idx < (int)pp->pb_count) { /* go a block deeper */
+ (colnr_T)0, true);
+ error++;
+ } else if (idx < (int)pp->pb_count) { // go a block deeper
if (pp->pb_pointer[idx].pe_bnum < 0) {
/*
* Data block with negative block number.
@@ -1072,7 +1074,7 @@ void ml_recover(void)
if (cannot_open) {
++error;
ml_append(lnum++, (char_u *)_("???LINES MISSING"),
- (colnr_T)0, TRUE);
+ (colnr_T)0, true);
}
++idx; /* get same block again for next index */
continue;
@@ -1102,23 +1104,21 @@ void ml_recover(void)
}
++error;
ml_append(lnum++, (char_u *)_("???BLOCK MISSING"),
- (colnr_T)0, TRUE);
+ (colnr_T)0, true);
} else {
- /*
- * it is a data block
- * Append all the lines in this block
- */
- has_error = FALSE;
- /*
- * check length of block
- * if wrong, use length in pointer block
- */
+ // it is a data block
+ // Append all the lines in this block
+ has_error = false;
+ // check length of block
+ // if wrong, use length in pointer block
if (page_count * mfp->mf_page_size != dp->db_txt_end) {
- ml_append(lnum++,
- (char_u *)_("??? from here until ???END lines may be messed up"),
- (colnr_T)0, TRUE);
- ++error;
- has_error = TRUE;
+ ml_append(
+ lnum++,
+ (char_u *)_("??? from here until ???END lines"
+ " may be messed up"),
+ (colnr_T)0, true);
+ error++;
+ has_error = true;
dp->db_txt_end = page_count * mfp->mf_page_size;
}
@@ -1130,12 +1130,13 @@ void ml_recover(void)
* if wrong, use count in data block
*/
if (line_count != dp->db_line_count) {
- ml_append(lnum++,
- (char_u *)_(
- "??? from here until ???END lines may have been inserted/deleted"),
- (colnr_T)0, TRUE);
- ++error;
- has_error = TRUE;
+ ml_append(
+ lnum++,
+ (char_u *)_("??? from here until ???END lines"
+ " may have been inserted/deleted"),
+ (colnr_T)0, true);
+ error++;
+ has_error = true;
}
for (i = 0; i < dp->db_line_count; ++i) {
@@ -1146,11 +1147,11 @@ void ml_recover(void)
++error;
} else
p = (char_u *)dp + txt_start;
- ml_append(lnum++, p, (colnr_T)0, TRUE);
+ ml_append(lnum++, p, (colnr_T)0, true);
+ }
+ if (has_error) {
+ ml_append(lnum++, (char_u *)_("???END"), (colnr_T)0, true);
}
- if (has_error)
- ml_append(lnum++, (char_u *)_("???END"),
- (colnr_T)0, TRUE);
}
}
}
@@ -1201,7 +1202,7 @@ void ml_recover(void)
*/
while (curbuf->b_ml.ml_line_count > lnum
&& !(curbuf->b_ml.ml_flags & ML_EMPTY))
- ml_delete(curbuf->b_ml.ml_line_count, FALSE);
+ ml_delete(curbuf->b_ml.ml_line_count, false);
curbuf->b_flags |= BF_RECOVERED;
recoverymode = FALSE;
@@ -1453,14 +1454,47 @@ static char *make_percent_swname(const char *dir, char *name)
return d;
}
-#ifdef UNIX
static bool process_still_running;
-#endif
-/*
- * Give information about an existing swap file.
- * Returns timestamp (0 when unknown).
- */
+/// Return information found in swapfile "fname" in dictionary "d".
+/// This is used by the swapinfo() function.
+void get_b0_dict(const char *fname, dict_T *d)
+{
+ int fd;
+ struct block0 b0;
+
+ if ((fd = os_open(fname, O_RDONLY, 0)) >= 0) {
+ if (read_eintr(fd, &b0, sizeof(b0)) == sizeof(b0)) {
+ if (ml_check_b0_id(&b0) == FAIL) {
+ tv_dict_add_str(d, S_LEN("error"), "Not a swap file");
+ } else if (b0_magic_wrong(&b0)) {
+ tv_dict_add_str(d, S_LEN("error"), "Magic number mismatch");
+ } else {
+ // We have swap information.
+ tv_dict_add_str_len(d, S_LEN("version"), (char *)b0.b0_version, 10);
+ tv_dict_add_str_len(d, S_LEN("user"), (char *)b0.b0_uname,
+ B0_UNAME_SIZE);
+ tv_dict_add_str_len(d, S_LEN("host"), (char *)b0.b0_hname,
+ B0_HNAME_SIZE);
+ tv_dict_add_str_len(d, S_LEN("fname"), (char *)b0.b0_fname,
+ B0_FNAME_SIZE_ORG);
+
+ tv_dict_add_nr(d, S_LEN("pid"), char_to_long(b0.b0_pid));
+ tv_dict_add_nr(d, S_LEN("mtime"), char_to_long(b0.b0_mtime));
+ tv_dict_add_nr(d, S_LEN("dirty"), b0.b0_dirty ? 1 : 0);
+ tv_dict_add_nr(d, S_LEN("inode"), char_to_long(b0.b0_ino));
+ }
+ } else {
+ tv_dict_add_str(d, S_LEN("error"), "Cannot read file");
+ }
+ close(fd);
+ } else {
+ tv_dict_add_str(d, S_LEN("error"), "Cannot open file");
+ }
+}
+
+/// Give information about an existing swap file.
+/// Returns timestamp (0 when unknown).
static time_t swapfile_info(char_u *fname)
{
assert(fname != NULL);
@@ -1530,12 +1564,10 @@ static time_t swapfile_info(char_u *fname)
if (char_to_long(b0.b0_pid) != 0L) {
MSG_PUTS(_("\n process ID: "));
msg_outnum(char_to_long(b0.b0_pid));
-#if defined(UNIX)
- if (kill((pid_t)char_to_long(b0.b0_pid), 0) == 0) {
+ if (os_proc_running((int)char_to_long(b0.b0_pid))) {
MSG_PUTS(_(" (STILL RUNNING)"));
process_still_running = true;
}
-#endif
}
if (b0_magic_wrong(&b0)) {
@@ -1552,6 +1584,51 @@ static time_t swapfile_info(char_u *fname)
return x;
}
+/// Returns TRUE if the swap file looks OK and there are no changes, thus it
+/// can be safely deleted.
+static time_t swapfile_unchanged(char *fname)
+{
+ struct block0 b0;
+ int ret = true;
+
+ // Swap file must exist.
+ if (!os_path_exists((char_u *)fname)) {
+ return false;
+ }
+
+ // must be able to read the first block
+ int fd = os_open(fname, O_RDONLY, 0);
+ if (fd < 0) {
+ return false;
+ }
+ if (read_eintr(fd, &b0, sizeof(b0)) != sizeof(b0)) {
+ close(fd);
+ return false;
+ }
+
+ // the ID and magic number must be correct
+ if (ml_check_b0_id(&b0) == FAIL|| b0_magic_wrong(&b0)) {
+ ret = false;
+ }
+
+ // must be unchanged
+ if (b0.b0_dirty) {
+ ret = false;
+ }
+
+ // process must be known and not running.
+ long pid = char_to_long(b0.b0_pid);
+ if (pid == 0L || os_proc_running((int)pid)) {
+ ret = false;
+ }
+
+ // TODO(bram): Should we check if the swap file was created on the current
+ // system? And the current user?
+
+ close(fd);
+ return ret;
+}
+
static int recov_file_names(char_u **names, char_u *path, int prepend_dot)
FUNC_ATTR_NONNULL_ALL
{
@@ -1737,7 +1814,7 @@ char_u *
ml_get_buf (
buf_T *buf,
linenr_T lnum,
- int will_change /* line will be changed */
+ bool will_change // line will be changed
)
{
bhdr_T *hp;
@@ -1823,12 +1900,11 @@ int ml_line_alloced(void)
*
* return FAIL for failure, OK otherwise
*/
-int
-ml_append (
- linenr_T lnum, /* append after this line (can be 0) */
- char_u *line, /* text of the new line */
- colnr_T len, /* length of new line, including NUL, or 0 */
- int newfile /* flag, see above */
+int ml_append(
+ linenr_T lnum, // append after this line (can be 0)
+ char_u *line, // text of the new line
+ colnr_T len, // length of new line, including NUL, or 0
+ bool newfile // flag, see above
)
{
/* When starting up, we might still need to create the memfile */
@@ -1844,13 +1920,12 @@ ml_append (
* Like ml_append() but for an arbitrary buffer. The buffer must already have
* a memline.
*/
-int
-ml_append_buf (
+int ml_append_buf(
buf_T *buf,
- linenr_T lnum, /* append after this line (can be 0) */
- char_u *line, /* text of the new line */
- colnr_T len, /* length of new line, including NUL, or 0 */
- int newfile /* flag, see above */
+ linenr_T lnum, // append after this line (can be 0)
+ char_u *line, // text of the new line
+ colnr_T len, // length of new line, including NUL, or 0
+ bool newfile // flag, see above
)
{
if (buf->b_ml.ml_mfp == NULL)
@@ -1861,14 +1936,13 @@ ml_append_buf (
return ml_append_int(buf, lnum, line, len, newfile, FALSE);
}
-static int
-ml_append_int (
+static int ml_append_int(
buf_T *buf,
- linenr_T lnum, /* append after this line (can be 0) */
- char_u *line, /* text of the new line */
- colnr_T len, /* length of line, including NUL, or 0 */
- int newfile, /* flag, see above */
- int mark /* mark the new line */
+ linenr_T lnum, // append after this line (can be 0)
+ char_u *line, // text of the new line
+ colnr_T len, // length of line, including NUL, or 0
+ bool newfile, // flag, see above
+ int mark // mark the new line
)
{
int i;
@@ -2351,13 +2425,13 @@ int ml_replace(linenr_T lnum, char_u *line, bool copy)
///
/// @param message Show "--No lines in buffer--" message.
/// @return FAIL for failure, OK otherwise
-int ml_delete(linenr_T lnum, int message)
+int ml_delete(linenr_T lnum, bool message)
{
ml_flush_line(curbuf);
return ml_delete_int(curbuf, lnum, message);
}
-static int ml_delete_int(buf_T *buf, linenr_T lnum, int message)
+static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message)
{
bhdr_T *hp;
memfile_T *mfp;
@@ -2677,17 +2751,15 @@ static void ml_flush_line(buf_T *buf)
/* The else case is already covered by the insert and delete */
ml_updatechunk(buf, lnum, (long)extra, ML_CHNK_UPDLINE);
} else {
- /*
- * Cannot do it in one data block: Delete and append.
- * Append first, because ml_delete_int() cannot delete the
- * last line in a buffer, which causes trouble for a buffer
- * that has only one line.
- * Don't forget to copy the mark!
- */
- /* How about handling errors??? */
- (void)ml_append_int(buf, lnum, new_line, new_len, FALSE,
- (dp->db_index[idx] & DB_MARKED));
- (void)ml_delete_int(buf, lnum, FALSE);
+ // Cannot do it in one data block: Delete and append.
+ // Append first, because ml_delete_int() cannot delete the
+ // last line in a buffer, which causes trouble for a buffer
+ // that has only one line.
+ // Don't forget to copy the mark!
+ // How about handling errors???
+ (void)ml_append_int(buf, lnum, new_line, new_len, false,
+ (dp->db_index[idx] & DB_MARKED));
+ (void)ml_delete_int(buf, lnum, false);
}
}
xfree(new_line);
@@ -2701,7 +2773,7 @@ static void ml_flush_line(buf_T *buf)
/*
* create a new, empty, data block
*/
-static bhdr_T *ml_new_data(memfile_T *mfp, int negative, int page_count)
+static bhdr_T *ml_new_data(memfile_T *mfp, bool negative, int page_count)
{
assert(page_count >= 0);
bhdr_T *hp = mf_new(mfp, negative, (unsigned)page_count);
@@ -3353,17 +3425,24 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname,
&& vim_strchr(p_shm, SHM_ATTENTION) == NULL) {
int choice = 0;
-#ifdef UNIX
process_still_running = false;
-#endif
- /*
- * If there is a SwapExists autocommand and we can handle
- * the response, trigger it. It may return 0 to ask the
- * user anyway.
- */
- if (swap_exists_action != SEA_NONE
- && has_autocmd(EVENT_SWAPEXISTS, (char_u *) buf_fname, buf))
- choice = do_swapexists(buf, (char_u *) fname);
+ // It's safe to delete the swap file if all these are true:
+ // - the edited file exists
+ // - the swap file has no changes and looks OK
+ if (os_path_exists(buf->b_fname) && swapfile_unchanged(fname)) {
+ choice = 4;
+ if (p_verbose > 0) {
+ verb_msg(_("Found a swap file that is not useful, deleting it"));
+ }
+ }
+
+ // If there is a SwapExists autocommand and we can handle the
+ // response, trigger it. It may return 0 to ask the user anyway.
+ if (choice == 0
+ && swap_exists_action != SEA_NONE
+ && has_autocmd(EVENT_SWAPEXISTS, (char_u *)buf_fname, buf)) {
+ choice = do_swapexists(buf, (char_u *)fname);
+ }
if (choice == 0) {
// Show info about the existing swap file.
@@ -3395,21 +3474,18 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname,
xstrlcat(name, sw_msg_2, name_len);
choice = do_dialog(VIM_WARNING, (char_u *)_("VIM - ATTENTION"),
(char_u *)name,
-# if defined(UNIX)
process_still_running
? (char_u *)_(
"&Open Read-Only\n&Edit anyway\n&Recover"
"\n&Quit\n&Abort") :
-# endif
(char_u *)_(
"&Open Read-Only\n&Edit anyway\n&Recover"
"\n&Delete it\n&Quit\n&Abort"),
1, NULL, false);
-# if defined(UNIX)
- if (process_still_running && choice >= 4)
- choice++; /* Skip missing "Delete it" button */
-# endif
+ if (process_still_running && choice >= 4) {
+ choice++; // Skip missing "Delete it" button.
+ }
xfree(name);
// pretend screen didn't scroll, need redraw anyway
diff --git a/src/nvim/menu.c b/src/nvim/menu.c
index 523bb20738..472481bb30 100644
--- a/src/nvim/menu.c
+++ b/src/nvim/menu.c
@@ -774,15 +774,12 @@ static vimmenu_T *find_menu(vimmenu_T *menu, char_u *name, int modes)
if (menu_name_equal(name, menu)) {
// Found menu
if (*p != NUL && menu->children == NULL) {
- if (*p != NUL) {
EMSG(_(e_notsubmenu));
return NULL;
- } else if ((menu->modes & modes) == 0x0) {
- EMSG(_(e_othermode));
- return NULL;
- }
- }
- if (*p == NUL) { // found a full match
+ } else if ((menu->modes & modes) == 0x0) {
+ EMSG(_(e_othermode));
+ return NULL;
+ } else if (*p == NUL) { // found a full match
return menu;
}
break;
diff --git a/src/nvim/message.c b/src/nvim/message.c
index f7c248184e..cb83d6482c 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -133,15 +133,11 @@ int msg(char_u *s)
return msg_attr_keep(s, 0, false, false);
}
-/*
- * Like msg() but keep it silent when 'verbosefile' is set.
- */
-int verb_msg(char_u *s)
+/// Like msg() but keep it silent when 'verbosefile' is set.
+int verb_msg(char *s)
{
- int n;
-
verbose_enter();
- n = msg_attr_keep(s, 0, false, false);
+ int n = msg_attr_keep((char_u *)s, 0, false, false);
verbose_leave();
return n;
@@ -1884,7 +1880,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr,
}
// Concat pieces with the same highlight
ga_concat_len(&msg_ext_last_chunk, (char *)str,
- strnlen((char *)str, maxlen));
+ strnlen((char *)str, maxlen)); // -V781
return;
}
@@ -3012,6 +3008,8 @@ void give_warning(char_u *message, bool hl) FUNC_ATTR_NONNULL_ARG(1)
} else {
keep_msg_attr = 0;
}
+ msg_ext_set_kind("wmsg");
+
if (msg_attr((const char *)message, keep_msg_attr) && msg_scrolled == 0) {
set_keep_msg(message, keep_msg_attr);
}
@@ -3352,6 +3350,7 @@ void display_confirm_msg(void)
// Avoid that 'q' at the more prompt truncates the message here.
confirm_msg_used++;
if (confirm_msg != NULL) {
+ msg_ext_set_kind("confirm");
msg_puts_attr((const char *)confirm_msg, HL_ATTR(HLF_M));
}
confirm_msg_used--;
diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c
index e5c22e5be3..0008409731 100644
--- a/src/nvim/misc1.c
+++ b/src/nvim/misc1.c
@@ -745,9 +745,9 @@ open_line (
if (dir == BACKWARD)
--curwin->w_cursor.lnum;
if (!(State & VREPLACE_FLAG) || old_cursor.lnum >= orig_line_count) {
- if (ml_append(curwin->w_cursor.lnum, p_extra, (colnr_T)0, FALSE)
- == FAIL)
+ if (ml_append(curwin->w_cursor.lnum, p_extra, (colnr_T)0, false) == FAIL) {
goto theend;
+ }
// Postpone calling changed_lines(), because it would mess up folding
// with markers.
// Skip mark_adjust when adding a line after the last one, there can't
@@ -1747,8 +1747,8 @@ del_lines (
if (curbuf->b_ml.ml_flags & ML_EMPTY) /* nothing to delete */
break;
- ml_delete(first, TRUE);
- ++n;
+ ml_delete(first, true);
+ n++;
/* If we delete the last line in the file, stop */
if (first > curbuf->b_ml.ml_line_count)
@@ -2248,6 +2248,7 @@ change_warning (
if (msg_row == Rows - 1)
msg_col = col;
msg_source(HL_ATTR(HLF_W));
+ msg_ext_set_kind("wmsg");
MSG_PUTS_ATTR(_(w_readonly), HL_ATTR(HLF_W) | MSG_HIST);
set_vim_var_string(VV_WARNINGMSG, _(w_readonly), -1);
msg_clr_eos();
@@ -2578,6 +2579,8 @@ void beep_flush(void)
// val is one of the BO_ values, e.g., BO_OPER
void vim_beep(unsigned val)
{
+ called_vim_beep = true;
+
if (emsg_silent == 0) {
if (!((bo_flags & val) || (bo_flags & BO_ALL))) {
if (p_vb) {
@@ -2587,8 +2590,9 @@ void vim_beep(unsigned val)
}
}
- /* When 'verbose' is set and we are sourcing a script or executing a
- * function give the user a hint where the beep comes from. */
+ // When 'debug' contains "beep" produce a message. If we are sourcing
+ // a script or executing a function give the user a hint where the beep
+ // comes from.
if (vim_strchr(p_debug, 'e') != NULL) {
msg_source(HL_ATTR(HLF_W));
msg_attr(_("Beep!"), HL_ATTR(HLF_W));
diff --git a/src/nvim/move.c b/src/nvim/move.c
index 43fdf0a4d1..e076543614 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -1524,9 +1524,9 @@ void scroll_cursor_bot(int min_scroll, int set_topbot)
/* Count screen lines that are below the window. */
scrolled += loff.height;
if (loff.lnum == curwin->w_botline
- && boff.fill == 0
- )
+ && loff.fill == 0) {
scrolled -= curwin->w_empty_rows;
+ }
}
if (boff.lnum < curbuf->b_ml.ml_line_count) {
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 5217db5b42..ca586cca29 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -4642,6 +4642,9 @@ static void nv_clear(cmdarg_T *cap)
if (!checkclearop(cap->oap)) {
/* Clear all syntax states to force resyncing. */
syn_stack_free_all(curwin->w_s);
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ wp->w_s->b_syn_slow = false;
+ }
redraw_later(CLEAR);
}
}
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 79a7271819..216bab4dda 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -3001,9 +3001,10 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
/* add a new line */
if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) {
if (ml_append(curbuf->b_ml.ml_line_count, (char_u *)"",
- (colnr_T)1, FALSE) == FAIL)
+ (colnr_T)1, false) == FAIL) {
break;
- ++nr_lines;
+ }
+ nr_lines++;
}
/* get the old line and advance to the position to insert at */
oldp = get_cursor_line_ptr();
@@ -3190,8 +3191,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
newp = (char_u *) xmalloc((size_t)(STRLEN(ptr) + totlen + 1));
STRCPY(newp, y_array[y_size - 1]);
STRCAT(newp, ptr);
- /* insert second line */
- ml_append(lnum, newp, (colnr_T)0, FALSE);
+ // insert second line
+ ml_append(lnum, newp, (colnr_T)0, false);
xfree(newp);
oldp = ml_get(lnum);
@@ -5616,6 +5617,9 @@ static yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing)
if (explicit_cb_reg) {
target = &y_regs[*name == '*' ? STAR_REGISTER : PLUS_REGISTER];
+ if (writing && (cb_flags & (*name == '*' ? CB_UNNAMED : CB_UNNAMEDPLUS))) {
+ clipboard_needs_update = false;
+ }
goto end;
} else { // unnamed register: "implicit" clipboard
if (writing && clipboard_delay_update) {
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 4403861af4..743f6c8311 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -432,13 +432,17 @@ static inline char *add_colon_dirs(char *dest, const char *const val,
return dest;
}
-/// Add directory to a comma-separated list of directories
+/// Adds directory `dest` to a comma-separated list of directories.
///
-/// In the added directory comma is escaped.
+/// Commas in the added directory are escaped.
+///
+/// Windows: Appends "nvim-data" instead of "nvim" if `type` is kXDGDataHome.
+///
+/// @see get_xdg_home
///
/// @param[in,out] dest Destination comma-separated array.
/// @param[in] dir Directory to append.
-/// @param[in] append_nvim If true, append "nvim" as the very first suffix.
+/// @param[in] type Decides whether to append "nvim" (Win: or "nvim-data").
/// @param[in] suf1 If not NULL, suffix appended to destination. Prior to it
/// directory separator is appended. Suffix must not contain
/// commas.
@@ -452,7 +456,7 @@ static inline char *add_colon_dirs(char *dest, const char *const val,
///
/// @return (dest + appended_characters_length)
static inline char *add_dir(char *dest, const char *const dir,
- const size_t dir_len, const bool append_nvim,
+ const size_t dir_len, const XDGVarType type,
const char *const suf1, const size_t len1,
const char *const suf2, const size_t len2)
FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT
@@ -461,12 +465,19 @@ static inline char *add_dir(char *dest, const char *const dir,
return dest;
}
dest = strcpy_comma_escaped(dest, dir, dir_len);
+ bool append_nvim = (type == kXDGDataHome || type == kXDGConfigHome);
if (append_nvim) {
if (!after_pathsep(dest - 1, dest)) {
*dest++ = PATHSEP;
}
+#if defined(WIN32)
+ size_t size = (type == kXDGDataHome ? sizeof("nvim-data") - 1 : NVIM_SIZE);
+ memmove(dest, (type == kXDGDataHome ? "nvim-data" : "nvim"), size);
+ dest += size;
+#else
memmove(dest, "nvim", NVIM_SIZE);
dest += NVIM_SIZE;
+#endif
if (suf1 != NULL) {
*dest++ = PATHSEP;
memmove(dest, suf1, len1);
@@ -482,7 +493,10 @@ static inline char *add_dir(char *dest, const char *const dir,
return dest;
}
-/// Set &runtimepath to default value
+/// Sets &runtimepath to default value.
+///
+/// Windows: Uses "…/nvim-data" for kXDGDataHome to avoid storing
+/// configuration and data files in the same path. #4403
static void set_runtimepath_default(void)
{
size_t rtp_size = 0;
@@ -499,8 +513,13 @@ static void set_runtimepath_default(void)
if (data_home != NULL) {
data_len = strlen(data_home);
if (data_len != 0) {
+#if defined(WIN32)
+ size_t nvim_size = (sizeof("nvim-data") - 1);
+#else
+ size_t nvim_size = NVIM_SIZE;
+#endif
rtp_size += ((data_len + memcnt(data_home, ',', data_len)
- + NVIM_SIZE + 1 + SITE_SIZE + 1
+ + nvim_size + 1 + SITE_SIZE + 1
+ !after_pathsep(data_home, data_home + data_len)) * 2
+ AFTER_SIZE + 1);
}
@@ -529,21 +548,22 @@ static void set_runtimepath_default(void)
}
char *const rtp = xmalloc(rtp_size);
char *rtp_cur = rtp;
- rtp_cur = add_dir(rtp_cur, config_home, config_len, true, NULL, 0, NULL, 0);
+ rtp_cur = add_dir(rtp_cur, config_home, config_len, kXDGConfigHome,
+ NULL, 0, NULL, 0);
rtp_cur = add_colon_dirs(rtp_cur, config_dirs, NULL, 0, NULL, 0, true);
- rtp_cur = add_dir(rtp_cur, data_home, data_len, true, "site", SITE_SIZE,
- NULL, 0);
+ rtp_cur = add_dir(rtp_cur, data_home, data_len, kXDGDataHome,
+ "site", SITE_SIZE, NULL, 0);
rtp_cur = add_colon_dirs(rtp_cur, data_dirs, "site", SITE_SIZE, NULL, 0,
true);
- rtp_cur = add_dir(rtp_cur, vimruntime, vimruntime_len, false, NULL, 0,
- NULL, 0);
+ rtp_cur = add_dir(rtp_cur, vimruntime, vimruntime_len, kXDGNone,
+ NULL, 0, NULL, 0);
rtp_cur = add_colon_dirs(rtp_cur, data_dirs, "site", SITE_SIZE,
"after", AFTER_SIZE, false);
- rtp_cur = add_dir(rtp_cur, data_home, data_len, true, "site", SITE_SIZE,
- "after", AFTER_SIZE);
+ rtp_cur = add_dir(rtp_cur, data_home, data_len, kXDGDataHome,
+ "site", SITE_SIZE, "after", AFTER_SIZE);
rtp_cur = add_colon_dirs(rtp_cur, config_dirs, "after", AFTER_SIZE, NULL, 0,
false);
- rtp_cur = add_dir(rtp_cur, config_home, config_len, true,
+ rtp_cur = add_dir(rtp_cur, config_home, config_len, kXDGConfigHome,
"after", AFTER_SIZE, NULL, 0);
// Strip trailing comma.
rtp_cur[-1] = NUL;
@@ -1813,34 +1833,30 @@ int do_set(
{
uint32_t *p = insecure_flag(opt_idx, opt_flags);
- int did_inc_secure = false;
+ const int secure_saved = secure;
// When an option is set in the sandbox, from a
// modeline or in secure mode, then deal with side
// effects in secure mode. Also when the value was
// set with the P_INSECURE flag and is not
// completely replaced.
- if (secure
+ if ((opt_flags & OPT_MODELINE)
|| sandbox != 0
- || (opt_flags & OPT_MODELINE)
|| (!value_is_replaced && (*p & P_INSECURE))) {
- did_inc_secure = true;
- secure++;
+ secure = 1;
}
- // Handle side effects, and set the global value for
- // ":set" on local options. Note: when setting 'syntax'
- // or 'filetype' autocommands may be triggered that can
- // cause havoc.
+ // Handle side effects, and set the global value
+ // for ":set" on local options. Note: when setting
+ // 'syntax' or 'filetype' autocommands may be
+ // triggered that can cause havoc.
errmsg = did_set_string_option(opt_idx, (char_u **)varp,
new_value_alloced, oldval,
errbuf, sizeof(errbuf),
opt_flags, &value_checked);
- if (did_inc_secure) {
- secure--;
- }
- }
+ secure = secure_saved;
+ }
if (errmsg == NULL) {
if (!starting) {
diff --git a/src/nvim/os/process.c b/src/nvim/os/process.c
index a67e7487eb..a1020be215 100644
--- a/src/nvim/os/process.c
+++ b/src/nvim/os/process.c
@@ -265,3 +265,9 @@ Dictionary os_proc_info(int pid)
return pinfo;
}
#endif
+
+/// Return true if process `pid` is running.
+bool os_proc_running(int pid)
+{
+ return uv_kill(pid, 0) == 0;
+}
diff --git a/src/nvim/os/stdpaths.c b/src/nvim/os/stdpaths.c
index f6503dfdb0..91ee45d3a9 100644
--- a/src/nvim/os/stdpaths.c
+++ b/src/nvim/os/stdpaths.c
@@ -80,14 +80,14 @@ char *stdpaths_get_xdg_var(const XDGVarType idx)
return ret;
}
-/// Return nvim-specific XDG directory subpath
+/// Return Nvim-specific XDG directory subpath.
///
-/// @param[in] idx XDG directory to use.
+/// Windows: Uses "…/nvim-data" for kXDGDataHome to avoid storing
+/// configuration and data files in the same path. #4403
///
-/// @return [allocated] `{xdg_directory}/nvim`
+/// @param[in] idx XDG directory to use.
///
-/// In WIN32 get_xdg_home(kXDGDataHome) returns `{xdg_directory}/nvim-data` to
-/// avoid storing configuration and data files in the same path.
+/// @return [allocated] "{xdg_directory}/nvim"
char *get_xdg_home(const XDGVarType idx)
FUNC_ATTR_WARN_UNUSED_RESULT
{
diff --git a/src/nvim/os/stdpaths_defs.h b/src/nvim/os/stdpaths_defs.h
index 1433e0bc62..44c30df373 100644
--- a/src/nvim/os/stdpaths_defs.h
+++ b/src/nvim/os/stdpaths_defs.h
@@ -3,6 +3,7 @@
/// List of possible XDG variables
typedef enum {
+ kXDGNone = -1,
kXDGConfigHome, ///< XDG_CONFIG_HOME
kXDGDataHome, ///< XDG_DATA_HOME
kXDGCacheHome, ///< XDG_CACHE_HOME
diff --git a/src/nvim/path.c b/src/nvim/path.c
index a706e32773..67b88a861a 100644
--- a/src/nvim/path.c
+++ b/src/nvim/path.c
@@ -1346,6 +1346,15 @@ void slash_adjust(char_u *p)
if (path_with_url((const char *)p)) {
return;
}
+
+ if (*p == '`') {
+ // don't replace backslash in backtick quoted strings
+ const size_t len = STRLEN(p);
+ if (len > 2 && *(p + len - 1) == '`') {
+ return;
+ }
+ }
+
while (*p) {
if (*p == (char_u)psepcN) {
*p = (char_u)psepc;
diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c
index 8cf09b14d7..58a0008e04 100644
--- a/src/nvim/popupmnu.c
+++ b/src/nvim/popupmnu.c
@@ -672,7 +672,7 @@ static int pum_set_selected(int n, int repeat)
&& (curbuf->b_p_bh[0] == 'w')) {
// Already a "wipeout" buffer, make it empty.
while (!BUFEMPTY()) {
- ml_delete((linenr_T)1, FALSE);
+ ml_delete((linenr_T)1, false);
}
} else {
// Don't want to sync undo in the current buffer.
@@ -697,11 +697,11 @@ static int pum_set_selected(int n, int repeat)
for (p = pum_array[pum_selected].pum_info; *p != NUL;) {
e = vim_strchr(p, '\n');
if (e == NULL) {
- ml_append(lnum++, p, 0, FALSE);
+ ml_append(lnum++, p, 0, false);
break;
} else {
*e = NUL;
- ml_append(lnum++, p, (int)(e - p + 1), FALSE);
+ ml_append(lnum++, p, (int)(e - p + 1), false);
*e = '\n';
p = e + 1;
}
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 2ee5af1ea5..29b3d19f52 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -57,19 +57,20 @@ struct dir_stack_T {
*/
typedef struct qfline_S qfline_T;
struct qfline_S {
- qfline_T *qf_next; /* pointer to next error in the list */
- qfline_T *qf_prev; /* pointer to previous error in the list */
- linenr_T qf_lnum; /* line number where the error occurred */
- int qf_fnum; /* file number for the line */
- int qf_col; /* column where the error occurred */
- int qf_nr; /* error number */
- char_u *qf_pattern; /* search pattern for the error */
- char_u *qf_text; /* description of the error */
- char_u qf_viscol; /* set to TRUE if qf_col is screen column */
- char_u qf_cleared; /* set to TRUE if line has been deleted */
- char_u qf_type; /* type of the error (mostly 'E'); 1 for
- :helpgrep */
- char_u qf_valid; /* valid error message detected */
+ qfline_T *qf_next; ///< pointer to next error in the list
+ qfline_T *qf_prev; ///< pointer to previous error in the list
+ linenr_T qf_lnum; ///< line number where the error occurred
+ int qf_fnum; ///< file number for the line
+ int qf_col; ///< column where the error occurred
+ int qf_nr; ///< error number
+ char_u *qf_module; ///< module name for this error
+ char_u *qf_pattern; ///< search pattern for the error
+ char_u *qf_text; ///< description of the error
+ char_u qf_viscol; ///< set to TRUE if qf_col is screen column
+ char_u qf_cleared; ///< set to TRUE if line has been deleted
+ char_u qf_type; ///< type of the error (mostly 'E'); 1 for
+ // :helpgrep
+ char_u qf_valid; ///< valid error message detected
};
/*
@@ -101,6 +102,7 @@ typedef struct qf_list_S {
bool qf_multiline;
bool qf_multiignore;
bool qf_multiscan;
+ long qf_changedtick;
} qf_list_T;
/// Quickfix/Location list stack definition
@@ -121,7 +123,7 @@ struct qf_info_S {
static qf_info_T ql_info; // global quickfix list
static unsigned last_qf_id = 0; // Last Used quickfix list id
-#define FMT_PATTERNS 10 /* maximum number of % recognized */
+#define FMT_PATTERNS 11 // maximum number of % recognized
/*
* Structure used to hold the info of one part of 'errorformat'
@@ -155,7 +157,8 @@ enum {
QF_OK = 1,
QF_END_OF_INPUT = 2,
QF_NOMEM = 3,
- QF_IGNORE_LINE = 4
+ QF_IGNORE_LINE = 4,
+ QF_MULTISCAN = 5,
};
typedef struct {
@@ -176,6 +179,7 @@ typedef struct {
typedef struct {
char_u *namebuf;
+ char_u *module;
char_u *errmsg;
size_t errmsglen;
long lnum;
@@ -205,6 +209,8 @@ typedef struct {
static char_u *qf_last_bufname = NULL;
static bufref_T qf_last_bufref = { NULL, 0, 0 };
+static char *e_loc_list_changed = N_("E926: Current location list was changed");
+
/// Read the errorfile "efile" into memory, line by line, building the error
/// list. Set the error list's title to qf_title.
///
@@ -247,7 +253,8 @@ static struct fmtpattern
{ 'r', ".*" },
{ 'p', "[- .]*" }, // NOLINT(whitespace/tab)
{ 'v', "\\d\\+" },
- { 's', ".\\+" }
+ { 's', ".\\+" },
+ { 'o', ".\\+" }
};
// Converts a 'errorformat' string to regular expression pattern
@@ -423,7 +430,11 @@ static efm_T * parse_efm_option(char_u *efm)
for (int round = FMT_PATTERNS - 1; round >= 0; ) {
i += STRLEN(fmt_pat[round--].pattern);
}
+#ifdef BACKSLASH_IN_FILENAME
+ i += 12; // "%f" can become twelve chars longer (see efm_to_regpat)
+#else
i += 2; // "%f" can become two chars longer
+#endif
char_u *fmtstr = xmalloc(i);
while (efm[0] != NUL) {
@@ -717,20 +728,17 @@ static int qf_parse_line(qf_info_T *qi, int qf_idx, char_u *linebuf,
size_t linelen, efm_T *fmt_first, qffields_T *fields)
{
efm_T *fmt_ptr;
- size_t len;
- int i;
int idx = 0;
char_u *tail = NULL;
- regmatch_T regmatch;
qf_list_T *qfl = &qi->qf_lists[qf_idx];
+ int status;
- // Always ignore case when looking for a matching error.
- regmatch.rm_ic = true;
-
+restofline:
// If there was no %> item start at the first pattern
if (fmt_start == NULL) {
fmt_ptr = fmt_first;
} else {
+ // Otherwise start from the last used pattern.
fmt_ptr = fmt_start;
fmt_start = NULL;
}
@@ -738,143 +746,15 @@ static int qf_parse_line(qf_info_T *qi, int qf_idx, char_u *linebuf,
// Try to match each part of 'errorformat' until we find a complete
// match or no match.
fields->valid = true;
-restofline:
for (; fmt_ptr != NULL; fmt_ptr = fmt_ptr->next) {
idx = fmt_ptr->prefix;
- if (qfl->qf_multiscan && vim_strchr((char_u *)"OPQ", idx) == NULL) {
- continue;
+ status = qf_parse_get_fields(linebuf, linelen, fmt_ptr, fields,
+ qfl->qf_multiline, qfl->qf_multiscan,
+ &tail);
+ if (status == QF_NOMEM) {
+ return status;
}
- fields->namebuf[0] = NUL;
- fields->pattern[0] = NUL;
- if (!qfl->qf_multiscan) {
- fields->errmsg[0] = NUL;
- }
- fields->lnum = 0;
- fields->col = 0;
- fields->use_viscol = false;
- fields->enr = -1;
- fields->type = 0;
- tail = NULL;
-
- regmatch.regprog = fmt_ptr->prog;
- int r = vim_regexec(&regmatch, linebuf, (colnr_T)0);
- fmt_ptr->prog = regmatch.regprog;
- if (r) {
- if ((idx == 'C' || idx == 'Z') && !qfl->qf_multiline) {
- continue;
- }
- if (vim_strchr((char_u *)"EWI", idx) != NULL) {
- fields->type = (char_u)idx;
- } else {
- fields->type = 0;
- }
- // Extract error message data from matched line.
- // We check for an actual submatch, because "\[" and "\]" in
- // the 'errorformat' may cause the wrong submatch to be used.
- if ((i = (int)fmt_ptr->addr[0]) > 0) { // %f
- if (regmatch.startp[i] == NULL || regmatch.endp[i] == NULL) {
- continue;
- }
- // Expand ~/file and $HOME/file to full path.
- char_u c = *regmatch.endp[i];
- *regmatch.endp[i] = NUL;
- expand_env(regmatch.startp[i], fields->namebuf, CMDBUFFSIZE);
- *regmatch.endp[i] = c;
-
- if (vim_strchr((char_u *)"OPQ", idx) != NULL
- && !os_path_exists(fields->namebuf)) {
- continue;
- }
- }
- if ((i = (int)fmt_ptr->addr[1]) > 0) { // %n
- if (regmatch.startp[i] == NULL) {
- continue;
- }
- fields->enr = (int)atol((char *)regmatch.startp[i]);
- }
- if ((i = (int)fmt_ptr->addr[2]) > 0) { // %l
- if (regmatch.startp[i] == NULL) {
- continue;
- }
- fields->lnum = atol((char *)regmatch.startp[i]);
- }
- if ((i = (int)fmt_ptr->addr[3]) > 0) { // %c
- if (regmatch.startp[i] == NULL) {
- continue;
- }
- fields->col = (int)atol((char *)regmatch.startp[i]);
- }
- if ((i = (int)fmt_ptr->addr[4]) > 0) { // %t
- if (regmatch.startp[i] == NULL) {
- continue;
- }
- fields->type = *regmatch.startp[i];
- }
- if (fmt_ptr->flags == '+' && !qfl->qf_multiscan) { // %+
- if (linelen >= fields->errmsglen) {
- // linelen + null terminator
- fields->errmsg = xrealloc(fields->errmsg, linelen + 1);
- fields->errmsglen = linelen + 1;
- }
- STRLCPY(fields->errmsg, linebuf, linelen + 1);
- } else if ((i = (int)fmt_ptr->addr[5]) > 0) { // %m
- if (regmatch.startp[i] == NULL || regmatch.endp[i] == NULL) {
- continue;
- }
- len = (size_t)(regmatch.endp[i] - regmatch.startp[i]);
- if (len >= fields->errmsglen) {
- // len + null terminator
- fields->errmsg = xrealloc(fields->errmsg, len + 1);
- fields->errmsglen = len + 1;
- }
- STRLCPY(fields->errmsg, regmatch.startp[i], len + 1);
- }
- if ((i = (int)fmt_ptr->addr[6]) > 0) { // %r
- if (regmatch.startp[i] == NULL) {
- continue;
- }
- tail = regmatch.startp[i];
- }
- if ((i = (int)fmt_ptr->addr[7]) > 0) { // %p
- char_u *match_ptr;
-
- if (regmatch.startp[i] == NULL || regmatch.endp[i] == NULL) {
- continue;
- }
- fields->col = 0;
- for (match_ptr = regmatch.startp[i];
- match_ptr != regmatch.endp[i]; match_ptr++) {
- fields->col++;
- if (*match_ptr == TAB) {
- fields->col += 7;
- fields->col -= fields->col % 8;
- }
- }
- fields->col++;
- fields->use_viscol = true;
- }
- if ((i = (int)fmt_ptr->addr[8]) > 0) { // %v
- if (regmatch.startp[i] == NULL) {
- continue;
- }
- fields->col = (int)atol((char *)regmatch.startp[i]);
- fields->use_viscol = true;
- }
- if ((i = (int)fmt_ptr->addr[9]) > 0) { // %s
- if (regmatch.startp[i] == NULL || regmatch.endp[i] == NULL) {
- continue;
- }
- len = (size_t)(regmatch.endp[i] - regmatch.startp[i]);
- if (len > CMDBUFFSIZE - 5) {
- len = CMDBUFFSIZE - 5;
- }
- STRCPY(fields->pattern, "^\\V");
- xstrlcat((char *)fields->pattern, (char *)regmatch.startp[i],
- CMDBUFFSIZE+1);
- fields->pattern[len + 3] = '\\';
- fields->pattern[len + 4] = '$';
- fields->pattern[len + 5] = NUL;
- }
+ if (status == QF_OK) {
break;
}
}
@@ -882,30 +762,16 @@ restofline:
if (fmt_ptr == NULL || idx == 'D' || idx == 'X') {
if (fmt_ptr != NULL) {
- if (idx == 'D') { // enter directory
- if (*fields->namebuf == NUL) {
- EMSG(_("E379: Missing or empty directory name"));
- return QF_FAIL;
- }
- qfl->qf_directory = qf_push_dir(fields->namebuf, &qfl->qf_dir_stack,
- false);
- if (qfl->qf_directory == NULL) {
- return QF_FAIL;
- }
- } else if (idx == 'X') { // leave directory
- qfl->qf_directory = qf_pop_dir(&qfl->qf_dir_stack);
+ // 'D' and 'X' directory specifiers.
+ status = qf_parse_dir_pfx(idx, fields, qfl);
+ if (status != QF_OK) {
+ return status;
}
}
- fields->namebuf[0] = NUL; // no match found, remove file name
- fields->lnum = 0; // don't jump to this line
- fields->valid = false;
- if (linelen >= fields->errmsglen) {
- // linelen + null terminator
- fields->errmsg = xrealloc(fields->errmsg, linelen + 1);
- fields->errmsglen = linelen + 1;
+ status = qf_parse_line_nomatch(linebuf, linelen, fields);
+ if (status != QF_OK) {
+ return status;
}
- // copy whole line to error message
- STRLCPY(fields->errmsg, linebuf, linelen + 1);
if (fmt_ptr == NULL) {
qfl->qf_multiline = qfl->qf_multiignore = false;
}
@@ -918,63 +784,17 @@ restofline:
if (vim_strchr((char_u *)"AEWI", idx) != NULL) {
qfl->qf_multiline = true; // start of a multi-line message
qfl->qf_multiignore = false; // reset continuation
- } else if (vim_strchr((char_u *)"CZ", idx)
- != NULL) { // continuation of multi-line msg
- if (!qfl->qf_multiignore) {
- qfline_T *qfprev = qfl->qf_last;
- if (qfprev == NULL) {
- return QF_FAIL;
- }
- if (*fields->errmsg) {
- size_t textlen = STRLEN(qfprev->qf_text);
- qfprev->qf_text = xrealloc(qfprev->qf_text,
- textlen + STRLEN(fields->errmsg) + 2);
- qfprev->qf_text[textlen] = '\n';
- STRCPY(qfprev->qf_text + textlen + 1, fields->errmsg);
- }
- if (qfprev->qf_nr == -1) {
- qfprev->qf_nr = fields->enr;
- }
- if (vim_isprintc(fields->type) && !qfprev->qf_type) {
- qfprev->qf_type = fields->type; // only printable chars allowed
- }
- if (!qfprev->qf_lnum) {
- qfprev->qf_lnum = fields->lnum;
- }
- if (!qfprev->qf_col) {
- qfprev->qf_col = fields->col;
- }
- qfprev->qf_viscol = fields->use_viscol;
- if (!qfprev->qf_fnum) {
- qfprev->qf_fnum = qf_get_fnum(qi, qf_idx, qfl->qf_directory,
- *fields->namebuf || qfl->qf_directory
- ? fields->namebuf
- : qfl->qf_currfile && fields->valid
- ? qfl->qf_currfile : 0);
- }
+ } else if (vim_strchr((char_u *)"CZ", idx) != NULL) {
+ // continuation of multi-line msg
+ status = qf_parse_multiline_pfx(qi, qf_idx, idx, qfl, fields);
+ if (status != QF_OK) {
+ return status;
}
- if (idx == 'Z') {
- qfl->qf_multiline = qfl->qf_multiignore = false;
- }
-
- line_breakcheck();
- return QF_IGNORE_LINE;
} else if (vim_strchr((char_u *)"OPQ", idx) != NULL) {
// global file names
- fields->valid = false;
- if (*fields->namebuf == NUL || os_path_exists(fields->namebuf)) {
- if (*fields->namebuf && idx == 'P') {
- qfl->qf_currfile = qf_push_dir(fields->namebuf, &qfl->qf_file_stack,
- true);
- } else if (idx == 'Q') {
- qfl->qf_currfile = qf_pop_dir(&qfl->qf_file_stack);
- }
- *fields->namebuf = NUL;
- if (tail && *tail) {
- STRMOVE(IObuff, skipwhite(tail));
- qfl->qf_multiscan = true;
- goto restofline;
- }
+ status = qf_parse_file_pfx(idx, fields, qfl, tail);
+ if (status == QF_MULTISCAN) {
+ goto restofline;
}
}
if (fmt_ptr->flags == '-') { // generally exclude this line
@@ -1034,6 +854,7 @@ qf_init_ext(
}
fields.namebuf = xmalloc(CMDBUFFSIZE + 1);
+ fields.module = xmalloc(CMDBUFFSIZE + 1);
fields.errmsglen = CMDBUFFSIZE + 1;
fields.errmsg = xmalloc(fields.errmsglen);
fields.pattern = xmalloc(CMDBUFFSIZE + 1);
@@ -1128,6 +949,7 @@ qf_init_ext(
(*fields.namebuf || qfl->qf_directory)
? fields.namebuf : ((qfl->qf_currfile && fields.valid)
? qfl->qf_currfile : (char_u *)NULL),
+ fields.module,
0,
fields.errmsg,
fields.lnum,
@@ -1172,6 +994,7 @@ qf_init_end:
fclose(state.fd);
}
xfree(fields.namebuf);
+ xfree(fields.module);
xfree(fields.errmsg);
xfree(fields.pattern);
xfree(state.growbuf);
@@ -1187,6 +1010,8 @@ qf_init_end:
return retval;
}
+/// Set the title of the specified quickfix list. Frees the previous title.
+/// Prepends ':' to the title.
static void qf_store_title(qf_info_T *qi, int qf_idx, char_u *title)
{
xfree(qi->qf_lists[qf_idx].qf_title);
@@ -1197,10 +1022,23 @@ static void qf_store_title(qf_info_T *qi, int qf_idx, char_u *title)
char_u *p = xmallocz(len);
qi->qf_lists[qf_idx].qf_title = p;
- snprintf((char *)p, len + 1, ":%s", (char *)title);
+ xstrlcpy((char *)p, (char *)title, len + 1);
}
}
+/// The title of a quickfix/location list is set, by default, to the command
+/// that created the quickfix list with the ":" prefix.
+/// Create a quickfix list title string by prepending ":" to a user command.
+/// Returns a pointer to a static buffer with the title.
+static char_u * qf_cmdtitle(char_u *cmd)
+{
+ static char_u qftitle_str[IOSIZE];
+
+ snprintf((char *)qftitle_str, IOSIZE, ":%s", (char *)cmd);
+
+ return qftitle_str;
+}
+
// Prepare for adding a new quickfix list. If the current list is in the
// middle of the stack, then all the following lists are freed and then
// the new list is added.
@@ -1230,9 +1068,301 @@ static void qf_new_list(qf_info_T *qi, char_u *qf_title)
qi->qf_lists[qi->qf_curlist].qf_id = ++last_qf_id;
}
-/*
- * Free a location list
- */
+/// Parse the error format matches in 'regmatch' and set the values in 'fields'.
+/// fmt_ptr contains the 'efm' format specifiers/prefixes that have a match.
+/// Returns QF_OK if all the matches are successfully parsed. On failure,
+/// returns QF_FAIL or QF_NOMEM.
+static int qf_parse_match(char_u *linebuf, size_t linelen, efm_T *fmt_ptr,
+ regmatch_T *regmatch, qffields_T *fields,
+ int qf_multiline, int qf_multiscan, char_u **tail)
+{
+ char_u idx = fmt_ptr->prefix;
+ int i;
+ size_t len;
+
+ if ((idx == 'C' || idx == 'Z') && !qf_multiline) {
+ return QF_FAIL;
+ }
+ if (vim_strchr((char_u *)"EWI", idx) != NULL) {
+ fields->type = idx;
+ } else {
+ fields->type = 0;
+ }
+
+ // Extract error message data from matched line.
+ // We check for an actual submatch, because "\[" and "\]" in
+ // the 'errorformat' may cause the wrong submatch to be used.
+ if ((i = (int)fmt_ptr->addr[0]) > 0) { // %f
+ if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) {
+ return QF_FAIL;
+ }
+
+ // Expand ~/file and $HOME/file to full path.
+ char_u c = *regmatch->endp[i];
+ *regmatch->endp[i] = NUL;
+ expand_env(regmatch->startp[i], fields->namebuf, CMDBUFFSIZE);
+ *regmatch->endp[i] = c;
+
+ if (vim_strchr((char_u *)"OPQ", idx) != NULL
+ && !os_path_exists(fields->namebuf)) {
+ return QF_FAIL;
+ }
+ }
+ if ((i = (int)fmt_ptr->addr[1]) > 0) { // %n
+ if (regmatch->startp[i] == NULL) {
+ return QF_FAIL;
+ }
+ fields->enr = (int)atol((char *)regmatch->startp[i]);
+ }
+ if ((i = (int)fmt_ptr->addr[2]) > 0) { // %l
+ if (regmatch->startp[i] == NULL) {
+ return QF_FAIL;
+ }
+ fields->lnum = atol((char *)regmatch->startp[i]);
+ }
+ if ((i = (int)fmt_ptr->addr[3]) > 0) { // %c
+ if (regmatch->startp[i] == NULL) {
+ return QF_FAIL;
+ }
+ fields->col = (int)atol((char *)regmatch->startp[i]);
+ }
+ if ((i = (int)fmt_ptr->addr[4]) > 0) { // %t
+ if (regmatch->startp[i] == NULL) {
+ return QF_FAIL;
+ }
+ fields->type = *regmatch->startp[i];
+ }
+ if (fmt_ptr->flags == '+' && !qf_multiscan) { // %+
+ if (linelen >= fields->errmsglen) {
+ // linelen + null terminator
+ fields->errmsg = xrealloc(fields->errmsg, linelen + 1);
+ fields->errmsglen = linelen + 1;
+ }
+ STRLCPY(fields->errmsg, linebuf, linelen + 1);
+ } else if ((i = (int)fmt_ptr->addr[5]) > 0) { // %m
+ if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) {
+ return QF_FAIL;
+ }
+ len = (size_t)(regmatch->endp[i] - regmatch->startp[i]);
+ if (len >= fields->errmsglen) {
+ // len + null terminator
+ fields->errmsg = xrealloc(fields->errmsg, len + 1);
+ fields->errmsglen = len + 1;
+ }
+ STRLCPY(fields->errmsg, regmatch->startp[i], len + 1);
+ }
+ if ((i = (int)fmt_ptr->addr[6]) > 0) { // %r
+ if (regmatch->startp[i] == NULL) {
+ return QF_FAIL;
+ }
+ *tail = regmatch->startp[i];
+ }
+ if ((i = (int)fmt_ptr->addr[7]) > 0) { // %p
+ if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) {
+ return QF_FAIL;
+ }
+ fields->col = 0;
+ char_u *match_ptr;
+ for (match_ptr = regmatch->startp[i]; match_ptr != regmatch->endp[i];
+ match_ptr++) {
+ fields->col++;
+ if (*match_ptr == TAB) {
+ fields->col += 7;
+ fields->col -= fields->col % 8;
+ }
+ }
+ fields->col++;
+ fields->use_viscol = true;
+ }
+ if ((i = (int)fmt_ptr->addr[8]) > 0) { // %v
+ if (regmatch->startp[i] == NULL) {
+ return QF_FAIL;
+ }
+ fields->col = (int)atol((char *)regmatch->startp[i]);
+ fields->use_viscol = true;
+ }
+ if ((i = (int)fmt_ptr->addr[9]) > 0) { // %s
+ if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) {
+ return QF_FAIL;
+ }
+ len = (size_t)(regmatch->endp[i] - regmatch->startp[i]);
+ if (len > CMDBUFFSIZE - 5) {
+ len = CMDBUFFSIZE - 5;
+ }
+ STRCPY(fields->pattern, "^\\V");
+ STRNCAT(fields->pattern, regmatch->startp[i], len);
+ fields->pattern[len + 3] = '\\';
+ fields->pattern[len + 4] = '$';
+ fields->pattern[len + 5] = NUL;
+ }
+ if ((i = (int)fmt_ptr->addr[10]) > 0) { // %o
+ if (regmatch->startp[i] == NULL || regmatch->endp[i] == NULL) {
+ return QF_FAIL;
+ }
+ len = (size_t)(regmatch->endp[i] - regmatch->startp[i]);
+ if (len > CMDBUFFSIZE) {
+ len = CMDBUFFSIZE;
+ }
+ STRNCAT(fields->module, regmatch->startp[i], len);
+ }
+
+ return QF_OK;
+}
+
+/// Parse an error line in 'linebuf' using a single error format string in
+/// 'fmt_ptr->prog' and return the matching values in 'fields'.
+/// Returns QF_OK if the efm format matches completely and the fields are
+/// successfully copied. Otherwise returns QF_FAIL or QF_NOMEM.
+static int qf_parse_get_fields(char_u *linebuf, size_t linelen, efm_T *fmt_ptr,
+ qffields_T *fields, int qf_multiline,
+ int qf_multiscan, char_u **tail)
+{
+ regmatch_T regmatch;
+ int status = QF_FAIL;
+ int r;
+
+ if (qf_multiscan && vim_strchr((char_u *)"OPQ", fmt_ptr->prefix) == NULL) {
+ return QF_FAIL;
+ }
+
+ fields->namebuf[0] = NUL;
+ fields->module[0] = NUL;
+ fields->pattern[0] = NUL;
+ if (!qf_multiscan) {
+ fields->errmsg[0] = NUL;
+ }
+ fields->lnum = 0;
+ fields->col = 0;
+ fields->use_viscol = false;
+ fields->enr = -1;
+ fields->type = 0;
+ *tail = NULL;
+
+ // Always ignore case when looking for a matching error.
+ regmatch.rm_ic = true;
+ regmatch.regprog = fmt_ptr->prog;
+ r = vim_regexec(&regmatch, linebuf, (colnr_T)0);
+ fmt_ptr->prog = regmatch.regprog;
+ if (r) {
+ status = qf_parse_match(linebuf, linelen, fmt_ptr, &regmatch, fields,
+ qf_multiline, qf_multiscan, tail);
+ }
+
+ return status;
+}
+
+/// Parse directory error format prefixes (%D and %X).
+/// Push and pop directories from the directory stack when scanning directory
+/// names.
+static int qf_parse_dir_pfx(int idx, qffields_T *fields, qf_list_T *qfl)
+{
+ if (idx == 'D') { // enter directory
+ if (*fields->namebuf == NUL) {
+ EMSG(_("E379: Missing or empty directory name"));
+ return QF_FAIL;
+ }
+ qfl->qf_directory = qf_push_dir(fields->namebuf, &qfl->qf_dir_stack, false);
+ if (qfl->qf_directory == NULL) {
+ return QF_FAIL;
+ }
+ } else if (idx == 'X') { // leave directory
+ qfl->qf_directory = qf_pop_dir(&qfl->qf_dir_stack);
+ }
+
+ return QF_OK;
+}
+
+/// Parse global file name error format prefixes (%O, %P and %Q).
+static int qf_parse_file_pfx(int idx, qffields_T *fields, qf_list_T *qfl,
+ char_u *tail)
+{
+ fields->valid = false;
+ if (*fields->namebuf == NUL || os_path_exists(fields->namebuf)) {
+ if (*fields->namebuf && idx == 'P') {
+ qfl->qf_currfile = qf_push_dir(fields->namebuf, &qfl->qf_file_stack,
+ true);
+ } else if (idx == 'Q') {
+ qfl->qf_currfile = qf_pop_dir(&qfl->qf_file_stack);
+ }
+ *fields->namebuf = NUL;
+ if (tail && *tail) {
+ STRMOVE(IObuff, skipwhite(tail));
+ qfl->qf_multiscan = true;
+ return QF_MULTISCAN;
+ }
+ }
+
+ return QF_OK;
+}
+
+/// Parse a non-error line (a line which doesn't match any of the error
+/// format in 'efm').
+static int qf_parse_line_nomatch(char_u *linebuf, size_t linelen,
+ qffields_T *fields)
+{
+ fields->namebuf[0] = NUL; // no match found, remove file name
+ fields->lnum = 0; // don't jump to this line
+ fields->valid = false;
+ if (linelen >= fields->errmsglen) {
+ // linelen + null terminator
+ fields->errmsg = xrealloc(fields->errmsg, linelen + 1);
+ fields->errmsglen = linelen + 1;
+ }
+ // copy whole line to error message
+ STRLCPY(fields->errmsg, linebuf, linelen + 1);
+
+ return QF_OK;
+}
+
+/// Parse multi-line error format prefixes (%C and %Z)
+static int qf_parse_multiline_pfx(qf_info_T *qi, int qf_idx, int idx,
+ qf_list_T *qfl, qffields_T *fields)
+{
+ if (!qfl->qf_multiignore) {
+ qfline_T *qfprev = qfl->qf_last;
+
+ if (qfprev == NULL) {
+ return QF_FAIL;
+ }
+ if (*fields->errmsg && !qfl->qf_multiignore) {
+ size_t textlen = strlen((char *)qfprev->qf_text);
+ size_t errlen = strlen((char *)fields->errmsg);
+ qfprev->qf_text = xrealloc(qfprev->qf_text, textlen + errlen + 2);
+ qfprev->qf_text[textlen] = '\n';
+ STRCPY(qfprev->qf_text + textlen + 1, fields->errmsg);
+ }
+ if (qfprev->qf_nr == -1) {
+ qfprev->qf_nr = fields->enr;
+ }
+ if (vim_isprintc(fields->type) && !qfprev->qf_type) {
+ // only printable chars allowed
+ qfprev->qf_type = fields->type;
+ }
+
+ if (!qfprev->qf_lnum) {
+ qfprev->qf_lnum = fields->lnum;
+ }
+ if (!qfprev->qf_col) {
+ qfprev->qf_col = fields->col;
+ }
+ qfprev->qf_viscol = fields->use_viscol;
+ if (!qfprev->qf_fnum) {
+ qfprev->qf_fnum = qf_get_fnum(qi, qf_idx, qfl->qf_directory,
+ *fields->namebuf || qfl->qf_directory
+ ? fields->namebuf
+ : qfl->qf_currfile && fields->valid
+ ? qfl->qf_currfile : 0);
+ }
+ }
+ if (idx == 'Z') {
+ qfl->qf_multiline = qfl->qf_multiignore = false;
+ }
+ line_breakcheck();
+
+ return QF_IGNORE_LINE;
+}
+
+/// Free a location list.
static void ll_free_all(qf_info_T **pqi)
{
int i;
@@ -1252,6 +1382,7 @@ static void ll_free_all(qf_info_T **pqi)
}
}
+/// Free all the quickfix/location lists in the stack.
void qf_free_all(win_T *wp)
{
int i;
@@ -1273,6 +1404,7 @@ void qf_free_all(win_T *wp)
/// @param qf_idx list index
/// @param dir optional directory name
/// @param fname file name or NULL
+/// @param module module name or NULL
/// @param bufnum buffer number or zero
/// @param mesg message
/// @param lnum line number
@@ -1285,9 +1417,9 @@ void qf_free_all(win_T *wp)
///
/// @returns OK or FAIL.
static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname,
- int bufnum, char_u *mesg, long lnum, int col,
- char_u vis_col, char_u *pattern, int nr, char_u type,
- char_u valid)
+ char_u *module, int bufnum, char_u *mesg, long lnum,
+ int col, char_u vis_col, char_u *pattern, int nr,
+ char_u type, char_u valid)
{
qfline_T *qfp = xmalloc(sizeof(qfline_T));
qfline_T **lastp; // pointer to qf_last or NULL
@@ -1312,9 +1444,15 @@ static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname,
} else {
qfp->qf_pattern = vim_strsave(pattern);
}
+ if (module == NULL || *module == NUL) {
+ qfp->qf_module = NULL;
+ } else {
+ qfp->qf_module = vim_strsave(module);
+ }
qfp->qf_nr = nr;
- if (type != 1 && !vim_isprintc(type)) /* only printable chars allowed */
+ if (type != 1 && !vim_isprintc(type)) { // only printable chars allowed
type = 0;
+ }
qfp->qf_type = (char_u)type;
qfp->qf_valid = valid;
@@ -1443,6 +1581,7 @@ void copy_loclist(win_T *from, win_T *to)
to->w_llist->qf_curlist,
NULL,
NULL,
+ from_qfp->qf_module,
0,
from_qfp->qf_text,
from_qfp->qf_lnum,
@@ -1473,6 +1612,7 @@ void copy_loclist(win_T *from, win_T *to)
// Assign a new ID for the location list
to_qfl->qf_id = ++last_qf_id;
+ to_qfl->qf_changedtick = 0L;
/* When no valid entries are present in the list, qf_ptr points to
* the first item in the list */
@@ -1699,6 +1839,27 @@ static char_u *qf_guess_filepath(qf_info_T *qi, int qf_idx, char_u *filename)
return ds_ptr == NULL ? NULL : ds_ptr->dirname;
}
+/// Returns true, if a quickfix/location list with the given identifier exists.
+static bool qflist_valid(win_T *wp, unsigned int qf_id)
+{
+ qf_info_T *qi = &ql_info;
+
+ if (wp) {
+ qi = GET_LOC_LIST(wp);
+ if (!qi) {
+ return false;
+ }
+ }
+
+ for (int i = 0; i < qi->qf_listcount; i++) {
+ if (qi->qf_lists[i].qf_id == qf_id) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
/// When loading a file from the quickfix, the auto commands may modify it.
/// This may invalidate the current quickfix entry. This function checks
/// whether a entry is still present in the quickfix.
@@ -2050,19 +2211,28 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit,
}
} else {
int old_qf_curlist = qi->qf_curlist;
+ unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id;
retval = buflist_getfile(qf_ptr->qf_fnum, (linenr_T)1,
GETF_SETMARK | GETF_SWITCH, forceit);
- if (qi != &ql_info && !win_valid_any_tab(oldwin)) {
- EMSG(_("E924: Current window was closed"));
- *abort = true;
- *opened_window = false;
+
+ if (qi != &ql_info) {
+ // Location list. Check whether the associated window is still
+ // present and the list is still valid.
+ if (!win_valid_any_tab(oldwin)) {
+ EMSG(_("E924: Current window was closed"));
+ *abort = true;
+ *opened_window = false;
+ } else if (!qflist_valid(oldwin, save_qfid)) {
+ EMSG(_(e_loc_list_changed));
+ *abort = true;
+ }
} else if (old_qf_curlist != qi->qf_curlist
|| !is_qf_entry_present(qi, qf_ptr)) {
if (qi == &ql_info) {
EMSG(_("E925: Current quickfix was changed"));
} else {
- EMSG(_("E926: Current location list was changed"));
+ EMSG(_(e_loc_list_changed));
}
*abort = true;
}
@@ -2360,17 +2530,22 @@ void qf_list(exarg_T *eap)
break;
fname = NULL;
- if (qfp->qf_fnum != 0
- && (buf = buflist_findnr(qfp->qf_fnum)) != NULL) {
- fname = buf->b_fname;
- if (qfp->qf_type == 1) /* :helpgrep */
- fname = path_tail(fname);
+ if (qfp->qf_module != NULL && *qfp->qf_module != NUL) {
+ vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", i,
+ (char *)qfp->qf_module);
+ } else {
+ if (qfp->qf_fnum != 0 && (buf = buflist_findnr(qfp->qf_fnum)) != NULL) {
+ fname = buf->b_fname;
+ if (qfp->qf_type == 1) { // :helpgrep
+ fname = path_tail(fname);
+ }
+ }
+ if (fname == NULL) {
+ snprintf((char *)IObuff, IOSIZE, "%2d", i);
+ } else {
+ vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", i, (char *)fname);
+ }
}
- if (fname == NULL)
- sprintf((char *)IObuff, "%2d", i);
- else
- vim_snprintf((char *)IObuff, IOSIZE, "%2d %s",
- i, (char *)fname);
msg_outtrans_attr(IObuff, i == qi->qf_lists[qi->qf_curlist].qf_index
? HL_ATTR(HLF_QFL) : HL_ATTR(HLF_D));
if (qfp->qf_lnum == 0) {
@@ -2431,6 +2606,8 @@ static void qf_fmt_text(char_u *text, char_u *buf, int bufsize)
buf[i] = NUL;
}
+/// Display information (list number, list size and the title) about a
+/// quickfix/location list.
static void qf_msg(qf_info_T *qi, int which, char *lead)
{
char *title = (char *)qi->qf_lists[which].qf_title;
@@ -2500,6 +2677,7 @@ void qf_age(exarg_T *eap)
qf_update_buffer(qi, NULL);
}
+/// Display the information about all the quickfix/location lists in the stack.
void qf_history(exarg_T *eap)
{
qf_info_T *qi = &ql_info;
@@ -2531,9 +2709,10 @@ static void qf_free_items(qf_info_T *qi, int idx)
qfp = qfl->qf_start;
qfpnext = qfp->qf_next;
if (!stop) {
+ xfree(qfp->qf_module);
xfree(qfp->qf_text);
- stop = (qfp == qfpnext);
xfree(qfp->qf_pattern);
+ stop = (qfp == qfpnext);
xfree(qfp);
if (stop) {
// Somehow qf_count may have an incorrect value, set it to 1
@@ -2575,6 +2754,7 @@ static void qf_free(qf_info_T *qi, int idx)
tv_free(qfl->qf_ctx);
qfl->qf_ctx = NULL;
qfl->qf_id = 0;
+ qfl->qf_changedtick = 0L;
}
/*
@@ -2944,10 +3124,8 @@ static int is_qf_win(win_T *win, qf_info_T *qi)
return FALSE;
}
-/*
- * Find a window displaying the quickfix/location list 'qi'
- * Searches in only the windows opened in the current tab.
- */
+/// Find a window displaying the quickfix/location list 'qi'
+/// Only searches in the current tabpage.
static win_T *qf_find_win(qf_info_T *qi)
{
FOR_ALL_WINDOWS_IN_TAB(win, curtab) {
@@ -3075,9 +3253,12 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last)
lnum = buf->b_ml.ml_line_count;
}
while (lnum < qi->qf_lists[qi->qf_curlist].qf_count) {
- if (qfp->qf_fnum != 0
- && (errbuf = buflist_findnr(qfp->qf_fnum)) != NULL
- && errbuf->b_fname != NULL) {
+ if (qfp->qf_module != NULL) {
+ STRCPY(IObuff, qfp->qf_module);
+ len = (int)STRLEN(IObuff);
+ } else if (qfp->qf_fnum != 0
+ && (errbuf = buflist_findnr(qfp->qf_fnum)) != NULL
+ && errbuf->b_fname != NULL) {
if (qfp->qf_type == 1) { // :helpgrep
STRLCPY(IObuff, path_tail(errbuf->b_fname), sizeof(IObuff));
} else {
@@ -3165,6 +3346,11 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last)
KeyTyped = old_KeyTyped;
}
+static void qf_list_changed(qf_info_T *qi, int qf_idx)
+{
+ qi->qf_lists[qf_idx].qf_changedtick++;
+}
+
/*
* Return TRUE when using ":vimgrep" for ":grep".
*/
@@ -3248,17 +3434,21 @@ void ex_make(exarg_T *eap)
res = qf_init(wp, fname, (eap->cmdidx != CMD_make
&& eap->cmdidx != CMD_lmake) ? p_gefm : p_efm,
(eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd),
- *eap->cmdlinep, enc);
+ qf_cmdtitle(*eap->cmdlinep), enc);
if (wp != NULL) {
qi = GET_LOC_LIST(wp);
}
+ if (res >= 0 && qi != NULL) {
+ qf_list_changed(qi, qi->qf_curlist);
+ }
if (au_name != NULL) {
- apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name,
- curbuf->b_fname, TRUE, curbuf);
- if (qi->qf_curlist < qi->qf_listcount)
+ apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, true,
+ curbuf);
+ if (qi != NULL && qi->qf_curlist < qi->qf_listcount) {
res = qi->qf_lists[qi->qf_curlist].qf_count;
- else
+ } else {
res = 0;
+ }
}
if (res > 0 && !eap->forceit)
qf_jump(qi, 0, 0, FALSE); /* display first error */
@@ -3602,19 +3792,218 @@ void ex_cfile(exarg_T *eap)
// first error.
// :caddfile adds to an existing quickfix list. If there is no
// quickfix list then a new list is created.
- if (qf_init(wp, p_ef, p_efm, (eap->cmdidx != CMD_caddfile
- && eap->cmdidx != CMD_laddfile),
- *eap->cmdlinep, enc) > 0
- && (eap->cmdidx == CMD_cfile
- || eap->cmdidx == CMD_lfile)) {
- if (au_name != NULL)
- apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, NULL, FALSE, curbuf);
- if (wp != NULL)
- qi = GET_LOC_LIST(wp);
- qf_jump(qi, 0, 0, eap->forceit); /* display first error */
+ int res = qf_init(wp, p_ef, p_efm, (eap->cmdidx != CMD_caddfile
+ && eap->cmdidx != CMD_laddfile),
+ qf_cmdtitle(*eap->cmdlinep), enc);
+ if (wp != NULL) {
+ qi = GET_LOC_LIST(wp);
+ }
+ if (res >= 0 && qi != NULL) {
+ qf_list_changed(qi, qi->qf_curlist);
+ }
+ unsigned save_qfid = 0;
+ if (qi != NULL) {
+ save_qfid = qi->qf_lists[qi->qf_curlist].qf_id;
+ }
+ if (au_name != NULL) {
+ apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, NULL, false, curbuf);
+ }
+ // Autocmd might have freed the quickfix/location list. Check whether it is
+ // still valid
+ if (qi != NULL && !qflist_valid(wp, save_qfid)) {
+ return;
+ }
+ if (res > 0 && (eap->cmdidx == CMD_cfile || eap->cmdidx == CMD_lfile)) {
+ qf_jump(qi, 0, 0, eap->forceit); // display first error
+ }
+}
+
+/// Return the vimgrep autocmd name.
+static char_u *vgr_get_auname(cmdidx_T cmdidx)
+{
+ switch (cmdidx) {
+ case CMD_vimgrep: return (char_u *)"vimgrep";
+ case CMD_lvimgrep: return (char_u *)"lvimgrep";
+ case CMD_vimgrepadd: return (char_u *)"vimgrepadd";
+ case CMD_lvimgrepadd: return (char_u *)"lvimgrepadd";
+ case CMD_grep: return (char_u *)"grep";
+ case CMD_lgrep: return (char_u *)"lgrep";
+ case CMD_grepadd: return (char_u *)"grepadd";
+ case CMD_lgrepadd: return (char_u *)"lgrepadd";
+ default: return NULL;
+ }
+}
+
+/// Initialize the regmatch used by vimgrep for pattern "s".
+static void vgr_init_regmatch(regmmatch_T *regmatch, char_u *s)
+{
+ // Get the search pattern: either white-separated or enclosed in //.
+ regmatch->regprog = NULL;
+
+ if (s == NULL || *s == NUL) {
+ // Pattern is empty, use last search pattern.
+ if (last_search_pat() == NULL) {
+ EMSG(_(e_noprevre));
+ return;
+ }
+ regmatch->regprog = vim_regcomp(last_search_pat(), RE_MAGIC);
+ } else {
+ regmatch->regprog = vim_regcomp(s, RE_MAGIC);
+ }
+
+ regmatch->rmm_ic = p_ic;
+ regmatch->rmm_maxcol = 0;
+}
+
+
+/// Display a file name when vimgrep is running.
+static void vgr_display_fname(char_u *fname)
+{
+ msg_start();
+ char_u *p = msg_strtrunc(fname, true);
+ if (p == NULL) {
+ msg_outtrans(fname);
} else {
- if (au_name != NULL)
- apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, NULL, FALSE, curbuf);
+ msg_outtrans(p);
+ xfree(p);
+ }
+ msg_clr_eos();
+ msg_didout = false; // overwrite this message
+ msg_nowait = true; // don't wait for this message
+ msg_col = 0;
+ ui_flush();
+}
+
+/// Load a dummy buffer to search for a pattern using vimgrep.
+static buf_T *vgr_load_dummy_buf(char_u *fname, char_u *dirname_start,
+ char_u *dirname_now)
+{
+ char_u *save_ei = NULL;
+
+ // Don't do Filetype autocommands to avoid loading syntax and
+ // indent scripts, a great speed improvement.
+ long save_mls = p_mls;
+ p_mls = 0;
+
+ // Load file into a buffer, so that 'fileencoding' is detected,
+ // autocommands applied, etc.
+ buf_T *buf = load_dummy_buffer(fname, dirname_start, dirname_now);
+
+ p_mls = save_mls;
+ au_event_restore(save_ei);
+
+ return buf;
+}
+
+/// Check whether a quickfix/location list is valid. Autocmds may remove or
+/// change a quickfix list when vimgrep is running. If the list is not found,
+/// create a new list.
+static bool vgr_qflist_valid(qf_info_T *qi, unsigned save_qfid,
+ qfline_T *cur_qf_start, int loclist_cmd,
+ char_u *title)
+{
+ if (loclist_cmd) {
+ // Verify that the location list is still valid. An autocmd might have
+ // freed the location list.
+ if (!qflist_valid(curwin, save_qfid)) {
+ EMSG(_(e_loc_list_changed));
+ return false;
+ }
+ }
+ if (cur_qf_start != qi->qf_lists[qi->qf_curlist].qf_start) {
+ int idx;
+ // Autocommands changed the quickfix list. Find the one we were using
+ // and restore it.
+ for (idx = 0; idx < LISTCOUNT; idx++) {
+ if (cur_qf_start == qi->qf_lists[idx].qf_start) {
+ qi->qf_curlist = idx;
+ break;
+ }
+ }
+ if (idx == LISTCOUNT) {
+ // List cannot be found, create a new one.
+ qf_new_list(qi, title);
+ }
+ }
+
+ return true;
+}
+
+
+/// Search for a pattern in all the lines in a buffer and add the matching lines
+/// to a quickfix list.
+static bool vgr_match_buflines(qf_info_T *qi, char_u *fname, buf_T *buf,
+ regmmatch_T *regmatch, long tomatch,
+ int duplicate_name, int flags)
+{
+ bool found_match = false;
+
+ for (long lnum = 1; lnum <= buf->b_ml.ml_line_count && tomatch > 0; lnum++) {
+ colnr_T col = 0;
+ while (vim_regexec_multi(regmatch, curwin, buf, lnum, col, NULL,
+ NULL) > 0) {
+ // Pass the buffer number so that it gets used even for a
+ // dummy buffer, unless duplicate_name is set, then the
+ // buffer will be wiped out below.
+ if (qf_add_entry(qi,
+ qi->qf_curlist,
+ NULL, // dir
+ fname,
+ NULL,
+ duplicate_name ? 0 : buf->b_fnum,
+ ml_get_buf(buf, regmatch->startpos[0].lnum + lnum,
+ false),
+ regmatch->startpos[0].lnum + lnum,
+ regmatch->startpos[0].col + 1,
+ false, // vis_col
+ NULL, // search pattern
+ 0, // nr
+ 0, // type
+ true // valid
+ ) == FAIL) {
+ got_int = true;
+ break;
+ }
+ found_match = true;
+ if (--tomatch == 0) {
+ break;
+ }
+ if ((flags & VGR_GLOBAL) == 0 || regmatch->endpos[0].lnum > 0) {
+ break;
+ }
+ col = regmatch->endpos[0].col + (col == regmatch->endpos[0].col);
+ if (col > (colnr_T)STRLEN(ml_get_buf(buf, lnum, false))) {
+ break;
+ }
+ }
+ line_breakcheck();
+ if (got_int) {
+ break;
+ }
+ }
+
+ return found_match;
+}
+
+/// Jump to the first match and update the directory.
+static void vgr_jump_to_match(qf_info_T *qi, int forceit, int *redraw_for_dummy,
+ buf_T *first_match_buf, char_u *target_dir)
+{
+ buf_T *buf = curbuf;
+ qf_jump(qi, 0, 0, forceit);
+ if (buf != curbuf) {
+ // If we jumped to another buffer redrawing will already be
+ // taken care of.
+ *redraw_for_dummy = false;
+ }
+
+ // Jump to the directory used after loading the buffer.
+ if (curbuf == first_match_buf && target_dir != NULL) {
+ exarg_T ea = {
+ .arg = target_dir,
+ .cmdidx = CMD_lcd,
+ };
+ ex_cd(&ea);
}
}
@@ -3634,8 +4023,9 @@ void ex_vimgrep(exarg_T *eap)
char_u *p;
int fi;
qf_info_T *qi = &ql_info;
+ int loclist_cmd = false;
qfline_T *cur_qf_start;
- long lnum;
+ win_T *wp;
buf_T *buf;
int duplicate_name = FALSE;
int using_dummy;
@@ -3643,28 +4033,15 @@ void ex_vimgrep(exarg_T *eap)
int found_match;
buf_T *first_match_buf = NULL;
time_t seconds = 0;
- long save_mls;
- char_u *save_ei = NULL;
aco_save_T aco;
int flags = 0;
- colnr_T col;
long tomatch;
char_u *dirname_start = NULL;
char_u *dirname_now = NULL;
char_u *target_dir = NULL;
char_u *au_name = NULL;
- switch (eap->cmdidx) {
- case CMD_vimgrep: au_name = (char_u *)"vimgrep"; break;
- case CMD_lvimgrep: au_name = (char_u *)"lvimgrep"; break;
- case CMD_vimgrepadd: au_name = (char_u *)"vimgrepadd"; break;
- case CMD_lvimgrepadd: au_name = (char_u *)"lvimgrepadd"; break;
- case CMD_grep: au_name = (char_u *)"grep"; break;
- case CMD_lgrep: au_name = (char_u *)"lgrep"; break;
- case CMD_grepadd: au_name = (char_u *)"grepadd"; break;
- case CMD_lgrepadd: au_name = (char_u *)"lgrepadd"; break;
- default: break;
- }
+ au_name = vgr_get_auname(eap->cmdidx);
if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name,
curbuf->b_fname, true, curbuf)) {
if (aborting()) {
@@ -3677,6 +4054,7 @@ void ex_vimgrep(exarg_T *eap)
|| eap->cmdidx == CMD_lgrepadd
|| eap->cmdidx == CMD_lvimgrepadd) {
qi = ll_get_or_alloc_list(curwin);
+ loclist_cmd = true;
}
if (eap->addr_count > 0)
@@ -3686,28 +4064,17 @@ void ex_vimgrep(exarg_T *eap)
/* Get the search pattern: either white-separated or enclosed in // */
regmatch.regprog = NULL;
- char_u *title = vim_strsave(*eap->cmdlinep);
+ char_u *title = vim_strsave(qf_cmdtitle(*eap->cmdlinep));
p = skip_vimgrep_pat(eap->arg, &s, &flags);
if (p == NULL) {
EMSG(_(e_invalpat));
goto theend;
}
- if (s == NULL || *s == NUL) {
- // Pattern is empty, use last search pattern.
- if (last_search_pat() == NULL) {
- EMSG(_(e_noprevre));
- goto theend;
- }
- regmatch.regprog = vim_regcomp(last_search_pat(), RE_MAGIC);
- } else {
- regmatch.regprog = vim_regcomp(s, RE_MAGIC);
- }
-
- if (regmatch.regprog == NULL)
+ vgr_init_regmatch(&regmatch, s);
+ if (regmatch.regprog == NULL) {
goto theend;
- regmatch.rmm_ic = p_ic;
- regmatch.rmm_maxcol = 0;
+ }
p = skipwhite(p);
if (*p == NUL) {
@@ -3737,8 +4104,9 @@ void ex_vimgrep(exarg_T *eap)
* ":lcd %:p:h" changes the meaning of short path names. */
os_dirname(dirname_start, MAXPATHL);
- /* Remember the value of qf_start, so that we can check for autocommands
- * changing the current quickfix list. */
+ // Remember the current values of the quickfix list and qf_start, so that
+ // we can check for autocommands changing the current quickfix list.
+ unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id;
cur_qf_start = qi->qf_lists[qi->qf_curlist].qf_start;
seconds = (time_t)0;
@@ -3748,19 +4116,7 @@ void ex_vimgrep(exarg_T *eap)
/* Display the file name every second or so, show the user we are
* working on it. */
seconds = time(NULL);
- msg_start();
- p = msg_strtrunc(fname, TRUE);
- if (p == NULL)
- msg_outtrans(fname);
- else {
- msg_outtrans(p);
- xfree(p);
- }
- msg_clr_eos();
- msg_didout = FALSE; /* overwrite this message */
- msg_nowait = TRUE; /* don't wait for this message */
- msg_col = 0;
- ui_flush();
+ vgr_display_fname(fname);
}
buf = buflist_findname_exp(fnames[fi]);
@@ -3770,88 +4126,28 @@ void ex_vimgrep(exarg_T *eap)
using_dummy = TRUE;
redraw_for_dummy = TRUE;
- /* Don't do Filetype autocommands to avoid loading syntax and
- * indent scripts, a great speed improvement. */
- save_ei = au_event_disable(",Filetype");
- /* Don't use modelines here, it's useless. */
- save_mls = p_mls;
- p_mls = 0;
-
- /* Load file into a buffer, so that 'fileencoding' is detected,
- * autocommands applied, etc. */
- buf = load_dummy_buffer(fname, dirname_start, dirname_now);
-
- p_mls = save_mls;
- au_event_restore(save_ei);
- } else
- /* Use existing, loaded buffer. */
- using_dummy = FALSE;
-
- if (cur_qf_start != qi->qf_lists[qi->qf_curlist].qf_start) {
- int idx;
+ buf = vgr_load_dummy_buf(fname, dirname_start, dirname_now);
+ } else {
+ // Use existing, loaded buffer.
+ using_dummy = false;
+ }
- /* Autocommands changed the quickfix list. Find the one we were
- * using and restore it. */
- for (idx = 0; idx < LISTCOUNT; ++idx)
- if (cur_qf_start == qi->qf_lists[idx].qf_start) {
- qi->qf_curlist = idx;
- break;
- }
- if (idx == LISTCOUNT) {
- /* List cannot be found, create a new one. */
- qf_new_list(qi, *eap->cmdlinep);
- cur_qf_start = qi->qf_lists[qi->qf_curlist].qf_start;
- }
+ // Check whether the quickfix list is still valid
+ if (!vgr_qflist_valid(qi, save_qfid, cur_qf_start, loclist_cmd,
+ *eap->cmdlinep)) {
+ goto theend;
}
+ cur_qf_start = qi->qf_lists[qi->qf_curlist].qf_start;
if (buf == NULL) {
if (!got_int)
smsg(_("Cannot open file \"%s\""), fname);
} else {
- /* Try for a match in all lines of the buffer.
- * For ":1vimgrep" look for first match only. */
- found_match = FALSE;
- for (lnum = 1; lnum <= buf->b_ml.ml_line_count && tomatch > 0;
- ++lnum) {
- col = 0;
- while (vim_regexec_multi(&regmatch, curwin, buf, lnum,
- col, NULL, NULL) > 0) {
- // Pass the buffer number so that it gets used even for a
- // dummy buffer, unless duplicate_name is set, then the
- // buffer will be wiped out below.
- if (qf_add_entry(qi,
- qi->qf_curlist,
- NULL, // dir
- fname,
- duplicate_name ? 0 : buf->b_fnum,
- ml_get_buf(buf,
- regmatch.startpos[0].lnum + lnum, false),
- regmatch.startpos[0].lnum + lnum,
- regmatch.startpos[0].col + 1,
- false, // vis_col
- NULL, // search pattern
- 0, // nr
- 0, // type
- true) // valid
- == FAIL) {
- got_int = true;
- break;
- }
- found_match = TRUE;
- if (--tomatch == 0)
- break;
- if ((flags & VGR_GLOBAL) == 0
- || regmatch.endpos[0].lnum > 0)
- break;
- col = regmatch.endpos[0].col
- + (col == regmatch.endpos[0].col);
- if (col > (colnr_T)STRLEN(ml_get_buf(buf, lnum, FALSE)))
- break;
- }
- line_breakcheck();
- if (got_int)
- break;
- }
+ // Try for a match in all lines of the buffer.
+ // For ":1vimgrep" look for first match only.
+ found_match = vgr_match_buflines(qi, fname, buf, &regmatch, tomatch,
+ duplicate_name, flags);
+
cur_qf_start = qi->qf_lists[qi->qf_curlist].qf_start;
if (using_dummy) {
@@ -3914,6 +4210,7 @@ void ex_vimgrep(exarg_T *eap)
qi->qf_lists[qi->qf_curlist].qf_nonevalid = FALSE;
qi->qf_lists[qi->qf_curlist].qf_ptr = qi->qf_lists[qi->qf_curlist].qf_start;
qi->qf_lists[qi->qf_curlist].qf_index = 1;
+ qf_list_changed(qi, qi->qf_curlist);
qf_update_buffer(qi, NULL);
@@ -3921,24 +4218,18 @@ void ex_vimgrep(exarg_T *eap)
apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name,
curbuf->b_fname, TRUE, curbuf);
+ // The QuickFixCmdPost autocmd may free the quickfix list. Check the list
+ // is still valid.
+ wp = loclist_cmd ? curwin : NULL;
+ if (!qflist_valid(wp, save_qfid)) {
+ goto theend;
+ }
+
/* Jump to first match. */
if (qi->qf_lists[qi->qf_curlist].qf_count > 0) {
if ((flags & VGR_NOJUMP) == 0) {
- buf = curbuf;
- qf_jump(qi, 0, 0, eap->forceit);
- if (buf != curbuf)
- /* If we jumped to another buffer redrawing will already be
- * taken care of. */
- redraw_for_dummy = FALSE;
-
- /* Jump to the directory used after loading the buffer. */
- if (curbuf == first_match_buf && target_dir != NULL) {
- exarg_T ea;
-
- ea.arg = target_dir;
- ea.cmdidx = CMD_lcd;
- ex_cd(&ea);
- }
+ vgr_jump_to_match(qi, eap->forceit, &redraw_for_dummy, first_match_buf,
+ target_dir);
}
} else
EMSG2(_(e_nomatch2), s);
@@ -3969,10 +4260,10 @@ static void restore_start_dir(char_u *dirname_start)
if (STRCMP(dirname_start, dirname_now) != 0) {
/* If the directory has changed, change it back by building up an
* appropriate ex command and executing it. */
- exarg_T ea;
-
- ea.arg = dirname_start;
- ea.cmdidx = (curwin->w_localdir == NULL) ? CMD_cd : CMD_lcd;
+ exarg_T ea = {
+ .arg = dirname_start,
+ .cmdidx = (curwin->w_localdir == NULL) ? CMD_cd : CMD_lcd,
+ };
ex_cd(&ea);
}
xfree(dirname_now);
@@ -4168,6 +4459,10 @@ int get_errorlist(const qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list)
|| (tv_dict_add_nr(dict, S_LEN("vcol"), (varnumber_T)qfp->qf_viscol)
== FAIL)
|| (tv_dict_add_nr(dict, S_LEN("nr"), (varnumber_T)qfp->qf_nr) == FAIL)
+ || tv_dict_add_str(dict, S_LEN("module"),
+ (qfp->qf_module == NULL
+ ? ""
+ : (const char *)qfp->qf_module)) == FAIL
|| tv_dict_add_str(dict, S_LEN("pattern"),
(qfp->qf_pattern == NULL
? ""
@@ -4203,10 +4498,12 @@ enum {
QF_GETLIST_ID = 0x20,
QF_GETLIST_IDX = 0x40,
QF_GETLIST_SIZE = 0x80,
- QF_GETLIST_ALL = 0xFF
+ QF_GETLIST_TICK = 0x100,
+ QF_GETLIST_ALL = 0x1FF
};
-// Parse text from 'di' and return the quickfix list items
+/// Parse text from 'di' and return the quickfix list items.
+/// Existing quickfix lists are not modified.
static int qf_get_list_from_lines(dict_T *what, dictitem_T *di, dict_T *retdict)
{
int status = FAIL;
@@ -4256,34 +4553,72 @@ static int qf_id2nr(const qf_info_T *const qi, const unsigned qfid)
return -1;
}
-/// Return quickfix/location list details (title) as a
-/// dictionary. 'what' contains the details to return. If 'list_idx' is -1,
-/// then current list is used. Otherwise the specified list is used.
-int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
+/// Return the quickfix/location list window identifier in the current tabpage.
+static int qf_winid(qf_info_T *qi)
{
- qf_info_T *qi = &ql_info;
- dictitem_T *di;
-
- if ((di = tv_dict_find(what, S_LEN("lines"))) != NULL) {
- return qf_get_list_from_lines(what, di, retdict);
+ // The quickfix window can be opened even if the quickfix list is not set
+ // using ":copen". This is not true for location lists.
+ if (qi == NULL) {
+ return 0;
}
+ win_T *win = qf_find_win(qi);
+ if (win != NULL) {
+ return win->handle;
+ }
+ return 0;
+}
- if (wp != NULL) {
- qi = GET_LOC_LIST(wp);
+/// Convert the keys in 'what' to quickfix list property flags.
+static int qf_getprop_keys2flags(dict_T *what)
+{
+ int flags = QF_GETLIST_NONE;
+
+ if (tv_dict_find(what, S_LEN("all")) != NULL) {
+ flags |= QF_GETLIST_ALL;
}
- // List is not present or is empty
- if (qi == NULL || qi->qf_listcount == 0) {
- // If querying for the size of the list, return 0
- if (((di = tv_dict_find(what, S_LEN("nr"))) != NULL)
- && (di->di_tv.v_type == VAR_STRING)
- && (STRCMP(di->di_tv.vval.v_string, "$") == 0)) {
- return tv_dict_add_nr(retdict, S_LEN("nr"), 0);
- }
- return FAIL;
+ if (tv_dict_find(what, S_LEN("title")) != NULL) {
+ flags |= QF_GETLIST_TITLE;
+ }
+ if (tv_dict_find(what, S_LEN("nr")) != NULL) {
+ flags |= QF_GETLIST_NR;
+ }
+ if (tv_dict_find(what, S_LEN("winid")) != NULL) {
+ flags |= QF_GETLIST_WINID;
+ }
+ if (tv_dict_find(what, S_LEN("context")) != NULL) {
+ flags |= QF_GETLIST_CONTEXT;
+ }
+ if (tv_dict_find(what, S_LEN("id")) != NULL) {
+ flags |= QF_GETLIST_ID;
+ }
+ if (tv_dict_find(what, S_LEN("items")) != NULL) {
+ flags |= QF_GETLIST_ITEMS;
+ }
+ if (tv_dict_find(what, S_LEN("idx")) != NULL) {
+ flags |= QF_GETLIST_IDX;
+ }
+ if (tv_dict_find(what, S_LEN("size")) != NULL) {
+ flags |= QF_GETLIST_SIZE;
+ }
+ if (tv_dict_find(what, S_LEN("changedtick")) != NULL) {
+ flags |= QF_GETLIST_TICK;
}
- int status = OK;
- int flags = QF_GETLIST_NONE;
+ return flags;
+}
+
+/// Return the quickfix list index based on 'nr' or 'id' in 'what'.
+///
+/// If 'nr' and 'id' are not present in 'what' then return the current
+/// quickfix list index.
+/// If 'nr' is zero then return the current quickfix list index.
+/// If 'nr' is '$' then return the last quickfix list index.
+/// If 'id' is present then return the index of the quickfix list with that id.
+/// If 'id' is zero then return the quickfix list index specified by 'nr'.
+/// Return -1, if quickfix list is not present or if the stack is empty.
+static int qf_getprop_qfidx(qf_info_T *qi, dict_T *what)
+{
+ dictitem_T *di = NULL;
int qf_idx = qi->qf_curlist; // default is the current list
if ((di = tv_dict_find(what, S_LEN("nr"))) != NULL) {
@@ -4293,7 +4628,7 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
if (di->di_tv.vval.v_number != 0) {
qf_idx = (int)di->di_tv.vval.v_number - 1;
if (qf_idx < 0 || qf_idx >= qi->qf_listcount) {
- return FAIL;
+ qf_idx = -1;
}
}
} else if (di->di_tv.v_type == VAR_STRING
@@ -4301,9 +4636,8 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
// Get the last quickfix list number
qf_idx = qi->qf_listcount - 1;
} else {
- return FAIL;
+ qf_idx = -1;
}
- flags |= QF_GETLIST_NR;
}
if ((di = tv_dict_find(what, S_LEN("id"))) != NULL) {
@@ -4312,91 +4646,160 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
// For zero, use the current list or the list specifed by 'nr'
if (di->di_tv.vval.v_number != 0) {
qf_idx = qf_id2nr(qi, (unsigned)di->di_tv.vval.v_number);
- if (qf_idx == -1) {
- return FAIL; // List not found
- }
}
- flags |= QF_GETLIST_ID;
} else {
- return FAIL;
+ qf_idx = -1;
}
}
- if (tv_dict_find(what, S_LEN("all")) != NULL) {
- flags |= QF_GETLIST_ALL;
+ return qf_idx;
+}
+
+/// Return default values for quickfix list properties in retdict.
+static int qf_getprop_defaults(qf_info_T *qi, int flags, dict_T *retdict)
+{
+ int status = OK;
+
+ if (flags & QF_GETLIST_TITLE) {
+ status = tv_dict_add_str(retdict, S_LEN("title"), (const char *)"");
}
- if (tv_dict_find(what, S_LEN("title")) != NULL) {
- flags |= QF_GETLIST_TITLE;
+ if ((status == OK) && (flags & QF_GETLIST_ITEMS)) {
+ list_T *l = tv_list_alloc(kListLenMayKnow);
+ status = tv_dict_add_list(retdict, S_LEN("items"), l);
}
- if (tv_dict_find(what, S_LEN("winid")) != NULL) {
- flags |= QF_GETLIST_WINID;
+ if ((status == OK) && (flags & QF_GETLIST_NR)) {
+ status = tv_dict_add_nr(retdict, S_LEN("nr"), 0);
}
- if (tv_dict_find(what, S_LEN("context")) != NULL) {
- flags |= QF_GETLIST_CONTEXT;
+ if ((status == OK) && (flags & QF_GETLIST_WINID)) {
+ status = tv_dict_add_nr(retdict, S_LEN("winid"), qf_winid(qi));
}
- if (tv_dict_find(what, S_LEN("items")) != NULL) {
- flags |= QF_GETLIST_ITEMS;
+ if ((status == OK) && (flags & QF_GETLIST_CONTEXT)) {
+ status = tv_dict_add_str(retdict, S_LEN("context"), (const char *)"");
}
- if (tv_dict_find(what, S_LEN("idx")) != NULL) {
- flags |= QF_GETLIST_IDX;
+ if ((status == OK) && (flags & QF_GETLIST_ID)) {
+ status = tv_dict_add_nr(retdict, S_LEN("id"), 0);
}
- if (tv_dict_find(what, S_LEN("size")) != NULL) {
- flags |= QF_GETLIST_SIZE;
+ if ((status == OK) && (flags & QF_GETLIST_IDX)) {
+ status = tv_dict_add_nr(retdict, S_LEN("idx"), 0);
+ }
+ if ((status == OK) && (flags & QF_GETLIST_SIZE)) {
+ status = tv_dict_add_nr(retdict, S_LEN("size"), 0);
+ }
+ if ((status == OK) && (flags & QF_GETLIST_TICK)) {
+ status = tv_dict_add_nr(retdict, S_LEN("changedtick"), 0);
}
+ return status;
+}
- if (flags & QF_GETLIST_TITLE) {
+/// Return the quickfix list title as 'title' in retdict
+static int qf_getprop_title(qf_info_T *qi, int qf_idx, dict_T *retdict)
+{
char_u *t = qi->qf_lists[qf_idx].qf_title;
if (t == NULL) {
t = (char_u *)"";
}
- status = tv_dict_add_str(retdict, S_LEN("title"), (const char *)t);
+ return tv_dict_add_str(retdict, S_LEN("title"), (const char *)t);
+}
+
+/// Return the quickfix list items/entries as 'items' in retdict
+static int qf_getprop_items(qf_info_T *qi, int qf_idx, dict_T *retdict)
+{
+ list_T *l = tv_list_alloc(kListLenMayKnow);
+ get_errorlist(qi, NULL, qf_idx, l);
+ tv_dict_add_list(retdict, S_LEN("items"), l);
+
+ return OK;
+}
+
+/// Return the quickfix list context (if any) as 'context' in retdict.
+static int qf_getprop_ctx(qf_info_T *qi, int qf_idx, dict_T *retdict)
+{
+ int status;
+
+ if (qi->qf_lists[qf_idx].qf_ctx != NULL) {
+ dictitem_T *di = tv_dict_item_alloc_len(S_LEN("context"));
+ tv_copy(qi->qf_lists[qf_idx].qf_ctx, &di->di_tv);
+ status = tv_dict_add(retdict, di);
+ if (status == FAIL) {
+ tv_dict_item_free(di);
+ }
+ } else {
+ status = tv_dict_add_str(retdict, S_LEN("context"), "");
+ }
+
+ return status;
+}
+
+/// Return the quickfix list index as 'idx' in retdict
+static int qf_getprop_idx(qf_info_T *qi, int qf_idx, dict_T *retdict)
+{
+ int idx = qi->qf_lists[qf_idx].qf_index;
+ if (qi->qf_lists[qf_idx].qf_count == 0) {
+ // For empty lists, qf_index is set to 1
+ idx = 0;
+ }
+ return tv_dict_add_nr(retdict, S_LEN("idx"), idx);
+}
+
+/// Return quickfix/location list details (title) as a dictionary.
+/// 'what' contains the details to return. If 'list_idx' is -1,
+/// then current list is used. Otherwise the specified list is used.
+int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
+{
+ qf_info_T *qi = &ql_info;
+ dictitem_T *di = NULL;
+ int status = OK;
+ int qf_idx;
+
+ if ((di = tv_dict_find(what, S_LEN("lines"))) != NULL) {
+ return qf_get_list_from_lines(what, di, retdict);
+ }
+
+ if (wp != NULL) {
+ qi = GET_LOC_LIST(wp);
+ }
+
+ int flags = qf_getprop_keys2flags(what);
+
+ if (qi != NULL && qi->qf_listcount != 0) {
+ qf_idx = qf_getprop_qfidx(qi, what);
+ }
+
+ // List is not present or is empty
+ if (qi == NULL || qi->qf_listcount == 0 || qf_idx == -1) {
+ return qf_getprop_defaults(qi, flags, retdict);
+ }
+
+ if (flags & QF_GETLIST_TITLE) {
+ status = qf_getprop_title(qi, qf_idx, retdict);
}
if ((status == OK) && (flags & QF_GETLIST_NR)) {
status = tv_dict_add_nr(retdict, S_LEN("nr"), qf_idx + 1);
}
if ((status == OK) && (flags & QF_GETLIST_WINID)) {
- win_T *win = qf_find_win(qi);
- if (win != NULL) {
- status = tv_dict_add_nr(retdict, S_LEN("winid"), win->handle);
- }
+ status = tv_dict_add_nr(retdict, S_LEN("winid"), qf_winid(qi));
}
if ((status == OK) && (flags & QF_GETLIST_ITEMS)) {
- list_T *l = tv_list_alloc(kListLenMayKnow);
- (void)get_errorlist(qi, NULL, qf_idx, l);
- tv_dict_add_list(retdict, S_LEN("items"), l);
+ status = qf_getprop_items(qi, qf_idx, retdict);
}
-
if ((status == OK) && (flags & QF_GETLIST_CONTEXT)) {
- if (qi->qf_lists[qf_idx].qf_ctx != NULL) {
- di = tv_dict_item_alloc_len(S_LEN("context"));
- tv_copy(qi->qf_lists[qf_idx].qf_ctx, &di->di_tv);
- status = tv_dict_add(retdict, di);
- if (status == FAIL) {
- tv_dict_item_free(di);
- }
- } else {
- status = tv_dict_add_str(retdict, S_LEN("context"), "");
- }
+ status = qf_getprop_ctx(qi, qf_idx, retdict);
}
-
if ((status == OK) && (flags & QF_GETLIST_ID)) {
status = tv_dict_add_nr(retdict, S_LEN("id"), qi->qf_lists[qf_idx].qf_id);
}
-
if ((status == OK) && (flags & QF_GETLIST_IDX)) {
- int idx = qi->qf_lists[qf_idx].qf_index;
- if (qi->qf_lists[qf_idx].qf_count == 0) {
- // For empty lists, qf_index is set to 1
- idx = 0;
- }
- status = tv_dict_add_nr(retdict, S_LEN("idx"), idx);
+ status = qf_getprop_idx(qi, qf_idx, retdict);
}
-
if ((status == OK) && (flags & QF_GETLIST_SIZE)) {
status = tv_dict_add_nr(retdict, S_LEN("size"),
qi->qf_lists[qf_idx].qf_count);
}
+ if ((status == OK) && (flags & QF_GETLIST_TICK)) {
+ status = tv_dict_add_nr(retdict, S_LEN("changedtick"),
+ qi->qf_lists[qf_idx].qf_changedtick);
+ }
return status;
}
@@ -4434,6 +4837,7 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list,
}
char *const filename = tv_dict_get_string(d, "filename", true);
+ char *const module = tv_dict_get_string(d, "module", true);
int bufnum = (int)tv_dict_get_number(d, "bufnr");
long lnum = (long)tv_dict_get_number(d, "lnum");
int col = (int)tv_dict_get_number(d, "col");
@@ -4471,6 +4875,7 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list,
qf_idx,
NULL, // dir
(char_u *)filename,
+ (char_u *)module,
bufnum,
(char_u *)text,
lnum,
@@ -4482,6 +4887,7 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list,
valid);
xfree(filename);
+ xfree(module);
xfree(pattern);
xfree(text);
@@ -4633,6 +5039,10 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action,
retval = OK;
}
+ if (retval == OK) {
+ qf_list_changed(qi, qf_idx);
+ }
+
return retval;
}
@@ -4714,11 +5124,15 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title,
retval = qf_set_properties(qi, what, action, title);
} else {
retval = qf_add_entries(qi, qi->qf_curlist, list, title, action);
+ if (retval == OK) {
+ qf_list_changed(qi, qi->qf_curlist);
+ }
}
return retval;
}
+/// Mark the context as in use for all the lists in a quickfix stack.
static bool mark_quickfix_ctx(qf_info_T *qi, int copyID)
{
bool abort = false;
@@ -4735,7 +5149,7 @@ static bool mark_quickfix_ctx(qf_info_T *qi, int copyID)
}
/// Mark the context of the quickfix list and the location lists (if present) as
-/// "in use". So that garabage collection doesn't free the context.
+/// "in use". So that garbage collection doesn't free the context.
bool set_ref_in_quickfix(int copyID)
{
bool abort = mark_quickfix_ctx(&ql_info, copyID);
@@ -4820,10 +5234,10 @@ void ex_cbuffer(exarg_T *eap)
eap->line2 = buf->b_ml.ml_line_count;
}
if (eap->line1 < 1 || eap->line1 > buf->b_ml.ml_line_count
- || eap->line2 < 1 || eap->line2 > buf->b_ml.ml_line_count)
+ || eap->line2 < 1 || eap->line2 > buf->b_ml.ml_line_count) {
EMSG(_(e_invrange));
- else {
- char_u *qf_title = *eap->cmdlinep;
+ } else {
+ char_u *qf_title = qf_cmdtitle(*eap->cmdlinep);
if (buf->b_sfname) {
vim_snprintf((char *)IObuff, IOSIZE, "%s (%s)",
@@ -4831,17 +5245,20 @@ void ex_cbuffer(exarg_T *eap)
qf_title = IObuff;
}
- if (qf_init_ext(qi, qi->qf_curlist, NULL, buf, NULL, p_efm,
- (eap->cmdidx != CMD_caddbuffer
- && eap->cmdidx != CMD_laddbuffer),
- eap->line1, eap->line2, qf_title, NULL) > 0) {
- if (au_name != NULL) {
- apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name,
- curbuf->b_fname, true, curbuf);
- }
- if (eap->cmdidx == CMD_cbuffer || eap->cmdidx == CMD_lbuffer) {
- qf_jump(qi, 0, 0, eap->forceit); // display first error
- }
+ int res = qf_init_ext(qi, qi->qf_curlist, NULL, buf, NULL, p_efm,
+ (eap->cmdidx != CMD_caddbuffer
+ && eap->cmdidx != CMD_laddbuffer),
+ eap->line1, eap->line2, qf_title, NULL);
+ if (res >= 0) {
+ qf_list_changed(qi, qi->qf_curlist);
+ }
+ if (au_name != NULL) {
+ apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name,
+ curbuf->b_fname, true, curbuf);
+ }
+ if (res > 0 && (eap->cmdidx == CMD_cbuffer
+ || eap->cmdidx == CMD_lbuffer)) {
+ qf_jump(qi, 0, 0, eap->forceit); // display first error
}
}
}
@@ -4856,11 +5273,6 @@ void ex_cexpr(exarg_T *eap)
qf_info_T *qi = &ql_info;
const char *au_name = NULL;
- if (eap->cmdidx == CMD_lexpr || eap->cmdidx == CMD_lgetexpr
- || eap->cmdidx == CMD_laddexpr) {
- qi = ll_get_or_alloc_list(curwin);
- }
-
switch (eap->cmdidx) {
case CMD_cexpr:
au_name = "cexpr";
@@ -4890,23 +5302,32 @@ void ex_cexpr(exarg_T *eap)
}
}
+ if (eap->cmdidx == CMD_lexpr
+ || eap->cmdidx == CMD_lgetexpr
+ || eap->cmdidx == CMD_laddexpr) {
+ qi = ll_get_or_alloc_list(curwin);
+ }
+
/* Evaluate the expression. When the result is a string or a list we can
* use it to fill the errorlist. */
typval_T tv;
if (eval0(eap->arg, &tv, NULL, true) != FAIL) {
if ((tv.v_type == VAR_STRING && tv.vval.v_string != NULL)
|| tv.v_type == VAR_LIST) {
- if (qf_init_ext(qi, qi->qf_curlist, NULL, NULL, &tv, p_efm,
- (eap->cmdidx != CMD_caddexpr
- && eap->cmdidx != CMD_laddexpr),
- (linenr_T)0, (linenr_T)0, *eap->cmdlinep, NULL) > 0) {
- if (au_name != NULL) {
- apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name,
- curbuf->b_fname, true, curbuf);
- }
- if (eap->cmdidx == CMD_cexpr || eap->cmdidx == CMD_lexpr) {
- qf_jump(qi, 0, 0, eap->forceit); // display first error
- }
+ int res = qf_init_ext(qi, qi->qf_curlist, NULL, NULL, &tv, p_efm,
+ (eap->cmdidx != CMD_caddexpr
+ && eap->cmdidx != CMD_laddexpr),
+ (linenr_T)0, (linenr_T)0,
+ qf_cmdtitle(*eap->cmdlinep), NULL);
+ if (res >= 0) {
+ qf_list_changed(qi, qi->qf_curlist);
+ }
+ if (au_name != NULL) {
+ apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name,
+ curbuf->b_fname, true, curbuf);
+ }
+ if (res > 0 && (eap->cmdidx == CMD_cexpr || eap->cmdidx == CMD_lexpr)) {
+ qf_jump(qi, 0, 0, eap->forceit); // display first error
}
} else {
EMSG(_("E777: String or List expected"));
@@ -4980,16 +5401,13 @@ void ex_helpgrep(exarg_T *eap)
}
}
- // Autocommands may change the list. Save it for later comparison
- qf_info_T *save_qi = qi;
-
regmatch.regprog = vim_regcomp(eap->arg, RE_MAGIC + RE_STRING);
regmatch.rm_ic = FALSE;
if (regmatch.regprog != NULL) {
// Create a new quickfix list.
- qf_new_list(qi, *eap->cmdlinep);
+ qf_new_list(qi, qf_cmdtitle(*eap->cmdlinep));
- /* Go through all directories in 'runtimepath' */
+ // Go through all the directories in 'runtimepath'
p = p_rtp;
while (*p != NUL && !got_int) {
copy_option_part(&p, NameBuff, MAXPATHL, ",");
@@ -5004,15 +5422,15 @@ void ex_helpgrep(exarg_T *eap)
if (gen_expand_wildcards(1, buff_list, &fcount,
&fnames, EW_FILE|EW_SILENT) == OK
&& fcount > 0) {
- for (fi = 0; fi < fcount && !got_int; ++fi) {
- /* Skip files for a different language. */
+ for (fi = 0; fi < fcount && !got_int; fi++) {
+ // Skip files for a different language.
if (lang != NULL
- && STRNICMP(lang, fnames[fi]
- + STRLEN(fnames[fi]) - 3, 2) != 0
+ && STRNICMP(lang, fnames[fi] + STRLEN(fnames[fi]) - 3, 2) != 0
&& !(STRNICMP(lang, "en", 2) == 0
&& STRNICMP("txt", fnames[fi]
- + STRLEN(fnames[fi]) - 3, 3) == 0))
+ + STRLEN(fnames[fi]) - 3, 3) == 0)) {
continue;
+ }
fd = mch_fopen((char *)fnames[fi], "r");
if (fd != NULL) {
lnum = 1;
@@ -5029,6 +5447,7 @@ void ex_helpgrep(exarg_T *eap)
qi->qf_curlist,
NULL, // dir
fnames[fi],
+ NULL,
0,
line,
lnum,
@@ -5073,12 +5492,13 @@ void ex_helpgrep(exarg_T *eap)
/* Darn, some plugin changed the value. */
free_string_option(save_cpo);
+ qf_list_changed(qi, qi->qf_curlist);
qf_update_buffer(qi, NULL);
if (au_name != NULL) {
apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name,
curbuf->b_fname, true, curbuf);
- if (!new_qi && qi != save_qi && qf_find_buf(qi) == NULL) {
+ if (!new_qi && qi != &ql_info && qf_find_buf(qi) == NULL) {
// autocommands made "qi" invalid
return;
}
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index c602e7df0f..39ce7ff844 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -5098,8 +5098,6 @@ static int regmatch(
printf("Premature EOL\n");
#endif
}
- if (status == RA_FAIL)
- got_int = TRUE;
return status == RA_MATCH;
}
@@ -7226,7 +7224,8 @@ static void report_re_switch(char_u *pat)
/// @param nl
///
/// @return TRUE if there is a match, FALSE if not.
-static int vim_regexec_both(regmatch_T *rmp, char_u *line, colnr_T col, bool nl)
+static int vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col,
+ bool nl)
{
regexec_T rex_save;
bool rex_in_use_save = rex_in_use;
@@ -7275,8 +7274,8 @@ static int vim_regexec_both(regmatch_T *rmp, char_u *line, colnr_T col, bool nl)
int vim_regexec_prog(regprog_T **prog, bool ignore_case, char_u *line,
colnr_T col)
{
- regmatch_T regmatch = {.regprog = *prog, .rm_ic = ignore_case};
- int r = vim_regexec_both(&regmatch, line, col, false);
+ regmatch_T regmatch = { .regprog = *prog, .rm_ic = ignore_case };
+ int r = vim_regexec_string(&regmatch, line, col, false);
*prog = regmatch.regprog;
return r;
}
@@ -7285,7 +7284,7 @@ int vim_regexec_prog(regprog_T **prog, bool ignore_case, char_u *line,
// Return TRUE if there is a match, FALSE if not.
int vim_regexec(regmatch_T *rmp, char_u *line, colnr_T col)
{
- return vim_regexec_both(rmp, line, col, false);
+ return vim_regexec_string(rmp, line, col, false);
}
// Like vim_regexec(), but consider a "\n" in "line" to be a line break.
@@ -7293,7 +7292,7 @@ int vim_regexec(regmatch_T *rmp, char_u *line, colnr_T col)
// Return TRUE if there is a match, FALSE if not.
int vim_regexec_nl(regmatch_T *rmp, char_u *line, colnr_T col)
{
- return vim_regexec_both(rmp, line, col, true);
+ return vim_regexec_string(rmp, line, col, true);
}
/// Match a regexp against multiple lines.
diff --git a/src/nvim/regexp_defs.h b/src/nvim/regexp_defs.h
index 298d82fc6b..5e5b19b63f 100644
--- a/src/nvim/regexp_defs.h
+++ b/src/nvim/regexp_defs.h
@@ -94,10 +94,8 @@ typedef struct {
char_u program[1]; /* actually longer.. */
} bt_regprog_T;
-/*
- * Structure representing a NFA state.
- * A NFA state may have no outgoing edge, when it is a NFA_MATCH state.
- */
+// Structure representing a NFA state.
+// An NFA state may have no outgoing edge, when it is a NFA_MATCH state.
typedef struct nfa_state nfa_state_T;
struct nfa_state {
int c;
diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c
index b935b44291..ce7270ae65 100644
--- a/src/nvim/regexp_nfa.c
+++ b/src/nvim/regexp_nfa.c
@@ -4968,13 +4968,15 @@ static int nfa_did_time_out(void)
///
/// When "nfa_endp" is not NULL it is a required end-of-match position.
///
-/// Return TRUE if there is a match, FALSE otherwise.
+/// Return TRUE if there is a match, FALSE if there is no match,
+/// NFA_TOO_EXPENSIVE if we end up with too many states.
/// When there is a match "submatch" contains the positions.
+///
/// Note: Caller must ensure that: start != NULL.
static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
regsubs_T *submatch, regsubs_T *m)
{
- int result;
+ int result = false;
int flag = 0;
bool go_to_nextline = false;
nfa_thread_T *t;
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index b4ebf2ece5..2709ba83f3 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -678,8 +678,6 @@ static void win_update(win_T *wp)
mod_bot = wp->w_redraw_bot + 1;
else
mod_bot = 0;
- wp->w_redraw_top = 0; /* reset for next time */
- wp->w_redraw_bot = 0;
if (buf->b_mod_set) {
if (mod_top == 0 || mod_top > buf->b_mod_top) {
mod_top = buf->b_mod_top;
@@ -776,6 +774,8 @@ static void win_update(win_T *wp)
if (mod_top != 0 && buf->b_mod_xlines != 0 && wp->w_p_nu)
mod_bot = MAXLNUM;
}
+ wp->w_redraw_top = 0; // reset for next time
+ wp->w_redraw_bot = 0;
/*
* When only displaying the lines at the top, set top_end. Used when
@@ -1135,6 +1135,9 @@ static void win_update(win_T *wp)
/* reset got_int, otherwise regexp won't work */
save_got_int = got_int;
got_int = 0;
+ // Set the time limit to 'redrawtime'.
+ proftime_T syntax_tm = profile_setlimit(p_rdt);
+ syn_set_timeout(&syntax_tm);
win_foldinfo.fi_level = 0;
/*
@@ -1493,6 +1496,7 @@ static void win_update(win_T *wp)
if (wp->w_redr_type >= REDRAW_TOP) {
draw_vsep_win(wp, 0);
}
+ syn_set_timeout(NULL);
/* Reset the type of redrawing required, the window has been updated. */
wp->w_redr_type = 0;
@@ -2189,7 +2193,7 @@ win_line (
// To speed up the loop below, set extra_check when there is linebreak,
// trailing white space and/or syntax processing to be done.
extra_check = wp->w_p_lbr;
- if (syntax_present(wp) && !wp->w_s->b_syn_error) {
+ if (syntax_present(wp) && !wp->w_s->b_syn_error && !wp->w_s->b_syn_slow) {
// Prepare for syntax highlighting in this line. When there is an
// error, stop syntax highlighting.
save_did_emsg = did_emsg;
@@ -2199,8 +2203,10 @@ win_line (
wp->w_s->b_syn_error = true;
} else {
did_emsg = save_did_emsg;
- has_syntax = true;
- extra_check = true;
+ if (!wp->w_s->b_syn_slow) {
+ has_syntax = true;
+ extra_check = true;
+ }
}
}
@@ -2440,7 +2446,9 @@ win_line (
}
if (wp->w_p_list) {
- if (curwin->w_p_lcs_chars.space || wp->w_p_lcs_chars.trail) {
+ if (curwin->w_p_lcs_chars.space
+ || wp->w_p_lcs_chars.trail
+ || wp->w_p_lcs_chars.nbsp) {
extra_check = true;
}
// find start of trailing whitespace
@@ -2539,9 +2547,10 @@ win_line (
}
wp->w_cursor = pos;
- /* Need to restart syntax highlighting for this line. */
- if (has_syntax)
+ // Need to restart syntax highlighting for this line.
+ if (has_syntax) {
syntax_start(wp, lnum);
+ }
}
}
@@ -2587,6 +2596,9 @@ win_line (
}
next_search_hl(wp, shl, lnum, (colnr_T)v,
shl == &search_hl ? NULL : cur);
+ if (wp->w_s->b_syn_slow) {
+ has_syntax = false;
+ }
// Need to get the line again, a multi-line regexp may have made it
// invalid.
@@ -3265,16 +3277,18 @@ win_line (
line = ml_get_buf(wp->w_buffer, lnum, FALSE);
ptr = line + v;
- if (!attr_pri)
+ if (!attr_pri) {
char_attr = syntax_attr;
- else
+ } else {
char_attr = hl_combine_attr(syntax_attr, char_attr);
- /* no concealing past the end of the line, it interferes
- * with line highlighting */
- if (c == NUL)
+ }
+ // no concealing past the end of the line, it interferes
+ // with line highlighting.
+ if (c == NUL) {
syntax_flags = 0;
- else
+ } else {
syntax_flags = get_syntax_info(&syntax_seqnr);
+ }
} else if (!attr_pri) {
char_attr = 0;
}
@@ -3366,7 +3380,7 @@ win_line (
}
if (wp->w_buffer->terminal) {
- char_attr = hl_combine_attr(char_attr, term_attrs[vcol]);
+ char_attr = hl_combine_attr(term_attrs[vcol], char_attr);
}
// Found last space before word: check for line break.
@@ -3981,8 +3995,10 @@ win_line (
break;
}
- // line continues beyond line end
- if (wp->w_p_lcs_chars.ext
+ // Show "extends" character from 'listchars' if beyond the line end and
+ // 'list' is set.
+ if (wp->w_p_lcs_chars.ext != NUL
+ && wp->w_p_list
&& !wp->w_p_wrap
&& filler_todo <= 0
&& (wp->w_p_rl ? col == 0 : col == grid->Columns - 1)
@@ -4821,13 +4837,13 @@ static void win_redr_status(win_T *wp)
p = NameBuff;
len = (int)STRLEN(p);
- if (wp->w_buffer->b_help
+ if (bt_help(wp->w_buffer)
|| wp->w_p_pvw
|| bufIsChanged(wp->w_buffer)
|| wp->w_buffer->b_p_ro) {
*(p + len++) = ' ';
}
- if (wp->w_buffer->b_help) {
+ if (bt_help(wp->w_buffer)) {
STRCPY(p + len, _("[Help]"));
len += (int)STRLEN(p + len);
}
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index 6bbf91e0f9..eace4a524c 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -1542,7 +1542,7 @@ static char *shada_filename(const char *file)
FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
{
if (file == NULL || *file == NUL) {
- if (p_shadafile != NULL) {
+ if (p_shadafile != NULL && *p_shadafile != NUL) {
file = p_shadafile;
} else {
if ((file = find_shada_parameter('n')) == NULL || *file == NUL) {
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index 331d8da55a..0fc33bec81 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -7100,9 +7100,9 @@ void ex_spelldump(exarg_T *eap)
spell_dump_compl(NULL, 0, NULL, eap->forceit ? DUMPFLAG_COUNT : 0);
// Delete the empty line that we started with.
- if (curbuf->b_ml.ml_line_count > 1)
- ml_delete(curbuf->b_ml.ml_line_count, FALSE);
-
+ if (curbuf->b_ml.ml_line_count > 1) {
+ ml_delete(curbuf->b_ml.ml_line_count, false);
+ }
redraw_later(NOT_VALID);
}
@@ -7171,7 +7171,7 @@ spell_dump_compl (
if (do_region && region_names != NULL) {
if (pat == NULL) {
vim_snprintf((char *)IObuff, IOSIZE, "/regions=%s", region_names);
- ml_append(lnum++, IObuff, (colnr_T)0, FALSE);
+ ml_append(lnum++, IObuff, (colnr_T)0, false);
}
} else
do_region = false;
@@ -7185,7 +7185,7 @@ spell_dump_compl (
if (pat == NULL) {
vim_snprintf((char *)IObuff, IOSIZE, "# file: %s", slang->sl_fname);
- ml_append(lnum++, IObuff, (colnr_T)0, FALSE);
+ ml_append(lnum++, IObuff, (colnr_T)0, false);
}
// When matching with a pattern and there are no prefixes only use
@@ -7347,14 +7347,15 @@ static void dump_word(slang_T *slang, char_u *word, char_u *pat, int *dir, int d
}
}
- ml_append(lnum, p, (colnr_T)0, FALSE);
+ ml_append(lnum, p, (colnr_T)0, false);
} else if (((dumpflags & DUMPFLAG_ICASE)
? mb_strnicmp(p, pat, STRLEN(pat)) == 0
: STRNCMP(p, pat, STRLEN(pat)) == 0)
&& ins_compl_add_infercase(p, (int)STRLEN(p),
- p_ic, NULL, *dir, 0) == OK)
+ p_ic, NULL, *dir, 0) == OK) {
// if dir was BACKWARD then honor it just once
*dir = FORWARD;
+ }
}
// For ":spelldump": Find matching prefixes for "word". Prepend each to
diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c
index ccad893d61..117939e7e9 100644
--- a/src/nvim/spellfile.c
+++ b/src/nvim/spellfile.c
@@ -955,8 +955,9 @@ someerror:
break;
}
if (ml_append_buf(slang->sl_sugbuf, (linenr_T)wordnr,
- ga.ga_data, ga.ga_len, TRUE) == FAIL)
+ ga.ga_data, ga.ga_len, true) == FAIL) {
goto someerror;
+ }
}
ga_clear(&ga);
@@ -4920,9 +4921,10 @@ sug_filltable (
((char_u *)gap->ga_data)[gap->ga_len++] = NUL;
if (ml_append_buf(spin->si_spellbuf, (linenr_T)wordnr,
- gap->ga_data, gap->ga_len, TRUE) == FAIL)
+ gap->ga_data, gap->ga_len, true) == FAIL) {
return -1;
- ++wordnr;
+ }
+ wordnr++;
// Remove extra NUL entries, we no longer need them. We don't
// bother freeing the nodes, the won't be reused anyway.
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index 8c3ce823d3..1b30161e94 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -356,15 +356,16 @@ static reg_extmatch_T *next_match_extmatch = NULL;
* The current state (within the line) of the recognition engine.
* When current_state.ga_itemsize is 0 the current state is invalid.
*/
-static win_T *syn_win; /* current window for highlighting */
-static buf_T *syn_buf; /* current buffer for highlighting */
-static synblock_T *syn_block; /* current buffer for highlighting */
-static linenr_T current_lnum = 0; /* lnum of current state */
-static colnr_T current_col = 0; /* column of current state */
-static int current_state_stored = 0; /* TRUE if stored current state
- * after setting current_finished */
-static int current_finished = 0; /* current line has been finished */
-static garray_T current_state /* current stack of state_items */
+static win_T *syn_win; // current window for highlighting
+static buf_T *syn_buf; // current buffer for highlighting
+static synblock_T *syn_block; // current buffer for highlighting
+static proftime_T *syn_tm; // timeout limit
+static linenr_T current_lnum = 0; // lnum of current state
+static colnr_T current_col = 0; // column of current state
+static int current_state_stored = 0; // TRUE if stored current state
+ // after setting current_finished
+static int current_finished = 0; // current line has been finished
+static garray_T current_state // current stack of state_items
= GA_EMPTY_INIT_VALUE;
static int16_t *current_next_list = NULL; // when non-zero, nextgroup list
static int current_next_flags = 0; // flags for current_next_list
@@ -375,7 +376,12 @@ static int current_line_id = 0; // unique number for current line
static int syn_time_on = FALSE;
# define IF_SYN_TIME(p) (p)
-
+// Set the timeout used for syntax highlighting.
+// Use NULL to reset, no timeout.
+void syn_set_timeout(proftime_T *tm)
+{
+ syn_tm = tm;
+}
/*
* Start the syntax recognition for a line. This function is normally called
@@ -2887,6 +2893,7 @@ static char_u *syn_getcurline(void)
static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T *st)
{
int r;
+ int timed_out = 0;
proftime_T pt;
const int l_syn_time_on = syn_time_on;
@@ -2902,7 +2909,8 @@ static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T
}
rmp->rmm_maxcol = syn_buf->b_p_smc;
- r = vim_regexec_multi(rmp, syn_win, syn_buf, lnum, col, NULL, NULL);
+ r = vim_regexec_multi(rmp, syn_win, syn_buf, lnum, col,
+ syn_tm, &timed_out);
if (l_syn_time_on) {
pt = profile_end(pt);
@@ -2914,6 +2922,9 @@ static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T
if (r > 0)
++st->match;
}
+ if (timed_out) {
+ syn_win->w_s->b_syn_slow = true;
+ }
if (r > 0) {
rmp->startpos[0].lnum += lnum;
@@ -3144,6 +3155,7 @@ static void syn_cmd_iskeyword(exarg_T *eap, int syncing)
void syntax_clear(synblock_T *block)
{
block->b_syn_error = false; // clear previous error
+ block->b_syn_slow = false; // clear previous timeout
block->b_syn_ic = false; // Use case, by default
block->b_syn_spell = SYNSPL_DEFAULT; // default spell checking
block->b_syn_containedin = false;
@@ -5756,8 +5768,10 @@ int syn_get_foldlevel(win_T *wp, long lnum)
{
int level = 0;
- /* Return quickly when there are no fold items at all. */
- if (wp->w_s->b_syn_folditems != 0) {
+ // Return quickly when there are no fold items at all.
+ if (wp->w_s->b_syn_folditems != 0
+ && !wp->w_s->b_syn_error
+ && !wp->w_s->b_syn_slow) {
syntax_start(wp, lnum);
for (int i = 0; i < current_state.ga_len; ++i) {
@@ -7446,7 +7460,7 @@ void highlight_attr_set_all(void)
void highlight_changed(void)
{
int id;
- char_u userhl[10];
+ char_u userhl[30]; // use 30 to avoid compiler warning
int id_SNC = -1;
int id_S = -1;
int hlcnt;
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index 861063ff62..81af23f911 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -1832,9 +1832,9 @@ parse_line:
// Don't add identical matches.
// Add all cscope tags, because they are all listed.
// "mfp" is used as a hash key, there is a NUL byte to end
- // the part matters for comparing, more bytes may follow
- // after it. E.g. help tags store the priority after the
- // NUL.
+ // the part that matters for comparing, more bytes may
+ // follow after it. E.g. help tags store the priority
+ // after the NUL.
if (use_cscope) {
hash++;
} else {
@@ -1970,7 +1970,13 @@ static garray_T tag_fnames = GA_EMPTY_INIT_VALUE;
*/
static void found_tagfile_cb(char_u *fname, void *cookie)
{
- GA_APPEND(char_u *, &tag_fnames, vim_strsave(fname));
+ char_u *const tag_fname = vim_strsave(fname);
+
+#ifdef BACKSLASH_IN_FILENAME
+ slash_adjust(tag_fname);
+#endif
+ simplify_filename(tag_fname);
+ GA_APPEND(char_u *, &tag_fnames, tag_fname);
}
#if defined(EXITFREE)
@@ -2028,9 +2034,20 @@ get_tagfname (
++tnp->tn_hf_idx;
STRCPY(buf, p_hf);
STRCPY(path_tail(buf), "tags");
- } else
- STRLCPY(buf, ((char_u **)(tag_fnames.ga_data))[
- tnp->tn_hf_idx++], MAXPATHL);
+#ifdef BACKSLASH_IN_FILENAME
+ slash_adjust(buf);
+#endif
+ simplify_filename(buf);
+
+ for (int i = 0; i < tag_fnames.ga_len; i++) {
+ if (STRCMP(buf, ((char_u **)(tag_fnames.ga_data))[i]) == 0) {
+ return FAIL; // avoid duplicate file names
+ }
+ }
+ } else {
+ STRLCPY(buf, ((char_u **)(tag_fnames.ga_data))[tnp->tn_hf_idx++],
+ MAXPATHL);
+ }
return OK;
}
@@ -2863,3 +2880,177 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname)
}
return ret;
}
+
+// Return information about 'tag' in dict 'retdict'.
+static void get_tag_details(taggy_T *tag, dict_T *retdict)
+{
+ list_T *pos;
+ fmark_T *fmark;
+
+ tv_dict_add_str(retdict, S_LEN("tagname"), (const char *)tag->tagname);
+ tv_dict_add_nr(retdict, S_LEN("matchnr"), tag->cur_match + 1);
+ tv_dict_add_nr(retdict, S_LEN("bufnr"), tag->cur_fnum);
+
+ pos = tv_list_alloc(4);
+ tv_dict_add_list(retdict, S_LEN("from"), pos);
+
+ fmark = &tag->fmark;
+ tv_list_append_number(pos,
+ (varnumber_T)(fmark->fnum != -1 ? fmark->fnum : 0));
+ tv_list_append_number(pos, (varnumber_T)fmark->mark.lnum);
+ tv_list_append_number(pos, (varnumber_T)(fmark->mark.col == MAXCOL
+ ? MAXCOL : fmark->mark.col + 1));
+ tv_list_append_number(pos, (varnumber_T)fmark->mark.coladd);
+}
+
+// Return the tag stack entries of the specified window 'wp' in dictionary
+// 'retdict'.
+void get_tagstack(win_T *wp, dict_T *retdict)
+{
+ list_T *l;
+ int i;
+ dict_T *d;
+
+ tv_dict_add_nr(retdict, S_LEN("length"), wp->w_tagstacklen);
+ tv_dict_add_nr(retdict, S_LEN("curidx"), wp->w_tagstackidx + 1);
+ l = tv_list_alloc(2);
+ tv_dict_add_list(retdict, S_LEN("items"), l);
+
+ for (i = 0; i < wp->w_tagstacklen; i++) {
+ d = tv_dict_alloc();
+ tv_list_append_dict(l, d);
+ get_tag_details(&wp->w_tagstack[i], d);
+ }
+}
+
+// Free all the entries in the tag stack of the specified window
+static void tagstack_clear(win_T *wp)
+{
+ // Free the current tag stack
+ for (int i = 0; i < wp->w_tagstacklen; i++) {
+ xfree(wp->w_tagstack[i].tagname);
+ }
+ wp->w_tagstacklen = 0;
+ wp->w_tagstackidx = 0;
+}
+
+// Remove the oldest entry from the tag stack and shift the rest of
+// the entires to free up the top of the stack.
+static void tagstack_shift(win_T *wp)
+{
+ taggy_T *tagstack = wp->w_tagstack;
+ xfree(tagstack[0].tagname);
+ for (int i = 1; i < wp->w_tagstacklen; i++) {
+ tagstack[i - 1] = tagstack[i];
+ }
+ wp->w_tagstacklen--;
+}
+
+// Push a new item to the tag stack
+static void tagstack_push_item(
+ win_T *wp,
+ char_u *tagname,
+ int cur_fnum,
+ int cur_match,
+ pos_T mark,
+ int fnum)
+{
+ taggy_T *tagstack = wp->w_tagstack;
+ int idx = wp->w_tagstacklen; // top of the stack
+
+ // if the tagstack is full: remove the oldest entry
+ if (idx >= TAGSTACKSIZE) {
+ tagstack_shift(wp);
+ idx = TAGSTACKSIZE - 1;
+ }
+
+ wp->w_tagstacklen++;
+ tagstack[idx].tagname = tagname;
+ tagstack[idx].cur_fnum = cur_fnum;
+ tagstack[idx].cur_match = cur_match;
+ if (tagstack[idx].cur_match < 0) {
+ tagstack[idx].cur_match = 0;
+ }
+ tagstack[idx].fmark.mark = mark;
+ tagstack[idx].fmark.fnum = fnum;
+}
+
+// Add a list of items to the tag stack in the specified window
+static void tagstack_push_items(win_T *wp, list_T *l)
+{
+ listitem_T *li;
+ dictitem_T *di;
+ dict_T *itemdict;
+ char_u *tagname;
+ pos_T mark;
+ int fnum;
+
+ // Add one entry at a time to the tag stack
+ for (li = tv_list_first(l); li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
+ if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT
+ || TV_LIST_ITEM_TV(li)->vval.v_dict == NULL) {
+ continue; // Skip non-dict items
+ }
+ itemdict = TV_LIST_ITEM_TV(li)->vval.v_dict;
+
+ // parse 'from' for the cursor position before the tag jump
+ if ((di = tv_dict_find(itemdict, "from", -1)) == NULL) {
+ continue;
+ }
+ if (list2fpos(&di->di_tv, &mark, &fnum, NULL) != OK) {
+ continue;
+ }
+ if ((tagname = (char_u *)tv_dict_get_string(itemdict, "tagname", true))
+ == NULL) {
+ continue;
+ }
+
+ if (mark.col > 0) {
+ mark.col--;
+ }
+ tagstack_push_item(wp, tagname,
+ (int)tv_dict_get_number(itemdict, "bufnr"),
+ (int)tv_dict_get_number(itemdict, "matchnr") - 1,
+ mark, fnum);
+ }
+}
+
+// Set the current index in the tag stack. Valid values are between 0
+// and the stack length (inclusive).
+static void tagstack_set_curidx(win_T *wp, int curidx)
+{
+ wp->w_tagstackidx = curidx;
+ if (wp->w_tagstackidx < 0) { // sanity check
+ wp->w_tagstackidx = 0;
+ }
+ if (wp->w_tagstackidx > wp->w_tagstacklen) {
+ wp->w_tagstackidx = wp->w_tagstacklen;
+ }
+}
+
+// Set the tag stack entries of the specified window.
+// 'action' is set to either 'a' for append or 'r' for replace.
+int set_tagstack(win_T *wp, dict_T *d, int action)
+{
+ dictitem_T *di;
+ list_T *l;
+
+ if ((di = tv_dict_find(d, "items", -1)) != NULL) {
+ if (di->di_tv.v_type != VAR_LIST) {
+ return FAIL;
+ }
+ l = di->di_tv.vval.v_list;
+
+ if (action == 'r') {
+ tagstack_clear(wp);
+ }
+
+ tagstack_push_items(wp, l);
+ }
+
+ if ((di = tv_dict_find(d, "curidx", -1)) != NULL) {
+ tagstack_set_curidx(wp, (int)tv_get_number(&di->di_tv) - 1);
+ }
+
+ return OK;
+}
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index bdcade0d86..d8d529d0f6 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -374,6 +374,7 @@ void terminal_enter(void)
TerminalState state, *s = &state;
memset(s, 0, sizeof(TerminalState));
s->term = buf->terminal;
+ stop_insert_mode = false;
// Ensure the terminal is properly sized. Ideally window size management
// code should always have resized the terminal already, but check here to
@@ -450,7 +451,6 @@ static void terminal_check_cursor(void)
static int terminal_check(VimState *state)
{
if (stop_insert_mode) {
- stop_insert_mode = false;
return 0;
}
@@ -1339,23 +1339,16 @@ static bool is_focused(Terminal *term)
return State & TERM_FOCUS && curbuf->terminal == term;
}
-#define GET_CONFIG_VALUE(k, o) \
- do { \
- Error err = ERROR_INIT; \
- /* Only called from terminal_open where curbuf->terminal is the */ \
- /* context */ \
- o = dict_get_value(curbuf->b_vars, cstr_as_string(k), &err); \
- api_clear_error(&err); \
- if (o.type == kObjectTypeNil) { \
- o = dict_get_value(&globvardict, cstr_as_string(k), &err); \
- api_clear_error(&err); \
- } \
- } while (0)
-
static char *get_config_string(char *key)
{
- Object obj;
- GET_CONFIG_VALUE(key, obj);
+ Error err = ERROR_INIT;
+ // Only called from terminal_open where curbuf->terminal is the context.
+ Object obj = dict_get_value(curbuf->b_vars, cstr_as_string(key), &err);
+ api_clear_error(&err);
+ if (obj.type == kObjectTypeNil) {
+ obj = dict_get_value(&globvardict, cstr_as_string(key), &err);
+ api_clear_error(&err);
+ }
if (obj.type == kObjectTypeString) {
return obj.data.string.data;
}
diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile
index fac27aa346..8ea2689939 100644
--- a/src/nvim/testdir/Makefile
+++ b/src/nvim/testdir/Makefile
@@ -14,7 +14,6 @@ export NVIM_PRG := $(NVIM_PRG)
export TMPDIR := $(abspath ../../../Xtest-tmpdir)
SCRIPTS_DEFAULT = \
- test14.out \
test37.out \
test42.out \
test48.out \
@@ -23,7 +22,6 @@ SCRIPTS_DEFAULT = \
ifneq ($(OS),Windows_NT)
SCRIPTS_DEFAULTS := $(SCRIPTS_DEFAULT) \
- test17.out \
test49.out \
endif
@@ -50,8 +48,6 @@ NEW_TESTS_IGNORE := $(NEW_TESTS_IN_ALOT) $(NEW_TESTS_ALOT) \
NEW_TESTS ?= $(addsuffix .res,$(sort $(filter-out $(NEW_TESTS_IGNORE),$(basename $(notdir $(wildcard test_*.vim))))) $(NEW_TESTS_ALOT))
-SCRIPTS_GUI := test16.out
-
ifdef VALGRIND_GDB
VGDB := --vgdb=yes \
@@ -83,8 +79,6 @@ endif
nongui: nolog $(SCRIPTS) newtests report
-gui: nolog $(SCRIPTS) $(SCRIPTS_GUI) newtests report
-
.gdbinit:
@echo "[OLDTEST-PREP] Setting up .gdbinit"
@echo 'set $$_exitcode = -1\nrun\nif $$_exitcode != -1\n quit\nend' > .gdbinit
@@ -102,7 +96,7 @@ report:
test1.out: $(NVIM_PRG)
-$(SCRIPTS) $(SCRIPTS_GUI): $(NVIM_PRG) test1.out
+$(SCRIPTS): $(NVIM_PRG) test1.out
RM_ON_RUN := test.out X* viminfo
RM_ON_START := test.ok
@@ -164,5 +158,6 @@ newtestssilent: $(NEW_TESTS)
%.res: %.vim .gdbinit
@echo "[OLDTEST] Running" $*
+ @rm -rf $*.failed test.ok $(RM_ON_RUN)
@mkdir -p $(TMPDIR)
@/bin/sh runnvim.sh $(ROOT) $(NVIM_PRG) $* $(RUN_VIMTEST) -u NONE -S runtest.vim $*.vim
diff --git a/src/nvim/testdir/test14.in b/src/nvim/testdir/test14.in
deleted file mode 100644
index bef2e45431..0000000000
--- a/src/nvim/testdir/test14.in
+++ /dev/null
@@ -1,94 +0,0 @@
-Tests for "vaBiB", end could be wrong.
-Also test ":s/pat/sub/" with different ~s in sub.
-Also test for ^Vxff and ^Vo123 in Insert mode.
-Also test "[m", "]m", "[M" and "]M"
-Also test search()
-
-STARTTEST
-/Start cursor here
-vaBiBD:?Bug?,/Piece/-2w! test.out
-/^- Bug
-:s/u/~u~/
-:s/i/~u~/
-:s/o/~~~/
-:.w >>test.out
-:let tt = "o\<C-V>65\<C-V>x42\<C-V>o103 \<C-V>33a\<C-V>xfg\<C-V>o78\<Esc>"
-:exe "normal " . tt
-:unlet tt
-:.w >>test.out
-:set vb
-/^Piece
-2]maA:.w >>test.out
-j]maB:.w >>test.out
-]maC:.w >>test.out
-[maD:.w >>test.out
-k2[maE:.w >>test.out
-3[maF:.w >>test.out
-]MaG:.w >>test.out
-j2]MaH:.w >>test.out
-]M]MaI:.w >>test.out
-2[MaJ:.w >>test.out
-k[MaK:.w >>test.out
-3[MaL:.w >>test.out
-:"
-/^foobar
-:let startline = line('.')
-:call search('foobar', 'c')
-:call append(line('$'), line('.') - startline)
-j:call search('^$', 'c')
-:call append(line('$'), line('.') - startline)
-:call search('^$', 'bc')
-:call append(line('$'), line('.') - startline)
-/two
-:call search('.', 'c')
-:call append(line('$'), getline('.')[col('.') - 1:])
-:"
-/^substitute
-:s/foo/bar/
-:$put =@/
-/^substitute
-:keeppatterns s/asdf/xyz/
-:$put =@/
-/^substitute
-Y:$put =@0
-/bar /e
-:$put =@0
--:keeppatterns /xyz
-0dn:/^search()/,$w >>test.out
-:qa!
-ENDTEST
-
-- Bug in "vPPPP" on this text (Webb):
- {
- cmd;
- {
- cmd; /* <-- Start cursor here */
- {
- }
- }
- }
-
-Piece of Java
-{
- tt m1 {
- t1;
- } e1
-
- tt m2 {
- t2;
- } e2
-
- tt m3 {
- if (x)
- {
- t3;
- }
- } e3
-}
-
-foobar
-
-substitute foo asdf
-
-one two
-search()
diff --git a/src/nvim/testdir/test14.ok b/src/nvim/testdir/test14.ok
deleted file mode 100644
index 0aa2db3f97..0000000000
--- a/src/nvim/testdir/test14.ok
+++ /dev/null
@@ -1,26 +0,0 @@
-- Bug in "vPPPP" on this text (Webb):
- {
- }
-- Bug uuun "vPPPP" uuuuuuuuun this text (Webb):
-ABC !ag8
- tt m1 {A
- tt m2 {B
- tt m3 {C
- tt m3 {DC
- tt m1 {EA
-{F
- }G e1
- }H e3
-}I
- }JH e3
- }K e2
-{LF
-search()
-0
-1
-1
-two
-foo
-^substitute
-substitute bar xyz
-xyz
diff --git a/src/nvim/testdir/test16.in b/src/nvim/testdir/test16.in
deleted file mode 100644
index b2cd159a8c..0000000000
--- a/src/nvim/testdir/test16.in
+++ /dev/null
@@ -1,15 +0,0 @@
-Tests for resetting "secure" flag after GUI has started.
-For KDE set a font, empty 'guifont' may cause a hang.
-
-STARTTEST
-:if $DISPLAY == "" | e! test.ok | wq! test.out | endif
-:set exrc secure
-:if has("gui_kde")
-: set guifont=Courier\ 10\ Pitch/8/-1/5/50/0/0/0/0/0
-:endif
-:gui -f
-:.,$w! test.out
-:qa!
-ENDTEST
-
- just some text
diff --git a/src/nvim/testdir/test16.ok b/src/nvim/testdir/test16.ok
deleted file mode 100644
index 25e2eea5c0..0000000000
--- a/src/nvim/testdir/test16.ok
+++ /dev/null
@@ -1,2 +0,0 @@
-
- just some text
diff --git a/src/nvim/testdir/test17.in b/src/nvim/testdir/test17.in
deleted file mode 100644
index 1a4ac6b6d1..0000000000
--- a/src/nvim/testdir/test17.in
+++ /dev/null
@@ -1,126 +0,0 @@
-Tests for:
-- "gf" on ${VAR},
-- ":checkpath!" with various 'include' settings.
-
-STARTTEST
-:set isfname=@,48-57,/,.,-,_,+,,,$,:,~,{,}
-:"
-:if has("unix")
-:let $CDIR = "."
-/CDIR
-:else
-:let $TDIR = "."
-/TDIR
-:endif
-:" Dummy writing for making that sure gf doesn't fail even if the current
-:" file is modified. It can be occurred when executing the following command
-:" directly on Windows without fixing the 'fileformat':
-:" > nmake -f Make_dos.mak test17.out
-:w! test.out
-gf
-:set ff=unix
-:w! test.out
-:brewind
-ENDTEST
-
- ${CDIR}/test17a.in
- $TDIR/test17a.in
-
-STARTTEST
-:" check for 'include' without \zs or \ze
-:lang C
-:call delete("./Xbase.a")
-:call delete("Xdir1", "rf")
-:!mkdir Xdir1
-:!mkdir "Xdir1/dir2"
-:e! Xdir1/dir2/foo.a
-i#include "bar.a":
-:w
-:e Xdir1/dir2/bar.a
-i#include "baz.a":
-:w
-:e Xdir1/dir2/baz.a
-i#include "foo.a":
-:w
-:e Xbase.a
-:set path=Xdir1/dir2
-i#include <foo.a>:
-:w
-:redir! >>test.out
-:checkpath!
-:redir END
-:brewind
-ENDTEST
-
-STARTTEST
-:" check for 'include' with \zs and \ze
-:call delete("./Xbase.b")
-:call delete("Xdir1", "rf")
-:!mkdir Xdir1
-:!mkdir "Xdir1/dir2"
-:let &include='^\s*%inc\s*/\zs[^/]\+\ze'
-:function! DotsToSlashes()
-: return substitute(v:fname, '\.', '/', 'g') . '.b'
-:endfunction
-:let &includeexpr='DotsToSlashes()'
-:e! Xdir1/dir2/foo.b
-i%inc /bar/:
-:w
-:e Xdir1/dir2/bar.b
-i%inc /baz/:
-:w
-:e Xdir1/dir2/baz.b
-i%inc /foo/:
-:w
-:e Xbase.b
-:set path=Xdir1/dir2
-i%inc /foo/:
-:w
-:redir! >>test.out
-:checkpath!
-:redir END
-:brewind
-ENDTEST
-
-STARTTEST
-:" check for 'include' with \zs and no \ze
-:call delete("./Xbase.c")
-:call delete("Xdir1", "rf")
-:!mkdir Xdir1
-:!mkdir "Xdir1/dir2"
-:let &include='^\s*%inc\s*\%([[:upper:]][^[:space:]]*\s\+\)\?\zs\S\+\ze'
-:function! StripNewlineChar()
-: if v:fname =~ '\n$'
-: return v:fname[:-2]
-: endif
-: return v:fname
-:endfunction
-:let &includeexpr='StripNewlineChar()'
-:e! Xdir1/dir2/foo.c
-i%inc bar.c:
-:w
-:e Xdir1/dir2/bar.c
-i%inc baz.c:
-:w
-:e Xdir1/dir2/baz.c
-i%inc foo.c:
-:w
-:e Xdir1/dir2/FALSE.c
-i%inc foo.c:
-:w
-:e Xbase.c
-:set path=Xdir1/dir2
-i%inc FALSE.c foo.c:
-:w
-:redir! >>test.out
-:checkpath!
-:redir END
-:brewind
-:" change "\" to "/" for Windows and fix 'fileformat'
-:e test.out
-:%s#\\#/#g
-:set ff&
-:w
-:q
-ENDTEST
-
diff --git a/src/nvim/testdir/test17.ok b/src/nvim/testdir/test17.ok
deleted file mode 100644
index b2a66d5f85..0000000000
--- a/src/nvim/testdir/test17.ok
+++ /dev/null
@@ -1,33 +0,0 @@
-This file is just to test "gf" in test 17.
-The contents is not important.
-Just testing!
-
-
---- Included files in path ---
-Xdir1/dir2/foo.a
-Xdir1/dir2/foo.a -->
- Xdir1/dir2/bar.a
- Xdir1/dir2/bar.a -->
- Xdir1/dir2/baz.a
- Xdir1/dir2/baz.a -->
- "foo.a" (Already listed)
-
-
---- Included files in path ---
-Xdir1/dir2/foo.b
-Xdir1/dir2/foo.b -->
- Xdir1/dir2/bar.b
- Xdir1/dir2/bar.b -->
- Xdir1/dir2/baz.b
- Xdir1/dir2/baz.b -->
- foo (Already listed)
-
-
---- Included files in path ---
-Xdir1/dir2/foo.c
-Xdir1/dir2/foo.c -->
- Xdir1/dir2/bar.c
- Xdir1/dir2/bar.c -->
- Xdir1/dir2/baz.c
- Xdir1/dir2/baz.c -->
- foo.c (Already listed)
diff --git a/src/nvim/testdir/test17a.in b/src/nvim/testdir/test17a.in
deleted file mode 100644
index 7e89364797..0000000000
--- a/src/nvim/testdir/test17a.in
+++ /dev/null
@@ -1,3 +0,0 @@
-This file is just to test "gf" in test 17.
-The contents is not important.
-Just testing!
diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
index 6d4c54bbf9..1c1da0572b 100644
--- a/src/nvim/testdir/test_autocmd.vim
+++ b/src/nvim/testdir/test_autocmd.vim
@@ -34,6 +34,28 @@ if has('timers')
call timer_start(LoadAdjust(100), 'ExitInsertMode')
call feedkeys('a', 'x!')
call assert_equal(1, g:triggered)
+ unlet g:triggered
+ au! CursorHoldI
+ set updatetime&
+ endfunc
+
+ func Test_cursorhold_insert_with_timer_interrupt()
+ if !has('job')
+ return
+ endif
+ " Need to move the cursor.
+ call feedkeys("ggG", "xt")
+
+ " Confirm the timer invoked in exit_cb of the job doesn't disturb
+ " CursorHoldI event.
+ let g:triggered = 0
+ au CursorHoldI * let g:triggered += 1
+ set updatetime=500
+ call job_start(has('win32') ? 'cmd /c echo:' : 'echo',
+ \ {'exit_cb': {j, s -> timer_start(1000, 'ExitInsertMode')}})
+ call feedkeys('a', 'x!')
+ call assert_equal(1, g:triggered)
+ unlet g:triggered
au! CursorHoldI
set updatetime&
endfunc
@@ -46,6 +68,7 @@ if has('timers')
" CursorHoldI does not trigger after CTRL-X
call feedkeys("a\<C-X>", 'x!')
call assert_equal(0, g:triggered)
+ unlet g:triggered
au! CursorHoldI
set updatetime&
endfunc
@@ -457,7 +480,7 @@ endfunc
func Test_OptionSet()
throw 'skipped: Nvim does not support test_override()'
- if !has("eval") || !has("autocmd") || !exists("+autochdir")
+ if !has("eval") || !exists("+autochdir")
return
endif
@@ -598,7 +621,7 @@ endfunc
func Test_OptionSet_diffmode()
throw 'skipped: Nvim does not support test_override()'
call test_override('starting', 1)
- " 18: Changing an option when enetering diff mode
+ " 18: Changing an option when entering diff mode
new
au OptionSet diff :let &l:cul=v:option_new
@@ -654,6 +677,29 @@ func Test_OptionSet_diffmode_close()
"delfunc! AutoCommandOptionSet
endfunc
+func Test_OptionSet_modeline()
+ throw 'skipped: Nvim does not support test_override()'
+ call test_override('starting', 1)
+ au! OptionSet
+ augroup set_tabstop
+ au OptionSet tabstop call timer_start(1, {-> execute("echo 'Handler called'", "")})
+ augroup END
+ call writefile(['vim: set ts=7 sw=5 :', 'something'], 'XoptionsetModeline')
+ set modeline
+ let v:errmsg = ''
+ call assert_fails('split XoptionsetModeline', 'E12:')
+ call assert_equal(7, &ts)
+ call assert_equal('', v:errmsg)
+
+ augroup set_tabstop
+ au!
+ augroup END
+ bwipe!
+ set ts&
+ call delete('XoptionsetModeline')
+ call test_override('starting', 0)
+endfunc
+
" Test for Bufleave autocommand that deletes the buffer we are about to edit.
func Test_BufleaveWithDelete()
new | edit Xfile1
@@ -1378,3 +1424,249 @@ func Test_autocmd_once()
call assert_fails('au WinNew * ++once ++once echo bad', 'E983:')
endfunc
+
+func Test_autocmd_bufreadpre()
+ new
+ let b:bufreadpre = 1
+ call append(0, range(100))
+ w! XAutocmdBufReadPre.txt
+ autocmd BufReadPre <buffer> :let b:bufreadpre += 1
+ norm! 50gg
+ sp
+ norm! 100gg
+ wincmd p
+ let g:wsv1 = winsaveview()
+ wincmd p
+ let g:wsv2 = winsaveview()
+ " triggers BufReadPre, should not move the cursor in either window
+ " The topline may change one line in a large window.
+ edit
+ call assert_inrange(g:wsv2.topline - 1, g:wsv2.topline + 1, winsaveview().topline)
+ call assert_equal(g:wsv2.lnum, winsaveview().lnum)
+ call assert_equal(2, b:bufreadpre)
+ wincmd p
+ call assert_equal(g:wsv1.topline, winsaveview().topline)
+ call assert_equal(g:wsv1.lnum, winsaveview().lnum)
+ call assert_equal(2, b:bufreadpre)
+ " Now set the cursor position in an BufReadPre autocommand
+ " (even though the position will be invalid, this should make Vim reset the
+ " cursor position in the other window.
+ wincmd p
+ 1
+ " won't do anything, but try to set the cursor on an invalid lnum
+ autocmd BufReadPre <buffer> :norm! 70gg
+ " triggers BufReadPre, should not move the cursor in either window
+ e
+ call assert_equal(1, winsaveview().topline)
+ call assert_equal(1, winsaveview().lnum)
+ call assert_equal(3, b:bufreadpre)
+ wincmd p
+ call assert_equal(g:wsv1.topline, winsaveview().topline)
+ call assert_equal(g:wsv1.lnum, winsaveview().lnum)
+ call assert_equal(3, b:bufreadpre)
+ close
+ close
+ call delete('XAutocmdBufReadPre.txt')
+endfunc
+
+" Tests for the following autocommands:
+" - FileWritePre writing a compressed file
+" - FileReadPost reading a compressed file
+" - BufNewFile reading a file template
+" - BufReadPre decompressing the file to be read
+" - FilterReadPre substituting characters in the temp file
+" - FilterReadPost substituting characters after filtering
+" - FileReadPre set options for decompression
+" - FileReadPost decompress the file
+func Test_ReadWrite_Autocmds()
+ " Run this test only on Unix-like systems and if gzip is available
+ if !has('unix') || !executable("gzip")
+ return
+ endif
+
+ " Make $GZIP empty, "-v" would cause trouble.
+ let $GZIP = ""
+
+ " Use a FileChangedShell autocommand to avoid a prompt for 'Xtestfile.gz'
+ " being modified outside of Vim (noticed on Solaris).
+ au FileChangedShell * echo 'caught FileChangedShell'
+
+ " Test for the FileReadPost, FileWritePre and FileWritePost autocmds
+ augroup Test1
+ au!
+ au FileWritePre *.gz '[,']!gzip
+ au FileWritePost *.gz undo
+ au FileReadPost *.gz '[,']!gzip -d
+ augroup END
+
+ new
+ set bin
+ call append(0, [
+ \ 'line 2 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 4 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 6 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 8 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 10 Abcdefghijklmnopqrstuvwxyz'
+ \ ])
+ 1,9write! Xtestfile.gz
+ enew! | close
+
+ new
+ " Read and decompress the testfile
+ 0read Xtestfile.gz
+ call assert_equal([
+ \ 'line 2 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 4 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 6 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 8 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 10 Abcdefghijklmnopqrstuvwxyz'
+ \ ], getline(1, 9))
+ enew! | close
+
+ augroup Test1
+ au!
+ augroup END
+
+ " Test for the FileAppendPre and FileAppendPost autocmds
+ augroup Test2
+ au!
+ au BufNewFile *.c read Xtest.c
+ au FileAppendPre *.out '[,']s/new/NEW/
+ au FileAppendPost *.out !cat Xtest.c >> test.out
+ augroup END
+
+ call writefile(['/*', ' * Here is a new .c file', ' */'], 'Xtest.c')
+ new foo.c " should load Xtest.c
+ call assert_equal(['/*', ' * Here is a new .c file', ' */'], getline(2, 4))
+ w! >> test.out " append it to the output file
+
+ let contents = readfile('test.out')
+ call assert_equal(' * Here is a NEW .c file', contents[2])
+ call assert_equal(' * Here is a new .c file', contents[5])
+
+ call delete('test.out')
+ enew! | close
+ augroup Test2
+ au!
+ augroup END
+
+ " Test for the BufReadPre and BufReadPost autocmds
+ augroup Test3
+ au!
+ " setup autocommands to decompress before reading and re-compress
+ " afterwards
+ au BufReadPre *.gz exe '!gzip -d ' . shellescape(expand("<afile>"))
+ au BufReadPre *.gz call rename(expand("<afile>:r"), expand("<afile>"))
+ au BufReadPost *.gz call rename(expand("<afile>"), expand("<afile>:r"))
+ au BufReadPost *.gz exe '!gzip ' . shellescape(expand("<afile>:r"))
+ augroup END
+
+ e! Xtestfile.gz " Edit compressed file
+ call assert_equal([
+ \ 'line 2 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 4 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 6 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 8 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 10 Abcdefghijklmnopqrstuvwxyz'
+ \ ], getline(1, 9))
+
+ w! >> test.out " Append it to the output file
+
+ augroup Test3
+ au!
+ augroup END
+
+ " Test for the FilterReadPre and FilterReadPost autocmds.
+ set shelltemp " need temp files here
+ augroup Test4
+ au!
+ au FilterReadPre *.out call rename(expand("<afile>"), expand("<afile>") . ".t")
+ au FilterReadPre *.out exe 'silent !sed s/e/E/ ' . shellescape(expand("<afile>")) . ".t >" . shellescape(expand("<afile>"))
+ au FilterReadPre *.out exe 'silent !rm ' . shellescape(expand("<afile>")) . '.t'
+ au FilterReadPost *.out '[,']s/x/X/g
+ augroup END
+
+ e! test.out " Edit the output file
+ 1,$!cat
+ call assert_equal([
+ \ 'linE 2 AbcdefghijklmnopqrstuvwXyz',
+ \ 'linE 3 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
+ \ 'linE 4 AbcdefghijklmnopqrstuvwXyz',
+ \ 'linE 5 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
+ \ 'linE 6 AbcdefghijklmnopqrstuvwXyz',
+ \ 'linE 7 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
+ \ 'linE 8 AbcdefghijklmnopqrstuvwXyz',
+ \ 'linE 9 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
+ \ 'linE 10 AbcdefghijklmnopqrstuvwXyz'
+ \ ], getline(1, 9))
+ call assert_equal([
+ \ 'line 2 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 4 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 6 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 8 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 10 Abcdefghijklmnopqrstuvwxyz'
+ \ ], readfile('test.out'))
+
+ augroup Test4
+ au!
+ augroup END
+ set shelltemp&vim
+
+ " Test for the FileReadPre and FileReadPost autocmds.
+ augroup Test5
+ au!
+ au FileReadPre *.gz exe 'silent !gzip -d ' . shellescape(expand("<afile>"))
+ au FileReadPre *.gz call rename(expand("<afile>:r"), expand("<afile>"))
+ au FileReadPost *.gz '[,']s/l/L/
+ augroup END
+
+ new
+ 0r Xtestfile.gz " Read compressed file
+ call assert_equal([
+ \ 'Line 2 Abcdefghijklmnopqrstuvwxyz',
+ \ 'Line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'Line 4 Abcdefghijklmnopqrstuvwxyz',
+ \ 'Line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'Line 6 Abcdefghijklmnopqrstuvwxyz',
+ \ 'Line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'Line 8 Abcdefghijklmnopqrstuvwxyz',
+ \ 'Line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'Line 10 Abcdefghijklmnopqrstuvwxyz'
+ \ ], getline(1, 9))
+ call assert_equal([
+ \ 'line 2 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 4 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 5 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 6 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 7 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 8 Abcdefghijklmnopqrstuvwxyz',
+ \ 'line 9 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
+ \ 'line 10 Abcdefghijklmnopqrstuvwxyz'
+ \ ], readfile('Xtestfile.gz'))
+
+ augroup Test5
+ au!
+ augroup END
+
+ au! FileChangedShell
+ call delete('Xtestfile.gz')
+ call delete('Xtest.c')
+ call delete('test.out')
+endfunc
diff --git a/src/nvim/testdir/test_checkpath.vim b/src/nvim/testdir/test_checkpath.vim
new file mode 100644
index 0000000000..a7557a107d
--- /dev/null
+++ b/src/nvim/testdir/test_checkpath.vim
@@ -0,0 +1,113 @@
+" Tests for the :checkpath command
+
+" Test for 'include' without \zs or \ze
+func Test_checkpath1()
+ let save_shellslash = &shellslash
+ set shellslash
+ call mkdir("Xdir1/dir2", "p")
+ call writefile(['#include "bar.a"'], 'Xdir1/dir2/foo.a')
+ call writefile(['#include "baz.a"'], 'Xdir1/dir2/bar.a')
+ call writefile(['#include "foo.a"'], 'Xdir1/dir2/baz.a')
+ call writefile(['#include <foo.a>'], 'Xbase.a')
+
+ edit Xbase.a
+ set path=Xdir1/dir2
+ let res = split(execute("checkpath!"), "\n")
+ call assert_equal([
+ \ '--- Included files in path ---',
+ \ 'Xdir1/dir2/foo.a',
+ \ 'Xdir1/dir2/foo.a -->',
+ \ ' Xdir1/dir2/bar.a',
+ \ ' Xdir1/dir2/bar.a -->',
+ \ ' Xdir1/dir2/baz.a',
+ \ ' Xdir1/dir2/baz.a -->',
+ \ ' "foo.a" (Already listed)'], res)
+
+ enew
+ call delete("./Xbase.a")
+ call delete("Xdir1", "rf")
+ set path&
+ let &shellslash = save_shellslash
+endfunc
+
+func DotsToSlashes()
+ return substitute(v:fname, '\.', '/', 'g') . '.b'
+endfunc
+
+" Test for 'include' with \zs and \ze
+func Test_checkpath2()
+ let save_shellslash = &shellslash
+ set shellslash
+ call mkdir("Xdir1/dir2", "p")
+ call writefile(['%inc /bar/'], 'Xdir1/dir2/foo.b')
+ call writefile(['%inc /baz/'], 'Xdir1/dir2/bar.b')
+ call writefile(['%inc /foo/'], 'Xdir1/dir2/baz.b')
+ call writefile(['%inc /foo/'], 'Xbase.b')
+
+ let &include='^\s*%inc\s*/\zs[^/]\+\ze'
+ let &includeexpr='DotsToSlashes()'
+
+ edit Xbase.b
+ set path=Xdir1/dir2
+ let res = split(execute("checkpath!"), "\n")
+ call assert_equal([
+ \ '--- Included files in path ---',
+ \ 'Xdir1/dir2/foo.b',
+ \ 'Xdir1/dir2/foo.b -->',
+ \ ' Xdir1/dir2/bar.b',
+ \ ' Xdir1/dir2/bar.b -->',
+ \ ' Xdir1/dir2/baz.b',
+ \ ' Xdir1/dir2/baz.b -->',
+ \ ' foo (Already listed)'], res)
+
+ enew
+ call delete("./Xbase.b")
+ call delete("Xdir1", "rf")
+ set path&
+ set include&
+ set includeexpr&
+ let &shellslash = save_shellslash
+endfunc
+
+func StripNewlineChar()
+ if v:fname =~ '\n$'
+ return v:fname[:-2]
+ endif
+ return v:fname
+endfunc
+
+" Test for 'include' with \zs and no \ze
+func Test_checkpath3()
+ let save_shellslash = &shellslash
+ set shellslash
+ call mkdir("Xdir1/dir2", "p")
+ call writefile(['%inc bar.c'], 'Xdir1/dir2/foo.c')
+ call writefile(['%inc baz.c'], 'Xdir1/dir2/bar.c')
+ call writefile(['%inc foo.c'], 'Xdir1/dir2/baz.c')
+ call writefile(['%inc foo.c'], 'Xdir1/dir2/FALSE.c')
+ call writefile(['%inc FALSE.c foo.c'], 'Xbase.c')
+
+ let &include='^\s*%inc\s*\%([[:upper:]][^[:space:]]*\s\+\)\?\zs\S\+\ze'
+ let &includeexpr='StripNewlineChar()'
+
+ edit Xbase.c
+ set path=Xdir1/dir2
+ let res = split(execute("checkpath!"), "\n")
+ call assert_equal([
+ \ '--- Included files in path ---',
+ \ 'Xdir1/dir2/foo.c',
+ \ 'Xdir1/dir2/foo.c -->',
+ \ ' Xdir1/dir2/bar.c',
+ \ ' Xdir1/dir2/bar.c -->',
+ \ ' Xdir1/dir2/baz.c',
+ \ ' Xdir1/dir2/baz.c -->',
+ \ ' foo.c (Already listed)'], res)
+
+ enew
+ call delete("./Xbase.c")
+ call delete("Xdir1", "rf")
+ set path&
+ set include&
+ set includeexpr&
+ let &shellslash = save_shellslash
+endfunc
diff --git a/src/nvim/testdir/test_debugger.vim b/src/nvim/testdir/test_debugger.vim
new file mode 100644
index 0000000000..db18f7695d
--- /dev/null
+++ b/src/nvim/testdir/test_debugger.vim
@@ -0,0 +1,232 @@
+" Tests for the Vim script debug commands
+
+source shared.vim
+" source screendump.vim
+
+" Run a Vim debugger command
+" If the expected output argument is supplied, then check for it.
+func RunDbgCmd(buf, cmd, ...)
+ call term_sendkeys(a:buf, a:cmd . "\r")
+ call term_wait(a:buf)
+
+ if a:0 != 0
+ " Verify the expected output
+ let lnum = 20 - len(a:1)
+ for l in a:1
+ call WaitForAssert({-> assert_equal(l, term_getline(a:buf, lnum))})
+ let lnum += 1
+ endfor
+ endif
+endfunc
+
+" Debugger tests
+func Test_Debugger()
+ if !CanRunVimInTerminal()
+ return
+ endif
+
+ " Create a Vim script with some functions
+ call writefile([
+ \ 'func Foo()',
+ \ ' let var1 = 1',
+ \ ' let var2 = Bar(var1) + 9',
+ \ ' return var2',
+ \ 'endfunc',
+ \ 'func Bar(var)',
+ \ ' let var1 = 2 + a:var',
+ \ ' let var2 = Bazz(var1) + 4',
+ \ ' return var2',
+ \ 'endfunc',
+ \ 'func Bazz(var)',
+ \ ' let var1 = 3 + a:var',
+ \ ' let var3 = "another var"',
+ \ ' let var3 = "value2"',
+ \ ' let var3 = "value3"',
+ \ ' return var1',
+ \ 'endfunc'], 'Xtest.vim')
+
+ " Start Vim in a terminal
+ let buf = RunVimInTerminal('-S Xtest.vim', {})
+
+ " Start the Vim debugger
+ call RunDbgCmd(buf, ':debug echo Foo()')
+
+ " Create a few stack frames by stepping through functions
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'step')
+
+ " check backtrace
+ call RunDbgCmd(buf, 'backtrace', [
+ \ ' 2 function Foo[2]',
+ \ ' 1 Bar[2]',
+ \ '->0 Bazz',
+ \ 'line 2: let var3 = "another var"'])
+
+ " Check variables in different stack frames
+ call RunDbgCmd(buf, 'echo var1', ['6'])
+
+ call RunDbgCmd(buf, 'up')
+ call RunDbgCmd(buf, 'back', [
+ \ ' 2 function Foo[2]',
+ \ '->1 Bar[2]',
+ \ ' 0 Bazz',
+ \ 'line 2: let var3 = "another var"'])
+ call RunDbgCmd(buf, 'echo var1', ['3'])
+
+ call RunDbgCmd(buf, 'u')
+ call RunDbgCmd(buf, 'bt', [
+ \ '->2 function Foo[2]',
+ \ ' 1 Bar[2]',
+ \ ' 0 Bazz',
+ \ 'line 2: let var3 = "another var"'])
+ call RunDbgCmd(buf, 'echo var1', ['1'])
+
+ " Undefined variables
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'frame 2')
+ call RunDbgCmd(buf, 'echo var3', [
+ \ 'Error detected while processing function Foo[2]..Bar[2]..Bazz:',
+ \ 'line 3:',
+ \ 'E121: Undefined variable: var3'])
+
+ " var3 is defined in this level with some other value
+ call RunDbgCmd(buf, 'fr 0')
+ call RunDbgCmd(buf, 'echo var3', ['another var'])
+
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'step', [
+ \ 'function Foo[2]..Bar',
+ \ 'line 3: End of function'])
+ call RunDbgCmd(buf, 'up')
+
+ " Undefined var2
+ call RunDbgCmd(buf, 'echo var2', [
+ \ 'Error detected while processing function Foo[2]..Bar:',
+ \ 'line 3:',
+ \ 'E121: Undefined variable: var2'])
+
+ " Var2 is defined with 10
+ call RunDbgCmd(buf, 'down')
+ call RunDbgCmd(buf, 'echo var2', ['10'])
+
+ " Backtrace movements
+ call RunDbgCmd(buf, 'b', [
+ \ ' 1 function Foo[2]',
+ \ '->0 Bar',
+ \ 'line 3: End of function'])
+
+ " next command cannot go down, we are on bottom
+ call RunDbgCmd(buf, 'down', ['frame is zero'])
+ call RunDbgCmd(buf, 'up')
+
+ " next command cannot go up, we are on top
+ call RunDbgCmd(buf, 'up', ['frame at highest level: 1'])
+ call RunDbgCmd(buf, 'where', [
+ \ '->1 function Foo[2]',
+ \ ' 0 Bar',
+ \ 'line 3: End of function'])
+
+ " fil is not frame or finish, it is file
+ call RunDbgCmd(buf, 'fil', ['"[No Name]" --No lines in buffer--'])
+
+ " relative backtrace movement
+ call RunDbgCmd(buf, 'fr -1')
+ call RunDbgCmd(buf, 'frame', [
+ \ ' 1 function Foo[2]',
+ \ '->0 Bar',
+ \ 'line 3: End of function'])
+
+ call RunDbgCmd(buf, 'fr +1')
+ call RunDbgCmd(buf, 'fram', [
+ \ '->1 function Foo[2]',
+ \ ' 0 Bar',
+ \ 'line 3: End of function'])
+
+ " go beyond limits does not crash
+ call RunDbgCmd(buf, 'fr 100', ['frame at highest level: 1'])
+ call RunDbgCmd(buf, 'fra', [
+ \ '->1 function Foo[2]',
+ \ ' 0 Bar',
+ \ 'line 3: End of function'])
+
+ call RunDbgCmd(buf, 'frame -40', ['frame is zero'])
+ call RunDbgCmd(buf, 'fram', [
+ \ ' 1 function Foo[2]',
+ \ '->0 Bar',
+ \ 'line 3: End of function'])
+
+ " final result 19
+ call RunDbgCmd(buf, 'cont', ['19'])
+
+ " breakpoints tests
+
+ " Start a debug session, so that reading the last line from the terminal
+ " works properly.
+ call RunDbgCmd(buf, ':debug echo Foo()')
+
+ " No breakpoints
+ call RunDbgCmd(buf, 'breakl', ['No breakpoints defined'])
+
+ " Place some breakpoints
+ call RunDbgCmd(buf, 'breaka func Bar')
+ call RunDbgCmd(buf, 'breaklis', [' 1 func Bar line 1'])
+ call RunDbgCmd(buf, 'breakadd func 3 Bazz')
+ call RunDbgCmd(buf, 'breaklist', [' 1 func Bar line 1',
+ \ ' 2 func Bazz line 3'])
+
+ " Check whether the breakpoints are hit
+ call RunDbgCmd(buf, 'cont', [
+ \ 'Breakpoint in "Bar" line 1',
+ \ 'function Foo[2]..Bar',
+ \ 'line 1: let var1 = 2 + a:var'])
+ call RunDbgCmd(buf, 'cont', [
+ \ 'Breakpoint in "Bazz" line 3',
+ \ 'function Foo[2]..Bar[2]..Bazz',
+ \ 'line 3: let var3 = "value2"'])
+
+ " Delete the breakpoints
+ call RunDbgCmd(buf, 'breakd 1')
+ call RunDbgCmd(buf, 'breakli', [' 2 func Bazz line 3'])
+ call RunDbgCmd(buf, 'breakdel func 3 Bazz')
+ call RunDbgCmd(buf, 'breakl', ['No breakpoints defined'])
+
+ call RunDbgCmd(buf, 'cont')
+
+ " Make sure the breakpoints are removed
+ call RunDbgCmd(buf, ':echo Foo()', ['19'])
+
+ " Delete a non-existing breakpoint
+ call RunDbgCmd(buf, ':breakdel 2', ['E161: Breakpoint not found: 2'])
+
+ " Expression breakpoint
+ call RunDbgCmd(buf, ':breakadd func 2 Bazz')
+ call RunDbgCmd(buf, ':echo Bazz(1)')
+ call RunDbgCmd(buf, 'step')
+ call RunDbgCmd(buf, 'breaka expr var3')
+ call RunDbgCmd(buf, 'breakl', [' 4 expr var3'])
+ call RunDbgCmd(buf, 'cont', ['Breakpoint in "Bazz" line 4',
+ \ 'Oldval = "''another var''"',
+ \ 'Newval = "''value2''"',
+ \ 'function Bazz',
+ \ 'line 4: let var3 = "value3"'])
+
+ call RunDbgCmd(buf, 'breakdel *')
+ call RunDbgCmd(buf, 'breakl', ['No breakpoints defined'])
+
+ " finish the current function
+ call RunDbgCmd(buf, 'finish', [
+ \ 'function Bazz',
+ \ 'line 5: End of function'])
+ call RunDbgCmd(buf, 'cont')
+
+ call StopVimInTerminal(buf)
+
+ call delete('Xtest.vim')
+endfunc
diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim
index fa7cfaa40c..de0f3ddb06 100644
--- a/src/nvim/testdir/test_edit.vim
+++ b/src/nvim/testdir/test_edit.vim
@@ -1454,3 +1454,19 @@ func Test_leave_insert_autocmd()
au! InsertLeave
iunmap x
endfunc
+
+" Test for inserting characters using CTRL-V followed by a number.
+func Test_edit_special_chars()
+ new
+
+ if has("ebcdic")
+ let t = "o\<C-V>193\<C-V>xc2\<C-V>o303 \<C-V>90a\<C-V>xfg\<C-V>o578\<Esc>"
+ else
+ let t = "o\<C-V>65\<C-V>x42\<C-V>o103 \<C-V>33a\<C-V>xfg\<C-V>o78\<Esc>"
+ endif
+
+ exe "normal " . t
+ call assert_equal("ABC !a\<C-O>g\<C-G>8", getline(2))
+
+ close!
+endfunc
diff --git a/src/nvim/testdir/test_eval_stuff.vim b/src/nvim/testdir/test_eval_stuff.vim
index 111c85bb95..1850fb0cf1 100644
--- a/src/nvim/testdir/test_eval_stuff.vim
+++ b/src/nvim/testdir/test_eval_stuff.vim
@@ -21,3 +21,31 @@ func Test_E963()
call assert_fails("let v:oldfiles=''", 'E963:')
call assert_equal(v_o, v:oldfiles)
endfunc
+
+func Test_mkdir_p()
+ call mkdir('Xmkdir/nested', 'p')
+ call assert_true(isdirectory('Xmkdir/nested'))
+ try
+ " Trying to make existing directories doesn't error
+ call mkdir('Xmkdir', 'p')
+ call mkdir('Xmkdir/nested', 'p')
+ catch /E739:/
+ call assert_report('mkdir(..., "p") failed for an existing directory')
+ endtry
+ " 'p' doesn't suppress real errors
+ call writefile([], 'Xfile')
+ call assert_fails('call mkdir("Xfile", "p")', 'E739')
+ call delete('Xfile')
+ call delete('Xmkdir', 'rf')
+endfunc
+
+func Test_line_continuation()
+ let array = [5,
+ "\ ignore this
+ \ 6,
+ "\ more to ignore
+ "\ more moreto ignore
+ \ ]
+ "\ and some more
+ call assert_equal([5, 6], array)
+endfunc
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index 5d4a0ff3cb..7a99a37be4 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -200,7 +200,7 @@ let s:filename_checks = {
\ 'hog': ['file.hog', 'snort.conf', 'vision.conf'],
\ 'hostconf': ['/etc/host.conf'],
\ 'hostsaccess': ['/etc/hosts.allow', '/etc/hosts.deny'],
- \ 'htmlcheetah': ['file.tmpl'],
+ \ 'template': ['file.tmpl'],
\ 'htmlm4': ['file.html.m4'],
\ 'httest': ['file.htt', 'file.htb'],
\ 'ibasic': ['file.iba', 'file.ibi'],
@@ -450,6 +450,7 @@ let s:filename_checks = {
\ 'tssgm': ['file.tssgm'],
\ 'tssop': ['file.tssop'],
\ 'twig': ['file.twig'],
+ \ 'typescript': ['file.ts'],
\ 'uc': ['file.uc'],
\ 'udevconf': ['/etc/udev/udev.conf'],
\ 'udevperm': ['/etc/udev/permissions.d/file.permissions'],
@@ -484,7 +485,7 @@ let s:filename_checks = {
\ 'xhtml': ['file.xhtml', 'file.xht'],
\ 'xinetd': ['/etc/xinetd.conf'],
\ 'xmath': ['file.msc', 'file.msf'],
- \ 'xml': ['/etc/blkid.tab', '/etc/blkid.tab.old', 'file.xmi', 'file.csproj', 'file.csproj.user', 'file.ts', 'file.ui', 'file.tpm', '/etc/xdg/menus/file.menu', 'fglrxrc', 'file.xlf', 'file.xliff', 'file.xul', 'file.wsdl'],
+ \ 'xml': ['/etc/blkid.tab', '/etc/blkid.tab.old', 'file.xmi', 'file.csproj', 'file.csproj.user', 'file.ui', 'file.tpm', '/etc/xdg/menus/file.menu', 'fglrxrc', 'file.xlf', 'file.xliff', 'file.xul', 'file.wsdl'],
\ 'xmodmap': ['anyXmodmap'],
\ 'xf86conf': ['xorg.conf', 'xorg.conf-4'],
\ 'xpm2': ['file.xpm2'],
diff --git a/src/nvim/testdir/test_findfile.vim b/src/nvim/testdir/test_findfile.vim
index 0bae161a8b..f5488a6a27 100644
--- a/src/nvim/testdir/test_findfile.vim
+++ b/src/nvim/testdir/test_findfile.vim
@@ -133,6 +133,7 @@ func Test_finddir()
let save_shellslash = &shellslash
let save_dir = getcwd()
set path=,,
+ set shellslash
call CreateFiles()
cd Xdir1
diff --git a/src/nvim/testdir/test_gf.vim b/src/nvim/testdir/test_gf.vim
index d233046d2b..accd21e9a3 100644
--- a/src/nvim/testdir/test_gf.vim
+++ b/src/nvim/testdir/test_gf.vim
@@ -64,3 +64,38 @@ func Test_gF()
bwipe Xfile
bwipe Xfile2
endfunc
+
+" Test for invoking 'gf' on a ${VAR} variable
+func Test_gf()
+ if has("ebcdic")
+ set isfname=@,240-249,/,.,-,_,+,,,$,:,~,{,}
+ else
+ set isfname=@,48-57,/,.,-,_,+,,,$,:,~,{,}
+ endif
+
+ call writefile(["Test for gf command"], "Xtest1")
+ if has("unix")
+ call writefile([" ${CDIR}/Xtest1"], "Xtestgf")
+ else
+ call writefile([" $TDIR/Xtest1"], "Xtestgf")
+ endif
+ new Xtestgf
+ if has("unix")
+ let $CDIR = "."
+ /CDIR
+ else
+ if has("amiga")
+ let $TDIR = "/testdir"
+ else
+ let $TDIR = "."
+ endif
+ /TDIR
+ endif
+
+ normal gf
+ call assert_equal('Xtest1', fnamemodify(bufname(''), ":t"))
+ close!
+
+ call delete('Xtest1')
+ call delete('Xtestgf')
+endfunc
diff --git a/src/nvim/testdir/test_listchars.vim b/src/nvim/testdir/test_listchars.vim
index 4899f59910..57cfaa298e 100644
--- a/src/nvim/testdir/test_listchars.vim
+++ b/src/nvim/testdir/test_listchars.vim
@@ -90,6 +90,45 @@ func Test_listchars()
\ '.....h>-$',
\ 'iii<<<<><<$', '$'], l)
+
+ " test nbsp
+ normal ggdG
+ set listchars=nbsp:X,trail:Y
+ set list
+ " Non-breaking space
+ let nbsp = nr2char(0xa0)
+ call append(0, [ ">".nbsp."<" ])
+
+ let expected = '>X< '
+
+ redraw!
+ call cursor(1, 1)
+ call assert_equal([expected], ScreenLines(1, virtcol('$')))
+
+ set listchars=nbsp:X
+ redraw!
+ call cursor(1, 1)
+ call assert_equal([expected], ScreenLines(1, virtcol('$')))
+
+ " test extends
+ normal ggdG
+ set listchars=extends:Z
+ set nowrap
+ set nolist
+ call append(0, [ repeat('A', &columns + 1) ])
+
+ let expected = repeat('A', &columns)
+
+ redraw!
+ call cursor(1, 1)
+ call assert_equal([expected], ScreenLines(1, &columns))
+
+ set list
+ let expected = expected[:-2] . 'Z'
+ redraw!
+ call cursor(1, 1)
+ call assert_equal([expected], ScreenLines(1, &columns))
+
enew!
set listchars& ff&
endfunc
diff --git a/src/nvim/testdir/test_mapping.vim b/src/nvim/testdir/test_mapping.vim
index 32593a423a..67f07ff6de 100644
--- a/src/nvim/testdir/test_mapping.vim
+++ b/src/nvim/testdir/test_mapping.vim
@@ -4,6 +4,8 @@ if !has('multi_byte')
finish
endif
+source shared.vim
+
func Test_abbreviation()
" abbreviation with 0x80 should work
inoreab чкпр vim
@@ -173,6 +175,9 @@ func Test_abbr_after_line_join()
endfunc
func Test_map_timeout()
+ if !has('timers')
+ return
+ endif
nnoremap aaaa :let got_aaaa = 1<CR>
nnoremap bb :let got_bb = 1<CR>
nmap b aaa
@@ -182,7 +187,7 @@ func Test_map_timeout()
call feedkeys("\<Esc>", "t")
endfunc
set timeout timeoutlen=200
- call timer_start(300, 'ExitInsert')
+ let timer = timer_start(300, 'ExitInsert')
" After the 'b' Vim waits for another character to see if it matches 'bb'.
" When it times out it is expanded to "aaa", but there is no wait for
" "aaaa". Can't check that reliably though.
@@ -197,6 +202,39 @@ func Test_map_timeout()
nunmap b
set timeoutlen&
delfunc ExitInsert
+ call timer_stop(timer)
+endfunc
+
+func Test_map_timeout_with_timer_interrupt()
+ if !has('job') || !has('timers')
+ return
+ endif
+
+ " Confirm the timer invoked in exit_cb of the job doesn't disturb mapped key
+ " sequence.
+ new
+ let g:val = 0
+ nnoremap \12 :let g:val = 1<CR>
+ nnoremap \123 :let g:val = 2<CR>
+ set timeout timeoutlen=1000
+
+ func ExitCb(job, status)
+ let g:timer = timer_start(1, {_ -> feedkeys("3\<Esc>", 't')})
+ endfunc
+
+ call job_start([&shell, &shellcmdflag, 'echo'], {'exit_cb': 'ExitCb'})
+ call feedkeys('\12', 'xt!')
+ call assert_equal(2, g:val)
+
+ bwipe!
+ nunmap \12
+ nunmap \123
+ set timeoutlen&
+ call WaitFor({-> exists('g:timer')})
+ call timer_stop(g:timer)
+ unlet g:timer
+ unlet g:val
+ delfunc ExitCb
endfunc
func Test_cabbr_visual_mode()
diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim
index d07b3fdbce..ef17209f74 100644
--- a/src/nvim/testdir/test_normal.vim
+++ b/src/nvim/testdir/test_normal.vim
@@ -82,7 +82,6 @@ fun! Test_normal00_optrans()
endfunc
func! Test_normal01_keymodel()
- throw "skipped: Nvim regression: 'keymodel'"
call Setup_NewWindow()
" Test 1: depending on 'keymodel' <s-down> does something different
50
@@ -1353,11 +1352,21 @@ func! Test_normal23_K()
bw!
return
endif
- set keywordprg=man\ --pager=cat
+
+ if has('mac')
+ " In MacOS, the option for specifying a pager is different
+ set keywordprg=man\ -P\ cat
+ else
+ set keywordprg=man\ --pager=cat
+ endif
" Test for using man
2
let a = execute('unsilent norm! K')
- call assert_match("man --pager=cat 'man'", a)
+ if has('mac')
+ call assert_match("man -P cat 'man'", a)
+ else
+ call assert_match("man --pager=cat 'man'", a)
+ endif
" clean up
let &keywordprg = k
@@ -2272,6 +2281,8 @@ endfunc
func! Test_normal45_drop()
if !has('dnd')
+ " The ~ register does not exist
+ call assert_beeps('norm! "~')
return
endif
@@ -2540,3 +2551,81 @@ func Test_delete_until_paragraph()
call assert_equal('', getline(1))
bwipe!
endfunc
+
+" Test for '[m', ']m', '[M' and ']M'
+" Jumping to beginning and end of methods in Java-like languages
+func Test_java_motion()
+ new
+ a
+Piece of Java
+{
+ tt m1 {
+ t1;
+ } e1
+
+ tt m2 {
+ t2;
+ } e2
+
+ tt m3 {
+ if (x)
+ {
+ t3;
+ }
+ } e3
+}
+.
+
+ normal gg
+
+ normal 2]maA
+ call assert_equal("\ttt m1 {A", getline('.'))
+ call assert_equal([3, 9, 16], [line('.'), col('.'), virtcol('.')])
+
+ normal j]maB
+ call assert_equal("\ttt m2 {B", getline('.'))
+ call assert_equal([7, 9, 16], [line('.'), col('.'), virtcol('.')])
+
+ normal ]maC
+ call assert_equal("\ttt m3 {C", getline('.'))
+ call assert_equal([11, 9, 16], [line('.'), col('.'), virtcol('.')])
+
+ normal [maD
+ call assert_equal("\ttt m3 {DC", getline('.'))
+ call assert_equal([11, 9, 16], [line('.'), col('.'), virtcol('.')])
+
+ normal k2[maE
+ call assert_equal("\ttt m1 {EA", getline('.'))
+ call assert_equal([3, 9, 16], [line('.'), col('.'), virtcol('.')])
+
+ normal 3[maF
+ call assert_equal("{F", getline('.'))
+ call assert_equal([2, 2, 2], [line('.'), col('.'), virtcol('.')])
+
+ normal ]MaG
+ call assert_equal("\t}G e1", getline('.'))
+ call assert_equal([5, 3, 10], [line('.'), col('.'), virtcol('.')])
+
+ normal j2]MaH
+ call assert_equal("\t}H e3", getline('.'))
+ call assert_equal([16, 3, 10], [line('.'), col('.'), virtcol('.')])
+
+ normal ]M]M
+ normal aI
+ call assert_equal("}I", getline('.'))
+ call assert_equal([17, 2, 2], [line('.'), col('.'), virtcol('.')])
+
+ normal 2[MaJ
+ call assert_equal("\t}JH e3", getline('.'))
+ call assert_equal([16, 3, 10], [line('.'), col('.'), virtcol('.')])
+
+ normal k[MaK
+ call assert_equal("\t}K e2", getline('.'))
+ call assert_equal([9, 3, 10], [line('.'), col('.'), virtcol('.')])
+
+ normal 3[MaL
+ call assert_equal("{LF", getline('.'))
+ call assert_equal([2, 2, 2], [line('.'), col('.'), virtcol('.')])
+
+ close!
+endfunc
diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim
index f50f33d255..c6d083dfcc 100644
--- a/src/nvim/testdir/test_quickfix.vim
+++ b/src/nvim/testdir/test_quickfix.vim
@@ -136,6 +136,19 @@ func XlistTests(cchar)
\ ' 4:40 col 20 x 44: Other',
\ ' 5:50 col 25 55: one'], l)
+ " Test for module names, one needs to explicitly set `'valid':v:true` so
+ let save_shellslash = &shellslash
+ set shellslash
+ call g:Xsetlist([
+ \ {'lnum':10,'col':5,'type':'W','module':'Data.Text','text':'ModuleWarning','nr':11,'valid':v:true},
+ \ {'lnum':20,'col':10,'type':'W','module':'Data.Text','filename':'Data/Text.hs','text':'ModuleWarning','nr':22,'valid':v:true},
+ \ {'lnum':30,'col':15,'type':'W','filename':'Data/Text.hs','text':'FileWarning','nr':33,'valid':v:true}])
+ let l = split(execute('Xlist', ""), "\n")
+ call assert_equal([' 1 Data.Text:10 col 5 warning 11: ModuleWarning',
+ \ ' 2 Data.Text:20 col 10 warning 22: ModuleWarning',
+ \ ' 3 Data/Text.hs:30 col 15 warning 33: FileWarning'], l)
+ let &shellslash = save_shellslash
+
" Error cases
call assert_fails('Xlist abc', 'E488:')
endfunc
@@ -1093,6 +1106,21 @@ func Test_efm2()
call assert_equal(1, l[4].valid)
call assert_equal(expand('unittests/dbfacadeTest.py'), bufname(l[4].bufnr))
+ " Test for %o
+ set efm=%f(%o):%l\ %m
+ cgetexpr ['Xtestfile(Language.PureScript.Types):20 Error']
+ call writefile(['Line1'], 'Xtestfile')
+ let l = getqflist()
+ call assert_equal(1, len(l), string(l))
+ call assert_equal('Language.PureScript.Types', l[0].module)
+ copen
+ call assert_equal('Language.PureScript.Types|20| Error', getline(1))
+ call feedkeys("\<CR>", 'xn')
+ call assert_equal('Xtestfile', expand('%:t'))
+ cclose
+ bd
+ call delete("Xtestfile")
+
" The following sequence of commands used to crash Vim
set efm=%W%m
cgetexpr ['msg1']
@@ -1379,6 +1407,11 @@ func XquickfixSetListWithAct(cchar)
call assert_fails("call g:Xsetlist(list1, 0)", 'E928:')
endfunc
+func Test_setqflist_invalid_nr()
+ " The following command used to crash Vim
+ call setqflist([], ' ', {'nr' : $XXX_DOES_NOT_EXIST})
+endfunc
+
func Test_quickfix_set_list_with_act()
call XquickfixSetListWithAct('c')
call XquickfixSetListWithAct('l')
@@ -1779,8 +1812,8 @@ func Xproperty_tests(cchar)
call assert_equal(-1, s)
call assert_equal({}, g:Xgetlist({'abc':1}))
- call assert_equal({}, g:Xgetlist({'nr':99, 'title':1}))
- call assert_equal({}, g:Xgetlist({'nr':[], 'title':1}))
+ call assert_equal('', g:Xgetlist({'nr':99, 'title':1}).title)
+ call assert_equal('', g:Xgetlist({'nr':[], 'title':1}).title)
if a:cchar == 'l'
call assert_equal({}, getloclist(99, {'title': 1}))
@@ -1816,7 +1849,7 @@ func Xproperty_tests(cchar)
call assert_equal([1, 2], getloclist(w2_id, {'context':1}).context)
only
call setloclist(0, [], 'f')
- call assert_equal({}, getloclist(0, {'context':1}))
+ call assert_equal('', getloclist(0, {'context':1}).context)
endif
" Test for changing the context of previous quickfix lists
@@ -1948,6 +1981,30 @@ func Test_Autocmd()
cexpr "F1:10:Line 10"
caddexpr "F1:20:Line 20"
cgetexpr "F1:30:Line 30"
+ cexpr ""
+ caddexpr ""
+ cgetexpr ""
+ silent! cexpr non_existing_func()
+ silent! caddexpr non_existing_func()
+ silent! cgetexpr non_existing_func()
+ let l = ['precexpr',
+ \ 'postcexpr',
+ \ 'precaddexpr',
+ \ 'postcaddexpr',
+ \ 'precgetexpr',
+ \ 'postcgetexpr',
+ \ 'precexpr',
+ \ 'postcexpr',
+ \ 'precaddexpr',
+ \ 'postcaddexpr',
+ \ 'precgetexpr',
+ \ 'postcgetexpr',
+ \ 'precexpr',
+ \ 'precaddexpr',
+ \ 'precgetexpr']
+ call assert_equal(l, g:acmds)
+
+ let g:acmds = []
enew! | call append(0, "F2:10:Line 10")
cbuffer!
enew! | call append(0, "F2:20:Line 20")
@@ -1955,19 +2012,108 @@ func Test_Autocmd()
enew! | call append(0, "F2:30:Line 30")
caddbuffer
- let l = ['precexpr',
- \ 'postcexpr',
- \ 'precaddexpr',
- \ 'postcaddexpr',
- \ 'precgetexpr',
- \ 'postcgetexpr',
- \ 'precbuffer',
+ new
+ let bnum = bufnr('%')
+ bunload
+ exe 'silent! cbuffer! ' . bnum
+ exe 'silent! cgetbuffer ' . bnum
+ exe 'silent! caddbuffer ' . bnum
+ enew!
+ let l = ['precbuffer',
\ 'postcbuffer',
\ 'precgetbuffer',
\ 'postcgetbuffer',
\ 'precaddbuffer',
- \ 'postcaddbuffer']
+ \ 'postcaddbuffer',
+ \ 'precbuffer',
+ \ 'precgetbuffer',
+ \ 'precaddbuffer']
+ call assert_equal(l, g:acmds)
+
+ call writefile(['Xtest:1:Line1'], 'Xtest')
+ call writefile([], 'Xempty')
+ let g:acmds = []
+ cfile Xtest
+ caddfile Xtest
+ cgetfile Xtest
+ cfile Xempty
+ caddfile Xempty
+ cgetfile Xempty
+ silent! cfile do_not_exist
+ silent! caddfile do_not_exist
+ silent! cgetfile do_not_exist
+ let l = ['precfile',
+ \ 'postcfile',
+ \ 'precaddfile',
+ \ 'postcaddfile',
+ \ 'precgetfile',
+ \ 'postcgetfile',
+ \ 'precfile',
+ \ 'postcfile',
+ \ 'precaddfile',
+ \ 'postcaddfile',
+ \ 'precgetfile',
+ \ 'postcgetfile',
+ \ 'precfile',
+ \ 'postcfile',
+ \ 'precaddfile',
+ \ 'postcaddfile',
+ \ 'precgetfile',
+ \ 'postcgetfile']
call assert_equal(l, g:acmds)
+
+ let g:acmds = []
+ helpgrep quickfix
+ silent! helpgrep non_existing_help_topic
+ vimgrep test Xtest
+ vimgrepadd test Xtest
+ silent! vimgrep non_existing_test Xtest
+ silent! vimgrepadd non_existing_test Xtest
+ set makeprg=
+ silent! make
+ set makeprg&
+ let l = ['prehelpgrep',
+ \ 'posthelpgrep',
+ \ 'prehelpgrep',
+ \ 'posthelpgrep',
+ \ 'previmgrep',
+ \ 'postvimgrep',
+ \ 'previmgrepadd',
+ \ 'postvimgrepadd',
+ \ 'previmgrep',
+ \ 'postvimgrep',
+ \ 'previmgrepadd',
+ \ 'postvimgrepadd',
+ \ 'premake',
+ \ 'postmake']
+ call assert_equal(l, g:acmds)
+
+ if has('unix')
+ " Run this test only on Unix-like systems. The grepprg may not be set on
+ " non-Unix systems.
+ " The following lines are used for the grep test. Don't remove.
+ " Grep_Autocmd_Text: Match 1
+ " GrepAdd_Autocmd_Text: Match 2
+ let g:acmds = []
+ silent grep Grep_Autocmd_Text test_quickfix.vim
+ silent grepadd GrepAdd_Autocmd_Text test_quickfix.vim
+ silent grep abc123def Xtest
+ silent grepadd abc123def Xtest
+ let l = ['pregrep',
+ \ 'postgrep',
+ \ 'pregrepadd',
+ \ 'postgrepadd',
+ \ 'pregrep',
+ \ 'postgrep',
+ \ 'pregrepadd',
+ \ 'postgrepadd']
+ call assert_equal(l, g:acmds)
+ endif
+
+ call delete('Xtest')
+ call delete('Xempty')
+ au! QuickFixCmdPre
+ au! QuickFixCmdPost
endfunc
func Test_Autocmd_Exception()
@@ -2219,8 +2365,8 @@ func XsizeTests(cchar)
call g:Xsetlist([], 'f')
call assert_equal(0, g:Xgetlist({'nr':'$'}).nr)
- call assert_equal(1, len(g:Xgetlist({'nr':'$', 'all':1})))
- call assert_equal(0, len(g:Xgetlist({'nr':0})))
+ call assert_equal('', g:Xgetlist({'nr':'$', 'all':1}).title)
+ call assert_equal(0, g:Xgetlist({'nr':0}).nr)
Xexpr "File1:10:Line1"
Xexpr "File2:20:Line2"
@@ -2440,6 +2586,29 @@ func Xmultifilestack_tests(cchar)
call assert_equal(3, l1.items[1].lnum)
call assert_equal('two.txt', bufname(l2.items[1].bufnr))
call assert_equal(5, l2.items[1].lnum)
+
+ " Test for start of a new error line in the same line where a previous
+ " error line ends with a file stack.
+ let efm_val = 'Error\ l%l\ in\ %f,'
+ let efm_val .= '%-P%>(%f%r,Error\ l%l\ in\ %m,%-Q)%r'
+ let l = g:Xgetlist({'lines' : [
+ \ '(one.txt',
+ \ 'Error l4 in one.txt',
+ \ ') (two.txt',
+ \ 'Error l6 in two.txt',
+ \ ')',
+ \ 'Error l8 in one.txt'
+ \ ], 'efm' : efm_val})
+ call assert_equal(3, len(l.items))
+ call assert_equal('one.txt', bufname(l.items[0].bufnr))
+ call assert_equal(4, l.items[0].lnum)
+ call assert_equal('one.txt', l.items[0].text)
+ call assert_equal('two.txt', bufname(l.items[1].bufnr))
+ call assert_equal(6, l.items[1].lnum)
+ call assert_equal('two.txt', l.items[1].text)
+ call assert_equal('one.txt', bufname(l.items[2].bufnr))
+ call assert_equal(8, l.items[2].lnum)
+ call assert_equal('', l.items[2].text)
endfunc
func Test_multifilestack()
@@ -2619,7 +2788,7 @@ func Xqfid_tests(cchar)
call s:setup_commands(a:cchar)
call g:Xsetlist([], 'f')
- call assert_equal({}, g:Xgetlist({'id':0}))
+ call assert_equal(0, g:Xgetlist({'id':0}).id)
Xexpr ''
let start_id = g:Xgetlist({'id' : 0}).id
Xexpr '' | Xexpr ''
@@ -2627,10 +2796,10 @@ func Xqfid_tests(cchar)
call assert_equal(start_id, g:Xgetlist({'id':0, 'nr':1}).id)
call assert_equal(start_id + 1, g:Xgetlist({'id':0, 'nr':0}).id)
call assert_equal(start_id + 2, g:Xgetlist({'id':0, 'nr':'$'}).id)
- call assert_equal({}, g:Xgetlist({'id':0, 'nr':99}))
+ call assert_equal(0, g:Xgetlist({'id':0, 'nr':99}).id)
call assert_equal(2, g:Xgetlist({'id':start_id + 1, 'nr':0}).nr)
- call assert_equal({}, g:Xgetlist({'id':99, 'nr':0}))
- call assert_equal({}, g:Xgetlist({'id':"abc", 'nr':0}))
+ call assert_equal(0, g:Xgetlist({'id':99, 'nr':0}).id)
+ call assert_equal(0, g:Xgetlist({'id':"abc", 'nr':0}).id)
call g:Xsetlist([], 'a', {'id':start_id, 'context':[1,2]})
call assert_equal([1,2], g:Xgetlist({'nr':1, 'context':1}).context)
@@ -2641,7 +2810,7 @@ func Xqfid_tests(cchar)
let qfid = g:Xgetlist({'id':0, 'nr':0})
call g:Xsetlist([], 'f')
- call assert_equal({}, g:Xgetlist({'id':qfid, 'nr':0}))
+ call assert_equal(0, g:Xgetlist({'id':qfid, 'nr':0}).id)
endfunc
func Test_qf_id()
@@ -2649,6 +2818,15 @@ func Test_qf_id()
call Xqfid_tests('l')
endfunc
+func Test_getqflist_invalid_nr()
+ " The following commands used to crash Vim
+ cexpr ""
+ call getqflist({'nr' : $XXX_DOES_NOT_EXIST_XXX})
+
+ " Cleanup
+ call setqflist([], 'r')
+endfunc
+
" Test for shortening/simplifying the file name when opening the
" quickfix window or when displaying the quickfix list
func Test_shorten_fname()
@@ -2673,6 +2851,99 @@ func Test_shorten_fname()
call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim'))
endfunc
+" Quickfix title tests
+" In the below tests, 'exe "cmd"' is used to invoke the quickfix commands.
+" Otherwise due to indentation, the title is set with spaces at the beginning
+" of the command.
+func Test_qftitle()
+ call writefile(["F1:1:Line1"], 'Xerr')
+
+ " :cexpr
+ exe "cexpr readfile('Xerr')"
+ call assert_equal(":cexpr readfile('Xerr')", getqflist({'title' : 1}).title)
+
+ " :cgetexpr
+ exe "cgetexpr readfile('Xerr')"
+ call assert_equal(":cgetexpr readfile('Xerr')",
+ \ getqflist({'title' : 1}).title)
+
+ " :caddexpr
+ call setqflist([], 'f')
+ exe "caddexpr readfile('Xerr')"
+ call assert_equal(":caddexpr readfile('Xerr')",
+ \ getqflist({'title' : 1}).title)
+
+ " :cbuffer
+ new Xerr
+ exe "cbuffer"
+ call assert_equal(':cbuffer (Xerr)', getqflist({'title' : 1}).title)
+
+ " :cgetbuffer
+ edit Xerr
+ exe "cgetbuffer"
+ call assert_equal(':cgetbuffer (Xerr)', getqflist({'title' : 1}).title)
+
+ " :caddbuffer
+ call setqflist([], 'f')
+ edit Xerr
+ exe "caddbuffer"
+ call assert_equal(':caddbuffer (Xerr)', getqflist({'title' : 1}).title)
+
+ " :cfile
+ exe "cfile Xerr"
+ call assert_equal(':cfile Xerr', getqflist({'title' : 1}).title)
+
+ " :cgetfile
+ exe "cgetfile Xerr"
+ call assert_equal(':cgetfile Xerr', getqflist({'title' : 1}).title)
+
+ " :caddfile
+ call setqflist([], 'f')
+ exe "caddfile Xerr"
+ call assert_equal(':caddfile Xerr', getqflist({'title' : 1}).title)
+
+ " :grep
+ set grepprg=internal
+ exe "grep F1 Xerr"
+ call assert_equal(':grep F1 Xerr', getqflist({'title' : 1}).title)
+
+ " :grepadd
+ call setqflist([], 'f')
+ exe "grepadd F1 Xerr"
+ call assert_equal(':grepadd F1 Xerr', getqflist({'title' : 1}).title)
+ set grepprg&vim
+
+ " :vimgrep
+ exe "vimgrep F1 Xerr"
+ call assert_equal(':vimgrep F1 Xerr', getqflist({'title' : 1}).title)
+
+ " :vimgrepadd
+ call setqflist([], 'f')
+ exe "vimgrepadd F1 Xerr"
+ call assert_equal(':vimgrepadd F1 Xerr', getqflist({'title' : 1}).title)
+
+ call setqflist(['F1:10:L10'], ' ')
+ call assert_equal(':setqflist()', getqflist({'title' : 1}).title)
+
+ call setqflist([], 'f')
+ call setqflist(['F1:10:L10'], 'a')
+ call assert_equal(':setqflist()', getqflist({'title' : 1}).title)
+
+ call setqflist([], 'f')
+ call setqflist(['F1:10:L10'], 'r')
+ call assert_equal(':setqflist()', getqflist({'title' : 1}).title)
+
+ close
+ call delete('Xerr')
+
+ call setqflist([], ' ', {'title' : 'Errors'})
+ copen
+ call assert_equal('Errors', w:quickfix_title)
+ call setqflist([], 'r', {'items' : [{'filename' : 'a.c', 'lnum' : 10}]})
+ call assert_equal('Errors', w:quickfix_title)
+ cclose
+endfunc
+
" Test for the position of the quickfix and location list window
func Test_qfwin_pos()
" Open two windows
@@ -2700,6 +2971,20 @@ func Test_qfwin_pos()
close
endfunc
+" The following test used to crash Vim
+func Test_lhelpgrep_autocmd()
+ lhelpgrep quickfix
+ autocmd QuickFixCmdPost * call setloclist(0, [], 'f')
+ lhelpgrep buffer
+ call assert_equal('help', &filetype)
+ call assert_equal(0, getloclist(0, {'nr' : '$'}).nr)
+ lhelpgrep tabpage
+ call assert_equal('help', &filetype)
+ call assert_equal(1, getloclist(0, {'nr' : '$'}).nr)
+ au! QuickFixCmdPost
+ new | only
+endfunc
+
" Test to make sure that an empty quickfix buffer is not reused for loading
" a normal buffer.
func Test_empty_qfbuf()
@@ -2713,3 +2998,170 @@ func Test_empty_qfbuf()
enew
call delete('Xfile1')
endfunc
+
+" Tests for the getqflist() and getloclist() functions when the list is not
+" present or is empty
+func Xgetlist_empty_tests(cchar)
+ call s:setup_commands(a:cchar)
+
+ " Empty quickfix stack
+ call g:Xsetlist([], 'f')
+ call assert_equal('', g:Xgetlist({'context' : 0}).context)
+ call assert_equal(0, g:Xgetlist({'id' : 0}).id)
+ call assert_equal(0, g:Xgetlist({'idx' : 0}).idx)
+ call assert_equal([], g:Xgetlist({'items' : 0}).items)
+ call assert_equal(0, g:Xgetlist({'nr' : 0}).nr)
+ call assert_equal(0, g:Xgetlist({'size' : 0}).size)
+ call assert_equal('', g:Xgetlist({'title' : 0}).title)
+ call assert_equal(0, g:Xgetlist({'winid' : 0}).winid)
+ call assert_equal(0, g:Xgetlist({'changedtick' : 0}).changedtick)
+ call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, 'changedtick': 0}, g:Xgetlist({'all' : 0}))
+
+ " Quickfix window with empty stack
+ silent! Xopen
+ let qfwinid = (a:cchar == 'c') ? win_getid() : 0
+ call assert_equal(qfwinid, g:Xgetlist({'winid' : 0}).winid)
+ Xclose
+
+ " Empty quickfix list
+ Xexpr ""
+ call assert_equal('', g:Xgetlist({'context' : 0}).context)
+ call assert_notequal(0, g:Xgetlist({'id' : 0}).id)
+ call assert_equal(0, g:Xgetlist({'idx' : 0}).idx)
+ call assert_equal([], g:Xgetlist({'items' : 0}).items)
+ call assert_notequal(0, g:Xgetlist({'nr' : 0}).nr)
+ call assert_equal(0, g:Xgetlist({'size' : 0}).size)
+ call assert_notequal('', g:Xgetlist({'title' : 0}).title)
+ call assert_equal(0, g:Xgetlist({'winid' : 0}).winid)
+ call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
+
+ let qfid = g:Xgetlist({'id' : 0}).id
+ call g:Xsetlist([], 'f')
+
+ " Non-existing quickfix identifier
+ call assert_equal('', g:Xgetlist({'id' : qfid, 'context' : 0}).context)
+ call assert_equal(0, g:Xgetlist({'id' : qfid}).id)
+ call assert_equal(0, g:Xgetlist({'id' : qfid, 'idx' : 0}).idx)
+ call assert_equal([], g:Xgetlist({'id' : qfid, 'items' : 0}).items)
+ call assert_equal(0, g:Xgetlist({'id' : qfid, 'nr' : 0}).nr)
+ call assert_equal(0, g:Xgetlist({'id' : qfid, 'size' : 0}).size)
+ call assert_equal('', g:Xgetlist({'id' : qfid, 'title' : 0}).title)
+ call assert_equal(0, g:Xgetlist({'id' : qfid, 'winid' : 0}).winid)
+ call assert_equal(0, g:Xgetlist({'id' : qfid, 'changedtick' : 0}).changedtick)
+ call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, 'changedtick' : 0}, g:Xgetlist({'id' : qfid, 'all' : 0}))
+
+ " Non-existing quickfix list number
+ call assert_equal('', g:Xgetlist({'nr' : 5, 'context' : 0}).context)
+ call assert_equal(0, g:Xgetlist({'nr' : 5}).nr)
+ call assert_equal(0, g:Xgetlist({'nr' : 5, 'idx' : 0}).idx)
+ call assert_equal([], g:Xgetlist({'nr' : 5, 'items' : 0}).items)
+ call assert_equal(0, g:Xgetlist({'nr' : 5, 'id' : 0}).id)
+ call assert_equal(0, g:Xgetlist({'nr' : 5, 'size' : 0}).size)
+ call assert_equal('', g:Xgetlist({'nr' : 5, 'title' : 0}).title)
+ call assert_equal(0, g:Xgetlist({'nr' : 5, 'winid' : 0}).winid)
+ call assert_equal(0, g:Xgetlist({'nr' : 5, 'changedtick' : 0}).changedtick)
+ call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, 'changedtick' : 0}, g:Xgetlist({'nr' : 5, 'all' : 0}))
+endfunc
+
+func Test_getqflist()
+ call Xgetlist_empty_tests('c')
+ call Xgetlist_empty_tests('l')
+endfunc
+
+" Tests for the quickfix/location list changedtick
+func Xqftick_tests(cchar)
+ call s:setup_commands(a:cchar)
+
+ call g:Xsetlist([], 'f')
+
+ Xexpr "F1:10:Line10"
+ let qfid = g:Xgetlist({'id' : 0}).id
+ call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
+ Xaddexpr "F2:20:Line20\nF2:21:Line21"
+ call assert_equal(2, g:Xgetlist({'changedtick' : 0}).changedtick)
+ call g:Xsetlist([], 'a', {'lines' : ["F3:30:Line30", "F3:31:Line31"]})
+ call assert_equal(3, g:Xgetlist({'changedtick' : 0}).changedtick)
+ call g:Xsetlist([], 'r', {'lines' : ["F4:40:Line40"]})
+ call assert_equal(4, g:Xgetlist({'changedtick' : 0}).changedtick)
+ call g:Xsetlist([], 'a', {'title' : 'New Title'})
+ call assert_equal(5, g:Xgetlist({'changedtick' : 0}).changedtick)
+
+ enew!
+ call append(0, ["F5:50:L50", "F6:60:L60"])
+ Xaddbuffer
+ call assert_equal(6, g:Xgetlist({'changedtick' : 0}).changedtick)
+ enew!
+
+ call g:Xsetlist([], 'a', {'context' : {'bus' : 'pci'}})
+ call assert_equal(7, g:Xgetlist({'changedtick' : 0}).changedtick)
+ call g:Xsetlist([{'filename' : 'F7', 'lnum' : 10, 'text' : 'L7'},
+ \ {'filename' : 'F7', 'lnum' : 11, 'text' : 'L11'}], 'a')
+ call assert_equal(8, g:Xgetlist({'changedtick' : 0}).changedtick)
+ call g:Xsetlist([{'filename' : 'F7', 'lnum' : 10, 'text' : 'L7'},
+ \ {'filename' : 'F7', 'lnum' : 11, 'text' : 'L11'}], ' ')
+ call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
+ call g:Xsetlist([{'filename' : 'F7', 'lnum' : 10, 'text' : 'L7'},
+ \ {'filename' : 'F7', 'lnum' : 11, 'text' : 'L11'}], 'r')
+ call assert_equal(2, g:Xgetlist({'changedtick' : 0}).changedtick)
+
+ call writefile(["F8:80:L80", "F8:81:L81"], "Xone")
+ Xfile Xone
+ call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
+ Xaddfile Xone
+ call assert_equal(2, g:Xgetlist({'changedtick' : 0}).changedtick)
+
+ " Test case for updating a non-current quickfix list
+ call g:Xsetlist([], 'f')
+ Xexpr "F1:1:L1"
+ Xexpr "F2:2:L2"
+ call g:Xsetlist([], 'a', {'nr' : 1, "lines" : ["F10:10:L10"]})
+ call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
+ call assert_equal(2, g:Xgetlist({'nr' : 1, 'changedtick' : 0}).changedtick)
+
+ call delete("Xone")
+endfunc
+
+func Test_qf_tick()
+ call Xqftick_tests('c')
+ call Xqftick_tests('l')
+endfunc
+
+" The following test used to crash vim
+func Test_lbuffer_crash()
+ sv Xtest
+ augroup QF_Test
+ au!
+ au * * bw
+ augroup END
+ lbuffer
+ augroup QF_Test
+ au!
+ augroup END
+endfunc
+
+" The following test used to crash vim
+func Test_lexpr_crash()
+ augroup QF_Test
+ au!
+ au * * call setloclist(0, [], 'f')
+ augroup END
+ lexpr ""
+ augroup QF_Test
+ au!
+ augroup END
+ enew | only
+endfunc
+
+" The following test used to crash Vim
+func Test_lvimgrep_crash()
+ sv Xtest
+ augroup QF_Test
+ au!
+ au * * call setloclist(0, [], 'f')
+ augroup END
+ lvimgrep quickfix test_quickfix.vim
+ augroup QF_Test
+ au!
+ augroup END
+ enew | only
+endfunc
diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim
index 08ccc8d4fe..f4fe4051e3 100644
--- a/src/nvim/testdir/test_search.vim
+++ b/src/nvim/testdir/test_search.vim
@@ -585,3 +585,27 @@ func Test_one_error_msg()
" This was also giving an internal error
call assert_fails('call search(" \\((\\v[[=P=]]){185}+ ")', 'E871:')
endfunc
+
+" Test for the search() function with match at the cursor position
+func Test_search_match_at_curpos()
+ new
+ call append(0, ['foobar', '', 'one two', ''])
+
+ normal gg
+
+ call search('foobar', 'c')
+ call assert_equal([1, 1], [line('.'), col('.')])
+
+ normal j
+ call search('^$', 'c')
+ call assert_equal([2, 1], [line('.'), col('.')])
+
+ call search('^$', 'bc')
+ call assert_equal([2, 1], [line('.'), col('.')])
+
+ exe "normal /two\<CR>"
+ call search('.', 'c')
+ call assert_equal([3, 5], [line('.'), col('.')])
+
+ close!
+endfunc
diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim
index 2738b00f2f..e074a85530 100644
--- a/src/nvim/testdir/test_substitute.vim
+++ b/src/nvim/testdir/test_substitute.vim
@@ -641,3 +641,52 @@ func Test_nocatch_sub_failure_handling()
bwipe!
endfunc
+
+" Test ":s/pat/sub/" with different ~s in sub.
+func Test_replace_with_tilde()
+ new
+ " Set the last replace string to empty
+ s/^$//
+ call append(0, ['- Bug in "vPPPP" on this text:'])
+ normal gg
+ s/u/~u~/
+ call assert_equal('- Bug in "vPPPP" on this text:', getline(1))
+ s/i/~u~/
+ call assert_equal('- Bug uuun "vPPPP" on this text:', getline(1))
+ s/o/~~~/
+ call assert_equal('- Bug uuun "vPPPP" uuuuuuuuun this text:', getline(1))
+ close!
+endfunc
+
+func Test_replace_keeppatterns()
+ new
+ a
+foobar
+
+substitute foo asdf
+
+one two
+.
+
+ normal gg
+ /^substitute
+ s/foo/bar/
+ call assert_equal('foo', @/)
+ call assert_equal('substitute bar asdf', getline('.'))
+
+ /^substitute
+ keeppatterns s/asdf/xyz/
+ call assert_equal('^substitute', @/)
+ call assert_equal('substitute bar xyz', getline('.'))
+
+ exe "normal /bar /e\<CR>"
+ call assert_equal(15, col('.'))
+ normal -
+ keeppatterns /xyz
+ call assert_equal('bar ', @/)
+ call assert_equal('substitute bar xyz', getline('.'))
+ exe "normal 0dn"
+ call assert_equal('xyz', getline('.'))
+
+ close!
+endfunc
diff --git a/src/nvim/testdir/test_swap.vim b/src/nvim/testdir/test_swap.vim
index bc7b7c00d3..3db438cf4b 100644
--- a/src/nvim/testdir/test_swap.vim
+++ b/src/nvim/testdir/test_swap.vim
@@ -1,5 +1,9 @@
" Tests for the swap feature
+func s:swapname()
+ return trim(execute('swapname'))
+endfunc
+
" Tests for 'directory' option.
func Test_swap_directory()
if !has("unix")
@@ -17,7 +21,7 @@ func Test_swap_directory()
" Verify that the swap file doesn't exist in the current directory
call assert_equal([], glob(".Xtest1*.swp", 1, 1, 1))
edit Xtest1
- let swfname = split(execute("swapname"))[0]
+ let swfname = s:swapname()
call assert_equal([swfname], glob(swfname, 1, 1, 1))
" './dir', swap file in a directory relative to the file
@@ -27,7 +31,7 @@ func Test_swap_directory()
edit Xtest1
call assert_equal([], glob(swfname, 1, 1, 1))
let swfname = "Xtest2/Xtest1.swp"
- call assert_equal(swfname, split(execute("swapname"))[0])
+ call assert_equal(swfname, s:swapname())
call assert_equal([swfname], glob("Xtest2/*", 1, 1, 1))
" 'dir', swap file in directory relative to the current dir
@@ -38,7 +42,7 @@ func Test_swap_directory()
edit Xtest2/Xtest3
call assert_equal(["Xtest2/Xtest3"], glob("Xtest2/*", 1, 1, 1))
let swfname = "Xtest.je/Xtest3.swp"
- call assert_equal(swfname, split(execute("swapname"))[0])
+ call assert_equal(swfname, s:swapname())
call assert_equal([swfname], glob("Xtest.je/*", 1, 1, 1))
set dir&
@@ -47,6 +51,42 @@ func Test_swap_directory()
call delete("Xtest.je", "rf")
endfunc
+func Test_swap_group()
+ if !has("unix")
+ return
+ endif
+ let groups = split(system('groups'))
+ if len(groups) <= 1
+ throw 'Skipped: need at least two groups, got ' . string(groups)
+ endif
+
+ try
+ call delete('Xtest')
+ split Xtest
+ call setline(1, 'just some text')
+ wq
+ if system('ls -l Xtest') !~ ' ' . groups[0] . ' \d'
+ throw 'Skipped: test file does not have the first group'
+ else
+ silent !chmod 640 Xtest
+ call system('chgrp ' . groups[1] . ' Xtest')
+ if system('ls -l Xtest') !~ ' ' . groups[1] . ' \d'
+ throw 'Skipped: cannot set second group on test file'
+ else
+ split Xtest
+ let swapname = substitute(execute('swapname'), '[[:space:]]', '', 'g')
+ call assert_match('Xtest', swapname)
+ " Group of swapfile must now match original file.
+ call assert_match(' ' . groups[1] . ' \d', system('ls -l ' . swapname))
+
+ bwipe!
+ endif
+ endif
+ finally
+ call delete('Xtest')
+ endtry
+endfunc
+
func Test_missing_dir()
call mkdir('Xswapdir')
exe 'set directory=' . getcwd() . '/Xswapdir'
@@ -61,3 +101,120 @@ func Test_missing_dir()
set directory&
call delete('Xswapdir', 'rf')
endfunc
+
+func Test_swapinfo()
+ new Xswapinfo
+ call setline(1, ['one', 'two', 'three'])
+ w
+ let fname = s:swapname()
+ call assert_match('Xswapinfo', fname)
+ let info = swapinfo(fname)
+
+ let ver = printf('VIM %d.%d', v:version / 100, v:version % 100)
+ call assert_equal(ver, info.version)
+
+ call assert_match('\w', info.user)
+ " host name is truncated to 39 bytes in the swap file
+ call assert_equal(hostname()[:38], info.host)
+ call assert_match('Xswapinfo', info.fname)
+ call assert_match(0, info.dirty)
+ call assert_equal(getpid(), info.pid)
+ call assert_match('^\d*$', info.mtime)
+ if has_key(info, 'inode')
+ call assert_match('\d', info.inode)
+ endif
+ bwipe!
+ call delete(fname)
+ call delete('Xswapinfo')
+
+ let info = swapinfo('doesnotexist')
+ call assert_equal('Cannot open file', info.error)
+
+ call writefile(['burp'], 'Xnotaswapfile')
+ let info = swapinfo('Xnotaswapfile')
+ call assert_equal('Cannot read file', info.error)
+ call delete('Xnotaswapfile')
+
+ call writefile([repeat('x', 10000)], 'Xnotaswapfile')
+ let info = swapinfo('Xnotaswapfile')
+ call assert_equal('Not a swap file', info.error)
+ call delete('Xnotaswapfile')
+endfunc
+
+func Test_swapname()
+ edit Xtest1
+ let expected = s:swapname()
+ call assert_equal(expected, swapname('%'))
+
+ new Xtest2
+ let buf = bufnr('%')
+ let expected = s:swapname()
+ wincmd p
+ call assert_equal(expected, swapname(buf))
+
+ new Xtest3
+ setlocal noswapfile
+ call assert_equal('', swapname('%'))
+
+ bwipe!
+ call delete('Xtest1')
+ call delete('Xtest2')
+ call delete('Xtest3')
+endfunc
+
+func Test_swapfile_delete()
+ throw 'skipped: need the "blob" feature for this test'
+ autocmd! SwapExists
+ function s:swap_exists()
+ let v:swapchoice = s:swap_choice
+ let s:swapname = v:swapname
+ let s:filename = expand('<afile>')
+ endfunc
+ augroup test_swapfile_delete
+ autocmd!
+ autocmd SwapExists * call s:swap_exists()
+ augroup END
+
+
+ " Create a valid swapfile by editing a file.
+ split XswapfileText
+ call setline(1, ['one', 'two', 'three'])
+ write " file is written, not modified
+ " read the swapfile as a Blob
+ let swapfile_name = swapname('%')
+ let swapfile_bytes = readfile(swapfile_name, 'B')
+
+ " Close the file and recreate the swap file.
+ " Now editing the file will run into the process still existing
+ quit
+ call writefile(swapfile_bytes, swapfile_name)
+ let s:swap_choice = 'e'
+ let s:swapname = ''
+ split XswapfileText
+ quit
+ call assert_equal(fnamemodify(swapfile_name, ':t'), fnamemodify(s:swapname, ':t'))
+
+ " Write the swapfile with a modified PID, now it will be automatically
+ " deleted. Process one should never be Vim.
+ let swapfile_bytes[24:27] = 0z01000000
+ call writefile(swapfile_bytes, swapfile_name)
+ let s:swapname = ''
+ split XswapfileText
+ quit
+ call assert_equal('', s:swapname)
+
+ " Now set the modified flag, the swap file will not be deleted
+ let swapfile_bytes[28 + 80 + 899] = 0x55
+ call writefile(swapfile_bytes, swapfile_name)
+ let s:swapname = ''
+ split XswapfileText
+ quit
+ call assert_equal(fnamemodify(swapfile_name, ':t'), fnamemodify(s:swapname, ':t'))
+
+ call delete('XswapfileText')
+ call delete(swapfile_name)
+ augroup test_swapfile_delete
+ autocmd!
+ augroup END
+ augroup! test_swapfile_delete
+endfunc
diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim
index 6978faeb7b..850947f89f 100644
--- a/src/nvim/testdir/test_syntax.vim
+++ b/src/nvim/testdir/test_syntax.vim
@@ -503,3 +503,37 @@ func Test_syn_wrong_z_one()
" call test_override("ALL", 0)
bwipe!
endfunc
+
+func Test_syntax_hangs()
+ if !has('reltime') || !has('float') || !has('syntax')
+ return
+ endif
+
+ " This pattern takes a long time to match, it should timeout.
+ new
+ call setline(1, ['aaa', repeat('abc ', 1000), 'ccc'])
+ let start = reltime()
+ set nolazyredraw redrawtime=101
+ syn match Error /\%#=1a*.*X\@<=b*/
+ redraw
+ let elapsed = reltimefloat(reltime(start))
+ call assert_true(elapsed > 0.1)
+ call assert_true(elapsed < 1.0)
+
+ " second time syntax HL is disabled
+ let start = reltime()
+ redraw
+ let elapsed = reltimefloat(reltime(start))
+ call assert_true(elapsed < 0.1)
+
+ " after CTRL-L the timeout flag is reset
+ let start = reltime()
+ exe "normal \<C-L>"
+ redraw
+ let elapsed = reltimefloat(reltime(start))
+ call assert_true(elapsed > 0.1)
+ call assert_true(elapsed < 1.0)
+
+ set redrawtime&
+ bwipe!
+endfunc
diff --git a/src/nvim/testdir/test_tabpage.vim b/src/nvim/testdir/test_tabpage.vim
index add9b3d7cf..8043d13433 100644
--- a/src/nvim/testdir/test_tabpage.vim
+++ b/src/nvim/testdir/test_tabpage.vim
@@ -1,5 +1,6 @@
" Tests for tabpage
+" source screendump.vim
function Test_tabpage()
bw!
@@ -105,6 +106,14 @@ function Test_tabpage()
call assert_equal(4, tabpagenr())
7tabmove 5
call assert_equal(5, tabpagenr())
+ -tabmove
+ call assert_equal(4, tabpagenr())
+ +tabmove
+ call assert_equal(5, tabpagenr())
+ -2tabmove
+ call assert_equal(3, tabpagenr())
+ +3tabmove
+ call assert_equal(6, tabpagenr())
" The following are a no-op
norm! 2gt
@@ -547,4 +556,27 @@ func Test_tabs()
bw!
endfunc
+func Test_tabpage_cmdheight()
+ if !CanRunVimInTerminal()
+ throw 'Skipped: only works with terminal'
+ endif
+ call writefile([
+ \ 'set laststatus=2',
+ \ 'set cmdheight=2',
+ \ 'tabnew',
+ \ 'set cmdheight=3',
+ \ 'tabnext',
+ \ 'redraw!',
+ \ 'echo "hello\nthere"',
+ \ 'tabnext',
+ \ 'redraw',
+ \ ], 'XTest_tabpage_cmdheight')
+ " Check that cursor line is concealed
+ let buf = RunVimInTerminal('-S XTest_tabpage_cmdheight', {'statusoff': 3})
+ call VerifyScreenDump(buf, 'Test_tabpage_cmdheight', {})
+
+ call StopVimInTerminal(buf)
+ call delete('XTest_tabpage_cmdheight')
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim
index ad1f5fab73..21ea00cab7 100644
--- a/src/nvim/testdir/test_tagjump.vim
+++ b/src/nvim/testdir/test_tagjump.vim
@@ -258,6 +258,107 @@ func Test_tagjump_etags()
bwipe!
endfunc
+" Test for getting and modifying the tag stack
+func Test_getsettagstack()
+ call writefile(['line1', 'line2', 'line3'], 'Xfile1')
+ call writefile(['line1', 'line2', 'line3'], 'Xfile2')
+ call writefile(['line1', 'line2', 'line3'], 'Xfile3')
+
+ enew | only
+ call settagstack(1, {'items' : []})
+ call assert_equal(0, gettagstack(1).length)
+ call assert_equal([], gettagstack(1).items)
+ " Error cases
+ call assert_equal({}, gettagstack(100))
+ call assert_equal(-1, settagstack(100, {'items' : []}))
+ call assert_fails('call settagstack(1, [1, 10])', 'E715')
+ call assert_fails("call settagstack(1, {'items' : 10})", 'E714')
+ call assert_fails("call settagstack(1, {'items' : []}, 10)", 'E928')
+ call assert_fails("call settagstack(1, {'items' : []}, 'b')", 'E962')
+
+ set tags=Xtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "one\tXfile1\t1",
+ \ "three\tXfile3\t3",
+ \ "two\tXfile2\t2"],
+ \ 'Xtags')
+
+ let stk = []
+ call add(stk, {'bufnr' : bufnr('%'), 'tagname' : 'one',
+ \ 'from' : [bufnr('%'), line('.'), col('.'), 0], 'matchnr' : 1})
+ tag one
+ call add(stk, {'bufnr' : bufnr('%'), 'tagname' : 'two',
+ \ 'from' : [bufnr('%'), line('.'), col('.'), 0], 'matchnr' : 1})
+ tag two
+ call add(stk, {'bufnr' : bufnr('%'), 'tagname' : 'three',
+ \ 'from' : [bufnr('%'), line('.'), col('.'), 0], 'matchnr' : 1})
+ tag three
+ call assert_equal(3, gettagstack(1).length)
+ call assert_equal(stk, gettagstack(1).items)
+ " Check for default - current window
+ call assert_equal(3, gettagstack().length)
+ call assert_equal(stk, gettagstack().items)
+
+ " Try to set current index to invalid values
+ call settagstack(1, {'curidx' : -1})
+ call assert_equal(1, gettagstack().curidx)
+ call settagstack(1, {'curidx' : 50})
+ call assert_equal(4, gettagstack().curidx)
+
+ " Try pushing invalid items onto the stack
+ call settagstack(1, {'items' : []})
+ call settagstack(1, {'items' : ["plate"]}, 'a')
+ call assert_equal(0, gettagstack().length)
+ call assert_equal([], gettagstack().items)
+ call settagstack(1, {'items' : [{"tagname" : "abc"}]}, 'a')
+ call assert_equal(0, gettagstack().length)
+ call assert_equal([], gettagstack().items)
+ call settagstack(1, {'items' : [{"from" : 100}]}, 'a')
+ call assert_equal(0, gettagstack().length)
+ call assert_equal([], gettagstack().items)
+ call settagstack(1, {'items' : [{"from" : [2, 1, 0, 0]}]}, 'a')
+ call assert_equal(0, gettagstack().length)
+ call assert_equal([], gettagstack().items)
+
+ " Push one item at a time to the stack
+ call settagstack(1, {'items' : []})
+ call settagstack(1, {'items' : [stk[0]]}, 'a')
+ call settagstack(1, {'items' : [stk[1]]}, 'a')
+ call settagstack(1, {'items' : [stk[2]]}, 'a')
+ call settagstack(1, {'curidx' : 4})
+ call assert_equal({'length' : 3, 'curidx' : 4, 'items' : stk},
+ \ gettagstack(1))
+
+ " Try pushing items onto a full stack
+ for i in range(7)
+ call settagstack(1, {'items' : stk}, 'a')
+ endfor
+ call assert_equal(20, gettagstack().length)
+ call settagstack(1,
+ \ {'items' : [{'tagname' : 'abc', 'from' : [1, 10, 1, 0]}]}, 'a')
+ call assert_equal('abc', gettagstack().items[19].tagname)
+
+ " Tag with multiple matches
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "two\tXfile1\t1",
+ \ "two\tXfile2\t3",
+ \ "two\tXfile3\t2"],
+ \ 'Xtags')
+ call settagstack(1, {'items' : []})
+ tag two
+ tnext
+ tnext
+ call assert_equal(1, gettagstack().length)
+ call assert_equal(3, gettagstack().items[0].matchnr)
+
+ call settagstack(1, {'items' : []})
+ call delete('Xfile1')
+ call delete('Xfile2')
+ call delete('Xfile3')
+ call delete('Xtags')
+ set tags&
+endfunc
+
func Test_tag_with_count()
call writefile([
\ 'test Xtest.h /^void test();$/;" p typeref:typename:void signature:()',
diff --git a/src/nvim/testdir/test_taglist.vim b/src/nvim/testdir/test_taglist.vim
index f4e939254a..ea0a6b9678 100644
--- a/src/nvim/testdir/test_taglist.vim
+++ b/src/nvim/testdir/test_taglist.vim
@@ -1,4 +1,4 @@
-" test 'taglist' function and :tags command
+" test taglist(), tagfiles() functions and :tags command
func Test_taglist()
call writefile([
@@ -74,3 +74,26 @@ func Test_tagsfile_without_trailing_newline()
call delete('Xtags')
endfunc
+
+func Test_tagfiles()
+ call assert_equal([], tagfiles())
+
+ call writefile(["FFoo\tXfoo\t1"], 'Xtags1')
+ call writefile(["FBar\tXbar\t1"], 'Xtags2')
+ set tags=Xtags1,Xtags2
+ call assert_equal(['Xtags1', 'Xtags2'], tagfiles())
+
+ help
+ let tf = tagfiles()
+ call assert_equal(1, len(tf))
+ call assert_equal(fnamemodify(expand('$VIMRUNTIME/doc/tags'), ':p:gs?\\?/?'),
+ \ fnamemodify(tf[0], ':p:gs?\\?/?'))
+ helpclose
+ call assert_equal(['Xtags1', 'Xtags2'], tagfiles())
+ set tags&
+ call assert_equal([], tagfiles())
+
+ call delete('Xtags1')
+ call delete('Xtags2')
+ bd
+endfunc
diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim
index 9729ca9f57..beb44f9699 100644
--- a/src/nvim/testdir/test_undo.vim
+++ b/src/nvim/testdir/test_undo.vim
@@ -242,6 +242,7 @@ func Test_undojoin()
endfunc
func Test_undo_write()
+ call delete('Xtest')
split Xtest
call feedkeys("ione one one\<Esc>", 'xt')
w!
diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim
index 74afc72f03..f69273635c 100644
--- a/src/nvim/testdir/test_visual.vim
+++ b/src/nvim/testdir/test_visual.vim
@@ -411,3 +411,27 @@ func Test_Visual_paragraph_textobject()
bwipe!
endfunc
+
+" Tests for "vaBiB", end could be wrong.
+func Test_Visual_Block()
+ new
+ a
+- Bug in "vPPPP" on this text:
+ {
+ cmd;
+ {
+ cmd;\t/* <-- Start cursor here */
+ {
+ }
+ }
+ }
+.
+ normal gg
+ call search('Start cursor here')
+ normal vaBiBD
+ call assert_equal(['- Bug in "vPPPP" on this text:',
+ \ "\t{",
+ \ "\t}"], getline(1, '$'))
+
+ close!
+endfunc
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c
index 04decb1a8c..6d9023bd79 100644
--- a/src/nvim/tui/input.c
+++ b/src/nvim/tui/input.c
@@ -49,15 +49,7 @@ void tinput_init(TermInput *input, Loop *loop)
int curflags = termkey_get_canonflags(input->tk);
termkey_set_canonflags(input->tk, curflags | TERMKEY_CANON_DELBS);
// setup input handle
-#ifdef WIN32
- uv_tty_init(&loop->uv, &input->tty_in, 0, 1);
- uv_tty_set_mode(&input->tty_in, UV_TTY_MODE_RAW);
- rstream_init_stream(&input->read_stream,
- (uv_stream_t *)&input->tty_in,
- 0xfff);
-#else
rstream_init_fd(loop, &input->read_stream, input->in_fd, 0xfff);
-#endif
// initialize a timer handle for handling ESC with libtermkey
time_watcher_init(loop, &input->timer_handle, input);
}
diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h
index 573cc9d683..7d59cf5c6a 100644
--- a/src/nvim/tui/input.h
+++ b/src/nvim/tui/input.h
@@ -17,9 +17,6 @@ typedef struct term_input {
#endif
TimeWatcher timer_handle;
Loop *loop;
-#ifdef WIN32
- uv_tty_t tty_in;
-#endif
Stream read_stream;
RBuffer *key_buffer;
uv_mutex_t key_buffer_mutex;
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index 0603e504ec..42e5b9b270 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -704,14 +704,6 @@ static void cursor_goto(UI *ui, int row, int col)
// even less expensive than using BSes or CUB.
unibi_out(ui, unibi_carriage_return);
ugrid_goto(grid, grid->row, 0);
- } else if (col > grid->col) {
- int n = col - grid->col;
- if (n <= (row == grid->row ? 4 : 2)
- && cheap_to_print(ui, grid->row, grid->col, n)) {
- UGRID_FOREACH_CELL(grid, grid->row, grid->col, col, {
- print_cell(ui, cell);
- });
- }
}
if (row == grid->row) {
if (col < grid->col
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index 0dddfe5f59..7dbb8ec790 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -141,6 +141,17 @@ bool ui_rgb_attached(void)
return false;
}
+/// Returns true if any UI requested `override=true`.
+bool ui_override(void)
+{
+ for (size_t i = 1; i < ui_count; i++) {
+ if (uis[i]->override) {
+ return true;
+ }
+ }
+ return false;
+}
+
bool ui_active(void)
{
return ui_count > 1;
@@ -173,12 +184,13 @@ void ui_refresh(void)
ext_widgets[i] = true;
}
+ bool inclusive = ui_override();
for (size_t i = 0; i < ui_count; i++) {
UI *ui = uis[i];
width = MIN(ui->width, width);
height = MIN(ui->height, height);
for (UIExtension j = 0; (int)j < kUIExtCount; j++) {
- ext_widgets[j] &= ui->ui_ext[j];
+ ext_widgets[j] &= (ui->ui_ext[j] || inclusive);
}
}
@@ -431,6 +443,7 @@ Array ui_array(void)
PUT(info, "width", INTEGER_OBJ(ui->width));
PUT(info, "height", INTEGER_OBJ(ui->height));
PUT(info, "rgb", BOOLEAN_OBJ(ui->rgb));
+ PUT(info, "override", BOOLEAN_OBJ(ui->override));
for (UIExtension j = 0; j < kUIExtCount; j++) {
if (ui_ext_names[j][0] != '_' || ui->ui_ext[j]) {
PUT(info, ui_ext_names[j], BOOLEAN_OBJ(ui->ui_ext[j]));
diff --git a/src/nvim/ui.h b/src/nvim/ui.h
index 3f6b3babad..e1dd18a289 100644
--- a/src/nvim/ui.h
+++ b/src/nvim/ui.h
@@ -48,9 +48,11 @@ typedef int LineFlags;
struct ui_t {
bool rgb;
+ bool override; ///< Force highest-requested UI capabilities.
bool composed;
- bool ui_ext[kUIExtCount]; ///< Externalized widgets
- int width, height;
+ bool ui_ext[kUIExtCount]; ///< Externalized UI capabilities.
+ int width;
+ int height;
void *data;
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index 8bb1b9e745..10996d99d5 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -1131,8 +1131,9 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf,
/* If there is no undo information at all, quit here after deleting any
* existing undo file. */
if (buf->b_u_numhead == 0 && buf->b_u_line_ptr == NULL) {
- if (p_verbose > 0)
- verb_msg((char_u *)_("Skipping undo file write, nothing to undo"));
+ if (p_verbose > 0) {
+ verb_msg(_("Skipping undo file write, nothing to undo"));
+ }
goto theend;
}
@@ -1818,7 +1819,7 @@ void undo_time(long step, bool sec, bool file, bool absolute)
u_header_T *uhp = NULL;
u_header_T *last;
int mark;
- int nomark;
+ int nomark = 0; // shut up compiler
int round;
bool dosec = sec;
bool dofile = file;
@@ -2150,7 +2151,7 @@ static void u_undoredo(int undo, bool do_buf_event)
int new_flags;
fmark_T namedm[NMARKS];
visualinfo_T visualinfo;
- int empty_buffer; /* buffer became empty */
+ bool empty_buffer; // buffer became empty
u_header_T *curhead = curbuf->b_u_curhead;
/* Don't want autocommands using the undo structures here, they are
@@ -2217,7 +2218,7 @@ static void u_undoredo(int undo, bool do_buf_event)
}
}
- empty_buffer = FALSE;
+ empty_buffer = false;
/* delete the lines between top and bot and save them in newarray */
if (oldsize > 0) {
@@ -2228,9 +2229,10 @@ static void u_undoredo(int undo, bool do_buf_event)
newarray[i] = u_save_line(lnum);
/* remember we deleted the last line in the buffer, and a
* dummy empty line will be inserted */
- if (curbuf->b_ml.ml_line_count == 1)
- empty_buffer = TRUE;
- ml_delete(lnum, FALSE);
+ if (curbuf->b_ml.ml_line_count == 1) {
+ empty_buffer = true;
+ }
+ ml_delete(lnum, false);
}
} else
newarray = NULL;
@@ -2245,7 +2247,7 @@ static void u_undoredo(int undo, bool do_buf_event)
if (empty_buffer && lnum == 0) {
ml_replace((linenr_T)1, uep->ue_array[i], true);
} else {
- ml_append(lnum, uep->ue_array[i], (colnr_T)0, FALSE);
+ ml_append(lnum, uep->ue_array[i], (colnr_T)0, false);
}
xfree(uep->ue_array[i]);
}
diff --git a/src/nvim/vim.h b/src/nvim/vim.h
index 767936ecee..3e0a5907be 100644
--- a/src/nvim/vim.h
+++ b/src/nvim/vim.h
@@ -277,7 +277,6 @@ enum { FOLD_TEXT_LEN = 51 }; //!< buffer size for get_foldtext()
// Enums need a typecast to be used as array index (for Ultrix).
#define HL_ATTR(n) highlight_attr[(int)(n)]
-#define TERM_STR(n) term_strings[(int)(n)]
/// Maximum number of bytes in a multi-byte character. It can be one 32-bit
/// character of up to 6 bytes, or one 16-bit character of up to three bytes
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 9d8cd21dba..6bc082ffb2 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -2295,8 +2295,7 @@ int win_close(win_T *win, bool free_buf)
EMSG(_("E813: Cannot close autocmd window"));
return FAIL;
}
- if ((firstwin == aucmd_win || lastwin_nofloating() == aucmd_win)
- && one_window()) {
+ if ((firstwin == aucmd_win || lastwin == aucmd_win) && one_window()) {
EMSG(_("E814: Cannot close window, only autocmd window would remain"));
return FAIL;
}
@@ -3413,16 +3412,18 @@ int win_alloc_first(void)
return OK;
}
-/*
- * Init "aucmd_win". This can only be done after the first
- * window is fully initialized, thus it can't be in win_alloc_first().
- */
+// Init `aucmd_win`. This can only be done after the first window
+// is fully initialized, thus it can't be in win_alloc_first().
void win_alloc_aucmd_win(void)
{
- aucmd_win = win_alloc(NULL, TRUE);
- win_init_some(aucmd_win, curwin);
+ Error err = ERROR_INIT;
+ FloatConfig fconfig = FLOAT_CONFIG_INIT;
+ fconfig.width = Columns;
+ fconfig.height = 5;
+ fconfig.focusable = false;
+ aucmd_win = win_new_float(NULL, fconfig, &err);
+ aucmd_win->w_buffer->b_nwindows--;
RESET_BINDING(aucmd_win);
- new_frame(aucmd_win);
}
/*
@@ -3793,6 +3794,9 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, int trigger_enter_au
* the frames for that. When the Vim window was resized need to update
* frame sizes too. Use the stored value of p_ch, so that it can be
* different for each tab page. */
+ if (p_ch != curtab->tp_ch_used) {
+ clear_cmdline = true;
+ }
p_ch = curtab->tp_ch_used;
if (curtab->tp_old_Rows != Rows || (old_off != firstwin->w_winrow
))
@@ -3847,7 +3851,7 @@ static void tabpage_check_windows(tabpage_T *old_curtab)
*/
void goto_tabpage(int n)
{
- tabpage_T *tp;
+ tabpage_T *tp = NULL; // shut up compiler
tabpage_T *ttp;
int i;
@@ -5959,16 +5963,40 @@ void check_lnums(int do_curwin)
{
FOR_ALL_TAB_WINDOWS(tp, wp) {
if ((do_curwin || wp != curwin) && wp->w_buffer == curbuf) {
+ // save the original cursor position and topline
+ wp->w_save_cursor.w_cursor_save = wp->w_cursor;
+ wp->w_save_cursor.w_topline_save = wp->w_topline;
+
if (wp->w_cursor.lnum > curbuf->b_ml.ml_line_count) {
wp->w_cursor.lnum = curbuf->b_ml.ml_line_count;
}
if (wp->w_topline > curbuf->b_ml.ml_line_count) {
wp->w_topline = curbuf->b_ml.ml_line_count;
}
+
+ // save the corrected cursor position and topline
+ wp->w_save_cursor.w_cursor_corr = wp->w_cursor;
+ wp->w_save_cursor.w_topline_corr = wp->w_topline;
}
}
}
+/// Reset cursor and topline to its stored values from check_lnums().
+/// check_lnums() must have been called first!
+void reset_lnums(void)
+{
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ if (wp->w_buffer == curbuf) {
+ // Restore the value if the autocommand didn't change it.
+ if (equalpos(wp->w_save_cursor.w_cursor_corr, wp->w_cursor)) {
+ wp->w_cursor = wp->w_save_cursor.w_cursor_save;
+ }
+ if (wp->w_save_cursor.w_topline_corr == wp->w_topline) {
+ wp->w_topline = wp->w_save_cursor.w_topline_save;
+ }
+ }
+ }
+}
/*
* A snapshot of the window sizes, to restore them after closing the help
diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua
index d26cb0dbb3..9d6cfb99ab 100644
--- a/test/functional/api/buffer_spec.lua
+++ b/test/functional/api/buffer_spec.lua
@@ -39,6 +39,41 @@ describe('api/buf', function()
eq(1, curbuf_depr('line_count'))
end)
+ it('cursor position is maintained after lines are inserted #9961', function()
+ -- replace the buffer contents with these three lines.
+ request('nvim_buf_set_lines', 0, 0, -1, 1, {"line1", "line2", "line3", "line4"})
+ -- Set the current cursor to {3, 2}.
+ curwin('set_cursor', {3, 2})
+
+ -- add 2 lines and delete 1 line above the current cursor position.
+ request('nvim_buf_set_lines', 0, 1, 2, 1, {"line5", "line6"})
+ -- check the current set of lines in the buffer.
+ eq({"line1", "line5", "line6", "line3", "line4"}, buffer('get_lines', 0, 0, -1, 1))
+ -- cursor should be moved below by 1 line.
+ eq({4, 2}, curwin('get_cursor'))
+
+ -- add a line after the current cursor position.
+ request('nvim_buf_set_lines', 0, 5, 5, 1, {"line7"})
+ -- check the current set of lines in the buffer.
+ eq({"line1", "line5", "line6", "line3", "line4", "line7"}, buffer('get_lines', 0, 0, -1, 1))
+ -- cursor position is unchanged.
+ eq({4, 2}, curwin('get_cursor'))
+
+ -- overwrite current cursor line.
+ request('nvim_buf_set_lines', 0, 3, 5, 1, {"line8", "line9"})
+ -- check the current set of lines in the buffer.
+ eq({"line1", "line5", "line6", "line8", "line9", "line7"}, buffer('get_lines', 0, 0, -1, 1))
+ -- cursor position is unchanged.
+ eq({4, 2}, curwin('get_cursor'))
+
+ -- delete current cursor line.
+ request('nvim_buf_set_lines', 0, 3, 5, 1, {})
+ -- check the current set of lines in the buffer.
+ eq({"line1", "line5", "line6", "line7"}, buffer('get_lines', 0, 0, -1, 1))
+ -- cursor position is unchanged.
+ eq({4, 2}, curwin('get_cursor'))
+ end)
+
it('line_count has defined behaviour for unloaded buffers', function()
-- we'll need to know our bufnr for when it gets unloaded
local bufnr = curbuf('get_number')
diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua
index f52372bee3..c150787425 100644
--- a/test/functional/api/keymap_spec.lua
+++ b/test/functional/api/keymap_spec.lua
@@ -1,15 +1,19 @@
local helpers = require('test.functional.helpers')(after_each)
local global_helpers = require('test.helpers')
+local bufmeths = helpers.bufmeths
local clear = helpers.clear
local command = helpers.command
local curbufmeths = helpers.curbufmeths
-local eq = helpers.eq
+local eq, neq = helpers.eq, helpers.neq
+local expect_err = helpers.expect_err
+local feed = helpers.feed
local funcs = helpers.funcs
local meths = helpers.meths
local source = helpers.source
local shallowcopy = global_helpers.shallowcopy
+local sleep = global_helpers.sleep
describe('nvim_get_keymap', function()
before_each(clear)
@@ -308,3 +312,502 @@ describe('nvim_get_keymap', function()
eq({space_table}, meths.get_keymap('n'))
end)
end)
+
+describe('nvim_set_keymap, nvim_del_keymap', function()
+ before_each(clear)
+
+ -- `generate_expected` is truthy: for generating an expected output for
+ -- maparg(), which does not accept "!" (though it returns "!" in its output
+ -- if getting a mapping set with |:map!|).
+ local function normalize_mapmode(mode, generate_expected)
+ if not generate_expected and mode == '!' then
+ -- Cannot retrieve mapmode-ic mappings with "!", but can with "i" or "c".
+ mode = 'i'
+ elseif mode == '' then
+ mode = generate_expected and ' ' or mode
+ end
+ return mode
+ end
+
+ -- Generate a mapargs dict, for comparison against the mapping that was
+ -- actually set
+ local function generate_mapargs(mode, lhs, rhs, opts)
+ if not opts then
+ opts = {}
+ end
+
+ local to_return = {}
+ to_return.mode = normalize_mapmode(mode, true)
+ to_return.noremap = not opts.noremap and 0 or 1
+ to_return.lhs = lhs
+ to_return.rhs = rhs
+ to_return.silent = not opts.silent and 0 or 1
+ to_return.nowait = not opts.nowait and 0 or 1
+ to_return.expr = not opts.expr and 0 or 1
+ to_return.sid = not opts.sid and 0 or opts.sid
+ to_return.buffer = not opts.buffer and 0 or opts.buffer
+
+ -- mode 't' doesn't print when calling maparg
+ if mode == 't' then
+ to_return.mode = ''
+ end
+
+ return to_return
+ end
+
+ -- Gets a maparg() dict from Nvim, if one exists.
+ local function get_mapargs(mode, lhs)
+ return funcs.maparg(lhs, normalize_mapmode(mode), false, true)
+ end
+
+ it('error on empty LHS', function()
+ -- escape parentheses in lua string, else comparison fails erroneously
+ expect_err('Invalid %(empty%) LHS',
+ meths.set_keymap, '', '', 'rhs', {})
+ expect_err('Invalid %(empty%) LHS',
+ meths.set_keymap, '', '', '', {})
+
+ expect_err('Invalid %(empty%) LHS', meths.del_keymap, '', '')
+ end)
+
+ it('error if LHS longer than MAXMAPLEN', function()
+ -- assume MAXMAPLEN of 50 chars, as declared in vim.h
+ local MAXMAPLEN = 50
+ local lhs = ''
+ for i=1,MAXMAPLEN do
+ lhs = lhs..(i % 10)
+ end
+
+ -- exactly 50 chars should be fine
+ meths.set_keymap('', lhs, 'rhs', {})
+
+ -- del_keymap should unmap successfully
+ meths.del_keymap('', lhs)
+ eq({}, get_mapargs('', lhs))
+
+ -- 51 chars should produce an error
+ lhs = lhs..'1'
+ expect_err('LHS exceeds maximum map length: '..lhs,
+ meths.set_keymap, '', lhs, 'rhs', {})
+ expect_err('LHS exceeds maximum map length: '..lhs,
+ meths.del_keymap, '', lhs)
+ end)
+
+ it('does not throw errors when rhs is longer than MAXMAPLEN', function()
+ local MAXMAPLEN = 50
+ local rhs = ''
+ for i=1,MAXMAPLEN do
+ rhs = rhs..(i % 10)
+ end
+ rhs = rhs..'1'
+ meths.set_keymap('', 'lhs', rhs, {})
+ eq(generate_mapargs('', 'lhs', rhs),
+ get_mapargs('', 'lhs'))
+ end)
+
+ it('throws errors when given too-long mode shortnames', function()
+ expect_err('Shortname is too long: map',
+ meths.set_keymap, 'map', 'lhs', 'rhs', {})
+
+ expect_err('Shortname is too long: vmap',
+ meths.set_keymap, 'vmap', 'lhs', 'rhs', {})
+
+ expect_err('Shortname is too long: xnoremap',
+ meths.set_keymap, 'xnoremap', 'lhs', 'rhs', {})
+
+ expect_err('Shortname is too long: map', meths.del_keymap, 'map', 'lhs')
+ expect_err('Shortname is too long: vmap', meths.del_keymap, 'vmap', 'lhs')
+ expect_err('Shortname is too long: xnoremap', meths.del_keymap, 'xnoremap', 'lhs')
+ end)
+
+ it('error on invalid mode shortname', function()
+ expect_err('Invalid mode shortname: " "',
+ meths.set_keymap, ' ', 'lhs', 'rhs', {})
+ expect_err('Invalid mode shortname: "m"',
+ meths.set_keymap, 'm', 'lhs', 'rhs', {})
+ expect_err('Invalid mode shortname: "?"',
+ meths.set_keymap, '?', 'lhs', 'rhs', {})
+ expect_err('Invalid mode shortname: "y"',
+ meths.set_keymap, 'y', 'lhs', 'rhs', {})
+ expect_err('Invalid mode shortname: "p"',
+ meths.set_keymap, 'p', 'lhs', 'rhs', {})
+ expect_err('Invalid mode shortname: "?"', meths.del_keymap, '?', 'lhs')
+ expect_err('Invalid mode shortname: "y"', meths.del_keymap, 'y', 'lhs')
+ expect_err('Invalid mode shortname: "p"', meths.del_keymap, 'p', 'lhs')
+ end)
+
+ it('error on invalid optnames', function()
+ expect_err('Invalid key: silentt',
+ meths.set_keymap, 'n', 'lhs', 'rhs', {silentt = true})
+ expect_err('Invalid key: sidd',
+ meths.set_keymap, 'n', 'lhs', 'rhs', {sidd = false})
+ expect_err('Invalid key: nowaiT',
+ meths.set_keymap, 'n', 'lhs', 'rhs', {nowaiT = false})
+ end)
+
+ it('error on <buffer> option key', function()
+ expect_err('Invalid key: buffer',
+ meths.set_keymap, 'n', 'lhs', 'rhs', {buffer = true})
+ end)
+
+ local optnames = {'nowait', 'silent', 'script', 'expr', 'unique'}
+ for _, opt in ipairs(optnames) do
+ -- note: need '%' to escape hyphens, which have special meaning in lua
+ it('throws an error when given non-boolean value for '..opt, function()
+ local opts = {}
+ opts[opt] = 2
+ expect_err('Gave non%-boolean value for an opt: '..opt,
+ meths.set_keymap, 'n', 'lhs', 'rhs', opts)
+ end)
+ end
+
+ -- Perform tests of basic functionality
+ it('sets ordinary mappings', function()
+ meths.set_keymap('n', 'lhs', 'rhs', {})
+ eq(generate_mapargs('n', 'lhs', 'rhs'), get_mapargs('n', 'lhs'))
+
+ meths.set_keymap('v', 'lhs', 'rhs', {})
+ eq(generate_mapargs('v', 'lhs', 'rhs'), get_mapargs('v', 'lhs'))
+ end)
+
+ it('does not throw when LHS or RHS have leading/trailing whitespace', function()
+ meths.set_keymap('n', ' lhs', 'rhs', {})
+ eq(generate_mapargs('n', '<Space><Space><Space>lhs', 'rhs'),
+ get_mapargs('n', ' lhs'))
+
+ meths.set_keymap('n', 'lhs ', 'rhs', {})
+ eq(generate_mapargs('n', 'lhs<Space><Space><Space><Space>', 'rhs'),
+ get_mapargs('n', 'lhs '))
+
+ meths.set_keymap('v', ' lhs ', '\trhs\t\f', {})
+ eq(generate_mapargs('v', '<Space>lhs<Space><Space>', '\trhs\t\f'),
+ get_mapargs('v', ' lhs '))
+ end)
+
+ it('can set noremap mappings', function()
+ meths.set_keymap('x', 'lhs', 'rhs', {noremap = true})
+ eq(generate_mapargs('x', 'lhs', 'rhs', {noremap = true}),
+ get_mapargs('x', 'lhs'))
+
+ meths.set_keymap('t', 'lhs', 'rhs', {noremap = true})
+ eq(generate_mapargs('t', 'lhs', 'rhs', {noremap = true}),
+ get_mapargs('t', 'lhs'))
+ end)
+
+ it('can unmap mappings', function()
+ meths.set_keymap('v', 'lhs', 'rhs', {})
+ meths.del_keymap('v', 'lhs')
+ eq({}, get_mapargs('v', 'lhs'))
+
+ meths.set_keymap('t', 'lhs', 'rhs', {noremap = true})
+ meths.del_keymap('t', 'lhs')
+ eq({}, get_mapargs('t', 'lhs'))
+ end)
+
+ -- Test some edge cases
+ it('"!" and empty string are synonyms for mapmode-nvo', function()
+ local nvo_shortnames = {'', '!'}
+ for _, name in ipairs(nvo_shortnames) do
+ meths.set_keymap(name, 'lhs', 'rhs', {})
+ meths.del_keymap(name, 'lhs')
+ eq({}, get_mapargs(name, 'lhs'))
+ end
+ end)
+
+ local special_chars = {'<C-U>', '<S-Left>', '<F12><F2><Tab>', '<Space><Tab>'}
+ for _, lhs in ipairs(special_chars) do
+ for _, rhs in ipairs(special_chars) do
+ local mapmode = '!'
+ it('can set mappings with special characters, lhs: '..lhs..', rhs: '..rhs,
+ function()
+ meths.set_keymap(mapmode, lhs, rhs, {})
+ eq(generate_mapargs(mapmode, lhs, rhs), get_mapargs(mapmode, lhs))
+ end)
+ end
+ end
+
+ it('can set mappings containing literal keycodes', function()
+ meths.set_keymap('n', '\n\r\n', 'rhs', {})
+ local expected = generate_mapargs('n', '<NL><CR><NL>', 'rhs')
+ eq(expected, get_mapargs('n', '<C-j><CR><C-j>'))
+ end)
+
+ it('can set mappings whose RHS is a <Nop>', function()
+ meths.set_keymap('i', 'lhs', '<Nop>', {})
+ command('normal ilhs')
+ eq({''}, curbufmeths.get_lines(0, -1, 0)) -- imap to <Nop> does nothing
+ eq(generate_mapargs('i', 'lhs', '<Nop>', {}),
+ get_mapargs('i', 'lhs'))
+
+ -- also test for case insensitivity
+ meths.set_keymap('i', 'lhs', '<nOp>', {})
+ command('normal ilhs')
+ eq({''}, curbufmeths.get_lines(0, -1, 0))
+ -- note: RHS in returned mapargs() dict reflects the original RHS
+ -- provided by the user
+ eq(generate_mapargs('i', 'lhs', '<nOp>', {}),
+ get_mapargs('i', 'lhs'))
+
+ meths.set_keymap('i', 'lhs', '<NOP>', {})
+ command('normal ilhs')
+ eq({''}, curbufmeths.get_lines(0, -1, 0))
+ eq(generate_mapargs('i', 'lhs', '<NOP>', {}),
+ get_mapargs('i', 'lhs'))
+ end)
+
+ it('treats an empty RHS in a mapping like a <Nop>', function()
+ meths.set_keymap('i', 'lhs', '', {})
+ command('normal ilhs')
+ eq({''}, curbufmeths.get_lines(0, -1, 0))
+ eq(generate_mapargs('i', 'lhs', '', {}),
+ get_mapargs('i', 'lhs'))
+ end)
+
+ it('can set and unset <M-">', function()
+ -- Taken from the legacy test: test_mapping.vim. Exposes a bug in which
+ -- replace_termcodes changes the length of the mapping's LHS, but
+ -- do_map continues to use the *old* length of LHS.
+ meths.set_keymap('i', '<M-">', 'foo', {})
+ meths.del_keymap('i', '<M-">')
+ eq({}, get_mapargs('i', '<M-">'))
+ end)
+
+ it('interprets control sequences in expr-quotes correctly when called '
+ ..'inside vim', function()
+ command([[call nvim_set_keymap('i', "\<space>", "\<tab>", {})]])
+ eq(generate_mapargs('i', '<Space>', '\t', {}),
+ get_mapargs('i', '<Space>'))
+ feed('i ')
+ eq({'\t'}, curbufmeths.get_lines(0, -1, 0))
+ end)
+
+ it('throws appropriate error messages when setting <unique> maps', function()
+ meths.set_keymap('l', 'lhs', 'rhs', {})
+ expect_err('E227: mapping already exists for lhs',
+ meths.set_keymap, 'l', 'lhs', 'rhs', {unique = true})
+ -- different mapmode, no error should be thrown
+ meths.set_keymap('t', 'lhs', 'rhs', {unique = true})
+ end)
+
+ it('can set <expr> mappings whose RHS change dynamically', function()
+ meths.command_output([[
+ function! FlipFlop() abort
+ if !exists('g:flip') | let g:flip = 0 | endif
+ let g:flip = !g:flip
+ return g:flip
+ endfunction
+ ]])
+ eq(1, meths.call_function('FlipFlop', {}))
+ eq(0, meths.call_function('FlipFlop', {}))
+ eq(1, meths.call_function('FlipFlop', {}))
+ eq(0, meths.call_function('FlipFlop', {}))
+
+ meths.set_keymap('i', 'lhs', 'FlipFlop()', {expr = true})
+ command('normal ilhs')
+ eq({'1'}, curbufmeths.get_lines(0, -1, 0))
+
+ command('normal! ggVGd')
+
+ command('normal ilhs')
+ eq({'0'}, curbufmeths.get_lines(0, -1, 0))
+ end)
+
+ it('can set mappings that do trigger other mappings', function()
+ meths.set_keymap('i', 'mhs', 'rhs', {})
+ meths.set_keymap('i', 'lhs', 'mhs', {})
+
+ command('normal imhs')
+ eq({'rhs'}, curbufmeths.get_lines(0, -1, 0))
+
+ command('normal! ggVGd')
+
+ command('normal ilhs')
+ eq({'rhs'}, curbufmeths.get_lines(0, -1, 0))
+ end)
+
+ it("can set noremap mappings that don't trigger other mappings", function()
+ meths.set_keymap('i', 'mhs', 'rhs', {})
+ meths.set_keymap('i', 'lhs', 'mhs', {noremap = true})
+
+ command('normal imhs')
+ eq({'rhs'}, curbufmeths.get_lines(0, -1, 0))
+
+ command('normal! ggVGd')
+
+ command('normal ilhs') -- shouldn't trigger mhs-to-rhs mapping
+ eq({'mhs'}, curbufmeths.get_lines(0, -1, 0))
+ end)
+
+ it("can set nowait mappings that fire without waiting", function()
+ meths.set_keymap('i', '123456', 'longer', {})
+ meths.set_keymap('i', '123', 'shorter', {nowait = true})
+
+ -- feed keys one at a time; if all keys arrive atomically, the longer
+ -- mapping will trigger
+ local keys = 'i123456'
+ for c in string.gmatch(keys, '.') do
+ feed(c)
+ sleep(5)
+ end
+ eq({'shorter456'}, curbufmeths.get_lines(0, -1, 0))
+ end)
+
+ -- Perform exhaustive tests of basic functionality
+ local mapmodes = {'n', 'v', 'x', 's', 'o', '!', 'i', 'l', 'c', 't', ''}
+ for _, mapmode in ipairs(mapmodes) do
+ it('can set/unset normal mappings in mapmode '..mapmode, function()
+ meths.set_keymap(mapmode, 'lhs', 'rhs', {})
+ eq(generate_mapargs(mapmode, 'lhs', 'rhs'),
+ get_mapargs(mapmode, 'lhs'))
+
+ -- some mapmodes (like 'o') will prevent other mapmodes (like '!') from
+ -- taking effect, so unmap after each mapping
+ meths.del_keymap(mapmode, 'lhs')
+ eq({}, get_mapargs(mapmode, 'lhs'))
+ end)
+ end
+
+ for _, mapmode in ipairs(mapmodes) do
+ it('can set/unset noremap mappings using mapmode '..mapmode, function()
+ meths.set_keymap(mapmode, 'lhs', 'rhs', {noremap = true})
+ eq(generate_mapargs(mapmode, 'lhs', 'rhs', {noremap = true}),
+ get_mapargs(mapmode, 'lhs'))
+
+ meths.del_keymap(mapmode, 'lhs')
+ eq({}, get_mapargs(mapmode, 'lhs'))
+ end)
+ end
+
+ -- Test map-arguments, using optnames from above
+ -- remove some map arguments that are harder to test, or were already tested
+ optnames = {'nowait', 'silent', 'expr', 'noremap'}
+ for _, mapmode in ipairs(mapmodes) do
+ local printable_mode = normalize_mapmode(mapmode)
+
+ -- Test with single mappings
+ for _, maparg in ipairs(optnames) do
+ it('can set/unset '..printable_mode..'-mappings with maparg: '..maparg,
+ function()
+ meths.set_keymap(mapmode, 'lhs', 'rhs', {[maparg] = true})
+ eq(generate_mapargs(mapmode, 'lhs', 'rhs', {[maparg] = true}),
+ get_mapargs(mapmode, 'lhs'))
+ meths.del_keymap(mapmode, 'lhs')
+ eq({}, get_mapargs(mapmode, 'lhs'))
+ end)
+ it ('can set/unset '..printable_mode..'-mode mappings with maparg '..
+ maparg..', whose value is false', function()
+ meths.set_keymap(mapmode, 'lhs', 'rhs', {[maparg] = false})
+ eq(generate_mapargs(mapmode, 'lhs', 'rhs'),
+ get_mapargs(mapmode, 'lhs'))
+ meths.del_keymap(mapmode, 'lhs')
+ eq({}, get_mapargs(mapmode, 'lhs'))
+ end)
+ end
+
+ -- Test with triplets of mappings, one of which is false
+ for i = 1, (#optnames - 2) do
+ local opt1, opt2, opt3 = optnames[i], optnames[i + 1], optnames[i + 2]
+ it('can set/unset '..printable_mode..'-mode mappings with mapargs '..
+ opt1..', '..opt2..', '..opt3, function()
+ local opts = {[opt1] = true, [opt2] = false, [opt3] = true}
+ meths.set_keymap(mapmode, 'lhs', 'rhs', opts)
+ eq(generate_mapargs(mapmode, 'lhs', 'rhs', opts),
+ get_mapargs(mapmode, 'lhs'))
+ meths.del_keymap(mapmode, 'lhs')
+ eq({}, get_mapargs(mapmode, 'lhs'))
+ end)
+ end
+ end
+end)
+
+describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function()
+ before_each(clear)
+
+ -- nvim_set_keymap is implemented as a wrapped call to nvim_buf_set_keymap,
+ -- so its tests also effectively test nvim_buf_set_keymap
+
+ -- here, we mainly test for buffer specificity and other special cases
+
+ -- switch to the given buffer, abandoning any changes in the current buffer
+ local function switch_to_buf(bufnr)
+ command(bufnr..'buffer!')
+ end
+
+ -- `set hidden`, then create two buffers and return their bufnr's
+ -- If start_from_first is truthy, the first buffer will be open when
+ -- the function returns; if falsy, the second buffer will be open.
+ local function make_two_buffers(start_from_first)
+ command('set hidden')
+
+ local first_buf = meths.call_function('bufnr', {'%'})
+ command('new')
+ local second_buf = meths.call_function('bufnr', {'%'})
+ neq(second_buf, first_buf) -- sanity check
+
+ if start_from_first then
+ switch_to_buf(first_buf)
+ end
+
+ return first_buf, second_buf
+ end
+
+ it('rejects negative bufnr values', function()
+ expect_err('Wrong type for argument 1, expecting Buffer',
+ bufmeths.set_keymap, -1, '', 'lhs', 'rhs', {})
+ end)
+
+ it('can set mappings active in the current buffer but not others', function()
+ local first, second = make_two_buffers(true)
+
+ bufmeths.set_keymap(0, '', 'lhs', 'irhs<Esc>', {})
+ command('normal lhs')
+ eq({'rhs'}, bufmeths.get_lines(0, 0, 1, 1))
+
+ -- mapping should have no effect in new buffer
+ switch_to_buf(second)
+ command('normal lhs')
+ eq({''}, bufmeths.get_lines(0, 0, 1, 1))
+
+ -- mapping should remain active in old buffer
+ switch_to_buf(first)
+ command('normal ^lhs')
+ eq({'rhsrhs'}, bufmeths.get_lines(0, 0, 1, 1))
+ end)
+
+ it('can set local mappings in buffer other than current', function()
+ local first = make_two_buffers(false)
+ bufmeths.set_keymap(first, '', 'lhs', 'irhs<Esc>', {})
+
+ -- shouldn't do anything
+ command('normal lhs')
+ eq({''}, bufmeths.get_lines(0, 0, 1, 1))
+
+ -- should take effect
+ switch_to_buf(first)
+ command('normal lhs')
+ eq({'rhs'}, bufmeths.get_lines(0, 0, 1, 1))
+ end)
+
+ it('can disable mappings made in another buffer, inside that buffer', function()
+ local first = make_two_buffers(false)
+ bufmeths.set_keymap(first, '', 'lhs', 'irhs<Esc>', {})
+ bufmeths.del_keymap(first, '', 'lhs')
+ switch_to_buf(first)
+
+ -- shouldn't do anything
+ command('normal lhs')
+ eq({''}, bufmeths.get_lines(0, 0, 1, 1))
+ end)
+
+ it("can't disable mappings given wrong buffer handle", function()
+ local first, second = make_two_buffers(false)
+ bufmeths.set_keymap(first, '', 'lhs', 'irhs<Esc>', {})
+ expect_err('E31: No such mapping',
+ bufmeths.del_keymap, second, '', 'lhs')
+
+ -- should still work
+ switch_to_buf(first)
+ command('normal lhs')
+ eq({'rhs'}, bufmeths.get_lines(0, 0, 1, 1))
+ end)
+end)
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index c508ca37db..2178abab53 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -1284,7 +1284,7 @@ describe('API', function()
end)
it('returns attached UIs', function()
local screen = Screen.new(20, 4)
- screen:attach()
+ screen:attach({override=true})
local expected = {
{
chan = 1,
@@ -1299,6 +1299,7 @@ describe('API', function()
ext_messages = false,
height = 4,
rgb = true,
+ override = true,
width = 20,
}
}
@@ -1308,6 +1309,7 @@ describe('API', function()
screen = Screen.new(44, 99)
screen:attach({ rgb = false })
expected[1].rgb = false
+ expected[1].override = false
expected[1].width = 44
expected[1].height = 99
eq(expected, nvim("list_uis"))
diff --git a/test/functional/autocmd/autocmd_spec.lua b/test/functional/autocmd/autocmd_spec.lua
index 814624d8e2..337c5442ef 100644
--- a/test/functional/autocmd/autocmd_spec.lua
+++ b/test/functional/autocmd/autocmd_spec.lua
@@ -1,4 +1,5 @@
local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
local dedent = helpers.dedent
local eq = helpers.eq
@@ -6,11 +7,13 @@ local eval = helpers.eval
local feed = helpers.feed
local clear = helpers.clear
local meths = helpers.meths
+local meth_pcall = helpers.meth_pcall
local funcs = helpers.funcs
local expect = helpers.expect
local command = helpers.command
local exc_exec = helpers.exc_exec
local curbufmeths = helpers.curbufmeths
+local source = helpers.source
describe('autocmd', function()
before_each(clear)
@@ -144,4 +147,117 @@ describe('autocmd', function()
--- Autocommands ---]]),
funcs.execute('autocmd Tabnew'))
end)
+
+ it('window works', function()
+ -- Nvim uses a special window to execute certain actions for an invisible buffer,
+ -- internally called autcmd_win and mentioned in the docs at :help E813
+ -- Do some safety checks for redrawing and api accesses to this window.
+
+ local screen = Screen.new(50, 10)
+ screen:attach()
+ screen:set_default_attr_ids({
+ [1] = {bold = true, foreground = Screen.colors.Blue1},
+ [2] = {background = Screen.colors.LightMagenta},
+ [3] = {background = Screen.colors.LightMagenta, bold = true, foreground = Screen.colors.Blue1},
+ })
+
+ source([[
+ function! Doit()
+ let g:winid = nvim_get_current_win()
+ redraw!
+ echo getchar()
+ " API functions work when aucmd_win is in scope
+ let g:had_value = has_key(w:, "testvar")
+ call nvim_win_set_var(g:winid, "testvar", 7)
+ let g:test = w:testvar
+ endfunction
+ set hidden
+ " add dummy text to not discard the buffer
+ call setline(1,"bb")
+ autocmd User <buffer> call Doit()
+ ]])
+ screen:expect([[
+ ^bb |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]])
+
+ feed(":enew | doautoall User<cr>")
+ screen:expect([[
+ {2:bb }|
+ {3:~ }|
+ {3:~ }|
+ {3:~ }|
+ {3:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ^:enew | doautoall User |
+ ]])
+
+ feed('<cr>')
+ screen:expect([[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 13 |
+ ]])
+ eq(7, eval('g:test'))
+
+ -- API calls are blocked when aucmd_win is not in scope
+ eq({false, 'Vim(call):Invalid window id'},
+ meth_pcall(command, "call nvim_set_current_win(g:winid)"))
+
+ -- second time aucmd_win is needed, a different code path is invoked
+ -- to reuse the same window, so check again
+ command("let g:test = v:null")
+ command("let g:had_value = v:null")
+ feed(":doautoall User<cr>")
+ screen:expect([[
+ {2:bb }|
+ {3:~ }|
+ {3:~ }|
+ {3:~ }|
+ {3:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ^:doautoall User |
+ ]])
+
+ feed('<cr>')
+ screen:expect([[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ 13 |
+ ]])
+ -- win vars in aucmd_win should have been reset
+ eq(0, eval('g:had_value'))
+ eq(7, eval('g:test'))
+
+ eq({false, 'Vim(call):Invalid window id'},
+ meth_pcall(command, "call nvim_set_current_win(g:winid)"))
+ end)
end)
diff --git a/test/functional/core/channels_spec.lua b/test/functional/core/channels_spec.lua
index 7b89172f92..0137092eb5 100644
--- a/test/functional/core/channels_spec.lua
+++ b/test/functional/core/channels_spec.lua
@@ -1,3 +1,5 @@
+local global_helpers = require('test.helpers')
+local uname = global_helpers.uname
local helpers = require('test.functional.helpers')(after_each)
local clear, eq, eval, next_msg, ok, source = helpers.clear, helpers.eq,
@@ -7,6 +9,7 @@ local sleep = helpers.sleep
local spawn, nvim_argv = helpers.spawn, helpers.nvim_argv
local set_session = helpers.set_session
local nvim_prog = helpers.nvim_prog
+local os_name = helpers.os_name
local retry = helpers.retry
local expect_twostreams = helpers.expect_twostreams
@@ -138,9 +141,8 @@ describe('channels', function()
command("call chansend(id, 'incomplet\004')")
- local is_freebsd = eval("system('uname') =~? 'FreeBSD'") == 1
- local bsdlike = is_freebsd or (helpers.os_name() == "osx")
- print("bsdlike:", bsdlike)
+ local is_freebsd = (string.lower(uname()) == 'freebsd')
+ local bsdlike = is_freebsd or (os_name() == "osx")
local extra = bsdlike and "^D\008\008" or ""
expect_twoline(id, "stdout",
"incomplet"..extra, "[1, ['incomplet'], 'stdin']", true)
diff --git a/test/functional/core/main_spec.lua b/test/functional/core/main_spec.lua
index a0981e9207..b793e531c9 100644
--- a/test/functional/core/main_spec.lua
+++ b/test/functional/core/main_spec.lua
@@ -7,19 +7,9 @@ local feed = helpers.feed
local eval = helpers.eval
local clear = helpers.clear
local funcs = helpers.funcs
-local nvim_prog = helpers.nvim_prog
+local nvim_prog_abs = helpers.nvim_prog_abs
local write_file = helpers.write_file
-local function nvim_prog_abs()
- -- system(['build/bin/nvim']) does not work for whatever reason. It needs to
- -- either be executable searched in $PATH or something starting with / or ./.
- if nvim_prog:match('[/\\]') then
- return funcs.fnamemodify(nvim_prog, ':p')
- else
- return nvim_prog
- end
-end
-
describe('Command-line option', function()
describe('-s', function()
local fname = 'Xtest-functional-core-main-s'
diff --git a/test/functional/spell/spellfile_spec.lua b/test/functional/core/spellfile_spec.lua
index afd2c1bce4..afd2c1bce4 100644
--- a/test/functional/spell/spellfile_spec.lua
+++ b/test/functional/core/spellfile_spec.lua
diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua
index 76e242b1e2..f77af836a6 100644
--- a/test/functional/core/startup_spec.lua
+++ b/test/functional/core/startup_spec.lua
@@ -13,9 +13,7 @@ local nvim_set = helpers.nvim_set
local read_file = helpers.read_file
local retry = helpers.retry
local rmdir = helpers.rmdir
-local set_session = helpers.set_session
local sleep = helpers.sleep
-local spawn = helpers.spawn
local iswin = helpers.iswin
local write_file = helpers.write_file
@@ -228,10 +226,6 @@ describe('sysinit', function()
local vimdir = 'Xvim'
local xhome = 'Xhome'
local pathsep = helpers.get_pathsep()
- local argv = {
- nvim_prog, '--headless', '--embed', '-i', 'NONE', '-n',
- '--cmd', 'set nomore undodir=. directory=. belloff='
- }
before_each(function()
rmdir(xdgdir)
@@ -260,19 +254,21 @@ describe('sysinit', function()
end)
it('prefers XDG_CONFIG_DIRS over VIM', function()
- set_session(spawn(argv, nil,
- { 'HOME='..xhome,
- 'XDG_CONFIG_DIRS='..xdgdir,
- 'VIM='..vimdir }))
+ clear{args={'--cmd', 'set nomore undodir=. directory=. belloff='},
+ args_rm={'-u', '--cmd'},
+ env={ HOME=xhome,
+ XDG_CONFIG_DIRS=xdgdir,
+ VIM=vimdir }}
eq('loaded 1 xdg 1 vim 0',
eval('printf("loaded %d xdg %d vim %d", g:loaded, get(g:, "xdg", 0), get(g:, "vim", 0))'))
end)
it('uses VIM if XDG_CONFIG_DIRS unset', function()
- set_session(spawn(argv, nil,
- { 'HOME='..xhome,
- 'XDG_CONFIG_DIRS=',
- 'VIM='..vimdir }))
+ clear{args={'--cmd', 'set nomore undodir=. directory=. belloff='},
+ args_rm={'-u', '--cmd'},
+ env={ HOME=xhome,
+ XDG_CONFIG_DIRS='',
+ VIM=vimdir }}
eq('loaded 1 xdg 0 vim 1',
eval('printf("loaded %d xdg %d vim %d", g:loaded, get(g:, "xdg", 0), get(g:, "vim", 0))'))
end)
diff --git a/test/functional/eval/api_functions_spec.lua b/test/functional/eval/api_functions_spec.lua
index 6f440c7d82..40d06b599f 100644
--- a/test/functional/eval/api_functions_spec.lua
+++ b/test/functional/eval/api_functions_spec.lua
@@ -6,7 +6,7 @@ local clear, curbufmeths = helpers.clear, helpers.curbufmeths
local exc_exec, expect, eval = helpers.exc_exec, helpers.expect, helpers.eval
local insert = helpers.insert
-describe('api functions', function()
+describe('eval-API', function()
before_each(clear)
it("work", function()
diff --git a/test/functional/ex_cmds/oldfiles_spec.lua b/test/functional/ex_cmds/oldfiles_spec.lua
index e2958c2924..802c3f68c6 100644
--- a/test/functional/ex_cmds/oldfiles_spec.lua
+++ b/test/functional/ex_cmds/oldfiles_spec.lua
@@ -1,18 +1,18 @@
local Screen = require('test.functional.ui.screen')
local helpers = require('test.functional.helpers')(after_each)
+local clear = helpers.clear
local buf, eq, feed_command = helpers.curbufmeths, helpers.eq, helpers.feed_command
-local feed, nvim_prog, wait = helpers.feed, helpers.nvim_prog, helpers.wait
-local ok, set_session, spawn = helpers.ok, helpers.set_session, helpers.spawn
+local feed, wait = helpers.feed, helpers.wait
+local ok = helpers.ok
local eval = helpers.eval
local shada_file = 'Xtest.shada'
local function _clear()
- set_session(spawn({nvim_prog, '--embed', '--headless', '-u', 'NONE',
- -- Need shada for these tests.
- '-i', shada_file,
- '--cmd', 'set noswapfile undodir=. directory=. viewdir=. backupdir=. belloff= noshowcmd noruler'}))
+ clear{args={'-i', shada_file, -- Need shada for these tests.
+ '--cmd', 'set noswapfile undodir=. directory=. viewdir=. backupdir=. belloff= noshowcmd noruler'},
+ args_rm={'-i', '--cmd'}}
end
describe(':oldfiles', function()
diff --git a/test/functional/ex_cmds/quickfix_commands_spec.lua b/test/functional/ex_cmds/quickfix_commands_spec.lua
index bf10f80401..3392a90270 100644
--- a/test/functional/ex_cmds/quickfix_commands_spec.lua
+++ b/test/functional/ex_cmds/quickfix_commands_spec.lua
@@ -37,9 +37,9 @@ for _, c in ipairs({'l', 'c'}) do
-- Second line of each entry (i.e. `nr=-1, …`) was obtained from actual
-- results. First line (i.e. `{lnum=…`) was obtained from legacy test.
local list = {
- {lnum=700, col=10, text='Line 700',
+ {lnum=700, col=10, text='Line 700', module='',
nr=-1, bufnr=2, valid=1, pattern='', vcol=0, ['type']=''},
- {lnum=800, col=15, text='Line 800',
+ {lnum=800, col=15, text='Line 800', module='',
nr=-1, bufnr=3, valid=1, pattern='', vcol=0, ['type']=''},
}
eq(list, getlist())
@@ -58,7 +58,7 @@ for _, c in ipairs({'l', 'c'}) do
]]):format(file))
command(('%s %s'):format(addfcmd, file))
list[#list + 1] = {
- lnum=900, col=30, text='Line 900',
+ lnum=900, col=30, text='Line 900', module='',
nr=-1, bufnr=5, valid=1, pattern='', vcol=0, ['type']='',
}
eq(list, getlist())
@@ -71,9 +71,9 @@ for _, c in ipairs({'l', 'c'}) do
command('enew!')
command(('%s %s'):format(getfcmd, file))
list = {
- {lnum=222, col=77, text='Line 222',
+ {lnum=222, col=77, text='Line 222', module='',
nr=-1, bufnr=2, valid=1, pattern='', vcol=0, ['type']=''},
- {lnum=333, col=88, text='Line 333',
+ {lnum=333, col=88, text='Line 333', module='',
nr=-1, bufnr=3, valid=1, pattern='', vcol=0, ['type']=''},
}
eq(list, getlist())
diff --git a/test/functional/ex_cmds/wviminfo_spec.lua b/test/functional/ex_cmds/wviminfo_spec.lua
index df0b9df5dd..7c00daf1d7 100644
--- a/test/functional/ex_cmds/wviminfo_spec.lua
+++ b/test/functional/ex_cmds/wviminfo_spec.lua
@@ -1,23 +1,21 @@
local helpers = require('test.functional.helpers')(after_each)
local lfs = require('lfs')
-local command, eq, neq, spawn, nvim_prog, set_session, write_file =
- helpers.command, helpers.eq, helpers.neq, helpers.spawn,
- helpers.nvim_prog, helpers.set_session, helpers.write_file
+local clear = helpers.clear
+local command, eq, neq, write_file =
+ helpers.command, helpers.eq, helpers.neq, helpers.write_file
local iswin = helpers.iswin
local read_file = helpers.read_file
describe(':wshada', function()
local shada_file = 'wshada_test'
- local session
before_each(function()
- -- Override the default session because we need 'swapfile' for these tests.
- session = spawn({nvim_prog, '-u', 'NONE', '-i', iswin() and 'nul' or '/dev/null', '--embed',
- '--cmd', 'set swapfile'})
- set_session(session)
+ clear{args={'-i', iswin() and 'nul' or '/dev/null',
+ -- Need 'swapfile' for these tests.
+ '--cmd', 'set swapfile undodir=. directory=. viewdir=. backupdir=. belloff= noshowcmd noruler'},
+ args_rm={'-n', '-i', '--cmd'}}
end)
after_each(function ()
- session:close()
os.remove(shada_file)
end)
diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua
index 7f9b5fe5fc..fd10a6afcd 100644
--- a/test/functional/helpers.lua
+++ b/test/functional/helpers.lua
@@ -38,7 +38,7 @@ local nvim_prog = (
local nvim_set = 'set shortmess+=I background=light noswapfile noautoindent'
..' laststatus=1 undodir=. directory=. viewdir=. backupdir=.'
..' belloff= noshowcmd noruler nomore'
-local nvim_argv = {nvim_prog, '-u', 'NONE', '-i', 'NONE', '-N',
+local nvim_argv = {nvim_prog, '-u', 'NONE', '-i', 'NONE',
'--cmd', nvim_set, '--embed'}
-- Directory containing nvim.
local nvim_dir = nvim_prog:gsub("[/\\][^/\\]+$", "")
@@ -238,6 +238,16 @@ local function stop()
session:stop()
end
+local function nvim_prog_abs()
+ -- system(['build/bin/nvim']) does not work for whatever reason. It must
+ -- be executable searched in $PATH or something starting with / or ./.
+ if nvim_prog:match('[/\\]') then
+ return request('nvim_call_function', 'fnamemodify', {nvim_prog, ':p'})
+ else
+ return nvim_prog
+ end
+end
+
-- Executes an ex-command. VimL errors manifest as client (lua) errors, but
-- v:errmsg will not be updated.
local function nvim_command(cmd)
@@ -312,6 +322,43 @@ local function merge_args(...)
return argv
end
+-- Removes Nvim startup args from `args` matching items in `args_rm`.
+--
+-- "-u", "-i", "--cmd" are treated specially: their "values" are also removed.
+-- Example:
+-- args={'--headless', '-u', 'NONE'}
+-- args_rm={'--cmd', '-u'}
+-- Result:
+-- {'--headless'}
+--
+-- All cases are removed.
+-- Example:
+-- args={'--cmd', 'foo', '-N', '--cmd', 'bar'}
+-- args_rm={'--cmd', '-u'}
+-- Result:
+-- {'-N'}
+local function remove_args(args, args_rm)
+ local new_args = {}
+ local skip_following = {'-u', '-i', '-c', '--cmd', '-s', '--listen'}
+ if not args_rm or #args_rm == 0 then
+ return {unpack(args)}
+ end
+ for _, v in ipairs(args_rm) do
+ assert(type(v) == 'string')
+ end
+ local last = ''
+ for _, arg in ipairs(args) do
+ if table_contains(skip_following, last) then
+ last = ''
+ elseif table_contains(args_rm, arg) then
+ last = arg
+ else
+ table.insert(new_args, arg)
+ end
+ end
+ return new_args
+end
+
local function spawn(argv, merge, env)
local child_stream = ChildProcessStream.spawn(
merge and merge_args(prepend_argv, argv) or argv,
@@ -350,20 +397,25 @@ local function retry(max, max_ms, fn)
end
-- Starts a new global Nvim session.
+--
-- Parameters are interpreted as startup args, OR a map with these keys:
--- args: Merged with the default `nvim_argv` set.
--- env : Defines the environment of the new session.
+-- args: List: Args appended to the default `nvim_argv` set.
+-- args_rm: List: Args removed from the default set. All cases are
+-- removed, e.g. args_rm={'--cmd'} removes all cases of "--cmd"
+-- (and its value) from the default set.
+-- env: Map: Defines the environment of the new session.
--
-- Example:
-- clear('-e')
--- clear({args={'-e'}, env={TERM=term}})
+-- clear{args={'-e'}, args_rm={'-i'}, env={TERM=term}}
local function clear(...)
local args = {unpack(nvim_argv)}
+ table.insert(args, '--headless')
local new_args
local env = nil
local opts = select(1, ...)
- local headless = true
if type(opts) == 'table' then
+ args = remove_args(args, opts.args_rm)
if opts.env then
local env_tbl = {}
for k, v in pairs(opts.env) do
@@ -374,7 +426,8 @@ local function clear(...)
for _, k in ipairs({
'HOME',
'ASAN_OPTIONS',
- 'LD_LIBRARY_PATH', 'PATH',
+ 'LD_LIBRARY_PATH',
+ 'PATH',
'NVIM_LOG_FILE',
'NVIM_RPLUGIN_MANIFEST',
}) do
@@ -388,15 +441,9 @@ local function clear(...)
end
end
new_args = opts.args or {}
- if opts.headless == false then
- headless = false
- end
else
new_args = {...}
end
- if headless then
- table.insert(args, '--headless')
- end
for _, arg in ipairs(new_args) do
table.insert(args, arg)
end
@@ -789,6 +836,7 @@ local module = {
nvim_async = nvim_async,
nvim_dir = nvim_dir,
nvim_prog = nvim_prog,
+ nvim_prog_abs = nvim_prog_abs,
nvim_set = nvim_set,
ok = ok,
os_name = os_name,
diff --git a/test/functional/legacy/074_global_var_in_viminfo_spec.lua b/test/functional/legacy/074_global_var_in_viminfo_spec.lua
index e17b463e30..f7f074c61a 100644
--- a/test/functional/legacy/074_global_var_in_viminfo_spec.lua
+++ b/test/functional/legacy/074_global_var_in_viminfo_spec.lua
@@ -2,9 +2,9 @@
local helpers = require('test.functional.helpers')(after_each)
local lfs = require('lfs')
-local clear, command, eq, neq, eval, wait, spawn =
+local clear, command, eq, neq, eval, wait =
helpers.clear, helpers.command, helpers.eq, helpers.neq, helpers.eval,
- helpers.wait, helpers.spawn
+ helpers.wait
describe('storing global variables in ShaDa files', function()
local tempname = 'Xtest-functional-legacy-074'
@@ -14,9 +14,7 @@ describe('storing global variables in ShaDa files', function()
end)
it('is working', function()
- local nvim2 = spawn({helpers.nvim_prog, '-u', 'NONE',
- '-i', 'Xviminfo', '--embed'})
- helpers.set_session(nvim2)
+ clear{args_rm={'-i'}, args={'-i', 'Xviminfo'}}
local test_dict = {foo = 1, bar = 0, longvarible = 1000}
local test_list = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
diff --git a/test/functional/legacy/assert_spec.lua b/test/functional/legacy/assert_spec.lua
index 10703465aa..8df2d89b70 100644
--- a/test/functional/legacy/assert_spec.lua
+++ b/test/functional/legacy/assert_spec.lua
@@ -18,6 +18,15 @@ describe('assert function:', function()
clear()
end)
+ describe('assert_beeps', function()
+ it('works', function()
+ call('assert_beeps', 'normal h')
+ expected_empty()
+ call('assert_beeps', 'normal 0')
+ expected_errors({'command did not beep: normal 0'})
+ end)
+ end)
+
-- assert_equal({expected}, {actual}, [, {msg}])
describe('assert_equal', function()
it('should not change v:errors when expected is equal to actual', function()
diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua
index f6f3f02f45..0fc2876d00 100644
--- a/test/functional/options/defaults_spec.lua
+++ b/test/functional/options/defaults_spec.lua
@@ -1,4 +1,5 @@
local helpers = require('test.functional.helpers')(after_each)
+local global_helpers = require('test.helpers')
local Screen = require('test.functional.ui.screen')
@@ -15,6 +16,7 @@ local neq = helpers.neq
local mkdir = helpers.mkdir
local rmdir = helpers.rmdir
local alter_slashes = helpers.alter_slashes
+local table_contains = global_helpers.table_contains
describe('startup defaults', function()
describe(':filetype', function()
@@ -160,20 +162,39 @@ describe('startup defaults', function()
end)
end)
- describe("'packpath'", function()
- it('defaults to &runtimepath', function()
- eq(meths.get_option('runtimepath'), meths.get_option('packpath'))
- end)
+ it("'shadafile' ('viminfofile')", function()
+ local env = {XDG_DATA_HOME='Xtest-userdata', XDG_CONFIG_HOME='Xtest-userconfig'}
+ clear{args={}, args_rm={'-i'}, env=env}
+ -- Default 'shadafile' is empty.
+ -- This means use the default location. :help shada-file-name
+ eq('', meths.get_option('shadafile'))
+ eq('', meths.get_option('viminfofile'))
+ -- Check that shada data (such as v:oldfiles) is saved/restored.
+ command('edit Xtest-foo')
+ command('write')
+ local f = eval('fnamemodify(@%,":p")')
+ assert(string.len(f) > 3)
+ command('qall')
+ clear{args={}, args_rm={'-i'}, env=env}
+ eq({ f }, eval('v:oldfiles'))
+ os.remove('Xtest-foo')
+ rmdir('Xtest-userdata')
+ end)
- it('does not follow modifications to runtimepath', function()
- meths.command('set runtimepath+=foo')
- neq(meths.get_option('runtimepath'), meths.get_option('packpath'))
- meths.command('set packpath+=foo')
- eq(meths.get_option('runtimepath'), meths.get_option('packpath'))
- end)
+ it("'packpath'", function()
+ clear()
+ -- Defaults to &runtimepath.
+ eq(meths.get_option('runtimepath'), meths.get_option('packpath'))
+
+ -- Does not follow modifications to runtimepath.
+ meths.command('set runtimepath+=foo')
+ neq(meths.get_option('runtimepath'), meths.get_option('packpath'))
+ meths.command('set packpath+=foo')
+ eq(meths.get_option('runtimepath'), meths.get_option('packpath'))
end)
it('v:progpath is set to the absolute path', function()
+ clear()
eq(eval("fnamemodify(v:progpath, ':p')"), eval('v:progpath'))
end)
@@ -231,6 +252,23 @@ describe('XDG-based defaults', function()
-- Need separate describe() blocks to not run clear() twice.
-- Do not put before_each() here for the same reasons.
+ it("&runtimepath data-dir matches stdpath('data') #9910", function()
+ clear()
+ local rtp = eval('split(&runtimepath, ",")')
+ local rv = {}
+ local expected = (iswin()
+ and { [[\nvim-data\site]], [[\nvim-data\site\after]], }
+ or { '/nvim/site', '/nvim/site/after', })
+
+ for _,v in ipairs(rtp) do
+ local m = string.match(v, [=[[/\]nvim[^/\]*[/\]site.*$]=])
+ if m and not table_contains(rv, m) then
+ table.insert(rv, m)
+ end
+ end
+ eq(expected, rv)
+ end)
+
describe('with empty/broken environment', function()
it('sets correct defaults', function()
clear({env={
diff --git a/test/functional/clipboard/clipboard_provider_spec.lua b/test/functional/provider/clipboard_spec.lua
index 2bbc678a02..b2d75db745 100644
--- a/test/functional/clipboard/clipboard_provider_spec.lua
+++ b/test/functional/provider/clipboard_spec.lua
@@ -236,7 +236,7 @@ describe('clipboard', function()
end)
end)
-describe('clipboard', function()
+describe('clipboard (with fake clipboard.vim)', function()
local function reset(...)
clear('--cmd', 'let &rtp = "test/functional/fixtures,".&rtp', ...)
end
@@ -664,4 +664,20 @@ describe('clipboard', function()
the a sourcetarget]])
end)
+ it('setreg("*") with clipboard=unnamed #5646', function()
+ source([=[
+ function! Paste_without_yank(direction) range
+ let [reg_save,regtype_save] = [getreg('*'), getregtype('*')]
+ normal! gvp
+ call setreg('*', reg_save, regtype_save)
+ endfunction
+ xnoremap p :call Paste_without_yank('p')<CR>
+ set clipboard=unnamed
+ ]=])
+ insert('some words')
+ feed('gg0yiw')
+ feed('wviwp')
+ expect('some some')
+ eq('some', eval('getreg("*")'))
+ end)
end)
diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua
index ca5737db7f..f3849709e3 100644
--- a/test/functional/terminal/ex_terminal_spec.lua
+++ b/test/functional/terminal/ex_terminal_spec.lua
@@ -106,6 +106,15 @@ describe(':terminal', function()
command('stopinsert')
eq({ blocking=false, mode='n' }, nvim('get_mode'))
end)
+
+ it(':stopinsert in normal mode doesn\'t break insert mode #9889', function()
+ command(':terminal')
+ eq({ blocking=false, mode='n' }, nvim('get_mode'))
+ command(':stopinsert')
+ eq({ blocking=false, mode='n' }, nvim('get_mode'))
+ feed('a')
+ eq({ blocking=false, mode='t' }, nvim('get_mode'))
+ end)
end)
describe(':terminal (with fake shell)', function()
diff --git a/test/functional/terminal/highlight_spec.lua b/test/functional/terminal/highlight_spec.lua
index 9579e0ea0b..48fedd5927 100644
--- a/test/functional/terminal/highlight_spec.lua
+++ b/test/functional/terminal/highlight_spec.lua
@@ -3,9 +3,12 @@ local Screen = require('test.functional.ui.screen')
local thelpers = require('test.functional.terminal.helpers')
local feed, clear, nvim = helpers.feed, helpers.clear, helpers.nvim
local nvim_dir, command = helpers.nvim_dir, helpers.command
+local nvim_prog_abs = helpers.nvim_prog_abs
local eq, eval = helpers.eq, helpers.eval
+local funcs = helpers.funcs
+local nvim_set = helpers.nvim_set
-describe(':terminal window highlighting', function()
+describe(':terminal highlight', function()
local screen
before_each(function()
@@ -112,8 +115,51 @@ describe(':terminal window highlighting', function()
end)
end)
+it(':terminal highlight has lower precedence than editor #9964', function()
+ clear()
+ local screen = Screen.new(30, 4)
+ screen:set_default_attr_ids({
+ -- "Normal" highlight emitted by the child nvim process.
+ N_child = {foreground = tonumber('0x4040ff'), background = tonumber('0xffff40')},
+ -- "Search" highlight emitted by the child nvim process.
+ S_child = {background = tonumber('0xffff40'), italic = true, foreground = tonumber('0x4040ff')},
+ -- "Search" highlight in the parent nvim process.
+ S = {background = Screen.colors.Green, italic = true, foreground = Screen.colors.Red},
+ -- "Question" highlight in the parent nvim process.
+ Q = {background = tonumber('0xffff40'), bold = true, foreground = Screen.colors.SeaGreen4},
+ })
+ screen:attach({rgb=true})
+ -- Child nvim process in :terminal (with cterm colors).
+ funcs.termopen({
+ nvim_prog_abs(), '-n', '-u', 'NORC', '-i', 'NONE', '--cmd', nvim_set,
+ '+hi Normal ctermfg=Blue ctermbg=Yellow',
+ '+norm! ichild nvim',
+ '+norm! oline 2',
+ })
+ screen:expect([[
+ {N_child:^child nvim }|
+ {N_child:line 2 }|
+ {N_child: }|
+ |
+ ]])
+ command('hi Search gui=italic guifg=Red guibg=Green cterm=italic ctermfg=Red ctermbg=Green')
+ feed('/nvim<cr>')
+ screen:expect([[
+ {N_child:child }{S:^nvim}{N_child: }|
+ {N_child:line 2 }|
+ {N_child: }|
+ /nvim |
+ ]])
+ command('syntax keyword Question line')
+ screen:expect([[
+ {N_child:child }{S:^nvim}{N_child: }|
+ {Q:line}{N_child: 2 }|
+ {N_child: }|
+ /nvim |
+ ]])
+end)
-describe('terminal window highlighting with custom palette', function()
+describe(':terminal highlight with custom palette', function()
local screen
before_each(function()
diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua
index 9d0eb5e40e..469d088c57 100644
--- a/test/functional/terminal/tui_spec.lua
+++ b/test/functional/terminal/tui_spec.lua
@@ -255,14 +255,14 @@ describe('TUI', function()
]])
end)
- it('shows up in nvim_list_uis', function()
+ it('is included in nvim_list_uis()', function()
feed_data(':echo map(nvim_list_uis(), {k,v -> sort(items(filter(v, {k,v -> k[:3] !=# "ext_" })))})\013')
screen:expect([=[
|
{4:~ }|
{5: }|
- [[['height', 6], ['rgb', v:false], ['width', 50]]]|
- |
+ [[['height', 6], ['override', v:false], ['rgb', v:|
+ false], ['width', 50]]] |
{10:Press ENTER or type command to continue}{1: } |
{3:-- TERMINAL --} |
]=])
diff --git a/test/functional/ui/cmdline_highlight_spec.lua b/test/functional/ui/cmdline_highlight_spec.lua
index 121e71c3c2..45808b3b1b 100644
--- a/test/functional/ui/cmdline_highlight_spec.lua
+++ b/test/functional/ui/cmdline_highlight_spec.lua
@@ -983,7 +983,7 @@ describe('Expressions coloring support', function()
{EOB:~ }|
{EOB:~ }|
{EOB:~ }|
- ={SQ:"}{SB:<c0>}{SQ:"}{E:<c0>"}{SB:foo}{E:"}^" |
+ ={SQ:"}{SB:<c0>}{SQ:"}{E:<c0>"}{SB:foo}{E:"}^ |
]])
end)
end)
diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua
index 16be846647..5d563895d6 100644
--- a/test/functional/ui/cmdline_spec.lua
+++ b/test/functional/ui/cmdline_spec.lua
@@ -116,6 +116,31 @@ local function test_cmdline(linegrid)
}}}
end)
+ it('from normal mode when : is mapped', function()
+ command('nnoremap ; :')
+
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {3:n }|
+ |
+ ]]}
+
+ feed(';')
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {3:c }|
+ |
+ ]], cmdline={{
+ firstc = ":",
+ content = {{""}},
+ pos = 0,
+ }}}
+ end)
+
it('but not with scrolled messages', function()
screen:try_resize(35,10)
feed(':echoerr doesnotexist<cr>')
diff --git a/test/functional/ui/embed_spec.lua b/test/functional/ui/embed_spec.lua
index 10dbc68672..9196c8af40 100644
--- a/test/functional/ui/embed_spec.lua
+++ b/test/functional/ui/embed_spec.lua
@@ -8,7 +8,7 @@ local clear = helpers.clear
local function test_embed(ext_linegrid)
local screen
local function startup(...)
- clear{headless=false, args={...}}
+ clear{args_rm={'--headless'}, args={...}}
-- attach immediately after startup, for early UI
screen = Screen.new(60, 8)
diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua
index efa776762b..d49d2f0316 100644
--- a/test/functional/ui/messages_spec.lua
+++ b/test/functional/ui/messages_spec.lua
@@ -22,8 +22,129 @@ describe('ui/ext_messages', function()
[6] = {bold = true, reverse = true},
})
end)
+ after_each(function()
+ os.remove('Xtest')
+ end)
+
+ it('msg_show kind=confirm,confirm_sub,emsg,wmsg', function()
+ feed('iline 1\nline 2<esc>')
+
+ -- kind=confirm
+ feed(':echo confirm("test")<cr>')
+ screen:expect{grid=[[
+ line 1 |
+ line ^2 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ]], messages={ {
+ content = {{"\ntest\n[O]k: ", 4}},
+ kind = 'confirm',
+ }}}
+ feed('<cr><cr>')
+ screen:expect{grid=[[
+ line 1 |
+ line ^2 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ]], messages={ {
+ content = { { "\ntest\n[O]k: ", 4 } },
+ kind = "confirm"
+ }, {
+ content = { { "1" } },
+ kind = "echo"
+ }, {
+ content = { { "Press ENTER or type command to continue", 4 } },
+ kind = "return_prompt"
+ } }}
+ feed('<cr><cr>')
+
+ -- kind=confirm_sub
+ feed(':%s/i/X/gc<cr>')
+ screen:expect{grid=[[
+ l{7:i}ne 1 |
+ l{8:i}ne ^2 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ]], attr_ids={
+ [1] = {bold = true, foreground = Screen.colors.Blue1},
+ [2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
+ [3] = {bold = true},
+ [4] = {bold = true, foreground = Screen.colors.SeaGreen4},
+ [5] = {foreground = Screen.colors.Blue1},
+ [6] = {bold = true, reverse = true},
+ [7] = {reverse = true},
+ [8] = {background = Screen.colors.Yellow},
+ }, messages={ {
+ content = { { "replace with X (y/n/a/q/l/^E/^Y)?", 4 } },
+ kind = "confirm_sub"
+ } }}
+ feed('nq')
+
+ -- kind=wmsg (editing readonly file)
+ command('write Xtest')
+ command('set readonly nohls')
+ feed('G$x')
+ screen:expect{grid=[[
+ line 1 |
+ {IGNORE}|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ]], attr_ids={
+ [1] = {bold = true, foreground = Screen.colors.Blue1},
+ [7] = {foreground = Screen.colors.Red},
+ }, messages={ {
+ content = { { "W10: Warning: Changing a readonly file", 7 } },
+ kind = "wmsg"
+ }
+ }}
+
+ -- kind=wmsg ('wrapscan' after search reaches EOF)
+ feed('uG$/i<cr>')
+ screen:expect{grid=[[
+ l^ine 1 |
+ line 2 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ]], attr_ids={
+ [1] = {bold = true, foreground = Screen.colors.Blue1},
+ [2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
+ [3] = {bold = true},
+ [4] = {bold = true, foreground = Screen.colors.SeaGreen4},
+ [5] = {foreground = Screen.colors.Blue1},
+ [6] = {bold = true, reverse = true},
+ [7] = {foreground = Screen.colors.Red},
+ }, messages={ {
+ content = { { "search hit BOTTOM, continuing at TOP", 7 } },
+ kind = "wmsg"
+ } }}
+
+ -- kind=emsg after :throw
+ feed(':throw "foo"<cr>')
+ screen:expect{grid=[[
+ l^ine 1 |
+ line 2 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ]], messages={ {
+ content = { { "Error detected while processing :", 2 } },
+ kind = "emsg"
+ }, {
+ content = { { "E605: Exception not caught: foo", 2 } },
+ kind = ""
+ }, {
+ content = { { "Press ENTER or type command to continue", 4 } },
+ kind = "return_prompt"
+ } }
+ }
+ end)
- it('supports :echoerr', function()
+ it(':echoerr', function()
feed(':echoerr "raa"<cr>')
screen:expect{grid=[[
^ |
@@ -142,7 +263,7 @@ describe('ui/ext_messages', function()
}}
end)
- it('supports showmode', function()
+ it('&showmode', function()
command('imap <f2> <cmd>echomsg "stuff"<cr>')
feed('i')
screen:expect{grid=[[
@@ -230,7 +351,7 @@ describe('ui/ext_messages', function()
}}
end)
- it('supports showmode with recording message', function()
+ it('&showmode with macro-recording message', function()
feed('qq')
screen:expect{grid=[[
^ |
@@ -268,7 +389,7 @@ describe('ui/ext_messages', function()
]])
end)
- it('shows recording message with noshowmode', function()
+ it('shows macro-recording message with &noshowmode', function()
command("set noshowmode")
feed('qq')
-- also check mode to avoid immediate success
@@ -308,7 +429,7 @@ describe('ui/ext_messages', function()
]], mode="normal"}
end)
- it('supports showcmd and ruler', function()
+ it('supports &showcmd and &ruler', function()
command('set showcmd ruler')
screen:expect{grid=[[
^ |
@@ -529,7 +650,7 @@ describe('ui/ext_messages', function()
local screen
before_each(function()
- clear{headless=false, args={"--cmd", "set shortmess-=I"}}
+ clear{args_rm={'--headless'}, args={"--cmd", "set shortmess-=I"}}
screen = Screen.new(80, 24)
screen:attach({rgb=true, ext_messages=true, ext_popupmenu=true})
screen:set_default_attr_ids({
diff --git a/test/functional/ui/multigrid_spec.lua b/test/functional/ui/multigrid_spec.lua
index c54d608ec4..c5a23e4661 100644
--- a/test/functional/ui/multigrid_spec.lua
+++ b/test/functional/ui/multigrid_spec.lua
@@ -11,7 +11,7 @@ describe('ext_multigrid', function()
local screen
before_each(function()
- clear{headless=false, args={'--cmd', 'set laststatus=2'}}
+ clear{args_rm={'--headless'}, args={'--cmd', 'set laststatus=2'}}
screen = Screen.new(53,14)
screen:attach({ext_multigrid=true})
screen:set_default_attr_ids({
diff --git a/test/functional/ui/options_spec.lua b/test/functional/ui/options_spec.lua
index ed630259be..7ce21f5d76 100644
--- a/test/functional/ui/options_spec.lua
+++ b/test/functional/ui/options_spec.lua
@@ -115,7 +115,8 @@ describe('ui receives option updates', function()
end)
local function startup_test(headless)
- local expected = reset(nil,{headless=headless,args={'--cmd', 'set guifont=Comic\\ Sans\\ 12'}})
+ local expected = reset(nil, {args_rm=(headless and {} or {'--headless'}),
+ args={'--cmd', 'set guifont=Comic\\ Sans\\ 12'}})
expected.guifont = "Comic Sans 12"
screen:expect(function()
eq(expected, screen.options)
diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua
index 983a2f3d40..53b6642207 100644
--- a/test/functional/ui/screen.lua
+++ b/test/functional/ui/screen.lua
@@ -74,6 +74,7 @@
local global_helpers = require('test.helpers')
local deepcopy = global_helpers.deepcopy
local shallowcopy = global_helpers.shallowcopy
+local concat_tables = global_helpers.concat_tables
local helpers = require('test.functional.helpers')(nil)
local request, run_session = helpers.request, helpers.run_session
local eq = helpers.eq
@@ -413,26 +414,23 @@ screen:redraw_debug() to show all intermediate screen states. ]])
end
end
- -- Extension features. The default expectations should cover the case of
+ -- UI extensions. 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). Some extensions require
+ -- (e.g. no external cmdline visible). Some extensions require
-- preprocessing to represent highlights in a reproducible way.
local extstate = self:_extstate_repr(attr_state)
-
- -- convert assertion errors into invalid screen state descriptions
- local status, res = pcall(function()
- for _, k in ipairs(ext_keys) do
- -- Empty states is considered the default and need not be mentioned
- if not (expected[k] == nil and isempty(extstate[k])) then
- eq(expected[k], extstate[k], k)
+ if expected['mode'] ~= nil then
+ extstate['mode'] = self.mode
+ end
+ -- Convert assertion errors into invalid screen state descriptions.
+ for _, k in ipairs(concat_tables(ext_keys, {'mode'})) do
+ -- Empty states are considered the default and need not be mentioned.
+ if (not (expected[k] == nil and isempty(extstate[k]))) then
+ local status, res = pcall(eq, expected[k], extstate[k], k)
+ if not status then
+ return (tostring(res)..'\nHint: full state of "'..k..'":\n '..inspect(extstate[k]))
end
end
- if expected.mode ~= nil then
- eq(expected.mode, self.mode, "mode")
- end
- end)
- if not status then
- return tostring(res)
end
end, expected)
end
diff --git a/test/functional/viml/errorlist_spec.lua b/test/functional/viml/errorlist_spec.lua
index 6c5a63e6b1..c5390cbd12 100644
--- a/test/functional/viml/errorlist_spec.lua
+++ b/test/functional/viml/errorlist_spec.lua
@@ -26,7 +26,7 @@ describe('setqflist()', function()
it('sets w:quickfix_title', function()
setqflist({''}, 'r', 'foo')
command('copen')
- eq(':foo', get_cur_win_var('quickfix_title'))
+ eq('foo', get_cur_win_var('quickfix_title'))
setqflist({''}, 'r', {['title'] = 'qf_title'})
eq('qf_title', get_cur_win_var('quickfix_title'))
end)
@@ -34,7 +34,7 @@ describe('setqflist()', function()
it('allows string {what} for backwards compatibility', function()
setqflist({}, 'r', '5')
command('copen')
- eq(':5', get_cur_win_var('quickfix_title'))
+ eq('5', get_cur_win_var('quickfix_title'))
end)
it('requires a dict for {what}', function()
@@ -64,8 +64,8 @@ describe('setloclist()', function()
setloclist(1, {}, 'r', 'foo')
setloclist(2, {}, 'r', 'bar')
command('lopen')
- eq(':bar', get_cur_win_var('quickfix_title'))
+ eq('bar', get_cur_win_var('quickfix_title'))
command('lclose | wincmd w | lopen')
- eq(':foo', get_cur_win_var('quickfix_title'))
+ eq('foo', get_cur_win_var('quickfix_title'))
end)
end)