hwatch
======

hwatch - alternative watch command.

<p align="center">
<img src="./img/hwatch.gif" />
</p>

## Description

`hwatch` is a alternative **watch** command.
That records the result of command execution and can display it history and diffs.

### Features

- Can keep the history when the difference, occurs and check it later.
- Can check the difference in the history. The display method can be changed in real time.
- Can output the execution result as log (json format).
- Can load diffmode plugins as dynamic libraries and add custom diff rendering.
- Custom keymaps are available.
- Support ANSI color code.
- Execution result can be scroll.
- Not only as a TUI application, but also to have the differences output as standard output.
- If a difference occurs, you can have the specified command additionally executed.

## Install

### macOS

```bash
# brew
brew install hwatch

# MacPorts
sudo port install hwatch
```

### Arch Linux (AUR)

```bash
# paru
paru -S hwatch

# yay
yay -S hwatch
```

### Nix

```bash
nix profile install nixpkgs#hwatch
```

### Alpine Linux (edge/testing)

```bash
apk add hwatch
```

### mise/asdf

```bash
# mise
mise use -g aqua:blacknon/hwatch

# asdf
asdf plugin add hwatch
asdf install hwatch latest
asdf global hwatch latest
```

### Cargo Install

```bash
cargo install hwatch
```

### Logfile Reuse

When `--logfile` points to an existing file, `hwatch` tries to read and reuse its history.
If the file is empty or unreadable, interactive sessions ask for confirmation before continuing.

For non-interactive runs such as CI, scripts, and batch mode, use
`--force-logfile-overwrite` to skip that confirmation and continue with the existing path:

```bash
hwatch --force-logfile-overwrite --logfile ./hwatch.jsonl -b -g 1 -n 0.1 sh ./script.sh
```

### Security Notes

- `--diff-plugin` loads native dynamic libraries. Only load plugins you trust.
- `--shell`, the monitored command itself, and `--aftercommand` execute commands on your system. Treat those values as trusted input only.

## Usage

### Command

```shell
$ hwatch --help
A modern alternative to the watch command, records the differences in execution results and can check this differences at after.

Usage: hwatch [OPTIONS] [command]...

Arguments:
  [command]...

Options:
  -b, --batch
          output execution results to stdout
  -B, --beep
          beep if command has a change result
  -g, --chgexit [<chgexit>]
          exit when output changes. With no value, exits after the first change; with N, exits after N changes
      --border
          Surround each pane with a border frame
      --with-scrollbar
          When the border option is enabled, display scrollbar on the right side of watch pane.
      --mouse
          enable mouse wheel support. With this option, copying text with your terminal may be harder. Try holding the Shift key.
  -c, --color
          interpret ANSI color and style sequences
  -r, --reverse
          display text upside down.
  -C, --compress
          Compress data in memory. Note: If the output of the command is small, you may not get the desired effect.
  -t, --no-title
          hide the UI on start. Use `t` to toggle it.
      --enable-summary-char
          collect character-level diff count in summary.
  -N, --line-number
          show line number
  -w, --wrap
          disable line wrap mode
      --no-help-banner
          hide the "Display help with h key" message
      --no-summary
          disable the calculation for summary that is running behind the scenes, and disable the summary function in the first place.
      --completion <SHELL>
          Output shell completion script [possible values: bash, fish, zsh]
  -x, --exec
          Run the command directly, not through the shell. Much like the `-x` option of the watch command.
  -p, --use-pty
          Run the command through a pseudo-TTY so commands that colorize on terminals can keep color output.
  -O, --diff-output-only
          Display only the lines with differences during `line` diff and `word` diff.
      --ignore-spaceblock
          Ignore diffs where only consecutive whitespace blocks differ.
  -A, --aftercommand <after_command>
          Executes the specified command if the output changes. Information about changes is stored in json format in environment variable ${HWATCH_DATA}.
      --after-command-result-write-file
          Passes `${HWATCH_DATA}` to `aftercommand` as a temporary file path instead of inline json data.
  -l, --logfile [<logfile>]
          logging file. if a log file is already used, its contents will be read and executed.
      --force-logfile-overwrite
          continue even if an existing logfile is empty or unreadable
  -s, --shell <shell_command>
          shell to use at runtime. can also insert the command to the location specified by {COMMAND}. [default: "sh -c"]
  -n, --interval <interval>
          seconds to wait between updates [default: 2]
      --precise
          Attempt to run as close to the interval as possible, regardless of how long the command takes to run
  -L, --limit <limit>
          Set the number of history records to keep. only work in watch mode. Set `0` for unlimited recording. [default: 5000]
      --tab-size <tab_size>
          Specifying tab display size [default: 4]
      --diff-plugin <diff_plugin>
          Load a diffmode plugin dynamic library.
  -d, --differences [<differences>]
          highlight changes between updates
  -o, --output [<output>]
          Select command output. [default: output] [possible values: output, stdout, stderr]
  -K, --keymap <keymap>
          Add keymap
  -h, --help
          Print help
  -V, --version
          Print version
```

