Refactor to make TerminalWidget self contained
This commit is contained in:
parent
5088f9b8e5
commit
226e333e9e
@ -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):
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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))
|
||||||
|
|||||||
@ -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]):
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user