File: custom.rst

package info (click to toggle)
kitty 0.42.1-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 28,564 kB
  • sloc: ansic: 82,787; python: 55,191; objc: 5,122; sh: 1,295; xml: 364; makefile: 143; javascript: 78
file content (431 lines) | stat: -rw-r--r-- 16,646 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
Custom kittens
=================

You can easily create your own kittens to extend kitty. They are just terminal
programs written in Python. When launching a kitten, kitty will open an overlay
window over the current window and optionally pass the contents of the current
window/scrollback to the kitten over its :file:`STDIN`. The kitten can then
perform whatever actions it likes, just as a normal terminal program. After
execution of the kitten is complete, it has access to the running kitty instance
so it can perform arbitrary actions such as closing windows, pasting text, etc.

Let's see a simple example of creating a kitten. It will ask the user for some
input and paste it into the terminal window.

Create a file in the kitty config directory, :file:`~/.config/kitty/mykitten.py`
(you might need to adjust the path to wherever the :ref:`kitty config directory
<confloc>` is on your machine).


.. code-block:: python

    from kitty.boss import Boss

    def main(args: list[str]) -> str:
        # this is the main entry point of the kitten, it will be executed in
        # the overlay window when the kitten is launched
        answer = input('Enter some text: ')
        # whatever this function returns will be available in the
        # handle_result() function
        return answer

    def handle_result(args: list[str], answer: str, target_window_id: int, boss: Boss) -> None:
        # get the kitty window into which to paste answer
        w = boss.window_id_map.get(target_window_id)
        if w is not None:
            w.paste_text(answer)


Now in :file:`kitty.conf` add the lines::

    map ctrl+k kitten mykitten.py


Start kitty and press :kbd:`Ctrl+K` and you should see the kitten running.
The best way to develop your own kittens is to modify one of the built-in
kittens. Look in the `kittens sub-directory
<https://github.com/kovidgoyal/kitty/tree/master/kittens>`__ of the kitty source
code for those. Or see below for a list of :ref:`third-party kittens
<external_kittens>`, that other kitty users have created.

kitty API to use with kittens
-------------------------------

Kittens have full access to internal kitty APIs. However these are neither
entirely stable nor documented. You can instead use the kitty
:doc:`Remote control API </remote-control>`. Simply call
:code:`boss.call_remote_control()`, with the same arguments you
would pass to ``kitten @``. For example:

.. code-block:: python

    def handle_result(args: list[str], answer: str, target_window_id: int, boss: Boss) -> None:
        # get the kitty window to which to send text
        w = boss.window_id_map.get(target_window_id)
        if w is not None:
            boss.call_remote_control(w, ('send-text', f'--match=id:{w.id}', 'hello world'))

.. note::
   Inside handle_result() the active window is still the window in which the
   kitten was run, therefore when using call_remote_control() be sure to pass
   the appropriate option to select the target window, usually ``--match`` as
   shown above or ``--self``.


Run, ``kitten @ --help`` in a kitty terminal, to see all the remote control
commands available to you.

Passing arguments to kittens
------------------------------

You can pass arguments to kittens by defining them in the map directive in
:file:`kitty.conf`. For example::

    map ctrl+k kitten mykitten.py arg1 arg2

These will be available as the ``args`` parameter in the ``main()`` and
``handle_result()`` functions. Note also that the current working directory
of the kitten is set to the working directory of whatever program is running in
the active kitty window. The special argument ``@selection`` is replaced by the
currently selected text in the active kitty window.


Passing the contents of the screen to the kitten
---------------------------------------------------

If you would like your kitten to have access to the contents of the screen
and/or the scrollback buffer, you just need to add an annotation to the
``handle_result()`` function, telling kitty what kind of input your kitten would
like. For example:

.. code-block:: py

    from kitty.boss import Boss

    # in main, STDIN is for the kitten process and will contain
    # the contents of the screen
    def main(args: list[str]) -> str:
        return sys.stdin.read()

    # in handle_result, STDIN is for the kitty process itself, rather
    # than the kitten process and should not be read from.
    from kittens.tui.handler import result_handler
    @result_handler(type_of_input='text')
    def handle_result(args: list[str], stdin_data: str, target_window_id: int, boss: Boss) -> None:
        pass


This will send the plain text of the active window to the kitten's
:file:`STDIN`. There are many other types of input you can ask for, described in
the table below:

.. table:: Types of input to kittens
    :align: left

    =========================== =======================================================================================================
    Keyword                     Type of :file:`STDIN` input
    =========================== =======================================================================================================
    ``text``                    Plain text of active window
    ``ansi``                    Formatted text of active window
    ``screen``                  Plain text of active window with line wrap markers
    ``screen-ansi``             Formatted text of active window with line wrap markers

    ``history``                 Plain text of active window and its scrollback
    ``ansi-history``            Formatted text of active window and its scrollback
    ``screen-history``          Plain text of active window and its scrollback with line wrap markers
    ``screen-ansi-history``     Formatted text of active window and its scrollback with line wrap markers

    ``output``                  Plain text of the output from the last run command
    ``output-screen``           Plain text of the output from the last run command with wrap markers
    ``output-ansi``             Formatted text of the output from the last run command
    ``output-screen-ansi``      Formatted text of the output from the last run command with wrap markers

    ``selection``               The text currently selected with the mouse
    =========================== =======================================================================================================

