Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3504e2ebd5 | ||
|
|
6c22e3df6b | ||
|
|
33bf765b80 | ||
|
|
d960793f0c | ||
|
|
4975f389ff | ||
|
|
4649ad7e7b | ||
|
|
a425c325b7 | ||
|
|
c77025b18c | ||
|
|
c6abc85810 | ||
|
|
710223ab27 | ||
|
|
ca9df42a73 | ||
|
|
9f033cb1d2 | ||
|
|
9613f36280 | ||
|
|
41673f2c20 | ||
|
|
6c98f82ffe | ||
|
|
a90ef52bda | ||
|
|
86884d2d88 | ||
|
|
47c486e1be | ||
|
|
e07b9684bb | ||
|
|
10fb48869e | ||
|
|
9f3746a3a1 | ||
|
|
50fc84f7d7 | ||
|
|
3e0f0331b3 | ||
|
|
b918f0d34a | ||
|
|
f0e3159ece | ||
|
|
ecfc7336c5 | ||
|
|
aec8c68bf6 | ||
|
|
0fb63561fb | ||
|
|
106ea62b4c | ||
|
|
e73bf11849 | ||
|
|
8d9eda5d0a | ||
|
|
cf0b7ce2d0 | ||
|
|
60419a7cc6 | ||
|
|
1b12b2d15d | ||
|
|
b5844990c4 | ||
|
|
2652768310 | ||
|
|
1f8a6bece4 | ||
|
|
8817ddf020 | ||
|
|
5d61956aff | ||
|
|
83ece031a7 | ||
|
|
566fad4108 | ||
|
|
a4aa111eaa | ||
|
|
f6ed184d49 | ||
|
|
c1820501a8 | ||
|
|
c7f50c5c28 | ||
|
|
371c96e1da | ||
|
|
11bd8b7eb1 | ||
|
|
cb418eb8d1 | ||
|
|
f811fb937e | ||
|
|
2a6ccb8009 | ||
|
|
bf49f99810 | ||
|
|
e027a5f97b | ||
|
|
d1ecb571d8 | ||
|
|
1e3937830f | ||
|
|
38b2fcb460 | ||
|
|
c7ed42226f | ||
|
|
d0dfad4c92 | ||
|
|
c66123b91f | ||
|
|
8d912e891e | ||
|
|
23fd6b8e60 | ||
|
|
0320e6e22a | ||
|
|
9e70d8fc84 | ||
|
|
27e28eccac | ||
|
|
0cb0313fc9 | ||
|
|
94a34c3703 | ||
|
|
c04c836f12 | ||
|
|
c5a9d2c52d | ||
|
|
c0050672ab | ||
|
|
705f94518f | ||
|
|
a096217526 | ||
|
|
795768667e | ||
|
|
5e9a8c6321 | ||
|
|
03df0565bd | ||
|
|
3ef6856b36 | ||
|
|
48e9f33263 | ||
|
|
9559dd47da | ||
|
|
ad7f8c303c | ||
|
|
edb7a0edda | ||
|
|
aedcbba211 | ||
|
|
ab63b51681 | ||
|
|
63f5d0c266 | ||
|
|
349ebd3846 | ||
|
|
ce496bc389 | ||
|
|
be1af82ee0 | ||
|
|
11bfb03add | ||
|
|
b89d4b102e | ||
|
|
0abd121653 | ||
|
|
eeb095b095 | ||
|
|
266a289520 | ||
|
|
3c1d6b575a | ||
|
|
bd08862074 | ||
|
|
70e0c0385a | ||
|
|
454cf1fb26 | ||
|
|
6d1029b3c7 | ||
|
|
f13f8fb38a | ||
|
|
2447064604 | ||
|
|
85a2589c0c | ||
|
|
2c821155ae | ||
|
|
c040be1ad2 | ||
|
|
2b91bb83fa | ||
|
|
5738f4545f | ||
|
|
01432a31d6 | ||
|
|
798a7fd52e | ||
|
|
cee121f414 | ||
|
|
a4fe871865 | ||
|
|
d85c80df95 | ||
|
|
95d01dec52 | ||
|
|
1506097faa | ||
|
|
dc541c2664 | ||
|
|
694da35f99 | ||
|
|
3c7697420c | ||
|
|
4a407dbe76 | ||
|
|
2479660028 | ||
|
|
612b922eeb | ||
|
|
ea2336b68e | ||
|
|
0c953a88c3 | ||
|
|
a551c69e9e | ||
|
|
7e7be15ab8 | ||
|
|
cb0242782a | ||
|
|
04655b1db5 | ||
|
|
7ce47c474a | ||
|
|
b6259d3e7e | ||
|
|
3e6bcc09b3 | ||
|
|
fbc34b649c | ||
|
|
4a0cb00450 | ||
|
|
0cf32d9b4d | ||
|
|
f6bf7dcd92 | ||
|
|
4f59d1561a | ||
|
|
86af9aa59d | ||
|
|
1cd4f79347 | ||
|
|
f928c7e156 | ||
|
|
0e6611b528 | ||
|
|
afc39106c3 | ||
|
|
06c8de381b | ||
|
|
ac0e1aeb19 | ||
|
|
1171a20262 | ||
|
|
8fa729a702 | ||
|
|
53eb54e656 | ||
|
|
3b9047be0f | ||
|
|
2562311606 | ||
|
|
98d0760898 | ||
|
|
4c943ddb0d | ||
|
|
61432ab65d | ||
|
|
0ccdc8d1ca | ||
|
|
8a3366b1eb | ||
|
|
c669cfbb23 | ||
|
|
45749f6017 | ||
|
|
bc6bb417ac | ||
|
|
5342ba6b10 | ||
|
|
a1dc0d1936 | ||
|
|
15f1759580 | ||
|
|
fb25be3ecd | ||
|
|
fbb8c6273b | ||
|
|
289014b32e | ||
|
|
9b91e3d5e2 | ||
|
|
d57e099e6e | ||
|
|
c5d30a9c63 | ||
|
|
8ee08a3be7 | ||
|
|
5af1554bbf | ||
|
|
e226a111f9 | ||
|
|
d5bc65ec0e | ||
|
|
106d74caa2 | ||
|
|
825784e0ec | ||
|
|
80c6c7f399 | ||
|
|
156563e832 | ||
|
|
b92260870a | ||
|
|
04f2b6e9ff | ||
|
|
a9862a1a6f | ||
|
|
2e2acd77f3 | ||
|
|
75e8999a88 | ||
|
|
7bf62a3078 | ||
|
|
0030ed214d | ||
|
|
f63f1f7b95 | ||
|
|
6daa3f4499 | ||
|
|
4e6151d011 | ||
|
|
18330b47ed | ||
|
|
864f1a9be9 | ||
|
|
1b6e24d94a | ||
|
|
617e702a94 | ||
|
|
6c0de23915 | ||
|
|
869d791f44 | ||
|
|
e5499711c3 | ||
|
|
3fbd215854 | ||
|
|
b7132e7fa7 | ||
|
|
7f201e3e6b | ||
|
|
cebbac8265 | ||
|
|
227a1f5aa3 | ||
|
|
07bdc10245 | ||
|
|
1b9b6f0cbf | ||
|
|
1f6ebb3dac | ||
|
|
3abb66500d | ||
|
|
b6b2e67180 | ||
|
|
db9d950fb4 | ||
|
|
bdd8b8d32f | ||
|
|
d8bc7ceaff | ||
|
|
6a0f7b0713 | ||
|
|
3ac8a89f9b | ||
|
|
fcec9c793c | ||
|
|
2140930793 | ||
|
|
2c16a7e0d7 | ||
|
|
325559e5d1 | ||
|
|
3ec926750a | ||
|
|
6d8e9c43c9 | ||
|
|
69f3f302d6 | ||
|
|
1282900140 | ||
|
|
768710b2d7 | ||
|
|
8750ad4ec2 | ||
|
|
409d995080 | ||
|
|
a151b72fc5 | ||
|
|
0626b7ec22 | ||
|
|
91205710ef | ||
|
|
5a4f352bb0 | ||
|
|
f62a14e976 | ||
|
|
5f3fb410f7 | ||
|
|
2d8dfde660 | ||
|
|
7fb885ce7d | ||
|
|
4aa3097cd1 | ||
|
|
0b87d9a39c | ||
|
|
92fdf0ce58 | ||
|
|
aa59f34aab | ||
|
|
05c149783f | ||
|
|
c3bc436e49 | ||
|
|
64328ee39c | ||
|
|
61667c1c23 | ||
|
|
3a9cdbab0d | ||
|
|
27af770068 | ||
|
|
71e45cdd25 | ||
|
|
f9adf8ad83 | ||
|
|
f886698dc3 | ||
|
|
7ab0a42b24 | ||
|
|
99e2a3db1c | ||
|
|
9ca6733f68 | ||
|
|
6d5ae1416a | ||
|
|
0707398a5b | ||
|
|
75e4e2089e | ||
|
|
484883f442 | ||
|
|
d488b49fc4 | ||
|
|
1168c6c52f | ||
|
|
f4e87cdbb3 | ||
|
|
24c64bfc7f | ||
|
|
ed5aeb7472 | ||
|
|
eb3f933290 | ||
|
|
832c43a509 | ||
|
|
31d3ae451c | ||
|
|
e413133602 | ||
|
|
06b8d36cf0 | ||
|
|
b75e42a56c | ||
|
|
66b2c01549 | ||
|
|
065eb13a9d | ||
|
|
15bb9aa137 | ||
|
|
07862c2e52 | ||
|
|
a8b6252c3c | ||
|
|
bbbf128b7c | ||
|
|
62b8398719 | ||
|
|
3ea9a7511e | ||
|
|
076d51fc35 | ||
|
|
210fa7f655 | ||
|
|
fbdb01d43c | ||
|
|
36e241cd8d | ||
|
|
c5b0068c9b | ||
|
|
ed7bab7f02 | ||
|
|
001e16e081 | ||
|
|
41670dc181 | ||
|
|
6c6900ede8 | ||
|
|
b450470142 | ||
|
|
d98fcf8d1f | ||
|
|
5b90b8a3ad | ||
|
|
e343e690f2 | ||
|
|
6f1f4ba98f | ||
|
|
7a34b4de73 | ||
|
|
b84831f148 | ||
|
|
d9ecafeb2a | ||
|
|
930de9aeb7 | ||
|
|
40a09d7412 | ||
|
|
ad9aabbb7e | ||
|
|
195bce0485 | ||
|
|
42b88f34f4 | ||
|
|
7bde7039bb | ||
|
|
4702a0af9d | ||
|
|
c818381ef1 | ||
|
|
ef4ecf7c8e | ||
|
|
fb65cea115 | ||
|
|
dcca79baef | ||
|
|
15df03ba09 | ||
|
|
5eb33e9d8a | ||
|
|
939de5d151 | ||
|
|
4c440b8552 | ||
|
|
1c73fd5454 | ||
|
|
9567ae4bfe | ||
|
|
853b454eae | ||
|
|
94b1fec91e | ||
|
|
a6a5c010fe | ||
|
|
52bcbaed08 | ||
|
|
74ac45730f | ||
|
|
0d0f04de5c | ||
|
|
e1be294147 | ||
|
|
45077d7106 | ||
|
|
e65ead3f20 | ||
|
|
5ed6d0fedb | ||
|
|
673138c5af | ||
|
|
0e145a1a57 | ||
|
|
a8bcfc62d0 | ||
|
|
394bb38913 | ||
|
|
80c5c94e7e | ||
|
|
03bfd2c842 | ||
|
|
bdb438972f | ||
|
|
f256206fd4 | ||
|
|
4d01929503 | ||
|
|
2a9b76246b | ||
|
|
21bcef5008 | ||
|
|
46e7fc61cc | ||
|
|
b9dee59900 | ||
|
|
2fd4c149fd | ||
|
|
d085768e8b | ||
|
|
6f9730c652 | ||
|
|
b79a0a9832 | ||
|
|
212061be50 | ||
|
|
c931341259 | ||
|
|
51dc03a730 | ||
|
|
31670da633 | ||
|
|
51a808c094 | ||
|
|
d756086b8e | ||
|
|
81d289b266 | ||
|
|
71c2551a2e | ||
|
|
ab2c8c7382 | ||
|
|
ba40fe4ff4 | ||
|
|
dbced5c1a4 | ||
|
|
3a1a0d6590 | ||
|
|
451d9aab96 | ||
|
|
a6949d522e | ||
|
|
eba372e2cc | ||
|
|
a3ac0d542d | ||
|
|
0709b0dc3b | ||
|
|
5bb710e921 | ||
|
|
a5c1e594b3 | ||
|
|
08f8f51ade | ||
|
|
3897950e22 | ||
|
|
e9db1ecd1f | ||
|
|
9346eba2f2 | ||
|
|
c5ba1d62e7 | ||
|
|
37996bcd15 | ||
|
|
d5729f9216 | ||
|
|
4cec11be9c | ||
|
|
b80c14639e | ||
|
|
0d3bd2052b | ||
|
|
353a8c5388 | ||
|
|
dac3037ee4 | ||
|
|
e3c81048af | ||
|
|
c752ca84df | ||
|
|
a5d50960a7 | ||
|
|
af0845b4a3 | ||
|
|
8d77b1ae16 | ||
|
|
c185529ad5 | ||
|
|
3a5def98e5 | ||
|
|
603c68bd69 | ||
|
|
2f984964da | ||
|
|
22aaf0471a | ||
|
|
1134d0d7d4 | ||
|
|
45ff3ee637 | ||
|
|
50ba6bfa1b | ||
|
|
cdcbb1d6da | ||
|
|
49bf22ace4 | ||
|
|
22fa95cf87 | ||
|
|
a76a889924 | ||
|
|
8f7c6a635f | ||
|
|
9ece6b1fbc | ||
|
|
9d7a45f459 | ||
|
|
0465ba24a2 | ||
|
|
b622c89967 | ||
|
|
7efb141f88 | ||
|
|
d093912f9d | ||
|
|
92e5c8d228 | ||
|
|
68995e6ed8 | ||
|
|
edac29fe51 | ||
|
|
e7f72a1ba8 | ||
|
|
d0519d6c25 | ||
|
|
2ce435f98d | ||
|
|
67fde3e941 | ||
|
|
1282c060fc | ||
|
|
52f1fc943d | ||
|
|
88ff19383b | ||
|
|
832312e095 | ||
|
|
f2ed01993d | ||
|
|
ccf869e671 | ||
|
|
48f579977e | ||
|
|
ab595659c2 | ||
|
|
0cd2b78425 | ||
|
|
c1e618fc3b | ||
|
|
934b9faf36 | ||
|
|
2836dcb71f | ||
|
|
2b874c5817 | ||
|
|
19b73d2ba7 | ||
|
|
548a17d94c | ||
|
|
de4005d470 | ||
|
|
46e9b9499c | ||
|
|
580b06ada5 | ||
|
|
1472e7b051 | ||
|
|
9817754f92 | ||
|
|
05f1959c83 | ||
|
|
80a692a751 | ||
|
|
a5ebb6d98c | ||
|
|
04c1e4b57d | ||
|
|
f8f9c06ed8 | ||
|
|
1f4b2607cc | ||
|
|
f0c518a134 | ||
|
|
c396518736 | ||
|
|
d6a45b511e | ||
|
|
f5c4c3e450 | ||
|
|
aecfb5772d | ||
|
|
3b7b936582 | ||
|
|
309bdf3d70 | ||
|
|
55f9203f60 | ||
|
|
df7ba37d57 | ||
|
|
96cb4e9a52 | ||
|
|
f070be8dd3 | ||
|
|
3408a0537f | ||
|
|
5669cf832f | ||
|
|
e2ac4b45d0 | ||
|
|
714daba393 | ||
|
|
1913e9512c | ||
|
|
ae72994cfb | ||
|
|
f9b2f3e87d | ||
|
|
e05bf87528 | ||
|
|
7609a5456e | ||
|
|
4809a2e0df | ||
|
|
e61c34b85f | ||
|
|
ba0dc60b09 | ||
|
|
f61d6320a3 | ||
|
|
3ef41f2b1d | ||
|
|
2e6f547bfc | ||
|
|
c66b25b4cc | ||
|
|
a66edbdda8 | ||
|
|
ef0d834db8 | ||
|
|
89daacac32 | ||
|
|
f4f15ee5e9 | ||
|
|
4c36ef9d24 | ||
|
|
886af692e2 | ||
|
|
c63949f384 | ||
|
|
9460e915cd | ||
|
|
34a324f551 | ||
|
|
996694aec4 | ||
|
|
e21060a259 | ||
|
|
951d342ed7 | ||
|
|
74b76704f8 | ||
|
|
6efc37eaf3 | ||
|
|
9cf4e68ac4 | ||
|
|
1c35c8b617 | ||
|
|
94dabe225b | ||
|
|
21b490f03a | ||
|
|
f8f509a296 | ||
|
|
062b771622 | ||
|
|
adff472011 | ||
|
|
9c043dbf9f | ||
|
|
59d28e7b99 | ||
|
|
8b5a133106 | ||
|
|
86a41dd6dd | ||
|
|
2d77b25e26 | ||
|
|
43526c2297 | ||
|
|
192762add8 | ||
|
|
0f64847351 | ||
|
|
88d2f26269 | ||
|
|
21aabb1215 | ||
|
|
e6ea51f8cb | ||
|
|
6d52e1709f | ||
|
|
aaf1fd0256 | ||
|
|
97ef539a6c | ||
|
|
b630aad44a | ||
|
|
2f6fb4b97c | ||
|
|
5c99114b02 | ||
|
|
318421df9d | ||
|
|
2f42dc6473 | ||
|
|
66bcdc3c00 | ||
|
|
8b042caf60 | ||
|
|
b3408f5ad4 | ||
|
|
5558bc1ef3 | ||
|
|
2426a731fc | ||
|
|
47e2b0991a | ||
|
|
ceec98ffc3 | ||
|
|
a24b977f72 | ||
|
|
4602ffba29 | ||
|
|
21d4ee8e2e | ||
|
|
8ec41a7128 | ||
|
|
8842625bb4 | ||
|
|
586be86b41 | ||
|
|
55190fd703 | ||
|
|
64f7978037 | ||
|
|
3f33751ff2 | ||
|
|
392f349da1 | ||
|
|
bbd83b9dba | ||
|
|
2e9b4779c9 | ||
|
|
8475f95b19 | ||
|
|
2c0669c865 | ||
|
|
937b551ae4 | ||
|
|
88a50d3779 | ||
|
|
57be6616ce | ||
|
|
7e073e6e26 | ||
|
|
10ff95af6a | ||
|
|
378d3e3889 | ||
|
|
88f1353670 | ||
|
|
c229aa8cfe | ||
|
|
96549a5421 | ||
|
|
a99c6145a7 | ||
|
|
c85ab8dcb6 | ||
|
|
db172b341e | ||
|
|
11dbba422f | ||
|
|
a1e60045d9 | ||
|
|
b36a9d5af3 | ||
|
|
fb5ff49008 | ||
|
|
1b857f743e | ||
|
|
c2df14cfa0 | ||
|
|
44ff7b0f5f | ||
|
|
5fdbd7428b | ||
|
|
bc9aaebd04 | ||
|
|
d90a0ce910 | ||
|
|
9c19043fed | ||
|
|
beb39ada0c | ||
|
|
47f3eb28a6 | ||
|
|
28105af761 | ||
|
|
e0ac87e702 | ||
|
|
b9ea05f97a | ||
|
|
e348163689 | ||
|
|
90375531bc | ||
|
|
ca85f2d725 | ||
|
|
eaadb75d39 | ||
|
|
f6793df94a | ||
|
|
90e716b932 | ||
|
|
db2853c8ae | ||
|
|
120527cb41 | ||
|
|
058f33cd7b | ||
|
|
88582726b7 | ||
|
|
fd5f5e3204 | ||
|
|
47d7a71ad0 | ||
|
|
88a6be16af | ||
|
|
23cacbf81a | ||
|
|
db304513b1 | ||
|
|
8d1b4bf78f | ||
|
|
1cff37af99 | ||
|
|
ecc7f2d780 | ||
|
|
dbb6610d50 | ||
|
|
ae135ac181 | ||
|
|
df1d68b626 | ||
|
|
d35d12f671 | ||
|
|
cb31179274 | ||
|
|
c9277c3c72 | ||
|
|
5896a05ca6 | ||
|
|
5cf0aadd84 | ||
|
|
a18bac7380 | ||
|
|
9a1c7953e7 | ||
|
|
53c526f48c | ||
|
|
49bfd05d7c | ||
|
|
62ea2c691e | ||
|
|
7de600cc64 | ||
|
|
0c703276b1 | ||
|
|
11ac8da14e | ||
|
|
f736b6c299 | ||
|
|
6f886fe167 | ||
|
|
986baed290 | ||
|
|
84c5f76b76 | ||
|
|
39c6f41f0e | ||
|
|
bdb01e6166 | ||
|
|
35ddcdd266 | ||
|
|
7e8e1215ad | ||
|
|
83abd7ef59 | ||
|
|
26fa6c97bf | ||
|
|
708c6bdeb8 | ||
|
|
c73adf02bc | ||
|
|
c36a468561 | ||
|
|
83794c433f | ||
|
|
eb82346ce7 | ||
|
|
1a6ec0d768 | ||
|
|
446f8f93c6 | ||
|
|
445a801d5d | ||
|
|
579a135087 | ||
|
|
cd1b251cef | ||
|
|
ed23194abd | ||
|
|
2ac0c0a957 | ||
|
|
477b161ba2 | ||
|
|
cb1353c898 | ||
|
|
47dc137de0 | ||
|
|
03c6b0c688 | ||
|
|
ffc5e2f446 | ||
|
|
4ebb61b580 | ||
|
|
4ae25e57a3 | ||
|
|
7bd4f3874e | ||
|
|
2b7080294d | ||
|
|
3b51565a13 | ||
|
|
7400b888df | ||
|
|
3f15a7251b | ||
|
|
a9356fd377 | ||
|
|
f3eb9f79a1 | ||
|
|
93026bcae0 | ||
|
|
6082c00b1d | ||
|
|
e6e181e6bc | ||
|
|
502452f58e | ||
|
|
1d0cceb2d3 | ||
|
|
b75ae0f7b1 | ||
|
|
5f0217cdb0 | ||
|
|
7b40aa89b4 | ||
|
|
e817a7b6c6 | ||
|
|
b53c2e1a26 | ||
|
|
383e2ed46f | ||
|
|
258b084a62 | ||
|
|
c5593bde08 | ||
|
|
2cd4580154 | ||
|
|
2442dc09b0 | ||
|
|
d482ca5d68 | ||
|
|
3616b697ec | ||
|
|
a19dfe608d | ||
|
|
0c1e9b6678 | ||
|
|
391313cd39 | ||
|
|
8fb6ded506 | ||
|
|
eb7b2f4683 | ||
|
|
520918348e | ||
|
|
e7b3910f3d | ||
|
|
7515ed6146 | ||
|
|
901adb38ae | ||
|
|
522378a507 | ||
|
|
1bb2fc912b | ||
|
|
423d112dfc | ||
|
|
63fde03552 | ||
|
|
d1d27b5367 | ||
|
|
0e302baa43 | ||
|
|
a7c2d54e53 | ||
|
|
54743eda16 | ||
|
|
660cf26e49 | ||
|
|
e4875f26ff | ||
|
|
7f1f22146d | ||
|
|
0103c689f7 | ||
|
|
f4a31251cb | ||
|
|
b18ded0e2d | ||
|
|
c327116e7f | ||
|
|
ef071ac7ac | ||
|
|
56990f2a29 | ||
|
|
4475a82776 | ||
|
|
560a30662b | ||
|
|
a066bbe32f | ||
|
|
e252b4b674 | ||
|
|
3283ffb4e1 | ||
|
|
a840db3e79 | ||
|
|
dc19d7c72c | ||
|
|
a95b1b4881 | ||
|
|
4d0fc16fe3 | ||
|
|
b13c3be92f | ||
|
|
a3a222c1f1 | ||
|
|
1d7cfcdd99 | ||
|
|
2c05e721fd | ||
|
|
22acf1ec25 | ||
|
|
9c8deb1470 | ||
|
|
9b9cb5c076 | ||
|
|
0b6a5b7630 | ||
|
|
fac025367b | ||
|
|
e5246f3993 | ||
|
|
89370a1f74 | ||
|
|
51a564eb9c | ||
|
|
b50b6ccb8b | ||
|
|
7a8e2c67b0 | ||
|
|
1b4a3475e8 | ||
|
|
3d2d35f5b6 | ||
|
|
489d8110c9 | ||
|
|
fc5bf99715 | ||
|
|
999bdc9498 | ||
|
|
06e3528bc8 | ||
|
|
b06a88b09f | ||
|
|
51aab3eacc | ||
|
|
545807755d | ||
|
|
b7ae55a07b | ||
|
|
a980b3d132 | ||
|
|
58047e8b82 | ||
|
|
9abeaa74c6 | ||
|
|
c512e48c52 | ||
|
|
8924437fc5 | ||
|
|
1d8e242b3c | ||
|
|
cf33f7c281 | ||
|
|
56ac6f66bf | ||
|
|
5ac649b21a | ||
|
|
4a567c9f5a | ||
|
|
479fddfeec | ||
|
|
cc6f9e9da4 | ||
|
|
de9df12ea9 | ||
|
|
8145829619 | ||
|
|
3bd0cb9a15 | ||
|
|
412d6fe42c | ||
|
|
12362cac61 | ||
|
|
001d26d73c | ||
|
|
75191982da | ||
|
|
8093a5f419 | ||
|
|
3d53847f30 | ||
|
|
bb944f190e | ||
|
|
7d84083f55 | ||
|
|
b591ccc40d | ||
|
|
46b95ed639 | ||
|
|
c050cf305c | ||
|
|
d5bf8966d9 | ||
|
|
49bc27e24f | ||
|
|
6240a5d7a4 | ||
|
|
af67b72492 | ||
|
|
d2145368fa | ||
|
|
4010900e47 | ||
|
|
2d1af70e87 | ||
|
|
76d4de225a | ||
|
|
f5c3b00c42 | ||
|
|
f9fddfc43d | ||
|
|
12b912140b | ||
|
|
56f4c3d6ae | ||
|
|
f560d187c1 | ||
|
|
d4d7446789 | ||
|
|
663da772e0 | ||
|
|
b51226f76c | ||
|
|
297ea529a2 | ||
|
|
016d176cbd | ||
|
|
83e582b7ab | ||
|
|
fbeb45d1da | ||
|
|
be24cdb917 | ||
|
|
685d14c4d5 | ||
|
|
c50cd4cc3e | ||
|
|
e240160d39 | ||
|
|
dae8c3599b | ||
|
|
7d8c0268ea | ||
|
|
fc2df6cfd6 | ||
|
|
5b2ccdbda7 | ||
|
|
a0d724c186 | ||
|
|
aec2baa7e7 | ||
|
|
67bbbd8671 | ||
|
|
3e1dbd4c39 | ||
|
|
df4b07f9f2 | ||
|
|
bd4c2da37c | ||
|
|
1c06e576ae | ||
|
|
5d3629173f | ||
|
|
5f25f40db9 | ||
|
|
a4efefbf5e | ||
|
|
56e77d3106 | ||
|
|
2a9e16bb8e | ||
|
|
c512485514 | ||
|
|
a0fb880ebc | ||
|
|
e52eafa7bb | ||
|
|
9f8ea28db4 | ||
|
|
1c1eaa6296 | ||
|
|
b32bfedde9 | ||
|
|
f7d36a670c | ||
|
|
37461c2a5a | ||
|
|
5a17825b47 | ||
|
|
761c25fa6d | ||
|
|
c40df65d13 | ||
|
|
79860e7b15 | ||
|
|
ca68a38f2c | ||
|
|
b075cc5e6f | ||
|
|
3e3c51ae53 | ||
|
|
28d7eddcb4 | ||
|
|
509327b4c7 | ||
|
|
ee74130806 | ||
|
|
0edbbf9fe0 | ||
|
|
7ec658a3f8 | ||
|
|
2b4da1f88e | ||
|
|
84608256ea | ||
|
|
0f8a034e8b | ||
|
|
0631323bf5 | ||
|
|
825cc006e5 | ||
|
|
f1e01c7166 | ||
|
|
15e3173177 | ||
|
|
34bf2f9a0f | ||
|
|
71a78cbf01 | ||
|
|
03fc6c3f97 | ||
|
|
e6c095754f | ||
|
|
d045d39f88 | ||
|
|
63bf719067 | ||
|
|
34168bd223 | ||
|
|
4f3e3d9285 | ||
|
|
c41f1c3d22 | ||
|
|
7d8086e138 | ||
|
|
0727797254 | ||
|
|
7a8c3a42cd | ||
|
|
84f5f20edb | ||
|
|
b80b5a94f9 | ||
|
|
a43cc23d67 | ||
|
|
9e1f607bd9 | ||
|
|
914861503b | ||
|
|
8e996ae571 | ||
|
|
5d351ec8c0 | ||
|
|
33db0203dd | ||
|
|
7a1bef7daf | ||
|
|
2bbda38488 | ||
|
|
e3e59c35fc | ||
|
|
dc61fae12a | ||
|
|
dada7fe728 | ||
|
|
bff74b1d7d | ||
|
|
224fb5c6a2 | ||
|
|
05e1cd8478 | ||
|
|
a2297a5fa6 | ||
|
|
94f5c37884 | ||
|
|
ed334fd790 | ||
|
|
efb2069500 | ||
|
|
924526c1e6 | ||
|
|
89995fe7ca | ||
|
|
929914da5d | ||
|
|
31669cbad6 | ||
|
|
a1e73331b8 | ||
|
|
31da88ad89 | ||
|
|
93cdee3bab | ||
|
|
ed7d8e7c3b | ||
|
|
050d9ff04d | ||
|
|
512076898d | ||
|
|
a628e427de | ||
|
|
05f1887559 | ||
|
|
266859edf3 | ||
|
|
79ea49cafd | ||
|
|
3cb67f2806 | ||
|
|
fa4de08934 | ||
|
|
70c8f8733a | ||
|
|
97dc61e65f | ||
|
|
48aebec01f | ||
|
|
58078459f8 | ||
|
|
fb9b19b95e | ||
|
|
7af6cf450b | ||
|
|
ca12cf5802 | ||
|
|
d7737f6c88 | ||
|
|
b5fdd3b8d8 | ||
|
|
5b95f7e565 | ||
|
|
be3f3da532 | ||
|
|
f13e52fffb | ||
|
|
ab66cb5d26 | ||
|
|
878b762537 | ||
|
|
c5e144f327 | ||
|
|
92ac99e878 | ||
|
|
70dabf11db | ||
|
|
0f83d645b6 | ||
|
|
794573969c | ||
|
|
303abf8a91 | ||
|
|
43303ba4c1 | ||
|
|
31ccb796ac | ||
|
|
3395ccb943 | ||
|
|
c630b92772 | ||
|
|
903d75e6d3 | ||
|
|
0be6d9f048 | ||
|
|
a574591481 | ||
|
|
435d480cf1 | ||
|
|
3969eb9382 | ||
|
|
45af02590e | ||
|
|
709214a414 | ||
|
|
6dda0c2b97 | ||
|
|
20578e0a43 | ||
|
|
5a1e8484a9 | ||
|
|
1093b7c3d3 | ||
|
|
7ec99c2469 | ||
|
|
3543155953 | ||
|
|
e2df7dc96c | ||
|
|
cd5f3e9740 | ||
|
|
ae70ef0fa2 | ||
|
|
f1a17e3cf5 | ||
|
|
22a2496360 | ||
|
|
7a7f3466f3 | ||
|
|
73f9d45fdc | ||
|
|
3cba961dad | ||
|
|
6710c13cc2 | ||
|
|
f12120b055 | ||
|
|
693f30477e | ||
|
|
92709110ef | ||
|
|
d81b4999dc | ||
|
|
f088a9953f | ||
|
|
a069df7a36 | ||
|
|
7f26b04ea1 | ||
|
|
f81cca67eb | ||
|
|
aca6d24e96 | ||
|
|
25bbde2be2 | ||
|
|
c7ad4aa460 | ||
|
|
9c9aa68c23 | ||
|
|
6cc6cb5ffd | ||
|
|
7ad2a68052 | ||
|
|
8991ed8270 | ||
|
|
45d1139ab0 | ||
|
|
26052a2963 | ||
|
|
eeb9270882 | ||
|
|
8fc2d22b28 | ||
|
|
d09e2fbcec | ||
|
|
2797faae6f | ||
|
|
a1906347b0 | ||
|
|
8924aa736e | ||
|
|
f93939ef20 | ||
|
|
700c4acf75 | ||
|
|
f18a6fe1eb | ||
|
|
b07a6d10f1 | ||
|
|
ac6c8a5bd3 | ||
|
|
b3d95d20dc | ||
|
|
28c43ac2bf | ||
|
|
fdfadfefbc | ||
|
|
e57cb8bf7e | ||
|
|
ec78fc7e7c | ||
|
|
cf082cca04 | ||
|
|
1e877a0f96 | ||
|
|
3e2e4ace48 | ||
|
|
0c5962c538 | ||
|
|
98cc17df24 | ||
|
|
2cad586ccc | ||
|
|
da3ae79c37 | ||
|
|
38f05dabe2 | ||
|
|
d0689abd35 | ||
|
|
50b7fcb90a | ||
|
|
7e01c833be | ||
|
|
308c8ba1a5 | ||
|
|
477d1b2569 | ||
|
|
5c7a14cfe0 | ||
|
|
fb9019e0c8 | ||
|
|
b3750bed2d | ||
|
|
fa12649642 | ||
|
|
aacc9c86b9 | ||
|
|
8b2d85c823 | ||
|
|
66709e6f22 | ||
|
|
f34edc3116 | ||
|
|
9cd80a2c6b | ||
|
|
2de22f9356 | ||
|
|
2e237cc449 | ||
|
|
bb734706fc | ||
|
|
09ede0221a | ||
|
|
f859bffa74 | ||
|
|
bca90245c8 | ||
|
|
fe3520c685 | ||
|
|
31f812cf94 | ||
|
|
57a8255654 | ||
|
|
a71ed7b5b7 | ||
|
|
3ba6ffe5f4 | ||
|
|
2571a0654f |
@@ -3,7 +3,7 @@
|
||||
"dockerFile": "Dockerfile",
|
||||
"appPort": 3000,
|
||||
"extensions": [
|
||||
"editorconfig.editorconfig",
|
||||
"ms-vscode.vscode-typescript-tslint-plugin"
|
||||
"dbaeumer.vscode-eslint",
|
||||
"editorconfig.editorconfig"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
node_modules/
|
||||
*.swp
|
||||
.lock-wscript
|
||||
lib/
|
||||
Makefile.gyp
|
||||
*.Makefile
|
||||
*.target.gyp.mk
|
||||
*.node
|
||||
example/*.log
|
||||
docs/
|
||||
npm-debug.log
|
||||
/.idea/
|
||||
.env
|
||||
build/
|
||||
.vscode/
|
||||
.git/
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": [
|
||||
"src/browser/tsconfig.json",
|
||||
"src/common/tsconfig.json",
|
||||
"test/api/tsconfig.json",
|
||||
"test/benchmark/tsconfig.json",
|
||||
"addons/xterm-addon-attach/src/tsconfig.json",
|
||||
"addons/xterm-addon-fit/src/tsconfig.json",
|
||||
"addons/xterm-addon-search/src/tsconfig.json",
|
||||
"addons/xterm-addon-unicode11/src/tsconfig.json",
|
||||
"addons/xterm-addon-web-links/src/tsconfig.json",
|
||||
"addons/xterm-addon-webgl/src/tsconfig.json",
|
||||
"addons/xterm-addon-serialize/src/tsconfig.json",
|
||||
"addons/xterm-addon-serialize/benchmark/tsconfig.json",
|
||||
"addons/xterm-addon-ligatures/src/tsconfig.json"
|
||||
],
|
||||
"sourceType": "module"
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"**/typings/*.d.ts",
|
||||
"**/node_modules",
|
||||
"**/*.js"
|
||||
],
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"no-extra-semi": "error",
|
||||
"@typescript-eslint/array-type": [
|
||||
"warn",
|
||||
{
|
||||
"default": "array-simple",
|
||||
"readonly": "generic"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/consistent-type-definitions": "warn",
|
||||
"@typescript-eslint/explicit-function-return-type": [
|
||||
"warn",
|
||||
{
|
||||
"allowExpressions": true
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/explicit-member-accessibility": [
|
||||
"warn",
|
||||
{
|
||||
"accessibility": "explicit",
|
||||
"overrides": {
|
||||
"constructors": "off"
|
||||
}
|
||||
}
|
||||
],
|
||||
"@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": "default", "format": ["camelCase"] },
|
||||
// variableLike
|
||||
{ "selector": "variable", "format": ["camelCase", "UPPER_CASE"] },
|
||||
{ "selector": "variable", "filter": "^I.+Service$", "format": ["PascalCase"], "prefix": ["I"] },
|
||||
// memberLike
|
||||
{ "selector": "memberLike", "modifiers": ["private"], "format": ["camelCase"], "leadingUnderscore": "require" },
|
||||
{ "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": "method", "modifiers": ["public"], "format": ["camelCase", "UPPER_CASE"] },
|
||||
// typeLike
|
||||
{ "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"
|
||||
],
|
||||
"keyword-spacing": "warn",
|
||||
"new-parens": "warn",
|
||||
"no-duplicate-imports": "warn",
|
||||
"no-else-return": [
|
||||
"warn",
|
||||
{
|
||||
"allowElseIf": false
|
||||
}
|
||||
],
|
||||
"no-eval": "warn",
|
||||
"no-irregular-whitespace": "warn",
|
||||
"no-restricted-imports": [
|
||||
"warn",
|
||||
{
|
||||
"patterns": [
|
||||
".*\\/out\\/.*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"no-trailing-spaces": "warn",
|
||||
"no-unsafe-finally": "warn",
|
||||
"no-var": "warn",
|
||||
"one-var": [
|
||||
"warn",
|
||||
"never"
|
||||
],
|
||||
"prefer-const": "warn",
|
||||
"spaced-comment": [
|
||||
"warn",
|
||||
"always",
|
||||
{
|
||||
"markers": ["/"],
|
||||
"exceptions": ["-"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,6 @@
|
||||
*.test.js
|
||||
*.test.js.map
|
||||
lib/test/
|
||||
tslint.json
|
||||
webpack.config.js
|
||||
|
||||
docs/
|
||||
|
||||
Vendored
+2
-2
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"typescript.preferences.quoteStyle": "single",
|
||||
"typescript.preferences.importModuleSpecifier": "non-relative"
|
||||
"typescript.preferences.importModuleSpecifier": "non-relative",
|
||||
"typescript.preferences.quoteStyle": "single"
|
||||
}
|
||||
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
FROM node:8
|
||||
MAINTAINER Paris Kasidiaris <paris@sourcelair.com>
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Set an entrypoint, to automatically install node modules
|
||||
ENTRYPOINT ["/bin/bash", "-c", "if [[ ! -d node_modules ]]; then npm install; fi; exec \"${@:0}\";"]
|
||||
CMD ["npm", "run", "start"]
|
||||
|
||||
# First, install dependencies to improve layer caching
|
||||
COPY package.json /usr/src/app/
|
||||
RUN npm install
|
||||
|
||||
# Add the code
|
||||
COPY . /usr/src/app
|
||||
|
||||
# Run the tests and build, to make sure everything is working nicely
|
||||
RUN npm run build && npm run test
|
||||
@@ -156,7 +156,18 @@ Xterm.js is used in several world-class applications to provide great terminal e
|
||||
- [**tty-share**](https://tty-share.com): Extremely simple terminal sharing over the Internet.
|
||||
- [**Ten Hands**](https://github.com/saisandeepvaddi/ten-hands): One place to run your command-line tasks.
|
||||
- [**WebAssembly.sh**](https://webassembly.sh): A WebAssembly WASI browser terminal
|
||||
|
||||
- [**Gus**](https://gus.jp): A shared coding pad where you can run Python with xterm.js
|
||||
- [**Linode**](https://linode.com): Linode uses xterm.js to provide users a web console for their Linode instances.
|
||||
- [**FluffOS**](https://www.fluffos.info): Active maintained LPMUD driver with websocket support.
|
||||
- [**x-terminal**](https://atom.io/packages/x-terminal): Atom plugin for providing terminals inside your Atom workspace.
|
||||
- [**CoCalc**](https://cocalc.com/): Lots of free software pre-installed, to chat, collaborate, develop, program, publish, research, share, teach, in C++, HTML, Julia, Jupyter, LaTeX, Markdown, Python, R, SageMath, Scala, ...
|
||||
- [**Dank Domain**](https://www.DDgame.us/): Open source multiuser medieval game supporting old & new terminal emulation.
|
||||
- [**DockerStacks**](https://docker-stacks.com/): Local LAMP/LEMP development studio
|
||||
- [**Codecademy**](https://codecademy.com/): Uses xterm.js in its courses on Bash.
|
||||
- [**Laravel Ssh Web Client**](https://github.com/roke22/Laravel-ssh-client): Laravel server inventory with ssh web client to connect at server using xterm.js
|
||||
- [**Repl.it**](https://repl.it): Collaborative browser based IDE with support for 50+ different languages.
|
||||
- [**TeleType**](https://github.com/akshaykmr/TeleType): cli tool that allows you to share your terminal online conveniently. Show off mad cli-fu, help a colleague, teach, or troubleshoot.
|
||||
- [**Intervue**](https://www.intervue.io): Pair programming for interviews. Multiple programming languages supported, with results displayed by xterm.js.
|
||||
[And much more...](https://github.com/xtermjs/xterm.js/network/dependents)
|
||||
|
||||
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 in our list. Note: Please add any new contributions to the end of the list only.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm-addon-attach",
|
||||
"version": "0.4.0",
|
||||
"version": "0.6.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
|
||||
@@ -3,77 +3,51 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import * as puppeteer from 'puppeteer';
|
||||
import { assert } from 'chai';
|
||||
import { ITerminalOptions } from 'xterm';
|
||||
import WebSocket = require('ws');
|
||||
import { openTerminal, pollFor, getBrowserType } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from 'playwright-core';
|
||||
|
||||
const APP = 'http://127.0.0.1:3000/test';
|
||||
|
||||
let browser: puppeteer.Browser;
|
||||
let page: puppeteer.Page;
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
|
||||
describe('AttachAddon', () => {
|
||||
before(async function(): Promise<any> {
|
||||
this.timeout(20000);
|
||||
browser = await puppeteer.launch({
|
||||
headless: process.argv.indexOf('--headless') !== -1,
|
||||
args: [`--window-size=${width},${height}`, `--no-sandbox`]
|
||||
const browserType = getBrowserType();
|
||||
browser = await browserType.launch({ dumpio: true,
|
||||
headless: process.argv.indexOf('--headless') !== -1
|
||||
});
|
||||
page = (await browser.pages())[0];
|
||||
await page.setViewport({ width, height });
|
||||
page = await (await browser.newContext()).newPage();
|
||||
await page.setViewportSize({ width, height });
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
beforeEach(async function(): Promise<any> {
|
||||
this.timeout(20000);
|
||||
await page.goto(APP);
|
||||
});
|
||||
beforeEach(async () => await page.goto(APP));
|
||||
|
||||
it('string', async function(): Promise<any> {
|
||||
this.timeout(20000);
|
||||
await openTerminal({ rendererType: 'dom' });
|
||||
await openTerminal(page, { rendererType: 'dom' });
|
||||
const port = 8080;
|
||||
const server = new WebSocket.Server({ port });
|
||||
server.on('connection', socket => socket.send('foo'));
|
||||
await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`);
|
||||
await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo');
|
||||
await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo');
|
||||
server.close();
|
||||
});
|
||||
|
||||
it('utf8', async function(): Promise<any> {
|
||||
this.timeout(20000);
|
||||
await openTerminal({ rendererType: 'dom' });
|
||||
await openTerminal(page, { rendererType: 'dom' });
|
||||
const port = 8080;
|
||||
const server = new WebSocket.Server({ port });
|
||||
const data = new Uint8Array([102, 111, 111]);
|
||||
server.on('connection', socket => socket.send(data));
|
||||
await page.evaluate(`window.term.loadAddon(new window.AttachAddon(new WebSocket('ws://localhost:${port}')))`);
|
||||
await pollFor(page, `window.term.buffer.getLine(0).translateToString(true)`, 'foo');
|
||||
await pollFor(page, `window.term.buffer.active.getLine(0).translateToString(true)`, 'foo');
|
||||
server.close();
|
||||
});
|
||||
});
|
||||
|
||||
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
|
||||
await page.evaluate(`window.term = new Terminal(${JSON.stringify(options)})`);
|
||||
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
|
||||
if (options.rendererType === 'dom') {
|
||||
await page.waitForSelector('.xterm-rows');
|
||||
} else {
|
||||
await page.waitForSelector('.xterm-text-layer');
|
||||
}
|
||||
}
|
||||
|
||||
async function pollFor(page: puppeteer.Page, fn: string, val: any): Promise<void> {
|
||||
const result = await page.evaluate(fn);
|
||||
if (result !== val) {
|
||||
return new Promise<void>(r => {
|
||||
setTimeout(() => r(pollFor(page, fn, val)), 10);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2015",
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2015"
|
||||
@@ -10,7 +10,11 @@
|
||||
"outDir": "../out",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"strict": true
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../out-test/api/TestUtils"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm-addon-fit",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
|
||||
@@ -3,28 +3,27 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import * as puppeteer from 'puppeteer';
|
||||
import { assert } from 'chai';
|
||||
import { ITerminalOptions } from 'xterm';
|
||||
import { openTerminal, getBrowserType } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from 'playwright-core';
|
||||
|
||||
const APP = 'http://127.0.0.1:3000/test';
|
||||
|
||||
let browser: puppeteer.Browser;
|
||||
let page: puppeteer.Page;
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
const width = 1024;
|
||||
const height = 768;
|
||||
|
||||
describe('FitAddon', () => {
|
||||
before(async function(): Promise<any> {
|
||||
this.timeout(20000);
|
||||
browser = await puppeteer.launch({
|
||||
headless: process.argv.indexOf('--headless') !== -1,
|
||||
args: [`--window-size=${width},${height}`, `--no-sandbox`]
|
||||
const browserType = getBrowserType();
|
||||
browser = await browserType.launch({ dumpio: true,
|
||||
headless: process.argv.indexOf('--headless') !== -1
|
||||
});
|
||||
page = (await browser.pages())[0];
|
||||
await page.setViewport({ width, height });
|
||||
page = await (await browser.newContext()).newPage();
|
||||
await page.setViewportSize({ width, height });
|
||||
await page.goto(APP);
|
||||
await openTerminal();
|
||||
await openTerminal(page);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
@@ -43,18 +42,18 @@ describe('FitAddon', () => {
|
||||
|
||||
it('default', async function(): Promise<any> {
|
||||
await loadFit();
|
||||
assert.deepEqual(await page.evaluate(`window.fit.proposeDimensions()`), {
|
||||
cols: 87,
|
||||
rows: 26
|
||||
});
|
||||
const dimensions: {cols: number, rows: number} = await page.evaluate(`window.fit.proposeDimensions()`);
|
||||
assert.equal(dimensions.cols, 87);
|
||||
assert.isAbove(dimensions.rows, 24);
|
||||
assert.isBelow(dimensions.rows, 29);
|
||||
});
|
||||
|
||||
it('width', async function(): Promise<any> {
|
||||
await loadFit(1008);
|
||||
assert.deepEqual(await page.evaluate(`window.fit.proposeDimensions()`), {
|
||||
cols: 110,
|
||||
rows: 26
|
||||
});
|
||||
const dimensions: {cols: number, rows: number} = await page.evaluate(`window.fit.proposeDimensions()`);
|
||||
assert.equal(dimensions.cols, 110);
|
||||
assert.isAbove(dimensions.rows, 24);
|
||||
assert.isBelow(dimensions.rows, 29);
|
||||
});
|
||||
|
||||
it('small', async function(): Promise<any> {
|
||||
@@ -74,15 +73,21 @@ describe('FitAddon', () => {
|
||||
it('default', async function(): Promise<any> {
|
||||
await loadFit();
|
||||
await page.evaluate(`window.fit.fit()`);
|
||||
assert.equal(await page.evaluate(`window.term.cols`), 87);
|
||||
assert.equal(await page.evaluate(`window.term.rows`), 26);
|
||||
const cols: number = await page.evaluate(`window.term.cols`);
|
||||
const rows: number = await page.evaluate(`window.term.rows`);
|
||||
assert.equal(cols, 87);
|
||||
assert.isAbove(rows, 24);
|
||||
assert.isBelow(rows, 29);
|
||||
});
|
||||
|
||||
it('width', async function(): Promise<any> {
|
||||
await loadFit(1008);
|
||||
await page.evaluate(`window.fit.fit()`);
|
||||
assert.equal(await page.evaluate(`window.term.cols`), 110);
|
||||
assert.equal(await page.evaluate(`window.term.rows`), 26);
|
||||
const cols: number = await page.evaluate(`window.term.cols`);
|
||||
const rows: number = await page.evaluate(`window.term.rows`);
|
||||
assert.equal(cols, 110);
|
||||
assert.isAbove(rows, 24);
|
||||
assert.isBelow(rows, 29);
|
||||
});
|
||||
|
||||
it('small', async function(): Promise<any> {
|
||||
@@ -106,13 +111,3 @@ async function loadFit(width: number = 800, height: number = 450): Promise<void>
|
||||
async function unloadFit(): Promise<void> {
|
||||
await page.evaluate(`window.fit.dispose();`);
|
||||
}
|
||||
|
||||
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
|
||||
await page.evaluate(`window.term = new Terminal(${JSON.stringify(options)})`);
|
||||
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
|
||||
if (options.rendererType === 'dom') {
|
||||
await page.waitForSelector('.xterm-rows');
|
||||
} else {
|
||||
await page.waitForSelector('.xterm-text-layer');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ export class FitAddon implements ITerminalAddon {
|
||||
}
|
||||
|
||||
// TODO: Remove reliance on private API
|
||||
const core = (<any>this._terminal)._core;
|
||||
const core = (this._terminal as any)._core;
|
||||
|
||||
// Force a full render
|
||||
if (this._terminal.rows !== dims.rows || this._terminal.cols !== dims.cols) {
|
||||
@@ -57,7 +57,7 @@ export class FitAddon implements ITerminalAddon {
|
||||
}
|
||||
|
||||
// TODO: Remove reliance on private API
|
||||
const core = (<any>this._terminal)._core;
|
||||
const core = (this._terminal as any)._core;
|
||||
|
||||
const parentElementStyle = window.getComputedStyle(this._terminal.element.parentElement);
|
||||
const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
|
||||
|
||||
@@ -10,7 +10,11 @@
|
||||
"outDir": "../out",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"strict": true
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../out-test/api/TestUtils"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
node_modules/
|
||||
.nyc_output/
|
||||
coverage/
|
||||
|
||||
lib/
|
||||
fonts/
|
||||
|
||||
.env
|
||||
.vscode/
|
||||
*.swp
|
||||
*.tgz
|
||||
npm-debug.log*
|
||||
yarn-error.log*
|
||||
@@ -0,0 +1,48 @@
|
||||
# Blacklist - exclude everything except npm defaults such as LICENSE, etc
|
||||
*
|
||||
!*/
|
||||
|
||||
# Whitelist - entries to be included must be negated with "!"
|
||||
!*.js
|
||||
!*.json
|
||||
|
||||
# Whitelist - lib/
|
||||
!lib/**/*.d.ts
|
||||
|
||||
!lib/**/*.js
|
||||
!lib/**/*.js.map
|
||||
|
||||
# Whitelist - out/
|
||||
!out/**/*.d.ts
|
||||
|
||||
!out/**/*.js
|
||||
!out/**/*.js.map
|
||||
|
||||
# Whitelist - src/
|
||||
!src/**/*.ts
|
||||
!src/**/*.d.ts
|
||||
|
||||
!src/**/*.js
|
||||
!src/**/*.js.map
|
||||
|
||||
# Whitelist - typings/
|
||||
!typings/**/*.d.ts
|
||||
|
||||
# Blacklist - (normal behavior) these will override any whitelist
|
||||
*.test.ts
|
||||
*.test.d.ts
|
||||
*.test.js
|
||||
*.test.js.map
|
||||
|
||||
docs/
|
||||
/.idea/
|
||||
.vscode/
|
||||
coverage/
|
||||
.nyc_output/
|
||||
fonts/
|
||||
|
||||
**/*.api.js
|
||||
**/*.api.ts
|
||||
tsconfig.json
|
||||
.yarnrc
|
||||
webpack.config.js
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018
|
||||
|
||||
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,53 @@
|
||||
## xterm-addon-ligatures
|
||||
|
||||
Add support for programming ligatures to [xterm.js] when running in environments with access to [Node.js] APIs (such as [Electron]).
|
||||
|
||||
### Requirements
|
||||
|
||||
* [Node.js] 8.x or higher (present in [Electron] 1.8.3 or higher)
|
||||
* [xterm.js] 4.0.0 or higher using the default canvas renderer
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
npm install --save xterm-addon-ligatures
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { Terminal } from 'xterm';
|
||||
import { LigaturesAddon } from 'xterm-addon-ligatures';
|
||||
|
||||
const terminal = new Terminal();
|
||||
const ligaturesAddon = new LigaturesAddon();
|
||||
terminal.open(containerElement);
|
||||
terminal.loadAddon(ligaturesAddon);
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
In a browser environment, font ligature information is read directly by the web browser and used to render text correctly without any intervention from the developer. As of version 3, xterm.js uses the canvas to render characters individually, resulting in a significant performance boost. However, this means that it can no longer lean on the browser to determine when to draw font ligatures.
|
||||
|
||||
This package locates the font file on disk for the font currently in use by the terminal and parses the ligature information out of it (via the [font-ligatures] package). As text is rendered in xterm.js, this package annotates it with the locations of ligatures, allowing xterm.js to render it correctly.
|
||||
|
||||
Since this package depends on being able to find and resolve a system font from disk, it has to have system access that isn't available in the web browser. As a result, this package is mainly useful in environments that combine browser and Node.js runtimes (such as [Electron]).
|
||||
|
||||
### Fonts
|
||||
|
||||
This package makes use of the following fonts for testing:
|
||||
|
||||
* [Fira Code][Fira Code] - [Licensed under the OFL][Fira Code License] by Nikita
|
||||
Prokopov, Mozilla Foundation with reserved names Fira Code, Fira Mono, and
|
||||
Fira Sans
|
||||
* [Iosevka] - [Licensed under the OFL][Iosevka License] by Belleve Invis with
|
||||
reserved name Iosevka
|
||||
|
||||
[xterm.js]: https://github.com/xtermjs/xterm.js
|
||||
[Electron]: https://electronjs.org/
|
||||
[Node.js]: https://nodejs.org/
|
||||
[font-ligatures]: https://github.com/princjef/font-ligatures
|
||||
[Fira Code]: https://github.com/tonsky/FiraCode
|
||||
[Fira Code License]: https://github.com/tonsky/FiraCode/blob/master/LICENSE
|
||||
[Iosevka]: https://github.com/be5invis/Iosevka
|
||||
[Iosevka License]: https://github.com/be5invis/Iosevka/blob/master/LICENSE.md
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const util = require('util');
|
||||
|
||||
const axios = require('axios').default;
|
||||
const mkdirp = require('mkdirp');
|
||||
const yauzl = require('yauzl');
|
||||
|
||||
const urls = {
|
||||
fira: 'https://github.com/tonsky/FiraCode/raw/d42e7276fa925e5f82748f3ec9ea429736611b48/distr/otf/FiraCode-Regular.otf',
|
||||
iosevka: 'https://github.com/be5invis/Iosevka/releases/download/v1.14.3/01-iosevka-1.14.3.zip'
|
||||
};
|
||||
|
||||
const writeFile = util.promisify(fs.writeFile);
|
||||
const fontsFolder = path.join(__dirname, '../fonts');
|
||||
|
||||
async function download() {
|
||||
await mkdirp(fontsFolder);
|
||||
|
||||
await downloadFiraCode();
|
||||
await downloadIosevka();
|
||||
|
||||
console.log('Loaded all fonts for testing')
|
||||
}
|
||||
|
||||
async function downloadFiraCode() {
|
||||
const file = path.join(fontsFolder, 'firaCode.otf');
|
||||
if (await util.promisify(fs.exists)(file)) {
|
||||
console.log('Fira Code already loaded');
|
||||
} else {
|
||||
console.log('Downloading Fira Code...');
|
||||
await writeFile(
|
||||
file,
|
||||
(await axios.get(urls.fira, { responseType: 'arraybuffer' })).data
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadIosevka() {
|
||||
const file = path.join(fontsFolder, 'iosevka.ttf');
|
||||
if (await util.promisify(fs.exists)(file)) {
|
||||
console.log('Iosevka already loaded');
|
||||
} else {
|
||||
console.log('Downloading Iosevka...');
|
||||
const iosevkaContents = (await axios.get(urls.iosevka, { responseType: 'arraybuffer' })).data;
|
||||
const iosevkaZipfile = await util.promisify(yauzl.fromBuffer)(iosevkaContents);
|
||||
await new Promise((resolve, reject) => {
|
||||
iosevkaZipfile.on('entry', entry => {
|
||||
if (entry.fileName === 'ttf/iosevka-regular.ttf') {
|
||||
iosevkaZipfile.openReadStream(entry, (err, stream) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
const writeStream = fs.createWriteStream(file);
|
||||
stream.pipe(writeStream);
|
||||
writeStream.on('close', () => resolve());
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
download();
|
||||
|
||||
process.on('unhandledRejection', e => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "xterm-addon-ligatures",
|
||||
"version": "0.3.0",
|
||||
"description": "Add support for programming ligatures to xterm.js",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
},
|
||||
"main": "lib/xterm-addon-ligatures.js",
|
||||
"types": "typings/xterm-addon-ligatures.d.ts",
|
||||
"repository": "https://github.com/xtermjs/xterm.js",
|
||||
"engines": {
|
||||
"node": ">8.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "node bin/download-fonts.js",
|
||||
"build": "tsc -p src",
|
||||
"watch": "tsc -w -p src",
|
||||
"prepackage": "npm run build",
|
||||
"package": "webpack",
|
||||
"pretest": "npm run build",
|
||||
"test": "nyc mocha out/**/*.test.js",
|
||||
"prepublish": "npm run package"
|
||||
},
|
||||
"keywords": [
|
||||
"font",
|
||||
"ligature",
|
||||
"xterm",
|
||||
"xterm.js",
|
||||
"terminal"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"font-finder": "^1.0.4",
|
||||
"font-ligatures": "^1.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/sinon": "^5.0.1",
|
||||
"axios": "^0.18.0",
|
||||
"mkdirp": "0.5.5",
|
||||
"sinon": "6.3.5",
|
||||
"yauzl": "^2.10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"xterm": "^4.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal } from 'xterm';
|
||||
import { enableLigatures } from '.';
|
||||
|
||||
export interface ITerminalAddon {
|
||||
activate(terminal: Terminal): void;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export class LigaturesAddon implements ITerminalAddon {
|
||||
constructor() {}
|
||||
|
||||
public activate(terminal: Terminal): void {
|
||||
enableLigatures(terminal);
|
||||
}
|
||||
|
||||
public dispose(): void {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import * as fontFinder from 'font-finder';
|
||||
import * as fontLigatures from 'font-ligatures';
|
||||
|
||||
import parse from './parse';
|
||||
|
||||
let fontsPromise: Promise<fontFinder.FontList> | undefined = undefined;
|
||||
|
||||
/**
|
||||
* Loads the font ligature wrapper for the specified font family if it could be
|
||||
* resolved, throwing if it is unable to find a suitable match.
|
||||
* @param fontFamily The CSS font family definition to resolve
|
||||
* @param cacheSize The size of the ligature cache to maintain if the font is resolved
|
||||
*/
|
||||
export default async function load(fontFamily: string, cacheSize: number): Promise<fontLigatures.Font | undefined> {
|
||||
if (!fontsPromise) {
|
||||
fontsPromise = fontFinder.list();
|
||||
}
|
||||
|
||||
const fonts = await fontsPromise;
|
||||
for (const family of parse(fontFamily)) {
|
||||
// If we reach one of the generic font families, the font resolution
|
||||
// will end for the browser and we can't determine the specific font
|
||||
// used. Throw.
|
||||
if (genericFontFamilies.includes(family)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (fonts.hasOwnProperty(family) && fonts[family].length > 0) {
|
||||
return await fontLigatures.loadFile(fonts[family][0].path, { cacheSize });
|
||||
}
|
||||
}
|
||||
|
||||
// If none of the fonts could resolve, throw an error
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/css-fonts-4/#generic-font-families
|
||||
const genericFontFamilies = [
|
||||
'serif',
|
||||
'sans-serif',
|
||||
'cursive',
|
||||
'fantasy',
|
||||
'monospace',
|
||||
'system-ui',
|
||||
'emoji',
|
||||
'math',
|
||||
'fangsong'
|
||||
];
|
||||
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import * as path from 'path';
|
||||
import * as sinon from 'sinon';
|
||||
import { assert } from 'chai';
|
||||
import * as fontFinder from 'font-finder';
|
||||
import * as fontLigatures from 'font-ligatures';
|
||||
|
||||
import * as ligatureSupport from '.';
|
||||
|
||||
describe('xterm-addon-ligatures', () => {
|
||||
let onRefresh: sinon.SinonStub;
|
||||
let term: MockTerminal;
|
||||
|
||||
// -> forms a ligature in Fira Code and Iosevka, but www only forms a ligature
|
||||
// in Fira Code
|
||||
const input = 'a -> b www c';
|
||||
|
||||
before(() => {
|
||||
sinon.stub(fontFinder, 'list').returns(Promise.resolve({
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
'Fira Code': [{
|
||||
path: path.join(__dirname, '../fonts/firaCode.otf'),
|
||||
style: fontFinder.Style.Regular,
|
||||
type: fontFinder.Type.Monospace,
|
||||
weight: 400
|
||||
}],
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
'Iosevka': [{
|
||||
path: path.join(__dirname, '../fonts/iosevka.ttf'),
|
||||
style: fontFinder.Style.Regular,
|
||||
type: fontFinder.Type.Monospace,
|
||||
weight: 400
|
||||
}],
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
'Nonexistant Font': [{
|
||||
path: path.join(__dirname, '../fonts/nonexistant.ttf'),
|
||||
style: fontFinder.Style.Regular,
|
||||
type: fontFinder.Type.Monospace,
|
||||
weight: 400
|
||||
}]
|
||||
} as fontFinder.FontList));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
onRefresh = sinon.stub();
|
||||
term = new MockTerminal(onRefresh);
|
||||
ligatureSupport.enableLigatures(term as any);
|
||||
});
|
||||
|
||||
it('registers itself correctly', () => {
|
||||
const term = new MockTerminal(sinon.spy());
|
||||
assert.isUndefined(term.joiner);
|
||||
ligatureSupport.enableLigatures(term as any);
|
||||
assert.isFunction(term.joiner);
|
||||
});
|
||||
|
||||
it('registers itself correctly when called directly', () => {
|
||||
const term = new MockTerminal(sinon.spy());
|
||||
assert.isUndefined(term.joiner);
|
||||
ligatureSupport.enableLigatures(term as any);
|
||||
assert.isFunction(term.joiner);
|
||||
});
|
||||
|
||||
it('returns an empty set of ranges on the first call while the font is loading', () => {
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
});
|
||||
|
||||
it('returns the correct set of ranges once the font has loaded', done => {
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
onRefresh.callsFake(() => {
|
||||
assert.deepEqual(term.joiner!(input), [[2, 4], [7, 10]]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles quoted font names', done => {
|
||||
term.setOption('fontFamily', '"Fira Code", monospace');
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
onRefresh.callsFake(() => {
|
||||
assert.deepEqual(term.joiner!(input), [[2, 4], [7, 10]]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('falls back to later fonts if earlier ones are not present', done => {
|
||||
term.setOption('fontFamily', 'notinstalled, Fira Code, monospace');
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
onRefresh.callsFake(() => {
|
||||
assert.deepEqual(term.joiner!(input), [[2, 4], [7, 10]]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('uses the current font value', done => {
|
||||
// The first three calls are all synchronous so that we don't allow time for
|
||||
// any fonts to load while we're switching things around
|
||||
term.setOption('fontFamily', 'Fira Code');
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
term.setOption('fontFamily', 'notinstalled');
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
term.setOption('fontFamily', 'Iosevka');
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
onRefresh.callsFake(() => {
|
||||
assert.deepEqual(term.joiner!(input), [[2, 4]]);
|
||||
|
||||
// And switch it back to Fira Code for good measure
|
||||
term.setOption('fontFamily', 'Fira Code');
|
||||
|
||||
// At this point, we haven't loaded the new font, so the result reverts
|
||||
// back to empty until that happens
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
|
||||
onRefresh.callsFake(() => {
|
||||
assert.deepEqual(term.joiner!(input), [[2, 4], [7, 10]]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('allows multiple terminal instances that use different fonts', done => {
|
||||
const onRefresh2 = sinon.stub();
|
||||
const term2 = new MockTerminal(onRefresh2);
|
||||
term2.setOption('fontFamily', 'Iosevka');
|
||||
ligatureSupport.enableLigatures(term2 as any);
|
||||
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
onRefresh.callsFake(() => {
|
||||
assert.deepEqual(term.joiner!(input), [[2, 4], [7, 10]]);
|
||||
assert.deepEqual(term2.joiner!(input), []);
|
||||
onRefresh2.callsFake(() => {
|
||||
assert.deepEqual(term2.joiner!(input), [[2, 4]]);
|
||||
assert.deepEqual(term.joiner!(input), [[2, 4], [7, 10]]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('fails if it finds but cannot load the font', async () => {
|
||||
term.setOption('fontFamily', 'Nonexistant Font, monospace');
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
await delay(500);
|
||||
assert.isTrue(onRefresh.notCalled);
|
||||
assert.throws(() => term.joiner!(input));
|
||||
});
|
||||
|
||||
it('returns nothing if the font is not present on the system', async () => {
|
||||
term.setOption('fontFamily', 'notinstalled');
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
await delay(500);
|
||||
assert.isTrue(onRefresh.notCalled);
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
});
|
||||
|
||||
it('returns nothing if no specific font is specified', async () => {
|
||||
term.setOption('fontFamily', 'monospace');
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
await delay(500);
|
||||
assert.isTrue(onRefresh.notCalled);
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
});
|
||||
|
||||
it('returns nothing if no fonts are provided', async () => {
|
||||
term.setOption('fontFamily', '');
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
await delay(500);
|
||||
assert.isTrue(onRefresh.notCalled);
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
});
|
||||
|
||||
it('fails when given malformed inputs', async () => {
|
||||
term.setOption('fontFamily', {} as any);
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
await delay(500);
|
||||
assert.isTrue(onRefresh.notCalled);
|
||||
assert.throws(() => term.joiner!(input));
|
||||
});
|
||||
|
||||
it('ensures no empty errors are thrown', async () => {
|
||||
sinon.stub(fontLigatures, 'loadFile').callsFake(async () => { throw undefined; });
|
||||
term.setOption('fontFamily', 'Iosevka');
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
await delay(500);
|
||||
assert.isTrue(onRefresh.notCalled);
|
||||
assert.throws(() => term.joiner!(input), 'Failure while loading font');
|
||||
(fontLigatures.loadFile as sinon.SinonStub).restore();
|
||||
});
|
||||
});
|
||||
|
||||
class MockTerminal {
|
||||
private _options: { [name: string]: string | number } = {
|
||||
fontFamily: 'Fira Code, monospace',
|
||||
rows: 50
|
||||
};
|
||||
public joiner?: (text: string) => [number, number][];
|
||||
public refresh: (start: number, end: number) => void;
|
||||
|
||||
constructor(onRefresh: (start: number, end: number) => void) {
|
||||
this.refresh = onRefresh;
|
||||
}
|
||||
|
||||
public registerCharacterJoiner(handler: (text: string) => [number, number][]): number {
|
||||
this.joiner = handler;
|
||||
return 1;
|
||||
}
|
||||
public deregisterCharacterJoiner(id: number): void {
|
||||
this.joiner = undefined;
|
||||
}
|
||||
public setOption(name: string, value: string | number): void {
|
||||
this._options[name] = value;
|
||||
}
|
||||
public getOption(name: string): string | number {
|
||||
return this._options[name];
|
||||
}
|
||||
}
|
||||
|
||||
function delay(delayMs: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, delayMs));
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal } from 'xterm';
|
||||
import { Font } from 'font-ligatures';
|
||||
|
||||
import load from './font';
|
||||
|
||||
const enum LoadingState {
|
||||
UNLOADED,
|
||||
LOADING,
|
||||
LOADED,
|
||||
FAILED
|
||||
}
|
||||
|
||||
// Caches 100K characters worth of ligatures. In practice this works out to
|
||||
// about 650 KB worth of cache, when a moderate number of ligatures are present.
|
||||
const CACHE_SIZE = 100000;
|
||||
|
||||
/**
|
||||
* Enable ligature support for the provided Terminal instance. To function
|
||||
* properly, this must be called after `open()` is called on the therminal. If
|
||||
* the font currently in use supports ligatures, the terminal will automatically
|
||||
* start to render them.
|
||||
* @param term Terminal instance from xterm.js
|
||||
*/
|
||||
export function enableLigatures(term: Terminal): void {
|
||||
let currentFontName: string | undefined = undefined;
|
||||
let font: Font | undefined = undefined;
|
||||
let loadingState: LoadingState = LoadingState.UNLOADED;
|
||||
let loadError: any | undefined = undefined;
|
||||
|
||||
term.registerCharacterJoiner((text: string): [number, number][] => {
|
||||
// If the font hasn't been loaded yet, load it and return an empty result
|
||||
const termFont = term.getOption('fontFamily');
|
||||
if (
|
||||
termFont &&
|
||||
(loadingState === LoadingState.UNLOADED || currentFontName !== termFont)
|
||||
) {
|
||||
font = undefined;
|
||||
loadingState = LoadingState.LOADING;
|
||||
currentFontName = termFont;
|
||||
const currentCallFontName = currentFontName;
|
||||
|
||||
load(currentCallFontName, CACHE_SIZE)
|
||||
.then(f => {
|
||||
// Another request may have come in while we were waiting, so make
|
||||
// sure our font is still vaild.
|
||||
if (currentCallFontName === term.getOption('fontFamily')) {
|
||||
loadingState = LoadingState.LOADED;
|
||||
font = f;
|
||||
|
||||
// Only refresh things if we actually found a font
|
||||
if (f) {
|
||||
term.refresh(0, term.getOption('rows') - 1);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
// Another request may have come in while we were waiting, so make
|
||||
// sure our font is still vaild.
|
||||
if (currentCallFontName === term.getOption('fontFamily')) {
|
||||
loadingState = LoadingState.FAILED;
|
||||
font = undefined;
|
||||
loadError = e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (font && loadingState === LoadingState.LOADED) {
|
||||
// We clone the entries to avoid the internal cache of the ligature finder
|
||||
// getting messed up.
|
||||
return font.findLigatureRanges(text).map<[number, number]>(
|
||||
range => [range[0], range[1]]
|
||||
);
|
||||
}
|
||||
if (loadingState === LoadingState.FAILED) {
|
||||
throw loadError || new Error('Failure while loading font');
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
|
||||
import parse from './parse';
|
||||
|
||||
// TODO: integrate tests from http://test.csswg.org/suites/css-fonts-4_dev/nightly-unstable/
|
||||
describe('parse', () => {
|
||||
it('parses individual families', () => {
|
||||
assert.deepEqual(parse('monospace'), ['monospace']);
|
||||
});
|
||||
|
||||
it('parses multiple families', () => {
|
||||
assert.deepEqual(parse('Arial, Verdana, serif'), ['Arial', 'Verdana', 'serif']);
|
||||
});
|
||||
|
||||
it('parses quoted families', () => {
|
||||
assert.deepEqual(parse('"Times New Roman", serif'), ['Times New Roman', 'serif']);
|
||||
});
|
||||
|
||||
it('parses single quoted families', () => {
|
||||
assert.deepEqual(parse('\'Times New Roman\', serif'), ['Times New Roman', 'serif']);
|
||||
});
|
||||
|
||||
it('parses families with spaces in their names', () => {
|
||||
assert.deepEqual(parse('Times New Roman, serif'), ['Times New Roman', 'serif']);
|
||||
});
|
||||
|
||||
it('collapses multiple spaces together in identifiers', () => {
|
||||
assert.deepEqual(parse('Times New Roman, serif'), ['Times New Roman', 'serif']);
|
||||
});
|
||||
|
||||
it('does not collapse multiple spaces together in quoted strings', () => {
|
||||
assert.deepEqual(parse('"Times New Roman", serif'), ['Times New Roman', 'serif']);
|
||||
});
|
||||
|
||||
it('handles escaped characters in strings', () => {
|
||||
assert.deepEqual(parse('"quote \\" slash \\\\ slashquote \\\\\\"", serif'), ['quote " slash \\ slashquote \\"', 'serif']);
|
||||
});
|
||||
|
||||
it('fails if a family has an unterminated string', () => {
|
||||
assert.throws(() => parse('"Unterminated, serif'));
|
||||
});
|
||||
|
||||
it('handles unicode escape sequences', () => {
|
||||
assert.deepEqual(parse('"space\\20 between", serif'), ['space between', 'serif']);
|
||||
});
|
||||
|
||||
it('swallows only the first space after a unicode escape', () => {
|
||||
assert.deepEqual(parse('"two-space\\20 between", serif'), ['two-space between', 'serif']);
|
||||
});
|
||||
|
||||
it('automatically ends the unicode escape after six digits', () => {
|
||||
assert.deepEqual(parse('space\\000020between, serif'), ['space between', 'serif']);
|
||||
});
|
||||
|
||||
it('handles unicode escapes at the end of the family', () => {
|
||||
assert.deepEqual(parse('endswithbrace \\7b, serif'), ['endswithbrace {', 'serif']);
|
||||
});
|
||||
|
||||
it('handles unicode escapes at the end of the input', () => {
|
||||
assert.deepEqual(parse('endswithbrace \\7b'), ['endswithbrace {']);
|
||||
});
|
||||
|
||||
it('handles other escaped characters in identifiers', () => {
|
||||
assert.deepEqual(parse('has\\,comma'), ['has,comma']);
|
||||
});
|
||||
|
||||
it('swallows escaped newlines in strings', () => {
|
||||
assert.deepEqual(parse('"multi \\\nline", serif'), ['multi line', 'serif']);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,180 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
interface IParseContext {
|
||||
input: string;
|
||||
offset: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a CSS font family value, returning the component font families
|
||||
* contained within.
|
||||
*
|
||||
* @param family The CSS font family input string to parse
|
||||
*/
|
||||
export default function parse(family: string): string[] {
|
||||
if (typeof family !== 'string') {
|
||||
throw new Error('Font family must be a string');
|
||||
}
|
||||
|
||||
const context: IParseContext = {
|
||||
input: family,
|
||||
offset: 0
|
||||
};
|
||||
|
||||
const families = [];
|
||||
let currentFamily = '';
|
||||
|
||||
// Work through the input character by character until there are none left.
|
||||
// This lexing and parsing in one pass.
|
||||
while (context.offset < context.input.length) {
|
||||
const char = context.input[context.offset++];
|
||||
switch (char) {
|
||||
// String
|
||||
case '\'':
|
||||
case '"':
|
||||
currentFamily += parseString(context, char);
|
||||
break;
|
||||
// End of family
|
||||
case ',':
|
||||
families.push(currentFamily);
|
||||
currentFamily = '';
|
||||
break;
|
||||
default:
|
||||
// Identifiers (whitespace between families is swallowed)
|
||||
if (!/\s/.test(char)) {
|
||||
context.offset--;
|
||||
currentFamily += parseIdentifier(context);
|
||||
families.push(currentFamily);
|
||||
currentFamily = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return families;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a CSS string.
|
||||
*
|
||||
* @param context Parsing input and offset
|
||||
* @param quoteChar The quote character for the string (' or ")
|
||||
*/
|
||||
function parseString(context: IParseContext, quoteChar: '\'' | '"'): string {
|
||||
let str = '';
|
||||
let escaped = false;
|
||||
while (context.offset < context.input.length) {
|
||||
const char = context.input[context.offset++];
|
||||
if (escaped) {
|
||||
if (/[0-9a-fA-F]/.test(char)) {
|
||||
// Unicode escape
|
||||
context.offset--;
|
||||
str += parseUnicode(context);
|
||||
} else if (char !== '\n') {
|
||||
// Newlines are ignored if escaped. Other characters are used as is.
|
||||
str += char;
|
||||
}
|
||||
escaped = false;
|
||||
} else {
|
||||
switch (char) {
|
||||
// Terminated quote
|
||||
case quoteChar:
|
||||
return str;
|
||||
// Begin escape
|
||||
case '\\':
|
||||
escaped = true;
|
||||
break;
|
||||
// Add character to string
|
||||
default:
|
||||
str += char;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Unterminated string');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a CSS custom identifier.
|
||||
*
|
||||
* @param context Parsing input and offset
|
||||
*/
|
||||
function parseIdentifier(context: IParseContext): string {
|
||||
let str = '';
|
||||
let escaped = false;
|
||||
while (context.offset < context.input.length) {
|
||||
const char = context.input[context.offset++];
|
||||
if (escaped) {
|
||||
if (/[0-9a-fA-F]/.test(char)) {
|
||||
// Unicode escape
|
||||
context.offset--;
|
||||
str += parseUnicode(context);
|
||||
} else {
|
||||
// Everything else is used as is
|
||||
str += char;
|
||||
}
|
||||
escaped = false;
|
||||
} else {
|
||||
switch (char) {
|
||||
// Begin escape
|
||||
case '\\':
|
||||
escaped = true;
|
||||
break;
|
||||
// Terminate identifier
|
||||
case ',':
|
||||
return str;
|
||||
default:
|
||||
if (/\s/.test(char)) {
|
||||
// Whitespace is collapsed into a single space within an identifier
|
||||
if (!str.endsWith(' ')) {
|
||||
str += ' ';
|
||||
}
|
||||
} else {
|
||||
// Add other characters directly
|
||||
str += char;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a CSS unicode escape.
|
||||
*
|
||||
* @param context Parsing input and offset
|
||||
*/
|
||||
function parseUnicode(context: IParseContext): string {
|
||||
let str = '';
|
||||
while (context.offset < context.input.length) {
|
||||
const char = context.input[context.offset++];
|
||||
if (/\s/.test(char)) {
|
||||
// The first whitespace character after a unicode escape indicates the end
|
||||
// of the escape and is swallowed.
|
||||
return unicodeToString(str);
|
||||
}
|
||||
if (str.length >= 6 || !/[0-9a-fA-F]/.test(char)) {
|
||||
// If the next character is not a valid hex digit or we have reached the
|
||||
// maximum of 6 digits in the escape, terminate the escape.
|
||||
context.offset--;
|
||||
return unicodeToString(str);
|
||||
}
|
||||
|
||||
// Otherwise, just add it to the escape
|
||||
str += char;
|
||||
}
|
||||
|
||||
return unicodeToString(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a unicode code point from a hex string to a utf8 string.
|
||||
*
|
||||
* @param codePoint Unicode code point represented as a hex string
|
||||
*/
|
||||
function unicodeToString(codePoint: string): string {
|
||||
return String.fromCodePoint(parseInt(codePoint, 16));
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2017",
|
||||
"module": "commonjs",
|
||||
"sourceMap": true,
|
||||
"outDir": "../out",
|
||||
"rootDir": ".",
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"preserveWatchOutput": true
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*
|
||||
* This contains the type declarations for the xterm-addon-ligatures library.
|
||||
* Note that some interfaces may differ between this file and the actual
|
||||
* implementation in src/, that's because this file declares the *public* API
|
||||
* which is intended to be stable and consumed by external programs.
|
||||
*/
|
||||
|
||||
import { Terminal, ITerminalAddon } from 'xterm';
|
||||
|
||||
declare module 'xterm-addon-ligatures' {
|
||||
/**
|
||||
* An xterm.js addon that enables web links.
|
||||
*/
|
||||
export class LigaturesAddon implements ITerminalAddon {
|
||||
/**
|
||||
* Creates a new ligatures addon.
|
||||
*/
|
||||
constructor();
|
||||
|
||||
/**
|
||||
* Activates the addon
|
||||
* @param terminal The terminal the addon is being loaded in.
|
||||
*/
|
||||
public activate(terminal: Terminal): void;
|
||||
|
||||
/**
|
||||
* Disposes the addon.
|
||||
*/
|
||||
public dispose(): void;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const addonName = 'LigaturesAddon';
|
||||
const mainFile = 'xterm-addon-ligatures.js';
|
||||
|
||||
module.exports = {
|
||||
entry: `./out/${addonName}.js`,
|
||||
devtool: 'source-map',
|
||||
target: 'electron-renderer',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: ["source-map-loader"],
|
||||
enforce: "pre",
|
||||
exclude: /node_modules/
|
||||
}
|
||||
]
|
||||
},
|
||||
output: {
|
||||
filename: mainFile,
|
||||
path: path.resolve('./lib'),
|
||||
library: addonName,
|
||||
libraryTarget: 'umd'
|
||||
},
|
||||
mode: 'production',
|
||||
externals: {
|
||||
'font-finder':'font-finder',
|
||||
'font-ligatures':'font-ligatures'
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,236 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@sinonjs/commons@^1", "@sinonjs/commons@^1.0.2", "@sinonjs/commons@^1.3.0", "@sinonjs/commons@^1.7.0":
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.0.tgz#c8d68821a854c555bba172f3b06959a0039b236d"
|
||||
integrity sha512-wEj54PfsZ5jGSwMX68G8ZXFawcSglQSXqCftWX3ec8MDUzQdHgcKvw97awHbY0efQEL5iKUOAmmVtoYgmrSG4Q==
|
||||
dependencies:
|
||||
type-detect "4.0.8"
|
||||
|
||||
"@sinonjs/formatio@^3.0.0", "@sinonjs/formatio@^3.2.1":
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-3.2.2.tgz#771c60dfa75ea7f2d68e3b94c7e888a78781372c"
|
||||
integrity sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1"
|
||||
"@sinonjs/samsam" "^3.1.0"
|
||||
|
||||
"@sinonjs/samsam@^2.1.2":
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-2.1.3.tgz#62cf2a9b624edc795134135fe37fc2ae8ea36be3"
|
||||
integrity sha512-8zNeBkSKhU9a5cRNbpCKau2WWPfan+Q2zDlcXvXyhn9EsMqgYs4qzo0XHNVlXC6ABQL8fT6nV+zzo5RTHJzyXw==
|
||||
|
||||
"@sinonjs/samsam@^3.1.0":
|
||||
version "3.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-3.3.3.tgz#46682efd9967b259b81136b9f120fd54585feb4a"
|
||||
integrity sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.3.0"
|
||||
array-from "^2.1.1"
|
||||
lodash "^4.17.15"
|
||||
|
||||
"@sinonjs/text-encoding@^0.7.1":
|
||||
version "0.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5"
|
||||
integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==
|
||||
|
||||
"@types/sinon@^5.0.1":
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-5.0.1.tgz#a15b36ec42f1f53166617491feabd1734cb03e21"
|
||||
|
||||
array-from@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/array-from/-/array-from-2.1.1.tgz#cfe9d8c26628b9dc5aecc62a9f5d8f1f352c1195"
|
||||
integrity sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=
|
||||
|
||||
axios@^0.18.0:
|
||||
version "0.18.1"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.1.tgz#ff3f0de2e7b5d180e757ad98000f1081b87bcea3"
|
||||
dependencies:
|
||||
follow-redirects "1.5.10"
|
||||
is-buffer "^2.0.2"
|
||||
|
||||
buffer-crc32@~0.2.3:
|
||||
version "0.2.13"
|
||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||
|
||||
debug@=3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
diff@^3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
|
||||
|
||||
fd-slicer@~1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
|
||||
dependencies:
|
||||
pend "~1.2.0"
|
||||
|
||||
follow-redirects@1.5.10:
|
||||
version "1.5.10"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
|
||||
dependencies:
|
||||
debug "=3.1.0"
|
||||
|
||||
font-finder@^1.0.3, font-finder@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/font-finder/-/font-finder-1.0.4.tgz#2ca944954dd8d0e1b5bdc4c596cc08607761d89b"
|
||||
dependencies:
|
||||
get-system-fonts "^2.0.0"
|
||||
promise-stream-reader "^1.0.1"
|
||||
|
||||
font-ligatures@^1.3.3:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/font-ligatures/-/font-ligatures-1.3.3.tgz#63fff18dc8adb3a11fe5eec1f4e8d7edfa8075b9"
|
||||
integrity sha512-NSGpHgVNX81M7AWS1XylK1UZbN3QllfUIDAAuPv6TUcl5O2b781JcKS5L2RopAU0AqlTyX3ZuX/04eaMpbVrHA==
|
||||
dependencies:
|
||||
font-finder "^1.0.3"
|
||||
lru-cache "^4.1.3"
|
||||
opentype.js "^0.8.0"
|
||||
|
||||
get-system-fonts@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/get-system-fonts/-/get-system-fonts-2.0.0.tgz#a43b9a33f05c0715a60176d2aad5ce6e98f0a3c6"
|
||||
|
||||
has-flag@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
|
||||
|
||||
is-buffer@^2.0.2:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725"
|
||||
|
||||
isarray@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
|
||||
|
||||
just-extend@^4.0.2:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.1.0.tgz#7278a4027d889601640ee0ce0e5a00b992467da4"
|
||||
integrity sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==
|
||||
|
||||
lodash.get@^4.4.2:
|
||||
version "4.4.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
|
||||
|
||||
lodash@^4.17.15:
|
||||
version "4.17.19"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
|
||||
integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
|
||||
|
||||
lolex@^2.7.5:
|
||||
version "2.7.5"
|
||||
resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.7.5.tgz#113001d56bfc7e02d56e36291cc5c413d1aa0733"
|
||||
integrity sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q==
|
||||
|
||||
lolex@^5.0.1:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/lolex/-/lolex-5.1.2.tgz#953694d098ce7c07bc5ed6d0e42bc6c0c6d5a367"
|
||||
integrity sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.7.0"
|
||||
|
||||
lru-cache@^4.1.3:
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c"
|
||||
dependencies:
|
||||
pseudomap "^1.0.2"
|
||||
yallist "^2.1.2"
|
||||
|
||||
minimist@^1.2.5:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||
|
||||
mkdirp@0.5.5:
|
||||
version "0.5.5"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
|
||||
integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
|
||||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
|
||||
nise@^1.4.5:
|
||||
version "1.5.3"
|
||||
resolved "https://registry.yarnpkg.com/nise/-/nise-1.5.3.tgz#9d2cfe37d44f57317766c6e9408a359c5d3ac1f7"
|
||||
integrity sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==
|
||||
dependencies:
|
||||
"@sinonjs/formatio" "^3.2.1"
|
||||
"@sinonjs/text-encoding" "^0.7.1"
|
||||
just-extend "^4.0.2"
|
||||
lolex "^5.0.1"
|
||||
path-to-regexp "^1.7.0"
|
||||
|
||||
opentype.js@^0.8.0:
|
||||
version "0.8.0"
|
||||
resolved "https://registry.yarnpkg.com/opentype.js/-/opentype.js-0.8.0.tgz#acabcfa1642fbe894a3e4d759e43ba694e02bd35"
|
||||
dependencies:
|
||||
tiny-inflate "^1.0.2"
|
||||
|
||||
path-to-regexp@^1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d"
|
||||
dependencies:
|
||||
isarray "0.0.1"
|
||||
|
||||
pend@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||
|
||||
promise-stream-reader@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/promise-stream-reader/-/promise-stream-reader-1.0.1.tgz#4e793a79c9d49a73ccd947c6da9c127f12923649"
|
||||
|
||||
pseudomap@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
|
||||
|
||||
sinon@6.3.5:
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/sinon/-/sinon-6.3.5.tgz#0f6d6a5b4ebaad1f6e8e019395542d1d02c144a0"
|
||||
integrity sha512-xgoZ2gKjyVRcF08RrIQc+srnSyY1JDJtxu3Nsz07j1ffjgXoY6uPLf/qja6nDBZgzYYEovVkFryw2+KiZz11xQ==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.0.2"
|
||||
"@sinonjs/formatio" "^3.0.0"
|
||||
"@sinonjs/samsam" "^2.1.2"
|
||||
diff "^3.5.0"
|
||||
lodash.get "^4.4.2"
|
||||
lolex "^2.7.5"
|
||||
nise "^1.4.5"
|
||||
supports-color "^5.5.0"
|
||||
type-detect "^4.0.8"
|
||||
|
||||
supports-color@^5.5.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
||||
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
tiny-inflate@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.2.tgz#93d9decffc8805bd57eae4310f0b745e9b6fb3a7"
|
||||
|
||||
type-detect@4.0.8, type-detect@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
|
||||
|
||||
yallist@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
|
||||
|
||||
yauzl@^2.10.0:
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
|
||||
dependencies:
|
||||
buffer-crc32 "~0.2.3"
|
||||
fd-slicer "~1.1.0"
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm-addon-search",
|
||||
"version": "0.4.0",
|
||||
"version": "0.7.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
|
||||
@@ -3,31 +3,29 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import * as puppeteer from 'puppeteer';
|
||||
import { assert } from 'chai';
|
||||
import { ITerminalOptions } from 'xterm';
|
||||
import { readFile } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
import { openTerminal, writeSync, getBrowserType } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from 'playwright-core';
|
||||
|
||||
const APP = 'http://127.0.0.1:3000/test';
|
||||
|
||||
let browser: puppeteer.Browser;
|
||||
let page: puppeteer.Page;
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
|
||||
describe('Search Tests', function (): void {
|
||||
this.timeout(20000);
|
||||
|
||||
before(async function (): Promise<any> {
|
||||
browser = await puppeteer.launch({
|
||||
headless: process.argv.indexOf('--headless') !== -1,
|
||||
args: [`--window-size=${width},${height}`, `--no-sandbox`]
|
||||
describe('Search Tests', function(): void {
|
||||
before(async function(): Promise<any> {
|
||||
const browserType = getBrowserType();
|
||||
browser = await browserType.launch({ dumpio: true,
|
||||
headless: process.argv.indexOf('--headless') !== -1
|
||||
});
|
||||
page = (await browser.pages())[0];
|
||||
await page.setViewport({ width, height });
|
||||
page = await (await browser.newContext()).newPage();
|
||||
await page.setViewportSize({ width, height });
|
||||
await page.goto(APP);
|
||||
await openTerminal();
|
||||
await openTerminal(page);
|
||||
await page.evaluate(`window.search = new SearchAddon();`);
|
||||
await page.evaluate(`window.term.loadAddon(window.search);`);
|
||||
});
|
||||
@@ -41,7 +39,7 @@ describe('Search Tests', function (): void {
|
||||
});
|
||||
|
||||
it('Simple Search', async () => {
|
||||
await writeSync('dafhdjfldshafhldsahfkjhldhjkftestlhfdsakjfhdjhlfdsjkafhjdlk');
|
||||
await writeSync(page, 'dafhdjfldshafhldsahfkjhldhjkftestlhfdsakjfhdjhlfdsjkafhjdlk');
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('test')`), true);
|
||||
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'test');
|
||||
});
|
||||
@@ -54,16 +52,16 @@ describe('Search Tests', function (): void {
|
||||
}
|
||||
dataString += makeData(50);
|
||||
}
|
||||
await writeSync(dataString);
|
||||
await writeSync(page, dataString);
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('$^1_3{}test$#')`), true);
|
||||
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), '$^1_3{}test$#');
|
||||
});
|
||||
it ('Incremental Find Previous', async () => {
|
||||
it('Incremental Find Previous', async () => {
|
||||
await page.evaluate(`window.term.writeln('package.jsonc\\n')`);
|
||||
await writeSync('package.json pack package.lock');
|
||||
await writeSync(page, 'package.json pack package.lock');
|
||||
await page.evaluate(`window.search.findPrevious('pack', {incremental: true})`);
|
||||
let line: string = await page.evaluate(`window.term.buffer.getLine(window.term.getSelectionPosition().startRow).translateToString()`);
|
||||
let selectionPosition: {startColumn: number, startRow: number, endColumn: number, endRow: number} = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
let line: string = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().startRow).translateToString()`);
|
||||
let selectionPosition: { startColumn: number, startRow: number, endColumn: number, endRow: number } = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
// We look further ahead in the line to ensure that pack was selected from package.lock
|
||||
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn + 8), 'package.lock');
|
||||
await page.evaluate(`window.search.findPrevious('package.j', {incremental: true})`);
|
||||
@@ -71,16 +69,16 @@ describe('Search Tests', function (): void {
|
||||
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn + 3), 'package.json');
|
||||
await page.evaluate(`window.search.findPrevious('package.jsonc', {incremental: true})`);
|
||||
// We have to reevaluate line because it should have switched starting rows at this point
|
||||
line = await page.evaluate(`window.term.buffer.getLine(window.term.getSelectionPosition().startRow).translateToString()`);
|
||||
line = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().startRow).translateToString()`);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn), 'package.jsonc');
|
||||
});
|
||||
it ('Incremental Find Next', async () => {
|
||||
it('Incremental Find Next', async () => {
|
||||
await page.evaluate(`window.term.writeln('package.lock pack package.json package.ups\\n')`);
|
||||
await writeSync('package.jsonc');
|
||||
await writeSync(page, 'package.jsonc');
|
||||
await page.evaluate(`window.search.findNext('pack', {incremental: true})`);
|
||||
let line: string = await page.evaluate(`window.term.buffer.getLine(window.term.getSelectionPosition().startRow).translateToString()`);
|
||||
let selectionPosition: {startColumn: number, startRow: number, endColumn: number, endRow: number} = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
let line: string = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().startRow).translateToString()`);
|
||||
let selectionPosition: { startColumn: number, startRow: number, endColumn: number, endRow: number } = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
// We look further ahead in the line to ensure that pack was selected from package.lock
|
||||
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn + 8), 'package.lock');
|
||||
await page.evaluate(`window.search.findNext('package.j', {incremental: true})`);
|
||||
@@ -88,12 +86,12 @@ describe('Search Tests', function (): void {
|
||||
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn + 3), 'package.json');
|
||||
await page.evaluate(`window.search.findNext('package.jsonc', {incremental: true})`);
|
||||
// We have to reevaluate line because it should have switched starting rows at this point
|
||||
line = await page.evaluate(`window.term.buffer.getLine(window.term.getSelectionPosition().startRow).translateToString()`);
|
||||
line = await page.evaluate(`window.term.buffer.active.getLine(window.term.getSelectionPosition().startRow).translateToString()`);
|
||||
selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(line.substring(selectionPosition.startColumn, selectionPosition.endColumn), 'package.jsonc');
|
||||
});
|
||||
it('Simple Regex', async () => {
|
||||
await writeSync('abc123defABCD');
|
||||
await writeSync(page, 'abc123defABCD');
|
||||
await page.evaluate(`window.search.findNext('[a-z]+', {regex: true})`);
|
||||
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'abc');
|
||||
await page.evaluate(`window.search.findNext('[A-Z]+', {regex: true, caseSensitive: true})`);
|
||||
@@ -101,7 +99,7 @@ describe('Search Tests', function (): void {
|
||||
});
|
||||
|
||||
it('Search for single result twice should not unselect it', async () => {
|
||||
await writeSync('abc def');
|
||||
await writeSync(page, 'abc def');
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('abc')`), true);
|
||||
assert.deepEqual(await page.evaluate(`window.term.getSelection()`), 'abc');
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('abc')`), true);
|
||||
@@ -118,7 +116,7 @@ describe('Search Tests', function (): void {
|
||||
.replace(/'/g, '\\\'');
|
||||
});
|
||||
it('should find all occurrences using findNext', async () => {
|
||||
await writeSync(fixture);
|
||||
await writeSync(page, fixture);
|
||||
assert.deepEqual(await page.evaluate(`window.search.findNext('opencv')`), true);
|
||||
let selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 53, endColumn: 30, endRow: 53 });
|
||||
@@ -152,7 +150,7 @@ describe('Search Tests', function (): void {
|
||||
assert.deepEqual(selectionPosition, { startColumn: 24, startRow: 53, endColumn: 30, endRow: 53 });
|
||||
});
|
||||
it('should find all occurrences using findPrevious', async () => {
|
||||
await writeSync(fixture);
|
||||
await writeSync(page, fixture);
|
||||
assert.deepEqual(await page.evaluate(`window.search.findPrevious('opencv')`), true);
|
||||
let selectionPosition = await page.evaluate(`window.term.getSelectionPosition()`);
|
||||
assert.deepEqual(selectionPosition, { startColumn: 11, startRow: 136, endColumn: 17, endRow: 136 });
|
||||
@@ -189,20 +187,6 @@ describe('Search Tests', function (): void {
|
||||
});
|
||||
});
|
||||
|
||||
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
|
||||
await page.evaluate(`window.term = new Terminal(${JSON.stringify(options)})`);
|
||||
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
|
||||
if (options.rendererType === 'dom') {
|
||||
await page.waitForSelector('.xterm-rows');
|
||||
} else {
|
||||
await page.waitForSelector('.xterm-text-layer');
|
||||
}
|
||||
}
|
||||
|
||||
async function writeSync(data: string): Promise<void> {
|
||||
return page.evaluate(`new Promise(resolve => window.term.write('${data}', resolve))`);
|
||||
}
|
||||
|
||||
function makeData(length: number): string {
|
||||
let result = '';
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
|
||||
@@ -87,7 +87,7 @@ export class SearchAddon implements ITerminalAddon {
|
||||
// Search from startRow + 1 to end
|
||||
if (!result) {
|
||||
|
||||
for (let y = startRow + 1; y < this._terminal.buffer.baseY + this._terminal.rows; y++) {
|
||||
for (let y = startRow + 1; y < this._terminal.buffer.active.baseY + this._terminal.rows; y++) {
|
||||
searchPosition.startRow = y;
|
||||
searchPosition.startCol = 0;
|
||||
// If the current line is wrapped line, increase index of column to ignore the previous scan
|
||||
@@ -110,8 +110,12 @@ export class SearchAddon implements ITerminalAddon {
|
||||
}
|
||||
}
|
||||
|
||||
// If there is only one result, return true.
|
||||
if (!result && currentSelection) return true;
|
||||
// If there is only one result, wrap back and return selection if it exists.
|
||||
if (!result && currentSelection) {
|
||||
searchPosition.startRow = currentSelection.startRow;
|
||||
searchPosition.startCol = 0;
|
||||
result = this._findInLine(term, searchPosition, searchOptions);
|
||||
}
|
||||
|
||||
// Set selection and scroll if a result was found
|
||||
return this._selectResult(result);
|
||||
@@ -135,7 +139,7 @@ export class SearchAddon implements ITerminalAddon {
|
||||
}
|
||||
|
||||
const isReverseSearch = true;
|
||||
let startRow = this._terminal.buffer.baseY + this._terminal.rows;
|
||||
let startRow = this._terminal.buffer.active.baseY + this._terminal.rows;
|
||||
let startCol = this._terminal.cols;
|
||||
let result: ISearchResult | undefined;
|
||||
const incremental = searchOptions ? searchOptions.incremental : false;
|
||||
@@ -174,8 +178,8 @@ export class SearchAddon implements ITerminalAddon {
|
||||
}
|
||||
}
|
||||
// If we hit the top and didn't search from the very bottom wrap back down
|
||||
if (!result && startRow !== (this._terminal.buffer.baseY + this._terminal.rows)) {
|
||||
for (let y = (this._terminal.buffer.baseY + this._terminal.rows); y > startRow; y--) {
|
||||
if (!result && startRow !== (this._terminal.buffer.active.baseY + this._terminal.rows)) {
|
||||
for (let y = (this._terminal.buffer.active.baseY + this._terminal.rows); y >= startRow; y--) {
|
||||
searchPosition.startRow = y;
|
||||
result = this._findInLine(term, searchPosition, searchOptions, isReverseSearch);
|
||||
if (result) {
|
||||
@@ -197,7 +201,7 @@ export class SearchAddon implements ITerminalAddon {
|
||||
private _initLinesCache(): void {
|
||||
const terminal = this._terminal!;
|
||||
if (!this._linesCache) {
|
||||
this._linesCache = new Array(terminal.buffer.length);
|
||||
this._linesCache = new Array(terminal.buffer.active.length);
|
||||
this._cursorMoveListener = terminal.onCursorMove(() => this._destroyLinesCache());
|
||||
this._resizeListener = terminal.onResize(() => this._destroyLinesCache());
|
||||
}
|
||||
@@ -249,7 +253,7 @@ export class SearchAddon implements ITerminalAddon {
|
||||
const col = searchPosition.startCol;
|
||||
|
||||
// Ignore wrapped lines, only consider on unwrapped line (first row of command string).
|
||||
const firstLine = terminal.buffer.getLine(row);
|
||||
const firstLine = terminal.buffer.active.getLine(row);
|
||||
if (firstLine && firstLine.isWrapped) {
|
||||
if (isReverseSearch) {
|
||||
searchPosition.startCol += terminal.cols;
|
||||
@@ -311,7 +315,7 @@ export class SearchAddon implements ITerminalAddon {
|
||||
return;
|
||||
}
|
||||
|
||||
const line = terminal.buffer.getLine(row);
|
||||
const line = terminal.buffer.active.getLine(row);
|
||||
|
||||
if (line) {
|
||||
for (let i = 0; i < resultIndex; i++) {
|
||||
@@ -320,13 +324,13 @@ export class SearchAddon implements ITerminalAddon {
|
||||
break;
|
||||
}
|
||||
// Adjust the searchIndex to normalize emoji into single chars
|
||||
const char = cell.char;
|
||||
const char = cell.getChars();
|
||||
if (char.length > 1) {
|
||||
resultIndex -= char.length - 1;
|
||||
}
|
||||
// Adjust the searchIndex for empty characters following wide unicode
|
||||
// chars (eg. CJK)
|
||||
const charWidth = cell.width;
|
||||
const charWidth = cell.getWidth();
|
||||
if (charWidth === 0) {
|
||||
resultIndex++;
|
||||
}
|
||||
@@ -354,9 +358,9 @@ export class SearchAddon implements ITerminalAddon {
|
||||
let lineWrapsToNext: boolean;
|
||||
|
||||
do {
|
||||
const nextLine = terminal.buffer.getLine(lineIndex + 1);
|
||||
const nextLine = terminal.buffer.active.getLine(lineIndex + 1);
|
||||
lineWrapsToNext = nextLine ? nextLine.isWrapped : false;
|
||||
const line = terminal.buffer.getLine(lineIndex);
|
||||
const line = terminal.buffer.active.getLine(lineIndex);
|
||||
if (!line) {
|
||||
break;
|
||||
}
|
||||
@@ -380,8 +384,8 @@ export class SearchAddon implements ITerminalAddon {
|
||||
}
|
||||
terminal.select(result.col, result.row, result.term.length);
|
||||
// If it is not in the viewport then we scroll else it just gets selected
|
||||
if (result.row >= (terminal.buffer.viewportY + terminal.rows) || result.row < terminal.buffer.viewportY) {
|
||||
let scroll = result.row - terminal.buffer.viewportY;
|
||||
if (result.row >= (terminal.buffer.active.viewportY + terminal.rows) || result.row < terminal.buffer.active.viewportY) {
|
||||
let scroll = result.row - terminal.buffer.active.viewportY;
|
||||
scroll = scroll - Math.floor(terminal.rows / 2);
|
||||
terminal.scrollLines(scroll);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,11 @@
|
||||
"outDir": "../out",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"strict": true
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../out-test/api/TestUtils"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
lib
|
||||
node_modules
|
||||
out-benchmark
|
||||
@@ -0,0 +1,5 @@
|
||||
**/*.api.js
|
||||
**/*.api.ts
|
||||
tsconfig.json
|
||||
.yarnrc
|
||||
webpack.config.js
|
||||
@@ -0,0 +1,42 @@
|
||||
## xterm-addon-serialize
|
||||
|
||||
An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables xterm.js to serialize a terminal framebuffer into string or html. This addon requires xterm.js v4+.
|
||||
|
||||
⚠️ This is an experimental addon that is still under construction ⚠️
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
npm install --save xterm-addon-serialize
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { Terminal } from "xterm";
|
||||
import { SerializeAddon } from "xterm-addon-serialize";
|
||||
|
||||
const terminal = new Terminal();
|
||||
const serializeAddon = new SerializeAddon();
|
||||
terminal.loadAddon(serializeAddon);
|
||||
|
||||
terminal.write("something...", () => {
|
||||
console.log(serializeAddon.serialize());
|
||||
});
|
||||
```
|
||||
|
||||
See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/xterm-addon-serialize/typings/xterm-addon-serialize.d.ts) for more advanced usage.
|
||||
|
||||
### Benchmark
|
||||
|
||||
⚠️ Ensure you have `lolcat`, `hexdump` programs installed in your computer
|
||||
|
||||
```shell
|
||||
$ git clone https://github.com/xtermjs/xterm.js.git
|
||||
$ cd xterm.js
|
||||
$ yarn
|
||||
$ cd addons/xterm-addon-serialize
|
||||
$ yarn benchmark && yarn benchmark-baseline
|
||||
$ # change some code in `xterm-addon-serialize`
|
||||
$ yarn benchmark-eval
|
||||
```
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { perfContext, before, ThroughputRuntimeCase } from 'xterm-benchmark';
|
||||
|
||||
import { spawn } from 'node-pty';
|
||||
import { Utf8ToUtf32, stringFromCodePoint } from 'common/input/TextDecoder';
|
||||
import { Terminal } from 'browser/public/Terminal';
|
||||
import { SerializeAddon } from 'SerializeAddon';
|
||||
|
||||
class TestTerminal extends Terminal {
|
||||
public writeSync(data: string): void {
|
||||
(this as any)._core.writeSync(data);
|
||||
}
|
||||
}
|
||||
|
||||
perfContext('Terminal: sh -c "dd if=/dev/random count=40 bs=1k | hexdump | lolcat -f"', () => {
|
||||
let content = '';
|
||||
let contentUtf8: Uint8Array;
|
||||
|
||||
before(async () => {
|
||||
const p = spawn('sh', ['-c', 'dd if=/dev/random count=40 bs=1k | hexdump | lolcat -f'], {
|
||||
name: 'xterm-256color',
|
||||
cols: 80,
|
||||
rows: 25,
|
||||
cwd: process.env.HOME,
|
||||
env: process.env,
|
||||
encoding: (null as unknown as string) // needs to be fixed in node-pty
|
||||
});
|
||||
const chunks: Buffer[] = [];
|
||||
let length = 0;
|
||||
p.on('data', data => {
|
||||
chunks.push(data as unknown as Buffer);
|
||||
length += data.length;
|
||||
});
|
||||
await new Promise(resolve => p.on('exit', () => resolve()));
|
||||
contentUtf8 = Buffer.concat(chunks, length);
|
||||
// translate to content string
|
||||
const buffer = new Uint32Array(contentUtf8.length);
|
||||
const decoder = new Utf8ToUtf32();
|
||||
const codepoints = decoder.decode(contentUtf8, buffer);
|
||||
for (let i = 0; i < codepoints; ++i) {
|
||||
content += stringFromCodePoint(buffer[i]);
|
||||
// peek into content to force flat repr in v8
|
||||
if (!(i % 10000000)) {
|
||||
content[i];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
perfContext('serialize', () => {
|
||||
let terminal: TestTerminal;
|
||||
const serializeAddon = new SerializeAddon();
|
||||
before(() => {
|
||||
terminal = new TestTerminal({ cols: 80, rows: 25, scrollback: 5000 });
|
||||
serializeAddon.activate(terminal);
|
||||
terminal.writeSync(content);
|
||||
});
|
||||
new ThroughputRuntimeCase('', () => {
|
||||
return { payloadSize: serializeAddon.serialize().length };
|
||||
}, { fork: false }).showAverageThroughput();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"APP_PATH": ".benchmark",
|
||||
"evalConfig": {
|
||||
"tolerance": {
|
||||
"*": [0.75, 1.5],
|
||||
"*.dev": [0.01, 1.5],
|
||||
"*.cv": [0.01, 1.5],
|
||||
"EscapeSequenceParser.benchmark.js.*.averageThroughput.mean": [0.9, 5]
|
||||
},
|
||||
"skip": [
|
||||
"*.median",
|
||||
"*.runs",
|
||||
"*.dev",
|
||||
"*.cv",
|
||||
"EscapeSequenceParser.benchmark.js.*.averageRuntime",
|
||||
"Terminal.benchmark.js.*.averageRuntime"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "es6"],
|
||||
"outDir": "../out-benchmark",
|
||||
"types": ["../../../node_modules/@types/node"],
|
||||
"moduleResolution": "node",
|
||||
"strict": false,
|
||||
"target": "es2015",
|
||||
"module": "commonjs",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"common/*": ["../../../src/common/*"],
|
||||
"browser/*": ["../../../src/browser/*"],
|
||||
"SerializeAddon": ["../src/SerializeAddon"]
|
||||
}
|
||||
},
|
||||
"include": ["../**/*", "../../../typings/xterm.d.ts", "../../../out/**/*"],
|
||||
"exclude": ["../../../**/*test.ts", "../../**/*api.ts"],
|
||||
"references": [
|
||||
{ "path": "../../../src/common" },
|
||||
{ "path": "../../../src/browser" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "xterm-addon-serialize",
|
||||
"version": "0.4.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
},
|
||||
"main": "lib/xterm-addon-serialize.js",
|
||||
"types": "typings/xterm-addon-serialize.d.ts",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "../../node_modules/.bin/tsc -p src",
|
||||
"prepackage": "npm run build",
|
||||
"package": "../../node_modules/.bin/webpack",
|
||||
"prepublishOnly": "npm run package",
|
||||
"benchmark": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json",
|
||||
"benchmark-baseline": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --baseline out-benchmark/addons/xterm-addon-serialize/benchmark/*benchmark.js",
|
||||
"benchmark-eval": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --eval out-benchmark/addons/xterm-addon-serialize/benchmark/*benchmark.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"xterm": "^4.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { openTerminal, writeSync, getBrowserType } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from 'playwright-core';
|
||||
|
||||
const APP = 'http://127.0.0.1:3000/test';
|
||||
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
|
||||
describe('SerializeAddon', () => {
|
||||
before(async function(): Promise<any> {
|
||||
const browserType = getBrowserType();
|
||||
browser = await browserType.launch({ dumpio: true,
|
||||
headless: process.argv.indexOf('--headless') !== -1
|
||||
});
|
||||
page = await (await browser.newContext()).newPage();
|
||||
await page.setViewportSize({ width, height });
|
||||
await page.goto(APP);
|
||||
await openTerminal(page, { rows: 10, cols: 10, rendererType: 'dom' });
|
||||
await page.evaluate(`
|
||||
window.serializeAddon = new SerializeAddon();
|
||||
window.term.loadAddon(window.serializeAddon);
|
||||
`);
|
||||
});
|
||||
|
||||
after(async () => await browser.close());
|
||||
beforeEach(async () => await page.evaluate(`window.term.reset()`));
|
||||
|
||||
it('empty content', async function(): Promise<any> {
|
||||
const rows = 10;
|
||||
const cols = 10;
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), '');
|
||||
});
|
||||
|
||||
it('trim last empty lines', async function(): Promise<any> {
|
||||
const cols = 10;
|
||||
const lines = [
|
||||
'',
|
||||
'',
|
||||
digitsString(cols),
|
||||
digitsString(cols),
|
||||
'',
|
||||
'',
|
||||
digitsString(cols),
|
||||
digitsString(cols),
|
||||
'',
|
||||
'',
|
||||
''
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.slice(0, 8).join('\r\n'));
|
||||
});
|
||||
|
||||
it('digits content', async function(): Promise<any> {
|
||||
const rows = 10;
|
||||
const cols = 10;
|
||||
const digitsLine = digitsString(cols);
|
||||
const lines = newArray<string>(digitsLine, rows);
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize half rows of content', async function(): Promise<any> {
|
||||
const rows = 10;
|
||||
const halfRows = rows >> 1;
|
||||
const cols = 10;
|
||||
const lines = newArray<string>((index: number) => digitsString(cols, index), rows);
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize(${halfRows});`), lines.slice(halfRows, 2 * halfRows).join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize 0 rows of content', async function(): Promise<any> {
|
||||
const rows = 10;
|
||||
const cols = 10;
|
||||
const lines = newArray<string>((index: number) => digitsString(cols, index), rows);
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize(0);`), '');
|
||||
});
|
||||
|
||||
it('serialize all rows of content with color16', async function(): Promise<any> {
|
||||
const cols = 10;
|
||||
const color16 = [
|
||||
30, 31, 32, 33, 34, 35, 36, 37, // Set foreground color
|
||||
90, 91, 92, 93, 94, 95, 96, 97,
|
||||
40, 41, 42, 43, 44, 45, 46, 47, // Set background color
|
||||
100, 101, 103, 104, 105, 106, 107
|
||||
];
|
||||
const rows = color16.length;
|
||||
const lines = newArray<string>(
|
||||
(index: number) => digitsString(cols, index, `\x1b[${color16[index % color16.length]}m`),
|
||||
rows
|
||||
);
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize all rows of content with fg/bg flags', async function(): Promise<any> {
|
||||
const cols = 10;
|
||||
const line = '+'.repeat(cols);
|
||||
const lines: string[] = [
|
||||
mkSGR(FG_P16_GREEN) + line, // Workaround: If we clear all flags a the end, serialize will use \x1b[0m to clear instead of the sepcific disable sequence
|
||||
mkSGR(INVERSE) + line,
|
||||
mkSGR(BOLD) + line,
|
||||
mkSGR(UNDERLINED) + line,
|
||||
mkSGR(BLINK) + line,
|
||||
mkSGR(INVISIBLE) + line,
|
||||
mkSGR(NO_INVERSE) + line,
|
||||
mkSGR(NO_BOLD) + line,
|
||||
mkSGR(NO_UNDERLINED) + line,
|
||||
mkSGR(NO_BLINK) + line,
|
||||
mkSGR(NO_INVISIBLE) + line
|
||||
];
|
||||
const rows = lines.length;
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize all rows of content with color256', async function(): Promise<any> {
|
||||
const rows = 32;
|
||||
const cols = 10;
|
||||
const lines = newArray<string>(
|
||||
(index: number) => digitsString(cols, index, `\x1b[38;5;${16 + index}m`),
|
||||
rows
|
||||
);
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize all rows of content with color16 and style separately', async function(): Promise<any> {
|
||||
const cols = 10;
|
||||
const line = '+'.repeat(cols);
|
||||
const lines: string[] = [
|
||||
mkSGR(FG_P16_RED) + line, // fg Red,
|
||||
mkSGR(UNDERLINED) + line, // fg Red, Underlined
|
||||
mkSGR(FG_P16_GREEN) + line, // fg Green, Underlined
|
||||
mkSGR(INVERSE) + line, // fg Green, Underlined, Inverse
|
||||
mkSGR(NO_INVERSE) + line, // fg Green, Underlined
|
||||
mkSGR(INVERSE) + line, // fg Green, Underlined, Inverse
|
||||
mkSGR(BG_P16_YELLOW) + line, // fg Green, bg Yellow, Underlined, Inverse
|
||||
mkSGR(FG_RESET) + line, // bg Yellow, Underlined, Inverse
|
||||
mkSGR(BG_RESET) + line, // Underlined, Inverse
|
||||
mkSGR(NORMAL) + line // Back to normal
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize all rows of content with color16 and style together', async function(): Promise<any> {
|
||||
const cols = 10;
|
||||
const line = '+'.repeat(cols);
|
||||
const lines: string[] = [
|
||||
mkSGR(FG_P16_RED) + line, // fg Red
|
||||
mkSGR(FG_P16_GREEN, BG_P16_YELLOW) + line, // fg Green, bg Yellow
|
||||
mkSGR(UNDERLINED, ITALIC) + line, // fg Green, bg Yellow, Underlined, Italic
|
||||
mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green, bg Yellow
|
||||
mkSGR(FG_RESET, ITALIC) + line, // bg Yellow, Italic
|
||||
mkSGR(BG_RESET) + line, // Italic
|
||||
mkSGR(NORMAL) + line, // Back to normal
|
||||
mkSGR(FG_P16_RED) + line, // fg Red
|
||||
mkSGR(FG_P16_GREEN, BG_P16_YELLOW) + line, // fg Green, bg Yellow
|
||||
mkSGR(UNDERLINED, ITALIC) + line, // fg Green, bg Yellow, Underlined, Italic
|
||||
mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green, bg Yellow
|
||||
mkSGR(FG_RESET, ITALIC) + line, // bg Yellow, Italic
|
||||
mkSGR(BG_RESET) + line // Italic
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize all rows of content with color256 and style separately', async function(): Promise<any> {
|
||||
const cols = 10;
|
||||
const line = '+'.repeat(cols);
|
||||
const lines: string[] = [
|
||||
mkSGR(FG_P256_RED) + line, // fg Red 256,
|
||||
mkSGR(UNDERLINED) + line, // fg Red 256, Underlined
|
||||
mkSGR(FG_P256_GREEN) + line, // fg Green 256, Underlined
|
||||
mkSGR(INVERSE) + line, // fg Green 256, Underlined, Inverse
|
||||
mkSGR(NO_INVERSE) + line, // fg Green 256, Underlined
|
||||
mkSGR(INVERSE) + line, // fg Green 256, Underlined, Inverse
|
||||
mkSGR(BG_P256_YELLOW) + line, // fg Green 256, bg Yellow 256, Underlined, Inverse
|
||||
mkSGR(FG_RESET) + line, // bg Yellow 256, Underlined, Inverse
|
||||
mkSGR(BG_RESET) + line, // Underlined, Inverse
|
||||
mkSGR(NORMAL) + line // Back to normal
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize all rows of content with color256 and style together', async function(): Promise<any> {
|
||||
const cols = 10;
|
||||
const line = '+'.repeat(cols);
|
||||
const lines: string[] = [
|
||||
mkSGR(FG_P256_RED) + line, // fg Red 256
|
||||
mkSGR(FG_P256_GREEN, BG_P256_YELLOW) + line, // fg Green 256, bg Yellow 256
|
||||
mkSGR(UNDERLINED, ITALIC) + line, // fg Green 256, bg Yellow 256, Underlined, Italic
|
||||
mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green 256, bg Yellow 256
|
||||
mkSGR(FG_RESET, ITALIC) + line, // bg Yellow 256, Italic
|
||||
mkSGR(BG_RESET) + line, // Italic
|
||||
mkSGR(NORMAL) + line, // Back to normal
|
||||
mkSGR(FG_P256_RED) + line, // fg Red 256
|
||||
mkSGR(FG_P256_GREEN, BG_P256_YELLOW) + line, // fg Green 256, bg Yellow 256
|
||||
mkSGR(UNDERLINED, ITALIC) + line, // fg Green 256, bg Yellow 256, Underlined, Italic
|
||||
mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green 256, bg Yellow 256
|
||||
mkSGR(FG_RESET, ITALIC) + line, // bg Yellow 256, Italic
|
||||
mkSGR(BG_RESET) + line // Italic
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize all rows of content with colorRGB and style separately', async function(): Promise<any> {
|
||||
const cols = 10;
|
||||
const line = '+'.repeat(cols);
|
||||
const lines: string[] = [
|
||||
mkSGR(FG_RGB_RED) + line, // fg Red RGB,
|
||||
mkSGR(UNDERLINED) + line, // fg Red RGB, Underlined
|
||||
mkSGR(FG_RGB_GREEN) + line, // fg Green RGB, Underlined
|
||||
mkSGR(INVERSE) + line, // fg Green RGB, Underlined, Inverse
|
||||
mkSGR(NO_INVERSE) + line, // fg Green RGB, Underlined
|
||||
mkSGR(INVERSE) + line, // fg Green RGB, Underlined, Inverse
|
||||
mkSGR(BG_RGB_YELLOW) + line, // fg Green RGB, bg Yellow RGB, Underlined, Inverse
|
||||
mkSGR(FG_RESET) + line, // bg Yellow RGB, Underlined, Inverse
|
||||
mkSGR(BG_RESET) + line, // Underlined, Inverse
|
||||
mkSGR(NORMAL) + line // Back to normal
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize all rows of content with colorRGB and style together', async function(): Promise<any> {
|
||||
const cols = 10;
|
||||
const line = '+'.repeat(cols);
|
||||
const lines: string[] = [
|
||||
mkSGR(FG_RGB_RED) + line, // fg Red RGB
|
||||
mkSGR(FG_RGB_GREEN, BG_RGB_YELLOW) + line, // fg Green RGB, bg Yellow RGB
|
||||
mkSGR(UNDERLINED, ITALIC) + line, // fg Green RGB, bg Yellow RGB, Underlined, Italic
|
||||
mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green RGB, bg Yellow RGB
|
||||
mkSGR(FG_RESET, ITALIC) + line, // bg Yellow RGB, Italic
|
||||
mkSGR(BG_RESET) + line, // Italic
|
||||
mkSGR(NORMAL) + line, // Back to normal
|
||||
mkSGR(FG_RGB_RED) + line, // fg Red RGB
|
||||
mkSGR(FG_RGB_GREEN, BG_RGB_YELLOW) + line, // fg Green RGB, bg Yellow RGB
|
||||
mkSGR(UNDERLINED, ITALIC) + line, // fg Green RGB, bg Yellow RGB, Underlined, Italic
|
||||
mkSGR(NO_UNDERLINED, NO_ITALIC) + line, // fg Green RGB, bg Yellow RGB
|
||||
mkSGR(FG_RESET, ITALIC) + line, // bg Yellow RGB, Italic
|
||||
mkSGR(BG_RESET) + line // Italic
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize tabs correctly', async () => {
|
||||
const lines = [
|
||||
'a\tb',
|
||||
'aa\tc',
|
||||
'aaa\td'
|
||||
];
|
||||
const expected = [
|
||||
'a\x1b[7Cb',
|
||||
'aa\x1b[6Cc',
|
||||
'aaa\x1b[5Cd'
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), expected.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize CJK correctly', async () => {
|
||||
const lines = [
|
||||
'中文中文',
|
||||
'12中文',
|
||||
'中文12',
|
||||
'1中文中文中' // this line is going to be wrapped at last character because it has line length of 11 (1+2*5)
|
||||
];
|
||||
const expected = [
|
||||
'中文中文',
|
||||
'12中文',
|
||||
'中文12',
|
||||
'1中文中文',
|
||||
'中'
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), expected.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize CJK Mixed with tab correctly', async () => {
|
||||
const lines = [
|
||||
'中文\t12' // CJK mixed with tab
|
||||
];
|
||||
const expected = [
|
||||
'中文\x1b[4C12'
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), expected.join('\r\n'));
|
||||
});
|
||||
});
|
||||
|
||||
function newArray<T>(initial: T | ((index: number) => T), count: number): T[] {
|
||||
const array: T[] = new Array<T>(count);
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
if (typeof initial === 'function') {
|
||||
array[i] = (<(index: number) => T>initial)(i);
|
||||
} else {
|
||||
array[i] = <T>initial;
|
||||
}
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
function digitsString(length: number, from: number = 0, sgr: string = ''): string {
|
||||
let s = sgr;
|
||||
for (let i = 0; i < length; i++) {
|
||||
s += `${(from++) % 10}`;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
function mkSGR(...seq: string[]): string {
|
||||
return `\x1b[${seq.join(';')}m`;
|
||||
}
|
||||
|
||||
const NORMAL = '0';
|
||||
|
||||
const FG_P16_RED = '31';
|
||||
const FG_P16_GREEN = '32';
|
||||
const FG_P16_YELLOW = '33';
|
||||
const FG_P256_RED = '38;5;196';
|
||||
const FG_P256_GREEN = '38;5;46';
|
||||
const FG_P256_YELLOW = '38;5;226';
|
||||
const FG_RGB_RED = '38;2;255;0;0';
|
||||
const FG_RGB_GREEN = '38;2;0;255;0';
|
||||
const FG_RGB_YELLOW = '38;2;255;255;0';
|
||||
const FG_RESET = '39';
|
||||
|
||||
|
||||
const BG_P16_RED = '41';
|
||||
const BG_P16_GREEN = '42';
|
||||
const BG_P16_YELLOW = '43';
|
||||
const BG_P256_RED = '48;5;196';
|
||||
const BG_P256_GREEN = '48;5;46';
|
||||
const BG_P256_YELLOW = '48;5;226';
|
||||
const BG_RGB_RED = '48;2;255;0;0';
|
||||
const BG_RGB_GREEN = '48;2;0;255;0';
|
||||
const BG_RGB_YELLOW = '48;2;255;255;0';
|
||||
const BG_RESET = '49';
|
||||
|
||||
const INVERSE = '7';
|
||||
const BOLD = '1';
|
||||
const UNDERLINED = '4';
|
||||
const BLINK = '5';
|
||||
const INVISIBLE = '8';
|
||||
|
||||
const NO_INVERSE = '27';
|
||||
const NO_BOLD = '22';
|
||||
const NO_UNDERLINED = '24';
|
||||
const NO_BLINK = '25';
|
||||
const NO_INVISIBLE = '28';
|
||||
|
||||
const ITALIC = '3';
|
||||
const DIM = '2';
|
||||
|
||||
const NO_ITALIC = '23';
|
||||
const NO_DIM = '22';
|
||||
@@ -0,0 +1,187 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*
|
||||
* (EXPERIMENTAL) This Addon is still under development
|
||||
*/
|
||||
|
||||
import { Terminal, ITerminalAddon, IBuffer, IBufferCell } from 'xterm';
|
||||
|
||||
function constrain(value: number, low: number, high: number): number {
|
||||
return Math.max(low, Math.min(value, high));
|
||||
}
|
||||
|
||||
// TODO: Refine this template class later
|
||||
abstract class BaseSerializeHandler {
|
||||
constructor(private _buffer: IBuffer) { }
|
||||
|
||||
public serialize(startRow: number, endRow: number): string {
|
||||
// we need two of them to flip between old and new cell
|
||||
const cell1 = this._buffer.getNullCell();
|
||||
const cell2 = this._buffer.getNullCell();
|
||||
let oldCell = cell1;
|
||||
|
||||
this._beforeSerialize(endRow - startRow);
|
||||
|
||||
for (let row = startRow; row < endRow; row++) {
|
||||
const line = this._buffer.getLine(row);
|
||||
if (line) {
|
||||
for (let col = 0; col < line.length; col++) {
|
||||
const c = line.getCell(col, oldCell === cell1 ? cell2 : cell1);
|
||||
if (!c) {
|
||||
console.warn(`Can't get cell at row=${row}, col=${col}`);
|
||||
continue;
|
||||
}
|
||||
this._nextCell(c, oldCell, row, col);
|
||||
oldCell = c;
|
||||
}
|
||||
}
|
||||
this._rowEnd(row);
|
||||
}
|
||||
|
||||
this._afterSerialize();
|
||||
|
||||
return this._serializeString();
|
||||
}
|
||||
|
||||
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
|
||||
protected _rowEnd(row: number): void { }
|
||||
protected _beforeSerialize(rows: number): void { }
|
||||
protected _afterSerialize(): void { }
|
||||
protected _serializeString(): string { return ''; }
|
||||
}
|
||||
|
||||
function equalFg(cell1: IBufferCell, cell2: IBufferCell): boolean {
|
||||
return cell1.getFgColorMode() === cell2.getFgColorMode()
|
||||
&& cell1.getFgColor() === cell2.getFgColor();
|
||||
}
|
||||
|
||||
function equalBg(cell1: IBufferCell, cell2: IBufferCell): boolean {
|
||||
return cell1.getBgColorMode() === cell2.getBgColorMode()
|
||||
&& cell1.getBgColor() === cell2.getBgColor();
|
||||
}
|
||||
|
||||
function equalFlags(cell1: IBufferCell, cell2: IBufferCell): boolean {
|
||||
return cell1.isInverse() === cell2.isInverse()
|
||||
&& cell1.isBold() === cell2.isBold()
|
||||
&& cell1.isUnderline() === cell2.isUnderline()
|
||||
&& cell1.isBlink() === cell2.isBlink()
|
||||
&& cell1.isInvisible() === cell2.isInvisible()
|
||||
&& cell1.isItalic() === cell2.isItalic()
|
||||
&& cell1.isDim() === cell2.isDim();
|
||||
}
|
||||
|
||||
class StringSerializeHandler extends BaseSerializeHandler {
|
||||
private _rowIndex: number = 0;
|
||||
private _allRows: string[] = new Array<string>();
|
||||
private _currentRow: string = '';
|
||||
private _nullCellCount: number = 0;
|
||||
|
||||
constructor(buffer: IBuffer) {
|
||||
super(buffer);
|
||||
}
|
||||
|
||||
protected _beforeSerialize(rows: number): void {
|
||||
this._allRows = new Array<string>(rows);
|
||||
}
|
||||
|
||||
protected _rowEnd(row: number): void {
|
||||
this._allRows[this._rowIndex++] = this._currentRow;
|
||||
this._currentRow = '';
|
||||
this._nullCellCount = 0;
|
||||
}
|
||||
|
||||
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
|
||||
const sgrSeq: number[] = [];
|
||||
const fgChanged = !equalFg(cell, oldCell);
|
||||
const bgChanged = !equalBg(cell, oldCell);
|
||||
const flagsChanged = !equalFlags(cell, oldCell);
|
||||
|
||||
if (fgChanged || bgChanged || flagsChanged) {
|
||||
if (cell.isAttributeDefault()) {
|
||||
this._currentRow += '\x1b[0m';
|
||||
} else {
|
||||
if (fgChanged) {
|
||||
const color = cell.getFgColor();
|
||||
if (cell.isFgRGB()) { sgrSeq.push(38, 2, (color >>> 16) & 0xFF, (color >>> 8) & 0xFF, color & 0xFF); }
|
||||
else if (cell.isFgPalette()) {
|
||||
if (color >= 16) { sgrSeq.push(38, 5, color); }
|
||||
else { sgrSeq.push(color & 8 ? 90 + (color & 7) : 30 + (color & 7)); }
|
||||
}
|
||||
else { sgrSeq.push(39); }
|
||||
}
|
||||
if (bgChanged) {
|
||||
const color = cell.getBgColor();
|
||||
if (cell.isBgRGB()) { sgrSeq.push(48, 2, (color >>> 16) & 0xFF, (color >>> 8) & 0xFF, color & 0xFF); }
|
||||
else if (cell.isBgPalette()) {
|
||||
if (color >= 16) { sgrSeq.push(48, 5, color); }
|
||||
else { sgrSeq.push(color & 8 ? 100 + (color & 7) : 40 + (color & 7)); }
|
||||
}
|
||||
else { sgrSeq.push(49); }
|
||||
}
|
||||
if (flagsChanged) {
|
||||
if (cell.isInverse() !== oldCell.isInverse()) { sgrSeq.push(cell.isInverse() ? 7 : 27); }
|
||||
if (cell.isBold() !== oldCell.isBold()) { sgrSeq.push(cell.isBold() ? 1 : 22); }
|
||||
if (cell.isUnderline() !== oldCell.isUnderline()) { sgrSeq.push(cell.isUnderline() ? 4 : 24); }
|
||||
if (cell.isBlink() !== oldCell.isBlink()) { sgrSeq.push(cell.isBlink() ? 5 : 25); }
|
||||
if (cell.isInvisible() !== oldCell.isInvisible()) { sgrSeq.push(cell.isInvisible() ? 8 : 28); }
|
||||
if (cell.isItalic() !== oldCell.isItalic()) { sgrSeq.push(cell.isItalic() ? 3 : 23); }
|
||||
if (cell.isDim() !== oldCell.isDim()) { sgrSeq.push(cell.isDim() ? 2 : 22); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sgrSeq.length) {
|
||||
this._currentRow += `\x1b[${sgrSeq.join(';')}m`;
|
||||
}
|
||||
|
||||
// Count number of null cells encountered after the last non-null cell and move the cursor
|
||||
// if a non-null cell is found (eg. \t or cursor move)
|
||||
if (cell.getChars() === '') {
|
||||
this._nullCellCount += cell.getWidth();
|
||||
} else if (this._nullCellCount > 0) {
|
||||
this._currentRow += `\x1b[${this._nullCellCount}C`;
|
||||
this._nullCellCount = 0;
|
||||
}
|
||||
|
||||
this._currentRow += cell.getChars();
|
||||
}
|
||||
|
||||
protected _serializeString(): string {
|
||||
let rowEnd = this._allRows.length;
|
||||
for (; rowEnd > 0; rowEnd--) {
|
||||
if (this._allRows[rowEnd - 1]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return this._allRows.slice(0, rowEnd).join('\r\n');
|
||||
}
|
||||
}
|
||||
|
||||
export class SerializeAddon implements ITerminalAddon {
|
||||
private _terminal: Terminal | undefined;
|
||||
|
||||
constructor() { }
|
||||
|
||||
public activate(terminal: Terminal): void {
|
||||
this._terminal = terminal;
|
||||
}
|
||||
|
||||
public serialize(rows?: number): string {
|
||||
// TODO: Add re-position cursor support
|
||||
// TODO: Add word wrap mode support
|
||||
// TODO: Add combinedData support
|
||||
if (!this._terminal) {
|
||||
throw new Error('Cannot use addon until it has been loaded');
|
||||
}
|
||||
|
||||
const maxRows = this._terminal.buffer.active.length;
|
||||
const handler = new StringSerializeHandler(this._terminal.buffer.active);
|
||||
|
||||
rows = (rows === undefined) ? maxRows : constrain(rows, 0, maxRows);
|
||||
|
||||
return handler.serialize(maxRows - rows, maxRows);
|
||||
}
|
||||
|
||||
public dispose(): void { }
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2015"
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../out",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"common/*": [
|
||||
"../../../src/common/*"
|
||||
]
|
||||
},
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../out-test/api/TestUtils"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../src/common"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
|
||||
import { Terminal, ITerminalAddon } from 'xterm';
|
||||
|
||||
declare module 'xterm-addon-serialize' {
|
||||
/**
|
||||
* An xterm.js addon that enables web links.
|
||||
*/
|
||||
export class SerializeAddon implements ITerminalAddon {
|
||||
|
||||
constructor();
|
||||
|
||||
/**
|
||||
* Activates the addon
|
||||
* @param terminal The terminal the addon is being loaded in.
|
||||
*/
|
||||
public activate(terminal: Terminal): void;
|
||||
|
||||
/**
|
||||
* Serializes terminal rows into a string that can be written back to the terminal
|
||||
* to restore the state. The cursor will also be positioned to the correct cell.
|
||||
* When restoring a terminal it is best to do before `Terminal.open` is called
|
||||
* to avoid wasting CPU cycles rendering incomplete frames.
|
||||
* @param rows The number of rows to serialize, starting from the bottom of the
|
||||
* terminal. This defaults to the number of rows in the viewport.
|
||||
*/
|
||||
public serialize(rows?: number): string;
|
||||
|
||||
/**
|
||||
* Disposes the addon.
|
||||
*/
|
||||
public dispose(): void;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const addonName = 'SerializeAddon';
|
||||
const mainFile = 'xterm-addon-serialize.js';
|
||||
|
||||
module.exports = {
|
||||
entry: `./out/${addonName}.js`,
|
||||
devtool: 'source-map',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: ["source-map-loader"],
|
||||
enforce: "pre",
|
||||
exclude: /node_modules/
|
||||
}
|
||||
]
|
||||
},
|
||||
output: {
|
||||
filename: mainFile,
|
||||
path: path.resolve('./lib'),
|
||||
library: addonName,
|
||||
libraryTarget: 'umd'
|
||||
},
|
||||
mode: 'production'
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
lib
|
||||
node_modules
|
||||
@@ -0,0 +1,5 @@
|
||||
**/*.api.js
|
||||
**/*.api.ts
|
||||
tsconfig.json
|
||||
.yarnrc
|
||||
webpack.config.js
|
||||
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2019, 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,23 @@
|
||||
## xterm-addon-unicode11
|
||||
|
||||
An addon providing Unicode version 11 rules for xterm.js.
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
npm install --save xterm-addon-unicode11
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { Terminal } from 'xterm';
|
||||
import { Unicode11Addon } from 'xterm-addon-unicode11';
|
||||
|
||||
const terminal = new Terminal();
|
||||
const unicode11Addon = new Unicode11Addon();
|
||||
terminal.loadAddon(unicode11Addon);
|
||||
|
||||
// activate the new version
|
||||
terminal.unicode.activeVersion = '11';
|
||||
```
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "xterm-addon-unicode11",
|
||||
"version": "0.2.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
},
|
||||
"main": "lib/xterm-addon-unicode11.js",
|
||||
"types": "typings/xterm-addon-unicode11.d.ts",
|
||||
"repository": "https://github.com/xtermjs/xterm.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "../../node_modules/.bin/tsc -p src",
|
||||
"prepackage": "npm run build",
|
||||
"package": "../../node_modules/.bin/webpack",
|
||||
"prepublishOnly": "npm run package"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"xterm": "^4.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { openTerminal, getBrowserType } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from 'playwright-core';
|
||||
|
||||
const APP = 'http://127.0.0.1:3000/test';
|
||||
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
|
||||
describe('Unicode11Addon', () => {
|
||||
before(async function(): Promise<any> {
|
||||
const browserType = getBrowserType();
|
||||
browser = await browserType.launch({ dumpio: true,
|
||||
headless: process.argv.indexOf('--headless') !== -1
|
||||
});
|
||||
page = await (await browser.newContext()).newPage();
|
||||
await page.setViewportSize({ width, height });
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
beforeEach(async function(): Promise<any> {
|
||||
await page.goto(APP);
|
||||
await openTerminal(page);
|
||||
});
|
||||
|
||||
it('wcwidth V11 emoji test', async () => {
|
||||
await page.evaluate(`
|
||||
window.unicode11 = new Unicode11Addon();
|
||||
window.term.loadAddon(window.unicode11);
|
||||
`);
|
||||
// should have loaded '11'
|
||||
assert.deepEqual(await page.evaluate(`window.term.unicode.versions`), ['6', '11']);
|
||||
// switch should not throw
|
||||
await page.evaluate(`window.term.unicode.activeVersion = '11';`);
|
||||
assert.deepEqual(await page.evaluate(`window.term.unicode.activeVersion`), '11');
|
||||
// v6: 10, V11: 20
|
||||
assert.deepEqual(await page.evaluate(`window.term._core.unicodeService.getStringCellWidth('🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣')`), 20);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*
|
||||
* UnicodeVersionProvider for V11.
|
||||
*/
|
||||
|
||||
import { Terminal, ITerminalAddon } from 'xterm';
|
||||
import { UnicodeV11 } from './UnicodeV11';
|
||||
|
||||
export class Unicode11Addon implements ITerminalAddon {
|
||||
public activate(terminal: Terminal): void {
|
||||
terminal.unicode.register(new UnicodeV11());
|
||||
}
|
||||
public dispose(): void { }
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IUnicodeVersionProvider } from 'xterm';
|
||||
import { fill } from 'common/TypedArrayUtils';
|
||||
|
||||
type CharWidth = 0 | 1 | 2;
|
||||
|
||||
const BMP_COMBINING = [
|
||||
[0x0300, 0x036F], [0x0483, 0x0489], [0x0591, 0x05BD],
|
||||
[0x05BF, 0x05BF], [0x05C1, 0x05C2], [0x05C4, 0x05C5],
|
||||
[0x05C7, 0x05C7], [0x0600, 0x0605], [0x0610, 0x061A],
|
||||
[0x061C, 0x061C], [0x064B, 0x065F], [0x0670, 0x0670],
|
||||
[0x06D6, 0x06DD], [0x06DF, 0x06E4], [0x06E7, 0x06E8],
|
||||
[0x06EA, 0x06ED], [0x070F, 0x070F], [0x0711, 0x0711],
|
||||
[0x0730, 0x074A], [0x07A6, 0x07B0], [0x07EB, 0x07F3],
|
||||
[0x07FD, 0x07FD], [0x0816, 0x0819], [0x081B, 0x0823],
|
||||
[0x0825, 0x0827], [0x0829, 0x082D], [0x0859, 0x085B],
|
||||
[0x08D3, 0x0902], [0x093A, 0x093A], [0x093C, 0x093C],
|
||||
[0x0941, 0x0948], [0x094D, 0x094D], [0x0951, 0x0957],
|
||||
[0x0962, 0x0963], [0x0981, 0x0981], [0x09BC, 0x09BC],
|
||||
[0x09C1, 0x09C4], [0x09CD, 0x09CD], [0x09E2, 0x09E3],
|
||||
[0x09FE, 0x09FE], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C],
|
||||
[0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D],
|
||||
[0x0A51, 0x0A51], [0x0A70, 0x0A71], [0x0A75, 0x0A75],
|
||||
[0x0A81, 0x0A82], [0x0ABC, 0x0ABC], [0x0AC1, 0x0AC5],
|
||||
[0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD], [0x0AE2, 0x0AE3],
|
||||
[0x0AFA, 0x0AFF], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C],
|
||||
[0x0B3F, 0x0B3F], [0x0B41, 0x0B44], [0x0B4D, 0x0B4D],
|
||||
[0x0B56, 0x0B56], [0x0B62, 0x0B63], [0x0B82, 0x0B82],
|
||||
[0x0BC0, 0x0BC0], [0x0BCD, 0x0BCD], [0x0C00, 0x0C00],
|
||||
[0x0C04, 0x0C04], [0x0C3E, 0x0C40], [0x0C46, 0x0C48],
|
||||
[0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0C62, 0x0C63],
|
||||
[0x0C81, 0x0C81], [0x0CBC, 0x0CBC], [0x0CBF, 0x0CBF],
|
||||
[0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD], [0x0CE2, 0x0CE3],
|
||||
[0x0D00, 0x0D01], [0x0D3B, 0x0D3C], [0x0D41, 0x0D44],
|
||||
[0x0D4D, 0x0D4D], [0x0D62, 0x0D63], [0x0DCA, 0x0DCA],
|
||||
[0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6], [0x0E31, 0x0E31],
|
||||
[0x0E34, 0x0E3A], [0x0E47, 0x0E4E], [0x0EB1, 0x0EB1],
|
||||
[0x0EB4, 0x0EBC], [0x0EC8, 0x0ECD], [0x0F18, 0x0F19],
|
||||
[0x0F35, 0x0F35], [0x0F37, 0x0F37], [0x0F39, 0x0F39],
|
||||
[0x0F71, 0x0F7E], [0x0F80, 0x0F84], [0x0F86, 0x0F87],
|
||||
[0x0F8D, 0x0F97], [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6],
|
||||
[0x102D, 0x1030], [0x1032, 0x1037], [0x1039, 0x103A],
|
||||
[0x103D, 0x103E], [0x1058, 0x1059], [0x105E, 0x1060],
|
||||
[0x1071, 0x1074], [0x1082, 0x1082], [0x1085, 0x1086],
|
||||
[0x108D, 0x108D], [0x109D, 0x109D], [0x1160, 0x11FF],
|
||||
[0x135D, 0x135F], [0x1712, 0x1714], [0x1732, 0x1734],
|
||||
[0x1752, 0x1753], [0x1772, 0x1773], [0x17B4, 0x17B5],
|
||||
[0x17B7, 0x17BD], [0x17C6, 0x17C6], [0x17C9, 0x17D3],
|
||||
[0x17DD, 0x17DD], [0x180B, 0x180E], [0x1885, 0x1886],
|
||||
[0x18A9, 0x18A9], [0x1920, 0x1922], [0x1927, 0x1928],
|
||||
[0x1932, 0x1932], [0x1939, 0x193B], [0x1A17, 0x1A18],
|
||||
[0x1A1B, 0x1A1B], [0x1A56, 0x1A56], [0x1A58, 0x1A5E],
|
||||
[0x1A60, 0x1A60], [0x1A62, 0x1A62], [0x1A65, 0x1A6C],
|
||||
[0x1A73, 0x1A7C], [0x1A7F, 0x1A7F], [0x1AB0, 0x1ABE],
|
||||
[0x1B00, 0x1B03], [0x1B34, 0x1B34], [0x1B36, 0x1B3A],
|
||||
[0x1B3C, 0x1B3C], [0x1B42, 0x1B42], [0x1B6B, 0x1B73],
|
||||
[0x1B80, 0x1B81], [0x1BA2, 0x1BA5], [0x1BA8, 0x1BA9],
|
||||
[0x1BAB, 0x1BAD], [0x1BE6, 0x1BE6], [0x1BE8, 0x1BE9],
|
||||
[0x1BED, 0x1BED], [0x1BEF, 0x1BF1], [0x1C2C, 0x1C33],
|
||||
[0x1C36, 0x1C37], [0x1CD0, 0x1CD2], [0x1CD4, 0x1CE0],
|
||||
[0x1CE2, 0x1CE8], [0x1CED, 0x1CED], [0x1CF4, 0x1CF4],
|
||||
[0x1CF8, 0x1CF9], [0x1DC0, 0x1DF9], [0x1DFB, 0x1DFF],
|
||||
[0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2064],
|
||||
[0x2066, 0x206F], [0x20D0, 0x20F0], [0x2CEF, 0x2CF1],
|
||||
[0x2D7F, 0x2D7F], [0x2DE0, 0x2DFF], [0x302A, 0x302D],
|
||||
[0x3099, 0x309A], [0xA66F, 0xA672], [0xA674, 0xA67D],
|
||||
[0xA69E, 0xA69F], [0xA6F0, 0xA6F1], [0xA802, 0xA802],
|
||||
[0xA806, 0xA806], [0xA80B, 0xA80B], [0xA825, 0xA826],
|
||||
[0xA8C4, 0xA8C5], [0xA8E0, 0xA8F1], [0xA8FF, 0xA8FF],
|
||||
[0xA926, 0xA92D], [0xA947, 0xA951], [0xA980, 0xA982],
|
||||
[0xA9B3, 0xA9B3], [0xA9B6, 0xA9B9], [0xA9BC, 0xA9BD],
|
||||
[0xA9E5, 0xA9E5], [0xAA29, 0xAA2E], [0xAA31, 0xAA32],
|
||||
[0xAA35, 0xAA36], [0xAA43, 0xAA43], [0xAA4C, 0xAA4C],
|
||||
[0xAA7C, 0xAA7C], [0xAAB0, 0xAAB0], [0xAAB2, 0xAAB4],
|
||||
[0xAAB7, 0xAAB8], [0xAABE, 0xAABF], [0xAAC1, 0xAAC1],
|
||||
[0xAAEC, 0xAAED], [0xAAF6, 0xAAF6], [0xABE5, 0xABE5],
|
||||
[0xABE8, 0xABE8], [0xABED, 0xABED], [0xFB1E, 0xFB1E],
|
||||
[0xFE00, 0xFE0F], [0xFE20, 0xFE2F], [0xFEFF, 0xFEFF],
|
||||
[0xFFF9, 0xFFFB]
|
||||
];
|
||||
const HIGH_COMBINING = [
|
||||
[0x101FD, 0x101FD], [0x102E0, 0x102E0],
|
||||
[0x10376, 0x1037A], [0x10A01, 0x10A03], [0x10A05, 0x10A06],
|
||||
[0x10A0C, 0x10A0F], [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F],
|
||||
[0x10AE5, 0x10AE6], [0x10D24, 0x10D27], [0x10F46, 0x10F50],
|
||||
[0x11001, 0x11001], [0x11038, 0x11046], [0x1107F, 0x11081],
|
||||
[0x110B3, 0x110B6], [0x110B9, 0x110BA], [0x110BD, 0x110BD],
|
||||
[0x110CD, 0x110CD], [0x11100, 0x11102], [0x11127, 0x1112B],
|
||||
[0x1112D, 0x11134], [0x11173, 0x11173], [0x11180, 0x11181],
|
||||
[0x111B6, 0x111BE], [0x111C9, 0x111CC], [0x1122F, 0x11231],
|
||||
[0x11234, 0x11234], [0x11236, 0x11237], [0x1123E, 0x1123E],
|
||||
[0x112DF, 0x112DF], [0x112E3, 0x112EA], [0x11300, 0x11301],
|
||||
[0x1133B, 0x1133C], [0x11340, 0x11340], [0x11366, 0x1136C],
|
||||
[0x11370, 0x11374], [0x11438, 0x1143F], [0x11442, 0x11444],
|
||||
[0x11446, 0x11446], [0x1145E, 0x1145E], [0x114B3, 0x114B8],
|
||||
[0x114BA, 0x114BA], [0x114BF, 0x114C0], [0x114C2, 0x114C3],
|
||||
[0x115B2, 0x115B5], [0x115BC, 0x115BD], [0x115BF, 0x115C0],
|
||||
[0x115DC, 0x115DD], [0x11633, 0x1163A], [0x1163D, 0x1163D],
|
||||
[0x1163F, 0x11640], [0x116AB, 0x116AB], [0x116AD, 0x116AD],
|
||||
[0x116B0, 0x116B5], [0x116B7, 0x116B7], [0x1171D, 0x1171F],
|
||||
[0x11722, 0x11725], [0x11727, 0x1172B], [0x1182F, 0x11837],
|
||||
[0x11839, 0x1183A], [0x119D4, 0x119D7], [0x119DA, 0x119DB],
|
||||
[0x119E0, 0x119E0], [0x11A01, 0x11A0A], [0x11A33, 0x11A38],
|
||||
[0x11A3B, 0x11A3E], [0x11A47, 0x11A47], [0x11A51, 0x11A56],
|
||||
[0x11A59, 0x11A5B], [0x11A8A, 0x11A96], [0x11A98, 0x11A99],
|
||||
[0x11C30, 0x11C36], [0x11C38, 0x11C3D], [0x11C3F, 0x11C3F],
|
||||
[0x11C92, 0x11CA7], [0x11CAA, 0x11CB0], [0x11CB2, 0x11CB3],
|
||||
[0x11CB5, 0x11CB6], [0x11D31, 0x11D36], [0x11D3A, 0x11D3A],
|
||||
[0x11D3C, 0x11D3D], [0x11D3F, 0x11D45], [0x11D47, 0x11D47],
|
||||
[0x11D90, 0x11D91], [0x11D95, 0x11D95], [0x11D97, 0x11D97],
|
||||
[0x11EF3, 0x11EF4], [0x13430, 0x13438], [0x16AF0, 0x16AF4],
|
||||
[0x16B30, 0x16B36], [0x16F4F, 0x16F4F], [0x16F8F, 0x16F92],
|
||||
[0x1BC9D, 0x1BC9E], [0x1BCA0, 0x1BCA3], [0x1D167, 0x1D169],
|
||||
[0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD],
|
||||
[0x1D242, 0x1D244], [0x1DA00, 0x1DA36], [0x1DA3B, 0x1DA6C],
|
||||
[0x1DA75, 0x1DA75], [0x1DA84, 0x1DA84], [0x1DA9B, 0x1DA9F],
|
||||
[0x1DAA1, 0x1DAAF], [0x1E000, 0x1E006], [0x1E008, 0x1E018],
|
||||
[0x1E01B, 0x1E021], [0x1E023, 0x1E024], [0x1E026, 0x1E02A],
|
||||
[0x1E130, 0x1E136], [0x1E2EC, 0x1E2EF], [0x1E8D0, 0x1E8D6],
|
||||
[0x1E944, 0x1E94A], [0xE0001, 0xE0001], [0xE0020, 0xE007F],
|
||||
[0xE0100, 0xE01EF]
|
||||
];
|
||||
const BMP_WIDE = [
|
||||
[0x1100, 0x115F], [0x231A, 0x231B], [0x2329, 0x232A],
|
||||
[0x23E9, 0x23EC], [0x23F0, 0x23F0], [0x23F3, 0x23F3],
|
||||
[0x25FD, 0x25FE], [0x2614, 0x2615], [0x2648, 0x2653],
|
||||
[0x267F, 0x267F], [0x2693, 0x2693], [0x26A1, 0x26A1],
|
||||
[0x26AA, 0x26AB], [0x26BD, 0x26BE], [0x26C4, 0x26C5],
|
||||
[0x26CE, 0x26CE], [0x26D4, 0x26D4], [0x26EA, 0x26EA],
|
||||
[0x26F2, 0x26F3], [0x26F5, 0x26F5], [0x26FA, 0x26FA],
|
||||
[0x26FD, 0x26FD], [0x2705, 0x2705], [0x270A, 0x270B],
|
||||
[0x2728, 0x2728], [0x274C, 0x274C], [0x274E, 0x274E],
|
||||
[0x2753, 0x2755], [0x2757, 0x2757], [0x2795, 0x2797],
|
||||
[0x27B0, 0x27B0], [0x27BF, 0x27BF], [0x2B1B, 0x2B1C],
|
||||
[0x2B50, 0x2B50], [0x2B55, 0x2B55], [0x2E80, 0x2E99],
|
||||
[0x2E9B, 0x2EF3], [0x2F00, 0x2FD5], [0x2FF0, 0x2FFB],
|
||||
[0x3000, 0x3029], [0x302E, 0x303E], [0x3041, 0x3096],
|
||||
[0x309B, 0x30FF], [0x3105, 0x312F], [0x3131, 0x318E],
|
||||
[0x3190, 0x31BA], [0x31C0, 0x31E3], [0x31F0, 0x321E],
|
||||
[0x3220, 0x3247], [0x3250, 0x4DBF], [0x4E00, 0xA48C],
|
||||
[0xA490, 0xA4C6], [0xA960, 0xA97C], [0xAC00, 0xD7A3],
|
||||
[0xF900, 0xFAFF], [0xFE10, 0xFE19], [0xFE30, 0xFE52],
|
||||
[0xFE54, 0xFE66], [0xFE68, 0xFE6B], [0xFF01, 0xFF60],
|
||||
[0xFFE0, 0xFFE6]
|
||||
];
|
||||
const HIGH_WIDE = [
|
||||
[0x16FE0, 0x16FE3], [0x17000, 0x187F7],
|
||||
[0x18800, 0x18AF2], [0x1B000, 0x1B11E], [0x1B150, 0x1B152],
|
||||
[0x1B164, 0x1B167], [0x1B170, 0x1B2FB], [0x1F004, 0x1F004],
|
||||
[0x1F0CF, 0x1F0CF], [0x1F18E, 0x1F18E], [0x1F191, 0x1F19A],
|
||||
[0x1F200, 0x1F202], [0x1F210, 0x1F23B], [0x1F240, 0x1F248],
|
||||
[0x1F250, 0x1F251], [0x1F260, 0x1F265], [0x1F300, 0x1F320],
|
||||
[0x1F32D, 0x1F335], [0x1F337, 0x1F37C], [0x1F37E, 0x1F393],
|
||||
[0x1F3A0, 0x1F3CA], [0x1F3CF, 0x1F3D3], [0x1F3E0, 0x1F3F0],
|
||||
[0x1F3F4, 0x1F3F4], [0x1F3F8, 0x1F43E], [0x1F440, 0x1F440],
|
||||
[0x1F442, 0x1F4FC], [0x1F4FF, 0x1F53D], [0x1F54B, 0x1F54E],
|
||||
[0x1F550, 0x1F567], [0x1F57A, 0x1F57A], [0x1F595, 0x1F596],
|
||||
[0x1F5A4, 0x1F5A4], [0x1F5FB, 0x1F64F], [0x1F680, 0x1F6C5],
|
||||
[0x1F6CC, 0x1F6CC], [0x1F6D0, 0x1F6D2], [0x1F6D5, 0x1F6D5],
|
||||
[0x1F6EB, 0x1F6EC], [0x1F6F4, 0x1F6FA], [0x1F7E0, 0x1F7EB],
|
||||
[0x1F90D, 0x1F971], [0x1F973, 0x1F976], [0x1F97A, 0x1F9A2],
|
||||
[0x1F9A5, 0x1F9AA], [0x1F9AE, 0x1F9CA], [0x1F9CD, 0x1F9FF],
|
||||
[0x1FA70, 0x1FA73], [0x1FA78, 0x1FA7A], [0x1FA80, 0x1FA82],
|
||||
[0x1FA90, 0x1FA95], [0x20000, 0x2FFFD], [0x30000, 0x3FFFD]
|
||||
];
|
||||
|
||||
// BMP lookup table, lazy initialized during first addon loading
|
||||
let table: Uint8Array;
|
||||
|
||||
function bisearch(ucs: number, data: number[][]): boolean {
|
||||
let min = 0;
|
||||
let max = data.length - 1;
|
||||
let mid;
|
||||
if (ucs < data[0][0] || ucs > data[max][1]) {
|
||||
return false;
|
||||
}
|
||||
while (max >= min) {
|
||||
mid = (min + max) >> 1;
|
||||
if (ucs > data[mid][1]) {
|
||||
min = mid + 1;
|
||||
} else if (ucs < data[mid][0]) {
|
||||
max = mid - 1;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
export class UnicodeV11 implements IUnicodeVersionProvider {
|
||||
public readonly version = '11';
|
||||
|
||||
constructor() {
|
||||
if (!table) {
|
||||
table = new Uint8Array(65536);
|
||||
fill(table, 1);
|
||||
table[0] = 0;
|
||||
fill(table, 0, 1, 32);
|
||||
fill(table, 0, 0x7f, 0xa0);
|
||||
for (let r = 0; r < BMP_COMBINING.length; ++r) {
|
||||
fill(table, 0, BMP_COMBINING[r][0], BMP_COMBINING[r][1] + 1);
|
||||
}
|
||||
for (let r = 0; r < BMP_WIDE.length; ++r) {
|
||||
fill(table, 2, BMP_WIDE[r][0], BMP_WIDE[r][1] + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public wcwidth(num: number): CharWidth {
|
||||
if (num < 32) return 0;
|
||||
if (num < 127) return 1;
|
||||
if (num < 65536) return table[num] as CharWidth;
|
||||
if (bisearch(num, HIGH_COMBINING)) return 0;
|
||||
if (bisearch(num, HIGH_WIDE)) return 2;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2015",
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2015"
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../out",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"strict": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"common/*": [
|
||||
"../../../src/common/*"
|
||||
]
|
||||
},
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../out-test/api/TestUtils"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../src/common"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, ITerminalAddon } from 'xterm';
|
||||
|
||||
declare module 'xterm-addon-unicode11' {
|
||||
export class Unicode11Addon implements ITerminalAddon {
|
||||
constructor();
|
||||
public activate(terminal: Terminal): void;
|
||||
public dispose(): void;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const addonName = 'Unicode11Addon';
|
||||
const mainFile = 'xterm-addon-unicode11.js';
|
||||
|
||||
module.exports = {
|
||||
entry: `./out/${addonName}.js`,
|
||||
devtool: 'source-map',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: ["source-map-loader"],
|
||||
enforce: "pre",
|
||||
exclude: /node_modules/
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
modules: ['./node_modules'],
|
||||
extensions: [ '.js' ],
|
||||
alias: {
|
||||
common: path.resolve('../../out/common')
|
||||
}
|
||||
},
|
||||
output: {
|
||||
filename: mainFile,
|
||||
path: path.resolve('./lib'),
|
||||
library: addonName,
|
||||
libraryTarget: 'umd'
|
||||
},
|
||||
mode: 'production'
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm-addon-web-links",
|
||||
"version": "0.2.1",
|
||||
"version": "0.4.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ILinkProvider, IBufferCellPosition, ILink, Terminal } from 'xterm';
|
||||
|
||||
export class WebLinkProvider implements ILinkProvider {
|
||||
|
||||
constructor(
|
||||
private readonly _terminal: Terminal,
|
||||
private readonly _regex: RegExp,
|
||||
private readonly _handler: (event: MouseEvent, uri: string) => void
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
public provideLinks(y: number, callback: (links: ILink[] | undefined) => void): void {
|
||||
callback(LinkComputer.computeLink(y, this._regex, this._terminal, this._handler));
|
||||
}
|
||||
}
|
||||
|
||||
export class LinkComputer {
|
||||
public static computeLink(y: number, regex: RegExp, terminal: Terminal, handler: (event: MouseEvent, uri: string) => void): ILink[] {
|
||||
const rex = new RegExp(regex.source, (regex.flags || '') + 'g');
|
||||
|
||||
const [line, startLineIndex] = LinkComputer._translateBufferLineToStringWithWrap(y - 1, false, terminal);
|
||||
|
||||
let match;
|
||||
let stringIndex = -1;
|
||||
const result: ILink[] = [];
|
||||
|
||||
while ((match = rex.exec(line)) !== null) {
|
||||
const text = match[1];
|
||||
if (!text) {
|
||||
// something matched but does not comply with the given matchIndex
|
||||
// since this is most likely a bug the regex itself we simply do nothing here
|
||||
console.log('match found without corresponding matchIndex');
|
||||
break;
|
||||
}
|
||||
|
||||
// Get index, match.index is for the outer match which includes negated chars
|
||||
// therefore we cannot use match.index directly, instead we search the position
|
||||
// of the match group in text again
|
||||
// also correct regex and string search offsets for the next loop run
|
||||
stringIndex = line.indexOf(text, stringIndex + 1);
|
||||
rex.lastIndex = stringIndex + text.length;
|
||||
if (stringIndex < 0) {
|
||||
// invalid stringIndex (should not have happened)
|
||||
break;
|
||||
}
|
||||
|
||||
let endX = stringIndex + text.length;
|
||||
let endY = startLineIndex + 1;
|
||||
|
||||
while (endX > terminal.cols) {
|
||||
endX -= terminal.cols;
|
||||
endY++;
|
||||
}
|
||||
|
||||
const range = {
|
||||
start: {
|
||||
x: stringIndex + 1,
|
||||
y: startLineIndex + 1
|
||||
},
|
||||
end: {
|
||||
x: endX,
|
||||
y: endY
|
||||
}
|
||||
};
|
||||
|
||||
result.push({ range, text, activate: handler });
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entire line for the buffer line
|
||||
* @param line The line being translated.
|
||||
* @param trimRight Whether to trim whitespace to the right.
|
||||
* @param terminal The terminal
|
||||
*/
|
||||
private static _translateBufferLineToStringWithWrap(lineIndex: number, trimRight: boolean, terminal: Terminal): [string, number] {
|
||||
let lineString = '';
|
||||
let lineWrapsToNext: boolean;
|
||||
let prevLinesToWrap: boolean;
|
||||
|
||||
do {
|
||||
const line = terminal.buffer.active.getLine(lineIndex);
|
||||
if (!line) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (line.isWrapped) {
|
||||
lineIndex--;
|
||||
}
|
||||
|
||||
prevLinesToWrap = line.isWrapped;
|
||||
} while (prevLinesToWrap);
|
||||
|
||||
const startLineIndex = lineIndex;
|
||||
|
||||
do {
|
||||
const nextLine = terminal.buffer.active.getLine(lineIndex + 1);
|
||||
lineWrapsToNext = nextLine ? nextLine.isWrapped : false;
|
||||
const line = terminal.buffer.active.getLine(lineIndex);
|
||||
if (!line) {
|
||||
break;
|
||||
}
|
||||
lineString += line.translateToString(!lineWrapsToNext && trimRight).substring(0, terminal.cols);
|
||||
lineIndex++;
|
||||
} while (lineWrapsToNext);
|
||||
|
||||
return [lineString, startLineIndex];
|
||||
}
|
||||
}
|
||||
@@ -3,55 +3,45 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import * as puppeteer from 'puppeteer';
|
||||
import { assert } from 'chai';
|
||||
import { ITerminalOptions } from 'xterm';
|
||||
import { openTerminal, pollFor, writeSync, getBrowserType } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from 'playwright-core';
|
||||
|
||||
const APP = 'http://127.0.0.1:3000/test';
|
||||
|
||||
let browser: puppeteer.Browser;
|
||||
let page: puppeteer.Page;
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
|
||||
describe('WebLinksAddon', () => {
|
||||
before(async function(): Promise<any> {
|
||||
this.timeout(10000);
|
||||
browser = await puppeteer.launch({
|
||||
headless: process.argv.indexOf('--headless') !== -1,
|
||||
args: [`--window-size=${width},${height}`, `--no-sandbox`]
|
||||
const browserType = getBrowserType();
|
||||
browser = await browserType.launch({ dumpio: true,
|
||||
headless: process.argv.indexOf('--headless') !== -1
|
||||
});
|
||||
page = (await browser.pages())[0];
|
||||
await page.setViewport({ width, height });
|
||||
page = await (await browser.newContext()).newPage();
|
||||
await page.setViewportSize({ width, height });
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
beforeEach(async function(): Promise<any> {
|
||||
this.timeout(5000);
|
||||
await page.goto(APP);
|
||||
});
|
||||
after(async () => await browser.close());
|
||||
beforeEach(async () => await page.goto(APP));
|
||||
|
||||
it('.com', async function(): Promise<any> {
|
||||
this.timeout(20000);
|
||||
await testHostName('foo.com');
|
||||
});
|
||||
|
||||
it('.com.au', async function(): Promise<any> {
|
||||
this.timeout(20000);
|
||||
await testHostName('foo.com.au');
|
||||
});
|
||||
|
||||
it('.io', async function(): Promise<any> {
|
||||
this.timeout(20000);
|
||||
await testHostName('foo.io');
|
||||
});
|
||||
});
|
||||
|
||||
async function testHostName(hostname: string): Promise<void> {
|
||||
await openTerminal({ rendererType: 'dom' });
|
||||
await openTerminal(page, { rendererType: 'dom', cols: 40 });
|
||||
await page.evaluate(`window.term.loadAddon(new window.WebLinksAddon())`);
|
||||
const data = ` http://${hostname} \\r\\n` +
|
||||
` http://${hostname}/a~b#c~d?e~f \\r\\n` +
|
||||
@@ -70,16 +60,6 @@ async function testHostName(hostname: string): Promise<void> {
|
||||
await pollForLinkAtCell(1, 7, `http://${hostname}/subpath/+/id`);
|
||||
}
|
||||
|
||||
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
|
||||
await page.evaluate(`window.term = new Terminal(${JSON.stringify(options)})`);
|
||||
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
|
||||
if (options.rendererType === 'dom') {
|
||||
await page.waitForSelector('.xterm-rows');
|
||||
} else {
|
||||
await page.waitForSelector('.xterm-text-layer');
|
||||
}
|
||||
}
|
||||
|
||||
async function pollForLinkAtCell(col: number, row: number, value: string): Promise<void> {
|
||||
const rowSelector = `.xterm-rows > :nth-child(${row})`;
|
||||
// Ensure the hover element exists before trying to hover it
|
||||
@@ -87,23 +67,3 @@ async function pollForLinkAtCell(col: number, row: number, value: string): Promi
|
||||
await pollFor(page, `document.querySelectorAll('${rowSelector} > span[style]').length >= ${value.length}`, true, async () => page.hover(`${rowSelector} > :nth-child(${col})`));
|
||||
assert.equal(await page.evaluate(`Array.prototype.reduce.call(document.querySelectorAll('${rowSelector} > span[style]'), (a, b) => a + b.textContent, '');`), value);
|
||||
}
|
||||
|
||||
async function pollFor(page: puppeteer.Page, fn: string, val: any, preFn?: () => Promise<void>): Promise<void> {
|
||||
if (preFn) {
|
||||
await preFn();
|
||||
}
|
||||
const result = await page.evaluate(fn);
|
||||
if (result !== val) {
|
||||
return new Promise<void>(r => {
|
||||
setTimeout(() => r(pollFor(page, fn, val, preFn)), 10);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function writeSync(page: puppeteer.Page, data: string): Promise<void> {
|
||||
await page.evaluate(`
|
||||
window.ready = false;
|
||||
window.term.write('${data}', () => window.ready = true);
|
||||
`);
|
||||
await pollFor(page, 'window.ready', true);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, ILinkMatcherOptions, ITerminalAddon } from 'xterm';
|
||||
import { Terminal, ILinkMatcherOptions, ITerminalAddon, IDisposable } from 'xterm';
|
||||
import { WebLinkProvider } from './WebLinkProvider';
|
||||
|
||||
const protocolClause = '(https?:\\/\\/)';
|
||||
const domainCharacterSet = '[\\da-z\\.-]+';
|
||||
@@ -14,7 +15,7 @@ const ipClause = '((\\d{1,3}\\.){3}\\d{1,3})';
|
||||
const localHostClause = '(localhost)';
|
||||
const portClause = '(:\\d{1,5})';
|
||||
const hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';
|
||||
const pathCharacterSet = '(\\/[\\/\\w\\.\\-%~:+]*)*([^:"\'\\s])';
|
||||
const pathCharacterSet = '(\\/[\\/\\w\\.\\-%~:+@]*)*([^:"\'\\s])';
|
||||
const pathClause = '(' + pathCharacterSet + ')?';
|
||||
const queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*';
|
||||
const queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?';
|
||||
@@ -26,28 +27,44 @@ const end = ')($|' + negatedPathCharacterSet + ')';
|
||||
const strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);
|
||||
|
||||
function handleLink(event: MouseEvent, uri: string): void {
|
||||
window.open(uri, '_blank');
|
||||
const newWindow = window.open();
|
||||
if (newWindow) {
|
||||
newWindow.opener = null;
|
||||
newWindow.location.href = uri;
|
||||
} else {
|
||||
console.warn('Opening link blocked as opener could not be cleared');
|
||||
}
|
||||
}
|
||||
|
||||
export class WebLinksAddon implements ITerminalAddon {
|
||||
private _linkMatcherId: number | undefined;
|
||||
private _terminal: Terminal | undefined;
|
||||
private _linkProvider: IDisposable | undefined;
|
||||
|
||||
constructor(
|
||||
private _handler: (event: MouseEvent, uri: string) => void = handleLink,
|
||||
private _options: ILinkMatcherOptions = {}
|
||||
private _options: ILinkMatcherOptions = {},
|
||||
private _useLinkProvider: boolean = false
|
||||
) {
|
||||
this._options.matchIndex = 1;
|
||||
}
|
||||
|
||||
public activate(terminal: Terminal): void {
|
||||
this._terminal = terminal;
|
||||
this._linkMatcherId = this._terminal.registerLinkMatcher(strictUrlRegex, this._handler, this._options);
|
||||
|
||||
if (this._useLinkProvider && 'registerLinkProvider' in this._terminal) {
|
||||
this._linkProvider = this._terminal.registerLinkProvider(new WebLinkProvider(this._terminal, strictUrlRegex, this._handler));
|
||||
} else {
|
||||
// TODO: This should be removed eventually
|
||||
this._linkMatcherId = (this._terminal as Terminal).registerLinkMatcher(strictUrlRegex, this._handler, this._options);
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this._linkMatcherId !== undefined && this._terminal !== undefined) {
|
||||
this._terminal.deregisterLinkMatcher(this._linkMatcherId);
|
||||
}
|
||||
|
||||
this._linkProvider?.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,11 @@
|
||||
"outDir": "../out",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"strict": true
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../out-test/api/TestUtils"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
|
||||
@@ -15,8 +15,12 @@ declare module 'xterm-addon-web-links' {
|
||||
* Creates a new web links addon.
|
||||
* @param handler The callback when the link is called.
|
||||
* @param options Options for the link matcher.
|
||||
* @param useLinkProvider Whether to use the new link provider API to create
|
||||
* the links. This is an option because use of both link matcher (old) and
|
||||
* link provider (new) may cause issues. Link provider will eventually be
|
||||
* the default and only option.
|
||||
*/
|
||||
constructor(handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions);
|
||||
constructor(handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions, useLinkProvider?: boolean);
|
||||
|
||||
/**
|
||||
* Activates the addon
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm-addon-webgl",
|
||||
"version": "0.4.0",
|
||||
"version": "0.9.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
|
||||
@@ -6,13 +6,14 @@
|
||||
import { createProgram, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils';
|
||||
import { WebglCharAtlas } from './atlas/WebglCharAtlas';
|
||||
import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types';
|
||||
import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_INDICIES_PER_CELL, RENDER_MODEL_FG_OFFSET } from './RenderModel';
|
||||
import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_INDICIES_PER_CELL, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_BG_OFFSET } from './RenderModel';
|
||||
import { fill } from 'common/TypedArrayUtils';
|
||||
import { slice } from './TypedArray';
|
||||
import { NULL_CELL_CODE, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants';
|
||||
import { NULL_CELL_CODE, WHITESPACE_CELL_CODE, Attributes, FgFlags } from 'common/buffer/Constants';
|
||||
import { Terminal, IBufferLine } from 'xterm';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { IColorSet, IColor } from 'browser/Types';
|
||||
import { IRenderDimensions } from 'browser/renderer/Types';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
|
||||
interface IVertices {
|
||||
attributes: Float32Array;
|
||||
@@ -212,14 +213,14 @@ export class GlyphRenderer {
|
||||
// a_cellpos only changes on resize
|
||||
}
|
||||
|
||||
public updateSelection(model: IRenderModel, columnSelectMode: boolean): void {
|
||||
public updateSelection(model: IRenderModel): void {
|
||||
const terminal = this._terminal;
|
||||
|
||||
this._vertices.selectionAttributes = slice(this._vertices.attributes, 0);
|
||||
|
||||
const bg = (this._colors.selectionOpaque.rgba >>> 8) | Attributes.CM_RGB;
|
||||
|
||||
if (columnSelectMode) {
|
||||
if (model.selection.columnSelectMode) {
|
||||
const startCol = model.selection.startCol;
|
||||
const width = model.selection.endCol - startCol;
|
||||
const height = model.selection.viewportCappedEndRow - model.selection.viewportCappedStartRow + 1;
|
||||
@@ -249,23 +250,54 @@ export class GlyphRenderer {
|
||||
|
||||
private _updateSelectionRange(startCol: number, endCol: number, y: number, model: IRenderModel, bg: number): void {
|
||||
const terminal = this._terminal;
|
||||
const row = y + terminal.buffer.viewportY;
|
||||
const row = y + terminal.buffer.active.viewportY;
|
||||
let line: IBufferLine | undefined;
|
||||
for (let x = startCol; x < endCol; x++) {
|
||||
const offset = (y * this._terminal.cols + x) * RENDER_MODEL_INDICIES_PER_CELL;
|
||||
const code = model.cells[offset];
|
||||
let fg = model.cells[offset + RENDER_MODEL_FG_OFFSET];
|
||||
if (fg & FgFlags.INVERSE) {
|
||||
const workCell = new AttributeData();
|
||||
workCell.fg = fg;
|
||||
workCell.bg = model.cells[offset + RENDER_MODEL_BG_OFFSET];
|
||||
// Get attributes from fg (excluding inverse) and resolve inverse by pullibng rgb colors
|
||||
// from bg. This is needed since the inverse fg color should be based on the original bg
|
||||
// color, not on the selection color
|
||||
fg = (fg & ~(Attributes.CM_MASK | Attributes.RGB_MASK | FgFlags.INVERSE));
|
||||
switch (workCell.getBgColorMode()) {
|
||||
case Attributes.CM_P16:
|
||||
case Attributes.CM_P256:
|
||||
const c = this._getColorFromAnsiIndex(workCell.getBgColor()).rgba;
|
||||
fg |= (c >> 8) & Attributes.RED_MASK | (c >> 8) & Attributes.GREEN_MASK | (c >> 8) & Attributes.BLUE_MASK;
|
||||
case Attributes.CM_RGB:
|
||||
const arr = AttributeData.toColorRGB(workCell.getBgColor());
|
||||
fg |= arr[0] << Attributes.RED_SHIFT | arr[1] << Attributes.GREEN_SHIFT | arr[2] << Attributes.BLUE_SHIFT;
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
const c2 = this._colors.background.rgba;
|
||||
fg |= (c2 >> 8) & Attributes.RED_MASK | (c2 >> 8) & Attributes.GREEN_MASK | (c2 >> 8) & Attributes.BLUE_MASK;
|
||||
}
|
||||
fg |= Attributes.CM_RGB;
|
||||
}
|
||||
if (code & COMBINED_CHAR_BIT_MASK) {
|
||||
if (!line) {
|
||||
line = terminal.buffer.getLine(row);
|
||||
line = terminal.buffer.active.getLine(row);
|
||||
}
|
||||
const chars = line!.getCell(x)!.char;
|
||||
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, model.cells[offset + RENDER_MODEL_FG_OFFSET], chars);
|
||||
const chars = line!.getCell(x)!.getChars();
|
||||
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg, chars);
|
||||
} else {
|
||||
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, model.cells[offset + RENDER_MODEL_FG_OFFSET]);
|
||||
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _getColorFromAnsiIndex(idx: number): IColor {
|
||||
if (idx >= this._colors.ansi.length) {
|
||||
throw new Error('No color found for idx ' + idx);
|
||||
}
|
||||
return this._colors.ansi[idx];
|
||||
}
|
||||
|
||||
public onResize(): void {
|
||||
const terminal = this._terminal;
|
||||
const gl = this._gl;
|
||||
|
||||
@@ -171,7 +171,7 @@ export class RectangleRenderer {
|
||||
);
|
||||
}
|
||||
|
||||
public updateSelection(model: ISelectionRenderModel, columnSelectMode: boolean): void {
|
||||
public updateSelection(model: ISelectionRenderModel): void {
|
||||
const terminal = this._terminal;
|
||||
|
||||
if (!model.hasSelection) {
|
||||
@@ -179,7 +179,7 @@ export class RectangleRenderer {
|
||||
return;
|
||||
}
|
||||
|
||||
if (columnSelectMode) {
|
||||
if (model.columnSelectMode) {
|
||||
const startCol = model.startCol;
|
||||
const width = model.endCol - startCol;
|
||||
const height = model.viewportCappedEndRow - model.viewportCappedStartRow + 1;
|
||||
@@ -196,7 +196,7 @@ export class RectangleRenderer {
|
||||
} else {
|
||||
// Draw first row
|
||||
const startCol = model.viewportStartRow === model.viewportCappedStartRow ? model.startCol : 0;
|
||||
const startRowEndCol = model.viewportCappedStartRow === model.viewportCappedEndRow ? model.endCol : terminal.cols;
|
||||
const startRowEndCol = model.viewportCappedStartRow === model.viewportEndRow ? model.endCol : terminal.cols;
|
||||
this._addRectangleFloat(
|
||||
this._vertices.selection,
|
||||
0,
|
||||
|
||||
@@ -22,6 +22,7 @@ export class RenderModel implements IRenderModel {
|
||||
this.lineLengths = new Uint32Array(0);
|
||||
this.selection = {
|
||||
hasSelection: false,
|
||||
columnSelectMode: false,
|
||||
viewportStartRow: 0,
|
||||
viewportEndRow: 0,
|
||||
viewportCappedStartRow: 0,
|
||||
@@ -42,7 +43,6 @@ export class RenderModel implements IRenderModel {
|
||||
public clear(): void {
|
||||
fill(this.cells, 0, 0);
|
||||
fill(this.lineLengths, 0, 0);
|
||||
this.clearSelection();
|
||||
}
|
||||
|
||||
public clearSelection(): void {
|
||||
|
||||
@@ -5,9 +5,7 @@
|
||||
import { assert } from 'chai';
|
||||
import { sliceFallback } from './TypedArray';
|
||||
|
||||
type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray
|
||||
| Int8Array | Int16Array | Int32Array
|
||||
| Float32Array | Float64Array;
|
||||
type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array | Float32Array | Float64Array;
|
||||
|
||||
function deepEquals(a: TypedArray, b: TypedArray): void {
|
||||
assert.equal(a.length, b.length);
|
||||
@@ -40,19 +38,19 @@ describe('polyfill conformance tests', function(): void {
|
||||
deepEquals(sliceFallback(u321, -1), u322.slice(-1));
|
||||
});
|
||||
it('Int8Array', () => {
|
||||
const i81 = new Int8Array(5);
|
||||
const i82 = new Int8Array(5);
|
||||
deepEquals(sliceFallback(i81, 2), i82.slice(2));
|
||||
deepEquals(sliceFallback(i81, 65537), i82.slice(65537));
|
||||
deepEquals(sliceFallback(i81, -1), i82.slice(-1));
|
||||
});
|
||||
const i81 = new Int8Array(5);
|
||||
const i82 = new Int8Array(5);
|
||||
deepEquals(sliceFallback(i81, 2), i82.slice(2));
|
||||
deepEquals(sliceFallback(i81, 65537), i82.slice(65537));
|
||||
deepEquals(sliceFallback(i81, -1), i82.slice(-1));
|
||||
});
|
||||
it('Int16Array', () => {
|
||||
const i161 = new Int16Array(5);
|
||||
const i162 = new Int16Array(5);
|
||||
deepEquals(sliceFallback(i161, 2), i162.slice(2));
|
||||
deepEquals(sliceFallback(i161, 65535), i162.slice(65535));
|
||||
deepEquals(sliceFallback(i161, -1), i162.slice(-1));
|
||||
});
|
||||
const i161 = new Int16Array(5);
|
||||
const i162 = new Int16Array(5);
|
||||
deepEquals(sliceFallback(i161, 2), i162.slice(2));
|
||||
deepEquals(sliceFallback(i161, 65535), i162.slice(65535));
|
||||
deepEquals(sliceFallback(i161, -1), i162.slice(-1));
|
||||
});
|
||||
it('Int32Array', () => {
|
||||
const i321 = new Int32Array(5);
|
||||
const i322 = new Int32Array(5);
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
export type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray
|
||||
| Int8Array | Int16Array | Int32Array
|
||||
| Float32Array | Float64Array;
|
||||
export type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array | Float32Array | Float64Array;
|
||||
|
||||
export function slice<T extends TypedArray>(array: T, start?: number, end?: number): T {
|
||||
// all modern engines that support .slice
|
||||
|
||||
+1
@@ -56,6 +56,7 @@ export interface IRenderModel {
|
||||
|
||||
export interface ISelectionRenderModel {
|
||||
hasSelection: boolean;
|
||||
columnSelectMode: boolean;
|
||||
viewportStartRow: number;
|
||||
viewportEndRow: number;
|
||||
viewportCappedStartRow: number;
|
||||
|
||||
@@ -10,6 +10,7 @@ import { IColorSet } from 'browser/Types';
|
||||
|
||||
export class WebglAddon implements ITerminalAddon {
|
||||
private _terminal?: Terminal;
|
||||
private _renderer?: WebglRenderer;
|
||||
|
||||
constructor(
|
||||
private _preserveDrawingBuffer?: boolean
|
||||
@@ -20,17 +21,27 @@ export class WebglAddon implements ITerminalAddon {
|
||||
throw new Error('Cannot activate WebglAddon before Terminal.open');
|
||||
}
|
||||
this._terminal = terminal;
|
||||
const renderService: IRenderService = (<any>terminal)._core._renderService;
|
||||
const renderService: IRenderService = (<any>terminal)._core._renderService;
|
||||
const colors: IColorSet = (<any>terminal)._core._colorManager.colors;
|
||||
renderService.setRenderer(new WebglRenderer(terminal, colors, this._preserveDrawingBuffer));
|
||||
this._renderer = new WebglRenderer(terminal, colors, this._preserveDrawingBuffer);
|
||||
renderService.setRenderer(this._renderer);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (!this._terminal) {
|
||||
throw new Error('Cannot dispose WebglAddon because it is activated');
|
||||
}
|
||||
const renderService: IRenderService = (<any>this._terminal)._core._renderService;
|
||||
renderService.setRenderer((<any>this._terminal)._core._createRenderer());
|
||||
const renderService: IRenderService = (this._terminal as any)._core._renderService;
|
||||
renderService.setRenderer((this._terminal as any)._core._createRenderer());
|
||||
renderService.onResize(this._terminal.cols, this._terminal.rows);
|
||||
this._renderer = undefined;
|
||||
}
|
||||
|
||||
public get textureAtlas(): HTMLCanvasElement | undefined {
|
||||
return this._renderer?.textureAtlas;
|
||||
}
|
||||
|
||||
public clearTextureAtlas(): void {
|
||||
this._renderer?.clearCharAtlas();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,21 +3,27 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import * as puppeteer from 'puppeteer';
|
||||
import { ITerminalOptions } from '../../../src/Types';
|
||||
import { ITerminalOptions } from '../../../src/common/Types';
|
||||
import { ITheme } from 'xterm';
|
||||
import { assert } from 'chai';
|
||||
import deepEqual = require('deep-equal');
|
||||
import { openTerminal, pollFor, writeSync, getBrowserType } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from 'playwright-core';
|
||||
|
||||
const APP = 'http://127.0.0.1:3000/test';
|
||||
|
||||
let browser: puppeteer.Browser;
|
||||
let page: puppeteer.Page;
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
|
||||
describe('WebGL Renderer Integration Tests', function(): void {
|
||||
it('dispose removes renderer canvases', async () => {
|
||||
describe('WebGL Renderer Integration Tests', async () => {
|
||||
const browserType = getBrowserType();
|
||||
const isHeadless = process.argv.indexOf('--headless') !== -1;
|
||||
// Firefox works only in non-headless mode https://github.com/microsoft/playwright/issues/1032
|
||||
const areTestsEnabled = browserType.name() === 'chromium' || (browserType.name() === 'firefox' && !isHeadless);
|
||||
const itWebgl = areTestsEnabled ? it : it.skip;
|
||||
|
||||
itWebgl('dispose removes renderer canvases', async function(): Promise<void> {
|
||||
await setupBrowser();
|
||||
assert.equal(await page.evaluate(`document.querySelectorAll('.xterm canvas').length`), 3);
|
||||
await page.evaluate(`addon.dispose()`);
|
||||
@@ -26,11 +32,13 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
});
|
||||
|
||||
describe('colors', () => {
|
||||
before(async () => setupBrowser());
|
||||
after(async () => browser.close());
|
||||
beforeEach(async () => page.evaluate(`window.term.reset()`));
|
||||
if (areTestsEnabled) {
|
||||
before(async () => setupBrowser());
|
||||
after(async () => browser.close());
|
||||
beforeEach(async () => page.evaluate(`window.term.reset()`));
|
||||
}
|
||||
|
||||
it('foreground 0-15', async () => {
|
||||
itWebgl('foreground 0-15', async () => {
|
||||
const theme: ITheme = {
|
||||
black: '#010203',
|
||||
red: '#040506',
|
||||
@@ -42,7 +50,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
white: '#161718'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await writeSync(`\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█`);
|
||||
await writeSync(page, `\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
@@ -53,7 +61,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
});
|
||||
|
||||
it('foreground 0-7 drawBoldTextInBrightColors', async () => {
|
||||
itWebgl('foreground 0-7 drawBoldTextInBrightColors', async () => {
|
||||
const theme: ITheme = {
|
||||
brightBlack: '#010203',
|
||||
brightRed: '#040506',
|
||||
@@ -68,7 +76,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
window.term.setOption('theme', ${JSON.stringify(theme)});
|
||||
window.term.setOption('drawBoldTextInBrightColors', true);
|
||||
`);
|
||||
await writeSync(`\\x1b[1;30m█\\x1b[1;31m█\\x1b[1;32m█\\x1b[1;33m█\\x1b[1;34m█\\x1b[1;35m█\\x1b[1;36m█\\x1b[1;37m█`);
|
||||
await writeSync(page, `\\x1b[1;30m█\\x1b[1;31m█\\x1b[1;32m█\\x1b[1;33m█\\x1b[1;34m█\\x1b[1;35m█\\x1b[1;36m█\\x1b[1;37m█`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
@@ -79,7 +87,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
});
|
||||
|
||||
it('background 0-15', async () => {
|
||||
itWebgl('background 0-15', async () => {
|
||||
const theme: ITheme = {
|
||||
black: '#010203',
|
||||
red: '#040506',
|
||||
@@ -91,7 +99,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
white: '#161718'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await writeSync(`\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `);
|
||||
await writeSync(page, `\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
@@ -102,7 +110,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
});
|
||||
|
||||
it('foreground 0-15 inverse', async () => {
|
||||
itWebgl('foreground 0-15 inverse', async () => {
|
||||
const theme: ITheme = {
|
||||
black: '#010203',
|
||||
red: '#040506',
|
||||
@@ -114,7 +122,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
white: '#161718'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await writeSync(`\\x1b[7;30m \\x1b[7;31m \\x1b[7;32m \\x1b[7;33m \\x1b[7;34m \\x1b[7;35m \\x1b[7;36m \\x1b[7;37m `);
|
||||
await writeSync(page, `\\x1b[7;30m \\x1b[7;31m \\x1b[7;32m \\x1b[7;33m \\x1b[7;34m \\x1b[7;35m \\x1b[7;36m \\x1b[7;37m `);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
@@ -125,7 +133,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
});
|
||||
|
||||
it('background 0-15 inverse', async () => {
|
||||
itWebgl('background 0-15 inverse', async () => {
|
||||
const theme: ITheme = {
|
||||
black: '#010203',
|
||||
red: '#040506',
|
||||
@@ -137,7 +145,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
white: '#161718'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await writeSync(`\\x1b[7;40m█\\x1b[7;41m█\\x1b[7;42m█\\x1b[7;43m█\\x1b[7;44m█\\x1b[7;45m█\\x1b[7;46m█\\x1b[7;47m█`);
|
||||
await writeSync(page, `\\x1b[7;40m█\\x1b[7;41m█\\x1b[7;42m█\\x1b[7;43m█\\x1b[7;44m█\\x1b[7;45m█\\x1b[7;46m█\\x1b[7;47m█`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
@@ -148,7 +156,53 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
});
|
||||
|
||||
it('foreground 0-15 bright', async () => {
|
||||
itWebgl('foreground 0-15 inivisible', async () => {
|
||||
const theme: ITheme = {
|
||||
black: '#010203',
|
||||
red: '#040506',
|
||||
green: '#070809',
|
||||
yellow: '#0a0b0c',
|
||||
blue: '#0d0e0f',
|
||||
magenta: '#101112',
|
||||
cyan: '#131415',
|
||||
white: '#161718'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await writeSync(page, `\\x1b[8;30m \\x1b[8;31m \\x1b[8;32m \\x1b[8;33m \\x1b[8;34m \\x1b[8;35m \\x1b[8;36m \\x1b[8;37m `);
|
||||
await pollFor(page, () => getCellColor(1, 1), [0, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [0, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [0, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [0, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [0, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [0, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [0, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [0, 0, 0, 255]);
|
||||
});
|
||||
|
||||
itWebgl('background 0-15 inivisible', async () => {
|
||||
const theme: ITheme = {
|
||||
black: '#010203',
|
||||
red: '#040506',
|
||||
green: '#070809',
|
||||
yellow: '#0a0b0c',
|
||||
blue: '#0d0e0f',
|
||||
magenta: '#101112',
|
||||
cyan: '#131415',
|
||||
white: '#161718'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await writeSync(page, `\\x1b[8;40m█\\x1b[8;41m█\\x1b[8;42m█\\x1b[8;43m█\\x1b[8;44m█\\x1b[8;45m█\\x1b[8;46m█\\x1b[8;47m█`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
});
|
||||
|
||||
itWebgl('foreground 0-15 bright', async () => {
|
||||
const theme: ITheme = {
|
||||
brightBlack: '#010203',
|
||||
brightRed: '#040506',
|
||||
@@ -160,7 +214,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
brightWhite: '#161718'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await writeSync(`\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`);
|
||||
await writeSync(page, `\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
@@ -171,7 +225,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
});
|
||||
|
||||
it('background 0-15 bright', async () => {
|
||||
itWebgl('background 0-15 bright', async () => {
|
||||
const theme: ITheme = {
|
||||
brightBlack: '#010203',
|
||||
brightRed: '#040506',
|
||||
@@ -183,7 +237,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
brightWhite: '#161718'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await writeSync(`\\x1b[100m \\x1b[101m \\x1b[102m \\x1b[103m \\x1b[104m \\x1b[105m \\x1b[106m \\x1b[107m `);
|
||||
await writeSync(page, `\\x1b[100m \\x1b[101m \\x1b[102m \\x1b[103m \\x1b[104m \\x1b[105m \\x1b[106m \\x1b[107m `);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
|
||||
@@ -194,7 +248,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
|
||||
});
|
||||
|
||||
it('foreground 16-255', async () => {
|
||||
itWebgl('foreground 16-255', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
@@ -202,7 +256,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
await writeSync(page, data);
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const cssColor = COLORS_16_TO_255[y * 16 + x];
|
||||
@@ -214,7 +268,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
});
|
||||
|
||||
it('background 16-255', async () => {
|
||||
itWebgl('background 16-255', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
@@ -222,7 +276,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
await writeSync(page, data);
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const cssColor = COLORS_16_TO_255[y * 16 + x];
|
||||
@@ -234,7 +288,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground 16-255 inverse', async () => {
|
||||
itWebgl('foreground 16-255 inverse', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
@@ -242,7 +296,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
await writeSync(page, data);
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const cssColor = COLORS_16_TO_255[y * 16 + x];
|
||||
@@ -254,7 +308,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
});
|
||||
|
||||
it('background 16-255 inverse', async () => {
|
||||
itWebgl('background 16-255 inverse', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
@@ -262,7 +316,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
await writeSync(page, data);
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const cssColor = COLORS_16_TO_255[y * 16 + x];
|
||||
@@ -274,7 +328,43 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground true color red', async () => {
|
||||
itWebgl('foreground 16-255 invisible', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
data += `\\x1b[8;38;5;${16 + y * 16 + x}m \x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(page, data);
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, 0, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itWebgl('background 16-255 invisible', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
data += `\\x1b[8;48;5;${16 + y * 16 + x}m█\x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(page, data);
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const cssColor = COLORS_16_TO_255[y * 16 + x];
|
||||
const r = parseInt(cssColor.substr(1, 2), 16);
|
||||
const g = parseInt(cssColor.substr(3, 2), 16);
|
||||
const b = parseInt(cssColor.substr(5, 2), 16);
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itWebgl('foreground true color red', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
@@ -283,7 +373,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
await writeSync(page, data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
@@ -292,7 +382,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
});
|
||||
|
||||
it('background true color red', async () => {
|
||||
itWebgl('background true color red', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
@@ -301,7 +391,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
await writeSync(page, data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
@@ -310,7 +400,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground true color green', async () => {
|
||||
itWebgl('foreground true color green', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
@@ -319,7 +409,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
await writeSync(page, data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
@@ -328,7 +418,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
});
|
||||
|
||||
it('background true color green', async () => {
|
||||
itWebgl('background true color green', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
@@ -337,7 +427,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
await writeSync(page, data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
@@ -346,7 +436,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground true color blue', async () => {
|
||||
itWebgl('foreground true color blue', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
@@ -355,7 +445,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
await writeSync(page, data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
@@ -364,7 +454,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
});
|
||||
|
||||
it('background true color blue', async () => {
|
||||
itWebgl('background true color blue', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
@@ -373,7 +463,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
await writeSync(page, data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
@@ -382,7 +472,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground true color grey', async () => {
|
||||
itWebgl('foreground true color grey', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
@@ -391,7 +481,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
await writeSync(page, data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
@@ -400,7 +490,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
});
|
||||
|
||||
it('background true color grey', async () => {
|
||||
itWebgl('background true color grey', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
@@ -409,7 +499,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
await writeSync(page, data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
@@ -418,7 +508,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground true color red inverse', async function(): Promise<void> {
|
||||
itWebgl('foreground true color red inverse', async function(): Promise<void> {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
@@ -427,7 +517,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
await writeSync(page, data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
@@ -436,7 +526,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
});
|
||||
|
||||
it('background true color red inverse', async function(): Promise<void> {
|
||||
itWebgl('background true color red inverse', async function(): Promise<void> {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
@@ -445,7 +535,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
await writeSync(page, data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
@@ -454,7 +544,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground true color green inverse', async () => {
|
||||
itWebgl('foreground true color green inverse', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
@@ -463,7 +553,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
await writeSync(page, data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
@@ -472,7 +562,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
});
|
||||
|
||||
it('background true color green inverse', async () => {
|
||||
itWebgl('background true color green inverse', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
@@ -481,7 +571,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
await writeSync(page, data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
@@ -490,7 +580,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground true color blue inverse', async () => {
|
||||
itWebgl('foreground true color blue inverse', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
@@ -499,7 +589,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
await writeSync(page, data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
@@ -508,7 +598,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
});
|
||||
|
||||
it('background true color blue inverse', async () => {
|
||||
itWebgl('background true color blue inverse', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
@@ -517,7 +607,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
await writeSync(page, data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
@@ -526,7 +616,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
});
|
||||
|
||||
it('foreground true color grey inverse', async () => {
|
||||
itWebgl('foreground true color grey inverse', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
@@ -535,7 +625,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
await writeSync(page, data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
@@ -544,7 +634,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
});
|
||||
|
||||
it('background true color grey inverse', async () => {
|
||||
itWebgl('background true color grey inverse', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
@@ -553,7 +643,42 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(data);
|
||||
await writeSync(page, data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itWebgl('foreground true color grey invisible', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[8;38;2;${i};${i};${i}m \x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(page, data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, 0, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itWebgl('background true color grey invisible', async () => {
|
||||
let data = '';
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
data += `\\x1b[8;48;2;${i};${i};${i}m█\x1b[0m`;
|
||||
}
|
||||
data += '\\r\\n';
|
||||
}
|
||||
await writeSync(page, data);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
@@ -564,11 +689,13 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
});
|
||||
|
||||
describe('minimumContrastRatio', async () => {
|
||||
before(async () => setupBrowser());
|
||||
after(async () => browser.close());
|
||||
beforeEach(async () => page.evaluate(`window.term.reset()`));
|
||||
if (areTestsEnabled) {
|
||||
before(async () => setupBrowser());
|
||||
after(async () => browser.close());
|
||||
beforeEach(async () => page.evaluate(`window.term.reset()`));
|
||||
}
|
||||
|
||||
it('should adjust 0-15 colors on black background', async () => {
|
||||
itWebgl('should adjust 0-15 colors on black background', async () => {
|
||||
const theme: ITheme = {
|
||||
black: '#2e3436',
|
||||
red: '#cc0000',
|
||||
@@ -591,7 +718,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
window.term.setOption('theme', ${JSON.stringify(theme)});
|
||||
window.term.setOption('minimumContrastRatio', 1);
|
||||
`);
|
||||
await writeSync(
|
||||
await writeSync(page,
|
||||
`\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█\\r\\n` +
|
||||
`\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`
|
||||
);
|
||||
@@ -635,7 +762,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]);
|
||||
});
|
||||
|
||||
it('should adjust 0-15 colors on white background', async () => {
|
||||
itWebgl('should adjust 0-15 colors on white background', async () => {
|
||||
const theme: ITheme = {
|
||||
background: '#ffffff',
|
||||
black: '#2e3436',
|
||||
@@ -659,7 +786,7 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
window.term.setOption('theme', ${JSON.stringify(theme)});
|
||||
window.term.setOption('minimumContrastRatio', 1);
|
||||
`);
|
||||
await writeSync(
|
||||
await writeSync(page,
|
||||
`\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█\\r\\n` +
|
||||
`\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`
|
||||
);
|
||||
@@ -703,37 +830,52 @@ describe('WebGL Renderer Integration Tests', function(): void {
|
||||
});
|
||||
});
|
||||
|
||||
describe('selection', async () => {
|
||||
if (areTestsEnabled) {
|
||||
before(async () => setupBrowser());
|
||||
after(async () => browser.close());
|
||||
beforeEach(async () => page.evaluate(`window.term.reset()`));
|
||||
}
|
||||
|
||||
itWebgl('should resolve the inverse foreground color based on the original background color, not the selection', async () => {
|
||||
const theme: ITheme = {
|
||||
foreground: '#FF0000',
|
||||
background: '#00FF00',
|
||||
selection: '#0000FF'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await writeSync(page, ` █\\x1b[7m█\\x1b[0m`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [0, 255, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [255, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [0, 255, 0, 255]);
|
||||
await page.evaluate(`window.term.selectAll()`);
|
||||
// Selection only cell needs to be first to ensure renderer has kicked in
|
||||
await pollFor(page, () => getCellColor(1, 1), [0, 0, 255, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [255, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [0, 255, 0, 255]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('allowTransparency', async () => {
|
||||
before(async () => setupBrowser({ rendererType: 'dom', allowTransparency: true}));
|
||||
after(async () => browser.close());
|
||||
beforeEach(async () => page.evaluate(`window.term.reset()`));
|
||||
it('transparent background inverse', async () => {
|
||||
if (areTestsEnabled) {
|
||||
before(async () => setupBrowser({ rendererType: 'dom', allowTransparency: true }));
|
||||
after(async () => browser.close());
|
||||
beforeEach(async () => page.evaluate(`window.term.reset()`));
|
||||
}
|
||||
|
||||
itWebgl('transparent background inverse', async () => {
|
||||
const theme: ITheme = {
|
||||
background: '#ff000080'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
const data = `\\x1b[7m█\x1b[0m`;
|
||||
await writeSync(data);
|
||||
await writeSync(page, data);
|
||||
// Inverse background should be opaque
|
||||
await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function openTerminal(options: ITerminalOptions = {}): Promise<void> {
|
||||
await page.evaluate(`window.term = new Terminal(${JSON.stringify(options)})`);
|
||||
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
|
||||
if (options.rendererType === 'dom') {
|
||||
await page.waitForSelector('.xterm-rows');
|
||||
} else {
|
||||
await page.waitForSelector('.xterm-text-layer');
|
||||
}
|
||||
}
|
||||
|
||||
async function writeSync(data: string): Promise<void> {
|
||||
return page.evaluate(`new Promise(resolve => window.term.write('${data}', resolve))`);
|
||||
}
|
||||
|
||||
async function getCellColor(col: number, row: number): Promise<number[]> {
|
||||
await page.evaluate(`
|
||||
window.gl = window.term._core._renderService._renderer._gl;
|
||||
@@ -749,32 +891,20 @@ async function getCellColor(col: number, row: number): Promise<number[]> {
|
||||
}
|
||||
|
||||
async function setupBrowser(options: ITerminalOptions = { rendererType: 'dom' }): Promise<void> {
|
||||
browser = await puppeteer.launch({
|
||||
headless: process.argv.indexOf('--headless') !== -1,
|
||||
args: [`--window-size=${width},${height}`, `--no-sandbox`]
|
||||
const browserType = getBrowserType();
|
||||
browser = await browserType.launch({ dumpio: true,
|
||||
headless: process.argv.indexOf('--headless') !== -1
|
||||
});
|
||||
page = (await browser.pages())[0];
|
||||
await page.setViewport({ width, height });
|
||||
page = await (await browser.newContext()).newPage();
|
||||
await page.setViewportSize({ width, height });
|
||||
await page.goto(APP);
|
||||
await openTerminal(options);
|
||||
await openTerminal(page, options);
|
||||
await page.evaluate(`
|
||||
window.addon = new WebglAddon(true);
|
||||
window.term.loadAddon(window.addon);
|
||||
`);
|
||||
}
|
||||
|
||||
export async function pollFor<T>(page: puppeteer.Page, evalOrFn: string | (() => Promise<T>), val: T, preFn?: () => Promise<void>): Promise<void> {
|
||||
if (preFn) {
|
||||
await preFn();
|
||||
}
|
||||
const result = typeof evalOrFn === 'string' ? await page.evaluate(evalOrFn) : await evalOrFn();
|
||||
if (!deepEqual(result, val)) {
|
||||
return new Promise<void>(r => {
|
||||
setTimeout(() => r(pollFor(page, evalOrFn, val, preFn)), 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const COLORS_16_TO_255 = [
|
||||
'#000000', '#00005f', '#000087', '#0000af', '#0000d7', '#0000ff', '#005f00', '#005f5f', '#005f87', '#005faf', '#005fd7', '#005fff', '#008700', '#00875f', '#008787', '#0087af',
|
||||
'#0087d7', '#0087ff', '#00af00', '#00af5f', '#00af87', '#00afaf', '#00afd7', '#00afff', '#00d700', '#00d75f', '#00d787', '#00d7af', '#00d7d7', '#00d7ff', '#00ff00', '#00ff5f',
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ITerminal } from '../../../src/Types';
|
||||
import { GlyphRenderer } from './GlyphRenderer';
|
||||
import { LinkRenderLayer } from './renderLayer/LinkRenderLayer';
|
||||
import { CursorRenderLayer } from './renderLayer/CursorRenderLayer';
|
||||
@@ -16,8 +15,8 @@ import { Disposable } from 'common/Lifecycle';
|
||||
import { NULL_CELL_CODE } from 'common/buffer/Constants';
|
||||
import { Terminal, IEvent } from 'xterm';
|
||||
import { IRenderLayer } from './renderLayer/Types';
|
||||
import { IRenderDimensions, IRenderer, IRequestRefreshRowsEvent } from 'browser/renderer/Types';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types';
|
||||
import { ITerminal, IColorSet } from 'browser/Types';
|
||||
import { EventEmitter } from 'common/EventEmitter';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
|
||||
@@ -39,8 +38,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
private _core: ITerminal;
|
||||
private _isAttached: boolean;
|
||||
|
||||
private _onRequestRefreshRows = new EventEmitter<IRequestRefreshRowsEvent>();
|
||||
public get onRequestRefreshRows(): IEvent<IRequestRefreshRowsEvent> { return this._onRequestRefreshRows.event; }
|
||||
private _onRequestRedraw = new EventEmitter<IRequestRedrawEvent>();
|
||||
public get onRequestRedraw(): IEvent<IRequestRedrawEvent> { return this._onRequestRedraw.event; }
|
||||
|
||||
constructor(
|
||||
private _terminal: Terminal,
|
||||
@@ -49,11 +48,11 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
) {
|
||||
super();
|
||||
|
||||
this._core = (<any>this._terminal)._core;
|
||||
this._core = (this._terminal as any)._core;
|
||||
|
||||
this._renderLayers = [
|
||||
new LinkRenderLayer(this._core.screenElement, 2, this._colors, this._core),
|
||||
new CursorRenderLayer(this._core.screenElement, 3, this._colors, this._onRequestRefreshRows)
|
||||
new LinkRenderLayer(this._core.screenElement!, 2, this._colors, this._core),
|
||||
new CursorRenderLayer(this._core.screenElement!, 3, this._colors, this._onRequestRedraw)
|
||||
];
|
||||
this.dimensions = {
|
||||
scaledCharWidth: 0,
|
||||
@@ -81,9 +80,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
};
|
||||
this._gl = this._canvas.getContext('webgl2', contextAttributes) as IWebGL2RenderingContext;
|
||||
if (!this._gl) {
|
||||
throw new Error('WebGL2 not supported');
|
||||
throw new Error('WebGL2 not supported ' + this._gl);
|
||||
}
|
||||
this._core.screenElement.appendChild(this._canvas);
|
||||
this._core.screenElement!.appendChild(this._canvas);
|
||||
|
||||
this._rectangleRenderer = new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions);
|
||||
this._glyphRenderer = new GlyphRenderer(this._terminal, this._colors, this._gl, this.dimensions);
|
||||
@@ -91,15 +90,19 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
// Update dimensions and acquire char atlas
|
||||
this.onCharSizeChanged();
|
||||
|
||||
this._isAttached = document.body.contains(this._core.screenElement);
|
||||
this._isAttached = document.body.contains(this._core.screenElement!);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._renderLayers.forEach(l => l.dispose());
|
||||
this._core.screenElement.removeChild(this._canvas);
|
||||
this._core.screenElement!.removeChild(this._canvas);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public get textureAtlas(): HTMLCanvasElement | undefined {
|
||||
return this._charAtlas?.cacheCanvas;
|
||||
}
|
||||
|
||||
public setColors(colors: IColorSet): void {
|
||||
this._colors = colors;
|
||||
// Clear layers and force a full render
|
||||
@@ -113,8 +116,12 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
|
||||
this._refreshCharAtlas();
|
||||
|
||||
this._rectangleRenderer.updateSelection(this._model.selection);
|
||||
this._glyphRenderer.updateSelection(this._model);
|
||||
|
||||
// Force a full refresh
|
||||
this._model.clear();
|
||||
this._model.clearSelection();
|
||||
}
|
||||
|
||||
public onDevicePixelRatioChange(): void {
|
||||
@@ -143,8 +150,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
this._canvas.style.height = `${this.dimensions.canvasHeight}px`;
|
||||
|
||||
// Resize the screen
|
||||
this._core.screenElement.style.width = `${this.dimensions.canvasWidth}px`;
|
||||
this._core.screenElement.style.height = `${this.dimensions.canvasHeight}px`;
|
||||
this._core.screenElement!.style.width = `${this.dimensions.canvasWidth}px`;
|
||||
this._core.screenElement!.style.height = `${this.dimensions.canvasHeight}px`;
|
||||
this._glyphRenderer.setDimensions(this.dimensions);
|
||||
this._glyphRenderer.onResize();
|
||||
|
||||
@@ -152,6 +159,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
|
||||
// Force a full refresh
|
||||
this._model.clear();
|
||||
this._model.clearSelection();
|
||||
}
|
||||
|
||||
public onCharSizeChanged(): void {
|
||||
@@ -166,15 +174,15 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
this._renderLayers.forEach(l => l.onFocus(this._terminal));
|
||||
}
|
||||
|
||||
public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void {
|
||||
public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
|
||||
this._renderLayers.forEach(l => l.onSelectionChanged(this._terminal, start, end, columnSelectMode));
|
||||
|
||||
this._updateSelectionModel(start, end);
|
||||
this._updateSelectionModel(start, end, columnSelectMode);
|
||||
|
||||
this._rectangleRenderer.updateSelection(this._model.selection, columnSelectMode);
|
||||
this._glyphRenderer.updateSelection(this._model, columnSelectMode);
|
||||
this._rectangleRenderer.updateSelection(this._model.selection);
|
||||
this._glyphRenderer.updateSelection(this._model);
|
||||
|
||||
this._onRequestRefreshRows.fire({ start: 0, end: this._terminal.rows - 1 });
|
||||
this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 });
|
||||
}
|
||||
|
||||
public onCursorMove(): void {
|
||||
@@ -208,6 +216,14 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
this._glyphRenderer.setAtlas(this._charAtlas);
|
||||
}
|
||||
|
||||
public clearCharAtlas(): void {
|
||||
this._charAtlas?.clearTexture();
|
||||
this._model.clear();
|
||||
this._updateModel(0, this._terminal.rows - 1);
|
||||
this._glyphRenderer.updateSelection(this._model);
|
||||
this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 });
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this._renderLayers.forEach(l => l.reset(this._terminal));
|
||||
}
|
||||
@@ -222,7 +238,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
|
||||
public renderRows(start: number, end: number): void {
|
||||
if (!this._isAttached) {
|
||||
if (document.body.contains(this._core.screenElement) && (<any>this._core)._charSizeService.width && (<any>this._core)._charSizeService.height) {
|
||||
if (document.body.contains(this._core.screenElement!) && (this._core as any)._charSizeService.width && (this._core as any)._charSizeService.height) {
|
||||
this._updateDimensions();
|
||||
this._refreshCharAtlas();
|
||||
this._isAttached = true;
|
||||
@@ -237,6 +253,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
// Tell renderer the frame is beginning
|
||||
if (this._glyphRenderer.beginFrame()) {
|
||||
this._model.clear();
|
||||
this._model.clearSelection();
|
||||
}
|
||||
|
||||
// Update model to reflect what's drawn
|
||||
@@ -288,7 +305,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
this._rectangleRenderer.updateBackgrounds(this._model);
|
||||
}
|
||||
|
||||
private _updateSelectionModel(start: [number, number], end: [number, number]): void {
|
||||
private _updateSelectionModel(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
|
||||
const terminal = this._terminal;
|
||||
|
||||
// Selection does not exist
|
||||
@@ -298,8 +315,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
}
|
||||
|
||||
// Translate from buffer position to viewport position
|
||||
const viewportStartRow = start[1] - terminal.buffer.viewportY;
|
||||
const viewportEndRow = end[1] - terminal.buffer.viewportY;
|
||||
const viewportStartRow = start[1] - terminal.buffer.active.viewportY;
|
||||
const viewportEndRow = end[1] - terminal.buffer.active.viewportY;
|
||||
const viewportCappedStartRow = Math.max(viewportStartRow, 0);
|
||||
const viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1);
|
||||
|
||||
@@ -310,6 +327,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
}
|
||||
|
||||
this._model.selection.hasSelection = true;
|
||||
this._model.selection.columnSelectMode = columnSelectMode;
|
||||
this._model.selection.viewportStartRow = viewportStartRow;
|
||||
this._model.selection.viewportEndRow = viewportEndRow;
|
||||
this._model.selection.viewportCappedStartRow = viewportCappedStartRow;
|
||||
@@ -325,7 +343,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
// TODO: Acquire CharSizeService properly
|
||||
|
||||
// Perform a new measure if the CharMeasure dimensions are not yet available
|
||||
if (!(<any>this._core)._charSizeService.width || !(<any>this._core)._charSizeService.height) {
|
||||
if (!(this._core as any)._charSizeService.width || !(this._core as any)._charSizeService.height) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -336,12 +354,12 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
|
||||
// NOTE: ceil fixes sometime, floor does others :s
|
||||
|
||||
this.dimensions.scaledCharWidth = Math.floor((<any>this._core)._charSizeService.width * this._devicePixelRatio);
|
||||
this.dimensions.scaledCharWidth = Math.floor((this._core as any)._charSizeService.width * this._devicePixelRatio);
|
||||
|
||||
// Calculate the scaled character height. Height is ceiled in case
|
||||
// devicePixelRatio is a floating point number in order to ensure there is
|
||||
// enough space to draw the character to the cell.
|
||||
this.dimensions.scaledCharHeight = Math.ceil((<any>this._core)._charSizeService.height * this._devicePixelRatio);
|
||||
this.dimensions.scaledCharHeight = Math.ceil((this._core as any)._charSizeService.height * this._devicePixelRatio);
|
||||
|
||||
// Calculate the scaled cell height, if lineHeight is not 1 then the value
|
||||
// will be floored because since lineHeight can never be lower then 1, there
|
||||
|
||||
@@ -20,7 +20,7 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number
|
||||
background: colors.background,
|
||||
cursor: NULL_COLOR,
|
||||
cursorAccent: NULL_COLOR,
|
||||
selection: NULL_COLOR,
|
||||
selectionTransparent: NULL_COLOR,
|
||||
selectionOpaque: NULL_COLOR,
|
||||
// For the static char atlas, we only use the first 16 colors, but we need all 256 for the
|
||||
// dynamic character atlas.
|
||||
|
||||
@@ -11,7 +11,7 @@ import { throwIfFalsy } from '../WebglUtils';
|
||||
import { IColor } from 'browser/Types';
|
||||
import { IDisposable } from 'xterm';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { toCss, ensureContrastRatioRgba } from 'browser/Color';
|
||||
import { channels, rgba } from 'browser/Color';
|
||||
|
||||
// In practice we're probably never going to exhaust a texture this large. For debugging purposes,
|
||||
// however, it can be useful to set this to a really tiny value, to verify that LRU eviction works.
|
||||
@@ -86,9 +86,6 @@ export class WebglCharAtlas implements IDisposable {
|
||||
this._tmpCanvas.width = this._config.scaledCharWidth * 2 + TMP_CANVAS_GLYPH_PADDING * 2;
|
||||
this._tmpCanvas.height = this._config.scaledCharHeight + TMP_CANVAS_GLYPH_PADDING * 2;
|
||||
this._tmpCtx = throwIfFalsy(this._tmpCanvas.getContext('2d', {alpha: this._config.allowTransparency}));
|
||||
|
||||
// This is useful for debugging
|
||||
document.body.appendChild(this.cacheCanvas);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
@@ -104,7 +101,7 @@ export class WebglCharAtlas implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
protected _doWarmUp(): void {
|
||||
private _doWarmUp(): void {
|
||||
// Pre-fill with ASCII 33-126
|
||||
for (let i = 33; i < 126; i++) {
|
||||
const rasterizedGlyph = this._drawToCache(i, DEFAULT_COLOR, DEFAULT_COLOR);
|
||||
@@ -118,17 +115,26 @@ export class WebglCharAtlas implements IDisposable {
|
||||
|
||||
public beginFrame(): boolean {
|
||||
if (this._currentRowY > TEXTURE_CAPACITY) {
|
||||
this._cacheCtx.clearRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);
|
||||
this._cacheMap = {};
|
||||
this._currentRowHeight = 0;
|
||||
this._currentRowX = 0;
|
||||
this._currentRowY = 0;
|
||||
this._doWarmUp();
|
||||
this.clearTexture();
|
||||
this.warmUp();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public clearTexture(): void {
|
||||
if (this._currentRowX === 0 && this._currentRowY === 0) {
|
||||
return;
|
||||
}
|
||||
this._cacheCtx.clearRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);
|
||||
this._cacheMap = {};
|
||||
this._cacheMapCombined = {};
|
||||
this._currentRowHeight = 0;
|
||||
this._currentRowX = 0;
|
||||
this._currentRowY = 0;
|
||||
this._didWarmUp = false;
|
||||
}
|
||||
|
||||
public getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number): IRasterizedGlyph {
|
||||
let rasterizedGlyphSet = this._cacheMapCombined[chars];
|
||||
if (!rasterizedGlyphSet) {
|
||||
@@ -224,7 +230,7 @@ export class WebglCharAtlas implements IDisposable {
|
||||
return this._getColorFromAnsiIndex(fgColor).css;
|
||||
case Attributes.CM_RGB:
|
||||
const arr = AttributeData.toColorRGB(fgColor);
|
||||
return toCss(arr[0], arr[1], arr[2]);
|
||||
return channels.toCss(arr[0], arr[1], arr[2]);
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
if (inverse) {
|
||||
@@ -287,14 +293,14 @@ export class WebglCharAtlas implements IDisposable {
|
||||
|
||||
const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, inverse);
|
||||
const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, inverse, bold);
|
||||
const result = ensureContrastRatioRgba(bgRgba, fgRgba, this._config.minimumContrastRatio);
|
||||
const result = rgba.ensureContrastRatio(bgRgba, fgRgba, this._config.minimumContrastRatio);
|
||||
|
||||
if (!result) {
|
||||
this._config.colors.contrastCache.setCss(bg, fg, null);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const css = toCss(
|
||||
const css = channels.toCss(
|
||||
(result >> 24) & 0xFF,
|
||||
(result >> 16) & 0xFF,
|
||||
(result >> 8) & 0xFF
|
||||
@@ -316,6 +322,11 @@ export class WebglCharAtlas implements IDisposable {
|
||||
this._workAttributeData.fg = fg;
|
||||
this._workAttributeData.bg = bg;
|
||||
|
||||
const invisible = !!this._workAttributeData.isInvisible();
|
||||
if (invisible) {
|
||||
return NULL_RASTERIZED_GLYPH;
|
||||
}
|
||||
|
||||
const bold = !!this._workAttributeData.isBold();
|
||||
const inverse = !!this._workAttributeData.isInverse();
|
||||
const dim = !!this._workAttributeData.isDim();
|
||||
@@ -347,7 +358,7 @@ export class WebglCharAtlas implements IDisposable {
|
||||
const fontStyle = italic ? 'italic' : '';
|
||||
this._tmpCtx.font =
|
||||
`${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`;
|
||||
this._tmpCtx.textBaseline = 'top';
|
||||
this._tmpCtx.textBaseline = 'middle';
|
||||
|
||||
this._tmpCtx.fillStyle = this._getForegroundCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold);
|
||||
|
||||
@@ -357,7 +368,7 @@ export class WebglCharAtlas implements IDisposable {
|
||||
}
|
||||
|
||||
// Draw the character
|
||||
this._tmpCtx.fillText(chars, TMP_CANVAS_GLYPH_PADDING, TMP_CANVAS_GLYPH_PADDING);
|
||||
this._tmpCtx.fillText(chars, TMP_CANVAS_GLYPH_PADDING, TMP_CANVAS_GLYPH_PADDING + this._config.scaledCharHeight / 2);
|
||||
this._tmpCtx.restore();
|
||||
|
||||
// clear the background from the character to avoid issues with drawing over the previous
|
||||
|
||||
@@ -58,7 +58,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
public onFocus(terminal: Terminal): void {}
|
||||
public onCursorMove(terminal: Terminal): void {}
|
||||
public onGridChanged(terminal: Terminal, startRow: number, endRow: number): void {}
|
||||
public onSelectionChanged(terminal: Terminal, start: [number, number], end: [number, number], columnSelectMode: boolean = false): void {}
|
||||
public onSelectionChanged(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {}
|
||||
|
||||
public setColors(terminal: Terminal, colorSet: IColorSet): void {
|
||||
this._refreshCharAtlas(terminal, colorSet);
|
||||
@@ -74,7 +74,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
const oldCanvas = this._canvas;
|
||||
this._alpha = alpha;
|
||||
// Cloning preserves properties
|
||||
this._canvas = <HTMLCanvasElement>this._canvas.cloneNode();
|
||||
this._canvas = this._canvas.cloneNode() as HTMLCanvasElement;
|
||||
this._initCanvas();
|
||||
this._container.replaceChild(this._canvas, oldCanvas);
|
||||
|
||||
@@ -127,10 +127,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
*/
|
||||
protected _fillCells(x: number, y: number, width: number, height: number): void {
|
||||
this._ctx.fillRect(
|
||||
x * this._scaledCellWidth,
|
||||
y * this._scaledCellHeight,
|
||||
width * this._scaledCellWidth,
|
||||
height * this._scaledCellHeight);
|
||||
x * this._scaledCellWidth,
|
||||
y * this._scaledCellHeight,
|
||||
width * this._scaledCellWidth,
|
||||
height * this._scaledCellHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,10 +141,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
*/
|
||||
protected _fillBottomLineAtCells(x: number, y: number, width: number = 1): void {
|
||||
this._ctx.fillRect(
|
||||
x * this._scaledCellWidth,
|
||||
(y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */,
|
||||
width * this._scaledCellWidth,
|
||||
window.devicePixelRatio);
|
||||
x * this._scaledCellWidth,
|
||||
(y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */,
|
||||
width * this._scaledCellWidth,
|
||||
window.devicePixelRatio);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,12 +153,12 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
* @param x The column to fill.
|
||||
* @param y The row to fill.
|
||||
*/
|
||||
protected _fillLeftLineAtCell(x: number, y: number): void {
|
||||
protected _fillLeftLineAtCell(x: number, y: number, width: number): void {
|
||||
this._ctx.fillRect(
|
||||
x * this._scaledCellWidth,
|
||||
y * this._scaledCellHeight,
|
||||
window.devicePixelRatio,
|
||||
this._scaledCellHeight);
|
||||
x * this._scaledCellWidth,
|
||||
y * this._scaledCellHeight,
|
||||
window.devicePixelRatio * width,
|
||||
this._scaledCellHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,10 +170,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
protected _strokeRectAtCell(x: number, y: number, width: number, height: number): void {
|
||||
this._ctx.lineWidth = window.devicePixelRatio;
|
||||
this._ctx.strokeRect(
|
||||
x * this._scaledCellWidth + window.devicePixelRatio / 2,
|
||||
y * this._scaledCellHeight + (window.devicePixelRatio / 2),
|
||||
width * this._scaledCellWidth - window.devicePixelRatio,
|
||||
(height * this._scaledCellHeight) - window.devicePixelRatio);
|
||||
x * this._scaledCellWidth + window.devicePixelRatio / 2,
|
||||
y * this._scaledCellHeight + (window.devicePixelRatio / 2),
|
||||
width * this._scaledCellWidth - window.devicePixelRatio,
|
||||
(height * this._scaledCellHeight) - window.devicePixelRatio);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,17 +198,17 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
protected _clearCells(x: number, y: number, width: number, height: number): void {
|
||||
if (this._alpha) {
|
||||
this._ctx.clearRect(
|
||||
x * this._scaledCellWidth,
|
||||
y * this._scaledCellHeight,
|
||||
width * this._scaledCellWidth,
|
||||
height * this._scaledCellHeight);
|
||||
x * this._scaledCellWidth,
|
||||
y * this._scaledCellHeight,
|
||||
width * this._scaledCellWidth,
|
||||
height * this._scaledCellHeight);
|
||||
} else {
|
||||
this._ctx.fillStyle = this._colors.background.css;
|
||||
this._ctx.fillRect(
|
||||
x * this._scaledCellWidth,
|
||||
y * this._scaledCellHeight,
|
||||
width * this._scaledCellWidth,
|
||||
height * this._scaledCellHeight);
|
||||
x * this._scaledCellWidth,
|
||||
y * this._scaledCellHeight,
|
||||
width * this._scaledCellWidth,
|
||||
height * this._scaledCellHeight);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,9 +227,9 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
this._ctx.textBaseline = 'middle';
|
||||
this._clipRow(terminal, y);
|
||||
this._ctx.fillText(
|
||||
cell.getChars(),
|
||||
x * this._scaledCellWidth + this._scaledCharLeft,
|
||||
y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2);
|
||||
cell.getChars(),
|
||||
x * this._scaledCellWidth + this._scaledCharLeft,
|
||||
y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -240,10 +240,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
private _clipRow(terminal: Terminal, y: number): void {
|
||||
this._ctx.beginPath();
|
||||
this._ctx.rect(
|
||||
0,
|
||||
y * this._scaledCellHeight,
|
||||
terminal.cols * this._scaledCellWidth,
|
||||
this._scaledCellHeight);
|
||||
0,
|
||||
y * this._scaledCellHeight,
|
||||
terminal.cols * this._scaledCellWidth,
|
||||
this._scaledCellHeight);
|
||||
this._ctx.clip();
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
import { ICellData } from 'common/Types';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { IRenderDimensions, IRequestRefreshRowsEvent } from 'browser/renderer/Types';
|
||||
import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types';
|
||||
import { IEventEmitter } from 'common/EventEmitter';
|
||||
|
||||
interface ICursorState {
|
||||
@@ -34,7 +34,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
container: HTMLElement,
|
||||
zIndex: number,
|
||||
colors: IColorSet,
|
||||
private _onRequestRefreshRowsEvent: IEventEmitter<IRequestRefreshRowsEvent>
|
||||
private _onRequestRefreshRowsEvent: IEventEmitter<IRequestRedrawEvent>
|
||||
) {
|
||||
super(container, 'cursor', zIndex, true, colors);
|
||||
this._state = {
|
||||
@@ -76,14 +76,14 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
if (this._cursorBlinkStateManager) {
|
||||
this._cursorBlinkStateManager.pause();
|
||||
}
|
||||
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.cursorY, end: terminal.buffer.cursorY });
|
||||
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY });
|
||||
}
|
||||
|
||||
public onFocus(terminal: Terminal): void {
|
||||
if (this._cursorBlinkStateManager) {
|
||||
this._cursorBlinkStateManager.resume(terminal);
|
||||
} else {
|
||||
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.cursorY, end: terminal.buffer.cursorY });
|
||||
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
}
|
||||
// Request a refresh from the terminal as management of rendering is being
|
||||
// moved back to the terminal
|
||||
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.cursorY, end: terminal.buffer.cursorY });
|
||||
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY });
|
||||
}
|
||||
|
||||
public onCursorMove(terminal: Terminal): void {
|
||||
@@ -125,8 +125,11 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
return;
|
||||
}
|
||||
|
||||
const cursorY = terminal.buffer.baseY + terminal.buffer.cursorY;
|
||||
const viewportRelativeCursorY = cursorY - terminal.buffer.viewportY;
|
||||
const cursorY = terminal.buffer.active.baseY + terminal.buffer.active.cursorY;
|
||||
const viewportRelativeCursorY = cursorY - terminal.buffer.active.viewportY;
|
||||
|
||||
// in case cursor.x == cols adjust visual cursor to cols - 1
|
||||
const cursorX = Math.min(terminal.buffer.active.cursorX, terminal.cols - 1);
|
||||
|
||||
// Don't draw the cursor if it's off-screen
|
||||
if (viewportRelativeCursorY < 0 || viewportRelativeCursorY >= terminal.rows) {
|
||||
@@ -135,7 +138,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
}
|
||||
|
||||
// TODO: Need fast buffer API for loading cell
|
||||
(terminal as any)._core.buffer.lines.get(cursorY).loadCell(terminal.buffer.cursorX, this._cell);
|
||||
(terminal as any)._core.buffer.lines.get(cursorY).loadCell(cursorX, this._cell);
|
||||
if (this._cell.content === undefined) {
|
||||
return;
|
||||
}
|
||||
@@ -146,12 +149,12 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
this._ctx.fillStyle = this._colors.cursor.css;
|
||||
const cursorStyle = terminal.getOption('cursorStyle');
|
||||
if (cursorStyle && cursorStyle !== 'block') {
|
||||
this._cursorRenderers[cursorStyle](terminal, terminal.buffer.cursorX, viewportRelativeCursorY, this._cell);
|
||||
this._cursorRenderers[cursorStyle](terminal, cursorX, viewportRelativeCursorY, this._cell);
|
||||
} else {
|
||||
this._renderBlurCursor(terminal, terminal.buffer.cursorX, viewportRelativeCursorY, this._cell);
|
||||
this._renderBlurCursor(terminal, cursorX, viewportRelativeCursorY, this._cell);
|
||||
}
|
||||
this._ctx.restore();
|
||||
this._state.x = terminal.buffer.cursorX;
|
||||
this._state.x = cursorX;
|
||||
this._state.y = viewportRelativeCursorY;
|
||||
this._state.isFocused = false;
|
||||
this._state.style = cursorStyle;
|
||||
@@ -167,7 +170,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
|
||||
if (this._state) {
|
||||
// The cursor is already in the correct spot, don't redraw
|
||||
if (this._state.x === terminal.buffer.cursorX &&
|
||||
if (this._state.x === cursorX &&
|
||||
this._state.y === viewportRelativeCursorY &&
|
||||
this._state.isFocused === isTerminalFocused(terminal) &&
|
||||
this._state.style === terminal.getOption('cursorStyle') &&
|
||||
@@ -178,10 +181,10 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
}
|
||||
|
||||
this._ctx.save();
|
||||
this._cursorRenderers[terminal.getOption('cursorStyle') || 'block'](terminal, terminal.buffer.cursorX, viewportRelativeCursorY, this._cell);
|
||||
this._cursorRenderers[terminal.getOption('cursorStyle') || 'block'](terminal, cursorX, viewportRelativeCursorY, this._cell);
|
||||
this._ctx.restore();
|
||||
|
||||
this._state.x = terminal.buffer.cursorX;
|
||||
this._state.x = cursorX;
|
||||
this._state.y = viewportRelativeCursorY;
|
||||
this._state.isFocused = false;
|
||||
this._state.style = terminal.getOption('cursorStyle');
|
||||
@@ -204,7 +207,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
private _renderBarCursor(terminal: Terminal, x: number, y: number, cell: ICellData): void {
|
||||
this._ctx.save();
|
||||
this._ctx.fillStyle = this._colors.cursor.css;
|
||||
this._fillLeftLineAtCell(x, y);
|
||||
this._fillLeftLineAtCell(x, y, terminal.getOption('cursorWidth'));
|
||||
this._ctx.restore();
|
||||
}
|
||||
|
||||
@@ -299,7 +302,7 @@ class CursorBlinkStateManager {
|
||||
// the regular interval is setup in order to support restarting the blink
|
||||
// animation in a lightweight way (without thrashing clearInterval and
|
||||
// setInterval).
|
||||
this._blinkStartTimeout = <number><any>setTimeout(() => {
|
||||
this._blinkStartTimeout = window.setTimeout(() => {
|
||||
// Check if another animation restart was requested while this was being
|
||||
// started
|
||||
if (this._animationTimeRestarted) {
|
||||
@@ -319,7 +322,7 @@ class CursorBlinkStateManager {
|
||||
});
|
||||
|
||||
// Setup the blink interval
|
||||
this._blinkInterval = <number><any>setInterval(() => {
|
||||
this._blinkInterval = window.setInterval(() => {
|
||||
// Adjust the animation time if it was restarted
|
||||
if (this._animationTimeRestarted) {
|
||||
// calc time diff
|
||||
@@ -357,6 +360,9 @@ class CursorBlinkStateManager {
|
||||
}
|
||||
|
||||
public resume(terminal: Terminal): void {
|
||||
// Clear out any existing timers just in case
|
||||
this.pause();
|
||||
|
||||
this._animationTimeRestarted = undefined;
|
||||
this._restartInterval();
|
||||
this.restartBlinkAnimation(terminal);
|
||||
|
||||
@@ -3,21 +3,23 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ILinkifierAccessor } from '../../../../src/Types';
|
||||
import { Terminal } from 'xterm';
|
||||
import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
|
||||
import { is256Color } from '../atlas/CharAtlasUtils';
|
||||
import { IColorSet, ILinkifierEvent } from 'browser/Types';
|
||||
import { ITerminal, IColorSet, ILinkifierEvent } from 'browser/Types';
|
||||
import { IRenderDimensions } from 'browser/renderer/Types';
|
||||
|
||||
export class LinkRenderLayer extends BaseRenderLayer {
|
||||
private _state: ILinkifierEvent | undefined;
|
||||
|
||||
constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ILinkifierAccessor) {
|
||||
constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ITerminal) {
|
||||
super(container, 'link', zIndex, true, colors);
|
||||
terminal.linkifier.onLinkHover(e => this._onLinkHover(e));
|
||||
terminal.linkifier.onLinkLeave(e => this._onLinkLeave(e));
|
||||
terminal.linkifier.onShowLinkUnderline(e => this._onShowLinkUnderline(e));
|
||||
terminal.linkifier.onHideLinkUnderline(e => this._onHideLinkUnderline(e));
|
||||
|
||||
terminal.linkifier2.onShowLinkUnderline(e => this._onShowLinkUnderline(e));
|
||||
terminal.linkifier2.onHideLinkUnderline(e => this._onHideLinkUnderline(e));
|
||||
}
|
||||
|
||||
public resize(terminal: Terminal, dim: IRenderDimensions): void {
|
||||
@@ -42,7 +44,7 @@ export class LinkRenderLayer extends BaseRenderLayer {
|
||||
}
|
||||
}
|
||||
|
||||
private _onLinkHover(e: ILinkifierEvent): void {
|
||||
private _onShowLinkUnderline(e: ILinkifierEvent): void {
|
||||
if (e.fg === INVERTED_DEFAULT_COLOR) {
|
||||
this._ctx.fillStyle = this._colors.background.css;
|
||||
} else if (e.fg !== undefined && is256Color(e.fg)) {
|
||||
@@ -66,7 +68,7 @@ export class LinkRenderLayer extends BaseRenderLayer {
|
||||
this._state = e;
|
||||
}
|
||||
|
||||
private _onLinkLeave(e: ILinkifierEvent): void {
|
||||
private _onHideLinkUnderline(e: ILinkifierEvent): void {
|
||||
this._clearCurrentLink();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ export interface IRenderLayer extends IDisposable {
|
||||
/**
|
||||
* Calls when the selection changes.
|
||||
*/
|
||||
onSelectionChanged(terminal: Terminal, start: [number, number], end: [number, number], columnSelectMode: boolean): void;
|
||||
onSelectionChanged(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void;
|
||||
|
||||
/**
|
||||
* Registers a handler to join characters to render as a group
|
||||
|
||||
@@ -12,17 +12,29 @@
|
||||
"removeComments": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"common/*": [ "../../../src/common/*" ],
|
||||
"browser/*": [ "../../../src/browser/*" ]
|
||||
"common/*": [
|
||||
"../../../src/common/*"
|
||||
],
|
||||
"browser/*": [
|
||||
"../../../src/browser/*"
|
||||
]
|
||||
},
|
||||
"strict": true
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../out-test/api/TestUtils"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../../src/common" },
|
||||
{ "path": "../../../src/browser" }
|
||||
{
|
||||
"path": "../../../src/common"
|
||||
},
|
||||
{
|
||||
"path": "../../../src/browser"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+9
-2
@@ -3,17 +3,19 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, IDisposable, ITerminalAddon } from 'xterm';
|
||||
import { Terminal, ITerminalAddon } from 'xterm';
|
||||
|
||||
declare module 'xterm-addon-webgl' {
|
||||
/**
|
||||
* An xterm.js addon that provides search functionality.
|
||||
*/
|
||||
export class WebglAddon implements ITerminalAddon {
|
||||
public textureAtlas?: HTMLCanvasElement;
|
||||
|
||||
constructor(preserveDrawingBuffer?: boolean);
|
||||
|
||||
/**
|
||||
* Activates the addon
|
||||
* Activates the addon.
|
||||
* @param terminal The terminal the addon is being loaded in.
|
||||
*/
|
||||
public activate(terminal: Terminal): void;
|
||||
@@ -22,5 +24,10 @@ declare module 'xterm-addon-webgl' {
|
||||
* Disposes the addon.
|
||||
*/
|
||||
public dispose(): void;
|
||||
|
||||
/**
|
||||
* Clears the terminal's texture atlas and triggers a redraw.
|
||||
*/
|
||||
public clearTextureAtlas(): void;
|
||||
}
|
||||
}
|
||||
|
||||
+43
-22
@@ -6,13 +6,11 @@
|
||||
jobs:
|
||||
- job: Linux
|
||||
pool:
|
||||
vmImage: 'ubuntu-16.04'
|
||||
variables:
|
||||
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1
|
||||
vmImage: 'ubuntu-18.04'
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '8.x'
|
||||
versionSpec: '10.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: YarnInstaller@3
|
||||
inputs:
|
||||
@@ -27,7 +25,9 @@ jobs:
|
||||
displayName: 'Install dependencies and build'
|
||||
- script: |
|
||||
yarn test-unit-coverage --forbid-only
|
||||
EXIT_CODE=$?
|
||||
./node_modules/.bin/nyc report --reporter=cobertura
|
||||
exit $EXIT_CODE
|
||||
displayName: 'Unit tests'
|
||||
- script: yarn lint
|
||||
displayName: 'Lint'
|
||||
@@ -39,13 +39,11 @@ jobs:
|
||||
|
||||
- job: macOS
|
||||
pool:
|
||||
vmImage: 'xcode9-macos10.13'
|
||||
variables:
|
||||
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1
|
||||
vmImage: 'macOS-10.15'
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '8.x'
|
||||
versionSpec: '10.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: CacheBeta@1
|
||||
inputs:
|
||||
@@ -62,12 +60,10 @@ jobs:
|
||||
- job: Windows
|
||||
pool:
|
||||
vmImage: 'vs2017-win2016'
|
||||
variables:
|
||||
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '8.x'
|
||||
versionSpec: '10.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: CacheBeta@1
|
||||
inputs:
|
||||
@@ -83,39 +79,66 @@ jobs:
|
||||
|
||||
- job: Linux_IntegrationTests
|
||||
pool:
|
||||
vmImage: 'ubuntu-16.04'
|
||||
vmImage: 'ubuntu-18.04'
|
||||
steps:
|
||||
- script: |
|
||||
# source: https://github.com/microsoft/playwright/issues/1041
|
||||
sudo apt update
|
||||
sudo apt install libwoff1 libopus0 libwebp6 libwebpdemux2 libenchant1c2a libgudev-1.0-0 libsecret-1-0 libhyphen0 libgdk-pixbuf2.0-0 libegl1 libnotify4 libxslt1.1 libevent-2.1-6 libgles2 libgl1 libegl1 libvpx5
|
||||
# for chromium
|
||||
sudo apt install libnss3 libxss1 libasound2
|
||||
# for firefox
|
||||
sudo apt install libdbus-glib-1-2 libxt6
|
||||
displayName: Install required packages
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '8.x'
|
||||
versionSpec: '10.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: YarnInstaller@3
|
||||
inputs:
|
||||
versionSpec: '1.x'
|
||||
displayName: 'Install Yarn'
|
||||
- task: CacheBeta@1
|
||||
inputs:
|
||||
key: yarn2 | $(Agent.OS) | yarn.lock
|
||||
path: node_modules
|
||||
displayName: Cache node modules
|
||||
- script: yarn --frozen-lockfile
|
||||
displayName: 'Install dependencies and build'
|
||||
- script: |
|
||||
yarn start &
|
||||
sleep 10
|
||||
yarn test-api --headless --forbid-only
|
||||
displayName: 'Linux Integration tests'
|
||||
displayName: 'Start test server'
|
||||
- script: yarn test-api-chromium --headless --forbid-only
|
||||
displayName: 'Integration tests (Chromium)'
|
||||
- script: xvfb-run --auto-servernum -- bash -c "yarn test-api-firefox --headless --forbid-only"
|
||||
displayName: 'Integration tests (Firefox)'
|
||||
|
||||
- job: macOS_IntegrationTests
|
||||
pool:
|
||||
vmImage: 'xcode9-macos10.13'
|
||||
vmImage: 'macOS-10.15'
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '8.x'
|
||||
versionSpec: '10.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: CacheBeta@1
|
||||
inputs:
|
||||
key: yarn2 | $(Agent.OS) | yarn.lock
|
||||
path: node_modules
|
||||
displayName: Cache node modules
|
||||
- script: yarn --frozen-lockfile
|
||||
displayName: 'Install dependencies and build'
|
||||
- script: |
|
||||
yarn start &
|
||||
sleep 10
|
||||
yarn test-api --headless --forbid-only
|
||||
displayName: 'MacOS Integration tests'
|
||||
displayName: 'Start test server'
|
||||
- script: yarn test-api-chromium --headless --forbid-only
|
||||
displayName: 'Integration tests (Chromium)'
|
||||
- script: yarn test-api-firefox --headless --forbid-only
|
||||
displayName: 'Integration tests (Firefox)'
|
||||
- script: yarn test-api-webkit --headless --forbid-only
|
||||
displayName: 'Integration tests (Webkit)'
|
||||
|
||||
- job: Release
|
||||
dependsOn:
|
||||
@@ -127,12 +150,10 @@ jobs:
|
||||
condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['FORCE_RELEASE'], 'true')))
|
||||
pool:
|
||||
vmImage: 'ubuntu-16.04'
|
||||
variables:
|
||||
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: '8.x'
|
||||
versionSpec: '10.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: YarnInstaller@3
|
||||
inputs:
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) 2020 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
const playwright = require('playwright-core');
|
||||
const fs = require('fs');
|
||||
|
||||
// Default to chromium
|
||||
let browserType = playwright['chromium'];
|
||||
const index = process.argv.indexOf('--browser');
|
||||
if (index !== -1 && process.argv.length > index + 1 && typeof process.argv[index + 1] === 'string') {
|
||||
const string = process.argv[index + 1];
|
||||
if (string === 'firefox' || string === 'webkit') {
|
||||
browserType = playwright[string];
|
||||
}
|
||||
}
|
||||
|
||||
const exists = fs.existsSync(browserType.executablePath());
|
||||
if (!exists) {
|
||||
console.log(`Downloading ${browserType.name()}`);
|
||||
browserType.downloadBrowserIfNeeded().then(() => process.exit(0));
|
||||
}
|
||||
@@ -0,0 +1,509 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*
|
||||
* Script to extract vt features documented in docstrings.
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const Mustache = require('mustache');
|
||||
|
||||
/**
|
||||
* regexp to fetch all comments
|
||||
* Fetches all multiline comments and single lines containing '// @vt:'.
|
||||
*/
|
||||
const REX_COMMENTS = /^\s*?[/][*][*]([\s\S]*?)[*][/]|^\s*?\/\/ ([@]vt[:].*?)$/mug;
|
||||
|
||||
/**
|
||||
* regexp to parse the @vt line
|
||||
* expected data - "@vt: <status> <kind> <mnemonic> "<name>" "<sequence>" "<short description>"
|
||||
*/
|
||||
const REX_VT_LINE = /^[@]vt\:\s*(\w+|#\w+|#\w+\[.*?\])\s*(\w+)\s*(\w+)\s*"(.*?)"\s*"(.*?)"\s*"(.*?)".*$/;
|
||||
|
||||
// known vt command types
|
||||
const TYPES = [
|
||||
'C0',
|
||||
'C1',
|
||||
'ESC',
|
||||
'CSI',
|
||||
'DCS',
|
||||
'OSC',
|
||||
'APC',
|
||||
'PM',
|
||||
'SOS'
|
||||
];
|
||||
|
||||
const MARKDOWN_TMPL = `---
|
||||
title: Supported Terminal Sequences
|
||||
category: API
|
||||
---
|
||||
|
||||
{::options parse_block_html="true" /}
|
||||
|
||||
# Supported Terminal Sequences
|
||||
|
||||
xterm.js version: {{version}}
|
||||
|
||||
## Table of Contents
|
||||
|
||||
<nav>
|
||||
|
||||
- [General notes](#general-notes)
|
||||
{{#C0.length}}
|
||||
- [C0](#c0)
|
||||
{{/C0.length}}
|
||||
{{#C1.length}}
|
||||
- [C1](#c1)
|
||||
{{/C1.length}}
|
||||
{{#CSI.length}}
|
||||
- [CSI](#csi)
|
||||
{{/CSI.length}}
|
||||
{{#DCS.length}}
|
||||
- [DCS](#dcs)
|
||||
{{/DCS.length}}
|
||||
{{#ESC.length}}
|
||||
- [ESC](#esc)
|
||||
{{/ESC.length}}
|
||||
{{#OSC.length}}
|
||||
- [OSC](#osc)
|
||||
{{/OSC.length}}
|
||||
|
||||
</nav>
|
||||
|
||||
|
||||
## General notes
|
||||
|
||||
This document lists xterm.js' support of terminal sequences. The sequences are grouped by their sequence type:
|
||||
|
||||
- C0: single byte command (7bit control codes, byte range \`\\x00\` .. \`\\x1F\`, \`\\x7F\`)
|
||||
- C1: single byte command (8bit control codes, byte range \`\\x80\` .. \`\\x9F\`)
|
||||
- ESC: sequence starting with \`ESC\` (\`\\x1B\`)
|
||||
- CSI - Control Sequence Introducer: sequence starting with \`ESC [\` (7bit) or CSI (\`\\x9B\`, 8bit)
|
||||
- DCS - Device Control String: sequence starting with \`ESC P\` (7bit) or DCS (\`\\x90\`, 8bit)
|
||||
- OSC - Operating System Command: sequence starting with \`ESC ]\` (7bit) or OSC (\`\\x9D\`, 8bit)
|
||||
|
||||
Application Program Command (APC), Privacy Message (PM) and Start of String (SOS) are recognized but not supported,
|
||||
any sequence of these types will be ignored. They are also not hookable by the API.
|
||||
|
||||
Note that the list only contains sequences implemented in xterm.js' core codebase. Missing sequences are either
|
||||
not supported or unstable/experimental. Furthermore addons or integrations can provide additional custom sequences.
|
||||
|
||||
To denote the sequences the tables use the same abbreviations as xterm does:
|
||||
- \`Ps\`: A single (usually optional) numeric parameter, composed of one or more decimal digits.
|
||||
- \`Pm\`: A multiple numeric parameter composed of any number of single numeric parameters, separated by ; character(s),
|
||||
e.g. \` Ps ; Ps ; ... \`.
|
||||
- \`Pt\`: A text parameter composed of printable characters. Note that for most commands with \`Pt\` only
|
||||
ASCII printables are specified to work. Additionally the parser will let pass any codepoint greater than C1 as printable.
|
||||
|
||||
|
||||
|
||||
{{#C0.length}}
|
||||
## C0
|
||||
|
||||
| Mnemonic | Name | Sequence | Short Description | Support |
|
||||
| -------- | ---- | -------- | ----------------- | ------- |
|
||||
{{#C0}}
|
||||
| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} |
|
||||
{{/C0}}
|
||||
|
||||
{{#C0.hasLongDescriptions}}
|
||||
{{#C0}}
|
||||
{{#longDescription.length}}
|
||||
<section class="sequence-details">
|
||||
|
||||
### {{name}}
|
||||
{{#longDescription}}
|
||||
{{{.}}}
|
||||
{{/longDescription}}
|
||||
|
||||
</section>
|
||||
{{/longDescription.length}}
|
||||
{{/C0}}
|
||||
{{/C0.hasLongDescriptions}}
|
||||
|
||||
{{/C0.length}}
|
||||
|
||||
|
||||
{{#C1.length}}
|
||||
## C1
|
||||
|
||||
| Mnemonic | Name | Sequence | Short Description | Support |
|
||||
| -------- | ---- | -------- | ----------------- | ------- |
|
||||
{{#C1}}
|
||||
| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} |
|
||||
{{/C1}}
|
||||
|
||||
{{#C1.hasLongDescriptions}}
|
||||
{{#C1}}
|
||||
{{#longDescription.length}}
|
||||
<section class="sequence-details">
|
||||
|
||||
### {{name}}
|
||||
{{#longDescription}}
|
||||
{{{.}}}
|
||||
{{/longDescription}}
|
||||
|
||||
</section>
|
||||
{{/longDescription.length}}
|
||||
{{/C1}}
|
||||
{{/C1.hasLongDescriptions}}
|
||||
|
||||
{{/C1.length}}
|
||||
|
||||
|
||||
{{#CSI.length}}
|
||||
## CSI
|
||||
|
||||
| Mnemonic | Name | Sequence | Short Description | Support |
|
||||
| -------- | ---- | -------- | ----------------- | ------- |
|
||||
{{#CSI}}
|
||||
| {{mnemonic}} | {{name}} | \`\`{{{sequence}}}\`\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} |
|
||||
{{/CSI}}
|
||||
|
||||
{{#CSI.hasLongDescriptions}}
|
||||
{{#CSI}}
|
||||
{{#longDescription.length}}
|
||||
<section class="sequence-details">
|
||||
|
||||
### {{name}}
|
||||
{{#longDescription}}
|
||||
{{{.}}}
|
||||
{{/longDescription}}
|
||||
|
||||
</section>
|
||||
{{/longDescription.length}}
|
||||
{{/CSI}}
|
||||
{{/CSI.hasLongDescriptions}}
|
||||
|
||||
{{/CSI.length}}
|
||||
|
||||
|
||||
{{#DCS.length}}
|
||||
## DCS
|
||||
|
||||
| Mnemonic | Name | Sequence | Short Description | Support |
|
||||
| -------- | ---- | -------- | ----------------- | ------- |
|
||||
{{#DCS}}
|
||||
| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} |
|
||||
{{/DCS}}
|
||||
|
||||
{{#DCS.hasLongDescriptions}}
|
||||
{{#DCS}}
|
||||
{{#longDescription.length}}
|
||||
<section class="sequence-details">
|
||||
|
||||
### {{name}}
|
||||
{{#longDescription}}
|
||||
{{{.}}}
|
||||
{{/longDescription}}
|
||||
|
||||
</section>
|
||||
{{/longDescription.length}}
|
||||
{{/DCS}}
|
||||
{{/DCS.hasLongDescriptions}}
|
||||
|
||||
{{/DCS.length}}
|
||||
|
||||
|
||||
{{#ESC.length}}
|
||||
## ESC
|
||||
|
||||
| Mnemonic | Name | Sequence | Short Description | Support |
|
||||
| -------- | ---- | -------- | ----------------- | ------- |
|
||||
{{#ESC}}
|
||||
| {{mnemonic}} | {{name}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} |
|
||||
{{/ESC}}
|
||||
|
||||
{{#ESC.hasLongDescriptions}}
|
||||
{{#ESC}}
|
||||
{{#longDescription.length}}
|
||||
<section class="sequence-details">
|
||||
|
||||
### {{name}}
|
||||
{{#longDescription}}
|
||||
{{{.}}}
|
||||
{{/longDescription}}
|
||||
|
||||
</section>
|
||||
{{/longDescription.length}}
|
||||
{{/ESC}}
|
||||
{{/ESC.hasLongDescriptions}}
|
||||
|
||||
{{/ESC.length}}
|
||||
|
||||
|
||||
{{#OSC.length}}
|
||||
## OSC
|
||||
|
||||
**Note**: Other than listed in the table, the parser recognizes both ST (ECMA-48) and BEL (xterm) as OSC sequence finalizer.
|
||||
|
||||
| Identifier | Sequence | Short Description | Support |
|
||||
| ---------- | -------- | ----------------- | ------- |
|
||||
{{#OSC}}
|
||||
| {{mnemonic}} | \`{{sequence}}\` | {{{shortDescription}}} {{#longDescription.length}}_[more](#{{longTarget}}){: .link-details}_{{/longDescription.length}} | {{{status}}} |
|
||||
{{/OSC}}
|
||||
|
||||
{{#OSC.hasLongDescriptions}}
|
||||
{{#OSC}}
|
||||
{{#longDescription.length}}
|
||||
<section class="sequence-details">
|
||||
|
||||
### {{name}}
|
||||
{{#longDescription}}
|
||||
{{{.}}}
|
||||
{{/longDescription}}
|
||||
|
||||
</section>
|
||||
{{/longDescription.length}}
|
||||
{{/OSC}}
|
||||
{{/OSC.hasLongDescriptions}}
|
||||
|
||||
{{/OSC.length}}
|
||||
|
||||
<script type="text/javascript">
|
||||
const linkStates = {};
|
||||
|
||||
function hideDetailSections() {
|
||||
for (let section of document.getElementsByClassName('sequence-details')) section.style.display = 'none';
|
||||
}
|
||||
|
||||
function decorateDetailLinks() {
|
||||
for (let link of document.getElementsByClassName('link-details')) {
|
||||
link.addEventListener("click", toggleDetails, false);
|
||||
if (linkStates[link.href] === undefined) {
|
||||
linkStates[link.href] = {open: false, links: [], trElem: null};
|
||||
}
|
||||
linkStates[link.href].links.push(link);
|
||||
}
|
||||
}
|
||||
|
||||
function openDetails(link) {
|
||||
const closestTr = link.closest('tr');
|
||||
if (!closestTr || !closestTr.parentNode) return;
|
||||
const closestSection = document.getElementById(link.href.split('#')[1]).closest('section');
|
||||
if (!closestSection || !closestSection.innerHTML) return;
|
||||
const content = closestSection.innerHTML.split('</h3>')[1];
|
||||
|
||||
const newRow = closestTr.parentNode.insertRow(closestTr.rowIndex);
|
||||
const cell = newRow.insertCell(0);
|
||||
cell.innerHTML = '<div style="font-style: italic">' + content + '</div>';
|
||||
cell.colSpan = closestTr.children.length;
|
||||
const state = linkStates[link.href];
|
||||
state.trElem = newRow;
|
||||
state.open = true;
|
||||
if (link.innerHTML === 'more') link.innerHTML = 'less';
|
||||
}
|
||||
|
||||
function closeDetails(link) {
|
||||
const state = linkStates[link.href];
|
||||
if (state.trElem && state.trElem.rowIndex !== -1) {
|
||||
if (state.trElem.parentNode) state.trElem.parentNode.deleteRow(state.trElem.rowIndex-1); // why -1 here?
|
||||
state.trElem.remove();
|
||||
state.trElem = null;
|
||||
}
|
||||
state.open = false;
|
||||
if (link.innerHTML === 'less') link.innerHTML = 'more';
|
||||
}
|
||||
|
||||
function toggleDetails(ev) {
|
||||
if (!ev.target || !ev.target.href) return;
|
||||
const isOpen = linkStates[ev.target.href].open;
|
||||
if (isOpen === undefined) return;
|
||||
if (isOpen) closeDetails(ev.target);
|
||||
else openDetails(ev.target);
|
||||
ev.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
function load() {
|
||||
hideDetailSections();
|
||||
decorateDetailLinks();
|
||||
}
|
||||
load(); // we are inlined behind all needed data, thus dont wait for onload (avoids flickering)
|
||||
</script>
|
||||
`
|
||||
|
||||
// support status marcos
|
||||
// applied for:
|
||||
// - status field of single @vt line
|
||||
// - all lines in long description
|
||||
const MACRO = [
|
||||
// #Y - supported
|
||||
[/#Y/g, s => '<span title="supported">✓</span>'],
|
||||
// #N - unsupported
|
||||
[/#N/g, s => '<span title="unsupported">✗</span>'],
|
||||
// #P[reason] - partial support with a reason as title
|
||||
[/#P\[(.*?)\]/g, (s, p1) => `<span title="${p1}" style="text-decoration: underline">Partial</span>`],
|
||||
// #B[reason] - supported but broken in a certain way, reason in title
|
||||
[/#B\[(.*?)\]/g, (s, p1) => `<span title="${p1}" style="text-decoration: underline">Broken</span>`]
|
||||
];
|
||||
|
||||
function applyMacros(s) {
|
||||
for (let i = 0; i < MACRO.length; ++i) {
|
||||
s = s.replace(MACRO[i][0], MACRO[i][1]);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// function replaceStatus(s) {
|
||||
// if (s === 'supported') return '<span title="supported">✓</span>';
|
||||
// if (s === 'unsupported') return '<span title="unsupported">✗</span>';
|
||||
// return s;
|
||||
// }
|
||||
|
||||
function createAnchorSlug(s) {
|
||||
return s.toLowerCase().split(' ').join('-');
|
||||
}
|
||||
|
||||
function empty(ar) {
|
||||
return !ar.filter(Boolean, ar).length;
|
||||
}
|
||||
|
||||
function* parseMultiLineGen(filename, s) {
|
||||
if (!~s.indexOf('@vt:')) {
|
||||
return;
|
||||
}
|
||||
const lines = s.split('\n').map(el => el.trim().replace(/[*]/, '').replace(/\s/, ''));
|
||||
let grabLine = false;
|
||||
let longDescription = [];
|
||||
let feature = undefined;
|
||||
let noLineCount = 0;
|
||||
for (const line of lines) {
|
||||
if (grabLine) {
|
||||
if (!line) noLineCount++;
|
||||
if (noLineCount >= 2) {
|
||||
if (feature) {
|
||||
feature.longDescription = empty(longDescription) ? [] : longDescription;
|
||||
feature.longTarget = createAnchorSlug(feature.name);
|
||||
yield feature;
|
||||
}
|
||||
grabLine = false;
|
||||
longDescription = [];
|
||||
feature = undefined;
|
||||
noLineCount = 0;
|
||||
}
|
||||
else if (line.indexOf('@vt:') === 0) {
|
||||
if (feature) {
|
||||
feature.longDescription = empty(longDescription) ? [] : longDescription;
|
||||
feature.longTarget = createAnchorSlug(feature.name);
|
||||
yield feature;
|
||||
}
|
||||
grabLine = true;
|
||||
longDescription = [];
|
||||
feature = undefined;
|
||||
noLineCount = 0;
|
||||
} else {
|
||||
//longDescription.push(line);
|
||||
longDescription.push(applyMacros(line));
|
||||
if (line) noLineCount = 0;
|
||||
}
|
||||
}
|
||||
if (line.indexOf('@vt:') === 0) {
|
||||
feature = parseSingleLine(filename, line);
|
||||
grabLine = true;
|
||||
}
|
||||
}
|
||||
if (grabLine && feature) {
|
||||
feature.longDescription = empty(longDescription) ? [] : longDescription;
|
||||
feature.longTarget = createAnchorSlug(feature.name);
|
||||
yield feature;
|
||||
}
|
||||
}
|
||||
|
||||
function parseSingleLine(filename, s) {
|
||||
const line = s.trim();
|
||||
const match = line.match(REX_VT_LINE);
|
||||
if (match !== null) {
|
||||
if (!~TYPES.indexOf(match[2])) {
|
||||
throw new Error(`unkown vt-command type "${match[2]}" specified in "${filename}"`);
|
||||
}
|
||||
return {
|
||||
status: match[1],
|
||||
type: match[2],
|
||||
mnemonic: match[3],
|
||||
name: match[4],
|
||||
sequence: match[5],
|
||||
shortDescription: match[6],
|
||||
longDescription: [],
|
||||
longTarget: '',
|
||||
source: filename
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getSorter(entry) {
|
||||
switch (entry) {
|
||||
case 'C0':
|
||||
case 'C1':
|
||||
// NOTE: expects hex value notation at last position in sequence
|
||||
return (a, b) => parseInt(a.sequence.slice(-2), 16) - parseInt(b.sequence.slice(-2), 16);
|
||||
case 'OSC':
|
||||
// NOTE: expects the decimal function identifier in mnemonic
|
||||
return (a, b) => parseInt(a.mnemonic) - parseInt(b.mnemonic);
|
||||
case 'DCS':
|
||||
return (a, b) => a.mnemonic > b.mnemonic;
|
||||
case 'CSI':
|
||||
case 'ESC':
|
||||
// default sort order by final byte
|
||||
return (a, b) => {
|
||||
// ugly hack to fix sorting of workaround in HPA sequence "CSI Ps ` "
|
||||
// for HPA compare with length - 2 instead
|
||||
const HPA = 'CSI Ps ` ';
|
||||
const aa = a.sequence === HPA ? a.sequence.slice(0, -1) : a.sequence;
|
||||
const bb = b.sequence === HPA ? b.sequence.slice(0, -1) : b.sequence;
|
||||
return aa.charCodeAt(aa.length - 1) - bb.charCodeAt(bb.length - 1);
|
||||
};
|
||||
default:
|
||||
return (a, b) => a.sequence > b.sequence;
|
||||
}
|
||||
};
|
||||
|
||||
function postProcessData(features) {
|
||||
const featureTable = {};
|
||||
for (const feature of features) {
|
||||
// feature.status = replaceStatus(feature.status);
|
||||
feature.status = applyMacros(feature.status);
|
||||
if (featureTable[feature.type] === undefined) {
|
||||
featureTable[feature.type] = [];
|
||||
}
|
||||
featureTable[feature.type].push(feature);
|
||||
if (feature.longDescription.length) {
|
||||
featureTable[feature.type].hasLongDescriptions = true;
|
||||
}
|
||||
}
|
||||
for (const entry in featureTable) {
|
||||
featureTable[entry].sort(getSorter(entry));
|
||||
}
|
||||
// console.error(featureTable);
|
||||
featureTable.version = require('../package.json').version;
|
||||
console.log(Mustache.render(MARKDOWN_TMPL, featureTable));
|
||||
}
|
||||
|
||||
function main(filenames) {
|
||||
// console.error(filenames);
|
||||
let leftToProcess = filenames.length;
|
||||
const features = [];
|
||||
for (const filename of filenames) {
|
||||
fs.readFile(filename, 'utf-8', (err, data) => {
|
||||
let match;
|
||||
while ((match = REX_COMMENTS.exec(data)) !== null) {
|
||||
if (match.index === REX_COMMENTS.lastIndex) {
|
||||
REX_COMMENTS.lastIndex++;
|
||||
}
|
||||
if (match[1]) {
|
||||
for (let feature of parseMultiLineGen(filename, match[1])) {
|
||||
if (feature) features.push(feature);
|
||||
}
|
||||
} else {
|
||||
const feature = parseSingleLine(filename, match[2]);
|
||||
if (feature) features.push(feature);
|
||||
}
|
||||
}
|
||||
leftToProcess--;
|
||||
if (!leftToProcess) {
|
||||
postProcessData(features);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
main(process.argv.slice(2))
|
||||
@@ -28,7 +28,10 @@ if (changedFiles.some(e => e.search(/^addons\//) === -1)) {
|
||||
const addonPackageDirs = [
|
||||
path.resolve(__dirname, '../addons/xterm-addon-attach'),
|
||||
path.resolve(__dirname, '../addons/xterm-addon-fit'),
|
||||
path.resolve(__dirname, '../addons/xterm-addon-ligatures'),
|
||||
path.resolve(__dirname, '../addons/xterm-addon-search'),
|
||||
path.resolve(__dirname, '../addons/xterm-addon-serialize'),
|
||||
path.resolve(__dirname, '../addons/xterm-addon-unicode11'),
|
||||
path.resolve(__dirname, '../addons/xterm-addon-web-links'),
|
||||
path.resolve(__dirname, '../addons/xterm-addon-webgl')
|
||||
];
|
||||
|
||||
+2
-2
@@ -13,8 +13,8 @@ const env = { ...process.env };
|
||||
env.NODE_PATH = path.resolve(__dirname, '../out');
|
||||
|
||||
let testFiles = [
|
||||
'./out/*test.js',
|
||||
'./out/**/*test.js'
|
||||
'./out/**/*test.js',
|
||||
'./addons/**/out/*test.js',
|
||||
];
|
||||
|
||||
let flagArgs = [];
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
const cp = require('child_process');
|
||||
const path = require('path');
|
||||
|
||||
|
||||
// Add `out` to the NODE_PATH so absolute paths can be resolved.
|
||||
const env = { ...process.env };
|
||||
env.NODE_PATH = path.resolve(__dirname, '../out');
|
||||
|
||||
let testFiles = [
|
||||
'./addons/**/out/*api.js',
|
||||
'./out-test/**/*api.js',
|
||||
];
|
||||
|
||||
|
||||
let flagArgs = [];
|
||||
|
||||
if (process.argv.length > 2) {
|
||||
const args = process.argv.slice(2);
|
||||
flagArgs = args.filter(e => e.startsWith('--')).map(arg => arg.split('=')).reduce((arr, val) => arr.concat([...val], []));
|
||||
console.info(flagArgs);
|
||||
// ability to inject particular test files via
|
||||
// yarn test [testFileA testFileB ...]
|
||||
files = args.filter(e => !e.startsWith('--'));
|
||||
if (files.length) {
|
||||
testFiles = files;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
env.DEBUG = flagArgs.indexOf('--debug') >= 0 ? 'debug' : '';
|
||||
|
||||
const run = cp.spawnSync(
|
||||
npmBinScript('mocha'),
|
||||
[...testFiles, ...flagArgs],
|
||||
{
|
||||
cwd: path.resolve(__dirname, '..'),
|
||||
env,
|
||||
stdio: 'inherit'
|
||||
}
|
||||
);
|
||||
|
||||
function npmBinScript(script) {
|
||||
return path.resolve(__dirname, `../node_modules/.bin/` + (process.platform === 'win32' ? `${script}.cmd` : script));
|
||||
}
|
||||
|
||||
process.exit(run.status);
|
||||
+4
-4
@@ -59,10 +59,10 @@
|
||||
}
|
||||
|
||||
.xterm .xterm-helper-textarea {
|
||||
/*
|
||||
* HACK: to fix IE's blinking cursor
|
||||
* Move textarea out of the screen to the far left, so that the cursor is not visible.
|
||||
*/
|
||||
padding: 0;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
/* Move textarea out of the screen to the far left, so that the cursor is not visible */
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
left: -9999em;
|
||||
|
||||
+59
-14
@@ -8,20 +8,24 @@
|
||||
/// <reference path="../typings/xterm.d.ts"/>
|
||||
|
||||
// Use tsc version (yarn watch)
|
||||
import { Terminal } from '../out/public/Terminal';
|
||||
import { Terminal } from '../out/browser/public/Terminal';
|
||||
import { AttachAddon } from '../addons/xterm-addon-attach/out/AttachAddon';
|
||||
import { FitAddon } from '../addons/xterm-addon-fit/out/FitAddon';
|
||||
import { SearchAddon, ISearchOptions } from '../addons/xterm-addon-search/out/SearchAddon';
|
||||
import { SerializeAddon } from '../addons/xterm-addon-serialize/out/SerializeAddon';
|
||||
import { WebLinksAddon } from '../addons/xterm-addon-web-links/out/WebLinksAddon';
|
||||
import { WebglAddon } from '../addons/xterm-addon-webgl/out/WebglAddon';
|
||||
import { Unicode11Addon } from '../addons/xterm-addon-unicode11/out/Unicode11Addon';
|
||||
|
||||
// Use webpacked version (yarn package)
|
||||
// import { Terminal } from '../lib/xterm';
|
||||
// import { AttachAddon } from 'xterm-addon-attach';
|
||||
// import { FitAddon } from 'xterm-addon-fit';
|
||||
// import { SearchAddon, ISearchOptions } from 'xterm-addon-search';
|
||||
// import { SerializeAddon } from 'xterm-addon-serialize';
|
||||
// import { WebLinksAddon } from 'xterm-addon-web-links';
|
||||
// import { WebglAddon } from 'xterm-addon-webgl';
|
||||
// import { Unicode11Addon } from 'xterm-addon-unicode11';
|
||||
|
||||
// Pulling in the module's types relies on the <reference> above, it's looks a
|
||||
// little weird here as we're importing "this" module
|
||||
@@ -33,8 +37,10 @@ export interface IWindowWithTerminal extends Window {
|
||||
AttachAddon?: typeof AttachAddon;
|
||||
FitAddon?: typeof FitAddon;
|
||||
SearchAddon?: typeof SearchAddon;
|
||||
SerializeAddon?: typeof SerializeAddon;
|
||||
WebLinksAddon?: typeof WebLinksAddon;
|
||||
WebglAddon?: typeof WebglAddon;
|
||||
Unicode11Addon?: typeof Unicode11Addon;
|
||||
}
|
||||
declare let window: IWindowWithTerminal;
|
||||
|
||||
@@ -44,7 +50,7 @@ let socketURL;
|
||||
let socket;
|
||||
let pid;
|
||||
|
||||
type AddonType = 'attach' | 'fit' | 'search' | 'web-links' | 'webgl';
|
||||
type AddonType = 'attach' | 'fit' | 'search' | 'serialize' | 'unicode11' | 'web-links' | 'webgl';
|
||||
|
||||
interface IDemoAddon<T extends AddonType> {
|
||||
name: T;
|
||||
@@ -53,14 +59,18 @@ interface IDemoAddon<T extends AddonType> {
|
||||
T extends 'attach' ? typeof AttachAddon :
|
||||
T extends 'fit' ? typeof FitAddon :
|
||||
T extends 'search' ? typeof SearchAddon :
|
||||
T extends 'serialize' ? typeof SerializeAddon :
|
||||
T extends 'web-links' ? typeof WebLinksAddon :
|
||||
T extends 'unicode11' ? typeof Unicode11Addon :
|
||||
typeof WebglAddon;
|
||||
instance?:
|
||||
T extends 'attach' ? AttachAddon :
|
||||
T extends 'fit' ? FitAddon :
|
||||
T extends 'search' ? SearchAddon :
|
||||
T extends 'serialize' ? SerializeAddon :
|
||||
T extends 'web-links' ? WebLinksAddon :
|
||||
T extends 'webgl' ? WebglAddon :
|
||||
T extends 'unicode11' ? typeof Unicode11Addon :
|
||||
never;
|
||||
}
|
||||
|
||||
@@ -68,8 +78,10 @@ const addons: { [T in AddonType]: IDemoAddon<T>} = {
|
||||
attach: { name: 'attach', ctor: AttachAddon, canChange: false },
|
||||
fit: { name: 'fit', ctor: FitAddon, canChange: false },
|
||||
search: { name: 'search', ctor: SearchAddon, canChange: true },
|
||||
serialize: { name: 'serialize', ctor: SerializeAddon, canChange: true },
|
||||
'web-links': { name: 'web-links', ctor: WebLinksAddon, canChange: true },
|
||||
webgl: { name: 'webgl', ctor: WebglAddon, canChange: true }
|
||||
webgl: { name: 'webgl', ctor: WebglAddon, canChange: true },
|
||||
unicode11: { name: 'unicode11', ctor: Unicode11Addon, canChange: true }
|
||||
};
|
||||
|
||||
const terminalContainer = document.getElementById('terminal-container');
|
||||
@@ -100,9 +112,15 @@ const disposeRecreateButtonHandler = () => {
|
||||
term = null;
|
||||
window.term = null;
|
||||
socket = null;
|
||||
addons.attach.instance = undefined;
|
||||
addons.fit.instance = undefined;
|
||||
addons.search.instance = undefined;
|
||||
addons.serialize.instance = undefined;
|
||||
addons.unicode11.instance = undefined;
|
||||
addons['web-links'].instance = undefined;
|
||||
addons.webgl.instance = undefined;
|
||||
document.getElementById('dispose').innerHTML = 'Recreate Terminal';
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
createTerminal();
|
||||
document.getElementById('dispose').innerHTML = 'Dispose terminal';
|
||||
}
|
||||
@@ -113,11 +131,14 @@ if (document.location.pathname === '/test') {
|
||||
window.AttachAddon = AttachAddon;
|
||||
window.FitAddon = FitAddon;
|
||||
window.SearchAddon = SearchAddon;
|
||||
window.SerializeAddon = SerializeAddon;
|
||||
window.Unicode11Addon = Unicode11Addon;
|
||||
window.WebLinksAddon = WebLinksAddon;
|
||||
window.WebglAddon = WebglAddon;
|
||||
} else {
|
||||
createTerminal();
|
||||
document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler);
|
||||
document.getElementById('serialize').addEventListener('click', serializeButtonHandler);
|
||||
}
|
||||
|
||||
function createTerminal(): void {
|
||||
@@ -133,12 +154,17 @@ function createTerminal(): void {
|
||||
|
||||
// Load addons
|
||||
const typedTerm = term as TerminalType;
|
||||
addons['web-links'].instance = new WebLinksAddon();
|
||||
addons.search.instance = new SearchAddon();
|
||||
addons.serialize.instance = new SerializeAddon();
|
||||
addons.fit.instance = new FitAddon();
|
||||
typedTerm.loadAddon(addons['web-links'].instance);
|
||||
typedTerm.loadAddon(addons.search.instance);
|
||||
addons.unicode11.instance = new Unicode11Addon();
|
||||
// TODO: Remove arguments when link provider API is the default
|
||||
addons['web-links'].instance = new WebLinksAddon(undefined, undefined, true);
|
||||
typedTerm.loadAddon(addons.fit.instance);
|
||||
typedTerm.loadAddon(addons.search.instance);
|
||||
typedTerm.loadAddon(addons.serialize.instance);
|
||||
typedTerm.loadAddon(addons.unicode11.instance);
|
||||
typedTerm.loadAddon(addons['web-links'].instance);
|
||||
|
||||
window.term = term; // Expose `term` to window for debugging purposes
|
||||
term.onResize((size: { cols: number, rows: number }) => {
|
||||
@@ -239,12 +265,10 @@ function initOptions(term: TerminalType): void {
|
||||
// Internal only options
|
||||
'cancelEvents',
|
||||
'convertEol',
|
||||
'handler',
|
||||
'screenKeys',
|
||||
'termName',
|
||||
'useFlowControl',
|
||||
// Complex option
|
||||
'theme'
|
||||
'theme',
|
||||
'windowOptions'
|
||||
];
|
||||
const stringOptions = {
|
||||
bellSound: null,
|
||||
@@ -339,11 +363,19 @@ function initAddons(term: TerminalType): void {
|
||||
if (!addon.canChange) {
|
||||
checkbox.disabled = true;
|
||||
}
|
||||
checkbox.addEventListener('change', () => {
|
||||
addDomListener(checkbox, 'change', () => {
|
||||
if (checkbox.checked) {
|
||||
addon.instance = new addon.ctor();
|
||||
term.loadAddon(addon.instance);
|
||||
if (name === 'webgl') {
|
||||
setTimeout(() => {
|
||||
document.body.appendChild((addon.instance as WebglAddon).textureAtlas);
|
||||
}, 0);
|
||||
}
|
||||
} else {
|
||||
if (name === 'webgl') {
|
||||
document.body.removeChild((addon.instance as WebglAddon).textureAtlas);
|
||||
}
|
||||
addon.instance!.dispose();
|
||||
addon.instance = undefined;
|
||||
}
|
||||
@@ -360,7 +392,9 @@ function initAddons(term: TerminalType): void {
|
||||
wrapper.appendChild(label);
|
||||
fragment.appendChild(wrapper);
|
||||
});
|
||||
document.getElementById('addons-container').appendChild(fragment);
|
||||
const container = document.getElementById('addons-container');
|
||||
container.innerHTML = '';
|
||||
container.appendChild(fragment);
|
||||
}
|
||||
|
||||
function addDomListener(element: HTMLElement, type: string, handler: (...args: any[]) => any): void {
|
||||
@@ -377,3 +411,14 @@ function updateTerminalSize(): void {
|
||||
terminalContainer.style.height = height;
|
||||
addons.fit.instance.fit();
|
||||
}
|
||||
|
||||
function serializeButtonHandler(): void {
|
||||
const output = addons.serialize.instance.serialize();
|
||||
const outputString = JSON.stringify(output);
|
||||
|
||||
document.getElementById('serialize-output').innerText = outputString;
|
||||
if ((document.getElementById('write-to-terminal') as HTMLInputElement).checked) {
|
||||
term.reset();
|
||||
term.write(output);
|
||||
}
|
||||
}
|
||||
|
||||
+21
-12
@@ -2,6 +2,11 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>xterm.js demo</title>
|
||||
<!--
|
||||
WARNING: This demo is a barebones implementation designed for development and evaluation
|
||||
purposes only. It is definitely NOT production ready and does not aim to be so. Exposing the
|
||||
demo to the public as is would introduce security risks for the host.
|
||||
-->
|
||||
<link rel="shortcut icon" type="image/png" href="/logo.png">
|
||||
<link rel="stylesheet" href="/xterm.css" />
|
||||
<link rel="stylesheet" href="/style.css" />
|
||||
@@ -12,7 +17,16 @@
|
||||
<h1 style="color: #2D2E2C">xterm.js: A terminal for the <em style="color: #5DA5D5">web</em></h1>
|
||||
<div id="terminal-container"></div>
|
||||
<div>
|
||||
<h3>Actions</h3>
|
||||
<h3>Options</h3>
|
||||
<p>These options can be set in the <code>Terminal</code> constructor or by using the <code>Terminal.setOption</code> function.</p>
|
||||
<div id="options-container"></div>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Addons</h3>
|
||||
<p>Addons can be loaded and unloaded on a particular terminal to extend its functionality.</p>
|
||||
<div id="addons-container"></div>
|
||||
<h3>Addons Control</h3>
|
||||
<h4>SearchAddon</h4>
|
||||
<p>
|
||||
<label>Find next <input id="find-next"/></label>
|
||||
<label>Find previous <input id="find-previous"/></label>
|
||||
@@ -20,16 +34,12 @@
|
||||
<label>Case sensitive<input type="checkbox" id="case-sensitive"/></label>
|
||||
<label>Whole word<input type="checkbox" id="whole-word"/></label>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Options</h3>
|
||||
<p>These options can be set in the <code>Terminal</code> constructor or using the <code>Terminal.setOption</code> function.</p>
|
||||
<div id="options-container"></div>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Addons</h3>
|
||||
<p>Addons can be loaded and unloaded on a particular terminal to extend its functionality.</p>
|
||||
<div id="addons-container"></div>
|
||||
<h4>SerializeAddon</h4>
|
||||
<p>
|
||||
<button id="serialize">Serialize the content of terminal</button>
|
||||
<label><input type="checkbox" id="write-to-terminal">Write back to terminal</label>
|
||||
<div><pre id="serialize-output"></pre></div>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Style</h3>
|
||||
@@ -39,7 +49,6 @@
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<p><strong>Attention:</strong> The demo is a barebones implementation and is designed for the development and evaluation of xterm.js only. Exposing the demo to the public as is would introduce security risks for the host.</p>
|
||||
<button id="dispose" title="This is used to testing memory leaks">Dispose terminal</button>
|
||||
<script src="dist/client-bundle.js" defer ></script>
|
||||
</body>
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/**
|
||||
* WARNING: This demo is a barebones implementation designed for development and evaluation
|
||||
* purposes only. It is definitely NOT production ready and does not aim to be so. Exposing the
|
||||
* demo to the public as is would introduce security risks for the host.
|
||||
**/
|
||||
|
||||
var express = require('express');
|
||||
var expressWs = require('express-ws');
|
||||
var os = require('os');
|
||||
|
||||
@@ -30,3 +30,14 @@ p {
|
||||
padding-left: 20px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
pre {
|
||||
display: block;
|
||||
padding: 9.5px;
|
||||
font-size: 13px;
|
||||
color: #c7254e;
|
||||
background-color: #f9f2f4;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"xterm-addon-attach": ["../addons/xterm-addon-attach"],
|
||||
"xterm-addon-fit": ["../addons/xterm-addon-fit"],
|
||||
"xterm-addon-search": ["../addons/xterm-addon-search"],
|
||||
"xterm-addon-serialize": ["../addons/xterm-addon-serialize"],
|
||||
"xterm-addon-web-links": ["../addons/xterm-addon-web-links"],
|
||||
"xterm-addon-webgl": ["../addons/xterm-addon-webgl"]
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
web:
|
||||
image: xtermjs/xterm.js:latest
|
||||
build: .
|
||||
volumes:
|
||||
- ./:/usr/src/app
|
||||
ports:
|
||||
- ${XTERMJS_PORT:-3000}:3000
|
||||
command: ["npm", "start"]
|
||||
|
||||
watch:
|
||||
build: .
|
||||
volumes:
|
||||
- ./:/usr/src/app
|
||||
command: ["npm", "run", "watch"]
|
||||
+36
-27
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "xterm",
|
||||
"description": "Full xterm terminal, in your browser",
|
||||
"version": "4.3.0",
|
||||
"version": "4.9.0",
|
||||
"main": "lib/xterm.js",
|
||||
"style": "css/xterm.css",
|
||||
"types": "typings/xterm.d.ts",
|
||||
@@ -11,10 +11,16 @@
|
||||
"prepackage": "npm run build",
|
||||
"package": "webpack",
|
||||
"start": "node demo/start",
|
||||
"lint": "tslint 'src/**/*.ts' 'addons/*/src/**/*.ts'",
|
||||
"lint": "eslint -c .eslintrc.json --max-warnings 0 --ext .ts src/ addons/",
|
||||
"test": "npm run test-unit",
|
||||
"posttest": "npm run lint",
|
||||
"test-api": "mocha \"**/*.api.js\"",
|
||||
"test-api": "npm run test-api-chromium",
|
||||
"test-api-chromium": "node ./bin/test_api.js --browser=chromium --timeout=20000",
|
||||
"test-api-firefox": "node ./bin/test_api.js --browser=firefox --timeout=20000",
|
||||
"test-api-webkit": "node ./bin/test_api.js --browser=webkit --timeout=20000",
|
||||
"pretest-api-chromium": "node ./bin/download_browser.js --browser chromium",
|
||||
"pretest-api-firefox": "node ./bin/download_browser.js --browser firefox",
|
||||
"pretest-api-webkit": "node ./bin/download_browser.js --browser webkit",
|
||||
"test-unit": "node ./bin/test.js",
|
||||
"test-unit-coverage": "node ./bin/test.js --coverage",
|
||||
"build": "tsc -b ./tsconfig.all.json",
|
||||
@@ -26,38 +32,41 @@
|
||||
"benchmark": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json",
|
||||
"benchmark-baseline": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --baseline out-test/benchmark/test/benchmark/*benchmark.js",
|
||||
"benchmark-eval": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --eval out-test/benchmark/test/benchmark/*benchmark.js",
|
||||
"clean": "rm -rf lib out addons/*/lib addons/*/out"
|
||||
"clean": "rm -rf lib out addons/*/lib addons/*/out",
|
||||
"vtfeatures": "node bin/extract_vtfeatures.js src/**/*.ts src/*.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^3.4.34",
|
||||
"@types/chai": "^4.2.12",
|
||||
"@types/debug": "^4.1.5",
|
||||
"@types/deep-equal": "^1.0.1",
|
||||
"@types/glob": "^5.0.35",
|
||||
"@types/jsdom": "11.0.1",
|
||||
"@types/mocha": "^2.2.33",
|
||||
"@types/node": "6.0.108",
|
||||
"@types/puppeteer": "^1.12.4",
|
||||
"@types/glob": "^7.1.3",
|
||||
"@types/jsdom": "^16.2.4",
|
||||
"@types/mocha": "^8.0.3",
|
||||
"@types/node": "^10.17.17",
|
||||
"@types/utf8": "^2.1.6",
|
||||
"@types/webpack": "^4.4.11",
|
||||
"@types/ws": "^6.0.1",
|
||||
"chai": "3.5.0",
|
||||
"deep-equal": "^1.1.0",
|
||||
"@types/webpack": "^4.41.22",
|
||||
"@types/ws": "^7.2.6",
|
||||
"@typescript-eslint/eslint-plugin": "^4.0.0",
|
||||
"@typescript-eslint/parser": "^3.10.1",
|
||||
"chai": "^4.2.0",
|
||||
"deep-equal": "^2.0.3",
|
||||
"eslint": "^7.8.1",
|
||||
"express": "^4.17.1",
|
||||
"express-ws": "^4.0.0",
|
||||
"glob": "^7.0.5",
|
||||
"jsdom": "^11.11.0",
|
||||
"mocha": "^6.1.4",
|
||||
"node-pty": "0.7.6",
|
||||
"nyc": "13",
|
||||
"puppeteer": "^1.15.0",
|
||||
"source-map-loader": "^0.2.4",
|
||||
"ts-loader": "^6.0.4",
|
||||
"tslint": "^5.18.0",
|
||||
"tslint-consistent-codestyle": "^1.13.0",
|
||||
"typescript": "3.7",
|
||||
"jsdom": "^16.4.0",
|
||||
"mocha": "^8.1.3",
|
||||
"mustache": "^4.0.1",
|
||||
"node-pty": "^0.9.0",
|
||||
"nyc": "^15.1.0",
|
||||
"playwright-core": "^0.11.1",
|
||||
"source-map-loader": "^1.1.0",
|
||||
"ts-loader": "^8.0.3",
|
||||
"typescript": "4.0",
|
||||
"utf8": "^3.0.0",
|
||||
"webpack": "^4.35.3",
|
||||
"webpack-cli": "^3.1.0",
|
||||
"ws": "^7.0.0",
|
||||
"webpack": "^4.44.1",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"ws": "^7.3.1",
|
||||
"xterm-benchmark": "^0.1.3"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,442 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IRenderer, IRenderDimensions, CharacterJoinerHandler, IRequestRefreshRowsEvent } from 'browser/renderer/Types';
|
||||
import { IInputHandlingTerminal, ICompositionHelper, ITerminal, IBrowser, ITerminalOptions } from './Types';
|
||||
import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types';
|
||||
import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, CoreMouseEventType } from 'common/Types';
|
||||
import { Buffer } from 'common/buffer/Buffer';
|
||||
import * as Browser from 'common/Platform';
|
||||
import { IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm';
|
||||
import { Terminal } from './Terminal';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { IColorManager, IColorSet, ILinkMatcherOptions, ILinkifier, IViewport } from 'browser/Types';
|
||||
import { IOptionsService } from 'common/services/Services';
|
||||
import { EventEmitter } from 'common/EventEmitter';
|
||||
import { IParams, IFunctionIdentifier } from 'common/parser/Types';
|
||||
import { ISelectionService } from 'browser/services/Services';
|
||||
|
||||
export class TestTerminal extends Terminal {
|
||||
keyDown(ev: any): boolean { return this._keyDown(ev); }
|
||||
keyPress(ev: any): boolean { return this._keyPress(ev); }
|
||||
}
|
||||
|
||||
export class MockTerminal implements ITerminal {
|
||||
onBlur: IEvent<void>;
|
||||
onFocus: IEvent<void>;
|
||||
onA11yChar: IEvent<string>;
|
||||
onA11yTab: IEvent<number>;
|
||||
onCursorMove: IEvent<void>;
|
||||
onLineFeed: IEvent<void>;
|
||||
onSelectionChange: IEvent<void>;
|
||||
onData: IEvent<string>;
|
||||
onBinary: IEvent<string>;
|
||||
onTitleChange: IEvent<string>;
|
||||
onScroll: IEvent<number>;
|
||||
onKey: IEvent<{ key: string; domEvent: KeyboardEvent; }>;
|
||||
onRender: IEvent<{ start: number; end: number; }>;
|
||||
onResize: IEvent<{ cols: number; rows: number; }>;
|
||||
markers: IMarker[];
|
||||
optionsService: IOptionsService;
|
||||
addMarker(cursorYOffset: number): IMarker {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
selectLines(start: number, end: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
scrollToLine(line: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
static string: any;
|
||||
setOption(key: any, value: any): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
blur(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
focus(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
resize(columns: number, rows: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
writeln(data: string): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
paste(data: string): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
open(parent: HTMLElement): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
addEscHandler(id: IFunctionIdentifier, handler: () => boolean): IDisposable {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => boolean | void, options?: ILinkMatcherOptions): number {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
deregisterLinkMatcher(matcherId: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
hasSelection(): boolean {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getSelection(): string {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getSelectionPosition(): ISelectionPosition | undefined {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
clearSelection(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
select(column: number, row: number, length: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
selectAll(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
dispose(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
scrollPages(pageCount: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
scrollToTop(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
scrollToBottom(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
clear(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
write(data: string): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
writeUtf8(data: Uint8Array): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
bracketedPasteMode: boolean;
|
||||
renderer: IRenderer;
|
||||
linkifier: ILinkifier;
|
||||
isFocused: boolean;
|
||||
options: ITerminalOptions = {};
|
||||
element: HTMLElement;
|
||||
screenElement: HTMLElement;
|
||||
rowContainer: HTMLElement;
|
||||
selectionContainer: HTMLElement;
|
||||
selectionService: ISelectionService;
|
||||
textarea: HTMLTextAreaElement;
|
||||
rows: number;
|
||||
cols: number;
|
||||
browser: IBrowser = <any>Browser;
|
||||
writeBuffer: string[];
|
||||
children: HTMLElement[];
|
||||
cursorHidden: boolean;
|
||||
cursorState: number;
|
||||
scrollback: number;
|
||||
buffers: IBufferSet;
|
||||
buffer: IBuffer;
|
||||
viewport: IViewport;
|
||||
applicationCursor: boolean;
|
||||
handler(data: string): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
on(event: string, callback: (...args: any[]) => void): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
off(type: string, listener: XtermListener): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
addDisposableListener(type: string, handler: XtermListener): IDisposable {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
scrollLines(disp: number, suppressScrollEvent: boolean): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
scrollToRow(absoluteRow: number): number {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
cancel(ev: Event, force?: boolean): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
log(text: string): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
emit(event: string, data: any): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
reset(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
showCursor(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
refresh(start: number, end: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
registerCharacterJoiner(handler: CharacterJoinerHandler): number { return 0; }
|
||||
deregisterCharacterJoiner(joinerId: number): void { }
|
||||
}
|
||||
|
||||
export class MockInputHandlingTerminal implements IInputHandlingTerminal {
|
||||
onA11yCharEmitter: EventEmitter<string>;
|
||||
onA11yTabEmitter: EventEmitter<number>;
|
||||
element: HTMLElement;
|
||||
options: ITerminalOptions = {};
|
||||
cols: number;
|
||||
rows: number;
|
||||
charset: { [key: string]: string; };
|
||||
gcharset: number;
|
||||
glevel: number;
|
||||
charsets: { [key: string]: string; }[];
|
||||
applicationKeypad: boolean;
|
||||
applicationCursor: boolean;
|
||||
originMode: boolean;
|
||||
insertMode: boolean;
|
||||
wraparoundMode: boolean;
|
||||
bracketedPasteMode: boolean;
|
||||
curAttrData = new AttributeData();
|
||||
savedCols: number;
|
||||
x10Mouse: boolean;
|
||||
vt200Mouse: boolean;
|
||||
normalMouse: boolean;
|
||||
mouseEvents: CoreMouseEventType;
|
||||
sendFocus: boolean;
|
||||
utfMouse: boolean;
|
||||
sgrMouse: boolean;
|
||||
urxvtMouse: boolean;
|
||||
cursorHidden: boolean;
|
||||
buffers: IBufferSet;
|
||||
buffer: IBuffer = new MockBuffer();
|
||||
viewport: IViewport;
|
||||
selectionService: ISelectionService;
|
||||
focus(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
convertEol: boolean;
|
||||
bell(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
updateRange(y: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
scroll(isWrapped?: boolean): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
nextStop(x?: number): number {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
setgLevel(g: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
eraseAttrData(): IAttributeData {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
eraseRight(x: number, y: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
eraseLine(y: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
eraseLeft(x: number, y: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
prevStop(x?: number): number {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
is(term: string): boolean {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
setgCharset(g: number, charset: { [key: string]: string; }): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
resize(x: number, y: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
log(text: string, data?: any): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
reset(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
showCursor(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
refresh(start: number, end: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
matchColor(r1: number, g1: number, b1: number): number {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
error(text: string, data?: any): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
setOption(key: string, value: any): void {
|
||||
(<any>this.options)[key] = value;
|
||||
}
|
||||
on(type: string, listener: XtermListener): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
off(type: string, listener: XtermListener): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
emit(type: string, data?: any): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
addDisposableListener(type: string, handler: XtermListener): IDisposable {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
handler(data: string): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
handleTitle(title: string): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
export class MockBuffer implements IBuffer {
|
||||
markers: IMarker[];
|
||||
addMarker(y: number): IMarker {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
isCursorInViewport: boolean;
|
||||
lines: ICircularList<IBufferLine>;
|
||||
ydisp: number;
|
||||
ybase: number;
|
||||
hasScrollback: boolean;
|
||||
y: number;
|
||||
x: number;
|
||||
tabs: any;
|
||||
scrollBottom: number;
|
||||
scrollTop: number;
|
||||
savedY: number;
|
||||
savedX: number;
|
||||
savedCharset: ICharset | null;
|
||||
savedCurAttrData = new AttributeData();
|
||||
translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string {
|
||||
return Buffer.prototype.translateBufferLineToString.apply(this, arguments);
|
||||
}
|
||||
getWrappedRangeForLine(y: number): { first: number; last: number; } {
|
||||
return Buffer.prototype.getWrappedRangeForLine.apply(this, arguments);
|
||||
}
|
||||
nextStop(x?: number): number {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
prevStop(x?: number): number {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
setLines(lines: ICircularList<IBufferLine>): void {
|
||||
this.lines = lines;
|
||||
}
|
||||
getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine {
|
||||
return Buffer.prototype.getBlankLine.apply(this, arguments);
|
||||
}
|
||||
stringIndexToBufferIndex(lineIndex: number, stringIndex: number): number[] {
|
||||
return Buffer.prototype.stringIndexToBufferIndex.apply(this, arguments);
|
||||
}
|
||||
iterator(trimRight: boolean, startIndex?: number, endIndex?: number): IBufferStringIterator {
|
||||
return Buffer.prototype.iterator.apply(this, arguments);
|
||||
}
|
||||
getNullCell(attr?: IAttributeData): ICellData {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getWhitespaceCell(attr?: IAttributeData): ICellData {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
export class MockRenderer implements IRenderer {
|
||||
onRequestRefreshRows: IEvent<IRequestRefreshRowsEvent>;
|
||||
onCanvasResize: IEvent<{ width: number; height: number; }>;
|
||||
onRender: IEvent<{ start: number; end: number; }>;
|
||||
dispose(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
colorManager: IColorManager;
|
||||
on(type: string, listener: XtermListener): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
off(type: string, listener: XtermListener): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
emit(type: string, data?: any): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
addDisposableListener(type: string, handler: XtermListener): IDisposable {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
dimensions: IRenderDimensions;
|
||||
setColors(colors: IColorSet): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
onResize(cols: number, rows: number): void {}
|
||||
onCharSizeChanged(): void {}
|
||||
onBlur(): void {}
|
||||
onFocus(): void {}
|
||||
onSelectionChanged(start: [number, number], end: [number, number]): void {}
|
||||
onCursorMove(): void {}
|
||||
onOptionsChanged(): void {}
|
||||
onDevicePixelRatioChange(): void {}
|
||||
clear(): void {}
|
||||
renderRows(start: number, end: number): void {}
|
||||
registerCharacterJoiner(handler: CharacterJoinerHandler): number { return 0; }
|
||||
deregisterCharacterJoiner(): boolean { return true; }
|
||||
}
|
||||
|
||||
export class MockViewport implements IViewport {
|
||||
dispose(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
scrollBarWidth: number = 0;
|
||||
onThemeChange(colors: IColorSet): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
onWheel(ev: WheelEvent): boolean {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
onTouchStart(ev: TouchEvent): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
onTouchMove(ev: TouchEvent): boolean {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
syncScrollArea(): void { }
|
||||
getLinesScrolled(ev: WheelEvent): number {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
export class MockCompositionHelper implements ICompositionHelper {
|
||||
compositionstart(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
compositionupdate(ev: CompositionEvent): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
compositionend(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
updateCompositionElements(dontRecurse?: boolean): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
keydown(ev: KeyboardEvent): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Vendored
-257
@@ -1,257 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ITerminalOptions as IPublicTerminalOptions, IDisposable, IMarker, ISelectionPosition } from 'xterm';
|
||||
import { ICharset, IAttributeData, CharData, CoreMouseEventType } from 'common/Types';
|
||||
import { IEvent, IEventEmitter } from 'common/EventEmitter';
|
||||
import { IColorSet, ILinkifier, ILinkMatcherOptions, IViewport } from 'browser/Types';
|
||||
import { IOptionsService } from 'common/services/Services';
|
||||
import { IBuffer, IBufferSet } from 'common/buffer/Types';
|
||||
import { IParams, IFunctionIdentifier } from 'common/parser/Types';
|
||||
|
||||
export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean;
|
||||
|
||||
export type LineData = CharData[];
|
||||
|
||||
/**
|
||||
* This interface encapsulates everything needed from the Terminal by the
|
||||
* InputHandler. This cleanly separates the large amount of methods needed by
|
||||
* InputHandler cleanly from the ITerminal interface.
|
||||
*/
|
||||
export interface IInputHandlingTerminal {
|
||||
element: HTMLElement;
|
||||
options: ITerminalOptions;
|
||||
cols: number;
|
||||
rows: number;
|
||||
charset: ICharset;
|
||||
gcharset: number;
|
||||
glevel: number;
|
||||
charsets: ICharset[];
|
||||
applicationKeypad: boolean;
|
||||
originMode: boolean;
|
||||
insertMode: boolean;
|
||||
wraparoundMode: boolean;
|
||||
bracketedPasteMode: boolean;
|
||||
curAttrData: IAttributeData;
|
||||
savedCols: number;
|
||||
mouseEvents: CoreMouseEventType;
|
||||
sendFocus: boolean;
|
||||
|
||||
buffers: IBufferSet;
|
||||
buffer: IBuffer;
|
||||
viewport: IViewport;
|
||||
|
||||
onA11yCharEmitter: IEventEmitter<string>;
|
||||
onA11yTabEmitter: IEventEmitter<number>;
|
||||
|
||||
bell(): void;
|
||||
focus(): void;
|
||||
scroll(isWrapped?: boolean): void;
|
||||
setgLevel(g: number): void;
|
||||
eraseAttrData(): IAttributeData;
|
||||
is(term: string): boolean;
|
||||
setgCharset(g: number, charset: ICharset): void;
|
||||
resize(x: number, y: number): void;
|
||||
reset(): void;
|
||||
showCursor(): void;
|
||||
refresh(start: number, end: number): void;
|
||||
handleTitle(title: string): void;
|
||||
}
|
||||
|
||||
export interface ICompositionHelper {
|
||||
compositionstart(): void;
|
||||
compositionupdate(ev: CompositionEvent): void;
|
||||
compositionend(): void;
|
||||
updateCompositionElements(dontRecurse?: boolean): void;
|
||||
keydown(ev: KeyboardEvent): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the parser and handles actions generated by the parser.
|
||||
*/
|
||||
export interface IInputHandler {
|
||||
parse(data: string | Uint8Array): void;
|
||||
print(data: Uint32Array, start: number, end: number): void;
|
||||
|
||||
/** C0 BEL */ bell(): void;
|
||||
/** C0 LF */ lineFeed(): void;
|
||||
/** C0 CR */ carriageReturn(): void;
|
||||
/** C0 BS */ backspace(): void;
|
||||
/** C0 HT */ tab(): void;
|
||||
/** C0 SO */ shiftOut(): void;
|
||||
/** C0 SI */ shiftIn(): void;
|
||||
|
||||
/** CSI @ */ insertChars(params: IParams): void;
|
||||
/** CSI SP @ */ scrollLeft(params: IParams): void;
|
||||
/** CSI A */ cursorUp(params: IParams): void;
|
||||
/** CSI SP A */ scrollRight(params: IParams): void;
|
||||
/** CSI B */ cursorDown(params: IParams): void;
|
||||
/** CSI C */ cursorForward(params: IParams): void;
|
||||
/** CSI D */ cursorBackward(params: IParams): void;
|
||||
/** CSI E */ cursorNextLine(params: IParams): void;
|
||||
/** CSI F */ cursorPrecedingLine(params: IParams): void;
|
||||
/** CSI G */ cursorCharAbsolute(params: IParams): void;
|
||||
/** CSI H */ cursorPosition(params: IParams): void;
|
||||
/** CSI I */ cursorForwardTab(params: IParams): void;
|
||||
/** CSI J */ eraseInDisplay(params: IParams): void;
|
||||
/** CSI K */ eraseInLine(params: IParams): void;
|
||||
/** CSI L */ insertLines(params: IParams): void;
|
||||
/** CSI M */ deleteLines(params: IParams): void;
|
||||
/** CSI P */ deleteChars(params: IParams): void;
|
||||
/** CSI S */ scrollUp(params: IParams): void;
|
||||
/** CSI T */ scrollDown(params: IParams, collect?: string): void;
|
||||
/** CSI X */ eraseChars(params: IParams): void;
|
||||
/** CSI Z */ cursorBackwardTab(params: IParams): void;
|
||||
/** CSI ` */ charPosAbsolute(params: IParams): void;
|
||||
/** CSI a */ hPositionRelative(params: IParams): void;
|
||||
/** CSI b */ repeatPrecedingCharacter(params: IParams): void;
|
||||
/** CSI c */ sendDeviceAttributesPrimary(params: IParams): void;
|
||||
/** CSI > c */ sendDeviceAttributesSecondary(params: IParams): void;
|
||||
/** CSI d */ linePosAbsolute(params: IParams): void;
|
||||
/** CSI e */ vPositionRelative(params: IParams): void;
|
||||
/** CSI f */ hVPosition(params: IParams): void;
|
||||
/** CSI g */ tabClear(params: IParams): void;
|
||||
/** CSI h */ setMode(params: IParams, collect?: string): void;
|
||||
/** CSI l */ resetMode(params: IParams, collect?: string): void;
|
||||
/** CSI m */ charAttributes(params: IParams): void;
|
||||
/** CSI n */ deviceStatus(params: IParams, collect?: string): void;
|
||||
/** CSI p */ softReset(params: IParams, collect?: string): void;
|
||||
/** CSI q */ setCursorStyle(params: IParams, collect?: string): void;
|
||||
/** CSI r */ setScrollRegion(params: IParams, collect?: string): void;
|
||||
/** CSI s */ saveCursor(params: IParams): void;
|
||||
/** CSI u */ restoreCursor(params: IParams): void;
|
||||
/** CSI ' } */ insertColumns(params: IParams): void;
|
||||
/** CSI ' ~ */ deleteColumns(params: IParams): void;
|
||||
/** OSC 0
|
||||
OSC 2 */ setTitle(data: string): void;
|
||||
/** ESC E */ nextLine(): void;
|
||||
/** ESC = */ keypadApplicationMode(): void;
|
||||
/** ESC > */ keypadNumericMode(): void;
|
||||
/** ESC % G
|
||||
ESC % @ */ selectDefaultCharset(): void;
|
||||
/** ESC ( C
|
||||
ESC ) C
|
||||
ESC * C
|
||||
ESC + C
|
||||
ESC - C
|
||||
ESC . C
|
||||
ESC / C */ selectCharset(collectAndFlag: string): void;
|
||||
/** ESC D */ index(): void;
|
||||
/** ESC H */ tabSet(): void;
|
||||
/** ESC M */ reverseIndex(): void;
|
||||
/** ESC c */ reset(): void;
|
||||
/** ESC n
|
||||
ESC o
|
||||
ESC |
|
||||
ESC }
|
||||
ESC ~ */ setgLevel(level: number): void;
|
||||
/** ESC # 8 */ screenAlignmentPattern(): void;
|
||||
}
|
||||
|
||||
export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor {
|
||||
screenElement: HTMLElement;
|
||||
browser: IBrowser;
|
||||
buffer: IBuffer;
|
||||
buffers: IBufferSet;
|
||||
viewport: IViewport;
|
||||
bracketedPasteMode: boolean;
|
||||
optionsService: IOptionsService;
|
||||
// TODO: We should remove options once components adopt optionsService
|
||||
options: ITerminalOptions;
|
||||
|
||||
onBlur: IEvent<void>;
|
||||
onFocus: IEvent<void>;
|
||||
onA11yChar: IEvent<string>;
|
||||
onA11yTab: IEvent<number>;
|
||||
|
||||
scrollLines(disp: number, suppressScrollEvent?: boolean): void;
|
||||
cancel(ev: Event, force?: boolean): boolean | void;
|
||||
showCursor(): void;
|
||||
}
|
||||
|
||||
// Portions of the public API that are required by the internal Terminal
|
||||
export interface IPublicTerminal extends IDisposable {
|
||||
textarea: HTMLTextAreaElement | undefined;
|
||||
rows: number;
|
||||
cols: number;
|
||||
buffer: IBuffer;
|
||||
markers: IMarker[];
|
||||
onCursorMove: IEvent<void>;
|
||||
onData: IEvent<string>;
|
||||
onBinary: IEvent<string>;
|
||||
onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>;
|
||||
onLineFeed: IEvent<void>;
|
||||
onScroll: IEvent<number>;
|
||||
onSelectionChange: IEvent<void>;
|
||||
onRender: IEvent<{ start: number, end: number }>;
|
||||
onResize: IEvent<{ cols: number, rows: number }>;
|
||||
onTitleChange: IEvent<string>;
|
||||
blur(): void;
|
||||
focus(): void;
|
||||
resize(columns: number, rows: number): void;
|
||||
open(parent: HTMLElement): void;
|
||||
attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void;
|
||||
addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable;
|
||||
addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable;
|
||||
addEscHandler(id: IFunctionIdentifier, callback: () => boolean): IDisposable;
|
||||
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;
|
||||
registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number;
|
||||
deregisterLinkMatcher(matcherId: number): void;
|
||||
registerCharacterJoiner(handler: (text: string) => [number, number][]): number;
|
||||
deregisterCharacterJoiner(joinerId: number): void;
|
||||
addMarker(cursorYOffset: number): IMarker;
|
||||
hasSelection(): boolean;
|
||||
getSelection(): string;
|
||||
getSelectionPosition(): ISelectionPosition | undefined;
|
||||
clearSelection(): void;
|
||||
select(column: number, row: number, length: number): void;
|
||||
selectAll(): void;
|
||||
selectLines(start: number, end: number): void;
|
||||
dispose(): void;
|
||||
scrollLines(amount: number): void;
|
||||
scrollPages(pageCount: number): void;
|
||||
scrollToTop(): void;
|
||||
scrollToBottom(): void;
|
||||
scrollToLine(line: number): void;
|
||||
clear(): void;
|
||||
write(data: string | Uint8Array, callback?: () => void): void;
|
||||
paste(data: string): void;
|
||||
refresh(start: number, end: number): void;
|
||||
reset(): void;
|
||||
}
|
||||
|
||||
export interface IBufferAccessor {
|
||||
buffer: IBuffer;
|
||||
}
|
||||
|
||||
export interface IElementAccessor {
|
||||
readonly element: HTMLElement | undefined;
|
||||
}
|
||||
|
||||
export interface ILinkifierAccessor {
|
||||
linkifier: ILinkifier;
|
||||
}
|
||||
|
||||
// TODO: The options that are not in the public API should be reviewed
|
||||
export interface ITerminalOptions extends IPublicTerminalOptions {
|
||||
[key: string]: any;
|
||||
cancelEvents?: boolean;
|
||||
convertEol?: boolean;
|
||||
handler?: (data: string) => void;
|
||||
screenKeys?: boolean;
|
||||
termName?: string;
|
||||
useFlowControl?: boolean;
|
||||
}
|
||||
|
||||
export interface IBrowser {
|
||||
isNode: boolean;
|
||||
userAgent: string;
|
||||
platform: string;
|
||||
isFirefox: boolean;
|
||||
isMac: boolean;
|
||||
isIpad: boolean;
|
||||
isIphone: boolean;
|
||||
isWindows: boolean;
|
||||
}
|
||||
@@ -3,8 +3,8 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import * as Strings from './browser/LocalizableStrings';
|
||||
import { ITerminal } from './Types';
|
||||
import * as Strings from 'browser/LocalizableStrings';
|
||||
import { ITerminal } from 'browser/Types';
|
||||
import { IBuffer } from 'common/buffer/Types';
|
||||
import { isMac } from 'common/Platform';
|
||||
import { RenderDebouncer } from 'browser/RenderDebouncer';
|
||||
@@ -12,6 +12,7 @@ import { addDisposableDomListener } from 'browser/Lifecycle';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { ScreenDprMonitor } from 'browser/ScreenDprMonitor';
|
||||
import { IRenderService } from 'browser/services/Services';
|
||||
import { removeElementFromParent } from 'browser/Dom';
|
||||
|
||||
const MAX_ROWS_TO_READ = 20;
|
||||
|
||||
@@ -56,6 +57,7 @@ export class AccessibilityManager extends Disposable {
|
||||
|
||||
this._rowContainer = document.createElement('div');
|
||||
this._rowContainer.classList.add('xterm-accessibility-tree');
|
||||
this._rowContainer.setAttribute('role', 'document');
|
||||
this._rowElements = [];
|
||||
for (let i = 0; i < this._terminal.rows; i++) {
|
||||
this._rowElements[i] = this._createAccessibilityTreeNode();
|
||||
@@ -78,6 +80,9 @@ export class AccessibilityManager extends Disposable {
|
||||
this._liveRegion.setAttribute('aria-live', 'assertive');
|
||||
this._accessibilityTreeRoot.appendChild(this._liveRegion);
|
||||
|
||||
if (!this._terminal.element) {
|
||||
throw new Error('Cannot enable accessibility before Terminal.open');
|
||||
}
|
||||
this._terminal.element.insertAdjacentElement('afterbegin', this._accessibilityTreeRoot);
|
||||
|
||||
this.register(this._renderRowsDebouncer);
|
||||
@@ -102,7 +107,7 @@ export class AccessibilityManager extends Disposable {
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
this._terminal.element.removeChild(this._accessibilityTreeRoot);
|
||||
removeElementFromParent(this._accessibilityTreeRoot);
|
||||
this._rowElements.length = 0;
|
||||
}
|
||||
|
||||
@@ -236,9 +241,7 @@ export class AccessibilityManager extends Disposable {
|
||||
|
||||
// Only detach/attach on mac as otherwise messages can go unaccounced
|
||||
if (isMac) {
|
||||
if (this._liveRegion.parentNode) {
|
||||
this._accessibilityTreeRoot.removeChild(this._liveRegion);
|
||||
}
|
||||
removeElementFromParent(this._liveRegion);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,17 +42,17 @@ export function copyHandler(ev: ClipboardEvent, selectionService: ISelectionServ
|
||||
* @param ev The original paste event to be handled
|
||||
* @param term The terminal on which to apply the handled paste event
|
||||
*/
|
||||
export function handlePasteEvent(ev: ClipboardEvent, textarea: HTMLTextAreaElement, bracketedPasteMode: boolean, coreService: ICoreService): void {
|
||||
export function handlePasteEvent(ev: ClipboardEvent, textarea: HTMLTextAreaElement, coreService: ICoreService): void {
|
||||
ev.stopPropagation();
|
||||
if (ev.clipboardData) {
|
||||
const text = ev.clipboardData.getData('text/plain');
|
||||
paste(text, textarea, bracketedPasteMode, coreService);
|
||||
paste(text, textarea, coreService);
|
||||
}
|
||||
}
|
||||
|
||||
export function paste(text: string, textarea: HTMLTextAreaElement, bracketedPasteMode: boolean, coreService: ICoreService): void {
|
||||
export function paste(text: string, textarea: HTMLTextAreaElement, coreService: ICoreService): void {
|
||||
text = prepareTextForTerminal(text);
|
||||
text = bracketTextForPaste(text, bracketedPasteMode);
|
||||
text = bracketTextForPaste(text, coreService.decPrivateModes.bracketedPasteMode);
|
||||
coreService.triggerDataEvent(text, true);
|
||||
textarea.value = '';
|
||||
}
|
||||
@@ -70,7 +70,6 @@ export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextA
|
||||
const top = ev.clientY - pos.top - 10;
|
||||
|
||||
// Bring textarea at the cursor position
|
||||
textarea.style.position = 'absolute';
|
||||
textarea.style.width = '20px';
|
||||
textarea.style.height = '20px';
|
||||
textarea.style.left = `${left}px`;
|
||||
@@ -78,17 +77,6 @@ export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextA
|
||||
textarea.style.zIndex = '1000';
|
||||
|
||||
textarea.focus();
|
||||
|
||||
// Reset the terminal textarea's styling
|
||||
// Timeout needs to be long enough for click event to be handled.
|
||||
setTimeout(() => {
|
||||
textarea.style.position = '';
|
||||
textarea.style.width = '';
|
||||
textarea.style.height = '';
|
||||
textarea.style.left = '';
|
||||
textarea.style.top = '';
|
||||
textarea.style.zIndex = '';
|
||||
}, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user