Problem:
Insert entered by a Visual-mode operator mapping (`xnoremap c c`) does
not live-mirror at cursors.
Solution:
Decide `typed` from the Visual session's own provenance (`vatom`, which
spans the selection and knows whether it was user input) instead of only
negating it.
Problem:
With follow-mode ("q="), an 'operatorfunc' that only moves the cursor
does not cascade.
Solution:
Update the `effect` condition to include cursor-moves if follow-mode is
active.
Problem:
With mcursors and 'autocomplete', a non-literal key (`<BS>`) during an
active completion attempts to insert-cascade, which attempts a nested
`edit()`, which refuses with E565.
Solution:
Defer the flush while completion is active; the pending keys will be
handled later.
Problem:
An atom queued in buffer A cascades on B's cursors if the mapping ends
in B ("nnoremap X x:bnext<CR>").
Solution:
Check the atom's origin buffer (`CmdAtom.origin.buf`).
Note: This does not preclude mappings etc from doing work in temporary
throwaway buffers, as long as they return to the origin buffer.
Problem:
If an edit cascades to cursor(s) that happen to be within a closed fold,
the edit acts on the fold itself (`:h fold-behavior`). E.g. "dd" deletes
the entire fold, not the cursor's line within the fold.
Solution:
Check `mc_replaying()` in `hasAnyFolding()`, so folds are ignored during
a multicursor cascade. This means multicursor always replays relative to
text *within* the fold.
Other (squashed) commits:
fix(tui): emit ui_send output atomically with the frame
Problem:
tui_ui_send() writes directly to the TTY, bypassing the output buffer.
Sequences sent via nvim_ui_send() (e.g. kitty multiple-cursors, or
visual-dot-repeat) always arrive in a separate TTY write from the frame
they were computed for. This manifests as "tearing", or e.g. in the case
of multicursor the terminal renders text with stale cursor overlays.
Solution:
- tui_ui_send(): while a frame is being assembled (pending invalid
regions or buffered output), buffer instead of writing directly.
- Out-of-frame sends (tty queries, clear-on-disable) still write
immediately.
- mcursor.lua: emit the terminal-cursor update at the end of the redraw
cycle (`on_end`, when screen positions are final) instead of
vim.schedule().
Problem:
Confirming cmdwin with a UTF-8 character containing 0x80 does not complete the
command.
Solution:
Escape K_SPECIAL bytes while feeding the cmdwin input after confirmation.
Problem:
The "." example mapping at `:h edit-repeat` doesn't work well with
`nvim_feedkeys(…, 'mt', false)`.
Solution:
Use `vim.b[ev.buf].maxseq` instead of `undotree()`.
Problem:
Replaying a deleted Lua mapping, may call an arbitrary function.
RHS of a Lua mapping embeds its LuaRef (`<K_LUA><ref><CR>`). The raw
keys may outlive the ref (redobuff ".", CmdAtom `keys`). If the mapping
is deleted, replaying it either (1) dereferences a freed registry slot,
or (2) calls whatever callback reused the slot (autocmd, timer, other
mapping).
Solution:
Assign a monotonic (never recycled) id to Lua mappings and encode the
mapping keys as `<K_LUA><id><CR>`.
Note: in the case of Vimscript, a deleted function raises E117, but if
the function is redefined with the same name, the mapping will find it.
Alternatives?:
- Globally ensure `LuaRef` ids are not recycled.
- Problem: could exhaust `int` in a long-lived Nvim session? Also,
difficult to impl bc the "recycling" is done by `luaL_ref` itself.
ref: 5ac2e47acc
Problem:
An operator completed by a Lua `:omap` textobject emits
`CmdAtom.type="mapping"` (lhs-only, no keys) instead of `type="operator"`.
Analysis:
`atom_redo_set()` declined K_LUA, though the prepped redo
("op" + K_LUA + id + CR) is exactly what "." replays. A no-edit "g@"
emits nothing at all.
Solution:
- `atom_redo_set`: don't decline K_LUA; the redo route now captures the
operator atom.
- `atom_capture_cmd`: don't early-return if the frame has prepped redo.
- op_function(): save/restore redobuff when invoking 'operatorfunc',
like `call_user_func()` does for Vimscript. (Else the Lua callback
may clobber the prepped "g@" redo / dot-repeat.)
fix#41482
TODO:
- async Lua (timer/vim.schedule) can still clobber the pending dot-repeat...
Problem:
The Vim "stuff" concept breaks the ability to reason about the call
stack and thus the boundaries of a `CmdAtom`: a stuffed translation ("x"
=> "dl") defers to the main loop. This "continuation" must be modeled in
`CmdAtom`, by checking global flags at undefined times, during undefined
circumstances.
Solution:
- After a stuffed "translation", eagerly execute the stuff buffer
(`exec_stuffed()`).
- Delete the CmdAtom "continuation" junk.
Note:
- op_colon runs its cmdline "nested", but that's fine because operators
already nest interactive sessions there (op_change runs edit()), and
the cmdline is frameless so the operator's frame can own/capture it.
Problem:
If an autocommand handler focuses the tab page we're closing during a
`:tabonly` (with some conditions), we hit an assert failure in
`win_close_othertab()`.
For this to occur, we need:
- `nvim_buf_delete()` to trigger `close_windows()` (as is done in
`cmdwin.lua`'s `_cleanup()`)
- `close_windows()` then calls `win_close_othertab()`, removing the
window (`win_free_mem()`)
- then in the caller, `tabpage_close_other()`, the loop continues (we
don't detect `tp_lastwin == wp` since we've unlinked the window)
- the loop assumes that `curtab != tp`
but we've refocused `curtab` so the `ex_win_close()` call passes `tp` as
`curtab`, causing the assert to fail
Solution:
Detect the focus of `curtab` and abort closing the tab.
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:
An insert-session entered by a scheduled `feedkeys('i','n')` is
classified on entry (as "not typed"), and not reevaluated after that,
thus user input following it is not captured.
Repro: an `:imap` that does `feedkeys('<esc>','n')` and schedules
re-entering insert, emits one CmdAtom for the first press, then nothing
else.
Solution:
Sample `maptick` (ticked by `gotchars()` on typed input, including
mappings) at session start; if it advanced by session end, the session
is user input.
Problem: ins_compl_dict_alloc() builds the same dict as
fill_complete_info_dict().
Solution: Call fill_complete_info_dict() instead (glepnir).
closes: vim/vim#21140303a153694
Problem:
A mapping that enters terminal-mode (`:FzfLua files` via `:startinsert`)
never reaches a composite-end: terminal-mode runs no "normal" CmdFrames.
The composite collects the entire terminal session (and more) into `lhs`.
E.g. if I have `<M-/>` mapped to open `:FzfLua files`, then interact
with fzf-lua UI, the emitted CmdAtom looks like:
<M-/>… => { type='mapping', lhs='<M-/><C-N><C-N><CR>', keys=nil }
Solution:
End the composite when terminal-mode is entered, which emits a more
meaningful and repeatable atom:
<M-/>… => { type='mapping', lhs='<M-/>', keys=nil }
Problem:
The "!" operator stuffs its cmdline continuation (`:.,.+1!`), so its
frame ends before capture (stuff pending) and the redo-prep disappears
with it.
!ipsort<CR> => { type='excmd', lhs=':.,.+1!sort<NL>', keys=':.,.+1!sort<NL>' }
Compare to builtin "." which works bc `do_bang()` completes the redo
(`!ip` + `sort<NL>`).
Solution:
Appoint the stuffed continuation frame as the "redo-prep" frame.
!ipsort<CR> => { type='operator', operator='!', lhs='!ipsort<NL>', keys='!ipsort<NL>' }
Notes:
- atom_cmd_end(): a frame ending with stuff pending re-points its
redo-prep to the next frame.
- atom_cmd_start(): a stuffed continuation frame (`KeyStuffed`) keeps
the redo-prep; flushed stuff discards it.
Problem:
A mapping that ends mid-operation (`nnoremap ,D d`) emits a content-free
"mapping" atom plus a `pending` field, and the "continuation" motion
arrives as a sibling atom. Consumers must stitch the two together (which
has broken cases, e.g. Insert-opening mappings (",i") lose their session
entirely).
- ",i": the session atom is dropped bc the mapping RHS is consumed
before the session starts (typebuf_maplen()==0), so
atom_is_user_input()=false.
- ":normal"-in-opfunc: the opfunc internal "v..y" session (a) became
kVatomTyped just because the deferred composite was open, masking the
real operator capture via atom_captures, and (b) its nested frames
re-derived the outer redo.
- "Motion" based on `moved=true`, has false negatives.
- `CmdAtom.remap` is unnecessary, and clutters the docs/usage.
Solution:
- Introduce `frame_id` to identify CmdFrames.
- Classify `type=motion` better, via `NV_MOTION` flag on the `nv_cmds` table.
- Drop `CmdAtom.pending`, `CmdAtom.remap`.
- Defer atom_composite_end() at the clock edge while an operator is
pending, Visual is active, or `restart_edit` is set: the composite
keeps collecting, so the continuation is captured in the mapping atom.
- ",i": Now an open composite counts as user input.
- ":normal"-in-opfunc: Now handled correctly.
- `remap` is now decided by `composite.payload || 0 subatoms`.
atom_payload_mark() records the read the resolution never captures.
- `toplevel` is now decided by `CmdFrame.parent == NULL`.
before/after:
INPUT BEFORE AFTER
---------------------------------------------------------------------
,D w {mapping lhs=,D pending=operator} {operator lhs=,Dw keys=dw}
+ {operator keys=dw}
ysiw" {mapping lhs=ys pending=operator} {operator lhs=ysiw" keys=g@iw"}
+ {operator lhs=g@iw"}
,v d {mapping pending=visual} {visual lhs=,vd keys=viwd}
+ {visual lhs=viwd}
,i XY<Esc> {normal keys=i lhs=,iXY<Esc>} {insert keys=1iXY<Esc> text=XY}
Problem:
- `lhs` is not fully realized. E.g. for a "payload" mapping
`lhs` omits the `getchar()` payload during a mapping (vim-surround
`ds'` reports `lhs="ds"`). This means plugins like vim-repeat are
still needed...
- The "delta" fields of a CmdAtom are calculated too late.
- `<abuf>` and `changed` check whatever (wrong) buffer a command
(":bnext") might land in.
- CTRL-W_w between two windows on the same buffer reports type="motion".
Solution:
- `CmdOrigin` samples (buf/win/cursor/changedtick) at each "scope" entry
(CmdFrame, composite, Visual session, insert session).
- `dd<C-w>l` reports `changed=true` for the buffer it edited, regardless
of where the cursor ends up.
- New fields:
- `pos`: cursor position at command start.
- `moved`: indicates whether the cursor moved (in same buffer).
- `undoseq`: undo state at settlement.
- lhs now includes the payload: "ds)" reports lhs="ds)" instead of "ds".
- Easy for users to "replay" any atom.
- Rename: type "command" => "normal", "ex" => "excmd"; `arg` => `cmdarg`
- Drop `cascade` field (no reason to expose it)
Problem:
An unreplayable Visual operation does not emit a CmdAtom event. That's
maybe not super important, but it hints at a flaw in how `vatom`
"voiding" is plumbed: `vatom.state=kVatomVoid` replaces the "kind", so
that info is lost to later parts in the lifecycle.
Solution:
- Define `VatomState` as "flags", so `kVatomVoid` can "poison"
`vatom.state` without losing its kind flag.
- Unify `lhs`: always report the original user input in `CmdAtom.lhs`,
for all kinds of user actions: visual, "translated"/"stuffed" cmds,
and dot-repeat (".") itself.
- Unreplayable Visual atom emits CmdAtom with non-empty `lhs` and empty
`keys`, like a mapping/macro composite.
Problem:
- Dot-repeat of a Visual selection prepared by a `<Cmd>` mapping,
results in E1255 and leaves Visual mode active.
- Visual-mode capture is implemented as a second, parallel "capture
engine": it re-composes a CmdSpec from cmdarg_T per key and decides
replayability from its own table of "void" key classes.
Solution:
- Model nested `normal_execute()` as a `CmdFrame` stack,
instead of a single module-scoped `stage`.
- Capture `<Cmd>` ":norm …" commands as subatoms from its nested frames.
- Produce every command exactly once; atom_push_raw() routes the atom to
the Visual composite while a selection is open, like it already does
for mapping composites.
- `v/pat<CR>d` is now repeatable and cascades.
Problem:
`<cmd>` mappings do not emit `CmdAtom.text`.
`<cmd>` and Lua-callback mappings that edit the buffer apply only at the
primary cursor, not cascaded (multicursor).
Solution:
Capture the `<cmd>` command in getcmdkeycmd().
Add kKeyOpaque ("no capturable keys"); narrow kKeySynthetic ("not
a keystroke") to K_EVENT/K_IGNORE, so an opaque mapping's edit still
sets `map_edit` and cascades via LHS-replay.
Problem:
`:rundo` on a corrupted undo file crashes or hangs, instead of failing
with E825. Patching one 4-byte field is enough:
ue_size = 0xFFFFFFFF " walks a NULL ue_array
ue_size = 0x7FFFFFF0 " 17 GB xmalloc + memset, then preserve_exit()
ue_top = 0xFFFFFFFB " negative lnum reaches ml_delete()
Analysis:
Every count in the file is read with `undo_read_4c()` and then checked,
differently at each site. None bounds the value by what the file can
hold, so a 2 GB count reaches `xmalloc()`.
Note:
- Vim doesn't have `bi_fsize` because it checks `U_ALLOC_LINE` result
everywhere (thus doesn't crash, but may thrash...); those checks were
dropped when Nvim moved to `xmalloc()`, and the `ue_size` loop counter
became unsigned.
- Vim *does* have the negative line numbers bug: `u_undoredo()` checks
`top > ml_line_count || top >= bot || bot > ml_line_count + 1`, which
rejects none of them.
Solution:
- Introduce `undo_read_len()` and use it to fail early instead of
continuing with nonsense.
- Validate `ue_top`/`ue_bot`/ `ue_lcount`.
- Use `xcalloc()`, so no site can proceed with a NULL array.
- Report a truncated "U" line, distinguish EOF from a 0xFFFFFFFF field,
and free the header on the extmark error path.
Problem:
A named mark updated after a change is moved back (treated as the
original mark) by undo:
:1mark d
:$
dw
:2mark d " 'd is on line 2
:undo " 'd is back on line 1
The undo header snapshots `b_namedm` when the change is recorded, and
`u_undoredo()` restores that snapshot indiscriminately.
Solution:
Update the pending header's snapshot when a mark is set explicitly.
Marks that the change itself moved go through mark_adjust(), not
setmark_pos(), so those are still reverted.
Similar to 2546741d1b (for extmarks): an explicit set inside an undo
block is confused with an edit-driven adjustment. But the extmarks case
is dealing with mid-edit moves, whereas named/regular marks only need
the stale snapshot dropped.
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: 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
Problem: A preselected item is inserted even with "noinsert", and the
first added preselected item wins over the first one shown
in the menu.
Solution: Use K_DOWN when "noinsert" is set; pick the first preselected
item that made it into the menu.
Problem: `q:` in visual mode does not insert `'<,'>` automatically in
the current line on cmdwin.
Solution: Insert `'<,'>` in the current line on cmdwin when `q:` is used
in visual mode.
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`.
Problems:
1. Can't get individual parts of a key-chord separately: modifiers, key.
2. Can't separate a key-combo into individual key-chords.
Solution:
Enhance `vim.keycode()` to optionally return a structured parse result
as a list of key-chords:
- `key_raw` the key-chord (problem 2)
- `mod` the modifiers of `key_raw` (problem 1)
- `key_orig` the key part of the key-chord, only here if differing from `key`
(this doesn't solve any of the above mentioned problems, but it may provide
useful and it's (in terms of code) free)
- `key` a normalized version of `key_orig` (solving problem 1), example(the
first is `key_orig` and second is `key`): `lt` and `<`, `Bar` and `|` (in
`<C-Bar>`)
Problem:
FAILED …/put_spec.lua:894: …/put_spec.lua @ 898: put command . register special tests should ring the bell when deleting if not appropriate
…/put_spec.lua:894: …/put_spec.lua:898: Bell was not rung after action
Snapshot:
screen:expect([[
^ine of words 1 |
Line of words 2 |
{1:~ }|*4
{3:[No Name] [+] }|
|
{1:~ }|*4
{UNKNOWN_HL_ID(4):[No Name] }|
{UNKNOWN_HL_ID(1): }|
]])
stack traceback:
…/ui/screen.lua:917: in function '_wait'
…/ui/screen.lua:540: in function 'expect'
…/editor/put_spec.lua:894: in function 'bell_test'
…/editor/put_spec.lua:940: in function <…/editor/put_spec.lua:932>
Analysis:
The bell was asserted via the UI 'bell' event (screen.bell), but
vim_beep() rate-limits that to 3 per 500ms. These tests share one
long-lived session, so beeps from earlier tests could exhaust the window
and drop the asserted beep.
Solution:
Use assert_beeps()/assert_nobeep(), which check called_vim_beep (set
before the rate-limit gate).
Problem: LSP completion commitCharacters are not handled. Typing a
commit character (e.g. `.`, `(`, `;`) while a completion item is
selected does not accept the item first.
Solution: Store commit characters as a flat string in complete-items.
Check it before completion stops, accept the match and let
the character be inserted normally.
Problem:
Vimscript ftplugins that inherit a base ftplugin often use explicit
`runtime! ftplugin/foo.vim` patterns. This skips corresponding Lua
ftplugins, unlike top-level ftplugin loading.
Solution:
Use the existing `{vim,lua}` patterns for bang runtime imports while
preserving each call's current lookup breadth.
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:
When screen:expect() fails, it renders a snapshot for the error
message. If the grid references a highlight id that was never defined
via "hl_attr_define", the renderer crashes:
screen.lua:1910: attempt to index local 'entry' (a nil value)
This hides the actual failure, and appears "flaky": it only fires on the
failure path, and only when the shared screen is missing an id the grid
still references. A screen created in setup() attaches mid-session, so
highlight ids allocated before it attached (still referenced by stale
grid cells) are never sent to it.
Solution:
- Don't crash while rendering a diagnostic: show undefined highlight ids
as "UNKNOWN_HL_ID(n)", so the real failure and the desync are legible.
- put_spec: fix `visualbell` typo. If it fails again then we can find
the actual root cause.
ref https://github.com/neovim/neovim/issues/36250
Problem: Some code for 'autocompletedelay' is no longer needed now that
'autocompletedelay' doesn't block redraw (after 9.2.0739).
Solution: Remove unnecessary code. Also remove a duplicate screendump
and an outdated comment in test (zeertzjq)
closes: vim/vim#206860b86b97cc9
Problem: 'autocompletedelay' interferes with i_CTRL-K (after 9.2.0739).
Solution: Clear the pending autocompltion from the previous key when a
new key is typed.
closes: vim/vim#206660d292e2067
Problem: With a non-zero 'autocompletedelay', Insert-mode autocommands
(TextChangedI, TextChangedP, CursorMovedI) are delayed, and
while typing faster than the delay they are dropped entirely,
because the delay blocks the main loop.
Solution: Make 'autocompletedelay' non-blocking: instead of busy-waiting
before showing the popup menu, defer it with an input-wait
timeout (K_COMPLETE_DELAY) modeled on CursorHoldI, so typing
stays responsive and the Insert-mode autocommands fire normally.
The delay timer coexists with 'updatetime': the main loop waits for the
sooner of the two and triggers the event whose deadline was reached, so
'autocompletedelay' no longer shadows CursorHold timing. Changing the
completion leader, for example with Backspace, updates the visible popup
immediately like a zero delay; only the first popup is deferred.
Update the 'autocompletedelay' screendumps for the non-blocking display.
One test opened the menu with CTRL-N right after the delay expired and
could race with the deferred popup, so it now waits a little longer than
the delay before sending the key.
fixes: vim/vim#20591closes: vim/vim#205988ce43ea4e3
Also include some insexpand.c and ui.c changes from patch 9.2.0750.
Co-authored-by: Hirohito Higashi <h.east.727@gmail.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Replace newlines in the current cmdline with NULs when opening cmdwin,
and do the reverse when putting a cmdwin line back into the cmdline.
- Escape control characters with Ctrl-V when feeding cmdline.
Problem:
- If cmdwin window is split, ENTER in one does not close the others.
- If cmdwin is put into a different tabpage via <c-w>T, it stops working
(ENTER does not execute the cmd).
Solution:
- Close the buffer instead of the window.
- In the WinClosed handler, skip `M._cleanup()` unless this is the last
cmdwin window.
Problem:
Legacy cmdwin set 'buflisted', but new one doesn't.
https://github.com/neovim/neovim/issues/40431#issuecomment-4811593353
'buflisted' useful for:
- "bufferline" style tablines, they usually show only listed buffers.
- some automatic actions can be conditioned on whether a buffer is
listed; 'buflisted' signals that the buffer is "important enough".
Solution:
Set 'buflisted'.