Refactor to make TerminalWidget self contained

This commit is contained in:
Kovid Goyal 2016-10-20 23:40:24 +05:30
parent 5088f9b8e5
commit 226e333e9e
5 changed files with 38 additions and 28 deletions

View File

@ -7,16 +7,13 @@ import io
from PyQt5.QtCore import QObject, QSocketNotifier from PyQt5.QtCore import QObject, QSocketNotifier
from .screen import Screen
from .term import TerminalWidget from .term import TerminalWidget
from .utils import resize_pty, hangup, create_pty from .utils import resize_pty, hangup, create_pty
from .tracker import ChangeTracker
from pyte.streams import Stream
class Boss(QObject): class Boss(QObject):
def __init__(self, opts, parent): def __init__(self, opts, parent, dump_commands):
QObject.__init__(self, parent) QObject.__init__(self, parent)
self.shutting_down = False self.shutting_down = False
self.write_buf = memoryview(b'') self.write_buf = memoryview(b'')
@ -25,17 +22,13 @@ class Boss(QObject):
self.write_notifier = QSocketNotifier(create_pty()[0], QSocketNotifier.Write, self) self.write_notifier = QSocketNotifier(create_pty()[0], QSocketNotifier.Write, self)
self.write_notifier.setEnabled(False) self.write_notifier.setEnabled(False)
self.write_notifier.activated.connect(self.write_ready) self.write_notifier.activated.connect(self.write_ready)
self.tracker = ChangeTracker(self) self.term = TerminalWidget(opts, parent, dump_commands)
self.screen = s = Screen(opts, self.tracker, parent=self)
self.stream = Stream(s)
s.write_to_child.connect(self.write_to_child)
self.term = TerminalWidget(opts, self.tracker, self.screen.linebuf, parent)
self.term.relayout_lines.connect(self.relayout_lines) self.term.relayout_lines.connect(self.relayout_lines)
self.term.send_data_to_child.connect(self.write_to_child) self.term.send_data_to_child.connect(self.write_to_child)
resize_pty(self.screen.columns, self.screen.lines) self.term.write_to_child.connect(self.write_to_child)
resize_pty(80, 24)
def apply_opts(self, opts): def apply_opts(self, opts):
self.screen.apply_opts(opts)
self.term.apply_opts(opts) self.term.apply_opts(opts)
def read_ready(self, read_fd): def read_ready(self, read_fd):
@ -50,7 +43,7 @@ class Boss(QObject):
self.read_notifier.setEnabled(False) self.read_notifier.setEnabled(False)
self.parent().child_process_died() self.parent().child_process_died()
return return
self.stream.feed(data) self.term.feed(data)
def write_ready(self, write_fd): def write_ready(self, write_fd):
if not self.shutting_down: if not self.shutting_down:
@ -65,8 +58,7 @@ class Boss(QObject):
self.write_buf = memoryview(self.write_buf.tobytes() + data) self.write_buf = memoryview(self.write_buf.tobytes() + data)
self.write_notifier.setEnabled(True) self.write_notifier.setEnabled(True)
def relayout_lines(self, previous, cells_per_line, previousl, lines_per_screen): def relayout_lines(self, cells_per_line, lines_per_screen):
self.screen.resize(lines_per_screen, cells_per_line)
resize_pty(cells_per_line, lines_per_screen) resize_pty(cells_per_line, lines_per_screen)
def shutdown(self): def shutdown(self):

View File

