Problem:
Undo places the cursor wherever the cursor happened to sit at "save
time" (`uh_cursor` is sampled lazily on the first change).
Examples:
- `i` preserves, but `a` does not
- `diw`, `atest<Esc>`, `d^` abandon the original position
- `D`, `o` restore it (by accident).
Solution:
`composite` tracks the pending atom (and its `origin`) across frames.
A `stuffed` continuation frame inherits the `origin` + prepped redo.
Store `origin` info in the undo header, so undo can restore it.
- Not for a mid-command undo break (i_CTRL-G_u).
- Undoing a mapping restores where the mapping started (which
technically may be different than where the "edit" started).
Problem:
`nlua_pcall()` references `_G.debug.traceback`. If user code deletes it
or breaks it some other way, various Lua features are broken.
_G.debug = nil
vim.schedule(function() end)
vim.wait(100)
E5113: Lua chunk: attempt to index a nil value
stack traceback:
[C]: in function 'loop_poll'
[string "vim/_core/editor"]:176: in function 'wait'
crash.lua:3: in main chunk
PANIC: unprotected error in call to Lua API (attempt to index a nil value)
Solution:
Check `_G.debug.traceback` before using it as errfunc. If it's broken,
omit the traceback and say so in the error message.
Note: We could cache `_G.debug` in LUA_REGISTRYINDEX on startup, but
that would prevent plugins from providing custom functionality there
(and we happen to do so in `tui_spec.lua` for example).
Problem:
`vim.on_key()` callbacks are invoked for keys that `getchar()` consumes,
which never reach the main loop, and there is no way to tell such a key
apart. With ui2 the `on_key` handler activated after interactive
cmdline, handles it as `pager_char` (thus enters the ui2 pager).
Solution:
Skip `vim.on_key()` callbacks during `getchar()`/`getcharstr()`.
Problem:
On Windows, vim.ui.open() passes URLs to `cmd.exe /c start`. cmd.exe
treats `&` as a command separator, so query strings are truncated.
Solution:
Caret-escape `&|<>^%!` in URIs when the open handler is cmd.exe.
Problem:
When `z=` delegates to `vim.ui.select()`, the picker may change the
current window before returning. `spell_suggest()` then continues to the
cursor restoration branch with the new window and assigns `prev_cursor`,
which belongs to the original window. This can leave Normal mode with an
invalid cursor position and produce E315.
Solution:
Clean up the spell suggestion state and return immediately after handing
control to `vim.ui.select()`.
Problem:
`vim.fs.slug()` does not handle URIs like `term://foo//123:bash`,
so callers (e.g. terminal persistence) must strip the scheme before
calling `slug()`.
Solution:
Detect `scheme://` from the raw input before `normalize()` and
replace it with a `=uri-<scheme>-` prefix.
Problem:
There is no unified notion of a "user action".
Vim processes input by one-char-at-a-time, and mostly throws away any
hints it might gather about the user's action, with one exception: it
stores the last _edit_ action (the "redo buffer", encoded as
unstructured `["x][v][count]body` bytes).
Plugins can only observe individual keys (vim.on_key) and high-level
effects (TextChanged, CursorMoved).
Solution:
- Users can subscribe to `CmdAtom` events to handle any user action.
- Event is deferred; handlers cannot cancel or interfere with user
actions.
- Capture `CmdSpec` from the normal/insert/visual subsystems.
- typeahead/readahead stay unstructured (`buffheader_T`): they are key
streams, not commands.
- the redo/record buffers become `StringBuilder`: fewer
allocations/copies.
- Repurpose the input/redo engine to accept `CmdSpec` objects.
"atom": one repeatable unit of user input, as a resolved (post-mapping)
keysequence plus structured fields. Only user actions, not `:normal`,
API calls, or non-"t" `feedkeys`.
BREAKING: dot-repeat of an Insert session, replays the entire session
including cursor-moves (:help ins-repeat).
BREAKING: dot-repeat of a Visual operation, replays the selection
instead of operating on a fixed-size region.
Problem:
vim.diagnostic.set() defers extmark position computation for an
unloaded buffer via a once=true BufRead autocmd, registering a new one
on every call without replacing the previous one. Each pending autocmd
also retains that call's diagnostics.
Solution:
Instead of registering an autocmd per set() call, register a single
static BufRead autocmd that computes positions from the diagnostic
cache for any buffer with cached diagnostics when it is read. This
removes the per-call registration entirely (nothing left to
accumulate) and means diagnostics cleared while the buffer was
unloaded no longer produce stale extmarks.
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.
`opts.plain=true` does not expand tildes in addition to environment
variables, unlike `opts.expand_env=false`.
`opts.expand_env=false` is soft-deprecated.
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
Problem:
Test fails if other "build*/" dirs exist with a "nvim" file, e.g.
`build-asan/bin/nvim`.
Solution:
Do the test in an isolated dir.
This also fixes the test for the zig build.
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.
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`.
Problem:
After expanding tabs with the `expandtab` option, if the old indent
matches the requested one, `vim.text.indent()` returns the input
unchanged.
The optimization path assumes that if old_indent == size, the input
wouldn't be changed, which is not correct when old_indent is formed by
expanded tabs.
Solution:
Apply the optimization only when `expandtab` is not set.
Problem:
Options parsing is still painful for dict-style options.
Solution:
schema-maxxing => better `opt:get()` (will be the basis for `vim.o()`),
unified (and more-detailed) err msgs.
- Drop bespoke structure-builder in `_core/options.lua`.
- Define `schema` for all non-primitive options (except 'guicursor' and
statusline-style options); generate reified keysets `OptKeyDict`).
- Generate 'fillchars' => `fcs_tab`, 'listchars' => `lcs_tab`.
- `nvim_set_option_value`:
- Return the improved structures. Also from `vim.opt.x:get()`.
- Eliminate api <=> lua roundtrip, centralize option structure
handling.
- Improve/unify errors.
- Bump ERR_BUFLEN 80 → 256 so the "one of" list isn't truncated.
- Eliminate old 'diffopt' order-dependence (`iwhiteall` before `iwhite`)
Error samples:
Typed-key path (opt_strings_check → diffopt/mousescroll/breakindentopt):
E474: Unknown item 'foo'
E474: 'context' requires a number
E474: 'ver' number is out of range
E474: 'algorithm' must be one of: myers, minimal, patience, histogram
E474: 'filler' does not take a value
Related:
- #31084
- #34661
- #31820
- #14739
- #20107
- fix#18875
- :get() returns `{ sbr = true, shift = '3' }` (reified-keyset) instead of `{'sbr', 'shift:3'}`
- Setting via table now works too. `object_as_optval_for` `is_map` now recognizes struct options.
- fix#30296
- instead of `E474: Invalid argument`, errors now look like:
```
E474: Invalid value 'x', expected one of: single, double: ambiwidth=x
E474: Unknown item 'foo': diffopt=foo
E474: 'context' requires a number: diffopt=context:x
```
simplify `win_float_parse_option` from #26799.
Problem:
When 'shellslash' is set, exepath() returns forward-slashed paths.
Passing that path to vim.system() can prevent cmd.exe from launching.
Solution:
Convert the resolved executable path to native separators before spawning.
Add a Windows regression test for cmd.exe with 'shellslash' enabled.
Problem:
The magic globals `it`, `describe`, etc., are more trouble than they are
worth.
- Hooking into `after_each` requires `getfenv()` hacks.
- They confuse luals/emmylua, because the top-level `.luarc.json` isn't
merged with `test/.luarc.json` (apparently a luals limitation?)
- They totally defeat discoverability because the user just has to
"know" about the various magic symbols.
So they harm DX, which means they serve no purpose at all.
Solution:
- Expose the test API from `testutil`, so tests can call `t.it()`,
`t.describe()`, etc., in the conventional way.
- Drop `getfenv()` hacks.
- Drop the `setfenv()` injection in `load_chunk`.
- Drop `test/_meta.lua`.
FAILED …/plugin/pack_spec.lua:92: …/plugin/pack_spec.lua @ 70: vim.pack update() can use lockfile revision as a target
…/plugin/pack_spec.lua:92: …/plugin/pack_spec.lua:70: process still exists
stack traceback:
D:/a/neovim/neovim/test/functional/testnvim/exec_lua.lua:124: in function 'system_sync'
…/plugin/pack_spec.lua:92: in function 'git_cmd'
…/plugin/pack_spec.lua:113: in function 'git_add_commit'
…/plugin/pack_spec.lua:1209: in function <…/plugin/pack_spec.lua:1173>
fix#40167
Problem:
Continue separators normalization, and try to keep it at the
nvim <-> external boundary, e.g., `fn.xxx`, `api.xxx`, `:xxx`
Solution:
some key changes
- normalize `$HOME-windows`
- normalize entry points of changing directory
- `nvim_set_current_dir()`, `chdir()`
- normalize the named pipe
- `--listen` arg, `--server` arg, `:restart`
- `serverstart()`, `sockconnect()`, `serverstop()`
- make `expand()` respect 'shellslash' again
- clean up `did_set_shellslash`
- replace `forward_slash` with `TO_SLASH`
- remove obsolete `TMP_PATHSEPSTR`
- remove `slash_adjust` in `do_autocmd_dirchanged`?
- make `fnamemodify()` always return `/` (except when substituting
separators via `:s`, `:gs` ?)
Note:
- these funcs still apply `slash_adjust` on return, as before:
`:pwd`, `chdir()`, `exepath()`, `getcwd()`
- these funcs alwarys return `/`, unlike before the normalization PRs
- `getcompletion()`, `finddir()`, `findfile()`
Also
- clean up code, comments, formatting and tests
- move `TO_SLASH` from `f_chdir` to `changedir_func`
- move `TO_SLASH` from `f_bufadd` to `buflist_new`
Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
Problem:
Malformed server responses with `null` (vim.NIL) values, do not show
clear error messages.
Solution:
Assert two known, specific cases, to avoid user confusion.
Problem:
URI buffer names can end with a slash, but slash-ended directory
matching should not preempt URI filetype patterns.
Solution:
Do not use the slash shortcut for names with URI schemes, so those
continue through normal filetype matching.
Co-authored-by: Adam Karafyllidis <akisarou90@gmail.com>
FAILED 2 tests, listed below:
FAILED …/lua/system_spec.lua:124: …/lua/system_spec.lua @ 44: vim.system (async) supports timeout
…/lua/system_spec.lua:124: …/lua/system_spec.lua:44: process still exists
stack traceback:
D:/a/neovim/neovim/test/functional/testnvim/exec_lua.lua:124: in function 'system'
…/lua/system_spec.lua:124: in function <…/lua/system_spec.lua:118>
FAILED …/lua/system_spec.lua:111: …/lua/system_spec.lua @ 22: vim.system (sync) can set environment with clear_env = true and env = nil
…/lua/system_spec.lua:111: …/lua/system_spec.lua:22: process still exists
stack traceback:
D:/a/neovim/neovim/test/functional/testnvim/exec_lua.lua:124: in function 'system'
…/lua/system_spec.lua:111: in function <…/lua/system_spec.lua:97>
Problem:
`vim.filetype.match()` needs a cheap way to recognize directory buffers
without doing filesystem stat work.
Solution:
Ensure full buffer names for directories end in a trailing slash. Now
directory buffers can proceed through the normal 'filetype' path.
Note side-effects: session and ShaDa buffer-list restore behavior must
be compatible, so those + corresponding tests must be updated.
Problem: No concise way to execute a callback with temporarily set
working directory. This might be useful when sourcing nested files to
allow them to assume that current working directory is their root
directory.
Solution: Add `cwd` context to `vim._with().`
fix(iconv): conversion to utf-16be using iconv is inconsistent
vim-patch:9.2.0769
Problem: enc_canonize function changes utf-16be to utf-16 but in linux
type utf-16 defaults to utf-16le.
Solution: Creating a separate entry for utf-16be in enc_canon_table.
Note: the effect is only visible on iconv implementations that
treat "utf-16" and "utf-16be" differently, so the test does
not necessarily fail on an unpatched Vim. But the bug is
visible in vim.iconv. (Manoj Panda)
from: vim/vim@2a63f74
closes: neovim#40262
Signed-off-by: Manoj Panda <manojpandawork@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
Problem:
vim.fs.dir() and vim.fs.find() drop errors returned by uv.fs_scandir().
Solution:
- vim.fs.dir():
- Return root scan failures as a secondary return value.
- Propagate recursive scan failures through the iterator. This allows
callers to distinguish unreadable directories from empty ones.
- vim.fs.find(): Collect errors during search, and return the list as
a second retval.
Problem:
- Lua<=>API roundtrips
- Although we prefer Lua for most business-logic code, doing this
conversion in C makes sense in this case because:
1. setting options is a hot path
2. most of the options logic lives in C
3. the current arrangement is MORE verbose and requires MORE code
Solution:
Move conversion to a C util.
- nvim_set_option_value passes the raw Object (scalar, Array, or Dict)
to `object_as_optval_for()` which flattens it to the canonical `:set`
string and validates the type.
- drop `convert_value_to_vim`, eliminate its roundtrip.
The added test shows the context, one expected a JSON field
to be a Object but it was a null value
pros: shows `vim.NIL` instead of `a userdata`
cons: the context `field 'foo'` is lost. I think this is generated
with internal magic which is hard to replicate.
Problem:
`nvim_set_option_value` cannot "update" options similar to `:set opt=`,
`:set opt+=`, etc. The Lua impls of "vim.opt" / "vim.o" have incomplete,
bespoke reimplementations of those operations.
ref #38420
Solution:
- Add `operation` param to `nvim_set_option_value`, which may be "set",
"append", "prepend", or "remove".
- Use this feature to implement `vim.opt` / `vim.o`.