Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2f8f9dc1a | ||
|
|
df13469b6e | ||
|
|
584ec681af | ||
|
|
0c5b5cca4a | ||
|
|
64663bf4a8 | ||
|
|
fe8334a873 | ||
|
|
c74fd40e2a | ||
|
|
0f8ff67451 | ||
|
|
8b0032c210 | ||
|
|
d9254ef2d9 | ||
|
|
66be05a962 | ||
|
|
0e85a09dd3 | ||
|
|
0c4ba9fd67 | ||
|
|
aca81c764a | ||
|
|
6b3ddb97d1 | ||
|
|
26ccf2a316 | ||
|
|
361894194a | ||
|
|
8c2db8ddb8 | ||
|
|
5546baa927 | ||
|
|
d814009721 | ||
|
|
08fd050c3f | ||
|
|
0b096d7d55 | ||
|
|
44cfb2dc87 | ||
|
|
b0624cadb7 | ||
|
|
045f361b0d | ||
|
|
cb77b9dc1a | ||
|
|
0723dac596 | ||
|
|
365f9b4c1b | ||
|
|
2cf90d90f7 | ||
|
|
da14f64d1b | ||
|
|
ef1b1c8ffc | ||
|
|
adde8ab2c3 | ||
|
|
22cc8079dc | ||
|
|
6d25706300 | ||
|
|
81419c980b | ||
|
|
097e21f641 | ||
|
|
b68180b97b | ||
|
|
d9059d18c0 | ||
|
|
5225a6e27a | ||
|
|
9055bace96 | ||
|
|
eb25e88754 | ||
|
|
0cb8ecc6f7 | ||
|
|
8cf76cc6e9 | ||
|
|
3c3a646d42 | ||
|
|
ff29fd69a3 | ||
|
|
91994a399c | ||
|
|
bef7137c92 | ||
|
|
11f62bab34 | ||
|
|
b66776a047 | ||
|
|
2c151f444c | ||
|
|
d8f8b060cb | ||
|
|
ea9376019c | ||
|
|
008d1df106 | ||
|
|
ec91a6c7f4 | ||
|
|
4b2ae6a79f | ||
|
|
2ec756fd6b | ||
|
|
f0d5b40121 | ||
|
|
31a0996b2f | ||
|
|
0bbd8f0a16 | ||
|
|
b1619e42fd | ||
|
|
4cfec2fc09 | ||
|
|
3d721a95bb | ||
|
|
dc3a136704 | ||
|
|
235ae2fbb9 | ||
|
|
1f518b0b45 | ||
|
|
ab34d53d6b | ||
|
|
aafb5333b9 | ||
|
|
5e2791f981 | ||
|
|
1ecc5674ac | ||
|
|
c9ad3a595a | ||
|
|
43558fa1dc | ||
|
|
15a867d2b7 | ||
|
|
43cb4f471f | ||
|
|
012051c1db | ||
|
|
b5483898da | ||
|
|
c7b4c2beaf | ||
|
|
8693d067e3 | ||
|
|
78d5fc9506 | ||
|
|
7ac4f1a9be | ||
|
|
670fb01fbf | ||
|
|
ec9608d54b | ||
|
|
dcffaf1c8c | ||
|
|
efac5293c0 | ||
|
|
c7563346a2 | ||
|
|
69906532b1 | ||
|
|
9404fae725 | ||
|
|
3542f15575 | ||
|
|
15d79143e2 | ||
|
|
f6dafee748 | ||
|
|
356d86ae74 | ||
|
|
e673c6ec97 | ||
|
|
3e5fcf7004 | ||
|
|
96d2a2bc68 | ||
|
|
b401992178 | ||
|
|
4c34351383 | ||
|
|
202b54afbb | ||
|
|
26ebc3d944 | ||
|
|
d0f36e9939 | ||
|
|
4c99c032ca | ||
|
|
6198556e50 | ||
|
|
514eba0e4e | ||
|
|
7279ee0f5f | ||
|
|
ccadf3fcca | ||
|
|
cb80ce0d2c | ||
|
|
9aae43961f | ||
|
|
b810687163 | ||
|
|
28d4ec779d | ||
|
|
cf4da6c748 | ||
|
|
e569b5669a | ||
|
|
695a9523fe | ||
|
|
01b8e28a7d | ||
|
|
e221030a2b | ||
|
|
88dc697392 | ||
|
|
99a270216c | ||
|
|
d06c920a2d | ||
|
|
5452986ce3 | ||
|
|
ae69841174 | ||
|
|
e6fc80c13b | ||
|
|
a10ee8f58c | ||
|
|
3f69da2455 | ||
|
|
a14a0834e9 | ||
|
|
c4f431849b | ||
|
|
ade3389cea | ||
|
|
6873ba0909 | ||
|
|
c3f31b5f65 | ||
|
|
3b62aa444b | ||
|
|
fb2b10b565 | ||
|
|
345553012f | ||
|
|
fddc13d98e | ||
|
|
aeb9f798c9 | ||
|
|
8fd44266a2 | ||
|
|
ef4155fbf6 | ||
|
|
92f27d3144 | ||
|
|
5183332f6e | ||
|
|
ee1de0980f | ||
|
|
c53183d94b | ||
|
|
1ee774d01d | ||
|
|
1c030f57ef | ||
|
|
c8bb321654 | ||
|
|
a1d71c9198 | ||
|
|
55cb43d70b | ||
|
|
551159e0c3 | ||
|
|
3bf31aa478 | ||
|
|
1316682343 | ||
|
|
7167b06bf8 | ||
|
|
34582f9f65 | ||
|
|
8dc9ec5733 | ||
|
|
e8adf8ad2b | ||
|
|
1306431e7c | ||
|
|
f7bc0fba4d | ||
|
|
639a2fd509 | ||
|
|
b00083e0e5 | ||
|
|
c3256556aa | ||
|
|
8dd11f5587 | ||
|
|
0f3ee21d9d | ||
|
|
a489037ec4 | ||
|
|
c48388af14 | ||
|
|
2207d356f2 | ||
|
|
795cdb52dc | ||
|
|
e79e67c3ce | ||
|
|
e0639e1b62 | ||
|
|
56b541b44f | ||
|
|
03e46fbdee | ||
|
|
ee0d6b4533 | ||
|
|
937a9bf433 | ||
|
|
3bcdc4a354 | ||
|
|
382cd0bbf4 | ||
|
|
e58a6975da | ||
|
|
833eb5216b | ||
|
|
742d28fcc3 | ||
|
|
cc78fdd63c | ||
|
|
20f22fb658 | ||
|
|
3c635f3ce5 | ||
|
|
9d8f6c9ae7 | ||
|
|
cc6893d0f2 | ||
|
|
ca4e99e810 | ||
|
|
8d7df670b4 | ||
|
|
4de7ed46bf | ||
|
|
f9aac1bcc6 | ||
|
|
92068f36f2 | ||
|
|
047729ee9e | ||
|
|
637a5bd9cc | ||
|
|
6cd5c2b846 | ||
|
|
365a9f949c | ||
|
|
d6de156cd8 | ||
|
|
23c9fca088 | ||
|
|
c2a69721f3 | ||
|
|
d1ad8a7831 | ||
|
|
cee2285818 | ||
|
|
ca19da16e0 | ||
|
|
5850cbcd05 | ||
|
|
b789a7e85b | ||
|
|
9f0dbdd5c7 | ||
|
|
98614f26ef | ||
|
|
3b16696672 | ||
|
|
0219ad2ed8 | ||
|
|
e771483041 | ||
|
|
4a7c3320fb | ||
|
|
9359dca0f3 | ||
|
|
c8ccc411d4 | ||
|
|
537c8bad10 | ||
|
|
1a55367fc9 | ||
|
|
4f7ee7dab0 | ||
|
|
da16dd7cae | ||
|
|
2aa7608ac0 | ||
|
|
74c3a4a6f7 | ||
|
|
d30fb89fde | ||
|
|
1b344ce789 | ||
|
|
99fbcd3d65 | ||
|
|
72063329fe | ||
|
|
adaf604ce0 | ||
|
|
d5f0fff523 | ||
|
|
43c3349494 | ||
|
|
b5d16ed7d7 | ||
|
|
aab67c2ed9 | ||
|
|
0461a822c2 | ||
|
|
7b87e78ed3 | ||
|
|
6e1628bc3b | ||
|
|
370c5a971d | ||
|
|
9a86e4e109 | ||
|
|
8cb46f2726 | ||
|
|
f4293a6dfa | ||
|
|
1c759f1d6f | ||
|
|
6918e83787 | ||
|
|
9626b1e39d | ||
|
|
9227aed906 | ||
|
|
0bd469f505 | ||
|
|
5bd803382a | ||
|
|
ef60e50e1d | ||
|
|
ed1ba14c6b | ||
|
|
a4080d3706 | ||
|
|
e2b2f98011 | ||
|
|
ed13218f90 | ||
|
|
1cdd2e00ab | ||
|
|
94eb08fb73 | ||
|
|
5e1ccb358b | ||
|
|
259338b8c2 | ||
|
|
a05bb50230 | ||
|
|
8a07b8c798 | ||
|
|
4d5046b5e7 | ||
|
|
12160a50cb | ||
|
|
375ccbd9e2 | ||
|
|
cc0f0d94f6 | ||
|
|
6309752208 | ||
|
|
4448ebe391 | ||
|
|
d1dac57f7a | ||
|
|
3422f84e00 | ||
|
|
ac6faf3dd8 | ||
|
|
7679475bfe | ||
|
|
5a932b2a20 | ||
|
|
b1ef934aea | ||
|
|
a2ab0ffc46 | ||
|
|
ecd7e69f1a | ||
|
|
fa3484cd14 | ||
|
|
f4846aa154 | ||
|
|
9b66208029 | ||
|
|
c43c3b411a | ||
|
|
f9a286a851 | ||
|
|
3de3912b96 | ||
|
|
23169e89c8 | ||
|
|
0f5f34e8f5 | ||
|
|
342e862d69 | ||
|
|
081fe3f301 | ||
|
|
fc528f6c0c | ||
|
|
411c974960 | ||
|
|
0ec125557a | ||
|
|
0b977fc35f | ||
|
|
0739a4be06 | ||
|
|
c4de07135f | ||
|
|
d52b53889e | ||
|
|
d9682bd6d6 | ||
|
|
411b80cdc0 | ||
|
|
db81c28bd7 | ||
|
|
c7fa2d55b4 | ||
|
|
7572dd5fa6 | ||
|
|
9942477bff | ||
|
|
635a4d7682 | ||
|
|
672dc1a251 | ||
|
|
6deaaa8ef8 | ||
|
|
f4267c4f74 | ||
|
|
1848fff4c9 | ||
|
|
991b2843d3 | ||
|
|
940bd2699e | ||
|
|
4a27b9a53f | ||
|
|
a31921ae73 | ||
|
|
27a7f1504c | ||
|
|
aee8ddeafb | ||
|
|
6c29ccfbd1 | ||
|
|
da7ea104de | ||
|
|
665a11acc0 | ||
|
|
dddc91fbcc | ||
|
|
ecb892d995 | ||
|
|
ada96a8373 | ||
|
|
cabe23b782 | ||
|
|
b25bf9562e | ||
|
|
e9da2a6330 | ||
|
|
9385b9309d | ||
|
|
facc2659e7 | ||
|
|
06852cd904 | ||
|
|
a1ee8aefbb | ||
|
|
eb3b3fa107 | ||
|
|
3457b73ac8 | ||
|
|
28b38dc76d | ||
|
|
659fb90c1c | ||
|
|
04b1ebf1a7 | ||
|
|
1685cf2d8f | ||
|
|
43d4d4f2bb | ||
|
|
3de3c0c6b6 | ||
|
|
bebe43e5e1 | ||
|
|
520f2bc9d6 | ||
|
|
996c25a364 | ||
|
|
d9d60063d7 | ||
|
|
ff523bfc79 | ||
|
|
94c01ec378 | ||
|
|
2b8820fdac | ||
|
|
0ec7b661f2 | ||
|
|
dc5efa886a | ||
|
|
e66b1c5704 | ||
|
|
6b8c43ed7f | ||
|
|
b9d374affa | ||
|
|
d2ef19dffe | ||
|
|
efdf37b887 | ||
|
|
6ffb8f545b | ||
|
|
2e4147b4fa | ||
|
|
07a8b8f4f7 | ||
|
|
74483fb294 | ||
|
|
4f18d842f9 | ||
|
|
188e197e20 | ||
|
|
7234bfb6eb | ||
|
|
97feb3321c | ||
|
|
3e1a607179 | ||
|
|
c3f46e4a6b | ||
|
|
1fbd3c8f65 | ||
|
|
965ebde22f | ||
|
|
0d9d3aca1a | ||
|
|
937cf43e09 | ||
|
|
23472e6fb4 | ||
|
|
445728b2a2 | ||
|
|
61c213f9e3 | ||
|
|
dba432d1b6 | ||
|
|
2f416371bd | ||
|
|
2bfbdb1c06 | ||
|
|
a4c958c54f | ||
|
|
b2e257bf37 | ||
|
|
6b6314227e | ||
|
|
1e0eb3784e | ||
|
|
f00ea4bfac | ||
|
|
0da4c9d90d | ||
|
|
df83298c54 | ||
|
|
5d29d4ea22 | ||
|
|
a9e1ed5a72 | ||
|
|
e6ad8d8b50 | ||
|
|
50cfe2b21a | ||
|
|
d7f2ec89d7 | ||
|
|
cc5ae8193b | ||
|
|
30a1f1ccb5 | ||
|
|
6f7cb990ca | ||
|
|
06f5746c86 | ||
|
|
cd44fe7bcd | ||
|
|
bb3d6ee72c | ||
|
|
bf16fdc01d | ||
|
|
969a2e953c | ||
|
|
bfaf9cd6eb | ||
|
|
be2e4b5d85 | ||
|
|
cc0fc953cf | ||
|
|
3b35d12e60 | ||
|
|
4bc679b0b3 | ||
|
|
be56c72b5f | ||
|
|
db92840ffc | ||
|
|
593a4ec0b0 | ||
|
|
d018a547a2 | ||
|
|
a246200675 | ||
|
|
1cb6517812 | ||
|
|
2824da371f | ||
|
|
22eb86089f | ||
|
|
2e17366bce | ||
|
|
ef407ae93a | ||
|
|
9032ce99d3 | ||
|
|
adaaeb6f36 | ||
|
|
8a146b5401 | ||
|
|
4ee1e00808 | ||
|
|
3a6368ba6c | ||
|
|
fbc314a74b | ||
|
|
850a238ca2 | ||
|
|
20b6f20908 | ||
|
|
33f7d7649a | ||
|
|
fb4d263211 | ||
|
|
ab87dece0b | ||
|
|
69e027394d | ||
|
|
6639b2c731 | ||
|
|
e23956cd77 | ||
|
|
56ca7743a8 | ||
|
|
fb17d57549 | ||
|
|
e7045f2b8a | ||
|
|
fd25bd7ca2 | ||
|
|
852f587bd2 | ||
|
|
e0e6f5e302 | ||
|
|
06a5ffdd1a | ||
|
|
a75bafc4ba | ||
|
|
60a59be4d3 | ||
|
|
b76b2d01eb | ||
|
|
cd70d298fc | ||
|
|
70d0ed3ebf | ||
|
|
5a5c6ab621 | ||
|
|
a34bb9724f | ||
|
|
824a9c6dbf | ||
|
|
c9eaae6bd5 | ||
|
|
9f5790fcaf | ||
|
|
6aeb0435ce | ||
|
|
70ec2496db | ||
|
|
55314e0fc7 | ||
|
|
243d76617b | ||
|
|
6cc66a0b20 | ||
|
|
1e928df531 | ||
|
|
db9bec02f5 | ||
|
|
0dc033692f | ||
|
|
1d30091188 | ||
|
|
64f80f8623 | ||
|
|
83b06c6bb0 | ||
|
|
5cbc2f0ea5 | ||
|
|
2bc8adee8c | ||
|
|
c6eb7f136c | ||
|
|
48b87c2f40 | ||
|
|
ca818fbf04 | ||
|
|
264fe4edc7 | ||
|
|
317c0ec2de | ||
|
|
607c819109 | ||
|
|
61fbbb0054 | ||
|
|
3b2e89d878 | ||
|
|
49b76baf3f | ||
|
|
15a94240eb | ||
|
|
f75b7db90e | ||
|
|
81dc48ef00 | ||
|
|
3638ef808b | ||
|
|
f24cba722a | ||
|
|
30fcdd6ccb | ||
|
|
b0df236dea | ||
|
|
d91e33ff39 | ||
|
|
1ee316ca83 | ||
|
|
dcbf014402 | ||
|
|
20b0d4fdcc | ||
|
|
2da1255c26 | ||
|
|
5bddb93745 | ||
|
|
51932161af | ||
|
|
e0d9871195 | ||
|
|
634d4af4dd | ||
|
|
a507e4f002 | ||
|
|
c15fed382d | ||
|
|
9937d54410 | ||
|
|
64f6610f11 | ||
|
|
e6f21f0bfc | ||
|
|
c63732ef5f | ||
|
|
99496de242 | ||
|
|
29cb576737 | ||
|
|
bf4ef599ad | ||
|
|
5ddb33419e | ||
|
|
bc10fbdfae | ||
|
|
6dcf7267a5 | ||
|
|
5712365cac | ||
|
|
1fc5a9aada | ||
|
|
38cd1bd6d7 | ||
|
|
771642d17d | ||
|
|
bb19b2040b | ||
|
|
12840dae6c | ||
|
|
521d7dbdaa | ||
|
|
2cba2ff3d4 | ||
|
|
547db92693 | ||
|
|
bf334238b7 | ||
|
|
5808de648b | ||
|
|
9ab0455b05 | ||
|
|
55ab36662a | ||
|
|
b1ad564b0d | ||
|
|
bc70b3b37d | ||
|
|
d426c99866 | ||
|
|
356377975b | ||
|
|
0de3d839e6 | ||
|
|
0fcd6b1d22 | ||
|
|
99b5464392 | ||
|
|
70965a8fd6 | ||
|
|
77ca15497c | ||
|
|
56ecc77dfc | ||
|
|
a995b610cf | ||
|
|
332bc6be76 | ||
|
|
26de9fe4c5 | ||
|
|
00dcb4f6e3 | ||
|
|
996c641a79 | ||
|
|
a077860464 | ||
|
|
5e68acfc76 | ||
|
|
269540bfed | ||
|
|
868f15173d | ||
|
|
06a93344bb | ||
|
|
e8e7870be2 | ||
|
|
a9417c6878 | ||
|
|
605d61b64f | ||
|
|
8feaacf57e | ||
|
|
553c651166 | ||
|
|
1ec0bac9c6 | ||
|
|
b1c6ae8a06 | ||
|
|
8a124d0b35 | ||
|
|
0ad02a4afa | ||
|
|
9389f899d7 | ||
|
|
050a34921b | ||
|
|
32e878db81 | ||
|
|
0bf7bf56a5 | ||
|
|
e5d130b698 | ||
|
|
941e57a218 | ||
|
|
c02eea6ad3 | ||
|
|
fe0d878b23 | ||
|
|
763f148fa1 | ||
|
|
8f0ada708f | ||
|
|
8117999ab3 | ||
|
|
10dd22a3d4 | ||
|
|
738f37e86c | ||
|
|
729c0abf04 | ||
|
|
fe54da34f8 | ||
|
|
09de0f31fa | ||
|
|
6ee524864e | ||
|
|
8fd350f0fc | ||
|
|
ebbeeefd86 | ||
|
|
76a890aa74 | ||
|
|
5ce522a401 | ||
|
|
68906b5def | ||
|
|
dddaa16573 | ||
|
|
d581559dd4 | ||
|
|
3e77db8149 | ||
|
|
bd994f9e57 | ||
|
|
9115846017 | ||
|
|
441009d359 | ||
|
|
ab7d63a8c8 | ||
|
|
a7acee726d | ||
|
|
a6f047331f | ||
|
|
a224acb50b | ||
|
|
42a1e4ef9b | ||
|
|
dff867b8a7 | ||
|
|
8bf8147028 | ||
|
|
ccf6d8f87d | ||
|
|
ac817ccc66 | ||
|
|
37c07eaaf9 | ||
|
|
71e20801f0 | ||
|
|
bae1bd6a42 | ||
|
|
c8b194931f | ||
|
|
c7a4881552 | ||
|
|
26fd963fbd | ||
|
|
122a34b40e | ||
|
|
33abd69e85 | ||
|
|
76a686beb2 | ||
|
|
53e8ac9b0c | ||
|
|
4b459fe0fd | ||
|
|
be4c8341ba | ||
|
|
a33bc027f1 | ||
|
|
3c9d8b0d84 | ||
|
|
26fc53990b | ||
|
|
852dac4d62 | ||
|
|
76719413f9 | ||
|
|
fca673d653 | ||
|
|
446c3958df | ||
|
|
59e72b8b07 | ||
|
|
15e56bd809 | ||
|
|
ab5cc0ad05 | ||
|
|
b89bce17ad | ||
|
|
7fcbddac04 | ||
|
|
4e1bbee67e | ||
|
|
ecfa44b0e8 | ||
|
|
db76868c26 | ||
|
|
b4c7989566 | ||
|
|
7b38b9ed57 | ||
|
|
c2fccc0350 | ||
|
|
b56ad5e8fe | ||
|
|
abfcf27962 | ||
|
|
6a04614549 | ||
|
|
89f981836f | ||
|
|
33d4d400f0 | ||
|
|
749ed6372a | ||
|
|
81fccee906 | ||
|
|
4caac78241 | ||
|
|
7ff03bb475 | ||
|
|
28c3a2026c | ||
|
|
a9c85fe825 | ||
|
|
c46e799dd0 | ||
|
|
ed1a31d1cf | ||
|
|
7f7156bafa | ||
|
|
62ce64f97d | ||
|
|
04a226f813 | ||
|
|
68b012ca06 | ||
|
|
220828f7db | ||
|
|
7df7f8f598 | ||
|
|
05aff199b2 | ||
|
|
fc22fc39e2 | ||
|
|
01c20807c2 | ||
|
|
243ce59806 | ||
|
|
5d6e0356b3 | ||
|
|
8324b43cc2 | ||
|
|
4c3ccc264c | ||
|
|
197c83d993 | ||
|
|
aaded29855 | ||
|
|
e5656b9982 | ||
|
|
c06b08df5b | ||
|
|
fc16752cec | ||
|
|
588d3b12e0 | ||
|
|
7de7bdac71 | ||
|
|
6e8e78db57 | ||
|
|
dcc991bd5c | ||
|
|
04cb9f33ff | ||
|
|
9bd167c10d | ||
|
|
dc6aab88b0 | ||
|
|
43fc6f1fa3 | ||
|
|
c2aa0d75ec | ||
|
|
c24ae84b00 | ||
|
|
92605a9734 | ||
|
|
b32a34b97c | ||
|
|
12923a6649 | ||
|
|
e7c7160f85 | ||
|
|
2a0962183e | ||
|
|
95d9ad46cf | ||
|
|
e60513f554 | ||
|
|
a557a82b40 | ||
|
|
3dd6f368d6 | ||
|
|
5ce012bdac | ||
|
|
a2338c49b9 | ||
|
|
dd6886f358 | ||
|
|
5ddf6301c2 | ||
|
|
14dd2c0afb | ||
|
|
fc2a0d3e65 | ||
|
|
0f8fc63a5e | ||
|
|
f57a40eeb3 | ||
|
|
2c9c95f531 | ||
|
|
7048f6edf7 | ||
|
|
c333cfa073 | ||
|
|
64d77d2491 | ||
|
|
9604674c65 | ||
|
|
d9294779a6 | ||
|
|
3117fe9465 | ||
|
|
0a74b9aa3b | ||
|
|
7e90576391 | ||
|
|
0a34885fbc | ||
|
|
61201525a8 | ||
|
|
1b886a4420 | ||
|
|
6f70984ab6 | ||
|
|
38fa2d78da | ||
|
|
fe905bace5 | ||
|
|
e7e1434293 | ||
|
|
363c647a5f | ||
|
|
aac60eb029 | ||
|
|
06ca03ae9c | ||
|
|
ff927b8e79 | ||
|
|
670b0d58c7 | ||
|
|
3113cdf0ea | ||
|
|
d2cef6f29d | ||
|
|
d62bd5f748 | ||
|
|
e6fd1381ce | ||
|
|
65b2be7831 | ||
|
|
e2d0de2686 | ||
|
|
495b2d7fbb | ||
|
|
5e662d0eb7 | ||
|
|
892db98c96 | ||
|
|
10fbf0dccc | ||
|
|
4595a18197 | ||
|
|
577a3d81e5 | ||
|
|
55fb5dbcb5 | ||
|
|
8e7eeff45c | ||
|
|
ec7130b46f | ||
|
|
dc17f0d02e | ||
|
|
14cd4315c7 | ||
|
|
644d881787 | ||
|
|
7fdfc62237 | ||
|
|
9fa0f00c23 | ||
|
|
fe6aaa3664 | ||
|
|
6e6b527d6e | ||
|
|
94d0009994 | ||
|
|
7cd99d1559 | ||
|
|
d91fa3b6fb | ||
|
|
466dca361c | ||
|
|
8b24c39a1d | ||
|
|
9dd64dfb49 | ||
|
|
0010a5a109 | ||
|
|
9cb5b005bb | ||
|
|
06a3aa6cf8 | ||
|
|
d4e9d34d2d | ||
|
|
a3a7017f6b | ||
|
|
fedc1fd325 | ||
|
|
11d5f51e67 | ||
|
|
2c810ce763 | ||
|
|
b61e81d5b4 | ||
|
|
534a9e7f5a | ||
|
|
14a62c0f8b | ||
|
|
89d29bbc10 | ||
|
|
ac8db6e8ec | ||
|
|
0a7c1bfd98 | ||
|
|
4efb8d24ac | ||
|
|
38d08873bc | ||
|
|
8074d754d3 | ||
|
|
f3db84decb | ||
|
|
13644ce351 | ||
|
|
e1c1b07a0b | ||
|
|
42ec3b492a | ||
|
|
c18794f202 | ||
|
|
0862fd1f59 | ||
|
|
6663a947ee | ||
|
|
3e8bb98197 | ||
|
|
9b2f68a25d | ||
|
|
11a7cd6983 | ||
|
|
7d904e455d | ||
|
|
c3cf6a22e9 | ||
|
|
12a150a438 | ||
|
|
8b46e84263 | ||
|
|
237e6819bf | ||
|
|
8faea59e31 | ||
|
|
cab79c473f | ||
|
|
c656ed04b0 | ||
|
|
29000fb79e | ||
|
|
03fa017d4a | ||
|
|
26af6ffd4d | ||
|
|
7d8e28a7dd | ||
|
|
a52b7e7a4e | ||
|
|
aaedcfc700 | ||
|
|
fc7b22dc48 | ||
|
|
5b44e36317 | ||
|
|
82f5de3bb9 | ||
|
|
dcf30b40c0 | ||
|
|
3b89134c05 | ||
|
|
d76d50677d | ||
|
|
8790a94a56 | ||
|
|
308535dc5c | ||
|
|
efe5b20b73 | ||
|
|
d1a6e8de3b | ||
|
|
f3cf646bbf | ||
|
|
a39b309b74 | ||
|
|
1aeb562066 | ||
|
|
107d1a1496 | ||
|
|
39edbfb655 | ||
|
|
246b0edaa1 | ||
|
|
4afa08da8d | ||
|
|
dc5b525559 | ||
|
|
30251e5d7c | ||
|
|
81e231ea20 | ||
|
|
36930e9661 | ||
|
|
838c11b69c | ||
|
|
05041722f1 | ||
|
|
36eb7efe5e | ||
|
|
87c9370e8c | ||
|
|
0b018fd4e9 | ||
|
|
39c4356ef6 | ||
|
|
59b79e9b04 | ||
|
|
b2e940f305 | ||
|
|
c3bc59b5e1 | ||
|
|
e3126ba3f8 | ||
|
|
f951abb7a9 | ||
|
|
549a8cd31b | ||
|
|
874ba72f4e | ||
|
|
ea1fd3d01b | ||
|
|
4c6b993458 | ||
|
|
323cc44e0e | ||
|
|
8bf81875ca | ||
|
|
15cf76c767 | ||
|
|
3df272fb92 | ||
|
|
04a8137f09 | ||
|
|
3217a4bee4 | ||
|
|
2aab58473e | ||
|
|
5d3bac6a86 | ||
|
|
09d67a6c52 | ||
|
|
af7588ef16 | ||
|
|
a6e85ad5f5 | ||
|
|
d5e83902a5 | ||
|
|
055614bbbc | ||
|
|
0d803ac895 | ||
|
|
253babff62 | ||
|
|
fac964bcf5 | ||
|
|
02d809c194 | ||
|
|
5d1b6cfe97 | ||
|
|
e37a9b62b7 | ||
|
|
623853f3f4 | ||
|
|
6c892e7161 | ||
|
|
be304c6ee7 | ||
|
|
00f380a75c | ||
|
|
af29effbf3 | ||
|
|
57300f5159 | ||
|
|
9d34ed8781 | ||
|
|
f3dd0a4ec8 | ||
|
|
9371520a73 | ||
|
|
fd5be55d81 | ||
|
|
e0c0fb6917 | ||
|
|
f2f0f460c4 | ||
|
|
08928d8b8e | ||
|
|
cb4728f8f5 | ||
|
|
4a71a41380 | ||
|
|
9c3b1105e2 | ||
|
|
edf897182e | ||
|
|
7c1559b8ad | ||
|
|
b01165c1f8 | ||
|
|
b5d9c6b113 | ||
|
|
5a56849df8 | ||
|
|
c908f849c5 | ||
|
|
fed92ac5c8 | ||
|
|
00f4232ecc | ||
|
|
fa1cd89aa9 | ||
|
|
5193f4fae0 | ||
|
|
740ae96f5f | ||
|
|
55ca998224 | ||
|
|
fdff1ca793 | ||
|
|
fa729b8306 | ||
|
|
eee99f62e1 | ||
|
|
da9f86f1a7 | ||
|
|
0535f9425b | ||
|
|
c86fd87819 | ||
|
|
3a866cf202 | ||
|
|
e2aaa8d362 | ||
|
|
524db02228 | ||
|
|
cf9d2348a1 | ||
|
|
92122bf29b | ||
|
|
abb7a49584 | ||
|
|
beff0db301 | ||
|
|
ab1526b085 | ||
|
|
b91e737017 | ||
|
|
fa093e2bd2 | ||
|
|
2f001f8af9 | ||
|
|
f87300d30e | ||
|
|
04ae96617d | ||
|
|
7a80efd8bd | ||
|
|
df268ad5cd | ||
|
|
e721bdc9f0 | ||
|
|
ae99ad83d0 | ||
|
|
c491537de1 | ||
|
|
383d59bbc3 | ||
|
|
2471910244 | ||
|
|
2a36ff0839 | ||
|
|
136c838f30 | ||
|
|
bd3936cc9f | ||
|
|
dab80b58ae | ||
|
|
23cfa3d824 | ||
|
|
289ff8092d | ||
|
|
47b01786e2 | ||
|
|
6bc1881727 | ||
|
|
21d765b227 | ||
|
|
d82c008052 | ||
|
|
11823ce890 | ||
|
|
670b179140 | ||
|
|
637c2eae43 | ||
|
|
53e8df40bb | ||
|
|
180a7af75d | ||
|
|
0fd0f45d8b | ||
|
|
8754f8d895 | ||
|
|
c02cc84498 | ||
|
|
647216876b | ||
|
|
f3bd6145a5 | ||
|
|
7320515820 | ||
|
|
8fa1a46584 | ||
|
|
fa7d214e78 | ||
|
|
178b611b0c | ||
|
|
1f28c22323 | ||
|
|
ae0f963799 | ||
|
|
82cac90005 | ||
|
|
8ebfa27210 | ||
|
|
4babc92d7c | ||
|
|
1a3846161a | ||
|
|
ff7d0e58dc | ||
|
|
3d64f91a5c | ||
|
|
f980f32fe5 | ||
|
|
dc67c94545 | ||
|
|
fff56eea1b | ||
|
|
bd2158613d | ||
|
|
537fbad705 | ||
|
|
52cc8fa5fe | ||
|
|
7988f634f3 | ||
|
|
a5e34e2288 | ||
|
|
ad3764ae4a | ||
|
|
685bc54bf2 | ||
|
|
a54eb8ac44 | ||
|
|
6a87253d94 | ||
|
|
6406a88ddf | ||
|
|
18b2a1c3b4 | ||
|
|
58056df23d | ||
|
|
a88e27cc7d | ||
|
|
7cab2501da | ||
|
|
dd93e62a41 | ||
|
|
d238a1998c | ||
|
|
6bbb21490c | ||
|
|
9f0fa2f815 | ||
|
|
f5fb10063c | ||
|
|
721bd272b5 | ||
|
|
c170156147 | ||
|
|
a2f6f2be03 | ||
|
|
412260392f | ||
|
|
5f8585a625 | ||
|
|
ee0d15c878 | ||
|
|
3f0b584f25 | ||
|
|
41fd20c6e4 | ||
|
|
01e9524098 | ||
|
|
d9d99c1c27 | ||
|
|
f53f5735e1 | ||
|
|
7a88a4b0e9 | ||
|
|
85733fea56 | ||
|
|
369e1f4c1e | ||
|
|
702fd24ce8 | ||
|
|
961e62207c | ||
|
|
cc950614c5 | ||
|
|
556140e7d9 | ||
|
|
987f09d880 | ||
|
|
4cf2f9a847 | ||
|
|
2fce845ea6 | ||
|
|
9914fd9a38 | ||
|
|
a221a3c9f8 | ||
|
|
b1058b9fcc | ||
|
|
4c2508028a | ||
|
|
d516d4df7c | ||
|
|
d20c36dff2 | ||
|
|
a523803d37 | ||
|
|
bbb688313e | ||
|
|
d6c8369630 | ||
|
|
05eb5df93c | ||
|
|
51fe17ee43 | ||
|
|
1d5737f843 | ||
|
|
13103aecb2 | ||
|
|
e9d2c806ba | ||
|
|
932ab00a53 | ||
|
|
6cfd957697 | ||
|
|
f00d8964c2 | ||
|
|
9ef6b52352 |
@@ -0,0 +1,16 @@
|
||||
node_modules/
|
||||
*.swp
|
||||
.lock-wscript
|
||||
lib/
|
||||
Makefile.gyp
|
||||
*.Makefile
|
||||
*.target.gyp.mk
|
||||
*.node
|
||||
example/*.log
|
||||
docs/
|
||||
npm-debug.log
|
||||
/.idea/
|
||||
.env
|
||||
build/
|
||||
.vscode/
|
||||
.git/
|
||||
@@ -0,0 +1,17 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
end_of_line = lf
|
||||
|
||||
[*.{j,t}s]
|
||||
max_line_length = 100
|
||||
|
||||
[*.css]
|
||||
indent_size = 4
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
@@ -0,0 +1,11 @@
|
||||
<!-- Enter your issue description here -->
|
||||
|
||||
## Details
|
||||
- Browser and browser version:
|
||||
- OS version:
|
||||
- xterm.js version:
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
1.
|
||||
2.
|
||||
+10
-3
@@ -1,11 +1,18 @@
|
||||
node_modules/
|
||||
*.swp
|
||||
build/*
|
||||
.lock-wscript
|
||||
out/
|
||||
lib/
|
||||
Makefile.gyp
|
||||
*.Makefile
|
||||
*.target.gyp.mk
|
||||
*.node
|
||||
example/*.log
|
||||
docs/_build
|
||||
docs/
|
||||
npm-debug.log
|
||||
/.idea/
|
||||
.env
|
||||
build/
|
||||
.vscode/
|
||||
|
||||
# Directories needed for code coverage
|
||||
/coverage/
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
Antonis Kalipetis <akalipetis@sourcelair.com>
|
||||
Antonis Kalipetis <akalipetis@sourcelair.com> <akalipetis@gmail.com>
|
||||
Daniel Imms <daimms@microsoft.com> <tyriar@tyriar.com>
|
||||
Paris Kasidiaris <paris@sourcelair.com>
|
||||
Paris Kasidiaris <pariskasidiaris@gmail.com>
|
||||
Thanasis Daglis <thanasis@sourcelair.com>
|
||||
Thanasis Daglis <thanasis@sourcelair.com> <ath.daglis@gmail.com>
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
node_modules/
|
||||
*.swp
|
||||
.lock-wscript
|
||||
lib/*.test.js
|
||||
lib/*.test.js.map
|
||||
lib/test/
|
||||
Makefile.gyp
|
||||
*.Makefile
|
||||
*.target.gyp.mk
|
||||
*.node
|
||||
example/*.log
|
||||
docs/
|
||||
npm-debug.log
|
||||
/.idea/
|
||||
.env
|
||||
build/
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 6
|
||||
env:
|
||||
global:
|
||||
- CXX=g++-4.8
|
||||
matrix:
|
||||
- NPM_COMMAND=lint
|
||||
- NPM_COMMAND=test
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-4.8
|
||||
notifications:
|
||||
email: false
|
||||
script: npm run $NPM_COMMAND
|
||||
after_success: npm run coveralls
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Mocha Tests",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha",
|
||||
"windows": {
|
||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha.cmd"
|
||||
},
|
||||
"runtimeArgs": [
|
||||
"--colors",
|
||||
"--recursive",
|
||||
"${workspaceRoot}/lib"
|
||||
],
|
||||
"sourceMaps": true,
|
||||
"outFiles": [ "${workspaceRoot}/lib/**/*.js" ],
|
||||
"internalConsoleOptions": "openOnSessionStart"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
List of xterm.js contributors. Updated before every release.
|
||||
|
||||
Aleksandr Andrienko <aandrienko@codenvy.com>
|
||||
Alessandro Nadalin <alessandro.nadalin@gmail.com>
|
||||
Alexander Olsson <noseglid@gmail.com>
|
||||
Antonis Kalipetis <akalipetis@sourcelair.com>
|
||||
Anton Skshidlevsky <meefik@gmail.com>
|
||||
Anton Yurovskykh <anton.yurovskykh@gmail.com>
|
||||
Austin Robertson <austinrobertson@gmail.com>
|
||||
ayapi <colors.aya@gmail.com>
|
||||
Ben Hall <ben@benhall.me.uk>
|
||||
Benjamin Fischer <benjamin.fischer@rwth-aachen.de>
|
||||
Bill Church <billchurch@users.noreply.github.com>
|
||||
Bob Reid <bobreid@Bobs-MacBook-Pro.local>
|
||||
bottleofwater <nison.mael+bottleofwater@gmail.com>
|
||||
Brian Mock <brian@mockbrian.com>
|
||||
Carson Anderson <carson@betterservers.com>
|
||||
Christian Budde Christensen <budde377@gmail.com>
|
||||
Christopher Jeffrey <chjjeffrey@gmail.com>
|
||||
coderaiser <mnemonic.enemy@gmail.com>
|
||||
Daniel Imms <daimms@microsoft.com>
|
||||
Daniel Risacher <drisacher@gmail.com>
|
||||
Dan Kaplun <dbkaplun@twitch.tv>
|
||||
Darin Morrison <freebroccolo@users.noreply.github.com>
|
||||
Edgar Andrés Margffoy Tuay <andfoy@gmail.com>
|
||||
Elliot Saba <staticfloat@gmail.com>
|
||||
hiro-su <h.sugipon@gmail.com>
|
||||
Ian Lewis <ianlewis@google.com>
|
||||
imoses <ido@twiggle.com>
|
||||
InDieTasten <indietasten@gmail.com>
|
||||
Jean Bruenn <himself@jeanbruenn.info>
|
||||
Jörg Breitbart <jerch@rockborn.de>
|
||||
Lucian Buzzo <lucian.buzzo@gmail.com>
|
||||
Maël Nison <nison.mael@gmail.com>
|
||||
Martin Wang <jiahaow@ca.ibm.com>
|
||||
Michael Irwin <mikesir87@gmail.com>
|
||||
Mikko Karvonen <mikko.karvonen@arm.com>
|
||||
Nicolas Ramz <nicolas.ramz@gmail.com>
|
||||
Paris Kasidiaris <pariskasidiaris@gmail.com>
|
||||
Paris Kasidiaris <paris@sourcelair.com>
|
||||
runarberg <runar@greenqloud.com>
|
||||
Saswat Das <saswatds@users.noreply.github.com>
|
||||
Shuanglei Tao <tsl0922@gmail.com>
|
||||
Steven Silvester <steven.silvester@ieee.org>
|
||||
Thanasis Daglis <thanasis@sourcelair.com>
|
||||
Tine Jozelj <tine.jozelj@outlook.com>
|
||||
Tyler Jewell <tjewell@codenvy.com>
|
||||
Vincent Woo <me@vincentwoo.com>
|
||||
yutaka <shigure@refy.net>
|
||||
YuviPanda <yuvipanda@gmail.com>
|
||||
@@ -0,0 +1,52 @@
|
||||
# How to contribute to xterm.js
|
||||
|
||||
- [Opening issues for bug reports or feature requests](#opening-issues)
|
||||
- [Contributing code](#contributing-code)
|
||||
|
||||
## Opening issues
|
||||
|
||||
The preferred way to report bugs or request features is to use
|
||||
[GitHub issues](http://github.com/sourcelair/xterm.js/issues). Before
|
||||
opening an issue, read these pointers.
|
||||
|
||||
### Opening issues effectively
|
||||
|
||||
- Include information about **the browser in which the problem occurred**. Even
|
||||
if you tested several browsers, and the problem occurred in all of them,
|
||||
mention this fact in the bug report. Also include browser version numbers and
|
||||
the operating system that you're on.
|
||||
|
||||
- Mention which release of xterm.js you're using. Preferably, try also with
|
||||
the current HEAD of the master branch, to ensure the problem has not already been
|
||||
fixed.
|
||||
|
||||
- Mention precisely what went wrong. What did you expect to happen? What happened instead? Describe the
|
||||
exact steps a maintainer has to take to make the problem occur.
|
||||
|
||||
- If the problem can not be reproduced in the [demo of xterm.js](README.md#demo), please provide an HTML document that demonstrates the problem.
|
||||
|
||||
- Be polite. Issues with an indignant or belligerent tone tend to be moved to the
|
||||
bottom of the pile.
|
||||
|
||||
## Contributing code
|
||||
|
||||
- Make sure you have a [GitHub account](https://github.com/join)
|
||||
- Fork [xterm.js](https://github.com/sourcelair/xterm.js/)
|
||||
([how to fork a repo](https://help.github.com/articles/fork-a-repo))
|
||||
- Make your changes
|
||||
- If your changes are easy to test or likely to regress, add tests. Tests go into `test`, directory.
|
||||
- Follow the general code style of the rest of the project (see below).
|
||||
- Submit a pull request
|
||||
([how to create a pull request](https://help.github.com/articles/fork-a-repo)).
|
||||
Don't put more than one feature/fix in a single pull request.
|
||||
|
||||
By contributing code to xterm.js you
|
||||
|
||||
- agree to license the contributed code under xterm.js' [MIT
|
||||
license](LICENSE).
|
||||
|
||||
- confirm that you have the right to contribute and license the code
|
||||
in question. (Either you hold all rights on the code, or the rights
|
||||
holder has explicitly granted the right to use it like this,
|
||||
through a compatible open source license or through a direct
|
||||
agreement with you.)
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
FROM node:6.9
|
||||
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", "dev"]
|
||||
|
||||
# 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
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2014, sourceLair Limited (https://github.com/sourcelair/)
|
||||
Copyright (c) 2014-2016, SourceLair Private Company (https://www.sourcelair.com)
|
||||
Copyright (c) 2012-2013, Christopher Jeffrey (https://github.com/chjj/)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
web: npm run dev
|
||||
@@ -1,19 +1,134 @@
|
||||
# xterm.js — xterm, in the browser
|
||||
# [](https://xtermjs.org)
|
||||
|
||||
Xterm.js is a full xterm clone, written completely in JavaScript.
|
||||
[](https://travis-ci.org/sourcelair/xterm.js) [](https://coveralls.io/github/sourcelair/xterm.js) [](https://gitter.im/sourcelair/xterm.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
|
||||
Xterm.js supplies a modular, event-based interface that lets developers build addons and themes that augment the experience of running a fully-featured terminal inside the browser.
|
||||
Xterm.js is a terminal front-end component written in JavaScript that works in the browser.
|
||||
|
||||
Xterm.js serves as foundation for the terminal found at www.sourcelair.com.
|
||||
It enables applications to provide fully featured terminals to their users and create great development experiences.
|
||||
|
||||

|
||||
## Features
|
||||
- **Text-based application support**: Use xterm.js to work with applications like `bash`, `git` etc.
|
||||
- **Curses-based application support**: Use xterm.js to work with applications like `vim`, `tmux` etc.
|
||||
- **Mouse events support**: Xterm.js captures mouse events like click and scroll and passes them to the terminal's back-end controlling process
|
||||
- **CJK (Chinese, Japanese, Korean) character support**: Xterm.js renders CJK characters seamlessly
|
||||
- **IME support**: Insert international (including CJK) characters using IME input with your keyboard
|
||||
- **Self-contained library**: Xterm.js works on its own. It does not require any external libraries like jQuery or React to work
|
||||
- **Modular, event-based API**: Lets you build addons and themes with ease
|
||||
|
||||
### Contribution and License Agreement
|
||||
## What xterm.js is not
|
||||
- Xterm.js is not a terminal application that you can download and use on your computer
|
||||
- Xterm.js is not `bash`. Xterm.js can be connected to processes like `bash` and let you interact with them (provide input, receive output)
|
||||
|
||||
## Real-world uses
|
||||
Xterm.js is used in several world-class applications to provide great terminal experiences.
|
||||
|
||||
- [**SourceLair**](https://www.sourcelair.com/): In-browser IDE that provides its users with fully-featured Linux terminals based on xterm.js
|
||||
- [**Microsoft Visual Studio Code**](http://code.visualstudio.com/): Modern, versatile and powerful open source code editor that provides an integrated terminal based on xterm.js
|
||||
- [**ttyd**](https://github.com/tsl0922/ttyd): A command-line tool for sharing terminal over the web, with fully-featured terminal emulation based on xterm.js
|
||||
- [**Katacoda**](https://www.katacoda.com/): Katacoda is an Interactive Learning Platform for software developers, covering the latest Cloud Native technologies.
|
||||
- [**Eclipse Che**](http://www.eclipse.org/che): Developer workspace server, cloud IDE, and Eclipse next-generation IDE.
|
||||
- [**Codenvy**](http://www.codenvy.com): Cloud workspaces for development teams.
|
||||
- [**CoderPad**](https://coderpad.io): Online interviewing platform for programmers. Run code in many programming languages, with results displayed by `xterm.js`.
|
||||
- [**WebSSH2**](https://github.com/billchurch/WebSSH2): A web based SSH2 client using `xterm.js`, socket.io, and ssh2.
|
||||
- [**Spyder Terminal**](https://github.com/spyder-ide/spyder-terminal): A full fledged system terminal embedded on Spyder IDE.
|
||||
- [**Cloud Commander**](https://cloudcmd.io "Cloud Commander"): Orthodox web file manager with console and editor.
|
||||
|
||||
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.
|
||||
|
||||
## Browser Support
|
||||
|
||||
Since xterm.js is typically implemented as a developer tool, only modern browsers are supported officially. Here is a list of the versions we aim to support:
|
||||
|
||||
- Chrome 48+
|
||||
- Edge 13+
|
||||
- Firefox 44+
|
||||
- Internet Explorer 11+
|
||||
- Opera 35+
|
||||
- Safari 8+
|
||||
|
||||
Xterm.js works seamlessly in Electron apps and may even work on earlier versions of the browsers but these are the browsers we strive to keep working.
|
||||
|
||||
## Demo
|
||||
|
||||
### Linux or macOS
|
||||
|
||||
Run the following commands:
|
||||
|
||||
```
|
||||
$ npm install
|
||||
$ npm start
|
||||
```
|
||||
|
||||
Then open http://0.0.0.0:3000 in a web browser.
|
||||
|
||||
### Windows
|
||||
|
||||
First, ensure [node-gyp](https://github.com/nodejs/node-gyp) is installed and configured correctly, then run these commands.
|
||||
|
||||
Note: Do not use ConEmu, as it seems to break the demo for some reason.
|
||||
|
||||
```
|
||||
> npm install
|
||||
> npm start
|
||||
```
|
||||
|
||||
Then open http://127.0.0.1:3000 in a web browser.
|
||||
|
||||
## Getting Started
|
||||
|
||||
To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to the head of your html page. Then create a `<div id="terminal"></div>` onto which xterm can attach itself.
|
||||
```html
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="bower_components/xterm.js/dist/xterm.css" />
|
||||
<script src="bower_components/xterm.js/dist/xterm.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="terminal"></div>
|
||||
<script>
|
||||
var term = new Terminal();
|
||||
term.open(document.getElementById('terminal'));
|
||||
term.write('Hello from \033[1;3;31mxterm.js\033[0m $ ')
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
Finally instantiate the `Terminal` object and then call the `open` function with the DOM object of the `div`.
|
||||
|
||||
## Addons
|
||||
|
||||
Addons are JavaScript modules that attach functions to the `Terminal` prototype to extend its functionality. There are a handful available in the main repository in the `dist/addons` directory, you can even write your own (though they may break when the internals of xterm.js change across versions).
|
||||
|
||||
To use an addon, just include the JavaScript file after xterm.js and before the `Terminal` object has been instantiated. The function should then be exposed on the `Terminal` object:
|
||||
|
||||
```html
|
||||
<script src="node_modules/dist/xterm.js"></script>
|
||||
<script src="node_modules/dist/addons/fit/fit.js"></script>
|
||||
```
|
||||
|
||||
```js
|
||||
var xterm = new Terminal();
|
||||
// init code...
|
||||
xterm.fit();
|
||||
```
|
||||
|
||||
## Releases
|
||||
|
||||
Xterm.js follows a monthly release cycle roughly.
|
||||
|
||||
The existing releases are available at this GitHub repo's [Releases](https://github.com/sourcelair/xterm.js/releases), while the roadmap is available as [Milestones](https://github.com/sourcelair/xterm.js/milestones).
|
||||
|
||||
## Development and Contribution
|
||||
|
||||
Xterm.js is maintained by [SourceLair](https://www.sourcelair.com/) and a few external contributors, but we would love to receive contributions from everyone!
|
||||
|
||||
To contribute either code, documentation or issues to xterm.js please read the [Contributing document](CONTRIBUTING.md) beforehand. The development of xterm.js does not require any special tool. All you need is an editor that supports JavaScript/TypeScript and a browser. You will need Node.js installed locally to get all the features working in the demo.
|
||||
|
||||
## License Agreement
|
||||
|
||||
If you contribute code to this project, you are implicitly allowing your code to be distributed under the MIT license. You are also implicitly verifying that all code is your original work.
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) 2014-2015, SourceLair, Ltd (www.sourcelair.com) (MIT License)
|
||||
Copyright (c) 2014-2016, SourceLair, Private Company ([www.sourcelair.com](https://www.sourcelair.com/home)) (MIT License)
|
||||
|
||||
Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Implements the attach method, that
|
||||
* attaches the terminal to a WebSocket stream.
|
||||
*
|
||||
* The bidirectional argument indicates, whether the terminal should
|
||||
* send data to the socket as well and is true, by default.
|
||||
*/
|
||||
|
||||
(function (attach) {
|
||||
if (typeof define == 'function') {
|
||||
/*
|
||||
* Require.js is available
|
||||
*/
|
||||
define(['../../src/xterm'], attach);
|
||||
} else {
|
||||
/*
|
||||
* Plain browser environment
|
||||
*/
|
||||
attach(this.Xterm);
|
||||
}
|
||||
})(function (Xterm) {
|
||||
Xterm.prototype.attach = function (socket, bidirectional) {
|
||||
var term = this;
|
||||
|
||||
bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional;
|
||||
this.socket = socket;
|
||||
|
||||
term._getMessage = function (ev) {
|
||||
term.write(ev.data);
|
||||
};
|
||||
|
||||
term._sendData = function (data) {
|
||||
socket.send(data);
|
||||
};
|
||||
|
||||
socket.addEventListener('message', term._getMessage);
|
||||
|
||||
if (bidirectional) {
|
||||
this.on('data', term._sendData);
|
||||
}
|
||||
|
||||
socket.addEventListener('close', term.detach.bind(term, socket));
|
||||
socket.addEventListener('error', term.detach.bind(term, socket));
|
||||
};
|
||||
|
||||
Xterm.prototype.detach = function (socket) {
|
||||
var term = this;
|
||||
|
||||
term.off('data', term._sendData);
|
||||
|
||||
socket = (typeof socket == 'undefined') ? term.socket : socket;
|
||||
|
||||
if (socket) {
|
||||
socket.removeEventListener('message', term._getMessage);
|
||||
}
|
||||
|
||||
delete term.socket;
|
||||
};
|
||||
});
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Fit terminal columns and rows to the dimensions of its
|
||||
* DOM element.
|
||||
*
|
||||
* Approach:
|
||||
* - Rows: Truncate the division of the terminal parent element height
|
||||
* by the terminal row height
|
||||
*
|
||||
* - Columns: Truncate the division of the terminal parent element width by
|
||||
* the terminal character width (apply display: inline at the
|
||||
* terminal row and truncate its width with the current number
|
||||
* of columns)
|
||||
*/
|
||||
(function (fit) {
|
||||
if (typeof define == 'function') {
|
||||
/*
|
||||
* Require.js is available
|
||||
*/
|
||||
define(['../../src/xterm'], fit);
|
||||
} else {
|
||||
/*
|
||||
* Plain browser environment
|
||||
*/
|
||||
fit(this.Xterm);
|
||||
}
|
||||
})(function (Xterm) {
|
||||
Xterm.prototype.proposeGeometry = function () {
|
||||
var parentElementStyle = window.getComputedStyle(this.element.parentElement),
|
||||
parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height')),
|
||||
parentElementWidth = parseInt(parentElementStyle.getPropertyValue('width')),
|
||||
elementStyle = window.getComputedStyle(this.element),
|
||||
elementPaddingVer = parseInt(elementStyle.getPropertyValue('padding-top')) + parseInt(elementStyle.getPropertyValue('padding-bottom')),
|
||||
elementPaddingHor = parseInt(elementStyle.getPropertyValue('padding-right')) + parseInt(elementStyle.getPropertyValue('padding-left')),
|
||||
availableHeight = parentElementHeight - elementPaddingVer,
|
||||
availableWidth = parentElementWidth - elementPaddingHor,
|
||||
container = this.rowContainer,
|
||||
subjectRow = this.rowContainer.firstElementChild,
|
||||
contentBuffer = subjectRow.innerHTML,
|
||||
characterHeight,
|
||||
rows,
|
||||
characterWidth,
|
||||
cols,
|
||||
geometry;
|
||||
|
||||
subjectRow.style.display = 'inline';
|
||||
subjectRow.innerHTML = 'W'; // Common character for measuring width, although on monospace
|
||||
characterWidth = parseInt(subjectRow.offsetWidth);
|
||||
subjectRow.style.display = ''; // Revert style before calculating height, since they differ.
|
||||
characterHeight = parseInt(subjectRow.offsetHeight);
|
||||
subjectRow.innerHTML = contentBuffer;
|
||||
|
||||
rows = parseInt(availableHeight / characterHeight);
|
||||
/*
|
||||
* The following hack takes place in order to get "fit" work properly
|
||||
* in Mozilla Firefox.
|
||||
* Most probably, because of a dimension calculation bug, Firefox
|
||||
* calculates the width to be 1px less than it is actually drawn on
|
||||
* screen.
|
||||
*/
|
||||
if (navigator.userAgent.match(/Firefox/)) {
|
||||
characterWidth++;
|
||||
}
|
||||
cols = parseInt(availableWidth / characterWidth);
|
||||
|
||||
geometry = {cols: cols, rows: rows};
|
||||
return geometry;
|
||||
};
|
||||
|
||||
Xterm.prototype.fit = function () {
|
||||
var geometry = this.proposeGeometry();
|
||||
|
||||
this.resize(geometry.cols, geometry.rows);
|
||||
};
|
||||
});
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Fullscreen addon for xterm.js
|
||||
*
|
||||
* Implements the toggleFullscreen function.
|
||||
*
|
||||
* If the `fullscreen` argument has been supplied, then
|
||||
* if it is true, the fullscreen mode gets turned on,
|
||||
* if it is false or null, the fullscreen mode gets turned off.
|
||||
*
|
||||
* If the `fullscreen` argument has not been supplied, the
|
||||
* fullscreen mode is being toggled.
|
||||
*/
|
||||
(function (fullscreen) {
|
||||
if (typeof define == 'function') {
|
||||
/*
|
||||
* Require.js is available
|
||||
*/
|
||||
define(['../../src/xterm'], fullscreen);
|
||||
} else {
|
||||
/*
|
||||
* Plain browser environment
|
||||
*/
|
||||
fullscreen(this.Xterm);
|
||||
}
|
||||
})(function (Xterm) {
|
||||
Xterm.prototype.toggleFullscreen = function (fullscreen) {
|
||||
var fn;
|
||||
|
||||
if (typeof fullscreen == 'undefined') {
|
||||
fn = (this.element.classList.contains('fullscreen')) ? 'remove' : 'add';
|
||||
} else if (!fullscreen) {
|
||||
fn = 'remove';
|
||||
} else {
|
||||
fn = 'add';
|
||||
}
|
||||
|
||||
this.element.classList[fn]('fullscreen');
|
||||
};
|
||||
});
|
||||
@@ -1,36 +0,0 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../../src/xterm.css" />
|
||||
<link rel="stylesheet" href="../../demo/style.css" />
|
||||
<script src="../../src/xterm.js"></script>
|
||||
<script src="linkify.js"></script>
|
||||
<style>
|
||||
body {
|
||||
color: #111;
|
||||
}
|
||||
|
||||
#terminal-container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#terminal-container a {
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="terminal-container"></div>
|
||||
<script>
|
||||
var term = new Terminal(),
|
||||
container = document.getElementById('terminal-container');
|
||||
|
||||
term.open(container);
|
||||
term.writeln('Hello, welcome to xterm.js');
|
||||
term.writeln('');
|
||||
term.writeln('Check it out at github.com/sourcelair/xterm.js');
|
||||
term.linkify();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,165 +0,0 @@
|
||||
(function (linkify) {
|
||||
if (typeof define == 'function') {
|
||||
/*
|
||||
* Require.js is available
|
||||
*/
|
||||
define(['../../src/xterm'], linkify);
|
||||
} else {
|
||||
/*
|
||||
* Plain browser environment
|
||||
*/
|
||||
linkify(this.Xterm);
|
||||
}
|
||||
})(function (Xterm) {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* This module provides methods for convertings valid URL substrings
|
||||
* into HTML anchor elements (links), inside a terminal view.
|
||||
*
|
||||
* @module xterm/addons/linkify/linkify
|
||||
*/
|
||||
var exports = {},
|
||||
protocolClause = '(https?:\\/\\/)',
|
||||
domainCharacterSet = '[\\da-z\\.-]+',
|
||||
negatedDomainCharacterSet = '[^\\da-z\\.-]+',
|
||||
domainBodyClause = '(' + domainCharacterSet + ')',
|
||||
tldClause = '([a-z\\.]{2,6})',
|
||||
hostClause = domainBodyClause + '\\.' + tldClause,
|
||||
pathClause = '([\\/\\w\\.-]*)*\\/?',
|
||||
negatedPathCharacterSet = '[^\\/\\w\\.-]+',
|
||||
bodyClause = hostClause + pathClause,
|
||||
start = '(?:^|' + negatedDomainCharacterSet + ')(',
|
||||
end = ')($|' + negatedPathCharacterSet + ')',
|
||||
urlClause = start + protocolClause + '?' + bodyClause + end,
|
||||
urlRegex = new RegExp(urlClause);
|
||||
|
||||
/**
|
||||
* Converts all valid URLs found in the given terminal line into
|
||||
* hyperlinks. The terminal line can be either the HTML element itself
|
||||
* or the index of the termina line in the children of the terminal
|
||||
* rows container.
|
||||
*
|
||||
* @param {Xterm} terminal - The terminal that owns the given line.
|
||||
* @param {number|HTMLDivElement} line - The terminal line that should get
|
||||
* "linkified".
|
||||
* @emits linkify
|
||||
* @emits linkify:line
|
||||
*/
|
||||
exports.linkifyTerminalLine = function (terminal, line) {
|
||||
if (typeof line == 'number') {
|
||||
line = terminal.rowContainer.children[line];
|
||||
} else if (! (line instanceof HTMLDivElement)) {
|
||||
var message = 'The "line" argument should be either a number';
|
||||
message += ' or an HTMLDivElement';
|
||||
|
||||
throw new TypeError(message);
|
||||
}
|
||||
|
||||
var buffer = document.createElement('span'),
|
||||
nodes = line.childNodes;
|
||||
|
||||
for (var j=0; j<nodes.length; j++) {
|
||||
var node = nodes[j];
|
||||
|
||||
/*
|
||||
* Since we cannot access the TextNode's HTML representation
|
||||
* from the instance itself, we assign its data as textContent
|
||||
* to a dummy buffer span, in order to retrieve the TextNode's
|
||||
* HTML representation from the buffer's innerHTML.
|
||||
*/
|
||||
buffer.textContent = node.data;
|
||||
|
||||
var nodeHTML = buffer.innerHTML;
|
||||
|
||||
/*
|
||||
* Apply function only on TextNodes
|
||||
*/
|
||||
if (node.nodeType != node.TEXT_NODE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
var match = node.data.match(urlRegex);
|
||||
|
||||
/*
|
||||
* If no URL was found in the current text, return.
|
||||
*/
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var url = match[1],
|
||||
startsWithProtocol = new RegExp('^' + protocolClause),
|
||||
urlHasProtocol = url.match(startsWithProtocol),
|
||||
href = (urlHasProtocol) ? url : 'http://' + url,
|
||||
link = '<a href="' + href + '" >' + url + '</a>',
|
||||
newHTML = nodeHTML.replace(url, link);
|
||||
|
||||
line.innerHTML = line.innerHTML.replace(nodeHTML, newHTML);
|
||||
}
|
||||
|
||||
/**
|
||||
* This event gets emitted when conversion of all URL susbtrings
|
||||
* to HTML anchor elements (links) has finished, for a specific
|
||||
* line of the current Xterm instance.
|
||||
*
|
||||
* @event linkify:line
|
||||
*/
|
||||
terminal.emit('linkify:line', line);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Converts all valid URLs found in the terminal view into hyperlinks.
|
||||
*
|
||||
* @param {Xterm} terminal - The terminal that should get "linkified".
|
||||
* @emits linkify
|
||||
* @emits linkify:line
|
||||
*/
|
||||
exports.linkify = function (terminal) {
|
||||
var rows = terminal.rowContainer.children;
|
||||
|
||||
for (var i=0; i<rows.length; i++) {
|
||||
var line = rows[i];
|
||||
|
||||
exports.linkifyTerminalLine(terminal, line);
|
||||
}
|
||||
|
||||
/**
|
||||
* This event gets emitted when conversion of all URL substrings to
|
||||
* HTML anchor elements (links) has finished for the current Xterm
|
||||
* instance's view.
|
||||
*
|
||||
* @event linkify
|
||||
*/
|
||||
terminal.emit('linkify');
|
||||
};
|
||||
|
||||
/*
|
||||
* Extend Xterm prototype.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Converts all valid URLs found in the current terminal linte into
|
||||
* hyperlinks.
|
||||
*
|
||||
* @memberof Xterm
|
||||
* @param {number|HTMLDivElement} line - The terminal line that should get
|
||||
* "linkified".
|
||||
*/
|
||||
Xterm.prototype.linkifyTerminalLine = function (line) {
|
||||
return exports.linkifyTerminalLine(this, line);
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts all valid URLs found in the current terminal into hyperlinks.
|
||||
*
|
||||
* @memberof Xterm
|
||||
*/
|
||||
Xterm.prototype.linkify = function () {
|
||||
return exports.linkify(this);
|
||||
};
|
||||
|
||||
return exports;
|
||||
});
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#! /usr/bin/env sh
|
||||
|
||||
tail --lines=+3 AUTHORS > AUTHORS.tmp
|
||||
git log --format='%aN <%aE>' >> AUTHORS.tmp
|
||||
echo "List of xterm.js contributors. Updated before every release.\n" > AUTHORS
|
||||
sort -u AUTHORS.tmp >> AUTHORS
|
||||
rm -f AUTHORS.tmp
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#! /usr/bin/env sh
|
||||
|
||||
# Usage: ./bin/prepare-release x.y.z
|
||||
# x.y.z should be semver (e.g. 1.0.0)
|
||||
|
||||
set -e
|
||||
|
||||
NEW_VERSION=$1
|
||||
CURRENT_PACKAGE_JSON_VERSION=$(cat package.json \
|
||||
| grep version \
|
||||
| head -1 \
|
||||
| awk -F: '{ print $2 }' \
|
||||
| sed 's/[",]//g' \
|
||||
| tr -d '[[:space:]]')
|
||||
CURRENT_BOWER_JSON_VERSION=$(cat bower.json \
|
||||
| grep version \
|
||||
| head -1 \
|
||||
| awk -F: '{ print $2 }' \
|
||||
| sed 's/[",]//g' \
|
||||
| tr -d '[[:space:]]')
|
||||
|
||||
# Build xterm.js into `dist`
|
||||
export BUILD_DIR=dist
|
||||
npm run build
|
||||
|
||||
# Update AUTHORS file
|
||||
sh bin/generate-authors
|
||||
|
||||
# Update version in package.json and bower.json
|
||||
sed -i "s/\"version\": \"$CURRENT_PACKAGE_JSON_VERSION\"/\"version\": \"$NEW_VERSION\"/g" package.json
|
||||
sed -i "s/\"version\": \"$CURRENT_BOWER_JSON_VERSION\"/\"version\": \"$NEW_VERSION\"/g" bower.json
|
||||
|
||||
git commit -S -s -a -m "Bump version to $NEW_VERSION"
|
||||
git tag $NEW_VERSION
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#! /usr/bin/env sh
|
||||
|
||||
# Usage: ./bin/release x.y.z
|
||||
# x.y.z should be semver (e.g. 1.0.0)
|
||||
|
||||
set -e
|
||||
|
||||
if [ -z "$1" ];
|
||||
then
|
||||
echo "No version supplied. Please a version argument\n"
|
||||
echo "Usage: $0 VERSION\n"
|
||||
echo "Example: $0 1.0.0"
|
||||
exit
|
||||
fi
|
||||
|
||||
NEW_VERSION=$1
|
||||
|
||||
./bin/prepare-release $NEW_VERSION
|
||||
|
||||
git push && \
|
||||
git push --tags && \
|
||||
npm publish
|
||||
+6
-2
@@ -1,5 +1,9 @@
|
||||
{
|
||||
"name": "xterm.js",
|
||||
"version": "0.27",
|
||||
"ignore": ["demo", "docs", "test", ".gitignore"]
|
||||
"version": "2.5.0",
|
||||
"ignore": ["demo", "test", ".gitignore"],
|
||||
"main": [
|
||||
"dist/xterm.js",
|
||||
"dist/xterm.css"
|
||||
]
|
||||
}
|
||||
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
var expressWs = require('express-ws')(app);
|
||||
var os = require('os');
|
||||
var pty = require('node-pty');
|
||||
|
||||
var terminals = {},
|
||||
logs = {};
|
||||
|
||||
app.use('/build', express.static(__dirname + '/../build'));
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.sendFile(__dirname + '/index.html');
|
||||
});
|
||||
|
||||
app.get('/style.css', function(req, res){
|
||||
res.sendFile(__dirname + '/style.css');
|
||||
});
|
||||
|
||||
app.get('/main.js', function(req, res){
|
||||
res.sendFile(__dirname + '/main.js');
|
||||
});
|
||||
|
||||
app.post('/terminals', function (req, res) {
|
||||
var cols = parseInt(req.query.cols),
|
||||
rows = parseInt(req.query.rows),
|
||||
term = pty.spawn(process.platform === 'win32' ? 'cmd.exe' : 'bash', [], {
|
||||
name: 'xterm-color',
|
||||
cols: cols || 80,
|
||||
rows: rows || 24,
|
||||
cwd: process.env.PWD,
|
||||
env: process.env
|
||||
});
|
||||
|
||||
console.log('Created terminal with PID: ' + term.pid);
|
||||
terminals[term.pid] = term;
|
||||
logs[term.pid] = '';
|
||||
term.on('data', function(data) {
|
||||
logs[term.pid] += data;
|
||||
});
|
||||
res.send(term.pid.toString());
|
||||
res.end();
|
||||
});
|
||||
|
||||
app.post('/terminals/:pid/size', function (req, res) {
|
||||
var pid = parseInt(req.params.pid),
|
||||
cols = parseInt(req.query.cols),
|
||||
rows = parseInt(req.query.rows),
|
||||
term = terminals[pid];
|
||||
|
||||
term.resize(cols, rows);
|
||||
console.log('Resized terminal ' + pid + ' to ' + cols + ' cols and ' + rows + ' rows.');
|
||||
res.end();
|
||||
});
|
||||
|
||||
app.ws('/terminals/:pid', function (ws, req) {
|
||||
var term = terminals[parseInt(req.params.pid)];
|
||||
console.log('Connected to terminal ' + term.pid);
|
||||
ws.send(logs[term.pid]);
|
||||
|
||||
term.on('data', function(data) {
|
||||
try {
|
||||
ws.send(data);
|
||||
} catch (ex) {
|
||||
// The WebSocket is not open, ignore
|
||||
}
|
||||
});
|
||||
ws.on('message', function(msg) {
|
||||
term.write(msg);
|
||||
});
|
||||
ws.on('close', function () {
|
||||
term.kill();
|
||||
console.log('Closed terminal ' + term.pid);
|
||||
// Clean things up
|
||||
delete terminals[term.pid];
|
||||
delete logs[term.pid];
|
||||
});
|
||||
});
|
||||
|
||||
var port = process.env.PORT || 3000,
|
||||
host = os.platform() === 'win32' ? '127.0.0.1' : '0.0.0.0';
|
||||
|
||||
console.log('App listening to http://' + host + ':' + port);
|
||||
app.listen(port, host);
|
||||
+36
-10
@@ -2,18 +2,44 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>xterm.js demo</title>
|
||||
<link rel="stylesheet" href="../src/xterm.css" />
|
||||
<link rel="stylesheet" href="../addons/fullscreen/fullscreen.css" />
|
||||
<link rel="stylesheet" href="/build/xterm.css" />
|
||||
<link rel="stylesheet" href="/build/addons/fullscreen/fullscreen.css" />
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script src="../src/xterm.js" ></script>
|
||||
<script src="../addons/fit/fit.js" ></script>
|
||||
<script src="../addons/fullscreen/fullscreen.js" ></script>
|
||||
<script src="main.js" defer ></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/1.0.0/fetch.min.js"></script>
|
||||
<script src="/build/xterm.js" ></script>
|
||||
<script src="/build/addons/attach/attach.js" ></script>
|
||||
<script src="/build/addons/fit/fit.js" ></script>
|
||||
<script src="/build/addons/fullscreen/fullscreen.js" ></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>
|
||||
xterm.js: xterm, in the browser
|
||||
</h1>
|
||||
<h1>xterm.js: xterm, in the browser</h1>
|
||||
<div id="terminal-container"></div>
|
||||
<div>
|
||||
<h2>Options</h2>
|
||||
<p>
|
||||
<label><input type="checkbox" id="option-cursor-blink"> cursorBlink</label>
|
||||
</p>
|
||||
<p>
|
||||
<label>scrollback <input type="number" id="option-scrollback" value="1000" /></label>
|
||||
</p>
|
||||
<p>
|
||||
<label>tabStopWidth <input type="number" id="option-tabstopwidth" value="8" /></label>
|
||||
</p>
|
||||
<div>
|
||||
<h3>Size</h3>
|
||||
<div>
|
||||
<div style="display: inline-block; margin-right: 16px;">
|
||||
<label for="cols">Columns</label>
|
||||
<input type="number" id="cols" />
|
||||
</div>
|
||||
<div style="display: inline-block; margin-right: 16px;">
|
||||
<label for="rows">Rows</label>
|
||||
<input type="number" id="rows" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p><strong>Attention:</strong> The demo is a barebones implementation and is designed for xterm.js evaluation purposes only. Exposing the demo to the public as is would introduce security risks for the host.</p>
|
||||
<script src="main.js" defer ></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
+130
-25
@@ -1,32 +1,137 @@
|
||||
var term,
|
||||
protocol,
|
||||
socketURL,
|
||||
socket,
|
||||
pid,
|
||||
charWidth,
|
||||
charHeight;
|
||||
|
||||
var terminalContainer = document.getElementById('terminal-container'),
|
||||
term = new Terminal(),
|
||||
prompt = '> ';
|
||||
optionElements = {
|
||||
cursorBlink: document.querySelector('#option-cursor-blink'),
|
||||
scrollback: document.querySelector('#option-scrollback'),
|
||||
tabstopwidth: document.querySelector('#option-tabstopwidth')
|
||||
},
|
||||
colsElement = document.getElementById('cols'),
|
||||
rowsElement = document.getElementById('rows');
|
||||
|
||||
term.open(terminalContainer);
|
||||
term.fit();
|
||||
function setTerminalSize () {
|
||||
var cols = parseInt(colsElement.value, 10),
|
||||
rows = parseInt(rowsElement.value, 10),
|
||||
width = (cols * charWidth).toString() + 'px',
|
||||
height = (rows * charHeight).toString() + 'px';
|
||||
|
||||
term.prompt = function () {
|
||||
term.write('\r\n' + prompt);
|
||||
};
|
||||
terminalContainer.style.width = width;
|
||||
terminalContainer.style.height = height;
|
||||
term.resize(cols, rows);
|
||||
}
|
||||
|
||||
term.writeln('Welcome to xterm.js');
|
||||
term.writeln('Just type some keys in the prompt below.');
|
||||
term.writeln('');
|
||||
term.prompt();
|
||||
colsElement.addEventListener('change', setTerminalSize);
|
||||
rowsElement.addEventListener('change', setTerminalSize);
|
||||
|
||||
term.on('key', function (key, ev) {
|
||||
var printable = (!ev.altKey && !ev.altGraphKey && !ev.ctrlKey && !ev.metaKey);
|
||||
optionElements.cursorBlink.addEventListener('change', function () {
|
||||
term.setOption('cursorBlink', optionElements.cursorBlink.checked);
|
||||
});
|
||||
optionElements.scrollback.addEventListener('change', function () {
|
||||
term.setOption('scrollback', parseInt(optionElements.scrollback.value, 10));
|
||||
});
|
||||
optionElements.tabstopwidth.addEventListener('change', function () {
|
||||
term.setOption('tabStopWidth', parseInt(optionElements.tabstopwidth.value, 10));
|
||||
});
|
||||
|
||||
if (ev.keyCode == 13) {
|
||||
term.prompt();
|
||||
} else if (ev.keyCode == 8) {
|
||||
/*
|
||||
* Do not delete the prompt
|
||||
*/
|
||||
if (term.x > 2) {
|
||||
term.write('\b \b');
|
||||
}
|
||||
} else if (printable) {
|
||||
term.write(key);
|
||||
createTerminal();
|
||||
|
||||
function createTerminal() {
|
||||
// Clean terminal
|
||||
while (terminalContainer.children.length) {
|
||||
terminalContainer.removeChild(terminalContainer.children[0]);
|
||||
}
|
||||
});
|
||||
term = new Terminal({
|
||||
cursorBlink: optionElements.cursorBlink.checked,
|
||||
scrollback: parseInt(optionElements.scrollback.value, 10),
|
||||
tabStopWidth: parseInt(optionElements.tabstopwidth.value, 10)
|
||||
});
|
||||
term.on('resize', function (size) {
|
||||
if (!pid) {
|
||||
return;
|
||||
}
|
||||
var cols = size.cols,
|
||||
rows = size.rows,
|
||||
url = '/terminals/' + pid + '/size?cols=' + cols + '&rows=' + rows;
|
||||
|
||||
fetch(url, {method: 'POST'});
|
||||
});
|
||||
protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://';
|
||||
socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/';
|
||||
|
||||
term.open(terminalContainer);
|
||||
term.fit();
|
||||
|
||||
var initialGeometry = term.proposeGeometry(),
|
||||
cols = initialGeometry.cols,
|
||||
rows = initialGeometry.rows;
|
||||
|
||||
colsElement.value = cols;
|
||||
rowsElement.value = rows;
|
||||
|
||||
fetch('/terminals?cols=' + cols + '&rows=' + rows, {method: 'POST'}).then(function (res) {
|
||||
|
||||
charWidth = Math.ceil(term.element.offsetWidth / cols);
|
||||
charHeight = Math.ceil(term.element.offsetHeight / rows);
|
||||
|
||||
res.text().then(function (pid) {
|
||||
window.pid = pid;
|
||||
socketURL += pid;
|
||||
socket = new WebSocket(socketURL);
|
||||
socket.onopen = runRealTerminal;
|
||||
socket.onclose = runFakeTerminal;
|
||||
socket.onerror = runFakeTerminal;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function runRealTerminal() {
|
||||
term.attach(socket);
|
||||
term._initialized = true;
|
||||
}
|
||||
|
||||
function runFakeTerminal() {
|
||||
if (term._initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
term._initialized = true;
|
||||
|
||||
var shellprompt = '$ ';
|
||||
|
||||
term.prompt = function () {
|
||||
term.write('\r\n' + shellprompt);
|
||||
};
|
||||
|
||||
term.writeln('Welcome to xterm.js');
|
||||
term.writeln('This is a local terminal emulation, without a real terminal in the back-end.');
|
||||
term.writeln('Type some keys and commands to play around.');
|
||||
term.writeln('');
|
||||
term.prompt();
|
||||
|
||||
term.on('key', function (key, ev) {
|
||||
var printable = (
|
||||
!ev.altKey && !ev.altGraphKey && !ev.ctrlKey && !ev.metaKey
|
||||
);
|
||||
|
||||
if (ev.keyCode == 13) {
|
||||
term.prompt();
|
||||
} else if (ev.keyCode == 8) {
|
||||
// Do not delete the prompt
|
||||
if (term.x > 2) {
|
||||
term.write('\b \b');
|
||||
}
|
||||
} else if (printable) {
|
||||
term.write(key);
|
||||
}
|
||||
});
|
||||
|
||||
term.on('paste', function (data, ev) {
|
||||
term.write(data);
|
||||
});
|
||||
}
|
||||
|
||||
+2
-6
@@ -9,8 +9,8 @@ h1 {
|
||||
}
|
||||
|
||||
#terminal-container {
|
||||
width: 960px;
|
||||
height: 600px;
|
||||
width: 800px;
|
||||
height: 450px;
|
||||
margin: 0 auto;
|
||||
padding: 2px;
|
||||
}
|
||||
@@ -20,7 +20,3 @@ h1 {
|
||||
color: #fafafa;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#terminal-container .terminal .terminal-cursor {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
Vendored
+126
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Implements the attach method, that attaches the terminal to a WebSocket stream.
|
||||
* @module xterm/addons/attach/attach
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
(function (attach) {
|
||||
if (typeof exports === 'object' && typeof module === 'object') {
|
||||
/*
|
||||
* CommonJS environment
|
||||
*/
|
||||
module.exports = attach(require('../../xterm'));
|
||||
} else if (typeof define == 'function') {
|
||||
/*
|
||||
* Require.js is available
|
||||
*/
|
||||
define(['../../xterm'], attach);
|
||||
} else {
|
||||
/*
|
||||
* Plain browser environment
|
||||
*/
|
||||
attach(window.Terminal);
|
||||
}
|
||||
})(function (Xterm) {
|
||||
'use strict';
|
||||
|
||||
var exports = {};
|
||||
|
||||
/**
|
||||
* Attaches the given terminal to the given socket.
|
||||
*
|
||||
* @param {Xterm} term - The terminal to be attached to the given socket.
|
||||
* @param {WebSocket} socket - The socket to attach the current terminal.
|
||||
* @param {boolean} bidirectional - Whether the terminal should send data
|
||||
* to the socket as well.
|
||||
* @param {boolean} buffered - Whether the rendering of incoming data
|
||||
* should happen instantly or at a maximum
|
||||
* frequency of 1 rendering per 10ms.
|
||||
*/
|
||||
exports.attach = function (term, socket, bidirectional, buffered) {
|
||||
bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional;
|
||||
term.socket = socket;
|
||||
|
||||
term._flushBuffer = function () {
|
||||
term.write(term._attachSocketBuffer);
|
||||
term._attachSocketBuffer = null;
|
||||
clearTimeout(term._attachSocketBufferTimer);
|
||||
term._attachSocketBufferTimer = null;
|
||||
};
|
||||
|
||||
term._pushToBuffer = function (data) {
|
||||
if (term._attachSocketBuffer) {
|
||||
term._attachSocketBuffer += data;
|
||||
} else {
|
||||
term._attachSocketBuffer = data;
|
||||
setTimeout(term._flushBuffer, 10);
|
||||
}
|
||||
};
|
||||
|
||||
term._getMessage = function (ev) {
|
||||
if (buffered) {
|
||||
term._pushToBuffer(ev.data);
|
||||
} else {
|
||||
term.write(ev.data);
|
||||
}
|
||||
};
|
||||
|
||||
term._sendData = function (data) {
|
||||
socket.send(data);
|
||||
};
|
||||
|
||||
socket.addEventListener('message', term._getMessage);
|
||||
|
||||
if (bidirectional) {
|
||||
term.on('data', term._sendData);
|
||||
}
|
||||
|
||||
socket.addEventListener('close', term.detach.bind(term, socket));
|
||||
socket.addEventListener('error', term.detach.bind(term, socket));
|
||||
};
|
||||
|
||||
/**
|
||||
* Detaches the given terminal from the given socket
|
||||
*
|
||||
* @param {Xterm} term - The terminal to be detached from the given socket.
|
||||
* @param {WebSocket} socket - The socket from which to detach the current
|
||||
* terminal.
|
||||
*/
|
||||
exports.detach = function (term, socket) {
|
||||
term.off('data', term._sendData);
|
||||
|
||||
socket = (typeof socket == 'undefined') ? term.socket : socket;
|
||||
|
||||
if (socket) {
|
||||
socket.removeEventListener('message', term._getMessage);
|
||||
}
|
||||
|
||||
delete term.socket;
|
||||
};
|
||||
|
||||
/**
|
||||
* Attaches the current terminal to the given socket
|
||||
*
|
||||
* @param {WebSocket} socket - The socket to attach the current terminal.
|
||||
* @param {boolean} bidirectional - Whether the terminal should send data
|
||||
* to the socket as well.
|
||||
* @param {boolean} buffered - Whether the rendering of incoming data
|
||||
* should happen instantly or at a maximum
|
||||
* frequency of 1 rendering per 10ms.
|
||||
*/
|
||||
Xterm.prototype.attach = function (socket, bidirectional, buffered) {
|
||||
return exports.attach(this, socket, bidirectional, buffered);
|
||||
};
|
||||
|
||||
/**
|
||||
* Detaches the current terminal from the given socket.
|
||||
*
|
||||
* @param {WebSocket} socket - The socket from which to detach the current
|
||||
* terminal.
|
||||
*/
|
||||
Xterm.prototype.detach = function (socket) {
|
||||
return exports.detach(this, socket);
|
||||
};
|
||||
|
||||
return exports;
|
||||
});
|
||||
Vendored
+86
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Fit terminal columns and rows to the dimensions of its DOM element.
|
||||
*
|
||||
* ## Approach
|
||||
* - Rows: Truncate the division of the terminal parent element height by the terminal row height.
|
||||
*
|
||||
* - Columns: Truncate the division of the terminal parent element width by the terminal character
|
||||
* width (apply display: inline at the terminal row and truncate its width with the current
|
||||
* number of columns).
|
||||
* @module xterm/addons/fit/fit
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
(function (fit) {
|
||||
if (typeof exports === 'object' && typeof module === 'object') {
|
||||
/*
|
||||
* CommonJS environment
|
||||
*/
|
||||
module.exports = fit(require('../../xterm'));
|
||||
} else if (typeof define == 'function') {
|
||||
/*
|
||||
* Require.js is available
|
||||
*/
|
||||
define(['../../xterm'], fit);
|
||||
} else {
|
||||
/*
|
||||
* Plain browser environment
|
||||
*/
|
||||
fit(window.Terminal);
|
||||
}
|
||||
})(function (Xterm) {
|
||||
var exports = {};
|
||||
|
||||
exports.proposeGeometry = function (term) {
|
||||
if (!term.element.parentElement) {
|
||||
return null;
|
||||
}
|
||||
var parentElementStyle = window.getComputedStyle(term.element.parentElement),
|
||||
parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height')),
|
||||
parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')) - 17),
|
||||
elementStyle = window.getComputedStyle(term.element),
|
||||
elementPaddingVer = parseInt(elementStyle.getPropertyValue('padding-top')) + parseInt(elementStyle.getPropertyValue('padding-bottom')),
|
||||
elementPaddingHor = parseInt(elementStyle.getPropertyValue('padding-right')) + parseInt(elementStyle.getPropertyValue('padding-left')),
|
||||
availableHeight = parentElementHeight - elementPaddingVer,
|
||||
availableWidth = parentElementWidth - elementPaddingHor,
|
||||
container = term.rowContainer,
|
||||
subjectRow = term.rowContainer.firstElementChild,
|
||||
contentBuffer = subjectRow.innerHTML,
|
||||
characterHeight,
|
||||
rows,
|
||||
characterWidth,
|
||||
cols,
|
||||
geometry;
|
||||
|
||||
subjectRow.style.display = 'inline';
|
||||
subjectRow.innerHTML = 'W'; // Common character for measuring width, although on monospace
|
||||
characterWidth = subjectRow.getBoundingClientRect().width;
|
||||
subjectRow.style.display = ''; // Revert style before calculating height, since they differ.
|
||||
characterHeight = subjectRow.getBoundingClientRect().height;
|
||||
subjectRow.innerHTML = contentBuffer;
|
||||
|
||||
rows = parseInt(availableHeight / characterHeight);
|
||||
cols = parseInt(availableWidth / characterWidth);
|
||||
|
||||
geometry = {cols: cols, rows: rows};
|
||||
return geometry;
|
||||
};
|
||||
|
||||
exports.fit = function (term) {
|
||||
var geometry = exports.proposeGeometry(term);
|
||||
|
||||
if (geometry) {
|
||||
term.resize(geometry.cols, geometry.rows);
|
||||
}
|
||||
};
|
||||
|
||||
Xterm.prototype.proposeGeometry = function () {
|
||||
return exports.proposeGeometry(this);
|
||||
};
|
||||
|
||||
Xterm.prototype.fit = function () {
|
||||
return exports.fit(this);
|
||||
};
|
||||
|
||||
return exports;
|
||||
});
|
||||
@@ -7,4 +7,4 @@
|
||||
width: auto;
|
||||
height: auto;
|
||||
z-index: 255;
|
||||
}
|
||||
}
|
||||
Vendored
+50
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Fullscreen addon for xterm.js
|
||||
* @module xterm/addons/fullscreen/fullscreen
|
||||
* @license MIT
|
||||
*/
|
||||
(function (fullscreen) {
|
||||
if (typeof exports === 'object' && typeof module === 'object') {
|
||||
/*
|
||||
* CommonJS environment
|
||||
*/
|
||||
module.exports = fullscreen(require('../../xterm'));
|
||||
} else if (typeof define == 'function') {
|
||||
/*
|
||||
* Require.js is available
|
||||
*/
|
||||
define(['../../xterm'], fullscreen);
|
||||
} else {
|
||||
/*
|
||||
* Plain browser environment
|
||||
*/
|
||||
fullscreen(window.Terminal);
|
||||
}
|
||||
})(function (Xterm) {
|
||||
var exports = {};
|
||||
|
||||
/**
|
||||
* Toggle the given terminal's fullscreen mode.
|
||||
* @param {Xterm} term - The terminal to toggle full screen mode
|
||||
* @param {boolean} fullscreen - Toggle fullscreen on (true) or off (false)
|
||||
*/
|
||||
exports.toggleFullScreen = function (term, fullscreen) {
|
||||
var fn;
|
||||
|
||||
if (typeof fullscreen == 'undefined') {
|
||||
fn = (term.element.classList.contains('fullscreen')) ? 'remove' : 'add';
|
||||
} else if (!fullscreen) {
|
||||
fn = 'remove';
|
||||
} else {
|
||||
fn = 'add';
|
||||
}
|
||||
|
||||
term.element.classList[fn]('fullscreen');
|
||||
};
|
||||
|
||||
Xterm.prototype.toggleFullscreen = function (fullscreen) {
|
||||
exports.toggleFullScreen(this, fullscreen);
|
||||
};
|
||||
|
||||
return exports;
|
||||
});
|
||||
Vendored
+207
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* Methods for turning URL subscrings in the terminal's content into links (`a` DOM elements).
|
||||
* @module xterm/addons/linkify/linkify
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
(function (linkify) {
|
||||
if (typeof exports === 'object' && typeof module === 'object') {
|
||||
/*
|
||||
* CommonJS environment
|
||||
*/
|
||||
module.exports = linkify(require('../../xterm'));
|
||||
} else if (typeof define == 'function') {
|
||||
/*
|
||||
* Require.js is available
|
||||
*/
|
||||
define(['../../xterm'], linkify);
|
||||
} else {
|
||||
/*
|
||||
* Plain browser environment
|
||||
*/
|
||||
linkify(window.Terminal);
|
||||
}
|
||||
})(function (Xterm) {
|
||||
'use strict';
|
||||
|
||||
var exports = {},
|
||||
protocolClause = '(https?:\\/\\/)',
|
||||
domainCharacterSet = '[\\da-z\\.-]+',
|
||||
negatedDomainCharacterSet = '[^\\da-z\\.-]+',
|
||||
domainBodyClause = '(' + domainCharacterSet + ')',
|
||||
tldClause = '([a-z\\.]{2,6})',
|
||||
ipClause = '((\\d{1,3}\\.){3}\\d{1,3})',
|
||||
portClause = '(:\\d{1,5})',
|
||||
hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + ')' + portClause + '?',
|
||||
pathClause = '(\\/[\\/\\w\\.-]*)*',
|
||||
negatedPathCharacterSet = '[^\\/\\w\\.-]+',
|
||||
bodyClause = hostClause + pathClause,
|
||||
start = '(?:^|' + negatedDomainCharacterSet + ')(',
|
||||
end = ')($|' + negatedPathCharacterSet + ')',
|
||||
lenientUrlClause = start + protocolClause + '?' + bodyClause + end,
|
||||
strictUrlClause = start + protocolClause + bodyClause + end,
|
||||
lenientUrlRegex = new RegExp(lenientUrlClause),
|
||||
strictUrlRegex = new RegExp(strictUrlClause);
|
||||
|
||||
/**
|
||||
* Converts all valid URLs found in the given terminal line into
|
||||
* hyperlinks. The terminal line can be either the HTML element itself
|
||||
* or the index of the termina line in the children of the terminal
|
||||
* rows container.
|
||||
*
|
||||
* @param {Xterm} terminal - The terminal that owns the given line.
|
||||
* @param {number|HTMLDivElement} line - The terminal line that should get
|
||||
* "linkified".
|
||||
* @param {boolean} lenient - The regex type that will be used to identify links. If lenient is
|
||||
* false, the regex requires a protocol clause. Defaults to true.
|
||||
* @param {string} target - Sets target="" attribute with value provided to links.
|
||||
* Default doesn't set target attribute
|
||||
* @emits linkify
|
||||
* @emits linkify:line
|
||||
*/
|
||||
exports.linkifyTerminalLine = function (terminal, line, lenient, target) {
|
||||
if (typeof line == 'number') {
|
||||
line = terminal.rowContainer.children[line];
|
||||
} else if (! (line instanceof HTMLDivElement)) {
|
||||
var message = 'The "line" argument should be either a number';
|
||||
message += ' or an HTMLDivElement';
|
||||
|
||||
throw new TypeError(message);
|
||||
}
|
||||
|
||||
if (typeof target === 'undefined') {
|
||||
target = '';
|
||||
} else {
|
||||
target = 'target="' + target + '"';
|
||||
}
|
||||
|
||||
var buffer = document.createElement('span'),
|
||||
nodes = line.childNodes;
|
||||
|
||||
for (var j=0; j<nodes.length; j++) {
|
||||
var node = nodes[j],
|
||||
match;
|
||||
|
||||
/**
|
||||
* Since we cannot access the TextNode's HTML representation
|
||||
* from the instance itself, we assign its data as textContent
|
||||
* to a dummy buffer span, in order to retrieve the TextNode's
|
||||
* HTML representation from the buffer's innerHTML.
|
||||
*/
|
||||
buffer.textContent = node.data;
|
||||
|
||||
var nodeHTML = buffer.innerHTML;
|
||||
|
||||
/**
|
||||
* Apply function only on TextNodes
|
||||
*/
|
||||
if (node.nodeType != node.TEXT_NODE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var url = exports.findLinkMatch(node.data, lenient);
|
||||
|
||||
if (!url) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var startsWithProtocol = new RegExp('^' + protocolClause),
|
||||
urlHasProtocol = url.match(startsWithProtocol),
|
||||
href = (urlHasProtocol) ? url : 'http://' + url,
|
||||
link = '<a href="' + href + '" ' + target + '>' + url + '</a>',
|
||||
newHTML = nodeHTML.replace(url, link);
|
||||
|
||||
line.innerHTML = line.innerHTML.replace(nodeHTML, newHTML);
|
||||
}
|
||||
|
||||
/**
|
||||
* This event gets emitted when conversion of all URL susbtrings
|
||||
* to HTML anchor elements (links) has finished, for a specific
|
||||
* line of the current Xterm instance.
|
||||
*
|
||||
* @event linkify:line
|
||||
*/
|
||||
terminal.emit('linkify:line', line);
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds a link within a block of text.
|
||||
*
|
||||
* @param {string} text - The text to search .
|
||||
* @param {boolean} lenient - Whether to use the lenient search.
|
||||
* @return {string} A URL.
|
||||
*/
|
||||
exports.findLinkMatch = function (text, lenient) {
|
||||
var match = text.match(lenient ? lenientUrlRegex : strictUrlRegex);
|
||||
if (!match || match.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return match[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts all valid URLs found in the terminal view into hyperlinks.
|
||||
*
|
||||
* @param {Xterm} terminal - The terminal that should get "linkified".
|
||||
* @param {boolean} lenient - The regex type that will be used to identify links. If lenient is
|
||||
* false, the regex requires a protocol clause. Defaults to true.
|
||||
* @param {string} target - Sets target="" attribute with value provided to links.
|
||||
* Default doesn't set target attribute
|
||||
* @emits linkify
|
||||
* @emits linkify:line
|
||||
*/
|
||||
exports.linkify = function (terminal, lenient, target) {
|
||||
var rows = terminal.rowContainer.children;
|
||||
|
||||
lenient = (typeof lenient == "boolean") ? lenient : true;
|
||||
for (var i=0; i<rows.length; i++) {
|
||||
var line = rows[i];
|
||||
|
||||
exports.linkifyTerminalLine(terminal, line, lenient, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* This event gets emitted when conversion of all URL substrings to
|
||||
* HTML anchor elements (links) has finished for the current Xterm
|
||||
* instance's view.
|
||||
*
|
||||
* @event linkify
|
||||
*/
|
||||
terminal.emit('linkify');
|
||||
};
|
||||
|
||||
/**
|
||||
* Extend Xterm prototype.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Converts all valid URLs found in the current terminal linte into
|
||||
* hyperlinks.
|
||||
*
|
||||
* @memberof Xterm
|
||||
* @param {number|HTMLDivElement} line - The terminal line that should get
|
||||
* "linkified".
|
||||
* @param {boolean} lenient - The regex type that will be used to identify links. If lenient is
|
||||
* false, the regex requires a protocol clause. Defaults to true.
|
||||
* @param {string} target - Sets target="" attribute with value provided to links.
|
||||
* Default doesn't set target attribute
|
||||
*/
|
||||
Xterm.prototype.linkifyTerminalLine = function (line, lenient, target) {
|
||||
return exports.linkifyTerminalLine(this, line, lenient, target);
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts all valid URLs found in the current terminal into hyperlinks.
|
||||
*
|
||||
* @memberof Xterm
|
||||
* @param {boolean} lenient - The regex type that will be used to identify links. If lenient is
|
||||
* false, the regex requires a protocol clause. Defaults to true.
|
||||
* @param {string} target - Sets target="" attribute with value provided to links.
|
||||
* Default doesn't set target attribute
|
||||
*/
|
||||
Xterm.prototype.linkify = function (lenient, target) {
|
||||
return exports.linkify(this, lenient, target);
|
||||
};
|
||||
|
||||
return exports;
|
||||
});
|
||||
Vendored
+135
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* This module provides methods for attaching a terminal to a terminado WebSocket stream.
|
||||
*
|
||||
* @module xterm/addons/terminado/terminado
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
(function (attach) {
|
||||
if (typeof exports === 'object' && typeof module === 'object') {
|
||||
/*
|
||||
* CommonJS environment
|
||||
*/
|
||||
module.exports = attach(require('../../xterm'));
|
||||
} else if (typeof define == 'function') {
|
||||
/*
|
||||
* Require.js is available
|
||||
*/
|
||||
define(['../../xterm'], attach);
|
||||
} else {
|
||||
/*
|
||||
* Plain browser environment
|
||||
*/
|
||||
attach(window.Terminal);
|
||||
}
|
||||
})(function (Xterm) {
|
||||
'use strict';
|
||||
|
||||
var exports = {};
|
||||
|
||||
/**
|
||||
* Attaches the given terminal to the given socket.
|
||||
*
|
||||
* @param {Xterm} term - The terminal to be attached to the given socket.
|
||||
* @param {WebSocket} socket - The socket to attach the current terminal.
|
||||
* @param {boolean} bidirectional - Whether the terminal should send data
|
||||
* to the socket as well.
|
||||
* @param {boolean} buffered - Whether the rendering of incoming data
|
||||
* should happen instantly or at a maximum
|
||||
* frequency of 1 rendering per 10ms.
|
||||
*/
|
||||
exports.terminadoAttach = function (term, socket, bidirectional, buffered) {
|
||||
bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional;
|
||||
term.socket = socket;
|
||||
|
||||
term._flushBuffer = function () {
|
||||
term.write(term._attachSocketBuffer);
|
||||
term._attachSocketBuffer = null;
|
||||
clearTimeout(term._attachSocketBufferTimer);
|
||||
term._attachSocketBufferTimer = null;
|
||||
};
|
||||
|
||||
term._pushToBuffer = function (data) {
|
||||
if (term._attachSocketBuffer) {
|
||||
term._attachSocketBuffer += data;
|
||||
} else {
|
||||
term._attachSocketBuffer = data;
|
||||
setTimeout(term._flushBuffer, 10);
|
||||
}
|
||||
};
|
||||
|
||||
term._getMessage = function (ev) {
|
||||
var data = JSON.parse(ev.data)
|
||||
if( data[0] == "stdout" ) {
|
||||
if (buffered) {
|
||||
term._pushToBuffer(data[1]);
|
||||
} else {
|
||||
term.write(data[1]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
term._sendData = function (data) {
|
||||
socket.send(JSON.stringify(['stdin', data]));
|
||||
};
|
||||
|
||||
term._setSize = function (size) {
|
||||
socket.send(JSON.stringify(['set_size', size.rows, size.cols]));
|
||||
};
|
||||
|
||||
socket.addEventListener('message', term._getMessage);
|
||||
|
||||
if (bidirectional) {
|
||||
term.on('data', term._sendData);
|
||||
}
|
||||
term.on('resize', term._setSize);
|
||||
|
||||
socket.addEventListener('close', term.terminadoDetach.bind(term, socket));
|
||||
socket.addEventListener('error', term.terminadoDetach.bind(term, socket));
|
||||
};
|
||||
|
||||
/**
|
||||
* Detaches the given terminal from the given socket
|
||||
*
|
||||
* @param {Xterm} term - The terminal to be detached from the given socket.
|
||||
* @param {WebSocket} socket - The socket from which to detach the current
|
||||
* terminal.
|
||||
*/
|
||||
exports.terminadoDetach = function (term, socket) {
|
||||
term.off('data', term._sendData);
|
||||
|
||||
socket = (typeof socket == 'undefined') ? term.socket : socket;
|
||||
|
||||
if (socket) {
|
||||
socket.removeEventListener('message', term._getMessage);
|
||||
}
|
||||
|
||||
delete term.socket;
|
||||
};
|
||||
|
||||
/**
|
||||
* Attaches the current terminal to the given socket
|
||||
*
|
||||
* @param {WebSocket} socket - The socket to attach the current terminal.
|
||||
* @param {boolean} bidirectional - Whether the terminal should send data
|
||||
* to the socket as well.
|
||||
* @param {boolean} buffered - Whether the rendering of incoming data
|
||||
* should happen instantly or at a maximum
|
||||
* frequency of 1 rendering per 10ms.
|
||||
*/
|
||||
Xterm.prototype.terminadoAttach = function (socket, bidirectional, buffered) {
|
||||
return exports.terminadoAttach(this, socket, bidirectional, buffered);
|
||||
};
|
||||
|
||||
/**
|
||||
* Detaches the current terminal from the given socket.
|
||||
*
|
||||
* @param {WebSocket} socket - The socket from which to detach the current
|
||||
* terminal.
|
||||
*/
|
||||
Xterm.prototype.terminadoDetach = function (socket) {
|
||||
return exports.terminadoDetach(this, socket);
|
||||
};
|
||||
|
||||
return exports;
|
||||
});
|
||||
Vendored
+2247
File diff suppressed because it is too large
Load Diff
Vendored
+4208
File diff suppressed because it is too large
Load Diff
Vendored
+1
File diff suppressed because one or more lines are too long
@@ -0,0 +1,9 @@
|
||||
version: '2'
|
||||
|
||||
services:
|
||||
web:
|
||||
build: ./
|
||||
volumes:
|
||||
- ./:/usr/src/app
|
||||
ports:
|
||||
- 3000:3000
|
||||
-177
@@ -1,177 +0,0 @@
|
||||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# User-friendly check for sphinx-build
|
||||
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
|
||||
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
|
||||
endif
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " texinfo to make Texinfo files"
|
||||
@echo " info to make Texinfo files and run them through makeinfo"
|
||||
@echo " gettext to make PO message catalogs"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " xml to make Docutils-native XML files"
|
||||
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/xtermjs.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/xtermjs.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/xtermjs"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/xtermjs"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
latexpdfja:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through platex and dvipdfmx..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
|
||||
xml:
|
||||
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
|
||||
@echo
|
||||
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
|
||||
|
||||
pseudoxml:
|
||||
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
||||
@echo
|
||||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
||||
-258
@@ -1,258 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# xterm.js documentation build configuration file, created by
|
||||
# sphinx-quickstart on Tue Mar 25 17:38:05 2014.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = []
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'xterm.js'
|
||||
copyright = u'2015, SourceLair Limited'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.29'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0.29 Alpha'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||
#keep_warnings = False
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'default'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# directly to the root of the documentation.
|
||||
#html_extra_path = []
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'xtermjsdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
('index', 'xtermjs.tex', u'xterm.js Documentation',
|
||||
u'sourceLair Limited', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'xtermjs', u'xterm.js Documentation',
|
||||
[u'sourceLair Limited'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'xtermjs', u'xterm.js Documentation',
|
||||
u'sourceLair Limited', 'xtermjs', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
|
||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||
#texinfo_no_detailmenu = False
|
||||
@@ -1,43 +0,0 @@
|
||||
------
|
||||
Events
|
||||
------
|
||||
|
||||
focus
|
||||
^^^^^
|
||||
This event is fired, when the Terminal gets focus.
|
||||
|
||||
blur
|
||||
^^^^
|
||||
This event is fired, when the Terminal loses focus.
|
||||
|
||||
open
|
||||
^^^^
|
||||
This event is fired, when the Terminal gets opened into a DOM Element.
|
||||
|
||||
keydown
|
||||
^^^^^^^
|
||||
This event is fired, when the `keydown` DOM Event gets triggered on the Terminal.
|
||||
|
||||
keypress
|
||||
^^^^^^^^
|
||||
This event is fired, when the `keypress` DOM Event gets triggered on the Terminal.
|
||||
|
||||
key
|
||||
^^^
|
||||
This event is fired, when a key is being handled by the Terminal.
|
||||
|
||||
resize
|
||||
^^^^^^
|
||||
This event is being fired, when the Terminal view gets resized by the `resize` method.
|
||||
|
||||
data
|
||||
^^^^
|
||||
This event is being fired, when a chunk of input data gets handled by the terminal.
|
||||
|
||||
copy
|
||||
^^^^
|
||||
This event is fired, when the `copy` DOM Event gets triggered on the Terminal.
|
||||
|
||||
paste
|
||||
^^^^^
|
||||
This event is fired, when the `paste` DOM Event gets triggered on the Terminal.
|
||||
@@ -1,45 +0,0 @@
|
||||
.. xterm.js documentation master file, created by
|
||||
sphinx-quickstart on Tue Mar 25 17:38:05 2014.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to xterm.js's documentation!
|
||||
====================================
|
||||
|
||||
This is the documentation for xterm.js. Xterm.js is a full xterm clone, written completely in JavaScript.
|
||||
|
||||
Xterm.js supplies a modular, event-based interface that lets developers build addons and themes that augment
|
||||
the experience of running a fully-feature terminal inside the browser.
|
||||
|
||||
Xterm.js serves as the foundation for the terminal found at www.sourcelair.com.
|
||||
|
||||
.. image:: screenshot.png
|
||||
|
||||
Getting started
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
var term = new Terminal(),
|
||||
url = 'ws://docker/containers/mycontainer/attach/ws/?stdin=1&stdout=1&stderr=1&logs=1',
|
||||
socket = new WebSocket(url);
|
||||
|
||||
term.open(document.body);
|
||||
term.on('data', function (data) {
|
||||
socket.send(data);
|
||||
});
|
||||
socket.onmessage = function (e) {
|
||||
term.write(e.data);
|
||||
}
|
||||
|
||||
|
||||
Contents
|
||||
^^^^^^^^
|
||||
The standard way to create a terminal-based application with xterm.js is to work with its events and
|
||||
methods, that let you manipulate input, output, state and size of the terminal.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
methods
|
||||
events
|
||||
@@ -1,47 +0,0 @@
|
||||
-------
|
||||
Methods
|
||||
-------
|
||||
|
||||
focus()
|
||||
^^^^^^^
|
||||
Sends focus to the terminal
|
||||
|
||||
blur()
|
||||
^^^^^^
|
||||
Blurs the Terminal
|
||||
|
||||
open(element)
|
||||
^^^^
|
||||
Open the Xterm terminal, inside the given element.
|
||||
|
||||
resize(x, y)
|
||||
^^^^^^^^^^^^
|
||||
Resize the terminal, according to the given columns and rows.
|
||||
|
||||
insertRow(row)
|
||||
^^^^^^^^^^^^^^
|
||||
Insert the given row in the terminal.
|
||||
|
||||
on(event, callback)
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
Hook the given callback, to the given event.
|
||||
|
||||
off(event, callback)
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
Remove the Hook of the given callback, from the given event.
|
||||
|
||||
once(event, callback)
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
Hook the given callback to the given event, for just a single invocation.
|
||||
|
||||
write(data)
|
||||
^^^^^^^^^^^
|
||||
Write the given data to the terminal.
|
||||
|
||||
writeln(data)
|
||||
^^^^^^^^^^^^^
|
||||
Write the given data to the terminal, followed by a carriage return and a new line.
|
||||
|
||||
handler(data)
|
||||
^^^^^^^^^^^^^
|
||||
Fire the data event, for the given data.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 140 KiB |
+110
@@ -0,0 +1,110 @@
|
||||
const browserify = require('browserify');
|
||||
const buffer = require('vinyl-buffer');
|
||||
const coveralls = require('gulp-coveralls');
|
||||
const fs = require('fs-extra');
|
||||
const gulp = require('gulp');
|
||||
const istanbul = require('gulp-istanbul');
|
||||
const merge = require('merge-stream');
|
||||
const mocha = require('gulp-mocha');
|
||||
const sorcery = require('sorcery');
|
||||
const source = require('vinyl-source-stream');
|
||||
const sourcemaps = require('gulp-sourcemaps');
|
||||
const ts = require('gulp-typescript');
|
||||
|
||||
|
||||
let buildDir = process.env.BUILD_DIR || 'build';
|
||||
let tsProject = ts.createProject('tsconfig.json');
|
||||
let srcDir = tsProject.config.compilerOptions.rootDir;
|
||||
let outDir = tsProject.config.compilerOptions.outDir;
|
||||
|
||||
/**
|
||||
* Compile TypeScript sources to JavaScript files and create a source map file for each TypeScript
|
||||
* file compiled.
|
||||
*/
|
||||
gulp.task('tsc', function () {
|
||||
// Remove the ${outDir}/ directory to prevent confusion if files were deleted in ${srcDir}/
|
||||
fs.emptyDirSync(`${outDir}`);
|
||||
|
||||
// Build all TypeScript files (including tests) to ${outDir}/, based on the configuration defined in
|
||||
// `tsconfig.json`.
|
||||
let tsResult = tsProject.src().pipe(sourcemaps.init()).pipe(tsProject());
|
||||
let tsc = tsResult.js.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''})).pipe(gulp.dest(outDir));
|
||||
|
||||
// Copy all addons from ${srcDir}/ to ${outDir}/
|
||||
let copyAddons = gulp.src(`${srcDir}/addons/**/*`).pipe(gulp.dest(`${outDir}/addons`));
|
||||
|
||||
// Copy stylesheets from ${srcDir}/ to ${outDir}/
|
||||
let copyStylesheets = gulp.src(`${srcDir}/**/*.css`).pipe(gulp.dest(outDir));
|
||||
|
||||
return merge(tsc, copyAddons, copyStylesheets);
|
||||
});
|
||||
|
||||
/**
|
||||
* Bundle JavaScript files produced by the `tsc` task, into a single file named `xterm.js` with
|
||||
* Browserify.
|
||||
*/
|
||||
gulp.task('browserify', ['tsc'], function() {
|
||||
// Ensure that the build directory exists
|
||||
fs.ensureDirSync(buildDir);
|
||||
|
||||
let browserifyOptions = {
|
||||
basedir: buildDir,
|
||||
debug: true,
|
||||
entries: [`../${outDir}/xterm.js`],
|
||||
standalone: 'Terminal',
|
||||
cache: {},
|
||||
packageCache: {}
|
||||
};
|
||||
let bundleStream = browserify(browserifyOptions)
|
||||
.bundle()
|
||||
.pipe(source('xterm.js'))
|
||||
.pipe(buffer())
|
||||
.pipe(sourcemaps.init({loadMaps: true, sourceRoot: '..'}))
|
||||
.pipe(sourcemaps.write('./'))
|
||||
.pipe(gulp.dest(buildDir));
|
||||
|
||||
// Copy all add-ons from ${outDir}/ to buildDir
|
||||
let copyAddons = gulp.src(`${outDir}/addons/**/*`).pipe(gulp.dest(`${buildDir}/addons`));
|
||||
|
||||
// Copy stylesheets from ${outDir}/ to ${buildDir}/
|
||||
let copyStylesheets = gulp.src(`${outDir}/**/*.css`).pipe(gulp.dest(buildDir));
|
||||
|
||||
return merge(bundleStream, copyAddons, copyStylesheets);
|
||||
});
|
||||
|
||||
gulp.task('instrument-test', function () {
|
||||
return gulp.src([`${outDir}/**/*.js`])
|
||||
// Covering files
|
||||
.pipe(istanbul())
|
||||
// Force `require` to return covered files
|
||||
.pipe(istanbul.hookRequire());
|
||||
});
|
||||
|
||||
gulp.task('mocha', ['instrument-test'], function () {
|
||||
return gulp.src([`${outDir}/*test.js`, `${outDir}/**/*test.js`], {read: false})
|
||||
.pipe(mocha())
|
||||
.pipe(istanbul.writeReports());
|
||||
});
|
||||
|
||||
/**
|
||||
* Use `sorcery` to resolve the source map chain and point back to the TypeScript files.
|
||||
* (Without this task the source maps produced for the JavaScript bundle points into the
|
||||
* compiled JavaScript files in ${outDir}/).
|
||||
*/
|
||||
gulp.task('sorcery', ['browserify'], function () {
|
||||
var chain = sorcery.loadSync(`${buildDir}/xterm.js`);
|
||||
chain.apply();
|
||||
chain.writeSync();
|
||||
});
|
||||
|
||||
/**
|
||||
* Submit coverage results to coveralls.io
|
||||
*/
|
||||
gulp.task('coveralls', function () {
|
||||
gulp.src('coverage/**/lcov.info')
|
||||
.pipe(coveralls());
|
||||
});
|
||||
|
||||
gulp.task('build', ['sorcery']);
|
||||
gulp.task('test', ['mocha']);
|
||||
gulp.task('default', ['build']);
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"source": {
|
||||
"include": [
|
||||
"src/"
|
||||
],
|
||||
"exclude": [
|
||||
"src/test/"
|
||||
],
|
||||
"excludePattern": "src/.+\\.test\\.(js|ts)$"
|
||||
},
|
||||
"opts": {
|
||||
"readme": "README.md",
|
||||
"template": "node_modules/docdash",
|
||||
"encoding": "utf8",
|
||||
"destination": "docs/",
|
||||
"recurse": true,
|
||||
"verbose": true
|
||||
},
|
||||
"plugins": [
|
||||
"plugins/markdown"
|
||||
],
|
||||
"templates": {
|
||||
"cleverLinks": false,
|
||||
"monospaceLinks": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"name": "xterm",
|
||||
"description": "Full xterm terminal, in your browser",
|
||||
"version": "2.5.0",
|
||||
"ignore": [
|
||||
"demo",
|
||||
"test",
|
||||
".gitignore"
|
||||
],
|
||||
"main": "lib/xterm.js",
|
||||
"repository": "https://github.com/sourcelair/xterm.js",
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"*.js",
|
||||
"*.json",
|
||||
"dist/*.css",
|
||||
"dist/**/*.css",
|
||||
"dist/*.js",
|
||||
"dist/*.js.map",
|
||||
"dist/**/*.js",
|
||||
"dist/**/*.js.map",
|
||||
"lib/*.css",
|
||||
"lib/**/*.css",
|
||||
"lib/*.js",
|
||||
"lib/*.js.map",
|
||||
"lib/**/*.js",
|
||||
"lib/**/*.js.map",
|
||||
"src/*.css",
|
||||
"src/**/*.css",
|
||||
"src/*.js",
|
||||
"src/*.js.map",
|
||||
"src/*.ts",
|
||||
"src/**/*.js",
|
||||
"src/**/*.js.map",
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/chai": "^3.4.34",
|
||||
"@types/jsdom": "^2.0.30",
|
||||
"@types/mocha": "^2.2.33",
|
||||
"@types/node": "^6.0.41",
|
||||
"browserify": "^13.1.0",
|
||||
"chai": "3.5.0",
|
||||
"docdash": "0.4.0",
|
||||
"express": "4.13.4",
|
||||
"express-ws": "2.0.0-rc.1",
|
||||
"fs-extra": "^1.0.0",
|
||||
"glob": "^7.0.5",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-cli": "^1.2.2",
|
||||
"gulp-coveralls": "^0.1.4",
|
||||
"gulp-istanbul": "^1.1.1",
|
||||
"gulp-mocha": "^3.0.1",
|
||||
"gulp-sourcemaps": "1.9.1",
|
||||
"gulp-typescript": "^3.1.3",
|
||||
"jsdoc": "3.4.3",
|
||||
"jsdom": "^9.11.0",
|
||||
"merge-stream": "^1.0.1",
|
||||
"node-pty": "^0.4.1",
|
||||
"nodemon": "1.10.2",
|
||||
"sleep": "^3.0.1",
|
||||
"sorcery": "^0.10.0",
|
||||
"tslint": "^4.0.2",
|
||||
"typescript": "~2.2.0",
|
||||
"vinyl-buffer": "^1.0.0",
|
||||
"vinyl-source-stream": "^1.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"prestart": "npm run build",
|
||||
"start": "node demo/app",
|
||||
"dev": "nodemon -e js,ts,css --watch src --watch demo --exec npm start",
|
||||
"lint": "tslint src/*.ts src/**/*.ts",
|
||||
"test": "gulp test",
|
||||
"build:docs": "jsdoc -c jsdoc.json",
|
||||
"build": "gulp build",
|
||||
"prepublish": "npm run build",
|
||||
"coveralls": "gulp coveralls"
|
||||
}
|
||||
}
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
/**
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* The character sets supported by the terminal. These enable several languages
|
||||
* to be represented within the terminal with only 8-bit encoding. See ISO 2022
|
||||
* for a discussion on character sets. Only VT100 character sets are supported.
|
||||
*/
|
||||
export const CHARSETS: {[key: string]: {[key: string]: string}} = {};
|
||||
|
||||
/**
|
||||
* The default character set, US.
|
||||
*/
|
||||
export const DEFAULT_CHARSET = CHARSETS['B'];
|
||||
|
||||
/**
|
||||
* DEC Special Character and Line Drawing Set.
|
||||
* Reference: http://vt100.net/docs/vt102-ug/table5-13.html
|
||||
* A lot of curses apps use this if they see TERM=xterm.
|
||||
* testing: echo -e '\e(0a\e(B'
|
||||
* The xterm output sometimes seems to conflict with the
|
||||
* reference above. xterm seems in line with the reference
|
||||
* when running vttest however.
|
||||
* The table below now uses xterm's output from vttest.
|
||||
*/
|
||||
CHARSETS['0'] = {
|
||||
'`': '\u25c6', // '◆'
|
||||
'a': '\u2592', // '▒'
|
||||
'b': '\u0009', // '\t'
|
||||
'c': '\u000c', // '\f'
|
||||
'd': '\u000d', // '\r'
|
||||
'e': '\u000a', // '\n'
|
||||
'f': '\u00b0', // '°'
|
||||
'g': '\u00b1', // '±'
|
||||
'h': '\u2424', // '\u2424' (NL)
|
||||
'i': '\u000b', // '\v'
|
||||
'j': '\u2518', // '┘'
|
||||
'k': '\u2510', // '┐'
|
||||
'l': '\u250c', // '┌'
|
||||
'm': '\u2514', // '└'
|
||||
'n': '\u253c', // '┼'
|
||||
'o': '\u23ba', // '⎺'
|
||||
'p': '\u23bb', // '⎻'
|
||||
'q': '\u2500', // '─'
|
||||
'r': '\u23bc', // '⎼'
|
||||
's': '\u23bd', // '⎽'
|
||||
't': '\u251c', // '├'
|
||||
'u': '\u2524', // '┤'
|
||||
'v': '\u2534', // '┴'
|
||||
'w': '\u252c', // '┬'
|
||||
'x': '\u2502', // '│'
|
||||
'y': '\u2264', // '≤'
|
||||
'z': '\u2265', // '≥'
|
||||
'{': '\u03c0', // 'π'
|
||||
'|': '\u2260', // '≠'
|
||||
'}': '\u00a3', // '£'
|
||||
'~': '\u00b7' // '·'
|
||||
};
|
||||
|
||||
/**
|
||||
* British character set
|
||||
* ESC (A
|
||||
* Reference: http://vt100.net/docs/vt220-rm/table2-5.html
|
||||
*/
|
||||
CHARSETS['A'] = {
|
||||
'#': '£'
|
||||
};
|
||||
|
||||
/**
|
||||
* United States character set
|
||||
* ESC (B
|
||||
*/
|
||||
CHARSETS['B'] = null;
|
||||
|
||||
/**
|
||||
* Dutch character set
|
||||
* ESC (4
|
||||
* Reference: http://vt100.net/docs/vt220-rm/table2-6.html
|
||||
*/
|
||||
CHARSETS['4'] = {
|
||||
'#': '£',
|
||||
'@': '¾',
|
||||
'[': 'ij',
|
||||
'\\': '½',
|
||||
']': '|',
|
||||
'{': '¨',
|
||||
'|': 'f',
|
||||
'}': '¼',
|
||||
'~': '´'
|
||||
};
|
||||
|
||||
/**
|
||||
* Finnish character set
|
||||
* ESC (C or ESC (5
|
||||
* Reference: http://vt100.net/docs/vt220-rm/table2-7.html
|
||||
*/
|
||||
CHARSETS['C'] =
|
||||
CHARSETS['5'] = {
|
||||
'[': 'Ä',
|
||||
'\\': 'Ö',
|
||||
']': 'Å',
|
||||
'^': 'Ü',
|
||||
'`': 'é',
|
||||
'{': 'ä',
|
||||
'|': 'ö',
|
||||
'}': 'å',
|
||||
'~': 'ü'
|
||||
};
|
||||
|
||||
/**
|
||||
* French character set
|
||||
* ESC (R
|
||||
* Reference: http://vt100.net/docs/vt220-rm/table2-8.html
|
||||
*/
|
||||
CHARSETS['R'] = {
|
||||
'#': '£',
|
||||
'@': 'à',
|
||||
'[': '°',
|
||||
'\\': 'ç',
|
||||
']': '§',
|
||||
'{': 'é',
|
||||
'|': 'ù',
|
||||
'}': 'è',
|
||||
'~': '¨'
|
||||
};
|
||||
|
||||
/**
|
||||
* French Canadian character set
|
||||
* ESC (Q
|
||||
* Reference: http://vt100.net/docs/vt220-rm/table2-9.html
|
||||
*/
|
||||
CHARSETS['Q'] = {
|
||||
'@': 'à',
|
||||
'[': 'â',
|
||||
'\\': 'ç',
|
||||
']': 'ê',
|
||||
'^': 'î',
|
||||
'`': 'ô',
|
||||
'{': 'é',
|
||||
'|': 'ù',
|
||||
'}': 'è',
|
||||
'~': 'û'
|
||||
};
|
||||
|
||||
/**
|
||||
* German character set
|
||||
* ESC (K
|
||||
* Reference: http://vt100.net/docs/vt220-rm/table2-10.html
|
||||
*/
|
||||
CHARSETS['K'] = {
|
||||
'@': '§',
|
||||
'[': 'Ä',
|
||||
'\\': 'Ö',
|
||||
']': 'Ü',
|
||||
'{': 'ä',
|
||||
'|': 'ö',
|
||||
'}': 'ü',
|
||||
'~': 'ß'
|
||||
};
|
||||
|
||||
/**
|
||||
* Italian character set
|
||||
* ESC (Y
|
||||
* Reference: http://vt100.net/docs/vt220-rm/table2-11.html
|
||||
*/
|
||||
CHARSETS['Y'] = {
|
||||
'#': '£',
|
||||
'@': '§',
|
||||
'[': '°',
|
||||
'\\': 'ç',
|
||||
']': 'é',
|
||||
'`': 'ù',
|
||||
'{': 'à',
|
||||
'|': 'ò',
|
||||
'}': 'è',
|
||||
'~': 'ì'
|
||||
};
|
||||
|
||||
/**
|
||||
* Norwegian/Danish character set
|
||||
* ESC (E or ESC (6
|
||||
* Reference: http://vt100.net/docs/vt220-rm/table2-12.html
|
||||
*/
|
||||
CHARSETS['E'] =
|
||||
CHARSETS['6'] = {
|
||||
'@': 'Ä',
|
||||
'[': 'Æ',
|
||||
'\\': 'Ø',
|
||||
']': 'Å',
|
||||
'^': 'Ü',
|
||||
'`': 'ä',
|
||||
'{': 'æ',
|
||||
'|': 'ø',
|
||||
'}': 'å',
|
||||
'~': 'ü'
|
||||
};
|
||||
|
||||
/**
|
||||
* Spanish character set
|
||||
* ESC (Z
|
||||
* Reference: http://vt100.net/docs/vt220-rm/table2-13.html
|
||||
*/
|
||||
CHARSETS['Z'] = {
|
||||
'#': '£',
|
||||
'@': '§',
|
||||
'[': '¡',
|
||||
'\\': 'Ñ',
|
||||
']': '¿',
|
||||
'{': '°',
|
||||
'|': 'ñ',
|
||||
'}': 'ç'
|
||||
};
|
||||
|
||||
/**
|
||||
* Swedish character set
|
||||
* ESC (H or ESC (7
|
||||
* Reference: http://vt100.net/docs/vt220-rm/table2-14.html
|
||||
*/
|
||||
CHARSETS['H'] =
|
||||
CHARSETS['7'] = {
|
||||
'@': 'É',
|
||||
'[': 'Ä',
|
||||
'\\': 'Ö',
|
||||
']': 'Å',
|
||||
'^': 'Ü',
|
||||
'`': 'é',
|
||||
'{': 'ä',
|
||||
'|': 'ö',
|
||||
'}': 'å',
|
||||
'~': 'ü'
|
||||
};
|
||||
|
||||
/**
|
||||
* Swiss character set
|
||||
* ESC (=
|
||||
* Reference: http://vt100.net/docs/vt220-rm/table2-15.html
|
||||
*/
|
||||
CHARSETS['='] = {
|
||||
'#': 'ù',
|
||||
'@': 'à',
|
||||
'[': 'é',
|
||||
'\\': 'ç',
|
||||
']': 'ê',
|
||||
'^': 'î',
|
||||
'_': 'è',
|
||||
'`': 'ô',
|
||||
'{': 'ä',
|
||||
'|': 'ö',
|
||||
'}': 'ü',
|
||||
'~': 'û'
|
||||
};
|
||||
@@ -0,0 +1,236 @@
|
||||
import { assert } from 'chai';
|
||||
import { CompositionHelper } from './CompositionHelper';
|
||||
|
||||
describe('CompositionHelper', () => {
|
||||
let terminal;
|
||||
let compositionHelper;
|
||||
let compositionView;
|
||||
let textarea;
|
||||
let handledText;
|
||||
|
||||
beforeEach(() => {
|
||||
compositionView = {
|
||||
classList: {
|
||||
add: () => {},
|
||||
remove: () => {},
|
||||
},
|
||||
getBoundingClientRect: () => {
|
||||
return { width: 0 };
|
||||
},
|
||||
style: {
|
||||
left: 0,
|
||||
top: 0
|
||||
},
|
||||
textContent: ''
|
||||
};
|
||||
textarea = {
|
||||
value: '',
|
||||
style: {
|
||||
left: 0,
|
||||
top: 0
|
||||
}
|
||||
};
|
||||
terminal = {
|
||||
element: {
|
||||
querySelector: () => {
|
||||
return { offsetLeft: 0, offsetTop: 0 };
|
||||
}
|
||||
},
|
||||
handler: function (text) {
|
||||
handledText += text;
|
||||
}
|
||||
};
|
||||
handledText = '';
|
||||
compositionHelper = new CompositionHelper(textarea, compositionView, terminal);
|
||||
});
|
||||
|
||||
describe('Input', () => {
|
||||
it('Should insert simple characters', function (done) {
|
||||
// First character 'ㅇ'
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: 'ㅇ' });
|
||||
textarea.value = 'ㅇ';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
assert.equal(handledText, 'ㅇ');
|
||||
// Second character 'ㅇ'
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: 'ㅇ' });
|
||||
textarea.value = 'ㅇㅇ';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
assert.equal(handledText, 'ㅇㅇ');
|
||||
done();
|
||||
}, 0);
|
||||
}, 0);
|
||||
}, 0);
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('Should insert complex characters', function (done) {
|
||||
// First character '앙'
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: 'ㅇ' });
|
||||
textarea.value = 'ㅇ';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionupdate({ data: '아' });
|
||||
textarea.value = '아';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionupdate({ data: '앙' });
|
||||
textarea.value = '앙';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
assert.equal(handledText, '앙');
|
||||
// Second character '앙'
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: 'ㅇ' });
|
||||
textarea.value = '앙ㅇ';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionupdate({ data: '아' });
|
||||
textarea.value = '앙아';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionupdate({ data: '앙' });
|
||||
textarea.value = '앙앙';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
assert.equal(handledText, '앙앙');
|
||||
done();
|
||||
}, 0);
|
||||
}, 0);
|
||||
}, 0);
|
||||
}, 0);
|
||||
}, 0);
|
||||
}, 0);
|
||||
}, 0);
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('Should insert complex characters that change with following character', function (done) {
|
||||
// First character '아'
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: 'ㅇ' });
|
||||
textarea.value = 'ㅇ';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionupdate({ data: '아' });
|
||||
textarea.value = '아';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
// Start second character '아' in first character
|
||||
compositionHelper.compositionupdate({ data: '앙' });
|
||||
textarea.value = '앙';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: '아' });
|
||||
textarea.value = '아아';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
assert.equal(handledText, '아아');
|
||||
done();
|
||||
}, 0);
|
||||
}, 0);
|
||||
}, 0);
|
||||
}, 0);
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('Should insert multi-characters compositions', function (done) {
|
||||
// First character 'だ'
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: 'd' });
|
||||
textarea.value = 'd';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionupdate({ data: 'だ' });
|
||||
textarea.value = 'だ';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
// Second character 'あ'
|
||||
compositionHelper.compositionupdate({ data: 'だあ' });
|
||||
textarea.value = 'だあ';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
assert.equal(handledText, 'だあ');
|
||||
done();
|
||||
}, 0);
|
||||
}, 0);
|
||||
}, 0);
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('Should insert multi-character compositions that are converted to other characters with the same length', function (done) {
|
||||
// First character 'だ'
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: 'd' });
|
||||
textarea.value = 'd';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionupdate({ data: 'だ' });
|
||||
textarea.value = 'だ';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
// Second character 'ー'
|
||||
compositionHelper.compositionupdate({ data: 'だー' });
|
||||
textarea.value = 'だー';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
// Convert to katakana 'ダー'
|
||||
compositionHelper.compositionupdate({ data: 'ダー' });
|
||||
textarea.value = 'ダー';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
assert.equal(handledText, 'ダー');
|
||||
done();
|
||||
}, 0);
|
||||
}, 0);
|
||||
}, 0);
|
||||
}, 0);
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('Should insert multi-character compositions that are converted to other characters with different lengths', function (done) {
|
||||
// First character 'い'
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: 'い' });
|
||||
textarea.value = 'い';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
// Second character 'ま'
|
||||
compositionHelper.compositionupdate({ data: 'いm' });
|
||||
textarea.value = 'いm';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionupdate({ data: 'いま' });
|
||||
textarea.value = 'いま';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
// Convert to kanji '今'
|
||||
compositionHelper.compositionupdate({ data: '今' });
|
||||
textarea.value = '今';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
assert.equal(handledText, '今');
|
||||
done();
|
||||
}, 0);
|
||||
}, 0);
|
||||
}, 0);
|
||||
}, 0);
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('Should insert non-composition characters input immediately after composition characters', function (done) {
|
||||
// First character 'ㅇ'
|
||||
compositionHelper.compositionstart();
|
||||
compositionHelper.compositionupdate({ data: 'ㅇ' });
|
||||
textarea.value = 'ㅇ';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
compositionHelper.compositionend();
|
||||
// Second character '1' (a non-composition character)
|
||||
textarea.value = 'ㅇ1';
|
||||
setTimeout(() => { // wait for any textarea updates
|
||||
assert.equal(handledText, 'ㅇ1');
|
||||
done();
|
||||
}, 0);
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,229 @@
|
||||
/**
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ITerminal } from './Interfaces';
|
||||
|
||||
interface IPosition {
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates the logic for handling compositionstart, compositionupdate and compositionend
|
||||
* events, displaying the in-progress composition to the UI and forwarding the final composition
|
||||
* to the handler.
|
||||
*/
|
||||
export class CompositionHelper {
|
||||
/**
|
||||
* Whether input composition is currently happening, eg. via a mobile keyboard, speech input or
|
||||
* IME. This variable determines whether the compositionText should be displayed on the UI.
|
||||
*/
|
||||
private isComposing: boolean;
|
||||
|
||||
/**
|
||||
* The position within the input textarea's value of the current composition.
|
||||
*/
|
||||
private compositionPosition: IPosition;
|
||||
|
||||
/**
|
||||
* Whether a composition is in the process of being sent, setting this to false will cancel any
|
||||
* in-progress composition.
|
||||
*/
|
||||
private isSendingComposition: boolean;
|
||||
|
||||
/**
|
||||
* Creates a new CompositionHelper.
|
||||
* @param textarea The textarea that xterm uses for input.
|
||||
* @param compositionView The element to display the in-progress composition in.
|
||||
* @param terminal The Terminal to forward the finished composition to.
|
||||
*/
|
||||
constructor(
|
||||
private textarea: HTMLTextAreaElement,
|
||||
private compositionView: HTMLElement,
|
||||
private terminal: ITerminal
|
||||
) {
|
||||
this.isComposing = false;
|
||||
this.isSendingComposition = false;
|
||||
this.compositionPosition = { start: null, end: null };
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the compositionstart event, activating the composition view.
|
||||
*/
|
||||
public compositionstart() {
|
||||
this.isComposing = true;
|
||||
this.compositionPosition.start = this.textarea.value.length;
|
||||
this.compositionView.textContent = '';
|
||||
this.compositionView.classList.add('active');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the compositionupdate event, updating the composition view.
|
||||
* @param {CompositionEvent} ev The event.
|
||||
*/
|
||||
public compositionupdate(ev: CompositionEvent) {
|
||||
this.compositionView.textContent = ev.data;
|
||||
this.updateCompositionElements();
|
||||
setTimeout(() => {
|
||||
this.compositionPosition.end = this.textarea.value.length;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the compositionend event, hiding the composition view and sending the composition to
|
||||
* the handler.
|
||||
*/
|
||||
public compositionend() {
|
||||
this.finalizeComposition(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the keydown event, routing any necessary events to the CompositionHelper functions.
|
||||
* @param ev The keydown event.
|
||||
* @return Whether the Terminal should continue processing the keydown event.
|
||||
*/
|
||||
public keydown(ev: KeyboardEvent) {
|
||||
if (this.isComposing || this.isSendingComposition) {
|
||||
if (ev.keyCode === 229) {
|
||||
// Continue composing if the keyCode is the "composition character"
|
||||
return false;
|
||||
} else if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) {
|
||||
// Continue composing if the keyCode is a modifier key
|
||||
return false;
|
||||
} else {
|
||||
// Finish composition immediately. This is mainly here for the case where enter is
|
||||
// pressed and the handler needs to be triggered before the command is executed.
|
||||
this.finalizeComposition(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (ev.keyCode === 229) {
|
||||
// If the "composition character" is used but gets to this point it means a non-composition
|
||||
// character (eg. numbers and punctuation) was pressed when the IME was active.
|
||||
this.handleAnyTextareaChanges();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizes the composition, resuming regular input actions. This is called when a composition
|
||||
* is ending.
|
||||
* @param waitForPropogation Whether to wait for events to propogate before sending
|
||||
* the input. This should be false if a non-composition keystroke is entered before the
|
||||
* compositionend event is triggered, such as enter, so that the composition is send before
|
||||
* the command is executed.
|
||||
*/
|
||||
private finalizeComposition(waitForPropogation: boolean) {
|
||||
this.compositionView.classList.remove('active');
|
||||
this.isComposing = false;
|
||||
this.clearTextareaPosition();
|
||||
|
||||
if (!waitForPropogation) {
|
||||
// Cancel any delayed composition send requests and send the input immediately.
|
||||
this.isSendingComposition = false;
|
||||
const input = this.textarea.value.substring(this.compositionPosition.start, this.compositionPosition.end);
|
||||
this.terminal.handler(input);
|
||||
} else {
|
||||
// Make a deep copy of the composition position here as a new compositionstart event may
|
||||
// fire before the setTimeout executes.
|
||||
const currentCompositionPosition = {
|
||||
start: this.compositionPosition.start,
|
||||
end: this.compositionPosition.end,
|
||||
};
|
||||
|
||||
// Since composition* events happen before the changes take place in the textarea on most
|
||||
// browsers, use a setTimeout with 0ms time to allow the native compositionend event to
|
||||
// complete. This ensures the correct character is retrieved, this solution was used
|
||||
// because:
|
||||
// - The compositionend event's data property is unreliable, at least on Chromium
|
||||
// - The last compositionupdate event's data property does not always accurately describe
|
||||
// the character, a counter example being Korean where an ending consonsant can move to
|
||||
// the following character if the following input is a vowel.
|
||||
this.isSendingComposition = true;
|
||||
setTimeout(() => {
|
||||
// Ensure that the input has not already been sent
|
||||
if (this.isSendingComposition) {
|
||||
this.isSendingComposition = false;
|
||||
let input;
|
||||
if (this.isComposing) {
|
||||
// Use the end position to get the string if a new composition has started.
|
||||
input = this.textarea.value.substring(currentCompositionPosition.start, currentCompositionPosition.end);
|
||||
} else {
|
||||
// Don't use the end position here in order to pick up any characters after the
|
||||
// composition has finished, for example when typing a non-composition character
|
||||
// (eg. 2) after a composition character.
|
||||
input = this.textarea.value.substring(currentCompositionPosition.start);
|
||||
}
|
||||
this.terminal.handler(input);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply any changes made to the textarea after the current event chain is allowed to complete.
|
||||
* This should be called when not currently composing but a keydown event with the "composition
|
||||
* character" (229) is triggered, in order to allow non-composition text to be entered when an
|
||||
* IME is active.
|
||||
*/
|
||||
private handleAnyTextareaChanges() {
|
||||
const oldValue = this.textarea.value;
|
||||
setTimeout(() => {
|
||||
// Ignore if a composition has started since the timeout
|
||||
if (!this.isComposing) {
|
||||
const newValue = this.textarea.value;
|
||||
const diff = newValue.replace(oldValue, '');
|
||||
if (diff.length > 0) {
|
||||
this.terminal.handler(diff);
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Positions the composition view on top of the cursor and the textarea just below it (so the
|
||||
* IME helper dialog is positioned correctly).
|
||||
* @param dontRecurse Whether to use setTimeout to recursively trigger another update, this is
|
||||
* necessary as the IME events across browsers are not consistently triggered.
|
||||
*/
|
||||
public updateCompositionElements(dontRecurse?: boolean) {
|
||||
if (!this.isComposing) {
|
||||
return;
|
||||
}
|
||||
const cursor = <HTMLElement>this.terminal.element.querySelector('.terminal-cursor');
|
||||
if (cursor) {
|
||||
// Take .xterm-rows offsetTop into account as well in case it's positioned absolutely within
|
||||
// the .xterm element.
|
||||
const xtermRows = <HTMLElement>this.terminal.element.querySelector('.xterm-rows');
|
||||
const cursorTop = xtermRows.offsetTop + cursor.offsetTop;
|
||||
|
||||
this.compositionView.style.left = cursor.offsetLeft + 'px';
|
||||
this.compositionView.style.top = cursorTop + 'px';
|
||||
this.compositionView.style.height = cursor.offsetHeight + 'px';
|
||||
this.compositionView.style.lineHeight = cursor.offsetHeight + 'px';
|
||||
// Sync the textarea to the exact position of the composition view so the IME knows where the
|
||||
// text is.
|
||||
const compositionViewBounds = this.compositionView.getBoundingClientRect();
|
||||
this.textarea.style.left = cursor.offsetLeft + 'px';
|
||||
this.textarea.style.top = cursorTop + 'px';
|
||||
this.textarea.style.width = compositionViewBounds.width + 'px';
|
||||
this.textarea.style.height = compositionViewBounds.height + 'px';
|
||||
this.textarea.style.lineHeight = compositionViewBounds.height + 'px';
|
||||
}
|
||||
if (!dontRecurse) {
|
||||
setTimeout(() => this.updateCompositionElements(true), 0);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears the textarea's position so that the cursor does not blink on IE.
|
||||
* @private
|
||||
*/
|
||||
private clearTextareaPosition() {
|
||||
this.textarea.style.left = '';
|
||||
this.textarea.style.top = '';
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* C0 control codes
|
||||
* See = https://en.wikipedia.org/wiki/C0_and_C1_control_codes
|
||||
*/
|
||||
export namespace C0 {
|
||||
/** Null (Caret = ^@, C = \0) */
|
||||
export const NUL = '\x00';
|
||||
/** Start of Heading (Caret = ^A) */
|
||||
export const SOH = '\x01';
|
||||
/** Start of Text (Caret = ^B) */
|
||||
export const STX = '\x02';
|
||||
/** End of Text (Caret = ^C) */
|
||||
export const ETX = '\x03';
|
||||
/** End of Transmission (Caret = ^D) */
|
||||
export const EOT = '\x04';
|
||||
/** Enquiry (Caret = ^E) */
|
||||
export const ENQ = '\x05';
|
||||
/** Acknowledge (Caret = ^F) */
|
||||
export const ACK = '\x06';
|
||||
/** Bell (Caret = ^G, C = \a) */
|
||||
export const BEL = '\x07';
|
||||
/** Backspace (Caret = ^H, C = \b) */
|
||||
export const BS = '\x08';
|
||||
/** Character Tabulation, Horizontal Tabulation (Caret = ^I, C = \t) */
|
||||
export const HT = '\x09';
|
||||
/** Line Feed (Caret = ^J, C = \n) */
|
||||
export const LF = '\x0a';
|
||||
/** Line Tabulation, Vertical Tabulation (Caret = ^K, C = \v) */
|
||||
export const VT = '\x0b';
|
||||
/** Form Feed (Caret = ^L, C = \f) */
|
||||
export const FF = '\x0c';
|
||||
/** Carriage Return (Caret = ^M, C = \r) */
|
||||
export const CR = '\x0d';
|
||||
/** Shift Out (Caret = ^N) */
|
||||
export const SO = '\x0e';
|
||||
/** Shift In (Caret = ^O) */
|
||||
export const SI = '\x0f';
|
||||
/** Data Link Escape (Caret = ^P) */
|
||||
export const DLE = '\x10';
|
||||
/** Device Control One (XON) (Caret = ^Q) */
|
||||
export const DC1 = '\x11';
|
||||
/** Device Control Two (Caret = ^R) */
|
||||
export const DC2 = '\x12';
|
||||
/** Device Control Three (XOFF) (Caret = ^S) */
|
||||
export const DC3 = '\x13';
|
||||
/** Device Control Four (Caret = ^T) */
|
||||
export const DC4 = '\x14';
|
||||
/** Negative Acknowledge (Caret = ^U) */
|
||||
export const NAK = '\x15';
|
||||
/** Synchronous Idle (Caret = ^V) */
|
||||
export const SYN = '\x16';
|
||||
/** End of Transmission Block (Caret = ^W) */
|
||||
export const ETB = '\x17';
|
||||
/** Cancel (Caret = ^X) */
|
||||
export const CAN = '\x18';
|
||||
/** End of Medium (Caret = ^Y) */
|
||||
export const EM = '\x19';
|
||||
/** Substitute (Caret = ^Z) */
|
||||
export const SUB = '\x1a';
|
||||
/** Escape (Caret = ^[, C = \e) */
|
||||
export const ESC = '\x1b';
|
||||
/** File Separator (Caret = ^\) */
|
||||
export const FS = '\x1c';
|
||||
/** Group Separator (Caret = ^]) */
|
||||
export const GS = '\x1d';
|
||||
/** Record Separator (Caret = ^^) */
|
||||
export const RS = '\x1e';
|
||||
/** Unit Separator (Caret = ^_) */
|
||||
export const US = '\x1f';
|
||||
/** Space */
|
||||
export const SP = '\x20';
|
||||
/** Delete (Caret = ^?) */
|
||||
export const DEL = '\x7f';
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
import { assert } from 'chai';
|
||||
import { EventEmitter } from './EventEmitter';
|
||||
|
||||
describe('EventEmitter', () => {
|
||||
let eventEmitter: EventEmitter;
|
||||
|
||||
beforeEach(() => {
|
||||
eventEmitter = new EventEmitter();
|
||||
});
|
||||
|
||||
describe('once', () => {
|
||||
it('should trigger the listener only once', () => {
|
||||
let count = 0;
|
||||
const listener = () => count++;
|
||||
eventEmitter.once('test', listener);
|
||||
eventEmitter.emit('test');
|
||||
assert.equal(count, 1);
|
||||
eventEmitter.emit('test');
|
||||
assert.equal(count, 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('emit', () => {
|
||||
it('should emit events to listeners', () => {
|
||||
let count1 = 0;
|
||||
let count2 = 0;
|
||||
const listener1 = () => count1++;
|
||||
const listener2 = () => count2++;
|
||||
eventEmitter.on('test', listener1);
|
||||
eventEmitter.on('test', listener2);
|
||||
eventEmitter.emit('test');
|
||||
assert.equal(count1, 1);
|
||||
assert.equal(count2, 1);
|
||||
eventEmitter.emit('test');
|
||||
assert.equal(count1, 2);
|
||||
assert.equal(count2, 2);
|
||||
});
|
||||
|
||||
it('should manage multiple listener types', () => {
|
||||
let count1 = 0;
|
||||
let count2 = 0;
|
||||
const listener1 = () => count1++;
|
||||
const listener2 = () => count2++;
|
||||
eventEmitter.on('test', listener1);
|
||||
eventEmitter.on('foo', listener2);
|
||||
eventEmitter.emit('test');
|
||||
assert.equal(count1, 1);
|
||||
assert.equal(count2, 0);
|
||||
eventEmitter.emit('foo');
|
||||
assert.equal(count1, 1);
|
||||
assert.equal(count2, 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('listeners', () => {
|
||||
it('should return listeners for the type requested', () => {
|
||||
assert.equal(eventEmitter.listeners('test').length, 0);
|
||||
const listener = () => {};
|
||||
eventEmitter.on('test', listener);
|
||||
assert.deepEqual(eventEmitter.listeners('test'), [listener]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('off', () => {
|
||||
it('should remove the specific listener', () => {
|
||||
const listener1 = () => {};
|
||||
const listener2 = () => {};
|
||||
eventEmitter.on('foo', listener1);
|
||||
eventEmitter.on('foo', listener2);
|
||||
assert.equal(eventEmitter.listeners('foo').length, 2);
|
||||
eventEmitter.off('foo', listener1);
|
||||
assert.deepEqual(eventEmitter.listeners('foo'), [listener2]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeAllListeners', () => {
|
||||
it('should clear all listeners', () => {
|
||||
eventEmitter.on('foo', () => {});
|
||||
assert.equal(eventEmitter.listeners('foo').length, 1);
|
||||
eventEmitter.removeAllListeners('foo');
|
||||
assert.equal(eventEmitter.listeners('foo').length, 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
interface ListenerType {
|
||||
(): void;
|
||||
listener?: () => void;
|
||||
};
|
||||
|
||||
export class EventEmitter {
|
||||
private _events: {[type: string]: ListenerType[]};
|
||||
|
||||
constructor() {
|
||||
// Restore the previous events if available, this will happen if the
|
||||
// constructor is called multiple times on the same object (terminal reset).
|
||||
this._events = this._events || {};
|
||||
}
|
||||
|
||||
public on(type, listener): void {
|
||||
this._events[type] = this._events[type] || [];
|
||||
this._events[type].push(listener);
|
||||
}
|
||||
|
||||
public off(type, listener): void {
|
||||
if (!this._events[type]) {
|
||||
return;
|
||||
}
|
||||
|
||||
let obj = this._events[type];
|
||||
let i = obj.length;
|
||||
|
||||
while (i--) {
|
||||
if (obj[i] === listener || obj[i].listener === listener) {
|
||||
obj.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public removeAllListeners(type): void {
|
||||
if (this._events[type]) {
|
||||
delete this._events[type];
|
||||
}
|
||||
}
|
||||
|
||||
public once(type, listener): any {
|
||||
function on() {
|
||||
let args = Array.prototype.slice.call(arguments);
|
||||
this.off(type, on);
|
||||
return listener.apply(this, args);
|
||||
}
|
||||
(<any>on).listener = listener;
|
||||
return this.on(type, on);
|
||||
}
|
||||
|
||||
public emit(type): void {
|
||||
if (!this._events[type]) {
|
||||
return;
|
||||
}
|
||||
|
||||
let args = Array.prototype.slice.call(arguments, 1);
|
||||
let obj = this._events[type];
|
||||
|
||||
for (let i = 0; i < obj.length; i++) {
|
||||
obj[i].apply(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
public listeners(type): ListenerType[] {
|
||||
return this._events[type] || [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { assert } from 'chai';
|
||||
import { InputHandler } from './InputHandler';
|
||||
|
||||
describe('InputHandler', () => {
|
||||
describe('setCursorStyle', () => {
|
||||
it('should call Terminal.setOption with correct params', () => {
|
||||
let options = {};
|
||||
let terminal = {
|
||||
setOption: (option, value) => options[option] = value
|
||||
};
|
||||
let inputHandler = new InputHandler(terminal);
|
||||
|
||||
inputHandler.setCursorStyle([0]);
|
||||
assert.equal(options['cursorStyle'], 'block');
|
||||
assert.equal(options['cursorBlink'], true);
|
||||
|
||||
options = {};
|
||||
inputHandler.setCursorStyle([1]);
|
||||
assert.equal(options['cursorStyle'], 'block');
|
||||
assert.equal(options['cursorBlink'], true);
|
||||
|
||||
options = {};
|
||||
inputHandler.setCursorStyle([2]);
|
||||
assert.equal(options['cursorStyle'], 'block');
|
||||
assert.equal(options['cursorBlink'], false);
|
||||
|
||||
options = {};
|
||||
inputHandler.setCursorStyle([3]);
|
||||
assert.equal(options['cursorStyle'], 'underline');
|
||||
assert.equal(options['cursorBlink'], true);
|
||||
|
||||
options = {};
|
||||
inputHandler.setCursorStyle([4]);
|
||||
assert.equal(options['cursorStyle'], 'underline');
|
||||
assert.equal(options['cursorBlink'], false);
|
||||
|
||||
options = {};
|
||||
inputHandler.setCursorStyle([5]);
|
||||
assert.equal(options['cursorStyle'], 'bar');
|
||||
assert.equal(options['cursorBlink'], true);
|
||||
|
||||
options = {};
|
||||
inputHandler.setCursorStyle([6]);
|
||||
assert.equal(options['cursorStyle'], 'bar');
|
||||
assert.equal(options['cursorBlink'], false);
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
+1584
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { LinkMatcherOptions } from './Interfaces';
|
||||
import { LinkMatcherHandler, LinkMatcherValidationCallback } from './Types';
|
||||
|
||||
export interface IBrowser {
|
||||
isNode: boolean;
|
||||
userAgent: string;
|
||||
platform: string;
|
||||
isFirefox: boolean;
|
||||
isMSIE: boolean;
|
||||
isMac: boolean;
|
||||
isIpad: boolean;
|
||||
isIphone: boolean;
|
||||
isMSWindows: boolean;
|
||||
}
|
||||
|
||||
export interface ITerminal {
|
||||
element: HTMLElement;
|
||||
rowContainer: HTMLElement;
|
||||
textarea: HTMLTextAreaElement;
|
||||
ybase: number;
|
||||
ydisp: number;
|
||||
lines: ICircularList<string>;
|
||||
rows: number;
|
||||
cols: number;
|
||||
browser: IBrowser;
|
||||
writeBuffer: string[];
|
||||
children: HTMLElement[];
|
||||
cursorHidden: boolean;
|
||||
cursorState: number;
|
||||
x: number;
|
||||
y: number;
|
||||
defAttr: number;
|
||||
|
||||
/**
|
||||
* Emit the 'data' event and populate the given data.
|
||||
* @param data The data to populate in the event.
|
||||
*/
|
||||
handler(data: string);
|
||||
on(event: string, callback: () => void);
|
||||
scrollDisp(disp: number, suppressScrollEvent: boolean);
|
||||
cancel(ev: Event, force?: boolean);
|
||||
log(text: string): void;
|
||||
emit(event: string, data: any);
|
||||
}
|
||||
|
||||
export interface ICharMeasure {
|
||||
width: number;
|
||||
height: number;
|
||||
measure(): void;
|
||||
}
|
||||
|
||||
export interface ILinkifier {
|
||||
linkifyRow(rowIndex: number): void;
|
||||
attachHypertextLinkHandler(handler: LinkMatcherHandler): void;
|
||||
registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: LinkMatcherOptions): number;
|
||||
deregisterLinkMatcher(matcherId: number): boolean;
|
||||
}
|
||||
|
||||
interface ICircularList<T> {
|
||||
length: number;
|
||||
maxLength: number;
|
||||
|
||||
forEach(callbackfn: (value: T, index: number, array: T[]) => void): void;
|
||||
get(index: number): T;
|
||||
set(index: number, value: T): void;
|
||||
push(value: T): void;
|
||||
pop(): T;
|
||||
splice(start: number, deleteCount: number, ...items: T[]): void;
|
||||
trimStart(count: number): void;
|
||||
shiftElements(start: number, count: number, offset: number): void;
|
||||
}
|
||||
|
||||
export interface LinkMatcherOptions {
|
||||
/**
|
||||
* The index of the link from the regex.match(text) call. This defaults to 0
|
||||
* (for regular expressions without capture groups).
|
||||
*/
|
||||
matchIndex?: number;
|
||||
/**
|
||||
* A callback that validates an individual link, returning true if valid and
|
||||
* false if invalid.
|
||||
*/
|
||||
validationCallback?: LinkMatcherValidationCallback;
|
||||
/**
|
||||
* The priority of the link matcher, this defines the order in which the link
|
||||
* matcher is evaluated relative to others, from highest to lowest. The
|
||||
* default value is 0.
|
||||
*/
|
||||
priority?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles actions generated by the parser.
|
||||
*/
|
||||
export interface IInputHandler {
|
||||
addChar(char: string, code: 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?: number[]): void;
|
||||
/** CSI A */ cursorUp(params?: number[]): void;
|
||||
/** CSI B */ cursorDown(params?: number[]): void;
|
||||
/** CSI C */ cursorForward(params?: number[]): void;
|
||||
/** CSI D */ cursorBackward(params?: number[]): void;
|
||||
/** CSI E */ cursorNextLine(params?: number[]): void;
|
||||
/** CSI F */ cursorPrecedingLine(params?: number[]): void;
|
||||
/** CSI G */ cursorCharAbsolute(params?: number[]): void;
|
||||
/** CSI H */ cursorPosition(params?: number[]): void;
|
||||
/** CSI I */ cursorForwardTab(params?: number[]): void;
|
||||
/** CSI J */ eraseInDisplay(params?: number[]): void;
|
||||
/** CSI K */ eraseInLine(params?: number[]): void;
|
||||
/** CSI L */ insertLines(params?: number[]): void;
|
||||
/** CSI M */ deleteLines(params?: number[]): void;
|
||||
/** CSI P */ deleteChars(params?: number[]): void;
|
||||
/** CSI S */ scrollUp(params?: number[]): void;
|
||||
/** CSI T */ scrollDown(params?: number[]): void;
|
||||
/** CSI X */ eraseChars(params?: number[]): void;
|
||||
/** CSI Z */ cursorBackwardTab(params?: number[]): void;
|
||||
/** CSI ` */ charPosAbsolute(params?: number[]): void;
|
||||
/** CSI a */ HPositionRelative(params?: number[]): void;
|
||||
/** CSI b */ repeatPrecedingCharacter(params?: number[]): void;
|
||||
/** CSI c */ sendDeviceAttributes(params?: number[]): void;
|
||||
/** CSI d */ linePosAbsolute(params?: number[]): void;
|
||||
/** CSI e */ VPositionRelative(params?: number[]): void;
|
||||
/** CSI f */ HVPosition(params?: number[]): void;
|
||||
/** CSI g */ tabClear(params?: number[]): void;
|
||||
/** CSI h */ setMode(params?: number[]): void;
|
||||
/** CSI l */ resetMode(params?: number[]): void;
|
||||
/** CSI m */ charAttributes(params?: number[]): void;
|
||||
/** CSI n */ deviceStatus(params?: number[]): void;
|
||||
/** CSI p */ softReset(params?: number[]): void;
|
||||
/** CSI q */ setCursorStyle(params?: number[]): void;
|
||||
/** CSI r */ setScrollRegion(params?: number[]): void;
|
||||
/** CSI s */ saveCursor(params?: number[]): void;
|
||||
/** CSI u */ restoreCursor(params?: number[]): void;
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/**
|
||||
* @license MIT
|
||||
*/
|
||||
import jsdom = require('jsdom');
|
||||
import { assert } from 'chai';
|
||||
import { ITerminal, ILinkifier } from './Interfaces';
|
||||
import { Linkifier } from './Linkifier';
|
||||
import { LinkMatcher } from './Types';
|
||||
|
||||
class TestLinkifier extends Linkifier {
|
||||
constructor() {
|
||||
Linkifier.TIME_BEFORE_LINKIFY = 0;
|
||||
super();
|
||||
}
|
||||
|
||||
public get linkMatchers(): LinkMatcher[] { return this._linkMatchers; }
|
||||
}
|
||||
|
||||
describe('Linkifier', () => {
|
||||
let window: Window;
|
||||
let document: Document;
|
||||
|
||||
let container: HTMLElement;
|
||||
let rows: HTMLElement[];
|
||||
let linkifier: TestLinkifier;
|
||||
|
||||
beforeEach(done => {
|
||||
jsdom.env('', (err, w) => {
|
||||
window = w;
|
||||
document = window.document;
|
||||
linkifier = new TestLinkifier();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
function addRow(html: string) {
|
||||
const element = document.createElement('div');
|
||||
element.innerHTML = html;
|
||||
container.appendChild(element);
|
||||
rows.push(element);
|
||||
}
|
||||
|
||||
describe('before attachToDom', () => {
|
||||
it('should allow link matcher registration', done => {
|
||||
assert.doesNotThrow(() => {
|
||||
const linkMatcherId = linkifier.registerLinkMatcher(/foo/, () => {});
|
||||
assert.isTrue(linkifier.deregisterLinkMatcher(linkMatcherId));
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('after attachToDom', () => {
|
||||
beforeEach(() => {
|
||||
rows = [];
|
||||
linkifier.attachToDom(document, rows);
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
});
|
||||
|
||||
function clickElement(element: Node) {
|
||||
const event = document.createEvent('MouseEvent');
|
||||
event.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
|
||||
element.dispatchEvent(event);
|
||||
}
|
||||
|
||||
function assertLinkifiesEntireRow(uri: string, done: MochaDone) {
|
||||
addRow(uri);
|
||||
linkifier.linkifyRow(0);
|
||||
setTimeout(() => {
|
||||
assert.equal((<HTMLElement>rows[0].firstChild).tagName, 'A');
|
||||
assert.equal((<HTMLElement>rows[0].firstChild).textContent, uri);
|
||||
done();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
describe('http links', () => {
|
||||
function assertLinkifiesEntireRow(uri: string, done: MochaDone) {
|
||||
addRow(uri);
|
||||
linkifier.linkifyRow(0);
|
||||
setTimeout(() => {
|
||||
assert.equal((<HTMLElement>rows[0].firstChild).tagName, 'A');
|
||||
assert.equal((<HTMLElement>rows[0].firstChild).textContent, uri);
|
||||
done();
|
||||
}, 0);
|
||||
}
|
||||
it('should allow ~ character in URI path', done => assertLinkifiesEntireRow('http://foo.com/a~b#c~d?e~f', done));
|
||||
});
|
||||
|
||||
describe('link matcher', () => {
|
||||
function assertLinkifiesRow(rowText: string, linkMatcherRegex: RegExp, expectedHtml: string, done: MochaDone) {
|
||||
addRow(rowText);
|
||||
linkifier.registerLinkMatcher(linkMatcherRegex, () => {});
|
||||
linkifier.linkifyRow(0);
|
||||
// Allow linkify to happen
|
||||
setTimeout(() => {
|
||||
assert.equal(rows[0].innerHTML, expectedHtml);
|
||||
done();
|
||||
}, 0);
|
||||
}
|
||||
it('should match a single link', done => {
|
||||
assertLinkifiesRow('foo', /foo/, '<a>foo</a>', done);
|
||||
});
|
||||
it('should match a single link at the start of a text node', done => {
|
||||
assertLinkifiesRow('foo bar', /foo/, '<a>foo</a> bar', done);
|
||||
});
|
||||
it('should match a single link in the middle of a text node', done => {
|
||||
assertLinkifiesRow('foo bar baz', /bar/, 'foo <a>bar</a> baz', done);
|
||||
});
|
||||
it('should match a single link at the end of a text node', done => {
|
||||
assertLinkifiesRow('foo bar', /bar/, 'foo <a>bar</a>', done);
|
||||
});
|
||||
it('should match a link after a link at the start of a text node', done => {
|
||||
assertLinkifiesRow('foo bar', /foo|bar/, '<a>foo</a> <a>bar</a>', done);
|
||||
});
|
||||
it('should match a link after a link in the middle of a text node', done => {
|
||||
assertLinkifiesRow('foo bar baz', /bar|baz/, 'foo <a>bar</a> <a>baz</a>', done);
|
||||
});
|
||||
it('should match a link immediately after a link at the end of a text node', done => {
|
||||
assertLinkifiesRow('<span>foo bar</span>baz', /bar|baz/, '<span>foo <a>bar</a></span><a>baz</a>', done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validationCallback', () => {
|
||||
it('should enable link if true', done => {
|
||||
addRow('test');
|
||||
linkifier.registerLinkMatcher(/test/, () => done(), {
|
||||
validationCallback: (url, element, cb) => {
|
||||
cb(true);
|
||||
assert.equal((<HTMLElement>rows[0].firstChild).tagName, 'A');
|
||||
setTimeout(() => clickElement(rows[0].firstChild), 0);
|
||||
}
|
||||
});
|
||||
linkifier.linkifyRow(0);
|
||||
});
|
||||
|
||||
it('should disable link if false', done => {
|
||||
addRow('test');
|
||||
linkifier.registerLinkMatcher(/test/, () => assert.fail(), {
|
||||
validationCallback: (url, element, cb) => {
|
||||
cb(false);
|
||||
assert.equal((<HTMLElement>rows[0].firstChild).tagName, 'A');
|
||||
setTimeout(() => clickElement(rows[0].firstChild), 0);
|
||||
}
|
||||
});
|
||||
linkifier.linkifyRow(0);
|
||||
// Allow time for the click to be performed
|
||||
setTimeout(() => done(), 10);
|
||||
});
|
||||
|
||||
it('should trigger for multiple link matches on one row', done => {
|
||||
addRow('test test');
|
||||
let count = 0;
|
||||
linkifier.registerLinkMatcher(/test/, () => assert.fail(), {
|
||||
validationCallback: (url, element, cb) => {
|
||||
count += 1;
|
||||
if (count === 2) {
|
||||
done();
|
||||
}
|
||||
cb(false);
|
||||
}
|
||||
});
|
||||
linkifier.linkifyRow(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('priority', () => {
|
||||
it('should order the list from highest priority to lowest #1', () => {
|
||||
const aId = linkifier.registerLinkMatcher(/a/, () => {}, { priority: 1 });
|
||||
const bId = linkifier.registerLinkMatcher(/b/, () => {}, { priority: -1 });
|
||||
assert.deepEqual(linkifier.linkMatchers.map(lm => lm.id), [aId, 0, bId]);
|
||||
});
|
||||
|
||||
it('should order the list from highest priority to lowest #2', () => {
|
||||
const aId = linkifier.registerLinkMatcher(/a/, () => {}, { priority: -1 });
|
||||
const bId = linkifier.registerLinkMatcher(/b/, () => {}, { priority: 1 });
|
||||
assert.deepEqual(linkifier.linkMatchers.map(lm => lm.id), [bId, 0, aId]);
|
||||
});
|
||||
|
||||
it('should order items of equal priority in the order they are added', () => {
|
||||
const aId = linkifier.registerLinkMatcher(/a/, () => {}, { priority: 0 });
|
||||
const bId = linkifier.registerLinkMatcher(/b/, () => {}, { priority: 0 });
|
||||
assert.deepEqual(linkifier.linkMatchers.map(lm => lm.id), [0, aId, bId]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,349 @@
|
||||
/**
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { LinkMatcherOptions } from './Interfaces';
|
||||
import { LinkMatcher, LinkMatcherHandler, LinkMatcherValidationCallback } from './Types';
|
||||
|
||||
const INVALID_LINK_CLASS = 'xterm-invalid-link';
|
||||
|
||||
const protocolClause = '(https?:\\/\\/)';
|
||||
const domainCharacterSet = '[\\da-z\\.-]+';
|
||||
const negatedDomainCharacterSet = '[^\\da-z\\.-]+';
|
||||
const domainBodyClause = '(' + domainCharacterSet + ')';
|
||||
const tldClause = '([a-z\\.]{2,6})';
|
||||
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 pathClause = '(\\/[\\/\\w\\.\\-%~]*)*';
|
||||
const queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*';
|
||||
const queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?';
|
||||
const hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';
|
||||
const negatedPathCharacterSet = '[^\\/\\w\\.\\-%]+';
|
||||
const bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause;
|
||||
const start = '(?:^|' + negatedDomainCharacterSet + ')(';
|
||||
const end = ')($|' + negatedPathCharacterSet + ')';
|
||||
const strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);
|
||||
|
||||
/**
|
||||
* The ID of the built in http(s) link matcher.
|
||||
*/
|
||||
const HYPERTEXT_LINK_MATCHER_ID = 0;
|
||||
|
||||
/**
|
||||
* The Linkifier applies links to rows shortly after they have been refreshed.
|
||||
*/
|
||||
export class Linkifier {
|
||||
/**
|
||||
* The time to wait after a row is changed before it is linkified. This prevents
|
||||
* the costly operation of searching every row multiple times, potentially a
|
||||
* huge amount of times.
|
||||
*/
|
||||
protected static TIME_BEFORE_LINKIFY = 200;
|
||||
|
||||
protected _linkMatchers: LinkMatcher[];
|
||||
|
||||
private _document: Document;
|
||||
private _rows: HTMLElement[];
|
||||
private _rowTimeoutIds: number[];
|
||||
private _nextLinkMatcherId = HYPERTEXT_LINK_MATCHER_ID;
|
||||
|
||||
constructor() {
|
||||
this._rowTimeoutIds = [];
|
||||
this._linkMatchers = [];
|
||||
this.registerLinkMatcher(strictUrlRegex, null, { matchIndex: 1 });
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches the linkifier to the DOM, enabling linkification.
|
||||
* @param document The document object.
|
||||
* @param rows The array of rows to apply links to.
|
||||
*/
|
||||
public attachToDom(document: Document, rows: HTMLElement[]) {
|
||||
this._document = document;
|
||||
this._rows = rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a row for linkification.
|
||||
* @param {number} rowIndex The index of the row to linkify.
|
||||
*/
|
||||
public linkifyRow(rowIndex: number): void {
|
||||
// Don't attempt linkify if not yet attached to DOM
|
||||
if (!this._document) {
|
||||
return;
|
||||
}
|
||||
|
||||
const timeoutId = this._rowTimeoutIds[rowIndex];
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
this._rowTimeoutIds[rowIndex] = setTimeout(this._linkifyRow.bind(this, rowIndex), Linkifier.TIME_BEFORE_LINKIFY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches a handler for hypertext links, overriding default <a> behavior
|
||||
* for standard http(s) links.
|
||||
* @param {LinkHandler} handler The handler to use, this can be cleared with
|
||||
* null.
|
||||
*/
|
||||
public setHypertextLinkHandler(handler: LinkMatcherHandler): void {
|
||||
this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].handler = handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches a validation callback for hypertext links.
|
||||
* @param {LinkMatcherValidationCallback} callback The callback to use, this
|
||||
* can be cleared with null.
|
||||
*/
|
||||
public setHypertextValidationCallback(callback: LinkMatcherValidationCallback): void {
|
||||
this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].validationCallback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a link matcher, allowing custom link patterns to be matched and
|
||||
* handled.
|
||||
* @param {RegExp} regex The regular expression to search for, specifically
|
||||
* this searches the textContent of the rows. You will want to use \s to match
|
||||
* a space ' ' character for example.
|
||||
* @param {LinkHandler} handler The callback when the link is called.
|
||||
* @param {LinkMatcherOptions} [options] Options for the link matcher.
|
||||
* @return {number} The ID of the new matcher, this can be used to deregister.
|
||||
*/
|
||||
public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options: LinkMatcherOptions = {}): number {
|
||||
if (this._nextLinkMatcherId !== HYPERTEXT_LINK_MATCHER_ID && !handler) {
|
||||
throw new Error('handler must be defined');
|
||||
}
|
||||
const matcher: LinkMatcher = {
|
||||
id: this._nextLinkMatcherId++,
|
||||
regex,
|
||||
handler,
|
||||
matchIndex: options.matchIndex,
|
||||
validationCallback: options.validationCallback,
|
||||
priority: options.priority || 0
|
||||
};
|
||||
this._addLinkMatcherToList(matcher);
|
||||
return matcher.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a link matcher to the list in the correct position based on the
|
||||
* priority of each link matcher. New link matchers of equal priority are
|
||||
* considered after older link matchers.
|
||||
* @param matcher The link matcher to be added.
|
||||
*/
|
||||
private _addLinkMatcherToList(matcher: LinkMatcher): void {
|
||||
if (this._linkMatchers.length === 0) {
|
||||
this._linkMatchers.push(matcher);
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = this._linkMatchers.length - 1; i >= 0; i--) {
|
||||
if (matcher.priority <= this._linkMatchers[i].priority) {
|
||||
this._linkMatchers.splice(i + 1, 0, matcher);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this._linkMatchers.splice(0, 0, matcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deregisters a link matcher if it has been registered.
|
||||
* @param {number} matcherId The link matcher's ID (returned after register)
|
||||
* @return {boolean} Whether a link matcher was found and deregistered.
|
||||
*/
|
||||
public deregisterLinkMatcher(matcherId: number): boolean {
|
||||
// ID 0 is the hypertext link matcher which cannot be deregistered
|
||||
for (let i = 1; i < this._linkMatchers.length; i++) {
|
||||
if (this._linkMatchers[i].id === matcherId) {
|
||||
this._linkMatchers.splice(i, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Linkifies a row.
|
||||
* @param {number} rowIndex The index of the row to linkify.
|
||||
*/
|
||||
private _linkifyRow(rowIndex: number): void {
|
||||
const row = this._rows[rowIndex];
|
||||
if (!row) {
|
||||
return;
|
||||
}
|
||||
const text = row.textContent;
|
||||
for (let i = 0; i < this._linkMatchers.length; i++) {
|
||||
const matcher = this._linkMatchers[i];
|
||||
const linkElements = this._doLinkifyRow(row, matcher);
|
||||
if (linkElements.length > 0) {
|
||||
// Fire validation callback
|
||||
if (matcher.validationCallback) {
|
||||
for (let j = 0; j < linkElements.length; j++) {
|
||||
const element = linkElements[j];
|
||||
matcher.validationCallback(element.textContent, element, isValid => {
|
||||
if (!isValid) {
|
||||
element.classList.add(INVALID_LINK_CLASS);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// Only allow a single LinkMatcher to trigger on any given row.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Linkifies a row given a specific handler.
|
||||
* @param {HTMLElement} row The row to linkify.
|
||||
* @param {LinkMatcher} matcher The link matcher for this line.
|
||||
* @return The link element if it was added, otherwise undefined.
|
||||
*/
|
||||
private _doLinkifyRow(row: HTMLElement, matcher: LinkMatcher): HTMLElement[] {
|
||||
// Iterate over nodes as we want to consider text nodes
|
||||
let result = [];
|
||||
const isHttpLinkMatcher = matcher.id === HYPERTEXT_LINK_MATCHER_ID;
|
||||
const nodes = row.childNodes;
|
||||
|
||||
// Find the first match
|
||||
let match = row.textContent.match(matcher.regex);
|
||||
if (!match || match.length === 0) {
|
||||
return result;
|
||||
}
|
||||
let uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex];
|
||||
// Set the next searches start index
|
||||
let rowStartIndex = match.index + uri.length;
|
||||
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const node = nodes[i];
|
||||
const searchIndex = node.textContent.indexOf(uri);
|
||||
if (searchIndex >= 0) {
|
||||
const linkElement = this._createAnchorElement(uri, matcher.handler, isHttpLinkMatcher);
|
||||
if (node.textContent.length === uri.length) {
|
||||
// Matches entire string
|
||||
if (node.nodeType === 3 /*Node.TEXT_NODE*/) {
|
||||
this._replaceNode(node, linkElement);
|
||||
} else {
|
||||
const element = (<HTMLElement>node);
|
||||
if (element.nodeName === 'A') {
|
||||
// This row has already been linkified
|
||||
return result;
|
||||
}
|
||||
element.innerHTML = '';
|
||||
element.appendChild(linkElement);
|
||||
}
|
||||
} else {
|
||||
// Matches part of string
|
||||
const nodesAdded = this._replaceNodeSubstringWithNode(node, linkElement, uri, searchIndex);
|
||||
// No need to consider the new nodes
|
||||
i += nodesAdded;
|
||||
}
|
||||
result.push(linkElement);
|
||||
|
||||
// Find the next match
|
||||
match = row.textContent.substring(rowStartIndex).match(matcher.regex);
|
||||
if (!match || match.length === 0) {
|
||||
return result;
|
||||
}
|
||||
uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex];
|
||||
rowStartIndex += match.index + uri.length;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a link anchor element.
|
||||
* @param {string} uri The uri of the link.
|
||||
* @return {HTMLAnchorElement} The link.
|
||||
*/
|
||||
private _createAnchorElement(uri: string, handler: LinkMatcherHandler, isHypertextLinkHandler: boolean): HTMLAnchorElement {
|
||||
const element = this._document.createElement('a');
|
||||
element.textContent = uri;
|
||||
element.draggable = false;
|
||||
if (isHypertextLinkHandler) {
|
||||
element.href = uri;
|
||||
// Force link on another tab so work is not lost
|
||||
element.target = '_blank';
|
||||
element.addEventListener('click', (event: MouseEvent) => {
|
||||
if (handler) {
|
||||
return handler(event, uri);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
element.addEventListener('click', (event: MouseEvent) => {
|
||||
// Don't execute the handler if the link is flagged as invalid
|
||||
if (element.classList.contains(INVALID_LINK_CLASS)) {
|
||||
return;
|
||||
}
|
||||
return handler(event, uri);
|
||||
});
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace a node with 1 or more other nodes.
|
||||
* @param {Node} oldNode The node to replace.
|
||||
* @param {Node[]} newNodes The new nodes to insert in order.
|
||||
*/
|
||||
private _replaceNode(oldNode: Node, ...newNodes: Node[]): void {
|
||||
const parent = oldNode.parentNode;
|
||||
for (let i = 0; i < newNodes.length; i++) {
|
||||
parent.insertBefore(newNodes[i], oldNode);
|
||||
}
|
||||
parent.removeChild(oldNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace a substring within a node with a new node.
|
||||
* @param {Node} targetNode The target node; either a text node or a <span>
|
||||
* containing a single text node.
|
||||
* @param {Node} newNode The new node to insert.
|
||||
* @param {string} substring The substring to replace.
|
||||
* @param {number} substringIndex The index of the substring within the string.
|
||||
* @return The number of nodes to skip when searching for the next uri.
|
||||
*/
|
||||
private _replaceNodeSubstringWithNode(targetNode: Node, newNode: Node, substring: string, substringIndex: number): number {
|
||||
let node = targetNode;
|
||||
if (node.nodeType !== 3/*Node.TEXT_NODE*/) {
|
||||
node = node.childNodes[0];
|
||||
}
|
||||
|
||||
// The targetNode will be either a text node or a <span>. The text node
|
||||
// (targetNode or its only-child) needs to be replaced with newNode plus new
|
||||
// text nodes potentially on either side.
|
||||
if (node.childNodes.length === 0 && node.nodeType !== 3/*Node.TEXT_NODE*/) {
|
||||
throw new Error('targetNode must be a text node or only contain a single text node');
|
||||
}
|
||||
|
||||
const fullText = node.textContent;
|
||||
|
||||
if (substringIndex === 0) {
|
||||
// Replace with <newNode><textnode>
|
||||
const rightText = fullText.substring(substring.length);
|
||||
const rightTextNode = this._document.createTextNode(rightText);
|
||||
this._replaceNode(node, newNode, rightTextNode);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (substringIndex === targetNode.textContent.length - substring.length) {
|
||||
// Replace with <textnode><newNode>
|
||||
const leftText = fullText.substring(0, substringIndex);
|
||||
const leftTextNode = this._document.createTextNode(leftText);
|
||||
this._replaceNode(node, leftTextNode, newNode);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Replace with <textnode><newNode><textnode>
|
||||
const leftText = fullText.substring(0, substringIndex);
|
||||
const leftTextNode = this._document.createTextNode(leftText);
|
||||
const rightText = fullText.substring(substringIndex + substring.length);
|
||||
const rightTextNode = this._document.createTextNode(rightText);
|
||||
this._replaceNode(node, leftTextNode, newNode, rightTextNode);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
+636
@@ -0,0 +1,636 @@
|
||||
/**
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { C0 } from './EscapeSequences';
|
||||
import { IInputHandler } from './Interfaces';
|
||||
import { CHARSETS, DEFAULT_CHARSET } from './Charsets';
|
||||
|
||||
const normalStateHandler: {[key: string]: (parser: Parser, handler: IInputHandler) => void} = {};
|
||||
normalStateHandler[C0.BEL] = (parser, handler) => handler.bell();
|
||||
normalStateHandler[C0.LF] = (parser, handler) => handler.lineFeed();
|
||||
normalStateHandler[C0.VT] = normalStateHandler[C0.LF];
|
||||
normalStateHandler[C0.FF] = normalStateHandler[C0.LF];
|
||||
normalStateHandler[C0.CR] = (parser, handler) => handler.carriageReturn();
|
||||
normalStateHandler[C0.BS] = (parser, handler) => handler.backspace();
|
||||
normalStateHandler[C0.HT] = (parser, handler) => handler.tab();
|
||||
normalStateHandler[C0.SO] = (parser, handler) => handler.shiftOut();
|
||||
normalStateHandler[C0.SI] = (parser, handler) => handler.shiftIn();
|
||||
normalStateHandler[C0.ESC] = (parser, handler) => parser.setState(ParserState.ESCAPED);
|
||||
|
||||
// TODO: Remove terminal when parser owns params and currentParam
|
||||
const escapedStateHandler: {[key: string]: (parser: Parser, terminal: any) => void} = {};
|
||||
escapedStateHandler['['] = (parser, terminal) => {
|
||||
// ESC [ Control Sequence Introducer (CSI is 0x9b)
|
||||
terminal.params = [];
|
||||
terminal.currentParam = 0;
|
||||
parser.setState(ParserState.CSI_PARAM);
|
||||
};
|
||||
escapedStateHandler[']'] = (parser, terminal) => {
|
||||
// ESC ] Operating System Command (OSC is 0x9d)
|
||||
terminal.params = [];
|
||||
terminal.currentParam = 0;
|
||||
parser.setState(ParserState.OSC);
|
||||
};
|
||||
escapedStateHandler['P'] = (parser, terminal) => {
|
||||
// ESC P Device Control String (DCS is 0x90)
|
||||
terminal.params = [];
|
||||
terminal.currentParam = 0;
|
||||
parser.setState(ParserState.DCS);
|
||||
};
|
||||
escapedStateHandler['_'] = (parser, terminal) => {
|
||||
// ESC _ Application Program Command ( APC is 0x9f).
|
||||
parser.setState(ParserState.IGNORE);
|
||||
};
|
||||
escapedStateHandler['^'] = (parser, terminal) => {
|
||||
// ESC ^ Privacy Message ( PM is 0x9e).
|
||||
parser.setState(ParserState.IGNORE);
|
||||
};
|
||||
escapedStateHandler['c'] = (parser, terminal) => {
|
||||
// ESC c Full Reset (RIS).
|
||||
terminal.reset();
|
||||
};
|
||||
escapedStateHandler['E'] = (parser, terminal) => {
|
||||
// ESC E Next Line ( NEL is 0x85).
|
||||
terminal.x = 0;
|
||||
terminal.index();
|
||||
parser.setState(ParserState.NORMAL);
|
||||
};
|
||||
escapedStateHandler['D'] = (parser, terminal) => {
|
||||
// ESC D Index ( IND is 0x84).
|
||||
terminal.index();
|
||||
parser.setState(ParserState.NORMAL);
|
||||
};
|
||||
escapedStateHandler['M'] = (parser, terminal) => {
|
||||
// ESC M Reverse Index ( RI is 0x8d).
|
||||
terminal.reverseIndex();
|
||||
parser.setState(ParserState.NORMAL);
|
||||
};
|
||||
escapedStateHandler['%'] = (parser, terminal) => {
|
||||
// ESC % Select default/utf-8 character set.
|
||||
// @ = default, G = utf-8
|
||||
terminal.setgLevel(0);
|
||||
terminal.setgCharset(0, DEFAULT_CHARSET); // US (default)
|
||||
parser.setState(ParserState.NORMAL);
|
||||
parser.skipNextChar();
|
||||
};
|
||||
escapedStateHandler[C0.CAN] = (parser) => parser.setState(ParserState.NORMAL);
|
||||
|
||||
const csiParamStateHandler: {[key: string]: (parser: Parser) => void} = {};
|
||||
csiParamStateHandler['?'] = (parser) => parser.setPrefix('?');
|
||||
csiParamStateHandler['>'] = (parser) => parser.setPrefix('>');
|
||||
csiParamStateHandler['!'] = (parser) => parser.setPrefix('!');
|
||||
csiParamStateHandler['0'] = (parser) => parser.setParam(parser.getParam() * 10);
|
||||
csiParamStateHandler['1'] = (parser) => parser.setParam(parser.getParam() * 10 + 1);
|
||||
csiParamStateHandler['2'] = (parser) => parser.setParam(parser.getParam() * 10 + 2);
|
||||
csiParamStateHandler['3'] = (parser) => parser.setParam(parser.getParam() * 10 + 3);
|
||||
csiParamStateHandler['4'] = (parser) => parser.setParam(parser.getParam() * 10 + 4);
|
||||
csiParamStateHandler['5'] = (parser) => parser.setParam(parser.getParam() * 10 + 5);
|
||||
csiParamStateHandler['6'] = (parser) => parser.setParam(parser.getParam() * 10 + 6);
|
||||
csiParamStateHandler['7'] = (parser) => parser.setParam(parser.getParam() * 10 + 7);
|
||||
csiParamStateHandler['8'] = (parser) => parser.setParam(parser.getParam() * 10 + 8);
|
||||
csiParamStateHandler['9'] = (parser) => parser.setParam(parser.getParam() * 10 + 9);
|
||||
csiParamStateHandler['$'] = (parser) => parser.setPostfix('$');
|
||||
csiParamStateHandler['"'] = (parser) => parser.setPostfix('"');
|
||||
csiParamStateHandler[' '] = (parser) => parser.setPostfix(' ');
|
||||
csiParamStateHandler['\''] = (parser) => parser.setPostfix('\'');
|
||||
csiParamStateHandler[';'] = (parser) => parser.finalizeParam();
|
||||
csiParamStateHandler[C0.CAN] = (parser) => parser.setState(ParserState.NORMAL);
|
||||
|
||||
const csiStateHandler: {[key: string]: (handler: IInputHandler, params: number[], prefix: string, postfix: string, parser: Parser) => void} = {};
|
||||
csiStateHandler['@'] = (handler, params, prefix) => handler.insertChars(params);
|
||||
csiStateHandler['A'] = (handler, params, prefix) => handler.cursorUp(params);
|
||||
csiStateHandler['B'] = (handler, params, prefix) => handler.cursorDown(params);
|
||||
csiStateHandler['C'] = (handler, params, prefix) => handler.cursorForward(params);
|
||||
csiStateHandler['D'] = (handler, params, prefix) => handler.cursorBackward(params);
|
||||
csiStateHandler['E'] = (handler, params, prefix) => handler.cursorNextLine(params);
|
||||
csiStateHandler['F'] = (handler, params, prefix) => handler.cursorPrecedingLine(params);
|
||||
csiStateHandler['G'] = (handler, params, prefix) => handler.cursorCharAbsolute(params);
|
||||
csiStateHandler['H'] = (handler, params, prefix) => handler.cursorPosition(params);
|
||||
csiStateHandler['I'] = (handler, params, prefix) => handler.cursorForwardTab(params);
|
||||
csiStateHandler['J'] = (handler, params, prefix) => handler.eraseInDisplay(params);
|
||||
csiStateHandler['K'] = (handler, params, prefix) => handler.eraseInLine(params);
|
||||
csiStateHandler['L'] = (handler, params, prefix) => handler.insertLines(params);
|
||||
csiStateHandler['M'] = (handler, params, prefix) => handler.deleteLines(params);
|
||||
csiStateHandler['P'] = (handler, params, prefix) => handler.deleteChars(params);
|
||||
csiStateHandler['S'] = (handler, params, prefix) => handler.scrollUp(params);
|
||||
csiStateHandler['T'] = (handler, params, prefix) => {
|
||||
if (params.length < 2 && !prefix) {
|
||||
handler.scrollDown(params);
|
||||
}
|
||||
};
|
||||
csiStateHandler['X'] = (handler, params, prefix) => handler.eraseChars(params);
|
||||
csiStateHandler['Z'] = (handler, params, prefix) => handler.cursorBackwardTab(params);
|
||||
csiStateHandler['`'] = (handler, params, prefix) => handler.charPosAbsolute(params);
|
||||
csiStateHandler['a'] = (handler, params, prefix) => handler.HPositionRelative(params);
|
||||
csiStateHandler['b'] = (handler, params, prefix) => handler.repeatPrecedingCharacter(params);
|
||||
csiStateHandler['c'] = (handler, params, prefix) => handler.sendDeviceAttributes(params);
|
||||
csiStateHandler['d'] = (handler, params, prefix) => handler.linePosAbsolute(params);
|
||||
csiStateHandler['e'] = (handler, params, prefix) => handler.VPositionRelative(params);
|
||||
csiStateHandler['f'] = (handler, params, prefix) => handler.HVPosition(params);
|
||||
csiStateHandler['g'] = (handler, params, prefix) => handler.tabClear(params);
|
||||
csiStateHandler['h'] = (handler, params, prefix) => handler.setMode(params);
|
||||
csiStateHandler['l'] = (handler, params, prefix) => handler.resetMode(params);
|
||||
csiStateHandler['m'] = (handler, params, prefix) => handler.charAttributes(params);
|
||||
csiStateHandler['n'] = (handler, params, prefix) => handler.deviceStatus(params);
|
||||
csiStateHandler['p'] = (handler, params, prefix) => {
|
||||
switch (prefix) {
|
||||
case '!': handler.softReset(params); break;
|
||||
}
|
||||
};
|
||||
csiStateHandler['q'] = (handler, params, prefix, postfix) => {
|
||||
if (postfix === ' ') {
|
||||
handler.setCursorStyle(params);
|
||||
}
|
||||
};
|
||||
csiStateHandler['r'] = (handler, params) => handler.setScrollRegion(params);
|
||||
csiStateHandler['s'] = (handler, params) => handler.saveCursor(params);
|
||||
csiStateHandler['u'] = (handler, params) => handler.restoreCursor(params);
|
||||
csiStateHandler[C0.CAN] = (handler, params, prefix, postfix, parser) => parser.setState(ParserState.NORMAL);
|
||||
|
||||
enum ParserState {
|
||||
NORMAL = 0,
|
||||
ESCAPED = 1,
|
||||
CSI_PARAM = 2,
|
||||
CSI = 3,
|
||||
OSC = 4,
|
||||
CHARSET = 5,
|
||||
DCS = 6,
|
||||
IGNORE = 7
|
||||
}
|
||||
|
||||
/**
|
||||
* The terminal's parser, all input into the terminal goes through the parser
|
||||
* which parses and defers the actual input handling the the IInputHandler
|
||||
* specified in the constructor.
|
||||
*/
|
||||
export class Parser {
|
||||
private _state: ParserState;
|
||||
private _position: number;
|
||||
|
||||
// TODO: Remove terminal when handler can do everything
|
||||
constructor(
|
||||
private _inputHandler: IInputHandler,
|
||||
private _terminal: any
|
||||
) {
|
||||
this._state = ParserState.NORMAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and handle data.
|
||||
*
|
||||
* @param data The data to parse.
|
||||
*/
|
||||
public parse(data: string) {
|
||||
let l = data.length, j, cs, ch, code, low;
|
||||
|
||||
this._position = 0;
|
||||
// apply leftover surrogate high from last write
|
||||
if (this._terminal.surrogate_high) {
|
||||
data = this._terminal.surrogate_high + data;
|
||||
this._terminal.surrogate_high = '';
|
||||
}
|
||||
|
||||
for (; this._position < l; this._position++) {
|
||||
ch = data[this._position];
|
||||
|
||||
// FIXME: higher chars than 0xa0 are not allowed in escape sequences
|
||||
// --> maybe move to default
|
||||
code = data.charCodeAt(this._position);
|
||||
if (0xD800 <= code && code <= 0xDBFF) {
|
||||
// we got a surrogate high
|
||||
// get surrogate low (next 2 bytes)
|
||||
low = data.charCodeAt(this._position + 1);
|
||||
if (isNaN(low)) {
|
||||
// end of data stream, save surrogate high
|
||||
this._terminal.surrogate_high = ch;
|
||||
continue;
|
||||
}
|
||||
code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000;
|
||||
ch += data.charAt(this._position + 1);
|
||||
}
|
||||
// surrogate low - already handled above
|
||||
if (0xDC00 <= code && code <= 0xDFFF)
|
||||
continue;
|
||||
|
||||
switch (this._state) {
|
||||
case ParserState.NORMAL:
|
||||
if (ch in normalStateHandler) {
|
||||
normalStateHandler[ch](this, this._inputHandler);
|
||||
} else {
|
||||
this._inputHandler.addChar(ch, code);
|
||||
}
|
||||
break;
|
||||
case ParserState.ESCAPED:
|
||||
if (ch in escapedStateHandler) {
|
||||
escapedStateHandler[ch](this, this._terminal);
|
||||
// Skip switch as it was just handled
|
||||
break;
|
||||
}
|
||||
switch (ch) {
|
||||
|
||||
// ESC (,),*,+,-,. Designate G0-G2 Character Set.
|
||||
case '(': // <-- this seems to get all the attention
|
||||
case ')':
|
||||
case '*':
|
||||
case '+':
|
||||
case '-':
|
||||
case '.':
|
||||
switch (ch) {
|
||||
case '(':
|
||||
this._terminal.gcharset = 0;
|
||||
break;
|
||||
case ')':
|
||||
this._terminal.gcharset = 1;
|
||||
break;
|
||||
case '*':
|
||||
this._terminal.gcharset = 2;
|
||||
break;
|
||||
case '+':
|
||||
this._terminal.gcharset = 3;
|
||||
break;
|
||||
case '-':
|
||||
this._terminal.gcharset = 1;
|
||||
break;
|
||||
case '.':
|
||||
this._terminal.gcharset = 2;
|
||||
break;
|
||||
}
|
||||
this._state = ParserState.CHARSET;
|
||||
break;
|
||||
|
||||
// Designate G3 Character Set (VT300).
|
||||
// A = ISO Latin-1 Supplemental.
|
||||
// Not implemented.
|
||||
case '/':
|
||||
this._terminal.gcharset = 3;
|
||||
this._state = ParserState.CHARSET;
|
||||
this._position--;
|
||||
break;
|
||||
|
||||
// ESC N
|
||||
// Single Shift Select of G2 Character Set
|
||||
// ( SS2 is 0x8e). This affects next character only.
|
||||
case 'N':
|
||||
break;
|
||||
// ESC O
|
||||
// Single Shift Select of G3 Character Set
|
||||
// ( SS3 is 0x8f). This affects next character only.
|
||||
case 'O':
|
||||
break;
|
||||
// ESC n
|
||||
// Invoke the G2 Character Set as GL (LS2).
|
||||
case 'n':
|
||||
this._terminal.setgLevel(2);
|
||||
break;
|
||||
// ESC o
|
||||
// Invoke the G3 Character Set as GL (LS3).
|
||||
case 'o':
|
||||
this._terminal.setgLevel(3);
|
||||
break;
|
||||
// ESC |
|
||||
// Invoke the G3 Character Set as GR (LS3R).
|
||||
case '|':
|
||||
this._terminal.setgLevel(3);
|
||||
break;
|
||||
// ESC }
|
||||
// Invoke the G2 Character Set as GR (LS2R).
|
||||
case '}':
|
||||
this._terminal.setgLevel(2);
|
||||
break;
|
||||
// ESC ~
|
||||
// Invoke the G1 Character Set as GR (LS1R).
|
||||
case '~':
|
||||
this._terminal.setgLevel(1);
|
||||
break;
|
||||
|
||||
// ESC 7 Save Cursor (DECSC).
|
||||
case '7':
|
||||
this._inputHandler.saveCursor();
|
||||
this._state = ParserState.NORMAL;
|
||||
break;
|
||||
|
||||
// ESC 8 Restore Cursor (DECRC).
|
||||
case '8':
|
||||
this._inputHandler.restoreCursor();
|
||||
this._state = ParserState.NORMAL;
|
||||
break;
|
||||
|
||||
// ESC # 3 DEC line height/width
|
||||
case '#':
|
||||
this._state = ParserState.NORMAL;
|
||||
this._position++;
|
||||
break;
|
||||
|
||||
// ESC H Tab Set (HTS is 0x88).
|
||||
case 'H':
|
||||
this._terminal.tabSet();
|
||||
this._state = ParserState.NORMAL;
|
||||
break;
|
||||
|
||||
// ESC = Application Keypad (DECKPAM).
|
||||
case '=':
|
||||
this._terminal.log('Serial port requested application keypad.');
|
||||
this._terminal.applicationKeypad = true;
|
||||
this._terminal.viewport.syncScrollArea();
|
||||
this._state = ParserState.NORMAL;
|
||||
break;
|
||||
|
||||
// ESC > Normal Keypad (DECKPNM).
|
||||
case '>':
|
||||
this._terminal.log('Switching back to normal keypad.');
|
||||
this._terminal.applicationKeypad = false;
|
||||
this._terminal.viewport.syncScrollArea();
|
||||
this._state = ParserState.NORMAL;
|
||||
break;
|
||||
|
||||
default:
|
||||
this._state = ParserState.NORMAL;
|
||||
this._terminal.error('Unknown ESC control: %s.', ch);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ParserState.CHARSET:
|
||||
if (ch in CHARSETS) {
|
||||
cs = CHARSETS[ch];
|
||||
if (ch === '/') { // ISOLatin is actually /A
|
||||
this.skipNextChar();
|
||||
}
|
||||
} else {
|
||||
cs = DEFAULT_CHARSET;
|
||||
}
|
||||
this._terminal.setgCharset(this._terminal.gcharset, cs);
|
||||
this._terminal.gcharset = null;
|
||||
this._state = ParserState.NORMAL;
|
||||
break;
|
||||
|
||||
case ParserState.OSC:
|
||||
// OSC Ps ; Pt ST
|
||||
// OSC Ps ; Pt BEL
|
||||
// Set Text Parameters.
|
||||
if (ch === C0.ESC || ch === C0.BEL) {
|
||||
if (ch === C0.ESC) this._position++;
|
||||
|
||||
this._terminal.params.push(this._terminal.currentParam);
|
||||
|
||||
switch (this._terminal.params[0]) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
if (this._terminal.params[1]) {
|
||||
this._terminal.title = this._terminal.params[1];
|
||||
this._terminal.handleTitle(this._terminal.title);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
// set X property
|
||||
break;
|
||||
case 4:
|
||||
case 5:
|
||||
// change dynamic colors
|
||||
break;
|
||||
case 10:
|
||||
case 11:
|
||||
case 12:
|
||||
case 13:
|
||||
case 14:
|
||||
case 15:
|
||||
case 16:
|
||||
case 17:
|
||||
case 18:
|
||||
case 19:
|
||||
// change dynamic ui colors
|
||||
break;
|
||||
case 46:
|
||||
// change log file
|
||||
break;
|
||||
case 50:
|
||||
// dynamic font
|
||||
break;
|
||||
case 51:
|
||||
// emacs shell
|
||||
break;
|
||||
case 52:
|
||||
// manipulate selection data
|
||||
break;
|
||||
case 104:
|
||||
case 105:
|
||||
case 110:
|
||||
case 111:
|
||||
case 112:
|
||||
case 113:
|
||||
case 114:
|
||||
case 115:
|
||||
case 116:
|
||||
case 117:
|
||||
case 118:
|
||||
// reset colors
|
||||
break;
|
||||
}
|
||||
|
||||
this._terminal.params = [];
|
||||
this._terminal.currentParam = 0;
|
||||
this._state = ParserState.NORMAL;
|
||||
} else {
|
||||
if (!this._terminal.params.length) {
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
this._terminal.currentParam =
|
||||
this._terminal.currentParam * 10 + ch.charCodeAt(0) - 48;
|
||||
} else if (ch === ';') {
|
||||
this._terminal.params.push(this._terminal.currentParam);
|
||||
this._terminal.currentParam = '';
|
||||
}
|
||||
} else {
|
||||
this._terminal.currentParam += ch;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ParserState.CSI_PARAM:
|
||||
if (ch in csiParamStateHandler) {
|
||||
csiParamStateHandler[ch](this);
|
||||
break;
|
||||
}
|
||||
this.finalizeParam();
|
||||
// Fall through the CSI as this character should be the CSI code.
|
||||
this._state = ParserState.CSI;
|
||||
|
||||
case ParserState.CSI:
|
||||
if (ch in csiStateHandler) {
|
||||
csiStateHandler[ch](this._inputHandler, this._terminal.params, this._terminal.prefix, this._terminal.postfix, this);
|
||||
} else {
|
||||
this._terminal.error('Unknown CSI code: %s.', ch);
|
||||
}
|
||||
|
||||
this._state = ParserState.NORMAL;
|
||||
this._terminal.prefix = '';
|
||||
this._terminal.postfix = '';
|
||||
break;
|
||||
|
||||
case ParserState.DCS:
|
||||
if (ch === C0.ESC || ch === C0.BEL) {
|
||||
if (ch === C0.ESC) this._position++;
|
||||
|
||||
switch (this._terminal.prefix) {
|
||||
// User-Defined Keys (DECUDK).
|
||||
case '':
|
||||
break;
|
||||
|
||||
// Request Status String (DECRQSS).
|
||||
// test: echo -e '\eP$q"p\e\\'
|
||||
case '$q':
|
||||
let pt = this._terminal.currentParam
|
||||
, valid = false;
|
||||
|
||||
switch (pt) {
|
||||
// DECSCA
|
||||
case '"q':
|
||||
pt = '0"q';
|
||||
break;
|
||||
|
||||
// DECSCL
|
||||
case '"p':
|
||||
pt = '61"p';
|
||||
break;
|
||||
|
||||
// DECSTBM
|
||||
case 'r':
|
||||
pt = ''
|
||||
+ (this._terminal.scrollTop + 1)
|
||||
+ ';'
|
||||
+ (this._terminal.scrollBottom + 1)
|
||||
+ 'r';
|
||||
break;
|
||||
|
||||
// SGR
|
||||
case 'm':
|
||||
pt = '0m';
|
||||
break;
|
||||
|
||||
default:
|
||||
this._terminal.error('Unknown DCS Pt: %s.', pt);
|
||||
pt = '';
|
||||
break;
|
||||
}
|
||||
|
||||
this._terminal.send(C0.ESC + 'P' + +valid + '$r' + pt + C0.ESC + '\\');
|
||||
break;
|
||||
|
||||
// Set Termcap/Terminfo Data (xterm, experimental).
|
||||
case '+p':
|
||||
break;
|
||||
|
||||
// Request Termcap/Terminfo String (xterm, experimental)
|
||||
// Regular xterm does not even respond to this sequence.
|
||||
// This can cause a small glitch in vim.
|
||||
// test: echo -ne '\eP+q6b64\e\\'
|
||||
case '+q':
|
||||
// TODO: Don't declare pt twice
|
||||
/*let*/ pt = this._terminal.currentParam
|
||||
, valid = false;
|
||||
|
||||
this._terminal.send(C0.ESC + 'P' + +valid + '+r' + pt + C0.ESC + '\\');
|
||||
break;
|
||||
|
||||
default:
|
||||
this._terminal.error('Unknown DCS prefix: %s.', this._terminal.prefix);
|
||||
break;
|
||||
}
|
||||
|
||||
this._terminal.currentParam = 0;
|
||||
this._terminal.prefix = '';
|
||||
this._state = ParserState.NORMAL;
|
||||
} else if (!this._terminal.currentParam) {
|
||||
if (!this._terminal.prefix && ch !== '$' && ch !== '+') {
|
||||
this._terminal.currentParam = ch;
|
||||
} else if (this._terminal.prefix.length === 2) {
|
||||
this._terminal.currentParam = ch;
|
||||
} else {
|
||||
this._terminal.prefix += ch;
|
||||
}
|
||||
} else {
|
||||
this._terminal.currentParam += ch;
|
||||
}
|
||||
break;
|
||||
|
||||
case ParserState.IGNORE:
|
||||
// For PM and APC.
|
||||
if (ch === C0.ESC || ch === C0.BEL) {
|
||||
if (ch === C0.ESC) this._position++;
|
||||
this._state = ParserState.NORMAL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the parser's current parsing state.
|
||||
*
|
||||
* @param state The new state.
|
||||
*/
|
||||
public setState(state: ParserState): void {
|
||||
this._state = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the parsier's current prefix. CSI codes can have prefixes of '?', '>'
|
||||
* or '!'.
|
||||
*
|
||||
* @param prefix The prefix.
|
||||
*/
|
||||
public setPrefix(prefix: string): void {
|
||||
this._terminal.prefix = prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the parsier's current prefix. CSI codes can have postfixes of '$',
|
||||
* '"', ' ', '\''.
|
||||
*
|
||||
* @param postfix The postfix.
|
||||
*/
|
||||
public setPostfix(postfix: string): void {
|
||||
this._terminal.postfix = postfix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the parser's current parameter.
|
||||
*
|
||||
* @param param the parameter.
|
||||
*/
|
||||
public setParam(param: number) {
|
||||
this._terminal.currentParam = param;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the parser's current parameter.
|
||||
*/
|
||||
public getParam(): number {
|
||||
return this._terminal.currentParam;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizes the parser's current parameter, adding it to the list of
|
||||
* parameters and setting the new current parameter to 0.
|
||||
*/
|
||||
public finalizeParam(): void {
|
||||
this._terminal.params.push(this._terminal.currentParam);
|
||||
this._terminal.currentParam = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the parser to skip the next character.
|
||||
*/
|
||||
public skipNextChar(): void {
|
||||
this._position++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the parser to repeat parsing the current character (for example if it
|
||||
* needs parsing using a different state.
|
||||
*/
|
||||
// public repeatChar(): void {
|
||||
// this._position--;
|
||||
// }
|
||||
}
|
||||
+306
@@ -0,0 +1,306 @@
|
||||
/**
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ITerminal } from './Interfaces';
|
||||
|
||||
/**
|
||||
* The maximum number of refresh frames to skip when the write buffer is non-
|
||||
* empty. Note that these frames may be intermingled with frames that are
|
||||
* skipped via requestAnimationFrame's mechanism.
|
||||
*/
|
||||
const MAX_REFRESH_FRAME_SKIP = 5;
|
||||
|
||||
/**
|
||||
* Flags used to render terminal text properly.
|
||||
*/
|
||||
enum FLAGS {
|
||||
BOLD = 1,
|
||||
UNDERLINE = 2,
|
||||
BLINK = 4,
|
||||
INVERSE = 8,
|
||||
INVISIBLE = 16
|
||||
};
|
||||
|
||||
let brokenBold: boolean = null;
|
||||
|
||||
export class Renderer {
|
||||
/** A queue of the rows to be refreshed */
|
||||
private _refreshRowsQueue: {start: number, end: number}[] = [];
|
||||
private _refreshFramesSkipped = 0;
|
||||
private _refreshAnimationFrame = null;
|
||||
|
||||
constructor(private _terminal: ITerminal) {
|
||||
// Figure out whether boldness affects
|
||||
// the character width of monospace fonts.
|
||||
if (brokenBold === null) {
|
||||
brokenBold = checkBoldBroken((<any>this._terminal).element);
|
||||
}
|
||||
|
||||
// TODO: Pull more DOM interactions into Renderer.constructor, element for
|
||||
// example should be owned by Renderer (and also exposed by Terminal due to
|
||||
// to established public API).
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a refresh between two rows (inclusive), to be done on next animation
|
||||
* frame.
|
||||
* @param {number} start The start row.
|
||||
* @param {number} end The end row.
|
||||
*/
|
||||
public queueRefresh(start: number, end: number): void {
|
||||
this._refreshRowsQueue.push({ start: start, end: end });
|
||||
if (!this._refreshAnimationFrame) {
|
||||
this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the refresh loop callback, calling refresh only if a refresh is
|
||||
* necessary before queueing up the next one.
|
||||
*/
|
||||
private _refreshLoop(): void {
|
||||
// Skip MAX_REFRESH_FRAME_SKIP frames if the writeBuffer is non-empty as it
|
||||
// will need to be immediately refreshed anyway. This saves a lot of
|
||||
// rendering time as the viewport DOM does not need to be refreshed, no
|
||||
// scroll events, no layouts, etc.
|
||||
const skipFrame = this._terminal.writeBuffer.length > 0 && this._refreshFramesSkipped++ <= MAX_REFRESH_FRAME_SKIP;
|
||||
if (skipFrame) {
|
||||
this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this));
|
||||
return;
|
||||
}
|
||||
|
||||
this._refreshFramesSkipped = 0;
|
||||
let start;
|
||||
let end;
|
||||
if (this._refreshRowsQueue.length > 4) {
|
||||
// Just do a full refresh when 5+ refreshes are queued
|
||||
start = 0;
|
||||
end = this._terminal.rows - 1;
|
||||
} else {
|
||||
// Get start and end rows that need refreshing
|
||||
start = this._refreshRowsQueue[0].start;
|
||||
end = this._refreshRowsQueue[0].end;
|
||||
for (let i = 1; i < this._refreshRowsQueue.length; i++) {
|
||||
if (this._refreshRowsQueue[i].start < start) {
|
||||
start = this._refreshRowsQueue[i].start;
|
||||
}
|
||||
if (this._refreshRowsQueue[i].end > end) {
|
||||
end = this._refreshRowsQueue[i].end;
|
||||
}
|
||||
}
|
||||
}
|
||||
this._refreshRowsQueue = [];
|
||||
this._refreshAnimationFrame = null;
|
||||
this._refresh(start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes (re-renders) terminal content within two rows (inclusive)
|
||||
*
|
||||
* Rendering Engine:
|
||||
*
|
||||
* In the screen buffer, each character is stored as a an array with a character
|
||||
* and a 32-bit integer:
|
||||
* - First value: a utf-16 character.
|
||||
* - Second value:
|
||||
* - Next 9 bits: background color (0-511).
|
||||
* - Next 9 bits: foreground color (0-511).
|
||||
* - Next 14 bits: a mask for misc. flags:
|
||||
* - 1=bold
|
||||
* - 2=underline
|
||||
* - 4=blink
|
||||
* - 8=inverse
|
||||
* - 16=invisible
|
||||
*
|
||||
* @param {number} start The row to start from (between 0 and terminal's height terminal - 1)
|
||||
* @param {number} end The row to end at (between fromRow and terminal's height terminal - 1)
|
||||
*/
|
||||
private _refresh(start: number, end: number): void {
|
||||
let x, y, i, line, out, ch, ch_width, width, data, attr, bg, fg, flags, row, parent, focused = document.activeElement;
|
||||
|
||||
// If this is a big refresh, remove the terminal rows from the DOM for faster calculations
|
||||
if (end - start >= this._terminal.rows / 2) {
|
||||
parent = this._terminal.element.parentNode;
|
||||
if (parent) {
|
||||
this._terminal.element.removeChild(this._terminal.rowContainer);
|
||||
}
|
||||
}
|
||||
|
||||
width = this._terminal.cols;
|
||||
y = start;
|
||||
|
||||
if (end >= this._terminal.rows) {
|
||||
this._terminal.log('`end` is too large. Most likely a bad CSR.');
|
||||
end = this._terminal.rows - 1;
|
||||
}
|
||||
|
||||
for (; y <= end; y++) {
|
||||
row = y + this._terminal.ydisp;
|
||||
|
||||
line = this._terminal.lines.get(row);
|
||||
if (!line || !this._terminal.children[y]) {
|
||||
// Continue if the line is not available, this means a resize is currently in progress
|
||||
continue;
|
||||
}
|
||||
out = '';
|
||||
|
||||
if (this._terminal.y === y - (this._terminal.ybase - this._terminal.ydisp)
|
||||
&& this._terminal.cursorState
|
||||
&& !this._terminal.cursorHidden) {
|
||||
x = this._terminal.x;
|
||||
} else {
|
||||
x = -1;
|
||||
}
|
||||
|
||||
attr = this._terminal.defAttr;
|
||||
i = 0;
|
||||
|
||||
for (; i < width; i++) {
|
||||
if (!line[i]) {
|
||||
// Continue if the character is not available, this means a resize is currently in progress
|
||||
continue;
|
||||
}
|
||||
data = line[i][0];
|
||||
ch = line[i][1];
|
||||
ch_width = line[i][2];
|
||||
if (!ch_width)
|
||||
continue;
|
||||
|
||||
if (i === x) data = -1;
|
||||
|
||||
if (data !== attr) {
|
||||
if (attr !== this._terminal.defAttr) {
|
||||
out += '</span>';
|
||||
}
|
||||
if (data !== this._terminal.defAttr) {
|
||||
if (data === -1) {
|
||||
out += '<span class="reverse-video terminal-cursor">';
|
||||
} else {
|
||||
let classNames = [];
|
||||
|
||||
bg = data & 0x1ff;
|
||||
fg = (data >> 9) & 0x1ff;
|
||||
flags = data >> 18;
|
||||
|
||||
if (flags & FLAGS.BOLD) {
|
||||
if (!brokenBold) {
|
||||
classNames.push('xterm-bold');
|
||||
}
|
||||
// See: XTerm*boldColors
|
||||
if (fg < 8) fg += 8;
|
||||
}
|
||||
|
||||
if (flags & FLAGS.UNDERLINE) {
|
||||
classNames.push('xterm-underline');
|
||||
}
|
||||
|
||||
if (flags & FLAGS.BLINK) {
|
||||
classNames.push('xterm-blink');
|
||||
}
|
||||
|
||||
// If inverse flag is on, then swap the foreground and background variables.
|
||||
if (flags & FLAGS.INVERSE) {
|
||||
/* One-line variable swap in JavaScript: http://stackoverflow.com/a/16201730 */
|
||||
bg = [fg, fg = bg][0];
|
||||
// Should inverse just be before the
|
||||
// above boldColors effect instead?
|
||||
if ((flags & 1) && fg < 8) fg += 8;
|
||||
}
|
||||
|
||||
if (flags & FLAGS.INVISIBLE) {
|
||||
classNames.push('xterm-hidden');
|
||||
}
|
||||
|
||||
/**
|
||||
* Weird situation: Invert flag used black foreground and white background results
|
||||
* in invalid background color, positioned at the 256 index of the 256 terminal
|
||||
* color map. Pin the colors manually in such a case.
|
||||
*
|
||||
* Source: https://github.com/sourcelair/xterm.js/issues/57
|
||||
*/
|
||||
if (flags & FLAGS.INVERSE) {
|
||||
if (bg === 257) {
|
||||
bg = 15;
|
||||
}
|
||||
if (fg === 256) {
|
||||
fg = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (bg < 256) {
|
||||
classNames.push('xterm-bg-color-' + bg);
|
||||
}
|
||||
|
||||
if (fg < 256) {
|
||||
classNames.push('xterm-color-' + fg);
|
||||
}
|
||||
|
||||
out += '<span';
|
||||
if (classNames.length) {
|
||||
out += ' class="' + classNames.join(' ') + '"';
|
||||
}
|
||||
out += '>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ch_width === 2) {
|
||||
out += '<span class="xterm-wide-char">';
|
||||
}
|
||||
switch (ch) {
|
||||
case '&':
|
||||
out += '&';
|
||||
break;
|
||||
case '<':
|
||||
out += '<';
|
||||
break;
|
||||
case '>':
|
||||
out += '>';
|
||||
break;
|
||||
default:
|
||||
if (ch <= ' ') {
|
||||
out += ' ';
|
||||
} else {
|
||||
out += ch;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (ch_width === 2) {
|
||||
out += '</span>';
|
||||
}
|
||||
|
||||
attr = data;
|
||||
}
|
||||
|
||||
if (attr !== this._terminal.defAttr) {
|
||||
out += '</span>';
|
||||
}
|
||||
|
||||
this._terminal.children[y].innerHTML = out;
|
||||
}
|
||||
|
||||
if (parent) {
|
||||
this._terminal.element.appendChild(this._terminal.rowContainer);
|
||||
}
|
||||
|
||||
this._terminal.emit('refresh', {element: this._terminal.element, start: start, end: end});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// if bold is broken, we can't
|
||||
// use it in the terminal.
|
||||
function checkBoldBroken(terminal) {
|
||||
const document = terminal.ownerDocument;
|
||||
const el = document.createElement('span');
|
||||
el.innerHTML = 'hello world';
|
||||
terminal.appendChild(el);
|
||||
const w1 = el.offsetWidth;
|
||||
const h1 = el.offsetHeight;
|
||||
el.style.fontWeight = 'bold';
|
||||
const w2 = el.offsetWidth;
|
||||
const h2 = el.offsetHeight;
|
||||
terminal.removeChild(el);
|
||||
return w1 !== w2 || h1 !== h2;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
export type LinkMatcher = {
|
||||
id: number,
|
||||
regex: RegExp,
|
||||
handler: LinkMatcherHandler,
|
||||
matchIndex?: number,
|
||||
validationCallback?: LinkMatcherValidationCallback,
|
||||
priority?: number
|
||||
};
|
||||
export type LinkMatcherHandler = (event: MouseEvent, uri: string) => boolean | void;
|
||||
export type LinkMatcherValidationCallback = (uri: string, element: HTMLElement, callback: (isValid: boolean) => void) => void;
|
||||
@@ -0,0 +1,86 @@
|
||||
import { assert } from 'chai';
|
||||
import { Viewport } from './Viewport';
|
||||
|
||||
describe('Viewport', () => {
|
||||
let terminal;
|
||||
let viewportElement;
|
||||
let charMeasure;
|
||||
let viewport;
|
||||
let scrollAreaElement;
|
||||
|
||||
const CHARACTER_HEIGHT = 10;
|
||||
|
||||
beforeEach(() => {
|
||||
terminal = {
|
||||
lines: [],
|
||||
rows: 0,
|
||||
ydisp: 0,
|
||||
on: () => {},
|
||||
rowContainer: {
|
||||
style: {
|
||||
lineHeight: 0
|
||||
}
|
||||
}
|
||||
};
|
||||
viewportElement = {
|
||||
addEventListener: () => {},
|
||||
style: {
|
||||
height: 0,
|
||||
lineHeight: 0
|
||||
}
|
||||
};
|
||||
scrollAreaElement = {
|
||||
style: {
|
||||
height: 0
|
||||
}
|
||||
};
|
||||
charMeasure = {
|
||||
height: CHARACTER_HEIGHT
|
||||
};
|
||||
viewport = new Viewport(terminal, viewportElement, scrollAreaElement, charMeasure);
|
||||
});
|
||||
|
||||
describe('refresh', () => {
|
||||
it('should set the line-height of the terminal', done => {
|
||||
// Allow CharMeasure to be initialized
|
||||
setTimeout(() => {
|
||||
assert.equal(viewportElement.style.lineHeight, CHARACTER_HEIGHT + 'px');
|
||||
assert.equal(terminal.rowContainer.style.lineHeight, CHARACTER_HEIGHT + 'px');
|
||||
charMeasure.height = 1;
|
||||
viewport.refresh();
|
||||
assert.equal(viewportElement.style.lineHeight, '1px');
|
||||
assert.equal(terminal.rowContainer.style.lineHeight, '1px');
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
it('should set the height of the viewport when the line-height changed', () => {
|
||||
terminal.lines.push('');
|
||||
terminal.lines.push('');
|
||||
terminal.rows = 1;
|
||||
viewport.refresh();
|
||||
assert.equal(viewportElement.style.height, 1 * CHARACTER_HEIGHT + 'px');
|
||||
charMeasure.height = 20;
|
||||
viewport.refresh();
|
||||
assert.equal(viewportElement.style.height, 20 + 'px');
|
||||
});
|
||||
});
|
||||
|
||||
describe('syncScrollArea', () => {
|
||||
it('should sync the scroll area', done => {
|
||||
// Allow CharMeasure to be initialized
|
||||
setTimeout(() => {
|
||||
terminal.lines.push('');
|
||||
terminal.rows = 1;
|
||||
assert.equal(scrollAreaElement.style.height, 0 * CHARACTER_HEIGHT + 'px');
|
||||
viewport.syncScrollArea();
|
||||
assert.equal(viewportElement.style.height, 1 * CHARACTER_HEIGHT + 'px');
|
||||
assert.equal(scrollAreaElement.style.height, 1 * CHARACTER_HEIGHT + 'px');
|
||||
terminal.lines.push('');
|
||||
viewport.syncScrollArea();
|
||||
assert.equal(viewportElement.style.height, 1 * CHARACTER_HEIGHT + 'px');
|
||||
assert.equal(scrollAreaElement.style.height, 2 * CHARACTER_HEIGHT + 'px');
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ITerminal } from './Interfaces';
|
||||
import { CharMeasure } from './utils/CharMeasure';
|
||||
|
||||
/**
|
||||
* Represents the viewport of a terminal, the visible area within the larger buffer of output.
|
||||
* Logic for the virtual scroll bar is included in this object.
|
||||
*/
|
||||
export class Viewport {
|
||||
private currentRowHeight: number;
|
||||
private lastRecordedBufferLength: number;
|
||||
private lastRecordedViewportHeight: number;
|
||||
|
||||
/**
|
||||
* Creates a new Viewport.
|
||||
* @param terminal The terminal this viewport belongs to.
|
||||
* @param viewportElement The DOM element acting as the viewport.
|
||||
* @param scrollArea The DOM element acting as the scroll area.
|
||||
* @param charMeasureElement A DOM element used to measure the character size of. the terminal.
|
||||
*/
|
||||
constructor(
|
||||
private terminal: ITerminal,
|
||||
private viewportElement: HTMLElement,
|
||||
private scrollArea: HTMLElement,
|
||||
private charMeasure: CharMeasure
|
||||
) {
|
||||
this.currentRowHeight = 0;
|
||||
this.lastRecordedBufferLength = 0;
|
||||
this.lastRecordedViewportHeight = 0;
|
||||
|
||||
this.terminal.on('scroll', this.syncScrollArea.bind(this));
|
||||
this.terminal.on('resize', this.syncScrollArea.bind(this));
|
||||
this.viewportElement.addEventListener('scroll', this.onScroll.bind(this));
|
||||
|
||||
// Perform this async to ensure the CharMeasure is ready.
|
||||
setTimeout(() => this.syncScrollArea(), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes row height, setting line-height, viewport height and scroll area height if
|
||||
* necessary.
|
||||
* @param charSize A character size measurement bounding rect object, if it doesn't exist it will
|
||||
* be created.
|
||||
*/
|
||||
private refresh(): void {
|
||||
if (this.charMeasure.height > 0) {
|
||||
const rowHeightChanged = this.charMeasure.height !== this.currentRowHeight;
|
||||
if (rowHeightChanged) {
|
||||
this.currentRowHeight = this.charMeasure.height;
|
||||
this.viewportElement.style.lineHeight = this.charMeasure.height + 'px';
|
||||
this.terminal.rowContainer.style.lineHeight = this.charMeasure.height + 'px';
|
||||
}
|
||||
const viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows;
|
||||
if (rowHeightChanged || viewportHeightChanged) {
|
||||
this.lastRecordedViewportHeight = this.terminal.rows;
|
||||
this.viewportElement.style.height = this.charMeasure.height * this.terminal.rows + 'px';
|
||||
}
|
||||
this.scrollArea.style.height = (this.charMeasure.height * this.lastRecordedBufferLength) + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates dimensions and synchronizes the scroll area if necessary.
|
||||
*/
|
||||
public syncScrollArea(): void {
|
||||
if (this.lastRecordedBufferLength !== this.terminal.lines.length) {
|
||||
// If buffer height changed
|
||||
this.lastRecordedBufferLength = this.terminal.lines.length;
|
||||
this.refresh();
|
||||
} else if (this.lastRecordedViewportHeight !== this.terminal.rows) {
|
||||
// If viewport height changed
|
||||
this.refresh();
|
||||
} else {
|
||||
// If size has changed, refresh viewport
|
||||
if (this.charMeasure.height !== this.currentRowHeight) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
// Sync scrollTop
|
||||
const scrollTop = this.terminal.ydisp * this.currentRowHeight;
|
||||
if (this.viewportElement.scrollTop !== scrollTop) {
|
||||
this.viewportElement.scrollTop = scrollTop;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles scroll events on the viewport, calculating the new viewport and requesting the
|
||||
* terminal to scroll to it.
|
||||
* @param ev The scroll event.
|
||||
*/
|
||||
private onScroll(ev: Event) {
|
||||
const newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight);
|
||||
const diff = newRow - this.terminal.ydisp;
|
||||
this.terminal.scrollDisp(diff, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles mouse wheel events by adjusting the viewport's scrollTop and delegating the actual
|
||||
* scrolling to `onScroll`, this event needs to be attached manually by the consumer of
|
||||
* `Viewport`.
|
||||
* @param ev The mouse wheel event.
|
||||
*/
|
||||
public onWheel(ev: WheelEvent) {
|
||||
if (ev.deltaY === 0) {
|
||||
// Do nothing if it's not a vertical scroll event
|
||||
return;
|
||||
}
|
||||
// Fallback to WheelEvent.DOM_DELTA_PIXEL
|
||||
let multiplier = 1;
|
||||
if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) {
|
||||
multiplier = this.currentRowHeight;
|
||||
} else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
|
||||
multiplier = this.currentRowHeight * this.terminal.rows;
|
||||
}
|
||||
this.viewportElement.scrollTop += ev.deltaY * multiplier;
|
||||
// Prevent the page from scrolling when the terminal scrolls
|
||||
ev.preventDefault();
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Implements the attach method, that attaches the terminal to a WebSocket stream.
|
||||
* @module xterm/addons/attach/attach
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
(function (attach) {
|
||||
if (typeof exports === 'object' && typeof module === 'object') {
|
||||
/*
|
||||
* CommonJS environment
|
||||
*/
|
||||
module.exports = attach(require('../../xterm'));
|
||||
} else if (typeof define == 'function') {
|
||||
/*
|
||||
* Require.js is available
|
||||
*/
|
||||
define(['../../xterm'], attach);
|
||||
} else {
|
||||
/*
|
||||
* Plain browser environment
|
||||
*/
|
||||
attach(window.Terminal);
|
||||
}
|
||||
})(function (Xterm) {
|
||||
'use strict';
|
||||
|
||||
var exports = {};
|
||||
|
||||
/**
|
||||
* Attaches the given terminal to the given socket.
|
||||
*
|
||||
* @param {Xterm} term - The terminal to be attached to the given socket.
|
||||
* @param {WebSocket} socket - The socket to attach the current terminal.
|
||||
* @param {boolean} bidirectional - Whether the terminal should send data
|
||||
* to the socket as well.
|
||||
* @param {boolean} buffered - Whether the rendering of incoming data
|
||||
* should happen instantly or at a maximum
|
||||
* frequency of 1 rendering per 10ms.
|
||||
*/
|
||||
exports.attach = function (term, socket, bidirectional, buffered) {
|
||||
bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional;
|
||||
term.socket = socket;
|
||||
|
||||
term._flushBuffer = function () {
|
||||
term.write(term._attachSocketBuffer);
|
||||
term._attachSocketBuffer = null;
|
||||
clearTimeout(term._attachSocketBufferTimer);
|
||||
term._attachSocketBufferTimer = null;
|
||||
};
|
||||
|
||||
term._pushToBuffer = function (data) {
|
||||
if (term._attachSocketBuffer) {
|
||||
term._attachSocketBuffer += data;
|
||||
} else {
|
||||
term._attachSocketBuffer = data;
|
||||
setTimeout(term._flushBuffer, 10);
|
||||
}
|
||||
};
|
||||
|
||||
term._getMessage = function (ev) {
|
||||
if (buffered) {
|
||||
term._pushToBuffer(ev.data);
|
||||
} else {
|
||||
term.write(ev.data);
|
||||
}
|
||||
};
|
||||
|
||||
term._sendData = function (data) {
|
||||
socket.send(data);
|
||||
};
|
||||
|
||||
socket.addEventListener('message', term._getMessage);
|
||||
|
||||
if (bidirectional) {
|
||||
term.on('data', term._sendData);
|
||||
}
|
||||
|
||||
socket.addEventListener('close', term.detach.bind(term, socket));
|
||||
socket.addEventListener('error', term.detach.bind(term, socket));
|
||||
};
|
||||
|
||||
/**
|
||||
* Detaches the given terminal from the given socket
|
||||
*
|
||||
* @param {Xterm} term - The terminal to be detached from the given socket.
|
||||
* @param {WebSocket} socket - The socket from which to detach the current
|
||||
* terminal.
|
||||
*/
|
||||
exports.detach = function (term, socket) {
|
||||
term.off('data', term._sendData);
|
||||
|
||||
socket = (typeof socket == 'undefined') ? term.socket : socket;
|
||||
|
||||
if (socket) {
|
||||
socket.removeEventListener('message', term._getMessage);
|
||||
}
|
||||
|
||||
delete term.socket;
|
||||
};
|
||||
|
||||
/**
|
||||
* Attaches the current terminal to the given socket
|
||||
*
|
||||
* @param {WebSocket} socket - The socket to attach the current terminal.
|
||||
* @param {boolean} bidirectional - Whether the terminal should send data
|
||||
* to the socket as well.
|
||||
* @param {boolean} buffered - Whether the rendering of incoming data
|
||||
* should happen instantly or at a maximum
|
||||
* frequency of 1 rendering per 10ms.
|
||||
*/
|
||||
Xterm.prototype.attach = function (socket, bidirectional, buffered) {
|
||||
return exports.attach(this, socket, bidirectional, buffered);
|
||||
};
|
||||
|
||||
/**
|
||||
* Detaches the current terminal from the given socket.
|
||||
*
|
||||
* @param {WebSocket} socket - The socket from which to detach the current
|
||||
* terminal.
|
||||
*/
|
||||
Xterm.prototype.detach = function (socket) {
|
||||
return exports.detach(this, socket);
|
||||
};
|
||||
|
||||
return exports;
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "xterm.attach",
|
||||
"main": "attach.js",
|
||||
"private": true
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Fit terminal columns and rows to the dimensions of its DOM element.
|
||||
*
|
||||
* ## Approach
|
||||
* - Rows: Truncate the division of the terminal parent element height by the terminal row height.
|
||||
*
|
||||
* - Columns: Truncate the division of the terminal parent element width by the terminal character
|
||||
* width (apply display: inline at the terminal row and truncate its width with the current
|
||||
* number of columns).
|
||||
* @module xterm/addons/fit/fit
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
(function (fit) {
|
||||
if (typeof exports === 'object' && typeof module === 'object') {
|
||||
/*
|
||||
* CommonJS environment
|
||||
*/
|
||||
module.exports = fit(require('../../xterm'));
|
||||
} else if (typeof define == 'function') {
|
||||
/*
|
||||
* Require.js is available
|
||||
*/
|
||||
define(['../../xterm'], fit);
|
||||
} else {
|
||||
/*
|
||||
* Plain browser environment
|
||||
*/
|
||||
fit(window.Terminal);
|
||||
}
|
||||
})(function (Xterm) {
|
||||
var exports = {};
|
||||
|
||||
exports.proposeGeometry = function (term) {
|
||||
if (!term.element.parentElement) {
|
||||
return null;
|
||||
}
|
||||
var parentElementStyle = window.getComputedStyle(term.element.parentElement),
|
||||
parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height')),
|
||||
parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')) - 17),
|
||||
elementStyle = window.getComputedStyle(term.element),
|
||||
elementPaddingVer = parseInt(elementStyle.getPropertyValue('padding-top')) + parseInt(elementStyle.getPropertyValue('padding-bottom')),
|
||||
elementPaddingHor = parseInt(elementStyle.getPropertyValue('padding-right')) + parseInt(elementStyle.getPropertyValue('padding-left')),
|
||||
availableHeight = parentElementHeight - elementPaddingVer,
|
||||
availableWidth = parentElementWidth - elementPaddingHor,
|
||||
container = term.rowContainer,
|
||||
subjectRow = term.rowContainer.firstElementChild,
|
||||
contentBuffer = subjectRow.innerHTML,
|
||||
characterHeight,
|
||||
rows,
|
||||
characterWidth,
|
||||
cols,
|
||||
geometry;
|
||||
|
||||
subjectRow.style.display = 'inline';
|
||||
subjectRow.innerHTML = 'W'; // Common character for measuring width, although on monospace
|
||||
characterWidth = subjectRow.getBoundingClientRect().width;
|
||||
subjectRow.style.display = ''; // Revert style before calculating height, since they differ.
|
||||
characterHeight = subjectRow.getBoundingClientRect().height;
|
||||
subjectRow.innerHTML = contentBuffer;
|
||||
|
||||
rows = parseInt(availableHeight / characterHeight);
|
||||
cols = parseInt(availableWidth / characterWidth);
|
||||
|
||||
geometry = {cols: cols, rows: rows};
|
||||
return geometry;
|
||||
};
|
||||
|
||||
exports.fit = function (term) {
|
||||
var geometry = exports.proposeGeometry(term);
|
||||
|
||||
if (geometry) {
|
||||
term.resize(geometry.cols, geometry.rows);
|
||||
}
|
||||
};
|
||||
|
||||
Xterm.prototype.proposeGeometry = function () {
|
||||
return exports.proposeGeometry(this);
|
||||
};
|
||||
|
||||
Xterm.prototype.fit = function () {
|
||||
return exports.fit(this);
|
||||
};
|
||||
|
||||
return exports;
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "xterm.fit",
|
||||
"main": "fit.js",
|
||||
"private": true
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
.xterm.fullscreen {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: auto;
|
||||
height: auto;
|
||||
z-index: 255;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Fullscreen addon for xterm.js
|
||||
* @module xterm/addons/fullscreen/fullscreen
|
||||
* @license MIT
|
||||
*/
|
||||
(function (fullscreen) {
|
||||
if (typeof exports === 'object' && typeof module === 'object') {
|
||||
/*
|
||||
* CommonJS environment
|
||||
*/
|
||||
module.exports = fullscreen(require('../../xterm'));
|
||||
} else if (typeof define == 'function') {
|
||||
/*
|
||||
* Require.js is available
|
||||
*/
|
||||
define(['../../xterm'], fullscreen);
|
||||
} else {
|
||||
/*
|
||||
* Plain browser environment
|
||||
*/
|
||||
fullscreen(window.Terminal);
|
||||
}
|
||||
})(function (Xterm) {
|
||||
var exports = {};
|
||||
|
||||
/**
|
||||
* Toggle the given terminal's fullscreen mode.
|
||||
* @param {Xterm} term - The terminal to toggle full screen mode
|
||||
* @param {boolean} fullscreen - Toggle fullscreen on (true) or off (false)
|
||||
*/
|
||||
exports.toggleFullScreen = function (term, fullscreen) {
|
||||
var fn;
|
||||
|
||||
if (typeof fullscreen == 'undefined') {
|
||||
fn = (term.element.classList.contains('fullscreen')) ? 'remove' : 'add';
|
||||
} else if (!fullscreen) {
|
||||
fn = 'remove';
|
||||
} else {
|
||||
fn = 'add';
|
||||
}
|
||||
|
||||
term.element.classList[fn]('fullscreen');
|
||||
};
|
||||
|
||||
Xterm.prototype.toggleFullscreen = function (fullscreen) {
|
||||
exports.toggleFullScreen(this, fullscreen);
|
||||
};
|
||||
|
||||
return exports;
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "xterm.fullscreen",
|
||||
"main": "fullscreen.js",
|
||||
"private": true
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "xterm.terminado",
|
||||
"main": "terminado.js",
|
||||
"private": true
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* This module provides methods for attaching a terminal to a terminado WebSocket stream.
|
||||
*
|
||||
* @module xterm/addons/terminado/terminado
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
(function (attach) {
|
||||
if (typeof exports === 'object' && typeof module === 'object') {
|
||||
/*
|
||||
* CommonJS environment
|
||||
*/
|
||||
module.exports = attach(require('../../xterm'));
|
||||
} else if (typeof define == 'function') {
|
||||
/*
|
||||
* Require.js is available
|
||||
*/
|
||||
define(['../../xterm'], attach);
|
||||
} else {
|
||||
/*
|
||||
* Plain browser environment
|
||||
*/
|
||||
attach(window.Terminal);
|
||||
}
|
||||
})(function (Xterm) {
|
||||
'use strict';
|
||||
|
||||
var exports = {};
|
||||
|
||||
/**
|
||||
* Attaches the given terminal to the given socket.
|
||||
*
|
||||
* @param {Xterm} term - The terminal to be attached to the given socket.
|
||||
* @param {WebSocket} socket - The socket to attach the current terminal.
|
||||
* @param {boolean} bidirectional - Whether the terminal should send data
|
||||
* to the socket as well.
|
||||
* @param {boolean} buffered - Whether the rendering of incoming data
|
||||
* should happen instantly or at a maximum
|
||||
* frequency of 1 rendering per 10ms.
|
||||
*/
|
||||
exports.terminadoAttach = function (term, socket, bidirectional, buffered) {
|
||||
bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional;
|
||||
term.socket = socket;
|
||||
|
||||
term._flushBuffer = function () {
|
||||
term.write(term._attachSocketBuffer);
|
||||
term._attachSocketBuffer = null;
|
||||
clearTimeout(term._attachSocketBufferTimer);
|
||||
term._attachSocketBufferTimer = null;
|
||||
};
|
||||
|
||||
term._pushToBuffer = function (data) {
|
||||
if (term._attachSocketBuffer) {
|
||||
term._attachSocketBuffer += data;
|
||||
} else {
|
||||
term._attachSocketBuffer = data;
|
||||
setTimeout(term._flushBuffer, 10);
|
||||
}
|
||||
};
|
||||
|
||||
term._getMessage = function (ev) {
|
||||
var data = JSON.parse(ev.data)
|
||||
if( data[0] == "stdout" ) {
|
||||
if (buffered) {
|
||||
term._pushToBuffer(data[1]);
|
||||
} else {
|
||||
term.write(data[1]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
term._sendData = function (data) {
|
||||
socket.send(JSON.stringify(['stdin', data]));
|
||||
};
|
||||
|
||||
term._setSize = function (size) {
|
||||
socket.send(JSON.stringify(['set_size', size.rows, size.cols]));
|
||||
};
|
||||
|
||||
socket.addEventListener('message', term._getMessage);
|
||||
|
||||
if (bidirectional) {
|
||||
term.on('data', term._sendData);
|
||||
}
|
||||
term.on('resize', term._setSize);
|
||||
|
||||
socket.addEventListener('close', term.terminadoDetach.bind(term, socket));
|
||||
socket.addEventListener('error', term.terminadoDetach.bind(term, socket));
|
||||
};
|
||||
|
||||
/**
|
||||
* Detaches the given terminal from the given socket
|
||||
*
|
||||
* @param {Xterm} term - The terminal to be detached from the given socket.
|
||||
* @param {WebSocket} socket - The socket from which to detach the current
|
||||
* terminal.
|
||||
*/
|
||||
exports.terminadoDetach = function (term, socket) {
|
||||
term.off('data', term._sendData);
|
||||
|
||||
socket = (typeof socket == 'undefined') ? term.socket : socket;
|
||||
|
||||
if (socket) {
|
||||
socket.removeEventListener('message', term._getMessage);
|
||||
}
|
||||
|
||||
delete term.socket;
|
||||
};
|
||||
|
||||
/**
|
||||
* Attaches the current terminal to the given socket
|
||||
*
|
||||
* @param {WebSocket} socket - The socket to attach the current terminal.
|
||||
* @param {boolean} bidirectional - Whether the terminal should send data
|
||||
* to the socket as well.
|
||||
* @param {boolean} buffered - Whether the rendering of incoming data
|
||||
* should happen instantly or at a maximum
|
||||
* frequency of 1 rendering per 10ms.
|
||||
*/
|
||||
Xterm.prototype.terminadoAttach = function (socket, bidirectional, buffered) {
|
||||
return exports.terminadoAttach(this, socket, bidirectional, buffered);
|
||||
};
|
||||
|
||||
/**
|
||||
* Detaches the current terminal from the given socket.
|
||||
*
|
||||
* @param {WebSocket} socket - The socket from which to detach the current
|
||||
* terminal.
|
||||
*/
|
||||
Xterm.prototype.terminadoDetach = function (socket) {
|
||||
return exports.terminadoDetach(this, socket);
|
||||
};
|
||||
|
||||
return exports;
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { assert } from 'chai';
|
||||
import * as Terminal from '../xterm';
|
||||
import * as Clipboard from './Clipboard';
|
||||
|
||||
|
||||
describe('evaluateCopiedTextProcessing', function () {
|
||||
it('should strip trailing whitespaces and replace nbsps with spaces', function () {
|
||||
let nonBreakingSpace = String.fromCharCode(160),
|
||||
copiedText = 'echo' + nonBreakingSpace + 'hello' + nonBreakingSpace,
|
||||
processedText = Clipboard.prepareTextForClipboard(copiedText);
|
||||
|
||||
// No trailing spaces
|
||||
assert.equal(processedText.match(/\s+$/), null);
|
||||
|
||||
// No non-breaking space
|
||||
assert.equal(processedText.indexOf(nonBreakingSpace), -1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,154 @@
|
||||
/**
|
||||
* Clipboard handler module: exports methods for handling all clipboard-related events in the
|
||||
* terminal.
|
||||
* @module xterm/handlers/Clipboard
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ITerminal } from '../Interfaces';
|
||||
|
||||
interface IWindow extends Window {
|
||||
clipboardData?: {
|
||||
getData(format: string): string;
|
||||
setData(format: string, data: string);
|
||||
};
|
||||
}
|
||||
|
||||
declare var window: IWindow;
|
||||
|
||||
/**
|
||||
* Prepares text copied from terminal selection, to be saved in the clipboard by:
|
||||
* 1. stripping all trailing white spaces
|
||||
* 2. converting all non-breaking spaces to regular spaces
|
||||
* @param {string} text The copied text that needs processing for storing in clipboard
|
||||
* @returns {string}
|
||||
*/
|
||||
export function prepareTextForClipboard(text: string): string {
|
||||
let space = String.fromCharCode(32),
|
||||
nonBreakingSpace = String.fromCharCode(160),
|
||||
allNonBreakingSpaces = new RegExp(nonBreakingSpace, 'g'),
|
||||
processedText = text.split('\n').map(function (line) {
|
||||
// Strip all trailing white spaces and convert all non-breaking spaces
|
||||
// to regular spaces.
|
||||
let processedLine = line.replace(/\s+$/g, '').replace(allNonBreakingSpaces, space);
|
||||
|
||||
return processedLine;
|
||||
}).join('\n');
|
||||
|
||||
return processedText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds copy functionality to the given terminal.
|
||||
* @param {ClipboardEvent} ev The original copy event to be handled
|
||||
*/
|
||||
export function copyHandler(ev: ClipboardEvent, term: ITerminal) {
|
||||
// We cast `window` to `any` type, because TypeScript has not declared the `clipboardData`
|
||||
// property that we use below for Internet Explorer.
|
||||
let copiedText = window.getSelection().toString(),
|
||||
text = prepareTextForClipboard(copiedText);
|
||||
|
||||
if (term.browser.isMSIE) {
|
||||
window.clipboardData.setData('Text', text);
|
||||
} else {
|
||||
ev.clipboardData.setData('text/plain', text);
|
||||
}
|
||||
|
||||
ev.preventDefault(); // Prevent or the original text will be copied.
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect the clipboard's data to the terminal's input handler.
|
||||
* @param {ClipboardEvent} ev The original paste event to be handled
|
||||
* @param {Terminal} term The terminal on which to apply the handled paste event
|
||||
*/
|
||||
export function pasteHandler(ev: ClipboardEvent, term: ITerminal) {
|
||||
ev.stopPropagation();
|
||||
|
||||
let text: string;
|
||||
|
||||
let dispatchPaste = function(text) {
|
||||
term.handler(text);
|
||||
term.textarea.value = '';
|
||||
return term.cancel(ev);
|
||||
};
|
||||
|
||||
if (term.browser.isMSIE) {
|
||||
if (window.clipboardData) {
|
||||
text = window.clipboardData.getData('Text');
|
||||
dispatchPaste(text);
|
||||
}
|
||||
} else {
|
||||
if (ev.clipboardData) {
|
||||
text = ev.clipboardData.getData('text/plain');
|
||||
dispatchPaste(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind to right-click event and allow right-click copy and paste.
|
||||
*
|
||||
* **Logic**
|
||||
* If text is selected and right-click happens on selected text, then
|
||||
* do nothing to allow seamless copying.
|
||||
* If no text is selected or right-click is outside of the selection
|
||||
* area, then bring the terminal's input below the cursor, in order to
|
||||
* trigger the event on the textarea and allow-right click paste, without
|
||||
* caring about disappearing selection.
|
||||
* @param {MouseEvent} ev The original right click event to be handled
|
||||
* @param {Terminal} term The terminal on which to apply the handled paste event
|
||||
*/
|
||||
export function rightClickHandler(ev: MouseEvent, term: ITerminal) {
|
||||
let s = document.getSelection(),
|
||||
selectedText = prepareTextForClipboard(s.toString()),
|
||||
clickIsOnSelection = false,
|
||||
x = ev.clientX,
|
||||
y = ev.clientY;
|
||||
|
||||
if (s.rangeCount) {
|
||||
let r = s.getRangeAt(0),
|
||||
cr = r.getClientRects();
|
||||
|
||||
for (let i = 0; i < cr.length; i++) {
|
||||
let rect = cr[i];
|
||||
|
||||
clickIsOnSelection = (
|
||||
(x > rect.left) && (x < rect.right) &&
|
||||
(y > rect.top) && (y < rect.bottom)
|
||||
);
|
||||
|
||||
if (clickIsOnSelection) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If we clicked on selection and selection is not a single space,
|
||||
// then mark the right click as copy-only. We check for the single
|
||||
// space selection, as this can happen when clicking on an
|
||||
// and there is not much pointing in copying a single space.
|
||||
if (selectedText.match(/^\s$/) || !selectedText.length) {
|
||||
clickIsOnSelection = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Bring textarea at the cursor position
|
||||
if (!clickIsOnSelection) {
|
||||
term.textarea.style.position = 'fixed';
|
||||
term.textarea.style.width = '20px';
|
||||
term.textarea.style.height = '20px';
|
||||
term.textarea.style.left = (x - 10) + 'px';
|
||||
term.textarea.style.top = (y - 10) + 'px';
|
||||
term.textarea.style.zIndex = '1000';
|
||||
term.textarea.focus();
|
||||
|
||||
// Reset the terminal textarea's styling
|
||||
setTimeout(function () {
|
||||
term.textarea.style.position = null;
|
||||
term.textarea.style.width = null;
|
||||
term.textarea.style.height = null;
|
||||
term.textarea.style.left = null;
|
||||
term.textarea.style.top = null;
|
||||
term.textarea.style.zIndex = null;
|
||||
}, 4);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
var assert = require('chai').assert;
|
||||
var Terminal = require('../../xterm');
|
||||
|
||||
describe('xterm.js addons', function() {
|
||||
it('should load addons with Terminal.loadAddon', function () {
|
||||
Terminal.loadAddon('attach');
|
||||
// Test that addon was loaded successfully
|
||||
assert.equal(typeof Terminal.prototype.attach, 'function');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,133 @@
|
||||
var glob = require('glob');
|
||||
var fs = require('fs');
|
||||
var os = require('os');
|
||||
var pty = require('node-pty');
|
||||
var sleep = require('sleep');
|
||||
var Terminal = require('../xterm');
|
||||
|
||||
if (os.platform() === 'win32') {
|
||||
// Skip tests on Windows since pty.open isn't supported
|
||||
return;
|
||||
}
|
||||
|
||||
var CONSOLE_LOG = console.log;
|
||||
|
||||
// expect files need terminal at 80x25!
|
||||
var COLS = 80;
|
||||
var ROWS = 25;
|
||||
|
||||
/** some helpers for pty interaction */
|
||||
// we need a pty in between to get the termios decorations
|
||||
// for the basic test cases a raw pty device is enough
|
||||
var primitive_pty = pty.native.open(COLS, ROWS);
|
||||
|
||||
// fake sychronous pty write - read
|
||||
// we just pipe the data from slave to master as a child program would do
|
||||
// pty.js opens pipe fds with O_NONBLOCK
|
||||
// just wait 10ms instead of setting fds to blocking mode
|
||||
function pty_write_read(s) {
|
||||
fs.writeSync(primitive_pty.slave, s);
|
||||
sleep.usleep(10000);
|
||||
var b = Buffer(64000);
|
||||
var bytes = fs.readSync(primitive_pty.master, b, 0, 64000);
|
||||
return b.toString('utf8', 0, bytes);
|
||||
}
|
||||
|
||||
// make sure raw pty is at x=0 and has no pending data
|
||||
function pty_reset() {
|
||||
pty_write_read('\r\n');
|
||||
}
|
||||
|
||||
/* debug helpers */
|
||||
// generate colorful noisy output to compare xterm and emulator cell states
|
||||
function formatError(in_, out_, expected) {
|
||||
function addLineNumber(start, color) {
|
||||
var counter = start || 0;
|
||||
return function(s) {
|
||||
counter += 1;
|
||||
return '\x1b[33m' + (' ' + counter).slice(-2) + color + s;
|
||||
}
|
||||
}
|
||||
var line80 = '12345678901234567890123456789012345678901234567890123456789012345678901234567890';
|
||||
var s = '';
|
||||
s += '\n\x1b[34m' + JSON.stringify(in_);
|
||||
s += '\n\x1b[33m ' + line80 + '\n';
|
||||
s += out_.split('\n').map(addLineNumber(0, '\x1b[31m')).join('\n');
|
||||
s += '\n\x1b[33m ' + line80 + '\n';
|
||||
s += expected.split('\n').map(addLineNumber(0, '\x1b[32m')).join('\n');
|
||||
return s;
|
||||
}
|
||||
|
||||
// simple debug output of terminal cells
|
||||
function terminalToString(term) {
|
||||
var result = '';
|
||||
var line_s = '';
|
||||
for (var line = term.ybase; line < term.ybase + term.rows; line++) {
|
||||
line_s = '';
|
||||
for (var cell=0; cell<term.cols; ++cell) {
|
||||
line_s += term.lines.get(line)[cell][1];
|
||||
}
|
||||
// rtrim empty cells as xterm does
|
||||
line_s = line_s.replace(/\s+$/, '');
|
||||
result += line_s;
|
||||
result += '\n';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** tests */
|
||||
describe('xterm output comparison', function() {
|
||||
var xterm;
|
||||
|
||||
beforeEach(function () {
|
||||
xterm = new Terminal(COLS, ROWS);
|
||||
xterm.refresh = function() {};
|
||||
xterm.viewport = {
|
||||
syncScrollArea: function() {}
|
||||
};
|
||||
});
|
||||
|
||||
// omit stack trace for escape sequence files
|
||||
Error.stackTraceLimit = 0;
|
||||
var files = glob.sync('**/escape_sequence_files/*.in');
|
||||
// only successful tests for now
|
||||
var skip = [
|
||||
10, 16, 17, 19, 32, 33, 34, 35, 36, 39,
|
||||
40, 42, 43, 44, 45, 46, 47, 48, 49, 50,
|
||||
51, 52, 54, 55, 56, 57, 58, 59, 60, 61,
|
||||
63, 68
|
||||
];
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
if (skip.indexOf(i) >= 0) {
|
||||
continue;
|
||||
}
|
||||
(function(filename) {
|
||||
it(filename.split('/').slice(-1)[0], function () {
|
||||
pty_reset();
|
||||
var in_file = fs.readFileSync(filename, 'utf8');
|
||||
var from_pty = pty_write_read(in_file);
|
||||
// uncomment this to get log from terminal
|
||||
//console.log = function(){};
|
||||
|
||||
// Perform a synchronous .write(data)
|
||||
xterm.writeBuffer.push(from_pty);
|
||||
xterm.innerWrite();
|
||||
|
||||
var from_emulator = terminalToString(xterm);
|
||||
console.log = CONSOLE_LOG;
|
||||
var expected = fs.readFileSync(filename.split('.')[0] + '.text', 'utf8');
|
||||
// Some of the tests have whitespace on the right of lines, we trim all the linex
|
||||
// from xterm.js so ignore this for now at least.
|
||||
var expectedRightTrimmed = expected.split('\n').map(function (l) {
|
||||
return l.replace(/\s+$/, '');
|
||||
}).join('\n');
|
||||
if (from_emulator != expectedRightTrimmed) {
|
||||
// uncomment to get noisy output
|
||||
throw new Error(formatError(in_file, from_emulator, expected));
|
||||
// throw new Error('mismatch');
|
||||
}
|
||||
});
|
||||
})(files[i]);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
All tests are made for 80x25 terminal. Make sure to run tests with 80x25.
|
||||
|
||||
Create .text files from xterm (expected output)
|
||||
- open xterm
|
||||
- resize xterm to 80x25
|
||||
- run `python run_tests.py`
|
||||
- copy & paste whole window output into editor
|
||||
- add 26th empty line (due to line handling in toString) - not a bug, a feature ;)
|
||||
- advance to next test with ^D
|
||||
|
||||
|
||||
Known problems
|
||||
##############
|
||||
|
||||
|
||||
t0031-HBP:
|
||||
- no documentation at all about CSIj found - skipping
|
||||
|
||||
t0050-ICH:
|
||||
- bug in xterm? (cant ICH last real char, always sticks to last col)
|
||||
- text used from https://github.com/MarkLodato/vt100-parser/blob/master/test/t0050-ICH.text
|
||||
@@ -0,0 +1,6 @@
|
||||
!"#$%&'()*+,-./
|
||||
0123456789:;<=>?
|
||||
@ABCDEFGHIJKLMNO
|
||||
PQRSTUVWXYZ[\]^_
|
||||
`abcdefghijklmno
|
||||
pqrstuvwxyz{|}~
|
||||
@@ -0,0 +1,25 @@
|
||||
!"#$%&'()*+,-./
|
||||
0123456789:;<=>?
|
||||
@ABCDEFGHIJKLMNO
|
||||
PQRSTUVWXYZ[\]^_
|
||||
`abcdefghijklmno
|
||||
pqrstuvwxyz{|}~
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
|
||||
!
|
||||
"
|
||||
#
|
||||
$
|
||||
%
|
||||
&
|
||||
'
|
||||
(
|
||||
)
|
||||
*
|
||||
+
|
||||
,
|
||||
-
|
||||
.
|
||||
/
|
||||
0
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
:
|
||||
;
|
||||
<
|
||||
=
|
||||
>
|
||||
?
|
||||
@
|
||||
A
|
||||
B
|
||||
C
|
||||
D
|
||||
E
|
||||
F
|
||||
G
|
||||
H
|
||||
I
|
||||
J
|
||||
K
|
||||
L
|
||||
M
|
||||
N
|
||||
O
|
||||
P
|
||||
Q
|
||||
R
|
||||
S
|
||||
T
|
||||
U
|
||||
V
|
||||
W
|
||||
X
|
||||
Y
|
||||
Z
|
||||
[
|
||||
\
|
||||
]
|
||||
^
|
||||
_
|
||||
`
|
||||
a
|
||||
b
|
||||
c
|
||||
d
|
||||
e
|
||||
f
|
||||
g
|
||||
h
|
||||
i
|
||||
j
|
||||
k
|
||||
l
|
||||
m
|
||||
n
|
||||
o
|
||||
p
|
||||
q
|
||||
r
|
||||
s
|
||||
t
|
||||
u
|
||||
v
|
||||
w
|
||||
x
|
||||
y
|
||||
z
|
||||
{
|
||||
|
|
||||
}
|
||||
~
|
||||
@@ -0,0 +1,25 @@
|
||||
g
|
||||
h
|
||||
i
|
||||
j
|
||||
k
|
||||
l
|
||||
m
|
||||
n
|
||||
o
|
||||
p
|
||||
q
|
||||
r
|
||||
s
|
||||
t
|
||||
u
|
||||
v
|
||||
w
|
||||
x
|
||||
y
|
||||
z
|
||||
{
|
||||
|
|
||||
}
|
||||
~
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
abcdefghijklmnopqrstuvwxyz0123456789
|
||||
@@ -0,0 +1,25 @@
|
||||
abcdefghijklmnopqrstuvwxyz0123456789
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
a
|
||||
ab
|
||||
abc
|
||||
abcd
|
||||
abcde
|
||||
abcdef
|
||||
abcdefg
|
||||
abcdefgh
|
||||
abcdefghi
|
||||
abcdefghij
|
||||
abcdefghijk
|
||||
abcdefghijkl
|
||||
abcdefghijklm
|
||||
abcdefghijklmn
|
||||
abcdefghijklmno
|
||||
abcdefghijklmnop
|
||||
abcdefghijklmnopq
|
||||
abcdefghijklmnopqr
|
||||
abcdefghijklmnopqrs
|
||||
abcdefghijklmnopqrst
|
||||
abcdefghijklmnopqrstu
|
||||
abcdefghijklmnopqrstuv
|
||||
abcdefghijklmnopqrstuvw
|
||||
abcdefghijklmnopqrstuvwx
|
||||
abcdefghijklmnopqrstuvwxy
|
||||
abcdefghijklmnopqrstuvwxyz
|
||||
abcdefghijklmnopqrstuvwxyzA
|
||||
abcdefghijklmnopqrstuvwxyzAB
|
||||
abcdefghijklmnopqrstuvwxyzABC
|
||||
abcdefghijklmnopqrstuvwxyzABCD
|
||||
abcdefghijklmnopqrstuvwxyzABCDE
|
||||
abcdefghijklmnopqrstuvwxyzABCDEF
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFG
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGH
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHI
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJ
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJK
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKL
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLM
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNO
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQ
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQR
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRS
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRST
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTU
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUV
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXY
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345678
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890a
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890ab
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abc
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcd
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcde
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdef
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefg
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefgh
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghi
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghij
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijk
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijkl
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklm
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmn
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmno
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnop
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopqr
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopqrs
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopqrst
|
||||
@@ -0,0 +1,25 @@
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890a
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890ab
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abc
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcd
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcde
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdef
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefg
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefgh
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghi
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghij
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijk
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijkl
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklm
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmn
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmno
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnop
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq
|
||||
r
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq
|
||||
rs
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq
|
||||
rst
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20
|
||||
21
|
||||
22
|
||||
23
|
||||
24
|
||||
25
|
||||
26
|
||||
@@ -0,0 +1,25 @@
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
10
|
||||
11
|
||||
12
|
||||
13
|
||||
14
|
||||
15
|
||||
16
|
||||
17
|
||||
18
|
||||
19
|
||||
20
|
||||
21
|
||||
22
|
||||
23
|
||||
24
|
||||
25
|
||||
26
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
a
|
||||
b
|
||||
c
|
||||
d
|
||||
e
|
||||
f
|
||||
g
|
||||
h
|
||||
i
|
||||
j
|
||||
k
|
||||
l
|
||||
m
|
||||
n
|
||||
o
|
||||
p
|
||||
q
|
||||
r
|
||||
s
|
||||
t
|
||||
u
|
||||
v
|
||||
w
|
||||
x
|
||||
y
|
||||
z
|
||||
A
|
||||
B
|
||||
C
|
||||
D
|
||||
E
|
||||
F
|
||||
G
|
||||
H
|
||||
I
|
||||
J
|
||||
K
|
||||
L
|
||||
M
|
||||
N
|
||||
O
|
||||
P
|
||||
Q
|
||||
R
|
||||
S
|
||||
T
|
||||
U
|
||||
V
|
||||
W
|
||||
X
|
||||
Y
|
||||
Z
|
||||
0
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
0
|
||||
a
|
||||
b
|
||||
c
|
||||
d
|
||||
e
|
||||
f
|
||||
g
|
||||
h
|
||||
i
|
||||
j
|
||||
k
|
||||
l
|
||||
m
|
||||
n
|
||||
o
|
||||
p
|
||||
q
|
||||
r
|
||||
s
|
||||
t
|
||||
@@ -0,0 +1,25 @@
|
||||
7
|
||||
8
|
||||
9
|
||||
0
|
||||
a
|
||||
b
|
||||
c
|
||||
d
|
||||
e
|
||||
f
|
||||
g
|
||||
h
|
||||
i
|
||||
j
|
||||
k
|
||||
l
|
||||
m
|
||||
n
|
||||
o
|
||||
p
|
||||
q
|
||||
r
|
||||
s
|
||||
t
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
1␍x
|
||||
2␍x
|
||||
3␍x
|
||||
4␍x
|
||||
5␍x
|
||||
6␍x
|
||||
7␍x
|
||||
@@ -0,0 +1,25 @@
|
||||
x
|
||||
x2
|
||||
x 3
|
||||
x 4
|
||||
x 5
|
||||
x 6
|
||||
x 7
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
b␍a
|
||||
@@ -0,0 +1,25 @@
|
||||
a b
|
||||
a b
|
||||
a b
|
||||
a b
|
||||
a b
|
||||
a b
|
||||
a b
|
||||
a b
|
||||
a b
|
||||
a b
|
||||
a b
|
||||
a b
|
||||
a b
|
||||
a b
|
||||
a b
|
||||
a b
|
||||
a b
|
||||
a b
|
||||
a b
|
||||
a b
|
||||
|
||||
a
|
||||
|
||||
ab
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
aDbDcDdDeDfDgDhDiDjDkDlDmDnDoDpDqDrDsDtDuDvDwDxDyDzDADBDCDDDEDFDGDHDIDJDKDLDMDNDODPDQDRDSDTDUDVDWDXDYDZD0D1D2D3D4D5D6D7D8D9D0DaDbDcDdDeDfDgDhDiDjDkDlDmDnDoDpDqDrDsDt
|
||||
@@ -0,0 +1,25 @@
|
||||
7
|
||||
8
|
||||
9
|
||||
0
|
||||
a
|
||||
b
|
||||
c
|
||||
d
|
||||
e
|
||||
f
|
||||
g
|
||||
h
|
||||
i
|
||||
j
|
||||
k
|
||||
l
|
||||
m
|
||||
n
|
||||
o
|
||||
p
|
||||
q
|
||||
r
|
||||
s
|
||||
t
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
0 space:
|
||||
1 space:
|
||||
2 space:
|
||||
3 space:
|
||||
70 space:
|
||||
71 space:
|
||||
72 space:
|
||||
73 space:
|
||||
@@ -0,0 +1,25 @@
|
||||
0 space:
|
||||
1 space:
|
||||
2 space:
|
||||
3 space:
|
||||
70 space:
|
||||
71 space:
|
||||
72 space:
|
||||
|
||||
73 space:
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
abcdefghijklmnopqrstuvwxyz!
|
||||
abc@
|
||||
#
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnop$
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopq%
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopqr^
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890abcdefghijklmnopqrs&
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user