261013.2 · ce788d5acb83628c66f6639920ec0f36576646d7
refactor(shell): swap action field index order "{q} {n}" -> "{n} {q}"
Reorder the field indexes appended by `shell.stringify_data2` to reload and exec-silent actions so the item index `{n}` precedes the query `{q}` in fzf's placeholder expansion (previously "{q} {n}").
Motivation ---------- `stringify_data2` appends both `{q}` and `{n}` to an action's `field_index` so the receiving callback can (a) record the live query for `resume` even when the process never terminates (e.g. profile "hide", where `--print-query` is never emitted) and (b) use `{n}` to disambiguate an intentionally-selected empty string from a zero-match/zero-selection state. The relative order of these two trailing placeholders is an internal contract; this change standardizes on "{n} {q}" everywhere for consistency and to keep the item count / selection index ahead of the free-form query text.
What changed ------------ The swap is applied at every site that builds or parses the field-index placeholder list, so the on-the-wire format and the Lua-side extraction stay in lockstep:
* shell.lua (`M.stringify_data2`):
- suffix check/append: "{q} {n}$" -> "{n} {q}$", append " {n} {q}"
- extraction: `items[#items-1], items[#items]` now read as
`idx, query` (was `query, idx`)
- zero-match guard: `field_index == "{+} {q} {n}"` ->
`"{+} {n} {q}"` (matches the post-append value
of the default `"{+}"` field_index)
- updated the "{q} {+}" / "{n} {+}" comments to reflect the new order
* previewer/builtin.lua (`base:cmdline`): field_index "{} {q} {n}" ->
"{} {n} {q}" and the positional `unpack(items, 1, 3)` reordered to
`entry, idx, query` so `idx` still drives the empty-entry nil-coercion
and `query` still feeds `get_info().query`.
* previewer/swiper.lua (`base:preview_cmd`): field_index "{} {q} {n}" ->
"{} {n} {q}" and `unpack(items, 1, 3)` reordered to `entry, idx, _`
(query is unused here, so it moves to the discarded slot).
Behavior / compatibility ------------------------
* No user-facing behavioral change: the `{n}`/`{q}` expansion is consumed
only internally before being stripped from the items table.
* The zero-match/zero-selected heuristic (`not tonumber(idx)` and the
empty-selection count check) is order-agnostic and continues to work;
the guard string simply had to match the new appended suffix.
* Edge cases preserved: older fzf versions that omit `{n}` on no-match
(#1833) and Windows expanding `{n}` to `''` (#1836) shift item *count*,
not order, so they are unaffected by the reordering.
Tests ----- Added `tests/shell_spec.lua` coverage that drives the registered `stringify_data2` callback synchronously (stubbing `uv.pipe_connect` and `vim.schedule`) and asserts:
1. the emitted command appends "{n} {q}" (not "{q} {n}"), the trailing
pair is stripped, and the resume query is taken from the last line;
2. the zero-match/zero-selected `""` -> `nil` patch still fires under
the new ordering.
Verified: shell/actions/files/win specs pass; the only failures in the full suite are pre-existing flaky screenshot comparisons (e.g. files `line_query`) that fail identically on the base commit.
reading declarations
Changes to tests/shell_spec.lua. All files
@@ -6,7 +6,66 @@ local err = function(f, ...) assert(not _ok, _err) end +-- Drives the function registered by `stringify_data2` (the RPC receiving+-- function) synchronously, as if fzf sent `lines` via the action pipe:+-- stubs `uv.pipe_connect` to succeed immediately and `vim.schedule` to+-- run inline. `cmd` is the stringified shell command, from which the+-- registered function id is parsed as the argument following `rpc.lua`.+-- NOTE: the rpc.lua path is shellescaped differently per platform+-- ('...' on Unix, "^"...^"" or "..." on Windows), so we match any+-- non-digit run after `rpc.lua` up to the id rather than a hard quote.+local function simulate_fzf_output(cmd, lines)+ local id = tonumber(assert(cmd:match("rpc%.lua%D-(%d+)")))+ local uv = vim.uv+ local orig_pipe_connect, orig_schedule = uv.pipe_connect, vim.schedule+ ---@diagnostic disable: duplicate-set-field+ ---@param cb? fun(err: string?)+ uv.pipe_connect = function(_, _, cb) if cb then cb(nil) end end+ vim.schedule = function(f) f() end+ ---@diagnostic enable: duplicate-set-field+ local ok_, err_ = pcall(shell.get_func(id), {+ pipe_path = "",+ selection = lines,+ env = {},+ })+ uv.pipe_connect, vim.schedule = orig_pipe_connect, orig_schedule+ assert(ok_, err_)+end+ describe("Testing shell module", function()+ it("stringify_data2 appends '{n} {q}' and extracts idx, query in that order", function()+ local opts = { __resume_key = "shell_spec" }+ ---@type string[]+ local captured+ local cmd = shell.stringify_data2(function(items, _)+ captured = vim.deepcopy(items)+ return ""+ end, opts, "{+}")+ -- "{n} {q}" (not "{q} {n}") is appended to the field index+ assert(cmd:match("{%+} {n} {q}"), cmd)+ -- simulate fzf output: last two lines are "{n}" (idx) then "{q}" (query)+ simulate_fzf_output(cmd, { "entry", "3", "query" })+ -- trailing "{n} {q}" lines are stripped from the items+ assert(#captured == 1 and captured[1] == "entry")+ -- resume query is set from the last line ("{q}")+ assert(FzfLua.config.resume_get("query", opts) == "query")+ assert(opts.last_query == "query")+ end)++ it("stringify_data2 '{n} {q}' order keeps zero-match/zero-selected detection", function()+ local opts = { __resume_key = "shell_spec" }+ ---@type string[]+ local captured+ local cmd = shell.stringify_data2(function(items, _)+ captured = vim.deepcopy(items)+ return ""+ end, opts, "{+}")+ -- no match + no selection: both {n} and {+} expand to "", items+ -- arriving as { "", "", "<query>" } must be converted to {}+ simulate_fzf_output(cmd, { "", "", "query" })+ assert(#captured == 0)+ end)+ it("check_upvalue", function() ok(shell.check_upvalue, function() end, "no upvalue")