236 lines
5.5 KiB
Markdown
236 lines
5.5 KiB
Markdown
# TUI
|
|
|
|
A small keyboard-driven terminal UI for browsing directories and executing
|
|
ordinary shell commands defined in `.tui` files.
|
|
|
|
The important design choice is that **TUI does not execute actions inside a
|
|
Python subprocess**. It hands the generated commands back to the Bash process
|
|
that started `tui`. This keeps the commands visible in Bash history and gives
|
|
actions the normal shell environment.
|
|
|
|
## Requirements
|
|
|
|
- Debian/Linux with Bash
|
|
- Python 3
|
|
- PyYAML (`python3-yaml` on Debian)
|
|
- `curses` from the normal Python installation
|
|
|
|
Install the Debian dependency if necessary:
|
|
|
|
```bash
|
|
sudo apt install python3-yaml
|
|
```
|
|
|
|
## Installation
|
|
|
|
Unpack the project and run:
|
|
|
|
```bash
|
|
./install.sh
|
|
```
|
|
|
|
The installer installs TUI under:
|
|
|
|
```text
|
|
~/.local/lib/tui/
|
|
```
|
|
|
|
and adds the shell integration to:
|
|
|
|
```text
|
|
~/.bashrc
|
|
```
|
|
|
|
The integration is therefore loaded automatically for interactive Bash
|
|
sessions, including the normal Debian login/terminal startup path.
|
|
|
|
After installation, start a new terminal/login session, or run:
|
|
|
|
```bash
|
|
source ~/.bashrc
|
|
```
|
|
|
|
Then:
|
|
|
|
```bash
|
|
tui
|
|
```
|
|
|
|
Use `-a` to include hidden files and directories:
|
|
|
|
```bash
|
|
tui -a
|
|
```
|
|
|
|
The installer does not overwrite an existing `~/.tui`.
|
|
|
|
`-a` / `--all` is a command-line option, so the shell integration preserves it
|
|
when the TUI restarts after an action.
|
|
|
|
## `.tui` configuration
|
|
|
|
TUI searches for the nearest `.tui` starting in the current directory and
|
|
walking upwards, but never above `$HOME`.
|
|
|
|
A configuration contains `keys` and `actions`:
|
|
|
|
```yaml
|
|
keys:
|
|
up: up
|
|
down: down
|
|
left: parent
|
|
right:
|
|
folder: enter
|
|
file:
|
|
- "*.zip": list-zip
|
|
|
|
ctrl-k:
|
|
b: mark-begin
|
|
k: mark-end
|
|
m:
|
|
file:
|
|
- "*": move
|
|
|
|
actions:
|
|
list-zip:
|
|
command:
|
|
- unzip -l <path>
|
|
- read -rp "Tryck Enter för att fortsätta..."
|
|
|
|
move:
|
|
command: mv -v <path> <dir>
|
|
```
|
|
|
|
Rules are evaluated from top to bottom; the first matching glob wins.
|
|
|
|
### Substitutions
|
|
|
|
| Token | Meaning |
|
|
|---|---|
|
|
| `<name>` | basename of the target |
|
|
| `<path>` | full path of the target |
|
|
| `<dir>` | directory currently displayed by TUI, with trailing `/` |
|
|
|
|
## Navigation
|
|
|
|
The default example configuration uses:
|
|
|
|
- `↑` / `↓` — move cursor
|
|
- `Enter` / `→` — execute the configured action
|
|
- `../` — synthetic parent-directory entry at the top of every listing
|
|
- `←` — move the cursor to `../`; pressing `←` again enters the parent directory
|
|
- `Enter` on `../` — enter the parent directory
|
|
- navigation cannot move above the directory containing the applicable `.tui` file
|
|
- hidden files and directories are hidden by default; `tui -a` shows them
|
|
- file and directory colors follow the usual GNU `ls --color=yes` conventions for common file types
|
|
- `Ctrl-K B` — set selection start
|
|
- `Ctrl-K K` — set selection end
|
|
- `Ctrl-K C` — copy marked files/folders
|
|
- `Ctrl-K M` — move marked files
|
|
- `Ctrl-K Y` — delete marked files/folders
|
|
- `Ctrl-C` — quit
|
|
|
|
## Marking
|
|
|
|
Marks are stored as absolute paths and belong to the TUI session, not to the
|
|
current directory.
|
|
|
|
For example:
|
|
|
|
```text
|
|
/home/joakim/notes/
|
|
A.txt *
|
|
B.txt *
|
|
work/
|
|
```
|
|
|
|
Entering `work/` does not clear the marks. Returning to the parent directory
|
|
shows `A.txt` and `B.txt` as marked again.
|
|
|
|
Marks in different directories can coexist. If `A.txt` is marked, then TUI
|
|
is moved to `work/`, `Ctrl-K M` operates on the original absolute path while
|
|
using the **current directory** as the destination:
|
|
|
|
```bash
|
|
mv -v /home/joakim/notes/A.txt .
|
|
```
|
|
|
|
A mark is removed automatically only when its target no longer exists.
|
|
|
|
`Ctrl-K B` and `Ctrl-K K` define a contiguous selection in the directory being
|
|
viewed. Changing directory clears the temporary selection endpoints but does
|
|
not clear the already marked paths.
|
|
|
|
## Shell integration
|
|
|
|
When an action is triggered, TUI writes the generated commands to a temporary
|
|
file and exits with a private handoff status. The `tui()` Bash function then
|
|
executes them.
|
|
|
|
For example:
|
|
|
|
```yaml
|
|
actions:
|
|
list-zip:
|
|
command:
|
|
- unzip -l <path>
|
|
- read -rp "Tryck Enter för att fortsätta..."
|
|
```
|
|
|
|
The flow is:
|
|
|
|
```text
|
|
TUI
|
|
↓
|
|
unzip -l archive.zip
|
|
↓
|
|
read -rp "Tryck Enter..."
|
|
↓
|
|
TUI
|
|
```
|
|
|
|
TUI is **not redrawn between commands belonging to the same action**.
|
|
|
|
Every command is also inserted into Bash history. If one command fails, the
|
|
remaining commands in that action are not executed. TUI prints the exit code
|
|
and waits for:
|
|
|
|
```text
|
|
Tryck Enter för att fortsätta...
|
|
```
|
|
|
|
before returning to TUI, so an error cannot disappear immediately.
|
|
|
|
The shell wrapper uses `pushd`/`popd` so the command runs in the directory
|
|
shown by TUI without permanently changing the user's Bash working directory.
|
|
|
|
## Security note
|
|
|
|
Action strings are intentionally ordinary Bash commands and are executed with
|
|
`eval` by the user's Bash shell. `.tui` files should therefore be treated as
|
|
executable configuration and only used from directories/configurations you
|
|
trust.
|
|
|
|
## Development
|
|
|
|
Run a syntax check with:
|
|
|
|
```bash
|
|
python3 -m py_compile tui/tui.py
|
|
```
|
|
|
|
For development without installing:
|
|
|
|
```bash
|
|
source tui/tui-shell.bash
|
|
tui
|
|
```
|
|
|
|
The example configuration is in `tui/example.tui`.
|
|
|
|
### Cursor and block selection
|
|
|
|
There is an empty cursor position immediately below the last object in a directory. This makes range selection behave like a text editor.
|
|
|
|
`Ctrl+K K` follows joe-style semantics: the end of the selection is the row immediately above the cursor. Thus, when the cursor is on the empty line below the final object, `Ctrl+K K` includes the final object.
|