Revert "Add interactive checkbox share selector with arrow keys and spacebar"
This reverts commit d0f3a7e77b.
This commit is contained in:
@@ -63,13 +63,6 @@ from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
try:
|
||||
import tty
|
||||
import termios
|
||||
_TTY_SUPPORTED = True
|
||||
except ImportError:
|
||||
_TTY_SUPPORTED = False # Windows
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Color helpers (ANSI; auto-disabled when stderr is not a TTY)
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
@@ -1117,130 +1110,33 @@ def _confirm(label: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _checkbox_select(items: list[str], title: str) -> list[int]:
|
||||
"""
|
||||
Full-screen interactive checkbox selector. Returns the indices of selected
|
||||
items. All items start selected.
|
||||
|
||||
Keys:
|
||||
↑ / k move cursor up
|
||||
↓ / j move cursor down
|
||||
Space toggle item under cursor
|
||||
a toggle all (select all if any unselected, else deselect all)
|
||||
Enter confirm
|
||||
q / Esc confirm with current selection
|
||||
|
||||
Falls back to simple numbered input if the terminal doesn't support raw mode.
|
||||
"""
|
||||
if not _TTY_SUPPORTED or not sys.stdin.isatty():
|
||||
return list(range(len(items))) # caller will use fallback
|
||||
|
||||
selected = [True] * len(items)
|
||||
cursor = 0
|
||||
n = len(items)
|
||||
|
||||
def _render():
|
||||
# Move cursor up to redraw in-place after first render
|
||||
lines = [
|
||||
"",
|
||||
f" {_bold(title)}",
|
||||
f" {_dim('↑/↓ move · Space toggle · a all/none · Enter confirm')}",
|
||||
"",
|
||||
]
|
||||
for i, label in enumerate(items):
|
||||
arrow = _cyan("▶") if i == cursor else " "
|
||||
box = _bold_green("✓") if selected[i] else _dim("·")
|
||||
if i == cursor:
|
||||
lines.append(f" {arrow} [{box}] {_bold(label)}")
|
||||
else:
|
||||
lines.append(f" {arrow} [{box}] {label}")
|
||||
lines.append("")
|
||||
return "\n".join(lines)
|
||||
|
||||
def _read_key(fd: int) -> str:
|
||||
ch = os.read(fd, 1)
|
||||
if ch == b"\x1b":
|
||||
rest = os.read(fd, 2)
|
||||
return "\x1b" + rest.decode("latin-1", errors="replace")
|
||||
return ch.decode("latin-1", errors="replace")
|
||||
|
||||
# Print initial render
|
||||
output = _render()
|
||||
print(output, end="", flush=True)
|
||||
line_count = output.count("\n")
|
||||
|
||||
fd = sys.stdin.fileno()
|
||||
old = termios.tcgetattr(fd)
|
||||
try:
|
||||
tty.setraw(fd)
|
||||
while True:
|
||||
key = _read_key(fd)
|
||||
|
||||
if key in ("\r", "\n", "q", "\x1b"):
|
||||
break
|
||||
elif key in ("\x1b[A", "k"): # up
|
||||
cursor = (cursor - 1) % n
|
||||
elif key in ("\x1b[B", "j"): # down
|
||||
cursor = (cursor + 1) % n
|
||||
elif key == " ":
|
||||
selected[cursor] = not selected[cursor]
|
||||
elif key in ("a", "A"):
|
||||
# Select all if any are unselected, otherwise deselect all
|
||||
new_state = not all(selected)
|
||||
selected[:] = [new_state] * n
|
||||
|
||||
# Redraw in-place
|
||||
sys.stdout.write(f"\033[{line_count}A\033[J")
|
||||
output = _render()
|
||||
print(output, end="", flush=True)
|
||||
finally:
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old)
|
||||
|
||||
return [i for i, s in enumerate(selected) if s]
|
||||
|
||||
|
||||
def _select_shares(shares: list[dict], share_type: str) -> list[dict]:
|
||||
"""
|
||||
Interactive share selector. Uses arrow-key/spacebar UI when stdin is a TTY,
|
||||
falls back to numbered text input otherwise.
|
||||
Display a numbered list of *shares* and return only those the user selects.
|
||||
Enter (or 'all') returns all shares unchanged. 'n' / 'none' returns [].
|
||||
"""
|
||||
if not shares:
|
||||
return shares
|
||||
|
||||
# Build display labels
|
||||
labels: list[str] = []
|
||||
for share in shares:
|
||||
print(f"\n {_bold(f'{share_type} shares in archive ({len(shares)}):')} \n")
|
||||
for i, share in enumerate(shares, 1):
|
||||
if share_type == "SMB":
|
||||
name = share.get("name", "<unnamed>")
|
||||
path = share.get("path", "")
|
||||
labels.append(f"{name:<22} {_dim(path)}")
|
||||
print(f" {_cyan(str(i) + '.')} {name:<22} {_dim(path)}")
|
||||
else: # NFS
|
||||
pl = share.get("paths") or []
|
||||
pl = share.get("paths") or []
|
||||
path = share.get("path") or (pl[0] if pl else "")
|
||||
extra = f" {_dim('+ ' + str(len(pl) - 1) + ' more')}" if len(pl) > 1 else ""
|
||||
labels.append(f"{path}{extra}")
|
||||
print(f" {_cyan(str(i) + '.')} {path}{extra}")
|
||||
|
||||
title = f"{share_type} shares to migrate ({len(shares)} found)"
|
||||
|
||||
if _TTY_SUPPORTED and sys.stdin.isatty():
|
||||
indices = _checkbox_select(labels, title)
|
||||
selected = [shares[i] for i in indices]
|
||||
if selected:
|
||||
print(f" {_green('✓')} {len(selected)} of {len(shares)} {share_type} share(s) selected.\n")
|
||||
else:
|
||||
print(f" {_yellow('–')} No {share_type} shares selected.\n")
|
||||
return selected
|
||||
|
||||
# ── Fallback: numbered text input ────────────────────────────────────────
|
||||
print(f"\n {_bold(title)}\n")
|
||||
for i, label in enumerate(labels, 1):
|
||||
print(f" {_cyan(str(i) + '.')} {label}")
|
||||
print()
|
||||
raw = _prompt(
|
||||
f" Select {share_type} shares to migrate "
|
||||
"(e.g. '1 3', Enter = all, 'n' = none)",
|
||||
default="all",
|
||||
)
|
||||
|
||||
low = raw.strip().lower()
|
||||
if low in ("", "all"):
|
||||
print(f" {_green('✓')} All {len(shares)} {share_type} share(s) selected.")
|
||||
@@ -1248,19 +1144,21 @@ def _select_shares(shares: list[dict], share_type: str) -> list[dict]:
|
||||
if low in ("n", "none", "0"):
|
||||
print(f" {_yellow('–')} No {share_type} shares selected.")
|
||||
return []
|
||||
|
||||
seen: set[int] = set()
|
||||
result: list[dict] = []
|
||||
selected: list[dict] = []
|
||||
for tok in raw.split():
|
||||
if tok.isdigit():
|
||||
idx = int(tok) - 1
|
||||
if 0 <= idx < len(shares) and idx not in seen:
|
||||
seen.add(idx)
|
||||
result.append(shares[idx])
|
||||
if result:
|
||||
print(f" {_green('✓')} {len(result)} of {len(shares)} {share_type} share(s) selected.")
|
||||
selected.append(shares[idx])
|
||||
|
||||
if selected:
|
||||
print(f" {_green('✓')} {len(selected)} of {len(shares)} {share_type} share(s) selected.")
|
||||
else:
|
||||
print(f" {_yellow('–')} No valid selections; skipping {share_type} shares.")
|
||||
return result
|
||||
return selected
|
||||
|
||||
|
||||
def interactive_mode() -> None:
|
||||
|
||||
Reference in New Issue
Block a user