Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ba6c00a19 | ||
|
|
78297e52aa | ||
|
|
53bc9f8442 | ||
|
|
4c90009a0e | ||
|
|
bee4fd0914 | ||
|
|
f8ae7edaa0 | ||
|
|
9ec9dca5f8 | ||
|
|
a4f1299f42 | ||
|
|
7b76ca7e49 | ||
|
|
18147b2134 | ||
|
|
c78af2b448 | ||
|
|
e2dc01cf6e | ||
|
|
14fbdd3e9c | ||
|
|
3e5d0015af | ||
|
|
b4c1007075 | ||
|
|
e0533db4d9 | ||
|
|
4071f41f52 | ||
|
|
37efeeb044 | ||
|
|
47409f39f6 | ||
|
|
b7b9d27627 | ||
|
|
6b3d485d77 | ||
|
|
48c6e96a78 | ||
|
|
44feecfc9a | ||
|
|
df559e3444 | ||
|
|
33a7a9b6a2 | ||
|
|
0658719d23 | ||
|
|
c32313a471 | ||
|
|
1da0db435f | ||
|
|
17fd7389c4 | ||
|
|
9da5f1401f | ||
|
|
da3aad4497 | ||
|
|
9a512d1abd | ||
|
|
6ca8dc54ad | ||
|
|
cff944fe8a | ||
|
|
72dea6279e | ||
|
|
37ecb9a6b5 | ||
|
|
fb867a914a | ||
|
|
8ef3c10844 | ||
|
|
10fe249877 | ||
|
|
5143f904ee | ||
|
|
768452a519 | ||
|
|
547bda6e4f | ||
|
|
6a281bd90e | ||
|
|
1c4e0111cb | ||
|
|
8d1771ef64 | ||
|
|
fb54bb0b6c | ||
|
|
546bcb5133 | ||
|
|
b1a72b2e83 | ||
|
|
dce6e31c89 | ||
|
|
cddc888065 | ||
|
|
06aa2c86df | ||
|
|
c51a0b7454 | ||
|
|
cae42772e8 | ||
|
|
09a0dd658d | ||
|
|
bbd1a1dbe2 | ||
|
|
f1e563ad5f | ||
|
|
8e015ebf87 | ||
|
|
3c88c767cd | ||
|
|
99df13b085 | ||
|
|
f03d870bcd | ||
|
|
856666d5bd | ||
|
|
1940bbec75 | ||
|
|
7b7fb73611 | ||
|
|
b97baa7ff1 | ||
|
|
a7bf3b22c1 | ||
|
|
442c1583e7 | ||
|
|
217acef6b2 | ||
|
|
0eed746638 | ||
|
|
1b4f0606ca | ||
|
|
3c4d567be9 | ||
|
|
82d50a4f89 | ||
|
|
379c382fe7 | ||
|
|
559026bbf3 | ||
|
|
08e67a3a0b | ||
|
|
1a82e0d421 | ||
|
|
2e03680128 | ||
|
|
94a9ce8566 | ||
|
|
6e351dd4ac | ||
|
|
53604e427b | ||
|
|
1b52c97fd0 | ||
|
|
dbcb43f1d3 | ||
|
|
d7c8d063b6 | ||
|
|
de41c9f306 | ||
|
|
8733d2cfa2 | ||
|
|
b3737d6ff0 | ||
|
|
56a6a010ab | ||
|
|
341d9f0597 | ||
|
|
a4505ed724 | ||
|
|
b843e0fc8e | ||
|
|
c0fac5e5f1 | ||
|
|
78a945ae9e | ||
|
|
9b62849bb8 | ||
|
|
66b426311e | ||
|
|
7bee001868 | ||
|
|
22f1ce4a11 | ||
|
|
805823df7a | ||
|
|
7c5ad6b7a9 | ||
|
|
0904078bf4 | ||
|
|
9f995c6ff8 | ||
|
|
427b4f6c6d | ||
|
|
81a4d860c5 | ||
|
|
7ac95fab28 | ||
|
|
1f15c47a43 | ||
|
|
88ba66d00b | ||
|
|
2592e1e84d | ||
|
|
97750f672f | ||
|
|
54608bf38d | ||
|
|
b4226bc0f5 | ||
|
|
1943636f02 | ||
|
|
d218bff2c4 | ||
|
|
fb2c39cb57 | ||
|
|
b595940bcf | ||
|
|
49eab6b1d9 | ||
|
|
2861a4dd39 | ||
|
|
b4fa7c6c06 | ||
|
|
58bb7fc73e | ||
|
|
b16724f06a | ||
|
|
5630239a6d | ||
|
|
13be985b36 | ||
|
|
a5646a58d5 | ||
|
|
b0407e20cf | ||
|
|
8a1790d6d5 | ||
|
|
86074c9210 | ||
|
|
2f074b87f3 | ||
|
|
6214f4c2e6 | ||
|
|
390e4f30c8 | ||
|
|
4036ab7055 | ||
|
|
139bf361b4 | ||
|
|
c9f15454e4 | ||
|
|
b6a09d67e2 | ||
|
|
1770bb1b65 | ||
|
|
52549f0a7a | ||
|
|
0d7cc9e9fd | ||
|
|
e8da73799f | ||
|
|
588da4f3b8 | ||
|
|
4beb7d0595 | ||
|
|
4c275a287f | ||
|
|
5ed2b013d5 | ||
|
|
19bdcab93e | ||
|
|
45e7efce67 | ||
|
|
fbc51ae691 | ||
|
|
92eb78230b | ||
|
|
31925596f1 | ||
|
|
88b0b4a5c7 | ||
|
|
c8b2783b85 | ||
|
|
2744549c0f | ||
|
|
1c522e16a4 | ||
|
|
4b2e52f13c | ||
|
|
bbbde9261f | ||
|
|
eac8124320 | ||
|
|
64534e1b7a | ||
|
|
71ec216161 | ||
|
|
2df591c5f7 | ||
|
|
d95f0177c8 | ||
|
|
b4b128709f | ||
|
|
0fd04aa76d | ||
|
|
4abb4c674f | ||
|
|
485fb455d6 | ||
|
|
765a8b21a3 | ||
|
|
dcc940c32e | ||
|
|
059acb2da6 | ||
|
|
a1f87809b2 | ||
|
|
2fb9702a3a | ||
|
|
5196ac5c8f | ||
|
|
9db2178d05 | ||
|
|
8c6d16486d | ||
|
|
cef869a206 | ||
|
|
efbcd41fab | ||
|
|
bb9728ccd1 | ||
|
|
905285efda | ||
|
|
e1d19e00a5 | ||
|
|
add7f8133e | ||
|
|
33100818d7 | ||
|
|
974b275f6e | ||
|
|
ea2d775595 | ||
|
|
f741d42165 | ||
|
|
6945e881a8 | ||
|
|
804ff187fa | ||
|
|
de1304d19e | ||
|
|
9fff1e6e44 | ||
|
|
0d0166b4bc | ||
|
|
6c5fb36c36 | ||
|
|
1c880f5199 | ||
|
|
4681b319ca | ||
|
|
78a6aac409 | ||
|
|
20a26a6589 | ||
|
|
a4e09d0ad8 | ||
|
|
dfd5385602 | ||
|
|
7dab4601d2 | ||
|
|
81e0718a74 | ||
|
|
6e12d94f11 | ||
|
|
92535e539f | ||
|
|
4cc51ca08f | ||
|
|
63790f8854 | ||
|
|
45605db3c5 | ||
|
|
c80453f602 | ||
|
|
eb4277e7bd | ||
|
|
4245283e05 | ||
|
|
41c62288f0 | ||
|
|
c3aa63cffa | ||
|
|
1b940de0bf | ||
|
|
023c778c4d | ||
|
|
6b5fc260ca | ||
|
|
d3e075f37b | ||
|
|
194b810ad5 | ||
|
|
7cbaacde40 | ||
|
|
34017f32fb | ||
|
|
57e2adc042 | ||
|
|
e9390d0d91 | ||
|
|
f6c4aee928 | ||
|
|
a673b110f5 | ||
|
|
cfde4d634f | ||
|
|
4e4110aeb4 | ||
|
|
4149aa0945 | ||
|
|
810cc1d47e | ||
|
|
c08062420f | ||
|
|
bde7bdc11d | ||
|
|
a3e933d98b | ||
|
|
543b82567f | ||
|
|
cfc94b332a | ||
|
|
f391cf6d43 | ||
|
|
da2687174f | ||
|
|
a8ef1d49a1 | ||
|
|
feb75a7f4d | ||
|
|
27fd011e35 | ||
|
|
03f1336042 | ||
|
|
fbf7d7adb5 | ||
|
|
da041f43ee | ||
|
|
a6f57262f6 | ||
|
|
aa5ec44c27 | ||
|
|
446549a043 | ||
|
|
ef21ed901f | ||
|
|
aaadefa548 | ||
|
|
0e66d608a1 | ||
|
|
1618092b91 | ||
|
|
2f9439b690 | ||
|
|
66c8e33b47 | ||
|
|
9048ded5eb | ||
|
|
17575e1d7b | ||
|
|
49dc046b9f | ||
|
|
27d89c2311 | ||
|
|
871400f407 | ||
|
|
a1229c78a2 | ||
|
|
457060f89d | ||
|
|
cc14bc4515 | ||
|
|
bdb25c2dbe | ||
|
|
32c616512d | ||
|
|
16bd8de78e | ||
|
|
3ad0ef1ac4 | ||
|
|
cb8917557d | ||
|
|
8183d1d221 | ||
|
|
9ab0cd648e | ||
|
|
249e1152f4 | ||
|
|
d41b747aa1 | ||
|
|
f69fcb971c | ||
|
|
bfa6f656ef | ||
|
|
24b932d67a | ||
|
|
9e536ca9c3 | ||
|
|
17c4d097bb | ||
|
|
1b5756c1b1 | ||
|
|
a003034b02 | ||
|
|
bddff604c2 | ||
|
|
b2c123e0e2 | ||
|
|
be82ffc01d | ||
|
|
efcca3fb3e | ||
|
|
b91de01a4d | ||
|
|
6b1c1e0751 | ||
|
|
8985326dd8 | ||
|
|
c609f5cca0 | ||
|
|
3b15fb6211 | ||
|
|
a7eb112d36 | ||
|
|
cc43fe5191 | ||
|
|
cfced0caf1 | ||
|
|
fc18e6e774 | ||
|
|
1d57ac5c63 | ||
|
|
b55d699451 | ||
|
|
4241704261 | ||
|
|
b50d21434e | ||
|
|
c3fd1c2c40 | ||
|
|
fe4528c9be | ||
|
|
b0868967cb | ||
|
|
1aac37e14f | ||
|
|
851c2e39af | ||
|
|
d2387342ce | ||
|
|
1792eaf05f | ||
|
|
36bf3ebff3 | ||
|
|
681ba4887f | ||
|
|
b7f28c56a9 | ||
|
|
575cd8d28b | ||
|
|
3d440e21bd | ||
|
|
a10113543b | ||
|
|
50369713de | ||
|
|
2f2154c13b | ||
|
|
056206d7a4 | ||
|
|
57ab29bb85 | ||
|
|
4e69307074 | ||
|
|
608a5e2329 | ||
|
|
5126481de2 | ||
|
|
fa168bd4c0 | ||
|
|
f159a862e4 | ||
|
|
9f5ff468e1 | ||
|
|
65e07dcebc | ||
|
|
cc83b9a4ca | ||
|
|
14872e091e | ||
|
|
3d3f58b6ff | ||
|
|
b99158f8f0 | ||
|
|
2d74505208 | ||
|
|
534b03b99f | ||
|
|
4af4d8dcc6 | ||
|
|
298c5b2041 | ||
|
|
328e97336b | ||
|
|
2e10a7f097 | ||
|
|
1798cab222 | ||
|
|
ebd27de4e1 | ||
|
|
beeff131a9 | ||
|
|
408240dd97 | ||
|
|
71eb897708 | ||
|
|
02aadf7c3e | ||
|
|
98e8c6ac73 | ||
|
|
34933d2c76 | ||
|
|
a0c34b19df | ||
|
|
48ba5e5856 | ||
|
|
47183b2939 | ||
|
|
9b9062aea4 | ||
|
|
cdf02d54da | ||
|
|
413e884efb | ||
|
|
070d194c6c | ||
|
|
e54755b074 | ||
|
|
db608e33fb | ||
|
|
b543842fc0 | ||
|
|
97be015234 | ||
|
|
a656409bb5 | ||
|
|
ea45b04105 | ||
|
|
66a6ecea14 | ||
|
|
f23af3661b | ||
|
|
2f233f2389 | ||
|
|
393a8b3a86 | ||
|
|
c64115e0a0 | ||
|
|
427c472f6f | ||
|
|
2e02c37e52 | ||
|
|
71ee3c7a33 | ||
|
|
009add2b4a | ||
|
|
fb4b662756 | ||
|
|
c1882eb1ff | ||
|
|
9963c58511 | ||
|
|
7b52b8ee45 | ||
|
|
c03cb2b016 | ||
|
|
930e5c4280 | ||
|
|
630d213bd0 | ||
|
|
82b6dd1ac8 | ||
|
|
29dd30a98d | ||
|
|
68c572345b | ||
|
|
5e628d7fc6 | ||
|
|
93aae670ba | ||
|
|
7c7ac86681 | ||
|
|
076ab0f49b | ||
|
|
5fc1c04bd6 | ||
|
|
525d498e9f | ||
|
|
2b5ebb2b7b | ||
|
|
2269e92c88 | ||
|
|
7c434a0566 | ||
|
|
31bbeca2d8 | ||
|
|
158f7a17a6 | ||
|
|
8d0fde2d7f | ||
|
|
4cd4597082 | ||
|
|
1624854bd7 | ||
|
|
c099cb2a3d | ||
|
|
1c57b98c9a | ||
|
|
a29ab32876 | ||
|
|
91456fff04 | ||
|
|
13af55f9fc | ||
|
|
66454d378b | ||
|
|
92d09f2ea1 | ||
|
|
fac5e5f02d | ||
|
|
4ed97cae3a | ||
|
|
ceef536b79 | ||
|
|
7fa56fde0b | ||
|
|
7bd9f8ee38 | ||
|
|
2dd4dfda80 | ||
|
|
f7ac0d878b | ||
|
|
fe2723b585 | ||
|
|
7fecec6599 | ||
|
|
44e19775f5 | ||
|
|
5b25c65521 | ||
|
|
cb977ab6b6 | ||
|
|
2b21f27ac2 | ||
|
|
ec984d25db | ||
|
|
7000447915 | ||
|
|
486d61736b | ||
|
|
a4683ab874 | ||
|
|
b26c53d7e2 | ||
|
|
86b8744838 | ||
|
|
6f04e00ea1 | ||
|
|
9eadaf7079 | ||
|
|
fbebabe2e6 | ||
|
|
48184cc94f | ||
|
|
923c28cb77 | ||
|
|
03c1906f13 | ||
|
|
2b7b4d6fea | ||
|
|
abae7b6210 | ||
|
|
12a862c277 | ||
|
|
38c6af9b46 | ||
|
|
2eaa9b3b69 | ||
|
|
37b0e4018c | ||
|
|
57db72d287 | ||
|
|
ec02bab97c | ||
|
|
94ed3e800a | ||
|
|
1e1ccba238 | ||
|
|
a0d0c923f3 | ||
|
|
aba16687b0 | ||
|
|
797a71695f | ||
|
|
55c5a8560c | ||
|
|
9e768a4c7c | ||
|
|
42d6c2bd6b | ||
|
|
810e62665f | ||
|
|
bec1642123 | ||
|
|
08f7b4c53c | ||
|
|
d15abd7f28 | ||
|
|
d367eb41d2 | ||
|
|
7c164efdcc | ||
|
|
bd76cdbd20 | ||
|
|
dc6dd6d2a8 | ||
|
|
40e51aba6b | ||
|
|
f9dc26d413 | ||
|
|
2fe082beea | ||
|
|
2835976b81 | ||
|
|
1968e8ced4 | ||
|
|
1965daff50 | ||
|
|
f4881872f7 | ||
|
|
f1f0191d98 | ||
|
|
6ace416224 | ||
|
|
52c87bc8b3 | ||
|
|
edfacbb5c6 | ||
|
|
564371eae7 | ||
|
|
e09ed98cf1 | ||
|
|
2b0106f7e2 | ||
|
|
0e3db591c0 | ||
|
|
224902af9f | ||
|
|
131f578367 | ||
|
|
6b794285c6 | ||
|
|
e78f85bf65 | ||
|
|
9be0b6d918 | ||
|
|
3aa31111de | ||
|
|
81baad6acd | ||
|
|
25ef47a78f | ||
|
|
7d7d593fee | ||
|
|
766ffde4ca | ||
|
|
4534b56c1f | ||
|
|
ee0da5e4c1 | ||
|
|
e5d60c0895 | ||
|
|
5ccf4d3311 | ||
|
|
1f3143cfab | ||
|
|
f5d128b0d5 | ||
|
|
2268ae3967 | ||
|
|
921da1a2ea | ||
|
|
ebd4092bee | ||
|
|
b6fd7a2b93 | ||
|
|
210ba8109c | ||
|
|
25afc19895 | ||
|
|
68ded1a5b4 | ||
|
|
317ccdac66 | ||
|
|
43a3251bee | ||
|
|
94ab56542f | ||
|
|
92f1881dfe | ||
|
|
ec4c018f73 | ||
|
|
2c25dd1970 | ||
|
|
06fbe69b09 | ||
|
|
4bd154e7dc | ||
|
|
516756fad8 | ||
|
|
2b3cfb39a1 | ||
|
|
e9cbb3e547 | ||
|
|
1744801867 | ||
|
|
64cb9a3c62 | ||
|
|
0e947788e0 | ||
|
|
60b59db94f | ||
|
|
6d7032a906 | ||
|
|
b452fc9e85 | ||
|
|
dac4dec0ad | ||
|
|
eae79e93f3 | ||
|
|
ee33765932 | ||
|
|
8af83fef85 | ||
|
|
5f0647f0ab | ||
|
|
fe7f17f422 | ||
|
|
e5a2ebda0e | ||
|
|
2204375788 | ||
|
|
c81dfee43c | ||
|
|
cb175e513d | ||
|
|
32da1d81d5 | ||
|
|
6f0a5c9f0c | ||
|
|
1529e0bf04 | ||
|
|
6021e6cf81 | ||
|
|
427d33c7b7 | ||
|
|
95f332cb13 | ||
|
|
db4567599b | ||
|
|
0667c7aedd | ||
|
|
1c5596de66 | ||
|
|
905707284d | ||
|
|
67f46a54b8 | ||
|
|
b8db3853f1 | ||
|
|
a9015c0ee6 | ||
|
|
60db1e7f82 | ||
|
|
6dae811dae | ||
|
|
a20599efb4 | ||
|
|
27b8d73f66 | ||
|
|
025e8505d5 | ||
|
|
16f5900252 | ||
|
|
e2d699cb32 | ||
|
|
156f75628a | ||
|
|
8fad65d1d5 | ||
|
|
0867804504 | ||
|
|
f0900fdc5f | ||
|
|
0be744b567 | ||
|
|
243b0bb626 | ||
|
|
0b32b42134 | ||
|
|
ef1cd4318b | ||
|
|
6e39d03a52 | ||
|
|
e21e52569c | ||
|
|
7b7dbd36d6 | ||
|
|
d03b5c9481 | ||
|
|
93e060aeb2 | ||
|
|
00fd2b8503 | ||
|
|
a84351ad0c | ||
|
|
25a78c77f2 | ||
|
|
a35fa611ff | ||
|
|
0b54c9a5d6 | ||
|
|
21fd545381 | ||
|
|
6c1eb97a18 | ||
|
|
2070cc45d4 | ||
|
|
f1bc95622d | ||
|
|
3b3b23a2fd | ||
|
|
e3d3cdb9d2 | ||
|
|
7b874473d3 | ||
|
|
9d7b9293eb | ||
|
|
957e3e0f08 | ||
|
|
5994e25841 | ||
|
|
2f0a7eee8d | ||
|
|
1a3e14a24c | ||
|
|
a6b67fb8a6 | ||
|
|
843bdc4d55 | ||
|
|
2aee19f0e1 | ||
|
|
9f3be5e28d | ||
|
|
f69f803f0f | ||
|
|
2eca26b210 | ||
|
|
2ca0ccbe0c | ||
|
|
cf243adfe3 | ||
|
|
c908eeed5c | ||
|
|
caa1223e5c | ||
|
|
62cf3d39e1 | ||
|
|
2437b68d5d | ||
|
|
09fc798e80 | ||
|
|
7ae6febedc | ||
|
|
39ae0f45fd | ||
|
|
855e911398 | ||
|
|
73a77cda3d | ||
|
|
6c9346011d | ||
|
|
28c4732abc | ||
|
|
1cef30cca0 | ||
|
|
af787e1c85 | ||
|
|
70f6fa2bc9 | ||
|
|
bef87403db | ||
|
|
5890029f71 | ||
|
|
1f4fe0a8c0 | ||
|
|
519f6a3d4a | ||
|
|
530f758905 | ||
|
|
fb7349bc87 | ||
|
|
dc390ab03a | ||
|
|
6f6cc6a686 | ||
|
|
136e2f2afe | ||
|
|
73bb72dbcf | ||
|
|
4b2f774f53 | ||
|
|
0a4151fbfa | ||
|
|
07d187151e | ||
|
|
47f606f0df | ||
|
|
18125dad84 | ||
|
|
f317b96262 | ||
|
|
c3d8c2a919 | ||
|
|
5c1e6dce99 | ||
|
|
0321bcb715 | ||
|
|
aac5d4704e | ||
|
|
fb2c6801d4 | ||
|
|
bb5d61fb14 | ||
|
|
3b3012353f | ||
|
|
adad53d526 | ||
|
|
9b21786062 | ||
|
|
1b4460e37d | ||
|
|
482dad3036 | ||
|
|
7276027861 | ||
|
|
62055522fb | ||
|
|
0366aa2217 | ||
|
|
2b79b7b29f | ||
|
|
3bfe5d86c0 | ||
|
|
02243173c2 | ||
|
|
a84a95055d | ||
|
|
b8bf35be17 | ||
|
|
b9a4760b44 | ||
|
|
3ba579f680 | ||
|
|
302da18c81 | ||
|
|
4fbc623706 | ||
|
|
b9abbc0a85 | ||
|
|
f1035405e6 | ||
|
|
720297946b | ||
|
|
988b3b1ab2 | ||
|
|
88bfc6bb2b | ||
|
|
a0dc88165f | ||
|
|
dccfc11b4f | ||
|
|
42042155d4 | ||
|
|
6b2459317b | ||
|
|
08a0914a78 | ||
|
|
4f41070b45 | ||
|
|
d2031955ec | ||
|
|
7baff4e249 | ||
|
|
91c08ed48e | ||
|
|
acf53f69e0 | ||
|
|
bb237f109a | ||
|
|
44d4ec83fc | ||
|
|
33124a31bd | ||
|
|
1e90aab2a6 | ||
|
|
44ced27bee | ||
|
|
c3def09c38 | ||
|
|
afff339d28 | ||
|
|
b7a597f925 | ||
|
|
ae84ee39f3 | ||
|
|
73e60586c9 | ||
|
|
b56bc77dcc | ||
|
|
32a852c0bf | ||
|
|
40e622dfc8 | ||
|
|
994861a6fa | ||
|
|
b770431102 | ||
|
|
2fd108c864 | ||
|
|
914f7be644 | ||
|
|
1fdb8c006d | ||
|
|
b46404b139 | ||
|
|
1dfd14fbc0 | ||
|
|
a1ddc629dc | ||
|
|
9cfc2c881c | ||
|
|
b7f9db69ca | ||
|
|
60eb4f3385 | ||
|
|
41e97ec042 | ||
|
|
fe838726b0 | ||
|
|
1cfce3d0b6 | ||
|
|
f64cb1566d | ||
|
|
9b204c012a | ||
|
|
6b33ac1e85 | ||
|
|
18e578bd09 | ||
|
|
e8a925b6af | ||
|
|
db6e0cd2ef | ||
|
|
f6738ec354 | ||
|
|
1067ec19a8 | ||
|
|
e7a7842808 | ||
|
|
ce3bb35c8c | ||
|
|
2d7b531bba | ||
|
|
2d81490ac4 | ||
|
|
5939d72d8f | ||
|
|
d1bd4973ad | ||
|
|
f407f0c5a6 | ||
|
|
40eccfa61a | ||
|
|
c79aa542c9 | ||
|
|
366ffedf08 | ||
|
|
c796514de8 | ||
|
|
e75ec5b51f | ||
|
|
702c3578a3 | ||
|
|
0eecf8187d | ||
|
|
e9058b7498 | ||
|
|
25b266e809 | ||
|
|
2e638598e4 | ||
|
|
8562635231 | ||
|
|
b358754b46 | ||
|
|
64d04f0712 | ||
|
|
ebdafe54aa | ||
|
|
829ecac8e3 | ||
|
|
d22e3e093a | ||
|
|
a9662e8b7b | ||
|
|
5f98db9b76 | ||
|
|
664a110fd1 | ||
|
|
3b5044b55b | ||
|
|
e848151b50 | ||
|
|
91197bd1aa | ||
|
|
d1fdaa17d6 | ||
|
|
5660c404f0 | ||
|
|
79c1fff853 | ||
|
|
962c070a75 | ||
|
|
5bdd5022b2 | ||
|
|
370bad1380 | ||
|
|
17b8247c3d | ||
|
|
c0d1dec847 | ||
|
|
5889344dd2 | ||
|
|
5f80fe00d5 | ||
|
|
b225b9a710 | ||
|
|
72c51b19a5 | ||
|
|
679c149164 | ||
|
|
92a18defce | ||
|
|
859c9ec68d | ||
|
|
b4aeb1b3e7 | ||
|
|
8f1313519f | ||
|
|
1c0a25d01b | ||
|
|
a5df5e7333 | ||
|
|
4bd3e1d0c2 | ||
|
|
ad145541af | ||
|
|
37153018b9 | ||
|
|
90f1318596 | ||
|
|
4a5ae78d35 | ||
|
|
097879b839 | ||
|
|
2d9ad32d93 | ||
|
|
4d257f469b | ||
|
|
521dc915dc | ||
|
|
5ea669f091 | ||
|
|
aa6e8d91c2 | ||
|
|
089dc282c3 | ||
|
|
329b465a63 | ||
|
|
6785dc19ab | ||
|
|
c515cc2823 | ||
|
|
848f89e8a7 | ||
|
|
bd7f76190c | ||
|
|
514afcd445 | ||
|
|
3e13abf974 | ||
|
|
4cfe32f626 | ||
|
|
f36954764d | ||
|
|
1c98037eed | ||
|
|
2d732e3a99 | ||
|
|
53d105714a | ||
|
|
78aad113a6 | ||
|
|
d2c28c819f | ||
|
|
3d75333e95 | ||
|
|
7e5bdb5321 | ||
|
|
be74969d70 | ||
|
|
196f863441 | ||
|
|
823258e311 | ||
|
|
e100a4310b | ||
|
|
681c912533 | ||
|
|
6339c09c01 | ||
|
|
96634298e4 | ||
|
|
65568f502e | ||
|
|
1fb9f0185a | ||
|
|
74c5256d6d | ||
|
|
556823328f | ||
|
|
dc6818de2b | ||
|
|
693f6b2d14 | ||
|
|
3991b5f251 | ||
|
|
9afb924e84 | ||
|
|
7a82e7370f | ||
|
|
709e9fd01a | ||
|
|
e16f87f973 | ||
|
|
d4ddac704c | ||
|
|
bc92d61616 | ||
|
|
4884d81704 | ||
|
|
d99bdb7710 | ||
|
|
eaa265b4fa | ||
|
|
8fa3cae618 | ||
|
|
0027b27ea3 | ||
|
|
2bcc653293 | ||
|
|
ebfff5efee | ||
|
|
ac0207bf2e | ||
|
|
8068880aab | ||
|
|
064fe0cfd9 | ||
|
|
096fe17135 | ||
|
|
4d501b38b8 | ||
|
|
f79e9a1951 | ||
|
|
64a62fb0c1 | ||
|
|
2c45989fea | ||
|
|
f779e4ddc0 | ||
|
|
7be0016e45 | ||
|
|
f616ebb083 | ||
|
|
ce2d115087 | ||
|
|
cf403bc45c | ||
|
|
fa89e1776b | ||
|
|
1a8c7dad5f | ||
|
|
e87fa1600d | ||
|
|
3003185676 | ||
|
|
ebfa2018dd | ||
|
|
68abf10c25 | ||
|
|
284cde8b1b | ||
|
|
466501c62f | ||
|
|
9c34bb4160 | ||
|
|
8e730cdc61 | ||
|
|
41760dfb09 | ||
|
|
67e968926c | ||
|
|
2f7fe1189c | ||
|
|
b7d73f63cd | ||
|
|
a8cec5f02f | ||
|
|
79d2f738a2 | ||
|
|
7701557b99 | ||
|
|
ed8c2269cb | ||
|
|
f1b47485cd |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"es2021": true,
|
||||
"node": true
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
@@ -13,27 +13,38 @@
|
||||
"src/headless/tsconfig.json",
|
||||
"test/api/tsconfig.json",
|
||||
"test/benchmark/tsconfig.json",
|
||||
"addons/xterm-addon-attach/src/tsconfig.json",
|
||||
"addons/xterm-addon-attach/test/tsconfig.json",
|
||||
"addons/xterm-addon-canvas/src/tsconfig.json",
|
||||
"addons/xterm-addon-fit/src/tsconfig.json",
|
||||
"addons/xterm-addon-fit/test/tsconfig.json",
|
||||
"addons/xterm-addon-ligatures/src/tsconfig.json",
|
||||
"addons/xterm-addon-search/src/tsconfig.json",
|
||||
"addons/xterm-addon-search/test/tsconfig.json",
|
||||
"addons/xterm-addon-serialize/src/tsconfig.json",
|
||||
"addons/xterm-addon-serialize/test/tsconfig.json",
|
||||
"addons/xterm-addon-serialize/benchmark/tsconfig.json",
|
||||
"addons/xterm-addon-unicode11/src/tsconfig.json",
|
||||
"addons/xterm-addon-unicode11/test/tsconfig.json",
|
||||
"addons/xterm-addon-web-links/src/tsconfig.json",
|
||||
"addons/xterm-addon-web-links/test/tsconfig.json",
|
||||
"addons/xterm-addon-webgl/src/tsconfig.json",
|
||||
"addons/xterm-addon-webgl/test/tsconfig.json"
|
||||
"test/playwright/tsconfig.json",
|
||||
"addons/addon-attach/src/tsconfig.json",
|
||||
"addons/addon-attach/test/tsconfig.json",
|
||||
"addons/addon-canvas/src/tsconfig.json",
|
||||
"addons/addon-canvas/test/tsconfig.json",
|
||||
"addons/addon-fit/src/tsconfig.json",
|
||||
"addons/addon-fit/test/tsconfig.json",
|
||||
"addons/addon-image/src/tsconfig.json",
|
||||
"addons/addon-image/test/tsconfig.json",
|
||||
"addons/addon-ligatures/src/tsconfig.json",
|
||||
"addons/addon-search/src/tsconfig.json",
|
||||
"addons/addon-search/test/tsconfig.json",
|
||||
"addons/addon-serialize/src/tsconfig.json",
|
||||
"addons/addon-serialize/test/tsconfig.json",
|
||||
"addons/addon-serialize/benchmark/tsconfig.json",
|
||||
"addons/addon-unicode11/src/tsconfig.json",
|
||||
"addons/addon-unicode11/test/tsconfig.json",
|
||||
"addons/addon-unicode-graphemes/src/tsconfig.json",
|
||||
"addons/addon-unicode-graphemes/test/tsconfig.json",
|
||||
"addons/addon-unicode-graphemes/benchmark/tsconfig.json",
|
||||
"addons/addon-web-links/src/tsconfig.json",
|
||||
"addons/addon-web-links/test/tsconfig.json",
|
||||
"addons/addon-webgl/src/tsconfig.json",
|
||||
"addons/addon-webgl/test/tsconfig.json"
|
||||
],
|
||||
"sourceType": "module"
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"addons/*/src/third-party/*.ts",
|
||||
"out/*",
|
||||
"out-test/*",
|
||||
"**/inwasm-sdks/*",
|
||||
"**/typings/*.d.ts",
|
||||
"**/node_modules",
|
||||
"**/*.js"
|
||||
@@ -87,7 +98,12 @@
|
||||
],
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"warn",
|
||||
{ "selector": "default", "format": ["camelCase"] },
|
||||
{ "selector": "default", "format": ["camelCase"],
|
||||
"filter": {
|
||||
"regex": "^[a-z]",
|
||||
"match": true
|
||||
}
|
||||
},
|
||||
// variableLike
|
||||
{ "selector": "variable", "format": ["camelCase", "UPPER_CASE"] },
|
||||
{ "selector": "variable", "filter": "^I.+Service$", "format": ["PascalCase"], "prefix": ["I"] },
|
||||
@@ -96,7 +112,12 @@
|
||||
{ "selector": "memberLike", "modifiers": ["protected"], "format": ["camelCase"], "leadingUnderscore": "require" },
|
||||
{ "selector": "enumMember", "format": ["UPPER_CASE"] },
|
||||
// memberLike - Allow enum-like objects to use UPPER_CASE
|
||||
{ "selector": "property", "modifiers": ["public"], "format": ["camelCase", "UPPER_CASE"] },
|
||||
{ "selector": "property", "modifiers": ["public"], "format": ["camelCase", "UPPER_CASE"],
|
||||
"filter": {
|
||||
"regex": "^[a-z]",
|
||||
"match": true
|
||||
}
|
||||
},
|
||||
// restrict on* naming for events only
|
||||
{ "selector": "method", "modifiers": ["public"], "format": ["camelCase", "UPPER_CASE"], "custom": {
|
||||
"regex": "^on[A-Z].+",
|
||||
@@ -114,6 +135,11 @@
|
||||
{ "selector": "typeLike", "format": ["PascalCase"] },
|
||||
{ "selector": "interface", "format": ["PascalCase"], "prefix": ["I"] }
|
||||
],
|
||||
"@typescript-eslint/no-confusing-void-expression": [
|
||||
"warn",
|
||||
{ "ignoreArrowShorthand": true }
|
||||
],
|
||||
"@typescript-eslint/no-useless-constructor": "warn",
|
||||
"@typescript-eslint/prefer-namespace-keyword": "warn",
|
||||
"@typescript-eslint/type-annotation-spacing": "warn",
|
||||
"@typescript-eslint/quotes": [
|
||||
@@ -146,6 +172,16 @@
|
||||
"jsdoc/check-param-names": 1,
|
||||
"jsdoc/no-multi-asterisks": 1,
|
||||
"keyword-spacing": "warn",
|
||||
"max-len": [
|
||||
"warn",
|
||||
{
|
||||
"code": 1000, // Don't enforce for code
|
||||
"comments": 100,
|
||||
"ignoreTrailingComments": true,
|
||||
"ignoreUrls": true,
|
||||
"ignorePattern": "^ *((?<vt_comment>(//|\\*) @vt)|(?<table_comment>\\* \\| )|(?<commented_code>// ))"
|
||||
}
|
||||
],
|
||||
"new-parens": "warn",
|
||||
"no-duplicate-imports": "warn",
|
||||
"no-else-return": [
|
||||
@@ -189,6 +225,10 @@
|
||||
],
|
||||
"no-trailing-spaces": "warn",
|
||||
"no-unsafe-finally": "warn",
|
||||
"no-unused-vars": ["warn", {
|
||||
"vars": "all",
|
||||
"args": "none"
|
||||
}],
|
||||
"no-var": "warn",
|
||||
"one-var": [
|
||||
"warn",
|
||||
@@ -210,9 +250,14 @@
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["**/*.test.ts"],
|
||||
"files": [
|
||||
"**/*.api.ts",
|
||||
"**/*.test.ts"
|
||||
],
|
||||
"rules": {
|
||||
"object-curly-spacing": "off"
|
||||
"object-curly-spacing": "off",
|
||||
"max-len": "off",
|
||||
"no-unused-vars": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true,
|
||||
"node": true
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": [
|
||||
"@typescript-eslint",
|
||||
"jsdoc"
|
||||
],
|
||||
"rules": {
|
||||
"no-extra-semi": "error",
|
||||
"@typescript-eslint/array-type": [
|
||||
"warn",
|
||||
{
|
||||
"default": "array",
|
||||
"readonly": "generic"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/explicit-function-return-type": [
|
||||
"warn",
|
||||
{
|
||||
"allowExpressions": true
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/indent": [
|
||||
"warn",
|
||||
2
|
||||
],
|
||||
"@typescript-eslint/member-delimiter-style": [
|
||||
"warn",
|
||||
{
|
||||
"multiline": {
|
||||
"delimiter": "semi",
|
||||
"requireLast": true
|
||||
},
|
||||
"singleline": {
|
||||
"delimiter": "comma",
|
||||
"requireLast": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"warn",
|
||||
{ "selector": "typeLike", "format": ["PascalCase"] },
|
||||
{ "selector": "interface", "format": ["PascalCase"], "prefix": ["I"] }
|
||||
],
|
||||
"@typescript-eslint/prefer-namespace-keyword": "warn",
|
||||
"@typescript-eslint/type-annotation-spacing": "warn",
|
||||
"@typescript-eslint/quotes": [
|
||||
"warn",
|
||||
"single",
|
||||
{ "allowTemplateLiterals": true }
|
||||
],
|
||||
"@typescript-eslint/semi": [
|
||||
"warn",
|
||||
"always"
|
||||
],
|
||||
"comma-dangle": [
|
||||
"warn",
|
||||
{
|
||||
"objects": "never",
|
||||
"arrays": "never",
|
||||
"functions": "never"
|
||||
}
|
||||
],
|
||||
"curly": [
|
||||
"warn",
|
||||
"multi-line"
|
||||
],
|
||||
"eol-last": "warn",
|
||||
"eqeqeq": [
|
||||
"warn",
|
||||
"always"
|
||||
],
|
||||
"jsdoc/check-alignment": 1,
|
||||
"jsdoc/check-param-names": 1,
|
||||
"keyword-spacing": "warn",
|
||||
"max-len": [
|
||||
"warn",
|
||||
{
|
||||
"code": 1000, // Don't enforce for code
|
||||
"comments": 80,
|
||||
"ignoreUrls": true,
|
||||
"ignorePattern": "^ *(?<ps_description>\\* Ps=)"
|
||||
}
|
||||
],
|
||||
"no-irregular-whitespace": "warn",
|
||||
"no-trailing-spaces": "warn",
|
||||
"object-curly-spacing": [
|
||||
"warn",
|
||||
"always"
|
||||
],
|
||||
"spaced-comment": [
|
||||
"warn",
|
||||
"always",
|
||||
{
|
||||
"markers": ["/"],
|
||||
"exceptions": ["-"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies
|
||||
run: yarn --frozen-lockfile
|
||||
- name: Build
|
||||
run: yarn setup
|
||||
- name: Zip artifacts
|
||||
run: |
|
||||
zip -r compressed-build \
|
||||
./out/* \
|
||||
./out-test/* \
|
||||
./addons/addon-attach/out/* \
|
||||
./addons/addon-attach/out-test/* \
|
||||
./addons/addon-canvas/out/* \
|
||||
./addons/addon-canvas/out-test/* \
|
||||
./addons/addon-fit/out/* \
|
||||
./addons/addon-fit/out-test/* \
|
||||
./addons/addon-image/out/* \
|
||||
./addons/addon-image/out-test/* \
|
||||
./addons/addon-ligatures/out/* \
|
||||
./addons/addon-ligatures/out-test/* \
|
||||
./addons/addon-search/out/* \
|
||||
./addons/addon-search/out-test/* \
|
||||
./addons/addon-serialize/out/* \
|
||||
./addons/addon-serialize/out-test/* \
|
||||
./addons/addon-unicode11/out/* \
|
||||
./addons/addon-unicode11/out-test/* \
|
||||
./addons/addon-unicode-graphemes/out/* \
|
||||
./addons/addon-unicode-graphemes/out-test/* \
|
||||
./addons/addon-web-links/out/* \
|
||||
./addons/addon-web-links/out-test/* \
|
||||
./addons/addon-webgl/out/* \
|
||||
./addons/addon-webgl/out-test/*
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: build-artifacts
|
||||
path: compressed-build.zip
|
||||
if-no-files-found: error
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn --frozen-lockfile
|
||||
yarn install-addons
|
||||
- name: Lint code
|
||||
env:
|
||||
NODE_OPTIONS: --max_old_space_size=4096
|
||||
run: yarn lint
|
||||
- name: Lint API
|
||||
run: yarn lint-api
|
||||
|
||||
test-unit-coverage:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn --frozen-lockfile
|
||||
yarn install-addons
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: build-artifacts
|
||||
- name: Unzip artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "$RUNNER_OS" == "Windows" ]; then
|
||||
pwsh -Command "7z x compressed-build.zip -aoa -o${{ github.workspace }}"
|
||||
else
|
||||
unzip -o compressed-build.zip
|
||||
fi
|
||||
ls -R
|
||||
- name: Unit test coverage
|
||||
run: |
|
||||
yarn test-unit-coverage --forbid-only
|
||||
EXIT_CODE=$?
|
||||
./node_modules/.bin/nyc report --reporter=cobertura
|
||||
exit $EXIT_CODE
|
||||
|
||||
test-unit-parallel:
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18]
|
||||
runs-on: [ubuntu, macos, windows]
|
||||
runs-on: ${{ matrix.runs-on }}-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}.x
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn --frozen-lockfile
|
||||
yarn install-addons
|
||||
- name: Wait for build job
|
||||
uses: NathanFirmo/wait-for-other-job@v1.1.1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
job: build
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: build-artifacts
|
||||
- name: Unzip artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "$RUNNER_OS" == "Windows" ]; then
|
||||
pwsh -Command "7z x compressed-build.zip -aoa -o${{ github.workspace }}"
|
||||
else
|
||||
unzip -o compressed-build.zip
|
||||
fi
|
||||
ls -R
|
||||
- name: Unit tests
|
||||
run: yarn test-unit --forbid-only
|
||||
|
||||
test-api-parallel:
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18] # just one as integration tests are about testing in browser
|
||||
runs-on: [ubuntu] # macos is flaky
|
||||
browser: [chromium, firefox]
|
||||
runs-on: ${{ matrix.runs-on }}-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}.x
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn --frozen-lockfile
|
||||
yarn install-addons
|
||||
- name: Install playwright
|
||||
run: npx playwright install
|
||||
- name: Wait for build job
|
||||
uses: NathanFirmo/wait-for-other-job@v1.1.1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
job: build
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: build-artifacts
|
||||
- name: Unzip artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "$RUNNER_OS" == "Windows" ]; then
|
||||
pwsh -Command "7z x compressed-build.zip -aoa -o${{ github.workspace }}"
|
||||
else
|
||||
unzip -o compressed-build.zip
|
||||
fi
|
||||
ls -R
|
||||
- name: Integration tests (${{ matrix.browser }})
|
||||
run: yarn test-api-${{ matrix.browser }} --headless --forbid-only
|
||||
|
||||
test-playwright-parallel:
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18] # just one as integration tests are about testing in browser
|
||||
runs-on: [ubuntu] # macos is flaky
|
||||
browser: [chromium, firefox, webkit]
|
||||
runs-on: ${{ matrix.runs-on }}-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}.x
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn --frozen-lockfile
|
||||
yarn install-addons
|
||||
- name: Install playwright
|
||||
run: npx playwright install --with-deps ${{ matrix.browser }}
|
||||
- name: Wait for build job
|
||||
uses: NathanFirmo/wait-for-other-job@v1.1.1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
job: build
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: build-artifacts
|
||||
- name: Unzip artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "$RUNNER_OS" == "Windows" ]; then
|
||||
pwsh -Command "7z x compressed-build.zip -aoa -o${{ github.workspace }}"
|
||||
else
|
||||
unzip -o compressed-build.zip
|
||||
fi
|
||||
ls -R
|
||||
- name: Build demo
|
||||
run: yarn build-demo
|
||||
- name: Integration tests (core) # Tests use 50% workers to reduce flakiness
|
||||
run: yarn test-playwright-${{ matrix.browser }} --workers=50% --forbid-only --suite=core
|
||||
- name: Integration tests (addon-canvas)
|
||||
run: yarn test-playwright-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-canvas
|
||||
- name: Integration tests (addon-webgl)
|
||||
run: yarn test-playwright-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-webgl
|
||||
|
||||
test-api:
|
||||
needs: build
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18] # just one as integration tests are about testing in browser
|
||||
runs-on: [windows] # macos is flaky
|
||||
browser: [chromium, firefox]
|
||||
runs-on: ${{ matrix.runs-on }}-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}.x
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn --frozen-lockfile
|
||||
yarn install-addons
|
||||
- name: Install playwright
|
||||
run: npx playwright install
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: build-artifacts
|
||||
- name: Unzip artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "$RUNNER_OS" == "Windows" ]; then
|
||||
pwsh -Command "7z x compressed-build.zip -aoa -o${{ github.workspace }}"
|
||||
else
|
||||
unzip -o compressed-build.zip
|
||||
fi
|
||||
ls -R
|
||||
- name: Integration tests (${{ matrix.browser }})
|
||||
run: yarn test-api-${{ matrix.browser }} --headless --forbid-only
|
||||
|
||||
release-dry-run:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}.x
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn --frozen-lockfile
|
||||
yarn install-addons
|
||||
- name: Install playwright
|
||||
run: npx playwright install
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: build-artifacts
|
||||
- name: Unzip artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "$RUNNER_OS" == "Windows" ]; then
|
||||
pwsh -Command "7z x compressed-build.zip -aoa -o${{ github.workspace }}"
|
||||
else
|
||||
unzip -o compressed-build.zip
|
||||
fi
|
||||
ls -R
|
||||
- name: Package headless
|
||||
run: |
|
||||
yarn package-headless
|
||||
node ./bin/package_headless.js
|
||||
- name: Publish to npm (dry run)
|
||||
run: node ./bin/publish.js --dry
|
||||
@@ -1,21 +1,9 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ "master" ]
|
||||
schedule:
|
||||
- cron: '41 17 * * 0'
|
||||
@@ -33,40 +21,15 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
|
||||
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||
|
||||
# - run: |
|
||||
# echo "Run, Build Application using script"
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
# If a commit reaches master, assume it has passed CI via PR and publish
|
||||
# without running tests to save time to publish
|
||||
branches: [ "master" ]
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js 18.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
cache: 'yarn'
|
||||
- name: Install dependencies
|
||||
run: yarn --frozen-lockfile
|
||||
- name: Build
|
||||
run: yarn setup
|
||||
- name: Package headless
|
||||
run: |
|
||||
yarn package-headless
|
||||
node ./bin/package_headless.js
|
||||
- name: Publish to npm
|
||||
env:
|
||||
NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: node ./bin/publish.js
|
||||
@@ -43,7 +43,14 @@
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Demo Client",
|
||||
"name": "Demo Client (Chrome)",
|
||||
"url": "http://127.0.0.1:3000",
|
||||
"webRoot": "${workspaceFolder}/"
|
||||
},
|
||||
{
|
||||
"type": "msedge",
|
||||
"request": "launch",
|
||||
"name": "Demo Client (Edge)",
|
||||
"url": "http://127.0.0.1:3000",
|
||||
"webRoot": "${workspaceFolder}/"
|
||||
},
|
||||
@@ -52,11 +59,9 @@
|
||||
"request": "launch",
|
||||
"name": "Demo Server",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"start-debug"
|
||||
],
|
||||
"port": 9229,
|
||||
"runtimeArgs": ["start"],
|
||||
"stopOnEntry": true,
|
||||
"runtimeVersion": "16",
|
||||
"serverReadyAction": {
|
||||
"action": "openExternally",
|
||||
"pattern": "App listening to (http://.*?:[0-9]+)"
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"files.associations": {
|
||||
".eslintrc.json.typings": "jsonc"
|
||||
},
|
||||
"typescript.preferences.importModuleSpecifier": "non-relative",
|
||||
"typescript.preferences.quoteStyle": "single",
|
||||
"mochaExplorer.envPath": ".mocha.env",
|
||||
|
||||
@@ -28,6 +28,10 @@ opening an issue, read these pointers.
|
||||
- Be polite. Issues with an indignant or belligerent tone tend to be moved to the
|
||||
bottom of the pile.
|
||||
|
||||
## Answering discussion questions
|
||||
|
||||
Issues are only meant to track (likely) feature requests and bugs. We use [GitHub Discussions](https://github.com/xtermjs/xterm.js/discussions) for general Q&A as well as discussing possible features. If you want to help out, many questions get asked over at the [discussions page](https://github.com/xtermjs/xterm.js/discussions) which could use an expert as the core team is often stretched thin.
|
||||
|
||||
## Contributing code
|
||||
|
||||
You can find issues to work on by looking at the [help wanted](https://github.com/xtermjs/xterm.js/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) or [good first issue](https://github.com/xtermjs/xterm.js/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) issues. It's a good idea to comment on the issue saying that you're taking it, just in case someone else comes along and you duplicate work. Once you have your issue, here are the steps to contribute:
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# [](https://xtermjs.org)
|
||||
|
||||
[](https://dev.azure.com/xtermjs/xterm.js/_build/latest?definitionId=3)
|
||||
|
||||
Xterm.js is a front-end component written in TypeScript that lets applications bring fully-featured terminals to their users in the browser. It's used by popular projects such as VS Code, Hyper and Theia.
|
||||
|
||||
## Features
|
||||
@@ -23,7 +21,7 @@ Xterm.js is a front-end component written in TypeScript that lets applications b
|
||||
First, you need to install the module, we ship exclusively through [npm](https://www.npmjs.com/), so you need that installed and then add xterm.js as a dependency by running:
|
||||
|
||||
```bash
|
||||
npm install xterm
|
||||
npm install @xterm/xterm
|
||||
```
|
||||
|
||||
To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to the head of your HTML page. Then create a `<div id="terminal"></div>` onto which xterm can attach itself. Finally, instantiate the `Terminal` object and then call the `open` function with the DOM object of the `div`.
|
||||
@@ -32,8 +30,8 @@ To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to t
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="node_modules/xterm/css/xterm.css" />
|
||||
<script src="node_modules/xterm/lib/xterm.js"></script>
|
||||
<link rel="stylesheet" href="node_modules/@xterm/xterm/css/xterm.css" />
|
||||
<script src="node_modules/@xterm/xterm/lib/xterm.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="terminal"></div>
|
||||
@@ -51,7 +49,7 @@ To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to t
|
||||
The recommended way to load xterm.js is via the ES6 module syntax:
|
||||
|
||||
```javascript
|
||||
import { Terminal } from 'xterm';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
```
|
||||
|
||||
### Addons
|
||||
@@ -61,14 +59,14 @@ import { Terminal } from 'xterm';
|
||||
Addons are separate modules that extend the `Terminal` by building on the [xterm.js API](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm.d.ts). To use an addon, you first need to install it in your project:
|
||||
|
||||
```bash
|
||||
npm i -S xterm-addon-web-links
|
||||
npm i -S @xterm/addon-web-links
|
||||
```
|
||||
|
||||
Then import the addon, instantiate it and call `Terminal.loadAddon`:
|
||||
|
||||
```ts
|
||||
import { Terminal } from 'xterm';
|
||||
import { WebLinksAddon } from 'xterm-addon-web-links';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { WebLinksAddon } from '@xterm/addon-web-links';
|
||||
|
||||
const terminal = new Terminal();
|
||||
// Load WebLinksAddon on terminal, this is all that's needed to get web links
|
||||
@@ -78,10 +76,15 @@ terminal.loadAddon(new WebLinksAddon());
|
||||
|
||||
The xterm.js team maintains the following addons, but anyone can build them:
|
||||
|
||||
- [`xterm-addon-attach`](https://github.com/xtermjs/xterm.js/tree/master/addons/xterm-addon-attach): Attaches to a server running a process via a websocket
|
||||
- [`xterm-addon-fit`](https://github.com/xtermjs/xterm.js/tree/master/addons/xterm-addon-fit): Fits the terminal to the containing element
|
||||
- [`xterm-addon-search`](https://github.com/xtermjs/xterm.js/tree/master/addons/xterm-addon-search): Adds search functionality
|
||||
- [`xterm-addon-web-links`](https://github.com/xtermjs/xterm.js/tree/master/addons/xterm-addon-web-links): Adds web link detection and interaction
|
||||
- [`@xterm/addon-attach`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-attach): Attaches to a server running a process via a websocket
|
||||
- [`@xterm/addon-canvas`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-canvas): Renders xterm.js using a `canvas` element's 2d context
|
||||
- [`@xterm/addon-fit`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-fit): Fits the terminal to the containing element
|
||||
- [`@xterm/addon-image`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-image): Adds image support
|
||||
- [`@xterm/addon-search`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-search): Adds search functionality
|
||||
- [`@xterm/addon-serialize`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-serialize): Serializes the terminal's buffer to a VT sequences or HTML
|
||||
- [`@xterm/addon-unicode11`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-unicode11): Updates character widths to their unicode11 values
|
||||
- [`@xterm/addon-web-links`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-web-links): Adds web link detection and interaction
|
||||
- [`@xterm/addon-webgl`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-webgl): Renders xterm.js using a `canvas` element's webgl2 context
|
||||
|
||||
## Browser Support
|
||||
|
||||
@@ -110,7 +113,7 @@ All current and past releases are available on this repo's [Releases page](https
|
||||
Our CI releases beta builds to npm for every change that goes into master. Install the latest beta build with:
|
||||
|
||||
```bash
|
||||
npm install -S xterm@beta
|
||||
npm install -S @xterm/xterm@beta
|
||||
```
|
||||
|
||||
These should generally be stable, but some bugs may slip in. We recommend using the beta build primarily to test out new features and to verify bug fixes.
|
||||
@@ -216,6 +219,9 @@ Xterm.js is used in several world-class applications to provide great terminal e
|
||||
- [**Super Terminal**](https://github.com/bugwheels94/super-terminal): It is a http based terminal for developers who dont like repetition and save time.
|
||||
- [**graSSHopper**](https://grasshopper.coding.kiwi): A simple SSH client with file explorer, history and many more features.
|
||||
- [**DomTerm**](https://domterm.org/xtermjs.html): Tiles and tabs. Detachable sessions (like tmux). [Remote connections](https://domterm.org/Remoting-over-ssh.html) using a nice ssh wrapper with predictive echo. Qt, Electron, Tauri/Wry, or desktop browser front-ends. Choose between xterm.js engine (faster) or native DomTerm (more functionality and graphics) - or both.
|
||||
- [**Cloudtutor.io**](https://cloudtutor.io): innovative online learning platform that offers users access to an interactive lab.
|
||||
- [**Helix Editor Playground**](https://github.com/tomgroenwoldt/helix-editor-playground): Online playground for the terminal based helix editor.
|
||||
- [**Coder**](https://github.com/coder/coder): Self-Hosted Remote Development Environments
|
||||
- [And much more...](https://github.com/xtermjs/xterm.js/network/dependents?package_id=UGFja2FnZS0xNjYzMjc4OQ%3D%3D)
|
||||
|
||||
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it on our list. Note: Please add any new contributions to the end of the list only.
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
## xterm-addon-attach
|
||||
## @xterm/addon-attach
|
||||
|
||||
An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables attaching to a web socket. This addon requires xterm.js v4+.
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
npm install --save xterm-addon-attach
|
||||
npm install --save @xterm/addon-attach
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { Terminal } from 'xterm';
|
||||
import { AttachAddon } from 'xterm-addon-attach';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { AttachAddon } from '@xterm/addon-attach';
|
||||
|
||||
const terminal = new Terminal();
|
||||
const attachAddon = new AttachAddon(webSocket);
|
||||
terminal.loadAddon(attachAddon);
|
||||
```
|
||||
|
||||
See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/xterm-addon-attach/typings/xterm-addon-attach.d.ts) for more advanced usage.
|
||||
See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-attach/typings/addon-attach.d.ts) for more advanced usage.
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "xterm-addon-attach",
|
||||
"version": "0.8.0",
|
||||
"name": "@xterm/addon-attach",
|
||||
"version": "0.11.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
},
|
||||
"main": "lib/xterm-addon-attach.js",
|
||||
"types": "typings/xterm-addon-attach.d.ts",
|
||||
"repository": "https://github.com/xtermjs/xterm.js",
|
||||
"main": "lib/addon-attach.js",
|
||||
"types": "typings/addon-attach.d.ts",
|
||||
"repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-attach",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"terminal",
|
||||
@@ -21,6 +21,6 @@
|
||||
"prepublishOnly": "npm run package"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"xterm": "^5.0.0"
|
||||
"@xterm/xterm": "^5.0.0"
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,14 @@
|
||||
* Implements the attach method, that attaches the terminal to a WebSocket stream.
|
||||
*/
|
||||
|
||||
import { Terminal, IDisposable, ITerminalAddon } from 'xterm';
|
||||
import type { Terminal, IDisposable, ITerminalAddon } from '@xterm/xterm';
|
||||
import type { AttachAddon as IAttachApi } from '@xterm/addon-attach';
|
||||
|
||||
interface IAttachOptions {
|
||||
bidirectional?: boolean;
|
||||
}
|
||||
|
||||
export class AttachAddon implements ITerminalAddon {
|
||||
export class AttachAddon implements ITerminalAddon , IAttachApi {
|
||||
private _socket: WebSocket;
|
||||
private _bidirectional: boolean;
|
||||
private _disposables: IDisposable[] = [];
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2015",
|
||||
"target": "es2021",
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2015"
|
||||
@@ -13,7 +13,12 @@
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha"
|
||||
]
|
||||
],
|
||||
"paths": {
|
||||
"@xterm/addon-attach": [
|
||||
"../typings/addon-attach.d.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import WebSocket = require('ws');
|
||||
import { openTerminal, pollFor, launchBrowser } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from 'playwright';
|
||||
import { Browser, Page } from '@playwright/test';
|
||||
|
||||
const APP = 'http://127.0.0.1:3001/test';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2015",
|
||||
"target": "es2021",
|
||||
"lib": [
|
||||
"es2015"
|
||||
],
|
||||
@@ -3,9 +3,9 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, ITerminalAddon } from 'xterm';
|
||||
import { Terminal, ITerminalAddon } from '@xterm/xterm';
|
||||
|
||||
declare module 'xterm-addon-attach' {
|
||||
declare module '@xterm/addon-attach' {
|
||||
export interface IAttachOptions {
|
||||
/**
|
||||
* Whether input should be written to the backend. Defaults to `true`.
|
||||
@@ -6,7 +6,7 @@
|
||||
const path = require('path');
|
||||
|
||||
const addonName = 'AttachAddon';
|
||||
const mainFile = 'xterm-addon-attach.js';
|
||||
const mainFile = 'addon-attach.js';
|
||||
|
||||
module.exports = {
|
||||
entry: `./out/${addonName}.js`,
|
||||
@@ -0,0 +1,28 @@
|
||||
## @xterm/addon-canvas
|
||||
|
||||
An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables a canvas-based renderer using a 2d context to draw. This addon requires xterm.js v5+.
|
||||
|
||||
The purpose of this addon is to be used as a fallback for the [webgl addon](https://www.npmjs.com/package/@xterm/addon-webgl) when better performance is desired over the default DOM renderer, but WebGL2 isn't supported or performant for some reason.
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
npm install --save @xterm/addon-canvas
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { CanvasAddon } from '@xterm/addon-canvas';
|
||||
|
||||
const terminal = new Terminal();
|
||||
terminal.open(element);
|
||||
terminal.loadAddon(new CanvasAddon());
|
||||
```
|
||||
|
||||
See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-canvas/typings/addon-canvas.d.ts) for more advanced usage.
|
||||
|
||||
### See also
|
||||
|
||||
- [@xterm/addon-webgl](https://www.npmjs.com/package/@xterm/addon-webgl) A renderer for xterm.js that uses WebGL
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "xterm-addon-canvas",
|
||||
"version": "0.4.0",
|
||||
"name": "@xterm/addon-canvas",
|
||||
"version": "0.7.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
},
|
||||
"main": "lib/xterm-addon-canvas.js",
|
||||
"types": "typings/xterm-addon-canvas.d.ts",
|
||||
"repository": "https://github.com/xtermjs/xterm.js",
|
||||
"main": "lib/addon-canvas.js",
|
||||
"types": "typings/addon-canvas.d.ts",
|
||||
"repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-canvas",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"terminal",
|
||||
@@ -19,9 +19,10 @@
|
||||
"build": "../../node_modules/.bin/tsc -p .",
|
||||
"prepackage": "npm run build",
|
||||
"package": "../../node_modules/.bin/webpack",
|
||||
"prepublishOnly": "npm run package"
|
||||
"prepublishOnly": "npm run package",
|
||||
"start-server-only": "node ../../demo/start-server-only"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"xterm": "^5.0.0"
|
||||
"@xterm/xterm": "^5.0.0"
|
||||
}
|
||||
}
|
||||
@@ -3,24 +3,24 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ReadonlyColorSet } from 'browser/Types';
|
||||
import { CellColorResolver } from 'browser/renderer/shared/CellColorResolver';
|
||||
import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache';
|
||||
import { TEXT_BASELINE } from 'browser/renderer/shared/Constants';
|
||||
import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs';
|
||||
import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
|
||||
import { IRasterizedGlyph, IRenderDimensions, ISelectionRenderModel, ITextureAtlas } from 'browser/renderer/shared/Types';
|
||||
import { allowRescaling, throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
|
||||
import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel';
|
||||
import { IRasterizedGlyph, IRenderDimensions, ISelectionRenderModel, ITextureAtlas } from 'browser/renderer/shared/Types';
|
||||
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
||||
import { ReadonlyColorSet } from 'browser/Types';
|
||||
import { EventEmitter, forwardEvent } from 'common/EventEmitter';
|
||||
import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
|
||||
import { isSafari } from 'common/Platform';
|
||||
import { ICellData } from 'common/Types';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { WHITESPACE_CELL_CODE } from 'common/buffer/Constants';
|
||||
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
import { ICellData, IDisposable } from 'common/Types';
|
||||
import { Terminal } from 'xterm';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { IRenderLayer } from './Types';
|
||||
import { CellColorResolver } from 'browser/renderer/shared/CellColorResolver';
|
||||
import { Disposable, toDisposable } from 'common/Lifecycle';
|
||||
import { isSafari } from 'common/Platform';
|
||||
import { EventEmitter, forwardEvent } from 'common/EventEmitter';
|
||||
|
||||
export abstract class BaseRenderLayer extends Disposable implements IRenderLayer {
|
||||
private _canvas: HTMLCanvasElement;
|
||||
@@ -37,7 +37,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer
|
||||
private _bitmapGenerator: (BitmapGenerator | undefined)[] = [];
|
||||
|
||||
protected _charAtlas!: ITextureAtlas;
|
||||
private _charAtlasDisposable?: IDisposable;
|
||||
protected _charAtlasDisposable = this.register(new MutableDisposable());
|
||||
|
||||
public get canvas(): HTMLCanvasElement { return this._canvas; }
|
||||
public get cacheCanvas(): HTMLCanvasElement { return this._charAtlas?.pages[0].canvas!; }
|
||||
@@ -58,8 +58,8 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer
|
||||
protected readonly _coreBrowserService: ICoreBrowserService
|
||||
) {
|
||||
super();
|
||||
this._cellColorResolver = new CellColorResolver(this._terminal, this._selectionModel, this._decorationService, this._coreBrowserService, this._themeService);
|
||||
this._canvas = document.createElement('canvas');
|
||||
this._cellColorResolver = new CellColorResolver(this._terminal, this._optionsService, this._selectionModel, this._decorationService, this._coreBrowserService, this._themeService);
|
||||
this._canvas = this._coreBrowserService.mainDocument.createElement('canvas');
|
||||
this._canvas.classList.add(`xterm-${id}-layer`);
|
||||
this._canvas.style.zIndex = zIndex.toString();
|
||||
this._initCanvas();
|
||||
@@ -74,7 +74,6 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer
|
||||
|
||||
this.register(toDisposable(() => {
|
||||
this._canvas.remove();
|
||||
this._charAtlas?.dispose();
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -92,7 +91,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer
|
||||
public handleGridChanged(startRow: number, endRow: number): void {}
|
||||
|
||||
public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {
|
||||
this._selectionModel.update(this._terminal, start, end, columnSelectMode);
|
||||
this._selectionModel.update((this._terminal as any)._core, start, end, columnSelectMode);
|
||||
}
|
||||
|
||||
protected _setTransparency(alpha: boolean): void {
|
||||
@@ -122,9 +121,8 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer
|
||||
if (this._deviceCharWidth <= 0 && this._deviceCharHeight <= 0) {
|
||||
return;
|
||||
}
|
||||
this._charAtlasDisposable?.dispose();
|
||||
this._charAtlas = acquireTextureAtlas(this._terminal, this._optionsService.rawOptions, colorSet, this._deviceCellWidth, this._deviceCellHeight, this._deviceCharWidth, this._deviceCharHeight, this._coreBrowserService.dpr);
|
||||
this._charAtlasDisposable = forwardEvent(this._charAtlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas);
|
||||
this._charAtlasDisposable.value = forwardEvent(this._charAtlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas);
|
||||
this._charAtlas.warmUp();
|
||||
for (let i = 0; i < this._charAtlas.pages.length; i++) {
|
||||
this._bitmapGenerator[i] = new BitmapGenerator(this._charAtlas.pages[i].canvas);
|
||||
@@ -367,12 +365,19 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer
|
||||
*/
|
||||
protected _drawChars(cell: ICellData, x: number, y: number): void {
|
||||
const chars = cell.getChars();
|
||||
this._cellColorResolver.resolve(cell, x, this._bufferService.buffer.ydisp + y);
|
||||
const code = cell.getCode();
|
||||
const width = cell.getWidth();
|
||||
this._cellColorResolver.resolve(cell, x, this._bufferService.buffer.ydisp + y, this._deviceCellWidth);
|
||||
|
||||
if (!this._charAtlas) {
|
||||
return;
|
||||
}
|
||||
|
||||
let glyph: IRasterizedGlyph;
|
||||
if (chars && chars.length > 1) {
|
||||
glyph = this._charAtlas.getRasterizedGlyphCombinedChar(chars, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext);
|
||||
glyph = this._charAtlas.getRasterizedGlyphCombinedChar(chars, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, true);
|
||||
} else {
|
||||
glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext);
|
||||
glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, true);
|
||||
}
|
||||
if (!glyph.size.x || !glyph.size.y) {
|
||||
return;
|
||||
@@ -397,6 +402,16 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer
|
||||
this._bitmapGenerator[glyph.texturePage]!.refresh();
|
||||
this._bitmapGenerator[glyph.texturePage]!.version = this._charAtlas.pages[glyph.texturePage].version;
|
||||
}
|
||||
|
||||
// Reduce scale horizontally for wide glyphs printed in cells that would overlap with the
|
||||
// following cell (ie. the width is not 2).
|
||||
let renderWidth = glyph.size.x;
|
||||
if (this._optionsService.rawOptions.rescaleOverlappingGlyphs) {
|
||||
if (allowRescaling(code, width, glyph.size.x, this._deviceCellWidth)) {
|
||||
renderWidth = this._deviceCellWidth - 1; // - 1 to improve readability
|
||||
}
|
||||
}
|
||||
|
||||
this._ctx.drawImage(
|
||||
this._bitmapGenerator[glyph.texturePage]?.bitmap || this._charAtlas!.pages[glyph.texturePage].canvas,
|
||||
glyph.texturePosition.x,
|
||||
@@ -405,7 +420,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer
|
||||
glyph.size.y,
|
||||
x * this._deviceCellWidth + this._deviceCharLeft - glyph.offset.x,
|
||||
y * this._deviceCellHeight + this._deviceCharTop - glyph.offset.y,
|
||||
glyph.size.x,
|
||||
renderWidth,
|
||||
glyph.size.y
|
||||
);
|
||||
this._ctx.restore();
|
||||
@@ -3,15 +3,17 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, ISelectionService, IThemeService } from 'browser/services/Services';
|
||||
import type { ITerminalAddon, Terminal } from '@xterm/xterm';
|
||||
import type { CanvasAddon as ICanvasApi } from '@xterm/addon-canvas';
|
||||
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
|
||||
import { ITerminal } from 'browser/Types';
|
||||
import { CanvasRenderer } from './CanvasRenderer';
|
||||
import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
import { ITerminalAddon, Terminal } from 'xterm';
|
||||
import { EventEmitter, forwardEvent } from 'common/EventEmitter';
|
||||
import { Disposable, toDisposable } from 'common/Lifecycle';
|
||||
import { setTraceLogger } from 'common/services/LogService';
|
||||
import { IBufferService, IDecorationService, ILogService } from 'common/services/Services';
|
||||
import { CanvasRenderer } from './CanvasRenderer';
|
||||
|
||||
export class CanvasAddon extends Disposable implements ITerminalAddon {
|
||||
export class CanvasAddon extends Disposable implements ITerminalAddon , ICanvasApi {
|
||||
private _terminal?: Terminal;
|
||||
private _renderer?: CanvasRenderer;
|
||||
|
||||
@@ -35,7 +37,7 @@ export class CanvasAddon extends Disposable implements ITerminalAddon {
|
||||
const coreService = core.coreService;
|
||||
const optionsService = core.optionsService;
|
||||
const screenElement = core.screenElement!;
|
||||
const linkifier = core.linkifier2;
|
||||
const linkifier = core.linkifier!;
|
||||
|
||||
const unsafeCore = core as any;
|
||||
const bufferService: IBufferService = unsafeCore._bufferService;
|
||||
@@ -44,8 +46,13 @@ export class CanvasAddon extends Disposable implements ITerminalAddon {
|
||||
const charSizeService: ICharSizeService = unsafeCore._charSizeService;
|
||||
const coreBrowserService: ICoreBrowserService = unsafeCore._coreBrowserService;
|
||||
const decorationService: IDecorationService = unsafeCore._decorationService;
|
||||
const logService: ILogService = unsafeCore._logService;
|
||||
const themeService: IThemeService = unsafeCore._themeService;
|
||||
|
||||
// Set trace logger just in case it hasn't been yet which could happen when the addon is
|
||||
// bundled separately to the core module
|
||||
setTraceLogger(logService);
|
||||
|
||||
this._renderer = new CanvasRenderer(terminal, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService, themeService);
|
||||
this.register(forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas));
|
||||
this.register(forwardEvent(this._renderer.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas));
|
||||
@@ -59,4 +66,8 @@ export class CanvasAddon extends Disposable implements ITerminalAddon {
|
||||
this._renderer = undefined;
|
||||
}));
|
||||
}
|
||||
|
||||
public clearTextureAtlas(): void {
|
||||
this._renderer?.clearTextureAtlas();
|
||||
}
|
||||
}
|
||||
@@ -3,16 +3,16 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ILinkifier2 } from 'browser/Types';
|
||||
import { removeTerminalFromCache } from 'browser/renderer/shared/CharAtlasCache';
|
||||
import { observeDevicePixelDimensions } from 'browser/renderer/shared/DevicePixelObserver';
|
||||
import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';
|
||||
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
|
||||
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, ISelectionService, IThemeService } from 'browser/services/Services';
|
||||
import { ILinkifier2 } from 'browser/Types';
|
||||
import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
||||
import { EventEmitter, forwardEvent } from 'common/EventEmitter';
|
||||
import { Disposable, toDisposable } from 'common/Lifecycle';
|
||||
import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
|
||||
import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
import { Terminal } from 'xterm';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { CursorRenderLayer } from './CursorRenderLayer';
|
||||
import { LinkRenderLayer } from './LinkRenderLayer';
|
||||
import { SelectionRenderLayer } from './SelectionRenderLayer';
|
||||
@@ -22,6 +22,7 @@ import { IRenderLayer } from './Types';
|
||||
export class CanvasRenderer extends Disposable implements IRenderer {
|
||||
private _renderLayers: IRenderLayer[];
|
||||
private _devicePixelRatio: number;
|
||||
private _observerDisposable = this.register(new MutableDisposable());
|
||||
|
||||
public dimensions: IRenderDimensions;
|
||||
|
||||
@@ -60,7 +61,11 @@ export class CanvasRenderer extends Disposable implements IRenderer {
|
||||
this._devicePixelRatio = this._coreBrowserService.dpr;
|
||||
this._updateDimensions();
|
||||
|
||||
this.register(observeDevicePixelDimensions(this._renderLayers[0].canvas, this._coreBrowserService.window, (w, h) => this._setCanvasDevicePixelDimensions(w, h)));
|
||||
this._observerDisposable.value = observeDevicePixelDimensions(this._renderLayers[0].canvas, this._coreBrowserService.window, (w, h) => this._setCanvasDevicePixelDimensions(w, h));
|
||||
this.register(this._coreBrowserService.onWindowChange(w => {
|
||||
this._observerDisposable.value = observeDevicePixelDimensions(this._renderLayers[0].canvas, w, (w, h) => this._setCanvasDevicePixelDimensions(w, h));
|
||||
}));
|
||||
|
||||
this.register(toDisposable(() => {
|
||||
for (const l of this._renderLayers) {
|
||||
l.dispose();
|
||||
@@ -3,17 +3,17 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { CursorBlinkStateManager } from 'browser/renderer/shared/CursorBlinkStateManager';
|
||||
import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
|
||||
import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
||||
import { IEventEmitter } from 'common/EventEmitter';
|
||||
import { MutableDisposable } from 'common/Lifecycle';
|
||||
import { isFirefox } from 'common/Platform';
|
||||
import { ICellData } from 'common/Types';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { IColorSet, ReadonlyColorSet } from 'browser/Types';
|
||||
import { IBufferService, IOptionsService, ICoreService, IDecorationService } from 'common/services/Services';
|
||||
import { IEventEmitter } from 'common/EventEmitter';
|
||||
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
||||
import { Terminal } from 'xterm';
|
||||
import { toDisposable } from 'common/Lifecycle';
|
||||
import { isFirefox } from 'common/Platform';
|
||||
import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
|
||||
interface ICursorState {
|
||||
x: number;
|
||||
@@ -23,15 +23,10 @@ interface ICursorState {
|
||||
width: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* The time between cursor blinks.
|
||||
*/
|
||||
const BLINK_INTERVAL = 600;
|
||||
|
||||
export class CursorRenderLayer extends BaseRenderLayer {
|
||||
private _state: ICursorState;
|
||||
private _cursorRenderers: {[key: string]: (x: number, y: number, cell: ICellData) => void};
|
||||
private _cursorBlinkStateManager: CursorBlinkStateManager | undefined;
|
||||
private _cursorBlinkStateManager: MutableDisposable<CursorBlinkStateManager> = this.register(new MutableDisposable());
|
||||
private _cell: ICellData = new CellData();
|
||||
|
||||
constructor(
|
||||
@@ -57,13 +52,11 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
this._cursorRenderers = {
|
||||
'bar': this._renderBarCursor.bind(this),
|
||||
'block': this._renderBlockCursor.bind(this),
|
||||
'underline': this._renderUnderlineCursor.bind(this)
|
||||
'underline': this._renderUnderlineCursor.bind(this),
|
||||
'outline': this._renderOutlineCursor.bind(this)
|
||||
};
|
||||
this.register(optionsService.onOptionChange(() => this._handleOptionsChanged()));
|
||||
this.register(toDisposable(() => {
|
||||
this._cursorBlinkStateManager?.dispose();
|
||||
this._cursorBlinkStateManager = undefined;
|
||||
}));
|
||||
this._handleOptionsChanged();
|
||||
}
|
||||
|
||||
public resize(dim: IRenderDimensions): void {
|
||||
@@ -80,30 +73,27 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
|
||||
public reset(): void {
|
||||
this._clearCursor();
|
||||
this._cursorBlinkStateManager?.restartBlinkAnimation();
|
||||
this._cursorBlinkStateManager.value?.restartBlinkAnimation();
|
||||
this._handleOptionsChanged();
|
||||
}
|
||||
|
||||
public handleBlur(): void {
|
||||
this._cursorBlinkStateManager?.pause();
|
||||
this._cursorBlinkStateManager.value?.pause();
|
||||
this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
|
||||
}
|
||||
|
||||
public handleFocus(): void {
|
||||
this._cursorBlinkStateManager?.resume();
|
||||
this._cursorBlinkStateManager.value?.resume();
|
||||
this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
|
||||
}
|
||||
|
||||
private _handleOptionsChanged(): void {
|
||||
if (this._optionsService.rawOptions.cursorBlink) {
|
||||
if (!this._cursorBlinkStateManager) {
|
||||
this._cursorBlinkStateManager = new CursorBlinkStateManager(this._coreBrowserService.isFocused, () => {
|
||||
this._render(true);
|
||||
}, this._coreBrowserService);
|
||||
if (!this._cursorBlinkStateManager.value) {
|
||||
this._cursorBlinkStateManager.value = new CursorBlinkStateManager(() => this._render(true), this._coreBrowserService);
|
||||
}
|
||||
} else {
|
||||
this._cursorBlinkStateManager?.dispose();
|
||||
this._cursorBlinkStateManager = undefined;
|
||||
this._cursorBlinkStateManager.clear();
|
||||
}
|
||||
// Request a refresh from the terminal as management of rendering is being
|
||||
// moved back to the terminal
|
||||
@@ -111,14 +101,14 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
}
|
||||
|
||||
public handleCursorMove(): void {
|
||||
this._cursorBlinkStateManager?.restartBlinkAnimation();
|
||||
this._cursorBlinkStateManager.value?.restartBlinkAnimation();
|
||||
}
|
||||
|
||||
public handleGridChanged(startRow: number, endRow: number): void {
|
||||
if (!this._cursorBlinkStateManager || this._cursorBlinkStateManager.isPaused) {
|
||||
if (!this._cursorBlinkStateManager.value || this._cursorBlinkStateManager.value.isPaused) {
|
||||
this._render(false);
|
||||
} else {
|
||||
this._cursorBlinkStateManager.restartBlinkAnimation();
|
||||
this._cursorBlinkStateManager.value.restartBlinkAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +140,10 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
this._ctx.save();
|
||||
this._ctx.fillStyle = this._themeService.colors.cursor.css;
|
||||
const cursorStyle = this._optionsService.rawOptions.cursorStyle;
|
||||
this._renderBlurCursor(cursorX, viewportRelativeCursorY, this._cell);
|
||||
const cursorInactiveStyle = this._optionsService.rawOptions.cursorInactiveStyle;
|
||||
if (cursorInactiveStyle && cursorInactiveStyle !== 'none') {
|
||||
this._cursorRenderers[cursorInactiveStyle](cursorX, viewportRelativeCursorY, this._cell);
|
||||
}
|
||||
this._ctx.restore();
|
||||
this._state.x = cursorX;
|
||||
this._state.y = viewportRelativeCursorY;
|
||||
@@ -161,7 +154,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
}
|
||||
|
||||
// Don't draw the cursor if it's blinking
|
||||
if (this._cursorBlinkStateManager && !this._cursorBlinkStateManager.isCursorVisible) {
|
||||
if (this._cursorBlinkStateManager.value && !this._cursorBlinkStateManager.value.isCursorVisible) {
|
||||
this._clearCursor();
|
||||
return;
|
||||
}
|
||||
@@ -231,146 +224,10 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
this._ctx.restore();
|
||||
}
|
||||
|
||||
private _renderBlurCursor(x: number, y: number, cell: ICellData): void {
|
||||
private _renderOutlineCursor(x: number, y: number, cell: ICellData): void {
|
||||
this._ctx.save();
|
||||
this._ctx.strokeStyle = this._themeService.colors.cursor.css;
|
||||
this._strokeRectAtCell(x, y, cell.getWidth(), 1);
|
||||
this._ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
class CursorBlinkStateManager {
|
||||
public isCursorVisible: boolean;
|
||||
|
||||
private _animationFrame: number | undefined;
|
||||
private _blinkStartTimeout: number | undefined;
|
||||
private _blinkInterval: number | undefined;
|
||||
|
||||
/**
|
||||
* The time at which the animation frame was restarted, this is used on the
|
||||
* next render to restart the timers so they don't need to restart the timers
|
||||
* multiple times over a short period.
|
||||
*/
|
||||
private _animationTimeRestarted: number | undefined;
|
||||
|
||||
constructor(
|
||||
isFocused: boolean,
|
||||
private _renderCallback: () => void,
|
||||
private _coreBrowserService: ICoreBrowserService
|
||||
) {
|
||||
this.isCursorVisible = true;
|
||||
if (isFocused) {
|
||||
this._restartInterval();
|
||||
}
|
||||
}
|
||||
|
||||
public get isPaused(): boolean { return !(this._blinkStartTimeout || this._blinkInterval); }
|
||||
|
||||
public dispose(): void {
|
||||
if (this._blinkInterval) {
|
||||
this._coreBrowserService.window.clearInterval(this._blinkInterval);
|
||||
this._blinkInterval = undefined;
|
||||
}
|
||||
if (this._blinkStartTimeout) {
|
||||
this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout);
|
||||
this._blinkStartTimeout = undefined;
|
||||
}
|
||||
if (this._animationFrame) {
|
||||
this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame);
|
||||
this._animationFrame = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public restartBlinkAnimation(): void {
|
||||
if (this.isPaused) {
|
||||
return;
|
||||
}
|
||||
// Save a timestamp so that the restart can be done on the next interval
|
||||
this._animationTimeRestarted = Date.now();
|
||||
// Force a cursor render to ensure it's visible and in the correct position
|
||||
this.isCursorVisible = true;
|
||||
if (!this._animationFrame) {
|
||||
this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => {
|
||||
this._renderCallback();
|
||||
this._animationFrame = undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _restartInterval(timeToStart: number = BLINK_INTERVAL): void {
|
||||
// Clear any existing interval
|
||||
if (this._blinkInterval) {
|
||||
this._coreBrowserService.window.clearInterval(this._blinkInterval);
|
||||
this._blinkInterval = undefined;
|
||||
}
|
||||
|
||||
// Setup the initial timeout which will hide the cursor, this is done before
|
||||
// the regular interval is setup in order to support restarting the blink
|
||||
// animation in a lightweight way (without thrashing clearInterval and
|
||||
// setInterval).
|
||||
this._blinkStartTimeout = this._coreBrowserService.window.setTimeout(() => {
|
||||
// Check if another animation restart was requested while this was being
|
||||
// started
|
||||
if (this._animationTimeRestarted) {
|
||||
const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted);
|
||||
this._animationTimeRestarted = undefined;
|
||||
if (time > 0) {
|
||||
this._restartInterval(time);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Hide the cursor
|
||||
this.isCursorVisible = false;
|
||||
this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => {
|
||||
this._renderCallback();
|
||||
this._animationFrame = undefined;
|
||||
});
|
||||
|
||||
// Setup the blink interval
|
||||
this._blinkInterval = this._coreBrowserService.window.setInterval(() => {
|
||||
// Adjust the animation time if it was restarted
|
||||
if (this._animationTimeRestarted) {
|
||||
// calc time diff
|
||||
// Make restart interval do a setTimeout initially?
|
||||
const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted);
|
||||
this._animationTimeRestarted = undefined;
|
||||
this._restartInterval(time);
|
||||
return;
|
||||
}
|
||||
|
||||
// Invert visibility and render
|
||||
this.isCursorVisible = !this.isCursorVisible;
|
||||
this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => {
|
||||
this._renderCallback();
|
||||
this._animationFrame = undefined;
|
||||
});
|
||||
}, BLINK_INTERVAL);
|
||||
}, timeToStart);
|
||||
}
|
||||
|
||||
public pause(): void {
|
||||
this.isCursorVisible = true;
|
||||
if (this._blinkInterval) {
|
||||
this._coreBrowserService.window.clearInterval(this._blinkInterval);
|
||||
this._blinkInterval = undefined;
|
||||
}
|
||||
if (this._blinkStartTimeout) {
|
||||
this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout);
|
||||
this._blinkStartTimeout = undefined;
|
||||
}
|
||||
if (this._animationFrame) {
|
||||
this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame);
|
||||
this._animationFrame = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public resume(): void {
|
||||
// Clear out any existing timers just in case
|
||||
this.pause();
|
||||
|
||||
this._animationTimeRestarted = undefined;
|
||||
this._restartInterval();
|
||||
this.restartBlinkAnimation();
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,14 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IRenderDimensions } from 'browser/renderer/shared/Types';
|
||||
import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants';
|
||||
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
||||
import { IColorSet, ILinkifierEvent, ILinkifier2, ReadonlyColorSet } from 'browser/Types';
|
||||
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
import { ILinkifier2, ILinkifierEvent } from 'browser/Types';
|
||||
import { is256Color } from 'browser/renderer/shared/CharAtlasUtils';
|
||||
import { Terminal } from 'xterm';
|
||||
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants';
|
||||
import { IRenderDimensions } from 'browser/renderer/shared/Types';
|
||||
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
||||
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
|
||||
export class LinkRenderLayer extends BaseRenderLayer {
|
||||
private _state: ILinkifierEvent | undefined;
|
||||
@@ -7,7 +7,7 @@ import { IRenderDimensions } from 'browser/renderer/shared/Types';
|
||||
import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
||||
import { Terminal } from 'xterm';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
|
||||
interface ISelectionState {
|
||||
start?: [number, number];
|
||||
@@ -4,18 +4,16 @@
|
||||
*/
|
||||
|
||||
import { IRenderDimensions } from 'browser/renderer/shared/Types';
|
||||
import { CharData, ICellData } from 'common/Types';
|
||||
import { GridCache } from './GridCache';
|
||||
import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { NULL_CELL_CODE, Content, UnderlineStyle } from 'common/buffer/Constants';
|
||||
import { IColorSet, ReadonlyColorSet } from 'browser/Types';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { IOptionsService, IBufferService, IDecorationService } from 'common/services/Services';
|
||||
import { ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
||||
import { JoinedCellData } from 'browser/services/CharacterJoinerService';
|
||||
import { color, css } from 'common/Color';
|
||||
import { Terminal } from 'xterm';
|
||||
import { ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
||||
import { CharData, ICellData } from 'common/Types';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { Content, NULL_CELL_CODE } from 'common/buffer/Constants';
|
||||
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
import { GridCache } from './GridCache';
|
||||
|
||||
/**
|
||||
* This CharData looks like a null character, which will forc a clear and render
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2015",
|
||||
"target": "es2021",
|
||||
"lib": [
|
||||
"dom",
|
||||
"es6"
|
||||
"es2021"
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../out",
|
||||
@@ -17,6 +17,9 @@
|
||||
],
|
||||
"browser/*": [
|
||||
"../../../src/browser/*"
|
||||
],
|
||||
"@xterm/addon-canvas": [
|
||||
"../typings/addon-canvas.d.ts"
|
||||
]
|
||||
},
|
||||
"strict": true,
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import test from '@playwright/test';
|
||||
import { ISharedRendererTestContext, injectSharedRendererTests, injectSharedRendererTestsStandalone } from '../../../out-test/playwright/SharedRendererTests';
|
||||
import { ITestContext, createTestContext, openTerminal } from '../../../out-test/playwright/TestUtils';
|
||||
|
||||
let ctx: ITestContext;
|
||||
const ctxWrapper: ISharedRendererTestContext = {
|
||||
value: undefined,
|
||||
skipCanvasExceptions: true
|
||||
} as any;
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
ctx = await createTestContext(browser);
|
||||
await openTerminal(ctx);
|
||||
ctxWrapper.value = ctx;
|
||||
await ctx.page.evaluate(`
|
||||
window.addon = new window.CanvasAddon(true);
|
||||
window.term.loadAddon(window.addon);
|
||||
`);
|
||||
});
|
||||
test.afterAll(async () => await ctx.page.close());
|
||||
|
||||
test.describe('Canvas Renderer Integration Tests', () => {
|
||||
// HACK: The tests fail for an unknown reason
|
||||
test.skip(({ browserName }) => browserName === 'webkit');
|
||||
|
||||
injectSharedRendererTests(ctxWrapper);
|
||||
injectSharedRendererTestsStandalone(ctxWrapper, async () => {
|
||||
await ctx.page.evaluate(`
|
||||
window.addon = new window.CanvasAddon(true);
|
||||
window.term.loadAddon(window.addon);
|
||||
`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { PlaywrightTestConfig } from '@playwright/test';
|
||||
|
||||
const config: PlaywrightTestConfig = {
|
||||
testDir: '.',
|
||||
timeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
name: 'Chrome Stable',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Firefox Stable',
|
||||
use: {
|
||||
browserName: 'firefox'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'WebKit',
|
||||
use: {
|
||||
browserName: 'webkit'
|
||||
}
|
||||
}
|
||||
],
|
||||
reporter: 'list',
|
||||
webServer: {
|
||||
command: 'npm run start-server-only',
|
||||
port: 3000,
|
||||
timeout: 120000,
|
||||
reuseExistingServer: !process.env.CI
|
||||
}
|
||||
};
|
||||
export default config;
|
||||
@@ -20,9 +20,10 @@
|
||||
},
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../node_modules/@types/node",
|
||||
"../../../out-test/api/TestUtils"
|
||||
"../../../node_modules/@lunapaint/png-codec",
|
||||
"../../../out-test/playwright/TestUtils",
|
||||
"../../../out-test/playwright/SharedRendererTests"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
@@ -3,9 +3,9 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, ITerminalAddon, IEvent } from 'xterm';
|
||||
import { Terminal, ITerminalAddon, IEvent } from '@xterm/xterm';
|
||||
|
||||
declare module 'xterm-addon-canvas' {
|
||||
declare module '@xterm/addon-canvas' {
|
||||
/**
|
||||
* An xterm.js addon that provides search functionality.
|
||||
*/
|
||||
@@ -6,7 +6,7 @@
|
||||
const path = require('path');
|
||||
|
||||
const addonName = 'CanvasAddon';
|
||||
const mainFile = 'xterm-addon-canvas.js';
|
||||
const mainFile = 'addon-canvas.js';
|
||||
|
||||
module.exports = {
|
||||
entry: `./out/${addonName}.js`,
|
||||
@@ -1,18 +1,18 @@
|
||||
## xterm-addon-fit
|
||||
## @xterm/addon-fit
|
||||
|
||||
An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables fitting the terminal's dimensions to a containing element. This addon requires xterm.js v4+.
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
npm install --save xterm-addon-fit
|
||||
npm install --save @xterm/addon-fit
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { Terminal } from 'xterm';
|
||||
import { FitAddon } from 'xterm-addon-fit';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { FitAddon } from '@xterm/addon-fit';
|
||||
|
||||
const terminal = new Terminal();
|
||||
const fitAddon = new FitAddon();
|
||||
@@ -21,4 +21,4 @@ terminal.open(containerElement);
|
||||
fitAddon.fit();
|
||||
```
|
||||
|
||||
See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/xterm-addon-fit/typings/xterm-addon-fit.d.ts) for more advanced usage.
|
||||
See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-fit/typings/addon-fit.d.ts) for more advanced usage.
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "xterm-addon-fit",
|
||||
"version": "0.7.0",
|
||||
"name": "@xterm/addon-fit",
|
||||
"version": "0.10.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
},
|
||||
"main": "lib/xterm-addon-fit.js",
|
||||
"types": "typings/xterm-addon-fit.d.ts",
|
||||
"repository": "https://github.com/xtermjs/xterm.js",
|
||||
"main": "lib/addon-fit.js",
|
||||
"types": "typings/addon-fit.d.ts",
|
||||
"repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-fit",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"terminal",
|
||||
@@ -21,6 +21,6 @@
|
||||
"prepublishOnly": "npm run package"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"xterm": "^5.0.0"
|
||||
"@xterm/xterm": "^5.0.0"
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,8 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, ITerminalAddon } from 'xterm';
|
||||
import type { Terminal, ITerminalAddon } from '@xterm/xterm';
|
||||
import type { FitAddon as IFitApi } from '@xterm/addon-fit';
|
||||
import { IRenderDimensions } from 'browser/renderer/shared/Types';
|
||||
|
||||
interface ITerminalDimensions {
|
||||
@@ -21,11 +22,9 @@ interface ITerminalDimensions {
|
||||
const MINIMUM_COLS = 2;
|
||||
const MINIMUM_ROWS = 1;
|
||||
|
||||
export class FitAddon implements ITerminalAddon {
|
||||
export class FitAddon implements ITerminalAddon , IFitApi {
|
||||
private _terminal: Terminal | undefined;
|
||||
|
||||
constructor() {}
|
||||
|
||||
public activate(terminal: Terminal): void {
|
||||
this._terminal = terminal;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2015",
|
||||
"target": "es2021",
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2015"
|
||||
@@ -17,6 +17,9 @@
|
||||
"paths": {
|
||||
"browser/*": [
|
||||
"../../../src/browser/*"
|
||||
],
|
||||
"@xterm/addon-fit": [
|
||||
"../typings/addon-fit.d.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -4,8 +4,8 @@
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { openTerminal, launchBrowser } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from 'playwright';
|
||||
import { openTerminal, launchBrowser, timeout } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from '@playwright/test';
|
||||
|
||||
const APP = 'http://127.0.0.1:3001/test';
|
||||
|
||||
@@ -41,9 +41,7 @@ describe('FitAddon', () => {
|
||||
});
|
||||
|
||||
describe('proposeDimensions', () => {
|
||||
afterEach(async () => {
|
||||
return await unloadFit();
|
||||
});
|
||||
afterEach(() => unloadFit());
|
||||
|
||||
it('default', async function(): Promise<any> {
|
||||
await loadFit();
|
||||
@@ -77,14 +75,20 @@ describe('FitAddon', () => {
|
||||
await page.evaluate(`window.term = new Terminal()`);
|
||||
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
|
||||
await loadFit();
|
||||
assert.equal(await page.evaluate(`window.fit.proposeDimensions()`), undefined);
|
||||
const dimensions: { cols: number, rows: number } | undefined = await page.evaluate(`window.fit.proposeDimensions()`);
|
||||
// The value of dims will be undefined if the char measure strategy falls back to the DOM
|
||||
// method, so only assert if it's not undefined.
|
||||
if (dimensions) {
|
||||
assert.isAbove(dimensions.cols, 85);
|
||||
assert.isBelow(dimensions.cols, 88);
|
||||
assert.isAbove(dimensions.rows, 24);
|
||||
assert.isBelow(dimensions.rows, 29);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('fit', () => {
|
||||
afterEach(async () => {
|
||||
return await unloadFit();
|
||||
});
|
||||
afterEach(() => unloadFit());
|
||||
|
||||
it('default', async function(): Promise<any> {
|
||||
await loadFit();
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2015",
|
||||
"target": "es2021",
|
||||
"lib": [
|
||||
"es2015"
|
||||
],
|
||||
@@ -3,9 +3,9 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, ITerminalAddon } from 'xterm';
|
||||
import { Terminal, ITerminalAddon } from '@xterm/xterm';
|
||||
|
||||
declare module 'xterm-addon-fit' {
|
||||
declare module '@xterm/addon-fit' {
|
||||
/**
|
||||
* An xterm.js addon that enables resizing the terminal to the dimensions of
|
||||
* its containing element.
|
||||
@@ -6,7 +6,7 @@
|
||||
const path = require('path');
|
||||
|
||||
const addonName = 'FitAddon';
|
||||
const mainFile = 'xterm-addon-fit.js';
|
||||
const mainFile = 'addon-fit.js';
|
||||
|
||||
module.exports = {
|
||||
entry: `./out/${addonName}.js`,
|
||||
@@ -0,0 +1,34 @@
|
||||
node_modules/
|
||||
*.swp
|
||||
.lock-wscript
|
||||
lib/
|
||||
out/
|
||||
out-test/
|
||||
out-worker/
|
||||
.nyc_output/
|
||||
Makefile.gyp
|
||||
*.Makefile
|
||||
*.target.gyp.mk
|
||||
*.node
|
||||
example/*.log
|
||||
docs/
|
||||
npm-debug.log
|
||||
/.idea/
|
||||
.env
|
||||
build/
|
||||
.DS_Store
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
# Keep bundled code out of Git
|
||||
dist/
|
||||
demo/dist/
|
||||
|
||||
# skip benchmark & inwasm folders, except builds
|
||||
.benchmark/
|
||||
timeline/
|
||||
inwasm-sdks/
|
||||
inwasm-builds/
|
||||
!inwasm-builds/**/final.wat
|
||||
!inwasm-builds/**/final.wasm
|
||||
!inwasm-builds/**/definition.json
|
||||
@@ -0,0 +1,10 @@
|
||||
.github
|
||||
fixture
|
||||
overwrite
|
||||
bootstrap.sh
|
||||
tsconfig.json
|
||||
webpack.config.js
|
||||
test
|
||||
out-test
|
||||
inwasm-sdks
|
||||
inwasm-builds
|
||||
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2019, 2020, 2021, 2022, 2023 The xterm.js authors (https://github.com/xtermjs/xterm.js)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -0,0 +1,231 @@
|
||||
## @xterm/addon-image
|
||||
|
||||
Inline image output in xterm.js. Supports SIXEL and iTerm's inline image protocol (IIP).
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
### Install from npm
|
||||
|
||||
```bash
|
||||
npm install --save @xterm/addon-image
|
||||
```
|
||||
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { ImageAddon, IImageAddonOptions } from '@xterm/addon-image';
|
||||
|
||||
// customize as needed (showing addon defaults)
|
||||
const customSettings: IImageAddonOptions = {
|
||||
enableSizeReports: true, // whether to enable CSI t reports (see below)
|
||||
pixelLimit: 16777216, // max. pixel size of a single image
|
||||
sixelSupport: true, // enable sixel support
|
||||
sixelScrolling: true, // whether to scroll on image output
|
||||
sixelPaletteLimit: 256, // initial sixel palette size
|
||||
sixelSizeLimit: 25000000, // size limit of a single sixel sequence
|
||||
storageLimit: 128, // FIFO storage limit in MB
|
||||
showPlaceholder: true, // whether to show a placeholder for evicted images
|
||||
iipSupport: true, // enable iTerm IIP support
|
||||
iipSizeLimit: 20000000 // size limit of a single IIP sequence
|
||||
}
|
||||
|
||||
// initialization
|
||||
const terminal = new Terminal();
|
||||
const imageAddon = new ImageAddon(customSettings);
|
||||
terminal.loadAddon(imageAddon);
|
||||
```
|
||||
|
||||
### General Notes
|
||||
|
||||
- *IMPORTANT:* The worker approach as done in previous releases got removed.
|
||||
The addon contructor no longer expects a worker path as first argument.
|
||||
|
||||
- By default the addon will activate these `windowOptions` reports on the terminal:
|
||||
- getWinSizePixels (CSI 14 t)
|
||||
- getCellSizePixels (CSI 16 t)
|
||||
- getWinSizeChars (CSI 18 t)
|
||||
|
||||
to help applications getting useful terminal metrics for their image preparations. Set `enableSizeReports` in the constructor options to `false`, if you dont want the addon to alter these terminal settings. This is especially useful, if you have very strict security needs not allowing any terminal reports, or deal with `windowOptions` by other means.
|
||||
|
||||
|
||||
### Operation Modes
|
||||
|
||||
- **SIXEL Support**
|
||||
Set by default, change it with `{sixelSupport: true}`.
|
||||
|
||||
- **Scrolling On | Off**
|
||||
By default scrolling is on, thus an image will advance the cursor at the bottom if needed.
|
||||
(see cursor positioning).
|
||||
|
||||
If scrolling is off, the image gets painted from the top left of the current viewport
|
||||
and might be truncated if the image exceeds the viewport size.
|
||||
The cursor position does not change.
|
||||
|
||||
You can customize this behavior with the constructor option `{sixelScrolling: false}`
|
||||
or with `DECSET 80` (off, binary: `\x1b [ ? 80 h`) and
|
||||
`DECRST 80` (on, binary: `\x1b [ ? 80 l`) during runtime.
|
||||
|
||||
- **Cursor Positioning**
|
||||
If scrolling is set, the cursor will be placed at the first image column of the last image row (VT340 mode).
|
||||
Other cursor positioning modes as used by xterm or mintty are not supported.
|
||||
|
||||
- **SIXEL Palette Handling**
|
||||
By default the addon limits the palette size to 256 registers (as demanded by the DEC specification).
|
||||
The limit can be increased to a maximum of 4096 registers (via `sixelPaletteLimit`).
|
||||
|
||||
The default palette is a mixture of VT340 colors (lower 16 registers), xterm colors (up to 256) and zeros (up to 4096).
|
||||
There is no private/shared palette distinction, palette colors are always carried over from a previous sixel image.
|
||||
Restoring the default palette size and colors is possible with `XTSMGRAPHICS 1 ; 2` (binary: `\x1b[?1;2S`).
|
||||
It gets also restored automatically on RIS and DECSTR.
|
||||
|
||||
Other than on older terminals, the underlying SIXEL library applies colors immediately to individual pixels
|
||||
(*printer mode*), thus it is technically possible to use more colors in one image than the palette has color slots.
|
||||
This feature is called *high-color* in libsixel.
|
||||
|
||||
A terminal wide shared palette mode with late indexed coloring of the output is not supported,
|
||||
therefore palette animations cannot be used.
|
||||
|
||||
- **SIXEL Raster Attributes Handling**
|
||||
If raster attributes were found in the SIXEL data (level 2), the image will always be truncated to the given height/width extend. We deviate here from the specification on purpose, as it allows several processing optimizations. For level 1 SIXEL data without any raster attributes the image can freely grow in width and height up to the last data byte, which has a much higher processing penalty. In general encoding libraries should not create level 1 data anymore and should not produce pixel information beyond the announced height/width extend. Both is discouraged by the >30 years old specification.
|
||||
|
||||
Currently the SIXEL implementation of the addon does not take custom pixel sizes into account, a SIXEL pixel will map 1:1 to a screen pixel.
|
||||
|
||||
- **IIP Support (iTerm's Inline Image Protocol)**
|
||||
Set by default, change it with `{iipSupport: true}`.
|
||||
|
||||
The IIP implementation has the following features / restrictions (sequence will silently fail for unmet conditions):
|
||||
- Supported formats: PNG, JPEG and GIF
|
||||
- No animation support.
|
||||
- Image type hinting is not supported (always deducted from data header).
|
||||
- File download is not supported.
|
||||
- Filename gets parsed but not used.
|
||||
- Strict base64 handling as of RFC4648 §4 (standard alphabet, optional padding, no separator bytes allowed).
|
||||
- Payload size may not exceed CEIL(sizeParameter * 4 / 3).
|
||||
- Image scaling beyond terminal viewport size is allowed (e.g. `width=200%`).
|
||||
- Image pixel size is restricted by `pixelLimit` (pre- and post resizing).
|
||||
- Size parameter is restricted by `iipSizeLimit`.
|
||||
- Cursor positioning behaves the same as for sixel (see above).
|
||||
|
||||
|
||||
### Storage and Drawing Settings
|
||||
|
||||
The internal storage holds images up to `storageLimit` (in MB, calculated as 4-channel RBGA unpacked, default 100 MB). Once hit images get evicted by FIFO rules. Furthermore images on the alternate buffer will always be erased on buffer changes.
|
||||
|
||||
The addon exposes two properties to interact with the storage limits at runtime:
|
||||
- `storageLimit`
|
||||
Change the value to your needs at runtime. This is especially useful, if you have multiple terminal
|
||||
instances running, that all add to one upper memory limit.
|
||||
- `storageUsage`
|
||||
Inspect the current memory usage of the image storage.
|
||||
|
||||
By default the addon will show a placeholder pattern for evicted images that are still part
|
||||
of the terminal (e.g. in the scrollback). The pattern can be deactivated by toggling `showPlaceholder`.
|
||||
|
||||
### Image Data Retrieval
|
||||
|
||||
The addon provides the following API endpoints to retrieve raw image data as canvas:
|
||||
|
||||
- `getImageAtBufferCell(x: number, y: number): HTMLCanvasElement | undefined`
|
||||
Returns the canvas containing the original image data (not resized) at the given buffer position.
|
||||
The buffer position is the 0-based absolute index (including scrollback at top).
|
||||
|
||||
- `extractTileAtBufferCell(x: number, y: number): HTMLCanvasElement | undefined`
|
||||
Returns a canvas containing the actual single tile image data (maybe resized) at the given buffer position.
|
||||
The buffer position is the 0-based absolute index (including scrollback at top).
|
||||
Note that the canvas gets created and data copied over for every call, thus it is not suitable for performance critical actions.
|
||||
|
||||
### Memory Usage
|
||||
|
||||
The addon does most image processing in Javascript and therefore can occupy a rather big amount of memory. To get an idea where the memory gets eaten, lets look at the data flow and processing steps:
|
||||
- Incomming image data chunk at `term.write` (terminal)
|
||||
`term.write` might stock up incoming chunks. To circumvent this, you will need proper flow control (see xterm.js docs). Note that with image output it is more likely to run into this issue, as it can create lots of MBs in very short time.
|
||||
- Sequence Parser (terminal)
|
||||
The parser operates on a buffer containing up to 2^17 codepoints (~0.5 MB).
|
||||
- Sequence Handler - Chunk Decoding (addon)
|
||||
Image data chunks are processed immediately by the SIXEL decoder (streamlined). The decoder allocates memory for image
|
||||
pixels as needed. The allowed image size is restricted by `pixelLimit` (default 16M pixels), the decoder holds 2 pixel buffers at maximum during decoding (RGBA, ~128 MB for 16M pixels).
|
||||
- Sequence Handler - Image Finalization (addon)
|
||||
After decoding the final pixel buffer is grabbed by the sequence handler and written to a canvas of the same size (~64 MB for 16M pixels) and added to the storage.
|
||||
- Image Storage (addon)
|
||||
The image storage implements a FIFO cache, that will remove old images, if a new one arrives and `storageLimit` is hit (default 128 MB). The storage holds a canvas with the original image, and may additionally hold resized versions of images after a font rescaling. Both are accounted in `storageUsage` as a rough estimation of _pixels x 4 channels_.
|
||||
|
||||
Following the steps above, a rough estimation of maximum memory usage by the addon can be calculated with these formulas (in bytes):
|
||||
```typescript
|
||||
// storage alone
|
||||
const storageBytes = storageUsage * storageLimit * 1024 * 1024;
|
||||
// decoding alone
|
||||
const decodingBytes = sixelSizeLimit + 2 * (pixelLimit * 4);
|
||||
|
||||
// totals
|
||||
// inactive decoding
|
||||
const totalInactive = storageBytes;
|
||||
// active decoding
|
||||
const totalActive = storageBytes + decodingBytes;
|
||||
```
|
||||
|
||||
Note that browsers have offloading tricks for rarely touched memory segments, esp. `storageBytes` might not directly translate into real memory usage. Usage peaks will happen during active decoding of multiple big images due to the need of 2 full pixel buffers at the same time, which cannot be offloaded. Thus you may want to keep an eye on `pixelLimit` under limited memory conditions.
|
||||
Further note that the formulas above do not respect the Javascript object's overhead. Compared to the raw buffer needs the book keeping by these objects is rather small (<<5%).
|
||||
|
||||
_Why should I care about memory usage at all?_
|
||||
Well you don't have to, and it probably will just work fine with the addon defaults. But for bigger integrations, where much more data is held in the Javascript context (like multiple terminals on one page), it is likely to hit the engine's memory limit sooner or later under decoding and/or storage pressure.
|
||||
|
||||
_How can I adjust the memory usage?_
|
||||
- `pixelLimit`
|
||||
A constructor setting, thus you would have to anticipate, whether (multiple) terminals in your page gonna do lots of concurrent decoding. Since this is normally not the case and the memory usage is only temporarily peaking, a rather high value should work even with multiple terminals in one page.
|
||||
- `storageLimit`
|
||||
A constructor and a runtime setting. In conjunction with `storageUsage` you can do runtime checks and adjust the limit to your needs. If you have to lower the limit below the current usage, images will be removed in FIFO order and may turn into a placeholder in the terminal's scrollback (if `showPlaceholder` is set). When adjusting keep in mind to leave enough room for memory peaking for decoding.
|
||||
- `sixelSizeLimit`
|
||||
A constructor setting. This has only a small impact on peaking memory during decoding. It is meant to avoid processing of overly big or broken SIXEL sequences at an earlier phase, thus may stop the decoder from entering its memory intensive task for potentially invalid data.
|
||||
|
||||
|
||||
### Terminal Interaction
|
||||
|
||||
- Images already on the terminal screen will reshape on font-rescaling to keep the terminal cell coverage intact.
|
||||
This behavior might diverge from other terminals, but is in general the more desired one.
|
||||
- On terminal resize images may expand to the right automatically, if they were right-truncated before.
|
||||
They never expand to the bottom, if they were bottom-truncated before (e.g. from scrolling-off).
|
||||
- Text autowrapping from terminal resize may break and wrap images into multiple parts. This is unfortunate,
|
||||
but cannot be avoided, while sticking to the stronger terminal text-grid mechanics.
|
||||
(Yes images are a second class citizen on a mainly text-driven interface.)
|
||||
- Characters written over an image will erase the image information for affected cells.
|
||||
- Images are painted above BG/FG data not erasing it. More advanced "composition tricks" like painting images
|
||||
below FG/BG data are not possible. (We currently dont hook into BG/FG rendering itself.)
|
||||
- Previous image data at a cell will be erased on new image data. (We currently have no transparency composition.)
|
||||
|
||||
|
||||
### Performance & Latency
|
||||
|
||||
- Performance should be good enough for occasional SIXEL output from REPLs, up to downscaled movies
|
||||
from `mpv` with its SIXEL renderer (tested in the demo). For 3rd party xterm.js integrations this
|
||||
furthermore depends highly on the overall incoming data throughput.
|
||||
- Image processing has a high latency. Most of the latency though is inherited from xterm.js' incoming data route
|
||||
(PTY -> server process -> websocket -> xterm.js async parsing), where every step creates more waiting time.
|
||||
Since we cannot do much about that "long line", keep that in mind when you try to run more demanding applications with realtime drawing and interactive response needs.
|
||||
|
||||
|
||||
### Status
|
||||
|
||||
- Sixel support and image handling in xterm.js is considered beta quality.
|
||||
- IIP support is in alpha stage. Please file a bug for any awkwardities.
|
||||
|
||||
|
||||
### Changelog
|
||||
|
||||
- 0.5.0 integrate with xtermjs base repo (at v0.4.3)
|
||||
- 0.4.3 defer canvas creation
|
||||
- 0.4.2 fix image canvas resize
|
||||
- 0.4.1 compat release for xterm.js 5.2.0
|
||||
- 0.4.0 IIP support
|
||||
- 0.3.1 compat release for xterm.js 5.1.0
|
||||
- 0.3.0 important change: worker removed from addon
|
||||
- 0.2.0 compat release for xterm.js 5.0.0
|
||||
- 0.1.3 bugfix: avoid striping
|
||||
- 0.1.2 bugfix: reset clear flag
|
||||
- 0.1.1 bugfixes:
|
||||
- clear sticky image tiles on render
|
||||
- create folder from bootstrap.sh
|
||||
- fix peer dependency in package.json
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
|
||||
# sixel endless mode
|
||||
# Should print an endless sine curve, abort with Ctrl-C.
|
||||
|
||||
period=200
|
||||
amplitude=50
|
||||
|
||||
sixels=(@\$ A\$ C\$ G\$ O\$ _\$-)
|
||||
pi=$(echo "scale=10; 4*a(1)" | bc -l)
|
||||
run=true
|
||||
trap run=false INT
|
||||
|
||||
echo -ne "\x1bP0;0;0q\"1;1#1;2;100;0;0#1"
|
||||
y=0
|
||||
while $run
|
||||
do
|
||||
x=$(echo "s(2*${pi}*${y}/${period})*${amplitude}+2*${amplitude}+0.5" | bc -l)
|
||||
echo -ne "!${x%%.*}?${sixels[$((y%6))]}"
|
||||
(( y++ ))
|
||||
done
|
||||
echo -e "\x1b\\"
|
||||
|
After Width: | Height: | Size: 76 KiB |
@@ -0,0 +1,52 @@
|
||||
#!/bin/bash
|
||||
|
||||
# GLF should move the text cursor downwards,
|
||||
# even if no pixels were modified,
|
||||
# when sixel scrolling is on.
|
||||
|
||||
echo -n $'\e[?80l' # Ensure sixel scrolling is on (disable DECSDM)
|
||||
|
||||
clear
|
||||
echo "A test of Sixel GLF (Graphics Line Feed) when sixel scrolling is on"
|
||||
echo
|
||||
|
||||
# Move cursor down three using GLF ("-")
|
||||
echo -e '\x1bPq$-$-$-$-\x1b\\'
|
||||
|
||||
# Show a single sixel line that says, "Your terminal ->"
|
||||
cat <<'EOF'
|
||||
P0;0;0q"1;1;244;21#0;2;0;0;0#1;2;80;80;80#0~~~NFFFN~~~n!4FN!68~NFN!64~bbb!42~rbbbBB!41~$#1???owwwo???O!4wo!68?owo!64?[[[!42?K[[[{{-#0!5~{woBFB`o{}!4~NFBpp!4xpbBF~~~x@@@~~~xxp@@!5~xxp@@b`pxwxpz!17~zpp???!5px!5~NFBpxxwxxpBBN!4~xxp@@b`pxwxpz~x@@@xxp@@pxx@@F!4~xppp@@!8~x@@@pxxwxp@B!5~zxXXXWXX@BF!10~??!24~!10^FNN^~~~$#1!5?BFN{w{]NB@!4?ow{MM!4EM[{w???E}}}???EEM}}!5?EEM}}[]MEFEMC!17?CMM~~~!5ME!5?ow{MEEFEEM{{o!4?EEM}}[]MEFEMC?E}}}EEM}}MEE}}w!4?EMMM}}!8?E}}}MEEFEM}{!5?CEeeefee}{w!10?~~!24?!10_woo_-#0!5~nFFF??FFN!5~wo_FF!4NFb_o!4~{o_!4Ffb??Fn~~~NFF??!4FN!23~_??!5NFFn~~~w__C!7KCC!4~NFF??!4FN!4~N???N~~??Fn~???N~~N!4F??!4Fn~~~F???Fn~~nF??Fn~~p??KMMMEE???Fn~~~N!4F??!4Fn!29~{{}!4~$#1!5?Owww~~wwo!5?FN^ww!4ow[^N!4?BN^!4wW[~~wO???oww~~!4wo!23?^~~!5owwO???F^^z!7rzz!4?oww~~!4wo!4?o~~~o??~~wO?~~~o??o!4w~~!4wO???w~~~wO??Ow~~wO??M~~rpppxx~~~wO???o!4w~~!4wO!29?BB@-#0!25FE!63FE!15FE!77FE!60F$#1!25?@!63?@!15?@!77?@-\
|
||||
EOF
|
||||
|
||||
sleep 1
|
||||
tput cup 2 30
|
||||
|
||||
# Show four lines saying, "<- A genuine VT340 would end here"
|
||||
cat <<'EOF'
|
||||
P0;0;0q"1;1;144;80#0;2;0;0;0#1;2;80;80;80#0~~rpp@@`@BN!25~!11^!5~^^^N^^^!6~!7^N^^^!5~!4^~~~!5^!6~^^^WWW!8~!7^N^^^!8~^^^N^^^!5~$#1??KMM}}]}{o!25?!11_!5?___o___!6?!7_o___!5?!4_???!5_!6?___fff!8?!7_o___!8?___o___-#0~~NB??EFE??@F^!19~B??{!4}{W??}}~~B@?K!5MK??B~~~}???{!4}{??!4~}???~~~}}{??!6~}{{{??!8~}???{!4}{??!4~B@?K!5MK??B~~$#1??o{~~xwx~~}w_!19?{~~B!4@Bf~~@@??{}~r!5pr~~{???@~~~B!4@B~~!4?@~~~???@@B~~!6?@BBB~~!8?@~~~B!4@B~~!4?{}~r!5pr~~{-#0rpoopr~~~zpooopr!17~}{ww!4pwW??!4~}wwprrb!4rpp~~~pooopz~~zpoopz!4~{w!4pxwoopz~~~r!4poo!4pz~~~pooopz~~zpoopz~~}wwprrb!4rpp~~$#1KMNNMK???CMNNNMK!17?@BFF!4MFf~~!4?@FFMKK[!4KMM???MNNNMC??CMNNMC!4?BF!4MEFNNMC???K!4MNN!4MC???MNNNMC??CMNNMC??@FFMKK[!4KMM-#0!32~^NNNKWwww[[MNNN^~!13N!5~NN!5FNN^!10~N!4F!8~NN!4FN^!36~$#1!32?_ooorfFFFbbpooo_?!13o!5?oo!5woo_!10?o!4w!8?oo!4wo_-#0!32~}{o?AM!4~E?_w}}~o_o}}???}}}__!5~}}~fFFFAOw!6~^FB_w{???!6~B??}!4~{??!35~$#1!32?@BN~|p!4?x~^F@@?N^N@@~~~@@@^^!5?@@?Wwww|nF!6?_w{^FB~~~!6?{~~@!4?B~~-#0!36~{_?FB?o}!7~^NN???NN^!6~!8NE?_x!4~poooP@@???X!5~{o?F!4NB_o!35~$#1!36?B^~w{~N@!7?_oo~~~oo_!6?!8ox~^E!4?MNNNm}}~~~e!5?BN~w!4o{^N-#0!38~}}}!11~!8}!8~!6}!13~!6}!9~!4}!38~$#1!38?@@@!11?!8@!8?!6@!13?!6@!9?!4@-#0!5^!5~!5^!5~!7^!6~!4^~~~!5^!6~{www??!11~^^^N^\W???!22~^^^N^^^!6~!7^N^^^!8~^^^N^\W???~~$#1!5_!5?!5_!5?!7_!6?!4_???!5_!6?BFFF~~!11?___o_af~~~!22?___o___!6?!7_o___!8?___o_af~~~-#0}{??CMB@@FM??o{~~B@?{{!4}{w?@~~~}???~~~}}{??!10~??!8~B??{!5}{???!19~B@?K!5MK??B~~~}???{!4}{??!4~B??{!5}{???~~$#1@B~~zp{}}wp~~NB??{}~BB!4@BF~}???@~~~???@@B~~!10?~~!8?{~~B!5@B~~~!19?{}~r!5pr~~{???@~~~B!4@B~~!4?{~~B!5@B~~~-#0~~~woow~}woo{!4~}{wpprbrrpww{!5~{w!4pxwoopz~~~r!4poo!4pz~~~}{ww!5pwooopz!17~}wwprrb!4rpp~~~pooopz~~zpoopz~~}{ww!5pwooopz$#1???FNNF?@FNNB!4?@BFMMK[KKMFFB!5?BF!4MEFNNMC???K!4MNN!4MC???@BFF!5MFNNNMC!17?@FFMKK[!4KMM???MNNNMC??CMNNMC??@BFF!5MFNNNMC-#0!40~n!4F!99~$#1!40?O!4w-#0!10~^^n!29~???b!4rbBF!4~^NFbrrprrbFF^!4~rrbBBFBbrrrbv~~^NFbrrprrbFF^!42~$#1!10?__O!29?~~~[!4K[{w!4?_ow[KKMKK[ww_!4?KK[{{w{[KKK[G??_ow[KKMKK[ww_-#0!9~}{wq!10}!18~N???N^~~^N??N^~~o??H!6XWGG!4~^NN??!4N^!5~o??H!6XWGG!42~$#1!9?@BFL!10@!18?o~~~o_??_o~~o_??N~~u!6efvv!4?_oo~~!4o_!5?N~~u!6efvv-#0!41B!6ABB!6A!5BAAA?!6A!4B!10A!8BAAA?!6A!42B$#1!41?!6@??!6@!5?@@@B!6@!4?!10@!8?@@@B!6@$-\This text should be indented.
|
||||
EOF
|
||||
|
||||
# For more details, please see:
|
||||
# https://github.com/jerch/xterm-addon-image/issues/37
|
||||
|
||||
# By default, sixel is in a 2:1 aspect ratio, which means
|
||||
# every sixel graphics linefeed (GLF) adds another 12 pixels.
|
||||
# With four GLFs, we have five sixel lines = 5 * 12 = 60 pixels.
|
||||
|
||||
# Which text line that ends up on depends upon the height of your
|
||||
# font. On the VT340, the font is 20 pixels high.
|
||||
|
||||
# On a VT340 sixel scrolling is on by default. DEC refers to this by
|
||||
# two different names "sixel scrolling" and its negation, "DECSDM"
|
||||
# (Sixel Display Mode). They control the same thing, so when DECSDM is
|
||||
# on, sixel scrolling is off, and vice versa.
|
||||
|
||||
# When DECSDM is on, the graphic line feeds do not affect the text cursor.
|
||||
|
||||
|
||||
# How hackerb9 created the two sixel test images:
|
||||
#
|
||||
# convert -family "Courier" -style normal -density 72 -pointsize 26 -interline-spacing -12 -gravity center -fill gray80 -background none label:$'A genuine\nVT340\nwould end\n← here ' +trim +dither -colors 2 sixel:- > reference.six
|
||||
#
|
||||
# convert -family "Courier" -style normal -density 72 -pointsize 26 -interline-spacing -12 -gravity center -fill gray80 -background none label:$'Your terminal →' +trim -bordercolor none -border 2 +dither -colors 2 sixel:- > yourterminal.six
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
const sixelEncode = require('../node_modules/sixel/lib/SixelEncoder').image2sixel;
|
||||
const toRGBA8888 = require('../node_modules/sixel/lib/Colors').toRGBA8888;
|
||||
|
||||
function createRect(size, color) {
|
||||
const pixels = new Uint32Array(size * size);
|
||||
pixels.fill(toRGBA8888(...color));
|
||||
return sixelEncode(new Uint8ClampedArray(pixels.buffer), size, size);
|
||||
}
|
||||
|
||||
function createRectMinusOne(size, color) {
|
||||
const pixels = new Uint32Array(size * size);
|
||||
if (size - 1) {
|
||||
const sub = new Uint32Array(size - 1);
|
||||
sub.fill(toRGBA8888(...color));
|
||||
const last = size * (size - 1);
|
||||
for (let y = 0; y < last; y += size) {
|
||||
pixels.set(sub, y);
|
||||
}
|
||||
}
|
||||
return sixelEncode(new Uint8ClampedArray(pixels.buffer), size, size);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// clear + cursor and sixelScrolling off
|
||||
process.stdout.write('\x1b[2J\x1b[?25;80h');
|
||||
|
||||
for (let i = 1; i < 300; ++i) {
|
||||
await new Promise(res => setTimeout(() => {
|
||||
process.stdout.write(createRect(i, [0, 255, 0]));
|
||||
res();
|
||||
}, 5));
|
||||
}
|
||||
for (let i = 299; i >= 1; --i) {
|
||||
await new Promise(res => setTimeout(() => {
|
||||
process.stdout.write(createRectMinusOne(i, [0, 255, 0]));
|
||||
res();
|
||||
}, 5));
|
||||
}
|
||||
|
||||
// re-enable cursor and sixel scrolling
|
||||
process.stdout.write('\x1b[2J\x1b[?25;80l');
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1 @@
|
||||
]1337;File=inline=1;size=525;name=Li4vcGFsZXR0ZS5wbmc=:iVBORw0KGgoAAAANSUhEUgAAAoAAAABQCAYAAACJbMQlAAAABmJLR0QA/wD/AP+gvaeTAAABwklEQVR4nO3dwQnEMAwAQedIIem/yVwLBj0C3pm3uAO/Fj2ia631rh3P1lRu7vnof0+Z836zOe83m/N+sznvN5vzfrM57zeb+23+HAAAhxCAAAAxAhAAIEYAAgDECEAAgBgBCAAQIwABAGIEIABAjAAEAIi59z8tDQDACWwAAQBiBCAAQIwABACIEYAAADECEAAgRgACAMQIQACAGAEIABAjAAEAYlwCAQCIsQEEAIgRgAAAMQIQACBGAAIAxAhAAIAYAQgAECMAAQBiBCAAQIwABACIcQkEACDGBhAAIEYAAgDECEAAgBgBCAAQIwABAGIEIABAjAAEAIgRgAAAMQIQACDGJRAAgBgbQACAGAEIABAjAAEAYgQgAECMAAQAiBGAAAAxAhAAIEYAAgDECEAAgBiXQAAAYmwAAQBiBCAAQIwABACIEYAAADECEAAgRgACAMQIQACAGAEIABAjAAEAYlwCAQCIsQEEAIgRgAAAMQIQACBGAAIAxAhAAIAYAQgAECMAAQBiBCAAQIwABACIcQkEACDGBhAAIEYAAgDECEAAgBgBCAAQIwABAGIEIABAjAAEAIgRgAAAMQIQACDmDz4lC02AaqMRAAAAAElFTkSuQmCC
|
||||
@@ -0,0 +1 @@
|
||||
]1337;File=inline=1;size=1667;name=Li4vdGVzdGltYWdlcy93M2NfaG9tZV8yNTYuZ2lm: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
|
||||
@@ -0,0 +1 @@
|
||||
]1337;File=inline=1;size=2159;name=Li4vdGVzdGltYWdlcy93M2NfaG9tZV8yNTYuanBn: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
|
||||
@@ -0,0 +1 @@
|
||||
]1337;File=inline=1;size=1658;name=Li4vdGVzdGltYWdlcy93M2NfaG9tZV8yNTYucG5n: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
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/bin/bash
|
||||
|
||||
function print_palette() {
|
||||
L=$(( LOWER / 256))
|
||||
U=$(( (UPPER-1) / 256))
|
||||
for ((p = $L; p <= $U; p++))
|
||||
do
|
||||
echo "slot $((p*256))..$((p*256+255)):"
|
||||
echo -ne "\x1bP;1q"
|
||||
for i in {0..15}
|
||||
do
|
||||
a=$((i * 16 + p * 256))
|
||||
for j in {0..15}
|
||||
do
|
||||
echo -ne "#$((a+j))!6~"
|
||||
done
|
||||
echo -ne "\$-"
|
||||
done
|
||||
echo -e "\x1b\\"
|
||||
done
|
||||
}
|
||||
|
||||
colors=undefined
|
||||
max_colors=undefined
|
||||
|
||||
echo "Terminal Reports (XTSMGRAPHICS):"
|
||||
IFS=";" read -a REPLY -s -t 1 -d "S" -p $'\e[?1;1;0S'
|
||||
[[ ${REPLY[1]} == "0" ]] && colors=${REPLY[2]}
|
||||
echo "active colors: ${colors}"
|
||||
|
||||
IFS=";" read -a REPLY -s -t 1 -d "S" -p $'\e[?1;4;0S'
|
||||
[[ ${REPLY[1]} == "0" ]] && max_colors=${REPLY[2]}
|
||||
echo "max colors : ${max_colors}"
|
||||
echo
|
||||
|
||||
|
||||
# query up to colors by default
|
||||
# if colors is undefined (no XTSMGRAPHICS), assume 256
|
||||
ARG1=${1:-${colors}}
|
||||
if [[ $colors == "undefined" ]]
|
||||
then
|
||||
ARG1=${1:-256}
|
||||
fi
|
||||
LOWER=0
|
||||
UPPER=$ARG1
|
||||
ARG2=${2:-undefined}
|
||||
if [[ $ARG2 != "undefined" ]]
|
||||
then
|
||||
LOWER=ARG1
|
||||
UPPER=ARG2
|
||||
fi
|
||||
|
||||
if [[ $colors != "undefined" ]]
|
||||
then
|
||||
if [[ $colors -lt $UPPER ]] || [[ $colors -lt 256 ]]
|
||||
then
|
||||
echo -e "\x1b[33mNote: Active colors is smaller than test range."
|
||||
echo -e "A spec-conform terminal may repeat colors in 'slot mod ${colors}'.\x1b[m"
|
||||
echo
|
||||
fi
|
||||
else
|
||||
echo -e "\x1b[33mNote: Cannot query active colors."
|
||||
echo -e "The terminal may repeat colors beyond it max slot (e.g. slot mod 16).\x1b[m"
|
||||
echo
|
||||
fi
|
||||
|
||||
print_palette
|
||||
@@ -0,0 +1,141 @@
|
||||
#!/bin/bash
|
||||
|
||||
function smiling_smiley() {
|
||||
echo -ne '\x1bP;2q"1;1;60;60
|
||||
#6!60~$-
|
||||
!60~$-
|
||||
!60~$
|
||||
!15?#1!4]!22?!4]$-
|
||||
#6!60~$-
|
||||
!60~$-
|
||||
!60~$-
|
||||
!60~$
|
||||
!15?#1!4~!22?!4~$-
|
||||
#6!60~$
|
||||
!15?#1!30N$-
|
||||
#6!60~$-
|
||||
!60~$-
|
||||
'
|
||||
echo -ne '\x1b\\'
|
||||
}
|
||||
|
||||
function indifferent_smiley() {
|
||||
echo -ne '\x1bP;2q"1;1;60;60
|
||||
#6!60~$-
|
||||
!60~$-
|
||||
!60~$
|
||||
!15?#1!4]!22?!4]$-
|
||||
#6!60~$-
|
||||
!60~$-
|
||||
!60~$-
|
||||
!60~$-
|
||||
!60~$
|
||||
!15?#1!30N$-
|
||||
#6!60~$-
|
||||
!60~$-
|
||||
'
|
||||
echo -ne '\x1b\\'
|
||||
}
|
||||
|
||||
function sad_smiley() {
|
||||
echo -ne '\x1bP;2q"1;1;60;60
|
||||
#6!60~$-
|
||||
!60~$-
|
||||
!60~$
|
||||
!15?#1!4]!22?!4]$-
|
||||
#6!60~$-
|
||||
!60~$-
|
||||
!60~$-
|
||||
!60~$-
|
||||
!60~$
|
||||
!15?#1!30N$
|
||||
!15?#1!4o!22?!4o$-
|
||||
#6!60~$
|
||||
!15?#1!4B!22?!4B$-
|
||||
#6!60~$-
|
||||
'
|
||||
echo -ne '\x1b\\'
|
||||
}
|
||||
|
||||
function smiling_smiley_slim() {
|
||||
echo -ne '\x1bP;1q"1;1;60;60
|
||||
$-
|
||||
$-
|
||||
$-
|
||||
$-
|
||||
$-
|
||||
$-
|
||||
!15?#1!4~!22?!4~$-
|
||||
!15?#1!30N
|
||||
'
|
||||
echo -ne '\x1b\\'
|
||||
}
|
||||
|
||||
function indifferent_smiley_slim() {
|
||||
echo -ne '\x1bP;1q"1;1;60;60
|
||||
$-
|
||||
$-
|
||||
$-
|
||||
$-
|
||||
$-
|
||||
$-
|
||||
!15?#6!4~!22?!4~$-
|
||||
!15?!4o!22?!4o$ !15?#1!30N$-
|
||||
!15?#6!4B!22?!4B
|
||||
'
|
||||
echo -ne '\x1b\\'
|
||||
}
|
||||
|
||||
function sad_smiley_slim() {
|
||||
echo -ne '\x1bP;1q"1;1;60;60
|
||||
$-
|
||||
$-
|
||||
$-
|
||||
$-
|
||||
$-
|
||||
$-
|
||||
$-
|
||||
!15?#1!30N$
|
||||
!15?#1!4o!22?!4o$-
|
||||
!15?#1!4B!22?!4B$-
|
||||
'
|
||||
echo -ne '\x1b\\'
|
||||
}
|
||||
|
||||
function full() {
|
||||
smiling_smiley
|
||||
sleep .5
|
||||
indifferent_smiley
|
||||
sleep .5
|
||||
sad_smiley
|
||||
sleep .5
|
||||
indifferent_smiley
|
||||
sleep .5
|
||||
smiling_smiley
|
||||
}
|
||||
|
||||
function slim() {
|
||||
smiling_smiley
|
||||
sleep .5
|
||||
indifferent_smiley_slim
|
||||
sleep .5
|
||||
sad_smiley_slim
|
||||
sleep .5
|
||||
indifferent_smiley_slim
|
||||
sleep .5
|
||||
smiling_smiley_slim
|
||||
}
|
||||
|
||||
# clear screen and place cursor to 1;10
|
||||
echo -ne '\x1b[2J\x1b[10;1H'
|
||||
|
||||
# switch sixel scrolling off
|
||||
echo -ne '\x1b[?80h'
|
||||
|
||||
case "$1" in
|
||||
full ) full ;;
|
||||
slim ) slim ;;
|
||||
esac
|
||||
|
||||
# re-enable sixel scrolling
|
||||
echo -ne '\x1b[?80l'
|
||||
|
After Width: | Height: | Size: 525 B |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 160 KiB |
|
After Width: | Height: | Size: 6.3 MiB |
|
After Width: | Height: | Size: 599 KiB |
|
After Width: | Height: | Size: 988 B |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 333 B |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 324 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,288 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Test cursor row, column placement after sixel image is sent.
|
||||
|
||||
# After a sixel image is displayed, the text cursor is moved to the
|
||||
# row of the last sixel cursor position, but the column stays the same
|
||||
# as it was before the sixel image was sent.
|
||||
#
|
||||
# This can be thought of as sixel images always ending with an
|
||||
# implicit Graphics Carriage Return (`$`).
|
||||
|
||||
# ADDENDUM: It is not as simple as I thought. When a row of sixels
|
||||
# straddles two rows of text, the text cursor can be left on the upper row.
|
||||
# It seems up to three lines of pixels may be beneath any words printed.
|
||||
#
|
||||
# The rule for when this happens is not obvious to me, but can be seen
|
||||
# with images of height: 21, 22, 23, 24, 41, 42, 81, 82, 83, 84...
|
||||
#
|
||||
# My guess:
|
||||
# for a sixel image of height h, let a=(h-1)%6 and b=(h-1)%20,
|
||||
# then, the text will overlap the image when a>b.
|
||||
#
|
||||
# If that is the case, then the entire list of heights for which this
|
||||
# will happen on the VT340's 480 pixel high screen is:
|
||||
#
|
||||
# 21 22 23 24 41 42 81 82 83 84
|
||||
# 101 102 141 142 143 144 161 162
|
||||
# 201 202 203 204 221 222 261 262 263 264 281 282
|
||||
# 321 322 323 324 341 342 381 382 383 384
|
||||
# 401 402 441 442 443 444 461 462
|
||||
#
|
||||
# Note that there are 48 entries, so that means there's a 10% chance
|
||||
# if heights are chosen randomly from 1 to 480. However, if one were
|
||||
# to always pick heights which are a multiple of the character cell
|
||||
# height (20px), then the chances are 0% as there are no problematic
|
||||
# heights divisible by 20.
|
||||
|
||||
|
||||
# Sixel images often do *not* end with a `-` (Graphics New Line = GNL)
|
||||
# which sends the sixel cursor down 6 pixels. Any text printed next
|
||||
# will potentially overlap the last row of sixels!
|
||||
|
||||
# I am not yet positive, but I believe that, in general, applications
|
||||
# should send sixel images without a GNL but then send `^J`, a text
|
||||
# newline (NL), before displaying more text or graphics.
|
||||
|
||||
# IMPORTANT: sometimes neither a graphics nor a text newline is wanted.
|
||||
# For example, if an image is full screen, either newline would cause
|
||||
# the top line to scroll off the screen.
|
||||
|
||||
# | Text cursor column | Text cursor row
|
||||
# --------|--------------------|-------------------------------------
|
||||
# !GNL !NL| Unchanged | Overlapping last line of graphics
|
||||
# !GNL NL| Column=1 | First line immediately after graphic (usually)
|
||||
# GNL !NL| Unchanged | _Sometimes_ overlapping graphics
|
||||
# GNL NL| Column=1 | First *or* second line after graphic
|
||||
|
||||
|
||||
CSI=$'\e[' # Control Sequence Introducer
|
||||
DCS=$'\eP' # Device Control String
|
||||
ST=$'\e\\' # String Terminator
|
||||
|
||||
set_cursor_pos() {
|
||||
# Home, top left is row 1, col 1.
|
||||
local row=$1 col=$2
|
||||
echo -n ${CSI}${row}';'${col}'H'
|
||||
}
|
||||
|
||||
reset_palette() {
|
||||
# Send DECRSTS to load colors from a Color Table Report
|
||||
echo -n ${DCS}'2$p'
|
||||
|
||||
echo -n "0;2;0;0;0/" # VT color #0 is black and BG text color
|
||||
|
||||
echo -n "1;2;20;20;79/" # VT color #1 is blue
|
||||
echo -n "2;2;79;13;13/" # VT color #2 is red
|
||||
echo -n "3;2;20;79;20/" # VT color #3 is green
|
||||
|
||||
echo -n "4;2;79;20;79/" # VT color #4 is magenta
|
||||
echo -n "5;2;20;79;79/" # VT color #5 is cyan
|
||||
echo -n "6;2;79;79;20/" # VT color #6 is yellow
|
||||
|
||||
echo -n "7;2;46;46;46/" # VT color #7 is gray 50% and FG text color
|
||||
echo -n "8;2;26;26;26/" # VT color #8 is gray 25%
|
||||
|
||||
echo -n "9;2;33;33;59/" # VT color #9 is pastel blue
|
||||
echo -n "10;2;59;26;26/" # VT color #10 is pastel red
|
||||
echo -n "11;2;33;59;33/" # VT color #11 is pastel green
|
||||
|
||||
echo -n "12;2;59;33;59/" # VT color #12 is pastel magenta
|
||||
echo -n "13;2;33;59;59/" # VT color #13 is pastel cyan
|
||||
echo -n "14;2;59;59;33/" # VT color #14 is pastel yellow
|
||||
|
||||
echo -n "15;2;79;79;79" # VT color #15 is gray 75% and BOLD text color
|
||||
|
||||
echo -n ${ST} # String Terminator
|
||||
}
|
||||
|
||||
# Generate square of size w with final graphics new line removed
|
||||
square() {
|
||||
# Given a color index number and (optionally) a size, row, and column,
|
||||
# draw a square with top left corner at (row, column) and of size×size px.
|
||||
# Default size 100×100px (10cols, 5 rows)
|
||||
|
||||
local -i color=${1:-1} # Default is color index 1 (blue)
|
||||
local -i size=${2:-100} # Size in pixels (defaults to 100)
|
||||
local -i row=$3 column=$4 # If set to 0, cursor is not moved
|
||||
|
||||
if [[ row -ne 0 && column -ne 0 ]]; then
|
||||
set_cursor_pos $row $column
|
||||
fi
|
||||
|
||||
# Draw a square of the right color & size
|
||||
squaresize $color $size
|
||||
}
|
||||
|
||||
squaresize() {
|
||||
# Helper for square() that uses convert to return a sixel square of
|
||||
# the right color ($1) and size ($2).
|
||||
|
||||
# Similar to this but with variable size squares:
|
||||
# echo -n ${DCS}'0;0;0q"1;1;100;100#'${color}'!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100~-!100N'${ST}
|
||||
|
||||
|
||||
local color=${1:-1} # Default color index is 1 (blue)
|
||||
local size=${2:-100} # Default size is 100x100
|
||||
|
||||
# Get a sixel string
|
||||
local sq=$(convert -geometry ${size}x${size} xc:black sixel:-)
|
||||
|
||||
# Remove ImageMagick's extraneous Graphic New Line at end of image.
|
||||
sq=${sq%-??}$'\e\\'
|
||||
|
||||
# VT340s always used the same color register for the first sixel
|
||||
# color defined no matter what number it was assigned. That means,
|
||||
# each time we send a new sixel image, the previous one's color
|
||||
# palette gets changed. We don't want squares of all the same
|
||||
# color, so remove the color definition and just use the defaults.
|
||||
sq=${sq/\#0;2;0;0;0/}
|
||||
|
||||
# And finally, switch to the proper index for the color we want.
|
||||
echo -n ${sq//\#0/#${color}}
|
||||
}
|
||||
|
||||
squaregnl() {
|
||||
# Same as square(), but sends a graphics newline at the end of the sixels.
|
||||
# (Sticks a `-` before the String Terminator, "Esc \")
|
||||
sq=$(square "${@}")
|
||||
echo -n ${sq%??}$'-\e\\'
|
||||
}
|
||||
|
||||
|
||||
main() {
|
||||
clear
|
||||
reset_palette
|
||||
show_labels
|
||||
neither_graphic_nor_text 96 4 4 31 # size, color, row, column
|
||||
text_newline_only 96 9 4 1
|
||||
text_newline_only 84 9 4 14
|
||||
graphics_new_line_only 100 1 4 51
|
||||
graphics_new_line_only 96 1 4 64
|
||||
set_cursor_pos 1000 1
|
||||
}
|
||||
|
||||
|
||||
neither_graphic_nor_text() {
|
||||
# Typically sixel images should not end with a Graphics New Line (GNL)
|
||||
# However, if a text newline isn't sent, there will be overlap.
|
||||
|
||||
local -i size color row column
|
||||
read size color row column <<<"$@"
|
||||
|
||||
set_cursor_pos $((row++)) $column
|
||||
echo -n "Height $size"
|
||||
set_cursor_pos $((row++)) $column
|
||||
|
||||
# Three squares sent as separate sixel images, indented +1
|
||||
for i in {1..3}; do
|
||||
square $((color++)) $size
|
||||
tput cuf 1
|
||||
done
|
||||
|
||||
echo -n "overlap?"
|
||||
}
|
||||
|
||||
|
||||
text_newline_only() {
|
||||
# USING A TEXT NEWLINE (NL) after an sixel image that does NOT
|
||||
# have GNL is probably the best way to be on the text line
|
||||
# immediately below the image. However, the text will still
|
||||
# occasionally overlap the last four rows of pixels.
|
||||
|
||||
# Also, if multiple images are intended to be shown, there will
|
||||
# usually be a gap between them when using a text newline.
|
||||
|
||||
# Overlap happens because the height of a text cell is 20 pixels
|
||||
# and the height of a sixel is 6.
|
||||
# for (h=0; h<480; h++) if ((h-1)%6 > (h-1)%20 ) { h }
|
||||
|
||||
# Let the pixel position of the top of the graphics cursor be 'Yg'
|
||||
# and let the pixel position of the top of the corresponding cell
|
||||
# of text which the text cursor will be placed on be 'Yt'. Note
|
||||
# that Yg is evenly divisible by 6 and Yt, by 20. Taking the
|
||||
# remainder, r, after dividing Yg by 20 tells us how many pixels
|
||||
# down into a row of text the last line of sixels started. When
|
||||
# r==0, the sixels started at the top of the text row.
|
||||
|
||||
# When r = 14, the sixels covered the bottom six pixels on the row
|
||||
# of text. When 14 < r < 20, the sixel line impinged by r - 14
|
||||
# pixels into the text row below and there is a chance the next
|
||||
# text printed will overlap.
|
||||
|
||||
|
||||
local -i size color row column
|
||||
read size color row column <<<"$@"
|
||||
|
||||
local -i offset
|
||||
offset=$((column-1))
|
||||
|
||||
set_cursor_pos $row 1
|
||||
|
||||
if ((offset)); then tput cuf $((offset)); fi
|
||||
echo "Height $size"
|
||||
|
||||
# Three squares, separated by text new lines and indented +1
|
||||
for i in {1..3}; do
|
||||
if ((offset)); then tput cuf $offset; fi
|
||||
square $((color++)) $size
|
||||
offset=offset+1
|
||||
echo
|
||||
done
|
||||
|
||||
tput cuf $((offset))
|
||||
echo -n "overlap?"
|
||||
}
|
||||
|
||||
graphics_new_line_only() {
|
||||
# However, some sixel images end with a `-`, a Graphics New Line.
|
||||
# This can be useful for writing another image starting at the same
|
||||
# column without having to reposition the cursor.
|
||||
#
|
||||
# However, this runs the risk of having occasional overlap.
|
||||
|
||||
local -i size color row column
|
||||
read size color row column <<<"$@"
|
||||
|
||||
set_cursor_pos $((row++)) $column
|
||||
echo -n "Height $size"
|
||||
set_cursor_pos $((row++)) $column
|
||||
|
||||
# Three squares, separated by graphics new lines and indented +1
|
||||
for i in {1..3}; do
|
||||
squaregnl $((color++)) $size
|
||||
tput cuf 1
|
||||
done
|
||||
|
||||
echo -n "overlap?"
|
||||
}
|
||||
|
||||
|
||||
show_labels() {
|
||||
set_cursor_pos 1 10
|
||||
echo -n "Should sixel images include a GNL ('-') at the end?"
|
||||
|
||||
set_cursor_pos 3 29
|
||||
echo -n "Neither NL nor GNL"
|
||||
set_cursor_pos 3 1
|
||||
echo -n "Text New Line only"
|
||||
set_cursor_pos 3 51
|
||||
echo -n "Graphics New Line only"
|
||||
|
||||
set_cursor_pos 22 29
|
||||
echo -n "Always overlaps" # Neither NL nor GNL
|
||||
|
||||
set_cursor_pos 22 3
|
||||
echo -n "Overlaps a little" # NL only
|
||||
set_cursor_pos 23 3
|
||||
echo -n " Gaps a little" # NL only
|
||||
|
||||
set_cursor_pos 22 54
|
||||
echo -n "Overlaps badly" # GNL only
|
||||
set_cursor_pos 23 54
|
||||
echo -n "Never gaps" # GNL only
|
||||
}
|
||||
|
||||
|
||||
main
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "@xterm/addon-image",
|
||||
"version": "0.8.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
},
|
||||
"main": "lib/addon-image.js",
|
||||
"types": "typings/addon-image.d.ts",
|
||||
"repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-image",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"terminal",
|
||||
"image",
|
||||
"sixel",
|
||||
"xterm",
|
||||
"xterm.js"
|
||||
],
|
||||
"scripts": {
|
||||
"prepackage": "../../node_modules/.bin/tsc -p .",
|
||||
"package": "../../node_modules/.bin/webpack",
|
||||
"prepublishOnly": "npm run package"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"sixel": "^0.16.0",
|
||||
"xterm-wasm-parts": "^0.1.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* Copyright (c) 2023 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
import { IImageAddonOptions, IOscHandler, IResetHandler, ITerminalExt } from './Types';
|
||||
import { ImageRenderer } from './ImageRenderer';
|
||||
import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage';
|
||||
import Base64Decoder from 'xterm-wasm-parts/lib/base64/Base64Decoder.wasm';
|
||||
import { HeaderParser, IHeaderFields, HeaderState } from './IIPHeaderParser';
|
||||
import { imageType, UNSUPPORTED_TYPE } from './IIPMetrics';
|
||||
|
||||
|
||||
// eslint-disable-next-line
|
||||
declare const Buffer: any;
|
||||
|
||||
// limit hold memory in base64 decoder
|
||||
const KEEP_DATA = 4194304;
|
||||
|
||||
// default IIP header values
|
||||
const DEFAULT_HEADER: IHeaderFields = {
|
||||
name: 'Unnamed file',
|
||||
size: 0,
|
||||
width: 'auto',
|
||||
height: 'auto',
|
||||
preserveAspectRatio: 1,
|
||||
inline: 0
|
||||
};
|
||||
|
||||
|
||||
export class IIPHandler implements IOscHandler, IResetHandler {
|
||||
private _aborted = false;
|
||||
private _hp = new HeaderParser();
|
||||
private _header: IHeaderFields = DEFAULT_HEADER;
|
||||
private _dec = new Base64Decoder(KEEP_DATA);
|
||||
private _metrics = UNSUPPORTED_TYPE;
|
||||
|
||||
constructor(
|
||||
private readonly _opts: IImageAddonOptions,
|
||||
private readonly _renderer: ImageRenderer,
|
||||
private readonly _storage: ImageStorage,
|
||||
private readonly _coreTerminal: ITerminalExt
|
||||
) {}
|
||||
|
||||
public reset(): void {}
|
||||
|
||||
public start(): void {
|
||||
this._aborted = false;
|
||||
this._header = DEFAULT_HEADER;
|
||||
this._metrics = UNSUPPORTED_TYPE;
|
||||
this._hp.reset();
|
||||
}
|
||||
|
||||
public put(data: Uint32Array, start: number, end: number): void {
|
||||
if (this._aborted) return;
|
||||
|
||||
if (this._hp.state === HeaderState.END) {
|
||||
if (this._dec.put(data, start, end)) {
|
||||
this._dec.release();
|
||||
this._aborted = true;
|
||||
}
|
||||
} else {
|
||||
const dataPos = this._hp.parse(data, start, end);
|
||||
if (dataPos === -1) {
|
||||
this._aborted = true;
|
||||
return;
|
||||
}
|
||||
if (dataPos > 0) {
|
||||
this._header = Object.assign({}, DEFAULT_HEADER, this._hp.fields);
|
||||
if (!this._header.inline || !this._header.size || this._header.size > this._opts.iipSizeLimit) {
|
||||
this._aborted = true;
|
||||
return;
|
||||
}
|
||||
this._dec.init(this._header.size);
|
||||
if (this._dec.put(data, dataPos, end)) {
|
||||
this._dec.release();
|
||||
this._aborted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public end(success: boolean): boolean | Promise<boolean> {
|
||||
if (this._aborted) return true;
|
||||
|
||||
let w = 0;
|
||||
let h = 0;
|
||||
|
||||
// early exit condition chain
|
||||
let cond: number | boolean = true;
|
||||
if (cond = success) {
|
||||
if (cond = !this._dec.end()) {
|
||||
this._metrics = imageType(this._dec.data8);
|
||||
if (cond = this._metrics.mime !== 'unsupported') {
|
||||
w = this._metrics.width;
|
||||
h = this._metrics.height;
|
||||
if (cond = w && h && w * h < this._opts.pixelLimit) {
|
||||
[w, h] = this._resize(w, h).map(Math.floor);
|
||||
cond = w && h && w * h < this._opts.pixelLimit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!cond) {
|
||||
this._dec.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
const blob = new Blob([this._dec.data8], { type: this._metrics.mime });
|
||||
this._dec.release();
|
||||
|
||||
if (!window.createImageBitmap) {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const img = new Image();
|
||||
return new Promise<boolean>(r => {
|
||||
img.addEventListener('load', () => {
|
||||
URL.revokeObjectURL(url);
|
||||
const canvas = ImageRenderer.createCanvas(window.document, w, h);
|
||||
canvas.getContext('2d')?.drawImage(img, 0, 0, w, h);
|
||||
this._storage.addImage(canvas);
|
||||
r(true);
|
||||
});
|
||||
img.src = url;
|
||||
// sanity measure to avoid terminal blocking from dangling promise
|
||||
// happens from corrupt data (onload never gets fired)
|
||||
setTimeout(() => r(true), 1000);
|
||||
});
|
||||
}
|
||||
return createImageBitmap(blob, { resizeWidth: w, resizeHeight: h })
|
||||
.then(bm => {
|
||||
this._storage.addImage(bm);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private _resize(w: number, h: number): [number, number] {
|
||||
const cw = this._renderer.dimensions?.css.cell.width || CELL_SIZE_DEFAULT.width;
|
||||
const ch = this._renderer.dimensions?.css.cell.height || CELL_SIZE_DEFAULT.height;
|
||||
const width = this._renderer.dimensions?.css.canvas.width || cw * this._coreTerminal.cols;
|
||||
const height = this._renderer.dimensions?.css.canvas.height || ch * this._coreTerminal.rows;
|
||||
|
||||
const rw = this._dim(this._header.width!, width, cw);
|
||||
const rh = this._dim(this._header.height!, height, ch);
|
||||
if (!rw && !rh) {
|
||||
const wf = width / w; // TODO: should this respect initial cursor offset?
|
||||
const hf = (height - ch) / h; // TODO: fix offset issues from float cell height
|
||||
const f = Math.min(wf, hf);
|
||||
return f < 1 ? [w * f, h * f] : [w, h];
|
||||
}
|
||||
return !rw
|
||||
? [w * rh / h, rh]
|
||||
: this._header.preserveAspectRatio || !rw || !rh
|
||||
? [rw, h * rw / w] : [rw, rh];
|
||||
}
|
||||
|
||||
private _dim(s: string, total: number, cdim: number): number {
|
||||
if (s === 'auto') return 0;
|
||||
if (s.endsWith('%')) return parseInt(s.slice(0, -1)) * total / 100;
|
||||
if (s.endsWith('px')) return parseInt(s.slice(0, -2));
|
||||
return parseInt(s) * cdim;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* Copyright (c) 2023 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { HeaderParser, HeaderState, IHeaderFields } from './IIPHeaderParser';
|
||||
|
||||
|
||||
const CASES: [string, IHeaderFields][] = [
|
||||
['File=size=123456;name=dGVzdA==:', {name: 'test', size: 123456}],
|
||||
['File=size=123456;name=dGVzdA:', {name: 'test', size: 123456}],
|
||||
// utf-8 encoding in name
|
||||
['File=size=123456;name=w7xtbMOkdXTDnw==:', {name: 'ümläutß', size: 123456}],
|
||||
['File=size=123456;name=w7xtbMOkdXTDnw:', {name: 'ümläutß', size: 123456}],
|
||||
// full header spec
|
||||
[
|
||||
'File=inline=1;width=10px;height=20%;preserveAspectRatio=1;size=123456;name=w7xtbMOkdXTDnw:',
|
||||
{
|
||||
inline: 1,
|
||||
width: '10px',
|
||||
height: '20%',
|
||||
preserveAspectRatio: 1,
|
||||
size: 123456,
|
||||
name: 'ümläutß'
|
||||
}
|
||||
],
|
||||
[
|
||||
'File=inline=1;width=auto;height=20;preserveAspectRatio=1;size=123456;name=w7xtbMOkdXTDnw:',
|
||||
{
|
||||
inline: 1,
|
||||
width: 'auto',
|
||||
height: '20',
|
||||
preserveAspectRatio: 1,
|
||||
size: 123456,
|
||||
name: 'ümläutß'
|
||||
}
|
||||
]
|
||||
];
|
||||
|
||||
function fromBs(bs: string): Uint32Array {
|
||||
const r = new Uint32Array(bs.length);
|
||||
for (let i = 0; i < r.length; ++i) r[i] = bs.charCodeAt(i);
|
||||
return r;
|
||||
}
|
||||
|
||||
describe('IIPHeaderParser', () => {
|
||||
it('at once', () => {
|
||||
const hp = new HeaderParser();
|
||||
for (const example of CASES) {
|
||||
hp.reset();
|
||||
const inp = fromBs(example[0]);
|
||||
const res = hp.parse(inp, 0, inp.length);
|
||||
assert.strictEqual(res, inp.length);
|
||||
assert.strictEqual(hp.state, HeaderState.END);
|
||||
assert.deepEqual(hp.fields, example[1]);
|
||||
}
|
||||
});
|
||||
it('bytewise', () => {
|
||||
const hp = new HeaderParser();
|
||||
for (const example of CASES) {
|
||||
hp.reset();
|
||||
const inp = fromBs(example[0]);
|
||||
let pos = 0;
|
||||
let res = -2;
|
||||
while (res === -2 && pos < inp.length) {
|
||||
res = hp.parse(new Uint32Array([inp[pos++]]), 0, 1);
|
||||
}
|
||||
assert.strictEqual(res, 1);
|
||||
assert.strictEqual(hp.state, HeaderState.END);
|
||||
assert.deepEqual(hp.fields, example[1]);
|
||||
}
|
||||
});
|
||||
it('no File= starter', () => {
|
||||
const hp = new HeaderParser();
|
||||
let inp = fromBs('size=123456;name=dGVzdA==:');
|
||||
let res = hp.parse(inp, 0, inp.length);
|
||||
assert.strictEqual(res, -1);
|
||||
hp.reset();
|
||||
inp = fromBs(CASES[0][0]);
|
||||
res = hp.parse(inp, 0, inp.length);
|
||||
assert.strictEqual(res, inp.length);
|
||||
assert.strictEqual(hp.state, HeaderState.END);
|
||||
assert.deepEqual(hp.fields, CASES[0][1]);
|
||||
});
|
||||
it('empty key - error', () => {
|
||||
const hp = new HeaderParser();
|
||||
let inp = fromBs('File=size=123456;=dGVzdA==:');
|
||||
let res = hp.parse(inp, 0, inp.length);
|
||||
assert.strictEqual(res, -1);
|
||||
hp.reset();
|
||||
inp = fromBs(CASES[0][0]);
|
||||
res = hp.parse(inp, 0, inp.length);
|
||||
assert.strictEqual(res, inp.length);
|
||||
assert.strictEqual(hp.state, HeaderState.END);
|
||||
assert.deepEqual(hp.fields, CASES[0][1]);
|
||||
});
|
||||
it('empty size value - set to 0', () => {
|
||||
const hp = new HeaderParser();
|
||||
let inp = fromBs('File=size=;name=dGVzdA==:');
|
||||
let res = hp.parse(inp, 0, inp.length);
|
||||
assert.strictEqual(res, inp.length);
|
||||
assert.strictEqual(hp.state, HeaderState.END);
|
||||
assert.deepEqual(hp.fields, {name: 'test', size: 0});
|
||||
hp.reset();
|
||||
inp = fromBs(CASES[0][0]);
|
||||
res = hp.parse(inp, 0, inp.length);
|
||||
assert.strictEqual(res, inp.length);
|
||||
assert.strictEqual(hp.state, HeaderState.END);
|
||||
assert.deepEqual(hp.fields, CASES[0][1]);
|
||||
});
|
||||
it('empty name value - set to empty string', () => {
|
||||
const hp = new HeaderParser();
|
||||
let inp = fromBs('File=size=123456;name=:');
|
||||
let res = hp.parse(inp, 0, inp.length);
|
||||
assert.strictEqual(res, inp.length);
|
||||
assert.strictEqual(hp.state, HeaderState.END);
|
||||
assert.deepEqual(hp.fields, {name: '', size: 123456});
|
||||
hp.reset();
|
||||
inp = fromBs(CASES[0][0]);
|
||||
res = hp.parse(inp, 0, inp.length);
|
||||
assert.strictEqual(res, inp.length);
|
||||
assert.strictEqual(hp.state, HeaderState.END);
|
||||
assert.deepEqual(hp.fields, CASES[0][1]);
|
||||
});
|
||||
it('empty size value - error', () => {
|
||||
const hp = new HeaderParser();
|
||||
let inp = fromBs('File=inline=1;width=;height=20%;preserveAspectRatio=1;size=123456;name=w7xtbMOkdXTDnw:');
|
||||
let res = hp.parse(inp, 0, inp.length);
|
||||
assert.strictEqual(res, -1);
|
||||
hp.reset();
|
||||
inp = fromBs(CASES[0][0]);
|
||||
res = hp.parse(inp, 0, inp.length);
|
||||
assert.strictEqual(res, inp.length);
|
||||
assert.strictEqual(hp.state, HeaderState.END);
|
||||
assert.deepEqual(hp.fields, CASES[0][1]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,186 @@
|
||||
/**
|
||||
* Copyright (c) 2023 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line
|
||||
declare const Buffer: any;
|
||||
|
||||
|
||||
export interface IHeaderFields {
|
||||
// base-64 encoded filename. Defaults to "Unnamed file".
|
||||
name: string;
|
||||
// File size in bytes. The file transfer will be canceled if this size is exceeded.
|
||||
size: number;
|
||||
/**
|
||||
* Optional width and height to render:
|
||||
* - N: N character cells.
|
||||
* - Npx: N pixels.
|
||||
* - N%: N percent of the session's width or height.
|
||||
* - auto: The image's inherent size will be used to determine an appropriate dimension.
|
||||
*/
|
||||
width?: string;
|
||||
height?: string;
|
||||
// Optional, defaults to 1 respecting aspect ratio (width takes precedence).
|
||||
preserveAspectRatio?: number;
|
||||
// Optional, defaults to 0. If set to 1, the file will be displayed inline, else downloaded
|
||||
// (download not supported).
|
||||
inline?: number;
|
||||
}
|
||||
|
||||
export const enum HeaderState {
|
||||
START = 0,
|
||||
ABORT = 1,
|
||||
KEY = 2,
|
||||
VALUE = 3,
|
||||
END = 4
|
||||
}
|
||||
|
||||
// field value decoders
|
||||
|
||||
// ASCII bytes to string
|
||||
function toStr(data: Uint32Array): string {
|
||||
let s = '';
|
||||
for (let i = 0; i < data.length; ++i) {
|
||||
s += String.fromCharCode(data[i]);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// digits to integer
|
||||
function toInt(data: Uint32Array): number {
|
||||
let v = 0;
|
||||
for (let i = 0; i < data.length; ++i) {
|
||||
if (data[i] < 48 || data[i] > 57) {
|
||||
throw new Error('illegal char');
|
||||
}
|
||||
v = v * 10 + data[i] - 48;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
// check for correct size entry
|
||||
function toSize(data: Uint32Array): string {
|
||||
const v = toStr(data);
|
||||
if (!v.match(/^((auto)|(\d+?((px)|(%)){0,1}))$/)) {
|
||||
throw new Error('illegal size');
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
// name is base64 encoded utf-8
|
||||
function toName(data: Uint32Array): string {
|
||||
if (typeof Buffer !== 'undefined') {
|
||||
return Buffer.from(toStr(data), 'base64').toString();
|
||||
}
|
||||
const bs = atob(toStr(data));
|
||||
const b = new Uint8Array(bs.length);
|
||||
for (let i = 0; i < b.length; ++i) {
|
||||
b[i] = bs.charCodeAt(i);
|
||||
}
|
||||
return new TextDecoder().decode(b);
|
||||
}
|
||||
|
||||
const DECODERS: {[key: string]: (v: Uint32Array) => any} = {
|
||||
inline: toInt,
|
||||
size: toInt,
|
||||
name: toName,
|
||||
width: toSize,
|
||||
height: toSize,
|
||||
preserveAspectRatio: toInt
|
||||
};
|
||||
|
||||
|
||||
const FILE_MARKER = [70, 105, 108, 101];
|
||||
const MAX_FIELDCHARS = 1024;
|
||||
|
||||
|
||||
export class HeaderParser {
|
||||
public state: HeaderState = HeaderState.START;
|
||||
private _buffer = new Uint32Array(MAX_FIELDCHARS);
|
||||
private _position = 0;
|
||||
private _key = '';
|
||||
public fields: {[key: string]: any} = {};
|
||||
|
||||
public reset(): void {
|
||||
this._buffer.fill(0);
|
||||
this.state = HeaderState.START;
|
||||
this._position = 0;
|
||||
this.fields = {};
|
||||
this._key = '';
|
||||
}
|
||||
|
||||
public parse(data: Uint32Array, start: number, end: number): number {
|
||||
let state = this.state;
|
||||
let pos = this._position;
|
||||
const buffer = this._buffer;
|
||||
if (state === HeaderState.ABORT || state === HeaderState.END) return -1;
|
||||
if (state === HeaderState.START && pos > 6) return -1;
|
||||
for (let i = start; i < end; ++i) {
|
||||
const c = data[i];
|
||||
switch (c) {
|
||||
case 59: // ;
|
||||
if (!this._storeValue(pos)) return this._a();
|
||||
state = HeaderState.KEY;
|
||||
pos = 0;
|
||||
break;
|
||||
case 61: // =
|
||||
if (state === HeaderState.START) {
|
||||
for (let k = 0; k < FILE_MARKER.length; ++k) {
|
||||
if (buffer[k] !== FILE_MARKER[k]) return this._a();
|
||||
}
|
||||
state = HeaderState.KEY;
|
||||
pos = 0;
|
||||
} else if (state === HeaderState.KEY) {
|
||||
if (!this._storeKey(pos)) return this._a();
|
||||
state = HeaderState.VALUE;
|
||||
pos = 0;
|
||||
} else if (state === HeaderState.VALUE) {
|
||||
if (pos >= MAX_FIELDCHARS) return this._a();
|
||||
buffer[pos++] = c;
|
||||
}
|
||||
break;
|
||||
case 58: // :
|
||||
if (state === HeaderState.VALUE) {
|
||||
if (!this._storeValue(pos)) return this._a();
|
||||
}
|
||||
this.state = HeaderState.END;
|
||||
return i + 1;
|
||||
default:
|
||||
if (pos >= MAX_FIELDCHARS) return this._a();
|
||||
buffer[pos++] = c;
|
||||
}
|
||||
}
|
||||
this.state = state;
|
||||
this._position = pos;
|
||||
return -2;
|
||||
}
|
||||
|
||||
private _a(): number {
|
||||
this.state = HeaderState.ABORT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
private _storeKey(pos: number): boolean {
|
||||
const k = toStr(this._buffer.subarray(0, pos));
|
||||
if (k) {
|
||||
this._key = k;
|
||||
this.fields[k] = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private _storeValue(pos: number): boolean {
|
||||
if (this._key) {
|
||||
try {
|
||||
const v = this._buffer.slice(0, pos);
|
||||
this.fields[this._key] = DECODERS[this._key] ? DECODERS[this._key](v) : v;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2023 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { imageType, IMetrics } from './IIPMetrics';
|
||||
|
||||
// fix missing nodejs decl
|
||||
declare const require: (s: string) => any;
|
||||
const fs = require('fs');
|
||||
|
||||
|
||||
const TEST_IMAGES: [string, IMetrics][] = [
|
||||
['w3c_home_256.gif', { mime: 'image/gif', width: 72, height: 48 }],
|
||||
['w3c_home_256.jpg', { mime: 'image/jpeg', width: 72, height: 48 }],
|
||||
['w3c_home_256.png', { mime: 'image/png', width: 72, height: 48 }],
|
||||
['w3c_home_2.gif', { mime: 'image/gif', width: 72, height: 48 }],
|
||||
['w3c_home_2.jpg', { mime: 'image/jpeg', width: 72, height: 48 }],
|
||||
['w3c_home_2.png', { mime: 'image/png', width: 72, height: 48 }],
|
||||
['w3c_home_animation.gif', { mime: 'image/gif', width: 72, height: 48 }],
|
||||
['w3c_home.gif', { mime: 'image/gif', width: 72, height: 48 }],
|
||||
['w3c_home_gray.gif', { mime: 'image/gif', width: 72, height: 48 }],
|
||||
['w3c_home_gray.jpg', { mime: 'image/jpeg', width: 72, height: 48 }],
|
||||
['w3c_home_gray.png', { mime: 'image/png', width: 72, height: 48 }],
|
||||
['w3c_home.jpg', { mime: 'image/jpeg', width: 72, height: 48 }],
|
||||
['w3c_home.png', { mime: 'image/png', width: 72, height: 48 }],
|
||||
['spinfox.png', { mime: 'image/png', width: 148, height: 148 }],
|
||||
['iphone_hdr_YES.jpg', { mime: 'image/jpeg', width: 3264, height: 2448 }],
|
||||
['nikon-e950.jpg', { mime: 'image/jpeg', width: 800, height: 600 }],
|
||||
['agfa-makernotes.jpg', { mime: 'image/jpeg', width: 8, height: 8 }],
|
||||
['sony-alpha-6000.jpg', { mime: 'image/jpeg', width: 6000, height: 4000 }]
|
||||
];
|
||||
|
||||
|
||||
describe('IIPMetrics', () => {
|
||||
it('bunch of testimages', () => {
|
||||
for (let i = 0; i < TEST_IMAGES.length; ++i) {
|
||||
const imageData = fs.readFileSync('./addons/addon-image/fixture/testimages/' + TEST_IMAGES[i][0]);
|
||||
assert.deepStrictEqual(imageType(imageData), TEST_IMAGES[i][1]);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (c) 2023 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
|
||||
export type ImageType = 'image/png' | 'image/jpeg' | 'image/gif' | 'unsupported' | '';
|
||||
|
||||
export interface IMetrics {
|
||||
mime: ImageType;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export const UNSUPPORTED_TYPE: IMetrics = {
|
||||
mime: 'unsupported',
|
||||
width: 0,
|
||||
height: 0
|
||||
};
|
||||
|
||||
export function imageType(d: Uint8Array): IMetrics {
|
||||
if (d.length < 24) {
|
||||
return UNSUPPORTED_TYPE;
|
||||
}
|
||||
const d32 = new Uint32Array(d.buffer, d.byteOffset, 6);
|
||||
// PNG: 89 50 4E 47 0D 0A 1A 0A (8 first bytes == magic number for PNG)
|
||||
// + first chunk must be IHDR
|
||||
if (d32[0] === 0x474E5089 && d32[1] === 0x0A1A0A0D && d32[3] === 0x52444849) {
|
||||
return {
|
||||
mime: 'image/png',
|
||||
width: d[16] << 24 | d[17] << 16 | d[18] << 8 | d[19],
|
||||
height: d[20] << 24 | d[21] << 16 | d[22] << 8 | d[23]
|
||||
};
|
||||
}
|
||||
// JPEG: FF D8 FF E0 xx xx JFIF or FF D8 FF E1 xx xx Exif 00 00
|
||||
if ((d32[0] === 0xE0FFD8FF || d32[0] === 0xE1FFD8FF)
|
||||
&& (
|
||||
(d[6] === 0x4a && d[7] === 0x46 && d[8] === 0x49 && d[9] === 0x46)
|
||||
|| (d[6] === 0x45 && d[7] === 0x78 && d[8] === 0x69 && d[9] === 0x66)
|
||||
)
|
||||
) {
|
||||
const [width, height] = jpgSize(d);
|
||||
return { mime: 'image/jpeg', width, height };
|
||||
}
|
||||
// GIF: GIF87a or GIF89a
|
||||
if (d32[0] === 0x38464947 && (d[4] === 0x37 || d[4] === 0x39) && d[5] === 0x61) {
|
||||
return {
|
||||
mime: 'image/gif',
|
||||
width: d[7] << 8 | d[6],
|
||||
height: d[9] << 8 | d[8]
|
||||
};
|
||||
}
|
||||
return UNSUPPORTED_TYPE;
|
||||
}
|
||||
|
||||
function jpgSize(d: Uint8Array): [number, number] {
|
||||
const len = d.length;
|
||||
let i = 4;
|
||||
let blockLength = d[i] << 8 | d[i + 1];
|
||||
while (true) {
|
||||
i += blockLength;
|
||||
if (i >= len) {
|
||||
// exhausted without size info
|
||||
return [0, 0];
|
||||
}
|
||||
if (d[i] !== 0xFF) {
|
||||
return [0, 0];
|
||||
}
|
||||
if (d[i + 1] === 0xC0 || d[i + 1] === 0xC2) {
|
||||
if (i + 8 < len) {
|
||||
return [
|
||||
d[i + 7] << 8 | d[i + 8],
|
||||
d[i + 5] << 8 | d[i + 6]
|
||||
];
|
||||
}
|
||||
return [0, 0];
|
||||
}
|
||||
i += 2;
|
||||
blockLength = d[i] << 8 | d[i + 1];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
/**
|
||||
* Copyright (c) 2020 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import type { ITerminalAddon, IDisposable } from '@xterm/xterm';
|
||||
import type { ImageAddon as IImageApi } from '@xterm/addon-image';
|
||||
import { IIPHandler } from './IIPHandler';
|
||||
import { ImageRenderer } from './ImageRenderer';
|
||||
import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage';
|
||||
import { SixelHandler } from './SixelHandler';
|
||||
import { ITerminalExt, IImageAddonOptions, IResetHandler } from './Types';
|
||||
|
||||
// default values of addon ctor options
|
||||
const DEFAULT_OPTIONS: IImageAddonOptions = {
|
||||
enableSizeReports: true,
|
||||
pixelLimit: 16777216, // limit to 4096 * 4096 pixels
|
||||
sixelSupport: true,
|
||||
sixelScrolling: true,
|
||||
sixelPaletteLimit: 256,
|
||||
sixelSizeLimit: 25000000,
|
||||
storageLimit: 128,
|
||||
showPlaceholder: true,
|
||||
iipSupport: true,
|
||||
iipSizeLimit: 20000000
|
||||
};
|
||||
|
||||
// max palette size supported by the sixel lib (compile time setting)
|
||||
const MAX_SIXEL_PALETTE_SIZE = 4096;
|
||||
|
||||
// definitions for _xtermGraphicsAttributes sequence
|
||||
const enum GaItem {
|
||||
COLORS = 1,
|
||||
SIXEL_GEO = 2,
|
||||
REGIS_GEO = 3
|
||||
}
|
||||
const enum GaAction {
|
||||
READ = 1,
|
||||
SET_DEFAULT = 2,
|
||||
SET = 3,
|
||||
READ_MAX = 4
|
||||
}
|
||||
const enum GaStatus {
|
||||
SUCCESS = 0,
|
||||
ITEM_ERROR = 1,
|
||||
ACTION_ERROR = 2,
|
||||
FAILURE = 3
|
||||
}
|
||||
|
||||
|
||||
export class ImageAddon implements ITerminalAddon , IImageApi {
|
||||
private _opts: IImageAddonOptions;
|
||||
private _defaultOpts: IImageAddonOptions;
|
||||
private _storage: ImageStorage | undefined;
|
||||
private _renderer: ImageRenderer | undefined;
|
||||
private _disposables: IDisposable[] = [];
|
||||
private _terminal: ITerminalExt | undefined;
|
||||
private _handlers: Map<String, IResetHandler> = new Map();
|
||||
|
||||
constructor(opts?: Partial<IImageAddonOptions>) {
|
||||
this._opts = Object.assign({}, DEFAULT_OPTIONS, opts);
|
||||
this._defaultOpts = Object.assign({}, DEFAULT_OPTIONS, opts);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
for (const obj of this._disposables) {
|
||||
obj.dispose();
|
||||
}
|
||||
this._disposables.length = 0;
|
||||
this._handlers.clear();
|
||||
}
|
||||
|
||||
private _disposeLater(...args: IDisposable[]): void {
|
||||
for (const obj of args) {
|
||||
this._disposables.push(obj);
|
||||
}
|
||||
}
|
||||
|
||||
public activate(terminal: ITerminalExt): void {
|
||||
this._terminal = terminal;
|
||||
|
||||
// internal data structures
|
||||
this._renderer = new ImageRenderer(terminal);
|
||||
this._storage = new ImageStorage(terminal, this._renderer, this._opts);
|
||||
|
||||
// enable size reports
|
||||
if (this._opts.enableSizeReports) {
|
||||
// const windowOptions = terminal.getOption('windowOptions');
|
||||
// windowOptions.getWinSizePixels = true;
|
||||
// windowOptions.getCellSizePixels = true;
|
||||
// windowOptions.getWinSizeChars = true;
|
||||
// terminal.setOption('windowOptions', windowOptions);
|
||||
const windowOps = terminal.options.windowOptions || {};
|
||||
windowOps.getWinSizePixels = true;
|
||||
windowOps.getCellSizePixels = true;
|
||||
windowOps.getWinSizeChars = true;
|
||||
terminal.options.windowOptions = windowOps;
|
||||
}
|
||||
|
||||
this._disposeLater(
|
||||
this._renderer,
|
||||
this._storage,
|
||||
|
||||
// DECSET/DECRST/DA1/XTSMGRAPHICS handlers
|
||||
terminal.parser.registerCsiHandler({ prefix: '?', final: 'h' }, params => this._decset(params)),
|
||||
terminal.parser.registerCsiHandler({ prefix: '?', final: 'l' }, params => this._decrst(params)),
|
||||
terminal.parser.registerCsiHandler({ final: 'c' }, params => this._da1(params)),
|
||||
terminal.parser.registerCsiHandler({ prefix: '?', final: 'S' }, params => this._xtermGraphicsAttributes(params)),
|
||||
|
||||
// render hook
|
||||
terminal.onRender(range => this._storage?.render(range)),
|
||||
|
||||
/**
|
||||
* reset handlers covered:
|
||||
* - DECSTR
|
||||
* - RIS
|
||||
* - Terminal.reset()
|
||||
*/
|
||||
terminal.parser.registerCsiHandler({ intermediates: '!', final: 'p' }, () => this.reset()),
|
||||
terminal.parser.registerEscHandler({ final: 'c' }, () => this.reset()),
|
||||
terminal._core._inputHandler.onRequestReset(() => this.reset()),
|
||||
|
||||
// wipe canvas and delete alternate images on buffer switch
|
||||
terminal.buffer.onBufferChange(() => this._storage?.wipeAlternate()),
|
||||
|
||||
// extend images to the right on resize
|
||||
terminal.onResize(metrics => this._storage?.viewportResize(metrics))
|
||||
);
|
||||
|
||||
// SIXEL handler
|
||||
if (this._opts.sixelSupport) {
|
||||
const sixelHandler = new SixelHandler(this._opts, this._storage!, terminal);
|
||||
this._handlers.set('sixel', sixelHandler);
|
||||
this._disposeLater(
|
||||
terminal._core._inputHandler._parser.registerDcsHandler({ final: 'q' }, sixelHandler)
|
||||
);
|
||||
}
|
||||
|
||||
// iTerm IIP handler
|
||||
if (this._opts.iipSupport) {
|
||||
const iipHandler = new IIPHandler(this._opts, this._renderer!, this._storage!, terminal);
|
||||
this._handlers.set('iip', iipHandler);
|
||||
this._disposeLater(
|
||||
terminal._core._inputHandler._parser.registerOscHandler(1337, iipHandler)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Note: storageLimit is skipped here to not intoduce a surprising side effect.
|
||||
public reset(): boolean {
|
||||
// reset options customizable by sequences to defaults
|
||||
this._opts.sixelScrolling = this._defaultOpts.sixelScrolling;
|
||||
this._opts.sixelPaletteLimit = this._defaultOpts.sixelPaletteLimit;
|
||||
// also clear image storage
|
||||
this._storage?.reset();
|
||||
// reset protocol handlers
|
||||
for (const handler of this._handlers.values()) {
|
||||
handler.reset();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public get storageLimit(): number {
|
||||
return this._storage?.getLimit() || -1;
|
||||
}
|
||||
|
||||
public set storageLimit(limit: number) {
|
||||
this._storage?.setLimit(limit);
|
||||
this._opts.storageLimit = limit;
|
||||
}
|
||||
|
||||
public get storageUsage(): number {
|
||||
if (this._storage) {
|
||||
return this._storage.getUsage();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public get showPlaceholder(): boolean {
|
||||
return this._opts.showPlaceholder;
|
||||
}
|
||||
|
||||
public set showPlaceholder(value: boolean) {
|
||||
this._opts.showPlaceholder = value;
|
||||
this._renderer?.showPlaceholder(value);
|
||||
}
|
||||
|
||||
public getImageAtBufferCell(x: number, y: number): HTMLCanvasElement | undefined {
|
||||
return this._storage?.getImageAtBufferCell(x, y);
|
||||
}
|
||||
|
||||
public extractTileAtBufferCell(x: number, y: number): HTMLCanvasElement | undefined {
|
||||
return this._storage?.extractTileAtBufferCell(x, y);
|
||||
}
|
||||
|
||||
private _report(s: string): void {
|
||||
this._terminal?._core.coreService.triggerDataEvent(s);
|
||||
}
|
||||
|
||||
private _decset(params: (number | number[])[]): boolean {
|
||||
for (let i = 0; i < params.length; ++i) {
|
||||
switch (params[i]) {
|
||||
case 80:
|
||||
this._opts.sixelScrolling = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private _decrst(params: (number | number[])[]): boolean {
|
||||
for (let i = 0; i < params.length; ++i) {
|
||||
switch (params[i]) {
|
||||
case 80:
|
||||
this._opts.sixelScrolling = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// overload DA to return something more appropriate
|
||||
private _da1(params: (number | number[])[]): boolean {
|
||||
if (params[0]) {
|
||||
return true;
|
||||
}
|
||||
// reported features:
|
||||
// 62 - VT220
|
||||
// 4 - SIXEL support
|
||||
// 9 - charsets
|
||||
// 22 - ANSI colors
|
||||
if (this._opts.sixelSupport) {
|
||||
this._report(`\x1b[?62;4;9;22c`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of xterm's graphics attribute sequence.
|
||||
*
|
||||
* Supported features:
|
||||
* - read/change palette limits (max 4096 by sixel lib)
|
||||
* - read SIXEL canvas geometry (reports current window canvas or
|
||||
* squared pixelLimit if canvas > pixel limit)
|
||||
*
|
||||
* Everything else is deactivated.
|
||||
*/
|
||||
private _xtermGraphicsAttributes(params: (number | number[])[]): boolean {
|
||||
if (params.length < 2) {
|
||||
return true;
|
||||
}
|
||||
if (params[0] === GaItem.COLORS) {
|
||||
switch (params[1]) {
|
||||
case GaAction.READ:
|
||||
this._report(`\x1b[?${params[0]};${GaStatus.SUCCESS};${this._opts.sixelPaletteLimit}S`);
|
||||
return true;
|
||||
case GaAction.SET_DEFAULT:
|
||||
this._opts.sixelPaletteLimit = this._defaultOpts.sixelPaletteLimit;
|
||||
this._report(`\x1b[?${params[0]};${GaStatus.SUCCESS};${this._opts.sixelPaletteLimit}S`);
|
||||
// also reset protocol handlers for now
|
||||
for (const handler of this._handlers.values()) {
|
||||
handler.reset();
|
||||
}
|
||||
return true;
|
||||
case GaAction.SET:
|
||||
if (params.length > 2 && !(params[2] instanceof Array) && params[2] <= MAX_SIXEL_PALETTE_SIZE) {
|
||||
this._opts.sixelPaletteLimit = params[2];
|
||||
this._report(`\x1b[?${params[0]};${GaStatus.SUCCESS};${this._opts.sixelPaletteLimit}S`);
|
||||
} else {
|
||||
this._report(`\x1b[?${params[0]};${GaStatus.ACTION_ERROR}S`);
|
||||
}
|
||||
return true;
|
||||
case GaAction.READ_MAX:
|
||||
this._report(`\x1b[?${params[0]};${GaStatus.SUCCESS};${MAX_SIXEL_PALETTE_SIZE}S`);
|
||||
return true;
|
||||
default:
|
||||
this._report(`\x1b[?${params[0]};${GaStatus.ACTION_ERROR}S`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (params[0] === GaItem.SIXEL_GEO) {
|
||||
switch (params[1]) {
|
||||
// we only implement read and read_max here
|
||||
case GaAction.READ:
|
||||
let width = this._renderer?.dimensions?.css.canvas.width;
|
||||
let height = this._renderer?.dimensions?.css.canvas.height;
|
||||
if (!width || !height) {
|
||||
// for some reason we have no working image renderer
|
||||
// --> fallback to default cell size
|
||||
const cellSize = CELL_SIZE_DEFAULT;
|
||||
width = (this._terminal?.cols || 80) * cellSize.width;
|
||||
height = (this._terminal?.rows || 24) * cellSize.height;
|
||||
}
|
||||
if (width * height < this._opts.pixelLimit) {
|
||||
this._report(`\x1b[?${params[0]};${GaStatus.SUCCESS};${width.toFixed(0)};${height.toFixed(0)}S`);
|
||||
} else {
|
||||
// if we overflow pixelLimit report that squared instead
|
||||
const x = Math.floor(Math.sqrt(this._opts.pixelLimit));
|
||||
this._report(`\x1b[?${params[0]};${GaStatus.SUCCESS};${x};${x}S`);
|
||||
}
|
||||
return true;
|
||||
case GaAction.READ_MAX:
|
||||
// read_max returns pixelLimit as square area
|
||||
const x = Math.floor(Math.sqrt(this._opts.pixelLimit));
|
||||
this._report(`\x1b[?${params[0]};${GaStatus.SUCCESS};${x};${x}S`);
|
||||
return true;
|
||||
default:
|
||||
this._report(`\x1b[?${params[0]};${GaStatus.ACTION_ERROR}S`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// exit with error on ReGIS or any other requests
|
||||
this._report(`\x1b[?${params[0]};${GaStatus.ITEM_ERROR}S`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||