diff --git a/kitty/boss.py b/kitty/boss.py index 1239359cc..0477ee0dd 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -9,19 +9,20 @@ import select import subprocess import struct from itertools import repeat +from functools import partial from time import monotonic from threading import Thread, current_thread from queue import Queue, Empty import glfw -from pyte.streams import Stream, DebugStream from .constants import appname from .char_grid import CharGrid from .keys import interpret_text_event, interpret_key_event -from .screen import Screen from .utils import resize_pty, create_pty, sanitize_title -from .fast_data_types import BRACKETED_PASTE_START, BRACKETED_PASTE_END +from .fast_data_types import ( + BRACKETED_PASTE_START, BRACKETED_PASTE_END, Screen, parse_bytes_dump, parse_bytes +) def handle_unix_signals(): @@ -53,10 +54,9 @@ class Boss(Thread): self.queue_action(self.initialize) self.profile = args.profile self.window, self.opts = window, opts - self.screen = Screen(self.opts.scrollback_lines, self) + self.screen = Screen(self) self.char_grid = CharGrid(self.screen, opts, window_width, window_height) - sclass = DebugStream if args.dump_commands else Stream - self.stream = sclass(self.screen) + self.parse_bytes = partial(parse_bytes_dump, print) if args.dump_commands else parse_bytes self.write_buf = memoryview(b'') glfw.glfwSetCharModsCallback(window, self.on_text_input) glfw.glfwSetKeyCallback(window, self.on_key) @@ -181,7 +181,7 @@ class Boss(Thread): if self.pending_update_screen is not None: if monotonic() > self.pending_update_screen: self.apply_update_screen() - elif self.screen.is_dirty: + elif self.screen.is_dirty(): self.pending_update_screen = monotonic() + self.SCREEN_UPDATE_DELAY def close(self): @@ -209,7 +209,7 @@ class Boss(Thread): except EnvironmentError: data = b'' if data: - self.stream.feed(data) + self.parse_bytes(self.screen, data) else: # EOF self.shutdown() diff --git a/kitty/control-codes.h b/kitty/control-codes.h index bce427849..d033b6d60 100644 --- a/kitty/control-codes.h +++ b/kitty/control-codes.h @@ -24,7 +24,7 @@ // of the line if there is no earlier tab stop. #define HT 0x09 -// *Linefeed*: Give a line feed, and, if :data:`pyte.modes.LNM` (new +// *Linefeed*: Give a line feed, and, if LNM (new // line mode) is set also a carriage return. #define LF 10 @@ -80,7 +80,7 @@ // at the bottom margin, the screen performs a scroll-up. #define IND 'D' -// *Next line*: Same as :data:`pyte.control.LF`. +// *Next line*: Same as LF. #define NEL 'E' // Tabulation set: Set a horizontal tab stop at cursor position. @@ -197,7 +197,7 @@ // *Select graphics rendition*: The terminal can display the following // character attributes that change the character display without -// changing the character (see :mod:`pyte.graphics`). +// changing the character #define SGR 'm' // *Device status report*. diff --git a/kitty/parser.c b/kitty/parser.c index 010138033..5a236145a 100644 --- a/kitty/parser.c +++ b/kitty/parser.c @@ -504,7 +504,7 @@ parse_bytes(PyObject UNUSED *self, PyObject *args) { Py_buffer pybuf; Screen *screen; #ifdef DUMP_COMMANDS - if (!PyArg_ParseTuple(args, "O!y*O", &Screen_Type, &screen, &pybuf, &dump_callback)) return NULL; + if (!PyArg_ParseTuple(args, "OO!y*", &dump_callback, &Screen_Type, &screen, &pybuf)) return NULL; if (!PyCallable_Check(dump_callback)) { PyErr_SetString(PyExc_TypeError, "The dump callback must be a callable object"); return NULL; } #else if (!PyArg_ParseTuple(args, "O!y*", &Screen_Type, &screen, &pybuf)) return NULL; diff --git a/kitty/screen.c b/kitty/screen.c index 3c7460c79..9c8d66bfe 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -18,7 +18,7 @@ static PyObject* new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { Screen *self; PyObject *callbacks = Py_None; - unsigned int columns, lines; + unsigned int columns=80, lines=24; if (!PyArg_ParseTuple(args, "|OII", &callbacks, &lines, &columns)) return NULL; self = (Screen *)type->tp_alloc(type, 0); @@ -102,6 +102,12 @@ static bool screen_resize(Screen *self, unsigned int lines, unsigned int columns return true; } +static bool screen_change_scrollback_size(Screen UNUSED *self, unsigned int UNUSED size) { + // TODO: Implement this + return true; +} + + static void dealloc(Screen* self) { Py_CLEAR(self->callbacks); @@ -980,6 +986,36 @@ resize(Screen *self, PyObject *args) { Py_RETURN_NONE; } +static PyObject* +change_scrollback_size(Screen *self, PyObject *args) { + unsigned int count = 1; + if (!PyArg_ParseTuple(args, "|I", &count)) return NULL; + if (!screen_change_scrollback_size(self, MAX(100, count))) return NULL; + Py_RETURN_NONE; +} + +static PyObject* +screen_update_cell_data(Screen *self, PyObject *args) { + SpriteMap *spm; + ColorProfile *color_profile; + PyObject *dp; + unsigned int *data; + unsigned long default_bg, default_fg; + int force_screen_refresh; + if (!PyArg_ParseTuple(args, "O!O!O!kkp", &SpriteMap_Type, &spm, &ColorProfile_Type, &color_profile, &PyLong_Type, &dp, &default_fg, &default_bg, &force_screen_refresh)) return NULL; + data = PyLong_AsVoidPtr(dp); + PyObject *cursor_changed = self->change_tracker->cursor_changed ? Py_True : Py_False; + if (!tracker_update_cell_data(self->change_tracker, self->linebuf, spm, color_profile, data, default_fg, default_bg, (bool)force_screen_refresh)) return NULL; + Py_INCREF(cursor_changed); + return cursor_changed; +} + +static PyObject* is_dirty(Screen *self) { + PyObject *ans = self->change_tracker->dirty ? Py_True : Py_False; + Py_INCREF(ans); + return ans; +} + #define COUNT_WRAP(name) \ static PyObject* name(Screen *self, PyObject *args) { \ unsigned int count = 1; \ @@ -1015,6 +1051,7 @@ static PyMethodDef methods[] = { MND(delete_lines, METH_VARARGS) MND(insert_characters, METH_VARARGS) MND(delete_characters, METH_VARARGS) + MND(change_scrollback_size, METH_VARARGS) MND(erase_characters, METH_VARARGS) MND(cursor_up, METH_VARARGS) MND(cursor_up1, METH_VARARGS) @@ -1023,7 +1060,9 @@ static PyMethodDef methods[] = { MND(cursor_forward, METH_VARARGS) MND(index, METH_NOARGS) MND(reverse_index, METH_NOARGS) + MND(is_dirty, METH_NOARGS) MND(resize, METH_VARARGS) + {"update_cell_data", (PyCFunction)screen_update_cell_data, METH_VARARGS, ""}, {NULL} /* Sentinel */ }; diff --git a/kitty/screen.py b/kitty/screen.py deleted file mode 100644 index a7de4e3cf..000000000 --- a/kitty/screen.py +++ /dev/null @@ -1,1031 +0,0 @@ -#!/usr/bin/env python -# vim:fileencoding=utf-8 -# License: GPL v3 Copyright: 2016, Kovid Goyal - - -import codecs -import unicodedata -from collections import deque, namedtuple -from typing import Sequence - -from pyte import charsets as cs, graphics as g, modes as mo -from .utils import wcwidth, is_simple_string, sanitize_title -from .unicode import ignore_pat -from .fast_data_types import LineBuf, REVERSE, Cursor, ChangeTracker, CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE - - -#: A container for screen's scroll margins. -Margins = namedtuple("Margins", "top bottom") - -#: A container for savepoint, created on :data:`~pyte.escape.DECSC`. -Savepoint = namedtuple("Savepoint", [ - "cursor", - "g0_charset", - "g1_charset", - "charset", - "use_utf8", - "origin", - "wrap" -]) - - -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 - - -default_callbacks = { - 'title_changed': lambda t: None, - 'icon_changed': lambda i: None, - 'write_to_child': lambda data: None, - 'change_default_color': lambda which, val: None -} - - -class Screen: - """ - See standard ECMA-48, Section 6.1.1 http://www.ecma-international.org/publications/standards/Ecma-048.htm - for a description of the presentational component, implemented by ``Screen``. - """ - - tracker_callbacks = 'cursor_changed update_screen update_line_range update_cell_range line_added_to_history'.split() - _notify_cursor_position = True - - def __init__(self, scrollback_sz, callbacks=None, columns: int=80, lines: int=24): - for attr in default_callbacks: - setattr(self, attr, getattr(callbacks, attr, default_callbacks[attr])) - self.main_savepoints, self.alt_savepoints = deque(), deque() - self.savepoints = self.main_savepoints - self.columns = columns - self.lines = lines - self.tracker = ChangeTracker(self.lines, self.columns) - for attr in self.tracker_callbacks: - setattr(self, attr, getattr(self.tracker, attr)) - self.consolidate_changes = self.tracker.consolidate_changes - self.reset_dirty = self.tracker.reset - sz = max(1000, scrollback_sz) - self.tophistorybuf = deque(maxlen=sz) - self.main_linebuf, self.alt_linebuf = LineBuf(self.lines, self.columns), LineBuf(self.lines, self.columns) - self.linebuf = self.main_linebuf - self.reset(notify=False) - - @property - def is_dirty(self): - return self.tracker.dirty - - def change_scrollback_size(self, sz): - sz = max(1000, sz) - if sz != self.tophistorybuf.maxlen: - previous = self.tophistorybuf - self.tophistorybuf = deque(maxlen=sz) - self.tophistorybuf.extend(previous) - - def line(self, i): - return self.linebuf.line(i) - - def update_cell_data(self, *a): - return self.tracker.update_cell_data(self.linebuf, *a) - - def __repr__(self): - return ("{0}({1}, {2})".format(self.__class__.__name__, - self.columns, self.lines)) - - def notify_cursor_position(self): - if self._notify_cursor_position: - self.cursor_changed() - - @property - def display(self) -> Sequence[str]: - return tuple(map(lambda l: str(self.linebuf.line(l)), range(self.linebuf.ynum))) - - def toggle_screen_buffer(self): - self.save_cursor() - if self.linebuf is self.main_linebuf: - self.linebuf, self.savepoints = self.alt_linebuf, self.alt_savepoints - else: - self.linebuf, self.savepoints = self.main_linebuf, self.main_savepoints - self.restore_cursor() - self.update_screen() - - def reset(self, notify=True): - """Resets the terminal to its initial state. - - * Scroll margins are reset to screen boundaries. - * Cursor is moved to home location -- ``(0, 0)`` and its - attributes are set to defaults (see :attr:`default_char`). - * Screen is cleared -- each character is reset to - :attr:`default_char`. - * Tabstops are reset to "every eight columns". - - .. note:: - - Neither VT220 nor VT102 manuals mentioned that terminal modes - and tabstops should be reset as well, thanks to - :manpage:`xterm` -- we now know that. - """ - if self.linebuf is self.alt_linebuf: - self.toggle_screen_buffer() - self.linebuf.clear() - self.mode = {mo.DECAWM, mo.DECTCEM} - self.margins = Margins(0, self.lines - 1) - - self.charset = 0 - self.g0_charset = cs.LAT1_MAP - self.g1_charset = cs.VT100_MAP - self.use_utf8 = True - self.utf8_decoder = codecs.getincrementaldecoder("utf-8")("replace") - - # From ``man terminfo`` -- "... hardware tabs are initially - # set every `n` spaces when the terminal is powered up. Since - # we aim to support VT102 / VT220 and linux -- we use n = 8. - self.tabstops = set(range(7, self.columns, 8)) - self.normal_keypad_mode() - - self.cursor = Cursor(0, 0) - self.cursor_changed() - self.cursor_position() - self.change_default_color('fg', None) - self.change_default_color('bg', None) - if notify: - self.update_screen() - - def resize(self, lines: int, columns: int): - """Resize the screen to the given dimensions. - - .. note:: According to `xterm`, we should also reset origin - mode and screen margins, see ``xterm/screen.c:1761``. - - """ - self.lines, self.columns = lines, columns - self.tracker.resize(self.lines, self.columns) - self.tabstops = {x for x in self.tabstops if x < self.columns} - # TODO: Implement rewrap for history buf - self.tophistorybuf.clear() - is_main = self.linebuf is self.main_linebuf - for x in 'main_linebuf alt_linebuf'.split(): - lb2 = LineBuf(self.lines, self.columns) - lb = getattr(self, x) - lb.rewrap(lb2) - setattr(self, x, lb2) - self.linebuf = self.main_linebuf if is_main else self.alt_linebuf - - self.margins = Margins(0, self.lines - 1) - self._notify_cursor_position = False - try: - x, y = self.cursor.x, self.cursor.y - self.reset_mode(mo.DECOM) - self.cursor.x, self.cursor.y = x, y - finally: - self._notify_cursor_position = True - self.ensure_bounds() - - def set_margins(self, top=None, bottom=None): - """Selects top and bottom margins for the scrolling region. - - Margins determine which screen lines move during scrolling - (see :meth:`index` and :meth:`reverse_index`). Characters added - outside the scrolling region do not cause the screen to scroll. - - :param int top: the smallest line number that is scrolled. - :param int bottom: the biggest line number that is scrolled. - """ - if top is None or bottom is None: - return - - # Arguments are 1-based, while :attr:`margins` are zero based -- - # so we have to decrement them by one. We also make sure that - # both of them is bounded by [0, lines - 1]. - top = max(0, min(top - 1, self.lines - 1)) - bottom = max(0, min(bottom - 1, self.lines - 1)) - - # Even though VT102 and VT220 require DECSTBM to ignore regions - # of width less than 2, some programs (like aptitude for example) - # rely on it. Practicality beats purity. - if bottom - top >= 1: - self.margins = Margins(top, bottom) - - # The cursor moves to the home position when the top and - # bottom margins of the scrolling region (DECSTBM) changes. - self.cursor_position() - - def set_mode(self, *modes, private=False): - """Sets (enables) a given list of modes. - - :param list modes: modes to set, where each mode is a constant - from :mod:`pyte.modes`. - """ - # Private mode codes are shifted, to be distingiushed from non - # private ones. - if private: - modes = [mode << 5 for mode in modes] - - self.mode.update(modes) - - # When DECOLM mode is set, the screen is erased and the cursor - # moves to the home position. - if mo.DECCOLM in modes: - # self.resize(columns=132) Disabled since we only allow resizing - # by the user - self.erase_in_display(2) - self.cursor_position() - - # According to `vttest`, DECOM should also home the cursor, see - # vttest/main.c:303. - if mo.DECOM in modes: - self.cursor_position() - - # Mark all displayed characters as reverse. - if mo.DECSCNM in modes: - self.linebuf.set_attribute(REVERSE, True) - self.update_screen() - self.select_graphic_rendition(7) # +reverse. - - # Show/hide the cursor. - previous, self.cursor.hidden = self.cursor.hidden, mo.DECTCEM not in self.mode - if previous != self.cursor.hidden: - self.cursor_changed() - - if mo.ALTERNATE_SCREEN in self.mode and self.linebuf is self.main_linebuf: - self.toggle_screen_buffer() - - def in_bracketed_paste_mode(self): - return mo.BRACKETED_PASTE in self.mode - - def enable_focus_tracking(self): - return mo.FOCUS_TRACKING in self.mode - - def reset_mode(self, *modes, private=False): - """Resets (disables) a given list of modes. - - :param list modes: modes to reset -- hopefully, each mode is a - constant from :mod:`pyte.modes`. - """ - # Private mode codes are shifted, to be distinguished from non - # private ones. - if private: - modes = [mode << 5 for mode in modes] - - self.mode.difference_update(modes) - - # Lines below follow the logic in :meth:`set_mode`. - if mo.DECCOLM in modes: - # self.resize(columns=80) Disabled since we only allow resizing by - # the user - self.erase_in_display(2) - self.cursor_position() - - if mo.DECOM in modes: - self.cursor_position() - - if mo.DECSCNM in modes: - self.linebuf.set_attribute(REVERSE, False) - self.update_screen() - self.select_graphic_rendition(27) # -reverse. - - # Show/hide the cursor. - previous, self.cursor.hidden = self.cursor.hidden, mo.DECTCEM not in self.mode - if previous != self.cursor.hidden: - self.cursor_changed() - - if mo.ALTERNATE_SCREEN not in self.mode and self.linebuf is not self.main_linebuf: - self.toggle_screen_buffer() - - def define_charset(self, code, mode): - """Defines ``G0`` or ``G1`` charset. - - :param str code: character set code, should be a character - from ``b"B0UK"``, otherwise ignored. - :param str mode: if ``"("`` ``G0`` charset is defined, if - ``")"`` -- we operate on ``G1``. - .. warning:: User-defined charsets are currently not supported. - """ - if code in cs.MAPS: - if mode == b"(": - self.g0_charset = cs.MAPS[code] - elif mode == b")": - self.g1_charset = cs.MAPS[code] - - def shift_in(self): - """Selects ``G0`` character set.""" - self.charset = 0 - - def shift_out(self): - """Selects ``G1`` character set.""" - self.charset = 1 - - def select_other_charset(self, code): - """Selects other (non G0 or G1) charset. - - :param str code: character set code, should be a character from - ``b"@G8"``, otherwise ignored. - - .. note:: We currently follow ``"linux"`` and only use this - command to switch from ISO-8859-1 to UTF-8 and back. - - .. seealso:: - - `Standard ECMA-35, Section 15.4 \ - `_ - for a description of VTXXX character set machinery. - """ - if code == b"@": - self.use_utf8 = False - self.utf8_decoder.reset() - elif code in b"G8": - self.use_utf8 = True - - def _decode(self, data): - """Decodes bytes to text according to the selected charset. - - :param bytes data: bytes to decode. - """ - if self.charset: - return "".join(self.g1_charset[b] for b in data) - if self.use_utf8: - return self.utf8_decoder.decode(data) - return "".join(self.g0_charset[b] for b in data) - - def _draw_fast(self, data: str) -> None: - do_insert = mo.IRM in self.mode - pos = 0 - while pos < len(data): - space_left_in_line = self.columns - self.cursor.x - len_left = len(data) - pos - if space_left_in_line < 1: - if mo.DECAWM in self.mode: - self.carriage_return() - self.linefeed() - self.linebuf.set_continued(self.cursor.y, True) - space_left_in_line = self.columns - else: - space_left_in_line = 1 - len_left = 1 - pos = len(data) - 1 - self.cursor.x = self.columns - 1 - write_sz = min(len_left, space_left_in_line) - line = self.linebuf.line(self.cursor.y) - if do_insert: - line.right_shift(self.cursor.x, write_sz) - line.set_text(data, pos, write_sz, self.cursor) - pos += write_sz - cx = self.cursor.x - self.cursor.x += write_sz - right = self.columns - 1 if do_insert else max(0, min(self.cursor.x - 1, self.columns - 1)) - self.update_cell_range(self.cursor.y, cx, right) - - def _draw_char(self, char: str, char_width: int) -> None: - space_left_in_line = self.columns - self.cursor.x - if space_left_in_line < char_width: - if mo.DECAWM in self.mode: - self.carriage_return() - self.linefeed() - self.linebuf.set_continued(self.cursor.y, True) - else: - self.cursor.x = self.columns - char_width - - do_insert = mo.IRM in self.mode - - cx = self.cursor.x - line = self.linebuf.line(self.cursor.y) - if char_width > 0: - if do_insert: - line.right_shift(self.cursor.x, char_width) - line.set_char(cx, char, char_width, self.cursor) - self.cursor.x += 1 - if char_width == 2: - line.set_char(self.cursor.x, '\0', 0, self.cursor) - self.cursor.x += 1 - right = self.columns - 1 if do_insert else max(0, min(self.cursor.x - 1, self.columns - 1)) - self.update_cell_range(self.cursor.y, cx, right) - elif unicodedata.combining(char): - # A zero-cell character is combined with the previous - # character either on this or the preceeding line. - if cx > 0: - line.add_combining_char(cx - 1, char) - self.update_cell_range(self.cursor.y, cx - 1, cx - 1) - elif self.cursor.y > 0: - lline = self.linebuf.line(self.cursor.y - 1) - lline.add_combining_char(self.columns - 1, char) - self.update_cell_range(self.cursor.y - 1, self.columns - 1, self.columns - 1) - - def draw(self, data: bytes) -> None: - """ Displays decoded characters at the current cursor position and - creates new lines as need if DECAWM is set. """ - orig_x, orig_y = self.cursor.x, self.cursor.y - self._notify_cursor_position = False - data = self._decode(data) - try: - if is_simple_string(data): - return self._draw_fast(data) - data = ignore_pat.sub('', data) - if data: - widths = list(map(wcwidth, data)) - if sum(widths) == len(data): - return self._draw_fast(data) - for char, char_width in zip(data, widths): - self._draw_char(char, char_width) - finally: - self._notify_cursor_position = True - if orig_x != self.cursor.x or orig_y != self.cursor.y: - self.notify_cursor_position() - - def set_title(self, param): - """Sets terminal title. - - .. note:: This is an XTerm extension supported by the Linux terminal. - """ - self.title_changed(sanitize_title(self._decode(param))) - - def set_icon_name(self, param): - """Sets icon name. - - .. note:: This is an XTerm extension supported by the Linux terminal. - """ - self.icon_changed(sanitize_title(self._decode(param))) - - def carriage_return(self): - """Move the cursor to the beginning of the current line.""" - x, self.cursor.x = self.cursor.x, 0 - if x != self.cursor.x: - self.notify_cursor_position() - - def index(self): - """Move the cursor down one line in the same column. If the - cursor is at the last line, create a new line at the bottom. - """ - top, bottom = self.margins - - if self.cursor.y == bottom: - self.linebuf.index(top, bottom) - if self.linebuf is self.main_linebuf: - if len(self.tophistorybuf) >= self.tophistorybuf.maxlen: - l = self.tophistorybuf.popleft() - self.linebuf.copy_line_to(bottom, l) - else: - self.tophistorybuf.append(self.linebuf.create_line_copy(bottom)) - self.line_added_to_history() - self.linebuf.clear_line(bottom) - if bottom - top >= self.lines - 1: - self.update_screen() - else: - self.update_line_range(top, bottom) - else: - self.cursor_down() - - def reverse_index(self): - """Move the cursor up one line in the same column. If the cursor - is at the first line, create a new line at the top. - """ - top, bottom = self.margins - - if self.cursor.y == top: - self.linebuf.reverse_index(top, bottom) - if bottom - top >= self.lines - 1: - self.update_screen() - else: - self.update_line_range(top, bottom) - self.linebuf.clear_line(top) - else: - self.cursor_up() - - def linefeed(self): - """Performs an index and, if :data:`~pyte.modes.LNM` is set, a - carriage return. - """ - self.index() - - if mo.LNM in self.mode: - self.carriage_return() - - self.ensure_bounds() - - def tab(self): - """Move to the next tab space, or the end of the screen if there - aren't anymore left. - """ - for stop in sorted(self.tabstops): - if self.cursor.x < stop: - column = stop - break - else: - column = self.columns - 1 - - if column != self.cursor.x: - self.cursor.x = column - self.notify_cursor_position() - - def backspace(self): - """Move cursor to the left one or keep it in it's position if - it's at the beginning of the line already. - """ - self.cursor_back() - - def save_cursor(self): - """Push the current cursor position onto the stack.""" - self.savepoints.append(Savepoint(self.cursor.copy(), - self.g0_charset, - self.g1_charset, - self.charset, - self.use_utf8, - mo.DECOM in self.mode, - mo.DECAWM in self.mode)) - - def restore_cursor(self): - """Set the current cursor position to whatever cursor is on top - of the stack. - """ - if self.savepoints: - savepoint = self.savepoints.pop() - - self.g0_charset = savepoint.g0_charset - self.g1_charset = savepoint.g1_charset - self.charset = savepoint.charset - self.use_utf8 = savepoint.use_utf8 - - if savepoint.origin: - self.set_mode(mo.DECOM) - if savepoint.wrap: - self.set_mode(mo.DECAWM) - - self.cursor = savepoint.cursor - self.cursor_changed() - self.ensure_bounds(use_margins=True) - else: - # If nothing was saved, the cursor moves to home position; - # origin mode is reset. TODO: DECAWM? - self.reset_mode(mo.DECOM) - self.cursor_position() - - def insert_lines(self, count=1): - """Inserts the indicated # of lines at line with cursor. Lines - displayed **at** and below the cursor move down. Lines moved - past the bottom margin are lost. - - :param count: number of lines to delete. - """ - count = count or 1 - top, bottom = self.margins - - # If cursor is outside scrolling margins -- do nothin'. - if top <= self.cursor.y <= bottom: - self.linebuf.insert_lines(count, self.cursor.y, bottom) - self.update_line_range(self.cursor.y, bottom) - self.carriage_return() - - def delete_lines(self, count=1): - """Deletes the indicated # of lines, starting at line with - cursor. As lines are deleted, lines displayed below cursor - move up. - - :param int count: number of lines to delete. - """ - count = count or 1 - top, bottom = self.margins - - # If cursor is outside scrolling margins it -- do nothin'. - if top <= self.cursor.y <= bottom: - self.linebuf.delete_lines(count, self.cursor.y, bottom) - self.update_line_range(self.cursor.y, bottom) - self.carriage_return() - - def insert_characters(self, count=1): - """Inserts the indicated # of blank characters at the cursor - position. The cursor does not move and remains at the beginning - of the inserted blank characters. Data on the line is shifted - forward. - - :param int count: number of characters to insert. - """ - count = count or 1 - top, bottom = self.margins - - y = self.cursor.y - if top <= y <= bottom: - x = self.cursor.x - # TODO: Check what to do if x is on the second char of a wide char - # pair. - num = min(self.columns - x, count) - line = self.linebuf.line(y) - line.right_shift(x, num) - line.apply_cursor(self.cursor, x, num, True) - self.update_cell_range(y, x, self.columns - 1) - - def delete_characters(self, count=1): - """Deletes the indicated # of characters, starting with the - character at cursor position. When a character is deleted, all - characters to the right of cursor move left. Character attributes - move with the characters. - - :param int count: number of characters to delete. - """ - count = count or 1 - top, bottom = self.margins - - y = self.cursor.y - if top <= y <= bottom: - x = self.cursor.x - num = min(self.columns - x, count) - # TODO: Check if we need to count wide chars as one or two chars - # for this control code. Also, what happens if we start deleting - # at the second cell of a wide character, or delete only the first - # cell of a wide character? - line = self.linebuf.line(y) - line.left_shift(x, num) - line.apply_cursor(self.cursor, self.columns - num, num, True) - self.update_cell_range(y, x, self.columns - 1) - - def erase_characters(self, count=1): - """Erases the indicated # of characters, starting with the - character at cursor position. Character attributes are set - to cursor attributes. The cursor remains in the same position. - - :param int count: number of characters to erase. - - .. warning:: - - Even though *ALL* of the VTXXX manuals state that character - attributes **should be reset to defaults**, ``libvte``, - ``xterm`` and ``ROTE`` completely ignore this. Same applies - to all ``erase_*()`` and ``delete_*()`` methods. - """ - count = count or 1 - x, y = self.cursor.x, self.cursor.y - # TODO: Same set of wide character questions as for delete_characters() - num = min(self.columns - x, count) - l = self.linebuf.line(y) - l.apply_cursor(self.cursor, x, num, True) - self.update_cell_range(y, x, min(x + num, self.columns) - 1) - - def erase_in_line(self, how=0, private=False): - """Erases a line in a specific way. - - :param int how: defines the way the line should be erased in: - - * ``0`` -- Erases from cursor to end of line, including cursor - position. - * ``1`` -- Erases from beginning of line to cursor, - including cursor position. - * ``2`` -- Erases complete line. - :param bool private: when ``True`` character attributes are left - unchanged. - """ - s = n = 0 - if how == 0: - # a) erase from the cursor to the end of line, including - # the cursor, - s, n = self.cursor.x, self.columns - self.cursor.x - elif how == 1: - # b) erase from the beginning of the line to the cursor, - # including it, - s, n = 0, self.cursor.x + 1 - elif how == 2: - # c) erase the entire line. - s, n = 0, self.columns - if n - s: - # TODO: Same set of questions as for delete_characters() - y = self.cursor.y - line = self.linebuf.line(y) - c = None if private else self.cursor - if private: - line.clear_text(s, n) - else: - line.apply_cursor(c, s, n, True) - self.update_cell_range(y, s, min(s + n, self.columns) - 1) - - def erase_in_display(self, how=0, private=False): - """Erases display in a specific way. - - :param int how: defines the way the line should be erased in: - - * ``0`` -- Erases from cursor to end of screen, including - cursor position. - * ``1`` -- Erases from beginning of screen to cursor, - including cursor position. - * ``2`` -- Erases complete display. All lines are erased - and changed to single-width. Cursor does not move. - :param bool private: when ``True`` character attributes are left unchanged - """ - if how == 0: - # a) erase from cursor to the end of the display, including - # the cursor, - interval = self.cursor.y + 1, self.lines - elif how == 1: - # b) erase from the beginning of the display to the cursor, - # including it, - interval = 0, self.cursor.y - elif how == 2: - # c) erase the whole display. - interval = 0, self.lines - else: - return - - if interval[1] > interval[0]: - for line in range(*interval): - if private: - self.linebuf.line(line).clear_text(0, self.columns) - else: - self.linebuf.line(line).apply_cursor(self.cursor, 0, self.columns, True) - self.update_line_range(interval[0], interval[1] - 1) - - # In case of 0 or 1 we have to erase the line with the cursor also - if how != 2: - self.erase_in_line(how, private=private) - - def set_tab_stop(self): - """Sets a horizontal tab stop at cursor position.""" - self.tabstops.add(self.cursor.x) - - def clear_tab_stop(self, how=0): - """Clears a horizontal tab stop. - - :param int how: defines a way the tab stop should be cleared: - - * ``0`` or nothing -- Clears a horizontal tab stop at cursor - position. - * ``3`` -- Clears all horizontal tab stops. - """ - if how == 0: - # Clears a horizontal tab stop at cursor position, if it's - # present, or silently fails if otherwise. - self.tabstops.discard(self.cursor.x) - elif how == 3: - self.tabstops = set() # Clears all horizontal tab stops. - - def ensure_bounds(self, use_margins=False): - """Ensure that current cursor position is within screen bounds. - - :param bool use_margins: when ``True`` or when - :data:`~pyte.modes.DECOM` is set, - cursor is bounded by top and and bottom - margins, instead of ``[0; lines - 1]``. - """ - if use_margins or mo.DECOM in self.mode: - top, bottom = self.margins - else: - top, bottom = 0, self.lines - 1 - - self.cursor.x = max(0, min(self.cursor.x, self.columns - 1)) - self.cursor.y = max(top, min(self.cursor.y, bottom)) - - def cursor_up(self, count=1, do_carriage_return=False, move_direction=-1): - """Moves cursor up the indicated # of lines in same column. - Cursor stops at top margin. - - :param int count: number of lines to skip. - """ - x, y = self.cursor.x, self.cursor.y - self.cursor.y += move_direction * (count or 1) - self.ensure_bounds(use_margins=True) - if do_carriage_return: - self.cursor.x = 0 - if y != self.cursor.y or x != self.cursor.x: - self.notify_cursor_position() - - def cursor_up1(self, count=1): - """Moves cursor up the indicated # of lines to column 1. Cursor - stops at bottom margin. - - :param int count: number of lines to skip. - """ - self.cursor_up(count, do_carriage_return=True) - - def cursor_down(self, count=1): - """Moves cursor down the indicated # of lines in same column. - Cursor stops at bottom margin. - - :param int count: number of lines to skip. - """ - self.cursor_up(count, move_direction=1) - - def cursor_down1(self, count=1): - """Moves cursor down the indicated # of lines to column 1. - Cursor stops at bottom margin. - - :param int count: number of lines to skip. - """ - self.cursor_up(count, do_carriage_return=True, move_direction=1) - - def cursor_back(self, count=1, move_direction=-1): - """Moves cursor left the indicated # of columns. Cursor stops - at left margin. - - :param int count: number of columns to skip. - """ - x = self.cursor.x - self.cursor.x += move_direction * (count or 1) - self.ensure_bounds() - if x != self.cursor.x: - self.notify_cursor_position() - - def cursor_forward(self, count=1): - """Moves cursor right the indicated # of columns. Cursor stops - at right margin. - - :param int count: number of columns to skip. - """ - self.cursor_back(count, move_direction=1) - - def cursor_position(self, line=1, column=1): - """Set the cursor to a specific `line` and `column`. - - Cursor is allowed to move out of the scrolling region only when - :data:`~pyte.modes.DECOM` is reset, otherwise -- the position - doesn't change. - - :param int line: line number to move the cursor to. - :param int column: column number to move the cursor to. - """ - column = (column 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 - # the top scrolling margin. - if mo.DECOM in self.mode: - line += self.margins.top - - # Cursor is not allowed to move out of the scrolling region. - if not self.margins.top <= line <= self.margins.bottom: - return - - self.cursor.x, self.cursor.y = column, line - self.ensure_bounds() - if y != self.cursor.y or x != self.cursor.x: - self.notify_cursor_position() - - def cursor_to_column(self, column=1): - """Moves cursor to a specific column in the current line. - - :param int column: column number to move the cursor to. - """ - x, self.cursor.x = self.cursor.x, (column or 1) - 1 - self.ensure_bounds() - if x != self.cursor.x: - self.notify_cursor_position() - - def cursor_to_line(self, line=1): - """Moves cursor to a specific line in the current column. - - :param int line: line number to move the cursor to. - """ - y, self.cursor.y = self.cursor.y, (line or 1) - 1 - - # If origin mode (DECOM) is set, line number are relative to - # the top scrolling margin. - if mo.DECOM in self.mode: - self.cursor.y += self.margins.top - - # TODO: should we also restrict the cursor to the scrolling - # region? - - self.ensure_bounds() - if y != self.cursor.y: - self.notify_cursor_position() - - def bell(self, *args): - """ Audbile bell """ - try: - with open('/dev/tty', 'wb') as f: - f.write(b'\x07') - except EnvironmentError: - pass - - def alignment_display(self): - """Fills screen with uppercase E's for screen focus and alignment.""" - for i in range(self.lines): - self.linebuf.line(i).clear_text(0, self.columns, 'E') - - def select_graphic_rendition(self, *attrs): - """Set display attributes. - - :param list attrs: a list of display attributes to set. - """ - attrs = list(reversed(attrs or (0,))) - - c = self.cursor - - while attrs: - attr = attrs.pop() - if attr in g.FG_ANSI: - c.fg = (attr << 8) | 1 - elif attr in g.BG_ANSI: - c.bg = (attr << 8) | 1 - elif attr in g.DISPLAY: - attr, val = g.DISPLAY[attr] - setattr(c, attr, val) - elif not attr: - c.reset_display_attrs() - elif attr in g.FG_AIXTERM: - c.fg = (attr << 8) | 1 - elif attr in g.BG_AIXTERM: - c.bg = (attr << 8) | 1 - elif attr in (g.FG_256, g.BG_256): - key = "fg" if attr == g.FG_256 else "bg" - n = attrs.pop() - try: - if n == 5: # 256. - setattr(c, key, (attrs.pop() << 8) | 2) - elif n == 2: # 24bit. - # This is somewhat non-standard but is nonetheless - # supported in quite a few terminals. See discussion - # here https://gist.github.com/XVilka/8346728. - r, gr, b = attrs.pop() << 24, attrs.pop() << 16, attrs.pop() << 8 - setattr(c, key, r | gr | b | 3) - except IndexError: - pass - - def report_device_attributes(self, mode=0, **kwargs): - """Reports terminal identity. - - .. versionadded:: 0.5.0 - """ - # Use the same responses as libvte v0.46 running in termite - # Ignore mode since vte seems to ignore it - if False and kwargs.get('secondary') == '>': - # http://www.vt100.net/docs/vt510-rm/DA2.html - # If you implement xterm keycode querying - # http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Device-Control-functions - # you can enable this. - self.write_to_child(b'\x1b[>1;4600;0c') - else: - # xterm gives: [[?64;1;2;6;9;15;18;21;22c - # use the simpler vte response, since we dont support - # windowing/horizontal scrolling etc. - # [[?64;1;2;6;9;15;18;21;22c - self.write_to_child(b"\x1b[?62c") - - def report_device_status(self, mode): - """Reports terminal status or cursor position. - - :param int mode: if 5 -- terminal status, 6 -- cursor position, - otherwise a noop. - - .. versionadded:: 0.5.0 - """ - if mode == 5: # Request for terminal status. - self.write_to_child(b"\x1b[0n") - elif mode == 6: # Request for cursor position. - x, y = wrap_cursor_position(self.cursor.x, self.cursor.y, self.lines, self.columns) - x, y = x + 1, y + 1 - - # "Origin mode (DECOM) selects line numbering." - if mo.DECOM in self.mode: - y -= self.margins.top - self.write_to_child("\x1b[{0};{1}R".format(y, x).encode('ascii')) - - def set_cursor_shape(self, mode, secondary=None): - if secondary == ' ': - shape = blink = None - if mode > 0: - blink = bool(mode % 2) - shape = CURSOR_BLOCK if mode < 3 else CURSOR_UNDERLINE if mode < 5 else CURSOR_BEAM if mode < 7 else None - if shape != self.cursor.shape or blink != self.cursor.blink: - self.cursor.shape, self.cursor.blink = shape, blink - self.cursor_changed() - elif secondary == '"': # DECSCA - pass - else: # DECLL - pass - - def set_dynamic_color(self, base, color_names=None): - # See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Operating-System-Commands - try: - color_names = color_names.decode('utf-8') if color_names else '' - except Exception: - return - - def handle_val(val, param=None): - val %= 100 - if val == 10: # foreground - self.change_default_color('fg', param) - elif val == 11: # background - self.change_default_color('bg', param) - elif val == 12: # cursor color - old, self.cursor.color = self.cursor.color, param - if old != self.cursor.color: - self.cursor_changed() - - if color_names: - for i, cn in enumerate(filter(None, color_names.split(';'))): - handle_val(base + i, cn) - else: - handle_val(base) - - def normal_keypad_mode(self): - pass # Useless for us, since Qt takes care of handling the numpad - - def alternate_keypad_mode(self): - pass # Useless for us, since Qt takes care of handling the numpad - - def debug(self, *args, **kwargs): - """Endpoint for unrecognized escape sequences. - - By default is a noop. - """ - import traceback - traceback.print_stack() - print('unknown escape code:', args, kwargs) diff --git a/kitty/tracker.c b/kitty/tracker.c index cfb26a3e0..05db625ef 100644 --- a/kitty/tracker.c +++ b/kitty/tracker.c @@ -97,25 +97,15 @@ update_cell_range(ChangeTracker *self, PyObject *args) { Py_RETURN_NONE; } -static PyObject* -update_cell_data(ChangeTracker *self, PyObject *args) { -#define update_cell_data_doc "update_cell_data(line_buf, sprite_map, color_profile, data_ptr, default_fg, default_bg, force_screen_refresh)" - SpriteMap *spm; - LineBuf *lb; - ColorProfile *color_profile; - PyObject *dp; - unsigned int *data, y; - unsigned long default_bg, default_fg; +bool tracker_update_cell_data(ChangeTracker *self, LineBuf *lb, SpriteMap *spm, ColorProfile *color_profile, unsigned int *data, unsigned long default_fg, unsigned long default_bg, bool force_screen_refresh) { + unsigned int y; Py_ssize_t start; - int force_screen_refresh; - if (!PyArg_ParseTuple(args, "O!O!O!O!kkp", &LineBuf_Type, &lb, &SpriteMap_Type, &spm, &ColorProfile_Type, &color_profile, &PyLong_Type, &dp, &default_fg, &default_bg, &force_screen_refresh)) return NULL; - data = PyLong_AsVoidPtr(dp); default_fg &= COL_MASK; default_bg &= COL_MASK; #define UPDATE_RANGE(xstart, xmax) \ linebuf_init_line(lb, y); \ - if (!update_cell_range_data(spm, lb->line, (xstart), (xmax), color_profile, default_bg, default_fg, data)) return NULL; + if (!update_cell_range_data(spm, lb->line, (xstart), (xmax), color_profile, default_bg, default_fg, data)) return false; if (self->screen_changed || force_screen_refresh) { for (y = 0; y < self->ynum; y++) { @@ -146,9 +136,24 @@ update_cell_data(ChangeTracker *self, PyObject *args) { } } } - - PyObject *cursor_changed = self->cursor_changed ? Py_True : Py_False; tracker_reset(self); + return true; +} + +PyObject* +update_cell_data(ChangeTracker *self, PyObject *args) { +#define update_cell_data_doc "update_cell_data(line_buf, sprite_map, color_profile, data_ptr, default_fg, default_bg, force_screen_refresh)" + SpriteMap *spm; + LineBuf *lb; + ColorProfile *color_profile; + PyObject *dp; + unsigned int *data; + unsigned long default_bg, default_fg; + int force_screen_refresh; + if (!PyArg_ParseTuple(args, "O!O!O!O!kkp", &LineBuf_Type, &lb, &SpriteMap_Type, &spm, &ColorProfile_Type, &color_profile, &PyLong_Type, &dp, &default_fg, &default_bg, &force_screen_refresh)) return NULL; + data = PyLong_AsVoidPtr(dp); + PyObject *cursor_changed = self->cursor_changed ? Py_True : Py_False; + if (!tracker_update_cell_data(self, lb, spm, color_profile, data, default_fg, default_bg, (bool)force_screen_refresh)) return NULL; Py_INCREF(cursor_changed); return cursor_changed; } diff --git a/kitty/tracker.h b/kitty/tracker.h index 75e644d52..94fb3ab6a 100644 --- a/kitty/tracker.h +++ b/kitty/tracker.h @@ -52,3 +52,4 @@ static inline void tracker_reset(ChangeTracker *self) { PyObject* tracker_consolidate_changes(ChangeTracker *self); bool tracker_resize(ChangeTracker *self, unsigned int ynum, unsigned int xnum); +bool tracker_update_cell_data(ChangeTracker *, LineBuf *, SpriteMap *, ColorProfile *, unsigned int *, unsigned long, unsigned long, bool); diff --git a/kitty_tests/parser.py b/kitty_tests/parser.py index e9d915be8..7dfa20ced 100644 --- a/kitty_tests/parser.py +++ b/kitty_tests/parser.py @@ -41,7 +41,7 @@ class TestScreen(BaseTest): cd = CmdDump() if isinstance(x, str): x = x.encode('utf-8') - parse_bytes_dump(s, x, cd) + parse_bytes_dump(cd, s, x) self.ae(tuple(cd), cmds) def test_simple_parsing(self): diff --git a/pyte/AUTHORS b/pyte/AUTHORS deleted file mode 100644 index 0840f4936..000000000 --- a/pyte/AUTHORS +++ /dev/null @@ -1,14 +0,0 @@ -Authors -======= - -- George Shuklin -- Sergei Lebedev - -Contributors ------------- - -- Alexey Shamrin -- Steve Cohen -- Jonathan Slenders -- David O'Shea -- Andreas Stührk diff --git a/pyte/LICENSE b/pyte/LICENSE deleted file mode 100644 index 65c5ca88a..000000000 --- a/pyte/LICENSE +++ /dev/null @@ -1,165 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. diff --git a/pyte/__init__.py b/pyte/__init__.py deleted file mode 100644 index a96d6ac20..000000000 --- a/pyte/__init__.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -""" - pyte - ~~~~ - - `pyte` implements a mix of VT100, VT220 and VT520 specification, - and aims to support most of the `TERM=linux` functionality. - - Two classes: :class:`~pyte.streams.Stream`, which parses the - command stream and dispatches events for commands, and - :class:`~pyte.screens.Screen` which, when used with a stream - maintains a buffer of strings representing the screen of a - terminal. - - .. warning:: From ``xterm/main.c`` "If you think you know what all - of this code is doing, you are probably very mistaken. - There be serious and nasty dragons here" -- nothing - has changed. - - :copyright: (c) 2011-2012 by Selectel. - :copyright: (c) 2012-2016 by pyte authors and contributors, - see AUTHORS for details. - :license: LGPL, see LICENSE for more details. -""" - -from __future__ import absolute_import - -__all__ = ("Screen", "DiffScreen", "HistoryScreen", - "Stream", "DebugStream") - -import io - -from .streams import Stream, DebugStream - - -if __debug__: - from .compat import str - - def dis(chars): - """A :func:`dis.dis` for terminals. - - >>> dis(b"\x07") # doctest: +NORMALIZE_WHITESPACE - BELL - >>> dis(b"\x1b[20m") # doctest: +NORMALIZE_WHITESPACE - SELECT_GRAPHIC_RENDITION 20 - """ - if isinstance(chars, str): - chars = chars.encode("utf-8") - - with io.StringIO() as buf: - DebugStream(to=buf).feed(chars) - print(buf.getvalue()) diff --git a/pyte/__main__.py b/pyte/__main__.py deleted file mode 100644 index c87805ac0..000000000 --- a/pyte/__main__.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -""" - pyte - ~~~~ - - Command-line tool for "disassembling" escape and CSI sequences:: - - $ echo -e "\e[Jfoo" | python -m pyte - ERASE_IN_DISPLAY 0 - DRAW f - DRAW o - DRAW o - LINEFEED - - $ python -m pyte foo - DRAW f - DRAW o - DRAW o - - :copyright: (c) 2011-2012 by Selectel. - :copyright: (c) 2012-2016 by pyte authors and contributors, - see AUTHORS for details. - :license: LGPL, see LICENSE for more details. -""" - -if __name__ == "__main__": - import sys - import pyte - - if len(sys.argv) == 1: - pyte.dis(sys.stdin.read()) - else: - pyte.dis("".join(sys.argv[1:])) diff --git a/pyte/charsets.py b/pyte/charsets.py deleted file mode 100644 index 4957fe2e4..000000000 --- a/pyte/charsets.py +++ /dev/null @@ -1,143 +0,0 @@ -# -*- coding: utf-8 -*- -""" - pyte.charsets - ~~~~~~~~~~~~~ - - This module defines ``G0`` and ``G1`` charset mappings the same way - they are defined for linux terminal, see - ``linux/drivers/tty/consolemap.c`` @ http://git.kernel.org - - .. note:: ``VT100_MAP`` and ``IBMPC_MAP`` were taken unchanged - from linux kernel source and therefore are licensed - under **GPL**. - - :copyright: (c) 2011-2012 by Selectel. - :copyright: (c) 2012-2016 by pyte authors and contributors, - see AUTHORS for details. - :license: LGPL, see LICENSE for more details. -""" - -from __future__ import absolute_import, unicode_literals - -from .compat import chr, map - - -#: Latin1. -LAT1_MAP = "".join(map(chr, range(256))) - -#: VT100 graphic character set. -VT100_MAP = "".join(chr(c) for c in [ - 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, - 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, - 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, - 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, - 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, - 0x0028, 0x0029, 0x002a, 0x2192, 0x2190, 0x2191, 0x2193, 0x002f, - 0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, - 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, - 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, - 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, - 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, - 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x00a0, - 0x25c6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1, - 0x2591, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba, - 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c, - 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, 0x007f, - 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, - 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, - 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, - 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, - 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, - 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, - 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, - 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, - 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, - 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, - 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, - 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df, - 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, - 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef, - 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, - 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff -]) - -#: IBM Codepage 437. -IBMPC_MAP = "".join(chr(c) for c in [ - 0x0000, 0x263a, 0x263b, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022, - 0x25d8, 0x25cb, 0x25d9, 0x2642, 0x2640, 0x266a, 0x266b, 0x263c, - 0x25b6, 0x25c0, 0x2195, 0x203c, 0x00b6, 0x00a7, 0x25ac, 0x21a8, - 0x2191, 0x2193, 0x2192, 0x2190, 0x221f, 0x2194, 0x25b2, 0x25bc, - 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, - 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, - 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, - 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, - 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, - 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, - 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, - 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, - 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, - 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, - 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, - 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2302, - 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, - 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5, - 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, - 0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192, - 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, - 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb, - 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, - 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510, - 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, - 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567, - 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, - 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580, - 0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, - 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229, - 0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, - 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0 -]) - - -#: VAX42 character set. -VAX42_MAP = "".join(chr(c) for c in [ - 0x0000, 0x263a, 0x263b, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022, - 0x25d8, 0x25cb, 0x25d9, 0x2642, 0x2640, 0x266a, 0x266b, 0x263c, - 0x25b6, 0x25c0, 0x2195, 0x203c, 0x00b6, 0x00a7, 0x25ac, 0x21a8, - 0x2191, 0x2193, 0x2192, 0x2190, 0x221f, 0x2194, 0x25b2, 0x25bc, - 0x0020, 0x043b, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, - 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, - 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, - 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x0435, - 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, - 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, - 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, - 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, - 0x0060, 0x0441, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, - 0x0435, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x043a, - 0x0070, 0x0071, 0x0442, 0x0073, 0x043b, 0x0435, 0x0076, 0x0077, - 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2302, - 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, - 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5, - 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, - 0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192, - 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, - 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb, - 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, - 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510, - 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, - 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567, - 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, - 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580, - 0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4, - 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229, - 0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, - 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0 -]) - - -MAPS = { - b"B": LAT1_MAP, - b"0": VT100_MAP, - b"U": IBMPC_MAP, - b"V": VAX42_MAP -} diff --git a/pyte/compat.py b/pyte/compat.py deleted file mode 100644 index cc60c193d..000000000 --- a/pyte/compat.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -""" - pyte.compat - ~~~~~~~~~~~ - - Python version specific compatibility fixes. - - :copyright: (c) 2015-2016 by pyte authors and contributors, - see AUTHORS for details. - :license: LGPL, see LICENSE for more details. -""" - -import sys - -if sys.version_info[0] == 2: - from future_builtins import map - - range = xrange - str = unicode - chr = unichr - - from functools import partial - iter_bytes = partial(map, ord) -else: - from builtins import map, range, str, chr - iter_bytes = iter diff --git a/pyte/control.py b/pyte/control.py deleted file mode 100644 index 931055e57..000000000 --- a/pyte/control.py +++ /dev/null @@ -1,73 +0,0 @@ -# -*- coding: utf-8 -*- -""" - pyte.control - ~~~~~~~~~~~~ - - This module defines simple control sequences, recognized by - :class:`~pyte.streams.Stream`, the set of codes here is for - ``TERM=linux`` which is a superset of VT102. - - :copyright: (c) 2011-2012 by Selectel. - :copyright: (c) 2012-2016 by pyte authors and contributors, - see AUTHORS for details. - :license: LGPL, see LICENSE for more details. -""" - -#: *Space*: Not suprisingly -- ``" "``. -SP = b" " - -#: *Null*: Does nothing. -NUL = b"\x00" - -#: *Bell*: Beeps. -BEL = b"\x07" - -#: *Backspace*: Backspace one column, but not past the begining of the -#: line. -BS = b"\x08" - -#: *Horizontal tab*: Move cursor to the next tab stop, or to the end -#: of the line if there is no earlier tab stop. -HT = b"\x09" - -#: *Linefeed*: Give a line feed, and, if :data:`pyte.modes.LNM` (new -#: line mode) is set also a carriage return. -LF = b"\n" -#: *Vertical tab*: Same as :data:`LF`. -VT = b"\x0b" -#: *Form feed*: Same as :data:`LF`. -FF = b"\x0c" - -#: *Carriage return*: Move cursor to left margin on current line. -CR = b"\r" - -#: *Shift out*: Activate G1 character set. -SO = b"\x0e" - -#: *Shift in*: Activate G0 character set. -SI = b"\x0f" - -#: *Cancel*: Interrupt escape sequence. If received during an escape or -#: control sequence, cancels the sequence and displays substitution -#: character. -CAN = b"\x18" -#: *Substitute*: Same as :data:`CAN`. -SUB = b"\x1a" - -#: *Escape*: Starts an escape sequence. -ESC = b"\x1b" - -#: *Delete*: Is ignored. -DEL = b"\x7f" - -#: *Control sequence introducer*: An equivalent for ``ESC [``. -CSI = b"\x9b" - -#: *String terminator*. -ST = b"\x9c" - -#: *Operating system command*. -OSC = b"\x9d" - -#: Device Control function (DCS) -DCS = ESC + b"P" diff --git a/pyte/escape.py b/pyte/escape.py deleted file mode 100644 index 7f039bc2a..000000000 --- a/pyte/escape.py +++ /dev/null @@ -1,165 +0,0 @@ -# -*- coding: utf-8 -*- -""" - pyte.escape - ~~~~~~~~~~~ - - This module defines both CSI and non-CSI escape sequences, recognized - by :class:`~pyte.streams.Stream` and subclasses. - - :copyright: (c) 2011-2012 by Selectel. - :copyright: (c) 2012-2016 by pyte authors and contributors, - see AUTHORS for details. - :license: LGPL, see LICENSE for more details. -""" - -#: *Reset*. -RIS = b"c" - -#: *Index*: Move cursor down one line in same column. If the cursor is -#: at the bottom margin, the screen performs a scroll-up. -IND = b"D" - -#: *Next line*: Same as :data:`pyte.control.LF`. -NEL = b"E" - -#: Tabulation set: Set a horizontal tab stop at cursor position. -HTS = b"H" - -#: *Reverse index*: Move cursor up one line in same column. If the -#: cursor is at the top margin, the screen performs a scroll-down. -RI = b"M" - -#: Save cursor: Save cursor position, character attribute (graphic -#: rendition), character set, and origin mode selection (see -#: :data:`DECRC`). -DECSC = b"7" - -#: *Restore cursor*: Restore previously saved cursor position, character -#: attribute (graphic rendition), character set, and origin mode -#: selection. If none were saved, move cursor to home position. -DECRC = b"8" - -#: Set normal keypad mode -DECPNM = b'>' - -#: Set alternate keypad mode -DECPAM = b'=' - -# "Sharp" escape sequences. -# ------------------------- - -#: *Alignment display*: Fill screen with uppercase E's for testing -#: screen focus and alignment. -DECALN = b"8" - - -# ECMA-48 CSI sequences. -# --------------------- - -#: *Insert character*: Insert the indicated # of blank characters. -ICH = b"@" - -#: *Cursor up*: Move cursor up the indicated # of lines in same column. -#: Cursor stops at top margin. -CUU = b"A" - -#: *Cursor down*: Move cursor down the indicated # of lines in same -#: column. Cursor stops at bottom margin. -CUD = b"B" - -#: *Cursor forward*: Move cursor right the indicated # of columns. -#: Cursor stops at right margin. -CUF = b"C" - -#: *Cursor back*: Move cursor left the indicated # of columns. Cursor -#: stops at left margin. -CUB = b"D" - -#: *Cursor next line*: Move cursor down the indicated # of lines to -#: column 1. -CNL = b"E" - -#: *Cursor previous line*: Move cursor up the indicated # of lines to -#: column 1. -CPL = b"F" - -#: *Cursor horizontal align*: Move cursor to the indicated column in -#: current line. -CHA = b"G" - -#: *Cursor position*: Move cursor to the indicated line, column (origin -#: at ``1, 1``). -CUP = b"H" - -#: *Erase data* (default: from cursor to end of line). -ED = b"J" - -#: *Erase in line* (default: from cursor to end of line). -EL = b"K" - -#: *Insert line*: Insert the indicated # of blank lines, starting from -#: the current line. Lines displayed below cursor move down. Lines moved -#: past the bottom margin are lost. -IL = b"L" - -#: *Delete line*: Delete the indicated # of lines, starting from the -#: current line. As lines are deleted, lines displayed below cursor -#: move up. Lines added to bottom of screen have spaces with same -#: character attributes as last line move up. -DL = b"M" - -#: *Delete character*: Delete the indicated # of characters on the -#: current line. When character is deleted, all characters to the right -#: of cursor move left. -DCH = b"P" - -#: *Erase character*: Erase the indicated # of characters on the -#: current line. -ECH = b"X" - -#: *Horizontal position relative*: Same as :data:`CUF`. -HPR = b"a" - -#: *Device Attributes*. -DA = b"c" - -#: *Vertical position adjust*: Move cursor to the indicated line, -#: current column. -VPA = b"d" - -#: *Vertical position relative*: Same as :data:`CUD`. -VPR = b"e" - -#: *Horizontal / Vertical position*: Same as :data:`CUP`. -HVP = b"f" - -#: *Tabulation clear*: Clears a horizontal tab stop at cursor position. -TBC = b"g" - -#: *Set mode*. -SM = b"h" - -#: *Reset mode*. -RM = b"l" - -#: *Select graphics rendition*: The terminal can display the following -#: character attributes that change the character display without -#: changing the character (see :mod:`pyte.graphics`). -SGR = b"m" - -#: *Device status report*. -DSR = b"n" - -#: *Select top and bottom margins*: Selects margins, defining the -#: scrolling region; parameters are top and bottom line. If called -#: without any arguments, whole screen is used. -DECSTBM = b"r" - -#: *Horizontal position adjust*: Same as :data:`CHA`. -HPA = b"'" - - -# Misc sequences - -#: Change cursor shape/blink -DECSCUSR = b'q' diff --git a/pyte/graphics.py b/pyte/graphics.py deleted file mode 100644 index 376f27236..000000000 --- a/pyte/graphics.py +++ /dev/null @@ -1,160 +0,0 @@ -# -*- coding: utf-8 -*- -""" - pyte.graphics - ~~~~~~~~~~~~~ - - This module defines graphic-related constants, mostly taken from - :manpage:`console_codes(4)` and - http://pueblo.sourceforge.net/doc/manual/ansi_color_codes.html. - - :copyright: (c) 2011-2012 by Selectel. - :copyright: (c) 2012-2016 by pyte authors and contributors, - see AUTHORS for details. - :license: LGPL, see LICENSE for more details. -""" - -from __future__ import unicode_literals - -#: A mapping of ANSI text style codes to style names, "+" means the: -#: attribute is set, "-" -- reset; example: -#: -#: >>> text[1] -#: '+bold' -#: >>> text[9] -#: '+strikethrough' -TEXT = { - 1: "+bold", - 3: "+italics", - 4: "+underscore", - 7: "+reverse", - 9: "+strikethrough", - 22: "-bold", - 23: "-italics", - 24: "-underscore", - 27: "-reverse", - 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. -#: -#: >>> FG_ANSI[30] -#: 'black' -#: >>> FG_ANSI[38] -#: 'default' -FG_ANSI = { - 30: "black", - 31: "red", - 32: "green", - 33: "brown", - 34: "blue", - 35: "magenta", - 36: "cyan", - 37: "white", - 39: "default" # white. -} - -#: An alias to :data:`~pyte.graphics.FG_ANSI` for compatibility. -FG = FG_ANSI - -#: A mapping of non-standard ``aixterm`` foreground color codes to -#: color names. These are high intensity colors and thus should be -#: complemented by ``+bold``. -FG_AIXTERM = { - 90: "black", - 91: "red", - 92: "green", - 93: "brown", - 94: "blue", - 95: "magenta", - 96: "cyan", - 97: "white" -} - -#: A mapping of ANSI background color codes to color names. -#: -#: >>> BG_ANSI[40] -#: 'black' -#: >>> BG_ANSI[48] -#: 'default' -BG_ANSI = { - 40: "black", - 41: "red", - 42: "green", - 43: "brown", - 44: "blue", - 45: "magenta", - 46: "cyan", - 47: "white", - 49: "default" # black. -} - -#: An alias to :data:`~pyte.graphics.BG_ANSI` for compatibility. -BG = BG_ANSI - -#: A mapping of non-standard ``aixterm`` background color codes to -#: color names. These are high intensity colors and thus should be -#: complemented by ``+bold``. -BG_AIXTERM = { - 100: "black", - 101: "red", - 102: "green", - 103: "brown", - 104: "blue", - 105: "magenta", - 106: "cyan", - 107: "white" -} - -#: SGR code for foreground in 256 or True color mode. -FG_256 = 38 - -#: SGR code for background in 256 or True color mode. -BG_256 = 48 - -#: A table of 256 foreground or background colors. -# The following code is part of the Pygments project (BSD licensed). -FG_BG_256 = [ - (0x00, 0x00, 0x00), # 0 - (0xcd, 0x00, 0x00), # 1 - (0x00, 0xcd, 0x00), # 2 - (0xcd, 0xcd, 0x00), # 3 - (0x00, 0x00, 0xee), # 4 - (0xcd, 0x00, 0xcd), # 5 - (0x00, 0xcd, 0xcd), # 6 - (0xe5, 0xe5, 0xe5), # 7 - (0x7f, 0x7f, 0x7f), # 8 - (0xff, 0x00, 0x00), # 9 - (0x00, 0xff, 0x00), # 10 - (0xff, 0xff, 0x00), # 11 - (0x5c, 0x5c, 0xff), # 12 - (0xff, 0x00, 0xff), # 13 - (0x00, 0xff, 0xff), # 14 - (0xff, 0xff, 0xff), # 15 -] - -# colors 16..232: the 6x6x6 color cube -valuerange = (0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff) - -for i in range(217): - r = valuerange[(i // 36) % 6] - g = valuerange[(i // 6) % 6] - b = valuerange[i % 6] - FG_BG_256.append((r, g, b)) - -# colors 233..253: grayscale -for i in range(1, 22): - v = 8 + i * 10 - FG_BG_256.append((v, v, v)) diff --git a/pyte/modes.py b/pyte/modes.py deleted file mode 100644 index c0db78b34..000000000 --- a/pyte/modes.py +++ /dev/null @@ -1,77 +0,0 @@ -# -*- coding: utf-8 -*- -""" - pyte.modes - ~~~~~~~~~~ - - This module defines terminal mode switches, used by - :class:`~pyte.screens.Screen`. There're two types of terminal modes: - - * `non-private` which should be set with ``ESC [ N h``, where ``N`` - is an integer, representing mode being set; and - * `private` which should be set with ``ESC [ ? N h``. - - The latter are shifted 5 times to the right, to be easily - distinguishable from the former ones; for example `Origin Mode` - -- :data:`DECOM` is ``192`` not ``6``. - - >>> DECOM - 192 - - :copyright: (c) 2011-2012 by Selectel. - :copyright: (c) 2012-2016 by pyte authors and contributors, - see AUTHORS for details. - :license: LGPL, see LICENSE for more details. -""" - -#: *Line Feed/New Line Mode*: When enabled, causes a received -#: :data:`~pyte.control.LF`, :data:`pyte.control.FF`, or -#: :data:`~pyte.control.VT` to move the cursor to the first column of -#: the next line. -LNM = 20 - -#: *Insert/Replace Mode*: When enabled, new display characters move -#: old display characters to the right. Characters moved past the -#: right margin are lost. Otherwise, new display characters replace -#: old display characters at the cursor position. -IRM = 4 - - -# Private modes. -# .............. - -#: *Text Cursor Enable Mode*: determines if the text cursor is -#: visible. -DECTCEM = 25 << 5 - -#: *Screen Mode*: toggles screen-wide reverse-video mode. -DECSCNM = 5 << 5 - -#: *Origin Mode*: allows cursor addressing relative to a user-defined -#: origin. This mode resets when the terminal is powered up or reset. -#: It does not affect the erase in display (ED) function. -DECOM = 6 << 5 - -#: *Auto Wrap Mode*: selects where received graphic characters appear -#: when the cursor is at the right margin. -DECAWM = 7 << 5 - -#: *Column Mode*: selects the number of columns per line (80 or 132) -#: on the screen. -DECCOLM = 3 << 5 - -#: Bracketed paste mode -# http://cirw.in/blog/bracketed-paste -BRACKETED_PASTE = 2004 << 5 -BRACKETED_PASTE_START = '\033[200~'.encode('ascii') -BRACKETED_PASTE_END = '\033[201~'.encode('ascii') - -#: Alternate screen buffer -ALTERNATE_SCREEN = 1049 << 5 - -#: Xterm mouse protocol -SEND_MOUSE_ON_PRESS_AND_RELEASE = 1000 << 5 -HILITE_MOUSE_TRACKING = 1001 << 5 -CELL_MOTION_MOUSE_TRACKING = 1002 << 5 -FOCUS_TRACKING = 1004 << 5 -UTF8_MOUSE_MODE = 1005 << 5 -SGR_MOUSE_MODE = 1006 << 6 diff --git a/pyte/streams.py b/pyte/streams.py deleted file mode 100644 index ec2aed939..000000000 --- a/pyte/streams.py +++ /dev/null @@ -1,419 +0,0 @@ -# -*- coding: utf-8 -*- -""" - pyte.streams - ~~~~~~~~~~~~ - - This module provides three stream implementations with different - features; for starters, here's a quick example of how streams are - typically used: - - >>> import pyte - >>> screen = pyte.Screen(80, 24) - >>> stream = pyte.Stream(screen) - >>> stream.feed(b"\x1B[5B") # Move the cursor down 5 rows. - >>> screen.cursor.y - 5 - - :copyright: (c) 2011-2012 by Selectel. - :copyright: (c) 2012-2016 by pyte authors and contributors, - see AUTHORS for details. - :license: LGPL, see LICENSE for more details. -""" - -from __future__ import absolute_import, unicode_literals - -from functools import wraps -import itertools -import os -import re -import sys -from collections import defaultdict - -from . import control as ctrl, escape as esc -from .compat import str - - -class Stream(object): - """A stream is a state machine that parses a stream of bytes and - dispatches events based on what it sees. - - :param pyte.screens.Screen screen: a screen to dispatch events to. - :param bool strict: check if a given screen implements all required - events. - - .. note:: - - Stream only accepts :func:`bytes` as input. Decoding it into text - is the responsibility of the :class:`~pyte.screens.Screen`. - - .. versionchanged 0.6.0:: - - For performance reasons the binding between stream events and - screen methods was made static. As a result, the stream **will - not** dispatch events to methods added to screen **after** the - stream was created. - - .. seealso:: - - `man console_codes `_ - For details on console codes listed bellow in :attr:`basic`, - :attr:`escape`, :attr:`csi`, :attr:`sharp` and :attr:`percent`. - """ - - #: Control sequences, which don't require any arguments. - basic = { - ctrl.BEL: "bell", - ctrl.BS: "backspace", - ctrl.HT: "tab", - ctrl.LF: "linefeed", - ctrl.VT: "linefeed", - ctrl.FF: "linefeed", - ctrl.CR: "carriage_return", - ctrl.SO: "shift_out", - ctrl.SI: "shift_in", - } - - #: non-CSI escape sequences. - escape = { - esc.RIS: "reset", - esc.IND: "index", - esc.NEL: "linefeed", - esc.RI: "reverse_index", - esc.HTS: "set_tab_stop", - esc.DECSC: "save_cursor", - esc.DECRC: "restore_cursor", - esc.DECPNM: 'normal_keypad_mode', - esc.DECPAM: 'alternate_keypad_mode', - } - - #: "sharp" escape sequences -- ``ESC # ``. - sharp = { - esc.DECALN: "alignment_display", - } - - #: CSI escape sequences -- ``CSI P1;P2;...;Pn ``. - csi = { - esc.ICH: "insert_characters", - esc.CUU: "cursor_up", - esc.CUD: "cursor_down", - esc.CUF: "cursor_forward", - esc.CUB: "cursor_back", - esc.CNL: "cursor_down1", - esc.CPL: "cursor_up1", - esc.CHA: "cursor_to_column", - esc.CUP: "cursor_position", - esc.ED: "erase_in_display", - esc.EL: "erase_in_line", - esc.IL: "insert_lines", - esc.DL: "delete_lines", - esc.DCH: "delete_characters", - esc.ECH: "erase_characters", - esc.HPR: "cursor_forward", - esc.DA: "report_device_attributes", - esc.VPA: "cursor_to_line", - esc.VPR: "cursor_down", - esc.HVP: "cursor_position", - esc.TBC: "clear_tab_stop", - esc.SM: "set_mode", - esc.RM: "reset_mode", - esc.SGR: "select_graphic_rendition", - esc.DSR: "report_device_status", - esc.DECSTBM: "set_margins", - esc.HPA: "cursor_to_column", - esc.DECSCUSR: 'set_cursor_shape', - } - - #: A set of all events dispatched by the stream. - events = frozenset(itertools.chain( - basic.values(), escape.values(), sharp.values(), csi.values(), - ["define_charset", "select_other_charset"], - ["set_icon", "set_title", 'set_dynamic_color'], # OSC. - ["draw", "debug"])) - - #: A regular expression pattern matching everything what can be - #: considered plain text. - _special = set([ctrl.ESC, ctrl.CSI, ctrl.NUL, ctrl.DEL, ctrl.OSC]) - _special.update(basic) - _text_pattern = re.compile( - b"[^" + b"".join(map(re.escape, _special)) + b"]+") - del _special - - def __init__(self, screen=None, strict=True): - self.listener = None - self.strict = False - - if screen is not None: - self.attach(screen) - - def attach(self, screen, only=()): - """Adds a given screen to the listener queue. - - :param pyte.screens.Screen screen: a screen to attach to. - :param list only: a list of events you want to dispatch to a - given screen (empty by default, which means - -- dispatch all events). - """ - if self.strict: - for event in self.events: - if not hasattr(screen, event): - error_message = "{0} is missing {1}".format(screen, event) - raise TypeError(error_message) - - self.listener = screen - self._parser = self._parser_fsm() - self._taking_plain_text = next(self._parser) - - def detach(self, screen): - """Removes a given screen from the listener queue and fails - silently if it's not attached. - - :param pyte.screens.Screen screen: a screen to detach. - """ - if screen is self.listener: - self.listener = None - - def feed(self, data: bytes) -> None: - """Consumes a string and advances the state as necessary. - - :param bytes data: a blob of data to feed from. - """ - send = self._parser.send - draw = self.listener.draw - match_text = self._text_pattern.match - taking_plain_text = self._taking_plain_text - - # TODO: use memoryview? - length = len(data) - offset = 0 - while offset < length: - if taking_plain_text: - match = match_text(data, offset) - if match is not None: - start, offset = match.span() - draw(data[start:offset]) - else: - taking_plain_text = False - else: - taking_plain_text = send(data[offset:offset + 1]) - offset += 1 - - self._taking_plain_text = taking_plain_text - - def _parser_fsm(self): - """An FSM implemented as a coroutine. - - This generator is not the most beautiful, but it is as performant - as possible. When a process generates a lot of output, then this - will be the bottleneck, because it processes just one character - at a time. - - We did many manual optimizations to this function in order to make - it as efficient as possible. Don't change anything without profiling - first. - """ - basic = self.basic - listener = self.listener - draw = listener.draw - debug = listener.debug - - ESC, CSI = ctrl.ESC, ctrl.CSI - OSC, ST, DCS = ctrl.OSC, ctrl.ST, ctrl.DCS - SP_OR_GT = ctrl.SP + b">" - NUL_OR_DEL = ctrl.NUL + ctrl.DEL - CAN_OR_SUB = ctrl.CAN + ctrl.SUB - ALLOWED_IN_CSI = b"".join([ctrl.BEL, ctrl.BS, ctrl.HT, ctrl.LF, - ctrl.VT, ctrl.FF, ctrl.CR]) - - def create_dispatcher(mapping): - return defaultdict(lambda: debug, dict( - (event, getattr(listener, attr)) - for event, attr in mapping.items())) - - basic_dispatch = create_dispatcher(basic) - sharp_dispatch = create_dispatcher(self.sharp) - escape_dispatch = create_dispatcher(self.escape) - csi_dispatch = create_dispatcher(self.csi) - - while True: - # ``True`` tells ``Screen.feed`` that it is allowed to send - # chunks of plain text directly to the listener, instead - # of this generator.) - char = yield True - - if char == ESC: - # Most non-VT52 commands start with a left-bracket after the - # escape and then a stream of parameters and a command; with - # a single notable exception -- :data:`escape.DECOM` sequence, - # which starts with a sharp. - # - # .. versionchanged:: 0.4.10 - # - # For compatibility with Linux terminal stream also - # recognizes ``ESC % C`` sequences for selecting control - # character set. However, in the current version these - # are noop. - char = yield - if char == b"[": - char = CSI # Go to CSI. - elif char == b"]": - char = OSC # Go to OSC. - elif char == b'P': - char = DCS # Go to DCS - else: - if char == b"#": - sharp_dispatch[(yield)]() - if char == b"%": - listener.select_other_charset((yield)) - elif char in b"()": - listener.define_charset((yield), mode=char) - else: - escape_dispatch[char]() - continue # Don't go to CSI. - - if char in basic: - basic_dispatch[char]() - elif char == CSI: - # All parameters are unsigned, positive decimal integers, with - # the most significant digit sent first. Any parameter greater - # than 9999 is set to 9999. If you do not specify a value, a 0 - # value is assumed. - # - # .. seealso:: - # - # `VT102 User Guide `_ - # For details on the formatting of escape arguments. - # - # `VT220 Programmer Ref. `_ - # For details on the characters valid for use as - # arguments. - params = [] - current = bytearray() - private = secondary = False - while True: - char = yield - if char == b"?": - private = True - elif char in ALLOWED_IN_CSI: - basic_dispatch[char]() - elif char in SP_OR_GT: - secondary = char.decode('ascii') # Added by Kovid - elif char in CAN_OR_SUB: - # If CAN or SUB is received during a sequence, the - # current sequence is aborted; terminal displays - # the substitute character, followed by characters - # in the sequence received after CAN or SUB. - draw(char) - break - elif char.isdigit(): - current.extend(char) - else: - params.append(min(int(bytes(current) or 0), 9999)) - - if char == b";": - current = bytearray() - else: - if private: - csi_dispatch[char](*params, private=True) - else: - if secondary: # Added by Kovid - csi_dispatch[char](*params, secondary=secondary) - else: - csi_dispatch[char](*params) - break # CSI is finished. - elif char == OSC: - code = bytearray() - while True: - char = yield - if char == ST or char == ctrl.BEL or char == b';': - break - code.extend(char) - code = bytes(code) - param = bytearray() - if char == b';': - while True: - char = yield - if char == ST or char == ctrl.BEL: - break - else: - param.extend(char) - - param = bytes(param) - try: - code = int(code) - except Exception: - code = None - if code == 0: - listener.set_title(param) - listener.set_icon_name(param) - elif code == 1: - listener.set_icon_name(param) - elif code == 2: - listener.set_title(param) - elif 9 < code < 20: - listener.set_dynamic_color(code, param) - elif 109 < code < 120: - listener.set_dynamic_color(code) - elif char == DCS: - # See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Device-Control-functions - code = yield - param = bytearray() - while True: - char = yield - if char == ST: - break - else: - param.extend(char) - # TODO: Implement these - elif char not in NUL_OR_DEL: - draw(char) - - -class DebugStream(Stream): - r"""Stream, which dumps a subset of the dispatched events to a given - file-like object (:data:`sys.stdout` by default). - - >>> import io - >>> with io.StringIO() as buf: - ... stream = DebugStream(to=buf) - ... stream.feed(b"\x1b[1;24r\x1b[4l\x1b[24;1H\x1b[0;10m") - ... print(buf.getvalue()) - ... - ... # doctest: +NORMALIZE_WHITESPACE - SET_MARGINS 1; 24 - RESET_MODE 4 - CURSOR_POSITION 24; 1 - SELECT_GRAPHIC_RENDITION 0; 10 - - :param file to: a file-like object to write debug information to. - :param list only: a list of events you want to debug (empty by - default, which means -- debug all events). - """ - - def __init__(self, screen, to=sys.stdout, only=()): - stream = super(DebugStream, self) - - def safe_str(chunk): - if isinstance(chunk, bytes): - chunk = chunk.decode("utf-8") - elif not isinstance(chunk, str): - chunk = str(chunk) - - return chunk - - class Bugger: - - def __getattr__(self, event): - - @wraps(getattr(screen, event)) - def inner(*args, **kwargs): - if not only or event in only: - to.write(event.upper() + " ") - to.write("; ".join(map(safe_str, args))) - to.write(" ") - to.write(", ".join("{0}: {1}".format(k, safe_str(v)) - for k, v in kwargs.items())) - to.write(os.linesep) - getattr(screen, event)(*args, **kwargs) - - return inner - stream.__init__(Bugger()) diff --git a/session.vim b/session.vim index 1edd24e9f..f3e572555 100644 --- a/session.vim +++ b/session.vim @@ -1,5 +1,5 @@ " Scan the following dirs recursively for tags -let g:project_tags_dirs = ['kitty', 'pyte'] +let g:project_tags_dirs = ['kitty'] let g:syntastic_python_flake8_exec = 'flake8' let g:ycm_python_binary_path = 'python3' set wildignore+==template.py