In addition to ``output``, that gets the output of the last run command,
``last_visited_output`` gives the output of the command last jumped to
and ``first_output`` gives the output of the first command currently on screen.
These can also be combined with ``screen`` and ``ansi`` for formatting.

.. note::
   For the types based on the output of a command, :ref:`shell_integration` is
   required.


Using kittens to script kitty, without any terminal UI
-----------------------------------------------------------

If you would like your kitten to script kitty, without bothering to write a
terminal program, you can tell the kittens system to run the ``handle_result()``
function without first running the ``main()`` function.

For example, here is a kitten that "zooms in/zooms out" the current terminal
window by switching to the stack layout or back to the previous layout. This is
equivalent to the builtin :ac:`toggle_layout` action.

Create a Python file in the :ref:`kitty config directory <confloc>`,
:file:`~/.config/kitty/zoom_toggle.py`

.. code-block:: py

    from kitty.boss import Boss

    def main(args: list[str]) -> str:
        pass

    from kittens.tui.handler import result_handler
    @result_handler(no_ui=True)
    def handle_result(args: list[str], answer: str, target_window_id: int, boss: Boss) -> None:
        tab = boss.active_tab
        if tab is not None:
            if tab.current_layout.name == 'stack':
                tab.last_used_layout()
            else:
                tab.goto_layout('stack')


Now in :file:`kitty.conf` add::

    map f11 kitten zoom_toggle.py

Pressing :kbd:`F11` will now act as a zoom toggle function. You can get even
more fancy, switching the kitty OS window to fullscreen as well as changing the
layout, by simply adding the line::

    boss.toggle_fullscreen()


to the ``handle_result()`` function, above.


.. _send_mouse_event:

Sending mouse events
--------------------

If the program running in a window is receiving mouse events, you can simulate
those using::

    from kitty.fast_data_types import send_mouse_event
    send_mouse_event(screen, x, y, button, action, mods)

``screen`` is the ``screen`` attribute of the window you want to send the event
to. ``x`` and ``y`` are the 0-indexed coordinates. ``button`` is a number using
the same numbering as X11 (left: ``1``, middle: ``2``, right: ``3``, scroll up:
``4``, scroll down: ``5``, scroll left: ``6``, scroll right: ``7``, back:
``8``, forward: ``9``). ``action`` is one of ``PRESS``, ``RELEASE``, ``DRAG``
or ``MOVE``. ``mods`` is a bitmask of ``GLFW_MOD_{mod}`` where ``{mod}`` is one
of ``SHIFT``, ``CONTROL`` or ``ALT``. All the mentioned constants are imported
from ``kitty.fast_data_types``.

For example, to send a left click at position x: 2, y: 3 to the active window::

    from kitty.fast_data_types import send_mouse_event, PRESS
    send_mouse_event(boss.active_window.screen, 2, 3, 1, PRESS, 0)

The function will only send the event if the program is receiving events of
that type, and will return ``True`` if it sent the event, and ``False`` if not.


.. _kitten_main_rc:

Using remote control inside the main() kitten function
------------------------------------------------------------

You can use kitty's remote control features inside the main() function of a
kitten, even without enabling remote control. This is useful if you want to
probe kitty for more information before presenting some UI to the user or if
you want the user to be able to control kitty from within your kitten's UI
rather than after it has finished running. To enable it, simply tell kitty your kitten
requires remote control, as shown in the example below::

    import json
    import sys
    from pprint import pprint

    from kittens.tui.handler import kitten_ui

    @kitten_ui(allow_remote_control=True)
    def main(args: list[str]) -> str:
        # get the result of running kitten @ ls
        cp = main.remote_control(['ls'], capture_output=True)
        if cp.returncode != 0:
            sys.stderr.buffer.write(cp.stderr)
            raise SystemExit(cp.returncode)
        output = json.loads(cp.stdout)
        pprint(output)
        # open a new tab with a title specified by the user
        title = input('Enter the name of tab: ')
        window_id = main.remote_control(['launch', '--type=tab', '--tab-title', title], check=True, capture_output=True).stdout.decode()
        return window_id

:code:`allow_remote_control=True` tells kitty to run this kitten with remote
control enabled, regardless of whether it is enabled globally or not.
To run a remote control command use the :code:`main.remote_control()` function
which is a thin wrapper around Python's :code:`subprocess.run` function. Note
that by default, for security, child processes launched by your kitten cannot use remote
control, thus it is necessary to use :code:`main.remote_control()`. If you wish
to enable child processes to use remote control, call
:code:`main.allow_indiscriminate_remote_control()`.

