Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee88fca8c0 | ||
|
|
9b3fbc9b84 | ||
|
|
187f072f43 | ||
|
|
3ab2376517 | ||
|
|
a73fe62b7a | ||
|
|
350f6cfea1 | ||
|
|
2936f80013 | ||
|
|
dec9f6d254 | ||
|
|
bd6676d3b6 | ||
|
|
d9309d33af | ||
|
|
bb9802e754 | ||
|
|
eec804dd7c | ||
|
|
ff6d3a0768 | ||
|
|
94d37bbe4c | ||
|
|
36136a4c29 | ||
|
|
8b273e6939 | ||
|
|
2340fa1c91 | ||
|
|
70051da2ab | ||
|
|
5659bf3283 | ||
|
|
8de8ac10d4 | ||
|
|
46c93f1b63 | ||
|
|
e3a9d0d14f | ||
|
|
0a4b403f6a | ||
|
|
b94b4f5353 | ||
|
|
d84576e160 | ||
|
|
a0903fbf9b | ||
|
|
4b29b8a9c6 | ||
|
|
c994c67dd9 | ||
|
|
0b09f0bafd | ||
|
|
54bb0ee189 | ||
|
|
28bcd88fb0 | ||
|
|
bee4b0ff06 | ||
|
|
559b3c1392 | ||
|
|
d3c9f70bec | ||
|
|
ff74caf50d | ||
|
|
3d941f2fdf | ||
|
|
93aab95f7b | ||
|
|
079702282c | ||
|
|
db8e449a8a | ||
|
|
a2480bae51 | ||
|
|
cd501f9dcc | ||
|
|
354f3e03a2 | ||
|
|
00ef60ebaf | ||
|
|
4da5d557a5 | ||
|
|
a1dc85ee57 | ||
|
|
554b4a8224 | ||
|
|
3f659a15da | ||
|
|
dd5b295219 | ||
|
|
9d0200252b | ||
|
|
d67faff92a | ||
|
|
c50c134e25 | ||
|
|
74ea558bfc | ||
|
|
c6f93701f5 | ||
|
|
8a4772ca39 | ||
|
|
66c80d8b78 | ||
|
|
0cb38cfdcb | ||
|
|
c669c3fba7 | ||
|
|
8860726c68 | ||
|
|
f64724f54f | ||
|
|
fd222b58b5 | ||
|
|
83ba481e3d | ||
|
|
7d5bbc341f | ||
|
|
519e44d30e | ||
|
|
3bb2c158fd | ||
|
|
5ada5f0c0c | ||
|
|
ea1fcc6a5f | ||
|
|
19c14b076b | ||
|
|
676aabc3ed | ||
|
|
5c33e4008d | ||
|
|
772e446192 | ||
|
|
66be7797cf | ||
|
|
3261c53c08 | ||
|
|
5bab94730f | ||
|
|
801aeb8462 | ||
|
|
cbe5abcf27 | ||
|
|
72dbd43be6 | ||
|
|
7145130835 | ||
|
|
b34289cc44 | ||
|
|
fe2b1c0d43 | ||
|
|
4de6f1c255 | ||
|
|
8baff9bade | ||
|
|
f8f7e7e4c1 | ||
|
|
7523505af1 | ||
|
|
d94b119e9d | ||
|
|
3cab8511e4 | ||
|
|
7d3f5375a8 | ||
|
|
b2c4ed638b | ||
|
|
2804bfa8b7 | ||
|
|
64ac67026a | ||
|
|
5a74ce2e59 | ||
|
|
f4a56389cd | ||
|
|
52b64beefc | ||
|
|
ec1234e8d8 | ||
|
|
b7695d2fd6 | ||
|
|
4c6dc45d20 | ||
|
|
5cb048094e | ||
|
|
e3cccd619b | ||
|
|
88f0b91056 | ||
|
|
78ddb9feba | ||
|
|
f68ff2a455 | ||
|
|
04e2bdd6ec | ||
|
|
84fa6be370 | ||
|
|
af2f744186 | ||
|
|
5f4b719444 | ||
|
|
9616851e52 | ||
|
|
4645fda126 | ||
|
|
057a8f6f1b | ||
|
|
e6917d1933 | ||
|
|
6535995753 | ||
|
|
77ed5ba7ed | ||
|
|
bc86b7804c | ||
|
|
d5f4ae53c9 | ||
|
|
b898e016f3 | ||
|
|
712f0ae2a7 | ||
|
|
c553107d23 | ||
|
|
b127436cc4 | ||
|
|
b61ac11a76 | ||
|
|
b22d7f1140 | ||
|
|
925aaf18a1 | ||
|
|
797ed47791 | ||
|
|
4128a6e2f1 | ||
|
|
69f34be4b8 | ||
|
|
1c3da7e2e6 | ||
|
|
5c7644adc0 | ||
|
|
f974010aa3 | ||
|
|
d4d18f741f | ||
|
|
d97b54b9be | ||
|
|
a7b94fbe1a | ||
|
|
2ab9de91fe | ||
|
|
fd6bf6d667 | ||
|
|
38e81f053f | ||
|
|
c6f1b456f5 | ||
|
|
8484978ef0 | ||
|
|
c4919b9a6b | ||
|
|
02e4db4c82 | ||
|
|
5200766617 | ||
|
|
8d9badb588 | ||
|
|
fc534a1067 | ||
|
|
f68ef19868 | ||
|
|
0a2e40502f | ||
|
|
e843a2f61e | ||
|
|
d95a633f16 | ||
|
|
6f41aad2ef | ||
|
|
e76cbd9c3f | ||
|
|
0c8b299996 | ||
|
|
86420d2535 | ||
|
|
9e4dcdaf91 | ||
|
|
45e7bf274a | ||
|
|
62cf413d54 | ||
|
|
3e0a270891 | ||
|
|
22d79eef26 | ||
|
|
31d806afce | ||
|
|
1e9ca83120 | ||
|
|
3d9a33bb69 | ||
|
|
ea989ed691 | ||
|
|
ee2c5969fa | ||
|
|
8e151ee6ef | ||
|
|
dc904b27a2 | ||
|
|
4de539a1ff | ||
|
|
913de3158e | ||
|
|
049ca653ed | ||
|
|
246d137c2c | ||
|
|
99b1a7a49b | ||
|
|
9fa068f574 | ||
|
|
eb5f964bcb | ||
|
|
91337d7e57 | ||
|
|
cb555d5297 | ||
|
|
50dd3cb841 | ||
|
|
b7750cbf91 | ||
|
|
dde47269de | ||
|
|
bc3f2a12c0 | ||
|
|
bdb112b23c | ||
|
|
214ccd14ac | ||
|
|
e5f3820285 | ||
|
|
f75b4cfa93 | ||
|
|
f5d651dda1 | ||
|
|
a90254e778 | ||
|
|
a6bcfcfba4 | ||
|
|
78ae1d09f7 | ||
|
|
1132da8563 | ||
|
|
199b9ceb6d | ||
|
|
8e41d22f8d | ||
|
|
57bd61697c | ||
|
|
3e8b8dc6ef | ||
|
|
b4bc830cee | ||
|
|
2e43f615f6 | ||
|
|
5f2d1d2b4e | ||
|
|
4ee305fa21 | ||
|
|
4b3e16dbd0 | ||
|
|
a7c836e42c | ||
|
|
886c057d0f | ||
|
|
038704786d | ||
|
|
c61076830f | ||
|
|
02fccfbdc7 | ||
|
|
b250cedf81 | ||
|
|
7243e330fc | ||
|
|
17a7fb2a6e | ||
|
|
d3d8ec3b20 | ||
|
|
b6074df4bf | ||
|
|
f3cc9469a7 | ||
|
|
a9d2b8a55c | ||
|
|
73e235d9f8 | ||
|
|
17ec85e39e | ||
|
|
723c64e481 | ||
|
|
faa60be6fb | ||
|
|
e95f9815d8 | ||
|
|
9b62d8a01a | ||
|
|
1c206c2c27 | ||
|
|
782797ca11 | ||
|
|
b34ee5157e | ||
|
|
ae37c43cd4 | ||
|
|
272b1d2194 | ||
|
|
260e2dab16 | ||
|
|
7c78390e2b | ||
|
|
f5a4eed27c | ||
|
|
b0768f18ca | ||
|
|
4d1e532069 | ||
|
|
5beb7f52c2 | ||
|
|
eaaf9b7af1 | ||
|
|
3c9687decf | ||
|
|
a5e269a3f9 | ||
|
|
30d84b1202 | ||
|
|
0aa1079cd0 | ||
|
|
1091255d2c | ||
|
|
56e63d762b | ||
|
|
f3c8b8f395 | ||
|
|
47be9a2df4 | ||
|
|
aee1629c25 | ||
|
|
8587eea54c | ||
|
|
d7ed109f9d | ||
|
|
84dfe8fccc | ||
|
|
6904dc5009 | ||
|
|
b89227d365 | ||
|
|
6c01630090 | ||
|
|
10282e4cb1 | ||
|
|
8518b354a7 | ||
|
|
00a8b80ef7 | ||
|
|
1a0a56c1f2 | ||
|
|
055267ee9c | ||
|
|
6e9040e6ba | ||
|
|
a32776e2af | ||
|
|
1d6d6eb716 | ||
|
|
881a4a0dbb | ||
|
|
003cf96910 | ||
|
|
3cb87af315 | ||
|
|
edee1a1067 | ||
|
|
e32d8774eb | ||
|
|
49d5608fa6 | ||
|
|
56bfdb64d4 | ||
|
|
9513c231c9 | ||
|
|
dd92161da5 | ||
|
|
55571a1ae8 | ||
|
|
c112990d8f | ||
|
|
23dba6aebd | ||
|
|
f669b8ed1d | ||
|
|
9a22cbe44b | ||
|
|
883ddba934 | ||
|
|
8f7295b8a2 | ||
|
|
0ba97f6941 | ||
|
|
d979c077a0 | ||
|
|
4e18a78c02 | ||
|
|
1d6e8abcca | ||
|
|
82a65be446 | ||
|
|
0778a823d1 | ||
|
|
eca36458b3 | ||
|
|
3504e2ebd5 | ||
|
|
6c22e3df6b | ||
|
|
33bf765b80 | ||
|
|
d960793f0c | ||
|
|
4975f389ff | ||
|
|
4649ad7e7b | ||
|
|
a425c325b7 | ||
|
|
c77025b18c | ||
|
|
c6abc85810 | ||
|
|
710223ab27 | ||
|
|
ca9df42a73 | ||
|
|
9f033cb1d2 | ||
|
|
9613f36280 | ||
|
|
41673f2c20 | ||
|
|
6c98f82ffe | ||
|
|
a90ef52bda | ||
|
|
86884d2d88 | ||
|
|
47c486e1be | ||
|
|
e07b9684bb | ||
|
|
10fb48869e | ||
|
|
9f3746a3a1 | ||
|
|
50fc84f7d7 | ||
|
|
3e0f0331b3 | ||
|
|
b918f0d34a | ||
|
|
f0e3159ece | ||
|
|
ecfc7336c5 | ||
|
|
aec8c68bf6 | ||
|
|
0fb63561fb | ||
|
|
106ea62b4c | ||
|
|
e73bf11849 | ||
|
|
8d9eda5d0a | ||
|
|
cf0b7ce2d0 | ||
|
|
60419a7cc6 | ||
|
|
1b12b2d15d | ||
|
|
b5844990c4 | ||
|
|
2652768310 | ||
|
|
1f8a6bece4 | ||
|
|
8817ddf020 | ||
|
|
5d61956aff | ||
|
|
83ece031a7 | ||
|
|
566fad4108 | ||
|
|
a4aa111eaa | ||
|
|
f6ed184d49 | ||
|
|
c1820501a8 | ||
|
|
c7f50c5c28 | ||
|
|
371c96e1da | ||
|
|
11bd8b7eb1 | ||
|
|
cb418eb8d1 | ||
|
|
f811fb937e | ||
|
|
2a6ccb8009 | ||
|
|
bf49f99810 | ||
|
|
e027a5f97b | ||
|
|
d1ecb571d8 | ||
|
|
1e3937830f | ||
|
|
38b2fcb460 | ||
|
|
c7ed42226f | ||
|
|
d0dfad4c92 | ||
|
|
c66123b91f | ||
|
|
8d912e891e | ||
|
|
23fd6b8e60 | ||
|
|
0320e6e22a | ||
|
|
9e70d8fc84 | ||
|
|
27e28eccac | ||
|
|
0cb0313fc9 | ||
|
|
94a34c3703 | ||
|
|
c04c836f12 | ||
|
|
c5a9d2c52d | ||
|
|
c0050672ab | ||
|
|
705f94518f | ||
|
|
a096217526 | ||
|
|
795768667e | ||
|
|
5e9a8c6321 | ||
|
|
03df0565bd | ||
|
|
3ef6856b36 | ||
|
|
48e9f33263 | ||
|
|
9559dd47da | ||
|
|
ad7f8c303c | ||
|
|
edb7a0edda | ||
|
|
aedcbba211 | ||
|
|
ab63b51681 | ||
|
|
63f5d0c266 | ||
|
|
349ebd3846 | ||
|
|
ce496bc389 | ||
|
|
be1af82ee0 | ||
|
|
11bfb03add | ||
|
|
b89d4b102e | ||
|
|
0abd121653 | ||
|
|
eeb095b095 | ||
|
|
266a289520 | ||
|
|
3c1d6b575a | ||
|
|
bd08862074 | ||
|
|
70e0c0385a | ||
|
|
454cf1fb26 | ||
|
|
6d1029b3c7 | ||
|
|
f13f8fb38a | ||
|
|
2447064604 | ||
|
|
85a2589c0c | ||
|
|
2c821155ae | ||
|
|
c040be1ad2 | ||
|
|
2b91bb83fa | ||
|
|
5738f4545f | ||
|
|
01432a31d6 | ||
|
|
798a7fd52e | ||
|
|
cee121f414 | ||
|
|
a4fe871865 | ||
|
|
d85c80df95 | ||
|
|
95d01dec52 | ||
|
|
1506097faa | ||
|
|
dc541c2664 | ||
|
|
694da35f99 | ||
|
|
3c7697420c | ||
|
|
4a407dbe76 | ||
|
|
2479660028 | ||
|
|
612b922eeb | ||
|
|
ea2336b68e | ||
|
|
0c953a88c3 | ||
|
|
a551c69e9e | ||
|
|
7e7be15ab8 | ||
|
|
cb0242782a | ||
|
|
04655b1db5 | ||
|
|
7ce47c474a | ||
|
|
b6259d3e7e | ||
|
|
3e6bcc09b3 | ||
|
|
fbc34b649c | ||
|
|
4a0cb00450 | ||
|
|
0cf32d9b4d | ||
|
|
f6bf7dcd92 | ||
|
|
4f59d1561a | ||
|
|
86af9aa59d | ||
|
|
1cd4f79347 | ||
|
|
f928c7e156 | ||
|
|
0e6611b528 | ||
|
|
afc39106c3 | ||
|
|
06c8de381b | ||
|
|
ac0e1aeb19 | ||
|
|
1171a20262 | ||
|
|
8fa729a702 | ||
|
|
53eb54e656 | ||
|
|
3b9047be0f | ||
|
|
2562311606 | ||
|
|
98d0760898 | ||
|
|
4c943ddb0d | ||
|
|
61432ab65d | ||
|
|
0ccdc8d1ca | ||
|
|
8a3366b1eb | ||
|
|
c669cfbb23 | ||
|
|
45749f6017 | ||
|
|
bc6bb417ac | ||
|
|
5342ba6b10 | ||
|
|
a1dc0d1936 | ||
|
|
15f1759580 | ||
|
|
fb25be3ecd | ||
|
|
fbb8c6273b | ||
|
|
289014b32e | ||
|
|
9b91e3d5e2 | ||
|
|
d57e099e6e | ||
|
|
c5d30a9c63 | ||
|
|
8ee08a3be7 | ||
|
|
5af1554bbf | ||
|
|
e226a111f9 | ||
|
|
d5bc65ec0e | ||
|
|
106d74caa2 | ||
|
|
825784e0ec | ||
|
|
80c6c7f399 | ||
|
|
156563e832 | ||
|
|
b92260870a | ||
|
|
04f2b6e9ff | ||
|
|
a9862a1a6f | ||
|
|
2e2acd77f3 | ||
|
|
75e8999a88 | ||
|
|
7bf62a3078 | ||
|
|
0030ed214d | ||
|
|
f63f1f7b95 | ||
|
|
6daa3f4499 | ||
|
|
4e6151d011 | ||
|
|
18330b47ed | ||
|
|
864f1a9be9 | ||
|
|
1b6e24d94a | ||
|
|
617e702a94 | ||
|
|
6c0de23915 | ||
|
|
869d791f44 | ||
|
|
e5499711c3 | ||
|
|
3fbd215854 | ||
|
|
b7132e7fa7 | ||
|
|
7f201e3e6b | ||
|
|
cebbac8265 | ||
|
|
227a1f5aa3 | ||
|
|
07bdc10245 | ||
|
|
1b9b6f0cbf | ||
|
|
1f6ebb3dac | ||
|
|
3abb66500d | ||
|
|
b6b2e67180 | ||
|
|
db9d950fb4 | ||
|
|
bdd8b8d32f | ||
|
|
d8bc7ceaff | ||
|
|
6a0f7b0713 | ||
|
|
3ac8a89f9b | ||
|
|
fcec9c793c | ||
|
|
2140930793 | ||
|
|
2c16a7e0d7 | ||
|
|
325559e5d1 |
@@ -1,16 +0,0 @@
|
||||
node_modules/
|
||||
*.swp
|
||||
.lock-wscript
|
||||
lib/
|
||||
Makefile.gyp
|
||||
*.Makefile
|
||||
*.target.gyp.mk
|
||||
*.node
|
||||
example/*.log
|
||||
docs/
|
||||
npm-debug.log
|
||||
/.idea/
|
||||
.env
|
||||
build/
|
||||
.vscode/
|
||||
.git/
|
||||
+12
-9
@@ -12,14 +12,21 @@
|
||||
"test/api/tsconfig.json",
|
||||
"test/benchmark/tsconfig.json",
|
||||
"addons/xterm-addon-attach/src/tsconfig.json",
|
||||
"addons/xterm-addon-attach/test/tsconfig.json",
|
||||
"addons/xterm-addon-fit/src/tsconfig.json",
|
||||
"addons/xterm-addon-fit/test/tsconfig.json",
|
||||
"addons/xterm-addon-ligatures/src/tsconfig.json",
|
||||
"addons/xterm-addon-search/src/tsconfig.json",
|
||||
"addons/xterm-addon-unicode11/src/tsconfig.json",
|
||||
"addons/xterm-addon-web-links/src/tsconfig.json",
|
||||
"addons/xterm-addon-webgl/src/tsconfig.json",
|
||||
"addons/xterm-addon-search/test/tsconfig.json",
|
||||
"addons/xterm-addon-serialize/src/tsconfig.json",
|
||||
"addons/xterm-addon-serialize/test/tsconfig.json",
|
||||
"addons/xterm-addon-serialize/benchmark/tsconfig.json",
|
||||
"addons/xterm-addon-ligatures/src/tsconfig.json"
|
||||
"addons/xterm-addon-unicode11/src/tsconfig.json",
|
||||
"addons/xterm-addon-unicode11/test/tsconfig.json",
|
||||
"addons/xterm-addon-web-links/src/tsconfig.json",
|
||||
"addons/xterm-addon-web-links/test/tsconfig.json",
|
||||
"addons/xterm-addon-webgl/src/tsconfig.json",
|
||||
"addons/xterm-addon-webgl/test/tsconfig.json"
|
||||
],
|
||||
"sourceType": "module"
|
||||
},
|
||||
@@ -32,6 +39,7 @@
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"no-extra-semi": "error",
|
||||
"@typescript-eslint/array-type": [
|
||||
"warn",
|
||||
{
|
||||
@@ -39,7 +47,6 @@
|
||||
"readonly": "generic"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/class-name-casing": "warn",
|
||||
"@typescript-eslint/consistent-type-definitions": "warn",
|
||||
"@typescript-eslint/explicit-function-return-type": [
|
||||
"warn",
|
||||
@@ -60,10 +67,6 @@
|
||||
"warn",
|
||||
2
|
||||
],
|
||||
"@typescript-eslint/interface-name-prefix": [
|
||||
"warn",
|
||||
"always"
|
||||
],
|
||||
"@typescript-eslint/member-delimiter-style": [
|
||||
"warn",
|
||||
{
|
||||
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
FROM node:8
|
||||
MAINTAINER Paris Kasidiaris <paris@sourcelair.com>
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Set an entrypoint, to automatically install node modules
|
||||
ENTRYPOINT ["/bin/bash", "-c", "if [[ ! -d node_modules ]]; then npm install; fi; exec \"${@:0}\";"]
|
||||
CMD ["npm", "run", "start"]
|
||||
|
||||
# First, install dependencies to improve layer caching
|
||||
COPY package.json /usr/src/app/
|
||||
RUN npm install
|
||||
|
||||
# Add the code
|
||||
COPY . /usr/src/app
|
||||
|
||||
# Run the tests and build, to make sure everything is working nicely
|
||||
RUN npm run build && npm run test
|
||||
@@ -87,7 +87,7 @@ The xterm.js team maintains the following addons but they can be built by anyone
|
||||
|
||||
Since xterm.js is typically implemented as a developer tool, only modern browsers are supported officially. Specifically the latest versions of *Chrome*, *Edge*, *Firefox* and *Safari*.
|
||||
|
||||
We also partially support *Intenet Explorer 11*, meaning xterm.js should work for the most part, but we reserve the right to not provide workarounds specifically for it unless it's absolutely necessary to get the basic input/output flow working.
|
||||
We also partially support *Internet Explorer 11*, meaning xterm.js should work for the most part, but we reserve the right to not provide workarounds specifically for it unless it's absolutely necessary to get the basic input/output flow working.
|
||||
|
||||
Xterm.js works seamlessly in [Electron](https://electronjs.org/) apps and may even work on earlier versions of the browsers, these are the versions we strive to keep working.
|
||||
|
||||
@@ -162,7 +162,16 @@ Xterm.js is used in several world-class applications to provide great terminal e
|
||||
- [**x-terminal**](https://atom.io/packages/x-terminal): Atom plugin for providing terminals inside your Atom workspace.
|
||||
- [**CoCalc**](https://cocalc.com/): Lots of free software pre-installed, to chat, collaborate, develop, program, publish, research, share, teach, in C++, HTML, Julia, Jupyter, LaTeX, Markdown, Python, R, SageMath, Scala, ...
|
||||
- [**Dank Domain**](https://www.DDgame.us/): Open source multiuser medieval game supporting old & new terminal emulation.
|
||||
|
||||
- [**DockerStacks**](https://docker-stacks.com/): Local LAMP/LEMP development studio
|
||||
- [**Codecademy**](https://codecademy.com/): Uses xterm.js in its courses on Bash.
|
||||
- [**Laravel Ssh Web Client**](https://github.com/roke22/Laravel-ssh-client): Laravel server inventory with ssh web client to connect at server using xterm.js
|
||||
- [**Repl.it**](https://repl.it): Collaborative browser based IDE with support for 50+ different languages.
|
||||
- [**TeleType**](https://github.com/akshaykmr/TeleType): cli tool that allows you to share your terminal online conveniently. Show off mad cli-fu, help a colleague, teach, or troubleshoot.
|
||||
- [**Intervue**](https://www.intervue.io): Pair programming for interviews. Multiple programming languages supported, with results displayed by xterm.js.
|
||||
- [**TRASA**](https://trasa.io): Zero trust access to Web, SSH, RDP and Database services.
|
||||
- [**Commas**](https://github.com/CyanSalt/commas): Commas is a hackable terminal and command runner.
|
||||
- [**Devtron**](https://github.com/devtron-labs/devtron): Software Delivery Workflow For Kubernetes.
|
||||
- [**NxShell**](https://github.com/nxshell/nxshell): An easy to use new terminal for SSH.
|
||||
[And much more...](https://github.com/xtermjs/xterm.js/network/dependents)
|
||||
|
||||
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it in our list. Note: Please add any new contributions to the end of the list only.
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"repository": "https://github.com/xtermjs/xterm.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "../../node_modules/.bin/tsc -p src",
|
||||
"build": "../../node_modules/.bin/tsc -p .",
|
||||
"prepackage": "npm run build",
|
||||
"package": "../../node_modules/.bin/webpack",
|
||||
"prepublishOnly": "npm run package"
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
"removeComments": true,
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../out-test/api/TestUtils"
|
||||
"../../../node_modules/@types/mocha"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@
|
||||
|
||||
import WebSocket = require('ws');
|
||||
import { openTerminal, pollFor, getBrowserType } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from 'playwright-core';
|
||||
import { Browser, Page } from 'playwright';
|
||||
|
||||
const APP = 'http://127.0.0.1:3000/test';
|
||||
|
||||
@@ -17,7 +17,7 @@ const height = 600;
|
||||
describe('AttachAddon', () => {
|
||||
before(async function(): Promise<any> {
|
||||
const browserType = getBrowserType();
|
||||
browser = await browserType.launch({ dumpio: true,
|
||||
browser = await browserType.launch({
|
||||
headless: process.argv.indexOf('--headless') !== -1
|
||||
});
|
||||
page = await (await browser.newContext()).newPage();
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"es2015"
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../out-test",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../node_modules/@types/node",
|
||||
"../../../out-test/api/TestUtils"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{ "path": "./src" },
|
||||
{ "path": "./test" }
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm-addon-fit",
|
||||
"version": "0.4.0",
|
||||
"version": "0.5.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
@@ -10,7 +10,7 @@
|
||||
"repository": "https://github.com/xtermjs/xterm.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "../../node_modules/.bin/tsc -p src",
|
||||
"build": "../../node_modules/.bin/tsc -p .",
|
||||
"prepackage": "npm run build",
|
||||
"package": "../../node_modules/.bin/webpack",
|
||||
"prepublishOnly": "npm run package"
|
||||
|
||||
@@ -59,6 +59,10 @@ export class FitAddon implements ITerminalAddon {
|
||||
// TODO: Remove reliance on private API
|
||||
const core = (this._terminal as any)._core;
|
||||
|
||||
if (core._renderService.dimensions.actualCellWidth === 0 || core._renderService.dimensions.actualCellHeight === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const parentElementStyle = window.getComputedStyle(this._terminal.element.parentElement);
|
||||
const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
|
||||
const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
"removeComments": true,
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../out-test/api/TestUtils"
|
||||
"../../../node_modules/@types/mocha"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
|
||||
+19
-2
@@ -5,7 +5,7 @@
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { openTerminal, getBrowserType } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from 'playwright-core';
|
||||
import { Browser, Page } from 'playwright';
|
||||
|
||||
const APP = 'http://127.0.0.1:3000/test';
|
||||
|
||||
@@ -17,12 +17,16 @@ const height = 768;
|
||||
describe('FitAddon', () => {
|
||||
before(async function(): Promise<any> {
|
||||
const browserType = getBrowserType();
|
||||
browser = await browserType.launch({ dumpio: true,
|
||||
browser = await browserType.launch({
|
||||
headless: process.argv.indexOf('--headless') !== -1
|
||||
});
|
||||
page = await (await browser.newContext()).newPage();
|
||||
await page.setViewportSize({ width, height });
|
||||
await page.goto(APP);
|
||||
});
|
||||
|
||||
beforeEach(async function(): Promise<any> {
|
||||
await page.evaluate(`document.querySelector('#terminal-container').style.display=''`);
|
||||
await openTerminal(page);
|
||||
});
|
||||
|
||||
@@ -30,6 +34,10 @@ describe('FitAddon', () => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
afterEach(async function(): Promise<any> {
|
||||
await page.evaluate(`window.term.dispose()`);
|
||||
});
|
||||
|
||||
it('no terminal', async function(): Promise<any> {
|
||||
await page.evaluate(`window.fit = new FitAddon();`);
|
||||
assert.equal(await page.evaluate(`window.fit.proposeDimensions()`), undefined);
|
||||
@@ -63,6 +71,15 @@ describe('FitAddon', () => {
|
||||
rows: 1
|
||||
});
|
||||
});
|
||||
|
||||
it('hidden', async function(): Promise<any> {
|
||||
await page.evaluate(`window.term.dispose()`);
|
||||
await page.evaluate(`document.querySelector('#terminal-container').style.display='none'`);
|
||||
await page.evaluate(`window.term = new Terminal()`);
|
||||
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
|
||||
await loadFit();
|
||||
assert.equal(await page.evaluate(`window.fit.proposeDimensions()`), undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fit', () => {
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"es2015"
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../out-test",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../node_modules/@types/node",
|
||||
"../../../out-test/api/TestUtils"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{ "path": "./src" },
|
||||
{ "path": "./test" }
|
||||
]
|
||||
}
|
||||
@@ -12,7 +12,7 @@ const mkdirp = require('mkdirp');
|
||||
const yauzl = require('yauzl');
|
||||
|
||||
const urls = {
|
||||
fira: 'https://github.com/tonsky/FiraCode/raw/master/distr/otf/FiraCode-Regular.otf',
|
||||
fira: 'https://github.com/tonsky/FiraCode/raw/d42e7276fa925e5f82748f3ec9ea429736611b48/distr/otf/FiraCode-Regular.otf',
|
||||
iosevka: 'https://github.com/be5invis/Iosevka/releases/download/v1.14.3/01-iosevka-1.14.3.zip'
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm-addon-ligatures",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.0",
|
||||
"description": "Add support for programming ligatures to xterm.js",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
@@ -31,14 +31,14 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"font-finder": "^1.0.4",
|
||||
"font-ligatures": "^1.3.2"
|
||||
"font-finder": "^1.1.0",
|
||||
"font-ligatures": "^1.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/sinon": "^5.0.1",
|
||||
"axios": "^0.18.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"sinon": "^6.1.3",
|
||||
"mkdirp": "0.5.5",
|
||||
"sinon": "6.3.5",
|
||||
"yauzl": "^2.10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2017",
|
||||
"module": "commonjs",
|
||||
"sourceMap": true,
|
||||
"outDir": "../out",
|
||||
"rootDir": ".",
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"preserveWatchOutput": true
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
"compilerOptions": {
|
||||
"target": "es2017",
|
||||
"module": "commonjs",
|
||||
"sourceMap": true,
|
||||
"outDir": "../out",
|
||||
"rootDir": ".",
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"preserveWatchOutput": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{ "path": "./src" }
|
||||
]
|
||||
}
|
||||
@@ -2,20 +2,49 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@sinonjs/formatio@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-2.0.0.tgz#84db7e9eb5531df18a8c5e0bfb6e449e55e654b2"
|
||||
"@sinonjs/commons@^1", "@sinonjs/commons@^1.0.2", "@sinonjs/commons@^1.3.0", "@sinonjs/commons@^1.7.0":
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.0.tgz#c8d68821a854c555bba172f3b06959a0039b236d"
|
||||
integrity sha512-wEj54PfsZ5jGSwMX68G8ZXFawcSglQSXqCftWX3ec8MDUzQdHgcKvw97awHbY0efQEL5iKUOAmmVtoYgmrSG4Q==
|
||||
dependencies:
|
||||
samsam "1.3.0"
|
||||
type-detect "4.0.8"
|
||||
|
||||
"@sinonjs/samsam@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-2.0.0.tgz#9163742ac35c12d3602dece74317643b35db6a80"
|
||||
"@sinonjs/formatio@^3.0.0", "@sinonjs/formatio@^3.2.1":
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-3.2.2.tgz#771c60dfa75ea7f2d68e3b94c7e888a78781372c"
|
||||
integrity sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1"
|
||||
"@sinonjs/samsam" "^3.1.0"
|
||||
|
||||
"@sinonjs/samsam@^2.1.2":
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-2.1.3.tgz#62cf2a9b624edc795134135fe37fc2ae8ea36be3"
|
||||
integrity sha512-8zNeBkSKhU9a5cRNbpCKau2WWPfan+Q2zDlcXvXyhn9EsMqgYs4qzo0XHNVlXC6ABQL8fT6nV+zzo5RTHJzyXw==
|
||||
|
||||
"@sinonjs/samsam@^3.1.0":
|
||||
version "3.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-3.3.3.tgz#46682efd9967b259b81136b9f120fd54585feb4a"
|
||||
integrity sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.3.0"
|
||||
array-from "^2.1.1"
|
||||
lodash "^4.17.15"
|
||||
|
||||
"@sinonjs/text-encoding@^0.7.1":
|
||||
version "0.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5"
|
||||
integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==
|
||||
|
||||
"@types/sinon@^5.0.1":
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-5.0.1.tgz#a15b36ec42f1f53166617491feabd1734cb03e21"
|
||||
|
||||
array-from@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/array-from/-/array-from-2.1.1.tgz#cfe9d8c26628b9dc5aecc62a9f5d8f1f352c1195"
|
||||
integrity sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=
|
||||
|
||||
axios@^0.18.0:
|
||||
version "0.18.1"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.1.tgz#ff3f0de2e7b5d180e757ad98000f1081b87bcea3"
|
||||
@@ -49,16 +78,25 @@ follow-redirects@1.5.10:
|
||||
dependencies:
|
||||
debug "=3.1.0"
|
||||
|
||||
font-finder@^1.0.3, font-finder@^1.0.4:
|
||||
font-finder@^1.0.3:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/font-finder/-/font-finder-1.0.4.tgz#2ca944954dd8d0e1b5bdc4c596cc08607761d89b"
|
||||
dependencies:
|
||||
get-system-fonts "^2.0.0"
|
||||
promise-stream-reader "^1.0.1"
|
||||
|
||||
font-ligatures@^1.3.2:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/font-ligatures/-/font-ligatures-1.3.2.tgz#227eb5fc38fef34b5373aa19b555320b82842a71"
|
||||
font-finder@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/font-finder/-/font-finder-1.1.0.tgz#2bff2b2762acba720239c8bec898a96daae90858"
|
||||
integrity sha512-wpCL2uIbi6GurJbU7ZlQ3nGd61Ho+dSU6U83/xJT5UPFfN35EeCW/rOtS+5k+IuEZu2SYmHzDIPL9eA5tSYRAw==
|
||||
dependencies:
|
||||
get-system-fonts "^2.0.0"
|
||||
promise-stream-reader "^1.0.1"
|
||||
|
||||
font-ligatures@^1.3.3:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/font-ligatures/-/font-ligatures-1.3.3.tgz#63fff18dc8adb3a11fe5eec1f4e8d7edfa8075b9"
|
||||
integrity sha512-NSGpHgVNX81M7AWS1XylK1UZbN3QllfUIDAAuPv6TUcl5O2b781JcKS5L2RopAU0AqlTyX3ZuX/04eaMpbVrHA==
|
||||
dependencies:
|
||||
font-finder "^1.0.3"
|
||||
lru-cache "^4.1.3"
|
||||
@@ -80,17 +118,31 @@ isarray@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
|
||||
|
||||
just-extend@^1.1.27:
|
||||
version "1.1.27"
|
||||
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-1.1.27.tgz#ec6e79410ff914e472652abfa0e603c03d60e905"
|
||||
just-extend@^4.0.2:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.1.0.tgz#7278a4027d889601640ee0ce0e5a00b992467da4"
|
||||
integrity sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==
|
||||
|
||||
lodash.get@^4.4.2:
|
||||
version "4.4.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
|
||||
|
||||
lolex@^2.3.2, lolex@^2.4.2:
|
||||
version "2.7.1"
|
||||
resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.7.1.tgz#e40a8c4d1f14b536aa03e42a537c7adbaf0c20be"
|
||||
lodash@^4.17.15:
|
||||
version "4.17.19"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
|
||||
integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
|
||||
|
||||
lolex@^2.7.5:
|
||||
version "2.7.5"
|
||||
resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.7.5.tgz#113001d56bfc7e02d56e36291cc5c413d1aa0733"
|
||||
integrity sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q==
|
||||
|
||||
lolex@^5.0.1:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/lolex/-/lolex-5.1.2.tgz#953694d098ce7c07bc5ed6d0e42bc6c0c6d5a367"
|
||||
integrity sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.7.0"
|
||||
|
||||
lru-cache@^4.1.3:
|
||||
version "4.1.3"
|
||||
@@ -99,29 +151,32 @@ lru-cache@^4.1.3:
|
||||
pseudomap "^1.0.2"
|
||||
yallist "^2.1.2"
|
||||
|
||||
minimist@0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||
minimist@^1.2.5:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||
|
||||
mkdirp@^0.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||
mkdirp@0.5.5:
|
||||
version "0.5.5"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
|
||||
integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
|
||||
dependencies:
|
||||
minimist "0.0.8"
|
||||
minimist "^1.2.5"
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
|
||||
nise@^1.3.3:
|
||||
version "1.4.2"
|
||||
resolved "https://registry.yarnpkg.com/nise/-/nise-1.4.2.tgz#a9a3800e3994994af9e452333d549d60f72b8e8c"
|
||||
nise@^1.4.5:
|
||||
version "1.5.3"
|
||||
resolved "https://registry.yarnpkg.com/nise/-/nise-1.5.3.tgz#9d2cfe37d44f57317766c6e9408a359c5d3ac1f7"
|
||||
integrity sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==
|
||||
dependencies:
|
||||
"@sinonjs/formatio" "^2.0.0"
|
||||
just-extend "^1.1.27"
|
||||
lolex "^2.3.2"
|
||||
"@sinonjs/formatio" "^3.2.1"
|
||||
"@sinonjs/text-encoding" "^0.7.1"
|
||||
just-extend "^4.0.2"
|
||||
lolex "^5.0.1"
|
||||
path-to-regexp "^1.7.0"
|
||||
text-encoding "^0.6.4"
|
||||
|
||||
opentype.js@^0.8.0:
|
||||
version "0.8.0"
|
||||
@@ -147,38 +202,33 @@ pseudomap@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
|
||||
|
||||
samsam@1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50"
|
||||
|
||||
sinon@^6.1.3:
|
||||
version "6.1.3"
|
||||
resolved "https://registry.yarnpkg.com/sinon/-/sinon-6.1.3.tgz#98e7d716b7b11f7f1200e9c997e74f50ae094660"
|
||||
sinon@6.3.5:
|
||||
version "6.3.5"
|
||||
resolved "https://registry.yarnpkg.com/sinon/-/sinon-6.3.5.tgz#0f6d6a5b4ebaad1f6e8e019395542d1d02c144a0"
|
||||
integrity sha512-xgoZ2gKjyVRcF08RrIQc+srnSyY1JDJtxu3Nsz07j1ffjgXoY6uPLf/qja6nDBZgzYYEovVkFryw2+KiZz11xQ==
|
||||
dependencies:
|
||||
"@sinonjs/formatio" "^2.0.0"
|
||||
"@sinonjs/samsam" "^2.0.0"
|
||||
"@sinonjs/commons" "^1.0.2"
|
||||
"@sinonjs/formatio" "^3.0.0"
|
||||
"@sinonjs/samsam" "^2.1.2"
|
||||
diff "^3.5.0"
|
||||
lodash.get "^4.4.2"
|
||||
lolex "^2.4.2"
|
||||
nise "^1.3.3"
|
||||
supports-color "^5.4.0"
|
||||
lolex "^2.7.5"
|
||||
nise "^1.4.5"
|
||||
supports-color "^5.5.0"
|
||||
type-detect "^4.0.8"
|
||||
|
||||
supports-color@^5.4.0:
|
||||
version "5.4.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54"
|
||||
supports-color@^5.5.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
||||
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
text-encoding@^0.6.4:
|
||||
version "0.6.4"
|
||||
resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19"
|
||||
|
||||
tiny-inflate@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.2.tgz#93d9decffc8805bd57eae4310f0b745e9b6fb3a7"
|
||||
|
||||
type-detect@^4.0.8:
|
||||
type-detect@4.0.8, type-detect@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm-addon-search",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
@@ -10,7 +10,7 @@
|
||||
"repository": "https://github.com/xtermjs/xterm.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "../../node_modules/.bin/tsc -p src",
|
||||
"build": "../../node_modules/.bin/tsc -p .",
|
||||
"prepackage": "npm run build",
|
||||
"package": "../../node_modules/.bin/webpack",
|
||||
"prepublishOnly": "npm run package"
|
||||
|
||||
@@ -158,8 +158,15 @@ export class SearchAddon implements ITerminalAddon {
|
||||
};
|
||||
|
||||
if (incremental) {
|
||||
// Try to expand selection to right first.
|
||||
result = this._findInLine(term, searchPosition, searchOptions, false);
|
||||
if (!(result && result.row === startRow && result.col === startCol)) {
|
||||
const isOldResultHighlighted = result && result.row === startRow && result.col === startCol;
|
||||
if (!isOldResultHighlighted) {
|
||||
// If selection was not able to be expanded to the right, then try reverse search
|
||||
if (currentSelection) {
|
||||
searchPosition.startRow = currentSelection.endRow;
|
||||
searchPosition.startCol = currentSelection.endColumn;
|
||||
}
|
||||
result = this._findInLine(term, searchPosition, searchOptions, true);
|
||||
}
|
||||
} else {
|
||||
@@ -245,6 +252,7 @@ export class SearchAddon implements ITerminalAddon {
|
||||
* @param term The search term.
|
||||
* @param position The position to start the search.
|
||||
* @param searchOptions Search options.
|
||||
* @param isReverseSearch Whether the search should start from the right side of the terminal and search to the left.
|
||||
* @return The search result if it was found.
|
||||
*/
|
||||
protected _findInLine(term: string, searchPosition: ISearchPosition, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult | undefined {
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
"removeComments": true,
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../out-test/api/TestUtils"
|
||||
"../../../node_modules/@types/mocha"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
|
||||
+11
-4
@@ -7,7 +7,7 @@ import { assert } from 'chai';
|
||||
import { readFile } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
import { openTerminal, writeSync, getBrowserType } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from 'playwright-core';
|
||||
import { Browser, Page } from 'playwright';
|
||||
|
||||
const APP = 'http://127.0.0.1:3000/test';
|
||||
|
||||
@@ -19,7 +19,7 @@ const height = 600;
|
||||
describe('Search Tests', function(): void {
|
||||
before(async function(): Promise<any> {
|
||||
const browserType = getBrowserType();
|
||||
browser = await browserType.launch({ dumpio: true,
|
||||
browser = await browserType.launch({
|
||||
headless: process.argv.indexOf('--headless') !== -1
|
||||
});
|
||||
page = await (await browser.newContext()).newPage();
|
||||
@@ -111,8 +111,15 @@ describe('Search Tests', function(): void {
|
||||
let fixture: string;
|
||||
before(async () => {
|
||||
const rawFixture = await new Promise<Buffer>(r => readFile(resolve(__dirname, '../fixtures/issue-2444'), (err, data) => r(data)));
|
||||
fixture = rawFixture.toString()
|
||||
.replace(/\n/g, '\\n\\r')
|
||||
if (process.platform === 'win32') {
|
||||
fixture = rawFixture.toString()
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\r/g, '\\r');
|
||||
} else {
|
||||
fixture = rawFixture.toString()
|
||||
.replace(/\n/g, '\\n\\r');
|
||||
}
|
||||
fixture = fixture
|
||||
.replace(/'/g, '\\\'');
|
||||
});
|
||||
it('should find all occurrences using findNext', async () => {
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"es6",
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../out-test",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../node_modules/@types/node",
|
||||
"../../../out-test/api/TestUtils"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{ "path": "./src" },
|
||||
{ "path": "./test" }
|
||||
]
|
||||
}
|
||||
@@ -16,12 +16,12 @@ class TestTerminal extends Terminal {
|
||||
}
|
||||
}
|
||||
|
||||
perfContext('Terminal: sh -c "dd if=/dev/random count=40 bs=1k | hexdump | lolcat -f"', () => {
|
||||
perfContext('Terminal: sh -c "dd if=/dev/urandom count=40 bs=1k | hexdump | lolcat -f"', () => {
|
||||
let content = '';
|
||||
let contentUtf8: Uint8Array;
|
||||
|
||||
before(async () => {
|
||||
const p = spawn('sh', ['-c', 'dd if=/dev/random count=40 bs=1k | hexdump | lolcat -f'], {
|
||||
const p = spawn('sh', ['-c', 'dd if=/dev/urandom count=40 bs=1k | hexdump | lolcat -f'], {
|
||||
name: 'xterm-256color',
|
||||
cols: 80,
|
||||
rows: 25,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm-addon-serialize",
|
||||
"version": "0.3.0",
|
||||
"version": "0.5.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
@@ -9,13 +9,13 @@
|
||||
"types": "typings/xterm-addon-serialize.d.ts",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "../../node_modules/.bin/tsc -p src",
|
||||
"build": "../../node_modules/.bin/tsc -p .",
|
||||
"prepackage": "npm run build",
|
||||
"package": "../../node_modules/.bin/webpack",
|
||||
"prepublishOnly": "npm run package",
|
||||
"benchmark": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json",
|
||||
"benchmark-baseline": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --baseline out-benchmark/addons/xterm-addon-serialize/benchmark/*benchmark.js",
|
||||
"benchmark-eval": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --eval out-benchmark/addons/xterm-addon-serialize/benchmark/*benchmark.js"
|
||||
"benchmark-baseline": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --baseline out-benchmark/benchmark/*benchmark.js",
|
||||
"benchmark-eval": "NODE_PATH=../../out:./out:./out-benchmark/ ../../node_modules/.bin/xterm-benchmark -r 5 -c benchmark/benchmark.json --eval out-benchmark/benchmark/*benchmark.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"xterm": "^4.0.0"
|
||||
|
||||
@@ -21,7 +21,7 @@ abstract class BaseSerializeHandler {
|
||||
const cell2 = this._buffer.getNullCell();
|
||||
let oldCell = cell1;
|
||||
|
||||
this._beforeSerialize(endRow - startRow);
|
||||
this._beforeSerialize(endRow - startRow, startRow, endRow);
|
||||
|
||||
for (let row = startRow; row < endRow; row++) {
|
||||
const line = this._buffer.getLine(row);
|
||||
@@ -36,7 +36,7 @@ abstract class BaseSerializeHandler {
|
||||
oldCell = c;
|
||||
}
|
||||
}
|
||||
this._rowEnd(row);
|
||||
this._rowEnd(row, row === endRow - 1);
|
||||
}
|
||||
|
||||
this._afterSerialize();
|
||||
@@ -45,8 +45,8 @@ abstract class BaseSerializeHandler {
|
||||
}
|
||||
|
||||
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void { }
|
||||
protected _rowEnd(row: number): void { }
|
||||
protected _beforeSerialize(rows: number): void { }
|
||||
protected _rowEnd(row: number, isLastRow: boolean): void { }
|
||||
protected _beforeSerialize(rows: number, startRow: number, endRow: number): void { }
|
||||
protected _afterSerialize(): void { }
|
||||
protected _serializeString(): string { return ''; }
|
||||
}
|
||||
@@ -71,27 +71,152 @@ function equalFlags(cell1: IBufferCell, cell2: IBufferCell): boolean {
|
||||
&& cell1.isDim() === cell2.isDim();
|
||||
}
|
||||
|
||||
|
||||
|
||||
class StringSerializeHandler extends BaseSerializeHandler {
|
||||
private _rowIndex: number = 0;
|
||||
private _allRows: string[] = new Array<string>();
|
||||
private _allRowSeparators: string[] = new Array<string>();
|
||||
private _currentRow: string = '';
|
||||
private _nullCellCount: number = 0;
|
||||
|
||||
constructor(buffer: IBuffer) {
|
||||
super(buffer);
|
||||
// we can see a full colored cell and a null cell that only have background the same style
|
||||
// but the information isn't preserved by null cell itself
|
||||
// so wee need to record it when required.
|
||||
private _cursorStyle: IBufferCell = this._buffer1.getNullCell();
|
||||
|
||||
// where exact the cursor styles comes from
|
||||
// because we can't copy the cell directly
|
||||
// so we remember where the content comes from instead
|
||||
private _cursorStyleRow: number = 0;
|
||||
private _cursorStyleCol: number = 0;
|
||||
|
||||
// this is a null cell for reference for checking whether background is empty or not
|
||||
private _backgroundCell: IBufferCell = this._buffer1.getNullCell();
|
||||
|
||||
private _firstRow: number = 0;
|
||||
private _lastCursorRow: number = 0;
|
||||
private _lastCursorCol: number = 0;
|
||||
private _lastContentCursorRow: number = 0;
|
||||
private _lastContentCursorCol: number = 0;
|
||||
|
||||
constructor(private _buffer1: IBuffer, private _terminal: Terminal) {
|
||||
super(_buffer1);
|
||||
}
|
||||
|
||||
protected _beforeSerialize(rows: number): void {
|
||||
protected _beforeSerialize(rows: number, start: number, end: number): void {
|
||||
this._allRows = new Array<string>(rows);
|
||||
this._lastContentCursorRow = start;
|
||||
this._lastCursorRow = start;
|
||||
this._firstRow = start;
|
||||
}
|
||||
|
||||
protected _rowEnd(row: number): void {
|
||||
this._allRows[this._rowIndex++] = this._currentRow;
|
||||
private _thisRowLastChar: IBufferCell = this._buffer1.getNullCell();
|
||||
private _thisRowLastSecondChar: IBufferCell = this._buffer1.getNullCell();
|
||||
private _nextRowFirstChar: IBufferCell = this._buffer1.getNullCell();
|
||||
protected _rowEnd(row: number, isLastRow: boolean): void {
|
||||
// if there is colorful empty cell at line end, whe must pad it back, or the the color block will missing
|
||||
if (this._nullCellCount > 0 && !equalBg(this._cursorStyle, this._backgroundCell)) {
|
||||
// use clear right to set background.
|
||||
this._currentRow += `\x1b[${this._nullCellCount}X`;
|
||||
}
|
||||
|
||||
let rowSeparator = '';
|
||||
|
||||
// handle row separator
|
||||
if (!isLastRow) {
|
||||
// Enable BCE
|
||||
if (row - this._firstRow >= this._terminal.rows) {
|
||||
this._buffer1.getLine(this._cursorStyleRow)?.getCell(this._cursorStyleCol, this._backgroundCell);
|
||||
}
|
||||
|
||||
// Fetch current line
|
||||
const currentLine = this._buffer1.getLine(row)!;
|
||||
// Fetch next line
|
||||
const nextLine = this._buffer1.getLine(row + 1)!;
|
||||
|
||||
if (!nextLine.isWrapped) {
|
||||
// just insert the line break
|
||||
rowSeparator = '\r\n';
|
||||
// we sended the enter
|
||||
this._lastCursorRow = row + 1;
|
||||
this._lastCursorCol = 0;
|
||||
} else {
|
||||
rowSeparator = '';
|
||||
const thisRowLastChar = currentLine.getCell(currentLine.length - 1, this._thisRowLastChar)!;
|
||||
const thisRowLastSecondChar = currentLine.getCell(currentLine.length - 2, this._thisRowLastSecondChar)!;
|
||||
const nextRowFirstChar = nextLine.getCell(0, this._nextRowFirstChar)!;
|
||||
const isNextRowFirstCharDoubleWidth = nextRowFirstChar.getWidth() > 1;
|
||||
|
||||
// validate whether this line wrap is ever possible
|
||||
// which mean whether cursor can placed at a overflow position (x === row) naturally
|
||||
let isValid = false;
|
||||
|
||||
if (
|
||||
// you must output character to cause overflow, control sequence can't do this
|
||||
nextRowFirstChar.getChars() &&
|
||||
isNextRowFirstCharDoubleWidth ? this._nullCellCount <= 1 : this._nullCellCount <= 0
|
||||
) {
|
||||
if (
|
||||
// the last character can't be null,
|
||||
// you can't use control sequence to move cursor to (x === row)
|
||||
(thisRowLastChar.getChars() || thisRowLastChar.getWidth() === 0) &&
|
||||
// change background of the first wrapped cell also affects BCE
|
||||
// so we mark it as invalid to simply the process to determine line separator
|
||||
equalBg(thisRowLastChar, nextRowFirstChar)
|
||||
) {
|
||||
isValid = true;
|
||||
}
|
||||
|
||||
if (
|
||||
// the second to last character can't be null if the next line starts with CJK,
|
||||
// you can't use control sequence to move cursor to (x === row)
|
||||
isNextRowFirstCharDoubleWidth &&
|
||||
(thisRowLastSecondChar.getChars() || thisRowLastSecondChar.getWidth() === 0) &&
|
||||
// change background of the first wrapped cell also affects BCE
|
||||
// so we mark it as invalid to simply the process to determine line separator
|
||||
equalBg(thisRowLastChar, nextRowFirstChar) &&
|
||||
equalBg(thisRowLastSecondChar, nextRowFirstChar)
|
||||
) {
|
||||
isValid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
// force the wrap with magic
|
||||
// insert enough character to force the wrap
|
||||
rowSeparator = '-'.repeat(this._nullCellCount + 1);
|
||||
// move back and erase next line head
|
||||
rowSeparator += '\x1b[1D\x1b[1X';
|
||||
|
||||
if (this._nullCellCount > 0) {
|
||||
// do these because we filled the last several null slot, which we shouldn't
|
||||
rowSeparator += '\x1b[A';
|
||||
rowSeparator += `\x1b[${currentLine.length - this._nullCellCount}C`;
|
||||
rowSeparator += `\x1b[${this._nullCellCount}X`;
|
||||
rowSeparator += `\x1b[${currentLine.length - this._nullCellCount}D`;
|
||||
rowSeparator += '\x1b[B';
|
||||
}
|
||||
|
||||
// This is content and need the be serialized even it is invisible.
|
||||
// without this, wrap will be missing from outputs.
|
||||
this._lastContentCursorRow = row + 1;
|
||||
this._lastContentCursorCol = 0;
|
||||
|
||||
// force commit the cursor position
|
||||
this._lastCursorRow = row + 1;
|
||||
this._lastCursorCol = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._allRows[this._rowIndex] = this._currentRow;
|
||||
this._allRowSeparators[this._rowIndex++] = rowSeparator;
|
||||
this._currentRow = '';
|
||||
this._nullCellCount = 0;
|
||||
}
|
||||
|
||||
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
|
||||
private _diffStyle(cell: IBufferCell, oldCell: IBufferCell): number[] {
|
||||
const sgrSeq: number[] = [];
|
||||
const fgChanged = !equalFg(cell, oldCell);
|
||||
const bgChanged = !equalBg(cell, oldCell);
|
||||
@@ -99,7 +224,9 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
||||
|
||||
if (fgChanged || bgChanged || flagsChanged) {
|
||||
if (cell.isAttributeDefault()) {
|
||||
this._currentRow += '\x1b[0m';
|
||||
if (!oldCell.isAttributeDefault()) {
|
||||
sgrSeq.push(0);
|
||||
}
|
||||
} else {
|
||||
if (fgChanged) {
|
||||
const color = cell.getFgColor();
|
||||
@@ -131,30 +258,129 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
if (sgrSeq.length) {
|
||||
return sgrSeq;
|
||||
}
|
||||
|
||||
protected _nextCell(cell: IBufferCell, oldCell: IBufferCell, row: number, col: number): void {
|
||||
// a width 0 cell don't need to be count because it is just a placeholder after a CJK character;
|
||||
const isPlaceHolderCell = cell.getWidth() === 0;
|
||||
|
||||
if (isPlaceHolderCell) {
|
||||
return;
|
||||
}
|
||||
|
||||
// this cell don't have content
|
||||
const isEmptyCell = cell.getChars() === '';
|
||||
|
||||
const sgrSeq = this._diffStyle(cell, this._cursorStyle);
|
||||
|
||||
// the empty cell style is only assumed to be changed when background changed, because foreground is always 0.
|
||||
const styleChanged = isEmptyCell ? !equalBg(this._cursorStyle, cell) : sgrSeq.length > 0;
|
||||
|
||||
/**
|
||||
* handles style change
|
||||
*/
|
||||
if (styleChanged) {
|
||||
// before update the style, we need to fill empty cell back
|
||||
if (this._nullCellCount > 0) {
|
||||
// use clear right to set background.
|
||||
if (!equalBg(this._cursorStyle, this._backgroundCell)) {
|
||||
this._currentRow += `\x1b[${this._nullCellCount}X`;
|
||||
}
|
||||
// use move right to move cursor.
|
||||
this._currentRow += `\x1b[${this._nullCellCount}C`;
|
||||
this._nullCellCount = 0;
|
||||
}
|
||||
|
||||
this._lastContentCursorRow = this._lastCursorRow = row;
|
||||
this._lastContentCursorCol = this._lastCursorCol = col;
|
||||
|
||||
this._currentRow += `\x1b[${sgrSeq.join(';')}m`;
|
||||
|
||||
// update the last cursor style
|
||||
const line = this._buffer1.getLine(row);
|
||||
if (line !== undefined) {
|
||||
line.getCell(col, this._cursorStyle);
|
||||
this._cursorStyleRow = row;
|
||||
this._cursorStyleCol = col;
|
||||
}
|
||||
}
|
||||
|
||||
// Count number of null cells encountered after the last non-null cell and move the cursor
|
||||
// if a non-null cell is found (eg. \t or cursor move)
|
||||
if (cell.getChars() === '') {
|
||||
this._nullCellCount++;
|
||||
} else if (this._nullCellCount > 0) {
|
||||
this._currentRow += `\x1b[${this._nullCellCount}C`;
|
||||
this._nullCellCount = 0;
|
||||
}
|
||||
/**
|
||||
* handles actual content
|
||||
*/
|
||||
if (isEmptyCell) {
|
||||
this._nullCellCount += cell.getWidth();
|
||||
} else {
|
||||
if (this._nullCellCount > 0) {
|
||||
// we can just assume we have same style with previous one here
|
||||
// because style change is handled by previous stage
|
||||
// use move right when background is empty, use clear right when there is background.
|
||||
if (equalBg(this._cursorStyle, this._backgroundCell)) {
|
||||
this._currentRow += `\x1b[${this._nullCellCount}C`;
|
||||
} else {
|
||||
this._currentRow += `\x1b[${this._nullCellCount}X`;
|
||||
this._currentRow += `\x1b[${this._nullCellCount}C`;
|
||||
}
|
||||
this._nullCellCount = 0;
|
||||
}
|
||||
|
||||
this._currentRow += cell.getChars();
|
||||
this._currentRow += cell.getChars();
|
||||
|
||||
// update cursor
|
||||
this._lastContentCursorRow = this._lastCursorRow = row;
|
||||
this._lastContentCursorCol = this._lastCursorCol = col + cell.getWidth();
|
||||
}
|
||||
}
|
||||
|
||||
protected _serializeString(): string {
|
||||
let rowEnd = this._allRows.length;
|
||||
for (; rowEnd > 0; rowEnd--) {
|
||||
if (this._allRows[rowEnd - 1]) {
|
||||
break;
|
||||
|
||||
// the fixup is only required for data without scrollback
|
||||
// because it will always be placed at last line otherwise
|
||||
if (this._buffer1.length - this._firstRow <= this._terminal.rows) {
|
||||
rowEnd = this._lastContentCursorRow + 1 - this._firstRow;
|
||||
this._lastCursorCol = this._lastContentCursorCol;
|
||||
this._lastCursorRow = this._lastContentCursorRow;
|
||||
}
|
||||
|
||||
let content = '';
|
||||
|
||||
for (let i = 0; i < rowEnd; i++) {
|
||||
content += this._allRows[i];
|
||||
if (i + 1 < rowEnd) {
|
||||
content += this._allRowSeparators[i];
|
||||
}
|
||||
}
|
||||
return this._allRows.slice(0, rowEnd).join('\r\n');
|
||||
|
||||
// restore the cursor
|
||||
const realCursorRow = this._buffer1.baseY + this._buffer1.cursorY;
|
||||
const realCursorCol = this._buffer1.cursorX;
|
||||
|
||||
const cursorMoved = (realCursorRow !== this._lastCursorRow || realCursorCol !== this._lastCursorCol);
|
||||
|
||||
const moveRight = (offset: number): void => {
|
||||
if (offset > 0) {
|
||||
content += `\u001b[${offset}C`;
|
||||
} else if (offset < 0) {
|
||||
content += `\u001b[${-offset}D`;
|
||||
}
|
||||
};
|
||||
const moveDown = (offset: number): void => {
|
||||
if (offset > 0) {
|
||||
content += `\u001b[${offset}B`;
|
||||
} else if (offset < 0) {
|
||||
content += `\u001b[${-offset}A`;
|
||||
}
|
||||
};
|
||||
|
||||
if (cursorMoved) {
|
||||
moveDown(realCursorRow - this._lastCursorRow);
|
||||
moveRight(realCursorCol - this._lastCursorCol);
|
||||
}
|
||||
|
||||
|
||||
return content;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,20 +393,33 @@ export class SerializeAddon implements ITerminalAddon {
|
||||
this._terminal = terminal;
|
||||
}
|
||||
|
||||
public serialize(rows?: number): string {
|
||||
// TODO: Add re-position cursor support
|
||||
// TODO: Add word wrap mode support
|
||||
private _getString(buffer: IBuffer, scrollback?: number): string {
|
||||
const maxRows = buffer.length;
|
||||
const handler = new StringSerializeHandler(buffer, this._terminal!);
|
||||
|
||||
const correctRows = (scrollback === undefined) ? maxRows : constrain(scrollback + this!._terminal!.rows, 0, maxRows);
|
||||
const result = handler.serialize(maxRows - correctRows, maxRows);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public serialize(scrollback?: number): string {
|
||||
// TODO: Add combinedData support
|
||||
if (!this._terminal) {
|
||||
throw new Error('Cannot use addon until it has been loaded');
|
||||
}
|
||||
|
||||
const maxRows = this._terminal.buffer.active.length;
|
||||
const handler = new StringSerializeHandler(this._terminal.buffer.active);
|
||||
if (this._terminal.buffer.active.type === 'normal') {
|
||||
return this._getString(this._terminal.buffer.active, scrollback);
|
||||
}
|
||||
|
||||
rows = (rows === undefined) ? maxRows : constrain(rows, 0, maxRows);
|
||||
const normalScreenContent = this._getString(this._terminal.buffer.normal, scrollback);
|
||||
// alt screen don't have scrollback
|
||||
const alternativeScreenContent = this._getString(this._terminal.buffer.alternate, undefined);
|
||||
|
||||
return handler.serialize(maxRows - rows, maxRows);
|
||||
return normalScreenContent
|
||||
+ '\u001b[?1049h\u001b[H'
|
||||
+ alternativeScreenContent;
|
||||
}
|
||||
|
||||
public dispose(): void { }
|
||||
|
||||
@@ -18,8 +18,7 @@
|
||||
},
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../out-test/api/TestUtils"
|
||||
"../../../node_modules/@types/mocha"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
|
||||
+222
-11
@@ -5,7 +5,7 @@
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { openTerminal, writeSync, getBrowserType } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from 'playwright-core';
|
||||
import { Browser, Page } from 'playwright';
|
||||
|
||||
const APP = 'http://127.0.0.1:3000/test';
|
||||
|
||||
@@ -14,10 +14,26 @@ let page: Page;
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
|
||||
const writeRawSync = (page: any, str: string): Promise<void> => writeSync(page, '\' +' + JSON.stringify(str) + '+ \'');
|
||||
|
||||
const testNormalScreenEqual = async (page: any, str: string): Promise<void> => {
|
||||
await writeRawSync(page, str);
|
||||
const originalBuffer = await page.evaluate(`inspectBuffer(term.buffer.normal);`);
|
||||
|
||||
const result = await page.evaluate(`serializeAddon.serialize();`) as string;
|
||||
await page.evaluate(`term.reset();`);
|
||||
await writeRawSync(page, result);
|
||||
const newBuffer = await page.evaluate(`inspectBuffer(term.buffer.normal);`);
|
||||
|
||||
// chai decides -0 and 0 are different number...
|
||||
// and firefox have a bug that output -0 for unknown reason
|
||||
assert.equal(JSON.stringify(originalBuffer), JSON.stringify(newBuffer));
|
||||
};
|
||||
|
||||
describe('SerializeAddon', () => {
|
||||
before(async function(): Promise<any> {
|
||||
const browserType = getBrowserType();
|
||||
browser = await browserType.launch({ dumpio: true,
|
||||
browser = await browserType.launch({
|
||||
headless: process.argv.indexOf('--headless') !== -1
|
||||
});
|
||||
page = await (await browser.newContext()).newPage();
|
||||
@@ -27,19 +43,74 @@ describe('SerializeAddon', () => {
|
||||
await page.evaluate(`
|
||||
window.serializeAddon = new SerializeAddon();
|
||||
window.term.loadAddon(window.serializeAddon);
|
||||
window.inspectBuffer = (buffer) => {
|
||||
const lines = [];
|
||||
for (let i = 0; i < buffer.length; i++) {
|
||||
// Do this intentionally to get content of underlining source
|
||||
const bufferLine = buffer.getLine(i)._line;
|
||||
lines.push(JSON.stringify(bufferLine));
|
||||
}
|
||||
return {
|
||||
x: buffer.cursorX,
|
||||
y: buffer.cursorY,
|
||||
data: lines
|
||||
};
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
after(async () => await browser.close());
|
||||
beforeEach(async () => await page.evaluate(`window.term.reset()`));
|
||||
|
||||
it('produce different output when we call test util with different text', async function(): Promise<any> {
|
||||
await writeRawSync(page, '12345');
|
||||
const buffer1 = await page.evaluate(`inspectBuffer(term.buffer.normal);`);
|
||||
|
||||
await page.evaluate(`term.reset();`);
|
||||
await writeRawSync(page, '67890');
|
||||
const buffer2 = await page.evaluate(`inspectBuffer(term.buffer.normal);`);
|
||||
|
||||
assert.throw(() => {
|
||||
assert.equal(JSON.stringify(buffer1), JSON.stringify(buffer2));
|
||||
});
|
||||
});
|
||||
|
||||
it('produce different output when we call test util with different line wrap', async function(): Promise<any> {
|
||||
await writeRawSync(page, '1234567890\r\n12345');
|
||||
const buffer3 = await page.evaluate(`inspectBuffer(term.buffer.normal);`);
|
||||
|
||||
await page.evaluate(`term.reset();`);
|
||||
await writeRawSync(page, '1234567890n12345');
|
||||
const buffer4 = await page.evaluate(`inspectBuffer(term.buffer.normal);`);
|
||||
|
||||
assert.throw(() => {
|
||||
assert.equal(JSON.stringify(buffer3), JSON.stringify(buffer4));
|
||||
});
|
||||
});
|
||||
|
||||
it('empty content', async function(): Promise<any> {
|
||||
const rows = 10;
|
||||
const cols = 10;
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), '');
|
||||
});
|
||||
|
||||
it('trim last empty lines', async function(): Promise<any> {
|
||||
it('unwrap wrapped line', async function(): Promise<any> {
|
||||
const lines = ['123456789123456789'];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('does not unwrap non-wrapped line', async function(): Promise<any> {
|
||||
const lines = [
|
||||
'123456789',
|
||||
'123456789'
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
|
||||
it('preserve last empty lines', async function(): Promise<any> {
|
||||
const cols = 10;
|
||||
const lines = [
|
||||
'',
|
||||
@@ -55,7 +126,7 @@ describe('SerializeAddon', () => {
|
||||
''
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.slice(0, 8).join('\r\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('digits content', async function(): Promise<any> {
|
||||
@@ -67,21 +138,22 @@ describe('SerializeAddon', () => {
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize half rows of content', async function(): Promise<any> {
|
||||
const rows = 10;
|
||||
const halfRows = rows >> 1;
|
||||
it('serialize with half of scrollback', async function(): Promise<any> {
|
||||
const rows = 20;
|
||||
const scrollback = rows - 10;
|
||||
const halfScrollback = scrollback / 2;
|
||||
const cols = 10;
|
||||
const lines = newArray<string>((index: number) => digitsString(cols, index), rows);
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize(${halfRows});`), lines.slice(halfRows, 2 * halfRows).join('\r\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize(${halfScrollback});`), lines.slice(halfScrollback, rows).join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize 0 rows of content', async function(): Promise<any> {
|
||||
const rows = 10;
|
||||
it('serialize 0 rows of scrollback', async function(): Promise<any> {
|
||||
const rows = 20;
|
||||
const cols = 10;
|
||||
const lines = newArray<string>((index: number) => digitsString(cols, index), rows);
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize(0);`), '');
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize(0);`), lines.slice(rows - 10, rows).join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize all rows of content with color16', async function(): Promise<any> {
|
||||
@@ -270,6 +342,145 @@ describe('SerializeAddon', () => {
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), expected.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize CJK correctly', async () => {
|
||||
const lines = [
|
||||
'中文中文',
|
||||
'12中文',
|
||||
'中文12',
|
||||
// This line is going to be wrapped at last character
|
||||
// because it has line length of 11 (1+2*5).
|
||||
// We concat it back without the null cell currently.
|
||||
// But this may be incorrect.
|
||||
// see also #3097
|
||||
'1中文中文中'
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize CJK Mixed with tab correctly', async () => {
|
||||
const lines = [
|
||||
'中文\t12' // CJK mixed with tab
|
||||
];
|
||||
const expected = [
|
||||
'中文\x1b[4C12'
|
||||
];
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`serializeAddon.serialize();`), expected.join('\r\n'));
|
||||
});
|
||||
|
||||
it('serialize with alt screen correctly', async () => {
|
||||
const SMCUP = '\u001b[?1049h';
|
||||
const CUP = '\u001b[H';
|
||||
|
||||
const lines = [
|
||||
`1${SMCUP}${CUP}2`
|
||||
];
|
||||
const expected = [
|
||||
`1${SMCUP}${CUP}2`
|
||||
];
|
||||
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`window.term.buffer.active.type`), 'alternate');
|
||||
assert.equal(JSON.stringify(await page.evaluate(`serializeAddon.serialize();`)), JSON.stringify(expected.join('\r\n')));
|
||||
});
|
||||
|
||||
it('serialize without alt screen correctly', async () => {
|
||||
const SMCUP = '\u001b[?1049h';
|
||||
const RMCUP = '\u001b[?1049l';
|
||||
|
||||
const lines = [
|
||||
`1${SMCUP}2${RMCUP}`
|
||||
];
|
||||
const expected = [
|
||||
`1`
|
||||
];
|
||||
|
||||
await writeSync(page, lines.join('\\r\\n'));
|
||||
assert.equal(await page.evaluate(`window.term.buffer.active.type`), 'normal');
|
||||
assert.equal(JSON.stringify(await page.evaluate(`serializeAddon.serialize();`)), JSON.stringify(expected.join('\r\n')));
|
||||
});
|
||||
|
||||
it('serialize with background', async () => {
|
||||
const CLEAR_RIGHT = (l: number): string => `\u001b[${l}X`;
|
||||
|
||||
const lines = [
|
||||
`1\u001b[44m${CLEAR_RIGHT(5)}`,
|
||||
`2${CLEAR_RIGHT(9)}`
|
||||
];
|
||||
|
||||
await testNormalScreenEqual(page, lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('cause the BCE on scroll', async () => {
|
||||
const CLEAR_RIGHT = (l: number): string => `\u001b[${l}X`;
|
||||
|
||||
const padLines = newArray<string>(
|
||||
(index: number) => digitsString(10, index),
|
||||
10
|
||||
);
|
||||
|
||||
const lines = [
|
||||
...padLines,
|
||||
`\u001b[44m${CLEAR_RIGHT(5)}1111111111111111`
|
||||
];
|
||||
|
||||
await testNormalScreenEqual(page, lines.join('\r\n'));
|
||||
});
|
||||
|
||||
it('handle invalid wrap before scroll', async () => {
|
||||
const CLEAR_RIGHT = (l: number): string => `\u001b[${l}X`;
|
||||
const MOVE_UP = (l: number): string => `\u001b[${l}A`;
|
||||
const MOVE_DOWN = (l: number): string => `\u001b[${l}B`;
|
||||
const MOVE_LEFT = (l: number): string => `\u001b[${l}D`;
|
||||
|
||||
// A line wrap happened after current line.
|
||||
// But there is no content.
|
||||
// so wrap shouldn't even be able to happen.
|
||||
const segments = [
|
||||
`123456789012345`,
|
||||
MOVE_UP(1),
|
||||
CLEAR_RIGHT(5),
|
||||
MOVE_DOWN(1),
|
||||
MOVE_LEFT(5),
|
||||
CLEAR_RIGHT(5),
|
||||
MOVE_UP(1),
|
||||
'1'
|
||||
];
|
||||
|
||||
await testNormalScreenEqual(page, segments.join(''));
|
||||
});
|
||||
|
||||
it('handle invalid wrap after scroll', async () => {
|
||||
const CLEAR_RIGHT = (l: number): string => `\u001b[${l}X`;
|
||||
const MOVE_UP = (l: number): string => `\u001b[${l}A`;
|
||||
const MOVE_DOWN = (l: number): string => `\u001b[${l}B`;
|
||||
const MOVE_LEFT = (l: number): string => `\u001b[${l}D`;
|
||||
|
||||
const padLines = newArray<string>(
|
||||
(index: number) => digitsString(10, index),
|
||||
10
|
||||
);
|
||||
|
||||
// A line wrap happened after current line.
|
||||
// But there is no content.
|
||||
// so wrap shouldn't even be able to happen.
|
||||
const lines = [
|
||||
padLines.join('\r\n'),
|
||||
'\r\n',
|
||||
`123456789012345`,
|
||||
MOVE_UP(1),
|
||||
CLEAR_RIGHT(5),
|
||||
MOVE_DOWN(1),
|
||||
MOVE_LEFT(5),
|
||||
CLEAR_RIGHT(5),
|
||||
MOVE_UP(1),
|
||||
'1'
|
||||
];
|
||||
|
||||
await testNormalScreenEqual(page, lines.join(''));
|
||||
});
|
||||
});
|
||||
|
||||
function newArray<T>(initial: T | ((index: number) => T), count: number): T[] {
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"es2015"
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../out-test",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"common/*": [
|
||||
"../../../src/common/*"
|
||||
]
|
||||
},
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../node_modules/@types/node",
|
||||
"../../../out-test/api/TestUtils"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../src/common"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{ "path": "./src" },
|
||||
{ "path": "./test" },
|
||||
{ "path": "./benchmark" }
|
||||
]
|
||||
}
|
||||
@@ -25,10 +25,10 @@ declare module 'xterm-addon-serialize' {
|
||||
* to restore the state. The cursor will also be positioned to the correct cell.
|
||||
* When restoring a terminal it is best to do before `Terminal.open` is called
|
||||
* to avoid wasting CPU cycles rendering incomplete frames.
|
||||
* @param rows The number of rows to serialize, starting from the bottom of the
|
||||
* terminal. This defaults to the number of rows in the viewport.
|
||||
* @param scrollback The number of rows in scrollback buffer to serialize, starting from the bottom of the
|
||||
* scrollback buffer. This defaults to the all available rows in the scrollback buffer.
|
||||
*/
|
||||
public serialize(rows?: number): string;
|
||||
public serialize(scrollback?: number): string;
|
||||
|
||||
/**
|
||||
* Disposes the addon.
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"repository": "https://github.com/xtermjs/xterm.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "../../node_modules/.bin/tsc -p src",
|
||||
"build": "../../node_modules/.bin/tsc -p .",
|
||||
"prepackage": "npm run build",
|
||||
"package": "../../node_modules/.bin/webpack",
|
||||
"prepublishOnly": "npm run package"
|
||||
|
||||
@@ -18,8 +18,7 @@
|
||||
]
|
||||
},
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../out-test/api/TestUtils"
|
||||
"../../../node_modules/@types/mocha"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { openTerminal, getBrowserType } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from 'playwright-core';
|
||||
import { Browser, Page } from 'playwright';
|
||||
|
||||
const APP = 'http://127.0.0.1:3000/test';
|
||||
|
||||
@@ -17,7 +17,7 @@ const height = 600;
|
||||
describe('Unicode11Addon', () => {
|
||||
before(async function(): Promise<any> {
|
||||
const browserType = getBrowserType();
|
||||
browser = await browserType.launch({ dumpio: true,
|
||||
browser = await browserType.launch({
|
||||
headless: process.argv.indexOf('--headless') !== -1
|
||||
});
|
||||
page = await (await browser.newContext()).newPage();
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2015",
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2015"
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../out-test",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"strict": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"common/*": [
|
||||
"../../../src/common/*"
|
||||
]
|
||||
},
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../node_modules/@types/node",
|
||||
"../../../out-test/api/TestUtils"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../src/common"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{ "path": "./src" },
|
||||
{ "path": "./test" }
|
||||
]
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
"repository": "https://github.com/xtermjs/xterm.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "../../node_modules/.bin/tsc -p src",
|
||||
"build": "../../node_modules/.bin/tsc -p .",
|
||||
"prepackage": "npm run build",
|
||||
"package": "../../node_modules/.bin/webpack",
|
||||
"prepublishOnly": "npm run package"
|
||||
|
||||
@@ -3,20 +3,40 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ILinkProvider, IBufferCellPosition, ILink, Terminal } from 'xterm';
|
||||
import { ILinkProvider, ILink, Terminal, IViewportRange } from 'xterm';
|
||||
|
||||
interface ILinkProviderOptions {
|
||||
hover?(event: MouseEvent, text: string, location: IViewportRange): void;
|
||||
leave?(event: MouseEvent, text: string): void;
|
||||
}
|
||||
|
||||
export class WebLinkProvider implements ILinkProvider {
|
||||
|
||||
constructor(
|
||||
private readonly _terminal: Terminal,
|
||||
private readonly _regex: RegExp,
|
||||
private readonly _handler: (event: MouseEvent, uri: string) => void
|
||||
private readonly _handler: (event: MouseEvent, uri: string) => void,
|
||||
private readonly _options: ILinkProviderOptions = {}
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
public provideLinks(y: number, callback: (links: ILink[] | undefined) => void): void {
|
||||
callback(LinkComputer.computeLink(y, this._regex, this._terminal, this._handler));
|
||||
const links = LinkComputer.computeLink(y, this._regex, this._terminal, this._handler);
|
||||
callback(this._addCallbacks(links));
|
||||
}
|
||||
|
||||
private _addCallbacks(links: ILink[]): ILink[] {
|
||||
return links.map(link => {
|
||||
link.leave = this._options.leave;
|
||||
link.hover = (event: MouseEvent, uri: string): void => {
|
||||
if (this._options.hover) {
|
||||
const { range } = link;
|
||||
this._options.hover(event, uri, range);
|
||||
}
|
||||
};
|
||||
return link;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, ILinkMatcherOptions, ITerminalAddon, IDisposable } from 'xterm';
|
||||
import { Terminal, ILinkMatcherOptions, ITerminalAddon, IDisposable, IViewportRange } from 'xterm';
|
||||
import { WebLinkProvider } from './WebLinkProvider';
|
||||
|
||||
const protocolClause = '(https?:\\/\\/)';
|
||||
@@ -36,6 +36,11 @@ function handleLink(event: MouseEvent, uri: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
interface ILinkProviderOptions {
|
||||
hover?(event: MouseEvent, text: string, location: IViewportRange): void;
|
||||
leave?(event: MouseEvent, text: string): void;
|
||||
}
|
||||
|
||||
export class WebLinksAddon implements ITerminalAddon {
|
||||
private _linkMatcherId: number | undefined;
|
||||
private _terminal: Terminal | undefined;
|
||||
@@ -43,20 +48,22 @@ export class WebLinksAddon implements ITerminalAddon {
|
||||
|
||||
constructor(
|
||||
private _handler: (event: MouseEvent, uri: string) => void = handleLink,
|
||||
private _options: ILinkMatcherOptions = {},
|
||||
private _options: ILinkMatcherOptions | ILinkProviderOptions = {},
|
||||
private _useLinkProvider: boolean = false
|
||||
) {
|
||||
this._options.matchIndex = 1;
|
||||
}
|
||||
|
||||
public activate(terminal: Terminal): void {
|
||||
this._terminal = terminal;
|
||||
|
||||
if (this._useLinkProvider && 'registerLinkProvider' in this._terminal) {
|
||||
this._linkProvider = this._terminal.registerLinkProvider(new WebLinkProvider(this._terminal, strictUrlRegex, this._handler));
|
||||
const options = this._options as ILinkProviderOptions;
|
||||
this._linkProvider = this._terminal.registerLinkProvider(new WebLinkProvider(this._terminal, strictUrlRegex, this._handler, options));
|
||||
} else {
|
||||
// TODO: This should be removed eventually
|
||||
this._linkMatcherId = (this._terminal as Terminal).registerLinkMatcher(strictUrlRegex, this._handler, this._options);
|
||||
const options = this._options as ILinkMatcherOptions;
|
||||
options.matchIndex = 1;
|
||||
this._linkMatcherId = (this._terminal as Terminal).registerLinkMatcher(strictUrlRegex, this._handler, options);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
"removeComments": true,
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../out-test/api/TestUtils"
|
||||
"../../../node_modules/@types/mocha"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { openTerminal, pollFor, writeSync, getBrowserType } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from 'playwright-core';
|
||||
import { Browser, Page } from 'playwright';
|
||||
|
||||
const APP = 'http://127.0.0.1:3000/test';
|
||||
|
||||
@@ -17,7 +17,7 @@ const height = 600;
|
||||
describe('WebLinksAddon', () => {
|
||||
before(async function(): Promise<any> {
|
||||
const browserType = getBrowserType();
|
||||
browser = await browserType.launch({ dumpio: true,
|
||||
browser = await browserType.launch({
|
||||
headless: process.argv.indexOf('--headless') !== -1
|
||||
});
|
||||
page = await (await browser.newContext()).newPage();
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"es2015"
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../out-test",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../node_modules/@types/node",
|
||||
"../../../out-test/api/TestUtils"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{ "path": "./src" },
|
||||
{ "path": "./test" }
|
||||
]
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
|
||||
import { Terminal, ILinkMatcherOptions, ITerminalAddon } from 'xterm';
|
||||
import { Terminal, ILinkMatcherOptions, ITerminalAddon, IViewportRange } from 'xterm';
|
||||
|
||||
declare module 'xterm-addon-web-links' {
|
||||
/**
|
||||
@@ -20,7 +20,7 @@ declare module 'xterm-addon-web-links' {
|
||||
* link provider (new) may cause issues. Link provider will eventually be
|
||||
* the default and only option.
|
||||
*/
|
||||
constructor(handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions, useLinkProvider?: boolean);
|
||||
constructor(handler?: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions | ILinkProviderOptions, useLinkProvider?: boolean);
|
||||
|
||||
/**
|
||||
* Activates the addon
|
||||
@@ -33,4 +33,21 @@ declare module 'xterm-addon-web-links' {
|
||||
*/
|
||||
public dispose(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* An object containing options for a link provider.
|
||||
*/
|
||||
export interface ILinkProviderOptions {
|
||||
/**
|
||||
* A callback that fires when the mouse hovers over a link for a period of
|
||||
* time (defined by {@link ITerminalOptions.linkTooltipHoverDuration}).
|
||||
*/
|
||||
hover?(event: MouseEvent, text: string, location: IViewportRange): void;
|
||||
|
||||
/**
|
||||
* A callback that fires when the mouse leaves a link. Note that this can
|
||||
* happen even when tooltipCallback hasn't fired for the link yet.
|
||||
*/
|
||||
leave?(event: MouseEvent, text: string): void;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,3 +3,5 @@
|
||||
tsconfig.json
|
||||
.yarnrc
|
||||
webpack.config.js
|
||||
out-test/
|
||||
test/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm-addon-webgl",
|
||||
"version": "0.7.0",
|
||||
"version": "0.10.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
@@ -10,7 +10,7 @@
|
||||
"repository": "https://github.com/xtermjs/xterm.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "../../node_modules/.bin/tsc -p src",
|
||||
"build": "../../node_modules/.bin/tsc -p .",
|
||||
"prepackage": "npm run build",
|
||||
"package": "../../node_modules/.bin/webpack",
|
||||
"prepublishOnly": "npm run package"
|
||||
|
||||
@@ -196,7 +196,7 @@ export class RectangleRenderer {
|
||||
} else {
|
||||
// Draw first row
|
||||
const startCol = model.viewportStartRow === model.viewportCappedStartRow ? model.startCol : 0;
|
||||
const startRowEndCol = model.viewportCappedStartRow === model.viewportCappedEndRow ? model.endCol : terminal.cols;
|
||||
const startRowEndCol = model.viewportCappedStartRow === model.viewportEndRow ? model.endCol : terminal.cols;
|
||||
this._addRectangleFloat(
|
||||
this._vertices.selection,
|
||||
0,
|
||||
|
||||
@@ -43,7 +43,6 @@ export class RenderModel implements IRenderModel {
|
||||
public clear(): void {
|
||||
fill(this.cells, 0, 0);
|
||||
fill(this.lineLengths, 0, 0);
|
||||
this.clearSelection();
|
||||
}
|
||||
|
||||
public clearSelection(): void {
|
||||
|
||||
@@ -40,4 +40,8 @@ export class WebglAddon implements ITerminalAddon {
|
||||
public get textureAtlas(): HTMLCanvasElement | undefined {
|
||||
return this._renderer?.textureAtlas;
|
||||
}
|
||||
|
||||
public clearTextureAtlas(): void {
|
||||
this._renderer?.clearCharAtlas();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,9 +116,6 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
|
||||
this._refreshCharAtlas();
|
||||
|
||||
this._rectangleRenderer.updateSelection(this._model.selection);
|
||||
this._glyphRenderer.updateSelection(this._model);
|
||||
|
||||
// Force a full refresh
|
||||
this._model.clear();
|
||||
}
|
||||
@@ -137,7 +134,6 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
this._updateDimensions();
|
||||
|
||||
this._model.resize(this._terminal.cols, this._terminal.rows);
|
||||
this._rectangleRenderer.onResize();
|
||||
|
||||
// Resize all render layers
|
||||
this._renderLayers.forEach(l => l.resize(this._terminal, this.dimensions));
|
||||
@@ -151,6 +147,13 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
// Resize the screen
|
||||
this._core.screenElement!.style.width = `${this.dimensions.canvasWidth}px`;
|
||||
this._core.screenElement!.style.height = `${this.dimensions.canvasHeight}px`;
|
||||
|
||||
this._rectangleRenderer.onResize();
|
||||
if (this._model.selection.hasSelection) {
|
||||
// Update selection as dimensions have changed
|
||||
this._rectangleRenderer.updateSelection(this._model.selection);
|
||||
}
|
||||
|
||||
this._glyphRenderer.setDimensions(this.dimensions);
|
||||
this._glyphRenderer.onResize();
|
||||
|
||||
@@ -177,9 +180,6 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
|
||||
this._updateSelectionModel(start, end, columnSelectMode);
|
||||
|
||||
this._rectangleRenderer.updateSelection(this._model.selection);
|
||||
this._glyphRenderer.updateSelection(this._model);
|
||||
|
||||
this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 });
|
||||
}
|
||||
|
||||
@@ -214,6 +214,13 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
this._glyphRenderer.setAtlas(this._charAtlas);
|
||||
}
|
||||
|
||||
public clearCharAtlas(): void {
|
||||
this._charAtlas?.clearTexture();
|
||||
this._model.clear();
|
||||
this._updateModel(0, this._terminal.rows - 1);
|
||||
this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 });
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this._renderLayers.forEach(l => l.reset(this._terminal));
|
||||
}
|
||||
@@ -243,6 +250,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
// Tell renderer the frame is beginning
|
||||
if (this._glyphRenderer.beginFrame()) {
|
||||
this._model.clear();
|
||||
this._updateSelectionModel(undefined, undefined);
|
||||
}
|
||||
|
||||
// Update model to reflect what's drawn
|
||||
@@ -292,14 +300,19 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
}
|
||||
}
|
||||
this._rectangleRenderer.updateBackgrounds(this._model);
|
||||
if (this._model.selection.hasSelection) {
|
||||
// Model could be updated but the selection is unchanged
|
||||
this._glyphRenderer.updateSelection(this._model);
|
||||
}
|
||||
}
|
||||
|
||||
private _updateSelectionModel(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
|
||||
private _updateSelectionModel(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {
|
||||
const terminal = this._terminal;
|
||||
|
||||
// Selection does not exist
|
||||
if (!start || !end || (start[0] === end[0] && start[1] === end[1])) {
|
||||
this._model.clearSelection();
|
||||
this._rectangleRenderer.updateSelection(this._model.selection);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -312,6 +325,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
// No need to draw the selection
|
||||
if (viewportCappedStartRow >= terminal.rows || viewportCappedEndRow < 0) {
|
||||
this._model.clearSelection();
|
||||
this._rectangleRenderer.updateSelection(this._model.selection);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -323,6 +337,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
this._model.selection.viewportCappedEndRow = viewportCappedEndRow;
|
||||
this._model.selection.startCol = start[0];
|
||||
this._model.selection.endCol = end[0];
|
||||
|
||||
this._rectangleRenderer.updateSelection(this._model.selection);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,7 +20,7 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number
|
||||
background: colors.background,
|
||||
cursor: NULL_COLOR,
|
||||
cursorAccent: NULL_COLOR,
|
||||
selection: NULL_COLOR,
|
||||
selectionTransparent: NULL_COLOR,
|
||||
selectionOpaque: NULL_COLOR,
|
||||
// For the static char atlas, we only use the first 16 colors, but we need all 256 for the
|
||||
// dynamic character atlas.
|
||||
|
||||
@@ -101,7 +101,7 @@ export class WebglCharAtlas implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
protected _doWarmUp(): void {
|
||||
private _doWarmUp(): void {
|
||||
// Pre-fill with ASCII 33-126
|
||||
for (let i = 33; i < 126; i++) {
|
||||
const rasterizedGlyph = this._drawToCache(i, DEFAULT_COLOR, DEFAULT_COLOR);
|
||||
@@ -115,17 +115,26 @@ export class WebglCharAtlas implements IDisposable {
|
||||
|
||||
public beginFrame(): boolean {
|
||||
if (this._currentRowY > TEXTURE_CAPACITY) {
|
||||
this._cacheCtx.clearRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);
|
||||
this._cacheMap = {};
|
||||
this._currentRowHeight = 0;
|
||||
this._currentRowX = 0;
|
||||
this._currentRowY = 0;
|
||||
this._doWarmUp();
|
||||
this.clearTexture();
|
||||
this.warmUp();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public clearTexture(): void {
|
||||
if (this._currentRowX === 0 && this._currentRowY === 0) {
|
||||
return;
|
||||
}
|
||||
this._cacheCtx.clearRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);
|
||||
this._cacheMap = {};
|
||||
this._cacheMapCombined = {};
|
||||
this._currentRowHeight = 0;
|
||||
this._currentRowX = 0;
|
||||
this._currentRowY = 0;
|
||||
this._didWarmUp = false;
|
||||
}
|
||||
|
||||
public getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number): IRasterizedGlyph {
|
||||
let rasterizedGlyphSet = this._cacheMapCombined[chars];
|
||||
if (!rasterizedGlyphSet) {
|
||||
|
||||
@@ -21,8 +21,7 @@
|
||||
},
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../out-test/api/TestUtils"
|
||||
"../../../node_modules/@types/mocha"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
|
||||
+3
-5
@@ -7,7 +7,7 @@ import { ITerminalOptions } from '../../../src/common/Types';
|
||||
import { ITheme } from 'xterm';
|
||||
import { assert } from 'chai';
|
||||
import { openTerminal, pollFor, writeSync, getBrowserType } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from 'playwright-core';
|
||||
import { Browser, Page } from 'playwright';
|
||||
|
||||
const APP = 'http://127.0.0.1:3000/test';
|
||||
|
||||
@@ -16,14 +16,12 @@ let page: Page;
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
|
||||
let itWebgl: (expectation: string, callback?: (this: Mocha.ITestCallbackContext, done: MochaDone) => any) => Mocha.ITest | void;
|
||||
|
||||
describe('WebGL Renderer Integration Tests', async () => {
|
||||
const browserType = getBrowserType();
|
||||
const isHeadless = process.argv.indexOf('--headless') !== -1;
|
||||
// Firefox works only in non-headless mode https://github.com/microsoft/playwright/issues/1032
|
||||
const areTestsEnabled = browserType.name() === 'chromium' || (browserType.name() === 'firefox' && !isHeadless);
|
||||
itWebgl = areTestsEnabled ? it : it.skip;
|
||||
const itWebgl = areTestsEnabled ? it : it.skip;
|
||||
|
||||
itWebgl('dispose removes renderer canvases', async function(): Promise<void> {
|
||||
await setupBrowser();
|
||||
@@ -894,7 +892,7 @@ async function getCellColor(col: number, row: number): Promise<number[]> {
|
||||
|
||||
async function setupBrowser(options: ITerminalOptions = { rendererType: 'dom' }): Promise<void> {
|
||||
const browserType = getBrowserType();
|
||||
browser = await browserType.launch({ dumpio: true,
|
||||
browser = await browserType.launch({
|
||||
headless: process.argv.indexOf('--headless') !== -1
|
||||
});
|
||||
page = await (await browser.newContext()).newPage();
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"es6",
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../out-test",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"common/*": [
|
||||
"../../../src/common/*"
|
||||
],
|
||||
"browser/*": [
|
||||
"../../../src/browser/*"
|
||||
]
|
||||
},
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../node_modules/@types/node",
|
||||
"../../../out-test/api/TestUtils"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../src/common"
|
||||
},
|
||||
{
|
||||
"path": "../../../src/browser"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{ "path": "./src" },
|
||||
{ "path": "./test" }
|
||||
]
|
||||
}
|
||||
+7
-2
@@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, IDisposable, ITerminalAddon } from 'xterm';
|
||||
import { Terminal, ITerminalAddon } from 'xterm';
|
||||
|
||||
declare module 'xterm-addon-webgl' {
|
||||
/**
|
||||
@@ -15,7 +15,7 @@ declare module 'xterm-addon-webgl' {
|
||||
constructor(preserveDrawingBuffer?: boolean);
|
||||
|
||||
/**
|
||||
* Activates the addon
|
||||
* Activates the addon.
|
||||
* @param terminal The terminal the addon is being loaded in.
|
||||
*/
|
||||
public activate(terminal: Terminal): void;
|
||||
@@ -24,5 +24,10 @@ declare module 'xterm-addon-webgl' {
|
||||
* Disposes the addon.
|
||||
*/
|
||||
public dispose(): void;
|
||||
|
||||
/**
|
||||
* Clears the terminal's texture atlas and triggers a redraw.
|
||||
*/
|
||||
public clearTextureAtlas(): void;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,11 +98,6 @@ jobs:
|
||||
inputs:
|
||||
versionSpec: '1.x'
|
||||
displayName: 'Install Yarn'
|
||||
- task: CacheBeta@1
|
||||
inputs:
|
||||
key: yarn2 | $(Agent.OS) | yarn.lock
|
||||
path: node_modules
|
||||
displayName: Cache node modules
|
||||
- script: yarn --frozen-lockfile
|
||||
displayName: 'Install dependencies and build'
|
||||
- script: |
|
||||
@@ -122,11 +117,6 @@ jobs:
|
||||
inputs:
|
||||
versionSpec: '10.x'
|
||||
displayName: 'Install Node.js'
|
||||
- task: CacheBeta@1
|
||||
inputs:
|
||||
key: yarn2 | $(Agent.OS) | yarn.lock
|
||||
path: node_modules
|
||||
displayName: Cache node modules
|
||||
- script: yarn --frozen-lockfile
|
||||
displayName: 'Install dependencies and build'
|
||||
- script: |
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2020 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
const playwright = require('playwright-core');
|
||||
const fs = require('fs');
|
||||
|
||||
// Default to chromium
|
||||
let browserType = playwright['chromium'];
|
||||
const index = process.argv.indexOf('--browser');
|
||||
if (index !== -1 && process.argv.length > index + 1 && typeof process.argv[index + 1] === 'string') {
|
||||
const string = process.argv[index + 1];
|
||||
if (string === 'firefox' || string === 'webkit') {
|
||||
browserType = playwright[string];
|
||||
}
|
||||
}
|
||||
|
||||
const exists = fs.existsSync(browserType.executablePath());
|
||||
if (!exists) {
|
||||
console.log(`Downloading ${browserType.name()}`);
|
||||
browserType.downloadBrowserIfNeeded().then(() => process.exit(0));
|
||||
}
|
||||
@@ -359,7 +359,7 @@ function empty(ar) {
|
||||
}
|
||||
|
||||
function* parseMultiLineGen(filename, s) {
|
||||
if (!~s.indexOf('@vt:')) {
|
||||
if (!s.includes('@vt:')) {
|
||||
return;
|
||||
}
|
||||
const lines = s.split('\n').map(el => el.trim().replace(/[*]/, '').replace(/\s/, ''));
|
||||
@@ -413,7 +413,7 @@ function parseSingleLine(filename, s) {
|
||||
const line = s.trim();
|
||||
const match = line.match(REX_VT_LINE);
|
||||
if (match !== null) {
|
||||
if (!~TYPES.indexOf(match[2])) {
|
||||
if (!TYPES.includes(match[2])) {
|
||||
throw new Error(`unkown vt-command type "${match[2]}" specified in "${filename}"`);
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -25,7 +25,7 @@ if (fs.existsSync(addonsPath)) {
|
||||
|
||||
// walk all addon folders
|
||||
fs.readdir(addonsPath, (err, files) => {
|
||||
files.forEach(folder => {
|
||||
for (const folder of files) {
|
||||
const addonPath = path.join(addonsPath, folder);
|
||||
|
||||
// install only if there are dependencies listed
|
||||
@@ -51,6 +51,6 @@ if (fs.existsSync(addonsPath)) {
|
||||
} else {
|
||||
console.log('Skipped', folder);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+5
-5
@@ -11,7 +11,7 @@ const path = require('path');
|
||||
// Setup auth
|
||||
fs.writeFileSync(`${process.env['HOME']}/.npmrc`, `//registry.npmjs.org/:_authToken=${process.env['NPM_AUTH_TOKEN']}`);
|
||||
|
||||
const isDryRun = process.argv.indexOf('--dry') !== -1;
|
||||
const isDryRun = process.argv.includes('--dry');
|
||||
if (isDryRun) {
|
||||
console.log('Publish dry run');
|
||||
}
|
||||
@@ -36,13 +36,13 @@ const addonPackageDirs = [
|
||||
path.resolve(__dirname, '../addons/xterm-addon-webgl')
|
||||
];
|
||||
console.log(`Checking if addons need to be published`);
|
||||
addonPackageDirs.forEach(p => {
|
||||
for (const p of addonPackageDirs) {
|
||||
const addon = path.basename(p);
|
||||
if (changedFiles.some(e => e.indexOf(addon) !== -1)) {
|
||||
if (changedFiles.some(e => e.includes(addon))) {
|
||||
console.log(`Try publish ${addon}`);
|
||||
checkAndPublishPackage(p);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Publish website if it's a stable release
|
||||
if (isStableRelease) {
|
||||
@@ -54,7 +54,7 @@ function checkAndPublishPackage(packageDir) {
|
||||
|
||||
// Determine if this is a stable or beta release
|
||||
const publishedVersions = getPublishedVersions(packageJson);
|
||||
const isStableRelease = publishedVersions.indexOf(packageJson.version) === -1;
|
||||
const isStableRelease = !publishedVersions.includes(packageJson.version);
|
||||
|
||||
// Get the next version
|
||||
let nextVersion = isStableRelease ? packageJson.version : getNextBetaVersion(packageJson);
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ const env = { ...process.env };
|
||||
env.NODE_PATH = path.resolve(__dirname, '../out');
|
||||
|
||||
let testFiles = [
|
||||
'./addons/**/out/*api.js',
|
||||
'./addons/**/out-test/*api.js',
|
||||
'./out-test/**/*api.js',
|
||||
];
|
||||
|
||||
|
||||
@@ -88,19 +88,19 @@ function evalButtonCode(code) {
|
||||
if (code & 64) {
|
||||
button |= 4
|
||||
}
|
||||
let actionS = 'press';
|
||||
let action = 'press';
|
||||
let buttonS = reverseButtons[button];
|
||||
if (button === 3) {
|
||||
buttonS = '<none>';
|
||||
actionS = 'release';
|
||||
action = 'release';
|
||||
}
|
||||
if (move) {
|
||||
actionS = 'move';
|
||||
action = 'move';
|
||||
} else if (4 <= button && button <= 7) {
|
||||
buttonS = 'wheel';
|
||||
actionS = button === 4 ? 'up' : button === 5 ? 'down' : button === 6 ? 'left' : 'right';
|
||||
action = button === 4 ? 'up' : button === 5 ? 'down' : button === 6 ? 'left' : 'right';
|
||||
}
|
||||
return {button: buttonS, action: actionS, modifier};
|
||||
return {button: buttonS, action, modifier};
|
||||
}
|
||||
|
||||
// protocols
|
||||
|
||||
+4
-4
@@ -59,10 +59,10 @@
|
||||
}
|
||||
|
||||
.xterm .xterm-helper-textarea {
|
||||
/*
|
||||
* HACK: to fix IE's blinking cursor
|
||||
* Move textarea out of the screen to the far left, so that the cursor is not visible.
|
||||
*/
|
||||
padding: 0;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
/* Move textarea out of the screen to the far left, so that the cursor is not visible */
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
left: -9999em;
|
||||
|
||||
+1
-1
@@ -93,7 +93,7 @@ const paddingElement = <HTMLInputElement>document.getElementById('padding');
|
||||
|
||||
function setPadding(): void {
|
||||
term.element.style.padding = parseInt(paddingElement.value, 10).toString() + 'px';
|
||||
term.fit();
|
||||
addons.fit.instance.fit();
|
||||
}
|
||||
|
||||
function getSearchOptions(e: KeyboardEvent): ISearchOptions {
|
||||
|
||||
+9
-9
@@ -43,15 +43,15 @@ function startServer() {
|
||||
const env = Object.assign({}, process.env);
|
||||
env['COLORTERM'] = 'truecolor';
|
||||
var cols = parseInt(req.query.cols),
|
||||
rows = parseInt(req.query.rows),
|
||||
term = pty.spawn(process.platform === 'win32' ? 'cmd.exe' : 'bash', [], {
|
||||
name: 'xterm-256color',
|
||||
cols: cols || 80,
|
||||
rows: rows || 24,
|
||||
cwd: env.PWD,
|
||||
env: env,
|
||||
encoding: USE_BINARY ? null : 'utf8'
|
||||
});
|
||||
rows = parseInt(req.query.rows),
|
||||
term = pty.spawn(process.platform === 'win32' ? 'cmd.exe' : 'bash', [], {
|
||||
name: 'xterm-256color',
|
||||
cols: cols || 80,
|
||||
rows: rows || 24,
|
||||
cwd: process.platform === 'win32' ? undefined : env.PWD,
|
||||
env: env,
|
||||
encoding: USE_BINARY ? null : 'utf8'
|
||||
});
|
||||
|
||||
console.log('Created terminal with PID: ' + term.pid);
|
||||
terminals[term.pid] = term;
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
web:
|
||||
image: xtermjs/xterm.js:latest
|
||||
build: .
|
||||
volumes:
|
||||
- ./:/usr/src/app
|
||||
ports:
|
||||
- ${XTERMJS_PORT:-3000}:3000
|
||||
command: ["npm", "start"]
|
||||
|
||||
watch:
|
||||
build: .
|
||||
volumes:
|
||||
- ./:/usr/src/app
|
||||
command: ["npm", "run", "watch"]
|
||||
+22
-24
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "xterm",
|
||||
"description": "Full xterm terminal, in your browser",
|
||||
"version": "4.6.0",
|
||||
"version": "4.11.0",
|
||||
"main": "lib/xterm.js",
|
||||
"style": "css/xterm.css",
|
||||
"types": "typings/xterm.d.ts",
|
||||
@@ -11,6 +11,7 @@
|
||||
"prepackage": "npm run build",
|
||||
"package": "webpack",
|
||||
"start": "node demo/start",
|
||||
"start-debug": "node --inspect-brk demo/start",
|
||||
"lint": "eslint -c .eslintrc.json --max-warnings 0 --ext .ts src/ addons/",
|
||||
"test": "npm run test-unit",
|
||||
"posttest": "npm run lint",
|
||||
@@ -18,9 +19,6 @@
|
||||
"test-api-chromium": "node ./bin/test_api.js --browser=chromium --timeout=20000",
|
||||
"test-api-firefox": "node ./bin/test_api.js --browser=firefox --timeout=20000",
|
||||
"test-api-webkit": "node ./bin/test_api.js --browser=webkit --timeout=20000",
|
||||
"pretest-api-chromium": "node ./bin/download_browser.js --browser chromium",
|
||||
"pretest-api-firefox": "node ./bin/download_browser.js --browser firefox",
|
||||
"pretest-api-webkit": "node ./bin/download_browser.js --browser webkit",
|
||||
"test-unit": "node ./bin/test.js",
|
||||
"test-unit-coverage": "node ./bin/test.js --coverage",
|
||||
"build": "tsc -b ./tsconfig.all.json",
|
||||
@@ -36,37 +34,37 @@
|
||||
"vtfeatures": "node bin/extract_vtfeatures.js src/**/*.ts src/*.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.2.11",
|
||||
"@types/chai": "^4.2.14",
|
||||
"@types/debug": "^4.1.5",
|
||||
"@types/deep-equal": "^1.0.1",
|
||||
"@types/glob": "^7.1.1",
|
||||
"@types/jsdom": "^16.2.1",
|
||||
"@types/mocha": "^7.0.2",
|
||||
"@types/glob": "^7.1.3",
|
||||
"@types/jsdom": "^16.2.5",
|
||||
"@types/mocha": "^8.0.3",
|
||||
"@types/node": "^10.17.17",
|
||||
"@types/utf8": "^2.1.6",
|
||||
"@types/webpack": "^4.41.12",
|
||||
"@types/ws": "^7.2.4",
|
||||
"@typescript-eslint/eslint-plugin": "^2.33.0",
|
||||
"@typescript-eslint/parser": "^2.33.0",
|
||||
"@types/webpack": "^4.41.24",
|
||||
"@types/ws": "^7.2.9",
|
||||
"@typescript-eslint/eslint-plugin": "^4.0.0",
|
||||
"@typescript-eslint/parser": "^3.10.1",
|
||||
"chai": "^4.2.0",
|
||||
"deep-equal": "^2.0.3",
|
||||
"eslint": "^6.8.0",
|
||||
"deep-equal": "^2.0.4",
|
||||
"eslint": "^7.12.1",
|
||||
"express": "^4.17.1",
|
||||
"express-ws": "^4.0.0",
|
||||
"glob": "^7.0.5",
|
||||
"jsdom": "^16.2.2",
|
||||
"mocha": "^7.1.2",
|
||||
"jsdom": "^16.4.0",
|
||||
"mocha": "^8.2.1",
|
||||
"mustache": "^4.0.1",
|
||||
"node-pty": "^0.9.0",
|
||||
"nyc": "^15.0.1",
|
||||
"playwright-core": "^0.11.1",
|
||||
"source-map-loader": "^0.2.4",
|
||||
"ts-loader": "^7.0.4",
|
||||
"typescript": "3.9",
|
||||
"nyc": "^15.1.0",
|
||||
"playwright": "^1.5.2",
|
||||
"source-map-loader": "^1.1.2",
|
||||
"ts-loader": "^8.0.8",
|
||||
"typescript": "4.0",
|
||||
"utf8": "^3.0.0",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"ws": "^7.3.0",
|
||||
"webpack": "^5.4.0",
|
||||
"webpack-cli": "^4.2.0",
|
||||
"ws": "^7.3.1",
|
||||
"xterm-benchmark": "^0.1.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { addDisposableDomListener } from 'browser/Lifecycle';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { ScreenDprMonitor } from 'browser/ScreenDprMonitor';
|
||||
import { IRenderService } from 'browser/services/Services';
|
||||
import { removeElementFromParent } from 'browser/Dom';
|
||||
|
||||
const MAX_ROWS_TO_READ = 20;
|
||||
|
||||
@@ -55,8 +56,8 @@ export class AccessibilityManager extends Disposable {
|
||||
this._accessibilityTreeRoot.classList.add('xterm-accessibility');
|
||||
|
||||
this._rowContainer = document.createElement('div');
|
||||
this._rowContainer.classList.add('xterm-accessibility-tree');
|
||||
this._rowContainer.setAttribute('role', 'list');
|
||||
this._rowContainer.classList.add('xterm-accessibility-tree');
|
||||
this._rowElements = [];
|
||||
for (let i = 0; i < this._terminal.rows; i++) {
|
||||
this._rowElements[i] = this._createAccessibilityTreeNode();
|
||||
@@ -106,7 +107,7 @@ export class AccessibilityManager extends Disposable {
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
this._terminal.element?.removeChild(this._accessibilityTreeRoot);
|
||||
removeElementFromParent(this._accessibilityTreeRoot);
|
||||
this._rowElements.length = 0;
|
||||
}
|
||||
|
||||
@@ -240,9 +241,7 @@ export class AccessibilityManager extends Disposable {
|
||||
|
||||
// Only detach/attach on mac as otherwise messages can go unaccounced
|
||||
if (isMac) {
|
||||
if (this._liveRegion.parentNode) {
|
||||
this._accessibilityTreeRoot.removeChild(this._liveRegion);
|
||||
}
|
||||
removeElementFromParent(this._liveRegion);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,7 +263,7 @@ export class AccessibilityManager extends Disposable {
|
||||
const element = this._rowElements[i];
|
||||
if (element) {
|
||||
if (lineData.length === 0) {
|
||||
element.innerHTML = ' ';
|
||||
element.innerText = '\u00a0';
|
||||
} else {
|
||||
element.textContent = lineData;
|
||||
}
|
||||
|
||||
@@ -70,7 +70,6 @@ export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextA
|
||||
const top = ev.clientY - pos.top - 10;
|
||||
|
||||
// Bring textarea at the cursor position
|
||||
textarea.style.position = 'absolute';
|
||||
textarea.style.width = '20px';
|
||||
textarea.style.height = '20px';
|
||||
textarea.style.left = `${left}px`;
|
||||
@@ -78,17 +77,6 @@ export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextA
|
||||
textarea.style.zIndex = '1000';
|
||||
|
||||
textarea.focus();
|
||||
|
||||
// Reset the terminal textarea's styling
|
||||
// Timeout needs to be long enough for click event to be handled.
|
||||
setTimeout(() => {
|
||||
textarea.style.position = '';
|
||||
textarea.style.width = '';
|
||||
textarea.style.height = '';
|
||||
textarea.style.left = '';
|
||||
textarea.style.top = '';
|
||||
textarea.style.zIndex = '';
|
||||
}, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,8 +89,8 @@ export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextA
|
||||
export function rightClickHandler(ev: MouseEvent, textarea: HTMLTextAreaElement, screenElement: HTMLElement, selectionService: ISelectionService, shouldSelectWord: boolean): void {
|
||||
moveTextAreaUnderMouseCursor(ev, textarea, screenElement);
|
||||
|
||||
if (shouldSelectWord && !selectionService.isClickInSelection(ev)) {
|
||||
selectionService.selectWordAtCursor(ev);
|
||||
if (shouldSelectWord) {
|
||||
selectionService.rightClickSelect(ev);
|
||||
}
|
||||
|
||||
// Get textarea ready to copy from the context menu
|
||||
|
||||
@@ -117,11 +117,43 @@ describe('Color', () => {
|
||||
assert.deepEqual(color.opaque({ css: '#ffffffff', rgba: 0xffffffff }), { css: '#ffffff', rgba: 0xffffffFF });
|
||||
});
|
||||
});
|
||||
|
||||
describe('isOpaque', () => {
|
||||
it('should return true for opaque colors', () => {
|
||||
assert.ok(color.isOpaque(css.toColor('#000000')));
|
||||
assert.ok(color.isOpaque(css.toColor('#000000ff')));
|
||||
assert.ok(color.isOpaque(css.toColor('#808080')));
|
||||
assert.ok(color.isOpaque(css.toColor('#808080ff')));
|
||||
assert.ok(color.isOpaque(css.toColor('#ffffff')));
|
||||
assert.ok(color.isOpaque(css.toColor('#ffffffff')));
|
||||
});
|
||||
it('should return false for transparent colors', () => {
|
||||
assert.ok(!color.isOpaque(css.toColor('#00000000')));
|
||||
assert.ok(!color.isOpaque(css.toColor('#00000080')));
|
||||
assert.ok(!color.isOpaque(css.toColor('#000000fe')));
|
||||
assert.ok(!color.isOpaque(css.toColor('#80808000')));
|
||||
assert.ok(!color.isOpaque(css.toColor('#80808080')));
|
||||
assert.ok(!color.isOpaque(css.toColor('#808080fe')));
|
||||
assert.ok(!color.isOpaque(css.toColor('#ffffff00')));
|
||||
assert.ok(!color.isOpaque(css.toColor('#ffffff80')));
|
||||
assert.ok(!color.isOpaque(css.toColor('#fffffffe')));
|
||||
});
|
||||
});
|
||||
|
||||
describe('opacity', () => {
|
||||
it('should make the color transparent', () => {
|
||||
assert.deepEqual(color.opacity(css.toColor('#000000'), 0), { css: '#00000000', rgba: 0x00000000 });
|
||||
assert.deepEqual(color.opacity(css.toColor('#000000'), 0.25), { css: '#00000040', rgba: 0x00000040 });
|
||||
assert.deepEqual(color.opacity(css.toColor('#000000'), 0.5), { css: '#00000080', rgba: 0x00000080 });
|
||||
assert.deepEqual(color.opacity(css.toColor('#000000'), 0.75), { css: '#000000bf', rgba: 0x000000bf });
|
||||
assert.deepEqual(color.opacity(css.toColor('#000000'), 1), { css: '#000000ff', rgba: 0x000000ff });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('css', () => {
|
||||
describe('toColor', () => {
|
||||
it('should covert a CSS string to an IColor', () => {
|
||||
it('should convert the #rrggbb format to an IColor', () => {
|
||||
assert.deepEqual(css.toColor('#000000'), { css: '#000000', rgba: 0x000000FF });
|
||||
assert.deepEqual(css.toColor('#101010'), { css: '#101010', rgba: 0x101010FF });
|
||||
assert.deepEqual(css.toColor('#202020'), { css: '#202020', rgba: 0x202020FF });
|
||||
@@ -140,6 +172,25 @@ describe('Color', () => {
|
||||
assert.deepEqual(css.toColor('#f0f0f0'), { css: '#f0f0f0', rgba: 0xf0f0f0FF });
|
||||
assert.deepEqual(css.toColor('#ffffff'), { css: '#ffffff', rgba: 0xffffffFF });
|
||||
});
|
||||
it('should convert the #rrggbbaa format to an IColor', () => {
|
||||
assert.deepEqual(css.toColor('#00000000'), { css: '#00000000', rgba: 0x00000000 });
|
||||
assert.deepEqual(css.toColor('#10101010'), { css: '#10101010', rgba: 0x10101010 });
|
||||
assert.deepEqual(css.toColor('#20202020'), { css: '#20202020', rgba: 0x20202020 });
|
||||
assert.deepEqual(css.toColor('#30303030'), { css: '#30303030', rgba: 0x30303030 });
|
||||
assert.deepEqual(css.toColor('#40404040'), { css: '#40404040', rgba: 0x40404040 });
|
||||
assert.deepEqual(css.toColor('#50505050'), { css: '#50505050', rgba: 0x50505050 });
|
||||
assert.deepEqual(css.toColor('#60606060'), { css: '#60606060', rgba: 0x60606060 });
|
||||
assert.deepEqual(css.toColor('#70707070'), { css: '#70707070', rgba: 0x70707070 });
|
||||
assert.deepEqual(css.toColor('#80808080'), { css: '#80808080', rgba: 0x80808080 });
|
||||
assert.deepEqual(css.toColor('#90909090'), { css: '#90909090', rgba: 0x90909090 });
|
||||
assert.deepEqual(css.toColor('#a0a0a0a0'), { css: '#a0a0a0a0', rgba: 0xa0a0a0a0 });
|
||||
assert.deepEqual(css.toColor('#b0b0b0b0'), { css: '#b0b0b0b0', rgba: 0xb0b0b0b0 });
|
||||
assert.deepEqual(css.toColor('#c0c0c0c0'), { css: '#c0c0c0c0', rgba: 0xc0c0c0c0 });
|
||||
assert.deepEqual(css.toColor('#d0d0d0d0'), { css: '#d0d0d0d0', rgba: 0xd0d0d0d0 });
|
||||
assert.deepEqual(css.toColor('#e0e0e0e0'), { css: '#e0e0e0e0', rgba: 0xe0e0e0e0 });
|
||||
assert.deepEqual(css.toColor('#f0f0f0f0'), { css: '#f0f0f0f0', rgba: 0xf0f0f0f0 });
|
||||
assert.deepEqual(css.toColor('#ffffffff'), { css: '#ffffffff', rgba: 0xffffffff });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+26
-4
@@ -48,6 +48,10 @@ export namespace color {
|
||||
return { css, rgba };
|
||||
}
|
||||
|
||||
export function isOpaque(color: IColor): boolean {
|
||||
return (color.rgba & 0xFF) === 0xFF;
|
||||
}
|
||||
|
||||
export function ensureContrastRatio(bg: IColor, fg: IColor, ratio: number): IColor | undefined {
|
||||
const result = rgba.ensureContrastRatio(bg.rgba, fg.rgba, ratio);
|
||||
if (!result) {
|
||||
@@ -68,6 +72,15 @@ export namespace color {
|
||||
rgba: rgbaColor
|
||||
};
|
||||
}
|
||||
|
||||
export function opacity(color: IColor, opacity: number): IColor {
|
||||
const a = Math.round(opacity * 0xFF);
|
||||
const [r, g, b] = rgba.toChannels(color.rgba);
|
||||
return {
|
||||
css: channels.toCss(r, g, b, a),
|
||||
rgba: channels.toRgba(r, g, b, a)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,10 +88,19 @@ export namespace color {
|
||||
*/
|
||||
export namespace css {
|
||||
export function toColor(css: string): IColor {
|
||||
return {
|
||||
css,
|
||||
rgba: (parseInt(css.slice(1), 16) << 8 | 0xFF) >>> 0
|
||||
};
|
||||
switch (css.length) {
|
||||
case 7: // #rrggbb
|
||||
return {
|
||||
css,
|
||||
rgba: (parseInt(css.slice(1), 16) << 8 | 0xFF) >>> 0
|
||||
};
|
||||
case 9: // #rrggbbaa
|
||||
return {
|
||||
css,
|
||||
rgba: parseInt(css.slice(1), 16) >>> 0
|
||||
};
|
||||
}
|
||||
throw new Error('css.toColor: Unsupported css format');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,9 +17,8 @@ const DEFAULT_SELECTION = {
|
||||
rgba: 0xFFFFFF4D
|
||||
};
|
||||
|
||||
// An IIFE to generate DEFAULT_ANSI_COLORS. Do not mutate DEFAULT_ANSI_COLORS, instead make a copy
|
||||
// and mutate that.
|
||||
export const DEFAULT_ANSI_COLORS = (() => {
|
||||
// An IIFE to generate DEFAULT_ANSI_COLORS.
|
||||
export const DEFAULT_ANSI_COLORS = Object.freeze((() => {
|
||||
const colors = [
|
||||
// dark:
|
||||
css.toColor('#2e3436'),
|
||||
@@ -64,7 +63,7 @@ export const DEFAULT_ANSI_COLORS = (() => {
|
||||
}
|
||||
|
||||
return colors;
|
||||
})();
|
||||
})());
|
||||
|
||||
/**
|
||||
* Manages the source of truth for a terminal's colors.
|
||||
@@ -92,7 +91,7 @@ export class ColorManager implements IColorManager {
|
||||
background: DEFAULT_BACKGROUND,
|
||||
cursor: DEFAULT_CURSOR,
|
||||
cursorAccent: DEFAULT_CURSOR_ACCENT,
|
||||
selection: DEFAULT_SELECTION,
|
||||
selectionTransparent: DEFAULT_SELECTION,
|
||||
selectionOpaque: color.blend(DEFAULT_BACKGROUND, DEFAULT_SELECTION),
|
||||
ansi: DEFAULT_ANSI_COLORS.slice(),
|
||||
contrastCache: this._contrastCache
|
||||
@@ -115,8 +114,16 @@ export class ColorManager implements IColorManager {
|
||||
this.colors.background = this._parseColor(theme.background, DEFAULT_BACKGROUND);
|
||||
this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR, true);
|
||||
this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT, true);
|
||||
this.colors.selection = this._parseColor(theme.selection, DEFAULT_SELECTION, true);
|
||||
this.colors.selectionOpaque = color.blend(this.colors.background, this.colors.selection);
|
||||
this.colors.selectionTransparent = this._parseColor(theme.selection, DEFAULT_SELECTION, true);
|
||||
this.colors.selectionOpaque = color.blend(this.colors.background, this.colors.selectionTransparent);
|
||||
/**
|
||||
* If selection color is opaque, blend it with background with 0.3 opacity
|
||||
* Issue #2737
|
||||
*/
|
||||
if (color.isOpaque(this.colors.selectionTransparent)) {
|
||||
const opacity = 0.3;
|
||||
this.colors.selectionTransparent = color.opacity(this.colors.selectionTransparent, opacity);
|
||||
}
|
||||
this.colors.ansi[0] = this._parseColor(theme.black, DEFAULT_ANSI_COLORS[0]);
|
||||
this.colors.ansi[1] = this._parseColor(theme.red, DEFAULT_ANSI_COLORS[1]);
|
||||
this.colors.ansi[2] = this._parseColor(theme.green, DEFAULT_ANSI_COLORS[2]);
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2020 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import jsdom = require('jsdom');
|
||||
import { removeElementFromParent } from 'browser/Dom';
|
||||
import { strictEqual, doesNotThrow } from 'assert';
|
||||
|
||||
describe('Dom', () => {
|
||||
const dom = new jsdom.JSDOM();
|
||||
const document = dom.window.document;
|
||||
|
||||
describe('removeElementFromParent', () => {
|
||||
it('should remove single child', () => {
|
||||
const e = document.createElement('div');
|
||||
document.body.appendChild(e);
|
||||
strictEqual(e.parentElement, document.body);
|
||||
removeElementFromParent(e);
|
||||
strictEqual(e.parentElement, null);
|
||||
});
|
||||
it('should remove multiple elements', () => {
|
||||
const e1 = document.createElement('div');
|
||||
const e2 = document.createElement('div');
|
||||
document.body.appendChild(e1);
|
||||
document.body.appendChild(e2);
|
||||
strictEqual(e1.parentElement, document.body);
|
||||
strictEqual(e2.parentElement, document.body);
|
||||
removeElementFromParent(e1, e2);
|
||||
strictEqual(e1.parentElement, null);
|
||||
strictEqual(e2.parentElement, null);
|
||||
});
|
||||
it('should not throw on undefined', () => {
|
||||
const e = document.createElement('div');
|
||||
document.body.appendChild(e);
|
||||
strictEqual(e.parentElement, document.body);
|
||||
doesNotThrow(() => removeElementFromParent(undefined, e));
|
||||
strictEqual(e.parentElement, null);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) 2020 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
export function removeElementFromParent(...elements: (HTMLElement | undefined)[]): void {
|
||||
for (const e of elements) {
|
||||
e?.parentElement?.removeChild(e);
|
||||
}
|
||||
}
|
||||
@@ -14,9 +14,9 @@ export function addDisposableDomListener(
|
||||
node: Element | Window | Document,
|
||||
type: string,
|
||||
handler: (e: any) => void,
|
||||
useCapture?: boolean
|
||||
options?: boolean | AddEventListenerOptions
|
||||
): IDisposable {
|
||||
node.addEventListener(type, handler, useCapture);
|
||||
node.addEventListener(type, handler, options);
|
||||
let disposed = false;
|
||||
return {
|
||||
dispose: () => {
|
||||
@@ -24,7 +24,7 @@ export function addDisposableDomListener(
|
||||
return;
|
||||
}
|
||||
disposed = true;
|
||||
node.removeEventListener(type, handler, useCapture);
|
||||
node.removeEventListener(type, handler, options);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ describe('Linkifier', () => {
|
||||
bufferService.buffer.lines.push(stringToRow(text));
|
||||
}
|
||||
|
||||
function assertLinkifiesRow(rowText: string, linkMatcherRegex: RegExp, links: {x: number, length: number}[], done: MochaDone): void {
|
||||
function assertLinkifiesRow(rowText: string, linkMatcherRegex: RegExp, links: {x: number, length: number}[], done: Mocha.Done): void {
|
||||
addRow(rowText);
|
||||
linkifier.registerLinkMatcher(linkMatcherRegex, () => {});
|
||||
linkifier.linkifyRows();
|
||||
@@ -76,7 +76,7 @@ describe('Linkifier', () => {
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function assertLinkifiesMultiLineLink(rowText: string, linkMatcherRegex: RegExp, links: {x1: number, y1: number, x2: number, y2: number}[], done: MochaDone): void {
|
||||
function assertLinkifiesMultiLineLink(rowText: string, linkMatcherRegex: RegExp, links: {x1: number, y1: number, x2: number, y2: number}[], done: Mocha.Done): void {
|
||||
addRow(rowText);
|
||||
linkifier.registerLinkMatcher(linkMatcherRegex, () => {});
|
||||
linkifier.linkifyRows();
|
||||
|
||||
@@ -125,6 +125,13 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
|
||||
|
||||
private _askForLink(position: IBufferCellPosition, useLineCache: boolean): void {
|
||||
if (!this._activeProviderReplies || !useLineCache) {
|
||||
this._activeProviderReplies?.forEach(reply => {
|
||||
reply?.forEach(linkWithState => {
|
||||
if (linkWithState.link.dispose) {
|
||||
linkWithState.link.dispose();
|
||||
}
|
||||
});
|
||||
});
|
||||
this._activeProviderReplies = new Map();
|
||||
this._activeLine = position.y;
|
||||
}
|
||||
|
||||
+205
-140
@@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert, expect } from 'chai';
|
||||
import { assert } from 'chai';
|
||||
import { MockViewport, MockCompositionHelper, MockRenderer, TestTerminal } from 'browser/TestUtils.test';
|
||||
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
@@ -11,6 +11,7 @@ import { IBufferService, IUnicodeService } from 'common/services/Services';
|
||||
import { Linkifier } from 'browser/Linkifier';
|
||||
import { MockLogService, MockUnicodeService } from 'common/TestUtils.test';
|
||||
import { IRegisteredLinkMatcher, IMouseZoneManager, IMouseZone } from 'browser/Types';
|
||||
import { IMarker } from 'common/Types';
|
||||
|
||||
const INIT_COLS = 80;
|
||||
const INIT_ROWS = 24;
|
||||
@@ -74,7 +75,7 @@ describe('Terminal', () => {
|
||||
it('should fire a key event after a keypress DOM event', (done) => {
|
||||
term.onKey(e => {
|
||||
assert.equal(typeof e.key, 'string');
|
||||
expect(e.domEvent).to.be.an.instanceof(Object);
|
||||
assert.equal(e.domEvent instanceof Object, true);
|
||||
done();
|
||||
});
|
||||
const evKeyPress = <KeyboardEvent>{
|
||||
@@ -88,7 +89,7 @@ describe('Terminal', () => {
|
||||
it('should fire a key event after a keydown DOM event', (done) => {
|
||||
term.onKey(e => {
|
||||
assert.equal(typeof e.key, 'string');
|
||||
expect(e.domEvent).to.be.an.instanceof(Object);
|
||||
assert.equal(e.domEvent instanceof Object, true);
|
||||
done();
|
||||
});
|
||||
(<any>term).textarea = { value: '' };
|
||||
@@ -102,7 +103,6 @@ describe('Terminal', () => {
|
||||
});
|
||||
it('should fire the onResize event', (done) => {
|
||||
term.onResize(e => {
|
||||
expect(e).to.have.keys(['cols', 'rows']);
|
||||
assert.equal(typeof e.cols, 'number');
|
||||
assert.equal(typeof e.rows, 'number');
|
||||
done();
|
||||
@@ -723,10 +723,10 @@ describe('Terminal', () => {
|
||||
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
|
||||
term.writeSync(high + String.fromCharCode(i));
|
||||
const tchar = term.buffer.lines.get(0)!.loadCell(0, cell);
|
||||
expect(tchar.getChars()).eql(high + String.fromCharCode(i));
|
||||
expect(tchar.getChars().length).eql(2);
|
||||
expect(tchar.getWidth()).eql(1);
|
||||
expect(term.buffer.lines.get(0)!.loadCell(1, cell).getChars()).eql('');
|
||||
assert.equal(tchar.getChars(), high + String.fromCharCode(i));
|
||||
assert.equal(tchar.getChars().length, 2);
|
||||
assert.equal(tchar.getWidth(), 1);
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(1, cell).getChars(), '');
|
||||
term.reset();
|
||||
}
|
||||
});
|
||||
@@ -736,9 +736,9 @@ describe('Terminal', () => {
|
||||
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
|
||||
term.buffer.x = term.cols - 1;
|
||||
term.writeSync(high + String.fromCharCode(i));
|
||||
expect(term.buffer.lines.get(0)!.loadCell(term.buffer.x - 1, cell).getChars()).eql(high + String.fromCharCode(i));
|
||||
expect(term.buffer.lines.get(0)!.loadCell(term.buffer.x - 1, cell).getChars().length).eql(2);
|
||||
expect(term.buffer.lines.get(1)!.loadCell(0, cell).getChars()).eql('');
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(term.buffer.x - 1, cell).getChars(), high + String.fromCharCode(i));
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(term.buffer.x - 1, cell).getChars().length, 2);
|
||||
assert.equal(term.buffer.lines.get(1)!.loadCell(0, cell).getChars(), '');
|
||||
term.reset();
|
||||
}
|
||||
});
|
||||
@@ -749,10 +749,10 @@ describe('Terminal', () => {
|
||||
term.buffer.x = term.cols - 1;
|
||||
|
||||
term.writeSync('a' + high + String.fromCharCode(i));
|
||||
expect(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars()).eql('a');
|
||||
expect(term.buffer.lines.get(1)!.loadCell(0, cell).getChars()).eql(high + String.fromCharCode(i));
|
||||
expect(term.buffer.lines.get(1)!.loadCell(0, cell).getChars().length).eql(2);
|
||||
expect(term.buffer.lines.get(1)!.loadCell(1, cell).getChars()).eql('');
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars(), 'a');
|
||||
assert.equal(term.buffer.lines.get(1)!.loadCell(0, cell).getChars(), high + String.fromCharCode(i));
|
||||
assert.equal(term.buffer.lines.get(1)!.loadCell(0, cell).getChars().length, 2);
|
||||
assert.equal(term.buffer.lines.get(1)!.loadCell(1, cell).getChars(), '');
|
||||
term.reset();
|
||||
}
|
||||
});
|
||||
@@ -768,9 +768,9 @@ describe('Terminal', () => {
|
||||
}
|
||||
term.writeSync('a' + high + String.fromCharCode(i));
|
||||
// auto wraparound mode should cut off the rest of the line
|
||||
expect(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars()).eql(high + String.fromCharCode(i));
|
||||
expect(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars().length).eql(2);
|
||||
expect(term.buffer.lines.get(1)!.loadCell(1, cell).getChars()).eql('');
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars(), high + String.fromCharCode(i));
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell).getChars().length, 2);
|
||||
assert.equal(term.buffer.lines.get(1)!.loadCell(1, cell).getChars(), '');
|
||||
term.reset();
|
||||
}
|
||||
});
|
||||
@@ -781,10 +781,10 @@ describe('Terminal', () => {
|
||||
term.writeSync(high);
|
||||
term.writeSync(String.fromCharCode(i));
|
||||
const tchar = term.buffer.lines.get(0)!.loadCell(0, cell);
|
||||
expect(tchar.getChars()).eql(high + String.fromCharCode(i));
|
||||
expect(tchar.getChars().length).eql(2);
|
||||
expect(tchar.getWidth()).eql(1);
|
||||
expect(term.buffer.lines.get(0)!.loadCell(1, cell).getChars()).eql('');
|
||||
assert.equal(tchar.getChars(), high + String.fromCharCode(i));
|
||||
assert.equal(tchar.getChars().length, 2);
|
||||
assert.equal(tchar.getWidth(), 1);
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(1, cell).getChars(), '');
|
||||
term.reset();
|
||||
}
|
||||
});
|
||||
@@ -795,81 +795,81 @@ describe('Terminal', () => {
|
||||
it('café', () => {
|
||||
term.writeSync('cafe\u0301');
|
||||
term.buffer.lines.get(0)!.loadCell(3, cell);
|
||||
expect(cell.getChars()).eql('e\u0301');
|
||||
expect(cell.getChars().length).eql(2);
|
||||
expect(cell.getWidth()).eql(1);
|
||||
assert.equal(cell.getChars(), 'e\u0301');
|
||||
assert.equal(cell.getChars().length, 2);
|
||||
assert.equal(cell.getWidth(), 1);
|
||||
});
|
||||
it('café - end of line', () => {
|
||||
term.buffer.x = term.cols - 1 - 3;
|
||||
term.writeSync('cafe\u0301');
|
||||
term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell);
|
||||
expect(cell.getChars()).eql('e\u0301');
|
||||
expect(cell.getChars().length).eql(2);
|
||||
expect(cell.getWidth()).eql(1);
|
||||
assert.equal(cell.getChars(), 'e\u0301');
|
||||
assert.equal(cell.getChars().length, 2);
|
||||
assert.equal(cell.getWidth(), 1);
|
||||
term.buffer.lines.get(0)!.loadCell(1, cell);
|
||||
expect(cell.getChars()).eql('');
|
||||
expect(cell.getChars().length).eql(0);
|
||||
expect(cell.getWidth()).eql(1);
|
||||
assert.equal(cell.getChars(), '');
|
||||
assert.equal(cell.getChars().length, 0);
|
||||
assert.equal(cell.getWidth(), 1);
|
||||
});
|
||||
it('multiple combined é', () => {
|
||||
term.writeSync(Array(100).join('e\u0301'));
|
||||
for (let i = 0; i < term.cols; ++i) {
|
||||
term.buffer.lines.get(0)!.loadCell(i, cell);
|
||||
expect(cell.getChars()).eql('e\u0301');
|
||||
expect(cell.getChars().length).eql(2);
|
||||
expect(cell.getWidth()).eql(1);
|
||||
assert.equal(cell.getChars(), 'e\u0301');
|
||||
assert.equal(cell.getChars().length, 2);
|
||||
assert.equal(cell.getWidth(), 1);
|
||||
}
|
||||
term.buffer.lines.get(1)!.loadCell(0, cell);
|
||||
expect(cell.getChars()).eql('e\u0301');
|
||||
expect(cell.getChars().length).eql(2);
|
||||
expect(cell.getWidth()).eql(1);
|
||||
assert.equal(cell.getChars(), 'e\u0301');
|
||||
assert.equal(cell.getChars().length, 2);
|
||||
assert.equal(cell.getWidth(), 1);
|
||||
});
|
||||
it('multiple surrogate with combined', () => {
|
||||
term.writeSync(Array(100).join('\uD800\uDC00\u0301'));
|
||||
for (let i = 0; i < term.cols; ++i) {
|
||||
term.buffer.lines.get(0)!.loadCell(i, cell);
|
||||
expect(cell.getChars()).eql('\uD800\uDC00\u0301');
|
||||
expect(cell.getChars().length).eql(3);
|
||||
expect(cell.getWidth()).eql(1);
|
||||
assert.equal(cell.getChars(), '\uD800\uDC00\u0301');
|
||||
assert.equal(cell.getChars().length, 3);
|
||||
assert.equal(cell.getWidth(), 1);
|
||||
}
|
||||
term.buffer.lines.get(1)!.loadCell(0, cell);
|
||||
expect(cell.getChars()).eql('\uD800\uDC00\u0301');
|
||||
expect(cell.getChars().length).eql(3);
|
||||
expect(cell.getWidth()).eql(1);
|
||||
assert.equal(cell.getChars(), '\uD800\uDC00\u0301');
|
||||
assert.equal(cell.getChars().length, 3);
|
||||
assert.equal(cell.getWidth(), 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('unicode - fullwidth characters', () => {
|
||||
const cell = new CellData();
|
||||
it('cursor movement even', () => {
|
||||
expect(term.buffer.x).eql(0);
|
||||
assert.equal(term.buffer.x, 0);
|
||||
term.writeSync('¥');
|
||||
expect(term.buffer.x).eql(2);
|
||||
assert.equal(term.buffer.x, 2);
|
||||
});
|
||||
it('cursor movement odd', () => {
|
||||
term.buffer.x = 1;
|
||||
expect(term.buffer.x).eql(1);
|
||||
assert.equal(term.buffer.x, 1);
|
||||
term.writeSync('¥');
|
||||
expect(term.buffer.x).eql(3);
|
||||
assert.equal(term.buffer.x, 3);
|
||||
});
|
||||
it('line of ¥ even', () => {
|
||||
term.writeSync(Array(50).join('¥'));
|
||||
for (let i = 0; i < term.cols; ++i) {
|
||||
term.buffer.lines.get(0)!.loadCell(i, cell);
|
||||
if (i % 2) {
|
||||
expect(cell.getChars()).eql('');
|
||||
expect(cell.getChars().length).eql(0);
|
||||
expect(cell.getWidth()).eql(0);
|
||||
assert.equal(cell.getChars(), '');
|
||||
assert.equal(cell.getChars().length, 0);
|
||||
assert.equal(cell.getWidth(), 0);
|
||||
} else {
|
||||
expect(cell.getChars()).eql('¥');
|
||||
expect(cell.getChars().length).eql(1);
|
||||
expect(cell.getWidth()).eql(2);
|
||||
assert.equal(cell.getChars(), '¥');
|
||||
assert.equal(cell.getChars().length, 1);
|
||||
assert.equal(cell.getWidth(), 2);
|
||||
}
|
||||
}
|
||||
term.buffer.lines.get(1)!.loadCell(0, cell);
|
||||
expect(cell.getChars()).eql('¥');
|
||||
expect(cell.getChars().length).eql(1);
|
||||
expect(cell.getWidth()).eql(2);
|
||||
assert.equal(cell.getChars(), '¥');
|
||||
assert.equal(cell.getChars().length, 1);
|
||||
assert.equal(cell.getWidth(), 2);
|
||||
});
|
||||
it('line of ¥ odd', () => {
|
||||
term.buffer.x = 1;
|
||||
@@ -877,23 +877,23 @@ describe('Terminal', () => {
|
||||
for (let i = 1; i < term.cols - 1; ++i) {
|
||||
term.buffer.lines.get(0)!.loadCell(i, cell);
|
||||
if (!(i % 2)) {
|
||||
expect(cell.getChars()).eql('');
|
||||
expect(cell.getChars().length).eql(0);
|
||||
expect(cell.getWidth()).eql(0);
|
||||
assert.equal(cell.getChars(), '');
|
||||
assert.equal(cell.getChars().length, 0);
|
||||
assert.equal(cell.getWidth(), 0);
|
||||
} else {
|
||||
expect(cell.getChars()).eql('¥');
|
||||
expect(cell.getChars().length).eql(1);
|
||||
expect(cell.getWidth()).eql(2);
|
||||
assert.equal(cell.getChars(), '¥');
|
||||
assert.equal(cell.getChars().length, 1);
|
||||
assert.equal(cell.getWidth(), 2);
|
||||
}
|
||||
}
|
||||
term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell);
|
||||
expect(cell.getChars()).eql('');
|
||||
expect(cell.getChars().length).eql(0);
|
||||
expect(cell.getWidth()).eql(1);
|
||||
assert.equal(cell.getChars(), '');
|
||||
assert.equal(cell.getChars().length, 0);
|
||||
assert.equal(cell.getWidth(), 1);
|
||||
term.buffer.lines.get(1)!.loadCell(0, cell);
|
||||
expect(cell.getChars()).eql('¥');
|
||||
expect(cell.getChars().length).eql(1);
|
||||
expect(cell.getWidth()).eql(2);
|
||||
assert.equal(cell.getChars(), '¥');
|
||||
assert.equal(cell.getChars().length, 1);
|
||||
assert.equal(cell.getWidth(), 2);
|
||||
});
|
||||
it('line of ¥ with combining odd', () => {
|
||||
term.buffer.x = 1;
|
||||
@@ -901,42 +901,42 @@ describe('Terminal', () => {
|
||||
for (let i = 1; i < term.cols - 1; ++i) {
|
||||
term.buffer.lines.get(0)!.loadCell(i, cell);
|
||||
if (!(i % 2)) {
|
||||
expect(cell.getChars()).eql('');
|
||||
expect(cell.getChars().length).eql(0);
|
||||
expect(cell.getWidth()).eql(0);
|
||||
assert.equal(cell.getChars(), '');
|
||||
assert.equal(cell.getChars().length, 0);
|
||||
assert.equal(cell.getWidth(), 0);
|
||||
} else {
|
||||
expect(cell.getChars()).eql('¥\u0301');
|
||||
expect(cell.getChars().length).eql(2);
|
||||
expect(cell.getWidth()).eql(2);
|
||||
assert.equal(cell.getChars(), '¥\u0301');
|
||||
assert.equal(cell.getChars().length, 2);
|
||||
assert.equal(cell.getWidth(), 2);
|
||||
}
|
||||
}
|
||||
term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell);
|
||||
expect(cell.getChars()).eql('');
|
||||
expect(cell.getChars().length).eql(0);
|
||||
expect(cell.getWidth()).eql(1);
|
||||
assert.equal(cell.getChars(), '');
|
||||
assert.equal(cell.getChars().length, 0);
|
||||
assert.equal(cell.getWidth(), 1);
|
||||
term.buffer.lines.get(1)!.loadCell(0, cell);
|
||||
expect(cell.getChars()).eql('¥\u0301');
|
||||
expect(cell.getChars().length).eql(2);
|
||||
expect(cell.getWidth()).eql(2);
|
||||
assert.equal(cell.getChars(), '¥\u0301');
|
||||
assert.equal(cell.getChars().length, 2);
|
||||
assert.equal(cell.getWidth(), 2);
|
||||
});
|
||||
it('line of ¥ with combining even', () => {
|
||||
term.writeSync(Array(50).join('¥\u0301'));
|
||||
for (let i = 0; i < term.cols; ++i) {
|
||||
term.buffer.lines.get(0)!.loadCell(i, cell);
|
||||
if (i % 2) {
|
||||
expect(cell.getChars()).eql('');
|
||||
expect(cell.getChars().length).eql(0);
|
||||
expect(cell.getWidth()).eql(0);
|
||||
assert.equal(cell.getChars(), '');
|
||||
assert.equal(cell.getChars().length, 0);
|
||||
assert.equal(cell.getWidth(), 0);
|
||||
} else {
|
||||
expect(cell.getChars()).eql('¥\u0301');
|
||||
expect(cell.getChars().length).eql(2);
|
||||
expect(cell.getWidth()).eql(2);
|
||||
assert.equal(cell.getChars(), '¥\u0301');
|
||||
assert.equal(cell.getChars().length, 2);
|
||||
assert.equal(cell.getWidth(), 2);
|
||||
}
|
||||
}
|
||||
term.buffer.lines.get(1)!.loadCell(0, cell);
|
||||
expect(cell.getChars()).eql('¥\u0301');
|
||||
expect(cell.getChars().length).eql(2);
|
||||
expect(cell.getWidth()).eql(2);
|
||||
assert.equal(cell.getChars(), '¥\u0301');
|
||||
assert.equal(cell.getChars().length, 2);
|
||||
assert.equal(cell.getWidth(), 2);
|
||||
});
|
||||
it('line of surrogate fullwidth with combining odd', () => {
|
||||
term.buffer.x = 1;
|
||||
@@ -944,42 +944,42 @@ describe('Terminal', () => {
|
||||
for (let i = 1; i < term.cols - 1; ++i) {
|
||||
term.buffer.lines.get(0)!.loadCell(i, cell);
|
||||
if (!(i % 2)) {
|
||||
expect(cell.getChars()).eql('');
|
||||
expect(cell.getChars().length).eql(0);
|
||||
expect(cell.getWidth()).eql(0);
|
||||
assert.equal(cell.getChars(), '');
|
||||
assert.equal(cell.getChars().length, 0);
|
||||
assert.equal(cell.getWidth(), 0);
|
||||
} else {
|
||||
expect(cell.getChars()).eql('\ud843\ude6d\u0301');
|
||||
expect(cell.getChars().length).eql(3);
|
||||
expect(cell.getWidth()).eql(2);
|
||||
assert.equal(cell.getChars(), '\ud843\ude6d\u0301');
|
||||
assert.equal(cell.getChars().length, 3);
|
||||
assert.equal(cell.getWidth(), 2);
|
||||
}
|
||||
}
|
||||
term.buffer.lines.get(0)!.loadCell(term.cols - 1, cell);
|
||||
expect(cell.getChars()).eql('');
|
||||
expect(cell.getChars().length).eql(0);
|
||||
expect(cell.getWidth()).eql(1);
|
||||
assert.equal(cell.getChars(), '');
|
||||
assert.equal(cell.getChars().length, 0);
|
||||
assert.equal(cell.getWidth(), 1);
|
||||
term.buffer.lines.get(1)!.loadCell(0, cell);
|
||||
expect(cell.getChars()).eql('\ud843\ude6d\u0301');
|
||||
expect(cell.getChars().length).eql(3);
|
||||
expect(cell.getWidth()).eql(2);
|
||||
assert.equal(cell.getChars(), '\ud843\ude6d\u0301');
|
||||
assert.equal(cell.getChars().length, 3);
|
||||
assert.equal(cell.getWidth(), 2);
|
||||
});
|
||||
it('line of surrogate fullwidth with combining even', () => {
|
||||
term.writeSync(Array(50).join('\ud843\ude6d\u0301'));
|
||||
for (let i = 0; i < term.cols; ++i) {
|
||||
term.buffer.lines.get(0)!.loadCell(i, cell);
|
||||
if (i % 2) {
|
||||
expect(cell.getChars()).eql('');
|
||||
expect(cell.getChars().length).eql(0);
|
||||
expect(cell.getWidth()).eql(0);
|
||||
assert.equal(cell.getChars(), '');
|
||||
assert.equal(cell.getChars().length, 0);
|
||||
assert.equal(cell.getWidth(), 0);
|
||||
} else {
|
||||
expect(cell.getChars()).eql('\ud843\ude6d\u0301');
|
||||
expect(cell.getChars().length).eql(3);
|
||||
expect(cell.getWidth()).eql(2);
|
||||
assert.equal(cell.getChars(), '\ud843\ude6d\u0301');
|
||||
assert.equal(cell.getChars().length, 3);
|
||||
assert.equal(cell.getWidth(), 2);
|
||||
}
|
||||
}
|
||||
term.buffer.lines.get(1)!.loadCell(0, cell);
|
||||
expect(cell.getChars()).eql('\ud843\ude6d\u0301');
|
||||
expect(cell.getChars().length).eql(3);
|
||||
expect(cell.getWidth()).eql(2);
|
||||
assert.equal(cell.getChars(), '\ud843\ude6d\u0301');
|
||||
assert.equal(cell.getChars().length, 3);
|
||||
assert.equal(cell.getWidth(), 2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -991,11 +991,11 @@ describe('Terminal', () => {
|
||||
term.buffer.y = 0;
|
||||
term.write('\x1b[4h');
|
||||
term.writeSync('abcde');
|
||||
expect(term.buffer.lines.get(0)!.length).eql(term.cols);
|
||||
expect(term.buffer.lines.get(0)!.loadCell(10, cell).getChars()).eql('a');
|
||||
expect(term.buffer.lines.get(0)!.loadCell(14, cell).getChars()).eql('e');
|
||||
expect(term.buffer.lines.get(0)!.loadCell(15, cell).getChars()).eql('0');
|
||||
expect(term.buffer.lines.get(0)!.loadCell(79, cell).getChars()).eql('4');
|
||||
assert.equal(term.buffer.lines.get(0)!.length, term.cols);
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(10, cell).getChars(), 'a');
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(14, cell).getChars(), 'e');
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(15, cell).getChars(), '0');
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(79, cell).getChars(), '4');
|
||||
});
|
||||
it('fullwidth - insert', () => {
|
||||
term.writeSync(Array(9).join('0123456789').slice(-80));
|
||||
@@ -1003,12 +1003,12 @@ describe('Terminal', () => {
|
||||
term.buffer.y = 0;
|
||||
term.write('\x1b[4h');
|
||||
term.writeSync('¥¥¥');
|
||||
expect(term.buffer.lines.get(0)!.length).eql(term.cols);
|
||||
expect(term.buffer.lines.get(0)!.loadCell(10, cell).getChars()).eql('¥');
|
||||
expect(term.buffer.lines.get(0)!.loadCell(11, cell).getChars()).eql('');
|
||||
expect(term.buffer.lines.get(0)!.loadCell(14, cell).getChars()).eql('¥');
|
||||
expect(term.buffer.lines.get(0)!.loadCell(15, cell).getChars()).eql('');
|
||||
expect(term.buffer.lines.get(0)!.loadCell(79, cell).getChars()).eql('3');
|
||||
assert.equal(term.buffer.lines.get(0)!.length, term.cols);
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(10, cell).getChars(), '¥');
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(11, cell).getChars(), '');
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(14, cell).getChars(), '¥');
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(15, cell).getChars(), '');
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(79, cell).getChars(), '3');
|
||||
});
|
||||
it('fullwidth - right border', () => {
|
||||
term.writeSync(Array(41).join('¥'));
|
||||
@@ -1016,15 +1016,15 @@ describe('Terminal', () => {
|
||||
term.buffer.y = 0;
|
||||
term.write('\x1b[4h');
|
||||
term.writeSync('a');
|
||||
expect(term.buffer.lines.get(0)!.length).eql(term.cols);
|
||||
expect(term.buffer.lines.get(0)!.loadCell(10, cell).getChars()).eql('a');
|
||||
expect(term.buffer.lines.get(0)!.loadCell(11, cell).getChars()).eql('¥');
|
||||
expect(term.buffer.lines.get(0)!.loadCell(79, cell).getChars()).eql(''); // fullwidth char got replaced
|
||||
assert.equal(term.buffer.lines.get(0)!.length, term.cols);
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(10, cell).getChars(), 'a');
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(11, cell).getChars(), '¥');
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(79, cell).getChars(), ''); // fullwidth char got replaced
|
||||
term.writeSync('b');
|
||||
expect(term.buffer.lines.get(0)!.length).eql(term.cols);
|
||||
expect(term.buffer.lines.get(0)!.loadCell(11, cell).getChars()).eql('b');
|
||||
expect(term.buffer.lines.get(0)!.loadCell(12, cell).getChars()).eql('¥');
|
||||
expect(term.buffer.lines.get(0)!.loadCell(79, cell).getChars()).eql(''); // empty cell after fullwidth
|
||||
assert.equal(term.buffer.lines.get(0)!.length, term.cols);
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(11, cell).getChars(), 'b');
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(12, cell).getChars(), '¥');
|
||||
assert.equal(term.buffer.lines.get(0)!.loadCell(79, cell).getChars(), ''); // empty cell after fullwidth
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1042,7 +1042,7 @@ describe('Terminal', () => {
|
||||
linkifier.attachToDom({} as any, mouseZoneManager);
|
||||
});
|
||||
|
||||
function assertLinkifiesInTerminal(rowText: string, linkMatcherRegex: RegExp, links: {x1: number, y1: number, x2: number, y2: number}[], done: MochaDone): void {
|
||||
function assertLinkifiesInTerminal(rowText: string, linkMatcherRegex: RegExp, links: {x1: number, y1: number, x2: number, y2: number}[], done: Mocha.Done): void {
|
||||
terminal.writeSync(rowText);
|
||||
linkifier.registerLinkMatcher(linkMatcherRegex, () => {});
|
||||
linkifier.linkifyRows();
|
||||
@@ -1345,7 +1345,7 @@ describe('Terminal', () => {
|
||||
];
|
||||
terminal.writeSync(data.join(''));
|
||||
// brute force test with insane values
|
||||
expect(() => {
|
||||
assert.doesNotThrow(() => {
|
||||
for (let overscan = 0; overscan < 20; ++overscan) {
|
||||
for (let start = -10; start < 20; ++start) {
|
||||
for (let end = -10; end < 20; ++end) {
|
||||
@@ -1356,7 +1356,7 @@ describe('Terminal', () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
}).to.not.throw();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1405,18 +1405,18 @@ describe('Terminal', () => {
|
||||
// not converting
|
||||
const termNotConverting = new TestTerminal({cols: 15, rows: 10});
|
||||
termNotConverting.writeSync('Hello\nWorld');
|
||||
expect(termNotConverting.buffer.lines.get(0)!.translateToString(false)).equals('Hello ');
|
||||
expect(termNotConverting.buffer.lines.get(1)!.translateToString(false)).equals(' World ');
|
||||
expect(termNotConverting.buffer.lines.get(0)!.translateToString(true)).equals('Hello');
|
||||
expect(termNotConverting.buffer.lines.get(1)!.translateToString(true)).equals(' World');
|
||||
assert.equal(termNotConverting.buffer.lines.get(0)!.translateToString(false), 'Hello ');
|
||||
assert.equal(termNotConverting.buffer.lines.get(1)!.translateToString(false), ' World ');
|
||||
assert.equal(termNotConverting.buffer.lines.get(0)!.translateToString(true), 'Hello');
|
||||
assert.equal(termNotConverting.buffer.lines.get(1)!.translateToString(true), ' World');
|
||||
|
||||
// converting
|
||||
const termConverting = new TestTerminal({cols: 15, rows: 10, convertEol: true});
|
||||
termConverting.writeSync('Hello\nWorld');
|
||||
expect(termConverting.buffer.lines.get(0)!.translateToString(false)).equals('Hello ');
|
||||
expect(termConverting.buffer.lines.get(1)!.translateToString(false)).equals('World ');
|
||||
expect(termConverting.buffer.lines.get(0)!.translateToString(true)).equals('Hello');
|
||||
expect(termConverting.buffer.lines.get(1)!.translateToString(true)).equals('World');
|
||||
assert.equal(termConverting.buffer.lines.get(0)!.translateToString(false), 'Hello ');
|
||||
assert.equal(termConverting.buffer.lines.get(1)!.translateToString(false), 'World ');
|
||||
assert.equal(termConverting.buffer.lines.get(0)!.translateToString(true), 'Hello');
|
||||
assert.equal(termConverting.buffer.lines.get(1)!.translateToString(true), 'World');
|
||||
});
|
||||
describe('Terminal InputHandler integration', () => {
|
||||
function getLines(term: TestTerminal, limit: number = term.rows): string[] {
|
||||
@@ -1533,6 +1533,71 @@ describe('Terminal', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// FIXME: move to common/CoreTerminal.test once the trimming is moved over
|
||||
describe('marker lifecycle', () => {
|
||||
// create a 10x5 terminal with markers on every line
|
||||
// to test marker lifecycle under various terminal actions
|
||||
let markers: IMarker[];
|
||||
let disposeStack: IMarker[];
|
||||
let term: TestTerminal;
|
||||
beforeEach(() => {
|
||||
term = new TestTerminal({});
|
||||
markers = [];
|
||||
disposeStack = [];
|
||||
term.optionsService.setOption('scrollback', 1);
|
||||
term.resize(10, 5);
|
||||
markers.push(term.buffers.active.addMarker(term.buffers.active.y));
|
||||
term.writeSync('\x1b[r0\r\n');
|
||||
markers.push(term.buffers.active.addMarker(term.buffers.active.y));
|
||||
term.writeSync('1\r\n');
|
||||
markers.push(term.buffers.active.addMarker(term.buffers.active.y));
|
||||
term.writeSync('2\r\n');
|
||||
markers.push(term.buffers.active.addMarker(term.buffers.active.y));
|
||||
term.writeSync('3\r\n');
|
||||
markers.push(term.buffers.active.addMarker(term.buffers.active.y));
|
||||
term.writeSync('4');
|
||||
for (let i = 0; i < markers.length; ++i) {
|
||||
const marker = markers[i];
|
||||
marker.onDispose(() => disposeStack.push(marker));
|
||||
}
|
||||
});
|
||||
it('initial', () => {
|
||||
assert.deepEqual(markers.map(m => m.line), [0, 1, 2, 3, 4]);
|
||||
});
|
||||
it('should dispose on normal trim off the top', () => {
|
||||
// moves top line into scrollback
|
||||
term.writeSync('\n');
|
||||
assert.deepEqual(disposeStack, []);
|
||||
// trims first marker
|
||||
term.writeSync('\n');
|
||||
assert.deepEqual(disposeStack, [markers[0]]);
|
||||
// trims second marker
|
||||
term.writeSync('\n');
|
||||
assert.deepEqual(disposeStack, [markers[0], markers[1]]);
|
||||
// trimmed marker objs should be disposed
|
||||
assert.deepEqual(disposeStack.map(el => el.isDisposed), [true, true]);
|
||||
assert.deepEqual(disposeStack.map(el => (el as any)._isDisposed), [true, true]);
|
||||
// trimmed markers should contain line -1
|
||||
assert.deepEqual(disposeStack.map(el => el.line), [-1, -1]);
|
||||
});
|
||||
it('should dispose on DL', () => {
|
||||
term.writeSync('\x1b[3;1H'); // move cursor to 0, 2
|
||||
term.writeSync('\x1b[2M'); // delete 2 lines
|
||||
assert.deepEqual(disposeStack, [markers[2], markers[3]]);
|
||||
});
|
||||
it('should dispose on IL', () => {
|
||||
term.writeSync('\x1b[3;1H'); // move cursor to 0, 2
|
||||
term.writeSync('\x1b[2L'); // insert 2 lines
|
||||
assert.deepEqual(disposeStack, [markers[4], markers[3]]);
|
||||
assert.deepEqual(markers.map(el => el.line), [0, 1, 4, -1, -1]);
|
||||
});
|
||||
it('should dispose on resize', () => {
|
||||
term.resize(10, 2);
|
||||
assert.deepEqual(disposeStack, [markers[0], markers[1]]);
|
||||
assert.deepEqual(markers.map(el => el.line), [-1, -1, 0, 1, 2]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class TestLinkifier extends Linkifier {
|
||||
|
||||
+46
-8
@@ -39,7 +39,7 @@ import { MouseZoneManager } from 'browser/MouseZoneManager';
|
||||
import { AccessibilityManager } from './AccessibilityManager';
|
||||
import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider } from 'xterm';
|
||||
import { DomRenderer } from 'browser/renderer/dom/DomRenderer';
|
||||
import { IKeyboardEvent, KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions } from 'common/Types';
|
||||
import { IKeyboardEvent, KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, IAnsiColorChangeEvent } from 'common/Types';
|
||||
import { evaluateKeyboardEvent } from 'common/input/Keyboard';
|
||||
import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter';
|
||||
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||
@@ -53,6 +53,7 @@ import { Linkifier2 } from 'browser/Linkifier2';
|
||||
import { CoreBrowserService } from 'browser/services/CoreBrowserService';
|
||||
import { CoreTerminal } from 'common/CoreTerminal';
|
||||
import { ITerminalOptions as IInitializedTerminalOptions } from 'common/services/Services';
|
||||
import { rgba } from 'browser/Color';
|
||||
|
||||
// Let it work inside Node.js for automated testing purposes.
|
||||
const document: Document = (typeof window !== 'undefined') ? window.document : null as any;
|
||||
@@ -148,6 +149,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
this.register(this._inputHandler.onRequestReset(() => this.reset()));
|
||||
this.register(this._inputHandler.onRequestScroll((eraseAttr, isWrapped) => this.scroll(eraseAttr, isWrapped || undefined)));
|
||||
this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type)));
|
||||
this.register(this._inputHandler.onAnsiColorChange((event) => this._changeAnsiColor(event)));
|
||||
this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove));
|
||||
this.register(forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange));
|
||||
this.register(forwardEvent(this._inputHandler.onA11yChar, this._onA11yCharEmitter));
|
||||
@@ -157,6 +159,19 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
this.register(this._bufferService.onResize(e => this._afterResize(e.cols, e.rows)));
|
||||
}
|
||||
|
||||
private _changeAnsiColor(event: IAnsiColorChangeEvent): void {
|
||||
if (!this._colorManager) { return; }
|
||||
|
||||
event.colors.forEach(ansiColor => {
|
||||
const color = rgba.toColor(ansiColor.red, ansiColor.green, ansiColor.blue);
|
||||
|
||||
this._colorManager!.colors.ansi[ansiColor.colorIndex] = color;
|
||||
});
|
||||
|
||||
this._renderService?.setColors(this._colorManager!.colors);
|
||||
this.viewport?.onThemeChange(this._colorManager!.colors);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this._isDisposed) {
|
||||
return;
|
||||
@@ -281,6 +296,25 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
this._onBlur.fire();
|
||||
}
|
||||
|
||||
private _syncTextArea(): void {
|
||||
if (!this.textarea || !this.buffer.isCursorInViewport || this._compositionHelper!.isComposing) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cellHeight = Math.ceil(this._charSizeService!.height * this.optionsService.options.lineHeight);
|
||||
const cursorTop = this._bufferService.buffer.y * cellHeight;
|
||||
const cursorLeft = this._bufferService.buffer.x * this._charSizeService!.width;
|
||||
|
||||
// Sync the textarea to the exact position of the composition view so the IME knows where the
|
||||
// text is.
|
||||
this.textarea.style.left = cursorLeft + 'px';
|
||||
this.textarea.style.top = cursorTop + 'px';
|
||||
this.textarea.style.width = this._charSizeService!.width + 'px';
|
||||
this.textarea.style.height = cellHeight + 'px';
|
||||
this.textarea.style.lineHeight = cellHeight + 'px';
|
||||
this.textarea.style.zIndex = '-5';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize default behavior
|
||||
*/
|
||||
@@ -352,7 +386,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
throw new Error('Terminal requires a parent element.');
|
||||
}
|
||||
|
||||
if (!document.body.contains(parent)) {
|
||||
if (!parent.isConnected) {
|
||||
this._logService.debug('Terminal.open was called on an element that was not attached to the DOM');
|
||||
}
|
||||
|
||||
@@ -364,6 +398,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
this.element.classList.add('terminal');
|
||||
this.element.classList.add('xterm');
|
||||
this.element.setAttribute('tabindex', '0');
|
||||
this.element.setAttribute('role', 'document');
|
||||
parent.appendChild(this.element);
|
||||
|
||||
// Performance: Use a document fragment to build the terminal
|
||||
@@ -436,7 +471,10 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
this.register(this._inputHandler.onRequestSyncScrollBar(() => this.viewport!.syncScrollArea()));
|
||||
this.register(this.viewport);
|
||||
|
||||
this.register(this.onCursorMove(() => this._renderService!.onCursorMove()));
|
||||
this.register(this.onCursorMove(() => {
|
||||
this._renderService!.onCursorMove();
|
||||
this._syncTextArea();
|
||||
}));
|
||||
this.register(this.onResize(() => this._renderService!.onResize(this.cols, this.rows)));
|
||||
this.register(this.onBlur(() => this._renderService!.onBlur()));
|
||||
this.register(this.onFocus(() => this._renderService!.onFocus()));
|
||||
@@ -674,7 +712,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
el.removeEventListener('wheel', requestedEvents.wheel!);
|
||||
requestedEvents.wheel = null;
|
||||
} else if (!requestedEvents.wheel) {
|
||||
el.addEventListener('wheel', eventListeners.wheel);
|
||||
el.addEventListener('wheel', eventListeners.wheel, { passive: false });
|
||||
requestedEvents.wheel = eventListeners.wheel;
|
||||
}
|
||||
|
||||
@@ -747,7 +785,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
}
|
||||
return;
|
||||
}
|
||||
}));
|
||||
}, { passive: true }));
|
||||
|
||||
// allow wheel scrolling in
|
||||
// the shell for example
|
||||
@@ -756,20 +794,20 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
if (!this.viewport!.onWheel(ev)) {
|
||||
return this.cancel(ev);
|
||||
}
|
||||
}));
|
||||
}, { passive: false }));
|
||||
|
||||
this.register(addDisposableDomListener(el, 'touchstart', (ev: TouchEvent) => {
|
||||
if (this._coreMouseService.areMouseEventsActive) return;
|
||||
this.viewport!.onTouchStart(ev);
|
||||
return this.cancel(ev);
|
||||
}));
|
||||
}, { passive: true }));
|
||||
|
||||
this.register(addDisposableDomListener(el, 'touchmove', (ev: TouchEvent) => {
|
||||
if (this._coreMouseService.areMouseEventsActive) return;
|
||||
if (!this.viewport!.onTouchMove(ev)) {
|
||||
return this.cancel(ev);
|
||||
}
|
||||
}));
|
||||
}, { passive: false }));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ if (os.platform() === 'darwin') {
|
||||
);
|
||||
}
|
||||
// filter skipFilenames
|
||||
const FILES = TESTFILES.filter(value => SKIP_FILES.indexOf(value.split('/').slice(-1)[0]) === -1);
|
||||
const FILES = TESTFILES.filter(value => !SKIP_FILES.includes(value.split('/').slice(-1)[0]));
|
||||
|
||||
describe('Escape Sequence Files', function(): void {
|
||||
this.timeout(1000);
|
||||
@@ -60,7 +60,7 @@ describe('Escape Sequence Files', function(): void {
|
||||
ptyTerm._master.destroy();
|
||||
});
|
||||
|
||||
FILES.forEach(filename => {
|
||||
for (const filename of FILES) {
|
||||
(process.platform === 'win32' ? it.skip : it)(filename.split('/').slice(-1)[0], async () => {
|
||||
// reset terminal and handler
|
||||
if (customHandler) {
|
||||
@@ -93,7 +93,7 @@ describe('Escape Sequence Files', function(): void {
|
||||
throw new Error(formatError(fs.readFileSync(filename, 'utf8'), content, expected));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -104,7 +104,7 @@ describe('Escape Sequence Files', function(): void {
|
||||
function formatError(input: string, output: string, expected: string): string {
|
||||
function addLineNumber(start: number, color: string): (s: string) => string {
|
||||
let counter = start || 0;
|
||||
return function(s: string): string {
|
||||
return (s: string): string => {
|
||||
counter += 1;
|
||||
return '\x1b[33m' + (' ' + counter).slice(-2) + color + s;
|
||||
};
|
||||
|
||||
@@ -309,6 +309,9 @@ export class MockViewport implements IViewport {
|
||||
}
|
||||
|
||||
export class MockCompositionHelper implements ICompositionHelper {
|
||||
public get isComposing(): boolean {
|
||||
return false;
|
||||
}
|
||||
public compositionstart(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
Vendored
+3
-1
@@ -87,6 +87,7 @@ export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean;
|
||||
export type LineData = CharData[];
|
||||
|
||||
export interface ICompositionHelper {
|
||||
readonly isComposing: boolean;
|
||||
compositionstart(): void;
|
||||
compositionupdate(ev: CompositionEvent): void;
|
||||
compositionend(): void;
|
||||
@@ -120,7 +121,7 @@ export interface IColorSet {
|
||||
background: IColor;
|
||||
cursor: IColor;
|
||||
cursorAccent: IColor;
|
||||
selection: IColor;
|
||||
selectionTransparent: IColor;
|
||||
/** The selection blended on top of background. */
|
||||
selectionOpaque: IColor;
|
||||
ansi: IColor[];
|
||||
@@ -274,6 +275,7 @@ interface ILink {
|
||||
activate(event: MouseEvent, text: string): void;
|
||||
hover?(event: MouseEvent, text: string): void;
|
||||
leave?(event: MouseEvent, text: string): void;
|
||||
dispose?(): void;
|
||||
}
|
||||
|
||||
interface ILinkDecorations {
|
||||
|
||||
@@ -22,6 +22,7 @@ export class CompositionHelper {
|
||||
* IME. This variable determines whether the compositionText should be displayed on the UI.
|
||||
*/
|
||||
private _isComposing: boolean;
|
||||
public get isComposing(): boolean { return this._isComposing; }
|
||||
|
||||
/**
|
||||
* The position within the input textarea's value of the current composition.
|
||||
@@ -34,6 +35,11 @@ export class CompositionHelper {
|
||||
*/
|
||||
private _isSendingComposition: boolean;
|
||||
|
||||
/**
|
||||
* Data already sent due to keydown event.
|
||||
*/
|
||||
private _dataAlreadySent: string;
|
||||
|
||||
constructor(
|
||||
private readonly _textarea: HTMLTextAreaElement,
|
||||
private readonly _compositionView: HTMLElement,
|
||||
@@ -45,6 +51,7 @@ export class CompositionHelper {
|
||||
this._isComposing = false;
|
||||
this._isSendingComposition = false;
|
||||
this._compositionPosition = { start: 0, end: 0 };
|
||||
this._dataAlreadySent = '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,6 +61,7 @@ export class CompositionHelper {
|
||||
this._isComposing = true;
|
||||
this._compositionPosition.start = this._textarea.value.length;
|
||||
this._compositionView.textContent = '';
|
||||
this._dataAlreadySent = '';
|
||||
this._compositionView.classList.add('active');
|
||||
}
|
||||
|
||||
@@ -118,7 +126,6 @@ export class CompositionHelper {
|
||||
private _finalizeComposition(waitForPropagation: boolean): void {
|
||||
this._compositionView.classList.remove('active');
|
||||
this._isComposing = false;
|
||||
this._clearTextareaPosition();
|
||||
|
||||
if (!waitForPropagation) {
|
||||
// Cancel any delayed composition send requests and send the input immediately.
|
||||
@@ -147,6 +154,9 @@ export class CompositionHelper {
|
||||
if (this._isSendingComposition) {
|
||||
this._isSendingComposition = false;
|
||||
let input;
|
||||
// Add length of data already sent due to keydown event,
|
||||
// otherwise input characters can be duplicated. (Issue #3191)
|
||||
currentCompositionPosition.start += this._dataAlreadySent.length;
|
||||
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);
|
||||
@@ -156,7 +166,9 @@ export class CompositionHelper {
|
||||
// (eg. 2) after a composition character.
|
||||
input = this._textarea.value.substring(currentCompositionPosition.start);
|
||||
}
|
||||
this._coreService.triggerDataEvent(input, true);
|
||||
if (input.length > 0) {
|
||||
this._coreService.triggerDataEvent(input, true);
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
@@ -176,6 +188,7 @@ export class CompositionHelper {
|
||||
const newValue = this._textarea.value;
|
||||
const diff = newValue.replace(oldValue, '');
|
||||
if (diff.length > 0) {
|
||||
this._dataAlreadySent = diff;
|
||||
this._coreService.triggerDataEvent(diff, true);
|
||||
}
|
||||
}
|
||||
@@ -218,13 +231,4 @@ export class CompositionHelper {
|
||||
setTimeout(() => this.updateCompositionElements(true), 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the textarea's position so that the cursor does not blink on IE.
|
||||
* @private
|
||||
*/
|
||||
private _clearTextareaPosition(): void {
|
||||
this._textarea.style.left = '';
|
||||
this._textarea.style.top = '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferNamespace as IBufferNamespaceApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, IParser, IFunctionIdentifier, ILinkProvider, IUnicodeHandling, IUnicodeVersionProvider } from 'xterm';
|
||||
import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBuffer as IBufferApi, IBufferNamespace as IBufferNamespaceApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi, IParser, IFunctionIdentifier, ILinkProvider, IUnicodeHandling, IUnicodeVersionProvider, FontWeight } from 'xterm';
|
||||
import { ITerminal } from 'browser/Types';
|
||||
import { IBufferLine, ICellData } from 'common/Types';
|
||||
import { IBuffer, IBufferSet } from 'common/buffer/Types';
|
||||
@@ -13,11 +13,13 @@ import * as Strings from '../LocalizableStrings';
|
||||
import { IEvent, EventEmitter } from 'common/EventEmitter';
|
||||
import { AddonManager } from './AddonManager';
|
||||
import { IParams } from 'common/parser/Types';
|
||||
import { BufferSet } from 'common/buffer/BufferSet';
|
||||
|
||||
export class Terminal implements ITerminalApi {
|
||||
private _core: ITerminal;
|
||||
private _addonManager: AddonManager;
|
||||
private _parser: IParser | undefined;
|
||||
private _buffer: BufferNamespaceApi | undefined;
|
||||
|
||||
constructor(options?: ITerminalOptions) {
|
||||
this._core = new TerminalCore(options);
|
||||
@@ -58,7 +60,10 @@ export class Terminal implements ITerminalApi {
|
||||
public get cols(): number { return this._core.cols; }
|
||||
public get buffer(): IBufferNamespaceApi {
|
||||
this._checkProposedApi();
|
||||
return new BufferNamespaceApi(this._core.buffers);
|
||||
if (!this._buffer) {
|
||||
this._buffer = new BufferNamespaceApi(this._core);
|
||||
}
|
||||
return this._buffer;
|
||||
}
|
||||
public get markers(): ReadonlyArray<IMarker> {
|
||||
this._checkProposedApi();
|
||||
@@ -169,19 +174,20 @@ export class Terminal implements ITerminalApi {
|
||||
public paste(data: string): void {
|
||||
this._core.paste(data);
|
||||
}
|
||||
public getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'fontWeight' | 'fontWeightBold' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string;
|
||||
public getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'visualBell'): boolean;
|
||||
public getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string;
|
||||
public getOption(key: 'allowTransparency' | 'altClickMovesCursor' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'visualBell'): boolean;
|
||||
public getOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number;
|
||||
public getOption(key: 'fontWeight' | 'fontWeightBold'): FontWeight;
|
||||
public getOption(key: string): any;
|
||||
public getOption(key: any): any {
|
||||
return this._core.optionsService.getOption(key);
|
||||
}
|
||||
public setOption(key: 'bellSound' | 'fontFamily' | 'termName' | 'wordSeparator', value: string): void;
|
||||
public setOption(key: 'fontWeight' | 'fontWeightBold', value: 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900'): void;
|
||||
public setOption(key: 'fontWeight' | 'fontWeightBold', value: 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number): void;
|
||||
public setOption(key: 'logLevel', value: 'debug' | 'info' | 'warn' | 'error' | 'off'): void;
|
||||
public setOption(key: 'bellStyle', value: 'none' | 'visual' | 'sound' | 'both'): void;
|
||||
public setOption(key: 'cursorStyle', value: 'block' | 'underline' | 'bar'): void;
|
||||
public setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'visualBell', value: boolean): void;
|
||||
public setOption(key: 'allowTransparency' | 'altClickMovesCursor' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'visualBell', value: boolean): void;
|
||||
public setOption(key: 'fontSize' | 'letterSpacing' | 'lineHeight' | 'tabStopWidth' | 'scrollback', value: number): void;
|
||||
public setOption(key: 'theme', value: ITheme): void;
|
||||
public setOption(key: 'cols' | 'rows', value: number): void;
|
||||
@@ -204,11 +210,11 @@ export class Terminal implements ITerminalApi {
|
||||
}
|
||||
|
||||
private _verifyIntegers(...values: number[]): void {
|
||||
values.forEach(value => {
|
||||
for (const value of values) {
|
||||
if (value === Infinity || isNaN(value) || value % 1 !== 0) {
|
||||
throw new Error('This API only accepts integers');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,21 +250,21 @@ class BufferNamespaceApi implements IBufferNamespaceApi {
|
||||
private _onBufferChange = new EventEmitter<IBufferApi>();
|
||||
public get onBufferChange(): IEvent<IBufferApi> { return this._onBufferChange.event; }
|
||||
|
||||
constructor(private _buffers: IBufferSet) {
|
||||
this._normal = new BufferApiView(this._buffers.normal, 'normal');
|
||||
this._alternate = new BufferApiView(this._buffers.alt, 'alternate');
|
||||
this._buffers.onBufferActivate(() => this._onBufferChange.fire(this.active));
|
||||
constructor(private _core: ITerminal) {
|
||||
this._normal = new BufferApiView(this._core.buffers.normal, 'normal');
|
||||
this._alternate = new BufferApiView(this._core.buffers.alt, 'alternate');
|
||||
this._core.buffers.onBufferActivate(() => this._onBufferChange.fire(this.active));
|
||||
}
|
||||
public get active(): IBufferApi {
|
||||
if (this._buffers.active === this._buffers.normal) { return this.normal; }
|
||||
if (this._buffers.active === this._buffers.alt) { return this.alternate; }
|
||||
if (this._core.buffers.active === this._core.buffers.normal) { return this.normal; }
|
||||
if (this._core.buffers.active === this._core.buffers.alt) { return this.alternate; }
|
||||
throw new Error('Active buffer is neither normal nor alternate');
|
||||
}
|
||||
public get normal(): IBufferApi {
|
||||
return this._normal.init(this._buffers.normal);
|
||||
return this._normal.init(this._core.buffers.normal);
|
||||
}
|
||||
public get alternate(): IBufferApi {
|
||||
return this._alternate.init(this._buffers.alt);
|
||||
return this._alternate.init(this._core.buffers.alt);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import { CellData } from 'common/buffer/CellData';
|
||||
import { IBufferService, IOptionsService } from 'common/services/Services';
|
||||
import { throwIfFalsy } from 'browser/renderer/RendererUtils';
|
||||
import { channels, color, rgba } from 'browser/Color';
|
||||
import { removeElementFromParent } from 'browser/Dom';
|
||||
|
||||
export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
private _canvas: HTMLCanvasElement;
|
||||
@@ -60,7 +61,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._canvas.parentElement?.removeChild(this._canvas);
|
||||
removeElementFromParent(this._canvas);
|
||||
this._charAtlas?.dispose();
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@ export class Renderer extends Disposable implements IRenderer {
|
||||
super();
|
||||
const allowTransparency = this._optionsService.options.allowTransparency;
|
||||
this._characterJoinerRegistry = new CharacterJoinerRegistry(this._bufferService);
|
||||
|
||||
this._renderLayers = [
|
||||
new TextRenderLayer(this._screenElement, 0, this._colors, this._characterJoinerRegistry, allowTransparency, this._id, this._bufferService, _optionsService),
|
||||
new SelectionRenderLayer(this._screenElement, 1, this._colors, this._id, this._bufferService, _optionsService),
|
||||
@@ -71,7 +70,9 @@ export class Renderer extends Disposable implements IRenderer {
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._renderLayers.forEach(l => l.dispose());
|
||||
for (const l of this._renderLayers) {
|
||||
l.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
removeTerminalFromCache(this._id);
|
||||
}
|
||||
@@ -87,12 +88,11 @@ export class Renderer extends Disposable implements IRenderer {
|
||||
|
||||
public setColors(colors: IColorSet): void {
|
||||
this._colors = colors;
|
||||
|
||||
// Clear layers and force a full render
|
||||
this._renderLayers.forEach(l => {
|
||||
for (const l of this._renderLayers) {
|
||||
l.setColors(this._colors);
|
||||
l.reset();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public onResize(cols: number, rows: number): void {
|
||||
@@ -100,7 +100,9 @@ export class Renderer extends Disposable implements IRenderer {
|
||||
this._updateDimensions();
|
||||
|
||||
// Resize all render layers
|
||||
this._renderLayers.forEach(l => l.resize(this.dimensions));
|
||||
for (const l of this._renderLayers) {
|
||||
l.resize(this.dimensions);
|
||||
}
|
||||
|
||||
// Resize the screen
|
||||
this._screenElement.style.width = `${this.dimensions.canvasWidth}px`;
|
||||
@@ -136,7 +138,9 @@ export class Renderer extends Disposable implements IRenderer {
|
||||
}
|
||||
|
||||
private _runOperation(operation: (layer: IRenderLayer) => void): void {
|
||||
this._renderLayers.forEach(l => operation(l));
|
||||
for (const l of this._renderLayers) {
|
||||
operation(l);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,7 +148,9 @@ export class Renderer extends Disposable implements IRenderer {
|
||||
* necessary before queueing up the next one.
|
||||
*/
|
||||
public renderRows(start: number, end: number): void {
|
||||
this._renderLayers.forEach(l => l.onGridChanged(start, end));
|
||||
for (const l of this._renderLayers) {
|
||||
l.onGridChanged(start, end);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -75,10 +75,11 @@ export class SelectionRenderLayer extends BaseRenderLayer {
|
||||
|
||||
// No need to draw the selection
|
||||
if (viewportCappedStartRow >= this._bufferService.rows || viewportCappedEndRow < 0) {
|
||||
this._state.ydisp = this._bufferService.buffer.ydisp;
|
||||
return;
|
||||
}
|
||||
|
||||
this._ctx.fillStyle = this._colors.selection.css;
|
||||
this._ctx.fillStyle = this._colors.selectionTransparent.css;
|
||||
|
||||
if (columnSelectMode) {
|
||||
const startCol = start[0];
|
||||
@@ -88,7 +89,7 @@ export class SelectionRenderLayer extends BaseRenderLayer {
|
||||
} else {
|
||||
// Draw first row
|
||||
const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
|
||||
const startRowEndCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;
|
||||
const startRowEndCol = viewportCappedStartRow === viewportEndRow ? end[0] : this._bufferService.cols;
|
||||
this._fillCells(startCol, viewportCappedStartRow, startRowEndCol - startCol, 1);
|
||||
|
||||
// Draw middle rows
|
||||
|
||||
@@ -188,13 +188,13 @@ export class TextRenderLayer extends BaseRenderLayer {
|
||||
|
||||
if (y !== startY) {
|
||||
// our row changed, draw the previous row
|
||||
ctx.fillStyle = prevFillStyle ? prevFillStyle : '';
|
||||
ctx.fillStyle = prevFillStyle || '';
|
||||
this._fillCells(startX, startY, cols - startX, 1);
|
||||
startX = x;
|
||||
startY = y;
|
||||
} else if (prevFillStyle !== nextFillStyle) {
|
||||
// our color changed, draw the previous characters in this row
|
||||
ctx.fillStyle = prevFillStyle ? prevFillStyle : '';
|
||||
ctx.fillStyle = prevFillStyle || '';
|
||||
this._fillCells(startX, startY, x - startX, 1);
|
||||
startX = x;
|
||||
startY = y;
|
||||
|
||||
@@ -26,7 +26,7 @@ export abstract class BaseCharAtlas implements IDisposable {
|
||||
* Perform any work needed to warm the cache before it can be used. Used by the default
|
||||
* implementation of warmUp(), and will only be called once.
|
||||
*/
|
||||
protected _doWarmUp(): void { }
|
||||
private _doWarmUp(): void { }
|
||||
|
||||
/**
|
||||
* Called when we start drawing a new frame.
|
||||
|
||||
@@ -16,9 +16,7 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number
|
||||
cursor: undefined,
|
||||
cursorAccent: undefined,
|
||||
selection: undefined,
|
||||
// For the static char atlas, we only use the first 16 colors, but we need all 256 for the
|
||||
// dynamic character atlas.
|
||||
ansi: colors.ansi.slice(0, 16)
|
||||
ansi: colors.ansi
|
||||
};
|
||||
return {
|
||||
devicePixelRatio: window.devicePixelRatio,
|
||||
|
||||
@@ -12,6 +12,7 @@ import { ICharSizeService } from 'browser/services/Services';
|
||||
import { IOptionsService, IBufferService } from 'common/services/Services';
|
||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
import { color } from 'browser/Color';
|
||||
import { removeElementFromParent } from 'browser/Dom';
|
||||
|
||||
const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-';
|
||||
const ROW_CONTAINER_CLASS = 'xterm-rows';
|
||||
@@ -53,7 +54,6 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
@IBufferService private readonly _bufferService: IBufferService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._rowContainer = document.createElement('div');
|
||||
this._rowContainer.classList.add(ROW_CONTAINER_CLASS);
|
||||
this._rowContainer.style.lineHeight = 'normal';
|
||||
@@ -95,10 +95,11 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
|
||||
public dispose(): void {
|
||||
this._element.classList.remove(TERMINAL_CLASS_PREFIX + this._terminalClass);
|
||||
this._screenElement.removeChild(this._rowContainer);
|
||||
this._screenElement.removeChild(this._selectionContainer);
|
||||
this._screenElement.removeChild(this._themeStyleElement);
|
||||
this._screenElement.removeChild(this._dimensionsStyleElement);
|
||||
|
||||
// Outside influences such as React unmounts may manipulate the DOM before our disposal.
|
||||
// https://github.com/xtermjs/xterm.js/issues/2960
|
||||
removeElementFromParent(this._rowContainer, this._selectionContainer, this._themeStyleElement, this._dimensionsStyleElement);
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -116,13 +117,13 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._bufferService.cols;
|
||||
this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._bufferService.rows;
|
||||
|
||||
this._rowElements.forEach(element => {
|
||||
for (const element of this._rowElements) {
|
||||
element.style.width = `${this.dimensions.canvasWidth}px`;
|
||||
element.style.height = `${this.dimensions.actualCellHeight}px`;
|
||||
element.style.lineHeight = `${this.dimensions.actualCellHeight}px`;
|
||||
// Make sure rows don't overflow onto following row
|
||||
element.style.overflow = 'hidden';
|
||||
});
|
||||
}
|
||||
|
||||
if (!this._dimensionsStyleElement) {
|
||||
this._dimensionsStyleElement = document.createElement('style');
|
||||
@@ -137,7 +138,7 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
` width: ${this.dimensions.actualCellWidth}px` +
|
||||
`}`;
|
||||
|
||||
this._dimensionsStyleElement.innerHTML = styles;
|
||||
this._dimensionsStyleElement.textContent = styles;
|
||||
|
||||
this._selectionContainer.style.height = this._viewportElement.style.height;
|
||||
this._screenElement.style.width = `${this.dimensions.canvasWidth}px`;
|
||||
@@ -224,7 +225,7 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
`}` +
|
||||
`${this._terminalSelector} .${SELECTION_CLASS} div {` +
|
||||
` position: absolute;` +
|
||||
` background-color: ${this._colors.selection.css};` +
|
||||
` background-color: ${this._colors.selectionTransparent.css};` +
|
||||
`}`;
|
||||
// Colors
|
||||
this._colors.ansi.forEach((c, i) => {
|
||||
@@ -236,7 +237,7 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
`${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${color.opaque(this._colors.background).css}; }` +
|
||||
`${this._terminalSelector} .${BG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { background-color: ${this._colors.foreground.css}; }`;
|
||||
|
||||
this._themeStyleElement.innerHTML = styles;
|
||||
this._themeStyleElement.textContent = styles;
|
||||
}
|
||||
|
||||
public onDevicePixelRatioChange(): void {
|
||||
@@ -305,7 +306,7 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
} else {
|
||||
// Draw first row
|
||||
const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
|
||||
const endCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;
|
||||
const endCol = viewportCappedStartRow === viewportEndRow ? end[0] : this._bufferService.cols;
|
||||
documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol));
|
||||
// Draw middle rows
|
||||
const middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1;
|
||||
@@ -346,7 +347,9 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this._rowElements.forEach(e => e.innerHTML = '');
|
||||
for (const e of this._rowElements) {
|
||||
e.innerText = '';
|
||||
}
|
||||
}
|
||||
|
||||
public renderRows(start: number, end: number): void {
|
||||
@@ -356,7 +359,7 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
|
||||
for (let y = start; y <= end; y++) {
|
||||
const rowElement = this._rowElements[y];
|
||||
rowElement.innerHTML = '';
|
||||
rowElement.innerText = '';
|
||||
|
||||
const row = y + this._bufferService.buffer.ydisp;
|
||||
const lineData = this._bufferService.buffer.lines.get(row);
|
||||
|
||||
@@ -14,6 +14,7 @@ export class CoreBrowserService implements ICoreBrowserService {
|
||||
}
|
||||
|
||||
public get isFocused(): boolean {
|
||||
return document.activeElement === this._textarea && document.hasFocus();
|
||||
const docOrShadowRoot = this._textarea.getRootNode ? this._textarea.getRootNode() as Document | ShadowRoot : document;
|
||||
return docOrShadowRoot.activeElement === this._textarea && document.hasFocus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,10 +51,13 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
private _rowCount: number,
|
||||
screenElement: HTMLElement,
|
||||
@IOptionsService optionsService: IOptionsService,
|
||||
@ICharSizeService charSizeService: ICharSizeService,
|
||||
@ICharSizeService private readonly _charSizeService: ICharSizeService,
|
||||
@IBufferService bufferService: IBufferService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.register({ dispose: () => this._renderer.dispose() });
|
||||
|
||||
this._renderDebouncer = new RenderDebouncer((start, end) => this._renderRows(start, end));
|
||||
this.register(this._renderDebouncer);
|
||||
|
||||
@@ -64,7 +67,7 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
|
||||
this.register(bufferService.onResize(e => this._fullRefresh()));
|
||||
this.register(optionsService.onOptionChange(() => this._renderer.onOptionsChanged()));
|
||||
this.register(charSizeService.onCharSizeChange(() => this.onCharSizeChanged()));
|
||||
this.register(this._charSizeService.onCharSizeChange(() => this.onCharSizeChanged()));
|
||||
|
||||
// No need to register this as renderer is explicitly disposed in RenderService.dispose
|
||||
this._renderer.onRequestRedraw(e => this.refreshRows(e.start, e.end, true));
|
||||
@@ -83,7 +86,13 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
}
|
||||
|
||||
private _onIntersectionChange(entry: IntersectionObserverEntry): void {
|
||||
this._isPaused = entry.intersectionRatio === 0;
|
||||
this._isPaused = entry.isIntersecting === undefined ? (entry.intersectionRatio === 0) : !entry.isIntersecting;
|
||||
|
||||
// Terminal was hidden on open
|
||||
if (!this._isPaused && !this._charSizeService.hasValidSize) {
|
||||
this._charSizeService.measure();
|
||||
}
|
||||
|
||||
if (!this._isPaused && this._needsFullRefresh) {
|
||||
this.refreshRows(0, this._rowCount - 1);
|
||||
this._needsFullRefresh = false;
|
||||
@@ -137,7 +146,6 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._renderer.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -166,6 +174,10 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
}
|
||||
|
||||
public onDevicePixelRatioChange(): void {
|
||||
// Force char size measurement as DomMeasureStrategy(getBoundingClientRect) is not stable
|
||||
// when devicePixelRatio changes
|
||||
this._charSizeService.measure();
|
||||
|
||||
this._renderer.onDevicePixelRatioChange();
|
||||
this.refreshRows(0, this._rowCount - 1);
|
||||
}
|
||||
|
||||
@@ -105,6 +105,9 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
private _workCell: CellData = new CellData();
|
||||
|
||||
private _mouseDownTimeStamp: number = 0;
|
||||
private _oldHasSelection: boolean = false;
|
||||
private _oldSelectionStart: [number, number] | undefined = undefined;
|
||||
private _oldSelectionEnd: [number, number] | undefined = undefined;
|
||||
|
||||
private _onLinuxMouseSelection = this.register(new EventEmitter<string>());
|
||||
public get onLinuxMouseSelection(): IEvent<string> { return this._onLinuxMouseSelection.event; }
|
||||
@@ -290,7 +293,7 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
* Checks if the current click was inside the current selection
|
||||
* @param event The mouse event
|
||||
*/
|
||||
public isClickInSelection(event: MouseEvent): boolean {
|
||||
private _isClickInSelection(event: MouseEvent): boolean {
|
||||
const coords = this._getMouseBufferCoords(event);
|
||||
const start = this._model.finalSelectionStart;
|
||||
const end = this._model.finalSelectionEnd;
|
||||
@@ -313,7 +316,7 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
* Selects word at the current mouse event coordinates.
|
||||
* @param event The mouse event.
|
||||
*/
|
||||
public selectWordAtCursor(event: MouseEvent): void {
|
||||
private _selectWordAtCursor(event: MouseEvent): void {
|
||||
const coords = this._getMouseBufferCoords(event);
|
||||
if (coords) {
|
||||
this._selectWordAt(coords, false);
|
||||
@@ -667,8 +670,8 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
|
||||
this._removeMouseDownListeners();
|
||||
|
||||
if (this.selectionText.length <= 1 && timeElapsed < ALT_CLICK_MOVE_CURSOR_TIME) {
|
||||
if (event.altKey && this._bufferService.buffer.ybase === this._bufferService.buffer.ydisp) {
|
||||
if (this.selectionText.length <= 1 && timeElapsed < ALT_CLICK_MOVE_CURSOR_TIME && event.altKey && this._optionsService.getOption('altClickMovesCursor')) {
|
||||
if (this._bufferService.buffer.ybase === this._bufferService.buffer.ydisp) {
|
||||
const coordinates = this._mouseService.getCoords(
|
||||
event,
|
||||
this._element,
|
||||
@@ -681,11 +684,43 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
this._coreService.triggerDataEvent(sequence, true);
|
||||
}
|
||||
}
|
||||
} else if (this.hasSelection) {
|
||||
this._onSelectionChange.fire();
|
||||
} else {
|
||||
this._fireEventIfSelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private _fireEventIfSelectionChanged(): void {
|
||||
const start = this._model.finalSelectionStart;
|
||||
const end = this._model.finalSelectionEnd;
|
||||
const hasSelection = !!start && !!end && (start[0] !== end[0] || start[1] !== end[1]);
|
||||
|
||||
if (!hasSelection) {
|
||||
if (this._oldHasSelection) {
|
||||
this._fireOnSelectionChange(start, end, hasSelection);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Sanity check, these should not be undefined as there is a selection
|
||||
if (!start || !end) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._oldSelectionStart || !this._oldSelectionEnd || (
|
||||
start[0] !== this._oldSelectionStart[0] || start[1] !== this._oldSelectionStart[1] ||
|
||||
end[0] !== this._oldSelectionEnd[0] || end[1] !== this._oldSelectionEnd[1])) {
|
||||
|
||||
this._fireOnSelectionChange(start, end, hasSelection);
|
||||
}
|
||||
}
|
||||
|
||||
private _fireOnSelectionChange(start: [number, number] | undefined, end: [number, number] | undefined, hasSelection: boolean): void {
|
||||
this._oldSelectionStart = start;
|
||||
this._oldSelectionEnd = end;
|
||||
this._oldHasSelection = hasSelection;
|
||||
this._onSelectionChange.fire();
|
||||
}
|
||||
|
||||
private _onBufferActivate(e: {activeBuffer: IBuffer, inactiveBuffer: IBuffer}): void {
|
||||
this.clearSelection();
|
||||
// Only adjust the selection on trim, shiftElements is rarely used (only in
|
||||
@@ -727,6 +762,13 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
public rightClickSelect(ev: MouseEvent): void {
|
||||
if (!this._isClickInSelection(ev)) {
|
||||
this._selectWordAtCursor(ev);
|
||||
this._fireEventIfSelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets positional information for the word at the coordinated specified.
|
||||
* @param coords The coordinates to get the word at.
|
||||
|
||||
@@ -91,8 +91,7 @@ export interface ISelectionService {
|
||||
selectAll(): void;
|
||||
selectLines(start: number, end: number): void;
|
||||
clearSelection(): void;
|
||||
isClickInSelection(event: MouseEvent): boolean;
|
||||
selectWordAtCursor(event: MouseEvent): void;
|
||||
rightClickSelect(event: MouseEvent): void;
|
||||
shouldColumnSelect(event: KeyboardEvent | MouseEvent): boolean;
|
||||
shouldForceSelection(event: MouseEvent): boolean;
|
||||
refresh(isLinuxMouseSelection?: boolean): void;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user