Implement favorites for unicode input

This commit is contained in:
Kovid Goyal 2018-02-13 10:33:55 +05:30
parent a36f4a4670
commit 67e0d3723a
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 118 additions and 31 deletions

View File

@ -38,3 +38,6 @@ class Handler:
def print(self, *args, sep=' ', end='\r\n'):
data = sep.join(map(str, args)) + end
self.write(data)
def suspend(self):
return self._term_manager.suspend()

View File

@ -36,25 +36,6 @@ def log(*a, **kw):
fd.flush()
@contextmanager
def non_block(fd):
oldfl = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, oldfl | os.O_NONBLOCK)
yield
fcntl.fcntl(fd, fcntl.F_SETFL, oldfl)
@contextmanager
def raw_terminal(fd):
isatty = os.isatty(fd)
if isatty:
old = termios.tcgetattr(fd)
tty.setraw(fd)
yield
if isatty:
termios.tcsetattr(fd, termios.TCSADRAIN, old)
def write_all(fd, data):
if isinstance(data, str):
data = data.encode('utf-8')
@ -65,11 +46,40 @@ def write_all(fd, data):
data = data[n:]
@contextmanager
def sanitize_term(output_fd):
write_all(output_fd, init_state())
yield
write_all(output_fd, reset_state())
class TermManager:
def __init__(self, input_fd, output_fd):
self.input_fd = input_fd
self.output_fd = output_fd
self.original_fl = fcntl.fcntl(self.input_fd, fcntl.F_GETFL)
self.isatty = os.isatty(self.input_fd)
if self.isatty:
self.original_termios = termios.tcgetattr(self.input_fd)
def set_state_for_loop(self):
write_all(self.output_fd, init_state())
fcntl.fcntl(self.input_fd, fcntl.F_SETFL, self.original_fl | os.O_NONBLOCK)
if self.isatty:
tty.setraw(self.input_fd)
def reset_state_to_original(self):
if self.isatty:
termios.tcsetattr(self.input_fd, termios.TCSADRAIN, self.original_termios)
fcntl.fcntl(self.input_fd, fcntl.F_SETFL, self.original_fl)
write_all(self.output_fd, reset_state())
@contextmanager
def suspend(self):
self.reset_state_to_original()
yield self
self.set_state_for_loop()
def __enter__(self):
self.set_state_for_loop()
return self
def __exit__(self, *a):
self.reset_state_to_original()
LEFT, MIDDLE, RIGHT, FOURTH, FIFTH = 1, 2, 4, 8, 16
@ -306,11 +316,12 @@ class Loop:
select = self.sel.select
tb = None
waiting_for_write = True
with closing(self.sel), sanitize_term(self.output_fd), non_block(self.input_fd), non_block(self.output_fd), raw_terminal(self.input_fd):
with closing(self.sel), TermManager(self.input_fd, self.output_fd) as term_manager:
signal.signal(signal.SIGWINCH, self._on_sigwinch)
signal.signal(signal.SIGTERM, self._on_sigterm)
signal.signal(signal.SIGINT, self._on_sigint)
handler.write_buf = []
handler._term_manager = term_manager
keep_going = True
try:
handler.initialize(screen_size(), self.quit, self.wakeup)
@ -337,13 +348,14 @@ class Loop:
keep_going = False
break
if tb is not None:
self._report_error_loop(tb)
self._report_error_loop(tb, term_manager)
def _report_error_loop(self, tb):
def _report_error_loop(self, tb, term_manager):
select = self.sel.select
waiting_for_write = False
handler = UnhandledException(tb)
handler.write_buf = []
handler._term_manager = term_manager
handler.initialize(screen_size(), self.quit, self.wakeup)
while True:
has_data_to_write = bool(handler.write_buf)

View File

