From 437efe5473209cf21753a868b10cb40215db1b2e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 15 Sep 2020 12:39:07 +0530 Subject: [PATCH] Allow using the ask kitten to pick a choice --- kittens/ask/main.py | 54 ++++++++++++++++++++++++++----------- kittens/remote_file/main.py | 27 ++++--------------- kittens/tui/utils.py | 29 ++++++++++++++++++++ 3 files changed, 73 insertions(+), 37 deletions(-) create mode 100644 kittens/tui/utils.py diff --git a/kittens/ask/main.py b/kittens/ask/main.py index bb7957993..c7b96356b 100644 --- a/kittens/ask/main.py +++ b/kittens/ask/main.py @@ -13,7 +13,8 @@ from kitty.constants import cache_dir from kitty.typing import BossType from ..tui.handler import result_handler -from ..tui.operations import alternate_screen, set_cursor_visible, styled +from ..tui.operations import alternate_screen, styled +from ..tui.utils import get_key_press if TYPE_CHECKING: import readline @@ -70,7 +71,7 @@ class HistoryCompleter: def option_text() -> str: return '''\ --type -t -choices=line,yesno +choices=line,yesno,choices default=line Type of input. Defaults to asking for a line of text. @@ -83,6 +84,16 @@ message is shown. --name -n The name for this question. Used to store history of previous answers which can be used for completions and via the browse history readline bindings. + + +--choice -c +type=list +dest=choices +A choice for the choices type. Every choice has the syntax: letter:text Where +letter is the accelerator key and text is the corresponding text. There can be +an optional color specification after the letter to indicate what color it should +be. +For example: y:Yes and n;red:No ''' @@ -97,23 +108,34 @@ class Response(TypedDict): response: Optional[str] -def yesno(cli_opts: AskCLIOptions, items: List[str]) -> Response: - import tty +def choice(cli_opts: AskCLIOptions, items: List[str]) -> Response: with alternate_screen(): if cli_opts.message: print(styled(cli_opts.message, bold=True)) print() - print(' ', styled('Y', fg='green') + 'es', ' ', styled('N', fg='red') + 'o', set_cursor_visible(False)) - sys.stdout.flush() - tty.setraw(sys.stdin.fileno()) - try: - response = sys.stdin.buffer.read(1) - yes = response in (b'y', b'Y', b'\r', b'\n', b' ') - return {'items': items, 'response': 'y' if yes else 'n'} - finally: - sys.stdout.write(set_cursor_visible(True)) - tty.setcbreak(sys.stdin.fileno()) - sys.stdout.flush() + allowed = '' + for choice in cli_opts.choices: + color = 'green' + letter, text = choice.split(':', maxsplit=1) + if ';' in letter: + letter, color = letter.split(';', maxsplit=1) + letter = letter.lower() + idx = text.lower().index(letter) + allowed += letter + print(text[:idx], styled(text[idx], fg=color), text[idx:], sep='', end=' ') + print() + response = get_key_press(allowed, '') + return {'items': items, 'response': response} + + +def yesno(cli_opts: AskCLIOptions, items: List[str]) -> Response: + with alternate_screen(): + if cli_opts.message: + print(styled(cli_opts.message, bold=True)) + print() + print(' ', styled('Y', fg='green') + 'es', ' ', styled('N', fg='red') + 'o') + response = get_key_press('yn', 'n') + return {'items': items, 'response': response} def main(args: List[str]) -> Response: @@ -132,6 +154,8 @@ def main(args: List[str]) -> Response: if cli_opts.type == 'yesno': return yesno(cli_opts, items) + if cli_opts.type == 'choices': + return choice(cli_opts, items) import readline as rl readline = rl diff --git a/kittens/remote_file/main.py b/kittens/remote_file/main.py index 1b1e7eee7..ff15dd10a 100644 --- a/kittens/remote_file/main.py +++ b/kittens/remote_file/main.py @@ -11,43 +11,27 @@ import subprocess import sys import tempfile import time -from contextlib import suppress from typing import Any, List, Optional from kitty.cli import parse_args from kitty.cli_stub import RemoteFileCLIOptions from kitty.constants import cache_dir from kitty.typing import BossType -from kitty.utils import command_for_open, get_editor, open_cmd, SSHConnectionData +from kitty.utils import ( + SSHConnectionData, command_for_open, get_editor, open_cmd +) from ..tui.handler import result_handler from ..tui.operations import ( - faint, raw_mode, reset_terminal, set_cursor_visible, styled + faint, raw_mode, reset_terminal, styled ) +from ..tui.utils import get_key_press def key(x: str) -> str: return styled(x, bold=True, fg='green') -def get_key_press(allowed: str, default: str) -> str: - response = default - with raw_mode(): - try: - while True: - q = sys.stdin.buffer.read(1) - if q: - if q in b'\x1b\x03': - break - with suppress(Exception): - response = q.decode('utf-8').lower() - if response in allowed: - break - except (KeyboardInterrupt, EOFError): - pass - return response - - def option_text() -> str: return '''\ --mode -m @@ -194,7 +178,6 @@ def main(args: List[str]) -> Result: input('Press enter to quit...') raise SystemExit(e.code) - print(set_cursor_visible(False), end='', flush=True) try: action = ask_action(cli_opts) finally: diff --git a/kittens/tui/utils.py b/kittens/tui/utils.py new file mode 100644 index 000000000..14868aacb --- /dev/null +++ b/kittens/tui/utils.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +# License: GPLv3 Copyright: 2020, Kovid Goyal + +import sys +from contextlib import suppress + +from .operations import raw_mode, set_cursor_visible + + +def get_key_press(allowed: str, default: str) -> str: + response = default + with raw_mode(): + print(set_cursor_visible(False), end='', flush=True) + try: + while True: + q = sys.stdin.buffer.read(1) + if q: + if q in b'\x1b\x03': + break + with suppress(Exception): + response = q.decode('utf-8').lower() + if response in allowed: + break + except (KeyboardInterrupt, EOFError): + pass + finally: + print(set_cursor_visible(True), end='', flush=True) + return response