@ -24,13 +24,13 @@ class MainWindow(QMainWindow):
report_error = pyqtSignal(object) report_error = pyqtSignal(object)
def __init__(self, opts): def __init__(self, opts, dump_commands):
QMainWindow.__init__(self) QMainWindow.__init__(self)
self.setWindowTitle(appname) self.setWindowTitle(appname)
sys.excepthook = self.on_unhandled_error sys.excepthook = self.on_unhandled_error
self.report_error.connect(self.show_error, type=Qt.QueuedConnection) self.report_error.connect(self.show_error, type=Qt.QueuedConnection)
self.handle_unix_signals() self.handle_unix_signals()
self.boss = Boss(opts, self) self.boss = Boss(opts, self, dump_commands)
self.setCentralWidget(self.boss.term) self.setCentralWidget(self.boss.term)
def on_unhandled_error(self, etype, value, tb): def on_unhandled_error(self, etype, value, tb):
@ -85,6 +85,7 @@ def option_parser():
a('-d', '--directory', default='.', help=_('Change to the specified directory when launching')) a('-d', '--directory', default='.', help=_('Change to the specified directory when launching'))
a('--version', action='version', version='{} {} by Kovid Goyal'.format(appname, '.'.join(str_version))) a('--version', action='version', version='{} {} by Kovid Goyal'.format(appname, '.'.join(str_version)))
a('--profile', action='store_true', default=False, help=_('Show profiling data after exit')) a('--profile', action='store_true', default=False, help=_('Show profiling data after exit'))
a('--dump-commands', action='store_true', default=False, help=_('Output commands received from child process to stdout'))
return parser return parser
@ -114,7 +115,7 @@ def main():
validate_font(opts) validate_font(opts)
except ValueError as err: except ValueError as err:
raise SystemExit(str(err)) from None raise SystemExit(str(err)) from None
w = MainWindow(opts) w = MainWindow(opts, args.dump_commands)
w.show() w.show()
if args.profile: if args.profile:
import cProfile import cProfile

View File

@ -69,6 +69,9 @@ class Screen(QObject):
if sz != self.tophistorybuf.maxlen: if sz != self.tophistorybuf.maxlen:
self.tophistorybuf = deque(self.tophistorybuf, maxlen=sz) self.tophistorybuf = deque(self.tophistorybuf, maxlen=sz)
def line(self, i):
return self.linebuf[i]
def __repr__(self): def __repr__(self):
return ("{0}({1}, {2})".format(self.__class__.__name__, return ("{0}({1}, {2})".format(self.__class__.__name__,
self.columns, self.lines)) self.columns, self.lines))

View File

