Compare commits
866 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70111d130e | ||
|
|
ce6dacd0d4 | ||
|
|
f3c434b7c1 | ||
|
|
54b6344985 | ||
|
|
18fc8dad37 | ||
|
|
9b5034f904 | ||
|
|
f2c8819d25 | ||
|
|
53482f4c84 | ||
|
|
e1c50cf124 | ||
|
|
aacb4db2dc | ||
|
|
8638e42135 | ||
|
|
4b322560c3 | ||
|
|
0a4ad1fe12 | ||
|
|
907f658fcc | ||
|
|
1e7e10f96f | ||
|
|
5338fcdc1e | ||
|
|
f8991ce3c8 | ||
|
|
b408abe304 | ||
|
|
084671b26e | ||
|
|
1cc69b3edd | ||
|
|
d88105319d | ||
|
|
eb50fac8de | ||
|
|
4185e30d73 | ||
|
|
3a126ffa9d | ||
|
|
4f5e3f6cf1 | ||
|
|
dd6e206f9b | ||
|
|
ea920ffdf9 | ||
|
|
366d65f592 | ||
|
|
5e645a7be7 | ||
|
|
e3040a9c91 | ||
|
|
a01d68ed85 | ||
|
|
f070b17fee | ||
|
|
22d562ca41 | ||
|
|
102197fabe | ||
|
|
83f505e209 | ||
|
|
5a86bc2411 | ||
|
|
b9c324586e | ||
|
|
41fb3c79c5 | ||
|
|
5066623089 | ||
|
|
8e98b4123e | ||
|
|
8f88aab9d2 | ||
|
|
d8bbb16d5e | ||
|
|
60791bb57b | ||
|
|
11f98592f7 | ||
|
|
97467acb1f | ||
|
|
cc1f0bc3fe | ||
|
|
a009d6b258 | ||
|
|
0903ae7b4d | ||
|
|
35c1ebd3f9 | ||
|
|
352f940010 | ||
|
|
1f00c27097 | ||
|
|
6b2d37366f | ||
|
|
a4075ff041 | ||
|
|
7a526d9588 | ||
|
|
5b4e4f032d | ||
|
|
0a1b399447 | ||
|
|
6a64df1afb | ||
|
|
dc1851acdb | ||
|
|
096c1e328e | ||
|
|
1a44c2117b | ||
|
|
bd13238d9b | ||
|
|
b1c27d4c2f | ||
|
|
dc0093cb51 | ||
|
|
b1934ce267 | ||
|
|
16f767de7e | ||
|
|
93e9332474 | ||
|
|
81cc09aa61 | ||
|
|
e5bc7255b2 | ||
|
|
75048d56bc | ||
|
|
19f853c29f | ||
|
|
26ca3f9bad | ||
|
|
bf7d27691c | ||
|
|
22532b4805 | ||
|
|
68a006444e | ||
|
|
2d846f53a1 | ||
|
|
63077e5432 | ||
|
|
5edb1e2d6b | ||
|
|
0c7b4df6fc | ||
|
|
03abbb315a | ||
|
|
dc03c14af2 | ||
|
|
821f52a748 | ||
|
|
750f2fa4d0 | ||
|
|
009fd6418c | ||
|
|
ce161e610f | ||
|
|
b1b9c3704b | ||
|
|
fd245e894b | ||
|
|
59170a3627 | ||
|
|
61143557a9 | ||
|
|
9a77fdcd04 | ||
|
|
0c8ec3c57b | ||
|
|
b799eba81e | ||
|
|
9d1de50bf9 | ||
|
|
d55a9b582b | ||
|
|
865fc24975 | ||
|
|
45b0788f28 | ||
|
|
bc73273cb3 | ||
|
|
dd5cfe38b7 | ||
|
|
6b8e5ea225 | ||
|
|
60310ced05 | ||
|
|
f61b007645 | ||
|
|
d4e34a4c31 | ||
|
|
15524ee0fb | ||
|
|
58a4b4218e | ||
|
|
ac67d9c72d | ||
|
|
b9692d480a | ||
|
|
3a2da22509 | ||
|
|
bf8d0c9732 | ||
|
|
836b652f4d | ||
|
|
69255f7525 | ||
|
|
6e41409a3f | ||
|
|
7fe5d7b58f | ||
|
|
f15d27bb62 | ||
|
|
a068e3e655 | ||
|
|
d248d5ad75 | ||
|
|
44866a6e79 | ||
|
|
1fe84f6057 | ||
|
|
41207aa830 | ||
|
|
66a2e6f80d | ||
|
|
397ac36011 | ||
|
|
02063a5a6a | ||
|
|
b3926ae9e7 | ||
|
|
3743ae50e7 | ||
|
|
2205bf4426 | ||
|
|
def35078d1 | ||
|
|
1b5fac3189 | ||
|
|
dab7f71d2f | ||
|
|
1223130ef7 | ||
|
|
4e90ae68a4 | ||
|
|
e16e3c1582 | ||
|
|
ddf36383b4 | ||
|
|
77a3916f6f | ||
|
|
5d42d1f955 | ||
|
|
a2c68a927e | ||
|
|
cab7856495 | ||
|
|
c317c934f3 | ||
|
|
4d21be9eb5 | ||
|
|
fd71d2035d | ||
|
|
7ebb281855 | ||
|
|
2d3da1db6d | ||
|
|
458adf967a | ||
|
|
eec4c6b802 | ||
|
|
0b9b207513 | ||
|
|
7e8017604e | ||
|
|
520b935cd3 | ||
|
|
9cb83a6ec2 | ||
|
|
7c2317d301 | ||
|
|
7237e5cf9c | ||
|
|
cd5dab581b | ||
|
|
481cebbd29 | ||
|
|
4623f580b9 | ||
|
|
6291d0d400 | ||
|
|
36d82267bb | ||
|
|
f9feb4954a | ||
|
|
d192dcb0d7 | ||
|
|
143fd6e4dd | ||
|
|
df06578c2d | ||
|
|
73a055fe12 | ||
|
|
1fa1a478d9 | ||
|
|
5a3a547c65 | ||
|
|
373ab95f14 | ||
|
|
7f866b2b1f | ||
|
|
24db38ba7e | ||
|
|
efdfaaec30 | ||
|
|
d694f48c91 | ||
|
|
5562a4d52f | ||
|
|
ce4c71c465 | ||
|
|
31df90a64e | ||
|
|
22d69d24d0 | ||
|
|
d76e0850ae | ||
|
|
b520882b62 | ||
|
|
79acab1547 | ||
|
|
4690f3c7c0 | ||
|
|
cf1d9eb303 | ||
|
|
ba2a3f228d | ||
|
|
a757587ea2 | ||
|
|
ea756db544 | ||
|
|
2d1a2c30bf | ||
|
|
6ace082bc2 | ||
|
|
7e161ea94b | ||
|
|
d01d5297b8 | ||
|
|
7a1140cd03 | ||
|
|
aab81c2d32 | ||
|
|
49418c2f53 | ||
|
|
a548e3eb4d | ||
|
|
779a7b7deb | ||
|
|
c83a8b0773 | ||
|
|
035c3de4bb | ||
|
|
e2543e8968 | ||
|
|
bc2492c212 | ||
|
|
c03310b5e5 | ||
|
|
bd33cef092 | ||
|
|
832506d785 | ||
|
|
13b09346b9 | ||
|
|
1c10c5fcc4 | ||
|
|
510c5bd73b | ||
|
|
774fdd7e94 | ||
|
|
58497161c0 | ||
|
|
f831c34813 | ||
|
|
456af90ad2 | ||
|
|
c18bff7821 | ||
|
|
080d1bf935 | ||
|
|
4f9ed6546a | ||
|
|
c76bbeabd6 | ||
|
|
0f24ce60f8 | ||
|
|
6a06769931 | ||
|
|
468b6fab02 | ||
|
|
306f9e5735 | ||
|
|
f4ac03b791 | ||
|
|
b8abdd2b50 | ||
|
|
e673747ef4 | ||
|
|
6e4376e44e | ||
|
|
f6801d48d1 | ||
|
|
ca57198927 | ||
|
|
e78000c879 | ||
|
|
68cf9f7514 | ||
|
|
4556f5b8f1 | ||
|
|
32d8aac808 | ||
|
|
2e81d00c92 | ||
|
|
cd92d50a0d | ||
|
|
155dd426c5 | ||
|
|
a8f2816ac2 | ||
|
|
0ddc5d030c | ||
|
|
a2c2d81373 | ||
|
|
307ce1cf87 | ||
|
|
61ceb12e07 | ||
|
|
b45dc20693 | ||
|
|
bcedbe5ec1 | ||
|
|
038639e2d3 | ||
|
|
c857492f37 | ||
|
|
42923ab673 | ||
|
|
9afd0309fc | ||
|
|
b9d098de2d | ||
|
|
09e6f740de | ||
|
|
1023084eb9 | ||
|
|
aa9679e56b | ||
|
|
d8284befd3 | ||
|
|
e4ee2cf995 | ||
|
|
e3f6f47f10 | ||
|
|
e5941b1b44 | ||
|
|
65c64d8745 | ||
|
|
40093a4702 | ||
|
|
3815cba8f3 | ||
|
|
7410cd62f9 | ||
|
|
ae0a8e73d2 | ||
|
|
8f3a8c828f | ||
|
|
6efb5dc2c2 | ||
|
|
bb33f6c0ac | ||
|
|
d34284ee1c | ||
|
|
74cae7b787 | ||
|
|
c6360537e5 | ||
|
|
7d28d7413f | ||
|
|
d3f3e9ec38 | ||
|
|
928becece4 | ||
|
|
80db167abb | ||
|
|
a5a6880a19 | ||
|
|
da13d03b98 | ||
|
|
0a5c409a12 | ||
|
|
eeb772ad07 | ||
|
|
2b66775f45 | ||
|
|
3f65ce0e71 | ||
|
|
d235b673d1 | ||
|
|
0ab618c2dc | ||
|
|
fefafda9a0 | ||
|
|
902373ed20 | ||
|
|
aac57550c9 | ||
|
|
95e05ce9ec | ||
|
|
b6c6316b7b | ||
|
|
91576cc42f | ||
|
|
032e01ebf7 | ||
|
|
bd70100e09 | ||
|
|
ff6d2e3c10 | ||
|
|
2f292bb5d2 | ||
|
|
52cf443daf | ||
|
|
f03a665e09 | ||
|
|
66e7919171 | ||
|
|
1289dd2ff0 | ||
|
|
fa50e0d6cc | ||
|
|
8c7a5288ae | ||
|
|
7fe5c79d53 | ||
|
|
ea5ffa4304 | ||
|
|
89b32e4545 | ||
|
|
879b27a045 | ||
|
|
9cb0e4d09d | ||
|
|
94410f2866 | ||
|
|
a3ba8647ba | ||
|
|
f35ee1bb40 | ||
|
|
23b3b171fa | ||
|
|
30b25709d2 | ||
|
|
9c488bb877 | ||
|
|
14a33dbd94 | ||
|
|
e37ee422b6 | ||
|
|
d7d96c2e7b | ||
|
|
fda4aa21a1 | ||
|
|
3cbca4955e | ||
|
|
8e0e70e2d6 | ||
|
|
7a348d6ef1 | ||
|
|
cf8f904720 | ||
|
|
fe53555dba | ||
|
|
b644a42a48 | ||
|
|
5c9c9a67bc | ||
|
|
76f6288e69 | ||
|
|
6422b323c6 | ||
|
|
a622a149f6 | ||
|
|
fa6527cdf3 | ||
|
|
0b293428c4 | ||
|
|
26e8a5186a | ||
|
|
bde737fa38 | ||
|
|
71e09ba1fb | ||
|
|
3e69cf81af | ||
|
|
f5d2c35755 | ||
|
|
38a7fa73e3 | ||
|
|
fdd42d5f19 | ||
|
|
fe75493c37 | ||
|
|
e187265d04 | ||
|
|
0a985134e2 | ||
|
|
e555d963b1 | ||
|
|
3601488b26 | ||
|
|
f29ce19097 | ||
|
|
f6ab641b00 | ||
|
|
722a1020fa | ||
|
|
fbd19f3bde | ||
|
|
3cf874f6e9 | ||
|
|
02b206eeb8 | ||
|
|
5c50e3869c | ||
|
|
6f66bbd424 | ||
|
|
2697ddaec3 | ||
|
|
d6dcdf0751 | ||
|
|
11724c8a5f | ||
|
|
b6792a6d71 | ||
|
|
f0d61c2de9 | ||
|
|
35ae689818 | ||
|
|
1305199bdf | ||
|
|
c62c31f776 | ||
|
|
defef3f88c | ||
|
|
3ee9f723f2 | ||
|
|
a8725d6307 | ||
|
|
4c72f92939 | ||
|
|
2150f261ee | ||
|
|
f720bc9fab | ||
|
|
d92f89e47f | ||
|
|
c54a4021ef | ||
|
|
375fa73826 | ||
|
|
5eb2142d70 | ||
|
|
00223c5bba | ||
|
|
aa9080d375 | ||
|
|
8d45f5011b | ||
|
|
b3f096b03b | ||
|
|
a721ffeb7d | ||
|
|
6a1b456bac | ||
|
|
63a08dc6cc | ||
|
|
4969611bdb | ||
|
|
5d3a9f2628 | ||
|
|
15a7aeff4d | ||
|
|
f9a22d0bc7 | ||
|
|
402c8b6803 | ||
|
|
ac60715ee2 | ||
|
|
cfc6bd4da5 | ||
|
|
4d3f3b5e91 | ||
|
|
55b5a45e27 | ||
|
|
fbce5e7524 | ||
|
|
daeb1b6c50 | ||
|
|
8fb2c209a9 | ||
|
|
2b676b63b1 | ||
|
|
c0d06adcb5 | ||
|
|
51bba9110e | ||
|
|
4fc91dcc03 | ||
|
|
3c0667afd6 | ||
|
|
03dc24f913 | ||
|
|
73b5857916 | ||
|
|
08d74a6c56 | ||
|
|
4290a8f9ba | ||
|
|
c6dd6ccc68 | ||
|
|
fd7cc66689 | ||
|
|
1c19fc77f1 | ||
|
|
4080a09748 | ||
|
|
b91eaa3b2a | ||
|
|
bdb25831a7 | ||
|
|
a131f9a82e | ||
|
|
ffefd0f581 | ||
|
|
43c0e0f586 | ||
|
|
76669ad14d | ||
|
|
a9bb341e43 | ||
|
|
0757fcc451 | ||
|
|
dd4051bfd5 | ||
|
|
a6dcbe9c1d | ||
|
|
efaf9faa38 | ||
|
|
36631ddc49 | ||
|
|
e5e8cc72c6 | ||
|
|
38547c9e7a | ||
|
|
e00a5ce911 | ||
|
|
0af48a4d05 | ||
|
|
c0d80f9a47 | ||
|
|
d2dabc7d57 | ||
|
|
81db5eb82f | ||
|
|
1addfea261 | ||
|
|
e5be31ee14 | ||
|
|
9cef714078 | ||
|
|
a5a4a1bf8f | ||
|
|
281ad13f6b | ||
|
|
5ad2ac259b | ||
|
|
a2f022d166 | ||
|
|
4df6c172a3 | ||
|
|
9db1d581f7 | ||
|
|
b3bc5a5565 | ||
|
|
fc20e8d04b | ||
|
|
86af1d5c16 | ||
|
|
d8574f8f85 | ||
|
|
bdd04f37d6 | ||
|
|
a4e43b3925 | ||
|
|
5e880b92f5 | ||
|
|
d4c103e53e | ||
|
|
3e4df7d812 | ||
|
|
3b18f90319 | ||
|
|
e31587ec1e | ||
|
|
5dc97af4d5 | ||
|
|
81916d0f02 | ||
|
|
ae93d95bbe | ||
|
|
dd1fcf6855 | ||
|
|
4b05822474 | ||
|
|
337f1fad3f | ||
|
|
463c9debe7 | ||
|
|
9225919112 | ||
|
|
e70c021371 | ||
|
|
f3b3d6c0ef | ||
|
|
0c0b9e6b9c | ||
|
|
d9215feda5 | ||
|
|
d54fe3c16a | ||
|
|
36dd5b2d00 | ||
|
|
87108a18ef | ||
|
|
234274c1df | ||
|
|
b42677b343 | ||
|
|
c03af4d294 | ||
|
|
8d12e60f62 | ||
|
|
5a71b8c209 | ||
|
|
16775c5539 | ||
|
|
8b8263d0ff | ||
|
|
9ddd7d070e | ||
|
|
780d5400cb | ||
|
|
63d7b2a80b | ||
|
|
1958d67847 | ||
|
|
737919505d | ||
|
|
d60ecfa8a6 | ||
|
|
e3b22a87e4 | ||
|
|
c7d47a59f1 | ||
|
|
7eae176b88 | ||
|
|
9361c62a1c | ||
|
|
018811c96c | ||
|
|
129646c199 | ||
|
|
1881f4582e | ||
|
|
a5d05a2954 | ||
|
|
59b43ee844 | ||
|
|
29bde6c72c | ||
|
|
a7ce642a00 | ||
|
|
723a9c91b5 | ||
|
|
9e2c96653f | ||
|
|
4974219e0f | ||
|
|
a4de4b7c6f | ||
|
|
eae4899df4 | ||
|
|
87b4800fdf | ||
|
|
64156fd6e6 | ||
|
|
1485981b11 | ||
|
|
8d76cf8d32 | ||
|
|
7c23536bfe | ||
|
|
f919efcd42 | ||
|
|
388e47a2df | ||
|
|
dc787a6529 | ||
|
|
cfb6d93dc0 | ||
|
|
1523fef000 | ||
|
|
31dcb13836 | ||
|
|
0c82832356 | ||
|
|
ffea66357a | ||
|
|
6b48624b81 | ||
|
|
5a425ccaad | ||
|
|
2f2dbfb45f | ||
|
|
13a266aa42 | ||
|
|
67b12159f4 | ||
|
|
8ad55f7562 | ||
|
|
e1ab2383b3 | ||
|
|
f77d07259a | ||
|
|
2e07f90baf | ||
|
|
8574e136cd | ||
|
|
c8e8cb5ad5 | ||
|
|
f127523ae9 | ||
|
|
22f7145e34 | ||
|
|
1f115870bb | ||
|
|
3237db00fc | ||
|
|
ea583f60b3 | ||
|
|
b7816d26be | ||
|
|
19bf07abd9 | ||
|
|
a008c627e3 | ||
|
|
0068ae8f66 | ||
|
|
5436408463 | ||
|
|
d260d2f480 | ||
|
|
00ef9c1955 | ||
|
|
5509673ff3 | ||
|
|
3c4a411cad | ||
|
|
f945ef8ee8 | ||
|
|
260249491d | ||
|
|
595a78c956 | ||
|
|
cbbf8a2d34 | ||
|
|
88567f69b2 | ||
|
|
fe91af5e09 | ||
|
|
5e2255591c | ||
|
|
01969cae1a | ||
|
|
b89dfc6d1d | ||
|
|
59edf1d349 | ||
|
|
9419fbc77c | ||
|
|
d27cf045e5 | ||
|
|
f7d44330a2 | ||
|
|
834385baff | ||
|
|
c0f17c279e | ||
|
|
7264bea8c9 | ||
|
|
1058c999c8 | ||
|
|
bf7dd1c369 | ||
|
|
936a7a5f97 | ||
|
|
350060e0f6 | ||
|
|
430bd23870 | ||
|
|
9f2b2eac85 | ||
|
|
fd36435262 | ||
|
|
27418eed88 | ||
|
|
48f1690913 | ||
|
|
eff239a195 | ||
|
|
5e5cae8391 | ||
|
|
57310a772a | ||
|
|
066465bce7 | ||
|
|
1792c2268a | ||
|
|
32059dba7e | ||
|
|
c8296a44eb | ||
|
|
565526624f | ||
|
|
f57832f501 | ||
|
|
e536ef7844 | ||
|
|
0dab006733 | ||
|
|
75ead358a2 | ||
|
|
654bd23109 | ||
|
|
6b04c42730 | ||
|
|
8f5302a650 | ||
|
|
74b1cac344 | ||
|
|
7d5849cc17 | ||
|
|
d2a2af9672 | ||
|
|
2633356842 | ||
|
|
3bd4fd999a | ||
|
|
262e2fb7a3 | ||
|
|
97716fea8b | ||
|
|
bf74413c1f | ||
|
|
79ca0408e7 | ||
|
|
97acb60981 | ||
|
|
3662efdf80 | ||
|
|
e608a945de | ||
|
|
5771bd0c01 | ||
|
|
4396dede85 | ||
|
|
cbc569af64 | ||
|
|
90c1745976 | ||
|
|
c4ab964d09 | ||
|
|
04022ed363 | ||
|
|
79cfc1e70a | ||
|
|
e7c14c78d0 | ||
|
|
cb2389efa5 | ||
|
|
49f5f25fb9 | ||
|
|
ff4353b209 | ||
|
|
c07dc220a8 | ||
|
|
4316018966 | ||
|
|
a0bf6177e2 | ||
|
|
3078b9074a | ||
|
|
dffb87ced9 | ||
|
|
4adea5b7fe | ||
|
|
317b108497 | ||
|
|
7c41737370 | ||
|
|
4b6bae576d | ||
|
|
8221713995 | ||
|
|
ef49634353 | ||
|
|
d6f42a11d7 | ||
|
|
800dbf1f4d | ||
|
|
b1e08adbce | ||
|
|
e7f38929d9 | ||
|
|
d0e133885c | ||
|
|
2cacd7a64a | ||
|
|
a44c89504b | ||
|
|
8807f6d539 | ||
|
|
2f83bbdc85 | ||
|
|
41a841c83d | ||
|
|
0cf8876f8a | ||
|
|
a3a89b3e21 | ||
|
|
2ddbe2a2bc | ||
|
|
707963b694 | ||
|
|
1811949706 | ||
|
|
bc38bd75fd | ||
|
|
54ec486d3a | ||
|
|
5666b1b0fd | ||
|
|
ef9b765f81 | ||
|
|
a2bb360eec | ||
|
|
e3b8de1ac0 | ||
|
|
4cbd2a0ee0 | ||
|
|
7cf9b21fc0 | ||
|
|
454c2e32e7 | ||
|
|
53dc079c10 | ||
|
|
08c697e99a | ||
|
|
1dce092ac0 | ||
|
|
24c2d27eea | ||
|
|
b04b483b3f | ||
|
|
a7c997c6ef | ||
|
|
266e51310c | ||
|
|
928a4db817 | ||
|
|
fd631bf402 | ||
|
|
dc403156a9 | ||
|
|
2cc359ccc8 | ||
|
|
cbbda23e01 | ||
|
|
1ff4f2df4f | ||
|
|
8796168469 | ||
|
|
d16ad40bbf | ||
|
|
5c0858d6b7 | ||
|
|
a32a8d096d | ||
|
|
67f556bd7c | ||
|
|
7737369fc9 | ||
|
|
25a7ec9a07 | ||
|
|
c2a2b4c087 | ||
|
|
3bf20594b7 | ||
|
|
3a8bab90dc | ||
|
|
0ff2446a1a | ||
|
|
946d44c43f | ||
|
|
26d4f5bcc9 | ||
|
|
d84efe105c | ||
|
|
f657e6e916 | ||
|
|
c5afceb4cb | ||
|
|
18c3e46ac6 | ||
|
|
3c29ce936b | ||
|
|
c9d986f9a8 | ||
|
|
d4b67f3b6a | ||
|
|
6faa908733 | ||
|
|
ced741b247 | ||
|
|
c47ccc8a59 | ||
|
|
3326e9ef49 | ||
|
|
134fce8507 | ||
|
|
5d89a6c3c4 | ||
|
|
833e9625f9 | ||
|
|
21b572d69a | ||
|
|
12d9787d0e | ||
|
|
8c6b391eda | ||
|
|
8c2e435793 | ||
|
|
10cf7f06c6 | ||
|
|
4575a14873 | ||
|
|
d679ea2cdf | ||
|
|
54378de52b | ||
|
|
63287e4115 | ||
|
|
d0efe00449 | ||
|
|
d703cb51cd | ||
|
|
005a9c7090 | ||
|
|
f4de6d2a10 | ||
|
|
e60b331152 | ||
|
|
3d79eb5730 | ||
|
|
af7f4e97cf | ||
|
|
aad8cb3d1e | ||
|
|
40a9ab8929 | ||
|
|
e433b90297 | ||
|
|
1ac7d9c10d | ||
|
|
ef5f0025e1 | ||
|
|
32804f01b2 | ||
|
|
7ecc1b7950 | ||
|
|
a7f6105393 | ||
|
|
081390b5a2 | ||
|
|
61094c6bfb | ||
|
|
6672904e64 | ||
|
|
905c4d641c | ||
|
|
fa7a6dfd4a | ||
|
|
cb452ba9fc | ||
|
|
85169c989f | ||
|
|
e8a321d097 | ||
|
|
3a198833da | ||
|
|
ea8fb10c05 | ||
|
|
67115530b4 | ||
|
|
7325921070 | ||
|
|
f86ce03d3b | ||
|
|
5cf10023c9 | ||
|
|
93d08011f1 | ||
|
|
7f77f7e408 | ||
|
|
65c3630099 | ||
|
|
457aab7c41 | ||
|
|
dcec926590 | ||
|
|
0a2455c8be | ||
|
|
bacca88213 | ||
|
|
bd1b3d9f6e | ||
|
|
364533b1ed | ||
|
|
d7985689c9 | ||
|
|
6d6bba4a4c | ||
|
|
5cc5759f3e | ||
|
|
a388a658ce | ||
|
|
b5e2871aa0 | ||
|
|
192eccc6cc | ||
|
|
a4b2e2a196 | ||
|
|
6f4968305a | ||
|
|
79c8862d4c | ||
|
|
441e4edfb2 | ||
|
|
ef71b071db | ||
|
|
b33a684357 | ||
|
|
43b35f6f4e | ||
|
|
3b2c4561c2 | ||
|
|
e69b02ad46 | ||
|
|
85b6053380 | ||
|
|
249df69ac9 | ||
|
|
4a4500d56b | ||
|
|
be22f49012 | ||
|
|
f9695a7947 | ||
|
|
29d9b70f0c | ||
|
|
42a8ca0842 | ||
|
|
91c61478dd | ||
|
|
fb482e28f6 | ||
|
|
387333492b | ||
|
|
9c58cb3f41 | ||
|
|
b2e610f9b1 | ||
|
|
a960937095 | ||
|
|
0cda5d43a6 | ||
|
|
a75d59643c | ||
|
|
bf35817d73 | ||
|
|
f103f8d5db | ||
|
|
b382587776 | ||
|
|
6fe0c8ba2f | ||
|
|
7b2991de02 | ||
|
|
1b90c03304 | ||
|
|
0aa05b02e8 | ||
|
|
5412a0126c | ||
|
|
841b368021 | ||
|
|
b1f9139ca5 | ||
|
|
4a49c3940a | ||
|
|
ee12349a50 | ||
|
|
69c847a48f | ||
|
|
03705cbec0 | ||
|
|
ff2ff9c04f | ||
|
|
a22ba9f739 | ||
|
|
c86f8a698c | ||
|
|
fa4711bd04 | ||
|
|
a939bbb3ec | ||
|
|
174bc25afb | ||
|
|
2ffba1c422 | ||
|
|
0913b64c6b | ||
|
|
32e59257d2 | ||
|
|
6f8c884bb5 | ||
|
|
80c5ac891d | ||
|
|
ccb60687b6 | ||
|
|
6a2393299b | ||
|
|
ab66b3f4c0 | ||
|
|
c8292d77f1 | ||
|
|
7280c712d6 | ||
|
|
9be2247081 | ||
|
|
e68b5fa504 | ||
|
|
c2ef6c986b | ||
|
|
619fcb0681 | ||
|
|
10a39d3757 | ||
|
|
d2a15a2009 | ||
|
|
235eb868b2 | ||
|
|
5703a3370e | ||
|
|
5dca2a1a25 | ||
|
|
4b18b575cd | ||
|
|
818f68ec53 | ||
|
|
cd54fefa99 | ||
|
|
4ab5456ead | ||
|
|
10d11bc749 | ||
|
|
19ffbc6f3d | ||
|
|
79b1af28b4 | ||
|
|
05d768d8df | ||
|
|
7bb310d3af | ||
|
|
9d56f8eed2 | ||
|
|
42a5129553 | ||
|
|
d6ed20323b | ||
|
|
63fdbd3fa0 | ||
|
|
99fde8723a | ||
|
|
526a331f47 | ||
|
|
e18b6638bb | ||
|
|
3c3e7b7f70 | ||
|
|
67f03621ae | ||
|
|
10cef16210 | ||
|
|
6a79b450f7 | ||
|
|
eb4ee13f73 | ||
|
|
cf287015de | ||
|
|
246277e7af | ||
|
|
13758e9600 | ||
|
|
73e4deb1c2 | ||
|
|
68d589826a | ||
|
|
7457746d65 | ||
|
|
eec8f04e93 | ||
|
|
3a7d26a3ef | ||
|
|
91b15d1a90 | ||
|
|
12b0f632bd | ||
|
|
84cb2638d6 | ||
|
|
7eb6cb2407 | ||
|
|
ce3062cbde | ||
|
|
e599a2c87f | ||
|
|
15e1f376a4 | ||
|
|
82d0bd9364 | ||
|
|
6a9f3feba2 | ||
|
|
2e31178980 | ||
|
|
141b9c8f08 | ||
|
|
17629add66 | ||
|
|
352a78f056 | ||
|
|
2ef5d0a140 | ||
|
|
08b4741024 | ||
|
|
11ffa961bc | ||
|
|
ad6d928c12 | ||
|
|
74b768bfc2 | ||
|
|
9b703078dd | ||
|
|
43e93414ea | ||
|
|
6c3a439455 | ||
|
|
a7bc2fcba8 | ||
|
|
417c8887b9 | ||
|
|
47feb73cdf | ||
|
|
0aa1bacbe7 | ||
|
|
63512f0512 | ||
|
|
a32251cab4 | ||
|
|
2ca8ae8e5f | ||
|
|
2d466f343d | ||
|
|
4596dc39ce | ||
|
|
605882582e | ||
|
|
a5876e5231 | ||
|
|
e127579ae6 | ||
|
|
b3be6792fd | ||
|
|
6c25f0cf4b | ||
|
|
a0bff4abab | ||
|
|
82410c58ed | ||
|
|
3a87cfce3e | ||
|
|
15ffd8e6d8 | ||
|
|
33e16df586 | ||
|
|
4432c1a2ea | ||
|
|
45540561cc | ||
|
|
f90753c69b | ||
|
|
bab914c497 | ||
|
|
d3bb69a0ac | ||
|
|
e356af2dc4 | ||
|
|
e9e7b97c48 | ||
|
|
1c7d6f8bc6 | ||
|
|
82a0e56eb2 | ||
|
|
54fd2c6773 | ||
|
|
fb4c7db25e | ||
|
|
c85af36116 | ||
|
|
780b5ca8bd | ||
|
|
20ad7ca437 | ||
|
|
d39036de2a | ||
|
|
4ac4ee643e | ||
|
|
47f35a06e6 | ||
|
|
77f7ce82c0 | ||
|
|
10b74d0703 | ||
|
|
393820e77a | ||
|
|
1b8805bc6f | ||
|
|
07dc83670b | ||
|
|
1429be3a19 | ||
|
|
4b69a600e5 | ||
|
|
ce9a5528bc | ||
|
|
2ea3fa36d2 | ||
|
|
14262b158d | ||
|
|
2fd013b593 | ||
|
|
1325844539 | ||
|
|
3a21605b05 | ||
|
|
cea9ff30dc | ||
|
|
63da2e1ec6 | ||
|
|
083deec663 | ||
|
|
8b91c227f6 | ||
|
|
06bd1f5d48 | ||
|
|
a7f0a471ed | ||
|
|
2a7aa46b77 | ||
|
|
c04b002d4e | ||
|
|
46840df1ad | ||
|
|
aaf0dea8dc | ||
|
|
abaafc2d68 | ||
|
|
c97556de65 | ||
|
|
bbf7504303 | ||
|
|
126468a5dd | ||
|
|
8280899ce1 | ||
|
|
b15c9f7419 | ||
|
|
2e8ef66496 |
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -15,6 +15,8 @@ kittens/diff/options/parse.py linguist-generated=true
|
||||
glfw/*.c linguist-vendored=true
|
||||
glfw/*.h linguist-vendored=true
|
||||
kittens/unicode_input/names.h linguist-generated=true
|
||||
tools/wcswidth/std.go linguist-generated=true
|
||||
|
||||
*.py text diff=python
|
||||
*.m text diff=objc
|
||||
*.go text diff=go
|
||||
|
||||
9
.github/workflows/ci.py
vendored
9
.github/workflows/ci.py
vendored
@@ -30,8 +30,9 @@ def install_deps():
|
||||
print('Installing kitty dependencies...')
|
||||
sys.stdout.flush()
|
||||
if is_macos:
|
||||
items = (x.split()[1].strip('"') for x in open('Brewfile').readlines() if x.strip().startswith('brew '))
|
||||
items = [x.split()[1].strip('"') for x in open('Brewfile').readlines() if x.strip().startswith('brew ')]
|
||||
openssl = 'openssl'
|
||||
items.remove('go') # already installed by ci.yml
|
||||
import ssl
|
||||
if ssl.OPENSSL_VERSION_INFO[0] == 1:
|
||||
openssl += '@1.1'
|
||||
@@ -129,6 +130,12 @@ def main():
|
||||
package_kitty()
|
||||
elif action == 'test':
|
||||
test_kitty()
|
||||
elif action == 'gofmt':
|
||||
q = subprocess.check_output('gofmt -s -l tools'.split())
|
||||
if q.strip():
|
||||
q = '\n'.join(filter(lambda x: not x.rstrip().endswith('_generated.go'), q.decode().strip().splitlines())).strip()
|
||||
if q:
|
||||
raise SystemExit(q)
|
||||
else:
|
||||
raise SystemExit(f'Unknown action: {action}')
|
||||
|
||||
|
||||
155
.github/workflows/ci.yml
vendored
155
.github/workflows/ci.yml
vendored
@@ -5,6 +5,7 @@ env:
|
||||
ASAN_OPTIONS: leak_check_at_exit=0
|
||||
LC_ALL: en_US.UTF-8
|
||||
LANG: en_US.UTF-8
|
||||
GO_INSTALL_VERSION: ">=1.19.0"
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
@@ -44,20 +45,26 @@ jobs:
|
||||
cc: gcc
|
||||
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Set up Python ${{ matrix.pyver }}
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: ${{ matrix.pyver }}
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 10
|
||||
|
||||
- name: Build kitty
|
||||
run: python .github/workflows/ci.py build
|
||||
- name: Set up Python ${{ matrix.pyver }}
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: ${{ matrix.pyver }}
|
||||
|
||||
- name: Test kitty
|
||||
run: python .github/workflows/ci.py test
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_INSTALL_VERSION }}
|
||||
|
||||
- name: Build kitty
|
||||
run: python .github/workflows/ci.py build
|
||||
|
||||
- name: Test kitty
|
||||
run: python .github/workflows/ci.py test
|
||||
|
||||
linux-package:
|
||||
name: Linux package
|
||||
@@ -65,39 +72,53 @@ jobs:
|
||||
env:
|
||||
CFLAGS: -funsigned-char
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0 # needed for :commit: docs role
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0 # needed for :commit: docs role
|
||||
|
||||
- name: Test for trailing whitespace
|
||||
run: if grep -Inr '\s$' kitty kitty_tests kittens docs *.py *.asciidoc *.rst .gitattributes .gitignore; then echo Trailing whitespace found, aborting.; exit 1; fi
|
||||
- name: Test for trailing whitespace
|
||||
run: if grep -Inr '\s$' kitty kitty_tests kittens docs *.py *.asciidoc *.rst *.go .gitattributes .gitignore; then echo Trailing whitespace found, aborting.; exit 1; fi
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: "3.10"
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- name: Install build-only deps
|
||||
run: pip install -r docs/requirements.txt flake8 mypy types-requests types-docutils
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_INSTALL_VERSION }}
|
||||
|
||||
- name: Run pyflakes
|
||||
run: python -m flake8 --count .
|
||||
- name: Install build-only deps
|
||||
run: python -m pip install -r docs/requirements.txt ruff mypy types-requests types-docutils
|
||||
|
||||
- name: Build kitty package
|
||||
run: python .github/workflows/ci.py package
|
||||
- name: Run ruff
|
||||
run: ruff .
|
||||
|
||||
- name: Build kitty
|
||||
run: python setup.py build --debug
|
||||
- name: Run gofmt
|
||||
run: go version && python .github/workflows/ci.py gofmt
|
||||
|
||||
- name: Run mypy
|
||||
run: ./test.py mypy
|
||||
- name: Build kitty package
|
||||
run: python .github/workflows/ci.py package
|
||||
|
||||
- name: Build man page
|
||||
run: make FAIL_WARN=1 man
|
||||
- name: Build kitty
|
||||
run: python setup.py build --debug
|
||||
|
||||
- name: Build HTML docs
|
||||
run: make FAIL_WARN=1 html
|
||||
- name: Build static kitty-tool
|
||||
run: python setup.py build-static-binaries
|
||||
|
||||
- name: Run mypy
|
||||
run: which python && python -m mypy --version && ./test.py mypy
|
||||
|
||||
- name: Run go vet
|
||||
run: go version && go vet ./...
|
||||
|
||||
- name: Build man page
|
||||
run: make FAIL_WARN=1 man
|
||||
|
||||
- name: Build HTML docs
|
||||
run: make FAIL_WARN=1 html
|
||||
|
||||
bundle:
|
||||
name: Bundle test (${{ matrix.os }})
|
||||
@@ -108,42 +129,52 @@ jobs:
|
||||
env:
|
||||
KITTY_BUNDLE: 1
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@master
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@master
|
||||
with:
|
||||
fetch-depth: 10
|
||||
|
||||
- name: Build kitty
|
||||
run: which python3 && python3 .github/workflows/ci.py build
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_INSTALL_VERSION }}
|
||||
|
||||
- name: Test kitty
|
||||
run: python3 .github/workflows/ci.py test
|
||||
- name: Build kitty
|
||||
run: which python3 && python3 .github/workflows/ci.py build
|
||||
|
||||
- name: Test kitty
|
||||
run: python3 .github/workflows/ci.py test
|
||||
|
||||
brew:
|
||||
name: macOS Brew
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@master
|
||||
with:
|
||||
fetch-depth: 0 # needed for :commit: docs role
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@master
|
||||
with:
|
||||
fetch-depth: 0 # needed for :commit: docs role
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: "3.10"
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- name: Build kitty
|
||||
run: python3 .github/workflows/ci.py build
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_INSTALL_VERSION }}
|
||||
|
||||
- name: Test kitty
|
||||
run: python3 .github/workflows/ci.py test
|
||||
- name: Build kitty
|
||||
run: python3 .github/workflows/ci.py build
|
||||
|
||||
- name: Install deps for docs
|
||||
run: python3 -m pip install -r docs/requirements.txt
|
||||
- name: Test kitty
|
||||
run: python3 .github/workflows/ci.py test
|
||||
|
||||
- name: Builds docs
|
||||
run: make FAIL_WARN=1 docs
|
||||
- name: Install deps for docs
|
||||
run: python3 -m pip install -r docs/requirements.txt
|
||||
|
||||
- name: Build kitty package
|
||||
run: python3 .github/workflows/ci.py package
|
||||
- name: Builds docs
|
||||
run: make FAIL_WARN=1 docs
|
||||
|
||||
- name: Build kitty package
|
||||
run: python3 .github/workflows/ci.py package
|
||||
|
||||
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
@@ -22,6 +22,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ">=1.19.0"
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
@@ -31,7 +37,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: python, c
|
||||
setup-python-dependencies: false
|
||||
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -9,10 +9,10 @@
|
||||
/build/
|
||||
/linux-package/
|
||||
/kitty.app/
|
||||
/compile_commands.json
|
||||
/link_commands.json
|
||||
/glad/out/
|
||||
/kitty/launcher/kitty*
|
||||
/kitty/launcher/kitt*
|
||||
/tools/cmd/at/*_generated.go
|
||||
*_generated.go
|
||||
/*.dSYM/
|
||||
__pycache__/
|
||||
/glfw/wayland-*-client-protocol.[ch]
|
||||
@@ -20,5 +20,6 @@ __pycache__/
|
||||
/docs/generated/
|
||||
/.mypy_cache
|
||||
.DS_Store
|
||||
.cache
|
||||
bypy/b
|
||||
bypy/virtual-machines.conf
|
||||
|
||||
1
Brewfile
1
Brewfile
@@ -5,3 +5,4 @@ brew "python"
|
||||
brew "imagemagick"
|
||||
brew "harfbuzz"
|
||||
brew "sphinx-doc"
|
||||
brew "go"
|
||||
|
||||
@@ -7,6 +7,11 @@ When reporting a bug, provide full details of your environment, that means, at
|
||||
a minimum, kitty version, OS and OS version, kitty config (ideally a minimal
|
||||
config to reproduce the issue with).
|
||||
|
||||
Note that bugs and feature requests are often closed quickly as they are either
|
||||
fixed or deemed wontfix/invalid. In my experience, this is the only scaleable way to
|
||||
manage a bug tracker. Feel free to continue to post to a closed bug report
|
||||
if you would like to discuss the issue further. Being closed does not mean you
|
||||
will not get any more responses.
|
||||
|
||||
### Contributing code
|
||||
|
||||
|
||||
@@ -11,9 +11,8 @@ import sys
|
||||
import tempfile
|
||||
from contextlib import suppress
|
||||
|
||||
from bypy.constants import (
|
||||
LIBDIR, PREFIX, PYTHON, SRC as KITTY_DIR, ismacos, worker_env
|
||||
)
|
||||
from bypy.constants import LIBDIR, PREFIX, PYTHON, ismacos, worker_env
|
||||
from bypy.constants import SRC as KITTY_DIR
|
||||
from bypy.utils import run_shell, walk
|
||||
|
||||
|
||||
@@ -63,20 +62,31 @@ def build_frozen_launcher(extra_include_dirs):
|
||||
|
||||
def run_tests(kitty_exe):
|
||||
with tempfile.TemporaryDirectory() as tdir:
|
||||
env = {
|
||||
uenv = {
|
||||
'KITTY_CONFIG_DIRECTORY': os.path.join(tdir, 'conf'),
|
||||
'KITTY_CACHE_DIRECTORY': os.path.join(tdir, 'cache')
|
||||
}
|
||||
[os.mkdir(x) for x in env.values()]
|
||||
cmd = [kitty_exe, '+runpy', 'from kitty_tests.main import run_tests; run_tests()']
|
||||
[os.mkdir(x) for x in uenv.values()]
|
||||
env = os.environ.copy()
|
||||
env.update(uenv)
|
||||
cmd = [kitty_exe, '+runpy', 'from kitty_tests.main import run_tests; run_tests(report_env=True)']
|
||||
print(*map(shlex.quote, cmd), flush=True)
|
||||
if subprocess.call(cmd, env=env) != 0:
|
||||
print('Checking of kitty build failed', file=sys.stderr)
|
||||
if subprocess.call(cmd, env=env, cwd=build_frozen_launcher.writeable_src_dir) != 0:
|
||||
print('Checking of kitty build failed, in directory:', build_frozen_launcher.writeable_src_dir, file=sys.stderr)
|
||||
os.chdir(os.path.dirname(kitty_exe))
|
||||
run_shell()
|
||||
raise SystemExit('Checking of kitty build failed')
|
||||
|
||||
|
||||
def build_frozen_tools(kitty_exe):
|
||||
cmd = SETUP_CMD + ['--prefix', os.path.dirname(kitty_exe)] + ['build-frozen-tools']
|
||||
if run(*cmd, cwd=build_frozen_launcher.writeable_src_dir) != 0:
|
||||
print('Building of frozen kitten failed', file=sys.stderr)
|
||||
os.chdir(KITTY_DIR)
|
||||
run_shell()
|
||||
raise SystemExit('Building of kitten launcher failed')
|
||||
|
||||
|
||||
def sanitize_source_folder(path: str) -> None:
|
||||
for q in walk(path):
|
||||
if os.path.splitext(q)[1] not in ('.py', '.glsl', '.ttf', '.otf'):
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
image 'https://cloud-images.ubuntu.com/releases/bionic/release/ubuntu-18.04-server-cloudimg-{}.img'
|
||||
|
||||
deps 'bison flex libxcursor-dev libxrandr-dev libxi-dev libxinerama-dev libgl1-mesa-dev libxcb-xkb-dev libfontconfig1-dev libdbus-1-dev'
|
||||
deps 'bison flex libxcursor-dev libxrandr-dev libxi-dev libxinerama-dev libgl1-mesa-dev libx11-xcb-dev libxcb-xkb-dev libfontconfig1-dev libdbus-1-dev'
|
||||
|
||||
@@ -10,12 +10,8 @@ import subprocess
|
||||
import tarfile
|
||||
import time
|
||||
|
||||
from bypy.constants import (
|
||||
OUTPUT_DIR, PREFIX, is64bit, python_major_minor_version
|
||||
)
|
||||
from bypy.freeze import (
|
||||
extract_extension_modules, freeze_python, path_to_freeze_dir
|
||||
)
|
||||
from bypy.constants import OUTPUT_DIR, PREFIX, is64bit, python_major_minor_version
|
||||
from bypy.freeze import extract_extension_modules, freeze_python, path_to_freeze_dir
|
||||
from bypy.utils import get_dll_path, mkdtemp, py_compile, walk
|
||||
|
||||
j = os.path.join
|
||||
@@ -238,10 +234,12 @@ def main():
|
||||
files = find_binaries(env)
|
||||
fix_permissions(files)
|
||||
add_ca_certs(env)
|
||||
kitty_exe = os.path.join(env.base, 'bin', 'kitty')
|
||||
iv['build_frozen_tools'](kitty_exe)
|
||||
if not args.dont_strip:
|
||||
strip_binaries(files)
|
||||
if not args.skip_tests:
|
||||
iv['run_tests'](os.path.join(env.base, 'bin', 'kitty'))
|
||||
iv['run_tests'](kitty_exe)
|
||||
create_tarfile(env, args.compression_level)
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Requires installation of XCode 10.3 and Python 3 and
|
||||
# Requires installation of XCode 10.3 and go 1.19 and Python 3 and
|
||||
# python3 -m pip install certifi
|
||||
|
||||
vm_name 'macos-kitty'
|
||||
|
||||
@@ -13,16 +13,9 @@ import tempfile
|
||||
import zipfile
|
||||
|
||||
from bypy.constants import PREFIX, PYTHON, SW, python_major_minor_version
|
||||
from bypy.freeze import (
|
||||
extract_extension_modules, freeze_python, path_to_freeze_dir
|
||||
)
|
||||
from bypy.macos_sign import (
|
||||
codesign, create_entitlements_file, make_certificate_useable, notarize_app,
|
||||
verify_signature
|
||||
)
|
||||
from bypy.utils import (
|
||||
current_dir, mkdtemp, py_compile, run_shell, timeit, walk
|
||||
)
|
||||
from bypy.freeze import extract_extension_modules, freeze_python, path_to_freeze_dir
|
||||
from bypy.macos_sign import codesign, create_entitlements_file, make_certificate_useable, notarize_app, verify_signature
|
||||
from bypy.utils import current_dir, mkdtemp, py_compile, run_shell, timeit, walk
|
||||
|
||||
iv = globals()['init_env']
|
||||
kitty_constants = iv['kitty_constants']
|
||||
@@ -113,6 +106,9 @@ def do_sign(app_dir):
|
||||
codesign(fw)
|
||||
items = set(os.listdir('.')) - fw
|
||||
codesign(expand_dirs(items))
|
||||
# Sign kitten
|
||||
with current_dir('MacOS'):
|
||||
codesign('kitten')
|
||||
|
||||
# Now sign the main app
|
||||
codesign(app_dir)
|
||||
@@ -171,6 +167,7 @@ class Freeze(object):
|
||||
self.add_misc_libraries()
|
||||
self.freeze_python()
|
||||
self.add_ca_certs()
|
||||
self.build_frozen_tools()
|
||||
if not self.dont_strip:
|
||||
self.strip_files()
|
||||
if not self.skip_tests:
|
||||
@@ -378,6 +375,10 @@ class Freeze(object):
|
||||
if f.endswith('.so') or f.endswith('.dylib'):
|
||||
self.fix_dependencies_in_lib(f)
|
||||
|
||||
@flush
|
||||
def build_frozen_tools(self):
|
||||
iv['build_frozen_tools'](join(self.contents_dir, 'MacOS', 'kitty'))
|
||||
|
||||
@flush
|
||||
def add_site_packages(self):
|
||||
print('\nAdding site-packages')
|
||||
|
||||
@@ -1 +1 @@
|
||||
to_vm_excludes '/build /dist /kitty/launcher/kitty /.build-cache /tags __pycache__ /*_commands.json *.so *.pyd *.pyc'
|
||||
to_vm_excludes '/build /dist /kitty/launcher/kitty* /.build-cache /tags __pycache__ /*_commands.json *.so *.pyd *.pyc *_generated.go'
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
{
|
||||
"name": "zlib",
|
||||
"unix": {
|
||||
"filename": "zlib-1.2.11.tar.xz",
|
||||
"hash": "sha256:4ff941449631ace0d4d203e3483be9dbc9da454084111f97ea0a2114e19bf066",
|
||||
"filename": "zlib-1.2.13.tar.xz",
|
||||
"hash": "sha256:d14c38e313afc35a9a8760dadf26042f51ea0f5d154b0630a31da0540107fb98",
|
||||
"urls": ["https://zlib.net/{filename}"]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -21,10 +21,17 @@ kitty/options/parse.py
|
||||
kitty/options/to-c-generated.h
|
||||
kittens/diff/options/types.py
|
||||
kittens/diff/options/parse.py
|
||||
tools/wcswidth/std.go
|
||||
'''
|
||||
|
||||
ignored = []
|
||||
for line in subprocess.check_output(['git', 'status', '--ignored', '--porcelain']).decode().splitlines():
|
||||
if line.startswith('!! '):
|
||||
ignored.append(line[3:])
|
||||
files_to_exclude += '\n'.join(ignored)
|
||||
|
||||
p = subprocess.Popen([
|
||||
'cloc', '--exclude-list-file', '/dev/stdin', 'kitty', 'kittens'
|
||||
'cloc', '--exclude-list-file', '/dev/stdin', 'kitty', 'kittens', 'tools',
|
||||
], stdin=subprocess.PIPE)
|
||||
p.communicate(files_to_exclude.encode('utf-8'))
|
||||
raise SystemExit(p.wait())
|
||||
|
||||
@@ -27,6 +27,11 @@ Browse scrollback in less :sc:`show_scrollback`
|
||||
Browse last cmd output :sc:`show_last_command_output` (see :ref:`shell_integration`)
|
||||
========================= =======================
|
||||
|
||||
The scroll actions only take effect when the terminal is in the main screen.
|
||||
When the alternate screen is active (for example when using a full screen
|
||||
program like an editor) the key events are instead passed to program running in the
|
||||
terminal.
|
||||
|
||||
Tabs
|
||||
~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -39,13 +39,14 @@ Run-time dependencies:
|
||||
* ``freetype`` (not needed on macOS)
|
||||
* ``fontconfig`` (not needed on macOS)
|
||||
* ``libcanberra`` (not needed on macOS)
|
||||
* ``ImageMagick`` (optional, needed to use the ``kitty +kitten icat`` tool to display images in the terminal)
|
||||
* ``ImageMagick`` (optional, needed to display uncommon image formats in the terminal)
|
||||
* ``pygments`` (optional, needed for syntax highlighting in ``kitty +kitten diff``)
|
||||
|
||||
|
||||
Build-time dependencies:
|
||||
|
||||
* ``gcc`` or ``clang``
|
||||
* ``go >= 1.19`` (see :file:`go.mod` for go packages used during building)
|
||||
* ``pkg-config``
|
||||
* For building on Linux in addition to the above dependencies you might also
|
||||
need to install the following packages, if they are not already installed by
|
||||
|
||||
@@ -35,6 +35,71 @@ mouse anywhere in the current command to move the cursor there. See
|
||||
Detailed list of changes
|
||||
-------------------------------------
|
||||
|
||||
0.27.0 [2023-01-31]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- A new statically compiled, standalone executable, ``kitten`` (written in Go)
|
||||
that can be used on all UNIX-like servers for remote control (``kitten @``),
|
||||
viewing images (``kitten icat``), manipulating the clipboard (``kitten clipboard``), etc.
|
||||
|
||||
- :doc:`clipboard kitten </kittens/clipboard>`: Allow copying arbitrary data types to/from the clipboard, not just plain text
|
||||
|
||||
- Speed up the ``kitty @`` executable by ~10x reducing the time for typical
|
||||
remote control commands from ~50ms to ~5ms
|
||||
|
||||
- icat kitten: Speed up by using POSIX shared memory when possible to transfer
|
||||
image data to the terminal. Also support common image formats
|
||||
GIF/PNG/JPEG/WEBP/TIFF/BMP out of the box without needing ImageMagick.
|
||||
|
||||
- Option :opt:`show_hyperlink_targets` to show the target of terminal hyperlinks when hovering over them with the mouse (:pull:`5830`)
|
||||
|
||||
- Keyboard protocol: Remove ``CSI R`` from the allowed encodings of the :kbd:`F3` key as it conflicts with the *Cursor Position Report* escape code (:disc:`5813`)
|
||||
|
||||
- Allow using the cwd of the original process for :option:`launch --cwd` (:iss:`5672`)
|
||||
|
||||
- Session files: Expand environment variables (:disc:`5917`)
|
||||
|
||||
- Pass key events mapped to scroll actions to the program running in the terminal when the terminal is in alternate screen mode (:iss:`5839`)
|
||||
|
||||
- Implement :ref:`edit-in-kitty <edit_file>` using the new ``kitten`` static executable (:iss:`5546`, :iss:`5630`)
|
||||
|
||||
- Add an option :opt:`background_tint_gaps` to control background image tinting for window gaps (:iss:`5596`)
|
||||
|
||||
- A new option :opt:`undercurl_style` to control the rendering of undercurls (:pull:`5883`)
|
||||
|
||||
- Bash integration: Fix ``clone-in-kitty`` not working on bash >= 5.2 if an environment variable values contain newlines or other special characters (:iss:`5629`)
|
||||
|
||||
- A new :ac:`sleep` action useful in combine based mappings to make kitty sleep before executing the next action
|
||||
|
||||
- Wayland GNOME: Workaround for latest mutter release breaking full screen for semi-transparent kitty windows (:iss:`5677`)
|
||||
|
||||
- A new option :opt:`tab_title_max_length` to limit the length of tab (:iss:`5718`)
|
||||
|
||||
- When drawing the tab bar have the default left and right margins drawn in a color matching the neighboring tab (:iss:`5719`)
|
||||
|
||||
- When using the :code:`include` directive in :file:`kitty.conf` make the environment variable :envvar:`KITTY_OS` available for OS specific config
|
||||
|
||||
- Wayland: Fix signal handling not working with some GPU drivers (:iss:`4636`)
|
||||
|
||||
- Remote control: When matching windows allow using negative id numbers to match recently created windows (:iss:`5753`)
|
||||
|
||||
- ZSH Integration: Bind :kbd:`alt+left` and :kbd:`alt+right` to move by word if not already bound. This mimics the default bindings in Terminal.app (:iss:`5793`)
|
||||
|
||||
- macOS: Allow to customize :sc:`Hide <hide_macos_app>`, :sc:`Hide Others <hide_macos_other_apps>`, :sc:`Minimize <minimize_macos_window>`, and :sc:`Quit <quit>` global menu shortcuts. Note that :opt:`clear_all_shortcuts` will remove these shortcuts now (:iss:`948`)
|
||||
|
||||
- When a multi-key sequence does not match any action, send all key events to the child program (:pull:`5841`)
|
||||
|
||||
- broadcast kitten: Allow pressing a key to stop echoing of input into the broadcast window itself (:disc:`5868`)
|
||||
|
||||
- When reporting unused activity in a window, ignore activity that occurs soon after a window resize (:iss:`5881`)
|
||||
|
||||
- Fix using :opt:`cursor` = ``none`` not working on text that has reverse video (:iss:`5897`)
|
||||
|
||||
- Fix ssh kitten not working on FreeBSD (:iss:`5928`)
|
||||
|
||||
- macOS: Export kitty selected text to the system for use with services that accept it (patch by Sertaç Ö. Yıldız)
|
||||
|
||||
|
||||
0.26.5 [2022-11-07]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -56,6 +121,7 @@ Detailed list of changes
|
||||
|
||||
- Remote control: When matching window by `state:focused` and no window currently has keyboard focus, match the window belonging to the OS window that was last focused (:iss:`5602`)
|
||||
|
||||
|
||||
0.26.4 [2022-10-17]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -128,6 +194,7 @@ Detailed list of changes
|
||||
code execution if the user clicked on a notification popup from a malicious
|
||||
source. Thanks to Carter Sande for discovering this vulnerability.
|
||||
|
||||
|
||||
0.26.1 [2022-08-30]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -137,6 +204,7 @@ Detailed list of changes
|
||||
|
||||
- Allow specifying a title when using the :ac:`set_tab_title` action (:iss:`5441`)
|
||||
|
||||
|
||||
0.26.0 [2022-08-29]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -1623,6 +1691,7 @@ Detailed list of changes
|
||||
|
||||
- Fix :option:`--title` not being applied at window creation time (:iss:`2570`)
|
||||
|
||||
|
||||
0.17.2 [2020-03-29]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -1807,6 +1876,7 @@ Detailed list of changes
|
||||
- When windows are semi-transparent and all contain graphics, correctly render
|
||||
them. (:iss:`2310`)
|
||||
|
||||
|
||||
0.15.1 [2019-12-21]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -1915,6 +1985,7 @@ Detailed list of changes
|
||||
|
||||
- Use selection foreground color for underlines as well (:iss:`1982`)
|
||||
|
||||
|
||||
0.14.4 [2019-08-31]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -2221,6 +2292,7 @@ Detailed list of changes
|
||||
- Mouse selection: When extending by word, fix extending selection to non-word
|
||||
characters not working well (:iss:`1616`)
|
||||
|
||||
|
||||
0.13.3 [2019-01-19]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -2314,6 +2386,7 @@ Detailed list of changes
|
||||
- Fix resizing window smaller and then restoring causing some wrapped lines to not
|
||||
be properly unwrapped (:iss:`1206`)
|
||||
|
||||
|
||||
0.13.0 [2018-12-05]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -2411,6 +2484,7 @@ Detailed list of changes
|
||||
- Fix hover detection of URLs not working when hovering over the first colon
|
||||
and slash characters in short URLs (:iss:`1201`)
|
||||
|
||||
|
||||
0.12.3 [2018-09-29]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -2487,6 +2561,7 @@ Detailed list of changes
|
||||
- Fix using :opt:`focus_follows_mouse` causing text selection with the
|
||||
mouse to malfunction when using multiple kitty windows (:iss:`1002`)
|
||||
|
||||
|
||||
0.12.1 [2018-09-08]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -2530,6 +2605,7 @@ Detailed list of changes
|
||||
- macOS: Diff kitten: Fix syntax highlighting not working because of
|
||||
a bug in the 0.12.0 macOS package
|
||||
|
||||
|
||||
0.12.0 [2018-09-01]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -3040,6 +3116,7 @@ Detailed list of changes
|
||||
|
||||
- Fix a crash when getting the contents of the scrollback buffer as text
|
||||
|
||||
|
||||
0.8.1 [2018-03-09]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -3117,6 +3194,7 @@ Detailed list of changes
|
||||
- Browsing the scrollback buffer now happens in an overlay window instead of a
|
||||
new window/tab.
|
||||
|
||||
|
||||
0.7.1 [2018-01-31]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
160
docs/clipboard.rst
Normal file
160
docs/clipboard.rst
Normal file
@@ -0,0 +1,160 @@
|
||||
Copying all data types to the clipboard
|
||||
==============================================
|
||||
|
||||
There already exists an escape code to allow terminal programs to
|
||||
read/write plain text data from the system clipboard, *OSC 52*.
|
||||
kitty introduces a more advanced protocol that supports:
|
||||
|
||||
* Copy arbitrary data including images, rich text documents, etc.
|
||||
* Allow terminals to ask the user for permission to access the clipboard and
|
||||
report permission denied
|
||||
|
||||
The escape code is *OSC 5522*, an extension of *OSC 52*. The basic format
|
||||
of the escape code is::
|
||||
|
||||
<OSC>5522;metadata;payload<ST>
|
||||
|
||||
Here, *metadata* is a colon separated list of key-value pairs and payload is
|
||||
base64 encoded data. :code:`OSC` is :code:`<ESC>[`.
|
||||
:code:`ST` is the string terminator, :code:`<ESC>\\`.
|
||||
|
||||
Reading data from the system clipboard
|
||||
----------------------------------------
|
||||
|
||||
To read data from the system clipboard, the escape code is::
|
||||
|
||||
<OSC>5522;type=read;<base 64 encoded space separated list of mime types to read><ST>
|
||||
|
||||
For example, to read plain text and PNG data, the payload would be::
|
||||
|
||||
text/plain image/png
|
||||
|
||||
encoded as base64. To read from the primary selection instead of the
|
||||
clipboard, add the key ``loc=primary`` to the metadata section.
|
||||
|
||||
To get the list of MIME types available on the clipboard the payload must be
|
||||
just a period (``.``), encoded as base64.
|
||||
|
||||
The terminal emulator will reply with a sequence of escape codes of the form::
|
||||
|
||||
<OSC>5522;type=read:status=OK<ST>
|
||||
<OSC>5522;type=read:status=DATA:mime=<base 64 encoded mime type>;<base64 encoded data><ST>
|
||||
<OSC>5522;type=read:status=DATA:mime=<base 64 encoded mime type>;<base64 encoded data><ST>
|
||||
.
|
||||
.
|
||||
.
|
||||
<OSC>5522;type=read:status=DONE<ST>
|
||||
|
||||
Here, the ``status=DATA`` packets deliver the data (as base64 encoded bytes)
|
||||
associated with each MIME type. The terminal emulator should chunk up the data
|
||||
for an individual type. A recommended size for each chunk is 4096 bytes. All
|
||||
the chunks for a given type must be transmitted sequentially and only once they
|
||||
are done the chunks for the next type, if any, should be sent. The end of data
|
||||
is indicated by a ``status=DONE`` packet.
|
||||
|
||||
If an error occurs, instead of the opening ``status=OK`` packet the terminal
|
||||
must send a ``status=ERRORCODE`` packet. The error code must be one of:
|
||||
|
||||
``status=ENOSYS``
|
||||
Sent if the requested clipboard type is not available. For example, primary
|
||||
selection is not available on all systems and ``loc=primary`` was used.
|
||||
|
||||
``status=EPERM``
|
||||
Sent if permission to read from the clipboard was denied by the system or
|
||||
the user.
|
||||
|
||||
``status=EBUSY``
|
||||
Sent if there is some temporary problem, such as multiple clients in a
|
||||
multiplexer trying to access the clipboard simultaneously.
|
||||
|
||||
Terminals should ask the user for permission before allowing a read request.
|
||||
However, if a read request only wishes to list the available data types on the
|
||||
clipboard, it should be allowed without a permission prompt. This is so that
|
||||
the user is not presented with a double permission prompt for reading the
|
||||
available MIME types and then reading the actual data.
|
||||
|
||||
|
||||
Writing data to the system clipboard
|
||||
----------------------------------------
|
||||
|
||||
To write data to the system clipboard, the terminal programs sends the
|
||||
following sequence of packets::
|
||||
|
||||
<OSC>5522;type=write<ST>
|
||||
<OSC>5522;type=wdata:mime=<base64 encoded mime type>;<base 64 encoded chunk of data for this type><ST>
|
||||
<OSC>5522;type=wdata:mime=<base64 encoded mime type>;<base 64 encoded chunk of data for this type><ST>
|
||||
.
|
||||
.
|
||||
.
|
||||
<OSC>5522;type=wdata<ST>
|
||||
|
||||
The final packet with no mime and no data indicates end of transmission. The
|
||||
data for every MIME type should be split into chunks of no more than 4096
|
||||
bytes. All the chunks for a given MIME type must be sent sequentially, before
|
||||
sending chunks for the next MIME type. After the transmission is complete, the
|
||||
terminal replies with a single packet indicating success::
|
||||
|
||||
<OSC>5522;type=write:status=DONE<ST>
|
||||
|
||||
If an error occurs the terminal can, at any time, send an error packet of the
|
||||
form::
|
||||
|
||||
<OSC>5522;type=write:status=ERRORCODE<ST>
|
||||
|
||||
Here ``ERRORCODE`` must be one of:
|
||||
|
||||
``status=EIO``
|
||||
An I/O error occurred while processing the data
|
||||
``status=EINVAL``
|
||||
One of the packets was invalid, usually because of invalid base64 encoding.
|
||||
``status=ENOSYS``
|
||||
The client asked to write to the primary selection with (``loc=primary``) and that is not
|
||||
available on the system
|
||||
``status=EPERM``
|
||||
Sent if permission to write to the clipboard was denied by the system or
|
||||
the user.
|
||||
``status=EBUSY``
|
||||
Sent if there is some temporary problem, such as multiple clients in a
|
||||
multiplexer trying to access the clipboard simultaneously.
|
||||
|
||||
Once an error occurs, the terminal must ignore all further OSC 5522 write related packets until it
|
||||
sees the start of a new write with a ``type=write`` packet.
|
||||
|
||||
The client can send to the primary selection instead of the clipboard by adding
|
||||
``loc=primary`` to the initial ``type=write`` packet.
|
||||
|
||||
Finally, clients have the ability to *alias* MIME types when sending data to
|
||||
the clipboard. To do that, the client must send a ``type=walias`` packet of the
|
||||
form::
|
||||
|
||||
<OSC>5522;type=walias;mime=<base64 encoded target MIME type>;<base64 encoded, space separated list of aliases><ST>
|
||||
|
||||
The effect of an alias is that the system clipboard will make available all the
|
||||
aliased MIME types, with the same data as was transmitted for the target MIME
|
||||
type. This saves bandwidth, allowing the client to only transmit one copy of
|
||||
the data, but create multiple references to it in the system clipboard. Alias
|
||||
packets can be sent anytime after the initial write packet and before the end
|
||||
of data packet.
|
||||
|
||||
|
||||
Support for terminal multiplexers
|
||||
------------------------------------
|
||||
|
||||
Since this protocol involves two way communication between the terminal
|
||||
emulator and the client program, multiplexers need a way to know which window
|
||||
to send responses from the terminal to. In order to make this possible, the
|
||||
metadata portion of this escape code includes an optional ``id`` field. If
|
||||
present the terminal emulator must send it back unchanged with every response.
|
||||
Valid ids must include only characters from the set: ``[a-zA-Z0-9-_+.]``. Any
|
||||
other characters must be stripped out from the id by the terminal emulator
|
||||
before retransmitting it.
|
||||
|
||||
Note that when using a terminal multiplexer it is possible for two different
|
||||
programs to tread on each others clipboard requests. This is fundamentally
|
||||
unavoidable since the system clipboard is a single global shared resource.
|
||||
However, there is an additional complication where responses form this protocol
|
||||
could get lost if, for instance, multiple write requests are received
|
||||
simultaneously. It is up to well designed multiplexers to ensure that only a
|
||||
single request is in flight at a time. The multiplexer can abort requests by
|
||||
sending back the ``EBUSY`` error code indicating some other window is trying
|
||||
to access the clipboard.
|
||||
20
docs/conf.py
20
docs/conf.py
@@ -18,9 +18,7 @@ from typing import Any, Callable, Dict, Iterable, List, Tuple
|
||||
from docutils import nodes
|
||||
from docutils.parsers.rst.roles import set_classes
|
||||
from pygments.lexer import RegexLexer, bygroups # type: ignore
|
||||
from pygments.token import ( # type: ignore
|
||||
Comment, Keyword, Literal, Name, Number, String, Whitespace
|
||||
)
|
||||
from pygments.token import Comment, Keyword, Literal, Name, Number, String, Whitespace # type: ignore
|
||||
from sphinx import addnodes, version_info
|
||||
from sphinx.util.logging import getLogger
|
||||
|
||||
@@ -215,10 +213,10 @@ def commit_role(
|
||||
|
||||
# CLI docs {{{
|
||||
def write_cli_docs(all_kitten_names: Iterable[str]) -> None:
|
||||
from kitty.cli import option_spec_as_rst
|
||||
from kitty.launch import options_spec as launch_options_spec
|
||||
from kittens.ssh.copy import option_text
|
||||
from kittens.ssh.options.definition import copy_message
|
||||
from kitty.cli import option_spec_as_rst
|
||||
from kitty.launch import options_spec as launch_options_spec
|
||||
with open('generated/ssh-copy.rst', 'w') as f:
|
||||
f.write(option_spec_as_rst(
|
||||
appname='copy', ospec=option_text, heading_char='^',
|
||||
@@ -255,6 +253,8 @@ if you specify a program-to-run you can use the special placeholder
|
||||
p('.. program::', 'kitty @', func.name)
|
||||
p('\n\n' + as_rst(*cli_params_for(func)))
|
||||
from kittens.runner import get_kitten_cli_docs
|
||||
from kitty.fast_data_types import wrapped_kitten_names
|
||||
|
||||
for kitten in all_kitten_names:
|
||||
data = get_kitten_cli_docs(kitten)
|
||||
if data:
|
||||
@@ -263,7 +263,11 @@ if you specify a program-to-run you can use the special placeholder
|
||||
p('.. program::', 'kitty +kitten', kitten)
|
||||
p('\nSource code for', kitten)
|
||||
p('-' * 72)
|
||||
p(f'\nThe source code for this kitten is `available on GitHub <https://github.com/kovidgoyal/kitty/tree/master/kittens/{kitten}>`_.')
|
||||
if kitten in wrapped_kitten_names():
|
||||
scurl = f'https://github.com/kovidgoyal/kitty/tree/master/tools/cmd/{kitten}'
|
||||
else:
|
||||
scurl = f'https://github.com/kovidgoyal/kitty/tree/master/kittens/{kitten}'
|
||||
p(f'\nThe source code for this kitten is `available on GitHub <{scurl}>`_.')
|
||||
p('\nCommand Line Interface')
|
||||
p('-' * 72)
|
||||
p('\n\n' + option_spec_as_rst(
|
||||
@@ -274,9 +278,7 @@ if you specify a program-to-run you can use the special placeholder
|
||||
|
||||
|
||||
def write_remote_control_protocol_docs() -> None: # {{{
|
||||
from kitty.rc.base import (
|
||||
RemoteCommand, all_command_names, command_for_name
|
||||
)
|
||||
from kitty.rc.base import RemoteCommand, all_command_names, command_for_name
|
||||
field_pat = re.compile(r'\s*([^:]+?)\s*:\s*(.+)')
|
||||
|
||||
def format_cmd(p: Callable[..., None], name: str, cmd: RemoteCommand) -> None:
|
||||
|
||||
@@ -27,11 +27,13 @@ line.
|
||||
|
||||
.. _include:
|
||||
|
||||
You can include secondary config files via the :code:`include` directive. If
|
||||
You can include secondary config files via the :code:`include` directive. If
|
||||
you use a relative path for :code:`include`, it is resolved with respect to the
|
||||
location of the current config file. Note that environment variables are
|
||||
expanded, so :code:`${USER}.conf` becomes :file:`name.conf` if
|
||||
:code:`USER=name`. Also, you can use :code:`globinclude` to include files
|
||||
:code:`USER=name`. A special environment variable :envvar:`KITTY_OS` is available,
|
||||
to detect the operating system. It is ``linux``, ``macos`` or ``bsd``.
|
||||
Also, you can use :code:`globinclude` to include files
|
||||
matching a shell glob pattern and :code:`envinclude` to include configuration
|
||||
from environment variables. For example::
|
||||
|
||||
|
||||
@@ -50,7 +50,8 @@ and the terminal emulator should hold off displaying it. A value of ``1`` means
|
||||
the notification is done, and should be displayed. You can specify the title or
|
||||
body multiple times and the terminal emulator will concatenate them, thereby
|
||||
allowing arbitrarily long text (terminal emulators are free to impose a sensible
|
||||
limit to avoid Denial-of-Service attacks).
|
||||
limit to avoid Denial-of-Service attacks). The size of the payload must be no
|
||||
longer than ``2048`` bytes, *before being encoded*.
|
||||
|
||||
Both the ``title`` and ``body`` payloads must be either UTF-8 encoded plain
|
||||
text with no embedded escape codes, or UTF-8 text that is Base64 encoded, in
|
||||
|
||||
175
docs/faq.rst
175
docs/faq.rst
@@ -27,96 +27,145 @@ turned off for specific symbols using :opt:`narrow_symbols`.
|
||||
Using a color theme with a background color does not work well in vim?
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
First make sure you have not changed the :envvar:`TERM` environment variable, it
|
||||
should be ``xterm-kitty``. vim uses *background color erase* even if the
|
||||
terminfo file does not contain the ``bce`` capability. This is a bug in vim. You
|
||||
can work around it by adding the following to your vimrc::
|
||||
Sadly, vim has very poor out-of-the-box detection for modern terminal features.
|
||||
Furthermore, it `recently broke detection even more <https://github.com/vim/vim/issues/11729>`__.
|
||||
It kind of, but not really, supports terminfo, except it overrides it with its own hard-coded
|
||||
values when it feels like it. Worst of all, it has no ability to detect modern
|
||||
features not present in terminfo, at all, even security sensitive ones like
|
||||
bracketed paste.
|
||||
|
||||
Thankfully, probably as a consequence of this lack of detection, vim allows users to
|
||||
configure these low level details. So, to make vim work well with any modern
|
||||
terminal, including kitty, add the following to your :file:`~/.vimrc`.
|
||||
|
||||
.. code-block:: vim
|
||||
|
||||
" Mouse support
|
||||
set mouse=a
|
||||
set ttymouse=sgr
|
||||
set balloonevalterm
|
||||
" Styled and colored underline support
|
||||
let &t_AU = "\e[58:5:%dm"
|
||||
let &t_8u = "\e[58:2:%lu:%lu:%lum"
|
||||
let &t_Us = "\e[4:2m"
|
||||
let &t_Cs = "\e[4:3m"
|
||||
let &t_ds = "\e[4:4m"
|
||||
let &t_Ds = "\e[4:5m"
|
||||
let &t_Ce = "\e[4:0m"
|
||||
" Strikethrough
|
||||
let &t_Ts = "\e[9m"
|
||||
let &t_Te = "\e[29m"
|
||||
" Truecolor support
|
||||
let &t_8f = "\e[38:2:%lu:%lu:%lum"
|
||||
let &t_8b = "\e[48:2:%lu:%lu:%lum"
|
||||
let &t_RF = "\e]10;?\e\\"
|
||||
let &t_RB = "\e]11;?\e\\"
|
||||
" Bracketed paste
|
||||
let &t_BE = "\e[?2004h"
|
||||
let &t_BD = "\e[?2004l"
|
||||
let &t_PS = "\e[200~"
|
||||
let &t_PE = "\e[201~"
|
||||
" Cursor control
|
||||
let &t_RC = "\e[?12$p"
|
||||
let &t_SH = "\e[%d q"
|
||||
let &t_RS = "\eP$q q\e\\"
|
||||
let &t_SI = "\e[5 q"
|
||||
let &t_SR = "\e[3 q"
|
||||
let &t_EI = "\e[1 q"
|
||||
let &t_VS = "\e[?12l"
|
||||
" Focus tracking
|
||||
let &t_fe = "\e[?1004h"
|
||||
let &t_fd = "\e[?1004l"
|
||||
execute "set <FocusGained>=\<Esc>[I"
|
||||
execute "set <FocusLost>=\<Esc>[O"
|
||||
" Window title
|
||||
let &t_ST = "\e[22;2t"
|
||||
let &t_RT = "\e[23;2t"
|
||||
|
||||
" vim hardcodes background color erase even if the terminfo file does
|
||||
" not contain bce. This causes incorrect background rendering when
|
||||
" using a color theme with a background color in terminals such as
|
||||
" kitty that do not support background color erase.
|
||||
let &t_ut=''
|
||||
|
||||
See :doc:`here <deccara>` for why |kitty| does not support background color
|
||||
erase.
|
||||
These settings must be placed **before** setting the ``colorscheme``. It is
|
||||
also important that the value of the vim ``term`` variable is not changed
|
||||
after these settings.
|
||||
|
||||
I get errors about the terminal being unknown or opening the terminal failing or functional keys like arrow keys don't work?
|
||||
-------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
I get errors about the terminal being unknown or opening the terminal failing when SSHing into a different computer?
|
||||
-----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
This happens because the |kitty| terminfo files are not available on the server.
|
||||
You can ssh in using the following command which will automatically copy the
|
||||
terminfo files to the server::
|
||||
These issues all have the same root cause: the kitty terminfo files not being
|
||||
available. The most common way this happens is SSHing into a computer that does
|
||||
not have the kitty terminfo files. The simplest fix for that is running::
|
||||
|
||||
kitty +kitten ssh myserver
|
||||
|
||||
It will automatically copy over the terminfo files and also magically enable
|
||||
:doc:`shell integration </shell-integration>` on the remote machine.
|
||||
|
||||
This :doc:`ssh kitten <kittens/ssh>` takes all the same command line arguments
|
||||
as :program:`ssh`, you can alias it to something small in your shell's rc files
|
||||
to avoid having to type it each time::
|
||||
|
||||
alias s="kitty +kitten ssh"
|
||||
|
||||
If the ssh kitten fails, use the following one-liner instead (it is slower as it
|
||||
needs to ssh into the server twice, but will work with most servers)::
|
||||
If this does not work, see :ref:`manual_terminfo_copy` for alternative ways to
|
||||
get the kitty terminfo files onto a remote computer.
|
||||
|
||||
infocmp -a xterm-kitty | ssh myserver tic -x -o \~/.terminfo /dev/stdin
|
||||
The next most common reason for this is if you are running commands as root
|
||||
using :program:`sudo` or :program:`su`. These programs often filter the
|
||||
:envvar:`TERMINFO` environment variable which is what points to the kitty
|
||||
terminfo files.
|
||||
|
||||
If you are behind a proxy (like Balabit) that prevents this, or :program:`tic`
|
||||
comes with macOS that does not support reading from STDIN, you must redirect the
|
||||
first command to a file, copy that to the server and run :program:`tic`
|
||||
manually. If you connect to a server, embedded or Android system that doesn't
|
||||
have :program:`tic`, copy over your local file terminfo to the other system as
|
||||
:file:`~/.terminfo/x/xterm-kitty`.
|
||||
First, make sure the :envvar:`TERM` is set to ``xterm-kitty`` in the sudo
|
||||
environment. By default, it should be automatically copied over.
|
||||
|
||||
Really, the correct solution for this is to convince the OpenSSH maintainers to
|
||||
have :program:`ssh` do this automatically, if possible, when connecting to a
|
||||
server, so that all terminals work transparently.
|
||||
If you are using a well maintained Linux distribution, it will have a
|
||||
``kitty-terminfo`` package that you can simply install to make the kitty
|
||||
terminfo files available system-wide. Then the problem will no longer occur.
|
||||
|
||||
If the server is running FreeBSD, or another system that relies on termcap
|
||||
rather than terminfo, you will need to convert the terminfo file on your local
|
||||
machine by running (on local machine with |kitty|)::
|
||||
|
||||
infocmp -CrT0 xterm-kitty
|
||||
|
||||
The output of this command is the termcap description, which should be appended
|
||||
to :file:`/usr/share/misc/termcap` on the remote server. Then run the following
|
||||
command to apply your change (on the server)::
|
||||
|
||||
cap_mkdb /usr/share/misc/termcap
|
||||
|
||||
|
||||
Keys such as arrow keys, backspace, delete, home/end, etc. do not work when using su or sudo?
|
||||
-------------------------------------------------------------------------------------------------
|
||||
|
||||
Make sure the :envvar:`TERM` environment variable, is ``xterm-kitty``. And
|
||||
either the :envvar:`TERMINFO` environment variable points to a directory
|
||||
containing :file:`x/xterm-kitty` or that file is under :file:`~/.terminfo/x/`.
|
||||
|
||||
For macOS, you may also need to put that file under :file:`~/.terminfo/78/`::
|
||||
|
||||
mkdir -p ~/.terminfo/{78,x}
|
||||
ln -snf ../x/xterm-kitty ~/.terminfo/78/xterm-kitty
|
||||
tic -x -o ~/.terminfo "$KITTY_INSTALLATION_DIR/terminfo/kitty.terminfo"
|
||||
|
||||
Note that :program:`sudo` might remove :envvar:`TERMINFO`. Then setting it at
|
||||
the shell prompt can be too late, because command line editing may not be
|
||||
reinitialized. In that case you can either ask :program:`sudo` to set it or if
|
||||
that is not supported, insert an :program:`env` command before starting the
|
||||
shell, or, if not possible, after sudo start another shell providing the right
|
||||
terminfo path::
|
||||
|
||||
sudo … TERMINFO=$HOME/.terminfo bash -i
|
||||
sudo … env TERMINFO=$HOME/.terminfo bash -i
|
||||
TERMINFO=/home/ORIGINALUSER/.terminfo exec bash -i
|
||||
|
||||
You can configure :program:`sudo` to preserve :envvar:`TERMINFO` by running
|
||||
``sudo visudo`` and adding the following line::
|
||||
Alternately, you can configure :program:`sudo` to preserve :envvar:`TERMINFO`
|
||||
by running ``sudo visudo`` and adding the following line::
|
||||
|
||||
Defaults env_keep += "TERM TERMINFO"
|
||||
|
||||
If none of these are suitable for you, you can run sudo as follows::
|
||||
|
||||
sudo TERMINFO="$TERMINFO" -s -H
|
||||
|
||||
This will start a new root shell with the correct :envvar:`TERMINFO` value from your
|
||||
current environment copied over.
|
||||
|
||||
If you have double width characters in your prompt, you may also need to
|
||||
explicitly set a UTF-8 locale, like::
|
||||
|
||||
export LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
|
||||
|
||||
|
||||
I cannot use the key combination X in program Y?
|
||||
-------------------------------------------------------
|
||||
|
||||
First, run::
|
||||
|
||||
kitty +kitten show_key -m kitty
|
||||
|
||||
Press the key combination X. If the kitten reports the key press
|
||||
that means kitty is correctly sending the key press to terminal programs.
|
||||
You need to report the issue to the developer of the terminal program. Most
|
||||
likely they have not added support for :doc:`/keyboard-protocol`.
|
||||
|
||||
If the kitten does not report it, it means that the key is bound to some action
|
||||
in kitty. You can unbind it in :file:`kitty.conf` with:
|
||||
|
||||
.. code-block:: conf
|
||||
|
||||
map X no_op
|
||||
|
||||
Here X is the keys you press on the keyboard. So for example
|
||||
:kbd:`ctrl+shift+1`.
|
||||
|
||||
|
||||
How do I change the colors in a running kitty instance?
|
||||
------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -212,3 +212,8 @@ Variables that kitty sets when running child programs
|
||||
|
||||
Set when enabling :ref:`shell_integration` with :program:`bash`, allowing
|
||||
:program:`bash` to automatically load the integration script.
|
||||
|
||||
.. envvar:: KITTY_OS
|
||||
|
||||
Set when using the include directive in kitty.conf. Can take values:
|
||||
``linux``, ``macos``, ``bsd``.
|
||||
|
||||
@@ -28,15 +28,14 @@ alpha-blending and text over graphics.
|
||||
Some programs and libraries that use the kitty graphics protocol:
|
||||
|
||||
* `termpdf.py <https://github.com/dsanson/termpdf.py>`_ - a terminal PDF/DJVU/CBR viewer
|
||||
* `ranger <https://github.com/ranger/ranger>`_ - a terminal file manager, with
|
||||
image previews, see this `PR <https://github.com/ranger/ranger/pull/1077>`_
|
||||
* `ranger <https://github.com/ranger/ranger>`_ - a terminal file manager, with image previews
|
||||
* :doc:`kitty-diff <kittens/diff>` - a side-by-side terminal diff program with support for images
|
||||
* `tpix <https://github.com/jesvedberg/tpix>`_ - a statically compiled binary that can be used to display images and easily installed on remote servers without root access
|
||||
* `mpv <https://github.com/mpv-player/mpv/commit/874e28f4a41a916bb567a882063dd2589e9234e1>`_ - A video player that can play videos in the terminal
|
||||
* `pixcat <https://github.com/mirukana/pixcat>`_ - a third party CLI and python library that wraps the graphics protocol
|
||||
* `neofetch <https://github.com/dylanaraps/neofetch>`_ - A command line system
|
||||
information tool
|
||||
* `viu <https://github.com/atanunq/viu>`_ - a terminal image viewer
|
||||
* `glkitty <https://github.com/michaeljclark/glkitty>`_ - C library to draw OpenGL shaders in the terminal with a glgears demo
|
||||
* `ctx.graphics <https://ctx.graphics/>`_ - Library for drawing graphics
|
||||
* `timg <https://github.com/hzeller/timg>`_ - a terminal image and video viewer
|
||||
* `notcurses <https://github.com/dankamongmen/notcurses>`_ - C library for terminal graphics with bindings for C++, Rust and Python
|
||||
@@ -44,6 +43,7 @@ Some programs and libraries that use the kitty graphics protocol:
|
||||
* `chafa <https://github.com/hpjansson/chafa>`_ - a terminal image viewer
|
||||
* `hologram.nvim <https://github.com/edluffy/hologram.nvim>`_ - view images inside nvim
|
||||
* `term-image <https://github.com/AnonymouX47/term-image>`_ - A Python library, CLI and TUI to display and browse images in the terminal
|
||||
* `glkitty <https://github.com/michaeljclark/glkitty>`_ - C library to draw OpenGL shaders in the terminal with a glgears demo
|
||||
|
||||
Other terminals that have implemented the graphics protocol:
|
||||
|
||||
@@ -88,6 +88,17 @@ code to demonstrate its use
|
||||
'number of rows: {} number of columns: {}'
|
||||
'screen width: {} screen height: {}').format(*buf))
|
||||
|
||||
.. tab:: Go
|
||||
|
||||
.. code-block:: go
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
fd, err := unix.Open(fd, unix.O_NOCTTY|unix.O_CLOEXEC|unix.O_NDELAY|unix.O_RDWR, 0666)
|
||||
sz, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
|
||||
fmt.Println("rows: %v columns: %v width: %v height %v", sz.Row, sz.Col, sz.Xpixel, sz.Ypixel)
|
||||
|
||||
|
||||
|
||||
Note that some terminals return ``0`` for the width and height values. Such
|
||||
terminals should be modified to return the correct values. Examples of
|
||||
terminals that return correct values: ``kitty, xterm``
|
||||
@@ -101,47 +112,71 @@ kitty.
|
||||
A minimal example
|
||||
------------------
|
||||
|
||||
Some minimal python code to display PNG images in kitty, using the most basic
|
||||
Some minimal code to display PNG images in kitty, using the most basic
|
||||
features of the graphics protocol:
|
||||
|
||||
.. code-block:: python
|
||||
.. tab:: Bash
|
||||
|
||||
import sys
|
||||
from base64 import standard_b64encode
|
||||
.. code-block:: sh
|
||||
|
||||
#!/bin/bash
|
||||
transmit_png() {
|
||||
data=$(base64 "$1")
|
||||
data="${data//[[:space:]]}"
|
||||
builtin local pos=0
|
||||
builtin local chunk_size=4096
|
||||
while [ $pos -lt ${#data} ]; do
|
||||
builtin printf "\e_G"
|
||||
[ $pos = "0" ] && printf "a=T,f=100,"
|
||||
builtin local chunk="${data:$pos:$chunk_size}"
|
||||
pos=$(($pos+$chunk_size))
|
||||
[ $pos -lt ${#data} ] && builtin printf "m=1"
|
||||
[ ${#chunk} -gt 0 ] && builtin printf ";%s" "${chunk}"
|
||||
builtin printf "\e\\"
|
||||
done
|
||||
}
|
||||
|
||||
transmit_png "$1"
|
||||
|
||||
.. tab:: Python
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
#!/usr/bin/python
|
||||
import sys
|
||||
from base64 import standard_b64encode
|
||||
|
||||
def serialize_gr_command(**cmd):
|
||||
payload = cmd.pop('payload', None)
|
||||
cmd = ','.join(f'{k}={v}' for k, v in cmd.items())
|
||||
ans = []
|
||||
w = ans.append
|
||||
w(b'\033_G'), w(cmd.encode('ascii'))
|
||||
if payload:
|
||||
w(b';')
|
||||
w(payload)
|
||||
w(b'\033\\')
|
||||
return b''.join(ans)
|
||||
|
||||
def write_chunked(**cmd):
|
||||
data = standard_b64encode(cmd.pop('data'))
|
||||
while data:
|
||||
chunk, data = data[:4096], data[4096:]
|
||||
m = 1 if data else 0
|
||||
sys.stdout.buffer.write(serialize_gr_command(payload=chunk, m=m,
|
||||
**cmd))
|
||||
sys.stdout.flush()
|
||||
cmd.clear()
|
||||
|
||||
with open(sys.argv[-1], 'rb') as f:
|
||||
write_chunked(a='T', f=100, data=f.read())
|
||||
|
||||
|
||||
def serialize_gr_command(**cmd):
|
||||
payload = cmd.pop('payload', None)
|
||||
cmd = ','.join(f'{k}={v}' for k, v in cmd.items())
|
||||
ans = []
|
||||
w = ans.append
|
||||
w(b'\033_G'), w(cmd.encode('ascii'))
|
||||
if payload:
|
||||
w(b';')
|
||||
w(payload)
|
||||
w(b'\033\\')
|
||||
return b''.join(ans)
|
||||
|
||||
|
||||
def write_chunked(**cmd):
|
||||
data = standard_b64encode(cmd.pop('data'))
|
||||
while data:
|
||||
chunk, data = data[:4096], data[4096:]
|
||||
m = 1 if data else 0
|
||||
sys.stdout.buffer.write(serialize_gr_command(payload=chunk, m=m,
|
||||
**cmd))
|
||||
sys.stdout.flush()
|
||||
cmd.clear()
|
||||
|
||||
|
||||
with open(sys.argv[-1], 'rb') as f:
|
||||
write_chunked(a='T', f=100, data=f.read())
|
||||
|
||||
|
||||
Save this script as :file:`png.py`, then you can use it to display any PNG
|
||||
Save this script as :file:`send-png`, then you can use it to display any PNG
|
||||
file in kitty as::
|
||||
|
||||
python png.py file.png
|
||||
chmod +x send-png
|
||||
./send-png file.png
|
||||
|
||||
|
||||
The graphics escape code
|
||||
|
||||
@@ -80,6 +80,12 @@ base application that uses kitty's graphics protocol for images.
|
||||
A text mode WWW browser that supports kitty's graphics protocol to display
|
||||
images.
|
||||
|
||||
.. _tool_mpv:
|
||||
|
||||
`mpv <https://github.com/mpv-player/mpv/commit/874e28f4a41a916bb567a882063dd2589e9234e1>`_
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
A video player that can play videos in the terminal.
|
||||
|
||||
.. _tool_timg:
|
||||
|
||||
`timg <https://github.com/hzeller/timg>`_
|
||||
|
||||
@@ -40,7 +40,8 @@ In addition to kitty, this protocol is also implemented in:
|
||||
<https://github.com/dankamongmen/notcurses/issues/2131>`__
|
||||
* The `crossterm library
|
||||
<https://github.com/crossterm-rs/crossterm/pull/688>`__
|
||||
* The `neovim text editor <https://github.com/neovim/neovim/pull/18181>`__
|
||||
* The `Vim text editor <https://github.com/vim/vim/commit/63a2e360cca2c70ab0a85d14771d3259d4b3aafa>`__
|
||||
* The `Neovim text editor <https://github.com/neovim/neovim/pull/18181>`__
|
||||
* The `kakoune text editor <https://github.com/mawww/kakoune/issues/4103>`__
|
||||
* The `dte text editor <https://gitlab.com/craigbarnes/dte/-/issues/138>`__
|
||||
|
||||
@@ -60,14 +61,14 @@ without too many changes, do the following:
|
||||
that are easy to parse unambiguously.
|
||||
#. Emit the escape sequence ``CSI < u`` at application exit if using the main
|
||||
screen or just before leaving alternate screen mode if using the alternate screen,
|
||||
to restore the previously used keyboard mode.
|
||||
to restore whatever the keyboard mode was before step 1.
|
||||
|
||||
Key events will all be delivered to your application either as plain UTF-8
|
||||
text, or using the following escape codes, for those keys that do not produce
|
||||
text (``CSI`` is the bytes ``0x1b 0x5b``)::
|
||||
|
||||
CSI number ; modifiers [u~]
|
||||
CSI 1; modifiers [ABCDEFHPQRS]
|
||||
CSI 1; modifiers [ABCDEFHPQS]
|
||||
0x0d - for the Enter key
|
||||
0x7f or 0x08 - for Backspace
|
||||
0x09 - for Tab
|
||||
@@ -82,7 +83,7 @@ The second form is used for a few functional keys, such as the :kbd:`Home`,
|
||||
:kbd:`End`, :kbd:`Arrow` keys and :kbd:`F1` ... :kbd:`F4`, they are enumerated in
|
||||
the :ref:`functional` table below. Note that if no modifiers are present the
|
||||
parameters are omitted entirely giving an escape code of the form ``CSI
|
||||
[ABCDEFHPQRS]``.
|
||||
[ABCDEFHPQS]``.
|
||||
|
||||
If you want support for more advanced features such as repeat and release
|
||||
events, alternate keys for shortcut matching et cetera, these can be turned on
|
||||
@@ -313,7 +314,7 @@ With this flag turned on, all key events that do not generate text are
|
||||
represented in one of the following two forms::
|
||||
|
||||
CSI number; modifier u
|
||||
CSI 1; modifier [~ABCDEFHPQRS]
|
||||
CSI 1; modifier [~ABCDEFHPQS]
|
||||
|
||||
This makes it very easy to parse key events in an application. In particular,
|
||||
:kbd:`ctrl+c` will no longer generate the ``SIGINT`` signal, but instead be
|
||||
@@ -404,7 +405,7 @@ Legacy functional keys
|
||||
These keys are encoded using three schemes::
|
||||
|
||||
CSI number ; modifier ~
|
||||
CSI 1 ; modifier {ABCDEFHPQRS}
|
||||
CSI 1 ; modifier {ABCDEFHPQS}
|
||||
SS3 {ABCDEFHPQRS}
|
||||
|
||||
In the above, if there are no modifiers, the modifier parameter is omitted.
|
||||
@@ -532,7 +533,7 @@ compatibility reasons.
|
||||
"NUM_LOCK", "``57360 u``", "PRINT_SCREEN", "``57361 u``"
|
||||
"PAUSE", "``57362 u``", "MENU", "``57363 u``"
|
||||
"F1", "``1 P or 11 ~``", "F2", "``1 Q or 12 ~``"
|
||||
"F3", "``1 R or 13 ~``", "F4", "``1 S or 14 ~``"
|
||||
"F3", "``13 ~``", "F4", "``1 S or 14 ~``"
|
||||
"F5", "``15 ~``", "F6", "``17 ~``"
|
||||
"F7", "``18 ~``", "F8", "``19 ~``"
|
||||
"F9", "``20 ~``", "F10", "``21 ~``"
|
||||
@@ -581,8 +582,14 @@ compatibility reasons.
|
||||
.. end functional key table
|
||||
.. }}}
|
||||
|
||||
Note that the escape codes above of the form ``CSI 1 letter`` will omit the
|
||||
``1`` if there are no modifiers, since ``1`` is the default value.
|
||||
.. note::
|
||||
The escape codes above of the form ``CSI 1 letter`` will omit the
|
||||
``1`` if there are no modifiers, since ``1`` is the default value.
|
||||
|
||||
.. note::
|
||||
The original version of this specification allowed F3 to be encoded as both
|
||||
CSI R and CSI ~. However, CSI R conflicts with the Cursor Position Report,
|
||||
so it was removed.
|
||||
|
||||
.. _ctrl_mapping:
|
||||
|
||||
@@ -675,3 +682,31 @@ specification.
|
||||
* Handwaves that :kbd:`ctrl` *tends to* mask with ``0x1f``. In actual fact it
|
||||
does this only for some keys. The action of :kbd:`ctrl` is not specified and
|
||||
varies between terminals, historically because of different keyboard layouts.
|
||||
|
||||
|
||||
Why xterm's modifyOtherKeys should not be used
|
||||
---------------------------------------------------
|
||||
|
||||
* Does not support release events
|
||||
|
||||
* Does not fix the issue of :kbd:`Esc` key presses not being distinguishable from
|
||||
escape codes.
|
||||
|
||||
* Does not fix the issue of some keypresses generating identical bytes and thus
|
||||
being indistinguishable
|
||||
|
||||
* There is no robust way to query it or manage its state from a program running
|
||||
in the terminal.
|
||||
|
||||
* No support for shifted keys.
|
||||
|
||||
* No support for alternate keyboard layouts.
|
||||
|
||||
* No support for modifiers beyond the basic four.
|
||||
|
||||
* No support for lock keys like Num lock and Caps lock.
|
||||
|
||||
* Is completely unspecified. The most discussion of it available anywhere is
|
||||
`here <https://invisible-island.net/xterm/modified-keys.html>`__
|
||||
And it contains no specification of what numbers to assign to what function
|
||||
keys beyond running a Perl script on an X11 system!!
|
||||
|
||||
@@ -11,14 +11,46 @@ from the shell. It even works over SSH. Using it is as simple as::
|
||||
|
||||
echo hooray | kitty +kitten clipboard
|
||||
|
||||
All text received on :file:`stdin` is copied to the clipboard.
|
||||
All text received on :file:`STDIN` is copied to the clipboard.
|
||||
|
||||
To get text from the clipboard you have to enable reading of the clipboard
|
||||
in :opt:`clipboard_control` in :file:`kitty.conf`. Once you do that, you can
|
||||
use::
|
||||
To get text from the clipboard::
|
||||
|
||||
kitty +kitten clipboard --get-clipboard
|
||||
|
||||
The text will be written to :file:`STDOUT`. Note that by default kitty asks for
|
||||
permission when a program attempts to read the clipboard. This can be
|
||||
controlled via :opt:`clipboard_control`.
|
||||
|
||||
.. versionadded:: 0.27.0
|
||||
Support for copying arbitrary data types
|
||||
|
||||
The clipboard kitten can be used to send/receive
|
||||
more than just plain text from the system clipboard. You can transfer arbitrary
|
||||
data types. Best illustrated with some examples::
|
||||
|
||||
# Copy an image to the clipboard:
|
||||
kitty +kitten clipboard picture.png
|
||||
|
||||
# Copy an image and some text to the clipboard:
|
||||
kitty +kitten clipboard picture.jpg text.txt
|
||||
|
||||
# Copy text from STDIN and an image to the clipboard:
|
||||
echo hello | kitty +kitten clipboard picture.png /dev/stdin
|
||||
|
||||
# Copy any raster image available on the clipboard to a PNG file:
|
||||
kitty +kitten clipboard -g picture.png
|
||||
|
||||
# Copy an image to a file and text to STDOUT:
|
||||
kitty +kitten clipboard -g picture.png /dev/stdout
|
||||
|
||||
# List the formats available on the system clipboard
|
||||
kitty +kitten clipboard -g -m . /dev/stdout
|
||||
|
||||
Normally, the kitten guesses MIME types based on the file names. To control the
|
||||
MIME types precisely, use the :option:`--mime <kitty +kitten clipboard --mime>` option.
|
||||
|
||||
This kitten uses a new protocol developed by kitty to function, for details,
|
||||
see :doc:`/clipboard`.
|
||||
|
||||
.. program:: kitty +kitten clipboard
|
||||
|
||||
|
||||
@@ -43,46 +43,18 @@ You can now run searches with::
|
||||
|
||||
hg some-search-term
|
||||
|
||||
If you want to enable completion, for the kitten, you can delegate completion
|
||||
to :program:`rg`. How to do that varies based on the shell:
|
||||
|
||||
|
||||
.. tab:: zsh
|
||||
|
||||
Instead of using an alias, create a simple wrapper script named
|
||||
:program:`hg` somewhere in your :envvar:`PATH`:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
#!/bin/sh
|
||||
exec kitty +kitten hyperlinked_grep "$@"
|
||||
|
||||
Then, add the following to :file:`.zshrc`::
|
||||
|
||||
compdef _rg hg
|
||||
|
||||
.. tab:: fish
|
||||
|
||||
You can combine both the aliasing/wrapping and pointing fish to ripgrep's
|
||||
autocompletion with a fish wrapper function in your :file:`config.fish`
|
||||
or :file:`~/.config/fish/functions/hg.fish`:
|
||||
|
||||
.. code-block:: fish
|
||||
|
||||
function hg --wraps rg; kitty +kitten hyperlinked_grep $argv; end
|
||||
|
||||
To learn more about kitty's powerful framework for customizing URL click
|
||||
actions, see :doc:`here </open_actions>`.
|
||||
|
||||
By default, this kitten adds hyperlinks for several parts of ripgrep output:
|
||||
the per-file header, match context lines, and match lines. You can control
|
||||
which items are linked with a :command:`--kitten hyperlink` flag. For example,
|
||||
:command:`--kitten hyperlink=matching_lines` will only add hyperlinks to the
|
||||
match lines. :command:`--kitten hyperlink=file_headers,context_lines` will link
|
||||
file headers and context lines but not match lines. :command:`--kitten
|
||||
which items are linked with a :code:`--kitten hyperlink` flag. For example,
|
||||
:code:`--kitten hyperlink=matching_lines` will only add hyperlinks to the
|
||||
match lines. :code:`--kitten hyperlink=file_headers,context_lines` will link
|
||||
file headers and context lines but not match lines. :code:`--kitten
|
||||
hyperlink=none` will cause the command line to be passed to directly to
|
||||
:command:`rg` so no hyperlinking will be performed. :command:`--kitten
|
||||
hyperlink` may be specified multiple times.
|
||||
:command:`rg` so no hyperlinking will be performed. :code:`--kitten hyperlink`
|
||||
may be specified multiple times.
|
||||
|
||||
Hopefully, someday this functionality will make it into some `upstream grep
|
||||
<https://github.com/BurntSushi/ripgrep/issues/665>`__ program directly removing
|
||||
@@ -93,3 +65,9 @@ the need for this kitten.
|
||||
While you can pass any of ripgrep's comand line options to the kitten and
|
||||
they will be forwarded to :program:`rg`, do not use options that change the
|
||||
output formatting as the kitten works by parsing the output from ripgrep.
|
||||
The unsupported options are: :code:`--context-separator`,
|
||||
:code:`--field-context-separator`, :code:`--field-match-separator`,
|
||||
:code:`--json`, :code:`-I --no-filename`, :code:`-0 --null`,
|
||||
:code:`--null-data`, :code:`--path-separator`. If you specify options via
|
||||
configuration file, then any changes to the default output format will not be
|
||||
supported, not just the ones listed.
|
||||
|
||||
@@ -7,6 +7,7 @@ The ``icat`` kitten can be used to display arbitrary images in the |kitty|
|
||||
terminal. Using it is as simple as::
|
||||
|
||||
kitty +kitten icat image.jpeg
|
||||
kitten icat image.jpeg
|
||||
|
||||
It supports all image types supported by `ImageMagick
|
||||
<https://www.imagemagick.org>`__. It even works over SSH. For details, see the
|
||||
@@ -20,8 +21,9 @@ Then you can simply use ``icat image.png`` to view images.
|
||||
|
||||
.. note::
|
||||
|
||||
`ImageMagick <https://www.imagemagick.org>`__ must be installed for icat
|
||||
kitten to work.
|
||||
`ImageMagick <https://www.imagemagick.org>`__ must be installed for the
|
||||
full range of image types. Without it only PNG/JPG/GIF/BMP/TIFF/WEBP are
|
||||
supported.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -35,16 +37,15 @@ Then you can simply use ``icat image.png`` to view images.
|
||||
|
||||
The ``icat`` kitten has various command line arguments to allow it to be used
|
||||
from inside other programs to display images. In particular, :option:`--place`,
|
||||
:option:`--detect-support`, :option:`--silent` and
|
||||
:option:`--print-window-size`.
|
||||
:option:`--detect-support` and :option:`--print-window-size`.
|
||||
|
||||
If you are trying to integrate icat into a complex program like a file manager
|
||||
or editor, there are a few things to keep in mind. icat works by communicating
|
||||
over the TTY device, it both writes to and reads from the TTY. So it is
|
||||
imperative that while it is running the host program does not do any TTY I/O.
|
||||
Any key presses or other input from the user on the TTY device will be
|
||||
discarded. At a minimum, you should use the :option:`--silent` and
|
||||
:option:`--transfer-mode` command line arguments. To be really robust you should
|
||||
discarded. At a minimum, you should use the :option:`--transfer-mode`
|
||||
command line arguments. To be really robust you should
|
||||
consider writing proper support for the :doc:`kitty graphics protocol
|
||||
</graphics-protocol>` in the program instead. Nowadays there are many libraries
|
||||
that have support for it.
|
||||
|
||||
@@ -162,3 +162,42 @@ The copy command
|
||||
--------------------
|
||||
|
||||
.. include:: /generated/ssh-copy.rst
|
||||
|
||||
|
||||
.. _manual_terminfo_copy:
|
||||
|
||||
Copying terminfo files manually
|
||||
-------------------------------------
|
||||
|
||||
Sometimes, the ssh kitten can fail, or maybe you dont like to use it. In such
|
||||
cases, the terminfo files can be copied over manually to a server with the
|
||||
following one liner::
|
||||
|
||||
infocmp -a xterm-kitty | ssh myserver tic -x -o \~/.terminfo /dev/stdin
|
||||
|
||||
If you are behind a proxy (like Balabit) that prevents this, or you are SSHing
|
||||
into macOS where the :program:`tic` does not support reading from :file:`STDIN`,
|
||||
you must redirect the first command to a file, copy that to the server and run :program:`tic`
|
||||
manually. If you connect to a server, embedded, or Android system that doesn't
|
||||
have :program:`tic`, copy over your local file terminfo to the other system as
|
||||
:file:`~/.terminfo/x/xterm-kitty`.
|
||||
|
||||
If the server is running a relatively modern Linux distribution and you have
|
||||
root access to it, you could simply install the ``kitty-terminfo`` package on
|
||||
the server to make the terminfo files available.
|
||||
|
||||
Really, the correct solution for this is to convince the OpenSSH maintainers to
|
||||
have :program:`ssh` do this automatically, if possible, when connecting to a
|
||||
server, so that all terminals work transparently.
|
||||
|
||||
If the server is running FreeBSD, or another system that relies on termcap
|
||||
rather than terminfo, you will need to convert the terminfo file on your local
|
||||
machine by running (on local machine with |kitty|)::
|
||||
|
||||
infocmp -CrT0 xterm-kitty
|
||||
|
||||
The output of this command is the termcap description, which should be appended
|
||||
to :file:`/usr/share/misc/termcap` on the remote server. Then run the following
|
||||
command to apply your change (on the server)::
|
||||
|
||||
cap_mkdb /usr/share/misc/termcap
|
||||
|
||||
@@ -1,295 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# License: GPLv3 Copyright: 2022, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
import argparse
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import termios
|
||||
import time
|
||||
import tty
|
||||
from contextlib import contextmanager
|
||||
from ctypes import (
|
||||
CDLL, POINTER, byref, c_char_p, c_int, c_size_t, c_void_p,
|
||||
create_string_buffer
|
||||
)
|
||||
from ctypes.util import find_library
|
||||
|
||||
_plat = sys.platform.lower()
|
||||
is_macos: bool = 'darwin' in _plat
|
||||
|
||||
|
||||
def build_crypto_tools(): # {{{
|
||||
class EVP_PKEY_POINTER(c_void_p):
|
||||
|
||||
algorithm = 0
|
||||
|
||||
def __del__(self):
|
||||
EVP_PKEY_free(self)
|
||||
|
||||
@property
|
||||
def public(self):
|
||||
sz = c_size_t(0)
|
||||
EVP_PKEY_get_raw_public_key(self, None, byref(sz))
|
||||
buf = create_string_buffer(sz.value)
|
||||
EVP_PKEY_get_raw_public_key(self, buf, byref(sz))
|
||||
return buf.raw
|
||||
|
||||
def derive_secret(self, pubkey):
|
||||
pubkey = EVP_PKEY_new_raw_public_key(self.algorithm, None, pubkey, len(pubkey))
|
||||
ctx = EVP_PKEY_CTX_new(self, None)
|
||||
EVP_PKEY_derive_init(ctx)
|
||||
EVP_PKEY_derive_set_peer(ctx, pubkey)
|
||||
sz = c_size_t(0)
|
||||
EVP_PKEY_derive(ctx, None, byref(sz))
|
||||
buf = create_string_buffer(sz.value)
|
||||
EVP_PKEY_derive(ctx, buf, byref(sz))
|
||||
return hashlib.sha256(buf.raw).digest()
|
||||
|
||||
class EVP_PKEY_CTX_POINTER(c_void_p):
|
||||
|
||||
def __del__(self):
|
||||
EVP_PKEY_CTX_free(self)
|
||||
|
||||
class EVP_CIPHER_CTX_POINTER(c_void_p):
|
||||
|
||||
def __del__(self):
|
||||
EVP_CIPHER_CTX_free(self)
|
||||
|
||||
class EVP_CIPHER_POINTER(c_void_p):
|
||||
pass
|
||||
|
||||
cl = find_library('crypto')
|
||||
if not cl:
|
||||
raise SystemExit('Failed to find libcrypto on your system, make sure OpenSSL is installed')
|
||||
crypto = CDLL(cl)
|
||||
libc = CDLL(None)
|
||||
|
||||
def create_crypto_func(name, *argtypes, restype=c_int, int_return_ok=lambda x: x == 1):
|
||||
|
||||
impl = getattr(crypto, name)
|
||||
impl.restype = restype
|
||||
impl.argtypes = argtypes
|
||||
|
||||
def func(*a):
|
||||
res = impl(*a)
|
||||
if restype is c_int:
|
||||
if not int_return_ok(res):
|
||||
print('Call to', name, 'failed with return code:', res, file=sys.stderr)
|
||||
abort_on_openssl_error()
|
||||
elif restype is not None and issubclass(restype, c_void_p):
|
||||
if res.value is None:
|
||||
print('Call to', name, 'failed with NULL return', file=sys.stderr)
|
||||
abort_on_openssl_error()
|
||||
return res
|
||||
return func
|
||||
|
||||
OBJ_txt2nid = create_crypto_func('OBJ_txt2nid', c_char_p, int_return_ok=bool)
|
||||
EVP_PKEY_CTX_new_id = create_crypto_func('EVP_PKEY_CTX_new_id', c_int, c_void_p, restype=EVP_PKEY_CTX_POINTER)
|
||||
EVP_PKEY_CTX_new = create_crypto_func('EVP_PKEY_CTX_new', EVP_PKEY_POINTER, c_void_p, restype=EVP_PKEY_CTX_POINTER)
|
||||
EVP_PKEY_keygen_init = create_crypto_func('EVP_PKEY_keygen_init', EVP_PKEY_CTX_POINTER)
|
||||
EVP_PKEY_keygen = create_crypto_func('EVP_PKEY_keygen', EVP_PKEY_CTX_POINTER, POINTER(EVP_PKEY_POINTER))
|
||||
ERR_print_errors_fp = create_crypto_func('ERR_print_errors_fp', c_void_p, restype=None)
|
||||
EVP_PKEY_free = create_crypto_func('EVP_PKEY_free', EVP_PKEY_POINTER, restype=None)
|
||||
EVP_PKEY_CTX_free = create_crypto_func('EVP_PKEY_CTX_free', EVP_PKEY_CTX_POINTER, restype=None)
|
||||
EVP_PKEY_get_raw_public_key = create_crypto_func('EVP_PKEY_get_raw_public_key', EVP_PKEY_POINTER, c_char_p, POINTER(c_size_t))
|
||||
EVP_PKEY_new_raw_public_key = create_crypto_func('EVP_PKEY_new_raw_public_key', c_int, c_void_p, c_char_p, c_size_t, restype=EVP_PKEY_POINTER)
|
||||
EVP_PKEY_derive_init = create_crypto_func('EVP_PKEY_derive_init', EVP_PKEY_CTX_POINTER)
|
||||
EVP_PKEY_derive_set_peer = create_crypto_func('EVP_PKEY_derive_set_peer', EVP_PKEY_CTX_POINTER, EVP_PKEY_POINTER)
|
||||
EVP_PKEY_derive = create_crypto_func('EVP_PKEY_derive', EVP_PKEY_CTX_POINTER, c_char_p, POINTER(c_size_t))
|
||||
EVP_CIPHER_CTX_free = create_crypto_func('EVP_CIPHER_CTX_free', EVP_CIPHER_CTX_POINTER, restype=None)
|
||||
EVP_get_cipherbyname = create_crypto_func('EVP_get_cipherbyname', c_char_p, restype=EVP_CIPHER_POINTER)
|
||||
EVP_CIPHER_key_length = create_crypto_func('EVP_CIPHER_key_length', EVP_CIPHER_POINTER, int_return_ok=bool)
|
||||
EVP_CIPHER_iv_length = create_crypto_func('EVP_CIPHER_iv_length', EVP_CIPHER_POINTER, int_return_ok=bool)
|
||||
EVP_CIPHER_CTX_block_size = create_crypto_func('EVP_CIPHER_CTX_block_size', EVP_CIPHER_CTX_POINTER, int_return_ok=bool)
|
||||
EVP_CIPHER_CTX_new = create_crypto_func('EVP_CIPHER_CTX_new', restype=EVP_CIPHER_CTX_POINTER)
|
||||
EVP_EncryptInit_ex = create_crypto_func('EVP_EncryptInit_ex', EVP_CIPHER_CTX_POINTER, EVP_CIPHER_POINTER, c_void_p, c_char_p, c_char_p)
|
||||
EVP_EncryptUpdate = create_crypto_func('EVP_EncryptUpdate', EVP_CIPHER_CTX_POINTER, c_char_p, POINTER(c_int), c_char_p, c_int)
|
||||
EVP_EncryptFinal_ex = create_crypto_func('EVP_EncryptFinal_ex', EVP_CIPHER_CTX_POINTER, c_char_p, POINTER(c_int))
|
||||
EVP_CIPHER_CTX_ctrl = create_crypto_func('EVP_CIPHER_CTX_ctrl', EVP_CIPHER_CTX_POINTER, c_int, c_int, c_char_p)
|
||||
try:
|
||||
EVP_CIPHER_CTX_tag_length = create_crypto_func('EVP_CIPHER_CTX_tag_length', EVP_CIPHER_CTX_POINTER, int_return_ok=bool)
|
||||
except AttributeError: # need openssl >= 3
|
||||
def EVP_CIPHER_CTX_tag_length(cipher):
|
||||
return 16
|
||||
EVP_CTRL_AEAD_GET_TAG, EVP_CTRL_AEAD_SET_TAG = 0x10, 0x11 # these are defines in the header dont know how to get them programmatically
|
||||
EVP_CTRL_AEAD_SET_TAG
|
||||
|
||||
def abort_on_openssl_error():
|
||||
stderr = c_void_p.in_dll(libc, 'stderr')
|
||||
ERR_print_errors_fp(stderr)
|
||||
raise SystemExit(1)
|
||||
|
||||
def elliptic_curve_keypair(algorithm='X25519'):
|
||||
nid = OBJ_txt2nid(algorithm.encode())
|
||||
pctx = EVP_PKEY_CTX_new_id(nid, None)
|
||||
EVP_PKEY_keygen_init(pctx)
|
||||
key = EVP_PKEY_POINTER()
|
||||
EVP_PKEY_keygen(pctx, byref(key))
|
||||
key.algorithm = nid
|
||||
return key
|
||||
|
||||
def encrypt(plaintext, symmetric_key, algorithm='aes-256-gcm'):
|
||||
cipher = EVP_get_cipherbyname(algorithm.encode())
|
||||
if len(symmetric_key) != EVP_CIPHER_key_length(cipher):
|
||||
raise KeyError(f'The symmetric key has length {len(symmetric_key)} != {EVP_CIPHER_key_length(cipher)} needed for {algorithm}')
|
||||
ctx = EVP_CIPHER_CTX_new()
|
||||
iv = os.urandom(EVP_CIPHER_iv_length(cipher))
|
||||
EVP_EncryptInit_ex(ctx, cipher, None, symmetric_key, iv)
|
||||
bs = EVP_CIPHER_CTX_block_size(ctx)
|
||||
ciphertext = create_string_buffer(len(plaintext) + 2 * bs)
|
||||
outlen = c_int(len(ciphertext))
|
||||
EVP_EncryptUpdate(ctx, ciphertext, byref(outlen), plaintext, len(plaintext))
|
||||
ans = ciphertext[:outlen.value]
|
||||
outlen = c_int(len(ciphertext))
|
||||
EVP_EncryptFinal_ex(ctx, ciphertext, byref(outlen))
|
||||
if outlen.value:
|
||||
ans += ciphertext[:outlen.value]
|
||||
tag = create_string_buffer(EVP_CIPHER_CTX_tag_length(cipher))
|
||||
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, len(tag), tag)
|
||||
return iv, ans, tag.raw
|
||||
return elliptic_curve_keypair, encrypt
|
||||
# }}}
|
||||
|
||||
|
||||
# utils {{{
|
||||
def encrypt_cmd(cmd, password, pubkey=None):
|
||||
elliptic_curve_keypair, encrypt = build_crypto_tools()
|
||||
if pubkey is None:
|
||||
pubkey = os.environ['KITTY_PUBLIC_KEY']
|
||||
v, d = pubkey.split(':', 1)
|
||||
if v != '1':
|
||||
raise SystemExit(f'Unsupported encryption protocol: {v}')
|
||||
pubkey = base64.b85decode(d)
|
||||
k = elliptic_curve_keypair()
|
||||
sk = k.derive_secret(pubkey)
|
||||
cmd['timestamp'] = time.time_ns()
|
||||
cmd['password'] = password
|
||||
data = json.dumps(cmd).encode()
|
||||
iv, encrypted, tag = encrypt(data, sk)
|
||||
|
||||
def e(x):
|
||||
return base64.b85encode(x).decode('ascii')
|
||||
|
||||
return {
|
||||
'encrypted': e(encrypted), 'iv': e(iv), 'tag': e(tag), 'pubkey': e(k.public), 'version': cmd['version']
|
||||
}
|
||||
|
||||
|
||||
@contextmanager
|
||||
def raw_mode(fd):
|
||||
old = termios.tcgetattr(fd)
|
||||
try:
|
||||
tty.setraw(fd)
|
||||
yield
|
||||
finally:
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old)
|
||||
|
||||
|
||||
def config_dir():
|
||||
if 'KITTY_CONFIG_DIRECTORY' in os.environ:
|
||||
return os.path.abspath(os.path.expanduser(os.environ['KITTY_CONFIG_DIRECTORY']))
|
||||
locations = []
|
||||
if 'XDG_CONFIG_HOME' in os.environ:
|
||||
locations.append(os.path.abspath(os.path.expanduser(os.environ['XDG_CONFIG_HOME'])))
|
||||
locations.append(os.path.expanduser('~/.config'))
|
||||
if is_macos:
|
||||
locations.append(os.path.expanduser('~/Library/Preferences'))
|
||||
for loc in filter(None, os.environ.get('XDG_CONFIG_DIRS', '').split(os.pathsep)):
|
||||
locations.append(os.path.abspath(os.path.expanduser(loc)))
|
||||
for loc in locations:
|
||||
if loc:
|
||||
q = os.path.join(loc, 'kitty')
|
||||
if os.access(q, os.W_OK) and os.path.exists(os.path.join(q, 'kitty.conf')):
|
||||
return q
|
||||
for loc in locations:
|
||||
if loc:
|
||||
q = os.path.join(loc, 'kitty')
|
||||
if os.path.isdir(q) and os.access(q, os.W_OK):
|
||||
return q
|
||||
return ''
|
||||
|
||||
|
||||
def resolve_custom_file(path):
|
||||
path = os.path.expanduser(path)
|
||||
path = os.path.expandvars(path)
|
||||
if not os.path.isabs(path):
|
||||
cdir = config_dir()
|
||||
if cdir:
|
||||
path = os.path.join(cdir, path)
|
||||
return path
|
||||
|
||||
|
||||
def get_password(opts):
|
||||
if opts.use_password == 'never':
|
||||
return ''
|
||||
ans = ''
|
||||
if opts.password:
|
||||
ans = opts.password
|
||||
if not ans and opts.password_file:
|
||||
if opts.password_file == '-':
|
||||
if sys.stdin.isatty():
|
||||
from getpass import getpass
|
||||
ans = getpass()
|
||||
else:
|
||||
ans = sys.stdin.read().rstrip()
|
||||
try:
|
||||
tty_fd = os.open(os.ctermid(), os.O_RDONLY | os.O_CLOEXEC)
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
with open(tty_fd, closefd=True):
|
||||
os.dup2(tty_fd, sys.stdin.fileno())
|
||||
else:
|
||||
try:
|
||||
with open(resolve_custom_file(opts.password_file)) as f:
|
||||
ans = f.read().rstrip()
|
||||
except OSError:
|
||||
pass
|
||||
if not ans and opts.password_env:
|
||||
ans = os.environ.get(opts.password_env, '')
|
||||
if not ans and opts.use_password == 'always':
|
||||
raise SystemExit('No password was found')
|
||||
if ans and len(ans) > 1024:
|
||||
raise SystemExit('Specified password is too long')
|
||||
return ans
|
||||
# }}}
|
||||
|
||||
|
||||
arg_parser = argparse.ArgumentParser(prog='kitty@', description='Control kitty remotely.')
|
||||
arg_parser.add_argument('--password', default='', help='''\
|
||||
A password to use when contacting kitty. This will cause kitty to ask the user
|
||||
for permission to perform the specified action, unless the password has been
|
||||
accepted before or is pre-configured in kitty.conf''')
|
||||
arg_parser.add_argument('--password-file', default='rc-pass', help='''\
|
||||
A file from which to read the password. Trailing whitespace is ignored. Relative
|
||||
paths are resolved from the kitty configuration directory. Use - to read from STDIN.
|
||||
Used if no --password is supplied. Defaults to checking for the
|
||||
rc-pass file in the kitty configuration directory.''')
|
||||
arg_parser.add_argument('--password-env', default='KITTY_RC_PASSWORD', help='''\
|
||||
The name of an environment variable to read the password from.
|
||||
Used if no --password-file is supplied. Defaults to checking the KITTY_RC_PASSWORD.''')
|
||||
arg_parser.add_argument('--use-password', default='if-available', choices=('if-available', 'always', 'never'), help='''\
|
||||
If no password is available, kitty will usually just send the remote control command
|
||||
without a password. This option can be used to force it to always or never use
|
||||
the supplied password.''')
|
||||
|
||||
args = arg_parser.parse_args()
|
||||
|
||||
|
||||
def populate_cmd(cmd):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
password = get_password(args)
|
||||
cmd = {'version': [0, 20, 0]} # use a random version that's fairly old
|
||||
populate_cmd(cmd)
|
||||
if password:
|
||||
encrypt_cmd(cmd, password)
|
||||
|
||||
# cmd = {'version': [0, 14, 2], 'cmd': 'ls'}
|
||||
# cmd = encrypt_cmd(cmd, 'test')
|
||||
# with open(os.open(os.ctermid(), os.O_RDWR | os.O_CLOEXEC), 'w') as tty_file, raw_mode(tty_file.fileno()):
|
||||
# print(end=f'\x1bP@kitty-cmd{json.dumps(cmd)}\x1b\\', flush=True, file=tty_file)
|
||||
# os.read(tty_file.fileno(), 4096)
|
||||
@@ -68,6 +68,13 @@ some special variables, documented below:
|
||||
The path, query and fragment portions of the URL, without any
|
||||
unquoting.
|
||||
|
||||
``EDITOR``
|
||||
The terminal based text editor. The configured :opt:`editor` in
|
||||
:file:`kitty.conf` is preferred.
|
||||
|
||||
``SHELL``
|
||||
The path to the shell. The configured :opt:`shell` in :file:`kitty.conf` is
|
||||
preferred, without arguments.
|
||||
|
||||
.. note::
|
||||
You can use the :opt:`action_alias` option just as in :file:`kitty.conf` to
|
||||
@@ -103,7 +110,8 @@ lines. The various available criteria are:
|
||||
Useful if your system MIME database does not have definitions you need. This
|
||||
file is in the standard format of one definition per line, like:
|
||||
``text/plain rst md``. Note that the MIME type for directories is
|
||||
``inode/directory``.
|
||||
``inode/directory``. MIME types are detected based on file extension, not
|
||||
file contents.
|
||||
|
||||
``ext``
|
||||
A comma separated list of file extensions, for example: ``jpeg, tar.gz``
|
||||
|
||||
@@ -85,7 +85,7 @@ Extending kitty
|
||||
------------------
|
||||
|
||||
kitty has a powerful framework for scripting. You can create small terminal
|
||||
programs called :doc:`kittens <kittens_intro>`. These can used to add features
|
||||
programs called :doc:`kittens <kittens_intro>`. These can be used to add features
|
||||
to kitty, for example, :doc:`editing remote files <kittens/remote_file>` or
|
||||
:doc:`inputting Unicode characters <kittens/unicode_input>`. They can also be
|
||||
used to create programs that leverage kitty's powerful features, for example,
|
||||
@@ -127,7 +127,7 @@ Startup Sessions
|
||||
You can control the :term:`tabs <tab>`, :term:`kitty window <window>` layout,
|
||||
working directory, startup programs, etc. by creating a *session* file and using
|
||||
the :option:`kitty --session` command line flag or the :opt:`startup_session`
|
||||
option in :file:`kitty.conf`. For example:
|
||||
option in :file:`kitty.conf`. An example, showing all available commands:
|
||||
|
||||
.. code-block:: session
|
||||
|
||||
@@ -154,7 +154,7 @@ option in :file:`kitty.conf`. For example:
|
||||
launch zsh
|
||||
|
||||
# Create a new OS window
|
||||
# Any definitions specifed before the first new_os_window will apply to first OS window.
|
||||
# Any definitions specified before the first new_os_window will apply to first OS window.
|
||||
new_os_window
|
||||
# Set new window size to 80x24 cells
|
||||
os_window_size 80c 24c
|
||||
@@ -173,6 +173,11 @@ option in :file:`kitty.conf`. For example:
|
||||
The :doc:`launch <launch>` command when used in a session file cannot create
|
||||
new OS windows, or tabs.
|
||||
|
||||
.. note::
|
||||
Environment variables of the for :code:`${NAME}` or :code:`$NAME` are
|
||||
expanded in the session file, except in the *arguments* (not options) to the
|
||||
launch command.
|
||||
|
||||
|
||||
Creating tabs/windows
|
||||
-------------------------------
|
||||
|
||||
@@ -32,3 +32,4 @@ please do so by opening issues in the `GitHub bug tracker
|
||||
unscroll
|
||||
color-stack
|
||||
deccara
|
||||
clipboard
|
||||
|
||||
@@ -15,6 +15,7 @@ Where ``<ESC>`` is the byte ``0x1b``. The JSON object has the form:
|
||||
"cmd": "command name",
|
||||
"version": "<kitty version>",
|
||||
"no_response": "<Optional Boolean>",
|
||||
"kitty_window_id": "<Optional value of the KITTY_WINDOW_ID env var>",
|
||||
"payload": "<Optional JSON object>"
|
||||
}
|
||||
|
||||
@@ -40,6 +41,12 @@ with the following command line::
|
||||
|
||||
echo -en '\eP@kitty-cmd{"cmd":"ls","version":[0,14,2]}\e\\' | socat - unix:/tmp/test | awk '{ print substr($0, 13, length($0) - 14) }' | jq -c '.data | fromjson' | jq .
|
||||
|
||||
There is also the statically compiled stand-alone executable ``kitten``
|
||||
that can be used for this, available from the `kitty releases
|
||||
<https://github.com/kovidgoyal/kitty/releases>`__ page::
|
||||
|
||||
kitten @ --help
|
||||
|
||||
.. _rc_crypto:
|
||||
|
||||
Encrypted communication
|
||||
|
||||
@@ -304,7 +304,13 @@ The remote control protocol
|
||||
-----------------------------------------------
|
||||
|
||||
If you wish to develop your own client to talk to |kitty|, you can use the
|
||||
:doc:`remote control protocol specification <rc_protocol>`.
|
||||
:doc:`remote control protocol specification <rc_protocol>`. Note that there
|
||||
is a statically compiled, standalone executable, ``kitten`` available that
|
||||
can be used as a remote control client on any UNIX like computer. This can be
|
||||
downloaded and used directly from the `kitty releases
|
||||
<https://github.com/kovidgoyal/kitty/releases>`__ page::
|
||||
|
||||
kitten @ --help
|
||||
|
||||
|
||||
.. _search_syntax:
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
from typing import List
|
||||
|
||||
from kitty.conf.generate import write_output
|
||||
@@ -11,14 +12,25 @@ from kitty.conf.generate import write_output
|
||||
def patch_color_list(path: str, colors: List[str], name: str, spc: str = ' ') -> None:
|
||||
with open(path, 'r+') as f:
|
||||
raw = f.read()
|
||||
nraw = re.sub(
|
||||
fr'(# {name}_COLORS_START).+?(\s+# {name}_COLORS_END)',
|
||||
r'\1' + f'\n{spc}' + f'\n{spc}'.join(map(lambda x: f'{x!r},', sorted(colors))) + r'\2',
|
||||
raw, flags=re.DOTALL | re.MULTILINE)
|
||||
colors = sorted(colors)
|
||||
if path.endswith('.go'):
|
||||
spc = '\t'
|
||||
nraw = re.sub(
|
||||
fr'(// {name}_COLORS_START).+?(\s+// {name}_COLORS_END)',
|
||||
r'\1' + f'\n{spc}' + f'\n{spc}'.join(map(lambda x: f'"{x}":true,', colors)) + r'\2',
|
||||
raw, flags=re.DOTALL | re.MULTILINE)
|
||||
else:
|
||||
nraw = re.sub(
|
||||
fr'(# {name}_COLORS_START).+?(\s+# {name}_COLORS_END)',
|
||||
r'\1' + f'\n{spc}' + f'\n{spc}'.join(map(lambda x: f'{x!r},', colors)) + r'\2',
|
||||
raw, flags=re.DOTALL | re.MULTILINE)
|
||||
if nraw != raw:
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
f.write(nraw)
|
||||
f.flush()
|
||||
if path.endswith('.go'):
|
||||
subprocess.check_call(['gofmt', '-w', path])
|
||||
|
||||
|
||||
def main() -> None:
|
||||
@@ -34,6 +46,7 @@ def main() -> None:
|
||||
elif opt.parser_func.__name__ in ('to_color', 'titlebar_color', 'macos_titlebar_color'):
|
||||
all_colors.append(opt.name)
|
||||
patch_color_list('kitty/rc/set_colors.py', nullable_colors, 'NULLABLE')
|
||||
patch_color_list('tools/cmd/at/set_colors.go', nullable_colors, 'NULLABLE')
|
||||
patch_color_list('kittens/themes/collection.py', all_colors, 'ALL', ' ' * 8)
|
||||
|
||||
from kittens.diff.options.definition import definition as kd
|
||||
|
||||
607
gen-go-code.py
Executable file
607
gen-go-code.py
Executable file
@@ -0,0 +1,607 @@
|
||||
#!./kitty/launcher/kitty +launch
|
||||
# License: GPLv3 Copyright: 2022, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from contextlib import contextmanager, suppress
|
||||
from functools import lru_cache
|
||||
from typing import Any, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Union
|
||||
|
||||
import kitty.constants as kc
|
||||
from kittens.tui.operations import Mode
|
||||
from kittens.tui.spinners import spinners
|
||||
from kitty.cli import (
|
||||
CompletionSpec,
|
||||
GoOption,
|
||||
go_options_for_seq,
|
||||
parse_option_spec,
|
||||
serialize_as_go_string,
|
||||
)
|
||||
from kitty.guess_mime_type import text_mimes
|
||||
from kitty.key_encoding import config_mod_map
|
||||
from kitty.key_names import character_key_name_aliases, functional_key_name_aliases
|
||||
from kitty.options.types import Options
|
||||
from kitty.rc.base import RemoteCommand, all_command_names, command_for_name
|
||||
from kitty.remote_control import global_options_spec
|
||||
from kitty.rgb import color_names
|
||||
|
||||
changed: List[str] = []
|
||||
|
||||
|
||||
# Utils {{{
|
||||
|
||||
def serialize_go_dict(x: Union[Dict[str, int], Dict[int, str], Dict[int, int], Dict[str, str]]) -> str:
|
||||
ans = []
|
||||
|
||||
def s(x: Union[int, str]) -> str:
|
||||
if isinstance(x, int):
|
||||
return str(x)
|
||||
return f'"{serialize_as_go_string(x)}"'
|
||||
|
||||
for k, v in x.items():
|
||||
ans.append(f'{s(k)}: {s(v)}')
|
||||
return '{' + ', '.join(ans) + '}'
|
||||
|
||||
|
||||
def replace(template: str, **kw: str) -> str:
|
||||
for k, v in kw.items():
|
||||
template = template.replace(k, v)
|
||||
return template
|
||||
# }}}
|
||||
|
||||
|
||||
# Completions {{{
|
||||
|
||||
@lru_cache
|
||||
def kitten_cli_docs(kitten: str) -> Any:
|
||||
from kittens.runner import get_kitten_cli_docs
|
||||
return get_kitten_cli_docs(kitten)
|
||||
|
||||
|
||||
@lru_cache
|
||||
def go_options_for_kitten(kitten: str) -> Tuple[Sequence[GoOption], Optional[CompletionSpec]]:
|
||||
kcd = kitten_cli_docs(kitten)
|
||||
if kcd:
|
||||
ospec = kcd['options']
|
||||
return (tuple(go_options_for_seq(parse_option_spec(ospec())[0])), kcd.get('args_completion'))
|
||||
return (), None
|
||||
|
||||
|
||||
def generate_kittens_completion() -> None:
|
||||
from kittens.runner import all_kitten_names, get_kitten_wrapper_of
|
||||
for kitten in sorted(all_kitten_names()):
|
||||
kn = 'kitten_' + kitten
|
||||
print(f'{kn} := plus_kitten.AddSubCommand(&cli.Command{{Name:"{kitten}", Group: "Kittens"}})')
|
||||
wof = get_kitten_wrapper_of(kitten)
|
||||
if wof:
|
||||
print(f'{kn}.ArgCompleter = cli.CompletionForWrapper("{serialize_as_go_string(wof)}")')
|
||||
print(f'{kn}.OnlyArgsAllowed = true')
|
||||
continue
|
||||
gopts, ac = go_options_for_kitten(kitten)
|
||||
if gopts or ac:
|
||||
for opt in gopts:
|
||||
print(opt.as_option(kn))
|
||||
if ac is not None:
|
||||
print(''.join(ac.as_go_code(kn + '.ArgCompleter', ' = ')))
|
||||
else:
|
||||
print(f'{kn}.HelpText = ""')
|
||||
|
||||
|
||||
@lru_cache
|
||||
def clone_safe_launch_opts() -> Sequence[GoOption]:
|
||||
from kitty.launch import clone_safe_opts, options_spec
|
||||
ans = []
|
||||
allowed = clone_safe_opts()
|
||||
for o in go_options_for_seq(parse_option_spec(options_spec())[0]):
|
||||
if o.obj_dict['name'] in allowed:
|
||||
ans.append(o)
|
||||
return tuple(ans)
|
||||
|
||||
|
||||
def completion_for_launch_wrappers(*names: str) -> None:
|
||||
for o in clone_safe_launch_opts():
|
||||
for name in names:
|
||||
print(o.as_option(name))
|
||||
|
||||
|
||||
def generate_completions_for_kitty() -> None:
|
||||
from kitty.config import option_names_for_completion
|
||||
print('package completion\n')
|
||||
print('import "kitty/tools/cli"')
|
||||
print('import "kitty/tools/cmd/tool"')
|
||||
print('import "kitty/tools/cmd/at"')
|
||||
conf_names = ', '.join((f'"{serialize_as_go_string(x)}"' for x in option_names_for_completion()))
|
||||
print('var kitty_option_names_for_completion = []string{' + conf_names + '}')
|
||||
|
||||
print('func kitty(root *cli.Command) {')
|
||||
|
||||
# The kitty exe
|
||||
print('k := root.AddSubCommand(&cli.Command{'
|
||||
'Name:"kitty", SubCommandIsOptional: true, ArgCompleter: cli.CompleteExecutableFirstArg, SubCommandMustBeFirst: true })')
|
||||
print('kt := root.AddSubCommand(&cli.Command{Name:"kitten", SubCommandMustBeFirst: true })')
|
||||
print('tool.KittyToolEntryPoints(kt)')
|
||||
for opt in go_options_for_seq(parse_option_spec()[0]):
|
||||
print(opt.as_option('k'))
|
||||
|
||||
# kitty +
|
||||
print('plus := k.AddSubCommand(&cli.Command{Name:"+", Group:"Entry points", ShortDescription: "Various special purpose tools and kittens"})')
|
||||
|
||||
# kitty +launch
|
||||
print('plus_launch := plus.AddSubCommand(&cli.Command{'
|
||||
'Name:"launch", Group:"Entry points", ShortDescription: "Launch Python scripts", ArgCompleter: complete_plus_launch})')
|
||||
print('k.AddClone("", plus_launch).Name = "+launch"')
|
||||
|
||||
# kitty +list-fonts
|
||||
print('plus_list_fonts := plus.AddSubCommand(&cli.Command{'
|
||||
'Name:"list-fonts", Group:"Entry points", ShortDescription: "List all available monospaced fonts"})')
|
||||
print('k.AddClone("", plus_list_fonts).Name = "+list-fonts"')
|
||||
|
||||
# kitty +runpy
|
||||
print('plus_runpy := plus.AddSubCommand(&cli.Command{'
|
||||
'Name: "runpy", Group:"Entry points", ArgCompleter: complete_plus_runpy, ShortDescription: "Run Python code"})')
|
||||
print('k.AddClone("", plus_runpy).Name = "+runpy"')
|
||||
|
||||
# kitty +open
|
||||
print('plus_open := plus.AddSubCommand(&cli.Command{'
|
||||
'Name:"open", Group:"Entry points", ArgCompleter: complete_plus_open, ShortDescription: "Open files and URLs"})')
|
||||
print('for _, og := range k.OptionGroups { plus_open.OptionGroups = append(plus_open.OptionGroups, og.Clone(plus_open)) }')
|
||||
print('k.AddClone("", plus_open).Name = "+open"')
|
||||
|
||||
# kitty +kitten
|
||||
print('plus_kitten := plus.AddSubCommand(&cli.Command{Name:"kitten", Group:"Kittens", SubCommandMustBeFirst: true})')
|
||||
generate_kittens_completion()
|
||||
print('k.AddClone("", plus_kitten).Name = "+kitten"')
|
||||
|
||||
# @
|
||||
print('at.EntryPoint(k)')
|
||||
|
||||
# clone-in-kitty, edit-in-kitty
|
||||
print('cik := root.AddSubCommand(&cli.Command{Name:"clone-in-kitty"})')
|
||||
completion_for_launch_wrappers('cik')
|
||||
|
||||
print('}')
|
||||
print('func init() {')
|
||||
print('cli.RegisterExeForCompletion(kitty)')
|
||||
print('}')
|
||||
# }}}
|
||||
|
||||
|
||||
# rc command wrappers {{{
|
||||
json_field_types: Dict[str, str] = {
|
||||
'bool': 'bool', 'str': 'escaped_string', 'list.str': '[]escaped_string', 'dict.str': 'map[escaped_string]escaped_string', 'float': 'float64', 'int': 'int',
|
||||
'scroll_amount': 'any', 'spacing': 'any', 'colors': 'any',
|
||||
}
|
||||
|
||||
|
||||
def go_field_type(json_field_type: str) -> str:
|
||||
q = json_field_types.get(json_field_type)
|
||||
if q:
|
||||
return q
|
||||
if json_field_type.startswith('choices.'):
|
||||
return 'string'
|
||||
if '.' in json_field_type:
|
||||
p, r = json_field_type.split('.', 1)
|
||||
p = {'list': '[]', 'dict': 'map[string]'}[p]
|
||||
return p + go_field_type(r)
|
||||
raise TypeError(f'Unknown JSON field type: {json_field_type}')
|
||||
|
||||
|
||||
class JSONField:
|
||||
|
||||
def __init__(self, line: str) -> None:
|
||||
field_def = line.split(':', 1)[0]
|
||||
self.required = False
|
||||
self.field, self.field_type = field_def.split('/', 1)
|
||||
if self.field.endswith('+'):
|
||||
self.required = True
|
||||
self.field = self.field[:-1]
|
||||
self.struct_field_name = self.field[0].upper() + self.field[1:]
|
||||
|
||||
def go_declaration(self) -> str:
|
||||
return self.struct_field_name + ' ' + go_field_type(self.field_type) + f'`json:"{self.field},omitempty"`'
|
||||
|
||||
|
||||
def go_code_for_remote_command(name: str, cmd: RemoteCommand, template: str) -> str:
|
||||
template = '\n' + template[len('//go:build exclude'):]
|
||||
NO_RESPONSE_BASE = 'false'
|
||||
af: List[str] = []
|
||||
a = af.append
|
||||
af.extend(cmd.args.as_go_completion_code('ans'))
|
||||
od: List[str] = []
|
||||
option_map: Dict[str, GoOption] = {}
|
||||
for o in rc_command_options(name):
|
||||
option_map[o.go_var_name] = o
|
||||
a(o.as_option('ans'))
|
||||
if o.go_var_name in ('NoResponse', 'ResponseTimeout'):
|
||||
continue
|
||||
od.append(o.struct_declaration())
|
||||
jd: List[str] = []
|
||||
json_fields = []
|
||||
field_types: Dict[str, str] = {}
|
||||
for line in cmd.protocol_spec.splitlines():
|
||||
line = line.strip()
|
||||
if ':' not in line:
|
||||
continue
|
||||
f = JSONField(line)
|
||||
json_fields.append(f)
|
||||
field_types[f.field] = f.field_type
|
||||
jd.append(f.go_declaration())
|
||||
jc: List[str] = []
|
||||
handled_fields: Set[str] = set()
|
||||
jc.extend(cmd.args.as_go_code(name, field_types, handled_fields))
|
||||
|
||||
unhandled = {}
|
||||
used_options = set()
|
||||
for field in json_fields:
|
||||
oq = (cmd.field_to_option_map or {}).get(field.field, field.field)
|
||||
oq = ''.join(x.capitalize() for x in oq.split('_'))
|
||||
if oq in option_map:
|
||||
o = option_map[oq]
|
||||
used_options.add(oq)
|
||||
if field.field_type == 'str':
|
||||
jc.append(f'payload.{field.struct_field_name} = escaped_string(options_{name}.{o.go_var_name})')
|
||||
elif field.field_type == 'list.str':
|
||||
jc.append(f'payload.{field.struct_field_name} = escape_list_of_strings(options_{name}.{o.go_var_name})')
|
||||
elif field.field_type == 'dict.str':
|
||||
jc.append(f'payload.{field.struct_field_name} = escape_dict_of_strings(options_{name}.{o.go_var_name})')
|
||||
else:
|
||||
jc.append(f'payload.{field.struct_field_name} = options_{name}.{o.go_var_name}')
|
||||
elif field.field in handled_fields:
|
||||
pass
|
||||
else:
|
||||
unhandled[field.field] = field
|
||||
for x in tuple(unhandled):
|
||||
if x == 'match_window' and 'Match' in option_map and 'Match' not in used_options:
|
||||
used_options.add('Match')
|
||||
o = option_map['Match']
|
||||
field = unhandled[x]
|
||||
if field.field_type == 'str':
|
||||
jc.append(f'payload.{field.struct_field_name} = escaped_string(options_{name}.{o.go_var_name})')
|
||||
else:
|
||||
jc.append(f'payload.{field.struct_field_name} = options_{name}.{o.go_var_name}')
|
||||
del unhandled[x]
|
||||
if unhandled:
|
||||
raise SystemExit(f'Cant map fields: {", ".join(unhandled)} for cmd: {name}')
|
||||
if name != 'send_text':
|
||||
unused_options = set(option_map) - used_options - {'NoResponse', 'ResponseTimeout'}
|
||||
if unused_options:
|
||||
raise SystemExit(f'Unused options: {", ".join(unused_options)} for command: {name}')
|
||||
|
||||
argspec = cmd.args.spec
|
||||
if argspec:
|
||||
argspec = ' ' + argspec
|
||||
ans = replace(
|
||||
template,
|
||||
CMD_NAME=name, __FILE__=__file__, CLI_NAME=name.replace('_', '-'),
|
||||
SHORT_DESC=serialize_as_go_string(cmd.short_desc),
|
||||
LONG_DESC=serialize_as_go_string(cmd.desc.strip()),
|
||||
IS_ASYNC='true' if cmd.is_asynchronous else 'false',
|
||||
NO_RESPONSE_BASE=NO_RESPONSE_BASE, ADD_FLAGS_CODE='\n'.join(af),
|
||||
WAIT_TIMEOUT=str(cmd.response_timeout),
|
||||
OPTIONS_DECLARATION_CODE='\n'.join(od),
|
||||
JSON_DECLARATION_CODE='\n'.join(jd),
|
||||
JSON_INIT_CODE='\n'.join(jc), ARGSPEC=argspec,
|
||||
STRING_RESPONSE_IS_ERROR='true' if cmd.string_return_is_error else 'false',
|
||||
STREAM_WANTED='true' if cmd.reads_streaming_data else 'false',
|
||||
)
|
||||
return ans
|
||||
# }}}
|
||||
|
||||
|
||||
# kittens {{{
|
||||
|
||||
@lru_cache
|
||||
def wrapped_kittens() -> Sequence[str]:
|
||||
with open('shell-integration/ssh/kitty') as f:
|
||||
for line in f:
|
||||
if line.startswith(' wrapped_kittens="'):
|
||||
val = line.strip().partition('"')[2][:-1]
|
||||
return tuple(sorted(filter(None, val.split())))
|
||||
raise Exception('Failed to read wrapped kittens from kitty wrapper script')
|
||||
|
||||
|
||||
def kitten_clis() -> None:
|
||||
for kitten in wrapped_kittens():
|
||||
with replace_if_needed(f'tools/cmd/{kitten}/cli_generated.go'):
|
||||
od = []
|
||||
kcd = kitten_cli_docs(kitten)
|
||||
has_underscore = '_' in kitten
|
||||
print(f'package {kitten}')
|
||||
print('import "kitty/tools/cli"')
|
||||
print('func create_cmd(root *cli.Command, run_func func(*cli.Command, *Options, []string)(int, error)) {')
|
||||
print('ans := root.AddSubCommand(&cli.Command{')
|
||||
print(f'Name: "{kitten}",')
|
||||
print(f'ShortDescription: "{serialize_as_go_string(kcd["short_desc"])}",')
|
||||
if kcd['usage']:
|
||||
print(f'Usage: "[options] {serialize_as_go_string(kcd["usage"])}",')
|
||||
print(f'HelpText: "{serialize_as_go_string(kcd["help_text"])}",')
|
||||
print('Run: func(cmd *cli.Command, args []string) (int, error) {')
|
||||
print('opts := Options{}')
|
||||
print('err := cmd.GetOptionValues(&opts)')
|
||||
print('if err != nil { return 1, err }')
|
||||
print('return run_func(cmd, &opts, args)},')
|
||||
if has_underscore:
|
||||
print('Hidden: true,')
|
||||
print('})')
|
||||
gopts, ac = go_options_for_kitten(kitten)
|
||||
for opt in gopts:
|
||||
print(opt.as_option('ans'))
|
||||
od.append(opt.struct_declaration())
|
||||
if ac is not None:
|
||||
print(''.join(ac.as_go_code('ans.ArgCompleter', ' = ')))
|
||||
if has_underscore:
|
||||
print("clone := root.AddClone(ans.Group, ans)")
|
||||
print('clone.Hidden = false')
|
||||
print(f'clone.Name = "{serialize_as_go_string(kitten.replace("_", "-"))}"')
|
||||
print('}')
|
||||
print('type Options struct {')
|
||||
print('\n'.join(od))
|
||||
print('}')
|
||||
|
||||
# }}}
|
||||
|
||||
|
||||
# Constants {{{
|
||||
|
||||
def generate_spinners() -> str:
|
||||
ans = ['package tui', 'import "time"', 'func NewSpinner(name string) *Spinner {', 'var ans *Spinner', 'switch name {']
|
||||
a = ans.append
|
||||
for name, spinner in spinners.items():
|
||||
a(f'case "{serialize_as_go_string(name)}":')
|
||||
a('ans = &Spinner{')
|
||||
a(f'Name: "{serialize_as_go_string(name)}",')
|
||||
a(f'interval: {spinner["interval"]},')
|
||||
frames = ', '.join(f'"{serialize_as_go_string(x)}"' for x in spinner['frames'])
|
||||
a(f'frames: []string{{{frames}}},')
|
||||
a('}')
|
||||
a('}')
|
||||
a('if ans != nil {')
|
||||
a('ans.interval *= time.Millisecond')
|
||||
a('ans.current_frame = -1')
|
||||
a('ans.last_change_at = time.Now().Add(-ans.interval)')
|
||||
a('}')
|
||||
a('return ans}')
|
||||
return '\n'.join(ans)
|
||||
|
||||
|
||||
def generate_color_names() -> str:
|
||||
return 'package style\n\nvar ColorNames = map[string]RGBA{' + '\n'.join(
|
||||
f'\t"{name}": RGBA{{ Red:{val.red}, Green:{val.green}, Blue:{val.blue} }},'
|
||||
for name, val in color_names.items()
|
||||
) + '\n}' + '\n\nvar ColorTable = [256]uint32{' + ', '.join(
|
||||
f'{x}' for x in Options.color_table) + '}\n'
|
||||
|
||||
|
||||
def load_ref_map() -> Dict[str, Dict[str, str]]:
|
||||
with open('kitty/docs_ref_map_generated.h') as f:
|
||||
raw = f.read()
|
||||
raw = raw.split('{', 1)[1].split('}', 1)[0]
|
||||
data = json.loads(bytes(bytearray(json.loads(f'[{raw}]'))))
|
||||
return data # type: ignore
|
||||
|
||||
|
||||
def generate_constants() -> str:
|
||||
ref_map = load_ref_map()
|
||||
dp = ", ".join(map(lambda x: f'"{serialize_as_go_string(x)}"', kc.default_pager_for_help))
|
||||
return f'''\
|
||||
package kitty
|
||||
|
||||
type VersionType struct {{
|
||||
Major, Minor, Patch int
|
||||
}}
|
||||
const VersionString string = "{kc.str_version}"
|
||||
const WebsiteBaseURL string = "{kc.website_base_url}"
|
||||
const VCSRevision string = ""
|
||||
const RC_ENCRYPTION_PROTOCOL_VERSION string = "{kc.RC_ENCRYPTION_PROTOCOL_VERSION}"
|
||||
const IsFrozenBuild bool = false
|
||||
const IsStandaloneBuild bool = false
|
||||
const HandleTermiosSignals = {Mode.HANDLE_TERMIOS_SIGNALS.value[0]}
|
||||
var Version VersionType = VersionType{{Major: {kc.version.major}, Minor: {kc.version.minor}, Patch: {kc.version.patch},}}
|
||||
var DefaultPager []string = []string{{ {dp} }}
|
||||
var FunctionalKeyNameAliases = map[string]string{serialize_go_dict(functional_key_name_aliases)}
|
||||
var CharacterKeyNameAliases = map[string]string{serialize_go_dict(character_key_name_aliases)}
|
||||
var ConfigModMap = map[string]uint16{serialize_go_dict(config_mod_map)}
|
||||
var RefMap = map[string]string{serialize_go_dict(ref_map['ref'])}
|
||||
var DocTitleMap = map[string]string{serialize_go_dict(ref_map['doc'])}
|
||||
''' # }}}
|
||||
|
||||
|
||||
# Boilerplate {{{
|
||||
|
||||
@contextmanager
|
||||
def replace_if_needed(path: str, show_diff: bool = False) -> Iterator[io.StringIO]:
|
||||
buf = io.StringIO()
|
||||
origb = sys.stdout
|
||||
sys.stdout = buf
|
||||
try:
|
||||
yield buf
|
||||
finally:
|
||||
sys.stdout = origb
|
||||
orig = ''
|
||||
with suppress(FileNotFoundError), open(path, 'r') as f:
|
||||
orig = f.read()
|
||||
new = buf.getvalue()
|
||||
new = f'// Code generated by {os.path.basename(__file__)}; DO NOT EDIT.\n\n' + new
|
||||
if orig != new:
|
||||
changed.append(path)
|
||||
if show_diff:
|
||||
with open(path + '.new', 'w') as f:
|
||||
f.write(new)
|
||||
subprocess.run(['diff', '-Naurp', path, f.name], stdout=open('/dev/tty', 'w'))
|
||||
os.remove(f.name)
|
||||
with open(path, 'w') as f:
|
||||
f.write(new)
|
||||
|
||||
|
||||
@lru_cache(maxsize=256)
|
||||
def rc_command_options(name: str) -> Tuple[GoOption, ...]:
|
||||
cmd = command_for_name(name)
|
||||
return tuple(go_options_for_seq(parse_option_spec(cmd.options_spec or '\n\n')[0]))
|
||||
|
||||
|
||||
def update_at_commands() -> None:
|
||||
with open('tools/cmd/at/template.go') as f:
|
||||
template = f.read()
|
||||
for name in all_command_names():
|
||||
cmd = command_for_name(name)
|
||||
code = go_code_for_remote_command(name, cmd, template)
|
||||
dest = f'tools/cmd/at/cmd_{name}_generated.go'
|
||||
with replace_if_needed(dest) as f:
|
||||
f.write(code)
|
||||
struct_def = []
|
||||
opt_def = []
|
||||
for o in go_options_for_seq(parse_option_spec(global_options_spec())[0]):
|
||||
struct_def.append(o.struct_declaration())
|
||||
opt_def.append(o.as_option(depth=1, group="Global options"))
|
||||
sdef = '\n'.join(struct_def)
|
||||
odef = '\n'.join(opt_def)
|
||||
code = f'''
|
||||
package at
|
||||
import "kitty/tools/cli"
|
||||
type rc_global_options struct {{
|
||||
{sdef}
|
||||
}}
|
||||
var rc_global_opts rc_global_options
|
||||
|
||||
func add_rc_global_opts(cmd *cli.Command) {{
|
||||
{odef}
|
||||
}}
|
||||
'''
|
||||
with replace_if_needed('tools/cmd/at/global_opts_generated.go') as f:
|
||||
f.write(code)
|
||||
|
||||
|
||||
def update_completion() -> None:
|
||||
with replace_if_needed('tools/cmd/completion/kitty_generated.go'):
|
||||
generate_completions_for_kitty()
|
||||
with replace_if_needed('tools/cmd/edit_in_kitty/launch_generated.go'):
|
||||
print('package edit_in_kitty')
|
||||
print('import "kitty/tools/cli"')
|
||||
print('func AddCloneSafeOpts(cmd *cli.Command) {')
|
||||
completion_for_launch_wrappers('cmd')
|
||||
print(''.join(CompletionSpec.from_string('type:file mime:text/* group:"Text files"').as_go_code('cmd.ArgCompleter', ' = ')))
|
||||
print('}')
|
||||
|
||||
|
||||
def define_enum(package_name: str, type_name: str, items: str, underlying_type: str = 'uint') -> str:
|
||||
actions = []
|
||||
for x in items.splitlines():
|
||||
x = x.strip()
|
||||
if x:
|
||||
actions.append(x)
|
||||
ans = [f'package {package_name}', 'import "strconv"', f'type {type_name} {underlying_type}', 'const (']
|
||||
stringer = [f'func (ac {type_name}) String() string ''{', 'switch(ac) {']
|
||||
for i, ac in enumerate(actions):
|
||||
stringer.append(f'case {ac}: return "{ac}"')
|
||||
if i == 0:
|
||||
ac = ac + f' {type_name} = iota'
|
||||
ans.append(ac)
|
||||
ans.append(')')
|
||||
stringer.append('}\nreturn strconv.Itoa(int(ac)) }')
|
||||
return '\n'.join(ans + stringer)
|
||||
|
||||
|
||||
def generate_readline_actions() -> str:
|
||||
return define_enum('readline', 'Action', '''\
|
||||
ActionNil
|
||||
|
||||
ActionBackspace
|
||||
ActionDelete
|
||||
ActionMoveToStartOfLine
|
||||
ActionMoveToEndOfLine
|
||||
ActionMoveToStartOfDocument
|
||||
ActionMoveToEndOfDocument
|
||||
ActionMoveToEndOfWord
|
||||
ActionMoveToStartOfWord
|
||||
ActionCursorLeft
|
||||
ActionCursorRight
|
||||
ActionEndInput
|
||||
ActionAcceptInput
|
||||
ActionCursorUp
|
||||
ActionHistoryPreviousOrCursorUp
|
||||
ActionCursorDown
|
||||
ActionHistoryNextOrCursorDown
|
||||
ActionHistoryNext
|
||||
ActionHistoryPrevious
|
||||
ActionHistoryFirst
|
||||
ActionHistoryLast
|
||||
ActionHistoryIncrementalSearchBackwards
|
||||
ActionHistoryIncrementalSearchForwards
|
||||
ActionTerminateHistorySearchAndApply
|
||||
ActionTerminateHistorySearchAndRestore
|
||||
ActionClearScreen
|
||||
ActionAddText
|
||||
ActionAbortCurrentLine
|
||||
|
||||
ActionStartKillActions
|
||||
ActionKillToEndOfLine
|
||||
ActionKillToStartOfLine
|
||||
ActionKillNextWord
|
||||
ActionKillPreviousWord
|
||||
ActionKillPreviousSpaceDelimitedWord
|
||||
ActionEndKillActions
|
||||
ActionYank
|
||||
ActionPopYank
|
||||
|
||||
ActionNumericArgumentDigit0
|
||||
ActionNumericArgumentDigit1
|
||||
ActionNumericArgumentDigit2
|
||||
ActionNumericArgumentDigit3
|
||||
ActionNumericArgumentDigit4
|
||||
ActionNumericArgumentDigit5
|
||||
ActionNumericArgumentDigit6
|
||||
ActionNumericArgumentDigit7
|
||||
ActionNumericArgumentDigit8
|
||||
ActionNumericArgumentDigit9
|
||||
ActionNumericArgumentDigitMinus
|
||||
|
||||
ActionCompleteForward
|
||||
ActionCompleteBackward
|
||||
''')
|
||||
|
||||
|
||||
def generate_mimetypes() -> str:
|
||||
import mimetypes
|
||||
if not mimetypes.inited:
|
||||
mimetypes.init()
|
||||
ans = ['package utils', 'import "sync"', 'var only_once sync.Once', 'var builtin_types_map map[string]string',
|
||||
'func set_builtins() {', 'builtin_types_map = map[string]string{',]
|
||||
for k, v in mimetypes.types_map.items():
|
||||
ans.append(f' "{serialize_as_go_string(k)}": "{serialize_as_go_string(v)}",')
|
||||
ans.append('}}')
|
||||
return '\n'.join(ans)
|
||||
|
||||
|
||||
def generate_textual_mimetypes() -> str:
|
||||
ans = ['package utils', 'var KnownTextualMimes = map[string]bool{',]
|
||||
for k in text_mimes:
|
||||
ans.append(f' "{serialize_as_go_string(k)}": true,')
|
||||
ans.append('}')
|
||||
return '\n'.join(ans)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
with replace_if_needed('constants_generated.go') as f:
|
||||
f.write(generate_constants())
|
||||
with replace_if_needed('tools/utils/style/color-names_generated.go') as f:
|
||||
f.write(generate_color_names())
|
||||
with replace_if_needed('tools/tui/readline/actions_generated.go') as f:
|
||||
f.write(generate_readline_actions())
|
||||
with replace_if_needed('tools/tui/spinners_generated.go') as f:
|
||||
f.write(generate_spinners())
|
||||
with replace_if_needed('tools/utils/mimetypes_generated.go') as f:
|
||||
f.write(generate_mimetypes())
|
||||
with replace_if_needed('tools/utils/mimetypes_textual_generated.go') as f:
|
||||
f.write(generate_textual_mimetypes())
|
||||
|
||||
update_completion()
|
||||
update_at_commands()
|
||||
kitten_clis()
|
||||
print(json.dumps(changed, indent=2))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main() # }}}
|
||||
@@ -2,8 +2,8 @@
|
||||
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import string
|
||||
from typing import Dict, List, Any
|
||||
from pprint import pformat
|
||||
from typing import Any, Dict, List, Union
|
||||
|
||||
functional_key_defs = '''# {{{
|
||||
# kitty XKB macVK macU
|
||||
@@ -130,7 +130,7 @@ functional_encoding_overrides = {
|
||||
}
|
||||
different_trailer_functionals = {
|
||||
'up': 'A', 'down': 'B', 'right': 'C', 'left': 'D', 'kp_begin': 'E', 'end': 'F', 'home': 'H',
|
||||
'f1': 'P', 'f2': 'Q', 'f3': 'R', 'f4': 'S', 'enter': 'u', 'tab': 'u',
|
||||
'f1': 'P', 'f2': 'Q', 'f3': '~', 'f4': 'S', 'enter': 'u', 'tab': 'u',
|
||||
'backspace': 'u', 'escape': 'u'
|
||||
}
|
||||
|
||||
@@ -248,6 +248,19 @@ def serialize_dict(x: Dict[Any, Any]) -> str:
|
||||
return pformat(x, indent=4).replace('{', '{\n ', 1)
|
||||
|
||||
|
||||
def serialize_go_dict(x: Union[Dict[str, int], Dict[int, str], Dict[int, int]]) -> str:
|
||||
ans = []
|
||||
|
||||
def s(x: Union[int, str]) -> str:
|
||||
if isinstance(x, int):
|
||||
return str(x)
|
||||
return f'"{x}"'
|
||||
|
||||
for k, v in x.items():
|
||||
ans.append(f'{s(k)}: {s(v)}')
|
||||
return '{' + ', '.join(ans) + '}'
|
||||
|
||||
|
||||
def generate_glfw_header() -> None:
|
||||
lines = [
|
||||
'typedef enum {',
|
||||
@@ -309,14 +322,20 @@ def generate_functional_table() -> None:
|
||||
patch_file('kitty/key_encoding.c', 'special numbers', '\n'.join(enc_lines))
|
||||
code_to_name = {v: k.upper() for k, v in name_to_code.items()}
|
||||
csi_map = {v: name_to_code[k] for k, v in functional_encoding_overrides.items()}
|
||||
letter_trailer_codes = {
|
||||
v: functional_encoding_overrides.get(k, name_to_code.get(k))
|
||||
letter_trailer_codes: Dict[str, int] = {
|
||||
v: functional_encoding_overrides.get(k, name_to_code.get(k, 0))
|
||||
for k, v in different_trailer_functionals.items() if v in 'ABCDEHFPQRSZ'}
|
||||
text = f'functional_key_number_to_name_map = {serialize_dict(code_to_name)}'
|
||||
text += f'\ncsi_number_to_functional_number_map = {serialize_dict(csi_map)}'
|
||||
text += f'\nletter_trailer_to_csi_number_map = {letter_trailer_codes!r}'
|
||||
text += f'\ntilde_trailers = {tilde_trailers!r}'
|
||||
patch_file('kitty/key_encoding.py', 'csi mapping', text, start_marker='# ', end_marker='')
|
||||
text = f'var functional_key_number_to_name_map = map[int]string{serialize_go_dict(code_to_name)}\n'
|
||||
text += f'\nvar csi_number_to_functional_number_map = map[int]int{serialize_go_dict(csi_map)}\n'
|
||||
text += f'\nvar letter_trailer_to_csi_number_map = map[string]int{serialize_go_dict(letter_trailer_codes)}\n'
|
||||
tt = ', '.join(f'{x}: true' for x in tilde_trailers)
|
||||
text += '\nvar tilde_trailers = map[int]bool{' + f'{tt}' + '}\n'
|
||||
patch_file('tools/tui/loop/key-encoding.go', 'csi mapping', text, start_marker='// ', end_marker='')
|
||||
|
||||
|
||||
def generate_legacy_text_key_maps() -> None:
|
||||
|
||||
104
gen-wcwidth.py
104
gen-wcwidth.py
@@ -3,17 +3,26 @@
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from contextlib import contextmanager
|
||||
from datetime import date
|
||||
from functools import partial
|
||||
from functools import lru_cache, partial
|
||||
from html.entities import html5
|
||||
from itertools import groupby
|
||||
from operator import itemgetter
|
||||
from typing import (
|
||||
Callable, DefaultDict, Dict, FrozenSet, Generator, Iterable, List,
|
||||
Optional, Set, Tuple, Union
|
||||
Callable,
|
||||
DefaultDict,
|
||||
Dict,
|
||||
FrozenSet,
|
||||
Generator,
|
||||
Iterable,
|
||||
List,
|
||||
Optional,
|
||||
Set,
|
||||
Tuple,
|
||||
Union,
|
||||
)
|
||||
from urllib.request import urlopen
|
||||
|
||||
@@ -44,6 +53,15 @@ def get_data(fname: str, folder: str = 'UCD') -> Iterable[str]:
|
||||
yield line
|
||||
|
||||
|
||||
@lru_cache(maxsize=2)
|
||||
def unicode_version() -> Tuple[int, int, int]:
|
||||
for line in get_data("ReadMe.txt"):
|
||||
m = re.search(r'Version\s+(\d+)\.(\d+)\.(\d+)', line)
|
||||
if m is not None:
|
||||
return int(m.group(1)), int(m.group(2)), int(m.group(3))
|
||||
raise ValueError('Could not find Unicode Version')
|
||||
|
||||
|
||||
# Map of class names to set of codepoints in class
|
||||
class_maps: Dict[str, Set[int]] = {}
|
||||
all_symbols: Set[int] = set()
|
||||
@@ -254,9 +272,13 @@ def get_ranges(items: List[int]) -> Generator[Union[int, Tuple[int, int]], None,
|
||||
yield a, b
|
||||
|
||||
|
||||
def write_case(spec: Union[Tuple[int, ...], int], p: Callable[..., None]) -> None:
|
||||
def write_case(spec: Union[Tuple[int, ...], int], p: Callable[..., None], for_go: bool = False) -> None:
|
||||
if isinstance(spec, tuple):
|
||||
p('\t\tcase 0x{:x} ... 0x{:x}:'.format(*spec))
|
||||
if for_go:
|
||||
v = ', '.join(f'0x{x:x}' for x in range(spec[0], spec[1] + 1))
|
||||
p(f'\t\tcase {v}:')
|
||||
else:
|
||||
p('\t\tcase 0x{:x} ... 0x{:x}:'.format(*spec))
|
||||
else:
|
||||
p(f'\t\tcase 0x{spec:x}:')
|
||||
|
||||
@@ -265,8 +287,8 @@ def write_case(spec: Union[Tuple[int, ...], int], p: Callable[..., None]) -> Non
|
||||
def create_header(path: str, include_data_types: bool = True) -> Generator[Callable[..., None], None, None]:
|
||||
with open(path, 'w') as f:
|
||||
p = partial(print, file=f)
|
||||
p('// unicode data, built from the unicode standard on:', date.today())
|
||||
p('// see gen-wcwidth.py')
|
||||
p('// Unicode data, built from the Unicode Standard', '.'.join(map(str, unicode_version())))
|
||||
p(f'// Code generated by {os.path.basename(__file__)}, DO NOT EDIT.', end='\n\n')
|
||||
if path.endswith('.h'):
|
||||
p('#pragma once')
|
||||
if include_data_types:
|
||||
@@ -476,7 +498,7 @@ def gen_names() -> None:
|
||||
|
||||
# The trie
|
||||
p('typedef struct { uint32_t children_offset; uint32_t match_offset; } word_trie;\n')
|
||||
all_trie_nodes: List['TrieNode'] = [] # noqa
|
||||
all_trie_nodes: List['TrieNode'] = []
|
||||
|
||||
class TrieNode:
|
||||
|
||||
@@ -524,45 +546,69 @@ def gen_names() -> None:
|
||||
|
||||
def gen_wcwidth() -> None:
|
||||
seen: Set[int] = set()
|
||||
non_printing = class_maps['Cc'] | class_maps['Cf'] | class_maps['Cs']
|
||||
|
||||
def add(p: Callable[..., None], comment: str, chars_: Union[Set[int], FrozenSet[int]], ret: int) -> None:
|
||||
def add(p: Callable[..., None], comment: str, chars_: Union[Set[int], FrozenSet[int]], ret: int, for_go: bool = False) -> None:
|
||||
chars = chars_ - seen
|
||||
seen.update(chars)
|
||||
p(f'\t\t// {comment} ({len(chars)} codepoints)' + ' {{' '{')
|
||||
for spec in get_ranges(list(chars)):
|
||||
write_case(spec, p)
|
||||
write_case(spec, p, for_go)
|
||||
p(f'\t\t\treturn {ret};')
|
||||
p('\t\t// }}}\n')
|
||||
|
||||
with create_header('kitty/wcwidth-std.h') as p:
|
||||
p('static inline int\nwcwidth_std(int32_t code) {')
|
||||
p('\tif (LIKELY(0x20 <= code && code <= 0x7e)) return 1;')
|
||||
p('\tswitch(code) {')
|
||||
def add_all(p: Callable[..., None], for_go: bool = False) -> None:
|
||||
seen.clear()
|
||||
add(p, 'Flags', flag_codepoints, 2, for_go)
|
||||
add(p, 'Marks', marks | {0}, 0, for_go)
|
||||
add(p, 'Non-printing characters', non_printing, -1, for_go)
|
||||
add(p, 'Private use', class_maps['Co'], -3, for_go)
|
||||
add(p, 'Text Presentation', narrow_emoji, 1, for_go)
|
||||
add(p, 'East Asian ambiguous width', ambiguous, -2, for_go)
|
||||
add(p, 'East Asian double width', doublewidth, 2, for_go)
|
||||
add(p, 'Emoji Presentation', wide_emoji, 2, for_go)
|
||||
|
||||
non_printing = class_maps['Cc'] | class_maps['Cf'] | class_maps['Cs']
|
||||
add(p, 'Flags', flag_codepoints, 2)
|
||||
add(p, 'Marks', marks | {0}, 0)
|
||||
add(p, 'Non-printing characters', non_printing, -1)
|
||||
add(p, 'Private use', class_maps['Co'], -3)
|
||||
add(p, 'Text Presentation', narrow_emoji, 1)
|
||||
add(p, 'East Asian ambiguous width', ambiguous, -2)
|
||||
add(p, 'East Asian double width', doublewidth, 2)
|
||||
add(p, 'Emoji Presentation', wide_emoji, 2)
|
||||
add(p, 'Not assigned in the unicode character database', not_assigned, -4, for_go)
|
||||
|
||||
add(p, 'Not assigned in the unicode character database', not_assigned, -4)
|
||||
|
||||
p('\t\tdefault: return 1;')
|
||||
p('\t\tdefault:\n\t\t\treturn 1;')
|
||||
p('\t}')
|
||||
p('\treturn 1;\n}')
|
||||
if for_go:
|
||||
p('\t}')
|
||||
else:
|
||||
p('\treturn 1;\n}')
|
||||
|
||||
with create_header('kitty/wcwidth-std.h') as p, open('tools/wcswidth/std.go', 'w') as gof:
|
||||
gop = partial(print, file=gof)
|
||||
gop('package wcswidth\n\n')
|
||||
gop('func Runewidth(code rune) int {')
|
||||
p('static inline int\nwcwidth_std(int32_t code) {')
|
||||
p('\tif (LIKELY(0x20 <= code && code <= 0x7e)) { return 1; }')
|
||||
p('\tswitch(code) {')
|
||||
gop('\tswitch(code) {')
|
||||
add_all(p)
|
||||
add_all(gop, True)
|
||||
|
||||
p('static inline bool\nis_emoji_presentation_base(uint32_t code) {')
|
||||
gop('func IsEmojiPresentationBase(code rune) bool {')
|
||||
p('\tswitch(code) {')
|
||||
gop('\tswitch(code) {')
|
||||
for spec in get_ranges(list(emoji_presentation_bases)):
|
||||
write_case(spec, p)
|
||||
write_case(spec, gop, for_go=True)
|
||||
p('\t\t\treturn true;')
|
||||
gop('\t\t\treturn true;')
|
||||
p('\t\tdefault: return false;')
|
||||
p('\t}')
|
||||
p('\treturn 1;\n}')
|
||||
gop('\t\tdefault:\n\t\t\treturn false')
|
||||
gop('\t}')
|
||||
p('\treturn true;\n}')
|
||||
gop('\n}')
|
||||
uv = unicode_version()
|
||||
p(f'#define UNICODE_MAJOR_VERSION {uv[0]}')
|
||||
p(f'#define UNICODE_MINOR_VERSION {uv[1]}')
|
||||
p(f'#define UNICODE_PATCH_VERSION {uv[2]}')
|
||||
gop('var UnicodeDatabaseVersion [3]int = [3]int{' f'{uv[0]}, {uv[1]}, {uv[2]}' + '}')
|
||||
subprocess.check_call(['gofmt', '-w', '-s', gof.name])
|
||||
|
||||
|
||||
parse_ucd()
|
||||
|
||||
@@ -8,7 +8,6 @@ import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
|
||||
cmdline = (
|
||||
'glad --out-path {dest} --api gl:core=3.3 '
|
||||
' --extensions GL_ARB_texture_storage,GL_ARB_copy_image,GL_ARB_multisample,GL_ARB_robustness,GL_KHR_debug '
|
||||
|
||||
@@ -1576,6 +1576,62 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
|
||||
return text;
|
||||
}
|
||||
|
||||
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/SysServices/Articles/using.html>
|
||||
|
||||
// Support services receiving "public.utf8-plain-text" and "NSStringPboardType"
|
||||
- (id)validRequestorForSendType:(NSString *)sendType returnType:(NSString *)returnType
|
||||
{
|
||||
if ([sendType isEqual:NSPasteboardTypeString] || [sendType isEqual:@"NSStringPboardType"]) {
|
||||
return self;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Selected text as input to be sent to Services
|
||||
- (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard types:(NSArray *)types
|
||||
{
|
||||
NSString *text = [self accessibilitySelectedText];
|
||||
if (text && [text length] > 0) {
|
||||
if ([types containsObject:NSPasteboardTypeString] == YES) {
|
||||
[pboard declareTypes:@[NSPasteboardTypeString] owner:self];
|
||||
return [pboard setString:text forType:NSPasteboardTypeString];
|
||||
} else if ([types containsObject:@"NSStringPboardType"] == YES) {
|
||||
[pboard declareTypes:@[@"NSStringPboardType"] owner:self];
|
||||
return [pboard setString:text forType:NSPasteboardTypeString];
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Service output to be handled
|
||||
- (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pboard
|
||||
{
|
||||
NSString* text = nil;
|
||||
NSArray *types = [pboard types];
|
||||
if ([types containsObject:NSPasteboardTypeString] == YES) {
|
||||
text = [pboard stringForType:NSPasteboardTypeString]; // public.utf8-plain-text
|
||||
} else if ([types containsObject:@"NSStringPboardType"] == YES) {
|
||||
text = [pboard stringForType:@"NSStringPboardType"]; // for older services (need re-encode?)
|
||||
} else {
|
||||
return NO;
|
||||
}
|
||||
if (text && [text length] > 0) {
|
||||
// Terminal.app inserts the output, do the same
|
||||
const char *utf8 = polymorphic_string_as_utf8(text);
|
||||
if ([self hasMarkedText]) {
|
||||
[self unmarkText];
|
||||
debug_key("Clearing pre-edit because insertText called from readSelectionFromPasteboard\n");
|
||||
GLFWkeyevent glfw_keyevent = {.ime_state = GLFW_IME_PREEDIT_CHANGED};
|
||||
_glfwInputKeyboard(window, &glfw_keyevent);
|
||||
}
|
||||
debug_key("Sending text received in readSelectionFromPasteboard as key event\n");
|
||||
GLFWkeyevent glfw_keyevent = {.text=utf8};
|
||||
_glfwInputKeyboard(window, &glfw_keyevent);
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
// }}}
|
||||
|
||||
|
||||
@@ -14,6 +14,10 @@ is_openbsd = 'openbsd' in _plat
|
||||
base = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def null_func() -> None:
|
||||
return None
|
||||
|
||||
|
||||
class CompileKey(NamedTuple):
|
||||
src: str
|
||||
dest: str
|
||||
@@ -23,7 +27,7 @@ class Command(NamedTuple):
|
||||
desc: str
|
||||
cmd: Sequence[str]
|
||||
is_newer_func: Callable[[], bool]
|
||||
on_success: Callable[[], None] = lambda: None
|
||||
on_success: Callable[[], None] = null_func
|
||||
key: Optional[CompileKey] = None
|
||||
keyfile: Optional[str] = None
|
||||
|
||||
|
||||
4
glfw/ibus_glfw.c
vendored
4
glfw/ibus_glfw.c
vendored
@@ -383,12 +383,12 @@ input_context_created(DBusMessage *msg, const char* errmsg, void *data) {
|
||||
enum Capabilities caps = IBUS_CAP_FOCUS | IBUS_CAP_PREEDIT_TEXT;
|
||||
if (!glfw_dbus_call_method_no_reply(ibus->conn, IBUS_SERVICE, ibus->input_ctx_path, IBUS_INPUT_INTERFACE, "SetCapabilities", DBUS_TYPE_UINT32, &caps, DBUS_TYPE_INVALID)) return;
|
||||
ibus->ok = true;
|
||||
glfw_ibus_set_focused(ibus, false);
|
||||
glfw_ibus_set_focused(ibus, _glfwFocusedWindow() != NULL);
|
||||
glfw_ibus_set_cursor_geometry(ibus, 0, 0, 0, 0);
|
||||
debug("Connected to IBUS daemon for IME input management\n");
|
||||
}
|
||||
|
||||
bool
|
||||
static bool
|
||||
setup_connection(_GLFWIBUSData *ibus) {
|
||||
const char *client_name = "GLFW_Application";
|
||||
const char *address_file_name = get_ibus_address_file_name();
|
||||
|
||||
24
glfw/wl_window.c
vendored
24
glfw/wl_window.c
vendored
@@ -75,7 +75,7 @@ get_activation_token(
|
||||
if (token == NULL) fail("Wayland: failed to create activation request token");
|
||||
if (_glfw.wl.activation_requests.capacity < _glfw.wl.activation_requests.sz + 1) {
|
||||
_glfw.wl.activation_requests.capacity = MAX(64u, _glfw.wl.activation_requests.capacity * 2);
|
||||
_glfw.wl.activation_requests.array = realloc(_glfw.wl.activation_requests.array, _glfw.wl.activation_requests.capacity);
|
||||
_glfw.wl.activation_requests.array = realloc(_glfw.wl.activation_requests.array, _glfw.wl.activation_requests.capacity * sizeof(_glfw.wl.activation_requests.array[0]));
|
||||
if (!_glfw.wl.activation_requests.array) {
|
||||
_glfw.wl.activation_requests.capacity = 0;
|
||||
fail("Wayland: Out of memory while allocation activation request");
|
||||
@@ -275,18 +275,6 @@ static void setOpaqueRegion(_GLFWwindow* window, bool commit_surface)
|
||||
wl_region_destroy(region);
|
||||
}
|
||||
|
||||
static void
|
||||
swap_buffers(_GLFWwindow *window) {
|
||||
// this will attach the buffer to the surface,
|
||||
// the client is responsible for clearing the buffer to an appropriate blank
|
||||
window->swaps_disallowed = false;
|
||||
GLFWwindow *current = glfwGetCurrentContext();
|
||||
bool context_is_current = ((_GLFWwindow*)current)->id == window->id;
|
||||
if (!context_is_current) glfwMakeContextCurrent((GLFWwindow*)window);
|
||||
window->context.swapBuffers(window);
|
||||
if (!context_is_current) glfwMakeContextCurrent(current);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
resizeFramebuffer(_GLFWwindow* window) {
|
||||
@@ -604,8 +592,9 @@ static void xdgSurfaceHandleConfigure(void* data,
|
||||
int width = window->wl.pending.width;
|
||||
int height = window->wl.pending.height;
|
||||
if (!window->wl.surface_configured_once) {
|
||||
window->swaps_disallowed = false;
|
||||
window->wl.waiting_for_swap_to_commit = true;
|
||||
window->wl.surface_configured_once = true;
|
||||
swap_buffers(window);
|
||||
}
|
||||
|
||||
if (new_states != window->wl.current.toplevel_states ||
|
||||
@@ -628,12 +617,11 @@ static void xdgSurfaceHandleConfigure(void* data,
|
||||
window->wl.current.decoration_mode = mode;
|
||||
}
|
||||
|
||||
bool resized = false;
|
||||
if (window->wl.pending_state) {
|
||||
int width = window->wl.pending.width, height = window->wl.pending.height;
|
||||
set_csd_window_geometry(window, &width, &height);
|
||||
resized = dispatchChangesAfterConfigure(window, width, height);
|
||||
if (window->wl.decorations.serverSide) {
|
||||
bool resized = dispatchChangesAfterConfigure(window, width, height);
|
||||
if (window->wl.decorations.serverSide || window->monitor || window->wl.current.toplevel_states & TOPLEVEL_STATE_FULLSCREEN) {
|
||||
free_csd_surfaces(window);
|
||||
} else {
|
||||
ensure_csd_resources(window);
|
||||
@@ -1650,7 +1638,7 @@ write_chunk(void *object, const char *data, size_t sz) {
|
||||
chunked_writer *cw = object;
|
||||
if (cw->cap < cw->sz + sz) {
|
||||
cw->cap = MAX(cw->cap * 2, cw->sz + 8*sz);
|
||||
cw->buf = realloc(cw->buf, cw->cap);
|
||||
cw->buf = realloc(cw->buf, cw->cap * sizeof(cw->buf[0]));
|
||||
}
|
||||
memcpy(cw->buf + cw->sz, data, sz);
|
||||
cw->sz += sz;
|
||||
|
||||
32
glfw/x11_window.c
vendored
32
glfw/x11_window.c
vendored
@@ -729,7 +729,7 @@ get_clipboard_data(const _GLFWClipboardData *cd, const char *mime, char **data)
|
||||
if (!chunk.sz) break;
|
||||
if (cap < sz + chunk.sz) {
|
||||
cap = MAX(cap * 2, sz + 4 * chunk.sz);
|
||||
buf = realloc(buf, cap);
|
||||
buf = realloc(buf, cap * sizeof(buf[0]));
|
||||
}
|
||||
memcpy(buf + sz, chunk.data, chunk.sz);
|
||||
sz += chunk.sz;
|
||||
@@ -1033,7 +1033,7 @@ getSelectionString(Atom selection, Atom *targets, size_t num_targets, GLFWclipbo
|
||||
}
|
||||
else if (actualType == XA_ATOM && targets[i] == _glfw.x11.TARGETS) {
|
||||
found = true;
|
||||
write_data(object, data, itemCount);
|
||||
write_data(object, data, sizeof(Atom) * itemCount);
|
||||
}
|
||||
|
||||
XFREE(data);
|
||||
@@ -2861,7 +2861,7 @@ static MimeAtom atom_for_mime(const char *mime) {
|
||||
MimeAtom ma = {.mime=_glfw_strdup(mime), .atom=XInternAtom(_glfw.x11.display, mime, 0)};
|
||||
if (_glfw.x11.mime_atoms.capacity < _glfw.x11.mime_atoms.sz + 1) {
|
||||
_glfw.x11.mime_atoms.capacity += 32;
|
||||
_glfw.x11.mime_atoms.array = realloc(_glfw.x11.mime_atoms.array, _glfw.x11.mime_atoms.capacity);
|
||||
_glfw.x11.mime_atoms.array = realloc(_glfw.x11.mime_atoms.array, _glfw.x11.mime_atoms.capacity * sizeof(_glfw.x11.mime_atoms.array[0]));
|
||||
}
|
||||
_glfw.x11.mime_atoms.array[_glfw.x11.mime_atoms.sz++] = ma;
|
||||
return ma;
|
||||
@@ -2897,17 +2897,20 @@ void _glfwPlatformSetClipboard(GLFWClipboardType t) {
|
||||
|
||||
typedef struct chunked_writer {
|
||||
char *buf; size_t sz, cap;
|
||||
bool is_self_offer;
|
||||
} chunked_writer;
|
||||
|
||||
static bool
|
||||
write_chunk(void *object, const char *data, size_t sz) {
|
||||
chunked_writer *cw = object;
|
||||
if (cw->cap < cw->sz + sz) {
|
||||
cw->cap = MAX(cw->cap * 2, cw->sz + 8*sz);
|
||||
cw->buf = realloc(cw->buf, cw->cap);
|
||||
}
|
||||
memcpy(cw->buf + cw->sz, data, sz);
|
||||
cw->sz += sz;
|
||||
if (data) {
|
||||
if (cw->cap < cw->sz + sz) {
|
||||
cw->cap = MAX(cw->cap * 2, cw->sz + 8*sz);
|
||||
cw->buf = realloc(cw->buf, cw->cap * sizeof(cw->buf[0]));
|
||||
}
|
||||
memcpy(cw->buf + cw->sz, data, sz);
|
||||
cw->sz += sz;
|
||||
} else if (sz == 1) cw->is_self_offer = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2915,6 +2918,10 @@ static void
|
||||
get_available_mime_types(Atom which_clipboard, GLFWclipboardwritedatafun write_data, void *object) {
|
||||
chunked_writer cw = {0};
|
||||
getSelectionString(which_clipboard, &_glfw.x11.TARGETS, 1, write_chunk, &cw, false);
|
||||
if (cw.is_self_offer) {
|
||||
write_data(object, NULL, 1);
|
||||
return;
|
||||
}
|
||||
size_t count = 0;
|
||||
bool ok = true;
|
||||
if (cw.buf) {
|
||||
@@ -2946,11 +2953,12 @@ _glfwPlatformGetClipboard(GLFWClipboardType clipboard_type, const char* mime_typ
|
||||
}
|
||||
size_t count = 0;
|
||||
if (strcmp(mime_type, "text/plain") == 0) {
|
||||
// we need to do this because GTK/GNOME is developed by morons
|
||||
// they convert text/plain to DOS line endings
|
||||
// UTF8_STRING is what xclip uses by default, and there are people out there that expect to be able to paste from it with a single read operation. See https://github.com/kovidgoyal/kitty/issues/5842
|
||||
// Also ancient versions of GNOME use DOS line endings even for text/plain;charset=utf-8. See https://github.com/kovidgoyal/kitty/issues/5528#issuecomment-1325348218
|
||||
atoms[count++] = _glfw.x11.UTF8_STRING;
|
||||
// we need to do this because GTK/GNOME is moronic they convert text/plain to DOS line endings, see
|
||||
// https://gitlab.gnome.org/GNOME/gtk/-/issues/2307
|
||||
atoms[count++] = atom_for_mime("text/plain;charset=utf-8").atom;
|
||||
atoms[count++] = _glfw.x11.UTF8_STRING;
|
||||
atoms[count++] = atom_for_mime("text/plain").atom;
|
||||
atoms[count++] = XA_STRING;
|
||||
} else {
|
||||
|
||||
18
go.mod
Normal file
18
go.mod
Normal file
@@ -0,0 +1,18 @@
|
||||
module kitty
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/ALTree/bigfloat v0.0.0-20220102081255-38c8b72a9924
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/google/go-cmp v0.5.8
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/jamesruan/go-rfc1924 v0.0.0-20170108144916-2767ca7c638f
|
||||
github.com/seancfoley/ipaddress-go v1.2.1
|
||||
golang.org/x/crypto v0.0.0-20220924013350-4ba4fb4dd9e7
|
||||
golang.org/x/exp v0.0.0-20220921164117-439092de6870
|
||||
golang.org/x/image v0.2.0
|
||||
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8
|
||||
)
|
||||
|
||||
require github.com/seancfoley/bintree v1.1.0 // indirect
|
||||
47
go.sum
Normal file
47
go.sum
Normal file
@@ -0,0 +1,47 @@
|
||||
github.com/ALTree/bigfloat v0.0.0-20220102081255-38c8b72a9924 h1:DG4UyTVIujioxwJc8Zj8Nabz1L1wTgQ/xNBSQDfdP3I=
|
||||
github.com/ALTree/bigfloat v0.0.0-20220102081255-38c8b72a9924/go.mod h1:+NaH2gLeY6RPBPPQf4aRotPPStg+eXc8f9ZaE4vRfD4=
|
||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jamesruan/go-rfc1924 v0.0.0-20170108144916-2767ca7c638f h1:Ko4+g6K16vSyUrtd/pPXuQnWsiHe5BYptEtTxfwYwCc=
|
||||
github.com/jamesruan/go-rfc1924 v0.0.0-20170108144916-2767ca7c638f/go.mod h1:eHzfhOKbTGJEGPSdMHzU6jft192tHHt2Bu2vIZArvC0=
|
||||
github.com/seancfoley/bintree v1.1.0 h1:6J0rj9hLNLIcWSsfYdZ4ZHkMHokaK/PHkak8qyBO/mc=
|
||||
github.com/seancfoley/bintree v1.1.0/go.mod h1:CtE6qO6/n9H3V2CAGEC0lpaYr6/OijhNaMG/dt7P70c=
|
||||
github.com/seancfoley/ipaddress-go v1.2.1 h1:yEZxnyC6NQEDDPflyQm4KkWozffx1vHWsx+knKBr/n0=
|
||||
github.com/seancfoley/ipaddress-go v1.2.1/go.mod h1:/UEVHyrBg1ASVap2ffdY2cq5UMYIX9f3QW3uWSVqpbo=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220924013350-4ba4fb4dd9e7 h1:WJywXQVIb56P2kAvXeMGTIgQ1ZHQxR60+F9dLsodECc=
|
||||
golang.org/x/crypto v0.0.0-20220924013350-4ba4fb4dd9e7/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20220921164117-439092de6870 h1:j8b6j9gzSigH28O5SjSpQSSh9lFd6f5D/q0aHjNTulc=
|
||||
golang.org/x/exp v0.0.0-20220921164117-439092de6870/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.2.0 h1:/DcQ0w3VHKCC5p0/P2B0JpAZ9Z++V2KOo2fyU89CXBQ=
|
||||
golang.org/x/image v0.2.0/go.mod h1:la7oBXb9w3YFjBqaAwtynVioc1ZvOnNteUNrifGNmAI=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc=
|
||||
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -6,13 +6,21 @@ import re
|
||||
import sys
|
||||
from contextlib import suppress
|
||||
from typing import (
|
||||
TYPE_CHECKING, Callable, Dict, Iterator, List, NamedTuple, Optional, Tuple
|
||||
TYPE_CHECKING,
|
||||
Callable,
|
||||
Dict,
|
||||
Iterator,
|
||||
List,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
Tuple,
|
||||
)
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cli_stub import AskCLIOptions
|
||||
from kitty.constants import cache_dir
|
||||
from kitty.fast_data_types import truncate_point_for_length, wcswidth
|
||||
from kitty.types import run_once
|
||||
from kitty.typing import BossType, KeyEventType, TypedDict
|
||||
from kitty.utils import ScreenSize
|
||||
|
||||
@@ -447,6 +455,17 @@ class Choose(Handler): # {{{
|
||||
# }}}
|
||||
|
||||
|
||||
@run_once
|
||||
def init_readline() -> None:
|
||||
import readline
|
||||
with suppress(OSError):
|
||||
readline.read_init_file()
|
||||
if 'libedit' in readline.__doc__:
|
||||
readline.parse_and_bind("bind ^I rl_complete")
|
||||
else:
|
||||
readline.parse_and_bind('tab: complete')
|
||||
|
||||
|
||||
def main(args: List[str]) -> Response:
|
||||
# For some reason importing readline in a key handler in the main kitty process
|
||||
# causes a crash of the python interpreter, probably because of some global
|
||||
@@ -454,7 +473,7 @@ def main(args: List[str]) -> Response:
|
||||
global readline
|
||||
msg = 'Ask the user for input'
|
||||
try:
|
||||
cli_opts, items = parse_args(args[1:], option_text, '', msg, 'kitty ask', result_class=AskCLIOptions)
|
||||
cli_opts, items = parse_args(args[1:], option_text, '', msg, 'kitty +kitten ask', result_class=AskCLIOptions)
|
||||
except SystemExit as e:
|
||||
if e.code != 0:
|
||||
print(e.args[0])
|
||||
@@ -476,17 +495,22 @@ def main(args: List[str]) -> Response:
|
||||
loop.loop(phandler)
|
||||
return {'items': items, 'response': phandler.response}
|
||||
|
||||
import readline as rl
|
||||
readline = rl
|
||||
from kitty.shell import init_readline
|
||||
init_readline()
|
||||
response = None
|
||||
# we do this file descriptor dance to get readline to work even when STDOUT
|
||||
# is redirected
|
||||
orig_stdout = os.dup(sys.stdout.fileno())
|
||||
try:
|
||||
with open(os.ctermid(), 'r') as tty:
|
||||
os.dup2(tty.fileno(), sys.stdin.fileno())
|
||||
with open(os.ctermid(), 'w') as tty:
|
||||
os.dup2(tty.fileno(), sys.stdout.fileno())
|
||||
import readline as rl
|
||||
readline = rl
|
||||
init_readline()
|
||||
response = None
|
||||
|
||||
with alternate_screen(), HistoryCompleter(cli_opts.name):
|
||||
if cli_opts.message:
|
||||
print(styled(cli_opts.message, bold=True))
|
||||
|
||||
with suppress(KeyboardInterrupt, EOFError):
|
||||
with alternate_screen(), HistoryCompleter(cli_opts.name), suppress(KeyboardInterrupt, EOFError):
|
||||
if cli_opts.message:
|
||||
print(styled(cli_opts.message, bold=True))
|
||||
if cli_opts.default:
|
||||
def prefill_text() -> None:
|
||||
readline.insert_text(cli_opts.default or '')
|
||||
@@ -496,6 +520,10 @@ def main(args: List[str]) -> Response:
|
||||
readline.set_pre_input_hook()
|
||||
else:
|
||||
response = input(prompt)
|
||||
sys.stdout.flush()
|
||||
os.dup2(orig_stdout, sys.stdout.fileno())
|
||||
finally:
|
||||
os.close(orig_stdout)
|
||||
return {'items': items, 'response': response}
|
||||
|
||||
|
||||
@@ -509,4 +537,5 @@ def handle_result(args: List[str], data: Response, target_window_id: int, boss:
|
||||
if __name__ == '__main__':
|
||||
ans = main(sys.argv)
|
||||
if ans:
|
||||
print(ans)
|
||||
import json
|
||||
print(json.dumps(ans))
|
||||
|
||||
@@ -31,6 +31,7 @@ class Broadcast(Handler):
|
||||
|
||||
def __init__(self, opts: BroadcastCLIOptions, initial_strings: List[str]) -> None:
|
||||
self.opts = opts
|
||||
self.hide_input = False
|
||||
self.initial_strings = initial_strings
|
||||
self.payload = {'exclude_active': True, 'data': '', 'match': opts.match, 'match_tab': opts.match_tab, 'session_id': uuid4()}
|
||||
self.line_edit = LineEdit()
|
||||
@@ -40,7 +41,7 @@ class Broadcast(Handler):
|
||||
|
||||
def initialize(self) -> None:
|
||||
self.write_broadcast_session()
|
||||
self.print('Type the text to broadcast below, press', styled('Ctrl+Esc', fg='yellow'), 'to quit:')
|
||||
self.print('Type the text to broadcast below, press', styled(self.opts.end_session, fg='yellow'), 'to quit:')
|
||||
for x in self.initial_strings:
|
||||
self.write_broadcast_text(x)
|
||||
self.write(SAVE_CURSOR)
|
||||
@@ -56,7 +57,8 @@ class Broadcast(Handler):
|
||||
|
||||
def on_text(self, text: str, in_bracketed_paste: bool = False) -> None:
|
||||
self.write_broadcast_text(text)
|
||||
self.line_edit.on_text(text, in_bracketed_paste)
|
||||
if not self.hide_input:
|
||||
self.line_edit.on_text(text, in_bracketed_paste)
|
||||
self.commit_line()
|
||||
|
||||
def on_interrupt(self) -> None:
|
||||
@@ -68,22 +70,33 @@ class Broadcast(Handler):
|
||||
self.write_broadcast_text('\x04')
|
||||
|
||||
def on_key(self, key_event: KeyEventType) -> None:
|
||||
if self.line_edit.on_key(key_event):
|
||||
if key_event.matches(self.opts.hide_input_toggle):
|
||||
self.hide_input ^= True
|
||||
self.cmd.set_cursor_visible(not self.hide_input)
|
||||
if self.hide_input:
|
||||
self.end_line()
|
||||
self.print('Input hidden, press', styled(self.opts.hide_input_toggle, fg='yellow'), 'to unhide:')
|
||||
self.end_line()
|
||||
return
|
||||
if key_event.matches(self.opts.end_session):
|
||||
self.quit_loop(0)
|
||||
return
|
||||
if not self.hide_input and self.line_edit.on_key(key_event):
|
||||
self.commit_line()
|
||||
if key_event.matches('enter'):
|
||||
self.write_broadcast_text('\r')
|
||||
self.print('')
|
||||
self.line_edit.clear()
|
||||
self.write(SAVE_CURSOR)
|
||||
return
|
||||
if key_event.matches('ctrl+esc'):
|
||||
self.quit_loop(0)
|
||||
self.end_line()
|
||||
return
|
||||
|
||||
ek = encode_key_event(key_event)
|
||||
ek = standard_b64encode(ek.encode('utf-8')).decode('ascii')
|
||||
self.write_broadcast_data('kitty-key:' + ek)
|
||||
|
||||
def end_line(self) -> None:
|
||||
self.print('')
|
||||
self.line_edit.clear()
|
||||
self.write(SAVE_CURSOR)
|
||||
|
||||
def write_broadcast_text(self, text: str) -> None:
|
||||
self.write_broadcast_data('base64:' + standard_b64encode(text.encode('utf-8')).decode('ascii'))
|
||||
|
||||
@@ -98,7 +111,19 @@ class Broadcast(Handler):
|
||||
self.write(session_command(self.payload, start))
|
||||
|
||||
|
||||
OPTIONS = (MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t')).format
|
||||
OPTIONS = ('''
|
||||
--hide-input-toggle
|
||||
default=Ctrl+Alt+Esc
|
||||
Key to press that will toggle hiding of the input in the broadcast window itself.
|
||||
Useful while typing a password, prevents the password from being visible on the screen.
|
||||
|
||||
|
||||
--end-session
|
||||
default=Ctrl+Esc
|
||||
Key to press to end the broadcast session.
|
||||
|
||||
|
||||
''' + MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t')).format
|
||||
help_text = 'Broadcast typed text to kitty windows. By default text is sent to all windows, unless one of the matching options is specified'
|
||||
usage = '[initial text to send ...]'
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
|
||||
def match(
|
||||
lines: List[bytes], levels: Tuple[str, str, str], needle: str,
|
||||
output_positions: bool, limit: int, num_threads: int, mark_before: str,
|
||||
|
||||
@@ -1,152 +1,91 @@
|
||||
#!/usr/bin/env python3
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import codecs
|
||||
import io
|
||||
import os
|
||||
import select
|
||||
import sys
|
||||
from typing import List, NoReturn, Optional
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cli_stub import ClipboardCLIOptions
|
||||
from kitty.fast_data_types import parse_input_from_terminal
|
||||
|
||||
from ..tui.operations import (
|
||||
raw_mode, request_from_clipboard, write_to_clipboard
|
||||
)
|
||||
|
||||
OPTIONS = r'''
|
||||
--get-clipboard
|
||||
default=False
|
||||
--get-clipboard -g
|
||||
type=bool-set
|
||||
Output the current contents of the clipboard to STDOUT. Note that by default
|
||||
kitty will prompt for permission to access the clipboard. Can be controlled
|
||||
by :opt:`clipboard_control`.
|
||||
|
||||
|
||||
--use-primary
|
||||
default=False
|
||||
--use-primary -p
|
||||
type=bool-set
|
||||
Use the primary selection rather than the clipboard on systems that support it,
|
||||
such as X11.
|
||||
such as Linux.
|
||||
|
||||
|
||||
--mime -m
|
||||
type=list
|
||||
The mimetype of the specified file. Useful when the auto-detected mimetype is
|
||||
likely to be incorrect or the filename has no extension and therefore no mimetype
|
||||
can be detected. If more than one file is specified, this option should be specified multiple
|
||||
times, once for each specified file. When copying data from the clipboard, you can use wildcards
|
||||
to match MIME types. For example: :code:`--mime 'text/*'` will match any textual MIME type
|
||||
available on the clipboard, usually the first matching MIME type is copied. The special MIME
|
||||
type :code:`.` will return the list of available MIME types currently on the system clipboard.
|
||||
|
||||
|
||||
--alias -a
|
||||
type=list
|
||||
Specify aliases for MIME types. Aliased MIME types are considered equivalent.
|
||||
When copying to clipboard both the original and alias are made available on the
|
||||
clipboard. When copying from clipboard if the original is not found, the alias
|
||||
is used, as a fallback. Can be specified multiple times to create multiple
|
||||
aliases. For example: :code:`--alias text/plain=text/x-rst` makes :code:`text/plain` an alias
|
||||
of :code:`text/rst`. Aliases are not used in filter mode. An alias for
|
||||
:code:`text/plain` is automatically created if :code:`text/plain` is not present in the input data, but some
|
||||
other :code:`text/*` MIME is present.
|
||||
|
||||
|
||||
--wait-for-completion
|
||||
default=False
|
||||
type=bool-set
|
||||
Wait till the copy to clipboard is complete before exiting. Useful if running
|
||||
the kitten in a dedicated, ephemeral window.
|
||||
the kitten in a dedicated, ephemeral window. Only needed in filter mode.
|
||||
'''.format
|
||||
help_text = '''\
|
||||
Read or write to the system clipboard.
|
||||
|
||||
To set the clipboard text, pipe in the new text on STDIN. Use the
|
||||
:option:`--get-clipboard` option to output the current clipboard contents to
|
||||
:file:`stdout`. Note that reading the clipboard will cause a permission
|
||||
This kitten operates most simply in :italic:`filter mode`.
|
||||
To set the clipboard text, pipe in the new text on :file:`STDIN`. Use the
|
||||
:option:`--get-clipboard` option to output the current clipboard text content to
|
||||
:file:`STDOUT`. Note that copying from the clipboard will cause a permission
|
||||
popup, see :opt:`clipboard_control` for details.
|
||||
|
||||
For more control, specify filename arguments. Then, different MIME types can be copied to/from
|
||||
the clipboard. Some examples:
|
||||
|
||||
.. code:: sh
|
||||
|
||||
# Copy an image to the clipboard:
|
||||
kitty +kitten clipboard picture.png
|
||||
|
||||
# Copy an image and some text to the clipboard:
|
||||
kitty +kitten clipboard picture.jpg text.txt
|
||||
|
||||
# Copy text from STDIN and an image to the clipboard:
|
||||
echo hello | kitty +kitten clipboard picture.png /dev/stdin
|
||||
|
||||
# Copy any raster image available on the clipboard to a PNG file:
|
||||
kitty +kitten clipboard -g picture.png
|
||||
|
||||
# Copy an image to a file and text to STDOUT:
|
||||
kitty +kitten clipboard -g picture.png /dev/stdout
|
||||
|
||||
# List the formats available on the system clipboard
|
||||
kitty +kitten clipboard -g -m . /dev/stdout
|
||||
'''
|
||||
|
||||
usage = ''
|
||||
got_capability_response = False
|
||||
got_clipboard_response = False
|
||||
clipboard_contents = ''
|
||||
clipboard_from_primary = False
|
||||
|
||||
|
||||
def ignore(x: str) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def on_text(x: str) -> None:
|
||||
if '\x03' in x:
|
||||
raise KeyboardInterrupt()
|
||||
if '\x04' in x:
|
||||
raise EOFError()
|
||||
|
||||
|
||||
def on_dcs(dcs: str) -> None:
|
||||
global got_capability_response
|
||||
if dcs.startswith('1+r'):
|
||||
got_capability_response = True
|
||||
|
||||
|
||||
def on_osc(osc: str) -> None:
|
||||
global clipboard_contents, clipboard_from_primary, got_clipboard_response
|
||||
idx = osc.find(';')
|
||||
if idx <= 0:
|
||||
return
|
||||
q = osc[:idx]
|
||||
if q == '52':
|
||||
got_clipboard_response = True
|
||||
widx = osc.find(';', idx + 1)
|
||||
if widx < idx:
|
||||
clipboard_from_primary = osc.find('p', idx + 1) > -1
|
||||
clipboard_contents = ''
|
||||
else:
|
||||
from base64 import standard_b64decode
|
||||
clipboard_from_primary = osc.find('p', idx+1, widx) > -1
|
||||
data = memoryview(osc.encode('ascii'))
|
||||
clipboard_contents = standard_b64decode(data[widx+1:]).decode('utf-8')
|
||||
|
||||
|
||||
def wait_loop(tty_fd: int) -> None:
|
||||
os.set_blocking(tty_fd, False)
|
||||
decoder = codecs.getincrementaldecoder('utf-8')('ignore')
|
||||
with raw_mode(tty_fd):
|
||||
buf = ''
|
||||
while not got_capability_response and not got_clipboard_response:
|
||||
rd = select.select([tty_fd], [], [])[0]
|
||||
if rd:
|
||||
raw = os.read(tty_fd, io.DEFAULT_BUFFER_SIZE)
|
||||
if not raw:
|
||||
raise EOFError()
|
||||
data = decoder.decode(raw)
|
||||
buf = (buf + data) if buf else data
|
||||
buf = parse_input_from_terminal(on_text, on_dcs, ignore, on_osc, ignore, ignore, buf, False)
|
||||
|
||||
|
||||
def main(args: List[str]) -> NoReturn:
|
||||
cli_opts, items = parse_args(args[1:], OPTIONS, usage, help_text, 'kitty +kitten clipboard', result_class=ClipboardCLIOptions)
|
||||
if items:
|
||||
raise SystemExit('Unrecognized extra command line arguments')
|
||||
data: Optional[bytes] = None
|
||||
if not sys.stdin.isatty():
|
||||
data = sys.stdin.buffer.read()
|
||||
wait_for_capability_response = False
|
||||
data_to_write = []
|
||||
if data:
|
||||
data_to_write.append(write_to_clipboard(data, cli_opts.use_primary).encode('ascii'))
|
||||
if not cli_opts.get_clipboard and cli_opts.wait_for_completion:
|
||||
data_to_write.append(b'\x1bP+q544e\x1b\\')
|
||||
wait_for_capability_response = True
|
||||
if cli_opts.get_clipboard:
|
||||
data_to_write.append(request_from_clipboard(cli_opts.use_primary).encode('ascii'))
|
||||
wait_for_capability_response = True
|
||||
tty_fd = os.open(os.ctermid(), os.O_RDWR | os.O_CLOEXEC)
|
||||
retcode = 0
|
||||
with open(tty_fd, 'wb', closefd=True) as ttyf:
|
||||
for x in data_to_write:
|
||||
ttyf.write(x)
|
||||
ttyf.flush()
|
||||
if wait_for_capability_response:
|
||||
try:
|
||||
wait_loop(tty_fd)
|
||||
except KeyboardInterrupt:
|
||||
sys.excepthook = lambda *a: None
|
||||
raise
|
||||
except EOFError:
|
||||
retcode = 1
|
||||
if clipboard_contents:
|
||||
print(end=clipboard_contents)
|
||||
|
||||
raise SystemExit(retcode)
|
||||
|
||||
|
||||
usage = '[files to copy to/from]'
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
||||
raise SystemExit('This should be run as kitten clipboard')
|
||||
elif __name__ == '__doc__':
|
||||
from kitty.cli import CompletionSpec
|
||||
cd = sys.cli_docs # type: ignore
|
||||
cd['usage'] = usage
|
||||
cd['options'] = OPTIONS
|
||||
cd['help_text'] = help_text
|
||||
cd['short_desc'] = 'Copy/paste with the system clipboard, even over SSH'
|
||||
cd['args_completion'] = CompletionSpec.from_string('type:file mime:* group:Files')
|
||||
|
||||
@@ -6,15 +6,13 @@ from contextlib import suppress
|
||||
from fnmatch import fnmatch
|
||||
from functools import lru_cache
|
||||
from hashlib import md5
|
||||
from typing import (
|
||||
TYPE_CHECKING, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Union
|
||||
)
|
||||
from typing import TYPE_CHECKING, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Union
|
||||
|
||||
from kitty.guess_mime_type import guess_type
|
||||
from kitty.utils import control_codes_pat
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .highlight import DiffHighlight # noqa
|
||||
from .highlight import DiffHighlight
|
||||
|
||||
|
||||
path_name_map: Dict[str, str] = {}
|
||||
|
||||
@@ -5,13 +5,13 @@ import os
|
||||
from typing import Any, Dict, Iterable, Optional
|
||||
|
||||
from kitty.cli_stub import DiffCLIOptions
|
||||
from kitty.conf.utils import (
|
||||
load_config as _load_config, parse_config_base, resolve_config
|
||||
)
|
||||
from kitty.conf.utils import load_config as _load_config
|
||||
from kitty.conf.utils import parse_config_base, resolve_config
|
||||
from kitty.constants import config_dir
|
||||
from kitty.rgb import color_as_sgr
|
||||
|
||||
from .options.types import Options as DiffOptions, defaults
|
||||
from .options.types import Options as DiffOptions
|
||||
from .options.types import defaults
|
||||
|
||||
formats: Dict[str, str] = {
|
||||
'title': '',
|
||||
@@ -41,9 +41,7 @@ defconf = os.path.join(config_dir, 'diff.conf')
|
||||
|
||||
|
||||
def load_config(*paths: str, overrides: Optional[Iterable[str]] = None) -> DiffOptions:
|
||||
from .options.parse import (
|
||||
create_result_dict, merge_result_dicts, parse_conf_item
|
||||
)
|
||||
from .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()
|
||||
|
||||
@@ -2,7 +2,6 @@ from typing import List, Optional, Tuple
|
||||
|
||||
from .collect import Segment
|
||||
|
||||
|
||||
def split_with_highlights(
|
||||
line: str, truncate_points: List[int], fg_highlights: List[Segment],
|
||||
bg_highlight: Optional[Segment]
|
||||
|
||||
@@ -5,9 +5,7 @@ import concurrent
|
||||
import os
|
||||
import re
|
||||
from concurrent.futures import ProcessPoolExecutor
|
||||
from typing import (
|
||||
IO, Dict, Iterable, Iterator, List, Optional, Tuple, Union, cast
|
||||
)
|
||||
from typing import IO, Dict, Iterable, Iterator, List, Optional, Tuple, Union, cast
|
||||
|
||||
from pygments import highlight # type: ignore
|
||||
from pygments.formatter import Formatter # type: ignore
|
||||
|
||||
@@ -14,16 +14,24 @@ from enum import Enum, auto
|
||||
from functools import partial
|
||||
from gettext import gettext as _
|
||||
from typing import (
|
||||
Any, DefaultDict, Dict, Iterable, Iterator, List, Optional, Tuple, Union
|
||||
Any,
|
||||
DefaultDict,
|
||||
Dict,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
Optional,
|
||||
Tuple,
|
||||
Union,
|
||||
)
|
||||
|
||||
from kitty.cli import CONFIG_HELP, parse_args
|
||||
from kitty.cli import CONFIG_HELP, CompletionSpec, parse_args
|
||||
from kitty.cli_stub import DiffCLIOptions
|
||||
from kitty.conf.utils import KeyAction
|
||||
from kitty.constants import appname
|
||||
from kitty.fast_data_types import wcswidth
|
||||
from kitty.key_encoding import EventType, KeyEvent
|
||||
from kitty.utils import ScreenSize
|
||||
from kitty.utils import ScreenSize, extract_all_from_tarfile_safely
|
||||
|
||||
from ..tui.handler import Handler
|
||||
from ..tui.images import ImageManager, Placement
|
||||
@@ -32,21 +40,33 @@ from ..tui.loop import Loop
|
||||
from ..tui.operations import styled
|
||||
from . import global_data
|
||||
from .collect import (
|
||||
Collection, add_remote_dir, create_collection, data_for_path,
|
||||
lines_for_path, sanitize, set_highlight_data
|
||||
Collection,
|
||||
add_remote_dir,
|
||||
create_collection,
|
||||
data_for_path,
|
||||
lines_for_path,
|
||||
sanitize,
|
||||
set_highlight_data,
|
||||
)
|
||||
from .config import init_config
|
||||
from .options.types import Options as DiffOptions
|
||||
from .patch import Differ, Patch, set_diff_command, worker_processes
|
||||
from .render import (
|
||||
ImagePlacement, ImageSupportWarning, Line, LineRef, Reference, render_diff
|
||||
ImagePlacement,
|
||||
ImageSupportWarning,
|
||||
Line,
|
||||
LineRef,
|
||||
Reference,
|
||||
render_diff,
|
||||
)
|
||||
from .search import BadRegex, Search
|
||||
|
||||
try:
|
||||
from .highlight import (
|
||||
DiffHighlight, get_highlight_processes, highlight_collection,
|
||||
initialize_highlighter
|
||||
DiffHighlight,
|
||||
get_highlight_processes,
|
||||
highlight_collection,
|
||||
initialize_highlighter,
|
||||
)
|
||||
has_highlighter = True
|
||||
DiffHighlight
|
||||
@@ -229,8 +249,8 @@ class DiffHandler(Handler):
|
||||
self.diff_lines: Tuple[Line, ...] = tuple(render_diff(self.collection, self.diff_map, self.args, self.screen_size.cols, self.image_manager))
|
||||
self.margin_size = render_diff.margin_size
|
||||
self.ref_path_map: DefaultDict[str, List[Tuple[int, Reference]]] = defaultdict(list)
|
||||
for i, l in enumerate(self.diff_lines):
|
||||
self.ref_path_map[l.ref.path].append((i, l.ref))
|
||||
for i, dl in enumerate(self.diff_lines):
|
||||
self.ref_path_map[dl.ref.path].append((i, dl.ref))
|
||||
self.max_scroll_pos = len(self.diff_lines) - self.num_lines
|
||||
if self.current_search is not None:
|
||||
self.current_search(self.diff_lines, self.margin_size, self.screen_size.cols)
|
||||
@@ -319,7 +339,7 @@ class DiffHandler(Handler):
|
||||
fg=self.opts.foreground, bg=self.opts.background,
|
||||
cursor=self.opts.foreground, select_fg=self.opts.select_fg,
|
||||
select_bg=self.opts.select_bg)
|
||||
self.cmd.set_cursor_shape('bar')
|
||||
self.cmd.set_cursor_shape('beam')
|
||||
|
||||
def finalize(self) -> None:
|
||||
self.cmd.set_default_colors()
|
||||
@@ -577,6 +597,7 @@ number set in :file:`diff.conf`.
|
||||
|
||||
--config
|
||||
type=list
|
||||
completion=type:file ext:conf group:"Config files" kwds:none,NONE
|
||||
{config_help}
|
||||
|
||||
|
||||
@@ -629,7 +650,7 @@ def get_ssh_file(hostname: str, rpath: str) -> str:
|
||||
raise SystemExit(p.returncode)
|
||||
with tarfile.open(fileobj=io.BytesIO(raw), mode='r:') as tf:
|
||||
members = tf.getmembers()
|
||||
tf.extractall(tdir)
|
||||
extract_all_from_tarfile_safely(tf, tdir)
|
||||
if len(members) == 1:
|
||||
for root, dirs, files in os.walk(tdir):
|
||||
if files:
|
||||
@@ -687,6 +708,7 @@ elif __name__ == '__doc__':
|
||||
cd['usage'] = usage
|
||||
cd['options'] = OPTIONS
|
||||
cd['help_text'] = help_text
|
||||
cd['args_completion'] = CompletionSpec.from_string('type:file mime:text/* mime:image/* group:"Text and image files"')
|
||||
elif __name__ == '__conf__':
|
||||
from .options.definition import definition
|
||||
sys.options_definition = definition # type: ignore
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
from kitty.conf.types import Action, Definition
|
||||
|
||||
|
||||
definition = Definition(
|
||||
'kittens.diff',
|
||||
Action('map', 'parse_map', {'key_definitions': 'kitty.conf.utils.KittensKeyMap'}, ['kitty.types.ParsedShortcut', 'kitty.conf.utils.KeyAction']),
|
||||
|
||||
1
kittens/diff/options/parse.py
generated
1
kittens/diff/options/parse.py
generated
@@ -1,5 +1,6 @@
|
||||
# generated by gen-config.py DO NOT edit
|
||||
|
||||
# isort: skip_file
|
||||
import typing
|
||||
from kittens.diff.options.utils import parse_map, store_multiple, syntax_aliases
|
||||
from kitty.conf.utils import merge_dicts, positive_int, python_string, to_color, to_color_or_none
|
||||
|
||||
51
kittens/diff/options/types.py
generated
51
kittens/diff/options/types.py
generated
@@ -1,5 +1,6 @@
|
||||
# generated by gen-config.py DO NOT edit
|
||||
|
||||
# isort: skip_file
|
||||
import typing
|
||||
from kitty.conf.utils import KeyAction, KittensKeyMap
|
||||
import kitty.conf.utils
|
||||
@@ -121,53 +122,53 @@ defaults = Options()
|
||||
defaults.ignore_name = {}
|
||||
defaults.map = [
|
||||
# quit
|
||||
(ParsedShortcut(mods=0, key_name='q'), KeyAction('quit')), # noqa
|
||||
(ParsedShortcut(mods=0, key_name='q'), KeyAction('quit')),
|
||||
# quit
|
||||
(ParsedShortcut(mods=0, key_name='ESCAPE'), KeyAction('quit')), # noqa
|
||||
(ParsedShortcut(mods=0, key_name='ESCAPE'), KeyAction('quit')),
|
||||
# scroll_down
|
||||
(ParsedShortcut(mods=0, key_name='j'), KeyAction('scroll_by', (1,))), # noqa
|
||||
(ParsedShortcut(mods=0, key_name='j'), KeyAction('scroll_by', (1,))),
|
||||
# scroll_down
|
||||
(ParsedShortcut(mods=0, key_name='DOWN'), KeyAction('scroll_by', (1,))), # noqa
|
||||
(ParsedShortcut(mods=0, key_name='DOWN'), KeyAction('scroll_by', (1,))),
|
||||
# scroll_up
|
||||
(ParsedShortcut(mods=0, key_name='k'), KeyAction('scroll_by', (-1,))), # noqa
|
||||
(ParsedShortcut(mods=0, key_name='k'), KeyAction('scroll_by', (-1,))),
|
||||
# scroll_up
|
||||
(ParsedShortcut(mods=0, key_name='UP'), KeyAction('scroll_by', (-1,))), # noqa
|
||||
(ParsedShortcut(mods=0, key_name='UP'), KeyAction('scroll_by', (-1,))),
|
||||
# scroll_top
|
||||
(ParsedShortcut(mods=0, key_name='HOME'), KeyAction('scroll_to', ('start',))), # noqa
|
||||
(ParsedShortcut(mods=0, key_name='HOME'), KeyAction('scroll_to', ('start',))),
|
||||
# scroll_bottom
|
||||
(ParsedShortcut(mods=0, key_name='END'), KeyAction('scroll_to', ('end',))), # noqa
|
||||
(ParsedShortcut(mods=0, key_name='END'), KeyAction('scroll_to', ('end',))),
|
||||
# scroll_page_down
|
||||
(ParsedShortcut(mods=0, key_name='PAGE_DOWN'), KeyAction('scroll_to', ('next-page',))), # noqa
|
||||
(ParsedShortcut(mods=0, key_name='PAGE_DOWN'), KeyAction('scroll_to', ('next-page',))),
|
||||
# scroll_page_down
|
||||
(ParsedShortcut(mods=0, key_name=' '), KeyAction('scroll_to', ('next-page',))), # noqa
|
||||
(ParsedShortcut(mods=0, key_name=' '), KeyAction('scroll_to', ('next-page',))),
|
||||
# scroll_page_up
|
||||
(ParsedShortcut(mods=0, key_name='PAGE_UP'), KeyAction('scroll_to', ('prev-page',))), # noqa
|
||||
(ParsedShortcut(mods=0, key_name='PAGE_UP'), KeyAction('scroll_to', ('prev-page',))),
|
||||
# next_change
|
||||
(ParsedShortcut(mods=0, key_name='n'), KeyAction('scroll_to', ('next-change',))), # noqa
|
||||
(ParsedShortcut(mods=0, key_name='n'), KeyAction('scroll_to', ('next-change',))),
|
||||
# prev_change
|
||||
(ParsedShortcut(mods=0, key_name='p'), KeyAction('scroll_to', ('prev-change',))), # noqa
|
||||
(ParsedShortcut(mods=0, key_name='p'), KeyAction('scroll_to', ('prev-change',))),
|
||||
# all_context
|
||||
(ParsedShortcut(mods=0, key_name='a'), KeyAction('change_context', ('all',))), # noqa
|
||||
(ParsedShortcut(mods=0, key_name='a'), KeyAction('change_context', ('all',))),
|
||||
# default_context
|
||||
(ParsedShortcut(mods=0, key_name='='), KeyAction('change_context', ('default',))), # noqa
|
||||
(ParsedShortcut(mods=0, key_name='='), KeyAction('change_context', ('default',))),
|
||||
# increase_context
|
||||
(ParsedShortcut(mods=0, key_name='+'), KeyAction('change_context', (5,))), # noqa
|
||||
(ParsedShortcut(mods=0, key_name='+'), KeyAction('change_context', (5,))),
|
||||
# decrease_context
|
||||
(ParsedShortcut(mods=0, key_name='-'), KeyAction('change_context', (-5,))), # noqa
|
||||
(ParsedShortcut(mods=0, key_name='-'), KeyAction('change_context', (-5,))),
|
||||
# search_forward
|
||||
(ParsedShortcut(mods=0, key_name='/'), KeyAction('start_search', (True, False))), # noqa
|
||||
(ParsedShortcut(mods=0, key_name='/'), KeyAction('start_search', (True, False))),
|
||||
# search_backward
|
||||
(ParsedShortcut(mods=0, key_name='?'), KeyAction('start_search', (True, True))), # noqa
|
||||
(ParsedShortcut(mods=0, key_name='?'), KeyAction('start_search', (True, True))),
|
||||
# next_match
|
||||
(ParsedShortcut(mods=0, key_name='.'), KeyAction('scroll_to', ('next-match',))), # noqa
|
||||
(ParsedShortcut(mods=0, key_name='.'), KeyAction('scroll_to', ('next-match',))),
|
||||
# next_match
|
||||
(ParsedShortcut(mods=0, key_name='>'), KeyAction('scroll_to', ('next-match',))), # noqa
|
||||
(ParsedShortcut(mods=0, key_name='>'), KeyAction('scroll_to', ('next-match',))),
|
||||
# prev_match
|
||||
(ParsedShortcut(mods=0, key_name=','), KeyAction('scroll_to', ('prev-match',))), # noqa
|
||||
(ParsedShortcut(mods=0, key_name=','), KeyAction('scroll_to', ('prev-match',))),
|
||||
# prev_match
|
||||
(ParsedShortcut(mods=0, key_name='<'), KeyAction('scroll_to', ('prev-match',))), # noqa
|
||||
(ParsedShortcut(mods=0, key_name='<'), KeyAction('scroll_to', ('prev-match',))),
|
||||
# search_forward_simple
|
||||
(ParsedShortcut(mods=0, key_name='f'), KeyAction('start_search', (False, False))), # noqa
|
||||
(ParsedShortcut(mods=0, key_name='f'), KeyAction('start_search', (False, False))),
|
||||
# search_backward_simple
|
||||
(ParsedShortcut(mods=0, key_name='b'), KeyAction('start_search', (False, True))), # noqa
|
||||
(ParsedShortcut(mods=0, key_name='b'), KeyAction('start_search', (False, True))),
|
||||
]
|
||||
|
||||
@@ -5,9 +5,7 @@
|
||||
|
||||
from typing import Any, Container, Dict, Iterable, Tuple, Union
|
||||
|
||||
from kitty.conf.utils import (
|
||||
KeyFuncWrapper, KittensKeyDefinition, parse_kittens_key
|
||||
)
|
||||
from kitty.conf.utils import KeyFuncWrapper, KittensKeyDefinition, parse_kittens_key
|
||||
|
||||
ReturnType = Tuple[str, Any]
|
||||
func_with_args = KeyFuncWrapper[ReturnType]()
|
||||
|
||||
@@ -13,10 +13,7 @@ from kitty.types import run_once
|
||||
from kitty.utils import ScreenSize
|
||||
|
||||
from ..tui.images import ImageManager, can_display_images
|
||||
from .collect import (
|
||||
Collection, Segment, data_for_path, highlights_for_path, is_image,
|
||||
lines_for_path, path_name_map, sanitize
|
||||
)
|
||||
from .collect import Collection, Segment, data_for_path, highlights_for_path, is_image, lines_for_path, path_name_map, sanitize
|
||||
from .config import formats
|
||||
from .diff_speedup import split_with_highlights as _split_with_highlights
|
||||
from .patch import Chunk, Hunk, Patch
|
||||
|
||||
@@ -5,9 +5,9 @@ import re
|
||||
from typing import TYPE_CHECKING, Callable, Dict, Iterable, List, Tuple
|
||||
|
||||
from kitty.fast_data_types import wcswidth
|
||||
from .options.types import Options as DiffOptions
|
||||
|
||||
from ..tui.operations import styled
|
||||
from .options.types import Options as DiffOptions
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .render import Line
|
||||
|
||||
@@ -8,10 +8,7 @@ import sys
|
||||
from functools import lru_cache
|
||||
from gettext import gettext as _
|
||||
from itertools import repeat
|
||||
from typing import (
|
||||
Any, Callable, Dict, Generator, Iterable, Iterator, List, Optional,
|
||||
Pattern, Sequence, Set, Tuple, Type, cast
|
||||
)
|
||||
from typing import Any, Callable, Dict, Generator, Iterable, Iterator, List, Optional, Pattern, Sequence, Set, Tuple, Type, cast
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cli_stub import HintsCLIOptions
|
||||
@@ -20,10 +17,7 @@ from kitty.constants import website_url
|
||||
from kitty.fast_data_types import get_options, wcswidth
|
||||
from kitty.key_encoding import KeyEvent
|
||||
from kitty.typing import BossType, KittyCommonOpts
|
||||
from kitty.utils import (
|
||||
ScreenSize, kitty_ansi_sanitizer_pat, resolve_custom_file,
|
||||
screen_size_function
|
||||
)
|
||||
from kitty.utils import ScreenSize, kitty_ansi_sanitizer_pat, resolve_custom_file, screen_size_function
|
||||
|
||||
from ..tui.handler import Handler, result_handler
|
||||
from ..tui.loop import Loop
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
#!/usr/bin/env python
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import signal
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Callable, cast
|
||||
from typing import Callable, List, cast
|
||||
from urllib.parse import quote_from_bytes
|
||||
|
||||
from kitty.utils import get_hostname
|
||||
|
||||
|
||||
def write_hyperlink(write: Callable[[bytes], None], url: bytes, line: bytes, frag: bytes = b'') -> None:
|
||||
text = b'\033]8;;' + url
|
||||
@@ -19,16 +21,46 @@ def write_hyperlink(write: Callable[[bytes], None], url: bytes, line: bytes, fra
|
||||
write(text)
|
||||
|
||||
|
||||
def parse_options(argv: List[str]) -> argparse.Namespace:
|
||||
p = argparse.ArgumentParser(add_help=False)
|
||||
p.add_argument('--context-separator', default='--')
|
||||
p.add_argument('-c', '--count', action='store_true')
|
||||
p.add_argument('--count-matches', action='store_true')
|
||||
p.add_argument('--field-context-separator', default='-')
|
||||
p.add_argument('--field-match-separator', default='-')
|
||||
p.add_argument('--files', action='store_true')
|
||||
p.add_argument('-l', '--files-with-matches', action='store_true')
|
||||
p.add_argument('--files-without-match', action='store_true')
|
||||
p.add_argument('-h', '--help', action='store_true')
|
||||
p.add_argument('--json', action='store_true')
|
||||
p.add_argument('-I', '--no-filename', action='store_true')
|
||||
p.add_argument('--no-heading', action='store_true')
|
||||
p.add_argument('-N', '--no-line-number', action='store_true')
|
||||
p.add_argument('-0', '--null', action='store_true')
|
||||
p.add_argument('--null-data', action='store_true')
|
||||
p.add_argument('--path-separator', default=os.path.sep)
|
||||
p.add_argument('--stats', action='store_true')
|
||||
p.add_argument('--type-list', action='store_true')
|
||||
p.add_argument('-V', '--version', action='store_true')
|
||||
p.add_argument('--vimgrep', action='store_true')
|
||||
p.add_argument(
|
||||
'-p', '--pretty',
|
||||
default=sys.stdout.isatty(),
|
||||
action='store_true',
|
||||
)
|
||||
p.add_argument('--kitten', action='append', default=[])
|
||||
args, _ = p.parse_known_args(argv)
|
||||
return args
|
||||
|
||||
|
||||
def main() -> None:
|
||||
i = 1
|
||||
args = parse_options(sys.argv[1:])
|
||||
all_link_options = {'matching_lines', 'context_lines', 'file_headers'}
|
||||
link_options = set()
|
||||
delegate_to_rg = False
|
||||
|
||||
def parse_link_options(raw: str) -> None:
|
||||
nonlocal delegate_to_rg
|
||||
if not raw:
|
||||
raise SystemExit('Must specify an argument for --kitten option')
|
||||
for raw in args.kitten:
|
||||
p, _, s = raw.partition('=')
|
||||
if p != 'hyperlink':
|
||||
raise SystemExit(f'Unknown argument for --kitten: {raw}')
|
||||
@@ -48,11 +80,8 @@ def main() -> None:
|
||||
|
||||
while i < len(sys.argv):
|
||||
if sys.argv[i] == '--kitten':
|
||||
next_item = '' if i + 1 >= len(sys.argv) else sys.argv[i + 1]
|
||||
parse_link_options(next_item)
|
||||
del sys.argv[i:i+2]
|
||||
elif sys.argv[i].startswith('--kitten='):
|
||||
parse_link_options(sys.argv[i][len('--kitten='):])
|
||||
del sys.argv[i]
|
||||
else:
|
||||
i += 1
|
||||
@@ -62,7 +91,23 @@ def main() -> None:
|
||||
link_context_lines = 'context_lines' in link_options
|
||||
link_matching_lines = 'matching_lines' in link_options
|
||||
|
||||
if delegate_to_rg or (not sys.stdout.isatty() and '--pretty' not in sys.argv and '-p' not in sys.argv):
|
||||
if any((
|
||||
args.context_separator != '--',
|
||||
args.field_context_separator != '-',
|
||||
args.field_match_separator != '-',
|
||||
args.help,
|
||||
args.json,
|
||||
args.no_filename,
|
||||
args.null,
|
||||
args.null_data,
|
||||
args.path_separator != os.path.sep,
|
||||
args.type_list,
|
||||
args.version,
|
||||
not args.pretty,
|
||||
)):
|
||||
delegate_to_rg = True
|
||||
|
||||
if delegate_to_rg:
|
||||
os.execlp('rg', 'rg', *sys.argv[1:])
|
||||
cmdline = ['rg', '--pretty', '--with-filename'] + sys.argv[1:]
|
||||
try:
|
||||
@@ -70,13 +115,22 @@ def main() -> None:
|
||||
except FileNotFoundError:
|
||||
raise SystemExit('Could not find the rg executable in your PATH. Is ripgrep installed?')
|
||||
assert p.stdout is not None
|
||||
|
||||
write: Callable[[bytes], None] = cast(Callable[[bytes], None], sys.stdout.buffer.write)
|
||||
sgr_pat = re.compile(br'\x1b\[.*?m')
|
||||
osc_pat = re.compile(b'\x1b\\].*?\x1b\\\\')
|
||||
num_pat = re.compile(br'^(\d+)([:-])')
|
||||
path_with_count_pat = re.compile(br'(.*?)(:\d+)')
|
||||
path_with_linenum_pat = re.compile(br'^(.*?):(\d+):')
|
||||
stats_pat = re.compile(br'^\d+ matches$')
|
||||
vimgrep_pat = re.compile(br'^(.*?):(\d+):(\d+):')
|
||||
|
||||
in_stats = False
|
||||
in_result: bytes = b''
|
||||
hostname = socket.gethostname().encode('utf-8')
|
||||
hostname = get_hostname().encode('utf-8')
|
||||
|
||||
def get_quoted_url(file_path: bytes) -> bytes:
|
||||
return b'file://' + hostname + quote_from_bytes(os.path.abspath(file_path)).encode('utf-8')
|
||||
|
||||
try:
|
||||
for line in p.stdout:
|
||||
@@ -85,21 +139,42 @@ def main() -> None:
|
||||
if not clean_line:
|
||||
in_result = b''
|
||||
write(b'\n')
|
||||
elif in_stats:
|
||||
write(line)
|
||||
elif in_result:
|
||||
m = num_pat.match(clean_line)
|
||||
if m is not None:
|
||||
is_match_line = m.group(2) == b':'
|
||||
if (is_match_line and link_matching_lines) or (not is_match_line and link_context_lines):
|
||||
write_hyperlink(write, in_result, line, frag=m.group(1))
|
||||
continue
|
||||
if not args.no_line_number:
|
||||
m = num_pat.match(clean_line)
|
||||
if m is not None:
|
||||
is_match_line = m.group(2) == b':'
|
||||
if (is_match_line and link_matching_lines) or (not is_match_line and link_context_lines):
|
||||
write_hyperlink(write, in_result, line, frag=m.group(1))
|
||||
continue
|
||||
write(line)
|
||||
else:
|
||||
if line.strip():
|
||||
path = quote_from_bytes(os.path.abspath(clean_line)).encode('utf-8')
|
||||
in_result = b'file://' + hostname + path
|
||||
if link_file_headers:
|
||||
write_hyperlink(write, in_result, line)
|
||||
continue
|
||||
# The option priority should be consistent with ripgrep here.
|
||||
if args.stats and not in_stats and stats_pat.match(clean_line):
|
||||
in_stats = True
|
||||
elif args.count or args.count_matches:
|
||||
m = path_with_count_pat.match(clean_line)
|
||||
if m is not None and link_file_headers:
|
||||
write_hyperlink(write, get_quoted_url(m.group(1)), line)
|
||||
continue
|
||||
elif args.files or args.files_with_matches or args.files_without_match:
|
||||
if link_file_headers:
|
||||
write_hyperlink(write, get_quoted_url(clean_line), line)
|
||||
continue
|
||||
elif args.vimgrep or args.no_heading:
|
||||
# When the vimgrep option is present, it will take precedence.
|
||||
m = vimgrep_pat.match(clean_line) if args.vimgrep else path_with_linenum_pat.match(clean_line)
|
||||
if m is not None and (link_file_headers or link_matching_lines):
|
||||
write_hyperlink(write, get_quoted_url(m.group(1)), line, frag=m.group(2))
|
||||
continue
|
||||
else:
|
||||
in_result = get_quoted_url(clean_line)
|
||||
if link_file_headers:
|
||||
write_hyperlink(write, in_result, line)
|
||||
continue
|
||||
write(line)
|
||||
except KeyboardInterrupt:
|
||||
p.send_signal(signal.SIGINT)
|
||||
@@ -112,3 +187,6 @@ def main() -> None:
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
elif __name__ == '__wrapper_of__':
|
||||
cd = sys.cli_docs # type: ignore
|
||||
cd['wrapper_of'] = 'rg'
|
||||
|
||||
@@ -1,38 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import re
|
||||
import signal
|
||||
import socket
|
||||
import sys
|
||||
import zlib
|
||||
from base64 import standard_b64encode
|
||||
from math import ceil
|
||||
from tempfile import NamedTemporaryFile
|
||||
from typing import (
|
||||
Dict, Generator, List, NamedTuple, Optional, Pattern, Tuple, Union
|
||||
)
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cli_stub import IcatCLIOptions
|
||||
from kitty.constants import appname
|
||||
from kitty.guess_mime_type import guess_type
|
||||
from kitty.rgb import to_color
|
||||
from kitty.types import run_once
|
||||
from kitty.typing import GRT_f, GRT_t
|
||||
from kitty.utils import (
|
||||
TTYIO, ScreenSize, ScreenSizeGetter, screen_size_function
|
||||
)
|
||||
|
||||
from ..tui.images import (
|
||||
ConvertFailed, Dispose, GraphicsCommand, NoImageMagick, OpenFailed,
|
||||
OutdatedImageMagick, RenderedImage, fsenc, identify,
|
||||
render_as_single_image, render_image
|
||||
)
|
||||
from ..tui.operations import clear_images_on_screen, raw_mode
|
||||
|
||||
OPTIONS = '''\
|
||||
--align
|
||||
type=choices
|
||||
@@ -46,7 +14,10 @@ Choose where on the screen to display the image. The image will be scaled to fit
|
||||
into the specified rectangle. The syntax for specifying rectangles is
|
||||
<:italic:`width`>x<:italic:`height`>@<:italic:`left`>x<:italic:`top`>.
|
||||
All measurements are in cells (i.e. cursor positions) with the origin
|
||||
:italic:`(0, 0)` at the top-left corner of the screen.
|
||||
:italic:`(0, 0)` at the top-left corner of the screen. Note that the :option:`--align`
|
||||
option will horizontally align the image within this rectangle. By default, the image
|
||||
is horizontally centered within the rectangle. Using place will cause the cursor to
|
||||
be positioned at the top left corner of the image, instead of on the line after the image.
|
||||
|
||||
|
||||
--scale-up
|
||||
@@ -76,13 +47,14 @@ Remove all images currently displayed on the screen.
|
||||
|
||||
--transfer-mode
|
||||
type=choices
|
||||
choices=detect,file,stream
|
||||
choices=detect,file,stream,memory
|
||||
default=detect
|
||||
Which mechanism to use to transfer images to the terminal. The default is to
|
||||
auto-detect. :italic:`file` means to use a temporary file and :italic:`stream`
|
||||
means to send the data via terminal escape codes. Note that if you use the
|
||||
:italic:`file` transfer mode and you are connecting over a remote session then
|
||||
image display will not work.
|
||||
auto-detect. :italic:`file` means to use a temporary file, :italic:`memory` means
|
||||
to use shared memory, :italic:`stream` means to send the data via terminal
|
||||
escape codes. Note that if you use the :italic:`file` or :italic:`memory` transfer
|
||||
modes and you are connecting over a remote session then image display will not
|
||||
work.
|
||||
|
||||
|
||||
--detect-support
|
||||
@@ -117,7 +89,17 @@ not a terminal, but you can turn it off or on explicitly, if needed.
|
||||
|
||||
--silent
|
||||
type=bool-set
|
||||
Do not print out anything to STDOUT during operation.
|
||||
Not used, present for legacy compatibility.
|
||||
|
||||
|
||||
--engine
|
||||
type=choices
|
||||
choices=auto,builtin,magick
|
||||
default=auto
|
||||
The engine used for decoding and processing of images. The default is to use
|
||||
the most appropriate engine. The :code:`builtin` engine uses Go's native
|
||||
imaging libraries. The :code:`magick` engine uses ImageMagick which requires
|
||||
it to be installed on the system.
|
||||
|
||||
|
||||
--z-index -z
|
||||
@@ -140,306 +122,6 @@ type=bool-set
|
||||
Wait for a key press before exiting after displaying the images.
|
||||
'''
|
||||
|
||||
|
||||
screen_size: Optional[ScreenSizeGetter] = None
|
||||
can_transfer_with_files = False
|
||||
|
||||
|
||||
def get_screen_size_function() -> ScreenSizeGetter:
|
||||
global screen_size
|
||||
if screen_size is None:
|
||||
screen_size = screen_size_function()
|
||||
return screen_size
|
||||
|
||||
|
||||
def get_screen_size() -> ScreenSize:
|
||||
screen_size = get_screen_size_function()
|
||||
return screen_size()
|
||||
|
||||
|
||||
@run_once
|
||||
def options_spec() -> str:
|
||||
return OPTIONS.format(appname=f'{appname}-icat')
|
||||
|
||||
|
||||
def write_gr_cmd(cmd: GraphicsCommand, payload: Optional[bytes] = None) -> None:
|
||||
sys.stdout.buffer.write(cmd.serialize(payload or b''))
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def calculate_in_cell_x_offset(width: int, cell_width: int, align: str) -> int:
|
||||
if align == 'left':
|
||||
return 0
|
||||
extra_pixels = width % cell_width
|
||||
if not extra_pixels:
|
||||
return 0
|
||||
if align == 'right':
|
||||
return cell_width - extra_pixels
|
||||
return (cell_width - extra_pixels) // 2
|
||||
|
||||
|
||||
def set_cursor(cmd: GraphicsCommand, width: int, height: int, align: str) -> None:
|
||||
ss = get_screen_size()
|
||||
cw = int(ss.width / ss.cols)
|
||||
num_of_cells_needed = int(ceil(width / cw))
|
||||
cmd.X = calculate_in_cell_x_offset(width, cw, align)
|
||||
extra_cells = 0
|
||||
if align == 'center':
|
||||
extra_cells = (ss.cols - num_of_cells_needed) // 2
|
||||
elif align == 'right':
|
||||
extra_cells = (ss.cols - num_of_cells_needed)
|
||||
if extra_cells:
|
||||
sys.stdout.buffer.write(b' ' * extra_cells)
|
||||
|
||||
|
||||
def set_cursor_for_place(place: 'Place', cmd: GraphicsCommand, width: int, height: int, align: str) -> None:
|
||||
x = place.left + 1
|
||||
ss = get_screen_size()
|
||||
cw = int(ss.width / ss.cols)
|
||||
num_of_cells_needed = int(ceil(width / cw))
|
||||
cmd.X = calculate_in_cell_x_offset(width, cw, align)
|
||||
extra_cells = 0
|
||||
if align == 'center':
|
||||
extra_cells = (place.width - num_of_cells_needed) // 2
|
||||
elif align == 'right':
|
||||
extra_cells = place.width - num_of_cells_needed
|
||||
sys.stdout.buffer.write(f'\033[{place.top + 1};{x + extra_cells}H'.encode('ascii'))
|
||||
|
||||
|
||||
def write_chunked(cmd: GraphicsCommand, data: bytes) -> None:
|
||||
cmd = cmd.clone()
|
||||
if cmd.f != 100:
|
||||
data = zlib.compress(data)
|
||||
cmd.o = 'z'
|
||||
data = standard_b64encode(data)
|
||||
ac = cmd.a
|
||||
quiet = cmd.q
|
||||
while data:
|
||||
chunk, data = data[:4096], data[4096:]
|
||||
cmd.m = 1 if data else 0
|
||||
write_gr_cmd(cmd, chunk)
|
||||
cmd.clear()
|
||||
cmd.a = ac
|
||||
cmd.q = quiet
|
||||
|
||||
|
||||
def show(
|
||||
outfile: str,
|
||||
width: int, height: int, zindex: int,
|
||||
fmt: 'GRT_f',
|
||||
transmit_mode: 'GRT_t' = 't',
|
||||
align: str = 'center',
|
||||
place: Optional['Place'] = None,
|
||||
use_number: int = 0
|
||||
) -> None:
|
||||
cmd = GraphicsCommand()
|
||||
cmd.a = 'T'
|
||||
cmd.f = fmt
|
||||
cmd.s = width
|
||||
cmd.v = height
|
||||
cmd.z = zindex
|
||||
if use_number:
|
||||
cmd.I = use_number # noqa
|
||||
cmd.q = 2
|
||||
if place:
|
||||
set_cursor_for_place(place, cmd, width, height, align)
|
||||
else:
|
||||
set_cursor(cmd, width, height, align)
|
||||
if can_transfer_with_files:
|
||||
cmd.t = transmit_mode
|
||||
write_gr_cmd(cmd, standard_b64encode(os.path.abspath(outfile).encode(fsenc)))
|
||||
else:
|
||||
with open(outfile, 'rb') as f:
|
||||
data = f.read()
|
||||
if transmit_mode == 't':
|
||||
os.unlink(outfile)
|
||||
if fmt == 100:
|
||||
cmd.S = len(data)
|
||||
write_chunked(cmd, data)
|
||||
|
||||
|
||||
def show_frames(frame_data: RenderedImage, use_number: int, loops: int) -> None:
|
||||
transmit_cmd = GraphicsCommand()
|
||||
transmit_cmd.a = 'f'
|
||||
transmit_cmd.I = use_number # noqa
|
||||
transmit_cmd.q = 2
|
||||
if can_transfer_with_files:
|
||||
transmit_cmd.t = 't'
|
||||
transmit_cmd.f = 24 if frame_data.mode == 'rgb' else 32
|
||||
|
||||
def control(frame_number: int = 0, loops: Optional[int] = None, gap: Optional[int] = 0, animation_control: int = 0) -> None:
|
||||
cmd = GraphicsCommand()
|
||||
cmd.a = 'a'
|
||||
cmd.I = use_number # noqa
|
||||
cmd.r = frame_number
|
||||
if loops is not None:
|
||||
cmd.v = loops + 1
|
||||
if gap is not None:
|
||||
cmd.z = gap if gap > 0 else -1
|
||||
if animation_control:
|
||||
cmd.s = animation_control
|
||||
write_gr_cmd(cmd)
|
||||
|
||||
anchor_frame = 0
|
||||
|
||||
for frame in frame_data.frames:
|
||||
frame_number = frame.index + 1
|
||||
if frame.dispose < Dispose.previous:
|
||||
anchor_frame = frame_number
|
||||
if frame_number == 1:
|
||||
control(frame_number, gap=frame.gap, loops=None if loops < 1 else loops)
|
||||
continue
|
||||
if frame.dispose is Dispose.previous:
|
||||
if anchor_frame != frame_number:
|
||||
transmit_cmd.c = anchor_frame
|
||||
else:
|
||||
transmit_cmd.c = (frame_number - 1) if frame.needs_blend else 0
|
||||
transmit_cmd.s = frame.width
|
||||
transmit_cmd.v = frame.height
|
||||
transmit_cmd.x = frame.canvas_x
|
||||
transmit_cmd.y = frame.canvas_y
|
||||
transmit_cmd.z = frame.gap if frame.gap > 0 else -1
|
||||
if can_transfer_with_files:
|
||||
write_gr_cmd(transmit_cmd, standard_b64encode(os.path.abspath(frame.path).encode(fsenc)))
|
||||
else:
|
||||
with open(frame.path, 'rb') as f:
|
||||
data = f.read()
|
||||
write_chunked(transmit_cmd, data)
|
||||
if frame_number == 2:
|
||||
control(animation_control=2)
|
||||
control(animation_control=3)
|
||||
|
||||
|
||||
def parse_z_index(val: str) -> int:
|
||||
origin = 0
|
||||
if val.startswith('--'):
|
||||
val = val[1:]
|
||||
origin = -1073741824
|
||||
return origin + int(val)
|
||||
|
||||
|
||||
class ParsedOpts:
|
||||
|
||||
place: Optional['Place'] = None
|
||||
z_index: int = 0
|
||||
remove_alpha: str = ''
|
||||
flip: bool = False
|
||||
flop: bool = False
|
||||
|
||||
|
||||
def process(path: str, args: IcatCLIOptions, parsed_opts: ParsedOpts, is_tempfile: bool) -> bool:
|
||||
m = identify(path)
|
||||
ss = get_screen_size()
|
||||
available_width = parsed_opts.place.width * (ss.width // ss.cols) if parsed_opts.place else ss.width
|
||||
available_height = parsed_opts.place.height * (ss.height // ss.rows) if parsed_opts.place else 10 * m.height
|
||||
needs_scaling = m.width > available_width or m.height > available_height
|
||||
needs_scaling = needs_scaling or args.scale_up
|
||||
needs_conversion = needs_scaling or bool(parsed_opts.remove_alpha) or parsed_opts.flip or parsed_opts.flop
|
||||
file_removed = False
|
||||
use_number = 0
|
||||
if m.fmt == 'png' and not needs_conversion:
|
||||
outfile = path
|
||||
transmit_mode: 'GRT_t' = 't' if is_tempfile else 'f'
|
||||
fmt: 'GRT_f' = 100
|
||||
width, height = m.width, m.height
|
||||
file_removed = transmit_mode == 't'
|
||||
else:
|
||||
fmt = 24 if m.mode == 'rgb' else 32
|
||||
transmit_mode = 't'
|
||||
if len(m) == 1 or args.loop == 0:
|
||||
outfile, width, height = render_as_single_image(
|
||||
path, m, available_width, available_height, args.scale_up,
|
||||
remove_alpha=parsed_opts.remove_alpha, flip=parsed_opts.flip, flop=parsed_opts.flop)
|
||||
else:
|
||||
import struct
|
||||
use_number = max(1, struct.unpack('@I', os.urandom(4))[0])
|
||||
with NamedTemporaryFile(prefix='tty-graphics-protocol-') as f:
|
||||
prefix = f.name
|
||||
frame_data = render_image(
|
||||
path, prefix, m, available_width, available_height, args.scale_up,
|
||||
remove_alpha=parsed_opts.remove_alpha, flip=parsed_opts.flip, flop=parsed_opts.flop)
|
||||
outfile, width, height = frame_data.frames[0].path, frame_data.width, frame_data.height
|
||||
show(
|
||||
outfile, width, height, parsed_opts.z_index, fmt, transmit_mode,
|
||||
align=args.align, place=parsed_opts.place, use_number=use_number
|
||||
)
|
||||
if use_number:
|
||||
show_frames(frame_data, use_number, args.loop)
|
||||
if not can_transfer_with_files:
|
||||
for fr in frame_data.frames:
|
||||
with contextlib.suppress(FileNotFoundError):
|
||||
os.unlink(fr.path)
|
||||
if not args.place:
|
||||
print() # ensure cursor is on a new line
|
||||
return file_removed
|
||||
|
||||
|
||||
def scan(d: str) -> Generator[Tuple[str, str], None, None]:
|
||||
for dirpath, dirnames, filenames in os.walk(d):
|
||||
for f in filenames:
|
||||
mt = guess_type(f)
|
||||
if mt and mt.startswith('image/'):
|
||||
yield os.path.join(dirpath, f), mt
|
||||
|
||||
|
||||
def detect_support(wait_for: float = 10, silent: bool = False) -> bool:
|
||||
global can_transfer_with_files
|
||||
if not silent:
|
||||
print(f'Checking for graphics ({wait_for}s max. wait)...', end='\r')
|
||||
sys.stdout.flush()
|
||||
try:
|
||||
received = b''
|
||||
responses: Dict[int, bool] = {}
|
||||
|
||||
def parse_responses() -> None:
|
||||
for m in re.finditer(b'\033_Gi=([1|2]);(.+?)\033\\\\', received):
|
||||
iid = m.group(1)
|
||||
if iid in (b'1', b'2'):
|
||||
iid_ = int(iid.decode('ascii'))
|
||||
if iid_ not in responses:
|
||||
responses[iid_] = m.group(2) == b'OK'
|
||||
|
||||
def more_needed(data: bytes) -> bool:
|
||||
nonlocal received
|
||||
received += data
|
||||
parse_responses()
|
||||
return 1 not in responses or 2 not in responses
|
||||
|
||||
with NamedTemporaryFile(prefix='tty-graphics-protocol') as f:
|
||||
f.write(b'abcd')
|
||||
f.flush()
|
||||
gc = GraphicsCommand()
|
||||
gc.a = 'q'
|
||||
gc.s = gc.v = gc.i = 1
|
||||
write_gr_cmd(gc, standard_b64encode(b'abcd'))
|
||||
gc.t = 'f'
|
||||
gc.i = 2
|
||||
write_gr_cmd(gc, standard_b64encode(f.name.encode(fsenc)))
|
||||
with TTYIO() as io:
|
||||
io.recv(more_needed, timeout=wait_for)
|
||||
finally:
|
||||
if not silent:
|
||||
sys.stdout.buffer.write(b'\033[J'), sys.stdout.flush()
|
||||
can_transfer_with_files = bool(responses.get(2))
|
||||
return responses.get(1, False)
|
||||
|
||||
|
||||
class Place(NamedTuple):
|
||||
width: int
|
||||
height: int
|
||||
left: int
|
||||
top: int
|
||||
|
||||
|
||||
def parse_place(raw: str) -> Optional[Place]:
|
||||
if raw:
|
||||
area, pos = raw.split('@', 1)
|
||||
w, h = map(int, area.split('x'))
|
||||
l, t = map(int, pos.split('x'))
|
||||
return Place(w, h, l, t)
|
||||
return None
|
||||
|
||||
|
||||
help_text = (
|
||||
'A cat like utility to display images in the terminal.'
|
||||
' You can specify multiple image files and/or directories.'
|
||||
@@ -451,174 +133,15 @@ help_text = (
|
||||
usage = 'image-file-or-url-or-directory ...'
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def socket_timeout(seconds: int) -> Generator[None, None, None]:
|
||||
old = socket.getdefaulttimeout()
|
||||
socket.setdefaulttimeout(seconds)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
socket.setdefaulttimeout(old)
|
||||
|
||||
|
||||
def process_single_item(
|
||||
item: Union[bytes, str],
|
||||
args: IcatCLIOptions,
|
||||
parsed_opts: ParsedOpts,
|
||||
url_pat: Optional['Pattern[str]'] = None,
|
||||
maybe_dir: bool = True
|
||||
) -> None:
|
||||
is_tempfile = False
|
||||
file_removed = False
|
||||
try:
|
||||
if isinstance(item, bytes):
|
||||
with NamedTemporaryFile(prefix='tty-graphics-protocol-', delete=False) as tf:
|
||||
tf.write(item)
|
||||
item = tf.name
|
||||
is_tempfile = True
|
||||
if url_pat is not None and url_pat.match(item) is not None:
|
||||
from urllib.request import urlretrieve
|
||||
with NamedTemporaryFile(prefix='tty-graphics-protocol-', delete=False) as tf:
|
||||
try:
|
||||
with socket_timeout(30):
|
||||
urlretrieve(item, filename=tf.name)
|
||||
except Exception as e:
|
||||
raise SystemExit(f'Failed to download image at URL: {item} with error: {e}')
|
||||
item = tf.name
|
||||
is_tempfile = True
|
||||
file_removed = process(item, args, parsed_opts, is_tempfile)
|
||||
elif item.lower().startswith('file://'):
|
||||
from urllib.parse import urlparse
|
||||
from urllib.request import url2pathname
|
||||
pitem = urlparse(item)
|
||||
if os.sep == '\\':
|
||||
item = pitem.netloc + pitem.path
|
||||
else:
|
||||
item = pitem.path
|
||||
item = url2pathname(item)
|
||||
file_removed = process(item, args, parsed_opts, is_tempfile)
|
||||
else:
|
||||
if maybe_dir and os.path.isdir(item):
|
||||
for (x, mt) in scan(item):
|
||||
process_single_item(x, args, parsed_opts, url_pat=None, maybe_dir=False)
|
||||
else:
|
||||
file_removed = process(item, args, parsed_opts, is_tempfile)
|
||||
finally:
|
||||
if is_tempfile and not file_removed:
|
||||
os.remove(item)
|
||||
|
||||
|
||||
def main(args: List[str] = sys.argv) -> None:
|
||||
global can_transfer_with_files
|
||||
cli_opts, items_ = parse_args(args[1:], options_spec, usage, help_text, f'{appname} +kitten icat', result_class=IcatCLIOptions)
|
||||
items: List[Union[str, bytes]] = list(items_)
|
||||
|
||||
if cli_opts.print_window_size:
|
||||
screen_size_function.cache_clear()
|
||||
with open(os.ctermid()) as tty:
|
||||
try:
|
||||
fd = tty.fileno()
|
||||
except AttributeError:
|
||||
# use default value for fd if ctermid is not available
|
||||
fd = None
|
||||
ss = screen_size_function(fd)()
|
||||
print(f'{ss.width}x{ss.height}', end='')
|
||||
raise SystemExit(0)
|
||||
|
||||
if not sys.stdout.isatty():
|
||||
sys.stdout = open(os.ctermid(), 'w')
|
||||
stdin_data = None
|
||||
if cli_opts.stdin == 'yes' or (cli_opts.stdin == 'detect' and sys.stdin is not None and not sys.stdin.isatty()):
|
||||
stdin_data = sys.stdin.buffer.read()
|
||||
if stdin_data:
|
||||
items.insert(0, stdin_data)
|
||||
sys.stdin.close()
|
||||
sys.stdin = open(os.ctermid())
|
||||
|
||||
screen_size = get_screen_size_function()
|
||||
signal.signal(signal.SIGWINCH, lambda signum, frame: setattr(screen_size, 'changed', True))
|
||||
if screen_size().width == 0:
|
||||
if cli_opts.detect_support:
|
||||
raise SystemExit(1)
|
||||
raise SystemExit(
|
||||
'Terminal does not support reporting screen sizes via the TIOCGWINSZ ioctl'
|
||||
)
|
||||
parsed_opts = ParsedOpts()
|
||||
if cli_opts.place:
|
||||
try:
|
||||
parsed_opts.place = parse_place(cli_opts.place)
|
||||
except Exception:
|
||||
raise SystemExit(f'Not a valid place specification: {cli_opts.place}')
|
||||
|
||||
try:
|
||||
parsed_opts.z_index = parse_z_index(cli_opts.z_index)
|
||||
except Exception:
|
||||
raise SystemExit(f'Not a valid z-index specification: {cli_opts.z_index}')
|
||||
if cli_opts.background != 'none':
|
||||
ra = to_color(cli_opts.background)
|
||||
if ra is None:
|
||||
raise SystemExit(f'Not a valid color specification: {cli_opts.background}')
|
||||
parsed_opts.remove_alpha = ra.as_sharp
|
||||
parsed_opts.flip = cli_opts.mirror in ('both', 'vertical')
|
||||
parsed_opts.flop = cli_opts.mirror in ('both', 'horizontal')
|
||||
|
||||
if cli_opts.detect_support:
|
||||
if not detect_support(wait_for=cli_opts.detection_timeout, silent=True):
|
||||
raise SystemExit(1)
|
||||
print('file' if can_transfer_with_files else 'stream', end='', file=sys.stderr)
|
||||
return
|
||||
if cli_opts.transfer_mode == 'detect':
|
||||
if not detect_support(wait_for=cli_opts.detection_timeout, silent=cli_opts.silent):
|
||||
raise SystemExit('This terminal emulator does not support the graphics protocol, use a terminal emulator such as kitty that does support it')
|
||||
else:
|
||||
can_transfer_with_files = cli_opts.transfer_mode == 'file'
|
||||
errors = []
|
||||
if cli_opts.clear:
|
||||
sys.stdout.write(clear_images_on_screen(delete_data=True))
|
||||
if not items:
|
||||
return
|
||||
if not items:
|
||||
raise SystemExit('You must specify at least one file to cat')
|
||||
if parsed_opts.place:
|
||||
if len(items) > 1 or (isinstance(items[0], str) and os.path.isdir(items[0])):
|
||||
raise SystemExit(f'The --place option can only be used with a single image, not {items}')
|
||||
sys.stdout.buffer.write(b'\0337') # save cursor
|
||||
url_pat = re.compile(r'(?:https?|ftp)://', flags=re.I)
|
||||
|
||||
def hold_if_needed(exit_code_or_msg: Union[int, str]) -> None:
|
||||
if cli_opts.hold:
|
||||
if isinstance(exit_code_or_msg, str):
|
||||
print(exit_code_or_msg, file=sys.stderr, flush=True)
|
||||
exit_code_or_msg = 1
|
||||
with open(os.ctermid()) as tty, raw_mode(tty.fileno()):
|
||||
tty.buffer.read(1)
|
||||
raise SystemExit(exit_code_or_msg)
|
||||
|
||||
for item in items:
|
||||
try:
|
||||
process_single_item(item, cli_opts, parsed_opts, url_pat)
|
||||
except NoImageMagick as e:
|
||||
hold_if_needed(str(e))
|
||||
except OutdatedImageMagick as e:
|
||||
print(e.detailed_error, file=sys.stderr)
|
||||
hold_if_needed(str(e))
|
||||
except ConvertFailed as e:
|
||||
hold_if_needed(str(e))
|
||||
except OpenFailed as e:
|
||||
errors.append(e)
|
||||
if parsed_opts.place:
|
||||
sys.stdout.buffer.write(b'\0338') # restore cursor
|
||||
if errors:
|
||||
for err in errors:
|
||||
print(err, file=sys.stderr)
|
||||
hold_if_needed(1 if errors else 0)
|
||||
raise SystemExit()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
raise SystemExit('This should be run as kitten icat')
|
||||
elif __name__ == '__doc__':
|
||||
import sys
|
||||
|
||||
from kitty.cli import CompletionSpec
|
||||
cd = sys.cli_docs # type: ignore
|
||||
cd['usage'] = usage
|
||||
cd['options'] = options_spec
|
||||
cd['options'] = lambda: OPTIONS.format()
|
||||
cd['help_text'] = help_text
|
||||
cd['short_desc'] = 'Display images in the terminal'
|
||||
cd['args_completion'] = CompletionSpec.from_string('type:file mime:image/* group:Images')
|
||||
|
||||
@@ -8,8 +8,8 @@ from typing import Any, Callable, Dict, List, Tuple
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cli_stub import PanelCLIOptions
|
||||
from kitty.constants import appname, is_macos
|
||||
from kitty.os_window_size import WindowSizeData
|
||||
from kitty.fast_data_types import make_x11_window_a_dock_window
|
||||
from kitty.os_window_size import WindowSizeData
|
||||
|
||||
OPTIONS = r'''
|
||||
--lines
|
||||
@@ -134,7 +134,8 @@ def main(sys_args: List[str]) -> None:
|
||||
for override in args.override:
|
||||
sys.argv.extend(('--override', override))
|
||||
sys.argv.extend(items)
|
||||
from kitty.main import main as real_main, run_app
|
||||
from kitty.main import main as real_main
|
||||
from kitty.main import run_app
|
||||
run_app.cached_values_name = 'panel'
|
||||
run_app.first_window_callback = setup_x11_window
|
||||
run_app.initial_window_size_func = initial_window_size_func
|
||||
|
||||
@@ -5,7 +5,7 @@ import re
|
||||
import sys
|
||||
from binascii import hexlify, unhexlify
|
||||
from contextlib import suppress
|
||||
from typing import Dict, Iterable, List, Type, Optional
|
||||
from typing import Dict, Iterable, List, Optional, Type
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cli_stub import QueryTerminalCLIOptions
|
||||
|
||||
@@ -16,17 +16,12 @@ from kitty.cli import parse_args
|
||||
from kitty.cli_stub import RemoteFileCLIOptions
|
||||
from kitty.constants import cache_dir
|
||||
from kitty.typing import BossType
|
||||
from kitty.utils import (
|
||||
SSHConnectionData, command_for_open, get_editor, open_cmd
|
||||
)
|
||||
from kitty.utils import SSHConnectionData, command_for_open, get_editor, open_cmd
|
||||
|
||||
from ..tui.handler import result_handler
|
||||
from ..tui.operations import (
|
||||
faint, raw_mode, reset_terminal, styled
|
||||
)
|
||||
from ..tui.operations import faint, raw_mode, reset_terminal, styled
|
||||
from ..tui.utils import get_key_press
|
||||
|
||||
|
||||
is_ssh_kitten_sentinel = '!#*&$#($ssh-kitten)(##$'
|
||||
|
||||
|
||||
@@ -234,9 +229,9 @@ Result = Optional[str]
|
||||
|
||||
|
||||
def main(args: List[str]) -> Result:
|
||||
msg = 'Ask the user what to do with the remote file'
|
||||
msg = 'Ask the user what to do with the remote file. For internal use by kitty, do not run it directly.'
|
||||
try:
|
||||
cli_opts, items = parse_args(args[1:], option_text, '', msg, 'kitty remote_file', result_class=RemoteFileCLIOptions)
|
||||
cli_opts, items = parse_args(args[1:], option_text, '', msg, 'kitty +kitten remote_file', result_class=RemoteFileCLIOptions)
|
||||
except SystemExit as e:
|
||||
if e.code != 0:
|
||||
print(e.args[0])
|
||||
|
||||
@@ -93,8 +93,8 @@ def launch(args: List[str]) -> None:
|
||||
finally:
|
||||
sys.stdin = sys.__stdin__
|
||||
if result is not None:
|
||||
import json
|
||||
import base64
|
||||
import json
|
||||
data = base64.b85encode(json.dumps(result).encode('utf-8'))
|
||||
sys.stdout.buffer.write(b'\x1bP@kitty-kitten-result|')
|
||||
sys.stdout.buffer.write(data)
|
||||
@@ -155,6 +155,14 @@ def get_kitten_cli_docs(kitten: str) -> Any:
|
||||
return ans
|
||||
|
||||
|
||||
def get_kitten_wrapper_of(kitten: str) -> str:
|
||||
setattr(sys, 'cli_docs', {})
|
||||
run_kitten(kitten, run_name='__wrapper_of__')
|
||||
ans = getattr(sys, 'cli_docs')
|
||||
delattr(sys, 'cli_docs')
|
||||
return ans.get('wrapper_of') or ''
|
||||
|
||||
|
||||
def get_kitten_completer(kitten: str) -> Any:
|
||||
run_kitten(kitten, run_name='__completer__')
|
||||
ans = getattr(sys, 'kitten_completer', None)
|
||||
|
||||
@@ -23,8 +23,10 @@ The title for the error message.
|
||||
|
||||
|
||||
def real_main(args: List[str]) -> None:
|
||||
msg = 'Show an error message'
|
||||
cli_opts, items = parse_args(args[1:], OPTIONS, '', msg, 'hints', result_class=ErrorCLIOptions)
|
||||
msg = 'Show an error message. For internal use by kitty.'
|
||||
cli_opts, items = parse_args(args[1:], OPTIONS, '', msg, 'kitty +kitten show_error', result_class=ErrorCLIOptions)
|
||||
if sys.stdin.isatty():
|
||||
raise SystemExit('Input data for this kitten must be piped as JSON to STDIN')
|
||||
data = json.loads(sys.stdin.buffer.read())
|
||||
error_message = data['msg']
|
||||
if cli_opts.title:
|
||||
@@ -33,6 +35,7 @@ def real_main(args: List[str]) -> None:
|
||||
print(error_message, flush=True)
|
||||
if data.get('tb'):
|
||||
import select
|
||||
|
||||
from kittens.tui.operations import init_state, set_cursor_visible
|
||||
fd, original_termios = open_tty()
|
||||
msg = '\n\r\x1b[1;32mPress e to see detailed traceback or any other key to exit\x1b[m'
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from kitty.key_encoding import (
|
||||
ALT, CAPS_LOCK, CTRL, HYPER, META, NUM_LOCK, PRESS, RELEASE, REPEAT, SHIFT,
|
||||
SUPER, KeyEvent, encode_key_event
|
||||
)
|
||||
|
||||
from kittens.tui.handler import Handler
|
||||
from kittens.tui.loop import Loop
|
||||
from kitty.key_encoding import ALT, CAPS_LOCK, CTRL, HYPER, META, NUM_LOCK, PRESS, RELEASE, REPEAT, SHIFT, SUPER, KeyEvent, encode_key_event
|
||||
|
||||
mod_names = {
|
||||
SHIFT: 'Shift',
|
||||
|
||||
@@ -6,9 +6,9 @@ import os
|
||||
import sys
|
||||
from typing import List
|
||||
|
||||
from kittens.tui.operations import raw_mode, styled
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cli_stub import ShowKeyCLIOptions
|
||||
from kittens.tui.operations import raw_mode, styled
|
||||
|
||||
ctrl_keys = '@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_'
|
||||
|
||||
|
||||
@@ -1,321 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from typing import Callable, Dict, Iterable, Iterator, Sequence, Tuple
|
||||
|
||||
from kitty.complete import Completions, complete_files_and_dirs, debug
|
||||
from kitty.types import run_once
|
||||
from .utils import ssh_options
|
||||
|
||||
debug
|
||||
|
||||
|
||||
def lines_from_file(path: str) -> Iterator[str]:
|
||||
try:
|
||||
f = open(os.path.expanduser(path))
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
yield from f
|
||||
|
||||
|
||||
def lines_from_command(*cmd: str) -> Iterator[str]:
|
||||
try:
|
||||
output = subprocess.check_output(cmd).decode('utf-8')
|
||||
except Exception:
|
||||
return
|
||||
yield from output.splitlines()
|
||||
|
||||
|
||||
def parts_yielder(lines: Iterable[str], pfilter: Callable[[str], Iterator[str]]) -> Iterator[str]:
|
||||
for line in lines:
|
||||
yield from pfilter(line)
|
||||
|
||||
|
||||
def hosts_from_config_lines(line: str) -> Iterator[str]:
|
||||
parts = line.strip().split()
|
||||
if len(parts) > 1 and parts[0] == 'Host':
|
||||
yield parts[1]
|
||||
|
||||
|
||||
def hosts_from_known_hosts(line: str) -> Iterator[str]:
|
||||
parts = line.strip().split()
|
||||
if parts:
|
||||
yield re.sub(r':\d+$', '', parts[0])
|
||||
|
||||
|
||||
def hosts_from_hosts(line: str) -> Iterator[str]:
|
||||
line = line.strip()
|
||||
if not line.startswith('#'):
|
||||
parts = line.split()
|
||||
if parts:
|
||||
yield parts[0]
|
||||
if len(parts) > 1:
|
||||
yield parts[1]
|
||||
if len(parts) > 2:
|
||||
yield parts[2]
|
||||
|
||||
|
||||
def iter_known_hosts() -> Iterator[str]:
|
||||
yield from parts_yielder(lines_from_file('~/.ssh/config'), hosts_from_config_lines)
|
||||
yield from parts_yielder(lines_from_file('~/.ssh/known_hosts'), hosts_from_known_hosts)
|
||||
yield from parts_yielder(lines_from_file('/etc/ssh/ssh_known_hosts'), hosts_from_known_hosts)
|
||||
yield from parts_yielder(lines_from_file('/etc/hosts'), hosts_from_hosts)
|
||||
yield from parts_yielder(lines_from_command('getent', 'hosts'), hosts_from_hosts)
|
||||
|
||||
|
||||
@run_once
|
||||
def known_hosts() -> Tuple[str, ...]:
|
||||
return tuple(sorted(filter(lambda x: '*' not in x and '[' not in x, set(iter_known_hosts()))))
|
||||
|
||||
|
||||
# option help {{{
|
||||
@run_once
|
||||
def option_help_map() -> Dict[str, str]:
|
||||
ans: Dict[str, str] = {}
|
||||
lines = '''
|
||||
-4 -- force ssh to use IPv4 addresses only
|
||||
-6 -- force ssh to use IPv6 addresses only
|
||||
-a -- disable forwarding of authentication agent connection
|
||||
-A -- enable forwarding of the authentication agent connection
|
||||
-B -- bind to specified interface before attempting to connect
|
||||
-b -- specify interface to transmit on
|
||||
-C -- compress data
|
||||
-c -- select encryption cipher
|
||||
-D -- specify a dynamic port forwarding
|
||||
-E -- append log output to file instead of stderr
|
||||
-e -- set escape character
|
||||
-f -- go to background
|
||||
-F -- specify alternate config file
|
||||
-g -- allow remote hosts to connect to local forwarded ports
|
||||
-G -- output configuration and exit
|
||||
-i -- select identity file
|
||||
-I -- specify smartcard device
|
||||
-J -- connect via a jump host
|
||||
-k -- disable forwarding of GSSAPI credentials
|
||||
-K -- enable GSSAPI-based authentication and forwarding
|
||||
-L -- specify local port forwarding
|
||||
-l -- specify login name
|
||||
-M -- master mode for connection sharing
|
||||
-m -- specify mac algorithms
|
||||
-N -- don't execute a remote command
|
||||
-n -- redirect stdin from /dev/null
|
||||
-O -- control an active connection multiplexing master process
|
||||
-o -- specify extra options
|
||||
-p -- specify port on remote host
|
||||
-P -- use non privileged port
|
||||
-Q -- query parameters
|
||||
-q -- quiet operation
|
||||
-R -- specify remote port forwarding
|
||||
-s -- invoke subsystem
|
||||
-S -- specify location of control socket for connection sharing
|
||||
-T -- disable pseudo-tty allocation
|
||||
-t -- force pseudo-tty allocation
|
||||
-V -- show version number
|
||||
-v -- verbose mode (multiple increase verbosity, up to 3)
|
||||
-W -- forward standard input and output to host
|
||||
-w -- request tunnel device forwarding
|
||||
-x -- disable X11 forwarding
|
||||
-X -- enable (untrusted) X11 forwarding
|
||||
-Y -- enable trusted X11 forwarding
|
||||
-y -- send log info via syslog instead of stderr
|
||||
'''.splitlines()
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if line:
|
||||
parts = line.split(maxsplit=2)
|
||||
ans[parts[0]] = parts[2]
|
||||
return ans
|
||||
# }}}
|
||||
|
||||
|
||||
# option names {{{
|
||||
@run_once
|
||||
def option_names() -> Tuple[str, ...]:
|
||||
return tuple(filter(None, (
|
||||
line.strip() for line in '''
|
||||
AddKeysToAgent
|
||||
AddressFamily
|
||||
BatchMode
|
||||
BindAddress
|
||||
CanonicalDomains
|
||||
CanonicalizeFallbackLocal
|
||||
CanonicalizeHostname
|
||||
CanonicalizeMaxDots
|
||||
CanonicalizePermittedCNAMEs
|
||||
CASignatureAlgorithms
|
||||
CertificateFile
|
||||
ChallengeResponseAuthentication
|
||||
CheckHostIP
|
||||
Ciphers
|
||||
ClearAllForwardings
|
||||
Compression
|
||||
ConnectionAttempts
|
||||
ConnectTimeout
|
||||
ControlMaster
|
||||
ControlPath
|
||||
ControlPersist
|
||||
DynamicForward
|
||||
EscapeChar
|
||||
ExitOnForwardFailure
|
||||
FingerprintHash
|
||||
ForwardAgent
|
||||
ForwardX11
|
||||
ForwardX11Timeout
|
||||
ForwardX11Trusted
|
||||
GatewayPorts
|
||||
GlobalKnownHostsFile
|
||||
GSSAPIAuthentication
|
||||
GSSAPIDelegateCredentials
|
||||
HashKnownHosts
|
||||
Host
|
||||
HostbasedAcceptedAlgorithms
|
||||
HostbasedAuthentication
|
||||
HostKeyAlgorithms
|
||||
HostKeyAlias
|
||||
Hostname
|
||||
IdentitiesOnly
|
||||
IdentityAgent
|
||||
IdentityFile
|
||||
IPQoS
|
||||
KbdInteractiveAuthentication
|
||||
KbdInteractiveDevices
|
||||
KexAlgorithms
|
||||
KnownHostsCommand
|
||||
LocalCommand
|
||||
LocalForward
|
||||
LogLevel
|
||||
MACs
|
||||
Match
|
||||
NoHostAuthenticationForLocalhost
|
||||
NumberOfPasswordPrompts
|
||||
PasswordAuthentication
|
||||
PermitLocalCommand
|
||||
PermitRemoteOpen
|
||||
PKCS11Provider
|
||||
Port
|
||||
PreferredAuthentications
|
||||
ProxyCommand
|
||||
ProxyJump
|
||||
ProxyUseFdpass
|
||||
PubkeyAcceptedAlgorithms
|
||||
PubkeyAuthentication
|
||||
RekeyLimit
|
||||
RemoteCommand
|
||||
RemoteForward
|
||||
RequestTTY
|
||||
SendEnv
|
||||
ServerAliveInterval
|
||||
ServerAliveCountMax
|
||||
SetEnv
|
||||
StreamLocalBindMask
|
||||
StreamLocalBindUnlink
|
||||
StrictHostKeyChecking
|
||||
TCPKeepAlive
|
||||
Tunnel
|
||||
TunnelDevice
|
||||
UpdateHostKeys
|
||||
User
|
||||
UserKnownHostsFile
|
||||
VerifyHostKeyDNS
|
||||
VisualHostKey
|
||||
XAuthLocation
|
||||
'''.splitlines())))
|
||||
# }}}
|
||||
|
||||
|
||||
def complete_choices(ans: Completions, prefix: str, title: str, choices: Iterable[str], comma_separated: bool = False) -> None:
|
||||
matches: Dict[str, str] = {}
|
||||
word_transforms = {}
|
||||
effective_prefix = prefix
|
||||
hidden_prefix = ''
|
||||
if comma_separated:
|
||||
effective_prefix = prefix.split(',')[-1]
|
||||
hidden_prefix = ','.join(prefix.split(',')[:-1])
|
||||
if hidden_prefix:
|
||||
hidden_prefix += ','
|
||||
for q in choices:
|
||||
if q.startswith(effective_prefix):
|
||||
if comma_separated:
|
||||
tq = q
|
||||
q = f'{hidden_prefix}{q},'
|
||||
word_transforms[q] = tq
|
||||
matches[q] = ''
|
||||
ans.add_match_group(title, matches, trailing_space=not comma_separated, word_transforms=word_transforms)
|
||||
|
||||
|
||||
def complete_q_choices(ans: Completions, prefix: str, title: str, key: str, comma_separated: bool) -> None:
|
||||
choices = (line.strip() for line in lines_from_command('ssh', '-Q', key))
|
||||
complete_choices(ans, prefix, title, choices, comma_separated)
|
||||
|
||||
|
||||
def complete_arg(ans: Completions, option_flag: str, prefix: str = '') -> None:
|
||||
options = ssh_options()
|
||||
option_name = options.get(option_flag[1:])
|
||||
if option_name and (option_name.endswith('file') or option_name.endswith('path')):
|
||||
return complete_files_and_dirs(ans, prefix, option_name)
|
||||
choices = {
|
||||
'mac_spec': ('MAC algorithm', 'mac', True),
|
||||
'cipher_spec': ('encryption cipher', 'cipher', True),
|
||||
'query_option': ('query option', 'help', False),
|
||||
}
|
||||
if option_name in choices:
|
||||
return complete_q_choices(ans, prefix, *choices[option_name])
|
||||
if option_name == 'destination':
|
||||
return complete_destination(ans, prefix)
|
||||
if option_name == 'ctl_cmd':
|
||||
return complete_choices(ans, prefix, 'control command', ('check', 'forward', 'cancel', 'exit'))
|
||||
if option_name == 'option':
|
||||
matches = (x+'=' for x in option_names() if x.startswith(prefix))
|
||||
word_transforms = {x+'=': x for x in option_names()}
|
||||
ans.add_match_group('configure file option', matches, trailing_space=False, word_transforms=word_transforms)
|
||||
|
||||
|
||||
def complete_destination(ans: Completions, prefix: str = '') -> None:
|
||||
result = (k for k in known_hosts() if k.startswith(prefix))
|
||||
ans.add_match_group('remote host name', result)
|
||||
|
||||
|
||||
def complete_option(ans: Completions, prefix: str = '-') -> None:
|
||||
hm = option_help_map()
|
||||
if len(prefix) <= 1:
|
||||
result = {k: v for k, v in hm.items() if k.startswith(prefix)}
|
||||
ans.add_match_group('option', result)
|
||||
else:
|
||||
ans.add_match_group('option', {prefix: ''})
|
||||
|
||||
|
||||
def complete(ans: Completions, words: Sequence[str], new_word: bool) -> None:
|
||||
options = ssh_options()
|
||||
expecting_arg = False
|
||||
seen_destination = False
|
||||
types = ['' for i in range(len(words))]
|
||||
for i, word in enumerate(words):
|
||||
if expecting_arg:
|
||||
types[i] = 'arg'
|
||||
expecting_arg = False
|
||||
continue
|
||||
if word.startswith('-'):
|
||||
types[i] = 'option'
|
||||
if len(word) == 2 and options.get(word[1]):
|
||||
expecting_arg = True
|
||||
continue
|
||||
if seen_destination:
|
||||
break
|
||||
types[i] = 'destination'
|
||||
seen_destination = True
|
||||
if new_word:
|
||||
if words:
|
||||
if expecting_arg:
|
||||
return complete_arg(ans, words[-1])
|
||||
return complete_destination(ans)
|
||||
if words:
|
||||
if types[-1] == 'arg' and len(words) > 1:
|
||||
return complete_arg(ans, words[-2], words[-1])
|
||||
if types[-1] == 'destination':
|
||||
return complete_destination(ans, words[-1])
|
||||
if types[-1] == 'option':
|
||||
return complete_option(ans, words[-1])
|
||||
@@ -6,12 +6,12 @@ import fnmatch
|
||||
import os
|
||||
from typing import Any, Dict, Iterable, Optional
|
||||
|
||||
from kitty.conf.utils import (
|
||||
load_config as _load_config, parse_config_base, resolve_config
|
||||
)
|
||||
from kitty.conf.utils import load_config as _load_config
|
||||
from kitty.conf.utils import parse_config_base, resolve_config
|
||||
from kitty.constants import config_dir
|
||||
|
||||
from .options.types import Options as SSHOptions, defaults
|
||||
from .options.types import Options as SSHOptions
|
||||
from .options.types import defaults
|
||||
|
||||
SYSTEM_CONF = '/etc/xdg/kitty/ssh.conf'
|
||||
defconf = os.path.join(config_dir, 'ssh.conf')
|
||||
@@ -28,12 +28,8 @@ def host_matches(mpat: str, hostname: str, username: str) -> bool:
|
||||
|
||||
|
||||
def load_config(*paths: str, overrides: Optional[Iterable[str]] = None, hostname: str = '!', username: str = '') -> SSHOptions:
|
||||
from .options.parse import (
|
||||
create_result_dict, merge_result_dicts, parse_conf_item
|
||||
)
|
||||
from .options.utils import (
|
||||
first_seen_positions, get_per_hosts_dict, init_results_dict
|
||||
)
|
||||
from .options.parse import create_result_dict, merge_result_dicts, parse_conf_item
|
||||
from .options.utils import first_seen_positions, get_per_hosts_dict, init_results_dict
|
||||
|
||||
def merge_dicts(base: Dict[str, Any], vals: Dict[str, Any]) -> Dict[str, Any]:
|
||||
base_phd = get_per_hosts_dict(base)
|
||||
|
||||
@@ -6,9 +6,7 @@ import glob
|
||||
import os
|
||||
import shlex
|
||||
import uuid
|
||||
from typing import (
|
||||
Dict, Iterable, Iterator, List, NamedTuple, Optional, Sequence, Tuple
|
||||
)
|
||||
from typing import Dict, Iterable, Iterator, List, NamedTuple, Optional, Sequence, Tuple
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cli_stub import CopyCLIOptions
|
||||
|
||||
@@ -22,27 +22,16 @@ from base64 import standard_b64decode, standard_b64encode
|
||||
from contextlib import contextmanager, suppress
|
||||
from getpass import getuser
|
||||
from select import select
|
||||
from typing import (
|
||||
Any, Callable, Dict, Iterator, List, NoReturn, Optional, Sequence, Set,
|
||||
Tuple, Union, cast
|
||||
)
|
||||
from typing import Any, Callable, Dict, Iterator, List, NoReturn, Optional, Sequence, Set, Tuple, Union, cast
|
||||
|
||||
from kitty.constants import (
|
||||
cache_dir, runtime_dir, shell_integration_dir, ssh_control_master_template,
|
||||
str_version, terminfo_dir
|
||||
)
|
||||
from kitty.constants import cache_dir, runtime_dir, shell_integration_dir, ssh_control_master_template, str_version, terminfo_dir
|
||||
from kitty.shell_integration import as_str_literal
|
||||
from kitty.shm import SharedMemory
|
||||
from kitty.types import run_once
|
||||
from kitty.utils import (
|
||||
SSHConnectionData, expandvars, resolve_abs_or_config_path,
|
||||
set_echo as turn_off_echo
|
||||
)
|
||||
from kitty.utils import SSHConnectionData, expandvars, resolve_abs_or_config_path
|
||||
from kitty.utils import set_echo as turn_off_echo
|
||||
|
||||
from ..tui.operations import (
|
||||
RESTORE_PRIVATE_MODE_VALUES, SAVE_PRIVATE_MODE_VALUES, Mode,
|
||||
restore_colors, save_colors, set_mode
|
||||
)
|
||||
from ..tui.operations import RESTORE_PRIVATE_MODE_VALUES, SAVE_PRIVATE_MODE_VALUES, Mode, restore_colors, save_colors, set_mode
|
||||
from ..tui.utils import kitty_opts, running_in_tmux
|
||||
from .config import init_config
|
||||
from .copy import CopyInstruction
|
||||
@@ -62,7 +51,7 @@ def read_data_from_shared_memory(shm_name: str) -> Any:
|
||||
if shm.stats.st_uid != os.geteuid() or shm.stats.st_gid != os.getegid():
|
||||
raise ValueError('Incorrect owner on pwfile')
|
||||
mode = stat.S_IMODE(shm.stats.st_mode)
|
||||
if mode != stat.S_IREAD:
|
||||
if mode != stat.S_IREAD | stat.S_IWRITE:
|
||||
raise ValueError('Incorrect permissions on pwfile')
|
||||
return json.loads(shm.read_data_with_size())
|
||||
|
||||
@@ -193,6 +182,7 @@ def make_tarfile(ssh_opts: SSHOptions, base_env: Dict[str, str], compression: st
|
||||
arcname = 'home/' + rd + '/kitty'
|
||||
add_data_as_file(tf, arcname + '/version', str_version.encode('ascii'))
|
||||
tf.add(shell_integration_dir + '/ssh/kitty', arcname=arcname + '/bin/kitty', filter=normalize_tarinfo)
|
||||
tf.add(shell_integration_dir + '/ssh/kitten', arcname=arcname + '/bin/kitten', filter=normalize_tarinfo)
|
||||
tf.add(f'{terminfo_dir}/kitty.terminfo', arcname='home/.terminfo/kitty.terminfo', filter=normalize_tarinfo)
|
||||
tf.add(glob.glob(f'{terminfo_dir}/*/xterm-kitty')[0], arcname='home/.terminfo/x/xterm-kitty', filter=normalize_tarinfo)
|
||||
return buf.getvalue()
|
||||
@@ -623,9 +613,7 @@ def drain_potential_tty_garbage(p: 'subprocess.Popen[bytes]', data_request: str)
|
||||
def change_colors(color_scheme: str) -> bool:
|
||||
if not color_scheme:
|
||||
return False
|
||||
from kittens.themes.collection import (
|
||||
NoCacheFound, load_themes, text_as_opts
|
||||
)
|
||||
from kittens.themes.collection import NoCacheFound, load_themes, text_as_opts
|
||||
from kittens.themes.main import colors_as_escape_codes
|
||||
if color_scheme.endswith('.conf'):
|
||||
conf_file = resolve_abs_or_config_path(color_scheme)
|
||||
@@ -754,9 +742,9 @@ def main(args: List[str]) -> None:
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
||||
elif __name__ == '__completer__':
|
||||
from .completion import complete
|
||||
setattr(sys, 'kitten_completer', complete)
|
||||
elif __name__ == '__wrapper_of__':
|
||||
cd = sys.cli_docs # type: ignore
|
||||
cd['wrapper_of'] = 'ssh'
|
||||
elif __name__ == '__conf__':
|
||||
from .options.definition import definition
|
||||
sys.options_definition = definition # type: ignore
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
from kitty.conf.types import Definition
|
||||
|
||||
|
||||
copy_message = '''\
|
||||
Copy files and directories from local to remote hosts. The specified files are
|
||||
assumed to be relative to the HOME directory and copied to the HOME on the
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# generated by gen-config.py DO NOT edit
|
||||
|
||||
# isort: skip_file
|
||||
import typing
|
||||
from kittens.ssh.options.utils import copy, env, hostname
|
||||
from kitty.conf.utils import merge_dicts, to_bool
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# generated by gen-config.py DO NOT edit
|
||||
|
||||
# isort: skip_file
|
||||
import typing
|
||||
import kittens.ssh.copy
|
||||
|
||||
|
||||
@@ -79,11 +79,10 @@ def set_cwd_in_cmdline(cwd: str, argv: List[str]) -> None:
|
||||
def create_shared_memory(data: Any, prefix: str) -> str:
|
||||
import atexit
|
||||
import json
|
||||
import stat
|
||||
|
||||
from kitty.shm import SharedMemory
|
||||
db = json.dumps(data).encode('utf-8')
|
||||
with SharedMemory(size=len(db) + SharedMemory.num_bytes_for_size, mode=stat.S_IREAD, prefix=prefix) as shm:
|
||||
with SharedMemory(size=len(db) + SharedMemory.num_bytes_for_size, prefix=prefix) as shm:
|
||||
shm.write_data_with_size(db)
|
||||
shm.flush()
|
||||
atexit.register(shm.unlink)
|
||||
|
||||
@@ -8,17 +8,18 @@ import os
|
||||
import re
|
||||
import shutil
|
||||
import signal
|
||||
import sys
|
||||
import tempfile
|
||||
import zipfile
|
||||
from contextlib import suppress
|
||||
from typing import Any, Callable, Dict, Iterator, Match, Optional, Tuple, Union, Type
|
||||
from typing import Any, Callable, Dict, Iterator, Match, Optional, Tuple, Type, Union
|
||||
from urllib.error import HTTPError
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
from kitty.config import atomic_save, parse_config
|
||||
from kitty.constants import cache_dir, config_dir
|
||||
from kitty.options.types import Options as KittyOptions
|
||||
from kitty.fast_data_types import Color
|
||||
from kitty.options.types import Options as KittyOptions
|
||||
from kitty.utils import reload_conf_in_all_kitties
|
||||
|
||||
from ..choose.match import match
|
||||
@@ -647,3 +648,13 @@ def load_themes(cache_age: float = 1., ignore_no_cache: bool = False) -> Themes:
|
||||
ans.load_from_dir(os.path.join(config_dir, 'themes'))
|
||||
ans.index_map = tuple(ans.themes)
|
||||
return ans
|
||||
|
||||
|
||||
def print_theme_names() -> None:
|
||||
found = False
|
||||
for theme in load_themes(cache_age=-1, ignore_no_cache=True):
|
||||
print(theme.name)
|
||||
found = True
|
||||
if not found:
|
||||
print('Default')
|
||||
sys.stdout.flush()
|
||||
|
||||
@@ -7,16 +7,14 @@ import sys
|
||||
import traceback
|
||||
from enum import Enum, auto
|
||||
from gettext import gettext as _
|
||||
from typing import (
|
||||
Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple, Union
|
||||
)
|
||||
from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple, Union
|
||||
|
||||
from kitty.cli import create_default_opts, parse_args
|
||||
from kitty.cli import CompletionSpec, create_default_opts, parse_args
|
||||
from kitty.cli_stub import ThemesCLIOptions
|
||||
from kitty.config import cached_values_for
|
||||
from kitty.options.types import Options as KittyOptions
|
||||
from kitty.constants import config_dir
|
||||
from kitty.fast_data_types import truncate_point_for_length, wcswidth
|
||||
from kitty.options.types import Options as KittyOptions
|
||||
from kitty.rgb import color_as_sharp, color_from_int
|
||||
from kitty.typing import KeyEventType
|
||||
from kitty.utils import ScreenSize
|
||||
@@ -180,7 +178,7 @@ class ThemesHandler(Handler):
|
||||
self.cmd.save_colors()
|
||||
self.cmd.set_line_wrapping(False)
|
||||
self.cmd.set_window_title('Choose a theme for kitty')
|
||||
self.cmd.set_cursor_shape('bar')
|
||||
self.cmd.set_cursor_shape('beam')
|
||||
|
||||
def initialize(self) -> None:
|
||||
self.init_terminal_state()
|
||||
@@ -235,8 +233,11 @@ class ThemesHandler(Handler):
|
||||
self.redraw_after_category_change()
|
||||
|
||||
def fetch() -> None:
|
||||
from urllib.error import URLError
|
||||
try:
|
||||
themes: Union[Themes, str] = load_themes(self.cli_opts.cache_age)
|
||||
except URLError as e:
|
||||
themes = f'Could not download themes, check your internet connection. Error: {e}'
|
||||
except Exception:
|
||||
themes = format_traceback('Failed to download themes')
|
||||
self.asyncio_loop.call_soon_threadsafe(fetching_done, themes)
|
||||
@@ -553,7 +554,9 @@ instead of changing kitty.conf.
|
||||
default=kitty.conf
|
||||
The name or path to the config file to edit. Relative paths are interpreted
|
||||
with respect to the kitty config directory. By default the kitty config file,
|
||||
kitty.conf is edited.
|
||||
kitty.conf is edited. This is most useful if you add :code:`include themes.conf`
|
||||
to your kitty.conf and then have the kitten operate only on :file:`themes.conf`,
|
||||
allowing :code:`kitty.conf` to remain unchanged.
|
||||
'''.format
|
||||
|
||||
|
||||
@@ -616,3 +619,4 @@ elif __name__ == '__doc__':
|
||||
cd['usage'] = usage
|
||||
cd['options'] = OPTIONS
|
||||
cd['help_text'] = help_text
|
||||
cd['args_completion'] = CompletionSpec.from_string('type:special group:complete_themes')
|
||||
|
||||
@@ -5,10 +5,7 @@ import os
|
||||
import tempfile
|
||||
from typing import IO, TYPE_CHECKING, Iterator, Union
|
||||
|
||||
from .rsync import (
|
||||
IO_BUFFER_SIZE, RsyncError, begin_create_delta, begin_create_signature,
|
||||
begin_load_signature, begin_patch, build_hash_table, iter_job
|
||||
)
|
||||
from .rsync import IO_BUFFER_SIZE, RsyncError, begin_create_delta, begin_create_signature, begin_load_signature, begin_patch, build_hash_table, iter_job
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .rsync import JobCapsule, SignatureCapsule
|
||||
|
||||
@@ -12,7 +12,6 @@ from kitty.cli_stub import TransferCLIOptions
|
||||
from .receive import receive_main
|
||||
from .send import send_main
|
||||
|
||||
|
||||
usage = 'source_files_or_directories destination_path'
|
||||
help_text = 'Transfer files over the TTY device'
|
||||
|
||||
|
||||
@@ -14,9 +14,16 @@ from typing import IO, Deque, Dict, Iterator, List, Optional, Union
|
||||
from kitty.cli_stub import TransferCLIOptions
|
||||
from kitty.fast_data_types import FILE_TRANSFER_CODE, wcswidth
|
||||
from kitty.file_transmission import (
|
||||
Action, Compression, FileTransmissionCommand, FileType,
|
||||
IdentityDecompressor, NameReprEnum, TransmissionType, ZlibDecompressor,
|
||||
encode_bypass, split_for_transfer
|
||||
Action,
|
||||
Compression,
|
||||
FileTransmissionCommand,
|
||||
FileType,
|
||||
IdentityDecompressor,
|
||||
NameReprEnum,
|
||||
TransmissionType,
|
||||
ZlibDecompressor,
|
||||
encode_bypass,
|
||||
split_for_transfer,
|
||||
)
|
||||
from kitty.typing import KeyEventType, ScreenSize
|
||||
from kitty.utils import sanitize_control_codes
|
||||
@@ -28,10 +35,7 @@ from ..tui.spinners import Spinner
|
||||
from ..tui.utils import human_size
|
||||
from .librsync import PatchFile, signature_of_file
|
||||
from .send import Transfer
|
||||
from .utils import (
|
||||
expand_home, print_rsync_stats, random_id, render_progress_in_width,
|
||||
safe_divide, should_be_compressed
|
||||
)
|
||||
from .utils import expand_home, print_rsync_stats, random_id, render_progress_in_width, safe_divide, should_be_compressed
|
||||
|
||||
debug
|
||||
file_counter = count(1)
|
||||
|
||||
@@ -9,17 +9,11 @@ from collections import deque
|
||||
from enum import auto
|
||||
from itertools import count
|
||||
from time import monotonic
|
||||
from typing import (
|
||||
IO, Callable, Deque, Dict, Iterable, Iterator, List, Optional, Sequence,
|
||||
Set, Tuple, Union
|
||||
)
|
||||
from typing import IO, Callable, Deque, Dict, Iterable, Iterator, List, Optional, Sequence, Set, Tuple, Union
|
||||
|
||||
from kitty.cli_stub import TransferCLIOptions
|
||||
from kitty.fast_data_types import FILE_TRANSFER_CODE, wcswidth
|
||||
from kitty.file_transmission import (
|
||||
Action, Compression, FileTransmissionCommand, FileType, NameReprEnum,
|
||||
TransmissionType, encode_bypass, split_for_transfer
|
||||
)
|
||||
from kitty.file_transmission import Action, Compression, FileTransmissionCommand, FileType, NameReprEnum, TransmissionType, encode_bypass, split_for_transfer
|
||||
from kitty.typing import KeyEventType, ScreenSize
|
||||
from kitty.utils import sanitize_control_codes
|
||||
|
||||
@@ -30,9 +24,16 @@ from ..tui.spinners import Spinner
|
||||
from ..tui.utils import human_size
|
||||
from .librsync import LoadSignature, delta_for_file
|
||||
from .utils import (
|
||||
IdentityCompressor, ZlibCompressor, abspath, expand_home, home_path,
|
||||
print_rsync_stats, random_id, render_progress_in_width, safe_divide,
|
||||
should_be_compressed
|
||||
IdentityCompressor,
|
||||
ZlibCompressor,
|
||||
abspath,
|
||||
expand_home,
|
||||
home_path,
|
||||
print_rsync_stats,
|
||||
random_id,
|
||||
render_progress_in_width,
|
||||
safe_divide,
|
||||
should_be_compressed,
|
||||
)
|
||||
|
||||
debug
|
||||
|
||||
@@ -6,16 +6,21 @@ from collections import deque
|
||||
from contextlib import suppress
|
||||
from time import monotonic
|
||||
from types import TracebackType
|
||||
from typing import (
|
||||
TYPE_CHECKING, Any, Callable, ContextManager, Deque, Dict, NamedTuple,
|
||||
Optional, Sequence, Type, Union, cast
|
||||
)
|
||||
from typing import TYPE_CHECKING, Any, Callable, ContextManager, Deque, Dict, NamedTuple, Optional, Sequence, Type, Union, cast
|
||||
|
||||
from kitty.types import DecoratedFunc, ParsedShortcut
|
||||
from kitty.typing import (
|
||||
AbstractEventLoop, BossType, Debug, ImageManagerType, KeyActionType,
|
||||
KeyEventType, LoopType, MouseButton, MouseEvent, ScreenSize,
|
||||
TermManagerType
|
||||
AbstractEventLoop,
|
||||
BossType,
|
||||
Debug,
|
||||
ImageManagerType,
|
||||
KeyActionType,
|
||||
KeyEventType,
|
||||
LoopType,
|
||||
MouseButton,
|
||||
MouseEvent,
|
||||
ScreenSize,
|
||||
TermManagerType,
|
||||
)
|
||||
|
||||
from .operations import MouseTracking, pending_update
|
||||
|
||||
@@ -9,17 +9,11 @@ from collections import defaultdict, deque
|
||||
from contextlib import suppress
|
||||
from enum import IntEnum
|
||||
from itertools import count
|
||||
from typing import (
|
||||
Any, Callable, ClassVar, DefaultDict, Deque, Dict, Generic, Iterator, List,
|
||||
Optional, Sequence, Tuple, Type, TypeVar, Union, cast
|
||||
)
|
||||
from typing import Any, Callable, ClassVar, DefaultDict, Deque, Dict, Generic, Iterator, List, Optional, Sequence, Tuple, Type, TypeVar, Union, cast
|
||||
|
||||
from kitty.conf.utils import positive_float, positive_int
|
||||
from kitty.fast_data_types import create_canvas
|
||||
from kitty.typing import (
|
||||
GRT_C, CompletedProcess, GRT_a, GRT_d, GRT_f, GRT_m, GRT_o, GRT_t,
|
||||
HandlerType
|
||||
)
|
||||
from kitty.typing import GRT_C, CompletedProcess, GRT_a, GRT_d, GRT_f, GRT_m, GRT_o, GRT_t, HandlerType
|
||||
from kitty.utils import ScreenSize, fit_image, which
|
||||
|
||||
from .operations import cursor
|
||||
|
||||
@@ -6,9 +6,7 @@ from typing import Callable, Tuple
|
||||
from kitty.fast_data_types import truncate_point_for_length, wcswidth
|
||||
from kitty.key_encoding import EventType, KeyEvent
|
||||
|
||||
from .operations import (
|
||||
RESTORE_CURSOR, SAVE_CURSOR, move_cursor_by, set_cursor_shape
|
||||
)
|
||||
from .operations import RESTORE_CURSOR, SAVE_CURSOR, move_cursor_by, set_cursor_shape
|
||||
|
||||
|
||||
class LineEdit:
|
||||
@@ -53,7 +51,7 @@ class LineEdit:
|
||||
write('\r')
|
||||
if cursor_pos:
|
||||
write(move_cursor_by(cursor_pos, 'right'))
|
||||
write(set_cursor_shape('bar'))
|
||||
write(set_cursor_shape('beam'))
|
||||
|
||||
def add_text(self, text: str) -> None:
|
||||
if self.current_input:
|
||||
|
||||
@@ -16,17 +16,10 @@ from functools import partial
|
||||
from typing import Any, Callable, Dict, Generator, List, NamedTuple, Optional
|
||||
|
||||
from kitty.constants import is_macos
|
||||
from kitty.fast_data_types import (
|
||||
FILE_TRANSFER_CODE, close_tty, normal_tty, open_tty,
|
||||
parse_input_from_terminal, raw_tty
|
||||
)
|
||||
from kitty.key_encoding import (
|
||||
ALT, CTRL, SHIFT, backspace_key, decode_key_event, enter_key
|
||||
)
|
||||
from kitty.fast_data_types import FILE_TRANSFER_CODE, close_tty, normal_tty, open_tty, parse_input_from_terminal, raw_tty
|
||||
from kitty.key_encoding import ALT, CTRL, SHIFT, backspace_key, decode_key_event, enter_key
|
||||
from kitty.typing import ImageManagerType, KeyEventType, Protocol
|
||||
from kitty.utils import (
|
||||
ScreenSize, ScreenSizeGetter, screen_size_function, write_all
|
||||
)
|
||||
from kitty.utils import ScreenSize, ScreenSizeGetter, screen_size_function, write_all
|
||||
|
||||
from .handler import Handler
|
||||
from .operations import MouseTracking, init_state, reset_state
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
from enum import Enum, auto
|
||||
from functools import wraps
|
||||
from typing import IO, Any, Callable, Dict, Generator, Optional, TypeVar, Union
|
||||
from typing import Any, Callable, Dict, Generator, Optional, TypeVar, Union
|
||||
|
||||
from kitty.fast_data_types import Color
|
||||
from kitty.rgb import color_as_sharp, to_color
|
||||
from kitty.typing import (
|
||||
GraphicsCommandType, HandlerType, ScreenSize, UnderlineLiteral
|
||||
)
|
||||
from kitty.typing import GraphicsCommandType, HandlerType, ScreenSize, UnderlineLiteral
|
||||
|
||||
from .operations_stub import CMD
|
||||
|
||||
@@ -141,7 +140,7 @@ def move_cursor_by(amt: int, direction: str) -> str:
|
||||
|
||||
@cmd
|
||||
def set_cursor_shape(shape: str = 'block', blink: bool = True) -> str:
|
||||
val = {'block': 1, 'underline': 3, 'bar': 5}.get(shape, 1)
|
||||
val = {'block': 1, 'underline': 3, 'beam': 5}.get(shape, 1)
|
||||
if not blink:
|
||||
val += 1
|
||||
return f'\033[{val} q'
|
||||
@@ -350,13 +349,13 @@ def cursor(write: Callable[[str], None]) -> Generator[None, None, None]:
|
||||
|
||||
|
||||
@contextmanager
|
||||
def alternate_screen(f: Optional[IO[str]] = None) -> Generator[None, None, None]:
|
||||
f = f or sys.stdout
|
||||
print(set_mode(Mode.ALTERNATE_SCREEN), end='', file=f)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
print(reset_mode(Mode.ALTERNATE_SCREEN), end='', file=f)
|
||||
def alternate_screen() -> Generator[None, None, None]:
|
||||
with open(os.ctermid(), 'w') as f:
|
||||
print(set_mode(Mode.ALTERNATE_SCREEN), end='', file=f, flush=True)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
print(reset_mode(Mode.ALTERNATE_SCREEN), end='', file=f, flush=True)
|
||||
|
||||
|
||||
@contextmanager
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
from .operations import styled, repeat
|
||||
from .operations import repeat, styled
|
||||
|
||||
|
||||
def render_progress_bar(frac: float, width: int = 80) -> str:
|
||||
|
||||
@@ -7,10 +7,7 @@ import sys
|
||||
from contextlib import suppress
|
||||
from functools import lru_cache
|
||||
from gettext import gettext as _
|
||||
from typing import (
|
||||
Any, Dict, FrozenSet, Generator, Iterable, List, Optional, Sequence, Tuple,
|
||||
Union
|
||||
)
|
||||
from typing import Any, Dict, FrozenSet, Generator, Iterable, List, Optional, Sequence, Tuple, Union
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cli_stub import UnicodeCLIOptions
|
||||
@@ -24,10 +21,7 @@ from kitty.utils import ScreenSize, get_editor
|
||||
from ..tui.handler import Handler, result_handler
|
||||
from ..tui.line_edit import LineEdit
|
||||
from ..tui.loop import Loop
|
||||
from ..tui.operations import (
|
||||
clear_screen, colored, cursor, faint, set_line_wrapping, set_window_title,
|
||||
sgr, styled
|
||||
)
|
||||
from ..tui.operations import clear_screen, colored, cursor, faint, set_line_wrapping, set_window_title, sgr, styled
|
||||
from ..tui.utils import report_unhandled_error
|
||||
|
||||
HEX, NAME, EMOTICONS, FAVORITES = 'HEX', 'NAME', 'EMOTICONS', 'FAVORITES'
|
||||
@@ -418,7 +412,7 @@ class UnicodeInput(Handler):
|
||||
if self.mode is HEX:
|
||||
writeln(faint(_('Type {} followed by the index for the recent entries below').format(INDEX_CHAR)))
|
||||
elif self.mode is NAME:
|
||||
writeln(faint(_('Use Tab or the arrow keys to choose a character from below')))
|
||||
writeln(faint(_('Use Tab or arrow keys to choose a character. Type space and {} to select by index').format(INDEX_CHAR)))
|
||||
elif self.mode is FAVORITES:
|
||||
writeln(faint(_('Press F12 to edit the list of favorites')))
|
||||
self.table_at = y
|
||||
|
||||
5
kittens/unicode_input/names.h
generated
5
kittens/unicode_input/names.h
generated
@@ -1,5 +1,6 @@
|
||||
// unicode data, built from the unicode standard on: 2022-09-29
|
||||
// see gen-wcwidth.py
|
||||
// Unicode data, built from the Unicode Standard 15.0.0
|
||||
// Code generated by gen-wcwidth.py, DO NOT EDIT.
|
||||
|
||||
#pragma once
|
||||
#include "data-types.h"
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from typing import Tuple, FrozenSet, Optional
|
||||
|
||||
from typing import FrozenSet, Optional, Tuple
|
||||
|
||||
def all_words() -> Tuple[str, ...]:
|
||||
pass
|
||||
|
||||
136
kitty/bash.py
Normal file
136
kitty/bash.py
Normal file
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env python
|
||||
# License: GPLv3 Copyright: 2022, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import string
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
ansi_c_escapes = {
|
||||
'a': '\a',
|
||||
'b': '\b',
|
||||
'e': '\x1b',
|
||||
'E': '\x1b',
|
||||
'f': '\f',
|
||||
'n': '\n',
|
||||
'r': '\r',
|
||||
't': '\t',
|
||||
'v': '\v',
|
||||
'\\': '\\',
|
||||
"'": "'",
|
||||
'"': '"',
|
||||
'?': '?',
|
||||
}
|
||||
|
||||
|
||||
def ctrl_mask_char(ch: str) -> str:
|
||||
try:
|
||||
o = ord(ch)
|
||||
except Exception:
|
||||
return ch
|
||||
return chr(o & 0b0011111)
|
||||
|
||||
|
||||
def read_digit(text: str, pos: int, max_len: int, valid_digits: str, base: int) -> Tuple[str, int]:
|
||||
epos = pos
|
||||
while (epos - pos) <= max_len and epos < len(text) and text[epos] in valid_digits:
|
||||
epos += 1
|
||||
raw = text[pos:epos]
|
||||
try:
|
||||
return chr(int(raw, base)), epos
|
||||
except Exception:
|
||||
return raw, epos
|
||||
|
||||
|
||||
def read_hex_digit(text: str, pos: int, max_len: int) -> Tuple[str, int]:
|
||||
return read_digit(text, pos, max_len, string.digits + 'abcdefABCDEF', 16)
|
||||
|
||||
|
||||
def read_octal_digit(text: str, pos: int) -> Tuple[str, int]:
|
||||
return read_digit(text, pos, 3, '01234567', 8)
|
||||
|
||||
|
||||
def decode_ansi_c_quoted_string(text: str, pos: int) -> Tuple[str, int]:
|
||||
buf: List[str] = []
|
||||
a = buf.append
|
||||
while pos < len(text):
|
||||
ch = text[pos]
|
||||
pos += 1
|
||||
if ch == '\\':
|
||||
ec = text[pos]
|
||||
pos += 1
|
||||
ev = ansi_c_escapes.get(ec)
|
||||
if ev is None:
|
||||
if ec == 'c' and pos + 1 < len(text):
|
||||
a(ctrl_mask_char(text[pos]))
|
||||
pos += 1
|
||||
elif ec in 'xuU' and pos + 1 < len(text):
|
||||
hd, pos = read_hex_digit(text, pos, {'x': 2, 'u': 4, 'U': 8}[ec])
|
||||
a(hd)
|
||||
elif ec.isdigit():
|
||||
hd, pos = read_octal_digit(text, pos-1)
|
||||
a(hd)
|
||||
else:
|
||||
a(ec)
|
||||
else:
|
||||
a(ev)
|
||||
elif ch == "'":
|
||||
break
|
||||
else:
|
||||
a(ch)
|
||||
return ''.join(buf), pos
|
||||
|
||||
|
||||
def decode_double_quoted_string(text: str, pos: int) -> Tuple[str, int]:
|
||||
escapes = r'"\$`'
|
||||
buf: List[str] = []
|
||||
a = buf.append
|
||||
while pos < len(text):
|
||||
ch = text[pos]
|
||||
pos += 1
|
||||
if ch == '\\':
|
||||
if text[pos] in escapes:
|
||||
a(text[pos])
|
||||
pos += 1
|
||||
continue
|
||||
a(ch)
|
||||
elif ch == '"':
|
||||
break
|
||||
else:
|
||||
a(ch)
|
||||
return ''.join(buf), pos
|
||||
|
||||
|
||||
def parse_modern_bash_env(text: str) -> Dict[str, str]:
|
||||
ans = {}
|
||||
for line in text.splitlines():
|
||||
idx = line.find('=')
|
||||
if idx < 0:
|
||||
break
|
||||
key = line[:idx].rpartition(' ')[2]
|
||||
val = line[idx+1:]
|
||||
if val.startswith('"'):
|
||||
val = decode_double_quoted_string(val, 1)[0]
|
||||
else:
|
||||
val = decode_ansi_c_quoted_string(val, 2)[0]
|
||||
ans[key] = val
|
||||
return ans
|
||||
|
||||
|
||||
def parse_bash_env(text: str, bash_version: str) -> Dict[str, str]:
|
||||
# See https://www.gnu.org/software/bash/manual/html_node/Double-Quotes.html
|
||||
parts = bash_version.split('.')
|
||||
bv = tuple(map(int, parts[:2]))
|
||||
if bv >= (5, 2):
|
||||
return parse_modern_bash_env(text)
|
||||
ans = {}
|
||||
pos = 0
|
||||
while pos < len(text):
|
||||
idx = text.find('="', pos)
|
||||
if idx < 0:
|
||||
break
|
||||
i = text.rfind(' ', 0, idx)
|
||||
if i < 0:
|
||||
break
|
||||
key = text[i+1:idx]
|
||||
pos = idx + 2
|
||||
ans[key], pos = decode_double_quoted_string(text, pos)
|
||||
return ans
|
||||
@@ -1,11 +1,6 @@
|
||||
#version GLSL_VERSION
|
||||
uniform uvec2 viewport;
|
||||
uniform vec3 default_bg;
|
||||
uniform vec3 active_border_color;
|
||||
uniform vec3 inactive_border_color;
|
||||
uniform vec3 bell_border_color;
|
||||
uniform vec3 tab_bar_bg;
|
||||
uniform vec3 tab_bar_margin_color;
|
||||
uniform uint colors[9];
|
||||
uniform float background_opacity;
|
||||
uniform float tint_opacity, tint_premult;
|
||||
in vec4 rect; // left, top, right, bottom
|
||||
@@ -30,16 +25,24 @@ float to_color(uint c) {
|
||||
return float(c & FF) / 255.0;
|
||||
}
|
||||
|
||||
#define W(bit_number, which_color) ((float(((1 << bit_number) & rc) >> bit_number)) * which_color)
|
||||
float is_integer_value(uint c, float x) {
|
||||
return 1. - step(0.5, abs(float(c) - x));
|
||||
}
|
||||
|
||||
vec3 as_color_vector(uint c, int shift) {
|
||||
return vec3(to_color(c >> shift), to_color(c >> (shift - 8)), to_color(c >> (shift - 16)));
|
||||
}
|
||||
|
||||
void main() {
|
||||
uvec2 pos = pos_map[gl_VertexID];
|
||||
gl_Position = vec4(rect[pos.x], rect[pos.y], 0, 1);
|
||||
int rc = int(rect_color);
|
||||
vec3 window_bg = vec3(to_color(rect_color >> 24), to_color(rect_color >> 16), to_color(rect_color >> 8));
|
||||
vec3 color3 = W(0, default_bg) + W(1, active_border_color) + W(2, inactive_border_color) + W(3, window_bg) + W(4, bell_border_color) + W(5, tab_bar_bg) + W(6, tab_bar_margin_color);
|
||||
float is_default_bg = float(rc & 1);
|
||||
float final_opacity = is_default_bg * tint_opacity + (1 - is_default_bg) * background_opacity;
|
||||
float final_premult_opacity = is_default_bg * tint_premult + (1 - is_default_bg) * background_opacity;
|
||||
vec3 window_bg = as_color_vector(rect_color, 24);
|
||||
uint rc = rect_color & FF;
|
||||
vec3 color3 = as_color_vector(colors[rc], 16);
|
||||
float is_window_bg = is_integer_value(rc, 3.);
|
||||
float is_default_bg = is_integer_value(rc, 0.);
|
||||
color3 = is_window_bg * window_bg + (1. - is_window_bg) * color3;
|
||||
float final_opacity = is_default_bg * tint_opacity + (1. - is_default_bg) * background_opacity;
|
||||
float final_premult_opacity = is_default_bg * tint_premult + (1. - is_default_bg) * background_opacity;
|
||||
color = vec4(color3 * final_premult_opacity, final_opacity);
|
||||
}
|
||||
|
||||
@@ -2,20 +2,17 @@
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from enum import IntFlag
|
||||
from typing import Iterable, Sequence, NamedTuple
|
||||
from typing import Iterable, NamedTuple, Sequence
|
||||
|
||||
from .fast_data_types import (
|
||||
BORDERS_PROGRAM, add_borders_rect, compile_program, get_options,
|
||||
init_borders_program, os_window_has_background_image
|
||||
)
|
||||
from .fast_data_types import BORDERS_PROGRAM, add_borders_rect, compile_program, get_options, init_borders_program, os_window_has_background_image
|
||||
from .typing import LayoutType
|
||||
from .utils import load_shaders
|
||||
from .window_list import WindowGroup, WindowList
|
||||
|
||||
|
||||
class BorderColor(IntFlag):
|
||||
# See the border vertex shader for how these flags become actual colors
|
||||
default_bg, active, inactive, window_bg, bell, tab_bar_bg, tab_bar_margin_color = ((1 << i) for i in range(7))
|
||||
# These are indices into the array of colors in the border vertex shader
|
||||
default_bg, active, inactive, window_bg, bell, tab_bar_bg, tab_bar_margin_color, tab_bar_left_edge_color, tab_bar_right_edge_color = range(9)
|
||||
|
||||
|
||||
class Border(NamedTuple):
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user