A new protocol extension to unscroll the screen
See https://gitlab.freedesktop.org/terminal-wg/specifications/-/issues/30
This commit is contained in:
parent
77b8e204ad
commit
b32c346eed
@ -7,6 +7,10 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
|
||||
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
|
||||
(:iss:`3506`)
|
||||
|
||||
|
||||
@ -157,6 +157,37 @@ protocol extension, it can be disabled by specifying ``no-append`` to the
|
||||
: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:
|
||||
|
||||
|
||||
|
||||
@ -932,7 +932,13 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
||||
NO_MODIFIERS(end_modifier, ' ', "Select presentation directions escape code not implemented");
|
||||
CALL_CSI_HANDLER1(screen_scroll, 1);
|
||||
case SD:
|
||||
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:
|
||||
if (end_modifier == '$') {
|
||||
// DECRQM
|
||||
|
||||
@ -1136,17 +1136,29 @@ screen_reverse_index(Screen *self) {
|
||||
} else screen_cursor_up(self, 1, false, -1);
|
||||
}
|
||||
|
||||
void
|
||||
screen_reverse_scroll(Screen *self, unsigned int count) {
|
||||
static void
|
||||
_reverse_scroll(Screen *self, unsigned int count, bool fill_from_scrollback) {
|
||||
// Scroll the screen down by count lines, not moving the cursor
|
||||
count = MIN(self->lines, count);
|
||||
unsigned int top = self->margin_top, bottom = self->margin_bottom;
|
||||
while (count > 0) {
|
||||
count--;
|
||||
fill_from_scrollback = fill_from_scrollback && self->linebuf == self->main_linebuf;
|
||||
while (count-- > 0) {
|
||||
if (fill_from_scrollback) historybuf_pop_line(self->historybuf, self->alt_linebuf->line);
|
||||
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
|
||||
screen_carriage_return(Screen *self) {
|
||||
@ -2888,6 +2900,15 @@ hyperlink_at(Screen *self, PyObject *args) {
|
||||
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 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(garbage_collect_hyperlink_pool, METH_NOARGS)
|
||||
MND(hyperlink_for_id, METH_O)
|
||||
MND(reverse_scroll, METH_VARARGS)
|
||||
METHOD(current_char_width, METH_NOARGS)
|
||||
MND(insert_lines, METH_VARARGS)
|
||||
MND(delete_lines, METH_VARARGS)
|
||||
|
||||
@ -157,6 +157,7 @@ void screen_reverse_index(Screen *self);
|
||||
void screen_index(Screen *self);
|
||||
void screen_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_set_tab_stop(Screen *self);
|
||||
void screen_tab(Screen *self);
|
||||
|
||||
@ -184,6 +184,9 @@ class TestParser(BaseTest):
|
||||
pb('\033[3 @', ('Shift left 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[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):
|
||||
s = self.create_screen(8)
|
||||
|
||||
@ -315,6 +315,12 @@ class TestScreen(BaseTest):
|
||||
def assert_lines(*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
|
||||
s = prepare_screen(map(str, range(6)))
|
||||
assert_lines('2', '3', '4', '5', '')
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user