aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.clang-format1
-rw-r--r--.github/workflows/api-docs-check.yml2
-rw-r--r--.github/workflows/ci.yml3
-rw-r--r--.github/workflows/coverity-scan.yml2
-rw-r--r--.github/workflows/vim-patches.yml2
-rw-r--r--README.md2
-rwxr-xr-xci/install.sh8
-rw-r--r--cmake/RunTests.cmake1
-rw-r--r--runtime/autoload/health.vim5
-rw-r--r--runtime/doc/api.txt16
-rw-r--r--runtime/doc/builtin.txt66
-rw-r--r--runtime/doc/change.txt2
-rw-r--r--runtime/doc/dev_style.txt52
-rw-r--r--runtime/doc/lsp.txt210
-rw-r--r--runtime/doc/lua.txt127
-rw-r--r--runtime/doc/map.txt131
-rw-r--r--runtime/doc/options.txt17
-rw-r--r--runtime/doc/pi_health.txt24
-rw-r--r--runtime/doc/starting.txt2
-rw-r--r--runtime/doc/treesitter.txt66
-rw-r--r--runtime/doc/undo.txt6
-rw-r--r--runtime/doc/usr_05.txt2
-rw-r--r--runtime/doc/usr_41.txt2
-rw-r--r--runtime/doc/vim_diff.txt2
-rw-r--r--runtime/filetype.vim38
-rw-r--r--runtime/ftplugin/confini.vim10
-rw-r--r--runtime/ftplugin/fortran.vim8
-rw-r--r--runtime/indent/confini.vim10
-rw-r--r--runtime/lua/health.lua29
-rw-r--r--runtime/lua/vim/_editor.lua2
-rw-r--r--runtime/lua/vim/_meta.lua45
-rw-r--r--runtime/lua/vim/diagnostic.lua11
-rw-r--r--runtime/lua/vim/filetype.lua506
-rw-r--r--runtime/lua/vim/filetype/detect.lua184
-rw-r--r--runtime/lua/vim/fs.lua208
-rw-r--r--runtime/lua/vim/health.lua49
-rw-r--r--runtime/lua/vim/highlight.lua7
-rw-r--r--runtime/lua/vim/lsp.lua78
-rw-r--r--runtime/lua/vim/lsp/buf.lua19
-rw-r--r--runtime/lua/vim/lsp/handlers.lua2
-rw-r--r--runtime/lua/vim/lsp/util.lua2
-rw-r--r--runtime/lua/vim/treesitter/languagetree.lua52
-rw-r--r--runtime/lua/vim/treesitter/query.lua27
-rw-r--r--runtime/optwin.vim2
-rw-r--r--runtime/plugin/rplugin.vim4
-rw-r--r--runtime/syntax/confini.vim12
-rw-r--r--runtime/syntax/i3config.vim8
-rwxr-xr-xscripts/gen_vimdoc.py3
-rwxr-xr-xscripts/vim-patch.sh4
-rw-r--r--scripts/windows.ti4
-rw-r--r--src/mpack/mpack_core.c10
-rw-r--r--src/mpack/mpack_core.h2
-rw-r--r--src/nvim/README.md11
-rw-r--r--src/nvim/api/keysets.lua1
-rw-r--r--src/nvim/api/private/dispatch.c33
-rw-r--r--src/nvim/api/private/dispatch.h1
-rw-r--r--src/nvim/api/private/helpers.c17
-rw-r--r--src/nvim/api/vim.c13
-rw-r--r--src/nvim/api/vimscript.c16
-rw-r--r--src/nvim/buffer_updates.c9
-rw-r--r--src/nvim/change.c4
-rw-r--r--src/nvim/channel.c2
-rw-r--r--src/nvim/eval.c7
-rw-r--r--src/nvim/ex_cmds.c257
-rw-r--r--src/nvim/ex_cmds.lua55
-rw-r--r--src/nvim/ex_cmds2.c8
-rw-r--r--src/nvim/ex_cmds_defs.h11
-rw-r--r--src/nvim/ex_docmd.c113
-rw-r--r--src/nvim/ex_docmd.h2
-rw-r--r--src/nvim/ex_getln.c385
-rw-r--r--src/nvim/fold.c5
-rw-r--r--src/nvim/generators/gen_api_dispatch.lua39
-rwxr-xr-xsrc/nvim/generators/gen_api_ui_events.lua32
-rw-r--r--src/nvim/generators/gen_ex_cmds.lua13
-rw-r--r--src/nvim/getchar.c25
-rw-r--r--src/nvim/getchar.h4
-rw-r--r--src/nvim/globals.h8
-rw-r--r--src/nvim/highlight_defs.h2
-rw-r--r--src/nvim/highlight_group.c4
-rw-r--r--src/nvim/log.c123
-rw-r--r--src/nvim/log.h47
-rw-r--r--src/nvim/lua/executor.c88
-rw-r--r--src/nvim/lua/treesitter.c2
-rw-r--r--src/nvim/main.c3
-rw-r--r--src/nvim/map.c4
-rw-r--r--src/nvim/map.h3
-rw-r--r--src/nvim/match.c2
-rw-r--r--src/nvim/move.c4
-rw-r--r--src/nvim/msgpack_rpc/channel.c224
-rw-r--r--src/nvim/msgpack_rpc/channel_defs.h4
-rw-r--r--src/nvim/msgpack_rpc/unpacker.c310
-rw-r--r--src/nvim/msgpack_rpc/unpacker.h40
-rw-r--r--src/nvim/normal.c2
-rw-r--r--src/nvim/po/cleanup.vim3
-rw-r--r--src/nvim/rbuffer.c17
-rw-r--r--src/nvim/regexp.c94
-rw-r--r--src/nvim/regexp_defs.h5
-rw-r--r--src/nvim/screen.c20
-rw-r--r--src/nvim/state.c6
-rw-r--r--src/nvim/testdir/test_excmd.vim8
-rw-r--r--src/nvim/testdir/test_filetype.vim3
-rw-r--r--src/nvim/testdir/test_filetype_lua.vim1
-rw-r--r--src/nvim/testdir/test_functions.vim39
-rw-r--r--src/nvim/testdir/test_mapping.vim30
-rw-r--r--src/nvim/tui/terminfo_defs.h8
-rw-r--r--src/nvim/ui.c6
-rw-r--r--src/nvim/ui_client.c27
-rw-r--r--src/nvim/ui_client.h5
-rw-r--r--src/nvim/vim.h1
-rw-r--r--src/nvim/window.c3
-rw-r--r--test/functional/api/command_spec.lua157
-rw-r--r--test/functional/api/highlight_spec.lua6
-rw-r--r--test/functional/api/keymap_spec.lua17
-rw-r--r--test/functional/api/window_spec.lua2
-rw-r--r--test/functional/core/job_spec.lua1
-rw-r--r--test/functional/core/startup_spec.lua1
-rw-r--r--test/functional/editor/ctrl_c_spec.lua (renamed from test/functional/ex_cmds/ctrl_c_spec.lua)19
-rw-r--r--test/functional/fixtures/fake-lsp-server.lua42
-rw-r--r--test/functional/fixtures/lua/test_plug/health/init.lua9
-rw-r--r--test/functional/fixtures/lua/test_plug/submodule/health.lua9
-rw-r--r--test/functional/fixtures/lua/test_plug/submodule_failed/health.lua7
-rw-r--r--test/functional/legacy/display_spec.lua2
-rw-r--r--test/functional/legacy/fixeol_spec.lua11
-rw-r--r--test/functional/legacy/listchars_spec.lua2
-rw-r--r--test/functional/legacy/mapping_spec.lua17
-rw-r--r--test/functional/legacy/statusline_spec.lua2
-rw-r--r--test/functional/lua/diagnostic_spec.lua13
-rw-r--r--test/functional/lua/fs_spec.lua101
-rw-r--r--test/functional/options/cursorbind_spec.lua2
-rw-r--r--test/functional/plugin/health_spec.lua10
-rw-r--r--test/functional/plugin/lsp_spec.lua64
-rw-r--r--test/functional/ui/cursor_spec.lua2
-rw-r--r--test/functional/ui/decorations_spec.lua45
-rw-r--r--test/functional/ui/float_spec.lua12
-rw-r--r--test/functional/ui/inccommand_spec.lua138
-rw-r--r--test/functional/ui/inccommand_user_spec.lua356
-rw-r--r--test/functional/ui/winbar_spec.lua19
-rw-r--r--third-party/CMakeLists.txt4
138 files changed, 4099 insertions, 1267 deletions
diff --git a/.clang-format b/.clang-format
index afb0df2e25..a31d753217 100644
--- a/.clang-format
+++ b/.clang-format
@@ -48,6 +48,7 @@ IncludeCategories:
AlignConsecutiveMacros: AcrossEmptyLines
IndentPPDirectives: AfterHash
SpaceBeforeParens: ControlStatementsExceptControlMacros
+PPIndentWidth: 1
ForEachMacros:
- FOR_ALL_AUEVENTS
- FOR_ALL_AUPATS_IN_EVENT
diff --git a/.github/workflows/api-docs-check.yml b/.github/workflows/api-docs-check.yml
index f76c035de4..0a57df7c33 100644
--- a/.github/workflows/api-docs-check.yml
+++ b/.github/workflows/api-docs-check.yml
@@ -1,7 +1,6 @@
name: Missing API docs
on:
pull_request:
- types: [opened, synchronize, reopened, ready_for_review]
branches-ignore:
- 'marvim/api-doc-update**'
paths:
@@ -11,7 +10,6 @@ on:
jobs:
call-regen-api-docs:
- if: github.event.pull_request.draft == false
permissions:
contents: write
pull-requests: write
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4657053167..16464426ee 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -120,7 +120,8 @@ jobs:
- if: "!cancelled()"
name: uncrustify
run: |
- ${{ env.CACHE_UNCRUSTIFY }} -c ./src/uncrustify.cfg -q --check $(find ./src/nvim -name "*.[ch]") >/dev/null
+ ${{ env.CACHE_UNCRUSTIFY }} -c ./src/uncrustify.cfg -q --replace --no-backup $(find ./src/nvim -name "*.[ch]")
+ git diff --color --exit-code
- if: "!cancelled()"
name: lualint
diff --git a/.github/workflows/coverity-scan.yml b/.github/workflows/coverity-scan.yml
index 064da54456..ce7822b5c1 100644
--- a/.github/workflows/coverity-scan.yml
+++ b/.github/workflows/coverity-scan.yml
@@ -6,7 +6,7 @@ on:
jobs:
scan:
- runs-on: ubuntu-18.04
+ runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
diff --git a/.github/workflows/vim-patches.yml b/.github/workflows/vim-patches.yml
index 45e6b81aed..df8c8116b5 100644
--- a/.github/workflows/vim-patches.yml
+++ b/.github/workflows/vim-patches.yml
@@ -5,7 +5,7 @@ on:
jobs:
update-vim-patches:
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
diff --git a/README.md b/README.md
index b5a469b32d..718b369012 100644
--- a/README.md
+++ b/README.md
@@ -63,7 +63,7 @@ After installing the dependencies, run the following command.
To install to a non-default location:
- make CMAKE_INSTALL_PREFIX=/full/path/
+ make CMAKE_BUILD_TYPE=RelWithDebInfo CMAKE_INSTALL_PREFIX=/full/path/
make install
CMake hints for inspecting the build:
diff --git a/ci/install.sh b/ci/install.sh
index 894e090de2..5925cc7b02 100755
--- a/ci/install.sh
+++ b/ci/install.sh
@@ -5,7 +5,7 @@ set -o pipefail
# Use default CC to avoid compilation problems when installing Python modules.
echo "Install neovim module for Python."
-CC=cc python -m pip -q install --user --upgrade pynvim
+CC=cc python3 -m pip -q install --user --upgrade pynvim
echo "Install neovim RubyGem."
gem install --no-document --bindir "$HOME/.local/bin" --user-install --pre neovim
@@ -14,5 +14,7 @@ echo "Install neovim npm package"
npm install -g neovim
npm link neovim
-sudo cpanm -n Neovim::Ext || cat "$HOME/.cpanm/build.log"
-perl -W -e 'use Neovim::Ext; print $Neovim::Ext::VERSION'
+if [[ $CI_OS_NAME != osx ]]; then
+ sudo cpanm -n Neovim::Ext || cat "$HOME/.cpanm/build.log"
+ perl -W -e 'use Neovim::Ext; print $Neovim::Ext::VERSION'
+fi
diff --git a/cmake/RunTests.cmake b/cmake/RunTests.cmake
index 3adbcbbfc5..bf5049c5f0 100644
--- a/cmake/RunTests.cmake
+++ b/cmake/RunTests.cmake
@@ -11,6 +11,7 @@ set(ENV{NVIM_RPLUGIN_MANIFEST} ${BUILD_DIR}/Xtest_rplugin_manifest)
set(ENV{XDG_CONFIG_HOME} ${BUILD_DIR}/Xtest_xdg/config)
set(ENV{XDG_DATA_HOME} ${BUILD_DIR}/Xtest_xdg/share)
unset(ENV{XDG_DATA_DIRS})
+unset(ENV{NVIM}) # Clear $NVIM in case tests are running from Nvim. #11009
if(NOT DEFINED ENV{NVIM_LOG_FILE})
set(ENV{NVIM_LOG_FILE} ${BUILD_DIR}/.nvimlog)
diff --git a/runtime/autoload/health.vim b/runtime/autoload/health.vim
index 1292e4344e..a693868381 100644
--- a/runtime/autoload/health.vim
+++ b/runtime/autoload/health.vim
@@ -171,6 +171,11 @@ function! s:get_healthcheck(plugin_names) abort
for v in values(healthchecks)
let output[v[0]] = v[1:]
endfor
+ try
+ " vim.health is not a healthcheck, skip it
+ call remove(output, 'vim')
+ catch
+ endtry
return output
endfunction
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index 3ab7c77056..2c75acbe1f 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -638,6 +638,12 @@ nvim__stats() *nvim__stats()*
Return: ~
Map of various internal stats.
+nvim__unpack({str}) *nvim__unpack()*
+ TODO: Documentation
+
+ Attributes: ~
+ |api-fast|
+
nvim_call_atomic({calls}) *nvim_call_atomic()*
Calls many API methods atomically.
@@ -743,6 +749,9 @@ nvim_create_user_command({name}, {command}, {*opts})
specified |<reg>|
• mods: (string) Command modifiers, if any
|<mods>|
+ • smods: (table) Command modifiers in a
+ structured format. Has the same structure as
+ the "mods" key of |nvim_parse_cmd()|.
{opts} Optional command attributes. See
|command-attributes| for more details. To use
boolean attributes (such as |:command-bang| or
@@ -757,6 +766,8 @@ nvim_create_user_command({name}, {command}, {*opts})
when a Lua function is used for {command}.
• force: (boolean, default true) Override any
previous definition.
+ • preview: (function) Preview callback for
+ 'inccommand' |:command-preview|
nvim_del_current_line() *nvim_del_current_line()*
Deletes the current line.
@@ -1614,8 +1625,7 @@ nvim_set_hl({ns_id}, {name}, {*val}) *nvim_set_hl()*
• reverse: boolean
• nocombine: boolean
• link: name of another highlight group to link
- to, see |:hi-link|. Additionally, the following
- keys are recognized:
+ to, see |:hi-link|.
• default: Don't override existing definition
|:hi-default|
• ctermfg: Sets foreground of cterm color
@@ -1703,7 +1713,7 @@ nvim_set_vvar({name}, {value}) *nvim_set_vvar()*
nvim_strwidth({text}) *nvim_strwidth()*
Calculates the number of display cells occupied by `text`.
- <Tab> counts as one cell.
+ Control characters including <Tab> count as one cell.
Parameters: ~
{text} Some text
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index cb0b3d3aa6..f63525242f 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -582,6 +582,8 @@ acos({expr}) *acos()*
|Float| in the range of [0, pi].
{expr} must evaluate to a |Float| or a |Number| in the range
[-1, 1].
+ Returns NaN if {expr} is outside the range [-1, 1]. Returns
+ 0.0 if {expr} is not a |Float| or a |Number|.
Examples: >
:echo acos(0)
< 1.570796 >
@@ -600,6 +602,7 @@ add({object}, {expr}) *add()*
item. Use |extend()| to concatenate |Lists|.
When {object} is a |Blob| then {expr} must be a number.
Use |insert()| to add an item at another position.
+ Returns 1 if {object} is not a |List| or a |Blob|.
Can also be used as a |method|: >
mylist->add(val1)->add(val2)
@@ -697,11 +700,17 @@ argv([{nr} [, {winid}]])
The {winid} argument specifies the window ID, see |argc()|.
For the Vim command line arguments see |v:argv|.
+ Returns an empty string if {nr}th argument is not present in
+ the argument list. Returns an empty List if the {winid}
+ argument is invalid.
+
asin({expr}) *asin()*
Return the arc sine of {expr} measured in radians, as a |Float|
in the range of [-pi/2, pi/2].
{expr} must evaluate to a |Float| or a |Number| in the range
[-1, 1].
+ Returns NaN if {expr} is outside the range [-1, 1]. Returns
+ 0.0 if {expr} is not a |Float| or a |Number|.
Examples: >
:echo asin(0.8)
< 0.927295 >
@@ -719,6 +728,7 @@ atan({expr}) *atan()*
Return the principal value of the arc tangent of {expr}, in
the range [-pi/2, +pi/2] radians, as a |Float|.
{expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
Examples: >
:echo atan(100)
< 1.560797 >
@@ -732,6 +742,8 @@ atan2({expr1}, {expr2}) *atan2()*
Return the arc tangent of {expr1} / {expr2}, measured in
radians, as a |Float| in the range [-pi, pi].
{expr1} and {expr2} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr1} or {expr2} is not a |Float| or a
+ |Number|.
Examples: >
:echo atan2(-1, 1)
< -0.785398 >
@@ -777,7 +789,8 @@ bufadd({name}) *bufadd()*
let bufnr = bufadd('someName')
call bufload(bufnr)
call setbufline(bufnr, 1, ['some', 'text'])
-< Can also be used as a |method|: >
+< Returns 0 on error.
+ Can also be used as a |method|: >
let bufnr = 'somename'->bufadd()
bufexists({buf}) *bufexists()*
@@ -919,6 +932,8 @@ byte2line({byte}) *byte2line()*
one.
Also see |line2byte()|, |go| and |:goto|.
+ Returns -1 if the {byte} value is invalid.
+
Can also be used as a |method|: >
GetOffset()->byte2line()
@@ -985,6 +1000,8 @@ ceil({expr}) *ceil()*
echo ceil(4.0)
< 4.0
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+
Can also be used as a |method|: >
Compute()->ceil()
@@ -995,6 +1012,7 @@ changenr() *changenr()*
When a change was made it is the number of that change. After
redo it is the number of the redone change. After undo it is
one less than the number of the undone change.
+ Returns 0 if the undo list is empty.
chanclose({id} [, {stream}]) *chanclose()*
Close a channel or a specific stream associated with it.
@@ -1038,6 +1056,8 @@ char2nr({string} [, {utf8}]) *char2nr()*
A combining character is a separate character.
|nr2char()| does the opposite.
+ Returns 0 if {string} is not a |String|.
+
Can also be used as a |method|: >
GetChar()->char2nr()
<
@@ -1146,7 +1166,7 @@ col({expr}) The result is a Number, which is the byte index of the column
col("$") length of cursor line plus one
col("'t") column of mark t
col("'" .. markname) column of mark markname
-< The first column is 1. 0 is returned for an error.
+< The first column is 1. Returns 0 if {expr} is invalid.
For an uppercase mark the column may actually be in another
buffer.
For the cursor position, when 'virtualedit' is active, the
@@ -1264,6 +1284,8 @@ complete_info([{what}]) *complete_info()*
|pum_getpos()|. It's also available in |v:event| during the
|CompleteChanged| event.
+ Returns an empty |Dictionary| on error.
+
Examples: >
" Get all items
call complete_info()
@@ -1346,6 +1368,7 @@ copy({expr}) Make a copy of {expr}. For Numbers and Strings this isn't
cos({expr}) *cos()*
Return the cosine of {expr}, measured in radians, as a |Float|.
{expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
Examples: >
:echo cos(100)
< 0.862319 >
@@ -1359,6 +1382,7 @@ cosh({expr}) *cosh()*
Return the hyperbolic cosine of {expr} as a |Float| in the range
[1, inf].
{expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
Examples: >
:echo cosh(0.5)
< 1.127626 >
@@ -1914,6 +1938,7 @@ exp({expr}) *exp()*
Return the exponential of {expr} as a |Float| in the range
[0, inf].
{expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
Examples: >
:echo exp(2)
< 7.389056 >
@@ -1929,6 +1954,9 @@ debugbreak({pid}) *debugbreak()*
processes is undefined. See |terminal-debugger|.
{Sends a SIGINT to a process {pid} other than MS-Windows}
+ Returns |TRUE| if successfully interrupted the program.
+ Otherwise returns |FALSE|.
+
Can also be used as a |method|: >
GetPid()->debugbreak()
@@ -2065,7 +2093,7 @@ extend({expr1}, {expr2} [, {expr3}]) *extend()*
{expr2} remains unchanged.
When {expr1} is locked and {expr2} is not empty the operation
fails.
- Returns {expr1}.
+ Returns {expr1}. Returns 0 on error.
Can also be used as a |method|: >
mylist->extend(otherlist)
@@ -2210,6 +2238,8 @@ finddir({name} [, {path} [, {count}]]) *finddir()*
{name} in {path} instead of the first one.
When {count} is negative return all the matches in a |List|.
+ Returns an empty string if the directory is not found.
+
This is quite similar to the ex-command `:find`.
Can also be used as a |method|: >
@@ -2252,6 +2282,7 @@ float2nr({expr}) *float2nr()*
Convert {expr} to a Number by omitting the part after the
decimal point.
{expr} must evaluate to a |Float| or a Number.
+ Returns 0 if {expr} is not a |Float| or a |Number|.
When the value of {expr} is out of range for a |Number| the
result is truncated to 0x7fffffff or -0x7fffffff (or when
64-bit Number support is enabled, 0x7fffffffffffffff or
@@ -2276,6 +2307,7 @@ floor({expr}) *floor()*
Return the largest integral value less than or equal to
{expr} as a |Float| (round down).
{expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
Examples: >
echo floor(1.856)
< 1.0 >
@@ -2295,6 +2327,8 @@ fmod({expr1}, {expr2}) *fmod()*
the magnitude of {expr2}. If {expr2} is zero, the value
returned is zero. The value returned is a |Float|.
{expr1} and {expr2} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr1} or {expr2} is not a |Float| or a
+ |Number|.
Examples: >
:echo fmod(12.33, 1.22)
< 0.13 >
@@ -2313,6 +2347,7 @@ fnameescape({string}) *fnameescape()*
appears in a filename, it depends on the value of 'isfname'.
A leading '+' and '>' is also escaped (special after |:edit|
and |:write|). And a "-" by itself (special after |:cd|).
+ Returns an empty string on error.
Example: >
:let fname = '+some str%nge|name'
:exe "edit " .. fnameescape(fname)
@@ -2330,7 +2365,8 @@ fnamemodify({fname}, {mods}) *fnamemodify()*
:echo fnamemodify("main.c", ":p:h")
< results in: >
/home/mool/vim/vim/src
-< If {mods} is empty then {fname} is returned.
+< If {mods} is empty or an unsupported modifier is used then
+ {fname} is returned.
Note: Environment variables don't work in {fname}, use
|expand()| first then.
@@ -2387,6 +2423,7 @@ foldtext() Returns a String, to be displayed for a closed fold. This is
When used to draw the actual foldtext, the rest of the line
will be filled with the fold char from the 'fillchars'
setting.
+ Returns an empty string when there is no fold.
foldtextresult({lnum}) *foldtextresult()*
Returns the text that is displayed for the closed fold at line
@@ -2426,6 +2463,7 @@ funcref({name} [, {arglist}] [, {dict}])
been loaded (to avoid mistakenly loading the autoload script
when only intending to use the function name, use |function()|
instead). {name} cannot be a builtin function.
+ Returns 0 on error.
Can also be used as a |method|: >
GetFuncname()->funcref([arg])
@@ -2480,6 +2518,8 @@ function({name} [, {arglist}] [, {dict}])
< Invokes the function as with: >
call context.Callback('one', 500)
<
+ Returns 0 on error.
+
Can also be used as a |method|: >
GetFuncname()->function([arg])
@@ -2527,6 +2567,7 @@ get({func}, {what})
"func" The function
"dict" The dictionary
"args" The list with arguments
+ Returns zero on error.
*getbufinfo()*
getbufinfo([{buf}])
@@ -2747,7 +2788,7 @@ getcharmod() *getcharmod()*
128 command (Macintosh only)
Only the modifiers that have not been included in the
character itself are obtained. Thus Shift-a results in "A"
- without a modifier.
+ without a modifier. Returns 0 if no modifiers are used.
*getcharpos()*
getcharpos({expr})
@@ -3067,7 +3108,8 @@ getjumplist([{winnr} [, {tabnr}]]) *getjumplist()*
With {winnr} only use this window in the current tab page.
{winnr} can also be a |window-ID|.
With {winnr} and {tabnr} use the window in the specified tab
- page.
+ page. If {winnr} or {tabnr} is invalid, an empty list is
+ returned.
The returned list contains two entries: a list with the jump
locations and the last used jump position number in the list.
@@ -3148,7 +3190,8 @@ getmarklist([{buf}]) *getmarklist()*
If the optional {buf} argument is specified, returns the
local marks defined in buffer {buf}. For the use of {buf},
- see |bufname()|.
+ see |bufname()|. If {buf} is invalid, an empty list is
+ returned.
Each item in the returned List is a |Dict| with the following:
mark name of the mark prefixed by "'"
@@ -3170,7 +3213,8 @@ getmatches([{win}]) *getmatches()*
as |setmatches()| can restore a list of matches saved by
|getmatches()|.
If {win} is specified, use the window with this number or
- window ID instead of the current window.
+ window ID instead of the current window. If {win} is invalid,
+ an empty list is returned.
Example: >
:echo getmatches()
< [{'group': 'MyGroup1', 'pattern': 'TODO',
@@ -3242,6 +3286,7 @@ getpos({expr}) Get the position for String {expr}. For possible values of
use |getcharpos()|.
The column number can be very large, e.g. 2147483647, in which
case it means "after the end of the line".
+ If {expr} is invalid, returns a list with all zeros.
This can be used to save and restore the position of a mark: >
let save_a_mark = getpos("'a")
...
@@ -7415,8 +7460,8 @@ sqrt({expr}) *sqrt()*
:echo sqrt(100)
< 10.0 >
:echo sqrt(-4.01)
-< nan
- "nan" may be different, it depends on system libraries.
+< str2float('nan')
+ NaN may be different, it depends on system libraries.
Can also be used as a |method|: >
Compute()->sqrt()
@@ -8797,6 +8842,7 @@ winnr([{arg}]) The result is a Number, which is the number of the current
current window (where |CTRL-W_l| goes to).
The number can be used with |CTRL-W_w| and ":wincmd w"
|:wincmd|.
+ When {arg} is invalid an error is given and zero is returned.
Also see |tabpagewinnr()| and |win_getid()|.
Examples: >
let window_count = winnr('$')
diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt
index ccc1f59b93..571fbaec95 100644
--- a/runtime/doc/change.txt
+++ b/runtime/doc/change.txt
@@ -1207,7 +1207,7 @@ to their previous contents. When the '>' flag is present in 'cpoptions' then
a line break is inserted before the appended text.
5. Read-only registers ":, ". and "%
-These are '%', '#', ':' and '.'. You can use them only with the "p", "P",
+These are '%', ':' and '.'. You can use them only with the "p", "P",
and ":put" commands and with CTRL-R.
*quote_.* *quote.* *E29*
". Contains the last inserted text (the same as what is inserted
diff --git a/runtime/doc/dev_style.txt b/runtime/doc/dev_style.txt
index a2ea1204b5..77253e7831 100644
--- a/runtime/doc/dev_style.txt
+++ b/runtime/doc/dev_style.txt
@@ -529,12 +529,12 @@ it is for and how it should be used. >
};
If the field comments are short, you can also put them next to the field. But
-be consistent within one struct. >
+be consistent within one struct, and follow the necessary doxygen style. >
struct wininfo_S {
- WinInfo *wi_next; /// Next entry or NULL for last entry.
- WinInfo *wi_prev; /// Previous entry or NULL for first entry.
- Win *wi_win; /// Pointer to window that did the wi_fpos.
+ WinInfo *wi_next; ///< Next entry or NULL for last entry.
+ WinInfo *wi_prev; ///< Previous entry or NULL for first entry.
+ Win *wi_win; ///< Pointer to window that did the wi_fpos.
...
};
@@ -789,11 +789,6 @@ example, `"\uFEFF"`, is the Unicode zero-width no-break space character, which
would be invisible if included in the source as straight UTF-8.
-Spaces vs. Tabs ~
-
-Use only spaces, and indent 2 spaces at a time. Do not use tabs in your code.
-
-
Function Declarations and Definitions ~
Return type on the same line as function name, parameters on the same line if
@@ -903,7 +898,7 @@ no name, assume a zero-length name. >
Conditionals ~
-Don't use spaces inside parentheses. Always use curly braces. >
+Don't use spaces inside parentheses. >
if (condition) { // no spaces inside parentheses
... // 2 space indent.
@@ -923,8 +918,8 @@ warn you if any values are not handled). If the default case should never
execute, simply `assert`: >
switch (var) {
- case 0: // 2 space indent
- ... // 4 space indent
+ case 0:
+ ...
break;
case 1:
...
@@ -951,15 +946,6 @@ Note that:
- There are no spaces around the period or arrow when accessing a member.
- Pointer operators have no space after the * or &.
-When declaring a pointer variable or argument, place the asterisk adjacent to
-the variable name: >
-
- char *c;
-
- char * c; // BAD: spaces on both sides of *
- char* c; // BAD
-
-
Boolean Expressions ~
When you have a boolean expression that is longer than the standard line
@@ -991,15 +977,10 @@ expr;`. >
Horizontal Whitespace ~
-Use of horizontal whitespace depends on location. Never put trailing
-whitespace at the end of a line.
+Use of horizontal whitespace depends on location.
General ~
>
- if (x) { // Open braces should always have a space before them.
- ...
- }
- int i = 0; // Semicolons usually have no space before them.
int x[] = { 0 }; // Spaces inside braces for braced-init-list.
<
@@ -1019,21 +1000,6 @@ whitespace at the end of a line.
};
<
- Loops and Conditionals ~
->
- if (b) { // Space after the keyword in condition.
- } else { // Spaces around else.
- }
- while (test) {} // There is usually no space inside parentheses.
- for (; i < 5; i++) { // For loops always have a space after the
- ... // semicolon and no a space before the
- ... // semicolon.
- }
- switch (i) {
- case 1: // No space before colon in a switch case.
- ...
- case 2: break; // Space after a colon if there's code after it.
-<
Operators ~
>
@@ -1043,8 +1009,6 @@ whitespace at the end of a line.
x++; // arguments.
if (x && !y)
...
- v = w*x + y/z; // Use spaces to indicate operator precedence.
- v = w * (x + z); // Parentheses should have no spaces inside them.
i = (int)d; // No spaces after a cast operator.
<
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index 41f083687d..4f07752cb6 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -24,88 +24,82 @@ QUICKSTART *lsp-quickstart*
Nvim provides an LSP client, but the servers are provided by third parties.
Follow these steps to get LSP features:
- 1. Install the nvim-lspconfig plugin. It provides common configuration for
- various servers so you can get started quickly.
- https://github.com/neovim/nvim-lspconfig
- 2. Install a language server. A list of language servers can be found here:
+ 1. Install language servers using your package manager or by
+ following the upstream installation instruction.
+
+ A list of language servers is available at:
+
https://microsoft.github.io/language-server-protocol/implementors/servers/
- See individual server documentation for installation instructions.
- 3. Add `lua require('lspconfig').xx.setup{…}` to your init.vim, where "xx" is
- the name of the relevant config. See the nvim-lspconfig README for details.
- NOTE: Make sure to restart nvim after installing and configuring.
- 4. Check that an LSP client has attached to the current buffer: >
- :lua print(vim.inspect(vim.lsp.buf_get_clients()))
+ 2. Configure the LSP client per language server.
+ A minimal example:
+>
+ vim.lsp.start({
+ name = 'my-server-name',
+ cmd = {'name-of-language-server-executable'},
+ root_dir = vim.fs.dirname(vim.fs.find({'setup.py', 'pyproject.toml'}, { upward = true })[1]),
+ })
+<
+ See |vim.lsp.start| for details.
+
+ 3. Configure keymaps and autocmds to utilize LSP features.
+ See |lsp-config|.
<
*lsp-config*
-Inline diagnostics are enabled automatically, e.g. syntax errors will be
-annotated in the buffer. But you probably also want to use other features
-like go-to-definition, hover, etc.
-
-While Nvim does not provide an "auto-completion" framework by default, it is
-still possible to get completions from the LSP server. To incorporate these
-completions, it is recommended to use |vim.lsp.omnifunc|, which is an 'omnifunc'
-handler. When 'omnifunc' is set to `v:lua.vim.lsp.omnifunc`, |i_CTRL-X_CTRL-O|
-will provide completions from the language server.
-
-Example config (in init.vim): >
-
- lua << EOF
- local custom_lsp_attach = function(client)
- -- See `:help nvim_buf_set_keymap()` for more information
- vim.api.nvim_buf_set_keymap(0, 'n', 'K', '<cmd>lua vim.lsp.buf.hover()<CR>', {noremap = true})
- vim.api.nvim_buf_set_keymap(0, 'n', '<c-]>', '<cmd>lua vim.lsp.buf.definition()<CR>', {noremap = true})
- -- ... and other keymappings for LSP
-
- -- Use LSP as the handler for omnifunc.
- -- See `:help omnifunc` and `:help ins-completion` for more information.
- vim.api.nvim_buf_set_option(0, 'omnifunc', 'v:lua.vim.lsp.omnifunc')
-
- -- Use LSP as the handler for formatexpr.
- -- See `:help formatexpr` for more information.
- vim.api.nvim_buf_set_option(0, 'formatexpr', 'v:lua.vim.lsp.formatexpr()')
-
- -- For plugins with an `on_attach` callback, call them here. For example:
- -- require('completion').on_attach()
- end
-
- -- An example of configuring for `sumneko_lua`,
- -- a language server for Lua.
-
- -- set the path to the sumneko installation
- local system_name = "Linux" -- (Linux, macOS, or Windows)
- local sumneko_root_path = '/path/to/lua-language-server'
- local sumneko_binary = sumneko_root_path.."/bin/"..system_name.."/lua-language-server"
-
- require('lspconfig').sumneko_lua.setup({
- cmd = {sumneko_binary, "-E", sumneko_root_path .. "/main.lua"};
- -- An example of settings for an LSP server.
- -- For more options, see nvim-lspconfig
- settings = {
- Lua = {
- runtime = {
- -- Tell the language server which version of Lua you're using (most likely LuaJIT in the case of Neovim)
- version = 'LuaJIT',
- -- Setup your lua path
- path = vim.split(package.path, ';'),
- },
- diagnostics = {
- -- Get the language server to recognize the `vim` global
- globals = {'vim'},
- },
- workspace = {
- -- Make the server aware of Neovim runtime files
- library = {
- [vim.fn.expand('$VIMRUNTIME/lua')] = true,
- [vim.fn.expand('$VIMRUNTIME/lua/vim/lsp')] = true,
- },
- },
- }
- },
-
- on_attach = custom_lsp_attach
+
+Starting a LSP client will automatically report diagnostics via
+|vim.diagnostic|. Read |vim.diagnostic.config| to learn how to customize the
+display.
+
+To get completion from the LSP server you can enable the |vim.lsp.omnifunc|:
+>
+ vim.bo.omnifunc = 'v:lua.vim.lsp.omnifunc'
+<
+To trigger completion, use |i_CTRL-X_CTRL-O|
+
+To get features like go-to-definition you can enable the |vim.lsp.tagfunc|
+which changes commands like |:tjump| to utilize the language server and also
+enables keymaps like |CTLR-]|, |CTRL-W_]|, |CTRL-W_}| and many more.
+
+To use other LSP features like hover, rename, etc. you can setup some
+additional keymaps. It's recommended to setup them in a |LspAttach| autocmd to
+ensure they're only active if there is a LSP client running. An example:
+>
+ vim.api.nvim_create_autocmd('LspAttach', {
+ callback = function(args)
+ vim.keymap.set('n', 'K', vim.lsp.buf.hover, { buffer = args.buf })
+ end,
+ })
+
+<
+The most used functions are:
+
+- |vim.lsp.buf.hover()|
+- |vim.lsp.buf.format()|
+- |vim.lsp.buf.references()|
+- |vim.lsp.buf.implementation()|
+- |vim.lsp.buf.code_action()|
+
+
+Not all language servers provide the same capabilities. To ensure you only set
+keymaps if the language server supports a feature, you can guard the keymap
+calls behind capability checks:
+>
+ vim.api.nvim_create_autocmd('LspAttach', {
+ callback = function(args)
+ local client = vim.lsp.get_client_by_id(args.data.client_id)
+ if client.server_capabilities.hoverProvider then
+ vim.keymap.set('n', 'K', vim.lsp.buf.hover, { buffer = args.buf })
+ end
+ end,
})
- EOF
+<
+
+To learn what capabilities are available you can run the following command in
+a buffer with a started LSP client:
+
+>
+ :lua =vim.lsp.get_active_clients()[1].server_capabilties
<
Full list of features provided by default can be found in |lsp-buf|.
@@ -800,6 +794,66 @@ set_log_level({level}) *vim.lsp.set_log_level()*
See also: ~
|vim.lsp.log_levels|
+start({config}, {opts}) *vim.lsp.start()*
+ Create a new LSP client and start a language server or reuses
+ an already running client if one is found matching `name` and
+ `root_dir`. Attaches the current buffer to the client.
+
+ Example:
+>
+
+ vim.lsp.start({
+ name = 'my-server-name',
+ cmd = {'name-of-language-server-executable'},
+ root_dir = vim.fs.dirname(vim.fs.find({'pyproject.toml', 'setup.py'}, { upward = true })[1]),
+ })
+<
+
+ See |lsp.start_client| for all available options. The most
+ important are:
+
+ `name` is an arbitrary name for the LSP client. It should be
+ unique per language server.
+
+ `cmd` the command as list - used to start the language server. The
+ command must be present in the `$PATH` environment variable or an absolute path to the executable.
+ Shell constructs like `~` are NOT expanded.
+
+ `root_dir` path to the project root. By default this is used
+ to decide if an existing client should be re-used. The example
+ above uses |vim.fs.find| and |vim.fs.dirname| to detect the
+ root by traversing the file system upwards starting from the
+ current directory until either a `pyproject.toml` or
+ `setup.py` file is found.
+
+ `workspace_folders` a list of { uri:string, name: string }
+ tables. The project root folders used by the language server.
+ If `nil` the property is derived from the `root_dir` for
+ convenience.
+
+ Language servers use this information to discover metadata
+ like the dependencies of your project and they tend to index
+ the contents within the project folder.
+
+ To ensure a language server is only started for languages it
+ can handle, make sure to call |vim.lsp.start| within a
+ |FileType| autocmd. Either use |:au|, |nvim_create_autocmd()|
+ or put the call in a `ftplugin/<filetype_name>.lua` (See
+ |ftplugin-name|)
+
+ Parameters: ~
+ {config} (table) Same configuration as documented in
+ |lsp.start_client()|
+ {opts} nil|table Optional keyword arguments:
+ • reuse_client (fun(client: client, config:
+ table): boolean) Predicate used to decide if a
+ client should be re-used. Used on all running
+ clients. The default implementation re-uses a
+ client if name and root_dir matches.
+
+ Return: ~
+ (number) client_id
+
start_client({config}) *vim.lsp.start_client()*
Starts and initializes a client with the given configuration.
@@ -933,7 +987,7 @@ start_client({config}) *vim.lsp.start_client()*
default true): Allow using
incremental sync for buffer edits
• debounce_text_changes (number,
- default nil): Debounce didChange
+ default 150): Debounce didChange
notifications to the server by the
given number in milliseconds. No
debounce occurs if nil
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index dd1843ade3..bdd2e6ff8e 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -1092,7 +1092,7 @@ vim.env *vim.env*
*lua-vim-optlocal*
*lua-vim-setlocal*
-In Vimscript, there is an way to set options |set-option|. In Lua, the
+In Vimscript, there is a way to set options |set-option|. In Lua, the
corresponding method is `vim.opt`.
`vim.opt` provides several conveniences for setting and controlling options
@@ -2147,4 +2147,129 @@ set({mode}, {lhs}, {rhs}, {opts}) *vim.keymap.set()*
See also: ~
|nvim_set_keymap()|
+
+==============================================================================
+Lua module: fs *lua-fs*
+
+basename({file}) *vim.fs.basename()*
+ Return the basename of the given file or directory
+
+ Parameters: ~
+ {file} (string) File or directory
+
+ Return: ~
+ (string) Basename of {file}
+
+dir({path}) *vim.fs.dir()*
+ Return an iterator over the files and directories located in
+ {path}
+
+ Parameters: ~
+ {path} (string) An absolute or relative path to the
+ directory to iterate over. The path is first
+ normalized |vim.fs.normalize()|.
+
+ Return: ~
+ Iterator over files and directories in {path}. Each
+ iteration yields two values: name and type. Each "name" is
+ the basename of the file or directory relative to {path}.
+ Type is one of "file" or "directory".
+
+dirname({file}) *vim.fs.dirname()*
+ Return the parent directory of the given file or directory
+
+ Parameters: ~
+ {file} (string) File or directory
+
+ Return: ~
+ (string) Parent directory of {file}
+
+find({names}, {opts}) *vim.fs.find()*
+ Find files or directories in the given path.
+
+ Finds any files or directories given in {names} starting from
+ {path}. If {upward} is "true" then the search traverses upward
+ through parent directories; otherwise, the search traverses
+ downward. Note that downward searches are recursive and may
+ search through many directories! If {stop} is non-nil, then
+ the search stops when the directory given in {stop} is
+ reached. The search terminates when {limit} (default 1)
+ matches are found. The search can be narrowed to find only
+ files or or only directories by specifying {type} to be "file"
+ or "directory", respectively.
+
+ Parameters: ~
+ {names} (string|table) Names of the files and directories
+ to find. Must be base names, paths and globs are
+ not supported.
+ {opts} (table) Optional keyword arguments:
+ • path (string): Path to begin searching from. If
+ omitted, the current working directory is used.
+ • upward (boolean, default false): If true,
+ search upward through parent directories.
+ Otherwise, search through child directories
+ (recursively).
+ • stop (string): Stop searching when this
+ directory is reached. The directory itself is
+ not searched.
+ • type (string): Find only files ("file") or
+ directories ("directory"). If omitted, both
+ files and directories that match {name} are
+ included.
+ • limit (number, default 1): Stop the search
+ after finding this many matches. Use
+ `math.huge` to place no limit on the number of
+ matches.
+
+ Return: ~
+ (table) The paths of all matching files or directories
+
+normalize({path}) *vim.fs.normalize()*
+ Normalize a path to a standard format. A tilde (~) character
+ at the beginning of the path is expanded to the user's home
+ directory and any backslash (\) characters are converted to
+ forward slashes (/). Environment variables are also expanded.
+
+ Example: >
+
+ vim.fs.normalize('C:\Users\jdoe')
+ => 'C:/Users/jdoe'
+
+ vim.fs.normalize('~/src/neovim')
+ => '/home/jdoe/src/neovim'
+
+ vim.fs.normalize('$XDG_CONFIG_HOME/nvim/init.vim')
+ => '/Users/jdoe/.config/nvim/init.vim'
+<
+
+ Parameters: ~
+ {path} (string) Path to normalize
+
+ Return: ~
+ (string) Normalized path
+
+parents({start}) *vim.fs.parents()*
+ Iterate over all the parents of the given file or directory.
+
+ Example: >
+
+ local root_dir
+ for dir in vim.fs.parents(vim.api.nvim_buf_get_name(0)) do
+ if vim.fn.isdirectory(dir .. "/.git") == 1 then
+ root_dir = dir
+ break
+ end
+ end
+
+ if root_dir then
+ print("Found git repository at", root_dir)
+ end
+<
+
+ Parameters: ~
+ {start} (string) Initial file or directory.
+
+ Return: ~
+ (function) Iterator
+
vim:tw=78:ts=8:ft=help:norl:
diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt
index 98da68b76a..59a5a63e16 100644
--- a/runtime/doc/map.txt
+++ b/runtime/doc/map.txt
@@ -48,6 +48,7 @@ modes.
allows for nested and recursive use of mappings.
Note: Trailing spaces are included in the {rhs},
because space is a valid Normal mode command.
+ See |map-trailing-white|.
*:nore* *:norem*
:no[remap] {lhs} {rhs} |mapmode-nvo| *:no* *:noremap* *:nor*
@@ -85,10 +86,8 @@ modes.
for other modes where it applies.
It also works when {lhs} matches the {rhs} of a
mapping. This is for when an abbreviation applied.
- Note: Trailing spaces are included in the {lhs}. This
- unmap does NOT work: >
- :map @@ foo
- :unmap @@ | print
+ Note: Trailing spaces are included in the {lhs}.
+ See |map-trailing-white|.
:mapc[lear] |mapmode-nvo| *:mapc* *:mapclear*
:nmapc[lear] |mapmode-n| *:nmapc* *:nmapclear*
@@ -152,6 +151,24 @@ that mapping won't get expanded yet, Vim is waiting for another character.
If you type a space, then "foo" will get inserted, plus the space. If you
type "a", then "bar" will get inserted.
+Trailing white space ~
+ *map-trailing-white*
+This unmap command does NOT work: >
+ :map @@ foo
+ :unmap @@ | print
+
+Because it tries to unmap "@@ ", including the white space before the command
+separator "|". Other examples with trailing white space: >
+ unmap @@
+ unmap @@ " comment
+
+An error will be issued, which is very hard to identify, because the ending
+whitespace character in `unmap @@ ` is not visible.
+
+A generic solution is to put the command separator "|" right after the mapped
+keys. After that white space and a comment may follow: >
+ unmap @@| " comment
+
1.2 SPECIAL ARGUMENTS *:map-arguments*
@@ -1430,6 +1447,112 @@ Possible values are (second column is the short name used in listing):
-addr=other ? other kind of range
+Incremental preview ~
+ *:command-preview* {nvim-api}
+Commands can show an 'inccommand' (as-you-type) preview by defining a preview
+handler (only from Lua, see |nvim_create_user_command()|).
+
+The preview callback must be a Lua function with this signature: >
+
+ function cmdpreview(opts, ns, buf)
+<
+where "opts" has the same form as that given to |nvim_create_user_command()|
+callbacks, "ns" is the preview namespace id for highlights, and "buf" is the
+buffer that your preview routine will directly modify to show the previewed
+results (for "inccommand=split", or nil for "inccommand=nosplit").
+
+Your command preview routine must implement this protocol:
+
+1. Modify the current buffer as required for the preview (see
+ |nvim_buf_set_text()| and |nvim_buf_set_lines()|).
+2. If preview buffer is provided, add necessary text to the preview buffer.
+3. Add required highlights to the current buffer. If preview buffer is
+ provided, add required highlights to the preview buffer as well. All
+ highlights must be added to the preview namespace which is provided as an
+ argument to the preview callback (see |nvim_buf_add_highlight()| and
+ |nvim_buf_set_extmark()| for help on how to add highlights to a namespace).
+4. Return an integer (0, 1, 2) which controls how Nvim behaves as follows:
+ 0: No preview is shown.
+ 1: Preview is shown without preview window (even with "inccommand=split").
+ 2: Preview is shown and preview window is opened (if "inccommand=split").
+ For "inccommand=nosplit" this is the same as 1.
+
+After preview ends, Nvim discards all changes to the buffer and all highlights
+in the preview namespace.
+
+Here's an example of a command to trim trailing whitespace from lines that
+supports incremental command preview:
+>
+ -- Trims trailing whitespace in the current buffer.
+ -- Also performs 'inccommand' preview if invoked as a preview callback
+ -- (preview_ns is non-nil).
+ local function trim_space(opts, preview_ns, preview_buf)
+ local line1 = opts.line1
+ local line2 = opts.line2
+ local buf = vim.api.nvim_get_current_buf()
+ local lines = vim.api.nvim_buf_get_lines(buf, line1 - 1, line2, 0)
+ local new_lines = {}
+ local preview_buf_line = 0
+
+ for i, line in ipairs(lines) do
+ local startidx, endidx = string.find(line, '%s+$')
+
+ if startidx ~= nil then
+ -- Highlight the match if in command preview mode
+ if preview_ns ~= nil then
+ vim.api.nvim_buf_add_highlight(
+ buf, preview_ns, 'Substitute', line1 + i - 2, startidx - 1,
+ endidx
+ )
+
+ -- Add lines and highlight to the preview buffer
+ -- if inccommand=split
+ if preview_buf ~= nil then
+ local prefix = string.format('|%d| ', line1 + i - 1)
+
+ vim.api.nvim_buf_set_lines(
+ preview_buf, preview_buf_line, preview_buf_line, 0,
+ { prefix .. line }
+ )
+ vim.api.nvim_buf_add_highlight(
+ preview_buf, preview_ns, 'Substitute', preview_buf_line,
+ #prefix + startidx - 1, #prefix + endidx
+ )
+
+ preview_buf_line = preview_buf_line + 1
+ end
+ end
+ end
+
+ if not preview_ns then
+ new_lines[#new_lines+1] = string.gsub(line, '%s+$', '')
+ end
+ end
+
+ -- Don't make any changes to the buffer if previewing
+ if not preview_ns then
+ vim.api.nvim_buf_set_lines(buf, line1 - 1, line2, 0, new_lines)
+ end
+
+ -- When called as a preview callback, return the value of the
+ -- preview type
+ if preview_ns ~= nil then
+ return 2
+ end
+ end
+
+ -- Create the user command
+ vim.api.nvim_create_user_command(
+ 'TrimTrailingWhitespace',
+ trim_space,
+ { nargs = '?', range = '%', addr = 'lines', preview = trim_space }
+ )
+<
+Note that in the above example, the same function is used as both the command
+callback and the preview callback, but you could instead use separate
+functions.
+
+
Special cases ~
*:command-bang* *:command-bar*
*:command-register* *:command-buffer*
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index a21d3bbce7..bd484366db 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -3266,8 +3266,9 @@ A jump table for the options with a short description can be found at |Q_op|.
'inccommand' 'icm' string (default "nosplit")
global
- When nonempty, shows the effects of |:substitute|, |:smagic|, and
- |:snomagic| as you type.
+ When nonempty, shows the effects of |:substitute|, |:smagic|,
+ |:snomagic| and user commands with the |:command-preview| flag as you
+ type.
Possible values:
nosplit Shows the effects of a command incrementally in the
@@ -3275,8 +3276,9 @@ A jump table for the options with a short description can be found at |Q_op|.
split Like "nosplit", but also shows partial off-screen
results in a preview window.
- If the preview is too slow (exceeds 'redrawtime') then 'inccommand' is
- automatically disabled until |Command-line-mode| is done.
+ If the preview for built-in commands is too slow (exceeds
+ 'redrawtime') then 'inccommand' is automatically disabled until
+ |Command-line-mode| is done.
*'include'* *'inc'*
'include' 'inc' string (default "^\s*#\s*include")
@@ -6061,10 +6063,9 @@ A jump table for the options with a short description can be found at |Q_op|.
If the statusline is not updated when you want it (e.g., after setting
a variable that's used in an expression), you can force an update by
- setting an option without changing its value. Example: >
- :let &ro = &ro
+ using `:redrawstatus`.
-< A result of all digits is regarded a number for display purposes.
+ A result of all digits is regarded a number for display purposes.
Otherwise the result is taken as flag text and applied to the rules
described above.
@@ -6686,7 +6687,7 @@ A jump table for the options with a short description can be found at |Q_op|.
global
When bigger than zero, Vim will give messages about what it is doing.
Currently, these messages are given:
- >= 1 Lua assignments to options,keymaps etc.
+ >= 1 Lua assignments to options, mappings, etc.
>= 2 When a file is ":source"'ed and when the shada file is read or written..
>= 3 UI info, terminal capabilities
>= 4 Shell commands.
diff --git a/runtime/doc/pi_health.txt b/runtime/doc/pi_health.txt
index 179c1066cd..04e04b5165 100644
--- a/runtime/doc/pi_health.txt
+++ b/runtime/doc/pi_health.txt
@@ -48,27 +48,26 @@ Commands *health-commands*
:checkhealth vim*
<
==============================================================================
-Lua Functions *health-functions-lua* *health-lua*
+Lua Functions *health-functions-lua* *health-lua* *vim.health*
The Lua "health" module can be used to create new healthchecks (see also
-|health-functions-vim|). To get started, simply use: >
- local health = require('health')
-<
-health.report_start({name}) *health.report_start()*
+|health-functions-vim|). To get started, simply use:
+
+vim.health.report_start({name}) *vim.health.report_start()*
Starts a new report. Most plugins should call this only once, but if
you want different sections to appear in your report, call this once
per section.
-health.report_info({msg}) *health.report_info()*
+vim.health.report_info({msg}) *vim.health.report_info()*
Reports an informational message.
-health.report_ok({msg}) *health.report_ok()*
+vim.health.report_ok({msg}) *vim.health.report_ok()*
Reports a "success" message.
-health.report_warn({msg} [, {advice}]) *health.report_warn()*
+vim.health.report_warn({msg} [, {advice}]) *vim.health.report_warn()*
Reports a warning. {advice} is an optional List of suggestions.
-health.report_error({msg} [, {advice}]) *health.report_error()*
+vim.health.report_error({msg} [, {advice}]) *vim.health.report_error()*
Reports an error. {advice} is an optional List of suggestions.
==============================================================================
@@ -98,15 +97,14 @@ Copy this sample code into `lua/foo/health/init.lua` or `lua/foo/health.lua`,
replacing "foo" in the path with your plugin name: >
local M = {}
- local health = require("health")
M.check = function()
- health.report_start("my_plugin report")
+ vim.health.report_start("my_plugin report")
-- make sure setup function parameters are ok
if check_setup() then
- health.report_ok("Setup function is correct")
+ vim.health.report_ok("Setup function is correct")
else
- health.report_error("Setup function is incorrect")
+ vim.health.report_error("Setup function is incorrect")
end
-- do some more checking
-- ...
diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt
index 8b88fa9363..4a88939715 100644
--- a/runtime/doc/starting.txt
+++ b/runtime/doc/starting.txt
@@ -1349,7 +1349,7 @@ STATE DIRECTORY (DEFAULT) ~
Note: Throughout the user manual these defaults are used as placeholders, e.g.
"~/.config" is understood to mean "$XDG_CONFIG_HOME or ~/.config".
-LOG FILE *$NVIM_LOG_FILE*
+LOG FILE *$NVIM_LOG_FILE* *E5430*
Besides 'debug' and 'verbose', Nvim keeps a general log file for internal
debugging, plugins and RPC clients. >
:echo $NVIM_LOG_FILE
diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt
index 339ae0c2ed..eb19bf5934 100644
--- a/runtime/doc/treesitter.txt
+++ b/runtime/doc/treesitter.txt
@@ -261,36 +261,37 @@ for a node or match and perform side effects. For example, the |set!|
predicate sets metadata on the match or node : >
((identifier) @foo (#set! "type" "parameter"))
-Here is a list of built-in directives:
+Built-in directives:
- `set!` *ts-directive-set!*
- Sets key/value metadata for a specific node or match : >
- ((identifier) @foo (#set! @foo "kind" "parameter"))
- ((node1) @left (node2) @right (#set! "type" "pair"))
+ `set!` *ts-directive-set!*
+ Sets key/value metadata for a specific match or capture.
+ Value is accessible as either `metadata[key]` (match
+ specific) or `metadata[capture_id][key]` (capture specific).
+
+ Parameters: ~
+ {capture_id} (optional)
+ {key}
+ {value}
+
+ Examples: >
+ ((identifier) @foo (#set! @foo "kind" "parameter"))
+ ((node1) @left (node2) @right (#set! "type" "pair"))
<
- `offset!` *ts-directive-offset!*
- Takes the range of the captured node and applies the offsets
- to it's range : >
- ((identifier) @constant (#offset! @constant 0 1 0 -1))
-< This will generate a range object for the captured node with
- the offsets applied. The arguments are
- `({capture_id}, {start_row}, {start_col}, {end_row}, {end_col}, {key?})`
- The default key is "offset".
-
- *vim.treesitter.query.add_directive()*
-vim.treesitter.query.add_directive({name}, {handler})
-
-This adds a directive with the name {name} to be used in queries.
-{handler} should be a function whose signature will be : >
- handler(match, pattern, bufnr, predicate, metadata)
-Handlers can set match level data by setting directly on the metadata object
-`metadata.key = value` Handlers can set node level data by using the capture
-id on the metadata table `metadata[capture_id].key = value`
+ `offset!` *ts-directive-offset!*
+ Takes the range of the captured node and applies an offset.
+ This will generate a new range object for the captured node
+ as `metadata[capture_id].range`.
- *vim.treesitter.query.list_directives()*
-vim.treesitter.query.list_directives()
+ Parameters: ~
+ {capture_id}
+ {start_row}
+ {start_col}
+ {end_row}
+ {end_col}
-This lists the currently available directives to use in queries.
+ Example: >
+ ((identifier) @constant (#offset! @constant 0 1 0 -1))
+<
Treesitter syntax highlighting (WIP) *lua-treesitter-highlight*
@@ -409,10 +410,15 @@ Lua module: vim.treesitter.query *treesitter-query*
add_directive({name}, {handler}, {force}) *add_directive()*
Adds a new directive to be used in queries
+ Handlers can set match level data by setting directly on the
+ metadata object `metadata.key = value`, additionally, handlers
+ can set node level data by using the capture id on the
+ metadata table `metadata[capture_id].key = value`
+
Parameters: ~
{name} the name of the directive, without leading #
{handler} the handler function to be used signature will
- be (match, pattern, bufnr, predicate)
+ be (match, pattern, bufnr, predicate, metadata)
add_predicate({name}, {handler}, {force}) *add_predicate()*
Adds a new predicate to be used in queries
@@ -451,6 +457,8 @@ get_query_files({lang}, {query_name}, {is_included})
as `nil`
list_directives() *list_directives()*
+ Lists the currently available directives to use in queries.
+
Return: ~
The list of supported directives.
@@ -746,8 +754,8 @@ LanguageTree:set_included_regions({self}, {regions})
parsed again.
Parameters: ~
- {regions} A list of regions this tree should manage and
- parse.
+ {regions} (table) list of regions this tree should manage
+ and parse.
{self}
LanguageTree:source({self}) *LanguageTree:source()*
diff --git a/runtime/doc/undo.txt b/runtime/doc/undo.txt
index 39b26476af..67f24103cd 100644
--- a/runtime/doc/undo.txt
+++ b/runtime/doc/undo.txt
@@ -108,12 +108,14 @@ change again. But you can do something like this: >
After this a "u" command will undo the delete command and the previous
change.
-
+ *undo-break*
To do the opposite, break a change into two undo blocks, in Insert mode use
CTRL-G u. This is useful if you want an insert command to be undoable in
parts. E.g., for each sentence. |i_CTRL-G_u|
+
Setting the value of 'undolevels' also breaks undo. Even when the new value
-is equal to the old value.
+is equal to the old value: >
+ let &undolevels = &undolevels
==============================================================================
4. Undo branches *undo-branches* *undo-tree*
diff --git a/runtime/doc/usr_05.txt b/runtime/doc/usr_05.txt
index 1cf383dce3..0e94d9a1b1 100644
--- a/runtime/doc/usr_05.txt
+++ b/runtime/doc/usr_05.txt
@@ -45,7 +45,7 @@ This file is always used and is recommended:
The vimrc file can contain all the commands that you type after a colon. The
simplest ones are for setting options. For example, if you want Vim to always
-start with the 'incsearch' option on, add this line your vimrc file: >
+start with the 'ignorecase' option on, add this line your vimrc file: >
set ignorecase
diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt
index 60bcadd582..7fbe88711d 100644
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -1596,7 +1596,7 @@ the same (the text may change, e.g., it may be translated).
When the ":read" command causes another error, the pattern "E484:" will not
match in it. Thus this exception will not be caught and result in the usual
-error message.
+error message and execution is aborted.
You might be tempted to do this: >
diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt
index c079b83c29..8e67cb0923 100644
--- a/runtime/doc/vim_diff.txt
+++ b/runtime/doc/vim_diff.txt
@@ -183,6 +183,7 @@ Commands:
|:sign-define| accepts a `numhl` argument, to highlight the line number
|:match| can be invoked before highlight group is defined
|:source| works with Lua and anonymous (no file) scripts
+ User commands can support |:command-preview| to show results as you type
Events:
|RecordingEnter|
@@ -235,6 +236,7 @@ Options:
"horizdown", "vertleft", "vertright", "verthoriz"
'foldcolumn' supports up to 9 dynamic/fixed columns
'inccommand' shows interactive results for |:substitute|-like commands
+ and |:command-preview| commands
'laststatus' global statusline support
'pumblend' pseudo-transparent popupmenu
'scrollback'
diff --git a/runtime/filetype.vim b/runtime/filetype.vim
index c73f5f6cb7..ed17002733 100644
--- a/runtime/filetype.vim
+++ b/runtime/filetype.vim
@@ -1,7 +1,7 @@
" Vim support file to detect file types
"
" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2022 Apr 25
+" Last Change: 2022 Jun 03
" Listen very carefully, I will say this only once
if exists("did_load_filetypes")
@@ -1286,25 +1286,25 @@ au BufNewFile,BufRead opam,*.opam,*.opam.template setf opam
au BufNewFile,BufRead [a-zA-Z0-9]*Dict\(.*\)\=,[a-zA-Z]*Properties\(.*\)\=,*Transport\(.*\),fvSchemes,fvSolution,fvConstrains,fvModels,*/constant/g,*/0\(\.orig\)\=/* call dist#ft#FTfoam()
" OpenROAD
-au BufNewFile,BufRead *.or setf openroad
+au BufNewFile,BufRead *.or setf openroad
" OPL
-au BufNewFile,BufRead *.[Oo][Pp][Ll] setf opl
+au BufNewFile,BufRead *.[Oo][Pp][Ll] setf opl
" OpenSCAD
-au BufNewFile,BufRead *.scad setf openscad
+au BufNewFile,BufRead *.scad setf openscad
" Oracle config file
-au BufNewFile,BufRead *.ora setf ora
+au BufNewFile,BufRead *.ora setf ora
" Org
-au BufNewFile,BufRead *.org,*.org_archive setf org
+au BufNewFile,BufRead *.org,*.org_archive setf org
" Packet filter conf
-au BufNewFile,BufRead pf.conf setf pf
+au BufNewFile,BufRead pf.conf setf pf
-" Pacman config
-au BufNewFile,BufRead */etc/pacman.conf setf conf
+" ini style config files, using # comments
+au BufNewFile,BufRead */etc/pacman.conf,mpv.conf setf confini
" Pacman hooks
au BufNewFile,BufRead *.hook
@@ -1319,36 +1319,36 @@ au BufNewFile,BufRead */etc/pam.conf setf pamconf
au BufNewFile,BufRead pam_env.conf,.pam_environment setf pamenv
" PApp
-au BufNewFile,BufRead *.papp,*.pxml,*.pxsl setf papp
+au BufNewFile,BufRead *.papp,*.pxml,*.pxsl setf papp
" Password file
au BufNewFile,BufRead */etc/passwd,*/etc/passwd-,*/etc/passwd.edit,*/etc/shadow,*/etc/shadow-,*/etc/shadow.edit,*/var/backups/passwd.bak,*/var/backups/shadow.bak setf passwd
" Pascal (also *.p, *.pp, *.inc)
-au BufNewFile,BufRead *.pas setf pascal
+au BufNewFile,BufRead *.pas setf pascal
" Pascal or Puppet manifest
-au BufNewFile,BufRead *.pp call dist#ft#FTpp()
+au BufNewFile,BufRead *.pp call dist#ft#FTpp()
" Delphi or Lazarus program file
-au BufNewFile,BufRead *.dpr,*.lpr setf pascal
+au BufNewFile,BufRead *.dpr,*.lpr setf pascal
" Free Pascal makefile definition file
-au BufNewFile,BufRead *.fpc setf fpcmake
+au BufNewFile,BufRead *.fpc setf fpcmake
" PDF
-au BufNewFile,BufRead *.pdf setf pdf
+au BufNewFile,BufRead *.pdf setf pdf
" PCMK - HAE - crm configure edit
-au BufNewFile,BufRead *.pcmk setf pcmk
+au BufNewFile,BufRead *.pcmk setf pcmk
" Perl
if has("fname_case")
- au BufNewFile,BufRead *.pl,*.PL call dist#ft#FTpl()
+ au BufNewFile,BufRead *.pl,*.PL call dist#ft#FTpl()
else
- au BufNewFile,BufRead *.pl call dist#ft#FTpl()
+ au BufNewFile,BufRead *.pl call dist#ft#FTpl()
endif
-au BufNewFile,BufRead *.plx,*.al,*.psgi setf perl
+au BufNewFile,BufRead *.plx,*.al,*.psgi setf perl
" Perl, XPM or XPM2
au BufNewFile,BufRead *.pm
diff --git a/runtime/ftplugin/confini.vim b/runtime/ftplugin/confini.vim
new file mode 100644
index 0000000000..310e046353
--- /dev/null
+++ b/runtime/ftplugin/confini.vim
@@ -0,0 +1,10 @@
+" Vim filetype plugin file
+" Language: confini
+
+" Quit if a ftplugin file was already loaded
+if exists("b:did_ftplugin")
+ finish
+endif
+
+" Use the cfg plugin, it's similar enough.
+runtime! ftplugin/cfg.vim
diff --git a/runtime/ftplugin/fortran.vim b/runtime/ftplugin/fortran.vim
index 26dc90a184..8f5b243b82 100644
--- a/runtime/ftplugin/fortran.vim
+++ b/runtime/ftplugin/fortran.vim
@@ -1,6 +1,6 @@
" Vim settings file
" Language: Fortran 2008 (and older: Fortran 2003, 95, 90, 77, 66)
-" Version: (v53) 2021 April 06
+" Version: (v53) 2021 April 06 (updated 2022 May 22)
" Maintainer: Ajit J. Thakkar <ajit@unb.ca>; <http://www2.unb.ca/~ajit/>
" Usage: For instructions, do :help fortran-plugin from Vim
" Credits:
@@ -105,12 +105,13 @@ if !exists("b:match_words")
let s:notselect = '\%(\<select\s\+\)\@<!'
let s:notelse = '\%(\<end\s\+\|\<else\s\+\)\@<!'
let s:notprocedure = '\%(\s\+procedure\>\)\@!'
+ let s:nothash = '\%(^\s*#\s*\)\@<!'
let b:match_ignorecase = 1
let b:match_words =
\ '(:),' .
\ '\<select\s*case\>:' . s:notselect. '\<case\>:\<end\s*select\>,' .
\ s:notelse . '\<if\s*(.\+)\s*then\>:' .
- \ '\<else\s*\%(if\s*(.\+)\s*then\)\=\>:\<end\s*if\>,'.
+ \ s:nothash . '\<else\s*\%(if\s*(.\+)\s*then\)\=\>:' . s:nothash . '\<end\s*if\>,'.
\ 'do\s\+\(\d\+\):\%(^\s*\)\@<=\1\s,'.
\ s:notend . '\<do\>:\<end\s*do\>,'.
\ s:notelse . '\<where\>:\<elsewhere\>:\<end\s*where\>,'.
@@ -122,7 +123,8 @@ if !exists("b:match_words")
\ s:notend . '\<subroutine\>:\<end\s*subroutine\>,'.
\ s:notend . '\<function\>:\<end\s*function\>,'.
\ s:notend . '\<module\>' . s:notprocedure . ':\<end\s*module\>,'.
- \ s:notend . '\<program\>:\<end\s*program\>'
+ \ s:notend . '\<program\>:\<end\s*program\>,'.
+ \ '\%(^\s*\)\@<=#\s*if\%(def\|ndef\)\=\>:\%(^\s*\)\@<=#\s*\%(elif\|else\)\>:\%(^\s*\)\@<=#\s*endif\>'
endif
" File filters for :browse e
diff --git a/runtime/indent/confini.vim b/runtime/indent/confini.vim
new file mode 100644
index 0000000000..50b3dd20c0
--- /dev/null
+++ b/runtime/indent/confini.vim
@@ -0,0 +1,10 @@
+" Vim indent file
+" Language: confini
+
+" Quit if an indent file was already loaded.
+if exists("b:did_indent")
+ finish
+endif
+
+" Use the cfg indenting, it's similar enough.
+runtime! indent/cfg.vim
diff --git a/runtime/lua/health.lua b/runtime/lua/health.lua
index 142a353bf2..40e2b3c3e7 100644
--- a/runtime/lua/health.lua
+++ b/runtime/lua/health.lua
@@ -1,23 +1,6 @@
-local M = {}
-
-function M.report_start(msg)
- vim.fn['health#report_start'](msg)
-end
-
-function M.report_info(msg)
- vim.fn['health#report_info'](msg)
-end
-
-function M.report_ok(msg)
- vim.fn['health#report_ok'](msg)
-end
-
-function M.report_warn(msg, ...)
- vim.fn['health#report_warn'](msg, ...)
-end
-
-function M.report_error(msg, ...)
- vim.fn['health#report_error'](msg, ...)
-end
-
-return M
+return setmetatable({}, {
+ __index = function(_, k)
+ vim.deprecate("require('health')", 'vim.health', '0.9', false)
+ return vim.health[k]
+ end,
+})
diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua
index e6ab48f30d..453aa6ac81 100644
--- a/runtime/lua/vim/_editor.lua
+++ b/runtime/lua/vim/_editor.lua
@@ -49,6 +49,8 @@ for k, v in pairs({
diagnostic = true,
keymap = true,
ui = true,
+ health = true,
+ fs = true,
}) do
vim._submodules[k] = v
end
diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua
index 1706956bca..59cf837a1d 100644
--- a/runtime/lua/vim/_meta.lua
+++ b/runtime/lua/vim/_meta.lua
@@ -10,29 +10,39 @@ local SET_TYPES = setmetatable({
GLOBAL = 2,
}, { __index = error })
-local options_info = {}
-for _, v in pairs(a.nvim_get_all_options_info()) do
- options_info[v.name] = v
- if v.shortname ~= '' then
- options_info[v.shortname] = v
+local options_info = nil
+local buf_options = nil
+local glb_options = nil
+local win_options = nil
+
+local function _setup()
+ if options_info ~= nil then
+ return
+ end
+ options_info = {}
+ for _, v in pairs(a.nvim_get_all_options_info()) do
+ options_info[v.name] = v
+ if v.shortname ~= '' then
+ options_info[v.shortname] = v
+ end
end
-end
-local get_scoped_options = function(scope)
- local result = {}
- for name, option_info in pairs(options_info) do
- if option_info.scope == scope then
- result[name] = true
+ local function get_scoped_options(scope)
+ local result = {}
+ for name, option_info in pairs(options_info) do
+ if option_info.scope == scope then
+ result[name] = true
+ end
end
+
+ return result
end
- return result
+ buf_options = get_scoped_options('buf')
+ glb_options = get_scoped_options('global')
+ win_options = get_scoped_options('win')
end
-local buf_options = get_scoped_options('buf')
-local glb_options = get_scoped_options('global')
-local win_options = get_scoped_options('win')
-
local function make_meta_accessor(get, set, del, validator)
validator = validator or function()
return true
@@ -90,6 +100,7 @@ do -- buffer option accessor
return make_meta_accessor(get, set, nil, function(k)
if type(k) == 'string' then
+ _setup()
if win_options[k] then
error(string.format([['%s' is a window option, not a buffer option. See ":help %s"]], k, k))
elseif glb_options[k] then
@@ -119,6 +130,7 @@ do -- window option accessor
return make_meta_accessor(get, set, nil, function(k)
if type(k) == 'string' then
+ _setup()
if buf_options[k] then
error(string.format([['%s' is a buffer option, not a window option. See ":help %s"]], k, k))
elseif glb_options[k] then
@@ -610,6 +622,7 @@ local create_option_metatable = function(set_type)
local set_mt, option_mt
local make_option = function(name, value)
+ _setup()
local info = assert(options_info[name], 'Not a valid option name: ' .. name)
if type(value) == 'table' and getmetatable(value) == option_mt then
diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua
index bb12362234..afc0a4095c 100644
--- a/runtime/lua/vim/diagnostic.lua
+++ b/runtime/lua/vim/diagnostic.lua
@@ -398,10 +398,19 @@ local function get_diagnostics(bufnr, opts, clamp)
if not opts.lnum or d.lnum == opts.lnum then
if clamp and vim.api.nvim_buf_is_loaded(b) then
local line_count = buf_line_count[b] - 1
- if d.lnum > line_count or d.end_lnum > line_count or d.lnum < 0 or d.end_lnum < 0 then
+ if
+ d.lnum > line_count
+ or d.end_lnum > line_count
+ or d.lnum < 0
+ or d.end_lnum < 0
+ or d.col < 0
+ or d.end_col < 0
+ then
d = vim.deepcopy(d)
d.lnum = math.max(math.min(d.lnum, line_count), 0)
d.end_lnum = math.max(math.min(d.end_lnum, line_count), 0)
+ d.col = math.max(d.col, 0)
+ d.end_col = math.max(d.end_col, 0)
end
end
table.insert(diagnostics, d)
diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index c26a43f776..c091e2f35c 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -3,32 +3,101 @@ local api = vim.api
local M = {}
---@private
-local function starsetf(ft)
+local function starsetf(ft, opts)
return {
- function(path)
- if not vim.g.fg_ignore_pat then
- return ft
+ function(path, bufnr)
+ local f = type(ft) == 'function' and ft(path, bufnr) or ft
+ if not vim.g.ft_ignore_pat then
+ return f
end
- local re = vim.regex(vim.g.fg_ignore_pat)
- if re:match_str(path) then
- return ft
+ local re = vim.regex(vim.g.ft_ignore_pat)
+ if not re:match_str(path) then
+ return f
end
end,
{
-- Starset matches should always have lowest priority
- priority = -math.huge,
+ priority = (opts and opts.priority) or -math.huge,
},
}
end
---@private
-local function getline(bufnr, lnum)
- return api.nvim_buf_get_lines(bufnr, lnum - 1, lnum, false)[1] or ''
+local function getline(bufnr, start_lnum, end_lnum)
+ end_lnum = end_lnum or start_lnum
+ local lines = vim.api.nvim_buf_get_lines(bufnr, start_lnum - 1, end_lnum, false)
+ return table.concat(lines) or ''
end
--- Filetypes based on file extension
+---@private
+--- Get a single line or line-range from the buffer.
+---
+---@param bufnr number|nil The buffer to get the lines from
+---@param start_lnum number The line number of the first line (inclusive, 1-based)
+---@param end_lnum number|nil The line number of the last line (inclusive, 1-based)
+---@return table<string>|string Array of lines, or string when end_lnum is omitted
+function M.getlines(bufnr, start_lnum, end_lnum)
+ if not end_lnum then
+ -- Return a single line as a string
+ return vim.api.nvim_buf_get_lines(bufnr, start_lnum - 1, start_lnum, false)[1]
+ end
+ return vim.api.nvim_buf_get_lines(bufnr, start_lnum - 1, end_lnum, false)
+end
+
+---@private
+--- Check whether a string matches any of the given Lua patterns.
+---
+---@param s string The string to check
+---@param patterns table<string> A list of Lua patterns
+---@return boolean `true` if s matched a pattern, else `false`
+function M.findany(s, patterns)
+ if s == nil then
+ return false
+ end
+ for _, v in ipairs(patterns) do
+ if s:find(v) then
+ return true
+ end
+ end
+ return false
+end
+
+---@private
+--- Get the next non-whitespace line in the buffer.
+---
+---@param bufnr number The buffer to get the line from
+---@param start_lnum number The line number of the first line to start from (inclusive, 1-based)
+---@return string|nil The first non-blank line if found or `nil` otherwise
+function M.nextnonblank(bufnr, start_lnum)
+ for _, line in ipairs(M.getlines(bufnr, start_lnum, -1)) do
+ if not line:find('^%s*$') then
+ return line
+ end
+ end
+ return nil
+end
+
+---@private
+--- Check whether the given string matches the Vim regex pattern.
+M.matchregex = (function()
+ local cache = {}
+ return function(s, pattern)
+ if s == nil then
+ return nil
+ end
+ if not cache[pattern] then
+ cache[pattern] = vim.regex(pattern)
+ end
+ return cache[pattern]:match_str(s)
+ end
+end)()
+
-- luacheck: push no unused args
+-- luacheck: push ignore 122
+
+-- Filetypes based on file extension
+---@diagnostic disable: unused-local
local extension = {
-- BEGIN EXTENSION
['8th'] = '8th',
@@ -52,8 +121,17 @@ local extension = {
art = 'art',
asciidoc = 'asciidoc',
adoc = 'asciidoc',
+ asa = function(path, bufnr)
+ if vim.g.filetype_asa then
+ return vim.g.filetype_asa
+ end
+ return 'aspvbs'
+ end,
['asn1'] = 'asn',
asn = 'asn',
+ asp = function(path, bufnr)
+ return require('vim.filetype.detect').asp(bufnr)
+ end,
atl = 'atlas',
as = 'atlas',
ahk = 'autohotkey',
@@ -92,7 +170,6 @@ local extension = {
cho = 'chordpro',
chordpro = 'chordpro',
eni = 'cl',
- dcl = 'clean',
icl = 'clean',
cljx = 'clojure',
clj = 'clojure',
@@ -129,7 +206,7 @@ local extension = {
hxx = 'cpp',
hpp = 'cpp',
cpp = function(path, bufnr)
- if vim.g.cynlib_syntax_for_cc then
+ if vim.g.cynlib_syntax_for_cpp then
return 'cynlib'
end
return 'cpp'
@@ -167,6 +244,7 @@ local extension = {
diff = 'diff',
rej = 'diff',
Dockerfile = 'dockerfile',
+ dockerfile = 'dockerfile',
bat = 'dosbatch',
wrap = 'dosini',
ini = 'dosini',
@@ -688,6 +766,16 @@ local extension = {
bbl = 'tex',
latex = 'tex',
sty = 'tex',
+ cls = function(path, bufnr)
+ local line = getline(bufnr, 1)
+ if line:find('^%%') then
+ return 'tex'
+ elseif line:find('^#') and line:lower():find('rexx') then
+ return 'rexx'
+ else
+ return 'st'
+ end
+ end,
texi = 'texinfo',
txi = 'texinfo',
texinfo = 'texinfo',
@@ -766,6 +854,12 @@ local extension = {
csproj = 'xml',
wpl = 'xml',
xmi = 'xml',
+ xpm = function(path, bufnr)
+ if getline(bufnr, 1):find('XPM2') then
+ return 'xpm2'
+ end
+ return 'xpm'
+ end,
['xpm2'] = 'xpm2',
xqy = 'xquery',
xqm = 'xquery',
@@ -1005,10 +1099,210 @@ local extension = {
end,
txt = function(path, bufnr)
--helpfiles match *.txt, but should have a modeline as last line
- if not getline(bufnr, -1):match('vim:.*ft=help') then
+ if not getline(bufnr, -1):find('vim:.*ft=help') then
return 'text'
end
end,
+ cmd = function(path, bufnr)
+ if getline(bufnr, 1):find('^/%*') then
+ return 'rexx'
+ end
+ return 'dosbatch'
+ end,
+ rul = function(path, bufnr)
+ return require('vim.filetype.detect').rul(bufnr)
+ end,
+ cpy = function(path, bufnr)
+ if getline(bufnr, 1):find('^##') then
+ return 'python'
+ end
+ return 'cobol'
+ end,
+ dsl = function(path, bufnr)
+ if getline(bufnr, 1):find('^%s*<!') then
+ return 'dsl'
+ end
+ return 'structurizr'
+ end,
+ edf = 'edif',
+ edfi = 'edif',
+ edo = 'edif',
+ edn = function(path, bufnr)
+ return require('vim.filetype.detect').edn(bufnr)
+ end,
+ smil = function(path, bufnr)
+ if getline(bufnr, 1):find('<%?%s*xml.*%?>') then
+ return 'xml'
+ end
+ return 'smil'
+ end,
+ smi = function(path, bufnr)
+ return require('vim.filetype.detect').smi(bufnr)
+ end,
+ install = function(path, bufnr)
+ if getline(bufnr, 1):lower():find('<%?php') then
+ return 'php'
+ end
+ return require('vim.filetype.detect').sh(path, bufnr, 'bash')
+ end,
+ pm = function(path, bufnr)
+ local line = getline(bufnr, 1)
+ if line:find('XPM2') then
+ return 'xpm2'
+ elseif line:find('XPM') then
+ return 'xpm'
+ else
+ return 'perl'
+ end
+ end,
+ me = function(path, bufnr)
+ local filename = vim.fn.fnamemodify(path, ':t'):lower()
+ if filename ~= 'read.me' and filename ~= 'click.me' then
+ return 'nroff'
+ end
+ end,
+ reg = function(path, bufnr)
+ local line = getline(bufnr, 1):lower()
+ if line:find('^regedit[0-9]*%s*$') or line:find('^windows registry editor version %d*%.%d*%s*$') then
+ return 'registry'
+ end
+ end,
+ decl = function(path, bufnr)
+ return require('vim.filetype.detect').decl(bufnr)
+ end,
+ dec = function(path, bufnr)
+ return require('vim.filetype.detect').decl(bufnr)
+ end,
+ dcl = function(path, bufnr)
+ local decl = require('vim.filetype.detect').decl(bufnr)
+ if decl then
+ return decl
+ end
+ return 'clean'
+ end,
+ web = function(path, bufnr)
+ return require('vim.filetype.detect').web(bufnr)
+ end,
+ ttl = function(path, bufnr)
+ local line = getline(bufnr, 1):lower()
+ if line:find('^@?prefix') or line:find('^@?base') then
+ return 'turtle'
+ end
+ return 'teraterm'
+ end,
+ am = function(path, bufnr)
+ if not path:lower():find('makefile%.am$') then
+ return 'elf'
+ end
+ end,
+ ['m4'] = function(path, bufnr)
+ local path_lower = path:lower()
+ if not path_lower:find('html%.m4$') and not path_lower:find('fvwm2rc') then
+ return 'm4'
+ end
+ end,
+ hw = function(path, bufnr)
+ return require('vim.filetype.detect').hw(bufnr)
+ end,
+ module = function(path, bufnr)
+ return require('vim.filetype.detect').hw(bufnr)
+ end,
+ pkg = function(path, bufnr)
+ return require('vim.filetype.detect').hw(bufnr)
+ end,
+ ms = function(path, bufnr)
+ if not require('vim.filetype.detect').nroff(bufnr) then
+ return 'xmath'
+ end
+ end,
+ t = function(path, bufnr)
+ if not require('vim.filetype.detect').nroff(bufnr) and not require('vim.filetype.detect').perl(path, bufnr) then
+ return 'tads'
+ end
+ end,
+ class = function(path, bufnr)
+ -- Check if not a Java class (starts with '\xca\xfe\xba\xbe')
+ if not getline(bufnr, 1):find('^\202\254\186\190') then
+ return 'stata'
+ end
+ end,
+ sgml = function(path, bufnr)
+ return require('vim.filetype.detect').sgml(bufnr)
+ end,
+ sgm = function(path, bufnr)
+ return require('vim.filetype.detect').sgml(bufnr)
+ end,
+ rc = function(path, bufnr)
+ if not path:find('/etc/Muttrc%.d/') then
+ return 'rc'
+ end
+ end,
+ rch = function(path, bufnr)
+ if not path:find('/etc/Muttrc%.d/') then
+ return 'rc'
+ end
+ end,
+ control = function(path, bufnr)
+ return require('vim.filetype.detect').control(bufnr)
+ end,
+ copyright = function(path, bufnr)
+ return require('vim.filetype.detect').copyright(bufnr)
+ end,
+ -- Ignored extensions
+ bak = function(path, bufnr)
+ local root = vim.fn.fnamemodify(path, ':r')
+ return M.match(root, bufnr)
+ end,
+ ['dpkg-dist'] = function(path, bufnr)
+ local root = vim.fn.fnamemodify(path, ':r')
+ return M.match(root, bufnr)
+ end,
+ ['dpkg-old'] = function(path, bufnr)
+ local root = vim.fn.fnamemodify(path, ':r')
+ return M.match(root, bufnr)
+ end,
+ ['dpkg-new'] = function(path, bufnr)
+ local root = vim.fn.fnamemodify(path, ':r')
+ return M.match(root, bufnr)
+ end,
+ ['dpkg-bak'] = function(path, bufnr)
+ local root = vim.fn.fnamemodify(path, ':r')
+ return M.match(root, bufnr)
+ end,
+ new = function(path, bufnr)
+ local root = vim.fn.fnamemodify(path, ':r')
+ return M.match(root, bufnr)
+ end,
+ old = function(path, bufnr)
+ local root = vim.fn.fnamemodify(path, ':r')
+ return M.match(root, bufnr)
+ end,
+ orig = function(path, bufnr)
+ local root = vim.fn.fnamemodify(path, ':r')
+ return M.match(root, bufnr)
+ end,
+ pacsave = function(path, bufnr)
+ local root = vim.fn.fnamemodify(path, ':r')
+ return M.match(root, bufnr)
+ end,
+ pacnew = function(path, bufnr)
+ local root = vim.fn.fnamemodify(path, ':r')
+ return M.match(root, bufnr)
+ end,
+ rpmsave = function(path, bufnr)
+ local root = vim.fn.fnamemodify(path, ':r')
+ return M.match(root, bufnr)
+ end,
+ rmpnew = function(path, bufnr)
+ local root = vim.fn.fnamemodify(path, ':r')
+ return M.match(root, bufnr)
+ end,
+ ['in'] = function(path, bufnr)
+ if vim.fs.basename(path) ~= 'configure.in' then
+ local root = vim.fn.fnamemodify(path, ':r')
+ return M.match(root, bufnr)
+ end
+ end,
-- END EXTENSION
}
@@ -1030,8 +1324,9 @@ local filename = {
['named.root'] = 'bindzone',
WORKSPACE = 'bzl',
BUILD = 'bzl',
- ['cabal.config'] = 'cabalconfig',
['cabal.project'] = 'cabalproject',
+ [vim.env.HOME .. '/cabal.config'] = 'cabalconfig',
+ ['cabal.config'] = 'cabalconfig',
calendar = 'calendar',
catalog = 'catalog',
['/etc/cdrdao.conf'] = 'cdrdaoconf',
@@ -1064,9 +1359,10 @@ local filename = {
Dockerfile = 'dockerfile',
npmrc = 'dosini',
['/etc/yum.conf'] = 'dosini',
- ['/etc/pacman.conf'] = 'conf',
['.npmrc'] = 'dosini',
['.editorconfig'] = 'dosini',
+ ['/etc/pacman.conf'] = 'confini',
+ ['mpv.conf'] = 'confini',
dune = 'dune',
jbuild = 'dune',
['dune-workspace'] = 'dune',
@@ -1188,6 +1484,9 @@ local filename = {
['/etc/nanorc'] = 'nanorc',
Neomuttrc = 'neomuttrc',
['.netrc'] = 'netrc',
+ NEWS = function(path, bufnr)
+ return require('vim.filetype.detect').news(bufnr)
+ end,
['.ocamlinit'] = 'ocaml',
['.octaverc'] = 'octave',
octaverc = 'octave',
@@ -1213,15 +1512,28 @@ local filename = {
['/etc/pinforc'] = 'pinfo',
['/.pinforc'] = 'pinfo',
['.povrayrc'] = 'povini',
+ ['printcap'] = function(path, bufnr)
+ vim.b[bufnr].ptcap_type = 'print'
+ return 'ptcap'
+ end,
+ ['termcap'] = function(path, bufnr)
+ vim.b[bufnr].ptcap_type = 'term'
+ return 'ptcap'
+ end,
['.procmailrc'] = 'procmail',
['.procmail'] = 'procmail',
['/etc/protocols'] = 'protocols',
+ ['INDEX'] = function(path, bufnr)
+ return require('vim.filetype.detect').psf(bufnr)
+ end,
+ ['INFO'] = function(path, bufnr)
+ return require('vim.filetype.detect').psf(bufnr)
+ end,
['.pythonstartup'] = 'python',
['.pythonrc'] = 'python',
SConstruct = 'python',
ratpoisonrc = 'ratpoison',
['.ratpoisonrc'] = 'ratpoison',
- v = 'rcs',
inputrc = 'readline',
['.inputrc'] = 'readline',
['.reminders'] = 'remind',
@@ -1291,6 +1603,14 @@ local filename = {
['.Xpdefaults'] = 'xdefaults',
['xdm-config'] = 'xdefaults',
['.Xdefaults'] = 'xdefaults',
+ ['xorg.conf'] = function(path, bufnr)
+ vim.b[bufnr].xf86conf_xfree86_version = 4
+ return 'xf86conf'
+ end,
+ ['xorg.conf-4'] = function(path, bufnr)
+ vim.b[bufnr].xf86conf_xfree86_version = 4
+ return 'xf86conf'
+ end,
['/etc/xinetd.conf'] = 'xinetd',
fglrxrc = 'xml',
['/etc/blkid.tab'] = 'xml',
@@ -1361,6 +1681,9 @@ local filename = {
['tcsh.tcshrc'] = function(path, bufnr)
return require('vim.filetype.detect').shell(path, bufnr, 'tcsh')
end,
+ ['XF86Config'] = function(path, bufnr)
+ return require('vim.filetype.detect').xf86conf(bufnr)
+ end,
-- END FILENAME
}
@@ -1377,12 +1700,22 @@ local pattern = {
['.*bsd'] = 'bsdl',
['bzr_log%..*'] = 'bzr',
['.*enlightenment/.*%.cfg'] = 'c',
+ ['.*/%.calendar/.*'] = starsetf('calendar'),
+ ['.*/share/calendar/.*/calendar%..*'] = starsetf('calendar'),
+ ['.*/share/calendar/calendar%..*'] = starsetf('calendar'),
['.*/etc/defaults/cdrdao'] = 'cdrdaoconf',
['.*/etc/cdrdao%.conf'] = 'cdrdaoconf',
['.*/etc/default/cdrdao'] = 'cdrdaoconf',
['.*hgrc'] = 'cfg',
['.*%.%.ch'] = 'chill',
['.*%.cmake%.in'] = 'cmake',
+ -- */cmus/rc and */.cmus/rc
+ ['.*/%.?cmus/rc'] = 'cmusrc',
+ -- */cmus/*.theme and */.cmus/*.theme
+ ['.*/%.?cmus/.*%.theme'] = 'cmusrc',
+ ['.*/%.cmus/autosave'] = 'cmusrc',
+ ['.*/%.cmus/command%-history'] = 'cmusrc',
+ ['cvs%d+'] = 'cvs',
['.*/debian/changelog'] = 'debchangelog',
['.*/debian/control'] = 'debcontrol',
['.*/debian/copyright'] = 'debcopyright',
@@ -1392,7 +1725,7 @@ local pattern = {
['.*/etc/DIR_COLORS'] = 'dircolors',
['.*/etc/dnsmasq%.conf'] = 'dnsmasq',
['php%.ini%-.*'] = 'dosini',
- ['.*/etc/pacman%.conf'] = 'conf',
+ ['.*/etc/pacman%.conf'] = 'confini',
['.*/etc/yum%.conf'] = 'dosini',
['.*lvs'] = 'dracula',
['.*lpe'] = 'dracula',
@@ -1405,11 +1738,26 @@ local pattern = {
['.*/%.config/git/config'] = 'gitconfig',
['.*%.git/config%.worktree'] = 'gitconfig',
['.*%.git/worktrees/.*/config%.worktree'] = 'gitconfig',
+ ['.*/git/config'] = function(path, bufnr)
+ if vim.env.XDG_CONFIG_HOME and path:find(vim.env.XDG_CONFIG_HOME .. '/git/config') then
+ return 'gitconfig'
+ end
+ end,
['%.gitsendemail%.msg%.......'] = 'gitsendemail',
['gkrellmrc_.'] = 'gkrellmrc',
['.*/usr/.*/gnupg/options%.skel'] = 'gpg',
['.*/%.gnupg/options'] = 'gpg',
['.*/%.gnupg/gpg%.conf'] = 'gpg',
+ ['.*/options'] = function(path, bufnr)
+ if vim.env.GNUPGHOME and path:find(vim.env.GNUPGHOME .. '/options') then
+ return 'gpg'
+ end
+ end,
+ ['.*/gpg%.conf'] = function(path, bufnr)
+ if vim.env.GNUPGHOME and path:find(vim.env.GNUPGHOME .. '/gpg%.conf') then
+ return 'gpg'
+ end
+ end,
['.*/etc/group'] = 'group',
['.*/etc/gshadow'] = 'group',
['.*/etc/group%.edit'] = 'group',
@@ -1421,6 +1769,7 @@ local pattern = {
['.*/boot/grub/grub%.conf'] = 'grub',
['.*/boot/grub/menu%.lst'] = 'grub',
['.*/etc/grub%.conf'] = 'grub',
+ [vim.env.VIMRUNTIME .. '/doc/.*%.txt'] = 'help',
['hg%-editor%-.*%.txt'] = 'hgcommit',
['.*/etc/host%.conf'] = 'hostconf',
['.*/etc/hosts%.deny'] = 'hostsaccess',
@@ -1475,9 +1824,16 @@ local pattern = {
['.*/etc/passwd'] = 'passwd',
['.*/etc/passwd%.edit'] = 'passwd',
['.*/etc/shadow-'] = 'passwd',
+ ['.*%.php%d'] = 'php',
['.*/%.pinforc'] = 'pinfo',
['.*/etc/pinforc'] = 'pinfo',
['.*/etc/protocols'] = 'protocols',
+ ['.*printcap.*'] = starsetf(function(path, bufnr)
+ if vim.fn.did_filetype() == 0 then
+ vim.b[bufnr].ptcap_type = 'print'
+ return 'ptcap'
+ end
+ end),
['.*baseq[2-3]/.*%.cfg'] = 'quake',
['.*quake[1-3]/.*%.cfg'] = 'quake',
['.*id1/.*%.cfg'] = 'quake',
@@ -1502,9 +1858,33 @@ local pattern = {
['.*%.swift%.gyb'] = 'swiftgyb',
['.*/etc/sysctl%.conf'] = 'sysctl',
['.*/etc/sysctl%.d/.*%.conf'] = 'sysctl',
+ ['.*/systemd/.*%.automount'] = 'systemd',
+ ['.*/systemd/.*%.dnssd'] = 'systemd',
+ ['.*/systemd/.*%.link'] = 'systemd',
+ ['.*/systemd/.*%.mount'] = 'systemd',
+ ['.*/systemd/.*%.netdev'] = 'systemd',
+ ['.*/systemd/.*%.network'] = 'systemd',
+ ['.*/systemd/.*%.nspawn'] = 'systemd',
+ ['.*/systemd/.*%.path'] = 'systemd',
+ ['.*/systemd/.*%.service'] = 'systemd',
+ ['.*/systemd/.*%.slice'] = 'systemd',
+ ['.*/systemd/.*%.socket'] = 'systemd',
+ ['.*/systemd/.*%.swap'] = 'systemd',
+ ['.*/systemd/.*%.target'] = 'systemd',
+ ['.*/systemd/.*%.timer'] = 'systemd',
['.*/etc/systemd/.*%.conf%.d/.*%.conf'] = 'systemd',
['.*/%.config/systemd/user/.*%.d/.*%.conf'] = 'systemd',
['.*/etc/systemd/system/.*%.d/.*%.conf'] = 'systemd',
+ ['.*/etc/systemd/system/.*%.d/%.#.*'] = 'systemd',
+ ['.*/etc/systemd/system/%.#.*'] = 'systemd',
+ ['.*/%.config/systemd/user/.*%.d/%.#.*'] = 'systemd',
+ ['.*/%.config/systemd/user/%.#.*'] = 'systemd',
+ ['.*termcap.*'] = starsetf(function(path, bufnr)
+ if vim.fn.did_filetype() == 0 then
+ vim.b[bufnr].ptcap_type = 'term'
+ return 'ptcap'
+ end
+ end),
['.*%.t%.html'] = 'tilde',
['%.?tmux.*%.conf'] = 'tmux',
['%.?tmux.*%.conf.*'] = { 'tmux', { priority = -1 } },
@@ -1550,7 +1930,19 @@ local pattern = {
['%.login.*'] = function(path, bufnr)
return require('vim.filetype.detect').csh(path, bufnr)
end,
- ['%.neomuttrc.*'] = starsetf('neomuttrc'),
+ ['Muttrc.*'] = starsetf('muttrc'),
+ ['Muttngrc.*'] = starsetf('muttrc'),
+ -- muttrc* and .muttrc*
+ ['%.?muttrc.*'] = starsetf('muttrc'),
+ -- muttngrc* and .muttngrc*
+ ['%.?muttngrc.*'] = starsetf('muttrc'),
+ ['.*/%.mutt/muttrc.*'] = starsetf('muttrc'),
+ ['.*/%.muttng/muttrc.*'] = starsetf('muttrc'),
+ ['.*/%.muttng/muttngrc.*'] = starsetf('muttrc'),
+ ['Neomuttrc.*'] = starsetf('neomuttrc'),
+ -- neomuttrc* and .neomuttrc*
+ ['%.?neomuttrc.*'] = starsetf('neomuttrc'),
+ ['.*/%.neomutt/neomuttrc.*'] = starsetf('neomuttrc'),
['%.profile.*'] = function(path, bufnr)
return require('vim.filetype.detect').sh(path, bufnr, getline(bufnr, 1))
end,
@@ -1573,8 +1965,15 @@ local pattern = {
['.*%.properties_.._.._.*'] = starsetf('jproperties'),
['.*%.vhdl_[0-9].*'] = starsetf('vhdl'),
['.*/%.fvwm/.*'] = starsetf('fvwm'),
+ ['.*fvwmrc.*'] = starsetf(function(path, bufnr)
+ vim.b[bufnr].fvwm_version = 1
+ return 'fvwm'
+ end),
+ ['.*fvwm95.*%.hook'] = starsetf(function(path, bufnr)
+ vim.b[bufnr].fvwm_version = 1
+ return 'fvwm'
+ end),
['.*/%.gitconfig%.d/.*'] = starsetf('gitconfig'),
- ['.*/%.neomutt/neomuttrc.*'] = starsetf('neomuttrc'),
['.*/Xresources/.*'] = starsetf('xdefaults'),
['.*/app%-defaults/.*'] = starsetf('xdefaults'),
['.*/bind/db%..*'] = starsetf('bindzone'),
@@ -1594,6 +1993,11 @@ local pattern = {
['.*/etc/httpd/sites%-.*/.*'] = starsetf('apache'),
['.*/etc/logcheck/.*%.d.*/.*'] = starsetf('logcheck'),
['.*/etc/modprobe%..*'] = starsetf('modconf'),
+ ['.*/etc/modutils/.*'] = starsetf(function(path, bufnr)
+ if vim.fn.executable(vim.fn.expand(path)) ~= 1 then
+ return 'modconf'
+ end
+ end),
['.*/etc/pam%.d/.*'] = starsetf('pamconf'),
['.*/etc/profile'] = function(path, bufnr)
return require('vim.filetype.detect').sh(path, bufnr, getline(bufnr, 1))
@@ -1616,7 +2020,6 @@ local pattern = {
['Dockerfile%..*'] = starsetf('dockerfile'),
['JAM.*%..*'] = starsetf('jam'),
['Kconfig%..*'] = starsetf('kconfig'),
- ['Neomuttrc.*'] = starsetf('neomuttrc'),
['Prl.*%..*'] = starsetf('jam'),
['Xresources.*'] = starsetf('xdefaults'),
['[mM]akefile.*'] = starsetf('make'),
@@ -1633,7 +2036,8 @@ local pattern = {
['gtkrc.*'] = starsetf('gtkrc'),
['httpd%.conf.*'] = starsetf('apache'),
['lilo%.conf.*'] = starsetf('lilo'),
- ['neomuttrc.*'] = starsetf('neomuttrc'),
+ ['Muttrc'] = 'muttrc',
+ ['Muttngrc'] = 'muttrc',
['proftpd%.conf.*'] = starsetf('apachestyle'),
['reportbug%-.*'] = starsetf('mail'),
['sgml%.catalog.*'] = starsetf('catalog'),
@@ -1647,8 +2051,8 @@ local pattern = {
['%.article%.%d+'] = 'mail',
['pico%.%d+'] = 'mail',
['mutt%-.*%-%w+'] = 'mail',
- ['neomutt%-.*%-%w+'] = 'mail',
['muttng%-.*%-%w+'] = 'mail',
+ ['neomutt%-.*%-%w+'] = 'mail',
['mutt' .. string.rep('[%w_-]', 6)] = 'mail',
['neomutt' .. string.rep('[%w_-]', 6)] = 'mail',
['/tmp/SLRN[0-9A-Z.]+'] = 'mail',
@@ -1692,7 +2096,7 @@ local pattern = {
{ priority = -1 },
},
['.*%.[Dd][Aa][Tt]'] = function(path, bufnr)
- return require('vim.filetype.detect').dat(bufnr)
+ return require('vim.filetype.detect').dat(path, bufnr)
end,
['.*%.[Mm][Oo][Dd]'] = function(path, bufnr)
return require('vim.filetype.detect').mod(path, bufnr)
@@ -1707,11 +2111,65 @@ local pattern = {
['.*%.[Ss][Yy][Ss]'] = function(path, bufnr)
return require('vim.filetype.detect').sys(bufnr)
end,
+ ['%.?gitolite%.rc'] = 'perl',
+ ['example%.gitolite%.rc'] = 'perl',
-- Neovim only
['.*/queries/.*%.scm'] = 'query', -- tree-sitter queries
+ ['.*,v'] = 'rcs',
+ ['.*/xorg%.conf%.d/.*%.conf'] = function(path, bufnr)
+ vim.b[bufnr].xf86conf_xfree86_version = 4
+ return 'xf86config'
+ end,
+ -- Increase priority to run before the pattern below
+ ['XF86Config%-4'] = starsetf(function(path, bufnr)
+ vim.b[bufnr].xf86conf_xfree86_version = 4
+ return 'xf86config'
+ end, { priority = -math.huge + 1 }),
+ ['XF86Config.*'] = starsetf(function(path, bufnr)
+ vim.b[bufnr].xf86conf_xfree86_version = 4
+ return require('vim.filetype.detect').xf86conf(bufnr)
+ end),
+ ['[cC]hange[lL]og.*'] = starsetf(function(path, bufnr)
+ local line = getline(bufnr, 1):lower()
+ if line:find('; urgency=') then
+ return 'debchangelog'
+ else
+ return 'changelog'
+ end
+ end),
+ ['.*fvwm2rc.*'] = starsetf(function(path, bufnr)
+ if vim.fn.fnamemodify(path, ':e') == 'm4' then
+ return 'fvwm2m4'
+ else
+ vim.b[bufnr].fvwm_version = 2
+ return 'fvwm'
+ end
+ end),
+ ['.*%.[Ll][Oo][Gg]'] = function(path, bufnr)
+ -- Innovation Data Processing
+ -- (refactor of filetype.vim since the patterns are case-insensitive)
+ path = path:lower()
+ if M.findany(path, { 'upstream%.log', 'upstream%..*%.log', '.*%.upstream%.log', 'upstream%-.*%.log' }) then
+ return 'upstreamlog'
+ elseif M.findany(path, { 'upstreaminstall%.log', 'upstreaminstall%..*%.log', '.*%.upstreaminstall%.log' }) then
+ return 'upstreaminstalllog'
+ elseif M.findany(path, { 'usserver%.log', 'usserver%..*%.log', '.*%.usserver%.log' }) then
+ return 'usserverlog'
+ elseif M.findany(path, { 'usw2kagt%.log', 'usws2kagt%..*%.log', '.*%.usws2kagt%.log' }) then
+ return 'usw2kagtlog'
+ end
+ end,
+ -- Ignored extension
+ ['.*~'] = function(path, bufnr)
+ local short = path:gsub('~$', '', 1)
+ if path ~= short and short ~= '' then
+ return M.match(vim.fn.fnameescape(short), bufnr)
+ end
+ end,
-- END PATTERN
}
-- luacheck: pop
+-- luacheck: pop
---@private
local function sort_by_priority(t)
diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua
index f195693dcf..2cc75b026e 100644
--- a/runtime/lua/vim/filetype/detect.lua
+++ b/runtime/lua/vim/filetype/detect.lua
@@ -16,56 +16,10 @@
local M = {}
----@private
-local function getlines(bufnr, start_lnum, end_lnum)
- if not end_lnum then
- -- Return a single line as a string
- return vim.api.nvim_buf_get_lines(bufnr, start_lnum - 1, start_lnum, false)[1]
- end
- return vim.api.nvim_buf_get_lines(bufnr, start_lnum - 1, end_lnum, false)
-end
-
----@private
-local function findany(s, patterns)
- if s == nil then
- return false
- end
- for _, v in ipairs(patterns) do
- if s:find(v) then
- return true
- end
- end
- return false
-end
-
----@private
-local function nextnonblank(bufnr, start_lnum)
- for _, line in ipairs(getlines(bufnr, start_lnum, -1)) do
- if not line:find('^%s*$') then
- return line
- end
- end
- return nil
-end
-
----@private
-local matchregex = (function()
- local cache = {}
- return function(line, pattern)
- if line == nil then
- return nil
- end
- if not cache[pattern] then
- cache[pattern] = vim.regex(pattern)
- end
- return cache[pattern]:match_str(line)
- end
-end)()
-
----@private
-local did_filetype = function()
- return vim.fn.did_filetype() ~= 0
-end
+local getlines = vim.filetype.getlines
+local findany = vim.filetype.findany
+local nextnonblank = vim.filetype.nextnonblank
+local matchregex = vim.filetype.matchregex
-- luacheck: push no unused args
-- luacheck: push ignore 122
@@ -200,7 +154,7 @@ function M.change(bufnr)
end
function M.csh(path, bufnr)
- if did_filetype() then
+ if vim.fn.did_filetype() ~= 0 then
-- Filetype was already detected
return
end
@@ -213,11 +167,15 @@ function M.csh(path, bufnr)
end
end
--- Determine if a *.dat file is Kuka Robot Language
-function M.dat(bufnr)
+function M.dat(path, bufnr)
+ -- Innovation data processing
+ if findany(path:lower(), { '^upstream%.dat$', '^upstream%..*%.dat$', '^.*%.upstream%.dat$' }) then
+ return 'upstreamdat'
+ end
if vim.g.filetype_dat then
return vim.g.filetype_dat
end
+ -- Determine if a *.dat file is Kuka Robot Language
local line = nextnonblank(bufnr, 1)
if matchregex(line, [[\c\v^\s*%(\&\w+|defdat>)]]) then
return 'krl'
@@ -257,7 +215,7 @@ function M.dep3patch(path, bufnr)
end
function M.dtrace(bufnr)
- if did_filetype() then
+ if vim.fn.did_filetype() ~= 0 then
-- Filetype was already detected
return
end
@@ -815,7 +773,7 @@ end
-- Also called from filetype.lua
function M.sh(path, bufnr, name)
- if did_filetype() or path:find(vim.g.ft_ignore_pat) then
+ if vim.fn.did_filetype() ~= 0 or path:find(vim.g.ft_ignore_pat) then
-- Filetype was already detected or detection should be skipped
return
end
@@ -848,7 +806,7 @@ end
-- For shell-like file types, check for an "exec" command hidden in a comment, as used for Tcl.
-- Also called from scripts.vim, thus can't be local to this script. [TODO]
function M.shell(path, bufnr, name)
- if did_filetype() or matchregex(path, vim.g.ft_ignore_pat) then
+ if vim.fn.did_filetype() ~= 0 or matchregex(path, vim.g.ft_ignore_pat) then
-- Filetype was already detected or detection should be skipped
return
end
@@ -983,6 +941,120 @@ function M.y(bufnr)
return 'yacc'
end
+function M.decl(bufnr)
+ for _, line in ipairs(getlines(bufnr, 1, 3)) do
+ if line:lower():find('^<!sgml') then
+ return 'sgmldecl'
+ end
+ end
+end
+
+function M.sgml(bufnr)
+ local lines = table.concat(getlines(bufnr, 1, 5))
+ if lines:find('linuxdoc') then
+ return 'smgllnx'
+ elseif lines:find('<!DOCTYPE.*DocBook') then
+ vim.b[bufnr].docbk_type = 'sgml'
+ vim.b[bufnr].docbk_ver = 4
+ return 'docbk'
+ else
+ return 'sgml'
+ end
+end
+
+function M.web(bufnr)
+ for _, line in ipairs(getlines(bufnr, 1, 5)) do
+ if line:find('^%%') then
+ return 'web'
+ end
+ end
+ return 'winbatch'
+end
+
+function M.rul(bufnr)
+ if table.concat(getlines(bufnr, 1, 6)):lower():find('installshield') then
+ return 'ishd'
+ end
+ return 'diva'
+end
+
+function M.asp(bufnr)
+ if vim.g.filetype_asp then
+ return vim.g.filetype_asp
+ elseif table.concat(getlines(bufnr, 1, 3)):lower():find('perlscript') then
+ return 'aspperl'
+ else
+ return 'aspvbs'
+ end
+end
+
+function M.edn(bufnr)
+ local line = getlines(bufnr, 1)
+ if matchregex(line, [[\c^\s*(\s*edif\>]]) then
+ return 'edif'
+ else
+ return 'clojure'
+ end
+end
+
+function M.smi(bufnr)
+ local line = getlines(bufnr, 1)
+ if matchregex(line, [[\c\<smil\>]]) then
+ return 'smil'
+ else
+ return 'mib'
+ end
+end
+
+function M.hw(bufnr)
+ if getlines(bufnr, 1):lower():find('<%?php') then
+ return 'php'
+ end
+ return 'virata'
+end
+
+function M.news(bufnr)
+ if getlines(bufnr, 1):lower():find('; urgency=') then
+ return 'debchangelog'
+ end
+end
+
+-- Debian Control
+function M.control(bufnr)
+ if getlines(bufnr, 1):find('^Source:') then
+ return 'debcontrol'
+ end
+end
+
+-- Debian Copyright
+function M.copyright(bufnr)
+ if getlines(bufnr, 1):find('^Format:') then
+ return 'debcopyright'
+ end
+end
+
+-- Software Distributor Product Specification File (POSIX 1387.2-1995)
+function M.psf(bufnr)
+ local line = getlines(bufnr, 1):lower()
+ if
+ findany(
+ line,
+ { '^%s*distribution%s*$', '^%s*installed_software%s*$', '^%s*root%s*$', '^%s*bundle%s*$', '^%s*product%s*$' }
+ )
+ then
+ return 'psf'
+ end
+end
+
+-- XFree86 config
+function M.xfree86(bufnr)
+ local line = getlines(bufnr, 1)
+ if matchregex(line, [[\<XConfigurator\>]]) then
+ vim.b[bufnr].xf86conf_xfree86_version = 3
+ return 'xf86conf'
+ end
+end
+
-- luacheck: pop
-- luacheck: pop
diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua
new file mode 100644
index 0000000000..6f9c48ca24
--- /dev/null
+++ b/runtime/lua/vim/fs.lua
@@ -0,0 +1,208 @@
+local M = {}
+
+--- Iterate over all the parents of the given file or directory.
+---
+--- Example:
+--- <pre>
+--- local root_dir
+--- for dir in vim.fs.parents(vim.api.nvim_buf_get_name(0)) do
+--- if vim.fn.isdirectory(dir .. "/.git") == 1 then
+--- root_dir = dir
+--- break
+--- end
+--- end
+---
+--- if root_dir then
+--- print("Found git repository at", root_dir)
+--- end
+--- </pre>
+---
+---@param start (string) Initial file or directory.
+---@return (function) Iterator
+function M.parents(start)
+ return function(_, dir)
+ local parent = M.dirname(dir)
+ if parent == dir then
+ return nil
+ end
+
+ return parent
+ end,
+ nil,
+ start
+end
+
+--- Return the parent directory of the given file or directory
+---
+---@param file (string) File or directory
+---@return (string) Parent directory of {file}
+function M.dirname(file)
+ if file == nil then
+ return nil
+ end
+ return vim.fn.fnamemodify(file, ':h')
+end
+
+--- Return the basename of the given file or directory
+---
+---@param file (string) File or directory
+---@return (string) Basename of {file}
+function M.basename(file)
+ return vim.fn.fnamemodify(file, ':t')
+end
+
+--- Return an iterator over the files and directories located in {path}
+---
+---@param path (string) An absolute or relative path to the directory to iterate
+--- over. The path is first normalized |vim.fs.normalize()|.
+---@return Iterator over files and directories in {path}. Each iteration yields
+--- two values: name and type. Each "name" is the basename of the file or
+--- directory relative to {path}. Type is one of "file" or "directory".
+function M.dir(path)
+ return function(fs)
+ return vim.loop.fs_scandir_next(fs)
+ end, vim.loop.fs_scandir(M.normalize(path))
+end
+
+--- Find files or directories in the given path.
+---
+--- Finds any files or directories given in {names} starting from {path}. If
+--- {upward} is "true" then the search traverses upward through parent
+--- directories; otherwise, the search traverses downward. Note that downward
+--- searches are recursive and may search through many directories! If {stop}
+--- is non-nil, then the search stops when the directory given in {stop} is
+--- reached. The search terminates when {limit} (default 1) matches are found.
+--- The search can be narrowed to find only files or or only directories by
+--- specifying {type} to be "file" or "directory", respectively.
+---
+---@param names (string|table) Names of the files and directories to find. Must
+--- be base names, paths and globs are not supported.
+---@param opts (table) Optional keyword arguments:
+--- - path (string): Path to begin searching from. If
+--- omitted, the current working directory is used.
+--- - upward (boolean, default false): If true, search
+--- upward through parent directories. Otherwise,
+--- search through child directories
+--- (recursively).
+--- - stop (string): Stop searching when this directory is
+--- reached. The directory itself is not searched.
+--- - type (string): Find only files ("file") or
+--- directories ("directory"). If omitted, both
+--- files and directories that match {name} are
+--- included.
+--- - limit (number, default 1): Stop the search after
+--- finding this many matches. Use `math.huge` to
+--- place no limit on the number of matches.
+---@return (table) The paths of all matching files or directories
+function M.find(names, opts)
+ opts = opts or {}
+ vim.validate({
+ names = { names, { 's', 't' } },
+ path = { opts.path, 's', true },
+ upward = { opts.upward, 'b', true },
+ stop = { opts.stop, 's', true },
+ type = { opts.type, 's', true },
+ limit = { opts.limit, 'n', true },
+ })
+
+ names = type(names) == 'string' and { names } or names
+
+ local path = opts.path or vim.loop.cwd()
+ local stop = opts.stop
+ local limit = opts.limit or 1
+
+ local matches = {}
+
+ ---@private
+ local function add(match)
+ matches[#matches + 1] = match
+ if #matches == limit then
+ return true
+ end
+ end
+
+ if opts.upward then
+ ---@private
+ local function test(p)
+ local t = {}
+ for _, name in ipairs(names) do
+ local f = p .. '/' .. name
+ local stat = vim.loop.fs_stat(f)
+ if stat and (not opts.type or opts.type == stat.type) then
+ t[#t + 1] = f
+ end
+ end
+
+ return t
+ end
+
+ for _, match in ipairs(test(path)) do
+ if add(match) then
+ return matches
+ end
+ end
+
+ for parent in M.parents(path) do
+ if stop and parent == stop then
+ break
+ end
+
+ for _, match in ipairs(test(parent)) do
+ if add(match) then
+ return matches
+ end
+ end
+ end
+ else
+ local dirs = { path }
+ while #dirs > 0 do
+ local dir = table.remove(dirs, 1)
+ if stop and dir == stop then
+ break
+ end
+
+ for other, type in M.dir(dir) do
+ local f = dir .. '/' .. other
+ for _, name in ipairs(names) do
+ if name == other and (not opts.type or opts.type == type) then
+ if add(f) then
+ return matches
+ end
+ end
+ end
+
+ if type == 'directory' then
+ dirs[#dirs + 1] = f
+ end
+ end
+ end
+ end
+
+ return matches
+end
+
+--- Normalize a path to a standard format. A tilde (~) character at the
+--- beginning of the path is expanded to the user's home directory and any
+--- backslash (\\) characters are converted to forward slashes (/). Environment
+--- variables are also expanded.
+---
+--- Example:
+--- <pre>
+--- vim.fs.normalize('C:\\Users\\jdoe')
+--- => 'C:/Users/jdoe'
+---
+--- vim.fs.normalize('~/src/neovim')
+--- => '/home/jdoe/src/neovim'
+---
+--- vim.fs.normalize('$XDG_CONFIG_HOME/nvim/init.vim')
+--- => '/Users/jdoe/.config/nvim/init.vim'
+--- </pre>
+---
+---@param path (string) Path to normalize
+---@return (string) Normalized path
+function M.normalize(path)
+ vim.validate({ path = { path, 's' } })
+ return (path:gsub('^~/', vim.env.HOME .. '/'):gsub('%$([%w_]+)', vim.env):gsub('\\', '/'))
+end
+
+return M
diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua
new file mode 100644
index 0000000000..b875da0abc
--- /dev/null
+++ b/runtime/lua/vim/health.lua
@@ -0,0 +1,49 @@
+local M = {}
+
+function M.report_start(msg)
+ vim.fn['health#report_start'](msg)
+end
+
+function M.report_info(msg)
+ vim.fn['health#report_info'](msg)
+end
+
+function M.report_ok(msg)
+ vim.fn['health#report_ok'](msg)
+end
+
+function M.report_warn(msg, ...)
+ vim.fn['health#report_warn'](msg, ...)
+end
+
+function M.report_error(msg, ...)
+ vim.fn['health#report_error'](msg, ...)
+end
+
+local path2name = function(path)
+ if path:match('%.lua$') then
+ -- Lua: transform "../lua/vim/lsp/health.lua" into "vim.lsp"
+ return path:gsub('.-lua[%\\%/]', '', 1):gsub('[%\\%/]', '.'):gsub('%.health.-$', '')
+ else
+ -- Vim: transform "../autoload/health/provider.vim" into "provider"
+ return vim.fn.fnamemodify(path, ':t:r')
+ end
+end
+
+local PATTERNS = { '/autoload/health/*.vim', '/lua/**/**/health.lua', '/lua/**/**/health/init.lua' }
+-- :checkhealth completion function used by ex_getln.c get_healthcheck_names()
+M._complete = function()
+ local names = vim.tbl_flatten(vim.tbl_map(function(pattern)
+ return vim.tbl_map(path2name, vim.api.nvim_get_runtime_file(pattern, true))
+ end, PATTERNS))
+ -- Remove duplicates
+ local unique = {}
+ vim.tbl_map(function(f)
+ unique[f] = true
+ end, names)
+ -- vim.health is this file, which is not a healthcheck
+ unique['vim'] = nil
+ return vim.tbl_keys(unique)
+end
+
+return M
diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua
index 22b67aee88..36e3c2ad20 100644
--- a/runtime/lua/vim/highlight.lua
+++ b/runtime/lua/vim/highlight.lua
@@ -64,6 +64,7 @@ function M.range(bufnr, ns, higroup, start, finish, opts)
end
local yank_ns = api.nvim_create_namespace('hlyank')
+local yank_timer
--- Highlight the yanked region
---
--- use from init.vim via
@@ -113,6 +114,9 @@ function M.on_yank(opts)
local bufnr = api.nvim_get_current_buf()
api.nvim_buf_clear_namespace(bufnr, yank_ns, 0, -1)
+ if yank_timer then
+ yank_timer:close()
+ end
local pos1 = vim.fn.getpos("'[")
local pos2 = vim.fn.getpos("']")
@@ -129,7 +133,8 @@ function M.on_yank(opts)
{ regtype = event.regtype, inclusive = event.inclusive, priority = M.priorities.user }
)
- vim.defer_fn(function()
+ yank_timer = vim.defer_fn(function()
+ yank_timer = nil
if api.nvim_buf_is_valid(bufnr) then
api.nvim_buf_clear_namespace(bufnr, yank_ns, 0, -1)
end
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index dac2860690..8bf736a2ca 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -4,6 +4,7 @@ local lsp_rpc = require('vim.lsp.rpc')
local protocol = require('vim.lsp.protocol')
local util = require('vim.lsp.util')
local sync = require('vim.lsp.sync')
+local api = vim.api
local vim = vim
local nvim_err_writeln, nvim_buf_get_lines, nvim_command, nvim_buf_get_option, nvim_exec_autocmds =
@@ -662,6 +663,77 @@ function lsp.client()
error()
end
+--- Create a new LSP client and start a language server or reuses an already
+--- running client if one is found matching `name` and `root_dir`.
+--- Attaches the current buffer to the client.
+---
+--- Example:
+---
+--- <pre>
+--- vim.lsp.start({
+--- name = 'my-server-name',
+--- cmd = {'name-of-language-server-executable'},
+--- root_dir = vim.fs.dirname(vim.fs.find({'pyproject.toml', 'setup.py'}, { upward = true })[1]),
+--- })
+--- </pre>
+---
+--- See |lsp.start_client| for all available options. The most important are:
+---
+--- `name` is an arbitrary name for the LSP client. It should be unique per
+--- language server.
+---
+--- `cmd` the command as list - used to start the language server.
+--- The command must be present in the `$PATH` environment variable or an
+--- absolute path to the executable. Shell constructs like `~` are *NOT* expanded.
+---
+--- `root_dir` path to the project root.
+--- By default this is used to decide if an existing client should be re-used.
+--- The example above uses |vim.fs.find| and |vim.fs.dirname| to detect the
+--- root by traversing the file system upwards starting
+--- from the current directory until either a `pyproject.toml` or `setup.py`
+--- file is found.
+---
+--- `workspace_folders` a list of { uri:string, name: string } tables.
+--- The project root folders used by the language server.
+--- If `nil` the property is derived from the `root_dir` for convenience.
+---
+--- Language servers use this information to discover metadata like the
+--- dependencies of your project and they tend to index the contents within the
+--- project folder.
+---
+---
+--- To ensure a language server is only started for languages it can handle,
+--- make sure to call |vim.lsp.start| within a |FileType| autocmd.
+--- Either use |:au|, |nvim_create_autocmd()| or put the call in a
+--- `ftplugin/<filetype_name>.lua` (See |ftplugin-name|)
+---
+---@param config table Same configuration as documented in |lsp.start_client()|
+---@param opts nil|table Optional keyword arguments:
+--- - reuse_client (fun(client: client, config: table): boolean)
+--- Predicate used to decide if a client should be re-used.
+--- Used on all running clients.
+--- The default implementation re-uses a client if name
+--- and root_dir matches.
+---@return number client_id
+function lsp.start(config, opts)
+ opts = opts or {}
+ local reuse_client = opts.reuse_client
+ or function(client, conf)
+ return client.config.root_dir == conf.root_dir and client.name == conf.name
+ end
+ config.name = config.name or (config.cmd[1] and vim.fs.basename(config.cmd[1])) or nil
+ local bufnr = api.nvim_get_current_buf()
+ for _, client in pairs(lsp.get_active_clients()) do
+ if reuse_client(client, config) then
+ lsp.buf_attach_client(bufnr, client.id)
+ return client.id
+ end
+ end
+ local client_id = lsp.start_client(config)
+ lsp.buf_attach_client(bufnr, client_id)
+ return client_id
+end
+
-- FIXME: DOC: Currently all methods on the `vim.lsp.client` object are
-- documented twice: Here, and on the methods themselves (e.g.
-- `client.request()`). This is a workaround for the vimdoc generator script
@@ -764,7 +836,7 @@ end
--- server in the initialize request. Invalid/empty values will default to "off"
---@param flags: A table with flags for the client. The current (experimental) flags are:
--- - allow_incremental_sync (bool, default true): Allow using incremental sync for buffer edits
---- - debounce_text_changes (number, default nil): Debounce didChange
+--- - debounce_text_changes (number, default 150): Debounce didChange
--- notifications to the server by the given number in milliseconds. No debounce
--- occurs if nil
--- - exit_timeout (number, default 500): Milliseconds to wait for server to
@@ -1045,6 +1117,10 @@ function lsp.start_client(config)
end
end
+ if next(config.settings) then
+ client.notify('workspace/didChangeConfiguration', { settings = config.settings })
+ end
+
if config.on_init then
local status, err = pcall(config.on_init, client, result)
if not status then
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index bcfaecdfcc..fa8ee23805 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -408,13 +408,13 @@ function M.rename(new_name, options)
local cword = vfn.expand('<cword>')
---@private
- local function get_text_at_range(range)
+ local function get_text_at_range(range, offset_encoding)
return vim.api.nvim_buf_get_text(
bufnr,
range.start.line,
- range.start.character,
+ util._get_line_byte_from_position(bufnr, range.start, offset_encoding),
range['end'].line,
- range['end'].character,
+ util._get_line_byte_from_position(bufnr, range['end'], offset_encoding),
{}
)[1]
end
@@ -461,9 +461,9 @@ function M.rename(new_name, options)
if result.placeholder then
prompt_opts.default = result.placeholder
elseif result.start then
- prompt_opts.default = get_text_at_range(result)
+ prompt_opts.default = get_text_at_range(result, client.offset_encoding)
elseif result.range then
- prompt_opts.default = get_text_at_range(result.range)
+ prompt_opts.default = get_text_at_range(result.range, client.offset_encoding)
else
prompt_opts.default = cword
end
@@ -752,7 +752,14 @@ local function on_code_action_results(results, ctx, options)
enriched_ctx.client_id = client.id
fn(command, enriched_ctx)
else
- M.execute_command(command)
+ -- Not using command directly to exclude extra properties,
+ -- see https://github.com/python-lsp/python-lsp-server/issues/146
+ local params = {
+ command = command.command,
+ arguments = command.arguments,
+ workDoneToken = command.workDoneToken,
+ }
+ client.request('workspace/executeCommand', params, nil, ctx.bufnr)
end
end
end
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
index 61cc89dcac..935f4b64f8 100644
--- a/runtime/lua/vim/lsp/handlers.lua
+++ b/runtime/lua/vim/lsp/handlers.lua
@@ -40,10 +40,12 @@ local function progress_handler(_, result, ctx, _)
if val.kind == 'begin' then
client.messages.progress[token] = {
title = val.title,
+ cancellable = val.cancellable,
message = val.message,
percentage = val.percentage,
}
elseif val.kind == 'report' then
+ client.messages.progress[token].cancellable = val.cancellable
client.messages.progress[token].message = val.message
client.messages.progress[token].percentage = val.percentage
elseif val.kind == 'end' then
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index 63e9342b1a..b041385c9c 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -1038,8 +1038,8 @@ function M.jump_to_location(location, offset_encoding, reuse_win)
if win then
api.nvim_set_current_win(win)
else
- api.nvim_set_current_buf(bufnr)
api.nvim_buf_set_option(bufnr, 'buflisted', true)
+ api.nvim_set_current_buf(bufnr)
end
local range = location.range or location.targetSelectionRange
local row = range.start.line
diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
index 57d8c5fd21..767573e345 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -261,22 +261,27 @@ end
---
--- Note, this call invalidates the tree and requires it to be parsed again.
---
----@param regions A list of regions this tree should manage and parse.
+---@param regions (table) list of regions this tree should manage and parse.
function LanguageTree:set_included_regions(regions)
- -- TODO(vigoux): I don't think string parsers are useful for now
- if type(self._source) == 'number' then
- -- Transform the tables from 4 element long to 6 element long (with byte offset)
- for _, region in ipairs(regions) do
- for i, range in ipairs(region) do
- if type(range) == 'table' and #range == 4 then
- local start_row, start_col, end_row, end_col = unpack(range)
+ -- Transform the tables from 4 element long to 6 element long (with byte offset)
+ for _, region in ipairs(regions) do
+ for i, range in ipairs(region) do
+ if type(range) == 'table' and #range == 4 then
+ local start_row, start_col, end_row, end_col = unpack(range)
+ local start_byte = 0
+ local end_byte = 0
+ -- TODO(vigoux): proper byte computation here, and account for EOL ?
+ if type(self._source) == 'number' then
-- Easy case, this is a buffer parser
- -- TODO(vigoux): proper byte computation here, and account for EOL ?
- local start_byte = a.nvim_buf_get_offset(self._source, start_row) + start_col
- local end_byte = a.nvim_buf_get_offset(self._source, end_row) + end_col
-
- region[i] = { start_row, start_col, start_byte, end_row, end_col, end_byte }
+ start_byte = a.nvim_buf_get_offset(self._source, start_row) + start_col
+ end_byte = a.nvim_buf_get_offset(self._source, end_row) + end_col
+ elseif type(self._source) == 'string' then
+ -- string parser, single `\n` delimited string
+ start_byte = vim.fn.byteidx(self._source, start_col)
+ end_byte = vim.fn.byteidx(self._source, end_col)
end
+
+ region[i] = { start_row, start_col, start_byte, end_row, end_col, end_byte }
end
end
end
@@ -295,6 +300,14 @@ function LanguageTree:included_regions()
return self._regions
end
+---@private
+local function get_node_range(node, id, metadata)
+ if metadata[id] and metadata[id].range then
+ return metadata[id].range
+ end
+ return { node:range() }
+end
+
--- Gets language injection points by language.
---
--- This is where most of the injection processing occurs.
@@ -327,10 +340,10 @@ function LanguageTree:_get_injections()
-- Allow for captured nodes to be used
if type(content) == 'number' then
- content = { match[content] }
+ content = { match[content]:range() }
end
- if content then
+ if type(content) == 'table' and #content >= 4 then
vim.list_extend(ranges, content)
end
end
@@ -351,7 +364,7 @@ function LanguageTree:_get_injections()
elseif name == 'combined' then
combined = true
elseif name == 'content' and #ranges == 0 then
- table.insert(ranges, node)
+ table.insert(ranges, get_node_range(node, id, metadata))
-- Ignore any tags that start with "_"
-- Allows for other tags to be used in matches
elseif string.sub(name, 1, 1) ~= '_' then
@@ -360,7 +373,7 @@ function LanguageTree:_get_injections()
end
if #ranges == 0 then
- table.insert(ranges, node)
+ table.insert(ranges, get_node_range(node, id, metadata))
end
end
end
@@ -397,7 +410,10 @@ function LanguageTree:_get_injections()
for _, entry in pairs(patterns) do
if entry.combined then
- table.insert(result[lang], vim.tbl_flatten(entry.regions))
+ local regions = vim.tbl_map(function(e)
+ return vim.tbl_flatten(e)
+ end, entry.regions)
+ table.insert(result[lang], regions)
else
for _, ranges in ipairs(entry.regions) do
table.insert(result[lang], ranges)
diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua
index 3c4c8fdb96..0cc2b6d2a4 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -313,20 +313,22 @@ local directive_handlers = {
['set!'] = function(_, _, _, pred, metadata)
if #pred == 4 then
-- (#set! @capture "key" "value")
- local capture = pred[2]
- if not metadata[capture] then
- metadata[capture] = {}
+ local _, capture_id, key, value = unpack(pred)
+ if not metadata[capture_id] then
+ metadata[capture_id] = {}
end
- metadata[capture][pred[3]] = pred[4]
+ metadata[capture_id][key] = value
else
+ local _, key, value = unpack(pred)
-- (#set! "key" "value")
- metadata[pred[2]] = pred[3]
+ metadata[key] = value
end
end,
-- Shifts the range of a node.
-- Example: (#offset! @_node 0 1 0 -1)
['offset!'] = function(match, _, _, pred, metadata)
- local offset_node = match[pred[2]]
+ local capture_id = pred[2]
+ local offset_node = match[capture_id]
local range = { offset_node:range() }
local start_row_offset = pred[3] or 0
local start_col_offset = pred[4] or 0
@@ -340,7 +342,10 @@ local directive_handlers = {
-- If this produces an invalid range, we just skip it.
if range[1] < range[3] or (range[1] == range[3] and range[2] <= range[4]) then
- metadata.content = { range }
+ if not metadata[capture_id] then
+ metadata[capture_id] = {}
+ end
+ metadata[capture_id].range = range
end
end,
}
@@ -360,9 +365,14 @@ end
--- Adds a new directive to be used in queries
---
+--- Handlers can set match level data by setting directly on the
+--- metadata object `metadata.key = value`, additionally, handlers
+--- can set node level data by using the capture id on the
+--- metadata table `metadata[capture_id].key = value`
+---
---@param name the name of the directive, without leading #
---@param handler the handler function to be used
---- signature will be (match, pattern, bufnr, predicate)
+--- signature will be (match, pattern, bufnr, predicate, metadata)
function M.add_directive(name, handler, force)
if directive_handlers[name] and not force then
error(string.format('Overriding %s', name))
@@ -371,6 +381,7 @@ function M.add_directive(name, handler, force)
directive_handlers[name] = handler
end
+--- Lists the currently available directives to use in queries.
---@return The list of supported directives.
function M.list_directives()
return vim.tbl_keys(directive_handlers)
diff --git a/runtime/optwin.vim b/runtime/optwin.vim
index 54f57a260c..228336e5a4 100644
--- a/runtime/optwin.vim
+++ b/runtime/optwin.vim
@@ -469,7 +469,7 @@ endif
call <SID>Header("multiple windows")
-call append("$", "laststatus\t0, 1 or 2; when to use a status line for the last window")
+call append("$", "laststatus\t0, 1, 2 or 3; when to use a status line for the last window")
call append("$", " \tset ls=" . &ls)
if has("statusline")
call append("$", "statusline\talternate format to be used for a status line")
diff --git a/runtime/plugin/rplugin.vim b/runtime/plugin/rplugin.vim
index 122d8d47f8..52f4bd05bc 100644
--- a/runtime/plugin/rplugin.vim
+++ b/runtime/plugin/rplugin.vim
@@ -62,4 +62,6 @@ endfunction
command! -bar UpdateRemotePlugins call remote#host#UpdateRemotePlugins()
-call s:LoadRemotePlugins()
+if index(v:argv, "--clean") < 0
+ call s:LoadRemotePlugins()
+endif
diff --git a/runtime/syntax/confini.vim b/runtime/syntax/confini.vim
new file mode 100644
index 0000000000..823d417a81
--- /dev/null
+++ b/runtime/syntax/confini.vim
@@ -0,0 +1,12 @@
+" Vim syntax file
+" Language: confini
+
+" Quit if a syntax file was already loaded
+if exists("b:current_syntax")
+ finish
+endif
+
+" Use the cfg syntax for now, it's similar.
+runtime! syntax/cfg.vim
+
+let b:current_syntax = 'confini'
diff --git a/runtime/syntax/i3config.vim b/runtime/syntax/i3config.vim
index 6163da29eb..0018081da7 100644
--- a/runtime/syntax/i3config.vim
+++ b/runtime/syntax/i3config.vim
@@ -3,7 +3,7 @@
" Original Author: Mohamed Boughaba <mohamed dot bgb at gmail dot com>
" Maintainer: Quentin Hibon (github user hiqua)
" Version: 0.4
-" Last Change: 2022 May 05
+" Last Change: 2022 Jun 05
" References:
" http://i3wm.org/docs/userguide.html#configuring
@@ -17,9 +17,6 @@ endif
scriptencoding utf-8
-" Error
-syn match i3ConfigError /.*/
-
" Todo
syn keyword i3ConfigTodo TODO FIXME XXX contained
@@ -180,13 +177,12 @@ syn match i3ConfigDrawingMarks /^\s*show_marks\s\+\(yes\|no\)\s\?$/ contains=i3C
" Group mode/bar
syn keyword i3ConfigBlockKeyword mode bar colors i3bar_command status_command position exec mode hidden_state modifier id position output background statusline tray_output tray_padding separator separator_symbol workspace_min_width workspace_buttons strip_workspace_numbers binding_mode_indicator focused_workspace active_workspace inactive_workspace urgent_workspace binding_mode contained
-syn region i3ConfigBlock start=+.*s\?{$+ end=+^}$+ contains=i3ConfigBlockKeyword,i3ConfigString,i3ConfigBind,i3ConfigComment,i3ConfigFont,i3ConfigFocusWrappingType,i3ConfigColor,i3ConfigVariable transparent keepend extend
+syn region i3ConfigBlock start=+^\s*[^#]*s\?{$+ end=+^\s*[^#]*}$+ contains=i3ConfigBlockKeyword,i3ConfigString,i3ConfigBind,i3ConfigComment,i3ConfigFont,i3ConfigFocusWrappingType,i3ConfigColor,i3ConfigVariable transparent keepend extend
" Line continuation
syn region i3ConfigLineCont start=/^.*\\$/ end=/^.*$/ contains=i3ConfigBlockKeyword,i3ConfigString,i3ConfigBind,i3ConfigComment,i3ConfigFont,i3ConfigFocusWrappingType,i3ConfigColor,i3ConfigVariable transparent keepend extend
" Define the highlighting.
-hi def link i3ConfigError Error
hi def link i3ConfigTodo Todo
hi def link i3ConfigComment Comment
hi def link i3ConfigFontContent Type
diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py
index 755749cef6..790c2ba52d 100755
--- a/scripts/gen_vimdoc.py
+++ b/scripts/gen_vimdoc.py
@@ -134,6 +134,7 @@ CONFIG = {
'ui.lua',
'filetype.lua',
'keymap.lua',
+ 'fs.lua',
],
'files': [
'runtime/lua/vim/_editor.lua',
@@ -142,6 +143,7 @@ CONFIG = {
'runtime/lua/vim/ui.lua',
'runtime/lua/vim/filetype.lua',
'runtime/lua/vim/keymap.lua',
+ 'runtime/lua/vim/fs.lua',
],
'file_patterns': '*.lua',
'fn_name_prefix': '',
@@ -167,6 +169,7 @@ CONFIG = {
'ui': 'vim.ui',
'filetype': 'vim.filetype',
'keymap': 'vim.keymap',
+ 'fs': 'vim.fs',
},
'append_only': [
'shared.lua',
diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh
index 8235949156..472b79cbf4 100755
--- a/scripts/vim-patch.sh
+++ b/scripts/vim-patch.sh
@@ -4,6 +4,8 @@ set -e
set -u
# Use privileged mode, which e.g. skips using CDPATH.
set -p
+# https://www.shellcheck.net/wiki/SC2031
+shopt -s lastpipe
# Ensure that the user has a bash that supports -A
if [[ "${BASH_VERSINFO[0]}" -lt 4 ]]; then
@@ -345,7 +347,7 @@ stage_patch() {
See the wiki for more information:
* https://github.com/neovim/neovim/wiki/Merging-patches-from-upstream-vim
-' "${vim_version}" "${BASENAME}" "${BASENAME}"
+' "${vim_version}" "${BASENAME}" "${BASENAME}" "${BASENAME}"
return $ret
}
diff --git a/scripts/windows.ti b/scripts/windows.ti
index 4f4832b786..c3a367e6d4 100644
--- a/scripts/windows.ti
+++ b/scripts/windows.ti
@@ -34,7 +34,7 @@ conemu|ANIS X3.64 and Xterm 256 colors for ConEmu with libuv,
smcup=\E[?1049h, rmir@, rmkx@, rmm@, rs1@, rs2@,
setab=\E[48;5;%p1%dm, setaf=\E[38;5;%p1%dm,
sgr=\E[0%?%p1%p3%|%t;7%;%?%p2%t;4%;%?%p6%t;1%;m,
- sgr0=\E[0m, smam@, smir@, smkx@, smm@, tbc@, u6@, u7@, u8@, u9@,
+ sgr0=\E[0m, smam@, smglr@, smir@, smkx@, smm@, tbc@, u6@, u7@, u8@, u9@,
Cr@, Cs@, Ms@, XM@, kDC3@, kDC4@, kDC5@, kDC6@, kDC7@,
kDN@, kDN3@, kDN4@, kDN5@, kDN6@, kDN7@,
kEND3@, kEND4@, kEND5@, kEND6@, kEND7@,
@@ -57,7 +57,7 @@ vtpcon|ANIS emulation for console virtual terminal sequence with libuv,
mc0@, mc4@, mc5@, meml@, memu@, oc@, rmam@, rmcup=\E[?1049l,
smcup=\E[?1049h, rmir@, rmkx@, rmm@, rs1@, rs2@,
sgr=\E[0%?%p1%p3%|%t;7%;%?%p2%t;4%;%?%p6%t;1%;m,
- sgr0=\E[0m, smam@, smir@, smkx@, smm@, tbc@, u6@, u7@, u8@, u9@,
+ sgr0=\E[0m, smam@, smglr@, smir@, smkx@, smm@, tbc@, u6@, u7@, u8@, u9@,
Cr@, Cs@, Ms@, XM@, kDC3@, kDC4@, kDC5@, kDC6@, kDC7@,
kDN@, kDN3@, kDN4@, kDN5@, kDN6@, kDN7@,
kEND3@, kEND4@, kEND5@, kEND6@, kEND7@,
diff --git a/src/mpack/mpack_core.c b/src/mpack/mpack_core.c
index f8ca63b7a3..4ee67a032a 100644
--- a/src/mpack/mpack_core.c
+++ b/src/mpack/mpack_core.c
@@ -12,8 +12,6 @@
# define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
#endif
-static int mpack_rtoken(const char **buf, size_t *buflen,
- mpack_token_t *tok);
static int mpack_rpending(const char **b, size_t *nl, mpack_tokbuf_t *tb);
static int mpack_rvalue(mpack_token_type_t t, mpack_uint32_t l,
const char **b, size_t *bl, mpack_token_t *tok);
@@ -52,7 +50,10 @@ MPACK_API int mpack_read(mpack_tokbuf_t *tokbuf, const char **buf,
int status;
size_t initial_ppos, ptrlen, advanced;
const char *ptr, *ptr_save;
- assert(*buf && *buflen);
+ assert(*buf);
+ if (*buflen == 0) {
+ return MPACK_EOF;
+ }
if (tokbuf->passthrough) {
/* pass data from str/bin/ext directly as a MPACK_TOKEN_CHUNK, adjusting
@@ -170,8 +171,7 @@ MPACK_API int mpack_write(mpack_tokbuf_t *tokbuf, char **buf, size_t *buflen,
return MPACK_OK;
}
-static int mpack_rtoken(const char **buf, size_t *buflen,
- mpack_token_t *tok)
+int mpack_rtoken(const char **buf, size_t *buflen, mpack_token_t *tok)
{
unsigned char t = ADVANCE(buf, buflen);
if (t < 0x80) {
diff --git a/src/mpack/mpack_core.h b/src/mpack/mpack_core.h
index 9edd13c41e..1d601bc82d 100644
--- a/src/mpack/mpack_core.h
+++ b/src/mpack/mpack_core.h
@@ -83,5 +83,7 @@ MPACK_API int mpack_read(mpack_tokbuf_t *tb, const char **b, size_t *bl,
mpack_token_t *tok) FUNUSED FNONULL;
MPACK_API int mpack_write(mpack_tokbuf_t *tb, char **b, size_t *bl,
const mpack_token_t *tok) FUNUSED FNONULL;
+int mpack_rtoken(const char **buf, size_t *buflen,
+ mpack_token_t *tok);
#endif /* MPACK_CORE_H */
diff --git a/src/nvim/README.md b/src/nvim/README.md
index 9417629691..91fb3ca2f6 100644
--- a/src/nvim/README.md
+++ b/src/nvim/README.md
@@ -23,7 +23,7 @@ Logs
Low-level log messages sink to `$NVIM_LOG_FILE`.
-UI events are logged at DEBUG level (`DEBUG_LOG_LEVEL`).
+UI events are logged at DEBUG level (`LOGLVL_DBG`).
rm -rf build/
make CMAKE_EXTRA_FLAGS="-DMIN_LOG_LEVEL=0"
@@ -204,9 +204,14 @@ Then you can compare `bar` with another session, to debug TUI behavior.
### TUI redraw
-Set the 'writedelay' option to see where and when the UI is painted.
+Set the 'writedelay' and 'redrawdebug' options to see where and when the UI is painted.
- :set writedelay=1
+ :set writedelay=50 rdb=compositor
+
+Note: neovim uses an internal screenbuffer to only send minimal updates even if a large
+region is repainted internally. To also highlight excess internal redraws, use
+
+ :set writedelay=50 rdb=compositor,nodelta
### Terminal reference
diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua
index d4882abffe..881a83e606 100644
--- a/src/nvim/api/keysets.lua
+++ b/src/nvim/api/keysets.lua
@@ -53,6 +53,7 @@ return {
"force";
"keepscript";
"nargs";
+ "preview";
"range";
"register";
};
diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c
index ba2e560d63..3da2c2cde4 100644
--- a/src/nvim/api/private/dispatch.c
+++ b/src/nvim/api/private/dispatch.c
@@ -32,37 +32,22 @@
#include "nvim/api/window.h"
#include "nvim/ui_client.h"
-static Map(String, MsgpackRpcRequestHandler) methods = MAP_INIT;
-
-static void msgpack_rpc_add_method_handler(String method, MsgpackRpcRequestHandler handler)
-{
- map_put(String, MsgpackRpcRequestHandler)(&methods, method, handler);
-}
-
-void msgpack_rpc_add_redraw(void)
-{
- msgpack_rpc_add_method_handler(STATIC_CSTR_AS_STRING("redraw"),
- (MsgpackRpcRequestHandler) { .fn = ui_client_handle_redraw,
- .fast = true });
-}
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "api/private/dispatch_wrappers.generated.h"
+#endif
/// @param name API method name
/// @param name_len name size (includes terminating NUL)
MsgpackRpcRequestHandler msgpack_rpc_get_handler_for(const char *name, size_t name_len,
Error *error)
{
- String m = { .data = (char *)name, .size = name_len };
- MsgpackRpcRequestHandler rv =
- map_get(String, MsgpackRpcRequestHandler)(&methods, m);
+ int hash = msgpack_rpc_get_handler_for_hash(name, name_len);
- if (!rv.fn) {
+ if (hash < 0) {
api_set_error(error, kErrorTypeException, "Invalid method: %.*s",
- m.size > 0 ? (int)m.size : (int)sizeof("<empty>"),
- m.size > 0 ? m.data : "<empty>");
+ name_len > 0 ? (int)name_len : (int)sizeof("<empty>"),
+ name_len > 0 ? name : "<empty>");
+ return (MsgpackRpcRequestHandler){ 0 };
}
- return rv;
+ return method_handlers[hash];
}
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "api/private/dispatch_wrappers.generated.h"
-#endif
diff --git a/src/nvim/api/private/dispatch.h b/src/nvim/api/private/dispatch.h
index bad5a13934..4b7c394944 100644
--- a/src/nvim/api/private/dispatch.h
+++ b/src/nvim/api/private/dispatch.h
@@ -10,6 +10,7 @@ typedef Object (*ApiDispatchWrapper)(uint64_t channel_id,
/// The rpc_method_handlers table, used in msgpack_rpc_dispatch(), stores
/// functions of this type.
typedef struct {
+ const char *name;
ApiDispatchWrapper fn;
bool fast; // Function is safe to be executed immediately while running the
// uv loop (the loop is run very frequently due to breakcheck).
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 3cccbc3cdf..af4aaf01aa 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -678,11 +678,7 @@ void modify_keymap(uint64_t channel_id, Buffer buffer, bool is_unmap, String mod
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!");
- api_set_error(err, kErrorTypeValidation, "Parsing of nonempty RHS failed: %s", rhs.data);
- goto fail_and_free;
+ abort(); // should never happen
}
} else if (is_unmap && (parsed_args.rhs_len || parsed_args.rhs_lua != LUA_NOREF)) {
if (parsed_args.rhs_len) {
@@ -1438,6 +1434,7 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
char *rep = NULL;
LuaRef luaref = LUA_NOREF;
LuaRef compl_luaref = LUA_NOREF;
+ LuaRef preview_luaref = LUA_NOREF;
if (!uc_validate_name(name.data)) {
api_set_error(err, kErrorTypeValidation, "Invalid command name");
@@ -1592,6 +1589,14 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
goto err;
}
+ if (opts->preview.type == kObjectTypeLuaRef) {
+ argt |= EX_PREVIEW;
+ preview_luaref = api_new_luaref(opts->preview.data.luaref);
+ } else if (HAS_KEY(opts->preview)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value for 'preview'");
+ goto err;
+ }
+
switch (command.type) {
case kObjectTypeLuaRef:
luaref = api_new_luaref(command.data.luaref);
@@ -1611,7 +1616,7 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
}
if (uc_add_command(name.data, name.size, rep, argt, def, flags, compl, compl_arg, compl_luaref,
- addr_type_arg, luaref, force) != OK) {
+ preview_luaref, addr_type_arg, luaref, force) != OK) {
api_set_error(err, kErrorTypeException, "Failed to create user command");
// Do not goto err, since uc_add_command now owns luaref, compl_luaref, and compl_arg
}
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index dd0b75bbfb..9430a37d27 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -45,6 +45,7 @@
#include "nvim/move.h"
#include "nvim/msgpack_rpc/channel.h"
#include "nvim/msgpack_rpc/helpers.h"
+#include "nvim/msgpack_rpc/unpacker.h"
#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/os/input.h"
@@ -156,7 +157,6 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err)
/// - reverse: boolean
/// - nocombine: boolean
/// - link: name of another highlight group to link to, see |:hi-link|.
-/// Additionally, the following keys are recognized:
/// - default: Don't override existing definition |:hi-default|
/// - ctermfg: Sets foreground of cterm color |highlight-ctermfg|
/// - ctermbg: Sets background of cterm color |highlight-ctermbg|
@@ -480,7 +480,7 @@ Object nvim_notify(String msg, Integer log_level, Dictionary opts, Error *err)
}
/// Calculates the number of display cells occupied by `text`.
-/// <Tab> counts as one cell.
+/// Control characters including <Tab> count as one cell.
///
/// @param text Some text
/// @param[out] err Error details, if any
@@ -2188,6 +2188,12 @@ void nvim__screenshot(String path)
ui_call_screenshot(path);
}
+Object nvim__unpack(String str, Error *err)
+ FUNC_API_FAST
+{
+ return unpack(str.data, str.size, err);
+}
+
/// Deletes an uppercase/file named mark. See |mark-motions|.
///
/// @note fails with error if a lowercase or buffer local named mark is used.
@@ -2501,6 +2507,8 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
/// - count: (number) Any count supplied |<count>|
/// - reg: (string) The optional register, if specified |<reg>|
/// - mods: (string) Command modifiers, if any |<mods>|
+/// - smods: (table) Command modifiers in a structured format. Has the same
+/// structure as the "mods" key of |nvim_parse_cmd()|.
/// @param opts Optional command attributes. See |command-attributes| for more details. To use
/// boolean attributes (such as |:command-bang| or |:command-bar|) set the value to
/// "true". In addition to the string options listed in |:command-complete|, the
@@ -2509,6 +2517,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
/// - desc: (string) Used for listing the command when a Lua function is used for
/// {command}.
/// - force: (boolean, default true) Override any previous definition.
+/// - preview: (function) Preview callback for 'inccommand' |:command-preview|
/// @param[out] err Error details, if any.
void nvim_create_user_command(String name, Object command, Dict(user_command) *opts, Error *err)
FUNC_API_SINCE(9)
diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index e71f1a11ec..4b4404ea09 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -1241,10 +1241,12 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
}
if (HAS_KEY(mods.verbose)) {
- if (mods.verbose.type != kObjectTypeInteger || mods.verbose.data.integer <= 0) {
- VALIDATION_ERROR("'mods.verbose' must be a non-negative Integer");
+ if (mods.verbose.type != kObjectTypeInteger) {
+ VALIDATION_ERROR("'mods.verbose' must be a Integer");
+ } else if (mods.verbose.data.integer >= 0) {
+ // Silently ignore negative integers to allow mods.verbose to be set to -1.
+ cmdinfo.verbose = mods.verbose.data.integer;
}
- cmdinfo.verbose = mods.verbose.data.integer;
}
bool vertical;
@@ -1256,8 +1258,10 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
VALIDATION_ERROR("'mods.split' must be a String");
}
- if (STRCMP(mods.split.data.string.data, "aboveleft") == 0
- || STRCMP(mods.split.data.string.data, "leftabove") == 0) {
+ if (*mods.split.data.string.data == NUL) {
+ // Empty string, do nothing.
+ } else if (STRCMP(mods.split.data.string.data, "aboveleft") == 0
+ || STRCMP(mods.split.data.string.data, "leftabove") == 0) {
cmdinfo.cmdmod.split |= WSP_ABOVE;
} else if (STRCMP(mods.split.data.string.data, "belowright") == 0
|| STRCMP(mods.split.data.string.data, "rightbelow") == 0) {
@@ -1311,7 +1315,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
}
WITH_SCRIPT_CONTEXT(channel_id, {
- execute_cmd(&ea, &cmdinfo);
+ execute_cmd(&ea, &cmdinfo, false);
});
if (output) {
diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c
index cb08ba0cfb..47b88945c7 100644
--- a/src/nvim/buffer_updates.c
+++ b/src/nvim/buffer_updates.c
@@ -187,7 +187,7 @@ void buf_updates_unload(buf_T *buf, bool can_reload)
}
void buf_updates_send_changes(buf_T *buf, linenr_T firstline, int64_t num_added,
- int64_t num_removed, bool send_tick)
+ int64_t num_removed)
{
size_t deleted_codepoints, deleted_codeunits;
size_t deleted_bytes = ml_flush_deleted_bytes(buf, &deleted_codepoints,
@@ -197,6 +197,9 @@ void buf_updates_send_changes(buf_T *buf, linenr_T firstline, int64_t num_added,
return;
}
+ // Don't send b:changedtick during 'inccommand' preview if "buf" is the current buffer.
+ bool send_tick = !(cmdpreview && buf == curbuf);
+
// if one the channels doesn't work, put its ID here so we can remove it later
uint64_t badchannelid = 0;
@@ -253,7 +256,7 @@ void buf_updates_send_changes(buf_T *buf, linenr_T firstline, int64_t num_added,
for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) {
BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i);
bool keep = true;
- if (cb.on_lines != LUA_NOREF && (cb.preview || !(State & MODE_CMDPREVIEW))) {
+ if (cb.on_lines != LUA_NOREF && (cb.preview || !cmdpreview)) {
Array args = ARRAY_DICT_INIT;
Object items[8];
args.size = 6; // may be increased to 8 below
@@ -312,7 +315,7 @@ void buf_updates_send_splice(buf_T *buf, int start_row, colnr_T start_col, bcoun
for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) {
BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i);
bool keep = true;
- if (cb.on_bytes != LUA_NOREF && (cb.preview || !(State & MODE_CMDPREVIEW))) {
+ if (cb.on_bytes != LUA_NOREF && (cb.preview || !cmdpreview)) {
FIXED_TEMP_ARRAY(args, 11);
// the first argument is always the buffer handle
diff --git a/src/nvim/change.c b/src/nvim/change.c
index 9fd5083fd3..fa1de69e2c 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -351,7 +351,7 @@ void changed_bytes(linenr_T lnum, colnr_T col)
changedOneline(curbuf, lnum);
changed_common(lnum, col, lnum + 1, 0L);
// notify any channels that are watching
- buf_updates_send_changes(curbuf, lnum, 1, 1, true);
+ buf_updates_send_changes(curbuf, lnum, 1, 1);
// Diff highlighting in other diff windows may need to be updated too.
if (curwin->w_p_diff) {
@@ -501,7 +501,7 @@ void changed_lines(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra, bool d
if (do_buf_event) {
int64_t num_added = (int64_t)(lnume + xtra - lnum);
int64_t num_removed = lnume - lnum;
- buf_updates_send_changes(curbuf, lnum, num_added, num_removed, true);
+ buf_updates_send_changes(curbuf, lnum, num_added, num_removed);
}
}
diff --git a/src/nvim/channel.c b/src/nvim/channel.c
index 7a71be58c1..ecc3a24784 100644
--- a/src/nvim/channel.c
+++ b/src/nvim/channel.c
@@ -188,7 +188,7 @@ Channel *channel_alloc(ChannelStreamType type)
void channel_create_event(Channel *chan, const char *ext_source)
{
-#if MIN_LOG_LEVEL <= INFO_LOG_LEVEL
+#if MIN_LOG_LEVEL <= LOGLVL_INF
const char *source;
if (ext_source) {
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 02dc5ec954..6eb210fc79 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -10413,7 +10413,7 @@ char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, char *flags
// - The text up to where the match is.
// - The substituted text.
// - The text after the match.
- sublen = vim_regsub(&regmatch, (char_u *)sub, expr, (char_u *)tail, false, true, false);
+ sublen = vim_regsub(&regmatch, (char_u *)sub, expr, (char_u *)tail, 0, REGSUB_MAGIC);
ga_grow(&ga, (int)((end - tail) + sublen -
(regmatch.endp[0] - regmatch.startp[0])));
@@ -10421,8 +10421,9 @@ char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, char *flags
int i = (int)(regmatch.startp[0] - (char_u *)tail);
memmove((char_u *)ga.ga_data + ga.ga_len, tail, (size_t)i);
// add the substituted text
- (void)vim_regsub(&regmatch, (char_u *)sub, expr, (char_u *)ga.ga_data
- + ga.ga_len + i, true, true, false);
+ (void)vim_regsub(&regmatch, (char_u *)sub, expr,
+ (char_u *)ga.ga_data + ga.ga_len + i, sublen,
+ REGSUB_COPY | REGSUB_MAGIC);
ga.ga_len += i + sublen - 1;
tail = (char *)regmatch.endp[0];
if (*tail == NUL) {
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index f6bdfc6175..7e56f131ba 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -14,7 +14,6 @@
#include <string.h>
#include "nvim/api/buffer.h"
-#include "nvim/api/extmark.h"
#include "nvim/api/private/defs.h"
#include "nvim/ascii.h"
#include "nvim/buffer.h"
@@ -111,8 +110,6 @@ typedef struct {
# include "ex_cmds.c.generated.h"
#endif
-static int preview_bufnr = 0;
-
/// ":ascii" and "ga" implementation
void do_ascii(const exarg_T *const eap)
{
@@ -1013,7 +1010,7 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
disable_fold_update--;
// send update regarding the new lines that were added
- buf_updates_send_changes(curbuf, dest + 1, num_lines, 0, true);
+ buf_updates_send_changes(curbuf, dest + 1, num_lines, 0);
/*
* Now we delete the original text -- webb
@@ -1055,7 +1052,7 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
}
// send nvim_buf_lines_event regarding lines that were deleted
- buf_updates_send_changes(curbuf, line1 + extra, 0, num_lines, true);
+ buf_updates_send_changes(curbuf, line1 + extra, 0, num_lines);
return OK;
}
@@ -3438,8 +3435,8 @@ static int check_regexp_delim(int c)
/// The usual escapes are supported as described in the regexp docs.
///
/// @param do_buf_event If `true`, send buffer updates.
-/// @return buffer used for 'inccommand' preview
-static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle_T bufnr)
+/// @return 0, 1 or 2. See show_cmdpreview() for more information on what the return value means.
+static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T cmdpreview_bufnr)
{
long i = 0;
regmmatch_T regmatch;
@@ -3467,14 +3464,10 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
char *sub_firstline; // allocated copy of first sub line
bool endcolumn = false; // cursor in last column when done
PreviewLines preview_lines = { KV_INITIAL_VALUE, 0 };
- static int pre_src_id = 0; // Source id for the preview highlight
static int pre_hl_id = 0;
- buf_T *orig_buf = curbuf; // save to reset highlighting
pos_T old_cursor = curwin->w_cursor;
int start_nsubs;
int save_ma = 0;
- int save_b_changed = curbuf->b_changed;
- bool preview = (State & MODE_CMDPREVIEW);
bool did_save = false;
@@ -3494,7 +3487,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
&& vim_strchr("0123456789cegriIp|\"", *cmd) == NULL) {
// don't accept alphanumeric for separator
if (check_regexp_delim(*cmd) == FAIL) {
- return NULL;
+ return 0;
}
// undocumented vi feature:
@@ -3504,7 +3497,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
cmd++;
if (vim_strchr("/?&", *cmd) == NULL) {
emsg(_(e_backslash));
- return NULL;
+ return 0;
}
if (*cmd != '&') {
which_pat = RE_SEARCH; // use last '/' pattern
@@ -3540,7 +3533,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
MB_PTR_ADV(cmd);
}
- if (!eap->skip && !preview) {
+ if (!eap->skip && !cmdpreview) {
sub_set_replacement((SubReplacementString) {
.sub = xstrdup(sub),
.timestamp = os_time(),
@@ -3550,7 +3543,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
} else if (!eap->skip) { // use previous pattern and substitution
if (old_sub.sub == NULL) { // there is no previous command
emsg(_(e_nopresub));
- return NULL;
+ return 0;
}
pat = NULL; // search_regcomp() will use previous pattern
sub = old_sub.sub;
@@ -3560,8 +3553,8 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
endcolumn = (curwin->w_curswant == MAXCOL);
}
- if (sub != NULL && sub_joining_lines(eap, pat, sub, cmd, !preview)) {
- return NULL;
+ if (sub != NULL && sub_joining_lines(eap, pat, sub, cmd, !cmdpreview)) {
+ return 0;
}
cmd = sub_parse_flags(cmd, &subflags, &which_pat);
@@ -3575,7 +3568,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
i = getdigits_long((char_u **)&cmd, true, 0);
if (i <= 0 && !eap->skip && subflags.do_error) {
emsg(_(e_zerocount));
- return NULL;
+ return 0;
}
eap->line1 = eap->line2;
eap->line2 += i - 1;
@@ -3592,26 +3585,26 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
eap->nextcmd = (char *)check_nextcmd((char_u *)cmd);
if (eap->nextcmd == NULL) {
emsg(_(e_trailing));
- return NULL;
+ return 0;
}
}
if (eap->skip) { // not executing commands, only parsing
- return NULL;
+ return 0;
}
if (!subflags.do_count && !MODIFIABLE(curbuf)) {
// Substitution is not allowed in non-'modifiable' buffer
emsg(_(e_modifiable));
- return NULL;
+ return 0;
}
- if (search_regcomp((char_u *)pat, RE_SUBST, which_pat, (preview ? 0 : SEARCH_HIS),
+ if (search_regcomp((char_u *)pat, RE_SUBST, which_pat, (cmdpreview ? 0 : SEARCH_HIS),
&regmatch) == FAIL) {
if (subflags.do_error) {
emsg(_(e_invcmd));
}
- return NULL;
+ return 0;
}
// the 'i' or 'I' flag overrules 'ignorecase' and 'smartcase'
@@ -3638,10 +3631,10 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
sub_copy = sub;
} else {
char *source = sub;
- sub = (char *)regtilde((char_u *)sub, p_magic, preview);
+ sub = (char *)regtilde((char_u *)sub, p_magic, cmdpreview);
// When previewing, the new pattern allocated by regtilde() needs to be freed
// in this function because it will not be used or freed by regtilde() later.
- sub_needs_free = preview && sub != source;
+ sub_needs_free = cmdpreview && sub != source;
}
// Check for a match on each line.
@@ -3650,7 +3643,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
for (linenr_T lnum = eap->line1;
lnum <= line2 && !got_quit && !aborting()
- && (!preview || preview_lines.lines_needed <= (linenr_T)p_cwh
+ && (!cmdpreview || preview_lines.lines_needed <= (linenr_T)p_cwh
|| lnum <= curwin->w_botline);
lnum++) {
long nmatch = vim_regexec_multi(&regmatch, curwin, curbuf, lnum,
@@ -3817,7 +3810,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
}
}
- if (subflags.do_ask && !preview) {
+ if (subflags.do_ask && !cmdpreview) {
int typed = 0;
// change State to MODE_CONFIRM, so that the mouse works
@@ -4049,7 +4042,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
// Save the line numbers for the preview buffer
// NOTE: If the pattern matches a final newline, the next line will
// be shown also, but should not be highlighted. Intentional for now.
- if (preview && !has_second_delim) {
+ if (cmdpreview && !has_second_delim) {
current_match.start.col = regmatch.startpos[0].col;
if (current_match.end.lnum == 0) {
current_match.end.lnum = sub_firstlnum + nmatch - 1;
@@ -4064,7 +4057,7 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
// 3. Substitute the string. During 'inccommand' preview only do this if
// there is a replace pattern.
- if (!preview || has_second_delim) {
+ if (!cmdpreview || has_second_delim) {
long lnum_start = lnum; // save the start lnum
save_ma = curbuf->b_p_ma;
if (subflags.do_count) {
@@ -4078,7 +4071,8 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
// get length of substitution part
sublen = vim_regsub_multi(&regmatch,
sub_firstlnum - regmatch.startpos[0].lnum,
- (char_u *)sub, (char_u *)sub_firstline, false, p_magic, true);
+ (char_u *)sub, (char_u *)sub_firstline, 0,
+ REGSUB_BACKSLASH | (p_magic ? REGSUB_MAGIC : 0));
// If getting the substitute string caused an error, don't do
// the replacement.
// Don't keep flags set by a recursive call
@@ -4118,7 +4112,8 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
(void)vim_regsub_multi(&regmatch,
sub_firstlnum - regmatch.startpos[0].lnum,
- (char_u *)sub, (char_u *)new_end, true, p_magic, true);
+ (char_u *)sub, (char_u *)new_end, sublen,
+ REGSUB_COPY | REGSUB_BACKSLASH | (p_magic ? REGSUB_MAGIC : 0));
sub_nsubs++;
did_sub = true;
@@ -4310,7 +4305,7 @@ skip:
#define PUSH_PREVIEW_LINES() \
do { \
- if (preview) { \
+ if (cmdpreview) { \
linenr_T match_lines = current_match.end.lnum \
- current_match.start.lnum +1; \
if (preview_lines.subresults.size > 0) { \
@@ -4366,8 +4361,7 @@ skip:
int64_t num_added = last_line - first_line;
int64_t num_removed = num_added - i;
- buf_updates_send_changes(curbuf, first_line, num_added, num_removed,
- do_buf_event);
+ buf_updates_send_changes(curbuf, first_line, num_added, num_removed);
}
xfree(sub_firstline); // may have to free allocated copy of the line
@@ -4394,7 +4388,7 @@ skip:
beginline(BL_WHITE | BL_FIX);
}
}
- if (!preview && !do_sub_msg(subflags.do_count) && subflags.do_ask) {
+ if (!cmdpreview && !do_sub_msg(subflags.do_count) && subflags.do_ask) {
msg("");
}
} else {
@@ -4431,34 +4425,23 @@ skip:
subflags.do_all = save_do_all;
subflags.do_ask = save_do_ask;
+ int retv = 0;
+
// Show 'inccommand' preview if there are matched lines.
- buf_T *preview_buf = NULL;
- size_t subsize = preview_lines.subresults.size;
- if (preview && !aborting()) {
+ if (cmdpreview && !aborting()) {
if (got_quit || profile_passed_limit(timeout)) { // Too slow, disable.
set_string_option_direct("icm", -1, (char_u *)"", OPT_FREE,
SID_NONE);
} else if (*p_icm != NUL && pat != NULL) {
- if (pre_src_id == 0) {
- // Get a unique new src_id, saved in a static
- pre_src_id = (int)nvim_create_namespace((String)STRING_INIT);
- }
if (pre_hl_id == 0) {
pre_hl_id = syn_check_group(S_LEN("Substitute"));
}
- curbuf->b_changed = save_b_changed; // preserve 'modified' during preview
- preview_buf = show_sub(eap, old_cursor, &preview_lines,
- pre_hl_id, pre_src_id, bufnr);
- if (subsize > 0) {
- extmark_clear(orig_buf, pre_src_id, eap->line1 - 1, 0,
- kv_last(preview_lines.subresults).end.lnum - 1, MAXCOL);
- }
+ retv = show_sub(eap, old_cursor, &preview_lines, pre_hl_id, cmdpreview_ns, cmdpreview_bufnr);
}
}
kv_destroy(preview_lines.subresults);
-
- return preview_buf;
+ return retv;
#undef ADJUST_SUB_FIRSTLNUM
#undef PUSH_PREVIEW_LINES
}
@@ -5854,52 +5837,26 @@ void ex_helpclose(exarg_T *eap)
}
}
-/// Tries to enter to an existing window of given buffer. If no existing buffer
-/// is found, creates a new split.
-///
-/// @return OK/FAIL.
-int sub_preview_win(buf_T *preview_buf)
-{
- if (preview_buf != NULL) {
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_buffer == preview_buf) {
- win_enter(wp, false);
-
- return OK;
- }
- }
- }
- return win_split((int)p_cwh, WSP_BOT);
-}
-
/// Shows the effects of the :substitute command being typed ('inccommand').
/// If inccommand=split, shows a preview window and later restores the layout.
-static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, PreviewLines *preview_lines, int hl_id,
- int src_id, handle_T bufnr)
+///
+/// @return 1 if preview window isn't needed, 2 if preview window is needed.
+static int show_sub(exarg_T *eap, pos_T old_cusr, PreviewLines *preview_lines, int hl_id,
+ long cmdpreview_ns, handle_T cmdpreview_bufnr)
FUNC_ATTR_NONNULL_ALL
{
- win_T *save_curwin = curwin;
- cmdmod_T save_cmdmod = cmdmod;
char *save_shm_p = (char *)vim_strsave(p_shm);
PreviewLines lines = *preview_lines;
buf_T *orig_buf = curbuf;
-
// We keep a special-purpose buffer around, but don't assume it exists.
- buf_T *preview_buf = bufnr ? buflist_findnr(bufnr) : 0;
- cmdmod.split = 0; // disable :leftabove/botright modifiers
- cmdmod.tab = 0; // disable :tab modifier
- cmdmod.noswapfile = true; // disable swap for preview buffer
+ buf_T *cmdpreview_buf = NULL;
+
// disable file info message
set_string_option_direct("shm", -1, (char_u *)"F", OPT_FREE,
SID_NONE);
- bool outside_curline = (eap->line1 != old_cusr.lnum
- || eap->line2 != old_cusr.lnum);
- bool preview = outside_curline && (*p_icm != 'n');
- if (preview_buf == curbuf) { // Preview buffer cannot preview itself!
- preview = false;
- preview_buf = NULL;
- }
+ // Update the topline to ensure that main window is on the correct line
+ update_topline(curwin);
// Place cursor on nearest matching line, to undo do_sub() cursor placement.
for (size_t i = 0; i < lines.subresults.size; i++) {
@@ -5914,27 +5871,17 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, PreviewLines *preview_lines
// Width of the "| lnum|..." column which displays the line numbers.
linenr_T highest_num_line = 0;
int col_width = 0;
+ // Use preview window only when inccommand=split and range is not just the current line
+ bool preview = (*p_icm == 's') && (eap->line1 != old_cusr.lnum || eap->line2 != old_cusr.lnum);
- if (preview && sub_preview_win(preview_buf) != FAIL) {
- buf_open_scratch(preview_buf ? bufnr : 0, "[Preview]");
- buf_clear();
- preview_buf = curbuf;
- curbuf->b_p_bl = false;
- curbuf->b_p_ma = true;
- curbuf->b_p_ul = -1;
- curbuf->b_p_tw = 0; // Reset 'textwidth' (was set by ftplugin)
- curwin->w_p_cul = false;
- curwin->w_p_cuc = false;
- curwin->w_p_spell = false;
- curwin->w_p_fen = false;
+ if (preview) {
+ cmdpreview_buf = buflist_findnr(cmdpreview_bufnr);
+ assert(cmdpreview_buf != NULL);
if (lines.subresults.size > 0) {
highest_num_line = kv_last(lines.subresults).end.lnum;
col_width = log10(highest_num_line) + 1 + 3;
}
- } else {
- // Failed to split the window, don't show 'inccommand' preview.
- preview_buf = NULL;
}
char *str = NULL; // construct the line to show in here
@@ -5944,10 +5891,13 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, PreviewLines *preview_lines
linenr_T linenr_origbuf = 0; // last line added to original buffer
linenr_T next_linenr = 0; // next line to show for the match
+ // Temporarily switch to preview buffer
+ aco_save_T aco;
+
for (size_t matchidx = 0; matchidx < lines.subresults.size; matchidx++) {
SubResult match = lines.subresults.items[matchidx];
- if (preview_buf) {
+ if (cmdpreview_buf) {
lpos_T p_start = { 0, match.start.col }; // match starts here in preview
lpos_T p_end = { 0, match.end.col }; // ... and ends here
@@ -5986,115 +5936,50 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr, PreviewLines *preview_lines
// Put "|lnum| line" into `str` and append it to the preview buffer.
snprintf(str, line_size, "|%*ld| %s", col_width - 3,
next_linenr, line);
+ // Temporarily switch to preview buffer
+ aucmd_prepbuf(&aco, cmdpreview_buf);
if (linenr_preview == 0) {
ml_replace(1, str, true);
} else {
ml_append(linenr_preview, str, (colnr_T)line_size, false);
}
+ aucmd_restbuf(&aco);
linenr_preview += 1;
}
linenr_origbuf = match.end.lnum;
- bufhl_add_hl_pos_offset(preview_buf, src_id, hl_id, p_start,
- p_end, col_width);
+ bufhl_add_hl_pos_offset(cmdpreview_buf, cmdpreview_ns, hl_id, p_start, p_end, col_width);
}
- bufhl_add_hl_pos_offset(orig_buf, src_id, hl_id, match.start,
- match.end, 0);
+ bufhl_add_hl_pos_offset(orig_buf, cmdpreview_ns, hl_id, match.start, match.end, 0);
}
- xfree(str);
- redraw_later(curwin, SOME_VALID);
- win_enter(save_curwin, false); // Return to original window
- update_topline(curwin);
-
- // Update screen now.
- int save_rd = RedrawingDisabled;
- RedrawingDisabled = 0;
- update_screen(SOME_VALID);
- RedrawingDisabled = save_rd;
+ xfree(str);
set_string_option_direct("shm", -1, (char_u *)save_shm_p, OPT_FREE, SID_NONE);
xfree(save_shm_p);
- cmdmod = save_cmdmod;
-
- return preview_buf;
+ return preview ? 2 : 1;
}
-/// Closes any open windows for inccommand preview buffer.
-void close_preview_windows(void)
+/// :substitute command.
+void ex_substitute(exarg_T *eap)
{
- block_autocmds();
- buf_T *buf = preview_bufnr ? buflist_findnr(preview_bufnr) : NULL;
- if (buf != NULL) {
- close_windows(buf, false);
- }
- unblock_autocmds();
+ (void)do_sub(eap, profile_zero(), 0, 0);
+ return;
}
-/// :substitute command
-///
-/// If 'inccommand' is empty: calls do_sub().
-/// If 'inccommand' is set: shows a "live" preview then removes the changes.
-/// from undo history.
-void ex_substitute(exarg_T *eap)
+/// :substitute command preview callback.
+int ex_substitute_preview(exarg_T *eap, long cmdpreview_ns, handle_T cmdpreview_bufnr)
{
- bool preview = (State & MODE_CMDPREVIEW);
- if (*p_icm == NUL || !preview) { // 'inccommand' is disabled
- close_preview_windows();
- (void)do_sub(eap, profile_zero(), true, preview_bufnr);
-
- return;
+ // Only preview once the pattern delimiter has been typed
+ if (*eap->arg && !ASCII_ISALNUM(*eap->arg)) {
+ char *save_eap = eap->arg;
+ int retv = do_sub(eap, profile_setlimit(p_rdt), cmdpreview_ns, cmdpreview_bufnr);
+ eap->arg = save_eap;
+ return retv;
}
- block_autocmds(); // Disable events during command preview.
-
- char *save_eap = eap->arg;
- garray_T save_view;
- win_size_save(&save_view); // Save current window sizes.
- save_search_patterns();
- int save_changedtick = buf_get_changedtick(curbuf);
- time_t save_b_u_time_cur = curbuf->b_u_time_cur;
- u_header_T *save_b_u_newhead = curbuf->b_u_newhead;
- long save_b_p_ul = curbuf->b_p_ul;
- int save_w_p_cul = curwin->w_p_cul;
- int save_w_p_cuc = curwin->w_p_cuc;
-
- curbuf->b_p_ul = LONG_MAX; // make sure we can undo all changes
- curwin->w_p_cul = false; // Disable 'cursorline'
- curwin->w_p_cuc = false; // Disable 'cursorcolumn'
-
- // Don't show search highlighting during live substitution
- bool save_hls = p_hls;
- p_hls = false;
- buf_T *preview_buf = do_sub(eap, profile_setlimit(p_rdt), false,
- preview_bufnr);
- p_hls = save_hls;
-
- if (preview_buf != NULL) {
- preview_bufnr = preview_buf->handle;
- }
-
- if (save_changedtick != buf_get_changedtick(curbuf)) {
- // Undo invisibly. This also moves the cursor!
- if (!u_undo_and_forget(1)) {
- abort();
- }
- // Restore newhead. It is meaningless when curhead is valid, but we must
- // restore it so that undotree() is identical before/after the preview.
- curbuf->b_u_newhead = save_b_u_newhead;
- curbuf->b_u_time_cur = save_b_u_time_cur;
- buf_set_changedtick(curbuf, save_changedtick);
- }
-
- curbuf->b_p_ul = save_b_p_ul;
- curwin->w_p_cul = save_w_p_cul; // Restore 'cursorline'
- curwin->w_p_cuc = save_w_p_cuc; // Restore 'cursorcolumn'
- eap->arg = save_eap;
- restore_search_patterns();
- win_size_restore(&save_view);
- ga_clear(&save_view);
- unblock_autocmds();
+ return 0;
}
/// Skip over the pattern argument of ":vimgrep /pat/[g][j]".
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index 427e018141..b18bdefc2a 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -4,28 +4,29 @@ local module = {}
-- Description of the values below is contained in ex_cmds_defs.h file.
-- "EX_" prefix is omitted.
-local RANGE = 0x001
-local BANG = 0x002
-local EXTRA = 0x004
-local XFILE = 0x008
-local NOSPC = 0x010
-local DFLALL = 0x020
-local WHOLEFOLD = 0x040
-local NEEDARG = 0x080
-local TRLBAR = 0x100
-local REGSTR = 0x200
-local COUNT = 0x400
-local NOTRLCOM = 0x800
-local ZEROR = 0x1000
-local CTRLV = 0x2000
-local CMDARG = 0x4000
-local BUFNAME = 0x8000
-local BUFUNL = 0x10000
-local ARGOPT = 0x20000
-local SBOXOK = 0x40000
-local CMDWIN = 0x80000
-local MODIFY = 0x100000
-local FLAGS = 0x200000
+local RANGE = 0x001
+local BANG = 0x002
+local EXTRA = 0x004
+local XFILE = 0x008
+local NOSPC = 0x010
+local DFLALL = 0x020
+local WHOLEFOLD = 0x040
+local NEEDARG = 0x080
+local TRLBAR = 0x100
+local REGSTR = 0x200
+local COUNT = 0x400
+local NOTRLCOM = 0x800
+local ZEROR = 0x1000
+local CTRLV = 0x2000
+local CMDARG = 0x4000
+local BUFNAME = 0x8000
+local BUFUNL = 0x10000
+local ARGOPT = 0x20000
+local SBOXOK = 0x40000
+local CMDWIN = 0x80000
+local MODIFY = 0x100000
+local FLAGS = 0x200000
+local PREVIEW = 0x8000000
local FILES = bit.bor(XFILE, EXTRA)
local WORD1 = bit.bor(EXTRA, NOSPC)
local FILE1 = bit.bor(FILES, NOSPC)
@@ -33,6 +34,7 @@ local FILE1 = bit.bor(FILES, NOSPC)
module.flags = {
RANGE = RANGE,
DFLALL = DFLALL,
+ PREVIEW = PREVIEW
}
-- The following table is described in ex_cmds_defs.h file.
@@ -2305,9 +2307,10 @@ module.cmds = {
},
{
command='substitute',
- flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN),
+ flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN, PREVIEW),
addr_type='ADDR_LINES',
func='ex_substitute',
+ preview_func='ex_substitute_preview',
},
{
command='sNext',
@@ -2479,9 +2482,10 @@ module.cmds = {
},
{
command='smagic',
- flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN),
+ flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN, PREVIEW),
addr_type='ADDR_LINES',
func='ex_submagic',
+ preview_func='ex_submagic_preview',
},
{
command='smap',
@@ -2509,9 +2513,10 @@ module.cmds = {
},
{
command='snomagic',
- flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN),
+ flags=bit.bor(RANGE, WHOLEFOLD, EXTRA, CMDWIN, PREVIEW),
addr_type='ADDR_LINES',
func='ex_submagic',
+ preview_func='ex_submagic_preview',
},
{
command='snoremap',
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index 0a91072036..45c05c9980 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -2798,15 +2798,11 @@ void ex_language(exarg_T *eap)
}
if (*name == NUL) {
-# ifdef HAVE_WORKING_LIBINTL
if (what == VIM_LC_MESSAGES) {
p = get_mess_env();
} else {
-# endif
- p = setlocale(what, NULL);
-# ifdef HAVE_WORKING_LIBINTL
- }
-# endif
+ p = setlocale(what, NULL);
+ }
if (p == NULL || *p == NUL) {
p = "Unknown";
}
diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h
index d8dd3da9e6..a5c9c6be2d 100644
--- a/src/nvim/ex_cmds_defs.h
+++ b/src/nvim/ex_cmds_defs.h
@@ -63,6 +63,7 @@
#define EX_MODIFY 0x100000 // forbidden in non-'modifiable' buffer
#define EX_FLAGS 0x200000 // allow flags after count in argument
#define EX_KEEPSCRIPT 0x4000000 // keep sctx of where command was invoked
+#define EX_PREVIEW 0x8000000 // allow incremental command preview
#define EX_FILES (EX_XFILE | EX_EXTRA) // multiple extra files allowed
#define EX_FILE1 (EX_FILES | EX_NOSPC) // 1 file, defaults to current file
#define EX_WORD1 (EX_EXTRA | EX_NOSPC) // one extra word allowed
@@ -91,6 +92,7 @@ typedef struct exarg exarg_T;
#define BAD_DROP (-2) // erase it
typedef void (*ex_func_T)(exarg_T *eap);
+typedef int (*ex_preview_func_T)(exarg_T *eap, long cmdpreview_ns, handle_T cmdpreview_bufnr);
// NOTE: These possible could be removed and changed so that
// Callback could take a "command" style string, and simply
@@ -125,10 +127,11 @@ typedef char *(*LineGetter)(int, void *, int, bool);
/// Structure for command definition.
typedef struct cmdname {
- char *cmd_name; ///< Name of the command.
- ex_func_T cmd_func; ///< Function with implementation of this command.
- uint32_t cmd_argt; ///< Relevant flags from the declared above.
- cmd_addr_T cmd_addr_type; ///< Flag for address type
+ char *cmd_name; ///< Name of the command.
+ ex_func_T cmd_func; ///< Function with implementation of this command.
+ ex_preview_func_T cmd_preview_func; ///< Preview callback function of this command.
+ uint32_t cmd_argt; ///< Relevant flags from the declared above.
+ cmd_addr_T cmd_addr_type; ///< Flag for address type.
} CommandDefinition;
// A list used for saving values of "emsg_silent". Used by ex_try() to save the
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 4b7958efa5..0e9c8dcf01 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -294,7 +294,6 @@ int do_cmdline_cmd(const char *cmd)
/// DOCMD_KEYTYPED - Don't reset KeyTyped.
/// DOCMD_EXCRESET - Reset the exception environment (used for debugging).
/// DOCMD_KEEPLINE - Store first typed line (for repeating with ".").
-/// DOCMD_PREVIEW - During 'inccommand' preview.
///
/// @param cookie argument for fgetline()
///
@@ -593,11 +592,6 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
next_cmdline = do_one_cmd(&cmdline_copy, flags, &cstack, cmd_getline, cmd_cookie);
recursive--;
- // Ignore trailing '|'-separated commands in preview-mode ('inccommand').
- if ((State & MODE_CMDPREVIEW) && (flags & DOCMD_PREVIEW)) {
- next_cmdline = NULL;
- }
-
if (cmd_cookie == (void *)&cmd_loop_cookie) {
// Use "current_line" from "cmd_loop_cookie", it may have been
// incremented when defining a function.
@@ -1578,9 +1572,11 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er
///
/// @param eap Ex-command arguments
/// @param cmdinfo Command parse information
-void execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo)
+/// @param preview Execute command preview callback instead of actual command
+int execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo, bool preview)
{
char *errormsg = NULL;
+ int retv = 0;
#define ERROR(msg) \
do { \
@@ -1698,11 +1694,17 @@ void execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo)
// Execute the command
if (IS_USER_CMDIDX(eap->cmdidx)) {
// Execute a user-defined command.
- do_ucmd(eap);
+ retv = do_ucmd(eap, preview);
} else {
- // Call the function to execute the command.
+ // Call the function to execute the command or the preview callback.
eap->errmsg = NULL;
- (cmdnames[eap->cmdidx].cmd_func)(eap);
+
+ if (preview) {
+ retv = (cmdnames[eap->cmdidx].cmd_preview_func)(eap, cmdpreview_get_ns(),
+ cmdpreview_get_bufnr());
+ } else {
+ (cmdnames[eap->cmdidx].cmd_func)(eap);
+ }
if (eap->errmsg != NULL) {
errormsg = _(eap->errmsg);
}
@@ -1718,6 +1720,7 @@ end:
if (eap->did_sandbox) {
sandbox--;
}
+ return retv;
#undef ERROR
}
@@ -2350,7 +2353,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter
/*
* Execute a user-defined command.
*/
- do_ucmd(&ea);
+ do_ucmd(&ea, false);
} else {
/*
* Call the function to execute the command.
@@ -2751,6 +2754,8 @@ int parse_cmd_address(exarg_T *eap, char **errormsg, bool silent)
{
int address_count = 1;
linenr_T lnum;
+ bool need_check_cursor = false;
+ int ret = FAIL;
// Repeat for all ',' or ';' separated addresses.
for (;;) {
@@ -2760,7 +2765,7 @@ int parse_cmd_address(exarg_T *eap, char **errormsg, bool silent)
lnum = get_address(eap, &eap->cmd, eap->addr_type, eap->skip, silent,
eap->addr_count == 0, address_count++);
if (eap->cmd == NULL) { // error detected
- return FAIL;
+ goto theend;
}
if (lnum == MAXLNUM) {
if (*eap->cmd == '%') { // '%' - all lines
@@ -2799,14 +2804,14 @@ int parse_cmd_address(exarg_T *eap, char **errormsg, bool silent)
// there is no Vim command which uses '%' and
// ADDR_WINDOWS or ADDR_TABS
*errormsg = _(e_invrange);
- return FAIL;
+ goto theend;
}
break;
case ADDR_TABS_RELATIVE:
case ADDR_UNSIGNED:
case ADDR_QUICKFIX:
*errormsg = _(e_invrange);
- return FAIL;
+ goto theend;
case ADDR_ARGUMENTS:
if (ARGCOUNT == 0) {
eap->line1 = eap->line2 = 0;
@@ -2831,19 +2836,19 @@ int parse_cmd_address(exarg_T *eap, char **errormsg, bool silent)
// '*' - visual area
if (eap->addr_type != ADDR_LINES) {
*errormsg = _(e_invrange);
- return FAIL;
+ goto theend;
}
eap->cmd++;
if (!eap->skip) {
pos_T *fp = getmark('<', false);
if (check_mark(fp) == FAIL) {
- return FAIL;
+ goto theend;
}
eap->line1 = fp->lnum;
fp = getmark('>', false);
if (check_mark(fp) == FAIL) {
- return FAIL;
+ goto theend;
}
eap->line2 = fp->lnum;
eap->addr_count++;
@@ -2857,11 +2862,14 @@ int parse_cmd_address(exarg_T *eap, char **errormsg, bool silent)
if (*eap->cmd == ';') {
if (!eap->skip) {
curwin->w_cursor.lnum = eap->line2;
+
// Don't leave the cursor on an illegal line or column, but do
// accept zero as address, so 0;/PATTERN/ works correctly.
+ // Check the cursor position before returning.
if (eap->line2 > 0) {
check_cursor();
}
+ need_check_cursor = true;
}
} else if (*eap->cmd != ',') {
break;
@@ -2877,7 +2885,13 @@ int parse_cmd_address(exarg_T *eap, char **errormsg, bool silent)
eap->addr_count = 0;
}
}
- return OK;
+ ret = OK;
+
+theend:
+ if (need_check_cursor) {
+ check_cursor();
+ }
+ return ret;
}
/// Check for an Ex command with optional tail.
@@ -5530,8 +5544,8 @@ char *uc_validate_name(char *name)
///
/// @return OK if the command is created, FAIL otherwise.
int uc_add_command(char *name, size_t name_len, char *rep, uint32_t argt, long def, int flags,
- int compl, char *compl_arg, LuaRef compl_luaref, cmd_addr_T addr_type,
- LuaRef luaref, bool force)
+ int compl, char *compl_arg, LuaRef compl_luaref, LuaRef preview_luaref,
+ cmd_addr_T addr_type, LuaRef luaref, bool force)
FUNC_ATTR_NONNULL_ARG(1, 3)
{
ucmd_T *cmd = NULL;
@@ -5586,6 +5600,7 @@ int uc_add_command(char *name, size_t name_len, char *rep, uint32_t argt, long d
XFREE_CLEAR(cmd->uc_compl_arg);
NLUA_CLEAR_REF(cmd->uc_luaref);
NLUA_CLEAR_REF(cmd->uc_compl_luaref);
+ NLUA_CLEAR_REF(cmd->uc_preview_luaref);
break;
}
@@ -5618,6 +5633,7 @@ int uc_add_command(char *name, size_t name_len, char *rep, uint32_t argt, long d
nlua_set_sctx(&cmd->uc_script_ctx);
cmd->uc_compl_arg = (char_u *)compl_arg;
cmd->uc_compl_luaref = compl_luaref;
+ cmd->uc_preview_luaref = preview_luaref;
cmd->uc_addr_type = addr_type;
cmd->uc_luaref = luaref;
@@ -5628,6 +5644,7 @@ fail:
xfree(compl_arg);
NLUA_CLEAR_REF(luaref);
NLUA_CLEAR_REF(compl_luaref);
+ NLUA_CLEAR_REF(preview_luaref);
return FAIL;
}
@@ -6060,8 +6077,7 @@ static void ex_command(exarg_T *eap)
} else if (compl > 0 && (argt & EX_EXTRA) == 0) {
emsg(_(e_complete_used_without_nargs));
} else {
- uc_add_command(name, name_len, p, argt, def, flags, compl,
- compl_arg, LUA_NOREF,
+ uc_add_command(name, name_len, p, argt, def, flags, compl, compl_arg, LUA_NOREF, LUA_NOREF,
addr_type_arg, LUA_NOREF, eap->forceit);
}
}
@@ -6081,6 +6097,7 @@ void free_ucmd(ucmd_T *cmd)
xfree(cmd->uc_compl_arg);
NLUA_CLEAR_REF(cmd->uc_compl_luaref);
NLUA_CLEAR_REF(cmd->uc_luaref);
+ NLUA_CLEAR_REF(cmd->uc_preview_luaref);
}
/// Clear all user commands for "gap".
@@ -6611,7 +6628,7 @@ size_t uc_mods(char *buf)
return result;
}
-static void do_ucmd(exarg_T *eap)
+static int do_ucmd(exarg_T *eap, bool preview)
{
char *buf;
char *p;
@@ -6632,9 +6649,14 @@ static void do_ucmd(exarg_T *eap)
cmd = USER_CMD_GA(&curbuf->b_ucmds, eap->useridx);
}
+ if (preview) {
+ assert(cmd->uc_preview_luaref > 0);
+ return nlua_do_ucmd(cmd, eap, true);
+ }
+
if (cmd->uc_luaref > 0) {
- nlua_do_ucmd(cmd, eap);
- return;
+ nlua_do_ucmd(cmd, eap, false);
+ return 0;
}
/*
@@ -6729,6 +6751,8 @@ static void do_ucmd(exarg_T *eap)
}
xfree(buf);
xfree(split_buf);
+
+ return 0;
}
static char *expand_user_command_name(int idx)
@@ -6785,7 +6809,8 @@ char *get_user_cmd_flags(expand_T *xp, int idx)
{
static char *user_cmd_flags[] = { "addr", "bang", "bar",
"buffer", "complete", "count",
- "nargs", "range", "register", "keepscript" };
+ "nargs", "range", "register",
+ "keepscript" };
if (idx >= (int)ARRAY_SIZE(user_cmd_flags)) {
return NULL;
@@ -8557,6 +8582,18 @@ static void ex_submagic(exarg_T *eap)
p_magic = magic_save;
}
+/// ":smagic" and ":snomagic" preview callback.
+static int ex_submagic_preview(exarg_T *eap, long cmdpreview_ns, handle_T cmdpreview_bufnr)
+{
+ int magic_save = p_magic;
+
+ p_magic = (eap->cmdidx == CMD_smagic);
+ int retv = ex_substitute_preview(eap, cmdpreview_ns, cmdpreview_bufnr);
+ p_magic = magic_save;
+
+ return retv;
+}
+
/// ":join".
static void ex_join(exarg_T *eap)
{
@@ -8798,7 +8835,7 @@ static void ex_redir(exarg_T *eap)
/// ":redraw": force redraw
static void ex_redraw(exarg_T *eap)
{
- if (State & MODE_CMDPREVIEW) {
+ if (cmdpreview) {
return; // Ignore :redraw during 'inccommand' preview. #9777
}
int r = RedrawingDisabled;
@@ -8832,7 +8869,7 @@ static void ex_redraw(exarg_T *eap)
/// ":redrawstatus": force redraw of status line(s) and window bar(s)
static void ex_redrawstatus(exarg_T *eap)
{
- if (State & MODE_CMDPREVIEW) {
+ if (cmdpreview) {
return; // Ignore :redrawstatus during 'inccommand' preview. #9777
}
int r = RedrawingDisabled;
@@ -10096,22 +10133,15 @@ bool cmd_can_preview(char *cmd)
if (*ea.cmd == '*') {
ea.cmd = skipwhite(ea.cmd + 1);
}
- char *end = find_ex_command(&ea, NULL);
- switch (ea.cmdidx) {
- case CMD_substitute:
- case CMD_smagic:
- case CMD_snomagic:
- // Only preview once the pattern delimiter has been typed
- if (*end && !ASCII_ISALNUM(*end)) {
- return true;
- }
- break;
- default:
- break;
+ if (find_ex_command(&ea, NULL) == NULL || ea.cmdidx == CMD_SIZE) {
+ return false;
+ } else if (!IS_USER_CMDIDX(ea.cmdidx)) {
+ // find_ex_command sets the flags for user commands automatically
+ ea.argt = cmdnames[(int)ea.cmdidx].cmd_argt;
}
- return false;
+ return (ea.argt & EX_PREVIEW);
}
/// Gets a map of maps describing user-commands defined for buffer `buf` or
@@ -10138,6 +10168,7 @@ Dictionary commands_array(buf_T *buf)
PUT(d, "bar", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_TRLBAR)));
PUT(d, "register", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_REGSTR)));
PUT(d, "keepscript", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_KEEPSCRIPT)));
+ PUT(d, "preview", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_PREVIEW)));
switch (cmd->uc_argt & (EX_EXTRA | EX_NOSPC | EX_NEEDARG)) {
case 0:
diff --git a/src/nvim/ex_docmd.h b/src/nvim/ex_docmd.h
index 24656f3851..dbe095ab13 100644
--- a/src/nvim/ex_docmd.h
+++ b/src/nvim/ex_docmd.h
@@ -12,7 +12,6 @@
#define DOCMD_KEYTYPED 0x08 // don't reset KeyTyped
#define DOCMD_EXCRESET 0x10 // reset exception environment (for debugging
#define DOCMD_KEEPLINE 0x20 // keep typed line for repeating with "."
-#define DOCMD_PREVIEW 0x40 // during 'inccommand' preview
// defines for eval_vars()
#define VALID_PATH 1
@@ -42,6 +41,7 @@ typedef struct ucmd {
sctx_T uc_script_ctx; // SCTX where the command was defined
char_u *uc_compl_arg; // completion argument if any
LuaRef uc_compl_luaref; // Reference to Lua completion function
+ LuaRef uc_preview_luaref; // Reference to Lua preview function
LuaRef uc_luaref; // Reference to Lua function
} ucmd_T;
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 13cfd76adf..ad3ccf2a99 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -11,7 +11,9 @@
#include <stdlib.h>
#include <string.h>
+#include "nvim/api/extmark.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/vim.h"
#include "nvim/arabic.h"
#include "nvim/ascii.h"
#include "nvim/assert.h"
@@ -69,6 +71,7 @@
#include "nvim/syntax.h"
#include "nvim/tag.h"
#include "nvim/ui.h"
+#include "nvim/undo.h"
#include "nvim/vim.h"
#include "nvim/viml/parser/expressions.h"
#include "nvim/viml/parser/parser.h"
@@ -230,27 +233,13 @@ static int compl_match_arraysize;
static int compl_startcol;
static int compl_selected;
-/// |:checkhealth| completion items
-///
-/// Regenerates on every new command line prompt, to accommodate changes on the
-/// runtime files.
-typedef struct {
- garray_T names; // healthcheck names
- unsigned last_gen; // last_prompt_id where names were generated
-} CheckhealthComp;
-
-/// Cookie used when converting filepath to name
-struct healthchecks_cookie {
- garray_T *names; // global healthchecks
- bool is_lua; // true if the current entry is a Lua healthcheck
-};
-
-static CheckhealthComp healthchecks = { GA_INIT(sizeof(char_u *), 10), 0 };
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_getln.c.generated.h"
#endif
+static handle_T cmdpreview_bufnr = 0;
+static long cmdpreview_ns = 0;
+
static int cmd_hkmap = 0; // Hebrew mapping during command line
static void save_viewstate(viewstate_T *vs)
@@ -295,59 +284,23 @@ static void init_incsearch_state(incsearch_state_T *s)
/// @param[in] xp Not used.
static char *get_healthcheck_names(expand_T *xp, int idx)
{
- // Generate the first time or on new prompt.
- if (healthchecks.last_gen == 0 || healthchecks.last_gen != last_prompt_id) {
- ga_clear_strings(&healthchecks.names);
- char *patterns[3] = { "autoload/health/**.vim", "lua/**/**/health/init.lua", // NOLINT
- "lua/**/**/health.lua" }; // NOLINT
- for (int i = 0; i < 3; i++) {
- struct healthchecks_cookie hcookie = { .names = &healthchecks.names, .is_lua = i != 0 };
- do_in_runtimepath(patterns[i], DIP_ALL, get_healthcheck_cb, &hcookie);
-
- if (healthchecks.names.ga_len > 0) {
- ga_remove_duplicate_strings(&healthchecks.names);
- }
- }
- // Tracked to regenerate items on next prompt.
- healthchecks.last_gen = last_prompt_id;
- }
- return idx < healthchecks.names.ga_len
- ? ((char **)(healthchecks.names.ga_data))[idx] : NULL;
-}
-
-/// Transform healthcheck file path into it's name.
-///
-/// Used as a callback for do_in_runtimepath
-/// @param[in] path Expanded path to a possible healthcheck.
-/// @param[out] cookie Array where names will be inserted.
-static void get_healthcheck_cb(char *path, void *cookie)
-{
- if (path != NULL) {
- struct healthchecks_cookie *hcookie = (struct healthchecks_cookie *)cookie;
- char *pattern;
- char *sub = "\\1";
- char *res;
-
- if (hcookie->is_lua) {
- // Lua: transform "../lua/vim/lsp/health.lua" into "vim.lsp"
- pattern = ".*lua[\\/]\\(.\\{-}\\)[\\/]health\\([\\/]init\\)\\?\\.lua$";
- } else {
- // Vim: transform "../autoload/health/provider.vim" into "provider"
- pattern = ".*[\\/]\\([^\\/]*\\)\\.vim$";
- }
+ static Object names = OBJECT_INIT;
+ static unsigned last_gen = 0;
- res = do_string_sub(path, pattern, sub, NULL, "g");
- if (hcookie->is_lua && res != NULL) {
- // Replace slashes with dots as represented by the healthcheck plugin.
- char *ares = do_string_sub(res, "[\\/]", ".", NULL, "g");
- xfree(res);
- res = ares;
- }
+ if (last_gen != last_prompt_id || last_gen == 0) {
+ Array a = ARRAY_DICT_INIT;
+ Error err = ERROR_INIT;
+ Object res = nlua_exec(STATIC_CSTR_AS_STRING("return vim.health._complete()"), a, &err);
+ api_clear_error(&err);
+ api_free_object(names);
+ names = res;
+ last_gen = last_prompt_id;
+ }
- if (res != NULL) {
- GA_APPEND(char *, hcookie->names, res);
- }
+ if (names.type == kObjectTypeArray && idx < (int)names.data.array.size) {
+ return names.data.array.items[idx].data.string.data;
}
+ return NULL;
}
// Return true when 'incsearch' highlighting is to be done.
@@ -740,6 +693,8 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init
static int cmdline_level = 0;
cmdline_level++;
+ bool save_cmdpreview = cmdpreview;
+ cmdpreview = false;
CommandLineState state = {
.firstc = firstc,
.count = count,
@@ -951,11 +906,6 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init
ExpandCleanup(&s->xpc);
ccline.xpc = NULL;
- if (s->gotesc) {
- // There might be a preview window open for inccommand. Close it.
- close_preview_windows();
- }
-
finish_incsearch_highlighting(s->gotesc, &s->is_state, false);
if (ccline.cmdbuff != NULL) {
@@ -998,6 +948,10 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init
set_string_option_direct("icm", -1, s->save_p_icm, OPT_FREE,
SID_NONE);
State = s->save_State;
+ if (cmdpreview != save_cmdpreview) {
+ cmdpreview = save_cmdpreview; // restore preview state
+ redraw_all_later(SOME_VALID);
+ }
setmouse();
ui_cursor_shape(); // may show different cursor shape
sb_text_end_cmdline();
@@ -2306,6 +2260,268 @@ static int empty_pattern(char_u *p)
return n == 0 || (n >= 2 && p[n - 2] == '\\' && p[n - 1] == '|');
}
+handle_T cmdpreview_get_bufnr(void)
+{
+ return cmdpreview_bufnr;
+}
+
+long cmdpreview_get_ns(void)
+{
+ return cmdpreview_ns;
+}
+
+/// Sets up command preview buffer.
+///
+/// @return Pointer to command preview buffer if succeeded, NULL if failed.
+static buf_T *cmdpreview_open_buf(void)
+{
+ buf_T *cmdpreview_buf = cmdpreview_bufnr ? buflist_findnr(cmdpreview_bufnr) : NULL;
+
+ // If preview buffer doesn't exist, open one.
+ if (cmdpreview_buf == NULL) {
+ Error err = ERROR_INIT;
+ handle_T bufnr = nvim_create_buf(false, true, &err);
+
+ if (ERROR_SET(&err)) {
+ return NULL;
+ }
+
+ cmdpreview_buf = buflist_findnr(bufnr);
+ }
+
+ // Preview buffer cannot preview itself!
+ if (cmdpreview_buf == curbuf) {
+ return NULL;
+ }
+
+ // Rename preview buffer.
+ aco_save_T aco;
+ aucmd_prepbuf(&aco, cmdpreview_buf);
+ int retv = rename_buffer("[Preview]");
+ aucmd_restbuf(&aco);
+
+ if (retv == FAIL) {
+ return NULL;
+ }
+
+ // Temporarily switch to preview buffer to set it up for previewing.
+ aucmd_prepbuf(&aco, cmdpreview_buf);
+ buf_clear();
+ curbuf->b_p_ma = true;
+ curbuf->b_p_ul = -1;
+ curbuf->b_p_tw = 0; // Reset 'textwidth' (was set by ftplugin)
+ aucmd_restbuf(&aco);
+ cmdpreview_bufnr = cmdpreview_buf->handle;
+
+ return cmdpreview_buf;
+}
+
+/// Open command preview window if it's not already open.
+/// Returns to original window after opening command preview window.
+///
+/// @param cmdpreview_buf Pointer to command preview buffer
+///
+/// @return Pointer to command preview window if succeeded, NULL if failed.
+static win_T *cmdpreview_open_win(buf_T *cmdpreview_buf)
+{
+ win_T *save_curwin = curwin;
+ bool win_found = false;
+
+ // Try to find an existing preview window.
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_buffer == cmdpreview_buf) {
+ win_enter(wp, false);
+ win_found = true;
+ break;
+ }
+ }
+
+ // If an existing window is not found, create one.
+ if (!win_found && win_split((int)p_cwh, WSP_BOT) == FAIL) {
+ return NULL;
+ }
+
+ win_T *preview_win = curwin;
+ Error err = ERROR_INIT;
+
+ // Switch to preview buffer
+ try_start();
+ int result = do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, cmdpreview_buf->handle, 0);
+ if (try_end(&err) || result == FAIL) {
+ api_clear_error(&err);
+ return NULL;
+ }
+
+ curwin->w_p_cul = false;
+ curwin->w_p_cuc = false;
+ curwin->w_p_spell = false;
+ curwin->w_p_fen = false;
+
+ win_enter(save_curwin, false);
+ return preview_win;
+}
+
+/// Closes any open command preview windows.
+static void cmdpreview_close_win(void)
+{
+ buf_T *buf = cmdpreview_bufnr ? buflist_findnr(cmdpreview_bufnr) : NULL;
+ if (buf != NULL) {
+ close_windows(buf, false);
+ }
+}
+
+/// Show 'inccommand' preview. It works like this:
+/// 1. Store current undo information so we can revert to current state later.
+/// 2. Execute the preview callback with the parsed command, preview buffer number and preview
+/// namespace number as arguments. The preview callback sets the highlight and does the
+/// changes required for the preview if needed.
+/// 3. Preview callback returns 0, 1 or 2. 0 means no preview is shown. 1 means preview is shown
+/// but preview window doesn't need to be opened. 2 means preview is shown and preview window
+/// needs to be opened if inccommand=split.
+/// 4. Use the return value of the preview callback to determine whether to
+/// open the preview window or not and open preview window if needed.
+/// 5. If the return value of the preview callback is not 0, update the screen while the effects
+/// of the preview are still in place.
+/// 6. Revert all changes made by the preview callback.
+static void cmdpreview_show(CommandLineState *s)
+{
+ // Parse the command line and return if it fails.
+ exarg_T ea;
+ CmdParseInfo cmdinfo;
+ // Copy the command line so we can modify it.
+ char *cmdline = xstrdup((char *)ccline.cmdbuff);
+ char *errormsg = NULL;
+
+ parse_cmdline(cmdline, &ea, &cmdinfo, &errormsg);
+ if (errormsg != NULL) {
+ goto end;
+ }
+
+ // Swap invalid command range if needed
+ if ((ea.argt & EX_RANGE) && ea.line1 > ea.line2) {
+ linenr_T lnum = ea.line1;
+ ea.line1 = ea.line2;
+ ea.line2 = lnum;
+ }
+
+ time_t save_b_u_time_cur = curbuf->b_u_time_cur;
+ long save_b_u_seq_cur = curbuf->b_u_seq_cur;
+ u_header_T *save_b_u_newhead = curbuf->b_u_newhead;
+ long save_b_p_ul = curbuf->b_p_ul;
+ int save_b_changed = curbuf->b_changed;
+ int save_w_p_cul = curwin->w_p_cul;
+ int save_w_p_cuc = curwin->w_p_cuc;
+ bool save_hls = p_hls;
+ varnumber_T save_changedtick = buf_get_changedtick(curbuf);
+ bool icm_split = *p_icm == 's'; // inccommand=split
+ buf_T *cmdpreview_buf;
+ win_T *cmdpreview_win;
+ cmdmod_T save_cmdmod = cmdmod;
+
+ cmdpreview = true;
+ emsg_silent++; // Block error reporting as the command may be incomplete
+ msg_silent++; // Block messages, namely ones that prompt
+ block_autocmds(); // Block events
+ garray_T save_view;
+ win_size_save(&save_view); // Save current window sizes
+ save_search_patterns(); // Save search patterns
+ curbuf->b_p_ul = LONG_MAX; // Make sure we can undo all changes
+ curwin->w_p_cul = false; // Disable 'cursorline' so it doesn't mess up the highlights
+ curwin->w_p_cuc = false; // Disable 'cursorcolumn' so it doesn't mess up the highlights
+ p_hls = false; // Don't show search highlighting during live substitution
+ cmdmod.split = 0; // Disable :leftabove/botright modifiers
+ cmdmod.tab = 0; // Disable :tab modifier
+ cmdmod.noswapfile = true; // Disable swap for preview buffer
+
+ // Open preview buffer if inccommand=split.
+ if (!icm_split) {
+ cmdpreview_bufnr = 0;
+ } else if ((cmdpreview_buf = cmdpreview_open_buf()) == NULL) {
+ abort();
+ }
+
+ // Setup preview namespace if it's not already set.
+ if (!cmdpreview_ns) {
+ cmdpreview_ns = (int)nvim_create_namespace((String)STRING_INIT);
+ }
+
+ // Execute the preview callback and use its return value to determine whether to show preview or
+ // open the preview window. The preview callback also handles doing the changes and highlights for
+ // the preview.
+ Error err = ERROR_INIT;
+ try_start();
+ int cmdpreview_type = execute_cmd(&ea, &cmdinfo, true);
+ if (try_end(&err)) {
+ api_clear_error(&err);
+ cmdpreview_type = 0;
+ }
+
+ // If inccommand=split and preview callback returns 2, open preview window.
+ if (icm_split && cmdpreview_type == 2
+ && (cmdpreview_win = cmdpreview_open_win(cmdpreview_buf)) == NULL) {
+ abort();
+ }
+
+ // If preview callback is nonzero, update screen now.
+ if (cmdpreview_type != 0) {
+ int save_rd = RedrawingDisabled;
+ RedrawingDisabled = 0;
+ update_screen(SOME_VALID);
+ RedrawingDisabled = save_rd;
+ }
+
+ // Close preview window if it's open.
+ if (icm_split && cmdpreview_type == 2 && cmdpreview_win != NULL) {
+ cmdpreview_close_win();
+ }
+ // Clear preview highlights.
+ extmark_clear(curbuf, (uint32_t)cmdpreview_ns, 0, 0, MAXLNUM, MAXCOL);
+
+ curbuf->b_changed = save_b_changed; // Preserve 'modified' during preview
+
+ if (curbuf->b_u_seq_cur != save_b_u_seq_cur) {
+ // Undo invisibly. This also moves the cursor!
+ while (curbuf->b_u_seq_cur != save_b_u_seq_cur) {
+ if (!u_undo_and_forget(1)) {
+ abort();
+ }
+ }
+ // Restore newhead. It is meaningless when curhead is valid, but we must
+ // restore it so that undotree() is identical before/after the preview.
+ curbuf->b_u_newhead = save_b_u_newhead;
+ curbuf->b_u_time_cur = save_b_u_time_cur;
+ }
+ if (save_changedtick != buf_get_changedtick(curbuf)) {
+ buf_set_changedtick(curbuf, save_changedtick);
+ }
+
+ cmdmod = save_cmdmod; // Restore cmdmod
+ p_hls = save_hls; // Restore 'hlsearch'
+ curwin->w_p_cul = save_w_p_cul; // Restore 'cursorline'
+ curwin->w_p_cuc = save_w_p_cuc; // Restore 'cursorcolumn'
+ curbuf->b_p_ul = save_b_p_ul; // Restore 'undolevels'
+ restore_search_patterns(); // Restore search patterns
+ win_size_restore(&save_view); // Restore window sizes
+ ga_clear(&save_view);
+ unblock_autocmds(); // Unblock events
+ msg_silent--; // Unblock messages
+ emsg_silent--; // Unblock error reporting
+
+ // Restore the window "view".
+ curwin->w_cursor = s->is_state.save_cursor;
+ restore_viewstate(&s->is_state.old_viewstate);
+ update_topline(curwin);
+
+ redrawcmdline();
+
+ // If preview callback returned 0, update screen to clear remnants of an earlier preview.
+ if (cmdpreview_type == 0) {
+ update_screen(SOME_VALID);
+ }
+end:
+ xfree(cmdline);
+}
+
static int command_line_changed(CommandLineState *s)
{
// Trigger CmdlineChanged autocommands.
@@ -2337,7 +2553,6 @@ static int command_line_changed(CommandLineState *s)
}
}
- // 'incsearch' highlighting.
if (s->firstc == ':'
&& current_sctx.sc_sid == 0 // only if interactive
&& *p_icm != NUL // 'inccommand' is set
@@ -2345,27 +2560,9 @@ static int command_line_changed(CommandLineState *s)
&& cmdline_star == 0 // not typing a password
&& cmd_can_preview((char *)ccline.cmdbuff)
&& !vpeekc_any()) {
- // Show 'inccommand' preview. It works like this:
- // 1. Do the command.
- // 2. Command implementation detects MODE_CMDPREVIEW state, then:
- // - Update the screen while the effects are in place.
- // - Immediately undo the effects.
- State |= MODE_CMDPREVIEW;
- emsg_silent++; // Block error reporting as the command may be incomplete
- msg_silent++; // Block messages, namely ones that prompt
- do_cmdline((char *)ccline.cmdbuff, NULL, NULL, DOCMD_KEEPLINE|DOCMD_NOWAIT|DOCMD_PREVIEW);
- msg_silent--; // Unblock messages
- emsg_silent--; // Unblock error reporting
-
- // Restore the window "view".
- curwin->w_cursor = s->is_state.save_cursor;
- restore_viewstate(&s->is_state.old_viewstate);
- update_topline(curwin);
-
- redrawcmdline();
- } else if (State & MODE_CMDPREVIEW) {
- State = (State & ~MODE_CMDPREVIEW);
- close_preview_windows();
+ cmdpreview_show(s);
+ } else if (cmdpreview) {
+ cmdpreview = false;
update_screen(SOME_VALID); // Clear 'inccommand' preview.
} else {
if (s->xpc.xp_context == EXPAND_NOTHING && (KeyTyped || vpeekc() == NUL)) {
diff --git a/src/nvim/fold.c b/src/nvim/fold.c
index 234c11227d..d5277b9910 100644
--- a/src/nvim/fold.c
+++ b/src/nvim/fold.c
@@ -754,8 +754,7 @@ void deleteFold(win_T *const wp, const linenr_T start, const linenr_T end, const
// the modification of the *first* line of the fold, but we send through a
// notification that includes every line that was part of the fold
int64_t num_changed = last_lnum - first_lnum;
- buf_updates_send_changes(wp->w_buffer, first_lnum, num_changed,
- num_changed, true);
+ buf_updates_send_changes(wp->w_buffer, first_lnum, num_changed, num_changed);
}
}
@@ -1614,7 +1613,7 @@ static void foldCreateMarkers(win_T *wp, pos_T start, pos_T end)
// u_save() is unable to save the buffer line, but we send the
// nvim_buf_lines_event anyway since it won't do any harm.
int64_t num_changed = 1 + end.lnum - start.lnum;
- buf_updates_send_changes(buf, start.lnum, num_changed, num_changed, true);
+ buf_updates_send_changes(buf, start.lnum, num_changed, num_changed);
}
// foldAddMarker() {{{2
diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua
index c6dd25154b..0f7052d351 100644
--- a/src/nvim/generators/gen_api_dispatch.lua
+++ b/src/nvim/generators/gen_api_dispatch.lua
@@ -16,6 +16,10 @@ local functions = {}
local nvimdir = arg[1]
package.path = nvimdir .. '/?.lua;' .. package.path
+_G.vim = loadfile(nvimdir..'/../../runtime/lua/vim/shared.lua')()
+
+local hashy = require'generators.hashy'
+
-- names of all headers relative to the source root (for inclusion in the
-- generated file)
local headers = {}
@@ -208,8 +212,8 @@ for i = 1, #functions do
output:write('Object handle_'..fn.name..'(uint64_t channel_id, Array args, Error *error)')
output:write('\n{')
- output:write('\n#if MIN_LOG_LEVEL <= DEBUG_LOG_LEVEL')
- output:write('\n logmsg(DEBUG_LOG_LEVEL, "RPC: ", NULL, -1, true, "ch %" PRIu64 ": invoke '
+ output:write('\n#if MIN_LOG_LEVEL <= LOGLVL_DBG')
+ output:write('\n logmsg(LOGLVL_DBG, "RPC: ", NULL, -1, true, "ch %" PRIu64 ": invoke '
..fn.name..'", channel_id);')
output:write('\n#endif')
output:write('\n Object ret = NIL;')
@@ -339,24 +343,27 @@ for i = 1, #functions do
end
end
--- Generate a function that initializes method names with handler functions
-output:write([[
-void msgpack_rpc_init_method_table(void)
-{
-]])
-
-for i = 1, #functions do
- local fn = functions[i]
+local remote_fns = {}
+for _,fn in ipairs(functions) do
if fn.remote then
- output:write(' msgpack_rpc_add_method_handler('..
- '(String) {.data = "'..fn.name..'", '..
- '.size = sizeof("'..fn.name..'") - 1}, '..
- '(MsgpackRpcRequestHandler) {.fn = handle_'.. (fn.impl_name or fn.name)..
- ', .fast = '..tostring(fn.fast)..'});\n')
+ remote_fns[fn.name] = fn
end
end
+remote_fns.redraw = {impl_name="ui_client_redraw", fast=true}
+
+local hashorder, hashfun = hashy.hashy_hash("msgpack_rpc_get_handler_for", vim.tbl_keys(remote_fns), function (idx)
+ return "method_handlers["..idx.."].name"
+end)
+
+output:write("static const MsgpackRpcRequestHandler method_handlers[] = {\n")
+for _, name in ipairs(hashorder) do
+ local fn = remote_fns[name]
+ output:write(' { .name = "'..name..'", .fn = handle_'.. (fn.impl_name or fn.name)..
+ ', .fast = '..tostring(fn.fast)..'},\n')
+end
+output:write("};\n\n")
+output:write(hashfun)
-output:write('\n}\n\n')
output:close()
local mpack_output = io.open(mpack_outputf, 'wb')
diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua
index 5e70442dce..99dfac05e8 100755
--- a/src/nvim/generators/gen_api_ui_events.lua
+++ b/src/nvim/generators/gen_api_ui_events.lua
@@ -15,6 +15,9 @@ local client_output = io.open(arg[8], 'wb')
local c_grammar = require('generators.c_grammar')
local events = c_grammar.grammar:match(input:read('*all'))
+_G.vim = loadfile(nvimdir..'/../../runtime/lua/vim/shared.lua')()
+local hashy = require'generators.hashy'
+
local function write_signature(output, ev, prefix, notype)
output:write('('..prefix)
if prefix == "" and #ev.parameters == 0 then
@@ -213,24 +216,25 @@ for i = 1, #events do
end
end
--- Generate the map_init method for client handlers
-client_output:write([[
-void ui_client_methods_table_init(void)
-{
+local client_events = {}
+for _,ev in ipairs(events) do
+ if (not ev.noexport) and ((not ev.remote_only) or ev.client_impl) then
+ client_events[ev.name] = ev
+ end
+end
-]])
+local hashorder, hashfun = hashy.hashy_hash("ui_client_handler", vim.tbl_keys(client_events), function (idx)
+ return "event_handlers["..idx.."].name"
+end)
-for i = 1, #events do
- local fn = events[i]
- if (not fn.noexport) and ((not fn.remote_only) or fn.client_impl) then
- client_output:write(' add_ui_client_event_handler('..
- '(String) {.data = "'..fn.name..'", '..
- '.size = sizeof("'..fn.name..'") - 1}, '..
- '(UIClientHandler) ui_client_event_'..fn.name..');\n')
- end
+client_output:write("static const UIClientHandler event_handlers[] = {\n")
+
+for _, name in ipairs(hashorder) do
+ client_output:write(' { .name = "'..name..'", .fn = ui_client_event_'..name..'},\n')
end
-client_output:write('\n}\n\n')
+client_output:write('\n};\n\n')
+client_output:write(hashfun)
proto_output:close()
call_output:close()
diff --git a/src/nvim/generators/gen_ex_cmds.lua b/src/nvim/generators/gen_ex_cmds.lua
index 27cfe194fa..255c415a4d 100644
--- a/src/nvim/generators/gen_ex_cmds.lua
+++ b/src/nvim/generators/gen_ex_cmds.lua
@@ -65,20 +65,31 @@ for _, cmd in ipairs(defs) do
assert(cmd.addr_type ~= 'ADDR_OTHER' and cmd.addr_type ~= 'ADDR_NONE',
string.format('ex_cmds.lua:%s: Missing misplaced DFLALL\n', cmd.command))
end
+ if bit.band(cmd.flags, flags.PREVIEW) == flags.PREVIEW then
+ assert(cmd.preview_func ~= nil,
+ string.format('ex_cmds.lua:%s: Missing preview_func\n', cmd.command))
+ end
local enumname = cmd.enum or ('CMD_' .. cmd.command)
local byte_cmd = cmd.command:sub(1, 1):byte()
if byte_a <= byte_cmd and byte_cmd <= byte_z then
table.insert(cmds, cmd.command)
end
+ local preview_func
+ if cmd.preview_func then
+ preview_func = string.format("(ex_preview_func_T)&%s", cmd.preview_func)
+ else
+ preview_func = "NULL"
+ end
enumfile:write(' ' .. enumname .. ',\n')
defsfile:write(string.format([[
[%s] = {
.cmd_name = "%s",
.cmd_func = (ex_func_T)&%s,
+ .cmd_preview_func = %s,
.cmd_argt = %uL,
.cmd_addr_type = %s
},
-]], enumname, cmd.command, cmd.func, cmd.flags, cmd.addr_type))
+]], enumname, cmd.command, cmd.func, preview_func, cmd.flags, cmd.addr_type))
end
for i = #cmds, 1, -1 do
local cmd = cmds[i]
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 788e1113e2..6b716c2e1f 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -629,7 +629,7 @@ void stuffReadbuffSpec(const char *s)
stuffReadbuffLen(s, 3);
s += 3;
} else {
- int c = mb_ptr2char_adv((const char_u **)&s);
+ int c = mb_cptr2char_adv((const char_u **)&s);
if (c == CAR || c == NL || c == ESC) {
c = ' ';
}
@@ -2328,19 +2328,19 @@ static int vgetorpeek(bool advance)
// try re-mapping.
for (;;) {
check_end_reg_executing(advance);
- // os_breakcheck() can call input_enqueue()
- if ((mapped_ctrl_c | curbuf->b_mapped_ctrl_c) & get_real_state()) {
- ctrl_c_interrupts = false;
- }
// os_breakcheck() is slow, don't use it too often when
// inside a mapping. But call it each time for typed
// characters.
if (typebuf.tb_maplen) {
line_breakcheck();
} else {
+ // os_breakcheck() can call input_enqueue()
+ if ((mapped_ctrl_c | curbuf->b_mapped_ctrl_c) & get_real_state()) {
+ ctrl_c_interrupts = false;
+ }
os_breakcheck(); // check for CTRL-C
+ ctrl_c_interrupts = true;
}
- ctrl_c_interrupts = true;
int keylen = 0;
if (got_int) {
// flush all input
@@ -2585,8 +2585,8 @@ static int vgetorpeek(bool advance)
// get a character: 3. from the user - get it
if (typebuf.tb_len == 0) {
- // timedout may have been set while waiting for a mapping
- // that has a <Nop> RHS.
+ // timedout may have been set if a mapping with empty RHS
+ // fully matched while longer mappings timed out.
timedout = false;
}
@@ -2839,8 +2839,8 @@ int fix_input_buffer(char_u *buf, int len)
/// 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.
+/// If RHS should be <Nop>, `rhs` will be an 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.
@@ -2898,8 +2898,9 @@ void set_maparg_lhs_rhs(const char *const orig_lhs, const size_t orig_lhs_len,
replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf, REPTERM_DO_LT, NULL,
cpo_flags);
mapargs->rhs_len = STRLEN(replaced);
- // XXX: even when orig_rhs is non-empty, replace_termcodes may produce an empty string.
- mapargs->rhs_is_noop = orig_rhs[0] != NUL && mapargs->rhs_len == 0;
+ // XXX: replace_termcodes may produce an empty string even if orig_rhs is non-empty
+ // (e.g. a single ^V, see :h map-empty-rhs)
+ mapargs->rhs_is_noop = orig_rhs_len != 0 && mapargs->rhs_len == 0;
mapargs->rhs = xcalloc(mapargs->rhs_len + 1, sizeof(char_u));
STRLCPY(mapargs->rhs, replaced, mapargs->rhs_len + 1);
}
diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h
index 237f0632bd..ddd6d81aef 100644
--- a/src/nvim/getchar.h
+++ b/src/nvim/getchar.h
@@ -54,8 +54,8 @@ struct map_arguments {
char_u *rhs; /// The {rhs} of the mapping.
size_t rhs_len;
- LuaRef rhs_lua; /// lua function as rhs
- bool rhs_is_noop; /// True when the {orig_rhs} is <nop>.
+ LuaRef rhs_lua; /// lua function as {rhs}
+ bool rhs_is_noop; /// True when the {rhs} should be <Nop>.
char_u *orig_rhs; /// The original text of the {rhs}.
size_t orig_rhs_len;
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index b0006ebaca..a42c8e979d 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -76,7 +76,8 @@
EXTERN struct nvim_stats_s {
int64_t fsync;
int64_t redraw;
-} g_stats INIT(= { 0, 0 });
+ int16_t log_skip; // How many logs were tried and skipped before log_init.
+} g_stats INIT(= { 0, 0, 0 });
// Values for "starting".
#define NO_SCREEN 2 // no screen updating yet
@@ -638,6 +639,9 @@ EXTERN int motion_force INIT(=0); // motion force for pending operator
EXTERN bool exmode_active INIT(= false); // true if Ex mode is active
EXTERN bool ex_no_reprint INIT(=false); // No need to print after z or p.
+// 'inccommand' command preview state
+EXTERN bool cmdpreview INIT(= false);
+
EXTERN int reg_recording INIT(= 0); // register for recording or zero
EXTERN int reg_executing INIT(= 0); // register being executed or zero
// Flag set when peeking a character and found the end of executed register
@@ -1029,7 +1033,7 @@ EXTERN FILE *time_fd INIT(= NULL); // where to write startup timing
// the warning.
EXTERN int vim_ignored;
-// Start a msgpack-rpc channel over stdin/stdout.
+// stdio is an RPC channel (--embed).
EXTERN bool embedded_mode INIT(= false);
// Do not start a UI nor read/write to stdio (unless embedding).
EXTERN bool headless_mode INIT(= false);
diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h
index caf345386d..9084cb500c 100644
--- a/src/nvim/highlight_defs.h
+++ b/src/nvim/highlight_defs.h
@@ -113,6 +113,7 @@ typedef enum {
HLF_BORDER, // Floating window border
HLF_WBR, // Window bars
HLF_WBRNC, // Window bars of not-current windows
+ HLF_CU, // Cursor
HLF_COUNT, // MUST be the last one
} hlf_T;
@@ -176,6 +177,7 @@ EXTERN const char *hlf_names[] INIT(= {
[HLF_BORDER] = "FloatBorder",
[HLF_WBR] = "WinBar",
[HLF_WBRNC] = "WinBarNC",
+ [HLF_CU] = "Cursor",
});
EXTERN int highlight_attr[HLF_COUNT]; // Highl. attr for each context.
diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c
index 05781dd7e2..9d61141e98 100644
--- a/src/nvim/highlight_group.c
+++ b/src/nvim/highlight_group.c
@@ -738,6 +738,8 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id)
g->sg_script_ctx = current_sctx;
g->sg_script_ctx.sc_lnum += sourcing_lnum;
+ g->sg_attr = hl_get_syn_attr(0, id, attrs);
+
// 'Normal' is special
if (STRCMP(g->sg_name_u, "NORMAL") == 0) {
cterm_normal_fg_color = g->sg_cterm_fg;
@@ -747,8 +749,6 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id)
normal_sp = g->sg_rgb_sp;
ui_default_colors_set();
} else {
- g->sg_attr = hl_get_syn_attr(0, id, attrs);
-
// a cursor style uses this syn_id, make sure its attribute is updated.
if (cursor_mode_uses_syn_id(id)) {
ui_mode_info_set();
diff --git a/src/nvim/log.c b/src/nvim/log.c
index 815d53b570..0f410f7c8c 100644
--- a/src/nvim/log.c
+++ b/src/nvim/log.c
@@ -17,6 +17,8 @@
#include "auto/config.h"
#include "nvim/log.h"
+#include "nvim/main.h"
+#include "nvim/message.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
#include "nvim/types.h"
@@ -26,6 +28,7 @@
/// Cached location of the expanded log file path decided by log_path_init().
static char log_file_path[MAXPATHL + 1] = { 0 };
+static bool did_log_init = false;
static uv_mutex_t mutex;
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -51,17 +54,11 @@ static bool log_try_create(char *fname)
/// Initializes path to log file. Sets $NVIM_LOG_FILE if empty.
///
-/// Tries $NVIM_LOG_FILE, or falls back to $XDG_STATE_HOME/nvim/log. Path to log
-/// file is cached, so only the first call has effect, unless first call was not
-/// successful. Failed initialization indicates either a bug in expand_env()
-/// or both $NVIM_LOG_FILE and $HOME environment variables are undefined.
-///
-/// @return true if path was initialized, false otherwise.
-static bool log_path_init(void)
+/// Tries $NVIM_LOG_FILE, or falls back to $XDG_STATE_HOME/nvim/log. Failed
+/// initialization indicates either a bug in expand_env() or both $NVIM_LOG_FILE
+/// and $HOME environment variables are undefined.
+static void log_path_init(void)
{
- if (log_file_path[0]) {
- return true;
- }
size_t size = sizeof(log_file_path);
expand_env((char_u *)"$" LOG_FILE_ENV, (char_u *)log_file_path,
(int)size - 1);
@@ -69,7 +66,7 @@ static bool log_path_init(void)
|| log_file_path[0] == '\0'
|| os_isdir((char_u *)log_file_path)
|| !log_try_create(log_file_path)) {
- // Make kXDGStateHome if it does not exist.
+ // Make $XDG_STATE_HOME if it does not exist.
char *loghome = get_xdg_home(kXDGStateHome);
char *failed_dir = NULL;
bool log_dir_failure = false;
@@ -88,7 +85,7 @@ static bool log_path_init(void)
// Fall back to stderr
if (len >= size || !log_try_create(log_file_path)) {
log_file_path[0] = '\0';
- return false;
+ return;
}
os_setenv(LOG_FILE_ENV, log_file_path, true);
if (log_dir_failure) {
@@ -97,13 +94,14 @@ static bool log_path_init(void)
}
XFREE_CLEAR(failed_dir);
}
- return true;
}
void log_init(void)
{
- uv_mutex_init(&mutex);
+ uv_mutex_init_recursive(&mutex);
+ // AFTER init_homedir ("~", XDG) and set_init_1 (env vars). 22b52dd462e5 #11501
log_path_init();
+ did_log_init = true;
}
void log_lock(void)
@@ -116,6 +114,14 @@ void log_unlock(void)
uv_mutex_unlock(&mutex);
}
+static void on_log_recursive_event(void **argv)
+{
+ char *fn_name = argv[0];
+ ptrdiff_t linenr = (ptrdiff_t)argv[1];
+ siemsg("E5430: %s:%d: recursive log!", fn_name, linenr);
+ xfree(fn_name);
+}
+
/// Logs a message to $NVIM_LOG_FILE.
///
/// @param log_level Log level (see log.h)
@@ -124,10 +130,20 @@ void log_unlock(void)
/// @param line_num Source line number, or -1
/// @param eol Append linefeed "\n"
/// @param fmt printf-style format string
+///
+/// @return true if log was emitted normally, false if failed or recursive
bool logmsg(int log_level, const char *context, const char *func_name, int line_num, bool eol,
const char *fmt, ...)
FUNC_ATTR_UNUSED FUNC_ATTR_PRINTF(6, 7)
{
+ static bool recursive = false;
+ static bool did_msg = false; // Showed recursion message?
+ if (!did_log_init) {
+ g_stats.log_skip++;
+ // set_init_1 may try logging before we are ready. 6f27f5ef91b3 #10183
+ return false;
+ }
+
if (log_level < MIN_LOG_LEVEL) {
return false;
}
@@ -139,13 +155,21 @@ bool logmsg(int log_level, const char *context, const char *func_name, int line_
#endif
log_lock();
+ if (recursive) {
+ if (!did_msg) {
+ did_msg = true;
+ char *arg1 = func_name ? xstrdup(func_name) : (context ? xstrdup(context) : NULL);
+ // coverity[leaked_storage]
+ loop_schedule_deferred(&main_loop, event_create(on_log_recursive_event, 2, arg1, line_num));
+ }
+ g_stats.log_skip++;
+ log_unlock();
+ return false;
+ }
+ recursive = true;
bool ret = false;
FILE *log_file = open_log_file();
- if (log_file == NULL) {
- goto end;
- }
-
va_list args;
va_start(args, fmt);
ret = v_do_log_to_file(log_file, log_level, context, func_name, line_num,
@@ -155,7 +179,8 @@ bool logmsg(int log_level, const char *context, const char *func_name, int line_
if (log_file != stderr && log_file != stdout) {
fclose(log_file);
}
-end:
+
+ recursive = false;
log_unlock();
return ret;
}
@@ -166,51 +191,36 @@ void log_uv_handles(void *loop)
log_lock();
FILE *log_file = open_log_file();
- if (log_file == NULL) {
- goto end;
- }
-
uv_print_all_handles(l, log_file);
if (log_file != stderr && log_file != stdout) {
fclose(log_file);
}
-end:
+
log_unlock();
}
/// Open the log file for appending.
///
-/// @return FILE* decided by log_path_init() or stderr in case of error
+/// @return Log file, or stderr on failure
FILE *open_log_file(void)
{
- static bool opening_log_file = false;
- // Disallow recursion. (This only matters for log_path_init; for logmsg and
- // friends we use a mutex: log_lock).
- if (opening_log_file) {
- do_log_to_file(stderr, ERROR_LOG_LEVEL, NULL, __func__, __LINE__, true,
- "Cannot LOG() recursively.");
- return stderr;
- }
-
- FILE *log_file = NULL;
- opening_log_file = true;
- if (log_path_init()) {
- log_file = fopen(log_file_path, "a");
- }
- opening_log_file = false;
-
- if (log_file != NULL) {
- return log_file;
+ errno = 0;
+ if (log_file_path[0]) {
+ FILE *f = fopen(log_file_path, "a");
+ if (f != NULL) {
+ return f;
+ }
}
// May happen if:
- // - LOG() is called before early_init()
+ // - fopen() failed
+ // - LOG() is called before log_init()
// - Directory does not exist
// - File is not writable
- do_log_to_file(stderr, ERROR_LOG_LEVEL, NULL, __func__, __LINE__, true,
- "Logging to stderr, failed to open $" LOG_FILE_ENV ": %s",
- log_file_path);
+ do_log_to_file(stderr, LOGLVL_ERR, NULL, __func__, __LINE__, true,
+ "failed to open $" LOG_FILE_ENV " (%s): %s",
+ strerror(errno), log_file_path);
return stderr;
}
@@ -237,8 +247,7 @@ void log_callstack_to_file(FILE *log_file, const char *const func_name, const in
// Now we have a command string like:
// addr2line -e /path/to/exe -f -p 0x123 0x456 ...
- do_log_to_file(log_file, DEBUG_LOG_LEVEL, NULL, func_name, line_num, true,
- "trace:");
+ do_log_to_file(log_file, LOGLVL_DBG, NULL, func_name, line_num, true, "trace:");
FILE *fp = popen(cmdbuf, "r");
char linebuf[IOSIZE];
while (fgets(linebuf, sizeof(linebuf) - 1, fp) != NULL) {
@@ -255,13 +264,7 @@ void log_callstack(const char *const func_name, const int line_num)
{
log_lock();
FILE *log_file = open_log_file();
- if (log_file == NULL) {
- goto end;
- }
-
log_callstack_to_file(log_file, func_name, line_num);
-
-end:
log_unlock();
}
#endif
@@ -285,12 +288,12 @@ static bool v_do_log_to_file(FILE *log_file, int log_level, const char *context,
FUNC_ATTR_PRINTF(7, 0)
{
static const char *log_levels[] = {
- [DEBUG_LOG_LEVEL] = "DEBUG",
- [INFO_LOG_LEVEL] = "INFO ",
- [WARN_LOG_LEVEL] = "WARN ",
- [ERROR_LOG_LEVEL] = "ERROR",
+ [LOGLVL_DBG] = "DBG",
+ [LOGLVL_INF] = "INF",
+ [LOGLVL_WRN] = "WRN",
+ [LOGLVL_ERR] = "ERR",
};
- assert(log_level >= DEBUG_LOG_LEVEL && log_level <= ERROR_LOG_LEVEL);
+ assert(log_level >= LOGLVL_DBG && log_level <= LOGLVL_ERR);
// Format the timestamp.
struct tm local_time;
diff --git a/src/nvim/log.h b/src/nvim/log.h
index 724d073d02..cbee0e0f81 100644
--- a/src/nvim/log.h
+++ b/src/nvim/log.h
@@ -16,11 +16,11 @@
# define NVIM_PROBE(name, n, ...)
#endif
-#define TRACE_LOG_LEVEL 0
-#define DEBUG_LOG_LEVEL 1
-#define INFO_LOG_LEVEL 2
-#define WARN_LOG_LEVEL 3
-#define ERROR_LOG_LEVEL 4
+#define LOGLVL_TRC 0
+#define LOGLVL_DBG 1
+#define LOGLVL_INF 2
+#define LOGLVL_WRN 3
+#define LOGLVL_ERR 4
#define DLOG(...)
#define DLOGN(...)
@@ -32,46 +32,37 @@
#define ELOGN(...)
#ifndef MIN_LOG_LEVEL
-# define MIN_LOG_LEVEL INFO_LOG_LEVEL
+# define MIN_LOG_LEVEL LOGLVL_INF
#endif
-#define LOG(level, ...) logmsg((level), NULL, __func__, __LINE__, true, \
- __VA_ARGS__)
+#define LOG(level, ...) logmsg((level), NULL, __func__, __LINE__, true, __VA_ARGS__)
-#if MIN_LOG_LEVEL <= DEBUG_LOG_LEVEL
+#if MIN_LOG_LEVEL <= LOGLVL_DBG
# undef DLOG
# undef DLOGN
-# define DLOG(...) logmsg(DEBUG_LOG_LEVEL, NULL, __func__, __LINE__, true, \
- __VA_ARGS__)
-# define DLOGN(...) logmsg(DEBUG_LOG_LEVEL, NULL, __func__, __LINE__, false, \
- __VA_ARGS__)
+# define DLOG(...) logmsg(LOGLVL_DBG, NULL, __func__, __LINE__, true, __VA_ARGS__)
+# define DLOGN(...) logmsg(LOGLVL_DBG, NULL, __func__, __LINE__, false, __VA_ARGS__)
#endif
-#if MIN_LOG_LEVEL <= INFO_LOG_LEVEL
+#if MIN_LOG_LEVEL <= LOGLVL_INF
# undef ILOG
# undef ILOGN
-# define ILOG(...) logmsg(INFO_LOG_LEVEL, NULL, __func__, __LINE__, true, \
- __VA_ARGS__)
-# define ILOGN(...) logmsg(INFO_LOG_LEVEL, NULL, __func__, __LINE__, false, \
- __VA_ARGS__)
+# define ILOG(...) logmsg(LOGLVL_INF, NULL, __func__, __LINE__, true, __VA_ARGS__)
+# define ILOGN(...) logmsg(LOGLVL_INF, NULL, __func__, __LINE__, false, __VA_ARGS__)
#endif
-#if MIN_LOG_LEVEL <= WARN_LOG_LEVEL
+#if MIN_LOG_LEVEL <= LOGLVL_WRN
# undef WLOG
# undef WLOGN
-# define WLOG(...) logmsg(WARN_LOG_LEVEL, NULL, __func__, __LINE__, true, \
- __VA_ARGS__)
-# define WLOGN(...) logmsg(WARN_LOG_LEVEL, NULL, __func__, __LINE__, false, \
- __VA_ARGS__)
+# define WLOG(...) logmsg(LOGLVL_WRN, NULL, __func__, __LINE__, true, __VA_ARGS__)
+# define WLOGN(...) logmsg(LOGLVL_WRN, NULL, __func__, __LINE__, false, __VA_ARGS__)
#endif
-#if MIN_LOG_LEVEL <= ERROR_LOG_LEVEL
+#if MIN_LOG_LEVEL <= LOGLVL_ERR
# undef ELOG
# undef ELOGN
-# define ELOG(...) logmsg(ERROR_LOG_LEVEL, NULL, __func__, __LINE__, true, \
- __VA_ARGS__)
-# define ELOGN(...) logmsg(ERROR_LOG_LEVEL, NULL, __func__, __LINE__, false, \
- __VA_ARGS__)
+# define ELOG(...) logmsg(LOGLVL_ERR, NULL, __func__, __LINE__, true, __VA_ARGS__)
+# define ELOGN(...) logmsg(LOGLVL_ERR, NULL, __func__, __LINE__, false, __VA_ARGS__)
#endif
#ifdef HAVE_EXECINFO_BACKTRACE
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index 0009420281..3ba4d0d70c 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -40,6 +40,7 @@
#include "nvim/undo.h"
#include "nvim/version.h"
#include "nvim/vim.h"
+#include "nvim/window.h"
static int in_fast_callback = 0;
@@ -1839,11 +1840,12 @@ cleanup:
xfree(info);
}
-void nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap)
+/// @param preview Invoke the callback as a |:command-preview| handler.
+int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview)
{
lua_State *const lstate = global_lstate;
- nlua_pushref(lstate, cmd->uc_luaref);
+ nlua_pushref(lstate, preview ? cmd->uc_preview_luaref : cmd->uc_luaref);
lua_newtable(lstate);
lua_pushboolean(lstate, eap->forceit == 1);
@@ -1913,7 +1915,87 @@ void nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap)
lua_pushstring(lstate, buf);
lua_setfield(lstate, -2, "mods");
- if (nlua_pcall(lstate, 1, 0)) {
+ lua_newtable(lstate); // smods table
+
+ lua_pushinteger(lstate, cmdmod.tab);
+ lua_setfield(lstate, -2, "tab");
+
+ lua_pushinteger(lstate, eap->verbose_save != -1 ? p_verbose : -1);
+ lua_setfield(lstate, -2, "verbose");
+
+ if (cmdmod.split & WSP_ABOVE) {
+ lua_pushstring(lstate, "aboveleft");
+ } else if (cmdmod.split & WSP_BELOW) {
+ lua_pushstring(lstate, "belowright");
+ } else if (cmdmod.split & WSP_TOP) {
+ lua_pushstring(lstate, "topleft");
+ } else if (cmdmod.split & WSP_BOT) {
+ lua_pushstring(lstate, "botright");
+ } else {
+ lua_pushstring(lstate, "");
+ }
+ lua_setfield(lstate, -2, "split");
+
+ lua_pushboolean(lstate, cmdmod.split & WSP_VERT);
+ lua_setfield(lstate, -2, "vertical");
+ lua_pushboolean(lstate, eap->save_msg_silent != -1 ? (msg_silent != 0) : 0);
+ lua_setfield(lstate, -2, "silent");
+ lua_pushboolean(lstate, eap->did_esilent);
+ lua_setfield(lstate, -2, "emsg_silent");
+ lua_pushboolean(lstate, eap->did_sandbox);
+ lua_setfield(lstate, -2, "sandbox");
+ lua_pushboolean(lstate, cmdmod.save_ei != NULL);
+ lua_setfield(lstate, -2, "noautocmd");
+
+ typedef struct {
+ bool *set;
+ char *name;
+ } mod_entry_T;
+ static mod_entry_T mod_entries[] = {
+ { &cmdmod.browse, "browse" },
+ { &cmdmod.confirm, "confirm" },
+ { &cmdmod.hide, "hide" },
+ { &cmdmod.keepalt, "keepalt" },
+ { &cmdmod.keepjumps, "keepjumps" },
+ { &cmdmod.keepmarks, "keepmarks" },
+ { &cmdmod.keeppatterns, "keeppatterns" },
+ { &cmdmod.lockmarks, "lockmarks" },
+ { &cmdmod.noswapfile, "noswapfile" }
+ };
+
+ // The modifiers that are simple flags
+ for (size_t i = 0; i < ARRAY_SIZE(mod_entries); i++) {
+ lua_pushboolean(lstate, *mod_entries[i].set);
+ lua_setfield(lstate, -2, mod_entries[i].name);
+ }
+
+ lua_setfield(lstate, -2, "smods");
+
+ if (preview) {
+ lua_pushinteger(lstate, cmdpreview_get_ns());
+
+ handle_T cmdpreview_bufnr = cmdpreview_get_bufnr();
+ if (cmdpreview_bufnr != 0) {
+ lua_pushinteger(lstate, cmdpreview_bufnr);
+ } else {
+ lua_pushnil(lstate);
+ }
+ }
+
+ if (nlua_pcall(lstate, preview ? 3 : 1, preview ? 1 : 0)) {
nlua_error(lstate, _("Error executing Lua callback: %.*s"));
+ return 0;
}
+
+ int retv = 0;
+
+ if (preview) {
+ if (lua_isnumber(lstate, -1) && (retv = (int)lua_tointeger(lstate, -1)) >= 0 && retv <= 2) {
+ lua_pop(lstate, 1);
+ } else {
+ retv = 0;
+ }
+ }
+
+ return retv;
}
diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c
index 6ec9cfd21d..b96193d199 100644
--- a/src/nvim/lua/treesitter.c
+++ b/src/nvim/lua/treesitter.c
@@ -391,7 +391,7 @@ static int parser_parse(lua_State *L)
return luaL_error(L, "An error occurred when parsing.");
}
- // The new tree will be pushed to the stack, without copy, owwership is now to
+ // The new tree will be pushed to the stack, without copy, ownership is now to
// the lua GC.
// Old tree is still owned by the lua GC.
uint32_t n_ranges = 0;
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 936b42be23..71c1ddfae1 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -121,7 +121,6 @@ void event_init(void)
resize_events = multiqueue_new_child(main_loop.events);
// early msgpack-rpc initialization
- msgpack_rpc_init_method_table();
msgpack_rpc_helpers_init();
input_init();
signal_init();
@@ -264,6 +263,8 @@ int main(int argc, char **argv)
nlua_init();
+ TIME_MSG("init lua interpreter");
+
if (embedded_mode) {
const char *err;
if (!channel_from_stdio(true, CALLBACK_READER_INIT, &err)) {
diff --git a/src/nvim/map.c b/src/nvim/map.c
index 05ad113008..d27e40b4ee 100644
--- a/src/nvim/map.c
+++ b/src/nvim/map.c
@@ -14,7 +14,6 @@
#include <stdlib.h>
#include <string.h>
-#include "nvim/api/private/dispatch.h"
#include "nvim/lib/khash.h"
#include "nvim/map.h"
#include "nvim/map_defs.h"
@@ -171,13 +170,10 @@ MAP_IMPL(uint64_t, ssize_t, SSIZE_INITIALIZER)
MAP_IMPL(uint64_t, uint64_t, DEFAULT_INITIALIZER)
MAP_IMPL(uint32_t, uint32_t, DEFAULT_INITIALIZER)
MAP_IMPL(handle_T, ptr_t, DEFAULT_INITIALIZER)
-#define MSGPACK_HANDLER_INITIALIZER { .fn = NULL, .fast = false }
-MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER)
MAP_IMPL(HlEntry, int, DEFAULT_INITIALIZER)
MAP_IMPL(String, handle_T, 0)
MAP_IMPL(String, int, DEFAULT_INITIALIZER)
MAP_IMPL(int, String, DEFAULT_INITIALIZER)
-MAP_IMPL(String, UIClientHandler, NULL)
MAP_IMPL(ColorKey, ColorItem, COLOR_ITEM_INITIALIZER)
diff --git a/src/nvim/map.h b/src/nvim/map.h
index 693ef50127..4f4aaa3552 100644
--- a/src/nvim/map.h
+++ b/src/nvim/map.h
@@ -4,7 +4,6 @@
#include <stdbool.h>
#include "nvim/api/private/defs.h"
-#include "nvim/api/private/dispatch.h"
#include "nvim/extmark_defs.h"
#include "nvim/highlight_defs.h"
#include "nvim/map_defs.h"
@@ -44,12 +43,10 @@ MAP_DECLS(uint64_t, uint64_t)
MAP_DECLS(uint32_t, uint32_t)
MAP_DECLS(handle_T, ptr_t)
-MAP_DECLS(String, MsgpackRpcRequestHandler)
MAP_DECLS(HlEntry, int)
MAP_DECLS(String, handle_T)
MAP_DECLS(String, int)
MAP_DECLS(int, String)
-MAP_DECLS(String, UIClientHandler)
MAP_DECLS(ColorKey, ColorItem)
diff --git a/src/nvim/match.c b/src/nvim/match.c
index 54f3bff472..4bfc06d7e5 100644
--- a/src/nvim/match.c
+++ b/src/nvim/match.c
@@ -873,11 +873,11 @@ void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
int i;
win_T *win = get_optional_window(argvars, 0);
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
if (win == NULL) {
return;
}
- tv_list_alloc_ret(rettv, kListLenMayKnow);
cur = win->w_match_head;
while (cur != NULL) {
dict_T *dict = tv_dict_alloc();
diff --git a/src/nvim/move.c b/src/nvim/move.c
index 9f72b36638..8c927d30d1 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -108,14 +108,14 @@ void redraw_for_cursorline(win_T *wp)
}
/// Redraw when w_virtcol changes and 'cursorcolumn' is set or 'cursorlineopt'
-/// contains "screenline" or when the 'CurSearch' highlight is in use.
+/// contains "screenline" or when the "CurSearch" highlight is in use.
/// Also when concealing is on and 'concealcursor' is active.
static void redraw_for_cursorcolumn(win_T *wp)
FUNC_ATTR_NONNULL_ALL
{
if ((wp->w_valid & VALID_VIRTCOL) == 0 && !pum_visible()) {
if (wp->w_p_cuc || ((HL_ATTR(HLF_LC) || wp->w_hl_ids[HLF_LC]) && using_hlsearch())) {
- // When 'cursorcolumn' is set or 'CurSearch' is in use
+ // When 'cursorcolumn' is set or "CurSearch" is in use
// need to redraw with SOME_VALID.
redraw_later(wp, SOME_VALID);
} else if (wp->w_p_cul && (wp->w_p_culopt_flags & CULOPT_SCRLINE)) {
diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c
index 79ecd9f827..79a9e1082d 100644
--- a/src/nvim/msgpack_rpc/channel.c
+++ b/src/nvim/msgpack_rpc/channel.c
@@ -31,7 +31,7 @@
#include "nvim/ui.h"
#include "nvim/vim.h"
-#if MIN_LOG_LEVEL > DEBUG_LOG_LEVEL
+#if MIN_LOG_LEVEL > LOGLVL_DBG
# define log_client_msg(...)
# define log_server_msg(...)
#endif
@@ -55,14 +55,15 @@ void rpc_start(Channel *channel)
channel->is_rpc = true;
RpcState *rpc = &channel->rpc;
rpc->closed = false;
- rpc->unpacker = msgpack_unpacker_new(MSGPACK_UNPACKER_INIT_BUFFER_SIZE);
+ rpc->unpacker = xcalloc(1, sizeof *rpc->unpacker);
+ unpacker_init(rpc->unpacker);
rpc->next_request_id = 1;
rpc->info = (Dictionary)ARRAY_DICT_INIT;
kv_init(rpc->call_stack);
if (channel->streamtype != kChannelStreamInternal) {
Stream *out = channel_outstream(channel);
-#if MIN_LOG_LEVEL <= DEBUG_LOG_LEVEL
+#if MIN_LOG_LEVEL <= LOGLVL_DBG
Stream *in = channel_instream(channel);
DLOG("rpc ch %" PRIu64 " in-stream=%p out-stream=%p", channel->id,
(void *)in, (void *)out);
@@ -209,20 +210,20 @@ static void receive_msgpack(Stream *stream, RBuffer *rbuf, size_t c, void *data,
char buf[256];
snprintf(buf, sizeof(buf), "ch %" PRIu64 " was closed by the client",
channel->id);
- call_set_error(channel, buf, INFO_LOG_LEVEL);
+ chan_close_with_error(channel, buf, LOGLVL_INF);
goto end;
}
- size_t count = rbuffer_size(rbuf);
DLOG("ch %" PRIu64 ": parsing %zu bytes from msgpack Stream: %p",
- channel->id, count, (void *)stream);
-
- // Feed the unpacker with data
- msgpack_unpacker_reserve_buffer(channel->rpc.unpacker, count);
- rbuffer_read(rbuf, msgpack_unpacker_buffer(channel->rpc.unpacker), count);
- msgpack_unpacker_buffer_consumed(channel->rpc.unpacker, count);
+ channel->id, rbuffer_size(rbuf), (void *)stream);
+ Unpacker *p = channel->rpc.unpacker;
+ size_t size = 0;
+ p->read_ptr = rbuffer_read_ptr(rbuf, &size);
+ p->read_size = size;
parse_msgpack(channel);
+ size_t consumed = size - p->read_size;
+ rbuffer_consumed_compact(rbuf, consumed);
end:
channel_decref(channel);
@@ -230,111 +231,70 @@ end:
static void parse_msgpack(Channel *channel)
{
- msgpack_unpacked unpacked;
- msgpack_unpacked_init(&unpacked);
- msgpack_unpack_return result;
-
- // Deserialize everything we can.
- while ((result = msgpack_unpacker_next(channel->rpc.unpacker, &unpacked)) ==
- MSGPACK_UNPACK_SUCCESS) {
- bool is_response = is_rpc_response(&unpacked.data);
- log_client_msg(channel->id, !is_response, unpacked.data);
-
- if (is_response) {
- if (is_valid_rpc_response(&unpacked.data, channel)) {
- complete_call(&unpacked.data, channel);
- } else {
+ Unpacker *p = channel->rpc.unpacker;
+ while (unpacker_advance(p)) {
+ if (p->type == kMessageTypeResponse) {
+ ChannelCallFrame *frame = kv_last(channel->rpc.call_stack);
+ if (p->request_id != frame->request_id) {
char buf[256];
snprintf(buf, sizeof(buf),
"ch %" PRIu64 " returned a response with an unknown request "
"id. Ensure the client is properly synchronized",
channel->id);
- call_set_error(channel, buf, ERROR_LOG_LEVEL);
+ chan_close_with_error(channel, buf, LOGLVL_ERR);
+ }
+ frame->returned = true;
+ frame->errored = (p->error.type != kObjectTypeNil);
+
+ if (frame->errored) {
+ frame->result = p->error;
+ // TODO(bfredl): p->result should not even be decoded
+ api_free_object(p->result);
+ } else {
+ frame->result = p->result;
}
- msgpack_unpacked_destroy(&unpacked);
} else {
- handle_request(channel, &unpacked.data);
- }
- }
+ log_client_msg(channel->id, p->type == kMessageTypeRequest, p->handler.name);
- if (result == MSGPACK_UNPACK_NOMEM_ERROR) {
- mch_errmsg(e_outofmem);
- mch_errmsg("\n");
- channel_decref(channel);
- preserve_exit();
+ Object res = p->result;
+ if (p->result.type != kObjectTypeArray) {
+ chan_close_with_error(channel, "msgpack-rpc request args has to be an array", LOGLVL_ERR);
+ api_free_object(p->result);
+ return;
+ }
+ Array arg = res.data.array;
+ handle_request(channel, p, arg);
+ }
}
- if (result == MSGPACK_UNPACK_PARSE_ERROR) {
- // See src/msgpack/unpack_template.h in msgpack source tree for
- // causes for this error(search for 'goto _failed')
- //
- // A not so uncommon cause for this might be deserializing objects with
- // a high nesting level: msgpack will break when its internal parse stack
- // size exceeds MSGPACK_EMBED_STACK_SIZE (defined as 32 by default)
- send_error(channel, kMessageTypeRequest, 0,
- "Invalid msgpack payload. "
- "This error can also happen when deserializing "
- "an object with high level of nesting");
+ if (unpacker_closed(p)) {
+ chan_close_with_error(channel, p->unpack_error.msg, LOGLVL_ERR);
+ api_clear_error(&p->unpack_error);
}
}
/// Handles requests and notifications received on the channel.
-static void handle_request(Channel *channel, msgpack_object *request)
+static void handle_request(Channel *channel, Unpacker *p, Array args)
FUNC_ATTR_NONNULL_ALL
{
- uint32_t request_id;
- Error error = ERROR_INIT;
- MessageType type = msgpack_rpc_validate(&request_id, request, &error);
-
- if (ERROR_SET(&error)) {
- // Validation failed, send response with error
- if (channel_write(channel,
- serialize_response(channel->id,
- type,
- request_id,
- &error,
- NIL,
- &out_buffer))) {
- char buf[256];
- snprintf(buf, sizeof(buf),
- "ch %" PRIu64 " sent an invalid message, closed.",
- channel->id);
- call_set_error(channel, buf, ERROR_LOG_LEVEL);
- }
- api_clear_error(&error);
- return;
- }
- assert(type == kMessageTypeRequest || type == kMessageTypeNotification);
-
- MsgpackRpcRequestHandler handler;
- msgpack_object *method = msgpack_rpc_method(request);
- handler = msgpack_rpc_get_handler_for(method->via.bin.ptr,
- method->via.bin.size,
- &error);
+ assert(p->type == kMessageTypeRequest || p->type == kMessageTypeNotification);
- // check method arguments
- Array args = ARRAY_DICT_INIT;
- if (!ERROR_SET(&error)
- && !msgpack_rpc_to_array(msgpack_rpc_args(request), &args)) {
- api_set_error(&error, kErrorTypeException, "Invalid method arguments");
- }
-
- if (ERROR_SET(&error)) {
- send_error(channel, type, request_id, error.msg);
- api_clear_error(&error);
+ if (!p->handler.fn) {
+ send_error(channel, p->type, p->request_id, p->unpack_error.msg);
+ api_clear_error(&p->unpack_error);
api_free_array(args);
return;
}
RequestEvent *evdata = xmalloc(sizeof(RequestEvent));
- evdata->type = type;
+ evdata->type = p->type;
evdata->channel = channel;
- evdata->handler = handler;
+ evdata->handler = p->handler;
evdata->args = args;
- evdata->request_id = request_id;
+ evdata->request_id = p->request_id;
channel_incref(channel);
- if (handler.fast) {
- bool is_get_mode = handler.fn == handle_nvim_get_mode;
+ if (p->handler.fast) {
+ bool is_get_mode = p->handler.fn == handle_nvim_get_mode;
if (is_get_mode && !input_blocking()) {
// Defer the event to a special queue used by os/input.c. #6247
@@ -344,7 +304,7 @@ static void handle_request(Channel *channel, msgpack_object *request)
request_event((void **)&evdata);
}
} else {
- bool is_resize = handler.fn == handle_nvim_ui_try_resize;
+ bool is_resize = p->handler.fn == handle_nvim_ui_try_resize;
if (is_resize) {
Event ev = event_create_oneshot(event_create(request_event, 1, evdata),
2);
@@ -352,7 +312,7 @@ static void handle_request(Channel *channel, msgpack_object *request)
multiqueue_put_event(resize_events, ev);
} else {
multiqueue_put(channel->events, request_event, 1, evdata);
- DLOG("RPC: scheduled %.*s", method->via.bin.size, method->via.bin.ptr);
+ DLOG("RPC: scheduled %.*s", (int)p->method_name_len, p->handler.name);
}
}
}
@@ -418,7 +378,7 @@ static bool channel_write(Channel *channel, WBuffer *buffer)
"ch %" PRIu64 ": stream write failed. "
"RPC canceled; closing channel",
channel->id);
- call_set_error(channel, buf, ERROR_LOG_LEVEL);
+ chan_close_with_error(channel, buf, LOGLVL_ERR);
}
return success;
@@ -428,14 +388,19 @@ static void internal_read_event(void **argv)
{
Channel *channel = argv[0];
WBuffer *buffer = argv[1];
+ Unpacker *p = channel->rpc.unpacker;
- msgpack_unpacker_reserve_buffer(channel->rpc.unpacker, buffer->size);
- memcpy(msgpack_unpacker_buffer(channel->rpc.unpacker),
- buffer->data, buffer->size);
- msgpack_unpacker_buffer_consumed(channel->rpc.unpacker, buffer->size);
-
+ p->read_ptr = buffer->data;
+ p->read_size = buffer->size;
parse_msgpack(channel);
+ if (p->read_size) {
+ // This should not happen, as WBuffer is one single serialized message.
+ if (!channel->rpc.closed) {
+ chan_close_with_error(channel, "internal channel: internal error", LOGLVL_ERR);
+ }
+ }
+
channel_decref(channel);
wstream_release_wbuffer(buffer);
}
@@ -558,7 +523,7 @@ static void exit_event(void **argv)
void rpc_free(Channel *channel)
{
remote_ui_disconnect(channel->id);
- msgpack_unpacker_free(channel->rpc.unpacker);
+ xfree(channel->rpc.unpacker);
// Unsubscribe from all events
char *event_string;
@@ -571,41 +536,7 @@ void rpc_free(Channel *channel)
api_free_dictionary(channel->rpc.info);
}
-static bool is_rpc_response(msgpack_object *obj)
-{
- return obj->type == MSGPACK_OBJECT_ARRAY
- && obj->via.array.size == 4
- && obj->via.array.ptr[0].type == MSGPACK_OBJECT_POSITIVE_INTEGER
- && obj->via.array.ptr[0].via.u64 == 1
- && obj->via.array.ptr[1].type == MSGPACK_OBJECT_POSITIVE_INTEGER;
-}
-
-static bool is_valid_rpc_response(msgpack_object *obj, Channel *channel)
-{
- uint32_t response_id = (uint32_t)obj->via.array.ptr[1].via.u64;
- if (kv_size(channel->rpc.call_stack) == 0) {
- return false;
- }
-
- // Must be equal to the frame at the stack's bottom
- ChannelCallFrame *frame = kv_last(channel->rpc.call_stack);
- return response_id == frame->request_id;
-}
-
-static void complete_call(msgpack_object *obj, Channel *channel)
-{
- ChannelCallFrame *frame = kv_last(channel->rpc.call_stack);
- frame->returned = true;
- frame->errored = obj->via.array.ptr[2].type != MSGPACK_OBJECT_NIL;
-
- if (frame->errored) {
- msgpack_rpc_to_object(&obj->via.array.ptr[2], &frame->result);
- } else {
- msgpack_rpc_to_object(&obj->via.array.ptr[3], &frame->result);
- }
-}
-
-static void call_set_error(Channel *channel, char *msg, int loglevel)
+static void chan_close_with_error(Channel *channel, char *msg, int loglevel)
{
LOG(loglevel, "RPC: %s", msg);
for (size_t i = 0; i < kv_size(channel->rpc.call_stack); i++) {
@@ -693,7 +624,7 @@ const char *rpc_client_name(Channel *chan)
return NULL;
}
-#if MIN_LOG_LEVEL <= DEBUG_LOG_LEVEL
+#if MIN_LOG_LEVEL <= LOGLVL_DBG
# define REQ "[request] "
# define RES "[response] "
# define NOT "[notify] "
@@ -723,7 +654,8 @@ static void log_server_msg(uint64_t channel_id, msgpack_sbuffer *packed)
log_lock();
FILE *f = open_log_file();
fprintf(f, type ? (type == 1 ? RES : NOT) : REQ);
- log_msg_close(f, unpacked.data);
+ msgpack_object_print(f, unpacked.data);
+ log_close(f);
msgpack_unpacked_destroy(&unpacked);
break;
}
@@ -734,30 +666,24 @@ static void log_server_msg(uint64_t channel_id, msgpack_sbuffer *packed)
log_lock();
FILE *f = open_log_file();
fprintf(f, ERR);
- log_msg_close(f, (msgpack_object) {
- .type = MSGPACK_OBJECT_STR,
- .via.str = {
- .ptr = (char *)msgpack_error_messages[result + MUR_OFF],
- .size = (uint32_t)strlen(msgpack_error_messages[result + MUR_OFF]),
- },
- });
+ fprintf(f, "%s", msgpack_error_messages[result + MUR_OFF]);
+ log_close(f);
break;
}
}
}
-static void log_client_msg(uint64_t channel_id, bool is_request, msgpack_object msg)
+static void log_client_msg(uint64_t channel_id, bool is_request, const char *name)
{
DLOGN("RPC <-ch %" PRIu64 ": ", channel_id);
log_lock();
FILE *f = open_log_file();
- fprintf(f, is_request ? REQ : RES);
- log_msg_close(f, msg);
+ fprintf(f, "%s: %s", is_request ? REQ : RES, name);
+ log_close(f);
}
-static void log_msg_close(FILE *f, msgpack_object msg)
+static void log_close(FILE *f)
{
- msgpack_object_print(f, msg);
fputc('\n', f);
fflush(f);
fclose(f);
diff --git a/src/nvim/msgpack_rpc/channel_defs.h b/src/nvim/msgpack_rpc/channel_defs.h
index 6647779db9..4dc3c7f22d 100644
--- a/src/nvim/msgpack_rpc/channel_defs.h
+++ b/src/nvim/msgpack_rpc/channel_defs.h
@@ -6,8 +6,10 @@
#include <uv.h>
#include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/event/process.h"
#include "nvim/event/socket.h"
+#include "nvim/msgpack_rpc/unpacker.h"
#include "nvim/vim.h"
typedef struct Channel Channel;
@@ -29,7 +31,7 @@ typedef struct {
typedef struct {
PMap(cstr_t) subscribed_events[1];
bool closed;
- msgpack_unpacker *unpacker;
+ Unpacker *unpacker;
uint32_t next_request_id;
kvec_t(ChannelCallFrame *) call_stack;
Dictionary info;
diff --git a/src/nvim/msgpack_rpc/unpacker.c b/src/nvim/msgpack_rpc/unpacker.c
new file mode 100644
index 0000000000..e60d9f220f
--- /dev/null
+++ b/src/nvim/msgpack_rpc/unpacker.c
@@ -0,0 +1,310 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
+#include "nvim/api/private/helpers.h"
+#include "nvim/log.h"
+#include "nvim/msgpack_rpc/helpers.h"
+#include "nvim/msgpack_rpc/unpacker.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "msgpack_rpc/unpacker.c.generated.h"
+#endif
+
+Object unpack(const char *data, size_t size, Error *err)
+{
+ Unpacker unpacker;
+ mpack_parser_init(&unpacker.parser, 0);
+ unpacker.parser.data.p = &unpacker;
+
+ int result = mpack_parse(&unpacker.parser, &data, &size,
+ api_parse_enter, api_parse_exit);
+
+ if (result == MPACK_NOMEM) {
+ api_set_error(err, kErrorTypeException, "object was too deep to unpack");
+ } else if (result == MPACK_EOF) {
+ api_set_error(err, kErrorTypeException, "incomplete msgpack string");
+ } else if (result == MPACK_ERROR) {
+ api_set_error(err, kErrorTypeException, "invalid msgpack string");
+ } else if (result == MPACK_OK && size) {
+ api_set_error(err, kErrorTypeException, "trailing data in msgpack string");
+ }
+
+ return unpacker.result;
+}
+
+static void api_parse_enter(mpack_parser_t *parser, mpack_node_t *node)
+{
+ Unpacker *unpacker = parser->data.p;
+ Object *result = NULL;
+ String *key_location = NULL;
+
+ mpack_node_t *parent = MPACK_PARENT_NODE(node);
+ if (parent) {
+ switch (parent->tok.type) {
+ case MPACK_TOKEN_ARRAY: {
+ Object *obj = parent->data[0].p;
+ result = &kv_A(obj->data.array, parent->pos);
+ break;
+ }
+ case MPACK_TOKEN_MAP: {
+ Object *obj = parent->data[0].p;
+ KeyValuePair *kv = &kv_A(obj->data.dictionary, parent->pos);
+ if (!parent->key_visited) {
+ // TODO(bfredl): when implementing interrupt parse on error,
+ // stop parsing here when node is not a STR/BIN
+ kv->key = (String)STRING_INIT;
+ key_location = &kv->key;
+ }
+ result = &kv->value;
+ break;
+ }
+
+ case MPACK_TOKEN_STR:
+ case MPACK_TOKEN_BIN:
+ case MPACK_TOKEN_EXT:
+ assert(node->tok.type == MPACK_TOKEN_CHUNK);
+ break;
+
+ default:
+ abort();
+ }
+ } else {
+ result = &unpacker->result;
+ }
+
+ switch (node->tok.type) {
+ case MPACK_TOKEN_NIL:
+ *result = NIL;
+ break;
+ case MPACK_TOKEN_BOOLEAN:
+ *result = BOOL(mpack_unpack_boolean(node->tok));
+ break;
+ case MPACK_TOKEN_SINT:
+ *result = INTEGER_OBJ(mpack_unpack_sint(node->tok));
+ break;
+ case MPACK_TOKEN_UINT:
+ *result = INTEGER_OBJ((Integer)mpack_unpack_uint(node->tok));
+ break;
+ case MPACK_TOKEN_FLOAT:
+ *result = FLOAT_OBJ(mpack_unpack_float(node->tok));
+ break;
+ case MPACK_TOKEN_BIN:
+ case MPACK_TOKEN_STR: {
+ String str = { .data = xmallocz(node->tok.length), .size = node->tok.length };
+
+ if (key_location) {
+ *key_location = str;
+ } else {
+ *result = STRING_OBJ(str);
+ }
+
+ node->data[0].p = str.data;
+ break;
+ }
+ case MPACK_TOKEN_EXT:
+ // handled in chunk; but save result location
+ node->data[0].p = result;
+ break;
+
+ case MPACK_TOKEN_CHUNK:
+ assert(parent);
+ if (parent->tok.type == MPACK_TOKEN_STR || parent->tok.type == MPACK_TOKEN_BIN) {
+ char *data = parent->data[0].p;
+ memcpy(data + parent->pos,
+ node->tok.data.chunk_ptr, node->tok.length);
+ } else {
+ Object *res = parent->data[0].p;
+
+ size_t endlen = parent->pos + node->tok.length;
+ if (endlen > MAX_EXT_LEN) {
+ *res = NIL;
+ break;
+ }
+ memcpy(unpacker->ext_buf + parent->pos,
+ node->tok.data.chunk_ptr, node->tok.length);
+ if (parent->pos + node->tok.length < parent->tok.length) {
+ break; // EOF, let's get back to it later
+ }
+ const char *buf = unpacker->ext_buf;
+ size_t size = parent->tok.length;
+ mpack_token_t ext_tok;
+ int status = mpack_rtoken(&buf, &size, &ext_tok);
+ if (status || ext_tok.type != MPACK_TOKEN_UINT) {
+ // TODO(bfredl): once we fixed memory management, we can set
+ // p->unpack_error and a flag like p->interrupted
+ *res = NIL;
+ break;
+ }
+ int ext_type = parent->tok.data.ext_type;
+ if (0 <= ext_type && ext_type <= EXT_OBJECT_TYPE_MAX) {
+ res->type = (ObjectType)(ext_type + EXT_OBJECT_TYPE_SHIFT);
+ res->data.integer = (int64_t)mpack_unpack_uint(ext_tok);
+ } else {
+ *res = NIL;
+ break;
+ }
+ }
+ break;
+
+ case MPACK_TOKEN_ARRAY: {
+ Array arr = KV_INITIAL_VALUE;
+ kv_resize(arr, node->tok.length);
+ kv_size(arr) = node->tok.length;
+ *result = ARRAY_OBJ(arr);
+ node->data[0].p = result;
+ break;
+ }
+ case MPACK_TOKEN_MAP: {
+ Dictionary dict = KV_INITIAL_VALUE;
+ kv_resize(dict, node->tok.length);
+ kv_size(dict) = node->tok.length;
+ *result = DICTIONARY_OBJ(dict);
+ node->data[0].p = result;
+ break;
+ }
+ default:
+ abort();
+ }
+}
+
+static void api_parse_exit(mpack_parser_t *parser, mpack_node_t *node)
+{}
+
+void unpacker_init(Unpacker *p)
+{
+ mpack_parser_init(&p->parser, 0);
+ p->parser.data.p = p;
+ mpack_tokbuf_init(&p->reader);
+ p->unpack_error = (Error)ERROR_INIT;
+}
+
+bool unpacker_parse_header(Unpacker *p)
+{
+ mpack_token_t tok;
+ int result;
+
+ const char *data = p->read_ptr;
+ size_t size = p->read_size;
+
+ assert(!ERROR_SET(&p->unpack_error));
+
+#define NEXT(tok) \
+ result = mpack_read(&p->reader, &data, &size, &tok); \
+ if (result) { goto error; }
+
+ NEXT(tok);
+ if (tok.type != MPACK_TOKEN_ARRAY || tok.length < 3 || tok.length > 4) {
+ goto error;
+ }
+ size_t array_length = tok.length;
+
+ NEXT(tok);
+ if (tok.type != MPACK_TOKEN_UINT) {
+ goto error;
+ }
+ uint32_t type = (uint32_t)mpack_unpack_uint(tok);
+ if ((array_length == 3) ? type != 2 : (type >= 2)) {
+ goto error;
+ }
+ p->type = (MessageType)type;
+ p->request_id = 0;
+
+ if (p->type != kMessageTypeNotification) {
+ NEXT(tok);
+ if (tok.type != MPACK_TOKEN_UINT) {
+ goto error;
+ }
+ p->request_id = (uint32_t)mpack_unpack_uint(tok);
+ }
+
+ if (p->type != kMessageTypeResponse) {
+ NEXT(tok);
+ if ((tok.type != MPACK_TOKEN_STR && tok.type != MPACK_TOKEN_BIN)
+ || tok.length > 100) {
+ goto error;
+ }
+ p->method_name_len = tok.length;
+
+ if (p->method_name_len > 0) {
+ NEXT(tok);
+ assert(tok.type == MPACK_TOKEN_CHUNK);
+ }
+ if (tok.length < p->method_name_len) {
+ result = MPACK_EOF;
+ goto error;
+ }
+ // if this fails, p->handler.fn will be NULL
+ p->handler = msgpack_rpc_get_handler_for(tok.length ? tok.data.chunk_ptr : "",
+ tok.length, &p->unpack_error);
+ }
+
+ p->read_ptr = data;
+ p->read_size = size;
+ return true;
+#undef NEXT
+
+error:
+ if (result == MPACK_EOF) {
+ // recover later by retrying from scratch
+ // when more data is available.
+ mpack_tokbuf_init(&p->reader);
+ } else {
+ api_set_error(&p->unpack_error, kErrorTypeValidation, "failed to decode msgpack");
+ p->state = -1;
+ }
+ return false;
+}
+
+// BASIC BITCH STATE MACHINE
+//
+// With some basic assumptions, we can parse the overall structure of msgpack-rpc
+// messages with a hand-rolled FSM of just 3 states (<x> = p->state):
+//
+// <0>[0, request_id, method_name, <2>args]
+// <0>[1, request_id, <1>err, <2>result]
+// <0>[2, method_name, <2>args]
+//
+// The assumption here is that the header of the message, which we define as the
+// initial array head, the kind integer, request_id and/or method name (when needed),
+// is relatively small, just ~10 bytes + the method name. Thus we can simply refuse
+// to advance the stream beyond the header until it can be parsed in its entirety.
+//
+// Of course, later on, we want to specialize state 2 into sub-states depending
+// on the specific method. "nvim_exec_lua" should just decode direct into lua
+// objects, and "redraw/grid_line" should use a hand-rolled decoder to avoid
+// a blizzard of small objects for each screen cell.
+
+bool unpacker_advance(Unpacker *p)
+{
+ assert(p->state >= 0);
+ if (p->state == 0) {
+ if (!unpacker_parse_header(p)) {
+ return false;
+ }
+ p->state = p->type == kMessageTypeResponse ? 1 : 2;
+ }
+
+ int result;
+
+rerun:
+ result = mpack_parse(&p->parser, &p->read_ptr, &p->read_size,
+ api_parse_enter, api_parse_exit);
+
+ if (result == MPACK_EOF) {
+ return false;
+ } else if (result != MPACK_OK) {
+ api_set_error(&p->unpack_error, kErrorTypeValidation, "failed to parse msgpack");
+ p->state = -1;
+ return false;
+ }
+
+ if (p->state == 1) {
+ p->error = p->result;
+ p->state = 2;
+ goto rerun;
+ } else {
+ assert(p->state == 2);
+ p->state = 0;
+ }
+ return true;
+}
diff --git a/src/nvim/msgpack_rpc/unpacker.h b/src/nvim/msgpack_rpc/unpacker.h
new file mode 100644
index 0000000000..bbd6b1ef4f
--- /dev/null
+++ b/src/nvim/msgpack_rpc/unpacker.h
@@ -0,0 +1,40 @@
+#ifndef NVIM_MSGPACK_RPC_UNPACKER_H
+#define NVIM_MSGPACK_RPC_UNPACKER_H
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include "mpack/mpack_core.h"
+#include "mpack/object.h"
+#include "nvim/api/private/dispatch.h"
+#include "nvim/api/private/helpers.h"
+
+typedef struct {
+ mpack_parser_t parser;
+ mpack_tokbuf_t reader;
+
+ const char *read_ptr;
+ size_t read_size;
+
+#define MAX_EXT_LEN 9 // byte + 8-byte integer
+ char ext_buf[MAX_EXT_LEN];
+
+ int state;
+ MessageType type;
+ uint32_t request_id;
+ size_t method_name_len;
+ MsgpackRpcRequestHandler handler;
+ Object error; // error return
+ Object result; // arg list or result
+ Error unpack_error;
+} Unpacker;
+
+// unrecovareble error. unpack_error should be set!
+#define unpacker_closed(p) ((p)->state < 0)
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "msgpack_rpc/unpacker.h.generated.h"
+#endif
+
+#endif // NVIM_MSGPACK_RPC_UNPACKER_H
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 3b91044f41..bf0ea2aeec 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -3917,7 +3917,7 @@ static void nv_regreplay(cmdarg_T *cap)
}
}
-/// Handle a ":" command and <Cmd> or Lua keymaps.
+/// Handle a ":" command and <Cmd> or Lua mappings.
static void nv_colon(cmdarg_T *cap)
{
bool cmd_result;
diff --git a/src/nvim/po/cleanup.vim b/src/nvim/po/cleanup.vim
index b27d88092f..ff5579a67a 100644
--- a/src/nvim/po/cleanup.vim
+++ b/src/nvim/po/cleanup.vim
@@ -12,6 +12,9 @@ setl nodiff
silent g/^#, c-format\n#/.d
silent g/^#\..*\n#/.d
+" c-format comments have no effect, the check.vim scripts checks it.
+silent g/^#, c-format$/d
+
silent g/^#[:~] /d
silent g/^#, fuzzy\(, .*\)\=\nmsgid ""\@!/.+1,/^$/-1s/^/#\~ /
silent g/^msgstr"/s//msgstr "/
diff --git a/src/nvim/rbuffer.c b/src/nvim/rbuffer.c
index d280e08c03..6407ac172e 100644
--- a/src/nvim/rbuffer.c
+++ b/src/nvim/rbuffer.c
@@ -154,6 +154,23 @@ void rbuffer_consumed(RBuffer *buf, size_t count)
}
}
+/// Use instead of rbuffer_consumed to use rbuffer in a linear, non-cyclic fashion.
+///
+/// This is generally usefull if we can guarantee to parse all input
+/// except some small incomplete token, like when parsing msgpack.
+void rbuffer_consumed_compact(RBuffer *buf, size_t count)
+ FUNC_ATTR_NONNULL_ALL
+{
+ assert(buf->read_ptr <= buf->write_ptr);
+ rbuffer_consumed(buf, count);
+ if (buf->read_ptr > buf->start_ptr) {
+ assert((size_t)(buf->read_ptr - buf->write_ptr) == buf->size);
+ memmove(buf->start_ptr, buf->read_ptr, buf->size);
+ buf->read_ptr = buf->start_ptr;
+ buf->write_ptr = buf->read_ptr + buf->size;
+ }
+}
+
// Higher level functions for copying from/to RBuffer instances and data
// pointers
size_t rbuffer_write(RBuffer *buf, const char *src, size_t src_size)
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index 6b8c75e430..44c9928f7b 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -1653,21 +1653,22 @@ static void clear_submatch_list(staticList10_T *sl)
/// vim_regsub() - perform substitutions after a vim_regexec() or
/// vim_regexec_multi() match.
///
-/// If "copy" is true really copy into "dest".
-/// If "copy" is false nothing is copied, this is just to find out the length
-/// of the result.
+/// If "flags" has REGSUB_COPY really copy into "dest[destlen]".
+/// Oterwise nothing is copied, only compue the length of the result.
///
-/// If "backslash" is true, a backslash will be removed later, need to double
-/// them to keep them, and insert a backslash before a CR to avoid it being
-/// replaced with a line break later.
+/// If "flags" has REGSUB_MAGIC then behave like 'magic' is set.
+///
+/// If "flags" has REGSUB_BACKSLASH a backslash will be removed later, need to
+/// double them to keep them, and insert a backslash before a CR to avoid it
+/// being replaced with a line break later.
///
/// Note: The matched text must not change between the call of
/// vim_regexec()/vim_regexec_multi() and vim_regsub()! It would make the back
/// references invalid!
///
/// Returns the size of the replacement, including terminating NUL.
-int vim_regsub(regmatch_T *rmp, char_u *source, typval_T *expr, char_u *dest, int copy, int magic,
- int backslash)
+int vim_regsub(regmatch_T *rmp, char_u *source, typval_T *expr, char_u *dest, int destlen,
+ int flags)
{
regexec_T rex_save;
bool rex_in_use_save = rex_in_use;
@@ -1683,7 +1684,7 @@ int vim_regsub(regmatch_T *rmp, char_u *source, typval_T *expr, char_u *dest, in
rex.reg_maxline = 0;
rex.reg_buf = curbuf;
rex.reg_line_lbr = true;
- int result = vim_regsub_both(source, expr, dest, copy, magic, backslash);
+ int result = vim_regsub_both(source, expr, dest, destlen, flags);
rex_in_use = rex_in_use_save;
if (rex_in_use) {
@@ -1693,8 +1694,8 @@ int vim_regsub(regmatch_T *rmp, char_u *source, typval_T *expr, char_u *dest, in
return result;
}
-int vim_regsub_multi(regmmatch_T *rmp, linenr_T lnum, char_u *source, char_u *dest, int copy,
- int magic, int backslash)
+int vim_regsub_multi(regmmatch_T *rmp, linenr_T lnum, char_u *source, char_u *dest, int destlen,
+ int flags)
{
regexec_T rex_save;
bool rex_in_use_save = rex_in_use;
@@ -1711,7 +1712,7 @@ int vim_regsub_multi(regmmatch_T *rmp, linenr_T lnum, char_u *source, char_u *de
rex.reg_firstlnum = lnum;
rex.reg_maxline = curbuf->b_ml.ml_line_count - lnum;
rex.reg_line_lbr = false;
- int result = vim_regsub_both(source, NULL, dest, copy, magic, backslash);
+ int result = vim_regsub_both(source, NULL, dest, destlen, flags);
rex_in_use = rex_in_use_save;
if (rex_in_use) {
@@ -1721,8 +1722,7 @@ int vim_regsub_multi(regmmatch_T *rmp, linenr_T lnum, char_u *source, char_u *de
return result;
}
-static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int copy, int magic,
- int backslash)
+static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int destlen, int flags)
{
char_u *src;
char_u *dst;
@@ -1735,6 +1735,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int cop
linenr_T clnum = 0; // init for GCC
int len = 0; // init for GCC
static char_u *eval_result = NULL;
+ bool copy = flags & REGSUB_COPY;
// We need to keep track of how many backslashes we escape, so that the byte
// counts for `extmark_splice` are correct.
@@ -1755,8 +1756,8 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int cop
if (expr != NULL || (source[0] == '\\' && source[1] == '=')) {
// To make sure that the length doesn't change between checking the
// length and copying the string, and to speed up things, the
- // resulting string is saved from the call with "copy" == false to the
- // call with "copy" == true.
+ // resulting string is saved from the call with "flags & REGSUB_COPY"
+ // == 0 to the call with "flags & REGSUB_COPY" != 0.
if (copy) {
if (eval_result != NULL) {
STRCPY(dest, eval_result);
@@ -1845,7 +1846,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int cop
had_backslash = true;
}
}
- if (had_backslash && backslash) {
+ if (had_backslash && (flags & REGSUB_BACKSLASH)) {
// Backslashes will be consumed, need to double them.
s = vim_strsave_escaped(eval_result, (char_u *)"\\");
xfree(eval_result);
@@ -1862,11 +1863,11 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int cop
}
} else {
while ((c = *src++) != NUL) {
- if (c == '&' && magic) {
+ if (c == '&' && (flags & REGSUB_MAGIC)) {
no = 0;
} else if (c == '\\' && *src != NUL) {
- if (*src == '&' && !magic) {
- ++src;
+ if (*src == '&' && !(flags & REGSUB_MAGIC)) {
+ src++;
no = 0;
} else if ('0' <= *src && *src <= '9') {
no = *src++ - '0';
@@ -1895,6 +1896,10 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int cop
if (c == K_SPECIAL && src[0] != NUL && src[1] != NUL) {
// Copy a special key as-is.
if (copy) {
+ if (dst + 3 > dest + destlen) {
+ iemsg("vim_regsub_both(): not enough space");
+ return 0;
+ }
*dst++ = c;
*dst++ = *src++;
*dst++ = *src++;
@@ -1922,9 +1927,13 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int cop
// If "backslash" is true the backslash will be removed
// later. Used to insert a literal CR.
default:
- if (backslash) {
+ if (flags & REGSUB_BACKSLASH) {
num_escaped += 1;
if (copy) {
+ if (dst + 1 > dest + destlen) {
+ iemsg("vim_regsub_both(): not enough space");
+ return 0;
+ }
*dst = '\\';
}
dst++;
@@ -1945,17 +1954,26 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int cop
}
int totlen = utfc_ptr2len((char *)src - 1);
+ int charlen = utf_char2len(cc);
if (copy) {
+ if (dst + charlen > dest + destlen) {
+ iemsg("vim_regsub_both(): not enough space");
+ return 0;
+ }
utf_char2bytes(cc, (char *)dst);
}
- dst += utf_char2len(cc) - 1;
+ dst += charlen - 1;
int clen = utf_ptr2len((char *)src - 1);
// If the character length is shorter than "totlen", there
// are composing characters; copy them as-is.
if (clen < totlen) {
if (copy) {
+ if (dst + totlen - clen > dest + destlen) {
+ iemsg("vim_regsub_both(): not enough space");
+ return 0;
+ }
memmove(dst + 1, src - 1 + clen, (size_t)(totlen - clen));
}
dst += totlen - clen;
@@ -1992,6 +2010,10 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int cop
break;
}
if (copy) {
+ if (dst + 1 > dest + destlen) {
+ iemsg("vim_regsub_both(): not enough space");
+ return 0;
+ }
*dst = CAR;
}
dst++;
@@ -2010,14 +2032,16 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int cop
}
goto exit;
} else {
- if (backslash && (*s == CAR || *s == '\\')) {
- /*
- * Insert a backslash in front of a CR, otherwise
- * it will be replaced by a line break.
- * Number of backslashes will be halved later,
- * double them here.
- */
+ if ((flags & REGSUB_BACKSLASH) && (*s == CAR || *s == '\\')) {
+ // Insert a backslash in front of a CR, otherwise
+ // it will be replaced by a line break.
+ // Number of backslashes will be halved later,
+ // double them here.
if (copy) {
+ if (dst + 2 > dest + destlen) {
+ iemsg("vim_regsub_both(): not enough space");
+ return 0;
+ }
dst[0] = '\\';
dst[1] = *s;
}
@@ -2037,6 +2061,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int cop
{
int l;
+ int charlen;
// Copy composing characters separately, one
// at a time.
@@ -2044,10 +2069,15 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int cop
s += l;
len -= l;
+ charlen = utf_char2len(cc);
if (copy) {
+ if (dst + charlen > dest + destlen) {
+ iemsg("vim_regsub_both(): not enough space");
+ return 0;
+ }
utf_char2bytes(cc, (char *)dst);
}
- dst += utf_char2len(cc) - 1;
+ dst += charlen - 1;
}
dst++;
}
@@ -2386,8 +2416,8 @@ static void report_re_switch(char_u *pat)
}
}
-/// Matches a regexp against a string.
-/// "rmp->regprog" is a compiled regexp as returned by vim_regcomp().
+/// Match a regexp against a string.
+/// "rmp->regprog" must be a compiled regexp as returned by vim_regcomp().
/// Note: "rmp->regprog" may be freed and changed.
/// Uses curbuf for line count and 'iskeyword'.
/// When "nl" is true consider a "\n" in "line" to be a line break.
diff --git a/src/nvim/regexp_defs.h b/src/nvim/regexp_defs.h
index decc832051..09f244c2f6 100644
--- a/src/nvim/regexp_defs.h
+++ b/src/nvim/regexp_defs.h
@@ -168,4 +168,9 @@ struct regengine {
// char_u *expr;
};
+// Flags used by vim_regsub() and vim_regsub_both()
+#define REGSUB_COPY 1
+#define REGSUB_MAGIC 2
+#define REGSUB_BACKSLASH 4
+
#endif // NVIM_REGEXP_DEFS_H
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index b0edad7740..fe306f8c6b 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -326,10 +326,11 @@ int update_screen(int type)
type = must_redraw;
}
- /* must_redraw is reset here, so that when we run into some weird
- * reason to redraw while busy redrawing (e.g., asynchronous
- * scrolling), or update_topline() in win_update() will cause a
- * scroll, the screen will be redrawn later or in win_update(). */
+ // must_redraw is reset here, so that when we run into some weird
+ // reason to redraw while busy redrawing (e.g., asynchronous
+ // scrolling), or update_topline() in win_update() will cause a
+ // scroll, or a decoration provider requires a redraw, the screen
+ // will be redrawn later or in win_update().
must_redraw = 0;
}
@@ -689,6 +690,9 @@ bool win_cursorline_standout(const win_T *wp)
*/
static void win_update(win_T *wp, DecorProviders *providers)
{
+ bool called_decor_providers = false;
+win_update_start:
+ ;
buf_T *buf = wp->w_buffer;
int type;
int top_end = 0; /* Below last row of the top area that needs
@@ -1306,6 +1310,14 @@ static void win_update(win_T *wp, DecorProviders *providers)
DecorProviders line_providers;
decor_providers_invoke_win(wp, providers, &line_providers, &provider_err);
+ (void)win_signcol_count(wp); // check if provider changed signcol width
+ if (must_redraw != 0) {
+ must_redraw = 0;
+ if (!called_decor_providers) {
+ called_decor_providers = true;
+ goto win_update_start;
+ }
+ }
bool cursorline_standout = win_cursorline_standout(wp);
diff --git a/src/nvim/state.c b/src/nvim/state.c
index f6d9b535fc..6475105192 100644
--- a/src/nvim/state.c
+++ b/src/nvim/state.c
@@ -81,8 +81,8 @@ getkey:
may_sync_undo();
}
-#if MIN_LOG_LEVEL <= DEBUG_LOG_LEVEL
- log_key(DEBUG_LOG_LEVEL, key);
+#if MIN_LOG_LEVEL <= LOGLVL_DBG
+ log_key(LOGLVL_DBG, key);
#endif
int execute_result = s->execute(s, key);
@@ -137,7 +137,7 @@ bool virtual_active(void)
|| ((cur_ve_flags & VE_INSERT) && (State & MODE_INSERT));
}
-/// MODE_VISUAL, MODE_SELECTMODE and MODE_OP_PENDING State are never set, they are
+/// MODE_VISUAL, MODE_SELECT and MODE_OP_PENDING State are never set, they are
/// equal to MODE_NORMAL State with a condition. This function returns the real
/// State.
int get_real_state(void)
diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim
index 8055a51a11..7dde8a0439 100644
--- a/src/nvim/testdir/test_excmd.vim
+++ b/src/nvim/testdir/test_excmd.vim
@@ -422,5 +422,13 @@ func Test_address_line_overflow()
bwipe!
endfunc
+" This was leaving the cursor in line zero
+func Test_using_zero_in_range()
+ new
+ norm o00
+ silent! 0;s/\%')
+ bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index 4819c4877c..fb6c9e46aa 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -114,8 +114,9 @@ let s:filename_checks = {
\ 'cobol': ['file.cbl', 'file.cob', 'file.lib'],
\ 'coco': ['file.atg'],
\ 'conaryrecipe': ['file.recipe'],
- \ 'conf': ['/etc/pacman.conf', 'any/etc/pacman.conf', 'auto.master'],
+ \ 'conf': ['auto.master'],
\ 'config': ['configure.in', 'configure.ac', '/etc/hostname.file'],
+ \ 'confini': ['/etc/pacman.conf', 'any/etc/pacman.conf', 'mpv.conf'],
\ 'context': ['tex/context/any/file.tex', 'file.mkii', 'file.mkiv', 'file.mkvi', 'file.mkxl', 'file.mklx'],
\ 'cook': ['file.cook'],
\ 'cpp': ['file.cxx', 'file.c++', 'file.hh', 'file.hxx', 'file.hpp', 'file.ipp', 'file.moc', 'file.tcc', 'file.inl', 'file.tlh'],
diff --git a/src/nvim/testdir/test_filetype_lua.vim b/src/nvim/testdir/test_filetype_lua.vim
index f73e4ca33f..d9c0dcba9c 100644
--- a/src/nvim/testdir/test_filetype_lua.vim
+++ b/src/nvim/testdir/test_filetype_lua.vim
@@ -1,2 +1,3 @@
let g:do_filetype_lua = 1
+let g:did_load_filetypes = 0
source test_filetype.vim
diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim
index 87606f17b8..2391b4a485 100644
--- a/src/nvim/testdir/test_functions.vim
+++ b/src/nvim/testdir/test_functions.vim
@@ -1180,6 +1180,45 @@ func Test_col()
bw!
endfunc
+" Test for input()
+func Test_input_func()
+ " Test for prompt with multiple lines
+ redir => v
+ call feedkeys(":let c = input(\"A\\nB\\nC\\n? \")\<CR>B\<CR>", 'xt')
+ redir END
+ call assert_equal("B", c)
+ call assert_equal(['A', 'B', 'C'], split(v, "\n"))
+
+ " Test for default value
+ call feedkeys(":let c = input('color? ', 'red')\<CR>\<CR>", 'xt')
+ call assert_equal('red', c)
+
+ " Test for completion at the input prompt
+ func! Tcomplete(arglead, cmdline, pos)
+ return "item1\nitem2\nitem3"
+ endfunc
+ call feedkeys(":let c = input('Q? ', '', 'custom,Tcomplete')\<CR>"
+ \ .. "\<C-A>\<CR>", 'xt')
+ delfunc Tcomplete
+ call assert_equal('item1 item2 item3', c)
+
+ " Test for using special characters as default input
+ call feedkeys(":let c = input('name? ', \"x\\<BS>y\")\<CR>\<CR>", 'xt')
+ call assert_equal('y', c)
+
+ " Test for using text with composing characters as default input
+ call feedkeys(":let c = input('name? ', \"ã̳\")\<CR>\<CR>", 'xt')
+ call assert_equal('ã̳', c)
+
+ " Test for using <CR> as default input
+ call feedkeys(":let c = input('name? ', \"\\<CR>\")\<CR>x\<CR>", 'xt')
+ call assert_equal(' x', c)
+
+ call assert_fails("call input('F:', '', 'invalid')", 'E180:')
+ call assert_fails("call input('F:', '', [])", 'E730:')
+endfunc
+
+" Test for inputlist()
func Test_inputlist()
call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>1\<cr>", 'tx')
call assert_equal(1, c)
diff --git a/src/nvim/testdir/test_mapping.vim b/src/nvim/testdir/test_mapping.vim
index 995511cddf..494f09e0e5 100644
--- a/src/nvim/testdir/test_mapping.vim
+++ b/src/nvim/testdir/test_mapping.vim
@@ -1081,4 +1081,34 @@ func Test_expr_map_escape_special()
nunmap …
endfunc
+" Testing for mapping after an <Nop> mapping is triggered on timeout.
+" Test for what patch 8.1.0052 fixes.
+func Test_map_after_timed_out_nop()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set timeout timeoutlen=400
+ inoremap ab TEST
+ inoremap a <Nop>
+ END
+ call writefile(lines, 'Xtest_map_after_timed_out_nop')
+ let buf = RunVimInTerminal('-S Xtest_map_after_timed_out_nop', #{rows: 6})
+
+ " Enter Insert mode
+ call term_sendkeys(buf, 'i')
+ " Wait for the "a" mapping to timeout
+ call term_sendkeys(buf, 'a')
+ call term_wait(buf, 500)
+ " Send "a" and wait for a period shorter than 'timeoutlen'
+ call term_sendkeys(buf, 'a')
+ call term_wait(buf, 100)
+ " Send "b", should trigger the "ab" mapping
+ call term_sendkeys(buf, 'b')
+ call WaitForAssert({-> assert_equal("TEST", term_getline(buf, 1))})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xtest_map_after_timed_out_nop')
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/tui/terminfo_defs.h b/src/nvim/tui/terminfo_defs.h
index 4e8affa4d3..0bc4972dd3 100644
--- a/src/nvim/tui/terminfo_defs.h
+++ b/src/nvim/tui/terminfo_defs.h
@@ -285,7 +285,7 @@ static const int8_t ansi_terminfo[] = {
// set_a_background=\E[48;5;%p1%dm,
// set_a_foreground=\E[38;5;%p1%dm,
// set_attributes=\E[0%?%p1%p3%|%t;7%;%?%p2%t;4%;%?%p6%t;1%;m,
-// set_lr_margin=\E[?69h\E[%i%p1%d;%p2%ds,
+// set_lr_margin@,
// set_tab@,
// tab=^I,
// user6@,
@@ -293,7 +293,7 @@ static const int8_t ansi_terminfo[] = {
// user8@,
// user9@,
static const int8_t conemu_terminfo[] = {
- 30,2,61,0,38,0,15,0,-99,1,80,3,99,111,110,101,109,117,124,65,78,73,83,32,88,51,46,54,52,32,97,110,100,32,88,116,101,114,109,32,50,53,54,32,99,111,108,111,114,115,32,102,111,114,32,67,111,110,69,109,117,32,119,105,116,104,32,108,105,98,117,118,0,0,1,0,0,0,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,1,0,80,0,0,0,8,0,0,0,24,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,1,0,0,0,0,1,0,-2,-1,0,0,2,0,4,0,-2,-1,21,0,29,0,33,0,37,0,-1,-1,48,0,65,0,69,0,73,0,80,0,-1,-1,82,0,89,0,-1,-1,93,0,-2,-1,97,0,101,0,-1,-1,-1,-1,-2,-1,-2,-1,105,0,110,0,-1,-1,-2,-1,-2,-1,-2,-1,-1,-1,119,0,124,0,-127,0,-122,0,-2,-1,-113,0,-108,0,-1,-1,-2,-1,-99,0,-93,0,-2,-1,-1,-1,-1,-1,-1,-1,-2,-1,-1,-1,-1,-1,-1,-1,-87,0,-1,-1,-83,0,-1,-1,-1,-1,-1,-1,-81,0,-1,-1,-76,0,-1,-1,-1,-1,-1,-1,-1,-1,-72,0,-67,0,-61,0,-56,0,-51,0,-46,0,-41,0,-35,0,-29,0,-23,0,-17,0,-12,0,-1,-1,-7,0,-1,-1,-3,0,2,1,7,1,11,1,18,1,-1,-1,25,1,-2,-1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-2,-1,-2,-1,29,1,-1,-1,32,1,41,1,50,1,59,1,68,1,77,1,86,1,95,1,104,1,113,1,-1,-1,-1,-1,-1,-1,-2,-1,-2,-1,-2,-1,122,1,-2,-1,-2,-1,-1,-1,-1,-1,-114,1,-111,1,-100,1,-97,1,-95,1,-92,1,-2,-1,-1,-1,-49,1,-1,-1,-1,-1,-1,-1,-1,-1,-47,1,-43,1,-39,1,-35,1,-31,1,-1,-1,-1,-1,-2,-1,-1,-1,-27,1,-1,-1,-1,-1,-2,-1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-23,1,-18,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-14,1,-1,-1,-1,-1,-7,1,-1,-1,-1,-1,-1,-1,-1,-1,0,2,7,2,14,2,-1,-1,-1,-1,21,2,-1,-1,28,2,-1,-1,-1,-1,-1,-1,35,2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,42,2,48,2,54,2,60,2,66,2,72,2,78,2,84,2,90,2,96,2,102,2,108,2,114,2,120,2,126,2,-124,2,-118,2,-112,2,-106,2,-100,2,-94,2,-88,2,-82,2,-76,2,-70,2,-64,2,-58,2,-52,2,-46,2,-40,2,-33,2,-27,2,-21,2,-15,2,-9,2,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-3,2,2,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-2,-1,-2,-1,-2,-1,-2,-1,9,3,-2,-1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,18,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,23,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-2,-1,-1,-1,-1,-1,-1,-1,29,3,43,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,57,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-2,-1,-2,-1,7,0,13,0,27,91,37,105,37,112,49,37,100,59,37,112,50,37,100,114,0,27,91,72,27,91,50,74,0,27,91,75,0,27,91,74,0,27,91,37,105,37,112,49,37,100,71,0,27,91,37,105,37,112,49,37,100,59,37,112,50,37,100,72,0,27,91,66,0,27,91,72,0,27,91,63,50,53,108,0,8,0,27,91,63,50,53,104,0,27,91,67,0,27,91,65,0,27,91,80,0,27,91,77,0,27,91,49,109,0,27,91,63,49,48,52,57,104,0,27,91,55,109,0,27,91,55,109,0,27,91,52,109,0,27,91,37,112,49,37,100,88,0,27,91,48,109,0,27,91,63,49,48,52,57,108,0,27,91,50,55,109,0,27,91,50,52,109,0,27,91,76,0,8,0,27,91,51,126,0,27,91,66,0,27,91,91,65,0,27,91,50,49,126,0,27,91,91,66,0,27,91,91,67,0,27,91,91,68,0,27,91,91,69,0,27,91,49,55,126,0,27,91,49,56,126,0,27,91,49,57,126,0,27,91,50,48,126,0,27,91,49,126,0,27,91,50,126,0,27,91,68,0,27,91,54,126,0,27,91,53,126,0,27,91,67,0,27,91,49,59,50,66,0,27,91,49,59,50,65,0,27,91,65,0,27,69,0,27,91,37,112,49,37,100,80,0,27,91,37,112,49,37,100,77,0,27,91,37,112,49,37,100,66,0,27,91,37,112,49,37,100,64,0,27,91,37,112,49,37,100,83,0,27,91,37,112,49,37,100,76,0,27,91,37,112,49,37,100,68,0,27,91,37,112,49,37,100,67,0,27,91,37,112,49,37,100,84,0,27,91,37,112,49,37,100,65,0,37,112,49,37,99,27,91,37,112,50,37,123,49,125,37,45,37,100,98,0,27,56,0,27,91,37,105,37,112,49,37,100,100,0,27,55,0,10,0,27,77,0,27,91,48,37,63,37,112,49,37,112,51,37,124,37,116,59,55,37,59,37,63,37,112,50,37,116,59,52,37,59,37,63,37,112,54,37,116,59,49,37,59,109,0,9,0,27,79,119,0,27,79,121,0,27,91,71,0,27,79,113,0,27,79,115,0,27,91,90,0,27,91,52,126,0,27,79,77,0,27,91,51,59,50,126,0,27,91,52,59,50,126,0,27,91,49,59,50,126,0,27,91,50,59,50,126,0,27,91,49,59,50,68,0,27,91,54,59,50,126,0,27,91,53,59,50,126,0,27,91,49,59,50,67,0,27,91,50,51,126,0,27,91,50,52,126,0,27,91,50,53,126,0,27,91,50,54,126,0,27,91,50,56,126,0,27,91,50,57,126,0,27,91,51,49,126,0,27,91,51,50,126,0,27,91,51,51,126,0,27,91,51,52,126,0,27,91,50,51,36,0,27,91,50,52,36,0,27,91,49,49,94,0,27,91,49,50,94,0,27,91,49,51,94,0,27,91,49,52,94,0,27,91,49,53,94,0,27,91,49,55,94,0,27,91,49,56,94,0,27,91,49,57,94,0,27,91,50,48,94,0,27,91,50,49,94,0,27,91,50,51,94,0,27,91,50,52,94,0,27,91,50,53,94,0,27,91,50,54,94,0,27,91,50,56,94,0,27,91,50,57,94,0,27,91,51,49,94,0,27,91,49,59,54,83,0,27,91,51,50,94,0,27,91,51,51,94,0,27,91,51,52,94,0,27,91,50,51,64,0,27,91,50,52,64,0,27,91,49,75,0,27,91,63,54,57,108,0,27,91,51,57,59,52,57,109,0,27,91,51,109,0,27,91,50,51,109,0,27,91,51,56,59,53,59,37,112,49,37,100,109,0,27,91,52,56,59,53,59,37,112,49,37,100,109,0,27,91,63,54,57,104,27,91,37,105,37,112,49,37,100,59,37,112,50,37,100,115,0,2,0,0,0,74,0,92,0,-46,1,1,1,-2,-1,-2,-1,0,0,-2,-1,5,0,11,0,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,21,0,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,28,0,32,0,36,0,40,0,44,0,48,0,52,0,56,0,60,0,64,0,68,0,72,0,-2,-1,-2,-1,-2,-1,0,0,3,0,6,0,9,0,12,0,15,0,18,0,21,0,24,0,27,0,32,0,37,0,42,0,47,0,52,0,56,0,61,0,66,0,71,0,76,0,81,0,87,0,93,0,99,0,105,0,111,0,117,0,123,0,-127,0,-121,0,-115,0,-110,0,-105,0,-100,0,-95,0,-90,0,-84,0,-78,0,-72,0,-66,0,-60,0,-54,0,-48,0,-42,0,-36,0,-30,0,-24,0,-18,0,-12,0,-6,0,0,1,6,1,12,1,18,1,24,1,30,1,34,1,39,1,44,1,49,1,54,1,59,1,63,1,67,1,71,1,75,1,79,1,85,1,91,1,97,1,103,1,109,1,115,1,121,1,126,1,-125,1,27,91,51,74,0,27,91,50,32,113,0,27,91,37,112,49,37,100,32,113,0,27,91,49,59,50,65,0,27,79,120,0,27,79,116,0,27,79,118,0,27,79,114,0,27,79,69,0,27,79,107,0,27,79,108,0,27,79,111,0,27,79,110,0,27,79,106,0,27,79,109,0,27,79,112,0,65,88,0,88,84,0,67,114,0,67,115,0,69,51,0,77,115,0,83,101,0,83,115,0,88,77,0,107,68,67,51,0,107,68,67,52,0,107,68,67,53,0,107,68,67,54,0,107,68,67,55,0,107,68,78,0,107,68,78,51,0,107,68,78,52,0,107,68,78,53,0,107,68,78,54,0,107,68,78,55,0,107,69,78,68,51,0,107,69,78,68,52,0,107,69,78,68,53,0,107,69,78,68,54,0,107,69,78,68,55,0,107,72,79,77,51,0,107,72,79,77,52,0,107,72,79,77,53,0,107,72,79,77,54,0,107,72,79,77,55,0,107,73,67,51,0,107,73,67,52,0,107,73,67,53,0,107,73,67,54,0,107,73,67,55,0,107,76,70,84,51,0,107,76,70,84,52,0,107,76,70,84,53,0,107,76,70,84,54,0,107,76,70,84,55,0,107,78,88,84,51,0,107,78,88,84,52,0,107,78,88,84,53,0,107,78,88,84,54,0,107,78,88,84,55,0,107,80,82,86,51,0,107,80,82,86,52,0,107,80,82,86,53,0,107,80,82,86,54,0,107,80,82,86,55,0,107,82,73,84,51,0,107,82,73,84,52,0,107,82,73,84,53,0,107,82,73,84,54,0,107,82,73,84,55,0,107,85,80,0,107,85,80,51,0,107,85,80,52,0,107,85,80,53,0,107,85,80,54,0,107,85,80,55,0,107,97,50,0,107,98,49,0,107,98,51,0,107,99,50,0,107,112,53,0,107,112,65,68,68,0,107,112,67,77,65,0,107,112,68,73,86,0,107,112,68,79,84,0,107,112,77,85,76,0,107,112,83,85,66,0,107,112,90,82,79,0,114,109,120,120,0,115,109,120,120,0,120,109,0
+ 30,2,61,0,38,0,15,0,-99,1,57,3,99,111,110,101,109,117,124,65,78,73,83,32,88,51,46,54,52,32,97,110,100,32,88,116,101,114,109,32,50,53,54,32,99,111,108,111,114,115,32,102,111,114,32,67,111,110,69,109,117,32,119,105,116,104,32,108,105,98,117,118,0,0,1,0,0,0,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,1,0,80,0,0,0,8,0,0,0,24,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,1,0,0,0,0,1,0,-2,-1,0,0,2,0,4,0,-2,-1,21,0,29,0,33,0,37,0,-1,-1,48,0,65,0,69,0,73,0,80,0,-1,-1,82,0,89,0,-1,-1,93,0,-2,-1,97,0,101,0,-1,-1,-1,-1,-2,-1,-2,-1,105,0,110,0,-1,-1,-2,-1,-2,-1,-2,-1,-1,-1,119,0,124,0,-127,0,-122,0,-2,-1,-113,0,-108,0,-1,-1,-2,-1,-99,0,-93,0,-2,-1,-1,-1,-1,-1,-1,-1,-2,-1,-1,-1,-1,-1,-1,-1,-87,0,-1,-1,-83,0,-1,-1,-1,-1,-1,-1,-81,0,-1,-1,-76,0,-1,-1,-1,-1,-1,-1,-1,-1,-72,0,-67,0,-61,0,-56,0,-51,0,-46,0,-41,0,-35,0,-29,0,-23,0,-17,0,-12,0,-1,-1,-7,0,-1,-1,-3,0,2,1,7,1,11,1,18,1,-1,-1,25,1,-2,-1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-2,-1,-2,-1,29,1,-1,-1,32,1,41,1,50,1,59,1,68,1,77,1,86,1,95,1,104,1,113,1,-1,-1,-1,-1,-1,-1,-2,-1,-2,-1,-2,-1,122,1,-2,-1,-2,-1,-1,-1,-1,-1,-114,1,-111,1,-100,1,-97,1,-95,1,-92,1,-2,-1,-1,-1,-49,1,-1,-1,-1,-1,-1,-1,-1,-1,-47,1,-43,1,-39,1,-35,1,-31,1,-1,-1,-1,-1,-2,-1,-1,-1,-27,1,-1,-1,-1,-1,-2,-1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-23,1,-18,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-14,1,-1,-1,-1,-1,-7,1,-1,-1,-1,-1,-1,-1,-1,-1,0,2,7,2,14,2,-1,-1,-1,-1,21,2,-1,-1,28,2,-1,-1,-1,-1,-1,-1,35,2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,42,2,48,2,54,2,60,2,66,2,72,2,78,2,84,2,90,2,96,2,102,2,108,2,114,2,120,2,126,2,-124,2,-118,2,-112,2,-106,2,-100,2,-94,2,-88,2,-82,2,-76,2,-70,2,-64,2,-58,2,-52,2,-46,2,-40,2,-33,2,-27,2,-21,2,-15,2,-9,2,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-3,2,2,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-2,-1,-2,-1,-2,-1,-2,-1,9,3,-2,-1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,18,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,23,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-2,-1,-1,-1,-1,-1,-1,-1,29,3,43,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-2,-1,-2,-1,7,0,13,0,27,91,37,105,37,112,49,37,100,59,37,112,50,37,100,114,0,27,91,72,27,91,50,74,0,27,91,75,0,27,91,74,0,27,91,37,105,37,112,49,37,100,71,0,27,91,37,105,37,112,49,37,100,59,37,112,50,37,100,72,0,27,91,66,0,27,91,72,0,27,91,63,50,53,108,0,8,0,27,91,63,50,53,104,0,27,91,67,0,27,91,65,0,27,91,80,0,27,91,77,0,27,91,49,109,0,27,91,63,49,48,52,57,104,0,27,91,55,109,0,27,91,55,109,0,27,91,52,109,0,27,91,37,112,49,37,100,88,0,27,91,48,109,0,27,91,63,49,48,52,57,108,0,27,91,50,55,109,0,27,91,50,52,109,0,27,91,76,0,8,0,27,91,51,126,0,27,91,66,0,27,91,91,65,0,27,91,50,49,126,0,27,91,91,66,0,27,91,91,67,0,27,91,91,68,0,27,91,91,69,0,27,91,49,55,126,0,27,91,49,56,126,0,27,91,49,57,126,0,27,91,50,48,126,0,27,91,49,126,0,27,91,50,126,0,27,91,68,0,27,91,54,126,0,27,91,53,126,0,27,91,67,0,27,91,49,59,50,66,0,27,91,49,59,50,65,0,27,91,65,0,27,69,0,27,91,37,112,49,37,100,80,0,27,91,37,112,49,37,100,77,0,27,91,37,112,49,37,100,66,0,27,91,37,112,49,37,100,64,0,27,91,37,112,49,37,100,83,0,27,91,37,112,49,37,100,76,0,27,91,37,112,49,37,100,68,0,27,91,37,112,49,37,100,67,0,27,91,37,112,49,37,100,84,0,27,91,37,112,49,37,100,65,0,37,112,49,37,99,27,91,37,112,50,37,123,49,125,37,45,37,100,98,0,27,56,0,27,91,37,105,37,112,49,37,100,100,0,27,55,0,10,0,27,77,0,27,91,48,37,63,37,112,49,37,112,51,37,124,37,116,59,55,37,59,37,63,37,112,50,37,116,59,52,37,59,37,63,37,112,54,37,116,59,49,37,59,109,0,9,0,27,79,119,0,27,79,121,0,27,91,71,0,27,79,113,0,27,79,115,0,27,91,90,0,27,91,52,126,0,27,79,77,0,27,91,51,59,50,126,0,27,91,52,59,50,126,0,27,91,49,59,50,126,0,27,91,50,59,50,126,0,27,91,49,59,50,68,0,27,91,54,59,50,126,0,27,91,53,59,50,126,0,27,91,49,59,50,67,0,27,91,50,51,126,0,27,91,50,52,126,0,27,91,50,53,126,0,27,91,50,54,126,0,27,91,50,56,126,0,27,91,50,57,126,0,27,91,51,49,126,0,27,91,51,50,126,0,27,91,51,51,126,0,27,91,51,52,126,0,27,91,50,51,36,0,27,91,50,52,36,0,27,91,49,49,94,0,27,91,49,50,94,0,27,91,49,51,94,0,27,91,49,52,94,0,27,91,49,53,94,0,27,91,49,55,94,0,27,91,49,56,94,0,27,91,49,57,94,0,27,91,50,48,94,0,27,91,50,49,94,0,27,91,50,51,94,0,27,91,50,52,94,0,27,91,50,53,94,0,27,91,50,54,94,0,27,91,50,56,94,0,27,91,50,57,94,0,27,91,51,49,94,0,27,91,49,59,54,83,0,27,91,51,50,94,0,27,91,51,51,94,0,27,91,51,52,94,0,27,91,50,51,64,0,27,91,50,52,64,0,27,91,49,75,0,27,91,63,54,57,108,0,27,91,51,57,59,52,57,109,0,27,91,51,109,0,27,91,50,51,109,0,27,91,51,56,59,53,59,37,112,49,37,100,109,0,27,91,52,56,59,53,59,37,112,49,37,100,109,0,0,2,0,0,0,74,0,92,0,-46,1,1,1,-2,-1,-2,-1,0,0,-2,-1,5,0,11,0,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,21,0,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,28,0,32,0,36,0,40,0,44,0,48,0,52,0,56,0,60,0,64,0,68,0,72,0,-2,-1,-2,-1,-2,-1,0,0,3,0,6,0,9,0,12,0,15,0,18,0,21,0,24,0,27,0,32,0,37,0,42,0,47,0,52,0,56,0,61,0,66,0,71,0,76,0,81,0,87,0,93,0,99,0,105,0,111,0,117,0,123,0,-127,0,-121,0,-115,0,-110,0,-105,0,-100,0,-95,0,-90,0,-84,0,-78,0,-72,0,-66,0,-60,0,-54,0,-48,0,-42,0,-36,0,-30,0,-24,0,-18,0,-12,0,-6,0,0,1,6,1,12,1,18,1,24,1,30,1,34,1,39,1,44,1,49,1,54,1,59,1,63,1,67,1,71,1,75,1,79,1,85,1,91,1,97,1,103,1,109,1,115,1,121,1,126,1,-125,1,27,91,51,74,0,27,91,50,32,113,0,27,91,37,112,49,37,100,32,113,0,27,91,49,59,50,65,0,27,79,120,0,27,79,116,0,27,79,118,0,27,79,114,0,27,79,69,0,27,79,107,0,27,79,108,0,27,79,111,0,27,79,110,0,27,79,106,0,27,79,109,0,27,79,112,0,65,88,0,88,84,0,67,114,0,67,115,0,69,51,0,77,115,0,83,101,0,83,115,0,88,77,0,107,68,67,51,0,107,68,67,52,0,107,68,67,53,0,107,68,67,54,0,107,68,67,55,0,107,68,78,0,107,68,78,51,0,107,68,78,52,0,107,68,78,53,0,107,68,78,54,0,107,68,78,55,0,107,69,78,68,51,0,107,69,78,68,52,0,107,69,78,68,53,0,107,69,78,68,54,0,107,69,78,68,55,0,107,72,79,77,51,0,107,72,79,77,52,0,107,72,79,77,53,0,107,72,79,77,54,0,107,72,79,77,55,0,107,73,67,51,0,107,73,67,52,0,107,73,67,53,0,107,73,67,54,0,107,73,67,55,0,107,76,70,84,51,0,107,76,70,84,52,0,107,76,70,84,53,0,107,76,70,84,54,0,107,76,70,84,55,0,107,78,88,84,51,0,107,78,88,84,52,0,107,78,88,84,53,0,107,78,88,84,54,0,107,78,88,84,55,0,107,80,82,86,51,0,107,80,82,86,52,0,107,80,82,86,53,0,107,80,82,86,54,0,107,80,82,86,55,0,107,82,73,84,51,0,107,82,73,84,52,0,107,82,73,84,53,0,107,82,73,84,54,0,107,82,73,84,55,0,107,85,80,0,107,85,80,51,0,107,85,80,52,0,107,85,80,53,0,107,85,80,54,0,107,85,80,55,0,107,97,50,0,107,98,49,0,107,98,51,0,107,99,50,0,107,112,53,0,107,112,65,68,68,0,107,112,67,77,65,0,107,112,68,73,86,0,107,112,68,79,84,0,107,112,77,85,76,0,107,112,83,85,66,0,107,112,90,82,79,0,114,109,120,120,0,115,109,120,120,0,120,109,0
};
// cygwin|ANSI emulation for Cygwin,
@@ -1963,7 +1963,7 @@ static const int8_t vte_256colour_terminfo[] = {
// set_a_background=\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m,
// set_a_foreground=\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m,
// set_attributes=\E[0%?%p1%p3%|%t;7%;%?%p2%t;4%;%?%p6%t;1%;m,
-// set_lr_margin=\E[?69h\E[%i%p1%d;%p2%ds,
+// set_lr_margin@,
// set_tab=\EH,
// tab=^I,
// user6@,
@@ -1971,7 +1971,7 @@ static const int8_t vte_256colour_terminfo[] = {
// user8@,
// user9@,
static const int8_t vtpcon_terminfo[] = {
- 30,2,71,0,38,0,15,0,-99,1,70,4,118,116,112,99,111,110,124,65,78,73,83,32,101,109,117,108,97,116,105,111,110,32,102,111,114,32,99,111,110,115,111,108,101,32,118,105,114,116,117,97,108,32,116,101,114,109,105,110,97,108,32,115,101,113,117,101,110,99,101,32,119,105,116,104,32,108,105,98,117,118,0,0,1,0,0,0,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,1,0,80,0,0,0,8,0,0,0,24,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,1,0,0,0,0,1,0,0,0,4,0,6,0,8,0,-2,-1,25,0,33,0,37,0,41,0,-1,-1,52,0,69,0,73,0,77,0,84,0,-1,-1,86,0,99,0,-1,-1,103,0,-2,-1,107,0,111,0,-1,-1,-1,-1,115,0,-2,-1,119,0,124,0,-1,-1,-2,-1,-2,-1,-2,-1,-1,-1,-123,0,-118,0,-113,0,-108,0,-99,0,-95,0,-90,0,-1,-1,-2,-1,-81,0,-75,0,-2,-1,-1,-1,-1,-1,-1,-1,-69,0,-1,-1,-1,-1,-1,-1,-59,0,-1,-1,-55,0,-1,-1,-1,-1,-1,-1,-53,0,-1,-1,-48,0,-1,-1,-1,-1,-1,-1,-1,-1,-44,0,-39,0,-33,0,-28,0,-23,0,-18,0,-13,0,-7,0,-1,0,5,1,11,1,16,1,-1,-1,21,1,-1,-1,25,1,30,1,35,1,39,1,46,1,-1,-1,53,1,-2,-1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-2,-1,-2,-1,57,1,-1,-1,60,1,69,1,78,1,87,1,96,1,105,1,114,1,123,1,-124,1,-115,1,-1,-1,-1,-1,-1,-1,-2,-1,-2,-1,-2,-1,-106,1,-2,-1,-2,-1,-1,-1,-1,-1,-86,1,-83,1,-72,1,-69,1,-67,1,-64,1,-21,1,-1,-1,-18,1,-1,-1,-1,-1,-1,-1,-1,-1,-16,1,-12,1,-8,1,-4,1,0,2,-1,-1,-1,-1,4,2,-1,-1,27,2,-1,-1,-1,-1,-2,-1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,31,2,36,2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,40,2,-1,-1,-1,-1,47,2,-1,-1,-1,-1,-1,-1,-1,-1,54,2,61,2,68,2,-1,-1,-1,-1,75,2,-1,-1,82,2,-1,-1,-1,-1,-1,-1,89,2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,96,2,102,2,108,2,114,2,120,2,126,2,-124,2,-118,2,-112,2,-106,2,-100,2,-94,2,-88,2,-82,2,-76,2,-70,2,-64,2,-58,2,-52,2,-46,2,-40,2,-34,2,-28,2,-22,2,-16,2,-10,2,-4,2,2,3,8,3,14,3,21,3,27,3,33,3,39,3,45,3,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,51,3,56,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-2,-1,-2,-1,-2,-1,-2,-1,63,3,-2,-1,72,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-91,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-86,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-2,-1,-1,-1,-1,-1,-1,-1,-80,3,-17,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,47,4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-2,-1,-2,-1,27,91,90,0,7,0,13,0,27,91,37,105,37,112,49,37,100,59,37,112,50,37,100,114,0,27,91,72,27,91,50,74,0,27,91,75,0,27,91,74,0,27,91,37,105,37,112,49,37,100,71,0,27,91,37,105,37,112,49,37,100,59,37,112,50,37,100,72,0,27,91,66,0,27,91,72,0,27,91,63,50,53,108,0,8,0,27,91,63,49,50,108,27,91,63,50,53,104,0,27,91,67,0,27,91,65,0,27,91,80,0,27,91,77,0,27,40,48,0,27,91,49,109,0,27,91,63,49,48,52,57,104,0,27,91,55,109,0,27,91,55,109,0,27,91,52,109,0,27,91,37,112,49,37,100,88,0,27,40,66,0,27,91,48,109,0,27,91,63,49,48,52,57,108,0,27,91,50,55,109,0,27,91,50,52,109,0,27,91,33,112,27,91,63,51,108,0,27,91,76,0,8,0,27,91,51,126,0,27,91,66,0,27,91,91,65,0,27,91,50,49,126,0,27,91,91,66,0,27,91,91,67,0,27,91,91,68,0,27,91,91,69,0,27,91,49,55,126,0,27,91,49,56,126,0,27,91,49,57,126,0,27,91,50,48,126,0,27,91,49,126,0,27,91,50,126,0,27,91,68,0,27,91,54,126,0,27,91,53,126,0,27,91,67,0,27,91,49,59,50,66,0,27,91,49,59,50,65,0,27,91,65,0,27,69,0,27,91,37,112,49,37,100,80,0,27,91,37,112,49,37,100,77,0,27,91,37,112,49,37,100,66,0,27,91,37,112,49,37,100,64,0,27,91,37,112,49,37,100,83,0,27,91,37,112,49,37,100,76,0,27,91,37,112,49,37,100,68,0,27,91,37,112,49,37,100,67,0,27,91,37,112,49,37,100,84,0,27,91,37,112,49,37,100,65,0,37,112,49,37,99,27,91,37,112,50,37,123,49,125,37,45,37,100,98,0,27,56,0,27,91,37,105,37,112,49,37,100,100,0,27,55,0,10,0,27,77,0,27,91,48,37,63,37,112,49,37,112,51,37,124,37,116,59,55,37,59,37,63,37,112,50,37,116,59,52,37,59,37,63,37,112,54,37,116,59,49,37,59,109,0,27,72,0,9,0,27,79,119,0,27,79,121,0,27,91,71,0,27,79,113,0,27,79,115,0,106,106,107,107,108,108,109,109,110,110,113,113,116,116,117,117,118,118,119,119,120,120,0,27,91,90,0,27,91,52,126,0,27,79,77,0,27,91,51,59,50,126,0,27,91,52,59,50,126,0,27,91,49,59,50,126,0,27,91,50,59,50,126,0,27,91,49,59,50,68,0,27,91,54,59,50,126,0,27,91,53,59,50,126,0,27,91,49,59,50,67,0,27,91,50,51,126,0,27,91,50,52,126,0,27,91,50,53,126,0,27,91,50,54,126,0,27,91,50,56,126,0,27,91,50,57,126,0,27,91,51,49,126,0,27,91,51,50,126,0,27,91,51,51,126,0,27,91,51,52,126,0,27,91,50,51,36,0,27,91,50,52,36,0,27,91,49,49,94,0,27,91,49,50,94,0,27,91,49,51,94,0,27,91,49,52,94,0,27,91,49,53,94,0,27,91,49,55,94,0,27,91,49,56,94,0,27,91,49,57,94,0,27,91,50,48,94,0,27,91,50,49,94,0,27,91,50,51,94,0,27,91,50,52,94,0,27,91,50,53,94,0,27,91,50,54,94,0,27,91,50,56,94,0,27,91,50,57,94,0,27,91,51,49,94,0,27,91,49,59,54,83,0,27,91,51,50,94,0,27,91,51,51,94,0,27,91,51,52,94,0,27,91,50,51,64,0,27,91,50,52,64,0,27,91,49,75,0,27,91,63,54,57,108,0,27,91,51,57,59,52,57,109,0,27,93,52,59,37,112,49,37,100,59,114,103,98,58,37,112,50,37,123,50,53,53,125,37,42,37,123,49,48,48,48,125,37,47,37,50,46,50,88,47,37,112,51,37,123,50,53,53,125,37,42,37,123,49,48,48,48,125,37,47,37,50,46,50,88,47,37,112,52,37,123,50,53,53,125,37,42,37,123,49,48,48,48,125,37,47,37,50,46,50,88,27,0,27,91,51,109,0,27,91,50,51,109,0,27,91,37,63,37,112,49,37,123,56,125,37,60,37,116,51,37,112,49,37,100,37,101,37,112,49,37,123,49,54,125,37,60,37,116,57,37,112,49,37,123,56,125,37,45,37,100,37,101,51,56,59,53,59,37,112,49,37,100,37,59,109,0,27,91,37,63,37,112,49,37,123,56,125,37,60,37,116,52,37,112,49,37,100,37,101,37,112,49,37,123,49,54,125,37,60,37,116,49,48,37,112,49,37,123,56,125,37,45,37,100,37,101,52,56,59,53,59,37,112,49,37,100,37,59,109,0,27,91,63,54,57,104,27,91,37,105,37,112,49,37,100,59,37,112,50,37,100,115,0,2,0,0,0,74,0,92,0,-46,1,1,1,-2,-1,-2,-1,0,0,-2,-1,5,0,11,0,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,21,0,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,28,0,32,0,36,0,40,0,44,0,48,0,52,0,56,0,60,0,64,0,68,0,72,0,-2,-1,-2,-1,-2,-1,0,0,3,0,6,0,9,0,12,0,15,0,18,0,21,0,24,0,27,0,32,0,37,0,42,0,47,0,52,0,56,0,61,0,66,0,71,0,76,0,81,0,87,0,93,0,99,0,105,0,111,0,117,0,123,0,-127,0,-121,0,-115,0,-110,0,-105,0,-100,0,-95,0,-90,0,-84,0,-78,0,-72,0,-66,0,-60,0,-54,0,-48,0,-42,0,-36,0,-30,0,-24,0,-18,0,-12,0,-6,0,0,1,6,1,12,1,18,1,24,1,30,1,34,1,39,1,44,1,49,1,54,1,59,1,63,1,67,1,71,1,75,1,79,1,85,1,91,1,97,1,103,1,109,1,115,1,121,1,126,1,-125,1,27,91,51,74,0,27,91,50,32,113,0,27,91,37,112,49,37,100,32,113,0,27,91,49,59,50,65,0,27,79,120,0,27,79,116,0,27,79,118,0,27,79,114,0,27,79,69,0,27,79,107,0,27,79,108,0,27,79,111,0,27,79,110,0,27,79,106,0,27,79,109,0,27,79,112,0,65,88,0,88,84,0,67,114,0,67,115,0,69,51,0,77,115,0,83,101,0,83,115,0,88,77,0,107,68,67,51,0,107,68,67,52,0,107,68,67,53,0,107,68,67,54,0,107,68,67,55,0,107,68,78,0,107,68,78,51,0,107,68,78,52,0,107,68,78,53,0,107,68,78,54,0,107,68,78,55,0,107,69,78,68,51,0,107,69,78,68,52,0,107,69,78,68,53,0,107,69,78,68,54,0,107,69,78,68,55,0,107,72,79,77,51,0,107,72,79,77,52,0,107,72,79,77,53,0,107,72,79,77,54,0,107,72,79,77,55,0,107,73,67,51,0,107,73,67,52,0,107,73,67,53,0,107,73,67,54,0,107,73,67,55,0,107,76,70,84,51,0,107,76,70,84,52,0,107,76,70,84,53,0,107,76,70,84,54,0,107,76,70,84,55,0,107,78,88,84,51,0,107,78,88,84,52,0,107,78,88,84,53,0,107,78,88,84,54,0,107,78,88,84,55,0,107,80,82,86,51,0,107,80,82,86,52,0,107,80,82,86,53,0,107,80,82,86,54,0,107,80,82,86,55,0,107,82,73,84,51,0,107,82,73,84,52,0,107,82,73,84,53,0,107,82,73,84,54,0,107,82,73,84,55,0,107,85,80,0,107,85,80,51,0,107,85,80,52,0,107,85,80,53,0,107,85,80,54,0,107,85,80,55,0,107,97,50,0,107,98,49,0,107,98,51,0,107,99,50,0,107,112,53,0,107,112,65,68,68,0,107,112,67,77,65,0,107,112,68,73,86,0,107,112,68,79,84,0,107,112,77,85,76,0,107,112,83,85,66,0,107,112,90,82,79,0,114,109,120,120,0,115,109,120,120,0,120,109,0
+ 30,2,71,0,38,0,15,0,-99,1,47,4,118,116,112,99,111,110,124,65,78,73,83,32,101,109,117,108,97,116,105,111,110,32,102,111,114,32,99,111,110,115,111,108,101,32,118,105,114,116,117,97,108,32,116,101,114,109,105,110,97,108,32,115,101,113,117,101,110,99,101,32,119,105,116,104,32,108,105,98,117,118,0,0,1,0,0,0,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,1,0,80,0,0,0,8,0,0,0,24,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,1,0,0,0,0,1,0,0,0,4,0,6,0,8,0,-2,-1,25,0,33,0,37,0,41,0,-1,-1,52,0,69,0,73,0,77,0,84,0,-1,-1,86,0,99,0,-1,-1,103,0,-2,-1,107,0,111,0,-1,-1,-1,-1,115,0,-2,-1,119,0,124,0,-1,-1,-2,-1,-2,-1,-2,-1,-1,-1,-123,0,-118,0,-113,0,-108,0,-99,0,-95,0,-90,0,-1,-1,-2,-1,-81,0,-75,0,-2,-1,-1,-1,-1,-1,-1,-1,-69,0,-1,-1,-1,-1,-1,-1,-59,0,-1,-1,-55,0,-1,-1,-1,-1,-1,-1,-53,0,-1,-1,-48,0,-1,-1,-1,-1,-1,-1,-1,-1,-44,0,-39,0,-33,0,-28,0,-23,0,-18,0,-13,0,-7,0,-1,0,5,1,11,1,16,1,-1,-1,21,1,-1,-1,25,1,30,1,35,1,39,1,46,1,-1,-1,53,1,-2,-1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-2,-1,-2,-1,57,1,-1,-1,60,1,69,1,78,1,87,1,96,1,105,1,114,1,123,1,-124,1,-115,1,-1,-1,-1,-1,-1,-1,-2,-1,-2,-1,-2,-1,-106,1,-2,-1,-2,-1,-1,-1,-1,-1,-86,1,-83,1,-72,1,-69,1,-67,1,-64,1,-21,1,-1,-1,-18,1,-1,-1,-1,-1,-1,-1,-1,-1,-16,1,-12,1,-8,1,-4,1,0,2,-1,-1,-1,-1,4,2,-1,-1,27,2,-1,-1,-1,-1,-2,-1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,31,2,36,2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,40,2,-1,-1,-1,-1,47,2,-1,-1,-1,-1,-1,-1,-1,-1,54,2,61,2,68,2,-1,-1,-1,-1,75,2,-1,-1,82,2,-1,-1,-1,-1,-1,-1,89,2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,96,2,102,2,108,2,114,2,120,2,126,2,-124,2,-118,2,-112,2,-106,2,-100,2,-94,2,-88,2,-82,2,-76,2,-70,2,-64,2,-58,2,-52,2,-46,2,-40,2,-34,2,-28,2,-22,2,-16,2,-10,2,-4,2,2,3,8,3,14,3,21,3,27,3,33,3,39,3,45,3,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,51,3,56,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-2,-1,-2,-1,-2,-1,-2,-1,63,3,-2,-1,72,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-91,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-86,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-2,-1,-1,-1,-1,-1,-1,-1,-80,3,-17,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-2,-1,-2,-1,27,91,90,0,7,0,13,0,27,91,37,105,37,112,49,37,100,59,37,112,50,37,100,114,0,27,91,72,27,91,50,74,0,27,91,75,0,27,91,74,0,27,91,37,105,37,112,49,37,100,71,0,27,91,37,105,37,112,49,37,100,59,37,112,50,37,100,72,0,27,91,66,0,27,91,72,0,27,91,63,50,53,108,0,8,0,27,91,63,49,50,108,27,91,63,50,53,104,0,27,91,67,0,27,91,65,0,27,91,80,0,27,91,77,0,27,40,48,0,27,91,49,109,0,27,91,63,49,48,52,57,104,0,27,91,55,109,0,27,91,55,109,0,27,91,52,109,0,27,91,37,112,49,37,100,88,0,27,40,66,0,27,91,48,109,0,27,91,63,49,48,52,57,108,0,27,91,50,55,109,0,27,91,50,52,109,0,27,91,33,112,27,91,63,51,108,0,27,91,76,0,8,0,27,91,51,126,0,27,91,66,0,27,91,91,65,0,27,91,50,49,126,0,27,91,91,66,0,27,91,91,67,0,27,91,91,68,0,27,91,91,69,0,27,91,49,55,126,0,27,91,49,56,126,0,27,91,49,57,126,0,27,91,50,48,126,0,27,91,49,126,0,27,91,50,126,0,27,91,68,0,27,91,54,126,0,27,91,53,126,0,27,91,67,0,27,91,49,59,50,66,0,27,91,49,59,50,65,0,27,91,65,0,27,69,0,27,91,37,112,49,37,100,80,0,27,91,37,112,49,37,100,77,0,27,91,37,112,49,37,100,66,0,27,91,37,112,49,37,100,64,0,27,91,37,112,49,37,100,83,0,27,91,37,112,49,37,100,76,0,27,91,37,112,49,37,100,68,0,27,91,37,112,49,37,100,67,0,27,91,37,112,49,37,100,84,0,27,91,37,112,49,37,100,65,0,37,112,49,37,99,27,91,37,112,50,37,123,49,125,37,45,37,100,98,0,27,56,0,27,91,37,105,37,112,49,37,100,100,0,27,55,0,10,0,27,77,0,27,91,48,37,63,37,112,49,37,112,51,37,124,37,116,59,55,37,59,37,63,37,112,50,37,116,59,52,37,59,37,63,37,112,54,37,116,59,49,37,59,109,0,27,72,0,9,0,27,79,119,0,27,79,121,0,27,91,71,0,27,79,113,0,27,79,115,0,106,106,107,107,108,108,109,109,110,110,113,113,116,116,117,117,118,118,119,119,120,120,0,27,91,90,0,27,91,52,126,0,27,79,77,0,27,91,51,59,50,126,0,27,91,52,59,50,126,0,27,91,49,59,50,126,0,27,91,50,59,50,126,0,27,91,49,59,50,68,0,27,91,54,59,50,126,0,27,91,53,59,50,126,0,27,91,49,59,50,67,0,27,91,50,51,126,0,27,91,50,52,126,0,27,91,50,53,126,0,27,91,50,54,126,0,27,91,50,56,126,0,27,91,50,57,126,0,27,91,51,49,126,0,27,91,51,50,126,0,27,91,51,51,126,0,27,91,51,52,126,0,27,91,50,51,36,0,27,91,50,52,36,0,27,91,49,49,94,0,27,91,49,50,94,0,27,91,49,51,94,0,27,91,49,52,94,0,27,91,49,53,94,0,27,91,49,55,94,0,27,91,49,56,94,0,27,91,49,57,94,0,27,91,50,48,94,0,27,91,50,49,94,0,27,91,50,51,94,0,27,91,50,52,94,0,27,91,50,53,94,0,27,91,50,54,94,0,27,91,50,56,94,0,27,91,50,57,94,0,27,91,51,49,94,0,27,91,49,59,54,83,0,27,91,51,50,94,0,27,91,51,51,94,0,27,91,51,52,94,0,27,91,50,51,64,0,27,91,50,52,64,0,27,91,49,75,0,27,91,63,54,57,108,0,27,91,51,57,59,52,57,109,0,27,93,52,59,37,112,49,37,100,59,114,103,98,58,37,112,50,37,123,50,53,53,125,37,42,37,123,49,48,48,48,125,37,47,37,50,46,50,88,47,37,112,51,37,123,50,53,53,125,37,42,37,123,49,48,48,48,125,37,47,37,50,46,50,88,47,37,112,52,37,123,50,53,53,125,37,42,37,123,49,48,48,48,125,37,47,37,50,46,50,88,27,0,27,91,51,109,0,27,91,50,51,109,0,27,91,37,63,37,112,49,37,123,56,125,37,60,37,116,51,37,112,49,37,100,37,101,37,112,49,37,123,49,54,125,37,60,37,116,57,37,112,49,37,123,56,125,37,45,37,100,37,101,51,56,59,53,59,37,112,49,37,100,37,59,109,0,27,91,37,63,37,112,49,37,123,56,125,37,60,37,116,52,37,112,49,37,100,37,101,37,112,49,37,123,49,54,125,37,60,37,116,49,48,37,112,49,37,123,56,125,37,45,37,100,37,101,52,56,59,53,59,37,112,49,37,100,37,59,109,0,0,2,0,0,0,74,0,92,0,-46,1,1,1,-2,-1,-2,-1,0,0,-2,-1,5,0,11,0,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,21,0,-2,-1,-2,-1,-2,-1,-2,-1,-2,-1,28,0,32,0,36,0,40,0,44,0,48,0,52,0,56,0,60,0,64,0,68,0,72,0,-2,-1,-2,-1,-2,-1,0,0,3,0,6,0,9,0,12,0,15,0,18,0,21,0,24,0,27,0,32,0,37,0,42,0,47,0,52,0,56,0,61,0,66,0,71,0,76,0,81,0,87,0,93,0,99,0,105,0,111,0,117,0,123,0,-127,0,-121,0,-115,0,-110,0,-105,0,-100,0,-95,0,-90,0,-84,0,-78,0,-72,0,-66,0,-60,0,-54,0,-48,0,-42,0,-36,0,-30,0,-24,0,-18,0,-12,0,-6,0,0,1,6,1,12,1,18,1,24,1,30,1,34,1,39,1,44,1,49,1,54,1,59,1,63,1,67,1,71,1,75,1,79,1,85,1,91,1,97,1,103,1,109,1,115,1,121,1,126,1,-125,1,27,91,51,74,0,27,91,50,32,113,0,27,91,37,112,49,37,100,32,113,0,27,91,49,59,50,65,0,27,79,120,0,27,79,116,0,27,79,118,0,27,79,114,0,27,79,69,0,27,79,107,0,27,79,108,0,27,79,111,0,27,79,110,0,27,79,106,0,27,79,109,0,27,79,112,0,65,88,0,88,84,0,67,114,0,67,115,0,69,51,0,77,115,0,83,101,0,83,115,0,88,77,0,107,68,67,51,0,107,68,67,52,0,107,68,67,53,0,107,68,67,54,0,107,68,67,55,0,107,68,78,0,107,68,78,51,0,107,68,78,52,0,107,68,78,53,0,107,68,78,54,0,107,68,78,55,0,107,69,78,68,51,0,107,69,78,68,52,0,107,69,78,68,53,0,107,69,78,68,54,0,107,69,78,68,55,0,107,72,79,77,51,0,107,72,79,77,52,0,107,72,79,77,53,0,107,72,79,77,54,0,107,72,79,77,55,0,107,73,67,51,0,107,73,67,52,0,107,73,67,53,0,107,73,67,54,0,107,73,67,55,0,107,76,70,84,51,0,107,76,70,84,52,0,107,76,70,84,53,0,107,76,70,84,54,0,107,76,70,84,55,0,107,78,88,84,51,0,107,78,88,84,52,0,107,78,88,84,53,0,107,78,88,84,54,0,107,78,88,84,55,0,107,80,82,86,51,0,107,80,82,86,52,0,107,80,82,86,53,0,107,80,82,86,54,0,107,80,82,86,55,0,107,82,73,84,51,0,107,82,73,84,52,0,107,82,73,84,53,0,107,82,73,84,54,0,107,82,73,84,55,0,107,85,80,0,107,85,80,51,0,107,85,80,52,0,107,85,80,53,0,107,85,80,54,0,107,85,80,55,0,107,97,50,0,107,98,49,0,107,98,51,0,107,99,50,0,107,112,53,0,107,112,65,68,68,0,107,112,67,77,65,0,107,112,68,73,86,0,107,112,68,79,84,0,107,112,77,85,76,0,107,112,83,85,66,0,107,112,90,82,79,0,114,109,120,120,0,115,109,120,120,0,120,109,0
};
// win32con|ANSI emulation for libuv on legacy console,
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index b40033296e..d66e57b13b 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -64,7 +64,7 @@ static handle_T cursor_grid_handle = DEFAULT_GRID_HANDLE;
static bool has_mouse = false;
static int pending_has_mouse = -1;
-#if MIN_LOG_LEVEL > DEBUG_LOG_LEVEL
+#if MIN_LOG_LEVEL > LOGLVL_DBG
# define UI_LOG(funname)
#else
static size_t uilog_seen = 0;
@@ -82,10 +82,10 @@ static char uilog_last_event[1024] = { 0 };
uilog_seen++; \
} else { \
if (uilog_seen > 0) { \
- logmsg(DEBUG_LOG_LEVEL, "UI: ", NULL, -1, true, \
+ logmsg(LOGLVL_DBG, "UI: ", NULL, -1, true, \
"%s (+%zu times...)", uilog_last_event, uilog_seen); \
} \
- logmsg(DEBUG_LOG_LEVEL, "UI: ", NULL, -1, true, STR(funname)); \
+ logmsg(LOGLVL_DBG, "UI: ", NULL, -1, true, STR(funname)); \
uilog_seen = 0; \
xstrlcpy(uilog_last_event, STR(funname), sizeof(uilog_last_event)); \
} \
diff --git a/src/nvim/ui_client.c b/src/nvim/ui_client.c
index 4d1b9b1c52..be01538f67 100644
--- a/src/nvim/ui_client.c
+++ b/src/nvim/ui_client.c
@@ -16,18 +16,17 @@
#include "nvim/ui_client.h"
#include "nvim/vim.h"
-static Map(String, UIClientHandler) ui_client_handlers = MAP_INIT;
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "ui_client.c.generated.h"
+
+# include "ui_events_client.generated.h"
+#endif
// Temporary buffer for converting a single grid_line event
static size_t buf_size = 0;
static schar_T *buf_char = NULL;
static sattr_T *buf_attr = NULL;
-static void add_ui_client_event_handler(String method, UIClientHandler handler)
-{
- map_put(String, UIClientHandler)(&ui_client_handlers, method, handler);
-}
-
void ui_client_init(uint64_t chan)
{
Array args = ARRAY_DICT_INIT;
@@ -44,9 +43,6 @@ void ui_client_init(uint64_t chan)
ADD(args, DICTIONARY_OBJ(opts));
rpc_send_event(chan, "nvim_ui_attach", args);
- msgpack_rpc_add_redraw(); // GAME!
- // TODO(bfredl): use a keyset instead
- ui_client_methods_table_init();
ui_client_channel_id = chan;
}
@@ -61,22 +57,23 @@ void ui_client_init(uint64_t chan)
/// @param channel_id: The id of the rpc channel
/// @param uidata: The dense array containing the ui_events sent by the server
/// @param[out] err Error details, if any
-Object ui_client_handle_redraw(uint64_t channel_id, Array args, Error *error)
+Object handle_ui_client_redraw(uint64_t channel_id, Array args, Error *error)
{
for (size_t i = 0; i < args.size; i++) {
Array call = args.items[i].data.array;
String name = call.items[0].data.string;
- UIClientHandler handler = map_get(String, UIClientHandler)(&ui_client_handlers, name);
- if (!handler) {
+ int hash = ui_client_handler_hash(name.data, name.size);
+ if (hash < 0) {
ELOG("No ui client handler for %s", name.size ? name.data : "<empty>");
continue;
}
+ UIClientHandler handler = event_handlers[hash];
// fprintf(stderr, "%s: %zu\n", name.data, call.size-1);
DLOG("Invoke ui client handler for %s", name.data);
for (size_t j = 1; j < call.size; j++) {
- handler(call.items[j].data.array);
+ handler.fn(call.items[j].data.array);
}
}
@@ -108,10 +105,6 @@ static HlAttrs ui_client_dict2hlattrs(Dictionary d, bool rgb)
return dict2hlattrs(&dict, true, NULL, &err);
}
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "ui_events_client.generated.h"
-#endif
-
void ui_client_event_grid_resize(Array args)
{
if (args.size < 3
diff --git a/src/nvim/ui_client.h b/src/nvim/ui_client.h
index d31341ae60..41d9fa6227 100644
--- a/src/nvim/ui_client.h
+++ b/src/nvim/ui_client.h
@@ -3,7 +3,10 @@
#include "nvim/api/private/defs.h"
-typedef void (*UIClientHandler)(Array args);
+typedef struct {
+ const char *name;
+ void (*fn)(Array args);
+} UIClientHandler;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ui_client.h.generated.h"
diff --git a/src/nvim/vim.h b/src/nvim/vim.h
index 7e82af2d93..31ac5a67ff 100644
--- a/src/nvim/vim.h
+++ b/src/nvim/vim.h
@@ -70,7 +70,6 @@ enum { NUMBUFLEN = 65, };
#define MODE_EXTERNCMD 0x5000 // executing an external command
#define MODE_SHOWMATCH (0x6000 | MODE_INSERT) // show matching paren
#define MODE_CONFIRM 0x7000 // ":confirm" prompt
-#define MODE_CMDPREVIEW 0x8000 // Showing 'inccommand' command "live" preview.
/// Directions.
typedef enum {
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 060f498f07..5f4179944d 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -3993,6 +3993,7 @@ void win_init_size(void)
firstwin->w_height = ROWS_AVAIL;
firstwin->w_height_inner = firstwin->w_height - firstwin->w_winbar_height;
firstwin->w_height_outer = firstwin->w_height;
+ firstwin->w_winrow_off = firstwin->w_winbar_height;
topframe->fr_height = ROWS_AVAIL;
firstwin->w_width = Columns;
firstwin->w_width_inner = firstwin->w_width;
@@ -6677,7 +6678,7 @@ static bool resize_frame_for_winbar(frame_T *fr)
frame_new_height(fp, fp->fr_height - 1, false, false);
win_new_height(wp, wp->w_height + 1);
frame_fix_height(wp);
- win_comp_pos();
+ (void)win_comp_pos();
}
return true;
diff --git a/test/functional/api/command_spec.lua b/test/functional/api/command_spec.lua
index e4963e8a65..a9ec2b6541 100644
--- a/test/functional/api/command_spec.lua
+++ b/test/functional/api/command_spec.lua
@@ -16,8 +16,8 @@ local feed = helpers.feed
local funcs = helpers.funcs
describe('nvim_get_commands', function()
- local cmd_dict = { addr=NIL, bang=false, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='echo "Hello World"', name='Hello', nargs='1', range=NIL, register=false, keepscript=false, script_id=0, }
- local cmd_dict2 = { addr=NIL, bang=false, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='pwd', name='Pwd', nargs='?', range=NIL, register=false, keepscript=false, script_id=0, }
+ local cmd_dict = { addr=NIL, bang=false, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='echo "Hello World"', name='Hello', nargs='1', preview=false, range=NIL, register=false, keepscript=false, script_id=0, }
+ local cmd_dict2 = { addr=NIL, bang=false, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='pwd', name='Pwd', nargs='?', preview=false, range=NIL, register=false, keepscript=false, script_id=0, }
before_each(clear)
it('gets empty list if no commands were defined', function()
@@ -59,11 +59,11 @@ describe('nvim_get_commands', function()
end)
it('gets various command attributes', function()
- local cmd0 = { addr='arguments', bang=false, bar=false, complete='dir', complete_arg=NIL, count='10', definition='pwd <args>', name='TestCmd', nargs='1', range='10', register=false, keepscript=false, script_id=0, }
- local cmd1 = { addr=NIL, bang=false, bar=false, complete='custom', complete_arg='ListUsers', count=NIL, definition='!finger <args>', name='Finger', nargs='+', range=NIL, register=false, keepscript=false, script_id=1, }
- local cmd2 = { addr=NIL, bang=true, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253R2_foo(<q-args>)', name='Cmd2', nargs='*', range=NIL, register=false, keepscript=false, script_id=2, }
- local cmd3 = { addr=NIL, bang=false, bar=true, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253R3_ohyeah()', name='Cmd3', nargs='0', range=NIL, register=false, keepscript=false, script_id=3, }
- local cmd4 = { addr=NIL, bang=false, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253R4_just_great()', name='Cmd4', nargs='0', range=NIL, register=true, keepscript=false, script_id=4, }
+ local cmd0 = { addr='arguments', bang=false, bar=false, complete='dir', complete_arg=NIL, count='10', definition='pwd <args>', name='TestCmd', nargs='1', preview=false, range='10', register=false, keepscript=false, script_id=0, }
+ local cmd1 = { addr=NIL, bang=false, bar=false, complete='custom', complete_arg='ListUsers', count=NIL, definition='!finger <args>', name='Finger', nargs='+', preview=false, range=NIL, register=false, keepscript=false, script_id=1, }
+ local cmd2 = { addr=NIL, bang=true, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253R2_foo(<q-args>)', name='Cmd2', nargs='*', preview=false, range=NIL, register=false, keepscript=false, script_id=2, }
+ local cmd3 = { addr=NIL, bang=false, bar=true, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253R3_ohyeah()', name='Cmd3', nargs='0', preview=false, range=NIL, register=false, keepscript=false, script_id=3, }
+ local cmd4 = { addr=NIL, bang=false, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253R4_just_great()', name='Cmd4', nargs='0', preview=false, range=NIL, register=true, keepscript=false, script_id=4, }
source([[
let s:foo = 1
command -complete=custom,ListUsers -nargs=+ Finger !finger <args>
@@ -120,6 +120,25 @@ describe('nvim_create_user_command', function()
line1 = 1,
line2 = 1,
mods = "",
+ smods = {
+ browse = false,
+ confirm = false,
+ emsg_silent = false,
+ hide = false,
+ keepalt = false,
+ keepjumps = false,
+ keepmarks = false,
+ keeppatterns = false,
+ lockmarks = false,
+ noautocmd = false,
+ noswapfile = false,
+ sandbox = false,
+ silent = false,
+ split = "",
+ tab = 0,
+ verbose = -1,
+ vertical = false,
+ },
range = 0,
count = 2,
reg = "",
@@ -135,6 +154,25 @@ describe('nvim_create_user_command', function()
line1 = 1,
line2 = 1,
mods = "",
+ smods = {
+ browse = false,
+ confirm = false,
+ emsg_silent = false,
+ hide = false,
+ keepalt = false,
+ keepjumps = false,
+ keepmarks = false,
+ keeppatterns = false,
+ lockmarks = false,
+ noautocmd = false,
+ noswapfile = false,
+ sandbox = false,
+ silent = false,
+ split = "",
+ tab = 0,
+ verbose = -1,
+ vertical = false,
+ },
range = 0,
count = 2,
reg = "",
@@ -150,6 +188,25 @@ describe('nvim_create_user_command', function()
line1 = 1,
line2 = 1,
mods = "",
+ smods = {
+ browse = false,
+ confirm = false,
+ emsg_silent = false,
+ hide = false,
+ keepalt = false,
+ keepjumps = false,
+ keepmarks = false,
+ keeppatterns = false,
+ lockmarks = false,
+ noautocmd = false,
+ noswapfile = false,
+ sandbox = false,
+ silent = false,
+ split = "",
+ tab = 0,
+ verbose = -1,
+ vertical = false,
+ },
range = 0,
count = 2,
reg = "",
@@ -165,6 +222,25 @@ describe('nvim_create_user_command', function()
line1 = 10,
line2 = 10,
mods = "botright",
+ smods = {
+ browse = false,
+ confirm = false,
+ emsg_silent = false,
+ hide = false,
+ keepalt = false,
+ keepjumps = false,
+ keepmarks = false,
+ keeppatterns = false,
+ lockmarks = false,
+ noautocmd = false,
+ noswapfile = false,
+ sandbox = false,
+ silent = false,
+ split = "botright",
+ tab = 0,
+ verbose = -1,
+ vertical = false,
+ },
range = 1,
count = 10,
reg = "",
@@ -180,6 +256,25 @@ describe('nvim_create_user_command', function()
line1 = 1,
line2 = 42,
mods = "",
+ smods = {
+ browse = false,
+ confirm = false,
+ emsg_silent = false,
+ hide = false,
+ keepalt = false,
+ keepjumps = false,
+ keepmarks = false,
+ keeppatterns = false,
+ lockmarks = false,
+ noautocmd = false,
+ noswapfile = false,
+ sandbox = false,
+ silent = false,
+ split = "",
+ tab = 0,
+ verbose = -1,
+ vertical = false,
+ },
range = 1,
count = 42,
reg = "",
@@ -195,6 +290,25 @@ describe('nvim_create_user_command', function()
line1 = 1,
line2 = 1,
mods = "",
+ smods = {
+ browse = false,
+ confirm = false,
+ emsg_silent = false,
+ hide = false,
+ keepalt = false,
+ keepjumps = false,
+ keepmarks = false,
+ keeppatterns = false,
+ lockmarks = false,
+ noautocmd = false,
+ noswapfile = false,
+ sandbox = false,
+ silent = false,
+ split = "",
+ tab = 0,
+ verbose = -1,
+ vertical = false,
+ },
range = 0,
count = 2,
reg = "",
@@ -222,6 +336,25 @@ describe('nvim_create_user_command', function()
line1 = 1,
line2 = 1,
mods = "",
+ smods = {
+ browse = false,
+ confirm = false,
+ emsg_silent = false,
+ hide = false,
+ keepalt = false,
+ keepjumps = false,
+ keepmarks = false,
+ keeppatterns = false,
+ lockmarks = false,
+ noautocmd = false,
+ noswapfile = false,
+ sandbox = false,
+ silent = false,
+ split = "",
+ tab = 0,
+ verbose = -1,
+ vertical = false,
+ },
range = 0,
count = 2,
reg = "",
@@ -285,6 +418,16 @@ describe('nvim_create_user_command', function()
vim.api.nvim_create_user_command('💩', 'echo "hi"', {})
]]))
end)
+
+ it('smods can be used with nvim_cmd', function()
+ exec_lua[[
+ vim.api.nvim_create_user_command('MyEcho', function(opts)
+ vim.api.nvim_cmd({ cmd = 'echo', args = { '&verbose' }, mods = opts.smods }, {})
+ end, {})
+ ]]
+
+ eq("3", meths.cmd({ cmd = 'MyEcho', mods = { verbose = 3 } }, { output = true }))
+ end)
end)
describe('nvim_del_user_command', function()
diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua
index a2f8353868..785f72b3db 100644
--- a/test/functional/api/highlight_spec.lua
+++ b/test/functional/api/highlight_spec.lua
@@ -337,4 +337,10 @@ describe("API: set highlight", function()
exec_capture('highlight Test_hl3'))
end)
+
+ it ("correctly sets 'Normal' internal properties", function()
+ -- Normal has some special handling internally. #18024
+ meths.set_hl(0, 'Normal', {fg='#000083', bg='#0000F3'})
+ eq({foreground = 131, background = 243}, nvim("get_hl_by_name", 'Normal', true))
+ end)
end)
diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua
index 4fb2d55a76..4bee10a006 100644
--- a/test/functional/api/keymap_spec.lua
+++ b/test/functional/api/keymap_spec.lua
@@ -321,7 +321,7 @@ describe('nvim_get_keymap', function()
eq({space_table}, meths.get_keymap('n'))
end)
- it('can handle lua keymaps', function()
+ it('can handle lua mappings', function()
eq(0, exec_lua [[
GlobalCount = 0
vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
@@ -606,6 +606,13 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
eq({''}, curbufmeths.get_lines(0, -1, 0))
eq(generate_mapargs('i', 'lhs', '<NOP>', {}),
get_mapargs('i', 'lhs'))
+
+ -- a single ^V in RHS is also <Nop> (see :h map-empty-rhs)
+ meths.set_keymap('i', 'lhs', '\022', {})
+ command('normal ilhs')
+ eq({''}, curbufmeths.get_lines(0, -1, 0))
+ eq(generate_mapargs('i', 'lhs', '\022', {}),
+ get_mapargs('i', 'lhs'))
end)
it('treats an empty RHS in a mapping like a <Nop>', function()
@@ -785,7 +792,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
end)
- it (':map command shows lua keymap correctly', function()
+ it (':map command shows lua mapping correctly', function()
exec_lua [[
vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() print('jkl;') end })
]]
@@ -793,7 +800,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
"^\nn asdf <Lua function %d+>"))
end)
- it ('mapcheck() returns lua keymap correctly', function()
+ it ('mapcheck() returns lua mapping correctly', function()
exec_lua [[
vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() print('jkl;') end })
]]
@@ -801,7 +808,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
"^<Lua function %d+>"))
end)
- it ('maparg() returns lua keymap correctly', function()
+ it ('maparg() returns lua mapping correctly', function()
exec_lua [[
vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() print('jkl;') end })
]]
@@ -895,7 +902,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
eq('\nNo mapping found', helpers.exec_capture('nmap <C-I>'))
end)
- it('can set descriptions on keymaps', function()
+ it('can set descriptions on mappings', function()
meths.set_keymap('n', 'lhs', 'rhs', {desc="map description"})
eq(generate_mapargs('n', 'lhs', 'rhs', {desc="map description"}), get_mapargs('n', 'lhs'))
eq("\nn lhs rhs\n map description",
diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua
index 9aacac37bf..901d24327c 100644
--- a/test/functional/api/window_spec.lua
+++ b/test/functional/api/window_spec.lua
@@ -153,7 +153,7 @@ describe('API/win', function()
[1] = {bold = true, foreground = Screen.colors.Blue}, -- NonText
[2] = {background = Screen.colors.Grey90}, -- CursorLine
[3] = {bold = true, reverse = true}, -- StatusLine
- [4] = {reverse = true}, -- VertSplit, StatusLineNC
+ [4] = {reverse = true}, -- StatusLineNC
})
screen:attach()
command('set ruler')
diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua
index cf24e570cb..461a69f357 100644
--- a/test/functional/core/job_spec.lua
+++ b/test/functional/core/job_spec.lua
@@ -682,6 +682,7 @@ describe('jobs', function()
-- User can explicitly set $NVIM_LOG_FILE, $VIM, $VIMRUNTIME.
eq('NVIM_LOG_FILE=Xtest_jobstart_env',
get_env_in_child_job('NVIM_LOG_FILE', { NVIM_LOG_FILE='Xtest_jobstart_env' }))
+ os.remove('Xtest_jobstart_env')
end)
describe('jobwait', function()
diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua
index f87fd8e951..18d48efadc 100644
--- a/test/functional/core/startup_spec.lua
+++ b/test/functional/core/startup_spec.lua
@@ -255,6 +255,7 @@ describe('startup', function()
it('does not crash when expanding cdpath during early_init', function()
clear{env={CDPATH='~doesnotexist'}}
+ assert_alive()
eq(',~doesnotexist', eval('&cdpath'))
end)
diff --git a/test/functional/ex_cmds/ctrl_c_spec.lua b/test/functional/editor/ctrl_c_spec.lua
index c2e4bf0fb7..60131bf2a4 100644
--- a/test/functional/ex_cmds/ctrl_c_spec.lua
+++ b/test/functional/editor/ctrl_c_spec.lua
@@ -72,4 +72,23 @@ describe("CTRL-C (mapped)", function()
-- INSERT -- |
]])
end)
+
+ it('interrupts recursive mapping', function()
+ command('nnoremap <C-C> <Nop>')
+ command('nmap <F2> <Ignore><F2>')
+ feed('<F2>')
+ sleep(10)
+ feed('foo<C-C>')
+ -- wait for input buffer to be flushed
+ sleep(10)
+ feed('i')
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ -- INSERT -- |
+ ]])
+ end)
end)
diff --git a/test/functional/fixtures/fake-lsp-server.lua b/test/functional/fixtures/fake-lsp-server.lua
index 86cdf4ef56..0dc0c8c2db 100644
--- a/test/functional/fixtures/fake-lsp-server.lua
+++ b/test/functional/fixtures/fake-lsp-server.lua
@@ -116,6 +116,19 @@ function tests.basic_init()
}
end
+function tests.basic_init_did_change_configuration()
+ skeleton({
+ on_init = function(_)
+ return {
+ capabilities = {},
+ }
+ end,
+ body = function()
+ expect_notification('workspace/didChangeConfiguration', { settings = { dummy = 1 } })
+ end,
+ })
+end
+
function tests.check_workspace_configuration()
skeleton {
on_init = function(_params)
@@ -744,6 +757,35 @@ function tests.code_action_with_resolve()
}
end
+function tests.code_action_server_side_command()
+ skeleton({
+ on_init = function()
+ return {
+ capabilities = {
+ codeActionProvider = {
+ resolveProvider = false,
+ },
+ },
+ }
+ end,
+ body = function()
+ notify('start')
+ local cmd = {
+ title = 'Command 1',
+ command = 'dummy1',
+ }
+ expect_request('textDocument/codeAction', function()
+ return nil, { cmd }
+ end)
+ expect_request('workspace/executeCommand', function()
+ return nil, cmd
+ end)
+ notify('shutdown')
+ end,
+ })
+end
+
+
function tests.code_action_filter()
skeleton {
on_init = function()
diff --git a/test/functional/fixtures/lua/test_plug/health/init.lua b/test/functional/fixtures/lua/test_plug/health/init.lua
index d07632cff4..58162d4515 100644
--- a/test/functional/fixtures/lua/test_plug/health/init.lua
+++ b/test/functional/fixtures/lua/test_plug/health/init.lua
@@ -1,11 +1,10 @@
local M = {}
-local health = require("health")
M.check = function()
- health.report_start("report 1")
- health.report_ok("everything is fine")
- health.report_start("report 2")
- health.report_ok("nothing to see here")
+ vim.health.report_start("report 1")
+ vim.health.report_ok("everything is fine")
+ vim.health.report_start("report 2")
+ vim.health.report_ok("nothing to see here")
end
return M
diff --git a/test/functional/fixtures/lua/test_plug/submodule/health.lua b/test/functional/fixtures/lua/test_plug/submodule/health.lua
index d07632cff4..58162d4515 100644
--- a/test/functional/fixtures/lua/test_plug/submodule/health.lua
+++ b/test/functional/fixtures/lua/test_plug/submodule/health.lua
@@ -1,11 +1,10 @@
local M = {}
-local health = require("health")
M.check = function()
- health.report_start("report 1")
- health.report_ok("everything is fine")
- health.report_start("report 2")
- health.report_ok("nothing to see here")
+ vim.health.report_start("report 1")
+ vim.health.report_ok("everything is fine")
+ vim.health.report_start("report 2")
+ vim.health.report_ok("nothing to see here")
end
return M
diff --git a/test/functional/fixtures/lua/test_plug/submodule_failed/health.lua b/test/functional/fixtures/lua/test_plug/submodule_failed/health.lua
index 3a8af6ebb2..ee5f4e404b 100644
--- a/test/functional/fixtures/lua/test_plug/submodule_failed/health.lua
+++ b/test/functional/fixtures/lua/test_plug/submodule_failed/health.lua
@@ -1,10 +1,9 @@
local M = {}
-local health = require("health")
M.check = function()
- health.report_start("report 1")
- health.report_ok("everything is fine")
- health.report_warn("About to add a number to nil")
+ vim.health.report_start("report 1")
+ vim.health.report_ok("everything is fine")
+ vim.health.report_warn("About to add a number to nil")
local a = nil + 2
return a
end
diff --git a/test/functional/legacy/display_spec.lua b/test/functional/legacy/display_spec.lua
index ccc709cbf6..f0ffaf2c48 100644
--- a/test/functional/legacy/display_spec.lua
+++ b/test/functional/legacy/display_spec.lua
@@ -61,7 +61,7 @@ describe('display', function()
screen:set_default_attr_ids({
[1] = {bold = true, foreground = Screen.colors.Blue}, -- NonText
[2] = {bold = true, reverse = true}, -- StatusLine
- [3] = {reverse = true}, -- VertSplit, StatusLineNC
+ [3] = {reverse = true}, -- StatusLineNC
})
screen:attach()
exec([[
diff --git a/test/functional/legacy/fixeol_spec.lua b/test/functional/legacy/fixeol_spec.lua
index d3ff86d349..3cc9d54e2b 100644
--- a/test/functional/legacy/fixeol_spec.lua
+++ b/test/functional/legacy/fixeol_spec.lua
@@ -6,12 +6,11 @@ local clear, feed_command, expect = helpers.clear, helpers.feed_command, helpers
describe('fixeol', function()
local function rmtestfiles()
- feed_command('%bwipeout!')
- feed_command('call delete("test.out")')
- feed_command('call delete("XXEol")')
- feed_command('call delete("XXNoEol")')
- feed_command('call delete("XXTestEol")')
- feed_command('call delete("XXTestNoEol")')
+ os.remove("test.out")
+ os.remove("XXEol")
+ os.remove("XXNoEol")
+ os.remove("XXTestEol")
+ os.remove("XXTestNoEol")
end
setup(function()
clear()
diff --git a/test/functional/legacy/listchars_spec.lua b/test/functional/legacy/listchars_spec.lua
index a94ec431d4..a9aa238d4e 100644
--- a/test/functional/legacy/listchars_spec.lua
+++ b/test/functional/legacy/listchars_spec.lua
@@ -105,7 +105,7 @@ describe("'listchars'", function()
screen:set_default_attr_ids({
[1] = {bold = true, foreground = Screen.colors.Blue}, -- NonText
[2] = {bold = true, reverse = true}, -- StatusLine
- [3] = {reverse = true}, -- StatusLineNC, VertSplit
+ [3] = {reverse = true}, -- StatusLineNC
[4] = {background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue}, -- FoldColumn, SignColumn
})
screen:attach()
diff --git a/test/functional/legacy/mapping_spec.lua b/test/functional/legacy/mapping_spec.lua
index 552c26e7f2..456acc12b5 100644
--- a/test/functional/legacy/mapping_spec.lua
+++ b/test/functional/legacy/mapping_spec.lua
@@ -162,4 +162,21 @@ describe('mapping', function()
sleep(10)
eq('n', eval('mode()'))
end)
+
+ it('timeout works after an <Nop> mapping is triggered on timeout vim-patch:8.1.0052', function()
+ command('set timeout timeoutlen=400')
+ command('inoremap ab TEST')
+ command('inoremap a <Nop>')
+ -- Enter Insert mode
+ feed('i')
+ -- Wait for the "a" mapping to time out
+ feed('a')
+ sleep(500)
+ -- Send "a" and wait for a period shorter than 'timeoutlen'
+ feed('a')
+ sleep(100)
+ -- Send "b", should trigger the "ab" mapping
+ feed('b')
+ expect('TEST')
+ end)
end)
diff --git a/test/functional/legacy/statusline_spec.lua b/test/functional/legacy/statusline_spec.lua
index 5eb46077ec..e2b30a7c82 100644
--- a/test/functional/legacy/statusline_spec.lua
+++ b/test/functional/legacy/statusline_spec.lua
@@ -18,7 +18,7 @@ describe('statusline', function()
screen:set_default_attr_ids({
[1] = {bold = true, foreground = Screen.colors.Blue}, -- NonText
[2] = {bold = true, reverse = true}, -- StatusLine
- [3] = {reverse = true}, -- StatusLineNC, VertSplit
+ [3] = {reverse = true}, -- StatusLineNC
})
exec([[
setlocal statusline=-%{mode()}-
diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua
index 7f929db8bf..f9647f5b6a 100644
--- a/test/functional/lua/diagnostic_spec.lua
+++ b/test/functional/lua/diagnostic_spec.lua
@@ -729,6 +729,19 @@ describe('vim.diagnostic', function()
return vim.diagnostic.get_next_pos { namespace = diagnostic_ns }
]])
end)
+
+ it('works with diagnostics before the start of the line', function()
+ eq({4, 0}, exec_lua [[
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error('Diagnostic #1', 3, 9001, 3, 9001),
+ make_error('Diagnostic #2', 4, -1, 4, -1),
+ })
+ vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
+ vim.api.nvim_win_set_cursor(0, {1, 1})
+ vim.diagnostic.goto_next { float = false }
+ return vim.diagnostic.get_next_pos { namespace = diagnostic_ns }
+ ]])
+end)
end)
describe('get_prev_pos()', function()
diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua
new file mode 100644
index 0000000000..2bcc84db0f
--- /dev/null
+++ b/test/functional/lua/fs_spec.lua
@@ -0,0 +1,101 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local clear = helpers.clear
+local exec_lua = helpers.exec_lua
+local eq = helpers.eq
+local mkdir_p = helpers.mkdir_p
+local rmdir = helpers.rmdir
+local nvim_dir = helpers.nvim_dir
+local test_build_dir = helpers.test_build_dir
+local iswin = helpers.iswin
+local nvim_prog = helpers.nvim_prog
+
+local nvim_prog_basename = iswin() and 'nvim.exe' or 'nvim'
+
+before_each(clear)
+
+describe('vim.fs', function()
+ describe('parents()', function()
+ it('works', function()
+ local test_dir = nvim_dir .. '/test'
+ mkdir_p(test_dir)
+ local dirs = exec_lua([[
+ local test_dir, test_build_dir = ...
+ local dirs = {}
+ for dir in vim.fs.parents(test_dir .. "/foo.txt") do
+ dirs[#dirs + 1] = dir
+ if dir == test_build_dir then
+ break
+ end
+ end
+ return dirs
+ ]], test_dir, test_build_dir)
+ eq({test_dir, nvim_dir, test_build_dir}, dirs)
+ rmdir(test_dir)
+ end)
+ end)
+
+ describe('dirname()', function()
+ it('works', function()
+ eq(test_build_dir, exec_lua([[
+ local nvim_dir = ...
+ return vim.fs.dirname(nvim_dir)
+ ]], nvim_dir))
+ end)
+ end)
+
+ describe('basename()', function()
+ it('works', function()
+ eq(nvim_prog_basename, exec_lua([[
+ local nvim_prog = ...
+ return vim.fs.basename(nvim_prog)
+ ]], nvim_prog))
+ end)
+ end)
+
+ describe('dir()', function()
+ it('works', function()
+ eq(true, exec_lua([[
+ local dir, nvim = ...
+ for name, type in vim.fs.dir(dir) do
+ if name == nvim and type == 'file' then
+ return true
+ end
+ end
+ return false
+ ]], nvim_dir, nvim_prog_basename))
+ end)
+ end)
+
+ describe('find()', function()
+ it('works', function()
+ eq({test_build_dir}, exec_lua([[
+ local dir = ...
+ return vim.fs.find('build', { path = dir, upward = true, type = 'directory' })
+ ]], nvim_dir))
+ eq({nvim_prog}, exec_lua([[
+ local dir, nvim = ...
+ return vim.fs.find(nvim, { path = dir, type = 'file' })
+ ]], test_build_dir, nvim_prog_basename))
+ end)
+ end)
+
+ describe('normalize()', function()
+ it('works with backward slashes', function()
+ eq('C:/Users/jdoe', exec_lua [[ return vim.fs.normalize('C:\\Users\\jdoe') ]])
+ end)
+ it('works with ~', function()
+ if iswin() then
+ pending([[$HOME does not exist on Windows ¯\_(ツ)_/¯]])
+ end
+ eq(os.getenv('HOME') .. '/src/foo', exec_lua [[ return vim.fs.normalize('~/src/foo') ]])
+ end)
+ it('works with environment variables', function()
+ local xdg_config_home = test_build_dir .. '/.config'
+ eq(xdg_config_home .. '/nvim', exec_lua([[
+ vim.env.XDG_CONFIG_HOME = ...
+ return vim.fs.normalize('$XDG_CONFIG_HOME/nvim')
+ ]], xdg_config_home))
+ end)
+ end)
+end)
diff --git a/test/functional/options/cursorbind_spec.lua b/test/functional/options/cursorbind_spec.lua
index fcb753451c..1a03ed099a 100644
--- a/test/functional/options/cursorbind_spec.lua
+++ b/test/functional/options/cursorbind_spec.lua
@@ -13,7 +13,7 @@ describe("'cursorbind'", function()
screen:set_default_attr_ids({
[1] = {bold = true, foreground = Screen.colors.Blue}, -- NonText
[2] = {bold = true, reverse = true}, -- StatusLine
- [3] = {reverse = true}, -- StatusLineNC, VertSplit
+ [3] = {reverse = true}, -- StatusLineNC
[4] = {background = Screen.colors.Grey90}, -- CursorLine, CursorColumn
})
screen:attach()
diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua
index a9bd76ce24..ba66117fb1 100644
--- a/test/functional/plugin/health_spec.lua
+++ b/test/functional/plugin/health_spec.lua
@@ -5,7 +5,7 @@ local Screen = require('test.functional.ui.screen')
local clear = helpers.clear
local curbuf_contents = helpers.curbuf_contents
local command = helpers.command
-local eq = helpers.eq
+local eq, neq = helpers.eq, helpers.neq
local getcompletion = helpers.funcs.getcompletion
describe(':checkhealth', function()
@@ -37,6 +37,7 @@ describe(':checkhealth', function()
eq('nvim', getcompletion('nvim', 'checkhealth')[1])
eq('provider', getcompletion('prov', 'checkhealth')[1])
eq('vim.lsp', getcompletion('vim.ls', 'checkhealth')[1])
+ neq('vim', getcompletion('^vim', 'checkhealth')[1]) -- should not complete vim.health
end)
end)
@@ -242,6 +243,13 @@ describe('health.vim', function()
- ERROR: No healthcheck found for "non_existent_healthcheck" plugin.
]])
end)
+
+ it("does not use vim.health as a healtcheck", function()
+ -- vim.health is not a healthcheck
+ command("checkhealth vim")
+ helpers.expect([[
+ ERROR: No healthchecks found.]])
+ end)
end)
end)
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua
index f8d4552330..22e2354723 100644
--- a/test/functional/plugin/lsp_spec.lua
+++ b/test/functional/plugin/lsp_spec.lua
@@ -45,10 +45,10 @@ local function clear_notrace()
end
-local function fake_lsp_server_setup(test_name, timeout_ms, options)
+local function fake_lsp_server_setup(test_name, timeout_ms, options, settings)
exec_lua([=[
lsp = require('vim.lsp')
- local test_name, fixture_filename, logfile, timeout, options = ...
+ local test_name, fixture_filename, logfile, timeout, options, settings = ...
TEST_RPC_CLIENT_ID = lsp.start_client {
cmd_env = {
NVIM_LOG_FILE = logfile;
@@ -79,17 +79,18 @@ local function fake_lsp_server_setup(test_name, timeout_ms, options)
allow_incremental_sync = options.allow_incremental_sync or false;
debounce_text_changes = options.debounce_text_changes or 0;
};
+ settings = settings;
on_exit = function(...)
vim.rpcnotify(1, "exit", ...)
end;
}
- ]=], test_name, fake_lsp_code, fake_lsp_logfile, timeout_ms or 1e3, options or {})
+ ]=], test_name, fake_lsp_code, fake_lsp_logfile, timeout_ms or 1e3, options or {}, settings or {})
end
local function test_rpc_server(config)
if config.test_name then
clear_notrace()
- fake_lsp_server_setup(config.test_name, config.timeout_ms or 1e3, config.options)
+ fake_lsp_server_setup(config.test_name, config.timeout_ms or 1e3, config.options, config.settings)
end
local client = setmetatable({}, {
__index = function(_, name)
@@ -298,6 +299,22 @@ describe('LSP', function()
}
end)
+ it('should send didChangeConfiguration after initialize if there are settings', function()
+ test_rpc_server({
+ test_name = 'basic_init_did_change_configuration',
+ on_init = function(client, _)
+ client.stop()
+ end,
+ on_exit = function(code, signal)
+ eq(0, code, 'exit code', fake_lsp_logfile)
+ eq(0, signal, 'exit signal', fake_lsp_logfile)
+ end,
+ settings = {
+ dummy = 1,
+ },
+ })
+ end)
+
it('should succeed with manual shutdown', function()
if isCI() then
pending('hangs the build on CI #14028, re-enable with freeze timeout #14204')
@@ -2776,6 +2793,45 @@ describe('LSP', function()
end
}
end)
+ it('Calls workspace/executeCommand if no client side command', function()
+ local client
+ local expected_handlers = {
+ { NIL, {}, { method = 'shutdown', client_id = 1 } },
+ {
+ NIL,
+ { command = 'dummy1', title = 'Command 1' },
+ { bufnr = 1, method = 'workspace/executeCommand', client_id = 1 },
+ },
+ { NIL, {}, { method = 'start', client_id = 1 } },
+ }
+ test_rpc_server({
+ test_name = 'code_action_server_side_command',
+ on_init = function(client_)
+ client = client_
+ end,
+ on_setup = function() end,
+ on_exit = function(code, signal)
+ eq(0, code, 'exit code', fake_lsp_logfile)
+ eq(0, signal, 'exit signal', fake_lsp_logfile)
+ end,
+ on_handler = function(err, result, ctx)
+ ctx.params = nil -- don't compare in assert
+ eq(table.remove(expected_handlers), { err, result, ctx })
+ if ctx.method == 'start' then
+ exec_lua([[
+ local bufnr = vim.api.nvim_get_current_buf()
+ vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID)
+ vim.fn.inputlist = function()
+ return 1
+ end
+ vim.lsp.buf.code_action()
+ ]])
+ elseif ctx.method == 'shutdown' then
+ client.stop()
+ end
+ end,
+ })
+ end)
it('Filters and automatically applies action if requested', function()
local client
local expected_handlers = {
diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua
index d61eebe3ea..a1423c98a8 100644
--- a/test/functional/ui/cursor_spec.lua
+++ b/test/functional/ui/cursor_spec.lua
@@ -212,7 +212,7 @@ describe('ui/cursor', function()
if m.blinkwait then m.blinkwait = 700 end
end
if m.hl_id then
- m.hl_id = 64
+ m.hl_id = 60
m.attr = {background = Screen.colors.DarkGray}
end
if m.id_lm then m.id_lm = 65 end
diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua
index 4982506631..cf4845474f 100644
--- a/test/functional/ui/decorations_spec.lua
+++ b/test/functional/ui/decorations_spec.lua
@@ -30,6 +30,7 @@ describe('decorations providers', function()
[11] = {foreground = Screen.colors.Red, background = tonumber('0x005028')};
[12] = {foreground = tonumber('0x990000')};
[13] = {background = Screen.colors.LightBlue};
+ [14] = {background = Screen.colors.WebGray, foreground = Screen.colors.DarkBlue};
}
end)
@@ -404,6 +405,50 @@ describe('decorations providers', function()
|
]]}
end)
+
+ it('can create and remove signs when CursorMoved autocommand validates botline #18661', function()
+ exec_lua([[
+ local lines = {}
+ for i = 1, 200 do
+ lines[i] = 'hello' .. tostring(i)
+ end
+ vim.api.nvim_buf_set_lines(0, 0, -1, false, lines)
+ ]])
+ setup_provider([[
+ local function on_do(kind, winid, bufnr, topline, botline_guess)
+ if kind == 'win' then
+ if topline < 100 and botline_guess > 100 then
+ vim.api.nvim_buf_set_extmark(bufnr, ns1, 99, -1, { sign_text = 'X' })
+ else
+ vim.api.nvim_buf_clear_namespace(bufnr, ns1, 0, -1)
+ end
+ end
+ end
+ ]])
+ command([[autocmd CursorMoved * call line('w$')]])
+ meths.win_set_cursor(0, {100, 0})
+ screen:expect([[
+ {14: }hello97 |
+ {14: }hello98 |
+ {14: }hello99 |
+ X ^hello100 |
+ {14: }hello101 |
+ {14: }hello102 |
+ {14: }hello103 |
+ |
+ ]])
+ meths.win_set_cursor(0, {1, 0})
+ screen:expect([[
+ ^hello1 |
+ hello2 |
+ hello3 |
+ hello4 |
+ hello5 |
+ hello6 |
+ hello7 |
+ |
+ ]])
+ end)
end)
describe('extmark decorations', function()
diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua
index ca5e269f92..fdd1504b13 100644
--- a/test/functional/ui/float_spec.lua
+++ b/test/functional/ui/float_spec.lua
@@ -3660,10 +3660,10 @@ describe('float window', function()
screen:expect{grid=[[
## grid 1
[2:----------------------------------------]|
- {5:[No Name] }|
- [5:----------------------------------------]|
- [5:----------------------------------------]|
- [5:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
{5:[Preview] }|
[3:----------------------------------------]|
## grid 2
@@ -3674,10 +3674,6 @@ describe('float window', function()
{17:f}{1:oo }|
{17:b}{1:ar }|
{1: }|
- ## grid 5
- |1| {17:f}oo |
- |2| {17:b}ar |
- {0:~ }|
]], float_pos=expected_pos}
else
screen:expect([[
diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua
index fb80444430..a95cb0e83a 100644
--- a/test/functional/ui/inccommand_spec.lua
+++ b/test/functional/ui/inccommand_spec.lua
@@ -255,42 +255,6 @@ describe(":substitute, 'inccommand' preserves", function()
end)
end
- for _, case in pairs{"", "split", "nosplit"} do
- it("visual selection for non-previewable command (inccommand="..case..") #5888", function()
- local screen = Screen.new(30,10)
- common_setup(screen, case, default_text)
- feed('1G2V')
-
- feed(':s')
- screen:expect([[
- {vis:Inc substitution on} |
- t{vis:wo lines} |
- |
- {15:~ }|
- {15:~ }|
- {15:~ }|
- {15:~ }|
- {15:~ }|
- {15:~ }|
- :'<,'>s^ |
- ]])
-
- feed('o')
- screen:expect([[
- {vis:Inc substitution on} |
- t{vis:wo lines} |
- |
- {15:~ }|
- {15:~ }|
- {15:~ }|
- {15:~ }|
- {15:~ }|
- {15:~ }|
- :'<,'>so^ |
- ]])
- end)
- end
-
for _, case in ipairs({'', 'split', 'nosplit'}) do
it('previous substitute string ~ (inccommand='..case..') #12109', function()
local screen = Screen.new(30,10)
@@ -1343,6 +1307,108 @@ describe(":substitute, inccommand=split", function()
]])
end)
+ it([[preview changes correctly with c_CTRL-R_= and c_CTRL-\_e]], function()
+ feed('gg')
+ feed(":1,2s/t/X")
+ screen:expect([[
+ Inc subs{12:X}itution on |
+ {12:X}wo lines |
+ Inc substitution on |
+ two lines |
+ |
+ {11:[No Name] [+] }|
+ |1| Inc subs{12:X}itution on |
+ |2| {12:X}wo lines |
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {10:[Preview] }|
+ :1,2s/t/X^ |
+ ]])
+
+ feed([[<C-R>='Y']])
+ -- preview should be unchanged during c_CTRL-R_= editing
+ screen:expect([[
+ Inc subs{12:X}itution on |
+ {12:X}wo lines |
+ Inc substitution on |
+ two lines |
+ |
+ {11:[No Name] [+] }|
+ |1| Inc subs{12:X}itution on |
+ |2| {12:X}wo lines |
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {10:[Preview] }|
+ ={1:'Y'}^ |
+ ]])
+
+ feed('<CR>')
+ -- preview should be changed by the result of the expression
+ screen:expect([[
+ Inc subs{12:XY}itution on |
+ {12:XY}wo lines |
+ Inc substitution on |
+ two lines |
+ |
+ {11:[No Name] [+] }|
+ |1| Inc subs{12:XY}itution on |
+ |2| {12:XY}wo lines |
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {10:[Preview] }|
+ :1,2s/t/XY^ |
+ ]])
+
+ feed([[<C-\>e'echo']])
+ -- preview should be unchanged during c_CTRL-\_e editing
+ screen:expect([[
+ Inc subs{12:XY}itution on |
+ {12:XY}wo lines |
+ Inc substitution on |
+ two lines |
+ |
+ {11:[No Name] [+] }|
+ |1| Inc subs{12:XY}itution on |
+ |2| {12:XY}wo lines |
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {10:[Preview] }|
+ ={1:'echo'}^ |
+ ]])
+
+ feed('<CR>')
+ -- preview should be cleared if command is changed to a non-previewable one
+ screen:expect([[
+ Inc substitution on |
+ two lines |
+ Inc substitution on |
+ two lines |
+ |
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ :echo^ |
+ ]])
+ end)
+
end)
describe("inccommand=nosplit", function()
diff --git a/test/functional/ui/inccommand_user_spec.lua b/test/functional/ui/inccommand_user_spec.lua
new file mode 100644
index 0000000000..b5816f6fe6
--- /dev/null
+++ b/test/functional/ui/inccommand_user_spec.lua
@@ -0,0 +1,356 @@
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+local clear = helpers.clear
+local exec_lua = helpers.exec_lua
+local insert = helpers.insert
+local feed = helpers.feed
+local command = helpers.command
+local assert_alive = helpers.assert_alive
+
+-- Implements a :Replace command that works like :substitute.
+local setup_replace_cmd = [[
+ local function show_replace_preview(buf, use_preview_win, preview_ns, preview_buf, matches)
+ -- Find the width taken by the largest line number, used for padding the line numbers
+ local highest_lnum = math.max(matches[#matches][1], 1)
+ local highest_lnum_width = math.floor(math.log10(highest_lnum))
+ local preview_buf_line = 0
+
+ vim.g.prevns = preview_ns
+ vim.g.prevbuf = preview_buf
+
+ for _, match in ipairs(matches) do
+ local lnum = match[1]
+ local line_matches = match[2]
+ local prefix
+
+ if use_preview_win then
+ prefix = string.format(
+ '|%s%d| ',
+ string.rep(' ', highest_lnum_width - math.floor(math.log10(lnum))),
+ lnum
+ )
+
+ vim.api.nvim_buf_set_lines(
+ preview_buf,
+ preview_buf_line,
+ preview_buf_line,
+ 0,
+ { prefix .. vim.api.nvim_buf_get_lines(buf, lnum - 1, lnum, false)[1] }
+ )
+ end
+
+ for _, line_match in ipairs(line_matches) do
+ vim.api.nvim_buf_add_highlight(
+ buf,
+ preview_ns,
+ 'Substitute',
+ lnum - 1,
+ line_match[1],
+ line_match[2]
+ )
+
+ if use_preview_win then
+ vim.api.nvim_buf_add_highlight(
+ preview_buf,
+ preview_ns,
+ 'Substitute',
+ preview_buf_line,
+ #prefix + line_match[1],
+ #prefix + line_match[2]
+ )
+ end
+ end
+
+ preview_buf_line = preview_buf_line + 1
+ end
+
+ if use_preview_win then
+ return 2
+ else
+ return 1
+ end
+ end
+
+ local function do_replace(opts, preview, preview_ns, preview_buf)
+ local pat1 = opts.fargs[1] or ''
+ local pat2 = opts.fargs[2] or ''
+ local line1 = opts.line1
+ local line2 = opts.line2
+
+ local buf = vim.api.nvim_get_current_buf()
+ local lines = vim.api.nvim_buf_get_lines(buf, line1 - 1, line2, 0)
+ local matches = {}
+
+ for i, line in ipairs(lines) do
+ local startidx, endidx = 0, 0
+ local line_matches = {}
+ local num = 1
+
+ while startidx ~= -1 do
+ local match = vim.fn.matchstrpos(line, pat1, 0, num)
+ startidx, endidx = match[2], match[3]
+
+ if startidx ~= -1 then
+ line_matches[#line_matches+1] = { startidx, endidx }
+ end
+
+ num = num + 1
+ end
+
+ if #line_matches > 0 then
+ matches[#matches+1] = { line1 + i - 1, line_matches }
+ end
+ end
+
+ local new_lines = {}
+
+ for _, match in ipairs(matches) do
+ local lnum = match[1]
+ local line_matches = match[2]
+ local line = lines[lnum - line1 + 1]
+ local pat_width_differences = {}
+
+ -- If previewing, only replace the text in current buffer if pat2 isn't empty
+ -- Otherwise, always replace the text
+ if pat2 ~= '' or not preview then
+ if preview then
+ for _, line_match in ipairs(line_matches) do
+ local startidx, endidx = unpack(line_match)
+ local pat_match = line:sub(startidx + 1, endidx)
+
+ pat_width_differences[#pat_width_differences+1] =
+ #vim.fn.substitute(pat_match, pat1, pat2, 'g') - #pat_match
+ end
+ end
+
+ new_lines[lnum] = vim.fn.substitute(line, pat1, pat2, 'g')
+ end
+
+ -- Highlight the matches if previewing
+ if preview then
+ local idx_offset = 0
+ for i, line_match in ipairs(line_matches) do
+ local startidx, endidx = unpack(line_match)
+ -- Starting index of replacement text
+ local repl_startidx = startidx + idx_offset
+ -- Ending index of the replacement text (if pat2 isn't empty)
+ local repl_endidx
+
+ if pat2 ~= '' then
+ repl_endidx = endidx + idx_offset + pat_width_differences[i]
+ else
+ repl_endidx = endidx + idx_offset
+ end
+
+ if pat2 ~= '' then
+ idx_offset = idx_offset + pat_width_differences[i]
+ end
+
+ line_matches[i] = { repl_startidx, repl_endidx }
+ end
+ end
+ end
+
+ for lnum, line in pairs(new_lines) do
+ vim.api.nvim_buf_set_lines(buf, lnum - 1, lnum, false, { line })
+ end
+
+ if preview then
+ local lnum = vim.api.nvim_win_get_cursor(0)[1]
+ -- Use preview window only if preview buffer is provided and range isn't just the current line
+ local use_preview_win = (preview_buf ~= nil) and (line1 ~= lnum or line2 ~= lnum)
+ return show_replace_preview(buf, use_preview_win, preview_ns, preview_buf, matches)
+ end
+ end
+
+ local function replace(opts)
+ do_replace(opts, false)
+ end
+
+ local function replace_preview(opts, preview_ns, preview_buf)
+ return do_replace(opts, true, preview_ns, preview_buf)
+ end
+
+ -- ":<range>Replace <pat1> <pat2>"
+ -- Replaces all occurences of <pat1> in <range> with <pat2>
+ vim.api.nvim_create_user_command(
+ 'Replace',
+ replace,
+ { nargs = '*', range = '%', addr = 'lines',
+ preview = replace_preview }
+ )
+]]
+
+describe("'inccommand' for user commands", function()
+ local screen
+
+ before_each(function()
+ clear()
+ screen = Screen.new(40, 17)
+ screen:set_default_attr_ids({
+ [1] = {background = Screen.colors.Yellow1},
+ [2] = {foreground = Screen.colors.Blue1, bold = true},
+ [3] = {reverse = true},
+ [4] = {reverse = true, bold = true}
+ })
+ screen:attach()
+ exec_lua(setup_replace_cmd)
+ command('set cmdwinheight=5')
+ insert[[
+ text on line 1
+ more text on line 2
+ oh no, even more text
+ will the text ever stop
+ oh well
+ did the text stop
+ why won't it stop
+ make the text stop
+ ]]
+ end)
+
+ it('works with inccommand=nosplit', function()
+ command('set inccommand=nosplit')
+ feed(':Replace text cats')
+ screen:expect([[
+ {1:cats} on line 1 |
+ more {1:cats} on line 2 |
+ oh no, even more {1:cats} |
+ will the {1:cats} ever stop |
+ oh well |
+ did the {1:cats} stop |
+ why won't it stop |
+ make the {1:cats} stop |
+ |
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ :Replace text cats^ |
+ ]])
+ end)
+
+ it('works with inccommand=split', function()
+ command('set inccommand=split')
+ feed(':Replace text cats')
+ screen:expect([[
+ {1:cats} on line 1 |
+ more {1:cats} on line 2 |
+ oh no, even more {1:cats} |
+ will the {1:cats} ever stop |
+ oh well |
+ did the {1:cats} stop |
+ why won't it stop |
+ make the {1:cats} stop |
+ |
+ {4:[No Name] [+] }|
+ |1| {1:cats} on line 1 |
+ |2| more {1:cats} on line 2 |
+ |3| oh no, even more {1:cats} |
+ |4| will the {1:cats} ever stop |
+ |6| did the {1:cats} stop |
+ {3:[Preview] }|
+ :Replace text cats^ |
+ ]])
+ end)
+
+ it('properly closes preview when inccommand=split', function()
+ command('set inccommand=split')
+ feed(':Replace text cats<Esc>')
+ screen:expect([[
+ text on line 1 |
+ more text on line 2 |
+ oh no, even more text |
+ will the text ever stop |
+ oh well |
+ did the text stop |
+ why won't it stop |
+ make the text stop |
+ ^ |
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ |
+ ]])
+ end)
+
+ it('properly executes command when inccommand=split', function()
+ command('set inccommand=split')
+ feed(':Replace text cats<CR>')
+ screen:expect([[
+ cats on line 1 |
+ more cats on line 2 |
+ oh no, even more cats |
+ will the cats ever stop |
+ oh well |
+ did the cats stop |
+ why won't it stop |
+ make the cats stop |
+ ^ |
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ :Replace text cats |
+ ]])
+ end)
+
+ it('shows preview window only when range is not current line', function()
+ command('set inccommand=split')
+ feed('gg:.Replace text cats')
+ screen:expect([[
+ {1:cats} on line 1 |
+ more text on line 2 |
+ oh no, even more text |
+ will the text ever stop |
+ oh well |
+ did the text stop |
+ why won't it stop |
+ make the text stop |
+ |
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ :.Replace text cats^ |
+ ]])
+ end)
+
+ it('does not crash on ambiguous command #18825', function()
+ command('set inccommand=split')
+ command('command Reply echo 1')
+ feed(':R')
+ assert_alive()
+ feed('e')
+ assert_alive()
+ end)
+
+ it('no crash if preview callback changes inccommand option', function()
+ command('set inccommand=nosplit')
+ exec_lua([[
+ vim.api.nvim_create_user_command('Replace', function() end, {
+ nargs = '*',
+ preview = function()
+ vim.api.nvim_set_option('inccommand', 'split')
+ return 2
+ end,
+ })
+ ]])
+ feed(':R')
+ assert_alive()
+ feed('e')
+ assert_alive()
+ end)
+end)
diff --git a/test/functional/ui/winbar_spec.lua b/test/functional/ui/winbar_spec.lua
index 982d2d67fd..92a6ab2e84 100644
--- a/test/functional/ui/winbar_spec.lua
+++ b/test/functional/ui/winbar_spec.lua
@@ -26,6 +26,8 @@ describe('winbar', function()
[7] = {background = Screen.colors.LightGrey},
[8] = {background = Screen.colors.LightMagenta},
[9] = {bold = true, foreground = Screen.colors.Blue, background = Screen.colors.LightMagenta},
+ [10] = {background = Screen.colors.LightGrey, underline = true},
+ [11] = {background = Screen.colors.LightGrey, underline = true, bold = true, foreground = Screen.colors.Magenta},
})
meths.set_option('winbar', 'Set Up The Bars')
end)
@@ -245,6 +247,23 @@ describe('winbar', function()
{4:[No Name] }|
|
]])
+ -- Test for issue #18791
+ command('tabnew')
+ screen:expect([[
+ {10: }{11:4}{10: [No Name] }{1: [No Name] }{2: }{10:X}|
+ {1:Set Up The Bars }|
+ ^ |
+ {3:~ }|
+ {3:~ }|
+ {3:~ }|
+ {3:~ }|
+ {3:~ }|
+ {3:~ }|
+ {3:~ }|
+ {3:~ }|
+ {4:[No Name] }|
+ |
+ ]])
end)
it('mouse click and drag work correctly in buffer', function()
diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt
index 06c3d47104..84b360b9d9 100644
--- a/third-party/CMakeLists.txt
+++ b/third-party/CMakeLists.txt
@@ -165,8 +165,8 @@ set(LIBVTERM_URL https://www.leonerd.org.uk/code/libvterm/libvterm-0.1.4.tar.gz)
set(LIBVTERM_SHA256 bc70349e95559c667672fc8c55b9527d9db9ada0fb80a3beda533418d782d3dd)
set(LUV_VERSION 1.43.0-0)
-set(LUV_URL https://github.com/luvit/luv/archive/c51e7052ec4f0a25058f70c1b4ee99dd36180e59.tar.gz)
-set(LUV_SHA256 cabb7e650f35992686eb95ae167c71614e281cd2979fc804e4e70f8051555728)
+set(LUV_URL https://github.com/luvit/luv/archive/9f80386338af7d164ff1f47d480ee1ae775cb0ef.tar.gz)
+set(LUV_SHA256 a6fe420f06944c0d84a173fccff2eb0d14dfd1293bc24666a580b98dd1a7254f)
set(LUA_COMPAT53_URL https://github.com/keplerproject/lua-compat-5.3/archive/v0.9.tar.gz)
set(LUA_COMPAT53_SHA256 ad05540d2d96a48725bb79a1def35cf6652a4e2ec26376e2617c8ce2baa6f416)