kitty @ get-text add an option to also get the current cursor position and state as ANSI escape codes

Fixes #3625
This commit is contained in:
Kovid Goyal 2021-05-15 09:27:28 +05:30
parent 3bf9130b0a
commit fcd206891f
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 55 additions and 15 deletions

View File

@ -53,6 +53,9 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
- Fix deleting windows that are not the last window via remote control leaving - Fix deleting windows that are not the last window via remote control leaving
no window focused (:iss:`3619`) 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] 0.20.3 [2021-05-06]
---------------------- ----------------------

View File

@ -278,6 +278,7 @@ PyInit_fast_data_types(void) {
PyModule_AddIntMacro(m, CURSOR_BLOCK); PyModule_AddIntMacro(m, CURSOR_BLOCK);
PyModule_AddIntMacro(m, CURSOR_BEAM); PyModule_AddIntMacro(m, CURSOR_BEAM);
PyModule_AddIntMacro(m, CURSOR_UNDERLINE); PyModule_AddIntMacro(m, CURSOR_UNDERLINE);
PyModule_AddIntMacro(m, NO_CURSOR_SHAPE);
PyModule_AddIntMacro(m, DECAWM); PyModule_AddIntMacro(m, DECAWM);
PyModule_AddIntMacro(m, DECCOLM); PyModule_AddIntMacro(m, DECCOLM);
PyModule_AddIntMacro(m, DECOM); PyModule_AddIntMacro(m, DECOM);

View File

@ -235,6 +235,7 @@ GLFW_RELEASE: int
GLFW_REPEAT: int GLFW_REPEAT: int
CURSOR_BEAM: int CURSOR_BEAM: int
CURSOR_BLOCK: int CURSOR_BLOCK: int
NO_CURSOR_SHAPE: int
CURSOR_UNDERLINE: int CURSOR_UNDERLINE: int
DECAWM: int DECAWM: int
BGIMAGE_PROGRAM: int BGIMAGE_PROGRAM: int
@ -923,6 +924,8 @@ class Cursor:
fg: int fg: int
bold: bool bold: bool
italic: bool italic: bool
blink: bool
shape: int
class Screen: class Screen:
@ -934,6 +937,7 @@ class Screen:
historybuf: HistoryBuf historybuf: HistoryBuf
linebuf: LineBuf linebuf: LineBuf
in_bracketed_paste_mode: bool in_bracketed_paste_mode: bool
cursor_visible: bool
scrolled_by: int scrolled_by: int
cursor: Cursor cursor: Cursor
disable_ligatures: int disable_ligatures: int

View File

@ -20,6 +20,7 @@ class GetText(RemoteCommand):
match: The tab to focus match: The tab to focus
extent: One of :code:`screen`, :code:`all`, or :code:`selection` extent: One of :code:`screen`, :code:`all`, or :code:`selection`
ansi: Boolean, if True send ANSI formatting codes 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 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. 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 --self
type=bool-set type=bool-set
If specified get text from the window this command is run in, rather than the active window. 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 = '' argspec = ''
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType: 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: 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] window = self.windows_for_match_payload(boss, window, payload_get)[0]
if payload_get('extent') == 'selection': if payload_get('extent') == 'selection':
ans = window.text_for_selection() ans = window.text_for_selection()
else: 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 return ans

View File

@ -22,15 +22,16 @@ from .config import build_ansi_color_table
from .constants import appname, is_macos, wakeup from .constants import appname, is_macos, wakeup
from .fast_data_types import ( from .fast_data_types import (
BGIMAGE_PROGRAM, BLIT_PROGRAM, CELL_BG_PROGRAM, CELL_FG_PROGRAM, 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, GRAPHICS_ALPHA_MASK_PROGRAM, GRAPHICS_PREMULT_PROGRAM, GRAPHICS_PROGRAM,
MARK, MARK_MASK, OSC, REVERSE, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE, MARK, MARK_MASK, NO_CURSOR_SHAPE, OSC, REVERSE, SCROLL_FULL, SCROLL_LINE,
STRIKETHROUGH, TINT_PROGRAM, KeyEvent, Screen, add_timer, add_window, SCROLL_PAGE, STRIKETHROUGH, TINT_PROGRAM, KeyEvent, Screen, add_timer,
cell_size_for_window, click_mouse_url, compile_program, encode_key_for_tty, add_window, cell_size_for_window, click_mouse_url, compile_program,
get_boss, get_clipboard_string, init_cell_program, mouse_selection, encode_key_for_tty, get_boss, get_clipboard_string, init_cell_program,
pt_to_px, set_clipboard_string, set_titlebar_color, set_window_padding, mouse_selection, pt_to_px, set_clipboard_string, set_titlebar_color,
set_window_render_data, update_window_title, update_window_visibility, set_window_padding, set_window_render_data, update_window_title,
viewport_for_window update_window_visibility, viewport_for_window
) )
from .keys import keyboard_mode_name from .keys import keyboard_mode_name
from .notify import NotificationCommand, handle_notification_cmd from .notify import NotificationCommand, handle_notification_cmd
@ -152,7 +153,8 @@ def as_text(
as_ansi: bool = False, as_ansi: bool = False,
add_history: bool = False, add_history: bool = False,
add_wrap_markers: bool = False, add_wrap_markers: bool = False,
alternate_screen: bool = False alternate_screen: bool = False,
add_cursor: bool = False
) -> str: ) -> str:
lines: List[str] = [] lines: List[str] = []
add_history = add_history and not (screen.is_using_alternate_linebuf() ^ alternate_screen) add_history = add_history and not (screen.is_using_alternate_linebuf() ^ alternate_screen)
@ -161,6 +163,19 @@ def as_text(
else: else:
f = screen.as_text_non_visual if add_history else screen.as_text f = screen.as_text_non_visual if add_history else screen.as_text
f(lines.append, as_ansi, add_wrap_markers) 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: if add_history:
h: List[str] = [] h: List[str] = []
pht = screen.historybuf.pagerhist_as_text() pht = screen.historybuf.pagerhist_as_text()
@ -175,8 +190,14 @@ def as_text(
h[-1] += '\n' h[-1] += '\n'
if as_ansi: if as_ansi:
h[-1] += '\x1b[m' h[-1] += '\x1b[m'
return ''.join(chain(h, lines)) ans = ''.join(chain(h, lines))
return ''.join(lines) if ctext:
ans += ctext
return ans
ans = ''.join(lines)
if ctext:
ans += ctext
return ans
class LoadShaderPrograms: class LoadShaderPrograms:
@ -848,9 +869,10 @@ class Window:
as_ansi: bool = False, as_ansi: bool = False,
add_history: bool = False, add_history: bool = False,
add_wrap_markers: bool = False, add_wrap_markers: bool = False,
alternate_screen: bool = False alternate_screen: bool = False,
add_cursor: bool = False
) -> str: ) -> 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 @property
def cwd_of_child(self) -> Optional[str]: def cwd_of_child(self) -> Optional[str]: