From d4c302bea38ae38eb7df3e1ef81051d7632f92f0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 28 Feb 2024 11:27:41 +0530 Subject: [PATCH] Cleanup clear to prompt implementation and allow moving cleared lies into scrollback --- docs/changelog.rst | 3 +++ kitty/boss.py | 10 +++++++++- kitty/fast_data_types.pyi | 2 +- kitty/options/utils.py | 7 ++++++- kitty/screen.c | 33 ++++++++++++++++++++++++--------- kitty/window.py | 11 +++++------ 6 files changed, 48 insertions(+), 18 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 235c5d54d..4bd882d70 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -72,6 +72,9 @@ Detailed list of changes - icat kitten: Add a command line argument to override terminal window size detection (:iss:`7165`) +- When :ac:`clearing terminal ` add a new type ``to_cursor_scroll`` which can be + used to clear to prompt while moving cleared lines into the scrollback + 0.32.2 [2024-02-12] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/kitty/boss.py b/kitty/boss.py index 5371357cc..5d485371d 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -1170,8 +1170,11 @@ def on_window_resize(self, os_window_id: int, w: int, h: int, dpi_changed: bool) map f1 clear_terminal scrollback active # Scroll the contents of the screen into the scrollback map f1 clear_terminal scroll active - # Clear everything up to the line with the cursor + # Clear everything up to the line with the cursor or if the cursor is at a prompt, the first line of the prompt. + # Useful for clearing the screen up to the shell prompt and moving the shell prompt to the top of the screen. map f1 clear_terminal to_cursor active + # Clear everything up to the line with the cursor or prompt, saving the cleared lines in the scrollback + map f1 clear_terminal to_cursor_scroll active ''') def clear_terminal(self, action: str, only_active: bool) -> None: if only_active: @@ -1196,6 +1199,11 @@ def clear_terminal(self, action: str, only_active: bool) -> None: elif action == 'to_cursor': for w in windows: w.scroll_prompt_to_top(clear_scrollback=True) + elif action == 'to_cursor_scroll': + for w in windows: + w.scroll_prompt_to_top(clear_scrollback=False) + else: + self.show_error(_('Unknown clear type'), _('The clear type: {} is unknown').format(action)) def increase_font_size(self) -> None: # legacy cfs = global_font_size() diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 446bab83e..5ea4c4f66 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -1171,7 +1171,7 @@ class Screen: def cmd_output(self, which: int, callback: Callable[[str], None], as_ansi: bool, insert_wrap_markers: bool) -> bool: pass - def scroll_until_cursor_prompt(self) -> None: + def scroll_until_cursor_prompt(self, add_to_scrollback: bool = True) -> None: pass def reset(self) -> None: diff --git a/kitty/options/utils.py b/kitty/options/utils.py index 2e76ec45f..d00ccd6d8 100644 --- a/kitty/options/utils.py +++ b/kitty/options/utils.py @@ -93,6 +93,11 @@ def parse_send_text_bytes(text: str) -> bytes: return defines.expand_ansi_c_escapes(text).encode('utf-8') +@func_with_args('scroll_prompt_to_top') +def scroll_prompt_to_top(func: str, rest: str) -> FuncArgsType: + return func, [to_bool(rest) if rest else False] + + @func_with_args('send_text') def send_text_parse(func: str, rest: str) -> FuncArgsType: args = rest.split(maxsplit=1) @@ -211,7 +216,7 @@ def clear_terminal(func: str, rest: str) -> FuncArgsType: args = ['reset', True] else: action = vals[0].lower() - if action not in ('reset', 'scroll', 'scrollback', 'clear', 'to_cursor',): + if action not in ('reset', 'scroll', 'scrollback', 'clear', 'to_cursor', 'to_cursor_scroll'): log_error(f'{action} is unknown for clear_terminal, using reset') action = 'reset' args = [action, vals[1].lower() == 'active'] diff --git a/kitty/screen.c b/kitty/screen.c index c26eddf76..dbcb6447b 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -1503,10 +1503,10 @@ screen_cursor_to_column(Screen *self, unsigned int column) { } } -#define INDEX_UP \ +#define INDEX_UP(add_to_history) \ linebuf_index(self->linebuf, top, bottom); \ INDEX_GRAPHICS(-1) \ - if (self->linebuf == self->main_linebuf && self->margin_top == 0) { \ + if (add_to_history) { \ /* Only add to history when no top margin has been set */ \ linebuf_init_line(self->linebuf, bottom); \ historybuf_add_line(self->historybuf, self->linebuf->line, &self->as_ansi_buf); \ @@ -1525,17 +1525,29 @@ screen_index(Screen *self) { // Move cursor down one line, scrolling screen if needed unsigned int top = self->margin_top, bottom = self->margin_bottom; if (self->cursor->y == bottom) { - INDEX_UP; + const bool add_to_history = self->linebuf == self->main_linebuf && self->margin_top == 0; + INDEX_UP(add_to_history); } else screen_cursor_down(self, 1); } +static void +screen_index_without_adding_to_history(Screen *self) { + // Move cursor down one line, scrolling screen if needed + unsigned int top = self->margin_top, bottom = self->margin_bottom; + if (self->cursor->y == bottom) { + INDEX_UP(false); + } else screen_cursor_down(self, 1); +} + + void screen_scroll(Screen *self, unsigned int count) { // Scroll the screen up by count lines, not moving the cursor unsigned int top = self->margin_top, bottom = self->margin_bottom; + const bool add_to_history = self->linebuf == self->main_linebuf && self->margin_top == 0; while (count > 0) { count--; - INDEX_UP; + INDEX_UP(add_to_history); } } @@ -1868,9 +1880,10 @@ screen_move_into_scrollback(Screen *self) { } if (num_of_lines_to_move) { unsigned int top, bottom; + const bool add_to_history = self->linebuf == self->main_linebuf && self->margin_top == 0; for (; num_of_lines_to_move; num_of_lines_to_move--) { top = 0, bottom = num_of_lines_to_move - 1; - INDEX_UP + INDEX_UP(add_to_history); } } } @@ -1945,14 +1958,15 @@ screen_insert_lines(Screen *self, unsigned int count) { } static void -screen_scroll_until_cursor_prompt(Screen *self) { +screen_scroll_until_cursor_prompt(Screen *self, bool add_to_scrollback) { bool in_margins = cursor_within_margins(self); int q = screen_cursor_at_a_shell_prompt(self); unsigned int y = q > -1 ? (unsigned int)q : self->cursor->y; unsigned int num_lines_to_scroll = MIN(self->margin_bottom, y); unsigned int final_y = num_lines_to_scroll <= self->cursor->y ? self->cursor->y - num_lines_to_scroll : 0; self->cursor->y = self->margin_bottom; - while (num_lines_to_scroll--) screen_index(self); + if (add_to_scrollback) while (num_lines_to_scroll--) screen_index(self); + else while (num_lines_to_scroll--) screen_index_without_adding_to_history(self); self->cursor->y = final_y; screen_ensure_bounds(self, false, in_margins); } @@ -3751,7 +3765,8 @@ is_using_alternate_linebuf(Screen *self, PyObject *a UNUSED) { WRAP1E(cursor_back, 1, -1) WRAP1B(erase_in_line, 0) WRAP1B(erase_in_display, 0) -WRAP0(scroll_until_cursor_prompt) +static PyObject* scroll_until_cursor_prompt(Screen *self, PyObject *args) { int b=false; if(!PyArg_ParseTuple(args, "|p", &b)) return NULL; screen_scroll_until_cursor_prompt(self, b); Py_RETURN_NONE; } + WRAP0(clear_scrollback) #define MODE_GETSET(name, uname) \ @@ -4699,7 +4714,7 @@ static PyMethodDef methods[] = { MND(erase_in_line, METH_VARARGS) MND(erase_in_display, METH_VARARGS) MND(clear_scrollback, METH_NOARGS) - MND(scroll_until_cursor_prompt, METH_NOARGS) + MND(scroll_until_cursor_prompt, METH_VARARGS) MND(hyperlinks_as_list, METH_NOARGS) MND(garbage_collect_hyperlink_pool, METH_NOARGS) MND(hyperlink_for_id, METH_O) diff --git a/kitty/window.py b/kitty/window.py index 484ec393e..991b56fc2 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -1821,14 +1821,13 @@ def scroll_to_prompt(self, num_of_prompts: int = -1) -> Optional[bool]: return None return True - @ac('sc', 'Scroll prompt to the top of the screen, filling screen with empty lines, when in main screen') + @ac('sc', 'Scroll prompt to the top of the screen, filling screen with empty lines, when in main screen.' + ' To avoid putting the lines above the prompt into the scrollback use scroll_prompt_to_top y') def scroll_prompt_to_top(self, clear_scrollback: bool = False) -> Optional[bool]: if self.screen.is_main_linebuf(): - self.screen.scroll_until_cursor_prompt() - if clear_scrollback: - self.screen.clear_scrollback() - elif self.screen.scrolled_by > 0: - self.screen.scroll(SCROLL_FULL, False) + self.screen.scroll_until_cursor_prompt(not clear_scrollback) + if self.screen.scrolled_by > 0: + self.scroll_end() return None return True