Skip to content

Close the parity gaps the upstream suites name - #48

Open
metaphoric-dev wants to merge 240 commits into
mainfrom
feat/upstream-parity-wave
Open

metaphoric-dev wants to merge 240 commits into
mainfrom
feat/upstream-parity-wave

Conversation

@metaphoric-dev

@metaphoric-dev metaphoric-dev commented Oct 28, 2026 •

Copy link
Copy Markdown

Close the parity gaps the upstream suites name

The oxvim binary answers the same --api-info document as the oracle — the
schema is byte-identical and just apidiff reports no differences — but the
behavioural surface was missing large pieces the functional and oldtest suites
point at directly: Lua loader paths, terminal buffers, put and register
semantics, a dozen Ex commands, history, channels, timers, closures and a set
of high-count builtins.

This branch closes those, each commit holding one concern with the probes that
justify it. Highlights, with the failure counts a full functional-gate census
ranked:

  • Lua loader: package.path keeps its relative entries like the oracle,
    which alone recovered the 418 harness-module failures; vim._core.keyparse
    exists, and <Tab>/<NL> encode as their control bytes.
  • Terminal: :terminal sets buftype=terminal, :sleep pumps the job
    reactor, and termopen exists with the oracle's name shape and E475 refusal.
  • Put and marks: :put/:iput honour range, direction, PUT_CURSEND and
    PUT_FIXINDENT; blockwise put pads by display cells; the normal-mode count
    guard answers E1240 instead of allocating.
  • Ex commands: :join, :change, :move, :uniq, :badd, :checktime,
    :tabfirst, :tablast, :helpclose, :cpfile, :lpfile, the
    :bufdo/:cdo/:ldo/:cfdo/:lfdo family, generic :defer,
    ++once/++nested, :checkhealth, and directory views for :edit.
  • Channels: sockconnect, rpcnotify, rpcrequest, chanclose,
    jobclose with upstream error codes and a real msgpack-RPC request path.
  • Timers and closures: the timer family armed on the editor's own loop,
    and a closure body now runs against the caller's live g:, b:, w:,
    t:, v: and s: maps. A stored lambda callback fires as a value.
  • Builtins: win_execute, win_screenpos, screenpos, state, mapset,
    complete_check, diff_filler, win_findbuf, getcmdwintype,
    shiftwidth, haslocaldir, bufloaded, msgpackparse/msgpackdump,
    histadd/histdel/histget/histnr, dictwatcheradd/dictwatcherdel.

Evidence

  • cargo nextest run --workspace: 3769 passed, 1 skipped.
  • cargo fmt --all --check does not pass: it reports 199 diffs across the
    workspace, a state that predates this branch. A workspace-wide rustfmt
    sweep was prepared and then dropped rather than published, because its
    diff could not be proven formatting-only; formatting belongs in its own
    reviewed change.
  • just apidiff: schemas match.
  • One probe now answers identically on both binaries:
call call({-> execute('let g:lam_ok = 1')}, [])
call timer_start(20, {-> execute('let g:timer_ok = 1')})
sleep 120m
" both: lam_call=1 timer=1 checkhealth=2 shiftwidth=8 bufloaded=0 msgpack=[1, 'a']
  • Oldtest, five files, before and after:
File Before After
test_put.vim 25 failed 7 failed
test_cdo.vim 3 failed 1 failed
test_winfixbuf.vim 86 failed 79 failed
test_user_func.vim 23 failed 19 failed

Gates that still fail

This branch does not pass the task's acceptance suites, and the PR does not
claim it does:

  • just functional exits 123. Groups end in Process terminated due to timeout; the largest remaining causes are notification delivery on the
    stdio channel, screen/row mismatches, and harness timing.
  • The full just oldtest has not been run green. The table above is a
    five-file census.

Known gaps left open on purpose, each with a probe on file

  • timer_info drops a stopped timer synchronously where upstream keeps it
    until the close callback runs.
  • Dict-watcher fire hooks are not yet attached to the lvalue and mutation
    sites, so a watcher registers and validates but does not observe.
  • :checkhealth cannot run a real provider re-entrantly and uses a native
    fallback.
  • vim.wait cannot pump terminal jobs while both executor slots are busy, so a
    -l script still misses mid-wait output.
  • :menu, :syntax and :mkspell remain absent; the scope assessments are in
    the campaign notes.

