From 5ddd2890435190f8b08b33cdb8d6cb6ae7fde66b Mon Sep 17 00:00:00 2001 From: Collin Williams <96917990+bluedragon1221@users.noreply.github.com> Date: Wed, 6 Aug 2025 07:23:11 -0500 Subject: fzf-exec mostly done --- fzf-exec/writeup.md | 125 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 fzf-exec/writeup.md (limited to 'fzf-exec/writeup.md') diff --git a/fzf-exec/writeup.md b/fzf-exec/writeup.md new file mode 100644 index 0000000..5c6d87e --- /dev/null +++ b/fzf-exec/writeup.md @@ -0,0 +1,125 @@ +Here's a writeup of how `fzf-exec` works, if your interested. + +## Setup +The basic setup is three scripts, plus a binding in `tmux.conf` to launch it. +- `tmux-doc.sh`: Fetches the documentation for a specific tmux command +- `fzf-exec.sh`: Launches the menu of all tmux commands and previously entered commands +- `minibuffer.sh`: Launches any program in a popup window at the bottom of the screen + +### `tmux-doc.sh` +This ended up being the toughest part of the project, and its still kind of finickey. +Also this is entirely optional if you want to just not have the docs for the selected command in the fzf preview. +The basic procedure is to grep tmux's manpage for the part that has the documentation about our selected command, and pull that out. +The problem is that if we're looking for, say, `source-file` in the docs, its likely that `source-file` appears before the part we're trying to extract. +To avoid this problem, we can search for the command, along with its usage, to get a unique result that we can extract from. +To get the search string, given the name of the command, we can use this command: +```sh +usage=$(tmux list-command -F "#{command_list_name} #{command_list_usage}" "$1") +``` + +Then, using this awk command, we can search through the manpage until we find an empty line, signaling the end of that doc section. +```sh +man tmux | awk -v usage="$usage" ' + index($0, usage) > 0 { found=1 } + found { + print + if ($0 == "") exit + } +' +``` + +Putting it all together: +```sh +usage=$(tmux list-command -F "#{command_list_name} #{command_list_usage}" "$1") +man tmux | awk -v usage="$usage" ' + index($0, usage) > 0 { found=1 } + found { + print + if ($0 == "") exit + } +' +``` + +### `fzf-exec.sh` +First, we'll get a list of all of the possible tmux commands. +This is trivial using `tmux list-commands`: +```sh +all_cmds() { + tmux list-commands -F "#{command_list_name}" +} +``` +However, we want to list aliases as well. +We can slightly modify our command to do that: +```sh +all_cmds() { + tmux list-commands -F $'#{command_list_name}#{?command_list_alias,\n#{command_list_alias},}' +} +``` + +Now, for the actual menu that we're interacting with, I wanted specific behavior that is not default to fzf: +- I want `tab` to replace the current query with the selected item in the menu. + This means you could type `sou` to get `source` without exiting fzf +- I want `enter` to, if nothing is selected, return the current query, otherwise return the selection. + This means if you press enter on a menu item, it will return it, but if your query isn't a menu item, it will return your query. +- It would also be nice to have a binding to clear the query + +I ended up with this fzf command: +```sh +selected_cmd=$(all_cmds | fzf \ + --prompt : \ + --bind 'enter:accept-or-print-query,tab:replace-query,alt-backspace:clear-query' +) +``` + +Assuming `tmux-doc.sh` is in the path, we can add `--preview ...` to the end to add the docs preview that we previously wrote: +```sh +selected_cmd=$(all_cmds | fzf \ + --prompt : \ + --bind 'enter:accept-or-print-query,tab:replace-query,alt-backspace:clear-query' \ + --preview "tmux-doc.sh $(printf '{}' | cut -d' ' -f1)" +) +``` + +Now we can execute the command: +```sh +tmux $selected_cmd +``` +One problem: this will fail if one of the arguments is a path containing a tilde `~`, since it looks for the literal directory `./~`. +We can fix this with a quick sed (but if anyone knows a better solution please let me know): +```sh +tmux $(echo "$selected_cmd" | sed "s@~@$HOME@g") +``` + +Putting it all together: +```sh +all_cmds() { + tmux list-commands -F $'#{command_list_name}#{?command_list_alias,\n#{command_list_alias},}' +} + +selected_cmd=$(all_cmds | fzf \ + --prompt : \ + --bind 'enter:accept-or-print-query,tab:replace-query,alt-backspace:clear-query' \ + --preview "tmux-doc.sh $(printf '{}' | cut -d' ' -f1)" +) + +tmux $(echo "$selected_cmd" | sed "s@~@$HOME@g") +``` + +### `minibuffer.sh` +This is the easiest one. +Basically, we just make a popup window that's as wide as the screen, positioned at the bottom of the screen, with no border. +```sh +window_height="$(tmux display -p '#{window_height}')" +tmux display-popup -EB \ + -w 100% -h 16 \ + -x 0 -y "$(($window_height + 1))" \ + "$@" +``` + +## Binding +Now we can just add a binding to override the default command prompt, and we're done! +```sh +# Replace command prompt with fzf +bind-key -T prefix : run-shell "minibuffer.sh -h 10 'fzf-exec.sh'" +``` + -- cgit v1.3.1