diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 39f70997e..2c2be9a9f 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -1272,6 +1272,8 @@ def set_window_padding(os_window_id: int, tab_id: int, window_id: int, left: int def click_mouse_url(os_window_id: int, tab_id: int, window_id: int) -> bool: pass +def click_mouse_cmd_output(os_window_id: int, tab_id: int, window_id: int) -> bool: + pass def move_cursor_to_mouse_if_in_prompt(os_window_id: int, tab_id: int, window_id: int) -> bool: pass diff --git a/kitty/mouse.c b/kitty/mouse.c index 5fab083e0..4867b84a2 100644 --- a/kitty/mouse.c +++ b/kitty/mouse.c @@ -424,6 +424,12 @@ mouse_open_url(Window *w) { return screen_open_url(screen); } +bool +mouse_select_cmd_output(Window *w) { + Screen *screen = w->render_data.screen; + return screen_select_cmd_output(screen, w->mouse_pos.cell_y); +} + bool move_cursor_to_mouse_if_at_shell_prompt(Window *w) { Screen *screen = w->render_data.screen; diff --git a/kitty/screen.c b/kitty/screen.c index 4b1f7e56b..a3101c7d4 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -2764,6 +2764,31 @@ cmd_output(Screen *self, PyObject *args) { Py_RETURN_NONE; } +bool +screen_select_cmd_output(Screen *self, index_type y) { + if (y >= self->lines) return false; + OutputOffset oo = {.screen=self}; + if (!find_cmd_output(self, &oo, y, self->scrolled_by, 0, true)) return false; + + screen_start_selection(self, 0, y, true, false, EXTEND_LINE); + Selection *s = self->selections.items; +#define S(which, offset_y, scrolled_by) \ + if (offset_y < 0) { \ + s->scrolled_by = -offset_y; s->which.y = 0; \ + } else { \ + s->scrolled_by = 0; s->which.y = offset_y; \ + } + S(start, oo.start, start_scrolled_by); + S(end, oo.start + (int)oo.num_lines - 1, end_scrolled_by); +#undef S + s->start.x = 0; s->start.in_left_half_of_cell = true; + s->end.x = self->columns; s->end.in_left_half_of_cell = false; + self->selections.in_progress = false; + + call_boss(set_primary_selection, NULL); + return true; +} + static PyObject* screen_truncate_point_for_length(PyObject UNUSED *self, PyObject *args) { PyObject *str; unsigned int num_cells, start_pos = 0; diff --git a/kitty/screen.h b/kitty/screen.h index 011458777..25f090726 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -246,6 +246,7 @@ void set_active_hyperlink(Screen*, char*, char*); hyperlink_id_type screen_mark_hyperlink(Screen*, index_type, index_type); void screen_handle_graphics_command(Screen *self, const GraphicsCommand *cmd, const uint8_t *payload); bool screen_open_url(Screen*); +bool screen_select_cmd_output(Screen*, index_type); void screen_dirty_sprite_positions(Screen *self); void screen_rescale_images(Screen *self); void screen_report_size(Screen *, unsigned int which); diff --git a/kitty/state.c b/kitty/state.c index 747ea9106..1c0b5eb79 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -1090,6 +1090,15 @@ click_mouse_url(id_type os_window_id, id_type tab_id, id_type window_id) { return clicked; } +static bool +click_mouse_cmd_output(id_type os_window_id, id_type tab_id, id_type window_id) { + bool selected = false; + WITH_WINDOW(os_window_id, tab_id, window_id); + selected = mouse_select_cmd_output(window); + END_WITH_WINDOW; + return selected; +} + static bool move_cursor_to_mouse_if_in_prompt(id_type os_window_id, id_type tab_id, id_type window_id) { bool moved = false; @@ -1117,6 +1126,12 @@ PYWRAP1(click_mouse_url) { Py_RETURN_FALSE; } +PYWRAP1(click_mouse_cmd_output) { + id_type a, b, c; PA("KKK", &a, &b, &c); + if (click_mouse_cmd_output(a, b, c)) { Py_RETURN_TRUE; } + Py_RETURN_FALSE; +} + PYWRAP1(move_cursor_to_mouse_if_in_prompt) { id_type a, b, c; PA("KKK", &a, &b, &c); if (move_cursor_to_mouse_if_in_prompt(a, b, c)) Py_RETURN_TRUE; @@ -1152,6 +1167,7 @@ static PyMethodDef module_methods[] = { MW(set_options, METH_VARARGS), MW(get_options, METH_NOARGS), MW(click_mouse_url, METH_VARARGS), + MW(click_mouse_cmd_output, METH_VARARGS), MW(move_cursor_to_mouse_if_in_prompt, METH_VARARGS), MW(redirect_mouse_handling, METH_O), MW(mouse_selection, METH_VARARGS), diff --git a/kitty/state.h b/kitty/state.h index be41649b0..9eed55746 100644 --- a/kitty/state.h +++ b/kitty/state.h @@ -311,6 +311,7 @@ void update_os_window_title(OSWindow *os_window); void fake_scroll(Window *w, int amount, bool upwards); Window* window_for_window_id(id_type kitty_window_id); bool mouse_open_url(Window *w); +bool mouse_select_cmd_output(Window *w); bool move_cursor_to_mouse_if_at_shell_prompt(Window *w); void mouse_selection(Window *w, int code, int button); const char* format_mods(unsigned mods); diff --git a/kitty/window.py b/kitty/window.py index 463157c32..757919c14 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -26,12 +26,12 @@ from .fast_data_types import ( GRAPHICS_ALPHA_MASK_PROGRAM, GRAPHICS_PREMULT_PROGRAM, GRAPHICS_PROGRAM, MARK, MARK_MASK, NO_CURSOR_SHAPE, OSC, REVERSE, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE, STRIKETHROUGH, TINT_PROGRAM, Color, KeyEvent, Screen, - add_timer, add_window, cell_size_for_window, click_mouse_url, - compile_program, encode_key_for_tty, get_boss, get_clipboard_string, - get_options, init_cell_program, mark_os_window_dirty, mouse_selection, - move_cursor_to_mouse_if_in_prompt, pt_to_px, set_clipboard_string, - set_titlebar_color, set_window_padding, set_window_render_data, - update_ime_position_for_window, update_window_title, + add_timer, add_window, cell_size_for_window, click_mouse_cmd_output, + click_mouse_url, compile_program, encode_key_for_tty, get_boss, + get_clipboard_string, get_options, init_cell_program, mark_os_window_dirty, + mouse_selection, move_cursor_to_mouse_if_in_prompt, pt_to_px, + set_clipboard_string, set_titlebar_color, set_window_padding, + set_window_render_data, update_ime_position_for_window, update_window_title, update_window_visibility, viewport_for_window ) from .keys import keyboard_mode_name, mod_mask @@ -1008,6 +1008,14 @@ class Window: txt = get_boss().current_primary_selection_or_clipboard() if txt: self.paste(txt) + + @ac('mouse', ''' + Select clicked command output + + Requires :ref:`shell_integration` to work + ''') + def mouse_select_cmd_output(self) -> None: + click_mouse_cmd_output(self.os_window_id, self.tab_id, self.id) # }}} def text_for_selection(self) -> str: