diff --git a/kitty/boss.py b/kitty/boss.py index 9c40fb949..75b1f9d88 100755 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -62,7 +62,7 @@ from .utils import ( remove_socket_file, safe_print, set_primary_selection, single_instance, startup_notification_handler ) -from .window import MatchPatternType, Window +from .window import MatchPatternType, Window, CommandOutput class OSWindowDict(TypedDict): @@ -111,17 +111,17 @@ def data_for_at(w: Optional[Window], arg: str, add_wrap_markers: bool = False) - if arg == '@ansi_alternate_scrollback': return as_text(as_ansi=True, alternate_screen=True, add_history=True) if arg == '@first_cmd_output_on_screen': - return w.first_cmd_output_on_screen(add_wrap_markers=add_wrap_markers) + return w.cmd_output(CommandOutput.first_on_screen, add_wrap_markers=add_wrap_markers) if arg == '@ansi_first_cmd_output_on_screen': - return w.first_cmd_output_on_screen(as_ansi=True, add_wrap_markers=add_wrap_markers) + return w.cmd_output(CommandOutput.first_on_screen, as_ansi=True, add_wrap_markers=add_wrap_markers) if arg == '@last_cmd_output': - return w.last_cmd_output(add_wrap_markers=add_wrap_markers) + return w.cmd_output(CommandOutput.last_run, add_wrap_markers=add_wrap_markers) if arg == '@ansi_last_cmd_output': - return w.last_cmd_output(as_ansi=True, add_wrap_markers=add_wrap_markers) + return w.cmd_output(CommandOutput.last_run, as_ansi=True, add_wrap_markers=add_wrap_markers) if arg == '@last_visited_cmd_output': - return w.last_visited_cmd_output(add_wrap_markers=add_wrap_markers) + return w.cmd_output(CommandOutput.last_visited, add_wrap_markers=add_wrap_markers) if arg == '@ansi_last_visited_cmd_output': - return w.last_visited_cmd_output(as_ansi=True, add_wrap_markers=add_wrap_markers) + return w.cmd_output(CommandOutput.last_visited, as_ansi=True, add_wrap_markers=add_wrap_markers) return None @@ -1320,8 +1320,10 @@ class Boss: sel = self.data_for_at(which='@selection', window=w) data = sel.encode('utf-8') if sel else None elif q[0] in ('output', 'first_output', 'last_visited_output'): - func = {'output': w.last_cmd_output, 'first_output': w.first_cmd_output_on_screen, 'last_visited_output': w.last_visited_cmd_output}[q[0]] - data = func(as_ansi='ansi' in q, add_wrap_markers='screen' in q).encode('utf-8') + which = { + 'output': CommandOutput.last_run, 'first_output': CommandOutput.first_on_screen, + 'last_visited_output': CommandOutput.last_visited}[q[0]] + data = w.cmd_output(which, as_ansi='ansi' in q, add_wrap_markers='screen' in q).encode('utf-8') else: raise ValueError(f'Unknown type_of_input: {type_of_input}') else: diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index c7a83acc7..39f70997e 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -1120,9 +1120,9 @@ class Screen: pass as_text_non_visual = as_text as_text_alternate = as_text - first_cmd_output_on_screen = as_text - last_cmd_output = as_text - last_visited_cmd_output = as_text + + def cmd_output(self, which: int, callback: Callable[[str], None], as_ansi: bool, insert_wrap_markers: bool) -> None: + pass def scroll_until_cursor(self) -> None: pass diff --git a/kitty/options/definition.py b/kitty/options/definition.py index ec83b9153..66664ce33 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -3033,9 +3033,8 @@ For example, the following opens the output in less in an overlay window:: map f1 launch --stdin-source=@last_cmd_output --stdin-add-formatting --type=overlay less +G -R -To get the first command output on the screen, use :code:`@first_cmd_output_on_screen`. -To get the first command output below the last scrolled position via scroll_to_prompt, use -:code:`@last_visited_cmd_output`. +To get the output of the first command on the screen, use :code:`@first_cmd_output_on_screen`. +To get the output of the last jumped to command, use :code:`@last_visited_cmd_output`. ''') egr() # }}} diff --git a/kitty/rc/get_text.py b/kitty/rc/get_text.py index f0c85b43b..0a2a321ef 100644 --- a/kitty/rc/get_text.py +++ b/kitty/rc/get_text.py @@ -74,21 +74,25 @@ If specified get text from the window this command is run in, rather than the ac } def response_from_kitty(self, boss: Boss, window: Optional[Window], payload_get: PayloadGetType) -> ResponseType: + from kitty.window import CommandOutput window = self.windows_for_match_payload(boss, window, payload_get)[0] if payload_get('extent') == 'selection': ans = window.text_for_selection() elif payload_get('extent') == 'first_cmd_output_on_screen': - ans = window.first_cmd_output_on_screen( + ans = window.cmd_output( + CommandOutput.first_on_screen, as_ansi=bool(payload_get('ansi')), add_wrap_markers=bool(payload_get('wrap_markers')), ) elif payload_get('extent') == 'last_cmd_output': - ans = window.last_cmd_output( + ans = window.cmd_output( + CommandOutput.last_run, as_ansi=bool(payload_get('ansi')), add_wrap_markers=bool(payload_get('wrap_markers')), ) elif payload_get('extent') == 'last_visited_cmd_output': - ans = window.last_visited_cmd_output( + ans = window.cmd_output( + CommandOutput.last_visited, as_ansi=bool(payload_get('ansi')), add_wrap_markers=bool(payload_get('wrap_markers')), ) diff --git a/kitty/screen.c b/kitty/screen.c index 113d2d0b0..fa8f0b406 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -2736,39 +2736,35 @@ find_cmd_output(Screen *self, OutputOffset *oo, index_type start_y, unsigned int } static PyObject* -first_cmd_output_on_screen(Screen *self, PyObject *args) { - if (self->linebuf != self->main_linebuf) return PyUnicode_FromString(""); - +cmd_output(Screen *self, PyObject *args) { + unsigned int which = 0; + DECREF_AFTER_FUNCTION PyObject *which_args = PyTuple_GetSlice(args, 0, 1); + DECREF_AFTER_FUNCTION PyObject *as_text_args = PyTuple_GetSlice(args, 1, PyTuple_GET_SIZE(args)); + if (!which_args || !as_text_args) return NULL; + if (!PyArg_ParseTuple(which_args, "I", &which)) return NULL; + if (self->linebuf != self->main_linebuf) Py_RETURN_NONE; OutputOffset oo = {.screen=self}; - if (find_cmd_output(self, &oo, 0, self->scrolled_by, 1, true)) { - return as_text_generic(args, &oo, get_line_from_offset, oo.num_lines, &self->as_ansi_buf); + bool found = false; + + switch (which) { + case 0: // last run cmd + found = find_cmd_output(self, &oo, self->cursor->y, self->scrolled_by, -1, false); + break; + case 1: // first on screen + found = find_cmd_output(self, &oo, 0, self->scrolled_by, 1, true); + break; + case 2: // last visited cmd + if (self->last_visited_prompt.scrolled_by <= self->historybuf->count && self->last_visited_prompt.is_set) { + found = find_cmd_output(self, &oo, 0, self->last_visited_prompt.scrolled_by, 0, false); + } break; + default: + PyErr_Format(PyExc_KeyError, "%u is not a valid type of command", which); + return NULL; } - return PyUnicode_FromString(""); + if (found) return as_text_generic(as_text_args, &oo, get_line_from_offset, oo.num_lines, &self->as_ansi_buf); + Py_RETURN_NONE; } -static PyObject* -last_cmd_output(Screen *self, PyObject *args) { - if (self->linebuf != self->main_linebuf) return PyUnicode_FromString(""); - - OutputOffset oo = {.screen=self}; - if (find_cmd_output(self, &oo, self->cursor->y, self->scrolled_by, -1, false)) { - return as_text_generic(args, &oo, get_line_from_offset, oo.num_lines, &self->as_ansi_buf); - } - return PyUnicode_FromString(""); -} - -static PyObject* -last_visited_cmd_output(Screen *self, PyObject *args) { - if (self->linebuf != self->main_linebuf || self->last_visited_prompt.scrolled_by > self->historybuf->count || !self->last_visited_prompt.is_set) return PyUnicode_FromString(""); - - OutputOffset oo = {.screen=self}; - if (find_cmd_output(self, &oo, 0, self->last_visited_prompt.scrolled_by, 0, false)) { - return as_text_generic(args, &oo, get_line_from_offset, oo.num_lines, &self->as_ansi_buf); - } - return PyUnicode_FromString(""); -} - - static PyObject* screen_truncate_point_for_length(PyObject UNUSED *self, PyObject *args) { PyObject *str; unsigned int num_cells, start_pos = 0; @@ -3758,9 +3754,7 @@ static PyMethodDef methods[] = { MND(as_text, METH_VARARGS) MND(as_text_non_visual, METH_VARARGS) MND(as_text_alternate, METH_VARARGS) - MND(first_cmd_output_on_screen, METH_VARARGS) - MND(last_cmd_output, METH_VARARGS) - MND(last_visited_cmd_output, METH_VARARGS) + MND(cmd_output, METH_VARARGS) MND(tab, METH_NOARGS) MND(backspace, METH_NOARGS) MND(linefeed, METH_NOARGS) diff --git a/kitty/window.py b/kitty/window.py index 611ab4696..463157c32 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -87,6 +87,10 @@ class DynamicColor(IntEnum): default_fg, default_bg, cursor_color, highlight_fg, highlight_bg = range(1, 6) +class CommandOutput(IntEnum): + last_run, first_on_screen, last_visited = 0, 1, 2 + + DYNAMIC_COLOR_CODES = { 10: DynamicColor.default_fg, 11: DynamicColor.default_bg, @@ -1041,19 +1045,9 @@ class Window: ) -> str: return as_text(self.screen, as_ansi, add_history, add_wrap_markers, alternate_screen, add_cursor) - def first_cmd_output_on_screen(self, as_ansi: bool = False, add_wrap_markers: bool = False) -> str: + def cmd_output(self, which: CommandOutput = CommandOutput.last_run, as_ansi: bool = False, add_wrap_markers: bool = False) -> str: lines: List[str] = [] - self.screen.first_cmd_output_on_screen(lines.append, as_ansi, add_wrap_markers) - return ''.join(lines) - - def last_cmd_output(self, as_ansi: bool = False, add_wrap_markers: bool = False) -> str: - lines: List[str] = [] - self.screen.last_cmd_output(lines.append, as_ansi, add_wrap_markers) - return ''.join(lines) - - def last_visited_cmd_output(self, as_ansi: bool = False, add_wrap_markers: bool = False) -> str: - lines: List[str] = [] - self.screen.last_visited_cmd_output(lines.append, as_ansi, add_wrap_markers) + self.screen.cmd_output(which, lines.append, as_ansi, add_wrap_markers) return ''.join(lines) @property @@ -1091,7 +1085,7 @@ class Window: Requires :ref:`shell_integration` to work ''') def show_first_command_output_on_screen(self) -> None: - text = self.first_cmd_output_on_screen(as_ansi=True, add_wrap_markers=True) + text = self.cmd_output(CommandOutput.first_on_screen, as_ansi=True, add_wrap_markers=True) text = text.replace('\r\n', '\n').replace('\r', '\n') get_boss().display_scrollback(self, text, title='First command output on screen', report_cursor=False) @@ -1101,7 +1095,7 @@ class Window: Requires :ref:`shell_integration` to work ''') def show_last_command_output(self) -> None: - text = self.last_cmd_output(as_ansi=True, add_wrap_markers=True) + text = self.cmd_output(CommandOutput.last_run, as_ansi=True, add_wrap_markers=True) text = text.replace('\r\n', '\n').replace('\r', '\n') get_boss().display_scrollback(self, text, title='Last command output', report_cursor=False) @@ -1111,7 +1105,7 @@ class Window: Requires :ref:`shell_integration` to work ''') def show_last_visited_command_output(self) -> None: - text = self.last_visited_cmd_output(as_ansi=True, add_wrap_markers=True) + text = self.cmd_output(CommandOutput.last_visited, as_ansi=True, add_wrap_markers=True) text = text.replace('\r\n', '\n').replace('\r', '\n') get_boss().display_scrollback(self, text, title='Last visited command output', report_cursor=False) diff --git a/kitty_tests/screen.py b/kitty_tests/screen.py index 9e0835198..3f874dc1b 100644 --- a/kitty_tests/screen.py +++ b/kitty_tests/screen.py @@ -959,12 +959,12 @@ class TestScreen(BaseTest): def fco(): a = [] - s.first_cmd_output_on_screen(a.append) + s.cmd_output(1, a.append) return ''.join(a) def lco(): a = [] - s.last_cmd_output(a.append) + s.cmd_output(0, a.append) return ''.join(a) s = self.create_screen()