### Keybind

Watch mode keybind(Default).

| Key                                                                                        | Action                                                      |
| ------------------------------------------------------------------------------------------ | ----------------------------------------------------------- |
| <kbd>↑</kbd>, <kbd>↓</kbd>                                                               | move selected screen(history/watch).                        |
| <kbd>pageup</kbd>, <kbd>pagedn</kbd>                                                       | move selected screen(history/watch).                        |
| <kbd>home</kbd>, <kbd>end</kbd>                                                            | move selected screen(history/watch).                        |
| <kbd>Tab</kbd>                                                                             | toggle select screen(history/watch).                        |
| <kbd>←</kbd>                                                                              | select watch screen.                                        |
| <kbd>→</kbd>                                                                              | select history screen.                                      |
| <kbd>Alt</kbd>+<kbd>←</kbd>, <kbd>Alt</kbd>+<kbd>→</kbd>                                 | Watch window scrll left/right.                              |
| <kbd>Shift</kbd>+<kbd>Alt</kbd>+<kbd>←</kbd>, <kbd>Shift</kbd>+<kbd>Alt</kbd>+<kbd>→</kbd> | Watch window scrll start/end.                               |
| <kbd>Q</kbd>                                                                               | exit hwatch.                                                |
| <kbd>Esc</kbd>                                                                             | unfiltering.                                                |
| <kbd>Shift</kbd>+<kbd>D</kbd>                                                              | delete selected history.                                    |
| <kbd>Shift</kbd>+<kbd>X</kbd>                                                              | clear all history except selected history.                  |
| <kbd>Ctrl</kbd>+<kbd>c</kbd>                                                               | cancel.                                                     |
| <kbd>H</kbd>                                                                               | show help window.                                           |
| <kbd>B</kbd>                                                                               | toggle enable/disable border.                               |
| <kbd>C</kbd>                                                                               | toggle color.                                               |
| <kbd>N</kbd>                                                                               | switch line number display.                                 |
| <kbd>R</kbd>                                                                               | toggle reverse mode.                                        |
| <kbd>M</kbd>                                                                               | toggle mouse support.                                       |
| <kbd>T</kbd>                                                                               | toggle the UI (history pane and header).                    |
| <kbd>Backspace</kbd>                                                                       | toggle the history pane.                                    |
| <kbd>D</kbd>                                                                               | switch diff mode.                                           |
| <kbd>0</kbd>                                                                               | disable diff.                                               |
| <kbd>1</kbd>                                                                               | switch watch type diff.                                     |
| <kbd>2</kbd>                                                                               | switch line type diff.                                      |
| <kbd>3</kbd>                                                                               | switch word type diff.                                      |
| <kbd>Shift</kbd>+<kbd>O</kbd>                                                              | show only lines with differences(line/word diff mode only). |
| <kbd>O</kbd>                                                                               | switch output mode(output->stdout->stderr).                 |
| <kbd>W</kbd>                                                                               | Toggle wrap.                                                |
| <kbd>F1</kbd>                                                                              | only stdout print.                                          |
| <kbd>F2</kbd>                                                                              | only stderr print.                                          |
| <kbd>F3</kbd>                                                                              | print output.                                               |
| <kbd>Ctrl</kbd>+<kbd>N</kbd>                                                               | Forcus next keyword.                                        |
| <kbd>Ctrl</kbd>+<kbd>P</kbd>                                                               | Forcus before keyword.                                      |
| <kbd>Shift</kbd>+<kbd>S</kbd>                                                              | show summary infomation in history.                         |
| <kbd>+</kbd>                                                                               | increase interval.                                          |
| <kbd>-</kbd>                                                                               | decrease interval.                                          |
| <kbd>P</kbd>                                                                               | Pause/unpause execution.                                    |
| <kbd>/</kbd>                                                                               | filter history by string.                                   |
| <kbd>*</kbd>                                                                               | filter history by regex.                                    |