Commit boundary note: most commits hold one concern. Three commit bodies
(aaa779d, 6a0b5e2, 6f62361) enumerate the files where two concerns shared
a file, rather than pretending the split was clean.

metaphorics added 30 commits October 28, 2026 12:50
Commit fc59163 added an nvim_mcursor API function and edited the canonical
metadata blob so nvim_get_api_info advertised it. Upstream at the reference
commit has no such function: only internal multicursor plumbing exists, in
input_cmdatom.c, and the oracle's --api-info is 32701 bytes without it. So
just apidiff failed on the one semantic difference the criterion forbids.

The blob is restored from the oracle's own --api-info output,
api_function_names.rs is regenerated from it, and the handler, its
registration, its tests, and the dead Editor::add_mcursor and
mcursor_count are removed. The registry and metadata tests now pin 262
functions.

The same file carries the scoped Lua dispatch fix: with_scoped_editor_api
installs functions that outlive the scope instead of borrowing it, so Lua
code that captures vim.fn or a vim.api entry at module load keeps a working
binding after the originals are restored.

BREAKING CHANGE: the nvim_mcursor RPC method is gone. It was never upstream
API and no in-tree consumer called it.
The collection parser treated every `[` as an opener and raised E54 when no
`]` followed, while upstream decides with a pre-scan and otherwise falls
through to a literal `[`. A pattern such as `foo[` now compiles, and the
strict entry point still reports E769 for vim.regex, which is the only
caller that turns a lone bracket into an error.