@ -4,18 +4,20 @@
from functools import lru_cache from functools import lru_cache
from itertools import product from itertools import product
from typing import Tuple, Iterator, Sequence from typing import Tuple, Iterator
from PyQt5.QtCore import pyqtSignal, QTimer, QRect, Qt from PyQt5.QtCore import pyqtSignal, QTimer, QRect, Qt
from PyQt5.QtGui import QColor, QPainter, QFont, QFontMetrics, QRegion, QPen, QPixmap from PyQt5.QtGui import QColor, QPainter, QFont, QFontMetrics, QRegion, QPen, QPixmap
from PyQt5.QtWidgets import QWidget from PyQt5.QtWidgets import QWidget
from .config import build_ansi_color_tables, Options, fg_color_table, bg_color_table from .config import build_ansi_color_tables, Options, fg_color_table, bg_color_table
from .data_types import Line, Cursor, HAS_BG_MASK, COL_SHIFT, COL_MASK, as_color from .data_types import Cursor, HAS_BG_MASK, COL_SHIFT, COL_MASK, as_color
from .utils import set_current_font_metrics from .utils import set_current_font_metrics
from .tracker import ChangeTracker from .tracker import ChangeTracker
from .screen import wrap_cursor_position from .screen import wrap_cursor_position
from .keys import key_event_to_data from .keys import key_event_to_data
from .screen import Screen
from pyte.streams import Stream, DebugStream
def ascii_width(fm: QFontMetrics) -> int: def ascii_width(fm: QFontMetrics) -> int:
@ -41,18 +43,24 @@ def pixmap_for_text(text, color, default_fg, font, w, h, baseline):
class TerminalWidget(QWidget): class TerminalWidget(QWidget):
relayout_lines = pyqtSignal(object, object, object, object) relayout_lines = pyqtSignal(object, object)
write_to_child = pyqtSignal(object)
send_data_to_child = pyqtSignal(object) send_data_to_child = pyqtSignal(object)
cells_per_line = 80 cells_per_line = 80
lines_per_screen = 24 lines_per_screen = 24
def __init__(self, opts: Options, tracker: ChangeTracker, linebuf: Sequence[Line], parent: QWidget=None): def __init__(self, opts: Options, parent: QWidget=None, dump_commands: bool=False):
QWidget.__init__(self, parent) QWidget.__init__(self, parent)
self.cursor = Cursor()
self.tracker = ChangeTracker(self)
self.tracker.dirtied.connect(self.update_screen)
sclass = DebugStream if dump_commands else Stream
self.screen = Screen(opts, self.tracker, parent=self)
self.screen.write_to_child.connect(self.write_to_child)
self.stream = sclass(self.screen)
self.feed = self.stream.feed
self.last_drew_cursor_at = (0, 0) self.last_drew_cursor_at = (0, 0)
self.setFocusPolicy(Qt.WheelFocus) self.setFocusPolicy(Qt.WheelFocus)
tracker.dirtied.connect(self.update_screen)
self.linebuf = linebuf
self.cursor = Cursor()
self.setAutoFillBackground(True) self.setAutoFillBackground(True)
self.apply_opts(opts) self.apply_opts(opts)
self.debounce_resize_timer = t = QTimer(self) self.debounce_resize_timer = t = QTimer(self)
@ -66,6 +74,7 @@ class TerminalWidget(QWidget):
self.pending_update = QRegion() self.pending_update = QRegion()
def apply_opts(self, opts): def apply_opts(self, opts):
self.screen.apply_opts(opts)
self.opts = opts self.opts = opts
pixmap_for_text.cache_clear() pixmap_for_text.cache_clear()
pal = self.palette() pal = self.palette()
@ -97,7 +106,8 @@ class TerminalWidget(QWidget):
self.line_width = self.cells_per_line * self.cell_width self.line_width = self.cells_per_line * self.cell_width
self.layout_size = self.size() self.layout_size = self.size()
if (previous, previousl) != (self.cells_per_line, self.lines_per_screen): if (previous, previousl) != (self.cells_per_line, self.lines_per_screen):
self.relayout_lines.emit(previous, self.cells_per_line, previousl, self.lines_per_screen) self.screen.resize(self.lines_per_screen, self.cells_per_line)
self.relayout_lines.emit(self.cells_per_line, self.lines_per_screen)
self.update() self.update()
def resizeEvent(self, ev): def resizeEvent(self, ev):
@ -180,7 +190,7 @@ class TerminalWidget(QWidget):
x, y = wrap_cursor_position(self.cursor.x, self.cursor.y, len(self.line_positions), len(self.cell_positions)) x, y = wrap_cursor_position(self.cursor.x, self.cursor.y, len(self.line_positions), len(self.cell_positions))
r = QRect(self.cell_positions[x], self.line_positions[y], self.cell_width, self.cell_height) r = QRect(self.cell_positions[x], self.line_positions[y], self.cell_width, self.cell_height)
self.last_drew_cursor_at = x, y self.last_drew_cursor_at = x, y
line = self.linebuf[x] line = self.screen.line(x)
colors = line.basic_cell_data(y)[2] colors = line.basic_cell_data(y)[2]
if colors & HAS_BG_MASK: if colors & HAS_BG_MASK:
bg = as_color(colors >> COL_SHIFT, bg_color_table()) bg = as_color(colors >> COL_SHIFT, bg_color_table())
@ -193,7 +203,7 @@ class TerminalWidget(QWidget):
painter.drawRect(r) painter.drawRect(r)
def paint_cell(self, painter: QPainter, col: int, row: int) -> None: def paint_cell(self, painter: QPainter, col: int, row: int) -> None:
line = self.linebuf[row] line = self.screen.line(row)
ch, attrs, colors = line.basic_cell_data(col) ch, attrs, colors = line.basic_cell_data(col)
x, y = self.cell_positions[col], self.line_positions[row] x, y = self.cell_positions[col], self.line_positions[row]
if colors & HAS_BG_MASK and (col != self.last_drew_cursor_at[0] or row != self.last_drew_cursor_at[1]): if colors & HAS_BG_MASK and (col != self.last_drew_cursor_at[0] or row != self.last_drew_cursor_at[1]):

View File

@ -362,7 +362,10 @@ class DebugStream(Stream):
default, which means -- debug all events). default, which means -- debug all events).
""" """
def __init__(self, to=sys.stdout, only=(), *args, **kwargs): def __init__(self, *args, **kwargs):
to = kwargs.pop('to', sys.stdout)
only = kwargs.pop('only', ())
def safe_str(chunk): def safe_str(chunk):
if isinstance(chunk, bytes): if isinstance(chunk, bytes):
chunk = chunk.decode("utf-8") chunk = chunk.decode("utf-8")
@ -375,6 +378,7 @@ class DebugStream(Stream):
pass pass
class Bugger(object): class Bugger(object):
def __getattr__(self, event): def __getattr__(self, event):
if only and event not in only: if only and event not in only:
return noop return noop