Compare commits

...

70 Commits

Author SHA1 Message Date
Justin M. Keyes
1063aff643 ci(release): link to release notes #35585
fix #35580
2025-09-01 11:30:51 -04:00
Wise Man
e415fae42e ci: Windows arm64 packages #35345
Problem:
Neovim binaries are not provided for Windows ARM64.
GitHub Actions now offer native CI runners for Windows on ARM devices
(windows-11-arm), enabling automated builds and testing.

Solution:
- Modified CMake packaging to include packaging windows on arm binaries.
- Modified env script to install and initialize vs setup for arm64 arch. 

Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
2025-09-01 11:17:37 -04:00
Justin M. Keyes
7b099d2b2b version bump 2025-08-31 15:29:56 -04:00
Justin M. Keyes
cec0ecabd8 NVIM v0.11.4
See runtime/doc/news.txt (or `:help news` in Nvim).

Following is a list of fix/feature commits in this release.

FEATURES
--------------------------------------------------------------------------------
- 5551da79c1 lsp: improve signature help display #35190
- abfbd155da provider: detect venv python via "pynvim-python" tool #35273

BUILD
--------------------------------------------------------------------------------
- 3343ee971b deps: CMake generation fails when path contains spaces #35332

FIXES
--------------------------------------------------------------------------------
- 27282696fe api: fix not capturing output in cmdline mode (#35322)
- 09b0003d38 api: nvim_create_user_command addr option should allow ranges #35077
- 3ab06d5188 api: on_detach consistently before buf_freeall autocmds (#35369)
- 53db7fc3ef api,lsp: call on_detach before wiping out the buffer #35367
- 30b801eff2 checkhealth: wrong ABI version for treesitter parsers #35327
- a05b70baa6 clipboard: correct blockwise register width computation (#35038)
- 990b320592 cmdline: :checkhealth completion with multiple args (#35060)
- a3590afba5 diff: set default diff flags properly (#35450)
- f4b4c27a35 float: cursor visible in "hidden" floating window (#35219)
- 3cf9dac2ba folds: error when deleting lines at end of buffer #35396
- d70d469c04 health: accept TERM=tmux-direct #35511
- edfb447ff8 health: update advice for Python #35564
- 359d65c902 iter: ArrayIter:last returns nil when filtered to empty #34697
- 44b8255fa2 lsp: close floating preview window correctly #34946
- 6b820258cd lsp: don't override `config.title` in `vim.lsp.buf.signature_help()` #35075
- 8f2d6f7ce2 lsp: show title when global winborder is set (#35181)
- 1bea812953 lsp: update window title when cycling through signatures #35407
- 9261aef2f3 lsp/health: always use vim.inspect to show root_markers (#34667)
- d185057bc7 lsp/health: ensure valid table before concatenating (#34930)
- 09f702bc13 lua: report error in Lua Funcref callback properly (#35555)
- 7f1e112a32 pum: check for cmdline mode properly
- 41fa343484 snippet: jumping backwards to choice node (#35062)
- 2e4baa3679 snippet: setting end_right_gravity (#35061)
- 4b957a4d18 treesitter: inconsistent highlight of multiline combined injection #35307
- fa64f2d09b treesitter: run FileType autocmds in the context of `<abuf>`
- e841d653af treesitter: show capture-level priorities in :Inspect #35443
- e299430ff5 tui: do not remove SIGWINCH handler when resize events are enabled (#35221) (#35238)
- 64afa93187 tutor: use `invalidate` field in `nvim_buf_set_extmark()`
- 2124146164 tutor: use legacy syntax for lesson 3.1 of vim-01-beginner.tutor
- 6fd842a4fd ui: check for cmdline mode properly

VIM PATCHES
--------------------------------------------------------------------------------
- e68d3ef886 5ddcecf: runtime(help): Add better support for language annotation highlighting
- 35a66f74c7 6fea0a5: runtime(help): Add Vim lang annotation support for codeblocks
- 819e545c28 714671d: runtime(misc): use :hor :term to ensure new term window is split horizontally (#35064)
- a65c4be2de 8.1.0425: ml_get error and crash with appendbufline()
- 8dd88056f1 8.2.1672: v_lock is used when it is not initialized (#35416)
- 6c2f06b537 9.1.0748: :keep* commmands are sometimes misidentified as :k
- 54c2ea142a 9.1.1599: :bnext doesn't go to unlisted help buffers (#35216)
- 53a0d99702 9.1.1601: Patch v8.1.0425 was wrong
- ced4eed733 9.1.1607: :apple command detected as :append (#35237)
- bd4b45dd1b 9.1.1608: No command-line completion for :unsilent {command}
- d21db345ef 9.1.1611: possible undefined behaviour in mb_decompose() (#35275)
- 744d96bd76 9.1.1612: Ctrl-G/Ctrl-T do not ignore the end search delimiter
- 5ec7d98857 9.1.1613: tests: test_search leaves a few swapfiles behind
- 39ae9a9971 9.1.1633: Search pattern shown incorrectly with negative offset (#35337)
- e6ea97a691 9.1.1665: Outdated comment in eval.c (#35436)
- 6fd8ba05a6 9.1.1667: Another outdated comment in eval.c (#35438)
- 4c5cb950c6 9.1.1688: potential buffer overrun in bufwrite.c (#35497)
- 99817471d7 9.1.1700: Multiline ignorecase specific pattern does not match with 'ignorecase' (#35520)
- fb6c677d57 b9ea0a8: runtime(doc): tweak documentation style in helphelp.txt
2025-08-31 15:25:45 -04:00
Michael Henry
edfb447ff8 fix(health): update advice for Python #35564
Problem: `:checkhealth` advice for Python is out-of-date.

Solution: Update the advice to point to `:help provider-python`.
(cherry picked from commit f311c96973)
2025-08-31 18:51:33 +00:00
zeertzjq
b032c2b53f Merge pull request #35561 from zeertzjq/backport
fix(lua): report error in Lua Funcref callback properly (#35555)
2025-08-31 07:37:42 +08:00
zeertzjq
09f702bc13 fix(lua): report error in Lua Funcref callback properly (#35555) 2025-08-31 07:13:00 +08:00
glepnir
c4845f3a12 docs(lsp): mention lsp/after/ in faq #35534
(cherry picked from commit 1b3abfa688)
2025-08-30 22:56:57 +00:00
zeertzjq
a2603016ce Merge pull request #35546 from zeertzjq/vim-a07a2f4
vim-patch:a07a2f4: runtime(astro): catch json_decode() error when parsing tsconfig.json
(cherry picked from commit 8a2587be23)
2025-08-30 00:55:38 +00:00
Meriel Luna Mittelbach
d70d469c04 fix(health): accept TERM=tmux-direct #35511
tmux-direct is functionally the same as tmux-256color, except it
directly reports 24-bit color and how to set them (setaf/setab)
via ncurses 6.x's extended terminfo format.

(cherry picked from commit a33284c2c0)
2025-08-28 04:25:15 +00:00
zeertzjq
99817471d7 vim-patch:9.1.1700: Multiline ignorecase specific pattern does not match with 'ignorecase' (#35520)
Problem:  a pattern that involves a backref on a different line does not
          match when 'ignorecase' is set (QiWei, after v9.1.0645)
Solution: Use MB_STRNICMP when ignorecase is set, fix tests to close
          swapfiles

related: vim/vim#14756
fixes: vim/vim#17470
closes: vim/vim#18104

bf82e58a70

Co-authored-by: Christian Brabandt <cb@256bit.org>
(cherry picked from commit 24020ef2dd)
2025-08-27 23:56:49 +00:00
zeertzjq
4c5cb950c6 vim-patch:9.1.1688: potential buffer overrun in bufwrite.c (#35497)
Problem:  potential buffer overrun in bufwrite.c
Solution: Use a temporary variable (John Marriott)

In my Windows 11 Pro 64-bit build MAXPATHL is 1024 and IOSIZE is 1025.
In my Archlinux Linux 64-bit build MAXPATHL is 4096 and IOSIZE is 1025.

In funuction buf_write():
There is a check (line 713) that makes sure the length of fname is less
than MAXPATHL. There is a call to STRCPY() (line 1208) which copies the
string at fname into IObuff (which has size IOSIZE). For Unix builds
fname is set to sfname which may or may not be shorter. However, if
sfname is NULL sfname is set to fname.

Therefore, in builds where MAXPATHL > IOSIZE (eg in my linux build), it
is theoretically possible for the STRCPY() call to exceed the bounds of
IObuff.

This PR addresses this by copying fname into a local variable that has
the same maximum size as fname.

In addition:
Given that the filename is unconditionally overwritten in the for loop,
only copy the directory portion of fname. Move variable i closer to
where it is used.

closes: vim/vim#18095

a19b019b87

Co-authored-by: John Marriott <basilisk@internode.on.net>
(cherry picked from commit 4263ec21c2)
2025-08-27 03:15:15 +00:00
zeertzjq
ec8900f1e6 Merge pull request #35451 from zeertzjq/backport
fix(diff): set default diff flags properly (#35450)
2025-08-24 15:22:22 +08:00
zeertzjq
a3590afba5 fix(diff): set default diff flags properly (#35450) 2025-08-24 14:43:19 +08:00
Riley Bruins
e841d653af fix(treesitter): show capture-level priorities in :Inspect #35443
(cherry picked from commit 29c5559ce1)
2025-08-23 23:18:41 +00:00
zeertzjq
6fd8ba05a6 vim-patch:9.1.1667: Another outdated comment in eval.c (#35438)
Problem:  Another outdated comment in eval.c (after 9.1.1665).
Solution: Remove that comment as well. Add a few more tests for mapnew()
          that fail without patch 8.2.1672 (zeertzjq).

closes: vim/vim#18089

6b56711804
(cherry picked from commit 639f9f4cda)
2025-08-23 11:10:29 +00:00
zeertzjq
e6ea97a691 vim-patch:9.1.1665: Outdated comment in eval.c (#35436)
Problem:  Outdated comment in eval.c.
Solution: Remove the comment, which is no longer true after 8.2.1672.
          Also fix a typo in version9.txt (zeertzjq).

closes: vim/vim#18077

5d3c39af2a
(cherry picked from commit b9699d5701)
2025-08-23 00:46:12 +00:00
tao
3cf9dac2ba fix(folds): error when deleting lines at end of buffer #35396
Problem:
with `foldmethod=expr foldexpr=v:lua.vim.treesitter.foldexpr()
foldminlines=0`, deleting lines at the end of the buffer always
reports an invalid top error, because the top value (i.e. the
start line number of the deletion) is always 1 greater than
the total line number of the modified buffer.

Solution:
remove the ml_line_count validation

(cherry picked from commit d73cfefed5)
2025-08-22 03:22:33 +00:00
zeertzjq
8dd88056f1 vim-patch:8.2.1672: v_lock is used when it is not initialized (#35416)
Problem:    v_lock is used when it is not initialized. (Yegappan Lakshmanan)
Solution:   Initialize the typval in eval1().

4a091b9978

Co-authored-by: Bram Moolenaar <Bram@vim.org>
(cherry picked from commit 865a28155e)
2025-08-21 23:02:40 +00:00
Tiago Inaba
1bea812953 fix(lsp): update window title when cycling through signatures #35407
(cherry picked from commit 848c7a7894)
2025-08-21 00:51:35 +00:00
Sean Dewar
fa64f2d09b fix(treesitter): run FileType autocmds in the context of <abuf>
Problem: many FileType autocommands assume curbuf is the same as the target
buffer; this can cause &syntax to be restored for the wrong buffer in some cases
when TSHighlighter:destroy is called.

Solution: run nvim_exec_autocmds in the context of the target buffer via
nvim_buf_call.

(cherry picked from commit 3ec63cdab8)
2025-08-19 19:52:41 +00:00
neovim-backports[bot]
3ab06d5188 fix(api): on_detach consistently before buf_freeall autocmds (#35369)
Problem: on_detach may be called after buf_freeall and other important things,
plus its textlock restrictions are insufficient. This can cause issues such as
leaks, internal errors and crashes.

Solution: disable buffer updates in buf_freeall, before autocommands (like the
order after #35355 and when do_ecmd reloads a buffer). Don't do so in
free_buffer_stuff; it's not safe to run user code there, and buf_freeall already
runs before then; just free them to avoid leaks if buf_freeall autocommands
registered more for some reason.

(cherry picked from commit 2211953266)

Co-authored-by: Sean Dewar <6256228+seandewar@users.noreply.github.com>
2025-08-17 15:39:32 -07:00
neovim-backports[bot]
37b2d42459 refactor(tests): remove redundant test (#35368)
(cherry picked from commit 7eb9badd93)

Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
2025-08-17 15:14:31 -07:00
neovim-backports[bot]
53db7fc3ef fix(api,lsp): call on_detach before wiping out the buffer #35367
Problem:
Buffer-updates on_detach callback is invoked before buf_freeall(), which
deletes autocmds of the buffer (via apply_autocmds(EVENT_BUFWIPEOUT,
...)). Due to this, buffer-local autocmds executed in on_detach (e.g.,
LspDetach) are not actually invoked.

Solution:
Call buf_updates_unload() before buf_freeall().

(cherry picked from commit 285c04e2d0)

Co-authored-by: Jaehwang Jung <tomtomjhj@gmail.com>
2025-08-17 14:55:19 -07:00
MinimalEffort07
3343ee971b build(deps): CMake generation fails when path contains spaces #35332
Problem:
Additional include directories in DEPS_INCLUDE_FLAGS variable are not
quoted. Paths with spaces break the resulting compile command.

Solution:
Enclose values in double quotes.
Note: normally we should avoid manual quoting, but in this case we can't
because of how `DEPS_INCLUDE_FLAGS` is used in `BuildLuv.cmake`
and `BuildLpeg.cmake`.

(cherry picked from commit 77860f5418)
2025-08-16 23:46:28 +00:00
Michael Henry
abfbd155da feat(provider): detect venv python via "pynvim-python" tool #35273
Problem:
Detection of the pynvim module is currently done by finding the first
Python interpreter in the `PATH` and checking if it can import pynvim.
This has several problems:
- Activation of an unrelated Python virtual environment will break
  automatic detection, unless pynvim is also installed in that
  environment.
- Installing pynvim to the expected location is difficult. User
  installation into the system-wide or user-wide Python site area is now
  deprecated.  On Ubuntu 24.04 with Python 3.12, for example, the
  command `pip install --user pynvim` now fails with the error message
  `error: externally-managed-environment`.
- Users may create a dedicated virtual environment in which to install
  pynvim, but Nvim won't detect it; instead, they must either activate
  it before launching Nvim (which interferes with the user of other
  virtual environments) or else hard-code the variable
  `g:python3_host_prog` in their `init.vim` to the path of the correct
  Python interpreter.  Neither option is desirable.

Solution:
Expose pynvim's Python interpreter on the `PATH` under the
name `pynvim-python`.  Typical user-flow:

1. User installs either uv or pipx.
2. User installs pynvim via:
   ```
   uv tool install --upgrade pynvim
   # Or:
   pipx install --upgrade pynvim
   ```

With corresponding changes in pynvim https://github.com/neovim/pynvim/issues/593
the above user-flow is all that's needed for Nvim to detect the
installed location of pynvim, even if an unrelated Python virtual
environments is activated.  It uses standard Python tooling to automate
the necessary creation of a Python virtual environment for pyenv and the
publication of `pynvim-python` to a directory on `PATH`.

(cherry picked from commit 5f8d4a248a)
2025-08-16 22:32:55 +00:00
zeertzjq
39ae9a9971 vim-patch:9.1.1633: Search pattern shown incorrectly with negative offset (#35337)
Problem:  Search pattern shown incorrectly with negative offset.
          (lkintact)
Solution: Don't prepend a '+' sign to a negative offset (zeertzjq).

fixes: vim/vim#17993
closes: vim/vim#17994

ade0815856
(cherry picked from commit 8d154e5927)
2025-08-15 00:34:11 +00:00
zeertzjq
5ec7d98857 vim-patch:9.1.1613: tests: test_search leaves a few swapfiles behind
Problem:  tests: test_search leaves a few swapfiles behind
Solution: Use :bw! instead of :close to close the swapfile at the end of
          the test.

related: vim/vim#17933

a2bb21a895

Co-authored-by: Christian Brabandt <cb@256bit.org>
(cherry picked from commit 9e8d551b1e)
2025-08-15 07:48:56 +08:00
zeertzjq
744d96bd76 vim-patch:9.1.1612: Ctrl-G/Ctrl-T do not ignore the end search delimiter
Problem:  Ctrl-G/Ctrl-T does not ignore the end search delimiter
          (irisjae)
Solution: Check if the pattern ends with a search delimiter and ignore
          it, unless it is part of the pattern.

fixes: vim/vim#17895
closes: vim/vim#17933

c03990d30f

Co-authored-by: Christian Brabandt <cb@256bit.org>
(cherry picked from commit 1eca030fb2)
2025-08-15 07:48:56 +08:00
Jalil David Salamé Messina
30b801eff2 fix(checkhealth): wrong ABI version for treesitter parsers #35327
Don't print ABI version of duplicated parsers that are later in the
runtime path (see [#35326]).

Change the sorting from `name > path` to `name > rtpath_index`, this
ensures the first (loaded) parser is first in the list and any
subsequent parsers can be considered "not loaded".

This is fuzzy at best since `vim.treesitter.language.add` can take a
path to a parser and change the load order.

The correct solution is for `vim.treesitter.language.inspect` to return
the parser path so we can compare against it and/or for it to also be
able to take a path to a parser so we can inspect it without loading it
first.

(cherry picked from commit bd45e2be63)
2025-08-14 19:19:01 +00:00
zeertzjq
27282696fe fix(api): fix not capturing output in cmdline mode (#35322)
(cherry picked from commit 7b9512e613)
2025-08-13 12:40:07 +00:00
Steven Xu
4b957a4d18 fix(treesitter): inconsistent highlight of multiline combined injection #35307
Problem:
Combined injections not entirely highlighted.

Solution:
Reapply layer highlights on each line.

Co-authored-by: Artem <vanaigranov@gmail.com>
2025-08-12 14:14:29 -07:00
zeertzjq
819e545c28 vim-patch:714671d: runtime(misc): use :hor :term to ensure new term window is split horizontally (#35064)
Problem:  :term splits new window above in vim, but in nvim it change
          the buffer for current window
Solution: :hor term to ensure consistent splitting for Vim and Neovim

closes: vim/vim#17822

714671de35

Co-authored-by: phanium <91544758+phanen@users.noreply.github.com>
(cherry picked from commit 1f7432a272)
2025-08-10 23:17:58 +00:00
zeertzjq
d21db345ef vim-patch:9.1.1611: possible undefined behaviour in mb_decompose() (#35275)
Problem:  possible undefined behaviour in mb_decompose(), when using the
          same pointer as argument several times
Solution: use separate assignments to avoid reading and writing the same
          object at the same time (Áron Hárnási)

closes: vim/vim#17953

c43a0614d4

Co-authored-by: Áron Hárnási <aron.harnasi@gmail.com>
(cherry picked from commit 77500c5ad5)
2025-08-09 23:50:33 +00:00
zeertzjq
bd4b45dd1b vim-patch:9.1.1608: No command-line completion for :unsilent {command}
Problem:  No command-line completion for :unsilent {command}.
Solution: Add missing command arg completion (Doug Kearns).
          (author)

Add completion tests for all command modifiers.

closes: vim/vim#17524

126731c8fd

Co-authored-by: Doug Kearns <dougkearns@gmail.com>
(cherry picked from commit fe42c81f2e)
2025-08-09 01:25:25 +00:00
zeertzjq
6c2f06b537 vim-patch:9.1.0748: :keep* commmands are sometimes misidentified as :k
Problem:  The :keep{alt,jumps,marks,patterns} commmands are sometimes
          misidentified as :k.
Solution: Make sure one_letter_cmd() only returns true for :k and not
          other :keep* commands (Doug Kearns).

This currently manifests as missing completion for :keep* commands and
incorrect results from fullcommand().

E.g., fullcommand("keepmarks") returns "k" rather than "keepmarks".

The correct command, however, is executed as command modifiers are
handled specially in do_one_cmd() rather than using find_ex_command().

Fix exists(':k') so that it returns 2 for a full match.

closes: vim/vim#15742

ea84202372

Cherry-pick Test_ex_command_completion() from patch 9.1.0624.

Co-authored-by: Doug Kearns <dougkearns@gmail.com>
(cherry picked from commit 70bb7999f7)
2025-08-09 01:25:25 +00:00
brianhuster
fb6c677d57 vim-patch:b9ea0a8: runtime(doc): tweak documentation style in helphelp.txt
closes: vim/vim#16302

b9ea0a89fa

Co-authored-by: h-east <h.east.727@gmail.com>

I removed some parts that are not applicable to Nvim, like HelpTOC

(cherry picked from commit 7082367b3a)
2025-08-08 23:37:28 +00:00
brianhuster
e68d3ef886 vim-patch:5ddcecf: runtime(help): Add better support for language annotation highlighting
closes: vim/vim#16238

5ddcecf05f

Co-authored-by: Shougo Matsushita <Shougo.Matsu@gmail.com>
Co-authored-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: h_east <h.east.727@gmail.com>
(cherry picked from commit 60b866049c)
2025-08-08 23:37:28 +00:00
brianhuster
35a66f74c7 vim-patch:6fea0a5: runtime(help): Add Vim lang annotation support for codeblocks
closes: vim/vim#16215

6fea0a5480

Co-authored-by: Shougo Matsushita <Shougo.Matsu@gmail.com>
Co-authored-by: zeertzjq <zeertzjq@outlook.com>
(cherry picked from commit 11ae879ebd)
2025-08-08 23:37:28 +00:00
zeertzjq
ced4eed733 vim-patch:9.1.1607: :apple command detected as :append (#35237)
Problem:  :apple command detected as :append (dai475694450)
Solution: Disallow to define a custom command with lower-case letter,
          correctly detect :insert/:change/:append ex commands
          (Hirohito Higashi).

fixes: vim/vim#17893
closes: vim/vim#17930

efd83d441b

Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
(cherry picked from commit e7dfbf1343)
2025-08-08 14:21:31 +00:00
Gregory Anders
e299430ff5 fix(tui): do not remove SIGWINCH handler when resize events are enabled (#35221) (#35238)
When Nvim is started in one terminal emulator,
suspended, and later resumed in a different terminal emulator (as can
happen when using e.g. a multiplexer), the new terminal emulator will
not have all of the same modes enabled that the original terminal
emulator had. This is a problem in particular for in-band resize events
because we were disabling the SIGWINCH signal handler when we determined
that the terminal supported this mode.

However, if the new terminal does not support this mode then the
SIGWINCH handler remains disabled, and Neovim no longer properly
resizes. Instead of disabling the SIGWINCH handler, we track the state
of the resize events mode internally. If the mode is enabled then we
return early from the SIGWINCH handler before performing any ioctls or
other system calls. But if the mode is not enabled we proceed as normal.

(cherry picked from commit b1679f0ab6)
2025-08-08 13:50:02 +00:00
zeertzjq
53a0d99702 vim-patch:9.1.1601: Patch v8.1.0425 was wrong
Problem:  Patch v8.1.0425 was wrong
Solution: Revert that patch (Hirohito Higashi)

This is because the root cause was fixed in 8.1.0786 and a regression
occurred elsewhere.

related: vim/vim#3455
related: vim/vim#3830
fixes: vim/vim#11558
closes: vim/vim#17899

6abe5e4904

Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
(cherry picked from commit 7230296bdb)
2025-08-08 00:12:27 +00:00
zeertzjq
a65c4be2de vim-patch:8.1.0425: ml_get error and crash with appendbufline()
Problem:    ml_get error and crash with appendbufline(). (Masashi Iizuka)
Solution:   Set per-window buffer info. (Hirohito Higashi, closes vim/vim#3455)

9cea87c577

Co-authored-by: Bram Moolenaar <Bram@vim.org>
(cherry picked from commit c6f0a19206)
2025-08-08 00:12:27 +00:00
zeertzjq
30db74de66 Merge pull request #35224 from zeertzjq/backport
Backport #35198 #35202 #35210
2025-08-08 06:31:10 +08:00
zeertzjq
6fd842a4fd fix(ui): check for cmdline mode properly
Backport of #35202
2025-08-08 06:11:28 +08:00
zeertzjq
7f1e112a32 fix(pum): check for cmdline mode properly
Backport of #35198 and #35210
2025-08-08 06:11:25 +08:00
zeertzjq
54c2ea142a vim-patch:9.1.1599: :bnext doesn't go to unlisted help buffers (#35216)
Problem:  :bnext doesn't go to unlisted help buffers when cycling
          through help buffers (after 9.1.0557).
Solution: Don't check if a help buffer is listed (zeertzjq).

From <https://github.com/vim/vim/issues/4478#issuecomment-498831057>:

> I think we should fix that, since once you get to a non-help buffer
> all unlisted buffers are skipped, thus you won't encounter another
> help buffer.

This implies that cycling through help buffers should work even if help
buffers are unlisted. Otherwise this part of :bnext isn't really useful,
as :h makes help buffers unlisted by default.

related: vim/vim#4478
related: vim/vim#15198
closes: vim/vim#17913

9662f33480
(cherry picked from commit 53ac2ad20a)
2025-08-07 16:47:04 +00:00
neovim-backports[bot]
f4b4c27a35 fix(float): cursor visible in "hidden" floating window (#35219)
Problem:
Cursor is visible in "hidden" floating window.

Solution:
Hide cursor when curwin is a hidden floating window.
Show cursor after returning to a normal (non-hidden) window.

(cherry picked from commit d4f2b9050d)

Co-authored-by: glepnir <glephunter@gmail.com>
2025-08-07 16:18:32 +00:00
skewb1k
5551da79c1 feat(lsp): improve signature help display #35190
- Add delimiter between function signature and documentation, matching hover formatting
- Show title only if there are multiple clients or multiple signatures
- Avoid duplicating the title inside the window if it's already shown in the border

(cherry picked from commit d26db4bfbf)
2025-08-07 03:29:04 +00:00
glepnir
8f2d6f7ce2 fix(lsp): show title when global winborder is set (#35181)
Problem: make_floating_popup_options only shows when opts.border is explicitly set, ignoring global winborder setting

Solution: check both opts.border and vim.o.winborder when determining whether to show title
(cherry picked from commit 5b1b46ea5a)
2025-08-07 03:01:03 +00:00
glepnir
09b0003d38 fix(api): nvim_create_user_command addr option should allow ranges #35077
Problem: Using `addr` without `range` in nvim_create_user_command gives
"No range allowed" error, inconsistent with `:command -addr` behavior.

Solution: Set EX_RANGE flag when `addr` option is specified to match
`:command` behavior.

(cherry picked from commit 22df649210)
2025-08-03 03:40:46 +00:00
TheBlob42
41fa343484 fix(snippet): jumping backwards to choice node (#35062)
Avoid duplicate text when jumping back to a choice node. Set cursor to
end of tabstop range and prioritize current choice in completion items.

(cherry picked from commit 628d569a59)
2025-07-28 00:35:42 +00:00
TheBlob42
2e4baa3679 fix(snippet): setting end_right_gravity (#35061)
When right_gravity is set to true for deactivating tabstop expansion we
have to set end_right_gravity to false to avoid expanding the tabstop
region on the right side. Vice versa for activating tabstop expansion
again.

(cherry picked from commit dff78f580d)
2025-07-28 00:24:44 +00:00
Maria José Solano
6b820258cd fix(lsp): don't override config.title in vim.lsp.buf.signature_help() #35075
(cherry picked from commit afebbd0f34)
2025-07-27 03:17:18 +00:00
zeertzjq
990b320592 fix(cmdline): :checkhealth completion with multiple args (#35060)
(cherry picked from commit 5de2ec76a3)
2025-07-25 23:29:54 +00:00
zeertzjq
a05b70baa6 fix(clipboard): correct blockwise register width computation (#35038)
(cherry picked from commit 95dfb063da)
2025-07-23 03:18:38 +00:00
glepnir
359d65c902 fix(iter): ArrayIter:last returns nil when filtered to empty #34697
Problem: After filtering out all elements, ArrayIter:last still returns a stale element.
Solution: Add check for self._head == self._tail and return nil early.

Fix #34696

(cherry picked from commit 4fe51dfdae)
2025-07-23 02:33:18 +00:00
Phạm Bình An
62aae1084f docs: update usr_02.txt #35031
Problem:
- There is reference to gVim in the usr_02.txt file, even though Nvim
  has no built-in GUI.
- `:h help-summary` has a section about optional features (e.g.
  `+conceal`) even though such thing does not exist in Nvim (`:h
  +conceal` will give E149 error).

Solution:
- Remove reference to gVim.
- Replace the section about optional features with a section about Lua.

(cherry picked from commit d591275db7)
2025-07-22 23:15:21 +00:00
Justin M. Keyes
e534afa5ab Merge #34986 from brianhuster/release-0.11 2025-07-22 07:46:18 -07:00
brianhuster
2124146164 fix(tutor): use legacy syntax for lesson 3.1 of vim-01-beginner.tutor
Problem:
- Extmark breaks lesson 3.1 of vim-01-beginner.tutor because when users
  delete the line and put it elsewhere, the extmark doesn't move to the
  put location.
- This doesn't mean the extmark implementation is bad though (note that
  thanks to extmark, for the first time, we can make lesson 2.6 really
  interactive), it's just that the tutor format has never been made for
  kinds of lessons like lesson 3.1, which is why all "expected" in that
  lesson are -1, which also means that lesson is not interactive in the
  first place. Also see lesson 2.1.3 in vim-02-beginner, where the mark
  is just used to mark the first line of the exercise, which also prove
  my point.

Solution:
- For a not-really-interactive lesson like lesson 3.1, just use legacy
  syntax. I borrow the old vimtutor's `--->` to mark the exercises of
  the lesson.
- Less redundant interactive marks also make the json files smaller and
  more maintainable.
2025-07-19 22:40:28 +07:00
brianhuster
64afa93187 fix(tutor): use invalidate field in nvim_buf_set_extmark()
Problem:
If users delete a line containing extmark, it will move to the next
line, which could highlight the next line in an unwanted way.

Solution:
- Use `invalidate` field in `nvim_buf_set_extmark()` to prevent the
extmark from moving.
- Also save from "priority" hacking, since we can check if the extmark
  is valid in `nvim_buf_get_extmarks()` now.
2025-07-19 09:42:16 +07:00
brianhuster
e6a0f0ee71 test(tutor_spec): remove test description("Tutor: tutor")
Problem:
It is redundant since we have another test that test interactive marks
in lesson 2.6 of tutor 1
2025-07-18 16:45:25 +07:00
brianhuster
685302682a refactor(tutor): reimplement interactive marks as extmark in Lua
Problem:
From https://matrix.to/#/!cylwlNXSwagQmZSkzs:matrix.org/$Ofj-TFIsEMbp0O9OhE8xuZSNi-nhRLtZTOgs6JRLNrs?via=matrix.org&via=gitter.im&via=mozilla.org

In lesson 2.6, users are asked to remove the second, forth and fifth
lines with `dd` command, then they are asked to undo twice to make the
text go back to original state. But after that, the mark ✗ appears
again, which confuses the user because they think they do something
wrong. This is a limitation with the current implementation, which is
based on line number only.

Solution:
Reimplement interactive marks as extmarks in Lua. This also make the
feature less fragile, as users can remove, add some arbitrary lines
without breaking the interactive marks.

Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
2025-07-18 16:45:06 +07:00
PilgrimLyieu
5e7021eb1b docs(tutor): Chinese (zh-CN) translation #34803
Co-authored-by: glepnir <glephunter@gmail.com>
2025-07-18 16:44:14 +07:00
Donatas
44b8255fa2 fix(lsp): close floating preview window correctly #34946
Problem:
After 28b7c2d (found with bisect) the hover preview window does not
close when :edit'ing another file, even when you move the cursor.

Solution:
Change the BufLeave to target the original buffer, not the preview
buffer.

(cherry picked from commit ace254c9ff)
2025-07-16 03:46:59 +00:00
Phạm Bình An
407fc0bb16 revert: "fix(runtime): set 'foldmethod' for Lua ftplugin #34929" (#34947)
This reverts commit 12276832ab.

(cherry picked from commit 9789a3b854)
2025-07-15 22:59:33 +00:00
Phạm Bình An
657540945c fix(runtime): set 'foldmethod' for Lua ftplugin #34929
Problem:
Neovim's Lua ftplugin doesn't set `'foldmethod'`, though Vim one sets it 1341176e7b/runtime/ftplugin/lua.vim (L66-L68)

Solution:
Set it

(cherry picked from commit 12276832ab)
2025-07-14 22:04:30 +00:00
Gregory Anders
9261aef2f3 fix(lsp/health): always use vim.inspect to show root_markers (#34667)
In https://github.com/neovim/neovim/pull/34092 we changed the
healthcheck to display root markers as a concatenated list if the first
item in root_markers is a string (not a table). However, this does not
solve the general case, because root_markers can contain a string as the
first element, but a table as the 2nd element.

Because root_markers has a more complex structure we should always just
display it using vim.inspect, rather than adding a special case for when
all items are a string.

(cherry picked from commit f0c0c24ed7)
2025-07-14 12:59:40 +00:00
neovim-backports[bot]
d185057bc7 fix(lsp/health): ensure valid table before concatenating (#34930)
The root_markers field can now contain a table of tables (as of
https://github.com/neovim/neovim/pull/33485) and :checkhealth will show
an error in that case since Lua cannot concatenate a table of tables.

Ensure that tables contain strings before concatenating and if not, fall
back to using vim.inspect().

(cherry picked from commit 5ad01184f3)

Co-authored-by: Gregory Anders <greg@gpanders.com>
2025-07-14 05:15:27 -07:00
Justin M. Keyes
6cfaa9c204 version bump 2025-07-12 14:36:34 -04:00
104 changed files with 3046 additions and 418 deletions

View File

@@ -1,9 +1,17 @@
# This script enables Developer Command Prompt
# See https://github.com/microsoft/vswhere/wiki/Start-Developer-Command-Prompt#using-powershell
$installationPath = vswhere.exe -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
if ($installationPath -and (Test-Path "$installationPath\Common7\Tools\vsdevcmd.bat")) {
& "${env:COMSPEC}" /s /c "`"$installationPath\Common7\Tools\vsdevcmd.bat`" -arch=x64 -no_logo && set" | ForEach-Object {
$name, $value = $_ -split '=', 2
"$name=$value" >> $env:GITHUB_ENV
}
if ($env:BUILD_ARCH -eq "arm64") {
$arch = "arm64"
$installationPath = vswhere.exe -latest -requires Microsoft.VisualStudio.Component.VC.Tools.arm64 -property installationPath
} else {
$arch = "x64"
$installationPath = vswhere.exe -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
}
if ($installationPath) {
& "${env:COMSPEC}" /s /c "`"$installationPath\Common7\Tools\vsdevcmd.bat`" -arch=$arch -no_logo && set" |
ForEach-Object {
$name, $value = $_ -split '=', 2
"$name=$value" >> $env:GITHUB_ENV
}
}

View File

@@ -2,21 +2,26 @@
${NVIM_VERSION}
```
## Release notes
- [Changelog](https://github.com/neovim/neovim/commit/${NVIM_COMMIT}) (fixes + features)
- [News](./runtime/doc/news.txt) (`:help news` in Nvim)
## Install
### Windows
#### Zip
1. Download **nvim-win64.zip**
1. Download **nvim-win64.zip** (or **nvim-win-arm64.zip** for ARM)
2. Extract the zip
3. Run `nvim.exe` on your CLI of choice
3. Run `nvim.exe` in your terminal
#### MSI
1. Download **nvim-win64.msi**
1. Download **nvim-win64.msi** (or **nvim-win-arm64.msi** for ARM)
2. Run the MSI
3. Run `nvim.exe` on your CLI of choice
3. Run `nvim.exe` in your terminal
Note: On Windows "Server" you may need to [install vcruntime140.dll](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170).

View File

@@ -132,27 +132,44 @@ jobs:
windows:
needs: setup
runs-on: windows-2022
strategy:
matrix:
include:
- runner: windows-2022
arch: x86_64
archive_name: nvim-win64
- runner: windows-11-arm
arch: arm64
archive_name: nvim-win-arm64
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
with:
# Perform a full checkout #13471
fetch-depth: 0
- run: .github/scripts/env.ps1
env:
BUILD_ARCH: ${{ matrix.arch }}
- name: Install Wix
run: |
Invoke-WebRequest -Uri "https://github.com/wixtoolset/wix3/releases/download/wix3141rtm/wix314-binaries.zip" -OutFile "wix314-binaries.zip"
Expand-Archive -Path "wix314-binaries.zip" -DestinationPath "C:/wix"
echo "C:\wix" >> $env:GITHUB_PATH
- name: Build deps
run: |
cmake -S cmake.deps -B .deps -G Ninja -DCMAKE_BUILD_TYPE=${{ needs.setup.outputs.build_type }}
cmake --build .deps
- name: build package
- name: Build package
run: |
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=${{ needs.setup.outputs.build_type }}
cmake --build build --target package
- uses: actions/upload-artifact@v4
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: nvim-win64
name: nvim-win-${{ matrix.arch }}
path: |
build/nvim-win64.msi
build/nvim-win64.zip
build/${{ matrix.archive_name }}.zip
build/${{ matrix.archive_name }}.msi
retention-days: 1
publish:
@@ -196,10 +213,11 @@ jobs:
- name: Publish release
env:
NVIM_VERSION: ${{ needs.linux.outputs.version }}
NVIM_COMMIT: ${{ github.sha }}
DEBUG: api
run: |
envsubst < "$GITHUB_WORKSPACE/.github/workflows/notes.md" > "$RUNNER_TEMP/notes.md"
if [ "$TAG_NAME" != "nightly" ]; then
gh release create stable $PRERELEASE --notes-file "$RUNNER_TEMP/notes.md" --title "$SUBJECT" --target $GITHUB_SHA nvim-macos-x86_64/* nvim-macos-arm64/* nvim-linux-x86_64/* nvim-linux-arm64/* nvim-appimage-x86_64/* nvim-appimage-arm64/* nvim-win64/*
gh release create stable $PRERELEASE --notes-file "$RUNNER_TEMP/notes.md" --title "$SUBJECT" --target $GITHUB_SHA nvim-macos-x86_64/* nvim-macos-arm64/* nvim-linux-x86_64/* nvim-linux-arm64/* nvim-appimage-x86_64/* nvim-appimage-arm64/* nvim-win-x86_64/* nvim-win-arm64/*
fi
gh release create $TAG_NAME $PRERELEASE --notes-file "$RUNNER_TEMP/notes.md" --title "$SUBJECT" --target $GITHUB_SHA nvim-macos-x86_64/* nvim-macos-arm64/* nvim-linux-x86_64/* nvim-linux-arm64/* nvim-appimage-x86_64/* nvim-appimage-arm64/* nvim-win64/*
gh release create $TAG_NAME $PRERELEASE --notes-file "$RUNNER_TEMP/notes.md" --title "$SUBJECT" --target $GITHUB_SHA nvim-macos-x86_64/* nvim-macos-arm64/* nvim-linux-x86_64/* nvim-linux-arm64/* nvim-appimage-x86_64/* nvim-appimage-arm64/* nvim-win-x86_64/* nvim-win-arm64/*

View File

@@ -141,8 +141,8 @@ endif()
# version string, else they are combined with the result of `git describe`.
set(NVIM_VERSION_MAJOR 0)
set(NVIM_VERSION_MINOR 11)
set(NVIM_VERSION_PATCH 3)
set(NVIM_VERSION_PRERELEASE "") # for package maintainers
set(NVIM_VERSION_PATCH 5)
set(NVIM_VERSION_PRERELEASE "-dev") # for package maintainers
# API level
set(NVIM_API_LEVEL 13) # Bump this after any API/stdlib change.

View File

@@ -66,11 +66,13 @@ Several Neovim GUIs are available from scoop (extras): [scoop.sh/#/apps?q=neovim
You can then copy your spell files over (for English, located
[here](https://github.com/vim/vim/blob/master/runtime/spell/en.utf-8.spl) and
[here](https://github.com/vim/vim/blob/master/runtime/spell/en.utf-8.sug));
- For Python plugins you need the `pynvim` module. "Virtual envs" are recommended. After activating the virtual env do `pip install pynvim` (in *both*). Edit your `init.vim` so that it contains the path to the env's Python executable:
```vim
let g:python3_host_prog='C:/Users/foo/Envs/neovim3/Scripts/python.exe'
- For Python plugins you need the `pynvim` module. Installation via uv
(https://docs.astral.sh/uv/) is recommended; the `--upgrade` switch ensures
installation of the latest version:
```
- Run `:checkhealth` and read `:help provider-python`.
uv tool install --upgrade pynvim
```
- Run `:checkhealth` and read `:help provider-python` for more details.
- **init.vim ("vimrc"):** If you already have Vim installed you can copy `%userprofile%\_vimrc` to `%userprofile%\AppData\Local\nvim\init.vim` to use your Vim config with Neovim.

View File

@@ -73,7 +73,7 @@ if(HAS_OG_FLAG)
set(DEFAULT_MAKE_CFLAGS CFLAGS+=-Og ${DEFAULT_MAKE_CFLAGS})
endif()
set(DEPS_INCLUDE_FLAGS "-I${DEPS_INSTALL_DIR}/include -I${DEPS_INSTALL_DIR}/include/luajit-2.1")
set(DEPS_INCLUDE_FLAGS "-I\"${DEPS_INSTALL_DIR}/include\" -I\"${DEPS_INSTALL_DIR}/include/luajit-2.1\"")
# If the macOS deployment target is not set manually (via $MACOSX_DEPLOYMENT_TARGET),
# fall back to local system version. Needs to be done here and in top-level CMakeLists.txt.
@@ -96,10 +96,10 @@ else()
find_package(Lua 5.1 EXACT)
if(LUAJIT_FOUND)
set(LUA_ENGINE LuaJit)
string(APPEND DEPS_INCLUDE_FLAGS " -I${LUAJIT_INCLUDE_DIR}")
string(APPEND DEPS_INCLUDE_FLAGS " -I\"${LUAJIT_INCLUDE_DIR}\"")
elseif(LUA_FOUND)
set(LUA_ENGINE Lua)
string(APPEND DEPS_INCLUDE_FLAGS " -I${LUA_INCLUDE_DIR}")
string(APPEND DEPS_INCLUDE_FLAGS " -I\"${LUA_INCLUDE_DIR}\"")
else()
message(FATAL_ERROR "Could not find system lua or luajit")
endif()

View File

@@ -1,4 +1,4 @@
if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64")
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm64|ARM64|aarch64)$")
set(CMAKE_SYSTEM_PROCESSOR arm64)
endif()
@@ -27,9 +27,13 @@ set(CPACK_RESOURCE_FILE_README ${PROJECT_SOURCE_DIR}/README.md)
if(WIN32)
set(CPACK_PACKAGE_FILE_NAME "nvim-win64")
if(CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64")
set(CPACK_PACKAGE_FILE_NAME "nvim-win-arm64")
else()
set(CPACK_PACKAGE_FILE_NAME "nvim-win64")
endif()
set(CPACK_GENERATOR ZIP WIX)
# WIX
# CPACK_WIX_UPGRADE_GUID should be set, but should never change.
# CPACK_WIX_PRODUCT_GUID should not be set (leave as default to auto-generate).

View File

@@ -77,46 +77,6 @@ function! tutor#TutorFolds()
endif
endfunction
" Marks: {{{1
function! tutor#ApplyMarks()
hi! link tutorExpect Special
if exists('b:tutor_metadata') && has_key(b:tutor_metadata, 'expect')
let b:tutor_sign_id = 1
for expct in keys(b:tutor_metadata['expect'])
let lnum = eval(expct)
call matchaddpos('tutorExpect', [lnum])
call tutor#CheckLine(lnum)
endfor
endif
endfunction
function! tutor#ApplyMarksOnChanged()
if exists('b:tutor_metadata') && has_key(b:tutor_metadata, 'expect')
let lnum = line('.')
if index(keys(b:tutor_metadata['expect']), string(lnum)) > -1
call tutor#CheckLine(lnum)
endif
endif
endfunction
function! tutor#CheckLine(line)
if exists('b:tutor_metadata') && has_key(b:tutor_metadata, 'expect')
let bufn = bufnr('%')
let ctext = getline(a:line)
let signs = sign_getplaced(bufn, {'lnum': a:line})[0].signs
if !empty(signs)
call sign_unplace('', {'id': signs[0].id})
endif
if b:tutor_metadata['expect'][string(a:line)] == -1 || ctext ==# b:tutor_metadata['expect'][string(a:line)]
exe "sign place ".b:tutor_sign_id." line=".a:line." name=tutorok buffer=".bufn
else
exe "sign place ".b:tutor_sign_id." line=".a:line." name=tutorbad buffer=".bufn
endif
let b:tutor_sign_id+=1
endif
endfunction
" Tutor Cmd: {{{1
function! s:Locale()
@@ -243,9 +203,9 @@ function! tutor#EnableInteractive(enable)
setlocal buftype=nofile
setlocal concealcursor+=inv
setlocal conceallevel=2
call tutor#ApplyMarks()
lua require('nvim.tutor').apply_marks()
augroup tutor_interactive
autocmd! TextChanged,TextChangedI <buffer> call tutor#ApplyMarksOnChanged()
autocmd! TextChanged,TextChangedI <buffer> lua require('nvim.tutor').apply_marks_on_changed()
augroup END
else
setlocal buftype<

View File

@@ -3536,7 +3536,8 @@ nvim_open_win({buffer}, {enter}, {config}) *nvim_open_win()*
the duration of the call.
• fixed: If true when anchor is NW or SW, the float window
would be kept fixed even if the window would be truncated.
• hide: If true the floating window will be hidden.
• hide: If true the floating window will be hidden and the
cursor will be invisible when focused on it.
• vertical: Split vertically |:vertical|.
• split: Split direction: "left", "right", "above", "below".

View File

@@ -187,20 +187,19 @@ Run |:checkhealth| in Nvim for automatic diagnosis.
Other hints:
- The python `neovim` module was renamed to `pynvim` (long ago).
- If you're using pyenv or virtualenv for the `pynvim` module
https://pypi.org/project/pynvim/, you must set `g:python3_host_prog` to
the virtualenv's interpreter path.
- Read |provider-python|.
- Read |provider-python| to learn how to install `pynvim`.
- Be sure you have the latest version of the `pynvim` Python module: >bash
python -m pip install setuptools
python -m pip install --upgrade pynvim
python3 -m pip install --upgrade pynvim
uv tool install --upgrade pynvim
<
See |provider-python| for other installation options.
- If you're manually creating a Python virtual environment for the `pynvim` module
https://pypi.org/project/pynvim/, you must set `g:python3_host_prog` to
the virtualenv's interpreter path.
- Try with `nvim -u NORC` to make sure your config (|init.vim|) isn't causing a
problem. If you get `E117: Unknown function`, that means there's a runtime
issue: |faq-runtime|.
- The python `neovim` module was renamed to `pynvim` (long ago).
:CHECKHEALTH REPORTS E5009: INVALID $VIMRUNTIME ~

View File

@@ -373,11 +373,17 @@ To quote a block of ex-commands verbatim, place a greater than (>) character
at the end of the line before the block and a less than (<) character as the
first non-blank on a line following the block. Any line starting in column 1
also implicitly stops the block of ex-commands before it. E.g. >
function Example_Func()
echo "Example"
endfunction
function Example_Func()
echo "Example"
endfunction
<
To enable syntax highlighting for a block of code, place a language name
annotation (e.g. "vim") after a greater than (>) character. E.g. >vim
function Example_Func()
echo "Example"
endfunction
<
*help-notation*
The following are highlighted differently in a Vim help file:
- a special key name expressed either in <> notation as in <PageDown>, or
as a Ctrl character as in CTRL-X

View File

@@ -135,7 +135,7 @@ following (in increasing priority):
1. Configuration defined for the `'*'` name.
2. Configuration from the result of merging all tables returned by
`lsp/<name>.lua` files in 'runtimepath' for a server of name `name`.
`lsp/<config>.lua` files in 'runtimepath' for the config named `<config>`.
3. Configurations defined anywhere else.
Note: The merge semantics of configurations follow the behaviour of
@@ -256,6 +256,12 @@ FAQ *lsp-faq*
" (async = false is the default for format)
autocmd BufWritePre *.rs lua vim.lsp.buf.format({ async = false })
<
- Q: How to avoid my own lsp/ folder being overridden?
- A: Place your configs under "after/lsp/". Files in "after/lsp/" are loaded
after those in "nvim/lsp/", so your settings will take precedence over
the defaults provided by nvim-lspconfig. See also: |after-directory|
*lsp-vs-treesitter*
- Q: How do LSP, Treesitter and Ctags compare?
- A: LSP requires a client and language server. The language server uses

View File

@@ -356,6 +356,11 @@ PLUGINS
• Customize :checkhealth by handling a `FileType checkhealth` event.
|health-usage|
• Simplify Python provider setup to a single step: `uv tool install pynvim`
Nvim will detect the plugin's location without user configuration, even if
unrelated Python virtual environments are activated.
|provider-python|
STARTUP
• |-es| ("script mode") disables shada by default.

View File

@@ -36,21 +36,18 @@ itself).
For Python 3 plugins:
1. Make sure Python 3.9+ is available in your $PATH.
2. Install the module (try "python" if "python3" is missing): >bash
python3 -m pip install --user --upgrade pynvim
2. Install either uv (https://docs.astral.sh/uv/) or pipx
(https://pipx.pypa.io/stable/).
3. Install the module: >bash
uv tool install --upgrade pynvim
# or:
pipx install --upgrade pynvim
The pip `--upgrade` flag ensures that you get the latest version even if
The `--upgrade` flag ensures that you get the latest version even if
a previous version was already installed.
See also |python-virtualenv|.
Note: The old "neovim" module was renamed to "pynvim".
https://github.com/neovim/neovim/wiki/Following-HEAD#20181118
If you run into problems, uninstall _both_ then install "pynvim" again: >bash
python -m pip uninstall neovim pynvim
python -m pip install --user --upgrade pynvim
PYTHON PROVIDER CONFIGURATION ~
*g:python3_host_prog*
Command to start Python 3 (executable, not directory). Setting this makes
@@ -65,20 +62,18 @@ To disable Python 3 support: >vim
PYTHON VIRTUALENVS ~
*python-virtualenv*
If you plan to use per-project virtualenvs often, you should assign one
virtualenv for Nvim and hard-code the interpreter path via
|g:python3_host_prog| so that the "pynvim" package is not required
for each virtualenv.
Example using pyenv: >bash
pyenv install 3.4.4
pyenv virtualenv 3.4.4 py3nvim
pyenv activate py3nvim
python3 -m pip install pynvim
pyenv which python # Note the path
The last command reports the interpreter path, add it to your init.vim: >vim
let g:python3_host_prog = '/path/to/py3nvim/bin/python'
Using pynvim 0.6.0+ installed via uv or pipx, Nvim will automatically detect
pynvim even if other Python virtual environments are activated (technical
note: via the "pynvim-python" global python tool). For older pynvim (or older
Neovim), where detection involved finding the first Python interpreter and
checking if it could import pynvim, automatic detection would fail when
another virtual environment is active. Upgrading to the latest pynvim is the
recommended solution to this; but if that's not an option, then you can set
the variable |g:python3_host_prog| in `init.vim` to point to the full path to
the Python interpreter where `pynvim` is installed, e.g.: >vim
let g:python3_host_prog = '/path/to/pynvim-venv/bin/python'
<
See also: https://github.com/zchee/deoplete-jedi/wiki/Setting-up-Python-for-Neovim
==============================================================================

View File

@@ -25,9 +25,9 @@ Table of contents: |usr_toc.txt|
==============================================================================
*02.1* Running Vim for the First Time
To start Vim, enter this command: >
To start Nvim, enter this command: >
gvim file.txt
nvim file.txt
On Unix you can type this at any command prompt. If you are running Microsoft
Windows, open a Command Prompt and enter the command. In either case, Vim
@@ -50,20 +50,6 @@ screen, a message line indicates the file is named file.txt and shows that you
are creating a new file. The message information is temporary and other
information overwrites it.
THE VIM COMMAND
The gvim command causes the editor to create a new window for editing. If you
use this command: >
vim file.txt
the editing occurs inside your command window. In other words, if you are
running inside an xterm, the editor uses your xterm window. If you are using
the command prompt under Microsoft Windows, the editing occurs inside this
window. The text in the window will look the same for both versions, but with
gvim you have extra features, like a menu bar. More about that later.
==============================================================================
*02.2* Inserting text
@@ -580,7 +566,7 @@ Summary: *help-summary* >
:help quote:
13) Vim Script is available at >
:help eval.txt
:help vimeval.txt
< Certain aspects of the language are available at :h expr-X where "X" is a
single letter. E.g. >
:help expr-!
@@ -660,10 +646,13 @@ Summary: *help-summary* >
command switch of Vim use: >
:help -f
24) Optional features always start with "+". To find out about the
conceal feature use: >
:help +conceal
24) Lua language and Nvim's Lua standard library are available at >vim
:help lua.txt
< Guide to using Lua in Nvim is available at >vim
:help lua-guide.txt
< Lua 5.1 reference manual is available at >vim
:help luaref.txt
<
25) Documentation for included filetype specific functionality is usually
available in the form ft-<filetype>-<functionality>. So >
:help ft-c-syntax

View File

@@ -3,6 +3,7 @@
" Maintainer: Romain Lafourcade <romainlafourcade@gmail.com>
" Last Change: 2024 Apr 21
" 2024 May 24 by Riley Bruins <ribru17@gmail.com> ('commentstring')
" 2025 Aug 29 by Vim project, add try/catch around json_decode(), #18141
if exists("b:did_ftplugin")
finish
@@ -52,13 +53,19 @@ function! s:CollectPathsFromConfig() abort
endif
endif
let paths_from_config = config_json
try
let paths_from_config = config_json
\ ->readfile()
\ ->filter({ _, val -> val =~ '^\s*[\[\]{}"0-9]' })
\ ->join()
\ ->json_decode()
\ ->get('compilerOptions', {})
\ ->get('paths', {})
catch /^Vim\%((\a\+)\)\=:E491:/ " invalid json
let paths_from_config = {}
catch /^Vim\%((\a\+)\)\=:E474:/ " invalid json in Nvim
let paths_from_config = {}
endtry
if !empty(paths_from_config)
let b:astro_paths = paths_from_config

View File

@@ -1,7 +1,8 @@
" Vim filetype plugin file
" Language: gpg(1) configuration file
" Maintainer: This runtime file is looking for a new maintainer.
" Previous Maintainer: Nikolai Weibull <now@bitwi.se>
" Latest Revision: 2024-09-19 (simplify keywordprg #15696)
" Latest Revision: 2025-07-22 (use :hor term #17822)
if exists("b:did_ftplugin")
finish
@@ -17,7 +18,7 @@ setlocal comments=:# commentstring=#\ %s formatoptions-=t formatoptions+=croql
if has('unix') && executable('less') && exists(':terminal') == 2
command -buffer -nargs=1 GpgKeywordPrg
\ silent exe ':term ' . 'env LESS= MANPAGER="less --pattern=''' . escape('^\s+--' . <q-args> . '\b', '\') . ''' --hilite-search" man ' . 'gpg'
\ silent exe ':hor term ' . 'env LESS= MANPAGER="less --pattern=''' . escape('^\s+--' . <q-args> . '\b', '\') . ''' --hilite-search" man ' . 'gpg'
setlocal iskeyword+=-
setlocal keywordprg=:GpgKeywordPrg
let b:undo_ftplugin .= '| setlocal keywordprg< iskeyword< | sil! delc -buffer GpgKeywordPrg'

View File

@@ -2,7 +2,7 @@
" Language: modules.conf(5) configuration file
" Maintainer: This runtime file is looking for a new maintainer.
" Previous Maintainer: Nikolai Weibull <now@bitwi.se>
" Latest Revision: 2024-09-20 (remove erroneous endif)
" Latest Revision: 2025-07-22 (use :hor term #17822)
if exists("b:did_ftplugin")
finish
@@ -19,7 +19,7 @@ setlocal formatoptions-=t formatoptions+=croql
if has('unix') && executable('less') && exists(':terminal') == 2
command -buffer -nargs=1 ModconfKeywordPrg
\ silent exe ':term ' . 'env LESS= MANPAGER="less --pattern=''' . escape('^\s{,8}' . <q-args> . '\b', '\') . ''' --hilite-search" man ' . 'modprobe.d'
\ silent exe ':hor term ' . 'env LESS= MANPAGER="less --pattern=''' . escape('^\s{,8}' . <q-args> . '\b', '\') . ''' --hilite-search" man ' . 'modprobe.d'
setlocal iskeyword+=-
setlocal keywordprg=:ModconfKeywordPrg
let b:undo_ftplugin .= '| setlocal keywordprg< iskeyword< | sil! delc -buffer ModconfKeywordPrg'

View File

@@ -1,7 +1,8 @@
" Vim filetype plugin file
" Language: mutt RC File
" Maintainer: This runtime file is looking for a new maintainer.
" Previous Maintainer: Nikolai Weibull <now@bitwi.se>
" Latest Revision: 2024-09-19 (simplify keywordprg #15696)
" Latest Revision: 2025-07-22 (use :hor term #17822)
if exists("b:did_ftplugin")
finish
@@ -20,7 +21,7 @@ let &l:include = '^\s*source\>'
if has('unix') && executable('less') && exists(':terminal') == 2
command -buffer -nargs=1 MuttrcKeywordPrg
\ silent exe 'term ' . 'env LESS= MANPAGER="less --pattern=''' . escape('^\s+' . <q-args> . '\b', '\') . ''' --hilite-search" man ' . 'muttrc'
\ silent exe 'hor term ' . 'env LESS= MANPAGER="less --pattern=''' . escape('^\s+' . <q-args> . '\b', '\') . ''' --hilite-search" man ' . 'muttrc'
setlocal iskeyword+=-
setlocal keywordprg=:MuttrcKeywordPrg
let b:undo_ftplugin .= '| setlocal keywordprg< iskeyword< | sil! delc -buffer MuttrcKeywordPrg'

View File

@@ -5,6 +5,7 @@
" 2024 Jan 14 by Vim Project (browsefilter)
" 2024 May 23 by Riley Bruins <ribru17@gmail.com> ('commentstring')
" 2024 Sep 19 by Konfekt (simplify keywordprg #15696)
" 2025 Jul 22 by phanium (use :hor term #17822)
" Only do this when not done yet for this buffer
if exists("b:did_ftplugin") | finish | endif
@@ -51,7 +52,7 @@ endif
if exists('s:pwsh_cmd')
if exists(':terminal') == 2
command! -buffer -nargs=1 GetHelp silent exe 'term ' . s:pwsh_cmd . ' -NoLogo -NoProfile -NonInteractive -ExecutionPolicy RemoteSigned -Command Get-Help -Full "<args>"' . (executable('less') ? ' | less' : '')
command! -buffer -nargs=1 GetHelp silent exe 'hor term ' . s:pwsh_cmd . ' -NoLogo -NoProfile -NonInteractive -ExecutionPolicy RemoteSigned -Command Get-Help -Full "<args>"' . (executable('less') ? ' | less' : '')
else
command! -buffer -nargs=1 GetHelp echo system(s:pwsh_cmd . ' -NoLogo -NoProfile -NonInteractive -ExecutionPolicy RemoteSigned -Command Get-Help -Full <args>')
endif

View File

@@ -3,6 +3,7 @@
" Maintainer: Doug Kearns <dougkearns@gmail.com>
" Previous Maintainer: Nikolai Weibull <now@bitwi.se>
" Last Change: 2024 Sep 19 (simplify keywordprg #15696)
" 2024 Jul 22 by Vim project (use :hor term #17822)
if exists("b:did_ftplugin")
finish
@@ -36,7 +37,7 @@ endif
if has('unix') && executable('less') && exists(':terminal') == 2
command -buffer -nargs=1 ReadlineKeywordPrg
\ silent exe 'term ' . 'env LESS= MANPAGER="less --pattern=''' . escape('^\s+' . <q-args> . '\b', '\') . ''' --hilite-search" man ' . '3 readline'
\ silent exe 'hor term ' . 'env LESS= MANPAGER="less --pattern=''' . escape('^\s+' . <q-args> . '\b', '\') . ''' --hilite-search" man ' . '3 readline'
setlocal iskeyword+=-
setlocal keywordprg=:ReadlineKeywordPrg
let b:undo_ftplugin .= '| setlocal keywordprg< iskeyword< | sil! delc -buffer ReadlineKeywordPrg'

View File

@@ -7,6 +7,7 @@
" Last Change: 2024 Sep 19 by Vim Project (compiler shellcheck)
" 2024 Dec 29 by Vim Project (improve setting shellcheck compiler)
" 2025 Mar 09 by Vim Project (set b:match_skip)
" 2025 Jul 22 by phanium (use :hor term #17822)
if exists("b:did_ftplugin")
finish
@@ -53,7 +54,7 @@ let s:is_kornshell = get(b:, "is_kornshell", get(g:, "is_kornshell", 0))
if s:is_bash
if exists(':terminal') == 2
command! -buffer -nargs=1 ShKeywordPrg silent exe ':term bash -c "help "<args>" 2>/dev/null || man "<args>""'
command! -buffer -nargs=1 ShKeywordPrg silent exe ':hor term bash -c "help "<args>" 2>/dev/null || man "<args>""'
else
command! -buffer -nargs=1 ShKeywordPrg echo system('bash -c "help <args>" 2>/dev/null || MANPAGER= man "<args>"')
endif

View File

@@ -2,7 +2,7 @@
" Language: OpenSSH client configuration file
" Maintainer: This runtime file is looking for a new maintainer.
" Previous Maintainer: Nikolai Weibull <now@bitwi.se>
" Latest Revision: 2024-09-19 (simplify keywordprg #15696)
" Latest Revision: 2025-07-22 (use :hor term #17822)
if exists("b:did_ftplugin")
finish
@@ -17,7 +17,7 @@ let b:undo_ftplugin = 'setlocal com< cms< fo<'
if has('unix') && executable('less') && exists(':terminal') == 2
command -buffer -nargs=1 SshconfigKeywordPrg
\ silent exe 'term ' . 'env LESS= MANPAGER="less --pattern=''' . escape('^\s+' . <q-args> . '$', '\') . ''' --hilite-search" man ' . 'ssh_config'
\ silent exe 'hor term ' . 'env LESS= MANPAGER="less --pattern=''' . escape('^\s+' . <q-args> . '$', '\') . ''' --hilite-search" man ' . 'ssh_config'
setlocal iskeyword+=-
setlocal keywordprg=:SshconfigKeywordPrg
let b:undo_ftplugin .= '| setlocal keywordprg< iskeyword< | sil! delc -buffer SshconfigKeywordPrg'

View File

@@ -2,7 +2,7 @@
" Language: sudoers(5) configuration files
" Maintainer: This runtime file is looking for a new maintainer.
" Previous Maintainer: Nikolai Weibull <now@bitwi.se>
" Latest Revision: 2024-09-19 (simplify keywordprg #15696)
" Latest Revision: 2025-07-22 (use :hor term #17822)
if exists("b:did_ftplugin")
finish
@@ -18,7 +18,7 @@ setlocal comments=:# commentstring=#\ %s formatoptions-=t formatoptions+=croql
if has('unix') && executable('less') && exists(':terminal') == 2
command -buffer -nargs=1 SudoersKeywordPrg
\ silent exe ':term ' . 'env LESS= MANPAGER="less --pattern=''' . escape('\b' . <q-args> . '\b', '\') . ''' --hilite-search" man ' . 'sudoers'
\ silent exe ':hor term ' . 'env LESS= MANPAGER="less --pattern=''' . escape('\b' . <q-args> . '\b', '\') . ''' --hilite-search" man ' . 'sudoers'
setlocal iskeyword+=-
setlocal keywordprg=:SudoersKeywordPrg
let b:undo_ftplugin .= '| setlocal keywordprg< iskeyword< | sil! delc -buffer SudoersKeywordPrg'

View File

@@ -2,7 +2,7 @@
" Language: udev(8) rules file
" Maintainer: This runtime file is looking for a new maintainer.
" Previous Maintainer: Nikolai Weibull <now@bitwi.se>
" Latest Revision: 2024-09-19 (simplify keywordprg #15696)
" Latest Revision: 2025-07-22 (use :hor term #17822)
if exists("b:did_ftplugin")
finish
@@ -18,7 +18,7 @@ setlocal comments=:# commentstring=#\ %s formatoptions-=t formatoptions+=croql
if has('unix') && executable('less') && exists(':terminal') == 2
command -buffer -nargs=1 UdevrulesKeywordPrg
\ silent exe ':term ' . 'env LESS= MANPAGER="less --pattern=''' . escape('^\s{,8}' . <q-args> . '\b', '\') . ''' --hilite-search" man ' . 'udev'
\ silent exe ':hor term ' . 'env LESS= MANPAGER="less --pattern=''' . escape('^\s{,8}' . <q-args> . '\b', '\') . ''' --hilite-search" man ' . 'udev'
setlocal iskeyword+=-
setlocal keywordprg=:UdevrulesKeywordPrg
let b:undo_ftplugin .= '| setlocal keywordprg< iskeyword< | sil! delc -buffer UdevrulesKeywordPrg'

View File

@@ -5,6 +5,8 @@
" Latest Revision: 2024 Sep 19
" License: Vim (see :h license)
" Repository: https://github.com/chrisbra/vim-zsh
" Last Change:
" 2025 Jul 23 by Vim Project (use :hor term #17822)
if exists("b:did_ftplugin")
finish
@@ -20,7 +22,7 @@ let b:undo_ftplugin = "setl com< cms< fo< "
if executable('zsh') && &shell !~# '/\%(nologin\|false\)$'
if exists(':terminal') == 2
command! -buffer -nargs=1 ZshKeywordPrg silent exe ':term zsh -c "autoload -Uz run-help; run-help <args>"'
command! -buffer -nargs=1 ZshKeywordPrg silent exe ':hor term zsh -c "autoload -Uz run-help; run-help <args>"'
else
command! -buffer -nargs=1 ZshKeywordPrg echo system('MANPAGER= zsh -c "autoload -Uz run-help; run-help <args> 2>/dev/null"')
endif

View File

@@ -0,0 +1,86 @@
---@class nvim.TutorMetadata
---@field expect table<string, string|-1>
---@alias nvim.TutorExtmarks table<string, string>
---@type nvim.TutorExtmarks?
vim.b.tutor_extmarks = vim.b.tutor_extmarks
---@type nvim.TutorMetadata?
vim.b.tutor_metadata = vim.b.tutor_metadata
local sign_text_correct = ''
local sign_text_incorrect = ''
local tutor_mark_ns = vim.api.nvim_create_namespace('nvim.tutor.mark')
local tutor_hl_ns = vim.api.nvim_create_namespace('nvim.tutor.hl')
local M = {}
---@param line integer 1-based
local function check_line(line)
if vim.b.tutor_metadata and vim.b.tutor_metadata.expect and vim.b.tutor_extmarks then
local ctext = vim.fn.getline(line)
---@type vim.api.keyset.get_extmark_item[]
local extmarks = vim
.iter(vim.api.nvim_buf_get_extmarks(
0,
tutor_mark_ns,
{ line - 1, 0 },
{ line - 1, -1 }, -- the extmark can move to col > 0 if users insert text there
{ details = true }
))
:filter(function(extmark)
return not extmark[4].invalid
end)
:totable()
for _, extmark in ipairs(extmarks) do
local mark_id = extmark[1]
local expct = vim.b.tutor_extmarks[tostring(mark_id)]
local expect = vim.b.tutor_metadata.expect[expct]
local is_correct = expect == -1 or ctext == expect
vim.api.nvim_buf_set_extmark(0, tutor_mark_ns, line - 1, 0, {
id = mark_id,
sign_text = is_correct and sign_text_correct or sign_text_incorrect,
sign_hl_group = is_correct and 'tutorOK' or 'tutorX',
invalidate = true,
})
end
end
end
function M.apply_marks()
vim.cmd [[hi! link tutorExpect Special]]
if vim.b.tutor_metadata and vim.b.tutor_metadata.expect then
vim.b.tutor_extmarks = {}
for expct, _ in pairs(vim.b.tutor_metadata.expect) do
---@diagnostic disable-next-line: assign-type-mismatch
local lnum = tonumber(expct) ---@type integer
vim.api.nvim_buf_set_extmark(0, tutor_hl_ns, lnum - 1, 0, {
line_hl_group = 'tutorExpect',
invalidate = true,
})
local mark_id = vim.api.nvim_buf_set_extmark(0, tutor_mark_ns, lnum - 1, 0, {})
-- Cannot edit field of a Vimscript dictionary from Lua directly, see `:h lua-vim-variables`
---@type nvim.TutorExtmarks
local tutor_extmarks = vim.b.tutor_extmarks
tutor_extmarks[tostring(mark_id)] = expct
vim.b.tutor_extmarks = tutor_extmarks
check_line(lnum)
end
end
end
function M.apply_marks_on_changed()
if vim.b.tutor_metadata and vim.b.tutor_metadata.expect and vim.b.tutor_extmarks then
local lnum = vim.fn.line('.')
check_line(lnum)
end
end
return M

View File

@@ -200,7 +200,9 @@ function vim.show_pos(bufnr, row, col, filter)
capture,
string.format(
'priority: %d language: %s',
capture.metadata.priority or vim.hl.priorities.treesitter,
capture.metadata.priority
or (capture.metadata[capture.id] and capture.metadata[capture.id].priority)
or vim.hl.priorities.treesitter,
capture.lang
)
)

View File

@@ -1840,7 +1840,8 @@ function vim.api.nvim_open_term(buffer, opts) end
--- the call.
--- - fixed: If true when anchor is NW or SW, the float window
--- would be kept fixed even if the window would be truncated.
--- - hide: If true the floating window will be hidden.
--- - hide: If true the floating window will be hidden and the cursor will be invisible when
--- focused on it.
--- - vertical: Split vertically `:vertical`.
--- - split: Split direction: "left", "right", "above", "below".
--- @return integer # |window-ID|, or 0 on error

View File

@@ -318,9 +318,11 @@ local function check_tmux()
'$TERM differs from the tmux `default-terminal` setting. Colors might look wrong.',
{ '$TERM may have been set by some rc (.bashrc, .zshrc, ...).' }
)
elseif not vim.regex([[\v(tmux-256color|screen-256color)]]):match_str(vim.env.TERM) then
elseif
not vim.regex([[\v(tmux-256color|tmux-direct|screen-256color)]]):match_str(vim.env.TERM)
then
health.error(
'$TERM should be "screen-256color" or "tmux-256color" in tmux. Colors might look wrong.',
'$TERM should be "screen-256color", "tmux-256color", or "tmux-direct" in tmux. Colors might look wrong.',
{
'Set default-terminal in ~/.tmux.conf:\nset-option -g default-terminal "screen-256color"',
suggest_faq,

View File

@@ -957,6 +957,9 @@ end
---@private
function ArrayIter:last()
if self._head >= self._tail then
return nil
end
local inc = self._head < self._tail and 1 or -1
local v = self._table[self._tail - inc]
self._head = self._tail

View File

@@ -352,6 +352,7 @@ function M.signature_help(config)
config = config and vim.deepcopy(config) or {}
config.focus_id = method
local user_title = config.title
lsp.buf_request_all(0, method, client_positional_params(), function(results, ctx)
if api.nvim_get_current_buf() ~= ctx.bufnr then
@@ -386,17 +387,19 @@ function M.signature_help(config)
return
end
local sfx = total > 1
and string.format(' (%d/%d)%s', idx, total, can_cycle and ' (<C-s> to cycle)' or '')
or ''
local title = string.format('Signature Help: %s%s', client.name, sfx)
if config.border then
config.title = title
else
table.insert(lines, 1, '# ' .. title)
if hl then
hl[1] = hl[1] + 1
hl[3] = hl[3] + 1
-- Show title only if there are multiple clients or multiple signatures.
if total > 1 then
local sfx = total > 1
and string.format(' (%d/%d)%s', idx, total, can_cycle and ' (<C-s> to cycle)' or '')
or ''
config.title = user_title or string.format('Signature Help: %s%s', client.name, sfx)
-- If no border is set, render title inside the window.
if not (config.border or vim.o.winborder ~= '') then
table.insert(lines, 1, '# ' .. config.title)
if hl then
hl[1] = hl[1] + 1
hl[3] = hl[3] + 1
end
end
end

View File

@@ -198,7 +198,7 @@ local function check_enabled_configs()
local v_str --- @type string?
if k == 'name' then
v_str = nil
elseif k == 'filetypes' or k == 'root_markers' then
elseif k == 'filetypes' then
v_str = table.concat(v, ', ')
elseif type(v) == 'function' then
v_str = func_tostring(v)

View File

@@ -745,6 +745,10 @@ function M.convert_signature_help_to_markdown_lines(signature_help, ft, triggers
if type(doc) == 'string' then
signature.documentation = { kind = 'plaintext', value = doc }
end
-- Add delimiter if there is documentation to display
if signature.documentation.value ~= '' then
contents[#contents + 1] = '---'
end
M.convert_input_to_markdown_lines(signature.documentation, contents)
end
if signature.parameters and #signature.parameters > 0 then
@@ -861,7 +865,7 @@ function M.make_floating_popup_options(width, height, opts)
col = 1
end
local title = (opts.border and opts.title) and opts.title or nil
local title = ((opts.border or vim.o.winborder ~= '') and opts.title) and opts.title or nil
local title_pos --- @type 'left'|'center'|'right'?
if title then
@@ -1340,9 +1344,10 @@ end
---
---@param events table list of events
---@param winnr integer window id of preview window
---@param bufnrs table list of buffers where the preview window will remain visible
---@param floating_bufnr integer floating preview buffer
---@param bufnr integer buffer that opened the floating preview buffer
---@see autocmd-events
local function close_preview_autocmd(events, winnr, bufnrs)
local function close_preview_autocmd(events, winnr, floating_bufnr, bufnr)
local augroup = api.nvim_create_augroup('nvim.preview_window_' .. winnr, {
clear = true,
})
@@ -1351,13 +1356,13 @@ local function close_preview_autocmd(events, winnr, bufnrs)
-- the floating window buffer or the buffer that spawned it
api.nvim_create_autocmd('BufLeave', {
group = augroup,
buffer = bufnrs[1],
buffer = bufnr,
callback = function()
vim.schedule(function()
-- When jumping to the quickfix window from the preview window,
-- do not close the preview window.
if api.nvim_get_option_value('filetype', { buf = 0 }) ~= 'qf' then
close_preview_window(winnr, bufnrs)
close_preview_window(winnr, { floating_bufnr, bufnr })
end
end)
end,
@@ -1366,7 +1371,7 @@ local function close_preview_autocmd(events, winnr, bufnrs)
if #events > 0 then
api.nvim_create_autocmd(events, {
group = augroup,
buffer = bufnrs[2],
buffer = bufnr,
callback = function()
close_preview_window(winnr)
end,
@@ -1613,7 +1618,7 @@ function M.open_floating_preview(contents, syntax, opts)
'<cmd>bdelete<cr>',
{ silent = true, noremap = true, nowait = true }
)
close_preview_autocmd(opts.close_events, floating_winnr, { floating_bufnr, bufnr })
close_preview_autocmd(opts.close_events, floating_winnr, floating_bufnr, bufnr)
-- save focus_id
if opts.focus_id then

View File

@@ -724,10 +724,9 @@ local function python()
local message = 'Detected pip upgrade failure: Python executable can import "pynvim" but not "neovim": '
.. pynvim_exe
local advice = {
'Use that Python version to reinstall "pynvim" and optionally "neovim".',
'Use that Python version to uninstall any "pynvim" or "neovim", e.g.:',
pynvim_exe .. ' -m pip uninstall pynvim neovim',
pynvim_exe .. ' -m pip install pynvim',
pynvim_exe .. ' -m pip install neovim # only if needed by third-party software',
'Then see :help provider-python for "pynvim" installation steps.',
}
health.error(message, advice)
end
@@ -753,7 +752,7 @@ local function python()
if is_bad_response(current) then
health.error(
'pynvim is not installed.\nError: ' .. current,
'Run in shell: ' .. python_exe .. ' -m pip install pynvim'
'See :help provider-python for "pynvim" installation steps.'
)
end

View File

@@ -83,6 +83,10 @@ function M.detect_by_module(module)
return vim.fn.exepath(vim.fn.expand(python_exe, true)), nil
end
if vim.fn.executable('pynvim-python') == 1 then
return 'pynvim-python'
end
local errors = {}
for _, exe in ipairs(python_candidates) do
local error = check_for_module(exe, module)

View File

@@ -119,7 +119,7 @@ local Tabstop = {}
function Tabstop.new(index, bufnr, range, choices)
local extmark_id = vim.api.nvim_buf_set_extmark(bufnr, snippet_ns, range[1], range[2], {
right_gravity = true,
end_right_gravity = true,
end_right_gravity = false,
end_line = range[3],
end_col = range[4],
hl_group = hl_group,
@@ -170,7 +170,7 @@ function Tabstop:set_right_gravity(right_gravity)
local range = self:get_range()
self.extmark_id = vim.api.nvim_buf_set_extmark(self.bufnr, snippet_ns, range[1], range[2], {
right_gravity = right_gravity,
end_right_gravity = true,
end_right_gravity = not right_gravity,
end_line = range[3],
end_col = range[4],
hl_group = hl_group,
@@ -257,10 +257,21 @@ local M = { session = nil }
local function display_choices(tabstop)
assert(tabstop.choices, 'Tabstop has no choices')
local text = tabstop:get_text()
local found_text = false
local start_col = tabstop:get_range()[2] + 1
local matches = {} --- @type table[]
for _, choice in ipairs(tabstop.choices) do
matches[#matches + 1] = { word = choice }
if choice ~= text then
matches[#matches + 1] = { word = choice }
else
found_text = true
end
end
if found_text then
table.insert(matches, 1, text)
end
vim.defer_fn(function()
@@ -298,6 +309,7 @@ local function select_tabstop(tabstop)
vim.cmd.startinsert({ bang = range[4] >= #vim.api.nvim_get_current_line() })
end
if tabstop.choices then
vim.fn.cursor(range[3] + 1, range[4] + 1)
display_choices(tabstop)
end
else

View File

@@ -23,23 +23,24 @@ function M.check()
---@class ParserEntry
---@field name string
---@field path string
---@field index integer runtime path index (unique)
local sorted_parsers = {} ---@type ParserEntry[]
for _, parser in ipairs(parsers) do
for i, parser in ipairs(parsers) do
local parsername = vim.fn.fnamemodify(parser, ':t:r')
table.insert(sorted_parsers, { name = parsername, path = parser })
table.insert(sorted_parsers, { name = parsername, path = parser, index = i })
end
table.sort(sorted_parsers, function(a, b)
if a.name == b.name then
return a.path < b.path
return a.index < b.index -- if names are the same sort by rtpath index (unique)
else
return a.name < b.name
end
end)
for _, parser in ipairs(sorted_parsers) do
for i, parser in ipairs(sorted_parsers) do
local is_loadable, err_or_nil = pcall(ts.language.add, parser.name)
if not is_loadable then
@@ -51,10 +52,15 @@ function M.check()
err_or_nil or '?'
)
)
elseif i > 1 and sorted_parsers[i - 1].name == parser.name then
-- Sorted by runtime path order (load order), thus, if the previous parser has the same name,
-- the current parser will not be loaded and `ts.language.inspect(parser.name)` with have
-- incorrect information.
health.ok(string.format('Parser: %-20s (not loaded), path: %s', parser.name, parser.path))
else
local lang = ts.language.inspect(parser.name)
health.ok(
string.format('Parser: %-20s ABI: %d, path: %s', parser.name, lang.abi_version, parser.path)
string.format('Parser: %-25s ABI: %d, path: %s', parser.name, lang.abi_version, parser.path)
)
end
end

View File

@@ -52,11 +52,14 @@ function TSHighlighterQuery:query()
return self._query
end
---@alias MarkInfo { start_line: integer, start_col: integer, opts: vim.api.keyset.set_extmark }
---@class (private) vim.treesitter.highlighter.State
---@field tstree TSTree
---@field next_row integer
---@field iter vim.treesitter.highlighter.Iter?
---@field highlighter_query vim.treesitter.highlighter.Query
---@field prev_marks MarkInfo[]
---@nodoc
---@class vim.treesitter.highlighter
@@ -179,10 +182,13 @@ function TSHighlighter:destroy()
vim.b[self.bufnr].ts_highlight = nil
api.nvim_buf_clear_namespace(self.bufnr, ns, 0, -1)
if vim.g.syntax_on == 1 then
api.nvim_exec_autocmds(
'FileType',
{ group = 'syntaxset', buffer = self.bufnr, modeline = false }
)
-- FileType autocmds commonly assume curbuf is the target buffer, so nvim_buf_call.
api.nvim_buf_call(self.bufnr, function()
api.nvim_exec_autocmds(
'FileType',
{ group = 'syntaxset', buffer = self.bufnr, modeline = false }
)
end)
end
end
end
@@ -220,6 +226,7 @@ function TSHighlighter:prepare_highlight_states(win, srow, erow)
next_row = 0,
iter = nil,
highlighter_query = hl_query,
prev_marks = {},
})
end)
end
@@ -311,6 +318,35 @@ local function get_spell(capture_name)
return nil, 0
end
---Adds the mark to the buffer, clipped by the line.
---Queues the remainder if the mark continues after the line.
---@param m MarkInfo
---@param buf integer
---@param line integer
---@param next_marks MarkInfo[]
local function add_mark(m, buf, line, next_marks)
local cur_start_l = m.start_line
local cur_start_c = m.start_col
if cur_start_l < line then
cur_start_l = line
cur_start_c = 0
end
local cur_opts = m.opts
if cur_opts.end_line >= line + 1 then
cur_opts = vim.deepcopy(cur_opts, true)
cur_opts.end_line = line + 1
cur_opts.end_col = 0
table.insert(next_marks, m)
end
local empty = cur_opts.end_line < cur_start_l
or (cur_opts.end_line == cur_start_l and cur_opts.end_col <= cur_start_c)
if cur_start_l <= line and not empty then
api.nvim_buf_set_extmark(buf, ns, cur_start_l, cur_start_c, cur_opts)
end
end
---@param self vim.treesitter.highlighter
---@param win integer
---@param buf integer
@@ -328,6 +364,12 @@ local function on_line_impl(self, win, buf, line, on_spell, on_conceal)
return
end
local next_marks = {}
for _, mark in ipairs(state.prev_marks) do
add_mark(mark, buf, line, next_marks)
end
if state.iter == nil or state.next_row < line then
-- Mainly used to skip over folds
@@ -367,7 +409,7 @@ local function on_line_impl(self, win, buf, line, on_spell, on_conceal)
local url = get_url(match, buf, capture, metadata)
if hl and end_row >= line and not on_conceal and (not on_spell or spell ~= nil) then
api.nvim_buf_set_extmark(buf, ns, start_row, start_col, {
local opts = {
end_line = end_row,
end_col = end_col,
hl_group = hl,
@@ -376,7 +418,9 @@ local function on_line_impl(self, win, buf, line, on_spell, on_conceal)
conceal = conceal,
spell = spell,
url = url,
})
}
local mark = { start_line = start_row, start_col = start_col, opts = opts }
add_mark(mark, buf, line, next_marks)
end
if
@@ -395,6 +439,8 @@ local function on_line_impl(self, win, buf, line, on_spell, on_conceal)
state.next_row = start_row
end
end
state.prev_marks = next_marks
end)
end

View File

@@ -26,6 +26,7 @@
</screenshots>
<releases>
<release date="2025-08-31" version="0.11.4"/>
<release date="2025-07-12" version="0.11.3"/>
<release date="2025-05-30" version="0.11.2"/>
<release date="2025-04-26" version="0.11.1"/>

View File

@@ -1,7 +1,7 @@
" Vim syntax file
" Language: Vim help file
" Maintainer: The Vim Project <https://github.com/vim/vim>
" Last Change: 2024 Oct 16
" Last Change: 2024 Dec 15
" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Quit when a (custom) syntax file was already loaded
@@ -12,15 +12,36 @@ endif
let s:cpo_save = &cpo
set cpo&vim
if !exists('g:help_example_languages')
let g:help_example_languages = #{ vim: 'vim' }
endif
syn match helpHeadline "^[A-Z.][-A-Z0-9 .,()_']*?\=\ze\(\s\+\*\|$\)"
syn match helpSectionDelim "^===.*===$"
syn match helpSectionDelim "^---.*--$"
" Nvim: support language annotation in codeblocks
if has("conceal")
syn region helpExample matchgroup=helpIgnore start=" >[a-z0-9]*$" start="^>[a-z0-9]*$" end="^[^ \t]"me=e-1 end="^<" concealends
syn region helpExample matchgroup=helpIgnore
\ start="\%(^\| \)>[a-z0-9]*$" end="^[^ \t]"me=e-1 end="^<" concealends
else
syn region helpExample matchgroup=helpIgnore start=" >[a-z0-9]*$" start="^>[a-z0-9]*$" end="^[^ \t]"me=e-1 end="^<"
syn region helpExample matchgroup=helpIgnore
\ start="\%(^\| \)>[a-z0-9]*$" end="^[^ \t]"me=e-1 end="^<"
endif
for [s:lang, s:syntax] in g:help_example_languages->items()
unlet! b:current_syntax
" silent! to prevent E403
execute 'silent! syn include' $'@helpExampleHighlight_{s:lang}'
\ $'syntax/{s:syntax}.vim'
execute $'syn region helpExampleHighlight_{s:lang} matchgroup=helpIgnore'
\ $'start=/\%(^\| \)>{s:lang}$/'
\ 'end=/^[^ \t]/me=e-1 end=/^</'
\ (has("conceal") ? 'concealends' : '')
\ $'contains=@helpExampleHighlight_{s:lang} keepend'
endfor
unlet! s:lang s:syntax
syn match helpHyperTextJump "\\\@<!|[#-)!+-~]\+|" contains=helpBar
syn match helpHyperTextEntry "\*[#-)!+-~]\+\*\s"he=e-1 contains=helpStar
syn match helpHyperTextEntry "\*[#-)!+-~]\+\*$" contains=helpStar

3
runtime/syntax/tutor.lua Normal file
View File

@@ -0,0 +1,3 @@
vim.cmd [[
syntax match tutorExpect /^--->.*$/
]]

View File

@@ -304,7 +304,7 @@ it would be easier to simply type two d's to delete a line.
3. Now move to the fourth line.
4. Type `2dd`{normal} to delete two lines, then press `u`{normal} twice to undo all three lines.
4. Type `2dd`{normal} to delete two lines.
1) Roses are red,
2) Mud is fun,
@@ -368,7 +368,7 @@ Fiix the errors oon thhis line and reeplace them witth undo.
** Type `p`{normal} to put previously deleted text after the cursor. **
1. Move the cursor to the first line below.
1. Move the cursor to the first `--->` line below.
2. Type `dd`{normal} to delete the line and store it in a Neovim register.
@@ -378,10 +378,10 @@ Fiix the errors oon thhis line and reeplace them witth undo.
5. Repeat steps 2 through 4 to put all the lines in correct order.
d) Can you learn too?
b) Violets are blue,
c) Intelligence is learned,
a) Roses are red,
---> d) Can you learn too?
---> b) Violets are blue,
---> c) Intelligence is learned,
---> a) Roses are red,
NOTE: You can also put the text before the cursor with `P`{normal} (capital P).

View File

@@ -12,17 +12,13 @@
"273": -1,
"292": "This line of words is cleaned up.",
"309": "1) Roses are red,",
"310": "3) Violets are blue,",
"311": "6) Sugar is sweet",
"312": "7) And so are you.",
"313": "7) And so are you.",
"314": "7) And so are you.",
"310": "",
"311": "3) Violets are blue,",
"312": "",
"313": "",
"314": "6) Sugar is sweet",
"315": "7) And so are you.",
"335": "Fix the errors on this line and replace them with undo.",
"381": -1,
"382": -1,
"383": -1,
"384": -1,
"400": "When this line was typed in, someone pressed some wrong keys!",
"401": "When this line was typed in, someone pressed some wrong keys!",
"421": "This line has a few words that need changing using the change operator.",

View File

@@ -368,7 +368,7 @@ This is just a line with words you can move around in.
** 最後に削除された行をカーソルの後に貼り付ける(Put)には `p`{normal} をタイプします。 **
1. と示された以下の最初の行にカーソルを移動しましょう。
1. `--->` と示された以下の最初の行にカーソルを移動しましょう。
2. `dd`{normal} とタイプして行を削除し、Neovim のレジスタに格納しましょう。
@@ -378,10 +378,10 @@ This is just a line with words you can move around in.
5. 順番が正しくなる様にステップ 2 から 4 を繰り返しましょう。
d) 貴方も学ぶことができるか?
b) 菫は青く
c) 知恵とは学ぶもの
a) 薔薇は赤く
---> d) 貴方も学ぶことができるか?
---> b) 菫は青く
---> c) 知恵とは学ぶもの
---> a) 薔薇は赤く
NOTE: `P`{normal} (大文字 P)とタイプすることで、カーソルの前に貼り付ける事もできます。

View File

@@ -11,18 +11,14 @@
"233": "誰かがこの行の最後を2度タイプしました。",
"272": -1,
"291": "この行の単語は綺麗になった。",
"308": -1,
"309": -1,
"310": -1,
"311": -1,
"312": -1,
"313": -1,
"314": -1,
"308": "1) 薔薇は赤く",
"309": "",
"310": "3) 菫は青く",
"311": "",
"312": "",
"313": "6) 砂糖は甘く",
"314": "7) そして貴方も",
"335": "この行の間違いを修正し、後でそれらの修正を取り消します。",
"381": -1,
"382": -1,
"383": -1,
"384": -1,
"400": "この行を入力した時に、その人は幾つか間違ったキーを押しました!",
"401": "この行を入力した時に、その人は幾つか間違ったキーを押しました!",
"421": "This line has a few words that need changing using the change operator.",

View File

@@ -0,0 +1,967 @@
# 欢迎来到 Neovim 教程
# 第 1 章
Neovim 是一个功能非常强大的编辑器,它的命令多到无法在这篇教程里一一讲解。本教程
旨在介绍足够多的基本命令,让你能轻松地将 Neovim 作为通用编辑器来使用。
请务必记住,本教程是为“在实践中学习”而设计的。这意味着你需要亲手完成这些练习才能
真正掌握它们。如果你只看不练,很快就会忘记最重要的内容!
现在请确保你的大写锁定键Caps-Lock是关闭状态然后多次按下 `j`{normal} 键,
直到光标移动到第 0 课完全充满屏幕为止。
# 第 0 课
NOTE: 课程中的命令会修改本文,但这些更改不会被保存。不用担心会搞乱什么;只要记住
按 [<Esc>](<Esc>) 键,再按 [u](u) 键,就可以撤销最近的更改。
本教程是交互式的,有几件事你需要知道。
- 在 [这样的](holy-grail ) 链接上按 [<Enter>](<Enter>) 键可以打开链接的帮助文档。
- 或者,你也可以在任意单词上按 [K](K) 键来查找它的文档!
- 你可以用 `:q`{vim} `<Enter>`{normal} 来关闭这个帮助窗口。
当左边出现 ✗ 标志时,你需要去修改文本。当你正确地完成修改后,左边的 ✗ 标志就会变
成 ✓。我想你已经能体会到 Neovim 的强大之处了。
其他时候,你会被提示运行一个命令(稍后会对此进行解释):
`:help`{vim} `<Enter>`{normal}
或者按下一系列按键:
~~~ normal
<Esc>0f<Space>d3wP$P
~~~
尖括号(<>)里的文本(如 `<Enter>`{normal})代表你需要按下的键,而不是要输入的文本。
现在,移动到下一课(使用 `j`{normal} 键向下滚动)。
# 第 1.1 课:移动光标
** 按 `h`、`j`、`k`、`l` 键来移动光标,如下所示。 **
k 提示:`h`{normal} 键在左边,向左移动。
← h l → `l`{normal} 键在右边,向右移动。
j `j`{normal} 键看起来像一个向下的箭头。
1. 在屏幕上四处移动光标,直到你习惯这种操作。
2. 按住向下键(`j`{normal})直到光标开始连续向下移动。
现在你知道如何移动到下一课了。
3. 使用下移键,移动到第 1.2 课。
NOTE: 如果你不确定自己输入了什么,随时可以按 <Esc> 键回到普通模式Normal mode
然后重新输入你想要的命令。
NOTE: 键盘上的方向键通常也能用。但一旦你习惯了,使用 hjkl 可以让你移动得更快。
# 第 1.2 课:退出 Neovim
!! NOTE: 在执行以下任何步骤之前,请先阅读完本课的全部内容 !!
1. 按下 <Esc> 键(确保你处于普通模式)。
2. 输入:
`:q!`{vim} `<Enter>`{normal}
这个命令会退出编辑器,并“丢弃”你所做的所有更改。
3. 重新打开 Neovim并通过执行带你进入本教程的命令回到这里。这个命令也许是
`:Tutor`{vim} `<Enter>`{normal}
4. 如果你已经记住了这些步骤并充满信心,请执行第 1 到 3 步来退出并重新进入编辑器。
NOTE: [:q!](:q) `<Enter>`{normal} 会丢弃你做的任何更改。在接下来的几课中,你将学习如何
将更改保存到文件中。
5. 向下移动光标到第 1.3 课。
# 第 1.3 课文本编辑——删除Deletion
** 按 `x`{normal} 键可以删除光标下的单个字符。 **
1. 将光标移动到下面标有 ✗ 的那一行。
2. 为了修正错误,请将光标移动到需要被删除的字符上。
3. 按下 [x 键](x) 来删除那个多余的字符。
4. 重复第 2 到 4 步,直到句子正确为止。
The ccow jumpedd ovverr thhe mooon.
The cow jumped over the moon.
5. 现在句子已经正确了,请继续学习第 1.4 课。
NOTE: 在学习本教程时,不必试图记住所有内容,你的 Neovim 词汇量会随着使用而增长。
可以考虑定期回到本教程进行复习。
# 第 1.4 课文本编辑——插入Insertion
** 按 `i`{normal} 键可以插入文本。 **
1. 将光标移动到下面标有 ✗ 的第一行。
2. 为了让第一行和第二行变得一样,请将光标移动到需要插入文本位置“之后”的那个字符上。
3. 按下 `i`{normal} 键,然后输入需要补充的内容。
4. 每个错误修正后,按 `<Esc>`{normal} 键回到普通模式。
重复第 2 到 4 步来修正整个句子。
There is text misng this .
There is some text missing from this line.
5. 当你熟练掌握插入文本后,请继续学习第 1.5 课。
# 第 1.5 课文本编辑——追加Appending
** 按 `A`{normal} 键可以在行末追加文本。 **
1. 将光标移动到下面标有 ✗ 的第一行。
光标在该行的哪个字符上并不重要。
2. 按下 [A](A) 键,然后输入需要补充的内容。
3. 文本追加完成后,按 `<Esc>`{normal} 键回到普通模式。
4. 将光标移动到标有 ✗ 的第二行,并重复第 2 和第 3 步来修正这个句子。
There is some text missing from th
There is some text missing from this line.
There is also some text miss
There is also some text missing here.
5. 当你熟练掌握追加文本后,请继续学习第 1.6 课。
# 第 1.6 课:编辑文件
** 使用 `:wq`{vim} 来写入(保存)文件并退出。 **
!! NOTE: 在执行以下任何步骤之前,请先阅读完本课的全部内容 !!
1. 像第 1.2 课那样退出本教程:`:q!`{vim}
或者,如果你能打开另一个终端,就在那个终端里执行以下操作。
2. 在 shell 提示符下输入这个命令:
~~~ sh
$ nvim tutor
~~~
'nvim' 是启动 Neovim 编辑器的命令,'tutor' 是你希望编辑的文件名。
请使用一个可以被修改的文件。
3. 像在前面课程中学到的那样,插入和删除一些文本。
4. 保存更改并退出 Neovim
~~~ cmd
:wq
~~~
注意,你需要按 `<Enter>` 键来执行该命令。
5. 如果你在第 1 步退出了本教程,请重启教程并移动到下面的总结部分。
6. 在阅读并理解了以上步骤后,亲手操作一遍。
# 第 1 课总结
1. 移动光标可以使用方向键,也可以使用 hjkl 键。
h (左) j (下) k (上) l (右)
2. 从 shell 提示符启动 Neovim输入
~~~ sh
$ nvim 文件名
~~~
3. 退出 Neovim输入`<Esc>`{normal} `:q!`{vim} `<Enter>`{normal} 放弃所有更改。
或者输入:`<Esc>`{normal} `:wq`{vim} `<Enter>`{normal} 保存所有更改。
4. 删除光标下的字符,输入:`x`{normal}
5. 插入或追加文本,输入:
`i`{normal} 插入文本 `<Esc>`{normal} 在光标前插入。
`A`{normal} 追加文本 `<Esc>`{normal} 在当前行末尾追加。
NOTE: 按 `<Esc>`{normal} 键会让你进入普通模式,或者取消一个不想继续输入的、未完成的命令。
现在,请继续学习第 2 课。
# 第 2.1 课删除类命令Deletion
** 输入 `dw`{normal} 来删除一个单词word。 **
1. 按 `<Esc>`{normal} 键确保你处于普通模式。
2. 将光标移动到下面标有 ✗ 的那一行。
3. 将光标移动到需要被删除的单词的开头。
4. 输入 [d](d)[w](w) 让这个单词消失。
There are a some words fun that don't belong paper in this sentence.
There are some words that don't belong in this sentence.
5. 重复第 3 和第 4 步,直到句子正确,然后继续学习第 2.2 课。
# 第 2.2 课:更多删除类命令
** 输入 `d$`{normal} 来删除从光标到行尾的内容。 **
1. 按 `<Esc>`{normal} 键确保你处于普通模式。
2. 将光标移动到下面标有 ✗ 的那一行。
3. 将光标移动到正确句子的末尾(在第一个 . 之后)。
4. 输入 `d$`{normal} 来删除到行尾的所有内容。
Somebody typed the end of this line twice. end of this line twice.
5. 继续学习第 2.3 课,来理解这背后的原理。
# 第 2.3 课关于操作符operator和移动motion
许多修改文本的命令都由一个 [操作符](operator) 和一个 [移动](navigation) 组成。
使用 [d](d) 删除操作符的命令格式如下:
d 移动
其中:
d - 是删除操作符。
移动 - 是操作符将要作用的范围(如下所列)。
一些常用的移动:
[w](w) - 到下一个单词的开头,但不包括其第一个字符。
[e](e) - 到当前单词的末尾,并包括最后一个字符。
[$]($) - 到当前行的末尾,并包括最后一个字符。
因此,输入 `de`{normal} 将会删除从光标到当前单词末尾的内容。
NOTE: 在普通模式下,不带操作符、只按下移动键,将会像预期的那样移动光标。
# 第 2.4 课:为移动增加计数
** 在移动命令前输入一个数字,可以将其重复执行那么多次。 **
1. 将光标移动到下面标有 ✓ 的那行的开头。
2. 输入 `2w`{normal},光标会向前移动两个单词。
3. 输入 `3e`{normal},光标会移动到前方第三个单词的末尾。
4. 输入 `0`{normal}[零](0))可以移动到行首。
5. 用不同的数字重复第 2 和第 3 步。
This is just a line with words you can move around in.
6. 继续学习第 2.5 课。
# 第 2.5 课:使用计数删除更多内容
** 在操作符和移动之间加上一个数字,可以将其重复执行那么多次。 **
在前面提到的“删除操作符 + 移动”组合中,你可以在移动前插入一个计数来删除更多内容:
d 数字 移动
1. 将光标移动到下面标有 ✗ 的那一行中第一个全大写单词上。
2. 输入 `d2w`{normal} 来删除两个全大写单词。
3. 重复第 1 和第 2 步,但使用不同的计数,用一个命令删除剩余连续的全大写单词。
This ABC DE line FGHI JK LMN OP of words is Q RS TUV cleaned up.
# 第 2.6 课:行操作
** 输入 `dd`{normal} 来删除一整行。 **
由于删除整行的操作非常频繁Vi 的设计者们决定,输入两个 d 来删除一行会更方便。
1. 将光标移动到下面短语的第 2 行。
2. 输入 [dd](dd) 来删除该行。
3. 现在移动到第 4 行。
4. 输入 `2dd`{normal} 来删除两行。
1) Roses are red,
2) Mud is fun,
3) Violets are blue,
4) I have a car,
5) Clocks tell time,
6) Sugar is sweet
7) And so are you.
# 第 2.7 课撤销命令Undo
** 按 `u`{normal} 撤销上一个命令,按 `U`{normal} 恢复一整行。 **
1. 将光标移动到下面标有 ✗ 的那一行,并把它放在第一个错误上。
2. 输入 `x`{normal} 来删除第一个多余的字符。
3. 现在输入 `u`{normal} 来撤销上一次执行的命令。
4. 这次,使用 `x`{normal} 命令修正该行所有的错误。
5. 现在输入大写的 `U`{normal},将该行恢复到它最初始的状态。
6. 现在多次输入 `u`{normal},来撤销 `U`{normal} 以及之前的命令。
7. 现在多次输入 `<C-r>`{normal}Ctrl + R来重做redo那些被撤销的命令。
Fiix the errors oon thhis line and reeplace them witth undo.
Fix the errors on this line and replace them with undo.
8. 这些都是非常有用的命令。现在请继续学习第 2 课的总结。
# 第 2 课总结
1. 删除从光标到下一个单词开头的内容,输入: `dw`{normal}
2. 删除从光标到行尾的内容,输入: `d$`{normal}
3. 删除一整行,输入: `dd`{normal}
4. 重复一个移动,在它前面加上数字: `2w`{normal}
5. 修改类命令的格式是:
操作符 [数字] 移动
其中:
操作符 - 是要做什么,比如 [d](d) 代表删除。
[数字] - 是一个可选的计数,用来重复移动。
移动 - 定义了操作符要作用的文本范围,例如:
[w](w)(单词),
[$]($)(到行尾),等等。
6. 移动到行首,使用零:[0](0)
7. 撤销之前的操作,输入: `u`{normal}(小写 u
撤销对一整行的所有更改,输入: `U`{normal}(大写 U
重做被撤销的操作,输入: `<C-r>`{normal}
# 第 3.1 课粘贴命令Put
** 输入 `p`{normal} 可以将之前删除的文本粘贴到光标之后。 **
1. 将光标移动到下面第一个标有 `--->` 的行。
2. 输入 `dd`{normal} 来删除该行,并将其存入 Neovim 的一个寄存器中。
3. 将光标移动到 c) 行,也就是被删除那一行的“上一行”。
4. 输入 `p`{normal},将之前删除的行粘贴到光标下方。
5. 重复第 2 到 4 步将所有行按正确的顺序abcd排列。
---> d) Can you learn too?
---> b) Violets are blue,
---> c) Intelligence is learned,
---> a) Roses are red,
NOTE: 你也可以用 `P`{normal}(大写 P将文本粘贴到光标之前。
# 第 3.2 课替换命令Replace
** 输入 `rx`{normal} 可以将光标下的字符替换为 x。 **
1. 将光标移动到下面标有 ✗ 的第一行。
2. 将光标移动到第一个错误字符上。
3. 输入 `r`{normal},然后输入正确的字符。
4. 重复第 2 和第 3 步,直到第一行和第二行完全一样。
Whan this lime was tuoed in, someone presswd some wrojg keys!
When this line was typed in, someone pressed some wrong keys!
5. 现在请继续学习第 3.3 课。
NOTE: 请记住,你应该通过实践来学习,而不是死记硬背。
# 第 3.3 课更改操作符Change
** 要更改到单词末尾,输入 `ce`{normal}。 **
1. 将光标移动到下面标有 ✗ 的第一行。
2. 将光标放在 "lubw" 的 "u" 上。
3. 输入 `ce`{normal},然后输入正确的单词(在这种情况是输入 "ine")。
4. 按下 `<Esc>`{normal},然后移动到下一个需要修改的字符。
5. 重复第 3 和第 4 步,直到第一句和第二句完全一样。
This lubw has a few wptfd that mrrf changing usf the change operator.
This line has a few words that need changing using the change operator.
请注意,[c](c)e 会删除单词并让你进入插入模式Insert mode
# 第 3.4 课:使用 `c`{normal} 进行更多更改
** 更改操作符可以和删除操作符使用相同的移动。 **
1. 更改操作符的工作方式和删除操作符一样。格式是:
c [数字] 移动
2. 移动也是一样的,比如 `w`{normal}(单词)和 `$`{normal}(到行尾)。
3. 移动到下面标有 ✗ 的第一行。
4. 将光标移动到第一个错误处。
5. 输入 `c$`{normal},然后输入该行余下正确的内容(参照第二行),最后按 `<Esc>`{normal}。
The end of this line needs some help to make it like the second.
The end of this line needs to be corrected using the c$ command.
NOTE: 在输入时你可以使用退格键Backspace来修正错误。
# 第 3 课总结
1. 要粘贴刚刚删除的文本,输入 [p](p)。这会把删除的文本放在光标“之后”
(如果删除的是一整行,它会被粘贴到光标所在行的下一行)。
2. 要替换光标下的单个字符,输入 [r](r) 然后输入你想要的那个字符。
3. [更改操作符](c) 允许你更改从光标开始到一个移动命令结束位置的文本。
输入 `ce`{normal} 来更改到单词末尾,`c$`{normal} 来更改到行尾,等等。
4. 更改命令的格式是:
c [数字] 移动
现在请继续学习下一课。
# 第 4.1 课:光标位置和文件状态
** 输入 `<C-g>`{normal} 来显示你在文件中的位置和文件状态。
输入 `{count}G`{normal} 来移动到文件中的第 {count} 行。 **
NOTE: 在执行任何步骤之前,请先阅读完本课的全部内容!!
1. 按住 `<Ctrl>`{normal} 键并按下 `g`{normal}。我们称这个操作为 `<C-g>`{normal}。
屏幕底部会出现一条消息,包含文件名和你在文件中的位置。
请记住行号,第 3 步会用到。
NOTE: 你可能会在屏幕右下角看到光标位置。这是因为设置了 ['ruler']('ruler') 选项。
2. 按 [G](G) 移动到文件的末尾。
输入 [gg](gg) 移动到文件的开头。
3. 输入你之前记住的行号,然后按 `G`{normal}。这会让你回到你第一次按 `<C-g>`{normal} 时所在的行。
4. 如果你觉得没问题,就执行第 1 到 3 步。
# 第 4.2 课:搜索命令
** 输入 `/`{normal} 后面跟一个短语,来搜索这个短语。 **
1. 在普通模式下,输入 `/`{normal} 字符。注意它和光标会出现在屏幕底部,
就像 `:`{normal} 命令一样。
2. 现在输入 errroor `<Enter>`{normal}。这是你想要搜索的词。
3. 要再次搜索同一个短语,只需输入 [n](n)。
要反向搜索同一个短语,输入 [N](N)。
4. 要反向搜索一个短语,使用 [?](?) 而不是 `/`{normal}。
5. 要回到你之前的位置,按 `<C-o>`{normal}。(按住 `<Ctrl>`{normal} 键的同时按字母 `o`{normal})。
重复按可以回到更早的位置。`<C-i>`{normal} 则会前进。
"errroor" is not the way to spell error; errroor is an error.
NOTE: 当搜索到达文件末尾时,它会从头开始继续搜索,除非 ['wrapscan']('wrapscan') 选项被关闭了。
# 第 4.3 课:括号匹配搜索
** 输入 `%`{normal} 来查找匹配的 ), ], }。 **
1. 将光标放在下面标有 ✓ 的那一行中的任意一个 (, [, { 上。
2. 现在输入 [%](%) 字符。
3. 光标会移动到与之匹配的括号上。
4. 再次输入 `%`{normal},光标会移回另一半匹配的括号。
5. 将光标移动到另一个 (, ), [, ], {, } 上,看看 `%`{normal} 的效果。
This ( is a test line with ('s, ['s, ] and {'s } in it. ))
NOTE: 这在调试程序中不匹配的括号时非常有用!
# 第 4.4 课替换命令Substitute
** 输入 `:s/old/new/g` 来用 "new" 替换 "old"。 **
1. 将光标移动到下面标有 ✗ 的那一行。
2. 输入
~~~ cmd
:s/thee/the/
~~~
NOTE: [:s](:s) 命令只改变了行中第一个匹配的 "thee"。
3. 现在输入
~~~ cmd
:s/thee/the/g
~~~
加上 g [标志](:s_flags) 意味着在行内进行全局替换,也就是改变该行中所有出现的 "thee"。
Usually thee best time to see thee flowers is in thee spring.
4. 要在两行之间替换一个字符串的所有出现,输入
~~~ cmd
:#,#s/old/new/g
~~~
其中 # 是要进行替换的行号范围(例如,`1,3` 表示从第 1 行到第 3 行,包含这两行)。
输入
~~~ cmd
:%s/old/new/g
~~~
可以在整个文件中进行替换。
输入
~~~ cmd
:%s/old/new/gc
~~~
可以在整个文件中查找所有出现,并对每一次替换进行确认。
NOTE: 你也可以先用可视模式Visual mode选中你想替换的行。这在后面的课程中会详细解释。
# 第 4 课总结
1. `<C-g>`{normal} 显示你的位置和文件状态。
`G`{normal} 移动到文件末尾。
数字 `G`{normal} 移动到指定的行号。
`gg`{normal} 移动到第一行。
2. 输入 `/`{normal} 后跟一个短语,会“向前”搜索该短语。
输入 `?`{normal} 后跟一个短语,会“向后”搜索该短语。
搜索后,输入 `n`{normal} 查找下一个(同方向),或 `N`{normal} 查找下一个(反方向)。
`<C-o>`{normal} 带你回到旧的光标位置,`<C-i>`{normal} 则去往新的位置。
3. 当光标在 (, ), [, ], {, } 上时,输入 `%`{normal} 会跳转到其匹配的另一半。
4. 将行内第一个 old 替换为 new输入
~~~ cmd
:s/old/new
~~~
将行内所有 old 替换为 new输入
~~~ cmd
:s/old/new/g
~~~
在两行 # 之间进行替换,输入
~~~ cmd
:#,#s/old/new/g
~~~
在整个文件中进行替换,输入
~~~ cmd
:%s/old/new/g
~~~
每次替换前进行确认,添加 'c' 标志
~~~ cmd
:%s/old/new/gc
~~~
# 第 5.1 课:如何执行外部命令
** 输入 `:!`{vim} 后面跟一个外部命令,来执行该命令。 **
1. 输入你熟悉的命令 `:`{normal},让光标定位到屏幕底部。这允许你输入一个
命令行命令。
2. 现在输入 [!](!cmd)(感叹号)字符。这允许你执行任何外部的 shell 命令。
3. 举个例子,在 "!" 后面输入 "ls",然后按 `<Enter>`{normal}。
这会显示你的目录列表,就像你在 shell 提示符下一样。
NOTE: 你可以用这种方式执行任何外部命令,并且可以带参数。
NOTE: 所有 `:`{vim} 命令都在你按下 `<Enter>`{normal} 后执行。
# 第 5.2 课:更多关于写入文件的知识
** 要保存对文本的更改,输入 `:w`{vim} 文件名。 **
1. 输入 `:!{unix:(ls),win:(dir)}`{vim} 来获取你的目录列表。
你已经知道在这之后必须按 `<Enter>`{normal}。
2. 选择一个还不存在的文件名,比如 TEST。
3. 现在输入:
~~~ cmd
:w TEST
~~~
(这里的 TEST 是你选择的文件名。)
4. 这会将当前文件以 TEST 的名字保存。
要验证这一点,再次输入 `:!{unix:(ls),win:(dir)}`{vim} 来查看你的目录。
NOTE: 如果你退出 Neovim然后用 `nvim TEST` 再次启动它,这个文件将会是你保存时教程
内容的一个精确副本。
5. 现在通过输入以下命令来删除该文件:
~~~ cmd
:!{unix:(rm),win:(del)} TEST
~~~
# 第 5.3 课:选择要写入的文本
** 要保存文件的一部分,输入 `v`{normal} 移动 `:w 文件名`{vim}。 **
1. 将光标移动到这一行。
2. 按下 [v](v) 并将光标移动到下面的第五项。注意文本被高亮了。
3. 按下 `:`{normal} 字符。在屏幕底部会出现:
`:'<,'>`{vim}
4. 输入
`w TEST`{vim}
这里的 TEST 是一个还不存在的文件名。在按 `<Enter>`{normal} 之前,
确认你看到的是:
`:'<,'>w TEST`{vim}
5. Neovim 会将被选中的行写入文件 TEST。使用 `:!{unix:(ls),win:(dir)}`{vim} 来查看它。
先不要删除它!我们将在下一课用到它。
NOTE: 按下 [v](v) 会启动 [可视选择Visual selection](visual-mode)。你可以移动光标来扩大或缩小选择范围。
然后你可以使用一个操作符来对选中的文本
做些什么。例如,`d`{normal} 会删除选中的文本。
# 第 5.4 课:读取和合并文件
** 要读取一个文件的内容,输入 `:r 文件名`{vim}。 **
1. 将光标放在这一行的正上方。
NOTE: 执行第 2 步后,你会看到第 5.3 课的文本。然后向下移动
才能再次看到本课内容。完成后按 `u`{normal} 撤销。
2. 现在使用以下命令读取你的 TEST 文件:
`:r TEST`{vim}
这里的 TEST 是你之前使用的文件名。
你读取的文件内容会被放在光标所在行的下方。
3. 要验证文件是否被读取,向上移动光标,你会注意到现在有两份第 5.3 课的内容,
一份是原始的,一份是读取进来的。
NOTE: 你也可以读取一个外部命令的输出。例如:
`:r !{unix:(ls),win:(dir)}`{vim}
会读取 `ls` 命令的输出,并将其放在光标下方。
# 第 5 课总结
1. [:!command](:!cmd) 执行一个外部命令。
一些有用的例子:
`:!{unix:(ls ),win:(dir)}`{vim} - 显示目录列表
`:!{unix:(rm ),win:(del)} 文件`{vim} - 删除文件
2. [:w](:w) 文件名 将当前 Neovim 文件以“文件名”这个名字写入磁盘。
3. [v](v) 移动 :w 文件名 将可视模式选中的行保存到文件“文件名”中。
4. [:r](:r) 文件名 读取磁盘文件“文件名”的内容,并将其放在光标位置的下方。
5. {unix:([:r !ls](:r!) ),win:([:r !dir](:r!))} 读取 {unix:(ls),win:(dir)} 命令的输出,并将其放在光标位置的下方。
# 第 6.1 课开启新行命令Open
** 输入 `o`{normal} 可以在光标下方开启一个新行,并进入插入模式。 **
1. 将光标移动到下面标有 ✓ 的那一行。
2. 输入小写字母 `o`{normal},在光标“下方”[开启](o)一个新行,并让你进入插入模式。
3. 现在输入一些文本,然后按 `<Esc>`{normal} 退出插入模式。完成后请删除你开启的行。
After typing `o`{normal} the cursor is placed on the open line in Insert mode.
4. 要在光标“上方”开启一个新行,只需输入[大写 O](O),而不是小写 `o`{normal}。
在下面这行上试试。完成后请删除你开启的行。
Open up a line above this by typing O while the cursor is on this line.
# 第 6.2 课追加命令Append
** 输入 `a`{normal} 可以在光标“之后”插入文本。 **
1. 将光标移动到下面标有 ✗ 的那行的开头。
2. 按 `e`{normal} 直到光标移动到 "li" 的末尾。
3. 输入小写字母 `a`{normal},在光标“之后”[追加](a)文本。
4. 参照下一行,补全这个单词。按 `<Esc>`{normal} 退出插入模式。
5. 使用 `e`{normal} 移动到下一个不完整的单词,并重复第 3 和第 4 步。
This li will allow you to pract appendi text to a line.
This line will allow you to practice appending text to a line.
NOTE: [a](a)、[i](i) 和 [A](A) 都会进入同一个插入模式,唯一的区别是字符被插入的位置不同。
# 第 6.3 课:另一种替换方式
** 输入大写 `R`{normal} 可以替换多个字符。 **
1. 将光标移动到下面标有 ✗ 的第一行。将光标移动到第一个 "xxx" 的开头。
2. 现在按下 `R`{normal}[大写 R](R))并输入第二行中对应的数字,用它来替换 "xxx"。
3. 按下 `<Esc>`{normal} 离开[替换模式Replace mode](mode-replace)。注意该行余下的部分保持不变。
4. 重复以上步骤来替换剩下的 "xxx"。
Adding 123 to xxx gives you xxx.
Adding 123 to 456 gives you 579.
NOTE: 替换模式很像插入模式,但你输入的每个字符都会替换掉一个已有的字符。
# 第 6.4 课:复制和粘贴文本
** 使用 `y`{normal} 操作符来复制yank文本用 `p`{normal} 来粘贴put它。 **
1. 去到下面标有 ✓ 的那一行,并将光标放在 "a)" 之后。
2. 用 `v`{normal} 启动可视模式,并将光标移动到 "first" 之前。
3. 输入 `y`{normal} 来 [复制yank](yank) 高亮的文本。
4. 将光标移动到下一行的末尾:`j$`{normal}
5. 输入 `p`{normal} 来 [粘贴put](put) 文本。
6. 按下 `a`{normal} 然后输入 "second"。按 `<Esc>`{normal} 离开
插入模式。
7. 使用可视模式选中 "item.",用 `y`{normal} 复制它,用 `j$`{normal} 移动到
下一行的末尾,然后用 `p`{normal} 在那里粘贴文本。
a) This is the first item.
b)
NOTE: 你可以把 `y`{normal} 当作一个操作符来使用:`yw`{normal} 会复制一个单词。
NOTE: 你可以用 `P`{normal} 在光标前粘贴,而不是在光标后。
# 第 6.5 课设置选项Set
** 设置一个选项,让搜索和替换命令忽略大小写。 **
Neovim 中有许多设置,你可以配置它们来满足你的需求。
1. 通过输入 `/ignore` 来搜索 'ignore'。
多按几次 `n`{normal} 来重复搜索。
2. 通过输入以下命令来设置 'ic'Ignore case即忽略大小写 选项:
~~~ cmd
:set ic
~~~
3. 现在再次按 `n`{normal} 搜索 'ignore'。
注意 Ignore 和 IGNORE 现在也能被找到了。
4. 设置 'hlsearch' 和 'incsearch' 选项:
~~~ cmd
:set hls is
~~~
5. 现在再次输入搜索命令,看看会发生什么:/ignore <Enter>
6. 要禁用忽略大小写,输入:
~~~ cmd
:set noic
~~~
7. 要反转一个设置的值,在它前面加上 "inv"
~~~ cmd
:set invic
~~~
NOTE: 要移除匹配项的高亮,输入:
~~~ cmd
:nohlsearch
~~~
NOTE: 如果你只想在某一次搜索命令中忽略大小写,在短语中使用 [\c](/\c)
/ignore\c <Enter>
# 第 6 课总结
1. 输入 `o`{normal} 在光标“下方”开启一个新行并进入插入模式。
输入 `O`{normal} 在光标“上方”开启一个新行。
2. 输入 `a`{normal} 在光标“之后”插入文本。
输入 `A`{normal} 在行尾之后插入文本。
3. `e`{normal} 命令移动到一个单词的末尾。
4. `y`{normal} 操作符复制文本,`p`{normal} 粘贴它。
5. 输入大写 `R`{normal} 会进入替换模式,直到按下 `<Esc>`{normal}。
6. 输入 "[:set](:set) xxx" 可以设置选项 "xxx"。一些有用的选项是:
'ic' 'ignorecase' 搜索时忽略大小写
'is' 'incsearch' 实时显示搜索短语的部分匹配
'hls' 'hlsearch' 高亮所有匹配的短语
你可以使用长选项名或短选项名。
7. 在选项前加上 "no" 来关闭一个选项:
~~~ cmd
:set noic
~~~
8. 在选项前加上 "inv" 来反转一个选项的值:
~~~ cmd
:set invic
~~~
# 第 7.1 课:获取帮助
** 使用在线帮助系统。 **
Neovim 有一个全面的在线帮助系统。
要开始使用,试试下面两种方法之一:
- 按下 `<F1>`{normal} 键(如果你有的话)
- 输入 `:help`{vim}
阅读帮助窗口中的文本,了解帮助系统是如何工作的。
输入 `<C-w><C-w>`{normal} 可以在窗口之间跳转。
输入 `:q`{vim} 关闭帮助窗口。
你几乎可以找到任何主题的帮助,只需给 ":help" 命令传递一个参数。
试试这些(别忘了按 <Enter>:
~~~ cmd
:help w
:help c_CTRL-D
:help insert-index
:help user-manual
~~~
# 第 7.2 课:补全功能
** 使用 `<C-d>`{normal} 和 `<Tab>`{normal} 进行命令行补全。 **
1. 列出当前目录的内容:`:!{unix:(ls),win:(dir)}`{vim}
2. 输入一个命令的开头:`:e`{vim}
3. 按下 `<C-d>`{normal}Neovim 会显示一个以 "e" 开头的命令列表。
4. 按下 `<Tab>`{normal}Neovim 会显示一个包含可能补全项的菜单
(或者如果输入的命令是唯一的,则直接补全,例如 ":ed`<Tab>`{normal}" 会被补全为 ":edit")。
5. 使用 `<Tab>`{normal} 或 `<C-n>`{normal} 移动到下一个匹配项。或者
使用 `<S-Tab>`{normal} 或 `<C-p>`{normal} 移动到上一个匹配项。
6. 选择 `edit`{vim} 条目。现在你可以看到 `edit`{vim} 这个词已经被自动插入到命令行了。
7. 现在加上一个空格和一个已存在文件名的开头:`:edit FIL`{vim}
8. 按下 `<Tab>`{normal}。Vim 会显示一个补全菜单,列出以 `FIL` 开头的文件名。
NOTE: 补全功能对许多命令都有效。它对 `:help`{vim} 命令尤其有用。
# 第 7.3 课:配置 NVIM
Neovim 是一个高度可配置的编辑器。你可以根据自己的需求进行定制。要开始使用更多功能,
可以创建一个 vimrc 文件,如果你想用 Lua文件名可以是 "init.lua",如果你想用
Vimscript文件名可以是 "init.vim"。在本课中,我们将使用 "init.lua"。
1. 开始编辑 "init.lua" 文件。
`:exe 'edit' stdpath('config')..'/init.lua'`{vim}
2. 将 Lua 的示例配置复制到你的 "init.lua" 文件中。
`:read $VIMRUNTIME/example_init.lua`{vim}
3. 写入文件(这也会创建任何缺失的父目录):
`:w ++p`{vim}
4. 下次你启动 Neovim 时,可以用以下命令快速打开这个 vimrc 文件:
`:e $MYVIMRC`{vim}
# 第 7 课总结
1. 输入 `:help`{vim}
或按下 `<F1>`{normal} 或 `<Help>`{normal} 来打开一个帮助窗口。
2. 输入 `:help 主题`{vim} 来查找关于“主题”的帮助。
3. 输入 `<C-w><C-w>`{normal} 来跳转到另一个窗口。
4. 输入 `:q`{vim} 来关闭帮助窗口。
5. 在命令模式下,按 `<C-d>`{normal} 查看可能的补全。按 `<Tab>`{normal} 使用补全菜单并选择一个匹配项。
6. 创建你的配置文件来保存你的偏好设置。你可以用 `:e $MYVIMRC`{vim} 再次访问它。
# 接下来呢?
运行 `:help nvim-quickstart`{vim} 获取更多关于扩展 Nvim 的信息。
# 结语
这就是 Vim 教程第一章的全部内容。你可以继续学习[第二章](@tutor:vim-02-beginner)。
本教程旨在简要介绍 Neovim 编辑器,内容刚好足够让你能比较轻松地使用它。它远非完
整,因为 Neovim 还有许许多多其他的命令。请经常查阅帮助文档。网上也有无数优秀的
教程和视频可供学习。这里有一些推荐:
- *Learn Vim Progressively*:
https://yannesposito.com/Scratch/en/blog/Learn-Vim-Progressively/
- *Learning Vim in 2014*:
https://benmccormick.org/learning-vim-in-2014/
- *Vimcasts*:
http://vimcasts.org/
- *Vim Video-Tutorials by Derek Wyatt*:
http://derekwyatt.org/vim/tutorials/
- *Learn Vimscript the Hard Way*:
https://learnvimscriptthehardway.stevelosh.com/
- *7 Habits of Effective Text Editing*:
https://www.moolenaar.net/habits.html
- *vim-galore*:
https://github.com/mhinz/vim-galore
如果你更喜欢书籍Drew Neil 的 *Practical Vim* 经常被推荐
(其续作 *Modern Vim* 包含了 Neovim 的特有内容)。
本教程由 Michael C. Pierce 和 Robert K. Ware科罗拉多矿业大学编写采用了
Charles Smith科罗拉多州立大学提供的想法。电子邮件: bware@mines.colorado.edu。
由 Bram Moolenaar 为 Vim 修改。
由 Felipe Morales 为 vim-tutor-mode 修改。
由 Rory Nesbitt 为 Neovim 修改。
Neovim Tutor 简体中文翻译版由 PilgrimLyieu <pilgrimlyieu@outlook.com> 译制并校对。
变更记录:
- 2025-07-06 PilgrimLyieu <pilgrimlyieu@outlook.com>
译制并校对
// vim: nowrap

View File

@@ -0,0 +1,43 @@
{
"expect": {
"96": "The cow jumped over the moon.",
"97": "The cow jumped over the moon.",
"117": "There is some text missing from this line.",
"118": "There is some text missing from this line.",
"135": "There is some text missing from this line.",
"136": "There is some text missing from this line.",
"137": "There is also some text missing here.",
"138": "There is also some text missing here.",
"204": "There are some words that don't belong in this sentence.",
"205": "There are some words that don't belong in this sentence.",
"221": "Somebody typed the end of this line twice.",
"259": -1,
"276": "This line of words is cleaned up.",
"292": "1) Roses are red,",
"293": "",
"294": "3) Violets are blue,",
"295": "",
"296": "",
"297": "6) Sugar is sweet",
"298": "7) And so are you.",
"318": "Fix the errors on this line and replace them with undo.",
"319": "Fix the errors on this line and replace them with undo.",
"384": "When this line was typed in, someone pressed some wrong keys!",
"385": "When this line was typed in, someone pressed some wrong keys!",
"405": "This line has a few words that need changing using the change operator.",
"406": "This line has a few words that need changing using the change operator.",
"426": "The end of this line needs to be corrected using the c$ command.",
"427": "The end of this line needs to be corrected using the c$ command.",
"484": -1,
"502": -1,
"524": "Usually the best time to see the flowers is in the spring.",
"702": -1,
"707": -1,
"723": "This line will allow you to practice appending text to a line.",
"724": "This line will allow you to practice appending text to a line.",
"740": "Adding 123 to 456 gives you 579.",
"741": "Adding 123 to 456 gives you 579.",
"765": "a) This is the first item.",
"766": "b) This is the second item."
}
}

View File

@@ -0,0 +1,189 @@
# 欢迎来到 Neovim 教程
# 第 2 章
此处有龙(拉丁语 Hic Sunt Dracones表示有危险如果这是您第一次接触 vim
且您希望从入门章节开始,请在 Vim 编辑器的命令行中输入:
~~~ cmd
:Tutor vim-01-beginner
~~~
或者直接点击链接打开教程的[第一章](@tutor:vim-01-beginner)。
完成本章大约需要 8-10 分钟,具体取决于您在实践探索上花费的时间。
# 第 2.1.1 课:命名寄存器
** 同时复制两个单词,然后分别粘贴它们 **
1. 将光标移动到下面标有 ✓ 的那一行。
2. 导航到 'Edward' 单词的任意位置,然后输入 `"ayiw`{normal}
**助记***将 (i)nner (w)ord内部单词(y)ank复制到名为 (a) 的寄存器(")中*
3. 向前导航到 'cookie' 单词(可以使用 `fk`{normal} 或 `2fc`{normal}
或 `$2b`{normal} 或 `/co`{normal} `<Enter>`{normal}),然后输入 `"byiw`{normal}
4. 导航到 'Vince' 单词的任意位置,然后输入 `ciw<CTRL-r>a<ESC>`{normal}
**助记***用名为 (a) 的寄存器(<contents of (r)egister>)的内容 (c)hange (i)
nner (w)ord修改内部单词*
5. 导航到 'cake' 单词的任意位置,然后输入 `ciw<CTRL-r>b<ESC>`{normal}
a) Edward will henceforth be in charge of the cookie rations
b) In this capacity, Vince will have sole cake discretionary powers
NOTE: 删除操作同样可以存入寄存器,例如 `"sdiw`{normal} 会将被光标下的单词删除并存入寄存器 s。
参考:[寄存器](registers)
[命名寄存器](quotea)
[移动与文本对象](text-objects)
[CTRL-R](i_CTRL-R)
# 第 2.1.2 课:表达式寄存器
** 即时插入计算结果 **
1. 将光标移动到下面标有 ✗ 的那一行。
2. 导航到所给数字的任意位置。
3. 输入 `ciw<CTRL-r>=`{normal}60\*60\*24 `<Enter>`{normal}
4. 在下一行,进入插入模式,并使用
`<CTRL-r>=`{normal}`system('date')`{vim} `<Enter>`{normal} 来添加今天的日期。
NOTE: 所有对 `system` 的调用都依赖于操作系统,例如在 Windows 上应使用
`system('date /t')`{vim} 或 `:r!date /t`{vim}
I have forgotten the exact number of seconds in a day, is it 84600?
Today's date is:
NOTE: 同样效果也可以通过 `:pu=`{normal}`system('date')`{vim} 实现,
或者用更少的按键 `:r!date`{vim}
参考:[表达式寄存器](quote=)
# 第 2.1.3 课:数字寄存器
** 按下 `yy`{normal} 和 `dd`{normal} 来观察它们对寄存器的影响 **
1. 将光标移动到下面标有 ✓ 的那一行。
2. 复制yank第 0 行,然后用 `:reg`{vim} `<Enter>`{normal} 查看寄存器。
3. 用 `"cdd`{normal} 删除第 0 行,然后再次查看寄存器。
(你觉得第 0 行的内容会出现在哪里?)
4. 继续删除后续的每一行,并在每次删除后用 `:reg`{vim} 查看寄存器。
NOTE: 你应该会发现,当新的整行删除内容被添加进来时,之前删除的内容会在寄存器列
表中依次下移。
5. 现在,按顺序 (p)aste粘贴以下寄存器中的内容c, 7, 4, 8, 2。例如使用 `"7p`{normal}
0. This
9. wobble
8. secret
7. is
6. on
5. axis
4. a
3. war
2. message
1. tribute
NOTE: 在数字寄存器中,整行删除(`dd`{normal})的内容比整行复制或涉及更小范围移动
的删除操作“存活”得更久。
参考:[数字寄存器](quote0)
# 第 2.1.4 课:标记之美
** 避免“码农式”的行号计算 **
NOTE: 在写代码时,一个常见的难题是移动大块的代码。
下面的技巧可以帮助你避免进行行号计算,比如 `"a147d`{normal} 或 `:945,1091d a`{vim}
甚至是更麻烦的先用 `i<CTRL-r>=`{normal}1091-945 `<Enter>`{normal} 计算行数。
1. 将光标移动到下面标有 ✓ 的那一行。
2. 跳转到函数的第一行,并用 `ma`{normal} 将其标记为 a。
NOTE: 光标在该行的确切位置并不重要!
3. 使用 `$%`{normal} 导航到行尾,然后再到代码块的末尾。
4. 使用 `"ad'a`{normal} 将该代码块删除并存入寄存器 a。
**助记***将从光标位置到包含标记('(a) 的那一行的内容 (d)elete删除到名为 (a) 的寄存器(")中*
5. 在 BBB 和 CCC 之间用 `"ap`{normal} 粘贴该代码块。
NOTE: 多次练习这个操作以达到熟练:`ma$%"ad'a`{normal}
~~~ cmd
AAA
function itGotRealBigRealFast() {
if ( somethingIsTrue ) {
doIt()
}
// the taxonomy of our function has changed and it
// no longer makes alphabetical sense in its current position
// imagine hundreds of lines of code
// naively you could navigate to the start and end and record or
// remember each line number
}
BBB
CCC
~~~
NOTE: 标记和寄存器不共享命名空间,因此寄存器 a 和标记 a 是完全独立的。
但寄存器和宏并非如此。
参考:[标记](marks)
[标记移动](mark-motions)' 和 \` 的区别)
# 第 2.1 课总结
1. 将文本 存储(复制、删除)到 26 个寄存器a-z并从中提取粘贴出来。
2. 从单词内的任意位置复制整个单词:`yiw`{normal}
3. 从单词内的任意位置更改整个单词:`ciw`{normal}
4. 在插入模式下直接从寄存器插入文本:`<CTRL-r>a`{normal}
5. 插入简单算术运算的结果:
在插入模式下使用 `<CTRL-r>=`{normal}60\*60 `<Enter>`{normal}
6. 插入系统调用的结果:
在插入模式下使用 `<CTRL-r>=`{normal}`system('ls -1')`{vim}
7. 使用 `:reg`{vim} 查看寄存器。
8. 了解整行删除 (`dd`{normal}) 的最终去向:在数字寄存器中,即从寄存器 1 到 9
依次向下存放。要理解整行删除的内容在数字寄存器中比任何其他操作都保存得更久。
9. 了解所有复制操作在数字寄存器中的最终去向,以及它们是多么“短暂易逝”。
10. 在普通模式下设置标记:`m[a-zA-Z0-9]`{normal}
11. 按行移动到标记位置:`'`{normal}
# 结语
Neovim 教程第二章到此结束。本教程仍在不断完善中。
本章由 Paul D. Parker 编写。
由 Restorer 为 vim-tutor-mode 修改。
简体中文翻译版由 PilgrimLyieu <pilgrimlyieu@outlook.com> 译制并校对。
变更记录:
- 2025-07-07 PilgrimLyieu <pilgrimlyieu@outlook.com>
译制并校对

View File

@@ -0,0 +1,10 @@
{
"expect": {
"35": -1,
"36": "b) In this capacity, Edward will have sole cookie discretionary powers",
"62": "I have forgotten the exact number of seconds in a day, is it 86400",
"63": -1,
"89": -1,
"132": -1
}
}

View File

@@ -636,6 +636,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Arena
garray_T capture_local;
const int save_msg_silent = msg_silent;
const bool save_redir_off = redir_off;
garray_T * const save_capture_ga = capture_ga;
const int save_msg_col = msg_col;
@@ -647,6 +648,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Arena
TRY_WRAP(err, {
if (opts->output) {
msg_silent++;
redir_off = false;
msg_col = 0; // prevent leading spaces
}
@@ -657,6 +659,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Arena
if (opts->output) {
capture_ga = save_capture_ga;
msg_silent = save_msg_silent;
redir_off = save_redir_off;
// Put msg_col back where it was, since nothing should have been written.
msg_col = save_msg_col;
}
@@ -1073,6 +1076,7 @@ void create_user_command(uint64_t channel_id, String name, Object command, Dict(
goto err;
});
argt |= EX_RANGE;
if (addr_type_arg != ADDR_LINES) {
argt |= EX_ZEROR;
}

View File

@@ -72,6 +72,7 @@ Dict nvim_exec2(uint64_t channel_id, String src, Dict(exec_opts) *opts, Error *e
String exec_impl(uint64_t channel_id, String src, Dict(exec_opts) *opts, Error *err)
{
const int save_msg_silent = msg_silent;
const bool save_redir_off = redir_off;
garray_T *const save_capture_ga = capture_ga;
const int save_msg_col = msg_col;
garray_T capture_local;
@@ -83,6 +84,7 @@ String exec_impl(uint64_t channel_id, String src, Dict(exec_opts) *opts, Error *
TRY_WRAP(err, {
if (opts->output) {
msg_silent++;
redir_off = false;
msg_col = 0; // prevent leading spaces
}
@@ -92,6 +94,7 @@ String exec_impl(uint64_t channel_id, String src, Dict(exec_opts) *opts, Error *
if (opts->output) {
capture_ga = save_capture_ga;
msg_silent = save_msg_silent;
redir_off = save_redir_off;
// Put msg_col back where it was, since nothing should have been written.
msg_col = save_msg_col;
}

View File

@@ -202,7 +202,8 @@
/// the call.
/// - fixed: If true when anchor is NW or SW, the float window
/// would be kept fixed even if the window would be truncated.
/// - hide: If true the floating window will be hidden.
/// - hide: If true the floating window will be hidden and the cursor will be invisible when
/// focused on it.
/// - vertical: Split vertically |:vertical|.
/// - split: Split direction: "left", "right", "above", "below".
///

View File

@@ -679,10 +679,6 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool i
return false;
}
// Disable buffer-updates for the current buffer.
// No need to check `unload_buf`: in that case the function returned above.
buf_updates_unload(buf, false);
if (win != NULL // Avoid bogus clang warning.
&& win_valid_any_tab(win)
&& win->w_buffer == buf) {
@@ -789,6 +785,11 @@ void buf_freeall(buf_T *buf, int flags)
bufref_T bufref;
set_bufref(&bufref, buf);
buf_updates_unload(buf, false);
if (!bufref_valid(&bufref)) {
// on_detach callback deleted the buffer.
return;
}
if ((buf->b_ml.ml_mfp != NULL)
&& apply_autocmds(EVENT_BUFUNLOAD, buf->b_fname, buf->b_fname, false, buf)
&& !bufref_valid(&bufref)) {
@@ -935,7 +936,7 @@ static void free_buffer_stuff(buf_T *buf, int free_flags)
map_clear_mode(buf, MAP_ALL_MODES, true, true); // clear local abbrevs
XFREE_CLEAR(buf->b_start_fenc);
buf_updates_unload(buf, false);
buf_free_callbacks(buf);
}
/// Go to another buffer. Handles the result of the ATTENTION dialog.
@@ -1255,8 +1256,11 @@ static int do_buffer_ext(int action, int start, int dir, int count, int flags)
buf = buf->b_next;
}
} else {
const bool help_only = (flags & DOBUF_SKIPHELP) != 0 && buf->b_help;
bp = NULL;
while (count > 0 || (!unload && !buf->b_p_bl && bp != buf)) {
while (count > 0 || (bp != buf && !unload
&& !(help_only ? buf->b_help : buf->b_p_bl))) {
// remember the buffer where we start, we come back there when all
// buffers are unlisted.
if (bp == NULL) {
@@ -1264,12 +1268,13 @@ static int do_buffer_ext(int action, int start, int dir, int count, int flags)
}
buf = dir == FORWARD ? (buf->b_next != NULL ? buf->b_next : firstbuf)
: (buf->b_prev != NULL ? buf->b_prev : lastbuf);
// Avoid non-help buffers if the starting point was a help buffer
// and vice-versa.
// Don't count unlisted buffers.
// Avoid non-help buffers if the starting point was a non-help buffer and
// vice-versa.
if (unload
|| (buf->b_p_bl
&& ((flags & DOBUF_SKIPHELP) == 0 || buf->b_help == bp->b_help))) {
|| (help_only
? buf->b_help
: (buf->b_p_bl && ((flags & DOBUF_SKIPHELP) == 0 || !buf->b_help)))) {
count--;
bp = NULL; // use this buffer as new starting point
}

View File

@@ -740,23 +740,24 @@ static int buf_write_make_backup(char *fname, bool append, FileInfo *file_info_o
// the ones from the original file.
// First find a file name that doesn't exist yet (use some
// arbitrary numbers).
xstrlcpy(IObuff, fname, IOSIZE);
size_t dirlen = (size_t)(path_tail(fname) - fname);
assert(dirlen < MAXPATHL);
char tmp_fname[MAXPATHL];
xmemcpyz(tmp_fname, fname, dirlen);
for (int i = 4913;; i += 123) {
char *tail = path_tail(IObuff);
size_t size = (size_t)(tail - IObuff);
snprintf(tail, IOSIZE - size, "%d", i);
if (!os_fileinfo_link(IObuff, &file_info)) {
snprintf(tmp_fname + dirlen, sizeof(tmp_fname) - dirlen, "%d", i);
if (!os_fileinfo_link(tmp_fname, &file_info)) {
break;
}
}
int fd = os_open(IObuff,
int fd = os_open(tmp_fname,
O_CREAT|O_WRONLY|O_EXCL|O_NOFOLLOW, perm);
if (fd < 0) { // can't write in directory
*backup_copyp = true;
} else {
#ifdef UNIX
os_fchown(fd, (uv_uid_t)file_info_old->stat.st_uid, (uv_gid_t)file_info_old->stat.st_gid);
if (!os_fileinfo(IObuff, &file_info)
if (!os_fileinfo(tmp_fname, &file_info)
|| file_info.stat.st_uid != file_info_old->stat.st_uid
|| file_info.stat.st_gid != file_info_old->stat.st_gid
|| (int)file_info.stat.st_mode != perm) {
@@ -766,7 +767,7 @@ static int buf_write_make_backup(char *fname, bool append, FileInfo *file_info_o
// Close the file before removing it, on MS-Windows we
// can't delete an open file.
close(fd);
os_remove(IObuff);
os_remove(tmp_fname);
}
}
}

View File

@@ -1887,6 +1887,7 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
case CMD_tab:
case CMD_tabdo:
case CMD_topleft:
case CMD_unsilent:
case CMD_verbose:
case CMD_vertical:
case CMD_windo:
@@ -2152,7 +2153,6 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
break;
case CMD_checkhealth:
xp->xp_context = EXPAND_CHECKHEALTH;
xp->xp_pattern = (char *)arg;
break;
case CMD_messages:

View File

@@ -87,10 +87,10 @@ static bool diff_need_update = false; // ex_diffupdate needs to be called
#define DIFF_FOLLOWWRAP 0x800 // follow the wrap option
#define DIFF_LINEMATCH 0x1000 // match most similar lines within diff
#define ALL_WHITE_DIFF (DIFF_IWHITE | DIFF_IWHITEALL | DIFF_IWHITEEOL)
static int diff_flags = DIFF_INTERNAL | DIFF_FILLER | DIFF_CLOSE_OFF;
static int diff_flags = DIFF_INTERNAL | DIFF_FILLER | DIFF_CLOSE_OFF | DIFF_LINEMATCH;
static int diff_algorithm = 0;
static int linematch_lines = 0;
static int linematch_lines = 40;
#define LBUFLEN 50 // length of line in diff file

View File

@@ -272,7 +272,7 @@ void screenclear(void)
/// to be re-emitted: avoid clearing the prompt from the message grid.
static bool cmdline_number_prompt(void)
{
return !ui_has(kUIMessages) && State == MODE_CMDLINE && get_cmdline_info()->mouse_used != NULL;
return !ui_has(kUIMessages) && (State & MODE_CMDLINE) && get_cmdline_info()->mouse_used != NULL;
}
/// Set dimensions of the Nvim application "screen".
@@ -374,8 +374,8 @@ void screen_resize(int width, int height)
// - in Ex mode, don't redraw anything.
// - Otherwise, redraw right now, and position the cursor.
if (State == MODE_ASKMORE || State == MODE_EXTERNCMD || exmode_active
|| (State == MODE_CMDLINE && get_cmdline_info()->one_key)) {
if (State == MODE_CMDLINE) {
|| ((State & MODE_CMDLINE) && get_cmdline_info()->one_key)) {
if (State & MODE_CMDLINE) {
update_screen();
}
if (msg_grid.chars) {

View File

@@ -2552,7 +2552,6 @@ void clear_evalarg(evalarg_T *evalarg, exarg_T *eap)
/// Handle zero level expression.
/// This calls eval1() and handles error message and nextcmd.
/// Put the result in "rettv" when returning OK and "evaluate" is true.
/// Note: "rettv.v_lock" is not set.
///
/// @param evalarg can be NULL, &EVALARG_EVALUATE or a pointer.
///
@@ -2649,11 +2648,11 @@ int eval0_simple_funccal(char *arg, typval_T *rettv, exarg_T *eap, evalarg_T *co
/// "arg" must point to the first non-white of the expression.
/// "arg" is advanced to the next non-white after the recognized expression.
///
/// Note: "rettv.v_lock" is not set.
///
/// @return OK or FAIL.
int eval1(char **arg, typval_T *rettv, evalarg_T *const evalarg)
{
CLEAR_POINTER(rettv);
// Get the first variable.
if (eval2(arg, rettv, evalarg) == FAIL) {
return FAIL;

View File

@@ -2408,19 +2408,14 @@ static int get_function_body(exarg_T *eap, garray_T *newlines, char *line_arg_in
}
// Check for ":append", ":change", ":insert".
p = skip_range(p, NULL);
if ((p[0] == 'a' && (!ASCII_ISALPHA(p[1]) || p[1] == 'p'))
|| (p[0] == 'c'
&& (!ASCII_ISALPHA(p[1])
|| (p[1] == 'h' && (!ASCII_ISALPHA(p[2])
|| (p[2] == 'a'
&& (strncmp(&p[3], "nge", 3) != 0
|| !ASCII_ISALPHA(p[6])))))))
|| (p[0] == 'i'
&& (!ASCII_ISALPHA(p[1]) || (p[1] == 'n'
&& (!ASCII_ISALPHA(p[2])
|| (p[2] == 's')))))) {
char *const tp = p = skip_range(p, NULL);
if ((checkforcmd(&p, "append", 1)
|| checkforcmd(&p, "change", 1)
|| checkforcmd(&p, "insert", 1))
&& (*p == '!' || *p == '|' || ascii_iswhite_nl_or_nul(*p))) {
skip_until = xmemdupz(".", 1);
} else {
p = tp;
}
// heredoc: Check for ":python <<EOF", ":lua <<EOF", etc.

View File

@@ -2512,13 +2512,11 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
goto theend;
}
u_unchanged(curbuf);
buf_updates_unload(curbuf, false);
buf_freeall(curbuf, BFA_KEEP_UNDO);
// Tell readfile() not to clear or reload undo info.
readfile_flags = READ_KEEP_UNDO;
} else {
buf_updates_unload(curbuf, false);
buf_freeall(curbuf, 0); // Free all things for buffer.
}
// If autocommands deleted the buffer we were going to re-edit, give

View File

@@ -3023,13 +3023,16 @@ static void append_command(const char *cmd)
}
/// Return true and set "*idx" if "p" points to a one letter command.
/// - The 'k' command can directly be followed by any character.
/// - The 'k' command can directly be followed by any character
/// but :keepa[lt] is another command, as are :keepj[umps],
/// :kee[pmarks] and :keepp[atterns].
/// - The 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r'
/// but :sre[wind] is another command, as are :scr[iptnames],
/// :scs[cope], :sim[alt], :sig[ns] and :sil[ent].
static int one_letter_cmd(const char *p, cmdidx_T *idx)
{
if (*p == 'k') {
if (p[0] == 'k'
&& (p[1] != 'e' || (p[1] == 'e' && p[2] != 'e'))) {
*idx = CMD_k;
return true;
}
@@ -3062,6 +3065,9 @@ char *find_ex_command(exarg_T *eap, int *full)
char *p = eap->cmd;
if (one_letter_cmd(p, &eap->cmdidx)) {
p++;
if (full != NULL) {
*full = true;
}
} else {
while (ASCII_ISALPHA(*p)) {
p++;

View File

@@ -1470,6 +1470,17 @@ static int may_do_command_line_next_incsearch(int firstc, int count, incsearch_s
pat = ccline.cmdbuff + skiplen;
}
bool bslsh = false;
// do not search for the search end delimiter,
// unless it is part of the pattern
if (patlen > 2 && firstc == pat[patlen - 1]) {
patlen--;
if (pat[patlen - 1] == '\\') {
pat[patlen - 1] = (char)(uint8_t)firstc;
bslsh = true;
}
}
if (next_match) {
t = s->match_end;
if (lt(s->match_start, s->match_end)) {
@@ -1493,6 +1504,9 @@ static int may_do_command_line_next_incsearch(int firstc, int count, incsearch_s
RE_SEARCH, NULL);
emsg_off--;
pat[patlen] = save;
if (bslsh) {
pat[patlen - 1] = '\\';
}
ui_busy_stop();
if (found) {
s->search_start = s->match_start;

View File

@@ -1493,7 +1493,7 @@ int typval_exec_lua_callable(LuaRef lua_cb, int argcount, typval_T *argvars, typ
PUSH_ALL_TYPVALS(lstate, argvars, argcount, false);
if (nlua_pcall(lstate, argcount, 1)) {
nlua_print(lstate);
nlua_error(lstate, _("Lua callback: %.*s"));
return FCERR_OTHER;
}

View File

@@ -565,7 +565,7 @@ static int nlua_foldupdate(lua_State *lstate)
}
// input is zero-based end-exclusive range
linenr_T top = (linenr_T)luaL_checkinteger(lstate, 2) + 1;
if (top < 1 || top > win->w_buffer->b_ml.ml_line_count) {
if (top < 1) {
return luaL_error(lstate, "invalid top");
}
linenr_T bot = (linenr_T)luaL_checkinteger(lstate, 3);

View File

@@ -3062,7 +3062,7 @@ void repeat_message(void)
if (State == MODE_ASKMORE) {
msg_moremsg(true); // display --more-- message again
msg_row = Rows - 1;
} else if (State == MODE_CMDLINE && confirm_msg != NULL) {
} else if ((State & MODE_CMDLINE) && confirm_msg != NULL) {
display_confirm_msg(); // display ":confirm" message again
msg_row = Rows - 1;
} else if (State == MODE_EXTERNCMD) {

View File

@@ -6615,10 +6615,17 @@ void finish_yankreg_from_object(yankreg_T *reg, bool clipboard_adjust)
}
}
update_yankreg_width(reg);
}
/// Updates the "y_width" of a blockwise register based on its contents.
/// Do nothing on a non-blockwise register.
static void update_yankreg_width(yankreg_T *reg)
{
if (reg->y_type == kMTBlockWise) {
size_t maxlen = 0;
for (size_t i = 0; i < reg->y_size; i++) {
size_t rowlen = reg->y_array[i].size;
size_t rowlen = mb_string2cells_len(reg->y_array[i].data, reg->y_array[i].size);
maxlen = MAX(maxlen, rowlen);
}
assert(maxlen <= INT_MAX);
@@ -6720,15 +6727,7 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet)
}
}
if (reg->y_type == kMTBlockWise) {
size_t maxlen = 0;
for (size_t i = 0; i < reg->y_size; i++) {
size_t rowlen = reg->y_array[i].size;
maxlen = MAX(maxlen, rowlen);
}
assert(maxlen <= INT_MAX);
reg->y_width = (int)maxlen - 1;
}
update_yankreg_width(reg);
*target = reg;
return true;

View File

@@ -139,10 +139,10 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
// To keep the code simple, we only allow changing the
// draw mode when the popup menu is not being displayed
pum_external = ui_has(kUIPopupmenu)
|| (State == MODE_CMDLINE && ui_has(kUIWildmenu));
|| ((State & MODE_CMDLINE) && ui_has(kUIWildmenu));
}
pum_rl = State != MODE_CMDLINE && curwin->w_p_rl;
pum_rl = (State & MODE_CMDLINE) == 0 && curwin->w_p_rl;
do {
// Mark the pum as visible already here,
@@ -154,7 +154,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
int below_row = cmdline_row;
// wildoptions=pum
if (State == MODE_CMDLINE) {
if (State & MODE_CMDLINE) {
pum_win_row = ui_has(kUICmdline) ? 0 : cmdline_row;
cursor_col = cmd_startcol;
pum_anchor_grid = ui_has(kUICmdline) ? -1 : DEFAULT_GRID_HANDLE;
@@ -244,7 +244,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
// pum above "pum_win_row"
pum_above = true;
if (State == MODE_CMDLINE) {
if (State & MODE_CMDLINE) {
// for cmdline pum, no need for context lines
context_lines = 0;
} else {
@@ -268,7 +268,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
// pum below "pum_win_row"
pum_above = false;
if (State == MODE_CMDLINE) {
if (State & MODE_CMDLINE) {
// for cmdline pum, no need for context lines
context_lines = 0;
} else {
@@ -404,7 +404,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
// room the window size will keep changing.
} while (pum_set_selected(selected, redo_count) && ++redo_count <= 2);
pum_grid.zindex = (State == MODE_CMDLINE) ? kZIndexCmdlinePopupMenu : kZIndexPopupMenu;
pum_grid.zindex = (State & MODE_CMDLINE) ? kZIndexCmdlinePopupMenu : kZIndexPopupMenu;
pum_redraw();
}
@@ -418,15 +418,15 @@ static int *pum_compute_text_attrs(char *text, hlf_T hlf, int user_hlattr)
return NULL;
}
char *leader = State == MODE_CMDLINE ? cmdline_compl_pattern()
: ins_compl_leader();
char *leader = (State & MODE_CMDLINE) ? cmdline_compl_pattern()
: ins_compl_leader();
if (leader == NULL || *leader == NUL) {
return NULL;
}
int *attrs = xmalloc(sizeof(int) * (size_t)vim_strsize(text));
bool in_fuzzy = State == MODE_CMDLINE ? cmdline_compl_is_fuzzy()
: (get_cot_flags() & kOptCotFlagFuzzy) != 0;
bool in_fuzzy = (State & MODE_CMDLINE) ? cmdline_compl_is_fuzzy()
: (get_cot_flags() & kOptCotFlagFuzzy) != 0;
size_t leader_len = strlen(leader);
garray_T *ga = NULL;

View File

@@ -1582,6 +1582,7 @@ static void reg_nextline(void)
// Returns RA_FAIL, RA_NOMATCH or RA_MATCH.
// If "bytelen" is not NULL, it is set to the byte length of the match in the
// last line.
// Optional: ignore case if rex.reg_ic is set.
static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T end_lnum,
colnr_T end_col, int *bytelen)
{
@@ -1619,7 +1620,8 @@ static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T e
len = reg_getline_len(clnum) - ccol;
}
if (cstrncmp(p + ccol, (char *)rex.input, &len) != 0) {
if ((!rex.reg_ic && cstrncmp(p + ccol, (char *)rex.input, &len) != 0)
|| (rex.reg_ic && mb_strnicmp(p + ccol, (char *)rex.input, (size_t)len) != 0)) {
return RA_NOMATCH; // doesn't match
}
if (bytelen != NULL) {
@@ -1727,7 +1729,8 @@ static void mb_decompose(int c, int *c1, int *c2, int *c3)
*c3 = d.c;
} else {
*c1 = c;
*c2 = *c3 = 0;
*c2 = 0;
*c3 = 0;
}
}

View File

@@ -1213,13 +1213,10 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, size_t patlen
} else if (!spats[0].off.line) {
off_buf[off_len++] = 's';
}
if (spats[0].off.off > 0 || spats[0].off.line) {
off_buf[off_len++] = '+';
}
off_buf[off_len] = NUL;
if (spats[0].off.off != 0 || spats[0].off.line) {
off_len += (size_t)snprintf(off_buf + off_len, sizeof(off_buf) - off_len,
"%" PRId64, spats[0].off.off);
"%+" PRId64, spats[0].off.off);
}
}

View File

@@ -185,11 +185,11 @@ void get_mode(char *buf)
int i = 0;
if (State == MODE_HITRETURN || State == MODE_ASKMORE || State == MODE_SETWSIZE
|| (State == MODE_CMDLINE && get_cmdline_info()->one_key)) {
|| ((State & MODE_CMDLINE) && get_cmdline_info()->one_key)) {
buf[i++] = 'r';
if (State == MODE_ASKMORE) {
buf[i++] = 'm';
} else if (State == MODE_CMDLINE) {
} else if (State & MODE_CMDLINE) {
buf[i++] = '?';
}
} else if (State == MODE_EXTERNCMD) {

View File

@@ -113,7 +113,15 @@ struct TUIData {
bool set_cursor_color_as_str;
bool cursor_has_color;
bool is_starting;
bool did_set_grapheme_cluster_mode;
bool resize_events_enabled;
// Terminal modes that Nvim enabled that it must disable on exit
struct {
bool grapheme_clusters : 1;
bool theme_updates : 1;
bool resize_events : 1;
} modes;
FILE *screenshot;
cursorentry_T cursor_shapes[SHAPE_IDX_COUNT];
HlAttrs clear_attrs;
@@ -246,15 +254,25 @@ void tui_handle_term_mode(TUIData *tui, TermMode mode, TermModeState state)
case kTermModeGraphemeClusters:
if (!is_set) {
tui_set_term_mode(tui, mode, true);
tui->did_set_grapheme_cluster_mode = true;
tui->modes.grapheme_clusters = true;
}
break;
case kTermModeThemeUpdates:
tui_set_term_mode(tui, mode, true);
if (!is_set) {
tui_set_term_mode(tui, mode, true);
tui->modes.theme_updates = true;
}
break;
case kTermModeResizeEvents:
signal_watcher_stop(&tui->winch_handle);
tui_set_term_mode(tui, mode, true);
if (!is_set) {
tui_set_term_mode(tui, mode, true);
tui->modes.resize_events = true;
}
// We track both whether the mode is enabled AND if Nvim was the one to enable it
tui->resize_events_enabled = true;
break;
default:
break;
}
}
@@ -358,7 +376,10 @@ static void terminfo_start(TUIData *tui)
tui->overflow = false;
tui->set_cursor_color_as_str = false;
tui->cursor_has_color = false;
tui->did_set_grapheme_cluster_mode = false;
tui->resize_events_enabled = false;
tui->modes.grapheme_clusters = false;
tui->modes.resize_events = false;
tui->modes.theme_updates = false;
tui->showing_mode = SHAPE_IDX_N;
tui->unibi_ext.enable_mouse = -1;
tui->unibi_ext.disable_mouse = -1;
@@ -519,7 +540,9 @@ static void terminfo_disable(TUIData *tui)
{
// Disable theme update notifications. We do this first to avoid getting any
// more notifications after we reset the cursor and any color palette changes.
tui_set_term_mode(tui, kTermModeThemeUpdates, false);
if (tui->modes.theme_updates) {
tui_set_term_mode(tui, kTermModeThemeUpdates, false);
}
// Destroy output stuff
tui_mode_change(tui, NULL_STRING, SHAPE_IDX_N);
@@ -532,9 +555,12 @@ static void terminfo_disable(TUIData *tui)
// Reset the key encoding
tui_reset_key_encoding(tui);
// Disable resize events
tui_set_term_mode(tui, kTermModeResizeEvents, false);
if (tui->did_set_grapheme_cluster_mode) {
// Disable terminal modes that we enabled
if (tui->modes.resize_events) {
tui_set_term_mode(tui, kTermModeResizeEvents, false);
}
if (tui->modes.grapheme_clusters) {
tui_set_term_mode(tui, kTermModeGraphemeClusters, false);
}
@@ -680,7 +706,7 @@ static void sigwinch_cb(SignalWatcher *watcher, int signum, void *cbdata)
{
got_winch++;
TUIData *tui = cbdata;
if (tui_is_stopped(tui)) {
if (tui_is_stopped(tui) || tui->resize_events_enabled) {
return;
}

View File

@@ -536,7 +536,21 @@ void ui_flush(void)
if (!ui_active()) {
return;
}
static bool was_busy = false;
cmdline_ui_flush();
if (!(State & MODE_CMDLINE) && curwin->w_floating && curwin->w_config.hide) {
if (!was_busy) {
ui_call_busy_start();
was_busy = true;
}
} else if (was_busy) {
ui_call_busy_stop();
was_busy = false;
}
win_ui_flush(false);
msg_ext_ui_flush();
msg_scroll_flush();

View File

@@ -782,6 +782,13 @@ describe('nvim_create_user_command', function()
eq(5, #api.nvim_list_tabpages())
eq(1, fn.tabpagenr())
end)
it('"addr" flag allows ranges without explicit "range" flag', function()
exec_lua([[
vim.api.nvim_create_user_command('Test2', 'echo "hello <line1><line2>"', { addr = 'other' })
]])
eq('hello 12', n.exec_capture('1,2Test2'))
end)
end)
describe('nvim_del_user_command', function()

View File

@@ -385,6 +385,9 @@ describe('API', function()
)
eq({ output = '' }, api.nvim_exec2('echo', { output = true }))
eq({ output = 'foo 42' }, api.nvim_exec2('echo "foo" 42', { output = true }))
-- Returns output in cmdline mode #35321
feed(':')
eq({ output = 'foo 42' }, api.nvim_exec2('echo "foo" 42', { output = true }))
end)
it('displays messages when opts.output=false', function()
@@ -1586,6 +1589,17 @@ describe('API', function()
eq("Invalid 'type': 'bx'", pcall_err(api.nvim_put, { 'xxx', 'yyy' }, 'bx', false, true))
eq("Invalid 'type': 'b3x'", pcall_err(api.nvim_put, { 'xxx', 'yyy' }, 'b3x', false, true))
end)
it('computes block width correctly when not specified #35034', function()
api.nvim_put({ 'line 1', 'line 2', 'line 3' }, 'l', false, false)
-- block width should be 4
api.nvim_put({ 'あい', 'xxx', 'xx' }, 'b', false, false)
expect([[
あいline 1
xxx line 2
xx line 3
]])
end)
end)
describe('nvim_strwidth', function()
@@ -4887,6 +4901,9 @@ describe('API', function()
it('captures output', function()
eq('foo', api.nvim_cmd({ cmd = 'echo', args = { '"foo"' } }, { output = true }))
-- Returns output in cmdline mode #35321
feed(':')
eq('foo', api.nvim_cmd({ cmd = 'echo', args = { '"foo"' } }, { output = true }))
end)
it('sets correct script context', function()

View File

@@ -814,6 +814,42 @@ describe('startup', function()
]])
end)
it("default 'diffopt' is applied with -d", function()
clear({
args = {
'-d',
'test/functional/fixtures/diff/startup_old.txt',
'test/functional/fixtures/diff/startup_new.txt',
'--cmd',
'set laststatus=0',
},
})
local screen = Screen.new(80, 24)
screen:expect([[
{7:+ }{13:^+-- 15 lines: package main············}│{7:+ }{13:+-- 15 lines: package main···········}|
{7: } │{7: } |
{7: }func printCharacters(str string) strin│{7: }func printCharacters(str string) stri|
{7: } return str │{7: } return str |
{7: }} │{7: }} |
{7: } │{7: } |
{7: }func main() { │{7: }func main() { |
{7: }{23:--------------------------------------}│{7: }{22: hello := "Hello, World!" }|
{7: }{23:--------------------------------------}│{7: }{22: }|
{7: }{4: fmt.Print}{27:Ln}{4:(compressString("aa}│{7: }{4: fmt.Print(compressString("aaa}|
{7: }{4: fmt.Print}{27:Ln}{4:(compressString("go}│{7: }{4: fmt.Print(compressString("gol}|
{7: }{4: fmt.Print}{27:Ln}{4:(removeDuplicate("a}│{7: }{4: fmt.Print(removeDuplicate("aa}|
{7: }{4: fmt.Print}{27:Ln}{4:(reverseAndDouble("}│{7: }{4: fmt.Print(reverseAndDouble("a}|
{7: }{4: fmt.Print}{27:Ln}{4:(printCharacters("g}│{7: }{4: fmt.Print(printCharacters("go}|
{7: }{23:--------------------------------------}│{7: }{22: }|
{7: }{23:--------------------------------------}│{7: }{22: fmt.Println(hello) }|
{7: }} │{7: }} |
{1:~ }│{1:~ }|*6
|
]])
command('let &diffopt = &diffopt')
screen:expect_unchanged()
end)
it('does not crash if --embed is given twice', function()
clear { args = { '--embed' } }
assert_alive()

View File

@@ -0,0 +1,31 @@
package main
import "fmt"
func compressString(str string) string {
return str
}
func removeDuplicate(str string) string {
return str
}
func reverseAndDouble(str string) string {
return str
}
func printCharacters(str string) string {
return str
}
func main() {
hello := "Hello, World!"
fmt.Print(compressString("aaaabbccgh"))
fmt.Print(compressString("golang"))
fmt.Print(removeDuplicate("aabccchbbccaaa"))
fmt.Print(reverseAndDouble("abcdfgh"))
fmt.Print(printCharacters("golang"))
fmt.Println(hello)
}

View File

@@ -0,0 +1,27 @@
package main
import "fmt"
func compressString(str string) string {
return str
}
func removeDuplicate(str string) string {
return str
}
func reverseAndDouble(str string) string {
return str
}
func printCharacters(str string) string {
return str
}
func main() {
fmt.PrintLn(compressString("aaaabbccgh"))
fmt.PrintLn(compressString("golang"))
fmt.PrintLn(removeDuplicate("aabccchbbccaaa"))
fmt.PrintLn(reverseAndDouble("abcdfgh"))
fmt.PrintLn(printCharacters("golang"))
}

View File

@@ -731,6 +731,111 @@ describe('search cmdline', function()
feed('<Esc>')
screen:expect(s)
end)
-- oldtest: Test_incsearch_delimiter_ctrlg()
it('incsearch does not include trailing delimiter', function()
screen:try_resize(20, 6)
exec([[
set incsearch hls
call setline(1, ["1 vim inc", "2 vim /", "3 vim /", "4 vim ?", "5 vim ?"])
normal gg
redraw
]])
feed('/')
poke_eventloop()
feed('v')
poke_eventloop()
feed('i')
poke_eventloop()
feed('m')
poke_eventloop()
feed(' ')
poke_eventloop()
feed('/')
poke_eventloop()
feed('<C-G>')
screen:expect([[
1 {10:vim }inc |
2 {2:vim }/ |
3 {10:vim }/ |
4 {10:vim }? |
5 {10:vim }? |
/vim /^ |
]])
feed('<Esc>')
command('5')
feed('?')
poke_eventloop()
feed('v')
poke_eventloop()
feed('i')
poke_eventloop()
feed('m')
poke_eventloop()
feed(' ')
poke_eventloop()
feed('?')
poke_eventloop()
feed('<C-T>')
screen:expect([[
1 {10:vim }inc |
2 {10:vim }/ |
3 {2:vim }/ |
4 {10:vim }? |
5 {10:vim }? |
?vim ?^ |
]])
feed('<Esc>')
feed('/')
poke_eventloop()
feed('v')
poke_eventloop()
feed('i')
poke_eventloop()
feed('m')
poke_eventloop()
feed(' ')
poke_eventloop()
feed('\\/')
poke_eventloop()
feed('<C-G>')
screen:expect([[
1 vim inc |
2 {10:vim /} |
3 {2:vim /} |
4 vim ? |
5 vim ? |
/vim \/^ |
]])
feed('<Esc>')
command('5')
feed('?')
poke_eventloop()
feed('v')
poke_eventloop()
feed('i')
poke_eventloop()
feed('m')
poke_eventloop()
feed(' ')
poke_eventloop()
feed('\\?')
poke_eventloop()
feed('<C-T>')
screen:expect([[
1 vim inc |
2 vim / |
3 vim / |
4 {10:vim ?} |
5 {2:vim ?} |
?vim \?^ |
]])
feed('<Esc>')
end)
end)
describe('Search highlight', function()

View File

@@ -14,6 +14,8 @@ local feed = n.feed
local expect_events = t.expect_events
local write_file = t.write_file
local dedent = t.dedent
local matches = t.matches
local pcall_err = t.pcall_err
local origlines = {
'original line 1',
@@ -1384,3 +1386,140 @@ describe('lua: nvim_buf_attach on_bytes', function()
do_both(false)
end)
end)
describe('nvim_buf_attach on_detach', function()
it('called before buf_freeall autocommands', function()
exec_lua(function()
vim.api.nvim_create_autocmd({ 'BufUnload', 'BufDelete', 'BufWipeout' }, {
callback = function(args)
table.insert(
_G.events,
('%s: %d %s'):format(
args.event,
args.buf,
tostring(vim.api.nvim_buf_is_loaded(args.buf))
)
)
end,
})
function _G.on_detach(_, b)
table.insert(
_G.events,
('on_detach: %d %s'):format(b, tostring(vim.api.nvim_buf_is_loaded(b)))
)
end
_G.events = {}
vim.cmd 'new'
vim.bo.bufhidden = 'wipe'
vim.api.nvim_buf_attach(0, false, { on_detach = _G.on_detach })
vim.cmd 'quit!'
end)
eq(
{ 'on_detach: 2 true', 'BufUnload: 2 true', 'BufDelete: 2 true', 'BufWipeout: 2 true' },
exec_lua('return _G.events')
)
eq(false, api.nvim_buf_is_valid(2))
exec_lua(function()
_G.events = {}
local buf = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_attach(buf, false, { on_detach = _G.on_detach })
vim.api.nvim_buf_delete(buf, { force = true })
end)
-- Was unlisted, so no BufDelete.
eq(
{ 'on_detach: 3 true', 'BufUnload: 3 true', 'BufWipeout: 3 true' },
exec_lua('return _G.events')
)
eq(false, api.nvim_buf_is_valid(3))
exec_lua(function()
_G.events = {}
vim.api.nvim_buf_attach(1, false, { on_detach = _G.on_detach })
vim.api.nvim_create_autocmd('BufUnload', {
buffer = 1,
once = true,
callback = function()
vim.api.nvim_buf_attach(1, false, {
on_detach = function(...)
vim.fn.bufload(1) -- Leaks the memfile it were to run inside free_buffer_stuff.
return _G.on_detach(...)
end,
})
table.insert(_G.events, 'local BufUnload')
end,
})
vim.cmd 'edit asdf' -- Reuses buffer 1.
end)
-- on_detach shouldn't run after autocommands when reusing a buffer (in free_buffer_stuff), even
-- if those autocommands registered it, as curbuf may be in a semi-unloaded state at that point.
eq({
'on_detach: 1 true',
'BufUnload: 1 true',
'local BufUnload',
'BufDelete: 1 true',
'BufWipeout: 1 true',
}, exec_lua('return _G.events'))
exec_lua(function()
_G.events = {}
vim.api.nvim_buf_attach(0, false, { on_detach = _G.on_detach })
vim.cmd 'edit'
end)
-- Re-edit buffer; on_detach is called.
eq({ 'on_detach: 1 true', 'BufUnload: 1 true' }, exec_lua('return _G.events'))
eq(true, api.nvim_buf_is_valid(1))
exec_lua(function()
vim.cmd '%bwipeout!'
vim.bo.modified = true
_G.events = {}
vim.api.nvim_buf_attach(0, false, { on_detach = _G.on_detach })
vim.api.nvim_buf_delete(0, { force = true })
end)
-- on_detach must still be first when wiping the last buffer if it's listed and non-reusable.
-- Previously: BufUnload → BufDelete → on_detach → BufWipeout.
eq(
{ 'on_detach: 4 true', 'BufUnload: 4 true', 'BufDelete: 4 true', 'BufWipeout: 4 false' },
exec_lua('return _G.events')
)
end)
it('disallows splitting', function()
command('new | setlocal bufhidden=wipe')
local buf = api.nvim_get_current_buf()
exec_lua(function()
vim.api.nvim_buf_attach(0, false, {
on_detach = function()
-- Used to allow opening more views into a closing buffer, resulting in open windows to an
-- unloaded buffer.
vim.cmd [=[execute "normal! \<C-W>s"]=]
end,
})
end)
matches('E1159: Cannot split a window when closing the buffer$', pcall_err(command, 'quit!'))
eq({}, fn.win_findbuf(buf))
eq(false, api.nvim_buf_is_valid(buf))
end)
end)
it('nvim_buf_attach from buf_freeall autocommands does not leak', function()
exec_lua(function()
local b = vim.api.nvim_create_buf(true, true)
vim.api.nvim_create_autocmd('BufWipeout', {
buffer = b,
once = true,
callback = function()
vim.api.nvim_buf_attach(b, false, {})
_G.autocmd_fired = true
end,
})
vim.api.nvim_buf_delete(b, { force = true })
end)
eq(true, exec_lua('return _G.autocmd_fired'))
end)

View File

@@ -339,6 +339,15 @@ describe('vim.iter', function()
local s = 'abcdefghijklmnopqrstuvwxyz'
eq('z', vim.iter(vim.split(s, '')):last())
eq('z', vim.iter(vim.gsplit(s, '')):last())
eq(
nil,
vim
.iter({ 1, 2, 3, 4, 5 })
:filter(function()
return false
end)
:last()
)
end)
it('enumerate()', function()

View File

@@ -16,6 +16,7 @@ local feed = n.feed
local assert_alive = n.assert_alive
local NIL = vim.NIL
local eq = t.eq
local matches = t.matches
before_each(clear)
@@ -266,6 +267,8 @@ describe('luaeval()', function()
return true
]])
-- v:errmsg is set properly #35554
matches(': dead function\n', api.nvim_get_vvar('errmsg'))
end)
it('should handle passing functions around', function()

View File

@@ -240,6 +240,23 @@ describe('vim.snippet', function()
eq({ 'public function foo() {', '\t', '}' }, buf_lines(0))
end)
it('does not change the chosen text when jumping back to a choice tabstop', function()
test_expand_success(
{ '${1|public,protected,private|} function ${2:name}() {', '\t$0', '}' },
{ ' function name() {', '\t', '}' }
)
wait_for_pum()
feed('<C-n><C-y><Tab>')
poke_eventloop()
feed('<S-Tab>')
poke_eventloop()
wait_for_pum()
feed('<Tab>')
poke_eventloop()
feed('foo')
eq({ 'protected function foo() {', '\t', '}' }, buf_lines(0))
end)
it('jumps through adjacent tabstops', function()
test_expand_success(
{ 'for i=1,${1:to}${2:,step} do\n\t$3\nend' },
@@ -249,7 +266,11 @@ describe('vim.snippet', function()
feed('<Tab>')
poke_eventloop()
feed(',2')
eq({ 'for i=1,10,2 do', '\t', 'end' }, buf_lines(0))
-- Make sure changes on previous tabstops does not change following ones
feed('<S-Tab>')
poke_eventloop()
feed('20')
eq({ 'for i=1,20,2 do', '\t', 'end' }, buf_lines(0))
end)
it('updates snippet state when built-in completion menu is visible', function()

View File

@@ -67,6 +67,14 @@ describe(':checkhealth', function()
assert_alive()
end)
it('cmdline completion works with multiple args #35054', function()
clear()
n.feed(':checkhealth vim.ls<Tab>')
eq('checkhealth vim.lsp', fn.getcmdline())
n.feed(' vim.prov<Tab>')
eq('checkhealth vim.lsp vim.provider', fn.getcmdline())
end)
it('vim.g.health', function()
clear {
args_rm = { '-u' },

View File

@@ -267,6 +267,14 @@ describe('vim.lsp.util', function()
eq(56, opts.height)
end)
it('title with winborder option #35179', function()
local opts = exec_lua(function()
vim.o.winborder = 'single'
return vim.lsp.util.make_floating_popup_options(100, 100, { title = 'Title' })
end)
eq('Title', opts.title)
end)
end)
end)

View File

@@ -593,32 +593,35 @@ describe('LSP', function()
exec_lua(create_server_definition)
local result = exec_lua(function()
local server = _G._create_server()
local bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_set_current_buf(bufnr)
local detach_called = false
local bufnr1 = vim.api.nvim_create_buf(false, true)
local bufnr2 = vim.api.nvim_create_buf(false, true)
local detach_called1 = false
local detach_called2 = false
vim.api.nvim_create_autocmd('LspDetach', {
buffer = bufnr1,
callback = function()
detach_called = true
detach_called1 = true
end,
})
local client_id = vim.lsp.start({ name = 'detach-dummy', cmd = server.cmd })
assert(client_id, 'lsp.start must return client_id')
vim.api.nvim_create_autocmd('LspDetach', {
buffer = bufnr2,
callback = function()
detach_called2 = true
end,
})
vim.api.nvim_set_current_buf(bufnr1)
local client_id = assert(vim.lsp.start({ name = 'detach-dummy', cmd = server.cmd }))
local client = assert(vim.lsp.get_client_by_id(client_id))
local num_attached_before = vim.tbl_count(client.attached_buffers)
vim.api.nvim_buf_delete(bufnr, { force = true })
local num_attached_after = vim.tbl_count(client.attached_buffers)
return {
bufnr = bufnr,
client_id = client_id,
num_attached_before = num_attached_before,
num_attached_after = num_attached_after,
detach_called = detach_called,
}
vim.api.nvim_set_current_buf(bufnr2)
vim.lsp.start({ name = 'detach-dummy', cmd = server.cmd })
assert(vim.tbl_count(client.attached_buffers) == 2)
vim.api.nvim_buf_delete(bufnr1, { force = true })
assert(vim.tbl_count(client.attached_buffers) == 1)
vim.api.nvim_buf_delete(bufnr2, { force = true })
assert(vim.tbl_count(client.attached_buffers) == 0)
return detach_called1 and detach_called2
end)
eq(true, result ~= nil, 'exec_lua must return result')
eq(1, result.num_attached_before)
eq(0, result.num_attached_after)
eq(true, result.detach_called)
eq(true, result)
end)
it('should not re-attach buffer if it was deleted in on_init #28575', function()
@@ -3657,7 +3660,7 @@ describe('LSP', function()
}
return vim.lsp.util.convert_signature_help_to_markdown_lines(signature_help, 'cs', { ',' })
end)
local expected = { '```cs', 'TestEntity.TestEntity()', '```', 'some doc' }
local expected = { '```cs', 'TestEntity.TestEntity()', '```', '---', 'some doc' }
eq(expected, result)
end)

View File

@@ -16,16 +16,21 @@ describe(':Tutor', function()
command('Tutor')
screen = Screen.new(81, 30)
screen:set_default_attr_ids({
[0] = { foreground = Screen.colors.DarkBlue, background = Screen.colors.Gray },
[0] = { foreground = Screen.colors.Blue4, background = Screen.colors.Grey },
[1] = { bold = true },
[2] = { underline = true, foreground = tonumber('0x0088ff') },
[3] = { foreground = Screen.colors.SlateBlue },
[4] = { bold = true, foreground = Screen.colors.Brown },
[5] = { bold = true, foreground = Screen.colors.Magenta1 },
[6] = { italic = true },
[7] = { foreground = tonumber('0x00ff88'), bold = true, background = Screen.colors.Grey },
[8] = { bold = true, foreground = Screen.colors.Blue },
[9] = { foreground = Screen.colors.Magenta1 },
[10] = { foreground = tonumber('0xff2000'), bold = true },
[11] = { foreground = tonumber('0xff2000'), bold = true, background = Screen.colors.Grey },
[12] = { foreground = tonumber('0x6a0dad') },
})
end)
it('applies {unix:…,win:…} transform', function()
local expected = is_os('win')
and [[
@@ -134,62 +139,150 @@ describe(':Tutor', function()
feed(':983<CR>zt')
screen:expect(expected)
end)
end)
describe(':Tutor tutor', function()
local screen --- @type test.functional.ui.screen
before_each(function()
clear({ args = { '--clean' } })
command('set cmdheight=0')
command('Tutor tutor')
screen = Screen.new(81, 30)
screen:set_default_attr_ids({
[0] = { foreground = Screen.colors.DarkBlue, background = Screen.colors.Gray },
[1] = { bold = true },
[2] = { underline = true, foreground = tonumber('0x0088ff') },
[3] = { foreground = Screen.colors.SlateBlue },
[4] = { bold = true, foreground = Screen.colors.Brown },
[5] = { bold = true, foreground = Screen.colors.Magenta1 },
[6] = { italic = true },
[7] = { foreground = tonumber('0x00ff88'), bold = true, background = Screen.colors.Grey },
[8] = { bold = true, foreground = Screen.colors.Blue1 },
})
end)
it('applies interactive marks', function()
feed(':216<CR>zt')
it("removing a line doesn't affect highlight/mark of other lines", function()
-- Do lesson 2.6
feed(':294<CR>zt')
screen:expect([[
{0: }{3:^###}{5: expect } |
{0: } |
{0: }"expect" lines check that the contents of the line are identical to some preset|
{0: } text |
{0: }(like in the exercises above). |
{0: } |
{0: }These elements are specified in separate JSON files like this |
{0: } |
{0: }{3:~~~ json} |
{0: }{ |
{0: } "expect": { |
{0: } "1": "This is how this line should look.", |
{0: } "2": "This is how this line should look.", |
{0: } "3": -1 |
{0: } } |
{0: }} |
{0: }{3:~~~} |
{0: } |
{0: }These files contain an "expect" dictionary, for which the keys are line numbers|
{0: } and |
{0: }the values are the expected text. A value of -1 means that the condition for th|
{0: }e line |
{0: }will always be satisfied, no matter what (this is useful for letting the user p|
{0: }lay a bit). |
{0: } |
{7:✓ }{3:This is an "expect" line that is always satisfied. Try changing it.} |
{0: } |
{0: }These files conventionally have the same name as the tutorial document with the|
{0: } .json |
{0: }extension appended (for a full example, see the file that corresponds to thi{8:@@@}|
{0: }{3:^#}{5: Lesson 2.6: OPERATING ON LINES} |
{0: } |
{0: }{1: Type }{4:dd}{1: to delete a whole line. } |
{0: } |
{0: }Due to the frequency of whole line deletion, the designers of Vi decided |
{0: }it would be easier to simply type two d's to delete a line. |
{0: } |
{0: } 1. Move the cursor to the second line in the phrase below. |
{0: } |
{0: } 2. Type {2:dd} to delete the line. |
{0: } |
{0: } 3. Now move to the fourth line. |
{0: } |
{0: } 4. Type {9:2}{4:dd} to delete two lines. |
{0: } |
{7:✓ }{3:1) Roses are red, }|
{11:✗ }{3:2) Mud is fun, }|
{7:✓ }{3:3) Violets are blue, }|
{11:✗ }{3:4) I have a car, }|
{11:✗ }{3:5) Clocks tell time, }|
{7:✓ }{3:6) Sugar is sweet }|
{7:✓ }{3:7) And so are you. }|
{0: } |
{0: }{3:#}{5: Lesson 2.7: THE UNDO COMMAND} |
{0: } |
{0: }{1: Press }{4:u}{1: to undo the last commands, }{4:U}{1: to fix a whole line. } |
{0: } |
{0: } 1. Move the cursor to the line below marked {10:✗} and place it on the first error.|
{0: } |
{0: } 2. Type {4:x} to delete the first unwanted character. |
]])
feed('<Cmd>310<CR>dd<Cmd>311<CR>2dd')
screen:expect([[
{0: }{3:#}{5: Lesson 2.6: OPERATING ON LINES} |
{0: } |
{0: }{1: Type }{4:dd}{1: to delete a whole line. } |
{0: } |
{0: }Due to the frequency of whole line deletion, the designers of Vi decided |
{0: }it would be easier to simply type two d's to delete a line. |
{0: } |
{0: } 1. Move the cursor to the second line in the phrase below. |
{0: } |
{0: } 2. Type {2:dd} to delete the line. |
{0: } |
{0: } 3. Now move to the fourth line. |
{0: } |
{0: } 4. Type {9:2}{4:dd} to delete two lines. |
{0: } |
{7:✓ }{3:1) Roses are red, }|
{7:✓ }{3:3) Violets are blue, }|
{7:✓ }{3:^6) Sugar is sweet }|
{7:✓ }{3:7) And so are you. }|
{0: } |
{0: }{3:#}{5: Lesson 2.7: THE UNDO COMMAND} |
{0: } |
{0: }{1: Press }{4:u}{1: to undo the last commands, }{4:U}{1: to fix a whole line. } |
{0: } |
{0: } 1. Move the cursor to the line below marked {10:✗} and place it on the first error.|
{0: } |
{0: } 2. Type {4:x} to delete the first unwanted character. |
{0: } |
{0: } 3. Now type {4:u} to undo the last command executed. |
{0: } |
]])
end)
it("inserting text at start of line doesn't affect highlight/sign", function()
-- Go to lesson 1.3 and make it top line in the window
feed('<Cmd>92<CR>zt')
screen:expect([[
{0: }{3:^#}{5: Lesson 1.3: TEXT EDITING: DELETION} |
{0: } |
{0: }{1: Press }{4:x}{1: to delete the character under the cursor. } |
{0: } |
{0: } 1. Move the cursor to the line below marked {10:✗}. |
{0: } |
{0: } 2. To fix the errors, move the cursor until it is on top of the |
{0: } character to be deleted. |
{0: } |
{0: } 3. Press {2:the x key} to delete the unwanted character. |
{0: } |
{0: } 4. Repeat steps 2 through 4 until the sentence is correct. |
{0: } |
{11:✗ }{3:The ccow jumpedd ovverr thhe mooon. }|
{0: } |
{0: } 5. Now that the line is correct, go on to Lesson 1.4. |
{0: } |
{0: }{1:NOTE}: As you go through this tutorial, do not try to memorize everything, |
{0: } your Neovim vocabulary will expand with usage. Consider returning to |
{0: } this tutorial periodically for a refresher. |
{0: } |
{0: }{3:#}{5: Lesson 1.4: TEXT EDITING: INSERTION} |
{0: } |
{0: }{1: Press }{12:i}{1: to insert text. } |
{0: } |
{0: } 1. Move the cursor to the first line below marked {10:✗}. |
{0: } |
{0: } 2. To make the first line the same as the second, move the cursor on top |
{0: } of the first character AFTER where the text is to be inserted. |
{0: } |
]])
-- Go to the test line and insert text at the start of the line
feed('<Cmd>105<CR>iThe <Esc>')
-- Remove redundant characters
feed('fcxfdxfvxfrxfhxfox')
-- Remove the original "The " text (not the just-inserted one)
feed('^4ldw^')
screen:expect([[
{0: }{3:#}{5: Lesson 1.3: TEXT EDITING: DELETION} |
{0: } |
{0: }{1: Press }{4:x}{1: to delete the character under the cursor. } |
{0: } |
{0: } 1. Move the cursor to the line below marked {10:✗}. |
{0: } |
{0: } 2. To fix the errors, move the cursor until it is on top of the |
{0: } character to be deleted. |
{0: } |
{0: } 3. Press {2:the x key} to delete the unwanted character. |
{0: } |
{0: } 4. Repeat steps 2 through 4 until the sentence is correct. |
{0: } |
{7:✓ }{3:^The cow jumped over the moon. }|
{0: } |
{0: } 5. Now that the line is correct, go on to Lesson 1.4. |
{0: } |
{0: }{1:NOTE}: As you go through this tutorial, do not try to memorize everything, |
{0: } your Neovim vocabulary will expand with usage. Consider returning to |
{0: } this tutorial periodically for a refresher. |
{0: } |
{0: }{3:#}{5: Lesson 1.4: TEXT EDITING: INSERTION} |
{0: } |
{0: }{1: Press }{12:i}{1: to insert text. } |
{0: } |
{0: } 1. Move the cursor to the first line below marked {10:✗}. |
{0: } |
{0: } 2. To make the first line the same as the second, move the cursor on top |
{0: } of the first character AFTER where the text is to be inserted. |
{0: } |
]])
end)
end)

View File

@@ -537,7 +537,7 @@ describe('clipboard (with fake clipboard.vim)', function()
eq('textstar', api.nvim_get_current_line())
end)
it('Block paste works correctly', function()
it('block paste works correctly', function()
insert([[
aabbcc
ddeeff
@@ -550,6 +550,20 @@ describe('clipboard (with fake clipboard.vim)', function()
ddeeddeeff
]])
end)
it('block paste computes block width correctly #35034', function()
insert('あいうえお')
feed('0<C-V>ly')
feed('P')
expect('あいあいうえお')
feed('A\nxxx\nxx<Esc>')
feed('0<C-V>kkly')
feed('P')
expect([[
あいあいあいうえお
xxx xxx
xx xx]])
end)
end)
describe('clipboard=unnamedplus', function()

View File

@@ -853,4 +853,16 @@ t2]])
command('set ft=c')
eq(foldlevels, get_fold_levels())
end)
it('no error when deleting lines at end of buffer with fml=0', function()
local screen = Screen.new(40, 2)
insert('hello')
parse('markdown')
command('set foldmethod=expr foldexpr=v:lua.vim.treesitter.foldexpr() foldminlines=0')
feed('o<Esc>dd')
screen:expect([[
^hello |
|
]])
end)
end)

View File

@@ -5,6 +5,7 @@ local Screen = require('test.functional.ui.screen')
local clear = n.clear
local insert = n.insert
local exec_lua = n.exec_lua
local eval = n.eval
local feed = n.feed
local command = n.command
local api = n.api
@@ -197,6 +198,14 @@ describe('treesitter highlighting (C)', function()
end)
-- legacy syntax highlighting is used
screen:expect(hl_grid_legacy_c)
exec_lua(function()
vim.cmd 'new | wincmd p'
vim.treesitter.start()
vim.cmd 'bdelete!'
end)
-- Does not change &syntax of the other, unrelated buffer.
eq('', eval('&syntax'))
end)
it('is updated with edits', function()

View File

@@ -1141,6 +1141,16 @@ describe('cmdline redraw', function()
]])
command('redraw')
screen:expect_unchanged()
command('set keymap=dvorak')
feed('<C-^>')
command('redraw')
screen:expect_unchanged()
feed('<C-^>')
command('set keymap&')
command('redraw')
screen:expect_unchanged()
end)
it('substitute confirm prompt does not scroll', function()

View File

@@ -1249,6 +1249,7 @@ end)
it('diff updates line numbers below filler lines', function()
local screen = Screen.new(40, 14)
exec([[
set diffopt=internal,filler,closeoff
call setline(1, ['a', 'a', 'a', 'y', 'b', 'b', 'b', 'b', 'b'])
vnew
call setline(1, ['a', 'a', 'a', 'x', 'x', 'x', 'b', 'b', 'b', 'b', 'b'])
@@ -1384,6 +1385,7 @@ end)
it("'relativenumber' doesn't draw beyond end of window in diff mode #29403", function()
local screen = Screen.new(60, 12)
command('set diffopt=internal,filler,closeoff')
command('set relativenumber')
feed('10aa<CR><Esc>gg')
command('vnew')

View File

@@ -9492,6 +9492,220 @@ describe('float window', function()
|
]])
end
--
-- Cursor visibility:
--
-- Cursor is not visible in a hide=true floating window.
api.nvim_set_current_win(win)
if multigrid then
screen:expect({
grid = [[
## grid 1
[2:----------------------------------------]|*6
[3:----------------------------------------]|
## grid 2
|
{0:~ }|*5
## grid 3
|
## grid 4 (hidden)
{1: }|
{2:~ }|
]], win_viewport={
[2] = {win = 1000, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
[4] = {win = 1001, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
}, win_viewport_margins={
[2] = {
bottom = 0,
left = 0,
right = 0,
top = 0,
win = 1000
},
[4] = {
bottom = 0,
left = 0,
right = 0,
top = 0,
win = 1001
}
}
})
else
screen:expect({
grid = [[
|
{0:~ }|*5
|
]]
})
end
-- Show cursor if cmdline is entered while curwin is a hide=true floating window.
feed(':')
if multigrid then
screen:expect({
grid = [[
## grid 1
[2:----------------------------------------]|*6
[3:----------------------------------------]|
## grid 2
|
{0:~ }|*5
## grid 3
:^ |
## grid 4 (hidden)
{1: }|
{2:~ }|
]], win_viewport={
[2] = {win = 1000, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
[4] = {win = 1001, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
}, win_viewport_margins={
[2] = {
bottom = 0,
left = 0,
right = 0,
top = 0,
win = 1000
},
[4] = {
bottom = 0,
left = 0,
right = 0,
top = 0,
win = 1001
}
}
})
else
screen:expect({
grid = [[
|
{0:~ }|*5
:^ |
]]
})
end
command('set keymap=dvorak')
feed('<C-^>')
screen:expect_unchanged()
feed('<C-^>')
command('set keymap&')
screen:expect_unchanged()
feed('<ESC>')
-- Show cursor after switching to a normal window (hide=false).
api.nvim_set_current_win(cwin)
if multigrid then
screen:expect({
grid = [[
## grid 1
[2:----------------------------------------]|*6
[3:----------------------------------------]|
## grid 2
^ |
{0:~ }|*5
## grid 3
|
## grid 4 (hidden)
{1: }|
{2:~ }|
]], win_viewport={
[2] = {win = 1000, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
[4] = {win = 1001, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
}, win_viewport_margins={
[2] = {
bottom = 0,
left = 0,
right = 0,
top = 0,
win = 1000
},
[4] = {
bottom = 0,
left = 0,
right = 0,
top = 0,
win = 1001
}
}
})
else
screen:expect({
grid = [[
^ |
{0:~ }|*5
|
]]
})
end
api.nvim_set_current_win(win)
local win1 = api.nvim_open_win(buf, false, {relative='editor', width=4, height=4, row=1, col=2})
api.nvim_set_current_win(win1)
if multigrid then
screen:expect({
grid = [[
## grid 1
[2:----------------------------------------]|*6
[3:----------------------------------------]|
## grid 2
|
{0:~ }|*5
## grid 3
|
## grid 4 (hidden)
{1: }|
{2:~ }|
## grid 5
{1:^ }|
{2:~ }|*3
]], float_pos={
[5] = {1002, "NW", 1, 1, 2, true, 50};
}, win_viewport={
[2] = {win = 1000, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
[4] = {win = 1001, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
[5] = {win = 1002, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
}, win_viewport_margins={
[2] = {
bottom = 0,
left = 0,
right = 0,
top = 0,
win = 1000
},
[4] = {
bottom = 0,
left = 0,
right = 0,
top = 0,
win = 1001
},
[5] = {
bottom = 0,
left = 0,
right = 0,
top = 0,
win = 1002
}
}
})
else
screen:expect({
grid = [[
|
{0:~ }{1:^ }{0: }|
{0:~ }{2:~ }{0: }|*3
{0:~ }|
|
]]
})
end
api.nvim_win_close(win1, true)
-- check window jump with hide
feed('<C-W><C-W>')
-- should keep on current window

View File

@@ -4130,7 +4130,7 @@ describe('builtin popupmenu', function()
]])
end
-- not rightleft on the cmdline
-- oldtest: Test_wildmenu_pum_rightleft()
feed('<esc>:sign ')
if multigrid then
screen:expect({
@@ -4154,9 +4154,8 @@ describe('builtin popupmenu', function()
]],
}
end
-- oldtest: Test_wildmenu_pum_rightleft()
feed('<tab>')
-- Not rightleft on the cmdline.
feed('<Tab>')
if multigrid then
screen:expect({
grid = [[
@@ -4195,6 +4194,15 @@ describe('builtin popupmenu', function()
]],
}
end
-- Behavior is the same when using 'keymap'.
feed('<Esc>')
command('set keymap=dvorak')
-- ";gul" -> "sign" when using Dvorak keymap.
feed(':<C-^>;gul <Tab>')
screen:expect_unchanged(true)
feed('<Esc>')
command('set keymap&')
end)
it('with rightleft vsplits', function()

View File

@@ -365,7 +365,7 @@ for _, v in ipairs(ext_keys) do
expect_keys[v] = true
end
--- @class test.function.ui.screen.Expect
--- @class test.functional.ui.screen.Expect
---
--- Expected screen state (string). Each line represents a screen
--- row. Last character of each row (typically "|") is stripped.
@@ -460,7 +460,7 @@ end
--- or keyword args (supports more options):
--- screen:expect({ grid=[[...]], cmdline={...}, condition=function() ... end })
---
--- @param expected string|function|test.function.ui.screen.Expect
--- @param expected string|function|test.functional.ui.screen.Expect
--- @param attr_ids? table<integer,table<string,any>>
function Screen:expect(expected, attr_ids, ...)
--- @type string, fun()

View File

@@ -169,6 +169,17 @@ func Test_bnext_bprev_help()
b XHelp1
blast | call assert_equal(b4, bufnr())
" Cycling through help buffers works even if they aren't listed.
b XHelp1
setlocal nobuflisted
bnext | call assert_equal(b3, bufnr())
bnext | call assert_equal(b1, bufnr())
bprev | call assert_equal(b3, bufnr())
setlocal nobuflisted
bprev | call assert_equal(b1, bufnr())
bprev | call assert_equal(b3, bufnr())
bnext | call assert_equal(b1, bufnr())
%bwipe!
endfunc
@@ -598,4 +609,31 @@ func Test_closed_buffer_still_in_window()
%bw!
endfunc
" Cursor position should be restored when switching to a buffer previously
" viewed in a window, regardless of whether it's visible in another one.
func Test_switch_to_previously_viewed_buffer()
set nostartofline
new Xviewbuf
call setline(1, range(1, 200))
let oldwin = win_getid()
vsplit
call cursor(100, 3)
edit Xotherbuf
buffer Xviewbuf
call assert_equal([0, 100, 3, 0], getpos('.'))
exe win_id2win(oldwin) .. 'close'
setlocal bufhidden=hide
call cursor(200, 3)
edit Xotherbuf
buffer Xviewbuf
call assert_equal([0, 200, 3, 0], getpos('.'))
bwipe! Xotherbuf
bwipe! Xviewbuf
set startofline&
endfunc
" vim: shiftwidth=2 sts=2 expandtab

View File

@@ -158,6 +158,33 @@ func Test_appendbufline()
exe "bwipe! " . b
endfunc
func Test_appendbufline_no_E315()
let after = [
\ 'set stl=%f ls=2',
\ 'new',
\ 'let buf = bufnr("%")',
\ 'quit',
\ 'vsp',
\ 'exec "buffer" buf',
\ 'wincmd w',
\ 'call appendbufline(buf, 0, "abc")',
\ 'redraw',
\ 'while getbufline(buf, 1)[0] =~ "^\\s*$"',
\ ' sleep 10m',
\ 'endwhile',
\ 'au VimLeavePre * call writefile([v:errmsg], "Xerror")',
\ 'au VimLeavePre * call writefile(["done"], "Xdone")',
\ 'qall!',
\ ]
if !RunVim([], after, '--clean')
return
endif
call assert_notmatch("^E315:", readfile("Xerror")[0])
call assert_equal("done", readfile("Xdone")[0])
call delete("Xerror")
call delete("Xdone")
endfunc
func Test_deletebufline()
new
let b = bufnr('%')

View File

@@ -1204,6 +1204,10 @@ func Test_cmdline_complete_various()
call feedkeys(":ka\<C-A>\<C-B>\"\<CR>", 'xt')
call assert_equal("\"ka\<C-A>", @:)
" completion for :keepmarks command
call feedkeys(":kee edi\<C-A>\<C-B>\"\<CR>", 'xt')
call assert_equal("\"kee edit", @:)
" completion for short version of the :s command
call feedkeys(":sI \<C-A>\<C-B>\"\<CR>", 'xt')
call assert_equal("\"sI \<C-A>", @:)
@@ -4276,6 +4280,28 @@ func Test_term_option()
let &cpo = _cpo
endfunc
func Test_ex_command_completion()
" required for :*
" set cpo+=*
let list = filter(getcompletion('', 'command'), 'exists(":" . v:val) == 0')
" :++ and :-- are only valid in Vim9 script context, so they can be ignored
" call assert_equal(['++', '--'], sort(list))
call assert_equal([], sort(list))
call assert_equal(2, exists(':k'))
call assert_equal(0, exists(':ke'))
call assert_equal(1, exists(':kee'))
call assert_equal(1, exists(':keep'))
call assert_equal(1, exists(':keepm'))
call assert_equal(1, exists(':keepma'))
call assert_equal(1, exists(':keepmar'))
call assert_equal(1, exists(':keepmark'))
call assert_equal(2, exists(':keepmarks'))
call assert_equal(2, exists(':keepalt'))
call assert_equal(2, exists(':keepjumps'))
call assert_equal(2, exists(':keeppatterns'))
set cpo-=*
endfunc
func Test_cd_bslash_completion_windows()
CheckMSWindows
let save_shellslash = &shellslash

View File

@@ -0,0 +1,59 @@
" Test for all command modifiers in
let s:luaeval_cmdmods =<< trim END
vim.iter(loadfile('../../../src/nvim/ex_cmds.lua')()):map(function(cmd)
if cmd.func == 'ex_wrongmodifier' or cmd.command == 'hide' then
return cmd.command
else
return nil
end
end):totable()
END
let s:cmdmods = []
func s:get_cmdmods()
if empty(s:cmdmods)
let s:cmdmods = luaeval(s:luaeval_cmdmods->join("\n"))
endif
return s:cmdmods
endfunc
func Test_keep_cmdmods_names()
call assert_equal('k', fullcommand(':k'))
call assert_equal('k', fullcommand(':ke'))
call assert_equal('keepmarks', fullcommand(':kee'))
call assert_equal('keepmarks', fullcommand(':keep'))
call assert_equal('keepmarks', fullcommand(':keepm'))
call assert_equal('keepmarks', fullcommand(':keepma'))
call assert_equal('keepmarks', fullcommand(':keepmar'))
call assert_equal('keepmarks', fullcommand(':keepmark'))
call assert_equal('keepmarks', fullcommand(':keepmarks'))
call assert_equal('keepalt', fullcommand(':keepa'))
call assert_equal('keepalt', fullcommand(':keepal'))
call assert_equal('keepalt', fullcommand(':keepalt'))
call assert_equal('keepjumps', fullcommand(':keepj'))
call assert_equal('keepjumps', fullcommand(':keepju'))
call assert_equal('keepjumps', fullcommand(':keepjum'))
call assert_equal('keepjumps', fullcommand(':keepjump'))
call assert_equal('keepjumps', fullcommand(':keepjumps'))
call assert_equal('keeppatterns', fullcommand(':keepp'))
call assert_equal('keeppatterns', fullcommand(':keeppa'))
call assert_equal('keeppatterns', fullcommand(':keeppat'))
call assert_equal('keeppatterns', fullcommand(':keeppatt'))
call assert_equal('keeppatterns', fullcommand(':keeppatte'))
call assert_equal('keeppatterns', fullcommand(':keeppatter'))
call assert_equal('keeppatterns', fullcommand(':keeppattern'))
call assert_equal('keeppatterns', fullcommand(':keeppatterns'))
endfunc
func Test_cmdmod_completion()
for mod in s:get_cmdmods()
let cmd = $'{mod} ed'
if mod == 'filter'
let cmd = $'{mod} /pattern/ ed'
endif
call assert_equal('edit', getcompletion(cmd, 'cmdline')[0])
endfor
endfunc
" vim: shiftwidth=2 sts=2 expandtab

View File

@@ -105,6 +105,13 @@ func Test_exists()
" Internal command with a count
call assert_equal(0, exists(':3buffer'))
" Valid internal command (full match)
call assert_equal(2, exists(':k'))
" Non-existing internal command (':k' with arg 'e')
call assert_equal(0, exists(':ke'))
" Valid internal command (partial match)
call assert_equal(1, exists(':kee'))
" User defined command (full match)
command! MyCmd :echo 'My command'
call assert_equal(2, exists(':MyCmd'))

Some files were not shown because too many files have changed in this diff Show More