The parser gains a close scan that mirrors skip_anyof: a leading `^`, `]`, or
`-` is a member, a `\` escape consumes two bytes, a POSIX class is one unit,
and a nested `[` after `\_.` no longer swallows the opener. Reversed ranges
report E944 through CompileError::code(), and the display no longer embeds a
code, so callers can render `E54: Unmatched \(` without a doubled prefix.

Covered by eleven behaviour tests that pin the oracle matrix, plus the
strict-mode and range-error cases.
The modifier loop now re-skips space, tab, and `:` at the top of every
iteration, as parse_command_modifiers does, so `:unsilent :norm! n` and
`:silent :silent :echo 1` parse as modifiers over a command instead of one
unknown name.

`:put =expr` takes its expression through the register path, so the six
shapes the oldtests exercise agree with the oracle and one dead parse helper
is gone. `:scriptnames` lists sourced scripts as `%3d: name` with home
replacement, edits a script given a count or a range, and reports upstream's
invalid-argument error; the startup noise it used to cause is gone because
upstream rplugin.vim calls it on every start.

A substitute replacement expression now holds the text lock, so a nested
`:s` inside `\=` raises E565 instead of recursing without bound, and the
first non-normal flow from `:tabdo` stops the loop while restoring the
original tabpage, with the check against the tab table happening immediately
before each switch.

Command dispatch reports a resolved but unimplemented builtin command as
E492 rather than E117, and the regex call sites use CompileError::code()
instead of a fixed E54 prefix.
…adly

A function defined in one script and called while another script is current
cloned a stale registry copy of the caller's `s:` map over the live one, so
`runtest.vim`'s `s:has_ffi` read as undefined from inside test functions.
Switching scripts now flushes the outgoing live map into its SID first and
the call frame reloads the caller from that SID on return, which removes the
whole class (41 oldtest failures across cmdline, search, and ins_complete).

`g:Name(...)` and `function('g:Name')` resolve a variable-held Funcref the
way upstream does, including E1085 with the written spelling when the
variable exists but is not callable, and `s:`/`<SID>` names translate to the
`<SNR>` form when stored.

screenchar, screenstring, screenchars, and screenattr answer the whole
lines by columns grid with blank cells, so `nr2char(screenchar(row, col))`
no longer produces E5070 outside the buffer text; -1 stays for out-of-grid
coordinates, matching the oracle.
getcmdline, getcmdpos, setcmdpos, getcmdtype, getcmdprompt, pumvisible, and
wildmenumode now dispatch from editor state instead of reporting E117. The
number coercion follows tv_get_number: floats truncate, strings take their
numeric prefix, and a Dictionary is E728.

setcmdpos stores a one-based slot that the next CTRL-R paste consumes and
clears, matching upstream's static new_cmdpos: the slot is invisible to
getcmdpos, resets with the command-line session, and paste writes into the
command-line text rather than the buffer. CTRL-R CTRL-R shares that path.

screenpos, win_screenpos, setcmdline, setcmdtype, and the completion-type
queries are not part of this change.
Fold ranges, foldmethod evaluation, and the open and close commands now come
from one model, so `:fold` reports E350 only for methods that need an
expression the port does not evaluate, and a closed fold answers foldclosed
and foldclosedend consistently.

diffget and diffput work on the hunk table instead of reconstructing it per
call, which fixes the added and deleted line cases and the fold refresh after
a put.
`:abbreviate`, `:abclear`, and the insert-mode, command-line, and
both-mode forms register on the same engine as mappings, with the
`noabbrev` and `iabbrev` forms and the `maparg`, `mapcheck`, and `maplist`
reporting the oldtests read.

Resolution rules match the mapping side: a full match wins over a shorter
one, and the expansion keeps the engine's one lookup per keystroke.
Key-notation escape and unescape now carry a UTF-8 continuation byte and a
widened KS_MODIFIER third byte, so a register holding a modified key or a
non-ASCII character round-trips instead of failing with E523 "invalid
special-key third byte". Recording unescapes at the stop and playback escapes
again, which is where upstream puts the conversion.

Register writes take their type from str_to_reg, so an append keeps the
existing type only when upstream does, and the dict-clear and slash-clear
paths go through the model rather than editing text directly.
The initial highlight table now carries the 140 command-line groups, the `@`
parents, the HLF interns, and the startup aliases the oracle reports, so
`:highlight` answers over the same 388-visible, 11-hidden set instead of
missing names the suite queries.

Neighbouring editor surfaces carry the wave's small corrections: buffer
variable ownership, insert-mode undo boundaries, and the autocmd entry
liveness check used by the plan executor.
The API and UI crates follow the changed signatures: buffer attach
subscriptions resolve through the editor's table, window text queries read
the same option snapshot as the compositor, and the tabpage and UI entry
points keep the existing validation order.

The Lua bridge refactor groups attach subscriptions by buffer and channel so
the callback table is looked up once per event, and vim.regex now compiles
strictly, which is what reports E769 for a lone bracket.

ox-tui, ox-sys, ox-text, ox-uv, ox-rpc codec, and the runtime locator carry
only the adjustments those signature changes require.
The upstream Makefile wraps every oldtest file in runnvim.sh, which starts
the test process with jobstart(..., {'term': v:true}) and reads the result
back as terminal-buffer lines. Oxvim does not paint a terminal buffer yet, so
that path reported an empty screen and exited non-zero before a single test
ran, and just oldtest produced no messages file at all.

tests/oldtest/run.sh drives runtest.vim directly: one scratch copy of
testdir, one HOME, one XDG tree, and one TMPDIR per file, eight files at a
time, with the same sandbox the recipe used to build by hand. It writes an
aggregated messages file and reports executed, failed, skipped, clean,
blocked, and timed-out counts. A full sweep now takes about six minutes.

The PTY differential test fed the raw byte stream to a substring check that
only matched if the client skipped blank cells, which it does not by
construction, so it failed against every build. It now parses the stream with
vt100 and asserts on the rendered screen, and the subject path honours
OXVIM_BIN, so a build in a non-default CARGO_TARGET_DIR is the one tested.

The Windows job checks the whole workspace for the host MSVC target and runs
the platform startup tests, which is what proves the CRLF and packpath fixes
on a real Windows runner.
The page claimed Windows was untested and left both upstream suites
unmeasured. It now records the 2026-10-28 counts: 1830 of 4076 executed
functional tests pass with 58 skipped and one error, and 2323 of 3593
executed oldtests fail with 40 files blocked before their first test and six
timed out.

The oldtest row also explains why the gate drives runtest.vim directly, and
the gap list gains the two defects this run measured: terminal output is not
parsed into buffer cells, and a Lua to Vimscript builtin call costs about
half a millisecond, which makes filetype detection on one file name take
seconds.
…port

The report states the gate results before and after this work, names the four
root causes that blocked the deterministic gates, lists the defect clusters
the waves closed with their measured effect, and lists what stays open with
the two defects the run measured: the Lua to Vimscript call cost that makes
filetype detection slow, and the terminal output that is not parsed into
buffer cells.

The ox-excmd test module now imports split_vimgrep_args from its own crate,
which the workspace test build needed after the function moved.
Three probes in this run produced empty or misleading results before they were
caught: a TEST_FILTER with `|` instead of Vim's `\|` selects no tests, a census
binary copied outside target/release cannot resolve the runtime and reports
every file blocked, and SIGTERM does not stop a hung oxvim on the shared PTY.
Each cost a detour, so the report names them.
Every Lua-to-Vimscript builtin call re-synced the editor into the scope.
That sync rebuilt two tables unconditionally: the mirrored `&opt` values for
every option under its canonical name, short name, and aliases, and the
mirrored `@r` registers for every register name. Measured on this host, one
`vim.fn.abs()` call cost 620 microseconds, of which 597 were that read sync;
upstream costs a few microseconds.

The option store and the register bank now carry a monotonic version bumped by
every mutation, and the sync rebuilds each mirror only when its stamp moved,
the same gate the variable maps already use. The same loop now runs at three
microseconds per call, and `cargo nextest run --workspace` dropped from 29
seconds to 17.

Option and register tests that observe the mirror after a write still pass,
which is what proves the stamps advance on every mutator.
Formatting only: the campaign's edits left several modules with the line
breaks the workspace profile rejects, so `cargo fmt` was run over the crates
that changed. No behaviour changes.
The compat page blamed the Lua to Vimscript bridge for the slow `:edit` path.
That bridge is fixed: the option and register mirrors are version-gated and a
call costs three microseconds instead of 620. The remaining loop is a
different one, measured at about 1.4 million `fnamemodify` calls in fifteen
seconds with `vim.filetype.match` never entered, so both documents now say
what is fixed and what is still open.
`:split` with no argument keeps the current buffer and inherits the current
window's local options, which is what win_init_some does. `:new`, `:vnew`,
and `:split {file}` initialise the window with win_init instead, so every
window-local option starts at its global value. The port copied in all four
cases, so a `foldnestmax` or `winhl` set in one window leaked into the next
window the command created.

The split path now drops the copied overlay whenever the window shows a
different buffer from the one the command started in. Measured against the
oracle: `:new` and `:vnew` answer 20 for a `foldnestmax` set to -1 in the
source window, `:split` answers -1, and `:split {file}` answers 20.

OptionStore gains a version-bumping reset_window for that drop, and the
editor exposes options_mut for the command layer.
`fnamemodify('foo', ':h')` returned an empty string where upstream's
`path_head` answers ".". `vim.fs.parents` walks `:h` until the path stops
changing, so the empty answer made it loop forever: every `:edit` in headless
mode ran a buffer-lifecycle autocmd that walked the path for minutes, and one
oldtest file timed out before running a single test.

Measured after the fix: `:edit foo` takes 0.05 seconds instead of more than
three minutes, `fnamemodify('foo', ':h')` answers ".", and the oldtest sweep
executes 61 more tests because test_substitute.vim now finishes. Its failures
rose by 52 because those tests now run instead of being cut off.
The `:edit` hang is gone, so the compat page and the campaign report now name
the five files that still exceed the runner's cap instead, and the oldtest row
carries the fresh counts: 3654 executed with 2368 failing.
`call({func}, {arglist} [, {dict}])` now dispatches: a Funcref or Partial goes
through the evaluator's invocation path, a function name goes through the
host, and a third argument turns the call into `function({func}, {dict})` so
the callee reads `self` from its frame. The oracle agrees on the plain form,
the Funcref form, a lambda, E117 for an unknown name, E119 for missing
arguments, and E731 for a non-dictionary third argument.

The same change fixes a pre-existing defect the new tests exposed: a Partial
created by `function('Name', dict)` carried its dictionary but the invocation
path dropped it, so `self` was undefined inside the callee. The evaluator now
hands the dictionary to the host, which passes it to the user-function frame
that already knew how to bind it. `function('Get', d)()`,
`call(function('Get', d), [])`, and `call('Get', [], d)` all answer the oracle.

The oldtest sweep still executes 3652 tests and fails 2368, but the four
`not implemented: call` sites are gone.
`fs_builtins::call` has served `getfperm` for a long time, but the predicate
that routes filesystem builtins never listed it, so the name fell through to
the pure evaluator and answered `E117: not implemented: getfperm` at seven
oldtest sites while a working implementation sat unreachable.

The predicate now lists it, and a test pins the agreement: every name the
dispatcher serves must be claimed by the routing predicate. Verified against
the oracle, `getfperm('f.txt')` on a 0640 file answers `rw-r-----` from both.
The workspace suite covers 3646 tests, all passing, and the oldtest sweep now
executes 3652 tests with 2365 failing after `call()` and the `getfperm`
routing fix landed.
`uniq(list, cmp [, dict])` ignored its comparator, so every call behaved like
the two-argument form. It now walks the list comparing the last kept item
with the next original one, calls the comparator through the evaluator, and
binds a third argument as `self`. The comparator's result decides removal:
an item is dropped when the comparator answers false.

That rule is measured, not assumed: `uniq([1,2,3], {a,b -> a < b})` keeps
every item while `{a,b -> a > b}` keeps only the first, and the six list
cases in the new probe set match the oracle item for item. A non-list
argument reports E686 with upstream's text.

The list is snapshotted before the walk and a comparator that changes it
reports E741, so the removal indices cannot address a shifted list.
The oldtest sweep executes 3654 tests with 2365 failing, and the report now
records that `call()` and the `uniq()` comparator are implemented while the
missing Ex commands remain open.
Seven tests run through a real executor and evaluate Vimscript, so they
exercise the route table, the dispatch arm, and the evaluator together:
call() by name, by Funcref, and by lambda; a dictionary argument binding
`self` through both call() and function()(); the E117, E119, and E731
argument errors; uniq() with and without a comparator, including the
dictionary-method comparator form; and the E686 non-list error.

The comparator tests were checked against a mutation: flipping the removal
predicate back to `number != 0` fails two of them, so they hold the measured
behaviour rather than the shape of the code.
Six read-only passes over the functional and oldtest logs produced the ranked
causes, their owners, and two corrections: eight functional groups ended in a
timeout so their counts are a floor, and the terminal surface already holds a
VT emulator, a pty pump, and row projection, which means the oldtest Makefile
path deserves a retry before new terminal work is scheduled.
The emulator, the pty pump, and the row projection all exist, but
`:terminal echo hello` still leaves the buffer blank in headless mode, so the
remaining terminal work is tracing that path rather than writing a new
emulator. The report now says so instead of guessing from the inventory.
`configure_package_path` stripped every relative entry from `package.path`
and `package.cpath`, on the premise that upstream never keeps a `.` entry.
The oracle says otherwise: its `package.path` starts with `./?.lua` and its
`cpath` keeps `./?.so`. The strip therefore diverged from the compatibility
target, and it cost 418 functional failures: the harness starts its subject
inside a tree that exposes `test/` as a relative symlink, and the fixture
modules are required through that entry.

The function now prepends the runtime paths to the loader defaults and leaves
`package.cpath` untouched, which is what upstream does. Verified from a
launch tree that mirrors the harness: `package.path` lists
`runtime/lua/?.lua;runtime/lua/?/init.lua;./?.lua;...` and
`require('test.functional.testnvim.exec_lua')` answers true, where it used to
report the module missing with only the luajit install paths listed.

The path test is re-pinned to the oracle's shape, and the test that asserted a
planted launch-directory module stays unloadable is deleted: it pinned the
divergence, and upstream loads such a module.
The builtin was missing, so `test_scriptnames.vim` stopped on E117 and nothing
could ask which scripts a session has loaded. It now answers every form
`f_getscriptinfo` answers (`runtime.c:2670-2740`): no argument lists each
sourced script as `{name, sid, version, autoload}`, a `name` pattern filters
that list, and a `sid` query returns one entry with the script's `variables`
and `functions`.

The script id, the variable map and the function list read the live registry:
`s:` variables come from the script context, and a function counts for a script
when it was defined while that script was current, so a global defined there is
listed too, as upstream does.

Probe against the reference binary for one sourced file with `s:Fn` and
`s:var`: `count`, `sid`, `variables`, `functions` and the total agree; only the
dictionary key order differs, which is the known iteration-order gap.
`test_scriptnames.vim` now reaches its assertions instead of E117. Suite: 3941
passed.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

metaphorics added 3 commits October 31, 2026 01:18
The line rewriter honoured single quotes but not double quotes, so
`let x = "<SNR>9_a"` became `"<SNR>1_9_a"`. The reference rewrites a script
prefix only when it translates a function name (`trans_function_name`,
`eval/userfunc.c:1967`), so a marker inside any string literal stays as typed.
Three rules of `do_source_ext` were missing, and each one hid the next.

`do_cmdline` breaks its loop on an error only for a line from the command line
(`ex_docmd.c:745-753`): `getexline` is the sole getter that stops it. A sourced
file therefore runs its remaining lines, while this port abandoned the file at
the first failing command — `source` of a file with one bad line never set the
variables declared after it.

`do_source_ext` calls `save_funccal` (`runtime.c:2337`), so the file body is not
the calling function's body. A bare name in a sourced file is global
(`eval/vars.c:2505-2512`), even when a function asked for the file; the caller's
frames are hidden by `source_bar` instead of by clearing the local map, which
would drop explicit `l:` writes at script level.

Measured against the reference: a file with `bogus` then `let g:after = 1` now
sets `g:after`, and a file sourced from a function sets the global it declares.
Suite: 3941 passed.
The builtin answered every bad `sid` with E1249. It now converts through the
shared coercions, so a List reports E745 and a Dictionary E728
(`typval.c:4178-4179`), a non-positive id reports E475, and a List `name`
reports E730 with E1206 for a non-Dictionary argument.

`test_scriptnames.vim` keeps one failure: an invalid `name` pattern reports
E54 here and E866 there, which is the regex engine's error text.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

After `:bcd` the reference reports the file's tail, so `bufname('%')` reads
`Xtest-cd-other` while this port kept the full path. `shorten_buf_fname`
(`fileio.c:2434-2463`) rewrites the stored name when the directory changes; this
port keeps the full name and trims on read, which answers the same string.

`cd_spec` T31 gets past its name assertion and now stops at the statusline
expectation: `'statusline'` with a `%!v:lua.*` item does not evaluate, so the
window shows an empty line where the reference shows `STL`. Suite: 3941 passed.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

metaphorics added 3 commits October 31, 2026 02:56
The Lua prelude builds `vim.str_utfindex` and `vim.str_byteindex` on host
functions that did not exist, and calls `vim.str_utf_pos`, `str_utf_start` and
`str_utf_end` directly, so any path touching them failed with "attempt to call a
nil value" (`vim/fs.lua:245`).

The three walkers follow `mb_utflen`, `mb_utf_index_to_bytes` and
`utf_ptr2len_len` (`mbyte.c`): a sequence cut by the byte limit still counts as
one character and keeps its full codepoint width, an embedded NUL counts as one
byte in the counting walks, and only `str_utf_pos` stops at NUL.

`vim_spec` T14 now passes; the probe for both binaries agrees on every case.
Suite: 3944 passed.
`ShaDa::read` called `read_to_end` before any limit applied, so the reader held
the entire history in memory. The reference never does: `shada_read_next_item`
reads type, timestamp and length from the descriptor and skips oversized payloads
with `sd_reader_skip` (`shada.c:3016-3087`, `3101-3107`, `3170-3187`).

The port now reads the same framing directly, skips a payload over `'shada'`'s
per-item limit through a fixed buffer, and keeps `max_kbyte == 0` unrestricted
(`shada.c:1342-1347`). It accepts every file the reference accepts; it just no
longer buffers one.

Two tests: an oversized payload is skipped without buffering the file, and a
normal history round-trips through `write` then `read`.
`jump_to` used `std::fs::read(filename).unwrap_or_default()`, so an unreadable
include opened an empty buffer instead of failing, steering clear of the
`IdentSearchError` channel the rest of the function uses.

A failed read now returns E484 with the file name, matching the reference, where
`find_pattern_in_path` breaks out when `getfile` fails
(`search.c:3425-3428`; the message is `errors.h:77-78`). The regression test
asserts the error and that no buffer is created.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

metaphorics added 2 commits October 31, 2026 03:09
`transfer` moved the pending map out of the mutex, then forwarded each deferred
event with `events.put(owner, event)?`. `schedule_deferred` accepts any owner,
while the destination `MultiQueue` only knows the owners it was given, so an
unknown owner is reachable: the error returned early and every event still in the
local map was dropped.

The transfer now restores the current event and the whole tail, ahead of anything
scheduled meanwhile, before returning the error. `MultiQueue::put` also validates
its whole parent chain before it mutates, so its documented `UnknownOwner` failure
leaves no partial state for any caller.

Two tests: a transfer into a queue missing the owner delivers both events on the
next pass, and `put` with a missing ancestor changes neither the event map nor the
queue.
Decoding each character from the whole remaining slice made a malformed *later*
byte widen or narrow the current one: for `F0 9F A4 A6 FF` at index 1 the port
answered one UTF-16 unit where the reference answers two, and `_str_byteindex`
returned 5 where the reference returns 4.

The walk keeps the limit-bounded step (`utf_ptr2len_len`, `mbyte.c:852-872`) and
decodes only the current sequence. The index conversion moved into one helper
whose single float-to-integer narrowing carries its invariant, replacing the `as`
casts the lint gate rejects.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

metaphorics added 2 commits October 31, 2026 03:17
Two divergences survived the first pass, both visible only on bytes a valid
UTF-8 string never holds.

`utf8len_tab` (`mbyte.c:99-117`) gives 0xC0 and 0xC1 a length of two and 0xF5 to
0xFD lengths of four to six, so `utf_ptr2len_len` walks those leads as one
character; the port called each of them a single byte and split the sequence.
`utf_ptr2char` (`mbyte.c:642-700`) also assembles the value from the bit layout
and validates only trail bytes, so a lead like 0xF5 yields an out-of-range scalar
that still counts as two UTF-16 units. Six malformed inputs now agree with the
reference, including `F5 80 80 80` at `(1, 2)`.

The runtime wrapper padded a missing index with `math.huge`; upstream uses
`vim._maxint` (`_core/editor.lua:908`), which is why the host had to reason in
floats. The copy matches upstream again.
The test stops at the unknown-owner preflight, before `put` runs, so its old
name claimed a path it never reached.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

The compositor built the window statusline itself as `{name}{modified}`, so
`:set statusline=` changed nothing: `cd_spec` T31 set `'statusline'` to
`%!v:lua.stl()` and expected `STL` on the row.

The server now renders it where the evaluator lives and hands finished strings to
the compositor, which stays a pure function of prepared state: it never evaluates
an expression. Rendering reuses the existing `nvim_eval_statusline` grammar, so
`%` items, `%{...}` and `%!...` mean one thing on screen and in the API; the
evaluator gained the `%h`/`%w` items it lacked. A window whose expression raises
is omitted from the map and keeps the default text, so a bad `%!` cannot abort a
redraw.

`cd_spec` 36 of 36. `tabline_spec` measures 1 of 8 with and without this change,
so the remaining screen-rendering gap there is unchanged. Suite: 3947 passed.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

A single window never drew a statusline row: the layout reserved one only when
the tabpage held more than one window, and nothing read the option. Upstream
reserves the row at `'laststatus'` 2, its default (`window.c:1164-1171`,
`drawscreen.c:2759-2762`), so one window now keeps its row while 0 and 1 behave
as before.

The renderer also evaluated `%!` items on the redraw path with no guard. A
statusline expression may run `:redrawstatus`, so upstream keeps a `busy` flag
and ignores the nested call (`statusline.c:81-92`); the session now carries the
same guard, released on unwind, and a nested render keeps the text it already
had.

Two geometry expectations follow from the extra row: the PTY text height at
120x40 becomes 38, and the scrolled-window helper grows by one row so its
scenarios keep the text height they describe. Probe against the reference:
`&lines` 24, `winheight(0)` 23 at `laststatus` 0 and 1, 22 at 2, split 11.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

metaphorics added 5 commits October 31, 2026 09:54
Dropping a UvLoop without pumping it left queued completions holding an
Arc to the queue they sat in, so the queue, the closures, and every value
they captured leaked. close() now drains the pending deque under the same
mutex post serialises on, and a post racing the close either lands before
the drain or is rejected, so nothing is stranded.

post() also enqueued before waking the loop; a failed wake left the
completion queued while Pool::submit reported failure and ended the
request, and the later dispatch ended it again. Request termination now
lives in a RequestGuard captured by the completion, so end() runs exactly
once whether the completion runs, panics, is drained, or is rolled back,
and a failed wake pops the entry back off before returning.
vim.mpack.encode rejected NaN and the infinities where upstream packs
them as float32 or float64 (conv.c mpack_pack_number: infinities fit
single, NaN does not), so RPC payloads carrying them failed at encode
time instead of on the wire.

vim.mpack.Session required an options table with an unpacker. Upstream
lmpack_session_new accepts no argument at all and attaches an unpacker
only when a table is given, and receive then returns three values from
the raw RPC header instead of five. The constructor now follows that
contract, and both receive modes enforce the same header token widths as
mpack_rpc_receive_tok (type at most one byte, id at most four), where the
unpacker path previously accepted wider tokens.
The port drew a statusline in every window at 'laststatus' 3. Upstream
reserves a single screen-width row at the bottom and replaces every
non-bottom window's statusline with a one-row horizontal separator
(window.c global_stl_height, last_status_rec), so layout, compositor,
and emitter now share that model: text heights subtract the separator
and the global row, the compositor paints the separator rows and the
global statusline from the server-rendered text, and ext_multigrid
clients see the statusline on grid 1 with no synthetic message grid
where the old code emitted a blank grid that overlaid content.

The global text is evaluated at screen width, not window width
(statusline.c), and the render cache keys on width so a resize
reevaluates. Window grid rows also account for a visible tabline again,
which the direct layout-height path had dropped.
winheight() returned the frame height including the statusline and
message rows, and nvim_win_get_height, nvim_win_set_height,
nvim_win_resize, and :resize worked in frame rows too, so a window
reported 24 rows where the reference reported 22 and a saved height
restored through :resize shrank the window each time. All of these now
use text rows (eval/window.c w_view_height, api/window.c w_height,
win_setheight_win), converted through the existing layout owner, with
floating windows keeping the requested height and clamping to the same
minimum the reference applies (window.c win_setheight_win).
nvim_get_context serialized a blockwise register as a bare Ctrl-V type,
and load re-derived its width from the longest row's byte length, so a
register cut wider than its content (setreg 'b7' with short rows) came
back narrower and later pastes padded to the wrong width. The type now
carries the public width the reference stores in ShaDa (shada.c rw),
explicit widths survive byte-for-byte, and bare Ctrl-V keeps its
previous inference, widened by display cells rather than bytes.

Malformed types are rejected instead of silently downgraded to
characterwise, widths are bounded before they can reach paste
allocation, and invalid UTF-8 rows count four cells per bad byte as the
reference's <xx> translation does, with the raw bytes preserved.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

encode() wrote every finite number as a float64 token. The reference
packs integral doubles inside the int64/uint64 range as integer tokens,
-0.0 as integer zero, and other finite doubles as float32 when the value
survives the narrowing (conv.c mpack_pack_number with mpack_fits_single),
so wire bytes now match: 1.5 packs ca 3f c0 00 00, 1.0 packs 01, and the
signed/unsigned boundaries pack d3/cf tokens as the reference does.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant