diff --git a/kittens/tui/operations.py b/kittens/tui/operations.py index f1065bd83..6f246ea91 100644 --- a/kittens/tui/operations.py +++ b/kittens/tui/operations.py @@ -2,6 +2,8 @@ # vim:fileencoding=utf-8 # License: GPL v3 Copyright: 2018, Kovid Goyal +from contextlib import contextmanager + from kitty.terminfo import string_capabilities S7C1T = b'\033 F' @@ -56,18 +58,50 @@ def set_line_wrapping(yes_or_no): STANDARD_COLORS = {name: i for i, name in enumerate( 'black red green yellow blue magenta cyan gray'.split())} +UNDERLINE_STYLES = {name: i + 1 for i, name in enumerate( + 'straight double curly'.split())} + + +def _color(color, intense=False, base=30): + if isinstance(color, str): + e = str((base + 60 if intense else base) + STANDARD_COLORS[color]) + elif isinstance(color, int): + e = '{}:5:{}'.format(base + 8, max(0, min(color, 255))) + else: + e = '{}:2:{}:{}:{}'.format(base + 8, *color) + return e def colored(text, color, intense=False): - if isinstance(color, str): - e = (90 if intense else 30) + STANDARD_COLORS[color] - elif isinstance(color, int): - e = '38:5:{}'.format(max(0, min(color, 255))) - else: - e = '38:2:{}:{}:{}'.format(*color) + e = _color(color, intense) return '\033[{}m{}\033[39m'.format(e, text) +def styled(text, fg=None, bg=None, fg_intense=False, bg_intense=False, italic=False, bold=False, underline=None, underline_color=None): + start, end = [], [] + if fg is not None: + start.append(_color(fg, fg_intense)) + end.append('39') + if bg is not None: + start.append(_color(bg, bg_intense, 40)) + end.append('49') + if underline_color is not None: + if isinstance(underline_color, str): + underline_color = STANDARD_COLORS[underline_color] + start.append(_color(underline_color, base=50)) + end.append('59') + if underline is not None: + start.append('4:{}'.format(UNDERLINE_STYLES[underline])) + end.append('4:0') + if italic: + start.append('3'), end.append('23') + if bold: + start.append('1'), end.append('21') + if not start: + return text + return '\033[{}m{}\033[{}m'.format(';'.join(start), text, ';'.join(end)) + + def init_state(alternate_screen=True): ans = ( S7C1T + SAVE_CURSOR + SAVE_PRIVATE_MODE_VALUES + reset_mode('LNM') + @@ -92,3 +126,10 @@ def reset_state(normal_screen=True): ans += RESTORE_PRIVATE_MODE_VALUES ans += RESTORE_CURSOR return ans + + +@contextmanager +def cursor(write): + write(SAVE_CURSOR) + yield + write(RESTORE_CURSOR) diff --git a/kittens/unicode_input/main.py b/kittens/unicode_input/main.py index 2b474dd20..cad49afba 100644 --- a/kittens/unicode_input/main.py +++ b/kittens/unicode_input/main.py @@ -3,6 +3,7 @@ # License: GPL v3 Copyright: 2018, Kovid Goyal import sys +from functools import lru_cache from gettext import gettext as _ from kitty.fast_data_types import wcswidth @@ -11,9 +12,28 @@ from kitty.key_encoding import ESCAPE, backspace_key, enter_key from ..tui.handler import Handler from ..tui.loop import Loop from ..tui.operations import ( - clear_screen, colored, set_line_wrapping, set_window_title + clear_screen, colored, cursor, set_line_wrapping, set_window_title, styled ) +HEX, NAME = 0, 1 + + +@lru_cache() +def points_for_word(w): + from .unicode_names import codepoints_for_word + return codepoints_for_word(w.lower()) + + +@lru_cache() +def name(cp): + from .unicode_names import name_for_codepoint + if isinstance(cp, str): + cp = ord(cp[0]) + return (name_for_codepoint(cp) or '').capitalize() + + +FAINT = 242 + class UnicodeInput(Handler): @@ -21,24 +41,43 @@ class UnicodeInput(Handler): self.current_input = '' self.current_char = None self.prompt_template = '{}> ' + self.choice_line = '' + self.mode = HEX self.update_prompt() def update_current_char(self): - try: - code = int(self.current_input, 16) - if code <= 32 or code == 127 or 128 <= code <= 159: + if self.mode is HEX: + try: + code = int(self.current_input, 16) + if code <= 32 or code == 127 or 128 <= code <= 159: + self.current_char = None + else: + self.current_char = chr(code) + except Exception: self.current_char = None - else: - self.current_char = chr(code) - except Exception: + else: self.current_char = None + parts = self.current_input.split() + if parts and parts[0]: + codepoints = points_for_word(parts[0]) + for word in parts[1:]: + pts = points_for_word(word) + if pts: + codepoints &= pts + if codepoints: + codepoints = tuple(sorted(codepoints)) + self.current_char = chr(codepoints[0]) + # name_map = {c: name(c) for c in codepoints} def update_prompt(self): self.update_current_char() if self.current_char is None: c, color = '??', 'red' + self.choice_line = '' else: c, color = self.current_char, 'green' + self.choice_line = _('Chosen:') + ' {} ({}) {}'.format( + colored(c, 'green'), hex(ord(c))[2:], styled(name(c) or '', italic=True, fg=FAINT)) w = wcswidth(c) self.prompt = self.prompt_template.format(colored(c, color)) self.promt_len = w + len(self.prompt_template) - 2 @@ -51,16 +90,28 @@ class UnicodeInput(Handler): def draw_screen(self): self.write(clear_screen()) - self.print(_('Enter the hex code for the unicode character')) + if self.mode is HEX: + self.print(styled(_('Press the / key to search by character name'), fg=FAINT, italic=True)) + self.print(_('Enter the hex code for the character')) + else: + self.print(_('Enter words from the name of the character')) self.write(self.prompt) self.write(self.current_input) + with cursor(self.write): + self.print() + if self.choice_line: + self.print(self.choice_line) + self.print() def refresh(self): self.update_prompt() self.draw_screen() def on_text(self, text, in_bracketed_paste): - self.current_input += text + if self.mode is HEX and text == '/': + self.mode = NAME + else: + self.current_input += text self.refresh() def on_key(self, key_event):