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]
|
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`)
|
||||||
|
|
||||||
|
|||||||
@ -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:
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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', '')
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user