menyer
This commit is contained in:
10
CHANGELOG.md
10
CHANGELOG.md
@@ -30,3 +30,13 @@
|
|||||||
- Fixed Ctrl+C exit so the parent Bash process changes to the directory where TUI was closed.
|
- Fixed Ctrl+C exit so the parent Bash process changes to the directory where TUI was closed.
|
||||||
- TUI now uses a separate exit status for normal Ctrl+C exit versus shell-action handoff.
|
- TUI now uses a separate exit status for normal Ctrl+C exit versus shell-action handoff.
|
||||||
- Shell actions are read into an array before execution, so commands containing `read`, `&&`, and `||` receive the real terminal stdin and behave like normal Bash commands.
|
- Shell actions are read into an array before execution, so commands containing `read`, `&&`, and `||` receive the real terminal stdin and behave like normal Bash commands.
|
||||||
|
|
||||||
|
## 3.0.0
|
||||||
|
|
||||||
|
- Added data-driven action menu with `M`.
|
||||||
|
- Added `A` toggle for hidden files and `Q` to quit; these are defined in `.tui` as normal key bindings.
|
||||||
|
- Menu entries, labels, shortcuts, conditions and actions are defined in `.tui`.
|
||||||
|
- Added generic menu conditions including `git`, `file`, `folder`, `marked`, `multiple`, `exists:...`, `missing:...` and boolean `all`/`any`/`not` expressions.
|
||||||
|
- Added `target: none` actions for directory-level operations that do not operate on a selected file.
|
||||||
|
- Added Git menu examples: status, pull, diff, log, add, commit and push.
|
||||||
|
- `A` state is preserved when returning from a shell action.
|
||||||
|
|||||||
41
README.md
41
README.md
@@ -232,3 +232,44 @@ The example configuration is in `tui/example.tui`.
|
|||||||
|
|
||||||
Use `tui -v` or `tui --version` to verify the installed TUI version.
|
Use `tui -v` or `tui --version` to verify the installed TUI version.
|
||||||
|
|
||||||
|
|
||||||
|
## v3 menu
|
||||||
|
|
||||||
|
Press `M` to open the action menu. The menu is generated entirely from the current `.tui` file, so a directory can provide its own context-specific operations.
|
||||||
|
|
||||||
|
The example configuration defines ordinary file operations and Git operations that appear when the current directory is inside a Git worktree.
|
||||||
|
|
||||||
|
The top-level keys are also configurable in `.tui`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
keys:
|
||||||
|
a: toggle-all
|
||||||
|
q: quit
|
||||||
|
m: menu
|
||||||
|
```
|
||||||
|
|
||||||
|
Menu entries support:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
menu:
|
||||||
|
- label: "Git: pull"
|
||||||
|
key: p
|
||||||
|
action: git-pull
|
||||||
|
selection: none
|
||||||
|
when: git
|
||||||
|
```
|
||||||
|
|
||||||
|
`selection` can be `auto`, `current`, `marked` or `none`.
|
||||||
|
|
||||||
|
Available generic `when` conditions include `always`, `git`, `not-git`, `file`, `folder`, `marked`, `multiple`, `exists:<path>`, `missing:<path>`, `glob:<pattern>`, plus `all`, `any` and `not` expressions.
|
||||||
|
|
||||||
|
Actions that operate on the current directory rather than a file can use:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
actions:
|
||||||
|
git-pull:
|
||||||
|
target: none
|
||||||
|
command: git pull
|
||||||
|
```
|
||||||
|
|
||||||
|
This keeps the Python code generic: menu presentation, labels, shortcuts, conditions and shell commands belong in `.tui`.
|
||||||
|
|||||||
114
tui/example.tui
114
tui/example.tui
@@ -1,7 +1,13 @@
|
|||||||
|
# TUI configuration. The Python program only provides the generic UI engine;
|
||||||
|
# actions, labels, shortcuts and context-specific menu entries live here.
|
||||||
|
|
||||||
keys:
|
keys:
|
||||||
up: up
|
up: up
|
||||||
down: down
|
down: down
|
||||||
left: parent
|
left: parent
|
||||||
|
a: toggle-all
|
||||||
|
q: quit
|
||||||
|
m: menu
|
||||||
|
|
||||||
right:
|
right:
|
||||||
folder: enter
|
folder: enter
|
||||||
@@ -33,18 +39,126 @@ 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:
|
||||||
|
- label: "Visa"
|
||||||
|
key: v
|
||||||
|
action: view
|
||||||
|
when: file
|
||||||
|
|
||||||
|
- label: "Redigera"
|
||||||
|
key: e
|
||||||
|
action: edit
|
||||||
|
when: file
|
||||||
|
|
||||||
|
- separator: true
|
||||||
|
|
||||||
|
- label: "Kopiera"
|
||||||
|
key: c
|
||||||
|
action: copy
|
||||||
|
selection: auto
|
||||||
|
|
||||||
|
- label: "Flytta"
|
||||||
|
key: m
|
||||||
|
action: move
|
||||||
|
selection: auto
|
||||||
|
|
||||||
|
- label: "Ta bort"
|
||||||
|
key: d
|
||||||
|
action: delete
|
||||||
|
selection: auto
|
||||||
|
|
||||||
|
- separator: true
|
||||||
|
|
||||||
|
- label: "Git: status"
|
||||||
|
action: git-status
|
||||||
|
selection: none
|
||||||
|
when: git
|
||||||
|
|
||||||
|
- label: "Git: pull"
|
||||||
|
key: p
|
||||||
|
action: git-pull
|
||||||
|
selection: none
|
||||||
|
when: git
|
||||||
|
|
||||||
|
- label: "Git: diff"
|
||||||
|
action: git-diff
|
||||||
|
selection: none
|
||||||
|
when: git
|
||||||
|
|
||||||
|
- label: "Git: log"
|
||||||
|
action: git-log
|
||||||
|
selection: none
|
||||||
|
when: git
|
||||||
|
|
||||||
|
- label: "Git: add -A"
|
||||||
|
action: git-add
|
||||||
|
selection: none
|
||||||
|
when: git
|
||||||
|
|
||||||
|
- label: "Git: commit"
|
||||||
|
action: git-commit
|
||||||
|
selection: none
|
||||||
|
when: git
|
||||||
|
|
||||||
|
- label: "Git: push"
|
||||||
|
action: git-push
|
||||||
|
selection: none
|
||||||
|
when: git
|
||||||
|
|
||||||
actions:
|
actions:
|
||||||
list-zip:
|
list-zip:
|
||||||
command:
|
command:
|
||||||
- unzip -l <path>
|
- unzip -l <path>
|
||||||
- read -rp "Tryck Enter för att fortsätta..."
|
- read -rp "Tryck Enter för att fortsätta..."
|
||||||
|
|
||||||
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:
|
edit:
|
||||||
command: joe <path>
|
command: joe <path>
|
||||||
|
|
||||||
view:
|
view:
|
||||||
command: less <path>
|
command: less <path>
|
||||||
|
|
||||||
|
# target: none means that the action runs once in the current TUI folder.
|
||||||
|
git-status:
|
||||||
|
target: none
|
||||||
|
command: git status
|
||||||
|
|
||||||
|
git-pull:
|
||||||
|
target: none
|
||||||
|
command: git pull
|
||||||
|
|
||||||
|
git-diff:
|
||||||
|
target: none
|
||||||
|
command: git diff
|
||||||
|
|
||||||
|
git-log:
|
||||||
|
target: none
|
||||||
|
command: git log --oneline --decorate -20
|
||||||
|
|
||||||
|
git-add:
|
||||||
|
target: none
|
||||||
|
command: git add -A
|
||||||
|
|
||||||
|
git-commit:
|
||||||
|
target: none
|
||||||
|
command: git commit
|
||||||
|
|
||||||
|
git-push:
|
||||||
|
target: none
|
||||||
|
command: git push
|
||||||
|
|||||||
@@ -23,52 +23,65 @@ tui() {
|
|||||||
done
|
done
|
||||||
local command_file
|
local command_file
|
||||||
local cwd_file
|
local cwd_file
|
||||||
|
local all_file
|
||||||
local rc
|
local rc
|
||||||
local cmd
|
local cmd
|
||||||
local tui_cwd
|
local tui_cwd
|
||||||
|
local tui_all
|
||||||
local -a tui_commands
|
local -a tui_commands
|
||||||
|
|
||||||
command_file="$(mktemp)"
|
command_file="$(mktemp)"
|
||||||
cwd_file="$(mktemp)"
|
cwd_file="$(mktemp)"
|
||||||
|
all_file="$(mktemp)"
|
||||||
|
printf '%s\n' "$show_all" > "$all_file"
|
||||||
|
|
||||||
while :; do
|
while :; do
|
||||||
: > "$command_file"
|
: > "$command_file"
|
||||||
: > "$cwd_file"
|
: > "$cwd_file"
|
||||||
|
: > "$all_file"
|
||||||
|
|
||||||
if (( show_all )); then
|
if (( show_all )); then
|
||||||
TUI_COMMAND_FILE="$command_file" \
|
TUI_COMMAND_FILE="$command_file" \
|
||||||
TUI_CWD_FILE="$cwd_file" \
|
TUI_CWD_FILE="$cwd_file" \
|
||||||
|
TUI_ALL_FILE="$all_file" \
|
||||||
python3 "$tui_python" -a "$tui_dir"
|
python3 "$tui_python" -a "$tui_dir"
|
||||||
else
|
else
|
||||||
TUI_COMMAND_FILE="$command_file" \
|
TUI_COMMAND_FILE="$command_file" \
|
||||||
TUI_CWD_FILE="$cwd_file" \
|
TUI_CWD_FILE="$cwd_file" \
|
||||||
|
TUI_ALL_FILE="$all_file" \
|
||||||
python3 "$tui_python" "$tui_dir"
|
python3 "$tui_python" "$tui_dir"
|
||||||
fi
|
fi
|
||||||
rc=$?
|
rc=$?
|
||||||
|
|
||||||
tui_cwd="$(<"$cwd_file")"
|
tui_cwd="$(<"$cwd_file")"
|
||||||
|
tui_all="$(<"$all_file")"
|
||||||
|
if [[ "$tui_all" == "1" ]]; then
|
||||||
|
show_all=1
|
||||||
|
elif [[ "$tui_all" == "0" ]]; then
|
||||||
|
show_all=0
|
||||||
|
fi
|
||||||
|
|
||||||
# Ctrl+C exits TUI. Apply the last directory in the parent Bash
|
# Ctrl+C exits TUI. Apply the last directory in the parent Bash
|
||||||
# process, then return to the caller.
|
# process, then return to the caller.
|
||||||
if (( rc == 43 )); then
|
if (( rc == 43 )); then
|
||||||
if [[ -n "$tui_cwd" && -d "$tui_cwd" ]]; then
|
if [[ -n "$tui_cwd" && -d "$tui_cwd" ]]; then
|
||||||
cd -- "$tui_cwd" || {
|
cd -- "$tui_cwd" || {
|
||||||
rm -f "$command_file" "$cwd_file"
|
rm -f "$command_file" "$cwd_file" "$all_file"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
fi
|
fi
|
||||||
rm -f "$command_file" "$cwd_file"
|
rm -f "$command_file" "$cwd_file" "$all_file"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if (( rc != 42 )); then
|
if (( rc != 42 )); then
|
||||||
rm -f "$command_file" "$cwd_file"
|
rm -f "$command_file" "$cwd_file" "$all_file"
|
||||||
return "$rc"
|
return "$rc"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -z "$tui_cwd" || ! -d "$tui_cwd" ]]; then
|
if [[ -z "$tui_cwd" || ! -d "$tui_cwd" ]]; then
|
||||||
echo "tui: invalid TUI working directory" >&2
|
echo "tui: invalid TUI working directory" >&2
|
||||||
rm -f "$command_file" "$cwd_file"
|
rm -f "$command_file" "$cwd_file" "$all_file"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -77,7 +90,7 @@ tui() {
|
|||||||
# own history entry. If a command fails, stop the action and pause so
|
# own history entry. If a command fails, stop the action and pause so
|
||||||
# the user has time to see the error.
|
# the user has time to see the error.
|
||||||
pushd "$tui_cwd" >/dev/null || {
|
pushd "$tui_cwd" >/dev/null || {
|
||||||
rm -f "$command_file" "$cwd_file"
|
rm -f "$command_file" "$cwd_file" "$all_file"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
192
tui/tui.py
192
tui/tui.py
@@ -6,7 +6,7 @@ import os
|
|||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
TUI_VERSION = "2.0.7"
|
TUI_VERSION = "3.0.0"
|
||||||
SHELL_HANDOFF = 42
|
SHELL_HANDOFF = 42
|
||||||
TUI_EXIT = 43
|
TUI_EXIT = 43
|
||||||
|
|
||||||
@@ -109,12 +109,85 @@ class TuiConfig:
|
|||||||
|
|
||||||
return self._match_rules(value, target.name)
|
return self._match_rules(value, target.name)
|
||||||
|
|
||||||
|
def menu_items(self):
|
||||||
|
"""Return configured menu entries in display order."""
|
||||||
|
menus = self.data.get("menu", self.data.get("menus", []))
|
||||||
|
return menus if isinstance(menus, list) else []
|
||||||
|
|
||||||
|
def condition_matches(self, condition, browser):
|
||||||
|
"""Evaluate the small, generic set of conditions supported by .tui."""
|
||||||
|
if condition is None or condition == "always":
|
||||||
|
return True
|
||||||
|
if isinstance(condition, list):
|
||||||
|
return all(self.condition_matches(item, browser) for item in condition)
|
||||||
|
if isinstance(condition, dict):
|
||||||
|
if "all" in condition:
|
||||||
|
return all(self.condition_matches(item, browser) for item in condition["all"])
|
||||||
|
if "any" in condition:
|
||||||
|
return any(self.condition_matches(item, browser) for item in condition["any"])
|
||||||
|
if "not" in condition:
|
||||||
|
return not self.condition_matches(condition["not"], browser)
|
||||||
|
return False
|
||||||
|
condition = str(condition)
|
||||||
|
if condition in ("git", "not-git"):
|
||||||
|
current = browser.path
|
||||||
|
git_root_found = False
|
||||||
|
while True:
|
||||||
|
if (current / ".git").exists():
|
||||||
|
git_root_found = True
|
||||||
|
break
|
||||||
|
if current == browser.home or current.parent == current:
|
||||||
|
break
|
||||||
|
current = current.parent
|
||||||
|
return git_root_found if condition == "git" else not git_root_found
|
||||||
|
if condition == "file":
|
||||||
|
target = browser.selected()
|
||||||
|
return bool(target and target.is_file())
|
||||||
|
if condition == "folder":
|
||||||
|
target = browser.selected()
|
||||||
|
return bool(target and target.is_dir())
|
||||||
|
if condition == "marked":
|
||||||
|
return bool(browser.marked)
|
||||||
|
if condition == "multiple":
|
||||||
|
return len(browser.targets(marked_only=False)) > 1
|
||||||
|
if condition.startswith("exists:"):
|
||||||
|
return (browser.path / condition[7:]).exists()
|
||||||
|
if condition.startswith("missing:"):
|
||||||
|
return not (browser.path / condition[8:]).exists()
|
||||||
|
if condition.startswith("glob:"):
|
||||||
|
return any(browser.path.glob(condition[5:]))
|
||||||
|
return False
|
||||||
|
|
||||||
|
def menu_available(self, item, browser):
|
||||||
|
if not isinstance(item, dict):
|
||||||
|
return False
|
||||||
|
if item.get("separator"):
|
||||||
|
return True
|
||||||
|
if not item.get("label") or not item.get("action"):
|
||||||
|
return False
|
||||||
|
if not self.condition_matches(item.get("when", "always"), browser):
|
||||||
|
return False
|
||||||
|
|
||||||
|
definition = self.action_definition(str(item["action"]))
|
||||||
|
if definition is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
selection = item.get("selection", "auto")
|
||||||
|
if definition.get("target") == "none" or selection == "none":
|
||||||
|
return True
|
||||||
|
|
||||||
|
if selection == "marked":
|
||||||
|
return bool(browser.targets(marked_only=True))
|
||||||
|
if selection == "current":
|
||||||
|
return bool(browser.targets(marked_only=False))
|
||||||
|
return bool(browser.targets(marked_only=False))
|
||||||
|
|
||||||
def action_definition(self, action):
|
def action_definition(self, action):
|
||||||
actions = self.data.get("actions", {})
|
actions = self.data.get("actions", {})
|
||||||
definition = actions.get(action)
|
definition = actions.get(action)
|
||||||
|
|
||||||
if isinstance(definition, str):
|
if isinstance(definition, str):
|
||||||
return {"commands": [definition], "pause": False}
|
return {"commands": [definition], "pause": False, "target": "auto"}
|
||||||
|
|
||||||
if not isinstance(definition, dict):
|
if not isinstance(definition, dict):
|
||||||
return None
|
return None
|
||||||
@@ -129,9 +202,11 @@ class TuiConfig:
|
|||||||
else:
|
else:
|
||||||
commands = []
|
commands = []
|
||||||
|
|
||||||
|
target_mode = definition.get("target", "auto")
|
||||||
return {
|
return {
|
||||||
"commands": commands,
|
"commands": commands,
|
||||||
"pause": bool(definition.get("pause", False)),
|
"pause": bool(definition.get("pause", False)),
|
||||||
|
"target": str(target_mode),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -309,14 +384,15 @@ class FileBrowser:
|
|||||||
|
|
||||||
def execute_action(self, action, stdscr, marked_only=False):
|
def execute_action(self, action, stdscr, marked_only=False):
|
||||||
"""Render normal shell commands and hand them to the parent shell wrapper."""
|
"""Render normal shell commands and hand them to the parent shell wrapper."""
|
||||||
targets = self.targets(marked_only=marked_only)
|
|
||||||
if not targets:
|
|
||||||
return True
|
|
||||||
|
|
||||||
definition = self.config.action_definition(action)
|
definition = self.config.action_definition(action)
|
||||||
if definition is None or not definition["commands"]:
|
if definition is None or not definition["commands"]:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
target_mode = definition.get("target", "auto")
|
||||||
|
targets = [] if target_mode == "none" else self.targets(marked_only=marked_only)
|
||||||
|
if target_mode != "none" and not targets:
|
||||||
|
return True
|
||||||
|
|
||||||
command_file = os.environ.get("TUI_COMMAND_FILE")
|
command_file = os.environ.get("TUI_COMMAND_FILE")
|
||||||
cwd_file = os.environ.get("TUI_CWD_FILE")
|
cwd_file = os.environ.get("TUI_CWD_FILE")
|
||||||
|
|
||||||
@@ -337,11 +413,14 @@ class FileBrowser:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
commands = []
|
commands = []
|
||||||
for target in targets:
|
if target_mode == "none":
|
||||||
for command_template in definition["commands"]:
|
commands = list(definition["commands"])
|
||||||
commands.append(
|
else:
|
||||||
self._command_for_target(command_template, target)
|
for target in targets:
|
||||||
)
|
for command_template in definition["commands"]:
|
||||||
|
commands.append(
|
||||||
|
self._command_for_target(command_template, target)
|
||||||
|
)
|
||||||
|
|
||||||
if not commands:
|
if not commands:
|
||||||
return True
|
return True
|
||||||
@@ -475,6 +554,69 @@ def read_ctrl_k(stdscr, browser):
|
|||||||
return None, "Unknown Ctrl-K command"
|
return None, "Unknown Ctrl-K command"
|
||||||
|
|
||||||
|
|
||||||
|
def show_menu(stdscr, browser):
|
||||||
|
"""Display the .tui-defined action menu and return an action or None."""
|
||||||
|
items = [item for item in browser.config.menu_items()
|
||||||
|
if browser.config.menu_available(item, browser)]
|
||||||
|
if not items:
|
||||||
|
return None, "Inga tillgängliga alternativ"
|
||||||
|
|
||||||
|
height, width = stdscr.getmaxyx()
|
||||||
|
selected = 0
|
||||||
|
selectable = [i for i, item in enumerate(items) if not item.get("separator")]
|
||||||
|
selected = selectable[0]
|
||||||
|
|
||||||
|
while True:
|
||||||
|
stdscr.erase()
|
||||||
|
title = f" tui {browser.path} — MENY "
|
||||||
|
stdscr.addnstr(0, 0, title, max(0, width - 1), curses.A_BOLD)
|
||||||
|
max_rows = max(1, height - 3)
|
||||||
|
selected_pos = selectable.index(selected)
|
||||||
|
first_pos = max(0, selected_pos - max_rows + 1)
|
||||||
|
visible_indices = selectable[first_pos:first_pos + max_rows]
|
||||||
|
visible_set = set(visible_indices)
|
||||||
|
first = max(0, items.index(items[selected]) - max_rows + 1)
|
||||||
|
|
||||||
|
menu_width = min(width - 2, max(30, max(len(str(i.get("label", ""))) for i in items if not i.get("separator")) + 10))
|
||||||
|
left = max(0, (width - menu_width) // 2)
|
||||||
|
row = 1
|
||||||
|
for idx, item in enumerate(items):
|
||||||
|
if idx < first or row >= height - 1:
|
||||||
|
continue
|
||||||
|
if item.get("separator"):
|
||||||
|
text = "─" * max(1, menu_width - 2)
|
||||||
|
attr = curses.A_DIM
|
||||||
|
else:
|
||||||
|
shortcut = str(item.get("key", ""))
|
||||||
|
text = f" {shortcut:>3} {item['label']}" if shortcut else f" {item['label']}"
|
||||||
|
attr = curses.A_REVERSE if idx == selected else curses.A_NORMAL
|
||||||
|
stdscr.addnstr(row, left, text, max(0, menu_width - 1), attr)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
footer = " ↑/↓ välj Enter kör Esc/M stäng "
|
||||||
|
stdscr.addnstr(height - 1, 0, footer, max(0, width - 1), curses.A_DIM)
|
||||||
|
stdscr.refresh()
|
||||||
|
|
||||||
|
key = stdscr.getch()
|
||||||
|
if key in (27, ord("m"), ord("M")):
|
||||||
|
return None, ""
|
||||||
|
if key in (curses.KEY_UP, ord("k")):
|
||||||
|
pos = selectable.index(selected)
|
||||||
|
selected = selectable[(pos - 1) % len(selectable)]
|
||||||
|
continue
|
||||||
|
if key in (curses.KEY_DOWN, ord("j")):
|
||||||
|
pos = selectable.index(selected)
|
||||||
|
selected = selectable[(pos + 1) % len(selectable)]
|
||||||
|
continue
|
||||||
|
if key in (10, 13, curses.KEY_ENTER):
|
||||||
|
return str(items[selected]["action"]), str(items[selected]["label"])
|
||||||
|
if 0 <= key < 256:
|
||||||
|
char = chr(key).lower()
|
||||||
|
for idx in selectable:
|
||||||
|
if str(items[idx].get("key", "")).lower() == char:
|
||||||
|
return str(items[idx]["action"]), str(items[idx]["label"])
|
||||||
|
|
||||||
|
|
||||||
def run(stdscr, start_dir=".", show_all=False):
|
def run(stdscr, start_dir=".", show_all=False):
|
||||||
curses.curs_set(0)
|
curses.curs_set(0)
|
||||||
stdscr.keypad(True)
|
stdscr.keypad(True)
|
||||||
@@ -504,6 +646,8 @@ def run(stdscr, start_dir=".", show_all=False):
|
|||||||
return browser.resolve_key_action(["right"], browser.selected())
|
return browser.resolve_key_action(["right"], browser.selected())
|
||||||
if key in (curses.KEY_ENTER, 10, 13):
|
if key in (curses.KEY_ENTER, 10, 13):
|
||||||
return browser.resolve_key_action(["enter"], browser.selected())
|
return browser.resolve_key_action(["enter"], browser.selected())
|
||||||
|
if 0 <= key < 256:
|
||||||
|
return browser.resolve_key_action([chr(key).lower()], browser.selected())
|
||||||
|
|
||||||
# Common Linux/curses key codes for Ctrl-Up / Ctrl-Down.
|
# Common Linux/curses key codes for Ctrl-Up / Ctrl-Down.
|
||||||
if key == 566:
|
if key == 566:
|
||||||
@@ -554,6 +698,26 @@ def run(stdscr, start_dir=".", show_all=False):
|
|||||||
|
|
||||||
action = configured_action(key)
|
action = configured_action(key)
|
||||||
|
|
||||||
|
if action == "quit":
|
||||||
|
return TUI_EXIT
|
||||||
|
elif action == "toggle-all":
|
||||||
|
browser.show_all = not browser.show_all
|
||||||
|
browser.load()
|
||||||
|
status = "Dolda filer: " + ("VISAS" if browser.show_all else "DÖLJS")
|
||||||
|
continue
|
||||||
|
elif action == "menu":
|
||||||
|
action, message = show_menu(stdscr, browser)
|
||||||
|
if action:
|
||||||
|
result = browser.execute_action(action, stdscr)
|
||||||
|
if result == SHELL_HANDOFF:
|
||||||
|
return SHELL_HANDOFF
|
||||||
|
if result is False:
|
||||||
|
return
|
||||||
|
status = message
|
||||||
|
else:
|
||||||
|
status = message
|
||||||
|
continue
|
||||||
|
|
||||||
if action == "up":
|
if action == "up":
|
||||||
browser.move(-1)
|
browser.move(-1)
|
||||||
elif action == "down":
|
elif action == "down":
|
||||||
@@ -579,6 +743,12 @@ def run(stdscr, start_dir=".", show_all=False):
|
|||||||
Path(cwd_file).write_text(str(browser.path), encoding="utf-8")
|
Path(cwd_file).write_text(str(browser.path), encoding="utf-8")
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
all_file = os.environ.get("TUI_ALL_FILE")
|
||||||
|
if all_file:
|
||||||
|
try:
|
||||||
|
Path(all_file).write_text("1" if browser.show_all else "0", encoding="utf-8")
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
Reference in New Issue
Block a user