Compare commits

...

77 Commits

Author SHA1 Message Date
a73904168a NVIM 0.11.2
This is a maintenance release, focusing on bug fixes. Some enhancements related
to vim.lsp.enable are also included.

FEATURES
--------------------------------------------------------------------------------
- 4e43264cd3 lsp: vim.lsp.is_enabled() #33703
- c4b9bdbdf4 lsp: start/stop LSPs as necessary during vim.lsp.enable() #33702
- 216c56b7e0 lsp: detach LSP clients when 'filetype' changes #33707
- 533ec6d492 lsp: `root_markers` can control priority
- ad7211ac8f checkhealth: trigger FileType event after showing report
- f25f6c8d13 health: summary in section heading #33388

FIXES
--------------------------------------------------------------------------------
- 710d561f88 lsp: don't eagerly enable LSP configs during startup #33762
- 0ed06d7271 lsp: check if client is stopping before reuse #33796
- f184c562c5 lsp: detect if Client:request resolved synchronously #33624
- b868257ef9 lsp: fix error with InsertReplaceEdit events #33973
- 6b69b3217b lsp: improper diagnostic end_col computation
- e512c9589e lsp: improve error completion message #33812
- 47686a1454 lsp: only auto-detach lsp.config clients #33834
- 901eeeb2e6 lsp: use `bufnr` when getting clients in `symbols_to_items` (#33760)
- a242902430 :print: don't use schar_from_ascii() for illegal byte (#34046)
- 4b6caa913c cmdline: do not move UI cursor when entering cmdline #33729
- fd8e0ae62d decor: extmark highlight not applied (#33858)
- 81233a41d7 display: adjust setting winline info for concealed lines (#33717)
- 4cb2b19197 folds: adjust filler text drawing for transparent folds
- bdd8498ed7 folds: avoid unnecessary loop with horizontal scrolling (#33932)
- 32842b0ee3 health: checkhealth float opens extra empty buffer #33648
- dc87a0d80a lua: vim.validate `message` param #33675
- 334d8f506f move: consume skipcol before revealing filler lines (#34143)
- 6a87b57c06 runtime: 'includeexpr' with non-Nvim-style Lua modules #33867
- 2b2a3449f7 runtime: conceal paths in help, man ToC loclist #33764
- 3db39ed21f runtime: cpoptions is reset in Lua file #33671
- cefc91a82e system: don't treat NUL at start as no input (#34167)
- 8daffd0cbb terminal: check size when switching buffers
- 0db89468d7 termkey: out-of-bounds write in array #33868
- 6563c6bde7 treesitter: close `:InspectTree` with `q`
- 5c6ee251a6 treesitter: eliminate flicker for single windows #33842
- 3b3cf1d7ef treesitter: invalidate conceal_lines marks (#33832)
- 58460e2d52 treesitter: parser metadata annotations
- 034d3c8f6c treesitter: proper tree `contains()` logic with combined injections
- 560c6ca947 trust: support for trusting directories #33735
- 12ae7aa846 tui: clear primary device callback before invoking it (#34032)
- 4296511087 tui: don't process UI events when suspending or stopping (#33710)
- cf73f21c07 tui: don't try to add unsupported modifiers (#33799)
- 465c181581 tui: forward C0 control codes literally (#33759)
- 0c2bf55e90 tutor: l:lang is undefined
- 3a0d37681f vim.system: improve error message when cwd does not exist
- 9b3426691c window: skip unfocusable and hidden floats with "{count}<C-W>w" #33810

VIM PATCHES
--------------------------------------------------------------------------------
- 9965cfb84c 9.1.1361: [security]: possible use-after-free when closing a buffer (#33820)
- 3c102303f5 9.1.1375: [security]: possible heap UAF with quickfix dummy buffer
- 1921dda92e 3704b5b: runtime(tutor): improve tutor.vim plugin and filetype plugin
- 4e5af2f5a6 5a8f995: runtime(doc): remove outdated Contribution section in pi_tutor (#34094)
- 3273c595c0 829eda7: runtime(new-tutor): update tutor and correct comandline completion
- 3e83a33108 9.1.1297: Ctrl-D scrolling can get stuck #33453
- 6417ba0c2f 9.1.1376: quickfix dummy buffer may remain as dummy buffer
- 30fa1c5f8c 9.1.1380: 'eventignorewin' only checked for current buffer
- 6b140ae899 9.1.1384: still some problem with the new tutors filetype plugin
- f623fad9c4 9.1.1385: inefficient loop for 'nosmoothscroll' scrolling (#33992)
- d50f71d2f1 9.1.1387: memory leak when buflist_new() fails to reuse curbuf
- 27abf5c81b 9.1.1388: Scrolling one line too far with 'nosmoothscroll' page scrolling (#34023)
- 917f496f75 9.1.1395: search_stat not reset when pattern differs in case (#34058)
- b07bffdc47 9.1.1402: multi-byte mappings not properly stored in session file (#34131)
- ff83c712cf 9.1.1405: tests: no test for mapping with special keys in session file (#34146)
- d1ca551983 9.1.1407: Can't use getpos('v') in OptionSet when using setbufvar() (#34177)

DOCUMENTATION
--------------------------------------------------------------------------------
- e5e69f758d add missing change to getcharstr() signature (#33797)
- 95ee908c40 backport #33549 and #33524 to 0.11 (#33678)
- 472d41b5b6 default mappings #33706
- 3a4d3934c4 fixups (#33815)
- fa292e6f61 lsp, emoji, startup #33683
- 714622fb45 lsp, lua #33682
- d68d212ad4 provide example_init.lua #33524
- 968947b3c3 lua: typing for vim.fn.winlayout #33817
- e304677993 tutor: move lesson 7.2 below lesson 7.3 #33662
2025-05-30 11:39:24 +02:00
58460e2d52 fix(treesitter): parser metadata annotations
Problem: `TSLangInfo` annotation does not reflect the structure returned
by `vim.treesitter.language.inspect()`.

Solution: Move version information under new (optional since ABI 15 only)
`TSLangMetadata` field.

(cherry picked from commit f82219c490)
2025-05-29 15:28:00 +00:00
3a0d37681f fix(vim.system): improve error message when cwd does not exist
Problem:
vim.uv.spawn will emit ENOENT for either when the cmd or cwd do not
exist and does not tell you which.

Solution:
If an error occurs, check if cwd was supplied and included in the error
message if it does not exist.

(cherry picked from commit 532610388b)
2025-05-29 13:29:23 +00:00
4cb2b19197 fix(folds): adjust filler text drawing for transparent folds
Problem: Search highlighting is applied strangely to the filler text of
transparent folds, and EOL list characters are not shown.

Solution: Don't apply search highlighting to the last column of the window row
if the last text char on the line is highlighted. Display the EOL list char if
needed. Don't highlight the entire filler text when matching EOL, just highlight
the EOL list char or the first filler char.

(cherry picked from commit 66dddd8b51)
2025-05-27 09:40:55 +01:00
d1ca551983 vim-patch:9.1.1407: Can't use getpos('v') in OptionSet when using setbufvar() (#34177)
Problem:  Can't use getpos('v') in OptionSet when using setbufvar().
Solution: Don't reset Visual selection when switching to the same
          buffer (zeertzjq).

closes: vim/vim#17373

5717ee33db
(cherry picked from commit bd01bd6564)
2025-05-25 23:17:15 +00:00
5e0ef6afc7 Merge pull request #34168 from zeertzjq/backport
fix(system): don't treat NUL at start as no input (#34167)
2025-05-25 10:21:25 +08:00
cefc91a82e fix(system): don't treat NUL at start as no input (#34167) 2025-05-25 09:32:45 +08:00
334d8f506f fix(move): consume skipcol before revealing filler lines (#34143)
Problem:  When scrolling (the text) down with 'smoothscroll', filler
          lines are revealed before the text skipped with `w_skipcol`.
Solution: Check `w_skipcol` before filler lines.

(cherry picked from commit 6ce2877327)
2025-05-23 23:14:18 +00:00
ff83c712cf vim-patch:9.1.1405: tests: no test for mapping with special keys in session file (#34146)
Problem:  tests: no test for mapping with special keys in session file.
Solution: Add a special keys to an existing test.  Also test with UTF-8
          characters containing 0x80 or 0x9b bytes (zeertzjq).

closes: vim/vim#17360

9ff1e598e8
(cherry picked from commit 071dcab68f)
2025-05-23 23:04:01 +00:00
b07bffdc47 vim-patch:9.1.1402: multi-byte mappings not properly stored in session file (#34131)
Problem:  multi-byte mappings not properly stored in session file
Solution: unescape the mapping before writing out the mapping, prefer
          single-byte mapping name if possible (Miguel Barro)

closes: vim/vim#17355

5b07aff2f6

Co-authored-by: GuyBrush <miguel.barro@live.com>
(cherry picked from commit 153a910897)
2025-05-23 00:15:14 +00:00
b868257ef9 fix(lsp): fix error with InsertReplaceEdit events #33973
Problem:
Some LSPs cause the following completion error (reformatted slightly):

    Error executing vim.schedule lua callback:
    .../runtime/lua/vim/lsp/completion.lua:373
    attempt to index field 'range' (a nil value)

This is because an internal function assumes edits are either missing
or of type `TextEdit`, but there's a third [possibility][0] that's not
handled: the `InsertReplaceEdit`.

This was previously reported in at least two issues:

- https://github.com/neovim/neovim/issues/33142
- https://github.com/neovim/neovim/issues/33224

Solution:
Don't assume the edit is a `TextEdit`. This implicitly handles
`InsertReplaceEdit`s.

Also, add a test case for this, which previously caused an error.

[0]: 2c07428966/runtime/lua/vim/lsp/_meta/protocol.lua (L1099)

(cherry picked from commit 927927e143)
2025-05-22 13:50:07 +00:00
e304677993 docs(tutor): move lesson 7.2 below lesson 7.3 #33662
Problem:

- Lesson 7.3 (Cmdline Completion) teaches an important way to discover
  Nvim features. I think users should learn it before they start
  configuring Nvim
- Nvim can be configured in Lua as well, but lesson 7.2 (Configuring
  Nvim) only mentions init.vim. And I think Nvim is promoting Lua more

Solution:

- Move lesson 7.2 to be after lesson 7.3
- Lesson 7.2 should teach about init.lua

Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
(cherry picked from commit dd43eb445a)
2025-05-21 09:51:16 +00:00
4e5af2f5a6 vim-patch:5a8f995: runtime(doc): remove outdated Contribution section in pi_tutor (#34094)
Problem:  The Github repo link in the Contribution section has been
          archived for 5 years. So people who want to contribute to the
          tutor plugin should just send PR to Vim repo, similar to most
          other Vim features, so there is no need for a Contribution
          section in the plugin doc.

Solution: Replace it with an Original Author note at the beginning of
          the help document.

closes: vim/vim#17341

5a8f9958e2
(cherry picked from commit 1524868711)
2025-05-20 01:16:27 +00:00
917f496f75 vim-patch:9.1.1395: search_stat not reset when pattern differs in case (#34058)
Problem:  search_stat not reset when pattern differs in case
          (tahzibijafar)
Solution: use STRNCMP instead of MB_STRNICMP macro

There was a long standing todo comment, that using MB_STRNICMP is wrong.
So let's change it to STRNCMP() instead. Even if it not handle
multi-byte characters correctly, then Vim will rather recompute the
search stat, instead of re-using the old (and possibly wrong) value.

fixes: vim/vim#17312
closes: vim/vim#17314

670d0c1468

Co-authored-by: Christian Brabandt <cb@256bit.org>
(cherry picked from commit ec5f054dc9)
2025-05-17 00:12:13 +00:00
a242902430 fix(:print): don't use schar_from_ascii() for illegal byte (#34046)
(cherry picked from commit 6af1b7e5e8)
2025-05-16 23:03:09 +00:00
12ae7aa846 fix(tui): clear primary device callback before invoking it (#34032)
A primary device callback may set a new callback (e.g. when suspending),
so clearing it after invoking it is too late.

While at it, add missing reset of did_set_grapheme_cluster_mode in
terminfo_start().

(cherry picked from commit 17e13ce3b6)
2025-05-15 13:13:02 +00:00
27abf5c81b vim-patch:9.1.1388: Scrolling one line too far with 'nosmoothscroll' page scrolling (#34023)
Problem:  One-off error in "count" to make "w_skipcol" zero with
          'nosmoothscroll' page scrolling when last virtual line
          in a buffer line is exactly the entire window width.
          (Hirohito Higashi)
Solution: Properly compute the smallest integer value necessary
          to make "w_skipcol" zero (Luuk van Baal)

c6c72d165c
(cherry picked from commit f87b6230f1)
2025-05-15 08:24:06 +00:00
d50f71d2f1 vim-patch:9.1.1387: memory leak when buflist_new() fails to reuse curbuf
Problem:  buflist_new() leaks ffname and fails to reuse curbuf when
          autocommands from buf_freeall change curbuf. Plus, a new
          buffer is not allocated in this case, despite what the comment
          above claims.
Solution: Remove the condition so ffname is not leaked and so a new
          buffer is allocated like before v8.2.4791. It should not be
          possible for undo_ftplugin or buf_freeall autocommands to
          delete the buffer as they set b_locked, but to stay consistent
          with other uses of buf_freeall, guard against that anyway
          (Sean Dewar).

Note that buf is set to NULL if it was deleted to guard against the (rare)
possibility of messing up the "buf != curbuf" condition below if a new buffer
happens to be allocated at the same address.

closes: vim/vim#17319

0077282c82

Co-authored-by: Sean Dewar <6256228+seandewar@users.noreply.github.com>
(cherry picked from commit 6b9665a507)
2025-05-15 08:14:43 +00:00
f623fad9c4 vim-patch:9.1.1385: inefficient loop for 'nosmoothscroll' scrolling (#33992)
Problem:  Loop that ensures "w_skipcol" is zero with 'nosmoothscroll'
	  for (half)-page scrolling is inefficient.
Solution: Calculate the required "count" instead of looping until
	  "w_skipcol" is zero (Luuk van Baal).

acf0ebe8a8
(cherry picked from commit d539a952da)
---------

Co-authored-by: luukvbaal <luukvbaal@gmail.com>
Co-authored-by: zeertzjq <zeertzjq@outlook.com>
2025-05-13 10:43:25 +02:00
3e83a33108 vim-patch:9.1.1297: Ctrl-D scrolling can get stuck #33453
Problem:  cursor_correct() calculates a valid cursor position which
	  is later changed by update_topline() and causes Ctrl-D
          scrolling to be stuck (Daniel Steinberg, after v9.1.0258).
Solution: Update the valid cursor position before validating topline
          (Luuk van Baal).

c98250377d
(cherry picked from commit aa47c8efa9)
2025-05-13 07:02:50 +00:00
0c2bf55e90 fix(tutor): l:lang is undefined
Problem:
The scope `elseif $LC_MESSAGES =~ '\a\a' || $LC_MESSAGES ==# "C"` in
function `s:Locale` in file `runtime/autoload/tutor.vim` leaves a case
when l:lang is not defined.

Solution:
Define `l:lang` at function scope instead of `if` scope

(cherry picked from commit e5665754d1)
2025-05-13 02:35:41 +00:00
6b140ae899 vim-patch:9.1.1384: still some problem with the new tutors filetype plugin
Problem:  still some problem with the new tutors filetype plugin
Solution: refactor code to enable/disable tutor mode into
          tutor#EnableInteractive() function, include a test
          (Phạm Bình An)

I find it annoying that Tutor's interactive mode is always on (or debug
mode is off) even when I open a tutor file with :edit command.
I think it makes more sense to make this "interactive mode":

- Always on when it is opened with :Tutor command
- Off otherwise

For more references, see `:help` feature, it is a much better than
:Tutor, since I don't have to run `:let g:help_debug = 1` just to be able
to edit and save a help file

Therefore, I remove `g:tutor_debug`

closes: vim/vim#17299

13bea589a2

Co-authored-by: Phạm Bình An <phambinhanctb2004@gmail.com>
(cherry picked from commit e01f196e44)
2025-05-13 02:35:41 +00:00
1921dda92e vim-patch:3704b5b: runtime(tutor): improve tutor.vim plugin and filetype plugin
- Set g:tutor_debug on startup if it doesn't exist so that users can get
  cmdline completion when interactively setting it.
- set b:undo_ftplugin in filetype plugin
- set default runtime file headers

closes: vim/vim#17274

3704b5b58a

Co-authored-by: Phạm Bình An <phambinhanctb2004@gmail.com>
(cherry picked from commit 238e1d6ecc)
2025-05-13 02:35:41 +00:00
30fa1c5f8c vim-patch:9.1.1380: 'eventignorewin' only checked for current buffer
Problem:  When an autocommand executes for a non-current buffer,
          'eventignorewin' is only checked from the buffer's last
          wininfo (overwrites win_ignore in the loop), not from the
          value of 'eventignorewin' in all windows showing the buffer as
          described (after v9.1.1084)

Solution: Fix the check and don't use wininfo, as that may only contain
          windows that recently showed the buffer. Consider all the
          buffer's windows in all tabpages (Sean Dewar).

closes: vim/vim#17294

d4110e0695

Co-authored-by: Sean Dewar <6256228+seandewar@users.noreply.github.com>
(cherry picked from commit ef5c5dc99b)
2025-05-11 22:48:42 +00:00
6417ba0c2f vim-patch:9.1.1376: quickfix dummy buffer may remain as dummy buffer
Problem:  when failing to wipeout a quickfix dummy buffer, it will
          remain as a dummy buffer, despite being kept.
Solution: clear its dummy BF_DUMMY flag in this case (Sean Dewar).

closes: vim/vim#17283

270124f46a

Co-authored-by: Sean Dewar <6256228+seandewar@users.noreply.github.com>
(cherry picked from commit d95b0a5396)
2025-05-11 16:38:20 +01:00
3c102303f5 vim-patch:9.1.1375: [security]: possible heap UAF with quickfix dummy buffer
Problem:  heap use-after-free possible when autocommands switch away from the
          quickfix dummy buffer, but leave it open in a window.
Solution: close its windows first before attempting the wipe.
          (Sean Dewar)

related: vim/vim#17283

b4074ead5c

Co-authored-by: Sean Dewar <6256228+seandewar@users.noreply.github.com>
(cherry picked from commit 05dab80d8d)
2025-05-11 16:38:20 +01:00
034d3c8f6c fix(treesitter): proper tree contains() logic with combined injections
**Problem:** `LanguageTree:contains()` considers any range within the
start of the first tree and end of the last tree as "within" the
language tree. In the case of combined injections, this is problematic
because we only want to consider ranges within any of the combined trees
as "contained" (as opposed to any range within the entire range spanned
by all combined trees).

**Solution:** Use a more discriminative check in
`LanguageTree:contains()`.

(cherry picked from commit de45b8e275)
2025-05-11 07:32:19 +00:00
9965cfb84c vim-patch:9.1.1361: [security]: possible use-after-free when closing a buffer (#33820)
Problem:  [security]: Possible to open more windows into a closing
          buffer without splitting, bypassing existing "b_locked_split"
          checks and triggering use-after-free
Solution: Disallow switching to a closing buffer. Editing a closing
          buffer (via ":edit", etc.) was fixed in v9.1.0764, but add an
          error message and check just "b_locked_split", as "b_locked"
          is necessary only when the buffer shouldn't be wiped, and may
          be set for buffers that are in-use but not actually closing.
          (Sean Dewar)

closes: vim/vim#17246

6cb1c82840
(cherry picked from commit 627c648252)
2025-05-10 17:07:31 +00:00
bdd8498ed7 fix(folds): avoid unnecessary loop with horizontal scrolling (#33932)
Fix #33931

(cherry picked from commit 1d9990daac)
2025-05-10 03:21:57 +00:00
8daffd0cbb fix(terminal): check size when switching buffers
Problem: terminal not always resized when switching to its buffer.
Solution: add missing calls to terminal_check_size.

Adjust screen test for v0.11.
(cherry picked from commit e56292071a)
2025-05-09 17:49:50 +00:00
fd8e0ae62d fix(decor): extmark highlight not applied (#33858)
Problem: If the only highlight present in the buffer is an extmark, and its end
position is outside the screen, redraws that start on lines after the
first line of the mark will consider the buffer as not having any highlights,
and skip drawing the mark's highlight.
Solution: Check the updated number of decor ranges.

(cherry picked from commit 6adf48b66d)
2025-05-09 11:02:31 +00:00
a56dcbbad7 test(swapfile): don't check for line with full file path (#33896)
Wrapping can happen anywhere where in the full file path, breaking the
matching. Match a line with the "Xswaptest" line instead.

(cherry picked from commit 1b8ae4336d)
2025-05-08 02:45:56 +00:00
6a87b57c06 fix(runtime): 'includeexpr' with non-Nvim-style Lua modules #33867
Closes #33862

(cherry picked from commit db2b774a16)
2025-05-07 01:06:12 +00:00
5c6ee251a6 fix(treesitter): eliminate flicker for single windows #33842
This commit will eliminate flicker while editing one open window. It
works by querying previously calculated trees for highlight marks, in
the case that we are still asynchronously parsing in `on_win`.

It will not fully solve the case of flicker when there are multiple open
windows, since the parser will drop previously parsed injection trees
whenever it receives a call to parse a different region of the buffer.
This will require a refactor of `languagetree.lua`.

(cherry picked from commit 8d75910ef9)
2025-05-06 15:22:43 +00:00
0db89468d7 fix(termkey): out-of-bounds write in array #33868
Problem:
termkey crashes due to an out-of-bounds write in an array when it
received a CSI sequence with 17 or more arguments. This could be
observed on startup with certain terminal emulators like [RLogin], which
send a response to the `CSI c` query containing 17 parameters.

The termkey code has a boundary check, but its comparison operator is
incorrect.

Solution:
Correct the comparison operator to ensure proper boundary checking.

With this change, I have confirmed that the crash no longer occurs on
RLogin. https://github.com/kmiya-culti/RLogin

Fixes #24356

(cherry picked from commit 8707ec2644)
2025-05-06 12:54:26 +00:00
dc87a0d80a fix(lua): vim.validate message param #33675
Problem:
vim.validate does not handle `message` param.

Solution:
Add the missing logic.

(cherry picked from commit 40351bbbbe)
2025-05-05 00:09:31 +00:00
c753e70abb test(lua/secure_spec): avoid magic number (#33700)
Avoid magic number in skipping condition by moving the expected message
to a variable.

(cherry picked from commit c489b5a3e3)
2025-05-04 23:50:15 +00:00
0ed06d7271 fix(lsp): check if client is stopping before reuse #33796
Problem:
Stopping a language server and then calling vim.lsp.start() with the same name/root will return the old language server that's in the middle of shutting down. vim.lsp.start() won't return a new server until the old process has terminated.

Solution:
Introducing a client._is_stopping field that tracks the shutdown phase, preventing the client from being reused.

(cherry picked from commit 0862c1036a)
2025-05-04 22:26:30 +00:00
e512c9589e fix(lsp): improve error completion message #33812
problem: Error notifications from LSP responses were difficult to read due to
inconsistent formatting and missing critical information like client name and error codes.

solution: Implemented a more structured error notification format using pipe separators to
clearly display client name, error code (when available), and error message.

(cherry picked from commit 5c15df449a)
2025-05-04 17:38:54 +00:00
47686a1454 fix(lsp): only auto-detach lsp.config clients #33834
Problem:
enable() routine detaches clients even if they were manually started
and not managed by vim.lsp.config.

Solution:
Skip clients that aren't managed by vim.lsp.config.

(cherry picked from commit 91e116f3a6)
2025-05-04 13:39:40 +00:00
3b3cf1d7ef fix(treesitter): invalidate conceal_lines marks (#33832)
Problem:  Spliced conceal_lines marks after changing the buffer text are
          left valid, concealing lines that shouldn't be.
Solution: Set the `invalidate` extmark property.
(cherry picked from commit 9274532615)
2025-05-03 23:41:23 +00:00
472d41b5b6 docs: default mappings #33706
Problem:
Docs don't mention that `gc` is just a mapping, not
a builtin normal-mode command.

Solution:
Update docs for all "default mappings".

(cherry picked from commit 2c1f5a6aa5)
2025-05-03 23:07:06 +00:00
216c56b7e0 feat(lsp): detach LSP clients when 'filetype' changes #33707
Problem:
When the buffer 'filetype' changes, invalid or non-applicable LSP
clients are not detached.

https://github.com/neovim/neovim/issues/33443
https://github.com/neovim/nvim-lspconfig/issues/3326

Solution:
In the enable() routine, check can_start() on _existing_ clients.

(cherry picked from commit b877aa34cf)
2025-05-03 22:49:53 +00:00
968947b3c3 docs(lua): typing for vim.fn.winlayout #33817
Problem:
`any[]` means nothing, and the return value is not the same as what's
documented in the comment (eg, Lua returns `{ "row", { { "leaf", 1000 },
{ "leaf", 1001 } } }`, not `{ "row", { "leaf", 1000, "leaf", 1001 } }`)

Solution:
Create two classes (vim.fn.winlayout.leaf and vim.fn.winlayout.branch)
and one alias that links the two together.

Also: Due to LuaLS limitations, there is an empty class,
vim.fn.winlayout.empty

Signed-Off-By: VoxelPrismatic <voxelprismatic@pm.me>
(cherry picked from commit 902b689c4d)
2025-05-03 19:10:02 +00:00
9b3426691c fix(window): skip unfocusable and hidden floats with "{count}<C-W>w" #33810
Problem: Using `<C-W>w`, `<C-W>W` or the ":wincmd" variants with a count can
enter unfocusable or hidden floating windows. This is especially problematic
when using the new in-development extui, which creates many unfocusable floats
for various UI elements.

Solution: Skip unfocusable and hidden floating windows. Instead, skip to the
next focusable, non-hidden window in the current tabpage's window list. Reword
the documentation a bit (hopefully an improvement?)

(cherry picked from commit 403fcacfc1)
2025-05-03 19:02:39 +00:00
4e43264cd3 feat(lsp): vim.lsp.is_enabled() #33703
Problem:
No way to check if a LSP config is enabled without causing it to
resolve. E.g. `vim.lsp.config['…'] ~= nil` will resolve the config,
which could be an unwanted and somewhat expensive side-effect.

Solution:
Introduce `vim.lsp.is_enabled()`.

(cherry picked from commit 03d378fda6)
2025-05-03 17:45:39 +00:00
3a4d3934c4 docs: fixups (#33815)
- Add missing diagnostics virtual lines hl groups.
- Fix LSP dynamic registration example; curbuf may not actually be attached to
  the client, and it may be attached to many such buffers.

(cherry picked from commit 94bc7f47bf)
2025-05-03 17:23:08 +01:00
2b2a3449f7 fix(runtime): conceal paths in help, man ToC loclist #33764
Problem:
The check for concealing paths in TOCs in the qf syntax file fails
because the TOC tile has changed.

Solution:
Force the qf syntax file to be reloaded after the qf_toc variable
has been set, so that the it can apply the correct settings.

Using the explicit qf_toc key, already used in the syntax file, instead
of the title is less prone to breaking.

It was also already being set for man pages but it had no effect because
the syntax file had already been loaded when the variable was set.

Fixes #33733

(cherry picked from commit f048298e9a)
2025-05-03 14:33:34 +00:00
6b69b3217b fix(lsp): improper diagnostic end_col computation
**Problem:** For multiline diagnostics, the end column was improperly
calculated by checking the byte index of the character position on the
*start* line.

**Solution:** Calculate the byte index for end_col using the *end* line.

(cherry picked from commit 5d1fd4aca5)
2025-05-03 08:28:03 +00:00
cf73f21c07 fix(tui): don't try to add unsupported modifiers (#33799)
Problem:  The TUI doesn't forward a key properly when it has unsupported
          modifiers like NumLock.
Solution: Don't try to add modifiers when only unsupported modifiers are
          present.

Related #33791

(cherry picked from commit adbd33027f)
2025-05-03 04:37:40 +00:00
e5e69f758d docs: add missing change to getcharstr() signature (#33797)
(cherry picked from commit 862e676efc)
2025-05-03 00:51:46 +00:00
901eeeb2e6 fix(lsp): use bufnr when getting clients in symbols_to_items (#33760)
(cherry picked from commit 34c769dd89)
2025-05-02 21:07:05 +00:00
6563c6bde7 fix(treesitter): close :InspectTree with q
Problem: `:InspectTree` window does not follow precedent for focused
"info windows" (like `checkhealth`, `Man`, etc.).

Solution: Map `q` to `<C-w>c`.
(cherry picked from commit 5a2edc483d)
2025-05-02 20:06:26 +00:00
465c181581 fix(tui): forward C0 control codes literally (#33759)
This fixes the problem that sending a raw C0 control code to trigger a
mapping for it does not work in Terminal mode.

Note: this isn't done for 00 or 7F, as that'll be backward-incompatible.
(cherry picked from commit 4b5364b423)
2025-05-02 10:16:30 +00:00
710d561f88 fix(vim.lsp.enable): don't eagerly enable LSP configs during startup #33762
closes #33761

(cherry picked from commit 1fb0126a08)
2025-05-02 09:59:07 +00:00
81233a41d7 fix(display): adjust setting winline info for concealed lines (#33717)
Problem:  Wrong winline info after partial redraw. Setting
          `conceal_cursor_used` is unnecessarily spread out.
Solution: Rather than adjusting `wl_lastlnum` for the previous
          winline, adjust it when setting the current winline.
          Set `conceal_cursor_used` when the current window is redrawn.
(cherry picked from commit 97a6259442)
2025-05-01 12:05:15 +02:00
c4b9bdbdf4 feat(lsp): start/stop LSPs as necessary during vim.lsp.enable() #33702
Problem:
enable() could be more flexible, so that it works even if called "late".

Solution:
- enable(true) calls `doautoall nvim.lsp.enable FileType`.
- enable(false) calls `client:stop()` on matching clients.

This will be useful for e.g. :LspStop/:LspStart also.

(cherry picked from commit 4bc7bac884)
2025-05-01 00:24:08 +00:00
560c6ca947 fix(trust): support for trusting directories #33735
Problem:
Directories that are "trusted" by `vim.secure.read()`, are not detectable later
(they will prompt again). https://github.com/neovim/neovim/discussions/33587#discussioncomment-12925887

Solution:
`vim.secure.read()` returns `true` if the user trusts a directory.

Also fix other bugs:

- If `f:read('*a')` returns `nil`, we treat that as a successful read of
  the file, and hash it. `f:read` returns `nil` for directories, but
  it's also documented as returning `nil` "if it cannot read data with the
  specified format". I reworked the implementation so we explicitly
  treat directories differently. Rather than hashing `nil` to put in the
  trust database, we now put "directory" in there explicitly*.
- `vim.secure.trust` (used by `:trust`) didn't actually work for
  directories, as it would blindly read the contents of a netrw buffer
  and hash it. Now it uses the same codepath as `vim.secure.read`, and
  as a result, works correctly for directories.

(cherry picked from commit 272dba7f07)
2025-04-30 14:35:41 -07:00
4b6caa913c fix(cmdline): do not move UI cursor when entering cmdline #33729
Problem:  Cursor is still moved to curwin when entering cmdline (after d41b8d47).

Solution: Remove call to `setcursor()`.
(cherry picked from commit 0015a105ca)
2025-04-30 14:38:40 +00:00
0fbc78caa1 Merge #33734 from justinmk/release
backport: LSP root_markers, docs
2025-04-30 07:21:21 -07:00
533ec6d492 feat(lsp): root_markers can control priority
Problem:
root_markers cannot specify "equal priority filenames.

Solution:
Support nesting:

    {
      ...
      root_markers = { { ".stylua.toml", ".luarc.json" }, { ".git "} }
      ...
    }

Co-authored-by: Maria José Solano <majosolano99@gmail.com>
Co-authored-by: Gregory Anders <github@gpanders.com>
Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
2025-04-30 15:59:00 +02:00
9dfb429e93 test: drop redundant clear() #33654
followup to #33647 after overlapping merge
2025-04-30 15:58:03 +02:00
051e14d347 test: drop redundant clear() #33647
Problem:
Tests call `clear()` even though `clear_notrace()` is already called in
a `before_each()` handler, wasting precious milliseconds!

Solution:
Remove redundant `clear()` calls.
2025-04-30 15:57:55 +02:00
714622fb45 docs: lsp, lua #33682
- sort fields alphabetically.
- in the `vim.lsp.Client` docs, reference `vim.lsp.ClientConfig` instead
  of duplicating its docs.
- cleanup lots of redundant-yet-drifted field docs.
2025-04-30 15:51:38 +02:00
4296511087 fix(tui): don't process UI events when suspending or stopping (#33710)
When the TUI is suspending or stopping, redraw events should not be
processed, as when it next processes redraw events it's already waiting
for a DA1 response after having disabled some terminal modes.

Fix #33708

(cherry picked from commit c35dde03c8)
2025-04-29 10:04:38 +00:00
f9c7a69cec Revert "fix(desktop): cannot open filename with spaces using OS file manager" #33684
This reverts commit 6e12ef4a7b

> Paths with spaces were already working. The original bug is most
> likely with user's terminal desktop entry, file manager or DE, and has
> nothing to do with nvim.desktop.

These are 3 different implementations that work correctly with unquoted %F and spaces:
```
$ DE=generic xdg-open "D I R/F I L E.txt" # pure bash
$ gio open "D I R/F I L E.txt" # glib2
$ handlr open "D I R/F I L E.txt" # rust
```

(cherry picked from commit 07a207a5f1)
2025-04-27 23:20:07 +00:00
fa292e6f61 docs: lsp, emoji, startup #33683
Co-authored-by: Maria José Solano <majosolano99@gmail.com>
2025-04-27 23:00:36 +00:00
bc66a5ff6f Merge pull request #33680 from justinmk/release 2025-04-27 15:32:22 -07:00
f25f6c8d13 feat(health): summary in section heading #33388
Problem:
As checkhealth grows, it is increasingly hard to quickly glance through
the information.

Solution:
Show a summary of ok, warn, and error outputs per section.
2025-04-27 22:35:39 +02:00
ad7211ac8f feat(checkhealth): trigger FileType event after showing report
Problem:
`FileType` event is fired before checkhealth report is finished, so
user can't override report settings or contents.
https://github.com/neovim/neovim/pull/33172#issuecomment-2833513916

Solution:
- Trigger FileType event later.
- Document how to remove emojis.
2025-04-27 20:39:14 +02:00
95ee908c40 docs: backport #33549 and #33524 to 0.11 (#33678)
* vim-patch:829eda7: runtime(new-tutor): update tutor and correct comandline completion

Problem: Some parts of the tutor are outdated.

- For example, pressing `<Tab>` after typing `:e` does not complete the
command `:edit`, but shows a completion menu with the first entry being
`:earlier`.

closes: vim/vim#17107

829eda7d38

Co-authored-by: Phạm Bình An <phambinhanctb2004@gmail.com>

Co-authored-by: zeertzjq <zeertzjq@outlook.com>
(cherry picked from commit 374e52a7ee)

* docs: provide example_init.lua #33524

Problem:
There are some "boilerplate" steps for new users. Although we are
constantly improving defaults and lifting patterns into core, users
eventually want to know how to start their own config, add plugins, etc.

Solution:
Add `runtime/example_init.lua` and refer to it from docs.

(cherry picked from commit 86b34ad073)

---------

Co-authored-by: zeertzjq <zeertzjq@outlook.com>
2025-04-27 10:33:37 -07:00
d68d212ad4 docs: provide example_init.lua #33524
Problem:
There are some "boilerplate" steps for new users. Although we are
constantly improving defaults and lifting patterns into core, users
eventually want to know how to start their own config, add plugins, etc.

Solution:
Add `runtime/example_init.lua` and refer to it from docs.

(cherry picked from commit 86b34ad073)
2025-04-28 00:01:50 +07:00
3273c595c0 vim-patch:829eda7: runtime(new-tutor): update tutor and correct comandline completion
Problem: Some parts of the tutor are outdated.

- For example, pressing `<Tab>` after typing `:e` does not complete the
command `:edit`, but shows a completion menu with the first entry being
`:earlier`.

closes: vim/vim#17107

829eda7d38

Co-authored-by: Phạm Bình An <phambinhanctb2004@gmail.com>

Co-authored-by: zeertzjq <zeertzjq@outlook.com>
(cherry picked from commit 374e52a7ee)
2025-04-27 23:44:37 +07:00
3db39ed21f fix(runtime): cpoptions is reset in Lua file #33671
closes #33670

(cherry picked from commit 923efaea28)
2025-04-27 12:19:58 +00:00
f184c562c5 fix(lsp): detect if Client:request resolved synchronously #33624
Problem:
In cases when the (in-process) LSP server responds to the request
immediately and calls `notify_reply_callback` the request will still be
marked as pending, because the code assumes that the response will occur
asynchronously. Then the request will be pending forever, because it was
already set as "completed" before we even set it as "pending".

A workaround is to wrap `notify_replay_callback` in `vim.shedule` ([like
so](https://github.com/neovim/neovim/pull/24338#issuecomment-2809568617)]
but that seems counterintuitive.

Solution:
Handle this case in Client:request().

(cherry picked from commit 8315697449)
2025-04-27 00:54:11 +00:00
32842b0ee3 fix(health): checkhealth float opens extra empty buffer #33648
(cherry picked from commit d927a87ed6)
2025-04-26 16:37:46 +00:00
4f0e828190 version bump 2025-04-26 16:31:02 +02:00
112 changed files with 3041 additions and 941 deletions

View File

@ -141,7 +141,7 @@ 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 1)
set(NVIM_VERSION_PATCH 2)
set(NVIM_VERSION_PRERELEASE "") # for package maintainers
# API level

View File

@ -120,6 +120,8 @@ endfunction
" Tutor Cmd: {{{1
function! s:Locale()
" Make sure l:lang exists before returning.
let l:lang = 'en_US'
if exists('v:lang') && v:lang =~ '\a\a'
let l:lang = v:lang
elseif $LC_ALL =~ '\a\a'
@ -132,8 +134,6 @@ function! s:Locale()
endif
elseif $LANG =~ '\a\a'
let l:lang = $LANG
else
let l:lang = 'en_US'
endif
return split(l:lang, '_')
endfunction
@ -220,6 +220,7 @@ function! tutor#TutorCmd(tutor_name)
call tutor#SetupVim()
exe "edit ".l:to_open
call tutor#EnableInteractive(v:true)
call tutor#ApplyTransform()
endfunction
@ -229,6 +230,27 @@ function! tutor#TutorCmdComplete(lead,line,pos)
return join(l:names, "\n")
endfunction
" Enables/disables interactive mode.
function! tutor#EnableInteractive(enable)
let enable = a:enable
if enable
setlocal buftype=nofile
setlocal concealcursor+=inv
setlocal conceallevel=2
call tutor#ApplyMarks()
augroup tutor_interactive
autocmd! TextChanged,TextChangedI <buffer> call tutor#ApplyMarksOnChanged()
augroup END
else
setlocal buftype<
setlocal concealcursor<
setlocal conceallevel<
if exists('#tutor_interactive')
autocmd! tutor_interactive * <buffer>
endif
endif
endfunction
function! tutor#ApplyTransform()
if has('win32')
sil! %s/{unix:(\(.\{-}\)),win:(\(.\{-}\))}/\2/g

View File

@ -3244,7 +3244,7 @@ getcharsearch() *getcharsearch()*
Return: ~
(`table`)
getcharstr([{expr}]) *getcharstr()*
getcharstr([{expr} [, {opts}]]) *getcharstr()*
The same as |getchar()|, except that this always returns a
String, and "number" isn't allowed in {opts}.
@ -12029,7 +12029,7 @@ winlayout([{tabnr}]) *winlayout()*
• {tabnr} (`integer?`)
Return: ~
(`any[]`)
(`vim.fn.winlayout.ret`)
winline() *winline()*
The result is a Number, which is the screen line of the cursor

View File

@ -311,7 +311,7 @@ Nvim's filetype detection behavior matches Vim, but is implemented as part of
|vim.filetype| (see `$VIMRUNTIME/lua/vim/filetype.lua`). The logic is encoded in
three tables, listed in order of precedence (the first match is returned):
1. `filename` for literal full path or basename lookup;
2. `pattern` for matching filenames or paths against |lua-patterns|, optimized
2. `pattern` for matching filenames or paths against |lua-pattern|s, optimized
for fast lookup;
3. `extension` for literal extension lookup.

View File

@ -269,6 +269,26 @@ DiagnosticVirtualTextHint
DiagnosticVirtualTextOk
Used for "Ok" diagnostic virtual text.
*hl-DiagnosticVirtualLinesError*
DiagnosticVirtualLinesError
Used for "Error" diagnostic virtual lines.
*hl-DiagnosticVirtualLinesWarn*
DiagnosticVirtualLinesWarn
Used for "Warn" diagnostic virtual lines.
*hl-DiagnosticVirtualLinesInfo*
DiagnosticVirtualLinesInfo
Used for "Info" diagnostic virtual lines.
*hl-DiagnosticVirtualLinesHint*
DiagnosticVirtualLinesHint
Used for "Hint" diagnostic virtual lines.
*hl-DiagnosticVirtualLinesOk*
DiagnosticVirtualLinesOk
Used for "Ok" diagnostic virtual lines.
*hl-DiagnosticUnderlineError*
DiagnosticUnderlineError
Used to underline "Error" diagnostics.

View File

@ -714,11 +714,14 @@ list of the current window.
omitted the current entry is used.
Also see |++opt| and |+cmd|.
:[count]n[ext] [++opt] [+cmd] *:n* *:ne* *:next* *]a* *E165* *E163*
:[count]n[ext] [++opt] [+cmd] *:n* *:ne* *:next* *E165* *E163*
Edit [count] next file. This fails when changes have
been made and Vim does not want to |abandon| the
current buffer. Also see |++opt| and |+cmd|.
*]a*
]a Mapped to |:next|. |default-mappings|
:[count]n[ext]! [++opt] [+cmd]
Edit [count] next file, discard any changes to the
buffer. Also see |++opt| and |+cmd|.
@ -740,16 +743,22 @@ list of the current window.
any changes to the buffer. Also see |++opt| and
|+cmd|.
:[count]prev[ious] [count] [++opt] [+cmd] *:prev* *:previous* *[a*
:[count]prev[ious] [count] [++opt] [+cmd] *:prev* *:previous*
Same as :Next. Also see |++opt| and |+cmd|.
*:rew* *:rewind* *[A*
*[a*
[a Mapped to |:previous|. |default-mappings|
*:rew* *:rewind*
:rew[ind] [++opt] [+cmd]
Start editing the first file in the argument list.
This fails when changes have been made and Vim does
not want to |abandon| the current buffer.
Also see |++opt| and |+cmd|.
*[A*
[A Mapped to |:rewind|. |default-mappings|
:rew[ind]! [++opt] [+cmd]
Start editing the first file in the argument list.
Discard any changes to the buffer. Also see |++opt|
@ -759,13 +768,16 @@ list of the current window.
:fir[st][!] [++opt] [+cmd]
Other name for ":rewind".
*:la* *:last* *]A*
*:la* *:last*
:la[st] [++opt] [+cmd]
Start editing the last file in the argument list.
This fails when changes have been made and Vim does
not want to |abandon| the current buffer.
Also see |++opt| and |+cmd|.
*]A*
]A Mapped to |:last|. |default-mappings|
:la[st]! [++opt] [+cmd]
Start editing the last file in the argument list.
Discard any changes to the buffer. Also see |++opt|

View File

@ -9,18 +9,18 @@
==============================================================================
Checkhealth *vim.health* *health*
vim.health is a minimal framework to help users troubleshoot configuration and
any other environment conditions that a plugin might care about. Nvim ships
with healthchecks for configuration, performance, python support, ruby
support, clipboard support, and more.
To run all healthchecks, use: >vim
:checkhealth
:checkhealth
<
Plugin authors are encouraged to write new healthchecks. |health-dev|
COMMANDS *health-commands*
*:che* *:checkhealth*
@ -56,7 +56,6 @@ Local mappings in the healthcheck buffer:
q Closes the window.
Global configuration:
*g:health*
g:health Dictionary with the following optional keys:
- `style` (`'float'|nil`) Set to "float" to display :checkhealth in
@ -65,16 +64,26 @@ g:health Dictionary with the following optional keys:
Example: >lua
vim.g.health = { style = 'float' }
Local configuration:
Checkhealth sets its buffer filetype to "checkhealth". You can customize the
buffer by handling the |FileType| event. For example if you don't want emojis
in the health report: >vim
autocmd FileType checkhealth :set modifiable | silent! %s/\v( ?[^\x00-\x7F])//g
<
--------------------------------------------------------------------------------
Create a healthcheck *health-dev*
Healthchecks are functions that check the user environment, configuration, or
any other prerequisites that a plugin cares about. Nvim ships with
healthchecks in:
- $VIMRUNTIME/autoload/health/
- $VIMRUNTIME/lua/vim/lsp/health.lua
- $VIMRUNTIME/lua/vim/treesitter/health.lua
- and more...
$VIMRUNTIME/autoload/health/
$VIMRUNTIME/lua/vim/lsp/health.lua
$VIMRUNTIME/lua/vim/treesitter/health.lua
and more...
To add a new healthcheck for your own plugin, simply create a "health.lua"
module on 'runtimepath' that returns a table with a "check()" function. Then
@ -82,35 +91,35 @@ module on 'runtimepath' that returns a table with a "check()" function. Then
For example if your plugin is named "foo", define your healthcheck module at
one of these locations (on 'runtimepath'):
- lua/foo/health/init.lua
- lua/foo/health.lua
lua/foo/health/init.lua
lua/foo/health.lua
If your plugin also provides a submodule named "bar" for which you want
a separate healthcheck, define the healthcheck at one of these locations:
- lua/foo/bar/health/init.lua
- lua/foo/bar/health.lua
If your plugin also provides a submodule named "bar" for which you want a
separate healthcheck, define the healthcheck at one of these locations:
lua/foo/bar/health/init.lua
lua/foo/bar/health.lua
All such health modules must return a Lua table containing a `check()`
function.
Copy this sample code into `lua/foo/health.lua`, replacing "foo" in the path
with your plugin name: >lua
local M = {}
local M = {}
M.check = function()
vim.health.start("foo report")
-- make sure setup function parameters are ok
if check_setup() then
vim.health.ok("Setup is correct")
else
vim.health.error("Setup is incorrect")
end
-- do some more checking
-- ...
end
M.check = function()
vim.health.start("foo report")
-- make sure setup function parameters are ok
if check_setup() then
vim.health.ok("Setup is correct")
else
vim.health.error("Setup is incorrect")
end
-- do some more checking
-- ...
end
return M
return M
<
error({msg}, {...}) *vim.health.error()*

View File

@ -1923,7 +1923,7 @@ These commands are used to start inserting text. You can end insert mode with
<Esc>. See |mode-ins-repl| for the other special characters in Insert mode.
The effect of [count] takes place after Insert mode is exited.
The following commands insert text, but stay in normal mode:
The following |default-mappings| insert text, but stay in normal mode:
*]<Space>*
]<Space> Insert an empty line below the cursor without leaving

View File

@ -41,7 +41,8 @@ Follow these steps to get LSP features:
-- current buffer that contains either a ".luarc.json" or a
-- ".luarc.jsonc" file. Files that share a root directory will reuse
-- the connection to the same LSP server.
root_markers = { '.luarc.json', '.luarc.jsonc' },
-- Nested lists indicate equal priority, see |vim.lsp.Config|.
root_markers = { { '.luarc.json', '.luarc.jsonc' }, '.git' },
-- Specific settings to send to the server. The schema for this is
-- defined by the server. For example the schema for lua-language-server
@ -286,38 +287,33 @@ They are also listed below.
- `'callHierarchy/incomingCalls'`
- `'callHierarchy/outgoingCalls'`
- `'textDocument/codeAction'`
- `'client/registerCapability'`
- `'client/unregisterCapability'`
- `'signature_help'`
- `'textDocument/codeLens'`
- `'textDocument/completion'`
- `'textDocument/declaration'`
- `'textDocument/definition'`
- `'textDocument/diagnostic'`
- `'textDocument/documentHighlight'`
- `'textDocument/documentSymbol'`
- `'textDocument/foldingRange'`
- `'textDocument/formatting'`
- `'textDocument/hover'`
- `'textDocument/implementation'`
- `'textDocument/inlayHint'`
- `'textDocument/prepareTypeHierarchy'`
- `'textDocument/publishDiagnostics'`
- `'textDocument/rangeFormatting'`
- `'textDocument/rangesFormatting'`
- `'textDocument/references'`
- `'textDocument/rename'`
- `'textDocument/semanticTokens/full'`
- `'textDocument/semanticTokens/full/delta'`
- `'textDocument/signatureHelp'`
- `'textDocument/typeDefinition*'`
- `'typeHierarchy/subtypes'`
- `'typeHierarchy/supertypes'`
- `'window/logMessage'`
- `'window/showMessage'`
- `'window/showDocument'`
- `'window/showMessage'`
- `'window/showMessageRequest'`
- `'window/workDoneProgress/create'`
- `'workspace/applyEdit'`
- `'workspace/configuration'`
- `'workspace/executeCommand'`
- `'workspace/inlayHint/refresh'`
- `'workspace/semanticTokens/refresh'`
- `'workspace/symbol'`
- `'workspace/workspaceFolders'`
@ -552,10 +548,19 @@ LspAttach *LspAttach*
|autocmd-pattern| is the buffer name. The client ID is passed in the
Lua handler |event-data| argument.
Example: >lua
vim.api.nvim_create_autocmd('LspAttach', {
callback = function(ev)
local client = vim.lsp.get_client_by_id(ev.data.client_id)
-- ...
end
})
<
Note: If the LSP server performs dynamic registration, capabilities may be
registered any time _after_ LspAttach. In that case you may want to handle
the "registerCapability" event. Example: >lua
the "registerCapability" event.
Example: >lua
vim.lsp.handlers['client/registerCapability'] = (function(overridden)
return function(err, res, ctx)
local result = overridden(err, res, ctx)
@ -563,8 +568,10 @@ LspAttach *LspAttach*
if not client then
return
end
-- Call your custom on_attach logic...
-- my_on_attach(client, vim.api.nvim_get_current_buf())
for bufnr, _ in pairs(client.attached_buffers) do
-- Call your custom on_attach logic...
-- my_on_attach(client, bufnr)
end
return result
end
end)(vim.lsp.handlers['client/registerCapability'])
@ -572,8 +579,9 @@ LspAttach *LspAttach*
LspDetach *LspDetach*
Just before an LSP client detaches from a buffer. The |autocmd-pattern| is
the buffer name. The client ID is passed in the Lua handler |event-data|
argument. Example: >lua
argument.
Example: >lua
vim.api.nvim_create_autocmd('LspDetach', {
callback = function(args)
-- Get the detaching client
@ -595,8 +603,9 @@ LspNotify *LspNotify*
LSP server.
The client_id, LSP method, and parameters are sent in the Lua handler
|event-data| table argument. Example: >lua
|event-data| table argument.
Example: >lua
vim.api.nvim_create_autocmd('LspNotify', {
callback = function(args)
local bufnr = args.buf
@ -642,8 +651,9 @@ LspRequest *LspRequest*
The Lua handler |event-data| argument has the client ID, request ID, and
request (described at |vim.lsp.Client|, {requests} field). If the request
type is `complete`, the request will be deleted from the client's pending
requests table after processing the event handlers. Example: >lua
requests table after processing the event handlers.
Example: >lua
vim.api.nvim_create_autocmd('LspRequest', {
callback = function(args)
local bufnr = args.buf
@ -670,8 +680,9 @@ LspTokenUpdate *LspTokenUpdate*
when an existing token becomes visible for the first time. The
|autocmd-pattern| is the buffer name. The Lua handler |event-data|
argument has the client ID and token (see
|vim.lsp.semantic_tokens.get_at_pos()|). Example: >lua
|vim.lsp.semantic_tokens.get_at_pos()|).
Example: >lua
vim.api.nvim_create_autocmd('LspTokenUpdate', {
callback = function(args)
local token = args.data.token
@ -697,25 +708,53 @@ Lua module: vim.lsp *lsp-core*
• {cmd}? (`string[]|fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient`)
See `cmd` in |vim.lsp.ClientConfig|.
• {filetypes}? (`string[]`) Filetypes the client will attach to, if
activated by `vim.lsp.enable()`. If not provided,
then the client will attach to all filetypes.
• {root_markers}? (`string[]`) Directory markers (.e.g. '.git/') where
the LSP server will base its workspaceFolders,
rootUri, and rootPath on initialization. Unused if
`root_dir` is provided.
• {root_dir}? (`string|fun(bufnr: integer, cb:fun(root_dir?:string))`)
Directory where the LSP server will base its
workspaceFolders, rootUri, and rootPath on
initialization. If a function, it is passed the
buffer number and a callback argument which must be
called with the value of root_dir to use. The LSP
server will not be started until the callback is
called.
activated by `vim.lsp.enable()`. If not provided, the
client will attach to all filetypes.
• {reuse_client}? (`fun(client: vim.lsp.Client, config: vim.lsp.ClientConfig): boolean`)
Predicate used to decide if a client should be
Predicate which decides if a client should be
re-used. Used on all running clients. The default
implementation re-uses a client if name and root_dir
matches.
• {root_dir}? (`string|fun(bufnr: integer, on_dir:fun(root_dir?:string))`)
*lsp-root_dir()* Directory where the LSP server will
base its workspaceFolders, rootUri, and rootPath on
initialization. The function form receives a buffer
number and `on_dir` callback which it must call to
provide root_dir, or LSP will not be activated for
the buffer. Thus a `root_dir()` function can
dynamically decide per-buffer whether to activate (or
skip) LSP. See example at |vim.lsp.enable()|.
• {root_markers}? (`(string|string[])[]`) Directory markers (.e.g.
'.git/') where the LSP server will base its
workspaceFolders, rootUri, and rootPath on
initialization. Unused if `root_dir` is provided.
The list order decides the priority. To indicate
"equal priority", specify names in a nested list
(`{ { 'a', 'b' }, ... }`) Each entry in this list is
a set of one or more markers. For each set, Nvim will
search upwards for each marker contained in the set.
If a marker is found, the directory which contains
that marker is used as the root directory. If no
markers from the set are found, the process is
repeated with the next set in the list.
Example: >lua
root_markers = { 'stylua.toml', '.git' }
<
Find the first parent directory containing the file
`stylua.toml`. If not found, find the first parent
directory containing the file or directory `.git`.
Example: >lua
root_markers = { { 'stylua.toml', '.luarc.json' }, '.git' }
<
Find the first parent directory containing EITHER
`stylua.toml` or `.luarc.json`. If not found, find
the first parent directory containing the file or
directory `.git`.
buf_attach_client({bufnr}, {client_id}) *vim.lsp.buf_attach_client()*
@ -871,14 +910,24 @@ config({name}, {cfg}) *vim.lsp.config()*
• {cfg} (`vim.lsp.Config`) See |vim.lsp.Config|.
enable({name}, {enable}) *vim.lsp.enable()*
Enable an LSP server to automatically start when opening a buffer.
Uses configuration defined with `vim.lsp.config`.
Auto-starts LSP when a buffer is opened, based on the |lsp-config|
`filetypes`, `root_markers`, and `root_dir` fields.
Examples: >lua
vim.lsp.enable('clangd')
vim.lsp.enable('clangd')
vim.lsp.enable({'luals', 'pyright'})
<
vim.lsp.enable({'luals', 'pyright'})
Example: To dynamically decide whether LSP is activated, define a
|lsp-root_dir()| function which calls `on_dir()` only when you want that
config to activate: >lua
vim.lsp.config('lua_ls', {
root_dir = function(bufnr, on_dir)
if not vim.fn.bufname(bufnr):match('%.txt$') then
on_dir(vim.fn.getcwd())
end
end
})
<
Parameters: ~
@ -993,6 +1042,15 @@ get_log_path() *vim.lsp.get_log_path()*
Return: ~
(`string`) path to log file
is_enabled({name}) *vim.lsp.is_enabled()*
Checks if the given LSP config is enabled (globally, not per-buffer).
Parameters: ~
• {name} (`string`) Config name
Return: ~
(`boolean`)
omnifunc({findstart}, {base}) *vim.lsp.omnifunc()*
Implements 'omnifunc' compatible LSP completion.
@ -1128,65 +1186,17 @@ Lua module: vim.lsp.client *lsp-client*
*vim.lsp.Client*
Fields: ~
• {id} (`integer`) The id allocated to the client.
• {name} (`string`) If a name is specified on creation,
that will be used. Otherwise it is just the
client id. This is used for logs and messages.
• {rpc} (`vim.lsp.rpc.PublicClient`) RPC client
object, for low level interaction with the
client. See |vim.lsp.rpc.start()|.
• {offset_encoding} (`string`) Called "position encoding" in LSP
spec, the encoding used for communicating with
the server. You can modify this in the
`config`'s `on_init` method before text is
sent to the server.
• {handlers} (`table<string,lsp.Handler>`) The handlers
used by the client as described in
|lsp-handler|.
• {requests} (`table<integer,{ type: string, bufnr: integer, method: string}?>`)
The current pending requests in flight to the
server. Entries are key-value pairs with the
key being the request id while the value is a
table with `type`, `bufnr`, and `method`
key-value pairs. `type` is either "pending"
for an active request, or "cancel" for a
cancel request. It will be "complete"
ephemerally while executing |LspRequest|
autocmds when replies are received from the
server.
• {config} (`vim.lsp.ClientConfig`) copy of the table
that was passed by the user to
|vim.lsp.start()|. See |vim.lsp.ClientConfig|.
• {server_capabilities} (`lsp.ServerCapabilities?`) Response from the
server sent on `initialize` describing the
server's capabilities.
• {server_info} (`lsp.ServerInfo?`) Response from the server
sent on `initialize` describing information
about the server.
• {progress} (`vim.lsp.Client.Progress`) A ring buffer
(|vim.ringbuf()|) containing progress messages
sent by the server. See
|vim.lsp.Client.Progress|.
• {initialized} (`true?`)
• {workspace_folders} (`lsp.WorkspaceFolder[]?`) The workspace
folders configured in the client when the
server starts. This property is only available
if the client supports workspace folders. It
can be `null` if the client supports workspace
folders but none are configured.
• {root_dir} (`string?`)
• {attached_buffers} (`table<integer,true>`)
• {capabilities} (`lsp.ClientCapabilities`) Capabilities
provided by the client (editor or tool), at
startup.
• {commands} (`table<string,fun(command: lsp.Command, ctx: table)>`)
Table of command name to function which is
called if any LSP action (code action, code
lenses, ...) triggers the command. Client
commands take precedence over the global
command registry.
• {settings} (`lsp.LSPObject`) Map with language server
specific settings. These are returned to the
language server if requested via
`workspace/configuration`. Keys are
case-sensitive.
Client commands. See |vim.lsp.ClientConfig|.
• {config} (`vim.lsp.ClientConfig`) Copy of the config
passed to |vim.lsp.start()|. See
|vim.lsp.ClientConfig|.
• {dynamic_capabilities} (`lsp.DynamicCapabilities`) Capabilities
provided at runtime (after startup).
• {flags} (`table`) A table with flags for the client.
The current (experimental) flags are:
• {allow_incremental_sync}? (`boolean`,
@ -1203,9 +1213,41 @@ Lua module: vim.lsp.client *lsp-client*
false, nvim exits immediately after sending
the "shutdown" request to the server.
• {get_language_id} (`fun(bufnr: integer, filetype: string): string`)
• {capabilities} (`lsp.ClientCapabilities`) The capabilities
provided by the client (editor or tool)
• {dynamic_capabilities} (`lsp.DynamicCapabilities`)
See |vim.lsp.ClientConfig|.
• {handlers} (`table<string,lsp.Handler>`) See
|vim.lsp.ClientConfig|.
• {id} (`integer`) The id allocated to the client.
• {initialized} (`true?`)
• {name} (`string`) See |vim.lsp.ClientConfig|.
• {offset_encoding} (`string`) See |vim.lsp.ClientConfig|.
• {progress} (`vim.lsp.Client.Progress`) A ring buffer
(|vim.ringbuf()|) containing progress messages
sent by the server. See
|vim.lsp.Client.Progress|.
• {requests} (`table<integer,{ type: string, bufnr: integer, method: string}?>`)
The current pending requests in flight to the
server. Entries are key-value pairs with the
key being the request id while the value is a
table with `type`, `bufnr`, and `method`
key-value pairs. `type` is either "pending"
for an active request, or "cancel" for a
cancel request. It will be "complete"
ephemerally while executing |LspRequest|
autocmds when replies are received from the
server.
• {root_dir} (`string?`) See |vim.lsp.ClientConfig|.
• {rpc} (`vim.lsp.rpc.PublicClient`) RPC client
object, for low level interaction with the
client. See |vim.lsp.rpc.start()|.
• {server_capabilities} (`lsp.ServerCapabilities?`) Response from the
server sent on `initialize` describing the
server's capabilities.
• {server_info} (`lsp.ServerInfo?`) Response from the server
sent on `initialize` describing server
information (e.g. version).
• {settings} (`lsp.LSPObject`) See |vim.lsp.ClientConfig|.
• {workspace_folders} (`lsp.WorkspaceFolder[]?`) See
|vim.lsp.ClientConfig|.
• {request} (`fun(self: vim.lsp.Client, method: string, params: table?, handler: lsp.Handler?, bufnr: integer?): boolean, integer?`)
See |Client:request()|.
• {request_sync} (`fun(self: vim.lsp.Client, method: string, params: table, timeout_ms: integer?, bufnr: integer?): {err: lsp.ResponseError?, result:any}?, string?`)
@ -1235,6 +1277,23 @@ Lua module: vim.lsp.client *lsp-client*
*vim.lsp.ClientConfig*
Fields: ~
• {before_init}? (`fun(params: lsp.InitializeParams, config: vim.lsp.ClientConfig)`)
Callback invoked before the LSP "initialize"
phase, where `params` contains the parameters
being sent to the server and `config` is the
config that was passed to |vim.lsp.start()|.
You can use this to modify parameters before
they are sent.
• {capabilities}? (`lsp.ClientCapabilities`) Map overriding the
default capabilities defined by
|vim.lsp.protocol.make_client_capabilities()|,
passed to the language server on
initialization. Hint: use
make_client_capabilities() and modify its
result.
• Note: To send an empty dictionary use
|vim.empty_dict()|, else it will be encoded
as an array.
• {cmd} (`string[]|fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient`)
command string[] that launches the language
server (treated as in |jobstart()|, must be
@ -1250,97 +1309,24 @@ Lua module: vim.lsp.client *lsp-client*
|vim.lsp.rpc.connect()|
• {cmd_cwd}? (`string`, default: cwd) Directory to launch
the `cmd` process. Not related to `root_dir`.
• {cmd_env}? (`table`) Environment flags to pass to the LSP
on spawn. Must be specified using a table.
Non-string values are coerced to string.
Example: >lua
{ PORT = 8080; HOST = "0.0.0.0"; }
• {cmd_env}? (`table`) Environment variables passed to the
LSP process on spawn. Non-string values are
coerced to string. Example: >lua
{ PORT = 8080; HOST = '0.0.0.0'; }
<
• {commands}? (`table<string,fun(command: lsp.Command, ctx: table)>`)
Client commands. Map of command names to
user-defined functions. Commands passed to
`start()` take precedence over the global
command registry. Each key must be a unique
command name, and the value is a function which
is called if any LSP action (code action, code
lenses, …) triggers the command.
• {detached}? (`boolean`, default: true) Daemonize the server
process so that it runs in a separate process
group from Nvim. Nvim will shutdown the process
on exit, but if Nvim fails to exit cleanly this
could leave behind orphaned server processes.
• {workspace_folders}? (`lsp.WorkspaceFolder[]`) List of workspace
folders passed to the language server. For
backwards compatibility rootUri and rootPath
will be derived from the first workspace folder
in this list. See `workspaceFolders` in the LSP
spec.
• {workspace_required}? (`boolean`) (default false) Server requires a
workspace (no "single file" support).
• {capabilities}? (`lsp.ClientCapabilities`) Map overriding the
default capabilities defined by
|vim.lsp.protocol.make_client_capabilities()|,
passed to the language server on
initialization. Hint: use
make_client_capabilities() and modify its
result.
• Note: To send an empty dictionary use
|vim.empty_dict()|, else it will be encoded
as an array.
• {handlers}? (`table<string,function>`) Map of language
server method names to |lsp-handler|
• {settings}? (`lsp.LSPObject`) Map with language server
specific settings. See the {settings} in
|vim.lsp.Client|.
• {commands}? (`table<string,fun(command: lsp.Command, ctx: table)>`)
Table that maps string of clientside commands
to user-defined functions. Commands passed to
`start()` take precedence over the global
command registry. Each key must be a unique
command name, and the value is a function which
is called if any LSP action (code action, code
lenses, ...) triggers the command.
• {init_options}? (`lsp.LSPObject`) Values to pass in the
initialization request as
`initializationOptions`. See `initialize` in
the LSP spec.
• {name}? (`string`, default: client-id) Name in log
messages.
• {get_language_id}? (`fun(bufnr: integer, filetype: string): string`)
Language ID as string. Defaults to the buffer
filetype.
• {offset_encoding}? (`'utf-8'|'utf-16'|'utf-32'`) Called "position
encoding" in LSP spec, the encoding that the
LSP server expects. Client does not verify this
is correct.
• {on_error}? (`fun(code: integer, err: string)`) Callback
invoked when the client operation throws an
error. `code` is a number describing the error.
Other arguments may be passed depending on the
error kind. See `vim.lsp.rpc.client_errors` for
possible errors. Use
`vim.lsp.rpc.client_errors[code]` to get
human-friendly name.
• {before_init}? (`fun(params: lsp.InitializeParams, config: vim.lsp.ClientConfig)`)
Callback invoked before the LSP "initialize"
phase, where `params` contains the parameters
being sent to the server and `config` is the
config that was passed to |vim.lsp.start()|.
You can use this to modify parameters before
they are sent.
• {on_init}? (`elem_or_list<fun(client: vim.lsp.Client, init_result: lsp.InitializeResult)>`)
Callback invoked after LSP "initialize", where
`result` is a table of `capabilities` and
anything else the server may send. For example,
clangd sends `init_result.offsetEncoding` if
`capabilities.offsetEncoding` was sent to it.
You can only modify the
`client.offset_encoding` here before any
notifications are sent.
• {on_exit}? (`elem_or_list<fun(code: integer, signal: integer, client_id: integer)>`)
Callback invoked on client exit.
• code: exit code of the process
• signal: number describing the signal used to
terminate (if any)
• client_id: client handle
• {on_attach}? (`elem_or_list<fun(client: vim.lsp.Client, bufnr: integer)>`)
Callback invoked when client attaches to a
buffer.
• {trace}? (`'off'|'messages'|'verbose'`, default: "off")
Passed directly to the language server in the
initialize request. Invalid/empty values will
• {flags}? (`table`) A table with flags for the client.
The current (experimental) flags are:
• {allow_incremental_sync}? (`boolean`,
@ -1356,9 +1342,72 @@ Lua module: vim.lsp.client *lsp-client*
request before sending kill -15. If set to
false, nvim exits immediately after sending
the "shutdown" request to the server.
• {get_language_id}? (`fun(bufnr: integer, filetype: string): string`)
Language ID as string. Defaults to the buffer
filetype.
• {handlers}? (`table<string,function>`) Map of LSP method
names to |lsp-handler|s.
• {init_options}? (`lsp.LSPObject`) Values to pass in the
initialization request as
`initializationOptions`. See `initialize` in
the LSP spec.
• {name}? (`string`) (default: client-id) Name in logs
and user messages.
• {offset_encoding}? (`'utf-8'|'utf-16'|'utf-32'`) Called "position
encoding" in LSP spec. The encoding that the
LSP server expects, used for communication. Not
validated. Can be modified in `on_init` before
text is sent to the server.
• {on_attach}? (`elem_or_list<fun(client: vim.lsp.Client, bufnr: integer)>`)
Callback invoked when client attaches to a
buffer.
• {on_error}? (`fun(code: integer, err: string)`) Callback
invoked when the client operation throws an
error. `code` is a number describing the error.
Other arguments may be passed depending on the
error kind. See `vim.lsp.rpc.client_errors` for
possible errors. Use
`vim.lsp.rpc.client_errors[code]` to get
human-friendly name.
• {on_exit}? (`elem_or_list<fun(code: integer, signal: integer, client_id: integer)>`)
Callback invoked on client exit.
• code: exit code of the process
• signal: number describing the signal used to
terminate (if any)
• client_id: client handle
• {on_init}? (`elem_or_list<fun(client: vim.lsp.Client, init_result: lsp.InitializeResult)>`)
Callback invoked after LSP "initialize", where
`result` is a table of `capabilities` and
anything else the server may send. For example,
clangd sends `init_result.offsetEncoding` if
`capabilities.offsetEncoding` was sent to it.
You can only modify the
`client.offset_encoding` here before any
notifications are sent.
• {root_dir}? (`string`) Directory where the LSP server will
base its workspaceFolders, rootUri, and
rootPath on initialization.
• {settings}? (`lsp.LSPObject`) Map of language
server-specific settings, decided by the
client. Sent to the LS if requested via
`workspace/configuration`. Keys are
case-sensitive.
• {trace}? (`'off'|'messages'|'verbose'`, default: "off")
Passed directly to the language server in the
initialize request. Invalid/empty values will
• {workspace_folders}? (`lsp.WorkspaceFolder[]`) List of workspace
folders passed to the language server. For
backwards compatibility rootUri and rootPath
are derived from the first workspace folder in
this list. Can be `null` if the client supports
workspace folders but none are configured. See
`workspaceFolders` in LSP spec.
• {workspace_required}? (`boolean`) (default false) Server requires a
workspace (no "single file" support). Note:
Without a workspace, cross-file features
(navigation, hover) may or may not work
depending on the language server, even if the
server doesn't require a workspace.
Client:cancel_request({id}) *Client:cancel_request()*
@ -1544,7 +1593,8 @@ clear_references() *vim.lsp.buf.clear_references()*
Removes document highlights from current buffer.
code_action({opts}) *vim.lsp.buf.code_action()*
Selects a code action available at the current cursor position.
Selects a code action (LSP: "textDocument/codeAction" request) available
at cursor position.
Parameters: ~
• {opts} (`table?`) A table with the following fields:
@ -2507,6 +2557,27 @@ symbols_to_items({symbols}, {bufnr}, {position_encoding})
==============================================================================
Lua module: vim.lsp.log *lsp-log*
The `vim.lsp.log` module provides logging for the Nvim LSP client.
When debugging language servers, it is helpful to enable extra-verbose logging
of the LSP client RPC events. Example: >lua
vim.lsp.set_log_level 'trace'
require('vim.lsp.log').set_format_func(vim.inspect)
<
Then try to run the language server, and open the log with: >vim
:lua vim.cmd('tabnew ' .. vim.lsp.get_log_path())
<
(Or use `:LspLog` if you have nvim-lspconfig installed.)
Note:
• Remember to DISABLE verbose logging ("debug" or "trace" level), else you may
encounter performance issues.
• "ERROR" messages containing "stderr" only indicate that the log was sent to
stderr. Many servers send harmless messages via stderr.
get_filename() *vim.lsp.log.get_filename()*
Returns the log filename.

View File

@ -93,10 +93,9 @@ Finally, you can include Lua code in a Vimscript file by putting it inside a
Using Lua files on startup *lua-guide-config*
Nvim supports using `init.vim` or `init.lua` as the configuration file, but
not both at the same time. This should be placed in your |config| directory,
which is typically `~/.config/nvim` for Linux, BSD, or macOS, and
`~/AppData/Local/nvim/` for Windows. Note that you can use Lua in `init.vim`
and Vimscript in `init.lua`, which will be covered below.
not both at the same time. This should be placed in your |config| directory
(run `:echo stdpath('config')` to see where it is). Note that you can also use
Lua in `init.vim` and Vimscript in `init.lua`, which will be covered below.
If you'd like to run any other Lua script on |startup| automatically, then you
can simply put it in `plugin/` in your |'runtimepath'|.

View File

@ -171,7 +171,7 @@ added. But visually, this small bit of sugar gets reasonably close to a
*lua-regex*
Lua intentionally does not support regular expressions, instead it has limited
|lua-patterns| which avoid the performance pitfalls of extended regex. Lua
|lua-pattern|s which avoid the performance pitfalls of extended regex. Lua
scripts can also use Vim regex via |vim.regex()|.
Examples: >lua
@ -2022,7 +2022,7 @@ vim.gsplit({s}, {sep}, {opts}) *vim.gsplit()*
See also: ~
• |string.gmatch()|
• |vim.split()|
• |lua-patterns|
• |lua-pattern|s
• https://www.lua.org/pil/20.2.html
• http://lua-users.org/wiki/StringLibraryTutorial
@ -2115,7 +2115,7 @@ vim.list_slice({list}, {start}, {finish}) *vim.list_slice()*
(`any[]`) Copy of table sliced from start to finish (inclusive)
vim.pesc({s}) *vim.pesc()*
Escapes magic chars in |lua-patterns|.
Escapes magic chars in |lua-pattern|s.
Parameters: ~
• {s} (`string`) String to escape
@ -2370,7 +2370,7 @@ vim.trim({s}) *vim.trim()*
(`string`) String with whitespace removed from its beginning and end
See also: ~
• |lua-patterns|
• |lua-pattern|s
• https://www.lua.org/pil/20.2.html
*vim.validate()*
@ -2670,7 +2670,7 @@ vim.filetype.add({filetypes}) *vim.filetype.add()*
Filetype mappings can be added either by extension or by filename (either
the "tail" or the full file path). The full file path is checked first,
followed by the file name. If a match is not found using the filename,
then the filename is matched against the list of |lua-patterns| (sorted by
then the filename is matched against the list of |lua-pattern|s (sorted by
priority) until a match is found. Lastly, if pattern matching does not
find a filetype, then the file extension is used.
@ -2898,7 +2898,7 @@ Use |uv.fs_stat()| to check a file's type, and whether it exists.
Example: >lua
if vim.uv.fs_stat(file) then
vim.print("file exists")
vim.print('file exists')
end
<
@ -2953,7 +2953,8 @@ vim.fs.dir({path}, {opts}) *vim.fs.dir()*
"fifo", "socket", "char", "block", "unknown".
vim.fs.dirname({file}) *vim.fs.dirname()*
Return the parent directory of the given path
Gets the parent directory of the given path (not expanded/resolved, the
caller must do that).
Attributes: ~
Since: 0.8.0
@ -2978,16 +2979,17 @@ vim.fs.find({names}, {opts}) *vim.fs.find()*
narrow the search to find only that type.
Examples: >lua
-- list all test directories under the runtime directory
local test_dirs = vim.fs.find(
{'test', 'tst', 'testdir'},
{limit = math.huge, type = 'directory', path = './runtime/'}
-- List all test directories under the runtime directory.
local dirs = vim.fs.find(
{ 'test', 'tst', 'testdir' },
{ limit = math.huge, type = 'directory', path = './runtime/' }
)
-- get all files ending with .cpp or .hpp inside lib/
local cpp_hpp = vim.fs.find(function(name, path)
-- Get all "lib/*.cpp" and "lib/*.hpp" files, using Lua patterns.
-- Or use `vim.glob.to_lpeg(…):match(…)` for glob/wildcard matching.
local files = vim.fs.find(function(name, path)
return name:match('.*%.[ch]pp$') and path:match('[/\\]lib$')
end, {limit = math.huge, type = 'file'})
end, { limit = math.huge, type = 'file' })
<
Attributes: ~
@ -3087,19 +3089,20 @@ vim.fs.normalize({path}, {opts}) *vim.fs.normalize()*
(`string`) Normalized path
vim.fs.parents({start}) *vim.fs.parents()*
Iterate over all the parents of the given path.
Iterate over all the parents of the given path (not expanded/resolved, the
caller must do that).
Example: >lua
local root_dir
for dir in vim.fs.parents(vim.api.nvim_buf_get_name(0)) do
if vim.fn.isdirectory(dir .. "/.git") == 1 then
if vim.fn.isdirectory(dir .. '/.git') == 1 then
root_dir = dir
break
end
end
if root_dir then
print("Found git repository at", root_dir)
print('Found git repository at', root_dir)
end
<
@ -3737,19 +3740,25 @@ vim.regex({re}) *vim.regex()*
Lua module: vim.secure *vim.secure*
vim.secure.read({path}) *vim.secure.read()*
Attempt to read the file at {path} prompting the user if the file should
be trusted. The user's choice is persisted in a trust database at
If {path} is a file: attempt to read the file, prompting the user if the
file should be trusted.
If {path} is a directory: return true if the directory is trusted
(non-recursive), prompting the user as necessary.
The user's choice is persisted in a trust database at
$XDG_STATE_HOME/nvim/trust.
Attributes: ~
Since: 0.9.0
Parameters: ~
• {path} (`string`) Path to a file to read.
• {path} (`string`) Path to a file or directory to read.
Return: ~
(`string?`) The contents of the given file if it exists and is
trusted, or nil otherwise.
(`boolean|string?`) If {path} is not trusted or does not exist,
returns `nil`. Otherwise, returns the contents of {path} if it is a
file, or true if {path} is a directory.
See also: ~
• |:trust|

View File

@ -4150,7 +4150,7 @@ string.upper({s}) *string.upper()*
locale.
------------------------------------------------------------------------------
5.4.1 Patterns *lua-patterns*
5.4.1 Patterns *lua-pattern*
A character class is used to represent a set of characters. The following
combinations are allowed in describing a character class:
@ -4207,6 +4207,7 @@ A pattern item may be
- a single character class followed by `+`, which matches 1 or more
repetitions of characters in the class. These repetition items will
always match the longest possible sequence;
*lua-nongreedy*
- a single character class followed by `-`, which also matches 0 or
more repetitions of characters in the class. Unlike `*`, these
repetition items will always match the shortest possible sequence;
@ -4221,7 +4222,7 @@ A pattern item may be
`y` where the count reaches 0. For instance, the item `%b()` matches
expressions with balanced parentheses.
PATTERN *lua-pattern*
PATTERN
A pattern is a sequence of pattern items. A `^` at the beginning of a pattern
anchors the match at the beginning of the subject string. A `$` at the end of

View File

@ -19,12 +19,7 @@ For changing the language of messages and menus see |mlang.txt|.
==============================================================================
Getting started *mbyte-first*
This is a summary of the multibyte features in Vim. If you are lucky it works
as described and you can start using Vim without much trouble. If something
doesn't work you will have to read the rest. Don't be surprised if it takes
quite a bit of work and experimenting to make Vim use all the multibyte
features. Unfortunately, every system has its own way to deal with multibyte
languages and it is quite complicated.
This is a summary of the multibyte features in Nvim.
LOCALE
@ -54,14 +49,14 @@ See |mbyte-locale| for details.
ENCODING
Nvim always uses UTF-8 internally. Thus 'encoding' option is always set
to "utf-8" and cannot be changed.
Nvim always uses UTF-8 internally. Thus 'encoding' is always set to "utf-8"
and cannot be changed.
All the text that is used inside Vim will be in UTF-8. Not only the text in
the buffers, but also in registers, variables, etc.
You can edit files in different encodings than UTF-8. Nvim
will convert the file when you read it and convert it back when you write it.
You can edit files in different encodings than UTF-8. Nvim will convert the
file when you read it and convert it back when you write it.
See 'fileencoding', 'fileencodings' and |++enc|.
@ -184,9 +179,9 @@ You could make a small shell script for this.
==============================================================================
Encoding *mbyte-encoding*
In Nvim UTF-8 is always used internally to encode characters.
This applies to all the places where text is used, including buffers (files
loaded into memory), registers and variables.
UTF-8 is always used internally to encode characters. This applies to all the
places where text is used, including buffers (files loaded into memory),
registers and variables.
*charset* *codeset*
Charset is another name for encoding. There are subtle differences, but these
@ -609,25 +604,25 @@ Combining forms:
Using UTF-8 *mbyte-utf8* *UTF-8* *utf-8* *utf8*
*Unicode* *unicode*
The Unicode character set was designed to include all characters from other
character sets. Therefore it is possible to write text in any language using
Unicode (with a few rarely used languages excluded). And it's mostly possible
to mix these languages in one file, which is impossible with other encodings.
character sets. Therefore it is possible to write text in (almost) any
language using Unicode. And it's mostly possible to mix these languages in
one file, which is impossible with other encodings.
Unicode can be encoded in several ways. The most popular one is UTF-8, which
uses one or more bytes for each character and is backwards compatible with
ASCII. On MS-Windows UTF-16 is also used (previously UCS-2), which uses
16-bit words. Vim can support all of these encodings, but always uses UTF-8
ASCII. On MS-Windows UTF-16 is also used (previously UCS-2), which uses
16-bit words. Nvim supports all of these encodings, but always uses UTF-8
internally.
Vim has comprehensive UTF-8 support. It works well in:
- xterm with UTF-8 support enabled
- MS-Windows GUI
- several other platforms
Double-width characters are supported. Works best with 'guifontwide'. When
Nvim supports double-width characters; works best with 'guifontwide'. When
using only 'guifont' the wide characters are drawn in the normal width and
a space to fill the gap.
EMOJI *emoji*
You can list emoji characters using this script: >vim
:source $VIMRUNTIME/scripts/emoji_list.lua
<
*bom-bytes*
When reading a file a BOM (Byte Order Mark) can be used to recognize the
Unicode encoding:
@ -698,7 +693,7 @@ You need to specify a font to be used. For double-wide characters another
font is required, which is exactly twice as wide. There are three ways to do
this:
1. Set 'guifont' and let Vim find a matching 'guifontwide'
1. Set 'guifont' and let Nvim find a matching 'guifontwide'
2. Set 'guifont' and 'guifontwide'
See the documentation for each option for details. Example: >
@ -730,7 +725,7 @@ COMMAND ARGUMENTS *utf-8-char-arg*
Commands like |f|, |F|, |t| and |r| take an argument of one character. For
UTF-8 this argument may include one or two composing characters. These need
to be produced together with the base character, Vim doesn't wait for the next
to be produced together with the base character, Nvim doesn't wait for the next
character to be typed to find out if it is a composing character or not.
Using 'keymap' or |:lmap| is a nice way to type these characters.
@ -741,7 +736,6 @@ searching for a character with a composing character, this will only find
matches with that composing character. It was implemented this way, because
not everybody is able to type a composing character.
==============================================================================
Overview of options *mbyte-options*

View File

@ -171,6 +171,11 @@ API
aligned text that truncates before covering up buffer text.
• `virt_lines_overflow` field accepts value `scroll` to enable horizontal
scrolling for virtual lines with 'nowrap'.
• |vim.secure.read()| now returns `true` for trusted directories. Previously
it would return `nil`, which made it impossible to tell if the directory was
actually trusted.
• Added |vim.lsp.is_enabled()| to check if a given LSP config has been enabled
by |vim.lsp.enable()|.
DEFAULTS
@ -277,6 +282,7 @@ LSP
• The `textDocument/completion` request now includes the completion context in
its parameters.
• |vim.lsp.Config| gained `workspace_required`.
• `root_markers` in |vim.lsp.Config| can now be ordered by priority.
LUA
@ -343,6 +349,8 @@ PLUGINS
• 'commentstring' values can now be specified in a Treesitter capture's
`bo.commentstring` metadata field, providing finer grained support for
languages like `JSX`.
• Customize :checkhealth by handling a `FileType checkhealth` event.
|health-usage|
STARTUP
@ -437,6 +445,7 @@ UI
• |ui-messages| content chunks now also contain the highlight group ID.
• |:checkhealth| can display in a floating window, controlled by the
|g:health| variable.
• |:checkhealth| shows a summary in the header for every healthcheck.
VIMSCRIPT

View File

@ -75,6 +75,16 @@ the same Nvim configuration on all of your machines, by creating
==============================================================================
What next? *nvim-quickstart*
If you want to use Lua to configure Nvim, you can copy an example
configuration to your |init.lua|
>vim
:exe 'edit' stdpath('config') .. '/init.lua'
:read $VIMRUNTIME/example_init.lua
<
See |lua-guide| for practical notes on using Lua to configure Nvim.
"IDE" features in Nvim are provided by Language Server Protocol. See |lsp|
If you are just trying out Nvim for a few minutes, and want to see the
extremes of what it can do, try one of these popular "extension packs" or
"distributions" (Note: Nvim is not affiliated with these projects, and does

View File

@ -2361,6 +2361,12 @@ A jump table for the options with a short description can be found at |Q_op|.
- 'exrc' can execute any code; editorconfig only specifies settings.
- 'exrc' is Nvim-specific; editorconfig works in other editors.
To achieve project-local LSP configuration:
1. Enable 'exrc'.
2. Place LSP configs at ".nvim/lsp/*.lua" in your project root.
3. Create ".nvim.lua" in your project root directory with this line: >lua
vim.cmd[[set runtimepath+=.nvim]]
<
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
@ -5946,8 +5952,7 @@ A jump table for the options with a short description can be found at |Q_op|.
*'statusline'* *'stl'* *E540* *E542*
'statusline' 'stl' string (default "")
global or local to window |global-local|
When non-empty, this option determines the content of the status line.
Also see |status-line|.
Sets the |status-line|.
The option consists of printf style '%' items interspersed with
normal text. Each status line item is of the form:

View File

@ -4,6 +4,8 @@
vim-tutor-mode provides a system to follow and create interactive tutorials
for vim and third party plugins. It replaces the venerable `vimtutor` system.
Original Author: Felipe Morales <https://github.com/fmoralesc>
==============================================================================
1. Usage *vim-tutor-usage*
@ -39,12 +41,5 @@ to be detected by the :Tutor command.
It is recommended to use a less formal style when writing tutorials than in
regular documentation (unless the content requires it).
============================================================================
3. Contributing
Development of the plugin is done over at github [1]. Feel free to report
issues and make suggestions.
[1]: https://github.com/fmoralesc/vim-tutor-mode
" vim: set ft=help :
=============================================================================
vim:tw=78:ts=8:noet:ft=help:norl:

View File

@ -91,28 +91,40 @@ processing a quickfix or location list command, it will be aborted.
:ll[!] [nr] Same as ":cc", except the location list for the
:[nr]ll[!] current window is used instead of the quickfix list.
*:cn* *:cne* *:cnext* *E553* *]q*
*:cn* *:cne* *:cnext* *E553*
:[count]cn[ext][!] Display the [count] next error in the list that
includes a file name. If there are no file names at
all, go to the [count] next error. See |:cc| for
[!] and 'switchbuf'.
*:lne* *:lnext* *]l*
*]q*
]q Mapped to |:cnext|. |default-mappings|
*:lne* *:lnext*
:[count]lne[xt][!] Same as ":cnext", except the location list for the
current window is used instead of the quickfix list.
:[count]cN[ext][!] *:cp* *:cprevious* *:cprev* *:cN* *:cNext* *[q*
*]l*
]l Mapped to |:lnext|. |default-mappings|
:[count]cN[ext][!] *:cp* *:cprevious* *:cprev* *:cN* *:cNext*
:[count]cp[revious][!] Display the [count] previous error in the list that
includes a file name. If there are no file names at
all, go to the [count] previous error. See |:cc| for
[!] and 'switchbuf'.
*[q*
[q Mapped to |:cprevious|. |default-mappings|
:[count]lN[ext][!] *:lp* *:lprevious* *:lprev* *:lN* *:lNext* *[l*
:[count]lN[ext][!] *:lp* *:lprevious* *:lprev* *:lN* *:lNext*
:[count]lp[revious][!] Same as ":cNext" and ":cprevious", except the location
list for the current window is used instead of the
quickfix list.
*[l*
[l Mapped to |:lprevious|. |default-mappings|
*:cabo* *:cabove*
:[count]cabo[ve] Go to the [count] error above the current line in the
current buffer. If [count] is omitted, then 1 is
@ -171,52 +183,76 @@ processing a quickfix or location list command, it will be aborted.
:[count]laf[ter] Same as ":cafter", except the location list for the
current window is used instead of the quickfix list.
*:cnf* *:cnfile* *]CTRL-Q*
*:cnf* *:cnfile*
:[count]cnf[ile][!] Display the first error in the [count] next file in
the list that includes a file name. If there are no
file names at all or if there is no next file, go to
the [count] next error. See |:cc| for [!] and
'switchbuf'.
*:lnf* *:lnfile* *]CTRL-L*
*]CTRL-Q*
]CTRL-Q Mapped to |:cnfile|. |default-mappings|
*:lnf* *:lnfile*
:[count]lnf[ile][!] Same as ":cnfile", except the location list for the
current window is used instead of the quickfix list.
:[count]cNf[ile][!] *:cpf* *:cpfile* *:cNf* *:cNfile* *[CTRL-Q*
*]CTRL-L*
]CTRL-L Mapped to |:lnfile|. |default-mappings|
:[count]cNf[ile][!] *:cpf* *:cpfile* *:cNf* *:cNfile*
:[count]cpf[ile][!] Display the last error in the [count] previous file in
the list that includes a file name. If there are no
file names at all or if there is no next file, go to
the [count] previous error. See |:cc| for [!] and
'switchbuf'.
*[CTRL-Q*
[CTRL-Q Mapped to |:cpfile|. |default-mappings|
:[count]lNf[ile][!] *:lpf* *:lpfile* *:lNf* *:lNfile* *[CTRL-L*
:[count]lNf[ile][!] *:lpf* *:lpfile* *:lNf* *:lNfile*
:[count]lpf[ile][!] Same as ":cNfile" and ":cpfile", except the location
list for the current window is used instead of the
quickfix list.
*:crewind* *:cr* *[Q*
*[CTRL-L*
[CTRL-L Mapped to |:lpfile|. |default-mappings|
*:crewind* *:cr*
:cr[ewind][!] [nr] Display error [nr]. If [nr] is omitted, the FIRST
error is displayed. See |:cc|.
*:lrewind* *:lr* *[L*
*[Q*
[Q Mapped to |:crewind|. |default-mappings|
*:lrewind* *:lr*
:lr[ewind][!] [nr] Same as ":crewind", except the location list for the
current window is used instead of the quickfix list.
*[L*
[L Mapped to |:lrewind|. |default-mappings|
*:cfirst* *:cfir*
:cfir[st][!] [nr] Same as ":crewind".
*:lfirst* *:lfir*
:lfir[st][!] [nr] Same as ":lrewind".
*:clast* *:cla* *]Q*
*:clast* *:cla*
:cla[st][!] [nr] Display error [nr]. If [nr] is omitted, the LAST
error is displayed. See |:cc|.
*:llast* *:lla* *]L*
*]Q*
]Q Mapped to |:clast|.
*:llast* *:lla*
:lla[st][!] [nr] Same as ":clast", except the location list for the
current window is used instead of the quickfix list.
*]L*
]L Mapped to |:llast|.
*:cq* *:cquit*
:cq[uit][!]
:{N}cq[uit][!]

View File

@ -1382,8 +1382,8 @@ The $XDG_CONFIG_HOME, $XDG_DATA_HOME, $XDG_RUNTIME_DIR, $XDG_STATE_HOME,
$XDG_CACHE_HOME, $XDG_CONFIG_DIRS and $XDG_DATA_DIRS environment variables
are used if defined, else default values (listed below) are used.
Throughout the help pages these defaults are used as placeholders, e.g.
"~/.config" is understood to mean "$XDG_CONFIG_HOME or ~/.config".
Note: In the help these defaults are used as placeholders, e.g. "~/.config" is
understood as "$XDG_CONFIG_HOME or ~/.config".
CONFIG DIRECTORY (DEFAULT) ~
*$XDG_CONFIG_HOME* Nvim: stdpath("config")
@ -1437,12 +1437,17 @@ configuration files in `$XDG_CONFIG_HOME/foo` instead of
`$XDG_CONFIG_HOME/nvim`. `$NVIM_APPNAME` must be a name, such as "foo", or a
relative path, such as "foo/bar".
Note: In the help wherever `$XDG_CONFIG_…/nvim` is mentioned it is understood
as `$XDG_CONFIG_…/$NVIM_APPNAME`.
*state-isolation*
One use-case for $NVIM_APPNAME is to "isolate" Nvim applications.
Alternatively, for true isolation, on Linux you can use cgroups namespaces: >
systemd-run --user -qt -p PrivateUsers=yes -p BindPaths=/home/user/profile_xy:/home/user/.config/nvim nvim
Note: Throughout the help pages, wherever `$XDG_CONFIG_…/nvim` is mentioned it
is understood to mean `$XDG_CONFIG_…/$NVIM_APPNAME`.
<
*stateless*
To run Nvim without creating any directories or data files: >
NVIM_LOG_FILE=/dev/null nvim -n -i NONE
LOG FILE *log* *$NVIM_LOG_FILE* *E5430*
Besides 'debug' and 'verbose', Nvim keeps a general log file for internal

View File

@ -274,27 +274,39 @@ g CTRL-] Like CTRL-], but use ":tjump" instead of ":tag".
{Visual}g CTRL-] Same as "g CTRL-]", but use the highlighted text as
the identifier.
*:tn* *:tnext* *]t*
*:tn* *:tnext*
:[count]tn[ext][!] Jump to [count] next matching tag (default 1). See
|tag-!| for [!].
*:tp* *:tprevious* *[t*
*]t*
]t Mapped to |:tnext|. |default-mappings|
*:tp* *:tprevious*
:[count]tp[revious][!] Jump to [count] previous matching tag (default 1).
See |tag-!| for [!].
*[t*
[t Mapped to |:tprevious|. |default-mappings|
*:tN* *:tNext*
:[count]tN[ext][!] Same as ":tprevious".
*:tr* *:trewind* *[T*
*:tr* *:trewind*
:[count]tr[ewind][!] Jump to first matching tag. If [count] is given, jump
to [count]th matching tag. See |tag-!| for [!].
*[T*
[T Mapped to |:trewind|. |default-mappings|
*:tf* *:tfirst*
:[count]tf[irst][!] Same as ":trewind".
*:tl* *:tlast* *]T*
*:tl* *:tlast*
:tl[ast][!] Jump to last matching tag. See |tag-!| for [!].
*]T*
]T Mapped to |:tlast|. |default-mappings|
*:lt* *:ltag*
:lt[ag][!] [name] Jump to tag [name] and add the matching tags to a new
location list for the current window. [name] can be
@ -335,12 +347,18 @@ the same as above, with a "p" prepended.
:ptj[ump][!] [name] Does ":tjump[!] [name]" and shows the new tag in a
"Preview" window. See |:ptag| for more info.
*:ptn* *:ptnext* *]CTRL-T*
*:ptn* *:ptnext*
:[count]ptn[ext][!] ":tnext" in the preview window. See |:ptag|.
*:ptp* *:ptprevious* *[CTRL-T*
*]CTRL-T*
]CTRL-T Mapped to |:ptnext|. |default-mappings|
*:ptp* *:ptprevious*
:[count]ptp[revious][!] ":tprevious" in the preview window. See |:ptag|.
*[CTRL-T*
[CTRL-T Mapped to |:ptprevious|. |default-mappings|
*:ptN* *:ptNext*
:[count]ptN[ext][!] Same as ":ptprevious".

View File

@ -120,7 +120,7 @@ The following predicates are built in:
match.
`lua-match?` *treesitter-predicate-lua-match?*
Match |lua-patterns| against the text corresponding to a node,
Match |lua-pattern|s against the text corresponding to a node,
similar to `match?`
`any-lua-match?` *treesitter-predicate-any-lua-match?*

View File

@ -103,11 +103,11 @@ g8 Print the hex values of the bytes used in the
*gx*
gx Opens the current filepath or URL (decided by
|<cfile>|, 'isfname') at cursor using the system
default handler, by calling |vim.ui.open()|.
default handler. Mapped to |vim.ui.open()|.
*v_gx*
{Visual}gx Opens the selected text using the system default
handler, by calling |vim.ui.open()|.
handler. Mapped to |vim.ui.open()|.
*:p* *:pr* *:print* *E749*
:[range]p[rint] [flags]
@ -611,6 +611,8 @@ to look up the value of 'commentstring' corresponding to the cursor position.
(This can be different from the buffer's 'commentstring' in case of
|treesitter-language-injections|.)
The following |default-mappings| are defined:
*gc* *gc-default*
gc{motion} Comment or uncomment lines covered by {motion}.

View File

@ -383,6 +383,7 @@ Options:
- 'showcmdloc' cannot be set to empty.
- 'signcolumn' can show multiple signs (dynamic or fixed columns)
- 'statuscolumn' full control of columns using 'statusline' format
- 'statusline' default is exposed as a statusline expression.
- 'splitkeep' cannot be set to empty.
- 'tabline' middle-click on tabpage label closes tabpage,
and %@Func@foo%X can call any function on mouse-click

View File

@ -439,18 +439,17 @@ CTRL-W l Move cursor to Nth window right of current one. Uses the
CTRL-W w *CTRL-W_w* *CTRL-W_CTRL-W*
CTRL-W CTRL-W Without count: move cursor to the |focusable| window
below/right of the current one. If there is no (focusable)
window below or right, go to top-left window. With count: go
to Nth window (windows are numbered from top-left to
bottom-right). To obtain the window number see |bufwinnr()|
and |winnr()|. When N is larger than the number of windows go
to the last window.
below/right of the current one. If none, go to the top-left
window. With count: go to Nth window (numbered top-left to
bottom-right), skipping unfocusable windows. To obtain the
window number see |bufwinnr()| and |winnr()|. When N is
larger than the number of windows go to the last focusable
window.
*CTRL-W_W*
CTRL-W W Without count: move cursor to the |focusable| window
above/left of current one. If there is no window above or
left, go to bottom-right window. With count: go to Nth
window, like with CTRL-W w.
above/left of current one. If none, go to the bottom-right
window. With count: go to Nth window, like CTRL-W w.
CTRL-W t *CTRL-W_t* *CTRL-W_CTRL-T*
CTRL-W CTRL-T Move cursor to top-left window.
@ -1273,7 +1272,7 @@ list of buffers. |unlisted-buffer|
:w foobar | sp #
< Also see |+cmd|.
:[N]bn[ext][!] [+cmd] [N] *:bn* *:bnext* *]b* *E87*
:[N]bn[ext][!] [+cmd] [N] *:bn* *:bnext* *E87*
Go to [N]th next buffer in buffer list. [N] defaults to one.
Wraps around the end of the buffer list.
See |:buffer-!| for [!].
@ -1285,13 +1284,20 @@ list of buffers. |unlisted-buffer|
the way when you're browsing code/text buffers. The next three
commands also work like this.
*]b*
]b Mapped to |:bnext|. |default-mappings|
*:sbn* *:sbnext*
:[N]sbn[ext] [+cmd] [N]
Split window and go to [N]th next buffer in buffer list.
Wraps around the end of the buffer list. Uses 'switchbuf'
Also see |+cmd|.
:[N]bN[ext][!] [+cmd] [N] *:bN* *:bNext* *:bp* *:bprevious* *[b* *E88*
:[N]bN[ext][!] [+cmd] [N] *:bN* *:bNext* *:bp* *:bprevious* *E88*
*[b*
[b Mapped to |:bprevious|. |default-mappings|
:[N]bp[revious][!] [+cmd] [N]
Go to [N]th previous buffer in buffer list. [N] defaults to
one. Wraps around the start of the buffer list.
@ -1305,11 +1311,14 @@ list of buffers. |unlisted-buffer|
Uses 'switchbuf'.
Also see |+cmd|.
:br[ewind][!] [+cmd] *:br* *:bre* *:brewind* *[B*
:br[ewind][!] [+cmd] *:br* *:bre* *:brewind*
Go to first buffer in buffer list. If the buffer list is
empty, go to the first unlisted buffer.
See |:buffer-!| for [!].
*[B*
[B Mapped to |:brewind|. |default-mappings|
:bf[irst] [+cmd] *:bf* *:bfirst*
Same as |:brewind|.
Also see |+cmd|.
@ -1323,11 +1332,14 @@ list of buffers. |unlisted-buffer|
:sbf[irst] [+cmd] *:sbf* *:sbfirst*
Same as ":sbrewind".
:bl[ast][!] [+cmd] *:bl* *:blast* *]B*
:bl[ast][!] [+cmd] *:bl* *:blast*
Go to last buffer in buffer list. If the buffer list is
empty, go to the last unlisted buffer.
See |:buffer-!| for [!].
*]B*
]B Mapped to |:blast|. |default-mappings|
:sbl[ast] [+cmd] *:sbl* *:sblast*
Split window and go to last buffer in buffer list. If the
buffer list is empty, go to the last unlisted buffer.

88
runtime/example_init.lua Normal file
View File

@ -0,0 +1,88 @@
-- Set <space> as the leader key
-- See `:help mapleader`
-- NOTE: Must happen before plugins are loaded (otherwise wrong leader will be used)
vim.g.mapleader = ' '
-- [[ Setting options ]] See `:h vim.o`
-- NOTE: You can change these options as you wish!
-- For more options, you can see `:help option-list`
-- To see documentation for an option, you can use `:h 'optionname'`, for example `:h 'number'`
-- (Note the single quotes)
-- Print the line number in front of each line
vim.o.number = true
-- Use relative line numbers, so that it is easier to jump with j, k. This will affect the 'number'
-- option above, see `:h number_relativenumber`
vim.o.relativenumber = true
-- Sync clipboard between OS and Neovim. Schedule the setting after `UiEnter` because it can
-- increase startup-time. Remove this option if you want your OS clipboard to remain independent.
-- See `:help 'clipboard'`
vim.api.nvim_create_autocmd('UIEnter', {
callback = function()
vim.o.clipboard = 'unnamedplus'
end,
})
-- Case-insensitive searching UNLESS \C or one or more capital letters in the search term
vim.o.ignorecase = true
vim.o.smartcase = true
-- Highlight the line where the cursor is on
vim.o.cursorline = true
-- Minimal number of screen lines to keep above and below the cursor.
vim.o.scrolloff = 10
-- Show <tab> and trailing spaces
vim.o.list = true
-- if performing an operation that would fail due to unsaved changes in the buffer (like `:q`),
-- instead raise a dialog asking if you wish to save the current file(s) See `:help 'confirm'`
vim.o.confirm = true
-- [[ Set up keymaps ]] See `:h vim.keymap.set()`, `:h mapping`, `:h keycodes`
-- Use <Esc> to exit terminal mode
vim.keymap.set('t', '<Esc>', '<C-\\><C-n>')
-- Map <A-j>, <A-k>, <A-h>, <A-l> to navigate between windows in any modes
vim.keymap.set({ 't', 'i' }, '<A-h>', '<C-\\><C-n><C-w>h')
vim.keymap.set({ 't', 'i' }, '<A-j>', '<C-\\><C-n><C-w>j')
vim.keymap.set({ 't', 'i' }, '<A-k>', '<C-\\><C-n><C-w>k')
vim.keymap.set({ 't', 'i' }, '<A-l>', '<C-\\><C-n><C-w>l')
vim.keymap.set({ 'n' }, '<A-h>', '<C-w>h')
vim.keymap.set({ 'n' }, '<A-j>', '<C-w>j')
vim.keymap.set({ 'n' }, '<A-k>', '<C-w>k')
vim.keymap.set({ 'n' }, '<A-l>', '<C-w>l')
-- [[ Basic Autocommands ]].
-- See `:h lua-guide-autocommands`, `:h autocmd`, `:h nvim_create_autocmd()`
-- Highlight when yanking (copying) text.
-- Try it with `yap` in normal mode. See `:h vim.hl.on_yank()`
vim.api.nvim_create_autocmd('TextYankPost', {
desc = 'Highlight when yanking (copying) text',
callback = function()
vim.hl.on_yank()
end,
})
-- [[ Create user commands ]]
-- See `:h nvim_create_user_command()` and `:h user-commands`
-- Create a command `:GitBlameLine` that print the git blame for the current line
vim.api.nvim_create_user_command('GitBlameLine', function()
local line_number = vim.fn.line('.') -- Get the current line number. See `:h line()`
local filename = vim.api.nvim_buf_get_name(0)
print(vim.fn.system({ 'git', 'blame', '-L', line_number .. ',+1', filename }))
end, { desc = 'Print the git blame for the current line' })
-- [[ Add optional packages ]]
-- Nvim comes bundled with a set of packages that are not enabled by
-- default. You can enable any of them by using the `:packadd` command.
-- For example, to add the "nohlsearch" package to automatically turn off search highlighting after
-- 'updatetime' and when going to insert mode
vim.cmd('packadd! nohlsearch')

View File

@ -78,4 +78,7 @@ function s:LuaInclude(fname) abort
return fname
endfunction
let &cpo = s:cpo_save
unlet s:cpo_save
" vim: nowrap sw=2 sts=2 ts=8 noet:

View File

@ -1,19 +1,18 @@
" vim: fdm=marker
" Tutor filetype plugin
" Language: Tutor (the new tutor plugin)
" Maintainer: This runtime file is looking for a new maintainer.
" Last Change: 2025 May 10
" Contributors: Phạm Bình An <phambinhanctb2004@gmail.com>
" Original Author: Felipe Morales <hel.sheep@gmail.com>
" Last Change:
" 2025 May 10 set b:undo_ftplugin
" 2025 May 12 update b:undo_ftplugin
" Base: {{{1
call tutor#SetupVim()
" Buffer Settings: {{{1
setlocal noreadonly
if !exists('g:tutor_debug') || g:tutor_debug == 0
setlocal buftype=nofile
setlocal concealcursor+=inv
setlocal conceallevel=2
else
setlocal buftype=
setlocal concealcursor&
setlocal conceallevel=0
endif
setlocal noundofile
setlocal keywordprg=:help
@ -39,7 +38,7 @@ call tutor#SetNormalMappings()
sign define tutorok text=texthl=tutorOK
sign define tutorbad text=texthl=tutorX
if !exists('g:tutor_debug') || g:tutor_debug == 0
call tutor#ApplyMarks()
autocmd! TextChanged,TextChangedI <buffer> call tutor#ApplyMarksOnChanged()
endif
let b:undo_ftplugin = "setl foldmethod< foldexpr< foldlevel< undofile< keywordprg< iskeyword< |"
\ . "call tutor#EnableInteractive(v:false) |"
" vim: fdm=marker

View File

@ -810,6 +810,8 @@ function M.show_toc()
fn.setloclist(0, {}, 'a', { title = 'Table of contents' })
vim.cmd.lopen()
vim.w.qf_toc = bufname
-- reload syntax file after setting qf_toc variable
vim.bo.filetype = 'qf'
end
return M

View File

@ -3,13 +3,15 @@ local M = {}
--- @param module string
---@return string
function M.includeexpr(module)
local fname = module:gsub('%.', '/')
module = module:gsub('%.', '/')
local root = vim.fs.root(vim.api.nvim_buf_get_name(0), 'lua') or vim.fn.getcwd()
for _, suf in ipairs { '.lua', '/init.lua' } do
local path = vim.fs.joinpath(root, 'lua', fname .. suf)
if vim.uv.fs_stat(path) then
return path
for _, fname in ipairs { module, vim.fs.joinpath(root, 'lua', module) } do
for _, suf in ipairs { '.lua', '/init.lua' } do
local path = fname .. suf
if vim.uv.fs_stat(path) then
return path
end
end
end

View File

@ -297,3 +297,18 @@
--- A list of dictionaries with information about
--- undo blocks.
--- @field entries vim.fn.undotree.entry[]
--- @class vim.fn.winlayout.leaf
--- @field [1] "leaf" Node type
--- @field [2] integer winid
--- @class vim.fn.winlayout.branch
--- @field [1] "row" | "col" Node type
--- @field [2] (vim.fn.winlayout.leaf|vim.fn.winlayout.branch)[] children
--- @class vim.fn.winlayout.empty
--- @alias vim.fn.winlayout.ret
--- | vim.fn.winlayout.leaf
--- | vim.fn.winlayout.branch
--- | vim.fn.winlayout.empty

View File

@ -2019,6 +2019,15 @@ vim.bo.et = vim.bo.expandtab
--- - 'exrc' can execute any code; editorconfig only specifies settings.
--- - 'exrc' is Nvim-specific; editorconfig works in other editors.
---
--- To achieve project-local LSP configuration:
--- 1. Enable 'exrc'.
--- 2. Place LSP configs at ".nvim/lsp/*.lua" in your project root.
--- 3. Create ".nvim.lua" in your project root directory with this line:
---
--- ```lua
--- vim.cmd[[set runtimepath+=.nvim]]
--- ```
---
--- This option cannot be set from a `modeline` or in the `sandbox`, for
--- security reasons.
---
@ -6343,8 +6352,7 @@ vim.o.stc = vim.o.statuscolumn
vim.wo.statuscolumn = vim.o.statuscolumn
vim.wo.stc = vim.wo.statuscolumn
--- When non-empty, this option determines the content of the status line.
--- Also see `status-line`.
--- Sets the `status-line`.
---
--- The option consists of printf style '%' items interspersed with
--- normal text. Each status line item is of the form:

View File

@ -10929,7 +10929,7 @@ function vim.fn.winheight(nr) end
--- <
---
--- @param tabnr? integer
--- @return any[]
--- @return vim.fn.winlayout.ret
function vim.fn.winlayout(tabnr) end
--- The result is a Number, which is the screen line of the cursor

View File

@ -245,7 +245,13 @@ local function spawn(cmd, opts, on_exit, on_error)
local handle, pid_or_err = uv.spawn(cmd, opts, on_exit)
if not handle then
on_error()
error(('%s: "%s"'):format(pid_or_err, cmd))
if opts.cwd and not uv.fs_stat(opts.cwd) then
error(("%s (cwd): '%s'"):format(pid_or_err, opts.cwd))
elseif vim.fn.executable(cmd) == 0 then
error(("%s (cmd): '%s'"):format(pid_or_err, cmd))
else
error(pid_or_err)
end
end
return handle, pid_or_err --[[@as integer]]
end

View File

@ -2605,7 +2605,7 @@ end
--- Filetype mappings can be added either by extension or by filename (either
--- the "tail" or the full file path). The full file path is checked first,
--- followed by the file name. If a match is not found using the filename, then
--- the filename is matched against the list of |lua-patterns| (sorted by priority)
--- the filename is matched against the list of |lua-pattern|s (sorted by priority)
--- until a match is found. Lastly, if pattern matching does not find a
--- filetype, then the file extension is used.
---

View File

@ -6,7 +6,7 @@
---
--- >lua
--- if vim.uv.fs_stat(file) then
--- vim.print("file exists")
--- vim.print('file exists')
--- end
--- <
@ -19,21 +19,21 @@ local sysname = uv.os_uname().sysname:lower()
local iswin = not not (sysname:find('windows') or sysname:find('mingw'))
local os_sep = iswin and '\\' or '/'
--- Iterate over all the parents of the given path.
--- Iterate over all the parents of the given path (not expanded/resolved, the caller must do that).
---
--- Example:
---
--- ```lua
--- local root_dir
--- for dir in vim.fs.parents(vim.api.nvim_buf_get_name(0)) do
--- if vim.fn.isdirectory(dir .. "/.git") == 1 then
--- if vim.fn.isdirectory(dir .. '/.git') == 1 then
--- root_dir = dir
--- break
--- end
--- end
---
--- if root_dir then
--- print("Found git repository at", root_dir)
--- print('Found git repository at', root_dir)
--- end
--- ```
---
@ -55,7 +55,7 @@ function M.parents(start)
start
end
--- Return the parent directory of the given path
--- Gets the parent directory of the given path (not expanded/resolved, the caller must do that).
---
---@since 10
---@generic T : string|nil
@ -234,16 +234,17 @@ end
--- Examples:
---
--- ```lua
--- -- list all test directories under the runtime directory
--- local test_dirs = vim.fs.find(
--- {'test', 'tst', 'testdir'},
--- {limit = math.huge, type = 'directory', path = './runtime/'}
--- -- List all test directories under the runtime directory.
--- local dirs = vim.fs.find(
--- { 'test', 'tst', 'testdir' },
--- { limit = math.huge, type = 'directory', path = './runtime/' }
--- )
---
--- -- get all files ending with .cpp or .hpp inside lib/
--- local cpp_hpp = vim.fs.find(function(name, path)
--- -- Get all "lib/*.cpp" and "lib/*.hpp" files, using Lua patterns.
--- -- Or use `vim.glob.to_lpeg(…):match(…)` for glob/wildcard matching.
--- local files = vim.fs.find(function(name, path)
--- return name:match('.*%.[ch]pp$') and path:match('[/\\]lib$')
--- end, {limit = math.huge, type = 'file'})
--- end, { limit = math.huge, type = 'file' })
--- ```
---
---@since 10

View File

@ -1,16 +1,16 @@
--- @brief
---<pre>help
--- vim.health is a minimal framework to help users troubleshoot configuration and
--- any other environment conditions that a plugin might care about. Nvim ships
--- with healthchecks for configuration, performance, python support, ruby
--- support, clipboard support, and more.
---
--- To run all healthchecks, use: >vim
--- vim.health is a minimal framework to help users troubleshoot configuration and any other
--- environment conditions that a plugin might care about. Nvim ships with healthchecks for
--- configuration, performance, python support, ruby support, clipboard support, and more.
---
--- :checkhealth
--- <
--- To run all healthchecks, use:
--- ```vim
--- :checkhealth
--- ```
--- Plugin authors are encouraged to write new healthchecks. |health-dev|
---
---<pre>help
--- COMMANDS *health-commands*
---
--- *:che* *:checkhealth*
@ -46,7 +46,6 @@
--- q Closes the window.
---
--- Global configuration:
---
--- *g:health*
--- g:health Dictionary with the following optional keys:
--- - `style` (`'float'|nil`) Set to "float" to display :checkhealth in
@ -55,57 +54,69 @@
--- Example: >lua
--- vim.g.health = { style = 'float' }
---
---</pre>
---
--- Local configuration:
---
--- Checkhealth sets its buffer filetype to "checkhealth". You can customize the buffer by handling
--- the |FileType| event. For example if you don't want emojis in the health report:
--- ```vim
--- autocmd FileType checkhealth :set modifiable | silent! %s/\v( ?[^\x00-\x7F])//g
--- ```
---
---<pre>help
--- --------------------------------------------------------------------------------
--- Create a healthcheck *health-dev*
---</pre>
---
--- Healthchecks are functions that check the user environment, configuration, or
--- any other prerequisites that a plugin cares about. Nvim ships with
--- healthchecks in:
--- - $VIMRUNTIME/autoload/health/
--- - $VIMRUNTIME/lua/vim/lsp/health.lua
--- - $VIMRUNTIME/lua/vim/treesitter/health.lua
--- - and more...
--- Healthchecks are functions that check the user environment, configuration, or any other
--- prerequisites that a plugin cares about. Nvim ships with healthchecks in:
--- - $VIMRUNTIME/autoload/health/
--- - $VIMRUNTIME/lua/vim/lsp/health.lua
--- - $VIMRUNTIME/lua/vim/treesitter/health.lua
--- - and more...
---
--- To add a new healthcheck for your own plugin, simply create a "health.lua"
--- module on 'runtimepath' that returns a table with a "check()" function. Then
--- |:checkhealth| will automatically find and invoke the function.
--- To add a new healthcheck for your own plugin, simply create a "health.lua" module on
--- 'runtimepath' that returns a table with a "check()" function. Then |:checkhealth| will
--- automatically find and invoke the function.
---
--- For example if your plugin is named "foo", define your healthcheck module at
--- one of these locations (on 'runtimepath'):
--- - lua/foo/health/init.lua
--- - lua/foo/health.lua
--- - lua/foo/health/init.lua
--- - lua/foo/health.lua
---
--- If your plugin also provides a submodule named "bar" for which you want
--- a separate healthcheck, define the healthcheck at one of these locations:
--- - lua/foo/bar/health/init.lua
--- - lua/foo/bar/health.lua
--- If your plugin also provides a submodule named "bar" for which you want a separate healthcheck,
--- define the healthcheck at one of these locations:
--- - lua/foo/bar/health/init.lua
--- - lua/foo/bar/health.lua
---
--- All such health modules must return a Lua table containing a `check()`
--- function.
--- All such health modules must return a Lua table containing a `check()` function.
---
--- Copy this sample code into `lua/foo/health.lua`, replacing "foo" in the path
--- with your plugin name: >lua
--- Copy this sample code into `lua/foo/health.lua`, replacing "foo" in the path with your plugin
--- name:
---
--- local M = {}
--- ```lua
--- local M = {}
---
--- M.check = function()
--- vim.health.start("foo report")
--- -- make sure setup function parameters are ok
--- if check_setup() then
--- vim.health.ok("Setup is correct")
--- else
--- vim.health.error("Setup is incorrect")
--- end
--- -- do some more checking
--- -- ...
--- end
--- M.check = function()
--- vim.health.start("foo report")
--- -- make sure setup function parameters are ok
--- if check_setup() then
--- vim.health.ok("Setup is correct")
--- else
--- vim.health.error("Setup is incorrect")
--- end
--- -- do some more checking
--- -- ...
--- end
---
--- return M
---</pre>
--- return M
--- ```
local M = {}
local s_output = {} ---@type string[]
local check_summary = { warn = 0, error = 0 }
-- From a path return a list [{name}, {func}, {type}] representing a healthcheck
local function filepath_to_healthcheck(path)
@ -286,6 +297,7 @@ end
function M.warn(msg, ...)
local input = format_report_message('⚠️ WARNING', msg, ...)
collect_output(input)
check_summary['warn'] = check_summary['warn'] + 1
end
--- Reports an error.
@ -295,6 +307,7 @@ end
function M.error(msg, ...)
local input = format_report_message('❌ ERROR', msg, ...)
collect_output(input)
check_summary['error'] = check_summary['error'] + 1
end
local path2name = function(path)
@ -341,6 +354,23 @@ M._complete = function()
return rv
end
--- Gets the results heading for the current report section.
---
---@return string
local function get_summary()
local s = ''
local errors = check_summary['error']
local warns = check_summary['warn']
s = s .. (warns > 0 and (' %2d ⚠️'):format(warns) or '')
s = s .. (errors > 0 and (' %2d ❌'):format(errors) or '')
if errors == 0 and warns == 0 then
s = s .. ''
end
return s
end
--- Runs the specified healthchecks.
--- Runs all discovered healthchecks if plugin_names is empty.
---
@ -353,7 +383,7 @@ function M._check(mods, plugin_names)
local emptybuf = vim.fn.bufnr('$') == 1 and vim.fn.getline(1) == '' and 1 == vim.fn.line('$')
local bufnr = vim.api.nvim_create_buf(true, true)
local bufnr ---@type integer
if
vim.g.health
and type(vim.g.health) == 'table'
@ -361,7 +391,8 @@ function M._check(mods, plugin_names)
then
local max_height = math.floor(vim.o.lines * 0.8)
local max_width = 80
local float_bufnr, float_winid = vim.lsp.util.open_floating_preview({}, '', {
local float_winid
bufnr, float_winid = vim.lsp.util.open_floating_preview({}, '', {
height = max_height,
width = max_width,
offset_x = math.floor((vim.o.columns - max_width) / 2),
@ -369,9 +400,10 @@ function M._check(mods, plugin_names)
relative = 'editor',
})
vim.api.nvim_set_current_win(float_winid)
vim.bo[float_bufnr].modifiable = true
vim.bo[bufnr].modifiable = true
vim.wo[float_winid].list = false
else
bufnr = vim.api.nvim_create_buf(true, true)
-- When no command modifiers are used:
-- - If the current buffer is empty, open healthcheck directly.
-- - If not specified otherwise open healthcheck in a tab.
@ -383,7 +415,6 @@ function M._check(mods, plugin_names)
vim.cmd.bwipe('health://')
end
vim.cmd.file('health://')
vim.cmd.setfiletype('checkhealth')
-- This should only happen when doing `:checkhealth vim`
if next(healthchecks) == nil then
@ -397,9 +428,9 @@ function M._check(mods, plugin_names)
local func = value[1]
local type = value[2]
s_output = {}
check_summary = { warn = 0, error = 0 }
if func == '' then
s_output = {}
M.error('No healthcheck found for "' .. name .. '" plugin.')
end
if type == 'v' then
@ -420,10 +451,12 @@ function M._check(mods, plugin_names)
M.error('The healthcheck report for "' .. name .. '" plugin is empty.')
end
local report = get_summary()
local replen = vim.fn.strwidth(report)
local header = {
string.rep('=', 78),
-- Example: `foo.health: [ …] require("foo.health").check()`
('%s: %s%s'):format(name, (' '):rep(76 - name:len() - func:len()), func),
-- Example: `foo.health: [ …] 1 ⚠️ 5 ❌`
('%s: %s%s'):format(name, (' '):rep(76 - name:len() - replen), report),
'',
}
@ -461,6 +494,7 @@ function M._check(mods, plugin_names)
-- Once we're done writing checks, set nomodifiable.
vim.bo[bufnr].modifiable = false
vim.cmd.setfiletype('checkhealth')
end
return M

View File

@ -147,7 +147,7 @@ end
--- @param config vim.lsp.ClientConfig
--- @return boolean
local function reuse_client_default(client, config)
if client.name ~= config.name then
if client.name ~= config.name or client:is_stopped() then
return false
end
@ -277,24 +277,52 @@ end
--- See `cmd` in [vim.lsp.ClientConfig].
--- @field cmd? string[]|fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient
---
--- Filetypes the client will attach to, if activated by `vim.lsp.enable()`.
--- If not provided, then the client will attach to all filetypes.
--- Filetypes the client will attach to, if activated by `vim.lsp.enable()`. If not provided, the
--- client will attach to all filetypes.
--- @field filetypes? string[]
---
--- Predicate which decides if a client should be re-used. Used on all running clients. The default
--- implementation re-uses a client if name and root_dir matches.
--- @field reuse_client? fun(client: vim.lsp.Client, config: vim.lsp.ClientConfig): boolean
---
--- [lsp-root_dir()]() Directory where the LSP server will base its workspaceFolders, rootUri, and
--- rootPath on initialization. The function form receives a buffer number and `on_dir` callback
--- which it must call to provide root_dir, or LSP will not be activated for the buffer. Thus
--- a `root_dir()` function can dynamically decide per-buffer whether to activate (or skip) LSP. See
--- example at |vim.lsp.enable()|.
--- @field root_dir? string|fun(bufnr: integer, on_dir:fun(root_dir?:string))
---
--- Directory markers (.e.g. '.git/') where the LSP server will base its workspaceFolders,
--- rootUri, and rootPath on initialization. Unused if `root_dir` is provided.
--- @field root_markers? string[]
---
--- Directory where the LSP server will base its workspaceFolders, rootUri, and
--- rootPath on initialization. If a function, it is passed the buffer number
--- and a callback argument which must be called with the value of root_dir to
--- use. The LSP server will not be started until the callback is called.
--- @field root_dir? string|fun(bufnr: integer, cb:fun(root_dir?:string))
--- The list order decides the priority. To indicate "equal priority", specify names in a nested list (`{ { 'a', 'b' }, ... }`)
--- Each entry in this list is a set of one or more markers. For each set, Nvim
--- will search upwards for each marker contained in the set. If a marker is
--- found, the directory which contains that marker is used as the root
--- directory. If no markers from the set are found, the process is repeated
--- with the next set in the list.
---
--- Predicate used to decide if a client should be re-used. Used on all
--- running clients. The default implementation re-uses a client if name and
--- root_dir matches.
--- @field reuse_client? fun(client: vim.lsp.Client, config: vim.lsp.ClientConfig): boolean
--- Example:
---
--- ```lua
--- root_markers = { 'stylua.toml', '.git' }
--- ```
---
--- Find the first parent directory containing the file `stylua.toml`. If not
--- found, find the first parent directory containing the file or directory
--- `.git`.
---
--- Example:
---
--- ```lua
--- root_markers = { { 'stylua.toml', '.luarc.json' }, '.git' }
--- ```
---
--- Find the first parent directory containing EITHER `stylua.toml` or
--- `.luarc.json`. If not found, find the first parent directory containing the
--- file or directory `.git`.
---
--- @field root_markers? (string|string[])[]
--- Update the configuration for an LSP client.
---
@ -429,7 +457,8 @@ lsp.config = setmetatable({ _configs = {} }, {
--- @param cfg vim.lsp.Config
__newindex = function(self, name, cfg)
validate_config_name(name)
validate('cfg', cfg, 'table')
local msg = ('table (hint: to resolve a config, use vim.lsp.config["%s"])'):format(name)
validate('cfg', cfg, 'table', msg)
invalidate_enabled_config(name)
self._configs[name] = cfg
end,
@ -439,7 +468,8 @@ lsp.config = setmetatable({ _configs = {} }, {
--- @param cfg vim.lsp.Config
__call = function(self, name, cfg)
validate_config_name(name)
validate('cfg', cfg, 'table')
local msg = ('table (hint: to resolve a config, use vim.lsp.config["%s"])'):format(name)
validate('cfg', cfg, 'table', msg)
invalidate_enabled_config(name)
self[name] = vim.tbl_deep_extend('force', self._configs[name] or {}, cfg)
end,
@ -498,6 +528,15 @@ local function lsp_enable_callback(bufnr)
return
end
-- Stop any clients that no longer apply to this buffer.
local clients = lsp.get_clients({ bufnr = bufnr, _uninitialized = true })
for _, client in ipairs(clients) do
if lsp.config[client.name] and not can_start(bufnr, client.name, lsp.config[client.name]) then
lsp.buf_detach_client(bufnr, client.id)
end
end
-- Start any clients that apply to this buffer.
for name in vim.spairs(lsp._enabled_configs) do
local config = lsp.config[name]
if config and can_start(bufnr, name, config) then
@ -520,16 +559,27 @@ local function lsp_enable_callback(bufnr)
end
end
--- Enable an LSP server to automatically start when opening a buffer.
---
--- Uses configuration defined with `vim.lsp.config`.
--- Auto-starts LSP when a buffer is opened, based on the |lsp-config| `filetypes`, `root_markers`,
--- and `root_dir` fields.
---
--- Examples:
---
--- ```lua
--- vim.lsp.enable('clangd')
--- vim.lsp.enable('clangd')
--- vim.lsp.enable({'luals', 'pyright'})
--- ```
---
--- vim.lsp.enable({'luals', 'pyright'})
--- Example: To _dynamically_ decide whether LSP is activated, define a |lsp-root_dir()| function
--- which calls `on_dir()` only when you want that config to activate:
---
--- ```lua
--- vim.lsp.config('lua_ls', {
--- root_dir = function(bufnr, on_dir)
--- if not vim.fn.bufname(bufnr):match('%.txt$') then
--- on_dir(vim.fn.getcwd())
--- end
--- end
--- })
--- ```
---
--- @param name string|string[] Name(s) of client(s) to enable.
@ -546,21 +596,42 @@ function lsp.enable(name, enable)
end
if not next(lsp._enabled_configs) then
-- If there are no remaining LSPs enabled, remove the enable autocmd.
if lsp_enable_autocmd_id then
api.nvim_del_autocmd(lsp_enable_autocmd_id)
lsp_enable_autocmd_id = nil
end
return
else
-- Only ever create autocmd once to reuse computation of config merging.
lsp_enable_autocmd_id = lsp_enable_autocmd_id
or api.nvim_create_autocmd('FileType', {
group = api.nvim_create_augroup('nvim.lsp.enable', {}),
callback = function(args)
lsp_enable_callback(args.buf)
end,
})
end
-- Only ever create autocmd once to reuse computation of config merging.
lsp_enable_autocmd_id = lsp_enable_autocmd_id
or api.nvim_create_autocmd('FileType', {
group = api.nvim_create_augroup('nvim.lsp.enable', {}),
callback = function(args)
lsp_enable_callback(args.buf)
end,
})
-- Ensure any pre-existing buffers start/stop their LSP clients.
if enable ~= false then
if vim.v.vim_did_enter == 1 then
vim.cmd.doautoall('nvim.lsp.enable FileType')
end
else
for _, nm in ipairs(names) do
for _, client in ipairs(lsp.get_clients({ name = nm })) do
client:stop()
end
end
end
end
--- Checks if the given LSP config is enabled (globally, not per-buffer).
---
--- @param name string Config name
--- @return boolean
function lsp.is_enabled(name)
return lsp._enabled_configs[name] ~= nil
end
--- @class vim.lsp.start.Opts
@ -582,7 +653,7 @@ end
--- Suppress error reporting if the LSP server fails to start (default false).
--- @field silent? boolean
---
--- @field package _root_markers? string[]
--- @field package _root_markers? (string|string[])[]
--- Create a new LSP client and start a language server or reuses an already
--- running client if one is found matching `name` and `root_dir`.
@ -629,8 +700,16 @@ function lsp.start(config, opts)
local bufnr = vim._resolve_bufnr(opts.bufnr)
if not config.root_dir and opts._root_markers then
validate('root_markers', opts._root_markers, 'table')
config = vim.deepcopy(config)
config.root_dir = vim.fs.root(bufnr, opts._root_markers)
for _, marker in ipairs(opts._root_markers) do
local root = vim.fs.root(bufnr, marker)
if root ~= nil then
config.root_dir = root
break
end
end
end
if

View File

@ -1211,8 +1211,7 @@ local function on_code_action_results(results, opts)
vim.ui.select(actions, select_opts, on_user_choice)
end
--- Selects a code action available at the current
--- cursor position.
--- Selects a code action (LSP: "textDocument/codeAction" request) available at cursor position.
---
---@param opts? vim.lsp.buf.code_action.Opts
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction

View File

@ -30,6 +30,19 @@ local validate = vim.validate
--- @field exit_timeout integer|false
--- @class vim.lsp.ClientConfig
---
--- Callback invoked before the LSP "initialize" phase, where `params` contains the parameters
--- being sent to the server and `config` is the config that was passed to |vim.lsp.start()|.
--- You can use this to modify parameters before they are sent.
--- @field before_init? fun(params: lsp.InitializeParams, config: vim.lsp.ClientConfig)
---
--- Map overriding the default capabilities defined by |vim.lsp.protocol.make_client_capabilities()|,
--- passed to the language server on initialization. Hint: use make_client_capabilities() and modify
--- its result.
--- - Note: To send an empty dictionary use |vim.empty_dict()|, else it will be encoded as an
--- array.
--- @field capabilities? lsp.ClientCapabilities
---
--- command string[] that launches the language
--- server (treated as in |jobstart()|, must be absolute or on `$PATH`, shell constructs like
--- "~" are not expanded), or function that creates an RPC client. Function receives
@ -43,123 +56,129 @@ local validate = vim.validate
--- (default: cwd)
--- @field cmd_cwd? string
---
--- Environment flags to pass to the LSP on spawn.
--- Must be specified using a table.
--- Non-string values are coerced to string.
--- Environment variables passed to the LSP process on spawn. Non-string values are coerced to
--- string.
--- Example:
--- ```lua
--- { PORT = 8080; HOST = "0.0.0.0"; }
--- { PORT = 8080; HOST = '0.0.0.0'; }
--- ```
--- @field cmd_env? table
---
--- Client commands. Map of command names to user-defined functions. Commands passed to `start()`
--- take precedence over the global command registry. Each key must be a unique command name, and
--- the value is a function which is called if any LSP action (code action, code lenses, …) triggers
--- the command.
--- @field commands? table<string,fun(command: lsp.Command, ctx: table)>
---
--- Daemonize the server process so that it runs in a separate process group from Nvim.
--- Nvim will shutdown the process on exit, but if Nvim fails to exit cleanly this could leave
--- behind orphaned server processes.
--- (default: true)
--- @field detached? boolean
---
--- List of workspace folders passed to the language server.
--- For backwards compatibility rootUri and rootPath will be derived from the first workspace
--- folder in this list. See `workspaceFolders` in the LSP spec.
--- @field workspace_folders? lsp.WorkspaceFolder[]
--- A table with flags for the client. The current (experimental) flags are:
--- @field flags? vim.lsp.Client.Flags
---
--- (default false) Server requires a workspace (no "single file" support).
--- @field workspace_required? boolean
--- Language ID as string. Defaults to the buffer filetype.
--- @field get_language_id? fun(bufnr: integer, filetype: string): string
---
--- Map overriding the default capabilities defined by |vim.lsp.protocol.make_client_capabilities()|,
--- passed to the language server on initialization. Hint: use make_client_capabilities() and modify
--- its result.
--- - Note: To send an empty dictionary use |vim.empty_dict()|, else it will be encoded as an
--- array.
--- @field capabilities? lsp.ClientCapabilities
---
--- Map of language server method names to |lsp-handler|
--- Map of LSP method names to |lsp-handler|s.
--- @field handlers? table<string,function>
---
--- Map with language server specific settings.
--- See the {settings} in |vim.lsp.Client|.
--- @field settings? lsp.LSPObject
---
--- Table that maps string of clientside commands to user-defined functions.
--- Commands passed to `start()` take precedence over the global command registry. Each key
--- must be a unique command name, and the value is a function which is called if any LSP action
--- (code action, code lenses, ...) triggers the command.
--- @field commands? table<string,fun(command: lsp.Command, ctx: table)>
---
--- Values to pass in the initialization request as `initializationOptions`. See `initialize` in
--- the LSP spec.
--- @field init_options? lsp.LSPObject
---
--- Name in log messages.
--- (default: client-id)
--- (default: client-id) Name in logs and user messages.
--- @field name? string
---
--- Language ID as string. Defaults to the buffer filetype.
--- @field get_language_id? fun(bufnr: integer, filetype: string): string
---
--- Called "position encoding" in LSP spec, the encoding that the LSP server expects.
--- Client does not verify this is correct.
--- Called "position encoding" in LSP spec. The encoding that the LSP server expects, used for
--- communication. Not validated. Can be modified in `on_init` before text is sent to the server.
--- @field offset_encoding? 'utf-8'|'utf-16'|'utf-32'
---
--- Callback invoked when client attaches to a buffer.
--- @field on_attach? elem_or_list<fun(client: vim.lsp.Client, bufnr: integer)>
---
--- Callback invoked when the client operation throws an error. `code` is a number describing the error.
--- Other arguments may be passed depending on the error kind. See `vim.lsp.rpc.client_errors`
--- for possible errors. Use `vim.lsp.rpc.client_errors[code]` to get human-friendly name.
--- @field on_error? fun(code: integer, err: string)
---
--- Callback invoked before the LSP "initialize" phase, where `params` contains the parameters
--- being sent to the server and `config` is the config that was passed to |vim.lsp.start()|.
--- You can use this to modify parameters before they are sent.
--- @field before_init? fun(params: lsp.InitializeParams, config: vim.lsp.ClientConfig)
---
--- Callback invoked after LSP "initialize", where `result` is a table of `capabilities` and
--- anything else the server may send. For example, clangd sends `init_result.offsetEncoding` if
--- `capabilities.offsetEncoding` was sent to it. You can only modify the `client.offset_encoding`
--- here before any notifications are sent.
--- @field on_init? elem_or_list<fun(client: vim.lsp.Client, init_result: lsp.InitializeResult)>
---
--- Callback invoked on client exit.
--- - code: exit code of the process
--- - signal: number describing the signal used to terminate (if any)
--- - client_id: client handle
--- @field on_exit? elem_or_list<fun(code: integer, signal: integer, client_id: integer)>
---
--- Callback invoked when client attaches to a buffer.
--- @field on_attach? elem_or_list<fun(client: vim.lsp.Client, bufnr: integer)>
--- Callback invoked after LSP "initialize", where `result` is a table of `capabilities` and
--- anything else the server may send. For example, clangd sends `init_result.offsetEncoding` if
--- `capabilities.offsetEncoding` was sent to it. You can only modify the `client.offset_encoding`
--- here before any notifications are sent.
--- @field on_init? elem_or_list<fun(client: vim.lsp.Client, init_result: lsp.InitializeResult)>
---
--- Directory where the LSP server will base its workspaceFolders, rootUri, and rootPath on initialization.
--- @field root_dir? string
---
--- Map of language server-specific settings, decided by the client. Sent to the LS if requested via
--- `workspace/configuration`. Keys are case-sensitive.
--- @field settings? lsp.LSPObject
---
--- Passed directly to the language server in the initialize request. Invalid/empty values will
--- (default: "off")
--- @field trace? 'off'|'messages'|'verbose'
---
--- A table with flags for the client. The current (experimental) flags are:
--- @field flags? vim.lsp.Client.Flags
--- List of workspace folders passed to the language server. For backwards compatibility rootUri and
--- rootPath are derived from the first workspace folder in this list. Can be `null` if the client
--- supports workspace folders but none are configured. See `workspaceFolders` in LSP spec.
--- @field workspace_folders? lsp.WorkspaceFolder[]
---
--- Directory where the LSP server will base its workspaceFolders, rootUri, and rootPath on initialization.
--- @field root_dir? string
--- (default false) Server requires a workspace (no "single file" support). Note: Without
--- a workspace, cross-file features (navigation, hover) may or may not work depending on the
--- language server, even if the server doesn't require a workspace.
--- @field workspace_required? boolean
--- @class vim.lsp.Client.Progress: vim.Ringbuf<{token: integer|string, value: any}>
--- @field pending table<lsp.ProgressToken,lsp.LSPAny>
--- @class vim.lsp.Client
---
--- @field attached_buffers table<integer,true>
---
--- Capabilities provided by the client (editor or tool), at startup.
--- @field capabilities lsp.ClientCapabilities
---
--- Client commands. See [vim.lsp.ClientConfig].
--- @field commands table<string,fun(command: lsp.Command, ctx: table)>
---
--- Copy of the config passed to |vim.lsp.start()|.
--- @field config vim.lsp.ClientConfig
---
--- Capabilities provided at runtime (after startup).
--- @field dynamic_capabilities lsp.DynamicCapabilities
---
--- A table with flags for the client. The current (experimental) flags are:
--- @field flags vim.lsp.Client.Flags
---
--- See [vim.lsp.ClientConfig].
--- @field get_language_id fun(bufnr: integer, filetype: string): string
---
--- See [vim.lsp.ClientConfig].
--- @field handlers table<string,lsp.Handler>
---
--- The id allocated to the client.
--- @field id integer
---
--- If a name is specified on creation, that will be used. Otherwise it is just
--- the client id. This is used for logs and messages.
--- @field initialized true?
---
--- See [vim.lsp.ClientConfig].
--- @field name string
---
--- RPC client object, for low level interaction with the client.
--- See |vim.lsp.rpc.start()|.
--- @field rpc vim.lsp.rpc.PublicClient
---
--- Called "position encoding" in LSP spec,
--- the encoding used for communicating with the server.
--- You can modify this in the `config`'s `on_init` method
--- before text is sent to the server.
--- See [vim.lsp.ClientConfig].
--- @field offset_encoding string
---
--- The handlers used by the client as described in |lsp-handler|.
--- @field handlers table<string,lsp.Handler>
--- A ring buffer (|vim.ringbuf()|) containing progress messages
--- sent by the server.
--- @field progress vim.lsp.Client.Progress
---
--- The current pending requests in flight to the server. Entries are key-value
--- pairs with the key being the request id while the value is a table with
@ -169,34 +188,25 @@ local validate = vim.validate
--- are received from the server.
--- @field requests table<integer,{ type: string, bufnr: integer, method: string}?>
---
--- copy of the table that was passed by the user
--- to |vim.lsp.start()|.
--- @field config vim.lsp.ClientConfig
---
--- Response from the server sent on `initialize` describing the server's
--- capabilities.
--- @field server_capabilities lsp.ServerCapabilities?
---
--- Response from the server sent on `initialize` describing information about
--- the server.
--- @field server_info lsp.ServerInfo?
---
--- A ring buffer (|vim.ringbuf()|) containing progress messages
--- sent by the server.
--- @field progress vim.lsp.Client.Progress
---
--- @field initialized true?
---
--- The workspace folders configured in the client when the server starts.
--- This property is only available if the client supports workspace folders.
--- It can be `null` if the client supports workspace folders but none are
--- configured.
--- @field workspace_folders lsp.WorkspaceFolder[]?
--- See [vim.lsp.ClientConfig].
--- @field root_dir string?
---
--- @field attached_buffers table<integer,true>
--- RPC client object, for low level interaction with the client.
--- See |vim.lsp.rpc.start()|.
--- @field rpc vim.lsp.rpc.PublicClient
---
--- Response from the server sent on `initialize` describing the server's capabilities.
--- @field server_capabilities lsp.ServerCapabilities?
---
--- Response from the server sent on `initialize` describing server information (e.g. version).
--- @field server_info lsp.ServerInfo?
---
--- See [vim.lsp.ClientConfig].
--- @field settings lsp.LSPObject
---
--- See [vim.lsp.ClientConfig].
--- @field workspace_folders lsp.WorkspaceFolder[]?
---
--- @field private _log_prefix string
---
--- Track this so that we can escalate automatically if we've already tried a
--- graceful shutdown
@ -206,26 +216,8 @@ local validate = vim.validate
--- trace = "off" | "messages" | "verbose";
--- @field private _trace 'off'|'messages'|'verbose'
---
--- Table of command name to function which is called if any LSP action
--- (code action, code lenses, ...) triggers the command.
--- Client commands take precedence over the global command registry.
--- @field commands table<string,fun(command: lsp.Command, ctx: table)>
---
--- Map with language server specific settings. These are returned to the
--- language server if requested via `workspace/configuration`. Keys are
--- case-sensitive.
--- @field settings lsp.LSPObject
---
--- A table with flags for the client. The current (experimental) flags are:
--- @field flags vim.lsp.Client.Flags
---
--- @field get_language_id fun(bufnr: integer, filetype: string): string
---
--- The capabilities provided by the client (editor or tool)
--- @field capabilities lsp.ClientCapabilities
--- @field private registrations table<string,lsp.Registration[]>
--- @field dynamic_capabilities lsp.DynamicCapabilities
---
--- @field private _log_prefix string
--- @field private _before_init_cb? vim.lsp.client.before_init_cb
--- @field private _on_attach_cbs vim.lsp.client.on_attach_cb[]
--- @field private _on_init_cbs vim.lsp.client.on_init_cb[]
@ -395,6 +387,7 @@ function Client.create(config)
capabilities = config.capabilities,
workspace_folders = lsp._get_workspace_folders(config.workspace_folders or config.root_dir),
root_dir = config.root_dir,
_is_stopping = false,
_before_init_cb = config.before_init,
_on_init_cbs = vim._ensure_list(config.on_init),
_on_exit_cbs = vim._ensure_list(config.on_exit),
@ -678,6 +671,12 @@ function Client:request(method, params, handler, bufnr)
bufnr = vim._resolve_bufnr(bufnr)
local version = lsp.util.buf_versions[bufnr]
log.debug(self._log_prefix, 'client.request', self.id, method, params, handler, bufnr)
-- Detect if request resolved synchronously (only possible with in-process servers).
local already_responded = false
local request_registered = false
-- NOTE: rpc.request might call an in-process (Lua) server, thus may be synchronous.
local success, request_id = self.rpc.request(method, params, function(err, result)
handler(err, result, {
method = method,
@ -688,11 +687,15 @@ function Client:request(method, params, handler, bufnr)
})
end, function(request_id)
-- Called when the server sends a response to the request (including cancelled acknowledgment).
self:_process_request(request_id, 'complete')
if request_registered then
self:_process_request(request_id, 'complete')
end
already_responded = true
end)
if success and request_id then
if success and request_id and not already_responded then
self:_process_request(request_id, 'pending', bufnr, method)
request_registered = true
end
return success, request_id
@ -802,12 +805,13 @@ end
---
--- @param force? boolean
function Client:stop(force)
local rpc = self.rpc
if rpc.is_closing() then
if self:is_stopped() then
return
end
self._is_stopping = true
local rpc = self.rpc
vim.lsp._watchfiles.cancel(self.id)
if force or not self.initialized or self._graceful_shutdown_failed then
@ -933,7 +937,7 @@ end
--- @return boolean # true if client is stopped or in the process of being
--- stopped; false otherwise
function Client:is_stopped()
return self.rpc.is_closing()
return self.rpc.is_closing() or self._is_stopping
end
--- Execute a lsp command, either via client command function (if available)

View File

@ -370,7 +370,7 @@ end
local function adjust_start_col(lnum, line, items, encoding)
local min_start_char = nil
for _, item in pairs(items) do
if item.textEdit and item.textEdit.range.start.line == lnum then
if item.textEdit and item.textEdit.range and item.textEdit.range.start.line == lnum then
if min_start_char and min_start_char ~= item.textEdit.range.start.character then
return nil
end
@ -506,14 +506,19 @@ local function trigger(bufnr, clients, ctx)
local matches = {}
local server_start_boundary --- @type integer?
for client_id, response in pairs(responses) do
local client = lsp.get_client_by_id(client_id)
if response.err then
vim.notify_once(response.err.message, vim.log.levels.WARN)
local msg = ('%s: %s %s'):format(
client and client.name or 'UNKNOWN',
response.err.code or 'NO_CODE',
response.err.message
)
vim.notify_once(msg, vim.log.levels.WARN)
end
local result = response.result
if result then
Context.isIncomplete = Context.isIncomplete or result.isIncomplete
local client = lsp.get_client_by_id(client_id)
local encoding = client and client.offset_encoding or 'utf-16'
local client_matches
client_matches, server_start_boundary = M._convert_results(

View File

@ -93,12 +93,16 @@ local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id)
message = diagnostic.message.value
end
local line = buf_lines and buf_lines[start.line + 1] or ''
local end_line = line
if _end.line > start.line then
end_line = buf_lines and buf_lines[_end.line + 1] or ''
end
--- @type vim.Diagnostic
return {
lnum = start.line,
col = vim.str_byteindex(line, position_encoding, start.character, false),
end_lnum = _end.line,
end_col = vim.str_byteindex(line, position_encoding, _end.character, false),
end_col = vim.str_byteindex(end_line, position_encoding, _end.character, false),
severity = severity_lsp_to_vim(diagnostic.severity),
message = message,
source = diagnostic.source,

View File

@ -1,4 +1,25 @@
-- Logger for language client plugin.
--- @brief
--- The `vim.lsp.log` module provides logging for the Nvim LSP client.
---
--- When debugging language servers, it is helpful to enable extra-verbose logging of the LSP client
--- RPC events. Example:
--- ```lua
--- vim.lsp.set_log_level 'trace'
--- require('vim.lsp.log').set_format_func(vim.inspect)
--- ```
---
--- Then try to run the language server, and open the log with:
--- ```vim
--- :lua vim.cmd('tabnew ' .. vim.lsp.get_log_path())
--- ```
---
--- (Or use `:LspLog` if you have nvim-lspconfig installed.)
---
--- Note:
--- - Remember to DISABLE verbose logging ("debug" or "trace" level), else you may encounter
--- performance issues.
--- - "ERROR" messages containing "stderr" only indicate that the log was sent to stderr. Many
--- servers send harmless messages via stderr.
local log = {}

View File

@ -1793,7 +1793,7 @@ function M.symbols_to_items(symbols, bufnr, position_encoding)
'symbols_to_items must be called with valid position encoding',
vim.log.levels.WARN
)
position_encoding = vim.lsp.get_clients({ bufnr = 0 })[1].offset_encoding
position_encoding = vim.lsp.get_clients({ bufnr = bufnr })[1].offset_encoding
end
local items = {} --- @type vim.quickfix.entry[]

View File

@ -21,6 +21,50 @@ local function read_trust()
return trust
end
--- If {fullpath} is a file, read the contents of {fullpath} (or the contents of {bufnr}
--- if given) and returns the contents and a hash of the contents.
---
--- If {fullpath} is a directory, then nothing is read from the filesystem, and
--- `contents = true` and `hash = "directory"` is returned instead.
---
---@param fullpath (string) Path to a file or directory to read.
---@param bufnr (number?) The number of the buffer.
---@return string|boolean? contents the contents of the file, or true if it's a directory
---@return string? hash the hash of the contents, or "directory" if it's a directory
local function compute_hash(fullpath, bufnr)
local contents ---@type string|boolean?
local hash ---@type string
if vim.fn.isdirectory(fullpath) == 1 then
return true, 'directory'
end
if bufnr then
local newline = vim.bo[bufnr].fileformat == 'unix' and '\n' or '\r\n'
contents =
table.concat(vim.api.nvim_buf_get_lines(bufnr --[[@as integer]], 0, -1, false), newline)
if vim.bo[bufnr].endofline then
contents = contents .. newline
end
else
do
local f = io.open(fullpath, 'r')
if not f then
return nil, nil
end
contents = f:read('*a')
f:close()
end
if not contents then
return nil, nil
end
end
hash = vim.fn.sha256(contents)
return contents, hash
end
--- Writes provided {trust} table to trust database at
--- $XDG_STATE_HOME/nvim/trust.
---
@ -37,17 +81,22 @@ local function write_trust(trust)
f:close()
end
--- Attempt to read the file at {path} prompting the user if the file should be
--- trusted. The user's choice is persisted in a trust database at
--- If {path} is a file: attempt to read the file, prompting the user if the file should be
--- trusted.
---
--- If {path} is a directory: return true if the directory is trusted (non-recursive), prompting
--- the user as necessary.
---
--- The user's choice is persisted in a trust database at
--- $XDG_STATE_HOME/nvim/trust.
---
---@since 11
---@see |:trust|
---
---@param path (string) Path to a file to read.
---@param path (string) Path to a file or directory to read.
---
---@return (string|nil) The contents of the given file if it exists and is
--- trusted, or nil otherwise.
---@return (boolean|string|nil) If {path} is not trusted or does not exist, returns `nil`. Otherwise,
--- returns the contents of {path} if it is a file, or true if {path} is a directory.
function M.read(path)
vim.validate('path', path, 'string')
local fullpath = vim.uv.fs_realpath(vim.fs.normalize(path))
@ -62,26 +111,25 @@ function M.read(path)
return nil
end
local contents ---@type string?
do
local f = io.open(fullpath, 'r')
if not f then
return nil
end
contents = f:read('*a')
f:close()
local contents, hash = compute_hash(fullpath, nil)
if not contents then
return nil
end
local hash = vim.fn.sha256(contents)
if trust[fullpath] == hash then
-- File already exists in trust database
return contents
end
local dir_msg = ''
if hash == 'directory' then
dir_msg = ' DIRECTORY trust is decided only by its name, not its contents.'
end
-- File either does not exist in trust database or the hash does not match
local ok, result = pcall(
vim.fn.confirm,
string.format('%s is not trusted.', fullpath),
string.format('%s is not trusted.%s', fullpath, dir_msg),
'&ignore\n&view\n&deny\n&allow',
1
)
@ -169,13 +217,10 @@ function M.trust(opts)
local trust = read_trust()
if action == 'allow' then
local newline = vim.bo[bufnr].fileformat == 'unix' and '\n' or '\r\n'
local contents =
table.concat(vim.api.nvim_buf_get_lines(bufnr --[[@as integer]], 0, -1, false), newline)
if vim.bo[bufnr].endofline then
contents = contents .. newline
local contents, hash = compute_hash(fullpath, bufnr)
if not contents then
return false, string.format('could not read path: %s', fullpath)
end
local hash = vim.fn.sha256(contents)
trust[fullpath] = hash
elseif action == 'deny' then

View File

@ -95,7 +95,7 @@ end
---
--- @see |string.gmatch()|
--- @see |vim.split()|
--- @see |lua-patterns|
--- @see |lua-pattern|s
--- @see https://www.lua.org/pil/20.2.html
--- @see http://lua-users.org/wiki/StringLibraryTutorial
---
@ -784,7 +784,7 @@ end
--- Trim whitespace (Lua pattern "%s") from both sides of a string.
---
---@see |lua-patterns|
---@see |lua-pattern|s
---@see https://www.lua.org/pil/20.2.html
---@param s string String to trim
---@return string String with whitespace removed from its beginning and end
@ -793,7 +793,7 @@ function vim.trim(s)
return s:match('^%s*(.*%S)') or ''
end
--- Escapes magic chars in |lua-patterns|.
--- Escapes magic chars in |lua-pattern|s.
---
---@see https://github.com/rxi/lume
---@param s string String to escape
@ -854,7 +854,7 @@ do
--- @param param_name string
--- @param val any
--- @param validator vim.validate.Validator
--- @param message? string
--- @param message? string "Expected" message
--- @param allow_alias? boolean Allow short type names: 'n', 's', 't', 'b', 'f', 'c'
--- @return string?
local function is_valid(param_name, val, validator, message, allow_alias)
@ -866,18 +866,18 @@ do
end
if not is_type(val, expected) then
return string.format('%s: expected %s, got %s', param_name, expected, type(val))
return ('%s: expected %s, got %s'):format(param_name, message or expected, type(val))
end
elseif vim.is_callable(validator) then
-- Check user-provided validation function
local valid, opt_msg = validator(val)
if not valid then
local err_msg =
string.format('%s: expected %s, got %s', param_name, message or '?', tostring(val))
if opt_msg then
err_msg = string.format('%s. Info: %s', err_msg, opt_msg)
end
local err_msg = ('%s: expected %s, got %s'):format(
param_name,
message or '?',
tostring(val)
)
err_msg = opt_msg and ('%s. Info: %s'):format(err_msg, opt_msg) or err_msg
return err_msg
end

View File

@ -86,6 +86,7 @@ end
--- Shows an Outline (table of contents) of the current buffer, in the loclist.
function M.show_toc()
local bufnr = api.nvim_get_current_buf()
local bufname = api.nvim_buf_get_name(bufnr)
local headings = get_headings(bufnr)
if #headings == 0 then
return
@ -102,6 +103,9 @@ function M.show_toc()
vim.fn.setloclist(0, headings, ' ')
vim.fn.setloclist(0, {}, 'a', { title = 'Table of contents' })
vim.cmd.lopen()
vim.w.qf_toc = bufname
-- reload syntax file after setting qf_toc variable
vim.bo.filetype = 'qf'
end
--- Jump to section

View File

@ -18,11 +18,14 @@ error('Cannot require a meta file')
---@field captures string[]
---@field patterns table<integer, (integer|string)[][]>
---
---@class TSLangMetadata
---@field major_version integer
---@field minor_version integer
---@field patch_version integer
---
---@class TSLangInfo
---@field abi_version integer
---@field major_version? integer
---@field minor_version? integer
---@field patch_version? integer
---@field metadata? TSLangMetadata -- ABI 15 only
---@field state_count integer
---@field fields string[]
---@field symbols table<string,boolean>

View File

@ -451,6 +451,8 @@ function M.inspect_tree(opts)
end,
})
api.nvim_buf_set_keymap(b, 'n', 'q', '<C-w>c', { desc = 'Close language tree window' })
local group = api.nvim_create_augroup('nvim.treesitter.dev', {})
api.nvim_create_autocmd('CursorMoved', {

View File

@ -131,6 +131,7 @@ function TSHighlighter.new(tree, opts)
self.redraw_count = 0
self._conceal_checked = {}
self._queries = {}
self._highlight_states = {}
-- Queries for a specific language can be overridden by a custom
-- string query... if one is not provided it will be looked up by file.
@ -380,6 +381,7 @@ local function on_line_impl(self, buf, line, on_spell, on_conceal)
api.nvim_buf_set_extmark(buf, ns, start_row, 0, {
end_line = end_row,
conceal_lines = '',
invalidate = true,
})
end
end
@ -459,19 +461,37 @@ end
---@param buf integer
---@param topline integer
---@param botline integer
function TSHighlighter._on_win(_, _, buf, topline, botline)
function TSHighlighter._on_win(_, win, buf, topline, botline)
local self = TSHighlighter.active[buf]
if not self or self.parsing then
if not self then
return false
end
self.parsing = self.tree:parse({ topline, botline + 1 }, function(_, trees)
if trees and self.parsing then
self.parsing = false
api.nvim__redraw({ buf = buf, valid = false, flush = false })
end
end) == nil
self.redraw_count = self.redraw_count + 1
self:prepare_highlight_states(topline, botline)
self.parsing = self.parsing
or nil
== self.tree:parse({ topline, botline + 1 }, function(_, trees)
if trees and self.parsing then
self.parsing = false
api.nvim__redraw({ win = win, valid = false, flush = false })
end
end)
if not self.parsing then
self.redraw_count = self.redraw_count + 1
self:prepare_highlight_states(topline, botline)
else
self:for_each_highlight_state(function(state)
-- TODO(ribru17): Inefficient. Eventually all marks should be applied in on_buf, and all
-- non-folded ranges of each open window should be merged, and iterators should only be
-- created over those regions. This would also fix #31777.
--
-- Currently this is not possible because the parser discards previously parsed injection
-- trees upon parsing a different region.
--
-- It would also be nice if rather than re-querying extmarks for old trees, we could tell the
-- decoration provider to not clear previous ephemeral marks for this redraw cycle.
state.iter = nil
state.next_row = 0
end)
end
return #self._highlight_states > 0
end

View File

@ -1268,12 +1268,13 @@ end
local function tree_contains(tree, range)
local tree_ranges = tree:included_ranges(false)
return Range.contains({
tree_ranges[1][1],
tree_ranges[1][2],
tree_ranges[#tree_ranges][3],
tree_ranges[#tree_ranges][4],
}, range)
for _, tree_range in ipairs(tree_ranges) do
if Range.contains(tree_range, range) then
return true
end
end
return false
end
--- Determines whether {range} is contained in the |LanguageTree|.

View File

@ -26,6 +26,7 @@
</screenshots>
<releases>
<release date="2025-05-30" version="0.11.2"/>
<release date="2025-04-26" version="0.11.1"/>
<release date="2025-03-26" version="0.11.0"/>
<release date="2025-01-29" version="0.10.4"/>

View File

@ -78,7 +78,7 @@ Comment[wa]=Asspougnî des fitchîs tecses
Comment[zh_CN]=编辑文本文件
Comment[zh_TW]=編輯文字檔
TryExec=nvim
Exec=nvim "%F"
Exec=nvim %F
Terminal=true
Type=Application
Keywords=Text;editor;

View File

@ -1,3 +1,9 @@
" Tutor: New Style Tutor Plugin :h vim-tutor-mode
" Maintainer: This runtime file is looking for a new maintainer.
" Contributors: Phạm Bình An <phambinhanctb2004@gmail.com>
" Original Author: Felipe Morales <hel.sheep@gmail.com>
" Date: 2025 May 12
if exists('g:loaded_tutor_mode_plugin') || &compatible
finish
endif

View File

@ -464,7 +464,7 @@ Now go on to the next lesson.
# Lesson 4.1: CURSOR LOCATION AND FILE STATUS
** Type `<C-g>`{normal} to show your location in a file and the file status.
Type `G`{normal} to move to a line in the file. **
Type `{count}G`{normal} to move to line {count} in the file. **
NOTE: Read the entire lesson before executing any of these steps!!
@ -905,26 +905,7 @@ You can find help on just about any subject, by giving an argument to the
:help insert-index
:help user-manual
~~~
# Lesson 7.2: CREATE A STARTUP SCRIPT
** Enable Neovim features. **
Neovim is a very configurable editor. You can customise it any way you like.
To start using more features create an "init.vim" file.
1. Start editing the "init.vim" file.
`:call mkdir(stdpath('config'),'p')`{vim}
`:exe 'edit' stdpath('config').'/init.vim'`{vim}
2. Write the file with:
`:w`{vim}
You can add all your preferred settings to this "init.vim" file.
For more information type `:help init.vim`{vim}.
# Lesson 7.3: COMPLETION
# Lesson 7.2: COMPLETION
** Command line completion with `<C-d>`{normal} and `<Tab>`{normal}. **
@ -934,14 +915,46 @@ For more information type `:help init.vim`{vim}.
3. Press `<C-d>`{normal} and Neovim will show a list of commands beginning with "e".
4. Press `<Tab>`{normal} and Neovim will complete the command name to ":edit".
4. Press `<Tab>`{normal} and Neovim will show a menu with possible completions
(or complete the match, if the entered command is unique, e.g.
":ed`<Tab>`{normal}" will be completed to ":edit").
5. Now add a space and the start of an existing file name: `:edit FIL`{vim}
5. Use `<Tab>`{normal} or `<C-n>`{normal} to go to the next match. Or use
`<S-Tab>`{normal} or `<C-p>`{normal} to go to the previous match.
6. Press `<Tab>`{normal}. Neovim will complete the name ("FIL" -> "FILE", if it is unique).
6. Choose the entry `edit`{vim}. Now you can see that the word `edit`{vim}
have been automatically inserted to the command line.
7. Now add a space and the start of an existing file name: `:edit FIL`{vim}
8. Press `<Tab>`{normal}. Vim will show a completion menu with list of file
names that start with `FIL`
NOTE: Completion works for many commands. It is especially useful for `:help`{vim}.
# Lesson 7.3: CONFIGURING NVIM
Neovim is a very configurable editor. You can customise it any way you like. To
start using more features, create a vimrc file, which can be "init.lua" if you
want to use Lua, or "init.vim" if you want to use Vimscript. We'll use
"init.lua" in this lesson.
1. Start editing the "init.lua" file.
`:exe 'edit' stdpath('config')..'/init.lua'`{vim}
2. Copy the example configuration in Lua to your "init.lua" file.
`:read $VIMRUNTIME/example_init.lua`{vim}
3. Write the file (also creates any missing parent directories):
`:w ++p`{vim}
4. Next time you start Neovim, you can quickly open this vimrc file with:
`:e $MYVIMRC`{vim}
# Lesson 7 SUMMARY
1. Type `:help`{vim}
@ -953,10 +966,15 @@ NOTE: Completion works for many commands. It is especially useful for `:help`{vi
4. Type `:q`{vim} to close the help window
5. Create an init.vim startup script to keep your preferred settings.
5. While in command mode, press `<C-d>`{normal} to see possible completions.
Press `<Tab>`{normal} to use the completion menu and select a match.
6. While in command mode, press `<C-d>`{normal} to see possible completions.
Press `<Tab>`{normal} to use one completion.
6. Create your configuration file to save your preferred settings. You can
revisit it with `:e $MYVIMRC`{vim}.
# What's next?
Run `:help nvim-quickstart`{vim} for more information on extending Nvim.
# CONCLUSION

View File

@ -31,7 +31,7 @@ NOTE: 以下の練習用コマンドにはこの文章を変更するものも
また、次のようにコマンドを実行するよう求められることや、(後で詳しく説明します。)
`:help`{vim} `<Enter>`{normal}
キーシークエンスを押すこともあります。
~~~ normal
<Esc>0f<Space>d3wP$P
@ -465,8 +465,8 @@ NOTE: タイプ中の間違いはバックスペースキーを使って直す
# レッスン 4.1: 位置とファイルの情報
** ファイル内での位置とファイルの状態を表示するには `<C-g>`{normal} をタイプします。ファイ
ル内のある行に移動するには `G`{normal} をタイプします。 **
** ファイル内での位置とファイルの状態を表示するには `<C-g>`{normal} をタイプします。
ファイル内の{count}行に移動するには `{count}G`{normal} をタイプします。 **
NOTE: ステップを実行する前に、このレッスン全てに目を通しましょう!!
@ -875,7 +875,7 @@ NOTE: 1つの検索コマンドだけ大文字小文字の区別をやめたい
~~~
# レッスン 7.1: ヘルプコマンド
** Use the online help system. **
** オンラインヘルプシステムを使用する **
Neovim には広範にわたるオンラインヘルプシステムがあります。
@ -895,27 +895,7 @@ Neovim には広範にわたるオンラインヘルプシステムがありま
:help insert-index
:help user-manual
~~~
# レッスン 7.2: 起動スクリプトの作成
** Neovim の特徴を発揮する **
Neovim はとても自由度の高いエディタです。あなたの好きなようにカスタマイズするこ
とができます。より多くの機能を使いはじめるには "init.vim" ファイルを作成します。
1. "init.vim" ファイルの編集を開始します。
`:call mkdir(stdpath('config'),'p')`{vim}
`:exe 'edit' stdpath('config').'/init.vim'`{vim}
2. 以下のようにしてファイルを保存します。
`:w`{vim}
この "init.vim" ファイルへ、お好みの設定を追加することができます。
より多くの情報を得るには `:help init.vim`{vim} とタイプします。
# レッスン 7.3: 補完
# レッスン 7.2: 補完
** `<C-d>`{normal} と `<Tab>`{normal} でコマンドラインを補完する **
@ -925,14 +905,45 @@ Neovim はとても自由度の高いエディタです。あなたの好きな
3. `<C-d>`{normal} を押すと Neovim は "e" から始まるコマンドの一覧を表示します。
4. `<Tab>`{normal} とタイプすると Neoim は ":edit" というコマンド名を補完します
4. `<Tab>`{normal} を押すと、Neovim は可能な補完候補のメニューを表示します
(または、ただの一つのマッチがあるときは、補完します。
例: ":ed`<Tab>`{normal}" は":edit"に補完されます)。
5. さらに空白と、既存のファイル名の始まりを加えます: `:edit FIL`{vim}
5. 次のマッチに移動するには `<Tab>`{normal} または `<C-n>`{normal}を使用し、
前のマッチに移動するには `<S-Tab>`{normal} または `<C-p>`{normal}を使用します。
6. `<Tab>`{normal} を押すと Neovim は名前を補完します。("FIL" -> "FILE"、重複しない場合)
6. `edit`{vim} を選択すると、コマンドラインに`edit` が挿入されます。
7. さらに空白と、既存のファイル名の始まりを加えます: `:edit FIL`{vim}
8. `<Tab>`{normal}を押すと、Vim は `FIL` で始まるファイル名の候補リストを含む補完メニューを
表示します。
NOTE: 補完は多くのコマンドで動作します。特に `:help`{vim} の際に役立ちます。
# レッスン 7.3: 設定ファイルの作成
Neovim はとても自由度の高いエディタです。あなたの好きなようにカスタマイズするこ
とができます。より多くの機能を使い始めるには設定ファイルを作成します。 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 を起動したときに、以下のコマンドでこの設定ファイルを開けます:
`:e $MYVIMRC`{vim}
# レッスン 7 要約
1. ヘルプウィンドウを開くには `:help`{vim} とするか `<F1>`{normal} を押す。
@ -943,11 +954,12 @@ NOTE: 補完は多くのコマンドで動作します。特に `:help`{vim} の
4. ヘルプウィンドウを閉じるには `:q`{vim} とタイプする。
5. お好みの設定を保つには init.vim 起動スクリプトを作成する。
6. : command で可能な補完を見るには `<C-d>`{normal} をタイプする。
5. コマンドラインモードで可能な補完を見るには `<C-d>`{normal} をタイプする。
補完を使用するには `<Tab>`{normal} を押す。
6. お好みの設定を保つには設定ファイルを作成する。作成した設定ファイルは
`:e $MYVIMRC`{vim} でいつでも開き直せます。
# おわりに
これにて Neovim のチュートリアルを終わります。エディタを簡単に、しかも充分に使う

View File

@ -17,15 +17,8 @@ Table of contents:
## SETTING UP *setting-up*
First, you'll need to enable "debug" mode
~~~ cmd
:let g:tutor_debug = 1
~~~
This will allow saving changes to the tutor files and will disable conceals, so
you can more easily check your changes.
After this, create a new .tutor file (we will be practicing on this very file, so you
don't need to do this now):
Create a new .tutor file (we will be practicing on this very file, so you don't
need to do this now):
~~~ cmd
:e new-tutorial.tutor
~~~

View File

@ -1268,9 +1268,10 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf)
{
win_T *win;
bool need_append = true; // Append `aucmd_win` to the window list.
const bool same_buffer = buf == curbuf;
// Find a window that is for the new buffer
if (buf == curbuf) { // be quick when buf is curbuf
if (same_buffer) { // be quick when buf is curbuf
win = curwin;
} else {
win = NULL;
@ -1360,9 +1361,11 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf)
aco->new_curwin_handle = curwin->handle;
set_bufref(&aco->new_curbuf, curbuf);
// disable the Visual area, the position may be invalid in another buffer
aco->save_VIsual_active = VIsual_active;
VIsual_active = false;
if (!same_buffer) {
// disable the Visual area, position may be invalid in another buffer
VIsual_active = false;
}
}
/// Cleanup after executing autocommands for a (hidden) buffer.
@ -1635,11 +1638,12 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
// into "buf" are ignoring the event.
if (buf == curbuf && event_names[event].event <= 0) {
win_ignore = event_ignored(event, curwin->w_p_eiw);
} else if (buf != NULL && event_names[event].event <= 0) {
for (size_t i = 0; i < kv_size(buf->b_wininfo); i++) {
WinInfo *wip = kv_A(buf->b_wininfo, i);
if (wip->wi_win != NULL && wip->wi_win->w_buffer == buf) {
win_ignore = event_ignored(event, wip->wi_win->w_p_eiw);
} else if (buf != NULL && event_names[event].event <= 0 && buf->b_nwindows > 0) {
win_ignore = true;
FOR_ALL_TAB_WINDOWS(tp, wp) {
if (wp->w_buffer == buf && !event_ignored(event, wp->w_p_eiw)) {
win_ignore = false;
break;
}
}
}

View File

@ -480,11 +480,6 @@ static bool can_unload_buffer(buf_T *buf)
return can_unload;
}
bool buf_locked(buf_T *buf)
{
return buf->b_locked || buf->b_locked_split;
}
/// Close the link to a buffer.
///
/// @param win If not NULL, set b_last_cursor.
@ -1300,11 +1295,17 @@ static int do_buffer_ext(int action, int start, int dir, int count, int flags)
return FAIL;
}
if (action == DOBUF_GOTO
&& buf != curbuf
&& !check_can_set_curbuf_forceit((flags & DOBUF_FORCEIT) ? true : false)) {
// disallow navigating to another buffer when 'winfixbuf' is applied
return FAIL;
if (action == DOBUF_GOTO && buf != curbuf) {
if (!check_can_set_curbuf_forceit((flags & DOBUF_FORCEIT) != 0)) {
// disallow navigating to another buffer when 'winfixbuf' is applied
return FAIL;
}
if (buf->b_locked_split) {
// disallow navigating to a closing buffer, which like splitting,
// can result in more windows displaying it
emsg(_(e_cannot_switch_to_a_closing_buffer));
return FAIL;
}
}
if ((action == DOBUF_GOTO || action == DOBUF_SPLIT) && (buf->b_flags & BF_DUMMY)) {
@ -1772,6 +1773,10 @@ void enter_buffer(buf_T *buf)
}
curbuf->b_last_used = time(NULL);
if (curbuf->terminal != NULL) {
terminal_check_size(curbuf->terminal);
}
redraw_later(curwin, UPD_NOT_VALID);
}
@ -1899,18 +1904,21 @@ buf_T *buflist_new(char *ffname_arg, char *sfname_arg, linenr_T lnum, int flags)
// buffer.)
buf = NULL;
if ((flags & BLN_CURBUF) && curbuf_reusable()) {
bufref_T bufref;
assert(curbuf != NULL);
buf = curbuf;
set_bufref(&bufref, buf);
// It's like this buffer is deleted. Watch out for autocommands that
// change curbuf! If that happens, allocate a new buffer anyway.
buf_freeall(buf, BFA_WIPE | BFA_DEL);
if (buf != curbuf) { // autocommands deleted the buffer!
return NULL;
}
if (aborting()) { // autocmds may abort script processing
xfree(ffname);
return NULL;
}
if (!bufref_valid(&bufref)) {
buf = NULL; // buf was deleted; allocate a new buffer
}
}
if (buf != curbuf || curbuf == NULL) {
buf = xcalloc(1, sizeof(buf_T));

View File

@ -366,7 +366,7 @@ struct file_buffer {
int b_locked; // Buffer is being closed or referenced, don't
// let autocommands wipe it out.
int b_locked_split; // Buffer is being closed, don't allow opening
// a new window with it.
// it in more windows.
int b_ro_locked; // Non-zero when the buffer can't be changed.
// Used for FileChangedRO

View File

@ -493,7 +493,7 @@ bool decor_redraw_start(win_T *wp, int top_row, DecorState *state)
return true; // TODO(bfredl): check if available in the region
}
bool decor_redraw_line(win_T *wp, int row, DecorState *state)
static void decor_state_pack(DecorState *state)
{
int count = (int)kv_size(state->ranges_i);
int const cur_end = state->current_end;
@ -513,6 +513,11 @@ bool decor_redraw_line(win_T *wp, int row, DecorState *state)
kv_size(state->ranges_i) = (size_t)count;
state->future_begin = fut_beg;
}
bool decor_redraw_line(win_T *wp, int row, DecorState *state)
{
decor_state_pack(state);
if (state->row == -1) {
decor_redraw_start(wp, row, state);
@ -525,7 +530,7 @@ bool decor_redraw_line(win_T *wp, int row, DecorState *state)
state->col_until = -1;
state->eol_col = -1;
if (cur_end != 0 || fut_beg != count) {
if (state->current_end != 0 || state->future_begin != (int)kv_size(state->ranges_i)) {
return true;
}

View File

@ -1447,7 +1447,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
? (startrow == 0 ? wp->w_skipcol : 0)
: wp->w_leftcol;
if (start_col > 0 && col_rows == 0) {
if (has_foldtext) {
wlv.vcol = start_col;
} else if (start_col > 0 && col_rows == 0) {
char *prev_ptr = ptr;
CharSize cs = { 0 };
@ -1490,12 +1492,14 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
// - 'cuc' is set, or
// - 'colorcolumn' is set, or
// - 'virtualedit' is set, or
// - the visual mode is active,
// - the visual mode is active, or
// - drawing a fold
// the end of the line may be before the start of the displayed part.
if (wlv.vcol < start_col && (wp->w_p_cuc
|| wlv.color_cols
|| virtual_active(wp)
|| (VIsual_active && wp->w_buffer == curwin->w_buffer))) {
|| (VIsual_active && wp->w_buffer == curwin->w_buffer)
|| has_fold)) {
wlv.vcol = start_col;
}
@ -1899,6 +1903,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
wlv.n_extra = (int)strlen(wlv.p_extra);
if (wlv.p_extra != buf_fold) {
assert(foldtext_free == NULL);
foldtext_free = wlv.p_extra;
}
wlv.sc_extra = NUL;
@ -1910,11 +1915,15 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
ptr = line + v;
}
if (draw_folded && wlv.n_extra == 0 && wlv.col < grid->cols && (has_foldtext || *ptr == NUL)) {
// Draw 'fold' fillchar after 'foldtext', or after 'eol' listchar for transparent 'foldtext'.
if (draw_folded && wlv.n_extra == 0 && wlv.col < grid->cols
&& (has_foldtext || (*ptr == NUL && (!wp->w_p_list || !lcs_eol_todo || lcs_eol == NUL)))) {
// Fill rest of line with 'fold'.
wlv.sc_extra = wp->w_p_fcs_chars.fold;
wlv.sc_final = NUL;
wlv.n_extra = grid->cols - wlv.col;
// Don't continue search highlighting past the first filler char.
search_attr = 0;
}
if (draw_folded && wlv.n_extra != 0 && wlv.col >= grid->cols) {
@ -2671,7 +2680,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
// Add a blank character to highlight.
linebuf_char[wlv.off] = schar_from_ascii(' ');
}
if (area_attr == 0 && !has_foldtext) {
if (area_attr == 0 && !has_fold) {
// Use attributes from match with highest priority among
// 'search_hl' and the match list.
get_search_match_hl(wp,
@ -2789,7 +2798,6 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, b
curwin->w_cline_height = wlv.row - startrow;
curwin->w_cline_folded = has_fold;
curwin->w_valid |= (VALID_CHEIGHT|VALID_CROW);
conceal_cursor_used = conceal_cursor_line(curwin);
}
break;

View File

@ -19,8 +19,6 @@ typedef struct {
} WinExtmark;
EXTERN kvec_t(WinExtmark) win_extmark_arr INIT( = KV_INITIAL_VALUE);
EXTERN bool conceal_cursor_used INIT( = false);
/// Spell checking variables passed from win_update() to win_line().
typedef struct {
bool spv_has_spell; ///< drawn window has spell checking

View File

@ -135,6 +135,7 @@ typedef enum {
static bool redraw_popupmenu = false;
static bool msg_grid_invalid = false;
static bool resizing_autocmd = false;
static bool conceal_cursor_used = false;
/// Check if the cursor line needs to be redrawn because of 'concealcursor'.
///
@ -2047,6 +2048,9 @@ static void win_update(win_T *wp)
foldinfo_T cursorline_fi = { 0 };
win_update_cursorline(wp, &cursorline_fi);
if (wp == curwin) {
conceal_cursor_used = conceal_cursor_line(curwin);
}
win_check_ns_hl(wp);
@ -2123,21 +2127,13 @@ static void win_update(win_T *wp)
// If the line is concealed and has no filler lines, go to the next line.
bool concealed = decor_conceal_line(wp, lnum - 1, false);
if (concealed) {
if (wp == curwin && lnum == curwin->w_cursor.lnum) {
conceal_cursor_used = conceal_cursor_line(curwin);
}
if (win_get_fill(wp, lnum) == 0) {
if (idx > 0) {
wp->w_lines[idx - 1].wl_lastlnum = lnum + foldinfo.fi_lines - (foldinfo.fi_lines != 0);
}
if (lnum == mod_top && lnum < mod_bot) {
mod_top += foldinfo.fi_lines ? foldinfo.fi_lines : 1;
}
lnum += foldinfo.fi_lines ? foldinfo.fi_lines : 1;
spv.spv_capcol_lnum = 0;
continue;
if (concealed && win_get_fill(wp, lnum) == 0) {
if (lnum == mod_top && lnum < mod_bot) {
mod_top += foldinfo.fi_lines ? foldinfo.fi_lines : 1;
}
lnum += foldinfo.fi_lines ? foldinfo.fi_lines : 1;
spv.spv_capcol_lnum = 0;
continue;
}
// When at start of changed lines: May scroll following lines
@ -2189,8 +2185,8 @@ static void win_update(win_T *wp)
// rows, and may insert/delete lines
int j = idx;
for (l = lnum; l < mod_bot; l++) {
int n = plines_win_full(wp, l, &l, NULL, true, false);
n -= (l == wp->w_topline ? adjust_plines_for_skipcol(wp) : 0);
int n = (l == wp->w_topline ? -adjust_plines_for_skipcol(wp) : 0);
n += plines_win_full(wp, l, &l, NULL, true, false);
new_rows += MIN(n, wp->w_height_inner);
j += n > 0; // don't count concealed lines
if (new_rows > wp->w_grid.rows - row - 2) {
@ -2306,23 +2302,19 @@ static void win_update(win_T *wp)
spv.spv_capcol_lnum = 0;
}
if (foldinfo.fi_lines == 0) {
wp->w_lines[idx].wl_folded = false;
wp->w_lines[idx].wl_foldend = lnum;
wp->w_lines[idx].wl_lastlnum = lnum;
did_update = DID_LINE;
} else {
foldinfo.fi_lines--;
wp->w_lines[idx].wl_folded = true;
wp->w_lines[idx].wl_foldend = lnum + foldinfo.fi_lines;
wp->w_lines[idx].wl_lastlnum = lnum + foldinfo.fi_lines;
did_update = DID_FOLD;
}
linenr_T lastlnum = lnum + foldinfo.fi_lines - (foldinfo.fi_lines > 0);
wp->w_lines[idx].wl_folded = foldinfo.fi_lines > 0;
wp->w_lines[idx].wl_foldend = lastlnum;
wp->w_lines[idx].wl_lastlnum = lastlnum;
did_update = foldinfo.fi_lines > 0 ? DID_FOLD : DID_LINE;
// Adjust "wl_lastlnum" for concealed lines below the last line in the window.
while (row == wp->w_grid.rows
&& wp->w_lines[idx].wl_lastlnum < buf->b_ml.ml_line_count
// Adjust "wl_lastlnum" for concealed lines below this line, unless it should
// still be drawn for below virt_lines attached to the current line. Below
// virt_lines attached to a second adjacent concealed line are concealed.
bool virt_below = decor_virt_lines(wp, lastlnum, lastlnum + 1, NULL, NULL, true) > 0;
while (!virt_below && wp->w_lines[idx].wl_lastlnum < buf->b_ml.ml_line_count
&& decor_conceal_line(wp, wp->w_lines[idx].wl_lastlnum, false)) {
virt_below = false;
wp->w_lines[idx].wl_lastlnum++;
hasFolding(wp, wp->w_lines[idx].wl_lastlnum, NULL, &wp->w_lines[idx].wl_lastlnum);
}
@ -2342,17 +2334,15 @@ static void win_update(win_T *wp)
if (dollar_vcol == -1) {
wp->w_lines[idx].wl_size = (uint16_t)(row - srow);
}
idx++;
lnum += foldinfo.fi_lines + 1;
lnum = wp->w_lines[idx++].wl_lastlnum + 1;
} else {
// If:
// - 'number' is set and below inserted/deleted lines, or
// - 'relativenumber' is set and cursor moved vertically,
// the text doesn't need to be redrawn, but the number column does.
if (((wp->w_p_nu && mod_top != 0 && lnum >= mod_bot
&& buf->b_mod_set && buf->b_mod_xlines != 0)
|| (wp->w_p_rnu && wp->w_last_cursor_lnum_rnu != wp->w_cursor.lnum))
&& !decor_conceal_line(wp, lnum - 1, true)) {
if ((wp->w_p_nu && mod_top != 0 && lnum >= mod_bot
&& buf->b_mod_set && buf->b_mod_xlines != 0)
|| (wp->w_p_rnu && wp->w_last_cursor_lnum_rnu != wp->w_cursor.lnum)) {
foldinfo_T info = wp->w_p_cul && lnum == wp->w_cursor.lnum
? cursorline_fi : fold_info(wp, lnum);
win_line(wp, lnum, srow, wp->w_grid.rows, wp->w_lines[idx].wl_size, false, &spv, info);

View File

@ -188,6 +188,7 @@ INIT(= N_("E5767: Cannot use :undo! to redo or move to a different undo branch")
EXTERN const char e_winfixbuf_cannot_go_to_buffer[]
INIT(= N_("E1513: Cannot switch buffer. 'winfixbuf' is enabled"));
EXTERN const char e_invalid_return_type_from_findfunc[] INIT( = N_("E1514: 'findfunc' did not return a List type"));
EXTERN const char e_cannot_switch_to_a_closing_buffer[] INIT( = N_("E1546: Cannot switch to a closing buffer"));
EXTERN const char e_trustfile[] INIT(= N_("E5570: Cannot update trust file: %s"));

View File

@ -3652,7 +3652,7 @@ M.funcs = {
name = 'getcharstr',
params = { { 'expr', '-1|0|1' }, { 'opts', 'table' } },
returns = 'string',
signature = 'getcharstr([{expr}])',
signature = 'getcharstr([{expr} [, {opts}]])',
},
getcmdcomplpat = {
desc = [=[
@ -13239,7 +13239,7 @@ M.funcs = {
]=],
name = 'winlayout',
params = { { 'tabnr', 'integer' } },
returns = 'any[]',
returns = 'vim.fn.winlayout.ret',
signature = 'winlayout([{tabnr}])',
},
winline = {

View File

@ -2270,14 +2270,16 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
if (buf == NULL) {
goto theend;
}
// autocommands try to edit a file that is going to be removed, abort
if (buf_locked(buf)) {
// autocommands try to edit a closing buffer, which like splitting, can
// result in more windows displaying it; abort
if (buf->b_locked_split) {
// window was split, but not editing the new buffer, reset b_nwindows again
if (oldwin == NULL
&& curwin->w_buffer != NULL
&& curwin->w_buffer->b_nwindows > 1) {
curwin->w_buffer->b_nwindows--;
}
emsg(_(e_cannot_switch_to_a_closing_buffer));
goto theend;
}
if (curwin->w_alt_fnum == buf->b_fnum && prev_alt_fnum != 0) {
@ -2726,6 +2728,9 @@ theend:
if (bufref_valid(&old_curbuf) && old_curbuf.br_buf->terminal != NULL) {
terminal_check_size(old_curbuf.br_buf->terminal);
}
if ((!bufref_valid(&old_curbuf) || curbuf != old_curbuf.br_buf) && curbuf->terminal != NULL) {
terminal_check_size(curbuf->terminal);
}
if (did_inc_redrawing_disabled) {
RedrawingDisabled--;

View File

@ -792,7 +792,6 @@ static uint8_t *command_line_enter(int firstc, int count, int indent, bool clear
}
setmouse();
setcursor();
Error err = ERROR_INIT;
char firstcbuf[2];

View File

@ -326,10 +326,11 @@ char *get_special_key_name(int c, int modifiers)
string[idx++] = (char)(uint8_t)KEY2TERMCAP1(c);
} else {
// Not a special key, only modifiers, output directly.
if (utf_char2len(c) > 1) {
idx += utf_char2bytes(c, string + idx);
} else if (vim_isprintc(c)) {
int len = utf_char2len(c);
if (len == 1 && vim_isprintc(c)) {
string[idx++] = (char)(uint8_t)c;
} else if (len > 1) {
idx += utf_char2bytes(c, string + idx);
} else {
char *s = transchar(c);
while (*s) {

View File

@ -1926,7 +1926,17 @@ int put_escstr(FILE *fd, const char *strstart, int what)
if (str[1] == KS_MODIFIER) {
modifiers = str[2];
str += 3;
c = *str;
// Modifiers can be applied too to multi-byte characters.
p = mb_unescape((const char **)&str);
if (p == NULL) {
c = *str;
} else {
// retrieve codepoint (character number) from unescaped string
c = utf_ptr2char(p);
str--;
}
}
if (c == K_SPECIAL) {
c = TO_SPECIAL(str[1], str[2]);

View File

@ -2011,6 +2011,11 @@ void msg_prt_line(const char *s, bool list)
} else {
hl_id = 0;
int c = (uint8_t)(*s++);
if (c >= 0x80) { // Illegal byte
col += utf_char2cells(c);
msg_putchar(c);
continue;
}
sc_extra = NUL;
sc_final = NUL;
if (list) {

View File

@ -1331,56 +1331,55 @@ bool scrolldown(win_T *wp, linenr_T line_count, int byfold)
hasFolding(wp, wp->w_topline, &wp->w_topline, NULL);
validate_cursor(wp); // w_wrow needs to be valid
for (int todo = line_count; todo > 0; todo--) {
if (wp->w_topfill < win_get_fill(wp, wp->w_topline)
&& wp->w_topfill < wp->w_height_inner - 1) {
bool can_fill = wp->w_topfill < wp->w_height_inner - 1
&& wp->w_topfill < win_get_fill(wp, wp->w_topline);
// break when at the very top
if (wp->w_topline == 1 && !can_fill && (!do_sms || wp->w_skipcol < width1)) {
break;
}
if (do_sms && wp->w_skipcol >= width1) {
// scroll a screen line down
if (wp->w_skipcol >= width1 + width2) {
wp->w_skipcol -= width2;
} else {
wp->w_skipcol -= width1;
}
redraw_later(wp, UPD_NOT_VALID);
done++;
} else if (can_fill) {
wp->w_topfill++;
done++;
} else {
// break when at the very top
if (wp->w_topline == 1 && (!do_sms || wp->w_skipcol < width1)) {
break;
}
if (do_sms && wp->w_skipcol >= width1) {
// scroll a screen line down
if (wp->w_skipcol >= width1 + width2) {
wp->w_skipcol -= width2;
} else {
wp->w_skipcol -= width1;
// scroll a text line down
wp->w_topline--;
wp->w_skipcol = 0;
wp->w_topfill = 0;
// A sequence of folded lines only counts for one logical line
linenr_T first;
if (hasFolding(wp, wp->w_topline, &first, NULL)) {
done += !decor_conceal_line(wp, first - 1, false);
if (!byfold) {
todo -= wp->w_topline - first - 1;
}
redraw_later(wp, UPD_NOT_VALID);
done++;
wp->w_botline -= wp->w_topline - first;
wp->w_topline = first;
} else if (decor_conceal_line(wp, wp->w_topline - 1, false)) {
todo++;
} else {
// scroll a text line down
wp->w_topline--;
wp->w_skipcol = 0;
wp->w_topfill = 0;
// A sequence of folded lines only counts for one logical line
linenr_T first;
if (hasFolding(wp, wp->w_topline, &first, NULL)) {
done += !decor_conceal_line(wp, first - 1, false);
if (!byfold) {
todo -= wp->w_topline - first - 1;
if (do_sms) {
int size = linetabsize_eol(wp, wp->w_topline);
if (size > width1) {
wp->w_skipcol = width1;
size -= width1;
redraw_later(wp, UPD_NOT_VALID);
}
wp->w_botline -= wp->w_topline - first;
wp->w_topline = first;
} else if (decor_conceal_line(wp, wp->w_topline - 1, false)) {
todo++;
while (size > width2) {
wp->w_skipcol += width2;
size -= width2;
}
done++;
} else {
if (do_sms) {
int size = linetabsize_eol(wp, wp->w_topline);
if (size > width1) {
wp->w_skipcol = width1;
size -= width1;
redraw_later(wp, UPD_NOT_VALID);
}
while (size > width2) {
wp->w_skipcol += width2;
size -= width2;
}
done++;
} else {
done += plines_win_nofill(wp, wp->w_topline, true);
}
done += plines_win_nofill(wp, wp->w_topline, true);
}
}
}
@ -2333,6 +2332,7 @@ void cursor_correct(win_T *wp)
~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW);
}
}
check_cursor_moved(wp);
wp->w_valid |= VALID_TOPLINE;
wp->w_viewport_invalid = true;
}
@ -2423,11 +2423,16 @@ static bool scroll_with_sms(Direction dir, int count, int *curscount)
if (labs(curwin->w_topline - prev_topline) > (dir == BACKWARD)) {
fixdir = dir * -1;
}
while (curwin->w_skipcol > 0
&& curwin->w_topline < curbuf->b_ml.ml_line_count) {
scroll_redraw(fixdir == FORWARD, 1);
*curscount += (fixdir == dir ? 1 : -1);
int width1 = curwin->w_width_inner - win_col_off(curwin);
int width2 = width1 + win_col_off2(curwin);
count = 1 + (curwin->w_skipcol - width1 - 1) / width2;
if (fixdir == FORWARD) {
count = 1 + (linetabsize_eol(curwin, curwin->w_topline)
- curwin->w_skipcol - width1 + width2 - 1) / width2;
}
scroll_redraw(fixdir == FORWARD, count);
*curscount += count * (fixdir == dir ? 1 : -1);
}
curwin->w_p_sms = prev_sms;

View File

@ -247,8 +247,7 @@ static void parse_msgpack(Channel *channel)
Unpacker *p = channel->rpc.unpacker;
while (unpacker_advance(p)) {
if (p->type == kMessageTypeRedrawEvent) {
// When exiting, ui_client_stop() has already been called, so don't handle UI events.
if (ui_client_channel_id && !exiting) {
if (ui_client_attached) {
if (p->has_grid_line_event) {
ui_client_event_raw_line(&p->grid_line_event);
p->has_grid_line_event = false;

View File

@ -2672,6 +2672,12 @@ local options = {
- 'exrc' can execute any code; editorconfig only specifies settings.
- 'exrc' is Nvim-specific; editorconfig works in other editors.
To achieve project-local LSP configuration:
1. Enable 'exrc'.
2. Place LSP configs at ".nvim/lsp/*.lua" in your project root.
3. Create ".nvim.lua" in your project root directory with this line: >lua
vim.cmd[[set runtimepath+=.nvim]]
<
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
]=],
@ -8420,8 +8426,7 @@ local options = {
cb = 'did_set_statusline',
defaults = '',
desc = [=[
When non-empty, this option determines the content of the status line.
Also see |status-line|.
Sets the |status-line|.
The option consists of printf style '%' items interspersed with
normal text. Each status line item is of the form:

View File

@ -856,7 +856,7 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu
{
out_data_decide_throttle(0); // Initialize throttle decider.
out_data_ring(NULL, 0); // Initialize output ring-buffer.
bool has_input = (input != NULL && input[0] != NUL);
bool has_input = (input != NULL && len > 0);
// the output buffer
StringBuilder buf = KV_INITIAL_VALUE;

View File

@ -5789,7 +5789,9 @@ static buf_T *load_dummy_buffer(char *fname, char *dirname_start, char *resultin
aucmd_restbuf(&aco);
if (newbuf_to_wipe.br_buf != NULL && bufref_valid(&newbuf_to_wipe)) {
wipe_buffer(newbuf_to_wipe.br_buf, false);
block_autocmds();
wipe_dummy_buffer(newbuf_to_wipe.br_buf, NULL);
unblock_autocmds();
}
// Add back the "dummy" flag, otherwise buflist_findname_file_id()
@ -5813,11 +5815,11 @@ static buf_T *load_dummy_buffer(char *fname, char *dirname_start, char *resultin
return newbuf;
}
// Wipe out the dummy buffer that load_dummy_buffer() created. Restores
// directory to "dirname_start" prior to returning, if autocmds or the
// 'autochdir' option have changed it.
/// Wipe out the dummy buffer that load_dummy_buffer() created. Restores
/// directory to "dirname_start" if not NULL prior to returning, if autocmds or
/// the 'autochdir' option have changed it.
static void wipe_dummy_buffer(buf_T *buf, char *dirname_start)
FUNC_ATTR_NONNULL_ALL
FUNC_ATTR_NONNULL_ARG(1)
{
// If any autocommand opened a window on the dummy buffer, close that
// window. If we can't close them all then give up.
@ -5835,7 +5837,7 @@ static void wipe_dummy_buffer(buf_T *buf, char *dirname_start)
}
}
if (!did_one) {
return;
goto fail;
}
}
@ -5852,14 +5854,23 @@ static void wipe_dummy_buffer(buf_T *buf, char *dirname_start)
// Restore the error/interrupt/exception state if not discarded by a
// new aborting error, interrupt, or uncaught exception.
leave_cleanup(&cs);
// When autocommands/'autochdir' option changed directory: go back.
restore_start_dir(dirname_start);
if (dirname_start != NULL) {
// When autocommands/'autochdir' option changed directory: go back.
restore_start_dir(dirname_start);
}
return;
}
fail:
// Keeping the buffer, remove the dummy flag.
buf->b_flags &= ~BF_DUMMY;
}
// Unload the dummy buffer that load_dummy_buffer() created. Restores
// directory to "dirname_start" prior to returning, if autocmds or the
// 'autochdir' option have changed it.
/// Unload the dummy buffer that load_dummy_buffer() created. Restores
/// directory to "dirname_start" prior to returning, if autocmds or the
/// 'autochdir' option have changed it.
static void unload_dummy_buffer(buf_T *buf, char *dirname_start)
{
if (curbuf == buf) { // safety check

View File

@ -2722,12 +2722,9 @@ static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchst
|| (dirc == '/' && lt(p, lastpos)));
// If anything relevant changed the count has to be recomputed.
// STRNICMP ignores case, but we should not ignore case.
// Unfortunately, there is no STRNICMP function.
// XXX: above comment should be "no MB_STRCMP function" ?
if (!(chgtick == buf_get_changedtick(curbuf)
&& (lastpat != NULL // suppress clang/NULL passed as nonnull parameter
&& mb_strnicmp(lastpat, spats[last_idx].pat, lastpatlen) == 0
&& strncmp(lastpat, spats[last_idx].pat, lastpatlen) == 0
&& lastpatlen == spats[last_idx].patlen)
&& equalpos(lastpos, *cursor_pos)
&& lbuf == curbuf)

View File

@ -21,6 +21,7 @@
#include "nvim/tui/input_defs.h"
#include "nvim/tui/termkey/driver-csi.h"
#include "nvim/tui/termkey/termkey.h"
#include "nvim/tui/termkey/termkey_defs.h"
#include "nvim/tui/tui.h"
#include "nvim/ui_client.h"
@ -144,8 +145,8 @@ void tinput_init(TermInput *input, Loop *loop)
term = ""; // termkey_new_abstract assumes non-null (#2745)
}
input->tk = termkey_new_abstract(term,
TERMKEY_FLAG_UTF8 | TERMKEY_FLAG_NOSTART);
input->tk = termkey_new_abstract(term, (TERMKEY_FLAG_UTF8 | TERMKEY_FLAG_NOSTART
| TERMKEY_FLAG_KEEPC0));
termkey_set_buffer_size(input->tk, INPUT_BUFFER_SIZE);
termkey_hook_terminfo_getstr(input->tk, input->tk_ti_hook_fn, input);
termkey_start(input->tk);
@ -248,6 +249,13 @@ static size_t handle_termkey_modifiers(TermKeyKey *key, char *buf, size_t buflen
return len;
}
enum {
KEYMOD_SUPER = 1 << 3,
KEYMOD_META = 1 << 5,
KEYMOD_RECOGNIZED = (TERMKEY_KEYMOD_SHIFT | TERMKEY_KEYMOD_ALT | TERMKEY_KEYMOD_CTRL
| KEYMOD_SUPER | KEYMOD_META),
};
/// Handle modifiers not handled by libtermkey.
/// Currently only Super ("D-") and Meta ("T-") are supported in Nvim.
///
@ -256,10 +264,10 @@ static size_t handle_more_modifiers(TermKeyKey *key, char *buf, size_t buflen)
FUNC_ATTR_WARN_UNUSED_RESULT
{
size_t len = 0;
if (key->modifiers & 8) { // Super
if (key->modifiers & KEYMOD_SUPER) {
len += (size_t)snprintf(buf + len, buflen - len, "D-");
}
if (key->modifiers & 32) { // Meta
if (key->modifiers & KEYMOD_META) {
len += (size_t)snprintf(buf + len, buflen - len, "T-");
}
assert(len < buflen);
@ -444,7 +452,7 @@ static void tk_getkeys(TermInput *input, bool force)
continue;
}
if (key.type == TERMKEY_TYPE_UNICODE && !key.modifiers) {
if (key.type == TERMKEY_TYPE_UNICODE && !(key.modifiers & KEYMOD_RECOGNIZED)) {
forward_simple_utf8(input, &key);
} else if (key.type == TERMKEY_TYPE_UNICODE
|| key.type == TERMKEY_TYPE_FUNCTION
@ -651,8 +659,10 @@ static void handle_unknown_csi(TermInput *input, const TermKeyKey *key)
case '?':
// Primary Device Attributes (DA1) response
if (input->callbacks.primary_device_attr) {
input->callbacks.primary_device_attr(input->tui_data);
void (*cb_save)(TUIData *) = input->callbacks.primary_device_attr;
// Clear the callback before invoking it, as it may set a new callback. #34031
input->callbacks.primary_device_attr = NULL;
cb_save(input->tui_data);
}
break;

View File

@ -507,7 +507,7 @@ static TermKeyResult parse_csi(TermKey *tk, size_t introlen, size_t *csi_len,
present = 0;
argi++;
if (argi > 16) {
if (argi >= 16) {
break;
}
} else if (c >= 0x20 && c <= 0x2f) {

View File

@ -719,7 +719,7 @@ static void emit_codepoint(TermKey *tk, int codepoint, TermKeyKey *key)
key->type = TERMKEY_TYPE_KEYSYM;
key->code.sym = TERMKEY_SYM_SPACE;
key->modifiers = TERMKEY_KEYMOD_CTRL;
} else if (codepoint < 0x20) {
} else if (codepoint < 0x20 && !(tk->flags & TERMKEY_FLAG_KEEPC0)) {
// C0 range
key->code.codepoint = 0;
key->modifiers = 0;
@ -750,7 +750,7 @@ static void emit_codepoint(TermKey *tk, int codepoint, TermKeyKey *key)
key->type = TERMKEY_TYPE_KEYSYM;
key->code.sym = TERMKEY_SYM_DEL;
key->modifiers = 0;
} else if (codepoint >= 0x20 && codepoint < 0x80) {
} else if (codepoint > 0 && codepoint < 0x80) {
// ASCII lowbyte range
key->type = TERMKEY_TYPE_UNICODE;
key->code.codepoint = codepoint;

View File

@ -151,6 +151,7 @@ enum {
TERMKEY_FLAG_CTRLC = 1 << 6, // Allow Ctrl-C to be read as normal, disabling SIGINT
TERMKEY_FLAG_EINTR = 1 << 7, // Return ERROR on signal (EINTR) rather than retry
TERMKEY_FLAG_NOSTART = 1 << 8, // Do not call termkey_start() in constructor
TERMKEY_FLAG_KEEPC0 = 1 << 9, // Keep raw C0 control codes
};
enum {

View File

@ -360,6 +360,7 @@ 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->showing_mode = SHAPE_IDX_N;
tui->unibi_ext.enable_mouse = -1;
tui->unibi_ext.disable_mouse = -1;

View File

@ -186,6 +186,7 @@ void ui_client_run(bool remote_ui)
void ui_client_stop(void)
{
ui_client_attached = false;
if (!tui_is_stopped(tui)) {
tui_stop(tui);
}

View File

@ -347,12 +347,23 @@ newwindow:
} else {
win_T *wp;
if (Prenum) { // go to specified window
win_T *last_focusable = firstwin;
for (wp = firstwin; --Prenum > 0;) {
if (!wp->w_floating || (!wp->w_config.hide && wp->w_config.focusable)) {
last_focusable = wp;
}
if (wp->w_next == NULL) {
break;
}
wp = wp->w_next;
}
while (wp != NULL && wp->w_floating
&& (wp->w_config.hide || !wp->w_config.focusable)) {
wp = wp->w_next;
}
if (wp == NULL) { // went past the last focusable window
wp = last_focusable;
}
} else {
if (nchar == 'W') { // go to previous window
wp = curwin->w_prev;

View File

@ -1769,10 +1769,10 @@ describe('API/win', function()
pcall_err(api.nvim_win_close, w, true)
)
-- OK when using window to different buffer than `win`s.
-- OK when using a buffer that isn't closing.
w = api.nvim_get_current_win()
command(
'only | autocmd BufHidden * ++once call nvim_open_win(0, 0, #{split: "left", win: '
'only | autocmd BufHidden * ++once call nvim_open_win(bufnr("#"), 0, #{split: "left", win: '
.. w
.. '})'
)

View File

@ -10,18 +10,18 @@ local eq = t.eq
---@param type string
---@return string
local function stdpath(type)
return exec_lua([[return vim.fs.normalize(vim.fn.stdpath(...))]], type)
return exec_lua([[return vim.fs.abspath(vim.fn.stdpath(...))]], type)
end
---@return string
local function vimruntime()
return exec_lua [[ return vim.fs.normalize(vim.env.VIMRUNTIME) ]]
return exec_lua [[ return vim.fs.abspath(vim.env.VIMRUNTIME) ]]
end
---@param module string
---@return string
local function lua_includeexpr(module)
return exec_lua([[return require('vim._ftplugin.lua').includeexpr(...)]], module)
return exec_lua([[return vim.fs.abspath(require 'vim._ftplugin.lua'.includeexpr(...))]], module)
end
describe("ftplugin: Lua 'includeexpr'", function()
@ -59,7 +59,12 @@ describe("ftplugin: Lua 'includeexpr'", function()
write ++p
edit %s/lua/runtime-foo/bar.lua
write ++p
]]):format(temp_dir, temp_dir))
edit %s/general-foo/bar/init.lua
write ++p
edit %s/general-foo/bar/baz.lua
write ++p
]]):format(temp_dir, temp_dir, temp_dir, temp_dir))
end)
it('finds module in current repo', function()
@ -94,4 +99,11 @@ describe("ftplugin: Lua 'includeexpr'", function()
eq(temp_dir .. '/lua/runtime-foo/init.lua', lua_includeexpr('runtime-foo'))
eq(temp_dir .. '/lua/runtime-foo/bar.lua', lua_includeexpr('runtime-foo.bar'))
end)
it('non-Nvim-style Lua modules', function()
command('cd ' .. temp_dir)
eq(temp_dir .. '/general-foo/bar/init.lua', lua_includeexpr('general-foo.bar'))
eq(temp_dir .. '/general-foo/bar/baz.lua', lua_includeexpr('general-foo.bar.baz'))
command('cd -')
end)
end)

View File

@ -1,7 +1,9 @@
local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local Screen = require('test.functional.ui.screen')
local clear, eq, command, fn = n.clear, t.eq, n.command, n.fn
local assert_alive = n.assert_alive
describe(':z^', function()
before_each(clear)
@ -11,3 +13,20 @@ describe(':z^', function()
eq(1, fn.line('.'))
end)
end)
describe(':print', function()
before_each(clear)
it('does not crash when printing 0xFF byte #34044', function()
local screen = Screen.new()
-- Needs raw 0xFF byte, not 0xFF char
command('call setline(1, "foo\\xFFbar")')
command('%print')
screen:expect([[
^foo{18:<ff>}bar |
{1:~ }|*12
fooÿbar |
]])
assert_alive()
end)
end)

View File

@ -450,8 +450,8 @@ pcall(vim.cmd.edit, 'Xtest_swapredraw.lua')
screen:expect({
any = table.concat({
pesc('{2:E325: ATTENTION}'),
'file name: .*Xswaptest',
'process ID: %d* %(STILL RUNNING%)',
'\n process ID: %d* %(STILL RUNNING%)',
'\nWhile opening file "Xswaptest"',
pesc('{1:[O]pen Read-Only, (E)dit anyway, (R)ecover, (Q)uit, (A)bort: }^'),
}, '.*'),
})

View File

@ -11,6 +11,30 @@ local eq = t.eq
before_each(clear)
local function expected_empty()
eq({}, api.nvim_get_vvar('errors'))
end
-- oldtest: Test_get_Visual_selection_in_curbuf_autocmd()
it('autocmd can get Visual selection when using setbufvar() on curbuf', function()
n.exec([[
new
autocmd OptionSet list let b:text = getregion(getpos('.'), getpos('v'))
call setline(1, 'foo bar baz')
normal! gg0fbvtb
setlocal list
call assert_equal(['bar '], b:text)
exe "normal! \<Esc>"
normal! v0
call setbufvar('%', '&list', v:false)
call assert_equal(['foo bar '], b:text)
exe "normal! \<Esc>"
]])
expected_empty()
end)
-- oldtest: Test_autocmd_invalidates_undo_on_textchanged()
it('no E440 in quickfix window when autocommand invalidates undo', function()
write_file(

View File

@ -184,4 +184,28 @@ describe('search stat', function()
{19:search hit TOP, continuing at BOTTOM} |
]])
end)
-- oldtest: Test_search_stat_smartcase_ignorecase()
it('when changing case of pattern', function()
exec([[
set shm-=S ignorecase smartcase
call setline(1, [' MainmainmainmmmainmAin', ''])
]])
feed('/main<cr>nnnn')
screen:expect([[
{10:Mainmainmain}mm{10:main^mAin} |
|
{1:~ }|*7
/main [5/5] |
]])
feed('/mAin<cr>')
screen:expect([[
Mainmainmainmmmain{10:^mAin} |
|
{1:~ }|*7
/mAin [1/1] |
]])
end)
end)

View File

@ -20,25 +20,33 @@ local read_file = t.read_file
describe('vim.secure', function()
describe('read()', function()
local xstate = 'Xstate'
local screen ---@type test.functional.ui.screen
setup(function()
before_each(function()
clear { env = { XDG_STATE_HOME = xstate } }
n.mkdir_p(xstate .. pathsep .. (is_os('win') and 'nvim-data' or 'nvim'))
t.mkdir('Xdir')
t.mkdir('Xdir/Xsubdir')
t.write_file('Xdir/Xfile.txt', [[hello, world]])
t.write_file(
'Xfile',
[[
let g:foobar = 42
]]
)
screen = Screen.new(500, 8)
end)
teardown(function()
after_each(function()
screen:detach()
os.remove('Xfile')
n.rmdir('Xdir')
n.rmdir(xstate)
end)
it('works', function()
local screen = Screen.new(500, 8)
it('regular file', function()
screen:set_default_attr_ids({
[1] = { bold = true, foreground = Screen.colors.Blue1 },
[2] = { bold = true, reverse = true },
@ -47,7 +55,8 @@ describe('vim.secure', function()
})
local cwd = fn.getcwd()
if #cwd + 23 > 500 then
local msg = cwd .. pathsep .. 'Xfile is not trusted.'
if #msg >= screen._width then
pending('path too long')
return
end
@ -59,7 +68,7 @@ describe('vim.secure', function()
{1:~{MATCH: +}}|*3
{2:{MATCH: +}}|
:lua vim.secure.read('Xfile'){MATCH: +}|
{3:]] .. cwd .. pathsep .. [[Xfile is not trusted.}{MATCH: +}|
{3:]] .. msg .. [[}{MATCH: +}|
{3:[i]gnore, (v)iew, (d)eny, (a)llow: }^{MATCH: +}|
]])
feed('d')
@ -81,7 +90,7 @@ describe('vim.secure', function()
{1:~{MATCH: +}}|*3
{2:{MATCH: +}}|
:lua vim.secure.read('Xfile'){MATCH: +}|
{3:]] .. cwd .. pathsep .. [[Xfile is not trusted.}{MATCH: +}|
{3:]] .. msg .. [[}{MATCH: +}|
{3:[i]gnore, (v)iew, (d)eny, (a)llow: }^{MATCH: +}|
]])
feed('a')
@ -94,7 +103,7 @@ describe('vim.secure', function()
local hash = fn.sha256(assert(read_file('Xfile')))
trust = assert(read_file(stdpath('state') .. pathsep .. 'trust'))
eq(string.format('%s %s', hash, cwd .. pathsep .. 'Xfile'), vim.trim(trust))
eq(vim.NIL, exec_lua([[vim.secure.read('Xfile')]]))
eq('let g:foobar = 42\n', exec_lua([[return vim.secure.read('Xfile')]]))
os.remove(stdpath('state') .. pathsep .. 'trust')
@ -104,7 +113,7 @@ describe('vim.secure', function()
{1:~{MATCH: +}}|*3
{2:{MATCH: +}}|
:lua vim.secure.read('Xfile'){MATCH: +}|
{3:]] .. cwd .. pathsep .. [[Xfile is not trusted.}{MATCH: +}|
{3:]] .. msg .. [[}{MATCH: +}|
{3:[i]gnore, (v)iew, (d)eny, (a)llow: }^{MATCH: +}|
]])
feed('i')
@ -123,7 +132,7 @@ describe('vim.secure', function()
{1:~{MATCH: +}}|*3
{2:{MATCH: +}}|
:lua vim.secure.read('Xfile'){MATCH: +}|
{3:]] .. cwd .. pathsep .. [[Xfile is not trusted.}{MATCH: +}|
{3:]] .. msg .. [[}{MATCH: +}|
{3:[i]gnore, (v)iew, (d)eny, (a)llow: }^{MATCH: +}|
]])
feed('v')
@ -144,6 +153,114 @@ describe('vim.secure', function()
pcall_err(command, 'write')
eq(true, api.nvim_get_option_value('readonly', {}))
end)
it('directory', function()
screen:set_default_attr_ids({
[1] = { bold = true, foreground = Screen.colors.Blue1 },
[2] = { bold = true, reverse = true },
[3] = { bold = true, foreground = Screen.colors.SeaGreen },
[4] = { reverse = true },
})
local cwd = fn.getcwd()
local msg = cwd
.. pathsep
.. 'Xdir is not trusted. DIRECTORY trust is decided only by its name, not its contents.'
if #msg >= screen._width then
pending('path too long')
return
end
-- Need to use feed_command instead of exec_lua because of the confirmation prompt
feed_command([[lua vim.secure.read('Xdir')]])
screen:expect([[
{MATCH: +}|
{1:~{MATCH: +}}|*3
{2:{MATCH: +}}|
:lua vim.secure.read('Xdir'){MATCH: +}|
{3:]] .. msg .. [[}{MATCH: +}|
{3:[i]gnore, (v)iew, (d)eny, (a)llow: }^{MATCH: +}|
]])
feed('d')
screen:expect([[
^{MATCH: +}|
{1:~{MATCH: +}}|*6
{MATCH: +}|
]])
local trust = assert(read_file(stdpath('state') .. pathsep .. 'trust'))
eq(string.format('! %s', cwd .. pathsep .. 'Xdir'), vim.trim(trust))
eq(vim.NIL, exec_lua([[return vim.secure.read('Xdir')]]))
os.remove(stdpath('state') .. pathsep .. 'trust')
feed_command([[lua vim.secure.read('Xdir')]])
screen:expect([[
{MATCH: +}|
{1:~{MATCH: +}}|*3
{2:{MATCH: +}}|
:lua vim.secure.read('Xdir'){MATCH: +}|
{3:]] .. msg .. [[}{MATCH: +}|
{3:[i]gnore, (v)iew, (d)eny, (a)llow: }^{MATCH: +}|
]])
feed('a')
screen:expect([[
^{MATCH: +}|
{1:~{MATCH: +}}|*6
{MATCH: +}|
]])
-- Directories aren't hashed in the trust database, instead a slug ("directory") is stored
-- instead.
local expected_hash = 'directory'
trust = assert(read_file(stdpath('state') .. pathsep .. 'trust'))
eq(string.format('%s %s', expected_hash, cwd .. pathsep .. 'Xdir'), vim.trim(trust))
eq(true, exec_lua([[return vim.secure.read('Xdir')]]))
os.remove(stdpath('state') .. pathsep .. 'trust')
feed_command([[lua vim.secure.read('Xdir')]])
screen:expect([[
{MATCH: +}|
{1:~{MATCH: +}}|*3
{2:{MATCH: +}}|
:lua vim.secure.read('Xdir'){MATCH: +}|
{3:]] .. msg .. [[}{MATCH: +}|
{3:[i]gnore, (v)iew, (d)eny, (a)llow: }^{MATCH: +}|
]])
feed('i')
screen:expect([[
^{MATCH: +}|
{1:~{MATCH: +}}|*6
{MATCH: +}|
]])
-- Trust database is not updated
eq(nil, read_file(stdpath('state') .. pathsep .. 'trust'))
feed_command([[lua vim.secure.read('Xdir')]])
screen:expect([[
{MATCH: +}|
{1:~{MATCH: +}}|*3
{2:{MATCH: +}}|
:lua vim.secure.read('Xdir'){MATCH: +}|
{3:]] .. msg .. [[}{MATCH: +}|
{3:[i]gnore, (v)iew, (d)eny, (a)llow: }^{MATCH: +}|
]])
feed('v')
screen:expect([[
^{MATCH: +}|
{1:~{MATCH: +}}|*2
{2:]] .. fn.fnamemodify(cwd, ':~') .. pathsep .. [[Xdir [RO]{MATCH: +}}|
{MATCH: +}|
{1:~{MATCH: +}}|
{4:[No Name]{MATCH: +}}|
{MATCH: +}|
]])
-- Trust database is not updated
eq(nil, read_file(stdpath('state') .. pathsep .. 'trust'))
end)
end)
describe('trust()', function()
@ -160,10 +277,12 @@ describe('vim.secure', function()
before_each(function()
t.write_file('test_file', 'test')
t.mkdir('test_dir')
end)
after_each(function()
os.remove('test_file')
n.rmdir('test_dir')
end)
it('returns error when passing both path and bufnr', function()
@ -275,5 +394,15 @@ describe('vim.secure', function()
exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]])
)
end)
it('trust directory bufnr', function()
local cwd = fn.getcwd()
local full_path = cwd .. pathsep .. 'test_dir'
command('edit test_dir')
eq({ true, full_path }, exec_lua([[return {vim.secure.trust({action='allow', bufnr=0})}]]))
local trust = read_file(stdpath('state') .. pathsep .. 'trust')
eq(string.format('directory %s', full_path), vim.trim(trust))
end)
end)
end)

View File

@ -55,9 +55,14 @@ describe('vim.system', function()
describe('(' .. name .. ')', function()
it('failure modes', function()
t.matches(
'ENOENT%: no such file .*: "non%-existent%-cmd"',
"ENOENT%: no such file .* %(cmd%): 'non%-existent%-cmd'",
t.pcall_err(system, { 'non-existent-cmd', 'arg1', 'arg2' }, { text = true })
)
t.matches(
"ENOENT%: no such file .* %(cwd%): 'non%-existent%-cwd'",
t.pcall_err(system, { 'echo', 'hello' }, { cwd = 'non-existent-cwd', text = true })
)
end)
it('can run simple commands', function()

View File

@ -1528,11 +1528,16 @@ describe('lua stdlib', function()
pcall_err(exec_lua, "vim.validate('arg1', nil, {'number', 'string'})")
)
-- Pass an additional message back.
-- Validator func can return an extra "Info" message.
matches(
'arg1: expected %?, got 3. Info: TEST_MSG',
pcall_err(exec_lua, "vim.validate('arg1', 3, function(a) return a == 1, 'TEST_MSG' end)")
)
-- Caller can override the "expected" message.
eq(
'arg1: expected TEST_MSG, got nil',
pcall_err(exec_lua, "vim.validate('arg1', nil, 'table', 'TEST_MSG')")
)
end)
it('vim.validate (spec form)', function()
@ -3925,6 +3930,17 @@ stack traceback:
]]
)
end)
it('can get Visual selection in current buffer #34162', function()
insert('foo bar baz')
feed('gg0fbvtb')
local text = exec_lua([[
return vim.api.nvim_buf_call(0, function()
return vim.fn.getregion(vim.fn.getpos('.'), vim.fn.getpos('v'))
end)
]])
eq({ 'bar ' }, text)
end)
end)
describe('vim.api.nvim_win_call', function()

View File

@ -78,6 +78,15 @@ describe(':checkhealth', function()
]])
)
end)
it("vim.provider works with a misconfigured 'shell'", function()
clear()
command([[set shell=echo\ WRONG!!!]])
command('let g:loaded_perl_provider = 0')
command('let g:loaded_python3_provider = 0')
command('checkhealth vim.provider')
eq(nil, string.match(curbuf_contents(), 'WRONG!!!'))
end)
end)
describe('vim.health', function()
@ -91,7 +100,7 @@ describe('vim.health', function()
n.expect([[
==============================================================================
test_plug.full_render: require("test_plug.full_render.health").check()
test_plug.full_render: 1 ⚠️ 1 ❌
report 1 ~
- ✅ OK life is fine
@ -109,12 +118,39 @@ describe('vim.health', function()
]])
end)
it('user FileType handler can modify report', function()
-- Define a FileType autocmd that removes emoji chars.
source [[
autocmd FileType checkhealth :set modifiable | silent! %s/\v( ?[^\x00-\x7F])//g
checkhealth full_render
]]
n.expect([[
==============================================================================
test_plug.full_render: 1 1
report 1 ~
- OK life is fine
- WARNING no what installed
- ADVICE:
- pip what
- make what
report 2 ~
- stuff is stable
- ERROR why no hardcopy
- ADVICE:
- :help |:hardcopy|
- :help |:TOhtml|
]])
end)
it('concatenates multiple reports', function()
command('checkhealth success1 success2 test_plug')
n.expect([[
==============================================================================
test_plug: require("test_plug.health").check()
test_plug:
report 1 ~
- ✅ OK everything is fine
@ -123,7 +159,7 @@ describe('vim.health', function()
- ✅ OK nothing to see here
==============================================================================
test_plug.success1: require("test_plug.success1.health").check()
test_plug.success1:
report 1 ~
- ✅ OK everything is fine
@ -132,7 +168,7 @@ describe('vim.health', function()
- ✅ OK nothing to see here
==============================================================================
test_plug.success2: require("test_plug.success2.health").check()
test_plug.success2:
another 1 ~
- ✅ OK ok
@ -144,7 +180,7 @@ describe('vim.health', function()
n.expect([[
==============================================================================
test_plug.submodule: require("test_plug.submodule.health").check()
test_plug.submodule:
report 1 ~
- ✅ OK everything is fine
@ -159,7 +195,7 @@ describe('vim.health', function()
n.expect([[
==============================================================================
test_plug.submodule_empty: require("test_plug.submodule_empty.health").check()
test_plug.submodule_empty: 1 ❌
- ❌ ERROR The healthcheck report for "test_plug.submodule_empty" plugin is empty.
]])
@ -185,7 +221,7 @@ describe('vim.health', function()
- ❌ {Error:ERROR} No healthcheck found for "foo" plugin. |
|
{Bar: }|
{h1:test_plug.success1: require("test_pl}|
{h1:test_plug.success1: }|
|
{h2:report 1} |
- ✅ {Ok:OK} everything is fine |
@ -200,7 +236,7 @@ describe('vim.health', function()
n.expect([[
==============================================================================
non_existent_healthcheck:
non_existent_healthcheck: 1 ❌
- ❌ ERROR No healthcheck found for "non_existent_healthcheck" plugin.
]])
@ -218,7 +254,7 @@ describe('vim.health', function()
n.expect([[
==============================================================================
test_plug.lua: require("test_plug.lua.health").check()
test_plug.lua:
nested lua/ directory ~
- ✅ OK everything is ok
@ -236,7 +272,7 @@ describe('vim.health', function()
n.expect([[
==============================================================================
nest: require("nest.health").check()
nest:
healthy pack ~
- ✅ OK healthy ok
@ -245,17 +281,6 @@ describe('vim.health', function()
end)
end)
describe(':checkhealth vim.provider', function()
it("works correctly with a wrongly configured 'shell'", function()
clear()
command([[set shell=echo\ WRONG!!!]])
command('let g:loaded_perl_provider = 0')
command('let g:loaded_python3_provider = 0')
command('checkhealth vim.provider')
eq(nil, string.match(curbuf_contents(), 'WRONG!!!'))
end)
end)
describe(':checkhealth window', function()
before_each(function()
clear { args = { '-u', 'NORC', '+set runtimepath+=test/functional/fixtures' } }
@ -281,14 +306,14 @@ describe(':checkhealth window', function()
^ |
{14: }|
{14: } |
{h1:test_plug.success1: }|
{h1:require("test_plug.success1.health").check()} |
{h1:test_plug. }|
{h1:success1: }|
{h1: ✅} |
|
{h2:report 1} |
- ✅ {32:OK} everything is fine |
|
{h2:report 2} |
- ✅ {32:OK} nothing to see here |
## grid 3
|
]],
@ -324,8 +349,8 @@ describe(':checkhealth window', function()
{14: } |
{h1:test_plug. }|
{h1:success1: }|
{h1:require("test_plug. }|
{h1:success1.health").check()}|
{h1: }|
{h1: ✅} |
|
{h2:report 1} |
- ✅ {32:OK} everything is |
@ -383,15 +408,15 @@ describe(':checkhealth window', function()
^ |
|
|
test_plug.success1: |
require("test_plug.success1.health").check() |
test_plug. |
success1: |
✅ |
|
report 1 |
- ✅ OK everything is fine |
|
report 2 |
- ✅ OK nothing to see here |
|
]]):format(
top
and [[

View File

@ -618,24 +618,53 @@ describe('vim.lsp.completion: item conversion', function()
},
},
},
{
label = 'insert_replace_edit',
kind = 9,
textEdit = {
newText = 'foobar',
insert = {
start = { line = 0, character = 7 },
['end'] = { line = 0, character = 11 },
},
replace = {
start = { line = 0, character = 0 },
['end'] = { line = 0, character = 0 },
},
},
},
},
}
local expected = {
abbr = ' this_thread',
dup = 1,
empty = 1,
icase = 1,
info = '',
kind = 'Module',
menu = '',
abbr_hlgroup = '',
word = 'this_thread',
{
abbr = ' this_thread',
dup = 1,
empty = 1,
icase = 1,
info = '',
kind = 'Module',
menu = '',
abbr_hlgroup = '',
word = 'this_thread',
},
{
abbr = 'insert_replace_edit',
dup = 1,
empty = 1,
icase = 1,
info = '',
kind = 'Module',
menu = '',
abbr_hlgroup = '',
word = 'foobar',
},
}
local result = complete(' std::this|', completion_list)
eq(7, result.server_start_boundary)
local item = result.items[1]
item.user_data = nil
eq(expected, item)
for _, item in ipairs(result.items) do
item.user_data = nil
end
eq(expected, result.items)
end)
it('should search from start boundary to cursor position', function()

View File

@ -108,7 +108,7 @@ describe('vim.lsp.diagnostic', function()
exec_lua(function()
diagnostic_bufnr = vim.uri_to_bufnr(fake_uri)
local lines = { '1st line of text', '2nd line of text', 'wow', 'cool', 'more', 'lines' }
local lines = { '1st line', '2nd line of text', 'wow', 'cool', 'more', 'lines' }
vim.fn.bufload(diagnostic_bufnr)
vim.api.nvim_buf_set_lines(diagnostic_bufnr, 0, 1, false, lines)
vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
@ -283,6 +283,36 @@ describe('vim.lsp.diagnostic', function()
eq('Pull Diagnostic', diags[1].message)
end)
it('handles multiline diagnostic ranges #33782', function()
local diags = exec_lua(function()
vim.lsp.diagnostic.on_diagnostic(nil, {
kind = 'full',
items = {
_G.make_error('Pull Diagnostic', 0, 6, 1, 10),
},
}, {
params = {
textDocument = { uri = fake_uri },
},
uri = fake_uri,
client_id = client_id,
bufnr = diagnostic_bufnr,
}, {})
return vim.diagnostic.get(diagnostic_bufnr)
end)
local lines = exec_lua(function()
return vim.api.nvim_buf_get_lines(diagnostic_bufnr, 0, -1, false)
end)
-- This test case must be run over a multiline diagnostic in which the start line is shorter
-- than the end line, and the end_col exceeds the start line's length.
eq(#lines[1], 8)
eq(#lines[2], 16)
eq(1, #diags)
eq(6, diags[1].col)
eq(10, diags[1].end_col)
end)
it('severity defaults to error if missing', function()
---@type vim.Diagnostic[]
local diagnostics = exec_lua(function()

View File

@ -5,7 +5,6 @@ local t_lsp = require('test.functional.plugin.lsp.testutil')
local assert_log = t.assert_log
local buf_lines = n.buf_lines
local clear = n.clear
local command = n.command
local dedent = t.dedent
local exec_lua = n.exec_lua
@ -219,6 +218,25 @@ describe('LSP', function()
)
end)
end)
it('does not reuse an already-stopping client #33616', function()
-- we immediately try to start a second client with the same name/root
-- before the first one has finished shutting down; we must get a new id.
local clients = exec_lua([[
local client1 = vim.lsp.start({
name = 'dup-test',
cmd = { vim.v.progpath, '-l', fake_lsp_code, 'basic_init' },
}, { attach = false })
vim.lsp.get_client_by_id(client1):stop()
local client2 = vim.lsp.start({
name = 'dup-test',
cmd = { vim.v.progpath, '-l', fake_lsp_code, 'basic_init' },
}, { attach = false })
return { client1, client2 }
]])
local c1, c2 = clients[1], clients[2]
eq(false, c1 == c2, 'Expected a fresh client while the old one is stopping')
end)
end)
describe('basic_init test', function()
@ -290,7 +308,6 @@ describe('LSP', function()
it(
"should set the client's offset_encoding when positionEncoding capability is supported",
function()
clear()
exec_lua(create_server_definition)
local result = exec_lua(function()
local server = _G._create_server({
@ -573,7 +590,6 @@ describe('LSP', function()
end)
it('should detach buffer on bufwipe', function()
clear()
exec_lua(create_server_definition)
local result = exec_lua(function()
local server = _G._create_server()
@ -606,7 +622,6 @@ describe('LSP', function()
end)
it('should not re-attach buffer if it was deleted in on_init #28575', function()
clear()
exec_lua(create_server_definition)
exec_lua(function()
local server = _G._create_server({
@ -637,7 +652,6 @@ describe('LSP', function()
end)
it('should allow on_lines + nvim_buf_delete during LSP initialization #28575', function()
clear()
exec_lua(create_server_definition)
exec_lua(function()
local initialized = false
@ -812,7 +826,6 @@ describe('LSP', function()
end)
it('BufWritePre does not send notifications if server lacks willSave capabilities', function()
clear()
exec_lua(create_server_definition)
local messages = exec_lua(function()
local server = _G._create_server({
@ -837,7 +850,6 @@ describe('LSP', function()
end)
it('BufWritePre sends willSave / willSaveWaitUntil, applies textEdits', function()
clear()
exec_lua(create_server_definition)
local result = exec_lua(function()
local server = _G._create_server({
@ -1252,6 +1264,65 @@ describe('LSP', function()
}
end)
it('request should not be pending for sync responses (in-process LS)', function()
--- @type boolean
local pending_request = exec_lua(function()
local function server(dispatchers)
local closing = false
local srv = {}
local request_id = 0
function srv.request(method, _params, callback, notify_reply_callback)
if method == 'textDocument/formatting' then
callback(nil, {})
elseif method == 'initialize' then
callback(nil, {
capabilities = {
textDocument = {
formatting = true,
},
},
})
elseif method == 'shutdown' then
callback(nil, nil)
end
request_id = request_id + 1
if notify_reply_callback then
notify_reply_callback(request_id)
end
return true, request_id
end
function srv.notify(method)
if method == 'exit' then
dispatchers.on_exit(0, 15)
end
end
function srv.is_closing()
return closing
end
function srv.terminate()
closing = true
end
return srv
end
local client_id = assert(vim.lsp.start({ cmd = server }))
local client = assert(vim.lsp.get_client_by_id(client_id))
local ok, request_id = client:request('textDocument/formatting', {})
assert(ok)
local has_pending = client.requests[request_id] ~= nil
vim.lsp.stop_client(client_id)
return has_pending
end)
eq(false, pending_request, 'expected no pending requests')
end)
it('should trigger LspRequest autocmd when requests table changes', function()
local expected_handlers = {
{ NIL, {}, { method = 'finish', client_id = 1 } },
@ -3810,7 +3881,6 @@ describe('LSP', function()
end)
it('opens the quickfix list with the right subtypes', function()
clear()
exec_lua(create_server_definition)
local qflist = exec_lua(function()
local clangd_response = {
@ -3958,7 +4028,6 @@ describe('LSP', function()
end)
it('opens the quickfix list with the right subtypes and details', function()
clear()
exec_lua(create_server_definition)
local qflist = exec_lua(function()
local jdtls_response = {
@ -4059,7 +4128,6 @@ describe('LSP', function()
end)
it('opens the quickfix list with the right supertypes', function()
clear()
exec_lua(create_server_definition)
local qflist = exec_lua(function()
local clangd_response = {
@ -4208,7 +4276,6 @@ describe('LSP', function()
end)
it('opens the quickfix list with the right supertypes and details', function()
clear()
exec_lua(create_server_definition)
local qflist = exec_lua(function()
local jdtls_response = {
@ -4562,7 +4629,6 @@ describe('LSP', function()
end)
it('fallback to command execution on resolve error', function()
clear()
exec_lua(create_server_definition)
local result = exec_lua(function()
local server = _G._create_server({
@ -4607,7 +4673,6 @@ describe('LSP', function()
end)
it('resolves command property', function()
clear()
exec_lua(create_server_definition)
local result = exec_lua(function()
local server = _G._create_server({
@ -4817,7 +4882,6 @@ describe('LSP', function()
['file:///fake/uri1'] = 'Lens1',
['file:///fake/uri2'] = 'Lens2',
}
clear()
exec_lua(create_server_definition)
-- setup lsp
@ -5327,7 +5391,6 @@ describe('LSP', function()
describe('vim.lsp.tagfunc', function()
before_each(function()
clear()
---@type lsp.Location[]
local mock_locations = {
{
@ -5668,7 +5731,7 @@ describe('LSP', function()
local created, changed, deleted
setup(function()
clear()
n.clear()
created = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]])
changed = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]])
deleted = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]])
@ -6247,7 +6310,7 @@ describe('LSP', function()
end)
describe('vim.lsp.config() and vim.lsp.enable()', function()
it('can merge settings from "*"', function()
it('merges settings from "*"', function()
eq(
{
name = 'foo',
@ -6263,6 +6326,15 @@ describe('LSP', function()
)
end)
it('config("bogus") shows a hint', function()
matches(
'hint%: to resolve a config',
pcall_err(exec_lua, function()
vim.print(vim.lsp.config('non-existent-config'))
end)
)
end)
it('sets up an autocmd', function()
eq(
1,
@ -6280,7 +6352,7 @@ describe('LSP', function()
)
end)
it('attaches to buffers', function()
it('attaches to buffers when they are opened', function()
exec_lua(create_server_definition)
local tmp1 = t.tmpname(true)
@ -6329,6 +6401,67 @@ describe('LSP', function()
)
end)
it('attaches/detaches preexisting buffers', function()
exec_lua(create_server_definition)
local tmp1 = t.tmpname(true)
local tmp2 = t.tmpname(true)
exec_lua(function()
vim.cmd.edit(tmp1)
vim.bo.filetype = 'foo'
_G.foo_buf = vim.api.nvim_get_current_buf()
vim.cmd.edit(tmp2)
vim.bo.filetype = 'bar'
_G.bar_buf = vim.api.nvim_get_current_buf()
local server = _G._create_server({
handlers = {
initialize = function(_, _, callback)
callback(nil, { capabilities = {} })
end,
},
})
vim.lsp.config('foo', {
cmd = server.cmd,
filetypes = { 'foo' },
root_markers = { '.foorc' },
})
vim.lsp.config('bar', {
cmd = server.cmd,
filetypes = { 'bar' },
root_markers = { '.foorc' },
})
vim.lsp.enable('foo')
vim.lsp.enable('bar')
end)
eq(
{ 1, 'foo', 1, 'bar' },
exec_lua(function()
local foos = vim.lsp.get_clients({ bufnr = assert(_G.foo_buf) })
local bars = vim.lsp.get_clients({ bufnr = assert(_G.bar_buf) })
return { #foos, foos[1].name, #bars, bars[1].name }
end)
)
-- Now disable the 'foo' lsp and confirm that it's detached from the buffer it was previous
-- attached to.
exec_lua([[vim.lsp.enable('foo', false)]])
eq(
{ 0, 'foo', 1, 'bar' },
exec_lua(function()
local foos = vim.lsp.get_clients({ bufnr = assert(_G.foo_buf) })
local bars = vim.lsp.get_clients({ bufnr = assert(_G.bar_buf) })
return { #foos, 'foo', #bars, bars[1].name }
end)
)
end)
it('does not attach to buffers more than once if no root_dir', function()
exec_lua(create_server_definition)
@ -6396,6 +6529,58 @@ describe('LSP', function()
end)
end)
it('starts correct LSP and stops incorrect LSP when filetype changes', function()
exec_lua(create_server_definition)
local tmp1 = t.tmpname(true)
exec_lua(function()
local server = _G._create_server({
handlers = {
initialize = function(_, _, callback)
callback(nil, { capabilities = {} })
end,
},
})
vim.lsp.config('foo', {
cmd = server.cmd,
filetypes = { 'foo' },
root_markers = { '.foorc' },
})
vim.lsp.config('bar', {
cmd = server.cmd,
filetypes = { 'bar' },
root_markers = { '.foorc' },
})
vim.lsp.enable('foo')
vim.lsp.enable('bar')
vim.cmd.edit(tmp1)
end)
local count_clients = function()
return exec_lua(function()
local foos = vim.lsp.get_clients({ name = 'foo', bufnr = 0 })
local bars = vim.lsp.get_clients({ name = 'bar', bufnr = 0 })
return { #foos, 'foo', #bars, 'bar' }
end)
end
-- No filetype on the buffer yet, so no LSPs.
eq({ 0, 'foo', 0, 'bar' }, count_clients())
-- Set the filetype to 'foo', confirm a LSP starts.
exec_lua([[vim.bo.filetype = 'foo']])
eq({ 1, 'foo', 0, 'bar' }, count_clients())
-- Set the filetype to 'bar', confirm a new LSP starts, and the old one goes away.
exec_lua([[vim.bo.filetype = 'bar']])
eq({ 0, 'foo', 1, 'bar' }, count_clients())
end)
it('validates config on attach', function()
local tmp1 = t.tmpname(true)
exec_lua(function()
@ -6491,5 +6676,135 @@ describe('LSP', function()
end)
)
end)
it('does not allow wildcards in config name', function()
local err =
'.../lsp.lua:0: name: expected non%-wildcard string, got foo%*%. Info: LSP config name cannot contain wildcard %("%*"%)'
matches(
err,
pcall_err(exec_lua, function()
local _ = vim.lsp.config['foo*']
end)
)
matches(
err,
pcall_err(exec_lua, function()
vim.lsp.config['foo*'] = {}
end)
)
matches(
err,
pcall_err(exec_lua, function()
vim.lsp.config('foo*', {})
end)
)
-- Exception for '*'
pcall(exec_lua, function()
vim.lsp.config('*', {})
end)
end)
it('correctly handles root_markers', function()
--- Setup directories for testing
-- root/
-- ├── dir_a/
-- │ ├── dir_b/
-- │ │ ├── target
-- │ │ └── marker_d
-- │ ├── marker_b
-- │ └── marker_c
-- └── marker_a
---@param filepath string
local function touch(filepath)
local file = io.open(filepath, 'w')
if file then
file:close()
end
end
local tmp_root = tmpname(false)
local marker_a = tmp_root .. '/marker_a'
local dir_a = tmp_root .. '/dir_a'
local marker_b = dir_a .. '/marker_b'
local marker_c = dir_a .. '/marker_c'
local dir_b = dir_a .. '/dir_b'
local marker_d = dir_b .. '/marker_d'
local target = dir_b .. '/target'
mkdir(tmp_root)
touch(marker_a)
mkdir(dir_a)
touch(marker_b)
touch(marker_c)
mkdir(dir_b)
touch(marker_d)
touch(target)
exec_lua(create_server_definition)
exec_lua(function()
_G._custom_server = _G._create_server()
end)
---@param root_markers (string|string[])[]
---@param expected_root_dir string?
local function markers_resolve_to(root_markers, expected_root_dir)
exec_lua(function()
vim.lsp.config['foo'] = {}
vim.lsp.config('foo', {
cmd = _G._custom_server.cmd,
reuse_client = function()
return false
end,
filetypes = { 'foo' },
root_markers = root_markers,
})
vim.lsp.enable('foo')
vim.cmd.edit(target)
vim.bo.filetype = 'foo'
end)
retry(nil, 1000, function()
eq(
expected_root_dir,
exec_lua(function()
local clients = vim.lsp.get_clients()
return clients[#clients].root_dir
end)
)
end)
end
markers_resolve_to({ 'marker_d' }, dir_b)
markers_resolve_to({ 'marker_b' }, dir_a)
markers_resolve_to({ 'marker_c' }, dir_a)
markers_resolve_to({ 'marker_a' }, tmp_root)
markers_resolve_to({ 'foo' }, nil)
markers_resolve_to({ { 'marker_b', 'marker_a' }, 'marker_d' }, dir_a)
markers_resolve_to({ 'marker_a', { 'marker_b', 'marker_d' } }, tmp_root)
markers_resolve_to({ 'foo', { 'bar', 'baz' }, 'marker_d' }, dir_b)
end)
end)
describe('vim.lsp.is_enabled()', function()
it('works', function()
exec_lua(function()
vim.lsp.config('foo', {
cmd = { 'foo' },
root_markers = { '.foorc' },
})
end)
-- LSP config defaults to disabled.
eq(false, exec_lua([[return vim.lsp.is_enabled('foo')]]))
-- Confirm we can enable it.
exec_lua([[vim.lsp.enable('foo')]])
eq(true, exec_lua([[return vim.lsp.is_enabled('foo')]]))
-- And finally, disable it again.
exec_lua([[vim.lsp.enable('foo', false)]])
eq(false, exec_lua([[return vim.lsp.is_enabled('foo')]]))
end)
end)
end)

View File

@ -131,7 +131,7 @@ describe(':Tutor', function()
{0: }University. E-mail: {2:bware@mines.colorado.edu}. |
]]
feed(':960<CR>zt')
feed(':978<CR>zt')
screen:expect(expected)
end)
end)

View File

@ -322,6 +322,27 @@ describe(':terminal buffer', function()
eq({ mode = 'nt', blocking = false }, api.nvim_get_mode())
command('bd!')
end)
it('correct size when switching buffers', function()
local term_buf = api.nvim_get_current_buf()
command('file foo | enew | vsplit')
api.nvim_set_current_buf(term_buf)
screen:expect([[
tty ready │ |
^rows: 5, cols: 25 │{4:~ }|
│{4:~ }|*3
{17:foo }{1:[No Name] }|
|
]])
feed('<C-^><C-W><C-O><C-^>')
screen:expect([[
tty ready |
^rows: 5, cols: 25 |
rows: 6, cols: 50 |
|*4
]])
end)
end)
describe(':terminal buffer', function()

View File

@ -440,19 +440,32 @@ describe('TUI', function()
]])
end)
it('interprets <Esc>[27u as <Esc>', function()
it('interprets <Esc> encoded with kitty keyboard protocol', function()
child_session:request(
'nvim_exec2',
[[
nnoremap <M-;> <Nop>
nnoremap <Esc> AESC<Esc>
nnoremap <C-Esc> ACtrlEsc<Esc>
nnoremap <D-Esc> ASuperEsc<Esc>
nnoremap ; Asemicolon<Esc>
]],
{}
)
-- Works with no modifier
feed_data('\027[27u;')
expect_child_buf_lines({ 'ESCsemicolon' })
-- Works with Ctrl modifier
feed_data('\027[27;5u')
expect_child_buf_lines({ 'ESCsemicolonCtrlEsc' })
-- Works with Super modifier
feed_data('\027[27;9u')
expect_child_buf_lines({ 'ESCsemicolonCtrlEscSuperEsc' })
-- Works with NumLock modifier (which should be the same as no modifier) #33799
feed_data('\027[27;129u')
expect_child_buf_lines({ 'ESCsemicolonCtrlEscSuperEscESC' })
screen:expect([[
ESCsemicolo^n |
ESCsemicolonCtrlEscSuperEscES^C |
{4:~ }|*3
{5:[No Name] [+] }|
|
@ -461,6 +474,7 @@ describe('TUI', function()
-- <Esc>; should be recognized as <M-;> when <M-;> is mapped
feed_data('\027;')
screen:expect_unchanged()
expect_child_buf_lines({ 'ESCsemicolonCtrlEscSuperEscESC' })
end)
it('interprets <Esc><Nul> as <M-C-Space> #17198', function()
@ -486,6 +500,38 @@ describe('TUI', function()
{3:-- INSERT --} |
{3:-- TERMINAL --} |
]])
child_session:request('nvim_set_keymap', 'i', '\031', '!!!', {})
feed_data('\031')
screen:expect([[
{6:^G^V^M}!!!^ |
{4:~ }|*3
{5:[No Name] [+] }|
{3:-- INSERT --} |
{3:-- TERMINAL --} |
]])
child_session:request('nvim_buf_delete', 0, { force = true })
child_session:request('nvim_set_option_value', 'laststatus', 0, {})
child_session:request(
'nvim_call_function',
'jobstart',
{ { testprg('shell-test'), 'INTERACT' }, { term = true } }
)
screen:expect([[
interact $ ^ |
|*4
{3:-- TERMINAL --} |*2
]])
-- mappings for C0 control codes should work in Terminal mode #33750
child_session:request('nvim_set_keymap', 't', '\031', '<Cmd>new<CR>', {})
feed_data('\031')
screen:expect([[
^ |
{4:~ }|
{5:[No Name] }|
interact $ |
|*2
{3:-- TERMINAL --} |
]])
end)
local function test_mouse_wheel(esc)
@ -1198,7 +1244,6 @@ describe('TUI', function()
pending('tty-test complains about not owning the terminal -- actions/runner#241')
end
screen:set_default_attr_ids({
[1] = { reverse = true }, -- focused cursor
[3] = { bold = true },
[19] = { bold = true, background = 121, foreground = 0 }, -- StatusLineTerm
})

View File

@ -2950,6 +2950,22 @@ describe('extmark decorations', function()
{1:~ }|*4
|
]])
-- Correct relativenumber for line below concealed line #33694
feed('4Gk')
screen:expect([[
{2: 2 }for _,item in ipairs(items) do |
{2:3 } if h^l_id_cell ~= nil then |
{2: 1 } hl_id = hl_id_cell |
{2: 3 } for _ = 1, (count or 1) do |
{2: 4 } local cell = line[colpos] |
{2: 5 } cell.text = text |
{2: 6 } cell.hl_id = hl_id |
{2: 7 } colpos = colpos+1 |
{2: 8 } end |
{2: 9 }end |
{1:~ }|*4
|
]])
-- Also with above virtual line #32744
command('set nornu')
api.nvim_buf_set_extmark(0, ns, 3, 0, { virt_lines = { { { "virt_below 4" } } } })
@ -3050,6 +3066,28 @@ describe('extmark decorations', function()
|
]])
end)
it('redraws extmark that starts and ends outisde the screen', function()
local lines = vim.split(('1'):rep(20), '', { plain = true })
api.nvim_buf_set_lines(0, 0, -1, true, lines)
api.nvim_buf_set_extmark(0, ns, 0, 0, { hl_group = 'ErrorMsg', end_row = 19, end_col = 0 })
screen:expect({
grid = [[
{4:^1} |
{4:1} |*13
|
]]
})
feed('<C-e>')
-- Newly visible line should also have the highlight.
screen:expect({
grid = [[
{4:^1} |
{4:1} |*13
|
]]
})
end)
end)
describe('decorations: inline virtual text', function()
@ -5913,6 +5951,33 @@ if (h->n_buckets < new_n_buckets) { // expand
]]
})
end)
it("not revealed before skipcol scrolling up with 'smoothscroll'", function()
api.nvim_set_option_value('smoothscroll', true, {})
api.nvim_buf_set_lines(0, 0, -1, false, { ('x'):rep(screen._width * 2) })
api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_lines_above = true, virt_lines = { { { 'VIRT1' } } } } )
feed('<C-E>')
screen:expect([[
{1:<<<}xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx^x|
{1:~ }|*10
|
]])
feed('<C-Y>')
screen:expect([[
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx^x|
{1:~ }|*9
|
]])
feed('<C-Y>')
screen:expect([[
VIRT1 |
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx^x|
{1:~ }|*8
|
]])
end)
end)
describe('decorations: signs', function()

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