diff --git a/lua/fzf-lua/core.lua b/lua/fzf-lua/core.lua index b8a8535..07bccf7 100644 --- a/lua/fzf-lua/core.lua +++ b/lua/fzf-lua/core.lua @@ -1,4 +1,5 @@ -local fzf = require "fzf" +-- local fzf = require "fzf" +local fzf = require "fzf-lua.fzf" local path = require "fzf-lua.path" local utils = require "fzf-lua.utils" local config = require "fzf-lua.config" diff --git a/lua/fzf-lua/fzf.lua b/lua/fzf-lua/fzf.lua new file mode 100644 index 0000000..716f262 --- /dev/null +++ b/lua/fzf-lua/fzf.lua @@ -0,0 +1,139 @@ +-- slimmed down version of nvim-fzf's 'raw_fzf', changes include: +-- DOES NOT SUPPORT WINDOWS +-- does not close the pipe before all writes are complete +-- option to not add '\n' on content function callbacks +-- https://github.com/vijaymarupudi/nvim-fzf/blob/master/lua/fzf.lua +local uv = vim.loop + +local M = {} + +local function get_lines_from_file(file) + local t = {} + for v in file:lines() do + table.insert(t, v) + end + return t +end + +-- contents can be either a table with tostring()able items, or a function that +-- can be called repeatedly for values. the latter can use coroutines for async +-- behavior. +function M.raw_fzf(contents, fzf_cli_args, opts) + if not coroutine.running() then + error("please run function in a coroutine") + end + + if not opts then opts = {} end + local cwd = opts.fzf_cwd or opts.cwd + local cmd = opts.fzf_binary or opts.fzf_bin or 'fzf' + local fifotmpname = vim.fn.tempname() + local outputtmpname = vim.fn.tempname() + + if fzf_cli_args then cmd = cmd .. " " .. fzf_cli_args end + if opts.fzf_cli_args then cmd = cmd .. " " .. opts.fzf_cli_args end + + if contents then + if type(contents) == "string" and #contents>0 then + cmd = ("%s | %s"):format(contents, cmd) + else + cmd = ("%s < %s"):format(cmd, vim.fn.shellescape(fifotmpname)) + end + end + + cmd = ("%s > %s"):format(cmd, vim.fn.shellescape(outputtmpname)) + + local fd, output_pipe = nil, nil + local finish_called = false + local write_cb_count = 0 + + -- Create the output pipe + vim.fn.system(("mkfifo %s"):format(vim.fn.shellescape(fifotmpname))) + + local function finish(_) + -- mark finish if once called + finish_called = true + -- close pipe if there are no outstanding writes + if output_pipe and write_cb_count == 0 then + output_pipe:close() + output_pipe = nil + end + end + + local function write_cb(data, cb) + if not output_pipe then return end + write_cb_count = write_cb_count + 1 + output_pipe:write(data, function(err) + -- decrement write call count + write_cb_count = write_cb_count - 1 + -- this will call the user's cb + if cb then cb(err) end + if err then + -- can fail with premature process kill + finish(2) + elseif finish_called and write_cb_count == 0 then + -- 'termopen.on_exit' already called and did not close the + -- pipe due to write_cb_count>0, since this is the last call + -- we can close the fzf pipe + finish(3) + end + end) + end + + local co = coroutine.running() + vim.fn.termopen(cmd, { + cwd = cwd, + on_exit = function(_, rc, _) + local f = io.open(outputtmpname) + local output = get_lines_from_file(f) + f:close() + finish(1) + vim.fn.delete(fifotmpname) + vim.fn.delete(outputtmpname) + if #output == 0 then output = nil end + coroutine.resume(co, output, rc) + end + }) + vim.cmd[[set ft=fzf]] + vim.cmd[[startinsert]] + + if not contents or type(contents) == "string" then + goto wait_for_fzf + end + + -- have to open this after there is a reader (termopen) + -- otherwise this will block + fd = uv.fs_open(fifotmpname, "w", -1) + output_pipe = uv.new_pipe(false) + output_pipe:open(fd) + -- print(uv.pipe_getpeername(output_pipe)) + + -- this part runs in the background, when the user has selected, it will + -- error out, but that doesn't matter so we just break out of the loop. + if contents then + if type(contents) == "table" then + if not vim.tbl_isempty(contents) then + write_cb(vim.tbl_map(function(x) return x.."\n" end, contents)) + end + finish(4) + else + contents(function(usrdata, cb, no_nl) + if usrdata == nil then + if cb then cb(nil) end + finish(5) + return + end + if no_nl then + write_cb(usrdata, cb) + else + write_cb(tostring(usrdata).."\n", cb) + end + end, output_pipe) + end + end + + ::wait_for_fzf:: + + return coroutine.yield() +end + +return M diff --git a/lua/fzf-lua/libuv.lua b/lua/fzf-lua/libuv.lua index 43b2154..4c1e335 100644 --- a/lua/fzf-lua/libuv.lua +++ b/lua/fzf-lua/libuv.lua @@ -91,13 +91,12 @@ M.spawn = function(opts, fn_transform, fn_done) local function write_cb(data) write_cb_count = write_cb_count + 1 opts.cb_write(data, function(err) + write_cb_count = write_cb_count - 1 if err then -- can fail with premature process kill -- assert(not err) finish(2, pid) - end - write_cb_count = write_cb_count - 1 - if write_cb_count == 0 and uv.is_closing(output_pipe) then + elseif write_cb_count == 0 and uv.is_closing(output_pipe) then -- spawn callback already called and did not close the pipe -- due to write_cb_count>0, since this is the last call -- we can close the fzf pipe @@ -171,18 +170,13 @@ M.spawn_nvim_fzf_cmd = function(opts, fn_transform) end local function on_write(data, cb) - -- nvim-fzf adds "\n" at the end - -- so we have to remove ours - assert(string_byte(data, #data) == 10) - fzf_cb(string_sub(data, 1, #data-1), cb) - end - - if not fn_transform then - -- must add a dummy function here, without it - -- spawn() wouldn't parse EOLs and send partial - -- data via nvim-fzf's fzf_cb() which adds "\n" - -- each call, resulting in mangled data - fn_transform = function(x) return x end + -- passthrough the data exactly as received from the pipe + -- 'false' as 3rd parameter instructs raw_fzf to not add "\n" + -- if 'fn_transform' was specified the last char must be EOL + -- otherwise something went terribly wrong + -- without 'fn_transform' EOL isn't guaranteed at the end + -- assert(not fn_transform or string_byte(data, #data) == 10) + fzf_cb(data, cb, true) end return M.spawn({ diff --git a/lua/fzf-lua/providers/git.lua b/lua/fzf-lua/providers/git.lua index e7b4e73..bbe8f07 100644 --- a/lua/fzf-lua/providers/git.lua +++ b/lua/fzf-lua/providers/git.lua @@ -48,8 +48,7 @@ local function git_cmd(opts) if not opts.cwd then return end coroutine.wrap(function () opts.fzf_fn = libuv.spawn_nvim_fzf_cmd( - { cmd = opts.cmd, cwd = opts.cwd, pid_cb = opts._pid_cb }, - function(x) return x end) + { cmd = opts.cmd, cwd = opts.cwd, pid_cb = opts._pid_cb }) local selected = core.fzf(opts, opts.fzf_fn) if not selected then return end actions.act(opts.actions, selected, opts)