#### Custom keybind

Can customize key bindings by using the `-K` Option.
Write it in the format `keybind=funciton`.

```bash
hwatch -K ctrl-p=history_pane_up -K ctrl-n=history_pane_down command...
```

Keybind functions that can be specified are as follows.

| function                 | description                                |
| ------------------------ | ------------------------------------------ |
| up                       | Move up                                    |
| watch_pane_up            | Move up in watch pane                      |
| history_pane_up          | Move up in history pane                    |
| down                     | Move down                                  |
| watch_pane_down          | Move down in watch pane                    |
| history_pane_down        | Move down in history pane                  |
| scroll_right             | Move page right in watch pane              |
| scroll_horizontal_end    | Move page right end in watch pane          |
| scroll_left              | Move page left in watch pane               |
| scroll_horizontal_home   | Move page left start in watch pane         |
| page_up                  | Move page up                               |
| watch_pane_page_up       | Move page up in watch pane                 |
| history_pane_page_up     | Move page up in history pane               |
| page_down                | Move page down                             |
| watch_pane_page_down     | Move page down in watch pane               |
| history_pane_page_down   | Move page down in history pane             |
| move_top                 | Move top                                   |
| watch_pane_move_top      | Move top in watch pane                     |
| history_pane_move_top    | Move top in history pane                   |
| move_end                 | Move end                                   |
| watch_pane_move_end      | Move end in watch pane                     |
| history_pane_move_end    | Move end in history pane                   |
| toggle_focus             | Toggle focus window                        |
| focus_watch_pane         | Focus watch pane                           |
| focus_history_pane       | Focus history pane                         |
| quit                     | Quit hwatch                                |
| reset                    | filter reset                               |
| delete                   | Delete selected history                     |
| clear_except_selected    | Clear all history except selected history   |
| cancel                   | Cancel                                     |
| force_cancel             | Cancel without displaying the exit dialog  |
| help                     | Show and hide help window                  |
| toggle_color             | Toggle enable/disable ANSI Color           |
| toggle_line_number       | Toggle enable/disable Line Number          |
| toggle_reverse           | Toggle enable/disable text reverse         |
| toggle_mouse_support     | Toggle enable/disable mouse support        |
| toggle_view_pane_ui      | Toggle view header/history pane            |
| toggle_view_header_pane  | Toggle view header pane                    |
| toggle_view_history_pane | Toggle view history pane                   |
| toggle_border            | Toggle enable/disable border               |
| toggle_scroll_bar        | Toggle enable/disable scroll bar           |
| toggle_diff_mode         | Toggle diff mode                           |
| set_diff_mode_plane      | Set diff mode plane                        |
| set_diff_mode_watch      | Set diff mode watch                        |
| set_diff_mode_line       | Set diff mode line                         |
| set_diff_mode_word       | Set diff mode word                         |
| set_diff_only            | Set diff line only (line/word diff only)   |
| toggle_output_mode       | Toggle output mode                         |
| set_output_mode_output   | Set output mode output                     |
| set_output_mode_stdout   | Set output mode stdout                     |
| set_output_mode_stderr   | Set output mode stderr                     |
| toggle_wrap_mode         | Toggle watch pane text wrap enable/disable |
| toggle_history_summary   | Toggle history summary                     |
| interval_plus            | Interval +0.5sec                           |
| interval_minus           | Interval -0.5sec                           |
| toggle_pause             | Toggle pause execution                     |
| prev_keyword             | Focus previous keyword                     |
| next_keyword             | Focus next keyword                         |
| change_filter_mode       | Change filter mode                         |
| change_regex_filter_mode | Change regex filter mode                   |


