aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/labeler.yml41
-rw-r--r--.github/workflows/api-docs.yml51
-rw-r--r--.github/workflows/ci.yml1
-rw-r--r--.github/workflows/labeler.yml13
-rw-r--r--.github/workflows/release.yml1
-rw-r--r--README.md2
-rw-r--r--runtime/CMakeLists.txt2
-rw-r--r--runtime/autoload/health/provider.vim4
-rw-r--r--runtime/doc/api.txt29
-rw-r--r--runtime/doc/change.txt23
-rw-r--r--runtime/doc/cmdline.txt6
-rw-r--r--runtime/doc/eval.txt159
-rw-r--r--runtime/doc/index.txt2
-rw-r--r--runtime/doc/intro.txt1
-rw-r--r--runtime/doc/lsp.txt229
-rw-r--r--runtime/doc/lua.txt312
-rw-r--r--runtime/doc/map.txt16
-rw-r--r--runtime/doc/mlang.txt12
-rw-r--r--runtime/doc/options.txt38
-rw-r--r--runtime/doc/pi_netrw.txt4
-rw-r--r--runtime/doc/quickfix.txt69
-rw-r--r--runtime/doc/repeat.txt5
-rw-r--r--runtime/doc/syntax.txt12
-rw-r--r--runtime/doc/treesitter.txt13
-rw-r--r--runtime/doc/usr_41.txt12
-rw-r--r--runtime/doc/usr_42.txt4
-rw-r--r--runtime/doc/vim_diff.txt3
-rw-r--r--runtime/filetype.vim3
-rw-r--r--runtime/ftplugin/meson.vim1
-rw-r--r--runtime/ftplugin/systemverilog.vim35
-rw-r--r--runtime/indent/meson.vim1
-rw-r--r--runtime/lua/vim/F.lua7
-rw-r--r--runtime/lua/vim/_meta.lua631
-rw-r--r--runtime/lua/vim/lsp.lua2
-rw-r--r--runtime/lua/vim/lsp/buf.lua24
-rw-r--r--runtime/lua/vim/lsp/diagnostic.lua75
-rw-r--r--runtime/lua/vim/lsp/handlers.lua40
-rw-r--r--runtime/lua/vim/lsp/rpc.lua28
-rw-r--r--runtime/lua/vim/lsp/util.lua491
-rw-r--r--runtime/lua/vim/treesitter/query.lua22
-rw-r--r--runtime/pack/dist/opt/termdebug/plugin/termdebug.vim6
-rw-r--r--runtime/syntax/dts.vim54
-rw-r--r--runtime/syntax/lsp_markdown.vim4
-rw-r--r--runtime/syntax/meson.vim53
-rw-r--r--runtime/tutor/tutor.tutor2
-rwxr-xr-xscripts/vim-patch.sh24
-rw-r--r--snap/snapcraft.yaml2
-rw-r--r--src/nvim/CMakeLists.txt2
-rw-r--r--src/nvim/api/buffer.c14
-rw-r--r--src/nvim/api/private/helpers.c80
-rw-r--r--src/nvim/api/ui_events.in.h3
-rw-r--r--src/nvim/api/vim.c25
-rw-r--r--src/nvim/api/window.c32
-rw-r--r--src/nvim/buffer.c86
-rw-r--r--src/nvim/buffer_defs.h6
-rw-r--r--src/nvim/change.c6
-rw-r--r--src/nvim/channel.c1
-rw-r--r--src/nvim/cursor.c5
-rw-r--r--src/nvim/diff.c6
-rw-r--r--src/nvim/edit.c11
-rw-r--r--src/nvim/eval.c23
-rw-r--r--src/nvim/eval.h1
-rw-r--r--src/nvim/eval.lua3
-rw-r--r--src/nvim/eval/funcs.c145
-rw-r--r--src/nvim/eval/typval.c13
-rw-r--r--src/nvim/eval/userfunc.c4
-rw-r--r--src/nvim/ex_cmds.c58
-rw-r--r--src/nvim/ex_cmds2.c23
-rw-r--r--src/nvim/ex_docmd.c56
-rw-r--r--src/nvim/ex_getln.c11
-rw-r--r--src/nvim/ex_session.c17
-rw-r--r--src/nvim/fileio.c109
-rw-r--r--src/nvim/globals.h2
-rw-r--r--src/nvim/grid_defs.h14
-rw-r--r--src/nvim/highlight.c38
-rw-r--r--src/nvim/indent_c.c2
-rw-r--r--src/nvim/lua/executor.c112
-rw-r--r--src/nvim/lua/vim.lua274
-rw-r--r--src/nvim/macros.h1
-rw-r--r--src/nvim/marktree.c2
-rw-r--r--src/nvim/memline.c2
-rw-r--r--src/nvim/menu.c20
-rw-r--r--src/nvim/message.c1
-rw-r--r--src/nvim/misc1.c18
-rw-r--r--src/nvim/normal.c24
-rw-r--r--src/nvim/ops.c73
-rw-r--r--src/nvim/ops.h16
-rw-r--r--src/nvim/option.c34
-rw-r--r--src/nvim/option.h3
-rw-r--r--src/nvim/option_defs.h8
-rw-r--r--src/nvim/options.lua8
-rw-r--r--src/nvim/path.c2
-rw-r--r--src/nvim/popupmnu.c6
-rw-r--r--src/nvim/quickfix.c271
-rw-r--r--src/nvim/regexp.c40
-rw-r--r--src/nvim/regexp_nfa.c67
-rw-r--r--src/nvim/screen.c13
-rw-r--r--src/nvim/search.c376
-rw-r--r--src/nvim/search.h16
-rw-r--r--src/nvim/sign.c6
-rw-r--r--src/nvim/spell.c1
-rw-r--r--src/nvim/tag.c5
-rw-r--r--src/nvim/testdir/shared.vim6
-rw-r--r--src/nvim/testdir/test_alot.vim1
-rw-r--r--src/nvim/testdir/test_arglist.vim18
-rw-r--r--src/nvim/testdir/test_autocmd.vim51
-rw-r--r--src/nvim/testdir/test_blockedit.vim1
-rw-r--r--src/nvim/testdir/test_cmdline.vim130
-rw-r--r--src/nvim/testdir/test_command_count.vim2
-rw-r--r--src/nvim/testdir/test_cscope.vim130
-rw-r--r--src/nvim/testdir/test_diffmode.vim88
-rw-r--r--src/nvim/testdir/test_ex_mode.vim82
-rw-r--r--src/nvim/testdir/test_excmd.vim126
-rw-r--r--src/nvim/testdir/test_filetype.vim4
-rw-r--r--src/nvim/testdir/test_fnameescape.vim6
-rw-r--r--src/nvim/testdir/test_functions.vim61
-rw-r--r--src/nvim/testdir/test_ga.vim9
-rw-r--r--src/nvim/testdir/test_glob2regpat.vim4
-rw-r--r--src/nvim/testdir/test_global.vim8
-rw-r--r--src/nvim/testdir/test_highlight.vim36
-rw-r--r--src/nvim/testdir/test_listdict.vim8
-rw-r--r--src/nvim/testdir/test_move.vim5
-rw-r--r--src/nvim/testdir/test_normal.vim38
-rw-r--r--src/nvim/testdir/test_options.vim19
-rw-r--r--src/nvim/testdir/test_partial.vim2
-rw-r--r--src/nvim/testdir/test_popup.vim101
-rw-r--r--src/nvim/testdir/test_quickfix.vim317
-rw-r--r--src/nvim/testdir/test_regexp_latin.vim9
-rw-r--r--src/nvim/testdir/test_regexp_utf8.vim46
-rw-r--r--src/nvim/testdir/test_registers.vim3
-rw-r--r--src/nvim/testdir/test_search.vim39
-rw-r--r--src/nvim/testdir/test_search_stat.vim81
-rw-r--r--src/nvim/testdir/test_sort.vim235
-rw-r--r--src/nvim/testdir/test_statusline.vim20
-rw-r--r--src/nvim/testdir/test_substitute.vim2
-rw-r--r--src/nvim/testdir/test_true_false.vim7
-rw-r--r--src/nvim/testdir/test_user_func.vim8
-rw-r--r--src/nvim/testdir/test_vimscript.vim34
-rw-r--r--src/nvim/testdir/test_visual.vim270
-rw-r--r--src/nvim/testdir/test_writefile.vim84
-rw-r--r--src/nvim/tui/tui.c4
-rw-r--r--src/nvim/ui_compositor.c22
-rw-r--r--src/nvim/undo.c4
-rw-r--r--src/nvim/version.c4
-rw-r--r--src/nvim/window.c53
-rw-r--r--test/functional/api/highlight_spec.lua95
-rw-r--r--test/functional/api/vim_spec.lua4
-rw-r--r--test/functional/api/window_spec.lua14
-rw-r--r--test/functional/core/job_spec.lua26
-rw-r--r--test/functional/eval/null_spec.lua5
-rw-r--r--test/functional/fixtures/streams-test.c17
-rw-r--r--test/functional/legacy/011_autocommands_spec.lua2
-rw-r--r--test/functional/legacy/delete_spec.lua78
-rw-r--r--test/functional/legacy/mksession_spec.lua42
-rw-r--r--test/functional/legacy/packadd_spec.lua3
-rw-r--r--test/functional/lua/buffer_updates_spec.lua51
-rw-r--r--test/functional/lua/vim_spec.lua474
-rw-r--r--test/functional/plugin/lsp/diagnostic_spec.lua33
-rw-r--r--test/functional/plugin/lsp_spec.lua151
-rw-r--r--test/functional/treesitter/parser_spec.lua96
-rw-r--r--test/functional/ui/float_spec.lua88
-rw-r--r--test/functional/ui/messages_spec.lua4
-rw-r--r--test/functional/ui/screen.lua29
-rw-r--r--third-party/CMakeLists.txt10
164 files changed, 6558 insertions, 1606 deletions
diff --git a/.github/labeler.yml b/.github/labeler.yml
new file mode 100644
index 0000000000..100f97c5f2
--- /dev/null
+++ b/.github/labeler.yml
@@ -0,0 +1,41 @@
+"lsp":
+ - runtime/lua/vim/lsp.lua
+ - runtime/lua/vim/lsp/*
+
+"lua":
+ - runtime/lua/**/*
+ - src/nvim/lua
+
+"tui":
+ - src/nvim/tui/tui.*
+
+"tree-sitter":
+ - src/nvim/lua/treesitter.*
+ - runtime/lua/vim/treesitter.lua
+ - runtime/lua/vim/treesitter/*
+
+"topic: spell":
+ - src/nvim/spell*
+
+"topic: :terminal":
+ - src/nvim/terminal.*
+
+"topic: column":
+ - src/nvim/mark.h
+ - src/nvim/mark.c
+ - src/nvim/sign*
+
+"topic: folds":
+ - src/nvim/fold*
+
+"topic: mouse":
+ - src/nvim/mouse*
+
+"topic: documentation":
+ - runtime/doc/*
+
+"topic: clipboard":
+ - runtime/autoload/provider/clipboard.vim
+
+"topic: diff":
+ - src/nvim/diff.*
diff --git a/.github/workflows/api-docs.yml b/.github/workflows/api-docs.yml
new file mode 100644
index 0000000000..413c2e90c6
--- /dev/null
+++ b/.github/workflows/api-docs.yml
@@ -0,0 +1,51 @@
+name: Autogenerate API docs
+on:
+ push:
+ paths:
+ - 'src/nvim/api/*.[ch]'
+ - 'src/nvim/**.lua'
+ - 'runtime/lua/**.lua'
+ branches:
+ - 'master'
+ - 'release-[0-9]+.[0-9]+'
+ workflow_dispatch:
+
+jobs:
+ regen-api-docs:
+ runs-on: ubuntu-20.04
+ permissions:
+ contents: write
+ pull-requests: write
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
+
+ - name: Install dependencies
+ run: |
+ sudo apt-get update
+ sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y doxygen python3 python3-msgpack luajit
+
+ - name: Setup git config
+ run: |
+ git config --global user.name 'marvim'
+ git config --global user.email 'marvim@users.noreply.github.com'
+
+ - run: printf 'DOC_BRANCH=marvim/api-doc-update/%s\n' ${GITHUB_REF#refs/heads/} >> $GITHUB_ENV
+
+ - name: Generate docs
+ id: docs
+ run: |
+ git checkout -b ${DOC_BRANCH}
+ python3 scripts/gen_vimdoc.py
+ printf '::set-output name=UPDATED_DOCS::%s\n' $([ -z "$(git diff)" ]; echo $?)
+
+ - name: Automatic PR
+ if: ${{ steps.docs.outputs.UPDATED_DOCS != 0 }}
+ run: |
+ git add -u
+ git commit -m 'docs: regenerate'
+ git push --force https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY} ${DOC_BRANCH}
+ gh pr create --fill --base ${GITHUB_REF#refs/heads/} --head ${DOC_BRANCH} || true
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 86be9adabb..72a6be304c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -59,7 +59,6 @@ jobs:
run: |
# Workaround brew issues
rm -f /usr/local/bin/2to3
- brew unlink gcc@8 gcc@9
brew update >/dev/null
brew upgrade
brew install automake ccache perl cpanminus ninja
diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml
new file mode 100644
index 0000000000..909e197b57
--- /dev/null
+++ b/.github/workflows/labeler.yml
@@ -0,0 +1,13 @@
+name: "Pull Request Labeler"
+on:
+- pull_request_target
+jobs:
+ triage:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: write
+ steps:
+ - uses: actions/labeler@main
+ with:
+ repo-token: "${{ secrets.GITHUB_TOKEN }}"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index a94b3d0a87..8c2333e68d 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -76,7 +76,6 @@ jobs:
- name: Install brew packages
run: |
rm -f /usr/local/bin/2to3
- brew unlink gcc@8 gcc@9
brew update >/dev/null
brew upgrade
brew install automake ninja
diff --git a/README.md b/README.md
index ddd6c6a60b..42c9056892 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
[![Neovim](https://raw.githubusercontent.com/neovim/neovim.github.io/master/logos/neovim-logo-300x87.png)](https://neovim.io)
-[Documentation](https://neovim.io/doc) |
+[Documentation](https://neovim.io/doc/general/) |
[Chat](https://gitter.im/neovim/neovim) |
[Twitter](https://twitter.com/Neovim)
diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt
index d66e04f5f2..4fdcfc0f33 100644
--- a/runtime/CMakeLists.txt
+++ b/runtime/CMakeLists.txt
@@ -111,7 +111,7 @@ if(NOT APPLE)
install_helper(
FILES ${CMAKE_CURRENT_SOURCE_DIR}/nvim.png
- DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pixmaps)
+ DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps)
endif()
file(GLOB_RECURSE RUNTIME_PROGRAMS
diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim
index de540405e6..001379c85d 100644
--- a/runtime/autoload/health/provider.vim
+++ b/runtime/autoload/health/provider.vim
@@ -423,10 +423,6 @@ function! s:check_python(version) abort
\ ' This could lead to confusing error messages.')
endif
- if a:version == 3 && str2float(pyversion) < 3.3
- call health#report_warn('Python 3.3+ is recommended.')
- endif
-
call health#report_info('Python version: ' . pyversion)
if s:is_bad_response(status)
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index 2783640c3c..bdabc6a9ee 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -993,7 +993,7 @@ nvim_get_option_info({name}) *nvim_get_option_info()*
Resulting dictionary has keys:
• name: Name of the option (like 'filetype')
• shortname: Shortened name of the option (like 'ft')
- • type: type of option ("string", "integer" or "boolean")
+ • type: type of option ("string", "number" or "boolean")
• default: The default value for the option
• was_set: Whether the option was set.
• last_set_sid: Last set script id (if any)
@@ -1299,6 +1299,17 @@ nvim_open_win({buffer}, {enter}, {config}) *nvim_open_win()*
an external top-level window. Currently
accepts no other positioning configuration
together with this.
+ • `zindex`: Stacking order. floats with higher`zindex`go on top on floats with lower indices. Must
+ be larger than zero. The following screen
+ elements have hard-coded z-indices:
+ • 100: insert completion popupmenu
+ • 200: message scrollback
+ • 250: cmdline completion popupmenu (when
+ wildoptions+=pum) The default value for
+ floats are 50. In general, values below 100
+ are recommended, unless there is a good
+ reason to overshadow builtin elements.
+
• `style`: Configure the appearance of the window.
Currently only takes one non-empty value:
• "minimal" Nvim will display the window with
@@ -1342,6 +1353,11 @@ nvim_open_win({buffer}, {enter}, {config}) *nvim_open_win()*
by character: [ {"+", "MyCorner"}, {"x",
"MyBorder"} ]
+ • `noautocmd` : If true then no buffer-related
+ autocommand events such as |BufEnter|,
+ |BufLeave| or |BufWinEnter| may fire from
+ calling this function.
+
Return: ~
Window handle, or 0 on error
@@ -1705,6 +1721,12 @@ nvim_set_hl({ns_id}, {name}, {val}) *nvim_set_hl()*
|nvim_get_hl_by_name|. in addition the following
keys are also recognized: `default` : don't
override existing definition, like `hi default`
+ `ctermfg` : sets foreground of cterm color
+ `ctermbg` : sets background of cterm color
+ `cterm` : cterm attribute map. sets attributed
+ for cterm colors. similer to `hi cterm` Note: by
+ default cterm attributes are same as attributes
+ of gui color
nvim_set_keymap({mode}, {lhs}, {rhs}, {opts}) *nvim_set_keymap()*
Sets a global |mapping| for the given mode.
@@ -2269,7 +2291,12 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {opts})
• "overlay": display over the specified
column, without shifting the underlying
text.
+ • "right_align": display right aligned in the
+ window.
+ • virt_text_win_col : position the virtual text
+ at a fixed window column (starting from the
+ first text column)
• virt_text_hide : hide the virtual text when
the background text is selected or hidden due
to horizontal scroll 'nowrap'
diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt
index 19a8be1102..924401be74 100644
--- a/runtime/doc/change.txt
+++ b/runtime/doc/change.txt
@@ -1091,6 +1091,11 @@ inside of strings can change! Also see 'softtabstop' option. >
Using the mouse only works when 'mouse' contains 'n'
or 'a'.
+["x]zp or *zp* *zP*
+["x]zP Like "p" and "P", except without adding trailing spaces
+ when pasting a block. Thus the inserted text will not
+ always be a rectangle.
+
You can use these commands to copy text from one place to another. Do this
by first getting the text into a register with a yank, delete or change
command, then inserting the register contents with a put command. You can
@@ -1130,6 +1135,9 @@ a register, a paste on a visual selected area will paste that single line on
each of the selected lines (thus replacing the blockwise selected region by a
block of the pasted line).
+Use |zP|/|zp| to paste a blockwise yanked register without appending trailing
+spaces.
+
*blockwise-register*
If you use a blockwise Visual mode command to get the text into the register,
the block of text will be inserted before ("P") or after ("p") the cursor
@@ -1771,7 +1779,7 @@ Vim has a sorting function and a sorting command. The sorting function can be
found here: |sort()|, |uniq()|.
*:sor* *:sort*
-:[range]sor[t][!] [b][f][i][n][o][r][u][x] [/{pattern}/]
+:[range]sor[t][!] [b][f][i][l][n][o][r][u][x] [/{pattern}/]
Sort lines in [range]. When no range is given all
lines are sorted.
@@ -1779,6 +1787,16 @@ found here: |sort()|, |uniq()|.
With [i] case is ignored.
+ With [l] sort uses the current collation locale.
+ Implementation details: strcoll() is used to compare
+ strings. See |:language| to check or set the collation
+ locale. Example: >
+ :language collate en_US.UTF-8
+ :%sort l
+< |v:collate| can also used to check the current locale.
+ Sorting using the locale typically ignores case.
+ This does not work properly on Mac.
+
Options [n][f][x][o][b] are mutually exclusive.
With [n] sorting is done on the first decimal number
@@ -1847,8 +1865,7 @@ found here: |sort()|, |uniq()|.
Note that using `:sort` with `:global` doesn't sort the matching lines, it's
quite useless.
-The details about sorting depend on the library function used. There is no
-guarantee that sorting obeys the current locale. You will have to try it out.
+`:sort` does not use the current locale unless the l flag is used.
Vim does do a "stable" sort.
The sorting can be interrupted, but if you interrupt it too late in the
diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt
index ae43aeeb25..dcdc2384dc 100644
--- a/runtime/doc/cmdline.txt
+++ b/runtime/doc/cmdline.txt
@@ -759,13 +759,15 @@ three lines: >
3:d<CR> is translated into: .,.+2d<CR>
<
-Visual Mode and Range *v_:*
- *:star-visual-range*
+Visual Mode and Range
+ *v_:*
{Visual}: Starts a command-line with the Visual selected lines as a
range. The code `:'<,'>` is used for this range, which makes
it possible to select a similar line from the command-line
history for repeating a command on different Visually selected
lines.
+
+:* *:star* *:star-visual-range*
When Visual mode was already ended, a short way to use the
Visual area for a range is `:*`.
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 885fc2d790..b7214d1390 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -1508,6 +1508,15 @@ v:cmdarg This variable is used for two purposes:
the argument for the ":hardcopy" command. This can be used
in 'printexpr'.
+ *v:collate* *collate-variable*
+v:collate The current locale setting for collation order of the runtime
+ environment. This allows Vim scripts to be aware of the
+ current locale encoding. Technical: it's the value of
+ LC_COLLATE. When not using a locale the value is "C".
+ This variable can not be set directly, use the |:language|
+ command.
+ See |multi-lang|.
+
*v:cmdbang* *cmdbang-variable*
v:cmdbang Set like v:cmdarg for a file read/write command. When a "!"
was used the value is 1, otherwise it is 0. Note that this
@@ -2385,6 +2394,7 @@ screenpos({winid}, {lnum}, {col}) Dict screen row and col of a text character
screenrow() Number current cursor row
search({pattern} [, {flags} [, {stopline} [, {timeout}]]])
Number search for {pattern}
+searchcount([{options}]) Dict Get or update the last search count
searchdecl({name} [, {global} [, {thisblock}]])
Number search for variable declaration
searchpair({start}, {middle}, {end} [, {flags} [, {skip} [...]]])
@@ -4763,8 +4773,9 @@ getqflist([{what}]) *getqflist()*
id get information for the quickfix list with
|quickfix-ID|; zero means the id for the
current list or the list specified by "nr"
- idx index of the current entry in the quickfix
- list specified by 'id' or 'nr'.
+ idx get information for the quickfix entry at this
+ index in the list specified by 'id' or 'nr'.
+ If set to zero, then uses the current entry.
See |quickfix-index|
items quickfix list entries
lines parse a list of lines using 'efm' and return
@@ -4797,7 +4808,7 @@ getqflist([{what}]) *getqflist()*
If not present, set to "".
id quickfix list ID |quickfix-ID|. If not
present, set to 0.
- idx index of the current entry in the list. If not
+ idx index of the quickfix entry in the list. If not
present, set to 0.
items quickfix list entries. If not present, set to
an empty list.
@@ -7284,6 +7295,126 @@ search({pattern} [, {flags} [, {stopline} [, {timeout}]]]) *search()*
The 'n' flag tells the function not to move the cursor.
+searchcount([{options}]) *searchcount()*
+ Get or update the last search count, like what is displayed
+ without the "S" flag in 'shortmess'. This works even if
+ 'shortmess' does contain the "S" flag.
+
+ This returns a Dictionary. The dictionary is empty if the
+ previous pattern was not set and "pattern" was not specified.
+
+ key type meaning ~
+ current |Number| current position of match;
+ 0 if the cursor position is
+ before the first match
+ exact_match |Boolean| 1 if "current" is matched on
+ "pos", otherwise 0
+ total |Number| total count of matches found
+ incomplete |Number| 0: search was fully completed
+ 1: recomputing was timed out
+ 2: max count exceeded
+
+ For {options} see further down.
+
+ To get the last search count when |n| or |N| was pressed, call
+ this function with `recompute: 0` . This sometimes returns
+ wrong information because |n| and |N|'s maximum count is 99.
+ If it exceeded 99 the result must be max count + 1 (100). If
+ you want to get correct information, specify `recompute: 1`: >
+
+ " result == maxcount + 1 (100) when many matches
+ let result = searchcount(#{recompute: 0})
+
+ " Below returns correct result (recompute defaults
+ " to 1)
+ let result = searchcount()
+<
+ The function is useful to add the count to |statusline|: >
+ function! LastSearchCount() abort
+ let result = searchcount(#{recompute: 0})
+ if empty(result)
+ return ''
+ endif
+ if result.incomplete ==# 1 " timed out
+ return printf(' /%s [?/??]', @/)
+ elseif result.incomplete ==# 2 " max count exceeded
+ if result.total > result.maxcount &&
+ \ result.current > result.maxcount
+ return printf(' /%s [>%d/>%d]', @/,
+ \ result.current, result.total)
+ elseif result.total > result.maxcount
+ return printf(' /%s [%d/>%d]', @/,
+ \ result.current, result.total)
+ endif
+ endif
+ return printf(' /%s [%d/%d]', @/,
+ \ result.current, result.total)
+ endfunction
+ let &statusline .= '%{LastSearchCount()}'
+
+ " Or if you want to show the count only when
+ " 'hlsearch' was on
+ " let &statusline .=
+ " \ '%{v:hlsearch ? LastSearchCount() : ""}'
+<
+ You can also update the search count, which can be useful in a
+ |CursorMoved| or |CursorMovedI| autocommand: >
+
+ autocmd CursorMoved,CursorMovedI *
+ \ let s:searchcount_timer = timer_start(
+ \ 200, function('s:update_searchcount'))
+ function! s:update_searchcount(timer) abort
+ if a:timer ==# s:searchcount_timer
+ call searchcount(#{
+ \ recompute: 1, maxcount: 0, timeout: 100})
+ redrawstatus
+ endif
+ endfunction
+<
+ This can also be used to count matched texts with specified
+ pattern in the current buffer using "pattern": >
+
+ " Count '\<foo\>' in this buffer
+ " (Note that it also updates search count)
+ let result = searchcount(#{pattern: '\<foo\>'})
+
+ " To restore old search count by old pattern,
+ " search again
+ call searchcount()
+<
+ {options} must be a Dictionary. It can contain:
+ key type meaning ~
+ recompute |Boolean| if |TRUE|, recompute the count
+ like |n| or |N| was executed.
+ otherwise returns the last
+ result by |n|, |N|, or this
+ function is returned.
+ (default: |TRUE|)
+ pattern |String| recompute if this was given
+ and different with |@/|.
+ this works as same as the
+ below command is executed
+ before calling this function >
+ let @/ = pattern
+< (default: |@/|)
+ timeout |Number| 0 or negative number is no
+ timeout. timeout milliseconds
+ for recomputing the result
+ (default: 0)
+ maxcount |Number| 0 or negative number is no
+ limit. max count of matched
+ text while recomputing the
+ result. if search exceeded
+ total count, "total" value
+ becomes `maxcount + 1`
+ (default: 0)
+ pos |List| `[lnum, col, off]` value
+ when recomputing the result.
+ this changes "current" result
+ value. see |cursor()|, |getpos()
+ (default: cursor's position)
+
+
searchdecl({name} [, {global} [, {thisblock}]]) *searchdecl()*
Search for the declaration of {name}.
@@ -7744,6 +7875,11 @@ setqflist({list} [, {action}[, {what}]]) *setqflist()*
nr list number in the quickfix stack; zero
means the current quickfix list and "$" means
the last quickfix list.
+ quickfixtextfunc
+ function to get the text to display in the
+ quickfix window. Refer to
+ |quickfix-window-function| for an explanation
+ of how to write the function and an example.
title quickfix list title text. See |quickfix-title|
Unsupported keys in {what} are ignored.
If the "nr" item is not present, then the current quickfix list
@@ -8007,6 +8143,23 @@ sort({list} [, {func} [, {dict}]]) *sort()* *E702*
When {func} is given and it is '1' or 'i' then case is
ignored.
+ When {func} is given and it is 'l' then the current collation
+ locale is used for ordering. Implementation details: strcoll()
+ is used to compare strings. See |:language| check or set the
+ collation locale. |v:collate| can also be used to check the
+ current locale. Sorting using the locale typically ignores
+ case. Example: >
+ " ö is sorted similarly to o with English locale.
+ :language collate en_US.UTF8
+ :echo sort(['n', 'o', 'O', 'ö', 'p', 'z'], 'l')
+< ['n', 'o', 'O', 'ö', 'p', 'z'] ~
+>
+ " ö is sorted after z with Swedish locale.
+ :language collate sv_SE.UTF8
+ :echo sort(['n', 'o', 'O', 'ö', 'p', 'z'], 'l')
+< ['n', 'o', 'O', 'p', 'z', 'ö'] ~
+ This does not work properly on Mac.
+
When {func} is given and it is 'n' then all items will be
sorted numerical (Implementation detail: this uses the
strtod() function to parse numbers, Strings, Lists, Dicts and
diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt
index e7d891bc33..17258f896d 100644
--- a/runtime/doc/index.txt
+++ b/runtime/doc/index.txt
@@ -851,6 +851,8 @@ tag char note action in Normal mode ~
|zm| zm subtract one from 'foldlevel'
|zn| zn reset 'foldenable'
|zo| zo open fold
+|zp| zp paste in block-mode without trailing spaces
+|zP| zP paste in block-mode without trailing spaces
|zr| zr add one to 'foldlevel'
|zs| zs when 'wrap' off scroll horizontally to
position the cursor at the start (left
diff --git a/runtime/doc/intro.txt b/runtime/doc/intro.txt
index 925b3e5dbb..f739e2e88b 100644
--- a/runtime/doc/intro.txt
+++ b/runtime/doc/intro.txt
@@ -169,6 +169,7 @@ Vim would never have become what it is now, without the help of these people!
Ken Takata fixes and features
Kazunobu Kuriyama GTK 3
Christian Brabandt many fixes, features, user support, etc.
+ Yegappan Lakshmanan many quickfix features
I wish to thank all the people that sent me bug reports and suggestions. The
list is too long to mention them all here. Vim would not be the same without
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index 5c2ee568c5..531374620a 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -619,15 +619,15 @@ buf_request_sync({bufnr}, {method}, {params}, {timeout_ms})
Calls |vim.lsp.buf_request_all()| but blocks Nvim while
awaiting the result. Parameters are the same as
|vim.lsp.buf_request()| but the return result is different.
- Wait maximum of {timeout_ms} (default 100) ms.
+ Wait maximum of {timeout_ms} (default 1000) ms.
Parameters: ~
{bufnr} (number) Buffer handle, or 0 for current.
{method} (string) LSP method name
{params} (optional, table) Parameters to send to the
server
- {timeout_ms} (optional, number, default=100) Maximum time
- in milliseconds to wait for a result.
+ {timeout_ms} (optional, number, default=1000) Maximum
+ time in milliseconds to wait for a result.
Return: ~
Map of client_id:request_result. On timeout, cancel or
@@ -651,6 +651,14 @@ client() *vim.lsp.client*
{status} is `true` , the function returns {request_id} as
the second result. You can use this with
`client.cancel_request(request_id)` to cancel the request.
+ • request_sync(method, params, timeout_ms, bufnr) Sends a
+ request to the server and synchronously waits for the
+ response. This is a wrapper around {client.request}
+ Returns: { err=err, result=result }, a dictionary, where
+ `err` and `result` come from the |lsp-handler|. On
+ timeout, cancel or error, returns `(nil, err)` where `err`
+ is a string describing the failure reason. If the request
+ was unsuccessful returns `nil` .
• notify(method, params) Sends a notification to an LSP
server. Returns: a boolean to indicate if the notification
was successful. If it is false, then it will always be
@@ -939,6 +947,9 @@ add_workspace_folder({workspace_folder})
not provided, the user will be prompted for a path using
|input()|.
+call_hierarchy({method}) *vim.lsp.buf.call_hierarchy()*
+ TODO: Documentation
+
clear_references() *vim.lsp.buf.clear_references()*
Removes document highlights from current buffer.
@@ -1017,6 +1028,32 @@ formatting({options}) *vim.lsp.buf.formatting()*
See also: ~
https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting
+ *vim.lsp.buf.formatting_seq_sync()*
+formatting_seq_sync({options}, {timeout_ms}, {order})
+ Formats the current buffer by sequentially requesting
+ formatting from attached clients.
+
+ Useful when multiple clients with formatting capability are
+ attached.
+
+ Since it's synchronous, can be used for running on save, to
+ make sure buffer is formatted prior to being saved.
+ {timeout_ms} is passed on to the |vim.lsp.client| `request_sync` method. Example: >
+
+ vim.api.nvim_command[[autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting_seq_sync()]]
+<
+
+ Parameters: ~
+ {options} (optional, table) `FormattingOptions`
+ entries
+ {timeout_ms} (optional, number) Request timeout
+ {order} (optional, table) List of client names.
+ Formatting is requested from clients in the
+ following order: first all clients that are
+ not in the `order` list, then the remaining
+ clients in the order as they occur in the
+ `order` list.
+
*vim.lsp.buf.formatting_sync()*
formatting_sync({options}, {timeout_ms})
Performs |vim.lsp.buf.formatting()| synchronously.
@@ -1033,6 +1070,9 @@ formatting_sync({options}, {timeout_ms})
{options} Table with valid `FormattingOptions` entries
{timeout_ms} (number) Request timeout
+ See also: ~
+ |vim.lsp.buf.formatting_seq_sync|
+
hover() *vim.lsp.buf.hover()*
Displays hover information about the symbol under the cursor
in a floating window. Calling the function twice will jump
@@ -1156,11 +1196,16 @@ get({bufnr}, {client_id}) *vim.lsp.diagnostic.get()*
diagnostics. Else, return just the
diagnostics associated with the client_id.
-get_all() *vim.lsp.diagnostic.get_all()*
- Get all diagnostics for all clients
+get_all({client_id}) *vim.lsp.diagnostic.get_all()*
+ Get all diagnostics for clients
+
+ Parameters: ~
+ {client_id} number Restrict included diagnostics to the
+ client If nil, diagnostics of all clients are
+ included.
Return: ~
- {bufnr:Diagnostic[]}
+ table with diagnostics grouped by bufnr (bufnr:Diagnostic[])
*vim.lsp.diagnostic.get_count()*
get_count({bufnr}, {severity}, {client_id})
@@ -1405,6 +1450,9 @@ set_loclist({opts}) *vim.lsp.diagnostic.set_loclist()*
"Warning" means { "Error", "Warning" } will be
valid.
+ • {workspace}: (boolean, default false)
+ • Set the list with workspace diagnostics
+
*vim.lsp.diagnostic.set_signs()*
set_signs({diagnostics}, {bufnr}, {client_id}, {sign_ns}, {opts})
Set signs for given diagnostics
@@ -1570,6 +1618,12 @@ apply_workspace_edit({workspace_edit})
Parameters: ~
{workspace_edit} (table) `WorkspaceEdit`
+border_height({id}) *vim.lsp.util.border_height()*
+ TODO: Documentation
+
+border_width({id}) *vim.lsp.util.border_width()*
+ TODO: Documentation
+
buf_clear_references({bufnr}) *vim.lsp.util.buf_clear_references()*
Removes document highlights from a buffer.
@@ -1585,7 +1639,11 @@ buf_highlight_references({bufnr}, {references})
{references} List of `DocumentHighlight` objects to
highlight
-character_offset({buf}, {row}, {col}) *vim.lsp.util.character_offset()*
+buf_lines({bufnr}) *vim.lsp.util.buf_lines()*
+ TODO: Documentation
+
+ *vim.lsp.util.character_offset()*
+character_offset({bufnr}, {row}, {col})
Returns the UTF-32 and UTF-16 offsets for a position in a
certain buffer.
@@ -1650,12 +1708,15 @@ convert_input_to_markdown_lines({input}, {contents})
https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover
*vim.lsp.util.convert_signature_help_to_markdown_lines()*
-convert_signature_help_to_markdown_lines({signature_help})
+convert_signature_help_to_markdown_lines({signature_help}, {ft})
Converts `textDocument/SignatureHelp` response to markdown
lines.
Parameters: ~
{signature_help} Response of `textDocument/SignatureHelp`
+ {ft} optional filetype that will be use as
+ the `lang` for the label markdown code
+ block
Return: ~
list of lines of converted markdown.
@@ -1669,6 +1730,19 @@ create_file({change}) *vim.lsp.util.create_file()*
delete_file({change}) *vim.lsp.util.delete_file()*
TODO: Documentation
+ *vim.lsp.util.diagnostics_to_items()*
+diagnostics_to_items({diagnostics_by_bufnr}, {predicate})
+ Convert diagnostics grouped by bufnr to a list of items for
+ use in the quickfix or location list.
+
+ Parameters: ~
+ {diagnostics_by_bufnr} table bufnr -> Diagnostic []
+ {predicate} an optional function to filter the
+ diagnostics.
+
+ Return: ~
+ table (A list of items)
+
*vim.lsp.util.extract_completion_items()*
extract_completion_items({result})
Can be used to extract the completion items from a `textDocument/completion` request, which may return one of `CompletionItem[]` , `CompletionList` or null.
@@ -1683,65 +1757,6 @@ extract_completion_items({result})
See also: ~
https://microsoft.github.io/language-server-protocol/specification#textDocument_completion
- *vim.lsp.util.fancy_floating_markdown()*
-fancy_floating_markdown({contents}, {opts})
- Converts markdown into syntax highlighted regions by stripping
- the code blocks and converting them into highlighted code.
- This will by default insert a blank line separator after those
- code block regions to improve readability. The result is shown
- in a floating preview.
-
- Parameters: ~
- {contents} table of lines to show in window
- {opts} dictionary with optional fields
- • height of floating window
- • width of floating window
- • wrap_at character to wrap at for computing
- height
- • max_width maximal width of floating window
- • max_height maximal height of floating window
- • pad_left number of columns to pad contents
- at left
- • pad_right number of columns to pad contents
- at right
- • pad_top number of lines to pad contents at
- top
- • pad_bottom number of lines to pad contents
- at bottom
- • separator insert separator after code block
-
- Return: ~
- width,height size of float
-
-focusable_float({unique_name}, {fn}) *vim.lsp.util.focusable_float()*
- Parameters: ~
- {unique_name} (string) Window variable
- {fn} (function) should return create a new
- window and return a tuple of
- ({focusable_buffer_id}, {window_id}). if
- {focusable_buffer_id} is a valid buffer id,
- the newly created window will be the new
- focus associated with the current buffer
- via the tag `unique_name` .
-
- Return: ~
- (pbufnr, pwinnr) if `fn()` has created a new window; nil
- otherwise
-
- *vim.lsp.util.focusable_preview()*
-focusable_preview({unique_name}, {fn})
- Focuses/unfocuses the floating preview window associated with
- the current buffer via the window variable `unique_name` . If
- no such preview window exists, makes a new one.
-
- Parameters: ~
- {unique_name} (string) Window variable
- {fn} (function) The return values of this
- function will be passed directly to
- |vim.lsp.util.open_floating_preview()|, in
- the case that a new floating window should
- be created
-
get_effective_tabstop({bufnr}) *vim.lsp.util.get_effective_tabstop()*
Returns visual width of tabstop.
@@ -1755,6 +1770,22 @@ get_effective_tabstop({bufnr}) *vim.lsp.util.get_effective_tabstop()*
See also: ~
|softtabstop|
+get_line({uri}, {row}) *vim.lsp.util.get_line()*
+ Parameters: ~
+ {uri} string uri of the resource to get the line from
+ {row} number zero-indexed line number
+
+ Return: ~
+ string the line at row in filename
+
+get_lines({uri}, {rows}) *vim.lsp.util.get_lines()*
+ Parameters: ~
+ {uri} string uri of the resource to get the lines from
+ {rows} number[] zero-indexed line numbers
+
+ Return: ~
+ table<number string> a table mapping rows to lines
+
get_progress_messages() *vim.lsp.util.get_progress_messages()*
TODO: Documentation
@@ -1883,6 +1914,28 @@ open_floating_preview({contents}, {syntax}, {opts})
{contents} table of lines to show in window
{syntax} string of syntax to set for opened buffer
{opts} dictionary with optional fields
+ • height of floating window
+ • width of floating window
+ • wrap boolean enable wrapping of long lines
+ (defaults to true)
+ • wrap_at character to wrap at for computing
+ height when wrap is enabled
+ • max_width maximal width of floating window
+ • max_height maximal height of floating window
+ • pad_left number of columns to pad contents
+ at left
+ • pad_right number of columns to pad contents
+ at right
+ • pad_top number of lines to pad contents at
+ top
+ • pad_bottom number of lines to pad contents
+ at bottom
+ • focus_id if a popup with this id is opened,
+ then focus it
+ • close_events list of events that closes the
+ floating window
+ • focusable (boolean, default true): Make
+ float focusable
Return: ~
bufnr,winnr buffer and window number of the newly created
@@ -1897,7 +1950,7 @@ parse_snippet({input}) *vim.lsp.util.parse_snippet()*
Return: ~
(string) parsed snippet
-preview_location({location}) *vim.lsp.util.preview_location()*
+preview_location({location}, {opts}) *vim.lsp.util.preview_location()*
Previews a location in a floating window
behavior depends on type of location:
@@ -1932,9 +1985,10 @@ set_lines({lines}, {A}, {B}, {new_lines}) *vim.lsp.util.set_lines()*
Return: ~
(table) The modified {lines} object
-set_loclist({items}) *vim.lsp.util.set_loclist()*
- Fills current window's location list with given list of items.
+set_loclist({items}, {win_id}) *vim.lsp.util.set_loclist()*
+ Fills target window's location list with given list of items.
Can be obtained with e.g. |vim.lsp.util.locations_to_items()|.
+ Defaults to current window.
Parameters: ~
{items} (table) list of items
@@ -1946,6 +2000,41 @@ set_qflist({items}) *vim.lsp.util.set_qflist()*
Parameters: ~
{items} (table) list of items
+ *vim.lsp.util.stylize_markdown()*
+stylize_markdown({bufnr}, {contents}, {opts})
+ Converts markdown into syntax highlighted regions by stripping
+ the code blocks and converting them into highlighted code.
+ This will by default insert a blank line separator after those
+ code block regions to improve readability.
+
+ This method configures the given buffer and returns the lines
+ to set.
+
+ If you want to open a popup with fancy markdown, use
+ `open_floating_preview` instead
+
+ Parameters: ~
+ {contents} table of lines to show in window
+ {opts} dictionary with optional fields
+ • height of floating window
+ • width of floating window
+ • wrap_at character to wrap at for computing
+ height
+ • max_width maximal width of floating window
+ • max_height maximal height of floating window
+ • pad_left number of columns to pad contents
+ at left
+ • pad_right number of columns to pad contents
+ at right
+ • pad_top number of lines to pad contents at
+ top
+ • pad_bottom number of lines to pad contents
+ at bottom
+ • separator insert separator after code block
+
+ Return: ~
+ width,height size of float
+
symbols_to_items({symbols}, {bufnr}) *vim.lsp.util.symbols_to_items()*
Converts symbols to quickfix list items.
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index be01966d42..fd1bedd8ef 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -650,42 +650,6 @@ vim.empty_dict() *vim.empty_dict()*
Note: if numeric keys are added to the table, the metatable will be
ignored and the dict converted to a list/array anyway.
-vim.region({bufnr}, {pos1}, {pos2}, {type}, {inclusive}) *vim.region()*
- Converts a selection specified by the buffer ({bufnr}), starting
- position ({pos1}, a zero-indexed pair `{line1,column1}`), ending
- position ({pos2}, same format as {pos1}), the type of the register
- for the selection ({type}, see |regtype|), and a boolean indicating
- whether the selection is inclusive or not, into a zero-indexed table
- of linewise selections of the form `{linenr = {startcol, endcol}}` .
-
- *vim.register_keystroke_callback()*
-vim.register_keystroke_callback({fn}, {ns_id})
- Register a lua {fn} with an {ns_id} to be run after every keystroke.
-
- Parameters: ~
- {fn}: (function): Function to call on keystroke.
- It should take one argument, which is a string.
- The string will contain the literal keys typed.
- See |i_CTRL-V|
-
- If {fn} is `nil`, it removes the callback for the
- associated {ns_id}.
-
- {ns_id}: (number) Namespace ID. If not passed or 0, will generate
- and return a new namespace ID from |nvim_create_namespace()|
-
- Return: ~
- (number) Namespace ID associated with {fn}
-
- NOTE: {fn} will be automatically removed if an error occurs while
- calling. This is to prevent the annoying situation of every keystroke
- erroring while trying to remove a broken callback.
-
- NOTE: {fn} will receive the keystrokes after mappings have been
- evaluated
-
- NOTE: {fn} will *NOT* be cleared from |nvim_buf_clear_namespace()|
-
vim.rpcnotify({channel}, {method}[, {args}...]) *vim.rpcnotify()*
Sends {event} to {channel} via |RPC| and returns immediately.
If {channel} is 0, the event is broadcast to all channels.
@@ -912,9 +876,167 @@ vim.env *vim.env*
print(vim.env.TERM)
<
- *lua-vim-options*
-From Lua you can work with editor |options| by reading and setting items in
-these Lua tables:
+ *lua-vim-options*
+ *lua-vim-opt*
+ *lua-vim-set*
+ *lua-vim-optlocal*
+ *lua-vim-setlocal*
+
+In vimL, there is a succint and simple way to set options. For more
+information, see |set-option|. In Lua, the corresponding method is `vim.opt`.
+
+`vim.opt` provides several conveniences for setting and controlling options
+from within Lua.
+
+ Examples: ~
+
+ To set a boolean toggle:
+ In vimL:
+ `set number`
+
+ In Lua:
+ `vim.opt.number = true`
+
+ To set an array of values:
+ In vimL:
+ `set wildignore=*.o,*.a,__pycache__`
+
+ In Lua, there are two ways you can do this now. One is very similar to
+ the vimL way:
+ `vim.opt.wildignore = '*.o,*.a,__pycache__'`
+
+ However, vim.opt also supports a more elegent way of setting
+ list-style options, but using lua tables:
+ `vim.opt.wildignore = { '*.o', '*.a', '__pycache__' }`
+
+ To replicate the behavior of |:set+=|, use: >
+
+ -- vim.opt supports appending options via the "+" operator
+ vim.opt.wildignore = vim.opt.wildignore + { "*.pyc", "node_modules" }
+
+ -- or using the `:append(...)` method
+ vim.opt.wildignore:append { "*.pyc", "node_modules" }
+<
+
+ To replicate the behavior of |:set^=|, use: >
+
+ -- vim.opt supports prepending options via the "^" operator
+ vim.opt.wildignore = vim.opt.wildignore ^ { "new_first_value" }
+
+ -- or using the `:prepend(...)` method
+ vim.opt.wildignore:prepend { "new_first_value" }
+<
+ To replicate the behavior of |:set-=|, use: >
+
+ -- vim.opt supports removing options via the "-" operator
+ vim.opt.wildignore = vim.opt.wildignore - { "node_modules" }
+
+ -- or using the `:remove(...)` method
+ vim.opt.wildignore:remove { "node_modules" }
+<
+ To set a map of values:
+ In vimL:
+ `set listchars=space:_,tab:>~`
+
+ In Lua:
+ `vim.opt.listchars = { space = '_', tab = '>~' }`
+
+
+In any of the above examples, to replicate the behavior |setlocal|, use
+`vim.opt_local`. Additionally, to replicate the behavior of |setglobal|, use
+`vim.opt_global`.
+ *vim.opt*
+
+|vim.opt| returns an Option object.
+
+For example: `local listchar_object = vim.opt.listchar`
+
+An `Option` has the following methods:
+
+
+ *vim.opt:get()*
+Option:get()
+
+ Returns a lua-representation of the option. Boolean, number and string
+ values will be returned in exactly the same fashion.
+
+ For values that are comma-separated lists, an array will be returned with
+ the values as entries in the array: >
+ vim.cmd [[set wildignore=*.pyc,*.o]]
+
+ print(vim.inspect(vim.opt.wildignore:get()))
+ -- { "*.pyc", "*.o", }
+
+ for _, ignore_pattern in ipairs(vim.opt.wildignore:get()) do
+ print("Will ignore:", ignore_pattern)
+ end
+ -- Will ignore: *.pyc
+ -- Will ignore: *.o
+<
+ For values that are comma-separated maps, a table will be returned with
+ the names as keys and the values as entries: >
+ vim.cmd [[set listchars=space:_,tab:>~]]
+
+ print(vim.inspect(vim.opt.listchars:get()))
+ -- { space = "_", tab = ">~", }
+
+ for char, representation in pairs(vim.opt.listchars:get()) do
+ print(char, "->", representation)
+ end
+<
+ For values that are lists of flags, a set will be returned with the flags
+ as keys and `true` as entries. >
+ vim.cmd [[set formatoptions=njtcroql]]
+
+ print(vim.inspect(vim.opt.formatoptions:get()))
+ -- { n = true, j = true, c = true, ... }
+
+ local format_opts = vim.opt.formatoptions:get()
+ if format_opts.j then
+ print("J is enabled!")
+ end
+<
+ *vim.opt:append()*
+Option:append(value)
+
+ Append a value to string-style options. See |:set+=|
+
+ These are equivalent:
+ `vim.opt.formatoptions:append('j')`
+ `vim.opt.formatoptions = vim.opt.formatoptions + 'j'`
+
+ *vim.opt:prepend()*
+Option:prepend(value)
+
+ Prepend a value to string-style options. See |:set^=|
+
+ These are equivalent:
+ `vim.opt.wildignore:prepend('*.o')`
+ `vim.opt.wildignore = vim.opt.wildignore ^ '*.o'`
+
+ *vim.opt:remove()*
+Option:remove(value)
+
+ Remove a value from string-style options. See |:set-=|
+
+ These are equivalent:
+ `vim.opt.wildignore:remove('*.pyc')`
+ `vim.opt.wildignore = vim.opt.wildignore - '*.pyc'`
+
+
+In general, using `vim.opt` will provide the expected result when the user is
+used to interacting with editor |options| via `set`. There are still times
+where the user may want to set particular options via a shorthand in Lua,
+which is where |vim.o|, |vim.bo|, |vim.wo|, and |vim.go| come into play.
+
+The behavior of |vim.o|, |vim.bo|, |vim.wo|, and |vim.go| is designed to
+follow that of |:set|, |:setlocal|, and |:setglobal| which can be seen in the
+table below:
+
+ lua command global_value local_value ~
+vim.o :set set set
+vim.bo/vim.wo :setlocal - set
+vim.go :setglobal set -
vim.o *vim.o*
Get or set editor options, like |:set|. Invalid key is an error.
@@ -922,14 +1044,36 @@ vim.o *vim.o*
vim.o.cmdheight = 4
print(vim.o.columns)
+
+vim.go *vim.go*
+ Get or set an |option|. Invalid key is an error.
+
+ This is a wrapper around |nvim_set_option()| and |nvim_get_option()|.
+
+ NOTE: This is different than |vim.o| because this ONLY sets the global
+ option, which generally produces confusing behavior for options with
+ |global-local| values.
+
+ Example: >
+ vim.go.cmdheight = 4
+<
+
vim.bo *vim.bo*
Get or set buffer-scoped |local-options|. Invalid key is an error.
+
+ This is a wrapper around |nvim_buf_set_option()| and
+ |nvim_buf_get_option()|.
+
Example: >
vim.bo.buflisted = true
print(vim.bo.comments)
vim.wo *vim.wo*
Get or set window-scoped |local-options|. Invalid key is an error.
+
+ This is a wrapper around |nvim_win_set_option()| and
+ |nvim_win_get_option()|.
+
Example: >
vim.wo.cursorcolumn = true
print(vim.wo.foldmarker)
@@ -938,6 +1082,23 @@ vim.wo *vim.wo*
==============================================================================
Lua module: vim *lua-vim*
+defer_fn({fn}, {timeout}) *vim.defer_fn()*
+ Defers calling `fn` until `timeout` ms passes.
+
+ Use to do a one-shot timer that calls `fn` Note: The {fn} is |schedule_wrap|ped automatically, so API
+ functions are safe to call.
+
+ Parameters: ~
+ {fn} Callback to call once `timeout` expires
+ {timeout} Number of milliseconds to wait before calling
+ `fn`
+
+ Return: ~
+ timer luv timer object
+
+insert_keys({obj}) *vim.insert_keys()*
+ TODO: Documentation
+
inspect({object}, {options}) *vim.inspect()*
Return a human-readable representation of the given object.
@@ -945,9 +1106,19 @@ inspect({object}, {options}) *vim.inspect()*
https://github.com/kikito/inspect.lua
https://github.com/mpeterv/vinspect
-make_meta_accessor({get}, {set}, {del}) *vim.make_meta_accessor()*
+make_dict_accessor({scope}) *vim.make_dict_accessor()*
TODO: Documentation
+notify({msg}, {log_level}, {_opts}) *vim.notify()*
+ Notification provider without a runtime, writes to :Messages
+
+ Parameters: ~
+ {msg} Content of the notification to show to the
+ user
+ {log_level} Optional log level
+ {opts} Dictionary with optional options (timeout,
+ etc)
+
paste({lines}, {phase}) *vim.paste()*
Paste handler, invoked by |nvim_paste()| when a conforming UI
(such as the |TUI|) pastes text into the editor.
@@ -980,6 +1151,53 @@ paste({lines}, {phase}) *vim.paste()*
See also: ~
|paste|
+region({bufnr}, {pos1}, {pos2}, {regtype}, {inclusive}) *vim.region()*
+ Get a table of lines with start, end columns for a region
+ marked by two points
+
+ Parameters: ~
+ {bufnr} number of buffer
+ {pos1} (line, column) tuple marking beginning of
+ region
+ {pos2} (line, column) tuple marking end of region
+ {regtype} type of selection (:help setreg)
+ {inclusive} boolean indicating whether the selection is
+ end-inclusive
+
+ Return: ~
+ region lua table of the form {linenr = {startcol,endcol}}
+
+ *vim.register_keystroke_callback()*
+register_keystroke_callback({fn}, {ns_id})
+ Register a lua {fn} with an {id} to be run after every
+ keystroke.
+
+ If {fn} is nil, it removes the callback for the associated
+ {ns_id}
+ Note:
+ {fn} will not be cleared from |nvim_buf_clear_namespace()|
+
+ Note:
+ {fn} will receive the keystrokes after mappings have been
+ evaluated
+
+ Parameters: ~
+ {fn} function: Function to call. It should take one
+ argument, which is a string. The string will contain
+ the literal keys typed. See |i_CTRL-V|
+ {ns_id} number? Namespace ID. If not passed or 0, will
+ generate and return a new namespace ID from
+ |nvim_create_namesapce()|
+
+ Return: ~
+ number Namespace ID associated with {fn}
+
+ Note:
+ {fn} will be automatically removed if an error occurs
+ while calling. This is to prevent the annoying situation
+ of every keystroke erroring while trying to remove a
+ broken callback.
+
schedule_wrap({cb}) *vim.schedule_wrap()*
Defers callback `cb` until the Nvim API is safe to call.
@@ -1287,12 +1505,14 @@ validate({opt}) *vim.validate()*
vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}}
=> NOP (success)
-
- vim.validate{arg1={1, 'table'}}
- => error('arg1: expected table, got number')
-
- vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}}
- => error('arg1: expected even number, got 3')
+<
+>
+ vim.validate{arg1={1, 'table'}}
+ => error('arg1: expected table, got number')
+<
+>
+ vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}}
+ => error('arg1: expected even number, got 3')
<
Parameters: ~
diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt
index ee42edf154..77cbf7d9b7 100644
--- a/runtime/doc/map.txt
+++ b/runtime/doc/map.txt
@@ -360,6 +360,22 @@ Overview of which map command works in which mode. More details below.
:cmap :cnoremap :cunmap Command-line
:tmap :tnoremap :tunmap Terminal
+Same information in a table:
+ *map-table*
+ Mode | Norm | Ins | Cmd | Vis | Sel | Opr | Term | Lang | ~
+Command +------+-----+-----+-----+-----+-----+------+------+ ~
+[nore]map | yes | - | - | yes | yes | yes | - | - |
+n[nore]map | yes | - | - | - | - | - | - | - |
+[nore]map! | - | yes | yes | - | - | - | - | - |
+i[nore]map | - | yes | - | - | - | - | - | - |
+c[nore]map | - | - | yes | - | - | - | - | - |
+v[nore]map | - | - | - | yes | yes | - | - | - |
+x[nore]map | - | - | - | yes | - | - | - | - |
+s[nore]map | - | - | - | - | yes | - | - | - |
+o[nore]map | - | - | - | - | - | yes | - | - |
+t[nore]map | - | - | - | - | - | - | yes | - |
+l[nore]map | - | yes | yes | - | - | - | - | yes |
+
COMMANDS MODES ~
Normal Visual+Select Operator-pending ~
diff --git a/runtime/doc/mlang.txt b/runtime/doc/mlang.txt
index b57d2b592a..9d3a51302d 100644
--- a/runtime/doc/mlang.txt
+++ b/runtime/doc/mlang.txt
@@ -31,6 +31,7 @@ use of "-" and "_".
:lan[guage] mes[sages]
:lan[guage] cty[pe]
:lan[guage] tim[e]
+:lan[guage] col[late]
Print the current language (aka locale).
With the "messages" argument the language used for
messages is printed. Technical: LC_MESSAGES.
@@ -38,15 +39,19 @@ use of "-" and "_".
character encoding is printed. Technical: LC_CTYPE.
With the "time" argument the language used for
strftime() is printed. Technical: LC_TIME.
+ With the "collate" argument the language used for
+ collation order is printed. Technical: LC_COLLATE.
Without argument all parts of the locale are printed
(this is system dependent).
The current language can also be obtained with the
- |v:lang|, |v:ctype| and |v:lc_time| variables.
+ |v:lang|, |v:ctype|, |v:collate| and |v:lc_time|
+ variables.
:lan[guage] {name}
:lan[guage] mes[sages] {name}
:lan[guage] cty[pe] {name}
:lan[guage] tim[e] {name}
+:lan[guage] col[late] {name}
Set the current language (aka locale) to {name}.
The locale {name} must be a valid locale on your
system. Some systems accept aliases like "en" or
@@ -66,7 +71,10 @@ use of "-" and "_".
With the "time" argument the language used for time
and date messages is set. This affects strftime().
This sets $LC_TIME.
- Without an argument both are set, and additionally
+ With the "collate" argument the language used for the
+ collation order is set. This affects sorting of
+ characters. This sets $LC_COLLATE.
+ Without an argument all are set, and additionally
$LANG is set.
The LC_NUMERIC value will always be set to "C" so
that floating point numbers use '.' as the decimal
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 269080e750..cc9696e536 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -595,10 +595,6 @@ A jump table for the options with a short description can be found at |Q_op|.
set to one of CJK locales. See Unicode Standard Annex #11
(http://www.unicode.org/reports/tr11).
- Vim may set this option automatically at startup time when Vim is
- compiled with the |+termresponse| feature and if t_u7 is set to the
- escape sequence to request cursor position report.
-
*'autochdir'* *'acd'* *'noautochdir'* *'noacd'*
'autochdir' 'acd' boolean (default off)
global
@@ -4603,6 +4599,19 @@ A jump table for the options with a short description can be found at |Q_op|.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
+ *'quickfixtextfunc'* *'qftf'*
+'quickfixtextfunc' 'qftf' string (default "")
+ global
+ This option specifies a function to be used to get the text to display
+ in the quickfix and location list windows. This can be used to
+ customize the information displayed in the quickfix or location window
+ for each entry in the corresponding quickfix or location list. See
+ |quickfix-window-function| for an explanation of how to write the
+ function and an example.
+
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+
*'quoteescape'* *'qe'*
'quoteescape' 'qe' string (default "\")
local to buffer
@@ -5043,6 +5052,7 @@ A jump table for the options with a short description can be found at |Q_op|.
global values for local options)
options all options and mappings (also global values for local
options)
+ skiprtp exclude 'runtimepath' and 'packpath' from the options
resize size of the Vim window: 'lines' and 'columns'
sesdir the directory in which the session file is located
will become the current directory (useful with
@@ -5267,9 +5277,9 @@ A jump table for the options with a short description can be found at |Q_op|.
in a file and echoed to the screen. If the 'shell' option is "csh" or
"tcsh" after initializations, the default becomes "|& tee". If the
'shell' option is "sh", "ksh", "mksh", "pdksh", "zsh", "zsh-beta",
- "bash" or "fish" the default becomes "2>&1| tee". This means that
- stderr is also included. Before using the 'shell' option a path is
- removed, thus "/bin/sh" uses "sh".
+ "bash", "fish", "ash" or "dash" the default becomes "2>&1| tee". This
+ means that stderr is also included. Before using the 'shell' option a
+ path is removed, thus "/bin/sh" uses "sh".
The initialization of this option is done after reading the vimrc
and the other initializations, so that when the 'shell' option is set
there, the 'shellpipe' option changes automatically, unless it was
@@ -5923,6 +5933,18 @@ A jump table for the options with a short description can be found at |Q_op|.
Note that there is no '%' before the closing '}'. The
expression cannot contain a '}' character, call a function to
work around that. See |stl-%{| below.
+ {% - This is almost same as { except the result of the expression is
+ re-evaluated as a statusline format string. Thus if the
+ return value of expr contains % items they will get expanded.
+ The expression can contain the } character, the end of
+ expression is denoted by %}.
+ The For example: >
+ func! Stl_filename() abort
+ return "%t"
+ endfunc
+< `stl=%{Stl_filename()}` results in `"%t"`
+ `stl=%{%Stl_filename()%}` results in `"Name of current file"`
+ } - End of `{%` expression
( - Start of item group. Can be used for setting the width and
alignment of a section. Must be followed by %) somewhere.
) - End of item group. No width fields allowed.
@@ -6417,7 +6439,7 @@ A jump table for the options with a short description can be found at |Q_op|.
a single <Esc> is assumed. Many TUI cursor key codes start with <Esc>.
On very slow systems this may fail, causing cursor keys not to work
- sometimes. If you discover this problem you can ":set ttimeout=9999".
+ sometimes. If you discover this problem you can ":set ttimeoutlen=9999".
Nvim will wait for the next character to arrive after an <Esc>.
*'timeoutlen'* *'tm'*
diff --git a/runtime/doc/pi_netrw.txt b/runtime/doc/pi_netrw.txt
index 5adafd7877..4b61cd4c25 100644
--- a/runtime/doc/pi_netrw.txt
+++ b/runtime/doc/pi_netrw.txt
@@ -3181,8 +3181,8 @@ window, then the one window will be horizontally split (by default).
If there's more than one window, the previous window will be re-used on
the selected file/directory. If the previous window's associated buffer
has been modified, and there's only one window with that buffer, then
-the user will be asked if s/he wishes to save the buffer first (yes,
-no, or cancel).
+the user will be asked if they wish to save the buffer first (yes, no, or
+cancel).
Related Actions |netrw-cr| |netrw-o| |netrw-t| |netrw-v|
Associated setting variables:
diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt
index db6b759af6..a937cfee98 100644
--- a/runtime/doc/quickfix.txt
+++ b/runtime/doc/quickfix.txt
@@ -1014,13 +1014,6 @@ commands can be combined to create a NewGrep command: >
updated. With the [!] any changes in the current
buffer are abandoned.
- 'f' When the 'f' flag is specified, fuzzy string
- matching is used to find matching lines. In this
- case, {pattern} is treated as a literal string
- instead of a regular expression. See
- |matchfuzzy()| for more info about fuzzy
- matching.
-
|QuickFixCmdPre| and |QuickFixCmdPost| are triggered.
A file that is opened for matching may use a buffer
number, but it is reused if possible to avoid
@@ -1889,5 +1882,67 @@ be used. See the description further above how to make such a filter known
by Vim.
+=============================================================================
+10. Customizing the quickfix window *quickfix-window-function*
+
+The default format for the lines displayed in the quickfix window and location
+list window is:
+
+ <filename>|<lnum> col <col>|<text>
+
+The values displayed in each line correspond to the "bufnr", "lnum", "col" and
+"text" fields returned by the |getqflist()| function.
+
+For some quickfix/location lists, the displayed text need to be customized.
+For example, if only the filename is present for a quickfix entry, then the
+two "|" field separator characters after the filename are not needed. Another
+use case is to customize the path displayed for a filename. By default, the
+complete path (which may be too long) is displayed for files which are not
+under the current directory tree. The file path may need to be simplified to a
+common parent directory.
+
+The displayed text can be customized by setting the 'quickfixtextfunc' option
+to a Vim function. This function will be called with a dict argument and
+should return a List of strings to be displayed in the quickfix or location
+list window. The dict argument will have the following fields:
+
+ quickfix set to 1 when called for a quickfix list and 0 when called for
+ a location list.
+ winid for a location list, set to the id of the window with the
+ location list. For a quickfix list, set to 0. Can be used in
+ getloclist() to get the location list entry.
+ id quickfix or location list identifier
+ start_idx index of the first entry for which text should be returned
+ end_idx index of the last entry for which text should be returned
+
+The function should return a single line of text to display in the quickfix
+window for each entry from start_idx to end_idx. The function can obtain
+information about the entries using the |getqflist()| function and specifying
+the quickfix list identifier "id". For a location list, getloclist() function
+can be used with the 'winid' argument.
+
+If a quickfix or location list specific customization is needed, then the
+'quickfixtextfunc' attribute of the list can be set using the |setqflist()| or
+|setloclist()| function. This overrides the global 'quickfixtextfunc' option.
+
+The example below displays the list of old files (|v:oldfiles|) in a quickfix
+window. As there is no line, column number and error text information
+associated with each entry, the 'quickfixtextfunc' function returns only the
+filename.
+Example: >
+ " create a quickfix list from v:oldfiles
+ call setqflist([], ' ', {'lines' : v:oldfiles, 'efm' : '%f',
+ \ 'quickfixtextfunc' : 'QfOldFiles'})
+ func QfOldFiles(info)
+ " get information about a range of quickfix entries
+ let items = getqflist({'id' : a:info.id, 'items' : 1}).items
+ let l = []
+ for idx in range(a:info.start_idx - 1, a:info.end_idx - 1)
+ " use the simplified file name
+ call add(l, fnamemodify(bufname(items[idx].bufnr), ':p:.'))
+ endfor
+ return l
+ endfunc
+<
vim:tw=78:ts=8:noet:ft=help:norl:
diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt
index b237d70760..dd05084652 100644
--- a/runtime/doc/repeat.txt
+++ b/runtime/doc/repeat.txt
@@ -50,6 +50,11 @@ Multiple repeats *multi-repeat*
:[range]v[global]/{pattern}/[cmd]
Same as :g!.
+Example: >
+ :g/^Obsolete/d _
+Using the underscore after `:d` avoids clobbering registers or the clipboard.
+This also makes it faster.
+
Instead of the '/' which surrounds the {pattern}, you can use any other
single byte character, but not an alphabetic character, '\', '"' or '|'.
This is useful if you want to include a '/' in the search pattern or
diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt
index 95e00720b1..b159f655fa 100644
--- a/runtime/doc/syntax.txt
+++ b/runtime/doc/syntax.txt
@@ -1969,7 +1969,7 @@ LEX *lex.vim* *ft-lex-syntax*
Lex uses brute-force synchronizing as the "^%%$" section delimiter
gives no clue as to what section follows. Consequently, the value for >
:syn sync minlines=300
-may be changed by the user if s/he is experiencing synchronization
+may be changed by the user if they are experiencing synchronization
difficulties (such as may happen with large lex files).
@@ -3009,11 +3009,11 @@ variables in your vimrc:
< (dash users should use posix)
-If there's no "#! ..." line, and the user hasn't availed himself/herself of a
-default sh.vim syntax setting as just shown, then syntax/sh.vim will assume
-the Bourne shell syntax. No need to quote RFCs or market penetration
-statistics in error reports, please -- just select the default version of the
-sh your system uses and install the associated "let..." in your <.vimrc>.
+If there's no "#! ..." line, and the user hasn't availed themself of a default
+sh.vim syntax setting as just shown, then syntax/sh.vim will assume the Bourne
+shell syntax. No need to quote RFCs or market penetration statistics in error
+reports, please -- just select the default version of the sh your system uses
+and install the associated "let..." in your <.vimrc>.
The syntax/sh.vim file provides several levels of syntax-based folding: >
diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt
index 1f4b5d3097..d6bca55bd6 100644
--- a/runtime/doc/treesitter.txt
+++ b/runtime/doc/treesitter.txt
@@ -212,6 +212,11 @@ Here is a list of built-in predicates :
((identifier) @foo (#contains? @foo "foo"))
((identifier) @foo-bar (#contains @foo-bar "foo" "bar"))
<
+ `any-of?` *ts-predicate-any-of?*
+ Will check if the text is the same as any of the following
+ arguments : >
+ ((identifier) @foo (#any-of? @foo "foo" "bar"))
+<
*lua-treesitter-not-predicate*
Each predicate has a `not-` prefixed predicate that is just the negation of
the predicate.
@@ -464,9 +469,11 @@ Query:iter_matches({self}, {node}, {source}, {start}, {stop})
for id, node in pairs(match) do
local name = query.captures[id]
-- `node` was captured by the `name` capture in the match
-
- local node_data = metadata[id] -- Node level metadata
-
+<
+>
+ local node_data = metadata[id] -- Node level metadata
+<
+>
... use the info here ...
end
end
diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt
index 4cba5a33d0..f92cb3c509 100644
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -1875,7 +1875,7 @@ NOT LOADING
It's possible that a user doesn't always want to load this plugin. Or the
system administrator has dropped it in the system-wide plugin directory, but a
-user has his own plugin he wants to use. Then the user must have a chance to
+user has their own plugin they want to use. Then the user must have a chance to
disable loading this specific plugin. This will make it possible: >
6 if exists("g:loaded_typecorr")
@@ -1908,7 +1908,7 @@ item can be used: >
The "<Plug>TypecorrAdd;" thing will do the work, more about that further on.
-The user can set the "mapleader" variable to the key sequence that he wants
+The user can set the "mapleader" variable to the key sequence that they want
this mapping to start with. Thus if the user has done: >
let mapleader = "_"
@@ -1919,7 +1919,7 @@ will be used, which is a backslash. Then a map for "\a" will be defined.
Note that <unique> is used, this will cause an error message if the mapping
already happened to exist. |:map-<unique>|
-But what if the user wants to define his own key sequence? We can allow that
+But what if the user wants to define their own key sequence? We can allow that
with this mechanism: >
21 if !hasmapto('<Plug>TypecorrAdd;')
@@ -1928,7 +1928,7 @@ with this mechanism: >
This checks if a mapping to "<Plug>TypecorrAdd;" already exists, and only
defines the mapping from "<Leader>a" if it doesn't. The user then has a
-chance of putting this in his vimrc file: >
+chance of putting this in their vimrc file: >
map ,c <Plug>TypecorrAdd;
@@ -2033,7 +2033,7 @@ Now let's add a user command to add a correction: >
The user command is defined only if no command with the same name already
exists. Otherwise we would get an error here. Overriding the existing user
command with ":command!" is not a good idea, this would probably make the user
-wonder why the command he defined himself doesn't work. |:command|
+wonder why the command they defined themself doesn't work. |:command|
SCRIPT VARIABLES
@@ -2285,7 +2285,7 @@ An example of how to define functionality in a filetype plugin: >
|hasmapto()| is used to check if the user has already defined a map to
<Plug>JavaImport;. If not, then the filetype plugin defines the default
mapping. This starts with |<LocalLeader>|, which allows the user to select
-the key(s) he wants filetype plugin mappings to start with. The default is a
+the key(s) they want filetype plugin mappings to start with. The default is a
backslash.
"<unique>" is used to give an error message if the mapping already exists or
overlaps with an existing mapping.
diff --git a/runtime/doc/usr_42.txt b/runtime/doc/usr_42.txt
index 99da1359c2..ff3ae7057a 100644
--- a/runtime/doc/usr_42.txt
+++ b/runtime/doc/usr_42.txt
@@ -209,8 +209,8 @@ argument: >
:amenu <silent> Mine.Next\ File :call <SID>NextFile()<CR>
Don't use "<silent>" too often. It is not needed for short commands. If you
-make a menu for someone else, being able to see the executed command will give
-him a hint about what he could have typed, instead of using the mouse.
+make a menu for someone else, being able to see the executed command will
+give them a hint about what they could have typed, instead of using the mouse.
LISTING MENUS
diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt
index 2c39fdb53c..bb30495d77 100644
--- a/runtime/doc/vim_diff.txt
+++ b/runtime/doc/vim_diff.txt
@@ -194,8 +194,7 @@ Options:
'cpoptions' flags: |cpo-_|
'display' flags: "msgsep" minimizes scrolling when showing messages
'guicursor' works in the terminal
- 'fillchars' flags: "msgsep" (see 'display'), "foldopen", "foldsep",
- "foldclose"
+ 'fillchars' flags: "msgsep" (see 'display')
'foldcolumn' supports up to 9 dynamic/fixed columns
'inccommand' shows interactive results for |:substitute|-like commands
'pumblend' pseudo-transparent popupmenu
diff --git a/runtime/filetype.vim b/runtime/filetype.vim
index f0d2b36a84..ed70ac5ce8 100644
--- a/runtime/filetype.vim
+++ b/runtime/filetype.vim
@@ -714,7 +714,7 @@ au BufNewFile,BufRead *.haml setf haml
au BufNewFile,BufRead *.hsm setf hamster
" Haskell
-au BufNewFile,BufRead *.hs,*.hsc,*.hs-boot setf haskell
+au BufNewFile,BufRead *.hs,*.hsc,*.hs-boot,*.hsig setf haskell
au BufNewFile,BufRead *.lhs setf lhaskell
au BufNewFile,BufRead *.chs setf chaskell
au BufNewFile,BufRead cabal.project setf cabalproject
@@ -1015,6 +1015,7 @@ au BufNewFile,BufRead *.hgrc,*hgrc setf cfg
" Meson Build system config
au BufNewFile,BufRead meson.build,meson_options.txt setf meson
+au BufNewFile,BufRead *.wrap setf dosini
" Messages (logs mostly)
au BufNewFile,BufRead */log/{auth,cron,daemon,debug,kern,lpr,mail,messages,news/news,syslog,user}{,.log,.err,.info,.warn,.crit,.notice}{,.[0-9]*,-[0-9]*} setf messages
diff --git a/runtime/ftplugin/meson.vim b/runtime/ftplugin/meson.vim
index e432ebf196..d48fa1dfd1 100644
--- a/runtime/ftplugin/meson.vim
+++ b/runtime/ftplugin/meson.vim
@@ -1,6 +1,7 @@
" Vim filetype plugin file
" Language: meson
" License: VIM License
+" Maintainer: Liam Beguin <liambeguin@gmail.com>
" Original Author: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
" Last Change: 2018 Nov 27
diff --git a/runtime/ftplugin/systemverilog.vim b/runtime/ftplugin/systemverilog.vim
index 4d0f565fcc..e350427022 100644
--- a/runtime/ftplugin/systemverilog.vim
+++ b/runtime/ftplugin/systemverilog.vim
@@ -1,7 +1,7 @@
" Vim filetype plugin file
" Language: SystemVerilog
" Maintainer: kocha <kocha.lsifrontend@gmail.com>
-" Last Change: 12-Aug-2013.
+" Last Change: 07-May-2021
if exists("b:did_ftplugin")
finish
@@ -9,3 +9,36 @@ endif
" Behaves just like Verilog
runtime! ftplugin/verilog.vim
+
+let s:cpo_save = &cpo
+set cpo&vim
+
+" Add SystemVerilog keywords for matchit plugin.
+if exists("loaded_matchit")
+ let b:match_words =
+ \ '\<begin\>:\<end\>,' .
+ \ '\<case\>\|\<casex\>\|\<casez\>:\<endcase\>,' .
+ \ '\<module\>:\<endmodule\>,' .
+ \ '\<if\>:`\@<!\<else\>,' .
+ \ '\<function\>:\<endfunction\>,' .
+ \ '`ifn\?def\>:`elsif\>:`else\>:`endif\>,' .
+ \ '\<task\>:\<endtask\>,' .
+ \ '\<specify\>:\<endspecify\>,' .
+ \ '\<config\>:\<endconfig\>,' .
+ \ '\<generate\>:\<endgenerate\>,' .
+ \ '\<fork\>:\<join\>\|\<join_any\>\|\<join_none\>,' .
+ \ '\<primitive\>:\<endprimitive\>,' .
+ \ '\<table\>:\<endtable\>,' .
+ \ '\<checker\>:\<endchecker\>,' .
+ \ '\<class\>:\<endclass\>,' .
+ \ '\<clocking\>:\<endclocking\>,' .
+ \ '\<gruop\>:\<endgruop\>,' .
+ \ '\<interface\>:\<endinterface\>,' .
+ \ '\<package\>:\<endpackage\>,' .
+ \ '\<program\>:\<endprogram\>,' .
+ \ '\<property\>:\<endproperty\>,' .
+ \ '\<sequence\>:\<endsequence\>'
+endif
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
diff --git a/runtime/indent/meson.vim b/runtime/indent/meson.vim
index f116781f74..549209ca33 100644
--- a/runtime/indent/meson.vim
+++ b/runtime/indent/meson.vim
@@ -2,6 +2,7 @@
" Language: Meson
" License: VIM License
" Maintainer: Nirbheek Chauhan <nirbheek.chauhan@gmail.com>
+" Liam Beguin <liambeguin@gmail.com>
" Original Authors: David Bustos <bustos@caltech.edu>
" Bram Moolenaar <Bram@vim.org>
" Last Change: 2019 Oct 18
diff --git a/runtime/lua/vim/F.lua b/runtime/lua/vim/F.lua
index 5887e978b9..7925ff6e44 100644
--- a/runtime/lua/vim/F.lua
+++ b/runtime/lua/vim/F.lua
@@ -20,5 +20,12 @@ function F.npcall(fn, ...)
return F.ok_or_nil(pcall(fn, ...))
end
+--- Wrap a function to return nil if it fails, otherwise the value
+function F.nil_wrap(fn)
+ return function(...)
+ return F.npcall(fn, ...)
+ end
+end
+
return F
diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua
new file mode 100644
index 0000000000..02d1154df4
--- /dev/null
+++ b/runtime/lua/vim/_meta.lua
@@ -0,0 +1,631 @@
+-- prevents luacheck from making lints for setting things on vim
+local vim = assert(vim)
+
+local a = vim.api
+local validate = vim.validate
+
+local SET_TYPES = setmetatable({
+ SET = 0,
+ LOCAL = 1,
+ GLOBAL = 2,
+}, { __index = error })
+
+local options_info = {}
+for _, v in pairs(a.nvim_get_all_options_info()) do
+ options_info[v.name] = v
+ if v.shortname ~= "" then options_info[v.shortname] = v end
+end
+
+local is_global_option = function(info) return info.scope == "global" end
+local is_buffer_option = function(info) return info.scope == "buf" end
+local is_window_option = function(info) return info.scope == "win" end
+
+local get_scoped_options = function(scope)
+ local result = {}
+ for name, option_info in pairs(options_info) do
+ if option_info.scope == scope then
+ result[name] = true
+ end
+ end
+
+ return result
+end
+
+local buf_options = get_scoped_options("buf")
+local glb_options = get_scoped_options("global")
+local win_options = get_scoped_options("win")
+
+local function make_meta_accessor(get, set, del, validator)
+ validator = validator or function() return true end
+
+ validate {
+ get = {get, 'f'};
+ set = {set, 'f'};
+ del = {del, 'f', true};
+ validator = {validator, 'f'};
+ }
+
+ local mt = {}
+ function mt:__newindex(k, v)
+ if not validator(k) then
+ return
+ end
+
+ if del and v == nil then
+ return del(k)
+ end
+ return set(k, v)
+ end
+ function mt:__index(k)
+ if not validator(k) then
+ return
+ end
+
+ return get(k)
+ end
+ return setmetatable({}, mt)
+end
+
+vim.env = make_meta_accessor(function(k)
+ local v = vim.fn.getenv(k)
+ if v == vim.NIL then
+ return nil
+ end
+ return v
+end, vim.fn.setenv)
+
+do -- buffer option accessor
+ local function new_buf_opt_accessor(bufnr)
+ local function get(k)
+ if bufnr == nil and type(k) == "number" then
+ return new_buf_opt_accessor(k)
+ end
+
+ return a.nvim_buf_get_option(bufnr or 0, k)
+ end
+
+ local function set(k, v)
+ return a.nvim_buf_set_option(bufnr or 0, k, v)
+ end
+
+ return make_meta_accessor(get, set, nil, function(k)
+ if type(k) == 'string' then
+ if win_options[k] then
+ error(string.format([['%s' is a window option, not a buffer option. See ":help %s"]], k, k))
+ elseif glb_options[k] then
+ error(string.format([['%s' is a global option, not a buffer option. See ":help %s"]], k, k))
+ end
+ end
+
+ return true
+ end)
+ end
+
+ vim.bo = new_buf_opt_accessor(nil)
+end
+
+do -- window option accessor
+ local function new_win_opt_accessor(winnr)
+ local function get(k)
+ if winnr == nil and type(k) == "number" then
+ return new_win_opt_accessor(k)
+ end
+ return a.nvim_win_get_option(winnr or 0, k)
+ end
+
+ local function set(k, v)
+ return a.nvim_win_set_option(winnr or 0, k, v)
+ end
+
+ return make_meta_accessor(get, set, nil, function(k)
+ if type(k) == 'string' then
+ if buf_options[k] then
+ error(string.format([['%s' is a buffer option, not a window option. See ":help %s"]], k, k))
+ elseif glb_options[k] then
+ error(string.format([['%s' is a global option, not a window option. See ":help %s"]], k, k))
+ end
+ end
+
+ return true
+ end)
+ end
+
+ vim.wo = new_win_opt_accessor(nil)
+end
+
+--[[
+Local window setter
+
+buffer options: does not get copied when split
+ nvim_set_option(buf_opt, value) -> sets the default for NEW buffers
+ this sets the hidden global default for buffer options
+
+ nvim_buf_set_option(...) -> sets the local value for the buffer
+
+ set opt=value, does BOTH global default AND buffer local value
+ setlocal opt=value, does ONLY buffer local value
+
+window options: gets copied
+ does not need to call nvim_set_option because nobody knows what the heck this does⸮
+ We call it anyway for more readable code.
+
+
+ Command global value local value
+ :set option=value set set
+ :setlocal option=value - set
+:setglobal option=value set -
+--]]
+local function set_scoped_option(k, v, set_type)
+ local info = options_info[k]
+
+ -- Don't let people do setlocal with global options.
+ -- That is a feature that doesn't make sense.
+ if set_type == SET_TYPES.LOCAL and is_global_option(info) then
+ error(string.format("Unable to setlocal option: '%s', which is a global option.", k))
+ end
+
+ -- Only `setlocal` skips setting the default/global value
+ -- This will more-or-less noop for window options, but that's OK
+ if set_type ~= SET_TYPES.LOCAL then
+ a.nvim_set_option(k, v)
+ end
+
+ if is_window_option(info) then
+ if set_type ~= SET_TYPES.GLOBAL then
+ a.nvim_win_set_option(0, k, v)
+ end
+ elseif is_buffer_option(info) then
+ if set_type == SET_TYPES.LOCAL
+ or (set_type == SET_TYPES.SET and not info.global_local) then
+ a.nvim_buf_set_option(0, k, v)
+ end
+ end
+end
+
+--[[
+Local window getter
+
+ Command global value local value
+ :set option? - display
+ :setlocal option? - display
+:setglobal option? display -
+--]]
+local function get_scoped_option(k, set_type)
+ local info = assert(options_info[k], "Must be a valid option: " .. tostring(k))
+
+ if set_type == SET_TYPES.GLOBAL or is_global_option(info) then
+ return a.nvim_get_option(k)
+ end
+
+ if is_buffer_option(info) then
+ local was_set, value = pcall(a.nvim_buf_get_option, 0, k)
+ if was_set then return value end
+
+ if info.global_local then
+ return a.nvim_get_option(k)
+ end
+
+ error("buf_get: This should not be able to happen, given my understanding of options // " .. k)
+ end
+
+ if is_window_option(info) then
+ if vim.api.nvim_get_option_info(k).was_set then
+ local was_set, value = pcall(a.nvim_win_get_option, 0, k)
+ if was_set then return value end
+ end
+
+ return a.nvim_get_option(k)
+ end
+
+ error("This fallback case should not be possible. " .. k)
+end
+
+-- vim global option
+-- this ONLY sets the global option. like `setglobal`
+vim.go = make_meta_accessor(a.nvim_get_option, a.nvim_set_option)
+
+-- vim `set` style options.
+-- it has no additional metamethod magic.
+vim.o = make_meta_accessor(
+ function(k) return get_scoped_option(k, SET_TYPES.SET) end,
+ function(k, v) return set_scoped_option(k, v, SET_TYPES.SET) end
+)
+
+---@brief [[
+--- vim.opt, vim.opt_local and vim.opt_global implementation
+---
+--- To be used as helpers for working with options within neovim.
+--- For information on how to use, see :help vim.opt
+---
+---@brief ]]
+
+--- Preserves the order and does not mutate the original list
+local remove_duplicate_values = function(t)
+ local result, seen = {}, {}
+ if type(t) == "function" then error(debug.traceback("asdf")) end
+ for _, v in ipairs(t) do
+ if not seen[v] then
+ table.insert(result, v)
+ end
+
+ seen[v] = true
+ end
+
+ return result
+end
+
+-- TODO(tjdevries): Improve option metadata so that this doesn't have to be hardcoded.
+-- Can be done in a separate PR.
+local key_value_options = {
+ fillchars = true,
+ listchars = true,
+ winhl = true,
+}
+
+---@class OptionType
+--- Option Type Enum
+local OptionTypes = setmetatable({
+ BOOLEAN = 0,
+ NUMBER = 1,
+ STRING = 2,
+ ARRAY = 3,
+ MAP = 4,
+ SET = 5,
+}, {
+ __index = function(_, k) error("Not a valid OptionType: " .. k) end,
+ __newindex = function(_, k) error("Cannot set a new OptionType: " .. k) end,
+})
+
+--- Convert a vimoption_T style dictionary to the correct OptionType associated with it.
+---@return OptionType
+local get_option_type = function(name, info)
+ if info.type == "boolean" then
+ return OptionTypes.BOOLEAN
+ elseif info.type == "number" then
+ return OptionTypes.NUMBER
+ elseif info.type == "string" then
+ if not info.commalist and not info.flaglist then
+ return OptionTypes.STRING
+ end
+
+ if key_value_options[name] then
+ assert(info.commalist, "Must be a comma list to use key:value style")
+ return OptionTypes.MAP
+ end
+
+ if info.flaglist then
+ return OptionTypes.SET
+ elseif info.commalist then
+ return OptionTypes.ARRAY
+ end
+
+ error("Fallthrough in OptionTypes")
+ else
+ error("Not a known info.type:" .. info.type)
+ end
+end
+
+
+--- Convert a lua value to a vimoption_T value
+local convert_value_to_vim = (function()
+ -- Map of functions to take a Lua style value and convert to vimoption_T style value.
+ -- Each function takes (info, lua_value) -> vim_value
+ local to_vim_value = {
+ [OptionTypes.BOOLEAN] = function(_, value) return value end,
+ [OptionTypes.NUMBER] = function(_, value) return value end,
+ [OptionTypes.STRING] = function(_, value) return value end,
+
+ [OptionTypes.SET] = function(_, value)
+ if type(value) == "string" then return value end
+ local result = ''
+ for k in pairs(value) do
+ result = result .. k
+ end
+
+ return result
+ end,
+
+ [OptionTypes.ARRAY] = function(_, value)
+ if type(value) == "string" then return value end
+ return table.concat(remove_duplicate_values(value), ",")
+ end,
+
+ [OptionTypes.MAP] = function(_, value)
+ if type(value) == "string" then return value end
+ if type(value) == "function" then error(debug.traceback("asdf")) end
+
+ local result = {}
+ for opt_key, opt_value in pairs(value) do
+ table.insert(result, string.format("%s:%s", opt_key, opt_value))
+ end
+
+ table.sort(result)
+ return table.concat(result, ",")
+ end,
+ }
+
+ return function(name, info, value)
+ return to_vim_value[get_option_type(name, info)](info, value)
+ end
+end)()
+
+--- Converts a vimoption_T style value to a Lua value
+local convert_value_to_lua = (function()
+ -- Map of OptionType to functions that take vimoption_T values and conver to lua values.
+ -- Each function takes (info, vim_value) -> lua_value
+ local to_lua_value = {
+ [OptionTypes.BOOLEAN] = function(_, value) return value end,
+ [OptionTypes.NUMBER] = function(_, value) return value end,
+ [OptionTypes.STRING] = function(_, value) return value end,
+
+ [OptionTypes.ARRAY] = function(_, value)
+ if type(value) == "table" then
+ value = remove_duplicate_values(value)
+ return value
+ end
+
+ return vim.split(value, ",")
+ end,
+
+ [OptionTypes.SET] = function(info, value)
+ if type(value) == "table" then return value end
+
+ assert(info.flaglist, "That is the only one I know how to handle")
+
+ local result = {}
+ for i = 1, #value do
+ result[value:sub(i, i)] = true
+ end
+
+ return result
+ end,
+
+ [OptionTypes.MAP] = function(info, raw_value)
+ if type(raw_value) == "table" then return raw_value end
+
+ assert(info.commalist, "Only commas are supported currently")
+
+ local result = {}
+
+ local comma_split = vim.split(raw_value, ",")
+ for _, key_value_str in ipairs(comma_split) do
+ local key, value = unpack(vim.split(key_value_str, ":"))
+ key = vim.trim(key)
+ value = vim.trim(value)
+
+ result[key] = value
+ end
+
+ return result
+ end,
+ }
+
+ return function(name, info, option_value)
+ return to_lua_value[get_option_type(name, info)](info, option_value)
+ end
+end)()
+
+--- Handles the mutation of various different values.
+local value_mutator = function(name, info, current, new, mutator)
+ return mutator[get_option_type(name, info)](current, new)
+end
+
+--- Handles the '^' operator
+local prepend_value = (function()
+ local methods = {
+ [OptionTypes.NUMBER] = function()
+ error("The '^' operator is not currently supported for")
+ end,
+
+ [OptionTypes.STRING] = function(left, right)
+ return right .. left
+ end,
+
+ [OptionTypes.ARRAY] = function(left, right)
+ for i = #right, 1, -1 do
+ table.insert(left, 1, right[i])
+ end
+
+ return left
+ end,
+
+ [OptionTypes.MAP] = function(left, right)
+ return vim.tbl_extend("force", left, right)
+ end,
+
+ [OptionTypes.SET] = function(left, right)
+ return vim.tbl_extend("force", left, right)
+ end,
+ }
+
+ return function(name, info, current, new)
+ return value_mutator(
+ name, info, convert_value_to_lua(name, info, current), convert_value_to_lua(name, info, new), methods
+ )
+ end
+end)()
+
+--- Handles the '+' operator
+local add_value = (function()
+ local methods = {
+ [OptionTypes.NUMBER] = function(left, right)
+ return left + right
+ end,
+
+ [OptionTypes.STRING] = function(left, right)
+ return left .. right
+ end,
+
+ [OptionTypes.ARRAY] = function(left, right)
+ for _, v in ipairs(right) do
+ table.insert(left, v)
+ end
+
+ return left
+ end,
+
+ [OptionTypes.MAP] = function(left, right)
+ return vim.tbl_extend("force", left, right)
+ end,
+
+ [OptionTypes.SET] = function(left, right)
+ return vim.tbl_extend("force", left, right)
+ end,
+ }
+
+ return function(name, info, current, new)
+ return value_mutator(
+ name, info, convert_value_to_lua(name, info, current), convert_value_to_lua(name, info, new), methods
+ )
+ end
+end)()
+
+--- Handles the '-' operator
+local remove_value = (function()
+ local remove_one_item = function(t, val)
+ if vim.tbl_islist(t) then
+ local remove_index = nil
+ for i, v in ipairs(t) do
+ if v == val then
+ remove_index = i
+ end
+ end
+
+ if remove_index then
+ table.remove(t, remove_index)
+ end
+ else
+ t[val] = nil
+ end
+ end
+
+ local methods = {
+ [OptionTypes.NUMBER] = function(left, right)
+ return left - right
+ end,
+
+ [OptionTypes.STRING] = function()
+ error("Subtraction not supported for strings.")
+ end,
+
+ [OptionTypes.ARRAY] = function(left, right)
+ if type(right) == "string" then
+ remove_one_item(left, right)
+ else
+ for _, v in ipairs(right) do
+ remove_one_item(left, v)
+ end
+ end
+
+ return left
+ end,
+
+ [OptionTypes.MAP] = function(left, right)
+ if type(right) == "string" then
+ left[right] = nil
+ else
+ for _, v in ipairs(right) do
+ left[v] = nil
+ end
+ end
+
+ return left
+ end,
+
+ [OptionTypes.SET] = function(left, right)
+ if type(right) == "string" then
+ left[right] = nil
+ else
+ for _, v in ipairs(right) do
+ left[v] = nil
+ end
+ end
+
+ return left
+ end,
+ }
+
+ return function(name, info, current, new)
+ return value_mutator(name, info, convert_value_to_lua(name, info, current), new, methods)
+ end
+end)()
+
+local create_option_metatable = function(set_type)
+ local set_mt, option_mt
+
+ local make_option = function(name, value)
+ local info = assert(options_info[name], "Not a valid option name: " .. name)
+
+ if type(value) == "table" and getmetatable(value) == option_mt then
+ assert(name == value._name, "must be the same value, otherwise that's weird.")
+
+ value = value._value
+ end
+
+ return setmetatable({
+ _name = name,
+ _value = value,
+ _info = info,
+ }, option_mt)
+ end
+
+ -- TODO(tjdevries): consider supporting `nil` for set to remove the local option.
+ -- vim.cmd [[set option<]]
+
+ option_mt = {
+ -- To set a value, instead use:
+ -- opt[my_option] = value
+ _set = function(self)
+ local value = convert_value_to_vim(self._name, self._info, self._value)
+ set_scoped_option(self._name, value, set_type)
+
+ return self
+ end,
+
+ get = function(self)
+ return convert_value_to_lua(self._name, self._info, self._value)
+ end,
+
+ append = function(self, right)
+ return self:__add(right):_set()
+ end,
+
+ __add = function(self, right)
+ return make_option(self._name, add_value(self._name, self._info, self._value, right))
+ end,
+
+ prepend = function(self, right)
+ return self:__pow(right):_set()
+ end,
+
+ __pow = function(self, right)
+ return make_option(self._name, prepend_value(self._name, self._info, self._value, right))
+ end,
+
+ remove = function(self, right)
+ return self:__sub(right):_set()
+ end,
+
+ __sub = function(self, right)
+ return make_option(self._name, remove_value(self._name, self._info, self._value, right))
+ end
+ }
+ option_mt.__index = option_mt
+
+ set_mt = {
+ __index = function(_, k)
+ return make_option(k, get_scoped_option(k, set_type))
+ end,
+
+ __newindex = function(_, k, v)
+ local opt = make_option(k, v)
+ opt:_set()
+ end,
+ }
+
+ return set_mt
+end
+
+vim.opt = setmetatable({}, create_option_metatable(SET_TYPES.SET))
+vim.opt_local = setmetatable({}, create_option_metatable(SET_TYPES.LOCAL))
+vim.opt_global = setmetatable({}, create_option_metatable(SET_TYPES.GLOBAL))
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 93ec9ed624..5a606188dd 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -1421,7 +1421,7 @@ function lsp.omnifunc(findstart, base)
local items = {}
lsp.buf_request(bufnr, 'textDocument/completion', params, function(err, _, result)
- if err or not result then return end
+ if err or not result or vim.fn.mode() ~= "i" then return end
local matches = util.text_document_completion_list_to_complete_items(result, prefix)
-- TODO(ashkan): is this the best way to do this?
vim.list_extend(items, matches)
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index 341a3e82fc..5dd7109bb0 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -297,26 +297,30 @@ local function pick_call_hierarchy_item(call_hierarchy_items)
return choice
end
+local function call_hierarchy(method)
+ local params = util.make_position_params()
+ request('textDocument/prepareCallHierarchy', params, function(err, _, result)
+ if err then
+ vim.notify(err.message, vim.log.levels.WARN)
+ return
+ end
+ local call_hierarchy_item = pick_call_hierarchy_item(result)
+ vim.lsp.buf_request(0, method, { item = call_hierarchy_item })
+ end)
+end
+
--- Lists all the call sites of the symbol under the cursor in the
--- |quickfix| window. If the symbol can resolve to multiple
--- items, the user can pick one in the |inputlist|.
function M.incoming_calls()
- local params = util.make_position_params()
- request('textDocument/prepareCallHierarchy', params, function(_, _, result)
- local call_hierarchy_item = pick_call_hierarchy_item(result)
- vim.lsp.buf_request(0, 'callHierarchy/incomingCalls', { item = call_hierarchy_item })
- end)
+ call_hierarchy('callHierarchy/incomingCalls')
end
--- Lists all the items that are called by the symbol under the
--- cursor in the |quickfix| window. If the symbol can resolve to
--- multiple items, the user can pick one in the |inputlist|.
function M.outgoing_calls()
- local params = util.make_position_params()
- request('textDocument/prepareCallHierarchy', params, function(_, _, result)
- local call_hierarchy_item = pick_call_hierarchy_item(result)
- vim.lsp.buf_request(0, 'callHierarchy/outgoingCalls', { item = call_hierarchy_item })
- end)
+ call_hierarchy('callHierarchy/outgoingCalls')
end
--- List workspace folders.
diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua
index 6f2f846a3b..dabe400e0d 100644
--- a/runtime/lua/vim/lsp/diagnostic.lua
+++ b/runtime/lua/vim/lsp/diagnostic.lua
@@ -330,15 +330,19 @@ end
-- Diagnostic Retrieval {{{
---- Get all diagnostics for all clients
+--- Get all diagnostics for clients
---
----@return {bufnr: Diagnostic[]}
-function M.get_all()
+---@param client_id number Restrict included diagnostics to the client
+--- If nil, diagnostics of all clients are included.
+---@return table with diagnostics grouped by bufnr (bufnr: Diagnostic[])
+function M.get_all(client_id)
local diagnostics_by_bufnr = {}
for bufnr, buf_diagnostics in pairs(diagnostic_cache) do
diagnostics_by_bufnr[bufnr] = {}
- for _, client_diagnostics in pairs(buf_diagnostics) do
- vim.list_extend(diagnostics_by_bufnr[bufnr], client_diagnostics)
+ for cid, client_diagnostics in pairs(buf_diagnostics) do
+ if client_id == nil or cid == client_id then
+ vim.list_extend(diagnostics_by_bufnr[bufnr], client_diagnostics)
+ end
end
end
return diagnostics_by_bufnr
@@ -1151,6 +1155,7 @@ function M.show_line_diagnostics(opts, bufnr, line_nr, client_id)
end
end
+ opts.focus_id = "line_diagnostics"
local popup_bufnr, winnr = util.open_floating_preview(lines, 'plaintext', opts)
for i, hi in ipairs(highlights) do
local prefixlen, hiname = unpack(hi)
@@ -1161,13 +1166,6 @@ function M.show_line_diagnostics(opts, bufnr, line_nr, client_id)
return popup_bufnr, winnr
end
-local loclist_type_map = {
- [DiagnosticSeverity.Error] = 'E',
- [DiagnosticSeverity.Warning] = 'W',
- [DiagnosticSeverity.Information] = 'I',
- [DiagnosticSeverity.Hint] = 'I',
-}
-
--- Clear diagnotics and diagnostic cache
---
@@ -1196,44 +1194,29 @@ end
--- - Exclusive severity to consider. Overrides {severity_limit}
--- - {severity_limit}: (DiagnosticSeverity)
--- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
+--- - {workspace}: (boolean, default false)
+--- - Set the list with workspace diagnostics
function M.set_loclist(opts)
opts = opts or {}
-
local open_loclist = if_nil(opts.open_loclist, true)
-
- local bufnr = vim.api.nvim_get_current_buf()
- local buffer_diags = M.get(bufnr, opts.client_id)
-
- if opts.severity then
- buffer_diags = filter_to_severity_limit(opts.severity, buffer_diags)
- elseif opts.severity_limit then
- buffer_diags = filter_by_severity_limit(opts.severity_limit, buffer_diags)
- end
-
- local items = {}
- local insert_diag = function(diag)
- local pos = diag.range.start
- local row = pos.line
- local col = util.character_offset(bufnr, row, pos.character)
-
- local line = (api.nvim_buf_get_lines(bufnr, row, row + 1, false) or {""})[1]
-
- table.insert(items, {
- bufnr = bufnr,
- lnum = row + 1,
- col = col + 1,
- text = line .. " | " .. diag.message,
- type = loclist_type_map[diag.severity or DiagnosticSeverity.Error] or 'E',
- })
- end
-
- for _, diag in ipairs(buffer_diags) do
- insert_diag(diag)
+ local current_bufnr = api.nvim_get_current_buf()
+ local diags = opts.workspace and M.get_all(opts.client_id) or {
+ [current_bufnr] = M.get(current_bufnr, opts.client_id)
+ }
+ local predicate = function(d)
+ local severity = to_severity(opts.severity)
+ if severity then
+ return d.severity == severity
+ end
+ severity = to_severity(opts.severity_limit)
+ if severity then
+ return d.severity == severity
+ end
+ return true
end
-
- table.sort(items, function(a, b) return a.lnum < b.lnum end)
-
- util.set_loclist(items)
+ local items = util.diagnostics_to_items(diags, predicate)
+ local win_id = vim.api.nvim_get_current_win()
+ util.set_loclist(items, win_id)
if open_loclist then
vim.cmd [[lopen]]
end
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
index 525ec4ce5b..6ae54ea253 100644
--- a/runtime/lua/vim/lsp/handlers.lua
+++ b/runtime/lua/vim/lsp/handlers.lua
@@ -260,24 +260,18 @@ end
--- - See |vim.api.nvim_open_win()|
function M.hover(_, method, result, _, _, config)
config = config or {}
- local bufnr, winnr = util.focusable_float(method, function()
- if not (result and result.contents) then
- -- return { 'No information available' }
- return
- end
- local markdown_lines = util.convert_input_to_markdown_lines(result.contents)
- markdown_lines = util.trim_empty_lines(markdown_lines)
- if vim.tbl_isempty(markdown_lines) then
- -- return { 'No information available' }
- return
- end
- local bufnr, winnr = util.fancy_floating_markdown(markdown_lines, {
- border = config.border
- })
- util.close_preview_autocmd({"CursorMoved", "BufHidden", "InsertCharPre"}, winnr)
- return bufnr, winnr
- end)
- return bufnr, winnr
+ config.focus_id = method
+ if not (result and result.contents) then
+ -- return { 'No information available' }
+ return
+ end
+ local markdown_lines = util.convert_input_to_markdown_lines(result.contents)
+ markdown_lines = util.trim_empty_lines(markdown_lines)
+ if vim.tbl_isempty(markdown_lines) then
+ -- return { 'No information available' }
+ return
+ end
+ return util.open_floating_preview(markdown_lines, "markdown", config)
end
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover
@@ -335,23 +329,21 @@ M['textDocument/implementation'] = location_handler
--- - See |vim.api.nvim_open_win()|
function M.signature_help(_, method, result, _, bufnr, config)
config = config or {}
+ config.focus_id = method
-- When use `autocmd CompleteDone <silent><buffer> lua vim.lsp.buf.signature_help()` to call signatureHelp handler
-- If the completion item doesn't have signatures It will make noise. Change to use `print` that can use `<silent>` to ignore
if not (result and result.signatures and result.signatures[1]) then
print('No signature help available')
return
end
- local lines = util.convert_signature_help_to_markdown_lines(result)
+ local ft = api.nvim_buf_get_option(bufnr, 'filetype')
+ local lines = util.convert_signature_help_to_markdown_lines(result, ft)
lines = util.trim_empty_lines(lines)
if vim.tbl_isempty(lines) then
print('No signature help available')
return
end
- local syntax = api.nvim_buf_get_option(bufnr, 'syntax')
- local p_bufnr, _ = util.focusable_preview(method, function()
- return lines, util.try_trim_markdown_code_blocks(lines), config
- end)
- api.nvim_buf_set_option(p_bufnr, 'syntax', syntax)
+ return util.open_floating_preview(lines, "markdown", config)
end
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp
diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua
index 0cabd1a0d4..98835d6708 100644
--- a/runtime/lua/vim/lsp/rpc.lua
+++ b/runtime/lua/vim/lsp/rpc.lua
@@ -523,27 +523,33 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
decoded.error = convert_NIL(decoded.error)
decoded.result = convert_NIL(decoded.result)
+ -- We sent a number, so we expect a number.
+ local result_id = tonumber(decoded.id)
+
-- Do not surface RequestCancelled or ContentModified to users, it is RPC-internal.
if decoded.error then
+ local mute_error = false
if decoded.error.code == protocol.ErrorCodes.RequestCancelled then
local _ = log.debug() and log.debug("Received cancellation ack", decoded)
+ mute_error = true
elseif decoded.error.code == protocol.ErrorCodes.ContentModified then
local _ = log.debug() and log.debug("Received content modified ack", decoded)
+ mute_error = true
end
- local result_id = tonumber(decoded.id)
- -- Clear any callback since this is cancelled now.
- -- This is safe to do assuming that these conditions hold:
- -- - The server will not send a result callback after this cancellation.
- -- - If the server sent this cancellation ACK after sending the result, the user of this RPC
- -- client will ignore the result themselves.
- if result_id then
- message_callbacks[result_id] = nil
+
+ if mute_error then
+ -- Clear any callback since this is cancelled now.
+ -- This is safe to do assuming that these conditions hold:
+ -- - The server will not send a result callback after this cancellation.
+ -- - If the server sent this cancellation ACK after sending the result, the user of this RPC
+ -- client will ignore the result themselves.
+ if result_id then
+ message_callbacks[result_id] = nil
+ end
+ return
end
- return
end
- -- We sent a number, so we expect a number.
- local result_id = tonumber(decoded.id)
local callback = message_callbacks[result_id]
if callback then
message_callbacks[result_id] = nil
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index ce8468aa8a..cb9a7cbed5 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -4,6 +4,7 @@ local validate = vim.validate
local api = vim.api
local list_extend = vim.list_extend
local highlight = require 'vim.highlight'
+local uv = vim.loop
local npcall = vim.F.npcall
local split = vim.split
@@ -29,6 +30,16 @@ local default_border = {
{" ", "NormalFloat"},
}
+
+local DiagnosticSeverity = protocol.DiagnosticSeverity
+local loclist_type_map = {
+ [DiagnosticSeverity.Error] = 'E',
+ [DiagnosticSeverity.Warning] = 'W',
+ [DiagnosticSeverity.Information] = 'I',
+ [DiagnosticSeverity.Hint] = 'I',
+}
+
+
--@private
-- Check the border given by opts or the default border for the additional
-- size it adds to a float.
@@ -39,14 +50,37 @@ local function get_border_size(opts)
local width = 0
if type(border) == 'string' then
- -- 'single', 'double', etc.
- height = 2
- width = 2
+ local border_size = {none = {0, 0}, single = {2, 2}, double = {2, 2}, shadow = {1, 1}}
+ if border_size[border] == nil then
+ error("floating preview border is not correct. Please refer to the docs |vim.api.nvim_open_win()|"
+ .. vim.inspect(border))
+ end
+ height, width = unpack(border_size[border])
else
- height = height + vim.fn.strdisplaywidth(border[2][1]) -- top
- height = height + vim.fn.strdisplaywidth(border[6][1]) -- bottom
- width = width + vim.fn.strdisplaywidth(border[4][1]) -- right
- width = width + vim.fn.strdisplaywidth(border[8][1]) -- left
+ local function border_width(id)
+ if type(border[id]) == "table" then
+ -- border specified as a table of <character, highlight group>
+ return vim.fn.strdisplaywidth(border[id][1])
+ elseif type(border[id]) == "string" then
+ -- border specified as a list of border characters
+ return vim.fn.strdisplaywidth(border[id])
+ end
+ error("floating preview border is not correct. Please refer to the docs |vim.api.nvim_open_win()|" .. vim.inspect(border))
+ end
+ local function border_height(id)
+ if type(border[id]) == "table" then
+ -- border specified as a table of <character, highlight group>
+ return #border[id][1] > 0 and 1 or 0
+ elseif type(border[id]) == "string" then
+ -- border specified as a list of border characters
+ return #border[id] > 0 and 1 or 0
+ end
+ error("floating preview border is not correct. Please refer to the docs |vim.api.nvim_open_win()|" .. vim.inspect(border))
+ end
+ height = height + border_height(2) -- top
+ height = height + border_height(6) -- bottom
+ width = width + border_width(4) -- right
+ width = width + border_width(8) -- left
end
return { height = height, width = width }
@@ -73,7 +107,7 @@ function M.set_lines(lines, A, B, new_lines)
-- way the LSP describes the range including the last newline is by
-- specifying a line number after what we would call the last line.
local i_n = math.min(B[1] + 1, #lines)
- if not (i_0 >= 1 and i_0 <= #lines and i_n >= 1 and i_n <= #lines) then
+ if not (i_0 >= 1 and i_0 <= #lines + 1 and i_n >= 1 and i_n <= #lines) then
error("Invalid range: "..vim.inspect{A = A; B = B; #lines, new_lines})
end
local prefix = ""
@@ -566,13 +600,15 @@ end
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
local function get_completion_word(item)
if item.textEdit ~= nil and item.textEdit.newText ~= nil and item.textEdit.newText ~= "" then
- if protocol.InsertTextFormat[item.insertTextFormat] == "PlainText" then
+ local insert_text_format = protocol.InsertTextFormat[item.insertTextFormat]
+ if insert_text_format == "PlainText" or insert_text_format == nil then
return item.textEdit.newText
else
return M.parse_snippet(item.textEdit.newText)
end
elseif item.insertText ~= nil and item.insertText ~= "" then
- if protocol.InsertTextFormat[item.insertTextFormat] == "PlainText" then
+ local insert_text_format = protocol.InsertTextFormat[item.insertTextFormat]
+ if insert_text_format == "PlainText" or insert_text_format == nil then
return item.insertText
else
return M.parse_snippet(item.insertText)
@@ -802,9 +838,10 @@ end
--- Converts `textDocument/SignatureHelp` response to markdown lines.
---
--@param signature_help Response of `textDocument/SignatureHelp`
+--@param ft optional filetype that will be use as the `lang` for the label markdown code block
--@returns list of lines of converted markdown.
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp
-function M.convert_signature_help_to_markdown_lines(signature_help)
+function M.convert_signature_help_to_markdown_lines(signature_help, ft)
if not signature_help.signatures then
return
end
@@ -822,7 +859,12 @@ function M.convert_signature_help_to_markdown_lines(signature_help)
if not signature then
return
end
- vim.list_extend(contents, vim.split(signature.label, '\n', true))
+ local label = signature.label
+ if ft then
+ -- wrap inside a code block so fancy_markdown can render it properly
+ label = ("```%s\n%s\n```"):format(ft, label)
+ end
+ vim.list_extend(contents, vim.split(label, '\n', true))
if signature.documentation then
M.convert_input_to_markdown_lines(signature.documentation, contents)
end
@@ -906,6 +948,7 @@ function M.make_floating_popup_options(width, height, opts)
anchor = anchor,
col = col + (opts.offset_x or 0),
height = height,
+ focusable = opts.focusable,
relative = 'cursor',
row = row + (opts.offset_y or 0),
style = 'minimal',
@@ -914,23 +957,6 @@ function M.make_floating_popup_options(width, height, opts)
}
end
-local function _should_add_to_tagstack(new_item)
- local stack = vim.fn.gettagstack()
-
- -- Check if we're at the bottom of the tagstack.
- if stack.curidx <= 1 then return true end
-
- local top_item = stack.items[stack.curidx-1]
-
- -- Check if the item at the top of the tagstack is exactly the
- -- same as the one we want to push.
- if top_item.tagname ~= new_item.tagname then return true end
- for i, v in ipairs(top_item.from) do
- if v ~= new_item.from[i] then return true end
- end
- return false
-end
-
--- Jumps to a location.
---
--@param location (`Location`|`LocationLink`)
@@ -939,36 +965,22 @@ function M.jump_to_location(location)
-- location may be Location or LocationLink
local uri = location.uri or location.targetUri
if uri == nil then return end
-
- local from_bufnr = vim.fn.bufnr('%')
- local from = {from_bufnr, vim.fn.line('.'), vim.fn.col('.'), 0}
- local item = {tagname=vim.fn.expand('<cword>'), from=from}
-
+ local bufnr = vim.uri_to_bufnr(uri)
-- Save position in jumplist
- vim.cmd("mark '")
+ vim.cmd "normal! m'"
+
+ -- Push a new item into tagstack
+ local from = {vim.fn.bufnr('%'), vim.fn.line('.'), vim.fn.col('.'), 0}
+ local items = {{tagname=vim.fn.expand('<cword>'), from=from}}
+ vim.fn.settagstack(vim.fn.win_getid(), {items=items}, 't')
--- Jump to new location (adjusting for UTF-16 encoding of characters)
- local bufnr = vim.uri_to_bufnr(uri)
api.nvim_set_current_buf(bufnr)
api.nvim_buf_set_option(0, 'buflisted', true)
local range = location.range or location.targetSelectionRange
local row = range.start.line
local col = get_line_byte_from_position(0, range.start)
- -- This prevents the tagstack to be filled with items that provide
- -- no motion when CTRL-T is pressed because they're both the source
- -- and the destination.
- local motionless =
- bufnr == from_bufnr and
- row+1 == from[2] and col+1 == from[3]
- if not motionless and _should_add_to_tagstack(item) then
- local winid = vim.fn.win_getid()
- local items = {item}
- vim.fn.settagstack(winid, {items=items}, 't')
- end
-
- -- Jump to new location
api.nvim_win_set_cursor(0, {row + 1, col})
-
return true
end
@@ -980,7 +992,7 @@ end
---
--@param location a single `Location` or `LocationLink`
--@returns (bufnr,winnr) buffer and window number of floating window or nil
-function M.preview_location(location)
+function M.preview_location(location, opts)
-- location may be LocationLink or Location (more useful for the former)
local uri = location.targetUri or location.uri
if uri == nil then return end
@@ -991,7 +1003,15 @@ function M.preview_location(location)
local range = location.targetRange or location.range
local contents = api.nvim_buf_get_lines(bufnr, range.start.line, range["end"].line+1, false)
local syntax = api.nvim_buf_get_option(bufnr, 'syntax')
- return M.open_floating_preview(contents, syntax)
+ if syntax == "" then
+ -- When no syntax is set, we use filetype as fallback. This might not result
+ -- in a valid syntax definition. See also ft detection in fancy_floating_win.
+ -- An empty syntax is more common now with TreeSitter, since TS disables syntax.
+ syntax = api.nvim_buf_get_option(bufnr, 'filetype')
+ end
+ opts = opts or {}
+ opts.focus_id = "location"
+ return M.open_floating_preview(contents, syntax, opts)
end
--@private
@@ -1013,7 +1033,9 @@ end
---buffer id, the newly created window will be the new focus associated with
---the current buffer via the tag `unique_name`.
--@returns (pbufnr, pwinnr) if `fn()` has created a new window; nil otherwise
+---@deprecated please use open_floating_preview directly
function M.focusable_float(unique_name, fn)
+ vim.notify("focusable_float is deprecated. Please use open_floating_preview and pass focus_id = [unique_name] instead", vim.log.levels.WARN)
-- Go back to previous window if we are in a focusable one
if npcall(api.nvim_win_get_var, 0, unique_name) then
return api.nvim_command("wincmd p")
@@ -1042,10 +1064,10 @@ end
--@param fn (function) The return values of this function will be passed
---directly to |vim.lsp.util.open_floating_preview()|, in the case that a new
---floating window should be created
+---@deprecated please use open_floating_preview directly
function M.focusable_preview(unique_name, fn)
- return M.focusable_float(unique_name, function()
- return M.open_floating_preview(fn())
- end)
+ vim.notify("focusable_preview is deprecated. Please use open_floating_preview and pass focus_id = [unique_name] instead", vim.log.levels.WARN)
+ return M.open_floating_preview(fn(), {focus_id = unique_name})
end
--- Trims empty lines from input and pad top and bottom with empty lines
@@ -1077,13 +1099,20 @@ end
--- TODO: refactor to separate stripping/converting and make use of open_floating_preview
---
+--- @deprecated please use open_floating_preview directly
+function M.fancy_floating_markdown(contents, opts)
+ vim.notify("fancy_floating_markdown is deprecated. Please use open_floating_preview and pass focus_id = [unique_name] instead", vim.log.levels.WARN)
+ return M.open_floating_preview(contents, "markdown", opts)
+end
+
--- Converts markdown into syntax highlighted regions by stripping the code
--- blocks and converting them into highlighted code.
--- This will by default insert a blank line separator after those code block
--- regions to improve readability.
---- The result is shown in a floating preview.
+---
+--- This method configures the given buffer and returns the lines to set.
+---
+--- If you want to open a popup with fancy markdown, use `open_floating_preview` instead
---
---@param contents table of lines to show in window
---@param opts dictionary with optional fields
@@ -1098,7 +1127,7 @@ end
--- - pad_bottom number of lines to pad contents at bottom
--- - separator insert separator after code block
---@returns width,height size of float
-function M.fancy_floating_markdown(contents, opts)
+function M.stylize_markdown(bufnr, contents, opts)
validate {
contents = { contents, 't' };
opts = { opts, 't', true };
@@ -1115,12 +1144,16 @@ function M.fancy_floating_markdown(contents, opts)
local ft = line:match("^```([a-zA-Z0-9_]*)$")
-- local ft = line:match("^```(.*)$")
-- TODO(ashkan): validate the filetype here.
+ local is_pre = line:match("^%s*<pre>%s*$")
+ if is_pre then
+ ft = ""
+ end
if ft then
local start = #stripped
i = i + 1
while i <= #contents do
line = contents[i]
- if line == "```" then
+ if line == "```" or (is_pre and line:match("^%s*</pre>%s*$")) then
i = i + 1
break
end
@@ -1149,55 +1182,69 @@ function M.fancy_floating_markdown(contents, opts)
local insert_separator = opts.separator
if insert_separator == nil then insert_separator = true end
if insert_separator then
- for i, h in ipairs(highlights) do
- h.start = h.start + i - 1
- h.finish = h.finish + i - 1
+ local offset = 0
+ for _, h in ipairs(highlights) do
+ h.start = h.start + offset
+ h.finish = h.finish + offset
+ -- check if a seperator already exists and use that one instead of creating a new one
if h.finish + 1 <= #stripped then
- table.insert(stripped, h.finish + 1, string.rep("─", math.min(width, opts.wrap_at or width)))
- height = height + 1
+ if stripped[h.finish + 1]:match("^---+$") then
+ stripped[h.finish + 1] = string.rep("─", math.min(width, opts.wrap_at or width))
+ else
+ table.insert(stripped, h.finish + 1, string.rep("─", math.min(width, opts.wrap_at or width)))
+ offset = offset + 1
+ height = height + 1
+ end
end
end
end
- -- Make the floating window.
- local bufnr = api.nvim_create_buf(false, true)
- local winnr = api.nvim_open_win(bufnr, false, M.make_floating_popup_options(width, height, opts))
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, stripped)
- api.nvim_buf_set_option(bufnr, 'modifiable', false)
-
- -- Switch to the floating window to apply the syntax highlighting.
- -- This is because the syntax command doesn't accept a target.
- local cwin = vim.api.nvim_get_current_win()
- vim.api.nvim_set_current_win(winnr)
- api.nvim_win_set_option(winnr, 'conceallevel', 2)
- api.nvim_win_set_option(winnr, 'concealcursor', 'n')
- vim.cmd("ownsyntax lsp_markdown")
local idx = 1
--@private
+ -- keep track of syntaxes we already inlcuded.
+ -- no need to include the same syntax more than once
+ local langs = {}
local function apply_syntax_to_region(ft, start, finish)
- if ft == '' then return end
+ if ft == "" then
+ vim.cmd(string.format("syntax region markdownCode start=+\\%%%dl+ end=+\\%%%dl+ keepend extend", start, finish + 1))
+ return
+ end
local name = ft..idx
idx = idx + 1
local lang = "@"..ft:upper()
- -- TODO(ashkan): better validation before this.
- if not pcall(vim.cmd, string.format("syntax include %s syntax/%s.vim", lang, ft)) then
- return
+ if not langs[lang] then
+ -- HACK: reset current_syntax, since some syntax files like markdown won't load if it is already set
+ pcall(vim.api.nvim_buf_del_var, bufnr, "current_syntax")
+ -- TODO(ashkan): better validation before this.
+ if not pcall(vim.cmd, string.format("syntax include %s syntax/%s.vim", lang, ft)) then
+ return
+ end
+ langs[lang] = true
end
- vim.cmd(string.format("syntax region %s start=+\\%%%dl+ end=+\\%%%dl+ contains=%s", name, start, finish + 1, lang))
- end
- -- Previous highlight region.
- -- TODO(ashkan): this wasn't working for some reason, but I would like to
- -- make sure that regions between code blocks are definitely markdown.
- -- local ph = {start = 0; finish = 1;}
- for _, h in ipairs(highlights) do
- -- apply_syntax_to_region('markdown', ph.finish, h.start)
- apply_syntax_to_region(h.ft, h.start, h.finish)
- -- ph = h
+ vim.cmd(string.format("syntax region %s start=+\\%%%dl+ end=+\\%%%dl+ contains=%s keepend", name, start, finish + 1, lang))
end
- vim.api.nvim_set_current_win(cwin)
- return bufnr, winnr
+ -- needs to run in the buffer for the regions to work
+ api.nvim_buf_call(bufnr, function()
+ -- we need to apply lsp_markdown regions speperately, since otherwise
+ -- markdown regions can "bleed" through the other syntax regions
+ -- and mess up the formatting
+ local last = 1
+ for _, h in ipairs(highlights) do
+ if last < h.start then
+ apply_syntax_to_region("lsp_markdown", last, h.start - 1)
+ end
+ apply_syntax_to_region(h.ft, h.start, h.finish)
+ last = h.finish + 1
+ end
+ if last < #stripped then
+ apply_syntax_to_region("lsp_markdown", last, #stripped)
+ end
+ end)
+
+ return stripped
end
--- Creates autocommands to close a preview window when events happen.
@@ -1206,7 +1253,9 @@ end
--@param winnr (number) window id of preview window
--@see |autocmd-events|
function M.close_preview_autocmd(events, winnr)
- api.nvim_command("autocmd "..table.concat(events, ',').." <buffer> ++once lua pcall(vim.api.nvim_win_close, "..winnr..", true)")
+ if #events > 0 then
+ api.nvim_command("autocmd "..table.concat(events, ',').." <buffer> ++once lua pcall(vim.api.nvim_win_close, "..winnr..", true)")
+ end
end
--@internal
@@ -1289,15 +1338,19 @@ end
--@param contents table of lines to show in window
--@param syntax string of syntax to set for opened buffer
--@param opts dictionary with optional fields
--- - height of floating window
--- - width of floating window
--- - wrap_at character to wrap at for computing height
--- - max_width maximal width of floating window
--- - max_height maximal height of floating window
--- - pad_left number of columns to pad contents at left
--- - pad_right number of columns to pad contents at right
--- - pad_top number of lines to pad contents at top
--- - pad_bottom number of lines to pad contents at bottom
+--- - height of floating window
+--- - width of floating window
+--- - wrap boolean enable wrapping of long lines (defaults to true)
+--- - wrap_at character to wrap at for computing height when wrap is enabled
+--- - max_width maximal width of floating window
+--- - max_height maximal height of floating window
+--- - pad_left number of columns to pad contents at left
+--- - pad_right number of columns to pad contents at right
+--- - pad_top number of lines to pad contents at top
+--- - pad_bottom number of lines to pad contents at bottom
+--- - focus_id if a popup with this id is opened, then focus it
+--- - close_events list of events that closes the floating window
+--- - focusable (boolean, default true): Make float focusable
--@returns bufnr,winnr buffer and window number of the newly created floating
---preview window
function M.open_floating_preview(contents, syntax, opts)
@@ -1307,27 +1360,85 @@ function M.open_floating_preview(contents, syntax, opts)
opts = { opts, 't', true };
}
opts = opts or {}
+ opts.wrap = opts.wrap ~= false -- wrapping by default
+ opts.stylize_markdown = opts.stylize_markdown ~= false
+ opts.close_events = opts.close_events or {"CursorMoved", "CursorMovedI", "BufHidden", "InsertCharPre"}
+
+ local bufnr = api.nvim_get_current_buf()
+
+ -- check if this popup is focusable and we need to focus
+ if opts.focus_id and opts.focusable ~= false then
+ -- Go back to previous window if we are in a focusable one
+ local current_winnr = api.nvim_get_current_win()
+ if npcall(api.nvim_win_get_var, current_winnr, opts.focus_id) then
+ api.nvim_command("wincmd p")
+ return bufnr, current_winnr
+ end
+ do
+ local win = find_window_by_var(opts.focus_id, bufnr)
+ if win and api.nvim_win_is_valid(win) and vim.fn.pumvisible() == 0 then
+ -- focus and return the existing buf, win
+ api.nvim_set_current_win(win)
+ api.nvim_command("stopinsert")
+ return api.nvim_win_get_buf(win), win
+ end
+ end
+ end
+
+ -- check if another floating preview already exists for this buffer
+ -- and close it if needed
+ local existing_float = npcall(api.nvim_buf_get_var, bufnr, "lsp_floating_preview")
+ if existing_float and api.nvim_win_is_valid(existing_float) then
+ api.nvim_win_close(existing_float, true)
+ end
+
+ local floating_bufnr = api.nvim_create_buf(false, true)
+ local do_stylize = syntax == "markdown" and opts.stylize_markdown
+
-- Clean up input: trim empty lines from the end, pad
contents = M._trim(contents, opts)
+ if do_stylize then
+ -- applies the syntax and sets the lines to the buffer
+ contents = M.stylize_markdown(floating_bufnr, contents, opts)
+ else
+ if syntax then
+ api.nvim_buf_set_option(floating_bufnr, 'syntax', syntax)
+ end
+ api.nvim_buf_set_lines(floating_bufnr, 0, -1, true, contents)
+ end
+
-- Compute size of float needed to show (wrapped) lines
- opts.wrap_at = opts.wrap_at or (vim.wo["wrap"] and api.nvim_win_get_width(0))
+ if opts.wrap then
+ opts.wrap_at = opts.wrap_at or api.nvim_win_get_width(0)
+ else
+ opts.wrap_at = nil
+ end
local width, height = M._make_floating_popup_size(contents, opts)
- local floating_bufnr = api.nvim_create_buf(false, true)
- if syntax then
- api.nvim_buf_set_option(floating_bufnr, 'syntax', syntax)
- end
local float_option = M.make_floating_popup_options(width, height, opts)
local floating_winnr = api.nvim_open_win(floating_bufnr, false, float_option)
- if syntax == 'markdown' then
+ if do_stylize then
api.nvim_win_set_option(floating_winnr, 'conceallevel', 2)
+ api.nvim_win_set_option(floating_winnr, 'concealcursor', 'n')
end
- api.nvim_buf_set_lines(floating_bufnr, 0, -1, true, contents)
+ -- disable folding
+ api.nvim_win_set_option(floating_winnr, 'foldenable', false)
+ -- soft wrapping
+ api.nvim_win_set_option(floating_winnr, 'wrap', opts.wrap)
+
api.nvim_buf_set_option(floating_bufnr, 'modifiable', false)
api.nvim_buf_set_option(floating_bufnr, 'bufhidden', 'wipe')
- M.close_preview_autocmd({"CursorMoved", "CursorMovedI", "BufHidden", "BufLeave"}, floating_winnr)
+ api.nvim_buf_set_keymap(floating_bufnr, "n", "q", "<cmd>bdelete<cr>", {silent = true, noremap = true})
+ M.close_preview_autocmd(opts.close_events, floating_winnr)
+
+ -- save focus_id
+ if opts.focus_id then
+ api.nvim_win_set_var(floating_winnr, opts.focus_id, bufnr)
+ end
+ api.nvim_buf_set_var(bufnr, "lsp_floating_preview", floating_winnr)
+
return floating_bufnr, floating_winnr
end
@@ -1366,6 +1477,88 @@ local position_sort = sort_by_key(function(v)
return {v.start.line, v.start.character}
end)
+-- Gets the zero-indexed line from the given uri.
+-- For non-file uris, we load the buffer and get the line.
+-- If a loaded buffer exists, then that is used.
+-- Otherwise we get the line using libuv which is a lot faster than loading the buffer.
+--@param uri string uri of the resource to get the line from
+--@param row number zero-indexed line number
+--@return string the line at row in filename
+function M.get_line(uri, row)
+ return M.get_lines(uri, { row })[row]
+end
+
+-- Gets the zero-indexed lines from the given uri.
+-- For non-file uris, we load the buffer and get the lines.
+-- If a loaded buffer exists, then that is used.
+-- Otherwise we get the lines using libuv which is a lot faster than loading the buffer.
+--@param uri string uri of the resource to get the lines from
+--@param rows number[] zero-indexed line numbers
+--@return table<number string> a table mapping rows to lines
+function M.get_lines(uri, rows)
+ rows = type(rows) == "table" and rows or { rows }
+
+ local function buf_lines(bufnr)
+ local lines = {}
+ for _, row in pairs(rows) do
+ lines[row] = (vim.api.nvim_buf_get_lines(bufnr, row, row + 1, false) or { "" })[1]
+ end
+ return lines
+ end
+
+ -- load the buffer if this is not a file uri
+ -- Custom language server protocol extensions can result in servers sending URIs with custom schemes. Plugins are able to load these via `BufReadCmd` autocmds.
+ if uri:sub(1, 4) ~= "file" then
+ local bufnr = vim.uri_to_bufnr(uri)
+ vim.fn.bufload(bufnr)
+ return buf_lines(bufnr)
+ end
+
+ local filename = vim.uri_to_fname(uri)
+
+ -- use loaded buffers if available
+ if vim.fn.bufloaded(filename) == 1 then
+ local bufnr = vim.fn.bufnr(filename, false)
+ return buf_lines(bufnr)
+ end
+
+ -- get the data from the file
+ local fd = uv.fs_open(filename, "r", 438)
+ if not fd then return "" end
+ local stat = uv.fs_fstat(fd)
+ local data = uv.fs_read(fd, stat.size, 0)
+ uv.fs_close(fd)
+
+ local lines = {} -- rows we need to retrieve
+ local need = 0 -- keep track of how many unique rows we need
+ for _, row in pairs(rows) do
+ if not lines[row] then
+ need = need + 1
+ end
+ lines[row] = true
+ end
+
+ local found = 0
+ local lnum = 0
+
+ for line in string.gmatch(data, "([^\n]*)\n?") do
+ if lines[lnum] == true then
+ lines[lnum] = line
+ found = found + 1
+ if found == need then break end
+ end
+ lnum = lnum + 1
+ end
+
+ -- change any lines we didn't find to the empty string
+ for i, line in pairs(lines) do
+ if line == true then
+ lines[i] = ""
+ end
+ end
+ return lines
+end
+
--- Returns the items with the byte position calculated correctly and in sorted
--- order, for display in quickfix and location lists.
---
@@ -1394,14 +1587,24 @@ function M.locations_to_items(locations)
for _, uri in ipairs(keys) do
local rows = grouped[uri]
table.sort(rows, position_sort)
- local bufnr = vim.uri_to_bufnr(uri)
- vim.fn.bufload(bufnr)
local filename = vim.uri_to_fname(uri)
+
+ -- list of row numbers
+ local uri_rows = {}
+ for _, temp in ipairs(rows) do
+ local pos = temp.start
+ local row = pos.line
+ table.insert(uri_rows, row)
+ end
+
+ -- get all the lines for this uri
+ local lines = M.get_lines(uri, uri_rows)
+
for _, temp in ipairs(rows) do
local pos = temp.start
local row = pos.line
- local line = (api.nvim_buf_get_lines(bufnr, row, row + 1, false) or {""})[1]
- local col = M.character_offset(bufnr, row, pos.character)
+ local line = lines[row] or ""
+ local col = pos.character
table.insert(items, {
filename = filename,
lnum = row + 1,
@@ -1413,12 +1616,13 @@ function M.locations_to_items(locations)
return items
end
---- Fills current window's location list with given list of items.
+--- Fills target window's location list with given list of items.
--- Can be obtained with e.g. |vim.lsp.util.locations_to_items()|.
+--- Defaults to current window.
---
--@param items (table) list of items
-function M.set_loclist(items)
- vim.fn.setloclist(0, {}, ' ', {
+function M.set_loclist(items, win_id)
+ vim.fn.setloclist(win_id or 0, {}, ' ', {
title = 'Language Server';
items = items;
})
@@ -1459,13 +1663,13 @@ function M.symbols_to_items(symbols, bufnr)
kind = kind,
text = '['..kind..'] '..symbol.name,
})
- elseif symbol.range then -- DocumentSymbole type
+ elseif symbol.selectionRange then -- DocumentSymbole type
local kind = M._get_symbol_kind_name(symbol.kind)
table.insert(_items, {
-- bufnr = _bufnr,
filename = vim.api.nvim_buf_get_name(_bufnr),
- lnum = symbol.range.start.line + 1,
- col = symbol.range.start.character + 1,
+ lnum = symbol.selectionRange.start.line + 1,
+ col = symbol.selectionRange.start.character + 1,
kind = kind,
text = '['..kind..'] '..symbol.name
})
@@ -1595,6 +1799,12 @@ function M.make_given_range_params(start_pos, end_pos)
if B[2] > 0 then
B = {B[1], M.character_offset(0, B[1], B[2])}
end
+ -- we need to offset the end character position otherwise we loose the last
+ -- character of the selection, as LSP end position is exclusive
+ -- see https://microsoft.github.io/language-server-protocol/specification#range
+ if vim.o.selection ~= 'exclusive' then
+ B[2] = B[2] + 1
+ end
return {
textDocument = M.make_text_document_params(),
range = {
@@ -1653,8 +1863,9 @@ end
--@param row 0-indexed line
--@param col 0-indexed byte offset in line
--@returns (number, number) UTF-32 and UTF-16 index of the character in line {row} column {col} in buffer {buf}
-function M.character_offset(buf, row, col)
- local line = api.nvim_buf_get_lines(buf, row, row+1, true)[1]
+function M.character_offset(bufnr, row, col)
+ local uri = vim.uri_from_bufnr(bufnr)
+ local line = M.get_line(uri, row)
-- If the col is past the EOL, use the line length.
if col > #line then
return str_utfindex(line)
@@ -1677,6 +1888,40 @@ function M.lookup_section(settings, section)
return settings
end
+
+--- Convert diagnostics grouped by bufnr to a list of items for use in the
+--- quickfix or location list.
+---
+--@param diagnostics_by_bufnr table bufnr -> Diagnostic[]
+--@param predicate an optional function to filter the diagnostics.
+-- If present, only diagnostic items matching will be included.
+--@return table (A list of items)
+function M.diagnostics_to_items(diagnostics_by_bufnr, predicate)
+ local items = {}
+ for bufnr, diagnostics in pairs(diagnostics_by_bufnr or {}) do
+ for _, d in pairs(diagnostics) do
+ if not predicate or predicate(d) then
+ table.insert(items, {
+ bufnr = bufnr,
+ lnum = d.range.start.line + 1,
+ col = d.range.start.character + 1,
+ text = d.message,
+ type = loclist_type_map[d.severity or DiagnosticSeverity.Error] or 'E'
+ })
+ end
+ end
+ end
+ table.sort(items, function(a, b)
+ if a.bufnr == b.bufnr then
+ return a.lnum < b.lnum
+ else
+ return a.bufnr < b.bufnr
+ end
+ end)
+ return items
+end
+
+
M._get_line_byte_from_position = get_line_byte_from_position
M._warn_once = warn_once
diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua
index 9b4d28e09a..b81eb18945 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -231,7 +231,7 @@ local predicate_handlers = {
local compiled_vim_regexes = setmetatable({}, {
__index = function(t, pattern)
- local res = vim.regex(check_magic(vim.fn.escape(pattern, '\\')))
+ local res = vim.regex(check_magic(pattern))
rawset(t, pattern, res)
return res
end
@@ -260,7 +260,25 @@ local predicate_handlers = {
end
return false
- end
+ end,
+
+ ["any-of?"] = function(match, _, source, predicate)
+ local node = match[predicate[2]]
+ local node_text = M.get_node_text(node, source)
+
+ -- Since 'predicate' will not be used by callers of this function, use it
+ -- to store a string set built from the list of words to check against.
+ local string_set = predicate["string_set"]
+ if not string_set then
+ string_set = {}
+ for i=3,#predicate do
+ string_set[predicate[i]] = true
+ end
+ predicate["string_set"] = string_set
+ end
+
+ return string_set[node_text]
+ end,
}
-- As we provide lua-match? also expose vim-match?
diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
index a62df1dcec..ae1274f81f 100644
--- a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
+++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
@@ -2,7 +2,7 @@
"
" Author: Bram Moolenaar
" Copyright: Vim license applies, see ":help license"
-" Last Change: 2021 Jan 03
+" Last Change: 2021 May 16
"
" WORK IN PROGRESS - Only the basics work
" Note: On MS-Windows you need a recent version of gdb. The one included with
@@ -246,7 +246,9 @@ func s:StartDebug_term(dict)
let s:gdbbuf = gdb_job_info['buffer']
let s:gdbwin = win_getid(winnr())
- " Set arguments to be run
+ " Set arguments to be run. First wait a bit to make detecting gdb a bit
+ " more reliable.
+ sleep 200m
if len(proc_args)
call chansend(s:gdb_job_id, 'set args ' . join(proc_args) . "\r")
endif
diff --git a/runtime/syntax/dts.vim b/runtime/syntax/dts.vim
index cd4bd776b7..d75b9c178a 100644
--- a/runtime/syntax/dts.vim
+++ b/runtime/syntax/dts.vim
@@ -1,7 +1,7 @@
" Vim syntax file
" Language: dts/dtsi (device tree files)
" Maintainer: Daniel Mack <vim@zonque.org>
-" Last Change: 2013 Oct 20
+" Last Change: 2021 May 15
if exists("b:current_syntax")
finish
@@ -9,7 +9,7 @@ endif
syntax region dtsComment start="/\*" end="\*/"
syntax match dtsReference "&[[:alpha:][:digit:]_]\+"
-syntax region dtsBinaryProperty start="\[" end="\]"
+syntax region dtsBinaryProperty start="\[" end="\]"
syntax match dtsStringProperty "\".*\""
syntax match dtsKeyword "/.\{-1,\}/"
syntax match dtsLabel "^[[:space:]]*[[:alpha:][:digit:]_]\+:"
@@ -18,6 +18,38 @@ syntax region dtsCellProperty start="<" end=">" contains=dtsReference,dtsBinar
syntax region dtsCommentInner start="/\*" end="\*/"
syntax match dtsCommentLine "//.*$"
+" Accept %: for # (C99)
+syn region cPreCondit start="^\s*\zs\(%:\|#\)\s*\(if\|ifdef\|ifndef\|elif\)\>" skip="\\$" end="$" keepend contains=cComment,cCommentL,cCppString,cCharacter,cCppParen,cParenError,cNumbers,cCommentError,cSpaceError
+syn match cPreConditMatch display "^\s*\zs\(%:\|#\)\s*\(else\|endif\)\>"
+if !exists("c_no_if0")
+ syn cluster cCppOutInGroup contains=cCppInIf,cCppInElse,cCppInElse2,cCppOutIf,cCppOutIf2,cCppOutElse,cCppInSkip,cCppOutSkip
+ syn region cCppOutWrapper start="^\s*\zs\(%:\|#\)\s*if\s\+0\+\s*\($\|//\|/\*\|&\)" end=".\@=\|$" contains=cCppOutIf,cCppOutElse,@NoSpell fold
+ syn region cCppOutIf contained start="0\+" matchgroup=cCppOutWrapper end="^\s*\(%:\|#\)\s*endif\>" contains=cCppOutIf2,cCppOutElse
+ if !exists("c_no_if0_fold")
+ syn region cCppOutIf2 contained matchgroup=cCppOutWrapper start="0\+" end="^\s*\(%:\|#\)\s*\(else\>\|elif\s\+\(0\+\s*\($\|//\|/\*\|&\)\)\@!\|endif\>\)"me=s-1 contains=cSpaceError,cCppOutSkip,@Spell fold
+ else
+ syn region cCppOutIf2 contained matchgroup=cCppOutWrapper start="0\+" end="^\s*\(%:\|#\)\s*\(else\>\|elif\s\+\(0\+\s*\($\|//\|/\*\|&\)\)\@!\|endif\>\)"me=s-1 contains=cSpaceError,cCppOutSkip,@Spell
+ endif
+ syn region cCppOutElse contained matchgroup=cCppOutWrapper start="^\s*\(%:\|#\)\s*\(else\|elif\)" end="^\s*\(%:\|#\)\s*endif\>"me=s-1 contains=TOP,cPreCondit
+ syn region cCppInWrapper start="^\s*\zs\(%:\|#\)\s*if\s\+0*[1-9]\d*\s*\($\|//\|/\*\||\)" end=".\@=\|$" contains=cCppInIf,cCppInElse fold
+ syn region cCppInIf contained matchgroup=cCppInWrapper start="\d\+" end="^\s*\(%:\|#\)\s*endif\>" contains=TOP,cPreCondit
+ if !exists("c_no_if0_fold")
+ syn region cCppInElse contained start="^\s*\(%:\|#\)\s*\(else\>\|elif\s\+\(0*[1-9]\d*\s*\($\|//\|/\*\||\)\)\@!\)" end=".\@=\|$" containedin=cCppInIf contains=cCppInElse2 fold
+ else
+ syn region cCppInElse contained start="^\s*\(%:\|#\)\s*\(else\>\|elif\s\+\(0*[1-9]\d*\s*\($\|//\|/\*\||\)\)\@!\)" end=".\@=\|$" containedin=cCppInIf contains=cCppInElse2
+ endif
+ syn region cCppInElse2 contained matchgroup=cCppInWrapper start="^\s*\(%:\|#\)\s*\(else\|elif\)\([^/]\|/[^/*]\)*" end="^\s*\(%:\|#\)\s*endif\>"me=s-1 contains=cSpaceError,cCppOutSkip,@Spell
+ syn region cCppOutSkip contained start="^\s*\(%:\|#\)\s*\(if\>\|ifdef\>\|ifndef\>\)" skip="\\$" end="^\s*\(%:\|#\)\s*endif\>" contains=cSpaceError,cCppOutSkip
+ syn region cCppInSkip contained matchgroup=cCppInWrapper start="^\s*\(%:\|#\)\s*\(if\s\+\(\d\+\s*\($\|//\|/\*\||\|&\)\)\@!\|ifdef\>\|ifndef\>\)" skip="\\$" end="^\s*\(%:\|#\)\s*endif\>" containedin=cCppOutElse,cCppInIf,cCppInSkip contains=TOP,cPreProc
+endif
+syn region cIncluded display contained start=+"+ skip=+\\\\\|\\"+ end=+"+
+syn match cIncluded display contained "<[^>]*>"
+syn match cInclude display "^\s*\zs\(%:\|#\)\s*include\>\s*["<]" contains=cIncluded
+"syn match cLineSkip "\\$"
+syn cluster cPreProcGroup contains=cPreCondit,cIncluded,cInclude,cDefine,cErrInParen,cErrInBracket,cUserLabel,cSpecial,cOctalZero,cCppOutWrapper,cCppInWrapper,@cCppOutInGroup,cFormat,cNumber,cFloat,cOctal,cOctalError,cNumbersCom,cString,cCommentSkip,cCommentString,cComment2String,@cCommentGroup,cCommentStartError,cParen,cBracket,cMulti,cBadBlock
+syn region cDefine start="^\s*\zs\(%:\|#\)\s*\(define\|undef\)\>" skip="\\$" end="$" keepend contains=ALLBUT,@cPreProcGroup,@Spell
+syn region cPreProc start="^\s*\zs\(%:\|#\)\s*\(pragma\>\|line\>\|warning\>\|warn\>\|error\>\)" skip="\\$" end="$" keepend contains=ALLBUT,@cPreProcGroup,@Spell
+
hi def link dtsCellProperty Number
hi def link dtsBinaryProperty Number
hi def link dtsStringProperty String
@@ -26,5 +58,21 @@ hi def link dtsLabel Label
hi def link dtsNode Structure
hi def link dtsReference Macro
hi def link dtsComment Comment
-hi def link dtsCommentInner Comment
+hi def link dtsCommentInner Comment
hi def link dtsCommentLine Comment
+
+hi def link cInclude Include
+hi def link cPreProc PreProc
+hi def link cDefine Macro
+hi def link cIncluded cString
+hi def link cString String
+
+hi def link cCppInWrapper cCppOutWrapper
+hi def link cCppOutWrapper cPreCondit
+hi def link cPreConditMatch cPreCondit
+hi def link cPreCondit PreCondit
+hi def link cCppOutSkip cCppOutIf2
+
+hi def link cCppInElse2 cCppOutIf2
+hi def link cCppOutIf2 cCppOut
+hi def link cCppOut Comment
diff --git a/runtime/syntax/lsp_markdown.vim b/runtime/syntax/lsp_markdown.vim
index d5c1414f01..d9b50be54c 100644
--- a/runtime/syntax/lsp_markdown.vim
+++ b/runtime/syntax/lsp_markdown.vim
@@ -4,7 +4,9 @@
" URL: http://neovim.io
" Remark: Uses markdown syntax file
-runtime! syntax/markdown.vim
+" always source the system included markdown instead of any other installed
+" markdown.vim syntax files
+execute 'source' expand('<sfile>:p:h') .. '/markdown.vim'
syn cluster mkdNonListItem add=mkdEscape,mkdNbsp
diff --git a/runtime/syntax/meson.vim b/runtime/syntax/meson.vim
index 78de2ab89e..7e6b565f19 100644
--- a/runtime/syntax/meson.vim
+++ b/runtime/syntax/meson.vim
@@ -2,6 +2,7 @@
" Language: Meson
" License: VIM License
" Maintainer: Nirbheek Chauhan <nirbheek.chauhan@gmail.com>
+" Liam Beguin <liambeguin@gmail.com>
" Last Change: 2019 Oct 18
" Credits: Zvezdan Petkovic <zpetkovic@acm.org>
" Neil Schemenauer <nas@meson.ca>
@@ -17,11 +18,7 @@
" let meson_space_error_highlight = 1
"
-" For version 5.x: Clear all syntax items.
-" For version 6.x: Quit when a syntax file was already loaded.
-if version < 600
- syntax clear
-elseif exists("b:current_syntax")
+if exists("b:current_syntax")
finish
endif
@@ -32,8 +29,9 @@ set cpo&vim
" http://mesonbuild.com/Syntax.html
syn keyword mesonConditional elif else if endif
-syn keyword mesonRepeat foreach endforeach
-syn keyword mesonOperator and not or
+syn keyword mesonRepeat foreach endforeach
+syn keyword mesonOperator and not or in
+syn keyword mesonStatement continue break
syn match mesonComment "#.*$" contains=mesonTodo,@Spell
syn keyword mesonTodo FIXME NOTE NOTES TODO XXX contained
@@ -117,10 +115,12 @@ syn keyword mesonBuiltin
\ subdir
\ subdir_done
\ subproject
+ \ summary
\ target_machine
\ test
\ vcs_tag
\ warning
+ \ range
if exists("meson_space_error_highlight")
" trailing whitespace
@@ -130,31 +130,20 @@ if exists("meson_space_error_highlight")
syn match mesonSpaceError display "\t\+ "
endif
-if version >= 508 || !exists("did_meson_syn_inits")
- if version <= 508
- let did_meson_syn_inits = 1
- command -nargs=+ HiLink hi link <args>
- else
- command -nargs=+ HiLink hi def link <args>
- endif
-
- " The default highlight links. Can be overridden later.
- HiLink mesonStatement Statement
- HiLink mesonConditional Conditional
- HiLink mesonRepeat Repeat
- HiLink mesonOperator Operator
- HiLink mesonComment Comment
- HiLink mesonTodo Todo
- HiLink mesonString String
- HiLink mesonEscape Special
- HiLink mesonNumber Number
- HiLink mesonBuiltin Function
- HiLink mesonConstant Number
- if exists("meson_space_error_highlight")
- HiLink mesonSpaceError Error
- endif
-
- delcommand HiLink
+" The default highlight links. Can be overridden later.
+hi def link mesonStatement Statement
+hi def link mesonConditional Conditional
+hi def link mesonRepeat Repeat
+hi def link mesonOperator Operator
+hi def link mesonComment Comment
+hi def link mesonTodo Todo
+hi def link mesonString String
+hi def link mesonEscape Special
+hi def link mesonNumber Number
+hi def link mesonBuiltin Function
+hi def link mesonConstant Number
+if exists("meson_space_error_higlight")
+ hi def link mesonSpaceError Error
endif
let b:current_syntax = "meson"
diff --git a/runtime/tutor/tutor.tutor b/runtime/tutor/tutor.tutor
index b46fcc4836..c2d5268e3d 100644
--- a/runtime/tutor/tutor.tutor
+++ b/runtime/tutor/tutor.tutor
@@ -216,7 +216,7 @@ starting with "$".
## INTERACTIVE ELEMENTS *interactive*
As visible in this very document, vim-tutor-mode includes some interactive
-elements to provide feedback to the user about his progress. If the text in
+elements to provide feedback to the user about their progress. If the text in
these elements satisfies some set condition, a ✓ sign will appear in the gutter
to the left. Otherwise, a ✗ sign is displayed.
diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh
index f583b2fdea..86552c0c8d 100755
--- a/scripts/vim-patch.sh
+++ b/scripts/vim-patch.sh
@@ -578,7 +578,7 @@ list_missing_previous_vimpatches_for_patch() {
local -a fnames
while IFS= read -r line ; do
fnames+=("$line")
- done < <(git -C "${VIM_SOURCE_DIR}" diff-tree --no-commit-id --name-only -r "${vim_commit}")
+ done < <(git -C "${VIM_SOURCE_DIR}" diff-tree --no-commit-id --name-only -r "${vim_commit}" -- . ':!src/version.c')
local i=0
local n=${#fnames[@]}
printf '=== getting missing patches for %d files ===\n' "$n"
@@ -593,18 +593,20 @@ list_missing_previous_vimpatches_for_patch() {
_set_missing_vimpatches 1 -- "${fname}"
set +u # Avoid "unbound variable" with bash < 4.4 below.
- local missing_vim_commit_info="${missing_vim_patches[0]}"
- if [[ -z "${missing_vim_commit_info}" ]]; then
- printf -- "-\n"
- else
- local missing_vim_commit="${missing_vim_commit_info%%:*}"
- if [[ -z "${vim_tag}" ]] || [[ "${missing_vim_commit}" < "${vim_tag}" ]]; then
- printf -- "%s\n" "$missing_vim_commit_info"
- missing_list+=("$missing_vim_commit_info")
+ for missing_vim_commit_info in "${missing_vim_patches[@]}"; do
+ if [[ -z "${missing_vim_commit_info}" ]]; then
+ printf -- "-\r"
else
- printf -- "-\n"
+ printf -- "-\r"
+ local missing_vim_commit="${missing_vim_commit_info%%:*}"
+ if [[ -z "${vim_tag}" ]] || [[ "${missing_vim_commit}" < "${vim_tag}" ]]; then
+ printf -- "%s\n" "$missing_vim_commit_info"
+ missing_list+=("$missing_vim_commit_info")
+ else
+ printf -- "-\r"
+ fi
fi
- fi
+ done
set -u
done
diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml
index 57bcb72d5d..fd57f2d5e4 100644
--- a/snap/snapcraft.yaml
+++ b/snap/snapcraft.yaml
@@ -69,7 +69,7 @@ parts:
# Fix Desktop file
sed -i 's|^Exec=nvim|Exec=/snap/bin/nvim.nvim|' ${SNAPCRAFT_PART_INSTALL}/usr/share/applications/nvim.desktop
sed -i 's|^TryExec=nvim|TryExec=/snap/bin/nvim.nvim|' ${SNAPCRAFT_PART_INSTALL}/usr/share/applications/nvim.desktop
- sed -i 's|^Icon=.*|Icon=${SNAP}/usr/share/pixmaps/nvim.png|' ${SNAPCRAFT_PART_INSTALL}/usr/share/applications/nvim.desktop
+ sed -i 's|^Icon=.*|Icon=${SNAP}/usr/share/icons/hicolor/128x128/apps/nvim.png|' ${SNAPCRAFT_PART_INSTALL}/usr/share/applications/nvim.desktop
build-packages:
- ninja-build
- libtool
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index 8b422b3abe..e4d7115654 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -505,7 +505,7 @@ if(WIN32)
set(EXTERNAL_BLOBS_SCRIPT
"file(MAKE_DIRECTORY \"${PROJECT_BINARY_DIR}/windows_runtime_deps/platforms\")")
foreach(DEP_FILE IN ITEMS
- ca-bundle.crt
+ curl-ca-bundle.crt
curl.exe
diff.exe
tee.exe
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 8724c83e12..f84e8c99a4 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -27,6 +27,7 @@
#include "nvim/map_defs.h"
#include "nvim/map.h"
#include "nvim/mark.h"
+#include "nvim/ops.h"
#include "nvim/extmark.h"
#include "nvim/decoration.h"
#include "nvim/fileio.h"
@@ -441,6 +442,8 @@ void nvim_buf_set_lines(uint64_t channel_id,
goto end;
}
+ bcount_t deleted_bytes = get_region_bytecount(curbuf, start, end, 0, 0);
+
// If the size of the range is reducing (ie, new_len < old_len) we
// need to delete some old_len. We do this at the start, by
// repeatedly deleting line "start".
@@ -460,6 +463,7 @@ void nvim_buf_set_lines(uint64_t channel_id,
// new old_len. This is a more efficient operation, as it requires
// less memory allocation and freeing.
size_t to_replace = old_len < new_len ? old_len : new_len;
+ bcount_t inserted_bytes = 0;
for (size_t i = 0; i < to_replace; i++) {
int64_t lnum = start + (int64_t)i;
@@ -472,6 +476,8 @@ void nvim_buf_set_lines(uint64_t channel_id,
api_set_error(err, kErrorTypeException, "Failed to replace line");
goto end;
}
+
+ inserted_bytes += (bcount_t)strlen(lines[i]) + 1;
// Mark lines that haven't been passed to the buffer as they need
// to be freed later
lines[i] = NULL;
@@ -491,6 +497,8 @@ void nvim_buf_set_lines(uint64_t channel_id,
goto end;
}
+ inserted_bytes += (bcount_t)strlen(lines[i]) + 1;
+
// Same as with replacing, but we also need to free lines
xfree(lines[i]);
lines[i] = NULL;
@@ -505,7 +513,11 @@ void nvim_buf_set_lines(uint64_t channel_id,
(linenr_T)(end - 1),
MAXLNUM,
(long)extra,
- kExtmarkUndo);
+ kExtmarkNOOP);
+
+ extmark_splice(curbuf, (int)start-1, 0, (int)(end-start), 0,
+ deleted_bytes, (int)new_len, 0, inserted_bytes,
+ kExtmarkUndo);
changed_lines((linenr_T)start, 0, (linenr_T)end, (long)extra, true);
fix_cursor((linenr_T)start, (linenr_T)end, (linenr_T)extra);
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 24ba6110c4..4b1c2d4baa 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -177,42 +177,47 @@ Object dict_get_value(dict_T *dict, String key, Error *err)
return vim_to_object(&di->di_tv);
}
-/// Set a value in a scope dict. Objects are recursively expanded into their
-/// vimscript equivalents.
-///
-/// @param dict The vimscript dict
-/// @param key The key
-/// @param value The new value
-/// @param del Delete key in place of setting it. Argument `value` is ignored in
-/// this case.
-/// @param retval If true the old value will be converted and returned.
-/// @param[out] err Details of an error that may have occurred
-/// @return The old value if `retval` is true and the key was present, else NIL
-Object dict_set_var(dict_T *dict, String key, Object value, bool del,
- bool retval, Error *err)
+dictitem_T *dict_check_writable(dict_T *dict, String key, bool del, Error *err)
{
- Object rv = OBJECT_INIT;
dictitem_T *di = tv_dict_find(dict, key.data, (ptrdiff_t)key.size);
if (di != NULL) {
if (di->di_flags & DI_FLAGS_RO) {
api_set_error(err, kErrorTypeException, "Key is read-only: %s", key.data);
- return rv;
} else if (di->di_flags & DI_FLAGS_LOCK) {
api_set_error(err, kErrorTypeException, "Key is locked: %s", key.data);
- return rv;
} else if (del && (di->di_flags & DI_FLAGS_FIX)) {
api_set_error(err, kErrorTypeException, "Key is fixed: %s", key.data);
- return rv;
}
} else if (dict->dv_lock) {
api_set_error(err, kErrorTypeException, "Dictionary is locked");
- return rv;
} else if (key.size == 0) {
api_set_error(err, kErrorTypeValidation, "Key name is empty");
- return rv;
} else if (key.size > INT_MAX) {
api_set_error(err, kErrorTypeValidation, "Key name is too long");
+ }
+
+ return di;
+}
+
+/// Set a value in a scope dict. Objects are recursively expanded into their
+/// vimscript equivalents.
+///
+/// @param dict The vimscript dict
+/// @param key The key
+/// @param value The new value
+/// @param del Delete key in place of setting it. Argument `value` is ignored in
+/// this case.
+/// @param retval If true the old value will be converted and returned.
+/// @param[out] err Details of an error that may have occurred
+/// @return The old value if `retval` is true and the key was present, else NIL
+Object dict_set_var(dict_T *dict, String key, Object value, bool del,
+ bool retval, Error *err)
+{
+ Object rv = OBJECT_INIT;
+ dictitem_T *di = dict_check_writable(dict, key, del, err);
+
+ if (ERROR_SET(err)) {
return rv;
}
@@ -1640,7 +1645,7 @@ bool api_object_to_bool(Object obj, const char *what,
} else if (obj.type == kObjectTypeNil) {
return nil_value; // caller decides what NIL (missing retval in lua) means
} else {
- api_set_error(err, kErrorTypeValidation, "%s is not an boolean", what);
+ api_set_error(err, kErrorTypeValidation, "%s is not a boolean", what);
return false;
}
}
@@ -1863,7 +1868,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
}
bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf,
- Error *err)
+ bool new_win, Error *err)
{
// TODO(bfredl): use a get/has_key interface instead and get rid of extra
// flags
@@ -1909,7 +1914,7 @@ bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf,
} else if (strequal(key, "height")) {
has_height = true;
if (val.type == kObjectTypeInteger && val.data.integer > 0) {
- fconfig->height= (int)val.data.integer;
+ fconfig->height = (int)val.data.integer;
} else {
api_set_error(err, kErrorTypeValidation,
"'height' key must be a positive Integer");
@@ -1963,24 +1968,23 @@ bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf,
}
has_bufpos = true;
} else if (!strcmp(key, "external")) {
- if (val.type == kObjectTypeInteger) {
- fconfig->external = val.data.integer;
- } else if (val.type == kObjectTypeBoolean) {
- fconfig->external = val.data.boolean;
- } else {
- api_set_error(err, kErrorTypeValidation,
- "'external' key must be Boolean");
+ has_external = fconfig->external
+ = api_object_to_bool(val, "'external' key", false, err);
+ if (ERROR_SET(err)) {
return false;
}
- has_external = fconfig->external;
} else if (!strcmp(key, "focusable")) {
- if (val.type == kObjectTypeInteger) {
- fconfig->focusable = val.data.integer;
- } else if (val.type == kObjectTypeBoolean) {
- fconfig->focusable = val.data.boolean;
+ fconfig->focusable
+ = api_object_to_bool(val, "'focusable' key", true, err);
+ if (ERROR_SET(err)) {
+ return false;
+ }
+ } else if (strequal(key, "zindex")) {
+ if (val.type == kObjectTypeInteger && val.data.integer > 0) {
+ fconfig->zindex = (int)val.data.integer;
} else {
api_set_error(err, kErrorTypeValidation,
- "'focusable' key must be Boolean");
+ "'zindex' key must be a positive Integer");
return false;
}
} else if (!strcmp(key, "border")) {
@@ -2002,6 +2006,12 @@ bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf,
api_set_error(err, kErrorTypeValidation,
"Invalid value of 'style' key");
}
+ } else if (strequal(key, "noautocmd") && new_win) {
+ fconfig->noautocmd
+ = api_object_to_bool(val, "'noautocmd' key", false, err);
+ if (ERROR_SET(err)) {
+ return false;
+ }
} else {
api_set_error(err, kErrorTypeValidation,
"Invalid key '%s'", key);
diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h
index e934d5dc92..11e21a88ea 100644
--- a/src/nvim/api/ui_events.in.h
+++ b/src/nvim/api/ui_events.in.h
@@ -106,7 +106,8 @@ void win_pos(Integer grid, Window win, Integer startrow,
Integer startcol, Integer width, Integer height)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
void win_float_pos(Integer grid, Window win, String anchor, Integer anchor_grid,
- Float anchor_row, Float anchor_col, Boolean focusable)
+ Float anchor_row, Float anchor_col, Boolean focusable,
+ Integer zindex)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
void win_external_pos(Integer grid, Window win)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index c363c77afb..60535b13b3 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -221,6 +221,12 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err)
/// in addition the following keys are also recognized:
/// `default`: don't override existing definition,
/// like `hi default`
+/// `ctermfg`: sets foreground of cterm color
+/// `ctermbg`: sets background of cterm color
+/// `cterm` : cterm attribute map. sets attributed for
+/// cterm colors. similer to `hi cterm`
+/// Note: by default cterm attributes are
+/// same as attributes of gui color
/// @param[out] err Error details, if any
///
/// TODO: ns_id = 0, should modify :highlight namespace
@@ -978,7 +984,7 @@ Dictionary nvim_get_all_options_info(Error *err)
/// Resulting dictionary has keys:
/// - name: Name of the option (like 'filetype')
/// - shortname: Shortened name of the option (like 'ft')
-/// - type: type of option ("string", "integer" or "boolean")
+/// - type: type of option ("string", "number" or "boolean")
/// - default: The default value for the option
/// - was_set: Whether the option was set.
///
@@ -1411,6 +1417,15 @@ void nvim_chan_send(Integer chan, String data, Error *err)
/// - `external`: GUI should display the window as an external
/// top-level window. Currently accepts no other positioning
/// configuration together with this.
+/// - `zindex`: Stacking order. floats with higher `zindex` go on top on
+/// floats with lower indices. Must be larger than zero. The
+/// following screen elements have hard-coded z-indices:
+/// - 100: insert completion popupmenu
+/// - 200: message scrollback
+/// - 250: cmdline completion popupmenu (when wildoptions+=pum)
+/// The default value for floats are 50. In general, values below 100 are
+/// recommended, unless there is a good reason to overshadow builtin
+/// elements.
/// - `style`: Configure the appearance of the window. Currently only takes
/// one non-empty value:
/// - "minimal" Nvim will display the window with many UI options
@@ -1444,6 +1459,9 @@ void nvim_chan_send(Integer chan, String data, Error *err)
/// By default `FloatBorder` highlight is used which links to `VertSplit`
/// when not defined. It could also be specified by character:
/// [ {"+", "MyCorner"}, {"x", "MyBorder"} ]
+/// - `noautocmd`: If true then no buffer-related autocommand events such as
+/// |BufEnter|, |BufLeave| or |BufWinEnter| may fire from
+/// calling this function.
///
/// @param[out] err Error details, if any
///
@@ -1454,7 +1472,7 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dictionary config,
FUNC_API_CHECK_TEXTLOCK
{
FloatConfig fconfig = FLOAT_CONFIG_INIT;
- if (!parse_float_config(config, &fconfig, false, err)) {
+ if (!parse_float_config(config, &fconfig, false, true, err)) {
return 0;
}
win_T *wp = win_new_float(NULL, fconfig, err);
@@ -1469,7 +1487,7 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dictionary config,
return 0;
}
if (buffer > 0) {
- nvim_win_set_buf(wp->handle, buffer, err);
+ win_set_buf(wp->handle, buffer, fconfig.noautocmd, err);
}
if (fconfig.style == kWinStyleMinimal) {
@@ -2936,6 +2954,7 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts,
FUNC_API_SINCE(7) FUNC_API_LUA_ONLY
{
DecorProvider *p = get_decor_provider((NS)ns_id, true);
+ assert(p != NULL);
decor_provider_clear(p);
// regardless of what happens, it seems good idea to redraw
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index 158e149628..a26213af98 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -46,35 +46,7 @@ void nvim_win_set_buf(Window window, Buffer buffer, Error *err)
FUNC_API_SINCE(5)
FUNC_API_CHECK_TEXTLOCK
{
- win_T *win = find_window_by_handle(window, err), *save_curwin = curwin;
- buf_T *buf = find_buffer_by_handle(buffer, err);
- tabpage_T *tab = win_find_tabpage(win), *save_curtab = curtab;
-
- if (!win || !buf) {
- return;
- }
-
- if (switch_win_noblock(&save_curwin, &save_curtab, win, tab, false) == FAIL) {
- api_set_error(err,
- kErrorTypeException,
- "Failed to switch to window %d",
- window);
- }
-
- try_start();
- int result = do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, buf->b_fnum, 0);
- if (!try_end(err) && result == FAIL) {
- api_set_error(err,
- kErrorTypeException,
- "Failed to set buffer %d",
- buffer);
- }
-
- // If window is not current, state logic will not validate its cursor.
- // So do it now.
- validate_cursor();
-
- restore_win_noblock(save_curwin, save_curtab, false);
+ win_set_buf(window, buffer, false, err);
}
/// Gets the (1,0)-indexed cursor position in the window. |api-indexing|
@@ -423,7 +395,7 @@ void nvim_win_set_config(Window window, Dictionary config, Error *err)
// reuse old values, if not overriden
FloatConfig fconfig = new_float ? FLOAT_CONFIG_INIT : win->w_float_config;
- if (!parse_float_config(config, &fconfig, !new_float, err)) {
+ if (!parse_float_config(config, &fconfig, !new_float, false, err)) {
return;
}
if (new_float) {
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index ce4163fccf..6a50264e0f 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -85,6 +85,9 @@
# include "buffer.c.generated.h"
#endif
+// Determines how deeply nested %{} blocks will be evaluated in statusline.
+#define MAX_STL_EVAL_DEPTH 100
+
static char *msg_loclist = N_("[Location List]");
static char *msg_qflist = N_("[Quickfix List]");
static char *e_auabort = N_("E855: Autocommands caused command to abort");
@@ -407,7 +410,8 @@ bool buf_valid(buf_T *buf)
/// there to be only one window with this buffer. e.g. when
/// ":quit" is supposed to close the window but autocommands
/// close all other windows.
-void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
+/// @returns true when we got to the end and b_nwindows was decremented.
+bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
{
bool unload_buf = (action != 0);
bool del_buf = (action == DOBUF_DEL || action == DOBUF_WIPE);
@@ -444,7 +448,7 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
// halfway a command that relies on it). Unloading is allowed.
if (buf->b_locked > 0 && (del_buf || wipe_buf)) {
EMSG(_("E937: Attempt to delete a buffer that is in use"));
- return;
+ return false;
}
if (win != NULL // Avoid bogus clang warning.
@@ -471,13 +475,13 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
buf) && !bufref_valid(&bufref)) {
// Autocommands deleted the buffer.
EMSG(_(e_auabort));
- return;
+ return false;
}
buf->b_locked--;
if (abort_if_last && last_nonfloat(win)) {
// Autocommands made this the only window.
EMSG(_(e_auabort));
- return;
+ return false;
}
// When the buffer becomes hidden, but is not unloaded, trigger
@@ -488,17 +492,17 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
buf) && !bufref_valid(&bufref)) {
// Autocommands deleted the buffer.
EMSG(_(e_auabort));
- return;
+ return false;
}
buf->b_locked--;
if (abort_if_last && last_nonfloat(win)) {
// Autocommands made this the only window.
EMSG(_(e_auabort));
- return;
+ return false;
}
}
if (aborting()) { // autocmds may abort script processing
- return;
+ return false;
}
}
@@ -525,7 +529,7 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
/* Return when a window is displaying the buffer or when it's not
* unloaded. */
if (buf->b_nwindows > 0 || !unload_buf) {
- return;
+ return false;
}
if (buf->terminal) {
@@ -561,11 +565,11 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
if (!bufref_valid(&bufref)) {
// Autocommands may have deleted the buffer.
- return;
+ return false;
}
if (aborting()) {
// Autocmds may abort script processing.
- return;
+ return false;
}
/*
@@ -576,7 +580,7 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
* deleted buffer.
*/
if (buf == curbuf && !is_curbuf) {
- return;
+ return false;
}
if (win != NULL // Avoid bogus clang warning.
@@ -636,6 +640,8 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
buf->b_p_bl = false;
}
}
+ // NOTE: at this point "curbuf" may be invalid!
+ return true;
}
/// Make buffer not contain a file.
@@ -3569,6 +3575,7 @@ int build_stl_str_hl(
}
int groupdepth = 0;
+ int evaldepth = 0;
int curitem = 0;
bool prevchar_isflag = true;
@@ -3906,6 +3913,13 @@ int build_stl_str_hl(
continue;
}
+ // Denotes end of expanded %{} block
+ if (*fmt_p == '}' && evaldepth > 0) {
+ fmt_p++;
+ evaldepth--;
+ continue;
+ }
+
// An invalid item was specified.
// Continue processing on the next character of the format string.
if (vim_strchr(STL_ALL, *fmt_p) == NULL) {
@@ -3947,18 +3961,30 @@ int build_stl_str_hl(
}
case STL_VIM_EXPR: // '{'
{
+ char_u *block_start = fmt_p - 1;
+ int reevaluate = (*fmt_p == '%');
itemisflag = true;
+ if (reevaluate) {
+ fmt_p++;
+ }
+
// Attempt to copy the expression to evaluate into
// the output buffer as a null-terminated string.
char_u *t = out_p;
- while (*fmt_p != '}' && *fmt_p != NUL && out_p < out_end_p)
+ while ((*fmt_p != '}' || (reevaluate && fmt_p[-1] != '%'))
+ && *fmt_p != NUL && out_p < out_end_p) {
*out_p++ = *fmt_p++;
+ }
if (*fmt_p != '}') { // missing '}' or out of space
break;
}
fmt_p++;
- *out_p = 0;
+ if (reevaluate) {
+ out_p[-1] = 0; // remove the % at the end of %{% expr %}
+ } else {
+ *out_p = 0;
+ }
// Move our position in the output buffer
// to the beginning of the expression
@@ -4004,6 +4030,40 @@ int build_stl_str_hl(
itemisflag = false;
}
}
+
+
+ // If the output of the expression needs to be evaluated
+ // replace the %{} block with the result of evaluation
+ if (reevaluate && str != NULL && *str != 0
+ && strchr((const char *)str, '%') != NULL
+ && evaldepth < MAX_STL_EVAL_DEPTH) {
+ size_t parsed_usefmt = (size_t)(block_start - usefmt);
+ size_t str_length = strlen((const char *)str);
+ size_t fmt_length = strlen((const char *)fmt_p);
+ size_t new_fmt_len = parsed_usefmt
+ + str_length + fmt_length + 3;
+ char_u *new_fmt = (char_u *)xmalloc(new_fmt_len * sizeof(char_u));
+ char_u *new_fmt_p = new_fmt;
+
+ new_fmt_p = (char_u *)memcpy(new_fmt_p, usefmt, parsed_usefmt)
+ + parsed_usefmt;
+ new_fmt_p = (char_u *)memcpy(new_fmt_p , str, str_length)
+ + str_length;
+ new_fmt_p = (char_u *)memcpy(new_fmt_p, "%}", 2) + 2;
+ new_fmt_p = (char_u *)memcpy(new_fmt_p , fmt_p, fmt_length)
+ + fmt_length;
+ *new_fmt_p = 0;
+ new_fmt_p = NULL;
+
+ if (usefmt != fmt) {
+ xfree(usefmt);
+ }
+ XFREE_CLEAR(str);
+ usefmt = new_fmt;
+ fmt_p = usefmt + parsed_usefmt;
+ evaldepth++;
+ continue;
+ }
break;
}
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index dd24db910e..e3e538bd12 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -1083,12 +1083,14 @@ typedef struct {
FloatRelative relative;
bool external;
bool focusable;
+ int zindex;
WinStyle style;
bool border;
bool shadow;
schar_T border_chars[8];
int border_hl_ids[8];
int border_attr[8];
+ bool noautocmd;
} FloatConfig;
#define FLOAT_CONFIG_INIT ((FloatConfig){ .height = 0, .width = 0, \
@@ -1096,7 +1098,9 @@ typedef struct {
.row = 0, .col = 0, .anchor = 0, \
.relative = 0, .external = false, \
.focusable = true, \
- .style = kWinStyleUnused })
+ .zindex = kZIndexFloatDefault, \
+ .style = kWinStyleUnused, \
+ .noautocmd = false })
// Structure to store last cursor position and topline. Used by check_lnums()
// and reset_lnums().
diff --git a/src/nvim/change.c b/src/nvim/change.c
index 74e27ca880..c0183d4317 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -1695,10 +1695,12 @@ int open_line(
int new_len = (int)STRLEN(saved_line);
// TODO(vigoux): maybe there is issues there with expandtabs ?
+ int cols_spliced = 0;
if (new_len < curwin->w_cursor.col) {
extmark_splice_cols(
- curbuf, (int)curwin->w_cursor.lnum,
+ curbuf, (int)curwin->w_cursor.lnum - 1,
new_len, curwin->w_cursor.col - new_len, 0, kExtmarkUndo);
+ cols_spliced = curwin->w_cursor.col - new_len;
}
saved_line = NULL;
@@ -1716,7 +1718,7 @@ int open_line(
// Always move extmarks - Here we move only the line where the
// cursor is, the previous mark_adjust takes care of the lines after
int cols_added = mincol-1+less_cols_off-less_cols;
- extmark_splice(curbuf, (int)lnum-1, mincol-1,
+ extmark_splice(curbuf, (int)lnum-1, mincol-1 - cols_spliced,
0, less_cols_off, less_cols_off,
1, cols_added, 1 + cols_added, kExtmarkUndo);
} else {
diff --git a/src/nvim/channel.c b/src/nvim/channel.c
index 22eb31513d..60af11e94b 100644
--- a/src/nvim/channel.c
+++ b/src/nvim/channel.c
@@ -162,6 +162,7 @@ void channel_init(void)
/// Channel is allocated with refcount 1, which should be decreased
/// when the underlying stream closes.
Channel *channel_alloc(ChannelStreamType type)
+ FUNC_ATTR_NONNULL_RET
{
Channel *chan = xcalloc(1, sizeof(*chan));
if (type == kChannelStreamStdio) {
diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c
index 74a6f77a6d..5d2210dc7d 100644
--- a/src/nvim/cursor.c
+++ b/src/nvim/cursor.c
@@ -14,6 +14,7 @@
#include "nvim/misc1.h"
#include "nvim/move.h"
#include "nvim/screen.h"
+#include "nvim/extmark.h"
#include "nvim/state.h"
#include "nvim/vim.h"
#include "nvim/ascii.h"
@@ -181,7 +182,7 @@ static int coladvance2(
memset(newline + idx, ' ', (size_t)correct);
ml_replace(pos->lnum, newline, false);
- changed_bytes(pos->lnum, (colnr_T)idx);
+ inserted_bytes(pos->lnum, (colnr_T)idx, 0, correct);
idx += correct;
col = wcol;
} else {
@@ -206,7 +207,7 @@ static int coladvance2(
memcpy(newline + idx + csize, line + idx + 1, n);
ml_replace(pos->lnum, newline, false);
- changed_bytes(pos->lnum, idx);
+ inserted_bytes(pos->lnum, idx, 1, csize);
idx += (csize - 1 + correct);
col += correct;
}
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index 31b7b1bd8f..5f8b81822b 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -982,12 +982,14 @@ static int check_external_diff(diffio_T *diffio)
char_u linebuf[LBUFLEN];
for (;;) {
- // There must be a line that contains "1c1".
+ // For normal diff there must be a line that contains
+ // "1c1". For unified diff "@@ -1 +1 @@".
if (vim_fgets(linebuf, LBUFLEN, fd)) {
break;
}
- if (STRNCMP(linebuf, "1c1", 3) == 0) {
+ if (STRNCMP(linebuf, "1c1", 3) == 0
+ || STRNCMP(linebuf, "@@ -1 +1 @@", 11) == 0) {
ok = kTrue;
}
}
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 56b563cba0..1579f3ff98 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -3150,9 +3150,7 @@ static void ins_compl_clear(void)
XFREE_CLEAR(compl_orig_text);
compl_enter_selects = false;
// clear v:completed_item
- dict_T *const d = tv_dict_alloc();
- d->dv_lock = VAR_FIXED;
- set_vim_var_dict(VV_COMPLETED_ITEM, d);
+ set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc_lock(VAR_FIXED));
}
/// Check that Insert completion is active.
@@ -4497,9 +4495,7 @@ static void ins_compl_delete(void)
// causes flicker, thus we can't do that.
changed_cline_bef_curs();
// clear v:completed_item
- dict_T *const d = tv_dict_alloc();
- d->dv_lock = VAR_FIXED;
- set_vim_var_dict(VV_COMPLETED_ITEM, d);
+ set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc_lock(VAR_FIXED));
}
// Insert the new text being completed.
@@ -4520,8 +4516,7 @@ static void ins_compl_insert(int in_compl_func)
static dict_T *ins_compl_dict_alloc(compl_T *match)
{
// { word, abbr, menu, kind, info }
- dict_T *dict = tv_dict_alloc();
- dict->dv_lock = VAR_FIXED;
+ dict_T *dict = tv_dict_alloc_lock(VAR_FIXED);
tv_dict_add_str(
dict, S_LEN("word"),
(const char *)EMPTY_IF_NULL(match->cp_str));
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 079c0dc3c0..a3fa9c986f 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -228,6 +228,7 @@ static struct vimvar {
VV(VV_EVENT, "event", VAR_DICT, VV_RO),
VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO),
VV(VV_ARGV, "argv", VAR_LIST, VV_RO),
+ VV(VV_COLLATE, "collate", VAR_STRING, VV_RO),
VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO),
// Neovim
VV(VV_STDERR, "stderr", VAR_NUMBER, VV_RO),
@@ -376,11 +377,9 @@ void eval_init(void)
msgpack_types_dict->dv_lock = VAR_FIXED;
set_vim_var_dict(VV_MSGPACK_TYPES, msgpack_types_dict);
- set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc());
+ set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc_lock(VAR_FIXED));
- dict_T *v_event = tv_dict_alloc();
- v_event->dv_lock = VAR_FIXED;
- set_vim_var_dict(VV_EVENT, v_event);
+ set_vim_var_dict(VV_EVENT, tv_dict_alloc_lock(VAR_FIXED));
set_vim_var_list(VV_ERRORS, tv_list_alloc(kListLenUnknown));
set_vim_var_nr(VV_STDERR, CHAN_STDERR);
set_vim_var_nr(VV_SEARCHFORWARD, 1L);
@@ -1620,7 +1619,7 @@ void list_hashtable_vars(hashtab_T *ht, const char *prefix, int empty,
char buf[IOSIZE];
// apply :filter /pat/ to variable name
- xstrlcpy(buf, prefix, IOSIZE - 1);
+ xstrlcpy(buf, prefix, IOSIZE);
xstrlcat(buf, (char *)di->di_key, IOSIZE);
if (message_filtered((char_u *)buf)) {
continue;
@@ -6327,7 +6326,7 @@ void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg,
if (what_arg->v_type == VAR_UNKNOWN) {
tv_list_alloc_ret(rettv, kListLenMayKnow);
if (is_qf || wp != NULL) {
- (void)get_errorlist(NULL, wp, -1, rettv->vval.v_list);
+ (void)get_errorlist(NULL, wp, -1, 0, rettv->vval.v_list);
}
} else {
tv_dict_alloc_ret(rettv);
@@ -7201,9 +7200,13 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg)
r = FAIL;
} else if (arg->v_type == VAR_FUNC || arg->v_type == VAR_STRING) {
char_u *name = arg->vval.v_string;
- func_ref(name);
- callback->data.funcref = vim_strsave(name);
- callback->type = kCallbackFuncref;
+ if (name != NULL) {
+ func_ref(name);
+ callback->data.funcref = vim_strsave(name);
+ callback->type = kCallbackFuncref;
+ } else {
+ r = FAIL;
+ }
} else if (nlua_is_table_from_lua(arg)) {
char_u *name = nlua_register_table_as_callable(arg);
@@ -7616,7 +7619,7 @@ char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl)
/// @param[out] ret_fnum Set to fnum for marks.
///
/// @return Pointer to position or NULL in case of error (e.g. invalid type).
-pos_T *var2fpos(const typval_T *const tv, const int dollar_lnum,
+pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum,
int *const ret_fnum)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index 8188502987..41120b3c78 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -157,6 +157,7 @@ typedef enum {
VV_EVENT,
VV_ECHOSPACE,
VV_ARGV,
+ VV_COLLATE,
VV_EXITING,
// Neovim
VV_STDERR,
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 148804e54c..33c6fae5cf 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -217,7 +217,7 @@ return {
len={args=1},
libcall={args=3},
libcallnr={args=3},
- line={args=1},
+ line={args={1, 2}},
line2byte={args=1},
lispindent={args=1},
list2str={args={1, 2}},
@@ -286,6 +286,7 @@ return {
screenpos={args=3},
screenrow={},
search={args={1, 4}},
+ searchcount={args={0,1}},
searchdecl={args={1, 3}},
searchpair={args={3, 7}},
searchpairpos={args={3, 7}},
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 865abcc110..ce09d268ea 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -1850,15 +1850,30 @@ static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr)
ptrdiff_t len = end - str;
assert(len > 0);
const char * value = str + len + 1;
- if (tv_dict_find(rettv->vval.v_dict, str, len) != NULL) {
+
+ char c = env[i][len];
+ env[i][len] = NUL;
+
+#ifdef WIN32
+ // Upper-case all the keys for Windows so we can detect duplicates
+ char *const key = strcase_save(str, true);
+#else
+ char *const key = xstrdup(str);
+#endif
+
+ env[i][len] = c;
+
+ if (tv_dict_find(rettv->vval.v_dict, key, len) != NULL) {
// Since we're traversing from the end of the env block to the front, any
// duplicate names encountered should be ignored. This preserves the
// semantics of env vars defined later in the env block taking precedence.
+ xfree(key);
continue;
}
tv_dict_add_str(rettv->vval.v_dict,
- str, len,
+ key, len,
value);
+ xfree(key);
}
os_free_fullenv(env);
}
@@ -2532,7 +2547,7 @@ static void f_fnamemodify(typval_T *argvars, typval_T *rettv, FunPtr fptr)
} else {
len = strlen(fname);
size_t usedlen = 0;
- if (mods != NULL && *mods != NUL) {
+ if (*mods != NUL) {
(void)modify_fname((char_u *)mods, false, &usedlen,
(char_u **)&fname, &fbuf, &len);
}
@@ -2770,10 +2785,9 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
} else if (strcmp(what, "args") == 0) {
rettv->v_type = VAR_LIST;
- if (tv_list_alloc_ret(rettv, pt->pt_argc) != NULL) {
- for (int i = 0; i < pt->pt_argc; i++) {
- tv_list_append_tv(rettv->vval.v_list, &pt->pt_argv[i]);
- }
+ tv_list_alloc_ret(rettv, pt->pt_argc);
+ for (int i = 0; i < pt->pt_argc; i++) {
+ tv_list_append_tv(rettv->vval.v_list, &pt->pt_argv[i]);
}
} else {
EMSG2(_(e_invarg2), what);
@@ -5097,7 +5111,21 @@ static dict_T *create_environment(const dictitem_T *job_env,
}
if (job_env) {
+#ifdef WIN32
+ TV_DICT_ITER(job_env->di_tv.vval.v_dict, var, {
+ // Always use upper-case keys for Windows so we detect duplicate keys
+ char *const key = strcase_save((const char *)var->di_key, true);
+ size_t len = strlen(key);
+ dictitem_T *dv = tv_dict_find(env, key, len);
+ if (dv) {
+ tv_dict_item_remove(env, dv);
+ }
+ tv_dict_add_str(env, key, len, tv_get_string(&var->di_tv));
+ xfree(key);
+ });
+#else
tv_dict_extend(env, job_env->di_tv.vval.v_dict, "force");
+#endif
}
if (pty) {
@@ -5540,18 +5568,36 @@ static void f_libcallnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
libcall_common(argvars, rettv, VAR_NUMBER);
}
-/*
- * "line(string)" function
- */
+// "line(string, [winid])" function
static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
linenr_T lnum = 0;
- pos_T *fp;
+ pos_T *fp = NULL;
int fnum;
- fp = var2fpos(&argvars[0], TRUE, &fnum);
- if (fp != NULL)
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ tabpage_T *tp;
+ win_T *save_curwin;
+ tabpage_T *save_curtab;
+
+ // use window specified in the second argument
+ win_T *wp = win_id2wp_tp(&argvars[1], &tp);
+ if (wp != NULL && tp != NULL) {
+ if (switch_win_noblock(&save_curwin, &save_curtab, wp, tp, true)
+ == OK) {
+ check_cursor();
+ fp = var2fpos(&argvars[0], true, &fnum);
+ }
+ restore_win_noblock(save_curwin, save_curtab, true);
+ }
+ } else {
+ // use current window
+ fp = var2fpos(&argvars[0], true, &fnum);
+ }
+
+ if (fp != NULL) {
lnum = fp->lnum;
+ }
rettv->vval.v_number = lnum;
}
@@ -6661,6 +6707,37 @@ static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
+// Evaluate "expr" for readdir().
+static varnumber_T readdir_checkitem(typval_T *expr, const char *name)
+{
+ typval_T save_val;
+ typval_T rettv;
+ typval_T argv[2];
+ varnumber_T retval = 0;
+ bool error = false;
+
+ prepare_vimvar(VV_VAL, &save_val);
+ set_vim_var_string(VV_VAL, name, -1);
+ argv[0].v_type = VAR_STRING;
+ argv[0].vval.v_string = (char_u *)name;
+
+ if (eval_expr_typval(expr, argv, 1, &rettv) == FAIL) {
+ goto theend;
+ }
+
+ retval = tv_get_number_chk(&rettv, &error);
+ if (error) {
+ retval = -1;
+ }
+
+ tv_clear(&rettv);
+
+theend:
+ set_vim_var_string(VV_VAL, NULL, 0);
+ restore_vimvar(VV_VAL, &save_val);
+ return retval;
+}
+
// "readdir()" function
static void f_readdir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
@@ -6672,14 +6749,43 @@ static void f_readdir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
tv_list_alloc_ret(rettv, kListLenUnknown);
path = tv_get_string(&argvars[0]);
expr = &argvars[1];
+ ga_init(&ga, (int)sizeof(char *), 20);
if (!os_scandir(&dir, path)) {
smsg(_(e_notopen), path);
} else {
- readdir_core(&ga, &dir, expr, true);
+ for (;;) {
+ bool ignore;
+
+ path = os_scandir_next(&dir);
+ if (path == NULL) {
+ break;
+ }
+
+ ignore = (path[0] == '.'
+ && (path[1] == NUL || (path[1] == '.' && path[2] == NUL)));
+ if (!ignore && expr->v_type != VAR_UNKNOWN) {
+ varnumber_T r = readdir_checkitem(expr, path);
+
+ if (r < 0) {
+ break;
+ }
+ if (r == 0) {
+ ignore = true;
+ }
+ }
+
+ if (!ignore) {
+ ga_grow(&ga, 1);
+ ((char **)ga.ga_data)[ga.ga_len++] = xstrdup(path);
+ }
+ }
+
+ os_closedir(&dir);
}
if (rettv->vval.v_list != NULL && ga.ga_len > 0) {
+ sort_strings((char_u **)ga.ga_data, ga.ga_len);
for (int i = 0; i < ga.ga_len; i++) {
path = ((const char **)ga.ga_data)[i];
tv_list_append_string(rettv->vval.v_list, path, -1);
@@ -9154,6 +9260,7 @@ static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// struct storing information about current sort
typedef struct {
int item_compare_ic;
+ bool item_compare_lc;
bool item_compare_numeric;
bool item_compare_numbers;
bool item_compare_float;
@@ -9228,10 +9335,10 @@ static int item_compare(const void *s1, const void *s2, bool keep_zero)
p2 = "";
}
if (!sortinfo->item_compare_numeric) {
- if (sortinfo->item_compare_ic) {
- res = STRICMP(p1, p2);
+ if (sortinfo->item_compare_lc) {
+ res = strcoll(p1, p2);
} else {
- res = STRCMP(p1, p2);
+ res = sortinfo->item_compare_ic ? STRICMP(p1, p2): STRCMP(p1, p2);
}
} else {
double n1, n2;
@@ -9366,6 +9473,7 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort)
}
info.item_compare_ic = false;
+ info.item_compare_lc = false;
info.item_compare_numeric = false;
info.item_compare_numbers = false;
info.item_compare_float = false;
@@ -9410,6 +9518,9 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort)
} else if (strcmp(info.item_compare_func, "i") == 0) {
info.item_compare_func = NULL;
info.item_compare_ic = true;
+ } else if (strcmp(info.item_compare_func, "l") == 0) {
+ info.item_compare_func = NULL;
+ info.item_compare_lc = true;
}
}
}
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index 71e4edc667..61de83fc21 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -2098,7 +2098,7 @@ void tv_dict_set_keys_readonly(dict_T *const dict)
///
/// @return [allocated] pointer to the created list.
list_T *tv_list_alloc_ret(typval_T *const ret_tv, const ptrdiff_t len)
- FUNC_ATTR_NONNULL_ALL
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
list_T *const l = tv_list_alloc(len);
tv_list_set_ret(ret_tv, l);
@@ -2106,6 +2106,14 @@ list_T *tv_list_alloc_ret(typval_T *const ret_tv, const ptrdiff_t len)
return l;
}
+dict_T *tv_dict_alloc_lock(VarLockStatus lock)
+ FUNC_ATTR_NONNULL_RET
+{
+ dict_T *const d = tv_dict_alloc();
+ d->dv_lock = lock;
+ return d;
+}
+
/// Allocate an empty dictionary for a return value
///
/// Also sets reference count.
@@ -2114,9 +2122,8 @@ list_T *tv_list_alloc_ret(typval_T *const ret_tv, const ptrdiff_t len)
void tv_dict_alloc_ret(typval_T *const ret_tv)
FUNC_ATTR_NONNULL_ALL
{
- dict_T *const d = tv_dict_alloc();
+ dict_T *const d = tv_dict_alloc_lock(VAR_UNLOCKED);
tv_dict_set_ret(ret_tv, d);
- ret_tv->v_lock = VAR_UNLOCKED;
}
//{{{3 Clear
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index dc7027980e..f5d1b1e870 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -144,10 +144,6 @@ static int get_function_args(char_u **argp, char_u endchar, garray_T *newargs,
c = *p;
*p = NUL;
expr = vim_strsave(expr);
- if (expr == NULL) {
- *p = c;
- goto err_ret;
- }
((char_u **)(default_args->ga_data))
[default_args->ga_len] = expr;
default_args->ga_len++;
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 3e330b88a2..6a0a08eee8 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -358,6 +358,7 @@ static int linelen(int *has_tab)
static char_u *sortbuf1;
static char_u *sortbuf2;
+static int sort_lc; ///< sort using locale
static int sort_ic; ///< ignore case
static int sort_nr; ///< sort on number
static int sort_rx; ///< sort on regex instead of skipping it
@@ -381,6 +382,13 @@ typedef struct {
} st_u;
} sorti_T;
+static int string_compare(const void *s1, const void *s2) FUNC_ATTR_NONNULL_ALL
+{
+ if (sort_lc) {
+ return strcoll((char *)s1, (char *)s2);
+ }
+ return sort_ic ? STRICMP(s1, s2) : STRCMP(s1, s2);
+}
static int sort_compare(const void *s1, const void *s2)
{
@@ -424,8 +432,7 @@ static int sort_compare(const void *s1, const void *s2)
l2.st_u.line.end_col_nr - l2.st_u.line.start_col_nr + 1);
sortbuf2[l2.st_u.line.end_col_nr - l2.st_u.line.start_col_nr] = NUL;
- result = sort_ic ? STRICMP(sortbuf1, sortbuf2)
- : STRCMP(sortbuf1, sortbuf2);
+ result = string_compare(sortbuf1, sortbuf2);
}
/* If two lines have the same value, preserve the original line order. */
@@ -466,7 +473,7 @@ void ex_sort(exarg_T *eap)
regmatch.regprog = NULL;
sorti_T *nrs = xmalloc(count * sizeof(sorti_T));
- sort_abort = sort_ic = sort_rx = sort_nr = sort_flt = 0;
+ sort_abort = sort_ic = sort_lc = sort_rx = sort_nr = sort_flt = 0;
size_t format_found = 0;
bool change_occurred = false; // Buffer contents changed.
@@ -474,6 +481,8 @@ void ex_sort(exarg_T *eap)
if (ascii_iswhite(*p)) {
} else if (*p == 'i') {
sort_ic = true;
+ } else if (*p == 'l') {
+ sort_lc = true;
} else if (*p == 'r') {
sort_rx = true;
} else if (*p == 'n') {
@@ -645,8 +654,7 @@ void ex_sort(exarg_T *eap)
s = ml_get(get_lnum);
size_t bytelen = STRLEN(s) + 1; // include EOL in bytelen
old_count += bytelen;
- if (!unique || i == 0
- || (sort_ic ? STRICMP(s, sortbuf1) : STRCMP(s, sortbuf1)) != 0) {
+ if (!unique || i == 0 || string_compare(s, sortbuf1) != 0) {
// Copy the line into a buffer, it may become invalid in
// ml_append(). And it's needed for "unique".
STRCPY(sortbuf1, s);
@@ -2426,21 +2434,25 @@ int do_ecmd(
* is returned by buflist_new(), nothing to do here.
*/
if (buf != curbuf) {
- /*
- * Be careful: The autocommands may delete any buffer and change
- * the current buffer.
- * - If the buffer we are going to edit is deleted, give up.
- * - If the current buffer is deleted, prefer to load the new
- * buffer when loading a buffer is required. This avoids
- * loading another buffer which then must be closed again.
- * - If we ended up in the new buffer already, need to skip a few
- * things, set auto_buf.
- */
+ const int save_cmdwin_type = cmdwin_type;
+
+ // BufLeave applies to the old buffer.
+ cmdwin_type = 0;
+
+ // Be careful: The autocommands may delete any buffer and change
+ // the current buffer.
+ // - If the buffer we are going to edit is deleted, give up.
+ // - If the current buffer is deleted, prefer to load the new
+ // buffer when loading a buffer is required. This avoids
+ // loading another buffer which then must be closed again.
+ // - If we ended up in the new buffer already, need to skip a few
+ // things, set auto_buf.
if (buf->b_fname != NULL) {
new_name = vim_strsave(buf->b_fname);
}
set_bufref(&au_new_curbuf, buf);
apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, false, curbuf);
+ cmdwin_type = save_cmdwin_type;
if (!bufref_valid(&au_new_curbuf)) {
// New buffer has been deleted.
delbuf_msg(new_name); // Frees new_name.
@@ -2454,6 +2466,7 @@ int do_ecmd(
auto_buf = true;
} else {
win_T *the_curwin = curwin;
+ buf_T *was_curbuf = curbuf;
// Set w_closing to avoid that autocommands close the window.
// Set b_locked for the same reason.
@@ -2467,9 +2480,10 @@ int do_ecmd(
// Close the link to the current buffer. This will set
// oldwin->w_buffer to NULL.
u_sync(false);
- close_buffer(oldwin, curbuf,
- (flags & ECMD_HIDE) || curbuf->terminal ? 0 : DOBUF_UNLOAD,
- false);
+ const bool did_decrement = close_buffer(
+ oldwin, curbuf,
+ (flags & ECMD_HIDE) || curbuf->terminal ? 0 : DOBUF_UNLOAD,
+ false);
// Autocommands may have closed the window.
if (win_valid(the_curwin)) {
@@ -2489,6 +2503,14 @@ int do_ecmd(
goto theend;
}
if (buf == curbuf) { // already in new buffer
+ // close_buffer() has decremented the window count,
+ // increment it again here and restore w_buffer.
+ if (did_decrement && buf_valid(was_curbuf)) {
+ was_curbuf->b_nwindows++;
+ }
+ if (win_valid_any_tab(oldwin) && oldwin->w_buffer == NULL) {
+ oldwin->w_buffer = was_curbuf;
+ }
auto_buf = true;
} else {
// <VN> We could instead free the synblock
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index 7f28c001f9..56a14887df 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -2958,7 +2958,7 @@ int do_source(char_u *fname, int check_other, int is_vimrc)
}
if (l_do_profiling == PROF_YES) {
- bool forceit;
+ bool forceit = false;
// Check if we do profiling for this script.
if (!si->sn_prof_on && has_profiling(true, si->sn_name, &forceit)) {
@@ -3621,6 +3621,14 @@ void set_lang_var(void)
loc = get_locale_val(LC_TIME);
# endif
set_vim_var_string(VV_LC_TIME, loc, -1);
+
+# ifdef HAVE_GET_LOCALE_VAL
+ loc = get_locale_val(LC_COLLATE);
+# else
+ // setlocale() not supported: use the default value
+ loc = "C";
+# endif
+ set_vim_var_string(VV_COLLATE, loc, -1);
}
#ifdef HAVE_WORKING_LIBINTL
@@ -3661,6 +3669,10 @@ void ex_language(exarg_T *eap)
what = LC_TIME;
name = skipwhite(p);
whatstr = "time ";
+ } else if (STRNICMP(eap->arg, "collate", p - eap->arg) == 0) {
+ what = LC_COLLATE;
+ name = skipwhite(p);
+ whatstr = "collate ";
}
}
@@ -3705,7 +3717,7 @@ void ex_language(exarg_T *eap)
// Reset $LC_ALL, otherwise it would overrule everything.
os_setenv("LC_ALL", "", 1);
- if (what != LC_TIME) {
+ if (what != LC_TIME && what != LC_COLLATE) {
// Tell gettext() what to translate to. It apparently doesn't
// use the currently effective locale.
if (what == LC_ALL) {
@@ -3720,7 +3732,7 @@ void ex_language(exarg_T *eap)
}
}
- // Set v:lang, v:lc_time and v:ctype to the final result.
+ // Set v:lang, v:lc_time, v:collate and v:ctype to the final result.
set_lang_var();
maketitle();
}
@@ -3805,12 +3817,15 @@ char_u *get_lang_arg(expand_T *xp, int idx)
if (idx == 2) {
return (char_u *)"time";
}
+ if (idx == 3) {
+ return (char_u *)"collate";
+ }
init_locales();
if (locales == NULL) {
return NULL;
}
- return locales[idx - 3];
+ return locales[idx - 4];
}
/// Function given to ExpandGeneric() to obtain the available locales.
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index ae5c334592..29347def4c 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -212,7 +212,7 @@ void do_exmode(int improved)
while (exmode_active) {
/* Check for a ":normal" command and no more characters left. */
if (ex_normal_busy > 0 && typebuf.tb_len == 0) {
- exmode_active = FALSE;
+ exmode_active = 0;
break;
}
msg_scroll = true;
@@ -1772,7 +1772,7 @@ static char_u * do_one_cmd(char_u **cmdlinep,
// count, it's a buffer name.
///
if ((ea.argt & EX_COUNT) && ascii_isdigit(*ea.arg)
- && (!(ea.argt & EX_BUFNAME) || *(p = skipdigits(ea.arg)) == NUL
+ && (!(ea.argt & EX_BUFNAME) || *(p = skipdigits(ea.arg + 1)) == NUL
|| ascii_iswhite(*p))) {
n = getdigits_long(&ea.arg, false, -1);
ea.arg = skipwhite(ea.arg);
@@ -2790,15 +2790,18 @@ static struct cmdmod {
*/
int modifier_len(char_u *cmd)
{
- int i, j;
char_u *p = cmd;
- if (ascii_isdigit(*cmd))
- p = skipwhite(skipdigits(cmd));
- for (i = 0; i < (int)ARRAY_SIZE(cmdmods); ++i) {
- for (j = 0; p[j] != NUL; ++j)
- if (p[j] != cmdmods[i].name[j])
+ if (ascii_isdigit(*cmd)) {
+ p = skipwhite(skipdigits(cmd + 1));
+ }
+ for (int i = 0; i < (int)ARRAY_SIZE(cmdmods); i++) {
+ int j;
+ for (j = 0; p[j] != NUL; j++) {
+ if (p[j] != cmdmods[i].name[j]) {
break;
+ }
+ }
if (j >= cmdmods[i].minlen
&& !ASCII_ISALPHA(p[j])
&& (p == cmd || cmdmods[i].has_count)) {
@@ -3642,7 +3645,8 @@ const char * set_one_cmd_context(
} else {
if (strncmp(arg, "messages", p - arg) == 0
|| strncmp(arg, "ctype", p - arg) == 0
- || strncmp(arg, "time", p - arg) == 0) {
+ || strncmp(arg, "time", p - arg) == 0
+ || strncmp(arg, "collate", p - arg) == 0) {
xp->xp_context = EXPAND_LOCALES;
xp->xp_pattern = skipwhite((const char_u *)p);
} else {
@@ -5013,7 +5017,7 @@ char_u *check_nextcmd(char_u *p)
static int
check_more(
int message, // when FALSE check only, no messages
- int forceit
+ bool forceit
)
{
int n = ARGCOUNT - curwin->w_arg_idx - 1;
@@ -6336,7 +6340,7 @@ void not_exiting(void)
exiting = false;
}
-static bool before_quit_autocmds(win_T *wp, bool quit_all, int forceit)
+bool before_quit_autocmds(win_T *wp, bool quit_all, bool forceit)
{
apply_autocmds(EVENT_QUITPRE, NULL, NULL, false, wp->w_buffer);
@@ -6402,7 +6406,7 @@ static void ex_quit(exarg_T *eap)
return;
}
- // If there are more files or windows we won't exit.
+ // If there is only one relevant window we will exit.
if (check_more(false, eap->forceit) == OK && only_one_window()) {
exiting = true;
}
@@ -6519,6 +6523,12 @@ ex_win_close(
int need_hide;
buf_T *buf = win->w_buffer;
+ // Never close the autocommand window.
+ if (win == aucmd_win) {
+ EMSG(_(e_autocmd_close));
+ return;
+ }
+
need_hide = (bufIsChanged(buf) && buf->b_nwindows <= 1);
if (need_hide && !buf_hide(buf) && !forceit) {
if ((p_confirm || cmdmod.confirm) && p_write) {
@@ -6588,9 +6598,6 @@ static void ex_tabonly(exarg_T *eap)
// Repeat this up to a 1000 times, because autocommands may
// mess up the lists.
for (int done = 0; done < 1000; done++) {
- FOR_ALL_TAB_WINDOWS(tp, wp) {
- assert(wp != aucmd_win);
- }
FOR_ALL_TABS(tp) {
if (tp->tp_topframe != topframe) {
tabpage_close_other(tp, eap->forceit);
@@ -6742,7 +6749,7 @@ static void ex_stop(exarg_T *eap)
apply_autocmds(EVENT_VIMRESUME, NULL, NULL, false, NULL);
}
-// ":exit", ":xit" and ":wq": Write file and quite the current window.
+// ":exit", ":xit" and ":wq": Write file and quit the current window.
static void ex_exit(exarg_T *eap)
{
if (cmdwin_type != 0) {
@@ -6755,17 +6762,15 @@ static void ex_exit(exarg_T *eap)
return;
}
- if (before_quit_autocmds(curwin, false, eap->forceit)) {
- return;
- }
-
- // if more files or windows we won't exit
+ // we plan to exit if there is only one relevant window
if (check_more(false, eap->forceit) == OK && only_one_window()) {
exiting = true;
}
- if (((eap->cmdidx == CMD_wq
- || curbufIsChanged())
- && do_write(eap) == FAIL)
+ // Write the buffer for ":wq" or when it was changed.
+ // Trigger QuitPre and ExitPre.
+ // Check if we can exit now, after autocommands have changed things.
+ if (((eap->cmdidx == CMD_wq || curbufIsChanged()) && do_write(eap) == FAIL)
+ || before_quit_autocmds(curwin, false, eap->forceit)
|| check_more(true, eap->forceit) == FAIL
|| (only_one_window() && check_changed_any(eap->forceit, false))) {
not_exiting();
@@ -7303,7 +7308,8 @@ do_exedit(
*/
if (exmode_active && (eap->cmdidx == CMD_visual
|| eap->cmdidx == CMD_view)) {
- exmode_active = FALSE;
+ exmode_active = 0;
+ ex_pressedreturn = false;
if (*eap->arg == NUL) {
/* Special case: ":global/pat/visual\NLvi-commands" */
if (global_busy) {
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 53571ec8da..75ed5dc0e5 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -3571,6 +3571,7 @@ static void save_cmdline(struct cmdline_info *ccp)
* Restore ccline after it has been saved with save_cmdline().
*/
static void restore_cmdline(struct cmdline_info *ccp)
+ FUNC_ATTR_NONNULL_ALL
{
ccline = *ccp;
}
@@ -3580,6 +3581,7 @@ static void restore_cmdline(struct cmdline_info *ccp)
* passed to restore_cmdline_alloc() later.
*/
char_u *save_cmdline_alloc(void)
+ FUNC_ATTR_NONNULL_RET
{
struct cmdline_info *p = xmalloc(sizeof(struct cmdline_info));
save_cmdline(p);
@@ -3590,6 +3592,7 @@ char_u *save_cmdline_alloc(void)
* Restore the command line from the return value of save_cmdline_alloc().
*/
void restore_cmdline_alloc(char_u *p)
+ FUNC_ATTR_NONNULL_ALL
{
restore_cmdline((struct cmdline_info *)p);
xfree(p);
@@ -6635,11 +6638,13 @@ static int open_cmdwin(void)
wp = curwin;
set_bufref(&bufref, curbuf);
win_goto(old_curwin);
- win_close(wp, true);
+ if (win_valid(wp) && wp != curwin) {
+ win_close(wp, true);
+ }
// win_close() may have already wiped the buffer when 'bh' is
- // set to 'wipe'.
- if (bufref_valid(&bufref)) {
+ // set to 'wipe', autocommands may have closed other windows
+ if (bufref_valid(&bufref) && bufref.br_buf != curbuf) {
close_buffer(NULL, bufref.br_buf, DOBUF_WIPE, false);
}
diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c
index 9e4e69e124..67b8e7e92f 100644
--- a/src/nvim/ex_session.c
+++ b/src/nvim/ex_session.c
@@ -725,7 +725,7 @@ static int makeopens(FILE *fd, char_u *dirnow)
}
}
- if (tab_firstwin->w_next != NULL) {
+ if (tab_firstwin != NULL && tab_firstwin->w_next != NULL) {
// Go to the first window.
PUTLINE_FAIL("wincmd t");
@@ -835,7 +835,7 @@ static int makeopens(FILE *fd, char_u *dirnow)
p_shm) < 0) {
return FAIL;
}
- if (tab_firstwin->w_next != NULL) {
+ if (tab_firstwin != NULL && tab_firstwin->w_next != NULL) {
// Restore 'winminheight' and 'winminwidth'.
PUTLINE_FAIL("let &winminheight = s:save_winminheight");
PUTLINE_FAIL("let &winminwidth = s:save_winminwidth");
@@ -937,11 +937,13 @@ void ex_mkrc(exarg_T *eap)
if (!view_session || (eap->cmdidx == CMD_mksession
&& (*flagp & SSOP_OPTIONS))) {
- failed |= (makemap(fd, NULL) == FAIL
- || makeset(fd, OPT_GLOBAL, false) == FAIL);
- if (p_hls && fprintf(fd, "%s", "set hlsearch\n") < 0) {
- failed = true;
+ int flags = OPT_GLOBAL;
+
+ if (eap->cmdidx == CMD_mksession && (*flagp & SSOP_SKIP_RTP)) {
+ flags |= OPT_SKIPRTP;
}
+ failed |= (makemap(fd, NULL) == FAIL
+ || makeset(fd, flags, false) == FAIL);
}
if (!failed && view_session) {
@@ -1002,6 +1004,9 @@ void ex_mkrc(exarg_T *eap)
< 0) {
failed = true;
}
+ if (p_hls && fprintf(fd, "%s", "set hlsearch\n") < 0) {
+ failed = true;
+ }
if (no_hlsearch && fprintf(fd, "%s", "nohlsearch\n") < 0) {
failed = true;
}
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index 2037ba5f19..29c29a2884 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -5204,113 +5204,38 @@ static void vim_maketempdir(void)
(void)umask(umask_save);
}
-// Evaluate "expr" for readdir().
-static varnumber_T readdir_checkitem(typval_T *expr, const char *name)
-{
- typval_T save_val;
- typval_T rettv;
- typval_T argv[2];
- varnumber_T retval = 0;
- bool error = false;
-
- prepare_vimvar(VV_VAL, &save_val);
- set_vim_var_string(VV_VAL, name, -1);
- argv[0].v_type = VAR_STRING;
- argv[0].vval.v_string = (char_u *)name;
-
- if (eval_expr_typval(expr, argv, 1, &rettv) == FAIL) {
- goto theend;
- }
-
- retval = tv_get_number_chk(&rettv, &error);
- if (error) {
- retval = -1;
- }
-
- tv_clear(&rettv);
-
-theend:
- set_vim_var_string(VV_VAL, NULL, 0);
- restore_vimvar(VV_VAL, &save_val);
- return retval;
-}
-
-/// Core part of "readdir()" function.
-/// Retrieve the list of files/directories of "dirp" into "gap".
-void readdir_core(
- garray_T *gap,
- Directory *dirp,
- typval_T *expr,
- bool is_checkitem)
-{
- ga_init(gap, (int)sizeof(char *), 20);
-
- for (;;) {
- bool ignore;
-
- const char *path = os_scandir_next(dirp);
- if (path == NULL) {
- break;
- }
-
- ignore = (path[0] == '.'
- && (path[1] == NUL || (path[1] == '.' && path[2] == NUL)));
- if (!ignore && expr != NULL && expr->v_type != VAR_UNKNOWN
- && is_checkitem) {
- varnumber_T r = readdir_checkitem(expr, path);
-
- if (r < 0) {
- break;
- }
- if (r == 0) {
- ignore = true;
- }
- }
-
- if (!ignore) {
- ga_grow(gap, 1);
- ((char **)gap->ga_data)[gap->ga_len++] = xstrdup(path);
- }
- }
-
- if (gap->ga_len > 0) {
- sort_strings((char_u **)gap->ga_data, gap->ga_len);
- }
-
- os_closedir(dirp);
-}
-
/// Delete "name" and everything in it, recursively.
/// @param name The path which should be deleted.
/// @return 0 for success, -1 if some file was not deleted.
int delete_recursive(const char *name)
{
int result = 0;
- char *exp = (char *)vim_strsave((char_u *)name);
- Directory dir;
-
- if (os_isrealdir(name) && os_scandir(&dir, exp)) {
- garray_T ga;
-
- readdir_core(&ga, &dir, NULL, false);
- for (int i = 0; i < ga.ga_len; i++) {
- vim_snprintf((char *)NameBuff, MAXPATHL, "%s/%s", exp,
- ((char_u **)ga.ga_data)[i]);
- if (delete_recursive((const char *)NameBuff) != 0) {
- result = -1;
+ if (os_isrealdir(name)) {
+ snprintf((char *)NameBuff, MAXPATHL, "%s/*", name); // NOLINT
+
+ char_u **files;
+ int file_count;
+ char_u *exp = vim_strsave(NameBuff);
+ if (gen_expand_wildcards(1, &exp, &file_count, &files,
+ EW_DIR | EW_FILE | EW_SILENT | EW_ALLLINKS
+ | EW_DODOT | EW_EMPTYOK) == OK) {
+ for (int i = 0; i < file_count; i++) {
+ if (delete_recursive((const char *)files[i]) != 0) {
+ result = -1;
+ }
}
+ FreeWild(file_count, files);
+ } else {
+ result = -1;
}
- ga_clear_strings(&ga);
-
+ xfree(exp);
os_rmdir(name);
} else {
result = os_remove(name) == 0 ? 0 : -1;
}
- xfree(exp);
-
return result;
}
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 624b7c93f3..0ce2b586e3 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -987,6 +987,8 @@ EXTERN char_u e_dirnotf[] INIT(= N_(
"E919: Directory not found in '%s': \"%s\""));
EXTERN char_u e_au_recursive[] INIT(= N_(
"E952: Autocommand caused recursive behavior"));
+EXTERN char_u e_autocmd_close[] INIT(= N_(
+ "E813: Cannot close autocmd window"));
EXTERN char_u e_unsupportedoption[] INIT(= N_("E519: Option not supported"));
EXTERN char_u e_fnametoolong[] INIT(= N_("E856: Filename too long"));
EXTERN char_u e_float_as_string[] INIT(= N_("E806: using Float as a String"));
diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h
index 3b34af46e4..724363674c 100644
--- a/src/nvim/grid_defs.h
+++ b/src/nvim/grid_defs.h
@@ -13,6 +13,15 @@
typedef char_u schar_T[(MAX_MCO+1) * 4 + 1];
typedef int sattr_T;
+enum {
+ kZIndexDefaultGrid = 0,
+ kZIndexFloatDefault = 50,
+ kZIndexPopupMenu = 100,
+ kZIndexMessages = 200,
+ kZIndexCmdlinePopupMenu = 250,
+};
+
+
/// ScreenGrid represents a resizable rectuangular grid displayed by UI clients.
///
/// chars[] contains the UTF-8 text that is currently displayed on the grid.
@@ -73,6 +82,9 @@ struct ScreenGrid {
// whether the grid can be focused with mouse clicks.
bool focusable;
+ // z-index: the order in the stack of grids.
+ int zindex;
+
// Below is state owned by the compositor. Should generally not be set/read
// outside this module, except for specific compatibilty hacks
@@ -96,7 +108,7 @@ struct ScreenGrid {
};
#define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, NULL, 0, 0, false, \
- false, 0, 0, NULL, false, true, \
+ false, 0, 0, NULL, false, true, 0, \
0, 0, 0, 0, 0, false }
#endif // NVIM_GRID_DEFS_H
diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c
index 79801262cb..79e474fa2e 100644
--- a/src/nvim/highlight.c
+++ b/src/nvim/highlight.c
@@ -806,8 +806,11 @@ HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err)
{
HlAttrs hlattrs = HLATTRS_INIT;
- int32_t fg = -1, bg = -1, sp = -1;
+ int32_t fg = -1, bg = -1, ctermfg = -1, ctermbg = -1, sp = -1;
int16_t mask = 0;
+ int16_t cterm_mask = 0;
+ bool cterm_mask_provided = false;
+
for (size_t i = 0; i < dict.size; i++) {
char *key = dict.items[i].key.data;
Object val = dict.items[i].value;
@@ -837,6 +840,25 @@ HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err)
}
}
+ // Handle cterm attrs
+ if (strequal(key, "cterm") && val.type == kObjectTypeDictionary) {
+ cterm_mask_provided = true;
+ Dictionary cterm_dict = val.data.dictionary;
+ for (size_t l = 0; l < cterm_dict.size; l++) {
+ char *cterm_dict_key = cterm_dict.items[l].key.data;
+ Object cterm_dict_val = cterm_dict.items[l].value;
+ for (int m = 0; flags[m].name; m++) {
+ if (strequal(flags[m].name, cterm_dict_key)) {
+ if (api_object_to_bool(cterm_dict_val, cterm_dict_key, false,
+ err)) {
+ cterm_mask |= flags[m].flag;
+ }
+ break;
+ }
+ }
+ }
+ }
+
struct {
const char *name;
const char *shortname;
@@ -844,6 +866,8 @@ HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err)
} colors[] = {
{ "foreground", "fg", &fg },
{ "background", "bg", &bg },
+ { "ctermfg", NULL, &ctermfg },
+ { "ctermbg", NULL, &ctermbg },
{ "special", "sp", &sp },
{ NULL, NULL, NULL },
};
@@ -867,7 +891,6 @@ HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err)
}
}
-
if (flags[j].name || colors[k].name) {
// handled above
} else if (link_id && strequal(key, "link")) {
@@ -888,13 +911,22 @@ HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err)
}
}
+ // apply gui mask as default for cterm mask
+ if (!cterm_mask_provided) {
+ cterm_mask = mask;
+ }
if (use_rgb) {
hlattrs.rgb_ae_attr = mask;
hlattrs.rgb_bg_color = bg;
hlattrs.rgb_fg_color = fg;
hlattrs.rgb_sp_color = sp;
+ hlattrs.cterm_bg_color =
+ ctermbg == -1 ? cterm_normal_bg_color : ctermbg + 1;
+ hlattrs.cterm_fg_color =
+ ctermfg == -1 ? cterm_normal_fg_color : ctermfg + 1;
+ hlattrs.cterm_ae_attr = cterm_mask;
} else {
- hlattrs.cterm_ae_attr = mask;
+ hlattrs.cterm_ae_attr = cterm_mask;
hlattrs.cterm_bg_color = bg == -1 ? cterm_normal_bg_color : bg + 1;
hlattrs.cterm_fg_color = fg == -1 ? cterm_normal_fg_color : fg + 1;
}
diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c
index 771bf923b2..25c27743f3 100644
--- a/src/nvim/indent_c.c
+++ b/src/nvim/indent_c.c
@@ -3552,7 +3552,7 @@ term_again:
* Position the cursor over the rightmost paren, so that
* matching it will take us back to the start of the line.
*/
- find_last_paren(l, '(', ')');
+ (void)find_last_paren(l, '(', ')');
if ((trypos = find_match_paren(curbuf->b_ind_maxparen)) != NULL)
curwin->w_cursor = *trypos;
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index f99a2dd0fe..0a52cc16cb 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -471,6 +471,15 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
lua_pushcfunction(lstate, &nlua_wait);
lua_setfield(lstate, -2, "wait");
+ // _getvar
+ lua_pushcfunction(lstate, &nlua_getvar);
+ lua_setfield(lstate, -2, "_getvar");
+
+ // _setvar
+ lua_pushcfunction(lstate, &nlua_setvar);
+ lua_setfield(lstate, -2, "_setvar");
+
+
// vim.loop
luv_set_loop(lstate, &main_loop.uv);
luv_set_callback(lstate, nlua_luv_cfpcall);
@@ -870,6 +879,109 @@ check_err:
return request ? 1 : 0;
}
+static dict_T *nlua_get_var_scope(lua_State *lstate) {
+ const char *scope = luaL_checkstring(lstate, 1);
+ handle_T handle = (handle_T)luaL_checkinteger(lstate, 2);
+ dict_T *dict = NULL;
+ Error err = ERROR_INIT;
+ if (strequal(scope, "g")) {
+ dict = &globvardict;
+ } else if (strequal(scope, "v")) {
+ dict = &vimvardict;
+ } else if (strequal(scope, "b")) {
+ buf_T *buf = find_buffer_by_handle(handle, &err);
+ if (buf) {
+ dict = buf->b_vars;
+ }
+ } else if (strequal(scope, "w")) {
+ win_T *win = find_window_by_handle(handle, &err);
+ if (win) {
+ dict = win->w_vars;
+ }
+ } else if (strequal(scope, "t")) {
+ tabpage_T *tabpage = find_tab_by_handle(handle, &err);
+ if (tabpage) {
+ dict = tabpage->tp_vars;
+ }
+ } else {
+ luaL_error(lstate, "invalid scope", err.msg);
+ return NULL;
+ }
+
+ if (ERROR_SET(&err)) {
+ luaL_error(lstate, "FAIL: %s", err.msg);
+ return NULL;
+ }
+ return dict;
+}
+
+
+static int nlua_getvar(lua_State *lstate)
+{
+ // non-local return if not found
+ dict_T *dict = nlua_get_var_scope(lstate);
+ size_t len;
+ const char *name = luaL_checklstring(lstate, 3, &len);
+
+ dictitem_T *di = tv_dict_find(dict, name, (ptrdiff_t)len);
+ if (di == NULL) {
+ return 0; // nil
+ }
+ nlua_push_typval(lstate, &di->di_tv, false);
+ return 1;
+}
+
+static int nlua_setvar(lua_State *lstate)
+{
+ // non-local return if not found
+ dict_T *dict = nlua_get_var_scope(lstate);
+ String key;
+ key.data = (char *)luaL_checklstring(lstate, 3, &key.size);
+
+ bool del = (lua_gettop(lstate) < 4) || lua_isnil(lstate, 4);
+
+ Error err = ERROR_INIT;
+ dictitem_T *di = dict_check_writable(dict, key, del, &err);
+ if (ERROR_SET(&err)) {
+ return 0;
+ }
+
+ if (del) {
+ // Delete the key
+ if (di == NULL) {
+ // Doesn't exist, nothing to do
+ return 0;
+ } else {
+ // Delete the entry
+ tv_dict_item_remove(dict, di);
+ }
+ } else {
+ // Update the key
+ typval_T tv;
+
+ // Convert the lua value to a vimscript type in the temporary variable
+ lua_pushvalue(lstate, 4);
+ if (!nlua_pop_typval(lstate, &tv)) {
+ return luaL_error(lstate, "Couldn't convert lua value");
+ }
+
+ if (di == NULL) {
+ // Need to create an entry
+ di = tv_dict_item_alloc_len(key.data, key.size);
+ tv_dict_add(dict, di);
+ } else {
+ // Clear the old value
+ tv_clear(&di->di_tv);
+ }
+
+ // Update the value
+ tv_copy(&tv, &di->di_tv);
+ // Clear the temporary variable
+ tv_clear(&tv);
+ }
+ return 0;
+}
+
static int nlua_nil_tostring(lua_State *lstate)
{
lua_pushstring(lstate, "vim.NIL");
diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua
index 3994c5bc5b..5c9c5103a7 100644
--- a/src/nvim/lua/vim.lua
+++ b/src/nvim/lua/vim.lua
@@ -39,6 +39,75 @@ assert(vim)
vim.inspect = package.loaded['vim.inspect']
assert(vim.inspect)
+local pathtrails = {}
+vim._so_trails = {}
+for s in (package.cpath..';'):gmatch('[^;]*;') do
+ s = s:sub(1, -2) -- Strip trailing semicolon
+ -- Find out path patterns. pathtrail should contain something like
+ -- /?.so, \?.dll. This allows not to bother determining what correct
+ -- suffixes are.
+ local pathtrail = s:match('[/\\][^/\\]*%?.*$')
+ if pathtrail and not pathtrails[pathtrail] then
+ pathtrails[pathtrail] = true
+ table.insert(vim._so_trails, pathtrail)
+ end
+end
+
+function vim._load_package(name)
+ local basename = name:gsub('%.', '/')
+ local paths = {"lua/"..basename..".lua", "lua/"..basename.."/init.lua"}
+ for _,path in ipairs(paths) do
+ local found = vim.api.nvim_get_runtime_file(path, false)
+ if #found > 0 then
+ local f, err = loadfile(found[1])
+ return f or error(err)
+ end
+ end
+
+ for _,trail in ipairs(vim._so_trails) do
+ local path = "lua"..trail:gsub('?', basename) -- so_trails contains a leading slash
+ local found = vim.api.nvim_get_runtime_file(path, false)
+ if #found > 0 then
+ -- Making function name in Lua 5.1 (see src/loadlib.c:mkfuncname) is
+ -- a) strip prefix up to and including the first dash, if any
+ -- b) replace all dots by underscores
+ -- c) prepend "luaopen_"
+ -- So "foo-bar.baz" should result in "luaopen_bar_baz"
+ local dash = name:find("-", 1, true)
+ local modname = dash and name:sub(dash + 1) or name
+ local f, err = package.loadlib(found[1], "luaopen_"..modname:gsub("%.", "_"))
+ return f or error(err)
+ end
+ end
+ return nil
+end
+
+table.insert(package.loaders, 1, vim._load_package)
+
+-- These are for loading runtime modules lazily since they aren't available in
+-- the nvim binary as specified in executor.c
+setmetatable(vim, {
+ __index = function(t, key)
+ if key == 'treesitter' then
+ t.treesitter = require('vim.treesitter')
+ return t.treesitter
+ elseif key == 'F' then
+ t.F = require('vim.F')
+ return t.F
+ elseif require('vim.uri')[key] ~= nil then
+ -- Expose all `vim.uri` functions on the `vim` module.
+ t[key] = require('vim.uri')[key]
+ return t[key]
+ elseif key == 'lsp' then
+ t.lsp = require('vim.lsp')
+ return t.lsp
+ elseif key == 'highlight' then
+ t.highlight = require('vim.highlight')
+ return t.highlight
+ end
+ end
+})
+
vim.log = {
levels = {
TRACE = 0;
@@ -105,51 +174,6 @@ function vim._os_proc_children(ppid)
return children
end
-local pathtrails = {}
-vim._so_trails = {}
-for s in (package.cpath..';'):gmatch('[^;]*;') do
- s = s:sub(1, -2) -- Strip trailing semicolon
- -- Find out path patterns. pathtrail should contain something like
- -- /?.so, \?.dll. This allows not to bother determining what correct
- -- suffixes are.
- local pathtrail = s:match('[/\\][^/\\]*%?.*$')
- if pathtrail and not pathtrails[pathtrail] then
- pathtrails[pathtrail] = true
- table.insert(vim._so_trails, pathtrail)
- end
-end
-
-function vim._load_package(name)
- local basename = name:gsub('%.', '/')
- local paths = {"lua/"..basename..".lua", "lua/"..basename.."/init.lua"}
- for _,path in ipairs(paths) do
- local found = vim.api.nvim_get_runtime_file(path, false)
- if #found > 0 then
- local f, err = loadfile(found[1])
- return f or error(err)
- end
- end
-
- for _,trail in ipairs(vim._so_trails) do
- local path = "lua"..trail:gsub('?', basename) -- so_trails contains a leading slash
- local found = vim.api.nvim_get_runtime_file(path, false)
- if #found > 0 then
- -- Making function name in Lua 5.1 (see src/loadlib.c:mkfuncname) is
- -- a) strip prefix up to and including the first dash, if any
- -- b) replace all dots by underscores
- -- c) prepend "luaopen_"
- -- So "foo-bar.baz" should result in "luaopen_bar_baz"
- local dash = name:find("-", 1, true)
- local modname = dash and name:sub(dash + 1) or name
- local f, err = package.loadlib(found[1], "luaopen_"..modname:gsub("%.", "_"))
- return f or error(err)
- end
- end
- return nil
-end
-
-table.insert(package.loaders, 1, vim._load_package)
-
-- TODO(ZyX-I): Create compatibility layer.
--- Return a human-readable representation of the given object.
@@ -282,32 +306,6 @@ vim.funcref = function(viml_func_name)
return vim.fn[viml_func_name]
end
--- These are for loading runtime modules lazily since they aren't available in
--- the nvim binary as specified in executor.c
-local function __index(t, key)
- if key == 'treesitter' then
- t.treesitter = require('vim.treesitter')
- return t.treesitter
- elseif require('vim.uri')[key] ~= nil then
- -- Expose all `vim.uri` functions on the `vim` module.
- t[key] = require('vim.uri')[key]
- return t[key]
- elseif key == 'lsp' then
- t.lsp = require('vim.lsp')
- return t.lsp
- elseif key == 'highlight' then
- t.highlight = require('vim.highlight')
- return t.highlight
- elseif key == 'F' then
- t.F = require('vim.F')
- return t.F
- end
-end
-
-setmetatable(vim, {
- __index = __index
-})
-
-- An easier alias for commands.
vim.cmd = function(command)
return vim.api.nvim_exec(command, false)
@@ -315,135 +313,27 @@ end
-- These are the vim.env/v/g/o/bo/wo variable magic accessors.
do
- local a = vim.api
local validate = vim.validate
- local function make_meta_accessor(get, set, del)
+
+ local function make_dict_accessor(scope)
validate {
- get = {get, 'f'};
- set = {set, 'f'};
- del = {del, 'f', true};
+ scope = {scope, 's'};
}
local mt = {}
- if del then
- function mt:__newindex(k, v)
- if v == nil then
- return del(k)
- end
- return set(k, v)
- end
- else
- function mt:__newindex(k, v)
- return set(k, v)
- end
+ function mt:__newindex(k, v)
+ return vim._setvar(scope, 0, k, v)
end
function mt:__index(k)
- return get(k)
+ return vim._getvar(scope, 0, k)
end
return setmetatable({}, mt)
end
- local function pcall_ret(status, ...)
- if status then return ... end
- end
- local function nil_wrap(fn)
- return function(...)
- return pcall_ret(pcall(fn, ...))
- end
- end
-
- vim.b = make_meta_accessor(
- nil_wrap(function(v) return a.nvim_buf_get_var(0, v) end),
- function(v, k) return a.nvim_buf_set_var(0, v, k) end,
- function(v) return a.nvim_buf_del_var(0, v) end
- )
- vim.w = make_meta_accessor(
- nil_wrap(function(v) return a.nvim_win_get_var(0, v) end),
- function(v, k) return a.nvim_win_set_var(0, v, k) end,
- function(v) return a.nvim_win_del_var(0, v) end
- )
- vim.t = make_meta_accessor(
- nil_wrap(function(v) return a.nvim_tabpage_get_var(0, v) end),
- function(v, k) return a.nvim_tabpage_set_var(0, v, k) end,
- function(v) return a.nvim_tabpage_del_var(0, v) end
- )
- vim.g = make_meta_accessor(nil_wrap(a.nvim_get_var), a.nvim_set_var, a.nvim_del_var)
- vim.v = make_meta_accessor(nil_wrap(a.nvim_get_vvar), a.nvim_set_vvar)
- vim.o = make_meta_accessor(a.nvim_get_option, a.nvim_set_option)
-
- local function getenv(k)
- local v = vim.fn.getenv(k)
- if v == vim.NIL then
- return nil
- end
- return v
- end
- vim.env = make_meta_accessor(getenv, vim.fn.setenv)
- -- TODO(ashkan) if/when these are available from an API, generate them
- -- instead of hardcoding.
- local window_options = {
- arab = true; arabic = true; breakindent = true; breakindentopt = true;
- bri = true; briopt = true; cc = true; cocu = true;
- cole = true; colorcolumn = true; concealcursor = true; conceallevel = true;
- crb = true; cuc = true; cul = true; cursorbind = true;
- cursorcolumn = true; cursorline = true; diff = true; fcs = true;
- fdc = true; fde = true; fdi = true; fdl = true;
- fdm = true; fdn = true; fdt = true; fen = true;
- fillchars = true; fml = true; fmr = true; foldcolumn = true;
- foldenable = true; foldexpr = true; foldignore = true; foldlevel = true;
- foldmarker = true; foldmethod = true; foldminlines = true; foldnestmax = true;
- foldtext = true; lbr = true; lcs = true; linebreak = true;
- list = true; listchars = true; nu = true; number = true;
- numberwidth = true; nuw = true; previewwindow = true; pvw = true;
- relativenumber = true; rightleft = true; rightleftcmd = true; rl = true;
- rlc = true; rnu = true; scb = true; scl = true;
- scr = true; scroll = true; scrollbind = true; signcolumn = true;
- spell = true; statusline = true; stl = true; wfh = true;
- wfw = true; winbl = true; winblend = true; winfixheight = true;
- winfixwidth = true; winhighlight = true; winhl = true; wrap = true;
- }
- --@private
- local function new_buf_opt_accessor(bufnr)
- --@private
- local function get(k)
- if window_options[k] then
- return a.nvim_err_writeln(k.." is a window option, not a buffer option")
- end
- if bufnr == nil and type(k) == "number" then
- return new_buf_opt_accessor(k)
- end
- return a.nvim_buf_get_option(bufnr or 0, k)
- end
-
- --@private
- local function set(k, v)
- if window_options[k] then
- return a.nvim_err_writeln(k.." is a window option, not a buffer option")
- end
- return a.nvim_buf_set_option(bufnr or 0, k, v)
- end
-
- return make_meta_accessor(get, set)
- end
- vim.bo = new_buf_opt_accessor(nil)
-
- --@private
- local function new_win_opt_accessor(winnr)
-
- --@private
- local function get(k)
- if winnr == nil and type(k) == "number" then
- return new_win_opt_accessor(k)
- end
- return a.nvim_win_get_option(winnr or 0, k)
- end
-
- --@private
- local function set(k, v)
- return a.nvim_win_set_option(winnr or 0, k, v)
- end
- return make_meta_accessor(get, set)
- end
- vim.wo = new_win_opt_accessor(nil)
+ vim.g = make_dict_accessor('g')
+ vim.v = make_dict_accessor('v')
+ vim.b = make_dict_accessor('b')
+ vim.w = make_dict_accessor('w')
+ vim.t = make_dict_accessor('t')
end
--- Get a table of lines with start, end columns for a region marked by two points
@@ -522,6 +412,8 @@ function vim.notify(msg, log_level, _opts)
if log_level == vim.log.levels.ERROR then
vim.api.nvim_err_writeln(msg)
+ elseif log_level == vim.log.levels.WARN then
+ vim.api.nvim_echo({{msg, 'WarningMsg'}}, true, {})
else
vim.api.nvim_echo({{msg}}, true, {})
end
@@ -744,4 +636,6 @@ vim._expand_pat_get_parts = function(lua_string)
return parts, search_index
end
+pcall(require, 'vim._meta')
+
return module
diff --git a/src/nvim/macros.h b/src/nvim/macros.h
index 303722b4a8..eb9357d027 100644
--- a/src/nvim/macros.h
+++ b/src/nvim/macros.h
@@ -238,5 +238,6 @@
# define PRAGMA_DIAG_POP
#endif
+#define EMPTY_POS(a) ((a).lnum == 0 && (a).col == 0 && (a).coladd == 0)
#endif // NVIM_MACROS_H
diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c
index b3afdefac1..34acf64d83 100644
--- a/src/nvim/marktree.c
+++ b/src/nvim/marktree.c
@@ -849,7 +849,7 @@ bool marktree_splice(MarkTree *b,
MarkTreeIter itr[1] = { 0 };
MarkTreeIter enditr[1] = { 0 };
- mtpos_t oldbase[MT_MAX_DEPTH];
+ mtpos_t oldbase[MT_MAX_DEPTH] = { 0 };
marktree_itr_get_ext(b, start, itr, false, true, oldbase);
if (!itr->node) {
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index 34d8eb0ffe..e42b138253 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -2238,7 +2238,7 @@ static int ml_append_int(
*/
lineadd = buf->b_ml.ml_locked_lineadd;
buf->b_ml.ml_locked_lineadd = 0;
- ml_find_line(buf, (linenr_T)0, ML_FLUSH); /* flush data block */
+ (void)ml_find_line(buf, (linenr_T)0, ML_FLUSH); // flush data block
/*
* update pointer blocks for the new data block
diff --git a/src/nvim/menu.c b/src/nvim/menu.c
index ac3b7768e6..112f51fc64 100644
--- a/src/nvim/menu.c
+++ b/src/nvim/menu.c
@@ -1069,7 +1069,7 @@ char_u *get_menu_names(expand_T *xp, int idx)
#define TBUFFER_LEN 256
static char_u tbuffer[TBUFFER_LEN]; /*hack*/
char_u *str;
- static int should_advance = FALSE;
+ static bool should_advance = false;
if (idx == 0) { /* first call: start at first item */
menu = expand_menu;
@@ -1089,12 +1089,13 @@ char_u *get_menu_names(expand_T *xp, int idx)
if (menu->modes & expand_modes) {
if (menu->children != NULL) {
- if (should_advance)
- STRLCPY(tbuffer, menu->en_dname, TBUFFER_LEN - 1);
- else {
- STRLCPY(tbuffer, menu->dname, TBUFFER_LEN - 1);
- if (menu->en_dname == NULL)
- should_advance = TRUE;
+ if (should_advance) {
+ STRLCPY(tbuffer, menu->en_dname, TBUFFER_LEN);
+ } else {
+ STRLCPY(tbuffer, menu->dname, TBUFFER_LEN);
+ if (menu->en_dname == NULL) {
+ should_advance = true;
+ }
}
/* hack on menu separators: use a 'magic' char for the separator
* so that '.' in names gets escaped properly */
@@ -1105,8 +1106,9 @@ char_u *get_menu_names(expand_T *xp, int idx)
str = menu->en_dname;
else {
str = menu->dname;
- if (menu->en_dname == NULL)
- should_advance = TRUE;
+ if (menu->en_dname == NULL) {
+ should_advance = true;
+ }
}
}
} else
diff --git a/src/nvim/message.c b/src/nvim/message.c
index a34b895033..ec5dabbbc0 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -165,6 +165,7 @@ void msg_grid_validate(void)
// TODO(bfredl): eventually should be set to "invalid". I e all callers
// will use the grid including clear to EOS if necessary.
grid_alloc(&msg_grid, Rows, Columns, false, true);
+ msg_grid.zindex = kZIndexMessages;
xfree(msg_grid.dirty_col);
msg_grid.dirty_col = xcalloc(Rows, sizeof(*msg_grid.dirty_col));
diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c
index 68a1bba78d..38d0a7dadf 100644
--- a/src/nvim/misc1.c
+++ b/src/nvim/misc1.c
@@ -753,8 +753,12 @@ get_number (
skip_redraw = TRUE; /* skip redraw once */
do_redraw = FALSE;
break;
- } else if (c == CAR || c == NL || c == Ctrl_C || c == ESC)
+ } else if (c == Ctrl_C || c == ESC || c == 'q') {
+ n = 0;
break;
+ } else if (c == CAR || c == NL) {
+ break;
+ }
}
no_mapping--;
return n;
@@ -771,11 +775,13 @@ int prompt_for_number(int *mouse_used)
int save_cmdline_row;
int save_State;
- /* When using ":silent" assume that <CR> was entered. */
- if (mouse_used != NULL)
- MSG_PUTS(_("Type number and <Enter> or click with mouse (empty cancels): "));
- else
- MSG_PUTS(_("Type number and <Enter> (empty cancels): "));
+ // When using ":silent" assume that <CR> was entered.
+ if (mouse_used != NULL) {
+ MSG_PUTS(_("Type number and <Enter> or click with the mouse "
+ "(q or empty cancels): "));
+ } else {
+ MSG_PUTS(_("Type number and <Enter> (q or empty cancels): "));
+ }
/* Set the state such that text can be selected/copied/pasted and we still
* get mouse events. */
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 2f4c441beb..69afe1644e 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -1942,10 +1942,12 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
case OP_FORMAT:
if (*curbuf->b_p_fex != NUL) {
op_formatexpr(oap); // use expression
- } else if (*p_fp != NUL || *curbuf->b_p_fp != NUL) {
- op_colon(oap); // use external command
} else {
- op_format(oap, false); // use internal function
+ if (*p_fp != NUL || *curbuf->b_p_fp != NUL) {
+ op_colon(oap); // use external command
+ } else {
+ op_format(oap, false); // use internal function
+ }
}
break;
@@ -4382,6 +4384,12 @@ dozet:
}
break;
+ // "zp", "zP" in block mode put without addind trailing spaces
+ case 'P':
+ case 'p':
+ nv_put(cap);
+ break;
+
/* "zF": create fold command */
/* "zf": create fold operator */
case 'F':
@@ -5112,8 +5120,8 @@ static void nv_scroll(cmdarg_T *cap)
/* Count a fold for one screen line. */
lnum = curwin->w_topline;
while (n-- > 0 && lnum < curwin->w_botline - 1) {
- hasFolding(lnum, NULL, &lnum);
- ++lnum;
+ (void)hasFolding(lnum, NULL, &lnum);
+ lnum++;
}
n = lnum - curwin->w_topline;
}
@@ -7911,12 +7919,14 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
flags |= PUT_FIXINDENT;
} else {
dir = (cap->cmdchar == 'P'
- || (cap->cmdchar == 'g' && cap->nchar == 'P'))
- ? BACKWARD : FORWARD;
+ || ((cap->cmdchar == 'g' || cap->cmdchar == 'z')
+ && cap->nchar == 'P')) ? BACKWARD : FORWARD;
}
prep_redo_cmd(cap);
if (cap->cmdchar == 'g') {
flags |= PUT_CURSEND;
+ } else if (cap->cmdchar == 'z') {
+ flags |= PUT_BLOCK_INNER;
}
if (VIsual_active) {
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 10b8ebdc22..2c8c7f0567 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -1676,17 +1676,14 @@ int op_delete(oparg_T *oap)
curbuf_splice_pending++;
pos_T startpos = curwin->w_cursor; // start position for delete
- bcount_t deleted_bytes = (bcount_t)STRLEN(
- ml_get(startpos.lnum)) + 1 - startpos.col;
+ bcount_t deleted_bytes = get_region_bytecount(
+ curbuf, startpos.lnum, oap->end.lnum, startpos.col,
+ oap->end.col) + oap->inclusive;
truncate_line(true); // delete from cursor to end of line
curpos = curwin->w_cursor; // remember curwin->w_cursor
curwin->w_cursor.lnum++;
- for (linenr_T i = 1; i <= oap->line_count - 2; i++) {
- deleted_bytes += (bcount_t)STRLEN(
- ml_get(startpos.lnum + i)) + 1;
- }
del_lines(oap->line_count - 2, false);
// delete from start of line until op_end
@@ -1694,7 +1691,6 @@ int op_delete(oparg_T *oap)
curwin->w_cursor.col = 0;
(void)del_bytes((colnr_T)n, !virtual_op,
oap->op_type == OP_DELETE && !oap->is_VIsual);
- deleted_bytes += n;
curwin->w_cursor = curpos; // restore curwin->w_cursor
(void)do_join(2, false, false, false, false);
curbuf_splice_pending--;
@@ -2792,13 +2788,13 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg)
recursive = false;
}
-/*
- * Put contents of register "regname" into the text.
- * Caller must check "regname" to be valid!
- * "flags": PUT_FIXINDENT make indent look nice
- * PUT_CURSEND leave cursor after end of new text
- * PUT_LINE force linewise put (":put")
- dir: BACKWARD for 'P', FORWARD for 'p' */
+// Put contents of register "regname" into the text.
+// Caller must check "regname" to be valid!
+// "flags": PUT_FIXINDENT make indent look nice
+// PUT_CURSEND leave cursor after end of new text
+// PUT_LINE force linewise put (":put")
+// PUT_BLOCK_INNER in block mode, do not add trailing spaces
+// dir: BACKWARD for 'P', FORWARD for 'p'
void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
{
char_u *ptr;
@@ -3130,7 +3126,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
curwin->w_cursor.coladd = 0;
bd.textcol = 0;
for (i = 0; i < y_size; i++) {
- int spaces;
+ int spaces = 0;
char shortline;
// can just be 0 or 1, needed for blockwise paste beyond the current
// buffer end
@@ -3181,13 +3177,16 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
yanklen = (int)STRLEN(y_array[i]);
- // calculate number of spaces required to fill right side of block
- spaces = y_width + 1;
- for (long j = 0; j < yanklen; j++) {
- spaces -= lbr_chartabsize(NULL, &y_array[i][j], 0);
- }
- if (spaces < 0) {
- spaces = 0;
+ if ((flags & PUT_BLOCK_INNER) == 0) {
+ // calculate number of spaces required to fill right side of
+ // block
+ spaces = y_width + 1;
+ for (int j = 0; j < yanklen; j++) {
+ spaces -= lbr_chartabsize(NULL, &y_array[i][j], 0);
+ }
+ if (spaces < 0) {
+ spaces = 0;
+ }
}
// insert the new text
@@ -6303,3 +6302,33 @@ bool op_reg_set_previous(const char name)
y_previous = &y_regs[i];
return true;
}
+
+/// Get the byte count of buffer region. End-exclusive.
+///
+/// @return number of bytes
+bcount_t get_region_bytecount(buf_T *buf, linenr_T start_lnum,
+ linenr_T end_lnum, colnr_T start_col,
+ colnr_T end_col)
+{
+ linenr_T max_lnum = buf->b_ml.ml_line_count;
+ if (start_lnum > max_lnum) {
+ return 0;
+ }
+ if (start_lnum == end_lnum) {
+ return end_col - start_col;
+ }
+ const char *first = (const char *)ml_get_buf(buf, start_lnum, false);
+ bcount_t deleted_bytes = (bcount_t)STRLEN(first) - start_col + 1;
+
+ for (linenr_T i = 1; i <= end_lnum-start_lnum-1; i++) {
+ if (start_lnum + i > max_lnum) {
+ return deleted_bytes;
+ }
+ deleted_bytes += (bcount_t)STRLEN(
+ ml_get_buf(buf, start_lnum + i, false)) + 1;
+ }
+ if (end_lnum > max_lnum) {
+ return deleted_bytes;
+ }
+ return deleted_bytes + end_col;
+}
diff --git a/src/nvim/ops.h b/src/nvim/ops.h
index a8867e02ea..112ffbeaba 100644
--- a/src/nvim/ops.h
+++ b/src/nvim/ops.h
@@ -6,6 +6,7 @@
#include "nvim/macros.h"
#include "nvim/ascii.h"
#include "nvim/types.h"
+#include "nvim/extmark.h"
#include "nvim/eval/typval.h"
#include "nvim/os/time.h"
#include "nvim/normal.h" // for MotionType and oparg_T
@@ -13,13 +14,14 @@
typedef int (*Indenter)(void);
-/* flags for do_put() */
-#define PUT_FIXINDENT 1 /* make indent look nice */
-#define PUT_CURSEND 2 /* leave cursor after end of new text */
-#define PUT_CURSLINE 4 /* leave cursor on last line of new text */
-#define PUT_LINE 8 /* put register as lines */
-#define PUT_LINE_SPLIT 16 /* split line for linewise register */
-#define PUT_LINE_FORWARD 32 /* put linewise register below Visual sel. */
+// flags for do_put()
+#define PUT_FIXINDENT 1 // make indent look nice
+#define PUT_CURSEND 2 // leave cursor after end of new text
+#define PUT_CURSLINE 4 // leave cursor on last line of new text
+#define PUT_LINE 8 // put register as lines
+#define PUT_LINE_SPLIT 16 // split line for linewise register
+#define PUT_LINE_FORWARD 32 // put linewise register below Visual sel.
+#define PUT_BLOCK_INNER 64 // in block mode, do not add trailing spaces
/*
* Registers:
diff --git a/src/nvim/option.c b/src/nvim/option.c
index a811d749b9..67fb78bcc8 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -855,15 +855,18 @@ void set_init_3(void)
p_srr = (char_u *)">&";
options[idx_srr].def_val[VI_DEFAULT] = p_srr;
}
- } else if ( fnamecmp(p, "sh") == 0
- || fnamecmp(p, "ksh") == 0
- || fnamecmp(p, "mksh") == 0
- || fnamecmp(p, "pdksh") == 0
- || fnamecmp(p, "zsh") == 0
- || fnamecmp(p, "zsh-beta") == 0
- || fnamecmp(p, "bash") == 0
- || fnamecmp(p, "fish") == 0
- ) {
+ } else if (fnamecmp(p, "sh") == 0
+ || fnamecmp(p, "ksh") == 0
+ || fnamecmp(p, "mksh") == 0
+ || fnamecmp(p, "pdksh") == 0
+ || fnamecmp(p, "zsh") == 0
+ || fnamecmp(p, "zsh-beta") == 0
+ || fnamecmp(p, "bash") == 0
+ || fnamecmp(p, "fish") == 0
+ || fnamecmp(p, "ash") == 0
+ || fnamecmp(p, "dash") == 0
+ ) {
+ // Always use POSIX shell style redirection if we reach this
if (do_sp) {
p_sp = (char_u *)"2>&1| tee";
options[idx_sp].def_val[VI_DEFAULT] = p_sp;
@@ -3638,9 +3641,11 @@ char_u *check_stl_option(char_u *s)
return illegal_char(errbuf, sizeof(errbuf), *s);
}
if (*s == '{') {
+ int reevaluate = (*s == '%');
s++;
- while (*s != '}' && *s)
+ while ((*s != '}' || (reevaluate && s[-1] != '%')) && *s) {
s++;
+ }
if (*s != '}') {
return (char_u *)N_("E540: Unclosed expression sequence");
}
@@ -4878,7 +4883,7 @@ int get_option_value_strict(char *name,
if (p->flags & P_STRING) {
*stringval = xstrdup(*(char **)(varp));
} else if (p->flags & P_NUM) {
- *numval = *(long *) varp;
+ *numval = *(long *)varp;
} else {
*numval = *(int *)varp;
}
@@ -5227,6 +5232,11 @@ int makeset(FILE *fd, int opt_flags, int local_only)
continue;
}
+ if ((opt_flags & OPT_SKIPRTP)
+ && (p->var == (char_u *)&p_rtp || p->var == (char_u *)&p_pp)) {
+ continue;
+ }
+
round = 2;
if (p->indir != PV_NONE) {
if (p->var == VAR_WIN) {
@@ -7695,7 +7705,7 @@ Dictionary get_vimoption(String name, Error *err)
Dictionary get_all_vimoptions(void)
{
Dictionary retval = ARRAY_DICT_INIT;
- for (size_t i = 0; i < PARAM_COUNT; i++) {
+ for (size_t i = 0; options[i].fullname != NULL; i++) {
Dictionary opt_dict = vimoption2dict(&options[i]);
PUT(retval, options[i].fullname, DICTIONARY_OBJ(opt_dict));
}
diff --git a/src/nvim/option.h b/src/nvim/option.h
index 60f14dea44..c6ee03e052 100644
--- a/src/nvim/option.h
+++ b/src/nvim/option.h
@@ -19,6 +19,9 @@ typedef enum {
OPT_MODELINE = 8, ///< Option in modeline.
OPT_WINONLY = 16, ///< Only set window-local options.
OPT_NOWIN = 32, ///< Don’t set window-local options.
+ OPT_ONECOLUMN = 64, ///< list options one per line
+ OPT_NO_REDRAW = 128, ///< ignore redraw flags on option
+ OPT_SKIPRTP = 256, ///< "skiprtp" in 'sessionoptions'
} OptionFlags;
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index 16749ba86b..beb62a6a0b 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -559,6 +559,7 @@ EXTERN int p_ri; // 'revins'
EXTERN int p_ru; // 'ruler'
EXTERN char_u *p_ruf; // 'rulerformat'
EXTERN char_u *p_pp; // 'packpath'
+EXTERN char_u *p_qftf; // 'quickfixtextfunc'
EXTERN char_u *p_rtp; // 'runtimepath'
EXTERN long p_scbk; // 'scrollback'
EXTERN long p_sj; // 'scrolljump'
@@ -571,11 +572,12 @@ EXTERN char_u *p_slm; // 'selectmode'
EXTERN char_u *p_ssop; // 'sessionoptions'
EXTERN unsigned ssop_flags;
# ifdef IN_OPTION_C
-// Also used for 'viewoptions'!
+// Also used for 'viewoptions'! Keep in sync with SSOP_ flags.
static char *(p_ssop_values[]) = {
"buffers", "winpos", "resize", "winsize",
"localoptions", "options", "help", "blank", "globals", "slash", "unix",
- "sesdir", "curdir", "folds", "cursor", "tabpages", NULL
+ "sesdir", "curdir", "folds", "cursor", "tabpages", "terminal", "skiprtp",
+ NULL
};
# endif
# define SSOP_BUFFERS 0x001
@@ -594,6 +596,8 @@ static char *(p_ssop_values[]) = {
# define SSOP_FOLDS 0x2000
# define SSOP_CURSOR 0x4000
# define SSOP_TABPAGES 0x8000
+# define SSOP_TERMINAL 0x10000
+# define SSOP_SKIP_RTP 0x20000
EXTERN char_u *p_sh; // 'shell'
EXTERN char_u *p_shcf; // 'shellcmdflag'
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index d12b31bcaf..86dec74f56 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -2074,6 +2074,14 @@ return {
defaults={if_true={vi=0}}
},
{
+ full_name='quickfixtextfunc', abbreviation='qftf',
+ short_desc=N_("customize the quickfix window"),
+ type='string', scope={'global'},
+ vi_def=true,
+ varname='p_qftf',
+ defaults={if_true={vi=""}}
+ },
+ {
full_name='quoteescape', abbreviation='qe',
short_desc=N_("escape characters used in a string"),
type='string', scope={'buffer'},
diff --git a/src/nvim/path.c b/src/nvim/path.c
index 3e1713fbdd..fe50be5ea1 100644
--- a/src/nvim/path.c
+++ b/src/nvim/path.c
@@ -65,7 +65,7 @@ FileComparison path_full_compare(char_u *const s1, char_u *const s2,
if (expandenv) {
expand_env(s1, exp1, MAXPATHL);
} else {
- xstrlcpy((char *)exp1, (const char *)s1, MAXPATHL - 1);
+ xstrlcpy((char *)exp1, (const char *)s1, MAXPATHL);
}
bool id_ok_1 = os_fileid((char *)exp1, &file_id_1);
bool id_ok_2 = os_fileid((char *)s2, &file_id_2);
diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c
index 32c9750628..7d452d6797 100644
--- a/src/nvim/popupmnu.c
+++ b/src/nvim/popupmnu.c
@@ -421,6 +421,10 @@ void pum_redraw(void)
}
grid_assign_handle(&pum_grid);
+
+ pum_grid.zindex = ((State == CMDLINE)
+ ? kZIndexCmdlinePopupMenu : kZIndexPopupMenu);
+
bool moved = ui_comp_put_grid(&pum_grid, pum_row, pum_col-col_off,
pum_height, grid_width, false, true);
bool invalid_grid = moved || pum_invalid;
@@ -439,7 +443,7 @@ void pum_redraw(void)
int row_off = pum_above ? pum_height : 0;
ui_call_win_float_pos(pum_grid.handle, -1, cstr_to_string(anchor),
pum_anchor_grid, pum_row-row_off, pum_col-col_off,
- false);
+ false, pum_grid.zindex);
}
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 464d72eccb..1a9bbe26f0 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -100,6 +100,7 @@ typedef struct qf_list_S {
char_u *qf_title; ///< title derived from the command that created
///< the error list or set by setqflist
typval_T *qf_ctx; ///< context set by setqflist/setloclist
+ char_u *qf_qftf; ///< 'quickfixtextfunc' setting for this list
struct dir_stack_T *qf_dir_stack;
char_u *qf_directory;
@@ -1999,6 +2000,11 @@ static int copy_loclist(const qf_list_T *from_qfl, qf_list_T *to_qfl)
} else {
to_qfl->qf_ctx = NULL;
}
+ if (from_qfl->qf_qftf != NULL) {
+ to_qfl->qf_qftf = vim_strsave(from_qfl->qf_qftf);
+ } else {
+ to_qfl->qf_qftf = NULL;
+ }
if (from_qfl->qf_count) {
if (copy_loclist_entries(from_qfl, to_qfl) == FAIL) {
@@ -2666,7 +2672,7 @@ static void qf_goto_win_with_qfl_file(int qf_fnum)
static int qf_jump_to_usable_window(int qf_fnum, bool newwin,
int *opened_window)
{
- win_T *usable_win_ptr = NULL;
+ win_T *usable_wp = NULL;
bool usable_win = false;
// If opening a new window, then don't use the location list referred by
@@ -2675,8 +2681,8 @@ static int qf_jump_to_usable_window(int qf_fnum, bool newwin,
qf_info_T *ll_ref = newwin ? NULL : curwin->w_llist_ref;
if (ll_ref != NULL) {
// Find a non-quickfix window with this location list
- usable_win_ptr = qf_find_win_with_loclist(ll_ref);
- if (usable_win_ptr != NULL) {
+ usable_wp = qf_find_win_with_loclist(ll_ref);
+ if (usable_wp != NULL) {
usable_win = true;
}
}
@@ -2704,7 +2710,7 @@ static int qf_jump_to_usable_window(int qf_fnum, bool newwin,
*opened_window = true; // close it when fail
} else {
if (curwin->w_llist_ref != NULL) { // In a location window
- qf_goto_win_with_ll_file(usable_win_ptr, qf_fnum, ll_ref);
+ qf_goto_win_with_ll_file(usable_wp, qf_fnum, ll_ref);
} else { // In a quickfix window
qf_goto_win_with_qfl_file(qf_fnum);
}
@@ -3032,14 +3038,11 @@ theend:
qfl->qf_ptr = qf_ptr;
qfl->qf_index = qf_index;
}
- if (p_swb != old_swb && opened_window) {
+ if (p_swb != old_swb && p_swb == empty_option && opened_window) {
// Restore old 'switchbuf' value, but not when an autocommand or
// modeline has changed the value.
- if (p_swb == empty_option) {
- p_swb = old_swb;
- swb_flags = old_swb_flags;
- } else
- free_string_option(old_swb);
+ p_swb = old_swb;
+ swb_flags = old_swb_flags;
}
}
@@ -3382,6 +3385,7 @@ static void qf_free(qf_list_T *qfl)
XFREE_CLEAR(qfl->qf_title);
tv_free(qfl->qf_ctx);
qfl->qf_ctx = NULL;
+ XFREE_CLEAR(qfl->qf_qftf);
qfl->qf_id = 0;
qfl->qf_changedtick = 0L;
}
@@ -3721,7 +3725,7 @@ void ex_copen(exarg_T *eap)
lnum = qfl->qf_index;
// Fill the buffer with the quickfix list.
- qf_fill_buffer(qfl, curbuf, NULL);
+ qf_fill_buffer(qfl, curbuf, NULL, curwin->handle);
decr_quickfix_busy();
@@ -3884,6 +3888,11 @@ static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last)
buf = qf_find_buf(qi);
if (buf != NULL) {
linenr_T old_line_count = buf->b_ml.ml_line_count;
+ int qf_winid = 0;
+
+ if (IS_LL_STACK(qi)) {
+ qf_winid = curwin->handle;
+ }
if (old_last == NULL) {
// set curwin/curbuf to buf and save a few things
@@ -3892,7 +3901,7 @@ static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last)
qf_update_win_titlevar(qi);
- qf_fill_buffer(qf_get_curlist(qi), buf, old_last);
+ qf_fill_buffer(qf_get_curlist(qi), buf, old_last, qf_winid);
buf_inc_changedtick(buf);
if (old_last == NULL) {
@@ -3911,70 +3920,75 @@ static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last)
}
// Add an error line to the quickfix buffer.
-static int qf_buf_add_line(buf_T *buf, linenr_T lnum, const qfline_T *qfp,
- char_u *dirname, bool first_bufline)
- FUNC_ATTR_NONNULL_ALL
+static int qf_buf_add_line(qf_list_T *qfl, buf_T *buf, linenr_T lnum,
+ const qfline_T *qfp, char_u *dirname,
+ char_u *qftf_str, bool first_bufline)
+ FUNC_ATTR_NONNULL_ARG(1, 2, 4, 5)
{
int len;
buf_T *errbuf;
- if (qfp->qf_module != NULL) {
- STRLCPY(IObuff, qfp->qf_module, IOSIZE - 1);
- len = (int)STRLEN(IObuff);
- } else if (qfp->qf_fnum != 0
- && (errbuf = buflist_findnr(qfp->qf_fnum)) != NULL
- && errbuf->b_fname != NULL) {
- if (qfp->qf_type == 1) { // :helpgrep
- STRLCPY(IObuff, path_tail(errbuf->b_fname), IOSIZE - 1);
- } else {
- // Shorten the file name if not done already.
- // For optimization, do this only for the first entry in a
- // buffer.
- if (first_bufline
- && (errbuf->b_sfname == NULL
- || path_is_absolute(errbuf->b_sfname))) {
- if (*dirname == NUL) {
- os_dirname(dirname, MAXPATHL);
+ if (qftf_str != NULL) {
+ STRLCPY(IObuff, qftf_str, IOSIZE);
+ } else {
+ if (qfp->qf_module != NULL) {
+ STRLCPY(IObuff, qfp->qf_module, IOSIZE);
+ len = (int)STRLEN(IObuff);
+ } else if (qfp->qf_fnum != 0
+ && (errbuf = buflist_findnr(qfp->qf_fnum)) != NULL
+ && errbuf->b_fname != NULL) {
+ if (qfp->qf_type == 1) { // :helpgrep
+ STRLCPY(IObuff, path_tail(errbuf->b_fname), IOSIZE);
+ } else {
+ // Shorten the file name if not done already.
+ // For optimization, do this only for the first entry in a
+ // buffer.
+ if (first_bufline
+ && (errbuf->b_sfname == NULL
+ || path_is_absolute(errbuf->b_sfname))) {
+ if (*dirname == NUL) {
+ os_dirname(dirname, MAXPATHL);
+ }
+ shorten_buf_fname(errbuf, dirname, false);
}
- shorten_buf_fname(errbuf, dirname, false);
+ STRLCPY(IObuff, errbuf->b_fname, IOSIZE);
}
- STRLCPY(IObuff, errbuf->b_fname, IOSIZE - 1);
+ len = (int)STRLEN(IObuff);
+ } else {
+ len = 0;
}
- len = (int)STRLEN(IObuff);
- } else {
- len = 0;
- }
- if (len < IOSIZE - 1) {
- IObuff[len++] = '|';
- }
- if (qfp->qf_lnum > 0) {
- snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), "%" PRId64,
- (int64_t)qfp->qf_lnum);
- len += (int)STRLEN(IObuff + len);
+ if (len < IOSIZE - 1) {
+ IObuff[len++] = '|';
+ }
+ if (qfp->qf_lnum > 0) {
+ snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), "%" PRId64,
+ (int64_t)qfp->qf_lnum);
+ len += (int)STRLEN(IObuff + len);
+
+ if (qfp->qf_col > 0) {
+ snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), " col %d",
+ qfp->qf_col);
+ len += (int)STRLEN(IObuff + len);
+ }
- if (qfp->qf_col > 0) {
- snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), " col %d",
- qfp->qf_col);
+ snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), "%s",
+ (char *)qf_types(qfp->qf_type, qfp->qf_nr));
len += (int)STRLEN(IObuff + len);
+ } else if (qfp->qf_pattern != NULL) {
+ qf_fmt_text(qfp->qf_pattern, IObuff + len, IOSIZE - len);
+ len += (int)STRLEN(IObuff + len);
+ }
+ if (len < IOSIZE - 2) {
+ IObuff[len++] = '|';
+ IObuff[len++] = ' ';
}
- snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), "%s",
- (char *)qf_types(qfp->qf_type, qfp->qf_nr));
- len += (int)STRLEN(IObuff + len);
- } else if (qfp->qf_pattern != NULL) {
- qf_fmt_text(qfp->qf_pattern, IObuff + len, IOSIZE - len);
- len += (int)STRLEN(IObuff + len);
+ // Remove newlines and leading whitespace from the text.
+ // For an unrecognized line keep the indent, the compiler may
+ // mark a word with ^^^^.
+ qf_fmt_text(len > 3 ? skipwhite(qfp->qf_text) : qfp->qf_text,
+ IObuff + len, IOSIZE - len);
}
- if (len < IOSIZE - 2) {
- IObuff[len++] = '|';
- IObuff[len++] = ' ';
- }
-
- // Remove newlines and leading whitespace from the text.
- // For an unrecognized line keep the indent, the compiler may
- // mark a word with ^^^^.
- qf_fmt_text(len > 3 ? skipwhite(qfp->qf_text) : qfp->qf_text,
- IObuff + len, IOSIZE - len);
if (ml_append_buf(buf, lnum, IObuff,
(colnr_T)STRLEN(IObuff) + 1, false) == FAIL) {
@@ -3983,17 +3997,56 @@ static int qf_buf_add_line(buf_T *buf, linenr_T lnum, const qfline_T *qfp,
return OK;
}
+static list_T *call_qftf_func(qf_list_T *qfl,
+ int qf_winid,
+ long start_idx,
+ long end_idx)
+{
+ char_u *qftf = p_qftf;
+ list_T *qftf_list = NULL;
+
+ // If 'quickfixtextfunc' is set, then use the user-supplied function to get
+ // the text to display. Use the local value of 'quickfixtextfunc' if it is
+ // set.
+ if (qfl->qf_qftf != NULL) {
+ qftf = qfl->qf_qftf;
+ }
+ if (qftf != NULL && *qftf != NUL) {
+ typval_T args[1];
+
+ // create the dict argument
+ dict_T *const dict = tv_dict_alloc_lock(VAR_FIXED);
+
+ tv_dict_add_nr(dict, S_LEN("quickfix"), IS_QF_LIST(qfl));
+ tv_dict_add_nr(dict, S_LEN("winid"), qf_winid);
+ tv_dict_add_nr(dict, S_LEN("id"), qfl->qf_id);
+ tv_dict_add_nr(dict, S_LEN("start_idx"), start_idx);
+ tv_dict_add_nr(dict, S_LEN("end_idx"), end_idx);
+ dict->dv_refcount++;
+ args[0].v_type = VAR_DICT;
+ args[0].vval.v_dict = dict;
+
+ qftf_list = call_func_retlist(qftf, 1, args);
+ dict->dv_refcount--;
+ }
+
+ return qftf_list;
+}
+
/// Fill current buffer with quickfix errors, replacing any previous contents.
/// curbuf must be the quickfix buffer!
/// If "old_last" is not NULL append the items after this one.
/// When "old_last" is NULL then "buf" must equal "curbuf"! Because ml_delete()
/// is used and autocommands will be triggered.
-static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last)
+static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last,
+ int qf_winid)
FUNC_ATTR_NONNULL_ARG(2)
{
linenr_T lnum;
qfline_T *qfp;
const bool old_KeyTyped = KeyTyped;
+ list_T *qftf_list = NULL;
+ listitem_T *qftf_li = NULL;
if (old_last == NULL) {
if (buf != curbuf) {
@@ -4026,8 +4079,19 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last)
}
lnum = buf->b_ml.ml_line_count;
}
+
+ qftf_list = call_qftf_func(qfl, qf_winid, lnum + 1, (long)qfl->qf_count);
+ qftf_li = tv_list_first(qftf_list);
+
while (lnum < qfl->qf_count) {
- if (qf_buf_add_line(buf, lnum, qfp, dirname,
+ char_u *qftf_str = NULL;
+
+ if (qftf_li != NULL) {
+ // Use the text supplied by the user defined function
+ qftf_str = (char_u *)tv_get_string_chk(TV_LIST_ITEM_TV(qftf_li));
+ }
+
+ if (qf_buf_add_line(qfl, buf, lnum, qfp, dirname, qftf_str,
prev_bufnr != qfp->qf_fnum) == FAIL) {
break;
}
@@ -4037,6 +4101,10 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last)
if (qfp == NULL) {
break;
}
+
+ if (qftf_li != NULL) {
+ qftf_li = TV_LIST_ITEM_NEXT(qftf_list, qftf_li);
+ }
}
if (old_last == NULL) {
// Delete the empty line which is now at the end
@@ -5665,7 +5733,10 @@ static int get_qfline_items(qfline_T *qfp, list_T *list)
/// Add each quickfix error to list "list" as a dictionary.
/// If qf_idx is -1, use the current list. Otherwise, use the specified list.
-int get_errorlist(qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list)
+/// If eidx is not 0, then return only the specified entry. Otherwise return
+/// all the entries.
+int get_errorlist(qf_info_T *qi_arg, win_T *wp, int qf_idx, int eidx,
+ list_T *list)
{
qf_info_T *qi = qi_arg;
qf_list_T *qfl;
@@ -5682,6 +5753,10 @@ int get_errorlist(qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list)
}
}
+ if (eidx < 0) {
+ return OK;
+ }
+
if (qf_idx == INVALID_QFIDX) {
qf_idx = qi->qf_curlist;
}
@@ -5696,7 +5771,13 @@ int get_errorlist(qf_info_T *qi_arg, win_T *wp, int qf_idx, list_T *list)
}
FOR_ALL_QFL_ITEMS(qfl, qfp, i) {
- get_qfline_items(qfp, list);
+ if (eidx > 0) {
+ if (eidx == i) {
+ return get_qfline_items(qfp, list);
+ }
+ } else if (get_qfline_items(qfp, list) == FAIL) {
+ return FAIL;
+ }
}
return OK;
@@ -5743,7 +5824,7 @@ static int qf_get_list_from_lines(dict_T *what, dictitem_T *di, dict_T *retdict)
if (qf_init_ext(qi, 0, NULL, NULL, &di->di_tv, errorformat,
true, (linenr_T)0, (linenr_T)0, NULL, NULL) > 0) {
- (void)get_errorlist(qi, NULL, 0, l);
+ (void)get_errorlist(qi, NULL, 0, 0, l);
qf_free(&qi->qf_lists[0]);
}
xfree(qi);
@@ -5934,11 +6015,13 @@ static int qf_getprop_filewinid(const win_T *wp, const qf_info_T *qi,
return tv_dict_add_nr(retdict, S_LEN("filewinid"), winid);
}
-/// Return the quickfix list items/entries as 'items' in retdict
-static int qf_getprop_items(qf_info_T *qi, int qf_idx, dict_T *retdict)
+/// Return the quickfix list items/entries as 'items' in retdict.
+/// If eidx is not 0, then return the item at the specified index.
+static int qf_getprop_items(qf_info_T *qi, int qf_idx, int eidx,
+ dict_T *retdict)
{
list_T *l = tv_list_alloc(kListLenMayKnow);
- get_errorlist(qi, NULL, qf_idx, l);
+ get_errorlist(qi, NULL, qf_idx, eidx, l);
tv_dict_add_list(retdict, S_LEN("items"), l);
return OK;
@@ -5963,15 +6046,18 @@ static int qf_getprop_ctx(qf_list_T *qfl, dict_T *retdict)
return status;
}
-/// Return the current quickfix list index as 'idx' in retdict
-static int qf_getprop_idx(qf_list_T *qfl, dict_T *retdict)
+/// Return the current quickfix list index as 'idx' in retdict.
+/// If a specific entry index (eidx) is supplied, then use that.
+static int qf_getprop_idx(qf_list_T *qfl, int eidx, dict_T *retdict)
{
- int curidx = qfl->qf_index;
- if (qf_list_empty(qfl)) {
- // For empty lists, current index is set to 0
- curidx = 0;
+ if (eidx == 0) {
+ eidx = qfl->qf_index;
+ if (qf_list_empty(qfl)) {
+ // For empty lists, current index is set to 0
+ eidx = 0;
+ }
}
- return tv_dict_add_nr(retdict, S_LEN("idx"), curidx);
+ return tv_dict_add_nr(retdict, S_LEN("idx"), eidx);
}
/// Return quickfix/location list details (title) as a dictionary.
@@ -5984,6 +6070,7 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
dictitem_T *di = NULL;
int status = OK;
int qf_idx = INVALID_QFIDX;
+ int eidx = 0;
if ((di = tv_dict_find(what, S_LEN("lines"))) != NULL) {
return qf_get_list_from_lines(what, di, retdict);
@@ -6006,6 +6093,14 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
qfl = qf_get_list(qi, qf_idx);
+ // If an entry index is specified, use that
+ if ((di = tv_dict_find(what, S_LEN("idx"))) != NULL) {
+ if (di->di_tv.v_type != VAR_NUMBER) {
+ return FAIL;
+ }
+ eidx = (int)di->di_tv.vval.v_number;
+ }
+
if (flags & QF_GETLIST_TITLE) {
status = qf_getprop_title(qfl, retdict);
}
@@ -6016,7 +6111,7 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
status = tv_dict_add_nr(retdict, S_LEN("winid"), qf_winid(qi));
}
if ((status == OK) && (flags & QF_GETLIST_ITEMS)) {
- status = qf_getprop_items(qi, qf_idx, retdict);
+ status = qf_getprop_items(qi, qf_idx, eidx, retdict);
}
if ((status == OK) && (flags & QF_GETLIST_CONTEXT)) {
status = qf_getprop_ctx(qfl, retdict);
@@ -6025,7 +6120,7 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
status = tv_dict_add_nr(retdict, S_LEN("id"), qfl->qf_id);
}
if ((status == OK) && (flags & QF_GETLIST_IDX)) {
- status = qf_getprop_idx(qfl, retdict);
+ status = qf_getprop_idx(qfl, eidx, retdict);
}
if ((status == OK) && (flags & QF_GETLIST_SIZE)) {
status = tv_dict_add_nr(retdict, S_LEN("size"),
@@ -6042,6 +6137,17 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict)
return status;
}
+/// Set the current index in the specified quickfix list
+static int qf_setprop_qftf(qf_info_T *qi, qf_list_T *qfl,
+ dictitem_T *di)
+{
+ XFREE_CLEAR(qfl->qf_qftf);
+ if (di->di_tv.v_type == VAR_STRING && di->di_tv.vval.v_string != NULL) {
+ qfl->qf_qftf = vim_strsave(di->di_tv.vval.v_string);
+ }
+ return OK;
+}
+
/// Add a new quickfix entry to list at 'qf_idx' in the stack 'qi' from the
/// items in the dict 'd'. If it is a valid error entry, then set 'valid_entry'
/// to true.
@@ -6407,6 +6513,9 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action,
if ((di = tv_dict_find(what, S_LEN("idx"))) != NULL) {
retval = qf_setprop_curidx(qi, qfl, di);
}
+ if ((di = tv_dict_find(what, S_LEN("quickfixtextfunc"))) != NULL) {
+ retval = qf_setprop_qftf(qi, qfl, di);
+ }
if (newlist || retval == OK) {
qf_list_changed(qfl);
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index e0cc25421a..c2ef217638 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -692,6 +692,7 @@ static char_u *regparse; ///< Input-scan pointer.
static int prevchr_len; ///< byte length of previous char
static int num_complex_braces; ///< Complex \{...} count
static int regnpar; ///< () count.
+static bool wants_nfa; ///< regex should use NFA engine
static int regnzpar; ///< \z() count.
static int re_has_z; ///< \z item detected
static char_u *regcode; ///< Code-emit pointer, or JUST_CALC_SIZE
@@ -2911,7 +2912,7 @@ static int peekchr(void)
at_start = false; // be able to say "/\*ptr"
regparse++;
after_slash++;
- peekchr();
+ (void)peekchr();
regparse--;
after_slash--;
curchr = toggle_Magic(curchr);
@@ -3754,6 +3755,7 @@ static bool reg_match_visual(void)
int mode;
colnr_T start, end;
colnr_T start2, end2;
+ colnr_T curswant;
// Check if the buffer is the current buffer.
if (rex.reg_buf != curbuf || VIsual.lnum == 0) {
@@ -3769,6 +3771,7 @@ static bool reg_match_visual(void)
bot = VIsual;
}
mode = VIsual_mode;
+ curswant = wp->w_curswant;
} else {
if (lt(curbuf->b_visual.vi_start, curbuf->b_visual.vi_end)) {
top = curbuf->b_visual.vi_start;
@@ -3778,6 +3781,7 @@ static bool reg_match_visual(void)
bot = curbuf->b_visual.vi_start;
}
mode = curbuf->b_visual.vi_mode;
+ curswant = curbuf->b_visual.vi_curswant;
}
lnum = rex.lnum + rex.reg_firstlnum;
if (lnum < top.lnum || lnum > bot.lnum) {
@@ -3797,8 +3801,9 @@ static bool reg_match_visual(void)
start = start2;
if (end2 > end)
end = end2;
- if (top.col == MAXCOL || bot.col == MAXCOL)
+ if (top.col == MAXCOL || bot.col == MAXCOL || curswant == MAXCOL) {
end = MAXCOL;
+ }
unsigned int cols_u = win_linetabsize(wp, rex.line,
(colnr_T)(rex.input - rex.line));
assert(cols_u <= MAXCOL);
@@ -3974,17 +3979,25 @@ static bool regmatch(
pos = getmark_buf(rex.reg_buf, mark, false);
if (pos == NULL // mark doesn't exist
- || pos->lnum <= 0 // mark isn't set in reg_buf
- || (pos->lnum == rex.lnum + rex.reg_firstlnum
- ? (pos->col == (colnr_T)(rex.input - rex.line)
- ? (cmp == '<' || cmp == '>')
- : (pos->col < (colnr_T)(rex.input - rex.line)
- ? cmp != '>'
- : cmp != '<'))
- : (pos->lnum < rex.lnum + rex.reg_firstlnum
- ? cmp != '>'
- : cmp != '<'))) {
+ || pos->lnum <= 0) { // mark isn't set in reg_buf
status = RA_NOMATCH;
+ } else {
+ const colnr_T pos_col = pos->lnum == rex.lnum + rex.reg_firstlnum
+ && pos->col == MAXCOL
+ ? (colnr_T)STRLEN(reg_getline(pos->lnum - rex.reg_firstlnum))
+ : pos->col;
+
+ if (pos->lnum == rex.lnum + rex.reg_firstlnum
+ ? (pos_col == (colnr_T)(rex.input - rex.line)
+ ? (cmp == '<' || cmp == '>')
+ : (pos_col < (colnr_T)(rex.input - rex.line)
+ ? cmp != '>'
+ : cmp != '<'))
+ : (pos->lnum < rex.lnum + rex.reg_firstlnum
+ ? cmp != '>'
+ : cmp != '<')) {
+ status = RA_NOMATCH;
+ }
}
}
break;
@@ -7240,7 +7253,7 @@ regprog_T *vim_regcomp(char_u *expr_arg, int re_flags)
// Check for error compiling regexp with initial engine.
if (prog == NULL) {
#ifdef BT_REGEXP_DEBUG_LOG
- // Debugging log for NFA.
+ // Debugging log for BT engine.
if (regexp_engine != BACKTRACKING_ENGINE) {
FILE *f = fopen(BT_REGEXP_DEBUG_LOG_NAME, "a");
if (f) {
@@ -7257,6 +7270,7 @@ regprog_T *vim_regcomp(char_u *expr_arg, int re_flags)
// But don't try if an error message was given.
if (regexp_engine == AUTOMATIC_ENGINE && !called_emsg) {
regexp_engine = BACKTRACKING_ENGINE;
+ report_re_switch(expr);
prog = bt_regengine.regcomp(expr, re_flags);
}
}
diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c
index 923db6422e..35c3285cda 100644
--- a/src/nvim/regexp_nfa.c
+++ b/src/nvim/regexp_nfa.c
@@ -328,6 +328,11 @@ static int *post_start; ///< holds the postfix form of r.e.
static int *post_end;
static int *post_ptr;
+// Set when the pattern should use the NFA engine.
+// E.g. [[:upper:]] only allows 8bit characters for BT engine,
+// while NFA engine handles multibyte characters correctly.
+static bool wants_nfa;
+
static int nstate; ///< Number of states in the NFA. Also used when executing.
static int istate; ///< Index in the state vector, used in alloc_state()
@@ -377,6 +382,7 @@ nfa_regcomp_start (
post_start = (int *)xmalloc(postfix_size);
post_ptr = post_start;
post_end = post_start + nstate_max;
+ wants_nfa = false;
rex.nfa_has_zend = false;
rex.nfa_has_backref = false;
@@ -1455,10 +1461,10 @@ static int nfa_regatom(void)
if (nfa_regatom() == FAIL)
return FAIL;
}
- getchr(); /* get the ] */
- if (n == 0)
- EMSG2_RET_FAIL(_(e_empty_sb),
- reg_magic == MAGIC_ALL);
+ (void)getchr(); // get the ]
+ if (n == 0) {
+ EMSG2_RET_FAIL(_(e_empty_sb), reg_magic == MAGIC_ALL);
+ }
EMIT(NFA_OPT_CHARS);
EMIT(n);
@@ -1618,6 +1624,7 @@ collection:
EMIT(NFA_CLASS_GRAPH);
break;
case CLASS_LOWER:
+ wants_nfa = true;
EMIT(NFA_CLASS_LOWER);
break;
case CLASS_PRINT:
@@ -1630,6 +1637,7 @@ collection:
EMIT(NFA_CLASS_SPACE);
break;
case CLASS_UPPER:
+ wants_nfa = true;
EMIT(NFA_CLASS_UPPER);
break;
case CLASS_XDIGIT:
@@ -1998,10 +2006,17 @@ static int nfa_regpiece(void)
return OK;
}
- // The engine is very inefficient (uses too many states) when the maximum
- // is much larger than the minimum and when the maximum is large. Bail out
- // if we can use the other engine.
- if ((nfa_re_flags & RE_AUTO) && (maxval > 500 || maxval > minval + 200)) {
+ // The engine is very inefficient (uses too many states) when the
+ // maximum is much larger than the minimum and when the maximum is
+ // large. However, when maxval is MAX_LIMIT, it is okay, as this
+ // will emit NFA_STAR.
+ // Bail out if we can use the other engine, but only, when the
+ // pattern does not need the NFA engine like (e.g. [[:upper:]]\{2,\}
+ // does not work with with characters > 8 bit with the BT engine)
+ if ((nfa_re_flags & RE_AUTO)
+ && (maxval > 500 || maxval > minval + 200)
+ && (maxval != MAX_LIMIT && minval < 200)
+ && !wants_nfa) {
return FAIL;
}
@@ -6055,21 +6070,27 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
{
pos_T *pos = getmark_buf(rex.reg_buf, t->state->val, false);
- // Compare the mark position to the match position.
- result = (pos != NULL // mark doesn't exist
- && pos->lnum > 0 // mark isn't set in reg_buf
- && (pos->lnum == rex.lnum + rex.reg_firstlnum
- ? (pos->col == (colnr_T)(rex.input - rex.line)
- ? t->state->c == NFA_MARK
- : (pos->col < (colnr_T)(rex.input - rex.line)
- ? t->state->c == NFA_MARK_GT
- : t->state->c == NFA_MARK_LT))
- : (pos->lnum < rex.lnum + rex.reg_firstlnum
- ? t->state->c == NFA_MARK_GT
- : t->state->c == NFA_MARK_LT)));
- if (result) {
- add_here = true;
- add_state = t->state->out;
+ // Compare the mark position to the match position, if the mark
+ // exists and mark is set in reg_buf.
+ if (pos != NULL && pos->lnum > 0) {
+ const colnr_T pos_col = pos->lnum == rex.lnum + rex.reg_firstlnum
+ && pos->col == MAXCOL
+ ? (colnr_T)STRLEN(reg_getline(pos->lnum - rex.reg_firstlnum))
+ : pos->col;
+
+ result = pos->lnum == rex.lnum + rex.reg_firstlnum
+ ? (pos_col == (colnr_T)(rex.input - rex.line)
+ ? t->state->c == NFA_MARK
+ : (pos_col < (colnr_T)(rex.input - rex.line)
+ ? t->state->c == NFA_MARK_GT
+ : t->state->c == NFA_MARK_LT))
+ : (pos->lnum < rex.lnum + rex.reg_firstlnum
+ ? t->state->c == NFA_MARK_GT
+ : t->state->c == NFA_MARK_LT);
+ if (result) {
+ add_here = true;
+ add_state = t->state->out;
+ }
}
break;
}
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 5151d82c1b..844104e7d0 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -2101,7 +2101,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
bool search_attr_from_match = false; // if search_attr is from :match
bool has_decor = false; // this buffer has decoration
bool do_virttext = false; // draw virtual text for this line
- int win_col_offset; // offsett for window columns
+ int win_col_offset = 0; // offset for window columns
char_u buf_fold[FOLD_TEXT_LEN + 1]; // Hold value returned by get_foldtext
@@ -4138,9 +4138,16 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
// highlight the cursor position itself.
// Also highlight the 'colorcolumn' if it is different than
// 'cursorcolumn'
+ // Also highlight the 'colorcolumn' if 'breakindent' and/or 'showbreak'
+ // options are set
vcol_save_attr = -1;
- if (draw_state == WL_LINE && !lnum_in_visual_area
- && search_attr == 0 && area_attr == 0) {
+ if ((draw_state == WL_LINE
+ || draw_state == WL_BRI
+ || draw_state == WL_SBR)
+ && !lnum_in_visual_area
+ && search_attr == 0
+ && area_attr == 0
+ && filler_todo <= 0) {
if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol
&& lnum != wp->w_cursor.lnum) {
vcol_save_attr = char_attr;
diff --git a/src/nvim/search.c b/src/nvim/search.c
index abe05bbd12..82fc0f9d8e 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -46,38 +46,36 @@
#include "nvim/window.h"
#include "nvim/os/time.h"
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "search.c.generated.h"
#endif
-/*
- * This file contains various searching-related routines. These fall into
- * three groups:
- * 1. string searches (for /, ?, n, and N)
- * 2. character searches within a single line (for f, F, t, T, etc)
- * 3. "other" kinds of searches like the '%' command, and 'word' searches.
- */
-/*
- * String searches
- *
- * The string search functions are divided into two levels:
- * lowest: searchit(); uses a pos_T for starting position and found match.
- * Highest: do_search(); uses curwin->w_cursor; calls searchit().
- *
- * The last search pattern is remembered for repeating the same search.
- * This pattern is shared between the :g, :s, ? and / commands.
- * This is in search_regcomp().
- *
- * The actual string matching is done using a heavily modified version of
- * Henry Spencer's regular expression library. See regexp.c.
- */
+// This file contains various searching-related routines. These fall into
+// three groups:
+// 1. string searches (for /, ?, n, and N)
+// 2. character searches within a single line (for f, F, t, T, etc)
+// 3. "other" kinds of searches like the '%' command, and 'word' searches.
+//
+//
+// String searches
+//
+// The string search functions are divided into two levels:
+// lowest: searchit(); uses a pos_T for starting position and found match.
+// Highest: do_search(); uses curwin->w_cursor; calls searchit().
+//
+// The last search pattern is remembered for repeating the same search.
+// This pattern is shared between the :g, :s, ? and / commands.
+// This is in search_regcomp().
+//
+// The actual string matching is done using a heavily modified version of
+// Henry Spencer's regular expression library. See regexp.c.
+//
+//
+//
+// Two search patterns are remembered: One for the :substitute command and
+// one for other searches. last_idx points to the one that was used the last
+// time.
-/*
- * Two search patterns are remembered: One for the :substitute command and
- * one for other searches. last_idx points to the one that was used the last
- * time.
- */
static struct spat spats[2] =
{
// Last used search pattern
@@ -1002,7 +1000,7 @@ static int first_submatch(regmmatch_T *rp)
/*
* Highest level string search function.
* Search for the 'count'th occurrence of pattern 'pat' in direction 'dirc'
- * If 'dirc' is 0: use previous dir.
+ * If 'dirc' is 0: use previous dir.
* If 'pat' is NULL or empty : use previous string.
* If 'options & SEARCH_REV' : go in reverse of previous dir.
* If 'options & SEARCH_ECHO': echo the search command and handle options
@@ -1042,7 +1040,6 @@ int do_search(
char_u *msgbuf = NULL;
size_t len;
bool has_offset = false;
-#define SEARCH_STAT_BUF_LEN 12
/*
* A line offset is not remembered, this is vi compatible.
@@ -1383,11 +1380,14 @@ int do_search(
&& c != FAIL
&& !shortmess(SHM_SEARCHCOUNT)
&& msgbuf != NULL) {
- search_stat(dirc, &pos, show_top_bot_msg, msgbuf,
- (count != 1
- || has_offset
- || (!(fdo_flags & FDO_SEARCH)
- && hasFolding(curwin->w_cursor.lnum, NULL, NULL))));
+ cmdline_search_stat(dirc, &pos, &curwin->w_cursor,
+ show_top_bot_msg, msgbuf,
+ (count != 1 || has_offset
+ || (!(fdo_flags & FDO_SEARCH)
+ && hasFolding(curwin->w_cursor.lnum, NULL,
+ NULL))),
+ SEARCH_STAT_DEF_MAX_COUNT,
+ SEARCH_STAT_DEF_TIMEOUT);
}
// The search command can be followed by a ';' to do another search.
@@ -1715,10 +1715,10 @@ static void find_mps_values(int *initc, int *findc, bool *backwards,
* '#' look for preprocessor directives
* 'R' look for raw string start: R"delim(text)delim" (only backwards)
*
- * flags: FM_BACKWARD search backwards (when initc is '/', '*' or '#')
- * FM_FORWARD search forwards (when initc is '/', '*' or '#')
- * FM_BLOCKSTOP stop at start/end of block ({ or } in column 0)
- * FM_SKIPCOMM skip comments (not implemented yet!)
+ * flags: FM_BACKWARD search backwards (when initc is '/', '*' or '#')
+ * FM_FORWARD search forwards (when initc is '/', '*' or '#')
+ * FM_BLOCKSTOP stop at start/end of block ({ or } in column 0)
+ * FM_SKIPCOMM skip comments (not implemented yet!)
*
* "oap" is only used to set oap->motion_type for a linewise motion, it can be
* NULL
@@ -3003,7 +3003,7 @@ current_word(
/*
* If the start is on white space, and white space should be included
- * (" word"), or start is not on white space, and white space should
+ * (" word"), or start is not on white space, and white space should
* not be included ("word"), find end of word.
*/
if ((cls() == 0) == include) {
@@ -3012,8 +3012,8 @@ current_word(
} else {
/*
* If the start is not on white space, and white space should be
- * included ("word "), or start is on white space and white
- * space should not be included (" "), find start of word.
+ * included ("word "), or start is on white space and white
+ * space should not be included (" "), find start of word.
* If we end up in the first column of the next line (single char
* word) back up to end of the line.
*/
@@ -4347,121 +4347,287 @@ int linewhite(linenr_T lnum)
}
// Add the search count "[3/19]" to "msgbuf".
-// When "recompute" is true Always recompute the numbers.
-static void search_stat(int dirc, pos_T *pos,
- bool show_top_bot_msg, char_u *msgbuf, bool recompute)
+// See update_search_stat() for other arguments.
+static void cmdline_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos,
+ bool show_top_bot_msg, char_u *msgbuf,
+ bool recompute, int maxcount, long timeout)
+{
+ searchstat_T stat;
+
+ update_search_stat(dirc, pos, cursor_pos, &stat, recompute, maxcount,
+ timeout);
+ if (stat.cur > 0) {
+ char t[SEARCH_STAT_BUF_LEN];
+
+ if (curwin->w_p_rl && *curwin->w_p_rlc == 's') {
+ if (stat.incomplete == 1) {
+ vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
+ } else if (stat.cnt > maxcount && stat.cur > maxcount) {
+ vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]",
+ maxcount, maxcount);
+ } else if (stat.cnt > maxcount) {
+ vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/%d]",
+ maxcount, stat.cur);
+ } else {
+ vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]",
+ stat.cnt, stat.cur);
+ }
+ } else {
+ if (stat.incomplete == 1) {
+ vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
+ } else if (stat.cnt > maxcount && stat.cur > maxcount) {
+ vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]",
+ maxcount, maxcount);
+ } else if (stat.cnt > maxcount) {
+ vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/>%d]",
+ stat.cur, maxcount);
+ } else {
+ vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]",
+ stat.cur, stat.cnt);
+ }
+ }
+
+ size_t len = strlen(t);
+ if (show_top_bot_msg && len + 2 < SEARCH_STAT_BUF_LEN) {
+ memmove(t + 2, t, len);
+ t[0] = 'W';
+ t[1] = ' ';
+ len += 2;
+ }
+
+ memmove(msgbuf + STRLEN(msgbuf) - len, t, len);
+ if (dirc == '?' && stat.cur == maxcount + 1) {
+ stat.cur = -1;
+ }
+
+ // keep the message even after redraw, but don't put in history
+ msg_hist_off = true;
+ msg_ext_set_kind("search_count");
+ give_warning(msgbuf, false);
+ msg_hist_off = false;
+ }
+}
+
+// Add the search count information to "stat".
+// "stat" must not be NULL.
+// When "recompute" is true always recompute the numbers.
+// dirc == 0: don't find the next/previous match (only set the result to "stat")
+// dirc == '/': find the next match
+// dirc == '?': find the previous match
+static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos,
+ searchstat_T *stat, bool recompute, int maxcount,
+ long timeout)
{
- int save_ws = p_ws;
- int wraparound = false;
- pos_T p = (*pos);
- static pos_T lastpos = { 0, 0, 0 };
+ int save_ws = p_ws;
+ bool wraparound = false;
+ pos_T p = (*pos);
+ static pos_T lastpos = { 0, 0, 0 };
static int cur = 0;
static int cnt = 0;
+ static bool exact_match = false;
+ static int incomplete = 0;
+ static int last_maxcount = SEARCH_STAT_DEF_MAX_COUNT;
static int chgtick = 0;
static char_u *lastpat = NULL;
static buf_T *lbuf = NULL;
- proftime_T start;
-#define OUT_OF_TIME 999
+ proftime_T start;
+ memset(stat, 0, sizeof(searchstat_T));
+
+ if (dirc == 0 && !recompute && !EMPTY_POS(lastpos)) {
+ stat->cur = cur;
+ stat->cnt = cnt;
+ stat->exact_match = exact_match;
+ stat->incomplete = incomplete;
+ stat->last_maxcount = last_maxcount;
+ return;
+ }
+ last_maxcount = maxcount;
wraparound = ((dirc == '?' && lt(lastpos, p))
|| (dirc == '/' && lt(p, lastpos)));
// If anything relevant changed the count has to be recomputed.
// STRNICMP ignores case, but we should not ignore case.
// Unfortunately, there is no STRNICMP function.
+ // XXX: above comment should be "no MB_STRCMP function" ?
if (!(chgtick == buf_get_changedtick(curbuf)
&& lastpat != NULL // supress clang/NULL passed as nonnull parameter
&& STRNICMP(lastpat, spats[last_idx].pat, STRLEN(lastpat)) == 0
&& STRLEN(lastpat) == STRLEN(spats[last_idx].pat)
- && equalpos(lastpos, curwin->w_cursor)
+ && equalpos(lastpos, *cursor_pos)
&& lbuf == curbuf)
- || wraparound || cur < 0 || cur > 99 || recompute) {
+ || wraparound || cur < 0 || (maxcount > 0 && cur > maxcount)
+ || recompute) {
cur = 0;
cnt = 0;
+ exact_match = false;
+ incomplete = 0;
clearpos(&lastpos);
lbuf = curbuf;
}
- if (equalpos(lastpos, curwin->w_cursor) && !wraparound
- && (dirc == '/' ? cur < cnt : cur > 0)) {
- cur += dirc == '/' ? 1 : -1;
+ if (equalpos(lastpos, *cursor_pos) && !wraparound
+ && (dirc == 0 || dirc == '/' ? cur < cnt : cur > 0)) {
+ cur += dirc == 0 ? 0 : dirc == '/' ? 1 : -1;
} else {
+ bool done_search = false;
+ pos_T endpos = { 0, 0, 0 };
p_ws = false;
- start = profile_setlimit(20L);
- while (!got_int && searchit(curwin, curbuf, &lastpos, NULL,
+ if (timeout > 0) {
+ start = profile_setlimit(timeout);
+ }
+ while (!got_int && searchit(curwin, curbuf, &lastpos, &endpos,
FORWARD, NULL, 1, SEARCH_KEEP, RE_LAST,
NULL) != FAIL) {
+ done_search = true;
// Stop after passing the time limit.
- if (profile_passed_limit(start)) {
- cnt = OUT_OF_TIME;
- cur = OUT_OF_TIME;
+ if (timeout > 0 && profile_passed_limit(start)) {
+ incomplete = 1;
break;
}
cnt++;
if (ltoreq(lastpos, p)) {
- cur++;
+ cur = cnt;
+ if (lt(p, endpos)) {
+ exact_match = true;
+ }
}
fast_breakcheck();
- if (cnt > 99) {
+ if (maxcount > 0 && cnt > maxcount) {
+ incomplete = 2; // max count exceeded
break;
}
}
if (got_int) {
cur = -1; // abort
}
+ if (done_search) {
+ xfree(lastpat);
+ lastpat = vim_strsave(spats[last_idx].pat);
+ chgtick = buf_get_changedtick(curbuf);
+ lbuf = curbuf;
+ lastpos = p;
+ }
}
- if (cur > 0) {
- char t[SEARCH_STAT_BUF_LEN] = "";
- int len;
+ stat->cur = cur;
+ stat->cnt = cnt;
+ stat->exact_match = exact_match;
+ stat->incomplete = incomplete;
+ stat->last_maxcount = last_maxcount;
+ p_ws = save_ws;
+}
- if (curwin->w_p_rl && *curwin->w_p_rlc == 's') {
- if (cur == OUT_OF_TIME) {
- vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?\?/?]");
- } else if (cnt > 99 && cur > 99) {
- vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>99/>99]");
- } else if (cnt > 99) {
- vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>99/%d]", cur);
- } else {
- vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]", cnt, cur);
+// "searchcount()" function
+void f_searchcount(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ pos_T pos = curwin->w_cursor;
+ char_u *pattern = NULL;
+ int maxcount = SEARCH_STAT_DEF_MAX_COUNT;
+ long timeout = SEARCH_STAT_DEF_TIMEOUT;
+ bool recompute = true;
+ searchstat_T stat;
+
+ tv_dict_alloc_ret(rettv);
+
+ if (shortmess(SHM_SEARCHCOUNT)) { // 'shortmess' contains 'S' flag
+ recompute = true;
+ }
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ dict_T *dict;
+ dictitem_T *di;
+ listitem_T *li;
+ bool error = false;
+
+ if (argvars[0].v_type != VAR_DICT || argvars[0].vval.v_dict == NULL) {
+ EMSG(_(e_dictreq));
+ return;
+ }
+ dict = argvars[0].vval.v_dict;
+ di = tv_dict_find(dict, (const char *)"timeout", -1);
+ if (di != NULL) {
+ timeout = (long)tv_get_number_chk(&di->di_tv, &error);
+ if (error) {
+ return;
}
- } else {
- if (cur == OUT_OF_TIME) {
- vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]");
- } else if (cnt > 99 && cur > 99) {
- vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>99/>99]");
- } else if (cnt > 99) {
- vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/>99]", cur);
- } else {
- vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]", cur, cnt);
+ }
+ di = tv_dict_find(dict, (const char *)"maxcount", -1);
+ if (di != NULL) {
+ maxcount = (int)tv_get_number_chk(&di->di_tv, &error);
+ if (error) {
+ return;
}
}
-
- len = STRLEN(t);
- if (show_top_bot_msg && len + 2 < SEARCH_STAT_BUF_LEN) {
- memmove(t + 2, t, len);
- t[0] = 'W';
- t[1] = ' ';
- len += 2;
+ di = tv_dict_find(dict, (const char *)"recompute", -1);
+ if (di != NULL) {
+ recompute = tv_get_number_chk(&di->di_tv, &error);
+ if (error) {
+ return;
+ }
}
+ di = tv_dict_find(dict, (const char *)"pattern", -1);
+ if (di != NULL) {
+ pattern = (char_u *)tv_get_string_chk(&di->di_tv);
+ if (pattern == NULL) {
+ return;
+ }
+ }
+ di = tv_dict_find(dict, (const char *)"pos", -1);
+ if (di != NULL) {
+ if (di->di_tv.v_type != VAR_LIST) {
+ EMSG2(_(e_invarg2), "pos");
+ return;
+ }
+ if (tv_list_len(di->di_tv.vval.v_list) != 3) {
+ EMSG2(_(e_invarg2), "List format should be [lnum, col, off]");
+ return;
+ }
+ li = tv_list_find(di->di_tv.vval.v_list, 0L);
+ if (li != NULL) {
+ pos.lnum = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error);
+ if (error) {
+ return;
+ }
+ }
+ li = tv_list_find(di->di_tv.vval.v_list, 1L);
+ if (li != NULL) {
+ pos.col = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error) - 1;
+ if (error) {
+ return;
+ }
+ }
+ li = tv_list_find(di->di_tv.vval.v_list, 2L);
+ if (li != NULL) {
+ pos.coladd = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error);
+ if (error) {
+ return;
+ }
+ }
+ }
+ }
- memmove(msgbuf + STRLEN(msgbuf) - len, t, len);
- if (dirc == '?' && cur == 100) {
- cur = -1;
+ save_last_search_pattern();
+ if (pattern != NULL) {
+ if (*pattern == NUL) {
+ goto the_end;
}
+ xfree(spats[last_idx].pat);
+ spats[last_idx].pat = vim_strsave(pattern);
+ }
+ if (spats[last_idx].pat == NULL || *spats[last_idx].pat == NUL) {
+ goto the_end; // the previous pattern was never defined
+ }
- xfree(lastpat);
- lastpat = vim_strsave(spats[last_idx].pat);
- chgtick = buf_get_changedtick(curbuf);
- lbuf = curbuf;
- lastpos = p;
+ update_search_stat(0, &pos, &pos, &stat, recompute, maxcount, timeout);
- // keep the message even after redraw, but don't put in history
- msg_hist_off = true;
- msg_ext_set_kind("search_count");
- give_warning(msgbuf, false);
- msg_hist_off = false;
- }
- p_ws = save_ws;
+ tv_dict_add_nr(rettv->vval.v_dict, S_LEN("current"), stat.cur);
+ tv_dict_add_nr(rettv->vval.v_dict, S_LEN("total"), stat.cnt);
+ tv_dict_add_nr(rettv->vval.v_dict, S_LEN("exact_match"), stat.exact_match);
+ tv_dict_add_nr(rettv->vval.v_dict, S_LEN("incomplete"), stat.incomplete);
+ tv_dict_add_nr(rettv->vval.v_dict, S_LEN("maxcount"), stat.last_maxcount);
+
+the_end:
+ restore_last_search_pattern();
}
/*
diff --git a/src/nvim/search.h b/src/nvim/search.h
index 0366aee8a1..98ddaa5eeb 100644
--- a/src/nvim/search.h
+++ b/src/nvim/search.h
@@ -6,6 +6,7 @@
#include "nvim/vim.h"
#include "nvim/buffer_defs.h"
+#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"
#include "nvim/normal.h"
#include "nvim/os/time.h"
@@ -49,6 +50,11 @@
#define RE_BOTH 2 /* save pat in both patterns */
#define RE_LAST 2 /* use last used pattern if "pat" is NULL */
+// Values for searchcount()
+#define SEARCH_STAT_DEF_TIMEOUT 40L
+#define SEARCH_STAT_DEF_MAX_COUNT 99
+#define SEARCH_STAT_BUF_LEN 12
+
/// Structure containing offset definition for the last search pattern
///
/// @note Only offset for the last search pattern is used, not for the last
@@ -78,6 +84,16 @@ typedef struct {
int sa_wrapped; ///< search wrapped around
} searchit_arg_T;
+typedef struct searchstat
+{
+ int cur; // current position of found words
+ int cnt; // total count of found words
+ int exact_match; // TRUE if matched exactly on specified position
+ int incomplete; // 0: search was fully completed
+ // 1: recomputing was timed out
+ // 2: max count exceeded
+ int last_maxcount; // the max count of the last search
+} searchstat_T;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "search.h.generated.h"
diff --git a/src/nvim/sign.c b/src/nvim/sign.c
index 97e64c6c4c..15fd25e096 100644
--- a/src/nvim/sign.c
+++ b/src/nvim/sign.c
@@ -2012,9 +2012,6 @@ int sign_place_from_dict(
group = NULL;
} else {
group = vim_strsave(group);
- if (group == NULL) {
- return -1;
- }
}
}
@@ -2114,9 +2111,6 @@ int sign_unplace_from_dict(typval_T *group_tv, dict_T *dict)
group = NULL;
} else {
group = vim_strsave(group);
- if (group == NULL) {
- return -1;
- }
}
}
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index f6dc3a04a7..d1428b0117 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -1677,6 +1677,7 @@ static void int_wordlist_spl(char_u *fname)
// Allocate a new slang_T for language "lang". "lang" can be NULL.
// Caller must fill "sl_next".
slang_T *slang_alloc(char_u *lang)
+ FUNC_ATTR_NONNULL_RET
{
slang_T *lp = xcalloc(1, sizeof(slang_T));
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index a6310344e9..ab35c936ca 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -1143,7 +1143,6 @@ static int find_tagfunc_tags(
typval_T args[4];
typval_T rettv;
char_u flagString[4];
- dict_T *d;
taggy_T *tag = &curwin->w_tagstack[curwin->w_tagstackidx];
if (*curbuf->b_p_tfu == NUL) {
@@ -1156,7 +1155,7 @@ static int find_tagfunc_tags(
args[1].vval.v_string = flagString;
// create 'info' dict argument
- d = tv_dict_alloc();
+ dict_T *const d = tv_dict_alloc_lock(VAR_FIXED);
if (tag->user_data != NULL) {
tv_dict_add_str(d, S_LEN("user_data"), (const char *)tag->user_data);
}
@@ -3011,7 +3010,7 @@ static int find_extra(char_u **pp)
// Repeat for addresses separated with ';'
for (;; ) {
if (ascii_isdigit(*str)) {
- str = skipdigits(str);
+ str = skipdigits(str + 1);
} else if (*str == '/' || *str == '?') {
str = skip_regexp(str + 1, *str, false, NULL);
if (*str != first_char) {
diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim
index 0e20ac1593..f456ff4250 100644
--- a/src/nvim/testdir/shared.vim
+++ b/src/nvim/testdir/shared.vim
@@ -179,7 +179,7 @@ endfunc
func s:WaitForCommon(expr, assert, timeout)
" using reltime() is more accurate, but not always available
let slept = 0
- if has('reltime')
+ if exists('*reltimefloat')
let start = reltime()
endif
@@ -204,7 +204,7 @@ func s:WaitForCommon(expr, assert, timeout)
endif
sleep 10m
- if has('reltime')
+ if exists('*reltimefloat')
let slept = float2nr(reltimefloat(reltime(start)) * 1000)
else
let slept += 10
@@ -220,7 +220,7 @@ endfunc
" feeds key-input and resumes process. Return time waited in milliseconds.
" Without +timers it uses simply :sleep.
func Standby(msec)
- if has('timers')
+ if has('timers') && exists('*reltimefloat')
let start = reltime()
let g:_standby_timer = timer_start(a:msec, function('s:feedkeys'))
call getchar()
diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim
index e50602ccad..b5c50b5894 100644
--- a/src/nvim/testdir/test_alot.vim
+++ b/src/nvim/testdir/test_alot.vim
@@ -10,6 +10,7 @@ source test_cursor_func.vim
source test_ex_equal.vim
source test_ex_undo.vim
source test_ex_z.vim
+source test_ex_mode.vim
source test_execute_func.vim
source test_expand_func.vim
source test_feedkeys.vim
diff --git a/src/nvim/testdir/test_arglist.vim b/src/nvim/testdir/test_arglist.vim
index 08e578a226..a1ef8325ec 100644
--- a/src/nvim/testdir/test_arglist.vim
+++ b/src/nvim/testdir/test_arglist.vim
@@ -26,8 +26,6 @@ func Test_argidx()
endfunc
func Test_argadd()
- call Reset_arglist()
-
%argdelete
argadd a b c
call assert_equal(0, argidx())
@@ -105,11 +103,6 @@ func Init_abc()
next
endfunc
-func Reset_arglist()
- cd
- args a | %argd
-endfunc
-
func Assert_argc(l)
call assert_equal(len(a:l), argc())
let i = 0
@@ -122,7 +115,8 @@ endfunc
" Test for [count]argument and [count]argdelete commands
" Ported from the test_argument_count.in test script
func Test_argument()
- call Reset_arglist()
+ " Clean the argument list
+ arga a | %argd
let save_hidden = &hidden
set hidden
@@ -250,7 +244,8 @@ endfunc
" Test for 0argadd and 0argedit
" Ported from the test_argument_0count.in test script
func Test_zero_argadd()
- call Reset_arglist()
+ " Clean the argument list
+ arga a | %argd
arga a b c d
2argu
@@ -277,6 +272,10 @@ func Test_zero_argadd()
call assert_equal('file with spaces', expand('%'))
endfunc
+func Reset_arglist()
+ args a | %argd
+endfunc
+
" Test for argc()
func Test_argc()
call Reset_arglist()
@@ -409,7 +408,6 @@ endfunc
" Test for the :argdelete command
func Test_argdelete()
call Reset_arglist()
-
args aa a aaa b bb
argdelete a*
call assert_equal(['b', 'bb'], argv())
diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
index 5611560b1b..bb84fa498e 100644
--- a/src/nvim/testdir/test_autocmd.vim
+++ b/src/nvim/testdir/test_autocmd.vim
@@ -190,7 +190,6 @@ func Test_autocmd_bufunload_avoiding_SEGV_02()
normal! i1
call assert_fails('edit a.txt', 'E517:')
- call feedkeys("\<CR>")
autocmd! test_autocmd_bufunload
augroup! test_autocmd_bufunload
@@ -452,6 +451,27 @@ func Test_autocmd_bufwipe_in_SessLoadPost()
endfor
endfunc
+" Using :blast and :ball for many events caused a crash, because b_nwindows was
+" not incremented correctly.
+func Test_autocmd_blast_badd()
+ let content =<< trim [CODE]
+ au BufNew,BufAdd,BufWinEnter,BufEnter,BufLeave,BufWinLeave,BufUnload,VimEnter foo* blast
+ edit foo1
+ au BufNew,BufAdd,BufWinEnter,BufEnter,BufLeave,BufWinLeave,BufUnload,VimEnter foo* ball
+ edit foo2
+ call writefile(['OK'], 'Xerrors')
+ qall
+ [CODE]
+
+ call writefile(content, 'XblastBall')
+ call system(GetVimCommand() .. ' --clean -S XblastBall')
+ " call assert_match('OK', readfile('Xerrors')->join())
+ call assert_match('OK', join(readfile('Xerrors')))
+
+ call delete('XblastBall')
+ call delete('Xerrors')
+endfunc
+
" SEGV occurs in older versions.
func Test_autocmd_bufwipe_in_SessLoadPost2()
tabnew
@@ -1949,6 +1969,26 @@ func Test_autocmd_window()
%bw!
endfunc
+" Test for trying to close the tab that has the temporary window for exeucing
+" an autocmd.
+func Test_close_autocmd_tab()
+ edit one.txt
+ tabnew two.txt
+ augroup aucmd_win_test
+ au!
+ au BufEnter * if expand('<afile>') == 'one.txt' | tabfirst | tabonly | endif
+ augroup END
+
+ call assert_fails('doautoall BufEnter', 'E813:')
+
+ tabonly
+ augroup aucmd_win_test
+ au!
+ augroup END
+ augroup! aucmd_win_test
+ %bwipe!
+endfunc
+
func Test_autocmd_closes_window()
au BufNew,BufWinLeave * e %e
file yyy
@@ -1960,4 +2000,13 @@ func Test_autocmd_closes_window()
au! BufWinLeave
endfunc
+func Test_autocmd_closing_cmdwin()
+ au BufWinLeave * nested q
+ call assert_fails("norm 7q?\n", 'E855:')
+
+ au! BufWinLeave
+ new
+ only
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_blockedit.vim b/src/nvim/testdir/test_blockedit.vim
index 527224ccd2..180524cd73 100644
--- a/src/nvim/testdir/test_blockedit.vim
+++ b/src/nvim/testdir/test_blockedit.vim
@@ -1,6 +1,5 @@
" Test for block inserting
"
-" TODO: rewrite test39.in into this new style test
func Test_blockinsert_indent()
new
diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim
index 489b2477e6..34126b49fa 100644
--- a/src/nvim/testdir/test_cmdline.vim
+++ b/src/nvim/testdir/test_cmdline.vim
@@ -477,7 +477,7 @@ func Test_expand_star_star()
call delete('a', 'rf')
endfunc
-func Test_paste_in_cmdline()
+func Test_cmdline_paste()
let @a = "def"
call feedkeys(":abc \<C-R>a ghi\<C-B>\"\<CR>", 'tx')
call assert_equal('"abc def ghi', @:)
@@ -517,18 +517,38 @@ func Test_paste_in_cmdline()
bwipe!
endfunc
-func Test_remove_char_in_cmdline()
- call feedkeys(":abc def\<S-Left>\<Del>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"abc ef', @:)
+func Test_cmdline_remove_char()
+ let encoding_save = &encoding
+
+ " for e in ['utf8', 'latin1']
+ for e in ['utf8']
+ exe 'set encoding=' . e
+
+ call feedkeys(":abc def\<S-Left>\<Del>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"abc ef', @:, e)
+
+ call feedkeys(":abc def\<S-Left>\<BS>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"abcdef', @:)
+
+ call feedkeys(":abc def ghi\<S-Left>\<C-W>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"abc ghi', @:, e)
+
+ call feedkeys(":abc def\<S-Left>\<C-U>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"def', @:, e)
+ endfor
- call feedkeys(":abc def\<S-Left>\<BS>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"abcdef', @:)
+ let &encoding = encoding_save
+endfunc
- call feedkeys(":abc def ghi\<S-Left>\<C-W>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"abc ghi', @:)
+func Test_cmdline_keymap_ctrl_hat()
+ if !has('keymap')
+ return
+ endif
- call feedkeys(":abc def\<S-Left>\<C-U>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"def', @:)
+ set keymap=esperanto
+ call feedkeys(":\"Jxauxdo \<C-^>Jxauxdo \<C-^>Jxauxdo\<CR>", 'tx')
+ call assert_equal('"Jxauxdo Ĵaŭdo Jxauxdo', @:)
+ set keymap=
endfunc
func Test_illegal_address1()
@@ -615,10 +635,20 @@ func Test_cmdline_complete_bang()
endfunc
funct Test_cmdline_complete_languages()
+ let lang = substitute(execute('language time'), '.*"\(.*\)"$', '\1', '')
+ call assert_equal(lang, v:lc_time)
+
+ let lang = substitute(execute('language ctype'), '.*"\(.*\)"$', '\1', '')
+ call assert_equal(lang, v:ctype)
+
+ let lang = substitute(execute('language collate'), '.*"\(.*\)"$', '\1', '')
+ call assert_equal(lang, v:collate)
+
let lang = substitute(execute('language messages'), '.*"\(.*\)"$', '\1', '')
+ call assert_equal(lang, v:lang)
call feedkeys(":language \<c-a>\<c-b>\"\<cr>", 'tx')
- call assert_match('^"language .*\<ctype\>.*\<messages\>.*\<time\>', @:)
+ call assert_match('^"language .*\<collate\>.*\<ctype\>.*\<messages\>.*\<time\>', @:)
if has('unix')
" TODO: these tests don't work on Windows. lang appears to be 'C'
@@ -633,6 +663,9 @@ funct Test_cmdline_complete_languages()
call feedkeys(":language time \<c-a>\<c-b>\"\<cr>", 'tx')
call assert_match('^"language .*\<' . lang . '\>', @:)
+
+ call feedkeys(":language collate \<c-a>\<c-b>\"\<cr>", 'tx')
+ call assert_match('^"language .*\<' . lang . '\>', @:)
endif
endfunc
@@ -850,20 +883,20 @@ func Test_cmdline_overstrike()
" Test overstrike in the middle of the command line.
call feedkeys(":\"01234\<home>\<right>\<right>ab\<right>\<insert>cd\<enter>", 'xt')
- call assert_equal('"0ab1cd4', @:)
+ call assert_equal('"0ab1cd4', @:, e)
" Test overstrike going beyond end of command line.
call feedkeys(":\"01234\<home>\<right>\<right>ab\<right>\<insert>cdefgh\<enter>", 'xt')
- call assert_equal('"0ab1cdefgh', @:)
+ call assert_equal('"0ab1cdefgh', @:, e)
" Test toggling insert/overstrike a few times.
call feedkeys(":\"01234\<home>\<right>ab\<right>\<insert>cd\<right>\<insert>ef\<enter>", 'xt')
- call assert_equal('"ab0cd3ef4', @:)
+ call assert_equal('"ab0cd3ef4', @:, e)
endfor
" Test overstrike with multi-byte characters.
call feedkeys(":\"テキストエディタ\<home>\<right>\<right>ab\<right>\<insert>cd\<enter>", 'xt')
- call assert_equal('"テabキcdエディタ', @:)
+ call assert_equal('"テabキcdエディタ', @:, e)
let &encoding = encoding_save
endfunc
@@ -972,6 +1005,25 @@ func Test_buffers_lastused()
bwipeout bufc
endfunc
+" Test for CmdwinEnter autocmd
+func Test_cmdwin_autocmd()
+ CheckFeature cmdwin
+
+ augroup CmdWin
+ au!
+ autocmd BufLeave * if &buftype == '' | update | endif
+ autocmd CmdwinEnter * startinsert
+ augroup END
+
+ call assert_fails('call feedkeys("q:xyz\<CR>", "xt")', 'E492:')
+ call assert_equal('xyz', @:)
+
+ augroup CmdWin
+ au!
+ augroup END
+ augroup! CmdWin
+endfunc
+
func Test_cmdlineclear_tabenter()
" See test/functional/legacy/cmdline_spec.lua
CheckScreendump
@@ -1020,4 +1072,52 @@ func Test_read_shellcmd()
endif
endfunc
+" Test for recalling newer or older cmdline from history with <Up>, <Down>,
+" <S-Up>, <S-Down>, <PageUp>, <PageDown>, <C-p>, or <C-n>.
+func Test_recalling_cmdline()
+ CheckFeature cmdline_hist
+
+ let g:cmdlines = []
+ cnoremap <Plug>(save-cmdline) <Cmd>let g:cmdlines += [getcmdline()]<CR>
+
+ let histories = [
+ \ {'name': 'cmd', 'enter': ':', 'exit': "\<Esc>"},
+ \ {'name': 'search', 'enter': '/', 'exit': "\<Esc>"},
+ \ {'name': 'expr', 'enter': ":\<C-r>=", 'exit': "\<Esc>\<Esc>"},
+ \ {'name': 'input', 'enter': ":call input('')\<CR>", 'exit': "\<CR>"},
+ "\ TODO: {'name': 'debug', ...}
+ \]
+ let keypairs = [
+ \ {'older': "\<Up>", 'newer': "\<Down>", 'prefixmatch': v:true},
+ \ {'older': "\<S-Up>", 'newer': "\<S-Down>", 'prefixmatch': v:false},
+ \ {'older': "\<PageUp>", 'newer': "\<PageDown>", 'prefixmatch': v:false},
+ \ {'older': "\<C-p>", 'newer': "\<C-n>", 'prefixmatch': v:false},
+ \]
+ let prefix = 'vi'
+ for h in histories
+ call histadd(h.name, 'vim')
+ call histadd(h.name, 'virtue')
+ call histadd(h.name, 'Virgo')
+ call histadd(h.name, 'vogue')
+ call histadd(h.name, 'emacs')
+ for k in keypairs
+ let g:cmdlines = []
+ let keyseqs = h.enter
+ \ .. prefix
+ \ .. repeat(k.older .. "\<Plug>(save-cmdline)", 2)
+ \ .. repeat(k.newer .. "\<Plug>(save-cmdline)", 2)
+ \ .. h.exit
+ call feedkeys(keyseqs, 'xt')
+ call histdel(h.name, -1) " delete the history added by feedkeys above
+ let expect = k.prefixmatch
+ \ ? ['virtue', 'vim', 'virtue', prefix]
+ \ : ['emacs', 'vogue', 'emacs', prefix]
+ call assert_equal(expect, g:cmdlines)
+ endfor
+ endfor
+
+ unlet g:cmdlines
+ cunmap <Plug>(save-cmdline)
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_command_count.vim b/src/nvim/testdir/test_command_count.vim
index 36ff4cd1bb..55b230373f 100644
--- a/src/nvim/testdir/test_command_count.vim
+++ b/src/nvim/testdir/test_command_count.vim
@@ -103,8 +103,6 @@ endfunc
func Test_command_count_2()
silent! %argd
- cd
-
arga a b c d
call assert_fails('5argu', 'E16:')
diff --git a/src/nvim/testdir/test_cscope.vim b/src/nvim/testdir/test_cscope.vim
index e5dab05511..cc6154af69 100644
--- a/src/nvim/testdir/test_cscope.vim
+++ b/src/nvim/testdir/test_cscope.vim
@@ -22,7 +22,7 @@ endfunc
func Test_cscopeWithCscopeConnections()
call CscopeSetupOrClean(1)
- " Test 0: E568: duplicate cscope database not added
+ " Test: E568: duplicate cscope database not added
try
set nocscopeverbose
cscope add Xcscope.out
@@ -33,44 +33,49 @@ func Test_cscopeWithCscopeConnections()
call assert_fails('cscope add', 'E560')
call assert_fails('cscope add Xcscope.out', 'E568')
call assert_fails('cscope add doesnotexist.out', 'E563')
+ if has('unix')
+ call assert_fails('cscope add /dev/null', 'E564:')
+ endif
- " Test 1: Find this C-Symbol
+ " Test: Find this C-Symbol
for cmd in ['cs find s main', 'cs find 0 main']
let a = execute(cmd)
- " Test 1.1 test where it moves the cursor
+ " Test where it moves the cursor
call assert_equal('main(void)', getline('.'))
- " Test 1.2 test the output of the :cs command
+ " Test the output of the :cs command
call assert_match('\n(1 of 1): <<main>> main(void )', a)
endfor
- " Test 2: Find this definition
- for cmd in ['cs find g test_mf_hash', 'cs find 1 test_mf_hash']
+ " Test: Find this definition
+ for cmd in ['cs find g test_mf_hash',
+ \ 'cs find 1 test_mf_hash',
+ \ 'cs find 1 test_mf_hash'] " leading space ignored.
exe cmd
call assert_equal(['', '/*', ' * Test mf_hash_*() functions.', ' */', ' static void', 'test_mf_hash(void)', '{'], getline(line('.')-5, line('.')+1))
endfor
- " Test 3: Find functions called by this function
+ " Test: Find functions called by this function
for cmd in ['cs find d test_mf_hash', 'cs find 2 test_mf_hash']
let a = execute(cmd)
call assert_match('\n(1 of 42): <<mf_hash_init>> mf_hash_init(&ht);', a)
call assert_equal(' mf_hash_init(&ht);', getline('.'))
endfor
- " Test 4: Find functions calling this function
+ " Test: Find functions calling this function
for cmd in ['cs find c test_mf_hash', 'cs find 3 test_mf_hash']
let a = execute(cmd)
call assert_match('\n(1 of 1): <<main>> test_mf_hash();', a)
call assert_equal(' test_mf_hash();', getline('.'))
endfor
- " Test 5: Find this text string
+ " Test: Find this text string
for cmd in ['cs find t Bram', 'cs find 4 Bram']
let a = execute(cmd)
call assert_match('(1 of 1): <<<unknown>>> \* VIM - Vi IMproved^Iby Bram Moolenaar', a)
call assert_equal(' * VIM - Vi IMproved by Bram Moolenaar', getline('.'))
endfor
- " Test 6: Find this egrep pattern
+ " Test: Find this egrep pattern
" test all matches returned by cscope
for cmd in ['cs find e ^\#includ.', 'cs find 6 ^\#includ.']
let a = execute(cmd)
@@ -83,7 +88,7 @@ func Test_cscopeWithCscopeConnections()
call assert_fails('cnext', 'E553:')
endfor
- " Test 7: Find the same egrep pattern using lcscope this time.
+ " Test: Find the same egrep pattern using lcscope this time.
let a = execute('lcs find e ^\#includ.')
call assert_match('\n(1 of 3): <<<unknown>>> #include <assert.h>', a)
call assert_equal('#include <assert.h>', getline('.'))
@@ -93,7 +98,7 @@ func Test_cscopeWithCscopeConnections()
call assert_equal('#include "memfile.c"', getline('.'))
call assert_fails('lnext', 'E553:')
- " Test 8: Find this file
+ " Test: Find this file
for cmd in ['cs find f Xmemfile_test.c', 'cs find 7 Xmemfile_test.c']
enew
let a = execute(cmd)
@@ -101,7 +106,7 @@ func Test_cscopeWithCscopeConnections()
call assert_equal('Xmemfile_test.c', @%)
endfor
- " Test 9: Find files #including this file
+ " Test: Find files #including this file
for cmd in ['cs find i assert.h', 'cs find 8 assert.h']
enew
let a = execute(cmd)
@@ -112,39 +117,42 @@ func Test_cscopeWithCscopeConnections()
call assert_equal('#include <assert.h>', getline('.'))
endfor
- " Test 10: Invalid find command
+ " Test: Invalid find command
+ call assert_fails('cs find', 'E560:')
call assert_fails('cs find x', 'E560:')
- " Test 11: Find places where this symbol is assigned a value
- " this needs a cscope >= 15.8
- " unfortunately, Travis has cscope version 15.7
- let cscope_version = systemlist('cscope --version')[0]
- let cs_version = str2float(matchstr(cscope_version, '\d\+\(\.\d\+\)\?'))
- if cs_version >= 15.8
- for cmd in ['cs find a item', 'cs find 9 item']
- let a = execute(cmd)
- call assert_equal(['', '(1 of 4): <<test_mf_hash>> item = (mf_hashitem_T *)lalloc_clear(sizeof(*item), FALSE);'], split(a, '\n', 1))
- call assert_equal(' item = (mf_hashitem_T *)lalloc_clear(sizeof(*item), FALSE);', getline('.'))
- cnext
- call assert_equal(' item = mf_hash_find(&ht, key);', getline('.'))
- cnext
- call assert_equal(' item = mf_hash_find(&ht, key);', getline('.'))
- cnext
- call assert_equal(' item = mf_hash_find(&ht, key);', getline('.'))
- endfor
+ if has('float')
+ " Test: Find places where this symbol is assigned a value
+ " this needs a cscope >= 15.8
+ " unfortunately, Travis has cscope version 15.7
+ let cscope_version = systemlist('cscope --version')[0]
+ let cs_version = str2float(matchstr(cscope_version, '\d\+\(\.\d\+\)\?'))
+ if cs_version >= 15.8
+ for cmd in ['cs find a item', 'cs find 9 item']
+ let a = execute(cmd)
+ call assert_equal(['', '(1 of 4): <<test_mf_hash>> item = (mf_hashitem_T *)lalloc_clear(sizeof(*item), FALSE);'], split(a, '\n', 1))
+ call assert_equal(' item = (mf_hashitem_T *)lalloc_clear(sizeof(*item), FALSE);', getline('.'))
+ cnext
+ call assert_equal(' item = mf_hash_find(&ht, key);', getline('.'))
+ cnext
+ call assert_equal(' item = mf_hash_find(&ht, key);', getline('.'))
+ cnext
+ call assert_equal(' item = mf_hash_find(&ht, key);', getline('.'))
+ endfor
+ endif
endif
- " Test 12: leading whitespace is not removed for cscope find text
+ " Test: leading whitespace is not removed for cscope find text
let a = execute('cscope find t test_mf_hash')
call assert_equal(['', '(1 of 1): <<<unknown>>> test_mf_hash();'], split(a, '\n', 1))
call assert_equal(' test_mf_hash();', getline('.'))
- " Test 13: test with scscope
+ " Test: test with scscope
let a = execute('scs find t Bram')
call assert_match('(1 of 1): <<<unknown>>> \* VIM - Vi IMproved^Iby Bram Moolenaar', a)
call assert_equal(' * VIM - Vi IMproved by Bram Moolenaar', getline('.'))
- " Test 14: cscope help
+ " Test: cscope help
for cmd in ['cs', 'cs help', 'cs xxx']
let a = execute(cmd)
call assert_match('^cscope commands:\n', a)
@@ -158,28 +166,35 @@ func Test_cscopeWithCscopeConnections()
let a = execute('scscope help')
call assert_match('This cscope command does not support splitting the window\.', a)
- " Test 15: reset connections
+ " Test: reset connections
let a = execute('cscope reset')
call assert_match('\nAdded cscope database.*Xcscope.out (#0)', a)
call assert_match('\nAll cscope databases reset', a)
- " Test 16: cscope show
+ " Test: cscope show
let a = execute('cscope show')
call assert_match('\n 0 \d\+.*Xcscope.out\s*<none>', a)
- " Test 17: cstag and 'csto' option
+ " Test: cstag and 'csto' option
set csto=0
let a = execute('cstag TEST_COUNT')
call assert_match('(1 of 1): <<TEST_COUNT>> #define TEST_COUNT 50000', a)
call assert_equal('#define TEST_COUNT 50000', getline('.'))
+ call assert_fails('cstag DOES_NOT_EXIST', 'E257:')
set csto=1
let a = execute('cstag index_to_key')
call assert_match('(1 of 1): <<index_to_key>> #define index_to_key(i) ((i) ^ 15167)', a)
call assert_equal('#define index_to_key(i) ((i) ^ 15167)', getline('.'))
- call assert_fails('cstag xxx', 'E257:')
+ call assert_fails('cstag DOES_NOT_EXIST', 'E257:')
call assert_fails('cstag', 'E562:')
+ let save_tags = &tags
+ set tags=
+ call assert_fails('cstag DOES_NOT_EXIST', 'E257:')
+ let a = execute('cstag index_to_key')
+ call assert_match('(1 of 1): <<index_to_key>> #define index_to_key(i) ((i) ^ 15167)', a)
+ let &tags = save_tags
- " Test 18: 'cst' option
+ " Test: 'cst' option
set nocst
call assert_fails('tag TEST_COUNT', 'E426:')
set cst
@@ -189,12 +204,28 @@ func Test_cscopeWithCscopeConnections()
let a = execute('tags')
call assert_match('1 1 TEST_COUNT\s\+\d\+\s\+#define index_to_key', a)
- " Test 19: this should trigger call to cs_print_tags()
+ " Test: 'cscoperelative'
+ call mkdir('Xcscoperelative')
+ cd Xcscoperelative
+ let a = execute('cs find g test_mf_hash')
+ call assert_notequal('test_mf_hash(void)', getline('.'))
+ set cscoperelative
+ let a = execute('cs find g test_mf_hash')
+ call assert_equal('test_mf_hash(void)', getline('.'))
+ set nocscoperelative
+ cd ..
+ call delete('Xcscoperelative', 'd')
+
+ " Test: E259: no match found
+ call assert_fails('cscope find g DOES_NOT_EXIST', 'E259:')
+
+ " Test: this should trigger call to cs_print_tags()
" Unclear how to check result though, we just exercise the code.
set cst cscopequickfix=s0
call feedkeys(":cs find s main\<CR>", 't')
- " Test 20: cscope kill
+ " Test: cscope kill
+ call assert_fails('cscope kill', 'E560:')
call assert_fails('cscope kill 2', 'E261:')
call assert_fails('cscope kill xxx', 'E261:')
@@ -211,20 +242,20 @@ func Test_cscopeWithCscopeConnections()
let a = execute('cscope kill -1')
call assert_equal('', a)
- " Test 21: 'csprg' option
+ " Test: 'csprg' option
call assert_equal('cscope', &csprg)
set csprg=doesnotexist
call assert_fails('cscope add Xcscope2.out', 'E609:')
set csprg=cscope
- " Test 22: multiple cscope connections
+ " Test: multiple cscope connections
cscope add Xcscope.out
cscope add Xcscope2.out . -C
let a = execute('cscope show')
call assert_match('\n 0 \d\+.*Xcscope.out\s*<none>', a)
call assert_match('\n 1 \d\+.*Xcscope2.out\s*\.', a)
- " Test 23: test Ex command line completion
+ " Test: test Ex command line completion
call feedkeys(":cs \<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"cs add find help kill reset show', @:)
@@ -240,19 +271,26 @@ func Test_cscopeWithCscopeConnections()
call feedkeys(":cs add Xcscope\<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"cs add Xcscope.out Xcscope2.out', @:)
- " Test 24: cscope_connection()
+ " Test: cscope_connection()
call assert_equal(cscope_connection(), 1)
call assert_equal(cscope_connection(0, 'out'), 1)
call assert_equal(cscope_connection(0, 'xxx'), 1)
+
call assert_equal(cscope_connection(1, 'out'), 1)
call assert_equal(cscope_connection(1, 'xxx'), 0)
+
call assert_equal(cscope_connection(2, 'out'), 0)
+ call assert_equal(cscope_connection(2, getcwd() .. '/Xcscope.out', 1), 1)
+
call assert_equal(cscope_connection(3, 'xxx', '..'), 0)
call assert_equal(cscope_connection(3, 'out', 'xxx'), 0)
call assert_equal(cscope_connection(3, 'out', '.'), 1)
+
call assert_equal(cscope_connection(4, 'out', '.'), 0)
- " CleanUp
+ call assert_equal(cscope_connection(5, 'out'), 0)
+ call assert_equal(cscope_connection(-1, 'out'), 0)
+
call CscopeSetupOrClean(0)
endfunc
diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim
index 21c1f98283..8592f48af7 100644
--- a/src/nvim/testdir/test_diffmode.vim
+++ b/src/nvim/testdir/test_diffmode.vim
@@ -781,17 +781,68 @@ func Test_diff_lastline()
bwipe!
endfunc
+func WriteDiffFiles(buf, list1, list2)
+ call writefile(a:list1, 'Xfile1')
+ call writefile(a:list2, 'Xfile2')
+ if a:buf
+ call term_sendkeys(a:buf, ":checktime\<CR>")
+ endif
+endfunc
+" Verify a screendump with both the internal and external diff.
+func VerifyBoth(buf, dumpfile, extra)
+ " trailing : for leaving the cursor on the command line
+ for cmd in [":set diffopt=filler" . a:extra . "\<CR>:", ":set diffopt+=internal\<CR>:"]
+ call term_sendkeys(a:buf, cmd)
+ if VerifyScreenDump(a:buf, a:dumpfile, {}, cmd =~ 'internal' ? 'internal' : 'external')
+ break " don't let the next iteration overwrite the "failed" file.
+ " don't let the next iteration overwrite the "failed" file.
+ return
+ endif
+ endfor
+
+ " also test unified diff
+ call term_sendkeys(a:buf, ":call SetupUnified()\<CR>:")
+ call term_sendkeys(a:buf, ":redraw!\<CR>:")
+ call VerifyScreenDump(a:buf, a:dumpfile, {}, 'unified')
+ call term_sendkeys(a:buf, ":call StopUnified()\<CR>:")
+endfunc
+
+" Verify a screendump with the internal diff only.
+func VerifyInternal(buf, dumpfile, extra)
+ call term_sendkeys(a:buf, ":diffupdate!\<CR>")
+ " trailing : for leaving the cursor on the command line
+ call term_sendkeys(a:buf, ":set diffopt=internal,filler" . a:extra . "\<CR>:")
+ call TermWait(a:buf)
+ call VerifyScreenDump(a:buf, a:dumpfile, {})
+endfunc
+
func Test_diff_screen()
CheckScreendump
CheckFeature menu
+ let lines =<< trim END
+ func UnifiedDiffExpr()
+ " Prepend some text to check diff type detection
+ call writefile(['warning', ' message'], v:fname_out)
+ silent exe '!diff -U0 ' .. v:fname_in .. ' ' .. v:fname_new .. '>>' .. v:fname_out
+ endfunc
+ func SetupUnified()
+ set diffexpr=UnifiedDiffExpr()
+ diffupdate
+ endfunc
+ func StopUnified()
+ set diffexpr=
+ endfunc
+ END
+ call writefile(lines, 'XdiffSetup')
+
" clean up already existing swap files, just in case
call delete('.Xfile1.swp')
call delete('.Xfile2.swp')
" Test 1: Add a line in beginning of file 2
call WriteDiffFiles(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
- let buf = RunVimInTerminal('-d Xfile1 Xfile2', {})
+ let buf = RunVimInTerminal('-d -S XdiffSetup Xfile1 Xfile2', {})
" Set autoread mode, so that Vim won't complain once we re-write the test
" files
call term_sendkeys(buf, ":set autoread\<CR>\<c-w>w:set autoread\<CR>\<c-w>w")
@@ -911,6 +962,7 @@ func Test_diff_screen()
call StopVimInTerminal(buf)
call delete('Xfile1')
call delete('Xfile2')
+ call delete('XdiffSetup')
endfunc
func Test_diff_with_cursorline()
@@ -1096,4 +1148,38 @@ func Test_diff_and_scroll()
set ls&
endfunc
+func Test_diff_filler_cursorcolumn()
+ CheckScreendump
+
+ let content =<< trim END
+ call setline(1, ['aa', 'bb', 'cc'])
+ vnew
+ call setline(1, ['aa', 'cc'])
+ windo diffthis
+ wincmd p
+ setlocal cursorcolumn foldcolumn=0
+ norm! gg0
+ redraw!
+ END
+ call writefile(content, 'Xtest_diff_cuc')
+ let buf = RunVimInTerminal('-S Xtest_diff_cuc', {})
+
+ call VerifyScreenDump(buf, 'Test_diff_cuc_01', {})
+
+ call term_sendkeys(buf, "l")
+ call term_sendkeys(buf, "\<C-l>")
+ call VerifyScreenDump(buf, 'Test_diff_cuc_02', {})
+ call term_sendkeys(buf, "0j")
+ call term_sendkeys(buf, "\<C-l>")
+ call VerifyScreenDump(buf, 'Test_diff_cuc_03', {})
+ call term_sendkeys(buf, "l")
+ call term_sendkeys(buf, "\<C-l>")
+ call VerifyScreenDump(buf, 'Test_diff_cuc_04', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xtest_diff_cuc')
+endfunc
+
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_ex_mode.vim b/src/nvim/testdir/test_ex_mode.vim
new file mode 100644
index 0000000000..f70cb261e0
--- /dev/null
+++ b/src/nvim/testdir/test_ex_mode.vim
@@ -0,0 +1,82 @@
+" Test editing line in Ex mode (see :help Q and :help gQ).
+
+" Helper function to test editing line in Q Ex mode
+func Ex_Q(cmd)
+ " Is there a simpler way to test editing Ex line?
+ call feedkeys("Q"
+ \ .. "let s:test_ex =<< END\<CR>"
+ \ .. a:cmd .. "\<CR>"
+ \ .. "END\<CR>"
+ \ .. "visual\<CR>", 'tx')
+ return s:test_ex[0]
+endfunc
+
+" Helper function to test editing line in gQ Ex mode
+func Ex_gQ(cmd)
+ call feedkeys("gQ" .. a:cmd .. "\<C-b>\"\<CR>", 'tx')
+ let ret = @:[1:] " Remove leading quote.
+ call feedkeys("visual\<CR>", 'tx')
+ return ret
+endfunc
+
+" Helper function to test editing line with both Q and gQ Ex mode.
+func Ex(cmd)
+ return [Ex_Q(a:cmd), Ex_gQ(a:cmd)]
+endfunc
+
+" Test editing line in Ex mode (both Q and gQ)
+func Test_ex_mode()
+ throw 'skipped: TODO: '
+ let encoding_save = &encoding
+ set sw=2
+
+ " for e in ['utf8', 'latin1']
+ for e in ['utf8']
+ exe 'set encoding=' . e
+
+ call assert_equal(['bar', 'bar'], Ex("foo bar\<C-u>bar"), e)
+ call assert_equal(["1\<C-u>2", "1\<C-u>2"], Ex("1\<C-v>\<C-u>2"), e)
+ call assert_equal(["1\<C-b>2\<C-e>3", '213'], Ex("1\<C-b>2\<C-e>3"), e)
+ call assert_equal(['0123', '2013'], Ex("01\<Home>2\<End>3"), e)
+ call assert_equal(['0123', '0213'], Ex("01\<Left>2\<Right>3"), e)
+ call assert_equal(['01234', '0342'], Ex("012\<Left>\<Left>\<Insert>3\<Insert>4"), e)
+ call assert_equal(["foo bar\<C-w>", 'foo '], Ex("foo bar\<C-w>"), e)
+ call assert_equal(['foo', 'foo'], Ex("fooba\<Del>\<Del>"), e)
+ call assert_equal(["foo\tbar", 'foobar'], Ex("foo\<Tab>bar"), e)
+ call assert_equal(["abbrev\t", 'abbreviate'], Ex("abbrev\<Tab>"), e)
+ call assert_equal([' 1', "1\<C-t>\<C-t>"], Ex("1\<C-t>\<C-t>"), e)
+ call assert_equal([' 1', "1\<C-t>\<C-t>"], Ex("1\<C-t>\<C-t>\<C-d>"), e)
+ call assert_equal([' foo', ' foo'], Ex(" foo\<C-d>"), e)
+ call assert_equal(['foo', ' foo0'], Ex(" foo0\<C-d>"), e)
+ call assert_equal(['foo', ' foo^'], Ex(" foo^\<C-d>"), e)
+ endfor
+
+ set sw&
+ let &encoding = encoding_save
+endfunc
+
+func Test_ex_mode_errors()
+ " Not allowed to enter ex mode when text is locked
+ au InsertCharPre <buffer> normal! gQ<CR>
+ let caught_e523 = 0
+ try
+ call feedkeys("ix\<esc>", 'xt')
+ catch /^Vim\%((\a\+)\)\=:E523/ " catch E523
+ let caught_e523 = 1
+ endtry
+ call assert_equal(1, caught_e523)
+ au! InsertCharPre
+
+ new
+ au CmdLineEnter * call ExEnterFunc()
+ func ExEnterFunc()
+
+ endfunc
+ call feedkeys("gQvi\r", 'xt')
+
+ au! CmdLineEnter
+ delfunc ExEnterFunc
+ quit
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim
index 4c7452fe69..ed2bb2c06b 100644
--- a/src/nvim/testdir/test_excmd.vim
+++ b/src/nvim/testdir/test_excmd.vim
@@ -54,6 +54,132 @@ func Test_buffers_lastused()
bwipeout bufc
endfunc
+" Test for the :copy command
+func Test_copy()
+ new
+
+ call setline(1, ['L1', 'L2', 'L3', 'L4'])
+ " copy lines in a range to inside the range
+ 1,3copy 2
+ call assert_equal(['L1', 'L2', 'L1', 'L2', 'L3', 'L3', 'L4'], getline(1, 7))
+
+ close!
+endfunc
+
+" Test for the :file command
+func Test_file_cmd()
+ call assert_fails('3file', 'E474:')
+ call assert_fails('0,0file', 'E474:')
+ call assert_fails('0file abc', 'E474:')
+endfunc
+
+" Test for the :drop command
+func Test_drop_cmd()
+ call writefile(['L1', 'L2'], 'Xfile')
+ enew | only
+ drop Xfile
+ call assert_equal('L2', getline(2))
+ " Test for switching to an existing window
+ below new
+ drop Xfile
+ call assert_equal(1, winnr())
+ " Test for splitting the current window
+ enew | only
+ set modified
+ drop Xfile
+ call assert_equal(2, winnr('$'))
+ " Check for setting the argument list
+ call assert_equal(['Xfile'], argv())
+ enew | only!
+ call delete('Xfile')
+endfunc
+
+" Test for the :append command
+func Test_append_cmd()
+ new
+ call setline(1, [' L1'])
+ call feedkeys(":append\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt')
+ call assert_equal([' L1', ' L2', ' L3'], getline(1, '$'))
+ %delete _
+ " append after a specific line
+ call setline(1, [' L1', ' L2', ' L3'])
+ call feedkeys(":2append\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt')
+ call assert_equal([' L1', ' L2', ' L4', ' L5', ' L3'], getline(1, '$'))
+ %delete _
+ " append with toggling 'autoindent'
+ call setline(1, [' L1'])
+ call feedkeys(":append!\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt')
+ call assert_equal([' L1', ' L2', ' L3'], getline(1, '$'))
+ call assert_false(&autoindent)
+ %delete _
+ " append with 'autoindent' set and toggling 'autoindent'
+ set autoindent
+ call setline(1, [' L1'])
+ call feedkeys(":append!\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt')
+ call assert_equal([' L1', ' L2', ' L3'], getline(1, '$'))
+ call assert_true(&autoindent)
+ set autoindent&
+ close!
+endfunc
+
+" Test for the :insert command
+func Test_insert_cmd()
+ set noautoindent " test assumes noautoindent, but it's on by default in Nvim
+ new
+ call setline(1, [' L1'])
+ call feedkeys(":insert\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt')
+ call assert_equal([' L2', ' L3', ' L1'], getline(1, '$'))
+ %delete _
+ " insert before a specific line
+ call setline(1, [' L1', ' L2', ' L3'])
+ call feedkeys(":2insert\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt')
+ call assert_equal([' L1', ' L4', ' L5', ' L2', ' L3'], getline(1, '$'))
+ %delete _
+ " insert with toggling 'autoindent'
+ call setline(1, [' L1'])
+ call feedkeys(":insert!\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt')
+ call assert_equal([' L2', ' L3', ' L1'], getline(1, '$'))
+ call assert_false(&autoindent)
+ %delete _
+ " insert with 'autoindent' set and toggling 'autoindent'
+ set autoindent
+ call setline(1, [' L1'])
+ call feedkeys(":insert!\<CR> L2\<CR> L3\<CR>.\<CR>", 'xt')
+ call assert_equal([' L2', ' L3', ' L1'], getline(1, '$'))
+ call assert_true(&autoindent)
+ set autoindent&
+ close!
+endfunc
+
+" Test for the :change command
+func Test_change_cmd()
+ set noautoindent " test assumes noautoindent, but it's on by default in Nvim
+ new
+ call setline(1, [' L1', 'L2', 'L3'])
+ call feedkeys(":change\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt')
+ call assert_equal([' L4', ' L5', 'L2', 'L3'], getline(1, '$'))
+ %delete _
+ " change a specific line
+ call setline(1, [' L1', ' L2', ' L3'])
+ call feedkeys(":2change\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt')
+ call assert_equal([' L1', ' L4', ' L5', ' L3'], getline(1, '$'))
+ %delete _
+ " change with toggling 'autoindent'
+ call setline(1, [' L1', 'L2', 'L3'])
+ call feedkeys(":change!\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt')
+ call assert_equal([' L4', ' L5', 'L2', 'L3'], getline(1, '$'))
+ call assert_false(&autoindent)
+ %delete _
+ " change with 'autoindent' set and toggling 'autoindent'
+ set autoindent
+ call setline(1, [' L1', 'L2', 'L3'])
+ call feedkeys(":change!\<CR> L4\<CR> L5\<CR>.\<CR>", 'xt')
+ call assert_equal([' L4', ' L5', 'L2', 'L3'], getline(1, '$'))
+ call assert_true(&autoindent)
+ set autoindent&
+ close!
+endfunc
+
" Test for the :confirm command dialog
func Test_confirm_cmd()
CheckNotGui
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index 6b1e484c3a..09fdbf4e20 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -148,7 +148,7 @@ let s:filename_checks = {
\ 'dnsmasq': ['/etc/dnsmasq.conf', '/etc/dnsmasq.d/file', 'any/etc/dnsmasq.conf', 'any/etc/dnsmasq.d/file'],
\ 'dockerfile': ['Containerfile', 'Dockerfile', 'file.Dockerfile'],
\ 'dosbatch': ['file.bat', 'file.sys'],
- \ 'dosini': ['.editorconfig', '/etc/pacman.conf', '/etc/yum.conf', 'file.ini', 'npmrc', '.npmrc', 'php.ini', 'php.ini-5', 'php.ini-file', '/etc/yum.repos.d/file', 'any/etc/pacman.conf', 'any/etc/yum.conf', 'any/etc/yum.repos.d/file'],
+ \ 'dosini': ['.editorconfig', '/etc/pacman.conf', '/etc/yum.conf', 'file.ini', 'npmrc', '.npmrc', 'php.ini', 'php.ini-5', 'php.ini-file', '/etc/yum.repos.d/file', 'any/etc/pacman.conf', 'any/etc/yum.conf', 'any/etc/yum.repos.d/file', 'file.wrap'],
\ 'dot': ['file.dot', 'file.gv'],
\ 'dracula': ['file.drac', 'file.drc', 'filelvs', 'filelpe', 'drac.file', 'lpe', 'lvs', 'some-lpe', 'some-lvs'],
\ 'dsl': ['file.dsl'],
@@ -211,7 +211,7 @@ let s:filename_checks = {
\ 'gtkrc': ['.gtkrc', 'gtkrc', '.gtkrc-file', 'gtkrc-file'],
\ 'haml': ['file.haml'],
\ 'hamster': ['file.hsm'],
- \ 'haskell': ['file.hs', 'file.hsc', 'file.hs-boot'],
+ \ 'haskell': ['file.hs', 'file.hsc', 'file.hs-boot', 'file.hsig'],
\ 'haste': ['file.ht'],
\ 'hastepreproc': ['file.htpp'],
\ 'hb': ['file.hb'],
diff --git a/src/nvim/testdir/test_fnameescape.vim b/src/nvim/testdir/test_fnameescape.vim
index 5382b89aa6..0bafdc29fb 100644
--- a/src/nvim/testdir/test_fnameescape.vim
+++ b/src/nvim/testdir/test_fnameescape.vim
@@ -18,4 +18,10 @@ func Test_fnameescape()
endtry
call assert_true(status, "ExclamationMark")
call delete(fname)
+
+ call assert_equal('\-', fnameescape('-'))
+ call assert_equal('\+', fnameescape('+'))
+ call assert_equal('\>', fnameescape('>'))
endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim
index c280aedffb..224ca257ab 100644
--- a/src/nvim/testdir/test_functions.vim
+++ b/src/nvim/testdir/test_functions.vim
@@ -28,12 +28,14 @@ func Test_empty()
call assert_equal(0, empty(1))
call assert_equal(0, empty(-1))
- call assert_equal(1, empty(0.0))
- call assert_equal(1, empty(-0.0))
- call assert_equal(0, empty(1.0))
- call assert_equal(0, empty(-1.0))
- call assert_equal(0, empty(1.0/0.0))
- call assert_equal(0, empty(0.0/0.0))
+ if has('float')
+ call assert_equal(1, empty(0.0))
+ call assert_equal(1, empty(-0.0))
+ call assert_equal(0, empty(1.0))
+ call assert_equal(0, empty(-1.0))
+ call assert_equal(0, empty(1.0/0.0))
+ call assert_equal(0, empty(0.0/0.0))
+ endif
call assert_equal(1, empty([]))
call assert_equal(0, empty(['a']))
@@ -115,7 +117,9 @@ func Test_strwidth()
call assert_fails('call strwidth({->0})', 'E729:')
call assert_fails('call strwidth([])', 'E730:')
call assert_fails('call strwidth({})', 'E731:')
- call assert_fails('call strwidth(1.2)', 'E806:')
+ if has('float')
+ call assert_fails('call strwidth(1.2)', 'E806:')
+ endif
endfor
set ambiwidth&
@@ -319,19 +323,19 @@ func Test_setbufvar_options()
let prev_id = win_getid()
wincmd j
- let wh = winheight('.')
+ let wh = winheight(0)
let dummy_buf = bufnr('dummy_buf1', v:true)
call setbufvar(dummy_buf, '&buftype', 'nofile')
execute 'belowright vertical split #' . dummy_buf
- call assert_equal(wh, winheight('.'))
+ call assert_equal(wh, winheight(0))
let dum1_id = win_getid()
wincmd h
- let wh = winheight('.')
+ let wh = winheight(0)
let dummy_buf = bufnr('dummy_buf2', v:true)
call setbufvar(dummy_buf, '&buftype', 'nofile')
execute 'belowright vertical split #' . dummy_buf
- call assert_equal(wh, winheight('.'))
+ call assert_equal(wh, winheight(0))
bwipe!
call win_gotoid(prev_id)
@@ -1067,6 +1071,22 @@ func Test_inputlist()
call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>3\<cr>", 'tx')
call assert_equal(3, c)
+ " CR to cancel
+ call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>\<cr>", 'tx')
+ call assert_equal(0, c)
+
+ " Esc to cancel
+ call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>\<Esc>", 'tx')
+ call assert_equal(0, c)
+
+ " q to cancel
+ call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>q", 'tx')
+ call assert_equal(0, c)
+
+ " Cancel after inputting a number
+ call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>5q", 'tx')
+ call assert_equal(0, c)
+
call assert_fails('call inputlist("")', 'E686:')
endfunc
@@ -1402,10 +1422,6 @@ func Test_bufadd_bufload()
endfunc
func Test_readdir()
- if isdirectory('Xdir')
- call delete('Xdir', 'rf')
- endif
-
call mkdir('Xdir')
call writefile([], 'Xdir/foo.txt')
call writefile([], 'Xdir/bar.txt')
@@ -1460,19 +1476,4 @@ func Test_default_arg_value()
call assert_equal('msg', HasDefault())
endfunc
-func Test_delete_rf()
- call mkdir('Xdir')
- call writefile([], 'Xdir/foo.txt')
- call writefile([], 'Xdir/bar.txt')
- call mkdir('Xdir/[a-1]') " issue #696
- call writefile([], 'Xdir/[a-1]/foo.txt')
- call writefile([], 'Xdir/[a-1]/bar.txt')
- call assert_true(filereadable('Xdir/foo.txt'))
- call assert_true(filereadable('Xdir/[a-1]/foo.txt'))
-
- call assert_equal(0, delete('Xdir', 'rf'))
- call assert_false(filereadable('Xdir/foo.txt'))
- call assert_false(filereadable('Xdir/[a-1]/foo.txt'))
-endfunc
-
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_ga.vim b/src/nvim/testdir/test_ga.vim
index 87f1382342..ce31edfc7a 100644
--- a/src/nvim/testdir/test_ga.vim
+++ b/src/nvim/testdir/test_ga.vim
@@ -18,6 +18,7 @@ func Test_ga_command()
call assert_equal("\nNUL", Do_ga(''))
call assert_equal("\n<^A> 1, Hex 01, Oct 001, Digr SH", Do_ga("\x01"))
call assert_equal("\n<^I> 9, Hex 09, Oct 011, Digr HT", Do_ga("\t"))
+ call assert_equal("\n<^@> 0, Hex 00, Octal 000", Do_ga("\n"))
call assert_equal("\n<e> 101, Hex 65, Octal 145", Do_ga('e'))
@@ -30,5 +31,13 @@ func Test_ga_command()
call assert_equal("\n<e> 101, Hex 65, Octal 145 < ́> 769, Hex 0301, Octal 1401", Do_ga("e\u0301"))
call assert_equal("\n<e> 101, Hex 65, Octal 145 < ́> 769, Hex 0301, Octal 1401 < ̱> 817, Hex 0331, Octal 1461", Do_ga("e\u0301\u0331"))
call assert_equal("\n<e> 101, Hex 65, Octal 145 < ́> 769, Hex 0301, Octal 1401 < ̱> 817, Hex 0331, Octal 1461 < ̸> 824, Hex 0338, Octal 1470", Do_ga("e\u0301\u0331\u0338"))
+
+ " When using Mac fileformat, CR instead of NL is used for line termination
+ enew!
+ set fileformat=mac
+ call assert_equal("\n<^J> 10, Hex 0a, Oct 012, Digr NU", Do_ga("\r"))
+
bwipe!
endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_glob2regpat.vim b/src/nvim/testdir/test_glob2regpat.vim
index e6e41f13e7..354f239ef1 100644
--- a/src/nvim/testdir/test_glob2regpat.vim
+++ b/src/nvim/testdir/test_glob2regpat.vim
@@ -1,7 +1,9 @@
" Test glob2regpat()
func Test_glob2regpat_invalid()
- call assert_fails('call glob2regpat(1.33)', 'E806:')
+ if has('float')
+ call assert_fails('call glob2regpat(1.33)', 'E806:')
+ endif
call assert_fails('call glob2regpat("}")', 'E219:')
call assert_fails('call glob2regpat("{")', 'E220:')
endfunc
diff --git a/src/nvim/testdir/test_global.vim b/src/nvim/testdir/test_global.vim
index 7ccf2812ff..2de2c412de 100644
--- a/src/nvim/testdir/test_global.vim
+++ b/src/nvim/testdir/test_global.vim
@@ -29,3 +29,11 @@ func Test_nested_global()
call assert_equal(['nothing', '++found', 'found bad', 'bad'], getline(1, 4))
bwipe!
endfunc
+
+func Test_global_error()
+ call assert_fails('g\\a', 'E10:')
+ call assert_fails('g', 'E148:')
+ call assert_fails('g/\(/y', 'E476:')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim
index ce22de09ca..24c9c3580e 100644
--- a/src/nvim/testdir/test_highlight.vim
+++ b/src/nvim/testdir/test_highlight.vim
@@ -595,6 +595,42 @@ func Test_cursorline_with_visualmode()
call delete('Xtest_cursorline_with_visualmode')
endfunc
+func Test_colorcolumn_bri()
+ CheckScreendump
+
+ " check 'colorcolumn' when 'breakindent' is set
+ let lines =<< trim END
+ call setline(1, 'The quick brown fox jumped over the lazy dogs')
+ END
+ call writefile(lines, 'Xtest_colorcolumn_bri')
+ let buf = RunVimInTerminal('-S Xtest_colorcolumn_bri', {'rows': 10,'columns': 40})
+ call term_sendkeys(buf, ":set co=40 linebreak bri briopt=shift:2 cc=40,41,43\<CR>")
+ call TermWait(buf)
+ call VerifyScreenDump(buf, 'Test_colorcolumn_2', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xtest_colorcolumn_bri')
+endfunc
+
+func Test_colorcolumn_sbr()
+ CheckScreendump
+
+ " check 'colorcolumn' when 'showbreak' is set
+ let lines =<< trim END
+ call setline(1, 'The quick brown fox jumped over the lazy dogs')
+ END
+ call writefile(lines, 'Xtest_colorcolumn_srb')
+ let buf = RunVimInTerminal('-S Xtest_colorcolumn_srb', {'rows': 10,'columns': 40})
+ call term_sendkeys(buf, ":set co=40 showbreak=+++>\\ cc=40,41,43\<CR>")
+ call TermWait(buf)
+ call VerifyScreenDump(buf, 'Test_colorcolumn_3', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xtest_colorcolumn_srb')
+endfunc
+
" This test must come before the Test_cursorline test, as it appears this
" defines the Normal highlighting group anyway.
func Test_1_highlight_Normalgroup_exists()
diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim
index affb141a26..5152af8f58 100644
--- a/src/nvim/testdir/test_listdict.vim
+++ b/src/nvim/testdir/test_listdict.vim
@@ -689,7 +689,9 @@ func Test_listdict_extend()
let l = [1, 2, 3]
call assert_fails("call extend(l, [4, 5, 6], 4)", 'E684:')
call assert_fails("call extend(l, [4, 5, 6], -4)", 'E684:')
- call assert_fails("call extend(l, [4, 5, 6], 1.2)", 'E805:')
+ if has('float')
+ call assert_fails("call extend(l, [4, 5, 6], 1.2)", 'E805:')
+ endif
" Test extend() with dictionaries.
@@ -713,7 +715,9 @@ func Test_listdict_extend()
let d = {'a': 'A', 'b': 'B'}
call assert_fails("call extend(d, {'b': 0, 'c':'C'}, 'error')", 'E737:')
call assert_fails("call extend(d, {'b': 0, 'c':'C'}, 'xxx')", 'E475:')
- call assert_fails("call extend(d, {'b': 0, 'c':'C'}, 1.2)", 'E806:')
+ if has('float')
+ call assert_fails("call extend(d, {'b': 0, 'c':'C'}, 1.2)", 'E806:')
+ endif
call assert_equal({'a': 'A', 'b': 'B'}, d)
call assert_fails("call extend([1, 2], 1)", 'E712:')
diff --git a/src/nvim/testdir/test_move.vim b/src/nvim/testdir/test_move.vim
index d774c93dbd..f666a904b0 100644
--- a/src/nvim/testdir/test_move.vim
+++ b/src/nvim/testdir/test_move.vim
@@ -35,6 +35,11 @@ func Test_move()
call assert_fails('1,2move 1', 'E134')
call assert_fails('2,3move 2', 'E134')
+ call assert_fails("move -100", 'E16:')
+ call assert_fails("move +100", 'E16:')
+ call assert_fails('move', 'E16:')
%bwipeout!
endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim
index 7a846e5ea0..4a00999c45 100644
--- a/src/nvim/testdir/test_normal.vim
+++ b/src/nvim/testdir/test_normal.vim
@@ -512,6 +512,12 @@ func Test_normal14_page_eol()
bw!
endfunc
+" Test for errors with z command
+func Test_normal_z_error()
+ call assert_beeps('normal! z2p')
+ call assert_beeps('normal! zq')
+endfunc
+
func Test_normal15_z_scroll_vert()
" basic test for z commands that scroll the window
call Setup_NewWindow()
@@ -2877,3 +2883,35 @@ func Test_normal_gk()
bw!
set cpoptions& number& numberwidth&
endfunc
+
+" Some commands like yy, cc, dd, >>, << and !! accept a count after
+" typing the first letter of the command.
+func Test_normal_count_after_operator()
+ new
+ setlocal shiftwidth=4 tabstop=8 autoindent
+ call setline(1, ['one', 'two', 'three', 'four', 'five'])
+ let @a = ''
+ normal! j"ay4y
+ call assert_equal("two\nthree\nfour\nfive\n", @a)
+ normal! 3G>2>
+ call assert_equal(['one', 'two', ' three', ' four', 'five'],
+ \ getline(1, '$'))
+ exe "normal! 3G0c2cred\nblue"
+ call assert_equal(['one', 'two', ' red', ' blue', 'five'],
+ \ getline(1, '$'))
+ exe "normal! gg<8<"
+ call assert_equal(['one', 'two', 'red', 'blue', 'five'],
+ \ getline(1, '$'))
+ exe "normal! ggd3d"
+ call assert_equal(['blue', 'five'], getline(1, '$'))
+ call setline(1, range(1, 4))
+ call feedkeys("gg!3!\<C-B>\"\<CR>", 'xt')
+ call assert_equal('".,.+2!', @:)
+ call feedkeys("gg!1!\<C-B>\"\<CR>", 'xt')
+ call assert_equal('".!', @:)
+ call feedkeys("gg!9!\<C-B>\"\<CR>", 'xt')
+ call assert_equal('".,$!', @:)
+ bw!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim
index 5aef33cb09..8796af7a20 100644
--- a/src/nvim/testdir/test_options.vim
+++ b/src/nvim/testdir/test_options.vim
@@ -629,6 +629,25 @@ func Test_visualbell()
set belloff=all
endfunc
+" Test for the 'write' option
+func Test_write()
+ new
+ call setline(1, ['L1'])
+ set nowrite
+ call assert_fails('write Xfile', 'E142:')
+ set write
+ close!
+endfunc
+
+" Test for 'buftype' option
+func Test_buftype()
+ new
+ call setline(1, ['L1'])
+ set buftype=nowrite
+ call assert_fails('write', 'E382:')
+ close!
+endfunc
+
" Test for setting option values using v:false and v:true
func Test_opt_boolean()
set number&
diff --git a/src/nvim/testdir/test_partial.vim b/src/nvim/testdir/test_partial.vim
index 52aac05ea1..8c90f21600 100644
--- a/src/nvim/testdir/test_partial.vim
+++ b/src/nvim/testdir/test_partial.vim
@@ -105,7 +105,7 @@ fun InnerCall(funcref)
endfu
fun OuterCall()
- let opt = { 'func' : function('sin') }
+ let opt = { 'func' : function('max') }
call InnerCall(opt.func)
endfu
diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim
index 9443958984..06bdb1236a 100644
--- a/src/nvim/testdir/test_popup.vim
+++ b/src/nvim/testdir/test_popup.vim
@@ -850,6 +850,34 @@ func Test_popup_position()
call delete('Xtest')
endfunc
+func Test_popup_command()
+ if !CanRunVimInTerminal() || !has('menu')
+ return
+ endif
+
+ call writefile([
+ \ 'one two three four five',
+ \ 'and one two Xthree four five',
+ \ 'one more two three four five',
+ \ ], 'Xtest')
+ let buf = RunVimInTerminal('Xtest', {})
+ call term_sendkeys(buf, ":source $VIMRUNTIME/menu.vim\<CR>")
+ call term_sendkeys(buf, "/X\<CR>:popup PopUp\<CR>")
+ call VerifyScreenDump(buf, 'Test_popup_command_01', {})
+
+ " Select a word
+ call term_sendkeys(buf, "jj")
+ call VerifyScreenDump(buf, 'Test_popup_command_02', {})
+
+ " Select a word
+ call term_sendkeys(buf, "j\<CR>")
+ call VerifyScreenDump(buf, 'Test_popup_command_03', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+ call delete('Xtest')
+endfunc
+
func Test_popup_complete_backwards()
new
call setline(1, ['Post', 'Port', 'Po'])
@@ -1077,4 +1105,77 @@ func Test_pum_getpos()
unlet g:pum_pos
endfunc
+" Test for the popup menu with the 'rightleft' option set
+func Test_pum_rightleft()
+ CheckFeature rightleft
+ CheckScreendump
+
+ let lines =<< trim END
+ abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
+ vim
+ victory
+ END
+ call writefile(lines, 'Xtest1')
+ let buf = RunVimInTerminal('--cmd "set rightleft" Xtest1', {})
+ call term_wait(buf)
+ call term_sendkeys(buf, "Go\<C-P>")
+ call term_wait(buf)
+ call VerifyScreenDump(buf, 'Test_pum_rightleft_01', {'rows': 8})
+ call term_sendkeys(buf, "\<C-P>\<C-Y>")
+ call term_wait(buf)
+ redraw!
+ call assert_match('\s*miv', Screenline(5))
+
+ " Test for expanding tabs to spaces in the popup menu
+ let lines =<< trim END
+ one two
+ one three
+ four
+ END
+ call writefile(lines, 'Xtest2')
+ call term_sendkeys(buf, "\<Esc>:e! Xtest2\<CR>")
+ call term_wait(buf)
+ call term_sendkeys(buf, "Goone\<C-X>\<C-L>")
+ call term_wait(buf)
+ redraw!
+ call VerifyScreenDump(buf, 'Test_pum_rightleft_02', {'rows': 7})
+ call term_sendkeys(buf, "\<C-Y>")
+ call term_wait(buf)
+ redraw!
+ call assert_match('\s*eerht eno', Screenline(4))
+
+ call StopVimInTerminal(buf)
+ call delete('Xtest1')
+ call delete('Xtest2')
+endfunc
+
+" Test for a popup menu with a scrollbar
+func Test_pum_scrollbar()
+ CheckScreendump
+ let lines =<< trim END
+ one
+ two
+ three
+ END
+ call writefile(lines, 'Xtest1')
+ let buf = RunVimInTerminal('--cmd "set pumheight=2" Xtest1', {})
+ call term_wait(buf)
+ call term_sendkeys(buf, "Go\<C-P>\<C-P>\<C-P>")
+ call term_wait(buf)
+ call VerifyScreenDump(buf, 'Test_pum_scrollbar_01', {'rows': 7})
+ call term_sendkeys(buf, "\<C-E>\<Esc>dd")
+ call term_wait(buf)
+
+ if has('rightleft')
+ call term_sendkeys(buf, ":set rightleft\<CR>")
+ call term_wait(buf)
+ call term_sendkeys(buf, "Go\<C-P>\<C-P>\<C-P>")
+ call term_wait(buf)
+ call VerifyScreenDump(buf, 'Test_pum_scrollbar_02', {'rows': 7})
+ endif
+
+ call StopVimInTerminal(buf)
+ call delete('Xtest1')
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim
index 16b6a5f464..bf15f7f52b 100644
--- a/src/nvim/testdir/test_quickfix.vim
+++ b/src/nvim/testdir/test_quickfix.vim
@@ -14,7 +14,7 @@ func s:setup_commands(cchar)
command! -nargs=* Xaddexpr <mods>caddexpr <args>
command! -nargs=* -count Xolder <mods><count>colder <args>
command! -nargs=* Xnewer <mods>cnewer <args>
- command! -nargs=* Xopen <mods>copen <args>
+ command! -nargs=* Xopen <mods> copen <args>
command! -nargs=* Xwindow <mods>cwindow <args>
command! -nargs=* Xbottom <mods>cbottom <args>
command! -nargs=* Xclose <mods>cclose <args>
@@ -32,8 +32,8 @@ func s:setup_commands(cchar)
command! -count -nargs=* -bang Xnfile <mods><count>cnfile<bang> <args>
command! -nargs=* -bang Xpfile <mods>cpfile<bang> <args>
command! -nargs=* Xexpr <mods>cexpr <args>
- command! -range -nargs=* Xvimgrep <mods><count>vimgrep <args>
- command! -nargs=* Xvimgrepadd <mods>vimgrepadd <args>
+ command! -count -nargs=* Xvimgrep <mods> <count>vimgrep <args>
+ command! -nargs=* Xvimgrepadd <mods> vimgrepadd <args>
command! -nargs=* Xgrep <mods> grep <args>
command! -nargs=* Xgrepadd <mods> grepadd <args>
command! -nargs=* Xhelpgrep helpgrep <args>
@@ -51,7 +51,7 @@ func s:setup_commands(cchar)
command! -nargs=* Xaddexpr <mods>laddexpr <args>
command! -nargs=* -count Xolder <mods><count>lolder <args>
command! -nargs=* Xnewer <mods>lnewer <args>
- command! -nargs=* Xopen <mods>lopen <args>
+ command! -nargs=* Xopen <mods> lopen <args>
command! -nargs=* Xwindow <mods>lwindow <args>
command! -nargs=* Xbottom <mods>lbottom <args>
command! -nargs=* Xclose <mods>lclose <args>
@@ -69,8 +69,8 @@ func s:setup_commands(cchar)
command! -count -nargs=* -bang Xnfile <mods><count>lnfile<bang> <args>
command! -nargs=* -bang Xpfile <mods>lpfile<bang> <args>
command! -nargs=* Xexpr <mods>lexpr <args>
- command! -range -nargs=* Xvimgrep <mods><count>lvimgrep <args>
- command! -nargs=* Xvimgrepadd <mods>lvimgrepadd <args>
+ command! -count -nargs=* Xvimgrep <mods> <count>lvimgrep <args>
+ command! -nargs=* Xvimgrepadd <mods> lvimgrepadd <args>
command! -nargs=* Xgrep <mods> lgrep <args>
command! -nargs=* Xgrepadd <mods> lgrepadd <args>
command! -nargs=* Xhelpgrep lhelpgrep <args>
@@ -157,6 +157,12 @@ func XlistTests(cchar)
\ ' 2 Data.Text:20 col 10 warning 22: ModuleWarning',
\ ' 3 Data/Text.hs:30 col 15 warning 33: FileWarning'], l)
+ " For help entries in the quickfix list, only the filename without directory
+ " should be displayed
+ Xhelpgrep setqflist()
+ let l = split(execute('Xlist 1', ''), "\n")
+ call assert_match('^ 1 [^\\/]\{-}:', l[0])
+
" Error cases
call assert_fails('Xlist abc', 'E488:')
endfunc
@@ -255,13 +261,13 @@ func XwindowTests(cchar)
" Open the window
Xopen 5
call assert_true(winnr('$') == 2 && getline('.') ==# '|| non-error 1'
- \ && winheight('.') == 5)
+ \ && winheight(0) == 5)
" Opening the window again, should move the cursor to that window
wincmd t
Xopen 7
call assert_true(winnr('$') == 2 && winnr() == 2 &&
- \ winheight('.') == 7 &&
+ \ winheight(0) == 7 &&
\ getline('.') ==# '|| non-error 1')
" :cnext in quickfix window should move to the next entry
@@ -272,6 +278,14 @@ func XwindowTests(cchar)
Xwindow
call assert_true(winnr('$') == 1)
+ " Specifying the width should adjust the width for a vertically split
+ " quickfix window.
+ vert Xopen
+ call assert_equal(10, winwidth(0))
+ vert Xopen 12
+ call assert_equal(12, winwidth(0))
+ Xclose
+
if a:cchar == 'c'
" Opening the quickfix window in multiple tab pages should reuse the
" quickfix buffer
@@ -352,6 +366,13 @@ func XfileTests(cchar)
\ l[0].lnum == 222 && l[0].col == 77 && l[0].text ==# 'Line 222' &&
\ l[1].lnum == 333 && l[1].col == 88 && l[1].text ==# 'Line 333')
+ " Test for a file with a long line and without a newline at the end
+ let text = repeat('x', 1024)
+ let t = 'a.txt:18:' . text
+ call writefile([t], 'Xqftestfile1', 'b')
+ silent! Xfile Xqftestfile1
+ call assert_equal(text, g:Xgetlist()[0].text)
+
call delete('Xqftestfile1')
endfunc
@@ -475,6 +496,12 @@ func Xtest_browse(cchar)
call assert_equal(5, g:Xgetlist({'idx':0}).idx)
2Xcc
call assert_equal(2, g:Xgetlist({'idx':0}).idx)
+ if a:cchar == 'c'
+ cc
+ else
+ ll
+ endif
+ call assert_equal(2, g:Xgetlist({'idx':0}).idx)
10Xcc
call assert_equal(6, g:Xgetlist({'idx':0}).idx)
Xlast
@@ -483,6 +510,14 @@ func Xtest_browse(cchar)
call assert_equal(11, line('.'))
call assert_fails('Xnext', 'E553')
call assert_fails('Xnfile', 'E553')
+ " To process the range using quickfix list entries, directly use the
+ " quickfix commands (don't use the user defined commands)
+ if a:cchar == 'c'
+ $cc
+ else
+ $ll
+ endif
+ call assert_equal(6, g:Xgetlist({'idx':0}).idx)
Xrewind
call assert_equal('Xqftestfile1', bufname('%'))
call assert_equal(5, line('.'))
@@ -1011,9 +1046,10 @@ endfunc
" Tests for %D and %X errorformat options
func Test_efm_dirstack()
" Create the directory stack and files
- call mkdir('dir1/a', 'p')
- call mkdir('dir1/a/b', 'p')
- call mkdir('dir1/c', 'p')
+ call mkdir('dir1')
+ call mkdir('dir1/a')
+ call mkdir('dir1/a/b')
+ call mkdir('dir1/c')
call mkdir('dir2')
let lines =<< trim [DATA]
@@ -1094,6 +1130,10 @@ func Xinvalid_efm_Tests(cchar)
set efm=%f:%l:%m,%f:%l:%m:%R
call assert_fails('Xexpr "abc.txt:1:Hello world"', 'E377:')
+ " Invalid regular expression
+ set efm=%\\%%k
+ call assert_fails('Xexpr "abc.txt:1:Hello world"', 'E867:')
+
set efm=
call assert_fails('Xexpr "abc.txt:1:Hello world"', 'E378:')
@@ -1124,6 +1164,11 @@ func Test_efm2()
let l = split(execute('clist', ''), "\n")
call assert_equal([' 1 Xtestfile:^\VLine search text\$: '], l)
+ " Test for a long line
+ cexpr 'Xtestfile:' . repeat('a', 1026)
+ let l = getqflist()
+ call assert_equal('^\V' . repeat('a', 1019) . '\$', l[0].pattern)
+
" Test for %P, %Q and %t format specifiers
let lines =<< trim [DATA]
[Xtestfile1]
@@ -1161,6 +1206,14 @@ func Test_efm2()
call delete('Xtestfile2')
call delete('Xtestfile3')
+ " Test for %P, %Q with non-existing files
+ cexpr lines
+ let l = getqflist()
+ call assert_equal(14, len(l))
+ call assert_equal('[Xtestfile1]', l[0].text)
+ call assert_equal('[Xtestfile2]', l[6].text)
+ call assert_equal('[Xtestfile3]', l[9].text)
+
" Tests for %E, %C and %Z format specifiers
let lines =<< trim [DATA]
Error 275
@@ -1202,18 +1255,19 @@ func Test_efm2()
File "/usr/lib/python2.2/unittest.py", line 286, in
failUnlessEqual
raise self.failureException, \\
- AssertionError: 34 != 33
+ W:AssertionError: 34 != 33
--------------------------------------------------------------
Ran 27 tests in 0.063s
[DATA]
- set efm=%C\ %.%#,%A\ \ File\ \"%f\"\\,\ line\ %l%.%#,%Z%[%^\ ]%\\@=%m
+ set efm=%C\ %.%#,%A\ \ File\ \"%f\"\\,\ line\ %l%.%#,%Z%[%^\ ]%\\@=%t:%m
cgetexpr lines
let l = getqflist()
call assert_equal(8, len(l))
call assert_equal(89, l[4].lnum)
call assert_equal(1, l[4].valid)
call assert_equal(expand('unittests/dbfacadeTest.py'), bufname(l[4].bufnr))
+ call assert_equal('W', l[4].type)
" Test for %o
set efm=%f(%o):%l\ %m
@@ -1230,6 +1284,14 @@ func Test_efm2()
bd
call delete("Xotestfile")
+ " Test for a long module name
+ cexpr 'Xtest(' . repeat('m', 1026) . '):15 message'
+ let l = getqflist()
+ " call assert_equal(repeat('m', 1024), l[0].module)
+ call assert_equal(repeat('m', 1023), l[0].module)
+ call assert_equal(15, l[0].lnum)
+ call assert_equal('message', l[0].text)
+
" The following sequence of commands used to crash Vim
set efm=%W%m
cgetexpr ['msg1']
@@ -1715,9 +1777,11 @@ func Test_switchbuf()
call assert_equal(winid, win_getid())
2cnext
call assert_equal(winid, win_getid())
- enew
+ " Test for 'switchbuf' set to search for files in windows in the current
+ " tabpage and jump to an existing window (if present)
set switchbuf=useopen
+ enew
cfirst | cnext
call assert_equal(file1_winid, win_getid())
2cnext
@@ -1725,6 +1789,8 @@ func Test_switchbuf()
2cnext
call assert_equal(file2_winid, win_getid())
+ " Test for 'switchbuf' set to search for files in tabpages and jump to an
+ " existing tabpage (if present)
enew | only
set switchbuf=usetab
tabedit Xqftestfile1
@@ -1743,6 +1809,7 @@ func Test_switchbuf()
call assert_equal(4, tabpagenr())
tabfirst | tabonly | enew
+ " Test for 'switchbuf' set to open a new window for every file
set switchbuf=split
cfirst | cnext
call assert_equal(1, winnr('$'))
@@ -1750,9 +1817,10 @@ func Test_switchbuf()
call assert_equal(2, winnr('$'))
cnext | cnext
call assert_equal(3, winnr('$'))
- enew | only
+ " Test for 'switchbuf' set to open a new tabpage for every file
set switchbuf=newtab
+ enew | only
cfirst | cnext
call assert_equal(1, tabpagenr('$'))
cnext | cnext
@@ -1769,6 +1837,8 @@ func Test_switchbuf()
call assert_equal(last_winid, win_getid())
enew | only
+ " With an empty 'switchbuf', jumping to a quickfix entry should open the
+ " file in an existing window (if present)
set switchbuf=
edit Xqftestfile1
let file1_winid = win_getid()
@@ -1798,6 +1868,32 @@ func Test_switchbuf()
call assert_equal(4, tabpagenr())
tabfirst | tabonly | enew | only
+ " Jumping to a file that is not present in any of the tabpages and the
+ " current tabpage doesn't have any usable windows, should open it in a new
+ " window in the current tabpage.
+ copen | only
+ cfirst
+ call assert_equal(1, tabpagenr())
+ call assert_equal('Xqftestfile1', bufname(''))
+
+ " If opening a file changes 'switchbuf', then the new value should be
+ " retained.
+ call writefile(["vim: switchbuf=split"], 'Xqftestfile1')
+ enew | only
+ set switchbuf&vim
+ cexpr "Xqftestfile1:1:10"
+ call assert_equal('split', &switchbuf)
+ call writefile(["vim: switchbuf=usetab"], 'Xqftestfile1')
+ enew | only
+ set switchbuf=useopen
+ cexpr "Xqftestfile1:1:10"
+ call assert_equal('usetab', &switchbuf)
+ call writefile(["vim: switchbuf&vim"], 'Xqftestfile1')
+ enew | only
+ set switchbuf=useopen
+ cexpr "Xqftestfile1:1:10"
+ call assert_equal('', &switchbuf)
+
call delete('Xqftestfile1')
call delete('Xqftestfile2')
call delete('Xqftestfile3')
@@ -1824,11 +1920,16 @@ func Xadjust_qflnum(cchar)
call append(6, ['Buffer', 'Window'])
let l = g:Xgetlist()
-
call assert_equal(5, l[0].lnum)
call assert_equal(6, l[2].lnum)
call assert_equal(13, l[3].lnum)
+ " If a file doesn't have any quickfix entries, then deleting lines in the
+ " file should not update the quickfix list
+ call g:Xsetlist([], 'f')
+ 1,2delete
+ call assert_equal([], g:Xgetlist())
+
enew!
call delete(fname)
endfunc
@@ -2616,7 +2717,7 @@ func XvimgrepTests(cchar)
call assert_equal(2, len(l))
call assert_equal('Editor:Notepad NOTEPAD', l[0].text)
- Xvimgrep #\cvim#g Xtestfile?
+ 10Xvimgrep #\cvim#g Xtestfile?
let l = g:Xgetlist()
call assert_equal(2, len(l))
call assert_equal(8, l[0].col)
@@ -3484,9 +3585,6 @@ func Xqftick_tests(cchar)
\ {'filename' : 'F7', 'lnum' : 11, 'text' : 'L11'}], 'r')
call assert_equal(2, g:Xgetlist({'changedtick' : 0}).changedtick)
- if isdirectory("Xone")
- call delete("Xone", 'rf')
- endif
call writefile(["F8:80:L80", "F8:81:L81"], "Xone")
Xfile Xone
call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
@@ -3692,6 +3790,41 @@ func Test_vimgrep_autocmd()
call setqflist([], 'f')
endfunc
+" Test for an autocmd changing the current directory when running vimgrep
+func Xvimgrep_autocmd_cd(cchar)
+ call s:setup_commands(a:cchar)
+
+ %bwipe
+ let save_cwd = getcwd()
+
+ augroup QF_Test
+ au!
+ autocmd BufRead * silent cd %:p:h
+ augroup END
+
+ 10Xvimgrep /vim/ Xdir/**
+ let l = g:Xgetlist()
+ call assert_equal('f1.txt', bufname(l[0].bufnr))
+ call assert_equal('f2.txt', fnamemodify(bufname(l[2].bufnr), ':t'))
+
+ augroup QF_Test
+ au!
+ augroup END
+
+ exe 'cd ' . save_cwd
+endfunc
+
+func Test_vimgrep_autocmd_cd()
+ call mkdir('Xdir/a', 'p')
+ call mkdir('Xdir/b', 'p')
+ call writefile(['a_L1_vim', 'a_L2_vim'], 'Xdir/a/f1.txt')
+ call writefile(['b_L1_vim', 'b_L2_vim'], 'Xdir/b/f2.txt')
+ call Xvimgrep_autocmd_cd('c')
+ call Xvimgrep_autocmd_cd('l')
+ %bwipe
+ call delete('Xdir', 'rf')
+endfunc
+
" The following test used to crash Vim
func Test_lhelpgrep_autocmd()
lhelpgrep quickfix
@@ -4788,4 +4921,148 @@ func Test_qfbuf_update()
call Xqfbuf_update('l')
endfunc
+" Test for getting a specific item from a quickfix list
+func Xtest_getqflist_by_idx(cchar)
+ call s:setup_commands(a:cchar)
+ " Empty list
+ call assert_equal([], g:Xgetlist({'idx' : 1, 'items' : 0}).items)
+ Xexpr ['F1:10:L10', 'F1:20:L20']
+ let l = g:Xgetlist({'idx' : 2, 'items' : 0}).items
+ call assert_equal(bufnr('F1'), l[0].bufnr)
+ call assert_equal(20, l[0].lnum)
+ call assert_equal('L20', l[0].text)
+ call assert_equal([], g:Xgetlist({'idx' : -1, 'items' : 0}).items)
+ call assert_equal([], g:Xgetlist({'idx' : 3, 'items' : 0}).items)
+ %bwipe!
+endfunc
+
+func Test_getqflist_by_idx()
+ call Xtest_getqflist_by_idx('c')
+ call Xtest_getqflist_by_idx('l')
+endfunc
+
+" Test for the 'quickfixtextfunc' setting
+func Tqfexpr(info)
+ if a:info.quickfix
+ let qfl = getqflist({'id' : a:info.id, 'items' : 1}).items
+ else
+ let qfl = getloclist(a:info.winid, {'id' : a:info.id, 'items' : 1}).items
+ endif
+
+
+ let l = []
+ for idx in range(a:info.start_idx - 1, a:info.end_idx - 1)
+ let e = qfl[idx]
+ let s = ''
+ if e.bufnr != 0
+ let bname = bufname(e.bufnr)
+ let s ..= fnamemodify(bname, ':.')
+ endif
+ let s ..= '-'
+ let s ..= 'L' .. string(e.lnum) .. 'C' .. string(e.col) .. '-'
+ let s ..= e.text
+ call add(l, s)
+ endfor
+
+ return l
+endfunc
+
+func Xtest_qftextfunc(cchar)
+ call s:setup_commands(a:cchar)
+
+ set efm=%f:%l:%c:%m
+ set quickfixtextfunc=Tqfexpr
+ Xexpr ['F1:10:2:green', 'F1:20:4:blue']
+ Xwindow
+ call assert_equal('F1-L10C2-green', getline(1))
+ call assert_equal('F1-L20C4-blue', getline(2))
+ Xclose
+ set quickfixtextfunc&vim
+ Xwindow
+ call assert_equal('F1|10 col 2| green', getline(1))
+ call assert_equal('F1|20 col 4| blue', getline(2))
+ Xclose
+ set efm&
+ set quickfixtextfunc&
+
+ " Test for per list 'quickfixtextfunc' setting
+ func PerQfText(info)
+ if a:info.quickfix
+ let qfl = getqflist({'id' : a:info.id, 'items' : 1}).items
+ else
+ let qfl = getloclist(a:info.winid, {'id' : a:info.id, 'items' : 1}).items
+ endif
+ if empty(qfl)
+ return []
+ endif
+ let l = []
+ for idx in range(a:info.start_idx - 1, a:info.end_idx - 1)
+ call add(l, 'Line ' .. qfl[idx].lnum .. ', Col ' .. qfl[idx].col)
+ endfor
+ return l
+ endfunc
+ set quickfixtextfunc=Tqfexpr
+ call g:Xsetlist([], ' ', {'quickfixtextfunc' : "PerQfText"})
+ Xaddexpr ['F1:10:2:green', 'F1:20:4:blue']
+ Xwindow
+ call assert_equal('Line 10, Col 2', getline(1))
+ call assert_equal('Line 20, Col 4', getline(2))
+ Xclose
+ " Add entries to the list when the quickfix buffer is hidden
+ Xaddexpr ['F1:30:6:red']
+ Xwindow
+ call assert_equal('Line 30, Col 6', getline(3))
+ Xclose
+ call g:Xsetlist([], 'r', {'quickfixtextfunc' : ''})
+ set quickfixtextfunc&
+ delfunc PerQfText
+
+ " Non-existing function
+ set quickfixtextfunc=Tabc
+ " call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue']", 'E117:')
+ Xexpr ['F1:10:2:green', 'F1:20:4:blue']"
+ call assert_fails("Xwindow", 'E117:')
+ Xclose
+ set quickfixtextfunc&
+
+ " set option to a non-function
+ set quickfixtextfunc=[10,\ 20]
+ " call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue']", 'E117:')
+ Xexpr ['F1:10:2:green', 'F1:20:4:blue']"
+ call assert_fails("Xwindow", 'E117:')
+ Xclose
+ set quickfixtextfunc&
+
+ " set option to a function with different set of arguments
+ func Xqftext(a, b, c)
+ return a:a .. a:b .. a:c
+ endfunc
+ set quickfixtextfunc=Xqftext
+ " call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue']", 'E119:')
+ Xexpr ['F1:10:2:green', 'F1:20:4:blue']"
+ call assert_fails("Xwindow", 'E119:')
+ Xclose
+
+ " set option to a function that returns a list with non-strings
+ func Xqftext2(d)
+ return ['one', [], 'two']
+ endfunc
+ set quickfixtextfunc=Xqftext2
+ " call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue', 'F1:30:6:red']",
+ " \ 'E730:')
+ Xexpr ['F1:10:2:green', 'F1:20:4:blue', 'F1:30:6:red']
+ call assert_fails('Xwindow', 'E730:')
+ call assert_equal(['one', 'F1|20 col 4| blue', 'two'], getline(1, '$'))
+ Xclose
+
+ set quickfixtextfunc&
+ delfunc Xqftext
+ delfunc Xqftext2
+endfunc
+
+func Test_qftextfunc()
+ call Xtest_qftextfunc('c')
+ call Xtest_qftextfunc('l')
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_regexp_latin.vim b/src/nvim/testdir/test_regexp_latin.vim
index cacdd68d10..712f1e6025 100644
--- a/src/nvim/testdir/test_regexp_latin.vim
+++ b/src/nvim/testdir/test_regexp_latin.vim
@@ -1,7 +1,10 @@
" Tests for regexp in latin1 encoding
+
" set encoding=latin1
scriptencoding latin1
+source check.vim
+
func s:equivalence_test()
let str = "AÀÁÂÃÄÅ B C D EÈÉÊË F G H IÌÍÎÏ J K L M NÑ OÒÓÔÕÖØ P Q R S T UÙÚÛÜ V W X YÝ Z aàáâãäå b c d eèéêë f g h iìíîï j k l m nñ oòóôõöø p q r s t uùúûü v w x yýÿ z"
let groups = split(str)
@@ -42,9 +45,9 @@ func Test_range_with_newline()
endfunc
func Test_pattern_compile_speed()
- if !exists('+spellcapcheck') || !has('reltime')
- return
- endif
+ CheckOption spellcapcheck
+ CheckFunction reltimefloat
+
let start = reltime()
" this used to be very slow, not it should be about a second
set spc=\\v(((((Nxxxxxxx&&xxxx){179})+)+)+){179}
diff --git a/src/nvim/testdir/test_regexp_utf8.vim b/src/nvim/testdir/test_regexp_utf8.vim
index 513780938e..d8d5797dcf 100644
--- a/src/nvim/testdir/test_regexp_utf8.vim
+++ b/src/nvim/testdir/test_regexp_utf8.vim
@@ -542,6 +542,52 @@ func Test_match_start_of_line_combining()
bwipe!
endfunc
+" Check that [[:upper:]] matches for automatic engine
+func Test_match_char_class_upper()
+ new
+ let _engine=&regexpengine
+
+ " Test 1: [[:upper:]]\{2,\}
+ set regexpengine=0
+ call setline(1, ['05. ПЕСНЯ О ГЕРОЯХ муз. А. Давиденко, М. Коваля и Б. Шехтера ...', '05. PJESNJA O GJEROJAKH mus. A. Davidjenko, M. Kovalja i B. Shjekhtjera ...'])
+ call cursor(1,1)
+ let search_cmd='norm /\<[[:upper:]]\{2,\}\>' .. "\<CR>"
+ exe search_cmd
+ call assert_equal(4, searchcount().total, 'TEST 1')
+ set regexpengine=1
+ exe search_cmd
+ call assert_equal(2, searchcount().total, 'TEST 1')
+ set regexpengine=2
+ exe search_cmd
+ call assert_equal(4, searchcount().total, 'TEST 1')
+
+ " Test 2: [[:upper:]].\+
+ let search_cmd='norm /\<[[:upper:]].\+\>' .. "\<CR>"
+ set regexpengine=0
+ exe search_cmd
+ call assert_equal(2, searchcount().total, 'TEST 2')
+ set regexpengine=1
+ exe search_cmd
+ call assert_equal(1, searchcount().total, 'TEST 2')
+ set regexpengine=2
+ exe search_cmd
+ call assert_equal(2, searchcount().total, 'TEST 2')
+
+ " Test 3: [[:lower:]]\+
+ let search_cmd='norm /\<[[:lower:]]\+\>' .. "\<CR>"
+ set regexpengine=0
+ exe search_cmd
+ call assert_equal(4, searchcount().total, 'TEST 3 lower')
+ set regexpengine=1
+ exe search_cmd
+ call assert_equal(2, searchcount().total, 'TEST 3 lower')
+ set regexpengine=2
+ exe search_cmd
+ call assert_equal(4, searchcount().total, 'TEST 3 lower')
+ " clean up
+ let &regexpengine=_engine
+ bwipe!
+endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim
index bcadb84ced..53069b3d31 100644
--- a/src/nvim/testdir/test_registers.vim
+++ b/src/nvim/testdir/test_registers.vim
@@ -43,9 +43,6 @@ func Test_yank_shows_register()
endfunc
func Test_display_registers()
- " Disable clipboard
- let g:clipboard = {}
-
e file1
e file2
call setline(1, ['foo', 'bar'])
diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim
index 75d42b986b..5ba24d047c 100644
--- a/src/nvim/testdir/test_search.vim
+++ b/src/nvim/testdir/test_search.vim
@@ -824,6 +824,26 @@ func Test_incsearch_search_dump()
call delete('Xis_search_script')
endfunc
+func Test_hlsearch_block_visual_match()
+ CheckScreendump
+
+ let lines =<< trim END
+ set hlsearch
+ call setline(1, ['aa', 'bbbb', 'cccccc'])
+ END
+ call writefile(lines, 'Xhlsearch_block')
+ let buf = RunVimInTerminal('-S Xhlsearch_block', {'rows': 9, 'cols': 60})
+
+ call term_sendkeys(buf, "G\<C-V>$kk\<Esc>")
+ sleep 100m
+ call term_sendkeys(buf, "/\\%V\<CR>")
+ sleep 100m
+ call VerifyScreenDump(buf, 'Test_hlsearch_block_visual_match', {})
+
+ call StopVimInTerminal(buf)
+ call delete('Xhlsearch_block')
+endfunc
+
func Test_incsearch_substitute()
CheckFunction test_override
CheckOption incsearch
@@ -1177,13 +1197,28 @@ func Test_look_behind()
bwipe!
endfunc
+func Test_search_visual_area_linewise()
+ new
+ call setline(1, ['aa', 'bb', 'cc'])
+ exe "normal 2GV\<Esc>"
+ for engine in [1, 2]
+ exe 'set regexpengine=' .. engine
+ exe "normal gg/\\%'<\<CR>>"
+ call assert_equal([0, 2, 1, 0, 1], getcurpos(), 'engine ' .. engine)
+ exe "normal gg/\\%'>\<CR>"
+ call assert_equal([0, 2, 2, 0, 2], getcurpos(), 'engine ' .. engine)
+ endfor
+
+ bwipe!
+ set regexpengine&
+endfunc
+
func Test_search_sentence()
new
" this used to cause a crash
- call assert_fails("/\\%')", 'E486')
- call assert_fails("/", 'E486')
/\%'(
/
+ bwipe
endfunc
" Test that there is no crash when there is a last search pattern but no last
diff --git a/src/nvim/testdir/test_search_stat.vim b/src/nvim/testdir/test_search_stat.vim
index 11c6489ca2..335a51268d 100644
--- a/src/nvim/testdir/test_search_stat.vim
+++ b/src/nvim/testdir/test_search_stat.vim
@@ -8,6 +8,41 @@ func Test_search_stat()
set shortmess-=S
" Append 50 lines with text to search for, "foobar" appears 20 times
call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 10))
+ call nvim_win_set_cursor(0, [1, 0])
+
+ " searchcount() returns an empty dictionary when previous pattern was not set
+ call assert_equal({}, searchcount(#{pattern: ''}))
+ " but setting @/ should also work (even 'n' nor 'N' was executed)
+ " recompute the count when the last position is different.
+ call assert_equal(
+ \ #{current: 1, exact_match: 1, total: 40, incomplete: 0, maxcount: 99},
+ \ searchcount(#{pattern: 'foo'}))
+ call assert_equal(
+ \ #{current: 0, exact_match: 0, total: 10, incomplete: 0, maxcount: 99},
+ \ searchcount(#{pattern: 'fooooobar'}))
+ call assert_equal(
+ \ #{current: 0, exact_match: 0, total: 10, incomplete: 0, maxcount: 99},
+ \ searchcount(#{pattern: 'fooooobar', pos: [2, 1, 0]}))
+ call assert_equal(
+ \ #{current: 1, exact_match: 1, total: 10, incomplete: 0, maxcount: 99},
+ \ searchcount(#{pattern: 'fooooobar', pos: [3, 1, 0]}))
+ " on last char of match
+ call assert_equal(
+ \ #{current: 1, exact_match: 1, total: 10, incomplete: 0, maxcount: 99},
+ \ searchcount(#{pattern: 'fooooobar', pos: [3, 9, 0]}))
+ " on char after match
+ call assert_equal(
+ \ #{current: 1, exact_match: 0, total: 10, incomplete: 0, maxcount: 99},
+ \ searchcount(#{pattern: 'fooooobar', pos: [3, 10, 0]}))
+ call assert_equal(
+ \ #{current: 1, exact_match: 0, total: 10, incomplete: 0, maxcount: 99},
+ \ searchcount(#{pattern: 'fooooobar', pos: [4, 1, 0]}))
+ call assert_equal(
+ \ #{current: 1, exact_match: 0, total: 2, incomplete: 2, maxcount: 1},
+ \ searchcount(#{pattern: 'fooooobar', pos: [4, 1, 0], maxcount: 1}))
+ call assert_equal(
+ \ #{current: 0, exact_match: 0, total: 2, incomplete: 2, maxcount: 1},
+ \ searchcount(#{pattern: 'fooooobar', maxcount: 1}))
" match at second line
call cursor(1, 1)
@@ -17,6 +52,9 @@ func Test_search_stat()
let stat = '\[2/50\]'
let pat = escape(@/, '()*?'). '\s\+'
call assert_match(pat .. stat, g:a)
+ call assert_equal(
+ \ #{current: 2, exact_match: 1, total: 50, incomplete: 0, maxcount: 99},
+ \ searchcount(#{recompute: 0}))
" didn't get added to message history
call assert_equal(messages_before, execute('messages'))
@@ -25,6 +63,9 @@ func Test_search_stat()
let g:a = execute(':unsilent :norm! n')
let stat = '\[50/50\]'
call assert_match(pat .. stat, g:a)
+ call assert_equal(
+ \ #{current: 50, exact_match: 1, total: 50, incomplete: 0, maxcount: 99},
+ \ searchcount(#{recompute: 0}))
" No search stat
set shortmess+=S
@@ -32,6 +73,14 @@ func Test_search_stat()
let stat = '\[2/50\]'
let g:a = execute(':unsilent :norm! n')
call assert_notmatch(pat .. stat, g:a)
+ call writefile(getline(1, '$'), 'sample.txt')
+ " n does not update search stat
+ call assert_equal(
+ \ #{current: 50, exact_match: 1, total: 50, incomplete: 0, maxcount: 99},
+ \ searchcount(#{recompute: 0}))
+ call assert_equal(
+ \ #{current: 2, exact_match: 1, total: 50, incomplete: 0, maxcount: 99},
+ \ searchcount(#{recompute: v:true}))
set shortmess-=S
" Many matches
@@ -41,10 +90,28 @@ func Test_search_stat()
let g:a = execute(':unsilent :norm! n')
let stat = '\[>99/>99\]'
call assert_match(pat .. stat, g:a)
+ call assert_equal(
+ \ #{current: 100, exact_match: 0, total: 100, incomplete: 2, maxcount: 99},
+ \ searchcount(#{recompute: 0}))
+ call assert_equal(
+ \ #{current: 272, exact_match: 1, total: 280, incomplete: 0, maxcount: 0},
+ \ searchcount(#{recompute: v:true, maxcount: 0, timeout: 200}))
+ call assert_equal(
+ \ #{current: 1, exact_match: 1, total: 280, incomplete: 0, maxcount: 0},
+ \ searchcount(#{recompute: 1, maxcount: 0, pos: [1, 1, 0], timeout: 200}))
call cursor(line('$'), 1)
let g:a = execute(':unsilent :norm! n')
let stat = 'W \[1/>99\]'
call assert_match(pat .. stat, g:a)
+ call assert_equal(
+ \ #{current: 1, exact_match: 1, total: 100, incomplete: 2, maxcount: 99},
+ \ searchcount(#{recompute: 0}))
+ call assert_equal(
+ \ #{current: 1, exact_match: 1, total: 280, incomplete: 0, maxcount: 0},
+ \ searchcount(#{recompute: 1, maxcount: 0, timeout: 200}))
+ call assert_equal(
+ \ #{current: 271, exact_match: 1, total: 280, incomplete: 0, maxcount: 0},
+ \ searchcount(#{recompute: 1, maxcount: 0, pos: [line('$')-2, 1, 0], timeout: 200}))
" Many matches
call cursor(1, 1)
@@ -180,12 +247,22 @@ func Test_search_stat()
call assert_match('^\s\+' .. stat, g:b)
unmap n
+ " Time out
+ %delete _
+ call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 100000))
+ call cursor(1, 1)
+ call assert_equal(1, searchcount(#{pattern: 'foo', maxcount: 0, timeout: 1}).incomplete)
+
" Clean up
set shortmess+=S
" close the window
bwipe!
endfunc
+func Test_searchcount_fails()
+ call assert_fails('echo searchcount("boo!")', 'E715:')
+endfunc
+
func Test_search_stat_foldopen()
CheckScreendump
@@ -252,9 +329,9 @@ func Test_searchcount_in_statusline()
function TestSearchCount() abort
let search_count = searchcount()
if !empty(search_count)
- return '[' . search_count.current . '/' . search_count.total . ']'
+ return '[' . search_count.current . '/' . search_count.total . ']'
else
- return ''
+ return ''
endif
endfunction
set hlsearch
diff --git a/src/nvim/testdir/test_sort.vim b/src/nvim/testdir/test_sort.vim
index 7533eaf2e8..570c4415c6 100644
--- a/src/nvim/testdir/test_sort.vim
+++ b/src/nvim/testdir/test_sort.vim
@@ -1,5 +1,7 @@
" Tests for the "sort()" function and for the ":sort" command.
+source check.vim
+
func Compare1(a, b) abort
call sort(range(3), 'Compare2')
return a:a - a:b
@@ -13,6 +15,37 @@ func Test_sort_strings()
" numbers compared as strings
call assert_equal([1, 2, 3], sort([3, 2, 1]))
call assert_equal([13, 28, 3], sort([3, 28, 13]))
+
+ call assert_equal(['A', 'O', 'P', 'a', 'o', 'p', 'Ä', 'Ô', 'ä', 'ô', 'Œ', 'œ'],
+ \ sort(['A', 'O', 'P', 'a', 'o', 'p', 'Ä', 'Ô', 'ä', 'ô', 'œ', 'Œ']))
+
+ call assert_equal(['A', 'a', 'o', 'O', 'p', 'P', 'Ä', 'Ô', 'ä', 'ô', 'Œ', 'œ'],
+ \ sort(['A', 'a', 'o', 'O', 'œ', 'Œ', 'p', 'P', 'Ä', 'ä', 'ô', 'Ô'], 'i'))
+
+ " This does not appear to work correctly on Mac.
+ if !has('mac')
+ if v:collate =~? '^\(en\|fr\)_ca.utf-\?8$'
+ " with Canadian English capitals come before lower case.
+ " 'Œ' is omitted because it can sort before or after 'œ'
+ call assert_equal(['A', 'a', 'Ä', 'ä', 'O', 'o', 'Ô', 'ô', 'œ', 'P', 'p'],
+ \ sort(['A', 'a', 'o', 'O', 'œ', 'p', 'P', 'Ä', 'ä', 'ô', 'Ô'], 'l'))
+ elseif v:collate =~? '^\(en\|es\|de\|fr\|it\|nl\).*\.utf-\?8$'
+ " With the following locales, the accentuated letters are ordered
+ " similarly to the non-accentuated letters...
+ call assert_equal(['a', 'A', 'ä', 'Ä', 'o', 'O', 'ô', 'Ô', 'œ', 'Œ', 'p', 'P'],
+ \ sort(['A', 'a', 'o', 'O', 'œ', 'Œ', 'p', 'P', 'Ä', 'ä', 'ô', 'Ô'], 'l'))
+ elseif v:collate =~? '^sv.*utf-\?8$'
+ " ... whereas with a Swedish locale, the accentuated letters are ordered
+ " after Z.
+ call assert_equal(['a', 'A', 'o', 'O', 'p', 'P', 'ä', 'Ä', 'œ', 'œ', 'ô', 'Ô'],
+ \ sort(['A', 'a', 'o', 'O', 'œ', 'œ', 'p', 'P', 'Ä', 'ä', 'ô', 'Ô'], 'l'))
+ endif
+ endif
+endfunc
+
+func Test_sort_null_string()
+ " null strings are sorted as empty strings.
+ call assert_equal(['', 'a', 'b'], sort(['b', v:_null_string, 'a']))
endfunc
func Test_sort_numeric()
@@ -28,6 +61,7 @@ func Test_sort_numbers()
endfunc
func Test_sort_float()
+ CheckFeature float
call assert_equal([0.28, 3, 13.5], sort([13.5, 0.28, 3], 'f'))
endfunc
@@ -37,6 +71,8 @@ func Test_sort_nested()
endfunc
func Test_sort_default()
+ CheckFeature float
+
" docs say omitted, empty or zero argument sorts on string representation.
call assert_equal(['2', 'A', 'AA', 'a', 1, 3.3], sort([3.3, 1, "2", "A", "a", "AA"]))
call assert_equal(['2', 'A', 'AA', 'a', 1, 3.3], sort([3.3, 1, "2", "A", "a", "AA"], ''))
@@ -1145,30 +1181,6 @@ func Test_sort_cmd()
\ ]
\ },
\ {
- \ 'name' : 'float',
- \ 'cmd' : 'sort f',
- \ 'input' : [
- \ '1.234',
- \ '0.88',
- \ '123.456',
- \ '1.15e-6',
- \ '-1.1e3',
- \ '-1.01e3',
- \ '',
- \ ''
- \ ],
- \ 'expected' : [
- \ '',
- \ '',
- \ '-1.1e3',
- \ '-1.01e3',
- \ '1.15e-6',
- \ '0.88',
- \ '1.234',
- \ '123.456'
- \ ]
- \ },
- \ {
\ 'name' : 'alphabetical, sorted input',
\ 'cmd' : 'sort',
\ 'input' : [
@@ -1197,8 +1209,133 @@ func Test_sort_cmd()
\ 'cc',
\ ]
\ },
+ \ {
+ \ 'name' : 'sort one line buffer',
+ \ 'cmd' : 'sort',
+ \ 'input' : [
+ \ 'single line'
+ \ ],
+ \ 'expected' : [
+ \ 'single line'
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'sort ignoring case',
+ \ 'cmd' : '%sort i',
+ \ 'input' : [
+ \ 'BB',
+ \ 'Cc',
+ \ 'aa'
+ \ ],
+ \ 'expected' : [
+ \ 'aa',
+ \ 'BB',
+ \ 'Cc'
+ \ ]
+ \ },
\ ]
+ " This does not appear to work correctly on Mac.
+ if !has('mac')
+ if v:collate =~? '^\(en\|fr\)_ca.utf-\?8$'
+ " en_CA.utf-8 sorts capitals before lower case
+ " 'Œ' is omitted because it can sort before or after 'œ'
+ let tests += [
+ \ {
+ \ 'name' : 'sort with locale ' .. v:collate,
+ \ 'cmd' : '%sort l',
+ \ 'input' : [
+ \ 'A',
+ \ 'E',
+ \ 'O',
+ \ 'À',
+ \ 'È',
+ \ 'É',
+ \ 'Ô',
+ \ 'Z',
+ \ 'a',
+ \ 'e',
+ \ 'o',
+ \ 'à',
+ \ 'è',
+ \ 'é',
+ \ 'ô',
+ \ 'œ',
+ \ 'z'
+ \ ],
+ \ 'expected' : [
+ \ 'A',
+ \ 'a',
+ \ 'À',
+ \ 'à',
+ \ 'E',
+ \ 'e',
+ \ 'É',
+ \ 'é',
+ \ 'È',
+ \ 'è',
+ \ 'O',
+ \ 'o',
+ \ 'Ô',
+ \ 'ô',
+ \ 'œ',
+ \ 'Z',
+ \ 'z'
+ \ ]
+ \ },
+ \ ]
+ elseif v:collate =~? '^\(en\|es\|de\|fr\|it\|nl\).*\.utf-\?8$'
+ " With these locales, the accentuated letters are ordered
+ " similarly to the non-accentuated letters.
+ let tests += [
+ \ {
+ \ 'name' : 'sort with locale ' .. v:collate,
+ \ 'cmd' : '%sort l',
+ \ 'input' : [
+ \ 'A',
+ \ 'E',
+ \ 'O',
+ \ 'À',
+ \ 'È',
+ \ 'É',
+ \ 'Ô',
+ \ 'Œ',
+ \ 'Z',
+ \ 'a',
+ \ 'e',
+ \ 'o',
+ \ 'à',
+ \ 'è',
+ \ 'é',
+ \ 'ô',
+ \ 'œ',
+ \ 'z'
+ \ ],
+ \ 'expected' : [
+ \ 'a',
+ \ 'A',
+ \ 'à',
+ \ 'À',
+ \ 'e',
+ \ 'E',
+ \ 'é',
+ \ 'É',
+ \ 'è',
+ \ 'È',
+ \ 'o',
+ \ 'O',
+ \ 'ô',
+ \ 'Ô',
+ \ 'œ',
+ \ 'Œ',
+ \ 'z',
+ \ 'Z'
+ \ ]
+ \ },
+ \ ]
+ endif
+ endif
+
for t in tests
enew!
call append(0, t.input)
@@ -1217,7 +1354,11 @@ func Test_sort_cmd()
endif
endfor
- call assert_fails('sort no', 'E474')
+ " Needs atleast two lines for this test
+ call setline(1, ['line1', 'line2'])
+ call assert_fails('sort no', 'E474:')
+ call assert_fails('sort c', 'E475:')
+ call assert_fails('sort #pat%', 'E682:')
enew!
endfunc
@@ -1319,4 +1460,46 @@ func Test_sort_cmd_report()
" the output comes from the :g command, not from the :sort
call assert_match("6 fewer lines", res)
enew!
- endfunc
+endfunc
+
+" Test for a :sort command followed by another command
+func Test_sort_followed_by_cmd()
+ new
+ let var = ''
+ call setline(1, ['cc', 'aa', 'bb'])
+ %sort | let var = "sortcmdtest"
+ call assert_equal(var, "sortcmdtest")
+ call assert_equal(['aa', 'bb', 'cc'], getline(1, '$'))
+ " Test for :sort followed by a comment
+ call setline(1, ['3b', '1c', '2a'])
+ %sort /\d\+/ " sort alphabetically
+ call assert_equal(['2a', '3b', '1c'], getline(1, '$'))
+ close!
+endfunc
+
+" Test for :sort using last search pattern
+func Test_sort_last_search_pat()
+ new
+ let @/ = '\d\+'
+ call setline(1, ['3b', '1c', '2a'])
+ sort //
+ call assert_equal(['2a', '3b', '1c'], getline(1, '$'))
+ close!
+endfunc
+
+" Test for retaining marks across a :sort
+func Test_sort_with_marks()
+ new
+ call setline(1, ['cc', 'aa', 'bb'])
+ call setpos("'c", [0, 1, 0, 0])
+ call setpos("'a", [0, 2, 0, 0])
+ call setpos("'b", [0, 3, 0, 0])
+ %sort
+ call assert_equal(['aa', 'bb', 'cc'], getline(1, '$'))
+ call assert_equal(2, line("'a"))
+ call assert_equal(3, line("'b"))
+ call assert_equal(1, line("'c"))
+ close!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim
index f5b6446108..a3e4dcdd25 100644
--- a/src/nvim/testdir/test_statusline.vim
+++ b/src/nvim/testdir/test_statusline.vim
@@ -241,6 +241,26 @@ func Test_statusline()
call assert_match('^vimLineComment\s*$', s:get_statusline())
syntax off
+ "%{%expr%}: evaluates enxpressions present in result of expr
+ func! Inner_eval()
+ return '%n some other text'
+ endfunc
+ func! Outer_eval()
+ return 'some text %{%Inner_eval()%}'
+ endfunc
+ set statusline=%{%Outer_eval()%}
+ call assert_match('^some text ' . bufnr() . ' some other text\s*$', s:get_statusline())
+ delfunc Inner_eval
+ delfunc Outer_eval
+
+ "%{%expr%}: Doesn't get stuck in recursion
+ func! Recurse_eval()
+ return '%{%Recurse_eval()%}'
+ endfunc
+ set statusline=%{%Recurse_eval()%}
+ call assert_match('^%{%Recurse_eval()%}\s*$', s:get_statusline())
+ delfunc Recurse_eval
+
"%(: Start of item group.
set statusline=ab%(cd%q%)de
call assert_match('^abde\s*$', s:get_statusline())
diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim
index 32167a45ba..e7f9bb76f2 100644
--- a/src/nvim/testdir/test_substitute.vim
+++ b/src/nvim/testdir/test_substitute.vim
@@ -426,6 +426,8 @@ func Test_substitute_errors()
call assert_fails('s/FOO/bar/', 'E486:')
call assert_fails('s/foo/bar/@', 'E488:')
call assert_fails('s/\(/bar/', 'E476:')
+ call assert_fails('s afooabara', 'E146:')
+ call assert_fails('s\\a', 'E10:')
setl nomodifiable
call assert_fails('s/foo/bar/', 'E21:')
diff --git a/src/nvim/testdir/test_true_false.vim b/src/nvim/testdir/test_true_false.vim
index 84aca737ac..315ba188cb 100644
--- a/src/nvim/testdir/test_true_false.vim
+++ b/src/nvim/testdir/test_true_false.vim
@@ -1,5 +1,7 @@
" Test behavior of boolean-like values.
+source check.vim
+
" Test what is explained at ":help TRUE" and ":help FALSE".
func Test_if()
if v:false
@@ -41,7 +43,9 @@ func Test_if()
call assert_fails('if [1]', 'E745')
call assert_fails('if {1: 1}', 'E728')
call assert_fails('if function("string")', 'E703')
- call assert_fails('if 1.3")', 'E805')
+ if has('float')
+ call assert_fails('if 1.3")', 'E805')
+ endif
endfunc
function Try_arg_true_false(expr, false_val, true_val)
@@ -113,6 +117,7 @@ func Test_true_false_arg()
endfunc
function Try_arg_non_zero(expr, false_val, true_val)
+ CheckFeature float
for v in ['v:false', '0', '[1]', '{2:3}', '3.4']
let r = eval(substitute(a:expr, '%v%', v, ''))
call assert_equal(a:false_val, r, 'result for ' . v . ' is not ' . a:false_val . ' but ' . r)
diff --git a/src/nvim/testdir/test_user_func.vim b/src/nvim/testdir/test_user_func.vim
index e9e181ce3d..7dcd92a54b 100644
--- a/src/nvim/testdir/test_user_func.vim
+++ b/src/nvim/testdir/test_user_func.vim
@@ -113,9 +113,11 @@ func MakeBadFunc()
endfunc
func Test_default_arg()
- call assert_equal(1.0, Log(10))
- call assert_equal(log(10), Log(10, exp(1)))
- call assert_fails("call Log(1,2,3)", 'E118')
+ if has('float')
+ call assert_equal(1.0, Log(10))
+ call assert_equal(log(10), Log(10, exp(1)))
+ call assert_fails("call Log(1,2,3)", 'E118')
+ endif
let res = Args(1)
call assert_equal(res.mandatory, 1)
diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim
index 43907de1d9..5922660ef9 100644
--- a/src/nvim/testdir/test_vimscript.vim
+++ b/src/nvim/testdir/test_vimscript.vim
@@ -1271,8 +1271,10 @@ func Test_num64()
call assert_equal(-9223372036854775807, -1 / 0)
call assert_equal(-9223372036854775807 - 1, 0 / 0)
- call assert_equal( 0x7FFFffffFFFFffff, float2nr( 1.0e150))
- call assert_equal(-0x7FFFffffFFFFffff, float2nr(-1.0e150))
+ if has('float')
+ call assert_equal( 0x7FFFffffFFFFffff, float2nr( 1.0e150))
+ call assert_equal(-0x7FFFffffFFFFffff, float2nr(-1.0e150))
+ endif
let rng = range(0xFFFFffff, 0x100000001)
call assert_equal([0xFFFFffff, 0x100000000, 0x100000001], rng)
@@ -1531,22 +1533,22 @@ func Test_compound_assignment_operators()
call assert_equal('string', x)
let x += 1
call assert_equal(1, x)
- let x -= 1.5
- call assert_equal(-0.5, x)
if has('float')
- " Test for float
- let x = 0.5
- let x += 4.5
- call assert_equal(5.0, x)
- let x -= 1.5
- call assert_equal(3.5, x)
- let x *= 3.0
- call assert_equal(10.5, x)
- let x /= 2.5
- call assert_equal(4.2, x)
- call assert_fails('let x %= 0.5', 'E734')
- call assert_fails('let x .= "f"', 'E734')
+ " Test for float
+ let x -= 1.5
+ call assert_equal(-0.5, x)
+ let x = 0.5
+ let x += 4.5
+ call assert_equal(5.0, x)
+ let x -= 1.5
+ call assert_equal(3.5, x)
+ let x *= 3.0
+ call assert_equal(10.5, x)
+ let x /= 2.5
+ call assert_equal(4.2, x)
+ call assert_fails('let x %= 0.5', 'E734')
+ call assert_fails('let x .= "f"', 'E734')
endif
" Test for environment variable
diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim
index 73c7960579..21fd57b791 100644
--- a/src/nvim/testdir/test_visual.vim
+++ b/src/nvim/testdir/test_visual.vim
@@ -1,4 +1,4 @@
-" Tests for various Visual mode.
+" Tests for various Visual modes.
func Test_block_shift_multibyte()
" Uses double-wide character.
@@ -738,4 +738,272 @@ func Test_select_mode_gv()
bwipe!
endfunc
+" Tests for the visual block mode commands
+func Test_visual_block_mode()
+ new
+ call append(0, '')
+ call setline(1, ['abcdefghijklm', 'abcdefghijklm', 'abcdefghijklm',
+ \ 'abcdefghijklm', 'abcdefghijklm'])
+ call cursor(1, 1)
+
+ " Test shift-right of a block
+ exe "normal jllll\<C-V>jj>wll\<C-V>jlll>"
+ " Test shift-left of a block
+ exe "normal G$hhhh\<C-V>kk<"
+ " Test block-insert
+ exe "normal Gkl\<C-V>kkkIxyz"
+ " Test block-replace
+ exe "normal Gllll\<C-V>kkklllrq"
+ " Test block-change
+ exe "normal G$khhh\<C-V>hhkkcmno"
+ call assert_equal(['axyzbcdefghijklm',
+ \ 'axyzqqqq mno ghijklm',
+ \ 'axyzqqqqef mno ghijklm',
+ \ 'axyzqqqqefgmnoklm',
+ \ 'abcdqqqqijklm'], getline(1, 5))
+
+ " Test from ':help v_b_I_example'
+ %d _
+ setlocal tabstop=8 shiftwidth=4
+ let lines =<< trim END
+ abcdefghijklmnopqrstuvwxyz
+ abc defghijklmnopqrstuvwxyz
+ abcdef ghi jklmnopqrstuvwxyz
+ abcdefghijklmnopqrstuvwxyz
+ END
+ call setline(1, lines)
+ exe "normal ggfo\<C-V>3jISTRING"
+ let expected =<< trim END
+ abcdefghijklmnSTRINGopqrstuvwxyz
+ abc STRING defghijklmnopqrstuvwxyz
+ abcdef ghi STRING jklmnopqrstuvwxyz
+ abcdefghijklmnSTRINGopqrstuvwxyz
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ " Test from ':help v_b_A_example'
+ %d _
+ let lines =<< trim END
+ abcdefghijklmnopqrstuvwxyz
+ abc defghijklmnopqrstuvwxyz
+ abcdef ghi jklmnopqrstuvwxyz
+ abcdefghijklmnopqrstuvwxyz
+ END
+ call setline(1, lines)
+ exe "normal ggfo\<C-V>3j$ASTRING"
+ let expected =<< trim END
+ abcdefghijklmnopqrstuvwxyzSTRING
+ abc defghijklmnopqrstuvwxyzSTRING
+ abcdef ghi jklmnopqrstuvwxyzSTRING
+ abcdefghijklmnopqrstuvwxyzSTRING
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ " Test from ':help v_b_<_example'
+ %d _
+ let lines =<< trim END
+ abcdefghijklmnopqrstuvwxyz
+ abc defghijklmnopqrstuvwxyz
+ abcdef ghi jklmnopqrstuvwxyz
+ abcdefghijklmnopqrstuvwxyz
+ END
+ call setline(1, lines)
+ exe "normal ggfo\<C-V>3j3l<.."
+ let expected =<< trim END
+ abcdefghijklmnopqrstuvwxyz
+ abc defghijklmnopqrstuvwxyz
+ abcdef ghi jklmnopqrstuvwxyz
+ abcdefghijklmnopqrstuvwxyz
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ " Test from ':help v_b_>_example'
+ %d _
+ let lines =<< trim END
+ abcdefghijklmnopqrstuvwxyz
+ abc defghijklmnopqrstuvwxyz
+ abcdef ghi jklmnopqrstuvwxyz
+ abcdefghijklmnopqrstuvwxyz
+ END
+ call setline(1, lines)
+ exe "normal ggfo\<C-V>3j>.."
+ let expected =<< trim END
+ abcdefghijklmn opqrstuvwxyz
+ abc defghijklmnopqrstuvwxyz
+ abcdef ghi jklmnopqrstuvwxyz
+ abcdefghijklmn opqrstuvwxyz
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ " Test from ':help v_b_r_example'
+ %d _
+ let lines =<< trim END
+ abcdefghijklmnopqrstuvwxyz
+ abc defghijklmnopqrstuvwxyz
+ abcdef ghi jklmnopqrstuvwxyz
+ abcdefghijklmnopqrstuvwxyz
+ END
+ call setline(1, lines)
+ exe "normal ggfo\<C-V>5l3jrX"
+ let expected =<< trim END
+ abcdefghijklmnXXXXXXuvwxyz
+ abc XXXXXXhijklmnopqrstuvwxyz
+ abcdef ghi XXXXXX jklmnopqrstuvwxyz
+ abcdefghijklmnXXXXXXuvwxyz
+ END
+ call assert_equal(expected, getline(1, '$'))
+
+ bwipe!
+ set tabstop& shiftwidth&
+endfunc
+
+" Test block-insert using cursor keys for movement
+func Test_visual_block_insert_cursor_keys()
+ new
+ call append(0, ['aaaaaa', 'bbbbbb', 'cccccc', 'dddddd'])
+ call cursor(1, 1)
+
+ exe "norm! l\<C-V>jjjlllI\<Right>\<Right> \<Esc>"
+ call assert_equal(['aaa aaa', 'bbb bbb', 'ccc ccc', 'ddd ddd'],
+ \ getline(1, 4))
+
+ call deletebufline('', 1, '$')
+ call setline(1, ['xaaa', 'bbbb', 'cccc', 'dddd'])
+ call cursor(1, 1)
+ exe "norm! \<C-V>jjjI<>\<Left>p\<Esc>"
+ call assert_equal(['<p>xaaa', '<p>bbbb', '<p>cccc', '<p>dddd'],
+ \ getline(1, 4))
+ bwipe!
+endfunc
+
+func Test_visual_block_create()
+ new
+ call append(0, '')
+ " Test for Visual block was created with the last <C-v>$
+ call setline(1, ['A23', '4567'])
+ call cursor(1, 1)
+ exe "norm! l\<C-V>j$Aab\<Esc>"
+ call assert_equal(['A23ab', '4567ab'], getline(1, 2))
+
+ " Test for Visual block was created with the middle <C-v>$ (1)
+ call deletebufline('', 1, '$')
+ call setline(1, ['B23', '4567'])
+ call cursor(1, 1)
+ exe "norm! l\<C-V>j$hAab\<Esc>"
+ call assert_equal(['B23 ab', '4567ab'], getline(1, 2))
+
+ " Test for Visual block was created with the middle <C-v>$ (2)
+ call deletebufline('', 1, '$')
+ call setline(1, ['C23', '4567'])
+ call cursor(1, 1)
+ exe "norm! l\<C-V>j$hhAab\<Esc>"
+ call assert_equal(['C23ab', '456ab7'], getline(1, 2))
+ bwipe!
+endfunc
+
+" Test for Visual block insert when virtualedit=all
+func Test_virtualedit_visual_block()
+ set ve=all
+ new
+ call append(0, ["\t\tline1", "\t\tline2", "\t\tline3"])
+ call cursor(1, 1)
+ exe "norm! 07l\<C-V>jjIx\<Esc>"
+ call assert_equal([" x \tline1",
+ \ " x \tline2",
+ \ " x \tline3"], getline(1, 3))
+
+ " Test for Visual block append when virtualedit=all
+ exe "norm! 012l\<C-v>jjAx\<Esc>"
+ call assert_equal([' x x line1',
+ \ ' x x line2',
+ \ ' x x line3'], getline(1, 3))
+ set ve=
+ bwipe!
+endfunc
+
+" Test for changing case
+func Test_visual_change_case()
+ new
+ " gUe must uppercase a whole word, also when ß changes to SS
+ exe "normal Gothe youtußeuu end\<Esc>Ypk0wgUe\r"
+ " gUfx must uppercase until x, inclusive.
+ exe "normal O- youßtußexu -\<Esc>0fogUfx\r"
+ " VU must uppercase a whole line
+ exe "normal YpkVU\r"
+ " same, when it's the last line in the buffer
+ exe "normal YPGi111\<Esc>VUddP\r"
+ " Uppercase two lines
+ exe "normal Oblah di\rdoh dut\<Esc>VkUj\r"
+ " Uppercase part of two lines
+ exe "normal ddppi333\<Esc>k0i222\<Esc>fyllvjfuUk"
+ call assert_equal(['the YOUTUSSEUU end', '- yOUSSTUSSEXu -',
+ \ 'THE YOUTUSSEUU END', '111THE YOUTUSSEUU END', 'BLAH DI', 'DOH DUT',
+ \ '222the yoUTUSSEUU END', '333THE YOUTUßeuu end'], getline(2, '$'))
+ bwipe!
+endfunc
+
+" Test for Visual replace using Enter or NL
+func Test_visual_replace_crnl()
+ new
+ exe "normal G3o123456789\e2k05l\<C-V>2jr\r"
+ exe "normal G3o98765\e2k02l\<C-V>2jr\<C-V>\r\n"
+ exe "normal G3o123456789\e2k05l\<C-V>2jr\n"
+ exe "normal G3o98765\e2k02l\<C-V>2jr\<C-V>\n"
+ call assert_equal(['12345', '789', '12345', '789', '12345', '789', "98\r65",
+ \ "98\r65", "98\r65", '12345', '789', '12345', '789', '12345', '789',
+ \ "98\n65", "98\n65", "98\n65"], getline(2, '$'))
+ bwipe!
+endfunc
+
+func Test_ve_block_curpos()
+ new
+ " Test cursor position. When ve=block and Visual block mode and $gj
+ call append(0, ['12345', '789'])
+ call cursor(1, 3)
+ set virtualedit=block
+ exe "norm! \<C-V>$gj\<Esc>"
+ call assert_equal([0, 2, 4, 0], getpos("'>"))
+ set virtualedit=
+ bwipe!
+endfunc
+
+" Test for block_insert when replacing spaces in front of the a with tabs
+func Test_block_insert_replace_tabs()
+ new
+ set ts=8 sts=4 sw=4
+ call append(0, ["#define BO_ALL\t 0x0001",
+ \ "#define BO_BS\t 0x0002",
+ \ "#define BO_CRSR\t 0x0004"])
+ call cursor(1, 1)
+ exe "norm! f0\<C-V>2jI\<tab>\<esc>"
+ call assert_equal([
+ \ "#define BO_ALL\t\t0x0001",
+ \ "#define BO_BS\t \t0x0002",
+ \ "#define BO_CRSR\t \t0x0004", ''], getline(1, '$'))
+ set ts& sts& sw&
+ bwipe!
+endfunc
+
+func Test_visual_put_in_block_using_zp()
+ new
+ " paste using zP
+ call setline(1, ['/path;text', '/path;text', '/path;text', '',
+ \ '/subdir',
+ \ '/longsubdir',
+ \ '/longlongsubdir'])
+ exe "normal! 5G\<c-v>2j$y"
+ norm! 1Gf;zP
+ call assert_equal(['/path/subdir;text', '/path/longsubdir;text', '/path/longlongsubdir;text'], getline(1, 3))
+ %d
+ " paste using zP
+ call setline(1, ['/path;text', '/path;text', '/path;text', '',
+ \ '/subdir',
+ \ '/longsubdir',
+ \ '/longlongsubdir'])
+ exe "normal! 5G\<c-v>2j$y"
+ norm! 1Gf;hzp
+ call assert_equal(['/path/subdir;text', '/path/longsubdir;text', '/path/longlongsubdir;text'], getline(1, 3))
+ bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim
index c62c01d5f3..6922e2185d 100644
--- a/src/nvim/testdir/test_writefile.vim
+++ b/src/nvim/testdir/test_writefile.vim
@@ -164,6 +164,69 @@ func Test_writefile_autowrite_nowrite()
set noautowrite
endfunc
+" Test for ':w !<cmd>' to pipe lines from the current buffer to an external
+" command.
+func Test_write_pipe_to_cmd()
+ if !has('unix')
+ return
+ endif
+ new
+ call setline(1, ['L1', 'L2', 'L3', 'L4'])
+ 2,3w !cat > Xfile
+ call assert_equal(['L2', 'L3'], readfile('Xfile'))
+ close!
+ call delete('Xfile')
+endfunc
+
+" Test for :saveas
+func Test_saveas()
+ call assert_fails('saveas', 'E471:')
+ call writefile(['L1'], 'Xfile')
+ new Xfile
+ new
+ call setline(1, ['L1'])
+ call assert_fails('saveas Xfile', 'E139:')
+ close!
+ enew | only
+ call delete('Xfile')
+endfunc
+
+func Test_write_errors()
+ " Test for writing partial buffer
+ call writefile(['L1', 'L2', 'L3'], 'Xfile')
+ new Xfile
+ call assert_fails('1,2write', 'E140:')
+ close!
+
+ " Try to overwrite a directory
+ if has('unix')
+ call mkdir('Xdir1')
+ call assert_fails('write Xdir1', 'E17:')
+ call delete('Xdir1', 'd')
+ endif
+
+ " Test for :wall for a buffer with no name
+ enew | only
+ call setline(1, ['L1'])
+ call assert_fails('wall', 'E141:')
+ enew!
+
+ " Test for writing a 'readonly' file
+ new Xfile
+ set readonly
+ call assert_fails('write', 'E45:')
+ close
+
+ " Test for writing to a read-only file
+ new Xfile
+ call setfperm('Xfile', 'r--r--r--')
+ call assert_fails('write', 'E505:')
+ call setfperm('Xfile', 'rw-rw-rw-')
+ close
+
+ call delete('Xfile')
+endfunc
+
func Test_writefile_sync_dev_stdout()
if !has('unix')
return
@@ -298,4 +361,25 @@ func Test_write_file_encoding()
%bw!
endfunc
+" Check that buffer is written before triggering QuitPre
+func Test_wq_quitpre_autocommand()
+ edit Xsomefile
+ call setline(1, 'hello')
+ split
+ let g:seq = []
+ augroup Testing
+ au QuitPre * call add(g:seq, 'QuitPre - ' .. (&modified ? 'modified' : 'not modified'))
+ au BufWritePost * call add(g:seq, 'written')
+ augroup END
+ wq
+ call assert_equal(['written', 'QuitPre - not modified'], g:seq)
+
+ augroup Testing
+ au!
+ augroup END
+ bwipe!
+ unlet g:seq
+ call delete('Xsomefile')
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index ed40a64c66..fd83681aed 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -2009,9 +2009,9 @@ static void augment_terminfo(TUIData *data, const char *term,
}
data->unibi_ext.save_title = (int)unibi_add_ext_str(
- ut, "ext.save_title", "\x1b[22;0;0t");
+ ut, "ext.save_title", "\x1b[22;0t");
data->unibi_ext.restore_title = (int)unibi_add_ext_str(
- ut, "ext.restore_title", "\x1b[23;0;0t");
+ ut, "ext.restore_title", "\x1b[23;0t");
/// Terminals usually ignore unrecognized private modes, and there is no
/// known ambiguity with these. So we just set them unconditionally.
diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c
index c1e4a40ef2..1ec5189795 100644
--- a/src/nvim/ui_compositor.c
+++ b/src/nvim/ui_compositor.c
@@ -165,22 +165,13 @@ bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width,
}
#endif
- // TODO(bfredl): this is pretty ad-hoc, add a proper z-order/priority
- // scheme. For now:
- // - msg_grid is always on top.
- // - pum_grid is on top of all windows but not msg_grid. Except for when
- // wildoptions=pum, and completing the cmdline with scrolled messages,
- // then the pum has to be drawn over the scrolled messages.
size_t insert_at = kv_size(layers);
- bool cmd_completion = (grid == &pum_grid && (State & CMDLINE)
- && (wop_flags & WOP_PUM));
- if (kv_A(layers, insert_at-1) == &msg_grid && !cmd_completion) {
- insert_at--;
- }
- if (kv_A(layers, insert_at-1) == &pum_grid && (grid != &msg_grid)) {
+ while (insert_at > 0 && kv_A(layers, insert_at-1)->zindex > grid->zindex) {
insert_at--;
}
+
if (curwin && kv_A(layers, insert_at-1) == &curwin->w_grid_alloc
+ && kv_A(layers, insert_at-1)->zindex == grid->zindex
&& !on_top) {
insert_at--;
}
@@ -279,12 +270,11 @@ static void ui_comp_grid_cursor_goto(UI *ui, Integer grid_handle,
// should configure all grids before entering win_update()
if (curgrid != &default_grid) {
size_t new_index = kv_size(layers)-1;
- if (kv_A(layers, new_index) == &msg_grid) {
- new_index--;
- }
- if (kv_A(layers, new_index) == &pum_grid) {
+
+ while (new_index > 1 && kv_A(layers, new_index)->zindex > curgrid->zindex) {
new_index--;
}
+
if (curgrid->comp_index < new_index) {
ui_comp_raise_grid(curgrid, new_index);
}
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index f52850f6f3..ffd613cec2 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -1018,9 +1018,7 @@ static ExtmarkUndoObject *unserialize_extmark(bufinfo_T *bi, bool *error,
goto error;
}
- if (buf) {
- xfree(buf);
- }
+ xfree(buf);
return extup;
diff --git a/src/nvim/version.c b/src/nvim/version.c
index deba3f6e49..f3a30630f8 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -406,13 +406,13 @@ static const int included_patches[] = {
1514,
1513,
1512,
- // 1511,
+ 1511,
1510,
1509,
1508,
1507,
1506,
- // 1505,
+ 1505,
1504,
1503,
1502,
diff --git a/src/nvim/window.c b/src/nvim/window.c
index d4d00c0a71..aea60fe24c 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -570,6 +570,45 @@ static void cmd_with_count(char *cmd, char_u *bufp, size_t bufsize,
}
}
+void win_set_buf(Window window, Buffer buffer, bool noautocmd, Error *err)
+{
+ win_T *win = find_window_by_handle(window, err), *save_curwin = curwin;
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ tabpage_T *tab = win_find_tabpage(win), *save_curtab = curtab;
+
+ if (!win || !buf) {
+ return;
+ }
+
+ if (noautocmd) {
+ block_autocmds();
+ }
+ if (switch_win_noblock(&save_curwin, &save_curtab, win, tab, false) == FAIL) {
+ api_set_error(err,
+ kErrorTypeException,
+ "Failed to switch to window %d",
+ window);
+ }
+
+ try_start();
+ int result = do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, buf->b_fnum, 0);
+ if (!try_end(err) && result == FAIL) {
+ api_set_error(err,
+ kErrorTypeException,
+ "Failed to set buffer %d",
+ buffer);
+ }
+
+ // If window is not current, state logic will not validate its cursor.
+ // So do it now.
+ validate_cursor();
+
+ restore_win_noblock(save_curwin, save_curtab, false);
+ if (noautocmd) {
+ unblock_autocmds();
+ }
+}
+
/// Create a new float.
///
/// if wp == NULL allocate a new window, otherwise turn existing window into a
@@ -763,10 +802,13 @@ void ui_ext_win_position(win_T *wp)
}
api_clear_error(&dummy);
}
+
+ wp->w_grid_alloc.zindex = wp->w_float_config.zindex;
if (ui_has(kUIMultigrid)) {
String anchor = cstr_to_string(float_anchor_str[c.anchor]);
ui_call_win_float_pos(wp->w_grid_alloc.handle, wp->handle, anchor,
- grid->handle, row, col, c.focusable);
+ grid->handle, row, col, c.focusable,
+ wp->w_grid_alloc.zindex);
} else {
// TODO(bfredl): ideally, compositor should work like any multigrid UI
// and use standard win_pos events.
@@ -2286,7 +2328,7 @@ int win_close(win_T *win, bool free_buf)
return FAIL; // window is already being closed
}
if (win == aucmd_win) {
- EMSG(_("E813: Cannot close autocmd window"));
+ EMSG(_(e_autocmd_close));
return FAIL;
}
if ((firstwin == aucmd_win || lastwin == aucmd_win) && one_window()) {
@@ -2486,7 +2528,7 @@ int win_close(win_T *win, bool free_buf)
// only resize that frame. Otherwise resize all windows.
win_equal(curwin, curwin->w_frame->fr_parent == win_frame, dir);
} else {
- win_comp_pos();
+ (void)win_comp_pos();
}
}
@@ -6254,9 +6296,10 @@ restore_snapshot (
&& curtab->tp_snapshot[idx]->fr_height == topframe->fr_height
&& check_snapshot_rec(curtab->tp_snapshot[idx], topframe) == OK) {
wp = restore_snapshot_rec(curtab->tp_snapshot[idx], topframe);
- win_comp_pos();
- if (wp != NULL && close_curwin)
+ (void)win_comp_pos();
+ if (wp != NULL && close_curwin) {
win_goto(wp);
+ }
redraw_all_later(NOT_VALID);
}
clear_snapshot(curtab, idx);
diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua
index 058706718a..21e3094f8e 100644
--- a/test/functional/api/highlight_spec.lua
+++ b/test/functional/api/highlight_spec.lua
@@ -158,3 +158,98 @@ describe('API: highlight',function()
assert_alive()
end)
end)
+
+describe("API: set highlight", function()
+ local highlight_color = {
+ fg = tonumber('0xff0000'),
+ bg = tonumber('0x0032aa'),
+ ctermfg = 8,
+ ctermbg = 15,
+ }
+ local highlight1 = {
+ background = highlight_color.bg,
+ foreground = highlight_color.fg,
+ bold = true,
+ italic = true,
+ }
+ local highlight2_config = {
+ ctermbg = highlight_color.ctermbg,
+ ctermfg = highlight_color.ctermfg,
+ underline = true,
+ reverse = true,
+ }
+ local highlight2_result = {
+ background = highlight_color.ctermbg,
+ foreground = highlight_color.ctermfg,
+ underline = true,
+ reverse = true,
+ }
+ local highlight3_config = {
+ background = highlight_color.bg,
+ foreground = highlight_color.fg,
+ ctermbg = highlight_color.ctermbg,
+ ctermfg = highlight_color.ctermfg,
+ bold = true,
+ italic = true,
+ reverse = true,
+ undercurl = true,
+ underline = true,
+ cterm = {
+ italic = true,
+ reverse = true,
+ undercurl = true,
+ }
+ }
+ local highlight3_result_gui = {
+ background = highlight_color.bg,
+ foreground = highlight_color.fg,
+ bold = true,
+ italic = true,
+ reverse = true,
+ undercurl = true,
+ underline = true,
+ }
+ local highlight3_result_cterm = {
+ background = highlight_color.ctermbg,
+ foreground = highlight_color.ctermfg,
+ italic = true,
+ reverse = true,
+ undercurl = true,
+ }
+
+ local function get_ns()
+ local ns = meths.create_namespace('Test_set_hl')
+ meths._set_hl_ns(ns)
+ return ns
+ end
+
+ before_each(clear)
+
+ it ("can set gui highlight", function()
+ local ns = get_ns()
+ meths.set_hl(ns, 'Test_hl', highlight1)
+ eq(highlight1, meths.get_hl_by_name('Test_hl', true))
+ end)
+
+ it ("can set cterm highlight", function()
+ local ns = get_ns()
+ meths.set_hl(ns, 'Test_hl', highlight2_config)
+ eq(highlight2_result, meths.get_hl_by_name('Test_hl', false))
+ end)
+
+ it ("cterm attr defaults to gui attr", function()
+ local ns = get_ns()
+ meths.set_hl(ns, 'Test_hl', highlight1)
+ eq({
+ bold = true,
+ italic = true,
+ }, meths.get_hl_by_name('Test_hl', false))
+ end)
+
+ it ("can overwrite attr for cterm", function()
+ local ns = get_ns()
+ meths.set_hl(ns, 'Test_hl', highlight3_config)
+ eq(highlight3_result_gui, meths.get_hl_by_name('Test_hl', true))
+ eq(highlight3_result_cterm, meths.get_hl_by_name('Test_hl', false))
+ end)
+end)
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index 6926022ee3..0c0f610401 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -1986,6 +1986,10 @@ describe('API', function()
eq(meths.get_option_info'winhighlight', options_info.winhighlight)
end)
+
+ it('should not crash when echoed', function()
+ meths.exec("echo nvim_get_all_options_info()", true)
+ end)
end)
describe('nvim_get_option_info', function()
diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua
index ceeb84cec9..a57826f7e7 100644
--- a/test/functional/api/window_spec.lua
+++ b/test/functional/api/window_spec.lua
@@ -387,4 +387,18 @@ describe('API/win', function()
eq({oldbuf}, meths.list_bufs())
end)
end)
+
+ describe('open_win', function()
+ it('noautocmd option works', function()
+ command('autocmd BufEnter,BufLeave,BufWinEnter * let g:fired = 1')
+ meths.open_win(meths.create_buf(true, true), true, {
+ relative='win', row=3, col=3, width=12, height=3, noautocmd=true
+ })
+ eq(0, funcs.exists('g:fired'))
+ meths.open_win(meths.create_buf(true, true), true, {
+ relative='win', row=3, col=3, width=12, height=3
+ })
+ eq(1, funcs.exists('g:fired'))
+ end)
+ end)
end)
diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua
index 9de0d08e79..34ab90d760 100644
--- a/test/functional/core/job_spec.lua
+++ b/test/functional/core/job_spec.lua
@@ -118,6 +118,32 @@ describe('jobs', function()
end
end)
+ it('handles case-insensitively matching #env vars', function()
+ nvim('command', "let $TOTO = 'abc'")
+ -- Since $Toto is being set in the job, it should take precedence over the
+ -- global $TOTO on Windows
+ nvim('command', "let g:job_opts = {'env': {'Toto': 'def'}, 'stdout_buffered': v:true}")
+ if iswin() then
+ nvim('command', [[let j = jobstart('set | find /I "toto="', g:job_opts)]])
+ else
+ nvim('command', [[let j = jobstart('env | grep -i toto=', g:job_opts)]])
+ end
+ nvim('command', "call jobwait([j])")
+ nvim('command', "let g:output = Normalize(g:job_opts.stdout)")
+ local actual = eval('g:output')
+ local expected
+ if iswin() then
+ -- Toto is normalized to TOTO so we can detect duplicates, and because
+ -- Windows doesn't care about case
+ expected = {'TOTO=def', ''}
+ else
+ expected = {'TOTO=abc', 'Toto=def', ''}
+ end
+ table.sort(actual)
+ table.sort(expected)
+ eq(expected, actual)
+ end)
+
it('uses &shell and &shellcmdflag if passed a string', function()
nvim('command', "let $VAR = 'abc'")
if iswin() then
diff --git a/test/functional/eval/null_spec.lua b/test/functional/eval/null_spec.lua
index d403fbc878..b1ceff9115 100644
--- a/test/functional/eval/null_spec.lua
+++ b/test/functional/eval/null_spec.lua
@@ -73,6 +73,7 @@ describe('NULL', function()
null_expr_test('does not crash col()', 'col(L)', 0, 0)
null_expr_test('does not crash virtcol()', 'virtcol(L)', 0, 0)
null_expr_test('does not crash line()', 'line(L)', 0, 0)
+ null_expr_test('does not crash line() with window id', 'line(L, 1000)', 0, 0)
null_expr_test('does not crash count()', 'count(L, 1)', 0, 0)
null_expr_test('does not crash cursor()', 'cursor(L)', 'E474: Invalid argument', -1)
null_expr_test('does not crash map()', 'map(L, "v:val")', 0, {})
@@ -96,7 +97,7 @@ describe('NULL', function()
null_expr_test('makes filter() return v:_null_list', 'filter(L, "1") is# L', 0, 1)
null_test('is treated by :let as empty list', ':let [l] = L', 'Vim(let):E688: More targets than List items')
null_expr_test('is accepted as an empty list by inputlist()', '[feedkeys("\\n"), inputlist(L)]',
- 'Type number and <Enter> or click with mouse (empty cancels): ', {0, 0})
+ 'Type number and <Enter> or click with the mouse (q or empty cancels): ', {0, 0})
null_expr_test('is accepted as an empty list by writefile()',
('[writefile(L, "%s"), readfile("%s")]'):format(tmpfname, tmpfname),
0, {0, {}})
@@ -149,6 +150,7 @@ describe('NULL', function()
null_test('does not crash :execute', 'execute S', 0)
null_expr_test('does not crash execute()', 'execute(S)', 0, '')
null_expr_test('makes executable() error out', 'executable(S)', 'E928: String required', 0)
+ null_expr_test('makes timer_start() error out', 'timer_start(0, S)', 'E921: Invalid callback argument', -1)
null_expr_test('does not crash filereadable()', 'filereadable(S)', 0, 0)
null_expr_test('does not crash filewritable()', 'filewritable(S)', 0, 0)
null_expr_test('does not crash fnamemodify()', 'fnamemodify(S, S)', 0, '')
@@ -161,7 +163,6 @@ describe('NULL', function()
null_expr_test('does not crash mkdir()', 'mkdir(S)', 0, 0)
null_expr_test('does not crash sort()', 'sort(["b", S, "a"])', 0, {'', 'a', 'b'})
null_expr_test('does not crash split()', 'split(S)', 0, {})
-
null_test('can be used to set an option', 'let &grepprg = S', 0)
null_expr_test('is equal to non-existent variable', 'S == V', 0, 1)
diff --git a/test/functional/fixtures/streams-test.c b/test/functional/fixtures/streams-test.c
index eec447153c..be40edfe7e 100644
--- a/test/functional/fixtures/streams-test.c
+++ b/test/functional/fixtures/streams-test.c
@@ -6,23 +6,22 @@
#include <uv.h>
-uv_loop_t *loop;
-uv_process_t child_req;
-uv_process_options_t options;
-
int main(int argc, char **argv)
{
- loop = uv_default_loop();
+ uv_loop_t *loop = uv_default_loop();
+ uv_process_t child_req;
char * args[3];
args[0] = "sleep";
args[1] = "10";
args[2] = NULL;
- options.exit_cb = NULL;
- options.file = "sleep";
- options.args = args;
- options.flags = UV_PROCESS_DETACHED;
+ uv_process_options_t options = {
+ .exit_cb = NULL,
+ .file = "sleep",
+ .args = args,
+ .flags = UV_PROCESS_DETACHED,
+ };
int r;
if ((r = uv_spawn(loop, &child_req, &options))) {
diff --git a/test/functional/legacy/011_autocommands_spec.lua b/test/functional/legacy/011_autocommands_spec.lua
index c9c004eec1..7cc31dc787 100644
--- a/test/functional/legacy/011_autocommands_spec.lua
+++ b/test/functional/legacy/011_autocommands_spec.lua
@@ -70,7 +70,7 @@ describe('file reading, writing and bufnew and filter autocommands', function()
feed_command('let $GZIP = ""')
--execute('au FileChangedShell * echo "caught FileChangedShell"')
feed_command('set bin')
- feed_command("au FileReadPost *.gz '[,']!GZIP= gzip -d")
+ feed_command("au FileReadPost *.gz '[,']!gzip -d")
-- Read and decompress the testfile.
feed_command('$r Xtestfile.gz')
expect('\n'..text1)
diff --git a/test/functional/legacy/delete_spec.lua b/test/functional/legacy/delete_spec.lua
index 3d2c4a7d91..f2ced8942d 100644
--- a/test/functional/legacy/delete_spec.lua
+++ b/test/functional/legacy/delete_spec.lua
@@ -17,6 +17,33 @@ describe('Test for delete()', function()
eq(-1, eval("delete('Xfile')"))
end)
+ it('directory delete', function()
+ command("call mkdir('Xdir1')")
+ eq(1, eval("isdirectory('Xdir1')"))
+ eq(0, eval("delete('Xdir1', 'd')"))
+ eq(0, eval("isdirectory('Xdir1')"))
+ eq(-1, eval("delete('Xdir1', 'd')"))
+ end)
+ it('recursive delete', function()
+ command("call mkdir('Xdir1')")
+ command("call mkdir('Xdir1/subdir')")
+ command("call mkdir('Xdir1/empty')")
+ command('split Xdir1/Xfile')
+ command("call setline(1, ['a', 'b'])")
+ command('w')
+ command('w Xdir1/subdir/Xfile')
+ command('close')
+
+ eq(1, eval("isdirectory('Xdir1')"))
+ eq(eval("['a', 'b']"), eval("readfile('Xdir1/Xfile')"))
+ eq(1, eval("isdirectory('Xdir1/subdir')"))
+ eq(eval("['a', 'b']"), eval("readfile('Xdir1/subdir/Xfile')"))
+ eq(1, eval("isdirectory('Xdir1/empty')"))
+ eq(0, eval("delete('Xdir1', 'rf')"))
+ eq(0, eval("isdirectory('Xdir1')"))
+ eq(-1, eval("delete('Xdir1', 'd')"))
+ end)
+
it('symlink delete', function()
source([[
split Xfile
@@ -36,4 +63,55 @@ describe('Test for delete()', function()
eq(-1, eval("delete('Xlink')"))
eq(0, eval("delete('Xfile')"))
end)
+
+ it('symlink directory delete', function()
+ command("call mkdir('Xdir1')")
+ if helpers.iswin() then
+ command("silent !mklink /j Xlink Xdir1")
+ else
+ command("silent !ln -s Xdir1 Xlink")
+ end
+ eq(1, eval("isdirectory('Xdir1')"))
+ eq(1, eval("isdirectory('Xlink')"))
+ -- Delete the link, not the directory
+ eq(0, eval("delete('Xlink')"))
+ eq(-1, eval("delete('Xlink')"))
+ eq(0, eval("delete('Xdir1', 'd')"))
+ end)
+
+ it('symlink recursive delete', function()
+ source([[
+ call mkdir('Xdir3')
+ call mkdir('Xdir3/subdir')
+ call mkdir('Xdir4')
+ split Xdir3/Xfile
+ call setline(1, ['a', 'b'])
+ w
+ w Xdir3/subdir/Xfile
+ w Xdir4/Xfile
+ close
+ if has('win32')
+ silent !mklink /j Xdir3\Xlink Xdir4
+ else
+ silent !ln -s ../Xdir4 Xdir3/Xlink
+ endif
+ ]])
+
+ eq(1, eval("isdirectory('Xdir3')"))
+ eq(eval("['a', 'b']"), eval("readfile('Xdir3/Xfile')"))
+ eq(1, eval("isdirectory('Xdir3/subdir')"))
+ eq(eval("['a', 'b']"), eval("readfile('Xdir3/subdir/Xfile')"))
+ eq(1, eval("isdirectory('Xdir4')"))
+ eq(1, eval("isdirectory('Xdir3/Xlink')"))
+ eq(eval("['a', 'b']"), eval("readfile('Xdir4/Xfile')"))
+
+ eq(0, eval("delete('Xdir3', 'rf')"))
+ eq(0, eval("isdirectory('Xdir3')"))
+ eq(-1, eval("delete('Xdir3', 'd')"))
+ -- symlink is deleted, not the directory it points to
+ eq(1, eval("isdirectory('Xdir4')"))
+ eq(eval("['a', 'b']"), eval("readfile('Xdir4/Xfile')"))
+ eq(0, eval("delete('Xdir4/Xfile')"))
+ eq(0, eval("delete('Xdir4', 'd')"))
+ end)
end)
diff --git a/test/functional/legacy/mksession_spec.lua b/test/functional/legacy/mksession_spec.lua
new file mode 100644
index 0000000000..a2af891107
--- /dev/null
+++ b/test/functional/legacy/mksession_spec.lua
@@ -0,0 +1,42 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear = helpers.clear
+local command = helpers.command
+local funcs = helpers.funcs
+local eq = helpers.eq
+
+describe('mksession', function()
+ before_each(clear)
+
+ after_each(function()
+ os.remove('Xtest_mks.out')
+ end)
+
+ it('supports "skiprtp" value', function()
+ command('set sessionoptions&vi')
+ command('set rtp+=$HOME')
+ command('set pp+=$HOME')
+ command('mksession! Xtest_mks.out')
+ local found_rtp = 0
+ local found_pp = 0
+ for _, line in pairs(funcs.readfile('Xtest_mks.out', 'b')) do
+ if line:find('set runtimepath') then
+ found_rtp = found_rtp + 1
+ end
+ if line:find('set packpath') then
+ found_pp = found_pp + 1
+ end
+ end
+ eq(1, found_rtp)
+ eq(1, found_pp)
+
+ command('set sessionoptions+=skiprtp')
+ command('mksession! Xtest_mks.out')
+ local found_rtp_or_pp = 0
+ for _, line in pairs(funcs.readfile('Xtest_mks.out', 'b')) do
+ if line:find('set runtimepath') or line:find('set packpath') then
+ found_rtp_or_pp = found_rtp_or_pp + 1
+ end
+ end
+ eq(0, found_rtp_or_pp)
+ end)
+end)
diff --git a/test/functional/legacy/packadd_spec.lua b/test/functional/legacy/packadd_spec.lua
index 3c84105c6b..486a1fe471 100644
--- a/test/functional/legacy/packadd_spec.lua
+++ b/test/functional/legacy/packadd_spec.lua
@@ -20,7 +20,6 @@ describe('packadd', function()
func SetUp()
let s:topdir = expand(getcwd() . '/Xdir')
- call delete(s:topdir, 'rf')
exe 'set packpath=' . s:topdir
let s:plugdir = expand(s:topdir . '/pack/mine/opt/mytest')
endfunc
@@ -268,6 +267,8 @@ describe('packadd', function()
call assert_match('look-here', tags1[0])
let tags2 = readfile(docdir2 . '/tags')
call assert_match('look-away', tags2[0])
+
+ call assert_fails('helptags abcxyz', 'E150:')
endfunc
func Test_colorscheme()
diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua
index d4c65eae5b..1b2c21783e 100644
--- a/test/functional/lua/buffer_updates_spec.lua
+++ b/test/functional/lua/buffer_updates_spec.lua
@@ -401,8 +401,8 @@ describe('lua: nvim_buf_attach on_bytes', function()
}
feed '<cr>'
check_events {
- { "test1", "bytes", 1, 4, 8, 0, 115, 0, 4, 4, 0, 0, 0 };
- { "test1", "bytes", 1, 5, 7, 4, 118, 0, 0, 0, 1, 4, 5 };
+ { "test1", "bytes", 1, 4, 7, 0, 114, 0, 4, 4, 0, 0, 0 };
+ { "test1", "bytes", 1, 5, 7, 0, 114, 0, 0, 0, 1, 4, 5 };
}
end)
@@ -447,8 +447,8 @@ describe('lua: nvim_buf_attach on_bytes', function()
feed '<CR>'
check_events {
- { "test1", "bytes", 1, 6, 2, 2, 16, 0, 1, 1, 0, 0, 0 };
- { "test1", "bytes", 1, 7, 1, 3, 14, 0, 0, 0, 1, 3, 4 };
+ { "test1", "bytes", 1, 6, 1, 2, 13, 0, 1, 1, 0, 0, 0 };
+ { "test1", "bytes", 1, 7, 1, 2, 13, 0, 0, 0, 1, 3, 4 };
}
end)
@@ -936,6 +936,26 @@ describe('lua: nvim_buf_attach on_bytes', function()
}
end)
+ it("virtual edit", function ()
+ local check_events = setup_eventcheck(verify, { "", " " })
+
+ meths.set_option("virtualedit", "all")
+
+ feed [[<Right><Right>iab<ESC>]]
+
+ check_events {
+ { "test1", "bytes", 1, 3, 0, 0, 0, 0, 0, 0, 0, 2, 2 };
+ { "test1", "bytes", 1, 4, 0, 2, 2, 0, 0, 0, 0, 2, 2 };
+ }
+
+ feed [[j<Right><Right>iab<ESC>]]
+
+ check_events {
+ { "test1", "bytes", 1, 5, 1, 0, 5, 0, 1, 1, 0, 8, 8 };
+ { "test1", "bytes", 1, 6, 1, 5, 10, 0, 0, 0, 0, 2, 2 };
+ }
+ end)
+
it("block visual paste", function()
local check_events = setup_eventcheck(verify, {"AAA",
"BBB",
@@ -958,6 +978,29 @@ describe('lua: nvim_buf_attach on_bytes', function()
}
end)
+ it("nvim_buf_set_lines", function()
+ local check_events = setup_eventcheck(verify, {"AAA", "BBB"})
+
+ -- delete
+ meths.buf_set_lines(0, 0, 1, true, {})
+
+ check_events {
+ { "test1", "bytes", 1, 3, 0, 0, 0, 1, 0, 4, 0, 0, 0 };
+ }
+
+ -- add
+ meths.buf_set_lines(0, 0, 0, true, {'asdf'})
+ check_events {
+ { "test1", "bytes", 1, 4, 0, 0, 0, 0, 0, 0, 1, 0, 5 };
+ }
+
+ -- replace
+ meths.buf_set_lines(0, 0, 1, true, {'asdf', 'fdsa'})
+ check_events {
+ { "test1", "bytes", 1, 5, 0, 0, 0, 1, 0, 5, 2, 0, 10 };
+ }
+ end)
+
teardown(function()
os.remove "Xtest-reload"
os.remove "Xtest-undofile"
diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua
index 9bf00b594b..08a0552a38 100644
--- a/test/functional/lua/vim_spec.lua
+++ b/test/functional/lua/vim_spec.lua
@@ -945,12 +945,20 @@ describe('lua stdlib', function()
exec_lua [[
vim.api.nvim_set_var("testing", "hi")
vim.api.nvim_set_var("other", 123)
+ vim.api.nvim_set_var("floaty", 5120.1)
+ vim.api.nvim_set_var("nullvar", vim.NIL)
vim.api.nvim_set_var("to_delete", {hello="world"})
]]
eq('hi', funcs.luaeval "vim.g.testing")
eq(123, funcs.luaeval "vim.g.other")
+ eq(5120.1, funcs.luaeval "vim.g.floaty")
eq(NIL, funcs.luaeval "vim.g.nonexistant")
+ eq(NIL, funcs.luaeval "vim.g.nullvar")
+ -- lost over RPC, so test locally:
+ eq({false, true}, exec_lua [[
+ return {vim.g.nonexistant == vim.NIL, vim.g.nullvar == vim.NIL}
+ ]])
eq({hello="world"}, funcs.luaeval "vim.g.to_delete")
exec_lua [[
@@ -963,12 +971,20 @@ describe('lua stdlib', function()
exec_lua [[
vim.api.nvim_buf_set_var(0, "testing", "hi")
vim.api.nvim_buf_set_var(0, "other", 123)
+ vim.api.nvim_buf_set_var(0, "floaty", 5120.1)
+ vim.api.nvim_buf_set_var(0, "nullvar", vim.NIL)
vim.api.nvim_buf_set_var(0, "to_delete", {hello="world"})
]]
eq('hi', funcs.luaeval "vim.b.testing")
eq(123, funcs.luaeval "vim.b.other")
+ eq(5120.1, funcs.luaeval "vim.b.floaty")
eq(NIL, funcs.luaeval "vim.b.nonexistant")
+ eq(NIL, funcs.luaeval "vim.b.nullvar")
+ -- lost over RPC, so test locally:
+ eq({false, true}, exec_lua [[
+ return {vim.b.nonexistant == vim.NIL, vim.b.nullvar == vim.NIL}
+ ]])
eq({hello="world"}, funcs.luaeval "vim.b.to_delete")
exec_lua [[
@@ -1097,6 +1113,464 @@ describe('lua stdlib', function()
eq(0, funcs.luaeval "vim.wo[1000].cole")
end)
+ describe('vim.opt', function()
+ -- TODO: We still need to write some tests for optlocal, opt and then getting the options
+ -- Probably could also do some stuff with getting things from viml side as well to confirm behavior is the same.
+
+ it('should allow setting number values', function()
+ local scrolloff = exec_lua [[
+ vim.opt.scrolloff = 10
+ return vim.o.scrolloff
+ ]]
+ eq(scrolloff, 10)
+ end)
+
+ pending('should handle STUPID window things', function()
+ local result = exec_lua [[
+ local result = {}
+
+ table.insert(result, vim.api.nvim_get_option('scrolloff'))
+ table.insert(result, vim.api.nvim_win_get_option(0, 'scrolloff'))
+
+ return result
+ ]]
+
+ eq({}, result)
+ end)
+
+ it('should allow setting tables', function()
+ local wildignore = exec_lua [[
+ vim.opt.wildignore = { 'hello', 'world' }
+ return vim.o.wildignore
+ ]]
+ eq(wildignore, "hello,world")
+ end)
+
+ it('should allow setting tables with shortnames', function()
+ local wildignore = exec_lua [[
+ vim.opt.wig = { 'hello', 'world' }
+ return vim.o.wildignore
+ ]]
+ eq(wildignore, "hello,world")
+ end)
+
+ it('should error when you attempt to set string values to numeric options', function()
+ local result = exec_lua [[
+ return {
+ pcall(function() vim.opt.textwidth = 'hello world' end)
+ }
+ ]]
+
+ eq(false, result[1])
+ end)
+
+ it('should error when you attempt to setlocal a global value', function()
+ local result = exec_lua [[
+ return pcall(function() vim.opt_local.clipboard = "hello" end)
+ ]]
+
+ eq(false, result)
+ end)
+
+ it('should allow you to set boolean values', function()
+ eq({true, false, true}, exec_lua [[
+ local results = {}
+
+ vim.opt.autoindent = true
+ table.insert(results, vim.bo.autoindent)
+
+ vim.opt.autoindent = false
+ table.insert(results, vim.bo.autoindent)
+
+ vim.opt.autoindent = not vim.opt.autoindent:get()
+ table.insert(results, vim.bo.autoindent)
+
+ return results
+ ]])
+ end)
+
+ it('should change current buffer values and defaults for global local values', function()
+ local result = exec_lua [[
+ local result = {}
+
+ vim.opt.makeprg = "global-local"
+ table.insert(result, vim.api.nvim_get_option('makeprg'))
+ table.insert(result, (pcall(vim.api.nvim_buf_get_option, 0, 'makeprg')))
+
+ vim.opt_local.mp = "only-local"
+ table.insert(result, vim.api.nvim_get_option('makeprg'))
+ table.insert(result, vim.api.nvim_buf_get_option(0, 'makeprg'))
+
+ vim.opt_global.makeprg = "only-global"
+ table.insert(result, vim.api.nvim_get_option('makeprg'))
+ table.insert(result, vim.api.nvim_buf_get_option(0, 'makeprg'))
+
+ vim.opt.makeprg = "global-local"
+ table.insert(result, vim.api.nvim_get_option('makeprg'))
+ table.insert(result, vim.api.nvim_buf_get_option(0, 'makeprg'))
+ return result
+ ]]
+
+ -- Set -> global & local
+ eq("global-local", result[1])
+ eq(false, result[2])
+
+ -- Setlocal -> only local
+ eq("global-local", result[3])
+ eq("only-local", result[4])
+
+ -- Setglobal -> only global
+ eq("only-global", result[5])
+ eq("only-local", result[6])
+
+ -- set -> doesn't override previously set value
+ eq("global-local", result[7])
+ eq("only-local", result[8])
+ end)
+
+ it('should allow all sorts of string manipulation', function()
+ eq({'hello', 'hello world', 'start hello world'}, exec_lua [[
+ local results = {}
+
+ vim.opt.makeprg = "hello"
+ table.insert(results, vim.o.makeprg)
+
+ vim.opt.makeprg = vim.opt.makeprg + " world"
+ table.insert(results, vim.o.makeprg)
+
+ vim.opt.makeprg = vim.opt.makeprg ^ "start "
+ table.insert(results, vim.o.makeprg)
+
+ return results
+ ]])
+ end)
+
+ describe('option:get()', function()
+ it('should work for boolean values', function()
+ eq(false, exec_lua [[
+ vim.opt.number = false
+ return vim.opt.number:get()
+ ]])
+ end)
+
+ it('should work for number values', function()
+ local tabstop = exec_lua[[
+ vim.opt.tabstop = 10
+ return vim.opt.tabstop:get()
+ ]]
+
+ eq(10, tabstop)
+ end)
+
+ it('should work for string values', function()
+ eq("hello world", exec_lua [[
+ vim.opt.makeprg = "hello world"
+ return vim.opt.makeprg:get()
+ ]])
+ end)
+
+ it('should work for set type flaglists', function()
+ local formatoptions = exec_lua [[
+ vim.opt.formatoptions = 'tcro'
+ return vim.opt.formatoptions:get()
+ ]]
+
+ eq(true, formatoptions.t)
+ eq(true, not formatoptions.q)
+ end)
+
+ it('should work for set type flaglists', function()
+ local formatoptions = exec_lua [[
+ vim.opt.formatoptions = { t = true, c = true, r = true, o = true }
+ return vim.opt.formatoptions:get()
+ ]]
+
+ eq(true, formatoptions.t)
+ eq(true, not formatoptions.q)
+ end)
+
+ it('should work for array list type options', function()
+ local wildignore = exec_lua [[
+ vim.opt.wildignore = "*.c,*.o,__pycache__"
+ return vim.opt.wildignore:get()
+ ]]
+
+ eq(3, #wildignore)
+ eq("*.c", wildignore[1])
+ end)
+
+ it('should work for key-value pair options', function()
+ local listchars = exec_lua [[
+ vim.opt.listchars = "tab:>~,space:_"
+ return vim.opt.listchars:get()
+ ]]
+
+ eq({
+ tab = ">~",
+ space = "_",
+ }, listchars)
+ end)
+
+ it('should allow you to add numeric options', function()
+ eq(16, exec_lua [[
+ vim.opt.tabstop = 12
+ vim.opt.tabstop = vim.opt.tabstop + 4
+ return vim.bo.tabstop
+ ]])
+ end)
+
+ it('should allow you to subtract numeric options', function()
+ eq(2, exec_lua [[
+ vim.opt.tabstop = 4
+ vim.opt.tabstop = vim.opt.tabstop - 2
+ return vim.bo.tabstop
+ ]])
+ end)
+ end)
+
+ describe('key:value style options', function()
+ it('should handle dictionary style', function()
+ local listchars = exec_lua [[
+ vim.opt.listchars = {
+ eol = "~",
+ space = ".",
+ }
+
+ return vim.o.listchars
+ ]]
+ eq("eol:~,space:.", listchars)
+ end)
+
+ it('should allow adding dictionary style', function()
+ local listchars = exec_lua [[
+ vim.opt.listchars = {
+ eol = "~",
+ space = ".",
+ }
+
+ vim.opt.listchars = vim.opt.listchars + { space = "-" }
+
+ return vim.o.listchars
+ ]]
+
+ eq("eol:~,space:-", listchars)
+ end)
+
+ it('should allow adding dictionary style', function()
+ local listchars = exec_lua [[
+ vim.opt.listchars = {
+ eol = "~",
+ space = ".",
+ }
+ vim.opt.listchars = vim.opt.listchars + { space = "-" } + { space = "_" }
+
+ return vim.o.listchars
+ ]]
+
+ eq("eol:~,space:_", listchars)
+ end)
+
+ it('should allow completely new keys', function()
+ local listchars = exec_lua [[
+ vim.opt.listchars = {
+ eol = "~",
+ space = ".",
+ }
+ vim.opt.listchars = vim.opt.listchars + { tab = ">>>" }
+
+ return vim.o.listchars
+ ]]
+
+ eq("eol:~,space:.,tab:>>>", listchars)
+ end)
+
+ it('should allow subtracting dictionary style', function()
+ local listchars = exec_lua [[
+ vim.opt.listchars = {
+ eol = "~",
+ space = ".",
+ }
+ vim.opt.listchars = vim.opt.listchars - "space"
+
+ return vim.o.listchars
+ ]]
+
+ eq("eol:~", listchars)
+ end)
+
+ it('should allow subtracting dictionary style', function()
+ local listchars = exec_lua [[
+ vim.opt.listchars = {
+ eol = "~",
+ space = ".",
+ }
+ vim.opt.listchars = vim.opt.listchars - "space" - "eol"
+
+ return vim.o.listchars
+ ]]
+
+ eq("", listchars)
+ end)
+
+ it('should allow subtracting dictionary style multiple times', function()
+ local listchars = exec_lua [[
+ vim.opt.listchars = {
+ eol = "~",
+ space = ".",
+ }
+ vim.opt.listchars = vim.opt.listchars - "space" - "space"
+
+ return vim.o.listchars
+ ]]
+
+ eq("eol:~", listchars)
+ end)
+
+ it('should allow adding a key:value string to a listchars', function()
+ local listchars = exec_lua [[
+ vim.opt.listchars = {
+ eol = "~",
+ space = ".",
+ }
+ vim.opt.listchars = vim.opt.listchars + "tab:>~"
+
+ return vim.o.listchars
+ ]]
+
+ eq("eol:~,space:.,tab:>~", listchars)
+ end)
+
+ it('should allow prepending a key:value string to a listchars', function()
+ local listchars = exec_lua [[
+ vim.opt.listchars = {
+ eol = "~",
+ space = ".",
+ }
+ vim.opt.listchars = vim.opt.listchars ^ "tab:>~"
+
+ return vim.o.listchars
+ ]]
+
+ eq("eol:~,space:.,tab:>~", listchars)
+ end)
+ end)
+
+ it('should automatically set when calling remove', function()
+ eq("foo,baz", exec_lua [[
+ vim.opt.wildignore = "foo,bar,baz"
+ vim.opt.wildignore:remove("bar")
+
+ return vim.o.wildignore
+ ]])
+ end)
+
+ it('should automatically set when calling remove with a table', function()
+ eq("foo", exec_lua [[
+ vim.opt.wildignore = "foo,bar,baz"
+ vim.opt.wildignore:remove { "bar", "baz" }
+
+ return vim.o.wildignore
+ ]])
+ end)
+
+ it('should automatically set when calling append', function()
+ eq("foo,bar,baz,bing", exec_lua [[
+ vim.opt.wildignore = "foo,bar,baz"
+ vim.opt.wildignore:append("bing")
+
+ return vim.o.wildignore
+ ]])
+ end)
+
+ it('should automatically set when calling append with a table', function()
+ eq("foo,bar,baz,bing,zap", exec_lua [[
+ vim.opt.wildignore = "foo,bar,baz"
+ vim.opt.wildignore:append { "bing", "zap" }
+
+ return vim.o.wildignore
+ ]])
+ end)
+
+ it('should allow adding tables', function()
+ local wildignore = exec_lua [[
+ vim.opt.wildignore = 'foo'
+ return vim.o.wildignore
+ ]]
+ eq(wildignore, 'foo')
+
+ wildignore = exec_lua [[
+ vim.opt.wildignore = vim.opt.wildignore + { 'bar', 'baz' }
+ return vim.o.wildignore
+ ]]
+ eq(wildignore, 'foo,bar,baz')
+ end)
+
+ it('should handle adding duplicates', function()
+ local wildignore = exec_lua [[
+ vim.opt.wildignore = 'foo'
+ return vim.o.wildignore
+ ]]
+ eq(wildignore, 'foo')
+
+ wildignore = exec_lua [[
+ vim.opt.wildignore = vim.opt.wildignore + { 'bar', 'baz' }
+ return vim.o.wildignore
+ ]]
+ eq(wildignore, 'foo,bar,baz')
+
+ wildignore = exec_lua [[
+ vim.opt.wildignore = vim.opt.wildignore + { 'bar', 'baz' }
+ return vim.o.wildignore
+ ]]
+ eq(wildignore, 'foo,bar,baz')
+ end)
+
+ it('should allow adding multiple times', function()
+ local wildignore = exec_lua [[
+ vim.opt.wildignore = 'foo'
+ vim.opt.wildignore = vim.opt.wildignore + 'bar' + 'baz'
+ return vim.o.wildignore
+ ]]
+ eq(wildignore, 'foo,bar,baz')
+ end)
+
+ it('should remove values when you use minus', function()
+ local wildignore = exec_lua [[
+ vim.opt.wildignore = 'foo'
+ return vim.o.wildignore
+ ]]
+ eq(wildignore, 'foo')
+
+ wildignore = exec_lua [[
+ vim.opt.wildignore = vim.opt.wildignore + { 'bar', 'baz' }
+ return vim.o.wildignore
+ ]]
+ eq(wildignore, 'foo,bar,baz')
+
+ wildignore = exec_lua [[
+ vim.opt.wildignore = vim.opt.wildignore - 'bar'
+ return vim.o.wildignore
+ ]]
+ eq(wildignore, 'foo,baz')
+ end)
+
+ it('should prepend values when using ^', function()
+ local wildignore = exec_lua [[
+ vim.opt.wildignore = 'foo'
+ vim.opt.wildignore = vim.opt.wildignore ^ 'first'
+ return vim.o.wildignore
+ ]]
+ eq('first,foo', wildignore)
+
+ wildignore = exec_lua [[
+ vim.opt.wildignore = vim.opt.wildignore ^ 'super_first'
+ return vim.o.wildignore
+ ]]
+ eq(wildignore, 'super_first,first,foo')
+ end)
+
+ end)
+
it('vim.cmd', function()
exec_lua [[
vim.cmd "autocmd BufNew * ++once lua BUF = vim.fn.expand('<abuf>')"
diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua
index 8c91c4ab2c..962028e7e1 100644
--- a/test/functional/plugin/lsp/diagnostic_spec.lua
+++ b/test/functional/plugin/lsp/diagnostic_spec.lua
@@ -86,6 +86,39 @@ describe('vim.lsp.diagnostic', function()
eq(2, #result[1])
eq('Diagnostic #1', result[1][1].message)
end)
+ it('Can convert diagnostic to quickfix items format', function()
+ local bufnr = exec_lua([[
+ local fake_uri = ...
+ return vim.uri_to_bufnr(fake_uri)
+ ]], fake_uri)
+ local result = exec_lua([[
+ local bufnr = ...
+ vim.lsp.diagnostic.save(
+ {
+ make_error('Diagnostic #1', 1, 1, 1, 1),
+ make_error('Diagnostic #2', 2, 1, 2, 1),
+ }, bufnr, 1
+ )
+ return vim.lsp.util.diagnostics_to_items(vim.lsp.diagnostic.get_all())
+ ]], bufnr)
+ local expected = {
+ {
+ bufnr = bufnr,
+ col = 2,
+ lnum = 2,
+ text = 'Diagnostic #1',
+ type = 'E'
+ },
+ {
+ bufnr = bufnr,
+ col = 2,
+ lnum = 3,
+ text = 'Diagnostic #2',
+ type = 'E'
+ },
+ }
+ eq(expected, result)
+ end)
it('should be able to save and count a single client error', function()
eq(1, exec_lua [[
vim.lsp.diagnostic.save(
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua
index 2b7198bf63..663271deab 100644
--- a/test/functional/plugin/lsp_spec.lua
+++ b/test/functional/plugin/lsp_spec.lua
@@ -9,6 +9,7 @@ local eq = helpers.eq
local pcall_err = helpers.pcall_err
local pesc = helpers.pesc
local insert = helpers.insert
+local funcs = helpers.funcs
local retry = helpers.retry
local NIL = helpers.NIL
local read_file = require('test.helpers').read_file
@@ -1392,10 +1393,10 @@ describe('LSP', function()
{ label='foocar', sortText="e", insertText='foodar', textEdit={newText='foobar'} },
{ label='foocar', sortText="f", textEdit={newText='foobar'} },
-- real-world snippet text
- { label='foocar', sortText="g", insertText='foodar', textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} },
- { label='foocar', sortText="h", insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', textEdit={} },
+ { label='foocar', sortText="g", insertText='foodar', insertTextFormat=2, textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} },
+ { label='foocar', sortText="h", insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', insertTextFormat=2, textEdit={} },
-- nested snippet tokens
- { label='foocar', sortText="i", insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', textEdit={} },
+ { label='foocar', sortText="i", insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', insertTextFormat=2, textEdit={} },
-- plain text
{ label='foocar', sortText="j", insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} },
}
@@ -1407,9 +1408,9 @@ describe('LSP', function()
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="d", insertText='foobar', textEdit={} } } } } },
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="e", insertText='foodar', textEdit={newText='foobar'} } } } } },
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="f", textEdit={newText='foobar'} } } } } },
- { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar(place holder, more ...holder{})', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="g", insertText='foodar', textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} } } } } },
- { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ1, var2 *typ2) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="h", insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', textEdit={} } } } } },
- { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ2,typ3 tail) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="i", insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', textEdit={} } } } } },
+ { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar(place holder, more ...holder{})', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="g", insertText='foodar', insertTextFormat=2, textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} } } } } },
+ { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ1, var2 *typ2) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="h", insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', insertTextFormat=2, textEdit={} } } } } },
+ { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ2,typ3 tail) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="i", insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', insertTextFormat=2, textEdit={} } } } } },
{ abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(${1:var1})', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="j", insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} } } } } },
}
@@ -1820,36 +1821,20 @@ describe('LSP', function()
end)
describe('lsp.util.jump_to_location', function()
- local default_target_bufnr
- local default_target_uri = 'file://fake/uri'
-
- local create_buf = function(uri, lines)
- for i, line in ipairs(lines) do
- lines[i] = '"' .. line .. '"'
- end
- lines = table.concat(lines, ", ")
-
- -- Let's set "hidden" to true in order to avoid errors when switching
- -- between buffers in test.
- local code = string.format([[
- vim.api.nvim_set_option('hidden', true)
+ local target_bufnr
- local bufnr = vim.uri_to_bufnr("%s")
- local lines = {%s}
+ before_each(function()
+ target_bufnr = exec_lua [[
+ local bufnr = vim.uri_to_bufnr("file://fake/uri")
+ local lines = {"1st line of text", "å å ɧ 汉语 ↥ 🤦 🦄"}
vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines)
return bufnr
- ]], uri, lines)
-
- return exec_lua(code)
- end
-
- before_each(function()
- default_target_bufnr = create_buf(default_target_uri, {'1st line of text', 'å å ɧ 汉语 ↥ 🤦 🦄'})
+ ]]
end)
- local location = function(uri, start_line, start_char, end_line, end_char)
+ local location = function(start_line, start_char, end_line, end_char)
return {
- uri = uri,
+ uri = "file://fake/uri",
range = {
start = { line = start_line, character = start_char },
["end"] = { line = end_line, character = end_char },
@@ -1857,9 +1842,9 @@ describe('LSP', function()
}
end
- local jump = function(bufnr, msg)
+ local jump = function(msg)
eq(true, exec_lua('return vim.lsp.util.jump_to_location(...)', msg))
- eq(bufnr, exec_lua[[return vim.fn.bufnr('%')]])
+ eq(target_bufnr, exec_lua[[return vim.fn.bufnr('%')]])
return {
line = exec_lua[[return vim.fn.line('.')]],
col = exec_lua[[return vim.fn.col('.')]],
@@ -1867,13 +1852,13 @@ describe('LSP', function()
end
it('jumps to a Location', function()
- local pos = jump(default_target_bufnr, location(default_target_uri, 0, 9, 0, 9))
+ local pos = jump(location(0, 9, 0, 9))
eq(1, pos.line)
eq(10, pos.col)
end)
it('jumps to a LocationLink', function()
- local pos = jump(default_target_bufnr, {
+ local pos = jump({
targetUri = "file://fake/uri",
targetSelectionRange = {
start = { line = 0, character = 4 },
@@ -1889,104 +1874,22 @@ describe('LSP', function()
end)
it('jumps to the correct multibyte column', function()
- local pos = jump(default_target_bufnr, location(default_target_uri, 1, 2, 1, 2))
+ local pos = jump(location(1, 2, 1, 2))
eq(2, pos.line)
eq(4, pos.col)
eq('å', exec_lua[[return vim.fn.expand('<cword>')]])
end)
it('adds current position to jumplist before jumping', function()
- exec_lua([[
- vim.api.nvim_win_set_buf(0, ...)
- vim.api.nvim_win_set_cursor(0, {2, 0})
- ]], default_target_bufnr)
- jump(default_target_bufnr, location(default_target_uri, 0, 9, 0, 9))
-
- local mark = exec_lua([[return vim.inspect(vim.api.nvim_buf_get_mark(..., "'"))]], default_target_bufnr)
- eq('{ 2, 0 }', mark)
- end)
-
- it('should not push item to tagstack if destination is the same as source', function()
- -- Set cursor at the 2nd line, 1st character. This is the source position
- -- for the test, and will also be the destination one, making the cursor
- -- "motionless", thus not triggering a push to the tagstack.
- exec_lua(string.format([[
- vim.api.nvim_win_set_buf(0, %d)
- vim.api.nvim_win_set_cursor(0, {2, 0})
- ]], default_target_bufnr))
-
- -- Jump to 'f' in 'foobar', at the 2nd line.
- jump(default_target_bufnr, location(default_target_uri, 1, 0, 1, 0))
+ funcs.nvim_win_set_buf(0, target_bufnr)
+ local mark = funcs.nvim_buf_get_mark(target_bufnr, "'")
+ eq({ 1, 0 }, mark)
- local stack = exec_lua[[return vim.fn.gettagstack()]]
- eq(0, stack.length)
- end)
-
- it('should not push the same item from same buffer twice to tagstack', function()
- -- Set cursor at the 2nd line, 5th character.
- exec_lua(string.format([[
- vim.api.nvim_win_set_buf(0, %d)
- vim.api.nvim_win_set_cursor(0, {2, 4})
- ]], default_target_bufnr))
-
- local stack
-
- -- Jump to 1st line, 1st column.
- jump(default_target_bufnr, location(default_target_uri, 0, 0, 0, 0))
-
- stack = exec_lua[[return vim.fn.gettagstack()]]
- eq({default_target_bufnr, 2, 5, 0}, stack.items[1].from)
-
- -- Go back to 5th character at 2nd line, which is currently at the top of
- -- the tagstack.
- exec_lua(string.format([[
- vim.api.nvim_win_set_cursor(0, {2, 4})
- ]], default_target_bufnr))
-
- -- Jump again to 1st line, 1st column. Since we're jumping from the same
- -- position we have just jumped from, this jump shouldn't be pushed to
- -- the tagstack.
- jump(default_target_bufnr, location(default_target_uri, 0, 0, 0, 0))
+ funcs.nvim_win_set_cursor(0, {2, 3})
+ jump(location(0, 9, 0, 9))
- stack = exec_lua[[return vim.fn.gettagstack()]]
- eq({default_target_bufnr, 2, 5, 0}, stack.items[1].from)
- eq(1, stack.length)
- end)
-
- it('should not push the same item from another buffer twice to tagstack', function()
- local target_uri = 'file://foo/bar'
- local target_bufnr = create_buf(target_uri, {'this is a line', 'foobar'})
-
- -- Set cursor at the 1st line, 3rd character of the default test buffer.
- exec_lua(string.format([[
- vim.api.nvim_win_set_buf(0, %d)
- vim.api.nvim_win_set_cursor(0, {1, 2})
- ]], default_target_bufnr))
-
- local stack
-
- -- Jump to 1st line, 1st column of a different buffer from the source
- -- position.
- jump(target_bufnr, location(target_uri, 0, 0, 0, 0))
-
- stack = exec_lua[[return vim.fn.gettagstack()]]
- eq({default_target_bufnr, 1, 3, 0}, stack.items[1].from)
-
- -- Go back to 3rd character at 1st line of the default test buffer, which
- -- is currently at the top of the tagstack.
- exec_lua(string.format([[
- vim.api.nvim_win_set_buf(0, %d)
- vim.api.nvim_win_set_cursor(0, {1, 2})
- ]], default_target_bufnr))
-
- -- Jump again to 1st line, 1st column of the different buffer. Since
- -- we're jumping from the same position we have just jumped from, this
- -- jump shouldn't be pushed to the tagstack.
- jump(target_bufnr, location(target_uri, 0, 0, 0, 0))
-
- stack = exec_lua[[return vim.fn.gettagstack()]]
- eq({default_target_bufnr, 1, 3, 0}, stack.items[1].from)
- eq(1, stack.length)
+ mark = funcs.nvim_buf_get_mark(target_bufnr, "'")
+ eq({ 2, 3 }, mark)
end)
end)
diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua
index f267f9fb5d..d2f9148e8f 100644
--- a/test/functional/treesitter/parser_spec.lua
+++ b/test/functional/treesitter/parser_spec.lua
@@ -235,6 +235,100 @@ void ui_refresh(void)
}, res)
end)
+ it('can match special regex characters like \\ * + ( with `vim-match?`', function()
+ if pending_c_parser(pending) then return end
+
+ insert('char* astring = "\\n"; (1 + 1) * 2 != 2;')
+
+ local res = exec_lua([[
+ cquery = vim.treesitter.parse_query("c", '((_) @plus (vim-match? @plus "^\\\\+$"))'..
+ '((_) @times (vim-match? @times "^\\\\*$"))'..
+ '((_) @paren (vim-match? @paren "^\\\\($"))'..
+ '((_) @escape (vim-match? @escape "^\\\\\\\\n$"))'..
+ '((_) @string (vim-match? @string "^\\"\\\\\\\\n\\"$"))')
+ parser = vim.treesitter.get_parser(0, "c")
+ tree = parser:parse()[1]
+ res = {}
+ for pattern, match in cquery:iter_matches(tree:root(), 0) do
+ -- can't transmit node over RPC. just check the name and range
+ local mrepr = {}
+ for cid,node in pairs(match) do
+ table.insert(mrepr, {cquery.captures[cid], node:type(), node:range()})
+ end
+ table.insert(res, {pattern, mrepr})
+ end
+ return res
+ ]])
+
+ eq({
+ { 2, { { "times", '*', 0, 4, 0, 5 } } },
+ { 5, { { "string", 'string_literal', 0, 16, 0, 20 } } },
+ { 4, { { "escape", 'escape_sequence', 0, 17, 0, 19 } } },
+ { 3, { { "paren", '(', 0, 22, 0, 23 } } },
+ { 1, { { "plus", '+', 0, 25, 0, 26 } } },
+ { 2, { { "times", '*', 0, 30, 0, 31 } } },
+ }, res)
+ end)
+
+ it('supports builtin query predicate any-of?', function()
+ if pending_c_parser(pending) then return end
+
+ insert([[
+ #include <stdio.h>
+
+ int main(void) {
+ int i;
+ for(i=1; i<=100; i++) {
+ if(((i%3)||(i%5))== 0)
+ printf("number= %d FizzBuzz\n", i);
+ else if((i%3)==0)
+ printf("number= %d Fizz\n", i);
+ else if((i%5)==0)
+ printf("number= %d Buzz\n", i);
+ else
+ printf("number= %d\n",i);
+ }
+ return 0;
+ }
+ ]])
+ exec_lua([[
+ function get_query_result(query_text)
+ cquery = vim.treesitter.parse_query("c", query_text)
+ parser = vim.treesitter.get_parser(0, "c")
+ tree = parser:parse()[1]
+ res = {}
+ for cid, node in cquery:iter_captures(tree:root(), 0) do
+ -- can't transmit node over RPC. just check the name, range, and text
+ local text = vim.treesitter.get_node_text(node, 0)
+ local range = {node:range()}
+ table.insert(res, {cquery.captures[cid], node:type(), range, text})
+ end
+ return res
+ end
+ ]])
+
+ local res0 = exec_lua([[return get_query_result(...)]],
+ [[((primitive_type) @c-keyword (#any-of? @c-keyword "int" "float"))]])
+ eq({
+ { "c-keyword", "primitive_type", { 2, 2, 2, 5 }, "int" },
+ { "c-keyword", "primitive_type", { 3, 4, 3, 7 }, "int" },
+ }, res0)
+
+ local res1 = exec_lua([[return get_query_result(...)]],
+ [[
+ ((string_literal) @fizzbuzz-strings (#any-of? @fizzbuzz-strings
+ "\"number= %d FizzBuzz\\n\""
+ "\"number= %d Fizz\\n\""
+ "\"number= %d Buzz\\n\""
+ ))
+ ]])
+ eq({
+ { "fizzbuzz-strings", "string_literal", { 6, 15, 6, 38 }, "\"number= %d FizzBuzz\\n\""},
+ { "fizzbuzz-strings", "string_literal", { 8, 15, 8, 34 }, "\"number= %d Fizz\\n\""},
+ { "fizzbuzz-strings", "string_literal", { 10, 15, 10, 34 }, "\"number= %d Buzz\\n\""},
+ }, res1)
+ end)
+
it('allow loading query with escaped quotes and capture them with `lua-match?` and `vim-match?`', function()
if pending_c_parser(pending) then return end
@@ -308,7 +402,7 @@ void ui_refresh(void)
return list
]]
- eq({ 'contains?', 'eq?', 'is-main?', 'lua-match?', 'match?', 'vim-match?' }, res_list)
+ eq({ 'any-of?', 'contains?', 'eq?', 'is-main?', 'lua-match?', 'match?', 'vim-match?' }, res_list)
end)
diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua
index 66aaf0c941..f3b840da21 100644
--- a/test/functional/ui/float_spec.lua
+++ b/test/functional/ui/float_spec.lua
@@ -64,10 +64,13 @@ describe('float window', function()
it('win_execute() should work' , function()
local buf = meths.create_buf(false, false)
- meths.buf_set_lines(buf, 0, -1, true, {'the floatwin'})
+ meths.buf_set_lines(buf, 0, -1, true, {'the floatwin', 'abc', 'def'})
local win = meths.open_win(buf, false, {relative='win', width=16, height=1, row=0, col=10})
local line = funcs.win_execute(win, 'echo getline(1)')
eq('\nthe floatwin', line)
+ eq('\n1', funcs.win_execute(win, 'echo line(".",'..win.id..')'))
+ eq('\n3', funcs.win_execute(win, 'echo line("$",'..win.id..')'))
+ eq('\n0', funcs.win_execute(win, 'echo line("$", 123456)'))
funcs.win_execute(win, 'bwipe!')
end)
@@ -1077,8 +1080,8 @@ describe('float window', function()
{1: abb }|
{13: acc }|
]], float_pos={
- [5] = { { id = 1002 }, "NW", 1, 0, 5, true },
- [6] = { { id = -1 }, "NW", 5, 4, 0, false }
+ [5] = { { id = 1002 }, "NW", 1, 0, 5, true, 50 },
+ [6] = { { id = -1 }, "NW", 5, 4, 0, false, 100 }
}, win_viewport={
[2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0};
[5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 2, curcol = 3};
@@ -2755,8 +2758,8 @@ describe('float window', function()
{1: word }|
{1: longtext }|
]], float_pos={
- [4] = {{ id = 1001 }, "NW", 1, 2, 5, true},
- [5] = {{ id = -1 }, "NW", 4, 1, 1, false}
+ [4] = {{ id = 1001 }, "NW", 1, 2, 5, true, 50},
+ [5] = {{ id = -1 }, "NW", 4, 1, 1, false, 100}
}}
else
screen:expect([[
@@ -2842,8 +2845,8 @@ describe('float window', function()
{1:yy }|
{1:zz }|
]], float_pos={
- [4] = {{ id = 1001 }, "NW", 1, 2, 5, true},
- [5] = {{ id = -1 }, "NW", 2, 1, 0, false}
+ [4] = {{ id = 1001 }, "NW", 1, 2, 5, true, 50},
+ [5] = {{ id = -1 }, "NW", 2, 1, 0, false, 100}
}}
else
screen:expect([[
@@ -3104,7 +3107,7 @@ describe('float window', function()
{1:word }|
{1:longtext }|
]], float_pos={
- [4] = {{id = -1}, "NW", 2, 1, 0, false}}
+ [4] = {{id = -1}, "NW", 2, 1, 0, false, 100}}
}
else
screen:expect([[
@@ -3148,8 +3151,8 @@ describe('float window', function()
{15:some info }|
{15:about item }|
]], float_pos={
- [4] = {{id = -1}, "NW", 2, 1, 0, false},
- [6] = {{id = 1002}, "NW", 2, 1, 12, true},
+ [4] = {{id = -1}, "NW", 2, 1, 0, false, 100},
+ [6] = {{id = 1002}, "NW", 2, 1, 12, true, 50},
}}
else
screen:expect([[
@@ -3263,7 +3266,7 @@ describe('float window', function()
{1:word }|
{1:longtext }|
]], float_pos={
- [4] = {{id = -1}, "NW", 2, 1, 0, false},
+ [4] = {{id = -1}, "NW", 2, 1, 0, false, 100},
}}
else
screen:expect([[
@@ -6296,6 +6299,69 @@ describe('float window', function()
]]}
end
end)
+
+ it('can use z-index', function()
+ local buf = meths.create_buf(false,false)
+ local win1 = meths.open_win(buf, false, {relative='editor', width=20, height=3, row=1, col=5, zindex=30})
+ meths.win_set_option(win1, "winhl", "Normal:ErrorMsg,EndOfBuffer:ErrorMsg")
+ local win2 = meths.open_win(buf, false, {relative='editor', width=20, height=3, row=2, col=6, zindex=50})
+ meths.win_set_option(win2, "winhl", "Normal:Search,EndOfBuffer:Search")
+ local win3 = meths.open_win(buf, false, {relative='editor', width=20, height=3, row=3, col=7, zindex=40})
+ meths.win_set_option(win3, "winhl", "Normal:Question,EndOfBuffer:Question")
+
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ {7: }|
+ {7:~ }|
+ {7:~ }|
+ ## grid 5
+ {17: }|
+ {17:~ }|
+ {17:~ }|
+ ## grid 6
+ {8: }|
+ {8:~ }|
+ {8:~ }|
+ ]], float_pos={
+ [4] = {{id = 1001}, "NW", 1, 1, 5, true, 30};
+ [5] = {{id = 1002}, "NW", 1, 2, 6, true, 50};
+ [6] = {{id = 1003}, "NW", 1, 3, 7, true, 40};
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0};
+ [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0};
+ [6] = {win = {id = 1003}, topline = 0, botline = 2, curline = 0, curcol = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }{7: }{0: }|
+ {0:~ }{7:~}{17: }{0: }|
+ {0:~ }{7:~}{17:~ }{8: }{0: }|
+ {0:~ }{17:~ }{8: }{0: }|
+ {0:~ }{8:~ }{0: }|
+ |
+ ]]}
+ end
+ end)
end
describe('with ext_multigrid', function()
diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua
index 9d7719a7c0..72468392ee 100644
--- a/test/functional/ui/messages_spec.lua
+++ b/test/functional/ui/messages_spec.lua
@@ -811,7 +811,7 @@ describe('ui/ext_messages', function()
{1:~ }|
{1:^~ }|
]], messages={
- {content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Helli"\nType number and <Enter> or click with mouse (empty cancels): ' } }, kind = ""}
+ {content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Helli"\nType number and <Enter> or click with the mouse (q or empty cancels): ' } }, kind = ""}
}}
feed('1')
@@ -822,7 +822,7 @@ describe('ui/ext_messages', function()
{1:~ }|
{1:^~ }|
]], messages={
- {content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Helli"\nType number and <Enter> or click with mouse (empty cancels): ' } }, kind = ""},
+ {content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Helli"\nType number and <Enter> or click with the mouse (q or empty cancels): ' } }, kind = ""},
{ content = { { "1" } }, kind = "" }
}}
diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua
index fcf6926433..f73d051857 100644
--- a/test/functional/ui/screen.lua
+++ b/test/functional/ui/screen.lua
@@ -429,6 +429,15 @@ screen:redraw_debug() to show all intermediate screen states. ]])
extstate.win_viewport = nil
end
+ if expected.float_pos then
+ expected.float_pos = deepcopy(expected.float_pos)
+ for _, v in pairs(expected.float_pos) do
+ if not v.external and v[7] == nil then
+ v[7] = 50
+ end
+ end
+ end
+
-- Convert assertion errors into invalid screen state descriptions.
for _, k in ipairs(concat_tables(ext_keys, {'mode', 'mouse_enabled'})) do
-- Empty states are considered the default and need not be mentioned.
@@ -1287,6 +1296,11 @@ function Screen:get_snapshot(attrs, ignore)
end
local function fmt_ext_state(name, state)
+ local function remove_all_metatables(item, path)
+ if path[#path] ~= inspect.METATABLE then
+ return item
+ end
+ end
if name == "win_viewport" then
local str = "{\n"
for k,v in pairs(state) do
@@ -1295,13 +1309,18 @@ local function fmt_ext_state(name, state)
..", curcol = "..v.curcol.."};\n")
end
return str .. "}"
- else
- -- TODO(bfredl): improve formatting of more states
- local function remove_all_metatables(item, path)
- if path[#path] ~= inspect.METATABLE then
- return item
+ elseif name == "float_pos" then
+ local str = "{\n"
+ for k,v in pairs(state) do
+ str = str.." ["..k.."] = {{id = "..v[1].id.."}"
+ for i = 2, #v do
+ str = str..", "..inspect(v[i])
end
+ str = str .. "};\n"
end
+ return str .. "}"
+ else
+ -- TODO(bfredl): improve formatting of more states
return inspect(state,{process=remove_all_metatables})
end
end
diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt
index 351f517945..e248232909 100644
--- a/third-party/CMakeLists.txt
+++ b/third-party/CMakeLists.txt
@@ -175,12 +175,12 @@ set(LUA_COMPAT53_SHA256 bec3a23114a3d9b3218038309657f0f506ad10dfbc03bb54e91da7e5
set(GPERF_URL https://github.com/neovim/deps/raw/ff5b4b18a87397a8564016071ae64f64bcd8c635/opt/gperf-3.1.tar.gz)
set(GPERF_SHA256 588546b945bba4b70b6a3a616e80b4ab466e3f33024a352fc2198112cdbb3ae2)
-# cat.exe curl.exe ca-bundle.crt diff.exe tee.exe xxd.exe
-set(WINTOOLS_URL https://github.com/neovim/deps/raw/da3520b568054ce057e6168243ff50eea223bfa0/opt/win32tools.zip)
-set(WINTOOLS_SHA256 190149d369ae1cd266bc39bceb2d1c061833a23640dfabd4089082c1a7824421)
+# cat.exe curl.exe curl-ca-bundle.crt diff.exe tee.exe xxd.exe
+set(WINTOOLS_URL https://github.com/neovim/deps/raw/9efd42511dcab26995fa3490f2319b270949159e/opt/win32tools.zip)
+set(WINTOOLS_SHA256 378069d88a34e7f7283622213569020a2aba7a54f0b431fba28690f7eae3af9c)
-set(WINGUI_URL https://github.com/equalsraf/neovim-qt/releases/download/v0.2.16/neovim-qt.zip)
-set(WINGUI_SHA256 aad95a1f8413a9ebf36fc0298d0dfd7d786abf88cb0f4ae9f7ec895b70c7b312)
+set(WINGUI_URL https://github.com/equalsraf/neovim-qt/releases/download/v0.2.16.1/neovim-qt.zip)
+set(WINGUI_SHA256 ddb4492db03da407703fb0ab271c4eb060250d1a7d71200e2b3b981cb0de59de)
set(WIN32YANK_X86_URL https://github.com/equalsraf/win32yank/releases/download/v0.0.4/win32yank-x86.zip)
set(WIN32YANK_X86_SHA256 62f34e5a46c5d4a7b3f3b512e1ff7b77fedd432f42581cbe825233a996eed62c)