custom.rst 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. Custom kittens
  2. =================
  3. You can easily create your own kittens to extend kitty. They are just terminal
  4. programs written in Python. When launching a kitten, kitty will open an overlay
  5. window over the current window and optionally pass the contents of the current
  6. window/scrollback to the kitten over its :file:`STDIN`. The kitten can then
  7. perform whatever actions it likes, just as a normal terminal program. After
  8. execution of the kitten is complete, it has access to the running kitty instance
  9. so it can perform arbitrary actions such as closing windows, pasting text, etc.
  10. Let's see a simple example of creating a kitten. It will ask the user for some
  11. input and paste it into the terminal window.
  12. Create a file in the kitty config directory, :file:`~/.config/kitty/mykitten.py`
  13. (you might need to adjust the path to wherever the :ref:`kitty config directory
  14. <confloc>` is on your machine).
  15. .. code-block:: python
  16. from kitty.boss import Boss
  17. def main(args: list[str]) -> str:
  18. # this is the main entry point of the kitten, it will be executed in
  19. # the overlay window when the kitten is launched
  20. answer = input('Enter some text: ')
  21. # whatever this function returns will be available in the
  22. # handle_result() function
  23. return answer
  24. def handle_result(args: list[str], answer: str, target_window_id: int, boss: Boss) -> None:
  25. # get the kitty window into which to paste answer
  26. w = boss.window_id_map.get(target_window_id)
  27. if w is not None:
  28. w.paste_text(answer)
  29. Now in :file:`kitty.conf` add the lines::
  30. map ctrl+k kitten mykitten.py
  31. Start kitty and press :kbd:`Ctrl+K` and you should see the kitten running.
  32. The best way to develop your own kittens is to modify one of the built-in
  33. kittens. Look in the `kittens sub-directory
  34. <https://github.com/kovidgoyal/kitty/tree/master/kittens>`__ of the kitty source
  35. code for those. Or see below for a list of :ref:`third-party kittens
  36. <external_kittens>`, that other kitty users have created.
  37. kitty API to use with kittens
  38. -------------------------------
  39. Kittens have full access to internal kitty APIs. However these are neither
  40. entirely stable nor documented. You can instead use the kitty
  41. :doc:`Remote control API </remote-control>`. Simply call
  42. :code:`boss.call_remote_control()`, with the same arguments you
  43. would pass to ``kitten @``. For example:
  44. .. code-block:: python
  45. def handle_result(args: list[str], answer: str, target_window_id: int, boss: Boss) -> None:
  46. # get the kitty window to which to send text
  47. w = boss.window_id_map.get(target_window_id)
  48. if w is not None:
  49. boss.call_remote_control(w, ('send-text', f'--match=id:{w.id}', 'hello world'))
  50. .. note::
  51. Inside handle_result() the active window is still the window in which the
  52. kitten was run, therefore when using call_remote_control() be sure to pass
  53. the appropriate option to select the target window, usually ``--match`` as
  54. shown above or ``--self``.
  55. Run, ``kitten @ --help`` in a kitty terminal, to see all the remote control
  56. commands available to you.
  57. Passing arguments to kittens
  58. ------------------------------
  59. You can pass arguments to kittens by defining them in the map directive in
  60. :file:`kitty.conf`. For example::
  61. map ctrl+k kitten mykitten.py arg1 arg2
  62. These will be available as the ``args`` parameter in the ``main()`` and
  63. ``handle_result()`` functions. Note also that the current working directory
  64. of the kitten is set to the working directory of whatever program is running in
  65. the active kitty window. The special argument ``@selection`` is replaced by the
  66. currently selected text in the active kitty window.
  67. Passing the contents of the screen to the kitten
  68. ---------------------------------------------------
  69. If you would like your kitten to have access to the contents of the screen
  70. and/or the scrollback buffer, you just need to add an annotation to the
  71. ``handle_result()`` function, telling kitty what kind of input your kitten would
  72. like. For example:
  73. .. code-block:: py
  74. from kitty.boss import Boss
  75. # in main, STDIN is for the kitten process and will contain
  76. # the contents of the screen
  77. def main(args: list[str]) -> str:
  78. return sys.stdin.read()
  79. # in handle_result, STDIN is for the kitty process itself, rather
  80. # than the kitten process and should not be read from.
  81. from kittens.tui.handler import result_handler
  82. @result_handler(type_of_input='text')
  83. def handle_result(args: list[str], stdin_data: str, target_window_id: int, boss: Boss) -> None:
  84. pass
  85. This will send the plain text of the active window to the kitten's
  86. :file:`STDIN`. There are many other types of input you can ask for, described in
  87. the table below:
  88. .. table:: Types of input to kittens
  89. :align: left
  90. =========================== =======================================================================================================
  91. Keyword Type of :file:`STDIN` input
  92. =========================== =======================================================================================================
  93. ``text`` Plain text of active window
  94. ``ansi`` Formatted text of active window
  95. ``screen`` Plain text of active window with line wrap markers
  96. ``screen-ansi`` Formatted text of active window with line wrap markers
  97. ``history`` Plain text of active window and its scrollback
  98. ``ansi-history`` Formatted text of active window and its scrollback
  99. ``screen-history`` Plain text of active window and its scrollback with line wrap markers
  100. ``screen-ansi-history`` Formatted text of active window and its scrollback with line wrap markers
  101. ``output`` Plain text of the output from the last run command
  102. ``output-screen`` Plain text of the output from the last run command with wrap markers
  103. ``output-ansi`` Formatted text of the output from the last run command
  104. ``output-screen-ansi`` Formatted text of the output from the last run command with wrap markers
  105. ``selection`` The text currently selected with the mouse
  106. =========================== =======================================================================================================
  107. In addition to ``output``, that gets the output of the last run command,
  108. ``last_visited_output`` gives the output of the command last jumped to
  109. and ``first_output`` gives the output of the first command currently on screen.
  110. These can also be combined with ``screen`` and ``ansi`` for formatting.
  111. .. note::
  112. For the types based on the output of a command, :ref:`shell_integration` is
  113. required.
  114. Using kittens to script kitty, without any terminal UI
  115. -----------------------------------------------------------
  116. If you would like your kitten to script kitty, without bothering to write a
  117. terminal program, you can tell the kittens system to run the ``handle_result()``
  118. function without first running the ``main()`` function.
  119. For example, here is a kitten that "zooms in/zooms out" the current terminal
  120. window by switching to the stack layout or back to the previous layout. This is
  121. equivalent to the builtin :ac:`toggle_layout` action.
  122. Create a Python file in the :ref:`kitty config directory <confloc>`,
  123. :file:`~/.config/kitty/zoom_toggle.py`
  124. .. code-block:: py
  125. from kitty.boss import Boss
  126. def main(args: list[str]) -> str:
  127. pass
  128. from kittens.tui.handler import result_handler
  129. @result_handler(no_ui=True)
  130. def handle_result(args: list[str], answer: str, target_window_id: int, boss: Boss) -> None:
  131. tab = boss.active_tab
  132. if tab is not None:
  133. if tab.current_layout.name == 'stack':
  134. tab.last_used_layout()
  135. else:
  136. tab.goto_layout('stack')
  137. Now in :file:`kitty.conf` add::
  138. map f11 kitten zoom_toggle.py
  139. Pressing :kbd:`F11` will now act as a zoom toggle function. You can get even
  140. more fancy, switching the kitty OS window to fullscreen as well as changing the
  141. layout, by simply adding the line::
  142. boss.toggle_fullscreen()
  143. to the ``handle_result()`` function, above.
  144. .. _send_mouse_event:
  145. Sending mouse events
  146. --------------------
  147. If the program running in a window is receiving mouse events, you can simulate
  148. those using::
  149. from kitty.fast_data_types import send_mouse_event
  150. send_mouse_event(screen, x, y, button, action, mods)
  151. ``screen`` is the ``screen`` attribute of the window you want to send the event
  152. to. ``x`` and ``y`` are the 0-indexed coordinates. ``button`` is a number using
  153. the same numbering as X11 (left: ``1``, middle: ``2``, right: ``3``, scroll up:
  154. ``4``, scroll down: ``5``, scroll left: ``6``, scroll right: ``7``, back:
  155. ``8``, forward: ``9``). ``action`` is one of ``PRESS``, ``RELEASE``, ``DRAG``
  156. or ``MOVE``. ``mods`` is a bitmask of ``GLFW_MOD_{mod}`` where ``{mod}`` is one
  157. of ``SHIFT``, ``CONTROL`` or ``ALT``. All the mentioned constants are imported
  158. from ``kitty.fast_data_types``.
  159. For example, to send a left click at position x: 2, y: 3 to the active window::
  160. from kitty.fast_data_types import send_mouse_event, PRESS
  161. send_mouse_event(boss.active_window.screen, 2, 3, 1, PRESS, 0)
  162. The function will only send the event if the program is receiving events of
  163. that type, and will return ``True`` if it sent the event, and ``False`` if not.
  164. .. _kitten_main_rc:
  165. Using remote control inside the main() kitten function
  166. ------------------------------------------------------------
  167. You can use kitty's remote control features inside the main() function of a
  168. kitten, even without enabling remote control. This is useful if you want to
  169. probe kitty for more information before presenting some UI to the user or if
  170. you want the user to be able to control kitty from within your kitten's UI
  171. rather than after it has finished running. To enable it, simply tell kitty your kitten
  172. requires remote control, as shown in the example below::
  173. import json
  174. import sys
  175. from pprint import pprint
  176. from kittens.tui.handler import kitten_ui
  177. @kitten_ui(allow_remote_control=True)
  178. def main(args: list[str]) -> str:
  179. # get the result of running kitten @ ls
  180. cp = main.remote_control(['ls'], capture_output=True)
  181. if cp.returncode != 0:
  182. sys.stderr.buffer.write(cp.stderr)
  183. raise SystemExit(cp.returncode)
  184. output = json.loads(cp.stdout)
  185. pprint(output)
  186. # open a new tab with a title specified by the user
  187. title = input('Enter the name of tab: ')
  188. window_id = main.remote_control(['launch', '--type=tab', '--tab-title', title], check=True, capture_output=True).stdout.decode()
  189. return window_id
  190. :code:`allow_remote_control=True` tells kitty to run this kitten with remote
  191. control enabled, regardless of whether it is enabled globally or not.
  192. To run a remote control command use the :code:`main.remote_control()` function
  193. which is a thin wrapper around Python's :code:`subprocess.run` function. Note
  194. that by default, for security, child processes launched by your kitten cannot use remote
  195. control, thus it is necessary to use :code:`main.remote_control()`. If you wish
  196. to enable child processes to use remote control, call
  197. :code:`main.allow_indiscriminate_remote_control()`.
  198. Remote control access can be further secured by using
  199. :code:`kitten_ui(allow_remote_control=True, remote_control_password='ls set-colors')`.
  200. This will use a secure generated password to restrict remote control.
  201. You can specify a space separated list of remote control commands to allow, see
  202. :opt:`remote_control_password` for details. The password value is accessible
  203. as :code:`main.password` and is used by :code:`main.remote_control()`
  204. automatically.
  205. Debugging kittens
  206. --------------------
  207. The part of the kitten that runs in ``main()`` is just a normal program and the
  208. output of print statements will be visible in the kitten window. Or alternately,
  209. you can use::
  210. from kittens.tui.loop import debug
  211. debug('whatever')
  212. The ``debug()`` function is just like ``print()`` except that the output will
  213. appear in the ``STDOUT`` of the kitty process inside which the kitten is
  214. running.
  215. The ``handle_result()`` part of the kitten runs inside the kitty process.
  216. The output of print statements will go to the ``STDOUT`` of the kitty process.
  217. So if you run kitty from another kitty instance, the output will be visible
  218. in the first kitty instance.
  219. Adding options to kittens
  220. ----------------------------
  221. If you would like to use kitty's config framework to make your kittens
  222. configurable, you will need some boilerplate. Put the following files in the
  223. directory of your kitten.
  224. :file:`kitten_options_definition.py`
  225. .. code-block:: python
  226. from kitty.conf.types import Action, Definition
  227. definition = Definition(
  228. '!kitten_options_utils',
  229. Action(
  230. 'map', 'parse_map',
  231. {'key_definitions': 'kitty.conf.utils.KittensKeyMap'},
  232. ['kitty.types.ParsedShortcut', 'kitty.conf.utils.KeyAction']
  233. ),
  234. )
  235. agr = definition.add_group
  236. egr = definition.end_group
  237. opt = definition.add_option
  238. map = definition.add_map
  239. # main options {{{
  240. agr('main', 'Main')
  241. opt('some_option', '33',
  242. option_type='some_option_parser',
  243. long_text='''
  244. Help text for this option
  245. '''
  246. )
  247. egr() # }}}
  248. # shortcuts {{{
  249. agr('shortcuts', 'Keyboard shortcuts')
  250. map('Quit', 'quit q quit')
  251. egr() # }}}
  252. :file:`kitten_options_utils.py`
  253. .. code-block:: python
  254. from kitty.conf.utils import KittensKeyDefinition, key_func, parse_kittens_key
  255. func_with_args, args_funcs = key_func()
  256. FuncArgsType = Tuple[str, Sequence[Any]]
  257. def some_option_parser(val: str) -> int:
  258. return int(val) + 3000
  259. def parse_map(val: str) -> Iterable[KittensKeyDefinition]:
  260. x = parse_kittens_key(val, args_funcs)
  261. if x is not None:
  262. yield x
  263. Then run::
  264. kitty +runpy 'from kitty.conf.generate import main; main()' /path/to/kitten_options_definition.py
  265. You can parse and read the options in your kitten using the following code:
  266. .. code-block:: python
  267. from .kitten_options_types import Options, defaults
  268. from kitty.conf.utils import load_config as _load_config, parse_config_base
  269. from typing import Optional, Iterable, Dict, Any
  270. def load_config(*paths: str, overrides: Optional[Iterable[str]] = None) -> Options:
  271. from .kitten_options_parse import (
  272. create_result_dict, merge_result_dicts, parse_conf_item
  273. )
  274. def parse_config(lines: Iterable[str]) -> Dict[str, Any]:
  275. ans: Dict[str, Any] = create_result_dict()
  276. parse_config_base(
  277. lines,
  278. parse_conf_item,
  279. ans,
  280. )
  281. return ans
  282. overrides = tuple(overrides) if overrides is not None else ()
  283. opts_dict, found_paths = _load_config(defaults, parse_config, merge_result_dicts, *paths, overrides=overrides)
  284. opts = Options(opts_dict)
  285. opts.config_paths = found_paths
  286. opts.all_config_paths = paths
  287. opts.config_overrides = overrides
  288. return opts
  289. See `the code <https://github.com/kovidgoyal/kitty/tree/master/kittens/diff>`__
  290. for the builtin :doc:`diff kitten </kittens/diff>` for examples of creating more
  291. options and keyboard shortcuts.
  292. Developing builtin kittens for inclusion with kitty
  293. ----------------------------------------------------------
  294. There is documentation for :doc:`developing-builtin-kittens` which are written in the Go
  295. language.
  296. .. _external_kittens:
  297. Kittens created by kitty users
  298. ---------------------------------------------
  299. `vim-kitty-navigator <https://github.com/knubie/vim-kitty-navigator>`_
  300. Allows you to navigate seamlessly between vim and kitty splits using a
  301. consistent set of hotkeys.
  302. `smart-scroll <https://github.com/yurikhan/kitty-smart-scroll>`_
  303. Makes the kitty scroll bindings work in full screen applications
  304. `gattino <https://github.com/salvozappa/gattino>`__
  305. Integrate kitty with an LLM to convert plain language prompts into shell commands.
  306. :iss:`insert password <1222>`
  307. Insert a password from a CLI password manager, taking care to only do it at
  308. a password prompt.
  309. `weechat-hints <https://github.com/GermainZ/kitty-weechat-hints>`_
  310. URL hints kitten for WeeChat that works without having to use WeeChat's
  311. raw-mode.