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
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`) - 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] 0.32.2 [2024-02-12]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -1170,8 +1170,11 @@ class Boss:
map f1 clear_terminal scrollback active map f1 clear_terminal scrollback active
# Scroll the contents of the screen into the scrollback # Scroll the contents of the screen into the scrollback
map f1 clear_terminal scroll active 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 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: def clear_terminal(self, action: str, only_active: bool) -> None:
if only_active: if only_active:
@@ -1196,6 +1199,11 @@ class Boss:
elif action == 'to_cursor': elif action == 'to_cursor':
for w in windows: for w in windows:
w.scroll_prompt_to_top(clear_scrollback=True) 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 def increase_font_size(self) -> None: # legacy
cfs = global_font_size() 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: def cmd_output(self, which: int, callback: Callable[[str], None], as_ansi: bool, insert_wrap_markers: bool) -> bool:
pass pass
def scroll_until_cursor_prompt(self) -> None: def scroll_until_cursor_prompt(self, add_to_scrollback: bool = True) -> None:
pass pass
def reset(self) -> None: 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') 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') @func_with_args('send_text')
def send_text_parse(func: str, rest: str) -> FuncArgsType: def send_text_parse(func: str, rest: str) -> FuncArgsType:
args = rest.split(maxsplit=1) args = rest.split(maxsplit=1)
@@ -211,7 +216,7 @@ def clear_terminal(func: str, rest: str) -> FuncArgsType:
args = ['reset', True] args = ['reset', True]
else: else:
action = vals[0].lower() 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') log_error(f'{action} is unknown for clear_terminal, using reset')
action = 'reset' action = 'reset'
args = [action, vals[1].lower() == 'active'] 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); \ linebuf_index(self->linebuf, top, bottom); \
INDEX_GRAPHICS(-1) \ 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 */ \ /* Only add to history when no top margin has been set */ \
linebuf_init_line(self->linebuf, bottom); \ linebuf_init_line(self->linebuf, bottom); \
historybuf_add_line(self->historybuf, self->linebuf->line, &self->as_ansi_buf); \ 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 // Move cursor down one line, scrolling screen if needed
unsigned int top = self->margin_top, bottom = self->margin_bottom; unsigned int top = self->margin_top, bottom = self->margin_bottom;
if (self->cursor->y == 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); } 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 void
screen_scroll(Screen *self, unsigned int count) { screen_scroll(Screen *self, unsigned int count) {
// Scroll the screen up by count lines, not moving the cursor // Scroll the screen up by count lines, not moving the cursor
unsigned int top = self->margin_top, bottom = self->margin_bottom; 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) { while (count > 0) {
count--; count--;
INDEX_UP; INDEX_UP(add_to_history);
} }
} }
@@ -1868,9 +1880,10 @@ screen_move_into_scrollback(Screen *self) {
} }
if (num_of_lines_to_move) { if (num_of_lines_to_move) {
unsigned int top, bottom; 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--) { for (; num_of_lines_to_move; num_of_lines_to_move--) {
top = 0, bottom = num_of_lines_to_move - 1; 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 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); bool in_margins = cursor_within_margins(self);
int q = screen_cursor_at_a_shell_prompt(self); int q = screen_cursor_at_a_shell_prompt(self);
unsigned int y = q > -1 ? (unsigned int)q : self->cursor->y; unsigned int y = q > -1 ? (unsigned int)q : self->cursor->y;
unsigned int num_lines_to_scroll = MIN(self->margin_bottom, 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; 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; 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; self->cursor->y = final_y;
screen_ensure_bounds(self, false, in_margins); 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) WRAP1E(cursor_back, 1, -1)
WRAP1B(erase_in_line, 0) WRAP1B(erase_in_line, 0)
WRAP1B(erase_in_display, 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) WRAP0(clear_scrollback)
#define MODE_GETSET(name, uname) \ #define MODE_GETSET(name, uname) \
@@ -4699,7 +4714,7 @@ static PyMethodDef methods[] = {
MND(erase_in_line, METH_VARARGS) MND(erase_in_line, METH_VARARGS)
MND(erase_in_display, METH_VARARGS) MND(erase_in_display, METH_VARARGS)
MND(clear_scrollback, METH_NOARGS) 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(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)

View File

@@ -1821,14 +1821,13 @@ class Window:
return None return None
return True 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]: def scroll_prompt_to_top(self, clear_scrollback: bool = False) -> Optional[bool]:
if self.screen.is_main_linebuf(): if self.screen.is_main_linebuf():
self.screen.scroll_until_cursor_prompt() self.screen.scroll_until_cursor_prompt(not clear_scrollback)
if clear_scrollback: if self.screen.scrolled_by > 0:
self.screen.clear_scrollback() self.scroll_end()
elif self.screen.scrolled_by > 0:
self.screen.scroll(SCROLL_FULL, False)
return None return None
return True return True