This commit is contained in:
Joakim
2026-10-13 12:13:00 +02:00
parent f9c6b2923b
commit 26dcf49f41
3 changed files with 138 additions and 28 deletions

View File

@@ -1,5 +1,16 @@
# Changelog # Changelog
## 3.2.0
- Floating menu is anchored near the current cursor item instead of a fixed screen position.
- Added configurable archive, file, folder, shell and file-information menu actions.
- Added Pack as ZIP and Pack as tar.gz.
- Added Extract archive here for common archive formats.
- Added Rename, New folder, New file, Open shell here, SHA-256 and File information.
- Added generic `archive` menu condition.
- All menu entries remain configurable in `.tui`.
## 3.1.0 ## 3.1.0
- Added floating action menu rendered over the browser without clearing the underlying file list. - Added floating action menu rendered over the browser without clearing the underlying file list.

View File

@@ -1,5 +1,4 @@
# TUI configuration. The Python program only provides the generic UI engine; # TUI configuration. UI behavior, labels, shortcuts and actions belong here.
# actions, labels, shortcuts and context-specific menu entries live here.
keys: keys:
up: up up: up
@@ -13,7 +12,6 @@ keys:
folder: enter folder: enter
file: file:
- "*.zip": list-zip - "*.zip": list-zip
- "*.md": view
enter: enter:
folder: enter folder: enter
@@ -39,14 +37,6 @@ keys:
folder: folder:
- "*": delete - "*": delete
# M opens this menu. The menu is entirely data-driven.
#
# selection: auto -> marked objects if there are marks, otherwise current object
# selection: current -> always current object
# selection: marked -> only when there is a marked selection
# selection: none -> action runs in the current directory, without a target
#
# when: git means this directory is inside a Git worktree.
menu: menu:
- label: "View" - label: "View"
key: v key: v
@@ -58,6 +48,13 @@ menu:
action: edit action: edit
when: file when: file
- label: "List archive"
key: l
action: list-zip
when:
- file
- glob:*.zip
- separator: true - separator: true
- label: "Copy" - label: "Copy"
@@ -75,6 +72,56 @@ menu:
action: delete action: delete
selection: auto selection: auto
- label: "Rename"
key: r
action: rename
selection: current
- separator: true
- label: "Pack as ZIP"
key: z
action: pack-zip
selection: auto
- label: "Pack as tar.gz"
key: t
action: pack-targz
selection: auto
- label: "Extract archive here"
key: x
action: extract
when: archive
selection: current
- separator: true
- label: "New folder"
key: n
action: new-folder
selection: none
- label: "New file"
key: f
action: new-file
selection: none
- label: "Open shell here"
key: s
action: shell
selection: none
- label: "SHA-256"
key: h
action: sha256
selection: auto
- label: "File information"
key: i
action: info
selection: auto
- separator: true - separator: true
- label: "Git: Status" - label: "Git: Status"
@@ -88,6 +135,11 @@ menu:
selection: none selection: none
when: git when: git
- label: "Git: Add -A"
action: git-add
selection: none
when: git
- label: "Git: Diff" - label: "Git: Diff"
action: git-diff action: git-diff
selection: none selection: none
@@ -98,11 +150,6 @@ menu:
selection: none selection: none
when: git when: git
- label: "Git: Add -A"
action: git-add
selection: none
when: git
- label: "Git: Commit" - label: "Git: Commit"
action: git-commit action: git-commit
selection: none selection: none
@@ -120,21 +167,54 @@ actions:
- read -rp "Press Enter to continue..." - read -rp "Press Enter to continue..."
copy: copy:
command: cp -v <path> <dir>copy_<name> command: cp -v -- <path> <dir>copy_<name>
move: move:
command: mv -v <path> <dir> command: mv -v -- <path> <dir>
delete: delete:
command: rm -v <path> command: rm -v -- <path>
edit: rename:
command: joe <path> command: 'read -rp "New name for <name>: " new_name && mv -v -- <path> "<dir>$new_name"'
view: view:
command: less <path> command: less <path>
# target: none means that the action runs once in the current TUI folder. edit:
command: joe <path>
pack-zip:
command: 'zip -r "<dir><name>.zip" <path>'
pack-targz:
command: 'tar -czvf "<dir><name>.tar.gz" <name>'
extract:
command:
- 'case "<path>" in *.zip) unzip -o -- <path> -d <dir> ;; *.tar.gz|*.tgz) tar -xzvf <path> -C <dir> ;; *.tar.bz2|*.tbz2) tar -xjvf <path> -C <dir> ;; *.tar.xz|*.txz) tar -xJvf <path> -C <dir> ;; *.tar) tar -xvf <path> -C <dir> ;; *.7z) 7z x <path> -o<dir> ;; *.rar) unrar x <path> <dir> ;; *) echo "Unsupported archive: <path>"; false ;; esac'
- read -rp "Press Enter to continue..."
new-folder:
target: none
command: 'read -rp "Folder name: " name && mkdir -pv -- "$name"'
new-file:
target: none
command: 'read -rp "File name: " name && touch -- "$name"'
shell:
target: none
command: 'bash'
sha256:
command: sha256sum <path>
info:
command:
- 'stat -- <path>'
- read -rp "Press Enter to continue..."
git-status: git-status:
target: none target: none
command: git status command: git status
@@ -143,6 +223,10 @@ actions:
target: none target: none
command: git pull command: git pull
git-add:
target: none
command: git add -A
git-diff: git-diff:
target: none target: none
command: git diff command: git diff
@@ -151,10 +235,6 @@ actions:
target: none target: none
command: git log --oneline --decorate -20 command: git log --oneline --decorate -20
git-add:
target: none
command: git add -A
git-commit: git-commit:
target: none target: none
command: git commit command: git commit

View File

@@ -6,7 +6,7 @@ import os
import subprocess import subprocess
from pathlib import Path from pathlib import Path
TUI_VERSION = "3.1.0" TUI_VERSION = "3.2.0"
SHELL_HANDOFF = 42 SHELL_HANDOFF = 42
TUI_EXIT = 43 TUI_EXIT = 43
@@ -146,6 +146,14 @@ class TuiConfig:
if condition == "folder": if condition == "folder":
target = browser.selected() target = browser.selected()
return bool(target and target.is_dir()) return bool(target and target.is_dir())
if condition == "archive":
target = browser.selected()
if not target or not target.is_file():
return False
return target.suffix.lower() in {
".zip", ".tar", ".gz", ".tgz", ".bz2", ".tbz2",
".xz", ".txz", ".7z", ".rar",
} or target.name.lower().endswith((".tar.gz", ".tar.bz2", ".tar.xz"))
if condition == "marked": if condition == "marked":
return bool(browser.marked) return bool(browser.marked)
if condition == "multiple": if condition == "multiple":
@@ -579,7 +587,18 @@ def show_menu(stdscr, browser):
menu_top = max(1, cursor_screen_row - menu_rows - 1) menu_top = max(1, cursor_screen_row - menu_rows - 1)
if menu_top + menu_rows > height - 1: if menu_top + menu_rows > height - 1:
menu_top = max(1, height - 1 - menu_rows) menu_top = max(1, height - 1 - menu_rows)
menu_left = max(1, min((width - menu_width) // 2, width - menu_width - 1)) # Anchor the floating menu near the current item rather than at a
# fixed screen position. The current row remains visible because the
# menu starts after the item's text when there is enough room.
if browser.cursor == 0:
cursor_text = "../" if browser.path != browser.root else "./"
elif 1 <= browser.cursor <= len(browser.entries):
current = browser.entries[browser.cursor - 1]
cursor_text = current.name + ("/" if current.is_dir() else "")
else:
cursor_text = ""
preferred_left = min(max(2, len(cursor_text) + 3), max(2, width - menu_width - 2))
menu_left = max(1, min(preferred_left, width - menu_width - 1))
win = curses.newwin(menu_rows, menu_width, menu_top, menu_left) win = curses.newwin(menu_rows, menu_width, menu_top, menu_left)
win.keypad(True) win.keypad(True)