diff --git a/README.md b/README.md index c6a772c8..8ad233be 100644 --- a/README.md +++ b/README.md @@ -145,16 +145,18 @@ Alternatively, resuming work on a specific picker: > By default pressing esc or ctrl-c terminates the fzf process, > as such resume is not perfect and is limited to resuming the > picker/query and sometimes additional parameters such as regex -> in grep, etc, for a more "complete" resume press alt-esc to -> hide the fzf process instead, this will keep the fzf process -> running in the background and thus will restore the process -> entirely including cursor position and selection. +> in grep, etc, for a more complete resume use the "hide" profile, +> this will keep the fzf process running in the background allowing +> `:FzfLua resume` to restore the picker state entirely, including +> cursor position and selection. > To configure hiding by default: > ```lua -> require("fzf-lua").setup({ keymap = { builtin = { true, [""] = "hide" } } }) +> require("fzf-lua").setup({ +> "hide", +> -- your other settings here +> }) > ``` - **LIST OF AVAILABLE COMMANDS BELOW** 👇 ## Commands @@ -1077,10 +1079,6 @@ previewers = { winopts = { height = 0.55, width = 0.30, }, -- uncomment to ignore colorschemes names (lua patterns) -- ignore_patterns = { "^delek$", "^blue$" }, - -- uncomment to execute a callback on preview|close - -- e.g. a call to reset statusline highlights - -- cb_preview = function() ... end, - -- cb_exit = function() ... end, }, awesome_colorschemes = { prompt = 'Colorschemes❯ ', @@ -1099,9 +1097,6 @@ previewers = { ["ctrl-r"] = { fn = actions.cs_update, reload = true }, ["ctrl-x"] = { fn = actions.cs_delete, reload = true }, }, - -- uncomment to execute a callback on preview|close - -- cb_preview = function() ... end, - -- cb_exit = function() ... end, }, keymaps = { prompt = "Keymaps> ", @@ -1359,6 +1354,7 @@ require('fzf-lua').setup({'fzf-vim'}) | `borderless` | borderless and minimalistic seamless look & feel | | `borderless-full` | borderless with description in window title (instead of prompt) | | `border-fused` | single border around both fzf and the previewer | +| `hide` | send fzf process to background instead of termination | diff --git a/lua/fzf-lua/actions.lua b/lua/fzf-lua/actions.lua index 31df4604..765c37d9 100644 --- a/lua/fzf-lua/actions.lua +++ b/lua/fzf-lua/actions.lua @@ -696,7 +696,8 @@ M.git_yank_commit = function(selected, opts) -- copy to the yank register regardless vim.fn.setreg(reg, commit_hash) vim.fn.setreg([[0]], commit_hash) - utils.info(string.format("commit hash %s copied to register %s, use 'p' to paste.", commit_hash, reg)) + utils.info(string.format("commit hash %s copied to register %s, use 'p' to paste.", + commit_hash, reg)) end M.git_checkout = function(selected, opts) diff --git a/lua/fzf-lua/config.lua b/lua/fzf-lua/config.lua index cbecfc77..fdfb5f9c 100644 --- a/lua/fzf-lua/config.lua +++ b/lua/fzf-lua/config.lua @@ -841,6 +841,10 @@ function M.normalize_opts(opts, globals, __resume_key) end end + if type(opts.enrich) == "function" then + opts = opts.enrich(opts) + end + -- mark as normalized opts._normalized = true diff --git a/lua/fzf-lua/core.lua b/lua/fzf-lua/core.lua index ad79986a..0b43adad 100644 --- a/lua/fzf-lua/core.lua +++ b/lua/fzf-lua/core.lua @@ -610,7 +610,7 @@ M.create_fzf_binds = function(opts) -- Separate "transform|execute|execute-silent" binds to their own `--bind` argument, this -- way we can use `transform:...` and not be forced to use brackets, i.e. `transform(...)` -- this enables us to use brackets in the inner actions, e.g. "zero:transform:rebind(...)" - if action:match("transform") or action:match("execute") then + if action:match("transform") or action:match("execute") or action:match("reload") then table.insert(separate, bind) else table.insert(combine, bind) @@ -1103,6 +1103,12 @@ M.convert_reload_actions = function(reload_cmd, opts) local shell_action = shell.raw_action(function(items, _, _) v.fn(items, opts) end, v.field_index == false and "" or v.field_index or "{+}", opts.debug) + if type(v.prefix) == "string" and not v.prefix:match("%+$") then + v.prefix = v.prefix .. "+" + end + if type(v.postfix) == "string" and not v.postfix:match("^%+") then + v.postfix = "+" .. v.postfix + end opts.keymap.fzf[k] = { string.format("%s%sexecute-silent(%s)+reload(%s)%s", type(v.prefix) == "string" and v.prefix or "", @@ -1110,7 +1116,7 @@ M.convert_reload_actions = function(reload_cmd, opts) shell_action, reload_cmd, type(v.postfix) == "string" and v.postfix or ""), - desc = config.get_action_helpstr(v.fn) + desc = v.desc or config.get_action_helpstr(v.fn) } opts.actions[k] = nil end @@ -1125,25 +1131,54 @@ end ---@param opts table ---@return table M.convert_exec_silent_actions = function(opts) - -- Does not work with fzf version < 0.36, fzf fails with - -- "error 2: bind action not specified:" - if not utils.has(opts, "fzf", { 0, 36 }) - or utils.has(opts, "sk") then + -- `execute-silent` actions are bugged with skim (can't use quotes) + if utils.has(opts, "sk") then return opts end for k, v in pairs(opts.actions) do if type(v) == "table" and v.exec_silent then assert(type(v.fn) == "function") + -- Use both {q} and {+} as field indexes so we can update last query when + -- executing the action, without this we lose the last query on "hide" as + -- the process never terminates and `--print-query` isn't being printed + local field_index = v.field_index == false and "" or v.field_index or "{q} {+}" -- replace the action with shell cmd proxy to the original action local shell_action = shell.raw_action(function(items, _, _) + if field_index:match("^{q}") then + local query = table.remove(items, 1) + config.resume_set("query", query, opts) + end v.fn(items, opts) - end, v.field_index == false and "" or v.field_index or "{+}", opts.debug) + end, field_index, opts.debug) + if type(v.prefix) == "string" and not v.prefix:match("%+$") then + v.prefix = v.prefix .. "+" + end + if type(v.postfix) == "string" and not v.postfix:match("^%+") then + v.postfix = "+" .. v.postfix + end + -- `execute-silent(...)` with fzf version < 0.36, errors with: + -- 'error 2: bind action not specified' (due to inner brackets) + -- changing to `execute-silent:...` removes the need to care for + -- brackets within the command with the limitation of not using + -- potfix (must be the last part of the arg), from `man fzf`: + -- + -- action-name:... + -- The last one is the special form that frees you from parse + -- errors as it does not expect the closing character. The catch is + -- that it should be the last one in the comma-separated list of + -- key-action pairs. + -- + local has_fzf036 = utils.has(opts, "fzf", { 0, 36 }) opts.keymap.fzf[k] = { - string.format("%sexecute-silent(%s)%s", + string.format("%sexecute-silent%s%s", type(v.prefix) == "string" and v.prefix or "", - shell_action, - type(v.postfix) == "string" and v.postfix or ""), - desc = config.get_action_helpstr(v.fn) + -- prefer "execute-silent:..." unless we have postfix + has_fzf036 and type(v.postfix) == "string" + and string.format("(%s)", shell_action) + or string.format(":%s", shell_action), + -- can't use postfix since we use "execute-silent:..." + has_fzf036 and type(v.postfix) == "string" and v.postfix or ""), + desc = v.desc or config.get_action_helpstr(v.fn) } opts.actions[k] = nil end diff --git a/lua/fzf-lua/defaults.lua b/lua/fzf-lua/defaults.lua index bf0a7f2f..315f3f7d 100644 --- a/lua/fzf-lua/defaults.lua +++ b/lua/fzf-lua/defaults.lua @@ -445,7 +445,7 @@ M.defaults.git = { prompt = "Branches> ", cmd = "git branch --all --color", preview = "git log --graph --pretty=oneline --abbrev-commit --color {1}", - remotes = "local", + remotes = "local", actions = { ["enter"] = actions.git_switch, ["ctrl-x"] = { fn = actions.git_branch_del, reload = true }, diff --git a/lua/fzf-lua/init.lua b/lua/fzf-lua/init.lua index e4dd6848..5a090c4b 100644 --- a/lua/fzf-lua/init.lua +++ b/lua/fzf-lua/init.lua @@ -143,7 +143,7 @@ end -- case the user decides not to call `setup()` M.setup_highlights() -local function load_profiles(profiles) +function M.load_profiles(profiles, silent, opts) local ret = {} profiles = type(profiles) == "table" and profiles or type(profiles) == "string" and { profiles } @@ -152,15 +152,15 @@ local function load_profiles(profiles) -- backward compat, renamed "borderless_full" > "borderless-full" if profile == "borderless_full" then profile = "borderless-full" end local fname = path.join({ vim.g.fzf_lua_directory, "profiles", profile .. ".lua" }) - local profile_opts = utils.load_profile_fname(fname, nil, 1) + local profile_opts = utils.load_profile_fname(fname, nil, silent) if type(profile_opts) == "table" then if profile_opts[1] then -- profile requires loading base profile(s) profile_opts = vim.tbl_deep_extend("keep", - profile_opts, load_profiles(profile_opts[1])) + profile_opts, M.load_profiles(profile_opts[1], silent, opts)) end if type(profile_opts.fn_load) == "function" then - profile_opts.fn_load() + profile_opts.fn_load(opts) profile_opts.fn_load = nil end ret = vim.tbl_deep_extend("force", ret, profile_opts) @@ -175,7 +175,7 @@ function M.setup(opts, do_not_reset_defaults) opts[1] = opts[1] == nil and utils.__HAS_NVIM_09 and "default-title" or opts[1] if opts[1] then -- Did the user supply profile(s) to load? - opts = vim.tbl_deep_extend("keep", opts, load_profiles(opts[1])) + opts = vim.tbl_deep_extend("keep", opts, M.load_profiles(opts[1], 1, opts)) end if do_not_reset_defaults then -- no defaults reset requested, merge with previous setup options @@ -381,6 +381,7 @@ M._exported_modules = { M._excluded_meta = { "setup", "redraw", + "load_profiles", "fzf", "fzf_raw", "fzf_wrap", diff --git a/lua/fzf-lua/profiles/README.md b/lua/fzf-lua/profiles/README.md index 535c483e..223456da 100644 --- a/lua/fzf-lua/profiles/README.md +++ b/lua/fzf-lua/profiles/README.md @@ -29,6 +29,7 @@ telescope defaults with `bat` previewer: | `borderless` | borderless and minimalistic seamless look & feel | | `borderless-full` | borderless with description in window title (instead of prompt) | | `border-fused` | single border around both fzf and the previewer | +| `hide` | send fzf process to background instead of termination | **Custom user settings which make sense and aren't mere duplications with minimal modifications diff --git a/lua/fzf-lua/profiles/hide.lua b/lua/fzf-lua/profiles/hide.lua new file mode 100644 index 00000000..fb558a80 --- /dev/null +++ b/lua/fzf-lua/profiles/hide.lua @@ -0,0 +1,53 @@ +local uv = vim.uv or vim.loop +local fzf = require("fzf-lua") +return { + desc = "hide interface instead of abort", + defaults = { + enrich = function(opts) + if opts._is_fzf_tmux then + fzf.utils.warn("'hide' profile cannot work with tmux, ignoring.") + return opts + end + -- `execute-silent` actions are bugged with skim + if fzf.utils.has(opts, "sk") then + opts.actions["esc"] = false + opts.keymap.builtin[""] = "hide" + return opts + end + -- While we can use `keymap.builtin.` (to hide) this is better + -- as it captures the query when execute-silent action is called as + -- we add "{q}" as the first field index similar to `--print-query` + local histfile = opts.fzf_opts and opts.fzf_opts["--history"] + opts.actions["esc"] = { fn = fzf.actions.dummy_abort, desc = "hide" } + opts.actions = vim.tbl_map(function(act) + act = type(act) == "function" and { fn = act } or act + act = type(act) == "table" and type(act[1]) == "function" + and { fn = act[1], noclose = true } or act + assert(type(act) == "table" and type(act.fn) == "function" or not act) + if type(act) == "table" and + not act.exec_silent and not act.reload and not act.noclose + then + local fn = act.fn + act.exec_silent = true + act.desc = act.desc or fzf.config.get_action_helpstr(fn) + act.fn = function(s, o) + fzf.hide() + fn(s, o) + -- As the process never terminates fzf history is never written + -- manually append to the fzf history file if needed + if histfile and type(o.last_query) == "string" and #o.last_query > 0 then + local fd = uv.fs_open(histfile, "a", -1) + if fd then + uv.fs_write(fd, o.last_query .. "\n", nil, function(_) + uv.fs_close(fd) + end) + end + end + end + end + return act + end, opts.actions) + return opts + end, + }, +} diff --git a/lua/fzf-lua/providers/colorschemes.lua b/lua/fzf-lua/providers/colorschemes.lua index 8bf33dca..7283dc65 100644 --- a/lua/fzf-lua/providers/colorschemes.lua +++ b/lua/fzf-lua/providers/colorschemes.lua @@ -61,32 +61,28 @@ M.colorschemes = function(opts) opts.fzf_opts["--preview-window"] = "nohidden:right:0" opts.preview = shell.raw_action(function(sel) if opts.live_preview and sel then + opts._live = sel[1] vim.cmd("colorscheme " .. sel[1]) - if type(opts.cb_preview) == "function" then - opts.cb_preview(sel, opts) - end end end, nil, opts.debug) end - opts.fn_selected = function(selected, o) + opts.winopts = opts.winopts or {} + opts.winopts.on_close = function() -- reset color scheme if live_preview is enabled - -- and nothing or non-default action was selected - if opts.live_preview and (not selected or #selected[1] > 0) then + if opts._live and opts._live ~= current_colorscheme then vim.cmd("colorscheme " .. current_colorscheme) vim.o.background = current_background end + end + opts.fn_selected = function(selected, o) if selected then actions.act(opts.actions, selected, o) end -- setup fzf-lua's own highlight groups utils.setup_highlights() - - if type(opts.cb_exit) == "function" then - opts.cb_exit(selected, opts) - end end core.fzf_exec(colors, opts) @@ -486,12 +482,11 @@ M.awesome_colorschemes = function(opts) -- wrap in pcall as some colorschemes have bg triggers that can fail pcall(function() vim.o.background = opts._cur_background end) M.apply_awesome_theme(dbkey, idx, opts) - if type(opts.cb_preview) == "function" then - opts.cb_preview(sel, opts) - end + opts._live = true else vim.cmd("colorscheme " .. opts._cur_colorscheme) vim.o.background = opts._cur_background + opts._live = nil end end end, "{}", opts.debug) @@ -509,6 +504,15 @@ M.awesome_colorschemes = function(opts) end end + opts.winopts = opts.winopts or {} + opts.winopts.on_close = function() + -- reset color scheme if live_preview is enabled + if opts._live then + vim.cmd("colorscheme " .. opts._cur_colorscheme) + vim.o.background = opts._cur_background + end + end + opts.fn_selected = function(sel, o) -- do not remove our cache path from packpath -- or packadd in `apply_awesome_theme` fails @@ -530,10 +534,6 @@ M.awesome_colorschemes = function(opts) -- setup fzf-lua's own highlight groups utils.setup_highlights() - - if type(o.cb_exit) == "function" then - o.cb_exit(sel, o) - end end opts = core.set_header(opts, opts.headers or { "actions" }) diff --git a/lua/fzf-lua/providers/module.lua b/lua/fzf-lua/providers/module.lua index 37a624c8..72e99606 100644 --- a/lua/fzf-lua/providers/module.lua +++ b/lua/fzf-lua/providers/module.lua @@ -57,6 +57,11 @@ M.profiles = function(opts) opts = config.normalize_opts(opts, "profiles") if not opts then return end + if opts.load then + utils.load_profiles(opts.load) + return + end + local dirs = { path.join({ vim.g.fzf_lua_directory, "profiles" }) } diff --git a/lua/fzf-lua/shell.lua b/lua/fzf-lua/shell.lua index 393176d2..819f9366 100644 --- a/lua/fzf-lua/shell.lua +++ b/lua/fzf-lua/shell.lua @@ -12,7 +12,7 @@ local M = {} -- provider are 2 (`live_grep` with `multiprocess=false`) -- and 4 (`git_status` with preview and 3 reload binds) -- we can always increase if we need more -local _MAX_LEN = vim.g.fzf_lua_shell_maxlen or 10 +local _MAX_LEN = 50 local _index = 0 local _registry = {} local _protected = {} diff --git a/lua/fzf-lua/utils.lua b/lua/fzf-lua/utils.lua index c543e663..7bb845a1 100644 --- a/lua/fzf-lua/utils.lua +++ b/lua/fzf-lua/utils.lua @@ -820,6 +820,10 @@ function M.CTX() return loadstring("return require'fzf-lua'.core.CTX()")() end +function M.__CTX() + return loadstring("return require'fzf-lua'.core.__CTX")() +end + function M.resume_get(what, opts) local f = loadstring("return require'fzf-lua'.config.resume_get")() return f(what, opts) @@ -841,7 +845,7 @@ end ---@param fname string ---@param name string|nil ----@param silent boolean|number +---@param silent boolean|integer function M.load_profile_fname(fname, name, silent) local profile = name or vim.fn.fnamemodify(fname, ":t:r") or "" local ok, res = pcall(dofile, fname) @@ -861,6 +865,19 @@ function M.load_profile_fname(fname, name, silent) end end +function M.load_profiles(profiles) + local serpent = require("fzf-lua.lib.serpent") + profiles = type(profiles) == "table" + and serpent.line(profiles, { comment = false, sortkeys = false }) + or type(profiles) == "string" and string.format("'%s'", profiles) + or nil + if type(profiles) == "string" then + loadstring(string.format( + "require'fzf-lua'.setup(require'fzf-lua'.load_profiles(%s, false))", + profiles))() + end +end + function M.send_ctrl_c() vim.api.nvim_feedkeys( vim.api.nvim_replace_termcodes("", true, false, true), "n", true) diff --git a/lua/fzf-lua/win.lua b/lua/fzf-lua/win.lua index bec44cd3..88927653 100644 --- a/lua/fzf-lua/win.lua +++ b/lua/fzf-lua/win.lua @@ -47,12 +47,12 @@ function TSInjector.deregister() TSInjector._setup = nil end -function TSInjector.clear_cache(buf, noassert) +function TSInjector.clear_cache(buf) -- If called from fzf-tmux buf will be `nil` (#1556) if not buf then return end TSInjector.cache[buf] = nil -- If called from `FzfWin.hide` cache will not be empty - assert(noassert or utils.tbl_isempty(TSInjector.cache)) + assert(utils.tbl_isempty(TSInjector.cache)) end ---@param buf number @@ -834,8 +834,8 @@ function FzfWin:set_winleave_autocmd() self:_nvim_create_autocmd("WinClosed", self.win_leave) end -function FzfWin:treesitter_detach(buf, noassert) - TSInjector.clear_cache(buf, noassert) +function FzfWin:treesitter_detach(buf) + TSInjector.clear_cache(buf) TSInjector.deregister() end @@ -889,7 +889,8 @@ function FzfWin:treesitter_attach() if #filepath == 0 or string.byte(text, 1) == 160 then if string.byte(text, 1) == 160 then text = text:sub(2) end -- remove A0+SPACE if string.byte(text, 1) == 32 then text = text:sub(2) end -- remove leading SPACE - local b = filepath:match("^%d+") or utils.CTX().bufnr + -- IMPORTANT: use the `__CTX` version that doesn't trigger a new context + local b = filepath:match("^%d+") or utils.__CTX().bufnr return vim.api.nvim_buf_is_valid(tonumber(b)) and b or nil end end)() @@ -943,8 +944,6 @@ function FzfWin:set_tmp_buffer(no_wipe) self:set_winleave_autocmd() -- automatically resize fzf window self:set_redraw_autocmd() - -- Use treesitter to highlight results on the main fzf window - self:treesitter_attach() -- since we have the cursorline workaround from -- issue #254, resume shows an ugly cursorline. -- remove it, nvim_win API is better than vim.wo? @@ -1099,7 +1098,7 @@ function FzfWin:close(fzf_bufnr, do_not_clear_cache) vim.api.nvim_buf_delete(self.fzf_bufnr, { force = true }) end -- Clear treesitter buffer cache and deregister decoration callbacks - self:treesitter_detach(self.fzf_bufnr, self._hidden_fzf_bufnr) + self:treesitter_detach(self._hidden_fzf_bufnr or self.fzf_bufnr) -- when using `split = "belowright new"` closing the fzf -- window may not always return to the correct source win -- depending on the user's split configuration (#397)