## Configuration

### Set configuration in enviroment

If you always want to use some command-line options, you can set them in the
`HWATCH` environment variable. For example, if you use `bash`, you can add
the following to your `.bashrc`:

```bash
export HWATCH="--no-title --color --no-help-banner --border --with-scrollbar"
```

### Set color configuration in enviroment

You can also customize the watch diff highlight colors with environment variables.

- `HWATCH_WATCH_FG`: foreground color for watch diff highlights
- `HWATCH_WATCH_BG`: background color for watch diff highlights

Accepted values are ANSI color names such as `red`, `blue`, `lightcyan`, color indexes `0-255`,
hex colors like `#RRGGBB`, or RGB values like `255,0,0`.

## Example

### interval 3 second

Use the -n option to specify the command execution interval.

```bash
hwatch -n 3 command...
```

### exit when output changes

Use `-g` to exit after the first detected change, like `watch -g`.

```bash
hwatch -g command...
```

Use `-g N` to exit after `N` detected changes.

```bash
hwatch -g 3 command...
```

### logging output

The command execution result can be output as a log in json format.

```bash
hwatch -n 3 -l hwatch_log.json command...
```

When you check the json log, you can easily check it by using [this script](https://gist.github.com/blacknon/551e52dce1651d2510162def5a0da1f0).

### Use shell function

If you want the shell function to be executed periodically, you can specify the shell command to be executed with -s as follows.

```bash
# bash
hwatch -n 3 -s 'bash -c "source ~/.bashrc; {COMMAND}"' command...

# zsh
hwatch -n 3 -s 'zsh -c "source ~/.zshrc; {COMMAND}"' command...
```

### ANSI Color code

If you want to see output colored with ANSI color code, enable color mode.

To enable color mode, run hwatch with the `-c` option.
Alternatively, you can enable / disable the color mode with the <kbd>C</kbd> key during execution.

```bash
hwatch -n 3 -c command...
```

### diff view

To enable color mode, run hwatch with the `-d` option.

There are several "diff modes" available.
Switching can be done with the <kbd>D</kbd> key.

```bash
hwatch -n 3 -d command...
```

#### watch diff

<p align="center">
<img src="./img/watch_diff.gif" />
</p>

#### line diff

<p align="center">
<img src="./img/line_diff.gif" />
</p>

#### word diff

<p align="center">
<img src="./img/word_diff.gif" />
</p>

### history filtering

You can filter history as a string with <kbd>/</kbd> key and as a regular expression with <kbd>*</kbd> key.

### run batch mode

You can have command diffs output directly to stdout instead with `-b` option of getting them as a TUI app.

```bash
hwatch -b command...
```

### Plugins

You can load diffmode plugins as dynamic libraries with `--diff-plugin`.
This allows you to add custom diff rendering for command output.

#### numeric-diff example

<p align="center">
<img src="./img/plugin.gif" />
</p>

The bundled `numeric-diff` plugin highlights numeric changes when the
surrounding text stays the same. Built-in plugins use the core default gutter
for line numbers and can optionally override gutter text or style when they
need a custom prefix.

Build the plugin:

```bash
cargo build --manifest-path plugins/numeric-diff/Cargo.toml --release
```

Then load it with `--diff-plugin`:

```bash
hwatch --diff-plugin plugins/numeric-diff/target/release/libhwatch_plugin_numeric_diff.dylib -d line "seq 3 | awk '{ print \"value=\" $1 * int(rand() * 10) }'"
```

## Alternatives

- The original [`watch`](https://man7.org/linux/man-pages/man1/watch.1.html);
  the newest version seems to be distributed as a part of
  [`procps`](https://gitlab.com/procps-ng/procps).
- [Viddy](https://github.com/sachaos/viddy).
- [sasqwatch](https://github.com/fabio42/sasqwatch)