Remote control access can be further secured by using
:code:`kitten_ui(allow_remote_control=True, remote_control_password='ls set-colors')`.
This will use a secure generated password to restrict remote control.
You can specify a space separated list of remote control commands to allow, see
:opt:`remote_control_password` for details. The password value is accessible
as :code:`main.password` and is used by :code:`main.remote_control()`
automatically.


Debugging kittens
--------------------

The part of the kitten that runs in ``main()`` is just a normal program and the
output of print statements will be visible in the kitten window. Or alternately,
you can use::

    from kittens.tui.loop import debug
    debug('whatever')

The ``debug()`` function is just like ``print()`` except that the output will
appear in the ``STDOUT`` of the kitty process inside which the kitten is
running.

The ``handle_result()`` part of the kitten runs inside the kitty process.
The output of print statements will go to the ``STDOUT`` of the kitty process.
So if you run kitty from another kitty instance, the output will be visible
in the first kitty instance.


Adding options to kittens
----------------------------

If you would like to use kitty's config framework to make your kittens
configurable, you will need some boilerplate. Put the following files in the
directory of your kitten.

:file:`kitten_options_definition.py`

.. code-block:: python

    from kitty.conf.types import Action, Definition

    definition = Definition(
        '!kitten_options_utils',
        Action(
            'map', 'parse_map',
            {'key_definitions': 'kitty.conf.utils.KittensKeyMap'},
            ['kitty.types.ParsedShortcut', 'kitty.conf.utils.KeyAction']
        ),
    )

    agr = definition.add_group
    egr = definition.end_group
    opt = definition.add_option
    map = definition.add_map

    # main options {{{
    agr('main', 'Main')

    opt('some_option', '33',
        option_type='some_option_parser',
        long_text='''
    Help text for this option
    '''
        )
    egr()  # }}}

    # shortcuts {{{
    agr('shortcuts', 'Keyboard shortcuts')

    map('Quit', 'quit q quit')
    egr()  # }}}


:file:`kitten_options_utils.py`

.. code-block:: python

    from kitty.conf.utils import KittensKeyDefinition, key_func, parse_kittens_key

    func_with_args, args_funcs = key_func()
    FuncArgsType = Tuple[str, Sequence[Any]]

    def some_option_parser(val: str) -> int:
        return int(val) + 3000

    def parse_map(val: str) -> Iterable[KittensKeyDefinition]:
        x = parse_kittens_key(val, args_funcs)
        if x is not None:
            yield x

Then run::

    kitty +runpy 'from kitty.conf.generate import main; main()' /path/to/kitten_options_definition.py

You can parse and read the options in your kitten using the following code:

.. code-block:: python

    from .kitten_options_types import Options, defaults
    from kitty.conf.utils import load_config as _load_config, parse_config_base
    from typing import Optional, Iterable, Dict, Any

    def load_config(*paths: str, overrides: Optional[Iterable[str]] = None) -> Options:
        from .kitten_options_parse import  (
            create_result_dict, merge_result_dicts, parse_conf_item
        )

        def parse_config(lines: Iterable[str]) -> Dict[str, Any]:
            ans: Dict[str, Any] = create_result_dict()
            parse_config_base(
                lines,
                parse_conf_item,
                ans,
            )
            return ans

        overrides = tuple(overrides) if overrides is not None else ()
        opts_dict, found_paths = _load_config(defaults, parse_config, merge_result_dicts, *paths, overrides=overrides)
        opts = Options(opts_dict)
        opts.config_paths = found_paths
        opts.all_config_paths = paths
        opts.config_overrides = overrides
        return opts

See `the code <https://github.com/kovidgoyal/kitty/tree/master/kittens/diff>`__
for the builtin :doc:`diff kitten </kittens/diff>` for examples of creating more
options and keyboard shortcuts.


Developing builtin kittens for inclusion with kitty
----------------------------------------------------------

There is documentation for :doc:`developing-builtin-kittens` which are written in the Go
language.


.. _external_kittens:

Kittens created by kitty users
---------------------------------------------

`vim-kitty-navigator <https://github.com/knubie/vim-kitty-navigator>`_
    Allows you to navigate seamlessly between vim and kitty splits using a
    consistent set of hotkeys.

`smart-scroll <https://github.com/yurikhan/kitty-smart-scroll>`_
    Makes the kitty scroll bindings work in full screen applications


`gattino <https://github.com/salvozappa/gattino>`__
    Integrate kitty with an LLM to convert plain language prompts into shell commands.

:iss:`insert password <1222>`
    Insert a password from a CLI password manager, taking care to only do it at
    a password prompt.

`weechat-hints <https://github.com/GermainZ/kitty-weechat-hints>`_
    URL hints kitten for WeeChat that works without having to use WeeChat's
    raw-mode.