@ -2,15 +2,19 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
import shlex
import string
import subprocess
import sys
from functools import lru_cache
from gettext import gettext as _
from kitty.config import cached_values_for
from kitty.constants import config_dir
from kitty.fast_data_types import wcswidth
from kitty.key_encoding import (
DOWN, ESCAPE, F1, F2, F3, LEFT, RELEASE, RIGHT, SHIFT, TAB, UP,
DOWN, ESCAPE, F1, F2, F3, F4, F12, LEFT, RELEASE, RIGHT, SHIFT, TAB, UP,
backspace_key, enter_key
)
@ -21,7 +25,12 @@ from ..tui.operations import (
set_window_title, sgr, styled
)
HEX, NAME, EMOTICONS = 'HEX', 'NAME', 'EMOTICONS'
HEX, NAME, EMOTICONS, FAVORITES = 'HEX', 'NAME', 'EMOTICONS', 'FAVORITES'
favorites_path = os.path.join(config_dir, 'unicode-input-favorites.conf')
def codepoint_ok(code):
return not (code <= 32 or code == 127 or 128 <= code <= 159 or 0xd800 <= code <= 0xdbff or 0xDC00 <= code <= 0xDFFF)
@lru_cache(maxsize=256)
@ -57,6 +66,48 @@ def codepoints_matching_search(text):
return ans
def parse_favorites(raw):
for line in raw.splitlines():
line = line.strip()
if line.startswith('#') or not line:
continue
idx = line.find('#')
if idx > -1:
line = line[:idx]
code_text = line.partition(' ')[0]
try:
code = int(code_text, 16)
except Exception:
pass
else:
if codepoint_ok(code):
yield code
def serialize_favorites(favorites):
ans = '''\
# Favorite characters for unicode input
# Enter the hex code for each favorite character on a new line. Blank lines are
# ignored and anything after a # is considered a comment.
'''.splitlines()
for cp in favorites:
ans.append('{:x} # {} {}'.format(cp, chr(cp), name(cp)))
return '\n'.join(ans)
def load_favorites(refresh=False):
ans = getattr(load_favorites, 'ans', None)
if ans is None or refresh:
try:
with open(favorites_path, 'rb') as f:
raw = f.read().decode('utf-8')
ans = load_favorites.ans = list(parse_favorites(raw)) or list(DEFAULT_SET)
except FileNotFoundError:
ans = load_favorites.ans = list(DEFAULT_SET)
return ans
FAINT = 242
DEFAULT_SET = tuple(map(
ord,
@ -208,6 +259,9 @@ class UnicodeInput(Handler):
elif self.mode is EMOTICONS:
q = self.mode, None
codepoints = list(EMOTICONS_SET)
elif self.mode is FAVORITES:
codepoints = load_favorites()
q = self.mode, tuple(codepoints)
elif self.mode is NAME:
q = self.mode, self.current_input
if q != self.last_updated_code_point_at:
@ -240,7 +294,7 @@ class UnicodeInput(Handler):
pass
if self.current_char is not None:
code = ord(self.current_char)
if code <= 32 or code == 127 or 128 <= code <= 159 or 0xd800 <= code <= 0xdbff or 0xDC00 <= code <= 0xDFFF:
if not codepoint_ok(code):
self.current_char = None
def update_prompt(self):
@ -266,6 +320,7 @@ class UnicodeInput(Handler):
(_('Code'), 'F1', HEX),
(_('Name'), 'F2', NAME),
(_('Emoji'), 'F3', EMOTICONS),
(_('Favorites'), 'F4', FAVORITES),
]:
entry = ' {} ({}) '.format(name, key)
if mode is self.mode:
@ -302,6 +357,8 @@ class UnicodeInput(Handler):
writeln(styled(_('Type {} followed by the index for the recent entries below').format('r'), fg=FAINT))
elif self.mode is NAME:
writeln(styled(_('Use Tab or the arrow keys to choose a character from below'), fg=FAINT))
elif self.mode is FAVORITES:
writeln(styled(_('Press F12 to edit the list of favorites'), fg=FAINT))
self.table_at = y
self.write(self.table.layout(self.screen_size.rows - self.table_at, self.screen_size.cols))
@ -319,7 +376,7 @@ class UnicodeInput(Handler):
self.refresh()
elif key_event is enter_key:
self.quit_loop(0)
elif key_event.type is RELEASE:
elif key_event.type is RELEASE and not key_event.mods:
if key_event.key is ESCAPE:
self.quit_loop(1)
elif key_event.key is F1:
@ -328,6 +385,10 @@ class UnicodeInput(Handler):
self.switch_mode(NAME)
elif key_event.key is F3:
self.switch_mode(EMOTICONS)
elif key_event.key is F4:
self.switch_mode(FAVORITES)
elif key_event.key is F12 and self.mode is FAVORITES:
self.edit_favorites()
elif self.mode is NAME:
if key_event.key is TAB:
if key_event.mods == SHIFT:
@ -343,6 +404,17 @@ class UnicodeInput(Handler):
elif key_event.key is DOWN and not key_event.mods:
self.table.move_current(rows=1), self.refresh()
def edit_favorites(self):
if not os.path.exists(favorites_path):
with open(favorites_path, 'wb') as f:
f.write(serialize_favorites(load_favorites()).encode('utf-8'))
editor = shlex.split(os.environ.get('EDITOR', 'vim'))
with self.suspend():
p = subprocess.Popen(editor + [favorites_path])
if p.wait() == 0:
load_favorites(refresh=True)
self.refresh()
def switch_mode(self, mode):
if mode is not self.mode:
self.mode = mode