1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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<TAB>` 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'"
```
|