diff --git a/docs/changelog.rst b/docs/changelog.rst index 435db73a9..b1267fb62 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -53,6 +53,9 @@ To update |kitty|, :doc:`follow the instructions `. - Fix deleting windows that are not the last window via remote control leaving no window focused (:iss:`3619`) +- ``kitty @ get-text`` add an option to also get the current cursor position + and state as ANSI escape codes (:iss:`3625`) + 0.20.3 [2021-05-06] ---------------------- diff --git a/kitty/data-types.c b/kitty/data-types.c index 498411099..613e1725e 100644 --- a/kitty/data-types.c +++ b/kitty/data-types.c @@ -278,6 +278,7 @@ PyInit_fast_data_types(void) { PyModule_AddIntMacro(m, CURSOR_BLOCK); PyModule_AddIntMacro(m, CURSOR_BEAM); PyModule_AddIntMacro(m, CURSOR_UNDERLINE); + PyModule_AddIntMacro(m, NO_CURSOR_SHAPE); PyModule_AddIntMacro(m, DECAWM); PyModule_AddIntMacro(m, DECCOLM); PyModule_AddIntMacro(m, DECOM); diff --git a/kitty/fast_data_types.pyi b/kitty/fast_data_types.pyi index 4d6682b45..7e3852302 100644 --- a/kitty/fast_data_types.pyi +++ b/kitty/fast_data_types.pyi @@ -235,6 +235,7 @@ GLFW_RELEASE: int GLFW_REPEAT: int CURSOR_BEAM: int CURSOR_BLOCK: int +NO_CURSOR_SHAPE: int CURSOR_UNDERLINE: int DECAWM: int BGIMAGE_PROGRAM: int @@ -923,6 +924,8 @@ class Cursor: fg: int bold: bool italic: bool + blink: bool + shape: int class Screen: @@ -934,6 +937,7 @@ class Screen: historybuf: HistoryBuf linebuf: LineBuf in_bracketed_paste_mode: bool + cursor_visible: bool scrolled_by: int cursor: Cursor disable_ligatures: int diff --git a/kitty/rc/get_text.py b/kitty/rc/get_text.py index 9462d838a..f075cdb41 100644 --- a/kitty/rc/get_text.py +++ b/kitty/rc/get_text.py @@ -20,6 +20,7 @@ class GetText(RemoteCommand): match: The tab to focus extent: One of :code:`screen`, :code:`all`, or :code:`selection` ansi: Boolean, if True send ANSI formatting codes + cursor: Boolean, if True send cursor position/style as ANSI codes self: Boolean, if True use window command was run in ''' @@ -39,6 +40,11 @@ include the formatting escape codes for colors/bold/italic/etc. Note that when getting the current selection, the result is always plain text. +--add-cursor +type=bool-set +Add ANSI escape codes specifying the cursor position and style to the end of the text. + + --self type=bool-set If specified get text from the window this command is run in, rather than the active window. @@ -46,14 +52,18 @@ If specified get text from the window this command is run in, rather than the ac argspec = '' def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: - return {'match': opts.match, 'extent': opts.extent, 'ansi': opts.ansi, 'self': opts.self} + return {'match': opts.match, 'extent': opts.extent, 'ansi': opts.ansi, 'self': opts.self, 'cursor': opts.add_cursor} def response_from_kitty(self, boss: Boss, window: Optional[Window], payload_get: PayloadGetType) -> ResponseType: window = self.windows_for_match_payload(boss, window, payload_get)[0] if payload_get('extent') == 'selection': ans = window.text_for_selection() else: - ans = window.as_text(as_ansi=bool(payload_get('ansi')), add_history=payload_get('extent') == 'all') + ans = window.as_text( + as_ansi=bool(payload_get('ansi')), + add_history=payload_get('extent') == 'all', + add_cursor=bool(payload_get('cursor')), + ) return ans diff --git a/kitty/window.py b/kitty/window.py index af8389c0f..82019ccc3 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -22,15 +22,16 @@ from .config import build_ansi_color_table from .constants import appname, is_macos, wakeup from .fast_data_types import ( BGIMAGE_PROGRAM, BLIT_PROGRAM, CELL_BG_PROGRAM, CELL_FG_PROGRAM, - CELL_PROGRAM, CELL_SPECIAL_PROGRAM, DCS, DECORATION, DIM, GLFW_MOD_CONTROL, + CELL_PROGRAM, CELL_SPECIAL_PROGRAM, CURSOR_BEAM, CURSOR_BLOCK, + CURSOR_UNDERLINE, DCS, DECORATION, DIM, GLFW_MOD_CONTROL, GRAPHICS_ALPHA_MASK_PROGRAM, GRAPHICS_PREMULT_PROGRAM, GRAPHICS_PROGRAM, - MARK, MARK_MASK, OSC, REVERSE, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE, - STRIKETHROUGH, TINT_PROGRAM, KeyEvent, Screen, add_timer, add_window, - cell_size_for_window, click_mouse_url, compile_program, encode_key_for_tty, - get_boss, get_clipboard_string, init_cell_program, mouse_selection, - pt_to_px, set_clipboard_string, set_titlebar_color, set_window_padding, - set_window_render_data, update_window_title, update_window_visibility, - viewport_for_window + MARK, MARK_MASK, NO_CURSOR_SHAPE, OSC, REVERSE, SCROLL_FULL, SCROLL_LINE, + SCROLL_PAGE, STRIKETHROUGH, TINT_PROGRAM, KeyEvent, Screen, add_timer, + add_window, cell_size_for_window, click_mouse_url, compile_program, + encode_key_for_tty, get_boss, get_clipboard_string, init_cell_program, + mouse_selection, pt_to_px, set_clipboard_string, set_titlebar_color, + set_window_padding, set_window_render_data, update_window_title, + update_window_visibility, viewport_for_window ) from .keys import keyboard_mode_name from .notify import NotificationCommand, handle_notification_cmd @@ -152,7 +153,8 @@ def as_text( as_ansi: bool = False, add_history: bool = False, add_wrap_markers: bool = False, - alternate_screen: bool = False + alternate_screen: bool = False, + add_cursor: bool = False ) -> str: lines: List[str] = [] add_history = add_history and not (screen.is_using_alternate_linebuf() ^ alternate_screen) @@ -161,6 +163,19 @@ def as_text( else: f = screen.as_text_non_visual if add_history else screen.as_text f(lines.append, as_ansi, add_wrap_markers) + ctext = '' + if add_cursor: + ctext += '\x1b[?25' + ('h' if screen.cursor_visible else 'l') + ctext += f'\x1b[{screen.cursor.y + 1};{screen.cursor.x + 1}H' + shape = screen.cursor.shape + if shape == NO_CURSOR_SHAPE: + ctext += '\x1b[?12' + ('h' if screen.cursor.blink else 'l') + else: + code = {CURSOR_BLOCK: 1, CURSOR_UNDERLINE: 3, CURSOR_BEAM: 5}[shape] + if not screen.cursor.blink: + code += 1 + ctext += f'\x1b[{code} q' + if add_history: h: List[str] = [] pht = screen.historybuf.pagerhist_as_text() @@ -175,8 +190,14 @@ def as_text( h[-1] += '\n' if as_ansi: h[-1] += '\x1b[m' - return ''.join(chain(h, lines)) - return ''.join(lines) + ans = ''.join(chain(h, lines)) + if ctext: + ans += ctext + return ans + ans = ''.join(lines) + if ctext: + ans += ctext + return ans class LoadShaderPrograms: @@ -848,9 +869,10 @@ class Window: as_ansi: bool = False, add_history: bool = False, add_wrap_markers: bool = False, - alternate_screen: bool = False + alternate_screen: bool = False, + add_cursor: bool = False ) -> str: - return as_text(self.screen, as_ansi, add_history, add_wrap_markers, alternate_screen) + return as_text(self.screen, as_ansi, add_history, add_wrap_markers, alternate_screen, add_cursor) @property def cwd_of_child(self) -> Optional[str]: