ask kitten: Use buttons for choices as well

This commit is contained in:
Kovid Goyal 2022-01-22 13:57:25 +05:30
parent 08f159c2ba
commit 0a3acd9738
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C

View File

@ -4,7 +4,9 @@
import os import os
import sys import sys
from contextlib import suppress from contextlib import suppress
from typing import TYPE_CHECKING, Dict, List, NamedTuple, Optional, Tuple from typing import (
TYPE_CHECKING, Callable, Dict, List, NamedTuple, Optional, Tuple
)
from kitty.cli import parse_args from kitty.cli import parse_args
from kitty.cli_stub import AskCLIOptions from kitty.cli_stub import AskCLIOptions
@ -113,6 +115,7 @@ class Choice(NamedTuple):
text: str text: str
idx: int idx: int
color: str color: str
letter: str
class Range(NamedTuple): class Range(NamedTuple):
@ -156,7 +159,7 @@ class Choose(Handler):
letter = letter.lower() letter = letter.lower()
idx = text.lower().index(letter) idx = text.lower().index(letter)
allowed.append(letter) allowed.append(letter)
self.choices[letter] = Choice(text, idx, color) self.choices[letter] = Choice(text, idx, color, letter)
self.allowed = frozenset(allowed) self.allowed = frozenset(allowed)
self.response = '' self.response = ''
self.response_on_accept = cli_opts.default or '' self.response_on_accept = cli_opts.default or ''
@ -196,7 +199,73 @@ class Choose(Handler):
else: else:
self.draw_choice(y) self.draw_choice(y)
def draw_choice_boxes(self, y: int, *choices: Choice) -> None:
self.clickable_ranges.clear()
width = self.screen_size.cols - 2
current_line_length = 0
current_line: List[Tuple[str, str]] = []
lines: List[List[Tuple[str, str]]] = []
sep, sep_sz = ' ', 2
for choice in choices:
self.clickable_ranges[choice.letter] = []
text = ' ' + choice.text[:choice.idx]
text += styled(choice.text[choice.idx], fg=choice.color or 'green')
text += choice.text[choice.idx + 1:] + ' '
sz = wcswidth(text)
if sz + sep_sz + current_line_length > width:
lines.append(current_line)
current_line = []
current_line_length = 0
current_line.append((choice.letter, text))
current_line_length += sz
if current_line:
lines.append(current_line)
def top(text: str) -> str:
return '' + '' * wcswidth(text) + ''
def middle(text: str) -> str:
return f'{text}'
def bottom(text: str) -> str:
return '' + '' * wcswidth(text) + ''
def highlight(text: str, only_edges: bool = False) -> str:
if only_edges:
return styled(text[0], fg='yellow') + text[1:-1] + styled(text[-1], fg='yellow')
return styled(text, fg='yellow')
def print_line(add_borders: Callable[[str], str], *items: Tuple[str, str], is_last: bool = False) -> None:
nonlocal y
texts = []
positions = []
x = 0
for (letter, text) in items:
positions.append((letter, x, wcswidth(text) + 2))
text = add_borders(text)
if letter == self.response_on_accept:
text = highlight(text, only_edges=add_borders is middle)
text += sep
x += wcswidth(text)
texts.append(text)
line = ''.join(texts).rstrip()
offset = extra_for(wcswidth(line), width)
for (letter, x, sz) in positions:
x += offset
self.clickable_ranges[letter].append(Range(x, x + sz - 1, y))
self.print(' ' * offset, line, sep='', end='' if is_last else '\r\n')
y += 1
for boxed_line in lines:
print_line(top, *boxed_line)
print_line(middle, *boxed_line)
print_line(bottom, *boxed_line, is_last=boxed_line is lines[-1])
def draw_choice(self, y: int) -> None: def draw_choice(self, y: int) -> None:
if y + 3 <= self.screen_size.rows:
self.draw_choice_boxes(y, *self.choices.values())
return
self.clickable_ranges.clear() self.clickable_ranges.clear()
current_line = '' current_line = ''
current_ranges: Dict[str, int] = {} current_ranges: Dict[str, int] = {}
@ -230,7 +299,7 @@ class Choose(Handler):
yes = styled('Y', fg='green') + 'es' yes = styled('Y', fg='green') + 'es'
no = styled('N', fg='red') + 'o' no = styled('N', fg='red') + 'o'
if y + 3 <= self.screen_size.rows: if y + 3 <= self.screen_size.rows:
self.draw_yesno_buttons(y, yes, no) self.draw_choice_boxes(y, Choice('Yes', 0, 'green', 'y'), Choice('No', 0, 'red', 'n'))
return return
sep = ' ' * 3 sep = ' ' * 3
text = yes + sep + no text = yes + sep + no
@ -240,45 +309,6 @@ class Choose(Handler):
self.clickable_ranges = {'y': [Range(x, x + wcswidth(yes) - 1, y)], 'n': [Range(nx, nx + wcswidth(no) - 1, y)]} self.clickable_ranges = {'y': [Range(x, x + wcswidth(yes) - 1, y)], 'n': [Range(nx, nx + wcswidth(no) - 1, y)]}
self.print(' ' * x + text, end='') self.print(' ' * x + text, end='')
def draw_yesno_buttons(self, y: int, yes: str, no: str) -> None:
sep = ' '
self.clickable_ranges = {'y': [], 'n': []}
yl, nl = wcswidth(yes), wcswidth(no)
line_count = 0
width = self.screen_size.cols - 2
def highlight(text: str, only_edges: bool = False) -> str:
if only_edges:
return styled(text[0], fg='yellow') + text[1:-1] + styled(text[-1], fg='yellow')
return styled(text, fg='yellow')
def print_line(yes: str, no: str) -> None:
nonlocal line_count
sz = wcswidth(yes) + wcswidth(sep) + wcswidth(no)
x = extra_for(sz, width)
nx = x + wcswidth(yes) + len(sep)
self.clickable_ranges['y'].append(Range(x, x + wcswidth(yes) - 1, y + line_count))
self.clickable_ranges['n'].append(Range(nx, nx + wcswidth(no) - 1, y + line_count))
if self.response_on_accept == 'y':
yes = highlight(yes, line_count == 1)
else:
no = highlight(no, line_count == 1)
self.print(' ' * x, yes, sep, no, sep='', end='' if line_count == 2 else '\r\n')
line_count += 1
def top(sz: int) -> str:
return '' + '' * (sz + 2) + ''
def middle(text: str) -> str:
return f'{text}'
def bottom(sz: int) -> str:
return '' + '' * (sz + 2) + ''
print_line(top(yl), top(nl))
print_line(middle(yes), middle(no))
print_line(bottom(yl), bottom(nl))
def on_text(self, text: str, in_bracketed_paste: bool = False) -> None: def on_text(self, text: str, in_bracketed_paste: bool = False) -> None:
text = text.lower() text = text.lower()
if text in self.allowed: if text in self.allowed: