Turn any shell command into a powerful TUI with custom keybindings.
The releases page contains pre-compiled binaries for Linux, macOS and Windows.
cargo install watchbind
Or install the latest git version from the main
branch:
cargo install --git https://github.com/fritzrehde/watchbind --branch main
watchbind
can be installed from the extra repository using pacman:
pacman -S watchbind
watchbind
is available for Alpine Edge. It can be installed via apk after enabling the testing repository.
apk add watchbind
Watchbind is a command-line tool that aims to help you build custom TUIs from static CLI commands very easily. It works by specifying a "watched command" that outputs some lines to stdout that you want to observe. Then, we make this static output dynamic by re-executing it at a specified watch rate, and we make the TUI interactive through custom keybindings that can operate on the individual output lines.
There are several ways to configure watchbind's settings:
watchbind -h
for all available arguments).watchbind --local-config-file <FILE>
.WATCHBIND_CONFIG_DIR
environment variable or in the default config directory (see watchbind -h
for the OS-specific default config directory), is loaded automatically if it exists.
The difference between the local and global config files is that the latter must not be specified with --local-config-file <FILE>
on every watchbind invocation, making it convenient for global settings that should apply to all watchbind invocations.All configuration ways can be used at the same time, and watchbind
will determine which settings to use according to the following configuration hierarchy:
CLI arguments > local TOML config file > global TOML config file
where a > b
means: If the config setting X
is configured in both a
and b
, the value of X
from a
is used.
On the command line, you can specify a comma-separated list of keybindings, where each keybinding is in the format KEY:OP[+OP]*
.
One KEY
can be bound to multiple operations, therefore, the syntax for each list of operations (OPS
) is OP[+OP]*
.
The operations are separated by +
and executed in succession (one after the other).
TLDR:
,
:
+
In a TOML config file, there are several different ways you can specify keybindings:
[keybindings]
# Single operation, without description
"KEY" = "OP"
# Single operation, with description
"KEY" = { description = "DESC", operations = "OP" }
# Multiple operations, without description
"KEY" = [ "OP_1", "OP_2", "OP_3" ]
# Multiple operations, with description
"KEY" = { description = "DESC", operations = [ "OP_1", "OP_2", "OP_3" ] }
This syntax differs from the command-line syntax because using the TOML array feature is more expressive and more native to the TOML file format.
Furthermore, this allows you to use the +
character in your commands, which is not possible with the CLI arguments (since that character is interpreted as a separator).
It also doesn't require escaping shell specific characters like $
(read more about subshell behaviour here).
You can find some keybinding examples in the examples/
directory.
All supported KEY
values:
<MODIFIER>+<CODE>
<CODE>
All supported MODIFIER
values:
alt
ctrl
All supported CODE
values:
esc
enter
left
right
up
down
home
end
pageup
pagedown
backtab
backspace
del
delete
insert
ins
f1
f2
f3
f4
f5
f6
f7
f8
f9
f10
f11
f12
space
tab
<any single character>
All supported OP
values:
Operation | Description |
---|---|
exit |
Quit watchbind. |
reload |
Reload the watched command manually, resets interval timer. |
cursor [down\|up] <N> |
Move cursor [down|up] N number of lines. |
cursor [first\|last] |
Move cursor to the [first|last] line. |
select |
Select line that cursor is currently on (i.e. add line that cursor is currently on to selected lines). |
unselect |
Unselect line that cursor is currently on. |
toggle-selection |
Toggle selection of line that cursor is currently on. |
select-all |
Select all lines. |
unselect-all |
Unselect all currently selected lines. |
exec -- <CMD> |
Execute CMD and block until termination. |
exec & -- <CMD> |
Execute CMD as background process, i.e. don't block until command terminates. |
exec tui -- <TUI-CMD> |
Execute a TUI-CMD that spawns a TUI (e.g. text editor). Watchbind's own TUI is replaced with TUI-CMD 's TUI until TUI-CMD terminates. Note that TUI-CMD must spawn a full-screen TUI that covers the entire terminal, otherwise undefined behaviour will ensue. |
set-env <ENV> -- <CMD> |
Blockingly execute CMD , and save its output to the environment variable ENV . |
unset-env <ENV> -- <CMD> |
Unsets environment variable ENV . |
help-[show\|hide\|toggle] |
[Show|Hide|Toggle the visibility of] the help menu. |
All CMD
and TUI-CMD
shell commands will be executed in a subshell (i.e. sh -c "CMD"
) that has some environment variables set.
The environment variable $line
is set to the line the cursor is on.
The environment variable $lines
set to all selected lines, or if none are selected, the line the cursor is currently on.
All set environment variables ENV
will be made available in all future spawned commands/processes, including the watched command, any executed subcommands, as well as commands executed in set-env
operations.
If multiple lines are selected, they will be separated by newlines in $lines
.
Foreground colors, background colors and boldness can be customized. These styling options are available for:
cursor-[fg|bg|boldness]
.header-[fg|bg|boldness]
.non-cursor-non-header-[fg|bg|boldness]
.selected-bg
.The names of the customization fields from the command-line options (e.g. --cursor-fg blue
) are the same in the TOML config file (e.g. cursor-fg = "blue"
).
Furthermore, watchbind
also supports styling according to ANSI codes in the input text.
All supported COLOR
values:
white
black
red
green
yellow
blue
magenta
cyan
gray
dark_gray
light_red
light_green
light_yellow
light_blue
light_magenta
light_cyan
reset # Reset the fg and bg
unspecified # Don't applying any styling => use style from ANSI input text
All supported BOLDNESS
values:
bold # Make everything bold
non-bold # Make sure nothing is bold (i.e. remove any bold styling from input ANSI)
unspecified # Don't applying any styling => use style from ANSI input text
watchbind
supports some extra formatting features reminiscent of the Unix cut
command:
Field Separators:
Define a separator/delimiter to segment your command's output into distinct fields.
Each separator will be replaced with an elastic tabstop, resulting in a "table"-like structure, similar to the cut -d <SEPARATOR> -t
command.
Field Selections:
Choose only specific fields to display.
You can specify a comma-separated list of the indexes (starting at index 1) for individual fields (X
), ranges (X-Y
), or the capture of all fields from X onwards (X-
).
For instance, the field selection 1,3-4,6-
will display the first, third and fourth fields, as well as all fields from the sixth onwards.
Important: The $lines
passed to the exec --
operations will remain unformatted, i.e. will not have the separators replaced with elastic tabstops and will not have non-selected fields ommitted.
The set-env
and unset-env
operations allow you to manage state through environment variables.
Additionally, you can use the initial-env
option to specify a list of set-env
commands that will be executed before the first execution of the watched command.
This powerful combination allows you to set some initial state with initial-env
, reference that state directly in the watched command, and update the state with keybindings at runtime with set-env
.
Watchbind supports a help menu that displays:
set-env
commands along with their values.--keybindings-help-menu-format
.I define "deleting input lines" as executing a keybinding that changes the length of the watched command's output. In other words: If, after executing a keybinding, the watched command generates an output longer or shorter than before the keybinding, then that keybinding deletes input lines.
Why is this definition important? Because the selected lines are only stored as indices and, therefore, have no association to the actual lines displayed in watchbind.
Here's an example that demonstrates what problems this can cause: You select five lines and then, through a keybinding, execute a command that deletes these five lines. The next time your watched command is executed, it will output five lines less (that are displayed in watchbind), since the five lines have been deleted. The problem is that the indices of the deleted lines will still be marked as selected. Therefore, five different lines, at the same indices as the deleted five lines, will now be selected, which is probably unwanted.
To solve this problem, the following keybinding format is recommended for keybindings that transform the input:
[keybindings]
"KEY" = [ "exec -- DELETE-OP", "reload", "unselect-all" ]
First, the selected lines are deleted using the DELETE-OP
(e.g. echo $lines | xargs rm
).
Then, we want to see the updated output of the watched command that doesn't contain the deleted lines anymore, so we reload
.
Finally, we want to remove our the selection of the now removed lines, so we call unselect-all
.
If you want to use pipes in your watched command on the command-line, make sure to escape the pipe symbol like so:
watchbind ls \| grep "test"
or put quotes around the watched command
watchbind "ls | grep test"
Otherwise, the shell will think you want to pipe the output of watchbind exec -- ls
to grep test
.
The commands you bind to keys will be executed in a subshell using sh -c
.
This means you can run a command like
watchbind --bind "enter:notify-send \$lines" ls
and the environment variable $lines
will contain the line the cursor is currently on.
But note that
watchbind --bind "enter:notify-send $lines" ls
will not work as expected, because $lines
will be replaced in the shell you are running the watchbind
command from.
Watchbind can enter blocking states when a blocking subcommand is executed.
The default behaviour in a blocking state is to not display any new output lines received from the watched command.
However, this behaviour can easily be customized with the --update-ui-while-blocking <BOOL>
option.