More work on the Screen class

This commit is contained in:
Kovid Goyal 2016-10-20 05:23:47 +05:30
parent b354e95401
commit c0c466a492
3 changed files with 68 additions and 43 deletions

View File

@ -8,6 +8,7 @@ from itertools import repeat
from PyQt5.QtGui import QColor from PyQt5.QtGui import QColor
from pyte.graphics import FG_BG_256
from .config import fg_color_table, bg_color_table from .config import fg_color_table, bg_color_table
code = 'I' if array.array('I').itemsize >= 4 else 'L' code = 'I' if array.array('I').itemsize >= 4 else 'L'
@ -34,6 +35,9 @@ class Cursor:
self.x = x self.x = x
self.y = y self.y = y
self.hidden = False self.hidden = False
self.reset_display_attrs()
def reset_display_attrs(self):
self.fg = self.bg = self.decoration_fg = 0 self.fg = self.bg = self.decoration_fg = 0
self.bold = self.italic = self.reverse = self.strikethrough = False self.bold = self.italic = self.reverse = self.strikethrough = False
self.decoration = 0 self.decoration = 0
@ -283,6 +287,9 @@ def as_color(entry: int, color_table: Dict[int, QColor]) -> Union[QColor, None]:
r = (entry >> 8) & 0xff r = (entry >> 8) & 0xff
return color_table.get(r) return color_table.get(r)
if t == 2: if t == 2:
r = (entry >> 8) & 0xff
return QColor(*FG_BG_256[r])
if t == 3:
r = (entry >> 8) & 0xff r = (entry >> 8) & 0xff
g = (entry >> 16) & 0xff g = (entry >> 16) & 0xff
b = (entry >> 24) & 0xff b = (entry >> 24) & 0xff

View File

