Skip to content

Commit 1c9a2da

Browse files
committed
Start work on documenting the multi cursor protocol
1 parent c393c16 commit 1c9a2da

File tree

3 files changed

+164
-6
lines changed

3 files changed

+164
-6
lines changed

docs/multiple-cursors-protocol.rst

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
The multiple cursors protocol
2+
==============================================
3+
4+
.. versionadded:: 0.43.0
5+
6+
Many editors support something called *multiple cursors* in which you can make
7+
the same changes at multiple locations in a file and the editor shows you
8+
cursors at each of the locations. In a terminal context editors typically
9+
implement this by showing some Unicode glyph at each location instead of the
10+
actual cursor. This is sub-optimal since actual cursors implemented by the
11+
terminal have many niceties like smooth animation, auto adjust colors, etc. To
12+
address this and other use cases, this protocol allows terminal programs to
13+
request that the terminal display multiple cursors at specific locations on the
14+
screen.
15+
16+
Quickstart
17+
----------------
18+
19+
An example, showing how to use the protocol:
20+
21+
.. code-block:: sh
22+
23+
# Show cursors of the same shape as the main cursor at y=4, x=5
24+
printf "\e[>-1;2:4:5 q"
25+
# Show more cursors on the seventh line, of various shapes, the underline shape is shown twice
26+
printf "\e[>1;2:7:1 q\e[>2;2:7:3 q\e[>3;2:7:5;2:7:7 q"
27+
28+
29+
The escape code to show a cursor has the following structure (ignore spaces
30+
they are present for readability only)::
31+
32+
CSI > SHAPE;CO-ORD TYPE : CO-ORDINATES ; CO-ORD TYPE : CO-ORDINATES ... TRAILER
33+
34+
Here ``CSI`` is the two bytes ESC (``0x1b``) and [ (``0x5b``). ``SHAPE`` can be
35+
one of:
36+
37+
* ``-2``: Used for querying currently set cursors
38+
* ``-1``: Follow the shape of the main cursor
39+
* ``0``: No cursor
40+
* ``1``: Block cursor
41+
* ``2``: Beam cursor
42+
* ``3``: Underline cursor
43+
44+
``CO-ORD TYPE`` can be one of:
45+
46+
* ``0``: This refers to the position of the main cursor and has no following
47+
co-ordinates.
48+
49+
* ``2``: In this case the following co-ordinates are pairs of numbers pointing
50+
to cells in the form ``y:x`` with the origin in the top left corner at
51+
``1,1``. There can be any number of pairs, the terminal must treat each pair
52+
as a new location to set a cursor.
53+
54+
* ``4``: In this case the following co-ordinates are sets of four numbers that
55+
define a rectangle in the same co-ordinate system as above of the form:
56+
``top:left:bottom:right``. The shape is set on every cell in the rectangle
57+
from the top left cell to the bottom right cell, inclusive. If no numbers
58+
are provided, the rectangle is the full screen. There can be any number of
59+
rectangles, the terminal must treat each set of four numbers as a new
60+
rectangle.
61+
62+
The sequence of ``CO-ORD TYPE : CO-ORDINATES`` can be repeated any number of
63+
times separated by ``;``. The ``SHAPE`` will be set on the cells indicated by
64+
each such group. For example: ``-1;2:3:4;4:5:6:7:8`` will set the shape ``-1``
65+
at the cell ``(3, 2)`` and in the rectangle ``(6, 5)`` to ``(8, 7)`` inclusive.
66+
67+
Finally, the ``TRAILER`` terminates the sequence and is the bytes SPACE
68+
(``0x20``) and q (``0x71``).
69+
70+
Terminals **must** ignore cells that fall outside the screen. That means, for
71+
rectangle co-ordinates only the intersection of the rectangle with the screen
72+
must be considered, and point co-ordinates that fall outside of the screen are
73+
simply ignored, with no effect.
74+
75+
Terminals **must** ignore extra co-ordinates, that means if an odd number of
76+
co-ordinates are specified for type ``2`` the last co-ordinate is ignored.
77+
Similarly for type ``4`` if the number of co-ordinates is not a multiple of
78+
four, the last ``1 <= n <= 3`` co-ordinates are ignored, as if they were not
79+
specified.
80+
81+
Querying for support
82+
-------------------------
83+
84+
A terminal program can query the terminal emulator for support of this
85+
protocol by sending the escape code::
86+
87+
CSI > TRAILER
88+
89+
In this case a supporting terminal must reply with::
90+
91+
CSI > -1;1;2;3 TRAILER
92+
93+
Here, the list of numbers indicates the cursor shapes the terminal supports and
94+
can be any subset of the above. No numbers indicates the protocol is not
95+
supported. To avoid having to wait with a timeout for a response from the
96+
terminal, the client should send this query code immediately followed by
97+
a request for the `primary device attributes <https://vt100.net/docs/vt510-rm/DA1.html>`_.
98+
If the terminal responds with an answer for the device attributes without
99+
an answer for the *query* the terminal emulator does not support this protocol at all.
100+
101+
Terminals **must** respond to these queries in FIFO order, so that
102+
multiplexers that split a single screen know which split to send responses too.
103+
104+
Clearing previously set multi-cursors
105+
------------------------------------------
106+
107+
The cursor at a cell is cleared by settings its shape to ``0``.
108+
The most common operation is to clear all previously set multi-cursors. This is
109+
easily done using the *rectangle* co-ordinate system above, like this::
110+
111+
CSI > 0;4 TRAILER
112+
113+
For more precise control different co-ordinate types can be used. This is
114+
particularly important for multiplexers that split up the screen and therefore
115+
need to re-write these escape codes.
116+
117+
Querying for already set cursors
118+
--------------------------------------
119+
120+
Programs can ask the terminal what extra cursors are currently set, by sending
121+
the escape code::
122+
123+
CSI > -2 TRAILER
124+
125+
The terminal must respond with **one** escape code::
126+
127+
CSI > -2; SHAPE:CO-ORDINATE TYPE:CO-ORDINATES ; ... TRAILER
128+
129+
Here, the ``SHAPE:CO-ORDINATE TYPE:CO-ORDINATES`` block can be repeated any
130+
number of times, separated by ``;``. This response gives the set of shapes and
131+
positions currently active. If no cursors are currently active, there will be
132+
no blocks, just an empty response of the form::
133+
134+
CSI > -2 TRAILER
135+
136+
Again, terminals **must** respond in FIFO order so that multiplexers know where
137+
to direct the responses.
138+
139+
140+
Interaction with other terminal controls and state
141+
-------------------------------------------------------
142+
143+
**The main cursor**
144+
The extra cursors must all have the same color and opacity and blink state
145+
as the main cursor. The main cursor's visibility must not affect the
146+
visibility of the extra cursors. Their visibility and shape are controlled
147+
only by this protocol.
148+
149+
**Clearing the screen**
150+

docs/protocol-extensions.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ please do so by opening issues in the `GitHub bug tracker
2828
graphics-protocol
2929
keyboard-protocol
3030
text-sizing-protocol
31+
multiple-cursors-protocol
3132
file-transfer-protocol
3233
desktop-notifications
3334
pointer-shapes

kitty/screen.c

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1900,11 +1900,6 @@ screen_is_cursor_visible(const Screen *self) {
19001900
return self->paused_rendering.expires_at ? self->paused_rendering.cursor_visible : self->modes.mDECTCEM;
19011901
}
19021902

1903-
unsigned
1904-
screen_multi_cursor_count(const Screen *self) {
1905-
return self->paused_rendering.expires_at ? self->paused_rendering.extra_cursors.count : self->extra_cursors.count;
1906-
}
1907-
19081903
void
19091904
screen_backspace(Screen *self) {
19101905
screen_cursor_move(self, 1, -1, true);
@@ -2862,6 +2857,11 @@ screen_set_cursor(Screen *self, unsigned int mode, uint8_t secondary) {
28622857
#define VAL_TY uint8_t
28632858
#include "kitty-verstable.h"
28642859

2860+
unsigned
2861+
screen_multi_cursor_count(const Screen *self) {
2862+
return self->paused_rendering.expires_at ? self->paused_rendering.extra_cursors.count : self->extra_cursors.count;
2863+
}
2864+
28652865
void
28662866
screen_multi_cursor(Screen *self, int queried_shape, int *params, unsigned num_params) {
28672867
// printf("%d;", queried_shape); for (unsigned i = 0; i < num_params; i++) {printf("%d:", params[i]);} printf("\n");
@@ -2893,7 +2893,13 @@ screen_multi_cursor(Screen *self, int queried_shape, int *params, unsigned num_p
28932893
}
28942894
self->extra_cursors.dirty = true;
28952895
int type = params[0]; params++; num_params--;
2896+
int extra[2];
28962897
switch (type) {
2898+
case 0:
2899+
extra[0] = MIN(self->cursor->y, self->lines-1) + 1;
2900+
extra[1] = MIN(self->cursor->x, self->columns-1) + 1;
2901+
params = extra; num_params = 2;
2902+
/* fallthrough */
28972903
case 2: {
28982904
multi_cursor_map s; vt_init(&s);
28992905
for (unsigned i = 0; i < self->extra_cursors.count; i++) {
@@ -2914,7 +2920,7 @@ screen_multi_cursor(Screen *self, int queried_shape, int *params, unsigned num_p
29142920
vt_cleanup(&s);
29152921
} break;
29162922
case 4: {
2917-
if (!num_params) { // full screen
2923+
if (num_params < 4) { // full screen
29182924
switch(shape) {
29192925
default: self->extra_cursors.count = 0; break;
29202926
case 1: case 2: case 3: case 4:
@@ -2942,6 +2948,7 @@ screen_multi_cursor(Screen *self, int queried_shape, int *params, unsigned num_p
29422948
if (shape) {
29432949
for (unsigned i = 0; i + 3 < num_params; i += 4) {
29442950
index_type top = params[i]-1, left = params[i+1]-1, bottom = params[i+2]-1, right = params[i+3]-1;
2951+
bottom = MIN(bottom, self->lines-1); right = MIN(right, self->columns -1);
29452952
index_type xnum = right + 1 - left, ynum = bottom + 1 - top;
29462953
ensure_space_for(&self->extra_cursors, locations, ExtraCursor,
29472954
self->extra_cursors.count + xnum * ynum, capacity, 20 * 80, false);

0 commit comments

Comments
 (0)