local M = {} local config = require("denote-fzf-lua.config") local fzflua = require("fzf-lua") local builtin = require("fzf-lua.previewer.builtin") local FIELDS = { "path", "date", "time", "sig", "title", "keywords", "ext" } local lua_dir = debug.getinfo(1, "S").source:match("@?(.*/)") local script_dir = lua_dir .. "denote-fzf-lua/scripts/" local function format_field(field, delim) if field == "." then return "" end return delim .. delim .. field:gsub("%s", delim) end function M.format_fzf_with_nth(options) local indices = {} for i, field in ipairs(FIELDS) do if options.search_fields[field] then table.insert(indices, i) end end return table.concat(indices, ",") end function M.has_prereqs(force_slow) if vim.fn.executable("fzf") ~= 1 then return "no" end if force_slow then return "partial" end for _, cmd in ipairs({ "fd", "sd", "qsv" }) do if vim.fn.executable(cmd) ~= 1 then return "partial" end end return "yes" end function M.get_script(force_slow) local prereqs = M.has_prereqs(force_slow) if prereqs == "no" then error("Missing dependency: fzf") return elseif prereqs == "yes" then return script_dir .. "search_files.sh" else return script_dir .. "fallback_search_files.sh" end end function M.reconstruct_filename(row) if not row or row == "" then return "" end row = row:gsub("%s+$", "") local parts = {} row = row:gsub("%s%s+", "|") parts.path, parts.year, parts.month, parts.day, parts.hour, parts.min, parts.sec, parts.sig, parts.title, parts.keywords, parts.ext = row:match("^(.*/)|(%d%d%d%d)%-(%d%d)%-(%d%d)|(%d%d):(%d%d):(%d%d)|([^|]+)|([^|]+)|([^|]+)|(%..+)") if not parts.path then return "" end local sig = format_field(parts.sig, "=") local title = format_field(parts.title, "-") local keywords = format_field(parts.keywords, "_") return parts.path .. parts.year .. parts.month .. parts.day .. "T" .. parts.hour .. parts.min .. parts.sec .. sig .. title .. keywords .. parts.ext end M.reconstruct_previewer = builtin.buffer_or_file:extend() function M.reconstruct_previewer:new(o, opts, fzf_win) M.reconstruct_previewer.super.new(self, o, opts, fzf_win) setmetatable(self, M.reconstruct_previewer) return self end function M.reconstruct_previewer:parse_entry(entry_str) return { path = M.reconstruct_filename(entry_str), line = 1, col = 1, } end function M.search_files(options) local script = M.get_script(options.force_slow_mode) if not script then return end local opts = {} opts.previewer = M.reconstruct_previewer opts.fzf_opts = vim.deepcopy(options.fzf_lua_opts.fzf_opts) opts.fzf_opts["--with-nth"] = M.format_fzf_with_nth(options) opts.fzf_opts["--header-lines"] = 1 opts.fzf_opts["--delimiter"] = "\\s{2,}" opts.actions = { default = function(selected) local filename = M.reconstruct_filename(selected[1]) vim.api.nvim_command("edit " .. vim.fn.fnameescape(filename)) end, } local dir = vim.fn.expand(vim.g.denote_dir or options.dir) fzflua.fzf_exec(script .. " " .. dir, opts) end function M.search_contents(options) if vim.fn.executable("rg") ~= 1 then vim.notify("denote-fzf-lua: ripgrep is required for content search", vim.log.levels.WARN) return end local opts = {} opts.cwd = vim.fn.expand(vim.g.denote_dir or options.dir) opts.previewer = "builtin" opts.fzf_opts = options.fzf_lua_opts.fzf_opts opts.actions = fzflua.defaults.actions.files fzflua.fzf_live(function(query) local q = type(query) == "table" and table.concat(query, " ") or query or "" return "rg --column --color=always -i -- " .. vim.fn.shellescape(q) end, opts) end function M.load_cmd(options) vim.api.nvim_create_user_command("DenoteSearch", function(opts) if opts.fargs[1] == "files" then M.search_files(options) elseif opts.fargs[1] == "contents" then M.search_contents(options) else error("Unsupported operation " .. opts.fargs[1]) end end, { nargs = 1, complete = function() return { "files", "contents" } end, }) end function M.setup(options) options = vim.tbl_deep_extend("force", config.defaults, options or {}) fzflua.setup(options.fzf_lua_opts) M.load_cmd(options) end return M