Cleanup clear to prompt implementation and allow moving cleared lies into scrollback

This commit is contained in:
Kovid Goyal 2024-02-28 11:27:41 +05:30
parent b8774327b6
commit d4c302bea3
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
6 changed files with 48 additions and 18 deletions

View file

@ -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 <clear_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]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View file

@ -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()

View file

@ -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:

View file

@ -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']

View file

@ -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)

View file

@ -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