Work on porting kittens to use new key infrastructure

Also move type definitions into their own module
This commit is contained in:
Kovid Goyal
2021-01-14 21:35:48 +05:30
parent 0714fd376b
commit 027c5a57f1
30 changed files with 524 additions and 721 deletions

View File

@@ -9,7 +9,7 @@ from typing import Any, Dict, List, Optional, Tuple
from kitty.cli import parse_args
from kitty.cli_stub import BroadcastCLIOptions
from kitty.key_encoding import RELEASE, encode_key_event, key_defs as K
from kitty.key_encoding import encode_key_event
from kitty.rc.base import MATCH_TAB_OPTION, MATCH_WINDOW_OPTION
from kitty.remote_control import create_basic_command, encode_send
from kitty.typing import KeyEventType, ScreenSize
@@ -59,15 +59,15 @@ class Broadcast(Handler):
def on_key(self, key_event: KeyEventType) -> None:
if self.line_edit.on_key(key_event):
self.commit_line()
if key_event.type is not RELEASE and not key_event.mods:
if key_event.key is K['ENTER']:
self.write_broadcast_text('\r')
self.print('')
self.line_edit.clear()
self.write(SAVE_CURSOR)
return
if key_event.matches('enter'):
self.write_broadcast_text('\r')
self.print('')
self.line_edit.clear()
self.write(SAVE_CURSOR)
return
ek = encode_key_event(key_event)
ek = standard_b64encode(ek.encode('utf-8')).decode('ascii')
self.write_broadcast_data('kitty-key:' + ek)
def write_broadcast_text(self, text: str) -> None:

View File

@@ -22,7 +22,7 @@ from kitty.cli_stub import DiffCLIOptions
from kitty.conf.utils import KittensKeyAction
from kitty.constants import appname
from kitty.fast_data_types import wcswidth
from kitty.key_encoding import RELEASE, KeyEvent, enter_key, key_defs as K
from kitty.key_encoding import KeyEvent, EventType
from kitty.options_stub import DiffOptions
from kitty.utils import ScreenSize
@@ -57,7 +57,6 @@ except ImportError:
INITIALIZING, COLLECTED, DIFFED, COMMAND, MESSAGE = range(5)
ESCAPE = K['ESCAPE']
def generate_diff(collection: Collection, context: int) -> Union[str, Dict[str, Patch]]:
@@ -483,48 +482,30 @@ class DiffHandler(Handler):
self.message = sanitize(_('No matches found'))
self.cmd.bell()
def on_text(self, text: str, in_bracketed_paste: bool = False) -> None:
if self.state is COMMAND:
self.line_edit.on_text(text, in_bracketed_paste)
self.draw_status_line()
return
if self.state is MESSAGE:
self.state = DIFFED
self.draw_status_line()
return
action = self.shortcut_action(text)
if action is not None:
return self.perform_action(action)
def on_key(self, key_event: KeyEvent) -> None:
if self.state is MESSAGE:
if key_event.type is not RELEASE:
def on_key_event(self, key_event: KeyEvent, in_bracketed_paste: bool = False) -> None:
if key_event.text:
if self.state is COMMAND:
self.line_edit.on_text(key_event.text, in_bracketed_paste)
self.draw_status_line()
return
if self.state is MESSAGE:
self.state = DIFFED
self.draw_status_line()
return
if self.state is COMMAND:
if self.line_edit.on_key(key_event):
if not self.line_edit.current_input:
return
else:
if self.state is MESSAGE:
if key_event.type is not EventType.RELEASE:
self.state = DIFFED
self.draw_status_line()
self.draw_status_line()
return
if key_event.type is RELEASE:
return
if self.state is COMMAND:
if key_event.key is ESCAPE:
self.state = DIFFED
self.draw_status_line()
if self.state is COMMAND:
if self.line_edit.on_key(key_event):
if not self.line_edit.current_input:
self.state = DIFFED
self.draw_status_line()
return
if key_event.type is EventType.RELEASE:
return
if key_event is enter_key:
self.state = DIFFED
self.do_search()
self.line_edit.clear()
self.draw_screen()
return
if self.state >= DIFFED and self.current_search is not None and key_event.key is ESCAPE:
self.current_search = None
self.draw_screen()
return
action = self.shortcut_action(key_event)
if action is not None:
return self.perform_action(action)

View File

