A new protocol extension to unscroll the screen

See https://gitlab.freedesktop.org/terminal-wg/specifications/-/issues/30
This commit is contained in:
Kovid Goyal 2021-04-23 15:44:38 +05:30
parent 77b8e204ad
commit b32c346eed
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
7 changed files with 78 additions and 5 deletions

View File

@ -7,6 +7,10 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
0.20.2 [future] 0.20.2 [future]
---------------------- ----------------------
- A new protocol extension to :ref:`unscroll <unscroll>` text from the
scrollback buffer onto the screen. Useful, for example, to restore
the screen after showing completions below the shell prompt.
- Linux: Fix binary kitty builds not able to load fonts in WOFF2 format - Linux: Fix binary kitty builds not able to load fonts in WOFF2 format
(:iss:`3506`) (:iss:`3506`)

View File

@ -157,6 +157,37 @@ protocol extension, it can be disabled by specifying ``no-append`` to the
:opt:`clipboard_control` setting. :opt:`clipboard_control` setting.
.. _unscroll:
Unscrolling the screen
-----------------------
This is a small extension to the `SD (Pan up) escape code
<https://vt100.net/docs/vt510-rm/SD.html>`_ from the VT-420 terminal. The
``SD`` escape code normally causes the text on screen to scroll down by the
specified number of lines, with empty lines appearing at the top of the screen.
This extension allows the new lines to be filled in from the scrollback buffer
instead of being blank.
The motivation for this is that many modern shells will show completions in a
block of lines under the cursor, this causes some of the on-screen text to be
lost even after the completion is completed, because it has scrolled off
screen. This escape code allows that text to be restored.
The syntax of the escape code is identical to that of ``SD`` except that it has
a trailing ``+`` modifier. This is legal under the `ECMA 48 standard
<https://www.ecma-international.org/publications-and-standards/standards/ecma-48/>`_
and unused for any other purpose as far as I can tell. So for example, to
unscroll three lines, the escape code would be::
CSI 3 + T
See `discussion here
<https://gitlab.freedesktop.org/terminal-wg/specifications/-/issues/30>`_.
.. versionadded:: 0.20.2
.. _desktop_notifications: .. _desktop_notifications:

View File

