4880 Commits

Author SHA1 Message Date
Kyle
8d406ed2ac
fix(defaults): emit events on automatic background change #41242
Problem:
After #40270, events are no longer emitted from the automatic background
detection. This applies not just during startup, but also if the user
manually changes the background of their terminal.

Solution:
Set the background as normal, assuming that a normal terminal will
respond within 100 ms. Change test to match expected behavior:
- BG set during startup won't trigger user autocmds since it runs before
  any user config
- If the terminal takes longer than 100 ms to respond to initial OSC 11,
  it does trigger the OptionSet, but it is triggered through the normal
  path to ensure values like v:option_new are set #38551
- BG change after startup still triggers autocmds #41146
2026-08-10 04:17:51 -04:00
Torben Leth
f538a4f16f
vim-patch:9.2.0926: filetype: Business Central files are not recognized (#41230)
Problem:  filetype: Business Central files are not recognized
Solution: Add filetype detection logic for *.al files to detect perl or
          use either perl or al filetype (Torben Leth).

Reference:
https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/developer/devenv-dev-overview

closes: vim/vim#20975

Supported by AI.

9b41cf6386
2026-08-09 00:49:20 +00:00
Mike J McGuirk
fe3aa64945
feat(lsp): pass target buffer to reuse_client predicate #41163
Problem: The reuse_client predicate does not pass the target buffer,
preventing decisions from being truly made per buffer.

Solution: Pass the target buffer.
2026-08-07 11:27:19 -04:00
Justin M. Keyes
2e0a5a596a
fix(terminal): spawn in effective CWD #41211
Problem:
`:terminal` does not respect the invocation-time CWD.
This wasn't noticeable with `:lcd` because the window-local CWD gets
applied to the new terminal buffer. But it is noticeable with `:bcd`.

Solution:
Specify `cwd` in the job spec.
2026-08-07 07:44:19 -04:00
Justin M. Keyes
a4a544032a
feat(cwd)!: :lcd! (bang), rearrange :bcd/:lcd/… scope precedence #41194
Problem:
- buf-local CWD scope is lower priority than :lcd, which is weird.
  ```
  win > buf > tab > global
  ```
- No way to clear current CWD at a given scope.

Solution:
- Rerrange scope precedence to:
  ```
  buf > win > tab > global
  ```
- Introduce "bang" variants (`:bcd!`/`:lcd!`/`:tcd!`) which clears the
  local CWD for the given scope.
2026-08-07 04:41:37 -04:00
zeertzjq
9c738cb718 vim-patch:9.2.0920: filetype: json-ld files are not recognized
Problem:  filetype: json-ld files are not recognized
Solution: Detect *.jsonld files as jsonld filetype, include
          filetype, indent and syntax plugins (Bogdan Barbu).

Reference:
https://www.w3.org/TR/json-ld11/

closes: vim/vim#20954

a1e2198a2c

Co-authored-by: Bogdan Barbu <l4b.bogdan.barbu@gmail.com>
2026-08-07 12:36:41 +08:00
Justin M. Keyes
eb19a52b7c feat(vim._with): keepcwd 2026-08-06 13:17:07 +02:00
Justin M. Keyes
1c1dc0558f feat(cwd): support explicit chdir (:bcd/:tcd/…) in temp context
Problem:
- Explicit `:bcd` (etc.) persists from `nvim_buf_call()` but not from an
  autocmd handler targeting a hidden buf (`LspAttach`, `TermRequest`, …),
  which needs a `vim.schedule()` workaround.
- `vim._with()` is supposed to work as a "sandbox", discarding
  side-effects, but it leaks CWD changes: `:lcd` from a `win` context,
  any chdir from a visible-buffer context.

Solution:
- Explicit :cd/:tcd/:bcd during a temp context persists by default.
  - "Ambient" directory changes ('autochdir', existing win-local CWD,
    etc.) are still undone, as before.
- Add `kCtxKeepDirs`: snapshot/restore the target's full CWD state
  (w/b/tp-local, global, cwd). Used by `vim._with()` and `'inccommand'`,
  which must not leak state.
2026-08-06 13:17:07 +02:00
Justin M. Keyes
9a93ac6533 refactor(vim.fs): slug() minor cleanup 2026-08-06 10:27:29 +02:00
Justin M. Keyes
7d2249c579 docs: misc, :bcd, slug() 2026-08-06 10:27:29 +02:00
Olivia Kinnear
6107629c5b
feat(fs): vim.fs.normalize{plain:boolean} #41127
`opts.plain=true` does not expand tildes in addition to environment
variables, unlike `opts.expand_env=false`.

`opts.expand_env=false` is soft-deprecated.
2026-08-05 16:11:13 -04:00
Justin M. Keyes
ab80ea92cc
fix(ui2): pager handling #41179
- Avoid shared state. Pass `focus` to set_pos()/expand_msg() instead of
  a shared `pager_focus` flag: the flag is only cleared when set_pos()
  actually enters the pager, so ":messages" from inside the pager left
  it set.
- pager_shown(): the pager window is invalid after leaving it with "q".
- Reuse pager_shown() in expand_msg().
2026-08-05 15:07:19 -04:00
Erdiansyah
a29a9130a9
fix(ui2): do not steal focus when consecutive cmds emit messages #41062
Problem:  A message emitted while a previous expanded message is still
          visible opens the pager and enters it, moving focus away from
          the buffer window without an explicit request (#41061).
Solution: Only enter the pager when it was explicitly requested ("g<",
          :messages, or entered from the expanded cmdline). An unfocused
          pager is dismissed by the cmdline key handler, which stays armed
          across the cmdline and no longer dismisses on non-typed keys
          (#39221).
2026-08-05 14:07:59 -04:00
Willaaaaaaa
7b03df5d54
feat(lua): vim.fs.slug() #41005
Problem:
Several subsystems need to derive a short, filesystem-safe identifier from an
arbitrary path, and each reinvents it ad-hoc:
- `'undodir'` and `swapfiles` encode the full path into a single filename, which
  may exceed filesystem length-limits.
- `:connect ssh://` needs the SSH ControlPath socket name to stay under the
  104-byte `sun_path` limit on macOS; today the path overflows it.
- the upcoming :terminal state dir.
- arbitrary plugin purposes.

Solution:
Provide `vim.fs.slug()`, which generates a bounded, one-way filename from an
arbitrary string. The input is normalized so equivalent paths produce the
same result. An 8-char hash is appended for uniqueness
2026-08-04 15:24:35 -04:00
Justin M. Keyes
9cd4dd1c19 fix(:bcd): do not "inherit" buffer-local dir
Problem:
Buffer-local CWD (:bcd) is "sticky", similar to window-local CWD (:lcd).
But this contradicts one of its main benefits: per-buffer "project root"
for LSP, OSC7.

Other problems:
- A buffer created with :edit/:enew/:new silently inherits b_localdir
  (and b_prevdir) from the previous buffer.
- curbuf_reusable() refuses to recycle a scratch buffer that has
  `b_localdir`.
- After :new/:vnew/:tabnew the CWD sticks to previous buffer's
  `b_localdir` even though the new curbuf has none, so :new is not
  equivalent to ":split | enew", and getcwd() disagrees with
  haslocaldir().
- Requires "which buffer spawned this buffer" semantics that no other
  buffer-local state has.

Solution:
Drop sticky/inherit behavior of buffer-local CWD (:bcd).

- do_ecmd: always apply the new curbuf's dir (`fix_current_dir`), like
  `do_autochdir` already does. :tabnew from a :bcd buffer now reverts to
  global CWD (and fires DirChanged), same as :tabnew from a :lcd window.
- curbuf_reusable(): recycling a scratch buffer frees its b_localdir.

To get sticky/inherit behavior of CWD, use `:lcd`.
2026-08-04 20:48:44 +02:00
Justin M. Keyes
46ca236525 fix(cwd): validate getcwd(…, -1) 2026-08-04 20:48:44 +02:00
Justin M. Keyes
db2e86fba4 refactor(editor): cleanup change-directory (:bcd) logic 2026-08-04 20:48:05 +02:00
saher
ea3868bcf9 feat(editor): :bcd changes buffer-local directory
Problem:
No way to set a buffer-local directory.
Use-cases:
- "Root dir" for LSP (and the "project" concept).
- `:terminal` OSC 7

Solution:
Add `:bcd` command.

- Extend `getcwd()` to take a third arg; `getcwd(-1, -1, bunfr)` returns
  the buffer-local working directory.
- Buffer-local directories have less priority than window-local
  ones, and more priority than tab-local ones.

Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
2026-08-04 20:09:48 +02:00
Justin M. Keyes
dab5fab948
Merge #41114 from epithet/default-ruler-as-expression 2026-08-04 06:51:34 -04:00
Sébastien Hoffmann
ca07e505f9 refactor(ruler)!: replace C implementation with 'rulerformat' expression
Problem:
The default 'ruler' is implemented in C instead of the 'statusline' DSL.

Solution:
Replace the C implementation with a default 'rulerformat' expression.
This is a continuation of #1248 and #33036.

Advantages:
- configuration is more discoverable, the default being a useful example
- users and plugins can augment the default
- code reuse and less C code to maintain
- ui2: due to the use of an item group with `minwid`, it can expand
  instead of truncating when the content gets too long, which is
  particularly useful for locales with long translations of Top/Bot/All

Implementation details:
As is the case for 'statusline', when trying to set 'rulerformat' to an
empty string, the default expression is restored instead, mimicking how
previously the default C implementation would have been activated.
Just like before, `:set rulerformat=` and `:set rulerformat&` have the
same effect, and the ruler is disabled with `:set noruler`.

The default expression uses an item group with `%=`, unlike the fallback
in the previous default statusline `%-14.(%l,%c%V%) %P`, because the
total width and how it is configured is immediately clear without
documentation, it is a more useful pattern in general that works when
both sides have flexible width, and it also works for vim, which is
useful for configuration sharing/reuse.

A truncation marker `%<` is added at the end to mimic how at small
screen widths, the scroll percentage would disappear first, so that the
cursor position can remain fully visible.

BREAKING CHANGES:
- `&rulerformat` can no longer be set to an empty string
- ui2: the default ruler is no longer of fixed width, but can expand
- at very small screen widths (< 36 columns)
  - ui2: it will no longer try to shrink white-space before truncating
  - it truncates gradually from the right, whereas previously, the
    scroll percentage would disappear all at once
- l10n can no longer add a space after the comma between line and column
  (this was only done for one language: Ukrainian)
2026-08-04 12:12:23 +02:00
Barrett Ruth
a29297ac53
docs(api): buffer columns are byte indices #41137 2026-08-04 05:19:54 -04:00
Rocco Vaccone
d0644230f1
fix(lsp): ignore (and log once) invalid documentSelector globs #41055
Problem:
Client:_get_registrations() errors if a documentSelector pattern is not
a valid glob (e.g. "**/**.dart" from dartls). The error discards
already-matched registrations and propagates to supports_method() and
the client/registerCapability handler, disabling capabilities unrelated
to the offending pattern.

Solution:
Skip invalid globs. Treat them as non-matching, and log once per client.

(Similar approach was taken for filewatcher globs: 4d9e5acfb5ee081b5b0761205c8a8c36ed7e49e5)
2026-08-04 05:14:15 -04:00
Sébastien Hoffmann
259e6fa9cc fix(ruler)!: consistent width in last line and statusline
Problem: Traditionally, the ruler in the last line is one cell shorter
than in the statusline, leaving the last cell of the screen blank.
According to code comments, this is in order to prevent unwanted
scrolling on "some" (unspecified, but presumably ancient) terminals.
Berkeley vi is more specific in its `vs_modeline` function: dumb
terminals with hardware scroll, SunOS 4.1.1 and Ultrix 4.2 curses.
(n)curses still has a similar limitation in `(w)addstr`, but apparently
only for historical reasons.

Maintaining the different widths leads to awkward inconsistencies when
the ruler is configured with 'rulerformat', except for the special case
where it contains a top-level `%=`. Shifting the ruler in the last line
to the left would be a solution, but the empty cell at the end doesn't
seem to be relevant anymore.

Solution: extend the ruler in the last line all the way to the right
edge of the screen, just like in the statusline. The exact same amount
of place will be available to the rest of the UI as before.

BREAKING CHANGE:
- the default ruler width is now 18 cells
- the last cell of the screen is no longer empty

Closes #41076
2026-08-04 06:36:58 +02:00
Maria Solano
1e6d4479d2
fix(lsp): only send didSave to clients attached to the buffer (#41126) 2026-08-03 11:43:33 -07:00
Freddie Haddad
67839a72f7
fix(treesitter): make TSHighlighter.new() idempotent for an active buffer #41090
Problem:
Calling vim.treesitter.start() a second time on a buffer that already
has an active TSHighlighter creates a brand new instance instead of
reusing it, whether the parser tree is unchanged (e.g. calling start()
twice) or different (e.g. switching languages). Either way the old
instance is silently discarded without calling :destroy() on it, so
its on_bytes/on_changedtree/on_detach callbacks stay registered and
its buffer-local state (spelloptions, decoration namespace) is never
restored, both leaking indefinitely for an orphaned instance that
nothing references anymore.

Solution:
Return the existing instance when TSHighlighter.active[source] is
already set for the same parser tree, instead of unconditionally
constructing a new one. When the tree differs instead (e.g. a language
switch), destroy() the old instance first, matching stop() semantics,
before constructing the new one.
2026-08-02 14:42:47 -04:00
Justin M. Keyes
f7fbe0e0c8
Merge #41106 from altermo/selection-refactor-fix 2026-08-02 05:33:17 -04:00
zeertzjq
e9190e9c4f
vim-patch:9.2.0894: filetype: ed script files not recognised (#41109)
Problem:  filetype: ed script files not recognised.
Solution: Add filetype detection for *.ed files and shebang lines,
          include syntax script and syntax tests.

Features of the ed syntax file:
- BSD and GNU extensions are supported
- Andrew L. Moore's ed extensions are not supported
- Rebuild synmenu.vim

closes: vim/vim#19602

c28515b999

Co-authored-by: Doug Kearns <dougkearns@gmail.com>
2026-08-02 09:56:10 +08:00
Justin M. Keyes
98d767cd53
docs: func/expr options, misc #41102 2026-08-01 17:48:32 -04:00
altermo
8695579636 fix(treesitter): handle &selection=exclusive 2026-08-01 18:21:59 +02:00
altermo
77dfa02f3e refactor: combine all visual_selection funcions 2026-08-01 18:20:13 +02:00
AlexCodesApps
b11f1b1f76
feat(vim.fs): abspath({cwd, plain}) #40597
Adds optional parameters to `vim.fs.abspath`:
1. `cwd` to specify directory akin to `--relative-from` in `realpath(1)`.
2. `plain` disables expansion of tilde (~) in paths.
2026-08-01 09:09:21 -04:00
Justin M. Keyes
3e01dcddbe refactor(options): memory management
Problem:
Numerous callers have to manually check optval ownership (i.e. whether,
and how, to release) via `is_callback_option`, `option_is_global_local`,
etc. This is fragile, hard to use correctly; and if we introduce another
optval variant in the future, we'll have to redo all of these careful
checks and boilerplate again.

Solution:
Provide a unified system and use it everywhere:

    optval_free_owned
    optval_is_owned
    optval_own
2026-07-31 21:50:04 +02:00
Justin M. Keyes
726d1a92d2 feat(options): lua closure/function options
Problem:
Cannot assign Lua functions/closures to "func" ('completefunc',
'tagfun', …) or "expr" ('foldexpr', 'indentexpr', …) options.

Solution:
- Store "func"/"expr" options as `Callback` instead of string.
- Delete oceans of copy-pasted code.
- BREAKING: LuaRef returned via RPC/Vimscript is now represented as
  `"<Lua N: file:line>"` (like what `:map` shows) instead of `nil`.
- Note: `man.vim` still uses `v:lua` string, bc it's a vimscript ftplugin.

Helped-by: Lewis Russell <lewis6991@gmail.com>
2026-07-31 21:50:04 +02:00
Justin M. Keyes
6cdab150bc
docs: misc #41063 2026-07-30 15:09:19 -04:00
zeertzjq
a85cce90e3
vim-patch:partial:b4ae16c: runtime(doc): clarify 'laststatus' effect (#41052)
fixes: vim/vim#20875

b4ae16ca3e

Co-authored-by: Christian Brabandt <cb@256bit.org>
2026-07-30 00:11:46 +00:00
Barrett Ruth
e29af9c32f
refactor(zip)!: builtin zip.lua plugin #40846
Problem:
The bundled `zip` plugin is implemented in Vimscript, making it harder to
maintain and build on with Nvim's Lua runtime infrastructure.

Solution:
Add an opt-out `zip.lua` browser backed by `nvim.dir`, and disable the legacy
Vimscript implementation by moving it to `pack/dist/opt/zip/`. Load it with
`:packadd zip`.
2026-07-29 12:54:43 -04:00
Nathan Zeng
1dbc766fa7
fix(ui2): clear empty cmdline after backspace #41043
Problem:
Cmdline area shows stale ":" after backspacing out of the command line.

Solution:
Clear the command line for empty commands. Note that `:<CR>` will now
clear the command line too.
2026-07-29 12:40:08 -04:00
flatplum
7b34bca2a6
docs(health): avoid warnings for dynamic filetypes #41036
Problem:
Dynamic filetypes defined by `vim.filetype.add` may show health warnings.

Solution:
Document a way to suppress these harmless warnings.
2026-07-29 09:03:53 -04:00
zeertzjq
1eba3a75da
vim-patch:9.2.0870: filetype: marko files are not recognized (#41038)
Problem:  filetype: marko files are not recognized
Solution: Detect *.marko files as marko filetype, include a syntax
          plugin and add syntax tests (Brian Carbone)

References:
https://markojs.com
https://github.com/marko-js/tree-sitter
https://github.com/marko-js/language-server
https://v5.markojs.com/docs/editor-plugins/

closes: vim/vim#20863

95da62a910

Co-authored-by: Brian Carbone <brian@briancarbone.com>
2026-07-29 09:14:48 +08:00
Evgeni Chasnovski
4bba83b4b7
fix(pack): do not write the lockfile when installing from it #41031
Problem: Installing plugins during lockfile synchronization always
  writes the lockfile, even though its content is used during install.
  This might be a problem if the lockfile (itself or its parent
  directory) is not writeable (can only be read).

Solution: Do not write the lockfile when installing directly from it.
  This is okay since the `src` and `rev` are used directly from the
  lockfile and don't change at this step. While potential change in
  `version` (that must be written to the lockfile) is handled in other
  code path.
2026-07-28 09:46:13 -04:00
glepnir
3fa5904cf2
fix(lsp): multibyte fragment treated as commit char #41019
Problem:
commit_chars_str() reads a 0x80-0xBF byte as a 2-byte lead, so
"\xA9x" makes "x" a commit character. nvim_get_hl() returns
a dict, so `#` on it is 0 whether or not the group exists

Solution:
use vim.str_utf_end() and drop entries that are not one whole
character. use next check dict length.
2026-07-28 08:39:03 -04:00
glepnir
916a6e9546
fix(eval): complete_info() equal, preselect, commit_chars #41022
Problem:  complete_info() does not report "equal", "preselect" or "commit_chars".

Solution: Report them for the items that set them.
2026-07-28 07:24:58 -04:00
Olivia Kinnear
5226e26e99
feat(pos): vim.pos.cursor() without args returns curwin pos #40975 2026-07-28 07:21:45 -04:00
altermo
59296a9fef
fix(treesitter): incremental selection: "vin" not always starts "v" #41016 2026-07-28 06:14:32 -04:00
zeertzjq
5745ea93f1
vim-patch:9.2.0860: filetype: xilinx design constraint files are not recognized (#41024)
Problem:  filetype: xilinx design constraint files are not recognized
Solution: Detect *.xdc files as tcl filetype (Wu, Zhenyu)

Reference:
https://docs.amd.com/r/en-US/ug903-vivado-using-constraints/About-XDC-Constraints

closes: vim/vim#20853

247a4cb45e

Co-authored-by: Wu, Zhenyu <wuzhenyu@ustc.edu>
2026-07-28 18:11:29 +08:00
Milad Rashidikhah
20ff82d9fc fix(treesitter): clamp unbounded fold ranges
Problem:
Tree-sitter uses UINT32_MAX for full-document ranges, which becomes -1 on 32-bit platforms and reaches _foldupdate as an invalid end row.

Solution:
Treat negative changed-range end rows as unbounded and clamp them to the buffer line count. Add a regression test that simulates the 32-bit sentinel.

AI-assisted: Codex
2026-07-28 11:00:30 +02:00
Justin M. Keyes
fbcb7a056c
feat(exmode): "1q:", :exmode #41010
Problem:
Want `gQ` for _le multicursor_.

Solution:
- Don't use `gQ` for exmode.
- Introduce `:exmode`.
- Introduce `[count]q:` as an alias to `:exmode`.
2026-07-27 11:12:47 -04:00
Justin M. Keyes
80cd482577
fix(lua): vim.regex():match_str abort in Luv callback #41008 2026-07-27 11:01:54 +00:00
Justin M. Keyes
359459dec6
refactor(exmode): Ex-mode as cmdwin + Lua #40991
Problem:
POSIX-compatible Ex-mode requires special-cases all over the codebase to
match various quirks that don't actually matter to users.
- The main utility of *interactive* Ex-mode is its REPL behavior, and
  that can be achieved with `cmdwin`, which also gains extra UX
  benefits.
- The main utility of *non-interactive* `nvim -es` is for shell
  scripting, where Ex-mode quirks are mostly unhelpful (e.g. the
  "Entering Ex mode" message).

Solution:
- Reimplement *interactive* Ex-mode as a "persistent, insert-mode
  cmdwin" in Lua.
  - "nvim -e/-E" is simply an alias to "gQ".
- Reframe *non-interactive* Ex-mode (`nvim -es`) as "script mode".
  - Drop POSIX Ex-mode quirks.

Improvements:
- "nvim -V1 -es" output ends with a final newline!
- "nvim -V1 -es" no longer shows the "Entering Ex mode" msg. (This was
  pointless noise, unwanted for scripting purposes.)
- stdin is no longer typeahead. Scripts (":lua io.read()") can read
  stdin as data.
- Empty line is a no-op: a stray blank line no longer moves the cursor
  (deviates from POSIX ex "+1"), no longer exits 1 at EOF (E501).

Preserved behavior:
- cursor starts at "$"
- mode()=="cv" (for non-interactive)
- multiline commands (:append/:function/heredoc pull continuation lines)
- bare-range print
- :print=>stdout
- -V1=>stderr
- CRLF input
- continue-after-error and exit codes

Dropped (regressed) POSIX behavior (non-interactive):
- Event loop only ticks while/between commands, not while blocked
  waiting for a stdin line.
- ":g/pat/visual...Q"
- input()/getchar()/":s/x/y/c" no longer consume stdin lines as
  answers: Nvim stops at end-of-input, skipping the rest of the script,
  exit 0. Use ":lua io.read()" instead.
  - If users care about this they should use interactive Ex-mode (`gQ`).
- ":@r" stops at end of the register instead of continuing to read
  cmdline input from stdin.
2026-07-27 06:25:21 -04:00
Rob Pilling
c57818bb43
fix(ui2): avoid flickering fileinfo messages #41002
## Flicker cause

ui2's `check_targets()` function creates the hidden ui2 windows with this call chain:
```
nvim_open_win()
win_set_buf()
do_buffer()
set_curbuf()
enter_buffer()
```

and `enter_buffer()` sets `need_fileinfo`:

fdc09be03c/src/nvim/buffer.c (L1837-L1839)

This is then picked up by `normal_redraw`:

fdc09be03c/src/nvim/normal.c (L1381-L1385)

The fileinfo message then causes a ui2 window to appear to display it:

fdc09be03c/runtime/lua/vim/_core/ui2/messages.lua (L325-L330)

... which ends up calling `set_pos`, which registers an `on_key` handler:

fdc09be03c/runtime/lua/vim/_core/ui2/messages.lua (L726-L728)

This handler closes the ui2 window when the user presses, for example, `l` (because the user's not focusing said window).
The handler eventually calls `check_targets()`:

fdc09be03c/runtime/lua/vim/_core/ui2/messages.lua (L594-L595)

and `check_targets()` takes us back to the beginning, causing the ui2 window flicker

## Repro

```vim
=require("vim._core.ui2").enable()
set ch=0 shm-=F ve=all
e /tmp/x
```

Then hit `Ctrl-G` to show fileinfo (if not already shown) and hold `l`

## Fix

Effectively surround the ui2 window creating with `:silent`
2026-07-26 17:40:06 -04:00