---@diagnostic disable-next-line: deprecated
local core = require "fzf-lua.core"
local path = require "fzf-lua.path"
local utils = require "fzf-lua.utils"
local config = require "fzf-lua.config"
local libuv = require "fzf-lua.libuv"
local M = {}
-- Given a path, attempt to find the first existing directory
-- returns a tuple:
-- display cwd: to be joined with the completed path, maintains
-- the original cwd format (i.e $HOME, ~, "./", etc)
-- fullpath cwd and postfix which will be used as fzf's "--query"
-- argument to be entered into the prompt automatically
local function find_toplevel_cwd(maybe_cwd, postfix, orig_cwd)
-- expand can fail on open curly braces with:
-- E5108: Error executing lua Vim:E220: Missing }.
local ok, _ = pcall(libuv.expand, maybe_cwd)
if not maybe_cwd or #maybe_cwd == 0 or not ok then
return nil, nil, nil
end
if not orig_cwd then
orig_cwd = maybe_cwd
end
if vim.fn.isdirectory(libuv.expand(maybe_cwd) --[[@as string]]) == 1 then
local disp_cwd, cwd = maybe_cwd, libuv.expand(maybe_cwd) --[[@as string]]
-- returned cwd must be full path
if path.has_cwd_prefix(cwd) then
cwd = utils.cwd() .. (#cwd > 1 and cwd:sub(2) or "")
-- inject "./" only if original path started with it
-- otherwise ignore the "." retval from fnamemodify
if #orig_cwd > 0 and orig_cwd:sub(1, 1) ~= "." then
disp_cwd = nil
end
elseif not path.is_absolute(cwd) then
cwd = path.join({ utils.cwd(), cwd })
end
return disp_cwd, cwd, postfix
end
postfix = vim.fn.fnamemodify(maybe_cwd, ":t")
maybe_cwd = vim.fn.fnamemodify(maybe_cwd, ":h")
return find_toplevel_cwd(maybe_cwd, postfix, orig_cwd)
end
-- forward and reverse match spaces and single/double quotes
-- and attempt to find the top level existing directory
-- set the cwd and prompt top the top level directory and
-- the leftover match to the input query
local set_cmp_opts_path = function(opts)
local match = opts.word_pattern or "[^%s\"']*"
local line = vim.api.nvim_get_current_line()
local col = vim.api.nvim_win_get_cursor(0)[2] + 1
local before = col > 1 and assert(line:sub(1, col - 1):reverse():match(match)):reverse() or ""
local after = line:sub(col):match(match) or ""
-- special case when the cursor is on the left surrounding char
if #before == 0 and #after == 0 and #line > col then
col = col + 1
after = line:sub(col):match(match) or ""
end
local cwd
opts._cwd, cwd, opts.query = find_toplevel_cwd(before .. after, nil, nil)
opts.prompt = path.add_trailing(opts._cwd or ".")
opts.cwd = cwd or opts.cwd or utils.cwd()
-- completion function rebuilds the line with the full path
opts.complete = function(selected, o, l, _)
-- query fuzzy matching is empty
if #selected == 0 then return end
local replace_at = col - #before
local path0 = assert(path.entry_to_file(selected[1], o).path)
local relpath = path.relative_to(path0, opts.cwd)
local before_path = replace_at > 1 and l:sub(1, replace_at - 1) or ""
local rest_of_line = #l >= (col + #after) and l:sub(col + #after) or ""
local resolved_path = opts._cwd and path.join({ opts._cwd, relpath }) or relpath
return before_path .. resolved_path .. rest_of_line,
-- this goes to `nvim_win_set_cursor` which is 0-based
replace_at + #resolved_path - 2
end
return opts
end
---@param opts fzf-lua.config.CompletePath|{}?
---@return thread?, string?, table?
M.path = function(opts)
---@type fzf-lua.config.CompletePath
opts = config.normalize_opts(opts, "complete_path")
if not opts then return end
opts.cmd = opts.cmd or (function()
if vim.fn.executable("fdfind") == 1 then
return "fdfind --strip-cwd-prefix"
elseif vim.fn.executable("fd") == 1 then
return "fd --strip-cwd-prefix"
elseif utils.__IS_WINDOWS then
return "dir /s/b"
else
return [[find ! -path '.' ! -path '*/\.git/*' -printf '%P\n']]
end
end)()
opts = set_cmp_opts_path(opts)
return core.fzf_exec(opts.cmd, opts)
end
---@param opts fzf-lua.config.CompleteFile|{}?
---@return thread?, string?, table?
M.file = function(opts)
---@type fzf-lua.config.CompleteFile
opts = config.normalize_opts(opts, "complete_file")
if not opts then return end
opts.cmd = opts.cmd or (function()
if vim.fn.executable("fdfind") == 1 then
return "fdfind --strip-cwd-prefix --type f --exclude .git"
elseif vim.fn.executable("fd") == 1 then
return "fd --strip-cwd-prefix --type f --exclude .git"
elseif vim.fn.executable("rg") == 1 then
return "rg --files"
elseif utils.__IS_WINDOWS then
return "dir /s/b"
else
return [[find -type f ! -path '*/\.git/*' -printf '%P\n']]
end
end)()
opts = set_cmp_opts_path(opts)
---@diagnostic disable-next-line: param-type-mismatch
return core.fzf_exec(opts.cmd, opts)
end
---@param opts fzf-lua.config.CompleteLine|{}?
---@return thread?, string?, table?
M.line = function(opts)
---@type fzf-lua.config.CompleteLine
opts = config.normalize_opts(opts, "complete_line")
if not opts then return end
opts.query = (function()
local col = vim.api.nvim_win_get_cursor(0)[2] + 1
local line = vim.api.nvim_get_current_line()
return vim.trim(line:sub(1, col))
end)()
opts.complete = function(selected, _, _, _)
if not selected[1] then return end
local newline = selected[1]:match(" (.-)$")
return newline, #newline
end
return require "fzf-lua.providers.buffers".lines(opts)
end
---@param opts fzf-lua.config.CompleteLine|{}?
---@return thread?, string?, table?
M.bline = function(opts)
---@type fzf-lua.config.CompleteLine
opts = opts or {}
opts.current_buffer_only = true
return M.line(opts)
end
return M