unicode input kitten: Add an option :option:kitty +kitten unicode_input --emoji-variation to control the presentation variant of selected emojis

Fixes #2139
This commit is contained in:
Kovid Goyal 2019-11-17 13:13:37 +05:30
parent d5682fe49a
commit 738878c2ff
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 81 additions and 17 deletions

View File

@ -30,6 +30,10 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
- diff kitten: Allow diffing remote files easily via ssh (:iss:`727`) - diff kitten: Allow diffing remote files easily via ssh (:iss:`727`)
- unicode input kitten: Add an option :option:`kitty +kitten unicode_input
--emoji-variation` to control the presentation variant of selected emojis
(:iss:`2139`)
- Add specialised rendering for a few more box powerline and unicode symbols - Add specialised rendering for a few more box powerline and unicode symbols
(:pull:`2074` and :pull:`2021`) (:pull:`2074` and :pull:`2021`)

View File

@ -22,3 +22,8 @@ In :guilabel:`Name` mode you instead type words from the character name and use
keys/tab to select the character from the displayed matches. You can also type keys/tab to select the character from the displayed matches. You can also type
a leading period and the index for the match if you don't like to use arrow a leading period and the index for the match if you don't like to use arrow
keys. keys.
Command Line Interface
-------------------------
.. include:: ../generated/cli-kitten-unicode_input.rst

View File

@ -5,25 +5,27 @@
import os import os
import string import string
import subprocess import subprocess
import sys
from contextlib import suppress
from functools import lru_cache from functools import lru_cache
from gettext import gettext as _ from gettext import gettext as _
from contextlib import suppress
from kitty.cli import parse_args
from kitty.config import cached_values_for from kitty.config import cached_values_for
from kitty.constants import config_dir from kitty.constants import config_dir
from kitty.utils import get_editor from kitty.fast_data_types import wcswidth, is_emoji_presentation_base
from kitty.fast_data_types import wcswidth
from kitty.key_encoding import ( from kitty.key_encoding import (
DOWN, ESCAPE, F1, F2, F3, F4, F12, LEFT, RELEASE, RIGHT, SHIFT, TAB, UP, DOWN, ESCAPE, F1, F2, F3, F4, F12, LEFT, RELEASE, RIGHT, SHIFT, TAB, UP,
enter_key enter_key
) )
from kitty.utils import get_editor
from ..tui.line_edit import LineEdit
from ..tui.handler import Handler from ..tui.handler import Handler
from ..tui.line_edit import LineEdit
from ..tui.loop import Loop from ..tui.loop import Loop
from ..tui.operations import ( from ..tui.operations import (
clear_screen, colored, cursor, faint, set_line_wrapping, clear_screen, colored, cursor, faint, set_line_wrapping, set_window_title,
set_window_title, sgr, styled sgr, styled
) )
HEX, NAME, EMOTICONS, FAVORITES = 'HEX', 'NAME', 'EMOTICONS', 'FAVORITES' HEX, NAME, EMOTICONS, FAVORITES = 'HEX', 'NAME', 'EMOTICONS', 'FAVORITES'
@ -131,7 +133,8 @@ def decode_hint(x):
class Table: class Table:
def __init__(self): def __init__(self, emoji_variation):
self.emoji_variation = emoji_variation
self.layout_dirty = True self.layout_dirty = True
self.last_rows = self.last_cols = -1 self.last_rows = self.last_cols = -1
self.codepoints = [] self.codepoints = []
@ -161,7 +164,10 @@ class Table:
self.layout_dirty = False self.layout_dirty = False
def safe_chr(codepoint): def safe_chr(codepoint):
return chr(codepoint).encode('utf-8', 'replace').decode('utf-8') ans = chr(codepoint).encode('utf-8', 'replace').decode('utf-8')
if self.emoji_variation and is_emoji_presentation_base(codepoint):
ans += self.emoji_variation
return ans
if self.mode is NAME: if self.mode is NAME:
def as_parts(i, codepoint): def as_parts(i, codepoint):
@ -248,8 +254,13 @@ def is_index(w):
class UnicodeInput(Handler): class UnicodeInput(Handler):
def __init__(self, cached_values): def __init__(self, cached_values, emoji_variation='none'):
self.cached_values = cached_values self.cached_values = cached_values
self.emoji_variation = ''
if emoji_variation == 'text':
self.emoji_variation = '\ufe0e'
elif emoji_variation == 'graphic':
self.emoji_variation = '\ufe0f'
self.line_edit = LineEdit() self.line_edit = LineEdit()
self.recent = list(self.cached_values.get('recent', DEFAULT_SET)) self.recent = list(self.cached_values.get('recent', DEFAULT_SET))
self.current_char = None self.current_char = None
@ -257,9 +268,17 @@ class UnicodeInput(Handler):
self.last_updated_code_point_at = None self.last_updated_code_point_at = None
self.choice_line = '' self.choice_line = ''
self.mode = globals().get(cached_values.get('mode', 'HEX'), 'HEX') self.mode = globals().get(cached_values.get('mode', 'HEX'), 'HEX')
self.table = Table() self.table = Table(self.emoji_variation)
self.update_prompt() self.update_prompt()
@property
def resolved_current_char(self):
ans = self.current_char
if ans:
if self.emoji_variation and is_emoji_presentation_base(ord(ans[0])):
ans += self.emoji_variation
return ans
def update_codepoints(self): def update_codepoints(self):
codepoints = None codepoints = None
if self.mode is HEX: if self.mode is HEX:
@ -320,8 +339,10 @@ class UnicodeInput(Handler):
self.choice_line = '' self.choice_line = ''
else: else:
c, color = self.current_char, 'green' c, color = self.current_char, 'green'
if self.emoji_variation and is_emoji_presentation_base(ord(c[0])):
c += self.emoji_variation
self.choice_line = _('Chosen:') + ' {} U+{} {}'.format( self.choice_line = _('Chosen:') + ' {} U+{} {}'.format(
colored(c, 'green'), hex(ord(c))[2:], faint(styled(name(c) or '', italic=True))) colored(c, 'green'), hex(ord(c[0]))[2:], faint(styled(name(c) or '', italic=True)))
self.prompt = self.prompt_template.format(colored(c, color)) self.prompt = self.prompt_template.format(colored(c, color))
def init_terminal_state(self): def init_terminal_state(self):
@ -475,17 +496,43 @@ class UnicodeInput(Handler):
self.refresh() self.refresh()
help_text = 'Input a unicode character'
usage = ''
OPTIONS = '''
--emoji-variation
type=choices
default=none
choices=none,graphic,text
Whether to use the textual or the graphical form for emoji. By default the
default form specified in the unicode standard for the symbol is used.
'''.format
def parse_unicode_input_args(args):
return parse_args(args, OPTIONS, usage, help_text, 'kitty +kitten unicode_input')
def main(args): def main(args):
try:
args, items = parse_unicode_input_args(args[1:])
except SystemExit as e:
if e.code != 0:
print(e.args[0], file=sys.stderr)
input(_('Press Enter to quit'))
return
loop = Loop() loop = Loop()
with cached_values_for('unicode-input') as cached_values: with cached_values_for('unicode-input') as cached_values:
handler = UnicodeInput(cached_values) handler = UnicodeInput(cached_values, args.emoji_variation)
loop.loop(handler) loop.loop(handler)
if handler.current_char and loop.return_code == 0: if handler.current_char and loop.return_code == 0:
with suppress(Exception): with suppress(Exception):
handler.recent.remove(ord(handler.current_char)) handler.recent.remove(ord(handler.current_char))
recent = [ord(handler.current_char)] + handler.recent recent = [ord(handler.current_char)] + handler.recent
cached_values['recent'] = recent[:len(DEFAULT_SET)] cached_values['recent'] = recent[:len(DEFAULT_SET)]
return handler.current_char return handler.resolved_current_char
if loop.return_code != 0: if loop.return_code != 0:
raise SystemExit(loop.return_code) raise SystemExit(loop.return_code)
@ -497,10 +544,10 @@ def handle_result(args, current_char, target_window_id, boss):
if __name__ == '__main__': if __name__ == '__main__':
import sys
if '-h' in sys.argv or '--help' in sys.argv:
print('Choose a unicode character to input into the terminal')
raise SystemExit(0)
ans = main(sys.argv) ans = main(sys.argv)
if ans: if ans:
print(ans) print(ans)
elif __name__ == '__doc__':
sys.cli_docs['usage'] = usage
sys.cli_docs['options'] = OPTIONS
sys.cli_docs['help_text'] = help_text

