mirror of
https://github.com/tmux/tmux.git
synced 2026-09-17 10:34:46 +00:00
Compare commits
652 Commits
3.7
...
floating_p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a91d5601e | ||
|
|
63a8af0e15 | ||
|
|
6b9b6c0b0b | ||
|
|
390805d8b9 | ||
|
|
70f37ef815 | ||
|
|
b6fca55447 | ||
|
|
e22c56f02f | ||
|
|
01c86e64b7 | ||
|
|
ca81c36383 | ||
|
|
a9a04c7bc0 | ||
|
|
0a81f4d742 | ||
|
|
0ad5035457 | ||
|
|
d626cef6de | ||
|
|
7713cbebd2 | ||
|
|
baf16f0419 | ||
|
|
9dcc2da348 | ||
|
|
7de5eb1254 | ||
|
|
f50a38de23 | ||
|
|
3ef51a469c | ||
|
|
d5688bf165 | ||
|
|
8dc9e24c74 | ||
|
|
735bbf9da7 | ||
|
|
6ad86ebdf1 | ||
|
|
3d58e04c93 | ||
|
|
9fb67a2cdd | ||
|
|
d9d12c5c43 | ||
|
|
728ad24d4a | ||
|
|
5b37471db7 | ||
|
|
c41e98be3c | ||
|
|
35f429cb6d | ||
|
|
85fc0ea57c | ||
|
|
6f588402c3 | ||
|
|
fada35478b | ||
|
|
bd5d7a8a7a | ||
|
|
8108356978 | ||
|
|
5e32eddb8c | ||
|
|
1818b6ac16 | ||
|
|
e802909de0 | ||
|
|
824ef37f5f | ||
|
|
565db46a19 | ||
|
|
87aa44f4bc | ||
|
|
e9396e59d9 | ||
|
|
002ac977f8 | ||
|
|
724f85d275 | ||
|
|
b3fa1c0b9c | ||
|
|
e69950a9ca | ||
|
|
41b4743eba | ||
|
|
1e9a16a748 | ||
|
|
74a5069c0a | ||
|
|
8d470017bc | ||
|
|
928c7a9771 | ||
|
|
eda7e563d0 | ||
|
|
90fc51542f | ||
|
|
f8674cc993 | ||
|
|
843adbf454 | ||
|
|
abefc3f705 | ||
|
|
dddd7b44c8 | ||
|
|
25c874c489 | ||
|
|
0e418b62d2 | ||
|
|
a97f643bd3 | ||
|
|
78a2145a47 | ||
|
|
dbe50934b1 | ||
|
|
9e0ca948bf | ||
|
|
8e070f1a34 | ||
|
|
543d104f85 | ||
|
|
e06207c914 | ||
|
|
166267c87a | ||
|
|
91e0737b8a | ||
|
|
65a032b205 | ||
|
|
80c7dcdbd7 | ||
|
|
bcd54d12d8 | ||
|
|
726e19a886 | ||
|
|
95719e342d | ||
|
|
132a63d1da | ||
|
|
822f700b15 | ||
|
|
5df51a06f5 | ||
|
|
ad74c5ed7f | ||
|
|
d5284be91d | ||
|
|
c29b41e527 | ||
|
|
4e612612dc | ||
|
|
705fbf92ee | ||
|
|
2dc704abb6 | ||
|
|
6c2ef75681 | ||
|
|
96899dc527 | ||
|
|
b0db912bde | ||
|
|
7cd33ce0dd | ||
|
|
e0dd40f812 | ||
|
|
79f0f97495 | ||
|
|
5e0b0a2aa3 | ||
|
|
e24969b422 | ||
|
|
dd02989e88 | ||
|
|
7383c04205 | ||
|
|
84291b021f | ||
|
|
91e30f4f22 | ||
|
|
5be44a6c03 | ||
|
|
8cc20a5618 | ||
|
|
c0f213b056 | ||
|
|
a2915cfdef | ||
|
|
4b921f3821 | ||
|
|
575f84ce0f | ||
|
|
1048796003 | ||
|
|
973c4ab569 | ||
|
|
445677dc00 | ||
|
|
da27529d91 | ||
|
|
f59921ce18 | ||
|
|
ec3c65122f | ||
|
|
9fa5e81836 | ||
|
|
4869a5165a | ||
|
|
30e84ff687 | ||
|
|
73fd01c986 | ||
|
|
3729af7438 | ||
|
|
1e5aef279a | ||
|
|
19db019e0c | ||
|
|
666464d7a9 | ||
|
|
391f880d07 | ||
|
|
d0bf3bf06d | ||
|
|
edb027e910 | ||
|
|
dd2fef6673 | ||
|
|
8e79c4a03d | ||
|
|
19d70a85fc | ||
|
|
f5e9bcc1fc | ||
|
|
3631f81236 | ||
|
|
1e6f64d5c9 | ||
|
|
569e9d932f | ||
|
|
6a7f4a1894 | ||
|
|
8c55a388cd | ||
|
|
d176f1f89c | ||
|
|
2d0845860a | ||
|
|
5ae701aab5 | ||
|
|
0d016195ac | ||
|
|
fc2de240d2 | ||
|
|
7c1ef6a387 | ||
|
|
0b538b6f2a | ||
|
|
3d26aa0dfb | ||
|
|
51d037e881 | ||
|
|
e5fb88fb85 | ||
|
|
787234eff4 | ||
|
|
6b1219b370 | ||
|
|
ed48555bdd | ||
|
|
ba9faae8b6 | ||
|
|
6935dee976 | ||
|
|
12473bb6d3 | ||
|
|
280a962328 | ||
|
|
37633ff957 | ||
|
|
1c2fc8c3c4 | ||
|
|
d3adecd819 | ||
|
|
0a71af41be | ||
|
|
8fb484da37 | ||
|
|
6e6a434a2d | ||
|
|
f23ea9e977 | ||
|
|
55cfdbaa3f | ||
|
|
1b5a07a740 | ||
|
|
00b0af7021 | ||
|
|
89027bd22c | ||
|
|
ff8de1a106 | ||
|
|
0c5e6770df | ||
|
|
c2ad33a04c | ||
|
|
c0a5e10313 | ||
|
|
1c394b8606 | ||
|
|
505d557fbb | ||
|
|
a63e429d22 | ||
|
|
de086f9848 | ||
|
|
6643acb375 | ||
|
|
c25cb107bb | ||
|
|
9dba08ac8b | ||
|
|
3ec115115d | ||
|
|
09c91b8931 | ||
|
|
de1940065a | ||
|
|
748ef8b2ae | ||
|
|
2ecf0a9053 | ||
|
|
b82048eb18 | ||
|
|
9d58ff97fe | ||
|
|
51b787e449 | ||
|
|
7654b518f1 | ||
|
|
52877c9af1 | ||
|
|
265f22be49 | ||
|
|
2a26c738bd | ||
|
|
6ec0afe5f7 | ||
|
|
02b37b3c69 | ||
|
|
d8370864f3 | ||
|
|
ffe48f00e3 | ||
|
|
49dff83a00 | ||
|
|
95afd7549c | ||
|
|
06ee890db3 | ||
|
|
e680420c89 | ||
|
|
41e731fc51 | ||
|
|
19c8da04d0 | ||
|
|
357ee692bd | ||
|
|
347baa6f3e | ||
|
|
8925000c42 | ||
|
|
568c99be4e | ||
|
|
69fc42afc9 | ||
|
|
19843cd826 | ||
|
|
4eb96fc144 | ||
|
|
cc922de758 | ||
|
|
dbca906ef5 | ||
|
|
1fc4dcb76f | ||
|
|
dcd7d1c8e6 | ||
|
|
72e768ef14 | ||
|
|
047f61aad6 | ||
|
|
d04b1ffca5 | ||
|
|
3d396f13ed | ||
|
|
7d8401d707 | ||
|
|
c3aac57dda | ||
|
|
5602571c01 | ||
|
|
c04236052c | ||
|
|
c93e2f2332 | ||
|
|
e7c29d0ceb | ||
|
|
6c46358be8 | ||
|
|
b0ff8af9ac | ||
|
|
624db256c6 | ||
|
|
cfb15d158e | ||
|
|
34c7881ddd | ||
|
|
f06d930445 | ||
|
|
b29f5688bd | ||
|
|
333e2f266a | ||
|
|
af94399473 | ||
|
|
3d35f26031 | ||
|
|
b1c72ec48c | ||
|
|
8dee34459b | ||
|
|
e7f649e40a | ||
|
|
5843fbc6ee | ||
|
|
c8eca3a69d | ||
|
|
12fdcea8b8 | ||
|
|
035447b08f | ||
|
|
584cafa284 | ||
|
|
8a4a150a1a | ||
|
|
de41b93b73 | ||
|
|
55b2e748e7 | ||
|
|
3246331876 | ||
|
|
6d9fbb0e97 | ||
|
|
14fc4a06a6 | ||
|
|
cba4ba9cdc | ||
|
|
937d99150c | ||
|
|
0a6c846091 | ||
|
|
c6b8ad6e7e | ||
|
|
d92a479d2f | ||
|
|
f242182840 | ||
|
|
f5871369cc | ||
|
|
b1b184cdb8 | ||
|
|
1a6de01210 | ||
|
|
4aa1dd3ae8 | ||
|
|
be33d01663 | ||
|
|
fdcb93a467 | ||
|
|
1dd2589a42 | ||
|
|
3485e1c089 | ||
|
|
6f7965e4f1 | ||
|
|
1ba8fa6f04 | ||
|
|
6eb67fcc5f | ||
|
|
a8abea8247 | ||
|
|
d4defd943a | ||
|
|
eafd2bed5d | ||
|
|
c54f7ec95e | ||
|
|
f8448764e2 | ||
|
|
6549765930 | ||
|
|
6cb2233cd2 | ||
|
|
9ad75e1dc3 | ||
|
|
bec0f7d975 | ||
|
|
4b772fd783 | ||
|
|
1d026b921c | ||
|
|
fcbb6a7384 | ||
|
|
bdc01f44de | ||
|
|
d2c23773ff | ||
|
|
38dc3ec0da | ||
|
|
c03acc9572 | ||
|
|
30e133d36a | ||
|
|
0890df5891 | ||
|
|
d223a49d18 | ||
|
|
26285e183e | ||
|
|
f71e81c483 | ||
|
|
9bd53b8565 | ||
|
|
dde9dda1a4 | ||
|
|
408e9f1481 | ||
|
|
7c8344930b | ||
|
|
482e254a49 | ||
|
|
eb65331403 | ||
|
|
55c8b0c2a7 | ||
|
|
9031f59f7b | ||
|
|
b86bd1fcd0 | ||
|
|
c156e597dc | ||
|
|
bb750b07bc | ||
|
|
0e6fe5a097 | ||
|
|
13de7cafb2 | ||
|
|
057b688fc7 | ||
|
|
45bb3e7ffc | ||
|
|
8ced9c332c | ||
|
|
9fcd5ce03d | ||
|
|
a41e6e2ed2 | ||
|
|
e54855ef88 | ||
|
|
7a18fa281d | ||
|
|
aed9018807 | ||
|
|
fd1fca51fa | ||
|
|
97b5f22a70 | ||
|
|
00620b57eb | ||
|
|
bba9f38636 | ||
|
|
9c37acd002 | ||
|
|
195a9b357a | ||
|
|
cd4c4bf723 | ||
|
|
e244be2540 | ||
|
|
5dad31b06e | ||
|
|
64ded18960 | ||
|
|
4dd496790a | ||
|
|
748dab2f38 | ||
|
|
3c11129b04 | ||
|
|
6e0e343f23 | ||
|
|
cc27470cb6 | ||
|
|
fb527fa7db | ||
|
|
87976aa48e | ||
|
|
863923472a | ||
|
|
778a15b64a | ||
|
|
5eaf5570c4 | ||
|
|
cbe0f1f1e9 | ||
|
|
97472e374f | ||
|
|
7a28aa0dd7 | ||
|
|
b1054ac227 | ||
|
|
1618b98533 | ||
|
|
5b6ed54817 | ||
|
|
d23a2b7e97 | ||
|
|
ab92b27226 | ||
|
|
eb6efdf17a | ||
|
|
b182791052 | ||
|
|
97a7f174ce | ||
|
|
654758e622 | ||
|
|
c8887a5846 | ||
|
|
56779f9542 | ||
|
|
f474838e22 | ||
|
|
19393e13db | ||
|
|
301b10de8d | ||
|
|
aa2fd3a151 | ||
|
|
2dd604949f | ||
|
|
ec481ac82a | ||
|
|
7afee45635 | ||
|
|
385d18590a | ||
|
|
bc9252f6e3 | ||
|
|
6c76f2a7c7 | ||
|
|
bf187170b1 | ||
|
|
14837e8e0f | ||
|
|
b44cdf1006 | ||
|
|
5d73f90133 | ||
|
|
34a6a9d3a1 | ||
|
|
db9e782d50 | ||
|
|
f92a4e611b | ||
|
|
b3deb9ec86 | ||
|
|
a92653f2b2 | ||
|
|
29cdab650b | ||
|
|
574a4eccb5 | ||
|
|
d4869ebdc5 | ||
|
|
d1fe0d449b | ||
|
|
e2b52abd47 | ||
|
|
f58eb4dc0e | ||
|
|
42c0641daf | ||
|
|
11b6e7844a | ||
|
|
f1b67152af | ||
|
|
b6be437bab | ||
|
|
8497b588a2 | ||
|
|
3d7f421809 | ||
|
|
fa6d33f742 | ||
|
|
dc22196394 | ||
|
|
3ea458b9dc | ||
|
|
86128a7f06 | ||
|
|
de60750506 | ||
|
|
57641f67dd | ||
|
|
b675c2149c | ||
|
|
4fbc30267c | ||
|
|
2a830aa567 | ||
|
|
f07ac3068b | ||
|
|
7ec94fda08 | ||
|
|
9231c80cd8 | ||
|
|
4702e58ee5 | ||
|
|
2719549cf2 | ||
|
|
cd26f29fb8 | ||
|
|
9c350af582 | ||
|
|
c019b3dad9 | ||
|
|
8cb4aabb8b | ||
|
|
7ad8b3366d | ||
|
|
c6c8f77bc0 | ||
|
|
f415a70c0b | ||
|
|
da31d94c2a | ||
|
|
84229d4735 | ||
|
|
e370ce5a00 | ||
|
|
2a1ad05671 | ||
|
|
5c0db5a293 | ||
|
|
d533d7c97c | ||
|
|
7568bff8e6 | ||
|
|
6766c8ec1d | ||
|
|
2955c1fdc7 | ||
|
|
45e9e541a4 | ||
|
|
490802099d | ||
|
|
34dd42dc7f | ||
|
|
b374868750 | ||
|
|
538688486e | ||
|
|
27111bd76d | ||
|
|
4a3f380fd2 | ||
|
|
4ec7e66e90 | ||
|
|
a42e425d44 | ||
|
|
2179c3e420 | ||
|
|
d5c9196348 | ||
|
|
97156f3382 | ||
|
|
27c58c764d | ||
|
|
1564584ba9 | ||
|
|
fd10db5a62 | ||
|
|
b2ac4475cc | ||
|
|
a7952492c1 | ||
|
|
6f683da2e9 | ||
|
|
a6a06c5aa6 | ||
|
|
4d1ab1ba2a | ||
|
|
d7a6a8e35b | ||
|
|
bf2e078ecf | ||
|
|
a25ce36925 | ||
|
|
a0f4038df2 | ||
|
|
7d8c1ae295 | ||
|
|
df7c2e605b | ||
|
|
ea51cdb3f2 | ||
|
|
fe986a52d6 | ||
|
|
34fd261a4f | ||
|
|
0b1089e105 | ||
|
|
6b3bdaff97 | ||
|
|
7c908bca7a | ||
|
|
90ede52728 | ||
|
|
17974ea7a0 | ||
|
|
25852623f0 | ||
|
|
446aa08286 | ||
|
|
12b3cc3750 | ||
|
|
6dd288d459 | ||
|
|
d8547fc721 | ||
|
|
68fdc4fcbe | ||
|
|
398cb6fb4a | ||
|
|
7302549903 | ||
|
|
e971bc79d6 | ||
|
|
650ad28f12 | ||
|
|
2637477eea | ||
|
|
e5c69afa98 | ||
|
|
ec901e9e31 | ||
|
|
d814529285 | ||
|
|
82dc3d57f2 | ||
|
|
3f77cb95c5 | ||
|
|
68e9ff797c | ||
|
|
97ee6e2587 | ||
|
|
e0215480cd | ||
|
|
bd33d9c47d | ||
|
|
3a025d39db | ||
|
|
1ed454d102 | ||
|
|
52250d518d | ||
|
|
bfa2ff2bdf | ||
|
|
c62b27f014 | ||
|
|
20cf0d3ea9 | ||
|
|
02caf7ce4a | ||
|
|
e1c7367a2e | ||
|
|
fc4641c5a5 | ||
|
|
8f9e2c7db2 | ||
|
|
46be03d88e | ||
|
|
f9bd5eb79e | ||
|
|
d89a4ab3df | ||
|
|
57b18d8a18 | ||
|
|
95465ca03a | ||
|
|
6513808720 | ||
|
|
6c1773542d | ||
|
|
c60422241b | ||
|
|
92dcb22e92 | ||
|
|
f75b1e5f07 | ||
|
|
fb9e47d8c9 | ||
|
|
43ae6e0a6b | ||
|
|
724d6d7c95 | ||
|
|
95c20aef20 | ||
|
|
791f77b879 | ||
|
|
a6986864db | ||
|
|
30866d06d5 | ||
|
|
8b474429b7 | ||
|
|
91b4e02805 | ||
|
|
e65e653ea5 | ||
|
|
29a1a1f8b0 | ||
|
|
71de737ee8 | ||
|
|
55fbacb469 | ||
|
|
2a6aac4c14 | ||
|
|
4fd8431ad0 | ||
|
|
d3e8dd3623 | ||
|
|
1fa6ebacf4 | ||
|
|
8fa822207d | ||
|
|
52ad3c8425 | ||
|
|
e5abcd217c | ||
|
|
4a0118aea1 | ||
|
|
90a748f7fb | ||
|
|
2434653867 | ||
|
|
1def587884 | ||
|
|
c56bc9ed05 | ||
|
|
2a9ba51907 | ||
|
|
406ae3d8a6 | ||
|
|
8a674265f3 | ||
|
|
ed43033c39 | ||
|
|
0a7b008b21 | ||
|
|
f8e908b89c | ||
|
|
2695c5305e | ||
|
|
55fdfef9d1 | ||
|
|
2f6da393e2 | ||
|
|
57b17bbb70 | ||
|
|
7c23da598d | ||
|
|
b7cc218a03 | ||
|
|
0ae8019e7f | ||
|
|
c3fc0229e5 | ||
|
|
76196b8ffa | ||
|
|
fd4e683151 | ||
|
|
2b05d8531d | ||
|
|
8e966688a7 | ||
|
|
c4ec9234e9 | ||
|
|
2e0eabbf0a | ||
|
|
1b2435fab6 | ||
|
|
1ff3dafe4e | ||
|
|
6b16e93f1b | ||
|
|
b7467a3110 | ||
|
|
4224ecd2ab | ||
|
|
e18c10d340 | ||
|
|
0719dcf2e7 | ||
|
|
3400a312dd | ||
|
|
b2f9144e10 | ||
|
|
a74f173902 | ||
|
|
a1cd68e3f8 | ||
|
|
eec2c19ad1 | ||
|
|
05fdd04262 | ||
|
|
3e36ddbceb | ||
|
|
0a55e5ca54 | ||
|
|
a7ad0c4434 | ||
|
|
271bac5347 | ||
|
|
914ffc8887 | ||
|
|
7a4e35e317 | ||
|
|
1bc85cb59e | ||
|
|
f992c68fd8 | ||
|
|
4bb7a86753 | ||
|
|
950194fb63 | ||
|
|
b528fc438c | ||
|
|
811604a663 | ||
|
|
4232bf9e2f | ||
|
|
8a3620e438 | ||
|
|
1408861511 | ||
|
|
44229e8af6 | ||
|
|
d002323de2 | ||
|
|
999c7246c3 | ||
|
|
c10a597bf0 | ||
|
|
75ed7b27c6 | ||
|
|
ef01e9daf8 | ||
|
|
866cb2d16f | ||
|
|
6a62d49720 | ||
|
|
f201d246fd | ||
|
|
0328fe44e4 | ||
|
|
bb85983a29 | ||
|
|
4341d6a095 | ||
|
|
10a9ce1ed2 | ||
|
|
943490cfa1 | ||
|
|
7e6bbc63ab | ||
|
|
0d195698f8 | ||
|
|
08fd890a89 | ||
|
|
ab0081294c | ||
|
|
c42a939e98 | ||
|
|
6c9e2f7e6c | ||
|
|
992f04f328 | ||
|
|
8a90fce476 | ||
|
|
e928e80a42 | ||
|
|
28c04b21f6 | ||
|
|
46427f1dd7 | ||
|
|
528e856566 | ||
|
|
14a934d412 | ||
|
|
0faed7aeb5 | ||
|
|
8e6450c8fc | ||
|
|
6898e26a7a | ||
|
|
e2cf40ef99 | ||
|
|
96515c9f47 | ||
|
|
96e18faa93 | ||
|
|
476c6e89ad | ||
|
|
b46a96d454 | ||
|
|
f67cf7d053 | ||
|
|
2ad0f4a9e7 | ||
|
|
1ec9ca497b | ||
|
|
dd5d0f4f0c | ||
|
|
d1a6ce8e7f | ||
|
|
b22537e8a4 | ||
|
|
a25c14d472 | ||
|
|
7730d38339 | ||
|
|
58e498c9d3 | ||
|
|
35485f2b5e | ||
|
|
b108653f02 | ||
|
|
25f72cf240 | ||
|
|
d1df8dcccc | ||
|
|
a50d00aa47 | ||
|
|
34add944f2 | ||
|
|
95f85efc49 | ||
|
|
f2f6a05e2c | ||
|
|
bd442a27ee | ||
|
|
73c0eb96c3 | ||
|
|
6a4a4a432b | ||
|
|
263529e886 | ||
|
|
818797ff08 | ||
|
|
d263dbe9dd | ||
|
|
67319ad9d9 | ||
|
|
2591df66cc | ||
|
|
2ac78bccb5 | ||
|
|
47de44001e | ||
|
|
eaa467618b | ||
|
|
ac01f15bfe | ||
|
|
5df5217e2e | ||
|
|
a6ec2b76c2 | ||
|
|
bc01b300c4 | ||
|
|
c8d4d8cb66 | ||
|
|
315202b007 | ||
|
|
7194fdca38 | ||
|
|
31d0945059 | ||
|
|
466e79d572 | ||
|
|
3bb4f72a4b | ||
|
|
329e9d54ab | ||
|
|
9c45dd693b | ||
|
|
cc671e4495 | ||
|
|
61ca158de1 | ||
|
|
7e0038c691 | ||
|
|
04792d0656 | ||
|
|
6c5cc40039 | ||
|
|
6b462474f0 | ||
|
|
9a061a2fee | ||
|
|
6dd552d689 | ||
|
|
34e858ea05 | ||
|
|
39d2839e37 | ||
|
|
7980d00e8c | ||
|
|
25ce5b4281 | ||
|
|
0cd3ab6299 | ||
|
|
28d038fb16 | ||
|
|
10dc308e6b | ||
|
|
6344bab6cc | ||
|
|
984fbacccf | ||
|
|
cc83ca6628 | ||
|
|
04fc6ea11b | ||
|
|
8a9e2fccbd | ||
|
|
cc1324e2d9 | ||
|
|
b2226d1608 | ||
|
|
8ce0af3fc9 | ||
|
|
b315a6c3d1 | ||
|
|
baf642b7d2 | ||
|
|
8db76e9057 | ||
|
|
e3b7bf9b31 | ||
|
|
38724f2e86 | ||
|
|
65fc09fac9 | ||
|
|
3197b715b3 | ||
|
|
26362dfc72 | ||
|
|
275781ce45 | ||
|
|
72dbbfedce | ||
|
|
a6c6eabb5a | ||
|
|
379e4d976c | ||
|
|
a6514e2656 | ||
|
|
032af23e18 | ||
|
|
b5f9293014 | ||
|
|
a37db55689 | ||
|
|
1b959d0eff | ||
|
|
ce03f1abea | ||
|
|
5faf41b695 | ||
|
|
7634daa834 | ||
|
|
968f439672 |
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
@@ -63,7 +63,7 @@ tmux -vv -Ltest -f/dev/null new
|
||||
|
||||
Or if you need your configuration:
|
||||
|
||||
~~~base
|
||||
~~~bash
|
||||
tmux kill-server
|
||||
tmux -vv new
|
||||
~~~
|
||||
|
||||
75
.github/workflows/regress.yml
vendored
Normal file
75
.github/workflows/regress.yml
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
name: 'Run Tests'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '33 3 * * *'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: tmux-tests
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
regress:
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 45
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: ubuntu-24.04-x64
|
||||
runner: ubuntu-24.04
|
||||
make: make
|
||||
# - name: ubuntu-24.04-arm64
|
||||
# runner: ubuntu-24.04-arm
|
||||
# make: make
|
||||
# - name: macos-26-arm64
|
||||
# runner: macos-26
|
||||
# make: gmake
|
||||
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: dependencies
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
autoconf \
|
||||
automake \
|
||||
bison \
|
||||
build-essential \
|
||||
libevent-dev \
|
||||
libncurses-dev \
|
||||
libutf8proc-dev \
|
||||
pkg-config
|
||||
|
||||
- name: dependencies
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
brew install \
|
||||
autoconf \
|
||||
automake \
|
||||
bison \
|
||||
libevent \
|
||||
make \
|
||||
ncurses \
|
||||
utf8proc \
|
||||
pkg-config
|
||||
|
||||
- name: build
|
||||
run: |
|
||||
sh autogen.sh
|
||||
./configure --enable-utf8proc
|
||||
${{ matrix.make }} -j"$(getconf _NPROCESSORS_ONLN)"
|
||||
|
||||
- name: test
|
||||
run: |
|
||||
cd regress
|
||||
${{ matrix.make }}
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -7,6 +7,8 @@
|
||||
*~
|
||||
.deps/
|
||||
.dirstamp
|
||||
.serena/
|
||||
AGENTS.md
|
||||
Makefile
|
||||
Makefile.in
|
||||
aclocal.m4
|
||||
|
||||
13
CHANGES
13
CHANGES
@@ -1,3 +1,16 @@
|
||||
CHANGES FROM 3.7a TO 3.7b
|
||||
|
||||
* Fix so that the end of a synchronized update again triggers a redraw.
|
||||
|
||||
CHANGES FROM 3.7 TO 3.7a
|
||||
|
||||
* Fix crash in break-pane when no name is provided.
|
||||
|
||||
* Scrollbar options are now cached rather than being looked up for every redraw
|
||||
(issue 5298).
|
||||
|
||||
* Only forbid #( in names, allow #[, empty names, : and .
|
||||
|
||||
CHANGES FROM 3.6b TO 3.7
|
||||
|
||||
* Add floating panes. These are panes which sit above the layout ("tiled
|
||||
|
||||
@@ -102,6 +102,7 @@ dist_tmux_SOURCES = \
|
||||
cmd-display-panes.c \
|
||||
cmd-find-window.c \
|
||||
cmd-find.c \
|
||||
cmd-hide-pane.c \
|
||||
cmd-if-shell.c \
|
||||
cmd-join-pane.c \
|
||||
cmd-kill-pane.c \
|
||||
@@ -162,6 +163,7 @@ dist_tmux_SOURCES = \
|
||||
file.c \
|
||||
format.c \
|
||||
format-draw.c \
|
||||
fuzzy.c \
|
||||
grid-reader.c \
|
||||
grid-view.c \
|
||||
grid.c \
|
||||
@@ -184,6 +186,8 @@ dist_tmux_SOURCES = \
|
||||
paste.c \
|
||||
popup.c \
|
||||
proc.c \
|
||||
prompt.c \
|
||||
prompt-history.c \
|
||||
regsub.c \
|
||||
resize.c \
|
||||
screen-redraw.c \
|
||||
@@ -209,12 +213,15 @@ dist_tmux_SOURCES = \
|
||||
tty.c \
|
||||
utf8-combined.c \
|
||||
utf8.c \
|
||||
window-border.c \
|
||||
window-buffer.c \
|
||||
window-client.c \
|
||||
window-clock.c \
|
||||
window-copy.c \
|
||||
window-customize.c \
|
||||
window-switch.c \
|
||||
window-tree.c \
|
||||
window-visible.c \
|
||||
window.c \
|
||||
xmalloc.c \
|
||||
xmalloc.h
|
||||
|
||||
18
arguments.c
18
arguments.c
@@ -149,7 +149,7 @@ args_parse_flag_argument(struct args_value *values, u_int count, char **cause,
|
||||
int optional_argument)
|
||||
{
|
||||
struct args_value *argument, *new;
|
||||
const char *s;
|
||||
const char *s, *as;
|
||||
|
||||
new = xcalloc(1, sizeof *new);
|
||||
if (*string != '\0') {
|
||||
@@ -180,12 +180,24 @@ args_parse_flag_argument(struct args_value *values, u_int count, char **cause,
|
||||
xasprintf(cause, "-%c expects an argument", flag);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
if (optional_argument && argument->type == ARGS_STRING) {
|
||||
as = argument->string;
|
||||
if (as[0] == '-' && (as[1] == '-' || isalpha((u_char)as[1]))) {
|
||||
args_free_value(new);
|
||||
free(new);
|
||||
log_debug("%s: -%c (optional)", __func__, flag);
|
||||
args_set(args, flag, NULL, ARGS_ENTRY_OPTIONAL_VALUE);
|
||||
return (0);
|
||||
}
|
||||
}
|
||||
args_copy_value(new, argument);
|
||||
(*i)++;
|
||||
|
||||
out:
|
||||
s = args_value_as_string(new);
|
||||
log_debug("%s: -%c = %s", __func__, flag, s);
|
||||
|
||||
args_set(args, flag, new, 0);
|
||||
return (0);
|
||||
}
|
||||
@@ -998,7 +1010,7 @@ args_string_percentage(const char *value, long long minval, long long maxval,
|
||||
copy = xstrdup(value);
|
||||
copy[valuelen - 1] = '\0';
|
||||
|
||||
ll = strtonum(copy, 0, 100, &errstr);
|
||||
ll = strtonum(copy, 0, 1000, &errstr);
|
||||
free(copy);
|
||||
if (errstr != NULL) {
|
||||
*cause = xstrdup(errstr);
|
||||
@@ -1066,7 +1078,7 @@ args_string_percentage_and_expand(const char *value, long long minval,
|
||||
copy[valuelen - 1] = '\0';
|
||||
|
||||
f = format_single_from_target(item, copy);
|
||||
ll = strtonum(f, 0, 100, &errstr);
|
||||
ll = strtonum(f, 0, 1000, &errstr);
|
||||
free(f);
|
||||
free(copy);
|
||||
if (errstr != NULL) {
|
||||
|
||||
@@ -45,7 +45,7 @@ attributes_tostring(int attr)
|
||||
(attr & GRID_ATTR_UNDERSCORE_3) ? "curly-underscore," : "",
|
||||
(attr & GRID_ATTR_UNDERSCORE_4) ? "dotted-underscore," : "",
|
||||
(attr & GRID_ATTR_UNDERSCORE_5) ? "dashed-underscore," : "",
|
||||
(attr & GRID_ATTR_OVERLINE) ? "overline," : "",
|
||||
(attr & GRID_ATTR_OVERLINE) ? "overline," : "",
|
||||
(attr & GRID_ATTR_NOATTR) ? "noattr," : "");
|
||||
if (len > 0)
|
||||
buf[len - 1] = '\0';
|
||||
|
||||
2
cfg.c
2
cfg.c
@@ -56,7 +56,7 @@ cfg_done(__unused struct cmdq_item *item, __unused void *data)
|
||||
if (cfg_item != NULL)
|
||||
cmdq_continue(cfg_item);
|
||||
|
||||
status_prompt_load_history();
|
||||
prompt_load_history();
|
||||
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
@@ -34,9 +34,10 @@ const struct cmd_entry cmd_break_pane_entry = {
|
||||
.name = "break-pane",
|
||||
.alias = "breakp",
|
||||
|
||||
.args = { "abdPF:n:s:t:", 0, 0, NULL },
|
||||
.usage = "[-abdP] [-F format] [-n window-name] [-s src-pane] "
|
||||
"[-t dst-window]",
|
||||
.args = { "abdPF:n:s:t:Wx:X:y:Y:", 0, 0, NULL },
|
||||
.usage = "[-abdPW] [-F format] [-n window-name] [-s src-pane] "
|
||||
"[-t dst-window] [-x width] [-y height] [-X x-position] "
|
||||
"[-Y y-position]",
|
||||
|
||||
.source = { 's', CMD_FIND_PANE, 0 },
|
||||
.target = { 't', CMD_FIND_WINDOW, CMD_FIND_WINDOW_INDEX },
|
||||
@@ -45,6 +46,52 @@ const struct cmd_entry cmd_break_pane_entry = {
|
||||
.exec = cmd_break_pane_exec
|
||||
};
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_break_pane_float(struct cmdq_item *item, struct args *args,
|
||||
struct window *w, struct window_pane *wp)
|
||||
{
|
||||
struct layout_cell *lc = wp->layout_cell;
|
||||
u_int sx = lc->saved_sx, sy = lc->saved_sy;
|
||||
int ox = lc->saved_xoff, oy = lc->saved_yoff;
|
||||
char *cause = NULL;
|
||||
enum pane_lines lines = window_get_pane_lines(w);
|
||||
|
||||
if (window_pane_is_floating(wp)) {
|
||||
cmdq_error(item, "pane is already floating");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
if (window_pane_is_hidden(wp)) {
|
||||
cmdq_error(item, "can't float a hidden pane");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
if (w->flags & WINDOW_ZOOMED) {
|
||||
cmdq_error(item, "can't float a pane while window is zoomed");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
|
||||
if (layout_floating_args_parse(item, args, lines, w, &sx, &sy, &ox, &oy,
|
||||
&cause) != 0) {
|
||||
cmdq_error(item, "failed to float pane: %s", cause);
|
||||
free(cause);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
layout_remove_tile(w, lc);
|
||||
layout_set_size(lc, sx, sy, ox, oy);
|
||||
|
||||
lc->flags |= LAYOUT_CELL_FLOATING;
|
||||
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
||||
TAILQ_INSERT_HEAD(&w->z_index, wp, zentry);
|
||||
|
||||
if (!args_has(args, 'd'))
|
||||
window_set_active_pane(w, wp, 1);
|
||||
layout_fix_offsets(w);
|
||||
layout_fix_panes(w, NULL);
|
||||
notify_window("window-layout-changed", w);
|
||||
server_redraw_window(w);
|
||||
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
{
|
||||
@@ -62,7 +109,10 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
int idx = target->idx, before;
|
||||
const char *template, *name = args_get(args, 'n');
|
||||
|
||||
if (name != NULL && !check_name(name, WINDOW_NAME_FORBID)) {
|
||||
if (args_has(args, 'W'))
|
||||
return (cmd_break_pane_float(item, args, w, wp));
|
||||
|
||||
if (name != NULL && !check_name(name)) {
|
||||
cmdq_error(item, "invalid window name: %s", name);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
@@ -86,7 +136,7 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
if (name != NULL) {
|
||||
window_set_name(w, name, WINDOW_NAME_FORBID);
|
||||
window_set_name(w, name, 0);
|
||||
options_set_number(w->options, "automatic-rename", 0);
|
||||
}
|
||||
server_unlink_window(src_s, wl);
|
||||
@@ -114,9 +164,9 @@ cmd_break_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
w->active = wp;
|
||||
w->latest = tc;
|
||||
|
||||
if (name != NULL) {
|
||||
if (name == NULL) {
|
||||
newname = default_window_name(w);
|
||||
window_set_name(w, newname, WINDOW_NAME_FORBID);
|
||||
window_set_name(w, newname, 0);
|
||||
free(newname);
|
||||
} else {
|
||||
window_set_name(w, name, 0);
|
||||
|
||||
@@ -42,8 +42,8 @@ const struct cmd_entry cmd_capture_pane_entry = {
|
||||
.name = "capture-pane",
|
||||
.alias = "capturep",
|
||||
|
||||
.args = { "ab:CeE:FHJLMNpPqS:Tt:", 0, 0, NULL },
|
||||
.usage = "[-aCeFHJLMNpPqT] " CMD_BUFFER_USAGE " [-E end-line] "
|
||||
.args = { "ab:CeE:FHJLMNpPqRS:Tt:", 0, 0, NULL },
|
||||
.usage = "[-aCeFHJLMNpPqRT] " CMD_BUFFER_USAGE " [-E end-line] "
|
||||
"[-S start-line] " CMD_TARGET_PANE_USAGE,
|
||||
|
||||
.target = { 't', CMD_FIND_PANE, 0 },
|
||||
@@ -75,6 +75,96 @@ cmd_capture_pane_append(char *buf, size_t *len, const char *line,
|
||||
return (buf);
|
||||
}
|
||||
|
||||
static char *
|
||||
cmd_capture_pane_cell(struct screen *s, u_int xx, u_int yy)
|
||||
{
|
||||
struct grid *gd = s->grid;
|
||||
struct hyperlinks *hl = s->hyperlinks;
|
||||
struct grid_cell gc;
|
||||
char *line, *data, *link, *linkid, *f, *b, *u;
|
||||
char c[UTF8_SIZE + 1];
|
||||
const char *uri, *iid;
|
||||
u_int flags;
|
||||
|
||||
grid_get_cell(gd, xx, yy, &gc);
|
||||
|
||||
memcpy(c, gc.data.data, gc.data.size);
|
||||
c[gc.data.size] = '\0';
|
||||
utf8_stravis(&data, c, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL);
|
||||
|
||||
if (gc.link != 0 && hyperlinks_get(hl, gc.link, &uri, &iid, NULL)) {
|
||||
xasprintf(&link, "%s", uri);
|
||||
if (iid != NULL && *iid != '\0')
|
||||
xasprintf(&linkid, "%s", iid);
|
||||
else
|
||||
xasprintf(&linkid, "NONE");
|
||||
} else {
|
||||
xasprintf(&link, "NONE");
|
||||
xasprintf(&linkid, "NONE");
|
||||
}
|
||||
|
||||
flags = gc.flags;
|
||||
if (gc.fg & COLOUR_FLAG_256)
|
||||
flags |= GRID_FLAG_FG256;
|
||||
if (gc.bg & COLOUR_FLAG_256)
|
||||
flags |= GRID_FLAG_BG256;
|
||||
|
||||
xasprintf(&f, "%s[%x]", colour_tostring(gc.fg), gc.fg);
|
||||
xasprintf(&b, "%s[%x]", colour_tostring(gc.bg), gc.bg);
|
||||
xasprintf(&u, "%s[%x]", colour_tostring(gc.us), gc.us);
|
||||
|
||||
xasprintf(&line, "\t\tC %u,%u data=(%u,%u,%s) flags=%s[%x] "
|
||||
"attr=%s[%x] fg=%s bg=%s us=%s link=%s linkid=%s\n",
|
||||
yy, xx, gc.data.width, gc.data.size, data,
|
||||
grid_cell_flags_string(flags), flags,
|
||||
grid_cell_attr_string(gc.attr), gc.attr, f, b, u, link, linkid);
|
||||
|
||||
free(f);
|
||||
free(b);
|
||||
free(u);
|
||||
free(link);
|
||||
free(linkid);
|
||||
free(data);
|
||||
return (line);
|
||||
}
|
||||
|
||||
static char *
|
||||
cmd_capture_pane_grid(struct window_pane *wp, size_t *len)
|
||||
{
|
||||
struct screen *s = &wp->base;
|
||||
struct grid *gd = s->grid;
|
||||
struct grid_line *gl;
|
||||
char *buf = xstrdup(""), *line;
|
||||
char p[11];
|
||||
u_int yy, xx, total = gd->hsize + gd->sy;
|
||||
|
||||
xasprintf(&line, "G %ux%u (%u/%u)\n", gd->sx, gd->sy, gd->hsize,
|
||||
gd->hlimit);
|
||||
buf = cmd_capture_pane_append(buf, len, line, strlen(line));
|
||||
free(line);
|
||||
|
||||
for (yy = 0; yy < total; yy++) {
|
||||
gl = grid_get_line(gd, yy);
|
||||
if (yy < gd->hsize)
|
||||
snprintf(p, sizeof p, "-");
|
||||
else
|
||||
snprintf(p, sizeof p, "%u", yy - gd->hsize);
|
||||
xasprintf(&line, "\tL %u (%s) flags=%s[%x] %u/%u\n", yy,
|
||||
p, grid_line_flags_string(gl->flags), gl->flags,
|
||||
gl->cellused, gl->cellsize);
|
||||
buf = cmd_capture_pane_append(buf, len, line, strlen(line));
|
||||
free(line);
|
||||
|
||||
for (xx = 0; xx < gd->sx; xx++) {
|
||||
line = cmd_capture_pane_cell(s, xx, yy);
|
||||
buf = cmd_capture_pane_append(buf, len, line,
|
||||
strlen(line));
|
||||
free(line);
|
||||
}
|
||||
}
|
||||
return (buf);
|
||||
}
|
||||
|
||||
static char *
|
||||
cmd_capture_pane_pending(struct args *args, struct window_pane *wp,
|
||||
size_t *len)
|
||||
@@ -318,11 +408,14 @@ cmd_capture_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
grid_clear_history(wp->base.grid);
|
||||
if (args_has(args, 'H'))
|
||||
screen_reset_hyperlinks(wp->screen);
|
||||
server_redraw_window(wp->window);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
len = 0;
|
||||
if (args_has(args, 'P') && !args_has(args, 'H'))
|
||||
if (args_has(args, 'R'))
|
||||
buf = cmd_capture_pane_grid(wp, &len);
|
||||
else if (args_has(args, 'P') && !args_has(args, 'H'))
|
||||
buf = cmd_capture_pane_pending(args, wp, &len);
|
||||
else
|
||||
buf = cmd_capture_pane_history(args, item, wp, &len);
|
||||
|
||||
@@ -33,8 +33,8 @@ const struct cmd_entry cmd_choose_tree_entry = {
|
||||
.name = "choose-tree",
|
||||
.alias = NULL,
|
||||
|
||||
.args = { "F:f:GK:NO:rst:wyZ", 0, 1, cmd_choose_tree_args_parse },
|
||||
.usage = "[-GNrswZ] [-F format] [-f filter] [-K key-format] "
|
||||
.args = { "F:f:GhK:kNO:rst:wyZ", 0, 1, cmd_choose_tree_args_parse },
|
||||
.usage = "[-GhkNrswZ] [-F format] [-f filter] [-K key-format] "
|
||||
"[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]",
|
||||
|
||||
.target = { 't', CMD_FIND_PANE, 0 },
|
||||
@@ -47,8 +47,8 @@ const struct cmd_entry cmd_choose_client_entry = {
|
||||
.name = "choose-client",
|
||||
.alias = NULL,
|
||||
|
||||
.args = { "F:f:K:NO:rt:yZ", 0, 1, cmd_choose_tree_args_parse },
|
||||
.usage = "[-NrZ] [-F format] [-f filter] [-K key-format] "
|
||||
.args = { "F:f:hiK:kNO:rt:yZ", 0, 1, cmd_choose_tree_args_parse },
|
||||
.usage = "[-hikNrZ] [-F format] [-f filter] [-K key-format] "
|
||||
"[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]",
|
||||
|
||||
.target = { 't', CMD_FIND_PANE, 0 },
|
||||
@@ -61,8 +61,8 @@ const struct cmd_entry cmd_choose_buffer_entry = {
|
||||
.name = "choose-buffer",
|
||||
.alias = NULL,
|
||||
|
||||
.args = { "F:f:K:NO:rt:yZ", 0, 1, cmd_choose_tree_args_parse },
|
||||
.usage = "[-NrZ] [-F format] [-f filter] [-K key-format] "
|
||||
.args = { "F:f:K:kNO:rt:yZ", 0, 1, cmd_choose_tree_args_parse },
|
||||
.usage = "[-kNrZ] [-F format] [-f filter] [-K key-format] "
|
||||
"[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]",
|
||||
|
||||
.target = { 't', CMD_FIND_PANE, 0 },
|
||||
@@ -75,8 +75,21 @@ const struct cmd_entry cmd_customize_mode_entry = {
|
||||
.name = "customize-mode",
|
||||
.alias = NULL,
|
||||
|
||||
.args = { "F:f:Nt:yZ", 0, 0, NULL },
|
||||
.usage = "[-NZ] [-F format] [-f filter] " CMD_TARGET_PANE_USAGE,
|
||||
.args = { "F:f:kNt:yZ", 0, 0, NULL },
|
||||
.usage = "[-kNZ] [-F format] [-f filter] " CMD_TARGET_PANE_USAGE,
|
||||
|
||||
.target = { 't', CMD_FIND_PANE, 0 },
|
||||
|
||||
.flags = 0,
|
||||
.exec = cmd_choose_tree_exec
|
||||
};
|
||||
|
||||
const struct cmd_entry cmd_switch_mode_entry = {
|
||||
.name = "switch-mode",
|
||||
.alias = NULL,
|
||||
|
||||
.args = { "F:kst:wZ", 0, 1, cmd_choose_tree_args_parse },
|
||||
.usage = "[-kswZ] [-F format] " CMD_TARGET_PANE_USAGE " [command]",
|
||||
|
||||
.target = { 't', CMD_FIND_PANE, 0 },
|
||||
|
||||
@@ -116,6 +129,8 @@ cmd_choose_tree_exec(struct cmd *self, struct cmdq_item *item)
|
||||
mode = &window_client_mode;
|
||||
} else if (cmd_get_entry(self) == &cmd_customize_mode_entry)
|
||||
mode = &window_customize_mode;
|
||||
else if (cmd_get_entry(self) == &cmd_switch_mode_entry)
|
||||
mode = &window_switch_mode;
|
||||
else
|
||||
mode = &window_tree_mode;
|
||||
|
||||
|
||||
@@ -34,16 +34,16 @@ static enum args_parse_type cmd_command_prompt_args_parse(struct args *,
|
||||
static enum cmd_retval cmd_command_prompt_exec(struct cmd *,
|
||||
struct cmdq_item *);
|
||||
|
||||
static int cmd_command_prompt_callback(struct client *, void *,
|
||||
const char *, int);
|
||||
static enum prompt_result cmd_command_prompt_callback(struct client *, void *,
|
||||
const char *, enum prompt_key_result);
|
||||
static void cmd_command_prompt_free(void *);
|
||||
|
||||
const struct cmd_entry cmd_command_prompt_entry = {
|
||||
.name = "command-prompt",
|
||||
.alias = NULL,
|
||||
|
||||
.args = { "1CbeFiklI:Np:t:T:", 0, 1, cmd_command_prompt_args_parse },
|
||||
.usage = "[-1CbeFiklN] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE
|
||||
.args = { "1CbeFiklI:NPp:t:T:", 0, 1, cmd_command_prompt_args_parse },
|
||||
.usage = "[-1CbeFiklNP] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE
|
||||
" [-T prompt-type] [template]",
|
||||
|
||||
.flags = CMD_CLIENT_TFLAG,
|
||||
@@ -62,6 +62,8 @@ struct cmd_command_prompt_cdata {
|
||||
int flags;
|
||||
enum prompt_type prompt_type;
|
||||
|
||||
struct window_pane *wp;
|
||||
|
||||
struct cmd_command_prompt_prompt *prompts;
|
||||
u_int count;
|
||||
u_int current;
|
||||
@@ -87,10 +89,15 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item)
|
||||
struct cmd_command_prompt_cdata *cdata;
|
||||
char *tmp, *prompts, *prompt, *next_prompt;
|
||||
char *inputs = NULL, *next_input;
|
||||
struct window_pane *wp = target->wp;
|
||||
u_int count = args_count(args);
|
||||
int wait = !args_has(args, 'b'), space = 1;
|
||||
int pane = args_has(args, 'P');
|
||||
|
||||
if (tc->prompt_string != NULL)
|
||||
if (pane) {
|
||||
if (wp == NULL || window_pane_has_prompt(wp))
|
||||
return (CMD_RETURN_NORMAL);
|
||||
} else if (tc->prompt != NULL)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
if (args_has(args, 'i'))
|
||||
wait = 0;
|
||||
@@ -98,6 +105,8 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item)
|
||||
cdata = xcalloc(1, sizeof *cdata);
|
||||
if (wait)
|
||||
cdata->item = item;
|
||||
if (pane)
|
||||
cdata->wp = wp;
|
||||
cdata->state = args_make_commands_prepare(self, item, 0, "%1", wait,
|
||||
args_has(args, 'F'));
|
||||
|
||||
@@ -146,7 +155,7 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item)
|
||||
}
|
||||
|
||||
if ((type = args_get(args, 'T')) != NULL) {
|
||||
cdata->prompt_type = status_prompt_type(type);
|
||||
cdata->prompt_type = prompt_type(type);
|
||||
if (cdata->prompt_type == PROMPT_TYPE_INVALID) {
|
||||
cmdq_error(item, "unknown type: %s", type);
|
||||
cmd_command_prompt_free(cdata);
|
||||
@@ -167,18 +176,27 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item)
|
||||
cdata->flags |= PROMPT_BSPACE_EXIT;
|
||||
if (args_has(args, 'C'))
|
||||
cdata->flags |= PROMPT_NOFREEZE;
|
||||
status_prompt_set(tc, target, cdata->prompts[0].prompt,
|
||||
cdata->prompts[0].input, cmd_command_prompt_callback,
|
||||
cmd_command_prompt_free, cdata, cdata->flags, cdata->prompt_type);
|
||||
if (pane) {
|
||||
cdata->flags |= PROMPT_ISPANE;
|
||||
window_pane_set_prompt(wp, tc, target, cdata->prompts[0].prompt,
|
||||
cdata->prompts[0].input, cmd_command_prompt_callback,
|
||||
cmd_command_prompt_free, cdata, cdata->flags,
|
||||
cdata->prompt_type);
|
||||
} else {
|
||||
status_prompt_set(tc, target, cdata->prompts[0].prompt,
|
||||
cdata->prompts[0].input, cmd_command_prompt_callback,
|
||||
cmd_command_prompt_free, cdata, cdata->flags,
|
||||
cdata->prompt_type);
|
||||
}
|
||||
|
||||
if (!wait)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
return (CMD_RETURN_WAIT);
|
||||
}
|
||||
|
||||
static int
|
||||
static enum prompt_result
|
||||
cmd_command_prompt_callback(struct client *c, void *data, const char *s,
|
||||
int done)
|
||||
enum prompt_key_result key)
|
||||
{
|
||||
struct cmd_command_prompt_cdata *cdata = data;
|
||||
char *error;
|
||||
@@ -188,26 +206,30 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s,
|
||||
int argc = 0;
|
||||
char **argv = NULL;
|
||||
|
||||
if (s == NULL)
|
||||
if (s == NULL || key == PROMPT_KEY_MOVE)
|
||||
goto out;
|
||||
|
||||
if (done) {
|
||||
if (key == PROMPT_KEY_CLOSE) {
|
||||
if (cdata->flags & PROMPT_INCREMENTAL)
|
||||
goto out;
|
||||
cmd_append_argv(&cdata->argc, &cdata->argv, s);
|
||||
if (++cdata->current != cdata->count) {
|
||||
prompt = &cdata->prompts[cdata->current];
|
||||
status_prompt_update(c, prompt->prompt, prompt->input);
|
||||
return (1);
|
||||
if (cdata->wp != NULL) {
|
||||
window_pane_update_prompt(cdata->wp,
|
||||
prompt->prompt, prompt->input);
|
||||
} else
|
||||
status_prompt_update(c, prompt->prompt,
|
||||
prompt->input);
|
||||
return (PROMPT_CONTINUE);
|
||||
}
|
||||
}
|
||||
|
||||
argc = cdata->argc;
|
||||
argv = cmd_copy_argv(cdata->argc, cdata->argv);
|
||||
if (!done)
|
||||
if (key != PROMPT_KEY_CLOSE)
|
||||
cmd_append_argv(&argc, &argv, s);
|
||||
|
||||
if (done) {
|
||||
else {
|
||||
cmd_free_argv(cdata->argc, cdata->argv);
|
||||
cdata->argc = argc;
|
||||
cdata->argv = cmd_copy_argv(argc, argv);
|
||||
@@ -226,13 +248,20 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s,
|
||||
}
|
||||
cmd_free_argv(argc, argv);
|
||||
|
||||
if (c->prompt_inputcb != cmd_command_prompt_callback)
|
||||
return (1);
|
||||
/*
|
||||
* An incremental prompt fires its callback on every edit but must stay
|
||||
* open for further typing; only an explicit close (handled above) ends
|
||||
* it.
|
||||
*/
|
||||
if (cdata->flags & PROMPT_INCREMENTAL)
|
||||
return (PROMPT_CONTINUE);
|
||||
|
||||
out:
|
||||
if (item != NULL)
|
||||
if (item != NULL) {
|
||||
cdata->item = NULL;
|
||||
cmdq_continue(item);
|
||||
return (0);
|
||||
}
|
||||
return (PROMPT_CLOSE);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -241,6 +270,11 @@ cmd_command_prompt_free(void *data)
|
||||
struct cmd_command_prompt_cdata *cdata = data;
|
||||
u_int i;
|
||||
|
||||
if (cdata->item != NULL) {
|
||||
cmdq_continue(cdata->item);
|
||||
cdata->item = NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < cdata->count; i++) {
|
||||
free(cdata->prompts[i].prompt);
|
||||
free(cdata->prompts[i].input);
|
||||
|
||||
@@ -33,8 +33,8 @@ static enum args_parse_type cmd_confirm_before_args_parse(struct args *,
|
||||
static enum cmd_retval cmd_confirm_before_exec(struct cmd *,
|
||||
struct cmdq_item *);
|
||||
|
||||
static int cmd_confirm_before_callback(struct client *, void *,
|
||||
const char *, int);
|
||||
static enum prompt_result cmd_confirm_before_callback(struct client *, void *,
|
||||
const char *, enum prompt_key_result);
|
||||
static void cmd_confirm_before_free(void *);
|
||||
|
||||
const struct cmd_entry cmd_confirm_before_entry = {
|
||||
@@ -118,9 +118,9 @@ cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item)
|
||||
return (CMD_RETURN_WAIT);
|
||||
}
|
||||
|
||||
static int
|
||||
static enum prompt_result
|
||||
cmd_confirm_before_callback(struct client *c, void *data, const char *s,
|
||||
__unused int done)
|
||||
__unused enum prompt_key_result key)
|
||||
{
|
||||
struct cmd_confirm_before_data *cdata = data;
|
||||
struct cmdq_item *item = cdata->item, *new_item;
|
||||
@@ -151,7 +151,7 @@ out:
|
||||
cmdq_get_client(item)->retval = retcode;
|
||||
cmdq_continue(item);
|
||||
}
|
||||
return (0);
|
||||
return (PROMPT_CLOSE);
|
||||
}
|
||||
|
||||
static void
|
||||
|
||||
@@ -48,6 +48,16 @@ struct cmd_display_panes_data {
|
||||
struct args_command_state *state;
|
||||
};
|
||||
|
||||
struct cmd_display_panes_ctx {
|
||||
struct client *c;
|
||||
int ox;
|
||||
int oy;
|
||||
u_int sx;
|
||||
u_int sy;
|
||||
u_int statuslines;
|
||||
int statustop;
|
||||
};
|
||||
|
||||
static enum args_parse_type
|
||||
cmd_display_panes_args_parse(__unused struct args *args, __unused u_int idx,
|
||||
__unused char **cause)
|
||||
@@ -56,7 +66,7 @@ cmd_display_panes_args_parse(__unused struct args *args, __unused u_int idx,
|
||||
}
|
||||
|
||||
static void
|
||||
cmd_display_panes_put(struct screen_redraw_ctx *ctx,
|
||||
cmd_display_panes_put(struct cmd_display_panes_ctx *ctx,
|
||||
struct window_pane *wp, u_int cx, u_int cy, const char *buf, size_t len)
|
||||
{
|
||||
struct client *c = ctx->c;
|
||||
@@ -65,8 +75,7 @@ cmd_display_panes_put(struct screen_redraw_ctx *ctx,
|
||||
struct visible_range *ri;
|
||||
u_int i, j;
|
||||
|
||||
r = screen_redraw_get_visible_ranges(wp, ctx->ox + cx, ctx->oy + cy,
|
||||
len, NULL);
|
||||
r = window_visible_ranges(wp, ctx->ox + cx, ctx->oy + cy, len, NULL);
|
||||
for (i = 0; i < r->used; i++) {
|
||||
ri = &r->ranges[i];
|
||||
for (j = ri->px; j < ri->px + ri->nx; j++) {
|
||||
@@ -77,7 +86,43 @@ cmd_display_panes_put(struct screen_redraw_ctx *ctx,
|
||||
}
|
||||
|
||||
static void
|
||||
cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx,
|
||||
cmd_display_panes_draw_format(struct cmd_display_panes_ctx *ctx,
|
||||
struct window_pane *wp, u_int xoff, u_int yoff, u_int sx,
|
||||
const struct grid_cell *gc)
|
||||
{
|
||||
struct client *c = ctx->c;
|
||||
struct tty *tty = &c->tty;
|
||||
struct session *s = c->session;
|
||||
struct screen *sc = wp->screen, screen;
|
||||
struct screen_write_ctx sctx;
|
||||
struct visible_ranges *r;
|
||||
struct visible_range *ri;
|
||||
const char *format;
|
||||
char *expanded;
|
||||
u_int i, px = ctx->ox + xoff;
|
||||
|
||||
format = options_get_string(s->options, "display-panes-format");
|
||||
expanded = format_single(NULL, format, c, s, s->curw, wp);
|
||||
|
||||
screen_init(&screen, sx, 1, 0);
|
||||
screen_write_start(&sctx, &screen);
|
||||
screen_write_fast_copy(&sctx, sc, 0, sc->grid->hsize, sx, 1);
|
||||
screen_write_cursormove(&sctx, 0, 0, 0);
|
||||
format_draw(&sctx, gc, sx, expanded, NULL, 0);
|
||||
screen_write_stop(&sctx);
|
||||
free(expanded);
|
||||
|
||||
r = window_visible_ranges(wp, px, wp->yoff, sx, NULL);
|
||||
for (i = 0; i < r->used; i++) {
|
||||
ri = &r->ranges[i];
|
||||
tty_draw_line(tty, &screen, ri->px - px, 0, ri->nx,
|
||||
ri->px - ctx->ox, yoff, NULL);
|
||||
}
|
||||
screen_free(&screen);
|
||||
}
|
||||
|
||||
static void
|
||||
cmd_display_panes_draw_pane(struct cmd_display_panes_ctx *ctx,
|
||||
struct window_pane *wp)
|
||||
{
|
||||
struct client *c = ctx->c;
|
||||
@@ -88,9 +133,10 @@ cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx,
|
||||
struct grid_cell fgc, bgc;
|
||||
u_int pane, idx, px, py, i, j, xoff, yoff, sx, sy;
|
||||
u_int cx, cy;
|
||||
int colour, active_colour;
|
||||
char buf[16], lbuf[16], rbuf[16], *ptr;
|
||||
size_t len, llen, rlen;
|
||||
const char *name;
|
||||
struct format_tree *ft;
|
||||
char buf[16], lbuf[16], *ptr;
|
||||
size_t len, llen;
|
||||
|
||||
if (wp->xoff + (int)wp->sx <= ctx->ox ||
|
||||
wp->xoff >= ctx->ox + (int)ctx->sx ||
|
||||
@@ -146,27 +192,24 @@ cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx,
|
||||
|
||||
if (sx < len)
|
||||
return;
|
||||
colour = options_get_number(oo, "display-panes-colour");
|
||||
active_colour = options_get_number(oo, "display-panes-active-colour");
|
||||
if (w->active == wp)
|
||||
name = "display-panes-active-colour";
|
||||
else
|
||||
name = "display-panes-colour";
|
||||
ft = format_create_defaults(NULL, c, s, NULL, wp);
|
||||
style_apply(&fgc, oo, name, ft);
|
||||
format_free(ft);
|
||||
|
||||
memcpy(&fgc, &grid_default_cell, sizeof fgc);
|
||||
memcpy(&bgc, &grid_default_cell, sizeof bgc);
|
||||
if (w->active == wp) {
|
||||
fgc.fg = active_colour;
|
||||
bgc.bg = active_colour;
|
||||
} else {
|
||||
fgc.fg = colour;
|
||||
bgc.bg = colour;
|
||||
}
|
||||
bgc.bg = fgc.fg;
|
||||
|
||||
rlen = xsnprintf(rbuf, sizeof rbuf, "%ux%u", wp->sx, wp->sy);
|
||||
if (pane > 9 && pane < 35)
|
||||
llen = xsnprintf(lbuf, sizeof lbuf, "%c", 'a' + (pane - 10));
|
||||
else
|
||||
llen = 0;
|
||||
|
||||
if (sx < len * 6 || sy < 5) {
|
||||
tty_attributes(tty, &fgc, &grid_default_cell, NULL, NULL);
|
||||
tty_attributes(tty, &fgc, NULL);
|
||||
if (sx >= len + llen + 1) {
|
||||
len += llen + 1;
|
||||
cx = xoff + px - len / 2;
|
||||
@@ -187,7 +230,7 @@ cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx,
|
||||
px -= len * 3;
|
||||
py -= 2;
|
||||
|
||||
tty_attributes(tty, &bgc, &grid_default_cell, NULL, NULL);
|
||||
tty_attributes(tty, &bgc, NULL);
|
||||
for (ptr = buf; *ptr != '\0'; ptr++) {
|
||||
if (*ptr < '0' || *ptr > '9')
|
||||
continue;
|
||||
@@ -207,13 +250,9 @@ cmd_display_panes_draw_pane(struct screen_redraw_ctx *ctx,
|
||||
|
||||
if (sy <= 6)
|
||||
goto out;
|
||||
tty_attributes(tty, &fgc, &grid_default_cell, NULL, NULL);
|
||||
if (rlen != 0 && sx >= rlen) {
|
||||
cx = xoff + sx - rlen;
|
||||
cy = yoff;
|
||||
cmd_display_panes_put(ctx, wp, cx, cy, rbuf, rlen);
|
||||
}
|
||||
cmd_display_panes_draw_format(ctx, wp, xoff, yoff, sx, &fgc);
|
||||
if (llen != 0) {
|
||||
tty_attributes(tty, &fgc, NULL);
|
||||
cx = xoff + sx / 2 + len * 3 - llen - 1;
|
||||
cy = yoff + py + 5;
|
||||
cmd_display_panes_put(ctx, wp, cx, cy, lbuf, llen);
|
||||
@@ -224,17 +263,30 @@ out:
|
||||
}
|
||||
|
||||
static void
|
||||
cmd_display_panes_draw(struct client *c, __unused void *data,
|
||||
struct screen_redraw_ctx *ctx)
|
||||
cmd_display_panes_draw(struct client *c, __unused void *data)
|
||||
{
|
||||
struct window *w = c->session->curw->window;
|
||||
struct window_pane *wp;
|
||||
struct session *s = c->session;
|
||||
struct window *w = s->curw->window;
|
||||
struct window_pane *wp;
|
||||
struct cmd_display_panes_ctx ctx;
|
||||
u_int lines;
|
||||
|
||||
log_debug("%s: %s @%u", __func__, c->name, w->id);
|
||||
|
||||
memset(&ctx, 0, sizeof ctx);
|
||||
ctx.c = c;
|
||||
tty_window_offset(&c->tty, &ctx.ox, &ctx.oy, &ctx.sx, &ctx.sy);
|
||||
if (options_get_number(s->options, "status-position") == 0) {
|
||||
lines = status_line_size(c);
|
||||
if (c->message_string != NULL || c->prompt != NULL)
|
||||
lines = (lines == 0 ? 1 : lines);
|
||||
ctx.statuslines = lines;
|
||||
ctx.statustop = 1;
|
||||
}
|
||||
|
||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||
if (window_pane_visible(wp))
|
||||
cmd_display_panes_draw_pane(ctx, wp);
|
||||
if (window_pane_is_visible(wp))
|
||||
cmd_display_panes_draw_pane(&ctx, wp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
27
cmd-find.c
27
cmd-find.c
@@ -147,6 +147,18 @@ cmd_find_session_better(struct session *s, struct session *than, int flags)
|
||||
return (timercmp(&s->activity_time, &than->activity_time, >));
|
||||
}
|
||||
|
||||
/* Can this session be usefully targeted? */
|
||||
static int
|
||||
cmd_find_session_valid(struct session *s)
|
||||
{
|
||||
if (!session_alive(s) ||
|
||||
s->curw == NULL ||
|
||||
s->curw->window == NULL ||
|
||||
s->curw->window->active == NULL)
|
||||
return (0);
|
||||
return (1);
|
||||
}
|
||||
|
||||
/* Find best session from a list, or all if list is NULL. */
|
||||
static struct session *
|
||||
cmd_find_best_session(struct session **slist, u_int ssize, int flags)
|
||||
@@ -159,11 +171,15 @@ cmd_find_best_session(struct session **slist, u_int ssize, int flags)
|
||||
s = NULL;
|
||||
if (slist != NULL) {
|
||||
for (i = 0; i < ssize; i++) {
|
||||
if (!cmd_find_session_valid(slist[i]))
|
||||
continue;
|
||||
if (cmd_find_session_better(slist[i], s, flags))
|
||||
s = slist[i];
|
||||
}
|
||||
} else {
|
||||
RB_FOREACH(s_loop, sessions, &sessions) {
|
||||
if (!cmd_find_session_valid(s_loop))
|
||||
continue;
|
||||
if (cmd_find_session_better(s_loop, s, flags))
|
||||
s = s_loop;
|
||||
}
|
||||
@@ -978,15 +994,20 @@ cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item,
|
||||
} else if (cmd_find_from_client(¤t, cmdq_get_client(item),
|
||||
flags) == 0) {
|
||||
fs->current = ¤t;
|
||||
/* No active pane, window empty, return the window instead. */
|
||||
if (current.wp == NULL) {
|
||||
type = CMD_FIND_WINDOW;
|
||||
}
|
||||
log_debug("%s: current is from client", __func__);
|
||||
} else {
|
||||
if (~flags & CMD_FIND_QUIET)
|
||||
cmdq_error(item, "no current target");
|
||||
goto error;
|
||||
}
|
||||
/*
|
||||
if (!cmd_find_valid_state(fs->current))
|
||||
fatalx("invalid current find state");
|
||||
|
||||
*/
|
||||
/* An empty or NULL target is the current. */
|
||||
if (target == NULL || *target == '\0')
|
||||
goto current;
|
||||
@@ -995,7 +1016,7 @@ cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item,
|
||||
strcmp(target, "{active}") == 0 ||
|
||||
strcmp(target, "{current}") == 0) {
|
||||
c = cmdq_get_client(item);
|
||||
if (c == NULL) {
|
||||
if (c == NULL || c->session == NULL) {
|
||||
cmdq_error(item, "no current client");
|
||||
goto error;
|
||||
}
|
||||
@@ -1025,7 +1046,7 @@ cmd_find_target(struct cmd_find_state *fs, struct cmdq_item *item,
|
||||
fs->w = fs->wl->window;
|
||||
fs->wp = fs->w->active;
|
||||
}
|
||||
break;
|
||||
goto found;
|
||||
}
|
||||
if (fs->wp == NULL) {
|
||||
if (~flags & CMD_FIND_QUIET)
|
||||
|
||||
213
cmd-hide-pane.c
Normal file
213
cmd-hide-pane.c
Normal file
@@ -0,0 +1,213 @@
|
||||
/* $OpenBSD$ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 Michael Grant <mgrant@gmail.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
||||
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "tmux.h"
|
||||
|
||||
/*
|
||||
* Hide or show panes.
|
||||
*/
|
||||
|
||||
static enum cmd_retval cmd_hide_pane_exec(struct cmd *, struct cmdq_item *);
|
||||
static enum cmd_retval cmd_show_pane_exec(struct cmd *, struct cmdq_item *);
|
||||
|
||||
const struct cmd_entry cmd_hide_pane_entry = {
|
||||
.name = "hide-pane",
|
||||
.alias = "hidep",
|
||||
|
||||
.args = { "at:", 0, 1, NULL },
|
||||
.usage = "[-a] " CMD_TARGET_PANE_USAGE,
|
||||
|
||||
.target = { 't', CMD_FIND_PANE, 0 },
|
||||
|
||||
.flags = CMD_AFTERHOOK,
|
||||
.exec = cmd_hide_pane_exec
|
||||
};
|
||||
|
||||
const struct cmd_entry cmd_show_pane_entry = {
|
||||
.name = "show-pane",
|
||||
.alias = "showp",
|
||||
|
||||
.args = { "at:", 0, 1, NULL },
|
||||
.usage = "[-a] " CMD_TARGET_PANE_USAGE,
|
||||
|
||||
.target = { 't', CMD_FIND_PANE, 0 },
|
||||
|
||||
.flags = CMD_AFTERHOOK,
|
||||
.exec = cmd_show_pane_exec
|
||||
};
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_hide_pane_hide(struct window *w, struct window_pane *wp)
|
||||
{
|
||||
struct window_pane *wpp = NULL;
|
||||
struct layout_cell *lc = wp->layout_cell;
|
||||
|
||||
if (window_pane_is_hidden(wp))
|
||||
return (CMD_RETURN_NORMAL);
|
||||
|
||||
if (wp == w->active) {
|
||||
/* Find previous active pane. */
|
||||
TAILQ_FOREACH(wpp, &w->last_panes, sentry) {
|
||||
if (wpp != wp && window_pane_is_visible(wpp))
|
||||
break;
|
||||
}
|
||||
if (wpp == NULL) {
|
||||
TAILQ_FOREACH(wpp, &w->z_index, zentry) {
|
||||
if (wpp != wp &&
|
||||
window_pane_is_visible(wpp))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lc->flags |= LAYOUT_CELL_HIDDEN;
|
||||
|
||||
layout_remove_tile(w, lc);
|
||||
layout_fix_offsets(w);
|
||||
layout_fix_panes(w, NULL);
|
||||
|
||||
window_pane_stack_remove(&w->last_panes, wp);
|
||||
if (wpp != NULL) {
|
||||
window_set_active_pane(w, wpp, 1);
|
||||
} else if (wp == w->active) {
|
||||
/* No visible previous active pane; null active pane
|
||||
* to show dots background. */
|
||||
w->active = NULL;
|
||||
if (options_get_number(global_options, "focus-events"))
|
||||
window_pane_update_focus(wp);
|
||||
notify_window("window-pane-changed", w);
|
||||
notify_window("window-layout-changed", w);
|
||||
server_redraw_window(w);
|
||||
} else {
|
||||
notify_window("window-layout-changed", w);
|
||||
server_redraw_window(w);
|
||||
}
|
||||
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_hide_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
{
|
||||
struct args *args = cmd_get_args(self);
|
||||
struct cmd_find_state *target = cmdq_get_target(item);
|
||||
struct winlink *wl = target->wl;
|
||||
struct window *w = wl->window;
|
||||
struct window_pane *wp, *active_pane = w->active;
|
||||
u_int id;
|
||||
char *cause = NULL;
|
||||
enum cmd_retval rv;
|
||||
|
||||
window_unzoom(w, 1);
|
||||
|
||||
if (args_has(args, 'a')) {
|
||||
TAILQ_FOREACH(wp, &w->z_index, zentry) {
|
||||
if (!window_pane_is_visible(wp) || wp == active_pane)
|
||||
continue;
|
||||
rv = cmd_hide_pane_hide(w, wp);
|
||||
if (rv != CMD_RETURN_NORMAL)
|
||||
return (rv);
|
||||
}
|
||||
return (CMD_RETURN_NORMAL);
|
||||
} else {
|
||||
wp = target->wp;
|
||||
if (wp == NULL) {
|
||||
id = args_strtonum_and_expand(args, 't', 0, INT_MAX,
|
||||
item, &cause);
|
||||
if (cause != NULL) {
|
||||
cmdq_error(item, "%s target pane", cause);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
wp = window_pane_find_by_id(id);
|
||||
}
|
||||
if (wp == NULL) {
|
||||
cmdq_error(item, "No target pane to hide.");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
return (cmd_hide_pane_hide(w, wp));
|
||||
}
|
||||
}
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_hide_pane_show(struct window *w, struct window_pane *wp)
|
||||
{
|
||||
struct layout_cell *lc = wp->layout_cell;
|
||||
|
||||
if (!window_pane_is_hidden(wp))
|
||||
return (CMD_RETURN_NORMAL);
|
||||
|
||||
lc->flags &= ~LAYOUT_CELL_HIDDEN;
|
||||
|
||||
layout_insert_tile(w, lc);
|
||||
layout_fix_offsets(w);
|
||||
layout_fix_panes(w, NULL);
|
||||
|
||||
window_set_active_pane(w, wp, 1);
|
||||
|
||||
notify_window("window-layout-changed", w);
|
||||
server_redraw_window(w);
|
||||
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_show_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
{
|
||||
struct args *args = cmd_get_args(self);
|
||||
struct cmd_find_state *target = cmdq_get_target(item);
|
||||
struct winlink *wl = target->wl;
|
||||
struct window *w = wl->window;
|
||||
struct window_pane *wp;
|
||||
u_int id;
|
||||
char *cause = NULL;
|
||||
enum cmd_retval rv;
|
||||
|
||||
window_unzoom(w, 1);
|
||||
|
||||
if (args_has(args, 'a')) {
|
||||
TAILQ_FOREACH(wp, &w->z_index, zentry) {
|
||||
if (!window_pane_is_visible(wp))
|
||||
continue;
|
||||
rv = cmd_hide_pane_show(w, wp);
|
||||
if (rv != CMD_RETURN_NORMAL)
|
||||
return (rv);
|
||||
}
|
||||
return (CMD_RETURN_NORMAL);
|
||||
} else {
|
||||
wp = target->wp;
|
||||
if (wp == NULL) {
|
||||
id = args_strtonum_and_expand(args, 't', 0, INT_MAX,
|
||||
item, &cause);
|
||||
if (cause != NULL) {
|
||||
cmdq_error(item, "%s target pane", cause);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
wp = window_pane_find_by_id(id);
|
||||
}
|
||||
if (wp == NULL) {
|
||||
cmdq_error(item, "No target pane to show.");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
return (cmd_hide_pane_show(w, wp));
|
||||
}
|
||||
}
|
||||
380
cmd-join-pane.c
380
cmd-join-pane.c
@@ -30,6 +30,9 @@
|
||||
*/
|
||||
|
||||
static enum cmd_retval cmd_join_pane_exec(struct cmd *, struct cmdq_item *);
|
||||
static enum cmd_retval cmd_join_pane_mouse_update(struct cmdq_item *);
|
||||
static void cmd_join_pane_mouse_move(struct client *,
|
||||
struct mouse_event *);
|
||||
|
||||
const struct cmd_entry cmd_join_pane_entry = {
|
||||
.name = "join-pane",
|
||||
@@ -49,8 +52,10 @@ const struct cmd_entry cmd_move_pane_entry = {
|
||||
.name = "move-pane",
|
||||
.alias = "movep",
|
||||
|
||||
.args = { "bdfhvp:l:s:t:", 0, 0, NULL },
|
||||
.usage = "[-bdfhv] [-l size] " CMD_SRCDST_PANE_USAGE,
|
||||
.args = { "bdD::fhMvl:L::P:R::s:t:U::X:Y:z:", 0, 0, NULL },
|
||||
.usage = "[-bdfhMv] [-D lines] [-l size] [-L columns] [-P position] "
|
||||
"[-R columns] " CMD_SRCDST_PANE_USAGE " [-U lines] "
|
||||
"[-X x-position] [-Y y-position] [-z z-index]",
|
||||
|
||||
.source = { 's', CMD_FIND_PANE, CMD_FIND_DEFAULT_MARKED },
|
||||
.target = { 't', CMD_FIND_PANE, 0 },
|
||||
@@ -59,6 +64,344 @@ const struct cmd_entry cmd_move_pane_entry = {
|
||||
.exec = cmd_join_pane_exec
|
||||
};
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_join_pane_place(struct cmdq_item *item, struct winlink *wl,
|
||||
struct window_pane *wp, const char *position)
|
||||
{
|
||||
struct window *w = wl->window;
|
||||
struct layout_cell *lc = wp->layout_cell;
|
||||
struct window_pane *owp;
|
||||
int wx = w->sx, wy = w->sy, px = lc->sx;
|
||||
int py = lc->sy, xoff = lc->xoff, yoff = lc->yoff;
|
||||
int border = 1;
|
||||
|
||||
if (window_pane_get_pane_lines(wp) == PANE_LINES_NONE)
|
||||
border = 0;
|
||||
|
||||
if (strcmp(position, "top-left") == 0) {
|
||||
xoff = border;
|
||||
yoff = border;
|
||||
} else if (strcmp(position, "top-centre") == 0 ||
|
||||
strcmp(position, "top-center") == 0) {
|
||||
xoff = (wx - px) / 2;
|
||||
yoff = border;
|
||||
} else if (strcmp(position, "top-right") == 0) {
|
||||
xoff = wx - px - border;
|
||||
yoff = border;
|
||||
} else if (strcmp(position, "centre-left") == 0 ||
|
||||
strcmp(position, "center-left") == 0) {
|
||||
xoff = border;
|
||||
yoff = (wy - py) / 2;
|
||||
} else if (strcmp(position, "centre") == 0 ||
|
||||
strcmp(position, "center") == 0) {
|
||||
xoff = (wx - px) / 2;
|
||||
yoff = (wy - py) / 2;
|
||||
} else if (strcmp(position, "centre-right") == 0 ||
|
||||
strcmp(position, "center-right") == 0) {
|
||||
xoff = wx - px - border;
|
||||
yoff = (wy - py) / 2;
|
||||
} else if (strcmp(position, "bottom-left") == 0) {
|
||||
xoff = border;
|
||||
yoff = wy - py - border;
|
||||
} else if (strcmp(position, "bottom-centre") == 0 ||
|
||||
strcmp(position, "bottom-center") == 0) {
|
||||
xoff = (wx - px) / 2;
|
||||
yoff = wy - py - border;
|
||||
} else if (strcmp(position, "bottom-right") == 0) {
|
||||
xoff = wx - px - border;
|
||||
yoff = wy - py - border;
|
||||
} else if (strcmp(position, "top-left-centre") == 0 ||
|
||||
strcmp(position, "top-left-center") == 0) {
|
||||
xoff = wx / 4 - px / 2;
|
||||
yoff = wy / 4 - py / 2;
|
||||
} else if (strcmp(position, "top-right-centre") == 0 ||
|
||||
strcmp(position, "top-right-center") == 0) {
|
||||
xoff = (3 * wx) / 4 - px / 2;
|
||||
yoff = wy / 4 - py / 2;
|
||||
} else if (strcmp(position, "bottom-left-centre") == 0 ||
|
||||
strcmp(position, "bottom-left-center") == 0) {
|
||||
xoff = wx / 4 - px / 2;
|
||||
yoff = (3 * wy) / 4 - py / 2;
|
||||
} else if (strcmp(position, "bottom-right-centre") == 0 ||
|
||||
strcmp(position, "bottom-right-center") == 0) {
|
||||
xoff = (3 * wx) / 4 - px / 2;
|
||||
yoff = (3 * wy) / 4 - py / 2;
|
||||
} else if (strcmp(position, "front") == 0) {
|
||||
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
||||
TAILQ_INSERT_HEAD(&w->z_index, wp, zentry);
|
||||
} else if (strcmp(position, "back") == 0) {
|
||||
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
||||
TAILQ_FOREACH(owp, &w->z_index, zentry) {
|
||||
if (!window_pane_is_floating(owp))
|
||||
break;
|
||||
}
|
||||
if (owp != NULL)
|
||||
TAILQ_INSERT_BEFORE(owp, wp, zentry);
|
||||
else
|
||||
TAILQ_INSERT_TAIL(&w->z_index, wp, zentry);
|
||||
} else if (strcmp(position, "forward") == 0) {
|
||||
owp = TAILQ_PREV(wp, window_panes_zindex, zentry);
|
||||
if (owp != NULL) {
|
||||
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
||||
TAILQ_INSERT_BEFORE(owp, wp, zentry);
|
||||
}
|
||||
} else if (strcmp(position, "backward") == 0) {
|
||||
owp = TAILQ_NEXT(wp, zentry);
|
||||
if (owp != NULL && window_pane_is_floating(owp)) {
|
||||
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
||||
TAILQ_INSERT_AFTER(&w->z_index, owp, wp, zentry);
|
||||
}
|
||||
} else if (strcmp(position, "forward-loop") == 0) {
|
||||
owp = TAILQ_PREV(wp, window_panes_zindex, zentry);
|
||||
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
||||
if (owp != NULL)
|
||||
TAILQ_INSERT_BEFORE(owp, wp, zentry);
|
||||
else {
|
||||
TAILQ_FOREACH(owp, &w->z_index, zentry) {
|
||||
if (!window_pane_is_floating(owp))
|
||||
break;
|
||||
}
|
||||
if (owp != NULL)
|
||||
TAILQ_INSERT_BEFORE(owp, wp, zentry);
|
||||
else
|
||||
TAILQ_INSERT_TAIL(&w->z_index, wp, zentry);
|
||||
}
|
||||
} else if (strcmp(position, "backward-loop") == 0) {
|
||||
owp = TAILQ_NEXT(wp, zentry);
|
||||
if (owp != NULL && window_pane_is_floating(owp)) {
|
||||
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
||||
TAILQ_INSERT_AFTER(&w->z_index, owp, wp, zentry);
|
||||
} else {
|
||||
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
||||
TAILQ_INSERT_HEAD(&w->z_index, wp, zentry);
|
||||
}
|
||||
} else {
|
||||
cmdq_error(item, "unknown position: %s", position);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
|
||||
if (xoff != lc->xoff || yoff != lc->yoff) {
|
||||
lc->xoff = xoff;
|
||||
lc->yoff = yoff;
|
||||
layout_fix_panes(w, NULL);
|
||||
}
|
||||
notify_window("window-layout-changed", w);
|
||||
server_redraw_window(w);
|
||||
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_join_pane_move(struct cmdq_item *item, struct args *args,
|
||||
struct winlink *wl, struct window_pane *wp)
|
||||
{
|
||||
struct window *w = wl->window;
|
||||
struct layout_cell *lc = wp->layout_cell;
|
||||
const char *errstr, *argval;
|
||||
const char flags[] = { 'U', 'D', 'L', 'R' };
|
||||
char *cause = NULL, flag;
|
||||
int xoff = lc->xoff, yoff = lc->yoff, adjust;
|
||||
u_int i;
|
||||
enum pane_lines lines = window_pane_get_pane_lines(wp);
|
||||
|
||||
if (args_has(args, 'X')) {
|
||||
xoff = args_percentage_and_expand(args, 'X', -(int)w->sx,
|
||||
w->sx, w->sx, item, &cause);
|
||||
if (cause != NULL) {
|
||||
cmdq_error(item, "position %s", cause);
|
||||
free(cause);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
if (lines != PANE_LINES_NONE)
|
||||
xoff += 1;
|
||||
}
|
||||
if (args_has(args, 'Y')) {
|
||||
yoff = args_percentage_and_expand(args, 'Y', -(int)w->sy,
|
||||
w->sy, w->sy, item, &cause);
|
||||
if (cause != NULL) {
|
||||
cmdq_error(item, "position %s", cause);
|
||||
free(cause);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
if (lines != PANE_LINES_NONE)
|
||||
yoff += 1;
|
||||
}
|
||||
|
||||
for (i = 0; i < nitems(flags); i++) {
|
||||
flag = flags[i];
|
||||
if (!args_has(args, flag))
|
||||
continue;
|
||||
|
||||
argval = args_get(args, flag);
|
||||
if (argval == NULL)
|
||||
argval = "1";
|
||||
adjust = strtonum(argval, INT_MIN, INT_MAX, &errstr);
|
||||
if (errstr != NULL) {
|
||||
cmdq_error(item, "offset %s", errstr);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
|
||||
if (flag == 'U')
|
||||
yoff -= adjust;
|
||||
else if (flag == 'D')
|
||||
yoff += adjust;
|
||||
else if (flag == 'L')
|
||||
xoff -= adjust;
|
||||
else
|
||||
xoff += adjust;
|
||||
}
|
||||
|
||||
if (xoff != lc->xoff || yoff != lc->yoff) {
|
||||
lc->xoff = xoff;
|
||||
lc->yoff = yoff;
|
||||
layout_fix_panes(w, NULL);
|
||||
notify_window("window-layout-changed", w);
|
||||
server_redraw_window(w);
|
||||
}
|
||||
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_join_pane_mouse_update(struct cmdq_item *item)
|
||||
{
|
||||
struct cmd_find_state *target = cmdq_get_target(item);
|
||||
struct key_event *event = cmdq_get_event(item);
|
||||
struct client *c = cmdq_get_client(item);
|
||||
struct session *s = target->s;
|
||||
struct winlink *wl;
|
||||
struct window *w;
|
||||
struct window_pane *wp;
|
||||
|
||||
if (!event->m.valid)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
wp = cmd_mouse_pane(&event->m, &s, &wl);
|
||||
if (wp == NULL || c == NULL || c->session != s)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
if (!window_pane_is_floating(wp))
|
||||
return (CMD_RETURN_NORMAL);
|
||||
|
||||
w = wl->window;
|
||||
window_redraw_active_switch(w, wp);
|
||||
window_set_active_pane(w, wp, 1);
|
||||
|
||||
c->tty.mouse_drag_update = cmd_join_pane_mouse_move;
|
||||
cmd_join_pane_mouse_move(c, &event->m);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
static void
|
||||
cmd_join_pane_mouse_move(struct client *c, struct mouse_event *m)
|
||||
{
|
||||
struct winlink *wl;
|
||||
struct window *w;
|
||||
struct window_pane *wp;
|
||||
struct layout_cell *lc;
|
||||
int y, ly, x, lx;
|
||||
|
||||
wp = cmd_mouse_pane(m, NULL, &wl);
|
||||
if (wp == NULL) {
|
||||
c->tty.mouse_drag_update = NULL;
|
||||
return;
|
||||
}
|
||||
w = wl->window;
|
||||
lc = wp->layout_cell;
|
||||
|
||||
y = m->y + m->oy; x = m->x + m->ox;
|
||||
if (m->statusat == 0 && y >= (int)m->statuslines)
|
||||
y -= m->statuslines;
|
||||
else if (m->statusat > 0 && y >= m->statusat)
|
||||
y = m->statusat - 1;
|
||||
ly = m->ly + m->oy; lx = m->lx + m->ox;
|
||||
if (m->statusat == 0 && ly >= (int)m->statuslines)
|
||||
ly -= m->statuslines;
|
||||
else if (m->statusat > 0 && ly >= m->statusat)
|
||||
ly = m->statusat - 1;
|
||||
|
||||
if (x != lx || y != ly) {
|
||||
lc->xoff += x - lx;
|
||||
lc->yoff += y - ly;
|
||||
layout_fix_panes(w, NULL);
|
||||
server_redraw_window(w);
|
||||
server_redraw_window_borders(w);
|
||||
}
|
||||
}
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_join_pane_zindex(struct cmdq_item *item, struct winlink *wl,
|
||||
struct window_pane *wp, const char *s)
|
||||
{
|
||||
struct window *w = wl->window;
|
||||
struct window_pane *owp;
|
||||
const char *errstr;
|
||||
u_int n, z;
|
||||
|
||||
z = strtonum(s, 0, UINT_MAX, &errstr);
|
||||
if (errstr != NULL) {
|
||||
cmdq_error(item, "z-index %s", errstr);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
||||
|
||||
n = 0;
|
||||
TAILQ_FOREACH(owp, &w->z_index, zentry) {
|
||||
if (!window_pane_is_floating(owp))
|
||||
break;
|
||||
if (n >= z)
|
||||
break;
|
||||
n++;
|
||||
}
|
||||
|
||||
if (owp != NULL)
|
||||
TAILQ_INSERT_BEFORE(owp, wp, zentry);
|
||||
else
|
||||
TAILQ_INSERT_TAIL(&w->z_index, wp, zentry);
|
||||
|
||||
notify_window("window-layout-changed", w);
|
||||
server_redraw_window(w);
|
||||
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_join_pane_tile(struct cmdq_item *item, struct args *args, struct window *w,
|
||||
struct window_pane *wp)
|
||||
{
|
||||
struct layout_cell *lc = wp->layout_cell;
|
||||
|
||||
if (!window_pane_is_floating(wp)) {
|
||||
cmdq_error(item, "pane is not floating");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
if (window_pane_is_hidden(wp)) {
|
||||
cmdq_error(item, "can't tile a hidden pane");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
if (w->flags & WINDOW_ZOOMED) {
|
||||
cmdq_error(item, "can't tile a pane while window is zoomed");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
|
||||
layout_save_size(lc);
|
||||
lc->flags &= ~LAYOUT_CELL_FLOATING;
|
||||
if (layout_insert_tile(w, lc) == 0) {
|
||||
cmdq_error(item, "no space for a new pane");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
|
||||
TAILQ_REMOVE(&w->z_index, wp, zentry);
|
||||
TAILQ_INSERT_TAIL(&w->z_index, wp, zentry);
|
||||
|
||||
if (!args_has(args, 'd'))
|
||||
window_set_active_pane(w, wp, 1);
|
||||
layout_fix_offsets(w);
|
||||
layout_fix_panes(w, NULL);
|
||||
notify_window("window-layout-changed", w);
|
||||
server_redraw_window(w);
|
||||
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
{
|
||||
@@ -70,9 +413,10 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
struct winlink *src_wl, *dst_wl;
|
||||
struct window *src_w, *dst_w;
|
||||
struct window_pane *src_wp, *dst_wp;
|
||||
const char *s;
|
||||
char *cause = NULL;
|
||||
int flags = 0, dst_idx;
|
||||
struct layout_cell *lc;
|
||||
struct layout_cell *lc, *src_lc;
|
||||
|
||||
dst_s = target->s;
|
||||
dst_wl = target->wl;
|
||||
@@ -81,11 +425,35 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
dst_idx = dst_wl->idx;
|
||||
server_unzoom_window(dst_w);
|
||||
|
||||
if (cmd_get_entry(self) == &cmd_move_pane_entry) {
|
||||
if (args_has(args, 'M'))
|
||||
return (cmd_join_pane_mouse_update(item));
|
||||
if (!window_pane_is_floating(dst_wp)) {
|
||||
cmdq_error(item, "pane is not floating");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
if ((s = args_get(args, 'P')) != NULL)
|
||||
return (cmd_join_pane_place(item, dst_wl, dst_wp, s));
|
||||
if ((s = args_get(args, 'z')) != NULL)
|
||||
return (cmd_join_pane_zindex(item, dst_wl, dst_wp, s));
|
||||
if (args_has(args, 'X') ||
|
||||
args_has(args, 'Y') ||
|
||||
args_has(args, 'U') ||
|
||||
args_has(args, 'D') ||
|
||||
args_has(args, 'L') ||
|
||||
args_has(args, 'R'))
|
||||
return (cmd_join_pane_move(item, args, dst_wl, dst_wp));
|
||||
}
|
||||
|
||||
src_wl = source->wl;
|
||||
src_wp = source->wp;
|
||||
src_lc = src_wp->layout_cell;
|
||||
src_w = src_wl->window;
|
||||
server_unzoom_window(src_w);
|
||||
|
||||
if (!args_has(args, 't') && window_pane_is_floating(src_wp))
|
||||
return (cmd_join_pane_tile(item, args, src_w, src_wp));
|
||||
|
||||
if (src_wp == dst_wp) {
|
||||
cmdq_error(item, "source and target panes must be different");
|
||||
return (CMD_RETURN_ERROR);
|
||||
@@ -97,6 +465,12 @@ cmd_join_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
free(cause);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
if (window_pane_is_floating(src_wp)) {
|
||||
lc->saved_sx = src_lc->sx;
|
||||
lc->saved_sy = src_lc->sy;
|
||||
lc->saved_xoff = src_lc->xoff;
|
||||
lc->saved_yoff = src_lc->yoff;
|
||||
}
|
||||
|
||||
layout_close_pane(src_wp);
|
||||
|
||||
|
||||
@@ -27,13 +27,17 @@
|
||||
*/
|
||||
|
||||
static enum cmd_retval cmd_kill_pane_exec(struct cmd *, struct cmdq_item *);
|
||||
static enum cmd_retval cmd_kill_pane_all(struct cmdq_item *, const char *);
|
||||
static int cmd_kill_pane_filter(struct cmdq_item *,
|
||||
struct session *, struct winlink *,
|
||||
struct window_pane *, const char *);
|
||||
|
||||
const struct cmd_entry cmd_kill_pane_entry = {
|
||||
.name = "kill-pane",
|
||||
.alias = "killp",
|
||||
|
||||
.args = { "at:", 0, 0, NULL },
|
||||
.usage = "[-a] " CMD_TARGET_PANE_USAGE,
|
||||
.args = { "af:t:", 0, 0, NULL },
|
||||
.usage = "[-a] [-f filter] " CMD_TARGET_PANE_USAGE,
|
||||
|
||||
.target = { 't', CMD_FIND_PANE, 0 },
|
||||
|
||||
@@ -46,22 +50,17 @@ cmd_kill_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
{
|
||||
struct args *args = cmd_get_args(self);
|
||||
struct cmd_find_state *target = cmdq_get_target(item);
|
||||
struct winlink *wl = target->wl;
|
||||
struct window_pane *loopwp, *tmpwp, *wp = target->wp;
|
||||
struct window_pane *wp = target->wp;
|
||||
const char *filter = args_get(args, 'f');
|
||||
|
||||
if (args_has(args, 'a')) {
|
||||
server_unzoom_window(wl->window);
|
||||
TAILQ_FOREACH_SAFE(loopwp, &wl->window->panes, entry, tmpwp) {
|
||||
if (loopwp == wp)
|
||||
continue;
|
||||
server_client_remove_pane(loopwp);
|
||||
layout_close_pane(loopwp);
|
||||
window_remove_pane(wl->window, loopwp);
|
||||
}
|
||||
server_redraw_window(wl->window);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
if (filter != NULL && !args_has(args, 'a')) {
|
||||
cmdq_error(item, "-f only valid with -a");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
|
||||
if (args_has(args, 'a'))
|
||||
return (cmd_kill_pane_all(item, filter));
|
||||
|
||||
if (wp == NULL) {
|
||||
cmdq_error(item, "no active pane to kill");
|
||||
return (CMD_RETURN_ERROR);
|
||||
@@ -69,3 +68,48 @@ cmd_kill_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
server_kill_pane(wp);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_kill_pane_all(struct cmdq_item *item, const char *filter)
|
||||
{
|
||||
struct cmd_find_state *target = cmdq_get_target(item);
|
||||
struct session *s = target->s;
|
||||
struct winlink *wl = target->wl;
|
||||
struct window_pane *wp = target->wp;
|
||||
struct window_pane *loopwp, *tmpwp;
|
||||
|
||||
server_unzoom_window(wl->window);
|
||||
TAILQ_FOREACH_SAFE(loopwp, &wl->window->panes, entry, tmpwp) {
|
||||
if (loopwp == wp)
|
||||
continue;
|
||||
if (!cmd_kill_pane_filter(item, s, wl, loopwp, filter))
|
||||
continue;
|
||||
server_client_remove_pane(loopwp);
|
||||
layout_close_pane(loopwp);
|
||||
window_remove_pane(wl->window, loopwp);
|
||||
}
|
||||
server_redraw_window(wl->window);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
static int
|
||||
cmd_kill_pane_filter(struct cmdq_item *item, struct session *s,
|
||||
struct winlink *wl, struct window_pane *wp, const char *filter)
|
||||
{
|
||||
struct format_tree *ft;
|
||||
char *expanded;
|
||||
int flag;
|
||||
|
||||
if (filter == NULL)
|
||||
return (1);
|
||||
|
||||
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
|
||||
format_defaults(ft, NULL, s, wl, wp);
|
||||
|
||||
expanded = format_expand(ft, filter);
|
||||
flag = format_true(expanded);
|
||||
free(expanded);
|
||||
|
||||
format_free(ft);
|
||||
return (flag);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "tmux.h"
|
||||
|
||||
/*
|
||||
@@ -28,13 +30,16 @@
|
||||
*/
|
||||
|
||||
static enum cmd_retval cmd_kill_session_exec(struct cmd *, struct cmdq_item *);
|
||||
static enum cmd_retval cmd_kill_session_all(struct cmdq_item *, const char *);
|
||||
static int cmd_kill_session_filter(struct cmdq_item *,
|
||||
struct session *, const char *);
|
||||
|
||||
const struct cmd_entry cmd_kill_session_entry = {
|
||||
.name = "kill-session",
|
||||
.alias = NULL,
|
||||
|
||||
.args = { "aCgt:", 0, 0, NULL },
|
||||
.usage = "[-aCg] " CMD_TARGET_SESSION_USAGE,
|
||||
.args = { "aCgf:t:", 0, 0, NULL },
|
||||
.usage = "[-aCg] [-f filter] " CMD_TARGET_SESSION_USAGE,
|
||||
|
||||
.target = { 't', CMD_FIND_SESSION, 0 },
|
||||
|
||||
@@ -50,6 +55,12 @@ cmd_kill_session_exec(struct cmd *self, struct cmdq_item *item)
|
||||
struct session *s = target->s, *sloop, *stmp;
|
||||
struct session_group *sg;
|
||||
struct winlink *wl;
|
||||
const char *filter = args_get(args, 'f');
|
||||
|
||||
if (filter != NULL && (!args_has(args, 'a') || args_has(args, 'C'))) {
|
||||
cmdq_error(item, "-f only valid with -a");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
|
||||
if (args_has(args, 'C')) {
|
||||
RB_FOREACH(wl, winlinks, &s->windows) {
|
||||
@@ -57,14 +68,9 @@ cmd_kill_session_exec(struct cmd *self, struct cmdq_item *item)
|
||||
wl->flags &= ~WINLINK_ALERTFLAGS;
|
||||
}
|
||||
server_redraw_session(s);
|
||||
} else if (args_has(args, 'a')) {
|
||||
RB_FOREACH_SAFE(sloop, sessions, &sessions, stmp) {
|
||||
if (sloop != s) {
|
||||
server_destroy_session(sloop);
|
||||
session_destroy(sloop, 1, __func__);
|
||||
}
|
||||
}
|
||||
} else if (args_has(args, 'g') &&
|
||||
} else if (args_has(args, 'a'))
|
||||
return (cmd_kill_session_all(item, filter));
|
||||
else if (args_has(args, 'g') &&
|
||||
(sg = session_group_contains(s)) != NULL) {
|
||||
TAILQ_FOREACH_SAFE(sloop, &sg->sessions, gentry, stmp) {
|
||||
server_destroy_session(sloop);
|
||||
@@ -76,3 +82,42 @@ cmd_kill_session_exec(struct cmd *self, struct cmdq_item *item)
|
||||
}
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_kill_session_all(struct cmdq_item *item, const char *filter)
|
||||
{
|
||||
struct session *s = cmdq_get_target(item)->s;
|
||||
struct session *sloop, *stmp;
|
||||
|
||||
RB_FOREACH_SAFE(sloop, sessions, &sessions, stmp) {
|
||||
if (sloop == s)
|
||||
continue;
|
||||
if (!cmd_kill_session_filter(item, sloop, filter))
|
||||
continue;
|
||||
server_destroy_session(sloop);
|
||||
session_destroy(sloop, 1, __func__);
|
||||
}
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
static int
|
||||
cmd_kill_session_filter(struct cmdq_item *item, struct session *s,
|
||||
const char *filter)
|
||||
{
|
||||
struct format_tree *ft;
|
||||
char *expanded;
|
||||
int flag;
|
||||
|
||||
if (filter == NULL)
|
||||
return (1);
|
||||
|
||||
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
|
||||
format_defaults(ft, NULL, s, NULL, NULL);
|
||||
|
||||
expanded = format_expand(ft, filter);
|
||||
flag = format_true(expanded);
|
||||
free(expanded);
|
||||
|
||||
format_free(ft);
|
||||
return (flag);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "tmux.h"
|
||||
|
||||
/*
|
||||
@@ -25,13 +27,16 @@
|
||||
*/
|
||||
|
||||
static enum cmd_retval cmd_kill_window_exec(struct cmd *, struct cmdq_item *);
|
||||
static enum cmd_retval cmd_kill_window_all(struct cmdq_item *, const char *);
|
||||
static int cmd_kill_window_filter(struct cmdq_item *,
|
||||
struct session *, struct winlink *, const char *);
|
||||
|
||||
const struct cmd_entry cmd_kill_window_entry = {
|
||||
.name = "kill-window",
|
||||
.alias = "killw",
|
||||
|
||||
.args = { "at:", 0, 0, NULL },
|
||||
.usage = "[-a] " CMD_TARGET_WINDOW_USAGE,
|
||||
.args = { "af:t:", 0, 0, NULL },
|
||||
.usage = "[-a] [-f filter] " CMD_TARGET_WINDOW_USAGE,
|
||||
|
||||
.target = { 't', CMD_FIND_WINDOW, 0 },
|
||||
|
||||
@@ -57,10 +62,15 @@ cmd_kill_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
{
|
||||
struct args *args = cmd_get_args(self);
|
||||
struct cmd_find_state *target = cmdq_get_target(item);
|
||||
struct winlink *wl = target->wl, *loop;
|
||||
struct winlink *wl = target->wl;
|
||||
struct window *w = wl->window;
|
||||
struct session *s = target->s;
|
||||
u_int found;
|
||||
const char *filter = args_get(args, 'f');
|
||||
|
||||
if (filter != NULL && !args_has(args, 'a')) {
|
||||
cmdq_error(item, "-f only valid with -a");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
|
||||
if (cmd_get_entry(self) == &cmd_unlink_window_entry) {
|
||||
if (!args_has(args, 'k') && !session_is_linked(s, w)) {
|
||||
@@ -72,39 +82,76 @@ cmd_kill_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
if (args_has(args, 'a')) {
|
||||
if (RB_PREV(winlinks, &s->windows, wl) == NULL &&
|
||||
RB_NEXT(winlinks, &s->windows, wl) == NULL)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
|
||||
/* Kill all windows except the current one. */
|
||||
do {
|
||||
found = 0;
|
||||
RB_FOREACH(loop, winlinks, &s->windows) {
|
||||
if (loop->window != wl->window) {
|
||||
server_kill_window(loop->window, 0);
|
||||
found++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (found != 0);
|
||||
|
||||
/*
|
||||
* If the current window appears in the session more than once,
|
||||
* kill it as well.
|
||||
*/
|
||||
found = 0;
|
||||
RB_FOREACH(loop, winlinks, &s->windows) {
|
||||
if (loop->window == wl->window)
|
||||
found++;
|
||||
}
|
||||
if (found > 1)
|
||||
server_kill_window(wl->window, 0);
|
||||
|
||||
server_renumber_all();
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
if (args_has(args, 'a'))
|
||||
return (cmd_kill_window_all(item, filter));
|
||||
|
||||
server_kill_window(wl->window, 1);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_kill_window_all(struct cmdq_item *item, const char *filter)
|
||||
{
|
||||
struct cmd_find_state *target = cmdq_get_target(item);
|
||||
struct session *s = target->s;
|
||||
struct winlink *wl = target->wl;
|
||||
struct winlink *loop;
|
||||
u_int found, kill_current;
|
||||
|
||||
if (RB_PREV(winlinks, &s->windows, wl) == NULL &&
|
||||
RB_NEXT(winlinks, &s->windows, wl) == NULL)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
|
||||
/* Kill all windows except the current one. */
|
||||
do {
|
||||
found = 0;
|
||||
RB_FOREACH(loop, winlinks, &s->windows) {
|
||||
if (loop->window != wl->window &&
|
||||
cmd_kill_window_filter(item, s, loop, filter)) {
|
||||
server_kill_window(loop->window, 0);
|
||||
found++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (found != 0);
|
||||
|
||||
/*
|
||||
* If the current window appears in the session more than once, kill it
|
||||
* as well if it matches the filter.
|
||||
*/
|
||||
found = kill_current = 0;
|
||||
RB_FOREACH(loop, winlinks, &s->windows) {
|
||||
if (loop->window == wl->window) {
|
||||
found++;
|
||||
if (cmd_kill_window_filter(item, s, loop, filter))
|
||||
kill_current = 1;
|
||||
}
|
||||
}
|
||||
if (kill_current && found > 1)
|
||||
server_kill_window(wl->window, 0);
|
||||
|
||||
server_renumber_all();
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
static int
|
||||
cmd_kill_window_filter(struct cmdq_item *item, struct session *s,
|
||||
struct winlink *wl, const char *filter)
|
||||
{
|
||||
struct format_tree *ft;
|
||||
char *expanded;
|
||||
int flag;
|
||||
|
||||
if (filter == NULL)
|
||||
return (1);
|
||||
|
||||
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
|
||||
format_defaults(ft, NULL, s, wl, NULL);
|
||||
|
||||
expanded = format_expand(ft, filter);
|
||||
flag = format_true(expanded);
|
||||
free(expanded);
|
||||
|
||||
format_free(ft);
|
||||
return (flag);
|
||||
}
|
||||
|
||||
@@ -223,11 +223,16 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item)
|
||||
cmd_list_keys_filter_key_list(filter_notes, filter_key, only, l,
|
||||
&n);
|
||||
}
|
||||
if (single)
|
||||
if (filter_key && n == 0) {
|
||||
cmdq_error(item, "unknown key: %s", keystr);
|
||||
free(prefix);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
if (single && n > 1)
|
||||
n = 1;
|
||||
|
||||
ft = format_create(cmdq_get_client(item), item, FORMAT_NONE, 0);
|
||||
format_defaults(ft, NULL, NULL, NULL, NULL);
|
||||
format_defaults(ft, tc, NULL, NULL, NULL);
|
||||
format_add(ft, "notes_only", "%d", notes_only);
|
||||
format_add(ft, "key_has_repeat", "%d", key_bindings_has_repeat(l, n));
|
||||
format_add(ft, "key_string_width", "%u", cmd_list_keys_get_width(l, n));
|
||||
@@ -237,7 +242,7 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item)
|
||||
cmd_list_keys_format_add_key_binding(ft, l[i], prefix);
|
||||
|
||||
line = format_expand(ft, template);
|
||||
if ((single && tc != NULL) || n == 1)
|
||||
if (single && tc != NULL && (~tc->flags & CLIENT_CONTROL))
|
||||
status_message_set(tc, -1, 1, 0, 0, "%s", line);
|
||||
else if (*line != '\0')
|
||||
cmdq_print(item, "%s", line);
|
||||
|
||||
@@ -101,22 +101,22 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item)
|
||||
|
||||
if ((tmp = args_get(args, 'n')) != NULL) {
|
||||
ename = format_single(item, tmp, c, NULL, NULL, NULL);
|
||||
if (!check_name(ename, WINDOW_NAME_FORBID)) {
|
||||
if (!check_name(ename)) {
|
||||
cmdq_error(item, "invalid window name: %s", ename);
|
||||
free(ename);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
wname = clean_name(ename, WINDOW_NAME_FORBID);
|
||||
wname = clean_name(ename, 0);
|
||||
free(ename);
|
||||
}
|
||||
if ((tmp = args_get(args, 's')) != NULL) {
|
||||
ename = format_single(item, tmp, c, NULL, NULL, NULL);
|
||||
if (!check_name(ename, SESSION_NAME_FORBID)) {
|
||||
if (!check_name(ename)) {
|
||||
cmdq_error(item, "invalid session name: %s", ename);
|
||||
free(ename);
|
||||
goto fail;
|
||||
}
|
||||
sname = clean_name(ename, SESSION_NAME_FORBID);
|
||||
sname = clean_name(ename, 0);
|
||||
free(ename);
|
||||
}
|
||||
if (args_has(args, 'A')) {
|
||||
@@ -152,12 +152,12 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item)
|
||||
else if (groupwith != NULL)
|
||||
prefix = xstrdup(groupwith->name);
|
||||
else {
|
||||
if (!check_name(group, SESSION_NAME_FORBID)) {
|
||||
if (!check_name(group)) {
|
||||
cmdq_error(item,
|
||||
"invalid session group name: %s", group);
|
||||
goto fail;
|
||||
}
|
||||
prefix = clean_name(group, SESSION_NAME_FORBID);
|
||||
prefix = clean_name(group, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -73,12 +73,12 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
name = args_get(args, 'n');
|
||||
if (name != NULL) {
|
||||
expanded = format_single(item, name, c, s, NULL, NULL);
|
||||
if (!check_name(expanded, WINDOW_NAME_FORBID)) {
|
||||
if (!check_name(expanded)) {
|
||||
cmdq_error(item, "invalid window name: %s", expanded);
|
||||
free(expanded);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
wname = clean_name(expanded, WINDOW_NAME_FORBID);
|
||||
wname = clean_name(expanded, 0);
|
||||
free(expanded);
|
||||
}
|
||||
if (args_has(args, 'S') && wname != NULL && target->idx == -1) {
|
||||
|
||||
@@ -168,6 +168,7 @@ cmd_refresh_report(struct tty *tty, const char *value)
|
||||
{
|
||||
struct window_pane *wp;
|
||||
u_int pane;
|
||||
int fg, bg;
|
||||
size_t size = 0;
|
||||
char *copy, *split;
|
||||
|
||||
@@ -184,8 +185,14 @@ cmd_refresh_report(struct tty *tty, const char *value)
|
||||
if (wp == NULL)
|
||||
goto out;
|
||||
|
||||
tty_keys_colours(tty, split, strlen(split), &size, &wp->control_fg,
|
||||
&wp->control_bg);
|
||||
fg = wp->control_fg;
|
||||
bg = wp->control_bg;
|
||||
if (tty_keys_colours(tty, split, strlen(split), &size, &fg, &bg) == 0) {
|
||||
if (bg != wp->control_bg)
|
||||
wp->flags |= PANE_THEMECHANGED;
|
||||
wp->control_fg = fg;
|
||||
wp->control_bg = bg;
|
||||
}
|
||||
|
||||
out:
|
||||
free(copy);
|
||||
|
||||
@@ -52,12 +52,12 @@ cmd_rename_session_exec(struct cmd *self, struct cmdq_item *item)
|
||||
char *newname, *tmp;
|
||||
|
||||
tmp = format_single_from_target(item, args_string(args, 0));
|
||||
if (!check_name(tmp, SESSION_NAME_FORBID)) {
|
||||
if (!check_name(tmp)) {
|
||||
cmdq_error(item, "invalid session name: %s", tmp);
|
||||
free(tmp);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
newname = clean_name(tmp, SESSION_NAME_FORBID);
|
||||
newname = clean_name(tmp, 0);
|
||||
free(tmp);
|
||||
if (strcmp(newname, s->name) == 0) {
|
||||
free(newname);
|
||||
|
||||
@@ -51,13 +51,13 @@ cmd_rename_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
char *name;
|
||||
|
||||
name = format_single_from_target(item, args_string(args, 0));
|
||||
if (!check_name(name, WINDOW_NAME_FORBID)) {
|
||||
if (!check_name(name)) {
|
||||
cmdq_error(item, "invalid window name: %s", name);
|
||||
free(name);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
|
||||
window_set_name(wl->window, name, WINDOW_NAME_FORBID);
|
||||
window_set_name(wl->window, name, 0);
|
||||
options_set_number(wl->window->options, "automatic-rename", 0);
|
||||
free(name);
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -31,18 +32,18 @@ static enum cmd_retval cmd_resize_pane_exec(struct cmd *, struct cmdq_item *);
|
||||
|
||||
static enum cmd_retval cmd_resize_pane_mouse_update(struct cmd *,
|
||||
struct cmdq_item *);
|
||||
static void cmd_resize_pane_mouse_update_floating(struct client *,
|
||||
struct mouse_event *);
|
||||
static void cmd_resize_pane_mouse_update_tiled(struct client *,
|
||||
static void cmd_resize_pane_mouse_resize_move_floating(
|
||||
struct client *, struct mouse_event *);
|
||||
static void cmd_resize_pane_mouse_resize_tiled(struct client *,
|
||||
struct mouse_event *);
|
||||
|
||||
const struct cmd_entry cmd_resize_pane_entry = {
|
||||
.name = "resize-pane",
|
||||
.alias = "resizep",
|
||||
|
||||
.args = { "DLMRTt:Ux:y:Z", 0, 1, NULL },
|
||||
.usage = "[-DLMRTUZ] [-x width] [-y height] " CMD_TARGET_PANE_USAGE " "
|
||||
"[adjustment]",
|
||||
.args = { "D::L::MR::Tt:U::x:y:Z", 0, 1, NULL },
|
||||
.usage = "[-MTZ] [-D lines] [-L columns] [-R columns] [-U lines] "
|
||||
"[-x width] [-y height] " CMD_TARGET_PANE_USAGE,
|
||||
|
||||
.target = { 't', CMD_FIND_PANE, 0 },
|
||||
|
||||
@@ -58,17 +59,20 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
struct window_pane *wp = target->wp;
|
||||
struct winlink *wl = target->wl;
|
||||
struct window *w = wl->window;
|
||||
const char *errstr;
|
||||
char *cause;
|
||||
u_int adjust;
|
||||
int x, y, status;
|
||||
struct layout_cell *lc = wp->layout_cell;
|
||||
enum layout_type type;
|
||||
const char *errstr, *argval;
|
||||
const char flags[4] = { 'U', 'D', 'L', 'R' };
|
||||
char *cause = NULL, flag;
|
||||
int adjust, x, y, status, opposite = 0;
|
||||
long unsigned i;
|
||||
struct grid *gd = wp->base.grid;
|
||||
|
||||
if (args_has(args, 'T')) {
|
||||
if (!TAILQ_EMPTY(&wp->modes))
|
||||
return (CMD_RETURN_NORMAL);
|
||||
adjust = screen_size_y(&wp->base) - 1 - wp->base.cy;
|
||||
if (adjust > gd->hsize)
|
||||
if (adjust > (int)gd->hsize)
|
||||
adjust = gd->hsize;
|
||||
grid_remove_history(gd, adjust);
|
||||
wp->base.cy += adjust;
|
||||
@@ -89,33 +93,31 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
}
|
||||
server_unzoom_window(w);
|
||||
|
||||
if (args_count(args) == 0)
|
||||
adjust = 1;
|
||||
else {
|
||||
adjust = strtonum(args_string(args, 0), 1, INT_MAX, &errstr);
|
||||
if (errstr != NULL) {
|
||||
cmdq_error(item, "adjustment %s", errstr);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
if (args_has(args, 'x')) {
|
||||
x = args_percentage(args, 'x', 0, INT_MAX, w->sx, &cause);
|
||||
x = args_percentage(args, 'x', 0, PANE_MAXIMUM, w->sx, &cause);
|
||||
if (cause != NULL) {
|
||||
cmdq_error(item, "width %s", cause);
|
||||
free(cause);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
layout_resize_pane_to(wp, LAYOUT_LEFTRIGHT, x);
|
||||
if (window_pane_is_floating(wp)) {
|
||||
if (layout_resize_floating_pane_to(wp, LAYOUT_LEFTRIGHT,
|
||||
x, &cause) != 0) {
|
||||
cmdq_error(item, "size %s", cause);
|
||||
free(cause);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
} else
|
||||
layout_resize_pane_to(wp, LAYOUT_LEFTRIGHT, x);
|
||||
}
|
||||
if (args_has(args, 'y')) {
|
||||
y = args_percentage(args, 'y', 0, INT_MAX, w->sy, &cause);
|
||||
y = args_percentage(args, 'y', 0, PANE_MAXIMUM, w->sy, &cause);
|
||||
if (cause != NULL) {
|
||||
cmdq_error(item, "height %s", cause);
|
||||
free(cause);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
status = options_get_number(w->options, "pane-border-status");
|
||||
status = window_get_pane_status(w);
|
||||
switch (status) {
|
||||
case PANE_STATUS_TOP:
|
||||
if (y != INT_MAX && wp->yoff == 1)
|
||||
@@ -126,18 +128,62 @@ cmd_resize_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
y++;
|
||||
break;
|
||||
}
|
||||
layout_resize_pane_to(wp, LAYOUT_TOPBOTTOM, y);
|
||||
if (window_pane_is_floating(wp)) {
|
||||
if (layout_resize_floating_pane_to(wp, LAYOUT_TOPBOTTOM,
|
||||
y, &cause) != 0) {
|
||||
cmdq_error(item, "size %s", cause);
|
||||
free(cause);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
} else
|
||||
layout_resize_pane_to(wp, LAYOUT_TOPBOTTOM, y);
|
||||
}
|
||||
|
||||
if (args_has(args, 'L'))
|
||||
layout_resize_pane(wp, LAYOUT_LEFTRIGHT, -adjust, 1);
|
||||
else if (args_has(args, 'R'))
|
||||
layout_resize_pane(wp, LAYOUT_LEFTRIGHT, adjust, 1);
|
||||
else if (args_has(args, 'U'))
|
||||
layout_resize_pane(wp, LAYOUT_TOPBOTTOM, -adjust, 1);
|
||||
else if (args_has(args, 'D'))
|
||||
layout_resize_pane(wp, LAYOUT_TOPBOTTOM, adjust, 1);
|
||||
server_redraw_window(wl->window);
|
||||
for (i = 0; i < nitems(flags); i++) {
|
||||
flag = flags[i];
|
||||
if (!args_has(args, flag))
|
||||
continue;
|
||||
|
||||
argval = args_get(args, flag);
|
||||
if (argval == NULL) {
|
||||
if (args_count(args) == 0)
|
||||
argval = "1";
|
||||
else
|
||||
argval = args_string(args, 0);
|
||||
}
|
||||
|
||||
adjust = strtonum(argval, INT_MIN, INT_MAX, &errstr);
|
||||
if (errstr != NULL) {
|
||||
cmdq_error(item, "adjustment %s", errstr);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
|
||||
type = LAYOUT_TOPBOTTOM;
|
||||
if (flag == 'L' || flag == 'R')
|
||||
type = LAYOUT_LEFTRIGHT;
|
||||
|
||||
if (window_pane_is_floating(wp)) {
|
||||
if (flag == 'L' || flag == 'U')
|
||||
opposite = 1;
|
||||
|
||||
if (layout_resize_floating_pane(wp, type, adjust,
|
||||
opposite, &cause) != 0) {
|
||||
cmdq_error(item, "adjustment %s", cause);
|
||||
free(cause);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
} else {
|
||||
if (flag == 'L' || flag == 'U')
|
||||
adjust = -adjust;
|
||||
layout_resize_pane(wp, type, adjust, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (lc->parent != NULL)
|
||||
layout_fix_offsets(w);
|
||||
layout_fix_panes(w, NULL);
|
||||
notify_window("window-layout-changed", w);
|
||||
server_redraw_window(w);
|
||||
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
@@ -160,28 +206,36 @@ cmd_resize_pane_mouse_update(__unused struct cmd *self, struct cmdq_item *item)
|
||||
return (CMD_RETURN_NORMAL);
|
||||
|
||||
if (!window_pane_is_floating(wp)) {
|
||||
c->tty.mouse_drag_update = cmd_resize_pane_mouse_update_tiled;
|
||||
cmd_resize_pane_mouse_update_tiled(c, &event->m);
|
||||
c->tty.mouse_drag_update = cmd_resize_pane_mouse_resize_tiled;
|
||||
cmd_resize_pane_mouse_resize_tiled(c, &event->m);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
window_redraw_active_switch(w, wp);
|
||||
window_set_active_pane(w, wp, 1);
|
||||
|
||||
c->tty.mouse_drag_update = cmd_resize_pane_mouse_update_floating;
|
||||
cmd_resize_pane_mouse_update_floating(c, &event->m);
|
||||
c->tty.mouse_drag_update = cmd_resize_pane_mouse_resize_move_floating;
|
||||
cmd_resize_pane_mouse_resize_move_floating(c, &event->m);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Resizes or moves the pane by dragging. Resize a floating pane by dragging
|
||||
* the borders or corners. Grabbing an edge only resizes that axis (special
|
||||
* case). Moves the pane if dragging the top border. Since characters are
|
||||
* generally rectangular, to make it easier to grab the corner, the character
|
||||
* next to the corner is also considered the corner.
|
||||
*/
|
||||
static void
|
||||
cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m)
|
||||
cmd_resize_pane_mouse_resize_move_floating(struct client *c,
|
||||
struct mouse_event *m)
|
||||
{
|
||||
struct winlink *wl;
|
||||
struct window *w;
|
||||
struct window_pane *wp;
|
||||
struct layout_cell *lc;
|
||||
int y, ly, x, lx, sx, sy, new_sx, new_sy;
|
||||
int scrollbars, sb_pos, left, right;
|
||||
int left, right;
|
||||
int new_xoff, new_yoff, resizes = 0;
|
||||
|
||||
wp = cmd_mouse_pane(m, NULL, &wl);
|
||||
@@ -193,15 +247,13 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m)
|
||||
lc = wp->layout_cell;
|
||||
sx = wp->sx;
|
||||
sy = wp->sy;
|
||||
scrollbars = options_get_number(w->options, "pane-scrollbars");
|
||||
sb_pos = options_get_number(w->options, "pane-scrollbars-position");
|
||||
left = wp->xoff - 1;
|
||||
right = wp->xoff + sx;
|
||||
if (window_pane_show_scrollbar(wp, scrollbars) &&
|
||||
sb_pos == PANE_SCROLLBARS_LEFT) {
|
||||
if (window_pane_scrollbar_reserve(wp) &&
|
||||
w->sb_pos == PANE_SCROLLBARS_LEFT) {
|
||||
left -= wp->scrollbar_style.width + wp->scrollbar_style.pad;
|
||||
} else if (window_pane_show_scrollbar(wp, scrollbars) &&
|
||||
sb_pos == PANE_SCROLLBARS_RIGHT) {
|
||||
} else if (window_pane_scrollbar_reserve(wp) &&
|
||||
w->sb_pos == PANE_SCROLLBARS_RIGHT) {
|
||||
right += wp->scrollbar_style.width + wp->scrollbar_style.pad;
|
||||
}
|
||||
|
||||
@@ -300,7 +352,7 @@ cmd_resize_pane_mouse_update_floating(struct client *c, struct mouse_event *m)
|
||||
}
|
||||
|
||||
static void
|
||||
cmd_resize_pane_mouse_update_tiled(struct client *c, struct mouse_event *m)
|
||||
cmd_resize_pane_mouse_resize_tiled(struct client *c, struct mouse_event *m)
|
||||
{
|
||||
struct winlink *wl;
|
||||
struct window *w;
|
||||
|
||||
@@ -109,6 +109,7 @@ cmd_rotate_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
window_set_active_pane(w, wp, 1);
|
||||
cmd_find_from_winlink_pane(current, wl, wp, 0);
|
||||
window_pop_zoom(w);
|
||||
redraw_invalidate_scene(w);
|
||||
server_redraw_window(w);
|
||||
|
||||
return (CMD_RETURN_NORMAL);
|
||||
|
||||
@@ -135,7 +135,7 @@ cmd_select_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
}
|
||||
|
||||
if (args_has(args, 'm') || args_has(args, 'M')) {
|
||||
if (args_has(args, 'm') && !window_pane_visible(wp))
|
||||
if (args_has(args, 'm') && !window_pane_is_visible(wp))
|
||||
return (CMD_RETURN_NORMAL);
|
||||
if (server_check_marked())
|
||||
lastwp = marked_pane.wp;
|
||||
|
||||
@@ -70,6 +70,7 @@ cmd_send_keys_inject_key(struct cmdq_item *item, struct cmdq_item *after,
|
||||
struct key_table *table = NULL;
|
||||
struct key_binding *bd;
|
||||
struct key_event *event;
|
||||
struct cmdq_item *new_after = after;
|
||||
|
||||
if (args_has(args, 'K')) {
|
||||
if (tc == NULL)
|
||||
@@ -77,10 +78,16 @@ cmd_send_keys_inject_key(struct cmdq_item *item, struct cmdq_item *after,
|
||||
event = xcalloc(1, sizeof *event);
|
||||
event->key = key|KEYC_SENT;
|
||||
memset(&event->m, 0, sizeof event->m);
|
||||
if (server_client_handle_key(tc, event) == 0) {
|
||||
free(event->buf);
|
||||
free(event);
|
||||
if (after == NULL) {
|
||||
if (server_client_handle_key(tc, event) != 0)
|
||||
return (item);
|
||||
} else {
|
||||
if (server_client_handle_key_after(tc, event, after,
|
||||
&new_after) != 0)
|
||||
return (new_after);
|
||||
}
|
||||
free(event->buf);
|
||||
free(event);
|
||||
return (item);
|
||||
}
|
||||
|
||||
@@ -229,8 +236,10 @@ cmd_send_keys_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if (count == 0) {
|
||||
if (args_has(args, 'N') || args_has(args, 'R'))
|
||||
return (CMD_RETURN_NORMAL);
|
||||
after = args_has(args, 'K') ? item : NULL;
|
||||
for (; np != 0; np--)
|
||||
cmd_send_keys_inject_key(item, NULL, args, event->key);
|
||||
after = cmd_send_keys_inject_key(item, after, args,
|
||||
event->key);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <grp.h>
|
||||
#include <pwd.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
@@ -37,67 +38,71 @@ const struct cmd_entry cmd_server_access_entry = {
|
||||
.name = "server-access",
|
||||
.alias = NULL,
|
||||
|
||||
.args = { "adlrw", 0, 1, NULL },
|
||||
.usage = "[-adlrw] " CMD_TARGET_PANE_USAGE " [user]",
|
||||
.args = { "adglrw", 0, 1, NULL },
|
||||
.usage = "[-adglrw] " CMD_TARGET_PANE_USAGE " [user|group]",
|
||||
|
||||
.flags = CMD_CLIENT_CANFAIL,
|
||||
.exec = cmd_server_access_exec
|
||||
};
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_server_access_deny(struct cmdq_item *item, struct passwd *pw)
|
||||
cmd_server_access_deny(struct cmdq_item *item, id_t id, int flags,
|
||||
const char *type, const char *name)
|
||||
{
|
||||
struct client *loop;
|
||||
struct server_acl_user *user;
|
||||
uid_t uid;
|
||||
|
||||
if ((user = server_acl_user_find(pw->pw_uid)) == NULL) {
|
||||
cmdq_error(item, "user %s not found", pw->pw_name);
|
||||
if (!server_acl_find(id, flags)) {
|
||||
cmdq_error(item, "%s %s not found", type, name);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
TAILQ_FOREACH(loop, &clients, entry) {
|
||||
uid = proc_get_peer_uid(loop->peer);
|
||||
if (uid == server_acl_get_uid(user)) {
|
||||
loop->exit_message = xstrdup("access not allowed");
|
||||
loop->flags |= CLIENT_EXIT;
|
||||
}
|
||||
}
|
||||
server_acl_user_deny(pw->pw_uid);
|
||||
|
||||
server_acl_deny(id, flags);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
static enum cmd_retval
|
||||
cmd_server_access_exec(struct cmd *self, struct cmdq_item *item)
|
||||
{
|
||||
|
||||
struct args *args = cmd_get_args(self);
|
||||
struct client *c = cmdq_get_target_client(item);
|
||||
char *name;
|
||||
struct passwd *pw = NULL;
|
||||
char *arg;
|
||||
const char *name = NULL, *type;
|
||||
struct passwd *pw;
|
||||
struct group *gr;
|
||||
id_t id;
|
||||
int flags = 0;
|
||||
|
||||
if (args_has(args, 'l')) {
|
||||
server_acl_display(item);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
if (args_count(args) == 0) {
|
||||
cmdq_error(item, "missing user argument");
|
||||
cmdq_error(item, "missing user or group argument");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
|
||||
name = format_single(item, args_string(args, 0), c, NULL, NULL, NULL);
|
||||
if (*name != '\0')
|
||||
pw = getpwnam(name);
|
||||
if (pw == NULL) {
|
||||
cmdq_error(item, "unknown user: %s", name);
|
||||
free(name);
|
||||
arg = format_single(item, args_string(args, 0), c, NULL, NULL, NULL);
|
||||
if (args_has(args, 'g')) {
|
||||
type = "group";
|
||||
if ((gr = getgrnam(arg)) != NULL) {
|
||||
id = gr->gr_gid;
|
||||
name = gr->gr_name;
|
||||
flags |= SERVER_ACL_IS_GROUP;
|
||||
}
|
||||
} else {
|
||||
type = "user";
|
||||
if ((pw = getpwnam(arg)) != NULL) {
|
||||
id = pw->pw_uid;
|
||||
name = pw->pw_name;
|
||||
}
|
||||
}
|
||||
if (name == NULL) {
|
||||
cmdq_error(item, "unknown %s: %s", type, arg);
|
||||
free(arg);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
free(name);
|
||||
free(arg);
|
||||
|
||||
if (pw->pw_uid == 0 || pw->pw_uid == getuid()) {
|
||||
if ((~flags & SERVER_ACL_IS_GROUP) && (id == 0 || id == getuid())) {
|
||||
cmdq_error(item, "%s owns the server, can't change access",
|
||||
pw->pw_name);
|
||||
name);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
|
||||
@@ -111,36 +116,35 @@ cmd_server_access_exec(struct cmd *self, struct cmdq_item *item)
|
||||
}
|
||||
|
||||
if (args_has(args, 'd'))
|
||||
return (cmd_server_access_deny(item, pw));
|
||||
return (cmd_server_access_deny(item, id, flags, type, name));
|
||||
if (args_has(args, 'a')) {
|
||||
if (server_acl_user_find(pw->pw_uid) != NULL) {
|
||||
cmdq_error(item, "user %s is already added",
|
||||
pw->pw_name);
|
||||
if (server_acl_find(id, flags)) {
|
||||
cmdq_error(item, "%s %s is already added", type, name);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
server_acl_user_allow(pw->pw_uid);
|
||||
server_acl_allow(id, flags);
|
||||
/* Do not return - allow -r or -w with -a. */
|
||||
} else if (args_has(args, 'r') || args_has(args, 'w')) {
|
||||
/* -r or -w implies -a if user does not exist. */
|
||||
if (server_acl_user_find(pw->pw_uid) == NULL)
|
||||
server_acl_user_allow(pw->pw_uid);
|
||||
/* -r or -w implies -a if the entry does not exist. */
|
||||
if (!server_acl_find(id, flags))
|
||||
server_acl_allow(id, flags);
|
||||
}
|
||||
|
||||
if (args_has(args, 'w')) {
|
||||
if (server_acl_user_find(pw->pw_uid) == NULL) {
|
||||
cmdq_error(item, "user %s not found", pw->pw_name);
|
||||
if (!server_acl_find(id, flags)) {
|
||||
cmdq_error(item, "%s %s not found", type, name);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
server_acl_user_allow_write(pw->pw_uid);
|
||||
server_acl_allow_write(id, flags);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
if (args_has(args, 'r')) {
|
||||
if (server_acl_user_find(pw->pw_uid) == NULL) {
|
||||
cmdq_error(item, "user %s not found", pw->pw_name);
|
||||
if (!server_acl_find(id, flags)) {
|
||||
cmdq_error(item, "%s %s not found", type, name);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
server_acl_user_deny_write(pw->pw_uid);
|
||||
server_acl_deny_write(id, flags);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
|
||||
@@ -55,56 +55,44 @@ cmd_show_prompt_history_exec(struct cmd *self, struct cmdq_item *item)
|
||||
struct args *args = cmd_get_args(self);
|
||||
const char *typestr = args_get(args, 'T');
|
||||
enum prompt_type type;
|
||||
u_int tidx, hidx;
|
||||
u_int t, h;
|
||||
const char *v;
|
||||
|
||||
if (cmd_get_entry(self) == &cmd_clear_prompt_history_entry) {
|
||||
if (typestr == NULL) {
|
||||
for (tidx = 0; tidx < PROMPT_NTYPES; tidx++) {
|
||||
for (hidx = 0; hidx < status_prompt_hsize[tidx];
|
||||
hidx++)
|
||||
free(status_prompt_hlist[tidx][hidx]);
|
||||
free(status_prompt_hlist[tidx]);
|
||||
status_prompt_hlist[tidx] = NULL;
|
||||
status_prompt_hsize[tidx] = 0;
|
||||
}
|
||||
for (t = 0; t < PROMPT_NTYPES; t++)
|
||||
prompt_history_clear(t);
|
||||
} else {
|
||||
type = status_prompt_type(typestr);
|
||||
type = prompt_type(typestr);
|
||||
if (type == PROMPT_TYPE_INVALID) {
|
||||
cmdq_error(item, "invalid type: %s", typestr);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
for (hidx = 0; hidx < status_prompt_hsize[type]; hidx++)
|
||||
free(status_prompt_hlist[type][hidx]);
|
||||
free(status_prompt_hlist[type]);
|
||||
status_prompt_hlist[type] = NULL;
|
||||
status_prompt_hsize[type] = 0;
|
||||
prompt_history_clear(type);
|
||||
}
|
||||
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
if (typestr == NULL) {
|
||||
for (tidx = 0; tidx < PROMPT_NTYPES; tidx++) {
|
||||
cmdq_print(item, "History for %s:\n",
|
||||
status_prompt_type_string(tidx));
|
||||
for (hidx = 0; hidx < status_prompt_hsize[tidx];
|
||||
hidx++) {
|
||||
cmdq_print(item, "%d: %s", hidx + 1,
|
||||
status_prompt_hlist[tidx][hidx]);
|
||||
for (t = 0; t < PROMPT_NTYPES; t++) {
|
||||
typestr = prompt_type_string(t);
|
||||
cmdq_print(item, "History for %s:\n", typestr);
|
||||
for (h = 0; h < prompt_history_size(t); h++) {
|
||||
v = prompt_history_get(t, h);
|
||||
cmdq_print(item, "%d: %s", h + 1, v);
|
||||
}
|
||||
cmdq_print(item, "%s", "");
|
||||
}
|
||||
} else {
|
||||
type = status_prompt_type(typestr);
|
||||
type = prompt_type(typestr);
|
||||
if (type == PROMPT_TYPE_INVALID) {
|
||||
cmdq_error(item, "invalid type: %s", typestr);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
cmdq_print(item, "History for %s:\n",
|
||||
status_prompt_type_string(type));
|
||||
for (hidx = 0; hidx < status_prompt_hsize[type]; hidx++) {
|
||||
cmdq_print(item, "%d: %s", hidx + 1,
|
||||
status_prompt_hlist[type][hidx]);
|
||||
cmdq_print(item, "History for %s:\n", prompt_type_string(type));
|
||||
for (h = 0; h < prompt_history_size(type); h++) {
|
||||
v = prompt_history_get(type, h);
|
||||
cmdq_print(item, "%d: %s", h + 1, v);
|
||||
}
|
||||
cmdq_print(item, "%s", "");
|
||||
}
|
||||
|
||||
@@ -38,11 +38,12 @@ const struct cmd_entry cmd_new_pane_entry = {
|
||||
.name = "new-pane",
|
||||
.alias = "newp",
|
||||
|
||||
.args = { "bc:de:EfF:hIkl:Lm:p:PR:s:S:t:vx:X:y:Y:Z", 0, -1, NULL },
|
||||
.usage = "[-bdefhIklPvZ] [-c start-directory] [-e environment] "
|
||||
.args = { "bB:c:de:EfF:hIkl:Lm:p:PR:s:S:t:T:vWx:X:y:Y:Z", 0, -1, NULL },
|
||||
.usage = "[-bdefhIklPvWZ] [-B border-lines] "
|
||||
"[-c start-directory] [-e environment] "
|
||||
"[-F format] [-l size] [-m message] [-p percentage] "
|
||||
"[-s style] [-S active-border-style] "
|
||||
"[-R inactive-border-style] [-x width] [-y height] "
|
||||
"[-R inactive-border-style] [-T title] [-x width] [-y height] "
|
||||
"[-X x-position] [-Y y-position] " CMD_TARGET_PANE_USAGE " "
|
||||
"[shell-command [argument ...]]",
|
||||
|
||||
@@ -56,11 +57,11 @@ const struct cmd_entry cmd_split_window_entry = {
|
||||
.name = "split-window",
|
||||
.alias = "splitw",
|
||||
|
||||
.args = { "bc:de:EfF:hIkl:m:p:PR:s:S:t:vZ", 0, -1, NULL },
|
||||
.usage = "[-bdefhIklPvZ] [-c start-directory] [-e environment] "
|
||||
.args = { "bc:de:EfF:hIkl:m:p:PR:s:S:t:T:vWZ", 0, -1, NULL },
|
||||
.usage = "[-bdefhIklPvWZ] [-c start-directory] [-e environment] "
|
||||
"[-F format] [-l size] [-m message] [-p percentage] "
|
||||
"[-s style] [-S active-border-style] "
|
||||
"[-R inactive-border-style] " CMD_TARGET_PANE_USAGE " "
|
||||
"[-R inactive-border-style] [-T title] " CMD_TARGET_PANE_USAGE " "
|
||||
"[shell-command [argument ...]]",
|
||||
|
||||
.target = { 't', CMD_FIND_PANE, 0 },
|
||||
@@ -84,15 +85,17 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
struct layout_cell *lc = NULL;
|
||||
struct cmd_find_state fs;
|
||||
int input, empty, is_floating, flags = 0;
|
||||
const char *template, *style;
|
||||
char *cause = NULL, *cp;
|
||||
const char *template, *style, *value;
|
||||
char *cause = NULL, *cp, *title;
|
||||
const struct options_table_entry *oe;
|
||||
struct args_value *av;
|
||||
enum pane_lines lines;
|
||||
u_int count = args_count(args);
|
||||
|
||||
if (cmd_get_entry(self) == &cmd_new_pane_entry)
|
||||
is_floating = !args_has(args, 'L');
|
||||
else
|
||||
is_floating = 0;
|
||||
is_floating = window_pane_is_floating(wp);
|
||||
|
||||
flags = is_floating ? SPAWN_FLOATING : 0;
|
||||
if (args_has(args, 'b'))
|
||||
@@ -114,12 +117,24 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if (empty)
|
||||
flags |= SPAWN_EMPTY;
|
||||
|
||||
if ((value = args_get(args, 'B')) == NULL)
|
||||
lines = window_get_pane_lines(w);
|
||||
else {
|
||||
oe = options_search("pane-border-lines");
|
||||
lines = options_find_choice(oe, value, &cause);
|
||||
if (cause != NULL) {
|
||||
cmdq_error(item, "pane-border-lines %s", cause);
|
||||
free(cause);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_floating)
|
||||
lc = layout_get_floating_cell(item, args, w, wp, &cause);
|
||||
lc = layout_get_floating_cell(item, args, lines, w, wp, &cause);
|
||||
else
|
||||
lc = layout_get_tiled_cell(item, args, w, wp, flags, &cause);
|
||||
if (cause != NULL) {
|
||||
cmdq_error(item, "size or position %s", cause);
|
||||
cmdq_error(item, "%s", cause);
|
||||
free(cause);
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
@@ -152,10 +167,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if ((new_wp = spawn_pane(&sc, &cause)) == NULL) {
|
||||
cmdq_error(item, "create pane failed: %s", cause);
|
||||
free(cause);
|
||||
if (sc.argv != NULL)
|
||||
cmd_free_argv(sc.argc, sc.argv);
|
||||
environ_free(sc.environ);
|
||||
return (CMD_RETURN_ERROR);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
style = args_get(args, 's');
|
||||
@@ -163,7 +175,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if (options_set_string(new_wp->options, "window-style", 0,
|
||||
"%s", style) == NULL) {
|
||||
cmdq_error(item, "bad style: %s", style);
|
||||
return (CMD_RETURN_ERROR);
|
||||
goto fail;
|
||||
}
|
||||
options_set_string(new_wp->options, "window-active-style", 0,
|
||||
"%s", style);
|
||||
@@ -175,7 +187,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if (options_set_string(new_wp->options,
|
||||
"pane-active-border-style", 0, "%s", style) == NULL) {
|
||||
cmdq_error(item, "bad active border style: %s", style);
|
||||
return (CMD_RETURN_ERROR);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
style = args_get(args, 'R');
|
||||
@@ -184,9 +196,11 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
"%s", style) == NULL) {
|
||||
cmdq_error(item, "bad inactive border style: %s",
|
||||
style);
|
||||
return (CMD_RETURN_ERROR);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
if (args_has(args, 'B'))
|
||||
options_set_number(new_wp->options, "pane-border-lines", lines);
|
||||
if (args_has(args, 'k') || args_has(args, 'm')) {
|
||||
options_set_number(new_wp->options, "remain-on-exit", 3);
|
||||
if (args_has(args, 'm')) {
|
||||
@@ -195,6 +209,12 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
args_get(args, 'm'));
|
||||
}
|
||||
}
|
||||
if (args_has(args, 'T')) {
|
||||
title = format_single_from_target(item, args_get(args, 'T'));
|
||||
screen_set_title(&new_wp->base, title, 0);
|
||||
notify_pane("pane-title-changed", new_wp);
|
||||
free(title);
|
||||
}
|
||||
|
||||
if (input) {
|
||||
switch (window_pane_start_input(new_wp, item, &cause)) {
|
||||
@@ -205,10 +225,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
window_remove_pane(wp->window, new_wp);
|
||||
cmdq_error(item, "%s", cause);
|
||||
free(cause);
|
||||
if (sc.argv != NULL)
|
||||
cmd_free_argv(sc.argc, sc.argv);
|
||||
environ_free(sc.environ);
|
||||
return (CMD_RETURN_ERROR);
|
||||
goto fail;
|
||||
case 1:
|
||||
input = 0;
|
||||
break;
|
||||
@@ -239,5 +256,24 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
|
||||
environ_free(sc.environ);
|
||||
if (input)
|
||||
return (CMD_RETURN_WAIT);
|
||||
|
||||
if (args_has(args, 'W')) {
|
||||
/*
|
||||
* With -W, block this command queue item until the pane's
|
||||
* command exits; window_pane_wait_finish will be called to
|
||||
* continue it.
|
||||
*/
|
||||
new_wp->wait_item = item;
|
||||
return (CMD_RETURN_WAIT);
|
||||
}
|
||||
return (CMD_RETURN_NORMAL);
|
||||
|
||||
fail:
|
||||
if (sc.argv != NULL)
|
||||
cmd_free_argv(sc.argc, sc.argv);
|
||||
environ_free(sc.environ);
|
||||
layout_destroy_cell(w, lc, &w->layout_root);
|
||||
|
||||
return (CMD_RETURN_ERROR);
|
||||
|
||||
}
|
||||
|
||||
@@ -79,12 +79,6 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
if (src_wp == dst_wp)
|
||||
goto out;
|
||||
|
||||
if (window_pane_is_floating(src_wp) ||
|
||||
window_pane_is_floating(dst_wp)) {
|
||||
cmdq_error(item, "cannot swap floating panes");
|
||||
return (CMD_RETURN_ERROR);
|
||||
}
|
||||
|
||||
server_client_remove_pane(src_wp);
|
||||
server_client_remove_pane(dst_wp);
|
||||
|
||||
@@ -114,10 +108,6 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
dst_wp->layout_cell = src_lc;
|
||||
dst_lc->wp = src_wp;
|
||||
src_wp->layout_cell = dst_lc;
|
||||
if (window_pane_is_floating(src_wp) != window_pane_is_floating(dst_wp)) {
|
||||
src_wp->layout_cell->flags ^= LAYOUT_CELL_FLOATING;
|
||||
dst_wp->layout_cell->flags ^= LAYOUT_CELL_FLOATING;
|
||||
}
|
||||
|
||||
src_wp->window = dst_w;
|
||||
options_set_parent(src_wp->options, dst_w->options);
|
||||
@@ -153,9 +143,11 @@ cmd_swap_pane_exec(struct cmd *self, struct cmdq_item *item)
|
||||
colour_palette_from_option(&src_wp->palette, src_wp->options);
|
||||
colour_palette_from_option(&dst_wp->palette, dst_wp->options);
|
||||
layout_fix_panes(src_w, NULL);
|
||||
redraw_invalidate_scene(src_w);
|
||||
server_redraw_window(src_w);
|
||||
}
|
||||
layout_fix_panes(dst_w, NULL);
|
||||
redraw_invalidate_scene(dst_w);
|
||||
server_redraw_window(dst_w);
|
||||
|
||||
notify_window("window-layout-changed", src_w);
|
||||
|
||||
6
cmd.c
6
cmd.c
@@ -69,6 +69,7 @@ extern const struct cmd_entry cmd_load_buffer_entry;
|
||||
extern const struct cmd_entry cmd_lock_client_entry;
|
||||
extern const struct cmd_entry cmd_lock_server_entry;
|
||||
extern const struct cmd_entry cmd_lock_session_entry;
|
||||
extern const struct cmd_entry cmd_hide_pane_entry;
|
||||
extern const struct cmd_entry cmd_move_pane_entry;
|
||||
extern const struct cmd_entry cmd_move_window_entry;
|
||||
extern const struct cmd_entry cmd_new_pane_entry;
|
||||
@@ -106,6 +107,7 @@ extern const struct cmd_entry cmd_show_environment_entry;
|
||||
extern const struct cmd_entry cmd_show_hooks_entry;
|
||||
extern const struct cmd_entry cmd_show_messages_entry;
|
||||
extern const struct cmd_entry cmd_show_options_entry;
|
||||
extern const struct cmd_entry cmd_show_pane_entry;
|
||||
extern const struct cmd_entry cmd_show_prompt_history_entry;
|
||||
extern const struct cmd_entry cmd_show_window_options_entry;
|
||||
extern const struct cmd_entry cmd_source_file_entry;
|
||||
@@ -115,6 +117,7 @@ extern const struct cmd_entry cmd_suspend_client_entry;
|
||||
extern const struct cmd_entry cmd_swap_pane_entry;
|
||||
extern const struct cmd_entry cmd_swap_window_entry;
|
||||
extern const struct cmd_entry cmd_switch_client_entry;
|
||||
extern const struct cmd_entry cmd_switch_mode_entry;
|
||||
extern const struct cmd_entry cmd_unbind_key_entry;
|
||||
extern const struct cmd_entry cmd_unlink_window_entry;
|
||||
extern const struct cmd_entry cmd_wait_for_entry;
|
||||
@@ -162,6 +165,7 @@ const struct cmd_entry *cmd_table[] = {
|
||||
&cmd_lock_client_entry,
|
||||
&cmd_lock_server_entry,
|
||||
&cmd_lock_session_entry,
|
||||
&cmd_hide_pane_entry,
|
||||
&cmd_move_pane_entry,
|
||||
&cmd_move_window_entry,
|
||||
&cmd_new_pane_entry,
|
||||
@@ -199,6 +203,7 @@ const struct cmd_entry *cmd_table[] = {
|
||||
&cmd_show_hooks_entry,
|
||||
&cmd_show_messages_entry,
|
||||
&cmd_show_options_entry,
|
||||
&cmd_show_pane_entry,
|
||||
&cmd_show_prompt_history_entry,
|
||||
&cmd_show_window_options_entry,
|
||||
&cmd_source_file_entry,
|
||||
@@ -208,6 +213,7 @@ const struct cmd_entry *cmd_table[] = {
|
||||
&cmd_swap_pane_entry,
|
||||
&cmd_swap_window_entry,
|
||||
&cmd_switch_client_entry,
|
||||
&cmd_switch_mode_entry,
|
||||
&cmd_unbind_key_entry,
|
||||
&cmd_unlink_window_entry,
|
||||
&cmd_wait_for_entry,
|
||||
|
||||
166
colour.c
166
colour.c
@@ -26,6 +26,85 @@
|
||||
|
||||
#include "tmux.h"
|
||||
|
||||
/* Theme colour slots and their server options. */
|
||||
static const struct {
|
||||
const char *name;
|
||||
const char *dark_option;
|
||||
const char *light_option;
|
||||
int terminal_colour;
|
||||
} colour_theme_table[] = {
|
||||
{ "themeblack",
|
||||
"dark-theme-black",
|
||||
"light-theme-black",
|
||||
0
|
||||
},
|
||||
{ "themewhite",
|
||||
"dark-theme-white",
|
||||
"light-theme-white",
|
||||
7
|
||||
},
|
||||
{ "themelightgrey",
|
||||
"dark-theme-light-grey",
|
||||
"light-theme-light-grey",
|
||||
7
|
||||
},
|
||||
{ "themedarkgrey",
|
||||
"dark-theme-dark-grey",
|
||||
"light-theme-dark-grey",
|
||||
0
|
||||
},
|
||||
{ "themegreen",
|
||||
"dark-theme-green",
|
||||
"light-theme-green",
|
||||
2
|
||||
},
|
||||
{ "themeyellow",
|
||||
"dark-theme-yellow",
|
||||
"light-theme-yellow",
|
||||
3
|
||||
},
|
||||
{ "themered",
|
||||
"dark-theme-red",
|
||||
"light-theme-red",
|
||||
1
|
||||
},
|
||||
{ "themeblue",
|
||||
"dark-theme-blue",
|
||||
"light-theme-blue",
|
||||
4
|
||||
},
|
||||
{ "themecyan",
|
||||
"dark-theme-cyan",
|
||||
"light-theme-cyan",
|
||||
6
|
||||
},
|
||||
{ "thememagenta",
|
||||
"dark-theme-magenta",
|
||||
"light-theme-magenta",
|
||||
5
|
||||
}
|
||||
};
|
||||
|
||||
/* Get theme colour option. */
|
||||
const char *
|
||||
colour_theme_option(u_int n, enum client_theme theme)
|
||||
{
|
||||
if (n >= nitems(colour_theme_table))
|
||||
return (NULL);
|
||||
if (theme == THEME_LIGHT)
|
||||
return (colour_theme_table[n].light_option);
|
||||
return (colour_theme_table[n].dark_option);
|
||||
}
|
||||
|
||||
/* Get theme terminal colour. */
|
||||
int
|
||||
colour_theme_terminal_colour(u_int n)
|
||||
{
|
||||
if (n >= nitems(colour_theme_table))
|
||||
return (8);
|
||||
return (colour_theme_table[n].terminal_colour);
|
||||
}
|
||||
|
||||
static int
|
||||
colour_dist_sq(int R, int G, int B, int r, int g, int b)
|
||||
{
|
||||
@@ -120,6 +199,28 @@ colour_force_rgb(int c)
|
||||
return (-1);
|
||||
}
|
||||
|
||||
/* Dim colour by a percentage. */
|
||||
int
|
||||
colour_dim(int c, u_int dim)
|
||||
{
|
||||
u_char r, g, b;
|
||||
|
||||
if (dim == 0 || COLOUR_DEFAULT(c) || (c & COLOUR_FLAG_THEME))
|
||||
return (c);
|
||||
if (dim >= 100)
|
||||
return (colour_join_rgb(0, 0, 0));
|
||||
|
||||
c = colour_force_rgb(c);
|
||||
if (c == -1)
|
||||
return (-1);
|
||||
colour_split_rgb(c, &r, &g, &b);
|
||||
|
||||
r = (r * (100 - dim)) / 100;
|
||||
g = (g * (100 - dim)) / 100;
|
||||
b = (b * (100 - dim)) / 100;
|
||||
return (colour_join_rgb(r, g, b));
|
||||
}
|
||||
|
||||
/* Convert colour to a string. */
|
||||
const char *
|
||||
colour_tostring(int c)
|
||||
@@ -130,6 +231,13 @@ colour_tostring(int c)
|
||||
if (c == -1)
|
||||
return ("none");
|
||||
|
||||
if (c & COLOUR_FLAG_THEME) {
|
||||
c &= 0xff;
|
||||
if (c >= 0 && (u_int)c < nitems(colour_theme_table))
|
||||
return (colour_theme_table[c].name);
|
||||
return ("invalid");
|
||||
}
|
||||
|
||||
if (c & COLOUR_FLAG_RGB) {
|
||||
colour_split_rgb(c, &r, &g, &b);
|
||||
xsnprintf(s, sizeof s, "#%02x%02x%02x", r, g, b);
|
||||
@@ -182,6 +290,58 @@ colour_tostring(int c)
|
||||
return ("invalid");
|
||||
}
|
||||
|
||||
/* Convert colour to an SGR escape sequence. */
|
||||
const char *
|
||||
colour_toescape(struct client *c, int colour, int bg)
|
||||
{
|
||||
static char s[32];
|
||||
u_char r, g, b;
|
||||
int n, flags = (TERM_256COLOURS|TERM_RGBCOLOURS);
|
||||
u_int o = (bg ? 40 : 30);
|
||||
|
||||
if (c != NULL && (c->tty.flags & TTY_OPENED) && c->tty.term != NULL)
|
||||
flags = c->tty.term->flags;
|
||||
|
||||
if (colour & COLOUR_FLAG_THEME) {
|
||||
n = colour & 0xff;
|
||||
if (c != NULL && (u_int)n < COLOUR_THEME_COUNT)
|
||||
colour = c->theme_colours[n];
|
||||
else
|
||||
colour = colour_theme_terminal_colour(n);
|
||||
}
|
||||
|
||||
if (colour == 8 || colour == 9) {
|
||||
xsnprintf(s, sizeof s, "\033[%dm", o + 9);
|
||||
return (s);
|
||||
}
|
||||
|
||||
if ((~flags & TERM_RGBCOLOURS) & (colour & COLOUR_FLAG_RGB)) {
|
||||
colour_split_rgb(colour, &r, &g, &b);
|
||||
colour = colour_find_rgb(r, g, b);
|
||||
}
|
||||
if ((~flags & TERM_256COLOURS) & (colour & COLOUR_FLAG_256))
|
||||
colour = colour_256to16(colour);
|
||||
|
||||
if (colour & COLOUR_FLAG_RGB) {
|
||||
colour_split_rgb(colour, &r, &g, &b);
|
||||
xsnprintf(s, sizeof s, "\033[%d;2;%u;%u;%um", o + 8, r, g, b);
|
||||
return (s);
|
||||
}
|
||||
if (colour & COLOUR_FLAG_256) {
|
||||
xsnprintf(s, sizeof s, "\033[%d;5;%um", o + 8, colour & 0xff);
|
||||
return (s);
|
||||
}
|
||||
if (colour >= 0 && colour <= 7) {
|
||||
xsnprintf(s, sizeof s, "\033[%dm", colour + o);
|
||||
return (s);
|
||||
}
|
||||
if (colour >= 90 && colour <= 97) {
|
||||
xsnprintf(s, sizeof s, "\033[%dm", colour + o - 30);
|
||||
return (s);
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Convert background colour to theme. */
|
||||
enum client_theme
|
||||
colour_totheme(int c)
|
||||
@@ -230,6 +390,7 @@ colour_fromstring(const char *s)
|
||||
const char *cp;
|
||||
int n;
|
||||
u_char r, g, b;
|
||||
u_int i;
|
||||
|
||||
if (*s == '#' && strlen(s) == 7) {
|
||||
for (cp = s + 1; isxdigit((u_char) *cp); cp++)
|
||||
@@ -260,6 +421,11 @@ colour_fromstring(const char *s)
|
||||
if (strcasecmp(s, "terminal") == 0)
|
||||
return (9);
|
||||
|
||||
for (i = 0; i < nitems(colour_theme_table); i++) {
|
||||
if (strcasecmp(s, colour_theme_table[i].name) == 0)
|
||||
return (i|COLOUR_FLAG_THEME);
|
||||
}
|
||||
|
||||
if (strcasecmp(s, "black") == 0 || strcmp(s, "0") == 0)
|
||||
return (0);
|
||||
if (strcasecmp(s, "red") == 0 || strcmp(s, "1") == 0)
|
||||
|
||||
@@ -339,7 +339,7 @@ static int
|
||||
getopt_internal(int nargc, char * const *nargv, const char *options,
|
||||
const struct option *long_options, int *idx, int flags)
|
||||
{
|
||||
char *oli; /* option letter list index */
|
||||
const char *oli; /* option letter list index */
|
||||
int optchar, short_too;
|
||||
static int posixly_correct = -1;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# configure.ac
|
||||
|
||||
AC_INIT([tmux], 3.7)
|
||||
AC_INIT([tmux], next-3.7)
|
||||
AC_PREREQ([2.60])
|
||||
|
||||
AC_CONFIG_AUX_DIR(etc)
|
||||
|
||||
@@ -224,6 +224,14 @@ control_notify_session_window_changed(struct session *s)
|
||||
{
|
||||
struct client *c;
|
||||
|
||||
/*
|
||||
* A deferred session-window-changed notification can fire after the
|
||||
* session has been destroyed (which sets curw to NULL) but is kept
|
||||
* alive by the notification's reference. Skip the notification.
|
||||
*/
|
||||
if (s->curw == NULL)
|
||||
return;
|
||||
|
||||
TAILQ_FOREACH(c, &clients, entry) {
|
||||
if (!CONTROL_SHOULD_NOTIFY_CLIENT(c))
|
||||
continue;
|
||||
|
||||
@@ -152,7 +152,8 @@ environ_clear(struct environ *env, const char *name)
|
||||
void
|
||||
environ_put(struct environ *env, const char *var, int flags)
|
||||
{
|
||||
char *name, *value;
|
||||
char *name;
|
||||
const char *value;
|
||||
|
||||
value = strchr(var, '=');
|
||||
if (value == NULL)
|
||||
|
||||
@@ -710,6 +710,7 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base,
|
||||
"AFTER" };
|
||||
size_t size = strlen(expanded);
|
||||
struct screen *os = octx->s, s[TOTAL];
|
||||
struct hyperlinks *hl = os->hyperlinks;
|
||||
struct screen_write_ctx ctx[TOTAL];
|
||||
u_int ocx = os->cx, ocy = os->cy, n, i, width[TOTAL];
|
||||
u_int map[] = { LEFT,
|
||||
@@ -723,7 +724,7 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base,
|
||||
struct grid_cell gc, current_default, base_default;
|
||||
struct style sy, saved_sy;
|
||||
struct utf8_data *ud = &sy.gc.data;
|
||||
const char *cp, *end;
|
||||
const char *cp, *end, *link_uri;
|
||||
enum utf8_state more;
|
||||
char *tmp;
|
||||
struct format_range *fr = NULL, *fr1;
|
||||
@@ -838,6 +839,17 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base,
|
||||
sy.gc.fg = base->fg;
|
||||
}
|
||||
|
||||
/*
|
||||
* Resolve any hyperlink and store it in the cell. The URI
|
||||
* doubles as the internal ID so repeated links share one entry
|
||||
* and the ID stays stable across redraws.
|
||||
*/
|
||||
link_uri = style_link(&sy);
|
||||
if (link_uri != NULL && hl != NULL)
|
||||
sy.gc.link = hyperlinks_put(hl, link_uri, link_uri);
|
||||
else
|
||||
sy.gc.link = 0;
|
||||
|
||||
/* If this style has a fill colour, store it for later. */
|
||||
if (sy.fill != 8)
|
||||
fill = sy.fill;
|
||||
|
||||
419
format.c
419
format.c
@@ -117,6 +117,12 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2)
|
||||
#define FORMAT_NOT_NOT 0x100000
|
||||
#define FORMAT_REPEAT 0x200000
|
||||
#define FORMAT_QUOTE_ARGUMENTS 0x400000
|
||||
#define FORMAT_RELATIVE 0x800000
|
||||
#define FORMAT_CLIENT_TERMCAP 0x1000000
|
||||
#define FORMAT_CLIENT_TERMFEAT 0x2000000
|
||||
#define FORMAT_CLIENT_ENVIRON 0x4000000
|
||||
#define FORMAT_COLOUR_ESC_FG 0x8000000
|
||||
#define FORMAT_COLOUR_ESC_BG 0x10000000
|
||||
|
||||
/* Limit on recursion. */
|
||||
#define FORMAT_LOOP_LIMIT 100
|
||||
@@ -124,6 +130,9 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2)
|
||||
/* Limit on time taken (milliseconds). */
|
||||
#define FORMAT_TIME_LIMIT 100
|
||||
|
||||
/* How often to check the time in long loops. */
|
||||
#define FORMAT_TIME_LOOP_CHECK 10000
|
||||
|
||||
/* Format expand flags. */
|
||||
#define FORMAT_EXPAND_TIME 0x1
|
||||
#define FORMAT_EXPAND_NOJOBS 0x2
|
||||
@@ -569,7 +578,7 @@ format_cb_session_attached_list(struct format_tree *ft)
|
||||
}
|
||||
|
||||
if ((size = EVBUFFER_LENGTH(buffer)) != 0)
|
||||
xasprintf(&value, "%.*s", size, EVBUFFER_DATA(buffer));
|
||||
value = xmemdup(EVBUFFER_DATA(buffer), size);
|
||||
evbuffer_free(buffer);
|
||||
return (value);
|
||||
}
|
||||
@@ -708,7 +717,7 @@ format_cb_window_linked_sessions_list(struct format_tree *ft)
|
||||
}
|
||||
|
||||
if ((size = EVBUFFER_LENGTH(buffer)) != 0)
|
||||
xasprintf(&value, "%.*s", size, EVBUFFER_DATA(buffer));
|
||||
value = xmemdup(EVBUFFER_DATA(buffer), size);
|
||||
evbuffer_free(buffer);
|
||||
return (value);
|
||||
}
|
||||
@@ -762,7 +771,7 @@ format_cb_window_active_sessions_list(struct format_tree *ft)
|
||||
}
|
||||
|
||||
if ((size = EVBUFFER_LENGTH(buffer)) != 0)
|
||||
xasprintf(&value, "%.*s", size, EVBUFFER_DATA(buffer));
|
||||
value = xmemdup(EVBUFFER_DATA(buffer), size);
|
||||
evbuffer_free(buffer);
|
||||
return (value);
|
||||
}
|
||||
@@ -826,7 +835,7 @@ format_cb_window_active_clients_list(struct format_tree *ft)
|
||||
}
|
||||
|
||||
if ((size = EVBUFFER_LENGTH(buffer)) != 0)
|
||||
xasprintf(&value, "%.*s", size, EVBUFFER_DATA(buffer));
|
||||
value = xmemdup(EVBUFFER_DATA(buffer), size);
|
||||
evbuffer_free(buffer);
|
||||
return (value);
|
||||
}
|
||||
@@ -1001,7 +1010,7 @@ format_cb_pane_tabs(struct format_tree *ft)
|
||||
evbuffer_add_printf(buffer, "%u", i);
|
||||
}
|
||||
if ((size = EVBUFFER_LENGTH(buffer)) != 0)
|
||||
xasprintf(&value, "%.*s", size, EVBUFFER_DATA(buffer));
|
||||
value = xmemdup(EVBUFFER_DATA(buffer), size);
|
||||
evbuffer_free(buffer);
|
||||
return (value);
|
||||
}
|
||||
@@ -1016,7 +1025,7 @@ format_cb_pane_fg(struct format_tree *ft)
|
||||
if (wp == NULL)
|
||||
return (NULL);
|
||||
|
||||
tty_default_colours(&gc, wp);
|
||||
tty_default_colours(&gc, wp, NULL);
|
||||
return (xstrdup(colour_tostring(gc.fg)));
|
||||
}
|
||||
|
||||
@@ -1053,7 +1062,7 @@ format_cb_pane_bg(struct format_tree *ft)
|
||||
if (wp == NULL)
|
||||
return (NULL);
|
||||
|
||||
tty_default_colours(&gc, wp);
|
||||
tty_default_colours(&gc, wp, NULL);
|
||||
return (xstrdup(colour_tostring(gc.bg)));
|
||||
}
|
||||
|
||||
@@ -1085,7 +1094,7 @@ format_cb_session_group_list(struct format_tree *ft)
|
||||
}
|
||||
|
||||
if ((size = EVBUFFER_LENGTH(buffer)) != 0)
|
||||
xasprintf(&value, "%.*s", size, EVBUFFER_DATA(buffer));
|
||||
value = xmemdup(EVBUFFER_DATA(buffer), size);
|
||||
evbuffer_free(buffer);
|
||||
return (value);
|
||||
}
|
||||
@@ -1125,7 +1134,7 @@ format_cb_session_group_attached_list(struct format_tree *ft)
|
||||
}
|
||||
|
||||
if ((size = EVBUFFER_LENGTH(buffer)) != 0)
|
||||
xasprintf(&value, "%.*s", size, EVBUFFER_DATA(buffer));
|
||||
value = xmemdup(EVBUFFER_DATA(buffer), size);
|
||||
evbuffer_free(buffer);
|
||||
return (value);
|
||||
}
|
||||
@@ -1153,15 +1162,13 @@ static void *
|
||||
format_cb_pane_at_top(struct format_tree *ft)
|
||||
{
|
||||
struct window_pane *wp = ft->wp;
|
||||
struct window *w;
|
||||
int status, flag;
|
||||
char *value;
|
||||
|
||||
if (wp == NULL)
|
||||
return (NULL);
|
||||
w = wp->window;
|
||||
|
||||
status = options_get_number(w->options, "pane-border-status");
|
||||
status = window_pane_get_pane_status(wp);
|
||||
if (status == PANE_STATUS_TOP)
|
||||
flag = (wp->yoff == 1);
|
||||
else
|
||||
@@ -1183,7 +1190,7 @@ format_cb_pane_at_bottom(struct format_tree *ft)
|
||||
return (NULL);
|
||||
w = wp->window;
|
||||
|
||||
status = options_get_number(w->options, "pane-border-status");
|
||||
status = window_pane_get_pane_status(wp);
|
||||
if (status == PANE_STATUS_BOTTOM)
|
||||
flag = (wp->yoff + (int)wp->sy == (int)w->sy - 1);
|
||||
else
|
||||
@@ -1472,6 +1479,33 @@ format_cb_client_cell_width(struct format_tree *ft)
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for client_colours. */
|
||||
static void *
|
||||
format_cb_client_colours(struct format_tree *ft)
|
||||
{
|
||||
struct tty_term *term;
|
||||
u_int colours;
|
||||
|
||||
if (ft->c == NULL || (~ft->c->tty.flags & TTY_STARTED))
|
||||
return (NULL);
|
||||
term = ft->c->tty.term;
|
||||
|
||||
if (term->flags & TERM_RGBCOLOURS)
|
||||
colours = 16777216;
|
||||
else if (term->flags & TERM_256COLOURS)
|
||||
colours = 256;
|
||||
else {
|
||||
colours = tty_term_number(term, TTYC_COLORS);
|
||||
if (colours < 8)
|
||||
colours = 2;
|
||||
else if (colours < 16)
|
||||
colours = 8;
|
||||
else
|
||||
colours = 16;
|
||||
}
|
||||
return (format_printf("%u", colours));
|
||||
}
|
||||
|
||||
/* Callback for client_control_mode. */
|
||||
static void *
|
||||
format_cb_client_control_mode(struct format_tree *ft)
|
||||
@@ -1851,15 +1885,6 @@ format_cb_keypad_flag(struct format_tree *ft)
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for loop_last_flag. */
|
||||
static void *
|
||||
format_cb_loop_last_flag(struct format_tree *ft)
|
||||
{
|
||||
if (ft->flags & FORMAT_LAST)
|
||||
return (xstrdup("1"));
|
||||
return (xstrdup("0"));
|
||||
}
|
||||
|
||||
/* Callback for mouse_all_flag. */
|
||||
static void *
|
||||
format_cb_mouse_all_flag(struct format_tree *ft)
|
||||
@@ -2144,6 +2169,20 @@ format_cb_pane_height(struct format_tree *ft)
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for pane_hidden_flag. */
|
||||
static void *
|
||||
format_cb_pane_hidden_flag(struct format_tree *ft)
|
||||
{
|
||||
struct window_pane *wp = ft->wp;
|
||||
|
||||
if (wp != NULL) {
|
||||
if (window_pane_is_hidden(wp))
|
||||
return (xstrdup("1"));
|
||||
return (xstrdup("0"));
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/* Callback for pane_id. */
|
||||
static void *
|
||||
format_cb_pane_id(struct format_tree *ft)
|
||||
@@ -3218,6 +3257,9 @@ static const struct format_table_entry format_table[] = {
|
||||
{ "client_cell_width", FORMAT_TABLE_STRING,
|
||||
format_cb_client_cell_width
|
||||
},
|
||||
{ "client_colours", FORMAT_TABLE_STRING,
|
||||
format_cb_client_colours
|
||||
},
|
||||
{ "client_control_mode", FORMAT_TABLE_STRING,
|
||||
format_cb_client_control_mode
|
||||
},
|
||||
@@ -3344,9 +3386,6 @@ static const struct format_table_entry format_table[] = {
|
||||
{ "last_window_index", FORMAT_TABLE_STRING,
|
||||
format_cb_last_window_index
|
||||
},
|
||||
{ "loop_last_flag", FORMAT_TABLE_STRING,
|
||||
format_cb_loop_last_flag
|
||||
},
|
||||
{ "mouse_all_flag", FORMAT_TABLE_STRING,
|
||||
format_cb_mouse_all_flag
|
||||
},
|
||||
@@ -3449,6 +3488,9 @@ static const struct format_table_entry format_table[] = {
|
||||
{ "pane_height", FORMAT_TABLE_STRING,
|
||||
format_cb_pane_height
|
||||
},
|
||||
{ "pane_hidden_flag", FORMAT_TABLE_STRING,
|
||||
format_cb_pane_hidden_flag
|
||||
},
|
||||
{ "pane_id", FORMAT_TABLE_STRING,
|
||||
format_cb_pane_id
|
||||
},
|
||||
@@ -3774,7 +3816,7 @@ format_table_compare(const void *key0, const void *entry0)
|
||||
}
|
||||
|
||||
/* Get a format callback. */
|
||||
static struct format_table_entry *
|
||||
static const struct format_table_entry *
|
||||
format_table_get(const char *key)
|
||||
{
|
||||
return (bsearch(key, format_table, nitems(format_table),
|
||||
@@ -4063,12 +4105,52 @@ format_pretty_time(time_t t, int seconds)
|
||||
return (xstrdup(s));
|
||||
}
|
||||
|
||||
/* Make a relative time. */
|
||||
static char *
|
||||
format_relative_time(time_t t)
|
||||
{
|
||||
time_t now, age;
|
||||
u_int d, h, m, s;
|
||||
char out[32];
|
||||
|
||||
time(&now);
|
||||
if (t > now)
|
||||
return (NULL);
|
||||
if (t == now)
|
||||
return (xstrdup("0s"));
|
||||
age = now - t;
|
||||
|
||||
d = age / 86400;
|
||||
h = (age % 86400) / 3600;
|
||||
m = (age % 3600) / 60;
|
||||
s = age % 60;
|
||||
|
||||
if (d != 0) {
|
||||
if (h != 0)
|
||||
xsnprintf(out, sizeof out, "%ud%uh", d, h);
|
||||
else
|
||||
xsnprintf(out, sizeof out, "%ud", d);
|
||||
} else if (h != 0) {
|
||||
if (m != 0)
|
||||
xsnprintf(out, sizeof out, "%uh%um", h, m);
|
||||
else
|
||||
xsnprintf(out, sizeof out, "%uh", h);
|
||||
} else if (m != 0) {
|
||||
if (s != 0)
|
||||
xsnprintf(out, sizeof out, "%um%us", m, s);
|
||||
else
|
||||
xsnprintf(out, sizeof out, "%um", m);
|
||||
} else
|
||||
xsnprintf(out, sizeof out, "%us", s);
|
||||
return (xstrdup(out));
|
||||
}
|
||||
|
||||
/* Find a format entry. */
|
||||
static char *
|
||||
format_find(struct format_tree *ft, const char *key, int modifiers,
|
||||
const char *time_format)
|
||||
{
|
||||
struct format_table_entry *fte;
|
||||
const struct format_table_entry *fte;
|
||||
void *value;
|
||||
struct format_entry *fe, fe_find;
|
||||
struct environ_entry *envent;
|
||||
@@ -4144,7 +4226,9 @@ found:
|
||||
}
|
||||
if (t == 0)
|
||||
return (NULL);
|
||||
if (modifiers & FORMAT_PRETTY)
|
||||
if (modifiers & FORMAT_RELATIVE)
|
||||
found = format_relative_time(t);
|
||||
else if (modifiers & FORMAT_PRETTY)
|
||||
found = format_pretty_time(t, 0);
|
||||
else {
|
||||
if (time_format != NULL) {
|
||||
@@ -4193,10 +4277,14 @@ found:
|
||||
|
||||
/* Check if format has not taken too long. */
|
||||
static int
|
||||
format_check_time(struct format_expand_state *es)
|
||||
format_check_time(struct format_expand_state *es, u_int *check)
|
||||
{
|
||||
uint64_t t = get_timer();
|
||||
uint64_t t;
|
||||
|
||||
if (check != NULL && ++*check % FORMAT_TIME_LOOP_CHECK != 0)
|
||||
return (1);
|
||||
|
||||
t = get_timer();
|
||||
if (t - es->start_time < FORMAT_TIME_LIMIT)
|
||||
return (1);
|
||||
t -= es->start_time;
|
||||
@@ -4207,21 +4295,24 @@ format_check_time(struct format_expand_state *es)
|
||||
|
||||
/* Unescape escaped characters. */
|
||||
static char *
|
||||
format_unescape(struct format_expand_state *es, const char *s)
|
||||
format_unescape(struct format_expand_state *es, const char *s, size_t n)
|
||||
{
|
||||
char *out, *cp;
|
||||
int brackets = 0;
|
||||
const char *end = s + n;
|
||||
char *out, *cp;
|
||||
int brackets = 0;
|
||||
u_int check = 0;
|
||||
|
||||
cp = out = xmalloc(strlen(s) + 1);
|
||||
for (; *s != '\0'; s++) {
|
||||
if (!format_check_time(es)){
|
||||
cp = out = xmalloc(n + 1);
|
||||
for (; s != end; s++) {
|
||||
if (!format_check_time(es, &check)) {
|
||||
free(out);
|
||||
return (xstrdup(""));
|
||||
}
|
||||
if (*s == '#' && s[1] == '{')
|
||||
if (*s == '#' && s + 1 != end && s[1] == '{')
|
||||
brackets++;
|
||||
if (brackets == 0 &&
|
||||
*s == '#' &&
|
||||
s + 1 != end &&
|
||||
strchr(",#{}:", s[1]) != NULL) {
|
||||
*cp++ = *++s;
|
||||
continue;
|
||||
@@ -4240,10 +4331,11 @@ format_strip(struct format_expand_state *es, const char *s)
|
||||
{
|
||||
char *out, *cp;
|
||||
int brackets = 0;
|
||||
u_int check = 0;
|
||||
|
||||
cp = out = xmalloc(strlen(s) + 1);
|
||||
for (; *s != '\0'; s++) {
|
||||
if (!format_check_time(es)){
|
||||
if (!format_check_time(es, &check)) {
|
||||
free(out);
|
||||
return (xstrdup(""));
|
||||
}
|
||||
@@ -4267,9 +4359,10 @@ static const char *
|
||||
format_skip1(struct format_expand_state *es, const char *s, const char *end)
|
||||
{
|
||||
int brackets = 0;
|
||||
u_int check = 0;
|
||||
|
||||
for (; *s != '\0'; s++) {
|
||||
if (es != NULL && !format_check_time(es))
|
||||
if (es != NULL && !format_check_time(es, &check))
|
||||
return (NULL);
|
||||
if (*s == '#' && s[1] == '{')
|
||||
brackets++;
|
||||
@@ -4379,7 +4472,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s,
|
||||
|
||||
/*
|
||||
* Modifiers are a ; separated list of the forms:
|
||||
* l,m,C,a,b,c,d,n,t,w,q,E,T,S,W,P,R,<,>
|
||||
* l,m,C,a,b,c,d,I,n,t,w,q,E,T,S,W,P,R,<,>
|
||||
* =a
|
||||
* =/a
|
||||
* =/a/
|
||||
@@ -4398,7 +4491,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s,
|
||||
break;
|
||||
|
||||
/* Check single character modifiers with no arguments. */
|
||||
if (strchr("labcdnwETSWPL!<>", cp[0]) != NULL &&
|
||||
if (strchr("labdnwETSWPL!<>", cp[0]) != NULL &&
|
||||
format_is_end(cp[1])) {
|
||||
format_add_modifier(&list, count, cp, 1, NULL, 0);
|
||||
cp++;
|
||||
@@ -4420,7 +4513,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s,
|
||||
}
|
||||
|
||||
/* Now try single character with arguments. */
|
||||
if (strchr("mCLNPSst=pReqW", cp[0]) == NULL)
|
||||
if (strchr("ImCLNPSst=pReqWc", cp[0]) == NULL)
|
||||
break;
|
||||
c = cp[0];
|
||||
|
||||
@@ -4440,7 +4533,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s,
|
||||
break;
|
||||
|
||||
argv = xcalloc(1, sizeof *argv);
|
||||
value = xstrndup(cp + 1, end - (cp + 1));
|
||||
value = format_unescape(es, cp + 1, end - (cp + 1));
|
||||
argv[0] = format_expand1(es, value);
|
||||
free(value);
|
||||
argc = 1;
|
||||
@@ -4464,7 +4557,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s,
|
||||
cp++;
|
||||
|
||||
argv = xreallocarray(argv, argc + 1, sizeof *argv);
|
||||
value = xstrndup(cp, end - cp);
|
||||
value = format_unescape(es, cp, end - cp);
|
||||
argv[argc++] = format_expand1(es, value);
|
||||
free(value);
|
||||
|
||||
@@ -4481,6 +4574,47 @@ format_build_modifiers(struct format_expand_state *es, const char **s,
|
||||
return (list);
|
||||
}
|
||||
|
||||
/* Match using the fuzzy matcher. */
|
||||
static char *
|
||||
format_match_fuzzy(const char *pattern, const char *text, int positions)
|
||||
{
|
||||
struct evbuffer *buffer;
|
||||
bitstr_t *bs;
|
||||
char *value;
|
||||
size_t size;
|
||||
u_int i, width;
|
||||
|
||||
width = format_width(text);
|
||||
if (width == 0)
|
||||
width = 1;
|
||||
bs = fuzzy_match(pattern, text, width, NULL);
|
||||
if (bs == NULL)
|
||||
return (xstrdup(positions ? "" : "0"));
|
||||
|
||||
if (!positions) {
|
||||
free(bs);
|
||||
return (xstrdup("1"));
|
||||
}
|
||||
|
||||
buffer = evbuffer_new();
|
||||
if (buffer == NULL)
|
||||
fatalx("out of memory");
|
||||
for (i = 0; i < width; i++) {
|
||||
if (!bit_test(bs, i))
|
||||
continue;
|
||||
if (EVBUFFER_LENGTH(buffer) != 0)
|
||||
evbuffer_add(buffer, ",", 1);
|
||||
evbuffer_add_printf(buffer, "%u", i);
|
||||
}
|
||||
if ((size = EVBUFFER_LENGTH(buffer)) != 0)
|
||||
value = xmemdup(EVBUFFER_DATA(buffer), size);
|
||||
else
|
||||
value = xstrdup("");
|
||||
evbuffer_free(buffer);
|
||||
free(bs);
|
||||
return (value);
|
||||
}
|
||||
|
||||
/* Match against an fnmatch(3) pattern or regular expression. */
|
||||
static char *
|
||||
format_match(struct format_modifier *fm, const char *pattern, const char *text)
|
||||
@@ -4491,6 +4625,10 @@ format_match(struct format_modifier *fm, const char *pattern, const char *text)
|
||||
|
||||
if (fm->argc >= 1)
|
||||
s = fm->argv[0];
|
||||
if (strchr(s, 'p') != NULL)
|
||||
return (format_match_fuzzy(pattern, text, 1));
|
||||
if (strchr(s, 'z') != NULL)
|
||||
return (format_match_fuzzy(pattern, text, 0));
|
||||
if (strchr(s, 'r') == NULL) {
|
||||
if (strchr(s, 'i') != NULL)
|
||||
flags |= FNM_CASEFOLD;
|
||||
@@ -4627,17 +4765,19 @@ format_loop_sessions(struct format_expand_state *es, const char *fmt)
|
||||
struct format_tree *nft;
|
||||
struct format_expand_state next;
|
||||
char *all, *active, *use, *expanded, *value;
|
||||
size_t valuelen;
|
||||
struct evbuffer *buffer;
|
||||
size_t size;
|
||||
struct session *s, **l;
|
||||
int i, n, last = 0;
|
||||
int i, n;
|
||||
|
||||
if (format_choose(es, fmt, &all, &active, 0) != 0) {
|
||||
all = xstrdup(fmt);
|
||||
active = NULL;
|
||||
}
|
||||
|
||||
value = xcalloc(1, 1);
|
||||
valuelen = 1;
|
||||
buffer = evbuffer_new();
|
||||
if (buffer == NULL)
|
||||
fatalx("out of memory");
|
||||
|
||||
l = sort_get_sessions(&n, sc);
|
||||
for (i = 0; i < n; i++) {
|
||||
@@ -4649,25 +4789,27 @@ format_loop_sessions(struct format_expand_state *es, const char *fmt)
|
||||
use = active;
|
||||
else
|
||||
use = all;
|
||||
if (i == n - 1)
|
||||
last = FORMAT_LAST;
|
||||
nft = format_create(c, item, FORMAT_NONE, ft->flags|last);
|
||||
nft = format_create(c, item, FORMAT_NONE, ft->flags);
|
||||
format_add(nft, "loop_index", "%d", i);
|
||||
format_add(nft, "loop_last_flag", "%d", i == n - 1);
|
||||
format_defaults(nft, ft->c, s, NULL, NULL);
|
||||
format_copy_state(&next, es, 0);
|
||||
next.ft = nft;
|
||||
expanded = format_expand1(&next, use);
|
||||
format_free(next.ft);
|
||||
|
||||
valuelen += strlen(expanded);
|
||||
value = xrealloc(value, valuelen);
|
||||
|
||||
strlcat(value, expanded, valuelen);
|
||||
evbuffer_add(buffer, expanded, strlen(expanded));
|
||||
free(expanded);
|
||||
}
|
||||
|
||||
free(active);
|
||||
free(all);
|
||||
|
||||
if ((size = EVBUFFER_LENGTH(buffer)) != 0)
|
||||
value = xmemdup(EVBUFFER_DATA(buffer), size);
|
||||
else
|
||||
value = xstrdup("");
|
||||
evbuffer_free(buffer);
|
||||
return (value);
|
||||
}
|
||||
|
||||
@@ -4737,10 +4879,11 @@ format_loop_windows(struct format_expand_state *es, const char *fmt)
|
||||
struct format_tree *nft;
|
||||
struct format_expand_state next;
|
||||
char *all, *active, *use, *expanded, *value;
|
||||
size_t valuelen;
|
||||
struct evbuffer *buffer;
|
||||
size_t size;
|
||||
struct winlink *wl, **l;
|
||||
struct window *w;
|
||||
int i, n, last = 0;
|
||||
int i, n;
|
||||
|
||||
if (ft->s == NULL) {
|
||||
format_log(es, "window loop but no session");
|
||||
@@ -4752,8 +4895,9 @@ format_loop_windows(struct format_expand_state *es, const char *fmt)
|
||||
active = NULL;
|
||||
}
|
||||
|
||||
value = xcalloc(1, 1);
|
||||
valuelen = 1;
|
||||
buffer = evbuffer_new();
|
||||
if (buffer == NULL)
|
||||
fatalx("out of memory");
|
||||
|
||||
l = sort_get_winlinks_session(ft->s, &n, sc);
|
||||
for (i = 0; i < n; i++) {
|
||||
@@ -4764,10 +4908,10 @@ format_loop_windows(struct format_expand_state *es, const char *fmt)
|
||||
use = active;
|
||||
else
|
||||
use = all;
|
||||
if (i == n - 1)
|
||||
last = FORMAT_LAST;
|
||||
nft = format_create(c, item, FORMAT_WINDOW|w->id,
|
||||
ft->flags|last);
|
||||
ft->flags);
|
||||
format_add(nft, "loop_index", "%d", i);
|
||||
format_add(nft, "loop_last_flag", "%d", i == n - 1);
|
||||
format_defaults(nft, ft->c, ft->s, wl, NULL);
|
||||
|
||||
/* Add neighbor window data to the format tree. */
|
||||
@@ -4785,16 +4929,18 @@ format_loop_windows(struct format_expand_state *es, const char *fmt)
|
||||
expanded = format_expand1(&next, use);
|
||||
format_free(nft);
|
||||
|
||||
valuelen += strlen(expanded);
|
||||
value = xrealloc(value, valuelen);
|
||||
|
||||
strlcat(value, expanded, valuelen);
|
||||
evbuffer_add(buffer, expanded, strlen(expanded));
|
||||
free(expanded);
|
||||
}
|
||||
|
||||
free(active);
|
||||
free(all);
|
||||
|
||||
if ((size = EVBUFFER_LENGTH(buffer)) != 0)
|
||||
value = xmemdup(EVBUFFER_DATA(buffer), size);
|
||||
else
|
||||
value = xstrdup("");
|
||||
evbuffer_free(buffer);
|
||||
return (value);
|
||||
}
|
||||
|
||||
@@ -4809,9 +4955,10 @@ format_loop_panes(struct format_expand_state *es, const char *fmt)
|
||||
struct format_tree *nft;
|
||||
struct format_expand_state next;
|
||||
char *all, *active, *use, *expanded, *value;
|
||||
size_t valuelen;
|
||||
struct evbuffer *buffer;
|
||||
size_t size;
|
||||
struct window_pane *wp, **l;
|
||||
int i, n, last = 0;
|
||||
int i, n;
|
||||
|
||||
if (ft->w == NULL) {
|
||||
format_log(es, "pane loop but no window");
|
||||
@@ -4823,8 +4970,9 @@ format_loop_panes(struct format_expand_state *es, const char *fmt)
|
||||
active = NULL;
|
||||
}
|
||||
|
||||
value = xcalloc(1, 1);
|
||||
valuelen = 1;
|
||||
buffer = evbuffer_new();
|
||||
if (buffer == NULL)
|
||||
fatalx("out of memory");
|
||||
|
||||
l = sort_get_panes_window(ft->w, &n, sc);
|
||||
for (i = 0; i < n; i++) {
|
||||
@@ -4834,26 +4982,28 @@ format_loop_panes(struct format_expand_state *es, const char *fmt)
|
||||
use = active;
|
||||
else
|
||||
use = all;
|
||||
if (i == n - 1)
|
||||
last = FORMAT_LAST;
|
||||
nft = format_create(c, item, FORMAT_PANE|wp->id,
|
||||
ft->flags|last);
|
||||
ft->flags);
|
||||
format_add(nft, "loop_index", "%d", i);
|
||||
format_add(nft, "loop_last_flag", "%d", i == n - 1);
|
||||
format_defaults(nft, ft->c, ft->s, ft->wl, wp);
|
||||
format_copy_state(&next, es, 0);
|
||||
next.ft = nft;
|
||||
expanded = format_expand1(&next, use);
|
||||
format_free(nft);
|
||||
|
||||
valuelen += strlen(expanded);
|
||||
value = xrealloc(value, valuelen);
|
||||
|
||||
strlcat(value, expanded, valuelen);
|
||||
evbuffer_add(buffer, expanded, strlen(expanded));
|
||||
free(expanded);
|
||||
}
|
||||
|
||||
free(active);
|
||||
free(all);
|
||||
|
||||
if ((size = EVBUFFER_LENGTH(buffer)) != 0)
|
||||
value = xmemdup(EVBUFFER_DATA(buffer), size);
|
||||
else
|
||||
value = xstrdup("");
|
||||
evbuffer_free(buffer);
|
||||
return (value);
|
||||
}
|
||||
|
||||
@@ -4868,32 +5018,36 @@ format_loop_clients(struct format_expand_state *es, const char *fmt)
|
||||
struct format_tree *nft;
|
||||
struct format_expand_state next;
|
||||
char *expanded, *value;
|
||||
size_t valuelen;
|
||||
int i, n, last = 0;
|
||||
struct evbuffer *buffer;
|
||||
size_t size;
|
||||
int i, n;
|
||||
|
||||
value = xcalloc(1, 1);
|
||||
valuelen = 1;
|
||||
buffer = evbuffer_new();
|
||||
if (buffer == NULL)
|
||||
fatalx("out of memory");
|
||||
|
||||
l = sort_get_clients(&n, sc);
|
||||
for (i = 0; i < n; i++) {
|
||||
c = l[i];
|
||||
format_log(es, "client loop: %s", c->name);
|
||||
if (i == n - 1)
|
||||
last = FORMAT_LAST;
|
||||
nft = format_create(c, item, 0, ft->flags|last);
|
||||
nft = format_create(c, item, 0, ft->flags);
|
||||
format_add(nft, "loop_index", "%d", i);
|
||||
format_add(nft, "loop_last_flag", "%d", i == n - 1);
|
||||
format_defaults(nft, c, ft->s, ft->wl, ft->wp);
|
||||
format_copy_state(&next, es, 0);
|
||||
next.ft = nft;
|
||||
expanded = format_expand1(&next, fmt);
|
||||
format_free(nft);
|
||||
|
||||
valuelen += strlen(expanded);
|
||||
value = xrealloc(value, valuelen);
|
||||
|
||||
strlcat(value, expanded, valuelen);
|
||||
evbuffer_add(buffer, expanded, strlen(expanded));
|
||||
free(expanded);
|
||||
}
|
||||
|
||||
if ((size = EVBUFFER_LENGTH(buffer)) != 0)
|
||||
value = xmemdup(EVBUFFER_DATA(buffer), size);
|
||||
else
|
||||
value = xstrdup("");
|
||||
evbuffer_free(buffer);
|
||||
return (value);
|
||||
}
|
||||
|
||||
@@ -5059,8 +5213,9 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
|
||||
struct format_modifier *list, *cmp = NULL, *search = NULL;
|
||||
struct format_modifier **sub = NULL, *mexp = NULL, *fm;
|
||||
struct format_modifier *bool_op_n = NULL;
|
||||
u_int i, count, nsub = 0, nrep;
|
||||
u_int i, count, nsub = 0, nrep, check = 0;
|
||||
struct format_expand_state next;
|
||||
struct environ_entry *envent;
|
||||
|
||||
/* Set sorting defaults. */
|
||||
sc->order = SORT_ORDER;
|
||||
@@ -5136,6 +5291,12 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
|
||||
break;
|
||||
case 'c':
|
||||
modifiers |= FORMAT_COLOUR;
|
||||
if (fm->argc < 1)
|
||||
break;
|
||||
if (strchr(fm->argv[0], 'f') != NULL)
|
||||
modifiers |= FORMAT_COLOUR_ESC_FG;
|
||||
if (strchr(fm->argv[0], 'b') != NULL)
|
||||
modifiers |= FORMAT_COLOUR_ESC_BG;
|
||||
break;
|
||||
case 'd':
|
||||
modifiers |= FORMAT_DIRNAME;
|
||||
@@ -5143,16 +5304,29 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
|
||||
case 'n':
|
||||
modifiers |= FORMAT_LENGTH;
|
||||
break;
|
||||
case 'I':
|
||||
if (fm->argc < 1)
|
||||
break;
|
||||
if (strchr(fm->argv[0], 'f') != NULL)
|
||||
modifiers |= FORMAT_CLIENT_TERMFEAT;
|
||||
if (strchr(fm->argv[0], 'c') != NULL)
|
||||
modifiers |= FORMAT_CLIENT_TERMCAP;
|
||||
if (strchr(fm->argv[0], 'e') != NULL)
|
||||
modifiers |= FORMAT_CLIENT_ENVIRON;
|
||||
break;
|
||||
case 't':
|
||||
modifiers |= FORMAT_TIMESTRING;
|
||||
if (fm->argc < 1)
|
||||
break;
|
||||
if (strchr(fm->argv[0], 'p') != NULL)
|
||||
modifiers |= FORMAT_PRETTY;
|
||||
else if (strchr(fm->argv[0], 'r') != NULL)
|
||||
modifiers |= FORMAT_RELATIVE;
|
||||
else if (fm->argc >= 2 &&
|
||||
strchr(fm->argv[0], 'f') != NULL) {
|
||||
free(time_format);
|
||||
time_format = format_strip(es, fm->argv[1]);
|
||||
time_format = format_strip(es,
|
||||
fm->argv[1]);
|
||||
}
|
||||
break;
|
||||
case 'q':
|
||||
@@ -5267,10 +5441,42 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
|
||||
}
|
||||
}
|
||||
|
||||
/* Look up client capability, feature or environment. */
|
||||
if ((modifiers & FORMAT_CLIENT_TERMCAP) ||
|
||||
(modifiers & FORMAT_CLIENT_TERMFEAT) ||
|
||||
(modifiers & FORMAT_CLIENT_ENVIRON)) {
|
||||
if (ft->c == NULL ||
|
||||
ft->c->tty.term == NULL ||
|
||||
ft->c->flags & CLIENT_UNATTACHEDFLAGS) {
|
||||
value = xstrdup("");
|
||||
goto done;
|
||||
}
|
||||
if (modifiers & FORMAT_CLIENT_TERMCAP) {
|
||||
if (tty_term_has_name(ft->c->tty.term, copy))
|
||||
value = xstrdup("1");
|
||||
else
|
||||
value = xstrdup("0");
|
||||
}
|
||||
if (modifiers & FORMAT_CLIENT_TERMFEAT) {
|
||||
if (tty_feature_present(ft->c->tty.term, copy))
|
||||
value = xstrdup("1");
|
||||
else
|
||||
value = xstrdup("0");
|
||||
}
|
||||
if (modifiers & FORMAT_CLIENT_ENVIRON) {
|
||||
envent = environ_find(ft->c->environ, copy);
|
||||
if (envent != NULL && envent->value != NULL)
|
||||
value = xstrdup(envent->value);
|
||||
else
|
||||
value = xstrdup("");
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Is this a literal string? */
|
||||
if (modifiers & FORMAT_LITERAL) {
|
||||
format_log(es, "literal string is '%s'", copy);
|
||||
value = format_unescape(es, copy);
|
||||
value = format_unescape(es, copy, strlen(copy));
|
||||
goto done;
|
||||
}
|
||||
|
||||
@@ -5289,11 +5495,28 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
|
||||
/* Is this a colour? */
|
||||
if (modifiers & FORMAT_COLOUR) {
|
||||
new = format_expand1(es, copy);
|
||||
c = colour_fromstring(new);
|
||||
if (c == -1 || (c = colour_force_rgb(c)) == -1)
|
||||
value = xstrdup("");
|
||||
else
|
||||
xasprintf(&value, "%06x", c & 0xffffff);
|
||||
if (modifiers & (FORMAT_COLOUR_ESC_FG|FORMAT_COLOUR_ESC_BG)) {
|
||||
if (strcasecmp(new, "none") == 0)
|
||||
value = xstrdup("\033[0m");
|
||||
else if ((c = colour_fromstring(new)) == -1)
|
||||
value = xstrdup("");
|
||||
else {
|
||||
if (modifiers & FORMAT_COLOUR_ESC_BG)
|
||||
cp = colour_toescape(ft->c, c, 1);
|
||||
else
|
||||
cp = colour_toescape(ft->c, c, 0);
|
||||
if (cp == NULL)
|
||||
value = xstrdup("");
|
||||
else
|
||||
value = xstrdup(cp);
|
||||
}
|
||||
} else {
|
||||
c = colour_fromstring(new);
|
||||
if (c == -1 || (c = colour_force_rgb(c)) == -1)
|
||||
value = xstrdup("");
|
||||
else
|
||||
xasprintf(&value, "%06x", c & 0xffffff);
|
||||
}
|
||||
free(new);
|
||||
goto done;
|
||||
}
|
||||
@@ -5346,7 +5569,7 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
|
||||
else {
|
||||
value = xstrdup("");
|
||||
for (i = 0; i < nrep; i++) {
|
||||
if (!format_check_time(es)) {
|
||||
if (!format_check_time(es, &check)) {
|
||||
free(right);
|
||||
free(left);
|
||||
free(value);
|
||||
@@ -5626,7 +5849,7 @@ format_expand1(struct format_expand_state *es, const char *fmt)
|
||||
int ch, brackets;
|
||||
char expanded[8192];
|
||||
|
||||
if (fmt == NULL || *fmt == '\0' || !format_check_time(es))
|
||||
if (fmt == NULL || *fmt == '\0' || !format_check_time(es, NULL))
|
||||
return (xstrdup(""));
|
||||
|
||||
if (es->loop == FORMAT_LOOP_LIMIT) {
|
||||
|
||||
655
fuzzy.c
Normal file
655
fuzzy.c
Normal file
@@ -0,0 +1,655 @@
|
||||
/* $OpenBSD$ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
||||
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "tmux.h"
|
||||
|
||||
/*
|
||||
* Fuzzy matching in the style of fzf. The pattern is split into groups by |
|
||||
* and each group is split on spaces into terms. A row matches if any group
|
||||
* matches; within a group all positive terms must match and all inverse terms
|
||||
* must not match.
|
||||
*
|
||||
* Plain positive terms are fuzzy subsequences. A leading ' makes a term an
|
||||
* exact substring match, ^ anchors a term at the start and $ anchors it at
|
||||
* the end. A leading ! inverts the term. Plain inverse terms are exact
|
||||
* substring matches rather than inverse fuzzy matches, like fzf.
|
||||
*
|
||||
* Both the pattern and the text are UTF-8. The text may contain tmux style
|
||||
* directives (#[...]); these and their contents are invisible to matching and
|
||||
* occupy no columns, but align= styles do move the surrounding text and are
|
||||
* accounted for exactly as format_draw lays it out (the no-list layout, see
|
||||
* format_draw_none). Matching is smart-case: case is ignored unless the pattern
|
||||
* contains an uppercase character (ASCII case folding only; other characters
|
||||
* are compared exactly by their UTF-8 data).
|
||||
*
|
||||
* On a match a bitstr_t of the requested display width is returned with a bit
|
||||
* set for every column occupied by a matched character, so the caller can
|
||||
* highlight them; NULL is returned if there is no match. A cheap fzf-style
|
||||
* score (matches at the start, after word boundaries and in contiguous runs
|
||||
* score higher) is also produced so callers can rank best-match-first.
|
||||
*/
|
||||
|
||||
#define FUZZY_BONUS_EXACT 1000
|
||||
#define FUZZY_BONUS_PREFIX 200
|
||||
#define FUZZY_BONUS_SUFFIX 100
|
||||
#define FUZZY_BONUS_START 12
|
||||
#define FUZZY_BONUS_BOUNDARY 8
|
||||
#define FUZZY_BONUS_CONSECUTIVE 6
|
||||
#define FUZZY_PENALTY_LEADING 1
|
||||
#define FUZZY_PENALTY_LEADING_MAX 10
|
||||
#define FUZZY_PENALTY_GAP 1
|
||||
|
||||
/* A single visible character of the text. */
|
||||
struct fuzzy_char {
|
||||
enum style_align align;
|
||||
struct utf8_data ud; /* original UTF-8 data */
|
||||
u_int width; /* display width */
|
||||
u_int offset; /* within its alignment */
|
||||
};
|
||||
|
||||
/* One parsed query term. */
|
||||
struct fuzzy_term {
|
||||
int inverse;
|
||||
int exact;
|
||||
int prefix;
|
||||
int suffix;
|
||||
const char *text;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
/* Is this character a word boundary, so a match after it scores higher? */
|
||||
static int
|
||||
fuzzy_is_boundary(const struct utf8_data *ud)
|
||||
{
|
||||
static const char *boundary = " -_/.:";
|
||||
|
||||
if (ud->size != 1)
|
||||
return (0);
|
||||
return (strchr(boundary, ud->data[0]) != NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Compare two characters, folding ASCII case if wanted. UTF-8 is compared
|
||||
* directly without case folding.
|
||||
*/
|
||||
static int
|
||||
fuzzy_char_equal(const struct utf8_data *a, const struct utf8_data *b, int fold)
|
||||
{
|
||||
if (fold &&
|
||||
a->size == 1 &&
|
||||
b->size == 1 &&
|
||||
a->data[0] < 0x80 &&
|
||||
b->data[0] < 0x80)
|
||||
return (tolower(a->data[0]) == tolower(b->data[0]));
|
||||
return (a->size == b->size && memcmp(a->data, b->data, a->size) == 0);
|
||||
}
|
||||
|
||||
/* Map a style alignment onto one of the four layout columns. */
|
||||
static enum style_align
|
||||
fuzzy_align(enum style_align align)
|
||||
{
|
||||
if (align == STYLE_ALIGN_DEFAULT)
|
||||
return (STYLE_ALIGN_LEFT);
|
||||
return (align);
|
||||
}
|
||||
|
||||
/* Add a visible character to the array, updating the alignment width. */
|
||||
static void
|
||||
fuzzy_add(struct fuzzy_char **cs, u_int *ncs, u_int *alloc, enum style_align a,
|
||||
const struct utf8_data *ud, u_int *widths)
|
||||
{
|
||||
struct fuzzy_char *fc;
|
||||
|
||||
if (*ncs == *alloc) {
|
||||
*alloc = (*alloc == 0) ? 64 : *alloc * 2;
|
||||
*cs = xreallocarray(*cs, *alloc, sizeof **cs);
|
||||
}
|
||||
fc = &(*cs)[(*ncs)++];
|
||||
fc->align = a;
|
||||
memcpy(&fc->ud, ud, sizeof fc->ud);
|
||||
fc->width = ud->width;
|
||||
fc->offset = widths[a];
|
||||
widths[a] += ud->width;
|
||||
}
|
||||
|
||||
/* Decode a character as UTF-8. */
|
||||
static const char *
|
||||
fuzzy_decode_one(const char *cp, const char *end, struct utf8_data *ud)
|
||||
{
|
||||
enum utf8_state more;
|
||||
const char *start = cp;
|
||||
|
||||
if ((more = utf8_open(ud, (u_char)*cp)) == UTF8_MORE) {
|
||||
while (++cp != end && more == UTF8_MORE)
|
||||
more = utf8_append(ud, (u_char)*cp);
|
||||
if (more == UTF8_DONE)
|
||||
return (cp);
|
||||
cp = start;
|
||||
}
|
||||
utf8_set(ud, (u_char)*cp);
|
||||
return (cp + 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Scan the text into an array of visible characters, skipping styles and
|
||||
* recording the alignment and intra-alignment offset of each. Returns the
|
||||
* array and its length and fills in the per-alignment widths.
|
||||
*/
|
||||
static struct fuzzy_char *
|
||||
fuzzy_scan(const char *text, u_int *ncs, u_int *widths)
|
||||
{
|
||||
struct fuzzy_char *cs = NULL;
|
||||
u_int alloc = 0, n, leading, i;
|
||||
enum style_align current = STYLE_ALIGN_LEFT;
|
||||
struct style sy;
|
||||
const char *cp = text, *textend = text + strlen(text);
|
||||
const char *end;
|
||||
struct utf8_data ud, hash, bracket;
|
||||
char *tmp;
|
||||
|
||||
*ncs = 0;
|
||||
memset(widths, 0, sizeof *widths * (STYLE_ALIGN_ABSOLUTE_CENTRE + 1));
|
||||
style_set(&sy, &grid_default_cell);
|
||||
utf8_set(&hash, '#');
|
||||
utf8_set(&bracket, '[');
|
||||
|
||||
while (*cp != '\0') {
|
||||
/* Handle a run of #s, which may introduce a style. */
|
||||
if (*cp == '#') {
|
||||
for (n = 0; cp[n] == '#'; n++)
|
||||
/* nothing */;
|
||||
if (cp[n] != '[') {
|
||||
/* Escaped #s: ##->#, so half (rounded up). */
|
||||
leading = (n % 2 == 0) ? n / 2 : n / 2 + 1;
|
||||
for (i = 0; i < leading; i++) {
|
||||
fuzzy_add(&cs, ncs, &alloc, current,
|
||||
&hash, widths);
|
||||
}
|
||||
cp += n;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Even count: all #s escaped, the [ is literal. */
|
||||
for (i = 0; i < n / 2; i++)
|
||||
fuzzy_add(&cs, ncs, &alloc, current, &hash,
|
||||
widths);
|
||||
if (n % 2 == 0) {
|
||||
fuzzy_add(&cs, ncs, &alloc, current, &bracket,
|
||||
widths);
|
||||
cp += n + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Odd count: this is a style, find and parse it. */
|
||||
end = format_skip(cp + n + 1, "]");
|
||||
if (end == NULL)
|
||||
break;
|
||||
tmp = xstrndup(cp + n + 1, end - (cp + n + 1));
|
||||
if (style_parse(&sy, &grid_default_cell, tmp) == 0)
|
||||
current = fuzzy_align(sy.align);
|
||||
free(tmp);
|
||||
cp = end + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Decode one character, multibyte or single byte. */
|
||||
cp = fuzzy_decode_one(cp, textend, &ud);
|
||||
|
||||
/*
|
||||
* Skip non-printable single bytes (control characters and raw
|
||||
* bytes left over from a failed decode); keep printable ASCII
|
||||
* and any decoded UTF-8.
|
||||
*/
|
||||
if (ud.size == 1 && (ud.data[0] <= 0x1f || ud.data[0] >= 0x7f))
|
||||
continue;
|
||||
fuzzy_add(&cs, ncs, &alloc, current, &ud, widths);
|
||||
}
|
||||
return (cs);
|
||||
}
|
||||
|
||||
/*
|
||||
* Work out the display column of a visible character given the trimmed widths
|
||||
* and start columns of each alignment. Returns 0 and sets the column if the
|
||||
* character is visible, otherwise returns -1.
|
||||
*/
|
||||
static int
|
||||
fuzzy_column(const struct fuzzy_char *fc, const u_int *start, const u_int *src,
|
||||
const u_int *vis, u_int *column)
|
||||
{
|
||||
enum style_align a = fc->align;
|
||||
|
||||
if (fc->offset < src[a] || fc->offset >= src[a] + vis[a])
|
||||
return (-1);
|
||||
*column = start[a] + (fc->offset - src[a]);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/* Decode a UTF-8 term into an array of characters. */
|
||||
static u_int
|
||||
fuzzy_decode(const char *tok, size_t len, struct utf8_data *out)
|
||||
{
|
||||
const char *cp = tok, *end = tok + len;
|
||||
u_int n = 0;
|
||||
|
||||
while (cp != end)
|
||||
cp = fuzzy_decode_one(cp, end, &out[n++]);
|
||||
return (n);
|
||||
}
|
||||
|
||||
/* Add the score for a fuzzy token matched at the given positions. */
|
||||
static int
|
||||
fuzzy_score_positions(const u_int *pos, u_int npos, const struct fuzzy_char *cs)
|
||||
{
|
||||
u_int i, gap, span;
|
||||
int score = 0;
|
||||
|
||||
if (npos == 0)
|
||||
return (0);
|
||||
if (pos[0] == 0)
|
||||
score += FUZZY_BONUS_START;
|
||||
else {
|
||||
if (fuzzy_is_boundary(&cs[pos[0] - 1].ud))
|
||||
score += FUZZY_BONUS_BOUNDARY;
|
||||
if (pos[0] < FUZZY_PENALTY_LEADING_MAX)
|
||||
score -= pos[0] * FUZZY_PENALTY_LEADING;
|
||||
else {
|
||||
score -= FUZZY_PENALTY_LEADING_MAX *
|
||||
FUZZY_PENALTY_LEADING;
|
||||
}
|
||||
}
|
||||
for (i = 1; i < npos; i++) {
|
||||
if (pos[i] == pos[i - 1] + 1)
|
||||
score += FUZZY_BONUS_CONSECUTIVE;
|
||||
else if (fuzzy_is_boundary(&cs[pos[i] - 1].ud))
|
||||
score += FUZZY_BONUS_BOUNDARY;
|
||||
}
|
||||
span = pos[npos - 1] - pos[0] + 1;
|
||||
gap = span - npos;
|
||||
score -= gap * FUZZY_PENALTY_GAP;
|
||||
return (score);
|
||||
}
|
||||
|
||||
/*
|
||||
* Match a token as a subsequence of the visible characters. Returns if the
|
||||
* token matches.
|
||||
*/
|
||||
static int
|
||||
fuzzy_match_fuzzy(const struct utf8_data *tok, u_int toklen,
|
||||
struct fuzzy_char *cs, u_int ncs, int fold, int *score, char *matched)
|
||||
{
|
||||
u_int pi, ci, *pos;
|
||||
int found, value;
|
||||
|
||||
if (toklen == 0 || ncs == 0)
|
||||
return (0);
|
||||
pos = xcalloc(toklen, sizeof *pos);
|
||||
|
||||
/* First find a subsequence from the start. */
|
||||
ci = 0;
|
||||
for (pi = 0; pi < toklen; pi++) {
|
||||
while (ci != ncs &&
|
||||
!fuzzy_char_equal(&tok[pi], &cs[ci].ud, fold))
|
||||
ci++;
|
||||
if (ci == ncs) {
|
||||
free(pos);
|
||||
return (0);
|
||||
}
|
||||
pos[pi] = ci++;
|
||||
}
|
||||
|
||||
/* Then compact it backwards to prefer a shorter span. */
|
||||
ci = pos[toklen - 1];
|
||||
for (pi = toklen; pi > 0; pi--) {
|
||||
found = 0;
|
||||
for (;;) {
|
||||
if (fuzzy_char_equal(&tok[pi - 1], &cs[ci].ud, fold)) {
|
||||
pos[pi - 1] = ci;
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
if (ci == 0)
|
||||
break;
|
||||
ci--;
|
||||
}
|
||||
if (!found) {
|
||||
free(pos);
|
||||
return (0);
|
||||
}
|
||||
if (pi != 1)
|
||||
ci--;
|
||||
}
|
||||
|
||||
value = fuzzy_score_positions(pos, toklen, cs);
|
||||
*score += value;
|
||||
for (pi = 0; pi < toklen; pi++)
|
||||
matched[pos[pi]] = 1;
|
||||
free(pos);
|
||||
return (1);
|
||||
}
|
||||
|
||||
/* Score an exact, prefix or suffix match. */
|
||||
static int
|
||||
fuzzy_score_exact(u_int start, u_int toklen, u_int ncs,
|
||||
const struct fuzzy_char *cs, int prefix, int suffix)
|
||||
{
|
||||
int score;
|
||||
|
||||
score = FUZZY_BONUS_EXACT + toklen * FUZZY_BONUS_CONSECUTIVE;
|
||||
if (prefix)
|
||||
score += FUZZY_BONUS_PREFIX;
|
||||
if (suffix)
|
||||
score += FUZZY_BONUS_SUFFIX;
|
||||
if (start == 0)
|
||||
score += FUZZY_BONUS_START;
|
||||
else if (fuzzy_is_boundary(&cs[start - 1].ud))
|
||||
score += FUZZY_BONUS_BOUNDARY;
|
||||
if (start < FUZZY_PENALTY_LEADING_MAX)
|
||||
score -= start * FUZZY_PENALTY_LEADING;
|
||||
else
|
||||
score -= FUZZY_PENALTY_LEADING_MAX * FUZZY_PENALTY_LEADING;
|
||||
if (!prefix && !suffix)
|
||||
score -= ncs - (start + toklen);
|
||||
return (score);
|
||||
}
|
||||
|
||||
/* Match an exact, prefix or suffix term against the visible characters. */
|
||||
static int
|
||||
fuzzy_match_exact(const struct utf8_data *tok, u_int toklen,
|
||||
struct fuzzy_char *cs, u_int ncs, int fold, int prefix, int suffix,
|
||||
int *score, char *matched)
|
||||
{
|
||||
u_int start, end, i, j, best = 0;
|
||||
int ok, found = 0, value, bestscore = 0;
|
||||
|
||||
if (toklen == 0 || toklen > ncs)
|
||||
return (0);
|
||||
|
||||
if (prefix && suffix) {
|
||||
if (toklen != ncs)
|
||||
return (0);
|
||||
start = 0;
|
||||
end = 1;
|
||||
} else if (prefix) {
|
||||
start = 0;
|
||||
end = 1;
|
||||
} else if (suffix) {
|
||||
start = ncs - toklen;
|
||||
end = start + 1;
|
||||
} else {
|
||||
start = 0;
|
||||
end = ncs - toklen + 1;
|
||||
}
|
||||
|
||||
for (i = start; i < end; i++) {
|
||||
ok = 1;
|
||||
for (j = 0; j < toklen; j++) {
|
||||
if (!fuzzy_char_equal(&tok[j], &cs[i + j].ud, fold)) {
|
||||
ok = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!ok)
|
||||
continue;
|
||||
value = fuzzy_score_exact(i, toklen, ncs, cs, prefix, suffix);
|
||||
if (!found || value > bestscore) {
|
||||
found = 1;
|
||||
best = i;
|
||||
bestscore = value;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
return (0);
|
||||
*score += bestscore;
|
||||
if (matched != NULL) {
|
||||
for (i = 0; i < toklen; i++)
|
||||
matched[best + i] = 1;
|
||||
}
|
||||
return (1);
|
||||
}
|
||||
|
||||
/* Parse one term. */
|
||||
static int
|
||||
fuzzy_parse_term(const char *start, const char *end, struct fuzzy_term *term)
|
||||
{
|
||||
memset(term, 0, sizeof *term);
|
||||
if (start == end)
|
||||
return (0);
|
||||
if (*start == '!') {
|
||||
term->inverse = 1;
|
||||
start++;
|
||||
}
|
||||
if (start == end)
|
||||
return (0);
|
||||
if (*start == '\'') {
|
||||
term->exact = 1;
|
||||
start++;
|
||||
} else if (*start == '^') {
|
||||
term->exact = 1;
|
||||
term->prefix = 1;
|
||||
start++;
|
||||
}
|
||||
if (start == end)
|
||||
return (0);
|
||||
if (end[-1] == '$') {
|
||||
term->exact = 1;
|
||||
term->suffix = 1;
|
||||
end--;
|
||||
}
|
||||
if (start == end)
|
||||
return (0);
|
||||
|
||||
if (term->inverse)
|
||||
term->exact = 1;
|
||||
term->text = start;
|
||||
term->len = end - start;
|
||||
return (1);
|
||||
}
|
||||
|
||||
/* Match one parsed term. */
|
||||
static int
|
||||
fuzzy_match_term(const struct fuzzy_term *term, struct utf8_data *tok,
|
||||
struct fuzzy_char *cs, u_int ncs, int fold, int *score, char *matched)
|
||||
{
|
||||
u_int toklen;
|
||||
int value = 0, matched_term;
|
||||
|
||||
toklen = fuzzy_decode(term->text, term->len, tok);
|
||||
if (term->exact) {
|
||||
matched_term = fuzzy_match_exact(tok, toklen, cs, ncs, fold,
|
||||
term->prefix, term->suffix, &value,
|
||||
term->inverse ? NULL : matched);
|
||||
} else {
|
||||
matched_term = fuzzy_match_fuzzy(tok, toklen, cs, ncs, fold,
|
||||
&value, term->inverse ? NULL : matched);
|
||||
}
|
||||
|
||||
if (term->inverse)
|
||||
return (!matched_term);
|
||||
if (!matched_term)
|
||||
return (0);
|
||||
*score += value;
|
||||
return (1);
|
||||
}
|
||||
|
||||
/* Match one AND group of terms. */
|
||||
static int
|
||||
fuzzy_match_group(const char *start, const char *end, struct utf8_data *tok,
|
||||
struct fuzzy_char *cs, u_int ncs, int fold, int *score, char *matched)
|
||||
{
|
||||
const char *cp = start, *sp;
|
||||
struct fuzzy_term term;
|
||||
int any = 0;
|
||||
|
||||
*score = 0;
|
||||
while (cp != end) {
|
||||
while (cp != end && *cp == ' ')
|
||||
cp++;
|
||||
if (cp == end)
|
||||
break;
|
||||
sp = cp;
|
||||
while (cp != end && *cp != ' ')
|
||||
cp++;
|
||||
if (!fuzzy_parse_term(sp, cp, &term))
|
||||
return (0);
|
||||
any = 1;
|
||||
if (!fuzzy_match_term(&term, tok, cs, ncs, fold, score,
|
||||
matched))
|
||||
return (0);
|
||||
}
|
||||
return (any);
|
||||
}
|
||||
|
||||
/*
|
||||
* Fuzzy match pattern against text, which is drawn into a region of the given
|
||||
* display width. Returns a bitstr_t of width bits with a bit set for each
|
||||
* column occupied by a matched character, or NULL if there is no match. A
|
||||
* higher returned score is better.
|
||||
*/
|
||||
bitstr_t *
|
||||
fuzzy_match(const char *pattern, const char *text, u_int width, u_int *score)
|
||||
{
|
||||
struct fuzzy_char *cs;
|
||||
char *matched = NULL, *best = NULL, *groupmatched;
|
||||
struct utf8_data *tok;
|
||||
bitstr_t *mask;
|
||||
u_int ncs, i, j, column;
|
||||
u_int widths[STYLE_ALIGN_ABSOLUTE_CENTRE + 1];
|
||||
u_int start[STYLE_ALIGN_ABSOLUTE_CENTRE + 1];
|
||||
u_int src[STYLE_ALIGN_ABSOLUTE_CENTRE + 1];
|
||||
u_int vis[STYLE_ALIGN_ABSOLUTE_CENTRE + 1];
|
||||
u_int wl, wc, wr, wa;
|
||||
const char *cp, *sp;
|
||||
int bestscore = 0, groupscore, found = 0, fold;
|
||||
|
||||
if (width == 0)
|
||||
return (NULL);
|
||||
|
||||
/* An empty query matches everything, with nothing highlighted. */
|
||||
for (cp = pattern; *cp == ' ' || *cp == '|'; cp++)
|
||||
/* nothing */;
|
||||
if (*cp == '\0') {
|
||||
if (score != NULL)
|
||||
*score = 0;
|
||||
return (bit_alloc(width));
|
||||
}
|
||||
|
||||
/* Smart-case: fold unless the pattern has an uppercase character. */
|
||||
fold = 1;
|
||||
for (cp = pattern; *cp != '\0'; cp++) {
|
||||
if (*cp >= 'A' && *cp <= 'Z') {
|
||||
fold = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Scan the text into visible characters. */
|
||||
cs = fuzzy_scan(text, &ncs, widths);
|
||||
matched = xcalloc(ncs == 0 ? 1 : ncs, sizeof *matched);
|
||||
best = xcalloc(ncs == 0 ? 1 : ncs, sizeof *best);
|
||||
tok = xreallocarray(NULL, strlen(pattern) + 1, sizeof *tok);
|
||||
|
||||
/* Match each |-separated group and keep the best-scoring one. */
|
||||
cp = pattern;
|
||||
while (*cp != '\0') {
|
||||
while (*cp == ' ' || *cp == '|')
|
||||
cp++;
|
||||
if (*cp == '\0')
|
||||
break;
|
||||
sp = cp;
|
||||
while (*cp != '\0' && *cp != '|')
|
||||
cp++;
|
||||
memset(matched, 0, ncs == 0 ? 1 : ncs);
|
||||
groupmatched = matched;
|
||||
if (fuzzy_match_group(sp, cp, tok, cs, ncs, fold,
|
||||
&groupscore, groupmatched)) {
|
||||
if (!found || groupscore > bestscore) {
|
||||
found = 1;
|
||||
bestscore = groupscore;
|
||||
memcpy(best, matched, ncs == 0 ? 1 : ncs);
|
||||
}
|
||||
}
|
||||
}
|
||||
free(tok);
|
||||
if (!found) {
|
||||
free(best);
|
||||
free(matched);
|
||||
free(cs);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Work out the trimmed widths and start columns of each alignment,
|
||||
* mirroring format_draw_none.
|
||||
*/
|
||||
wl = widths[STYLE_ALIGN_LEFT];
|
||||
wc = widths[STYLE_ALIGN_CENTRE];
|
||||
wr = widths[STYLE_ALIGN_RIGHT];
|
||||
wa = widths[STYLE_ALIGN_ABSOLUTE_CENTRE];
|
||||
while (wl + wc + wr > width) {
|
||||
if (wc > 0)
|
||||
wc--;
|
||||
else if (wr > 0)
|
||||
wr--;
|
||||
else
|
||||
wl--;
|
||||
}
|
||||
if (wa > width)
|
||||
wa = width;
|
||||
|
||||
start[STYLE_ALIGN_LEFT] = 0;
|
||||
src[STYLE_ALIGN_LEFT] = 0;
|
||||
vis[STYLE_ALIGN_LEFT] = wl;
|
||||
|
||||
start[STYLE_ALIGN_RIGHT] = width - wr;
|
||||
src[STYLE_ALIGN_RIGHT] = widths[STYLE_ALIGN_RIGHT] - wr;
|
||||
vis[STYLE_ALIGN_RIGHT] = wr;
|
||||
|
||||
start[STYLE_ALIGN_CENTRE] =
|
||||
wl + ((width - wr) - wl) / 2 - wc / 2;
|
||||
src[STYLE_ALIGN_CENTRE] = widths[STYLE_ALIGN_CENTRE] / 2 - wc / 2;
|
||||
vis[STYLE_ALIGN_CENTRE] = wc;
|
||||
|
||||
start[STYLE_ALIGN_ABSOLUTE_CENTRE] = (width - wa) / 2;
|
||||
src[STYLE_ALIGN_ABSOLUTE_CENTRE] = 0;
|
||||
vis[STYLE_ALIGN_ABSOLUTE_CENTRE] = wa;
|
||||
|
||||
/* Set a bit for each column of each matched character. */
|
||||
mask = bit_alloc(width);
|
||||
for (i = 0; i < ncs; i++) {
|
||||
if (!best[i])
|
||||
continue;
|
||||
if (fuzzy_column(&cs[i], start, src, vis, &column) != 0)
|
||||
continue;
|
||||
for (j = 0; j < cs[i].width && column + j < width; j++)
|
||||
bit_set(mask, column + j);
|
||||
}
|
||||
|
||||
free(best);
|
||||
free(matched);
|
||||
free(cs);
|
||||
|
||||
if (score != NULL)
|
||||
*score = (bestscore < 0) ? 0 : (u_int)bestscore;
|
||||
return (mask);
|
||||
}
|
||||
230
grid.c
230
grid.c
@@ -18,6 +18,9 @@
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <assert.h>
|
||||
#endif
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -56,6 +59,62 @@ static const struct grid_cell_entry grid_cleared_entry = {
|
||||
{ .data = { 0, 8, 8, ' ' } }, GRID_FLAG_CLEARED
|
||||
};
|
||||
|
||||
#ifdef __APPLE__
|
||||
static void
|
||||
grid_check_lines(struct grid *gd)
|
||||
{
|
||||
u_int i, j;
|
||||
|
||||
for (i = 0; i < gd->hsize + gd->sy; i++) {
|
||||
for (j = i + 1; j < gd->hsize + gd->sy; j++) {
|
||||
if (gd->linedata[i].celldata != NULL)
|
||||
assert(gd->linedata[i].celldata != gd->linedata[j].celldata);
|
||||
if (gd->linedata[i].extddata != NULL)
|
||||
assert(gd->linedata[i].extddata != gd->linedata[j].extddata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
grid_check_is_clear(struct grid *gd)
|
||||
{
|
||||
struct grid_line *gl;
|
||||
u_int yy, ny;
|
||||
|
||||
assert(gd != NULL);
|
||||
|
||||
if (gd->sy == 0) {
|
||||
assert(gd->linedata == NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(gd->linedata != NULL);
|
||||
|
||||
ny = gd->hsize + gd->sy;
|
||||
for (yy = 0; yy < ny; yy++) {
|
||||
gl = &gd->linedata[yy];
|
||||
|
||||
assert(gl->celldata == NULL);
|
||||
assert(gl->cellused == 0);
|
||||
assert(gl->cellsize == 0);
|
||||
assert(gl->extddata == NULL);
|
||||
assert(gl->extdsize == 0);
|
||||
assert(gl->flags == 0);
|
||||
assert(gl->time == 0);
|
||||
}
|
||||
}
|
||||
#else
|
||||
static void
|
||||
grid_check_lines(__unused struct grid *gd)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
grid_check_is_clear(__unused struct grid *gd)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Store cell in entry. */
|
||||
static void
|
||||
grid_store_cell(struct grid_cell_entry *gce, const struct grid_cell *gc,
|
||||
@@ -86,7 +145,8 @@ grid_need_extended_cell(const struct grid_cell_entry *gce,
|
||||
return (1);
|
||||
if (gc->data.size > 1 || gc->data.width > 1)
|
||||
return (1);
|
||||
if ((gc->fg & COLOUR_FLAG_RGB) || (gc->bg & COLOUR_FLAG_RGB))
|
||||
if ((gc->fg & (COLOUR_FLAG_RGB|COLOUR_FLAG_THEME)) ||
|
||||
(gc->bg & (COLOUR_FLAG_RGB|COLOUR_FLAG_THEME)))
|
||||
return (1);
|
||||
if (gc->us != 8) /* only supports 256 or RGB */
|
||||
return (1);
|
||||
@@ -218,7 +278,7 @@ grid_clear_cell(struct grid *gd, u_int px, u_int py, u_int bg, int moved)
|
||||
if (bg != 8)
|
||||
gee->bg = bg;
|
||||
} else if (bg != 8) {
|
||||
if (bg & COLOUR_FLAG_RGB) {
|
||||
if (bg & (COLOUR_FLAG_RGB|COLOUR_FLAG_THEME)) {
|
||||
grid_get_extended_cell(gl, gce, gce->flags);
|
||||
gee = grid_extended_cell(gl, gce, &grid_cleared_cell);
|
||||
gee->bg = bg;
|
||||
@@ -286,13 +346,21 @@ grid_set_tab(struct grid_cell *gc, u_int width)
|
||||
static void
|
||||
grid_free_line(struct grid *gd, u_int py)
|
||||
{
|
||||
free(gd->linedata[py].celldata);
|
||||
free(gd->linedata[py].extddata);
|
||||
memset(&gd->linedata[py], 0, sizeof gd->linedata[py]);
|
||||
struct grid_line *gl = &gd->linedata[py];
|
||||
|
||||
#ifdef __APPLE__
|
||||
assert(gl->cellused <= gl->cellsize);
|
||||
assert(gl->extdsize == 0 || gl->extddata != NULL);
|
||||
assert(gl->cellsize == 0 || gl->celldata != NULL);
|
||||
#endif
|
||||
|
||||
free(gl->celldata);
|
||||
free(gl->extddata);
|
||||
memset(gl, 0, sizeof *gl);
|
||||
}
|
||||
|
||||
/* Free several lines. */
|
||||
static void
|
||||
void
|
||||
grid_free_lines(struct grid *gd, u_int py, u_int ny)
|
||||
{
|
||||
u_int yy;
|
||||
@@ -307,24 +375,18 @@ grid_create(u_int sx, u_int sy, u_int hlimit)
|
||||
{
|
||||
struct grid *gd;
|
||||
|
||||
gd = xmalloc(sizeof *gd);
|
||||
gd = xcalloc(1, sizeof *gd);
|
||||
gd->sx = sx;
|
||||
gd->sy = sy;
|
||||
|
||||
if (hlimit != 0)
|
||||
gd->flags = GRID_HISTORY;
|
||||
else
|
||||
gd->flags = 0;
|
||||
|
||||
gd->hscrolled = 0;
|
||||
gd->hsize = 0;
|
||||
gd->hlimit = hlimit;
|
||||
|
||||
if (gd->sy != 0)
|
||||
gd->linedata = xcalloc(gd->sy, sizeof *gd->linedata);
|
||||
else
|
||||
gd->linedata = NULL;
|
||||
|
||||
grid_check_is_clear(gd);
|
||||
return (gd);
|
||||
}
|
||||
|
||||
@@ -405,6 +467,7 @@ grid_collect_history(struct grid *gd, int all)
|
||||
grid_trim_history(gd, ny);
|
||||
|
||||
gd->hsize -= ny;
|
||||
gd->scroll_collected += ny;
|
||||
if (gd->hscrolled > gd->hsize)
|
||||
gd->hscrolled = gd->hsize;
|
||||
}
|
||||
@@ -442,6 +505,9 @@ grid_scroll_history(struct grid *gd, u_int bg)
|
||||
grid_compact_line(&gd->linedata[gd->hsize]);
|
||||
gd->linedata[gd->hsize].time = current_time;
|
||||
gd->hsize++;
|
||||
gd->scroll_added++;
|
||||
|
||||
grid_check_lines(gd);
|
||||
}
|
||||
|
||||
/* Clear the history. */
|
||||
@@ -452,6 +518,7 @@ grid_clear_history(struct grid *gd)
|
||||
|
||||
gd->hscrolled = 0;
|
||||
gd->hsize = 0;
|
||||
gd->scroll_generation++;
|
||||
|
||||
gd->linedata = xreallocarray(gd->linedata, gd->sy,
|
||||
sizeof *gd->linedata);
|
||||
@@ -489,6 +556,9 @@ grid_scroll_history_region(struct grid *gd, u_int upper, u_int lower, u_int bg)
|
||||
/* Move the history offset down over the line. */
|
||||
gd->hscrolled++;
|
||||
gd->hsize++;
|
||||
gd->scroll_added++;
|
||||
|
||||
grid_check_lines(gd);
|
||||
}
|
||||
|
||||
/* Expand line to fit to cell. */
|
||||
@@ -750,6 +820,8 @@ grid_move_lines(struct grid *gd, u_int dy, u_int py, u_int ny, u_int bg)
|
||||
}
|
||||
if (py != 0 && (py < dy || py >= dy + ny))
|
||||
gd->linedata[py - 1].flags &= ~GRID_LINE_WRAPPED;
|
||||
|
||||
grid_check_lines(gd);
|
||||
}
|
||||
|
||||
/* Move a group of cells. */
|
||||
@@ -788,9 +860,16 @@ grid_string_cells_fg(const struct grid_cell *gc, int *values)
|
||||
{
|
||||
size_t n;
|
||||
u_char r, g, b;
|
||||
int c;
|
||||
|
||||
n = 0;
|
||||
if (gc->fg & COLOUR_FLAG_256) {
|
||||
if (gc->fg & COLOUR_FLAG_THEME) {
|
||||
c = colour_theme_terminal_colour(gc->fg & 0xff);
|
||||
if (c == 8)
|
||||
values[n++] = 39;
|
||||
else
|
||||
values[n++] = c + 30;
|
||||
} else if (gc->fg & COLOUR_FLAG_256) {
|
||||
values[n++] = 38;
|
||||
values[n++] = 5;
|
||||
values[n++] = gc->fg & 0xff;
|
||||
@@ -837,9 +916,16 @@ grid_string_cells_bg(const struct grid_cell *gc, int *values)
|
||||
{
|
||||
size_t n;
|
||||
u_char r, g, b;
|
||||
int c;
|
||||
|
||||
n = 0;
|
||||
if (gc->bg & COLOUR_FLAG_256) {
|
||||
if (gc->bg & COLOUR_FLAG_THEME) {
|
||||
c = colour_theme_terminal_colour(gc->bg & 0xff);
|
||||
if (c == 8)
|
||||
values[n++] = 49;
|
||||
else
|
||||
values[n++] = c + 40;
|
||||
} else if (gc->bg & COLOUR_FLAG_256) {
|
||||
values[n++] = 48;
|
||||
values[n++] = 5;
|
||||
values[n++] = gc->bg & 0xff;
|
||||
@@ -886,9 +972,19 @@ grid_string_cells_us(const struct grid_cell *gc, int *values)
|
||||
{
|
||||
size_t n;
|
||||
u_char r, g, b;
|
||||
int c;
|
||||
|
||||
n = 0;
|
||||
if (gc->us & COLOUR_FLAG_256) {
|
||||
if (gc->us & COLOUR_FLAG_THEME) {
|
||||
c = colour_theme_terminal_colour(gc->us & 0xff);
|
||||
if (c == 8)
|
||||
values[n++] = 59;
|
||||
else {
|
||||
values[n++] = 58;
|
||||
values[n++] = 5;
|
||||
values[n++] = c;
|
||||
}
|
||||
} else if (gc->us & COLOUR_FLAG_256) {
|
||||
values[n++] = 58;
|
||||
values[n++] = 5;
|
||||
values[n++] = gc->us & 0xff;
|
||||
@@ -1215,6 +1311,8 @@ grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src, u_int sy,
|
||||
sy++;
|
||||
dy++;
|
||||
}
|
||||
|
||||
grid_check_lines(dst);
|
||||
}
|
||||
|
||||
/* Mark line as dead. */
|
||||
@@ -1510,6 +1608,9 @@ grid_reflow(struct grid *gd, u_int sx)
|
||||
free(gd->linedata);
|
||||
gd->linedata = target->linedata;
|
||||
free(target);
|
||||
gd->scroll_generation++;
|
||||
|
||||
grid_check_lines(gd);
|
||||
}
|
||||
|
||||
/* Convert to position based on wrapped lines. */
|
||||
@@ -1611,3 +1712,98 @@ grid_in_set(struct grid *gd, u_int px, u_int py, const char *set)
|
||||
return (0);
|
||||
return (utf8_cstrhas(set, &gc.data));
|
||||
}
|
||||
|
||||
/* Line flags to string. */
|
||||
const char *
|
||||
grid_line_flags_string(int flags)
|
||||
{
|
||||
static char s[128];
|
||||
|
||||
*s = '\0';
|
||||
if (flags & GRID_LINE_WRAPPED)
|
||||
strlcat(s, "WRAPPED,", sizeof s);
|
||||
if (flags & GRID_LINE_EXTENDED)
|
||||
strlcat(s, "EXTENDED,", sizeof s);
|
||||
if (flags & GRID_LINE_DEAD)
|
||||
strlcat(s, "DEAD,", sizeof s);
|
||||
if (flags & GRID_LINE_START_PROMPT)
|
||||
strlcat(s, "START_PROMPT,", sizeof s);
|
||||
if (flags & GRID_LINE_START_OUTPUT)
|
||||
strlcat(s, "START_OUTPUT,", sizeof s);
|
||||
if (flags & GRID_LINE_HYPERLINK)
|
||||
strlcat(s, "HYPERLINK,", sizeof s);
|
||||
if (*s == '\0')
|
||||
return ("NONE");
|
||||
s[strlen(s) - 1] = '\0';
|
||||
return (s);
|
||||
}
|
||||
|
||||
/* Cell flags to string. */
|
||||
const char *
|
||||
grid_cell_flags_string(int flags)
|
||||
{
|
||||
static char s[128];
|
||||
|
||||
*s = '\0';
|
||||
if (flags & GRID_FLAG_FG256)
|
||||
strlcat(s, "FG256,", sizeof s);
|
||||
if (flags & GRID_FLAG_BG256)
|
||||
strlcat(s, "BG256,", sizeof s);
|
||||
if (flags & GRID_FLAG_PADDING)
|
||||
strlcat(s, "PADDING,", sizeof s);
|
||||
if (flags & GRID_FLAG_EXTENDED)
|
||||
strlcat(s, "EXTENDED,", sizeof s);
|
||||
if (flags & GRID_FLAG_SELECTED)
|
||||
strlcat(s, "SELECTED,", sizeof s);
|
||||
if (flags & GRID_FLAG_CLEARED)
|
||||
strlcat(s, "CLEARED,", sizeof s);
|
||||
if (flags & GRID_FLAG_TAB)
|
||||
strlcat(s, "TAB,", sizeof s);
|
||||
if (flags & GRID_FLAG_NOPALETTE)
|
||||
strlcat(s, "NOPALETTE,", sizeof s);
|
||||
if (*s == '\0')
|
||||
return ("NONE");
|
||||
s[strlen(s) - 1] = '\0';
|
||||
return (s);
|
||||
}
|
||||
|
||||
/* Cell attributes to string. */
|
||||
const char *
|
||||
grid_cell_attr_string(int attr)
|
||||
{
|
||||
static char s[256];
|
||||
|
||||
*s = '\0';
|
||||
if (attr & GRID_ATTR_CHARSET)
|
||||
strlcat(s, "CHARSET,", sizeof s);
|
||||
if (attr & GRID_ATTR_BRIGHT)
|
||||
strlcat(s, "BRIGHT,", sizeof s);
|
||||
if (attr & GRID_ATTR_DIM)
|
||||
strlcat(s, "DIM,", sizeof s);
|
||||
if (attr & GRID_ATTR_UNDERSCORE)
|
||||
strlcat(s, "UNDERSCORE,", sizeof s);
|
||||
if (attr & GRID_ATTR_BLINK)
|
||||
strlcat(s, "BLINK,", sizeof s);
|
||||
if (attr & GRID_ATTR_REVERSE)
|
||||
strlcat(s, "REVERSE,", sizeof s);
|
||||
if (attr & GRID_ATTR_HIDDEN)
|
||||
strlcat(s, "HIDDEN,", sizeof s);
|
||||
if (attr & GRID_ATTR_ITALICS)
|
||||
strlcat(s, "ITALICS,", sizeof s);
|
||||
if (attr & GRID_ATTR_STRIKETHROUGH)
|
||||
strlcat(s, "STRIKETHROUGH,", sizeof s);
|
||||
if (attr & GRID_ATTR_UNDERSCORE_2)
|
||||
strlcat(s, "UNDERSCORE_2,", sizeof s);
|
||||
if (attr & GRID_ATTR_UNDERSCORE_3)
|
||||
strlcat(s, "UNDERSCORE_3,", sizeof s);
|
||||
if (attr & GRID_ATTR_UNDERSCORE_4)
|
||||
strlcat(s, "UNDERSCORE_4,", sizeof s);
|
||||
if (attr & GRID_ATTR_UNDERSCORE_5)
|
||||
strlcat(s, "UNDERSCORE_5,", sizeof s);
|
||||
if (attr & GRID_ATTR_OVERLINE)
|
||||
strlcat(s, "OVERLINE,", sizeof s);
|
||||
if (*s == '\0')
|
||||
return ("NONE");
|
||||
s[strlen(s) - 1] = '\0';
|
||||
return (s);
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
*/
|
||||
|
||||
#define MAX_HYPERLINKS 5000
|
||||
#define MAX_HYPERLINK_URI 1024
|
||||
|
||||
static long long hyperlinks_next_external_id = 1;
|
||||
static u_int global_hyperlinks_count;
|
||||
@@ -145,9 +146,13 @@ hyperlinks_put(struct hyperlinks *hl, const char *uri_in,
|
||||
internal_id_in = "";
|
||||
|
||||
utf8_stravis(&uri, uri_in, VIS_OCTAL|VIS_CSTYLE);
|
||||
utf8_stravis(&internal_id, internal_id_in, VIS_OCTAL|VIS_CSTYLE);
|
||||
if (strlen(uri) > MAX_HYPERLINK_URI) {
|
||||
free(uri);
|
||||
return (0);
|
||||
}
|
||||
|
||||
if (*internal_id_in != '\0') {
|
||||
utf8_stravis(&internal_id, internal_id_in, VIS_OCTAL|VIS_CSTYLE);
|
||||
if (*internal_id != '\0') {
|
||||
find.uri = uri;
|
||||
find.internal_id = internal_id;
|
||||
|
||||
|
||||
@@ -450,11 +450,11 @@ sixel_scale(struct sixel_image *si, u_int xpixel, u_int ypixel, u_int ox,
|
||||
|
||||
new->set_ra = si->set_ra;
|
||||
/* subtract offset */
|
||||
new->ra_x = new->ra_x > pox ? new->ra_x - pox : 0;
|
||||
new->ra_y = new->ra_y > poy ? new->ra_y - poy : 0;
|
||||
new->ra_x = si->ra_x > pox ? si->ra_x - pox : 0;
|
||||
new->ra_y = si->ra_y > poy ? si->ra_y - poy : 0;
|
||||
/* clamp to size */
|
||||
new->ra_x = si->ra_x < psx ? si->ra_x : psx;
|
||||
new->ra_y = si->ra_y < psy ? si->ra_y : psy;
|
||||
new->ra_x = new->ra_x < psx ? new->ra_x : psx;
|
||||
new->ra_y = new->ra_y < psy ? new->ra_y : psy;
|
||||
/* resize */
|
||||
new->ra_x = new->ra_x * xpixel / si->xpixel;
|
||||
new->ra_y = new->ra_y * ypixel / si->ypixel;
|
||||
@@ -586,7 +586,7 @@ sixel_print(struct sixel_image *si, struct sixel_image *map, size_t *size)
|
||||
len = 8192;
|
||||
buf = xmalloc(len);
|
||||
|
||||
tmplen = xsnprintf(tmp, sizeof tmp, "\033P0;%uq", si->p2);
|
||||
tmplen = xsnprintf(tmp, sizeof tmp, "\033P9;%uq", si->p2);
|
||||
sixel_print_add(&buf, &len, &used, tmp, tmplen);
|
||||
|
||||
if (si->set_ra) {
|
||||
|
||||
@@ -483,7 +483,7 @@ input_key_vt10x(struct bufferevent *bev, key_code key)
|
||||
{
|
||||
struct utf8_data ud;
|
||||
key_code onlykey;
|
||||
char *p;
|
||||
const char *p;
|
||||
static const char *standard_map[2] = {
|
||||
"1!9(0)=+;:'\",<.>/-8? 2",
|
||||
"119900=+;;'',,..\x1f\x1f\x7f\x7f\0\0",
|
||||
@@ -806,7 +806,7 @@ input_key_mouse(struct window_pane *wp, struct mouse_event *m)
|
||||
return;
|
||||
if (cmd_mouse_at(wp, m, &x, &y, 0) != 0)
|
||||
return;
|
||||
if (!window_pane_visible(wp))
|
||||
if (!window_pane_is_visible(wp))
|
||||
return;
|
||||
if (!input_key_get_mouse(s, m, x, y, &buf, &len))
|
||||
return;
|
||||
|
||||
32
input.c
32
input.c
@@ -1365,7 +1365,7 @@ input_esc_dispatch(struct input_ctx *ictx)
|
||||
{
|
||||
struct screen_write_ctx *sctx = &ictx->ctx;
|
||||
struct screen *s = sctx->s;
|
||||
struct input_table_entry *entry;
|
||||
const struct input_table_entry *entry;
|
||||
|
||||
if (ictx->flags & INPUT_DISCARD)
|
||||
return (0);
|
||||
@@ -1439,12 +1439,12 @@ input_esc_dispatch(struct input_ctx *ictx)
|
||||
static int
|
||||
input_csi_dispatch(struct input_ctx *ictx)
|
||||
{
|
||||
struct screen_write_ctx *sctx = &ictx->ctx;
|
||||
struct screen *s = sctx->s;
|
||||
struct input_table_entry *entry;
|
||||
struct options *oo;
|
||||
int i, n, m, ek, set, p;
|
||||
u_int cx, bg = ictx->cell.cell.bg;
|
||||
struct screen_write_ctx *sctx = &ictx->ctx;
|
||||
struct screen *s = sctx->s;
|
||||
const struct input_table_entry *entry;
|
||||
struct options *oo;
|
||||
int i, n, m, ek, set, p;
|
||||
u_int cx, bg = ictx->cell.cell.bg;
|
||||
|
||||
if (ictx->flags & INPUT_DISCARD)
|
||||
return (0);
|
||||
@@ -1953,16 +1953,14 @@ input_csi_dispatch_rm_private(struct input_ctx *ictx)
|
||||
case 2004:
|
||||
screen_write_mode_clear(sctx, MODE_BRACKETPASTE);
|
||||
break;
|
||||
case 2026:
|
||||
screen_write_stop_sync(ictx->wp);
|
||||
break;
|
||||
case 2031:
|
||||
screen_write_mode_clear(sctx, MODE_THEME_UPDATES);
|
||||
if (ictx->wp != NULL)
|
||||
ictx->wp->flags &= ~PANE_THEMECHANGED;
|
||||
break;
|
||||
case 2026: /* synchronized output */
|
||||
screen_write_stop_sync(ictx->wp);
|
||||
if (ictx->wp != NULL)
|
||||
ictx->wp->flags |= PANE_REDRAW;
|
||||
break;
|
||||
default:
|
||||
log_debug("%s: unknown '%c'", __func__, ictx->ch);
|
||||
break;
|
||||
@@ -2065,7 +2063,7 @@ input_csi_dispatch_sm_private(struct input_ctx *ictx)
|
||||
ictx->wp->flags &= ~PANE_THEMECHANGED;
|
||||
}
|
||||
break;
|
||||
case 2026: /* synchronized output */
|
||||
case 2026:
|
||||
screen_write_start_sync(ictx->wp);
|
||||
break;
|
||||
default:
|
||||
@@ -2822,10 +2820,10 @@ input_exit_rename(struct input_ctx *ictx)
|
||||
if (o != NULL)
|
||||
options_remove_or_default(o, -1, NULL);
|
||||
if (!options_get_number(w->options, "automatic-rename"))
|
||||
window_set_name(w, "", WINDOW_NAME_FORBID_EXT);
|
||||
window_set_name(w, "", 1);
|
||||
} else {
|
||||
options_set_number(w->options, "automatic-rename", 0);
|
||||
window_set_name(w, ictx->input_buf, WINDOW_NAME_FORBID_EXT);
|
||||
window_set_name(w, ictx->input_buf, 1);
|
||||
}
|
||||
server_redraw_window_borders(w);
|
||||
server_status_window(w);
|
||||
@@ -3055,7 +3053,7 @@ input_osc_10(struct input_ctx *ictx, const char *p)
|
||||
return;
|
||||
c = window_pane_get_fg_control_client(wp);
|
||||
if (c == -1) {
|
||||
tty_default_colours(&defaults, wp);
|
||||
tty_default_colours(&defaults, wp, NULL);
|
||||
if (COLOUR_DEFAULT(defaults.fg))
|
||||
c = window_pane_get_fg(wp);
|
||||
else
|
||||
@@ -3225,7 +3223,7 @@ static int
|
||||
input_osc_52_parse(struct input_ctx *ictx, const char *p, u_char **out,
|
||||
int *outlen, char *clip)
|
||||
{
|
||||
char *end;
|
||||
const char *end;
|
||||
size_t len;
|
||||
const char *allow = "cpqs01234567";
|
||||
u_int i, j = 0;
|
||||
|
||||
104
key-bindings.c
104
key-bindings.c
@@ -25,11 +25,10 @@
|
||||
#include "tmux.h"
|
||||
|
||||
#define DEFAULT_SESSION_MENU \
|
||||
" 'Next' 'n' {switch-client -n}" \
|
||||
" 'Previous' 'p' {switch-client -p}" \
|
||||
" #{S/t:#{?#{&&:#{<:#{loop_index},6},#{!:#{session_active}}},'Switch To #[underscore]#{session_name}' '' {switch-client -t=#{session_id}#} ,}}" \
|
||||
" ''" \
|
||||
" 'Renumber' 'N' {move-window -r}" \
|
||||
" 'Rename' 'r' {command-prompt -I \"#S\" {rename-session -- '%%'}}" \
|
||||
" 'Rename' 'r' {command-prompt -I '#S' {rename-session -- '%%'}}" \
|
||||
" 'Detach' 'd' {detach-client}" \
|
||||
" ''" \
|
||||
" 'New Session' 's' {new-session}" \
|
||||
@@ -351,6 +350,8 @@ key_bindings_init(void)
|
||||
{
|
||||
static const char *const defaults[] = {
|
||||
/* Prefix keys. */
|
||||
"bind -N 'Hide pane' _ { hide-pane }",
|
||||
|
||||
"bind -N 'Send the prefix key' C-b { send-prefix }",
|
||||
"bind -N 'Rotate through the panes' C-o { rotate-window }",
|
||||
"bind -N 'Suspend the current client' C-z { suspend-client }",
|
||||
@@ -363,6 +364,7 @@ key_bindings_init(void)
|
||||
"bind -N 'Kill current window' & { confirm-before -p\"kill-window #W? (y/n)\" kill-window }",
|
||||
"bind -N 'Prompt for window index to select' \"'\" { command-prompt -T window-target -pindex { select-window -t ':%%' } }",
|
||||
"bind -N 'New floating pane' * { new-pane }",
|
||||
"bind -N 'Toggle pane between floating and tiled' @ { if -F '#{pane_floating_flag}' { join-pane } { break-pane -W } }",
|
||||
"bind -N 'Switch to previous client' ( { switch-client -p }",
|
||||
"bind -N 'Switch to next client' ) { switch-client -n }",
|
||||
"bind -N 'Rename current window' , { command-prompt -I'#W' { rename-window -- '%%' } }",
|
||||
@@ -403,11 +405,15 @@ key_bindings_init(void)
|
||||
"bind -N 'Redraw the current client' r { refresh-client }",
|
||||
"bind -N 'Choose a session from a list' s { choose-tree -Zs }",
|
||||
"bind -N 'Show a clock' t { clock-mode }",
|
||||
"bind -N 'Switch to a window' Tab { new-pane -E -x75% -y30% -X0 -Y0; move-pane -P bottom-centre; switch-mode -wk }",
|
||||
"bind -N 'Switch to a session' BTab { new-pane -E -x75% -y30% -X0 -Y0; move-pane -P bottom-centre; switch-mode -sk }",
|
||||
"bind -N 'Choose a window from a list' w { choose-tree -Zw }",
|
||||
"bind -N 'Kill the active pane' x { confirm-before -p\"kill-pane #P? (y/n)\" kill-pane }",
|
||||
"bind -N 'Zoom the active pane' z { resize-pane -Z }",
|
||||
"bind -N 'Swap the active pane with the pane above' '{' { swap-pane -U }",
|
||||
"bind -N 'Swap the active pane with the pane below' '}' { swap-pane -D }",
|
||||
"bind -N 'Move pane to top-left corner' '{' { resize-pane -x50% -y50%; move-pane -P top-left }",
|
||||
"bind -N 'Move pane to top-right corner' '}' { resize-pane -x50% -y50%; move-pane -P top-right }",
|
||||
"bind -N 'Move pane to bottom-left corner' 'M-{' { resize-pane -x50% -y50%; move-pane -P bottom-left }",
|
||||
"bind -N 'Move pane to bottom-right corner' 'M-}' { resize-pane -x50% -y50%; move-pane -P bottom-right }",
|
||||
"bind -N 'Show messages' '~' { show-messages }",
|
||||
"bind -N 'Enter copy mode and scroll up' PPage { copy-mode -u }",
|
||||
"bind -N 'Select the pane above the active pane' -r Up { select-pane -U }",
|
||||
@@ -429,13 +435,13 @@ key_bindings_init(void)
|
||||
"bind -N 'Move the visible part of the window left' -r S-Left { refresh-client -L 10 }",
|
||||
"bind -N 'Move the visible part of the window right' -r S-Right { refresh-client -R 10 }",
|
||||
"bind -N 'Reset so the visible part of the window follows the cursor' -r DC { refresh-client -c }",
|
||||
"bind -N 'Resize the pane up by 5' -r M-Up { resize-pane -U 5 }",
|
||||
"bind -N 'Resize the pane up by 5' -r M-Up if -F '#{?floating_pane_flag}' { resizep -D-5 } { resize-pane -U 5 }",
|
||||
"bind -N 'Resize the pane down by 5' -r M-Down { resize-pane -D 5 }",
|
||||
"bind -N 'Resize the pane left by 5' -r M-Left { resize-pane -L 5 }",
|
||||
"bind -N 'Resize the pane right by 5' -r M-Right { resize-pane -R 5 }",
|
||||
"bind -N 'Resize the pane up' -r C-Up { resize-pane -U }",
|
||||
"bind -N 'Resize the pane left by 5' -r M-Left if -F '#{?floating_pane_flag}' { resizep -R-5 } { resize-pane -L 5 }",
|
||||
"bind -N 'Resize the pane right by 5' -r M-Right resize-pane -R 5",
|
||||
"bind -N 'Resize the pane up' -r C-Up if -F '#{?floating_pane_flag}' { resizep -D-1 } { resize-pane -U }",
|
||||
"bind -N 'Resize the pane down' -r C-Down { resize-pane -D }",
|
||||
"bind -N 'Resize the pane left' -r C-Left { resize-pane -L }",
|
||||
"bind -N 'Resize the pane left' -r C-Left if -F '#{?floating_pane_flag}' { resizep -R-1 } { resize-pane -L }",
|
||||
"bind -N 'Resize the pane right' -r C-Right { resize-pane -R }",
|
||||
|
||||
/* Menu keys */
|
||||
@@ -448,6 +454,7 @@ key_bindings_init(void)
|
||||
|
||||
/* Mouse button 1 drag on pane. */
|
||||
"bind -n MouseDrag1Pane { if -F '#{||:#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -M } }",
|
||||
"bind -n M-MouseDrag1Pane { move-pane -M }",
|
||||
|
||||
/* Mouse wheel up on pane. */
|
||||
"bind -n WheelUpPane { if -F '#{||:#{alternate_on},#{pane_in_mode},#{mouse_any_flag}}' { send -M } { copy-mode -e } }",
|
||||
@@ -466,9 +473,10 @@ key_bindings_init(void)
|
||||
|
||||
/* Mouse button 1 drag on border. */
|
||||
"bind -n MouseDrag1Border { resize-pane -M }",
|
||||
"bind -n M-MouseDrag1Border { move-pane -M }",
|
||||
|
||||
/* Mouse button 1 down on status line. */
|
||||
"bind -n MouseDown1Status { switch-client -t= }",
|
||||
"bind -n MouseDown1Status { if -F '#{&&:#{pane_active},#{!#{pane_hidden_flag}}}' { hide-pane -t= } { switch-client -t= } }",
|
||||
"bind -n C-MouseDown1Status { swap-window -t@ }",
|
||||
|
||||
/* Mouse button 1 down on default pane-border-format */
|
||||
@@ -482,8 +490,8 @@ key_bindings_init(void)
|
||||
"bind -n WheelUpStatus { previous-window }",
|
||||
|
||||
/* Mouse button 3 down on status left. */
|
||||
"bind -n MouseDown3StatusLeft { display-menu -t= -xM -yW -T '#[align=centre]#{session_name}' " DEFAULT_SESSION_MENU " }",
|
||||
"bind -n M-MouseDown3StatusLeft { display-menu -t= -xM -yW -T '#[align=centre]#{session_name}' " DEFAULT_SESSION_MENU " }",
|
||||
"bind -n MouseDown3StatusLeft { run -C \"display-menu -t= -xM -yW -T '#[align=centre]#{session_name}' " DEFAULT_SESSION_MENU "\" }",
|
||||
"bind -n M-MouseDown3StatusLeft { run -C \"display-menu -t= -xM -yW -T '#[align=centre]#{session_name}' " DEFAULT_SESSION_MENU "\" }",
|
||||
|
||||
/* Mouse button 3 down on status line. */
|
||||
"bind -n MouseDown3Status { display-menu -t= -xW -yW -T '#[align=centre]#{window_index}:#{window_name}' " DEFAULT_WINDOW_MENU "}",
|
||||
@@ -511,8 +519,8 @@ key_bindings_init(void)
|
||||
"bind -Tcopy-mode M-l { send -X cursor-centre-horizontal }",
|
||||
"bind -Tcopy-mode C-n { send -X cursor-down }",
|
||||
"bind -Tcopy-mode C-p { send -X cursor-up }",
|
||||
"bind -Tcopy-mode C-r { command-prompt -T search -ip'(search up)' -I'#{pane_search_string}' { send -X search-backward-incremental -- '%%' } }",
|
||||
"bind -Tcopy-mode C-s { command-prompt -T search -ip'(search down)' -I'#{pane_search_string}' { send -X search-forward-incremental -- '%%' } }",
|
||||
"bind -Tcopy-mode C-r { command-prompt -P -T search -ip'(search up)' -I'#{pane_search_string}' { send -X search-backward-incremental -- '%%' } }",
|
||||
"bind -Tcopy-mode C-s { command-prompt -P -T search -ip'(search down)' -I'#{pane_search_string}' { send -X search-forward-incremental -- '%%' } }",
|
||||
"bind -Tcopy-mode C-v { send -X page-down }",
|
||||
"bind -Tcopy-mode C-w { send -X copy-pipe-and-cancel }",
|
||||
"bind -Tcopy-mode Escape { send -X cancel }",
|
||||
@@ -520,18 +528,18 @@ key_bindings_init(void)
|
||||
"bind -Tcopy-mode Space { send -X page-down }",
|
||||
"bind -Tcopy-mode , { send -X jump-reverse }",
|
||||
"bind -Tcopy-mode \\; { send -X jump-again }",
|
||||
"bind -Tcopy-mode F { command-prompt -1p'(jump backward)' { send -X jump-backward -- '%%' } }",
|
||||
"bind -Tcopy-mode F { command-prompt -P -1p'(jump backward)' { send -X jump-backward -- '%%' } }",
|
||||
"bind -Tcopy-mode N { send -X search-reverse }",
|
||||
"bind -Tcopy-mode P { send -X toggle-position }",
|
||||
"bind -Tcopy-mode R { send -X rectangle-toggle }",
|
||||
"bind -Tcopy-mode T { command-prompt -1p'(jump to backward)' { send -X jump-to-backward -- '%%' } }",
|
||||
"bind -Tcopy-mode T { command-prompt -P -1p'(jump to backward)' { send -X jump-to-backward -- '%%' } }",
|
||||
"bind -Tcopy-mode X { send -X set-mark }",
|
||||
"bind -Tcopy-mode f { command-prompt -1p'(jump forward)' { send -X jump-forward -- '%%' } }",
|
||||
"bind -Tcopy-mode g { command-prompt -p'(goto line)' { send -X goto-line -- '%%' } }",
|
||||
"bind -Tcopy-mode f { command-prompt -P -1p'(jump forward)' { send -X jump-forward -- '%%' } }",
|
||||
"bind -Tcopy-mode g { command-prompt -P -p'(goto line)' { send -X goto-line -- '%%' } }",
|
||||
"bind -Tcopy-mode n { send -X search-again }",
|
||||
"bind -Tcopy-mode q { send -X cancel }",
|
||||
"bind -Tcopy-mode r { send -X refresh-from-pane }",
|
||||
"bind -Tcopy-mode t { command-prompt -1p'(jump to forward)' { send -X jump-to-forward -- '%%' } }",
|
||||
"bind -Tcopy-mode r { send -X refresh-toggle }",
|
||||
"bind -Tcopy-mode t { command-prompt -P -1p'(jump to forward)' { send -X jump-to-forward -- '%%' } }",
|
||||
"bind -Tcopy-mode Home { send -X start-of-line }",
|
||||
"bind -Tcopy-mode End { send -X end-of-line }",
|
||||
"bind -Tcopy-mode MouseDown1Pane select-pane",
|
||||
@@ -547,15 +555,15 @@ key_bindings_init(void)
|
||||
"bind -Tcopy-mode Down { send -X cursor-down }",
|
||||
"bind -Tcopy-mode Left { send -X cursor-left }",
|
||||
"bind -Tcopy-mode Right { send -X cursor-right }",
|
||||
"bind -Tcopy-mode M-1 { command-prompt -Np'(repeat)' -I1 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode M-2 { command-prompt -Np'(repeat)' -I2 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode M-3 { command-prompt -Np'(repeat)' -I3 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode M-4 { command-prompt -Np'(repeat)' -I4 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode M-5 { command-prompt -Np'(repeat)' -I5 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode M-6 { command-prompt -Np'(repeat)' -I6 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode M-7 { command-prompt -Np'(repeat)' -I7 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode M-8 { command-prompt -Np'(repeat)' -I8 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode M-9 { command-prompt -Np'(repeat)' -I9 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode M-1 { command-prompt -P -Np'(repeat)' -I1 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode M-2 { command-prompt -P -Np'(repeat)' -I2 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode M-3 { command-prompt -P -Np'(repeat)' -I3 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode M-4 { command-prompt -P -Np'(repeat)' -I4 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode M-5 { command-prompt -P -Np'(repeat)' -I5 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode M-6 { command-prompt -P -Np'(repeat)' -I6 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode M-7 { command-prompt -P -Np'(repeat)' -I7 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode M-8 { command-prompt -P -Np'(repeat)' -I8 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode M-9 { command-prompt -P -Np'(repeat)' -I9 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode M-< { send -X history-top }",
|
||||
"bind -Tcopy-mode M-> { send -X history-bottom }",
|
||||
"bind -Tcopy-mode M-R { send -X top-line }",
|
||||
@@ -574,6 +582,8 @@ key_bindings_init(void)
|
||||
"bind -Tcopy-mode M-Down { send -X halfpage-down }",
|
||||
"bind -Tcopy-mode C-Up { send -X scroll-up }",
|
||||
"bind -Tcopy-mode C-Down { send -X scroll-down }",
|
||||
"bind -Tcopy-mode M-C-Up { send -X previous-prompt }",
|
||||
"bind -Tcopy-mode M-C-Down { send -X next-prompt }",
|
||||
|
||||
/* Copy mode (vi) keys. */
|
||||
"bind -Tcopy-mode-vi '#' { send -FX search-backward -- '#{copy_cursor_word}' }",
|
||||
@@ -594,25 +604,25 @@ key_bindings_init(void)
|
||||
"bind -Tcopy-mode-vi Space { send -X begin-selection }",
|
||||
"bind -Tcopy-mode-vi '$' { send -X end-of-line }",
|
||||
"bind -Tcopy-mode-vi , { send -X jump-reverse }",
|
||||
"bind -Tcopy-mode-vi / { command-prompt -T search -p'(search down)' { send -X search-forward -- '%%' } }",
|
||||
"bind -Tcopy-mode-vi / { command-prompt -P -T search -p'(search down)' { send -X search-forward -- '%%' } }",
|
||||
"bind -Tcopy-mode-vi 0 { send -X start-of-line }",
|
||||
"bind -Tcopy-mode-vi 1 { command-prompt -Np'(repeat)' -I1 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode-vi 2 { command-prompt -Np'(repeat)' -I2 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode-vi 3 { command-prompt -Np'(repeat)' -I3 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode-vi 4 { command-prompt -Np'(repeat)' -I4 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode-vi 5 { command-prompt -Np'(repeat)' -I5 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode-vi 6 { command-prompt -Np'(repeat)' -I6 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode-vi 7 { command-prompt -Np'(repeat)' -I7 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode-vi 8 { command-prompt -Np'(repeat)' -I8 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode-vi 9 { command-prompt -Np'(repeat)' -I9 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode-vi : { command-prompt -p'(goto line)' { send -X goto-line -- '%%' } }",
|
||||
"bind -Tcopy-mode-vi 1 { command-prompt -P -Np'(repeat)' -I1 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode-vi 2 { command-prompt -P -Np'(repeat)' -I2 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode-vi 3 { command-prompt -P -Np'(repeat)' -I3 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode-vi 4 { command-prompt -P -Np'(repeat)' -I4 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode-vi 5 { command-prompt -P -Np'(repeat)' -I5 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode-vi 6 { command-prompt -P -Np'(repeat)' -I6 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode-vi 7 { command-prompt -P -Np'(repeat)' -I7 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode-vi 8 { command-prompt -P -Np'(repeat)' -I8 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode-vi 9 { command-prompt -P -Np'(repeat)' -I9 { send -N '%%' } }",
|
||||
"bind -Tcopy-mode-vi : { command-prompt -P -p'(goto line)' { send -X goto-line -- '%%' } }",
|
||||
"bind -Tcopy-mode-vi \\; { send -X jump-again }",
|
||||
"bind -Tcopy-mode-vi ? { command-prompt -T search -p'(search up)' { send -X search-backward -- '%%' } }",
|
||||
"bind -Tcopy-mode-vi ? { command-prompt -P -T search -p'(search up)' { send -X search-backward -- '%%' } }",
|
||||
"bind -Tcopy-mode-vi A { send -X append-selection-and-cancel }",
|
||||
"bind -Tcopy-mode-vi B { send -X previous-space }",
|
||||
"bind -Tcopy-mode-vi D { send -X copy-pipe-end-of-line-and-cancel }",
|
||||
"bind -Tcopy-mode-vi E { send -X next-space-end }",
|
||||
"bind -Tcopy-mode-vi F { command-prompt -1p'(jump backward)' { send -X jump-backward -- '%%' } }",
|
||||
"bind -Tcopy-mode-vi F { command-prompt -P -1p'(jump backward)' { send -X jump-backward -- '%%' } }",
|
||||
"bind -Tcopy-mode-vi G { send -X history-bottom }",
|
||||
"bind -Tcopy-mode-vi H { send -X top-line }",
|
||||
"bind -Tcopy-mode-vi J { send -X scroll-down }",
|
||||
@@ -621,14 +631,14 @@ key_bindings_init(void)
|
||||
"bind -Tcopy-mode-vi M { send -X middle-line }",
|
||||
"bind -Tcopy-mode-vi N { send -X search-reverse }",
|
||||
"bind -Tcopy-mode-vi P { send -X toggle-position }",
|
||||
"bind -Tcopy-mode-vi T { command-prompt -1p'(jump to backward)' { send -X jump-to-backward -- '%%' } }",
|
||||
"bind -Tcopy-mode-vi T { command-prompt -P -1p'(jump to backward)' { send -X jump-to-backward -- '%%' } }",
|
||||
"bind -Tcopy-mode-vi V { send -X select-line }",
|
||||
"bind -Tcopy-mode-vi W { send -X next-space }",
|
||||
"bind -Tcopy-mode-vi X { send -X set-mark }",
|
||||
"bind -Tcopy-mode-vi ^ { send -X back-to-indentation }",
|
||||
"bind -Tcopy-mode-vi b { send -X previous-word }",
|
||||
"bind -Tcopy-mode-vi e { send -X next-word-end }",
|
||||
"bind -Tcopy-mode-vi f { command-prompt -1p'(jump forward)' { send -X jump-forward -- '%%' } }",
|
||||
"bind -Tcopy-mode-vi f { command-prompt -P -1p'(jump forward)' { send -X jump-forward -- '%%' } }",
|
||||
"bind -Tcopy-mode-vi g { send -X history-top }",
|
||||
"bind -Tcopy-mode-vi h { send -X cursor-left }",
|
||||
"bind -Tcopy-mode-vi j { send -X cursor-down }",
|
||||
@@ -638,8 +648,8 @@ key_bindings_init(void)
|
||||
"bind -Tcopy-mode-vi n { send -X search-again }",
|
||||
"bind -Tcopy-mode-vi o { send -X other-end }",
|
||||
"bind -Tcopy-mode-vi q { send -X cancel }",
|
||||
"bind -Tcopy-mode-vi r { send -X refresh-from-pane }",
|
||||
"bind -Tcopy-mode-vi t { command-prompt -1p'(jump to forward)' { send -X jump-to-forward -- '%%' } }",
|
||||
"bind -Tcopy-mode-vi r { send -X refresh-toggle }",
|
||||
"bind -Tcopy-mode-vi t { command-prompt -P -1p'(jump to forward)' { send -X jump-to-forward -- '%%' } }",
|
||||
"bind -Tcopy-mode-vi v { send -X rectangle-toggle }",
|
||||
"bind -Tcopy-mode-vi w { send -X next-word }",
|
||||
"bind -Tcopy-mode-vi '{' { send -X previous-paragraph }",
|
||||
|
||||
@@ -265,7 +265,7 @@ layout_parse(struct window *w, const char *layout, char **cause)
|
||||
window_resize(w, tiled_lc->sx, tiled_lc->sy, -1, -1);
|
||||
|
||||
/* Destroy the old layout and swap to the new. */
|
||||
layout_free_cell(w->layout_root);
|
||||
layout_free_cell(w->layout_root, 0);
|
||||
w->layout_root = tiled_lc;
|
||||
|
||||
/* Assign the panes into the cells. */
|
||||
@@ -291,7 +291,7 @@ layout_parse(struct window *w, const char *layout, char **cause)
|
||||
return (0);
|
||||
|
||||
fail:
|
||||
layout_free_cell(tiled_lc);
|
||||
layout_free_cell(tiled_lc, 0);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
@@ -379,6 +379,8 @@ layout_construct(struct layout_cell *lcparent, const char **layout,
|
||||
struct layout_cell *lcchild;
|
||||
|
||||
*lc = layout_construct_cell(lcparent, layout);
|
||||
if (*lc == NULL)
|
||||
return (-1);
|
||||
|
||||
switch (**layout) {
|
||||
case ',':
|
||||
@@ -421,6 +423,6 @@ layout_construct(struct layout_cell *lcparent, const char **layout,
|
||||
return (0);
|
||||
|
||||
fail:
|
||||
layout_free_cell(*lc);
|
||||
layout_free_cell(*lc, 0);
|
||||
return (-1);
|
||||
}
|
||||
|
||||
314
layout-set.c
314
layout-set.c
@@ -124,12 +124,12 @@ layout_set_previous(struct window *w)
|
||||
}
|
||||
|
||||
static struct window_pane *
|
||||
layout_first_tiled(struct window *w)
|
||||
layout_set_first_tiled(struct window *w)
|
||||
{
|
||||
struct window_pane *wp;
|
||||
|
||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||
if (!window_pane_is_floating(wp))
|
||||
if (wp->layout_cell && layout_cell_is_tiled(wp->layout_cell))
|
||||
return (wp);
|
||||
}
|
||||
return (NULL);
|
||||
@@ -139,19 +139,15 @@ static void
|
||||
layout_set_even(struct window *w, enum layout_type type)
|
||||
{
|
||||
struct window_pane *wp;
|
||||
struct layout_cell *lc, *lcnew;
|
||||
struct layout_cell *lcroot, *lcchild;
|
||||
u_int n, sx, sy;
|
||||
|
||||
layout_print_cell(w->layout_root, __func__, 1);
|
||||
|
||||
/* Get number of panes. */
|
||||
n = window_count_panes(w, 0);
|
||||
if (n <= 1)
|
||||
return;
|
||||
|
||||
/* Free the old root and construct a new. */
|
||||
layout_free(w);
|
||||
lc = w->layout_root = layout_create_cell(NULL);
|
||||
if (type == LAYOUT_LEFTRIGHT) {
|
||||
sx = (n * (PANE_MINIMUM + 1)) - 1;
|
||||
if (sx < w->sx)
|
||||
@@ -163,30 +159,30 @@ layout_set_even(struct window *w, enum layout_type type)
|
||||
sy = w->sy;
|
||||
sx = w->sx;
|
||||
}
|
||||
layout_set_size(lc, sx, sy, 0, 0);
|
||||
layout_make_node(lc, type);
|
||||
|
||||
/* Build new leaf cells. */
|
||||
layout_free(w, 1);
|
||||
lcroot = w->layout_root = layout_create_cell(NULL);
|
||||
layout_set_size(lcroot, sx, sy, 0, 0);
|
||||
layout_make_node(lcroot, type);
|
||||
|
||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||
if (window_pane_is_floating(wp))
|
||||
continue;
|
||||
lcnew = layout_create_cell(lc);
|
||||
layout_make_leaf(lcnew, wp);
|
||||
lcnew->sx = w->sx;
|
||||
lcnew->sy = w->sy;
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcnew, entry);
|
||||
lcchild = wp->layout_cell;
|
||||
TAILQ_INSERT_TAIL(&lcroot->cells, lcchild, entry);
|
||||
lcchild->parent = lcroot;
|
||||
if (layout_cell_is_tiled(lcchild)) {
|
||||
lcchild->sx = w->sx;
|
||||
lcchild->sy = w->sy;
|
||||
}
|
||||
}
|
||||
|
||||
/* Spread out cells. */
|
||||
layout_spread_cell(w, lc);
|
||||
layout_spread_cell(w, lcroot);
|
||||
|
||||
/* Fix cell offsets. */
|
||||
layout_fix_offsets(w);
|
||||
layout_fix_panes(w, NULL);
|
||||
|
||||
layout_print_cell(w->layout_root, __func__, 1);
|
||||
|
||||
window_resize(w, lc->sx, lc->sy, -1, -1);
|
||||
window_resize(w, lcroot->sx, lcroot->sy, -1, -1);
|
||||
notify_window("window-layout-changed", w);
|
||||
server_redraw_window(w);
|
||||
}
|
||||
@@ -206,15 +202,14 @@ layout_set_even_v(struct window *w)
|
||||
static void
|
||||
layout_set_main_h(struct window *w)
|
||||
{
|
||||
struct window_pane *wp;
|
||||
struct layout_cell *lc, *lcmain, *lcother, *lcchild;
|
||||
struct window_pane *wp, *wpmain;
|
||||
struct layout_cell *lcroot, *lcmain, *lcother, *lcchild;
|
||||
u_int n, mainh, otherh, sx, sy;
|
||||
char *cause;
|
||||
const char *s;
|
||||
|
||||
layout_print_cell(w->layout_root, __func__, 1);
|
||||
|
||||
/* Get number of panes. */
|
||||
n = window_count_panes(w, 0);
|
||||
if (n <= 1)
|
||||
return;
|
||||
@@ -255,52 +250,48 @@ layout_set_main_h(struct window *w)
|
||||
if (sx < w->sx)
|
||||
sx = w->sx;
|
||||
|
||||
/* Free old tree and create a new root. */
|
||||
layout_free(w);
|
||||
lc = w->layout_root = layout_create_cell(NULL);
|
||||
layout_set_size(lc, sx, mainh + otherh + 1, 0, 0);
|
||||
layout_make_node(lc, LAYOUT_TOPBOTTOM);
|
||||
layout_free(w, 1);
|
||||
lcroot = w->layout_root = layout_create_cell(NULL);
|
||||
layout_set_size(lcroot, sx, mainh + otherh + 1, 0, 0);
|
||||
layout_make_node(lcroot, LAYOUT_TOPBOTTOM);
|
||||
|
||||
/* Create the main pane. */
|
||||
lcmain = layout_create_cell(lc);
|
||||
wpmain = layout_set_first_tiled(w);
|
||||
lcmain = wpmain->layout_cell;
|
||||
lcmain->parent = lcroot;
|
||||
layout_set_size(lcmain, sx, mainh, 0, 0);
|
||||
layout_make_leaf(lcmain, layout_first_tiled(w));
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry);
|
||||
TAILQ_INSERT_TAIL(&lcroot->cells, lcmain, entry);
|
||||
|
||||
/* Create the other pane. */
|
||||
lcother = layout_create_cell(lc);
|
||||
layout_set_size(lcother, sx, otherh, 0, 0);
|
||||
if (n == 1) {
|
||||
wp = TAILQ_NEXT(layout_first_tiled(w), entry);
|
||||
while (wp != NULL && window_pane_is_floating(wp))
|
||||
wp = TAILQ_NEXT(wpmain, entry);
|
||||
while (wp != NULL && !layout_cell_is_tiled(wp->layout_cell))
|
||||
wp = TAILQ_NEXT(wp, entry);
|
||||
layout_make_leaf(lcother, wp);
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcother, entry);
|
||||
TAILQ_INSERT_TAIL(&lcroot->cells, wp->layout_cell, entry);
|
||||
wp->layout_cell->parent = lcroot;
|
||||
} else {
|
||||
lcother = layout_create_cell(lcroot);
|
||||
layout_set_size(lcother, sx, otherh, 0, 0);
|
||||
layout_make_node(lcother, LAYOUT_LEFTRIGHT);
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcother, entry);
|
||||
TAILQ_INSERT_TAIL(&lcroot->cells, lcother, entry);
|
||||
|
||||
/* Add the remaining panes as children. */
|
||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||
if (window_pane_is_floating(wp))
|
||||
if (wp == wpmain)
|
||||
continue;
|
||||
if (wp == layout_first_tiled(w))
|
||||
continue;
|
||||
lcchild = layout_create_cell(lcother);
|
||||
layout_set_size(lcchild, PANE_MINIMUM, otherh, 0, 0);
|
||||
layout_make_leaf(lcchild, wp);
|
||||
lcchild = wp->layout_cell;
|
||||
TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry);
|
||||
lcchild->parent = lcother;
|
||||
if (layout_cell_is_tiled(lcchild))
|
||||
layout_set_size(lcchild, PANE_MINIMUM, otherh,
|
||||
0, 0);
|
||||
}
|
||||
layout_spread_cell(w, lcother);
|
||||
}
|
||||
|
||||
/* Fix cell offsets. */
|
||||
layout_fix_offsets(w);
|
||||
layout_fix_panes(w, NULL);
|
||||
|
||||
layout_print_cell(w->layout_root, __func__, 1);
|
||||
|
||||
window_resize(w, lc->sx, lc->sy, -1, -1);
|
||||
window_resize(w, lcroot->sx, lcroot->sy, -1, -1);
|
||||
notify_window("window-layout-changed", w);
|
||||
server_redraw_window(w);
|
||||
}
|
||||
@@ -308,15 +299,14 @@ layout_set_main_h(struct window *w)
|
||||
static void
|
||||
layout_set_main_h_mirrored(struct window *w)
|
||||
{
|
||||
struct window_pane *wp;
|
||||
struct layout_cell *lc, *lcmain, *lcother, *lcchild;
|
||||
struct window_pane *wp, *wpmain;
|
||||
struct layout_cell *lcroot, *lcmain, *lcother, *lcchild;
|
||||
u_int n, mainh, otherh, sx, sy;
|
||||
char *cause;
|
||||
const char *s;
|
||||
|
||||
layout_print_cell(w->layout_root, __func__, 1);
|
||||
|
||||
/* Get number of panes. */
|
||||
n = window_count_panes(w, 0);
|
||||
if (n <= 1)
|
||||
return;
|
||||
@@ -357,52 +347,48 @@ layout_set_main_h_mirrored(struct window *w)
|
||||
if (sx < w->sx)
|
||||
sx = w->sx;
|
||||
|
||||
/* Free old tree and create a new root. */
|
||||
layout_free(w);
|
||||
lc = w->layout_root = layout_create_cell(NULL);
|
||||
layout_set_size(lc, sx, mainh + otherh + 1, 0, 0);
|
||||
layout_make_node(lc, LAYOUT_TOPBOTTOM);
|
||||
layout_free(w, 1);
|
||||
lcroot = w->layout_root = layout_create_cell(NULL);
|
||||
layout_set_size(lcroot, sx, mainh + otherh + 1, 0, 0);
|
||||
layout_make_node(lcroot, LAYOUT_TOPBOTTOM);
|
||||
|
||||
wpmain = layout_set_first_tiled(w);
|
||||
lcmain = wpmain->layout_cell;
|
||||
lcmain->parent = lcroot;
|
||||
layout_set_size(lcmain, sx, mainh, 0, 0);
|
||||
TAILQ_INSERT_TAIL(&lcroot->cells, lcmain, entry);
|
||||
|
||||
/* Create the other pane. */
|
||||
lcother = layout_create_cell(lc);
|
||||
layout_set_size(lcother, sx, otherh, 0, 0);
|
||||
if (n == 1) {
|
||||
wp = TAILQ_NEXT(layout_first_tiled(w), entry);
|
||||
while (wp != NULL && window_pane_is_floating(wp))
|
||||
wp = TAILQ_NEXT(wpmain, entry);
|
||||
while (wp != NULL && !layout_cell_is_tiled(wp->layout_cell))
|
||||
wp = TAILQ_NEXT(wp, entry);
|
||||
layout_make_leaf(lcother, wp);
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcother, entry);
|
||||
TAILQ_INSERT_HEAD(&lcroot->cells, wp->layout_cell, entry);
|
||||
wp->layout_cell->parent = lcroot;
|
||||
} else {
|
||||
lcother = layout_create_cell(lcroot);
|
||||
layout_set_size(lcother, sx, otherh, 0, 0);
|
||||
layout_make_node(lcother, LAYOUT_LEFTRIGHT);
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcother, entry);
|
||||
TAILQ_INSERT_HEAD(&lcroot->cells, lcother, entry);
|
||||
|
||||
/* Add the remaining panes as children. */
|
||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||
if (window_pane_is_floating(wp))
|
||||
if (wp == wpmain)
|
||||
continue;
|
||||
if (wp == layout_first_tiled(w))
|
||||
continue;
|
||||
lcchild = layout_create_cell(lcother);
|
||||
layout_set_size(lcchild, PANE_MINIMUM, otherh, 0, 0);
|
||||
layout_make_leaf(lcchild, wp);
|
||||
lcchild = wp->layout_cell;
|
||||
TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry);
|
||||
lcchild->parent = lcother;
|
||||
if (layout_cell_is_tiled(lcchild))
|
||||
layout_set_size(lcchild, PANE_MINIMUM, otherh,
|
||||
0, 0);
|
||||
}
|
||||
layout_spread_cell(w, lcother);
|
||||
}
|
||||
|
||||
/* Create the main pane. */
|
||||
lcmain = layout_create_cell(lc);
|
||||
layout_set_size(lcmain, sx, mainh, 0, 0);
|
||||
layout_make_leaf(lcmain, layout_first_tiled(w));
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry);
|
||||
|
||||
/* Fix cell offsets. */
|
||||
layout_fix_offsets(w);
|
||||
layout_fix_panes(w, NULL);
|
||||
|
||||
layout_print_cell(w->layout_root, __func__, 1);
|
||||
|
||||
window_resize(w, lc->sx, lc->sy, -1, -1);
|
||||
window_resize(w, lcroot->sx, lcroot->sy, -1, -1);
|
||||
notify_window("window-layout-changed", w);
|
||||
server_redraw_window(w);
|
||||
}
|
||||
@@ -410,21 +396,20 @@ layout_set_main_h_mirrored(struct window *w)
|
||||
static void
|
||||
layout_set_main_v(struct window *w)
|
||||
{
|
||||
struct window_pane *wp;
|
||||
struct layout_cell *lc, *lcmain, *lcother, *lcchild;
|
||||
struct window_pane *wp, *wpmain;
|
||||
struct layout_cell *lcroot, *lcmain, *lcother, *lcchild;
|
||||
u_int n, mainw, otherw, sx, sy;
|
||||
char *cause;
|
||||
const char *s;
|
||||
|
||||
layout_print_cell(w->layout_root, __func__, 1);
|
||||
|
||||
/* Get number of panes. */
|
||||
n = window_count_panes(w, 0);
|
||||
if (n <= 1)
|
||||
return;
|
||||
n--; /* take off main pane */
|
||||
|
||||
/* Find available width - take off one line for the border. */
|
||||
/* Find available width - take off one column for the border. */
|
||||
sx = w->sx - 1;
|
||||
|
||||
/* Get the main pane width. */
|
||||
@@ -459,52 +444,48 @@ layout_set_main_v(struct window *w)
|
||||
if (sy < w->sy)
|
||||
sy = w->sy;
|
||||
|
||||
/* Free old tree and create a new root. */
|
||||
layout_free(w);
|
||||
lc = w->layout_root = layout_create_cell(NULL);
|
||||
layout_set_size(lc, mainw + otherw + 1, sy, 0, 0);
|
||||
layout_make_node(lc, LAYOUT_LEFTRIGHT);
|
||||
layout_free(w, 1);
|
||||
lcroot = w->layout_root = layout_create_cell(NULL);
|
||||
layout_set_size(lcroot, mainw + otherw + 1, sy, 0, 0);
|
||||
layout_make_node(lcroot, LAYOUT_LEFTRIGHT);
|
||||
|
||||
/* Create the main pane. */
|
||||
lcmain = layout_create_cell(lc);
|
||||
wpmain = layout_set_first_tiled(w);
|
||||
lcmain = wpmain->layout_cell;
|
||||
lcmain->parent = lcroot;
|
||||
layout_set_size(lcmain, mainw, sy, 0, 0);
|
||||
layout_make_leaf(lcmain, layout_first_tiled(w));
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry);
|
||||
TAILQ_INSERT_TAIL(&lcroot->cells, lcmain, entry);
|
||||
|
||||
/* Create the other pane. */
|
||||
lcother = layout_create_cell(lc);
|
||||
layout_set_size(lcother, otherw, sy, 0, 0);
|
||||
if (n == 1) {
|
||||
wp = TAILQ_NEXT(layout_first_tiled(w), entry);
|
||||
while (wp != NULL && window_pane_is_floating(wp))
|
||||
wp = TAILQ_NEXT(wpmain, entry);
|
||||
while (wp != NULL && !layout_cell_is_tiled(wp->layout_cell))
|
||||
wp = TAILQ_NEXT(wp, entry);
|
||||
layout_make_leaf(lcother, wp);
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcother, entry);
|
||||
TAILQ_INSERT_TAIL(&lcroot->cells, wp->layout_cell, entry);
|
||||
wp->layout_cell->parent = lcroot;
|
||||
} else {
|
||||
lcother = layout_create_cell(lcroot);
|
||||
layout_make_node(lcother, LAYOUT_TOPBOTTOM);
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcother, entry);
|
||||
layout_set_size(lcother, otherw, sy, 0, 0);
|
||||
TAILQ_INSERT_TAIL(&lcroot->cells, lcother, entry);
|
||||
|
||||
/* Add the remaining panes as children. */
|
||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||
if (window_pane_is_floating(wp))
|
||||
if (wp == wpmain)
|
||||
continue;
|
||||
if (wp == layout_first_tiled(w))
|
||||
continue;
|
||||
lcchild = layout_create_cell(lcother);
|
||||
layout_set_size(lcchild, otherw, PANE_MINIMUM, 0, 0);
|
||||
layout_make_leaf(lcchild, wp);
|
||||
lcchild = wp->layout_cell;
|
||||
TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry);
|
||||
lcchild->parent = lcother;
|
||||
if (layout_cell_is_tiled(lcchild))
|
||||
layout_set_size(lcchild, otherw, PANE_MINIMUM,
|
||||
0, 0);
|
||||
}
|
||||
layout_spread_cell(w, lcother);
|
||||
}
|
||||
|
||||
/* Fix cell offsets. */
|
||||
layout_fix_offsets(w);
|
||||
layout_fix_panes(w, NULL);
|
||||
|
||||
layout_print_cell(w->layout_root, __func__, 1);
|
||||
|
||||
window_resize(w, lc->sx, lc->sy, -1, -1);
|
||||
window_resize(w, lcroot->sx, lcroot->sy, -1, -1);
|
||||
notify_window("window-layout-changed", w);
|
||||
server_redraw_window(w);
|
||||
}
|
||||
@@ -512,8 +493,8 @@ layout_set_main_v(struct window *w)
|
||||
static void
|
||||
layout_set_main_v_mirrored(struct window *w)
|
||||
{
|
||||
struct window_pane *wp;
|
||||
struct layout_cell *lc, *lcmain, *lcother, *lcchild;
|
||||
struct window_pane *wp, *wpmain;
|
||||
struct layout_cell *lcroot, *lcmain, *lcother, *lcchild;
|
||||
u_int n, mainw, otherw, sx, sy;
|
||||
char *cause;
|
||||
const char *s;
|
||||
@@ -526,7 +507,7 @@ layout_set_main_v_mirrored(struct window *w)
|
||||
return;
|
||||
n--; /* take off main pane */
|
||||
|
||||
/* Find available width - take off one line for the border. */
|
||||
/* Find available width - take off one column for the border. */
|
||||
sx = w->sx - 1;
|
||||
|
||||
/* Get the main pane width. */
|
||||
@@ -561,62 +542,58 @@ layout_set_main_v_mirrored(struct window *w)
|
||||
if (sy < w->sy)
|
||||
sy = w->sy;
|
||||
|
||||
/* Free old tree and create a new root. */
|
||||
layout_free(w);
|
||||
lc = w->layout_root = layout_create_cell(NULL);
|
||||
layout_set_size(lc, mainw + otherw + 1, sy, 0, 0);
|
||||
layout_make_node(lc, LAYOUT_LEFTRIGHT);
|
||||
layout_free(w, 1);
|
||||
lcroot = w->layout_root = layout_create_cell(NULL);
|
||||
layout_set_size(lcroot, mainw + otherw + 1, sy, 0, 0);
|
||||
layout_make_node(lcroot, LAYOUT_LEFTRIGHT);
|
||||
|
||||
wpmain = layout_set_first_tiled(w);
|
||||
lcmain = wpmain->layout_cell;
|
||||
lcmain->parent = lcroot;
|
||||
layout_set_size(lcmain, mainw, sy, 0, 0);
|
||||
TAILQ_INSERT_TAIL(&lcroot->cells, lcmain, entry);
|
||||
|
||||
/* Create the other pane. */
|
||||
lcother = layout_create_cell(lc);
|
||||
layout_set_size(lcother, otherw, sy, 0, 0);
|
||||
if (n == 1) {
|
||||
wp = TAILQ_NEXT(layout_first_tiled(w), entry);
|
||||
while (wp != NULL && window_pane_is_floating(wp))
|
||||
wp = TAILQ_NEXT(wpmain, entry);
|
||||
while (wp != NULL && !layout_cell_is_tiled(wp->layout_cell))
|
||||
wp = TAILQ_NEXT(wp, entry);
|
||||
layout_make_leaf(lcother, wp);
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcother, entry);
|
||||
TAILQ_INSERT_HEAD(&lcroot->cells, wp->layout_cell, entry);
|
||||
wp->layout_cell->parent = lcroot;
|
||||
} else {
|
||||
lcother = layout_create_cell(lcroot);
|
||||
layout_make_node(lcother, LAYOUT_TOPBOTTOM);
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcother, entry);
|
||||
layout_set_size(lcother, otherw, sy, 0, 0);
|
||||
TAILQ_INSERT_HEAD(&lcroot->cells, lcother, entry);
|
||||
|
||||
/* Add the remaining panes as children. */
|
||||
TAILQ_FOREACH(wp, &w->panes, entry) {
|
||||
if (window_pane_is_floating(wp))
|
||||
if (wp == wpmain)
|
||||
continue;
|
||||
if (wp == layout_first_tiled(w))
|
||||
continue;
|
||||
lcchild = layout_create_cell(lcother);
|
||||
layout_set_size(lcchild, otherw, PANE_MINIMUM, 0, 0);
|
||||
layout_make_leaf(lcchild, wp);
|
||||
lcchild = wp->layout_cell;
|
||||
TAILQ_INSERT_TAIL(&lcother->cells, lcchild, entry);
|
||||
lcchild->parent = lcother;
|
||||
if (layout_cell_is_tiled(lcchild))
|
||||
layout_set_size(lcchild, otherw, PANE_MINIMUM,
|
||||
0, 0);
|
||||
}
|
||||
layout_spread_cell(w, lcother);
|
||||
}
|
||||
|
||||
/* Create the main pane. */
|
||||
lcmain = layout_create_cell(lc);
|
||||
layout_set_size(lcmain, mainw, sy, 0, 0);
|
||||
layout_make_leaf(lcmain, layout_first_tiled(w));
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcmain, entry);
|
||||
|
||||
/* Fix cell offsets. */
|
||||
layout_fix_offsets(w);
|
||||
layout_fix_panes(w, NULL);
|
||||
|
||||
layout_print_cell(w->layout_root, __func__, 1);
|
||||
|
||||
window_resize(w, lc->sx, lc->sy, -1, -1);
|
||||
window_resize(w, lcroot->sx, lcroot->sy, -1, -1);
|
||||
notify_window("window-layout-changed", w);
|
||||
server_redraw_window(w);
|
||||
}
|
||||
|
||||
void
|
||||
static void
|
||||
layout_set_tiled(struct window *w)
|
||||
{
|
||||
struct options *oo = w->options;
|
||||
struct window_pane *wp;
|
||||
struct layout_cell *lc, *lcrow, *lcchild;
|
||||
struct layout_cell *lcroot, *lcrow, *lcchild;
|
||||
u_int n, width, height, used, sx, sy;
|
||||
u_int i, j, columns, rows, max_columns;
|
||||
|
||||
@@ -647,56 +624,59 @@ layout_set_tiled(struct window *w)
|
||||
if (height < PANE_MINIMUM)
|
||||
height = PANE_MINIMUM;
|
||||
|
||||
/* Free old tree and create a new root. */
|
||||
layout_free(w);
|
||||
lc = w->layout_root = layout_create_cell(NULL);
|
||||
sx = ((width + 1) * columns) - 1;
|
||||
if (sx < w->sx)
|
||||
sx = w->sx;
|
||||
sy = ((height + 1) * rows) - 1;
|
||||
if (sy < w->sy)
|
||||
sy = w->sy;
|
||||
layout_set_size(lc, sx, sy, 0, 0);
|
||||
layout_make_node(lc, LAYOUT_TOPBOTTOM);
|
||||
|
||||
/* Create a grid of the cells, skipping any floating panes. */
|
||||
layout_free(w, 1);
|
||||
lcroot = w->layout_root = layout_create_cell(NULL);
|
||||
layout_set_size(lcroot, sx, sy, 0, 0);
|
||||
layout_make_node(lcroot, LAYOUT_TOPBOTTOM);
|
||||
|
||||
/* Create a grid of the tiled cells. */
|
||||
wp = TAILQ_FIRST(&w->panes);
|
||||
while (wp != NULL && window_pane_is_floating(wp))
|
||||
wp = TAILQ_NEXT(wp, entry);
|
||||
for (j = 0; j < rows; j++) {
|
||||
while (wp != NULL && !layout_cell_is_tiled(wp->layout_cell))
|
||||
wp = TAILQ_NEXT(wp, entry);
|
||||
/* If this is the last cell, all done. */
|
||||
if (wp == NULL)
|
||||
break;
|
||||
|
||||
/* Create the new row. */
|
||||
lcrow = layout_create_cell(lc);
|
||||
layout_set_size(lcrow, w->sx, height, 0, 0);
|
||||
TAILQ_INSERT_TAIL(&lc->cells, lcrow, entry);
|
||||
lcchild = wp->layout_cell;
|
||||
|
||||
/* If only one column, just use the row directly. */
|
||||
if (n - (j * columns) == 1 || columns == 1) {
|
||||
layout_make_leaf(lcrow, wp);
|
||||
lcchild->parent = lcroot;
|
||||
TAILQ_INSERT_TAIL(&lcroot->cells, lcchild, entry);
|
||||
layout_set_size(lcchild, w->sx, height, 0, 0);
|
||||
wp = TAILQ_NEXT(wp, entry);
|
||||
while (wp != NULL && window_pane_is_floating(wp))
|
||||
wp = TAILQ_NEXT(wp, entry);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Add in the columns. */
|
||||
/* Create the new row. */
|
||||
lcrow = layout_create_cell(lcroot);
|
||||
layout_make_node(lcrow, LAYOUT_LEFTRIGHT);
|
||||
layout_set_size(lcrow, w->sx, height, 0, 0);
|
||||
TAILQ_INSERT_TAIL(&lcroot->cells, lcrow, entry);
|
||||
|
||||
/* Add in the columns. */
|
||||
for (i = 0; i < columns; i++) {
|
||||
/* Create and add a pane cell. */
|
||||
lcchild = layout_create_cell(lcrow);
|
||||
layout_set_size(lcchild, width, height, 0, 0);
|
||||
layout_make_leaf(lcchild, wp);
|
||||
lcchild->parent = lcrow;
|
||||
TAILQ_INSERT_TAIL(&lcrow->cells, lcchild, entry);
|
||||
layout_set_size(lcchild, width, height, 0, 0);
|
||||
|
||||
/* Move to the next non-floating cell. */
|
||||
wp = TAILQ_NEXT(wp, entry);
|
||||
while (wp != NULL && window_pane_is_floating(wp))
|
||||
while (wp != NULL &&
|
||||
!layout_cell_is_tiled(wp->layout_cell))
|
||||
wp = TAILQ_NEXT(wp, entry);
|
||||
if (wp == NULL)
|
||||
break;
|
||||
lcchild = wp->layout_cell;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -713,21 +693,19 @@ layout_set_tiled(struct window *w)
|
||||
w->sx - used);
|
||||
}
|
||||
|
||||
/* Adjust the last row height to fit if necessary. */
|
||||
used = (rows * height) + rows - 1;
|
||||
if (w->sy > used) {
|
||||
lcrow = TAILQ_LAST(&lc->cells, layout_cells);
|
||||
lcrow = TAILQ_LAST(&lcroot->cells, layout_cells);
|
||||
layout_resize_adjust(w, lcrow, LAYOUT_TOPBOTTOM,
|
||||
w->sy - used);
|
||||
}
|
||||
|
||||
/* Fix cell offsets. */
|
||||
layout_fix_offsets(w);
|
||||
layout_fix_panes(w, NULL);
|
||||
|
||||
layout_print_cell(w->layout_root, __func__, 1);
|
||||
|
||||
window_resize(w, lc->sx, lc->sy, -1, -1);
|
||||
window_resize(w, lcroot->sx, lcroot->sy, -1, -1);
|
||||
notify_window("window-layout-changed", w);
|
||||
server_redraw_window(w);
|
||||
}
|
||||
|
||||
15
menu.c
15
menu.c
@@ -257,8 +257,7 @@ menu_reapply_styles(struct menu_data *md, struct client *c)
|
||||
}
|
||||
|
||||
void
|
||||
menu_draw_cb(struct client *c, void *data,
|
||||
__unused struct screen_redraw_ctx *rctx)
|
||||
menu_draw_cb(struct client *c, void *data)
|
||||
{
|
||||
struct menu_data *md = data;
|
||||
struct tty *tty = &c->tty;
|
||||
@@ -281,10 +280,8 @@ menu_draw_cb(struct client *c, void *data,
|
||||
&md->style_gc, &md->border_style_gc, &md->selected_style_gc);
|
||||
screen_write_stop(&ctx);
|
||||
|
||||
for (i = 0; i < screen_size_y(&md->s); i++) {
|
||||
tty_draw_line(tty, s, 0, i, menu->width + 4, px, py + i,
|
||||
&grid_default_cell, NULL);
|
||||
}
|
||||
for (i = 0; i < screen_size_y(&md->s); i++)
|
||||
tty_draw_line(tty, s, 0, i, menu->width + 4, px, py + i, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -440,7 +437,7 @@ menu_key_cb(struct client *c, void *data, struct key_event *event)
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (name == NULL || *name == '-') {
|
||||
while ((name == NULL || *name == '-') && md->choice != 0) {
|
||||
md->choice--;
|
||||
name = menu->items[md->choice].name;
|
||||
}
|
||||
@@ -450,7 +447,7 @@ menu_key_cb(struct client *c, void *data, struct key_event *event)
|
||||
case KEYC_HOME:
|
||||
md->choice = 0;
|
||||
name = menu->items[md->choice].name;
|
||||
while (name == NULL || *name == '-') {
|
||||
while ((name == NULL || *name == '-') && md->choice != count - 1) {
|
||||
md->choice++;
|
||||
name = menu->items[md->choice].name;
|
||||
}
|
||||
@@ -460,7 +457,7 @@ menu_key_cb(struct client *c, void *data, struct key_event *event)
|
||||
case KEYC_END:
|
||||
md->choice = count - 1;
|
||||
name = menu->items[md->choice].name;
|
||||
while (name == NULL || *name == '-') {
|
||||
while ((name == NULL || *name == '-') && md->choice != 0) {
|
||||
md->choice--;
|
||||
name = menu->items[md->choice].name;
|
||||
}
|
||||
|
||||
660
mode-tree.c
660
mode-tree.c
@@ -36,7 +36,26 @@ enum mode_tree_preview {
|
||||
MODE_TREE_PREVIEW_BIG
|
||||
};
|
||||
|
||||
#define MODE_TREE_PREFIX_STYLE \
|
||||
"#{?mode_tree_selected,#[default]#[noacs]," \
|
||||
"#[fg=themelightgrey]#[bg=default]#[noacs]}"
|
||||
|
||||
#define MODE_TREE_PREFIX_FORMAT \
|
||||
MODE_TREE_PREFIX_STYLE \
|
||||
"#{p/#{mode_tree_key_width}:" \
|
||||
"#{?#{!=:#{mode_tree_key},},(#{mode_tree_key}),}}" \
|
||||
"#{R:#{?mode_tree_parent_last, ," \
|
||||
"#[acs]x" MODE_TREE_PREFIX_STYLE " }," \
|
||||
"#{mode_tree_repeat}}" \
|
||||
"#{?mode_tree_branch," \
|
||||
"#[acs]#{?mode_tree_last,mq,tq}" MODE_TREE_PREFIX_STYLE "> ,}" \
|
||||
"#{?mode_tree_has_children," \
|
||||
"#{?mode_tree_expanded,#[fg=themered]-" MODE_TREE_PREFIX_STYLE " ," \
|
||||
"#[fg=themegreen]+" MODE_TREE_PREFIX_STYLE " }," \
|
||||
"#{?mode_tree_flat,, }}"
|
||||
|
||||
struct mode_tree_item;
|
||||
struct mode_tree_prompt;
|
||||
TAILQ_HEAD(mode_tree_list, mode_tree_item);
|
||||
|
||||
struct mode_tree_data {
|
||||
@@ -49,12 +68,13 @@ struct mode_tree_data {
|
||||
const struct menu_item *menu;
|
||||
|
||||
struct sort_criteria sort_crit;
|
||||
const char *view_name;
|
||||
|
||||
mode_tree_build_cb buildcb;
|
||||
mode_tree_draw_cb drawcb;
|
||||
mode_tree_search_cb searchcb;
|
||||
mode_tree_menu_cb menucb;
|
||||
mode_tree_height_cb heightcb;
|
||||
mode_tree_build_cb buildcb;
|
||||
mode_tree_draw_cb drawcb;
|
||||
mode_tree_search_cb searchcb;
|
||||
mode_tree_menu_cb menucb;
|
||||
mode_tree_height_cb heightcb;
|
||||
mode_tree_key_cb keycb;
|
||||
mode_tree_swap_cb swapcb;
|
||||
mode_tree_sort_cb sortcb;
|
||||
@@ -76,6 +96,10 @@ struct mode_tree_data {
|
||||
u_int current;
|
||||
|
||||
struct screen screen;
|
||||
struct prompt *prompt;
|
||||
struct mode_tree_prompt *prompt_data;
|
||||
u_int prompt_cx;
|
||||
int prompt_top;
|
||||
|
||||
int preview;
|
||||
char *search;
|
||||
@@ -83,6 +107,7 @@ struct mode_tree_data {
|
||||
int no_matches;
|
||||
enum mode_tree_search_dir search_dir;
|
||||
int search_icase;
|
||||
int help;
|
||||
};
|
||||
|
||||
struct mode_tree_item {
|
||||
@@ -122,7 +147,25 @@ struct mode_tree_menu {
|
||||
u_int line;
|
||||
};
|
||||
|
||||
static void mode_tree_free_items(struct mode_tree_list *);
|
||||
/*
|
||||
* Wrapper around a prompt owned by a mode tree. The mode tree holds a reference
|
||||
* while the prompt is alive; the wrapper callbacks forward to the caller's
|
||||
* callbacks and drop that reference when the prompt is freed.
|
||||
*/
|
||||
struct mode_tree_prompt {
|
||||
struct mode_tree_data *mtd;
|
||||
struct client *c;
|
||||
mode_tree_prompt_input_cb inputcb;
|
||||
prompt_free_cb freecb;
|
||||
void *data;
|
||||
};
|
||||
|
||||
static void mode_tree_free_items(struct mode_tree_list *);
|
||||
static void mode_tree_draw_help(struct mode_tree_data *,
|
||||
struct screen_write_ctx *);
|
||||
static void mode_tree_draw_prompt(struct mode_tree_data *,
|
||||
struct screen_write_ctx *);
|
||||
static enum cmd_retval mode_tree_prompt_accept(struct cmdq_item *, void *);
|
||||
|
||||
static const struct menu_item mode_tree_menu_items[] = {
|
||||
{ "Scroll Left", '<', NULL },
|
||||
@@ -134,31 +177,51 @@ static const struct menu_item mode_tree_menu_items[] = {
|
||||
};
|
||||
|
||||
static const char* mode_tree_help_start[] = {
|
||||
"\r\033[1m Up, k \033[0m\016x\017 \033[0mMove cursor up\n",
|
||||
"\r\033[1m Down, j \033[0m\016x\017 \033[0mMove cursor down\n",
|
||||
"\r\033[1m g \033[0m\016x\017 \033[0mGo to top\n",
|
||||
"\r\033[1m G \033[0m\016x\017 \033[0mGo to bottom\n",
|
||||
"\r\033[1m PPage, C-b \033[0m\016x\017 \033[0mPage up\n",
|
||||
"\r\033[1m NPage, C-f \033[0m\016x\017 \033[0mPage down\n",
|
||||
"\r\033[1m Left, h \033[0m\016x\017 \033[0mCollapse %1\n",
|
||||
"\r\033[1m Right, l \033[0m\016x\017 \033[0mExpand %1\n",
|
||||
"\r\033[1m M-- \033[0m\016x\017 \033[0mCollapse all %1s\n",
|
||||
"\r\033[1m M-+ \033[0m\016x\017 \033[0mExpand all %1s\n",
|
||||
"\r\033[1m t \033[0m\016x\017 \033[0mToggle %1 tag\n",
|
||||
"\r\033[1m T \033[0m\016x\017 \033[0mUntag all %1s\n",
|
||||
"\r\033[1m C-t \033[0m\016x\017 \033[0mTag all %1s\n",
|
||||
"\r\033[1m C-s \033[0m\016x\017 \033[0mSearch forward\n",
|
||||
"\r\033[1m C-r \033[0m\016x\017 \033[0mSearch backward\n",
|
||||
"\r\033[1m n \033[0m\016x\017 \033[0mRepeat search forward\n",
|
||||
"\r\033[1m N \033[0m\016x\017 \033[0mRepeat search backward\n",
|
||||
"\r\033[1m f \033[0m\016x\017 \033[0mFilter %1s\n",
|
||||
"\r\033[1m O \033[0m\016x\017 \033[0mChange sort order\n",
|
||||
"\r\033[1m r \033[0m\016x\017 \033[0mReverse sort order\n",
|
||||
"\r\033[1m v \033[0m\016x\017 \033[0mToggle preview\n",
|
||||
"#[fg=themelightgrey]"
|
||||
" Up, k #[#{E:tree-mode-border-style},acs]x#[default] Move cursor up",
|
||||
"#[fg=themelightgrey]"
|
||||
" Down, j #[#{E:tree-mode-border-style},acs]x#[default] Move cursor down",
|
||||
"#[fg=themelightgrey]"
|
||||
" g #[#{E:tree-mode-border-style},acs]x#[default] Go to top",
|
||||
"#[fg=themelightgrey]"
|
||||
" G #[#{E:tree-mode-border-style},acs]x#[default] Go to bottom",
|
||||
"#[fg=themelightgrey]"
|
||||
" PPage, C-b #[#{E:tree-mode-border-style},acs]x#[default] Page up",
|
||||
"#[fg=themelightgrey]"
|
||||
" NPage, C-f #[#{E:tree-mode-border-style},acs]x#[default] Page down",
|
||||
"#[fg=themelightgrey]"
|
||||
" Left, h #[#{E:tree-mode-border-style},acs]x#[default] Collapse %1",
|
||||
"#[fg=themelightgrey]"
|
||||
" Right, l #[#{E:tree-mode-border-style},acs]x#[default] Expand %1",
|
||||
"#[fg=themelightgrey]"
|
||||
" M-- #[#{E:tree-mode-border-style},acs]x#[default] Collapse all %1s",
|
||||
"#[fg=themelightgrey]"
|
||||
" M-+ #[#{E:tree-mode-border-style},acs]x#[default] Expand all %1s",
|
||||
"#[fg=themelightgrey]"
|
||||
" t #[#{E:tree-mode-border-style},acs]x#[default] Toggle %1 tag",
|
||||
"#[fg=themelightgrey]"
|
||||
" T #[#{E:tree-mode-border-style},acs]x#[default] Untag all %1s",
|
||||
"#[fg=themelightgrey]"
|
||||
" C-t #[#{E:tree-mode-border-style},acs]x#[default] Tag all %1s",
|
||||
"#[fg=themelightgrey]"
|
||||
" C-s #[#{E:tree-mode-border-style},acs]x#[default] Search forward",
|
||||
"#[fg=themelightgrey]"
|
||||
" n #[#{E:tree-mode-border-style},acs]x#[default] Repeat search forward",
|
||||
"#[fg=themelightgrey]"
|
||||
" N #[#{E:tree-mode-border-style},acs]x#[default] Repeat search backward",
|
||||
"#[fg=themelightgrey]"
|
||||
" f #[#{E:tree-mode-border-style},acs]x#[default] Filter %1s",
|
||||
"#[fg=themelightgrey]"
|
||||
" O #[#{E:tree-mode-border-style},acs]x#[default] Change sort order",
|
||||
"#[fg=themelightgrey]"
|
||||
" r #[#{E:tree-mode-border-style},acs]x#[default] Reverse sort order",
|
||||
"#[fg=themelightgrey]"
|
||||
" v #[#{E:tree-mode-border-style},acs]x#[default] Toggle preview",
|
||||
NULL
|
||||
};
|
||||
static const char* mode_tree_help_end[] = {
|
||||
"\r\033[1m q, Escape \033[0m\016x\017 \033[0mExit mode\033[H",
|
||||
"#[fg=themelightgrey]"
|
||||
" q, Escape #[#{E:tree-mode-border-style},acs]x#[default] Exit mode",
|
||||
NULL
|
||||
};
|
||||
#define MODE_TREE_HELP_DEFAULT_WIDTH 39
|
||||
@@ -378,6 +441,13 @@ mode_tree_get_current_name(struct mode_tree_data *mtd)
|
||||
return (mtd->line_list[mtd->current].item->name);
|
||||
}
|
||||
|
||||
void
|
||||
mode_tree_select_top(struct mode_tree_data *mtd)
|
||||
{
|
||||
mtd->current = 0;
|
||||
mtd->offset = 0;
|
||||
}
|
||||
|
||||
void
|
||||
mode_tree_expand_current(struct mode_tree_data *mtd)
|
||||
{
|
||||
@@ -505,7 +575,9 @@ mode_tree_start(struct window_pane *wp, struct args *args,
|
||||
mtd->modedata = modedata;
|
||||
mtd->menu = menu;
|
||||
|
||||
if (args_has(args, 'N') > 1)
|
||||
if (drawcb == NULL)
|
||||
mtd->preview = MODE_TREE_PREVIEW_OFF;
|
||||
else if (args_has(args, 'N') > 1)
|
||||
mtd->preview = MODE_TREE_PREVIEW_BIG;
|
||||
else if (args_has(args, 'N'))
|
||||
mtd->preview = MODE_TREE_PREVIEW_OFF;
|
||||
@@ -637,6 +709,7 @@ mode_tree_free(struct mode_tree_data *mtd)
|
||||
if (mtd->zoomed == 0)
|
||||
server_unzoom_window(wp->window);
|
||||
|
||||
mode_tree_clear_prompt(mtd);
|
||||
mode_tree_free_items(&mtd->children);
|
||||
mode_tree_clear_lines(mtd);
|
||||
screen_free(&mtd->screen);
|
||||
@@ -700,6 +773,12 @@ mode_tree_add(struct mode_tree_data *mtd, struct mode_tree_item *parent,
|
||||
return (mti);
|
||||
}
|
||||
|
||||
void
|
||||
mode_tree_view_name(struct mode_tree_data *mtd, const char *name)
|
||||
{
|
||||
mtd->view_name = name;
|
||||
}
|
||||
|
||||
void
|
||||
mode_tree_draw_as_parent(struct mode_tree_item *mti)
|
||||
{
|
||||
@@ -713,8 +792,8 @@ mode_tree_no_tag(struct mode_tree_item *mti)
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the alignment of mti->name: -1 to align left, 0 (default) to not align,
|
||||
* or 1 to align right.
|
||||
* Set the alignment of the item name: -1 to align left, 0 (default) to not
|
||||
* align, or 1 to align right.
|
||||
*/
|
||||
void
|
||||
mode_tree_align(struct mode_tree_item *mti, int align)
|
||||
@@ -743,25 +822,34 @@ mode_tree_draw(struct mode_tree_data *mtd)
|
||||
struct mode_tree_item *mti;
|
||||
struct options *oo = wp->window->options;
|
||||
struct screen_write_ctx ctx;
|
||||
struct grid_cell gc0, gc;
|
||||
u_int w, h, i, j, sy, box_x, box_y, width;
|
||||
char *text, *start, *key;
|
||||
const char *tag, *symbol;
|
||||
size_t size, n;
|
||||
int keylen, pad, alignlen[mtd->maxdepth + 1];
|
||||
struct format_tree *ft;
|
||||
struct grid_cell gc0, gc, box_gc;
|
||||
u_int w, h, i, sy, box_x, box_y;
|
||||
u_int width, text_width, prefix_width, left;
|
||||
char *text, *prefix;
|
||||
const char *tag, *separator;
|
||||
size_t n;
|
||||
int keylen, alignlen[mtd->maxdepth + 1];
|
||||
int dfg, dfg0;
|
||||
|
||||
if (mtd->line_size == 0)
|
||||
return;
|
||||
|
||||
memcpy(&gc0, &grid_default_cell, sizeof gc0);
|
||||
memcpy(&gc, &grid_default_cell, sizeof gc);
|
||||
style_apply(&gc, oo, "mode-style", NULL);
|
||||
style_apply(&gc, oo, "tree-mode-selection-style", NULL);
|
||||
memcpy(&box_gc, &grid_default_cell, sizeof box_gc);
|
||||
style_apply(&box_gc, oo, "tree-mode-border-style", NULL);
|
||||
|
||||
dfg = gc.fg;
|
||||
dfg0 = gc0.fg;
|
||||
|
||||
w = mtd->width;
|
||||
h = mtd->height;
|
||||
|
||||
screen_write_start(&ctx, s);
|
||||
screen_write_clearscreen(&ctx, 8);
|
||||
ft = format_create_defaults(NULL, NULL, NULL, NULL, wp);
|
||||
|
||||
keylen = 0;
|
||||
for (i = 0; i < mtd->line_size; i++) {
|
||||
@@ -792,81 +880,98 @@ mode_tree_draw(struct mode_tree_data *mtd)
|
||||
|
||||
screen_write_cursormove(&ctx, 0, i - mtd->offset, 0);
|
||||
|
||||
pad = keylen - 2 - mti->keylen;
|
||||
if (mti->key != KEYC_NONE)
|
||||
xasprintf(&key, "(%s)%*s", mti->keystr, pad, "");
|
||||
format_add(ft, "mode_tree_key", "%s", mti->keystr);
|
||||
else
|
||||
key = xstrdup("");
|
||||
|
||||
if (line->flat)
|
||||
symbol = "";
|
||||
else if (TAILQ_EMPTY(&mti->children))
|
||||
symbol = " ";
|
||||
else if (mti->expanded)
|
||||
symbol = "- ";
|
||||
else
|
||||
symbol = "+ ";
|
||||
|
||||
if (line->depth == 0)
|
||||
start = xstrdup(symbol);
|
||||
else {
|
||||
size = (4 * line->depth) + 32;
|
||||
|
||||
start = xcalloc(1, size);
|
||||
for (j = 1; j < line->depth; j++) {
|
||||
if (mti->parent != NULL &&
|
||||
mtd->line_list[mti->parent->line].last)
|
||||
strlcat(start, " ", size);
|
||||
else
|
||||
strlcat(start, "\001x\001 ", size);
|
||||
}
|
||||
if (line->last)
|
||||
strlcat(start, "\001mq\001> ", size);
|
||||
format_add(ft, "mode_tree_key", "%s", "");
|
||||
format_add(ft, "mode_tree_key_width", "%d", keylen);
|
||||
format_add(ft, "mode_tree_selected", "%d", i == mtd->current);
|
||||
if (line->depth == 0) {
|
||||
format_add(ft, "mode_tree_repeat", "%u", 0);
|
||||
format_add(ft, "mode_tree_branch", "0");
|
||||
format_add(ft, "mode_tree_parent_last", "0");
|
||||
} else {
|
||||
format_add(ft, "mode_tree_repeat", "%u",
|
||||
line->depth - 1);
|
||||
format_add(ft, "mode_tree_branch", "1");
|
||||
if (mti->parent != NULL &&
|
||||
mtd->line_list[mti->parent->line].last)
|
||||
format_add(ft, "mode_tree_parent_last", "1");
|
||||
else
|
||||
strlcat(start, "\001tq\001> ", size);
|
||||
strlcat(start, symbol, size);
|
||||
format_add(ft, "mode_tree_parent_last", "0");
|
||||
}
|
||||
if (TAILQ_EMPTY(&mti->children))
|
||||
format_add(ft, "mode_tree_has_children", "0");
|
||||
else
|
||||
format_add(ft, "mode_tree_has_children", "1");
|
||||
format_add(ft, "mode_tree_last", "%d", line->last);
|
||||
format_add(ft, "mode_tree_expanded", "%d", mti->expanded);
|
||||
format_add(ft, "mode_tree_flat", "%d", line->flat);
|
||||
prefix = format_expand(ft, MODE_TREE_PREFIX_FORMAT);
|
||||
prefix_width = format_width(prefix);
|
||||
if (prefix_width > w)
|
||||
prefix_width = w;
|
||||
|
||||
if (mti->tagged)
|
||||
tag = "*";
|
||||
else
|
||||
tag = "";
|
||||
xasprintf(&text, "%-*s%s%*s%s%s", keylen, key, start,
|
||||
mti->align * alignlen[line->depth], mti->name, tag,
|
||||
(mti->text != NULL) ? ": " : "" );
|
||||
width = utf8_cstrwidth(text);
|
||||
if (width > w)
|
||||
width = w;
|
||||
free(start);
|
||||
if (mti->text != NULL)
|
||||
separator = "#[fg=themelightgrey]: #[default]";
|
||||
else
|
||||
separator = "";
|
||||
xasprintf(&text, "%*s%s%s",
|
||||
mti->align * alignlen[line->depth], mti->name, tag, separator);
|
||||
text_width = format_width(text);
|
||||
left = (prefix_width < w) ? (w - prefix_width) : 0;
|
||||
if (text_width > left)
|
||||
text_width = left;
|
||||
width = prefix_width + text_width;
|
||||
|
||||
if (mti->tagged) {
|
||||
gc.attr ^= GRID_ATTR_BRIGHT;
|
||||
gc0.attr ^= GRID_ATTR_BRIGHT;
|
||||
gc.fg = COLOUR_THEME_CYAN|COLOUR_FLAG_THEME;
|
||||
gc0.fg = COLOUR_THEME_CYAN|COLOUR_FLAG_THEME;
|
||||
}
|
||||
|
||||
if (i != mtd->current) {
|
||||
screen_write_clearendofline(&ctx, 8);
|
||||
screen_write_nputs(&ctx, w, &gc0, "%s", text);
|
||||
if (mti->text != NULL) {
|
||||
format_draw(&ctx, &gc0, w - width, mti->text,
|
||||
NULL, 0);
|
||||
format_draw(&ctx, &grid_default_cell, prefix_width,
|
||||
prefix, NULL, 0);
|
||||
if (left != 0) {
|
||||
screen_write_cursormove(&ctx, prefix_width,
|
||||
i - mtd->offset, 0);
|
||||
format_draw(&ctx, &gc0, left, text, NULL, 0);
|
||||
if (mti->text != NULL && width < w) {
|
||||
screen_write_cursormove(&ctx, width,
|
||||
i - mtd->offset, 0);
|
||||
format_draw(&ctx, &gc0, w - width,
|
||||
mti->text, NULL, 0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
screen_write_clearendofline(&ctx, gc.bg);
|
||||
screen_write_nputs(&ctx, w, &gc, "%s", text);
|
||||
if (mti->text != NULL) {
|
||||
format_draw(&ctx, &gc, w - width, mti->text,
|
||||
NULL, 0);
|
||||
format_draw(&ctx, &gc, prefix_width, prefix, NULL, 0);
|
||||
if (left != 0) {
|
||||
screen_write_cursormove(&ctx, prefix_width,
|
||||
i - mtd->offset, 0);
|
||||
format_draw(&ctx, &gc, left, text, NULL, 0);
|
||||
if (mti->text != NULL && width < w) {
|
||||
screen_write_cursormove(&ctx, width,
|
||||
i - mtd->offset, 0);
|
||||
format_draw(&ctx, &gc, w - width,
|
||||
mti->text, NULL, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
free(text);
|
||||
free(key);
|
||||
free(prefix);
|
||||
|
||||
if (mti->tagged) {
|
||||
gc.attr ^= GRID_ATTR_BRIGHT;
|
||||
gc0.attr ^= GRID_ATTR_BRIGHT;
|
||||
gc.fg = dfg;
|
||||
gc0.fg = dfg0;
|
||||
}
|
||||
}
|
||||
format_free(ft);
|
||||
|
||||
if (mtd->preview == MODE_TREE_PREVIEW_OFF)
|
||||
goto done;
|
||||
@@ -881,31 +986,34 @@ mode_tree_draw(struct mode_tree_data *mtd)
|
||||
mti = mti->parent;
|
||||
|
||||
screen_write_cursormove(&ctx, 0, h, 0);
|
||||
screen_write_box(&ctx, w, sy - h, BOX_LINES_DEFAULT, NULL, NULL);
|
||||
screen_write_box(&ctx, w, sy - h, BOX_LINES_DEFAULT, &box_gc, NULL);
|
||||
|
||||
if (mtd->sort_crit.order_seq != NULL) {
|
||||
xasprintf(&text, " %s (sort: %s%s)", mti->name,
|
||||
xasprintf(&text, " %s (sort: %s%s)%s%s%s", mti->name,
|
||||
sort_order_to_string(mtd->sort_crit.order),
|
||||
mtd->sort_crit.reversed ? ", reversed" : "");
|
||||
mtd->sort_crit.reversed ? ", reversed" : "",
|
||||
mtd->view_name == NULL ? "" : " (view: ",
|
||||
mtd->view_name == NULL ? "" : mtd->view_name,
|
||||
mtd->view_name == NULL ? "" : ")");
|
||||
} else
|
||||
xasprintf(&text, " %s", mti->name);
|
||||
if (w - 2 >= strlen(text)) {
|
||||
screen_write_cursormove(&ctx, 1, h, 0);
|
||||
screen_write_puts(&ctx, &gc0, "%s", text);
|
||||
screen_write_puts(&ctx, &box_gc, "%s", text);
|
||||
|
||||
if (mtd->no_matches)
|
||||
n = (sizeof "no matches") - 1;
|
||||
else
|
||||
n = (sizeof "active") - 1;
|
||||
if (mtd->filter != NULL && w - 2 >= strlen(text) + 10 + n + 2) {
|
||||
screen_write_puts(&ctx, &gc0, " (filter: ");
|
||||
screen_write_puts(&ctx, &box_gc, " (filter: ");
|
||||
if (mtd->no_matches)
|
||||
screen_write_puts(&ctx, &gc, "no matches");
|
||||
screen_write_puts(&ctx, &box_gc, "no matches");
|
||||
else
|
||||
screen_write_puts(&ctx, &gc0, "active");
|
||||
screen_write_puts(&ctx, &gc0, ") ");
|
||||
screen_write_puts(&ctx, &box_gc, "active");
|
||||
screen_write_puts(&ctx, &box_gc, ") ");
|
||||
} else
|
||||
screen_write_puts(&ctx, &gc0, " ");
|
||||
screen_write_puts(&ctx, &box_gc, " ");
|
||||
}
|
||||
free(text);
|
||||
|
||||
@@ -918,10 +1026,151 @@ mode_tree_draw(struct mode_tree_data *mtd)
|
||||
}
|
||||
|
||||
done:
|
||||
screen_write_cursormove(&ctx, 0, mtd->current - mtd->offset, 0);
|
||||
if (mtd->help)
|
||||
mode_tree_draw_help(mtd, &ctx);
|
||||
if (mtd->prompt != NULL)
|
||||
mode_tree_draw_prompt(mtd, &ctx);
|
||||
else {
|
||||
s->mode &= ~MODE_CURSOR;
|
||||
screen_write_cursormove(&ctx, 0, mtd->current - mtd->offset, 0);
|
||||
}
|
||||
screen_write_stop(&ctx);
|
||||
}
|
||||
|
||||
static void
|
||||
mode_tree_draw_prompt(struct mode_tree_data *mtd, struct screen_write_ctx *ctx)
|
||||
{
|
||||
struct screen *s = &mtd->screen;
|
||||
struct prompt_draw_data pdd;
|
||||
u_int sx = screen_size_x(s), sy = screen_size_y(s);
|
||||
u_int py;
|
||||
|
||||
if (sx == 0 || sy == 0)
|
||||
return;
|
||||
|
||||
if (mtd->prompt_top)
|
||||
py = 0;
|
||||
else
|
||||
py = sy - 1;
|
||||
|
||||
pdd.ctx = ctx;
|
||||
pdd.cursor_x = &mtd->prompt_cx;
|
||||
pdd.area_x = 0;
|
||||
pdd.area_width = sx;
|
||||
pdd.prompt_line = py;
|
||||
|
||||
s->mode |= MODE_CURSOR;
|
||||
prompt_draw(mtd->prompt, &pdd);
|
||||
screen_write_cursormove(ctx, mtd->prompt_cx, py, 0);
|
||||
}
|
||||
|
||||
void
|
||||
mode_tree_clear_prompt(struct mode_tree_data *mtd)
|
||||
{
|
||||
struct prompt *prompt = mtd->prompt;
|
||||
|
||||
if (mtd->prompt != NULL) {
|
||||
mtd->prompt = NULL;
|
||||
prompt_free(prompt);
|
||||
mtd->screen.mode &= ~MODE_CURSOR;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
mode_tree_has_prompt(struct mode_tree_data *mtd)
|
||||
{
|
||||
return (mtd->prompt != NULL);
|
||||
}
|
||||
|
||||
static enum cmd_retval
|
||||
mode_tree_prompt_accept(struct cmdq_item *item, void *data)
|
||||
{
|
||||
struct mode_tree_data *mtd = data;
|
||||
struct client *c = cmdq_get_client(item);
|
||||
key_code key = 'y';
|
||||
|
||||
if (mtd->prompt != NULL && c != NULL)
|
||||
mode_tree_key(mtd, c, &key, NULL, NULL, NULL);
|
||||
|
||||
mode_tree_remove_ref(mtd);
|
||||
return (CMD_RETURN_NORMAL);
|
||||
}
|
||||
|
||||
static enum prompt_result
|
||||
mode_tree_prompt_input_callback(void *data, const char *s,
|
||||
enum prompt_key_result key)
|
||||
{
|
||||
struct mode_tree_prompt *mtp = data;
|
||||
|
||||
if (mtp->inputcb != NULL)
|
||||
return (mtp->inputcb(mtp->c, mtp->data, s, key));
|
||||
return (PROMPT_CLOSE);
|
||||
}
|
||||
|
||||
static void
|
||||
mode_tree_prompt_free_callback(void *data)
|
||||
{
|
||||
struct mode_tree_prompt *mtp = data;
|
||||
|
||||
if (mtp->mtd->prompt_data == mtp)
|
||||
mtp->mtd->prompt_data = NULL;
|
||||
if (mtp->freecb != NULL)
|
||||
mtp->freecb(mtp->data);
|
||||
mode_tree_remove_ref(mtp->mtd);
|
||||
free(mtp);
|
||||
}
|
||||
|
||||
void
|
||||
mode_tree_set_prompt(struct mode_tree_data *mtd, struct client *c,
|
||||
const char *prompt, const char *input, enum prompt_type type, int flags,
|
||||
mode_tree_prompt_input_cb inputcb, prompt_free_cb freecb, void *data)
|
||||
{
|
||||
struct session *s;
|
||||
struct options *oo;
|
||||
struct prompt_create_data pd;
|
||||
struct mode_tree_prompt *mtp;
|
||||
|
||||
if (c != NULL && c->session != NULL) {
|
||||
s = c->session;
|
||||
oo = s->options;
|
||||
} else {
|
||||
s = NULL;
|
||||
oo = global_s_options;
|
||||
}
|
||||
|
||||
mode_tree_clear_prompt(mtd);
|
||||
|
||||
mtp = xcalloc(1, sizeof *mtp);
|
||||
mtp->mtd = mtd;
|
||||
mtp->c = c;
|
||||
mtp->inputcb = inputcb;
|
||||
mtp->freecb = freecb;
|
||||
mtp->data = data;
|
||||
|
||||
mtd->references++;
|
||||
mtd->prompt_top = options_get_number(oo, "status-position") == 0;
|
||||
|
||||
memset(&pd, 0, sizeof pd);
|
||||
prompt_set_options(&pd, s);
|
||||
pd.prompt = prompt;
|
||||
pd.input = input;
|
||||
pd.type = type;
|
||||
pd.flags = flags|PROMPT_ISMODE;
|
||||
pd.inputcb = mode_tree_prompt_input_callback;
|
||||
pd.freecb = mode_tree_prompt_free_callback;
|
||||
pd.data = mtp;
|
||||
mtd->prompt = prompt_create(&pd);
|
||||
mtd->prompt_data = mtp;
|
||||
|
||||
mode_tree_draw(mtd);
|
||||
mtd->wp->flags |= PANE_REDRAW;
|
||||
|
||||
if ((flags & PROMPT_SINGLE) && (flags & PROMPT_ACCEPT) && c != NULL) {
|
||||
mtd->references++;
|
||||
cmdq_append(c, cmdq_get_callback(mode_tree_prompt_accept, mtd));
|
||||
}
|
||||
}
|
||||
|
||||
static struct mode_tree_item *
|
||||
mode_tree_search_backward(struct mode_tree_data *mtd)
|
||||
{
|
||||
@@ -1042,41 +1291,37 @@ mode_tree_search_set(struct mode_tree_data *mtd)
|
||||
mtd->wp->flags |= PANE_REDRAW;
|
||||
}
|
||||
|
||||
static int
|
||||
static enum prompt_result
|
||||
mode_tree_search_callback(__unused struct client *c, void *data, const char *s,
|
||||
__unused int done)
|
||||
enum prompt_key_result key)
|
||||
{
|
||||
struct mode_tree_data *mtd = data;
|
||||
|
||||
if (mtd->dead)
|
||||
return (0);
|
||||
return (PROMPT_CLOSE);
|
||||
|
||||
free(mtd->search);
|
||||
if (s == NULL || *s == '\0') {
|
||||
if (s == NULL || *s == '\0')
|
||||
mtd->search = NULL;
|
||||
return (0);
|
||||
else {
|
||||
mtd->search = xstrdup(s);
|
||||
mtd->search_icase = mode_tree_is_lowercase(s);
|
||||
mode_tree_search_set(mtd);
|
||||
}
|
||||
mtd->search = xstrdup(s);
|
||||
mtd->search_icase = mode_tree_is_lowercase(s);
|
||||
mode_tree_search_set(mtd);
|
||||
|
||||
return (0);
|
||||
if (key == PROMPT_KEY_HANDLED)
|
||||
return (PROMPT_CONTINUE);
|
||||
return (PROMPT_CLOSE);
|
||||
}
|
||||
|
||||
static void
|
||||
mode_tree_search_free(void *data)
|
||||
{
|
||||
mode_tree_remove_ref(data);
|
||||
}
|
||||
|
||||
static int
|
||||
static enum prompt_result
|
||||
mode_tree_filter_callback(__unused struct client *c, void *data, const char *s,
|
||||
__unused int done)
|
||||
enum prompt_key_result key)
|
||||
{
|
||||
struct mode_tree_data *mtd = data;
|
||||
|
||||
if (mtd->dead)
|
||||
return (0);
|
||||
return (PROMPT_CLOSE);
|
||||
|
||||
if (mtd->filter != NULL)
|
||||
free(mtd->filter);
|
||||
@@ -1089,13 +1334,20 @@ mode_tree_filter_callback(__unused struct client *c, void *data, const char *s,
|
||||
mode_tree_draw(mtd);
|
||||
mtd->wp->flags |= PANE_REDRAW;
|
||||
|
||||
return (0);
|
||||
if (key == PROMPT_KEY_HANDLED)
|
||||
return (PROMPT_CONTINUE);
|
||||
return (PROMPT_CLOSE);
|
||||
}
|
||||
|
||||
static void
|
||||
mode_tree_filter_free(void *data)
|
||||
mode_tree_clear_filter(struct mode_tree_data *mtd)
|
||||
{
|
||||
mode_tree_remove_ref(data);
|
||||
free(mtd->filter);
|
||||
mtd->filter = NULL;
|
||||
|
||||
mode_tree_build(mtd);
|
||||
mode_tree_draw(mtd);
|
||||
mtd->wp->flags |= PANE_REDRAW;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -1165,12 +1417,32 @@ mode_tree_display_menu(struct mode_tree_data *mtd, struct client *c, u_int x,
|
||||
}
|
||||
|
||||
static void
|
||||
mode_tree_display_help(__unused struct mode_tree_data *mtd, struct client *c)
|
||||
mode_tree_draw_help_line(struct screen_write_ctx *ctx,
|
||||
const struct grid_cell *gc, struct format_tree *ft, const char *line,
|
||||
const char *item, u_int x, u_int y, u_int w)
|
||||
{
|
||||
struct session *s = c->session;
|
||||
u_int px, py, w, h = 0;
|
||||
const char **line, **lines = NULL, *item = "item";
|
||||
char *new_line;
|
||||
char *expanded, *replaced;
|
||||
|
||||
replaced = cmd_template_replace(line, item, 1);
|
||||
expanded = format_expand(ft, replaced);
|
||||
free(replaced);
|
||||
screen_write_cursormove(ctx, x, y, 0);
|
||||
screen_write_clearcharacter(ctx, w, gc->bg);
|
||||
screen_write_cursormove(ctx, x, y, 0);
|
||||
format_draw(ctx, gc, w, expanded, NULL, 0);
|
||||
free(expanded);
|
||||
}
|
||||
|
||||
static void
|
||||
mode_tree_draw_help(struct mode_tree_data *mtd, struct screen_write_ctx *ctx)
|
||||
{
|
||||
struct screen *s = &mtd->screen;
|
||||
struct options *oo = mtd->wp->window->options;
|
||||
struct grid_cell box_gc, gc;
|
||||
struct format_tree *ft;
|
||||
const char **line, **lines = NULL, *item = "item";
|
||||
u_int sx = screen_size_x(s), sy = screen_size_y(s);
|
||||
u_int x, y, w, h = 0, box_w, box_h;
|
||||
|
||||
if (mtd->helpcb == NULL)
|
||||
w = MODE_TREE_HELP_DEFAULT_WIDTH;
|
||||
@@ -1186,33 +1458,36 @@ mode_tree_display_help(__unused struct mode_tree_data *mtd, struct client *c)
|
||||
for (line = mode_tree_help_end; *line != NULL; line++)
|
||||
h++;
|
||||
|
||||
if (c->tty.sx < w || c->tty.sy < h)
|
||||
box_w = w + 2;
|
||||
box_h = h + 2;
|
||||
if (sx < box_w || sy < box_h)
|
||||
return;
|
||||
px = (c->tty.sx - w) / 2;
|
||||
py = (c->tty.sy - h) / 2;
|
||||
x = (sx - box_w) / 2;
|
||||
y = (sy - box_h) / 2;
|
||||
|
||||
if (popup_display(POPUP_CLOSEANYKEY|POPUP_NOJOB, BOX_LINES_DEFAULT,
|
||||
NULL, px, py, w, h, NULL, NULL, 0, NULL, NULL, NULL, c, s, NULL,
|
||||
NULL, NULL, NULL) != 0)
|
||||
return;
|
||||
memcpy(&box_gc, &grid_default_cell, sizeof box_gc);
|
||||
style_apply(&box_gc, oo, "tree-mode-border-style", NULL);
|
||||
memcpy(&gc, &grid_default_cell, sizeof gc);
|
||||
ft = format_create_defaults(NULL, NULL, NULL, NULL, mtd->wp);
|
||||
screen_write_cursormove(ctx, x, y, 0);
|
||||
screen_write_box(ctx, box_w, box_h, BOX_LINES_DEFAULT, &box_gc, NULL);
|
||||
|
||||
popup_write(c, "\033[H\033[?25l\033[?7l\033)0", 17);
|
||||
for (line = mode_tree_help_start; *line != NULL; line++) {
|
||||
new_line = cmd_template_replace(*line, item, 1);
|
||||
popup_write(c, new_line, strlen(new_line));
|
||||
free(new_line);
|
||||
}
|
||||
for (line = lines; line != NULL && *line != NULL; line++) {
|
||||
new_line = cmd_template_replace(*line, item, 1);
|
||||
popup_write(c, new_line, strlen(new_line));
|
||||
free(new_line);
|
||||
}
|
||||
for (line = mode_tree_help_end; *line != NULL; line++) {
|
||||
new_line = cmd_template_replace(*line, item, 1);
|
||||
popup_write(c, new_line, strlen(new_line));
|
||||
free(new_line);
|
||||
}
|
||||
popup_write(c, "\033[H", 3);
|
||||
y++;
|
||||
x++;
|
||||
for (line = mode_tree_help_start; *line != NULL; line++, y++)
|
||||
mode_tree_draw_help_line(ctx, &gc, ft, *line, item, x, y, w);
|
||||
for (line = lines; line != NULL && *line != NULL; line++, y++)
|
||||
mode_tree_draw_help_line(ctx, &gc, ft, *line, item, x, y, w);
|
||||
for (line = mode_tree_help_end; *line != NULL; line++, y++)
|
||||
mode_tree_draw_help_line(ctx, &gc, ft, *line, item, x, y, w);
|
||||
format_free(ft);
|
||||
}
|
||||
|
||||
static void
|
||||
mode_tree_display_help(struct mode_tree_data *mtd)
|
||||
{
|
||||
mtd->help = 1;
|
||||
mode_tree_draw(mtd);
|
||||
}
|
||||
|
||||
int
|
||||
@@ -1221,14 +1496,81 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
|
||||
{
|
||||
struct mode_tree_line *line;
|
||||
struct mode_tree_item *current, *parent, *mti;
|
||||
u_int i, x, y;
|
||||
u_int i, x, y, py, sx;
|
||||
int choice, preview;
|
||||
enum prompt_key_result result;
|
||||
int redraw;
|
||||
struct prompt *prompt;
|
||||
struct mode_tree_prompt *mtp;
|
||||
|
||||
if (mtd->line_size == 0) {
|
||||
*key = KEYC_NONE;
|
||||
return (1);
|
||||
}
|
||||
|
||||
if (mtd->prompt != NULL) {
|
||||
redraw = 0;
|
||||
prompt = mtd->prompt;
|
||||
|
||||
mtp = mtd->prompt_data;
|
||||
if (mtp != NULL)
|
||||
mtp->c = c;
|
||||
if (KEYC_IS_MOUSE(*key)) {
|
||||
if (m == NULL ||
|
||||
MOUSE_BUTTONS(m->b) != MOUSE_BUTTON_1 ||
|
||||
MOUSE_DRAG(m->b) || MOUSE_RELEASE(m->b) ||
|
||||
cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0)
|
||||
result = PROMPT_KEY_NOT_HANDLED;
|
||||
else {
|
||||
sx = screen_size_x(&mtd->screen);
|
||||
if (mtd->prompt_top)
|
||||
py = 0;
|
||||
else
|
||||
py = screen_size_y(&mtd->screen) - 1;
|
||||
if (y == py) {
|
||||
result = prompt_mouse(prompt, x, 0, sx,
|
||||
&redraw);
|
||||
} else
|
||||
result = PROMPT_KEY_NOT_HANDLED;
|
||||
}
|
||||
} else
|
||||
result = prompt_key(prompt, *key, &redraw);
|
||||
if (mtd->prompt_data == mtp && mtp != NULL)
|
||||
mtp->c = NULL;
|
||||
|
||||
/*
|
||||
* Only an explicit close or the prompt marking itself closed
|
||||
* ends it; cursor movement and editing keep it open.
|
||||
*/
|
||||
if (mtd->prompt == prompt &&
|
||||
(result == PROMPT_KEY_CLOSE || prompt_closed(prompt)))
|
||||
mode_tree_clear_prompt(mtd);
|
||||
|
||||
if (redraw || mtd->prompt != prompt) {
|
||||
mode_tree_draw(mtd);
|
||||
mtd->wp->flags |= PANE_REDRAW;
|
||||
}
|
||||
if (result != PROMPT_KEY_NOT_HANDLED) {
|
||||
*key = KEYC_NONE;
|
||||
return (0);
|
||||
}
|
||||
}
|
||||
|
||||
if (mtd->help) {
|
||||
if (KEYC_IS_MOUSE(*key)) {
|
||||
*key = KEYC_NONE;
|
||||
return (0);
|
||||
}
|
||||
if (*key == KEYC_FOCUS_IN || *key == KEYC_FOCUS_OUT) {
|
||||
*key = KEYC_NONE;
|
||||
return (0);
|
||||
}
|
||||
mtd->help = 0;
|
||||
mode_tree_draw(mtd);
|
||||
*key = KEYC_NONE;
|
||||
return (0);
|
||||
}
|
||||
|
||||
if (KEYC_IS_MOUSE(*key) && m != NULL) {
|
||||
if (cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0) {
|
||||
*key = KEYC_NONE;
|
||||
@@ -1294,7 +1636,7 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
|
||||
return (1);
|
||||
case KEYC_F1:
|
||||
case 'h'|KEYC_CTRL:
|
||||
mode_tree_display_help(mtd, c);
|
||||
mode_tree_display_help(mtd);
|
||||
break;
|
||||
case KEYC_UP:
|
||||
case 'k':
|
||||
@@ -1424,11 +1766,10 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
|
||||
case '?':
|
||||
case '/':
|
||||
case 's'|KEYC_CTRL:
|
||||
mtd->references++;
|
||||
mtd->search_dir = MODE_TREE_SEARCH_FORWARD;
|
||||
status_prompt_set(c, NULL, "(search) ", "",
|
||||
mode_tree_search_callback, mode_tree_search_free, mtd,
|
||||
PROMPT_NOFORMAT, PROMPT_TYPE_SEARCH);
|
||||
mode_tree_set_prompt(mtd, c, "(search) ", "",
|
||||
PROMPT_TYPE_SEARCH, PROMPT_NOFORMAT,
|
||||
mode_tree_search_callback, NULL, mtd);
|
||||
break;
|
||||
case 'n':
|
||||
mtd->search_dir = MODE_TREE_SEARCH_FORWARD;
|
||||
@@ -1439,10 +1780,13 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
|
||||
mode_tree_search_set(mtd);
|
||||
break;
|
||||
case 'f':
|
||||
mtd->references++;
|
||||
status_prompt_set(c, NULL, "(filter) ", mtd->filter,
|
||||
mode_tree_filter_callback, mode_tree_filter_free, mtd,
|
||||
PROMPT_NOFORMAT, PROMPT_TYPE_SEARCH);
|
||||
mode_tree_set_prompt(mtd, c, "(filter) ", mtd->filter,
|
||||
PROMPT_TYPE_SEARCH, PROMPT_NOFORMAT,
|
||||
mode_tree_filter_callback, NULL, mtd);
|
||||
break;
|
||||
case 'c':
|
||||
mode_tree_clear_prompt(mtd);
|
||||
mode_tree_clear_filter(mtd);
|
||||
break;
|
||||
case 'v':
|
||||
switch (mtd->preview) {
|
||||
|
||||
4
names.c
4
names.c
@@ -95,7 +95,7 @@ check_window_name(struct window *w)
|
||||
name = format_window_name(w);
|
||||
if (strcmp(name, w->name) != 0) {
|
||||
log_debug("@%u new name %s (was %s)", w->id, name, w->name);
|
||||
window_set_name(w, name, WINDOW_NAME_FORBID_EXT);
|
||||
window_set_name(w, name, 1);
|
||||
server_redraw_window_borders(w);
|
||||
server_status_window(w);
|
||||
} else
|
||||
@@ -166,7 +166,7 @@ parse_window_name(const char *in)
|
||||
|
||||
if (*name == '/')
|
||||
name = basename(name);
|
||||
name = clean_name(name, WINDOW_NAME_FORBID);
|
||||
name = clean_name(name, 0);
|
||||
free(copy);
|
||||
if (name == NULL)
|
||||
return (xstrdup(""));
|
||||
|
||||
338
options-table.c
338
options-table.c
@@ -64,19 +64,19 @@ static const char *options_table_cursor_style_list[] = {
|
||||
"blinking-bar", "bar", NULL
|
||||
};
|
||||
static const char *options_table_pane_scrollbars_list[] = {
|
||||
"off", "modal", "on", NULL
|
||||
"off", "modal", "on", "auto-hide", NULL
|
||||
};
|
||||
static const char *options_table_pane_scrollbars_position_list[] = {
|
||||
"right", "left", NULL
|
||||
};
|
||||
static const char *options_table_pane_status_list[] = {
|
||||
"off", "top", "bottom", NULL
|
||||
"off", "top", "bottom", "top-floating", "bottom-floating", NULL
|
||||
};
|
||||
static const char *options_table_pane_border_indicators_list[] = {
|
||||
"off", "colour", "arrows", "both", NULL
|
||||
};
|
||||
static const char *options_table_pane_border_lines_list[] = {
|
||||
"single", "double", "heavy", "simple", "number", "spaces", NULL
|
||||
"single", "double", "heavy", "simple", "number", "spaces", "none", NULL
|
||||
};
|
||||
static const char *options_table_popup_border_lines_list[] = {
|
||||
"single", "double", "heavy", "simple", "rounded", "padded", "none", NULL
|
||||
@@ -108,6 +108,9 @@ static const char *options_table_extended_keys_format_list[] = {
|
||||
static const char *options_table_allow_passthrough_list[] = {
|
||||
"off", "on", "all", NULL
|
||||
};
|
||||
static const char *options_table_theme_list[] = {
|
||||
"detect", "terminal", "light", "dark", NULL
|
||||
};
|
||||
static const char *options_table_copy_mode_line_numbers_list[] = {
|
||||
"off", "default", "absolute", "relative", "hybrid", NULL
|
||||
};
|
||||
@@ -270,6 +273,7 @@ const struct options_name_map options_other_names[] = {
|
||||
{ "clock-mode-color", "clock-mode-colour" },
|
||||
{ "cursor-color", "cursor-colour" },
|
||||
{ "prompt-cursor-color", "prompt-cursor-colour" },
|
||||
{ "prompt-command-cursor-color", "prompt-command-cursor-colour" },
|
||||
{ "pane-colors", "pane-colours" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
@@ -327,9 +331,10 @@ const struct options_table_entry options_table[] = {
|
||||
},
|
||||
|
||||
{ .name = "cursor-colour",
|
||||
.type = OPTIONS_TABLE_COLOUR,
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
|
||||
.default_num = -1,
|
||||
.flags = OPTIONS_TABLE_IS_COLOUR,
|
||||
.default_str = "",
|
||||
.text = "Colour of the cursor."
|
||||
},
|
||||
|
||||
@@ -444,7 +449,7 @@ const struct options_table_entry options_table[] = {
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.default_str = "default",
|
||||
.default_str = "bg=themedarkgrey,fg=themewhite",
|
||||
.separator = ",",
|
||||
.text = "Default style of menu."
|
||||
},
|
||||
@@ -453,7 +458,7 @@ const struct options_table_entry options_table[] = {
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.default_str = "bg=yellow,fg=black",
|
||||
.default_str = "bg=themeyellow,fg=themeblack",
|
||||
.separator = ",",
|
||||
.text = "Default style of selected menu item."
|
||||
},
|
||||
@@ -461,7 +466,7 @@ const struct options_table_entry options_table[] = {
|
||||
{ .name = "menu-border-style",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
.default_str = "default",
|
||||
.default_str = "bg=themedarkgrey,fg=themelightgrey",
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.separator = ",",
|
||||
.text = "Default style of menu borders."
|
||||
@@ -536,6 +541,175 @@ const struct options_table_entry options_table[] = {
|
||||
"automatically detected."
|
||||
},
|
||||
|
||||
{ .name = "theme",
|
||||
.type = OPTIONS_TABLE_CHOICE,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.choices = options_table_theme_list,
|
||||
.default_num = 0,
|
||||
.text = "Whether tmux should detect the terminal theme, use terminal "
|
||||
"ANSI colours, or force the light or dark theme."
|
||||
},
|
||||
|
||||
{ .name = "dark-theme-black",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.flags = OPTIONS_TABLE_IS_COLOUR,
|
||||
.default_str = "#{?#{e|>=:#{client_colours},256},gray5,black}",
|
||||
.text = "Dark theme colour for black."
|
||||
},
|
||||
|
||||
{ .name = "dark-theme-white",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.flags = OPTIONS_TABLE_IS_COLOUR,
|
||||
.default_str = "#{?#{e|>=:#{client_colours},256},gray90,white}",
|
||||
.text = "Dark theme colour for white."
|
||||
},
|
||||
|
||||
{ .name = "dark-theme-light-grey",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.flags = OPTIONS_TABLE_IS_COLOUR,
|
||||
.default_str = "#{?#{e|>=:#{client_colours},256},gray70,white}",
|
||||
.text = "Dark theme colour for light grey."
|
||||
},
|
||||
|
||||
{ .name = "dark-theme-dark-grey",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.flags = OPTIONS_TABLE_IS_COLOUR,
|
||||
.default_str = "#{?#{e|>=:#{client_colours},256},gray15,black}",
|
||||
.text = "Dark theme colour for dark grey."
|
||||
},
|
||||
|
||||
{ .name = "dark-theme-green",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.flags = OPTIONS_TABLE_IS_COLOUR,
|
||||
.default_str = "#{?#{e|>=:#{client_colours},256},yellowgreen,green}",
|
||||
.text = "Dark theme colour for green."
|
||||
},
|
||||
|
||||
{ .name = "dark-theme-yellow",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.flags = OPTIONS_TABLE_IS_COLOUR,
|
||||
.default_str = "#{?#{e|>=:#{client_colours},256},darkgoldenrod,yellow}",
|
||||
.text = "Dark theme colour for yellow."
|
||||
},
|
||||
|
||||
{ .name = "dark-theme-red",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.flags = OPTIONS_TABLE_IS_COLOUR,
|
||||
.default_str = "#{?#{e|>=:#{client_colours},256},indianred,red}",
|
||||
.text = "Dark theme colour for red."
|
||||
},
|
||||
|
||||
{ .name = "dark-theme-blue",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.flags = OPTIONS_TABLE_IS_COLOUR,
|
||||
.default_str = "#{?#{e|>=:#{client_colours},256},skyblue3,blue}",
|
||||
.text = "Dark theme colour for blue."
|
||||
},
|
||||
|
||||
{ .name = "dark-theme-cyan",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.flags = OPTIONS_TABLE_IS_COLOUR,
|
||||
.default_str = "#{?#{e|>=:#{client_colours},256},cadetblue,cyan}",
|
||||
.text = "Dark theme colour for cyan."
|
||||
},
|
||||
|
||||
{ .name = "dark-theme-magenta",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.flags = OPTIONS_TABLE_IS_COLOUR,
|
||||
.default_str = "#{?#{e|>=:#{client_colours},256},mediumpurple,magenta}",
|
||||
.text = "Dark theme colour for magenta."
|
||||
},
|
||||
|
||||
{ .name = "light-theme-black",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.flags = OPTIONS_TABLE_IS_COLOUR,
|
||||
.default_str = "#{?#{e|>=:#{client_colours},256},gray10,black}",
|
||||
.text = "Light theme colour for black."
|
||||
},
|
||||
|
||||
{ .name = "light-theme-white",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.flags = OPTIONS_TABLE_IS_COLOUR,
|
||||
.default_str = "#{?#{e|>=:#{client_colours},256},gray95,white}",
|
||||
.text = "Light theme colour for white."
|
||||
},
|
||||
|
||||
{ .name = "light-theme-light-grey",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.flags = OPTIONS_TABLE_IS_COLOUR,
|
||||
.default_str = "#{?#{e|>=:#{client_colours},256},gray80,white}",
|
||||
.text = "Light theme colour for light grey."
|
||||
},
|
||||
|
||||
{ .name = "light-theme-dark-grey",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.flags = OPTIONS_TABLE_IS_COLOUR,
|
||||
.default_str = "#{?#{e|>=:#{client_colours},256},gray45,black}",
|
||||
.text = "Light theme colour for dark grey."
|
||||
},
|
||||
|
||||
{ .name = "light-theme-green",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.flags = OPTIONS_TABLE_IS_COLOUR,
|
||||
.default_str = "#{?#{e|>=:#{client_colours},256},seagreen,green}",
|
||||
.text = "Light theme colour for green."
|
||||
},
|
||||
|
||||
{ .name = "light-theme-yellow",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.flags = OPTIONS_TABLE_IS_COLOUR,
|
||||
.default_str = "#{?#{e|>=:#{client_colours},256},darkgoldenrod,yellow}",
|
||||
.text = "Light theme colour for yellow."
|
||||
},
|
||||
|
||||
{ .name = "light-theme-red",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.flags = OPTIONS_TABLE_IS_COLOUR,
|
||||
.default_str = "#{?#{e|>=:#{client_colours},256},indianred4,red}",
|
||||
.text = "Light theme colour for red."
|
||||
},
|
||||
|
||||
{ .name = "light-theme-blue",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.flags = OPTIONS_TABLE_IS_COLOUR,
|
||||
.default_str = "#{?#{e|>=:#{client_colours},256},steelblue,blue}",
|
||||
.text = "Light theme colour for blue."
|
||||
},
|
||||
|
||||
{ .name = "light-theme-cyan",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.flags = OPTIONS_TABLE_IS_COLOUR,
|
||||
.default_str = "#{?#{e|>=:#{client_colours},256},darkcyan,cyan}",
|
||||
.text = "Light theme colour for cyan."
|
||||
},
|
||||
|
||||
{ .name = "light-theme-magenta",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
.flags = OPTIONS_TABLE_IS_COLOUR,
|
||||
.default_str = "#{?#{e|>=:#{client_colours},256},purple4,magenta}",
|
||||
.text = "Light theme colour for magenta."
|
||||
},
|
||||
|
||||
{ .name = "user-keys",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SERVER,
|
||||
@@ -634,19 +808,29 @@ const struct options_table_entry options_table[] = {
|
||||
},
|
||||
|
||||
{ .name = "display-panes-active-colour",
|
||||
.type = OPTIONS_TABLE_COLOUR,
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SESSION,
|
||||
.default_num = 1,
|
||||
.flags = OPTIONS_TABLE_IS_COLOUR,
|
||||
.default_str = "themered",
|
||||
.text = "Colour of the active pane for 'display-panes'."
|
||||
},
|
||||
|
||||
{ .name = "display-panes-colour",
|
||||
.type = OPTIONS_TABLE_COLOUR,
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SESSION,
|
||||
.default_num = 4,
|
||||
.flags = OPTIONS_TABLE_IS_COLOUR,
|
||||
.default_str = "themeblue",
|
||||
.text = "Colour of not active panes for 'display-panes'."
|
||||
},
|
||||
|
||||
{ .name = "display-panes-format",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SESSION,
|
||||
.default_str = "#[align=right]#{pane_width}x#{pane_height}",
|
||||
.text = "Format of text shown by 'display-panes', expanded for each "
|
||||
"pane."
|
||||
},
|
||||
|
||||
{ .name = "display-panes-time",
|
||||
.type = OPTIONS_TABLE_NUMBER,
|
||||
.scope = OPTIONS_TABLE_SESSION,
|
||||
@@ -726,11 +910,13 @@ const struct options_table_entry options_table[] = {
|
||||
{ .name = "message-command-style",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SESSION,
|
||||
.default_str = "bg=black,fg=yellow,fill=black",
|
||||
.default_str = "bg=themeblack,fg=themeyellow,"
|
||||
"#{?#{m/r:(^|#,)IS(PANE|MODE)($|#,),#{prompt_flags}},,"
|
||||
"fill=themeblack}",
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.separator = ",",
|
||||
.text = "Style of the command prompt when in command mode, if "
|
||||
"'mode-keys' is set to 'vi'."
|
||||
"'status-keys' is set to 'vi'."
|
||||
},
|
||||
|
||||
{ .name = "message-format",
|
||||
@@ -755,7 +941,9 @@ const struct options_table_entry options_table[] = {
|
||||
{ .name = "message-style",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SESSION,
|
||||
.default_str = "bg=yellow,fg=black,fill=yellow",
|
||||
.default_str = "bg=themeyellow,fg=themeblack,"
|
||||
"#{?#{m/r:(^|#,)IS(PANE|MODE)($|#,),#{prompt_flags}},,"
|
||||
"fill=themeyellow}",
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.separator = ",",
|
||||
.text = "Style of messages and the command prompt. "
|
||||
@@ -954,7 +1142,7 @@ const struct options_table_entry options_table[] = {
|
||||
{ .name = "status-style",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SESSION,
|
||||
.default_str = "bg=green,fg=black",
|
||||
.default_str = "bg=themegreen,fg=themeblack",
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.separator = ",",
|
||||
.text = "Style of the status line."
|
||||
@@ -963,7 +1151,7 @@ const struct options_table_entry options_table[] = {
|
||||
{ .name = "pane-status-current-style",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
.default_str = "default",
|
||||
.default_str = "underscore",
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.separator = ",",
|
||||
.text = "Style of the current pane in the status line."
|
||||
@@ -980,12 +1168,22 @@ const struct options_table_entry options_table[] = {
|
||||
},
|
||||
|
||||
{ .name = "prompt-cursor-colour",
|
||||
.type = OPTIONS_TABLE_COLOUR,
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SESSION,
|
||||
.default_num = -1,
|
||||
.flags = OPTIONS_TABLE_IS_COLOUR,
|
||||
.default_str = "",
|
||||
.text = "Colour of the cursor when in the command prompt."
|
||||
},
|
||||
|
||||
{ .name = "prompt-command-cursor-colour",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_SESSION,
|
||||
.flags = OPTIONS_TABLE_IS_COLOUR,
|
||||
.default_str = "",
|
||||
.text = "Colour of the cursor in the command prompt when in command "
|
||||
"mode, if 'status-keys' is set to 'vi'."
|
||||
},
|
||||
|
||||
{ .name = "prompt-cursor-style",
|
||||
.type = OPTIONS_TABLE_CHOICE,
|
||||
.scope = OPTIONS_TABLE_SESSION,
|
||||
@@ -1006,7 +1204,7 @@ const struct options_table_entry options_table[] = {
|
||||
{ .name = "session-status-current-style",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
.default_str = "default",
|
||||
.default_str = "underscore",
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.separator = ",",
|
||||
.text = "Style of the current session in the status line."
|
||||
@@ -1134,9 +1332,10 @@ const struct options_table_entry options_table[] = {
|
||||
},
|
||||
|
||||
{ .name = "clock-mode-colour",
|
||||
.type = OPTIONS_TABLE_COLOUR,
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
.default_num = 4,
|
||||
.flags = OPTIONS_TABLE_IS_COLOUR,
|
||||
.default_str = "themeblue",
|
||||
.text = "Colour of the clock in clock mode."
|
||||
},
|
||||
|
||||
@@ -1151,7 +1350,7 @@ const struct options_table_entry options_table[] = {
|
||||
{ .name = "copy-mode-match-style",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
.default_str = "bg=cyan,fg=black",
|
||||
.default_str = "bg=themecyan,fg=themeblack",
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.separator = ",",
|
||||
.text = "Style of search matches in copy mode."
|
||||
@@ -1160,7 +1359,7 @@ const struct options_table_entry options_table[] = {
|
||||
{ .name = "copy-mode-current-match-style",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
.default_str = "bg=magenta,fg=black",
|
||||
.default_str = "bg=thememagenta,fg=themeblack",
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.separator = ",",
|
||||
.text = "Style of the current search match in copy mode."
|
||||
@@ -1169,7 +1368,7 @@ const struct options_table_entry options_table[] = {
|
||||
{ .name = "copy-mode-mark-style",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
.default_str = "bg=red,fg=black",
|
||||
.default_str = "bg=themeyellow,fg=themeblack",
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.separator = ",",
|
||||
.text = "Style of the marked line in copy mode."
|
||||
@@ -1208,7 +1407,7 @@ const struct options_table_entry options_table[] = {
|
||||
{ .name = "copy-mode-current-line-number-style",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
.default_str = "fg=yellow",
|
||||
.default_str = "fg=themeyellow",
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.separator = ",",
|
||||
.text = "Style of current line number in copy mode."
|
||||
@@ -1217,7 +1416,7 @@ const struct options_table_entry options_table[] = {
|
||||
{ .name = "copy-mode-line-number-style",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
.default_str = "fg=white,dim",
|
||||
.default_str = "fg=themelightgrey,dim",
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.separator = ",",
|
||||
.text = "Style of line numbers in copy mode."
|
||||
@@ -1238,6 +1437,28 @@ const struct options_table_entry options_table[] = {
|
||||
.text = "Character used to fill unused parts of window."
|
||||
},
|
||||
|
||||
{ .name = "floating-pane-border-style",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
|
||||
.default_str = "default",
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.separator = ",",
|
||||
.text = "Default border style for floating panes. "
|
||||
"Overrides pane-border-style for floating panes unless "
|
||||
"a per-pane style is set."
|
||||
},
|
||||
|
||||
{ .name = "floating-pane-style",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
|
||||
.default_str = "default",
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.separator = ",",
|
||||
.text = "Default content style for floating panes. "
|
||||
"Overrides window-style for floating panes unless "
|
||||
"a per-pane style is set."
|
||||
},
|
||||
|
||||
{ .name = "main-pane-height",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
@@ -1266,7 +1487,7 @@ const struct options_table_entry options_table[] = {
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.default_str = "noattr,bg=yellow,fg=black",
|
||||
.default_str = "noattr,bg=themeyellow,fg=themeblack",
|
||||
.separator = ",",
|
||||
.text = "Style of indicators and highlighting in modes."
|
||||
},
|
||||
@@ -1315,7 +1536,9 @@ const struct options_table_entry options_table[] = {
|
||||
{ .name = "pane-active-border-style",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
|
||||
.default_str = "#{?pane_in_mode,fg=yellow,#{?synchronize-panes,fg=red,fg=green}}",
|
||||
.default_str = "fg=#{?pane_marked,thememagenta,"
|
||||
"#{?synchronize-panes,themered,"
|
||||
"#{?pane_in_mode,themeyellow,themegreen}}}",
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.separator = ",",
|
||||
.text = "Style of the active pane border."
|
||||
@@ -1356,7 +1579,7 @@ const struct options_table_entry options_table[] = {
|
||||
|
||||
{ .name = "pane-border-lines",
|
||||
.type = OPTIONS_TABLE_CHOICE,
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
|
||||
.choices = options_table_pane_border_lines_list,
|
||||
.default_num = PANE_LINES_SINGLE,
|
||||
.text = "Type of characters used to draw pane border lines. Some of "
|
||||
@@ -1365,7 +1588,7 @@ const struct options_table_entry options_table[] = {
|
||||
|
||||
{ .name = "pane-border-status",
|
||||
.type = OPTIONS_TABLE_CHOICE,
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
|
||||
.choices = options_table_pane_status_list,
|
||||
.default_num = PANE_STATUS_OFF,
|
||||
.text = "Position of the pane status lines."
|
||||
@@ -1374,7 +1597,7 @@ const struct options_table_entry options_table[] = {
|
||||
{ .name = "pane-border-style",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
|
||||
.default_str = "default",
|
||||
.default_str = "fg=themelightgrey",
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.separator = ",",
|
||||
.text = "Style of the pane status lines."
|
||||
@@ -1393,13 +1616,23 @@ const struct options_table_entry options_table[] = {
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
.choices = options_table_pane_scrollbars_list,
|
||||
.default_num = PANE_SCROLLBARS_OFF,
|
||||
.text = "Pane scrollbar state."
|
||||
.text = "Pane scrollbar state: off, on, modal, or auto-hide."
|
||||
},
|
||||
|
||||
{ .name = "pane-scrollbars-timeout",
|
||||
.type = OPTIONS_TABLE_NUMBER,
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
.minimum = 0,
|
||||
.maximum = INT_MAX,
|
||||
.default_num = 500,
|
||||
.unit = "milliseconds",
|
||||
.text = "Time before modal and auto-hide pane scrollbars disappear."
|
||||
},
|
||||
|
||||
{ .name = "pane-scrollbars-style",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
|
||||
.default_str = "bg=black,fg=white,width=1,pad=0",
|
||||
.default_str = "bg=themedarkgrey,fg=themelightgrey,width=1,pad=0",
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.separator = ",",
|
||||
.text = "Style of the pane scrollbar."
|
||||
@@ -1416,7 +1649,7 @@ const struct options_table_entry options_table[] = {
|
||||
{ .name = "popup-style",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
.default_str = "default",
|
||||
.default_str = "bg=themedarkgrey,fg=themewhite",
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.separator = ",",
|
||||
.text = "Default style of popups."
|
||||
@@ -1425,7 +1658,7 @@ const struct options_table_entry options_table[] = {
|
||||
{ .name = "popup-border-style",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
.default_str = "default",
|
||||
.default_str = "bg=themedarkgrey,fg=themelightgrey",
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.separator = ",",
|
||||
.text = "Default style of popup borders."
|
||||
@@ -1471,6 +1704,15 @@ const struct options_table_entry options_table[] = {
|
||||
"history when clearing the whole screen."
|
||||
},
|
||||
|
||||
{ .name = "switch-mode-match-style",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
|
||||
.default_str = "bg=cyan fg=black",
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.separator = ",",
|
||||
.text = "Style of matched characters in switch mode."
|
||||
},
|
||||
|
||||
{ .name = "synchronize-panes",
|
||||
.type = OPTIONS_TABLE_FLAG,
|
||||
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
|
||||
@@ -1488,6 +1730,15 @@ const struct options_table_entry options_table[] = {
|
||||
"A value of 0 means no limit."
|
||||
},
|
||||
|
||||
{ .name = "tree-mode-border-style",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
.default_str = "bg=themedarkgrey,fg=themelightgrey",
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.separator = ",",
|
||||
.text = "Style of borders in tree mode."
|
||||
},
|
||||
|
||||
{ .name = "tree-mode-preview-format",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
|
||||
@@ -1503,13 +1754,22 @@ const struct options_table_entry options_table[] = {
|
||||
.default_str = "fg=#{?#{||:"
|
||||
"#{&&:#{pane_format},#{pane_active}},"
|
||||
"#{&&:#{window_format},#{window_active}}},"
|
||||
"#{display-panes-active-colour},"
|
||||
"#{display-panes-colour}}",
|
||||
"themered,"
|
||||
"themeblue}",
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.separator = ",",
|
||||
.text = "Style of preview indicator in tree mode."
|
||||
},
|
||||
|
||||
{ .name = "tree-mode-selection-style",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
.default_str = "#{E:mode-style}",
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.separator = ",",
|
||||
.text = "Style of the selected line in tree mode."
|
||||
},
|
||||
|
||||
{ .name = "window-active-style",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE,
|
||||
@@ -1583,7 +1843,7 @@ const struct options_table_entry options_table[] = {
|
||||
{ .name = "window-status-current-style",
|
||||
.type = OPTIONS_TABLE_STRING,
|
||||
.scope = OPTIONS_TABLE_WINDOW,
|
||||
.default_str = "default",
|
||||
.default_str = "underscore",
|
||||
.flags = OPTIONS_TABLE_IS_STYLE,
|
||||
.separator = ",",
|
||||
.text = "Style of the current window in the status line."
|
||||
|
||||
78
options.c
78
options.c
@@ -662,6 +662,18 @@ options_parse_get(struct options *oo, const char *s, int *idx, int only)
|
||||
return (o);
|
||||
}
|
||||
|
||||
const struct options_table_entry *
|
||||
options_search(const char *name)
|
||||
{
|
||||
const struct options_table_entry *oe;
|
||||
|
||||
for (oe = options_table; oe->name != NULL; oe++) {
|
||||
if (strcmp(oe->name, name) == 0)
|
||||
return (oe);
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
char *
|
||||
options_match(const char *s, int *idx, int *ambiguous)
|
||||
{
|
||||
@@ -977,9 +989,12 @@ struct style *
|
||||
options_string_to_style(struct options *oo, const char *name,
|
||||
struct format_tree *ft)
|
||||
{
|
||||
struct options_entry *o;
|
||||
const char *s;
|
||||
char *expanded;
|
||||
struct options_entry *o;
|
||||
const struct options_table_entry *oe;
|
||||
const struct grid_cell *dgc = &grid_default_cell;
|
||||
const char *s;
|
||||
char *expanded;
|
||||
int failed;
|
||||
|
||||
o = options_get(oo, name);
|
||||
if (o == NULL || !OPTIONS_IS_STRING(o))
|
||||
@@ -988,20 +1003,27 @@ options_string_to_style(struct options *oo, const char *name,
|
||||
if (o->cached)
|
||||
return (&o->style);
|
||||
s = o->value.string;
|
||||
oe = o->tableentry;
|
||||
log_debug("%s: %s is '%s'", __func__, name, s);
|
||||
|
||||
style_set(&o->style, &grid_default_cell);
|
||||
style_set(&o->style, dgc);
|
||||
o->cached = (strstr(s, "#{") == NULL);
|
||||
|
||||
if (ft != NULL && !o->cached) {
|
||||
expanded = format_expand(ft, s);
|
||||
if (style_parse(&o->style, &grid_default_cell, expanded) != 0) {
|
||||
free(expanded);
|
||||
return (NULL);
|
||||
}
|
||||
if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_COLOUR))
|
||||
failed = style_parse_colour(&o->style, dgc, expanded);
|
||||
else
|
||||
failed = style_parse(&o->style, dgc, expanded);
|
||||
free(expanded);
|
||||
if (failed != 0)
|
||||
return (NULL);
|
||||
} else {
|
||||
if (style_parse(&o->style, &grid_default_cell, s) != 0)
|
||||
if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_COLOUR))
|
||||
failed = style_parse_colour(&o->style, dgc, s);
|
||||
else
|
||||
failed = style_parse(&o->style, dgc, s);
|
||||
if (failed != 0)
|
||||
return (NULL);
|
||||
}
|
||||
return (&o->style);
|
||||
@@ -1029,6 +1051,12 @@ options_from_string_check(const struct options_table_entry *oe,
|
||||
xasprintf(cause, "invalid style: %s", value);
|
||||
return (-1);
|
||||
}
|
||||
if ((oe->flags & OPTIONS_TABLE_IS_COLOUR) &&
|
||||
strstr(value, "#{") == NULL &&
|
||||
style_parse_colour(&sy, &grid_default_cell, value) != 0) {
|
||||
xasprintf(cause, "invalid colour: %s", value);
|
||||
return (-1);
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
@@ -1186,6 +1214,17 @@ options_push_changes(const char *name)
|
||||
|
||||
log_debug("%s: %s", __func__, name);
|
||||
|
||||
if (strcmp(name, "theme") == 0 ||
|
||||
strncmp(name, "dark-theme-", 11) == 0 ||
|
||||
strncmp(name, "light-theme-", 12) == 0) {
|
||||
TAILQ_FOREACH(loop, &clients, entry) {
|
||||
server_client_update_theme_colours(loop);
|
||||
if (loop->tty.flags & TTY_OPENED)
|
||||
tty_invalidate(&loop->tty);
|
||||
server_redraw_client(loop);
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(name, "automatic-rename") == 0) {
|
||||
RB_FOREACH(w, windows, &windows) {
|
||||
if (w->active == NULL)
|
||||
@@ -1219,6 +1258,16 @@ options_push_changes(const char *name)
|
||||
if (strcmp(name, "status") == 0 ||
|
||||
strcmp(name, "status-interval") == 0)
|
||||
status_timer_start_all();
|
||||
if (strcmp(name, "status") == 0 ||
|
||||
strcmp(name, "status-position") == 0 ||
|
||||
strcmp(name, "pane-border-indicators") == 0 ||
|
||||
strcmp(name, "pane-border-lines") == 0 ||
|
||||
strcmp(name, "pane-border-status") == 0 ||
|
||||
strcmp(name, "pane-scrollbars") == 0 ||
|
||||
strcmp(name, "pane-scrollbars-timeout") == 0 ||
|
||||
strcmp(name, "pane-scrollbars-position") == 0 ||
|
||||
strcmp(name, "pane-scrollbars-style") == 0)
|
||||
redraw_invalidate_all_scenes();
|
||||
if (strcmp(name, "monitor-silence") == 0)
|
||||
alerts_reset_all();
|
||||
if (strcmp(name, "window-style") == 0 ||
|
||||
@@ -1237,8 +1286,17 @@ options_push_changes(const char *name)
|
||||
if (strcmp(name, "pane-border-status") == 0 ||
|
||||
strcmp(name, "pane-scrollbars") == 0 ||
|
||||
strcmp(name, "pane-scrollbars-position") == 0) {
|
||||
RB_FOREACH(w, windows, &windows)
|
||||
RB_FOREACH(w, windows, &windows) {
|
||||
w->sb = options_get_number(w->options,
|
||||
"pane-scrollbars");
|
||||
w->sb_pos = options_get_number(w->options,
|
||||
"pane-scrollbars-position");
|
||||
layout_fix_panes(w, NULL);
|
||||
}
|
||||
}
|
||||
if (strcmp(name, "pane-scrollbars") == 0) {
|
||||
RB_FOREACH(wp, window_pane_tree, &all_window_panes)
|
||||
window_pane_scrollbar_hide(wp);
|
||||
}
|
||||
if (strcmp(name, "pane-scrollbars-style") == 0) {
|
||||
RB_FOREACH(wp, window_pane_tree, &all_window_panes) {
|
||||
|
||||
4
paste.c
4
paste.c
@@ -219,7 +219,7 @@ paste_rename(const char *oldname, const char *newname, char **cause)
|
||||
return (-1);
|
||||
}
|
||||
|
||||
name = clean_name(newname, "");
|
||||
name = clean_name(newname, 0);
|
||||
if (name == NULL) {
|
||||
if (cause != NULL)
|
||||
xasprintf(cause, "invalid buffer name: %s", newname);
|
||||
@@ -287,7 +287,7 @@ paste_set(char *data, size_t size, const char *name, char **cause)
|
||||
return (-1);
|
||||
}
|
||||
|
||||
newname = clean_name(name, "");
|
||||
newname = clean_name(name, 0);
|
||||
if (newname == NULL) {
|
||||
if (cause != NULL)
|
||||
xasprintf(cause, "invalid buffer name: %s", name);
|
||||
|
||||
169
popup.c
169
popup.c
@@ -75,12 +75,6 @@ struct popup_data {
|
||||
u_int lb;
|
||||
};
|
||||
|
||||
struct popup_editor {
|
||||
char *path;
|
||||
popup_finish_edit_cb cb;
|
||||
void *arg;
|
||||
};
|
||||
|
||||
static const struct menu_item popup_menu_items[] = {
|
||||
{ "Close", 'q', NULL },
|
||||
{ "#{?buffer_name,Paste #[underscore]#{buffer_name},}", 'p', NULL },
|
||||
@@ -94,15 +88,6 @@ static const struct menu_item popup_menu_items[] = {
|
||||
{ NULL, KEYC_NONE, NULL }
|
||||
};
|
||||
|
||||
static const struct menu_item popup_internal_menu_items[] = {
|
||||
{ "Close", 'q', NULL },
|
||||
{ "", KEYC_NONE, NULL },
|
||||
{ "Fill Space", 'F', NULL },
|
||||
{ "Centre", 'C', NULL },
|
||||
|
||||
{ NULL, KEYC_NONE, NULL }
|
||||
};
|
||||
|
||||
static void
|
||||
popup_free(struct popup_data *pd)
|
||||
{
|
||||
@@ -208,7 +193,8 @@ popup_init_ctx_cb(struct screen_write_ctx *ctx, struct tty_ctx *ttyctx)
|
||||
|
||||
memcpy(&ttyctx->defaults, &pd->defaults, sizeof ttyctx->defaults);
|
||||
ttyctx->flags &= ~TTY_CTX_WINDOW_BIGGER;
|
||||
ttyctx->palette = &pd->palette;
|
||||
ttyctx->style_ctx.defaults = &ttyctx->defaults;
|
||||
ttyctx->style_ctx.palette = &pd->palette;
|
||||
ttyctx->redraw_cb = popup_redraw_cb;
|
||||
ttyctx->set_client_cb = popup_set_client_cb;
|
||||
ttyctx->arg = pd;
|
||||
@@ -287,15 +273,15 @@ popup_check_cb(struct client* c, void *data, u_int px, u_int py, u_int nx)
|
||||
}
|
||||
|
||||
static void
|
||||
popup_draw_cb(struct client *c, void *data, struct screen_redraw_ctx *rctx)
|
||||
popup_draw_cb(struct client *c, void *data)
|
||||
{
|
||||
struct popup_data *pd = data;
|
||||
struct tty *tty = &c->tty;
|
||||
struct screen s;
|
||||
struct screen_write_ctx ctx;
|
||||
u_int i, px = pd->px, py = pd->py;
|
||||
struct colour_palette *palette = &pd->palette;
|
||||
struct grid_cell defaults;
|
||||
struct tty_style_ctx style_ctx;
|
||||
|
||||
popup_reapply_styles(pd);
|
||||
|
||||
@@ -321,9 +307,13 @@ popup_draw_cb(struct client *c, void *data, struct screen_redraw_ctx *rctx)
|
||||
|
||||
memcpy(&defaults, &pd->defaults, sizeof defaults);
|
||||
if (defaults.fg == 8)
|
||||
defaults.fg = palette->fg;
|
||||
defaults.fg = pd->palette.fg;
|
||||
if (defaults.bg == 8)
|
||||
defaults.bg = palette->bg;
|
||||
defaults.bg = pd->palette.bg;
|
||||
style_ctx.defaults = &defaults;
|
||||
style_ctx.palette = &pd->palette;
|
||||
style_ctx.dim = 0;
|
||||
style_ctx.hyperlinks = s.hyperlinks;
|
||||
|
||||
if (pd->md != NULL) {
|
||||
c->overlay_check = menu_check_cb;
|
||||
@@ -332,15 +322,13 @@ popup_draw_cb(struct client *c, void *data, struct screen_redraw_ctx *rctx)
|
||||
c->overlay_check = NULL;
|
||||
c->overlay_data = NULL;
|
||||
}
|
||||
for (i = 0; i < pd->sy; i++) {
|
||||
tty_draw_line(tty, &s, 0, i, pd->sx, px, py + i, &defaults,
|
||||
palette);
|
||||
}
|
||||
for (i = 0; i < pd->sy; i++)
|
||||
tty_draw_line(tty, &s, 0, i, pd->sx, px, py + i, &style_ctx);
|
||||
screen_free(&s);
|
||||
if (pd->md != NULL) {
|
||||
c->overlay_check = NULL;
|
||||
c->overlay_data = NULL;
|
||||
menu_draw_cb(c, pd->md, rctx);
|
||||
menu_draw_cb(c, pd->md);
|
||||
}
|
||||
c->overlay_check = popup_check_cb;
|
||||
c->overlay_data = pd;
|
||||
@@ -647,11 +635,7 @@ popup_key_cb(struct client *c, void *data, struct key_event *event)
|
||||
|
||||
menu:
|
||||
pd->menu = menu_create("");
|
||||
if (pd->flags & POPUP_INTERNAL) {
|
||||
menu_add_items(pd->menu, popup_internal_menu_items, NULL, c,
|
||||
NULL);
|
||||
} else
|
||||
menu_add_items(pd->menu, popup_menu_items, NULL, c, NULL);
|
||||
menu_add_items(pd->menu, popup_menu_items, NULL, c, NULL);
|
||||
if (m->x >= (pd->menu->width + 4) / 2)
|
||||
x = m->x - (pd->menu->width + 4) / 2;
|
||||
else
|
||||
@@ -858,127 +842,16 @@ popup_display(int flags, enum box_lines lines, struct cmdq_item *item, u_int px,
|
||||
pd->psx = sx;
|
||||
pd->psy = sy;
|
||||
|
||||
if (flags & POPUP_NOJOB)
|
||||
pd->ictx = input_init(NULL, NULL, &pd->palette, NULL);
|
||||
else {
|
||||
pd->job = job_run(shellcmd, argc, argv, env, s, cwd,
|
||||
popup_job_update_cb, popup_job_complete_cb, NULL, pd,
|
||||
JOB_NOWAIT|JOB_PTY|JOB_KEEPWRITE|JOB_DEFAULTSHELL, jx, jy);
|
||||
if (pd->job == NULL) {
|
||||
popup_free(pd);
|
||||
return (-1);
|
||||
}
|
||||
pd->ictx = input_init(NULL, job_get_event(pd->job),
|
||||
&pd->palette, c);
|
||||
pd->job = job_run(shellcmd, argc, argv, env, s, cwd,
|
||||
popup_job_update_cb, popup_job_complete_cb, NULL, pd,
|
||||
JOB_NOWAIT|JOB_PTY|JOB_KEEPWRITE|JOB_DEFAULTSHELL, jx, jy);
|
||||
if (pd->job == NULL) {
|
||||
popup_free(pd);
|
||||
return (-1);
|
||||
}
|
||||
pd->ictx = input_init(NULL, job_get_event(pd->job), &pd->palette, c);
|
||||
|
||||
server_client_set_overlay(c, 0, popup_check_cb, popup_mode_cb,
|
||||
popup_draw_cb, popup_key_cb, popup_free_cb, popup_resize_cb, pd);
|
||||
return (0);
|
||||
}
|
||||
|
||||
void
|
||||
popup_write(struct client *c, const char *data, size_t size)
|
||||
{
|
||||
struct popup_data *pd = c->overlay_data;
|
||||
|
||||
if (!popup_present(c))
|
||||
return;
|
||||
c->overlay_check = NULL;
|
||||
c->overlay_data = NULL;
|
||||
input_parse_screen(pd->ictx, &pd->s, popup_init_ctx_cb, pd, data, size);
|
||||
c->overlay_check = popup_check_cb;
|
||||
c->overlay_data = pd;
|
||||
}
|
||||
|
||||
static void
|
||||
popup_editor_free(struct popup_editor *pe)
|
||||
{
|
||||
unlink(pe->path);
|
||||
free(pe->path);
|
||||
free(pe);
|
||||
}
|
||||
|
||||
static void
|
||||
popup_editor_close_cb(int status, void *arg)
|
||||
{
|
||||
struct popup_editor *pe = arg;
|
||||
FILE *f;
|
||||
char *buf = NULL;
|
||||
off_t len = 0;
|
||||
|
||||
if (status != 0) {
|
||||
pe->cb(NULL, 0, pe->arg);
|
||||
popup_editor_free(pe);
|
||||
return;
|
||||
}
|
||||
|
||||
f = fopen(pe->path, "r");
|
||||
if (f != NULL) {
|
||||
fseeko(f, 0, SEEK_END);
|
||||
len = ftello(f);
|
||||
fseeko(f, 0, SEEK_SET);
|
||||
|
||||
if (len == 0 ||
|
||||
(uintmax_t)len > (uintmax_t)SIZE_MAX ||
|
||||
(buf = malloc(len)) == NULL ||
|
||||
fread(buf, len, 1, f) != 1) {
|
||||
free(buf);
|
||||
buf = NULL;
|
||||
len = 0;
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
pe->cb(buf, len, pe->arg); /* callback now owns buffer */
|
||||
popup_editor_free(pe);
|
||||
}
|
||||
|
||||
int
|
||||
popup_editor(struct client *c, const char *buf, size_t len,
|
||||
popup_finish_edit_cb cb, void *arg)
|
||||
{
|
||||
struct popup_editor *pe;
|
||||
int fd;
|
||||
FILE *f;
|
||||
char *cmd;
|
||||
char path[] = _PATH_TMP "tmux.XXXXXXXX";
|
||||
const char *editor;
|
||||
u_int px, py, sx, sy;
|
||||
|
||||
editor = options_get_string(global_options, "editor");
|
||||
if (*editor == '\0')
|
||||
return (-1);
|
||||
|
||||
fd = mkstemp(path);
|
||||
if (fd == -1)
|
||||
return (-1);
|
||||
f = fdopen(fd, "w");
|
||||
if (f == NULL)
|
||||
return (-1);
|
||||
if (fwrite(buf, len, 1, f) != 1) {
|
||||
fclose(f);
|
||||
return (-1);
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
pe = xcalloc(1, sizeof *pe);
|
||||
pe->path = xstrdup(path);
|
||||
pe->cb = cb;
|
||||
pe->arg = arg;
|
||||
|
||||
sx = c->tty.sx * 9 / 10;
|
||||
sy = c->tty.sy * 9 / 10;
|
||||
px = (c->tty.sx / 2) - (sx / 2);
|
||||
py = (c->tty.sy / 2) - (sy / 2);
|
||||
|
||||
xasprintf(&cmd, "%s %s", editor, path);
|
||||
if (popup_display(POPUP_INTERNAL|POPUP_CLOSEEXIT, BOX_LINES_DEFAULT,
|
||||
NULL, px, py, sx, sy, NULL, cmd, 0, NULL, _PATH_TMP, NULL, c, NULL,
|
||||
NULL, NULL, popup_editor_close_cb, pe) != 0) {
|
||||
popup_editor_free(pe);
|
||||
free(cmd);
|
||||
return (-1);
|
||||
}
|
||||
free(cmd);
|
||||
return (0);
|
||||
}
|
||||
|
||||
12
proc.c
12
proc.c
@@ -57,6 +57,7 @@ struct tmuxpeer {
|
||||
struct imsgbuf ibuf;
|
||||
struct event event;
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
|
||||
int flags;
|
||||
#define PEER_BAD 0x1
|
||||
@@ -301,7 +302,6 @@ proc_add_peer(struct tmuxproc *tp, int fd,
|
||||
void (*dispatchcb)(struct imsg *, void *), void *arg)
|
||||
{
|
||||
struct tmuxpeer *peer;
|
||||
gid_t gid;
|
||||
|
||||
peer = xcalloc(1, sizeof *peer);
|
||||
peer->parent = tp;
|
||||
@@ -314,8 +314,10 @@ proc_add_peer(struct tmuxproc *tp, int fd,
|
||||
imsgbuf_allow_fdpass(&peer->ibuf);
|
||||
event_set(&peer->event, fd, EV_READ, proc_event_cb, peer);
|
||||
|
||||
if (getpeereid(fd, &peer->uid, &gid) != 0)
|
||||
if (getpeereid(fd, &peer->uid, &peer->gid) != 0) {
|
||||
peer->uid = (uid_t)-1;
|
||||
peer->gid = (gid_t)-1;
|
||||
}
|
||||
|
||||
log_debug("add peer %p: %d (%p)", peer, fd, arg);
|
||||
TAILQ_INSERT_TAIL(&tp->peers, peer, entry);
|
||||
@@ -384,3 +386,9 @@ proc_get_peer_uid(struct tmuxpeer *peer)
|
||||
{
|
||||
return (peer->uid);
|
||||
}
|
||||
|
||||
gid_t
|
||||
proc_get_peer_gid(struct tmuxpeer *peer)
|
||||
{
|
||||
return (peer->gid);
|
||||
}
|
||||
|
||||
264
prompt-history.c
Normal file
264
prompt-history.c
Normal file
@@ -0,0 +1,264 @@
|
||||
/* $OpenBSD$ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
||||
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "tmux.h"
|
||||
|
||||
static char *prompt_find_history_file(void);
|
||||
static void prompt_add_typed_history(char *);
|
||||
|
||||
/* Prompt history. */
|
||||
static char **prompt_hlist[PROMPT_NTYPES];
|
||||
static u_int prompt_hsize[PROMPT_NTYPES];
|
||||
|
||||
/* Find the history file to load/save from/to. */
|
||||
static char *
|
||||
prompt_find_history_file(void)
|
||||
{
|
||||
const char *home, *history_file;
|
||||
char *path;
|
||||
|
||||
history_file = options_get_string(global_options, "history-file");
|
||||
if (*history_file == '\0')
|
||||
return (NULL);
|
||||
if (*history_file == '/')
|
||||
return (xstrdup(history_file));
|
||||
|
||||
if (history_file[0] != '~' || history_file[1] != '/')
|
||||
return (NULL);
|
||||
if ((home = find_home()) == NULL)
|
||||
return (NULL);
|
||||
xasprintf(&path, "%s%s", home, history_file + 1);
|
||||
return (path);
|
||||
}
|
||||
|
||||
/* Add loaded history item to the appropriate list. */
|
||||
static void
|
||||
prompt_add_typed_history(char *line)
|
||||
{
|
||||
char *typestr;
|
||||
enum prompt_type type = PROMPT_TYPE_INVALID;
|
||||
|
||||
typestr = strsep(&line, ":");
|
||||
if (line != NULL)
|
||||
type = prompt_type(typestr);
|
||||
if (type == PROMPT_TYPE_INVALID) {
|
||||
/*
|
||||
* Invalid types are not expected, but this provides backward
|
||||
* compatibility with old history files.
|
||||
*/
|
||||
if (line != NULL)
|
||||
*(--line) = ':';
|
||||
prompt_add_history(typestr, PROMPT_TYPE_COMMAND);
|
||||
} else
|
||||
prompt_add_history(line, type);
|
||||
}
|
||||
|
||||
/* Load prompt history from file. */
|
||||
void
|
||||
prompt_load_history(void)
|
||||
{
|
||||
FILE *f;
|
||||
char *history_file, *line, *tmp;
|
||||
size_t length;
|
||||
|
||||
if ((history_file = prompt_find_history_file()) == NULL)
|
||||
return;
|
||||
log_debug("loading history from %s", history_file);
|
||||
|
||||
f = fopen(history_file, "r");
|
||||
if (f == NULL) {
|
||||
log_debug("%s: %s", history_file, strerror(errno));
|
||||
free(history_file);
|
||||
return;
|
||||
}
|
||||
free(history_file);
|
||||
|
||||
for (;;) {
|
||||
if ((line = fgetln(f, &length)) == NULL)
|
||||
break;
|
||||
|
||||
if (length > 0) {
|
||||
if (line[length - 1] == '\n') {
|
||||
line[length - 1] = '\0';
|
||||
prompt_add_typed_history(line);
|
||||
} else {
|
||||
tmp = xmalloc(length + 1);
|
||||
memcpy(tmp, line, length);
|
||||
tmp[length] = '\0';
|
||||
prompt_add_typed_history(tmp);
|
||||
free(tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
/* Save prompt history to file. */
|
||||
void
|
||||
prompt_save_history(void)
|
||||
{
|
||||
FILE *f;
|
||||
u_int i, type;
|
||||
char *history_file;
|
||||
|
||||
if ((history_file = prompt_find_history_file()) == NULL)
|
||||
return;
|
||||
log_debug("saving history to %s", history_file);
|
||||
|
||||
f = fopen(history_file, "w");
|
||||
if (f == NULL) {
|
||||
log_debug("%s: %s", history_file, strerror(errno));
|
||||
free(history_file);
|
||||
return;
|
||||
}
|
||||
free(history_file);
|
||||
|
||||
for (type = 0; type < PROMPT_NTYPES; type++) {
|
||||
for (i = 0; i < prompt_hsize[type]; i++) {
|
||||
fputs(prompt_type_string(type), f);
|
||||
fputc(':', f);
|
||||
fputs(prompt_hlist[type][i], f);
|
||||
fputc('\n', f);
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
}
|
||||
|
||||
/* Get previous line from the history. */
|
||||
const char *
|
||||
prompt_up_history(u_int *idx, u_int type)
|
||||
{
|
||||
/*
|
||||
* History runs from 0 to size - 1. Index is from 0 to size. Zero is
|
||||
* empty.
|
||||
*/
|
||||
|
||||
if (type >= PROMPT_NTYPES)
|
||||
return (NULL);
|
||||
if (prompt_hsize[type] == 0 || idx[type] == prompt_hsize[type])
|
||||
return (NULL);
|
||||
idx[type]++;
|
||||
return (prompt_hlist[type][prompt_hsize[type] - idx[type]]);
|
||||
}
|
||||
|
||||
/* Get next line from the history. */
|
||||
const char *
|
||||
prompt_down_history(u_int *idx, u_int type)
|
||||
{
|
||||
if (type >= PROMPT_NTYPES)
|
||||
return ("");
|
||||
if (prompt_hsize[type] == 0 || idx[type] == 0)
|
||||
return ("");
|
||||
idx[type]--;
|
||||
if (idx[type] == 0)
|
||||
return ("");
|
||||
return (prompt_hlist[type][prompt_hsize[type] - idx[type]]);
|
||||
}
|
||||
|
||||
/* Add line to the history. */
|
||||
void
|
||||
prompt_add_history(const char *line, u_int type)
|
||||
{
|
||||
u_int i, oldsize, newsize, freecount, hlimit, new = 1;
|
||||
size_t movesize;
|
||||
|
||||
if (type >= PROMPT_NTYPES)
|
||||
return;
|
||||
|
||||
oldsize = prompt_hsize[type];
|
||||
if (oldsize > 0 &&
|
||||
strcmp(prompt_hlist[type][oldsize - 1], line) == 0)
|
||||
new = 0;
|
||||
|
||||
hlimit = options_get_number(global_options, "prompt-history-limit");
|
||||
if (hlimit > oldsize) {
|
||||
if (new == 0)
|
||||
return;
|
||||
newsize = oldsize + new;
|
||||
} else {
|
||||
newsize = hlimit;
|
||||
freecount = oldsize + new - newsize;
|
||||
if (freecount > oldsize)
|
||||
freecount = oldsize;
|
||||
if (freecount == 0)
|
||||
return;
|
||||
for (i = 0; i < freecount; i++)
|
||||
free(prompt_hlist[type][i]);
|
||||
movesize = (oldsize - freecount) *
|
||||
sizeof *prompt_hlist[type];
|
||||
if (movesize > 0) {
|
||||
memmove(&prompt_hlist[type][0],
|
||||
&prompt_hlist[type][freecount], movesize);
|
||||
}
|
||||
}
|
||||
|
||||
if (newsize == 0) {
|
||||
free(prompt_hlist[type]);
|
||||
prompt_hlist[type] = NULL;
|
||||
} else if (newsize != oldsize) {
|
||||
prompt_hlist[type] =
|
||||
xreallocarray(prompt_hlist[type], newsize,
|
||||
sizeof *prompt_hlist[type]);
|
||||
}
|
||||
|
||||
if (new == 1 && newsize > 0)
|
||||
prompt_hlist[type][newsize - 1] = xstrdup(line);
|
||||
prompt_hsize[type] = newsize;
|
||||
}
|
||||
|
||||
/* Get history size. */
|
||||
u_int
|
||||
prompt_history_size(enum prompt_type type)
|
||||
{
|
||||
if (type >= PROMPT_NTYPES)
|
||||
return (0);
|
||||
return (prompt_hsize[type]);
|
||||
}
|
||||
|
||||
/* Get history entry. */
|
||||
const char *
|
||||
prompt_history_get(enum prompt_type type, u_int idx)
|
||||
{
|
||||
if (type >= PROMPT_NTYPES)
|
||||
return (NULL);
|
||||
if (idx >= prompt_hsize[type])
|
||||
return (NULL);
|
||||
return (prompt_hlist[type][idx]);
|
||||
}
|
||||
|
||||
/* Clear prompt history. */
|
||||
void
|
||||
prompt_history_clear(enum prompt_type type)
|
||||
{
|
||||
u_int idx;
|
||||
|
||||
if (type >= PROMPT_NTYPES)
|
||||
return;
|
||||
for (idx = 0; idx < prompt_hsize[type]; idx++)
|
||||
free(prompt_hlist[type][idx]);
|
||||
free(prompt_hlist[type]);
|
||||
prompt_hlist[type] = NULL;
|
||||
prompt_hsize[type] = 0;
|
||||
}
|
||||
@@ -3,8 +3,31 @@ TESTS!= echo *.sh
|
||||
.PHONY: all $(TESTS)
|
||||
.NOTPARALLEL: all $(TESTS)
|
||||
|
||||
all: $(TESTS)
|
||||
all:
|
||||
@failed=0; failures=; \
|
||||
for test in $(TESTS); do \
|
||||
printf '%-40s ' "$$test"; \
|
||||
start=$$(date +%s); \
|
||||
if sh "$$test" >/dev/null 2>&1; then \
|
||||
end=$$(date +%s); \
|
||||
echo "PASS ($$((end - start))s)"; \
|
||||
else \
|
||||
end=$$(date +%s); \
|
||||
echo "FAIL ($$((end - start))s)"; \
|
||||
failed=1; \
|
||||
failures="$$failures $$test"; \
|
||||
fi; \
|
||||
sleep 1; \
|
||||
done; \
|
||||
if [ "$$failed" -ne 0 ]; then \
|
||||
echo; \
|
||||
echo "failures:"; \
|
||||
for test in $$failures; do \
|
||||
echo " $$test"; \
|
||||
done; \
|
||||
fi; \
|
||||
exit $$failed
|
||||
|
||||
$(TESTS):
|
||||
sh $*.sh
|
||||
sh $@
|
||||
sleep 1
|
||||
|
||||
Binary file not shown.
137
regress/check-names.sh
Normal file
137
regress/check-names.sh
Normal file
@@ -0,0 +1,137 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Name validation policy.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 1
|
||||
}
|
||||
|
||||
must_fail()
|
||||
{
|
||||
"$@" >/dev/null 2>&1 && fail "unexpected success: $*"
|
||||
return 0
|
||||
}
|
||||
|
||||
must_equal()
|
||||
{
|
||||
got=$1
|
||||
want=$2
|
||||
|
||||
[ "$got" = "$want" ] || fail "got '$got', expected '$want'"
|
||||
}
|
||||
|
||||
$TMUX new-session -d -x 80 -y 24 || exit 1
|
||||
$TMUX set-option -qg allow-set-title on || exit 1
|
||||
$TMUX set-option -qg allow-rename on || exit 1
|
||||
$TMUX set-option -qg automatic-rename off || exit 1
|
||||
|
||||
# Commands allow empty names, ':', '.', '#' and '#('.
|
||||
$TMUX rename-session '' || fail "empty session name rejected"
|
||||
must_equal "$($TMUX display-message -p '#{session_name}')" ''
|
||||
$TMUX rename-session 'session:.##(ok)' || \
|
||||
fail "session name with : . or #( rejected"
|
||||
must_equal "$($TMUX display-message -p '#{session_name}')" 'session:.#(ok)'
|
||||
|
||||
$TMUX rename-window '' || fail "empty window name rejected"
|
||||
must_equal "$($TMUX display-message -p '#{window_name}')" ''
|
||||
$TMUX rename-window 'window:.##(ok)' || \
|
||||
fail "window name with : . or #( rejected"
|
||||
must_equal "$($TMUX display-message -p '#{window_name}')" 'window:.#(ok)'
|
||||
|
||||
$TMUX set-option -q @name 'format:.#(ok)' || exit 1
|
||||
$TMUX rename-session '#{@name}' || fail "format in session name not expanded"
|
||||
must_equal "$($TMUX display-message -p '#{session_name}')" 'format:.#(ok)'
|
||||
$TMUX rename-window '#{@name}' || fail "format in window name not expanded"
|
||||
must_equal "$($TMUX display-message -p '#{window_name}')" 'format:.#(ok)'
|
||||
$TMUX set-option -q @name 'format:.#(ok)' || exit 1
|
||||
pid=$($TMUX display-message -p '#{pid}')
|
||||
|
||||
created=$($TMUX new-session -dP -F '#{session_id}:#{window_id}' \
|
||||
-s 'new-session:.##(ok)' -n 'new-window:.##(ok)') || \
|
||||
fail "new-session name with : . or #( rejected"
|
||||
created_session=${created%:*}
|
||||
created_window=${created#*:}
|
||||
must_equal "$($TMUX display-message -pt "$created_session" '#{session_name}')" \
|
||||
'new-session:.#(ok)'
|
||||
must_equal "$($TMUX display-message -pt "$created_window" '#{window_name}')" \
|
||||
'new-window:.#(ok)'
|
||||
$TMUX kill-session -t "$created_session"
|
||||
|
||||
created_window=$($TMUX new-window -dP -F '#{window_id}' \
|
||||
-n 'created-window:.##(ok)') || \
|
||||
fail "new-window name with : . or #( rejected"
|
||||
must_equal "$($TMUX display-message -pt "$created_window" '#{window_name}')" \
|
||||
'created-window:.#(ok)'
|
||||
|
||||
created=$($TMUX new-session -dP -F '#{session_id}:#{window_id}' \
|
||||
-s 'new-session-#{pid}:.##(ok)' -n 'new-window-#{pid}:.##(ok)') || \
|
||||
fail "format in new-session name not expanded"
|
||||
created_session=${created%:*}
|
||||
created_window=${created#*:}
|
||||
must_equal "$($TMUX display-message -pt "$created_session" '#{session_name}')" \
|
||||
"new-session-$pid:.#(ok)"
|
||||
must_equal "$($TMUX display-message -pt "$created_window" '#{window_name}')" \
|
||||
"new-window-$pid:.#(ok)"
|
||||
$TMUX kill-session -t "$created_session"
|
||||
|
||||
created_window=$($TMUX new-window -dP -F '#{window_id}' -n '#{@name}') || \
|
||||
fail "format in new-window name not expanded"
|
||||
must_equal "$($TMUX display-message -pt "$created_window" '#{window_name}')" \
|
||||
'format:.#(ok)'
|
||||
|
||||
# Invalid UTF-8 is never allowed for command names.
|
||||
invalid=$(printf '\302')
|
||||
must_fail $TMUX rename-session "bad${invalid}name"
|
||||
must_fail $TMUX rename-window "bad${invalid}name"
|
||||
must_fail $TMUX new-session -d -s "bad${invalid}name"
|
||||
must_fail $TMUX new-session -d -n "bad${invalid}name"
|
||||
must_fail $TMUX new-window -d -n "bad${invalid}name"
|
||||
must_fail $TMUX set-buffer -b "bad${invalid}name" data
|
||||
|
||||
# Titles set by commands allow '#', ':' and '.'.
|
||||
$TMUX select-pane -T 'title#:.ok' || fail "command title rejected"
|
||||
must_equal "$($TMUX display-message -p '#{pane_title}')" 'title#:.ok'
|
||||
$TMUX send-keys "printf '\\033]2;title#[fg=red]ok\\007'" Enter || exit 1
|
||||
sleep 1
|
||||
must_equal "$($TMUX display-message -p '#{pane_title}')" 'title#[fg=red]ok'
|
||||
$TMUX send-keys "printf '\\033]2;title#(bad)\\007'" Enter || exit 1
|
||||
sleep 1
|
||||
must_equal "$($TMUX display-message -p '#{pane_title}')" 'title_(bad)'
|
||||
|
||||
# Buffer names allow '#', ':' and '.'.
|
||||
$TMUX set-buffer -b 'buffer#:.ok' data || fail "buffer name rejected"
|
||||
must_equal "$($TMUX list-buffers -F '#{buffer_name}')" 'buffer#:.ok'
|
||||
|
||||
# Window names from escape sequences allow '#' except in '#('.
|
||||
$TMUX send-keys "printf '\\033kescape#:.ok\\033\\\\'" Enter || exit 1
|
||||
sleep 1
|
||||
must_equal "$($TMUX display-message -p '#{window_name}')" 'escape#:.ok'
|
||||
|
||||
# Titles from escape sequences allow '#' except in '#('.
|
||||
$TMUX send-keys "printf '\\033]2;escape#:.ok\\007'" Enter || exit 1
|
||||
sleep 1
|
||||
must_equal "$($TMUX display-message -p '#{pane_title}')" 'escape#:.ok'
|
||||
|
||||
# Invalid UTF-8 from escape sequences is ignored.
|
||||
$TMUX rename-window 'before-invalid' || exit 1
|
||||
$TMUX send-keys "printf '\\033kbad\\302name\\033\\\\'" Enter || exit 1
|
||||
sleep 1
|
||||
must_equal "$($TMUX display-message -p '#{window_name}')" 'before-invalid'
|
||||
|
||||
$TMUX select-pane -T 'before-invalid-title' || exit 1
|
||||
$TMUX send-keys "printf '\\033]2;bad\\302title\\007'" Enter || exit 1
|
||||
sleep 1
|
||||
must_equal "$($TMUX display-message -p '#{pane_title}')" 'before-invalid-title'
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
20
regress/command-alias.sh
Normal file
20
regress/command-alias.sh
Normal file
@@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
|
||||
# command-alias expansion
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
$TMUX new-session -d -sfoo || exit 1
|
||||
$TMUX split-window -d -tfoo:0.0 || exit 1
|
||||
$TMUX set -s command-alias[100] zoom='resize-pane -Z' || exit 1
|
||||
$TMUX zoom -tfoo:0.0 || exit 1
|
||||
[ "$($TMUX display-message -p -tfoo:0.0 '#{window_zoomed_flag}')" = 1 ] || exit 1
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
exit 0
|
||||
126
regress/environ-update.sh
Normal file
126
regress/environ-update.sh
Normal file
@@ -0,0 +1,126 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of update-environment handling (environ_update() in environ.c), which
|
||||
# runs when a client attaches to a session: for each pattern in the session's
|
||||
# update-environment option, a matching variable in the attaching client's
|
||||
# environment is copied into the session environment, and a pattern that
|
||||
# matches nothing clears that name in the session (a NULL-valued entry).
|
||||
#
|
||||
# This needs a real attached client with a controllable environment, so - as in
|
||||
# format-variables.sh - a second server provides one: an inner "tmux attach"
|
||||
# runs inside a pane of the second server, and the variables to import are set
|
||||
# in that inner command's own environment.
|
||||
#
|
||||
# environ.sh covers the set-environment/show-environment commands themselves.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
# A second server on its own socket hosts the pane that runs the inner client.
|
||||
TMUX2="$TEST_TMUX -Ltest2 -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$1"
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
# check_value $var $expected
|
||||
#
|
||||
# Compare show-environment of $var on the session with $expected.
|
||||
check_value()
|
||||
{
|
||||
out=$($TMUX show-environment -t main "$1" 2>&1)
|
||||
if [ "$out" != "$2" ]; then
|
||||
echo "show-environment $1 failed."
|
||||
echo "Expected: '$2'"
|
||||
echo "But got: '$out'"
|
||||
cleanup
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# wait_clients $n
|
||||
#
|
||||
# Wait (up to ~10s) until the test server has exactly $n clients attached.
|
||||
wait_clients()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 10 ]; do
|
||||
c=$($TMUX list-clients -F x 2>/dev/null | grep -c x)
|
||||
[ "$c" -eq "$1" ] && return 0
|
||||
sleep 1
|
||||
i=$((i + 1))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
|
||||
$TMUX new-session -d -s main -x 80 -y 24 || exit 1
|
||||
|
||||
# The session imports MYVAR and ABSENTVAR by exact name and anything matching
|
||||
# the glob TEST_*; nothing else is imported.
|
||||
$TMUX set -g update-environment "MYVAR ABSENTVAR TEST_*" || exit 1
|
||||
|
||||
# Seed the session so the effect of attaching is visible: MYVAR will be
|
||||
# overwritten by the client's value and ABSENTVAR will be cleared.
|
||||
$TMUX set-environment -t main MYVAR oldvalue || exit 1
|
||||
$TMUX set-environment -t main ABSENTVAR pre-existing || exit 1
|
||||
|
||||
# --- attach a client whose environment carries the imported variables ------
|
||||
#
|
||||
# MYVAR and TEST_GLOB are present in the inner client's environment; ABSENTVAR
|
||||
# is deliberately absent; OTHER is present but not named by update-environment.
|
||||
$TMUX2 new-session -d -x 90 -y 30 \
|
||||
"MYVAR=fromclient TEST_GLOB=globval OTHER=nope $TMUX attach -t main" \
|
||||
|| fail "could not start inner client"
|
||||
wait_clients 1 || fail "no client attached to test server"
|
||||
|
||||
# MYVAR matched by name and present in the client -> imported (overwrites).
|
||||
check_value MYVAR "MYVAR=fromclient"
|
||||
# TEST_GLOB matched by the TEST_* glob and present -> imported.
|
||||
check_value TEST_GLOB "TEST_GLOB=globval"
|
||||
# ABSENTVAR named but not in the client environment -> cleared (NULL value,
|
||||
# printed as -NAME).
|
||||
check_value ABSENTVAR "-ABSENTVAR"
|
||||
# OTHER is in the client environment but not named by update-environment, so it
|
||||
# is not imported at all.
|
||||
out=$($TMUX show-environment -t main OTHER 2>&1)
|
||||
[ "$out" = "unknown variable: OTHER" ] || \
|
||||
fail "OTHER was imported but should not have been: '$out'"
|
||||
|
||||
# --- -E disables the update-environment import -----------------------------
|
||||
#
|
||||
# Detach the client (kill its host server), reset the session variables, then
|
||||
# reattach with -E: the session values must be left untouched.
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
wait_clients 0 || fail "client did not detach"
|
||||
|
||||
$TMUX set-environment -t main MYVAR oldvalue2 || exit 1
|
||||
$TMUX set-environment -t main ABSENTVAR pre2 || exit 1
|
||||
|
||||
$TMUX2 new-session -d -x 90 -y 30 \
|
||||
"MYVAR=fromclientE $TMUX attach -E -t main" \
|
||||
|| fail "could not start inner -E client"
|
||||
wait_clients 1 || fail "no -E client attached to test server"
|
||||
|
||||
# With -E neither variable is touched by the attach.
|
||||
check_value MYVAR "MYVAR=oldvalue2"
|
||||
check_value ABSENTVAR "ABSENTVAR=pre2"
|
||||
|
||||
if [ "$($TMUX display-message -p alive)" != "alive" ]; then
|
||||
fail "server died after update-environment tests"
|
||||
fi
|
||||
|
||||
cleanup
|
||||
exit 0
|
||||
187
regress/environ.sh
Normal file
187
regress/environ.sh
Normal file
@@ -0,0 +1,187 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of the environment engine (environ.c) and its two commands,
|
||||
# set-environment/setenv (cmd-set-environment.c) and show-environment/showenv
|
||||
# (cmd-show-environment.c).
|
||||
#
|
||||
# The environment is a red-black tree of name/value entries held at two
|
||||
# scopes: the global environment and each session's environment. An entry
|
||||
# may be marked hidden (ENVIRON_HIDDEN) or "cleared" (a NULL value, which
|
||||
# masks an inherited variable rather than removing the entry). This
|
||||
# exercises: set and show at global and session scope; the shell (-s) output
|
||||
# form and its escaping of $ ` " and \; hidden variables (-h) and their
|
||||
# filtering; -r cleared entries printed as -NAME / "unset NAME;"; -u removal;
|
||||
# -F expansion of the value at set time; the plain "NAME=value" and "%hidden"
|
||||
# config-file assignment forms (environ_put); and the full set of argument and
|
||||
# target errors from both commands.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
# check_value $args $expected
|
||||
#
|
||||
# Run show-environment with $args and compare the single-line output.
|
||||
check_value()
|
||||
{
|
||||
out=$($TMUX show-environment $1 2>&1)
|
||||
if [ "$out" != "$2" ]; then
|
||||
echo "show-environment $1 failed."
|
||||
echo "Expected: '$2'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_ok()
|
||||
{
|
||||
if ! $TMUX "$@"; then
|
||||
echo "Command failed (expected success): $*"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# check_fail $expected_error $cmd...
|
||||
check_fail()
|
||||
{
|
||||
exp="$1"
|
||||
shift
|
||||
out=$($TMUX "$@" 2>&1)
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Command succeeded (expected failure): $*"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$out" != "$exp" ]; then
|
||||
echo "Wrong error for: $*"
|
||||
echo "Expected: '$exp'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_alive()
|
||||
{
|
||||
if [ "$($TMUX display-message -p alive)" != "alive" ]; then
|
||||
echo "Server died: $1"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
$TMUX new-session -d -s main -x 80 -y 24 || exit 1
|
||||
|
||||
# --- set and show at session scope ----------------------------------------
|
||||
check_ok set-environment FOO bar
|
||||
check_value "FOO" "FOO=bar"
|
||||
# setenv is an alias for set-environment; showenv for show-environment.
|
||||
check_ok setenv FOO2 bar2
|
||||
out=$($TMUX showenv FOO2 2>&1)
|
||||
[ "$out" = "FOO2=bar2" ] || { echo "setenv/showenv alias failed: '$out'"; exit 1; }
|
||||
|
||||
# --- set and show at global scope -----------------------------------------
|
||||
#
|
||||
# The global scope is separate from the session scope: a session variable is
|
||||
# not visible in the global environment.
|
||||
check_ok set-environment -g GVAR gval
|
||||
check_value "-g GVAR" "GVAR=gval"
|
||||
check_fail "unknown variable: FOO" show-environment -g FOO
|
||||
|
||||
# --- overwrite replaces the value -----------------------------------------
|
||||
check_ok set-environment FOO baz
|
||||
check_value "FOO" "FOO=baz"
|
||||
|
||||
# --- shell (-s) output form and escaping ----------------------------------
|
||||
#
|
||||
# With -s the value is printed as a shell assignment with export, and the
|
||||
# characters $ ` " and \ are backslash-escaped (POSIX double-quote rules).
|
||||
check_ok set-environment ESC 'a$b`c"d\e'
|
||||
check_value "-s ESC" 'ESC="a\$b\`c\"d\\e"; export ESC;'
|
||||
|
||||
# --- -F expands the value as a format at set time -------------------------
|
||||
#
|
||||
# With a resolvable target the value is expanded once when set; the stored
|
||||
# value is the result, not the format.
|
||||
check_ok set-environment -t main -F EXP '#{session_name}'
|
||||
check_value "EXP" "EXP=main"
|
||||
|
||||
# --- hidden variables (-h) ------------------------------------------------
|
||||
#
|
||||
# set-environment -h marks a variable hidden. show-environment hides it by
|
||||
# default and only prints it when -h is given; conversely a normal variable is
|
||||
# omitted when -h is given.
|
||||
check_ok set-environment -h SECRET s3cr
|
||||
check_value "SECRET" ""
|
||||
check_value "-h SECRET" "SECRET=s3cr"
|
||||
check_value "-h FOO" ""
|
||||
|
||||
# --- -r clears a variable (NULL value, masks inheritance) -----------------
|
||||
#
|
||||
# A cleared entry still exists but has no value: normal form prints "-NAME"
|
||||
# and shell form prints "unset NAME;".
|
||||
check_ok set-environment -r FOO
|
||||
check_value "FOO" "-FOO"
|
||||
check_value "-s FOO" "unset FOO;"
|
||||
|
||||
# --- -u removes a variable entirely ---------------------------------------
|
||||
check_ok set-environment -u FOO
|
||||
check_fail "unknown variable: FOO" show-environment FOO
|
||||
|
||||
# --- show with no name lists every (non-hidden) variable ------------------
|
||||
check_ok set-environment -g LISTA 1
|
||||
check_ok set-environment -g LISTB 2
|
||||
check_ok set-environment -gh LISTHID 3
|
||||
out=$($TMUX show-environment -g 2>&1)
|
||||
echo "$out" | grep -q '^LISTA=1$' || { echo "list missing LISTA"; exit 1; }
|
||||
echo "$out" | grep -q '^LISTB=2$' || { echo "list missing LISTB"; exit 1; }
|
||||
# A hidden variable is not listed without -h.
|
||||
echo "$out" | grep -q '^LISTHID' && { echo "list showed hidden var without -h"; exit 1; }
|
||||
# With -h only hidden variables are listed.
|
||||
$TMUX show-environment -gh 2>&1 | grep -q '^LISTHID=3$' || \
|
||||
{ echo "list -h missing LISTHID"; exit 1; }
|
||||
|
||||
# --- config-file assignment forms (environ_put) ---------------------------
|
||||
#
|
||||
# A bare NAME=value line in a config file sets a global variable; a "%hidden"
|
||||
# NAME=value line sets a hidden one. Start a second server from such a config
|
||||
# and read the values back.
|
||||
CONF=$(mktemp)
|
||||
cat > "$CONF" <<EOF
|
||||
CFGVAR=fromconfig
|
||||
%hidden CFGHID=hiddencfg
|
||||
EOF
|
||||
CTMUX="$TEST_TMUX -Ltest2 -f$CONF"
|
||||
$CTMUX kill-server 2>/dev/null
|
||||
$CTMUX new-session -d -s c -x 80 -y 24 || { rm -f "$CONF"; exit 1; }
|
||||
out=$($CTMUX show-environment -g CFGVAR 2>&1)
|
||||
[ "$out" = "CFGVAR=fromconfig" ] || \
|
||||
{ echo "config assignment failed: '$out'"; $CTMUX kill-server; rm -f "$CONF"; exit 1; }
|
||||
out=$($CTMUX show-environment -gh CFGHID 2>&1)
|
||||
[ "$out" = "CFGHID=hiddencfg" ] || \
|
||||
{ echo "config %hidden failed: '$out'"; $CTMUX kill-server; rm -f "$CONF"; exit 1; }
|
||||
# The %hidden variable is hidden from a plain show.
|
||||
out=$($CTMUX show-environment -g CFGHID 2>&1)
|
||||
[ "$out" = "" ] || \
|
||||
{ echo "config %hidden not hidden: '$out'"; $CTMUX kill-server; rm -f "$CONF"; exit 1; }
|
||||
$CTMUX kill-server 2>/dev/null
|
||||
rm -f "$CONF"
|
||||
|
||||
# --- set-environment argument errors --------------------------------------
|
||||
check_fail "empty variable name" set-environment "" x
|
||||
check_fail "variable name contains =" set-environment "A=B" x
|
||||
check_fail "can't specify a value with -u" set-environment -u FOO val
|
||||
check_fail "can't specify a value with -r" set-environment -r FOO val
|
||||
check_fail "no value specified" set-environment NOVAL
|
||||
|
||||
# --- show-environment errors ----------------------------------------------
|
||||
check_fail "unknown variable: MISSING" show-environment MISSING
|
||||
|
||||
# --- unresolvable target errors -------------------------------------------
|
||||
check_fail "no such session: nosuch" show-environment -t nosuch FOO
|
||||
check_fail "no such session: nosuch" set-environment -t nosuch FOO bar
|
||||
|
||||
assert_alive "after environ tests"
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
203
regress/floating-pane-geometry.sh
Normal file
203
regress/floating-pane-geometry.sh
Normal file
@@ -0,0 +1,203 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Test new-pane, resize-pane, and move-pane -x/-y/-X/-Y with floating panes.
|
||||
# Verifies that size and position are correctly applied with the default border,
|
||||
# with -B none, and with the pane-border-lines window option set to none. Also
|
||||
# verifies that zero sizes are rejected.
|
||||
#
|
||||
# With a border, -x/-y/-X/-Y specify the onscreen footprint including the
|
||||
# border: new-pane subtracts 2 from width/height and adds 1 to x/y-position
|
||||
# so that the border lands on the specified screen coordinates. resize-pane
|
||||
# -x/-y similarly subtracts 2 from the requested size. move-pane -X/-Y adds 1
|
||||
# to the position. Without a border all values are used directly.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 1
|
||||
}
|
||||
|
||||
must_fail()
|
||||
{
|
||||
"$@" >/dev/null 2>&1 && fail "unexpected success: $*"
|
||||
return 0
|
||||
}
|
||||
|
||||
must_equal()
|
||||
{
|
||||
got=$1
|
||||
want=$2
|
||||
[ "$got" = "$want" ] || fail "got '$got', expected '$want'"
|
||||
}
|
||||
|
||||
$TMUX new-session -d -x 80 -y 24 || exit 1
|
||||
|
||||
# --- Default border (single-line) ---
|
||||
|
||||
# Explicit values: border subtracts 2 from size and adds 1 to position.
|
||||
# -x 20 -> pane_width 18; -y 6 -> pane_height 4;
|
||||
# -X 8 -> pane_left 9; -Y 3 -> pane_top 4.
|
||||
id=$($TMUX new-pane -dPF '#{pane_id}' -x 20 -y 6 -X 8 -Y 3 'sleep 100') \
|
||||
|| fail "new-pane with explicit geometry failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 18
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 4
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 9
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 4
|
||||
|
||||
# resize-pane with border: requested size decremented by 2.
|
||||
$TMUX resize-pane -t "$id" -x 30 || fail "resize-pane -x 30 failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 28
|
||||
$TMUX resize-pane -t "$id" -y 10 || fail "resize-pane -y 10 failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 8
|
||||
|
||||
# resize-pane with percentage and border.
|
||||
# 75% of 80 = 60, 60 - 2 = 58; 75% of 24 = 18, 18 - 2 = 16.
|
||||
$TMUX resize-pane -t "$id" -x 75% || fail "resize-pane -x 75% failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 58
|
||||
$TMUX resize-pane -t "$id" -y 75% || fail "resize-pane -y 75% failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 16
|
||||
$TMUX kill-pane -t "$id" || exit 1
|
||||
|
||||
# Percentage width, height and position with border.
|
||||
# 50% of 80 = 40, 40-2 = 38; 50% of 24 = 12, 12-2 = 10.
|
||||
# 10% of 80 = 8, 8+1 = 9; 10% of 24 = 2, 2+1 = 3.
|
||||
id=$($TMUX new-pane -dPF '#{pane_id}' -x 50% -y 50% -X 10% -Y 10% 'sleep 100') \
|
||||
|| fail "new-pane with percentage geometry failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 38
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 10
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 9
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 3
|
||||
|
||||
# move-pane -X/-Y with border: border lands at the given column/row, content
|
||||
# is one cell inside. -X 5 -> pane_left 6; -Y 1 -> pane_top 2.
|
||||
$TMUX move-pane -t "$id" -X 5 || fail "move-pane -X 5 failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 6
|
||||
$TMUX move-pane -t "$id" -Y 1 || fail "move-pane -Y 1 failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 2
|
||||
# 25% of 80 = 20, 20+1 = 21; 25% of 24 = 6, 6+1 = 7.
|
||||
$TMUX move-pane -t "$id" -X 25% || fail "move-pane -X 25% failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 21
|
||||
$TMUX move-pane -t "$id" -Y 25% || fail "move-pane -Y 25% failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 7
|
||||
$TMUX kill-pane -t "$id" || exit 1
|
||||
|
||||
# --- -B none ---
|
||||
|
||||
# Without a border all values are used directly.
|
||||
id=$($TMUX new-pane -dPF '#{pane_id}' -B none -x 20 -y 6 -X 8 -Y 3 'sleep 100') \
|
||||
|| fail "new-pane -B none with explicit geometry failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 20
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 6
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 3
|
||||
|
||||
# resize-pane without border: size used directly.
|
||||
$TMUX resize-pane -t "$id" -x 30 || fail "resize-pane -x 30 failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 30
|
||||
$TMUX resize-pane -t "$id" -y 10 || fail "resize-pane -y 10 failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 10
|
||||
|
||||
# resize-pane with percentage, no border: 75% of 80 = 60; 75% of 24 = 18.
|
||||
$TMUX resize-pane -t "$id" -x 75% || fail "resize-pane -x 75% failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 60
|
||||
$TMUX resize-pane -t "$id" -y 75% || fail "resize-pane -y 75% failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 18
|
||||
$TMUX kill-pane -t "$id" || exit 1
|
||||
|
||||
# Percentage geometry without border: 50% of 80 = 40; 10% of 80 = 8 etc.
|
||||
id=$($TMUX new-pane -dPF '#{pane_id}' -B none -x 50% -y 50% -X 10% -Y 10% 'sleep 100') \
|
||||
|| fail "new-pane -B none with percentage geometry failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 40
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 12
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 2
|
||||
|
||||
# move-pane without border: -X/-Y set the content position directly.
|
||||
# -X 5 -> pane_left 5; -Y 1 -> pane_top 1.
|
||||
$TMUX move-pane -t "$id" -X 5 || fail "move-pane -X 5 failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 5
|
||||
$TMUX move-pane -t "$id" -Y 1 || fail "move-pane -Y 1 failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 1
|
||||
# 25% of 80 = 20; 25% of 24 = 6.
|
||||
$TMUX move-pane -t "$id" -X 25% || fail "move-pane -X 25% failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 20
|
||||
$TMUX move-pane -t "$id" -Y 25% || fail "move-pane -Y 25% failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 6
|
||||
$TMUX kill-pane -t "$id" || exit 1
|
||||
|
||||
# --- Window option pane-border-lines none ---
|
||||
|
||||
$TMUX set-option -w pane-border-lines none || exit 1
|
||||
|
||||
# Inherits none from window option: same behaviour as -B none.
|
||||
id=$($TMUX new-pane -dPF '#{pane_id}' -x 20 -y 6 -X 8 -Y 3 'sleep 100') \
|
||||
|| fail "new-pane with window pane-border-lines none failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 20
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 6
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 3
|
||||
|
||||
$TMUX resize-pane -t "$id" -x 30 || fail "resize-pane -x 30 failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 30
|
||||
$TMUX resize-pane -t "$id" -y 10 || fail "resize-pane -y 10 failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 10
|
||||
|
||||
$TMUX resize-pane -t "$id" -x 75% || fail "resize-pane -x 75% failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 60
|
||||
$TMUX resize-pane -t "$id" -y 75% || fail "resize-pane -y 75% failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 18
|
||||
$TMUX kill-pane -t "$id" || exit 1
|
||||
|
||||
id=$($TMUX new-pane -dPF '#{pane_id}' -x 50% -y 50% -X 10% -Y 10% 'sleep 100') \
|
||||
|| fail "new-pane with window pane-border-lines none and percentages failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 40
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 12
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 2
|
||||
|
||||
# move-pane with window pane-border-lines none: same behaviour as -B none.
|
||||
$TMUX move-pane -t "$id" -X 5 || fail "move-pane -X 5 failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 5
|
||||
$TMUX move-pane -t "$id" -Y 1 || fail "move-pane -Y 1 failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 1
|
||||
$TMUX move-pane -t "$id" -X 25% || fail "move-pane -X 25% failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 20
|
||||
$TMUX move-pane -t "$id" -Y 25% || fail "move-pane -Y 25% failed"
|
||||
must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 6
|
||||
$TMUX kill-pane -t "$id" || exit 1
|
||||
|
||||
# --- Invalid sizes ---
|
||||
|
||||
# Zero width and height must be rejected by new-pane with the default border.
|
||||
$TMUX set-option -w -u pane-border-lines || exit 1
|
||||
must_fail $TMUX new-pane -d -x 0 -y 6 -X 8 -Y 3 'sleep 100'
|
||||
must_fail $TMUX new-pane -d -x 20 -y 0 -X 8 -Y 3 'sleep 100'
|
||||
|
||||
# Zero width and height must be rejected by resize-pane.
|
||||
id=$($TMUX new-pane -dPF '#{pane_id}' -x 20 -y 6 -X 8 -Y 3 'sleep 100') \
|
||||
|| fail "new-pane for resize-pane invalid size tests failed"
|
||||
must_fail $TMUX resize-pane -t "$id" -x 0
|
||||
must_fail $TMUX resize-pane -t "$id" -y 0
|
||||
$TMUX kill-pane -t "$id" || exit 1
|
||||
|
||||
# Same rejections apply with no border.
|
||||
$TMUX set-option -w pane-border-lines none || exit 1
|
||||
must_fail $TMUX new-pane -d -x 0 -y 6 -X 8 -Y 3 'sleep 100'
|
||||
must_fail $TMUX new-pane -d -x 20 -y 0 -X 8 -Y 3 'sleep 100'
|
||||
|
||||
id=$($TMUX new-pane -dPF '#{pane_id}' -x 20 -y 6 -X 8 -Y 3 'sleep 100') \
|
||||
|| fail "new-pane for resize-pane invalid size tests (no border) failed"
|
||||
must_fail $TMUX resize-pane -t "$id" -x 0
|
||||
must_fail $TMUX resize-pane -t "$id" -y 0
|
||||
$TMUX kill-pane -t "$id" || exit 1
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
106
regress/format-fuzzy.sh
Normal file
106
regress/format-fuzzy.sh
Normal file
@@ -0,0 +1,106 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of fuzzy format matching.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
|
||||
# test_format $format $expected_result
|
||||
test_format()
|
||||
{
|
||||
fmt="$1"
|
||||
exp="$2"
|
||||
|
||||
out=$($TMUX display-message -p "$fmt")
|
||||
|
||||
if [ "$out" != "$exp" ]; then
|
||||
echo "Fuzzy format test failed for '$fmt'."
|
||||
echo "Expected: '$exp'"
|
||||
echo "But got '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX -f/dev/null new-session -d || exit 1
|
||||
|
||||
# Empty pattern matches everything but has no highlighted positions.
|
||||
test_format '#{m/z:,abc}' '1'
|
||||
test_format '#{m/p:,abc}' ''
|
||||
|
||||
# Plain fuzzy matching and positions.
|
||||
test_format '#{m/z:abc,a_b_c}' '1'
|
||||
test_format '#{m/p:abc,a_b_c}' '0,2,4'
|
||||
test_format '#{m/z:abc,acb}' '0'
|
||||
test_format '#{m/p:abc,acb}' ''
|
||||
|
||||
# Multiple terms are ANDed. Positions are for all positive matched terms.
|
||||
test_format '#{m/z:dev bash,dev:1 bash}' '1'
|
||||
test_format '#{m/p:dev bash,dev:1 bash}' '0,1,2,6,7,8,9'
|
||||
test_format '#{m/z:dev bash,dev:1 sh}' '0'
|
||||
test_format '#{m/p:dev bash,dev:1 sh}' ''
|
||||
|
||||
# Lowercase patterns are case-insensitive for ASCII; uppercase makes the
|
||||
# pattern case-sensitive.
|
||||
test_format '#{m/z:abc,ABC}' '1'
|
||||
test_format '#{m/p:abc,ABC}' '0,1,2'
|
||||
test_format '#{m/z:ABC,abc}' '0'
|
||||
test_format '#{m/p:ABC,abc}' ''
|
||||
|
||||
# Exact substring matching with a leading quote.
|
||||
test_format "#{m/z:'bash,dev bash}" '1'
|
||||
test_format "#{m/p:'bash,dev bash}" '4,5,6,7'
|
||||
test_format "#{m/z:'bash,b-a-s-h}" '0'
|
||||
test_format "#{m/p:'bash,b-a-s-h}" ''
|
||||
|
||||
# Prefix and suffix matching.
|
||||
test_format '#{m/z:^dev,dev bash}' '1'
|
||||
test_format '#{m/p:^dev,dev bash}' '0,1,2'
|
||||
test_format '#{m/z:^dev,prod dev}' '0'
|
||||
test_format '#{m/p:^dev,prod dev}' ''
|
||||
test_format '#{m/z:bash$,dev bash}' '1'
|
||||
test_format '#{m/p:bash$,dev bash}' '4,5,6,7'
|
||||
test_format '#{m/z:bash$,bash dev}' '0'
|
||||
test_format '#{m/p:bash$,bash dev}' ''
|
||||
|
||||
# Inverse terms. Plain inverse terms are exact substring tests, not fuzzy.
|
||||
test_format '#{m/z:!ssh,dev bash}' '1'
|
||||
test_format '#{m/z:!ssh,dev ssh}' '0'
|
||||
test_format '#{m/z:!ssh,s_s_h}' '1'
|
||||
test_format '#{m/z:dev !ssh,dev bash}' '1'
|
||||
test_format '#{m/p:dev !ssh,dev bash}' '0,1,2'
|
||||
test_format '#{m/z:dev !ssh,dev ssh}' '0'
|
||||
test_format '#{m/p:dev !ssh,dev ssh}' ''
|
||||
|
||||
# OR separates groups; each group is an AND of its terms.
|
||||
test_format '#{m/z:prod | dev,dev bash}' '1'
|
||||
test_format '#{m/p:prod | dev,dev bash}' '0,1,2'
|
||||
test_format '#{m/z:prod | dev,prod bash}' '1'
|
||||
test_format '#{m/p:prod | dev,prod bash}' '0,1,2,3'
|
||||
test_format '#{m/z:prod | dev,test bash}' '0'
|
||||
test_format '#{m/p:prod | dev,test bash}' ''
|
||||
|
||||
# Style sequences do not count towards display positions.
|
||||
test_format '#{m/z:dev,#[bold]dev#[default]}' '1'
|
||||
test_format '#{m/p:dev,#[bold]dev#[default]}' '0,1,2'
|
||||
test_format '#{m/p:dev,#[fg=red#,bg=blue]dev#[default]}' '0,1,2'
|
||||
test_format '#{m/p:dev,#[bold]d#[default]e#[underscore]v#[default]}' '0,1,2'
|
||||
test_format '#{m/p:bash,#[bold]dev#[default] bash}' '4,5,6,7'
|
||||
|
||||
# UTF-8. Non-ASCII matching is exact, even when ASCII would fold case.
|
||||
test_format '#{m/z:é,café}' '1'
|
||||
test_format '#{m/p:é,café}' '3'
|
||||
test_format '#{m/z:é,É}' '0'
|
||||
test_format '#{m/p:é,É}' ''
|
||||
test_format '#{m/z:éx,éx}' '1'
|
||||
test_format '#{m/p:éx,éx}' '0,1'
|
||||
|
||||
# Wide UTF-8 characters occupy two display cells and both should be marked for
|
||||
# highlighting.
|
||||
test_format '#{m/z:界,a界b}' '1'
|
||||
test_format '#{m/p:界,a界b}' '1,2'
|
||||
|
||||
exit 0
|
||||
563
regress/format-modifiers.sh
Normal file
563
regress/format-modifiers.sh
Normal file
@@ -0,0 +1,563 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of format modifiers as described in tmux(1) FORMATS.
|
||||
#
|
||||
# This complements format-strings.sh (which covers escapes, conditionals,
|
||||
# boolean operators and the l: literal modifier). Here we exercise the
|
||||
# remaining modifiers: comparisons/matching (m, C, <, >, ==, ...), numeric
|
||||
# operations (e|op|), width/padding/truncation (=, p, n, w, a, R), basename
|
||||
# and dirname (b, d), time conversion (t), loops (S, W, P), colour (c) and
|
||||
# modifier nesting/limits.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
TZ=UTC
|
||||
export TZ
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
|
||||
ESC=$(printf '\033')
|
||||
|
||||
# test_format $format $expected [$target]
|
||||
#
|
||||
# Expand $format with display-message and compare with $expected. If $target
|
||||
# is given it is passed to display-message with -t.
|
||||
test_format()
|
||||
{
|
||||
fmt="$1"
|
||||
exp="$2"
|
||||
target="$3"
|
||||
|
||||
if [ -n "$target" ]; then
|
||||
out=$($TMUX display-message -t "$target" -p "$fmt")
|
||||
else
|
||||
out=$($TMUX display-message -p "$fmt")
|
||||
fi
|
||||
|
||||
if [ "$out" != "$exp" ]; then
|
||||
echo "Format test failed for '$fmt'."
|
||||
echo "Expected: '$exp'"
|
||||
echo "But got '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# test_expand $format $expected
|
||||
#
|
||||
# Expand $format in a plain format_expand context (list-windows -F on the
|
||||
# single-window "tf" session) rather than the format_expand_time context of
|
||||
# display-message. This matters for t/f: display-message runs the whole format
|
||||
# through strftime(3), so a strftime specifier there must be doubled (%%H); in a
|
||||
# format_expand context a single specifier (%H) is applied directly to the
|
||||
# variable's time.
|
||||
test_expand()
|
||||
{
|
||||
fmt="$1"
|
||||
exp="$2"
|
||||
|
||||
out=$($TMUX list-windows -t tf -F "$fmt")
|
||||
|
||||
if [ "$out" != "$exp" ]; then
|
||||
echo "Format test failed for '$fmt'."
|
||||
echo "Expected: '$exp'"
|
||||
echo "But got '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# assert_alive
|
||||
#
|
||||
# Check that the server is still responding (used after operations that could
|
||||
# in principle crash it, such as recursion and division by zero).
|
||||
assert_alive()
|
||||
{
|
||||
if [ "$($TMUX display-message -p alive)" != "alive" ]; then
|
||||
echo "Server did not survive: $1"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX new-session -d -s main -x 80 -y 24 || exit 1
|
||||
|
||||
# Single-window session used by test_expand for format_expand-context tests.
|
||||
$TMUX new-session -d -s tf || exit 1
|
||||
|
||||
# User options used as inputs. Modifiers operate on variable names, so plain
|
||||
# literals must be provided via options (or a nested #{l:...}). They are set
|
||||
# globally (-g) so they are visible from every session, including the "tf"
|
||||
# session used by test_expand.
|
||||
$TMUX set -g @s 'abcdefghij' || exit 1
|
||||
$TMUX set -g @path '/usr/local/bin/foo' || exit 1
|
||||
$TMUX set -g @name 'window-name' || exit 1
|
||||
$TMUX set -g @greek 'αβγ' || exit 1 # 6 bytes, 3 columns wide
|
||||
$TMUX set -g @cjk '中文' || exit 1 # 6 bytes, 4 columns wide
|
||||
$TMUX set -g @host 'myhost' || exit 1
|
||||
$TMUX set -g @ts '1000000000' || exit 1 # 2001-09-09 01:46:40 UTC
|
||||
$TMUX set -g @sp 'a b$c' || exit 1 # shell-special characters for q:
|
||||
$TMUX set -g @hash 'a#b' || exit 1 # a "#" for q/e:
|
||||
|
||||
|
||||
# --- Comparisons and matching --------------------------------------------
|
||||
|
||||
# m: glob match, first argument is the pattern.
|
||||
test_format "#{m:*foo*,barfoobar}" "1"
|
||||
test_format "#{m:*foo*,barbar}" "0"
|
||||
test_format "#{m:abc,abc}" "1"
|
||||
# m/i: ignore case.
|
||||
test_format "#{m/i:*FOO*,barfoobar}" "1"
|
||||
test_format "#{m/i:*FOO*,barbar}" "0"
|
||||
# m/r: regular expression.
|
||||
test_format "#{m/r:^[0-9]+\$,12345}" "1"
|
||||
test_format "#{m/r:^[0-9]+\$,12a45}" "0"
|
||||
# m/ri: regular expression, ignore case.
|
||||
test_format "#{m/ri:^ab+\$,ABBB}" "1"
|
||||
test_format "#{m/ri:^ab+\$,ACCC}" "0"
|
||||
# m/z: fuzzy match, returns a boolean.
|
||||
test_format "#{m/z:foo,foobar}" "1"
|
||||
test_format "#{m/z:xyz,foobar}" "0"
|
||||
# m/p: fuzzy match, returns the matched (0-based) column positions.
|
||||
test_format "#{m/p:ac,abc}" "0,2"
|
||||
test_format "#{m/p:xyz,abc}" ""
|
||||
# Fuzzy match against empty text.
|
||||
test_format "#{m/p:x,}" ""
|
||||
test_format "#{m/z:x,}" "0"
|
||||
|
||||
# String comparisons.
|
||||
test_format "#{==:#{@host},myhost}" "1"
|
||||
test_format "#{==:#{@host},other}" "0"
|
||||
test_format "#{!=:abc,xyz}" "1"
|
||||
test_format "#{!=:abc,abc}" "0"
|
||||
test_format "#{<:3,5}" "1"
|
||||
test_format "#{<:5,3}" "0"
|
||||
test_format "#{>:5,3}" "1"
|
||||
test_format "#{>:3,5}" "0"
|
||||
test_format "#{<=:5,5}" "1"
|
||||
test_format "#{<=:6,5}" "0"
|
||||
test_format "#{>=:5,5}" "1"
|
||||
test_format "#{>=:4,5}" "0"
|
||||
|
||||
# Negation and canonical boolean.
|
||||
test_format "#{!:0}" "1"
|
||||
test_format "#{!:1}" "0"
|
||||
test_format "#{!!:}" "0"
|
||||
test_format "#{!!:0}" "0"
|
||||
test_format "#{!!:non-empty}" "1"
|
||||
|
||||
|
||||
# --- Quoting (q) ---------------------------------------------------------
|
||||
|
||||
# q: escapes shell special characters with a backslash.
|
||||
test_format "#{q:@sp}" 'a\ b\$c'
|
||||
# q/e and q/h escape "#" for the format/style parser by doubling it.
|
||||
test_format "#{q/e:@hash}" 'a##b'
|
||||
test_format "#{q/h:@hash}" 'a##b'
|
||||
# q/a quotes the value as a single shell argument.
|
||||
test_format "#{q/a:@sp}" '"a b\$c"'
|
||||
|
||||
|
||||
# --- Name existence (N) --------------------------------------------------
|
||||
|
||||
# N/w is true if a window with the (expanded) name exists in the session, N/s
|
||||
# if a session with that name exists. The default (no argument) is /w.
|
||||
$TMUX rename-window -t main:0 knownwin
|
||||
test_format "#{N/s:main}" "1"
|
||||
test_format "#{N/s:nosuchsession}" "0"
|
||||
test_format "#{N/w:knownwin}" "1" "main:"
|
||||
test_format "#{N/w:nosuchwindow}" "0" "main:"
|
||||
test_format "#{N:nosuchwindow}" "0" "main:"
|
||||
|
||||
|
||||
# --- Numeric operations (e) ----------------------------------------------
|
||||
|
||||
# Integer operators.
|
||||
test_format "#{e|+|:2,3}" "5"
|
||||
test_format "#{e|-|:10,4}" "6"
|
||||
test_format "#{e|-|:2,5}" "-3"
|
||||
test_format "#{e|*|:6,7}" "42"
|
||||
test_format "#{e|/|:20,4}" "5"
|
||||
# Modulus - both spellings (% must be doubled as it is a strftime specifier).
|
||||
test_format "#{e|m|:7,3}" "1"
|
||||
test_format "#{e|%%|:7,3}" "1"
|
||||
|
||||
# Numeric comparison operators.
|
||||
test_format "#{e|==|:5,5}" "1"
|
||||
test_format "#{e|!=|:5,5}" "0"
|
||||
test_format "#{e|<|:2,5}" "1"
|
||||
test_format "#{e|>|:9,2}" "1"
|
||||
test_format "#{e|<=|:5,5}" "1"
|
||||
test_format "#{e|>=|:5,5}" "1"
|
||||
|
||||
# Floating point with a decimal-place count.
|
||||
test_format "#{e|*|f|4:5.5,3}" "16.5000"
|
||||
test_format "#{e|/|f|3:1,3}" "0.333"
|
||||
test_format "#{e|/|f|2:10,3}" "3.33"
|
||||
# Default number of decimal places for float is two.
|
||||
test_format "#{e|*|f:2.5,2}" "5.00"
|
||||
|
||||
# Division by zero must not crash the server (result is unspecified).
|
||||
$TMUX display-message -p "#{e|/|:5,0}" >/dev/null 2>&1
|
||||
$TMUX display-message -p "#{e|/|f:5,0}" >/dev/null 2>&1
|
||||
assert_alive "division by zero"
|
||||
|
||||
|
||||
# --- ASCII and repeat ----------------------------------------------------
|
||||
|
||||
# a: numeric value to its ASCII character.
|
||||
test_format "#{a:98}" "b"
|
||||
test_format "#{a:65}" "A"
|
||||
# a: out-of-range or non-numeric input yields an empty string.
|
||||
test_format "#{a:200}" ""
|
||||
test_format "#{a:notanumber}" ""
|
||||
# R: repeat first argument second-argument times.
|
||||
test_format "#{R:a,3}" "aaa"
|
||||
test_format "#{R:ab,2}" "abab"
|
||||
# A long repeat exercises output-buffer growth during expansion.
|
||||
test_format "#{n:#{R:x,300}}" "300"
|
||||
|
||||
|
||||
# --- Width, padding and truncation ---------------------------------------
|
||||
|
||||
# =N truncates from the start, =-N from the end.
|
||||
test_format "#{=5:@s}" "abcde"
|
||||
test_format "#{=-5:@s}" "fghij"
|
||||
# = with no width, or a non-numeric width, does not truncate.
|
||||
test_format "#{=:@s}" "abcdefghij"
|
||||
test_format "#{=/x:@s}" "abcdefghij"
|
||||
# A marker is appended/prepended only when trimming actually occurs.
|
||||
test_format "#{=/5/...:@s}" "abcde..."
|
||||
test_format "#{=/5/...:@name}" "windo..."
|
||||
test_format "#{=/20/...:@s}" "abcdefghij"
|
||||
# Truncation is display-width (UTF-8) aware: a wide (2-column) character is only
|
||||
# included if it fits entirely within the limit.
|
||||
test_format "#{=3:@greek}" "αβγ"
|
||||
test_format "#{=2:@greek}" "αβ"
|
||||
test_format "#{=2:@cjk}" "中"
|
||||
test_format "#{=1:@cjk}" ""
|
||||
# Markers with wide characters: the marker is added when trimming occurs, and a
|
||||
# limit that splits a wide character drops it entirely.
|
||||
test_format "#{=/2/x:@cjk}" "中x"
|
||||
test_format "#{=/1/x:@cjk}" "x"
|
||||
|
||||
# p pads to a width: a positive width left-aligns (pads on the right), a
|
||||
# negative width right-aligns (pads on the left).
|
||||
test_format "#{p12:@name}" "window-name "
|
||||
test_format "#{p-12:@name}" " window-name"
|
||||
# No padding once the value already meets the width.
|
||||
test_format "#{p3:@name}" "window-name"
|
||||
# p with no width does nothing.
|
||||
test_format "#{p:@name}" "window-name"
|
||||
# Padding is display-width aware: @cjk is 4 columns wide, so p6/p-6 add two
|
||||
# spaces (not four).
|
||||
test_format "#{p6:@cjk}" "中文 "
|
||||
test_format "#{p-6:@cjk}" " 中文"
|
||||
|
||||
# n is byte length, w is display width.
|
||||
test_format "#{n:@s}" "10"
|
||||
test_format "#{w:@s}" "10"
|
||||
test_format "#{n:@greek}" "6"
|
||||
test_format "#{w:@greek}" "3"
|
||||
test_format "#{n:@cjk}" "6"
|
||||
test_format "#{w:@cjk}" "4"
|
||||
|
||||
|
||||
# --- basename and dirname ------------------------------------------------
|
||||
|
||||
test_format "#{b:@path}" "foo"
|
||||
test_format "#{d:@path}" "/usr/local/bin"
|
||||
|
||||
|
||||
# --- Time conversion -----------------------------------------------------
|
||||
|
||||
# t: converts an integer time to a ctime(3) string.
|
||||
test_format "#{t:@ts}" "Sun Sep 9 01:46:40 2001"
|
||||
# t/p: shorter format for times in the past.
|
||||
test_format "#{t/p:@ts}" "Sep01"
|
||||
# t/r: relative time depends on the current time, just check it is non-empty.
|
||||
if [ -z "$($TMUX display-message -p '#{t/r:@ts}')" ]; then
|
||||
echo "Format test failed for '#{t/r:@ts}': empty result"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# t/f: custom strftime format applied to the variable's time. Tested in a
|
||||
# format_expand context (list-windows -F), where a single strftime specifier is
|
||||
# applied directly. (In display-message, which additionally expands the format
|
||||
# through strftime, these would need to be doubled - %%Y etc.) The colon in the
|
||||
# format is escaped as '#:' because it is otherwise the modifier separator.
|
||||
test_expand "#{t/f/%Y:@ts}" "2001"
|
||||
test_expand "#{t/f/%Y-%m-%d:@ts}" "2001-09-09"
|
||||
test_expand "#{t/f/%H#:%M#:%S:@ts}" "01:46:40"
|
||||
# An escaped comma in the custom format is unescaped before strftime.
|
||||
test_expand "#{t/f/%Y#,end:@ts}" "2001,end"
|
||||
|
||||
# T: expands its argument and then runs the result through strftime with the
|
||||
# current time. A value with no strftime specifier is returned unchanged.
|
||||
test_format "#{T:@ts}" "1000000000"
|
||||
|
||||
# t/p (pretty) and t/r (relative) format times by age relative to now, with a
|
||||
# different branch per age band. Build options a known number of seconds in the
|
||||
# past and check each yields a non-empty result (the exact text depends on the
|
||||
# wall clock, so only non-emptiness is asserted).
|
||||
now=$(date +%s)
|
||||
for age in 30 300 4000 90000 200000 3000000 40000000; do
|
||||
$TMUX set -g @age "$((now - age))"
|
||||
if [ -z "$($TMUX display-message -p '#{t/r:@age}')" ]; then
|
||||
echo "Empty #{t/r:@age} for age ${age}s"
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$($TMUX display-message -p '#{t/p:@age}')" ]; then
|
||||
echo "Empty #{t/p:@age} for age ${age}s"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
# A time in the future has no relative form.
|
||||
$TMUX set -g @future "$((now + 100000))"
|
||||
test_format "#{t/r:@future}" ""
|
||||
|
||||
|
||||
# --- Content search (C) --------------------------------------------------
|
||||
|
||||
# Use a window running cat so the content is deterministic (no shell prompt).
|
||||
$TMUX new-session -d -s search -x 80 -y 10 'cat'
|
||||
sleep 1
|
||||
$TMUX send-keys -t search: 'Zebra_Marker_42' Enter
|
||||
sleep 1
|
||||
# C: returns the (1-based) line number of a match or 0 if not found.
|
||||
test_format "#{C:Zebra_Marker_42}" "1" "search:"
|
||||
test_format "#{C:Absent_String_999}" "0" "search:"
|
||||
test_format "#{C/r:Zebra_.*_42}" "1" "search:"
|
||||
test_format "#{C/i:zebra_marker_42}" "1" "search:"
|
||||
$TMUX kill-session -t search 2>/dev/null
|
||||
|
||||
|
||||
# --- Colour (c) ----------------------------------------------------------
|
||||
|
||||
# c: converts a colour to its six-digit hexadecimal RGB value.
|
||||
test_format "#{c:red}" "800000"
|
||||
test_format "#{c:colour4}" "000080"
|
||||
test_format "#{c:#7f7f7f}" "7f7f7f"
|
||||
# c/f and c/b produce the SGR escape sequence for fg/bg respectively.
|
||||
test_format "#{c/f:red}" "${ESC}[31m"
|
||||
test_format "#{c/b:red}" "${ESC}[41m"
|
||||
test_format "#{c/b:colour4}" "${ESC}[48;5;4m"
|
||||
# "none" gives a reset; an unknown colour gives an empty string.
|
||||
test_format "#{c/f:none}" "${ESC}[0m"
|
||||
test_format "#{c:notacolour}" ""
|
||||
test_format "#{c/f:notacolour}" ""
|
||||
|
||||
|
||||
# --- Nesting and limits --------------------------------------------------
|
||||
|
||||
# Modifier chaining: inner b: then outer truncation/padding/length.
|
||||
test_format "#{=5:#{b:@path}}" "foo"
|
||||
test_format "#{=2:#{b:@path}}" "fo"
|
||||
test_format "#{p6:#{b:@path}}" "foo "
|
||||
test_format "#{n:#{b:@path}}" "3"
|
||||
|
||||
# Nested l: literal expanded then truncated.
|
||||
test_format "#{=5:#{l:abcdefghij}}" "abcde"
|
||||
|
||||
# Deeper nesting: basename -> pad to 10 -> truncate to 5.
|
||||
test_format "#{=5:#{p10:#{b:@path}}}" "foo "
|
||||
# A substitution applied to a nested basename.
|
||||
test_format "#{s/o/O/:#{b:@path}}" "fOO"
|
||||
|
||||
# Unbounded self-recursion must hit the loop limit rather than crash.
|
||||
$TMUX set @rec '#{E:@rec}'
|
||||
$TMUX display-message -p '#{E:@rec}' >/dev/null 2>&1
|
||||
assert_alive "recursive expansion"
|
||||
|
||||
|
||||
# --- Missing, malformed and limit inputs ---------------------------------
|
||||
|
||||
# An undefined variable expands to empty; modifiers on it behave sensibly.
|
||||
test_format "#{@undefined}" ""
|
||||
test_format "#{=5:@undefined}" ""
|
||||
test_format "#{b:@undefined}" ""
|
||||
test_format "#{n:@undefined}" "0"
|
||||
|
||||
# Malformed numeric expressions expand to empty rather than erroring out.
|
||||
test_format "#{e|+|:notanumber,2}" "" # invalid left operand
|
||||
test_format "#{e|+|:2,notanumber}" "" # invalid right operand
|
||||
test_format "#{e|badop|:1,2}" "" # unknown operator
|
||||
test_format "#{e|+|f|x:1,2}" "" # invalid precision
|
||||
test_format "#{e|+|:1}" "" # too few operands
|
||||
test_format "#{e|+|f|2|extra:1,2}" "" # too many arguments (limit is 3)
|
||||
|
||||
# Repeat with a non-numeric or zero count yields an empty string.
|
||||
test_format "#{R:a,notanumber}" ""
|
||||
test_format "#{R:a,0}" ""
|
||||
|
||||
# Comparisons with too few arguments expand to empty.
|
||||
test_format "#{==:a}" ""
|
||||
test_format "#{<:a}" ""
|
||||
|
||||
# A substitution with fewer than two arguments is a no-op.
|
||||
test_format "#{s/onlyone:@s}" "abcdefghij"
|
||||
|
||||
# A non-numeric width for = or p is treated as no width (no change).
|
||||
test_format "#{=/x:@s}" "abcdefghij"
|
||||
test_format "#{p/x:@s}" "abcdefghij"
|
||||
|
||||
# The I (client terminal) modifier with no attached client is empty; this also
|
||||
# exercises its argument parsing (/c termcap, /f feature, default). The
|
||||
# non-empty terminal cases are covered with a real client in format-variables.sh.
|
||||
test_format "#{I/c:RGB}" ""
|
||||
test_format "#{I/f:overline}" ""
|
||||
test_format "#{I:x}" ""
|
||||
|
||||
|
||||
# --- Escaping inside modifiers -------------------------------------------
|
||||
|
||||
# A "," or "#" inside a modifier argument is escaped with "#".
|
||||
test_format "#{s/#,/-/:#{l:a,b,c}}" "a-b-c" # escaped comma in the pattern
|
||||
test_format "#{=/3/#,:@s}" "abc," # escaped comma in the marker
|
||||
# The truncation marker is itself expanded as a format.
|
||||
test_format "#{=/3/#{l:>}:@s}" "abc>"
|
||||
|
||||
# Substitution flags: a third argument of "i" is case-insensitive; an invalid
|
||||
# regular expression leaves the text unchanged.
|
||||
test_format "#{s/A/X/i:@s}" "Xbcdefghij"
|
||||
test_format "#{s/[/X/:@s}" "abcdefghij"
|
||||
|
||||
|
||||
# --- Unicode in modifier arguments ---------------------------------------
|
||||
|
||||
# Wide (CJK) and emoji text: matching, substitution, repeat and markers all
|
||||
# operate on characters, and n/w report bytes/columns.
|
||||
$TMUX set -g @emoji '😀😀' || exit 1 # 8 bytes, 4 columns
|
||||
test_format "#{m:*中*,#{@cjk}}" "1"
|
||||
test_format "#{s/文/X/:@cjk}" "中X"
|
||||
test_format "#{R:中,3}" "中中中"
|
||||
test_format "#{=/1/中:@s}" "a中"
|
||||
test_format "#{w:@emoji}" "4"
|
||||
test_format "#{n:@emoji}" "8"
|
||||
test_format "#{=2:@emoji}" "😀"
|
||||
|
||||
|
||||
# --- Server messages (show-messages) -------------------------------------
|
||||
|
||||
# show-messages formats each logged message (this exercises the message-time
|
||||
# formatting path); just check the server survives producing it.
|
||||
$TMUX show-messages >/dev/null 2>&1
|
||||
assert_alive "show-messages"
|
||||
|
||||
|
||||
# --- Verbose expansion (logging) -----------------------------------------
|
||||
|
||||
# display-message -v turns on format logging, so re-expanding a representative
|
||||
# set of formats with -v exercises the logging code paths. Only survival is
|
||||
# checked; the log text itself is not asserted.
|
||||
for f in \
|
||||
'#{=3:@s}' \
|
||||
'#{e|+|:2,3}' \
|
||||
'#{e|*|f|2:2.5,2}' \
|
||||
'#{m:*a*,abc}' \
|
||||
'#{<:3,5}' \
|
||||
'#{s/a/X/:@s}' \
|
||||
'#{b:@path}' \
|
||||
'#{t:@ts}' \
|
||||
'#{p6:@name}' \
|
||||
'#{=3:#{b:@path}}'; do
|
||||
$TMUX display-message -v -p "$f" >/dev/null 2>&1
|
||||
done
|
||||
assert_alive "verbose expansion"
|
||||
|
||||
|
||||
# --- Loops and sorting (S, W, P, L) --------------------------------------
|
||||
#
|
||||
# These need a fully controlled server so the set of sessions, windows and
|
||||
# panes (and their order) is known, so start from a clean server. This must be
|
||||
# the last section as it discards the setup above.
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
# Sessions, created in this order, so session ids (and hence creation order)
|
||||
# are zeta=$0, alpha=$1, mike=$2.
|
||||
$TMUX new-session -d -s zeta -x 80 -y 24 || exit 1
|
||||
$TMUX new-session -d -s alpha || exit 1
|
||||
$TMUX new-session -d -s mike || exit 1
|
||||
$TMUX set -g automatic-rename off
|
||||
|
||||
# S loops over every session. The default order is by session id (SORT_INDEX),
|
||||
# /i is the same, /n is by name, and the r suffix reverses.
|
||||
test_format "#{S:#{session_name} }" "zeta alpha mike "
|
||||
test_format "#{S/i:#{session_name} }" "zeta alpha mike "
|
||||
test_format "#{S/n:#{session_name} }" "alpha mike zeta "
|
||||
test_format "#{S/nr:#{session_name} }" "zeta mike alpha "
|
||||
test_format "#{S/ir:#{session_name} }" "mike alpha zeta "
|
||||
# /t sorts by activity time; the exact order is timing-dependent, so just check
|
||||
# every session is still iterated (this exercises the activity-sort branch).
|
||||
test_format "#{S/t:x}" "xxx"
|
||||
# An unrecognised sort letter falls back to the default order; /r on its own
|
||||
# reverses that default (this covers the fall-through branch).
|
||||
test_format "#{S/r:#{session_name} }" "mike alpha zeta "
|
||||
|
||||
# Windows in session zeta: window 0 renamed charlie, then alpha at 1, bravo at
|
||||
# 2. The default order is by index (SORT_ORDER), /n is by name, r reverses.
|
||||
$TMUX rename-window -t zeta:0 charlie
|
||||
$TMUX new-window -d -t zeta:1 -n alpha
|
||||
$TMUX new-window -d -t zeta:2 -n bravo
|
||||
test_format "#{W:#{window_name} }" "charlie alpha bravo " "zeta:"
|
||||
test_format "#{W/n:#{window_name} }" "alpha bravo charlie " "zeta:"
|
||||
test_format "#{W/nr:#{window_name} }" "charlie bravo alpha " "zeta:"
|
||||
test_format "#{W/ir:#{window_index}}" "210" "zeta:"
|
||||
# /i (by index) and /t (by activity); /i matches the default order here.
|
||||
test_format "#{W/i:#{window_name} }" "charlie alpha bravo " "zeta:"
|
||||
test_format "#{W/t:x}" "xxx" "zeta:"
|
||||
# An unrecognised sort letter falls back to the default order; /r reverses it.
|
||||
test_format "#{W/r:#{window_name} }" "bravo alpha charlie " "zeta:"
|
||||
|
||||
# Panes in window zeta:charlie. Splitting the active (newest) pane each time
|
||||
# makes pane index match creation order (0,1,2 left to right). The default
|
||||
# order is by creation (SORT_CREATION), r reverses.
|
||||
$TMUX split-window -h -t zeta:charlie
|
||||
$TMUX split-window -h -t zeta:charlie
|
||||
test_format "#{P:#{pane_index}}" "012" "zeta:charlie"
|
||||
test_format "#{P/r:#{pane_index}}" "210" "zeta:charlie"
|
||||
# A pane-sort argument is accepted; for panes only the r (reverse) suffix has an
|
||||
# effect, so these all keep the count and exercise the argument branch.
|
||||
test_format "#{P/i:x}" "xxx" "zeta:charlie"
|
||||
test_format "#{P/n:x}" "xxx" "zeta:charlie"
|
||||
test_format "#{P/t:x}" "xxx" "zeta:charlie"
|
||||
|
||||
# Verbose expansion of the loops, to exercise their logging paths.
|
||||
$TMUX display-message -v -p "#{S:#{session_name}}" >/dev/null 2>&1
|
||||
$TMUX display-message -v -t zeta: -p "#{W:#{window_name}}" >/dev/null 2>&1
|
||||
$TMUX display-message -v -t zeta:charlie -p "#{P:#{pane_index}}" >/dev/null 2>&1
|
||||
assert_alive "verbose loop expansion"
|
||||
|
||||
# L loops over attached clients. Attach two control-mode clients, each held
|
||||
# open by a background process keeping a FIFO's write end open.
|
||||
FIFO1="${TMPDIR:-/tmp}/fmt-l-$$-1"
|
||||
FIFO2="${TMPDIR:-/tmp}/fmt-l-$$-2"
|
||||
rm -f "$FIFO1" "$FIFO2"
|
||||
mkfifo "$FIFO1" "$FIFO2" || exit 1
|
||||
# Hold the write ends open so the control clients stay attached.
|
||||
sleep 30 >"$FIFO1" &
|
||||
HOLD1=$!
|
||||
sleep 30 >"$FIFO2" &
|
||||
HOLD2=$!
|
||||
$TMUX -C attach -t zeta <"$FIFO1" >/dev/null 2>&1 &
|
||||
CC1=$!
|
||||
$TMUX -C attach -t alpha <"$FIFO2" >/dev/null 2>&1 &
|
||||
CC2=$!
|
||||
sleep 1
|
||||
# Two clients attached: L emits one item per client.
|
||||
test_format "#{L:x}" "xx"
|
||||
# The client sort orders (default, index, name, activity, reversed) are all
|
||||
# accepted; assert only the count so the test does not depend on client names or
|
||||
# timing.
|
||||
test_format "#{L/i:x}" "xx"
|
||||
test_format "#{L/n:x}" "xx"
|
||||
test_format "#{L/t:x}" "xx"
|
||||
test_format "#{L/nr:x}" "xx"
|
||||
test_format "#{L/r:x}" "xx"
|
||||
# Now detach one and confirm the count drops to one.
|
||||
kill $HOLD2 2>/dev/null
|
||||
sleep 1
|
||||
test_format "#{L:x}" "x"
|
||||
kill $HOLD1 $CC1 $CC2 2>/dev/null
|
||||
rm -f "$FIFO1" "$FIFO2"
|
||||
|
||||
exit 0
|
||||
143
regress/format-mouse.sh
Normal file
143
regress/format-mouse.sh
Normal file
@@ -0,0 +1,143 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of the mouse format variables (mouse_x, mouse_y, mouse_word,
|
||||
# mouse_line, ...). These are only populated while a mouse key binding is being
|
||||
# dispatched, so the test drives a real mouse event:
|
||||
#
|
||||
# - an inner client is attached inside a pane of a second ("outer") tmux
|
||||
# server, giving the inner server a genuine terminal;
|
||||
# - mouse mode is on and a MouseDown1Pane binding records the mouse format
|
||||
# variables into an option;
|
||||
# - an SGR mouse sequence is written to the outer pane, so the inner client
|
||||
# receives it as a real mouse click.
|
||||
#
|
||||
# This exercises the mouse callbacks and the grid word/line lookup code that
|
||||
# display-message cannot otherwise reach.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -Ltest2 -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server >/dev/null 2>&1
|
||||
$TMUX2 kill-server >/dev/null 2>&1
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$1"
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
# click COL ROW
|
||||
#
|
||||
# Write an SGR mouse press then release (button 0) at 1-based COL/ROW to the
|
||||
# outer pane holding the inner client.
|
||||
click()
|
||||
{
|
||||
col="$1"
|
||||
row="$2"
|
||||
seq=$(printf '\033[<0;%s;%sM\033[<0;%s;%sm' "$col" "$row" "$col" "$row")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 1
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
# Inner session with a single pane running cat, so its content is exactly what
|
||||
# we send it.
|
||||
$TMUX new-session -d -s cov -x 80 -y 24 'cat' || exit 1
|
||||
$TMUX set -g mouse on
|
||||
sleep 1
|
||||
$TMUX send-keys -t cov:0.0 'alpha beta gamma' Enter
|
||||
sleep 1
|
||||
|
||||
# Record every pane mouse variable when the pane is clicked.
|
||||
$TMUX bind -n MouseDown1Pane run-shell \
|
||||
"$TMUX set -g @m 'x=#{mouse_x} y=#{mouse_y} word=#{mouse_word} line=#{mouse_line} pane=#{mouse_pane} hl=[#{mouse_hyperlink}]'"
|
||||
|
||||
# Attach a real client inside an outer tmux pane. Clicks all target the first
|
||||
# row, which lines up with the inner client regardless of the outer status line.
|
||||
$TMUX2 new-session -d -x 80 -y 24 "$TMUX attach -t cov" || exit 1
|
||||
sleep 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "No outer pane."
|
||||
|
||||
# Click column 3, row 1: over the first word ("alpha") of the first line.
|
||||
click 3 1
|
||||
|
||||
M=$($TMUX show -gv @m 2>/dev/null)
|
||||
[ -n "$M" ] || fail "Mouse binding did not fire (no @m)."
|
||||
|
||||
# mouse_x is 0-based column (SGR column 3 -> x 2); mouse_y is 0-based row 0.
|
||||
case "$M" in
|
||||
*"x=2 "*) ;;
|
||||
*) fail "Unexpected mouse_x in: $M" ;;
|
||||
esac
|
||||
case "$M" in
|
||||
*"y=0 "*) ;;
|
||||
*) fail "Unexpected mouse_y in: $M" ;;
|
||||
esac
|
||||
# mouse_word is the word under the cursor, mouse_line the whole line.
|
||||
case "$M" in
|
||||
*"word=alpha "*) ;;
|
||||
*) fail "Unexpected mouse_word in: $M" ;;
|
||||
esac
|
||||
case "$M" in
|
||||
*"line=alpha beta gamma "*) ;;
|
||||
*) fail "Unexpected mouse_line in: $M" ;;
|
||||
esac
|
||||
|
||||
# A click in a different column selects a different word.
|
||||
click 8 1
|
||||
M=$($TMUX show -gv @m 2>/dev/null)
|
||||
case "$M" in
|
||||
*"word=beta "*) ;;
|
||||
*) fail "Unexpected mouse_word for second click in: $M" ;;
|
||||
esac
|
||||
|
||||
# The same variables have a separate path when the pane is in a mode (the word
|
||||
# and line come from the mode, not the live grid). A binding in the copy-mode
|
||||
# key table fires while copy mode is active.
|
||||
$TMUX bind -T copy-mode MouseDown1Pane run-shell \
|
||||
"$TMUX set -g @cm 'x=#{mouse_x} word=#{mouse_word} line=#{mouse_line}'"
|
||||
$TMUX copy-mode -t cov:0.0
|
||||
sleep 1
|
||||
click 8 1
|
||||
CM=$($TMUX show -gv @cm 2>/dev/null)
|
||||
case "$CM" in
|
||||
*"word=beta"*) ;;
|
||||
*) fail "Unexpected copy-mode mouse_word in: $CM" ;;
|
||||
esac
|
||||
$TMUX send-keys -t cov:0.0 -X cancel
|
||||
sleep 1
|
||||
|
||||
# Hyperlinks: a new window whose pane emits an OSC 8 hyperlink over the text
|
||||
# "LINKED". Clicking it reports the target URL via mouse_hyperlink (this drives
|
||||
# the grid hyperlink lookup). The emitter is written to a small script to keep
|
||||
# the escape sequence readable.
|
||||
LINKSH="${TMPDIR:-/tmp}/fmt-mouse-link-$$.sh"
|
||||
cat >"$LINKSH" <<'EOF'
|
||||
#!/bin/sh
|
||||
printf '\033]8;;http://example.com\033\\LINKED\033]8;;\033\\\n'
|
||||
exec cat
|
||||
EOF
|
||||
chmod +x "$LINKSH"
|
||||
$TMUX neww -t cov: -n link "$LINKSH"
|
||||
sleep 1
|
||||
$TMUX select-window -t cov:link
|
||||
sleep 1
|
||||
click 3 1
|
||||
M=$($TMUX show -gv @m 2>/dev/null)
|
||||
rm -f "$LINKSH"
|
||||
case "$M" in
|
||||
*"hl=[http://example.com]"*) ;;
|
||||
*) fail "Unexpected mouse_hyperlink in: $M" ;;
|
||||
esac
|
||||
|
||||
cleanup
|
||||
exit 0
|
||||
@@ -67,6 +67,7 @@ $TMUX set @true 1 || exit 1
|
||||
$TMUX set @false 0 || exit 1
|
||||
$TMUX set @warm Summer || exit 1
|
||||
$TMUX set @cold Winter || exit 1
|
||||
$TMUX set @v 'foo:bar' || exit 1
|
||||
|
||||
# Plain string without substitutions et al
|
||||
test_format "abc xyz" "abc xyz"
|
||||
@@ -74,6 +75,8 @@ test_format "abc xyz" "abc xyz"
|
||||
# Test basic escapes for "#", "{", "#{" "}", "#}", ","
|
||||
test_format "##" "#"
|
||||
test_format "#," ","
|
||||
test_format "#{s/#:/_/:@v}" "foo_bar"
|
||||
test_format "#{s/o/#:/:@v}" "f:::bar"
|
||||
test_format "{" "{"
|
||||
test_format "##{" "#{"
|
||||
test_format "#}" "}"
|
||||
|
||||
388
regress/format-variables.sh
Normal file
388
regress/format-variables.sh
Normal file
@@ -0,0 +1,388 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests that every format variable listed in tmux(1) (the format_table in
|
||||
# format.c) can be expanded without crashing the server, and checks the value
|
||||
# of a stable subset.
|
||||
#
|
||||
# The main point is coverage and crash-safety: each variable is expanded in a
|
||||
# rich context - a real attached client (from a nested tmux), a control-mode
|
||||
# client, a grouped session, two windows with a bell alert, a window with two
|
||||
# panes running cat, a paste buffer and options - so the per-variable callbacks
|
||||
# actually run. format-modifiers.sh covers the modifier machinery; this covers
|
||||
# the variable callbacks.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
# A second server on its own socket provides a real terminal (an inner client
|
||||
# attached inside one of its panes) so client terminal variables are populated.
|
||||
TMUX2="$TEST_TMUX -Ltest2 -f/dev/null"
|
||||
|
||||
# Every variable name in format_table[]. Kept as a plain word list so it can be
|
||||
# iterated with normal shell word splitting.
|
||||
NAMES="
|
||||
active_window_index
|
||||
alternate_on
|
||||
alternate_saved_x
|
||||
alternate_saved_y
|
||||
bracket_paste_flag
|
||||
buffer_created
|
||||
buffer_full
|
||||
buffer_mode_format
|
||||
buffer_name
|
||||
buffer_sample
|
||||
buffer_size
|
||||
client_activity
|
||||
client_cell_height
|
||||
client_cell_width
|
||||
client_colours
|
||||
client_control_mode
|
||||
client_created
|
||||
client_discarded
|
||||
client_flags
|
||||
client_height
|
||||
client_key_table
|
||||
client_last_session
|
||||
client_mode_format
|
||||
client_name
|
||||
client_pid
|
||||
client_prefix
|
||||
client_readonly
|
||||
client_session
|
||||
client_termfeatures
|
||||
client_termname
|
||||
client_termtype
|
||||
client_theme
|
||||
client_tty
|
||||
client_uid
|
||||
client_user
|
||||
client_utf8
|
||||
client_width
|
||||
client_written
|
||||
config_files
|
||||
cursor_blinking
|
||||
cursor_character
|
||||
cursor_colour
|
||||
cursor_flag
|
||||
cursor_shape
|
||||
cursor_very_visible
|
||||
cursor_x
|
||||
cursor_y
|
||||
history_all_bytes
|
||||
history_bytes
|
||||
history_limit
|
||||
history_size
|
||||
host
|
||||
host_short
|
||||
insert_flag
|
||||
keypad_cursor_flag
|
||||
keypad_flag
|
||||
last_window_index
|
||||
mouse_all_flag
|
||||
mouse_any_flag
|
||||
mouse_button_flag
|
||||
mouse_hyperlink
|
||||
mouse_line
|
||||
mouse_pane
|
||||
mouse_sgr_flag
|
||||
mouse_standard_flag
|
||||
mouse_status_line
|
||||
mouse_status_range
|
||||
mouse_utf8_flag
|
||||
mouse_word
|
||||
mouse_x
|
||||
mouse_y
|
||||
next_session_id
|
||||
origin_flag
|
||||
pane_active
|
||||
pane_at_bottom
|
||||
pane_at_left
|
||||
pane_at_right
|
||||
pane_at_top
|
||||
pane_bg
|
||||
pane_bottom
|
||||
pane_current_command
|
||||
pane_current_path
|
||||
pane_dead
|
||||
pane_dead_signal
|
||||
pane_dead_status
|
||||
pane_dead_time
|
||||
pane_fg
|
||||
pane_flags
|
||||
pane_floating_flag
|
||||
pane_format
|
||||
pane_height
|
||||
pane_id
|
||||
pane_in_mode
|
||||
pane_index
|
||||
pane_input_off
|
||||
pane_key_mode
|
||||
pane_last
|
||||
pane_left
|
||||
pane_marked
|
||||
pane_marked_set
|
||||
pane_mode
|
||||
pane_path
|
||||
pane_pb_progress
|
||||
pane_pb_state
|
||||
pane_pid
|
||||
pane_pipe
|
||||
pane_pipe_pid
|
||||
pane_right
|
||||
pane_search_string
|
||||
pane_start_command
|
||||
pane_start_path
|
||||
pane_synchronized
|
||||
pane_tabs
|
||||
pane_title
|
||||
pane_top
|
||||
pane_tty
|
||||
pane_unseen_changes
|
||||
pane_width
|
||||
pane_x
|
||||
pane_y
|
||||
pane_z
|
||||
pane_zoomed_flag
|
||||
pid
|
||||
scroll_region_lower
|
||||
scroll_region_upper
|
||||
server_sessions
|
||||
session_active
|
||||
session_activity
|
||||
session_activity_flag
|
||||
session_alert
|
||||
session_alerts
|
||||
session_attached
|
||||
session_attached_list
|
||||
session_bell_flag
|
||||
session_created
|
||||
session_format
|
||||
session_group
|
||||
session_group_attached
|
||||
session_group_attached_list
|
||||
session_group_list
|
||||
session_group_many_attached
|
||||
session_group_size
|
||||
session_grouped
|
||||
session_id
|
||||
session_last_attached
|
||||
session_many_attached
|
||||
session_marked
|
||||
session_name
|
||||
session_path
|
||||
session_silence_flag
|
||||
session_stack
|
||||
session_windows
|
||||
sixel_support
|
||||
socket_path
|
||||
start_time
|
||||
synchronized_output_flag
|
||||
tree_mode_format
|
||||
uid
|
||||
user
|
||||
version
|
||||
window_active
|
||||
window_active_clients
|
||||
window_active_clients_list
|
||||
window_active_sessions
|
||||
window_active_sessions_list
|
||||
window_activity
|
||||
window_activity_flag
|
||||
window_bell_flag
|
||||
window_bigger
|
||||
window_cell_height
|
||||
window_cell_width
|
||||
window_end_flag
|
||||
window_flags
|
||||
window_format
|
||||
window_height
|
||||
window_id
|
||||
window_index
|
||||
window_last_flag
|
||||
window_layout
|
||||
window_linked
|
||||
window_linked_sessions
|
||||
window_linked_sessions_list
|
||||
window_marked_flag
|
||||
window_name
|
||||
window_offset_x
|
||||
window_offset_y
|
||||
window_panes
|
||||
window_raw_flags
|
||||
window_silence_flag
|
||||
window_stack_index
|
||||
window_start_flag
|
||||
window_visible_layout
|
||||
window_width
|
||||
window_zoomed_flag
|
||||
wrap_flag
|
||||
"
|
||||
|
||||
# test_var $name $expected [$extra_args...]
|
||||
#
|
||||
# Expand a single #{name} and compare against $expected. Any extra arguments
|
||||
# are passed straight to display-message (e.g. -c or -t).
|
||||
test_var()
|
||||
{
|
||||
name="$1"
|
||||
exp="$2"
|
||||
shift 2
|
||||
|
||||
out=$($TMUX display-message "$@" -p "#{$name}")
|
||||
if [ "$out" != "$exp" ]; then
|
||||
echo "Variable test failed for '#{$name}'."
|
||||
echo "Expected: '$exp'"
|
||||
echo "But got '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_alive()
|
||||
{
|
||||
if [ "$($TMUX display-message -p alive)" != "alive" ]; then
|
||||
echo "Server did not survive: $1"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
FIFO="${TMPDIR:-/tmp}/fmt-vars-$$"
|
||||
HOLD=""
|
||||
CC=""
|
||||
|
||||
cleanup()
|
||||
{
|
||||
[ -n "$HOLD" ] && kill $HOLD 2>/dev/null
|
||||
[ -n "$CC" ] && kill $CC 2>/dev/null
|
||||
rm -f "$FIFO"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$1"
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
|
||||
# A session "cov" with a window "win0" holding two panes running cat, plus a
|
||||
# second window, an option and a paste buffer.
|
||||
$TMUX new-session -d -s cov -x 80 -y 24 -n win0 'cat' || exit 1
|
||||
$TMUX set -g automatic-rename off
|
||||
$TMUX set -g monitor-bell on
|
||||
$TMUX set -g monitor-activity on
|
||||
$TMUX split-window -t cov:win0 -d 'cat' || exit 1
|
||||
$TMUX new-window -d -t cov:1 -n win1 'cat' || exit 1
|
||||
$TMUX set -g @opt 'optionvalue' || exit 1
|
||||
$TMUX set-buffer -b buf0 'somebuffer' || exit 1
|
||||
|
||||
# A second session grouped with cov, so the session_group_* variables have real
|
||||
# data to report.
|
||||
$TMUX new-session -d -s cov2 -t cov || exit 1
|
||||
|
||||
sleep 1
|
||||
$TMUX send-keys -t cov:win0.0 'some pane content' Enter
|
||||
# Ring the bell in the non-current window so a bell alert is raised on the
|
||||
# session (this populates session_alert/session_alerts and window_bell_flag).
|
||||
$TMUX send-keys -t cov:win1.0 C-g
|
||||
sleep 1
|
||||
|
||||
# Attach a control-mode client, held open by a background process keeping the
|
||||
# write end of a FIFO open, so client_* variables have a client to read.
|
||||
rm -f "$FIFO"
|
||||
mkfifo "$FIFO" || exit 1
|
||||
sleep 30 >"$FIFO" &
|
||||
HOLD=$!
|
||||
$TMUX -C attach -t cov <"$FIFO" >/dev/null 2>&1 &
|
||||
CC=$!
|
||||
|
||||
# Attach a real client too: an inner tmux running inside a pane of the second
|
||||
# server gets a genuine terminal, which populates the terminal-dependent client
|
||||
# variables (client_termname, cursor_shape, the I modifier, ...).
|
||||
$TMUX2 new-session -d -x 90 -y 30 "$TMUX attach -t cov" || exit 1
|
||||
sleep 1
|
||||
|
||||
# The real (terminal) client, identified by not being in control mode.
|
||||
RC=$($TMUX list-clients -F '#{client_control_mode} #{client_name}' |
|
||||
awk '$1==0 { print $2; exit }')
|
||||
# The control client.
|
||||
CLIENT=$($TMUX list-clients -F '#{client_control_mode} #{client_name}' |
|
||||
awk '$1==1 { print $2; exit }')
|
||||
[ -n "$RC" ] || fail "No real client attached."
|
||||
[ -n "$CLIENT" ] || fail "No control client attached."
|
||||
|
||||
# Expand every variable at once, with the real terminal client and a target
|
||||
# pane in context, and confirm the server survives. This runs every callback.
|
||||
FMT=""
|
||||
for n in $NAMES; do
|
||||
FMT="$FMT#{$n}"
|
||||
done
|
||||
$TMUX display-message -c "$RC" -t cov:win0.0 -p "$FMT" >/dev/null 2>&1
|
||||
assert_alive "expanding all variables together"
|
||||
|
||||
# Expand each variable on its own too, so a crash can be pinned to one name.
|
||||
for n in $NAMES; do
|
||||
$TMUX display-message -c "$RC" -t cov:win0.0 -p "#{$n}" >/dev/null 2>&1
|
||||
assert_alive "expanding #{$n}"
|
||||
done
|
||||
|
||||
# Deterministic checks on stable variables (targeting pane 0 of window 0).
|
||||
TGT="cov:win0.0"
|
||||
test_var session_name "cov" -t "$TGT"
|
||||
test_var window_name "win0" -t "$TGT"
|
||||
test_var window_index "0" -t "$TGT"
|
||||
test_var window_panes "2" -t "$TGT"
|
||||
test_var session_windows "2" -t "$TGT"
|
||||
test_var pane_index "0" -t "$TGT"
|
||||
test_var pane_in_mode "0" -t "$TGT"
|
||||
test_var pane_at_top "1" -t "$TGT"
|
||||
test_var pane_at_left "1" -t "$TGT"
|
||||
test_var last_window_index "1" -t "$TGT"
|
||||
test_var pid "$($TMUX display-message -p '#{pid}')" -t "$TGT"
|
||||
|
||||
# The grouped session is reported as such.
|
||||
test_var session_grouped "1" -t "cov:"
|
||||
test_var session_group_size "2" -t "cov:"
|
||||
|
||||
# list-buffers -F formats each paste buffer (this fills in the paste-buffer
|
||||
# format defaults).
|
||||
if [ "$($TMUX list-buffers -F '#{buffer_name}=#{buffer_sample}')" != \
|
||||
"buf0=somebuffer" ]; then
|
||||
fail "Unexpected list-buffers format output."
|
||||
fi
|
||||
|
||||
# Version reported by the variable matches tmux -V.
|
||||
VER=$($TMUX -V | sed 's/^tmux //')
|
||||
test_var version "$VER" -t "$TGT"
|
||||
|
||||
# Client variables from each kind of client.
|
||||
test_var client_name "$CLIENT" -c "$CLIENT"
|
||||
test_var client_control_mode "1" -c "$CLIENT"
|
||||
test_var client_control_mode "0" -c "$RC"
|
||||
test_var socket_path "$($TMUX display-message -p '#{socket_path}')" -c "$CLIENT"
|
||||
# The real client has a terminal, so termcap/feature/environ queries work.
|
||||
test_var "I/e:TERM" "$($TMUX display-message -c "$RC" -p '#{client_termname}')" \
|
||||
-c "$RC"
|
||||
# Termcap and feature queries against a real terminal return a boolean.
|
||||
case "$($TMUX display-message -c "$RC" -p '#{I/c:colors}')" in
|
||||
0|1) ;;
|
||||
*) fail "Unexpected #{I/c:colors} for real client." ;;
|
||||
esac
|
||||
case "$($TMUX display-message -c "$RC" -p '#{I/f:256}')" in
|
||||
0|1) ;;
|
||||
*) fail "Unexpected #{I/f:256} for real client." ;;
|
||||
esac
|
||||
|
||||
# Time variables through the pretty and relative modifiers: start_time is the
|
||||
# recent server start, exercising the "last 24 hours" and "just now" paths.
|
||||
[ -n "$($TMUX display-message -p '#{t/p:start_time}')" ] ||
|
||||
fail "Empty #{t/p:start_time}."
|
||||
[ -n "$($TMUX display-message -p '#{t/r:start_time}')" ] ||
|
||||
fail "Empty #{t/r:start_time}."
|
||||
|
||||
cleanup
|
||||
exit 0
|
||||
176
regress/input-common.inc
Normal file
176
regress/input-common.inc
Normal file
@@ -0,0 +1,176 @@
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
|
||||
TMP=$(mktemp)
|
||||
EXP=$(mktemp)
|
||||
trap 'rm -f "$TMP" "$EXP"; $TMUX kill-server 2>/dev/null' 0 1 15
|
||||
|
||||
exit_status=0
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "FAIL: $1"
|
||||
diff -u "$EXP" "$TMP"
|
||||
exit_status=1
|
||||
}
|
||||
|
||||
start_pane()
|
||||
{
|
||||
start_pane_hlimit "$1" "$2" "$3" "$4" 0
|
||||
}
|
||||
|
||||
start_pane_history()
|
||||
{
|
||||
start_pane_hlimit "$1" "$2" "$3" "$4" 2000
|
||||
}
|
||||
|
||||
start_pane_hlimit()
|
||||
{
|
||||
name=$1
|
||||
sx=$2
|
||||
sy=$3
|
||||
seq=$4
|
||||
hlimit=$5
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
sleep 0.1
|
||||
$TMUX new-session -d -x 1 -y 1 -s test-setup "sleep 2" || exit 1
|
||||
$TMUX set-option -g history-limit "$hlimit" || exit 1
|
||||
$TMUX new-session -d -x "$sx" -y "$sy" -s "$name" \
|
||||
"printf '$seq'; sleep 2" || exit 1
|
||||
$TMUX kill-session -t test-setup
|
||||
sleep 0.3
|
||||
}
|
||||
|
||||
start_cmd()
|
||||
{
|
||||
name=$1
|
||||
sx=$2
|
||||
sy=$3
|
||||
cmd=$4
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
sleep 0.1
|
||||
$TMUX new-session -d -x "$sx" -y "$sy" -s "$name" "$cmd" || exit 1
|
||||
sleep 0.3
|
||||
}
|
||||
|
||||
normalize_capture()
|
||||
{
|
||||
sed 's/[ ]*$//' |
|
||||
awk '{ line[NR] = $0; if ($0 != "") last = NR }
|
||||
END { for (i = 1; i <= last; i++) print line[i] }'
|
||||
}
|
||||
|
||||
capture_grid()
|
||||
{
|
||||
$TMUX capture-pane -pN -t "$1:" -S 0 -E - | normalize_capture
|
||||
}
|
||||
|
||||
check_capture()
|
||||
{
|
||||
name=$1
|
||||
expected=$2
|
||||
|
||||
capture_grid "$name" >"$TMP"
|
||||
printf "%s\n" "$expected" >"$EXP"
|
||||
cmp "$TMP" "$EXP" || fail "$name"
|
||||
}
|
||||
|
||||
check_cursor()
|
||||
{
|
||||
name=$1
|
||||
expected=$2
|
||||
|
||||
actual=$($TMUX display-message -p -t "$name:" '#{cursor_x},#{cursor_y}')
|
||||
if [ "$actual" != "$expected" ]; then
|
||||
printf "%s\n" "$expected" >"$EXP"
|
||||
printf "%s\n" "$actual" >"$TMP"
|
||||
fail "$name cursor"
|
||||
fi
|
||||
}
|
||||
|
||||
check_flags()
|
||||
{
|
||||
name=$1
|
||||
expected=$2
|
||||
|
||||
$TMUX capture-pane -pNF -t "$name:" -S 0 -E - |
|
||||
normalize_capture |
|
||||
awk '$0 != "-"' >"$TMP"
|
||||
printf "%s\n" "$expected" >"$EXP"
|
||||
cmp "$TMP" "$EXP" || fail "$name flags"
|
||||
}
|
||||
|
||||
check_joined()
|
||||
{
|
||||
name=$1
|
||||
expected=$2
|
||||
|
||||
$TMUX capture-pane -pNJ -t "$name:" -S 0 -E - |
|
||||
normalize_capture >"$TMP"
|
||||
printf "%s\n" "$expected" >"$EXP"
|
||||
cmp "$TMP" "$EXP" || fail "$name joined"
|
||||
}
|
||||
|
||||
capture_raw()
|
||||
{
|
||||
$TMUX capture-pane -pR -t "$1:"
|
||||
}
|
||||
|
||||
capture_raw_used()
|
||||
{
|
||||
capture_raw "$1" |
|
||||
awk '/^(G| L)/ || /^ C/ && $3 !~ /^data=\(1,1, \)$/'
|
||||
}
|
||||
|
||||
check_raw()
|
||||
{
|
||||
name=$1
|
||||
expected=$2
|
||||
|
||||
capture_raw "$name" >"$TMP"
|
||||
printf "%s\n" "$expected" >"$EXP"
|
||||
cmp "$TMP" "$EXP" || fail "$name raw"
|
||||
}
|
||||
|
||||
check_raw_used()
|
||||
{
|
||||
name=$1
|
||||
expected=$2
|
||||
|
||||
capture_raw_used "$name" >"$TMP"
|
||||
printf "%s\n" "$expected" >"$EXP"
|
||||
cmp "$TMP" "$EXP" || fail "$name raw used"
|
||||
}
|
||||
|
||||
check_raw_has()
|
||||
{
|
||||
name=$1
|
||||
shift
|
||||
|
||||
capture_raw "$name" >"$TMP"
|
||||
for expected in "$@"; do
|
||||
if ! grep -Fqx "$expected" "$TMP"; then
|
||||
printf "%s\n" "$expected" >"$EXP"
|
||||
fail "$name raw missing"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
check_raw_matches()
|
||||
{
|
||||
name=$1
|
||||
shift
|
||||
|
||||
capture_raw "$name" >"$TMP"
|
||||
for expected in "$@"; do
|
||||
if ! grep -Eq "$expected" "$TMP"; then
|
||||
printf "%s\n" "$expected" >"$EXP"
|
||||
fail "$name raw missing"
|
||||
fi
|
||||
done
|
||||
}
|
||||
39
regress/input-cursor.sh
Normal file
39
regress/input-cursor.sh
Normal file
@@ -0,0 +1,39 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane cursor 10 3 'ABCDE\r\033[2Cxy\033[1D!\033[4GZ\n'
|
||||
check_capture cursor 'ABxZE'
|
||||
check_cursor cursor '0,1'
|
||||
|
||||
start_pane saverc 10 3 'abc\0337\033[2;5HXY\0338Z\n'
|
||||
check_capture saverc 'abcZ
|
||||
XY'
|
||||
check_cursor saverc '0,1'
|
||||
|
||||
start_pane hvp 10 4 'A\033[3dB\033[5GC\033[2;2fD\n'
|
||||
check_capture hvp 'A
|
||||
D
|
||||
B C'
|
||||
check_cursor hvp '0,2'
|
||||
|
||||
start_pane cursorlines 8 4 'A\033[2BB\033[1FC\033[1AD\n'
|
||||
check_capture cursorlines 'AD
|
||||
C
|
||||
B'
|
||||
check_cursor cursorlines '0,1'
|
||||
|
||||
start_pane tabs 12 3 'a\tb\n'
|
||||
check_capture tabs 'a b'
|
||||
check_cursor tabs '0,1'
|
||||
|
||||
start_pane tabclear 12 3 '\033H\ta\033[3g\r\tb\n'
|
||||
check_capture tabclear ' a b'
|
||||
check_cursor tabclear '0,1'
|
||||
|
||||
start_pane cbt 16 3 '0123456789\r\033[10C\033[Zx\n'
|
||||
check_capture cbt '01234567x9'
|
||||
check_cursor cbt '0,1'
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit $exit_status
|
||||
54
regress/input-edit.sh
Normal file
54
regress/input-edit.sh
Normal file
@@ -0,0 +1,54 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane dch 10 3 'abcdef\r\033[3C\033[2PXY\n'
|
||||
check_capture dch 'abcXY'
|
||||
|
||||
start_pane ich 10 3 'abcdef\r\033[3C\033[2@XY\n'
|
||||
check_capture ich 'abcXYdef'
|
||||
|
||||
start_pane erase 10 3 'abcdef\r\033[3C\033[KZ\n'
|
||||
check_capture erase 'abcZ'
|
||||
|
||||
start_pane el1 10 3 'abcdef\r\033[3C\033[1KZ\n'
|
||||
check_capture el1 ' Zef'
|
||||
|
||||
start_pane ech 10 3 'abcdef\r\033[3C\033[2XX\n'
|
||||
check_capture ech 'abcX f'
|
||||
|
||||
start_pane ed 10 3 'one\ntwo\033[2;2H\033[JX\n'
|
||||
check_capture ed 'one
|
||||
tX'
|
||||
|
||||
start_pane ed1 10 3 'one\ntwo\033[2;2H\033[1JX\n'
|
||||
check_capture ed1 '
|
||||
Xo'
|
||||
|
||||
start_pane ed2 10 3 'one\ntwo\033[2JZ\n'
|
||||
check_capture ed2 '
|
||||
Z'
|
||||
|
||||
start_pane il 8 4 '111\n222\n333\033[2;1H\033[LAAA\n'
|
||||
check_capture il '111
|
||||
AAA
|
||||
222
|
||||
333'
|
||||
|
||||
start_pane dl 8 4 '111\n222\n333\033[2;1H\033[MZZZ\n'
|
||||
check_capture dl '111
|
||||
ZZZ'
|
||||
|
||||
start_pane irm 10 3 'abcdef\r\033[4h\033[3CXY\033[4lZ\n'
|
||||
check_capture irm 'abcXYZef'
|
||||
|
||||
start_pane rep 10 3 'A\033[4bB\n'
|
||||
check_capture rep 'AAAAAB'
|
||||
|
||||
start_pane decaln 6 3 '\033#8'
|
||||
check_capture decaln 'EEEEEE
|
||||
EEEEEE
|
||||
EEEEEE'
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit $exit_status
|
||||
42
regress/input-malformed.sh
Normal file
42
regress/input-malformed.sh
Normal file
@@ -0,0 +1,42 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_cmd csi-param-discard 8 3 \
|
||||
"perl -e 'print qq{\e[}, q{1} x 80, qq{\030OK}'; sleep 2"
|
||||
check_capture csi-param-discard 'OK'
|
||||
|
||||
start_cmd csi-interm-discard 8 3 \
|
||||
"perl -e 'print qq{\e[ \030OK}'; sleep 2"
|
||||
check_capture csi-interm-discard 'OK'
|
||||
|
||||
start_cmd osc-discard 8 3 \
|
||||
"perl -e 'print qq{\e]2;}, q{x} x 1100000, qq{\e\\\\OK}'; sleep 2"
|
||||
check_capture osc-discard 'OK'
|
||||
|
||||
start_cmd apc-discard 8 3 \
|
||||
"perl -e 'print qq{\e_}, q{x} x 1100000, qq{\e\\\\OK}'; sleep 2"
|
||||
check_capture apc-discard 'OK'
|
||||
|
||||
start_pane unknown-csi 8 3 '\033[?9999zOK'
|
||||
check_capture unknown-csi 'OK'
|
||||
|
||||
start_pane unknown-osc 8 3 '\033]999;bad\aOK'
|
||||
check_capture unknown-osc 'OK'
|
||||
|
||||
start_pane malformed-osc 8 3 '\033]8;id=a:id=b;http://bad\aX\033]8;id=no-separator\aY\033]9;4;5;200\a\033]9;4;z\a\033]10;notacolour\a\033]11;notacolour\a\033]12;notacolour\a\033]4;999;red\a\033]104;999\a\033]52bad\a\033]52;c;@@@\aOK'
|
||||
check_capture malformed-osc 'XYOK'
|
||||
check_raw_matches malformed-osc \
|
||||
'C 0,0 data=\(1,1,X\).* link=NONE linkid=NONE' \
|
||||
'C 0,1 data=\(1,1,Y\).* link=NONE linkid=NONE' \
|
||||
'C 0,2 data=\(1,1,O\).* link=NONE linkid=NONE' \
|
||||
'C 0,3 data=\(1,1,K\).* link=NONE linkid=NONE'
|
||||
|
||||
start_pane malformed-dcs 8 3 '\033P$qBAD\033\\OK'
|
||||
check_capture malformed-dcs 'OK^[P0$r
|
||||
^[\'
|
||||
|
||||
start_pane malformed-utf8 8 3 '\360\200\200\200A\355\240\200B'
|
||||
check_capture malformed-utf8 '<27>A<EFBFBD>B'
|
||||
|
||||
exit $exit_status
|
||||
15
regress/input-modes.sh
Normal file
15
regress/input-modes.sh
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane alternate 10 3 'MAIN\033[?1049hALT\033[?1049lZ\n'
|
||||
check_capture alternate 'MAINZ'
|
||||
|
||||
start_pane osc133 10 4 '\033]133;A\007prompt\n\033]133;C\007output\n'
|
||||
check_capture osc133 'prompt
|
||||
output'
|
||||
check_flags osc133 'P prompt
|
||||
O output'
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit $exit_status
|
||||
37
regress/input-osc.sh
Normal file
37
regress/input-osc.sh
Normal file
@@ -0,0 +1,37 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane hyperlink 20 3 '\033]8;id=1;https://example.com\033\\link\033]8;;\033\\ plain\n'
|
||||
check_capture hyperlink 'link plain'
|
||||
check_flags hyperlink 'HX link plain'
|
||||
$TMUX capture-pane -peH -t hyperlink: -S 0 -E - >/dev/null || exit 1
|
||||
|
||||
start_pane palette 20 3 '\033]4;1;rgb:11/22/33;2;red\007\033]104;1;2\007X\n'
|
||||
check_capture palette 'X'
|
||||
|
||||
start_pane osc-colours 20 3 '\033]10;rgb:11/22/33\007\033]11;rgb:44/55/66\007\033]12;rgb:77/88/99\007\033]110\007\033]111\007\033]112\007X\n'
|
||||
check_capture osc-colours 'X'
|
||||
|
||||
start_pane progress 20 3 '\033]9;4;1;25\007\033]9;4;0\007\033]9;4;5;200\007X\n'
|
||||
check_capture progress 'X'
|
||||
|
||||
start_pane rename 20 3 '\033krenamed\033\\X\n'
|
||||
check_capture rename 'X'
|
||||
|
||||
start_pane apc-title 20 3 '\033_test-title\033\\X\n'
|
||||
check_capture apc-title 'X'
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
sleep 0.1
|
||||
$TMUX new-session -d -x 20 -y 3 -s osc52 "sleep 2" || exit 1
|
||||
$TMUX set-option -s set-clipboard on || exit 1
|
||||
$TMUX respawn-pane -k -t osc52: \
|
||||
"printf '\033]52;c;SGVsbG8=\007'; sleep 2" || exit 1
|
||||
sleep 0.3
|
||||
$TMUX save-buffer -b buffer0 - >"$TMP"
|
||||
printf "Hello" >"$EXP"
|
||||
cmp "$TMP" "$EXP" || fail "osc52"
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit $exit_status
|
||||
84
regress/input-raw-controls.sh
Normal file
84
regress/input-raw-controls.sh
Normal file
@@ -0,0 +1,84 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane bs 8 3 'abc\bd'
|
||||
check_capture bs 'abd'
|
||||
check_raw_matches bs \
|
||||
'C 0,0 data=\(1,1,a\) flags=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1,b\) flags=NONE\[0\]' \
|
||||
'C 0,2 data=\(1,1,d\) flags=NONE\[0\]'
|
||||
|
||||
start_pane nel 8 3 'A\033EB'
|
||||
check_capture nel 'A
|
||||
B'
|
||||
check_raw_matches nel \
|
||||
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
|
||||
'C 1,0 data=\(1,1,B\) flags=NONE\[0\]'
|
||||
|
||||
start_pane tabstops 16 3 '\033H1\t2\033[3g\r\t3'
|
||||
check_raw_matches tabstops \
|
||||
'C 0,0 data=\(1,1,1\) flags=NONE\[0\]' \
|
||||
'C 0,8 data=\(1,1,2\) flags=NONE\[0\]' \
|
||||
'C 0,15 data=\(1,1,3\) flags=NONE\[0\]'
|
||||
|
||||
start_pane decaln 6 3 '\033#8'
|
||||
check_raw_matches decaln \
|
||||
'C 0,0 data=\(1,1,E\) flags=NONE\[0\]' \
|
||||
'C 1,5 data=\(1,1,E\) flags=NONE\[0\]' \
|
||||
'C 2,5 data=\(1,1,E\) flags=NONE\[0\]'
|
||||
|
||||
start_pane charset 8 3 '\033(0qxl\033(BZ'
|
||||
check_raw_matches charset \
|
||||
'C 0,0 data=\(1,1,q\) flags=NONE\[0\] attr=CHARSET\[[0-9a-f]+\]' \
|
||||
'C 0,1 data=\(1,1,x\) flags=NONE\[0\] attr=CHARSET\[[0-9a-f]+\]' \
|
||||
'C 0,2 data=\(1,1,l\) flags=NONE\[0\] attr=CHARSET\[[0-9a-f]+\]' \
|
||||
'C 0,3 data=\(1,1,Z\) flags=NONE\[0\] attr=NONE\[0\]'
|
||||
|
||||
start_pane g1charset 8 3 '\033)0\016q\017Z'
|
||||
check_raw_matches g1charset \
|
||||
'C 0,0 data=\(1,1,q\) flags=NONE\[0\] attr=CHARSET\[[0-9a-f]+\]' \
|
||||
'C 0,1 data=\(1,1,Z\) flags=NONE\[0\] attr=NONE\[0\]'
|
||||
|
||||
start_pane csisave 8 3 '\033[3;3HS\033[s\033[1;1HA\033[uR'
|
||||
check_raw_matches csisave \
|
||||
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
|
||||
'C 2,2 data=\(1,1,S\) flags=NONE\[0\]' \
|
||||
'C 2,3 data=\(1,1,R\) flags=NONE\[0\]'
|
||||
|
||||
start_pane alternate 8 3 'main\033[?1049halt\033[?1049lback'
|
||||
check_capture alternate 'mainback'
|
||||
check_raw_matches alternate \
|
||||
'C 0,0 data=\(1,1,m\) flags=NONE\[0\]' \
|
||||
'C 0,4 data=\(1,1,b\) flags=NONE\[0\]' \
|
||||
'C 0,7 data=\(1,1,k\) flags=NONE\[0\]'
|
||||
|
||||
start_pane sync 8 3 '\033P=1signored\033\\A\033P=2s\033\\B'
|
||||
check_raw_matches sync \
|
||||
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1,B\) flags=NONE\[0\]'
|
||||
|
||||
start_pane private 8 3 '\033[?25lA\033[?25hB\033[?1000hC\033[?1000lD'
|
||||
check_raw_matches private \
|
||||
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1,B\) flags=NONE\[0\]' \
|
||||
'C 0,2 data=\(1,1,C\) flags=NONE\[0\]' \
|
||||
'C 0,3 data=\(1,1,D\) flags=NONE\[0\]'
|
||||
|
||||
start_pane ris 8 3 'A\033cB'
|
||||
check_capture ris 'B'
|
||||
check_raw_matches ris \
|
||||
'C 0,0 data=\(1,1,B\) flags=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1, \) flags=CLEARED\[[0-9a-f]+\]'
|
||||
|
||||
start_pane keypad 8 3 '\033=A\033>B'
|
||||
check_raw_matches keypad \
|
||||
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1,B\) flags=NONE\[0\]'
|
||||
|
||||
start_pane cursorstyle 8 3 '\033[5 qA\033[0 qB'
|
||||
check_raw_matches cursorstyle \
|
||||
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1,B\) flags=NONE\[0\]'
|
||||
|
||||
exit $exit_status
|
||||
33
regress/input-raw-cursor.sh
Normal file
33
regress/input-raw-cursor.sh
Normal file
@@ -0,0 +1,33 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane absolute 8 4 'A\033[3;5HB\033[2GC\033[2D!'
|
||||
check_capture absolute 'A
|
||||
|
||||
!C B'
|
||||
check_cursor absolute '1,2'
|
||||
check_raw_matches absolute \
|
||||
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
|
||||
'C 2,4 data=\(1,1,B\) flags=NONE\[0\]' \
|
||||
'C 2,0 data=\(1,1,!\) flags=NONE\[0\]' \
|
||||
'C 2,1 data=\(1,1,C\) flags=NONE\[0\]'
|
||||
|
||||
start_pane savecursor 8 4 '\033[4;4HS\0337\033[1;1HA\0338R'
|
||||
check_capture savecursor 'A
|
||||
|
||||
|
||||
SR'
|
||||
check_cursor savecursor '5,3'
|
||||
check_raw_matches savecursor \
|
||||
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
|
||||
'C 3,3 data=\(1,1,S\) flags=NONE\[0\]' \
|
||||
'C 3,4 data=\(1,1,R\) flags=NONE\[0\]'
|
||||
|
||||
start_pane origin 8 5 '\033[2;4r\033[?6h\033[1;1HO\033[3;1HP\033[?6lQ'
|
||||
check_raw_matches origin \
|
||||
'C 1,0 data=\(1,1,O\) flags=NONE\[0\]' \
|
||||
'C 3,0 data=\(1,1,P\) flags=NONE\[0\]' \
|
||||
'C 0,0 data=\(1,1,Q\) flags=NONE\[0\]'
|
||||
|
||||
exit $exit_status
|
||||
47
regress/input-raw-edit.sh
Normal file
47
regress/input-raw-edit.sh
Normal file
@@ -0,0 +1,47 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane erasechars 8 3 'ABCDEFGH\r\033[3C\033[2X'
|
||||
check_capture erasechars 'ABC FGH'
|
||||
check_raw_matches erasechars \
|
||||
'C 0,3 data=\(1,1, \) flags=CLEARED\[[0-9a-f]+\] attr=NONE\[0\]' \
|
||||
'C 0,4 data=\(1,1, \) flags=CLEARED\[[0-9a-f]+\] attr=NONE\[0\]' \
|
||||
'C 0,5 data=\(1,1,F\) flags=NONE\[0\]'
|
||||
|
||||
start_pane deletechars 8 3 'ABCDEFGH\r\033[3C\033[3P'
|
||||
check_capture deletechars 'ABCGH'
|
||||
check_raw_matches deletechars \
|
||||
'C 0,3 data=\(1,1,G\) flags=NONE\[0\]' \
|
||||
'C 0,4 data=\(1,1,H\) flags=NONE\[0\]' \
|
||||
'C 0,5 data=\(1,1, \) flags=CLEARED\[[0-9a-f]+\] attr=NONE\[0\]'
|
||||
|
||||
start_pane insertchars 8 3 'ABCDEF\r\033[3C\033[2@xy'
|
||||
check_capture insertchars 'ABCxyDEF'
|
||||
check_raw_matches insertchars \
|
||||
'C 0,3 data=\(1,1,x\) flags=NONE\[0\]' \
|
||||
'C 0,4 data=\(1,1,y\) flags=NONE\[0\]' \
|
||||
'C 0,5 data=\(1,1,D\) flags=NONE\[0\]'
|
||||
|
||||
start_pane eraseline 8 3 'ABCDEFGH\r\033[4C\033[K'
|
||||
check_capture eraseline 'ABCD'
|
||||
check_raw_matches eraseline \
|
||||
'C 0,4 data=\(1,1, \) flags=CLEARED\[[0-9a-f]+\] attr=NONE\[0\]' \
|
||||
'C 0,7 data=\(1,1, \) flags=CLEARED\[[0-9a-f]+\] attr=NONE\[0\]'
|
||||
|
||||
start_pane erasescreen 8 3 '1111111\033[2;1H2222222\033[H\033[JZ'
|
||||
check_capture erasescreen 'Z'
|
||||
check_raw_matches erasescreen \
|
||||
'^G 8x3 \(0/0\)$' \
|
||||
'C [0-9]+,0 data=\(1,1,Z\) flags=NONE\[0\]' \
|
||||
'C [0-9]+,1 data=\(1,1, \) flags=CLEARED\[[0-9a-f]+\] attr=NONE\[0\]'
|
||||
|
||||
start_pane tabs 12 3 'A\tB\033[2g\r\033[IC'
|
||||
check_raw_matches tabs \
|
||||
'L 0 \(0\) flags=EXTENDED\[[0-9a-f]+\]' \
|
||||
'C 0,1 data=\(7,7, \) flags=TAB\[[0-9a-f]+\]' \
|
||||
'C 0,2 data=\(1,1,!\) flags=PADDING\[[0-9a-f]+\]' \
|
||||
'C 0,8 data=\(1,1,B\) flags=NONE\[0\]' \
|
||||
'C 0,0 data=\(1,1,C\) flags=NONE\[0\]'
|
||||
|
||||
exit $exit_status
|
||||
23
regress/input-raw-history.sh
Normal file
23
regress/input-raw-history.sh
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane_hlimit trim 6 3 'one\ntwo\nthree\nfour\nfive\nsix' 2
|
||||
check_raw_matches trim \
|
||||
'^G 6x3 \(2/2\)$' \
|
||||
'L 0 \(-\) flags=NONE\[0\]' \
|
||||
'L 1 \(-\) flags=NONE\[0\]'
|
||||
|
||||
$TMUX clear-history -t trim:
|
||||
check_raw_matches trim \
|
||||
'^G 6x3 \(0/2\)$' \
|
||||
'C 0,0 data=\(1,1,f\) flags=NONE\[0\]' \
|
||||
'C 2,0 data=\(1,1,s\) flags=NONE\[0\]'
|
||||
|
||||
start_pane_hlimit edhistory 6 3 'one\ntwo\nthree\033[H\033[JZ' 5
|
||||
check_raw_matches edhistory \
|
||||
'^G 6x3 \([1-9][0-9]*/5\)$' \
|
||||
'L [0-9]+ \(-\) flags=NONE\[0\]' \
|
||||
'C [0-9]+,0 data=\(1,1,Z\) flags=NONE\[0\]'
|
||||
|
||||
exit $exit_status
|
||||
21
regress/input-raw-reflow.sh
Normal file
21
regress/input-raw-reflow.sh
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane_history reflow 8 4 'abcdefgh\nijklmnop\nqrstuvwx\nyz'
|
||||
$TMUX resize-window -t reflow: -x 4 -y 4
|
||||
sleep 0.2
|
||||
check_raw_matches reflow \
|
||||
'^G 4x4 \([0-9]+/2000\)$' \
|
||||
'L [0-9]+ \([0-9-]+\) flags=WRAPPED\[[0-9a-f]+\]' \
|
||||
'C [0-9]+,0 data=\(1,1,a\) flags=NONE\[0\]' \
|
||||
'C [0-9]+,3 data=\(1,1,d\) flags=NONE\[0\]'
|
||||
|
||||
$TMUX resize-window -t reflow: -x 12 -y 4
|
||||
sleep 0.2
|
||||
check_raw_matches reflow \
|
||||
'^G 12x4 \([0-9]+/2000\)$' \
|
||||
'C [0-9]+,0 data=\(1,1,a\) flags=NONE\[0\]' \
|
||||
'C [0-9]+,7 data=\(1,1,h\) flags=NONE\[0\]'
|
||||
|
||||
exit $exit_status
|
||||
39
regress/input-raw-scroll.sh
Normal file
39
regress/input-raw-scroll.sh
Normal file
@@ -0,0 +1,39 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane_history history 6 3 'one\ntwo\nthree\nfour\nfive'
|
||||
check_raw_matches history \
|
||||
'^G 6x3 \([1-9][0-9]*/2000\)$' \
|
||||
'L [0-9]+ \(-\) flags=NONE\[0\]' \
|
||||
'C [0-9]+,0 data=\(1,1,o\) flags=NONE\[0\]'
|
||||
|
||||
start_pane_history index 6 4 'A\nB\nC\033[2;3r\033[2;1HX\033D\033DY'
|
||||
check_raw_matches index \
|
||||
'C [0-9]+,0 data=\(1,1,X\) flags=NONE\[0\]' \
|
||||
'C [0-9]+,0 data=\(1,1,A\) flags=NONE\[0\]' \
|
||||
'C [0-9]+,1 data=\(1,1,Y\) flags=NONE\[0\]'
|
||||
|
||||
start_pane reverse 6 4 'A\nB\nC\033[2;3r\033[2;1H\033MY'
|
||||
check_raw_matches reverse \
|
||||
'C 1,0 data=\(1,1,Y\) flags=NONE\[0\]' \
|
||||
'C 2,0 data=\(1,1,B\) flags=NONE\[0\]'
|
||||
|
||||
start_pane insertline 6 4 'A\nB\nC\033[2;3r\033[2;1H\033[LY'
|
||||
check_raw_matches insertline \
|
||||
'C 1,0 data=\(1,1,Y\) flags=NONE\[0\]' \
|
||||
'C 2,0 data=\(1,1,B\) flags=NONE\[0\]'
|
||||
|
||||
start_pane deleteline 6 4 'A\nB\nC\033[2;3r\033[2;1H\033[MY'
|
||||
check_raw_matches deleteline \
|
||||
'C 1,0 data=\(1,1,Y\) flags=NONE\[0\]' \
|
||||
'C 2,0 data=\(1,1, \) flags=NONE\[0\]'
|
||||
|
||||
start_pane region-edge 6 4 'top\033[2;3rmid\033[2;1H\033D\033Mbot'
|
||||
check_raw_matches region-edge \
|
||||
'C 0,0 data=\(1,1,m\) flags=NONE\[0\]' \
|
||||
'C 1,0 data=\(1,1,b\) flags=NONE\[0\]' \
|
||||
'C 2,0 data=\(1,1, \) flags=NONE\[0\]' \
|
||||
'C 3,0 data=\(1,1, \) flags=NONE\[0\]'
|
||||
|
||||
exit $exit_status
|
||||
39
regress/input-raw-sgr.sh
Normal file
39
regress/input-raw-sgr.sh
Normal file
@@ -0,0 +1,39 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane attrs 16 3 '\033[1mB\033[2mD\033[3mI\033[4mU\033[5mK\033[7mR\033[8mH\033[9mS\033[53mO'
|
||||
check_raw_matches attrs \
|
||||
'C 0,0 data=\(1,1,B\) flags=NONE\[0\] attr=BRIGHT\[[0-9a-f]+\]' \
|
||||
'C 0,1 data=\(1,1,D\) flags=NONE\[0\] attr=BRIGHT,DIM\[[0-9a-f]+\]' \
|
||||
'C 0,2 data=\(1,1,I\) flags=NONE\[0\] attr=BRIGHT,DIM,ITALICS\[[0-9a-f]+\]' \
|
||||
'C 0,3 data=\(1,1,U\) flags=NONE\[0\] attr=BRIGHT,DIM,UNDERSCORE,ITALICS\[[0-9a-f]+\]' \
|
||||
'C 0,5 data=\(1,1,R\) flags=NONE\[0\] attr=BRIGHT,DIM,UNDERSCORE,BLINK,REVERSE,ITALICS\[[0-9a-f]+\]' \
|
||||
'C 0,7 data=\(1,1,S\) flags=NONE\[0\] attr=BRIGHT,DIM,UNDERSCORE,BLINK,REVERSE,HIDDEN,ITALICS,STRIKETHROUGH\[[0-9a-f]+\]' \
|
||||
'C 0,8 data=\(1,1,O\) flags=NONE\[0\] attr=BRIGHT,DIM,UNDERSCORE,BLINK,REVERSE,HIDDEN,ITALICS,STRIKETHROUGH,OVERLINE\[[0-9a-f]+\]'
|
||||
|
||||
start_pane colours 12 3 '\033[38;5;196;48;5;17mX\033[58;5;45mY'
|
||||
check_raw_matches colours \
|
||||
'C 0,0 data=\(1,1,X\) flags=FG256,BG256\[[0-9a-f]+\] attr=NONE\[0\] fg=colour196\[10000c4\] bg=colour17\[1000011\]' \
|
||||
'C 0,1 data=\(1,1,Y\) flags=FG256,BG256\[[0-9a-f]+\] attr=NONE\[0\] fg=colour196\[10000c4\] bg=colour17\[1000011\] us=colour45\[100002d\]'
|
||||
|
||||
start_pane bce 8 3 '\033[44mA\033[K'
|
||||
check_raw_matches bce \
|
||||
'C 0,0 data=\(1,1,A\) flags=NONE\[0\] attr=NONE\[0\].* bg=blue\[4\]' \
|
||||
'C 0,1 data=\(1,1, \) flags=CLEARED\[[0-9a-f]+\] attr=NONE\[0\].* bg=blue\[4\]' \
|
||||
'C 0,7 data=\(1,1, \) flags=CLEARED\[[0-9a-f]+\] attr=NONE\[0\].* bg=blue\[4\]'
|
||||
|
||||
start_pane underlines 12 3 '\033[4:2m2\033[4:3m3\033[4:4m4\033[4:5m5'
|
||||
check_raw_matches underlines \
|
||||
'C 0,0 data=\(1,1,2\) flags=NONE\[0\] attr=UNDERSCORE_2\[[0-9a-f]+\]' \
|
||||
'C 0,1 data=\(1,1,3\) flags=NONE\[0\] attr=UNDERSCORE_3\[[0-9a-f]+\]' \
|
||||
'C 0,2 data=\(1,1,4\) flags=NONE\[0\] attr=UNDERSCORE_4\[[0-9a-f]+\]' \
|
||||
'C 0,3 data=\(1,1,5\) flags=NONE\[0\] attr=UNDERSCORE_5\[[0-9a-f]+\]'
|
||||
|
||||
start_pane hyperlink 12 3 '\033]8;id=id1;https://example.com/a\033\\A\033]8;;\033\\B'
|
||||
check_raw_matches hyperlink \
|
||||
'L 0 \(0\) flags=EXTENDED,HYPERLINK\[[0-9a-f]+\]' \
|
||||
'C 0,0 data=\(1,1,A\) flags=NONE\[0\].* link=https://example.com/a linkid=id1' \
|
||||
'C 0,1 data=\(1,1,B\) flags=NONE\[0\].* link=NONE linkid=NONE'
|
||||
|
||||
exit $exit_status
|
||||
82
regress/input-raw-unicode.sh
Normal file
82
regress/input-raw-unicode.sh
Normal file
@@ -0,0 +1,82 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane wide 8 3 'A\343\201\202B'
|
||||
check_capture wide 'AあB'
|
||||
check_raw_matches wide \
|
||||
'L 0 \(0\) flags=EXTENDED\[[0-9a-f]+\]' \
|
||||
'C 0,1 data=\(2,3,あ\) flags=NONE\[0\] attr=NONE\[0\]' \
|
||||
'C 0,2 data=\(1,1,!\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \
|
||||
'C 0,3 data=\(1,1,B\) flags=NONE\[0\]'
|
||||
|
||||
start_pane combining 8 3 'e\314\201x'
|
||||
check_raw_matches combining \
|
||||
'L 0 \(0\) flags=EXTENDED\[[0-9a-f]+\]' \
|
||||
'C 0,0 data=\(1,[0-9]+,.*\) flags=NONE\[0\] attr=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1,x\) flags=NONE\[0\]'
|
||||
|
||||
start_pane emoji 10 3 '\360\237\230\200Z'
|
||||
check_raw_matches emoji \
|
||||
'L 0 \(0\) flags=EXTENDED\[[0-9a-f]+\]' \
|
||||
'C 0,0 data=\(2,4,😀\) flags=NONE\[0\] attr=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1,!\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \
|
||||
'C 0,2 data=\(1,1,Z\) flags=NONE\[0\]'
|
||||
|
||||
start_pane flag 10 3 '\360\237\207\254\360\237\207\247!'
|
||||
check_raw_matches flag \
|
||||
'L 0 \(0\) flags=EXTENDED\[[0-9a-f]+\]' \
|
||||
'C 0,0 data=\(2,8,🇬🇧\) flags=NONE\[0\] attr=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1,!\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \
|
||||
'C 0,2 data=\(1,1,!\) flags=NONE\[0\]'
|
||||
|
||||
start_pane variation 10 3 '*\357\270\217!'
|
||||
check_raw_matches variation \
|
||||
'L 0 \(0\) flags=EXTENDED\[[0-9a-f]+\]' \
|
||||
'C 0,0 data=\(2,[0-9]+,.*\) flags=NONE\[0\] attr=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1,!\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \
|
||||
'C 0,2 data=\(1,1,!\) flags=NONE\[0\]'
|
||||
|
||||
start_pane invalid 10 3 '\377A'
|
||||
check_raw_matches invalid \
|
||||
'C 0,0 data=\(1,3,.*\) flags=NONE\[0\] attr=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1,A\) flags=NONE\[0\]'
|
||||
|
||||
start_pane trunc2 10 3 '\303A'
|
||||
check_raw_matches trunc2 \
|
||||
'C 0,0 data=\(1,3,.*\) flags=NONE\[0\] attr=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1,A\) flags=NONE\[0\]'
|
||||
|
||||
start_pane trunc3 10 3 '\342\202A'
|
||||
check_raw_matches trunc3 \
|
||||
'C 0,0 data=\(1,3,.*\) flags=NONE\[0\] attr=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1,A\) flags=NONE\[0\]'
|
||||
|
||||
start_pane overwrite-wide-left 10 3 'A\343\201\202B\r\033[1CX'
|
||||
check_raw_matches overwrite-wide-left \
|
||||
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1,X\) flags=NONE\[0\]' \
|
||||
'C 0,2 data=\(1,1, \) flags=NONE\[0\]' \
|
||||
'C 0,3 data=\(1,1,B\) flags=NONE\[0\]'
|
||||
|
||||
start_pane overwrite-wide-pad 10 3 'A\343\201\202B\r\033[2CX'
|
||||
check_raw_matches overwrite-wide-pad \
|
||||
'C 0,0 data=\(1,1,A\) flags=NONE\[0\]' \
|
||||
'C 0,1 data=\(1,1, \) flags=NONE\[0\]' \
|
||||
'C 0,2 data=\(1,1,X\) flags=NONE\[0\]' \
|
||||
'C 0,3 data=\(1,1,B\) flags=NONE\[0\]'
|
||||
|
||||
start_pane overwrite-wide-with-wide 10 3 'A\343\201\202B\r\033[1C\347\225\214'
|
||||
check_raw_matches overwrite-wide-with-wide \
|
||||
'C 0,1 data=\(2,3,界\) flags=NONE\[0\] attr=NONE\[0\]' \
|
||||
'C 0,2 data=\(1,1,!\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \
|
||||
'C 0,3 data=\(1,1,B\) flags=NONE\[0\]'
|
||||
|
||||
start_pane wide-right-edge 4 3 'ABC\343\201\202Z'
|
||||
check_raw_matches wide-right-edge \
|
||||
'L 0 \(0\) flags=WRAPPED\[[0-9a-f]+\]' \
|
||||
'C 1,0 data=\(2,3,あ\) flags=NONE\[0\] attr=NONE\[0\]' \
|
||||
'C 1,1 data=\(1,1,!\) flags=PADDING\[[0-9a-f]+\] attr=NONE\[0\]' \
|
||||
'C 1,2 data=\(1,1,Z\) flags=NONE\[0\]'
|
||||
|
||||
exit $exit_status
|
||||
28
regress/input-raw-wrap.sh
Normal file
28
regress/input-raw-wrap.sh
Normal file
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane wrap 5 3 'ABCDEZ'
|
||||
check_capture wrap 'ABCDE
|
||||
Z'
|
||||
check_raw_matches wrap \
|
||||
'^G 5x3 \(0/0\)$' \
|
||||
'L 0 \(0\) flags=WRAPPED\[[0-9a-f]+\]' \
|
||||
'C 0,4 data=\(1,1,E\) flags=NONE\[0\]' \
|
||||
'C 1,0 data=\(1,1,Z\) flags=NONE\[0\]'
|
||||
|
||||
start_pane nowrap 5 3 '\033[?7lABCDEZ'
|
||||
check_capture nowrap 'ABCDZ'
|
||||
check_raw_matches nowrap \
|
||||
'^G 5x3 \(0/0\)$' \
|
||||
'L 0 \(0\) flags=NONE\[0\]' \
|
||||
'C 0,4 data=\(1,1,Z\) flags=NONE\[0\]'
|
||||
|
||||
start_pane pending 5 3 'ABCD\r\033[4CZ'
|
||||
check_capture pending 'ABCDZ'
|
||||
check_cursor pending '5,0'
|
||||
check_raw_matches pending \
|
||||
'L 0 \(0\) flags=NONE\[0\]' \
|
||||
'C 0,4 data=\(1,1,Z\) flags=NONE\[0\]'
|
||||
|
||||
exit $exit_status
|
||||
94
regress/input-replies.sh
Normal file
94
regress/input-replies.sh
Normal file
@@ -0,0 +1,94 @@
|
||||
#!/bin/sh
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
sleep 0.1
|
||||
|
||||
TMP=$(mktemp)
|
||||
EXP=$(mktemp)
|
||||
trap 'rm -f "$TMP" "$EXP"; $TMUX kill-server 2>/dev/null' 0 1 15
|
||||
|
||||
$TMUX new-session -d -x 80 -y 24 -s replies \; \
|
||||
set-window-option -t replies:0 remain-on-exit on || exit 1
|
||||
$TMUX set-option -s set-clipboard on || exit 1
|
||||
$TMUX set-option -s get-clipboard buffer || exit 1
|
||||
printf Hello | $TMUX load-buffer -
|
||||
sleep 0.3
|
||||
|
||||
exit_status=0
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "FAIL: $1"
|
||||
diff -u "$EXP" "$TMP"
|
||||
exit_status=1
|
||||
}
|
||||
|
||||
query()
|
||||
{
|
||||
name=$1
|
||||
expected=$2
|
||||
seq=$3
|
||||
count=$4
|
||||
setup=$5
|
||||
|
||||
$TMUX respawn-window -k -t replies:0 \
|
||||
"stty raw -echo min 1 time 20; printf '$setup'; printf '$seq'; dd bs=1 count=$count 2>/dev/null | cat -v >$TMP"
|
||||
sleep 0.5
|
||||
printf "%s" "$expected" >"$EXP"
|
||||
cmp "$TMP" "$EXP" || fail "$name"
|
||||
}
|
||||
|
||||
query_timeout()
|
||||
{
|
||||
name=$1
|
||||
expected=$2
|
||||
seq=$3
|
||||
setup=$4
|
||||
|
||||
$TMUX respawn-window -k -t replies:0 \
|
||||
"stty raw -echo min 0 time 5; printf '$setup'; printf '$seq'; sleep 0.1; dd bs=1 count=128 2>/dev/null | cat -v >$TMP"
|
||||
sleep 0.7
|
||||
printf "%s" "$expected" >"$EXP"
|
||||
cmp "$TMP" "$EXP" || fail "$name"
|
||||
}
|
||||
|
||||
query "dsr-ok" '^[[0n' '\033[5n' 4 ''
|
||||
query "dsr-cursor" '^[[1;1R' '\033[6n' 6 ''
|
||||
query "da-primary" '^[[?1;2c' '\033[c' 7 ''
|
||||
query "da-secondary" '^[[>84;0;0c' '\033[>c' 10 ''
|
||||
query "decrqm-irm-reset" '^[[4;2$y' '\033[4$p' 8 ''
|
||||
query "decrqm-irm-set" '^[[4;1$y' '\033[4$p' 8 '\033[4h'
|
||||
query "decrqm-cursor-keys-reset" '^[[?1;2$y' '\033[?1$p' 9 ''
|
||||
query "decrqm-cursor-keys-set" '^[[?1;1$y' '\033[?1$p' 9 '\033[?1h'
|
||||
query "decrqm-columns" '^[[?3;4$y' '\033[?3$p' 9 ''
|
||||
query "decrqm-origin-reset" '^[[?6;2$y' '\033[?6$p' 9 ''
|
||||
query "decrqm-origin-set" '^[[?6;1$y' '\033[?6$p' 9 '\033[?6h'
|
||||
query "decrqm-wrap-set" '^[[?7;1$y' '\033[?7$p' 9 ''
|
||||
query "decrqm-wrap-reset" '^[[?7;2$y' '\033[?7$p' 9 '\033[?7l'
|
||||
query "decrqm-cursor-visible-set" '^[[?25;1$y' '\033[?25$p' 10 ''
|
||||
query "decrqm-cursor-visible-reset" '^[[?25;2$y' '\033[?25$p' 10 '\033[?25l'
|
||||
query "decrqm-mouse-standard-set" '^[[?1000;1$y' '\033[?1000$p' 12 '\033[?1000h'
|
||||
query "decrqm-mouse-button-set" '^[[?1002;1$y' '\033[?1002$p' 12 '\033[?1002h'
|
||||
query "decrqm-mouse-all-set" '^[[?1003;1$y' '\033[?1003$p' 12 '\033[?1003h'
|
||||
query "decrqm-focus-set" '^[[?1004;1$y' '\033[?1004$p' 12 '\033[?1004h'
|
||||
query "decrqm-mouse-utf8-set" '^[[?1005;1$y' '\033[?1005$p' 12 '\033[?1005h'
|
||||
query "decrqm-mouse-sgr-set" '^[[?1006;1$y' '\033[?1006$p' 12 '\033[?1006h'
|
||||
query "decrqm-bracket-paste-set" '^[[?2004;1$y' '\033[?2004$p' 12 '\033[?2004h'
|
||||
query "decrqm-theme-updates-set" '^[[?2031;1$y' '\033[?2031$p' 12 '\033[?2031h'
|
||||
query "decrqss-cursor-style" '^[P1$r q0 q' '\033P$q q\033\\' 10 ''
|
||||
|
||||
query_timeout "osc-10-query" '^[]10;rgb:ffff/0000/0000^G' '\033]10;?\007' '\033]10;red\007'
|
||||
query_timeout "osc-11-query" '^[]11;rgb:0000/0000/ffff^G' '\033]11;?\007' '\033]11;blue\007'
|
||||
query_timeout "osc-12-query" '^[]12;rgb:0000/ffff/0000^G' '\033]12;?\007' '\033]12;green\007'
|
||||
query_timeout "osc-4-query" '^[]4;1;rgb:ffff/0000/0000^G' '\033]4;1;?\007' '\033]4;1;red\007'
|
||||
query_timeout "osc-104-reset-query" '' '\033]4;1;?\007' '\033]4;1;red\007\033]104;1\007'
|
||||
query_timeout "osc-52-query" '^[]52;c;SGVsbG8=^G' '\033]52;c;?\007' ''
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit $exit_status
|
||||
117
regress/input-requests.sh
Normal file
117
regress/input-requests.sh
Normal file
@@ -0,0 +1,117 @@
|
||||
#!/bin/sh
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
python3 - "$TEST_TMUX" <<'PY'
|
||||
import os
|
||||
import select
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
tmux = sys.argv[1]
|
||||
server = [tmux, "-Ltest", "-f/dev/null"]
|
||||
|
||||
def run(*args, check=True):
|
||||
return subprocess.run(server + list(args), check=check,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
def attach():
|
||||
pid, fd = os.forkpty()
|
||||
if pid == 0:
|
||||
os.environ["TERM"] = "xterm-256color"
|
||||
os.execl(tmux, tmux, "-Ltest", "-f/dev/null", "attach-session",
|
||||
"-t", "requests")
|
||||
os.set_blocking(fd, False)
|
||||
return pid, fd
|
||||
|
||||
def read_until(fd, needle, timeout=5):
|
||||
end = time.time() + timeout
|
||||
data = b""
|
||||
while time.time() < end:
|
||||
r, _, _ = select.select([fd], [], [], 0.05)
|
||||
if fd in r:
|
||||
try:
|
||||
chunk = os.read(fd, 4096)
|
||||
except BlockingIOError:
|
||||
chunk = b""
|
||||
if chunk == b"":
|
||||
continue
|
||||
data += chunk
|
||||
if needle in data:
|
||||
return data
|
||||
raise RuntimeError("did not see terminal request %r in %r" %
|
||||
(needle, data))
|
||||
|
||||
def wait_file(path, timeout=5):
|
||||
end = time.time() + timeout
|
||||
while time.time() < end:
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
data = f.read()
|
||||
if data:
|
||||
return data
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
time.sleep(0.05)
|
||||
return b""
|
||||
|
||||
def respawn(command):
|
||||
run("respawn-window", "-k", "-t", "requests:0", command)
|
||||
time.sleep(0.2)
|
||||
|
||||
def cleanup(pid=None):
|
||||
if pid is not None:
|
||||
try:
|
||||
os.kill(pid, signal.SIGHUP)
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
run("kill-server", check=False)
|
||||
|
||||
run("kill-server", check=False)
|
||||
run("new-session", "-d", "-x", "80", "-y", "24", "-s", "requests",
|
||||
"sleep 60")
|
||||
|
||||
pid, fd = attach()
|
||||
try:
|
||||
time.sleep(0.5)
|
||||
|
||||
with tempfile.NamedTemporaryFile(delete=False) as f:
|
||||
palette_out = f.name
|
||||
respawn("stty raw -echo min 1 time 50; "
|
||||
"printf '\\033]4;99;?\\033\\\\'; "
|
||||
"dd bs=1 count=64 2>/dev/null | cat -v >%s; sleep 1" %
|
||||
palette_out)
|
||||
read_until(fd, b"\033]4;99;?\033\\")
|
||||
os.write(fd, b"\033]4;99;rgb:0101/0202/0303\033\\")
|
||||
got = wait_file(palette_out)
|
||||
expected = b"^[]4;99;rgb:0101/0202/0303^[\\"
|
||||
if got != expected:
|
||||
raise AssertionError("palette reply: expected %r got %r" %
|
||||
(expected, got))
|
||||
|
||||
run("set-option", "-s", "set-clipboard", "on")
|
||||
run("set-option", "-s", "get-clipboard", "request")
|
||||
with tempfile.NamedTemporaryFile(delete=False) as f:
|
||||
clip_out = f.name
|
||||
respawn("stty raw -echo min 1 time 50; "
|
||||
"printf '\\033]52;c;?\\033\\\\'; "
|
||||
"dd bs=1 count=64 2>/dev/null | cat -v >%s; sleep 1" %
|
||||
clip_out)
|
||||
data = read_until(fd, b"]52;")
|
||||
if b"?" not in data:
|
||||
raise RuntimeError("clipboard request missing query in %r" % data)
|
||||
os.write(fd, b"\033]52;c;UmVxdWVzdA==\033\\")
|
||||
got = wait_file(clip_out)
|
||||
expected = b"^[]52;c;UmVxdWVzdA==^[\\"
|
||||
if got != expected:
|
||||
raise AssertionError("clipboard reply: expected %r got %r" %
|
||||
(expected, got))
|
||||
finally:
|
||||
cleanup(pid)
|
||||
PY
|
||||
73
regress/input-scroll.sh
Normal file
73
regress/input-scroll.sh
Normal file
@@ -0,0 +1,73 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane wrap 5 3 'abcdeF'
|
||||
check_capture wrap 'abcde
|
||||
F'
|
||||
check_cursor wrap '1,1'
|
||||
check_flags wrap 'W abcde
|
||||
- F'
|
||||
check_joined wrap 'abcdeF'
|
||||
|
||||
start_pane wraplast 5 3 'abcd\033[5GZQ'
|
||||
check_capture wraplast 'abcdZ
|
||||
Q'
|
||||
check_cursor wraplast '1,1'
|
||||
|
||||
start_pane nowrap 5 3 '\033[?7labcdeF'
|
||||
check_capture nowrap 'abcdF'
|
||||
check_cursor nowrap '4,0'
|
||||
|
||||
start_pane origin 6 4 '111111\n222222\n333333\n444444\033[2;3r\033[?6h\033[1;1HAA\033[?6l\033[r'
|
||||
check_capture origin '111111
|
||||
AA2222
|
||||
333333
|
||||
444444'
|
||||
|
||||
start_pane scrollup 5 4 '11111\n22222\n33333\n44444\033[2;3r\033[3;1HAAAAA\nBBBBB\033[r'
|
||||
check_capture scrollup '11111
|
||||
AAAAA
|
||||
BBBBB
|
||||
44444'
|
||||
|
||||
start_pane scrolldown 5 4 '11111\n22222\n33333\n44444\033[2;3r\033[2;1H\033[TZZZZZ\033[r'
|
||||
check_capture scrolldown '11111
|
||||
ZZZZZ
|
||||
22222
|
||||
44444'
|
||||
|
||||
start_pane ri 5 4 '11111\n22222\n33333\n44444\033[2;3r\033[2;1H\033MZZZZZ\033[r'
|
||||
check_capture ri '11111
|
||||
ZZZZZ
|
||||
22222
|
||||
44444'
|
||||
|
||||
start_pane nel 5 3 'AA\033EBC\n'
|
||||
check_capture nel 'AA
|
||||
BC'
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
sleep 0.1
|
||||
$TMUX new-session -d -x 5 -y 3 -s history \; \
|
||||
set-option -g history-limit 3 \; \
|
||||
respawn-pane -k "printf '01\n02\n03\n04\n05\n06'; sleep 2" || exit 1
|
||||
sleep 0.3
|
||||
$TMUX capture-pane -pN -t history: -S - -E - | normalize_capture >"$TMP"
|
||||
printf "%s\n" '01
|
||||
02
|
||||
03
|
||||
04
|
||||
05
|
||||
06' >"$EXP"
|
||||
cmp "$TMP" "$EXP" || fail "history limit"
|
||||
|
||||
$TMUX clear-history -t history:
|
||||
$TMUX capture-pane -pN -t history: -S - -E - | normalize_capture >"$TMP"
|
||||
printf "%s\n" '04
|
||||
05
|
||||
06' >"$EXP"
|
||||
cmp "$TMP" "$EXP" || fail "clear-history"
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit $exit_status
|
||||
26
regress/input-sgr.sh
Normal file
26
regress/input-sgr.sh
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane sgr-basic 20 3 '\033[1;2;3;4;5;7;8;9mA\033[22;23;24;25;27;28;29mB\n'
|
||||
check_capture sgr-basic 'AB'
|
||||
$TMUX capture-pane -peN -t sgr-basic: -S 0 -E - >/dev/null || exit 1
|
||||
|
||||
start_pane sgr-colour 20 3 '\033[31;42mA\033[38;5;196;48;5;22mB\033[38;2;1;2;3;48;2;4;5;6mC\033[39;49mD\n'
|
||||
check_capture sgr-colour 'ABCD'
|
||||
$TMUX capture-pane -peN -t sgr-colour: -S 0 -E - >/dev/null || exit 1
|
||||
|
||||
start_pane sgr-underline 20 3 '\033[4:1mA\033[4:2mB\033[4:3mC\033[4:4mD\033[4:5mE\033[4:0mF\n'
|
||||
check_capture sgr-underline 'ABCDEF'
|
||||
$TMUX capture-pane -peN -t sgr-underline: -S 0 -E - >/dev/null || exit 1
|
||||
|
||||
start_pane sgr-uscolour 20 3 '\033[58;5;45;4mA\033[58:2::10:20:30mB\033[59mC\n'
|
||||
check_capture sgr-uscolour 'ABC'
|
||||
$TMUX capture-pane -peN -t sgr-uscolour: -S 0 -E - >/dev/null || exit 1
|
||||
|
||||
start_pane sgr-reset 20 3 '\033[90;100mA\033[0mB\033[91;101mC\033[39;49mD\n'
|
||||
check_capture sgr-reset 'ABCD'
|
||||
$TMUX capture-pane -peN -t sgr-reset: -S 0 -E - >/dev/null || exit 1
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit $exit_status
|
||||
45
regress/input-unicode.sh
Normal file
45
regress/input-unicode.sh
Normal file
@@ -0,0 +1,45 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
start_pane wide 10 3 '\343\201\202B\rX\n'
|
||||
check_capture wide 'X B'
|
||||
check_flags wide 'X X B'
|
||||
|
||||
start_pane widepad 10 3 'A\343\201\202B\r\033[2CX\n'
|
||||
check_capture widepad 'A XB'
|
||||
check_flags widepad 'X A XB'
|
||||
|
||||
start_pane wideedge 5 3 'abc\343\201\202Z\n'
|
||||
check_capture wideedge 'abcあ
|
||||
Z'
|
||||
check_cursor wideedge '0,2'
|
||||
check_joined wideedge 'abcあZ'
|
||||
|
||||
start_pane wideeol 5 3 'abcd\343\201\202Z\n'
|
||||
check_capture wideeol 'abcd
|
||||
あZ'
|
||||
check_cursor wideeol '0,2'
|
||||
|
||||
start_pane combine 10 3 'e\314\201\n'
|
||||
check_capture combine 'é'
|
||||
check_cursor combine '0,1'
|
||||
|
||||
start_pane combinewide 10 3 '\343\201\202\314\201X\n'
|
||||
check_capture combinewide 'あ́X'
|
||||
check_cursor combinewide '0,1'
|
||||
|
||||
start_pane variation 10 3 '\342\234\224\357\270\217X\n'
|
||||
check_capture variation '✔️X'
|
||||
check_cursor variation '0,1'
|
||||
|
||||
start_pane flag 10 3 '\360\237\207\254\360\237\207\247X\n'
|
||||
check_capture flag '🇬🇧X'
|
||||
check_cursor flag '0,1'
|
||||
|
||||
start_pane combining-left 10 3 '\314\201A\n'
|
||||
check_capture combining-left 'A'
|
||||
check_cursor combining-left '0,1'
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit $exit_status
|
||||
161
regress/options-array.sh
Normal file
161
regress/options-array.sh
Normal file
@@ -0,0 +1,161 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of array options in the options engine (options_array_* in options.c
|
||||
# and the array handling in cmd-set-option.c / cmd-show-options.c).
|
||||
#
|
||||
# Array options are indexed by integer. This exercises: setting a whole array
|
||||
# from a separator-delimited string; per-index set with option[N]; -a append
|
||||
# (which lands at the next free index); show ordering by ascending index and
|
||||
# preservation of gaps; per-index unset with -u; show -v of a single index and
|
||||
# of a missing index; and per-option separators (user-keys splits only on
|
||||
# comma, update-environment on space or comma).
|
||||
#
|
||||
# update-environment (session), status-format (session), user-keys (server)
|
||||
# and command-alias (server) are used as representative array options.
|
||||
#
|
||||
# options-scope.sh covers scoping/inheritance and options-values.sh covers
|
||||
# value validation.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
check_value()
|
||||
{
|
||||
out=$($TMUX show $1 2>&1)
|
||||
if [ "$out" != "$2" ]; then
|
||||
echo "show $1 failed."
|
||||
echo "Expected: '$2'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# check_array $args $expected
|
||||
#
|
||||
# Compare the full (multi-line) show output for an array option with a
|
||||
# newline-separated $expected string.
|
||||
check_array()
|
||||
{
|
||||
out=$($TMUX show $1 2>&1)
|
||||
if [ "$out" != "$(printf '%s' "$2")" ]; then
|
||||
echo "show $1 (array) failed."
|
||||
echo "Expected:"; printf '%s\n' "$2"
|
||||
echo "But got:"; printf '%s\n' "$out"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_ok()
|
||||
{
|
||||
if ! $TMUX "$@"; then
|
||||
echo "Command failed (expected success): $*"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_fail()
|
||||
{
|
||||
exp="$1"
|
||||
shift
|
||||
out=$($TMUX "$@" 2>&1)
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Command succeeded (expected failure): $*"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$out" != "$exp" ]; then
|
||||
echo "Wrong error for: $*"
|
||||
echo "Expected: '$exp'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_alive()
|
||||
{
|
||||
if [ "$($TMUX display-message -p alive)" != "alive" ]; then
|
||||
echo "Server died: $1"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
$TMUX new-session -d -s main -x 80 -y 24 || exit 1
|
||||
|
||||
# --- whole-array assignment splits on the separator -----------------------
|
||||
#
|
||||
# update-environment has the default " ," separator, so a single string value
|
||||
# is split into consecutive indices starting at 0.
|
||||
check_ok set -g update-environment "AAA BBB,CCC"
|
||||
check_array "-g update-environment" "update-environment[0] AAA
|
||||
update-environment[1] BBB
|
||||
update-environment[2] CCC"
|
||||
|
||||
# --- -a append goes to the next free index --------------------------------
|
||||
check_ok set -ga update-environment "DDD"
|
||||
check_array "-g update-environment" "update-environment[0] AAA
|
||||
update-environment[1] BBB
|
||||
update-environment[2] CCC
|
||||
update-environment[3] DDD"
|
||||
|
||||
# --- per-index unset leaves a gap; show preserves order and gaps ----------
|
||||
check_ok set -gu update-environment[1]
|
||||
check_array "-g update-environment" "update-environment[0] AAA
|
||||
update-environment[2] CCC
|
||||
update-environment[3] DDD"
|
||||
# show -v of an existing index returns its value; a missing index is empty.
|
||||
check_value "-gv update-environment[0]" "AAA"
|
||||
check_value "-gv update-environment[1]" ""
|
||||
|
||||
# --- explicit indexed set, including out-of-order and gaps ----------------
|
||||
#
|
||||
# status-format is a session array; assigning an empty string first clears its
|
||||
# multi-index default, then set specific indices out of order and confirm show
|
||||
# sorts by ascending index and keeps the gap at [1].
|
||||
check_ok set -g status-format ""
|
||||
check_array "-g status-format" "status-format"
|
||||
check_ok set -g status-format[5] "five"
|
||||
check_ok set -g status-format[0] "zero"
|
||||
check_ok set -g status-format[2] "two"
|
||||
check_array "-g status-format" "status-format[0] zero
|
||||
status-format[2] two
|
||||
status-format[5] five"
|
||||
|
||||
# --- comma-only separator (user-keys) -------------------------------------
|
||||
#
|
||||
# user-keys splits only on comma, so an embedded space stays within one entry
|
||||
# (and show quotes a value containing a space).
|
||||
check_ok set -g user-keys "One,Two Three"
|
||||
check_array "-g user-keys" 'user-keys[0] One
|
||||
user-keys[1] "Two Three"'
|
||||
|
||||
# --- command-type array (a hook) ------------------------------------------
|
||||
#
|
||||
# Hooks are command arrays: an indexed value is parsed as a command when set
|
||||
# and re-printed from the parsed command list; a syntax error is reported.
|
||||
check_ok set -g alert-bell[0] "display-message hi"
|
||||
check_value "-gv alert-bell[0]" "display-message hi"
|
||||
check_fail "syntax error" set -g alert-bell[0] "if -x {"
|
||||
|
||||
# --- colour-type array ----------------------------------------------------
|
||||
#
|
||||
# pane-colours is a colour array; an indexed value is validated as a colour.
|
||||
check_ok set -w pane-colours[0] red
|
||||
check_value "-wv pane-colours[0]" "red"
|
||||
check_fail "bad colour: xxxyyy" set -w pane-colours[1] xxxyyy
|
||||
|
||||
# --- -o refuses to overwrite an already-set index -------------------------
|
||||
check_ok set -g command-alias[9] "x=list-keys"
|
||||
check_fail "already set: command-alias[9]" set -go command-alias[9] "y=list-keys"
|
||||
|
||||
# --- non-array option rejects index syntax --------------------------------
|
||||
#
|
||||
# status-left is a plain string; indexing it is an error.
|
||||
check_fail "not an array: status-left[0]" set -g status-left[0] "x"
|
||||
|
||||
assert_alive "after options-array tests"
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
208
regress/options-scope.sh
Normal file
208
regress/options-scope.sh
Normal file
@@ -0,0 +1,208 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of the options engine scoping and inheritance, as described in the
|
||||
# OPTIONS section of tmux(1) and implemented in options.c, cmd-set-option.c and
|
||||
# cmd-show-options.c.
|
||||
#
|
||||
# This exercises: global vs session vs window vs pane precedence; -u to remove
|
||||
# an option (revealing the inherited value); -gu to restore a global option to
|
||||
# its compiled default; scope inference from the option name (-w/-p and the
|
||||
# set-window-option alias); show -v (which does NOT walk parents) versus show -A
|
||||
# (which does, marking inherited values with a trailing *); unknown/ambiguous
|
||||
# option errors and -q suppression; and user options (@foo) at every scope.
|
||||
#
|
||||
# options-values.sh covers value validation and options-array.sh covers arrays.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
# check_value $args $expected
|
||||
#
|
||||
# Run show-option with $args and compare the single-line output with $expected.
|
||||
check_value()
|
||||
{
|
||||
out=$($TMUX show $1 2>&1)
|
||||
if [ "$out" != "$2" ]; then
|
||||
echo "show $1 failed."
|
||||
echo "Expected: '$2'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# check_ok $cmd...
|
||||
#
|
||||
# Run a command and require that it succeeds.
|
||||
check_ok()
|
||||
{
|
||||
if ! $TMUX "$@"; then
|
||||
echo "Command failed (expected success): $*"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# check_fail $expected_error $cmd...
|
||||
#
|
||||
# Run a command and require that it fails with the given error message.
|
||||
check_fail()
|
||||
{
|
||||
exp="$1"
|
||||
shift
|
||||
out=$($TMUX "$@" 2>&1)
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Command succeeded (expected failure): $*"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$out" != "$exp" ]; then
|
||||
echo "Wrong error for: $*"
|
||||
echo "Expected: '$exp'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_alive()
|
||||
{
|
||||
if [ "$($TMUX display-message -p alive)" != "alive" ]; then
|
||||
echo "Server died: $1"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
$TMUX new-session -d -s main -x 80 -y 24 || exit 1
|
||||
|
||||
# --- global vs session precedence -----------------------------------------
|
||||
#
|
||||
# status-left is a session option. A value set at the session scope shadows
|
||||
# the global one; show -v at each scope reports that scope's own value.
|
||||
check_ok set -g status-left "GLOBAL"
|
||||
check_ok set status-left "SESSION"
|
||||
check_value "-v status-left" "SESSION"
|
||||
check_value "-gv status-left" "GLOBAL"
|
||||
|
||||
# show -v does NOT inherit: -u removes the session entry, after which the
|
||||
# session-scope show -v is empty even though the global value still exists.
|
||||
check_ok set -u status-left
|
||||
check_value "-v status-left" ""
|
||||
check_value "-gv status-left" "GLOBAL"
|
||||
|
||||
# show -A walks the parent scopes and marks an inherited value with a "*".
|
||||
out=$($TMUX show -A 2>/dev/null | grep '^status-left\*')
|
||||
if [ "$out" != "status-left* GLOBAL" ]; then
|
||||
echo "show -A did not mark inherited status-left."
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- -gu restores the compiled default ------------------------------------
|
||||
#
|
||||
# Removing a global option with -u restores its built-in default rather than
|
||||
# deleting it; status-left's default is the format "[#{session_name}] ".
|
||||
check_ok set -g status-left "GLOBAL2"
|
||||
check_value "-gv status-left" "GLOBAL2"
|
||||
check_ok set -gu status-left
|
||||
check_value "-gv status-left" "[#{session_name}] "
|
||||
|
||||
# --- scope inference from the option name ---------------------------------
|
||||
#
|
||||
# mode-keys is a window option, so a bare set-option infers the window scope;
|
||||
# set-window-option (setw) is an explicit alias for the same thing, and -g w
|
||||
# targets the global window options.
|
||||
check_ok set mode-keys vi
|
||||
check_value "-wv mode-keys" "vi"
|
||||
check_ok setw mode-keys emacs
|
||||
check_value "-wv mode-keys" "emacs"
|
||||
check_ok set -gw mode-keys vi
|
||||
check_value "-gwv mode-keys" "vi"
|
||||
|
||||
# cursor-colour is a window-and-pane option. A pane-scope value overrides a
|
||||
# window-scope one for that pane.
|
||||
check_ok set -w cursor-colour blue
|
||||
check_ok set -p cursor-colour red
|
||||
check_value "-pv cursor-colour" "red"
|
||||
out=$($TMUX show -Ap 2>/dev/null | grep '^cursor-colour ')
|
||||
if [ "$out" != "cursor-colour red" ]; then
|
||||
echo "pane cursor-colour did not override window value."
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- -U unsets a window option and clears pane copies ----------------------
|
||||
#
|
||||
# When a window option also has per-pane copies, -u on the window scope leaves
|
||||
# those pane copies in place; -U additionally removes the option from every
|
||||
# pane in the window, so all panes fall back to inheritance.
|
||||
$TMUX split-window -t main || exit 1
|
||||
panes=$($TMUX list-panes -t main -F '#{pane_id}')
|
||||
set -- $panes
|
||||
pa=$1
|
||||
pb=$2
|
||||
check_ok set -p -t "$pa" cursor-colour red
|
||||
check_ok set -p -t "$pb" cursor-colour blue
|
||||
check_ok set -w -t main cursor-colour green
|
||||
check_value "-pv -t $pa cursor-colour" "red"
|
||||
check_value "-pv -t $pb cursor-colour" "blue"
|
||||
check_value "-wv -t main cursor-colour" "green"
|
||||
check_ok set -Uw -t main cursor-colour
|
||||
check_value "-pv -t $pa cursor-colour" ""
|
||||
check_value "-pv -t $pb cursor-colour" ""
|
||||
check_value "-wv -t main cursor-colour" ""
|
||||
|
||||
# --- unknown, ambiguous and -q --------------------------------------------
|
||||
check_fail "invalid option: no-such-option" set -g no-such-option x
|
||||
check_fail "ambiguous option: status-l" set -g status-l x
|
||||
# A unique prefix resolves to the full option name.
|
||||
check_ok set -g status-inte 5
|
||||
check_value "-gv status-interval" "5"
|
||||
# -q suppresses the error and exits successfully.
|
||||
check_ok set -gq no-such-option x
|
||||
check_ok show -gqv no-such-option
|
||||
|
||||
# --- errors from unresolvable targets -------------------------------------
|
||||
#
|
||||
# A -t target that does not resolve produces a scope-specific error from
|
||||
# options_scope_from_name()/options_scope_from_flags().
|
||||
check_fail "no such session: nosuch" show -t nosuch status-left
|
||||
check_fail "no such window: nosuch" show -w -t nosuch mode-keys
|
||||
check_fail "no such pane: nosuch" set -p -t nosuch cursor-colour red
|
||||
|
||||
# --- show with no option name lists every option --------------------------
|
||||
#
|
||||
# show without a specific option walks the whole table (cmd_show_options_all).
|
||||
# Hooks are hidden unless -H is given.
|
||||
$TMUX set -g @listme "here" || exit 1
|
||||
if ! $TMUX show -g | grep -q '^@listme here$'; then
|
||||
echo "show -g did not list @listme."
|
||||
exit 1
|
||||
fi
|
||||
# alert-bell is a hook: only shown with -H.
|
||||
if $TMUX show -g | grep -q '^alert-bell'; then
|
||||
echo "show -g listed a hook without -H."
|
||||
exit 1
|
||||
fi
|
||||
if ! $TMUX show -gH | grep -q '^alert-bell'; then
|
||||
echo "show -gH did not list the alert-bell hook."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- user options at every scope ------------------------------------------
|
||||
#
|
||||
# @-prefixed user options can be created freely at any scope and do not
|
||||
# inherit type checking.
|
||||
check_ok set -g @u "global-user"
|
||||
check_ok set @u "session-user"
|
||||
check_ok set -w @u "window-user"
|
||||
check_ok set -p @u "pane-user"
|
||||
check_value "-gv @u" "global-user"
|
||||
check_value "-v @u" "session-user"
|
||||
check_value "-wv @u" "window-user"
|
||||
check_value "-pv @u" "pane-user"
|
||||
|
||||
assert_alive "after options-scope tests"
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
193
regress/options-values.sh
Normal file
193
regress/options-values.sh
Normal file
@@ -0,0 +1,193 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests of options engine value validation, as implemented by
|
||||
# options_from_string() and friends in options.c.
|
||||
#
|
||||
# Each option table entry has a type (string, number, key, colour, flag,
|
||||
# choice, command) with type-specific parsing and validation. This exercises:
|
||||
# number range limits; choice options rejecting unknown values; flag options
|
||||
# toggling with no value and rejecting garbage; colour and key options
|
||||
# rejecting invalid input; string append with -a; -F expansion at set time;
|
||||
# and -o refusing to overwrite an option that is already set.
|
||||
#
|
||||
# options-scope.sh covers scoping/inheritance and options-array.sh covers
|
||||
# arrays.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
check_value()
|
||||
{
|
||||
out=$($TMUX show $1 2>&1)
|
||||
if [ "$out" != "$2" ]; then
|
||||
echo "show $1 failed."
|
||||
echo "Expected: '$2'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_ok()
|
||||
{
|
||||
if ! $TMUX "$@"; then
|
||||
echo "Command failed (expected success): $*"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_fail()
|
||||
{
|
||||
exp="$1"
|
||||
shift
|
||||
out=$($TMUX "$@" 2>&1)
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Command succeeded (expected failure): $*"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$out" != "$exp" ]; then
|
||||
echo "Wrong error for: $*"
|
||||
echo "Expected: '$exp'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_alive()
|
||||
{
|
||||
if [ "$($TMUX display-message -p alive)" != "alive" ]; then
|
||||
echo "Server died: $1"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
$TMUX new-session -d -s main -x 80 -y 24 || exit 1
|
||||
|
||||
# --- number options -------------------------------------------------------
|
||||
#
|
||||
# display-time is a number with a minimum of 0; a negative value and a
|
||||
# non-numeric value are both rejected via strtonum(3).
|
||||
check_ok set -g display-time 4000
|
||||
check_value "-gv display-time" "4000"
|
||||
check_fail "value is too small: -5" set -g display-time -5
|
||||
check_fail "value is invalid: abc" set -g display-time abc
|
||||
# A missing value is rejected for a non-flag, non-choice option.
|
||||
check_fail "empty value" set -g display-time
|
||||
|
||||
# --- choice options -------------------------------------------------------
|
||||
#
|
||||
# status-keys accepts only its listed choices (vi/emacs); anything else is an
|
||||
# "unknown value" error and the option keeps its previous value.
|
||||
check_ok set -g status-keys vi
|
||||
check_value "-gv status-keys" "vi"
|
||||
check_fail "unknown value: bogus" set -g status-keys bogus
|
||||
check_value "-gv status-keys" "vi"
|
||||
|
||||
# --- flag options ---------------------------------------------------------
|
||||
#
|
||||
# focus-events is an on/off flag. Setting with no value toggles it; explicit
|
||||
# on/off/yes/no/1/0 are accepted (case-insensitively); anything else fails.
|
||||
check_ok set -g focus-events off
|
||||
check_value "-gv focus-events" "off"
|
||||
check_ok set -g focus-events # toggle
|
||||
check_value "-gv focus-events" "on"
|
||||
check_ok set -g focus-events # toggle back
|
||||
check_value "-gv focus-events" "off"
|
||||
check_ok set -g focus-events yes
|
||||
check_value "-gv focus-events" "on"
|
||||
check_ok set -g focus-events NO
|
||||
check_value "-gv focus-events" "off"
|
||||
check_fail "bad value: maybe" set -g focus-events maybe
|
||||
|
||||
# --- colour options -------------------------------------------------------
|
||||
#
|
||||
# status-bg is a colour; named colours, numbers and #rrggbb are accepted,
|
||||
# garbage is rejected.
|
||||
check_ok set -g status-bg red
|
||||
check_value "-gv status-bg" "red"
|
||||
check_ok set -g status-bg colour123
|
||||
check_value "-gv status-bg" "colour123"
|
||||
check_ok set -g status-bg "#00ff00"
|
||||
check_value "-gv status-bg" "#00ff00"
|
||||
check_fail "bad colour: xxxyyy" set -g status-bg xxxyyy
|
||||
|
||||
# --- style options --------------------------------------------------------
|
||||
#
|
||||
# status-style is a style string, validated when set; a bogus style keyword is
|
||||
# rejected and the old value is retained.
|
||||
check_ok set -g status-style "fg=red,bg=black"
|
||||
check_value "-gv status-style" "fg=red,bg=black"
|
||||
check_fail "invalid style: bg=xxxyyy" set -g status-style "bg=xxxyyy"
|
||||
check_value "-gv status-style" "fg=red,bg=black"
|
||||
|
||||
# --- key options ----------------------------------------------------------
|
||||
#
|
||||
# prefix is a key; a valid key name is stored in canonical form, a bad one is
|
||||
# rejected.
|
||||
check_ok set -g prefix C-a
|
||||
check_value "-gv prefix" "C-a"
|
||||
check_fail "bad key: boguskey" set -g prefix boguskey
|
||||
|
||||
# --- string options with extra validation ---------------------------------
|
||||
#
|
||||
# default-shell is a string but is checked to be an executable shell; a bogus
|
||||
# path is rejected and the old value kept.
|
||||
old=$($TMUX show -gv default-shell)
|
||||
check_fail "not a suitable shell: /not/a/shell" set -g default-shell /not/a/shell
|
||||
check_value "-gv default-shell" "$old"
|
||||
|
||||
# --- user options require a value ------------------------------------------
|
||||
#
|
||||
# A user option set with no value at all is an error.
|
||||
check_fail "empty value" set -g @novalue
|
||||
|
||||
# --- command options ------------------------------------------------------
|
||||
#
|
||||
# default-client-command is a command option: the value is parsed as a tmux
|
||||
# command when set and re-printed from the parsed command list. A syntax
|
||||
# error is reported and the option is left unchanged.
|
||||
check_ok set -g default-client-command "new-window"
|
||||
check_value "-gv default-client-command" "new-window"
|
||||
check_fail "syntax error" set -g default-client-command "if -x {"
|
||||
check_value "-gv default-client-command" "new-window"
|
||||
|
||||
# --- renamed option aliases -----------------------------------------------
|
||||
#
|
||||
# Historical option names are mapped to their current spelling, so setting
|
||||
# cursor-color updates cursor-colour.
|
||||
check_ok set -w cursor-color red
|
||||
check_value "-wv cursor-colour" "red"
|
||||
|
||||
# --- string append (-a) ---------------------------------------------------
|
||||
#
|
||||
# -a appends to the current string value rather than replacing it.
|
||||
check_ok set -g @str "foo"
|
||||
check_ok set -ga @str "bar"
|
||||
check_value "-gv @str" "foobar"
|
||||
|
||||
# --- -F expands at set time -----------------------------------------------
|
||||
#
|
||||
# With -F the value is expanded as a format once, at set time; without -F it is
|
||||
# stored literally.
|
||||
check_ok set -gF @expanded "#{session_name}"
|
||||
check_value "-gv @expanded" "main"
|
||||
check_ok set -g @literal "#{session_name}"
|
||||
check_value "-gv @literal" "#{session_name}"
|
||||
|
||||
# --- -o refuses to overwrite ----------------------------------------------
|
||||
#
|
||||
# -o makes set-option fail if the option is already set, leaving it unchanged;
|
||||
# it succeeds for an option that is not yet set.
|
||||
check_ok set -g @once "first"
|
||||
check_fail "already set: @once" set -go @once "second"
|
||||
check_value "-gv @once" "first"
|
||||
check_ok set -go @fresh "value"
|
||||
check_value "-gv @fresh" "value"
|
||||
|
||||
assert_alive "after options-values tests"
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
303
regress/prompt-keys.sh
Normal file
303
regress/prompt-keys.sh
Normal file
@@ -0,0 +1,303 @@
|
||||
#!/bin/sh
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
LANG=C.UTF-8
|
||||
export TERM LC_ALL LANG
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
OUT="$TEST_TMUX -Ltest -f/dev/null" # outer (host for the client)
|
||||
IN="$TEST_TMUX -Ltest2 -f/dev/null" # inner (under test)
|
||||
|
||||
$OUT kill-server 2>/dev/null
|
||||
$IN kill-server 2>/dev/null
|
||||
trap "$OUT kill-server 2>/dev/null; $IN kill-server 2>/dev/null" EXIT
|
||||
|
||||
fail() {
|
||||
echo "[FAIL] $1"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Capture the outer pane: what the inner client rendered. No -e, so we match
|
||||
# plain visible text, not styles or escape sequences.
|
||||
capture() {
|
||||
$OUT capture-pane -p
|
||||
}
|
||||
|
||||
# The "(search) ..." prompt row of a mode prompt.
|
||||
search_row() {
|
||||
capture | grep '(search)' | head -1
|
||||
}
|
||||
|
||||
# The inner status line is the last row of the outer capture (status is at the
|
||||
# bottom). Used to assert a prompt is in the pane and not on the status line.
|
||||
status_line() {
|
||||
capture | tail -1
|
||||
}
|
||||
|
||||
# Inner mode must still be a tree mode (the prompt did not close or crash it).
|
||||
in_tree_mode() {
|
||||
[ "$($IN display-message -p '#{pane_mode}')" = "tree-mode" ]
|
||||
}
|
||||
|
||||
# Small settle for the key -> inner server -> inner client -> outer pane round
|
||||
# trip to redraw before we capture. Matches the short waits other regress tests
|
||||
# use; we do not depend on exact timing beyond the redraw completing.
|
||||
settle() {
|
||||
sleep 0.5
|
||||
}
|
||||
|
||||
# Assert the mode search prompt currently shows exactly "(search) <want>".
|
||||
search_is() {
|
||||
want=$1; msg=$2
|
||||
search_row | grep -qF "(search) $want" || \
|
||||
fail "$msg (wanted '(search) $want', got '$(search_row)')"
|
||||
}
|
||||
|
||||
# --- Inner session under test. ---------------------------------------------
|
||||
#
|
||||
# Two windows so the tree has content; status on so we can distinguish the pane
|
||||
# from the status line; fixed size and manual sizing so the layout is stable.
|
||||
# A root-table key opens a status-line command prompt whose accept action writes
|
||||
# the final buffer into @r, so we can recover it exactly.
|
||||
$IN new -d -x80 -y24 "sh -c 'exec sleep 1000'" || exit 1
|
||||
$IN set -g status on || exit 1
|
||||
$IN set -g status-position bottom || exit 1
|
||||
$IN set -g status-keys emacs || exit 1
|
||||
$IN set -g window-size manual || exit 1
|
||||
$IN new-window -d "sh -c 'exec sleep 1000'" || exit 1
|
||||
$IN bind -n M-r command-prompt -p ">" "set -g @r '%%'" || exit 1
|
||||
|
||||
# --- Outer session: attach the inner one inside its pane. -------------------
|
||||
$OUT new -d -x80 -y24 || exit 1
|
||||
$OUT set -g status off || exit 1
|
||||
$OUT set -g window-size manual || exit 1
|
||||
$OUT send-keys -l "$IN attach" || exit 1
|
||||
$OUT send-keys Enter || exit 1
|
||||
sleep 1
|
||||
|
||||
# ===========================================================================
|
||||
# Mode prompt (choose-tree search): the thorough engine vehicle.
|
||||
# ===========================================================================
|
||||
|
||||
$IN choose-tree || exit 1
|
||||
settle
|
||||
in_tree_mode || fail "choose-tree did not enter tree-mode"
|
||||
|
||||
# --- 1. Search prompt is drawn in the pane, not on the status line. ---
|
||||
$IN send-keys C-s || exit 1
|
||||
settle
|
||||
search_row | grep -q '(search)' || fail "search prompt not drawn in the pane"
|
||||
status_line | grep -q '(search)' && \
|
||||
fail "search prompt drawn on the status line, not in the pane"
|
||||
|
||||
# --- 2. emacs editing: insert and delete at middle, start and end. ---
|
||||
# Cursor position is checked behaviourally: move, insert a marker, read the row.
|
||||
# This needs no cursor coordinates and fails if a movement/edit key is wrong.
|
||||
|
||||
# Middle insert: "abcd", Left Left (cursor between b and c), insert X -> abXcd.
|
||||
$IN send-keys -l "abcd" || exit 1
|
||||
settle
|
||||
search_is "abcd" "literal input not shown in search prompt"
|
||||
$IN send-keys Left Left || exit 1
|
||||
$IN send-keys -l "X" || exit 1
|
||||
settle
|
||||
search_is "abXcd" "middle insert wrong (Left/insert)"
|
||||
|
||||
# Middle delete: BSpace removes X (before cursor), DC removes c (at cursor).
|
||||
$IN send-keys BSpace || exit 1
|
||||
$IN send-keys DC || exit 1
|
||||
settle
|
||||
search_is "abd" "middle delete wrong (BSpace/Delete)"
|
||||
|
||||
# Clear, then start/end insert with C-a and C-e.
|
||||
$IN send-keys C-u || exit 1
|
||||
$IN send-keys -l "mno" || exit 1
|
||||
$IN send-keys C-a || exit 1
|
||||
$IN send-keys -l "S" || exit 1
|
||||
$IN send-keys C-e || exit 1
|
||||
$IN send-keys -l "E" || exit 1
|
||||
settle
|
||||
search_is "SmnoE" "C-a/C-e start/end insert wrong"
|
||||
|
||||
# Word kill: "hello world", C-w removes the last word leaving "hello " (with the
|
||||
# separating space). capture-pane trims trailing spaces, so make the space
|
||||
# visible by inserting a marker after it: the buffer becomes "hello Z".
|
||||
$IN send-keys C-u || exit 1
|
||||
$IN send-keys -l "hello world" || exit 1
|
||||
$IN send-keys C-w || exit 1
|
||||
$IN send-keys -l "Z" || exit 1
|
||||
settle
|
||||
search_is "hello Z" "C-w did not kill a word"
|
||||
|
||||
# C-a then C-k kills the whole line. The mode prompt no longer fills the rest
|
||||
# of the row, so insert a marker to distinguish prompt input from tree content
|
||||
# that may remain visible after the prompt.
|
||||
$IN send-keys C-a || exit 1
|
||||
$IN send-keys C-k || exit 1
|
||||
$IN send-keys -l "X" || exit 1
|
||||
settle
|
||||
search_is "X" "C-a C-k did not clear the line"
|
||||
|
||||
# --- 3. Editing kept the prompt open the whole time. ---
|
||||
in_tree_mode || fail "editing keys closed the mode"
|
||||
search_row | grep -q '(search)' || fail "editing keys closed the prompt"
|
||||
|
||||
# --- 4. Unicode wide character: insert, render, delete as one unit. ---
|
||||
$IN send-keys C-u || exit 1
|
||||
$IN send-keys -l "a中b" || exit 1
|
||||
settle
|
||||
search_is "a中b" "wide character not shown"
|
||||
# Left moves over "b" (one column); BSpace deletes the wide "中" as a single
|
||||
# width-2 unit, leaving "ab".
|
||||
$IN send-keys Left || exit 1
|
||||
$IN send-keys BSpace || exit 1
|
||||
settle
|
||||
search_is "ab" "wide character not deleted as one unit"
|
||||
|
||||
# --- 5. Control character: quote-next inserts it literally, shown as ^G. ---
|
||||
$IN send-keys C-u || exit 1
|
||||
$IN send-keys -l "a" || exit 1
|
||||
$IN send-keys C-v || exit 1 # quote next key
|
||||
$IN send-keys C-g || exit 1 # literal BEL -> displayed as ^G
|
||||
$IN send-keys -l "b" || exit 1
|
||||
settle
|
||||
search_is "a^Gb" "control character not shown as ^G"
|
||||
# Deleted as a single unit too.
|
||||
$IN send-keys Left || exit 1
|
||||
$IN send-keys BSpace || exit 1
|
||||
settle
|
||||
search_is "ab" "control character not deleted as one unit"
|
||||
|
||||
# --- 6. Kill and yank: C-w fills the yank buffer, C-y pastes it at the cursor. ---
|
||||
# (prompt_key only fills the yank buffer from C-w; C-y then yanks that text, or
|
||||
# the top paste buffer if nothing has been killed. Establish our own kill here
|
||||
# so the result is deterministic.)
|
||||
$IN send-keys C-u || exit 1
|
||||
$IN send-keys -l "one two" || exit 1
|
||||
$IN send-keys C-w || exit 1 # kill "two", buffer "one "
|
||||
$IN send-keys C-y || exit 1 # yank it back -> "one two"
|
||||
settle
|
||||
search_is "one two" "C-y did not yank the killed text"
|
||||
$IN send-keys C-y || exit 1 # yank again at cursor -> "one twotwo"
|
||||
settle
|
||||
search_is "one twotwo" "second C-y did not yank again"
|
||||
|
||||
# --- 7. History: accept a string, reopen, Up recalls it. ---
|
||||
$IN send-keys C-u || exit 1
|
||||
$IN send-keys -l "alpha" || exit 1
|
||||
$IN send-keys Enter || exit 1
|
||||
settle
|
||||
$IN send-keys C-s || exit 1
|
||||
settle
|
||||
$IN send-keys Up || exit 1
|
||||
settle
|
||||
search_is "alpha" "history (Up) did not recall the previous entry"
|
||||
|
||||
# --- 8. Escape closes the prompt but leaves the mode open. ---
|
||||
$IN send-keys Escape || exit 1
|
||||
settle
|
||||
search_row | grep -q '(search)' && fail "Escape left a dangling search prompt"
|
||||
in_tree_mode || fail "Escape closed the mode as well as the prompt"
|
||||
|
||||
# Leave the mode.
|
||||
$IN send-keys q || exit 1
|
||||
settle
|
||||
|
||||
# ===========================================================================
|
||||
# Status-line prompt (command-prompt): the same engine on the status line.
|
||||
# Keys go through the inner client's terminal (outer send-keys); the accepted
|
||||
# buffer is recovered exactly via %% -> @r.
|
||||
# ===========================================================================
|
||||
|
||||
# --- 10. Prompt is drawn on the status line (the last row). ---
|
||||
$IN set -g @r "" || exit 1
|
||||
$OUT send-keys M-r || exit 1
|
||||
settle
|
||||
status_line | grep -q '>' || fail "status-line prompt not drawn on the status line"
|
||||
|
||||
# --- 11. emacs cursor-marker edit, accept recovers the exact buffer. ---
|
||||
$OUT send-keys -l "abc" || exit 1
|
||||
$OUT send-keys Home || exit 1
|
||||
$OUT send-keys -l "X" || exit 1
|
||||
settle
|
||||
status_line | grep -qF "> Xabc" || \
|
||||
fail "status-line edit wrong (got '$(status_line)')"
|
||||
$OUT send-keys Enter || exit 1
|
||||
settle
|
||||
[ "$($IN show -gv @r)" = "Xabc" ] || \
|
||||
fail "status-line accept recovered '$($IN show -gv @r)', wanted 'Xabc'"
|
||||
|
||||
# --- 12. Unicode on the status line: insert, move, delete wide char. ---
|
||||
$IN set -g @r "" || exit 1
|
||||
$OUT send-keys M-r || exit 1
|
||||
settle
|
||||
$OUT send-keys -l "a㋡b" || exit 1
|
||||
settle
|
||||
status_line | grep -qF "a㋡b" || \
|
||||
fail "status-line wide character not shown (got '$(status_line)')"
|
||||
# Home, insert Z (start); End, BSpace (delete b); BSpace (delete wide char).
|
||||
$OUT send-keys Home || exit 1
|
||||
$OUT send-keys -l "Z" || exit 1
|
||||
$OUT send-keys End || exit 1
|
||||
$OUT send-keys BSpace || exit 1
|
||||
$OUT send-keys BSpace || exit 1
|
||||
settle
|
||||
$OUT send-keys Enter || exit 1
|
||||
settle
|
||||
[ "$($IN show -gv @r)" = "Za" ] || \
|
||||
fail "status-line wide edit recovered '$($IN show -gv @r)', wanted 'Za'"
|
||||
|
||||
# --- 13. Overflow: more text than fits stays within the line and is kept. ---
|
||||
big="0123456789012345678901234567890123456789012345678901234567890123456789ABCDEFGHIJ"
|
||||
$IN set -g @r "" || exit 1
|
||||
$OUT send-keys M-r || exit 1
|
||||
settle
|
||||
$OUT send-keys -l "$big" || exit 1
|
||||
settle
|
||||
# The drawn status line must not exceed the client width (80): no wrap, no crash.
|
||||
width=$(status_line | awk '{print length($0)}')
|
||||
[ "$width" -le 80 ] || fail "overflowing prompt drew $width columns, wider than 80"
|
||||
$OUT send-keys Enter || exit 1
|
||||
settle
|
||||
# The whole buffer was kept despite only part being visible.
|
||||
[ "$($IN show -gv @r)" = "$big" ] || fail "overflowing prompt lost buffer content"
|
||||
|
||||
# --- 14. Escape closes the status-line prompt cleanly. ---
|
||||
$IN set -g @r "SENTINEL" || exit 1
|
||||
$OUT send-keys M-r || exit 1
|
||||
settle
|
||||
$OUT send-keys -l "discard" || exit 1
|
||||
$OUT send-keys Escape || exit 1
|
||||
settle
|
||||
status_line | grep -q '> discard' && fail "Escape left a dangling status-line prompt"
|
||||
[ "$($IN show -gv @r)" = "SENTINEL" ] || fail "Escape ran the prompt's accept action"
|
||||
|
||||
# ===========================================================================
|
||||
# Two clients attached to the same window: a mode prompt must render on both.
|
||||
# ===========================================================================
|
||||
|
||||
$OUT new-window || exit 1
|
||||
$OUT set -g status off || exit 1
|
||||
$OUT send-keys -l "$IN attach" || exit 1
|
||||
$OUT send-keys Enter || exit 1
|
||||
sleep 1
|
||||
|
||||
$IN choose-tree || exit 1
|
||||
settle
|
||||
$IN send-keys C-s || exit 1
|
||||
settle
|
||||
$IN send-keys -l "dual" || exit 1
|
||||
settle
|
||||
for w in $($OUT list-windows -F '#{window_index}'); do
|
||||
$OUT capture-pane -t ":$w" -p | grep -qF "(search) dual" || \
|
||||
fail "mode prompt not shown on client in outer window $w"
|
||||
done
|
||||
$IN send-keys Escape || exit 1
|
||||
settle
|
||||
|
||||
# --- Inner tmux is still alive and responsive. ---
|
||||
$IN display-message -p '#{version}' >/dev/null 2>&1 || fail "inner tmux died"
|
||||
|
||||
exit 0
|
||||
336
regress/prompt-mechanics.sh
Normal file
336
regress/prompt-mechanics.sh
Normal file
@@ -0,0 +1,336 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exercise the prompt mechanics shared by all three host paths of the prompt
|
||||
# engine in prompt.c:
|
||||
#
|
||||
# status.c status_prompt_set - the status-line command prompt.
|
||||
# window.c window_pane_set_prompt - a prompt drawn over a pane (-P).
|
||||
# mode-tree.c mode_tree_set_prompt - search/filter prompts in tree modes.
|
||||
#
|
||||
# prompt-keys.sh covers the editing keys; this test covers that each path OPENS
|
||||
# and DRAWS in the right place and that the prompt flags select the right engine
|
||||
# behaviour: -1 (single), -N (numeric), -i (incremental), -k (key), -e
|
||||
# (backspace exit), -I (prefill), multi prompts, type-scoped history and command
|
||||
# completion.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
LANG=C.UTF-8
|
||||
export TERM LC_ALL LANG
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
OUT="$TEST_TMUX -Ltest -f/dev/null" # outer (host for the client)
|
||||
IN="$TEST_TMUX -Ltest2 -f/dev/null" # inner (under test)
|
||||
|
||||
$OUT kill-server 2>/dev/null
|
||||
$IN kill-server 2>/dev/null
|
||||
trap "$OUT kill-server 2>/dev/null; $IN kill-server 2>/dev/null" EXIT
|
||||
|
||||
fail() {
|
||||
echo "[FAIL] $1"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Capture the outer pane: what the inner client rendered. Pane-area prompts
|
||||
# (over a pane or in a tree mode) and the status-line prompt all end up here.
|
||||
capture() {
|
||||
$OUT capture-pane -p
|
||||
}
|
||||
|
||||
# The inner status line is the last row of the outer capture.
|
||||
status_line() {
|
||||
capture | tail -1
|
||||
}
|
||||
|
||||
# The recovered buffer. Every prompt below accepts into the @r option so the
|
||||
# exact final string can be checked; reset it to a sentinel first so we can tell
|
||||
# "accept ran" from "prompt cancelled".
|
||||
got() {
|
||||
$IN show -gv @r
|
||||
}
|
||||
reset() {
|
||||
$IN set -g @r "SENTINEL" || exit 1
|
||||
}
|
||||
|
||||
# Settle for the key -> inner server -> inner client -> outer pane redraw round
|
||||
# trip, as in prompt-keys.sh.
|
||||
settle() {
|
||||
sleep 0.5
|
||||
}
|
||||
|
||||
# --- Inner session under test. ---------------------------------------------
|
||||
#
|
||||
# The window is created at -y23 so that, with a one-row bottom status, the pane
|
||||
# area (23) exactly fills the 24-row client: a pane prompt drawn on the pane's
|
||||
# bottom row lands on visible row 23, just above the status line on row 24. A
|
||||
# second, distinctively named window gives the tree something to filter.
|
||||
$IN new -d -x80 -y23 -n YAK "sh -c 'exec sleep 1000'" || exit 1
|
||||
$IN set -g status on || exit 1
|
||||
$IN set -g status-position bottom || exit 1
|
||||
$IN set -g status-keys emacs || exit 1
|
||||
$IN set -g window-size manual || exit 1
|
||||
$IN new-window -d -n ZEBRA "sh -c 'exec sleep 1000'" || exit 1
|
||||
|
||||
# One root-table key per path/flag. The accept template records the final
|
||||
# buffer in @r (or both buffers, for the multi-prompt case).
|
||||
$IN bind -n M-s command-prompt -p '(stat)' "set -g @r '%%'" || exit 1
|
||||
$IN bind -n M-p command-prompt -P -p '(pane)' "set -g @r '%%'" || exit 1
|
||||
$IN bind -n M-o command-prompt -P -1 -p '(one)' "set -g @r '%%'" || exit 1
|
||||
$IN bind -n M-n command-prompt -P -N -I 5 -p '(num)' "set -g @r '%%'" || exit 1
|
||||
$IN bind -n M-i command-prompt -P -i -p '(inc)' "set -g @r '%%'" || exit 1
|
||||
$IN bind -n M-k command-prompt -P -k -p '(key)' "set -g @r '%%'" || exit 1
|
||||
$IN bind -n M-e command-prompt -P -e -p '(bs)' "set -g @r '%%'" || exit 1
|
||||
$IN bind -n M-j command-prompt -P -I hello -p '(pre)' "set -g @r '%%'" || exit 1
|
||||
$IN bind -n M-m command-prompt -p 'first,second' "set -g @r '%1/%2'" || exit 1
|
||||
$IN bind -n M-c command-prompt -p '(cmd)' "set -g @r '%%'" || exit 1
|
||||
$IN bind -n M-h command-prompt -T search -p '(srch)' "set -g @r '%%'" || exit 1
|
||||
|
||||
# --- Outer session: attach the inner one inside its pane. -------------------
|
||||
$OUT new -d -x80 -y24 || exit 1
|
||||
$OUT set -g status off || exit 1
|
||||
$OUT set -g window-size manual || exit 1
|
||||
$OUT send-keys -l "$IN attach" || exit 1
|
||||
$OUT send-keys Enter || exit 1
|
||||
sleep 1
|
||||
|
||||
# ===========================================================================
|
||||
# 1. Each path opens and draws in the right place.
|
||||
# ===========================================================================
|
||||
|
||||
# --- 1a. status.c: drawn on the status line (the last row). ---
|
||||
reset
|
||||
$OUT send-keys M-s || exit 1
|
||||
settle
|
||||
status_line | grep -qF '(stat)' || \
|
||||
fail "status-line prompt not on the status line (got '$(status_line)')"
|
||||
$OUT send-keys -l "go" || exit 1
|
||||
$OUT send-keys Enter || exit 1
|
||||
settle
|
||||
[ "$(got)" = "go" ] || fail "status-line accept recovered '$(got)', wanted 'go'"
|
||||
|
||||
# --- 1b. window.c: drawn over the pane, not on the status line. ---
|
||||
reset
|
||||
$OUT send-keys M-p || exit 1
|
||||
settle
|
||||
capture | grep -qF '(pane)' || fail "pane prompt not drawn in the pane"
|
||||
status_line | grep -qF '(pane)' && \
|
||||
fail "pane prompt drawn on the status line, not over the pane"
|
||||
$OUT send-keys -l "deep" || exit 1
|
||||
$OUT send-keys Enter || exit 1
|
||||
settle
|
||||
[ "$(got)" = "deep" ] || fail "pane prompt accept recovered '$(got)', wanted 'deep'"
|
||||
|
||||
# --- 1c. mode-tree.c: search prompt drawn in the pane. ---
|
||||
$IN choose-tree || exit 1
|
||||
settle
|
||||
[ "$($IN display-message -p '#{pane_mode}')" = "tree-mode" ] || \
|
||||
fail "choose-tree did not enter tree-mode"
|
||||
$IN send-keys C-s || exit 1
|
||||
settle
|
||||
capture | grep -qF '(search)' || fail "mode-tree search prompt not drawn in the pane"
|
||||
status_line | grep -qF '(search)' && \
|
||||
fail "mode-tree search prompt drawn on the status line"
|
||||
$IN send-keys Escape || exit 1
|
||||
settle
|
||||
|
||||
# --- 1d. mode-tree.c: filter prompt opens, applies, prefills, and clears. ---
|
||||
$IN send-keys f || exit 1
|
||||
settle
|
||||
capture | grep -qF '(filter)' || fail "mode-tree filter prompt not drawn"
|
||||
$IN send-keys -l "ZEBRA" || exit 1
|
||||
$IN send-keys Enter || exit 1
|
||||
settle
|
||||
capture | grep -q 'filter: active' || fail "accepting the filter did not apply it"
|
||||
# Reopening the filter prompt prefills it with the current filter.
|
||||
$IN send-keys f || exit 1
|
||||
settle
|
||||
capture | grep -qF '(filter) ZEBRA' || \
|
||||
fail "filter prompt not prefilled with the current filter"
|
||||
$IN send-keys Escape || exit 1
|
||||
settle
|
||||
# 'c' clears the filter.
|
||||
$IN send-keys c || exit 1
|
||||
settle
|
||||
capture | grep -q 'filter: active' && fail "'c' did not clear the filter"
|
||||
$IN send-keys q || exit 1
|
||||
settle
|
||||
|
||||
# ===========================================================================
|
||||
# 2. Flags select the right engine behaviour.
|
||||
# ===========================================================================
|
||||
|
||||
# --- 2a. -1 (PROMPT_SINGLE): one keystroke closes and accepts that char. ---
|
||||
reset
|
||||
$OUT send-keys M-o || exit 1
|
||||
settle
|
||||
capture | grep -qF '(one)' || fail "single prompt did not open"
|
||||
$OUT send-keys -l "q" || exit 1
|
||||
settle
|
||||
[ "$(got)" = "q" ] || fail "single prompt recovered '$(got)', wanted 'q'"
|
||||
capture | grep -qF '(one)' && fail "single prompt stayed open after one key"
|
||||
|
||||
# --- 2b. -N (PROMPT_NUMERIC): prefilled, digits append, non-digit closes. ---
|
||||
reset
|
||||
$OUT send-keys M-n || exit 1
|
||||
settle
|
||||
capture | grep -qF '(num) 5' || fail "numeric prompt not prefilled with 5"
|
||||
$OUT send-keys -l "7" || exit 1 # 57
|
||||
$OUT send-keys Enter || exit 1 # Enter is a non-digit: close
|
||||
settle
|
||||
[ "$(got)" = "57" ] || fail "numeric accept recovered '$(got)', wanted '57'"
|
||||
# A non-digit key closes the prompt with the existing buffer, dropping the key.
|
||||
reset
|
||||
$OUT send-keys M-n || exit 1
|
||||
settle
|
||||
$OUT send-keys -l "x" || exit 1
|
||||
settle
|
||||
[ "$(got)" = "5" ] || fail "numeric non-digit close recovered '$(got)', wanted '5'"
|
||||
capture | grep -qF '(num)' && fail "numeric prompt stayed open after a non-digit"
|
||||
|
||||
# --- 2c. -i (PROMPT_INCREMENTAL): callback fires on every edit, stays open. ---
|
||||
# The incremental code path prefixes the buffer with '=' (or +/-), so the '='
|
||||
# proves the value came through the incremental callback, not a plain accept.
|
||||
reset
|
||||
$OUT send-keys M-i || exit 1
|
||||
settle
|
||||
[ "$(got)" = "=" ] || fail "incremental prompt did not fire on open (got '$(got)')"
|
||||
$OUT send-keys -l "a" || exit 1
|
||||
settle
|
||||
[ "$(got)" = "=a" ] || fail "incremental did not fire after 'a' (got '$(got)')"
|
||||
$OUT send-keys -l "b" || exit 1
|
||||
settle
|
||||
[ "$(got)" = "=ab" ] || fail "incremental did not fire after 'b' (got '$(got)')"
|
||||
capture | grep -qF '(inc)' || fail "incremental prompt closed during editing"
|
||||
$OUT send-keys Escape || exit 1
|
||||
settle
|
||||
capture | grep -qF '(inc)' && fail "Escape did not close the incremental prompt"
|
||||
|
||||
# --- 2d. -k (PROMPT_KEY): the next key closes and delivers its name. ---
|
||||
reset
|
||||
$OUT send-keys M-k || exit 1
|
||||
settle
|
||||
capture | grep -qF '(key)' || fail "key prompt did not open"
|
||||
$OUT send-keys -l "z" || exit 1
|
||||
settle
|
||||
[ "$(got)" = "z" ] || fail "key prompt recovered '$(got)', wanted 'z'"
|
||||
capture | grep -qF '(key)' && fail "key prompt stayed open after a key"
|
||||
|
||||
# --- 2e. -e (PROMPT_BSPACE_EXIT): backspace on empty cancels (no accept). ---
|
||||
reset
|
||||
$OUT send-keys M-e || exit 1
|
||||
settle
|
||||
$OUT send-keys BSpace || exit 1
|
||||
settle
|
||||
[ "$(got)" = "SENTINEL" ] || fail "backspace-exit ran the accept action (got '$(got)')"
|
||||
capture | grep -qF '(bs)' && fail "backspace on empty did not close the prompt"
|
||||
|
||||
# --- 2f. -I (prefill): prompt opens with the given buffer. ---
|
||||
reset
|
||||
$OUT send-keys M-j || exit 1
|
||||
settle
|
||||
capture | grep -qF '(pre) hello' || fail "prefill not shown (got '$(capture | grep -F '(pre)')')"
|
||||
$OUT send-keys Enter || exit 1
|
||||
settle
|
||||
[ "$(got)" = "hello" ] || fail "prefill accept recovered '$(got)', wanted 'hello'"
|
||||
|
||||
# --- 2g. Multi prompt: accept advances to the next, both are delivered. ---
|
||||
reset
|
||||
$OUT send-keys M-m || exit 1
|
||||
settle
|
||||
capture | grep -qF 'first' || fail "first of multi prompt not shown"
|
||||
$OUT send-keys -l "X" || exit 1
|
||||
$OUT send-keys Enter || exit 1
|
||||
settle
|
||||
capture | grep -qF 'second' || fail "multi prompt did not advance to the second"
|
||||
$OUT send-keys -l "Y" || exit 1
|
||||
$OUT send-keys Enter || exit 1
|
||||
settle
|
||||
[ "$(got)" = "X/Y" ] || fail "multi prompt recovered '$(got)', wanted 'X/Y'"
|
||||
|
||||
# ===========================================================================
|
||||
# 3. Type-scoped history and command completion.
|
||||
# ===========================================================================
|
||||
|
||||
# --- 3a. Command history is recalled with Up; search history is separate. ---
|
||||
reset
|
||||
$OUT send-keys M-c || exit 1
|
||||
settle
|
||||
$OUT send-keys -l "alpha" || exit 1
|
||||
$OUT send-keys Enter || exit 1
|
||||
settle
|
||||
[ "$(got)" = "alpha" ] || fail "command prompt accept recovered '$(got)'"
|
||||
# Reopen the command prompt: Up recalls the command-type entry.
|
||||
$OUT send-keys M-c || exit 1
|
||||
settle
|
||||
$OUT send-keys Up || exit 1
|
||||
settle
|
||||
capture | grep -qF '(cmd) alpha' || fail "Up did not recall command history"
|
||||
$OUT send-keys Escape || exit 1
|
||||
settle
|
||||
# A search-type prompt must NOT recall the command-type entry (separate rings).
|
||||
$OUT send-keys M-h || exit 1
|
||||
settle
|
||||
$OUT send-keys Up || exit 1
|
||||
settle
|
||||
capture | grep -qF 'alpha' && fail "search prompt recalled command-type history"
|
||||
$OUT send-keys Escape || exit 1
|
||||
settle
|
||||
|
||||
# --- 3b. Tab completion works in a command prompt (command type only). ---
|
||||
$OUT send-keys M-c || exit 1
|
||||
settle
|
||||
$OUT send-keys -l "new-w" || exit 1
|
||||
$OUT send-keys Tab || exit 1
|
||||
settle
|
||||
status_line | grep -qF 'new-window' || \
|
||||
fail "Tab did not complete new-w to new-window (got '$(status_line)')"
|
||||
$OUT send-keys Escape || exit 1
|
||||
settle
|
||||
# A search-type prompt does not complete commands: Tab is literal / inert.
|
||||
$OUT send-keys M-h || exit 1
|
||||
settle
|
||||
$OUT send-keys -l "new-w" || exit 1
|
||||
$OUT send-keys Tab || exit 1
|
||||
settle
|
||||
status_line | grep -qF 'new-window' && fail "search prompt completed a command"
|
||||
$OUT send-keys Escape || exit 1
|
||||
settle
|
||||
|
||||
# ===========================================================================
|
||||
# 4. Robustness: re-entrancy guard and Escape, then liveness.
|
||||
# ===========================================================================
|
||||
|
||||
# --- 4a. A second prompt while one is open is refused (status path). ---
|
||||
client=$($IN list-clients -F '#{client_name}' | head -1)
|
||||
reset
|
||||
$OUT send-keys M-s || exit 1
|
||||
settle
|
||||
$OUT send-keys -l "AAA" || exit 1
|
||||
settle
|
||||
$IN command-prompt -t"$client" -p '(re)' "set -g @r 'REENTERED'" 2>/dev/null
|
||||
settle
|
||||
capture | grep -qF '(re)' && fail "a second status prompt opened over the first"
|
||||
status_line | grep -qF '(stat) AAA' || fail "first status prompt was disturbed"
|
||||
$OUT send-keys Escape || exit 1
|
||||
settle
|
||||
[ "$(got)" = "SENTINEL" ] || fail "Escape ran the status prompt accept action"
|
||||
|
||||
# --- 4b. A second pane prompt while one is open is refused (pane path). ---
|
||||
reset
|
||||
$OUT send-keys M-p || exit 1
|
||||
settle
|
||||
$OUT send-keys -l "BBB" || exit 1
|
||||
settle
|
||||
$IN command-prompt -P -t"$client" -p '(re)' "set -g @r 'REENTERED'" 2>/dev/null
|
||||
settle
|
||||
capture | grep -qF '(re)' && fail "a second pane prompt opened over the first"
|
||||
capture | grep -qF '(pane) BBB' || fail "first pane prompt was disturbed"
|
||||
$OUT send-keys Escape || exit 1
|
||||
settle
|
||||
[ "$(got)" = "SENTINEL" ] || fail "Escape ran the pane prompt accept action"
|
||||
|
||||
# --- 4c. Inner tmux survived every path and flag. ---
|
||||
$IN display-message -p '#{version}' >/dev/null 2>&1 || fail "inner tmux died"
|
||||
|
||||
exit 0
|
||||
79
regress/screen-redraw-bidi.sh
Normal file
79
regress/screen-redraw-bidi.sh
Normal file
@@ -0,0 +1,79 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exercise screen-redraw.c bidirectional-isolate handling. When the client is
|
||||
# UTF-8 and its terminal has the Bidi capability, each drawn span is wrapped in
|
||||
# directional isolate characters (U+2066 .. U+2069) so a bidi terminal does not
|
||||
# reorder pane contents across borders (the REDRAW_ISOLATES path).
|
||||
#
|
||||
# The Bidi capability is added with terminal-overrides before the client
|
||||
# attaches, so the attached client picks it up. The isolate characters appear in
|
||||
# the captured output around each span.
|
||||
#
|
||||
# Run with GENERATE=1 to (re)create the golden files.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -Ltest2 -f/dev/null"
|
||||
RESULTS=screen-redraw-results
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "rm -f $TMP; $TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null" \
|
||||
0 1 15
|
||||
|
||||
fail() {
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
compare() {
|
||||
sleep 1
|
||||
$TMUX capturep -p >$TMP || exit 1
|
||||
if [ -n "$GENERATE" ]; then
|
||||
cp $TMP "$RESULTS/$1.result" || exit 1
|
||||
echo "generated $1"
|
||||
else
|
||||
cmp -s $TMP "$RESULTS/$1.result" || \
|
||||
fail "scene $1 differs from $RESULTS/$1.result"
|
||||
fi
|
||||
}
|
||||
|
||||
new_scene() {
|
||||
$TMUX2 neww -d "sh -c 'printf \"BIDI-LEFT 12345\nBIDI-RIGHT 67890\"; exec sleep 100'" || exit 1
|
||||
$TMUX2 selectw -t:\$ || exit 1
|
||||
$TMUX2 resizew -x40 -y12 || exit 1
|
||||
}
|
||||
|
||||
C="sh -c 'printf \"BIDI-PANE 12345\nBIDI-PANE 67890\"; exec sleep 100'"
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
|
||||
$TMUX2 new -d -x40 -y12 "sh -c 'printf \"BIDI-BASE 12345\nBIDI-BASE 67890\"; exec sleep 100'" || exit 1
|
||||
$TMUX2 set -g status off || exit 1
|
||||
$TMUX2 set -g window-size manual || exit 1
|
||||
# Add the Bidi capability before the client attaches.
|
||||
$TMUX2 set -ag terminal-overrides ",*:Bidi=\\E[8h" || exit 1
|
||||
|
||||
$TMUX new -d -x40 -y12 || exit 1
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX set -g window-size manual || exit 1
|
||||
$TMUX set -g default-terminal "tmux-256color" || exit 1
|
||||
$TMUX send -l "$TMUX2 attach" || exit 1
|
||||
$TMUX send Enter || exit 1
|
||||
sleep 1
|
||||
|
||||
# Single pane: content is wrapped in isolates.
|
||||
new_scene
|
||||
compare bidi-single
|
||||
|
||||
# Two panes: borders and both panes are isolated.
|
||||
new_scene
|
||||
$TMUX2 splitw -h "$C" || exit 1
|
||||
compare bidi-split
|
||||
|
||||
exit 0
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user