@ -932,7 +932,13 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
NO_MODIFIERS(end_modifier, ' ', "Select presentation directions escape code not implemented"); NO_MODIFIERS(end_modifier, ' ', "Select presentation directions escape code not implemented");
CALL_CSI_HANDLER1(screen_scroll, 1); CALL_CSI_HANDLER1(screen_scroll, 1);
case SD: case SD:
CALL_CSI_HANDLER1(screen_reverse_scroll, 1); if (!start_modifier && end_modifier == '+') {
CALL_CSI_HANDLER1(screen_reverse_scroll_and_fill_from_scrollback, 1);
} else {
NO_MODIFIERS(start_modifier, 0, "");
CALL_CSI_HANDLER1(screen_reverse_scroll, 1);
}
break;
case DECSTR: case DECSTR:
if (end_modifier == '$') { if (end_modifier == '$') {
// DECRQM // DECRQM

View File

@ -1136,17 +1136,29 @@ screen_reverse_index(Screen *self) {
} else screen_cursor_up(self, 1, false, -1); } else screen_cursor_up(self, 1, false, -1);
} }
void static void
screen_reverse_scroll(Screen *self, unsigned int count) { _reverse_scroll(Screen *self, unsigned int count, bool fill_from_scrollback) {
// Scroll the screen down by count lines, not moving the cursor // Scroll the screen down by count lines, not moving the cursor
count = MIN(self->lines, count); count = MIN(self->lines, count);
unsigned int top = self->margin_top, bottom = self->margin_bottom; unsigned int top = self->margin_top, bottom = self->margin_bottom;
while (count > 0) { fill_from_scrollback = fill_from_scrollback && self->linebuf == self->main_linebuf;
count--; while (count-- > 0) {
if (fill_from_scrollback) historybuf_pop_line(self->historybuf, self->alt_linebuf->line);
INDEX_DOWN; INDEX_DOWN;
if (fill_from_scrollback) linebuf_copy_line_to(self->main_linebuf, self->alt_linebuf->line, 0);
} }
} }
void
screen_reverse_scroll(Screen *self, unsigned int count) {
_reverse_scroll(self, count, false);
}
void
screen_reverse_scroll_and_fill_from_scrollback(Screen *self, unsigned int count) {
_reverse_scroll(self, count, true);
}
void void
screen_carriage_return(Screen *self) { screen_carriage_return(Screen *self) {
@ -2888,6 +2900,15 @@ hyperlink_at(Screen *self, PyObject *args) {
return Py_BuildValue("s", url); return Py_BuildValue("s", url);
} }
static PyObject*
reverse_scroll(Screen *self, PyObject *args) {
int fill_from_scrollback = 0;
unsigned int amt;
if (!PyArg_ParseTuple(args, "I|p", &amt, &fill_from_scrollback)) return NULL;
_reverse_scroll(self, amt, fill_from_scrollback);
Py_RETURN_NONE;
}
#define MND(name, args) {#name, (PyCFunction)name, args, #name}, #define MND(name, args) {#name, (PyCFunction)name, args, #name},
#define MODEFUNC(name) MND(name, METH_NOARGS) MND(set_##name, METH_O) #define MODEFUNC(name) MND(name, METH_NOARGS) MND(set_##name, METH_O)
@ -2911,6 +2932,7 @@ static PyMethodDef methods[] = {
MND(hyperlinks_as_list, METH_NOARGS) MND(hyperlinks_as_list, METH_NOARGS)
MND(garbage_collect_hyperlink_pool, METH_NOARGS) MND(garbage_collect_hyperlink_pool, METH_NOARGS)
MND(hyperlink_for_id, METH_O) MND(hyperlink_for_id, METH_O)
MND(reverse_scroll, METH_VARARGS)
METHOD(current_char_width, METH_NOARGS) METHOD(current_char_width, METH_NOARGS)
MND(insert_lines, METH_VARARGS) MND(insert_lines, METH_VARARGS)
MND(delete_lines, METH_VARARGS) MND(delete_lines, METH_VARARGS)

View File

@ -157,6 +157,7 @@ void screen_reverse_index(Screen *self);
void screen_index(Screen *self); void screen_index(Screen *self);
void screen_scroll(Screen *self, unsigned int count); void screen_scroll(Screen *self, unsigned int count);
void screen_reverse_scroll(Screen *self, unsigned int count); void screen_reverse_scroll(Screen *self, unsigned int count);
void screen_reverse_scroll_and_fill_from_scrollback(Screen *self, unsigned int count);
void screen_reset(Screen *self); void screen_reset(Screen *self);
void screen_set_tab_stop(Screen *self); void screen_set_tab_stop(Screen *self);
void screen_tab(Screen *self); void screen_tab(Screen *self);

View File

@ -184,6 +184,9 @@ class TestParser(BaseTest):
pb('\033[3 @', ('Shift left escape code not implemented',)) pb('\033[3 @', ('Shift left escape code not implemented',))
pb('\033[3 A', ('Shift right escape code not implemented',)) pb('\033[3 A', ('Shift right escape code not implemented',))
pb('\033[3;4 S', ('Select presentation directions escape code not implemented',)) pb('\033[3;4 S', ('Select presentation directions escape code not implemented',))
pb('\033[1T', ('screen_reverse_scroll', 1))
pb('\033[T', ('screen_reverse_scroll', 1))
pb('\033[+T', ('screen_reverse_scroll_and_fill_from_scrollback', 1))
def test_csi_code_rep(self): def test_csi_code_rep(self):
s = self.create_screen(8) s = self.create_screen(8)

View File

@ -315,6 +315,12 @@ class TestScreen(BaseTest):
def assert_lines(*lines): def assert_lines(*lines):
return self.ae(lines, tuple(str(s.line(i)) for i in range(s.lines))) return self.ae(lines, tuple(str(s.line(i)) for i in range(s.lines)))
# test the reverse scroll function
s = prepare_screen(map(str, range(6)))
assert_lines('2', '3', '4', '5', '')
s.reverse_scroll(2, True)
assert_lines('0', '1', '2', '3', '4')
# Height increased, width unchanged → pull down lines to fill new space at the top # Height increased, width unchanged → pull down lines to fill new space at the top
s = prepare_screen(map(str, range(6))) s = prepare_screen(map(str, range(6)))
assert_lines('2', '3', '4', '5', '') assert_lines('2', '3', '4', '5', '')