Allow getting the last non-empty command output easily via an action or remote control
Fixes #4973
This commit is contained in:
parent
4c74462763
commit
0509855930
@ -39,6 +39,9 @@ Detailed list of changes
|
|||||||
|
|
||||||
- A new command :command:`edit-in-kitty` to :ref:`edit_file`
|
- A new command :command:`edit-in-kitty` to :ref:`edit_file`
|
||||||
|
|
||||||
|
- Allow getting the last non-empty command output easily via an action or
|
||||||
|
remote control (:pull:`4973`)
|
||||||
|
|
||||||
- Fix a bug that caused :opt:`macos_colorspace` to always be ``default`` regardless of its actual value (:iss:`5129`)
|
- Fix a bug that caused :opt:`macos_colorspace` to always be ``default`` regardless of its actual value (:iss:`5129`)
|
||||||
|
|
||||||
- ssh kitten: Fix bash not being executed as a login shell since kitty 0.25.0 (:iss:`5130`)
|
- ssh kitten: Fix bash not being executed as a login shell since kitty 0.25.0 (:iss:`5130`)
|
||||||
|
|||||||
@ -73,6 +73,13 @@ left_shift_line(Line *line, index_type at, index_type num) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
line_is_empty(const Line *line) {
|
||||||
|
for (index_type i = 0; i < line->xnum; i++) {
|
||||||
|
if (line->cpu_cells[i].ch != BLANK_CHAR) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
typedef Line*(get_line_func)(void *, int);
|
typedef Line*(get_line_func)(void *, int);
|
||||||
void line_clear_text(Line *self, unsigned int at, unsigned int num, char_type ch);
|
void line_clear_text(Line *self, unsigned int at, unsigned int num, char_type ch);
|
||||||
|
|||||||
@ -30,14 +30,15 @@ class GetText(RemoteCommand):
|
|||||||
options_spec = MATCH_WINDOW_OPTION + '''\n
|
options_spec = MATCH_WINDOW_OPTION + '''\n
|
||||||
--extent
|
--extent
|
||||||
default=screen
|
default=screen
|
||||||
choices=screen, all, selection, first_cmd_output_on_screen, last_cmd_output, last_visited_cmd_output
|
choices=screen, all, selection, first_cmd_output_on_screen, last_cmd_output, last_visited_cmd_output, last_non_empty_output
|
||||||
What text to get. The default of :code:`screen` means all text currently on the screen.
|
What text to get. The default of :code:`screen` means all text currently on the screen.
|
||||||
:code:`all` means all the screen+scrollback and :code:`selection` means the
|
:code:`all` means all the screen+scrollback and :code:`selection` means the
|
||||||
currently selected text. :code:`first_cmd_output_on_screen` means the output of the first
|
currently selected text. :code:`first_cmd_output_on_screen` means the output of the first
|
||||||
command that was run in the window on screen. :code:`last_cmd_output` means
|
command that was run in the window on screen. :code:`last_cmd_output` means
|
||||||
the output of the last command that was run in the window. :code:`last_visited_cmd_output` means
|
the output of the last command that was run in the window. :code:`last_visited_cmd_output` means
|
||||||
the first command output below the last scrolled position via scroll_to_prompt. The last three
|
the first command output below the last scrolled position via scroll_to_prompt.
|
||||||
requires :ref:`shell_integration` to be enabled.
|
:code:`last_non_empty_output` is the output from the last command run in the window that had
|
||||||
|
some non empty output. The last four require :ref:`shell_integration` to be enabled.
|
||||||
|
|
||||||
|
|
||||||
--ansi
|
--ansi
|
||||||
@ -99,6 +100,12 @@ Get text from the window this command is run in, rather than the active window.
|
|||||||
as_ansi=bool(payload_get('ansi')),
|
as_ansi=bool(payload_get('ansi')),
|
||||||
add_wrap_markers=bool(payload_get('wrap_markers')),
|
add_wrap_markers=bool(payload_get('wrap_markers')),
|
||||||
)
|
)
|
||||||
|
elif payload_get('extent') == 'last_non_empty_output':
|
||||||
|
ans = window.cmd_output(
|
||||||
|
CommandOutput.last_non_empty,
|
||||||
|
as_ansi=bool(payload_get('ansi')),
|
||||||
|
add_wrap_markers=bool(payload_get('wrap_markers')),
|
||||||
|
)
|
||||||
elif payload_get('extent') == 'last_visited_cmd_output':
|
elif payload_get('extent') == 'last_visited_cmd_output':
|
||||||
ans = window.cmd_output(
|
ans = window.cmd_output(
|
||||||
CommandOutput.last_visited,
|
CommandOutput.last_visited,
|
||||||
|
|||||||
@ -2859,6 +2859,30 @@ cmd_output(Screen *self, PyObject *args) {
|
|||||||
if (self->last_visited_prompt.scrolled_by <= self->historybuf->count && self->last_visited_prompt.is_set) {
|
if (self->last_visited_prompt.scrolled_by <= self->historybuf->count && self->last_visited_prompt.is_set) {
|
||||||
found = find_cmd_output(self, &oo, self->last_visited_prompt.y, self->last_visited_prompt.scrolled_by, 0, false);
|
found = find_cmd_output(self, &oo, self->last_visited_prompt.y, self->last_visited_prompt.scrolled_by, 0, false);
|
||||||
} break;
|
} break;
|
||||||
|
case 3: { // last non-empty output
|
||||||
|
int y = self->cursor->y;
|
||||||
|
Line *line;
|
||||||
|
bool reached_upper_limit = false;
|
||||||
|
while (!found && !reached_upper_limit) {
|
||||||
|
line = checked_range_line(self, y);
|
||||||
|
if (!line || (line->attrs.prompt_kind == OUTPUT_START && !line->attrs.continued)) {
|
||||||
|
int start = line ? y : y + 1; reached_upper_limit = !line;
|
||||||
|
int y2 = start; unsigned int num_lines = 0;
|
||||||
|
bool found_content = false;
|
||||||
|
while ((line = checked_range_line(self, y2)) && line->attrs.prompt_kind != PROMPT_START) {
|
||||||
|
if (!found_content) found_content = !line_is_empty(line);
|
||||||
|
num_lines++; y2++;
|
||||||
|
}
|
||||||
|
if (found_content) {
|
||||||
|
found = true;
|
||||||
|
oo.reached_upper_limit = reached_upper_limit;
|
||||||
|
oo.start = start; oo.num_lines = num_lines;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
y--;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
default:
|
default:
|
||||||
PyErr_Format(PyExc_KeyError, "%u is not a valid type of command", which);
|
PyErr_Format(PyExc_KeyError, "%u is not a valid type of command", which);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|||||||
@ -164,7 +164,7 @@ class DynamicColor(IntEnum):
|
|||||||
|
|
||||||
|
|
||||||
class CommandOutput(IntEnum):
|
class CommandOutput(IntEnum):
|
||||||
last_run, first_on_screen, last_visited = 0, 1, 2
|
last_run, first_on_screen, last_visited, last_non_empty = 0, 1, 2, 3
|
||||||
|
|
||||||
|
|
||||||
DYNAMIC_COLOR_CODES = {
|
DYNAMIC_COLOR_CODES = {
|
||||||
@ -1460,6 +1460,14 @@ class Window:
|
|||||||
def show_last_visited_command_output(self) -> None:
|
def show_last_visited_command_output(self) -> None:
|
||||||
self.show_cmd_output(CommandOutput.last_visited, 'Last visited command output')
|
self.show_cmd_output(CommandOutput.last_visited, 'Last visited command output')
|
||||||
|
|
||||||
|
@ac('cp', '''
|
||||||
|
Show the last non-empty output from a shell command in a pager like less
|
||||||
|
|
||||||
|
Requires :ref:`shell_integration` to work
|
||||||
|
''')
|
||||||
|
def show_last_non_empty_command_output(self) -> None:
|
||||||
|
self.show_cmd_output(CommandOutput.last_non_empty, 'Last non-empty command output')
|
||||||
|
|
||||||
@ac('cp', 'Paste the specified text into the current window')
|
@ac('cp', 'Paste the specified text into the current window')
|
||||||
def paste(self, text: str) -> None:
|
def paste(self, text: str) -> None:
|
||||||
self.paste_with_actions(text)
|
self.paste_with_actions(text)
|
||||||
|
|||||||
@ -1009,9 +1009,9 @@ class TestScreen(BaseTest):
|
|||||||
self.assertTrue(s.scroll_to_prompt())
|
self.assertTrue(s.scroll_to_prompt())
|
||||||
self.ae(str(s.visual_line(0)), '$ 1')
|
self.ae(str(s.visual_line(0)), '$ 1')
|
||||||
|
|
||||||
def lco(as_ansi=False):
|
def lco(as_ansi=False, which=0):
|
||||||
a = []
|
a = []
|
||||||
if s.cmd_output(0, a.append, as_ansi):
|
if s.cmd_output(which, a.append, as_ansi):
|
||||||
pht = pagerhist(s, as_ansi=as_ansi, upto_output_start=True)
|
pht = pagerhist(s, as_ansi=as_ansi, upto_output_start=True)
|
||||||
if pht:
|
if pht:
|
||||||
a.insert(0, pht)
|
a.insert(0, pht)
|
||||||
@ -1098,3 +1098,11 @@ class TestScreen(BaseTest):
|
|||||||
draw_prompt('p1')
|
draw_prompt('p1')
|
||||||
draw_output(30)
|
draw_output(30)
|
||||||
self.ae(tuple(map(int, lco().split())), tuple(range(0, 30)))
|
self.ae(tuple(map(int, lco().split())), tuple(range(0, 30)))
|
||||||
|
|
||||||
|
# last non empty command output
|
||||||
|
draw_prompt('a'), draw_output(2, 'a')
|
||||||
|
draw_prompt('b'), mark_output()
|
||||||
|
self.ae(lco(), '')
|
||||||
|
self.ae(lco(which=3), '0a\n1a')
|
||||||
|
s.draw('running'), s.index(), s.carriage_return()
|
||||||
|
self.ae(lco(which=3), 'running\n')
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user