mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-08 22:28:24 +02:00
Work on porting kittens to use new key infrastructure
Also move type definitions into their own module
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
# }}}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user