@@ -17,9 +17,7 @@ from typing import (
from kitty.cli import parse_args
from kitty.cli_stub import HintsCLIOptions
from kitty.fast_data_types import set_clipboard_string
from kitty.key_encoding import (
KeyEvent, backspace_key, enter_key, key_defs as K
)
from kitty.key_encoding import KeyEvent
from kitty.typing import BossType, KittyCommonOpts
from kitty.utils import ScreenSize, screen_size_function, set_primary_selection
@@ -40,7 +38,6 @@ def kitty_common_opts() -> KittyCommonOpts:
DEFAULT_HINT_ALPHABET = string.digits + string.ascii_lowercase
DEFAULT_REGEX = r'(?m)^\s*(.+)\s*$'
ESCAPE = K['ESCAPE']
class Mark:
@@ -177,11 +174,11 @@ class Hints(Handler):
self.draw_screen()
def on_key(self, key_event: KeyEvent) -> None:
if key_event is backspace_key:
if key_event.matches('backspace'):
self.current_input = self.current_input[:-1]
self.current_text = None
self.draw_screen()
elif key_event is enter_key and self.current_input:
elif key_event.matches('enter') and self.current_input:
try:
idx = decode_hint(self.current_input, self.alphabet)
self.chosen.append(self.index_map[idx])
@@ -196,7 +193,7 @@ class Hints(Handler):
self.draw_screen()
else:
self.quit_loop(0)
elif key_event.key is ESCAPE:
elif key_event.matches('esc'):
self.quit_loop(0 if self.multiple else 1)
def on_interrupt(self) -> None:
@@ -288,6 +285,7 @@ def quotes(text: str, s: int, e: int) -> Tuple[int, int]:
@postprocessor
def ip(text: str, s: int, e: int) -> Tuple[int, int]:
from ipaddress import ip_address
# Check validity of IPs (or raise InvalidMatch)
ip = text[s:e]

View File

@@ -8,6 +8,7 @@ from typing import (
Any, Callable, ContextManager, Dict, Optional, Sequence, Type, Union
)
from kitty.types import ParsedShortcut
from kitty.typing import (
AbstractEventLoop, BossType, Debug, ImageManagerType, KeyEventType,
KittensKeyActionType, LoopType, MouseEvent, ScreenSize, TermManagerType
@@ -35,6 +36,7 @@ class Handler:
self.debug = debug
self.cmd = commander(self)
self._image_manager = image_manager
self._key_shortcuts: Dict[ParsedShortcut, KittensKeyActionType] = {}
@property
def image_manager(self) -> ImageManagerType:
@@ -45,18 +47,16 @@ class Handler:
def asyncio_loop(self) -> AbstractEventLoop:
return self._tui_loop.asycio_loop
def add_shortcut(self, action: KittensKeyActionType, key: str, mods: Optional[int] = None, is_text: Optional[bool] = False) -> None:
if not hasattr(self, '_text_shortcuts'):
self._text_shortcuts, self._key_shortcuts = {}, {}
if is_text:
self._text_shortcuts[key] = action
else:
self._key_shortcuts[(key, mods or 0)] = action
def add_shortcut(self, action: KittensKeyActionType, spec: Union[str, ParsedShortcut]) -> None:
if isinstance(spec, str):
from kitty.key_encoding import parse_shortcut
spec = parse_shortcut(spec)
self._key_shortcuts[spec] = action
def shortcut_action(self, key_event_or_text: Union[str, KeyEventType]) -> Optional[KittensKeyActionType]:
if isinstance(key_event_or_text, str):
return self._text_shortcuts.get(key_event_or_text)
return self._key_shortcuts.get((key_event_or_text.key, key_event_or_text.mods))
def shortcut_action(self, key_event: KeyEventType) -> Optional[KittensKeyActionType]:
for sc, action in self._key_shortcuts.items():
if key_event.matches(sc):
return action
def __enter__(self) -> None:
if self._image_manager is not None:
@@ -85,6 +85,12 @@ class Handler:
def on_term(self) -> None:
self._tui_loop.quit(1)
def on_key_event(self, key_event: KeyEventType, in_bracketed_paste: bool = False) -> None:
if key_event.text:
self.on_text(key_event.text, in_bracketed_paste)
else:
self.on_key(key_event)
def on_text(self, text: str, in_bracketed_paste: bool = False) -> None:
pass

View File

@@ -5,17 +5,10 @@
from typing import Callable, Tuple
from kitty.fast_data_types import truncate_point_for_length, wcswidth
from kitty.key_encoding import RELEASE, KeyEvent, key_defs as K
from kitty.key_encoding import EventType, KeyEvent
from .operations import RESTORE_CURSOR, SAVE_CURSOR, move_cursor_by
HOME = K['HOME']
END = K['END']
BACKSPACE = K['BACKSPACE']
DELETE = K['DELETE']
LEFT = K['LEFT']
RIGHT = K['RIGHT']
class LineEdit:
@@ -137,22 +130,22 @@ class LineEdit:
return self.cursor_pos != orig
def on_key(self, key_event: KeyEvent) -> bool:
if key_event.type is RELEASE:
if key_event.type is EventType.RELEASE:
return False
elif key_event.key is HOME:
if key_event.matches('home'):
return self.home()
elif key_event.key is END:
if key_event.matches('end'):
return self.end()
elif key_event.key is BACKSPACE:
if key_event.matches('backspace'):
self.backspace()
return True
elif key_event.key is DELETE:
if key_event.matches('delete'):
self.delete()
return True
elif key_event.key is LEFT:
if key_event.matches('left'):
self.left()
return True
elif key_event.key is RIGHT:
if key_event.matches('right'):
self.right()
return True
return False

View File

@@ -21,7 +21,7 @@ from kitty.fast_data_types import (
)
from kitty.key_encoding import (
ALT, CTRL, PRESS, RELEASE, REPEAT, SHIFT, backspace_key, decode_key_event,
enter_key, key_defs as K
enter_key
)
from kitty.typing import ImageManagerType, KeyEventType, Protocol
from kitty.utils import ScreenSizeGetter, screen_size_function, write_all
@@ -29,8 +29,6 @@ from kitty.utils import ScreenSizeGetter, screen_size_function, write_all
from .handler import Handler
from .operations import init_state, reset_state
C, D = K['C'], K['D']
class BinaryWrite(Protocol):
@@ -149,7 +147,7 @@ class UnhandledException(Handler):
self.write('Press the Enter key to quit')
def on_key(self, key_event: KeyEventType) -> None:
if key_event is enter_key:
if key_event.key == 'ENTER':
self.quit_loop(1)
def on_interrupt(self) -> None:
@@ -282,8 +280,23 @@ class Loop:
elif q == '~':
if csi == '200~':
self.in_bracketed_paste = True
return
elif csi == '201~':
self.in_bracketed_paste = False
return
elif q in 'u~ABCDHFPQRS':
try:
k = decode_key_event(csi[:-1], q)
except Exception:
pass
else:
if k.matches('ctrl+c'):
self.handler.on_interrupt()
return
if k.matches('ctrl+d'):
self.handler.on_eot()
return
self.handler.on_key_event(k)
def _on_pm(self, pm: str) -> None:
pass
@@ -300,21 +313,7 @@ class Loop:
self.handler.on_clipboard_response(standard_b64decode(rest).decode('utf-8'), from_primary)
def _on_apc(self, apc: str) -> None:
if apc.startswith('K'):
try:
k = decode_key_event(apc[1:])
except Exception:
pass
else:
if k.mods is CTRL and k.type is not RELEASE:
if k.key is C:
self.handler.on_interrupt()
return
if k.key is D:
self.handler.on_eot()
return
self.handler.on_key(k)
elif apc.startswith('G'):
if apc.startswith('G'):
if self.handler.image_manager is not None:
self.handler.image_manager.handle_response(apc)
# }}}

View File

@@ -40,7 +40,6 @@ MODES = dict(
MOUSE_URXVT_MODE=(1015, '?'),
ALTERNATE_SCREEN=(1049, '?'),
BRACKETED_PASTE=(2004, '?'),
EXTENDED_KEYBOARD=(2017, '?'),
)
F = TypeVar('F')
@@ -270,8 +269,8 @@ def init_state(alternate_screen: bool = True) -> str:
reset_mode('MOUSE_MOTION_TRACKING') + reset_mode('MOUSE_MOVE_TRACKING') +
reset_mode('FOCUS_TRACKING') + reset_mode('MOUSE_UTF8_MODE') +
reset_mode('MOUSE_SGR_MODE') + reset_mode('MOUSE_UTF8_MODE') +
set_mode('BRACKETED_PASTE') + set_mode('EXTENDED_KEYBOARD') +
SAVE_COLORS +
set_mode('BRACKETED_PASTE') + SAVE_COLORS +
'\033[>31u' + # extended keyboard mode
'\033[*x' # reset DECSACE to default region select
)
if alternate_screen:
@@ -287,6 +286,7 @@ def reset_state(normal_screen: bool = True) -> str:
ans += RESTORE_PRIVATE_MODE_VALUES
ans += RESTORE_CURSOR
ans += RESTORE_COLORS
ans += '\033[<u' # restore keyboard mode
return ans