mirror of
https://github.com/kovidgoyal/kitty.git
synced 2026-08-04 14:46:03 +00:00
Add support for DECSTR soft screen reset escape code
Some checks are pending
CI / Linux (python=3.14 cc=clang sanitize=1) (push) Waiting to run
CI / Linux (python=3.12 cc=gcc sanitize=0) (push) Waiting to run
CI / Linux (python=3.13 cc=gcc sanitize=1) (push) Waiting to run
CI / Linux package (push) Waiting to run
CI / Bundle test (macos-latest) (push) Waiting to run
CI / Bundle test (ubuntu-latest) (push) Waiting to run
CI / macOS Brew (push) Waiting to run
CI / Test ./dev.sh and benchmark (push) Waiting to run
CodeQL / CodeQL-Build (actions, ubuntu-latest) (push) Waiting to run
CodeQL / CodeQL-Build (c, macos-latest) (push) Waiting to run
CodeQL / CodeQL-Build (c, ubuntu-latest) (push) Waiting to run
CodeQL / CodeQL-Build (go, ubuntu-latest) (push) Waiting to run
CodeQL / CodeQL-Build (python, ubuntu-latest) (push) Waiting to run
Depscan / Scan dependencies for vulnerabilities (push) Waiting to run
Some checks are pending
CI / Linux (python=3.14 cc=clang sanitize=1) (push) Waiting to run
CI / Linux (python=3.12 cc=gcc sanitize=0) (push) Waiting to run
CI / Linux (python=3.13 cc=gcc sanitize=1) (push) Waiting to run
CI / Linux package (push) Waiting to run
CI / Bundle test (macos-latest) (push) Waiting to run
CI / Bundle test (ubuntu-latest) (push) Waiting to run
CI / macOS Brew (push) Waiting to run
CI / Test ./dev.sh and benchmark (push) Waiting to run
CodeQL / CodeQL-Build (actions, ubuntu-latest) (push) Waiting to run
CodeQL / CodeQL-Build (c, macos-latest) (push) Waiting to run
CodeQL / CodeQL-Build (c, ubuntu-latest) (push) Waiting to run
CodeQL / CodeQL-Build (go, ubuntu-latest) (push) Waiting to run
CodeQL / CodeQL-Build (python, ubuntu-latest) (push) Waiting to run
Depscan / Scan dependencies for vulnerabilities (push) Waiting to run
This escape code is largely undefined. There is no specification for how it affects alternate screen mode, overriden colors, kitty keyboard state, paused rendering, etc. Do what I feel is sensible in these cases. Fixes #10263
This commit is contained in:
parent
41c82aa418
commit
0816716c72
7 changed files with 90 additions and 16 deletions
|
|
@ -234,6 +234,8 @@ Detailed list of changes
|
|||
|
||||
- macOS: Disable macOS one time code autofill popups (:pull:`10250`)
|
||||
|
||||
- Add support for DECSTR soft screen reset escape code (:iss:`10263`)
|
||||
|
||||
0.47.4 [2026-06-15]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
|||
|
|
@ -173,13 +173,13 @@ new_screen_object(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
|||
|
||||
static Line* range_line_(Screen *self, int y);
|
||||
|
||||
void
|
||||
screen_reset(Screen *self) {
|
||||
static void
|
||||
do_screen_reset(Screen *self, bool is_hard_reset) {
|
||||
screen_pause_rendering(self, false, 0);
|
||||
self->dnd_chunking.active = false;
|
||||
self->extra_cursors.count = 0; zero_at_ptr(&self->extra_cursors.color); self->extra_cursors.dirty = true;
|
||||
self->main_pointer_shape_stack.count = 0; self->alternate_pointer_shape_stack.count = 0;
|
||||
if (self->linebuf == self->alt_linebuf) screen_toggle_screen_buffer(self, true, true);
|
||||
if (is_hard_reset && self->linebuf == self->alt_linebuf) screen_toggle_screen_buffer(self, true, true);
|
||||
if (screen_is_overlay_active(self)) {
|
||||
deactivate_overlay_line(self);
|
||||
// Cancel IME composition
|
||||
|
|
@ -197,11 +197,13 @@ screen_reset(Screen *self) {
|
|||
self->last_graphic_char = 0;
|
||||
self->main_savepoint.is_valid = false;
|
||||
self->alt_savepoint.is_valid = false;
|
||||
linebuf_clear(self->linebuf, BLANK_CHAR);
|
||||
historybuf_clear(self->historybuf);
|
||||
clear_hyperlink_pool(self->hyperlink_pool);
|
||||
grman_clear(self->main_grman, false, self->cell_size); // dont delete images in scrollback
|
||||
grman_clear(self->alt_grman, true, self->cell_size);
|
||||
if (is_hard_reset) {
|
||||
linebuf_clear(self->linebuf, BLANK_CHAR);
|
||||
historybuf_clear(self->historybuf);
|
||||
clear_hyperlink_pool(self->hyperlink_pool);
|
||||
grman_clear(self->main_grman, false, self->cell_size); // dont delete images in scrollback
|
||||
grman_clear(self->alt_grman, true, self->cell_size);
|
||||
}
|
||||
self->modes = empty_modes;
|
||||
self->saved_modes = empty_modes;
|
||||
self->active_hyperlink_id = 0;
|
||||
|
|
@ -218,7 +220,17 @@ screen_reset(Screen *self) {
|
|||
screen_cursor_position(self, 1, 1);
|
||||
set_dynamic_color(self, 111, NULL); // does default_bg_changed processing
|
||||
colorprofile_reset(self->color_profile);
|
||||
CALLBACK("on_reset", NULL)
|
||||
CALLBACK("on_reset", "O", is_hard_reset ? Py_True : Py_False);
|
||||
}
|
||||
|
||||
void
|
||||
screen_reset(Screen *self) { do_screen_reset(self, true); }
|
||||
|
||||
void
|
||||
screen_soft_reset(Screen *self) {
|
||||
index_type x = self->cursor->x, y = self->cursor->y;
|
||||
do_screen_reset(self, false);
|
||||
self->cursor->x = x; self->cursor->y = y;
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
|||
|
|
@ -242,6 +242,7 @@ 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_soft_reset(Screen *self);
|
||||
void screen_set_tab_stop(Screen *self);
|
||||
void screen_tab(Screen *self);
|
||||
void screen_backtab(Screen *self, unsigned int);
|
||||
|
|
|
|||
|
|
@ -1380,11 +1380,19 @@ dispatch_csi(PS *self) {
|
|||
}
|
||||
break;
|
||||
case DECSTR:
|
||||
if (end_modifier == '$') {
|
||||
// DECRQM
|
||||
CALL_CSI_HANDLER1P(report_mode_status, 0, '?');
|
||||
} else {
|
||||
REPORT_ERROR("Unknown DECSTR CSI sequence with start and end modifiers: '%c' '%c'", start_modifier, end_modifier);
|
||||
switch (end_modifier) {
|
||||
case '$': // DECRQM
|
||||
CALL_CSI_HANDLER1P(report_mode_status, 0, '?'); break;
|
||||
case '!': // Soft reset
|
||||
if (num_params) {
|
||||
REPORT_ERROR("DECSTR escape code with parameters is invalid");
|
||||
} else {
|
||||
REPORT_COMMAND(screen_soft_reset);
|
||||
screen_soft_reset(self->screen);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
REPORT_ERROR("Unknown CSI p sequence with start and end modifiers: '%c' '%c'", start_modifier, end_modifier); break;
|
||||
}
|
||||
break;
|
||||
case 'm':
|
||||
|
|
|
|||
|
|
@ -1499,7 +1499,7 @@ class Window:
|
|||
b |= b << 8
|
||||
self.screen.send_escape_code_to_child(ESC_OSC, f'{code};rgb:{r:04x}/{g:04x}/{b:04x}')
|
||||
|
||||
def on_reset(self) -> None:
|
||||
def on_reset(self, is_hard_reset: bool = True) -> None:
|
||||
from .progress import ProgressState
|
||||
if self.progress.state is not ProgressState.unset:
|
||||
self.progress.update(0) # unset
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ class Callbacks:
|
|||
def notify_child_of_resize(self):
|
||||
self.num_of_resize_events += 1
|
||||
|
||||
def on_reset(self) -> None:
|
||||
def on_reset(self, is_hard_reset: bool = True) -> None:
|
||||
if self.pty is not None:
|
||||
self.pty.reset_termios_state()
|
||||
|
||||
|
|
|
|||
|
|
@ -1864,6 +1864,57 @@ class TestScreen(BaseTest):
|
|||
sc(2, 1, 2, 3, slot=slot)
|
||||
sc(5, 13, slot=slot)
|
||||
|
||||
def test_soft_reset(self):
|
||||
SOFT_RESET = b'\x1b[!p' # DECSTR sequence
|
||||
|
||||
# Screen content is preserved (unlike hard reset)
|
||||
s = self.create_screen()
|
||||
s.draw('hello')
|
||||
parse_bytes(s, SOFT_RESET)
|
||||
self.ae(str(s.line(0)), 'hello')
|
||||
|
||||
# Hard reset clears screen content; soft reset does not
|
||||
s = self.create_screen()
|
||||
s.draw('hello')
|
||||
s.reset()
|
||||
self.ae(str(s.line(0)), '')
|
||||
|
||||
# Cursor SGR attributes are cleared
|
||||
s = self.create_screen()
|
||||
s.select_graphic_rendition(1) # bold
|
||||
s.select_graphic_rendition(31) # red fg
|
||||
self.assertTrue(s.cursor.bold)
|
||||
self.assertNotEqual(s.cursor.fg, 0)
|
||||
parse_bytes(s, SOFT_RESET)
|
||||
self.assertFalse(s.cursor.bold)
|
||||
self.ae(s.cursor.fg, 0)
|
||||
|
||||
# Cursor position is preserved
|
||||
s = self.create_screen()
|
||||
s.cursor_position(3, 4)
|
||||
self.ae((s.cursor.y, s.cursor.x), (2, 3))
|
||||
parse_bytes(s, SOFT_RESET)
|
||||
self.ae((s.cursor.y, s.cursor.x), (2, 3))
|
||||
|
||||
# Insert mode (IRM) is cleared
|
||||
s = self.create_screen()
|
||||
s.draw('abcde')
|
||||
s.set_mode(IRM)
|
||||
parse_bytes(s, SOFT_RESET)
|
||||
# without IRM, drawing overwrites
|
||||
s.cursor.x = 0
|
||||
s.draw('X')
|
||||
self.ae(str(s.line(0)), 'Xbcde')
|
||||
|
||||
# Alternate screen is NOT exited on soft reset
|
||||
s = self.create_screen()
|
||||
parse_bytes(s, b'\x1b[?1049h') # enter alternate screen
|
||||
self.assertFalse(s.is_main_linebuf())
|
||||
parse_bytes(s, SOFT_RESET)
|
||||
self.assertFalse(s.is_main_linebuf())
|
||||
s.reset()
|
||||
self.assertTrue(s.is_main_linebuf())
|
||||
|
||||
|
||||
def detect_url(self, scale=1):
|
||||
s = self.create_screen(cols=30 * scale)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue