From 4227383e5c64c8c7b2a20c8f3ec553bd80cf2c8b Mon Sep 17 00:00:00 2001 From: Joakim Date: Mon, 12 Oct 2026 22:54:45 +0200 Subject: [PATCH] .tui --- CHANGELOG.md | 6 +++++ README.md | 59 ++++++++++++++++++++++------------------------ tui/example.tui | 10 ++++---- tui/tui-shell.bash | 42 +++++++++++++++++++++------------ tui/tui.py | 5 +++- 5 files changed, 69 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1802ea..0633cc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## v2 - latest fix + +- Fixed `Ctrl+C` exit handling so the Bash wrapper reliably changes to the directory where TUI was closed, including when Bash `set -e` is enabled. +- Action commands are evaluated as complete Bash commands, so `&&` and `||` chains work as they do directly in the terminal. +- Updated the example ZIP action to demonstrate success/failure prompts with `&&` / `||`. + # Changelog ## v2.1 diff --git a/README.md b/README.md index 2bf745e..65bfbd2 100644 --- a/README.md +++ b/README.md @@ -94,8 +94,7 @@ keys: actions: list-zip: command: - - unzip -l - - read -rp "Tryck Enter för att fortsätta..." + command: 'unzip -l && read -rp "Enter to continue.." || read -rp "FAILED - Enter to continue..."' move: command: mv -v @@ -165,44 +164,42 @@ not clear the already marked paths. 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. +executes them in the parent Bash process. -For example: +When TUI is closed with `Ctrl+C`, the wrapper changes the parent Bash shell to +the directory where TUI was actually closed. + +Action commands are handed to Bash as complete commands, so normal shell +operators such as `&&` and `||` work exactly as they do at the terminal. For +example: ```yaml actions: list-zip: + command: 'unzip -l && read -rp "Enter to continue.." || read -rp "FAILED - Enter to continue..."' +``` + +The complete command is evaluated once. If `unzip` succeeds, the success +`read` is executed. If `unzip` fails, the failure `read` is executed. + +For actions containing several separate commands, use a YAML list: + +```yaml +actions: + example: command: - - unzip -l - - read -rp "Tryck Enter för att fortsätta..." + - command-one + - command-two ``` -The flow is: +TUI is **not redrawn between commands belonging to the same action**. Each +command is inserted into Bash history. If a separate command fails, the +remaining commands in that action are not executed; TUI prints the exit code +and waits before returning to TUI. -```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. +The shell wrapper uses `pushd`/`popd` so action commands run in the directory +shown by TUI without permanently changing the user's Bash directory during +the action. ## Security note diff --git a/tui/example.tui b/tui/example.tui index 01be929..9c6e070 100644 --- a/tui/example.tui +++ b/tui/example.tui @@ -29,21 +29,19 @@ keys: - "*": move y: file: - - "*": delete-file + - "*": delete folder: - - "*": delete-folder + - "*": delete actions: list-zip: - command: unzip -l && read -rp "Enter to continue.." || read -rp "FAILED - Enter to continue..." + command: 'unzip -l && read -rp "Enter to continue.." || read -rp "FAILED - Enter to continue..."' copy: command: cp -v copy_ move: command: mv -v - delete-file: + delete: command: rm -v - delete-folder: - command: rmdir -v || read -rp "FAILED - Enter to continue..." edit: command: joe view: diff --git a/tui/tui-shell.bash b/tui/tui-shell.bash index 3c845ae..bf31f3f 100644 --- a/tui/tui-shell.bash +++ b/tui/tui-shell.bash @@ -33,16 +33,26 @@ tui() { : > "$command_file" : > "$cwd_file" + # Use an if/else wrapper so this remains safe when the caller has + # `set -e` enabled. A Ctrl+C from the child must still return control + # here so we can cd to the TUI's final directory. if (( show_all )); then - TUI_COMMAND_FILE="$command_file" \ - TUI_CWD_FILE="$cwd_file" \ - python3 "$tui_python" -a "$tui_dir" + if TUI_COMMAND_FILE="$command_file" \ + TUI_CWD_FILE="$cwd_file" \ + python3 "$tui_python" -a "$tui_dir"; then + rc=0 + else + rc=$? + fi else - TUI_COMMAND_FILE="$command_file" \ - TUI_CWD_FILE="$cwd_file" \ - python3 "$tui_python" "$tui_dir" + if TUI_COMMAND_FILE="$command_file" \ + TUI_CWD_FILE="$cwd_file" \ + python3 "$tui_python" "$tui_dir"; then + rc=0 + else + rc=$? + fi fi - rc=$? tui_cwd="$(<"$cwd_file")" @@ -56,16 +66,18 @@ tui() { return 1 fi - # A normal TUI exit means the user wants to leave TUI. Change the - # parent Bash shell to the directory where TUI was actually closed. + # Any exit other than SHELL_HANDOFF means TUI is finished. Always + # change the parent Bash shell to the directory where TUI was + # actually closed. This includes Ctrl+C (status 130). if (( rc != 42 )); then - if (( rc == 0 )); then - cd "$tui_cwd" || { - rm -f "$command_file" "$cwd_file" - return 1 - } - fi + cd "$tui_cwd" || { + rm -f "$command_file" "$cwd_file" + return 1 + } rm -f "$command_file" "$cwd_file" + if (( rc == 130 )); then + return 0 + fi return "$rc" fi diff --git a/tui/tui.py b/tui/tui.py index ae78f16..2880b7b 100755 --- a/tui/tui.py +++ b/tui/tui.py @@ -607,7 +607,10 @@ def main(): try: result = curses.wrapper(run, args.directory, args.all) except KeyboardInterrupt: - return 0 + # Keep a distinct status so the Bash wrapper can reliably distinguish + # Ctrl+C from a normal action handoff. The current directory has + # already been written by run()'s finally block. + return 130 return SHELL_HANDOFF if result == SHELL_HANDOFF else 0