Skip to content

Commit

Permalink
feat(profiles): added hide (improved resume, #1686)
Browse files Browse the repository at this point in the history
The profile changes the default binds to hide the fzf-lua window
instead of aborting it, therefore keeping cursor position, selection,
etc.

Note that this also continues long running operations in the background
so be mindful of sending fzf to background on a large mono repo, can
still be aborted with `ctrl-c` or `alt-esc`.

Enable with:
```lua
-- Set as base profile
:lua require("fzf-lua").setup({"hide"})
-- More than one profile
:lua require("fzf-lua").setup({"border-fused","hide"})
-- Or with `profiles`:
:FzfLua profiles load=hide
-- More than one profile
:FzfLua profiles load={"border-fused","hide"}
```
  • Loading branch information
ibhagwan committed Jan 18, 2025
1 parent 46a7682 commit e99c7ee
Show file tree
Hide file tree
Showing 13 changed files with 170 additions and 58 deletions.
22 changes: 9 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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, ["<Esc>"] = "hide" } } })
> require("fzf-lua").setup({
> "hide",
> -- your other settings here
> })
> ```
**LIST OF AVAILABLE COMMANDS BELOW** 👇
## Commands
Expand Down Expand Up @@ -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❯ ',
Expand All @@ -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> ",
Expand Down Expand Up @@ -1359,6 +1354,7 @@ require('fzf-lua').setup({'fzf-vim'})
| `borderless` | borderless and minimalistic seamless look &amp; 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 |

</details>

Expand Down
3 changes: 2 additions & 1 deletion lua/fzf-lua/actions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions lua/fzf-lua/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
57 changes: 46 additions & 11 deletions lua/fzf-lua/core.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -1103,14 +1103,20 @@ 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 "",
unbind and (unbind .. "+") or "",
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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lua/fzf-lua/defaults.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
11 changes: 6 additions & 5 deletions lua/fzf-lua/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -381,6 +381,7 @@ M._exported_modules = {
M._excluded_meta = {
"setup",
"redraw",
"load_profiles",
"fzf",
"fzf_raw",
"fzf_wrap",
Expand Down
1 change: 1 addition & 0 deletions lua/fzf-lua/profiles/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ telescope defaults with `bat` previewer:
| `borderless` | borderless and minimalistic seamless look &amp; 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
Expand Down
53 changes: 53 additions & 0 deletions lua/fzf-lua/profiles/hide.lua
Original file line number Diff line number Diff line change
@@ -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["<esc>"] = "hide"
return opts
end
-- While we can use `keymap.builtin.<esc>` (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,
},
}
34 changes: 17 additions & 17 deletions lua/fzf-lua/providers/colorschemes.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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" })
Expand Down
5 changes: 5 additions & 0 deletions lua/fzf-lua/providers/module.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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" })
}
Expand Down
2 changes: 1 addition & 1 deletion lua/fzf-lua/shell.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}
Expand Down
Loading

0 comments on commit e99c7ee

Please sign in to comment.