@ -31,6 +31,15 @@ Savepoint = namedtuple("Savepoint", [
]) ])
def wrap_cursor_position(x, y, lines, columns):
if x >= columns:
if y < lines - 1:
x, y = 0, y + 1
else:
x, y = x - 1, y
return x, y
class Screen(QObject): class Screen(QObject):
""" """
See standard ECMA-48, Section 6.1.1 http://www.ecma-international.org/publications/standards/Ecma-048.htm See standard ECMA-48, Section 6.1.1 http://www.ecma-international.org/publications/standards/Ecma-048.htm
@ -66,11 +75,7 @@ class Screen(QObject):
def notify_cursor_position(self, x, y): def notify_cursor_position(self, x, y):
if self._notify_cursor_position: if self._notify_cursor_position:
if x >= self.columns: x, y = wrap_cursor_position(x, y, self.lines, self.columns)
if y < self.lines - 1:
x, y = 0, y + 1
else:
x, y = x - 1, y
self.cursor_position_changed(self.cursor, x, y) self.cursor_position_changed(self.cursor, x, y)
@property @property
@ -770,7 +775,7 @@ class Screen(QObject):
""" """
self.cursor_back(count, move_direction=1) self.cursor_back(count, move_direction=1)
def cursor_position(self, line=None, column=None): def cursor_position(self, line=1, column=1):
"""Set the cursor to a specific `line` and `column`. """Set the cursor to a specific `line` and `column`.
Cursor is allowed to move out of the scrolling region only when Cursor is allowed to move out of the scrolling region only when
@ -782,6 +787,7 @@ class Screen(QObject):
""" """
column = (column or 1) - 1 column = (column or 1) - 1
line = (line or 1) - 1 line = (line or 1) - 1
x, y = self.cursor.x, self.cursor.y
# If origin mode (DECOM) is set, line number are relative to # If origin mode (DECOM) is set, line number are relative to
# the top scrolling margin. # the top scrolling margin.
@ -794,21 +800,25 @@ class Screen(QObject):
self.cursor.x, self.cursor.y = column, line self.cursor.x, self.cursor.y = column, line
self.ensure_bounds() self.ensure_bounds()
if y != self.cursor.y or x != self.cursor.x:
self.notify_cursor_position(x, y)
def cursor_to_column(self, column=None): def cursor_to_column(self, column=1):
"""Moves cursor to a specific column in the current line. """Moves cursor to a specific column in the current line.
:param int column: column number to move the cursor to. :param int column: column number to move the cursor to.
""" """
self.cursor.x = (column or 1) - 1 x, self.cursor.x = self.cursor.x, (column or 1) - 1
self.ensure_bounds() self.ensure_bounds()
if x != self.cursor.x:
self.notify_cursor_position(x, self.cursor.y)
def cursor_to_line(self, line=None): def cursor_to_line(self, line=1):
"""Moves cursor to a specific line in the current column. """Moves cursor to a specific line in the current column.
:param int line: line number to move the cursor to. :param int line: line number to move the cursor to.
""" """
self.cursor.y = (line or 1) - 1 y, self.cursor.y = self.cursor.y, (line or 1) - 1
# If origin mode (DECOM) is set, line number are relative to # If origin mode (DECOM) is set, line number are relative to
# the top scrolling margin. # the top scrolling margin.
@ -819,63 +829,61 @@ class Screen(QObject):
# region? # region?
self.ensure_bounds() self.ensure_bounds()
if y != self.cursor.y:
self.notify_cursor_position(self.cursor.x, y)
def bell(self, *args): def bell(self, *args):
"""Bell stub -- the actual implementation should probably be """ Audbile bell """
provided by the end-user. try:
""" with open('/dev/tty', 'wb') as f:
f.write(b'\x07')
except EnvironmentError:
pass
def alignment_display(self): def alignment_display(self):
"""Fills screen with uppercase E's for screen focus and alignment.""" """Fills screen with uppercase E's for screen focus and alignment."""
for line in self.buffer: for i in range(self.lines):
for column, char in enumerate(line): self.linebuf[i].clear_text(0, self.columns, 'E')
line[column] = char._replace(data="E")
def select_graphic_rendition(self, *attrs): def select_graphic_rendition(self, *attrs):
"""Set display attributes. """Set display attributes.
:param list attrs: a list of display attributes to set. :param list attrs: a list of display attributes to set.
""" """
replace = {} attrs = list(reversed(attrs or (0,)))
if not attrs: c = self.cursor
attrs = [0]
else:
attrs = list(reversed(attrs))
while attrs: while attrs:
attr = attrs.pop() attr = attrs.pop()
if attr in g.FG_ANSI: if attr in g.FG_ANSI:
replace["fg"] = g.FG_ANSI[attr] c.fg = (attr << 8) | 1
elif attr in g.BG: elif attr in g.BG_ANSI:
replace["bg"] = g.BG_ANSI[attr] c.bg = (attr << 8) | 1
elif attr in g.TEXT: elif attr in g.DISPLAY:
attr = g.TEXT[attr] attr, val = g.TEXT[attr]
replace[attr[1:]] = attr.startswith("+") setattr(c, attr, val)
elif not attr: elif not attr:
replace = self.default_char._asdict() c.reset_display_attrs()
elif attr in g.FG_AIXTERM: elif attr in g.FG_AIXTERM:
replace.update(fg=g.FG_AIXTERM[attr], bold=True) c.fg = g.FG_AIXTERM[attr]
elif attr in g.BG_AIXTERM: elif attr in g.BG_AIXTERM:
replace.update(bg=g.BG_AIXTERM[attr], bold=True) c.bg = g.BG_AIXTERM[attr]
elif attr in (g.FG_256, g.BG_256): elif attr in (g.FG_256, g.BG_256):
key = "fg" if attr == g.FG_256 else "bg" key = "fg" if attr == g.FG_256 else "bg"
n = attrs.pop() n = attrs.pop()
try: try:
if n == 5: # 256. if n == 5: # 256.
m = attrs.pop() setattr(c, key, (attrs.pop() << 8) | 2)
replace[key] = g.FG_BG_256[m]
elif n == 2: # 24bit. elif n == 2: # 24bit.
# This is somewhat non-standard but is nonetheless # This is somewhat non-standard but is nonetheless
# supported in quite a few terminals. See discussion # supported in quite a few terminals. See discussion
# here https://gist.github.com/XVilka/8346728. # here https://gist.github.com/XVilka/8346728.
replace[key] = "{0:02x}{1:02x}{2:02x}".format( r, gr, b = attrs.pop() << 8, attrs.pop() << 16, attrs.pop() << 24
attrs.pop(), attrs.pop(), attrs.pop()) setattr(c, key, r | gr | b | 3)
except IndexError: except IndexError:
pass pass
self.cursor.attrs = self.cursor.attrs._replace(**replace)
def report_device_attributes(self, mode=0, **kwargs): def report_device_attributes(self, mode=0, **kwargs):
"""Reports terminal identity. """Reports terminal identity.
@ -907,14 +915,13 @@ class Screen(QObject):
if mode == 5: # Request for terminal status. if mode == 5: # Request for terminal status.
self.write_process_input(ctrl.CSI + b"0n") self.write_process_input(ctrl.CSI + b"0n")
elif mode == 6: # Request for cursor position. elif mode == 6: # Request for cursor position.
x = self.cursor.x + 1 x, y = wrap_cursor_position(self.cursor.x, self.cursor.y, self.lines, self.columns)
y = self.cursor.y + 1 x, y = x + 1, y + 1
# "Origin mode (DECOM) selects line numbering." # "Origin mode (DECOM) selects line numbering."
if mo.DECOM in self.mode: if mo.DECOM in self.mode:
y -= self.margins.top y -= self.margins.top
self.write_process_input( self.write_process_input(ctrl.CSI + "{0};{1}R".format(y, x).encode('ascii'))
ctrl.CSI + "{0};{1}R".format(y, x).encode())
def debug(self, *args, **kwargs): def debug(self, *args, **kwargs):
"""Endpoint for unrecognized escape sequences. """Endpoint for unrecognized escape sequences.

View File

@ -23,7 +23,7 @@ from __future__ import unicode_literals
#: >>> text[9] #: >>> text[9]
#: '+strikethrough' #: '+strikethrough'
TEXT = { TEXT = {
1: "+bold" , 1: "+bold",
3: "+italics", 3: "+italics",
4: "+underscore", 4: "+underscore",
7: "+reverse", 7: "+reverse",
@ -35,6 +35,19 @@ TEXT = {
29: "-strikethrough", 29: "-strikethrough",
} }
DISPLAY = {
1: ("bold", True),
3: ("italic", True),
4: ("decoration", 1),
7: ("reverse", True),
9: ("strikethrough", True),
22: ("bold", False),
23: ("italic", False),
24: ("decoration", 0),
27: ("reverse", False),
29: ("strikethrough", False),
}
#: A mapping of ANSI foreground color codes to color names. #: A mapping of ANSI foreground color codes to color names.
#: #:
#: >>> FG_ANSI[30] #: >>> FG_ANSI[30]
@ -145,5 +158,3 @@ for i in range(217):
for i in range(1, 22): for i in range(1, 22):
v = 8 + i * 10 v = 8 + i * 10
FG_BG_256.append((v, v, v)) FG_BG_256.append((v, v, v))
FG_BG_256 = ["{0:02x}{1:02x}{2:02x}".format(r, g, b) for r, g, b in FG_BG_256]