Skip to content

Commit 87f83f0

Browse files
committed
fix: unesacpe fzf's {q}
1 parent c79d370 commit 87f83f0

File tree

4 files changed

+68
-9
lines changed

4 files changed

+68
-9
lines changed

lua/fzf-lua/core.lua

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -542,11 +542,15 @@ M.build_fzf_cli = function(opts)
542542
table.insert(cli_args, k)
543543
if type(v) == "string" or type(v) == "number" then
544544
v = tostring(v) -- convert number type to string
545-
if libuv.is_escaped(v) then
546-
utils.warn(string.format("`fzf_opts` are automatically shellescaped."
547-
.. " Please remove surrounding quotes from %s=%s", k, v))
545+
if k == "--query" then
546+
table.insert(cli_args, libuv.shellescape(v))
547+
else
548+
if libuv.is_escaped(v) then
549+
utils.warn(string.format("`fzf_opts` are automatically shellescaped."
550+
.. " Please remove surrounding quotes from %s=%s", k, v))
551+
end
552+
table.insert(cli_args, libuv.is_escaped(v) and v or libuv.shellescape(v))
548553
end
549-
table.insert(cli_args, libuv.is_escaped(v) and v or libuv.shellescape(v))
550554
end
551555
end
552556
end
@@ -1009,8 +1013,9 @@ M.setup_fzf_interactive_flags = function(command, fzf_field_expression, opts)
10091013
-- use `true` as $FZF_DEFAULT_COMMAND instead (#510)
10101014
opts.__fzf_init_cmd = utils._if_win("break", "true")
10111015
if opts.exec_empty_query or (opts.query and #opts.query > 0) then
1012-
opts.__fzf_init_cmd = initial_command:gsub(fzf_field_expression,
1013-
libuv.shellescape(opts.query))
1016+
-- gsub doesn't like single % on rhs
1017+
local escaped_q = libuv.shellescape(libuv.escape_q(opts.query)):gsub("%%", "%%%%")
1018+
opts.__fzf_init_cmd = initial_command:gsub(fzf_field_expression, escaped_q)
10141019
end
10151020
opts.fzf_opts["--disabled"] = true
10161021
opts.fzf_opts["--query"] = opts.query

lua/fzf-lua/libuv.lua

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,33 @@ M.shellescape = function(s, win_style)
661661
end
662662
end
663663

664+
-- Windows fzf oddities, fzf's {q} will send escaped blackslahes,
665+
-- but only when the backslash prefixes another character which
666+
-- isn't a backslash
667+
M.unescape_q = function(s)
668+
if not is_windows then return s end
669+
local ret = s:gsub("\\+[^\\]", function(x)
670+
local bslash_num = #x:match([[\+]])
671+
return string.rep([[\]],
672+
bslash_num == 1 and bslash_num or bslash_num / 2) .. x:sub(-1)
673+
end)
674+
return ret
675+
end
676+
677+
-- with live_grep, we use a modified "reload" command as our
678+
-- FZF_DEFAULT_COMMAND and due to the above oddity with fzf
679+
-- doing weird extra escaping with {q}, we use this to simulate
680+
-- {q} being sent via the reload action as the initial command
681+
-- TODO: better solution for these stupid hacks (upstream issues?)
682+
M.escape_q = function(s)
683+
if not is_windows then return s end
684+
local ret = s:gsub("\\+[^\\]", function(x)
685+
local bslash_num = #x:match([[\+]])
686+
return string.rep([[\]], bslash_num * 2) .. x:sub(-1)
687+
end)
688+
return ret
689+
end
690+
664691
---@param opts string
665692
---@param fn_transform string?
666693
---@param fn_preprocess string?

lua/fzf-lua/make_entry.lua

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -337,12 +337,13 @@ M.preprocess = function(opts)
337337
-- If no index was supplied use the last argument
338338
local idx = tonumber(i) and tonumber(i) + 6 or #vim.v.argv
339339
local arg = vim.v.argv[idx]
340+
if debug == "v" or debug == "verbose" then
341+
io.stdout:write(("[DEBUGV]: raw_argv(%d) = %s\n"):format(idx, arg))
342+
end
340343
if utils.__IS_WINDOWS then
341-
-- fzf's {q} will send escaped blackslahes, unescape
342-
arg = arg:gsub([[\\]], [[\]])
344+
arg = libuv.unescape_q(arg)
343345
end
344346
if debug == "v" or debug == "verbose" then
345-
io.stdout:write(("[DEBUGV]: raw_argv(%d) = %s\n"):format(idx, arg))
346347
io.stdout:write(("[DEBUGV]: esc_argv(%d) = %s\n"):format(idx, libuv.shellescape(arg)))
347348
end
348349
return arg

tests/libuv_spec.lua

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,30 @@ describe("Testing libuv module", function()
8989
assert.are.same(libuv.shellescape([[foo\\^^"bar]], 2), [[^"foo\\^^^^\^"bar^"]])
9090
assert.are.same(libuv.shellescape([[foo\\\^^^"]], 2), [[^"foo\\\^^^^^^\^"^"]])
9191
end)
92+
93+
it("escape {q} (win)", function()
94+
assert.are.same(libuv.escape_q([[]]), [[]])
95+
assert.are.same(libuv.escape_q([[\]]), [[\]])
96+
assert.are.same(libuv.escape_q([[\\]]), [[\\]])
97+
assert.are.same(libuv.escape_q([[foo]]), [[foo]])
98+
assert.are.same(libuv.escape_q([[\foo]]), [[\\foo]])
99+
assert.are.same(libuv.escape_q([[\\foo]]), [[\\\\foo]])
100+
assert.are.same(libuv.escape_q([[\\\foo]]), [[\\\\\\foo]])
101+
assert.are.same(libuv.escape_q([[\\\\foo]]), [[\\\\\\\\foo]])
102+
assert.are.same(libuv.escape_q([[foo\]]), [[foo\]])
103+
assert.are.same(libuv.escape_q([[foo\\]]), [[foo\\]])
104+
end)
105+
106+
it("unescape {q} (win)", function()
107+
assert.are.same(libuv.unescape_q([[]]), [[]])
108+
assert.are.same(libuv.unescape_q([[\]]), [[\]])
109+
assert.are.same(libuv.unescape_q([[\\]]), [[\\]])
110+
assert.are.same(libuv.unescape_q([[foo]]), [[foo]])
111+
assert.are.same(libuv.unescape_q([[\foo]]), [[\foo]])
112+
assert.are.same(libuv.unescape_q([[\\foo]]), [[\foo]])
113+
assert.are.same(libuv.unescape_q([[\\\foo]]), [[\foo]])
114+
assert.are.same(libuv.unescape_q([[\\\\foo]]), [[\\foo]])
115+
assert.are.same(libuv.unescape_q([[foo\]]), [[foo\]])
116+
assert.are.same(libuv.unescape_q([[foo\\]]), [[foo\\]])
117+
end)
92118
end)

0 commit comments

Comments
 (0)