diff --git a/kittens/tui/handler.py b/kittens/tui/handler.py new file mode 100644 index 000000000..8414eda22 --- /dev/null +++ b/kittens/tui/handler.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +# License: GPL v3 Copyright: 2018, Kovid Goyal + + +class Handler: + + def initialize(self, screen_size, quit_loop, wakeup): + self.screen_size, self.quit_loop = screen_size, quit_loop + self.wakeup = wakeup + + def on_resize(self, screen_size): + self.screen_size = screen_size + + def on_term(self): + self.quit_loop(1) + + def on_text(self, text, in_bracketed_paste=False): + pass + + def on_key(self, key_event): + pass + + def on_mouse(self, mouse_event): + pass + + def write(self, data): + if isinstance(data, str): + data = data.encode('utf-8') + self.write_buf.append(data) diff --git a/kittens/tui/loop.py b/kittens/tui/loop.py index 99b23eef6..dea141a7f 100644 --- a/kittens/tui/loop.py +++ b/kittens/tui/loop.py @@ -8,6 +8,7 @@ import io import os import re import selectors +import signal import sys import termios import tty @@ -16,10 +17,12 @@ from contextlib import closing, contextmanager from functools import partial from kitty.fast_data_types import parse_input_from_terminal +from kitty.icat import screen_size from kitty.key_encoding import ( ALT, CTRL, SHIFT, backspace_key, decode_key_event, enter_key ) +from .handler import Handler from .operations import init_state, reset_state @@ -90,6 +93,22 @@ def decode_sgr_mouse(text): return MouseEvent(x, y, typ, buttons, mods) +class UnhandledException(Handler): + + def __init__(self, tb): + self.tb = tb + + def initialize(self, screen_size, quit_loop, wakeup): + Handler.initialize(self, screen_size, quit_loop, wakeup) + self.write(self.tb) + self.write('\n') + self.write('Press the Enter key to quit') + + def on_key(self, key_event): + if key_event is enter_key: + self.quit_loop(1) + + class Loop: def __init__(self, input_fd=None, output_fd=None, sanitize_bracketed_paste='[\x0e\x0f\r\x07\x7f\x8d\x8e\x8f\x90\x9b\x9d\x9e\x9f]'): @@ -110,7 +129,7 @@ class Loop: self.iov_limit = os.sysconf('SC_IOV_MAX') - 1 except Exception: self.iov_limit = 255 - self.parse_input_from_terminal = partial(parse_input_from_terminal, self.on_text, self.on_dcs, self.on_csi, self.on_osc, self.on_pm, self.on_apc) + self.parse_input_from_terminal = partial(parse_input_from_terminal, self._on_text, self._on_dcs, self._on_csi, self.on_osc, self._on_pm, self._on_apc) self.ebs_pat = re.compile('([\177\r])') self.in_bracketed_paste = False self.sanitize_bracketed_paste = bool(sanitize_bracketed_paste) @@ -133,7 +152,7 @@ class Loop: finally: del self.handler - def on_text(self, text): + def _on_text(self, text): if self.in_bracketed_paste and self.sanitize_bracketed_paste: text = self.sanitize_ibp_pat.sub('', text) @@ -145,10 +164,10 @@ class Loop: else: self.handler.on_text(chunk, self.in_bracketed_paste) - def on_dcs(self, dcs): + def _on_dcs(self, dcs): pass - def on_csi(self, csi): + def _on_csi(self, csi): q = csi[-1] if q in 'mM': if csi.startswith('<'): @@ -165,10 +184,10 @@ class Loop: elif csi == '201~': self.in_bracketed_paste = False - def on_pm(self, pm): + def _on_pm(self, pm): pass - def on_apc(self, apc): + def _on_apc(self, apc): if apc.startswith('K'): try: k = decode_key_event(apc) @@ -200,25 +219,41 @@ class Loop: break del handler.write_buf[:consumed] - def _on_unhandled_exception(self, tb): - pass - def _wakeup_ready(self, handler): - os.read(self.wakeup_read_fd) + data = os.read(self.wakeup_read_fd) + if b'r' in data: + screen_size.changed = True + handler.on_resize(screen_size()) + if b't' in data: + handler.on_term() - def wakeup(self): - os.write(self.wakeup_write_fd, b'1') + def _wakeup_write(self, val): + while not os.write(self.wakeup_write_fd, val): + pass + + def _on_sigwinch(self, signum, frame): + self._wakeup_write(b'r') + + def _on_sigterm(self, signum, frame): + self._wakeup_write(b't') def quit(self, return_code=None): self.read_allowed = False if return_code is not None: self.return_code = return_code - def _loop(self, handler): + def wakeup(self): + self._wakeup_write(b'1') + + def loop(self, handler): select = self.sel.select tb = None waiting_for_write = True with closing(self.sel), sanitize_term(self.output_fd), non_block(self.input_fd), non_block(self.output_fd), raw_terminal(self.input_fd): + signal.signal(signal.SIGWINCH, self._on_sigwinch) + signal.signal(signal.SIGTERM, self._on_sigterm) + handler.write_buf = [] + handler.initialize(screen_size(), self.quit, self.wakeup) while True: has_data_to_write = bool(handler.write_buf) if not has_data_to_write and not self.read_allowed: @@ -242,4 +277,21 @@ class Loop: self._report_error_loop(tb) def _report_error_loop(self, tb): - raise NotImplementedError('TODO: Implement') + select = self.sel.select + waiting_for_write = False + handler = UnhandledException(tb) + handler.write_buf = [] + handler.initialize(screen_size(), self.quit, self.wakeup) + while True: + has_data_to_write = bool(handler.write_buf) + if not has_data_to_write and not self.read_allowed: + break + if has_data_to_write != waiting_for_write: + waiting_for_write = has_data_to_write + self.sel.modify( + self.output_fd, selectors.EVENT_WRITE + if waiting_for_write else 0, self._write_ready + ) + events = select() + for key, mask in events: + key.data(handler)