Implement searching for unicode characters by name
This commit is contained in:
parent
193ac6cd81
commit
dab852c412
@ -62,7 +62,7 @@ UNDERLINE_STYLES = {name: i + 1 for i, name in enumerate(
|
||||
'straight double curly'.split())}
|
||||
|
||||
|
||||
def _color(color, intense=False, base=30):
|
||||
def color_code(color, intense=False, base=30):
|
||||
if isinstance(color, str):
|
||||
e = str((base + 60 if intense else base) + STANDARD_COLORS[color])
|
||||
elif isinstance(color, int):
|
||||
@ -72,23 +72,27 @@ def _color(color, intense=False, base=30):
|
||||
return e
|
||||
|
||||
|
||||
def colored(text, color, intense=False):
|
||||
e = _color(color, intense)
|
||||
return '\033[{}m{}\033[39m'.format(e, text)
|
||||
def sgr(*parts):
|
||||
return '\033[{}m'.format(';'.join(parts))
|
||||
|
||||
|
||||
def colored(text, color, intense=False, reset_to=None, reset_to_intense=False):
|
||||
e = color_code(color, intense)
|
||||
return '\033[{}m{}\033[{}m'.format(e, text, 39 if reset_to is None else color_code(reset_to, reset_to_intense))
|
||||
|
||||
|
||||
def styled(text, fg=None, bg=None, fg_intense=False, bg_intense=False, italic=None, bold=None, underline=None, underline_color=None, reverse=None):
|
||||
start, end = [], []
|
||||
if fg is not None:
|
||||
start.append(_color(fg, fg_intense))
|
||||
start.append(color_code(fg, fg_intense))
|
||||
end.append('39')
|
||||
if bg is not None:
|
||||
start.append(_color(bg, bg_intense, 40))
|
||||
start.append(color_code(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))
|
||||
start.append(color_code(underline_color, base=50))
|
||||
end.append('59')
|
||||
if underline is not None:
|
||||
start.append('4:{}'.format(UNDERLINE_STYLES[underline]))
|
||||
|
||||
@ -10,13 +10,15 @@ from gettext import gettext as _
|
||||
from kitty.config import cached_values_for
|
||||
from kitty.fast_data_types import wcswidth
|
||||
from kitty.key_encoding import (
|
||||
ESCAPE, F1, F2, RELEASE, backspace_key, enter_key
|
||||
DOWN, ESCAPE, F1, F2, LEFT, RELEASE, RIGHT, SHIFT, TAB, UP, backspace_key,
|
||||
enter_key
|
||||
)
|
||||
|
||||
from ..tui.handler import Handler
|
||||
from ..tui.loop import Loop
|
||||
from ..tui.operations import (
|
||||
clear_screen, colored, cursor, set_line_wrapping, set_window_title, styled
|
||||
clear_screen, color_code, colored, cursor, set_line_wrapping,
|
||||
set_window_title, sgr, styled
|
||||
)
|
||||
|
||||
HEX, NAME = 'HEX', 'NAME'
|
||||
@ -25,10 +27,10 @@ HEX, NAME = 'HEX', 'NAME'
|
||||
@lru_cache(maxsize=256)
|
||||
def points_for_word(w):
|
||||
from .unicode_names import codepoints_for_word
|
||||
return codepoints_for_word(w.lower())
|
||||
return codepoints_for_word(w)
|
||||
|
||||
|
||||
@lru_cache(maxsize=256)
|
||||
@lru_cache(maxsize=4096)
|
||||
def name(cp):
|
||||
from .unicode_names import name_for_codepoint
|
||||
if isinstance(cp, str):
|
||||
@ -36,6 +38,25 @@ def name(cp):
|
||||
return (name_for_codepoint(cp) or '').capitalize()
|
||||
|
||||
|
||||
@lru_cache(maxsize=256)
|
||||
def codepoints_matching_search(text):
|
||||
parts = text.lower().split()
|
||||
ans = []
|
||||
if parts and parts[0]:
|
||||
codepoints = points_for_word(parts[0])
|
||||
for word in parts[1:]:
|
||||
pts = points_for_word(word)
|
||||
if pts:
|
||||
intersection = codepoints & pts
|
||||
if intersection:
|
||||
codepoints = intersection
|
||||
continue
|
||||
codepoints = {c for c in codepoints if word in name(c).lower()}
|
||||
if codepoints:
|
||||
ans = list(sorted(codepoints))
|
||||
return ans
|
||||
|
||||
|
||||
FAINT = 242
|
||||
DEFAULT_SET = tuple(map(
|
||||
ord,
|
||||
@ -65,14 +86,21 @@ class Table:
|
||||
self.layout_dirty = True
|
||||
self.last_rows = self.last_cols = -1
|
||||
self.codepoints = []
|
||||
self.current_idx = 0
|
||||
self.text = ''
|
||||
self.num_cols = self.num_rows = 0
|
||||
self.mode = HEX
|
||||
|
||||
@property
|
||||
def current_codepoint(self):
|
||||
if self.codepoints:
|
||||
return self.codepoints[self.current_idx]
|
||||
|
||||
def set_codepoints(self, codepoints, mode=HEX):
|
||||
self.codepoints = codepoints
|
||||
self.mode = HEX
|
||||
self.mode = mode
|
||||
self.layout_dirty = True
|
||||
self.current_idx = 0
|
||||
|
||||
def codepoint_at_hint(self, hint):
|
||||
return self.codepoints[decode_hint(hint)]
|
||||
@ -83,9 +111,38 @@ class Table:
|
||||
self.last_cols, self.last_rows = cols, rows
|
||||
self.layout_dirty = False
|
||||
|
||||
if self.mode is NAME:
|
||||
def as_parts(i, codepoint):
|
||||
return encode_hint(i).ljust(idx_size), chr(codepoint), name(codepoint)
|
||||
|
||||
def cell(i, idx, c, desc):
|
||||
is_current = i == self.current_idx
|
||||
if is_current:
|
||||
yield sgr(color_code('gray', base=40))
|
||||
yield colored(idx, 'green') + ' '
|
||||
yield colored(c, 'black' if is_current else 'gray', True) + ' '
|
||||
w = wcswidth(c)
|
||||
if w < 2:
|
||||
yield ' ' * (2 - w)
|
||||
if len(desc) > space_for_desc:
|
||||
desc = desc[:space_for_desc - 1] + '…'
|
||||
yield colored(desc, FAINT)
|
||||
extra = space_for_desc - len(desc)
|
||||
if extra > 0:
|
||||
yield ' ' * extra
|
||||
if is_current:
|
||||
yield sgr('49')
|
||||
else:
|
||||
def as_parts(i, codepoint):
|
||||
return encode_hint(i).ljust(idx_size), chr(codepoint), ''
|
||||
|
||||
def cell(i, idx, c, desc):
|
||||
yield colored(idx, 'green') + ' '
|
||||
yield colored(c, 'gray', True) + ' '
|
||||
w = wcswidth(c)
|
||||
if w < 2:
|
||||
yield ' ' * (2 - w)
|
||||
|
||||
num = len(self.codepoints)
|
||||
if num < 1:
|
||||
self.text = ''
|
||||
@ -94,35 +151,40 @@ class Table:
|
||||
idx_size = len(encode_hint(num - 1))
|
||||
|
||||
parts = [as_parts(i, c) for i, c in enumerate(self.codepoints)]
|
||||
if self.mode is NAME:
|
||||
sizes = [idx_size + 2 + len(p[2]) + 2 for p in parts]
|
||||
else:
|
||||
sizes = [idx_size + 3 for p in parts]
|
||||
longest = max(sizes) if sizes else 0
|
||||
col_width = longest + 2
|
||||
col_width = min(col_width, 40)
|
||||
space_for_desc = col_width - 2 - idx_size - 4
|
||||
num_cols = cols // col_width
|
||||
num_cols = self.num_cols = cols // col_width
|
||||
buf = []
|
||||
rows_left = rows
|
||||
a = buf.append
|
||||
rows_left = self.num_rows = rows
|
||||
|
||||
for i, (idx, c, desc) in enumerate(parts):
|
||||
if i > 0 and i % num_cols == 0:
|
||||
rows_left -= 1
|
||||
if rows_left == 0:
|
||||
break
|
||||
a('\r\n')
|
||||
a(colored(idx, 'green')), a(' '), a(colored(c, 'gray', True)), a(' ')
|
||||
w = wcswidth(c)
|
||||
if w < 2:
|
||||
a(' ' * (2 - w))
|
||||
if len(desc) > space_for_desc:
|
||||
desc = desc[:space_for_desc - 1] + '…'
|
||||
a(colored(desc, FAINT))
|
||||
extra = space_for_desc - len(desc)
|
||||
if extra > 0:
|
||||
a(' ' * extra)
|
||||
buf.append('\r\n')
|
||||
buf.extend(cell(i, idx, c, desc))
|
||||
a(' ')
|
||||
self.text = ''.join(buf)
|
||||
return self.text
|
||||
|
||||
def move_current(self, rows=0, cols=0):
|
||||
if cols:
|
||||
self.current_idx = (self.current_idx + len(self.codepoints) + cols) % len(self.codepoints)
|
||||
self.layout_dirty = True
|
||||
if rows:
|
||||
amt = rows * self.num_cols
|
||||
self.current_idx += amt
|
||||
self.current_idx = max(0, min(self.current_idx, len(self.codepoints) - 1))
|
||||
self.layout_dirty = True
|
||||
|
||||
|
||||
class UnicodeInput(Handler):
|
||||
|
||||
@ -132,14 +194,27 @@ class UnicodeInput(Handler):
|
||||
self.current_input = ''
|
||||
self.current_char = None
|
||||
self.prompt_template = '{}> '
|
||||
self.last_updated_code_point_at = None
|
||||
self.choice_line = ''
|
||||
self.mode = globals().get(cached_values.get('mode', 'HEX'), 'HEX')
|
||||
self.table = Table()
|
||||
if self.mode is HEX:
|
||||
self.table.set_codepoints(self.recent, self.mode)
|
||||
self.update_prompt()
|
||||
|
||||
def update_codepoints(self):
|
||||
codepoints = None
|
||||
if self.mode is HEX:
|
||||
q = self.mode, None
|
||||
codepoints = self.recent
|
||||
elif self.mode is NAME:
|
||||
q = self.mode, self.current_input
|
||||
if q != self.last_updated_code_point_at:
|
||||
codepoints = codepoints_matching_search(self.current_input)
|
||||
if q != self.last_updated_code_point_at:
|
||||
self.last_updated_code_point_at = q
|
||||
self.table.set_codepoints(codepoints, self.mode)
|
||||
|
||||
def update_current_char(self):
|
||||
self.update_codepoints()
|
||||
self.current_char = None
|
||||
if self.mode is HEX:
|
||||
try:
|
||||
@ -151,17 +226,9 @@ class UnicodeInput(Handler):
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
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}
|
||||
cc = self.table.current_codepoint
|
||||
if cc:
|
||||
self.current_char = chr(cc)
|
||||
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:
|
||||
@ -210,18 +277,21 @@ class UnicodeInput(Handler):
|
||||
self.print(text)
|
||||
y += 1
|
||||
|
||||
if self.mode is HEX:
|
||||
if self.mode is NAME:
|
||||
writeln(_('Enter the hex code for the character'))
|
||||
else:
|
||||
elif self.mode is HEX:
|
||||
writeln(_('Enter words from the name of the character'))
|
||||
else:
|
||||
writeln(_('Enter the index for the character you want from the list below'))
|
||||
self.write(self.prompt)
|
||||
self.write(self.current_input)
|
||||
with cursor(self.write):
|
||||
writeln()
|
||||
if self.choice_line:
|
||||
writeln(self.choice_line)
|
||||
if self.mode is HEX:
|
||||
writeln(styled('Use r followed by the index for the recent entries below', fg=FAINT))
|
||||
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))
|
||||
self.table_at = y
|
||||
self.write(self.table.layout(self.screen_size.rows - self.table_at, self.screen_size.cols))
|
||||
|
||||
@ -246,6 +316,20 @@ class UnicodeInput(Handler):
|
||||
self.switch_mode(HEX)
|
||||
elif key_event.key is F2:
|
||||
self.switch_mode(NAME)
|
||||
elif self.mode is NAME:
|
||||
if key_event.key is TAB:
|
||||
if key_event.mods == SHIFT:
|
||||
self.table.move_current(cols=-1), self.refresh()
|
||||
elif not key_event.mods:
|
||||
self.table.move_current(cols=1), self.refresh()
|
||||
elif key_event.key is LEFT and not key_event.mods:
|
||||
self.table.move_current(cols=-1), self.refresh()
|
||||
elif key_event.key is RIGHT and not key_event.mods:
|
||||
self.table.move_current(cols=1), self.refresh()
|
||||
elif key_event.key is UP and not key_event.mods:
|
||||
self.table.move_current(rows=-1), self.refresh()
|
||||
elif key_event.key is DOWN and not key_event.mods:
|
||||
self.table.move_current(rows=1), self.refresh()
|
||||
|
||||
def switch_mode(self, mode):
|
||||
if mode is not self.mode:
|
||||
|
||||
@ -27,7 +27,7 @@ codepoints_for_word(const char *word, size_t len) {
|
||||
for (unsigned short i = 1; i <= words[0]; i++) {
|
||||
unsigned short word_idx = words[i];
|
||||
const char *w = idx_to_word[word_idx];
|
||||
if(strncmp(word, w, len) == 0) {
|
||||
if (strncmp(word, w, len) == 0 && strlen(w) == len) {
|
||||
const char_type* codepoints = codepoints_for_word_idx[word_idx];
|
||||
for (char_type i = 1; i <= codepoints[0]; i++) {
|
||||
PyObject *t = PyLong_FromUnsignedLong(codepoints[i]); if (t == NULL) { Py_DECREF(ans); return NULL; }
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user