mirror of
https://github.com/kovidgoyal/kitty.git
synced 2024-11-14 01:20:31 +03:00
510022c3c1
Add more text roles and links. Add an example that broadcasts only to other windows in the current tab. Initial capitalization of the key names in the kbd text role. Add Python type hints for custom kittens. Note about hyperlink support for ls on macOS. Add description text for show_key.
606 lines
22 KiB
Python
606 lines
22 KiB
Python
#!/usr/bin/env python3
|
|
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
|
import os
|
|
import string
|
|
import subprocess
|
|
import sys
|
|
from contextlib import suppress
|
|
from functools import lru_cache
|
|
from gettext import gettext as _
|
|
from typing import (
|
|
Any, Dict, FrozenSet, Generator, Iterable, List, Optional, Sequence, Tuple,
|
|
Union
|
|
)
|
|
|
|
from kitty.cli import parse_args
|
|
from kitty.cli_stub import UnicodeCLIOptions
|
|
from kitty.config import cached_values_for
|
|
from kitty.constants import config_dir
|
|
from kitty.fast_data_types import is_emoji_presentation_base, wcswidth
|
|
from kitty.key_encoding import EventType, KeyEvent
|
|
from kitty.typing import BossType
|
|
from kitty.utils import ScreenSize, get_editor
|
|
|
|
from ..tui.handler import Handler, result_handler
|
|
from ..tui.line_edit import LineEdit
|
|
from ..tui.loop import Loop
|
|
from ..tui.operations import (
|
|
clear_screen, colored, cursor, faint, set_line_wrapping, set_window_title,
|
|
sgr, styled
|
|
)
|
|
from ..tui.utils import report_unhandled_error
|
|
|
|
HEX, NAME, EMOTICONS, FAVORITES = 'HEX', 'NAME', 'EMOTICONS', 'FAVORITES'
|
|
favorites_path = os.path.join(config_dir, 'unicode-input-favorites.conf')
|
|
INDEX_CHAR = '.'
|
|
INDEX_BASE = 36
|
|
DEFAULT_SET = tuple(map(
|
|
ord,
|
|
'‘’“”‹›«»‚„' '😀😛😇😈😉😍😎😮👍👎' '—–§¶†‡©®™' '→⇒•·°±−×÷¼½½¾'
|
|
'…µ¢£€¿¡¨´¸ˆ˜' 'ÀÁÂÃÄÅÆÇÈÉÊË' 'ÌÍÎÏÐÑÒÓÔÕÖØ' 'ŒŠÙÚÛÜÝŸÞßàá' 'âãäåæçèéêëìí'
|
|
'îïðñòóôõöøœš' 'ùúûüýÿþªºαΩ∞'
|
|
))
|
|
EMOTICONS_SET = tuple(range(0x1f600, 0x1f64f + 1))
|
|
all_modes = (
|
|
(_('Code'), 'F1', HEX),
|
|
(_('Name'), 'F2', NAME),
|
|
(_('Emoji'), 'F3', EMOTICONS),
|
|
(_('Favorites'), 'F4', FAVORITES),
|
|
)
|
|
|
|
|
|
def codepoint_ok(code: int) -> bool:
|
|
return not (code <= 32 or code == 127 or 128 <= code <= 159 or 0xd800 <= code <= 0xdbff or 0xDC00 <= code <= 0xDFFF)
|
|
|
|
|
|
@lru_cache(maxsize=256)
|
|
def points_for_word(w: str) -> FrozenSet[int]:
|
|
from .unicode_names import codepoints_for_word
|
|
return codepoints_for_word(w.lower())
|
|
|
|
|
|
@lru_cache(maxsize=4096)
|
|
def name(cp: Union[int, str]) -> str:
|
|
from .unicode_names import name_for_codepoint
|
|
c = ord(cp[0]) if isinstance(cp, str) else cp
|
|
return (name_for_codepoint(c) or '').capitalize()
|
|
|
|
|
|
@lru_cache(maxsize=256)
|
|
def codepoints_matching_search(parts: Tuple[str, ...]) -> List[int]:
|
|
ans = []
|
|
if parts and parts[0] and len(parts[0]) > 1:
|
|
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 = frozenset(c for c in codepoints if word in name(c).lower())
|
|
if codepoints:
|
|
ans = list(sorted(codepoints))
|
|
return ans
|
|
|
|
|
|
def parse_favorites(raw: str) -> Generator[int, None, None]:
|
|
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: Iterable[int]) -> str:
|
|
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(f'{cp:x} # {chr(cp)} {name(cp)}')
|
|
return '\n'.join(ans)
|
|
|
|
|
|
def load_favorites(refresh: bool = False) -> List[int]:
|
|
ans: Optional[List[int]] = 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 = list(parse_favorites(raw)) or list(DEFAULT_SET)
|
|
except FileNotFoundError:
|
|
ans = list(DEFAULT_SET)
|
|
setattr(load_favorites, 'ans', ans)
|
|
return ans
|
|
|
|
|
|
def encode_hint(num: int, digits: str = string.digits + string.ascii_lowercase) -> str:
|
|
res = ''
|
|
d = len(digits)
|
|
while not res or num > 0:
|
|
num, i = divmod(num, d)
|
|
res = digits[i] + res
|
|
return res
|
|
|
|
|
|
def decode_hint(x: str) -> int:
|
|
return int(x, INDEX_BASE)
|
|
|
|
|
|
class Table:
|
|
|
|
def __init__(self, emoji_variation: str) -> None:
|
|
self.emoji_variation = emoji_variation
|
|
self.layout_dirty: bool = True
|
|
self.last_rows = self.last_cols = -1
|
|
self.codepoints: List[int] = []
|
|
self.current_idx = 0
|
|
self.scroll_rows = 0
|
|
self.text = ''
|
|
self.num_cols = 0
|
|
self.num_rows = 0
|
|
self.mode = HEX
|
|
|
|
@property
|
|
def current_codepoint(self) -> Optional[int]:
|
|
if self.codepoints:
|
|
return self.codepoints[self.current_idx]
|
|
return None
|
|
|
|
def set_codepoints(self, codepoints: List[int], mode: str = HEX, current_idx: int = 0) -> None:
|
|
self.codepoints = codepoints
|
|
self.mode = mode
|
|
self.layout_dirty = True
|
|
self.current_idx = current_idx if current_idx < len(codepoints) else 0
|
|
self.scroll_rows = 0
|
|
|
|
def codepoint_at_hint(self, hint: str) -> int:
|
|
return self.codepoints[decode_hint(hint)]
|
|
|
|
def layout(self, rows: int, cols: int) -> Optional[str]:
|
|
if not self.layout_dirty and self.last_cols == cols and self.last_rows == rows:
|
|
return self.text
|
|
self.last_cols, self.last_rows = cols, rows
|
|
self.layout_dirty = False
|
|
|
|
def safe_chr(codepoint: int) -> str:
|
|
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:
|
|
def as_parts(i: int, codepoint: int) -> Tuple[str, str, str]:
|
|
return encode_hint(i).ljust(idx_size), safe_chr(codepoint), name(codepoint)
|
|
|
|
def cell(i: int, idx: str, c: str, desc: str) -> Generator[str, None, None]:
|
|
is_current = i == self.current_idx
|
|
text = colored(idx, 'green') + ' ' + sgr('49') + c + ' '
|
|
w = wcswidth(c)
|
|
if w < 2:
|
|
text += ' ' * (2 - w)
|
|
if len(desc) > space_for_desc:
|
|
text += f'{desc[:space_for_desc - 1]}…'
|
|
else:
|
|
text += desc
|
|
extra = space_for_desc - len(desc)
|
|
if extra > 0:
|
|
text += ' ' * extra
|
|
|
|
yield styled(text, reverse=True if is_current else None)
|
|
|
|
else:
|
|
def as_parts(i: int, codepoint: int) -> Tuple[str, str, str]:
|
|
return encode_hint(i).ljust(idx_size), safe_chr(codepoint), ''
|
|
|
|
def cell(i: int, idx: str, c: str, desc: str) -> Generator[str, None, None]:
|
|
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 = ''
|
|
self.num_cols = 0
|
|
self.num_rows = 0
|
|
return self.text
|
|
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]
|
|
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 = self.num_cols = max(cols // col_width, 1)
|
|
buf: List[str] = []
|
|
a = buf.append
|
|
rows_left = self.num_rows = rows
|
|
skip_scroll = self.scroll_rows * num_cols
|
|
|
|
for i, (idx, c, desc) in enumerate(parts):
|
|
if skip_scroll > 0:
|
|
skip_scroll -= 1
|
|
continue
|
|
buf.extend(cell(i, idx, c, desc))
|
|
a(' ')
|
|
if i > 0 and (i+1) % num_cols == 0:
|
|
rows_left -= 1
|
|
if rows_left == 0:
|
|
break
|
|
a('\r\n')
|
|
self.text = ''.join(buf)
|
|
return self.text
|
|
|
|
def move_current(self, rows: int = 0, cols: int = 0) -> None:
|
|
if len(self.codepoints) == 0:
|
|
return
|
|
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
|
|
first_visible = self.scroll_rows * self.num_cols
|
|
last_visible = first_visible + ((self.num_cols * self.num_rows) - 1)
|
|
scroll_amount = self.num_rows
|
|
if self.current_idx < first_visible:
|
|
self.scroll_rows = max(self.scroll_rows - scroll_amount, 0)
|
|
if self.current_idx > last_visible:
|
|
self.scroll_rows += scroll_amount
|
|
|
|
|
|
def is_index(w: str) -> bool:
|
|
if w[0] != INDEX_CHAR:
|
|
return False
|
|
try:
|
|
int(w.lstrip(INDEX_CHAR), INDEX_BASE)
|
|
return True
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
class UnicodeInput(Handler):
|
|
|
|
overlay_ready_report_needed = True
|
|
|
|
def __init__(self, cached_values: Dict[str, Any], emoji_variation: str = 'none') -> None:
|
|
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.recent = list(self.cached_values.get('recent', DEFAULT_SET))
|
|
self.current_char: Optional[str] = None
|
|
self.prompt_template = '{}> '
|
|
self.last_updated_code_point_at: Optional[Tuple[str, Union[Sequence[int], None, str]]] = None
|
|
self.choice_line = ''
|
|
self.mode = globals().get(cached_values.get('mode', 'HEX'), 'HEX')
|
|
self.table = Table(self.emoji_variation)
|
|
self.update_prompt()
|
|
|
|
@property
|
|
def resolved_current_char(self) -> Optional[str]:
|
|
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) -> None:
|
|
codepoints = None
|
|
iindex_word = 0
|
|
if self.mode is HEX:
|
|
q: Tuple[str, Optional[Union[str, Sequence[int]]]] = (self.mode, None)
|
|
codepoints = self.recent
|
|
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.line_edit.current_input
|
|
if q != self.last_updated_code_point_at:
|
|
words = self.line_edit.current_input.split()
|
|
words = [w for w in words if w != INDEX_CHAR]
|
|
index_words = [i for i, w in enumerate(words) if i > 0 and is_index(w)]
|
|
if index_words:
|
|
index_word = words[index_words[0]]
|
|
words = words[:index_words[0]]
|
|
iindex_word = int(index_word.lstrip(INDEX_CHAR), INDEX_BASE)
|
|
codepoints = codepoints_matching_search(tuple(words))
|
|
if q != self.last_updated_code_point_at:
|
|
self.last_updated_code_point_at = q
|
|
self.table.set_codepoints(codepoints or [], self.mode, iindex_word)
|
|
|
|
def update_current_char(self) -> None:
|
|
self.update_codepoints()
|
|
self.current_char = None
|
|
if self.mode is HEX:
|
|
with suppress(Exception):
|
|
if self.line_edit.current_input.startswith(INDEX_CHAR):
|
|
if len(self.line_edit.current_input) > 1:
|
|
self.current_char = chr(self.table.codepoint_at_hint(self.line_edit.current_input[1:]))
|
|
elif self.line_edit.current_input:
|
|
code = int(self.line_edit.current_input, 16)
|
|
self.current_char = chr(code)
|
|
elif self.mode is NAME:
|
|
cc = self.table.current_codepoint
|
|
if cc:
|
|
self.current_char = chr(cc)
|
|
else:
|
|
with suppress(Exception):
|
|
if self.line_edit.current_input:
|
|
self.current_char = chr(self.table.codepoint_at_hint(self.line_edit.current_input.lstrip(INDEX_CHAR)))
|
|
if self.current_char is not None:
|
|
code = ord(self.current_char)
|
|
if not codepoint_ok(code):
|
|
self.current_char = None
|
|
|
|
def update_prompt(self) -> None:
|
|
self.update_current_char()
|
|
if self.current_char is None:
|
|
c, color = '??', 'red'
|
|
self.choice_line = ''
|
|
else:
|
|
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(
|
|
colored(c, 'green'), hex(ord(c[0]))[2:], faint(styled(name(c) or '', italic=True)))
|
|
self.prompt = self.prompt_template.format(colored(c, color))
|
|
|
|
def init_terminal_state(self) -> None:
|
|
self.write(set_line_wrapping(False))
|
|
self.write(set_window_title(_('Unicode input')))
|
|
|
|
def initialize(self) -> None:
|
|
self.init_terminal_state()
|
|
self.draw_screen()
|
|
|
|
def draw_title_bar(self) -> None:
|
|
entries = []
|
|
for name, key, mode in all_modes:
|
|
entry = f' {name} ({key}) '
|
|
if mode is self.mode:
|
|
entry = styled(entry, reverse=False, bold=True)
|
|
entries.append(entry)
|
|
text = _('Search by:{}').format(' '.join(entries))
|
|
extra = self.screen_size.cols - wcswidth(text)
|
|
if extra > 0:
|
|
text += ' ' * extra
|
|
self.print(styled(text, reverse=True))
|
|
|
|
@Handler.atomic_update
|
|
def draw_screen(self) -> None:
|
|
self.write(clear_screen())
|
|
self.draw_title_bar()
|
|
y = 1
|
|
|
|
def writeln(text: str = '') -> None:
|
|
nonlocal y
|
|
self.print(text)
|
|
y += 1
|
|
|
|
if self.mode is NAME:
|
|
writeln(_('Enter words from the name of the character'))
|
|
elif self.mode is HEX:
|
|
writeln(_('Enter the hex code for the character'))
|
|
else:
|
|
writeln(_('Enter the index for the character you want from the list below'))
|
|
self.line_edit.write(self.write, self.prompt)
|
|
with cursor(self.write):
|
|
writeln()
|
|
writeln(self.choice_line)
|
|
if self.mode is HEX:
|
|
writeln(faint(_('Type {} followed by the index for the recent entries below').format(INDEX_CHAR)))
|
|
elif self.mode is NAME:
|
|
writeln(faint(_('Use Tab or the arrow keys to choose a character from below')))
|
|
elif self.mode is FAVORITES:
|
|
writeln(faint(_('Press F12 to edit the list of favorites')))
|
|
self.table_at = y
|
|
q = self.table.layout(self.screen_size.rows - self.table_at, self.screen_size.cols)
|
|
if q:
|
|
self.write(q)
|
|
|
|
def refresh(self) -> None:
|
|
self.update_prompt()
|
|
self.draw_screen()
|
|
|
|
def on_text(self, text: str, in_bracketed_paste: bool = False) -> None:
|
|
self.line_edit.on_text(text, in_bracketed_paste)
|
|
self.refresh()
|
|
|
|
def on_key(self, key_event: KeyEvent) -> None:
|
|
if self.mode is HEX and key_event.type is not EventType.RELEASE and not key_event.has_mods:
|
|
try:
|
|
val = int(self.line_edit.current_input, 16)
|
|
except Exception:
|
|
pass
|
|
else:
|
|
if key_event.matches('tab'):
|
|
self.line_edit.current_input = hex(val + 0x10)[2:]
|
|
self.refresh()
|
|
return
|
|
if key_event.matches('up'):
|
|
self.line_edit.current_input = hex(val + 1)[2:]
|
|
self.refresh()
|
|
return
|
|
if key_event.matches('down'):
|
|
self.line_edit.current_input = hex(val - 1)[2:]
|
|
self.refresh()
|
|
return
|
|
if self.mode is NAME:
|
|
if key_event.matches('shift+tab'):
|
|
self.table.move_current(cols=-1)
|
|
self.refresh()
|
|
return
|
|
if key_event.matches('tab'):
|
|
self.table.move_current(cols=1)
|
|
self.refresh()
|
|
return
|
|
if key_event.matches('left'):
|
|
self.table.move_current(cols=-1)
|
|
self.refresh()
|
|
return
|
|
if key_event.matches('right'):
|
|
self.table.move_current(cols=1)
|
|
self.refresh()
|
|
return
|
|
if key_event.matches('up'):
|
|
self.table.move_current(rows=-1)
|
|
self.refresh()
|
|
return
|
|
if key_event.matches('down'):
|
|
self.table.move_current(rows=1)
|
|
self.refresh()
|
|
return
|
|
|
|
if self.line_edit.on_key(key_event):
|
|
self.refresh()
|
|
return
|
|
if key_event.matches('enter'):
|
|
self.quit_loop(0)
|
|
return
|
|
if key_event.matches('esc'):
|
|
self.quit_loop(1)
|
|
return
|
|
if key_event.matches_without_mods('f1') or key_event.matches('ctrl+1'):
|
|
self.switch_mode(HEX)
|
|
return
|
|
if key_event.matches_without_mods('f2') or key_event.matches('ctrl+2'):
|
|
self.switch_mode(NAME)
|
|
return
|
|
if key_event.matches_without_mods('f3') or key_event.matches('ctrl+3'):
|
|
self.switch_mode(EMOTICONS)
|
|
return
|
|
if key_event.matches_without_mods('f4') or key_event.matches('ctrl+4'):
|
|
self.switch_mode(FAVORITES)
|
|
return
|
|
if key_event.matches_without_mods('f12') and self.mode is FAVORITES:
|
|
self.edit_favorites()
|
|
return
|
|
if key_event.matches('ctrl+shift+tab'):
|
|
self.next_mode(-1)
|
|
return
|
|
for key in ('tab', '[', ']'):
|
|
if key_event.matches(f'ctrl+{key}'):
|
|
self.next_mode(-1 if key == '[' else 1)
|
|
return
|
|
|
|
def edit_favorites(self) -> None:
|
|
if not os.path.exists(favorites_path):
|
|
with open(favorites_path, 'wb') as f:
|
|
f.write(serialize_favorites(load_favorites()).encode('utf-8'))
|
|
with self.suspend():
|
|
p = subprocess.Popen(get_editor() + [favorites_path])
|
|
if p.wait() == 0:
|
|
load_favorites(refresh=True)
|
|
self.init_terminal_state()
|
|
self.refresh()
|
|
|
|
def switch_mode(self, mode: str) -> None:
|
|
if mode is not self.mode:
|
|
self.mode = mode
|
|
self.cached_values['mode'] = mode
|
|
self.line_edit.clear()
|
|
self.current_char = None
|
|
self.choice_line = ''
|
|
self.refresh()
|
|
|
|
def next_mode(self, delta: int = 1) -> None:
|
|
modes = tuple(x[-1] for x in all_modes)
|
|
idx = (modes.index(self.mode) + delta + len(modes)) % len(modes)
|
|
self.switch_mode(modes[idx])
|
|
|
|
def on_interrupt(self) -> None:
|
|
self.quit_loop(1)
|
|
|
|
def on_eot(self) -> None:
|
|
self.quit_loop(1)
|
|
|
|
def on_resize(self, new_size: ScreenSize) -> None:
|
|
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: List[str]) -> Tuple[UnicodeCLIOptions, List[str]]:
|
|
return parse_args(args, OPTIONS, usage, help_text, 'kitty +kitten unicode_input', result_class=UnicodeCLIOptions)
|
|
|
|
|
|
def main(args: List[str]) -> Optional[str]:
|
|
try:
|
|
cli_opts, items = parse_unicode_input_args(args[1:])
|
|
except SystemExit as e:
|
|
if e.code != 0:
|
|
report_unhandled_error(e.args[0])
|
|
return None
|
|
|
|
loop = Loop()
|
|
with cached_values_for('unicode-input') as cached_values:
|
|
handler = UnicodeInput(cached_values, cli_opts.emoji_variation)
|
|
loop.loop(handler)
|
|
if handler.current_char and loop.return_code == 0:
|
|
with suppress(Exception):
|
|
handler.recent.remove(ord(handler.current_char))
|
|
recent = [ord(handler.current_char)] + handler.recent
|
|
cached_values['recent'] = recent[:len(DEFAULT_SET)]
|
|
return handler.resolved_current_char
|
|
if loop.return_code != 0:
|
|
raise SystemExit(loop.return_code)
|
|
return None
|
|
|
|
|
|
@result_handler(has_ready_notification=UnicodeInput.overlay_ready_report_needed)
|
|
def handle_result(args: List[str], current_char: str, target_window_id: int, boss: BossType) -> None:
|
|
w = boss.window_id_map.get(target_window_id)
|
|
if w is not None:
|
|
w.paste_text(current_char)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
ans = main(sys.argv)
|
|
if ans:
|
|
print(ans)
|
|
elif __name__ == '__doc__':
|
|
cd = sys.cli_docs # type: ignore
|
|
cd['usage'] = usage
|
|
cd['options'] = OPTIONS
|
|
cd['help_text'] = help_text
|