View File

@ -2267,6 +2267,13 @@ wcwidth_wrap(PyObject UNUSED *self, PyObject *chr) {
return PyLong_FromLong(wcwidth_std(PyLong_AsLong(chr))); return PyLong_FromLong(wcwidth_std(PyLong_AsLong(chr)));
} }
static PyObject*
screen_is_emoji_presentation_base(PyObject UNUSED *self, PyObject *code_) {
unsigned long code = PyLong_AsUnsignedLong(code_);
if (is_emoji_presentation_base(code)) Py_RETURN_TRUE;
Py_RETURN_FALSE;
}
#define MND(name, args) {#name, (PyCFunction)name, args, #name}, #define MND(name, args) {#name, (PyCFunction)name, args, #name},
#define MODEFUNC(name) MND(name, METH_NOARGS) MND(set_##name, METH_O) #define MODEFUNC(name) MND(name, METH_NOARGS) MND(set_##name, METH_O)
@ -2380,6 +2387,7 @@ PyTypeObject Screen_Type = {
static PyMethodDef module_methods[] = { static PyMethodDef module_methods[] = {
{"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""}, {"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""},
{"wcswidth", (PyCFunction)screen_wcswidth, METH_O, ""}, {"wcswidth", (PyCFunction)screen_wcswidth, METH_O, ""},
{"is_emoji_presentation_base", (PyCFunction)screen_is_emoji_presentation_base, METH_O, ""},
{"truncate_point_for_length", (PyCFunction)screen_truncate_point_for_length, METH_VARARGS, ""}, {"truncate_point_for_length", (PyCFunction)screen_truncate_point_for_length, METH_VARARGS, ""},
{NULL} /* Sentinel */ {NULL} /* Sentinel */
}; };