Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a61642b81 | ||
|
|
0a0ebb68b5 | ||
|
|
e77a0ec886 | ||
|
|
1bf2ccc572 | ||
|
|
5f680c2185 | ||
|
|
38450082ac | ||
|
|
1801403ce2 | ||
|
|
4c7d89e67d | ||
|
|
5d3a9e864d | ||
|
|
911d92200a | ||
|
|
1a788f6363 | ||
|
|
a354f36e6f | ||
|
|
343e1c8a86 | ||
|
|
783722c2f3 | ||
|
|
44e4b56ced | ||
|
|
52044e9aad | ||
|
|
4cdf308c2f | ||
|
|
d24a27d93e | ||
|
|
55beec8b96 | ||
|
|
81dce7a4ba | ||
|
|
ddf3f08a6c | ||
|
|
8c6140c2e3 | ||
|
|
1639c0a031 | ||
|
|
fb950fcbe5 | ||
|
|
1bb465d6b0 | ||
|
|
efb939d171 | ||
|
|
fe99422ca4 | ||
|
|
d28819de41 | ||
|
|
f8e9f1813a | ||
|
|
48b8e31794 | ||
|
|
58b657bc3b | ||
|
|
bc9c9dd6fd | ||
|
|
6b16732192 | ||
|
|
5aaa8dafc5 | ||
|
|
7a652c4169 | ||
|
|
1980f675f9 | ||
|
|
98c40e3f8e | ||
|
|
6ced3014cf | ||
|
|
3e8aecaea3 | ||
|
|
e3c008ba38 | ||
|
|
d2083c5e8c | ||
|
|
0c0a34d888 | ||
|
|
cd975d22cc | ||
|
|
b5f53b341d | ||
|
|
221db55680 | ||
|
|
5cc1114594 | ||
|
|
6ad553483d | ||
|
|
49872f9377 | ||
|
|
1e17ba7a80 | ||
|
|
1c8b08d55c | ||
|
|
ca083f257c | ||
|
|
8b53b8c255 | ||
|
|
ba078ca1f9 | ||
|
|
4ededf5b06 | ||
|
|
384cd9db30 | ||
|
|
55b420b016 | ||
|
|
ddd43e0f09 | ||
|
|
41fdb96b68 | ||
|
|
e5fb255b79 | ||
|
|
7f14bfc7d9 | ||
|
|
8f97a5a786 | ||
|
|
5353808437 | ||
|
|
38f8bc5f3c | ||
|
|
48c21dc3ae | ||
|
|
bcb71e854f | ||
|
|
e3c361327c | ||
|
|
51ef27894e | ||
|
|
7edc11bd04 | ||
|
|
ebe55e5b5f | ||
|
|
3d541dd323 | ||
|
|
53f05de3e7 | ||
|
|
d1a87facfd | ||
|
|
18a2751471 | ||
|
|
59e1bddb6e | ||
|
|
d6ad4a4fc3 | ||
|
|
f9bd0b8fed | ||
|
|
ec26820ea6 | ||
|
|
d7fdf17eab | ||
|
|
a5f2ea336e | ||
|
|
de5df69333 | ||
|
|
f2b90d690f | ||
|
|
eec53261e0 | ||
|
|
eb96187801 | ||
|
|
c502272373 | ||
|
|
87bc48f5bc | ||
|
|
d7fb0141d2 | ||
|
|
2fa125904a | ||
|
|
de03391c2d | ||
|
|
a38e97f1ec | ||
|
|
5d98304249 | ||
|
|
82a6144fd1 | ||
|
|
995080c40c | ||
|
|
5ad12dff8d | ||
|
|
a458bae36d | ||
|
|
900200b871 | ||
|
|
77d5d52e71 | ||
|
|
1a50cfabe0 | ||
|
|
3abbaa9ba9 | ||
|
|
5a9f2635d3 | ||
|
|
d24c473172 | ||
|
|
da93a35671 | ||
|
|
7d4b61413e | ||
|
|
13e33a8da4 | ||
|
|
ede3d10528 | ||
|
|
64ea96cc13 | ||
|
|
47e3dc7bcd | ||
|
|
748d0cc74f | ||
|
|
343ea9bcd0 | ||
|
|
cd01157e55 | ||
|
|
4cc4b2cd42 | ||
|
|
29c1264b42 | ||
|
|
d84b71d4c6 | ||
|
|
f18528d2bb | ||
|
|
ddde738326 | ||
|
|
24919e36f0 | ||
|
|
f5223d30b8 | ||
|
|
7b4423819e | ||
|
|
372c2970e6 | ||
|
|
bc4158c9b8 | ||
|
|
8164c638e5 | ||
|
|
8b9fa324f4 | ||
|
|
0976b15709 | ||
|
|
181fb1cfe5 | ||
|
|
09aed4d339 | ||
|
|
aeb4fa0fe9 | ||
|
|
0ae5aedcf9 | ||
|
|
3d41f028e4 | ||
|
|
324ea2ecb7 | ||
|
|
999f255893 | ||
|
|
931ee9e89e | ||
|
|
c0b4edaf31 | ||
|
|
abdce874db | ||
|
|
edba006045 | ||
|
|
6b4df216d3 | ||
|
|
f502f55ae1 | ||
|
|
afe1d7ed6f | ||
|
|
1628795557 | ||
|
|
e9643f5936 | ||
|
|
ba61509fdd | ||
|
|
a67e7286d3 | ||
|
|
b85f5aacd6 | ||
|
|
de574a27fc | ||
|
|
7c87f5b47a | ||
|
|
0a26cee08b | ||
|
|
40716b8ca5 | ||
|
|
d29388394c | ||
|
|
714d4b1cd6 | ||
|
|
9f1db12b1b | ||
|
|
56c7c5c4ed | ||
|
|
13abd2aa96 | ||
|
|
10598462d0 | ||
|
|
0c6877f46f | ||
|
|
0c4ee0db65 | ||
|
|
5d09d3b217 | ||
|
|
63eb9a4555 | ||
|
|
b7c0332626 | ||
|
|
95979c4a7d | ||
|
|
5019e14753 | ||
|
|
85e0625078 | ||
|
|
873e3745ec | ||
|
|
9b19187307 | ||
|
|
218745ab85 | ||
|
|
1687d06f1c | ||
|
|
261209cab6 | ||
|
|
bb55cd2881 | ||
|
|
7b38283db0 | ||
|
|
908e84a1f8 | ||
|
|
656912c934 | ||
|
|
7fa32af051 | ||
|
|
fa92e6074d | ||
|
|
239f669041 | ||
|
|
214555085a | ||
|
|
da33543ffb | ||
|
|
e643286cd5 | ||
|
|
38f0dd9be2 | ||
|
|
6d801311cd | ||
|
|
3bd37115d1 | ||
|
|
0785a1bf24 | ||
|
|
413a2b22fa | ||
|
|
e98b6315e4 | ||
|
|
d9a33d080b | ||
|
|
85b5161380 | ||
|
|
2b916a24aa | ||
|
|
ccaf0a71c6 | ||
|
|
b3fd901699 | ||
|
|
a32e3958d8 | ||
|
|
dbd999c4a4 | ||
|
|
8158a16629 | ||
|
|
7eace4d479 | ||
|
|
e98a20ba9e | ||
|
|
d072a628ca | ||
|
|
6b26a1bcf8 | ||
|
|
8524c23deb | ||
|
|
7c5853bf00 | ||
|
|
23afd53301 | ||
|
|
1d728f7f87 | ||
|
|
5030dc3d6a | ||
|
|
47fe7b2318 | ||
|
|
51d18e533f | ||
|
|
842f4c2088 | ||
|
|
c8b0ddbc72 | ||
|
|
4319df0eeb | ||
|
|
bd3bd7fda8 | ||
|
|
7f87b46328 | ||
|
|
32ae29be37 | ||
|
|
89af5b1098 | ||
|
|
5aaed8cf0b | ||
|
|
06f3f8f267 | ||
|
|
14d22a2002 | ||
|
|
4337326169 | ||
|
|
28dcd38a53 | ||
|
|
bb2367ab4a | ||
|
|
02795f8803 | ||
|
|
8528e8e83b | ||
|
|
1f690c7703 | ||
|
|
24fb498757 | ||
|
|
7836097e56 | ||
|
|
86f80b344b | ||
|
|
49bcb969b6 | ||
|
|
b55c7e342a | ||
|
|
23ed3327ce | ||
|
|
d57329f7e5 | ||
|
|
f6a384b401 | ||
|
|
79c990d73e | ||
|
|
ecfc2d4b40 | ||
|
|
becab08b6a | ||
|
|
8ad0bd1638 | ||
|
|
544c10009c | ||
|
|
3457fb4872 | ||
|
|
8e17cace6f | ||
|
|
93f7ad86da | ||
|
|
3a0f64545e | ||
|
|
5af5fe319b | ||
|
|
b9d10f1453 | ||
|
|
14bfc0bf0e | ||
|
|
078d88fbbe | ||
|
|
dfe35a93d5 | ||
|
|
7ff27240f0 | ||
|
|
550b19f334 | ||
|
|
74409da947 | ||
|
|
1f60a398b2 | ||
|
|
21e1c5ef58 | ||
|
|
dddde8ec23 | ||
|
|
43a014e8c1 | ||
|
|
b39477d81a | ||
|
|
6ef1bdd7ce | ||
|
|
f9deb2f840 | ||
|
|
23076ba6cc | ||
|
|
fe530d92ec | ||
|
|
efda0e77a9 | ||
|
|
87dd275a51 | ||
|
|
ef3695f727 | ||
|
|
09d1a01126 | ||
|
|
7a70db709c | ||
|
|
7f131aa19a | ||
|
|
4645ef706e | ||
|
|
866ee23df9 | ||
|
|
fefdd8a4e9 | ||
|
|
7a502c6b83 | ||
|
|
90dca7d650 | ||
|
|
e8aa4eba75 | ||
|
|
64d5af38f5 | ||
|
|
4cea6b3f41 | ||
|
|
b9331ec810 | ||
|
|
97e4af3707 | ||
|
|
faa3769ed1 | ||
|
|
1098173908 | ||
|
|
8ff34a6aee | ||
|
|
bec4cb37d4 | ||
|
|
12a999dfe0 | ||
|
|
8225ecd3ba | ||
|
|
31c11c7a65 | ||
|
|
a0aae62cb7 | ||
|
|
e79c21d4b2 | ||
|
|
61e5c10fd3 | ||
|
|
fef30bdba4 | ||
|
|
61c2ca71d8 | ||
|
|
8328fcac52 | ||
|
|
dd503785be | ||
|
|
85683a457d | ||
|
|
15c2432b6f | ||
|
|
66dcd1694c | ||
|
|
8e68c67a0c | ||
|
|
d2913d607d | ||
|
|
01fd148673 | ||
|
|
47ec31361c | ||
|
|
39d1546753 | ||
|
|
b5f208294d | ||
|
|
77622f8919 | ||
|
|
816220ebd2 | ||
|
|
4a661da57b | ||
|
|
f43afde8df | ||
|
|
6c4f8b42dd | ||
|
|
a1468d0f58 | ||
|
|
c8dc679086 | ||
|
|
9bc5090bac | ||
|
|
29e9685839 | ||
|
|
02e2727fb5 | ||
|
|
a05cdbd48e | ||
|
|
8f1dcecb50 | ||
|
|
bb2193676b | ||
|
|
f5b5716582 | ||
|
|
38ccf1d5c7 | ||
|
|
ba4252bc0b | ||
|
|
900d0132cb | ||
|
|
5a6a275a53 | ||
|
|
731432a852 | ||
|
|
c70be89bc2 | ||
|
|
03bc29e0ed | ||
|
|
eb562b5d89 | ||
|
|
d60431f4bf | ||
|
|
de56d04ae5 | ||
|
|
c2145d5125 | ||
|
|
90f64ba6cc | ||
|
|
26f8fce538 | ||
|
|
19399066f1 | ||
|
|
f38d7d86ac | ||
|
|
450ee0c6a5 | ||
|
|
6899f9dc4d | ||
|
|
f92d1f46a6 | ||
|
|
12efc1a455 | ||
|
|
f4593b76df | ||
|
|
9e8e6159cf | ||
|
|
b5d0a46ccb | ||
|
|
fb13f812f7 | ||
|
|
6162ae9ffe | ||
|
|
b6e1925ba8 | ||
|
|
fd3b22e8ab | ||
|
|
a4212e98d2 | ||
|
|
e293d29fd3 | ||
|
|
1a9965ef01 | ||
|
|
05eb12e6dd | ||
|
|
40b8854df4 | ||
|
|
1bab173e99 | ||
|
|
f678f0824b | ||
|
|
8b32d0c8ba | ||
|
|
8b55497adf | ||
|
|
3870507305 | ||
|
|
a0a7abbf49 | ||
|
|
c277fb742d | ||
|
|
7f8592282f | ||
|
|
963ed59459 | ||
|
|
22d889ab07 | ||
|
|
a4f830aa5d | ||
|
|
6f5008dba6 | ||
|
|
5777fbc608 | ||
|
|
035a7f710e | ||
|
|
d92e636482 | ||
|
|
b05db37ac1 | ||
|
|
53c432df27 | ||
|
|
8ccbef850e | ||
|
|
19b212d570 | ||
|
|
a9a6920b13 | ||
|
|
9f6d4f52f3 | ||
|
|
e7baccdbcd | ||
|
|
4cc0db4273 | ||
|
|
3e283e9168 | ||
|
|
a39facfe28 | ||
|
|
1c38f859b7 | ||
|
|
cee2411fb4 | ||
|
|
fdc2bc4960 | ||
|
|
b146ffd30f | ||
|
|
45ecc837e4 | ||
|
|
5b8777267a | ||
|
|
8488f1ad63 | ||
|
|
a93f81c270 | ||
|
|
21263a5f60 | ||
|
|
1905d7d8ca | ||
|
|
adaee5e495 | ||
|
|
71649f5e40 | ||
|
|
bd21c6afa5 | ||
|
|
df929f2dd5 | ||
|
|
e7e9d5ca4c | ||
|
|
80e7963dbe | ||
|
|
f98b4ce1ba | ||
|
|
520d5dd9eb | ||
|
|
4ef5f6ae86 | ||
|
|
39f9c3507b | ||
|
|
3e47c96445 | ||
|
|
079bbc8242 | ||
|
|
bfb048110f | ||
|
|
fc09730ffb | ||
|
|
3dc4f1a7f2 | ||
|
|
65b9a6cfc4 | ||
|
|
4b615f48ff | ||
|
|
c8d1266f0a | ||
|
|
7dc3332b05 | ||
|
|
ced06629d4 | ||
|
|
3b279e4a4d | ||
|
|
9e598d9fba | ||
|
|
f025c0cf4e | ||
|
|
8adf9473b7 | ||
|
|
692df151e5 | ||
|
|
4022277cec | ||
|
|
1250ab637a | ||
|
|
a14248d710 | ||
|
|
98a03dd477 | ||
|
|
2f85448e51 | ||
|
|
b4de2787c7 | ||
|
|
e7bbc4151a | ||
|
|
453cb00807 | ||
|
|
da2c59d1d0 | ||
|
|
372b6b633b | ||
|
|
fa4479c797 | ||
|
|
96574425c7 | ||
|
|
60c11e87f6 | ||
|
|
bdfc82d1b7 | ||
|
|
167a6fdbf6 | ||
|
|
89a0cee5a4 | ||
|
|
724bbebc14 | ||
|
|
e797070121 | ||
|
|
cfd7912afa | ||
|
|
1948ec886f | ||
|
|
249e73cd25 | ||
|
|
89112fb1ae | ||
|
|
d46a685925 | ||
|
|
785a8190a6 | ||
|
|
3a499fc35d | ||
|
|
4350c7ffcd | ||
|
|
a5a9c3b368 | ||
|
|
19367a6042 | ||
|
|
2263a1579e | ||
|
|
622a5bb9c3 | ||
|
|
82706e77db | ||
|
|
e75b47ca01 | ||
|
|
1af22b24c0 | ||
|
|
85dd6a9e87 | ||
|
|
7bf12f9123 | ||
|
|
798b805fe0 | ||
|
|
f5b66775db | ||
|
|
ff6fa739c4 | ||
|
|
4799832a33 | ||
|
|
c10a451381 | ||
|
|
78c2f06107 | ||
|
|
1e8901630c | ||
|
|
f4da8af2e9 | ||
|
|
83cb7c3184 |
@@ -189,6 +189,9 @@ Xterm.js is used in several world-class applications to provide great terminal e
|
||||
- [**KubeSail**](https://kubesail.com): The Self-Hosting Company - uses xterm to allow users to exec into kubernetes pods and build github apps
|
||||
- [**WiTTY**](https://github.com/syssecfsu/witty): Web-based interactive terminal emulator that allows users to easily record, share, and replay console sessions.
|
||||
- [**libv86 Terminal Forwarding**](https://github.com/hello-smile6/libv86-terminal-forwarding): Peer-to-peer SSH for the web, using WebRTC via [Bugout](https://github.com/chr15m/bugout) for data transfer and [v86](https://github.com/copy/v86) for web-based virtualization.
|
||||
- [**hack.courses**](https://hack.courses): Interactive Linux and command-line classes using xterm.js to expose a real terminal available for everyone.
|
||||
- [**Render**](https://render.com): Platform-as-a-service for your apps, websites, and databases using xterm.js to provide a command prompt for user containers and for streaming build and runtime logs.
|
||||
- [**CloudTTY**](https://github.com/cloudtty/cloudtty): A Friendly Kubernetes CloudShell (Web Terminal).
|
||||
- [And much more...](https://github.com/xtermjs/xterm.js/network/dependents?package_id=UGFja2FnZS0xNjYzMjc4OQ%3D%3D)
|
||||
|
||||
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it on our list. Note: Please add any new contributions to the end of the list only.
|
||||
|
||||
@@ -63,6 +63,9 @@ export class FitAddon implements ITerminalAddon {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const scrollbarWidth = this._terminal.options.scrollback === 0 ?
|
||||
0 : core.viewport.scrollBarWidth;
|
||||
|
||||
const parentElementStyle = window.getComputedStyle(this._terminal.element.parentElement);
|
||||
const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));
|
||||
const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));
|
||||
@@ -76,7 +79,7 @@ export class FitAddon implements ITerminalAddon {
|
||||
const elementPaddingVer = elementPadding.top + elementPadding.bottom;
|
||||
const elementPaddingHor = elementPadding.right + elementPadding.left;
|
||||
const availableHeight = parentElementHeight - elementPaddingVer;
|
||||
const availableWidth = parentElementWidth - elementPaddingHor - core.viewport.scrollBarWidth;
|
||||
const availableWidth = parentElementWidth - elementPaddingHor - scrollbarWidth;
|
||||
const geometry = {
|
||||
cols: Math.max(MINIMUM_COLS, Math.floor(availableWidth / core._renderService.dimensions.actualCellWidth)),
|
||||
rows: Math.max(MINIMUM_ROWS, Math.floor(availableHeight / core._renderService.dimensions.actualCellHeight))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm-addon-ligatures",
|
||||
"version": "0.5.2",
|
||||
"version": "0.5.3",
|
||||
"description": "Add support for programming ligatures to xterm.js",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
|
||||
@@ -78,7 +78,7 @@ describe('xterm-addon-ligatures', () => {
|
||||
});
|
||||
|
||||
it('handles quoted font names', done => {
|
||||
term.setOption('fontFamily', '"Fira Code", monospace');
|
||||
term.options.fontFamily = '"Fira Code", monospace';
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
onRefresh.callsFake(() => {
|
||||
assert.deepEqual(term.joiner!(input), [[2, 4], [7, 10]]);
|
||||
@@ -87,7 +87,7 @@ describe('xterm-addon-ligatures', () => {
|
||||
});
|
||||
|
||||
it('falls back to later fonts if earlier ones are not present', done => {
|
||||
term.setOption('fontFamily', 'notinstalled, Fira Code, monospace');
|
||||
term.options.fontFamily = 'notinstalled, Fira Code, monospace';
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
onRefresh.callsFake(() => {
|
||||
assert.deepEqual(term.joiner!(input), [[2, 4], [7, 10]]);
|
||||
@@ -98,17 +98,17 @@ describe('xterm-addon-ligatures', () => {
|
||||
it('uses the current font value', done => {
|
||||
// The first three calls are all synchronous so that we don't allow time for
|
||||
// any fonts to load while we're switching things around
|
||||
term.setOption('fontFamily', 'Fira Code');
|
||||
term.options.fontFamily = 'Fira Code';
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
term.setOption('fontFamily', 'notinstalled');
|
||||
term.options.fontFamily = 'notinstalled';
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
term.setOption('fontFamily', 'Iosevka');
|
||||
term.options.fontFamily = 'Iosevka';
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
onRefresh.callsFake(() => {
|
||||
assert.deepEqual(term.joiner!(input), [[2, 4]]);
|
||||
|
||||
// And switch it back to Fira Code for good measure
|
||||
term.setOption('fontFamily', 'Fira Code');
|
||||
term.options.fontFamily = 'Fira Code';
|
||||
|
||||
// At this point, we haven't loaded the new font, so the result reverts
|
||||
// back to empty until that happens
|
||||
@@ -124,7 +124,7 @@ describe('xterm-addon-ligatures', () => {
|
||||
it('allows multiple terminal instances that use different fonts', done => {
|
||||
const onRefresh2 = sinon.stub();
|
||||
const term2 = new MockTerminal(onRefresh2);
|
||||
term2.setOption('fontFamily', 'Iosevka');
|
||||
term2.options.fontFamily = 'Iosevka';
|
||||
ligatureSupport.enableLigatures(term2 as any);
|
||||
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
@@ -140,7 +140,7 @@ describe('xterm-addon-ligatures', () => {
|
||||
});
|
||||
|
||||
it('fails if it finds but cannot load the font', async () => {
|
||||
term.setOption('fontFamily', 'Nonexistant Font, monospace');
|
||||
term.options.fontFamily = 'Nonexistant Font, monospace';
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
await delay(500);
|
||||
assert.isTrue(onRefresh.notCalled);
|
||||
@@ -148,7 +148,7 @@ describe('xterm-addon-ligatures', () => {
|
||||
});
|
||||
|
||||
it('returns nothing if the font is not present on the system', async () => {
|
||||
term.setOption('fontFamily', 'notinstalled');
|
||||
term.options.fontFamily = 'notinstalled';
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
await delay(500);
|
||||
assert.isTrue(onRefresh.notCalled);
|
||||
@@ -156,7 +156,7 @@ describe('xterm-addon-ligatures', () => {
|
||||
});
|
||||
|
||||
it('returns nothing if no specific font is specified', async () => {
|
||||
term.setOption('fontFamily', 'monospace');
|
||||
term.options.fontFamily = 'monospace';
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
await delay(500);
|
||||
assert.isTrue(onRefresh.notCalled);
|
||||
@@ -164,7 +164,7 @@ describe('xterm-addon-ligatures', () => {
|
||||
});
|
||||
|
||||
it('returns nothing if no fonts are provided', async () => {
|
||||
term.setOption('fontFamily', '');
|
||||
term.options.fontFamily = '';
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
await delay(500);
|
||||
assert.isTrue(onRefresh.notCalled);
|
||||
@@ -172,7 +172,7 @@ describe('xterm-addon-ligatures', () => {
|
||||
});
|
||||
|
||||
it('fails when given malformed inputs', async () => {
|
||||
term.setOption('fontFamily', {} as any);
|
||||
term.options.fontFamily = {} as any;
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
await delay(500);
|
||||
assert.isTrue(onRefresh.notCalled);
|
||||
@@ -181,7 +181,7 @@ describe('xterm-addon-ligatures', () => {
|
||||
|
||||
it('ensures no empty errors are thrown', async () => {
|
||||
sinon.stub(fontLigatures, 'loadFile').callsFake(async () => { throw undefined; });
|
||||
term.setOption('fontFamily', 'Iosevka');
|
||||
term.options.fontFamily = 'Iosevka';
|
||||
assert.deepEqual(term.joiner!(input), []);
|
||||
await delay(500);
|
||||
assert.isTrue(onRefresh.notCalled);
|
||||
@@ -209,11 +209,11 @@ class MockTerminal {
|
||||
public deregisterCharacterJoiner(id: number): void {
|
||||
this.joiner = undefined;
|
||||
}
|
||||
public setOption(name: string, value: string | number): void {
|
||||
this._options[name] = value;
|
||||
}
|
||||
public getOption(name: string): string | number {
|
||||
return this._options[name];
|
||||
public get options(): { [name: string]: string | number } { return this._options; }
|
||||
public set options(options: { [name: string]: string | number }) {
|
||||
for (const key in this._options) {
|
||||
this._options[key] = options[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ export function enableLigatures(term: Terminal): void {
|
||||
|
||||
term.registerCharacterJoiner((text: string): [number, number][] => {
|
||||
// If the font hasn't been loaded yet, load it and return an empty result
|
||||
const termFont = term.getOption('fontFamily');
|
||||
const termFont = term.options.fontFamily;
|
||||
if (
|
||||
termFont &&
|
||||
(loadingState === LoadingState.UNLOADED || currentFontName !== termFont)
|
||||
@@ -48,20 +48,20 @@ export function enableLigatures(term: Terminal): void {
|
||||
.then(f => {
|
||||
// Another request may have come in while we were waiting, so make
|
||||
// sure our font is still vaild.
|
||||
if (currentCallFontName === term.getOption('fontFamily')) {
|
||||
if (currentCallFontName === term.options.fontFamily) {
|
||||
loadingState = LoadingState.LOADED;
|
||||
font = f;
|
||||
|
||||
// Only refresh things if we actually found a font
|
||||
if (f) {
|
||||
term.refresh(0, term.getOption('rows') - 1);
|
||||
term.refresh(0, term.options.rows! - 1);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
// Another request may have come in while we were waiting, so make
|
||||
// sure our font is still vaild.
|
||||
if (currentCallFontName === term.getOption('fontFamily')) {
|
||||
if (currentCallFontName === term.options.fontFamily) {
|
||||
loadingState = LoadingState.FAILED;
|
||||
font = undefined;
|
||||
loadError = e;
|
||||
|
||||
@@ -141,9 +141,9 @@ lru-cache@^6.0.0:
|
||||
yallist "^4.0.0"
|
||||
|
||||
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==
|
||||
version "1.2.6"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
||||
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
||||
|
||||
mkdirp@0.5.5:
|
||||
version "0.5.5"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm-addon-search",
|
||||
"version": "0.8.2",
|
||||
"version": "0.9.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
|
||||
@@ -3,13 +3,25 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, IBufferLine, IDisposable, ITerminalAddon, ISelectionPosition } from 'xterm';
|
||||
import { Terminal, IDisposable, ITerminalAddon, ISelectionPosition, IDecoration } from 'xterm';
|
||||
import { EventEmitter } from 'common/EventEmitter';
|
||||
|
||||
export interface ISearchOptions {
|
||||
regex?: boolean;
|
||||
wholeWord?: boolean;
|
||||
caseSensitive?: boolean;
|
||||
incremental?: boolean;
|
||||
decorations?: ISearchDecorationOptions;
|
||||
noScroll?: boolean;
|
||||
}
|
||||
|
||||
interface ISearchDecorationOptions {
|
||||
matchBackground?: string;
|
||||
matchBorder?: string;
|
||||
matchOverviewRuler: string;
|
||||
activeMatchBackground?: string;
|
||||
activeMatchBorder?: string;
|
||||
activeMatchColorOverviewRuler: string;
|
||||
}
|
||||
|
||||
export interface ISearchPosition {
|
||||
@@ -40,7 +52,14 @@ const LINES_CACHE_TIME_TO_LIVE = 15 * 1000; // 15 secs
|
||||
|
||||
export class SearchAddon implements ITerminalAddon {
|
||||
private _terminal: Terminal | undefined;
|
||||
|
||||
private _cachedSearchTerm: string | undefined;
|
||||
private _selectedDecoration: IDecoration | undefined;
|
||||
private _resultDecorations: Map<number, IDecoration[]> | undefined;
|
||||
private _searchResults: Map<string, ISearchResult> | undefined;
|
||||
private _onDataDisposable: IDisposable | undefined;
|
||||
private _onResizeDisposable: IDisposable | undefined;
|
||||
private _lastSearchOptions: ISearchOptions | undefined;
|
||||
private _highlightTimeout: number | undefined;
|
||||
/**
|
||||
* translateBufferLineToStringWithWrap is a fairly expensive call.
|
||||
* We memoize the calls into an array that has a time based ttl.
|
||||
@@ -51,11 +70,56 @@ export class SearchAddon implements ITerminalAddon {
|
||||
private _cursorMoveListener: IDisposable | undefined;
|
||||
private _resizeListener: IDisposable | undefined;
|
||||
|
||||
private _resultIndex: number | undefined;
|
||||
|
||||
private readonly _onDidChangeResults = new EventEmitter<{ resultIndex: number, resultCount: number } | undefined>();
|
||||
public readonly onDidChangeResults = this._onDidChangeResults.event;
|
||||
|
||||
public activate(terminal: Terminal): void {
|
||||
this._terminal = terminal;
|
||||
this._onDataDisposable = this._terminal.onWriteParsed(() => this._updateMatches());
|
||||
this._onResizeDisposable = this._terminal.onResize(() => this._updateMatches());
|
||||
}
|
||||
|
||||
public dispose(): void { }
|
||||
private _updateMatches(): void {
|
||||
if (this._highlightTimeout) {
|
||||
window.clearTimeout(this._highlightTimeout);
|
||||
}
|
||||
if (this._cachedSearchTerm && this._lastSearchOptions?.decorations) {
|
||||
this._highlightTimeout = setTimeout(() => {
|
||||
this.findPrevious(this._cachedSearchTerm!, { ...this._lastSearchOptions, incremental: true, noScroll: true });
|
||||
this._resultIndex = this._searchResults ? this._searchResults.size - 1 : -1;
|
||||
this._onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults?.size ?? -1 });
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.clearDecorations();
|
||||
this._onDataDisposable?.dispose();
|
||||
this._onResizeDisposable?.dispose();
|
||||
}
|
||||
|
||||
public clearDecorations(retainCachedSearchTerm?: boolean): void {
|
||||
this._selectedDecoration?.dispose();
|
||||
this._searchResults?.clear();
|
||||
this._resultDecorations?.forEach(decorations => {
|
||||
for (const d of decorations) {
|
||||
d.dispose();
|
||||
}
|
||||
});
|
||||
this._resultDecorations?.clear();
|
||||
this._searchResults = undefined;
|
||||
this._resultDecorations = undefined;
|
||||
if (!retainCachedSearchTerm) {
|
||||
this._cachedSearchTerm = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public clearActiveDecoration(): void {
|
||||
this._selectedDecoration?.dispose();
|
||||
this._selectedDecoration = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the next instance of the term, then scroll to and select it. If it
|
||||
@@ -68,12 +132,107 @@ export class SearchAddon implements ITerminalAddon {
|
||||
if (!this._terminal) {
|
||||
throw new Error('Cannot use addon until it has been loaded');
|
||||
}
|
||||
this._lastSearchOptions = searchOptions;
|
||||
if (searchOptions?.decorations) {
|
||||
if (this._resultIndex !== undefined || this._cachedSearchTerm === undefined || term !== this._cachedSearchTerm) {
|
||||
this._highlightAllMatches(term, searchOptions);
|
||||
}
|
||||
}
|
||||
return this._fireResults(term, this._findNextAndSelect(term, searchOptions), searchOptions);
|
||||
}
|
||||
|
||||
private _highlightAllMatches(term: string, searchOptions: ISearchOptions): void {
|
||||
if (!this._terminal) {
|
||||
throw new Error('Cannot use addon until it has been loaded');
|
||||
}
|
||||
if (!term || term.length === 0) {
|
||||
this._terminal.clearSelection();
|
||||
this.clearDecorations();
|
||||
return;
|
||||
}
|
||||
searchOptions = searchOptions || {};
|
||||
|
||||
// new search, clear out the old decorations
|
||||
this.clearDecorations(true);
|
||||
this._searchResults = new Map<string, ISearchResult>();
|
||||
this._resultDecorations = new Map<number, IDecoration[]>();
|
||||
const resultDecorations = this._resultDecorations;
|
||||
let result = this._find(term, 0, 0, searchOptions);
|
||||
while (result && !this._searchResults.get(`${result.row}-${result.col}`)) {
|
||||
this._searchResults.set(`${result.row}-${result.col}`, result);
|
||||
result = this._find(
|
||||
term,
|
||||
result.col + result.term.length >= this._terminal.cols ? result.row + 1 : result.row,
|
||||
result.col + result.term.length >= this._terminal.cols ? 0 : result.col + 1,
|
||||
searchOptions
|
||||
);
|
||||
if (this._searchResults.size > 1000) {
|
||||
this.clearDecorations();
|
||||
this._resultIndex = undefined;
|
||||
return;
|
||||
}
|
||||
}
|
||||
this._searchResults.forEach(result => {
|
||||
const resultDecoration = this._createResultDecoration(result, searchOptions.decorations!);
|
||||
if (resultDecoration) {
|
||||
const decorationsForLine = resultDecorations.get(resultDecoration.marker.line) || [];
|
||||
decorationsForLine.push(resultDecoration);
|
||||
resultDecorations.set(resultDecoration.marker.line, decorationsForLine);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _find(term: string, startRow: number, startCol: number, searchOptions?: ISearchOptions): ISearchResult | undefined {
|
||||
if (!this._terminal || !term || term.length === 0) {
|
||||
this._terminal?.clearSelection();
|
||||
this.clearDecorations();
|
||||
return undefined;
|
||||
}
|
||||
if (startCol > this._terminal.cols) {
|
||||
throw new Error(`Invalid col: ${startCol} to search in terminal of ${this._terminal.cols} cols`);
|
||||
}
|
||||
|
||||
let result: ISearchResult | undefined = undefined;
|
||||
|
||||
this._initLinesCache();
|
||||
|
||||
const searchPosition: ISearchPosition = {
|
||||
startRow,
|
||||
startCol
|
||||
};
|
||||
|
||||
// Search startRow
|
||||
result = this._findInLine(term, searchPosition, searchOptions);
|
||||
// Search from startRow + 1 to end
|
||||
if (!result) {
|
||||
|
||||
for (let y = startRow + 1; y < this._terminal.buffer.active.baseY + this._terminal.rows; y++) {
|
||||
searchPosition.startRow = y;
|
||||
searchPosition.startCol = 0;
|
||||
// If the current line is wrapped line, increase index of column to ignore the previous scan
|
||||
// Otherwise, reset beginning column index to zero with set new unwrapped line index
|
||||
result = this._findInLine(term, searchPosition, searchOptions);
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private _findNextAndSelect(term: string, searchOptions?: ISearchOptions): boolean {
|
||||
if (!this._terminal || !term || term.length === 0) {
|
||||
this._terminal?.clearSelection();
|
||||
this.clearDecorations();
|
||||
this._cachedSearchTerm = undefined;
|
||||
this._resultIndex = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._cachedSearchTerm !== term) {
|
||||
this._resultIndex = undefined;
|
||||
this._terminal.clearSelection();
|
||||
}
|
||||
|
||||
let startCol = 0;
|
||||
let startRow = 0;
|
||||
let currentSelection: ISelectionPosition | undefined;
|
||||
@@ -95,7 +254,6 @@ export class SearchAddon implements ITerminalAddon {
|
||||
|
||||
// Search startRow
|
||||
let result = this._findInLine(term, searchPosition, searchOptions);
|
||||
|
||||
// Search from startRow + 1 to end
|
||||
if (!result) {
|
||||
|
||||
@@ -129,10 +287,21 @@ export class SearchAddon implements ITerminalAddon {
|
||||
result = this._findInLine(term, searchPosition, searchOptions);
|
||||
}
|
||||
|
||||
if (this._searchResults) {
|
||||
if (this._searchResults.size === 0) {
|
||||
this._resultIndex = -1;
|
||||
} else if (this._resultIndex === undefined) {
|
||||
this._resultIndex = 0;
|
||||
} else {
|
||||
this._resultIndex++;
|
||||
if (this._resultIndex >= this._searchResults.size) {
|
||||
this._resultIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Set selection and scroll if a result was found
|
||||
return this._selectResult(result);
|
||||
return this._selectResult(result, searchOptions?.decorations, searchOptions?.noScroll);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the previous instance of the term, then scroll to and select it. If it
|
||||
* doesn't exist, do nothing.
|
||||
@@ -144,16 +313,49 @@ export class SearchAddon implements ITerminalAddon {
|
||||
if (!this._terminal) {
|
||||
throw new Error('Cannot use addon until it has been loaded');
|
||||
}
|
||||
this._lastSearchOptions = searchOptions;
|
||||
if (searchOptions?.decorations) {
|
||||
if (this._resultIndex !== undefined || this._cachedSearchTerm === undefined || term !== this._cachedSearchTerm) {
|
||||
this._highlightAllMatches(term, searchOptions);
|
||||
}
|
||||
}
|
||||
return this._fireResults(term, this._findPreviousAndSelect(term, searchOptions), searchOptions);
|
||||
}
|
||||
|
||||
if (!term || term.length === 0) {
|
||||
this._terminal.clearSelection();
|
||||
private _fireResults(term: string, found: boolean, searchOptions?: ISearchOptions): boolean {
|
||||
if (searchOptions?.decorations) {
|
||||
if (this._resultIndex !== undefined && this._searchResults?.size !== undefined) {
|
||||
this._onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults.size });
|
||||
} else {
|
||||
this._onDidChangeResults.fire(undefined);
|
||||
}
|
||||
}
|
||||
this._cachedSearchTerm = term;
|
||||
return found;
|
||||
}
|
||||
|
||||
private _findPreviousAndSelect(term: string, searchOptions?: ISearchOptions): boolean {
|
||||
if (!this._terminal) {
|
||||
throw new Error('Cannot use addon until it has been loaded');
|
||||
}
|
||||
let result: ISearchResult | undefined;
|
||||
if (!this._terminal || !term || term.length === 0) {
|
||||
result = undefined;
|
||||
this._terminal?.clearSelection();
|
||||
this.clearDecorations();
|
||||
this._resultIndex = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
const isReverseSearch = true;
|
||||
if (this._cachedSearchTerm !== term) {
|
||||
this._resultIndex = undefined;
|
||||
this._terminal.clearSelection();
|
||||
}
|
||||
|
||||
let startRow = this._terminal.buffer.active.baseY + this._terminal.rows;
|
||||
let startCol = this._terminal.cols;
|
||||
let result: ISearchResult | undefined;
|
||||
const isReverseSearch = true;
|
||||
|
||||
const incremental = searchOptions ? searchOptions.incremental : false;
|
||||
let currentSelection: ISelectionPosition | undefined;
|
||||
if (this._terminal.hasSelection()) {
|
||||
@@ -207,11 +409,24 @@ export class SearchAddon implements ITerminalAddon {
|
||||
}
|
||||
}
|
||||
|
||||
if (this._searchResults) {
|
||||
if (this._searchResults.size === 0) {
|
||||
this._resultIndex = -1;
|
||||
} else if (this._resultIndex === undefined || this._resultIndex < 0) {
|
||||
this._resultIndex = this._searchResults.size - 1;
|
||||
} else {
|
||||
this._resultIndex--;
|
||||
if (this._resultIndex === -1) {
|
||||
this._resultIndex = this._searchResults.size - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there is only one result, return true.
|
||||
if (!result && currentSelection) return true;
|
||||
|
||||
// Set selection and scroll if a result was found
|
||||
return this._selectResult(result);
|
||||
return this._selectResult(result, searchOptions?.decorations, searchOptions?.noScroll);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -393,7 +608,8 @@ export class SearchAddon implements ITerminalAddon {
|
||||
break;
|
||||
}
|
||||
if (cell.getWidth()) {
|
||||
offset += cell.getChars().length;
|
||||
// Treat null characters as whitespace to align with the translateToString API
|
||||
offset += cell.getCode() === 0 ? 1 : cell.getChars().length;
|
||||
}
|
||||
}
|
||||
lineIndex++;
|
||||
@@ -446,21 +662,91 @@ export class SearchAddon implements ITerminalAddon {
|
||||
/**
|
||||
* Selects and scrolls to a result.
|
||||
* @param result The result to select.
|
||||
* @return Whethera result was selected.
|
||||
* @return Whether a result was selected.
|
||||
*/
|
||||
private _selectResult(result: ISearchResult | undefined): boolean {
|
||||
private _selectResult(result: ISearchResult | undefined, options?: ISearchDecorationOptions, noScroll?: boolean): boolean {
|
||||
const terminal = this._terminal!;
|
||||
this.clearActiveDecoration();
|
||||
if (!result) {
|
||||
terminal.clearSelection();
|
||||
return false;
|
||||
}
|
||||
terminal.select(result.col, result.row, result.size);
|
||||
// If it is not in the viewport then we scroll else it just gets selected
|
||||
if (result.row >= (terminal.buffer.active.viewportY + terminal.rows) || result.row < terminal.buffer.active.viewportY) {
|
||||
let scroll = result.row - terminal.buffer.active.viewportY;
|
||||
scroll -= Math.floor(terminal.rows / 2);
|
||||
terminal.scrollLines(scroll);
|
||||
if (options) {
|
||||
const marker = terminal.registerMarker(-terminal.buffer.active.baseY - terminal.buffer.active.cursorY + result.row);
|
||||
if (marker) {
|
||||
this._selectedDecoration = terminal.registerDecoration({
|
||||
marker,
|
||||
x: result.col,
|
||||
width: result.size,
|
||||
backgroundColor: options.activeMatchBackground,
|
||||
layer: 'top',
|
||||
overviewRulerOptions: {
|
||||
color: options.activeMatchColorOverviewRuler
|
||||
}
|
||||
});
|
||||
this._selectedDecoration?.onRender((e) => this._applyStyles(e, options.activeMatchBorder, true));
|
||||
this._selectedDecoration?.onDispose(() => marker.dispose());
|
||||
}
|
||||
}
|
||||
|
||||
if (!noScroll) {
|
||||
// If it is not in the viewport then we scroll else it just gets selected
|
||||
if (result.row >= (terminal.buffer.active.viewportY + terminal.rows) || result.row < terminal.buffer.active.viewportY) {
|
||||
let scroll = result.row - terminal.buffer.active.viewportY;
|
||||
scroll -= Math.floor(terminal.rows / 2);
|
||||
terminal.scrollLines(scroll);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies styles to the decoration when it is rendered
|
||||
* @param element the decoration's element
|
||||
* @param backgroundColor the background color to apply
|
||||
* @param borderColor the border color to apply
|
||||
* @returns
|
||||
*/
|
||||
private _applyStyles(element: HTMLElement, borderColor: string | undefined, isActiveResult: boolean): void {
|
||||
if (element.clientWidth <= 0) {
|
||||
return;
|
||||
}
|
||||
if (!element.classList.contains('xterm-find-result-decoration')) {
|
||||
element.classList.add('xterm-find-result-decoration');
|
||||
if (borderColor) {
|
||||
element.style.outline = `1px solid ${borderColor}`;
|
||||
}
|
||||
}
|
||||
if (isActiveResult) {
|
||||
element.classList.add('xterm-find-active-result-decoration');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a decoration for the result and applies styles
|
||||
* @param result the search result for which to create the decoration
|
||||
* @param options the options for the decoration
|
||||
* @returns the {@link IDecoration} or undefined if the marker has already been disposed of
|
||||
*/
|
||||
private _createResultDecoration(result: ISearchResult, options: ISearchDecorationOptions): IDecoration | undefined {
|
||||
const terminal = this._terminal!;
|
||||
const marker = terminal.registerMarker(-terminal.buffer.active.baseY - terminal.buffer.active.cursorY + result.row);
|
||||
if (!marker) {
|
||||
return undefined;
|
||||
}
|
||||
const findResultDecoration = terminal.registerDecoration({
|
||||
marker,
|
||||
x: result.col,
|
||||
width: result.size,
|
||||
backgroundColor: options.matchBackground,
|
||||
overviewRulerOptions: this._resultDecorations?.get(marker.line) ? undefined : {
|
||||
color: options.matchOverviewRuler,
|
||||
position: 'center'
|
||||
}
|
||||
});
|
||||
findResultDecoration?.onRender((e) => this._applyStyles(e, options.matchBorder, false));
|
||||
findResultDecoration?.onDispose(() => marker.dispose());
|
||||
return findResultDecoration;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,20 @@
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha"
|
||||
]
|
||||
],
|
||||
"paths": {
|
||||
"common/*": [
|
||||
"../../../src/common/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../src/common"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { assert } from 'chai';
|
||||
import { readFile } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
import { openTerminal, writeSync, launchBrowser } from '../../../out-test/api/TestUtils';
|
||||
import { openTerminal, writeSync, launchBrowser, timeout } from '../../../out-test/api/TestUtils';
|
||||
import { Browser, Page } from 'playwright';
|
||||
|
||||
const APP = 'http://127.0.0.1:3001/test';
|
||||
@@ -16,15 +16,13 @@ let page: Page;
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
|
||||
describe('Search Tests', function(): void {
|
||||
before(async function(): Promise<any> {
|
||||
describe('Search Tests', function (): void {
|
||||
before(async function (): Promise<any> {
|
||||
browser = await launchBrowser();
|
||||
page = await (await browser.newContext()).newPage();
|
||||
await page.setViewportSize({ width, height });
|
||||
await page.goto(APP);
|
||||
await openTerminal(page);
|
||||
await page.evaluate(`window.search = new SearchAddon();`);
|
||||
await page.evaluate(`window.term.loadAddon(window.search);`);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
@@ -32,7 +30,12 @@ describe('Search Tests', function(): void {
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await page.evaluate(`window.term.reset()`);
|
||||
await page.evaluate(`
|
||||
window.term.reset()
|
||||
window.search?.dispose();
|
||||
window.search = new SearchAddon();
|
||||
window.term.loadAddon(window.search);
|
||||
`);
|
||||
});
|
||||
|
||||
it('Simple Search', async () => {
|
||||
@@ -120,6 +123,176 @@ describe('Search Tests', function(): void {
|
||||
});
|
||||
});
|
||||
|
||||
describe('onDidChangeResults', async () => {
|
||||
describe('findNext', () => {
|
||||
it('should not fire unless the decorations option is set', async () => {
|
||||
await page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
await writeSync(page, 'abc');
|
||||
assert.strictEqual(await page.evaluate(`window.search.findNext('a')`), true);
|
||||
assert.strictEqual(await page.evaluate('window.calls.length'), 0);
|
||||
assert.strictEqual(await page.evaluate(`window.search.findNext('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.strictEqual(await page.evaluate('window.calls.length'), 1);
|
||||
});
|
||||
it('should fire with correct event values', async () => {
|
||||
await page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
await writeSync(page, 'abc bc c');
|
||||
assert.strictEqual(await page.evaluate(`window.search.findNext('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 1, resultIndex: 0 }
|
||||
]);
|
||||
assert.strictEqual(await page.evaluate(`window.search.findNext('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 1, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 }
|
||||
]);
|
||||
assert.strictEqual(await page.evaluate(`window.search.findNext('d', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 1, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 0, resultIndex: -1 }
|
||||
]);
|
||||
assert.strictEqual(await page.evaluate(`window.search.findNext('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.strictEqual(await page.evaluate(`window.search.findNext('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.strictEqual(await page.evaluate(`window.search.findNext('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 1, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 0, resultIndex: -1 },
|
||||
{ resultCount: 3, resultIndex: 0 },
|
||||
{ resultCount: 3, resultIndex: 1 },
|
||||
{ resultCount: 3, resultIndex: 2 }
|
||||
]);
|
||||
});
|
||||
it('should fire with correct event values (incremental)', async () => {
|
||||
await page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
await writeSync(page, 'abc aabc');
|
||||
assert.deepStrictEqual(await page.evaluate(`window.search.findNext('a', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 0 }
|
||||
]);
|
||||
assert.deepStrictEqual(await page.evaluate(`window.search.findNext('ab', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 }
|
||||
]);
|
||||
assert.deepStrictEqual(await page.evaluate(`window.search.findNext('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 }
|
||||
]);
|
||||
assert.deepStrictEqual(await page.evaluate(`window.search.findNext('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 1 }
|
||||
]);
|
||||
assert.deepStrictEqual(await page.evaluate(`window.search.findNext('abcd', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 0, resultIndex: -1 }
|
||||
]);
|
||||
});
|
||||
});
|
||||
describe('findPrevious', () => {
|
||||
it('should not fire unless the decorations option is set', async () => {
|
||||
await page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
await writeSync(page, 'abc');
|
||||
assert.strictEqual(await page.evaluate(`window.search.findPrevious('a')`), true);
|
||||
assert.strictEqual(await page.evaluate('window.calls.length'), 0);
|
||||
assert.strictEqual(await page.evaluate(`window.search.findPrevious('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.strictEqual(await page.evaluate('window.calls.length'), 1);
|
||||
});
|
||||
it('should fire with correct event values', async () => {
|
||||
await page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
await writeSync(page, 'abc bc c');
|
||||
assert.strictEqual(await page.evaluate(`window.search.findPrevious('a', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 1, resultIndex: 0 }
|
||||
]);
|
||||
assert.strictEqual(await page.evaluate(`window.search.findPrevious('b', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 1, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 1 }
|
||||
]);
|
||||
await timeout(2000);
|
||||
assert.strictEqual(await page.evaluate(`debugger; window.search.findPrevious('d', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 1, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 0, resultIndex: -1 }
|
||||
]);
|
||||
assert.strictEqual(await page.evaluate(`window.search.findPrevious('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.strictEqual(await page.evaluate(`window.search.findPrevious('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.strictEqual(await page.evaluate(`window.search.findPrevious('c', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 1, resultIndex: 0 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 0, resultIndex: -1 },
|
||||
{ resultCount: 3, resultIndex: 2 },
|
||||
{ resultCount: 3, resultIndex: 1 },
|
||||
{ resultCount: 3, resultIndex: 0 }
|
||||
]);
|
||||
});
|
||||
it('should fire with correct event values (incremental)', async () => {
|
||||
await page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
await writeSync(page, 'abc aabc');
|
||||
assert.deepStrictEqual(await page.evaluate(`window.search.findPrevious('a', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 2 }
|
||||
]);
|
||||
assert.deepStrictEqual(await page.evaluate(`window.search.findPrevious('ab', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 2 },
|
||||
{ resultCount: 2, resultIndex: 1 }
|
||||
]);
|
||||
assert.deepStrictEqual(await page.evaluate(`window.search.findPrevious('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 2 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 2, resultIndex: 1 }
|
||||
]);
|
||||
assert.deepStrictEqual(await page.evaluate(`window.search.findPrevious('abc', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 2 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 2, resultIndex: 0 }
|
||||
]);
|
||||
assert.deepStrictEqual(await page.evaluate(`window.search.findPrevious('abcd', { incremental: true, decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), false);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 3, resultIndex: 2 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 2, resultIndex: 1 },
|
||||
{ resultCount: 2, resultIndex: 0 },
|
||||
{ resultCount: 0, resultIndex: -1 }
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Regression tests', () => {
|
||||
describe('#2444 wrapped line content not being found', () => {
|
||||
let fixture: string;
|
||||
@@ -134,7 +307,7 @@ describe('Search Tests', function(): void {
|
||||
.replace(/\n/g, '\\n\\r');
|
||||
}
|
||||
fixture = fixture
|
||||
.replace(/'/g, '\\\'');
|
||||
.replace(/'/g, `\\'`);
|
||||
});
|
||||
it('should find all occurrences using findNext', async () => {
|
||||
await writeSync(page, fixture);
|
||||
@@ -206,6 +379,21 @@ describe('Search Tests', function(): void {
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#3834 lines with null characters before search terms', () => {
|
||||
// This case can be triggered by the prompt when using starship under conpty
|
||||
it('should find all matches on a line containing null characters', async () => {
|
||||
await page.evaluate(`
|
||||
window.calls = [];
|
||||
window.search.onDidChangeResults(e => window.calls.push(e));
|
||||
`);
|
||||
// Move cursor forward 1 time to create a null character, as opposed to regular whitespace
|
||||
await writeSync(page, '\\x1b[CHi Hi');
|
||||
assert.strictEqual(await page.evaluate(`window.search.findPrevious('h', { decorations: { activeMatchColorOverviewRuler: '#ff0000' } })`), true);
|
||||
assert.deepStrictEqual(await page.evaluate('window.calls'), [
|
||||
{ resultCount: 2, resultIndex: 1 }
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function makeData(length: number): string {
|
||||
|
||||
+63
-1
@@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, ILinkMatcherOptions, IDisposable, ITerminalAddon } from 'xterm';
|
||||
import { Terminal, ITerminalAddon, IEvent } from 'xterm';
|
||||
|
||||
declare module 'xterm-addon-search' {
|
||||
/**
|
||||
@@ -32,6 +32,47 @@ declare module 'xterm-addon-search' {
|
||||
* `findNext`, not `findPrevious`.
|
||||
*/
|
||||
incremental?: boolean;
|
||||
|
||||
/**
|
||||
* When set, will highlight all instances of the word on search and show
|
||||
* them in the overview ruler if it's enabled.
|
||||
*/
|
||||
decorations?: ISearchDecorationOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for showing decorations when searching.
|
||||
*/
|
||||
interface ISearchDecorationOptions {
|
||||
/**
|
||||
* The background color of a match, this must use #RRGGBB format.
|
||||
*/
|
||||
matchBackground?: string;
|
||||
|
||||
/**
|
||||
* The border color of a match.
|
||||
*/
|
||||
matchBorder?: string;
|
||||
|
||||
/**
|
||||
* The overview ruler color of a match.
|
||||
*/
|
||||
matchOverviewRuler: string;
|
||||
|
||||
/**
|
||||
* The background color for the currently active match, this must use #RRGGBB format.
|
||||
*/
|
||||
activeMatchBackground?: string;
|
||||
|
||||
/**
|
||||
* The border color of the currently active match.
|
||||
*/
|
||||
activeMatchBorder?: string;
|
||||
|
||||
/**
|
||||
* The overview ruler color of the currently active match.
|
||||
*/
|
||||
activeMatchColorOverviewRuler: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,5 +105,26 @@ declare module 'xterm-addon-search' {
|
||||
* @param searchOptions The options for the search.
|
||||
*/
|
||||
public findPrevious(term: string, searchOptions?: ISearchOptions): boolean;
|
||||
|
||||
/**
|
||||
* Clears the decorations and selection
|
||||
*/
|
||||
public clearDecorations(): void;
|
||||
|
||||
/**
|
||||
* Clears the active result decoration, this decoration is applied on top of the selection so
|
||||
* removing it will reveal the selection underneath. This is intended to be called on the search
|
||||
* textarea's `blur` event.
|
||||
*/
|
||||
public clearActiveDecoration(): void;
|
||||
|
||||
/**
|
||||
* When decorations are enabled, fires when
|
||||
* the search results change.
|
||||
* @returns -1 for resultIndex for a resultCount of 0
|
||||
* and @returns undefined when the threshold of 1k results
|
||||
* is exceeded and decorations are disposed of.
|
||||
*/
|
||||
readonly onDidChangeResults: IEvent<{ resultIndex: number, resultCount: number } | undefined>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,13 @@ module.exports = {
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
modules: ['./node_modules'],
|
||||
extensions: [ '.js' ],
|
||||
alias: {
|
||||
common: path.resolve('../../out/common')
|
||||
}
|
||||
},
|
||||
output: {
|
||||
filename: mainFile,
|
||||
path: path.resolve('./lib'),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm-addon-serialize",
|
||||
"version": "0.6.1",
|
||||
"version": "0.7.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
|
||||
@@ -43,7 +43,7 @@ class TestSelectionService {
|
||||
}
|
||||
}
|
||||
|
||||
describe('xterm-addon-serialize html', () => {
|
||||
describe('xterm-addon-serialize', () => {
|
||||
let cm: ColorManager;
|
||||
let dom: jsdom.JSDOM;
|
||||
let document: Document;
|
||||
@@ -83,123 +83,132 @@ describe('xterm-addon-serialize html', () => {
|
||||
(terminal as any)._core._selectionService = selectionService;
|
||||
});
|
||||
|
||||
it('empty terminal with selection turned off', () => {
|
||||
const output = serializeAddon.serializeAsHTML();
|
||||
assert.notEqual(output, '');
|
||||
assert.equal((output.match(new RegExp('<div><span> {10}</span><\/div>', 'g')) || []).length, 2);
|
||||
});
|
||||
|
||||
it('empty terminal with no selection', () => {
|
||||
const output = serializeAddon.serializeAsHTML({
|
||||
onlySelection: true
|
||||
describe('text', () => {
|
||||
it('restoring cursor styles', async () => {
|
||||
await writeP(terminal, sgr('32') + '> ' + sgr('0'));
|
||||
assert.equal(serializeAddon.serialize(), '\u001b[32m> \u001b[0m');
|
||||
});
|
||||
assert.equal(output, '');
|
||||
});
|
||||
|
||||
it('basic terminal with selection', async () => {
|
||||
await writeP(terminal, ' terminal ');
|
||||
terminal.select(1, 0, 8);
|
||||
|
||||
const output = serializeAddon.serializeAsHTML({
|
||||
onlySelection: true
|
||||
describe('html', () => {
|
||||
it('empty terminal with selection turned off', () => {
|
||||
const output = serializeAddon.serializeAsHTML();
|
||||
assert.notEqual(output, '');
|
||||
assert.equal((output.match(/<div><span> {10}<\/span><\/div>/g) || []).length, 2);
|
||||
});
|
||||
assert.equal((output.match(new RegExp('<div><span>terminal<\/span><\/div>', 'g')) || []).length, 1, output);
|
||||
});
|
||||
|
||||
it('cells with bold styling', async () => {
|
||||
await writeP(terminal, ' ' + sgr('1') + 'terminal' + sgr('22') + ' ');
|
||||
|
||||
const output = serializeAddon.serializeAsHTML();
|
||||
assert.equal((output.match(new RegExp('<span style=\'font-weight: bold;\'>terminal<\/span>', 'g')) || []).length, 1, output);
|
||||
});
|
||||
|
||||
it('cells with italic styling', async () => {
|
||||
await writeP(terminal, ' ' + sgr('3') + 'terminal' + sgr('23') + ' ');
|
||||
|
||||
const output = serializeAddon.serializeAsHTML();
|
||||
assert.equal((output.match(new RegExp('<span style=\'font-style: italic;\'>terminal<\/span>', 'g')) || []).length, 1, output);
|
||||
});
|
||||
|
||||
it('cells with inverse styling', async () => {
|
||||
await writeP(terminal, ' ' + sgr('7') + 'terminal' + sgr('27') + ' ');
|
||||
|
||||
const output = serializeAddon.serializeAsHTML();
|
||||
assert.equal((output.match(new RegExp('<span style=\'color: #000000; background-color: #BFBFBF;\'>terminal<\/span>', 'g')) || []).length, 1, output);
|
||||
});
|
||||
|
||||
it('cells with underline styling', async () => {
|
||||
await writeP(terminal, ' ' + sgr('4') + 'terminal' + sgr('24') + ' ');
|
||||
|
||||
const output = serializeAddon.serializeAsHTML();
|
||||
assert.equal((output.match(new RegExp('<span style=\'text-decoration: underline;\'>terminal<\/span>', 'g')) || []).length, 1, output);
|
||||
});
|
||||
|
||||
it('cells with invisible styling', async () => {
|
||||
await writeP(terminal, ' ' + sgr('8') + 'terminal' + sgr('28') + ' ');
|
||||
|
||||
const output = serializeAddon.serializeAsHTML();
|
||||
assert.equal((output.match(new RegExp('<span style=\'visibility: hidden;\'>terminal<\/span>', 'g')) || []).length, 1, output);
|
||||
});
|
||||
|
||||
it('cells with dim styling', async () => {
|
||||
await writeP(terminal, ' ' + sgr('2') + 'terminal' + sgr('22') + ' ');
|
||||
|
||||
const output = serializeAddon.serializeAsHTML();
|
||||
assert.equal((output.match(new RegExp('<span style=\'opacity: 0.5;\'>terminal<\/span>', 'g')) || []).length, 1, output);
|
||||
});
|
||||
|
||||
it('cells with strikethrough styling', async () => {
|
||||
await writeP(terminal, ' ' + sgr('9') + 'terminal' + sgr('29') + ' ');
|
||||
|
||||
const output = serializeAddon.serializeAsHTML();
|
||||
assert.equal((output.match(new RegExp('<span style=\'text-decoration: line-through;\'>terminal<\/span>', 'g')) || []).length, 1, output);
|
||||
});
|
||||
|
||||
it('cells with combined styling', async () => {
|
||||
await writeP(terminal, sgr('1') + ' ' + sgr('9') + 'termi' + sgr('22') + 'nal' + sgr('29') + ' ');
|
||||
|
||||
const output = serializeAddon.serializeAsHTML();
|
||||
assert.equal((output.match(new RegExp('<span style=\'font-weight: bold;\'> <\/span>', 'g')) || []).length, 1, output);
|
||||
assert.equal((output.match(new RegExp('<span style=\'font-weight: bold; text-decoration: line-through;\'>termi<\/span>', 'g')) || []).length, 1, output);
|
||||
assert.equal((output.match(new RegExp('<span style=\'text-decoration: line-through;\'>nal<\/span>', 'g')) || []).length, 1, output);
|
||||
});
|
||||
|
||||
it('cells with color styling', async () => {
|
||||
await writeP(terminal, ' ' + sgr('38;5;46') + 'terminal' + sgr('39') + ' ');
|
||||
|
||||
const output = serializeAddon.serializeAsHTML();
|
||||
assert.equal((output.match(new RegExp('<span style=\'color: #00ff00;\'>terminal<\/span>', 'g')) || []).length, 1, output);
|
||||
});
|
||||
|
||||
it('cells with background styling', async () => {
|
||||
await writeP(terminal, ' ' + sgr('48;5;46') + 'terminal' + sgr('49') + ' ');
|
||||
|
||||
const output = serializeAddon.serializeAsHTML();
|
||||
assert.equal((output.match(new RegExp('<span style=\'background-color: #00ff00;\'>terminal<\/span>', 'g')) || []).length, 1, output);
|
||||
});
|
||||
|
||||
it('empty terminal with default options', async () => {
|
||||
const output = serializeAddon.serializeAsHTML();
|
||||
assert.equal((output.match(new RegExp('color: #000000; background-color: #ffffff; font-family: courier-new, courier, monospace; font-size: 15px;', 'g')) || []).length, 1, output);
|
||||
});
|
||||
|
||||
it('empty terminal with custom options', async () => {
|
||||
terminal.options.fontFamily = 'verdana';
|
||||
terminal.options.fontSize = 20;
|
||||
terminal.options.theme = {
|
||||
foreground: '#ff00ff',
|
||||
background: '#00ff00'
|
||||
};
|
||||
const output = serializeAddon.serializeAsHTML({
|
||||
includeGlobalBackground: true
|
||||
it('empty terminal with no selection', () => {
|
||||
const output = serializeAddon.serializeAsHTML({
|
||||
onlySelection: true
|
||||
});
|
||||
assert.equal(output, '');
|
||||
});
|
||||
assert.equal((output.match(new RegExp('color: #ff00ff; background-color: #00ff00; font-family: verdana; font-size: 20px;', 'g')) || []).length, 1, output);
|
||||
});
|
||||
|
||||
it('empty terminal with background included', async () => {
|
||||
const output = serializeAddon.serializeAsHTML({
|
||||
includeGlobalBackground: true
|
||||
it('basic terminal with selection', async () => {
|
||||
await writeP(terminal, ' terminal ');
|
||||
terminal.select(1, 0, 8);
|
||||
|
||||
const output = serializeAddon.serializeAsHTML({
|
||||
onlySelection: true
|
||||
});
|
||||
assert.equal((output.match(/<div><span>terminal<\/span><\/div>/g) || []).length, 1, output);
|
||||
});
|
||||
|
||||
it('cells with bold styling', async () => {
|
||||
await writeP(terminal, ' ' + sgr('1') + 'terminal' + sgr('22') + ' ');
|
||||
|
||||
const output = serializeAddon.serializeAsHTML();
|
||||
assert.equal((output.match(/<span style='font-weight: bold;'>terminal<\/span>/g) || []).length, 1, output);
|
||||
});
|
||||
|
||||
it('cells with italic styling', async () => {
|
||||
await writeP(terminal, ' ' + sgr('3') + 'terminal' + sgr('23') + ' ');
|
||||
|
||||
const output = serializeAddon.serializeAsHTML();
|
||||
assert.equal((output.match(/<span style='font-style: italic;'>terminal<\/span>/g) || []).length, 1, output);
|
||||
});
|
||||
|
||||
it('cells with inverse styling', async () => {
|
||||
await writeP(terminal, ' ' + sgr('7') + 'terminal' + sgr('27') + ' ');
|
||||
|
||||
const output = serializeAddon.serializeAsHTML();
|
||||
assert.equal((output.match(/<span style='color: #000000; background-color: #BFBFBF;'>terminal<\/span>/g) || []).length, 1, output);
|
||||
});
|
||||
|
||||
it('cells with underline styling', async () => {
|
||||
await writeP(terminal, ' ' + sgr('4') + 'terminal' + sgr('24') + ' ');
|
||||
|
||||
const output = serializeAddon.serializeAsHTML();
|
||||
assert.equal((output.match(/<span style='text-decoration: underline;'>terminal<\/span>/g) || []).length, 1, output);
|
||||
});
|
||||
|
||||
it('cells with invisible styling', async () => {
|
||||
await writeP(terminal, ' ' + sgr('8') + 'terminal' + sgr('28') + ' ');
|
||||
|
||||
const output = serializeAddon.serializeAsHTML();
|
||||
assert.equal((output.match(/<span style='visibility: hidden;'>terminal<\/span>/g) || []).length, 1, output);
|
||||
});
|
||||
|
||||
it('cells with dim styling', async () => {
|
||||
await writeP(terminal, ' ' + sgr('2') + 'terminal' + sgr('22') + ' ');
|
||||
|
||||
const output = serializeAddon.serializeAsHTML();
|
||||
assert.equal((output.match(/<span style='opacity: 0.5;'>terminal<\/span>/g) || []).length, 1, output);
|
||||
});
|
||||
|
||||
it('cells with strikethrough styling', async () => {
|
||||
await writeP(terminal, ' ' + sgr('9') + 'terminal' + sgr('29') + ' ');
|
||||
|
||||
const output = serializeAddon.serializeAsHTML();
|
||||
assert.equal((output.match(/<span style='text-decoration: line-through;'>terminal<\/span>/g) || []).length, 1, output);
|
||||
});
|
||||
|
||||
it('cells with combined styling', async () => {
|
||||
await writeP(terminal, sgr('1') + ' ' + sgr('9') + 'termi' + sgr('22') + 'nal' + sgr('29') + ' ');
|
||||
|
||||
const output = serializeAddon.serializeAsHTML();
|
||||
assert.equal((output.match(/<span style='font-weight: bold;'> <\/span>/g) || []).length, 1, output);
|
||||
assert.equal((output.match(/<span style='font-weight: bold; text-decoration: line-through;'>termi<\/span>/g) || []).length, 1, output);
|
||||
assert.equal((output.match(/<span style='text-decoration: line-through;'>nal<\/span>/g) || []).length, 1, output);
|
||||
});
|
||||
|
||||
it('cells with color styling', async () => {
|
||||
await writeP(terminal, ' ' + sgr('38;5;46') + 'terminal' + sgr('39') + ' ');
|
||||
|
||||
const output = serializeAddon.serializeAsHTML();
|
||||
assert.equal((output.match(/<span style='color: #00ff00;'>terminal<\/span>/g) || []).length, 1, output);
|
||||
});
|
||||
|
||||
it('cells with background styling', async () => {
|
||||
await writeP(terminal, ' ' + sgr('48;5;46') + 'terminal' + sgr('49') + ' ');
|
||||
|
||||
const output = serializeAddon.serializeAsHTML();
|
||||
assert.equal((output.match(/<span style='background-color: #00ff00;'>terminal<\/span>/g) || []).length, 1, output);
|
||||
});
|
||||
|
||||
it('empty terminal with default options', async () => {
|
||||
const output = serializeAddon.serializeAsHTML();
|
||||
assert.equal((output.match(/color: #000000; background-color: #ffffff; font-family: courier-new, courier, monospace; font-size: 15px;/g) || []).length, 1, output);
|
||||
});
|
||||
|
||||
it('empty terminal with custom options', async () => {
|
||||
terminal.options.fontFamily = 'verdana';
|
||||
terminal.options.fontSize = 20;
|
||||
terminal.options.theme = {
|
||||
foreground: '#ff00ff',
|
||||
background: '#00ff00'
|
||||
};
|
||||
const output = serializeAddon.serializeAsHTML({
|
||||
includeGlobalBackground: true
|
||||
});
|
||||
assert.equal((output.match(/color: #ff00ff; background-color: #00ff00; font-family: verdana; font-size: 20px;/g) || []).length, 1, output);
|
||||
});
|
||||
|
||||
it('empty terminal with background included', async () => {
|
||||
const output = serializeAddon.serializeAsHTML({
|
||||
includeGlobalBackground: true
|
||||
});
|
||||
assert.equal((output.match(/color: #ffffff; background-color: #000000; font-family: courier-new, courier, monospace; font-size: 15px;/g) || []).length, 1, output);
|
||||
});
|
||||
assert.equal((output.match(new RegExp('color: #ffffff; background-color: #000000; font-family: courier-new, courier, monospace; font-size: 15px;', 'g')) || []).length, 1, output);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import { Terminal, ITerminalAddon, IBuffer, IBufferCell, IBufferRange } from 'xterm';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { IAttributeData } from 'common/Types';
|
||||
|
||||
function constrain(value: number, low: number, high: number): number {
|
||||
return Math.max(low, Math.min(value, high));
|
||||
@@ -62,17 +63,17 @@ abstract class BaseSerializeHandler {
|
||||
protected _serializeString(): string { return ''; }
|
||||
}
|
||||
|
||||
function equalFg(cell1: IBufferCell, cell2: IBufferCell): boolean {
|
||||
function equalFg(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): boolean {
|
||||
return cell1.getFgColorMode() === cell2.getFgColorMode()
|
||||
&& cell1.getFgColor() === cell2.getFgColor();
|
||||
}
|
||||
|
||||
function equalBg(cell1: IBufferCell, cell2: IBufferCell): boolean {
|
||||
function equalBg(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): boolean {
|
||||
return cell1.getBgColorMode() === cell2.getBgColorMode()
|
||||
&& cell1.getBgColor() === cell2.getBgColor();
|
||||
}
|
||||
|
||||
function equalFlags(cell1: IBufferCell, cell2: IBufferCell): boolean {
|
||||
function equalFlags(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): boolean {
|
||||
return cell1.isInverse() === cell2.isInverse()
|
||||
&& cell1.isBold() === cell2.isBold()
|
||||
&& cell1.isUnderline() === cell2.isUnderline()
|
||||
@@ -229,7 +230,7 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
||||
this._nullCellCount = 0;
|
||||
}
|
||||
|
||||
private _diffStyle(cell: IBufferCell, oldCell: IBufferCell): number[] {
|
||||
private _diffStyle(cell: IBufferCell | IAttributeData, oldCell: IBufferCell): number[] {
|
||||
const sgrSeq: number[] = [];
|
||||
const fgChanged = !equalFg(cell, oldCell);
|
||||
const bgChanged = !equalBg(cell, oldCell);
|
||||
@@ -393,6 +394,15 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
||||
moveRight(realCursorCol - this._lastCursorCol);
|
||||
}
|
||||
|
||||
// Restore the cursor's current style, see https://github.com/xtermjs/xterm.js/issues/3677
|
||||
// HACK: Internal API access since it's awkward to expose this in the API and serialize will
|
||||
// likely be the only consumer
|
||||
const curAttrData: IAttributeData = (this._terminal as any)._core._inputHandler._curAttrData;
|
||||
const sgrSeq = this._diffStyle(curAttrData, this._cursorStyle);
|
||||
if (sgrSeq.length > 0) {
|
||||
content += `\u001b[${sgrSeq.join(';')}m`;
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
}
|
||||
@@ -544,7 +554,7 @@ export class HTMLSerializeHandler extends BaseSerializeHandler {
|
||||
return target;
|
||||
}
|
||||
|
||||
targetLength = targetLength - target.length;
|
||||
targetLength -= target.length;
|
||||
if (targetLength > padString.length) {
|
||||
padString += padString.repeat(targetLength / padString.length);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ let page: Page;
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
|
||||
const writeRawSync = (page: any, str: string): Promise<void> => writeSync(page, '\' +' + JSON.stringify(str) + '+ \'');
|
||||
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);
|
||||
@@ -83,7 +83,7 @@ describe('SerializeAddon', () => {
|
||||
const buffer3 = await page.evaluate(`inspectBuffer(term.buffer.normal);`);
|
||||
|
||||
await page.evaluate(`term.reset();`);
|
||||
await writeRawSync(page, '1234567890n12345');
|
||||
await writeRawSync(page, '123456789012345');
|
||||
const buffer4 = await page.evaluate(`inspectBuffer(term.buffer.normal);`);
|
||||
|
||||
assert.throw(() => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm-addon-web-links",
|
||||
"version": "0.5.1",
|
||||
"version": "0.6.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
|
||||
import { ILinkProvider, ILink, Terminal, IViewportRange } from 'xterm';
|
||||
|
||||
interface ILinkProviderOptions {
|
||||
export interface ILinkProviderOptions {
|
||||
hover?(event: MouseEvent, text: string, location: IViewportRange): void;
|
||||
leave?(event: MouseEvent, text: string): void;
|
||||
urlRegex?: RegExp;
|
||||
}
|
||||
|
||||
export class WebLinkProvider implements ILinkProvider {
|
||||
@@ -78,10 +79,17 @@ export class LinkComputer {
|
||||
endY++;
|
||||
}
|
||||
|
||||
let startX = stringIndex + 1;
|
||||
let startY = startLineIndex + 1;
|
||||
while (startX > terminal.cols) {
|
||||
startX -= terminal.cols;
|
||||
startY++;
|
||||
}
|
||||
|
||||
const range = {
|
||||
start: {
|
||||
x: stringIndex + 1,
|
||||
y: startLineIndex + 1
|
||||
x: startX,
|
||||
y: startY
|
||||
},
|
||||
end: {
|
||||
x: endX,
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, ILinkMatcherOptions, ITerminalAddon, IDisposable, IViewportRange } from 'xterm';
|
||||
import { WebLinkProvider } from './WebLinkProvider';
|
||||
import { Terminal, ILinkMatcherOptions, ITerminalAddon, IDisposable } from 'xterm';
|
||||
import { ILinkProviderOptions, WebLinkProvider } from './WebLinkProvider';
|
||||
|
||||
const protocolClause = '(https?:\\/\\/)';
|
||||
const domainCharacterSet = '[\\da-z\\.-]+';
|
||||
@@ -40,12 +40,6 @@ function handleLink(event: MouseEvent, uri: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
interface ILinkProviderOptions {
|
||||
hover?(event: MouseEvent, text: string, location: IViewportRange): void;
|
||||
leave?(event: MouseEvent, text: string): void;
|
||||
urlRegex?: RegExp;
|
||||
}
|
||||
|
||||
export class WebLinksAddon implements ITerminalAddon {
|
||||
private _linkMatcherId: number | undefined;
|
||||
private _terminal: Terminal | undefined;
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../node_modules/@types/node",
|
||||
"../../../out-test/api/TestUtils"
|
||||
"../../../node_modules/@types/node"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
|
||||
@@ -49,5 +49,10 @@ declare module 'xterm-addon-web-links' {
|
||||
* happen even when tooltipCallback hasn't fired for the link yet.
|
||||
*/
|
||||
leave?(event: MouseEvent, text: string): void;
|
||||
|
||||
/**
|
||||
* A callback to use instead of the default one.
|
||||
*/
|
||||
urlRegex?: RegExp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm-addon-webgl",
|
||||
"version": "0.11.4",
|
||||
"version": "0.12.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
|
||||
@@ -6,14 +6,11 @@
|
||||
import { createProgram, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils';
|
||||
import { WebglCharAtlas } from './atlas/WebglCharAtlas';
|
||||
import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types';
|
||||
import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_INDICIES_PER_CELL, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_BG_OFFSET } from './RenderModel';
|
||||
import { fill } from 'common/TypedArrayUtils';
|
||||
import { slice } from './TypedArray';
|
||||
import { NULL_CELL_CODE, WHITESPACE_CELL_CODE, Attributes, FgFlags } from 'common/buffer/Constants';
|
||||
import { NULL_CELL_CODE } from 'common/buffer/Constants';
|
||||
import { Terminal, IBufferLine } from 'xterm';
|
||||
import { IColorSet, IColor } from 'browser/Types';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { IRenderDimensions } from 'browser/renderer/Types';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
|
||||
interface IVertices {
|
||||
attributes: Float32Array;
|
||||
@@ -24,7 +21,6 @@ interface IVertices {
|
||||
* working on the next frame.
|
||||
*/
|
||||
attributesBuffers: Float32Array[];
|
||||
selectionAttributes: Float32Array;
|
||||
count: number;
|
||||
}
|
||||
|
||||
@@ -91,8 +87,7 @@ export class GlyphRenderer {
|
||||
attributesBuffers: [
|
||||
new Float32Array(0),
|
||||
new Float32Array(0)
|
||||
],
|
||||
selectionAttributes: new Float32Array(0)
|
||||
]
|
||||
};
|
||||
|
||||
constructor(
|
||||
@@ -187,6 +182,8 @@ export class GlyphRenderer {
|
||||
if (!this._atlas) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the glyph
|
||||
if (chars && chars.length > 1) {
|
||||
rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg);
|
||||
} else {
|
||||
@@ -214,91 +211,6 @@ export class GlyphRenderer {
|
||||
// a_cellpos only changes on resize
|
||||
}
|
||||
|
||||
public updateSelection(model: IRenderModel): void {
|
||||
const terminal = this._terminal;
|
||||
|
||||
this._vertices.selectionAttributes = slice(this._vertices.attributes, 0);
|
||||
|
||||
const bg = (this._colors.selectionOpaque.rgba >>> 8) | Attributes.CM_RGB;
|
||||
|
||||
if (model.selection.columnSelectMode) {
|
||||
const startCol = model.selection.startCol;
|
||||
const width = model.selection.endCol - startCol;
|
||||
const height = model.selection.viewportCappedEndRow - model.selection.viewportCappedStartRow + 1;
|
||||
for (let y = model.selection.viewportCappedStartRow; y < model.selection.viewportCappedStartRow + height; y++) {
|
||||
this._updateSelectionRange(startCol, startCol + width, y, model, bg);
|
||||
}
|
||||
} else {
|
||||
// Draw first row
|
||||
const startCol = model.selection.viewportStartRow === model.selection.viewportCappedStartRow ? model.selection.startCol : 0;
|
||||
const startRowEndCol = model.selection.viewportCappedStartRow === model.selection.viewportCappedEndRow ? model.selection.endCol : terminal.cols;
|
||||
this._updateSelectionRange(startCol, startRowEndCol, model.selection.viewportCappedStartRow, model, bg);
|
||||
|
||||
// Draw middle rows
|
||||
const middleRowsCount = Math.max(model.selection.viewportCappedEndRow - model.selection.viewportCappedStartRow - 1, 0);
|
||||
for (let y = model.selection.viewportCappedStartRow + 1; y <= model.selection.viewportCappedStartRow + middleRowsCount; y++) {
|
||||
this._updateSelectionRange(0, startRowEndCol, y, model, bg);
|
||||
}
|
||||
|
||||
// Draw final row
|
||||
if (model.selection.viewportCappedStartRow !== model.selection.viewportCappedEndRow) {
|
||||
// Only draw viewportEndRow if it's not the same as viewportStartRow
|
||||
const endCol = model.selection.viewportEndRow === model.selection.viewportCappedEndRow ? model.selection.endCol : terminal.cols;
|
||||
this._updateSelectionRange(0, endCol, model.selection.viewportCappedEndRow, model, bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _updateSelectionRange(startCol: number, endCol: number, y: number, model: IRenderModel, bg: number): void {
|
||||
const terminal = this._terminal;
|
||||
const row = y + terminal.buffer.active.viewportY;
|
||||
let line: IBufferLine | undefined;
|
||||
for (let x = startCol; x < endCol; x++) {
|
||||
const offset = (y * this._terminal.cols + x) * RENDER_MODEL_INDICIES_PER_CELL;
|
||||
const code = model.cells[offset];
|
||||
let fg = model.cells[offset + RENDER_MODEL_FG_OFFSET];
|
||||
if (fg & FgFlags.INVERSE) {
|
||||
const workCell = new AttributeData();
|
||||
workCell.fg = fg;
|
||||
workCell.bg = model.cells[offset + RENDER_MODEL_BG_OFFSET];
|
||||
// Get attributes from fg (excluding inverse) and resolve inverse by pullibng rgb colors
|
||||
// from bg. This is needed since the inverse fg color should be based on the original bg
|
||||
// color, not on the selection color
|
||||
fg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK | FgFlags.INVERSE);
|
||||
switch (workCell.getBgColorMode()) {
|
||||
case Attributes.CM_P16:
|
||||
case Attributes.CM_P256:
|
||||
const c = this._getColorFromAnsiIndex(workCell.getBgColor()).rgba;
|
||||
fg |= (c >> 8) & Attributes.RED_MASK | (c >> 8) & Attributes.GREEN_MASK | (c >> 8) & Attributes.BLUE_MASK;
|
||||
case Attributes.CM_RGB:
|
||||
const arr = AttributeData.toColorRGB(workCell.getBgColor());
|
||||
fg |= arr[0] << Attributes.RED_SHIFT | arr[1] << Attributes.GREEN_SHIFT | arr[2] << Attributes.BLUE_SHIFT;
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
const c2 = this._colors.background.rgba;
|
||||
fg |= (c2 >> 8) & Attributes.RED_MASK | (c2 >> 8) & Attributes.GREEN_MASK | (c2 >> 8) & Attributes.BLUE_MASK;
|
||||
}
|
||||
fg |= Attributes.CM_RGB;
|
||||
}
|
||||
if (code & COMBINED_CHAR_BIT_MASK) {
|
||||
if (!line) {
|
||||
line = terminal.buffer.active.getLine(row);
|
||||
}
|
||||
const chars = line!.getCell(x)!.getChars();
|
||||
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg, chars);
|
||||
} else {
|
||||
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _getColorFromAnsiIndex(idx: number): IColor {
|
||||
if (idx >= this._colors.ansi.length) {
|
||||
throw new Error('No color found for idx ' + idx);
|
||||
}
|
||||
return this._colors.ansi[idx];
|
||||
}
|
||||
|
||||
public clear(force?: boolean): void {
|
||||
const terminal = this._terminal;
|
||||
const newCount = terminal.cols * terminal.rows * INDICES_PER_CELL;
|
||||
@@ -333,7 +245,7 @@ export class GlyphRenderer {
|
||||
public setColors(): void {
|
||||
}
|
||||
|
||||
public render(renderModel: IRenderModel, isSelectionVisible: boolean): void {
|
||||
public render(renderModel: IRenderModel): void {
|
||||
if (!this._atlas) {
|
||||
return;
|
||||
}
|
||||
@@ -357,7 +269,7 @@ export class GlyphRenderer {
|
||||
let bufferLength = 0;
|
||||
for (let y = 0; y < renderModel.lineLengths.length; y++) {
|
||||
const si = y * this._terminal.cols * INDICES_PER_CELL;
|
||||
const sub = (isSelectionVisible ? this._vertices.selectionAttributes : this._vertices.attributes).subarray(si, si + renderModel.lineLengths[y] * INDICES_PER_CELL);
|
||||
const sub = this._vertices.attributes.subarray(si, si + renderModel.lineLengths[y] * INDICES_PER_CELL);
|
||||
activeBuffer.set(sub, bufferLength);
|
||||
bufferLength += sub.length;
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
*/
|
||||
|
||||
import { createProgram, expandFloat32Array, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils';
|
||||
import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types';
|
||||
import { fill } from 'common/TypedArrayUtils';
|
||||
import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext } from './Types';
|
||||
import { Attributes, FgFlags } from 'common/buffer/Constants';
|
||||
import { Terminal } from 'xterm';
|
||||
import { IColorSet, IColor } from 'browser/Types';
|
||||
import { IColor } from 'common/Types';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { IRenderDimensions } from 'browser/renderer/Types';
|
||||
import { RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel';
|
||||
|
||||
@@ -49,7 +49,6 @@ void main() {
|
||||
|
||||
interface IVertices {
|
||||
attributes: Float32Array;
|
||||
selection: Float32Array;
|
||||
count: number;
|
||||
}
|
||||
|
||||
@@ -66,12 +65,10 @@ export class RectangleRenderer {
|
||||
private _attributesBuffer: WebGLBuffer;
|
||||
private _projectionLocation: WebGLUniformLocation;
|
||||
private _bgFloat!: Float32Array;
|
||||
private _selectionFloat!: Float32Array;
|
||||
|
||||
private _vertices: IVertices = {
|
||||
count: 0,
|
||||
attributes: new Float32Array(INITIAL_BUFFER_RECTANGLE_CAPACITY),
|
||||
selection: new Float32Array(3 * INDICES_PER_RECTANGLE)
|
||||
attributes: new Float32Array(INITIAL_BUFFER_RECTANGLE_CAPACITY)
|
||||
};
|
||||
|
||||
constructor(
|
||||
@@ -137,11 +134,6 @@ export class RectangleRenderer {
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, this._vertices.attributes, gl.DYNAMIC_DRAW);
|
||||
gl.drawElementsInstanced(this._gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, this._vertices.count);
|
||||
|
||||
// Bind selection buffer and draw
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, this._vertices.selection, gl.DYNAMIC_DRAW);
|
||||
gl.drawElementsInstanced(this._gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, 3);
|
||||
}
|
||||
|
||||
public onResize(): void {
|
||||
@@ -155,7 +147,6 @@ export class RectangleRenderer {
|
||||
|
||||
private _updateCachedColors(): void {
|
||||
this._bgFloat = this._colorToFloat32Array(this._colors.background);
|
||||
this._selectionFloat = this._colorToFloat32Array(this._colors.selectionOpaque);
|
||||
}
|
||||
|
||||
private _updateViewportRectangle(): void {
|
||||
@@ -171,73 +162,6 @@ export class RectangleRenderer {
|
||||
);
|
||||
}
|
||||
|
||||
public updateSelection(model: ISelectionRenderModel): void {
|
||||
const terminal = this._terminal;
|
||||
|
||||
if (!model.hasSelection) {
|
||||
fill(this._vertices.selection, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (model.columnSelectMode) {
|
||||
const startCol = model.startCol;
|
||||
const width = model.endCol - startCol;
|
||||
const height = model.viewportCappedEndRow - model.viewportCappedStartRow + 1;
|
||||
this._addRectangleFloat(
|
||||
this._vertices.selection,
|
||||
0,
|
||||
startCol * this._dimensions.scaledCellWidth,
|
||||
model.viewportCappedStartRow * this._dimensions.scaledCellHeight,
|
||||
width * this._dimensions.scaledCellWidth,
|
||||
height * this._dimensions.scaledCellHeight,
|
||||
this._selectionFloat
|
||||
);
|
||||
fill(this._vertices.selection, 0, INDICES_PER_RECTANGLE);
|
||||
} else {
|
||||
// Draw first row
|
||||
const startCol = model.viewportStartRow === model.viewportCappedStartRow ? model.startCol : 0;
|
||||
const startRowEndCol = model.viewportCappedStartRow === model.viewportEndRow ? model.endCol : terminal.cols;
|
||||
this._addRectangleFloat(
|
||||
this._vertices.selection,
|
||||
0,
|
||||
startCol * this._dimensions.scaledCellWidth,
|
||||
model.viewportCappedStartRow * this._dimensions.scaledCellHeight,
|
||||
(startRowEndCol - startCol) * this._dimensions.scaledCellWidth,
|
||||
this._dimensions.scaledCellHeight,
|
||||
this._selectionFloat
|
||||
);
|
||||
|
||||
// Draw middle rows
|
||||
const middleRowsCount = Math.max(model.viewportCappedEndRow - model.viewportCappedStartRow - 1, 0);
|
||||
this._addRectangleFloat(
|
||||
this._vertices.selection,
|
||||
INDICES_PER_RECTANGLE,
|
||||
0,
|
||||
(model.viewportCappedStartRow + 1) * this._dimensions.scaledCellHeight,
|
||||
terminal.cols * this._dimensions.scaledCellWidth,
|
||||
middleRowsCount * this._dimensions.scaledCellHeight,
|
||||
this._selectionFloat
|
||||
);
|
||||
|
||||
// Draw final row
|
||||
if (model.viewportCappedStartRow !== model.viewportCappedEndRow) {
|
||||
// Only draw viewportEndRow if it's not the same as viewportStartRow
|
||||
const endCol = model.viewportEndRow === model.viewportCappedEndRow ? model.endCol : terminal.cols;
|
||||
this._addRectangleFloat(
|
||||
this._vertices.selection,
|
||||
INDICES_PER_RECTANGLE * 2,
|
||||
0,
|
||||
model.viewportCappedEndRow * this._dimensions.scaledCellHeight,
|
||||
endCol * this._dimensions.scaledCellWidth,
|
||||
this._dimensions.scaledCellHeight,
|
||||
this._selectionFloat
|
||||
);
|
||||
} else {
|
||||
fill(this._vertices.selection, 0, INDICES_PER_RECTANGLE * 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public updateBackgrounds(model: IRenderModel): void {
|
||||
const terminal = this._terminal;
|
||||
const vertices = this._vertices;
|
||||
|
||||
@@ -9,6 +9,7 @@ import { ICharacterJoinerService, IRenderService } from 'browser/services/Servic
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { EventEmitter } from 'common/EventEmitter';
|
||||
import { isSafari } from 'common/Platform';
|
||||
import { IDecorationService } from 'common/services/Services';
|
||||
|
||||
export class WebglAddon implements ITerminalAddon {
|
||||
private _terminal?: Terminal;
|
||||
@@ -30,8 +31,9 @@ export class WebglAddon implements ITerminalAddon {
|
||||
this._terminal = terminal;
|
||||
const renderService: IRenderService = (terminal as any)._core._renderService;
|
||||
const characterJoinerService: ICharacterJoinerService = (terminal as any)._core._characterJoinerService;
|
||||
const decorationService: IDecorationService = (terminal as any)._core._decorationService;
|
||||
const colors: IColorSet = (terminal as any)._core._colorManager.colors;
|
||||
this._renderer = new WebglRenderer(terminal, colors, characterJoinerService, this._preserveDrawingBuffer);
|
||||
this._renderer = new WebglRenderer(terminal, colors, characterJoinerService, decorationService, this._preserveDrawingBuffer);
|
||||
this._renderer.onContextLoss(() => this._onContextLoss.fire());
|
||||
renderService.setRenderer(this._renderer);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import { RectangleRenderer } from './RectangleRenderer';
|
||||
import { IWebGL2RenderingContext } from './Types';
|
||||
import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
|
||||
import { Attributes, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
|
||||
import { Terminal, IEvent } from 'xterm';
|
||||
import { IRenderLayer } from './renderLayer/Types';
|
||||
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types';
|
||||
@@ -23,6 +23,7 @@ import { addDisposableDomListener } from 'browser/Lifecycle';
|
||||
import { ICharacterJoinerService } from 'browser/services/Services';
|
||||
import { CharData, ICellData } from 'common/Types';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { IDecorationService } from 'common/services/Services';
|
||||
|
||||
export class WebglRenderer extends Disposable implements IRenderer {
|
||||
private _renderLayers: IRenderLayer[];
|
||||
@@ -31,6 +32,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
|
||||
private _model: RenderModel = new RenderModel();
|
||||
private _workCell: CellData = new CellData();
|
||||
private _workColors: { fg: number, bg: number } = { fg: 0, bg: 0 };
|
||||
|
||||
private _canvas: HTMLCanvasElement;
|
||||
private _gl: IWebGL2RenderingContext;
|
||||
@@ -52,6 +54,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
private _terminal: Terminal,
|
||||
private _colors: IColorSet,
|
||||
private readonly _characterJoinerService: ICharacterJoinerService,
|
||||
private readonly _decorationService: IDecorationService,
|
||||
preserveDrawingBuffer?: boolean
|
||||
) {
|
||||
super();
|
||||
@@ -164,11 +167,6 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
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();
|
||||
|
||||
@@ -198,10 +196,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
for (const l of this._renderLayers) {
|
||||
l.onSelectionChanged(this._terminal, start, end, columnSelectMode);
|
||||
}
|
||||
|
||||
this._updateSelectionModel(start, end, columnSelectMode);
|
||||
|
||||
this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 });
|
||||
this._requestRedrawViewport();
|
||||
}
|
||||
|
||||
public onCursorMove(): void {
|
||||
@@ -243,7 +239,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
this._charAtlas?.clearTexture();
|
||||
this._model.clear();
|
||||
this._updateModel(0, this._terminal.rows - 1);
|
||||
this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 });
|
||||
this._requestRedrawViewport();
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
@@ -289,7 +285,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
|
||||
// Render
|
||||
this._rectangleRenderer.render();
|
||||
this._glyphRenderer.render(this._model, this._model.selection.hasSelection);
|
||||
this._glyphRenderer.render(this._model);
|
||||
}
|
||||
|
||||
private _updateModel(start: number, end: number): void {
|
||||
@@ -331,14 +327,17 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
let code = cell.getCode();
|
||||
const i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
|
||||
|
||||
// Load colors/resolve overrides into work colors
|
||||
this._loadColorsForCell(x, row);
|
||||
|
||||
if (code !== NULL_CELL_CODE) {
|
||||
this._model.lineLengths[y] = x + 1;
|
||||
}
|
||||
|
||||
// Nothing has changed, no updates needed
|
||||
if (this._model.cells[i] === code &&
|
||||
this._model.cells[i + RENDER_MODEL_BG_OFFSET] === cell.bg &&
|
||||
this._model.cells[i + RENDER_MODEL_FG_OFFSET] === cell.fg) {
|
||||
this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._workColors.bg &&
|
||||
this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._workColors.fg) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -349,10 +348,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
|
||||
// Cache the results in the model
|
||||
this._model.cells[i] = code;
|
||||
this._model.cells[i + RENDER_MODEL_BG_OFFSET] = cell.bg;
|
||||
this._model.cells[i + RENDER_MODEL_FG_OFFSET] = cell.fg;
|
||||
this._model.cells[i + RENDER_MODEL_BG_OFFSET] = this._workColors.bg;
|
||||
this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._workColors.fg;
|
||||
|
||||
this._glyphRenderer.updateCell(x, y, code, cell.bg, cell.fg, chars);
|
||||
this._glyphRenderer.updateCell(x, y, code, this._workColors.bg, this._workColors.fg, chars);
|
||||
|
||||
if (isJoined) {
|
||||
// Restore work cell
|
||||
@@ -363,17 +362,110 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
const j = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
|
||||
this._glyphRenderer.updateCell(x, y, NULL_CELL_CODE, 0, 0, NULL_CELL_CHAR);
|
||||
this._model.cells[j] = NULL_CELL_CODE;
|
||||
this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._workCell.bg;
|
||||
this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._workCell.fg;
|
||||
this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._workColors.bg;
|
||||
this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._workColors.fg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this._rectangleRenderer.updateBackgrounds(this._model);
|
||||
if (this._model.selection.hasSelection) {
|
||||
// Model could be updated but the selection is unchanged
|
||||
this._glyphRenderer.updateSelection(this._model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads colors for the cell into the work colors object. This resolves overrides/inverse if
|
||||
* necessary which is why the work cell object is not used.
|
||||
*/
|
||||
private _loadColorsForCell(x: number, y: number): void {
|
||||
this._workColors.bg = this._workCell.bg;
|
||||
this._workColors.fg = this._workCell.fg;
|
||||
|
||||
// Get any foreground/background overrides, this happens on the model to avoid spreading
|
||||
// override logic throughout the different sub-renderers
|
||||
let bgOverride: number | undefined;
|
||||
let fgOverride: number | undefined;
|
||||
|
||||
// Apply decorations on the bottom layer
|
||||
for (const d of this._decorationService.getDecorationsAtCell(x, y, 'bottom')) {
|
||||
if (d.backgroundColorRGB) {
|
||||
bgOverride = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF;
|
||||
}
|
||||
if (d.foregroundColorRGB) {
|
||||
fgOverride = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the selection color if needed
|
||||
if (this._isCellSelected(x, y)) {
|
||||
bgOverride = this._colors.selectionOpaque.rgba >> 8 & 0xFFFFFF;
|
||||
if (this._colors.selectionForeground) {
|
||||
fgOverride = this._colors.selectionForeground.rgba >> 8 & 0xFFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply decorations on the top layer
|
||||
for (const d of this._decorationService.getDecorationsAtCell(x, y, 'top')) {
|
||||
if (d.backgroundColorRGB) {
|
||||
bgOverride = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF;
|
||||
}
|
||||
if (d.foregroundColorRGB) {
|
||||
fgOverride = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert any overrides from rgba to the fg/bg packed format. This resolves the inverse flag
|
||||
// ahead of time in order to use the correct cache key
|
||||
if (bgOverride !== undefined) {
|
||||
// Non-RGB attributes from model + override + force RGB color mode
|
||||
bgOverride = (this._workCell.bg & ~Attributes.RGB_MASK) | bgOverride | Attributes.CM_RGB;
|
||||
}
|
||||
if (fgOverride !== undefined) {
|
||||
// Non-RGB attributes from model + force disable inverse + override + force RGB color mode
|
||||
fgOverride = (this._workCell.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | fgOverride | Attributes.CM_RGB;
|
||||
}
|
||||
|
||||
// Handle case where inverse was specified by only one of bgOverride or fgOverride was set,
|
||||
// resolving the other inverse color and setting the inverse flag if needed.
|
||||
if (this._workColors.fg & FgFlags.INVERSE) {
|
||||
if (bgOverride !== undefined && fgOverride === undefined) {
|
||||
// Resolve bg color type (default color has a different meaning in fg vs bg)
|
||||
if ((this._workColors.bg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) {
|
||||
fgOverride = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | ((this._colors.background.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB;
|
||||
} else {
|
||||
fgOverride = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | this._workColors.bg & (Attributes.RGB_MASK | Attributes.CM_MASK);
|
||||
}
|
||||
}
|
||||
if (bgOverride === undefined && fgOverride !== undefined) {
|
||||
// Resolve bg color type (default color has a different meaning in fg vs bg)
|
||||
if ((this._workColors.fg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) {
|
||||
bgOverride = (this._workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | ((this._colors.foreground.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB;
|
||||
} else {
|
||||
bgOverride = (this._workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | this._workColors.fg & (Attributes.RGB_MASK | Attributes.CM_MASK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use the override if it exists
|
||||
this._workColors.bg = bgOverride ?? this._workColors.bg;
|
||||
this._workColors.fg = fgOverride ?? this._workColors.fg;
|
||||
}
|
||||
|
||||
private _isCellSelected(x: number, y: number): boolean {
|
||||
if (!this._model.selection.hasSelection) {
|
||||
return false;
|
||||
}
|
||||
y -= this._terminal.buffer.active.viewportY;
|
||||
if (this._model.selection.columnSelectMode) {
|
||||
if (this._model.selection.startCol <= this._model.selection.endCol) {
|
||||
return x >= this._model.selection.startCol && y >= this._model.selection.viewportCappedStartRow &&
|
||||
x < this._model.selection.endCol && y <= this._model.selection.viewportCappedEndRow;
|
||||
}
|
||||
return x < this._model.selection.startCol && y >= this._model.selection.viewportCappedStartRow &&
|
||||
x >= this._model.selection.endCol && y <= this._model.selection.viewportCappedEndRow;
|
||||
}
|
||||
return (y > this._model.selection.viewportStartRow && y < this._model.selection.viewportEndRow) ||
|
||||
(this._model.selection.viewportStartRow === this._model.selection.viewportEndRow && y === this._model.selection.viewportStartRow && x >= this._model.selection.startCol && x < this._model.selection.endCol) ||
|
||||
(this._model.selection.viewportStartRow < this._model.selection.viewportEndRow && y === this._model.selection.viewportEndRow && x < this._model.selection.endCol) ||
|
||||
(this._model.selection.viewportStartRow < this._model.selection.viewportEndRow && y === this._model.selection.viewportStartRow && x >= this._model.selection.startCol);
|
||||
}
|
||||
|
||||
private _updateSelectionModel(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {
|
||||
@@ -382,7 +474,6 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -395,7 +486,6 @@ 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;
|
||||
}
|
||||
|
||||
@@ -407,8 +497,6 @@ 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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -440,18 +528,18 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
// will be floored because since lineHeight can never be lower then 1, there
|
||||
// is a guarentee that the scaled line height will always be larger than
|
||||
// scaled char height.
|
||||
this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.getOption('lineHeight'));
|
||||
this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight!);
|
||||
|
||||
// Calculate the y coordinate within a cell that text should draw from in
|
||||
// order to draw in the center of a cell.
|
||||
this.dimensions.scaledCharTop = this._terminal.getOption('lineHeight') === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2);
|
||||
this.dimensions.scaledCharTop = this._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2);
|
||||
|
||||
// Calculate the scaled cell width, taking the letterSpacing into account.
|
||||
this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.getOption('letterSpacing'));
|
||||
this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing!);
|
||||
|
||||
// Calculate the x coordinate with a cell that text should draw from in
|
||||
// order to draw in the center of a cell.
|
||||
this.dimensions.scaledCharLeft = Math.floor(this._terminal.getOption('letterSpacing') / 2);
|
||||
this.dimensions.scaledCharLeft = Math.floor(this._terminal.options.letterSpacing! / 2);
|
||||
|
||||
// Recalculate the canvas dimensions; scaled* define the actual number of
|
||||
// pixel in the canvas
|
||||
@@ -482,6 +570,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
this.dimensions.actualCellHeight = this.dimensions.scaledCellHeight / this._devicePixelRatio;
|
||||
this.dimensions.actualCellWidth = this.dimensions.scaledCellWidth / this._devicePixelRatio;
|
||||
}
|
||||
|
||||
private _requestRedrawViewport(): void {
|
||||
this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 });
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Share impl with core
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
import { ICharAtlasConfig } from './Types';
|
||||
import { Attributes } from 'common/buffer/Constants';
|
||||
import { Terminal, FontWeight } from 'xterm';
|
||||
import { IColorSet, IColor } from 'browser/Types';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { IColor } from 'common/Types';
|
||||
|
||||
const NULL_COLOR: IColor = {
|
||||
css: '',
|
||||
@@ -22,27 +23,28 @@ export function generateConfig(scaledCellWidth: number, scaledCellHeight: number
|
||||
cursorAccent: NULL_COLOR,
|
||||
selectionTransparent: NULL_COLOR,
|
||||
selectionOpaque: NULL_COLOR,
|
||||
selectionForeground: NULL_COLOR,
|
||||
// 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(),
|
||||
contrastCache: colors.contrastCache
|
||||
};
|
||||
return {
|
||||
customGlyphs: terminal.getOption('customGlyphs'),
|
||||
customGlyphs: terminal.options.customGlyphs!,
|
||||
devicePixelRatio: window.devicePixelRatio,
|
||||
letterSpacing: terminal.getOption('letterSpacing'),
|
||||
lineHeight: terminal.getOption('lineHeight'),
|
||||
letterSpacing: terminal.options.letterSpacing!,
|
||||
lineHeight: terminal.options.lineHeight!,
|
||||
scaledCellWidth,
|
||||
scaledCellHeight,
|
||||
scaledCharWidth,
|
||||
scaledCharHeight,
|
||||
fontFamily: terminal.getOption('fontFamily'),
|
||||
fontSize: terminal.getOption('fontSize'),
|
||||
fontWeight: terminal.getOption('fontWeight') as FontWeight,
|
||||
fontWeightBold: terminal.getOption('fontWeightBold') as FontWeight,
|
||||
allowTransparency: terminal.getOption('allowTransparency'),
|
||||
drawBoldTextInBrightColors: terminal.getOption('drawBoldTextInBrightColors'),
|
||||
minimumContrastRatio: terminal.getOption('minimumContrastRatio'),
|
||||
fontFamily: terminal.options.fontFamily!,
|
||||
fontSize: terminal.options.fontSize!,
|
||||
fontWeight: terminal.options.fontWeight as FontWeight,
|
||||
fontWeightBold: terminal.options.fontWeightBold as FontWeight,
|
||||
allowTransparency: terminal.options.allowTransparency!,
|
||||
drawBoldTextInBrightColors: terminal.options.drawBoldTextInBrightColors!,
|
||||
minimumContrastRatio: terminal.options.minimumContrastRatio!,
|
||||
colors: clonedColors
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,11 +8,12 @@ import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/atlas/Constants';
|
||||
import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types';
|
||||
import { DEFAULT_COLOR, Attributes } from 'common/buffer/Constants';
|
||||
import { throwIfFalsy } from '../WebglUtils';
|
||||
import { IColor } from 'browser/Types';
|
||||
import { IColor } from 'common/Types';
|
||||
import { IDisposable } from 'xterm';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { channels, rgba } from 'browser/Color';
|
||||
import { channels, rgba } from 'common/Color';
|
||||
import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs';
|
||||
import { excludeFromContrastRatioDemands, isPowerlineGlyph } from 'browser/renderer/RendererUtils';
|
||||
|
||||
// For debugging purposes, it can be useful to set this to a really tiny value,
|
||||
// to verify that LRU eviction works.
|
||||
@@ -216,8 +217,8 @@ export class WebglCharAtlas implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
private _getForegroundCss(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string {
|
||||
const minimumContrastCss = this._getMinimumContrastCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold);
|
||||
private _getForegroundCss(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean, excludeFromContrastRatioDemands: boolean): string {
|
||||
const minimumContrastCss = this._getMinimumContrastCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold, excludeFromContrastRatioDemands);
|
||||
if (minimumContrastCss) {
|
||||
return minimumContrastCss;
|
||||
}
|
||||
@@ -238,7 +239,7 @@ export class WebglCharAtlas implements IDisposable {
|
||||
const bg = this._config.colors.background.css;
|
||||
if (bg.length === 9) {
|
||||
// Remove bg alpha channel if present
|
||||
return bg.substr(0, 7);
|
||||
return bg.slice(0, 7);
|
||||
}
|
||||
return bg;
|
||||
}
|
||||
@@ -281,8 +282,8 @@ export class WebglCharAtlas implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
private _getMinimumContrastCss(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): string | undefined {
|
||||
if (this._config.minimumContrastRatio === 1) {
|
||||
private _getMinimumContrastCss(bg: number, bgColorMode: number, bgColor: number, fg: number, fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean, excludeFromContrastRatioDemands: boolean): string | undefined {
|
||||
if (this._config.minimumContrastRatio === 1 || excludeFromContrastRatioDemands) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -321,10 +322,15 @@ export class WebglCharAtlas implements IDisposable {
|
||||
// Allow 1 cell width per character, with a minimum of 2 (CJK), plus some padding. This is used
|
||||
// to draw the glyph to the canvas as well as to restrict the bounding box search to ensure
|
||||
// giant ligatures (eg. =====>) don't impact overall performance.
|
||||
const allowedWidth = this._config.scaledCharWidth * Math.max(chars.length, 2) + TMP_CANVAS_GLYPH_PADDING * 2;
|
||||
const allowedWidth = this._config.scaledCellWidth * Math.max(chars.length, 2) + TMP_CANVAS_GLYPH_PADDING * 2;
|
||||
if (this._tmpCanvas.width < allowedWidth) {
|
||||
this._tmpCanvas.width = allowedWidth;
|
||||
}
|
||||
// Include line height when drawing glyphs
|
||||
const allowedHeight = this._config.scaledCellHeight + TMP_CANVAS_GLYPH_PADDING * 2;
|
||||
if (this._tmpCanvas.height < allowedHeight) {
|
||||
this._tmpCanvas.height = allowedHeight;
|
||||
}
|
||||
this._tmpCtx.save();
|
||||
|
||||
this._workAttributeData.fg = fg;
|
||||
@@ -370,26 +376,16 @@ export class WebglCharAtlas implements IDisposable {
|
||||
`${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`;
|
||||
this._tmpCtx.textBaseline = TEXT_BASELINE;
|
||||
|
||||
this._tmpCtx.fillStyle = this._getForegroundCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold);
|
||||
const powerLineGlyph = chars.length === 1 && isPowerlineGlyph(chars.charCodeAt(0));
|
||||
this._tmpCtx.fillStyle = this._getForegroundCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold, excludeFromContrastRatioDemands(chars.charCodeAt(0)));
|
||||
|
||||
// Apply alpha to dim the character
|
||||
if (dim) {
|
||||
this._tmpCtx.globalAlpha = DIM_OPACITY;
|
||||
}
|
||||
|
||||
// Check if the char is a powerline glyph, these will be restricted to a single cell glyph, no
|
||||
// padding on either side that are allowed for other glyphs since they are designed to be pixel
|
||||
// perfect but may render with "bad" anti-aliasing
|
||||
let isPowerlineGlyph = false;
|
||||
if (chars.length === 1) {
|
||||
const code = chars.charCodeAt(0);
|
||||
if (code >= 0xE0A0 && code <= 0xE0D6) {
|
||||
isPowerlineGlyph = true;
|
||||
}
|
||||
}
|
||||
|
||||
// For powerline glyphs left/top padding is excluded (https://github.com/microsoft/vscode/issues/120129)
|
||||
const padding = isPowerlineGlyph ? 0 : TMP_CANVAS_GLYPH_PADDING;
|
||||
const padding = powerLineGlyph ? 0 : TMP_CANVAS_GLYPH_PADDING;
|
||||
|
||||
// Draw custom characters if applicable
|
||||
let drawSuccess = false;
|
||||
@@ -459,7 +455,7 @@ export class WebglCharAtlas implements IDisposable {
|
||||
return NULL_RASTERIZED_GLYPH;
|
||||
}
|
||||
|
||||
const rasterizedGlyph = this._findGlyphBoundingBox(imageData, this._workBoundingBox, allowedWidth, isPowerlineGlyph, drawSuccess);
|
||||
const rasterizedGlyph = this._findGlyphBoundingBox(imageData, this._workBoundingBox, allowedWidth, powerLineGlyph, drawSuccess);
|
||||
const clippedImageData = this._clipImageData(imageData, this._workBoundingBox);
|
||||
|
||||
// Check if there is enough room in the current row and go to next if needed
|
||||
@@ -494,7 +490,7 @@ export class WebglCharAtlas implements IDisposable {
|
||||
*/
|
||||
private _findGlyphBoundingBox(imageData: ImageData, boundingBox: IBoundingBox, allowedWidth: number, restrictedGlyph: boolean, customGlyph: boolean): IRasterizedGlyph {
|
||||
boundingBox.top = 0;
|
||||
const height = restrictedGlyph ? this._config.scaledCharHeight : this._tmpCanvas.height;
|
||||
const height = restrictedGlyph ? this._config.scaledCellHeight : this._tmpCanvas.height;
|
||||
const width = restrictedGlyph ? this._config.scaledCharWidth : allowedWidth;
|
||||
let found = false;
|
||||
for (let y = 0; y < height; y++) {
|
||||
|
||||
@@ -254,10 +254,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
* @param isBold If we should use the bold fontWeight.
|
||||
*/
|
||||
protected _getFont(terminal: Terminal, isBold: boolean, isItalic: boolean): string {
|
||||
const fontWeight = isBold ? terminal.getOption('fontWeightBold') : terminal.getOption('fontWeight');
|
||||
const fontWeight = isBold ? terminal.options.fontWeightBold : terminal.options.fontWeight;
|
||||
const fontStyle = isItalic ? 'italic' : '';
|
||||
|
||||
return `${fontStyle} ${fontWeight} ${terminal.getOption('fontSize') * window.devicePixelRatio}px ${terminal.getOption('fontFamily')}`;
|
||||
return `${fontStyle} ${fontWeight} ${terminal.options.fontSize! * window.devicePixelRatio}px ${terminal.options.fontFamily}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
}
|
||||
|
||||
public onOptionsChanged(terminal: Terminal): void {
|
||||
if (terminal.getOption('cursorBlink')) {
|
||||
if (terminal.options.cursorBlink) {
|
||||
if (!this._cursorBlinkStateManager) {
|
||||
this._cursorBlinkStateManager = new CursorBlinkStateManager(terminal, () => {
|
||||
this._render(terminal, true);
|
||||
@@ -140,7 +140,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
this._clearCursor();
|
||||
this._ctx.save();
|
||||
this._ctx.fillStyle = this._colors.cursor.css;
|
||||
const cursorStyle = terminal.getOption('cursorStyle');
|
||||
const cursorStyle = terminal.options.cursorStyle;
|
||||
if (cursorStyle && cursorStyle !== 'block') {
|
||||
this._cursorRenderers[cursorStyle](terminal, cursorX, viewportRelativeCursorY, this._cell);
|
||||
} else {
|
||||
@@ -150,7 +150,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
this._state.x = cursorX;
|
||||
this._state.y = viewportRelativeCursorY;
|
||||
this._state.isFocused = false;
|
||||
this._state.style = cursorStyle;
|
||||
this._state.style = cursorStyle!;
|
||||
this._state.width = this._cell.getWidth();
|
||||
return;
|
||||
}
|
||||
@@ -166,7 +166,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
if (this._state.x === cursorX &&
|
||||
this._state.y === viewportRelativeCursorY &&
|
||||
this._state.isFocused === isTerminalFocused(terminal) &&
|
||||
this._state.style === terminal.getOption('cursorStyle') &&
|
||||
this._state.style === terminal.options.cursorStyle &&
|
||||
this._state.width === this._cell.getWidth()) {
|
||||
return;
|
||||
}
|
||||
@@ -174,13 +174,13 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
}
|
||||
|
||||
this._ctx.save();
|
||||
this._cursorRenderers[terminal.getOption('cursorStyle') || 'block'](terminal, cursorX, viewportRelativeCursorY, this._cell);
|
||||
this._cursorRenderers[terminal.options.cursorStyle || 'block'](terminal, cursorX, viewportRelativeCursorY, this._cell);
|
||||
this._ctx.restore();
|
||||
|
||||
this._state.x = cursorX;
|
||||
this._state.y = viewportRelativeCursorY;
|
||||
this._state.isFocused = false;
|
||||
this._state.style = terminal.getOption('cursorStyle');
|
||||
this._state.style = terminal.options.cursorStyle!;
|
||||
this._state.width = this._cell.getWidth();
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
private _renderBarCursor(terminal: Terminal, x: number, y: number, cell: ICellData): void {
|
||||
this._ctx.save();
|
||||
this._ctx.fillStyle = this._colors.cursor.css;
|
||||
this._fillLeftLineAtCell(x, y, terminal.getOption('cursorWidth'));
|
||||
this._fillLeftLineAtCell(x, y, terminal.options.cursorWidth!);
|
||||
this._ctx.restore();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
]
|
||||
},
|
||||
"strict": true,
|
||||
"downlevelIteration": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha"
|
||||
]
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { assert } from 'chai';
|
||||
import { Browser, Page } from 'playwright';
|
||||
import { ITheme } from 'xterm';
|
||||
import { getBrowserType, launchBrowser, openTerminal, pollFor, writeSync } from '../../../out-test/api/TestUtils';
|
||||
import { getBrowserType, launchBrowser, openTerminal, pollFor, timeout, writeSync } from '../../../out-test/api/TestUtils';
|
||||
import { ITerminalOptions } from '../../../src/common/Types';
|
||||
|
||||
const APP = 'http://127.0.0.1:3001/test';
|
||||
@@ -49,7 +49,7 @@ describe('WebGL Renderer Integration Tests', async () => {
|
||||
cyan: '#131415',
|
||||
white: '#161718'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
|
||||
await writeSync(page, `\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
@@ -73,8 +73,8 @@ describe('WebGL Renderer Integration Tests', async () => {
|
||||
brightWhite: '#161718'
|
||||
};
|
||||
await page.evaluate(`
|
||||
window.term.setOption('theme', ${JSON.stringify(theme)});
|
||||
window.term.setOption('drawBoldTextInBrightColors', true);
|
||||
window.term.options.theme = ${JSON.stringify(theme)};
|
||||
window.term.options.drawBoldTextInBrightColors = true;
|
||||
`);
|
||||
await writeSync(page, `\\x1b[1;30m█\\x1b[1;31m█\\x1b[1;32m█\\x1b[1;33m█\\x1b[1;34m█\\x1b[1;35m█\\x1b[1;36m█\\x1b[1;37m█`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
@@ -98,7 +98,7 @@ describe('WebGL Renderer Integration Tests', async () => {
|
||||
cyan: '#131415',
|
||||
white: '#161718'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
|
||||
await writeSync(page, `\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
@@ -121,7 +121,7 @@ describe('WebGL Renderer Integration Tests', async () => {
|
||||
cyan: '#131415',
|
||||
white: '#161718'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
|
||||
await writeSync(page, `\\x1b[7;30m \\x1b[7;31m \\x1b[7;32m \\x1b[7;33m \\x1b[7;34m \\x1b[7;35m \\x1b[7;36m \\x1b[7;37m `);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
@@ -144,7 +144,7 @@ describe('WebGL Renderer Integration Tests', async () => {
|
||||
cyan: '#131415',
|
||||
white: '#161718'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
|
||||
await writeSync(page, `\\x1b[7;40m█\\x1b[7;41m█\\x1b[7;42m█\\x1b[7;43m█\\x1b[7;44m█\\x1b[7;45m█\\x1b[7;46m█\\x1b[7;47m█`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
@@ -167,7 +167,7 @@ describe('WebGL Renderer Integration Tests', async () => {
|
||||
cyan: '#131415',
|
||||
white: '#161718'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
|
||||
await writeSync(page, `\\x1b[8;30m \\x1b[8;31m \\x1b[8;32m \\x1b[8;33m \\x1b[8;34m \\x1b[8;35m \\x1b[8;36m \\x1b[8;37m `);
|
||||
await pollFor(page, () => getCellColor(1, 1), [0, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [0, 0, 0, 255]);
|
||||
@@ -190,7 +190,7 @@ describe('WebGL Renderer Integration Tests', async () => {
|
||||
cyan: '#131415',
|
||||
white: '#161718'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
|
||||
await writeSync(page, `\\x1b[8;40m█\\x1b[8;41m█\\x1b[8;42m█\\x1b[8;43m█\\x1b[8;44m█\\x1b[8;45m█\\x1b[8;46m█\\x1b[8;47m█`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
@@ -213,7 +213,7 @@ describe('WebGL Renderer Integration Tests', async () => {
|
||||
brightCyan: '#131415',
|
||||
brightWhite: '#161718'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
|
||||
await writeSync(page, `\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
@@ -236,7 +236,7 @@ describe('WebGL Renderer Integration Tests', async () => {
|
||||
brightCyan: '#131415',
|
||||
brightWhite: '#161718'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
|
||||
await writeSync(page, `\\x1b[100m \\x1b[101m \\x1b[102m \\x1b[103m \\x1b[104m \\x1b[105m \\x1b[106m \\x1b[107m `);
|
||||
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
|
||||
@@ -260,9 +260,9 @@ describe('WebGL Renderer Integration Tests', async () => {
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const cssColor = COLORS_16_TO_255[y * 16 + x];
|
||||
const r = parseInt(cssColor.substr(1, 2), 16);
|
||||
const g = parseInt(cssColor.substr(3, 2), 16);
|
||||
const b = parseInt(cssColor.substr(5, 2), 16);
|
||||
const r = parseInt(cssColor.slice(1, 3), 16);
|
||||
const g = parseInt(cssColor.slice(3, 5), 16);
|
||||
const b = parseInt(cssColor.slice(5, 7), 16);
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]);
|
||||
}
|
||||
}
|
||||
@@ -280,9 +280,9 @@ describe('WebGL Renderer Integration Tests', async () => {
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const cssColor = COLORS_16_TO_255[y * 16 + x];
|
||||
const r = parseInt(cssColor.substr(1, 2), 16);
|
||||
const g = parseInt(cssColor.substr(3, 2), 16);
|
||||
const b = parseInt(cssColor.substr(5, 2), 16);
|
||||
const r = parseInt(cssColor.slice(1, 3), 16);
|
||||
const g = parseInt(cssColor.slice(3, 5), 16);
|
||||
const b = parseInt(cssColor.slice(5, 7), 16);
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]);
|
||||
}
|
||||
}
|
||||
@@ -300,9 +300,9 @@ describe('WebGL Renderer Integration Tests', async () => {
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const cssColor = COLORS_16_TO_255[y * 16 + x];
|
||||
const r = parseInt(cssColor.substr(1, 2), 16);
|
||||
const g = parseInt(cssColor.substr(3, 2), 16);
|
||||
const b = parseInt(cssColor.substr(5, 2), 16);
|
||||
const r = parseInt(cssColor.slice(1, 3), 16);
|
||||
const g = parseInt(cssColor.slice(3, 5), 16);
|
||||
const b = parseInt(cssColor.slice(5, 7), 16);
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]);
|
||||
}
|
||||
}
|
||||
@@ -320,9 +320,9 @@ describe('WebGL Renderer Integration Tests', async () => {
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const cssColor = COLORS_16_TO_255[y * 16 + x];
|
||||
const r = parseInt(cssColor.substr(1, 2), 16);
|
||||
const g = parseInt(cssColor.substr(3, 2), 16);
|
||||
const b = parseInt(cssColor.substr(5, 2), 16);
|
||||
const r = parseInt(cssColor.slice(1, 3), 16);
|
||||
const g = parseInt(cssColor.slice(3, 5), 16);
|
||||
const b = parseInt(cssColor.slice(5, 7), 16);
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]);
|
||||
}
|
||||
}
|
||||
@@ -356,9 +356,9 @@ describe('WebGL Renderer Integration Tests', async () => {
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const cssColor = COLORS_16_TO_255[y * 16 + x];
|
||||
const r = parseInt(cssColor.substr(1, 2), 16);
|
||||
const g = parseInt(cssColor.substr(3, 2), 16);
|
||||
const b = parseInt(cssColor.substr(5, 2), 16);
|
||||
const r = parseInt(cssColor.slice(1, 3), 16);
|
||||
const g = parseInt(cssColor.slice(3, 5), 16);
|
||||
const b = parseInt(cssColor.slice(5, 7), 16);
|
||||
await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]);
|
||||
}
|
||||
}
|
||||
@@ -715,12 +715,13 @@ describe('WebGL Renderer Integration Tests', async () => {
|
||||
brightWhite: '#eeeeec'
|
||||
};
|
||||
await page.evaluate(`
|
||||
window.term.setOption('theme', ${JSON.stringify(theme)});
|
||||
window.term.setOption('minimumContrastRatio', 1);
|
||||
window.term.options.theme = ${JSON.stringify(theme)};
|
||||
window.term.options.minimumContrastRatio = 1;
|
||||
`);
|
||||
// Block characters ignore block elements so a different char is used here
|
||||
await writeSync(page,
|
||||
`\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█\\r\\n` +
|
||||
`\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`
|
||||
`\\x1b[30m■\\x1b[31m■\\x1b[32m■\\x1b[33m■\\x1b[34m■\\x1b[35m■\\x1b[36m■\\x1b[37m■\\r\\n` +
|
||||
`\\x1b[90m■\\x1b[91m■\\x1b[92m■\\x1b[93m■\\x1b[94m■\\x1b[95m■\\x1b[96m■\\x1b[97m■`
|
||||
);
|
||||
// Validate before minimumContrastRatio is applied
|
||||
await pollFor(page, () => getCellColor(1, 1), [0x2e, 0x34, 0x36, 255]);
|
||||
@@ -742,21 +743,21 @@ describe('WebGL Renderer Integration Tests', async () => {
|
||||
// Setting and check for minimum contrast values, note that these are note
|
||||
// exact to the contrast ratio, if the increase luminance algorithm
|
||||
// changes then these will probably fail
|
||||
await page.evaluate(`window.term.setOption('minimumContrastRatio', 10);`);
|
||||
await page.evaluate(`window.term.options.minimumContrastRatio = 10;`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [176, 180, 180, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [238, 158, 158, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [197, 223, 171, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [235, 221, 158, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [124, 156, 198, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [183, 165, 187, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [152, 198, 110, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [208, 179, 49, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [161, 183, 215, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [191, 174, 194, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [110, 197, 198, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [211, 215, 207, 255]);
|
||||
await pollFor(page, () => getCellColor(1, 2), [183, 185, 183, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 2), [249, 156, 156, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 2), [138, 226, 52, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 2), [252, 233, 79, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 2), [114, 159, 207, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 2), [190, 152, 185, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 2), [154, 186, 221, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 2), [203, 173, 199, 255]);
|
||||
// Unchanged
|
||||
await pollFor(page, () => getCellColor(7, 2), [0x34, 0xe2, 0xe2, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 2), [0xee, 0xee, 0xec, 255]);
|
||||
@@ -783,12 +784,13 @@ describe('WebGL Renderer Integration Tests', async () => {
|
||||
brightWhite: '#eeeeec'
|
||||
};
|
||||
await page.evaluate(`
|
||||
window.term.setOption('theme', ${JSON.stringify(theme)});
|
||||
window.term.setOption('minimumContrastRatio', 1);
|
||||
window.term.options.theme = ${JSON.stringify(theme)};
|
||||
window.term.options.minimumContrastRatio = 1;
|
||||
`);
|
||||
// Block characters ignore block elements so a different char is used here
|
||||
await writeSync(page,
|
||||
`\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█\\r\\n` +
|
||||
`\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`
|
||||
`\\x1b[30m■\\x1b[31m■\\x1b[32m■\\x1b[33m■\\x1b[34m■\\x1b[35m■\\x1b[36m■\\x1b[37m■\\r\\n` +
|
||||
`\\x1b[90m■\\x1b[91m■\\x1b[92m■\\x1b[93m■\\x1b[94m■\\x1b[95m■\\x1b[96m■\\x1b[97m■`
|
||||
);
|
||||
// Validate before minimumContrastRatio is applied
|
||||
await pollFor(page, () => getCellColor(1, 1), [0x2e, 0x34, 0x36, 255]);
|
||||
@@ -810,21 +812,21 @@ describe('WebGL Renderer Integration Tests', async () => {
|
||||
// Setting and check for minimum contrast values, note that these are note
|
||||
// exact to the contrast ratio, if the increase luminance algorithm
|
||||
// changes then these will probably fail
|
||||
await page.evaluate(`window.term.setOption('minimumContrastRatio', 10);`);
|
||||
await page.evaluate(`window.term.options.minimumContrastRatio = 10;`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [46, 52, 54, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [132, 0, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [78, 154, 6, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [114, 93, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [19, 40, 68, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [60, 40, 64, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 1), [36, 72, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 1), [72, 59, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 1), [32, 64, 106, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 1), [75, 51, 80, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 1), [0, 71, 72, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 1), [64, 64, 63, 255]);
|
||||
await pollFor(page, () => getCellColor(1, 2), [61, 63, 59, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 2), [125, 19, 19, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 2), [89, 146, 32, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 2), [105, 98, 32, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 2), [36, 52, 70, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 2), [64, 45, 63, 255]);
|
||||
await pollFor(page, () => getCellColor(3, 2), [40, 67, 13, 255]);
|
||||
await pollFor(page, () => getCellColor(4, 2), [67, 63, 19, 255]);
|
||||
await pollFor(page, () => getCellColor(5, 2), [45, 65, 87, 255]);
|
||||
await pollFor(page, () => getCellColor(6, 2), [81, 57, 78, 255]);
|
||||
await pollFor(page, () => getCellColor(7, 2), [13, 67, 67, 255]);
|
||||
await pollFor(page, () => getCellColor(8, 2), [64, 64, 64, 255]);
|
||||
});
|
||||
@@ -843,7 +845,7 @@ describe('WebGL Renderer Integration Tests', async () => {
|
||||
background: '#00FF00',
|
||||
selection: '#0000FF'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
|
||||
await writeSync(page, ` █\\x1b[7m█\\x1b[0m`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [0, 255, 0, 255]);
|
||||
await pollFor(page, () => getCellColor(2, 1), [255, 0, 0, 255]);
|
||||
@@ -867,13 +869,121 @@ describe('WebGL Renderer Integration Tests', async () => {
|
||||
const theme: ITheme = {
|
||||
background: '#ff000080'
|
||||
};
|
||||
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
|
||||
await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
|
||||
const data = `\\x1b[7m█\x1b[0m`;
|
||||
await writeSync(page, data);
|
||||
// Inverse background should be opaque
|
||||
await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('selectionForeground', () => {
|
||||
if (areTestsEnabled) {
|
||||
before(async () => setupBrowser({ rendererType: 'dom' }));
|
||||
after(async () => browser.close());
|
||||
beforeEach(async () => page.evaluate(`window.term.reset()`));
|
||||
}
|
||||
|
||||
itWebgl('transparent background inverse', async () => {
|
||||
const theme: ITheme = {
|
||||
selectionForeground: '#ff0000'
|
||||
};
|
||||
await page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
|
||||
const data = `\\x1b[7m█\x1b[0m`;
|
||||
await writeSync(page, data);
|
||||
await page.evaluate(`window.term.selectAll()`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('decoration color overrides', async () => {
|
||||
if (areTestsEnabled) {
|
||||
before(async () => setupBrowser({ rendererType: 'dom' }));
|
||||
after(async () => browser.close());
|
||||
beforeEach(async () => page.evaluate(`window.term.reset()`));
|
||||
}
|
||||
|
||||
itWebgl('foregroundColor', async () => {
|
||||
await page.evaluate(`
|
||||
const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);
|
||||
window.term.registerDecoration({
|
||||
marker,
|
||||
foregroundColor: '#ff0000',
|
||||
backgroundColor: '#0000ff'
|
||||
});
|
||||
`);
|
||||
const data = `█`;
|
||||
await writeSync(page, data);
|
||||
await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]);
|
||||
});
|
||||
itWebgl('foregroundColor should ignore inverse', async () => {
|
||||
await page.evaluate(`
|
||||
const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);
|
||||
window.term.registerDecoration({
|
||||
marker,
|
||||
foregroundColor: '#ff0000',
|
||||
backgroundColor: '#0000ff'
|
||||
});
|
||||
`);
|
||||
const data = `\\x1b[7m█\\x1b[0m`;
|
||||
await writeSync(page, data);
|
||||
await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]);
|
||||
});
|
||||
itWebgl('foregroundColor should ignore inverse (only fg on decoration)', async () => {
|
||||
await page.evaluate(`
|
||||
const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);
|
||||
window.term.registerDecoration({
|
||||
marker,
|
||||
width: 2,
|
||||
foregroundColor: '#ff0000'
|
||||
});
|
||||
`);
|
||||
const data = `\\x1b[7m█ \\x1b[0m`;
|
||||
await writeSync(page, data);
|
||||
await pollFor(page, () => getCellColor(1, 1), [255, 0, 0, 255]); // inverse foreground of '█' should be decoration fg override
|
||||
await pollFor(page, () => getCellColor(2, 1), [255, 255, 255, 255]); // inverse background of ' ' should be default foreground
|
||||
});
|
||||
itWebgl('backgroundColor', async () => {
|
||||
await page.evaluate(`
|
||||
const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);
|
||||
window.term.registerDecoration({
|
||||
marker,
|
||||
foregroundColor: '#ff0000',
|
||||
backgroundColor: '#0000ff'
|
||||
});
|
||||
`);
|
||||
const data = ` `;
|
||||
await writeSync(page, data);
|
||||
await pollFor(page, () => getCellColor(1, 1), [0, 0, 255, 255]);
|
||||
});
|
||||
itWebgl('backgroundColor should ignore inverse', async () => {
|
||||
await page.evaluate(`
|
||||
const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);
|
||||
window.term.registerDecoration({
|
||||
marker,
|
||||
foregroundColor: '#ff0000',
|
||||
backgroundColor: '#0000ff'
|
||||
});
|
||||
`);
|
||||
const data = `\\x1b[7m \\x1b[0m`;
|
||||
await writeSync(page, data);
|
||||
await pollFor(page, () => getCellColor(1, 1), [0, 0, 255, 255]);
|
||||
});
|
||||
itWebgl('backgroundColor should ignore inverse (only bg on decoration)', async () => {
|
||||
const data = `\\x1b[7m█ \\x1b[0m`;
|
||||
await writeSync(page, data);
|
||||
await page.evaluate(`
|
||||
const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);
|
||||
window.term.registerDecoration({
|
||||
marker,
|
||||
width: 2,
|
||||
backgroundColor: '#0000ff'
|
||||
});
|
||||
`);
|
||||
await pollFor(page, () => getCellColor(1, 1), [0, 0, 0, 255]); // inverse foreground of '█' should be default
|
||||
await pollFor(page, () => getCellColor(2, 1), [0, 0, 255, 255]); // inverse background of ' ' should be decoration bg override
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function getCellColor(col: number, row: number): Promise<number[]> {
|
||||
|
||||
+3
-3
@@ -104,11 +104,11 @@ function getNextBetaVersion(packageJson) {
|
||||
return `${nextStableVersion}-${tag}.1`;
|
||||
}
|
||||
const latestPublishedVersion = publishedVersions.sort((a, b) => {
|
||||
const aVersion = parseInt(a.substr(a.search(/\d+$/)));
|
||||
const bVersion = parseInt(b.substr(b.search(/\d+$/)));
|
||||
const aVersion = parseInt(a.slice(a.search(/\d+$/)));
|
||||
const bVersion = parseInt(b.slice(b.search(/\d+$/)));
|
||||
return aVersion > bVersion ? -1 : 1;
|
||||
})[0];
|
||||
const latestTagVersion = parseInt(latestPublishedVersion.substr(latestPublishedVersion.search(/\d+$/)), 10);
|
||||
const latestTagVersion = parseInt(latestPublishedVersion.slice(latestPublishedVersion.search(/\d+$/)), 10);
|
||||
return `${nextStableVersion}-${tag}.${latestTagVersion + 1}`;
|
||||
}
|
||||
|
||||
|
||||
+14
-4
@@ -36,6 +36,7 @@
|
||||
*/
|
||||
|
||||
.xterm {
|
||||
cursor: text;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
-ms-user-select: none;
|
||||
@@ -124,10 +125,6 @@
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.xterm {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.xterm.enable-mouse-events {
|
||||
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
|
||||
cursor: default;
|
||||
@@ -178,3 +175,16 @@
|
||||
z-index: 6;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.xterm-decoration-overview-ruler {
|
||||
z-index: 7;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.xterm-decoration-top {
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
+107
-7
@@ -92,8 +92,10 @@ const addons: { [T in AddonType]: IDemoAddon<T>} = {
|
||||
|
||||
const terminalContainer = document.getElementById('terminal-container');
|
||||
const actionElements = {
|
||||
find: <HTMLInputElement>document.querySelector('#find'),
|
||||
findNext: <HTMLInputElement>document.querySelector('#find-next'),
|
||||
findPrevious: <HTMLInputElement>document.querySelector('#find-previous')
|
||||
findPrevious: <HTMLInputElement>document.querySelector('#find-previous'),
|
||||
findResults: document.querySelector('#find-results')
|
||||
};
|
||||
const paddingElement = <HTMLInputElement>document.getElementById('padding');
|
||||
|
||||
@@ -107,7 +109,15 @@ function getSearchOptions(e: KeyboardEvent): ISearchOptions {
|
||||
regex: (document.getElementById('regex') as HTMLInputElement).checked,
|
||||
wholeWord: (document.getElementById('whole-word') as HTMLInputElement).checked,
|
||||
caseSensitive: (document.getElementById('case-sensitive') as HTMLInputElement).checked,
|
||||
incremental: e.key !== `Enter`
|
||||
incremental: e.key !== `Enter`,
|
||||
decorations: (document.getElementById('highlight-all-matches') as HTMLInputElement).checked ? {
|
||||
matchBackground: '#232422',
|
||||
matchBorder: '#555753',
|
||||
matchOverviewRuler: '#555753',
|
||||
activeMatchBackground: '#ef2929',
|
||||
activeMatchBorder: '#ffffff',
|
||||
activeMatchColorOverviewRuler: '#ef2929'
|
||||
} : undefined
|
||||
};
|
||||
}
|
||||
|
||||
@@ -150,7 +160,9 @@ if (document.location.pathname === '/test') {
|
||||
document.getElementById('htmlserialize').addEventListener('click', htmlSerializeButtonHandler);
|
||||
document.getElementById('custom-glyph').addEventListener('click', writeCustomGlyphHandler);
|
||||
document.getElementById('load-test').addEventListener('click', loadTest);
|
||||
document.getElementById('powerline-symbol-test').addEventListener('click', powerlineSymbolTest);
|
||||
document.getElementById('add-decoration').addEventListener('click', addDecoration);
|
||||
document.getElementById('add-overview-ruler').addEventListener('click', addOverviewRuler);
|
||||
}
|
||||
|
||||
function createTerminal(): void {
|
||||
@@ -202,10 +214,15 @@ function createTerminal(): void {
|
||||
addDomListener(actionElements.findNext, 'keyup', (e) => {
|
||||
addons.search.instance.findNext(actionElements.findNext.value, getSearchOptions(e));
|
||||
});
|
||||
|
||||
addDomListener(actionElements.findPrevious, 'keyup', (e) => {
|
||||
addons.search.instance.findPrevious(actionElements.findPrevious.value, getSearchOptions(e));
|
||||
});
|
||||
addDomListener(actionElements.findNext, 'blur', (e) => {
|
||||
addons.search.instance.clearActiveDecoration();
|
||||
});
|
||||
addDomListener(actionElements.findPrevious, 'blur', (e) => {
|
||||
addons.search.instance.clearActiveDecoration();
|
||||
});
|
||||
|
||||
// fit is called within a setTimeout, cols and rows need this.
|
||||
setTimeout(() => {
|
||||
@@ -382,9 +399,12 @@ function initAddons(term: TerminalType): void {
|
||||
if (!addon.canChange) {
|
||||
checkbox.disabled = true;
|
||||
}
|
||||
if(name === 'unicode11' && checkbox.checked) {
|
||||
if (name === 'unicode11' && checkbox.checked) {
|
||||
term.unicode.activeVersion = '11';
|
||||
}
|
||||
if (name === 'search' && checkbox.checked) {
|
||||
addon.instance.onDidChangeResults(e => updateFindResults(e));
|
||||
}
|
||||
addDomListener(checkbox, 'change', () => {
|
||||
if (checkbox.checked) {
|
||||
addon.instance = new addon.ctor();
|
||||
@@ -395,6 +415,8 @@ function initAddons(term: TerminalType): void {
|
||||
}, 0);
|
||||
} else if (name === 'unicode11') {
|
||||
term.unicode.activeVersion = '11';
|
||||
} else if (name === 'search') {
|
||||
addon.instance.onDidChangeResults(e => updateFindResults(e));
|
||||
}
|
||||
} else {
|
||||
if (name === 'webgl') {
|
||||
@@ -423,6 +445,16 @@ function initAddons(term: TerminalType): void {
|
||||
container.appendChild(fragment);
|
||||
}
|
||||
|
||||
function updateFindResults(e: { resultIndex: number, resultCount: number } | undefined) {
|
||||
let content: string;
|
||||
if (e === undefined) {
|
||||
content = 'undefined';
|
||||
} else {
|
||||
content = `index: ${e.resultIndex}, count: ${e.resultCount}`;
|
||||
}
|
||||
actionElements.findResults.textContent = content;
|
||||
}
|
||||
|
||||
function addDomListener(element: HTMLElement, type: string, handler: (...args: any[]) => any): void {
|
||||
element.addEventListener(type, handler);
|
||||
term._core.register({ dispose: () => element.removeEventListener(type, handler) });
|
||||
@@ -543,10 +575,78 @@ function loadTest() {
|
||||
});
|
||||
}
|
||||
|
||||
function powerlineSymbolTest() {
|
||||
function s(char: string): string {
|
||||
return `${char} \x1b[7m${char}\x1b[0m `;
|
||||
}
|
||||
term.write('\n\n\r');
|
||||
term.writeln('Standard powerline symbols:');
|
||||
term.writeln(' 0 1 2 3 4 5 6 7 8 9 A B C D E F');
|
||||
term.writeln(`0xA_ ${s('\ue0a0')}${s('\ue0a1')}${s('\ue0a2')}`);
|
||||
term.writeln(`0xB_ ${s('\ue0b0')}${s('\ue0b1')}${s('\ue0b2')}${s('\ue0b3')}`);
|
||||
term.writeln('');
|
||||
term.writeln(
|
||||
`\x1b[7m` +
|
||||
` inverse \ue0b1 \x1b[0;40m\ue0b0` +
|
||||
` 0 \ue0b1 \x1b[30;41m\ue0b0\x1b[39m` +
|
||||
` 1 \ue0b1 \x1b[31;42m\ue0b0\x1b[39m` +
|
||||
` 2 \ue0b1 \x1b[32;43m\ue0b0\x1b[39m` +
|
||||
` 3 \ue0b1 \x1b[33;44m\ue0b0\x1b[39m` +
|
||||
` 4 \ue0b1 \x1b[34;45m\ue0b0\x1b[39m` +
|
||||
` 5 \ue0b1 \x1b[35;46m\ue0b0\x1b[39m` +
|
||||
` 6 \ue0b1 \x1b[36;47m\ue0b0\x1b[39m` +
|
||||
` 7 \ue0b1 \x1b[37;49m\ue0b0\x1b[0m`
|
||||
);
|
||||
term.writeln('');
|
||||
term.writeln(
|
||||
`\x1b[7m` +
|
||||
` inverse \ue0b3 \x1b[0;7;40m\ue0b2\x1b[27m` +
|
||||
` 0 \ue0b3 \x1b[7;30;41m\ue0b2\x1b[27;39m` +
|
||||
` 1 \ue0b3 \x1b[7;31;42m\ue0b2\x1b[27;39m` +
|
||||
` 2 \ue0b3 \x1b[7;32;43m\ue0b2\x1b[27;39m` +
|
||||
` 3 \ue0b3 \x1b[7;33;44m\ue0b2\x1b[27;39m` +
|
||||
` 4 \ue0b3 \x1b[7;34;45m\ue0b2\x1b[27;39m` +
|
||||
` 5 \ue0b3 \x1b[7;35;46m\ue0b2\x1b[27;39m` +
|
||||
` 6 \ue0b3 \x1b[7;36;47m\ue0b2\x1b[27;39m` +
|
||||
` 7 \ue0b3 \x1b[7;37;49m\ue0b2\x1b[0m`
|
||||
);
|
||||
term.writeln('');
|
||||
term.writeln('Powerline extra symbols:');
|
||||
term.writeln(' 0 1 2 3 4 5 6 7 8 9 A B C D E F');
|
||||
term.writeln(`0xA_ ${s('\ue0a3')}`);
|
||||
term.writeln(`0xB_ ${s('\ue0b4')}${s('\ue0b5')}${s('\ue0b6')}${s('\ue0b7')}${s('\ue0b8')}${s('\ue0b9')}${s('\ue0ba')}${s('\ue0bb')}${s('\ue0bc')}${s('\ue0bd')}${s('\ue0be')}${s('\ue0bf')}`);
|
||||
term.writeln(`0xC_ ${s('\ue0c0')}${s('\ue0c1')}${s('\ue0c2')}${s('\ue0c3')}${s('\ue0c4')}${s('\ue0c5')}${s('\ue0c6')}${s('\ue0c7')}${s('\ue0c8')}${s('\ue0c9')}${s('\ue0ca')}${s('\ue0cb')}${s('\ue0cc')}${s('\ue0cd')}${s('\ue0be')}${s('\ue0bf')}`);
|
||||
term.writeln(`0xD_ ${s('\ue0d0')}${s('\ue0d1')}${s('\ue0d2')} ${s('\ue0d4')}`);
|
||||
term.writeln('');
|
||||
term.writeln('Sample of nerd fonts icons:');
|
||||
term.writeln(' nf-linux-apple (\\uF302) \uf302');
|
||||
term.writeln('nf-mdi-github_face (\\uFbd9) \ufbd9');
|
||||
}
|
||||
|
||||
function addDecoration() {
|
||||
term.options['overviewRulerWidth'] = 15;
|
||||
const marker = term.addMarker(1);
|
||||
const decoration = term.registerDecoration({ marker });
|
||||
decoration.onRender(() => {
|
||||
decoration.element.style.backgroundColor = 'red';
|
||||
const decoration = term.registerDecoration({
|
||||
marker,
|
||||
backgroundColor: '#00FF00',
|
||||
foregroundColor: '#00FE00',
|
||||
overviewRulerOptions: { color: '#ef292980', position: 'left' }
|
||||
});
|
||||
decoration.onRender((e: HTMLElement) => {
|
||||
e.style.right = '100%';
|
||||
e.style.backgroundColor = '#ef292980';
|
||||
});
|
||||
}
|
||||
|
||||
function addOverviewRuler() {
|
||||
term.options['overviewRulerWidth'] = 15;
|
||||
term.registerDecoration({marker: term.addMarker(1), overviewRulerOptions: { color: '#ef2929' }});
|
||||
term.registerDecoration({marker: term.addMarker(3), overviewRulerOptions: { color: '#8ae234' }});
|
||||
term.registerDecoration({marker: term.addMarker(5), overviewRulerOptions: { color: '#729fcf' }});
|
||||
term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#ef2929', position: 'left' }});
|
||||
term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#8ae234', position: 'center' }});
|
||||
term.registerDecoration({marker: term.addMarker(7), overviewRulerOptions: { color: '#729fcf', position: 'right' }});
|
||||
term.registerDecoration({marker: term.addMarker(10), overviewRulerOptions: { color: '#8ae234', position: 'center' }});
|
||||
term.registerDecoration({marker: term.addMarker(10), overviewRulerOptions: { color: '#ffffff80', position: 'full' }});
|
||||
}
|
||||
|
||||
|
||||
+18
-5
@@ -28,7 +28,7 @@
|
||||
</div>
|
||||
<div id="options" class="tabContent">
|
||||
<h3>Options</h3>
|
||||
<p>These options can be set in the <code>Terminal</code> constructor or by using the <code>Terminal.setOption</code> function.</p>
|
||||
<p>These options can be set in the <code>Terminal</code> constructor or by using the <code>Terminal.options</code> property.</p>
|
||||
<div id="options-container"></div>
|
||||
</div>
|
||||
<div id="addons" class="tabContent">
|
||||
@@ -40,9 +40,11 @@
|
||||
<div style= "display:flex; flex-direction:column;">
|
||||
<label>Find next <input id="find-next"/></label>
|
||||
<label>Find previous <input id="find-previous"/></label>
|
||||
<div>Results: <span id="find-results"></span></div>
|
||||
<label><input type="checkbox" id="regex"/>Use regex</label>
|
||||
<label><input type="checkbox" id="case-sensitive"/>Case sensitive</label>
|
||||
<label><input type="checkbox" id="whole-word"/>Whole word</label>
|
||||
<label><input type="checkbox" id="highlight-all-matches" checked/>Highlight All Matches</label>
|
||||
</div>
|
||||
<h4>SerializeAddon</h4>
|
||||
<div>
|
||||
@@ -65,10 +67,21 @@
|
||||
<div id="test" class="tabContent">
|
||||
<h3>Test</h3>
|
||||
<div style="display: inline-block; margin-right: 16px;">
|
||||
<button id="dispose" title="This is used to testing memory leaks">Dispose terminal</button>
|
||||
<button id="custom-glyph" title="Write custom box drawing and block element characters to the terminal">Test custom glyphs</button>
|
||||
<button id="load-test" title="Write several MB of data to simulate a lot of data coming from the process">Load test</button>
|
||||
<button id="add-decoration" title="Add a decoration to the terminal">Decoration</button>
|
||||
<dl>
|
||||
<dt>Lifecycle</dt>
|
||||
<dd><button id="dispose" title="This is used to testing memory leaks">Dispose terminal</button></dd>
|
||||
|
||||
<dt>Performance</dt>
|
||||
<dd><button id="load-test" title="Write several MB of data to simulate a lot of data coming from the process">Load test</button></dd>
|
||||
|
||||
<dt>Styles</dt>
|
||||
<dd><button id="custom-glyph" title="Write custom box drawing and block element characters to the terminal">Test custom glyphs</button></dd>
|
||||
<dd><button id="powerline-symbol-test" title="Write powerline symbol characters to the terminal (\ue0a0+)">Powerline symbol test</button></dd>
|
||||
|
||||
<dt>Decorations</dt>
|
||||
<dd><button id="add-decoration" title="Add a decoration to the terminal">Decoration</button></dd>
|
||||
<dd><button id="add-overview-ruler" title="Add an overview ruler to the terminal">Add Overview Ruler</button></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -114,6 +114,9 @@ function startServer() {
|
||||
}
|
||||
const send = USE_BINARY ? bufferUtf8(ws, 5) : buffer(ws, 5);
|
||||
|
||||
// WARNING: This is a naive implementation that will not throttle the flow of data. This means
|
||||
// it could flood the communication channel and make the terminal unresponsive. Learn more about
|
||||
// the problem and how to implement flow control at https://xtermjs.org/docs/guides/flowcontrol/
|
||||
term.on('data', function(data) {
|
||||
try {
|
||||
send(data);
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "xterm",
|
||||
"description": "Full xterm terminal, in your browser",
|
||||
"version": "4.17.0",
|
||||
"version": "4.19.0",
|
||||
"main": "lib/xterm.js",
|
||||
"style": "css/xterm.css",
|
||||
"types": "typings/xterm.d.ts",
|
||||
@@ -76,7 +76,7 @@
|
||||
"mustache": "^4.2.0",
|
||||
"node-pty": "^0.10.1",
|
||||
"nyc": "^15.1.0",
|
||||
"playwright": "^1.16.2",
|
||||
"playwright": "^1.22.1",
|
||||
"source-map-loader": "^3.0.0",
|
||||
"source-map-support": "^0.5.20",
|
||||
"ts-loader": "^9.1.2",
|
||||
@@ -85,6 +85,6 @@
|
||||
"webpack": "^5.61.0",
|
||||
"webpack-cli": "^4.9.1",
|
||||
"ws": "^8.2.3",
|
||||
"xterm-benchmark": "^0.3.0"
|
||||
"xterm-benchmark": "^0.3.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IColor, IColorContrastCache } from 'browser/Types';
|
||||
import { IColorContrastCache } from 'browser/Types';
|
||||
import { IColor } from 'common/Types';
|
||||
|
||||
export class ColorContrastCache implements IColorContrastCache {
|
||||
private _color: { [bg: number]: { [fg: number]: IColor | null | undefined } | undefined } = {};
|
||||
|
||||
@@ -34,7 +34,7 @@ describe('ColorManager', () => {
|
||||
describe('constructor', () => {
|
||||
it('should fill all colors with values', () => {
|
||||
for (const key of Object.keys(cm.colors)) {
|
||||
if (key !== 'ansi' && key !== 'contrastCache') {
|
||||
if (key !== 'ansi' && key !== 'contrastCache' && key !== 'selectionForeground') {
|
||||
// A #rrggbb or rgba(...)
|
||||
assert.ok((cm.colors as any)[key].css.length >= 7);
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IColorManager, IColor, IColorSet, IColorContrastCache } from 'browser/Types';
|
||||
import { IColorManager, IColorSet, IColorContrastCache } from 'browser/Types';
|
||||
import { ITheme } from 'common/services/Services';
|
||||
import { channels, color, css } from 'browser/Color';
|
||||
import { channels, color, css } from 'common/Color';
|
||||
import { ColorContrastCache } from 'browser/ColorContrastCache';
|
||||
import { ColorIndex } from 'common/Types';
|
||||
import { ColorIndex, IColor } from 'common/Types';
|
||||
|
||||
|
||||
interface IRestoreColorSet {
|
||||
@@ -104,6 +104,7 @@ export class ColorManager implements IColorManager {
|
||||
cursorAccent: DEFAULT_CURSOR_ACCENT,
|
||||
selectionTransparent: DEFAULT_SELECTION,
|
||||
selectionOpaque: color.blend(DEFAULT_BACKGROUND, DEFAULT_SELECTION),
|
||||
selectionForeground: undefined,
|
||||
ansi: DEFAULT_ANSI_COLORS.slice(),
|
||||
contrastCache: this._contrastCache
|
||||
};
|
||||
@@ -128,6 +129,15 @@ export class ColorManager implements IColorManager {
|
||||
this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT, true);
|
||||
this.colors.selectionTransparent = this._parseColor(theme.selection, DEFAULT_SELECTION, true);
|
||||
this.colors.selectionOpaque = color.blend(this.colors.background, this.colors.selectionTransparent);
|
||||
const nullColor: IColor = {
|
||||
css: '',
|
||||
rgba: 0
|
||||
};
|
||||
this.colors.selectionForeground = theme.selectionForeground ? this._parseColor(theme.selectionForeground, nullColor) : undefined;
|
||||
if (this.colors.selectionForeground === nullColor) {
|
||||
this.colors.selectionForeground = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* If selection color is opaque, blend it with background with 0.3 opacity
|
||||
* Issue #2737
|
||||
@@ -185,7 +195,7 @@ export class ColorManager implements IColorManager {
|
||||
foreground: this.colors.foreground,
|
||||
background: this.colors.background,
|
||||
cursor: this.colors.cursor,
|
||||
ansi: [...this.colors.ansi]
|
||||
ansi: this.colors.ansi.slice()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
|
||||
private _linkProviders: ILinkProvider[] = [];
|
||||
public get currentLink(): ILinkWithState | undefined { return this._currentLink; }
|
||||
protected _currentLink: ILinkWithState | undefined;
|
||||
private _mouseDownLink: ILinkWithState | undefined;
|
||||
private _lastMouseEvent: MouseEvent | undefined;
|
||||
private _linkCacheDisposables: IDisposable[] = [];
|
||||
private _lastBufferCell: IBufferCellPosition | undefined;
|
||||
@@ -61,7 +62,8 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
|
||||
this._clearCurrentLink();
|
||||
}));
|
||||
this.register(addDisposableDomListener(this._element, 'mousemove', this._onMouseMove.bind(this)));
|
||||
this.register(addDisposableDomListener(this._element, 'click', this._onClick.bind(this)));
|
||||
this.register(addDisposableDomListener(this._element, 'mousedown', this._handleMouseDown.bind(this)));
|
||||
this.register(addDisposableDomListener(this._element, 'mouseup', this._handleMouseUp.bind(this)));
|
||||
}
|
||||
|
||||
private _onMouseMove(event: MouseEvent): void {
|
||||
@@ -129,7 +131,7 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
|
||||
let linkProvided = false;
|
||||
|
||||
// There is no link cached, so ask for one
|
||||
this._linkProviders.forEach((linkProvider, i) => {
|
||||
for (const [i, linkProvider] of this._linkProviders.entries()) {
|
||||
if (useLineCache) {
|
||||
const existingReply = this._activeProviderReplies?.get(i);
|
||||
// If there isn't a reply, the provider hasn't responded yet.
|
||||
@@ -156,7 +158,7 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _removeIntersectingLinks(y: number, replies: Map<Number, ILinkWithState[] | undefined>): void {
|
||||
@@ -222,18 +224,21 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
|
||||
return linkProvided;
|
||||
}
|
||||
|
||||
private _onClick(event: MouseEvent): void {
|
||||
private _handleMouseDown(): void {
|
||||
this._mouseDownLink = this._currentLink;
|
||||
}
|
||||
|
||||
private _handleMouseUp(event: MouseEvent): void {
|
||||
if (!this._element || !this._mouseService || !this._currentLink) {
|
||||
return;
|
||||
}
|
||||
|
||||
const position = this._positionFromMouseEvent(event, this._element, this._mouseService);
|
||||
|
||||
if (!position) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._linkAtPosition(this._currentLink.link, position)) {
|
||||
if (this._mouseDownLink === this._currentLink && this._linkAtPosition(this._currentLink.link, position)) {
|
||||
this._currentLink.link.activate(event, this._currentLink.link.text);
|
||||
}
|
||||
}
|
||||
@@ -303,7 +308,7 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
|
||||
|
||||
// Add listener for rerendering
|
||||
if (this._renderService) {
|
||||
this._linkCacheDisposables.push(this._renderService.onRenderedBufferChange(e => {
|
||||
this._linkCacheDisposables.push(this._renderService.onRenderedViewportChange(e => {
|
||||
// When start is 0 a scroll most likely occurred, make sure links above the fold also get
|
||||
// cleared.
|
||||
const start = e.start === 0 ? 0 : e.start + 1 + this._bufferService.buffer.ydisp;
|
||||
|
||||
@@ -3,16 +3,17 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IRenderDebouncer } from 'browser/Types';
|
||||
import { IRenderDebouncerWithCallback } from 'browser/Types';
|
||||
|
||||
/**
|
||||
* Debounces calls to render terminal rows using animation frames.
|
||||
*/
|
||||
export class RenderDebouncer implements IRenderDebouncer {
|
||||
export class RenderDebouncer implements IRenderDebouncerWithCallback {
|
||||
private _rowStart: number | undefined;
|
||||
private _rowEnd: number | undefined;
|
||||
private _rowCount: number | undefined;
|
||||
private _animationFrame: number | undefined;
|
||||
private _refreshCallbacks: FrameRequestCallback[] = [];
|
||||
|
||||
constructor(
|
||||
private _renderCallback: (start: number, end: number) => void
|
||||
@@ -26,6 +27,14 @@ export class RenderDebouncer implements IRenderDebouncer {
|
||||
}
|
||||
}
|
||||
|
||||
public addRefreshCallback(callback: FrameRequestCallback): number {
|
||||
this._refreshCallbacks.push(callback);
|
||||
if (!this._animationFrame) {
|
||||
this._animationFrame = window.requestAnimationFrame(() => this._innerRefresh());
|
||||
}
|
||||
return this._animationFrame;
|
||||
}
|
||||
|
||||
public refresh(rowStart: number | undefined, rowEnd: number | undefined, rowCount: number): void {
|
||||
this._rowCount = rowCount;
|
||||
// Get the min/max row start/end for the arg values
|
||||
@@ -43,8 +52,11 @@ export class RenderDebouncer implements IRenderDebouncer {
|
||||
}
|
||||
|
||||
private _innerRefresh(): void {
|
||||
this._animationFrame = undefined;
|
||||
|
||||
// Make sure values are set
|
||||
if (this._rowStart === undefined || this._rowEnd === undefined || this._rowCount === undefined) {
|
||||
this._runRefreshCallbacks();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -55,9 +67,16 @@ export class RenderDebouncer implements IRenderDebouncer {
|
||||
// Reset debouncer (this happens before render callback as the render could trigger it again)
|
||||
this._rowStart = undefined;
|
||||
this._rowEnd = undefined;
|
||||
this._animationFrame = undefined;
|
||||
|
||||
// Run render callback
|
||||
this._renderCallback(start, end);
|
||||
this._runRefreshCallbacks();
|
||||
}
|
||||
|
||||
private _runRefreshCallbacks(): void {
|
||||
for (const callback of this._refreshCallbacks) {
|
||||
callback(0);
|
||||
}
|
||||
this._refreshCallbacks = [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +231,7 @@ describe('Terminal', () => {
|
||||
});
|
||||
term.paste('foo');
|
||||
});
|
||||
it('should sanitize \n chars', done => {
|
||||
it('should sanitize \\n chars', done => {
|
||||
term.onData(e => {
|
||||
assert.equal(e, '\rfoo\rbar\r');
|
||||
done();
|
||||
@@ -1332,8 +1332,8 @@ describe('Terminal', () => {
|
||||
(!(i % 3))
|
||||
? input[i]
|
||||
: (i % 3 === 1)
|
||||
? input.substr(i, 2)
|
||||
: input.substr(i - 1, 2),
|
||||
? input.slice(i, i + 2)
|
||||
: input.slice(i - 1, i + 1),
|
||||
terminal.buffer.lines.get(bufferIndex[0])!.loadCell(bufferIndex[1], new CellData()).getChars());
|
||||
}
|
||||
});
|
||||
|
||||
+57
-18
@@ -26,7 +26,7 @@ import { IRenderer } from 'browser/renderer/Types';
|
||||
import { CompositionHelper } from 'browser/input/CompositionHelper';
|
||||
import { Viewport } from 'browser/Viewport';
|
||||
import { rightClickHandler, moveTextAreaUnderMouseCursor, handlePasteEvent, copyHandler, paste } from 'browser/Clipboard';
|
||||
import { C0 } from 'common/data/EscapeSequences';
|
||||
import { C0, C1_ESCAPED } from 'common/data/EscapeSequences';
|
||||
import { WindowsOptionsReportType } from '../common/InputHandler';
|
||||
import { Renderer } from 'browser/renderer/Renderer';
|
||||
import { Linkifier } from 'browser/Linkifier';
|
||||
@@ -45,17 +45,20 @@ import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter';
|
||||
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||
import { ColorManager } from 'browser/ColorManager';
|
||||
import { RenderService } from 'browser/services/RenderService';
|
||||
import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService, ICharacterJoinerService, IDecorationService } from 'browser/services/Services';
|
||||
import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService, ICharacterJoinerService } from 'browser/services/Services';
|
||||
import { CharSizeService } from 'browser/services/CharSizeService';
|
||||
import { IBuffer } from 'common/buffer/Types';
|
||||
import { MouseService } from 'browser/services/MouseService';
|
||||
import { Linkifier2 } from 'browser/Linkifier2';
|
||||
import { CoreBrowserService } from 'browser/services/CoreBrowserService';
|
||||
import { CoreTerminal } from 'common/CoreTerminal';
|
||||
import { color, rgba } from 'browser/Color';
|
||||
import { color, rgba } from 'common/Color';
|
||||
import { CharacterJoinerService } from 'browser/services/CharacterJoinerService';
|
||||
import { toRgbString } from 'common/input/XParseColor';
|
||||
import { DecorationService } from 'browser/services/DecorationService';
|
||||
import { BufferDecorationRenderer } from 'browser/decorations/BufferDecorationRenderer';
|
||||
import { OverviewRulerRenderer } from 'browser/decorations/OverviewRulerRenderer';
|
||||
import { DecorationService } from 'common/services/DecorationService';
|
||||
import { IDecorationService } from 'common/services/Services';
|
||||
|
||||
// Let it work inside Node.js for automated testing purposes.
|
||||
const document: Document = (typeof window !== 'undefined') ? window.document : null as any;
|
||||
@@ -71,6 +74,8 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
private _helperContainer: HTMLElement | undefined;
|
||||
private _compositionView: HTMLElement | undefined;
|
||||
|
||||
private _overviewRulerRenderer: OverviewRulerRenderer | undefined;
|
||||
|
||||
// private _visualBellTimer: number;
|
||||
|
||||
public browser: IBrowser = Browser as any;
|
||||
@@ -78,6 +83,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
private _customKeyEventHandler: CustomKeyEventHandler | undefined;
|
||||
|
||||
// browser services
|
||||
private _decorationService: DecorationService;
|
||||
private _charSizeService: ICharSizeService | undefined;
|
||||
private _mouseService: IMouseService | undefined;
|
||||
private _renderService: IRenderService | undefined;
|
||||
@@ -92,6 +98,12 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
*/
|
||||
private _keyDownHandled: boolean = false;
|
||||
|
||||
/**
|
||||
* Records whether a keydown event has occured since the last keyup event, i.e. whether a key
|
||||
* is currently "pressed".
|
||||
*/
|
||||
private _keyDownSeen: boolean = false;
|
||||
|
||||
/**
|
||||
* Records whether the keypress event has already been handled and triggered a data event, if so
|
||||
* the input event should not trigger a data event but should still print to the textarea so
|
||||
@@ -109,7 +121,6 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
public linkifier: ILinkifier;
|
||||
public linkifier2: ILinkifier2;
|
||||
public viewport: IViewport | undefined;
|
||||
public decorationService: IDecorationService;
|
||||
private _compositionHelper: ICompositionHelper | undefined;
|
||||
private _mouseZoneManager: IMouseZoneManager | undefined;
|
||||
private _accessibilityManager: AccessibilityManager | undefined;
|
||||
@@ -159,7 +170,8 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
|
||||
this.linkifier = this._instantiationService.createInstance(Linkifier);
|
||||
this.linkifier2 = this.register(this._instantiationService.createInstance(Linkifier2));
|
||||
this.decorationService = this.register(this._instantiationService.createInstance(DecorationService));
|
||||
this._decorationService = this._instantiationService.createInstance(DecorationService);
|
||||
this._instantiationService.setService(IDecorationService, this._decorationService);
|
||||
|
||||
// Setup InputHandler listeners
|
||||
this.register(this._inputHandler.onRequestBell(() => this.bell()));
|
||||
@@ -212,7 +224,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
const channels = color.toColorRGB(acc === 'ansi'
|
||||
? this._colorManager.colors.ansi[req.index]
|
||||
: this._colorManager.colors[acc]);
|
||||
this.coreService.triggerDataEvent(`${C0.ESC}]${ident};${toRgbString(channels)}${C0.BEL}`);
|
||||
this.coreService.triggerDataEvent(`${C0.ESC}]${ident};${toRgbString(channels)}${C1_ESCAPED.ST}`);
|
||||
break;
|
||||
case ColorRequestType.SET:
|
||||
if (acc === 'ansi') this._colorManager.colors.ansi[req.index] = rgba.toColor(...req.color);
|
||||
@@ -471,6 +483,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
this._viewportElement = document.createElement('div');
|
||||
this._viewportElement.classList.add('xterm-viewport');
|
||||
fragment.appendChild(this._viewportElement);
|
||||
|
||||
this._viewportScrollArea = document.createElement('div');
|
||||
this._viewportScrollArea.classList.add('xterm-scroll-area');
|
||||
this._viewportElement.appendChild(this._viewportScrollArea);
|
||||
@@ -513,7 +526,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
const renderer = this._createRenderer();
|
||||
this._renderService = this.register(this._instantiationService.createInstance(RenderService, renderer, this.rows, this.screenElement));
|
||||
this._instantiationService.setService(IRenderService, this._renderService);
|
||||
this.register(this._renderService.onRenderedBufferChange(e => this._onRender.fire(e)));
|
||||
this.register(this._renderService.onRenderedViewportChange(e => this._onRender.fire(e)));
|
||||
this.onResize(e => this._renderService!.resize(e.cols, e.rows));
|
||||
|
||||
this._compositionView = document.createElement('div');
|
||||
@@ -576,8 +589,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
this.register(this.onScroll(() => this._mouseZoneManager!.clearAll()));
|
||||
this.linkifier.attachToDom(this.element, this._mouseZoneManager);
|
||||
this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService);
|
||||
|
||||
this.decorationService.attachToDom(this.screenElement, this._renderService, this._bufferService);
|
||||
this.register(this._instantiationService.createInstance(BufferDecorationRenderer, this.screenElement));
|
||||
// This event listener must be registered aftre MouseZoneManager is created
|
||||
this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e)));
|
||||
|
||||
@@ -595,6 +607,14 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
this._accessibilityManager = new AccessibilityManager(this, this._renderService);
|
||||
}
|
||||
|
||||
if (this.options.overviewRulerWidth) {
|
||||
this._overviewRulerRenderer = this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement);
|
||||
}
|
||||
this.optionsService.onOptionChange(() => {
|
||||
if (!this._overviewRulerRenderer && this.options.overviewRulerWidth && this._viewportElement && this.screenElement) {
|
||||
this._overviewRulerRenderer = this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement);
|
||||
}
|
||||
});
|
||||
// Measure the character size
|
||||
this._charSizeService.measure();
|
||||
|
||||
@@ -683,10 +703,13 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
but = ev.button < 3 ? ev.button : CoreMouseButton.NONE;
|
||||
break;
|
||||
case 'wheel':
|
||||
// only UP/DOWN wheel events are respected
|
||||
if ((ev as WheelEvent).deltaY !== 0) {
|
||||
action = (ev as WheelEvent).deltaY < 0 ? CoreMouseAction.UP : CoreMouseAction.DOWN;
|
||||
const amount = self.viewport!.getLinesScrolled(ev as WheelEvent);
|
||||
|
||||
if (amount === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
action = (ev as WheelEvent).deltaY < 0 ? CoreMouseAction.UP : CoreMouseAction.DOWN;
|
||||
but = CoreMouseButton.WHEEL;
|
||||
break;
|
||||
default:
|
||||
@@ -1003,7 +1026,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
}
|
||||
|
||||
public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined {
|
||||
return this.decorationService!.registerDecoration(decorationOptions);
|
||||
return this._decorationService.registerDecoration(decorationOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1070,19 +1093,23 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
*/
|
||||
protected _keyDown(event: KeyboardEvent): boolean | undefined {
|
||||
this._keyDownHandled = false;
|
||||
this._keyDownSeen = true;
|
||||
|
||||
if (this._customKeyEventHandler && this._customKeyEventHandler(event) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this._compositionHelper!.keydown(event)) {
|
||||
// Ignore composing with Alt key on Mac when macOptionIsMeta is enabled
|
||||
const shouldIgnoreComposition = this.browser.isMac && this.options.macOptionIsMeta && event.altKey;
|
||||
|
||||
if (!shouldIgnoreComposition && !this._compositionHelper!.keydown(event)) {
|
||||
if (this.buffer.ybase !== this.buffer.ydisp) {
|
||||
this._bufferService.scrollToBottom();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event.key === 'Dead' || event.key === 'AltGraph') {
|
||||
if (!shouldIgnoreComposition && (event.key === 'Dead' || event.key === 'AltGraph')) {
|
||||
this._unprocessedDeadKey = true;
|
||||
}
|
||||
|
||||
@@ -1113,6 +1140,14 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
return true;
|
||||
}
|
||||
|
||||
// HACK: Process A-Z in the keypress event to fix an issue with macOS IMEs where lower case
|
||||
// letters cannot be input while caps lock is on.
|
||||
if (event.key && !event.ctrlKey && !event.altKey && !event.metaKey && event.key.length === 1) {
|
||||
if (event.key.charCodeAt(0) >= 65 && event.key.charCodeAt(0) <= 90) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (this._unprocessedDeadKey) {
|
||||
this._unprocessedDeadKey = false;
|
||||
return true;
|
||||
@@ -1155,6 +1190,8 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
}
|
||||
|
||||
protected _keyUp(ev: KeyboardEvent): void {
|
||||
this._keyDownSeen = false;
|
||||
|
||||
if (this._customKeyEventHandler && this._customKeyEventHandler(ev) === false) {
|
||||
return;
|
||||
}
|
||||
@@ -1228,7 +1265,8 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
protected _inputEvent(ev: InputEvent): boolean {
|
||||
// Only support emoji IMEs when screen reader mode is disabled as the event must bubble up to
|
||||
// support reading out character input which can doubling up input characters
|
||||
if (ev.data && ev.inputType === 'insertText' && !ev.composed && !this.optionsService.rawOptions.screenReaderMode) {
|
||||
// Based on these event traces: https://github.com/xtermjs/xterm.js/issues/3679
|
||||
if (ev.data && ev.inputType === 'insertText' && (!ev.composed || !this._keyDownSeen) && !this.optionsService.rawOptions.screenReaderMode) {
|
||||
if (this._keyPressHandled) {
|
||||
return false;
|
||||
}
|
||||
@@ -1301,7 +1339,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
// Don't clear if it's already clear
|
||||
return;
|
||||
}
|
||||
this.buffer.clearMarkers();
|
||||
this.buffer.clearAllMarkers();
|
||||
this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y)!);
|
||||
this.buffer.lines.length = 1;
|
||||
this.buffer.ydisp = 0;
|
||||
@@ -1334,6 +1372,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
this._setup();
|
||||
super.reset();
|
||||
this._selectionService?.reset();
|
||||
this._decorationService.reset();
|
||||
|
||||
// reattach
|
||||
this._customKeyEventHandler = customKeyEventHandler;
|
||||
|
||||
@@ -7,7 +7,7 @@ import { IDisposable, IMarker, ISelectionPosition, ILinkProvider, IDecorationOpt
|
||||
import { IEvent, EventEmitter } from 'common/EventEmitter';
|
||||
import { ICharacterJoinerService, ICharSizeService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services';
|
||||
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types';
|
||||
import { IColorSet, ILinkMatcherOptions, ITerminal, ILinkifier, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler } from 'browser/Types';
|
||||
import { IColorSet, ILinkMatcherOptions, ITerminal, ILinkifier, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler, IRenderDebouncer } from 'browser/Types';
|
||||
import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types';
|
||||
import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, ITerminalOptions } from 'common/Types';
|
||||
import { Buffer } from 'common/buffer/Buffer';
|
||||
@@ -16,6 +16,7 @@ import { Terminal } from 'browser/Terminal';
|
||||
import { IUnicodeService, IOptionsService, ICoreService, ICoreMouseService } from 'common/services/Services';
|
||||
import { IFunctionIdentifier, IParams } from 'common/parser/Types';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { ISelectionRedrawRequestEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types';
|
||||
|
||||
export class TestTerminal extends Terminal {
|
||||
public get curAttrData(): IAttributeData { return (this as any)._inputHandler._curAttrData; }
|
||||
@@ -30,6 +31,7 @@ export class MockTerminal implements ITerminal {
|
||||
public onBlur!: IEvent<void>;
|
||||
public onFocus!: IEvent<void>;
|
||||
public onA11yChar!: IEvent<string>;
|
||||
public onWriteParsed!: IEvent<void>;
|
||||
public onA11yTab!: IEvent<number>;
|
||||
public onCursorMove!: IEvent<void>;
|
||||
public onLineFeed!: IEvent<void>;
|
||||
@@ -257,7 +259,10 @@ export class MockBuffer implements IBuffer {
|
||||
public getWhitespaceCell(attr?: IAttributeData): ICellData {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public clearMarkers(): void {
|
||||
public clearMarkers(y: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public clearAllMarkers(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
@@ -367,7 +372,8 @@ export class MockMouseService implements IMouseService {
|
||||
export class MockRenderService implements IRenderService {
|
||||
public serviceBrand: undefined;
|
||||
public onDimensionsChange: IEvent<IRenderDimensions> = new EventEmitter<IRenderDimensions>().event;
|
||||
public onRenderedBufferChange: IEvent<{ start: number, end: number }, void> = new EventEmitter<{ start: number, end: number }>().event;
|
||||
public onRenderedViewportChange: IEvent<{ start: number, end: number }, void> = new EventEmitter<{ start: number, end: number }>().event;
|
||||
public onRender: IEvent<{ start: number, end: number }, void> = new EventEmitter<{ start: number, end: number }>().event;
|
||||
public onRefreshRequest: IEvent<{ start: number, end: number}, void> = new EventEmitter<{ start: number, end: number }>().event;
|
||||
public dimensions: IRenderDimensions = {
|
||||
scaledCharWidth: 0,
|
||||
@@ -386,15 +392,15 @@ export class MockRenderService implements IRenderService {
|
||||
public refreshRows(start: number, end: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public addRefreshCallback(callback: FrameRequestCallback): number {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public clearTextureAtlas(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public resize(cols: number, rows: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public changeOptions(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public setRenderer(renderer: IRenderer): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
@@ -445,3 +451,54 @@ export class MockCharacterJoinerService implements ICharacterJoinerService {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export class MockSelectionService implements ISelectionService {
|
||||
public serviceBrand: undefined;
|
||||
public selectionText: string = '';
|
||||
public hasSelection: boolean = false;
|
||||
public selectionStart: [number, number] | undefined;
|
||||
public selectionEnd: [number, number] | undefined;
|
||||
public onLinuxMouseSelection = new EventEmitter<string>().event;
|
||||
public onRequestRedraw = new EventEmitter<ISelectionRedrawRequestEvent>().event;
|
||||
public onRequestScrollLines = new EventEmitter<ISelectionRequestScrollLinesEvent>().event;
|
||||
public onSelectionChange = new EventEmitter<void>().event;
|
||||
public disable(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public enable(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public reset(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public setSelection(row: number, col: number, length: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public selectAll(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public selectLines(start: number, end: number): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public clearSelection(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public rightClickSelect(event: MouseEvent): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public shouldColumnSelect(event: MouseEvent | KeyboardEvent): boolean {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public shouldForceSelection(event: MouseEvent): boolean {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public refresh(isLinuxMouseSelection?: boolean): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public onMouseDown(event: MouseEvent): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public isCellInSelection(x: number, y: number): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+7
-6
@@ -5,7 +5,7 @@
|
||||
|
||||
import { IDecorationOptions, IDecoration, IDisposable, IMarker, ISelectionPosition } from 'xterm';
|
||||
import { IEvent } from 'common/EventEmitter';
|
||||
import { ICoreTerminal, CharData, ITerminalOptions } from 'common/Types';
|
||||
import { ICoreTerminal, CharData, ITerminalOptions, IColor } from 'common/Types';
|
||||
import { IMouseService, IRenderService } from './services/Services';
|
||||
import { IBuffer } from 'common/buffer/Types';
|
||||
import { IFunctionIdentifier, IParams } from 'common/parser/Types';
|
||||
@@ -44,6 +44,7 @@ export interface IPublicTerminal extends IDisposable {
|
||||
onSelectionChange: IEvent<void>;
|
||||
onRender: IEvent<{ start: number, end: number }>;
|
||||
onResize: IEvent<{ cols: number, rows: number }>;
|
||||
onWriteParsed: IEvent<void>;
|
||||
onTitleChange: IEvent<string>;
|
||||
onBell: IEvent<void>;
|
||||
blur(): void;
|
||||
@@ -112,11 +113,6 @@ export interface IColorManager {
|
||||
onOptionsChange(key: string): void;
|
||||
}
|
||||
|
||||
export interface IColor {
|
||||
css: string;
|
||||
rgba: number; // 32-bit int with rgba in each byte
|
||||
}
|
||||
|
||||
export interface IColorSet {
|
||||
foreground: IColor;
|
||||
background: IColor;
|
||||
@@ -125,6 +121,7 @@ export interface IColorSet {
|
||||
selectionTransparent: IColor;
|
||||
/** The selection blended on top of background. */
|
||||
selectionOpaque: IColor;
|
||||
selectionForeground: IColor | undefined;
|
||||
ansi: IColor[];
|
||||
contrastCache: IColorContrastCache;
|
||||
}
|
||||
@@ -314,3 +311,7 @@ export interface ICharacterJoiner {
|
||||
export interface IRenderDebouncer extends IDisposable {
|
||||
refresh(rowStart: number | undefined, rowEnd: number | undefined, rowCount: number): void;
|
||||
}
|
||||
|
||||
export interface IRenderDebouncerWithCallback extends IRenderDebouncer {
|
||||
addRefreshCallback(callback: FrameRequestCallback): number;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ export class Viewport extends Disposable implements IViewport {
|
||||
private _lastRecordedBufferHeight: number = 0;
|
||||
private _lastTouchY: number = 0;
|
||||
private _lastScrollTop: number = 0;
|
||||
private _lastHadScrollBar: boolean = false;
|
||||
private _activeBuffer: IBuffer;
|
||||
private _renderDimensions: IRenderDimensions;
|
||||
|
||||
@@ -54,7 +53,6 @@ export class Viewport extends Disposable implements IViewport {
|
||||
// Unfortunately the overlay scrollbar would be hidden underneath the screen element in that case,
|
||||
// therefore we account for a standard amount to make it visible
|
||||
this.scrollBarWidth = (this._viewportElement.offsetWidth - this._scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH;
|
||||
this._lastHadScrollBar = true;
|
||||
this.register(addDisposableDomListener(this._viewportElement, 'scroll', this._onScroll.bind(this)));
|
||||
|
||||
// Track properties used in performance critical code manually to avoid using slow getters
|
||||
@@ -109,17 +107,6 @@ export class Viewport extends Disposable implements IViewport {
|
||||
this._viewportElement.scrollTop = scrollTop;
|
||||
}
|
||||
|
||||
// Update scroll bar width
|
||||
if (this._optionsService.rawOptions.scrollback === 0) {
|
||||
this.scrollBarWidth = 0;
|
||||
} else {
|
||||
this.scrollBarWidth = (this._viewportElement.offsetWidth - this._scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH;
|
||||
}
|
||||
this._lastHadScrollBar = this.scrollBarWidth > 0;
|
||||
|
||||
const elementStyle = window.getComputedStyle(this._element);
|
||||
const elementPadding = parseInt(elementStyle.paddingLeft) + parseInt(elementStyle.paddingRight);
|
||||
this._viewportElement.style.width = (this._renderService.dimensions.actualCellWidth * (this._bufferService.cols) + this.scrollBarWidth + (this._lastHadScrollBar ? elementPadding : 0)).toString() + 'px';
|
||||
this._refreshAnimationFrame = null;
|
||||
}
|
||||
|
||||
@@ -151,11 +138,6 @@ export class Viewport extends Disposable implements IViewport {
|
||||
this._refresh(immediate);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the scroll bar visibility changed
|
||||
if (this._lastHadScrollBar !== (this._optionsService.rawOptions.scrollback > 0)) {
|
||||
this._refresh(immediate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { addDisposableDomListener } from 'browser/Lifecycle';
|
||||
import { IRenderService } from 'browser/services/Services';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { IBufferService, IDecorationService, IInternalDecoration } from 'common/services/Services';
|
||||
|
||||
export class BufferDecorationRenderer extends Disposable {
|
||||
private readonly _container: HTMLElement;
|
||||
private readonly _decorationElements: Map<IInternalDecoration, HTMLElement> = new Map();
|
||||
|
||||
private _animationFrame: number | undefined;
|
||||
private _altBufferIsActive: boolean = false;
|
||||
private _dimensionsChanged: boolean = false;
|
||||
|
||||
constructor(
|
||||
private readonly _screenElement: HTMLElement,
|
||||
@IBufferService private readonly _bufferService: IBufferService,
|
||||
@IDecorationService private readonly _decorationService: IDecorationService,
|
||||
@IRenderService private readonly _renderService: IRenderService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._container = document.createElement('div');
|
||||
this._container.classList.add('xterm-decoration-container');
|
||||
this._screenElement.appendChild(this._container);
|
||||
|
||||
this.register(this._renderService.onRenderedViewportChange(() => this._queueRefresh()));
|
||||
this.register(this._renderService.onDimensionsChange(() => {
|
||||
this._dimensionsChanged = true;
|
||||
this._queueRefresh();
|
||||
}));
|
||||
this.register(addDisposableDomListener(window, 'resize', () => this._queueRefresh()));
|
||||
this.register(this._bufferService.buffers.onBufferActivate(() => {
|
||||
this._altBufferIsActive = this._bufferService.buffer === this._bufferService.buffers.alt;
|
||||
}));
|
||||
this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh()));
|
||||
this.register(this._decorationService.onDecorationRemoved(decoration => this._removeDecoration(decoration)));
|
||||
}
|
||||
|
||||
public override dispose(): void {
|
||||
this._container.remove();
|
||||
this._decorationElements.clear();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private _queueRefresh(): void {
|
||||
if (this._animationFrame !== undefined) {
|
||||
return;
|
||||
}
|
||||
this._animationFrame = this._renderService.addRefreshCallback(() => {
|
||||
this.refreshDecorations();
|
||||
this._animationFrame = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
public refreshDecorations(): void {
|
||||
for (const decoration of this._decorationService.decorations) {
|
||||
this._renderDecoration(decoration);
|
||||
}
|
||||
this._dimensionsChanged = false;
|
||||
}
|
||||
|
||||
private _renderDecoration(decoration: IInternalDecoration): void {
|
||||
this._refreshStyle(decoration);
|
||||
if (this._dimensionsChanged) {
|
||||
this._refreshXPosition(decoration);
|
||||
}
|
||||
}
|
||||
|
||||
private _createElement(decoration: IInternalDecoration): HTMLElement {
|
||||
const element = document.createElement('div');
|
||||
element.classList.add('xterm-decoration');
|
||||
element.style.width = `${Math.round((decoration.options.width || 1) * this._renderService.dimensions.actualCellWidth)}px`;
|
||||
element.style.height = `${(decoration.options.height || 1) * this._renderService.dimensions.actualCellHeight}px`;
|
||||
element.style.top = `${(decoration.marker.line - this._bufferService.buffers.active.ydisp) * this._renderService.dimensions.actualCellHeight}px`;
|
||||
element.style.lineHeight = `${this._renderService.dimensions.actualCellHeight}px`;
|
||||
|
||||
const x = decoration.options.x ?? 0;
|
||||
if (x && x > this._bufferService.cols) {
|
||||
// exceeded the container width, so hide
|
||||
element.style.display = 'none';
|
||||
}
|
||||
this._refreshXPosition(decoration, element);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
private _refreshStyle(decoration: IInternalDecoration): void {
|
||||
const line = decoration.marker.line - this._bufferService.buffers.active.ydisp;
|
||||
if (line < 0 || line >= this._bufferService.rows) {
|
||||
// outside of viewport
|
||||
if (decoration.element) {
|
||||
decoration.element.style.display = 'none';
|
||||
decoration.onRenderEmitter.fire(decoration.element);
|
||||
}
|
||||
} else {
|
||||
let element = this._decorationElements.get(decoration);
|
||||
if (!element) {
|
||||
decoration.onDispose(() => this._removeDecoration(decoration));
|
||||
element = this._createElement(decoration);
|
||||
decoration.element = element;
|
||||
this._decorationElements.set(decoration, element);
|
||||
this._container.appendChild(element);
|
||||
}
|
||||
element.style.top = `${line * this._renderService.dimensions.actualCellHeight}px`;
|
||||
element.style.display = this._altBufferIsActive ? 'none' : 'block';
|
||||
decoration.onRenderEmitter.fire(element);
|
||||
}
|
||||
}
|
||||
|
||||
private _refreshXPosition(decoration: IInternalDecoration, element: HTMLElement | undefined = decoration.element): void {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
const x = decoration.options.x ?? 0;
|
||||
if ((decoration.options.anchor || 'left') === 'right') {
|
||||
element.style.right = x ? `${x * this._renderService.dimensions.actualCellWidth}px` : '';
|
||||
} else {
|
||||
element.style.left = x ? `${x * this._renderService.dimensions.actualCellWidth}px` : '';
|
||||
}
|
||||
}
|
||||
|
||||
private _removeDecoration(decoration: IInternalDecoration): void {
|
||||
this._decorationElements.get(decoration)?.remove();
|
||||
this._decorationElements.delete(decoration);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { ColorZoneStore } from 'browser/decorations/ColorZoneStore';
|
||||
|
||||
const optionsRedFull = {
|
||||
overviewRulerOptions: {
|
||||
color: 'red',
|
||||
position: 'full' as 'full'
|
||||
}
|
||||
};
|
||||
|
||||
describe('ColorZoneStore', () => {
|
||||
let store: ColorZoneStore;
|
||||
|
||||
beforeEach(() => {
|
||||
store = new ColorZoneStore();
|
||||
store.setPadding({
|
||||
full: 1,
|
||||
left: 1,
|
||||
center: 1,
|
||||
right: 1
|
||||
});
|
||||
});
|
||||
|
||||
it('should merge adjacent zones', () => {
|
||||
store.addDecoration({
|
||||
marker: { line: 0 },
|
||||
options: optionsRedFull
|
||||
});
|
||||
store.addDecoration({
|
||||
marker: { line: 1 },
|
||||
options: optionsRedFull
|
||||
});
|
||||
assert.deepStrictEqual(store.zones, [
|
||||
{
|
||||
color: 'red',
|
||||
position: 'full',
|
||||
startBufferLine: 0,
|
||||
endBufferLine: 1
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not merge non-adjacent zones', () => {
|
||||
store.addDecoration({
|
||||
marker: { line: 0 },
|
||||
options: optionsRedFull
|
||||
});
|
||||
store.addDecoration({
|
||||
marker: { line: 2 },
|
||||
options: optionsRedFull
|
||||
});
|
||||
assert.deepStrictEqual(store.zones, [
|
||||
{
|
||||
color: 'red',
|
||||
position: 'full',
|
||||
startBufferLine: 0,
|
||||
endBufferLine: 0
|
||||
},
|
||||
{
|
||||
color: 'red',
|
||||
position: 'full',
|
||||
startBufferLine: 2,
|
||||
endBufferLine: 2
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
it('should reuse zone objects', () => {
|
||||
const obj = {
|
||||
marker: { line: 0 },
|
||||
options: optionsRedFull
|
||||
};
|
||||
store.addDecoration(obj);
|
||||
const zone = store.zones[0];
|
||||
store.clear();
|
||||
store.addDecoration({
|
||||
marker: { line: 1 },
|
||||
options: optionsRedFull
|
||||
});
|
||||
// The object reference should be the same
|
||||
assert.equal(zone, store.zones[0]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,117 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IInternalDecoration } from 'common/services/Services';
|
||||
|
||||
export interface IColorZoneStore {
|
||||
readonly zones: IColorZone[];
|
||||
clear(): void;
|
||||
addDecoration(decoration: IInternalDecoration): void;
|
||||
/**
|
||||
* Sets the amount of padding in lines that will be added between zones, if new lines intersect
|
||||
* the padding they will be merged into the same zone.
|
||||
*/
|
||||
setPadding(padding: { [position: string]: number }): void;
|
||||
}
|
||||
|
||||
export interface IColorZone {
|
||||
/** Color in a format supported by canvas' fillStyle. */
|
||||
color: string;
|
||||
position: 'full' | 'left' | 'center' | 'right' | undefined;
|
||||
startBufferLine: number;
|
||||
endBufferLine: number;
|
||||
}
|
||||
|
||||
interface IMinimalDecorationForColorZone {
|
||||
marker: Pick<IInternalDecoration['marker'], 'line'>;
|
||||
options: Pick<IInternalDecoration['options'], 'overviewRulerOptions'>;
|
||||
}
|
||||
|
||||
export class ColorZoneStore implements IColorZoneStore {
|
||||
private _zones: IColorZone[] = [];
|
||||
|
||||
// The zone pool is used to keep zone objects from being freed between clearing the color zone
|
||||
// store and fetching the zones. This helps reduce GC pressure since the color zones are
|
||||
// accumulated on potentially every scroll event.
|
||||
private _zonePool: IColorZone[] = [];
|
||||
private _zonePoolIndex = 0;
|
||||
|
||||
private _linePadding: { [position: string]: number } = {
|
||||
full: 0,
|
||||
left: 0,
|
||||
center: 0,
|
||||
right: 0
|
||||
};
|
||||
|
||||
public get zones(): IColorZone[] {
|
||||
// Trim the zone pool to free unused memory
|
||||
this._zonePool.length = Math.min(this._zonePool.length, this._zones.length);
|
||||
return this._zones;
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this._zones.length = 0;
|
||||
this._zonePoolIndex = 0;
|
||||
}
|
||||
|
||||
public addDecoration(decoration: IMinimalDecorationForColorZone): void {
|
||||
if (!decoration.options.overviewRulerOptions) {
|
||||
return;
|
||||
}
|
||||
for (const z of this._zones) {
|
||||
if (z.color === decoration.options.overviewRulerOptions.color &&
|
||||
z.position === decoration.options.overviewRulerOptions.position) {
|
||||
if (this._lineIntersectsZone(z, decoration.marker.line)) {
|
||||
return;
|
||||
}
|
||||
if (this._lineAdjacentToZone(z, decoration.marker.line, decoration.options.overviewRulerOptions.position)) {
|
||||
this._addLineToZone(z, decoration.marker.line);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Create using zone pool if possible
|
||||
if (this._zonePoolIndex < this._zonePool.length) {
|
||||
this._zonePool[this._zonePoolIndex].color = decoration.options.overviewRulerOptions.color;
|
||||
this._zonePool[this._zonePoolIndex].position = decoration.options.overviewRulerOptions.position;
|
||||
this._zonePool[this._zonePoolIndex].startBufferLine = decoration.marker.line;
|
||||
this._zonePool[this._zonePoolIndex].endBufferLine = decoration.marker.line;
|
||||
this._zones.push(this._zonePool[this._zonePoolIndex++]);
|
||||
return;
|
||||
}
|
||||
// Create
|
||||
this._zones.push({
|
||||
color: decoration.options.overviewRulerOptions.color,
|
||||
position: decoration.options.overviewRulerOptions.position,
|
||||
startBufferLine: decoration.marker.line,
|
||||
endBufferLine: decoration.marker.line
|
||||
});
|
||||
this._zonePool.push(this._zones[this._zones.length - 1]);
|
||||
this._zonePoolIndex++;
|
||||
}
|
||||
|
||||
public setPadding(padding: { [position: string]: number }): void {
|
||||
this._linePadding = padding;
|
||||
}
|
||||
|
||||
private _lineIntersectsZone(zone: IColorZone, line: number): boolean {
|
||||
return (
|
||||
line >= zone.startBufferLine &&
|
||||
line <= zone.endBufferLine
|
||||
);
|
||||
}
|
||||
|
||||
private _lineAdjacentToZone(zone: IColorZone, line: number, position: IColorZone['position']): boolean {
|
||||
return (
|
||||
(line >= zone.startBufferLine - this._linePadding[position || 'full']) &&
|
||||
(line <= zone.endBufferLine + this._linePadding[position || 'full'])
|
||||
);
|
||||
}
|
||||
|
||||
private _addLineToZone(zone: IColorZone, line: number): void {
|
||||
zone.startBufferLine = Math.min(zone.startBufferLine, line);
|
||||
zone.endBufferLine = Math.max(zone.endBufferLine, line);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ColorZoneStore, IColorZone, IColorZoneStore } from 'browser/decorations/ColorZoneStore';
|
||||
import { addDisposableDomListener } from 'browser/Lifecycle';
|
||||
import { IRenderService } from 'browser/services/Services';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { IBufferService, IDecorationService, IInternalDecoration, IOptionsService } from 'common/services/Services';
|
||||
|
||||
// Helper objects to avoid excessive calculation and garbage collection during rendering. These are
|
||||
// static values for each render and can be accessed using the decoration position as the key.
|
||||
const drawHeight = {
|
||||
full: 0,
|
||||
left: 0,
|
||||
center: 0,
|
||||
right: 0
|
||||
};
|
||||
const drawWidth = {
|
||||
full: 0,
|
||||
left: 0,
|
||||
center: 0,
|
||||
right: 0
|
||||
};
|
||||
const drawX = {
|
||||
full: 0,
|
||||
left: 0,
|
||||
center: 0,
|
||||
right: 0
|
||||
};
|
||||
|
||||
export class OverviewRulerRenderer extends Disposable {
|
||||
private readonly _canvas: HTMLCanvasElement;
|
||||
private readonly _ctx: CanvasRenderingContext2D;
|
||||
private readonly _colorZoneStore: IColorZoneStore = new ColorZoneStore();
|
||||
private get _width(): number {
|
||||
return this._optionsService.options.overviewRulerWidth || 0;
|
||||
}
|
||||
private _animationFrame: number | undefined;
|
||||
|
||||
private _shouldUpdateDimensions: boolean | undefined = true;
|
||||
private _shouldUpdateAnchor: boolean | undefined = true;
|
||||
private _lastKnownBufferLength: number = 0;
|
||||
|
||||
private _containerHeight: number | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly _viewportElement: HTMLElement,
|
||||
private readonly _screenElement: HTMLElement,
|
||||
@IBufferService private readonly _bufferService: IBufferService,
|
||||
@IDecorationService private readonly _decorationService: IDecorationService,
|
||||
@IRenderService private readonly _renderService: IRenderService,
|
||||
@IOptionsService private readonly _optionsService: IOptionsService
|
||||
) {
|
||||
super();
|
||||
this._canvas = document.createElement('canvas');
|
||||
this._canvas.classList.add('xterm-decoration-overview-ruler');
|
||||
this._refreshCanvasDimensions();
|
||||
this._viewportElement.parentElement?.insertBefore(this._canvas, this._viewportElement);
|
||||
const ctx = this._canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
throw new Error('Ctx cannot be null');
|
||||
} else {
|
||||
this._ctx = ctx;
|
||||
}
|
||||
this._registerDecorationListeners();
|
||||
this._registerBufferChangeListeners();
|
||||
this._registerDimensionChangeListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* On decoration add or remove, redraw
|
||||
*/
|
||||
private _registerDecorationListeners(): void {
|
||||
this.register(this._decorationService.onDecorationRegistered(() => this._queueRefresh(undefined, true)));
|
||||
this.register(this._decorationService.onDecorationRemoved(() => this._queueRefresh(undefined, true)));
|
||||
}
|
||||
|
||||
/**
|
||||
* On buffer change, redraw
|
||||
* and hide the canvas if the alt buffer is active
|
||||
*/
|
||||
private _registerBufferChangeListeners(): void {
|
||||
this.register(this._renderService.onRenderedViewportChange(() => this._queueRefresh()));
|
||||
this.register(this._bufferService.buffers.onBufferActivate(() => {
|
||||
this._canvas!.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block';
|
||||
}));
|
||||
this.register(this._bufferService.onScroll(() => {
|
||||
if (this._lastKnownBufferLength !== this._bufferService.buffers.normal.lines.length) {
|
||||
this._refreshDrawHeightConstants();
|
||||
this._refreshColorZonePadding();
|
||||
}
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* On dimension change, update canvas dimensions
|
||||
* and then redraw
|
||||
*/
|
||||
private _registerDimensionChangeListeners(): void {
|
||||
// container height changed
|
||||
this.register(this._renderService.onRender((): void => {
|
||||
if (!this._containerHeight || this._containerHeight !== this._screenElement.clientHeight) {
|
||||
this._queueRefresh(true);
|
||||
this._containerHeight = this._screenElement.clientHeight;
|
||||
}
|
||||
}));
|
||||
// overview ruler width changed
|
||||
this.register(this._optionsService.onOptionChange(o => {
|
||||
if (o === 'overviewRulerWidth') {
|
||||
this._queueRefresh(true);
|
||||
}
|
||||
}));
|
||||
// device pixel ratio changed
|
||||
this.register(addDisposableDomListener(window, 'resize', () => {
|
||||
this._queueRefresh(true);
|
||||
}));
|
||||
// set the canvas dimensions
|
||||
this._queueRefresh(true);
|
||||
}
|
||||
|
||||
public override dispose(): void {
|
||||
this._canvas?.remove();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private _refreshDrawConstants(): void {
|
||||
// width
|
||||
const outerWidth = Math.floor(this._canvas.width / 3);
|
||||
const innerWidth = Math.ceil(this._canvas.width / 3);
|
||||
drawWidth.full = this._canvas.width;
|
||||
drawWidth.left = outerWidth;
|
||||
drawWidth.center = innerWidth;
|
||||
drawWidth.right = outerWidth;
|
||||
// height
|
||||
this._refreshDrawHeightConstants();
|
||||
// x
|
||||
drawX.full = 0;
|
||||
drawX.left = 0;
|
||||
drawX.center = drawWidth.left;
|
||||
drawX.right = drawWidth.left + drawWidth.center;
|
||||
}
|
||||
|
||||
private _refreshDrawHeightConstants(): void {
|
||||
drawHeight.full = Math.round(2 * window.devicePixelRatio);
|
||||
// Calculate actual pixels per line
|
||||
const pixelsPerLine = this._canvas.height / this._bufferService.buffer.lines.length;
|
||||
// Clamp actual pixels within a range
|
||||
const nonFullHeight = Math.round(Math.max(Math.min(pixelsPerLine, 12), 6) * window.devicePixelRatio);
|
||||
drawHeight.left = nonFullHeight;
|
||||
drawHeight.center = nonFullHeight;
|
||||
drawHeight.right = nonFullHeight;
|
||||
}
|
||||
|
||||
private _refreshColorZonePadding(): void {
|
||||
this._colorZoneStore.setPadding({
|
||||
full: Math.floor(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * drawHeight.full),
|
||||
left: Math.floor(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * drawHeight.left),
|
||||
center: Math.floor(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * drawHeight.center),
|
||||
right: Math.floor(this._bufferService.buffers.active.lines.length / (this._canvas.height - 1) * drawHeight.right)
|
||||
});
|
||||
this._lastKnownBufferLength = this._bufferService.buffers.normal.lines.length;
|
||||
}
|
||||
|
||||
private _refreshCanvasDimensions(): void {
|
||||
this._canvas.style.width = `${this._width}px`;
|
||||
this._canvas.width = Math.round(this._width * window.devicePixelRatio);
|
||||
this._canvas.style.height = `${this._screenElement.clientHeight}px`;
|
||||
this._canvas.height = Math.round(this._screenElement.clientHeight * window.devicePixelRatio);
|
||||
this._refreshDrawConstants();
|
||||
this._refreshColorZonePadding();
|
||||
}
|
||||
|
||||
private _refreshDecorations(): void {
|
||||
if (this._shouldUpdateDimensions) {
|
||||
this._refreshCanvasDimensions();
|
||||
}
|
||||
this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
|
||||
this._colorZoneStore.clear();
|
||||
for (const decoration of this._decorationService.decorations) {
|
||||
this._colorZoneStore.addDecoration(decoration);
|
||||
}
|
||||
this._ctx.lineWidth = 1;
|
||||
const zones = this._colorZoneStore.zones;
|
||||
for (const zone of zones) {
|
||||
if (zone.position !== 'full') {
|
||||
this._renderColorZone(zone);
|
||||
}
|
||||
}
|
||||
for (const zone of zones) {
|
||||
if (zone.position === 'full') {
|
||||
this._renderColorZone(zone);
|
||||
}
|
||||
}
|
||||
this._shouldUpdateDimensions = false;
|
||||
this._shouldUpdateAnchor = false;
|
||||
}
|
||||
|
||||
private _renderColorZone(zone: IColorZone): void {
|
||||
// TODO: Is _decorationElements needed?
|
||||
|
||||
this._ctx.fillStyle = zone.color;
|
||||
this._ctx.fillRect(
|
||||
/* x */ drawX[zone.position || 'full'],
|
||||
/* y */ Math.round(
|
||||
(this._canvas.height - 1) * // -1 to ensure at least 2px are allowed for decoration on last line
|
||||
(zone.startBufferLine / this._bufferService.buffers.active.lines.length) - drawHeight[zone.position || 'full'] / 2
|
||||
),
|
||||
/* w */ drawWidth[zone.position || 'full'],
|
||||
/* h */ Math.round(
|
||||
(this._canvas.height - 1) * // -1 to ensure at least 2px are allowed for decoration on last line
|
||||
((zone.endBufferLine - zone.startBufferLine) / this._bufferService.buffers.active.lines.length) + drawHeight[zone.position || 'full']
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private _queueRefresh(updateCanvasDimensions?: boolean, updateAnchor?: boolean): void {
|
||||
this._shouldUpdateDimensions = updateCanvasDimensions || this._shouldUpdateDimensions;
|
||||
this._shouldUpdateAnchor = updateAnchor || this._shouldUpdateAnchor;
|
||||
if (this._animationFrame !== undefined) {
|
||||
return;
|
||||
}
|
||||
this._animationFrame = window.requestAnimationFrame(() => {
|
||||
this._refreshDecorations();
|
||||
this._animationFrame = undefined;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -11,30 +11,38 @@ const CHAR_WIDTH = 10;
|
||||
const CHAR_HEIGHT = 20;
|
||||
|
||||
describe('Mouse getCoords', () => {
|
||||
let windowOverride: Pick<Window, 'getComputedStyle'>;
|
||||
let document: Document;
|
||||
|
||||
beforeEach(() => {
|
||||
windowOverride = {
|
||||
getComputedStyle(): any {
|
||||
return {
|
||||
getPropertyValue: () => '0px'
|
||||
} as Pick<CSSStyleDeclaration, 'getPropertyValue'>;
|
||||
}
|
||||
};
|
||||
document = new jsdom.JSDOM('').window.document;
|
||||
});
|
||||
|
||||
it('should return the cell that was clicked', () => {
|
||||
let coords: [number, number] | undefined;
|
||||
coords = getCoords({ clientX: CHAR_WIDTH / 2, clientY: CHAR_HEIGHT / 2 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
|
||||
coords = getCoords(windowOverride, { clientX: CHAR_WIDTH / 2, clientY: CHAR_HEIGHT / 2 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
|
||||
assert.deepEqual(coords, [1, 1]);
|
||||
coords = getCoords({ clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
|
||||
coords = getCoords(windowOverride, { clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
|
||||
assert.deepEqual(coords, [1, 1]);
|
||||
coords = getCoords({ clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT + 1 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
|
||||
coords = getCoords(windowOverride, { clientX: CHAR_WIDTH, clientY: CHAR_HEIGHT + 1 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
|
||||
assert.deepEqual(coords, [1, 2]);
|
||||
coords = getCoords({ clientX: CHAR_WIDTH + 1, clientY: CHAR_HEIGHT }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
|
||||
coords = getCoords(windowOverride, { clientX: CHAR_WIDTH + 1, clientY: CHAR_HEIGHT }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
|
||||
assert.deepEqual(coords, [2, 1]);
|
||||
});
|
||||
|
||||
it('should ensure the coordinates are returned within the terminal bounds', () => {
|
||||
let coords: [number, number] | undefined;
|
||||
coords = getCoords({ clientX: -1, clientY: -1 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
|
||||
coords = getCoords(windowOverride, { clientX: -1, clientY: -1 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
|
||||
assert.deepEqual(coords, [1, 1]);
|
||||
// Event are double the cols/rows
|
||||
coords = getCoords({ clientX: CHAR_WIDTH * 20, clientY: CHAR_HEIGHT * 20 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
|
||||
coords = getCoords(windowOverride, { clientX: CHAR_WIDTH * 20, clientY: CHAR_HEIGHT * 20 }, document.createElement('div'), 10, 10, true, CHAR_WIDTH, CHAR_HEIGHT);
|
||||
assert.deepEqual(coords, [10, 10], 'coordinates should never come back as larger than the terminal');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,9 +3,15 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
export function getCoordsRelativeToElement(event: {clientX: number, clientY: number}, element: HTMLElement): [number, number] {
|
||||
export function getCoordsRelativeToElement(window: Pick<Window, 'getComputedStyle'>, event: {clientX: number, clientY: number}, element: HTMLElement): [number, number] {
|
||||
const rect = element.getBoundingClientRect();
|
||||
return [event.clientX - rect.left, event.clientY - rect.top];
|
||||
const elementStyle = window.getComputedStyle(element);
|
||||
const leftPadding = parseInt(elementStyle.getPropertyValue('padding-left'));
|
||||
const topPadding = parseInt(elementStyle.getPropertyValue('padding-top'));
|
||||
return [
|
||||
event.clientX - rect.left - leftPadding,
|
||||
event.clientY - rect.top - topPadding
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -20,13 +26,13 @@ export function getCoordsRelativeToElement(event: {clientX: number, clientY: num
|
||||
* apply an offset to the x value such that the left half of the cell will
|
||||
* select that cell and the right half will select the next cell.
|
||||
*/
|
||||
export function getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, hasValidCharSize: boolean, actualCellWidth: number, actualCellHeight: number, isSelection?: boolean): [number, number] | undefined {
|
||||
export function getCoords(window: Pick<Window, 'getComputedStyle'>, event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, hasValidCharSize: boolean, actualCellWidth: number, actualCellHeight: number, isSelection?: boolean): [number, number] | undefined {
|
||||
// Coordinates cannot be measured if there are no valid
|
||||
if (!hasValidCharSize) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const coords = getCoordsRelativeToElement(event, element);
|
||||
const coords = getCoordsRelativeToElement(window, event, element);
|
||||
if (!coords) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ export class Terminal implements ITerminalApi {
|
||||
public get onScroll(): IEvent<number> { return this._core.onScroll; }
|
||||
public get onSelectionChange(): IEvent<void> { return this._core.onSelectionChange; }
|
||||
public get onTitleChange(): IEvent<string> { return this._core.onTitleChange; }
|
||||
public get onWriteParsed(): IEvent<void> { return this._core.onWriteParsed; }
|
||||
|
||||
public get element(): HTMLElement | undefined { return this._core.element; }
|
||||
public get parser(): IParser {
|
||||
|
||||
@@ -4,18 +4,18 @@
|
||||
*/
|
||||
|
||||
import { IRenderDimensions, IRenderLayer } from 'browser/renderer/Types';
|
||||
import { ICellData } from 'common/Types';
|
||||
import { ICellData, IColor } from 'common/Types';
|
||||
import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants';
|
||||
import { IGlyphIdentifier } from 'browser/renderer/atlas/Types';
|
||||
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/atlas/Constants';
|
||||
import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas';
|
||||
import { acquireCharAtlas } from 'browser/renderer/atlas/CharAtlasCache';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { IColorSet, IColor } from 'browser/Types';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
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 { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
import { excludeFromContrastRatioDemands, throwIfFalsy } from 'browser/renderer/RendererUtils';
|
||||
import { channels, color, rgba } from 'common/Color';
|
||||
import { removeElementFromParent } from 'browser/Dom';
|
||||
import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs';
|
||||
|
||||
@@ -29,6 +29,10 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
private _scaledCharLeft: number = 0;
|
||||
private _scaledCharTop: number = 0;
|
||||
|
||||
private _selectionStart: [number, number] | undefined;
|
||||
private _selectionEnd: [number, number] | undefined;
|
||||
private _columnSelectMode: boolean = false;
|
||||
|
||||
protected _charAtlas: BaseCharAtlas | undefined;
|
||||
|
||||
/**
|
||||
@@ -52,7 +56,8 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
protected _colors: IColorSet,
|
||||
private _rendererId: number,
|
||||
protected readonly _bufferService: IBufferService,
|
||||
protected readonly _optionsService: IOptionsService
|
||||
protected readonly _optionsService: IOptionsService,
|
||||
protected readonly _decorationService: IDecorationService
|
||||
) {
|
||||
this._canvas = document.createElement('canvas');
|
||||
this._canvas.classList.add(`xterm-${id}-layer`);
|
||||
@@ -79,7 +84,12 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
public onFocus(): void {}
|
||||
public onCursorMove(): void {}
|
||||
public onGridChanged(startRow: number, endRow: number): void {}
|
||||
public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {}
|
||||
|
||||
public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {
|
||||
this._selectionStart = start;
|
||||
this._selectionEnd = end;
|
||||
this._columnSelectMode = columnSelectMode;
|
||||
}
|
||||
|
||||
public setColors(colorSet: IColorSet): void {
|
||||
this._refreshCharAtlas(colorSet);
|
||||
@@ -294,7 +304,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
* @param bold Whether the text is bold.
|
||||
*/
|
||||
protected _drawChars(cell: ICellData, x: number, y: number): void {
|
||||
const contrastColor = this._getContrastColor(cell);
|
||||
const contrastColor = this._getContrastColor(cell, x, y);
|
||||
|
||||
// skip cache right away if we draw in RGB
|
||||
// Note: to avoid bad runtime JoinedCellData will be skipped
|
||||
@@ -325,7 +335,17 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
this._currentGlyphIdentifier.bold = !!cell.isBold();
|
||||
this._currentGlyphIdentifier.dim = !!cell.isDim();
|
||||
this._currentGlyphIdentifier.italic = !!cell.isItalic();
|
||||
const atlasDidDraw = this._charAtlas?.draw(this._ctx, this._currentGlyphIdentifier, x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop);
|
||||
|
||||
// Don't try cache the glyph if it uses any decoration foreground/background override.
|
||||
let hasOverrides = false;
|
||||
for (const d of this._decorationService.getDecorationsAtCell(x, y)) {
|
||||
if (d.backgroundColorRGB || d.foregroundColorRGB) {
|
||||
hasOverrides = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const atlasDidDraw = hasOverrides ? false : this._charAtlas?.draw(this._ctx, this._currentGlyphIdentifier, x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop);
|
||||
|
||||
if (!atlasDidDraw) {
|
||||
this._drawUncachedChars(cell, x, y);
|
||||
@@ -427,15 +447,42 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
return `${fontStyle} ${fontWeight} ${this._optionsService.rawOptions.fontSize * window.devicePixelRatio}px ${this._optionsService.rawOptions.fontFamily}`;
|
||||
}
|
||||
|
||||
private _getContrastColor(cell: CellData): IColor | undefined {
|
||||
if (this._optionsService.rawOptions.minimumContrastRatio === 1) {
|
||||
private _getContrastColor(cell: CellData, x: number, y: number): IColor | undefined {
|
||||
// Get any decoration foreground/background overrides, this must be fetched before the early
|
||||
// exist but applied after inverse
|
||||
let bgOverride: number | undefined;
|
||||
let fgOverride: number | undefined;
|
||||
let isTop = false;
|
||||
for (const d of this._decorationService.getDecorationsAtCell(x, y)) {
|
||||
if (d.options.layer !== 'top' && isTop) {
|
||||
continue;
|
||||
}
|
||||
if (d.backgroundColorRGB) {
|
||||
bgOverride = d.backgroundColorRGB.rgba;
|
||||
}
|
||||
if (d.foregroundColorRGB) {
|
||||
fgOverride = d.foregroundColorRGB.rgba;
|
||||
}
|
||||
isTop = d.options.layer === 'top';
|
||||
}
|
||||
|
||||
// Apply selection foreground if applicable
|
||||
if (!isTop) {
|
||||
if (this._colors.selectionForeground && this._isCellInSelection(x, y)) {
|
||||
fgOverride = this._colors.selectionForeground.rgba;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bgOverride && !fgOverride && (this._optionsService.rawOptions.minimumContrastRatio === 1 || excludeFromContrastRatioDemands(cell.getCode()))) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Try get from cache first
|
||||
const adjustedColor = this._colors.contrastCache.getColor(cell.bg, cell.fg);
|
||||
if (adjustedColor !== undefined) {
|
||||
return adjustedColor || undefined;
|
||||
if (!bgOverride && !fgOverride) {
|
||||
// Try get from cache
|
||||
const adjustedColor = this._colors.contrastCache.getColor(cell.bg, cell.fg);
|
||||
if (adjustedColor !== undefined) {
|
||||
return adjustedColor || undefined;
|
||||
}
|
||||
}
|
||||
|
||||
let fgColor = cell.getFgColor();
|
||||
@@ -453,13 +500,17 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
bgColorMode = temp2;
|
||||
}
|
||||
|
||||
const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, isInverse);
|
||||
const bgRgba = this._resolveBackgroundRgba(bgOverride !== undefined ? Attributes.CM_RGB : bgColorMode, bgOverride ?? bgColor, isInverse);
|
||||
const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, isInverse, isBold);
|
||||
const result = rgba.ensureContrastRatio(bgRgba, fgRgba, this._optionsService.rawOptions.minimumContrastRatio);
|
||||
let result = rgba.ensureContrastRatio(bgOverride ?? bgRgba, fgOverride ?? fgRgba, this._optionsService.rawOptions.minimumContrastRatio);
|
||||
|
||||
if (!result) {
|
||||
this._colors.contrastCache.setColor(cell.bg, cell.fg, null);
|
||||
return undefined;
|
||||
if (!fgOverride) {
|
||||
this._colors.contrastCache.setColor(cell.bg, cell.fg, null);
|
||||
return undefined;
|
||||
}
|
||||
// If it was an override and there was no contrast change, set as the result
|
||||
result = fgOverride;
|
||||
}
|
||||
|
||||
const color: IColor = {
|
||||
@@ -470,7 +521,9 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
),
|
||||
rgba: result
|
||||
};
|
||||
this._colors.contrastCache.setColor(cell.bg, cell.fg, color);
|
||||
if (!bgOverride && !fgOverride) {
|
||||
this._colors.contrastCache.setColor(cell.bg, cell.fg, color);
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
@@ -509,5 +562,21 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
return this._colors.foreground.rgba;
|
||||
}
|
||||
}
|
||||
|
||||
private _isCellInSelection(x: number, y: number): boolean {
|
||||
const start = this._selectionStart;
|
||||
const end = this._selectionEnd;
|
||||
if (!start || !end) {
|
||||
return false;
|
||||
}
|
||||
if (this._columnSelectMode) {
|
||||
return x >= start[0] && y >= start[1] &&
|
||||
x < end[0] && y < end[1];
|
||||
}
|
||||
return (y > start[1] && y < end[1]) ||
|
||||
(start[1] === end[1] && y === start[1] && x >= start[0] && x < end[0]) ||
|
||||
(start[1] < end[1] && y === end[1] && x < end[0]) ||
|
||||
(start[1] < end[1] && y === start[1] && x >= start[0]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer';
|
||||
import { ICellData } from 'common/Types';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services';
|
||||
import { IBufferService, IOptionsService, ICoreService, IDecorationService } from 'common/services/Services';
|
||||
import { IEventEmitter } from 'common/EventEmitter';
|
||||
import { ICoreBrowserService } from 'browser/services/Services';
|
||||
|
||||
@@ -40,9 +40,10 @@ export class CursorRenderLayer extends BaseRenderLayer {
|
||||
@IBufferService bufferService: IBufferService,
|
||||
@IOptionsService optionsService: IOptionsService,
|
||||
@ICoreService private readonly _coreService: ICoreService,
|
||||
@ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService
|
||||
@ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService,
|
||||
@IDecorationService decorationService: IDecorationService
|
||||
) {
|
||||
super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService);
|
||||
super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService);
|
||||
this._state = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
|
||||
@@ -325,6 +325,37 @@ export const boxDrawingDefinitions: { [character: string]: { [fontWeight: number
|
||||
'╰': { [Style.NORMAL]: 'C.5,0,.5,.5,1,.5' }
|
||||
};
|
||||
|
||||
interface IVectorShape {
|
||||
d: string;
|
||||
type: VectorType;
|
||||
/** Padding to apply to the vector's x axis in CSS pixels. */
|
||||
horizontalPadding?: number;
|
||||
}
|
||||
|
||||
const enum VectorType {
|
||||
FILL,
|
||||
STROKE
|
||||
}
|
||||
|
||||
/**
|
||||
* This contains the definitions of the primarily used box drawing characters as vector shapes. The
|
||||
* reason these characters are defined specially is to avoid common problems if a user's font has
|
||||
* not been patched with powerline characters and also to get pixel perfect rendering as rendering
|
||||
* issues can occur around AA/SPAA.
|
||||
*
|
||||
* Original symbols defined in https://github.com/powerline/fontpatcher
|
||||
*/
|
||||
export const powerlineDefinitions: { [index: string]: IVectorShape } = {
|
||||
// Right triangle solid
|
||||
'\u{E0B0}': { d: 'M0,0 L1,.5 L0,1', type: VectorType.FILL },
|
||||
// Right triangle line
|
||||
'\u{E0B1}': { d: 'M0,0 L1,.5 L0,1', type: VectorType.STROKE, horizontalPadding: 0.5 },
|
||||
// Left triangle solid
|
||||
'\u{E0B2}': { d: 'M1,0 L0,.5 L1,1', type: VectorType.FILL },
|
||||
// Left triangle line
|
||||
'\u{E0B3}': { d: 'M1,0 L0,.5 L1,1', type: VectorType.STROKE, horizontalPadding: 0.5 }
|
||||
};
|
||||
|
||||
/**
|
||||
* Try drawing a custom block element or box drawing character, returning whether it was
|
||||
* successfully drawn.
|
||||
@@ -355,6 +386,12 @@ export function tryDrawCustomChar(
|
||||
return true;
|
||||
}
|
||||
|
||||
const powerlineDefinition = powerlineDefinitions[c];
|
||||
if (powerlineDefinition) {
|
||||
drawPowerlineChar(ctx, powerlineDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -414,10 +451,10 @@ function drawPatternChar(
|
||||
let b: number;
|
||||
let a: number;
|
||||
if (fillStyle.startsWith('#')) {
|
||||
r = parseInt(fillStyle.substr(1, 2), 16);
|
||||
g = parseInt(fillStyle.substr(3, 2), 16);
|
||||
b = parseInt(fillStyle.substr(5, 2), 16);
|
||||
a = fillStyle.length > 7 && parseInt(fillStyle.substr(7, 2), 16) || 1;
|
||||
r = parseInt(fillStyle.slice(1, 3), 16);
|
||||
g = parseInt(fillStyle.slice(3, 5), 16);
|
||||
b = parseInt(fillStyle.slice(5, 7), 16);
|
||||
a = fillStyle.length > 7 && parseInt(fillStyle.slice(7, 9), 16) || 1;
|
||||
} else if (fillStyle.startsWith('rgba')) {
|
||||
([r, g, b, a] = fillStyle.substring(5, fillStyle.length - 1).split(',').map(e => parseFloat(e)));
|
||||
} else {
|
||||
@@ -518,6 +555,38 @@ function drawBoxDrawingChar(
|
||||
}
|
||||
}
|
||||
|
||||
function drawPowerlineChar(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
charDefinition: IVectorShape,
|
||||
xOffset: number,
|
||||
yOffset: number,
|
||||
scaledCellWidth: number,
|
||||
scaledCellHeight: number
|
||||
): void {
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = window.devicePixelRatio;
|
||||
for (const instruction of charDefinition.d.split(' ')) {
|
||||
const type = instruction[0];
|
||||
const f = svgToCanvasInstructionMap[type];
|
||||
if (!f) {
|
||||
console.error(`Could not find drawing instructions for "${type}"`);
|
||||
continue;
|
||||
}
|
||||
const args: string[] = instruction.substring(1).split(',');
|
||||
if (!args[0] || !args[1]) {
|
||||
continue;
|
||||
}
|
||||
f(ctx, translateArgs(args, scaledCellWidth, scaledCellHeight, xOffset, yOffset, charDefinition.horizontalPadding));
|
||||
}
|
||||
if (charDefinition.type === VectorType.STROKE) {
|
||||
ctx.strokeStyle = ctx.fillStyle;
|
||||
ctx.stroke();
|
||||
} else {
|
||||
ctx.fill();
|
||||
}
|
||||
ctx.closePath();
|
||||
}
|
||||
|
||||
function clamp(value: number, max: number, min: number = 0): number {
|
||||
return Math.max(Math.min(value, max), min);
|
||||
}
|
||||
@@ -528,7 +597,7 @@ const svgToCanvasInstructionMap: { [index: string]: any } = {
|
||||
'M': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.moveTo(args[0], args[1])
|
||||
};
|
||||
|
||||
function translateArgs(args: string[], cellWidth: number, cellHeight: number, xOffset: number, yOffset: number): number[] {
|
||||
function translateArgs(args: string[], cellWidth: number, cellHeight: number, xOffset: number, yOffset: number, horizontalPadding: number = 0): number[] {
|
||||
const result = args.map(e => parseFloat(e) || parseInt(e));
|
||||
|
||||
if (result.length < 2) {
|
||||
@@ -537,14 +606,14 @@ function translateArgs(args: string[], cellWidth: number, cellHeight: number, xO
|
||||
|
||||
for (let x = 0; x < result.length; x += 2) {
|
||||
// Translate from 0-1 to 0-cellWidth
|
||||
result[x] *= cellWidth;
|
||||
result[x] *= cellWidth - (horizontalPadding * 2 * window.devicePixelRatio);
|
||||
// Ensure coordinate doesn't escape cell bounds and round to the nearest 0.5 to ensure a crisp
|
||||
// line at 100% devicePixelRatio
|
||||
if (result[x] !== 0) {
|
||||
result[x] = clamp(Math.round(result[x] + 0.5) - 0.5, cellWidth, 0);
|
||||
}
|
||||
// Apply the cell's offset (ie. x*cellWidth)
|
||||
result[x] += xOffset;
|
||||
result[x] += xOffset + (horizontalPadding * window.devicePixelRatio);
|
||||
}
|
||||
|
||||
for (let y = 1; y < result.length; y += 2) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
|
||||
import { is256Color } from 'browser/renderer/atlas/CharAtlasUtils';
|
||||
import { IColorSet, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Types';
|
||||
import { IBufferService, IOptionsService } from 'common/services/Services';
|
||||
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
|
||||
export class LinkRenderLayer extends BaseRenderLayer {
|
||||
private _state: ILinkifierEvent | undefined;
|
||||
@@ -21,9 +21,10 @@ export class LinkRenderLayer extends BaseRenderLayer {
|
||||
linkifier: ILinkifier,
|
||||
linkifier2: ILinkifier2,
|
||||
@IBufferService bufferService: IBufferService,
|
||||
@IOptionsService optionsService: IOptionsService
|
||||
@IOptionsService optionsService: IOptionsService,
|
||||
@IDecorationService decorationService: IDecorationService
|
||||
) {
|
||||
super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService);
|
||||
super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService);
|
||||
linkifier.onShowLinkUnderline(e => this._onShowLinkUnderline(e));
|
||||
linkifier.onHideLinkUnderline(e => this._onHideLinkUnderline(e));
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import { ICharSizeService } from 'browser/services/Services';
|
||||
import { IBufferService, IOptionsService, IInstantiationService } from 'common/services/Services';
|
||||
import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache';
|
||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
import { IDecorationOptions, IDecoration } from 'xterm';
|
||||
|
||||
let nextRendererId = 1;
|
||||
|
||||
@@ -120,6 +119,10 @@ export class Renderer extends Disposable implements IRenderer {
|
||||
|
||||
public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {
|
||||
this._runOperation(l => l.onSelectionChanged(start, end, columnSelectMode));
|
||||
// Selection foreground requires a full re-render
|
||||
if (this._colors.selectionForeground) {
|
||||
this._onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 });
|
||||
}
|
||||
}
|
||||
|
||||
public onCursorMove(): void {
|
||||
|
||||
@@ -9,3 +9,18 @@ export function throwIfFalsy<T>(value: T | undefined | null): T {
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function isPowerlineGlyph(codepoint: number): boolean {
|
||||
// Only return true for Powerline symbols which require
|
||||
// different padding and should be excluded from minimum contrast
|
||||
// ratio standards
|
||||
return 0xE0A4 <= codepoint && codepoint <= 0xE0D6;
|
||||
}
|
||||
|
||||
function isBoxOrBlockGlyph(codepoint: number): boolean {
|
||||
return (0x2500 <= codepoint && codepoint <= 0x259F);
|
||||
}
|
||||
|
||||
export function excludeFromContrastRatioDemands(codepoint: number): boolean {
|
||||
return isPowerlineGlyph(codepoint) || isBoxOrBlockGlyph(codepoint);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { IRenderDimensions } from 'browser/renderer/Types';
|
||||
import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { IBufferService, IOptionsService } from 'common/services/Services';
|
||||
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
|
||||
interface ISelectionState {
|
||||
start?: [number, number];
|
||||
@@ -24,9 +24,10 @@ export class SelectionRenderLayer extends BaseRenderLayer {
|
||||
colors: IColorSet,
|
||||
rendererId: number,
|
||||
@IBufferService bufferService: IBufferService,
|
||||
@IOptionsService optionsService: IOptionsService
|
||||
@IOptionsService optionsService: IOptionsService,
|
||||
@IDecorationService decorationService: IDecorationService
|
||||
) {
|
||||
super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService);
|
||||
super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService);
|
||||
this._clearState();
|
||||
}
|
||||
|
||||
@@ -53,6 +54,8 @@ export class SelectionRenderLayer extends BaseRenderLayer {
|
||||
}
|
||||
|
||||
public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
|
||||
super.onSelectionChanged(start, end, columnSelectMode);
|
||||
|
||||
// Selection has not changed
|
||||
if (!this._didStateChange(start, end, columnSelectMode, this._bufferService.buffer.ydisp)) {
|
||||
return;
|
||||
|
||||
@@ -11,7 +11,7 @@ import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { NULL_CELL_CODE, Content } from 'common/buffer/Constants';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { IOptionsService, IBufferService } from 'common/services/Services';
|
||||
import { IOptionsService, IBufferService, IDecorationService } from 'common/services/Services';
|
||||
import { ICharacterJoinerService } from 'browser/services/Services';
|
||||
import { JoinedCellData } from 'browser/services/CharacterJoinerService';
|
||||
|
||||
@@ -37,9 +37,10 @@ export class TextRenderLayer extends BaseRenderLayer {
|
||||
rendererId: number,
|
||||
@IBufferService bufferService: IBufferService,
|
||||
@IOptionsService optionsService: IOptionsService,
|
||||
@ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService
|
||||
@ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService,
|
||||
@IDecorationService decorationService: IDecorationService
|
||||
) {
|
||||
super(container, 'text', zIndex, alpha, colors, rendererId, bufferService, optionsService);
|
||||
super(container, 'text', zIndex, alpha, colors, rendererId, bufferService, optionsService, decorationService);
|
||||
this._state = new GridCache<CharData>();
|
||||
}
|
||||
|
||||
@@ -176,6 +177,19 @@ export class TextRenderLayer extends BaseRenderLayer {
|
||||
nextFillStyle = this._colors.ansi[cell.getBgColor()].css;
|
||||
}
|
||||
|
||||
// Get any decoration foreground/background overrides, this must be fetched before the early
|
||||
// exist but applied after inverse
|
||||
let isTop = false;
|
||||
for (const d of this._decorationService.getDecorationsAtCell(x, this._bufferService.buffer.ydisp + y)) {
|
||||
if (d.options.layer !== 'top' && isTop) {
|
||||
continue;
|
||||
}
|
||||
if (d.backgroundColorRGB) {
|
||||
nextFillStyle = d.backgroundColorRGB.css;
|
||||
}
|
||||
isTop = d.options.layer === 'top';
|
||||
}
|
||||
|
||||
if (prevFillStyle === null) {
|
||||
// This is either the first iteration, or the default background was set. Either way, we
|
||||
// don't need to draw anything.
|
||||
|
||||
@@ -16,7 +16,7 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number
|
||||
cursor: undefined,
|
||||
cursorAccent: undefined,
|
||||
selection: undefined,
|
||||
ansi: [...colors.ansi]
|
||||
ansi: colors.ansi.slice()
|
||||
};
|
||||
return {
|
||||
devicePixelRatio: window.devicePixelRatio,
|
||||
|
||||
@@ -9,9 +9,9 @@ import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas';
|
||||
import { DEFAULT_ANSI_COLORS } from 'browser/ColorManager';
|
||||
import { LRUMap } from 'browser/renderer/atlas/LRUMap';
|
||||
import { isFirefox, isSafari } from 'common/Platform';
|
||||
import { IColor } from 'browser/Types';
|
||||
import { IColor } from 'common/Types';
|
||||
import { throwIfFalsy } from 'browser/renderer/RendererUtils';
|
||||
import { color } from 'browser/Color';
|
||||
import { color } from 'common/Color';
|
||||
|
||||
// In practice we're probably never going to exhaust a texture this large. For debugging purposes,
|
||||
// however, it can be useful to set this to a really tiny value, to verify that LRU eviction works.
|
||||
|
||||
@@ -9,9 +9,9 @@ import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { IColorSet, ILinkifierEvent, ILinkifier, ILinkifier2 } from 'browser/Types';
|
||||
import { ICharSizeService } from 'browser/services/Services';
|
||||
import { IOptionsService, IBufferService, IInstantiationService } from 'common/services/Services';
|
||||
import { IOptionsService, IBufferService, IInstantiationService, IDecorationService } from 'common/services/Services';
|
||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
import { color } from 'browser/Color';
|
||||
import { color } from 'common/Color';
|
||||
import { removeElementFromParent } from 'browser/Dom';
|
||||
|
||||
const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-';
|
||||
@@ -87,11 +87,11 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
this._screenElement.appendChild(this._rowContainer);
|
||||
this._screenElement.appendChild(this._selectionContainer);
|
||||
|
||||
this._linkifier.onShowLinkUnderline(e => this._onLinkHover(e));
|
||||
this._linkifier.onHideLinkUnderline(e => this._onLinkLeave(e));
|
||||
this.register(this._linkifier.onShowLinkUnderline(e => this._onLinkHover(e)));
|
||||
this.register(this._linkifier.onHideLinkUnderline(e => this._onLinkLeave(e)));
|
||||
|
||||
this._linkifier2.onShowLinkUnderline(e => this._onLinkHover(e));
|
||||
this._linkifier2.onHideLinkUnderline(e => this._onLinkLeave(e));
|
||||
this.register(this._linkifier2.onShowLinkUnderline(e => this._onLinkHover(e)));
|
||||
this.register(this._linkifier2.onHideLinkUnderline(e => this._onLinkLeave(e)));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
@@ -226,7 +226,7 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
`}` +
|
||||
`${this._terminalSelector} .${SELECTION_CLASS} div {` +
|
||||
` position: absolute;` +
|
||||
` background-color: ${this._colors.selectionTransparent.css};` +
|
||||
` background-color: ${this._colors.selectionOpaque.css};` +
|
||||
`}`;
|
||||
// Colors
|
||||
this._colors.ansi.forEach((c, i) => {
|
||||
@@ -281,6 +281,9 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
this._selectionContainer.removeChild(this._selectionContainer.children[0]);
|
||||
}
|
||||
|
||||
this._rowFactory.onSelectionChanged(start, end, columnSelectMode);
|
||||
this.renderRows(0, this._bufferService.rows - 1);
|
||||
|
||||
// Selection does not exist
|
||||
if (!start || !end) {
|
||||
return;
|
||||
@@ -301,8 +304,9 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
const documentFragment = document.createDocumentFragment();
|
||||
|
||||
if (columnSelectMode) {
|
||||
const isXFlipped = start[0] > end[0];
|
||||
documentFragment.appendChild(
|
||||
this._createSelectionElement(viewportCappedStartRow, start[0], end[0], viewportCappedEndRow - viewportCappedStartRow + 1)
|
||||
this._createSelectionElement(viewportCappedStartRow, isXFlipped ? end[0] : start[0], isXFlipped ? start[0] : end[0], viewportCappedEndRow - viewportCappedStartRow + 1)
|
||||
);
|
||||
} else {
|
||||
// Draw first row
|
||||
@@ -361,7 +365,6 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
for (let y = start; y <= end; y++) {
|
||||
const rowElement = this._rowElements[y];
|
||||
rowElement.innerText = '';
|
||||
|
||||
const row = y + this._bufferService.buffer.ydisp;
|
||||
const lineData = this._bufferService.buffer.lines.get(row);
|
||||
const cursorStyle = this._optionsService.rawOptions.cursorStyle;
|
||||
|
||||
@@ -10,8 +10,8 @@ import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, DEFAULT_ATTR, FgFlags,
|
||||
import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||
import { IBufferLine } from 'common/Types';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { MockCoreService, MockOptionsService } from 'common/TestUtils.test';
|
||||
import { css } from 'browser/Color';
|
||||
import { MockCoreService, MockDecorationService, MockOptionsService } from 'common/TestUtils.test';
|
||||
import { css } from 'common/Color';
|
||||
import { MockCharacterJoinerService } from 'browser/TestUtils.test';
|
||||
|
||||
describe('DomRendererRowFactory', () => {
|
||||
@@ -49,7 +49,8 @@ describe('DomRendererRowFactory', () => {
|
||||
} as any,
|
||||
new MockCharacterJoinerService(),
|
||||
new MockOptionsService({ drawBoldTextInBrightColors: true }),
|
||||
new MockCoreService()
|
||||
new MockCoreService(),
|
||||
new MockDecorationService()
|
||||
);
|
||||
lineData = createEmptyLineData(2);
|
||||
});
|
||||
@@ -183,7 +184,7 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.setCell(0, cell);
|
||||
const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
'<span class="xterm-fg-1 xterm-bg-2">a</span>'
|
||||
'<span class="xterm-bg-2 xterm-fg-1">a</span>'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -194,7 +195,7 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.setCell(0, cell);
|
||||
const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
'<span class="xterm-fg-1 xterm-bg-257">a</span>'
|
||||
'<span class="xterm-bg-257 xterm-fg-1">a</span>'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -204,7 +205,7 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.setCell(0, cell);
|
||||
const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
'<span class="xterm-fg-257 xterm-bg-1">a</span>'
|
||||
'<span class="xterm-bg-1 xterm-fg-257">a</span>'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -229,7 +230,7 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.setCell(0, cell);
|
||||
const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
'<span style="color:#010203;background-color:#040506;">a</span>'
|
||||
'<span style="background-color:#040506;color:#010203;">a</span>'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -240,7 +241,27 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.setCell(0, cell);
|
||||
const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
'<span style="color:#040506;background-color:#010203;">a</span>'
|
||||
'<span style="background-color:#010203;color:#040506;">a</span>'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('selectionForeground', () => {
|
||||
it('should force selected cells with content to be rendered above the background', () => {
|
||||
lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)]));
|
||||
lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)]));
|
||||
rowFactory.onSelectionChanged([1, 0], [2, 0], false);
|
||||
const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
'<span>a</span><span class="xterm-decoration-top">b</span>'
|
||||
);
|
||||
});
|
||||
it('should force whitespace cells to be rendered above the background', () => {
|
||||
lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)]));
|
||||
rowFactory.onSelectionChanged([0, 0], [2, 0], false);
|
||||
const fragment = rowFactory.createRow(lineData, 0, false, undefined, 0, false, 5, 20);
|
||||
assert.equal(getFragmentHtml(fragment),
|
||||
'<span class="xterm-decoration-top"> </span><span class="xterm-decoration-top">a</span>'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,15 +3,16 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IBufferLine } from 'common/Types';
|
||||
import { IBufferLine, ICellData, IColor } from 'common/Types';
|
||||
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
|
||||
import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { ICoreService, IOptionsService } from 'common/services/Services';
|
||||
import { color, rgba } from 'browser/Color';
|
||||
import { IColorSet, IColor } from 'browser/Types';
|
||||
import { ICharacterJoinerService } from 'browser/services/Services';
|
||||
import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
import { color, rgba } from 'common/Color';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { ICharacterJoinerService, ISelectionService } from 'browser/services/Services';
|
||||
import { JoinedCellData } from 'browser/services/CharacterJoinerService';
|
||||
import { excludeFromContrastRatioDemands } from 'browser/renderer/RendererUtils';
|
||||
|
||||
export const BOLD_CLASS = 'xterm-bold';
|
||||
export const DIM_CLASS = 'xterm-dim';
|
||||
@@ -27,12 +28,17 @@ export const CURSOR_STYLE_UNDERLINE_CLASS = 'xterm-cursor-underline';
|
||||
export class DomRendererRowFactory {
|
||||
private _workCell: CellData = new CellData();
|
||||
|
||||
private _selectionStart: [number, number] | undefined;
|
||||
private _selectionEnd: [number, number] | undefined;
|
||||
private _columnSelectMode: boolean = false;
|
||||
|
||||
constructor(
|
||||
private readonly _document: Document,
|
||||
private _colors: IColorSet,
|
||||
@ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService,
|
||||
@IOptionsService private readonly _optionsService: IOptionsService,
|
||||
@ICoreService private readonly _coreService: ICoreService
|
||||
@ICoreService private readonly _coreService: ICoreService,
|
||||
@IDecorationService private readonly _decorationService: IDecorationService
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -40,6 +46,12 @@ export class DomRendererRowFactory {
|
||||
this._colors = colors;
|
||||
}
|
||||
|
||||
public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
|
||||
this._selectionStart = start;
|
||||
this._selectionEnd = end;
|
||||
this._columnSelectMode = columnSelectMode;
|
||||
}
|
||||
|
||||
public createRow(lineData: IBufferLine, row: number, isCursorRow: boolean, cursorStyle: string | undefined, cursorX: number, cursorBlink: boolean, cellWidth: number, cols: number): DocumentFragment {
|
||||
const fragment = this._document.createDocumentFragment();
|
||||
|
||||
@@ -171,6 +183,72 @@ export class DomRendererRowFactory {
|
||||
bgColorMode = temp2;
|
||||
}
|
||||
|
||||
// Apply any decoration foreground/background overrides, this must happen after inverse has
|
||||
// been applied
|
||||
let bgOverride: IColor | undefined;
|
||||
let fgOverride: IColor | undefined;
|
||||
let isTop = false;
|
||||
for (const d of this._decorationService.getDecorationsAtCell(x, row)) {
|
||||
if (d.options.layer !== 'top' && isTop) {
|
||||
continue;
|
||||
}
|
||||
if (d.backgroundColorRGB) {
|
||||
bgColorMode = Attributes.CM_RGB;
|
||||
bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF;
|
||||
bgOverride = d.backgroundColorRGB;
|
||||
}
|
||||
if (d.foregroundColorRGB) {
|
||||
fgColorMode = Attributes.CM_RGB;
|
||||
fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF;
|
||||
fgOverride = d.foregroundColorRGB;
|
||||
}
|
||||
isTop = d.options.layer === 'top';
|
||||
}
|
||||
|
||||
// Apply selection foreground if applicable
|
||||
const isInSelection = this._isCellInSelection(x, row);
|
||||
if (!isTop) {
|
||||
if (this._colors.selectionForeground && isInSelection) {
|
||||
fgColorMode = Attributes.CM_RGB;
|
||||
fg = this._colors.selectionForeground.rgba >> 8 & 0xFFFFFF;
|
||||
fgOverride = this._colors.selectionForeground;
|
||||
}
|
||||
}
|
||||
|
||||
// If in the selection, force the element to be above the selection to improve contrast and
|
||||
// support opaque selections
|
||||
if (isInSelection) {
|
||||
bgOverride = this._colors.selectionOpaque;
|
||||
isTop = true;
|
||||
}
|
||||
|
||||
// If it's a top decoration, render above the selection
|
||||
if (isTop) {
|
||||
charElement.classList.add(`xterm-decoration-top`);
|
||||
}
|
||||
|
||||
// Background
|
||||
let resolvedBg: IColor;
|
||||
switch (bgColorMode) {
|
||||
case Attributes.CM_P16:
|
||||
case Attributes.CM_P256:
|
||||
resolvedBg = this._colors.ansi[bg];
|
||||
charElement.classList.add(`xterm-bg-${bg}`);
|
||||
break;
|
||||
case Attributes.CM_RGB:
|
||||
resolvedBg = rgba.toColor(bg >> 16, bg >> 8 & 0xFF, bg & 0xFF);
|
||||
this._addStyle(charElement, `background-color:#${padStart((bg >>> 0).toString(16), '0', 6)}`);
|
||||
break;
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
if (isInverse) {
|
||||
resolvedBg = this._colors.foreground;
|
||||
charElement.classList.add(`xterm-bg-${INVERTED_DEFAULT_COLOR}`);
|
||||
} else {
|
||||
resolvedBg = this._colors.background;
|
||||
}
|
||||
}
|
||||
|
||||
// Foreground
|
||||
switch (fgColorMode) {
|
||||
case Attributes.CM_P16:
|
||||
@@ -178,7 +256,7 @@ export class DomRendererRowFactory {
|
||||
if (cell.isBold() && fg < 8 && this._optionsService.rawOptions.drawBoldTextInBrightColors) {
|
||||
fg += 8;
|
||||
}
|
||||
if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.ansi[fg])) {
|
||||
if (!this._applyMinimumContrast(charElement, resolvedBg, this._colors.ansi[fg], cell, bgOverride, undefined)) {
|
||||
charElement.classList.add(`xterm-fg-${fg}`);
|
||||
}
|
||||
break;
|
||||
@@ -188,35 +266,19 @@ export class DomRendererRowFactory {
|
||||
(fg >> 8) & 0xFF,
|
||||
(fg ) & 0xFF
|
||||
);
|
||||
if (!this._applyMinimumContrast(charElement, this._colors.background, color)) {
|
||||
if (!this._applyMinimumContrast(charElement, resolvedBg, color, cell, bgOverride, fgOverride)) {
|
||||
this._addStyle(charElement, `color:#${padStart(fg.toString(16), '0', 6)}`);
|
||||
}
|
||||
break;
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.foreground)) {
|
||||
if (!this._applyMinimumContrast(charElement, resolvedBg, this._colors.foreground, cell, bgOverride, undefined)) {
|
||||
if (isInverse) {
|
||||
charElement.classList.add(`xterm-fg-${INVERTED_DEFAULT_COLOR}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Background
|
||||
switch (bgColorMode) {
|
||||
case Attributes.CM_P16:
|
||||
case Attributes.CM_P256:
|
||||
charElement.classList.add(`xterm-bg-${bg}`);
|
||||
break;
|
||||
case Attributes.CM_RGB:
|
||||
this._addStyle(charElement, `background-color:#${padStart(bg.toString(16), '0', 6)}`);
|
||||
break;
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
if (isInverse) {
|
||||
charElement.classList.add(`xterm-bg-${INVERTED_DEFAULT_COLOR}`);
|
||||
}
|
||||
}
|
||||
|
||||
fragment.appendChild(charElement);
|
||||
|
||||
x = lastCharX;
|
||||
@@ -224,18 +286,21 @@ export class DomRendererRowFactory {
|
||||
return fragment;
|
||||
}
|
||||
|
||||
private _applyMinimumContrast(element: HTMLElement, bg: IColor, fg: IColor): boolean {
|
||||
if (this._optionsService.rawOptions.minimumContrastRatio === 1) {
|
||||
private _applyMinimumContrast(element: HTMLElement, bg: IColor, fg: IColor, cell: ICellData, bgOverride: IColor | undefined, fgOverride: IColor | undefined): boolean {
|
||||
if (this._optionsService.rawOptions.minimumContrastRatio === 1 || excludeFromContrastRatioDemands(cell.getCode())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try get from cache first
|
||||
let adjustedColor = this._colors.contrastCache.getColor(this._workCell.bg, this._workCell.fg);
|
||||
// Try get from cache first, only use the cache when there are no decoration overrides
|
||||
let adjustedColor: IColor | undefined | null = undefined;
|
||||
if (!bgOverride && !fgOverride) {
|
||||
adjustedColor = this._colors.contrastCache.getColor(bg.rgba, fg.rgba);
|
||||
}
|
||||
|
||||
// Calculate and store in cache
|
||||
if (adjustedColor === undefined) {
|
||||
adjustedColor = color.ensureContrastRatio(bg, fg, this._optionsService.rawOptions.minimumContrastRatio);
|
||||
this._colors.contrastCache.setColor(this._workCell.bg, this._workCell.fg, adjustedColor ?? null);
|
||||
adjustedColor = color.ensureContrastRatio(bgOverride || bg, fgOverride || fg, this._optionsService.rawOptions.minimumContrastRatio);
|
||||
this._colors.contrastCache.setColor((bgOverride || bg).rgba, (fgOverride || fg).rgba, adjustedColor ?? null);
|
||||
}
|
||||
|
||||
if (adjustedColor) {
|
||||
@@ -249,6 +314,26 @@ export class DomRendererRowFactory {
|
||||
private _addStyle(element: HTMLElement, style: string): void {
|
||||
element.setAttribute('style', `${element.getAttribute('style') || ''}${style};`);
|
||||
}
|
||||
|
||||
private _isCellInSelection(x: number, y: number): boolean {
|
||||
const start = this._selectionStart;
|
||||
const end = this._selectionEnd;
|
||||
if (!start || !end) {
|
||||
return false;
|
||||
}
|
||||
if (this._columnSelectMode) {
|
||||
if (start[0] <= end[0]) {
|
||||
return x >= start[0] && y >= start[1] &&
|
||||
x < end[0] && y <= end[1];
|
||||
}
|
||||
return x < start[0] && y >= start[1] &&
|
||||
x >= end[0] && y <= end[1];
|
||||
}
|
||||
return (y > start[1] && y < end[1]) ||
|
||||
(start[1] === end[1] && y === start[1] && x >= start[0] && x < end[0]) ||
|
||||
(start[1] < end[1] && y === end[1] && x < end[0]) ||
|
||||
(start[1] < end[1] && y === start[1] && x >= start[0]);
|
||||
}
|
||||
}
|
||||
|
||||
function padStart(text: string, padChar: string, length: number): string {
|
||||
|
||||
@@ -116,6 +116,12 @@ describe('SelectionModel', () => {
|
||||
model.selectionStartLength = 4;
|
||||
assert.deepEqual(model.finalSelectionEnd, [2, 3]);
|
||||
});
|
||||
it('should return the end on a different row when start + length overflows onto a following row with selectionEnd inbetween', () => {
|
||||
model.selectionStart = [78, 2];
|
||||
model.selectionEnd = [79, 2];
|
||||
model.selectionStartLength = 4;
|
||||
assert.deepEqual(model.finalSelectionEnd, [2, 3]);
|
||||
});
|
||||
it('should return selection end if selection end is after selection start + length', () => {
|
||||
model.selectionStart = [2, 2];
|
||||
model.selectionStartLength = 2;
|
||||
|
||||
@@ -92,7 +92,12 @@ export class SelectionModel {
|
||||
if (this.selectionStartLength) {
|
||||
// Select the larger of the two when start and end are on the same line
|
||||
if (this.selectionEnd[1] === this.selectionStart[1]) {
|
||||
return [Math.max(this.selectionStart[0] + this.selectionStartLength, this.selectionEnd[0]), this.selectionEnd[1]];
|
||||
// Keep the whole wrapped word/line selected if the content wraps multiple lines
|
||||
const startPlusLength = this.selectionStart[0] + this.selectionStartLength;
|
||||
if (startPlusLength > this._bufferService.cols) {
|
||||
return [startPlusLength % this._bufferService.cols, this.selectionStart[1] + Math.floor(startPlusLength / this._bufferService.cols)];
|
||||
}
|
||||
return [Math.max(startPlusLength, this.selectionEnd[0]), this.selectionEnd[1]];
|
||||
}
|
||||
}
|
||||
return this.selectionEnd;
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2022 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IDecorationService, IRenderService } from 'browser/services/Services';
|
||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { IBufferService, IInstantiationService } from 'common/services/Services';
|
||||
import { IDecorationOptions, IDecoration, IMarker } from 'xterm';
|
||||
|
||||
export class DecorationService extends Disposable implements IDecorationService {
|
||||
|
||||
private readonly _decorations: Decoration[] = [];
|
||||
private _container: HTMLElement | undefined;
|
||||
private _screenElement: HTMLElement | undefined;
|
||||
private _renderService: IRenderService | undefined;
|
||||
private _animationFrame: number | undefined;
|
||||
|
||||
constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService) { super(); }
|
||||
|
||||
public attachToDom(screenElement: HTMLElement, renderService: IRenderService): void {
|
||||
this._renderService = renderService;
|
||||
this._screenElement = screenElement;
|
||||
this._container = document.createElement('div');
|
||||
this._container.classList.add('xterm-decoration-container');
|
||||
screenElement.appendChild(this._container);
|
||||
this.register(this._renderService.onRenderedBufferChange(() => this.refresh()));
|
||||
this.register(this._renderService.onDimensionsChange(() => this.refresh(true)));
|
||||
}
|
||||
|
||||
public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined {
|
||||
if (decorationOptions.marker.isDisposed || !this._container) {
|
||||
return undefined;
|
||||
}
|
||||
const decoration = this._instantiationService.createInstance(Decoration, decorationOptions, this._container);
|
||||
this._decorations.push(decoration);
|
||||
decoration.onDispose(() => this._decorations.splice(this._decorations.indexOf(decoration), 1));
|
||||
this._queueRefresh();
|
||||
return decoration;
|
||||
}
|
||||
|
||||
private _queueRefresh(): void {
|
||||
if (this._animationFrame !== undefined) {
|
||||
return;
|
||||
}
|
||||
this._animationFrame = window.requestAnimationFrame(() => {
|
||||
this.refresh();
|
||||
this._animationFrame = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
public refresh(shouldRecreate?: boolean): void {
|
||||
if (!this._renderService) {
|
||||
return;
|
||||
}
|
||||
for (const decoration of this._decorations) {
|
||||
decoration.render(this._renderService, shouldRecreate);
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
for (const decoration of this._decorations) {
|
||||
decoration.dispose();
|
||||
}
|
||||
if (this._screenElement && this._container && this._screenElement.contains(this._container)) {
|
||||
this._screenElement.removeChild(this._container);
|
||||
}
|
||||
}
|
||||
}
|
||||
export class Decoration extends Disposable implements IDecoration {
|
||||
private readonly _marker: IMarker;
|
||||
private _element: HTMLElement | undefined;
|
||||
|
||||
public isDisposed: boolean = false;
|
||||
|
||||
public get element(): HTMLElement | undefined { return this._element; }
|
||||
public get marker(): IMarker { return this._marker; }
|
||||
|
||||
private _onDispose = new EventEmitter<void>();
|
||||
public get onDispose(): IEvent<void> { return this._onDispose.event; }
|
||||
|
||||
private _onRender = new EventEmitter<HTMLElement>();
|
||||
public get onRender(): IEvent<HTMLElement> { return this._onRender.event; }
|
||||
|
||||
public x: number;
|
||||
public anchor: 'left' | 'right';
|
||||
public width: number;
|
||||
public height: number;
|
||||
|
||||
constructor(
|
||||
options: IDecorationOptions,
|
||||
private readonly _container: HTMLElement,
|
||||
@IBufferService private readonly _bufferService: IBufferService
|
||||
) {
|
||||
super();
|
||||
this.x = options.x ?? 0;
|
||||
this._marker = options.marker;
|
||||
this._marker.onDispose(() => this.dispose());
|
||||
this.anchor = options.anchor || 'left';
|
||||
this.width = options.width || 1;
|
||||
this.height = options.height || 1;
|
||||
}
|
||||
|
||||
public render(renderService: IRenderService, shouldRecreate?: boolean): void {
|
||||
if (!this._element || shouldRecreate) {
|
||||
this._createElement(renderService, shouldRecreate);
|
||||
}
|
||||
if (this._container && this._element && !this._container.contains(this._element)) {
|
||||
this._container.append(this._element);
|
||||
}
|
||||
this._refreshStyle(renderService);
|
||||
if (this._element) {
|
||||
this._onRender.fire(this._element);
|
||||
}
|
||||
}
|
||||
|
||||
private _createElement(renderService: IRenderService, shouldRecreate?: boolean): void {
|
||||
if (shouldRecreate && this._element && this._container.contains(this._element)) {
|
||||
this._container.removeChild(this._element);
|
||||
}
|
||||
this._element = document.createElement('div');
|
||||
this._element.classList.add('xterm-decoration');
|
||||
this._element.style.width = `${this.width * renderService.dimensions.actualCellWidth}px`;
|
||||
this._element.style.height = `${this.height * renderService.dimensions.actualCellHeight}px`;
|
||||
this._element.style.top = `${(this.marker.line - this._bufferService.buffers.active.ydisp) * renderService.dimensions.actualCellHeight}px`;
|
||||
this._element.style.lineHeight = `${renderService.dimensions.actualCellHeight}px`;
|
||||
|
||||
if (this.x && this.x > this._bufferService.cols) {
|
||||
// exceeded the container width, so hide
|
||||
this._element.style.display = 'none';
|
||||
}
|
||||
if (this.anchor === 'right') {
|
||||
this._element.style.right = this.x ? `${this.x * renderService.dimensions.actualCellWidth}px` : '';
|
||||
} else {
|
||||
this._element.style.left = this.x ? `${this.x * renderService.dimensions.actualCellWidth}px` : '';
|
||||
}
|
||||
}
|
||||
|
||||
private _refreshStyle(renderService: IRenderService): void {
|
||||
if (!this._element) {
|
||||
return;
|
||||
}
|
||||
const line = this.marker.line - this._bufferService.buffers.active.ydisp;
|
||||
if (line < 0 || line > this._bufferService.rows) {
|
||||
// outside of viewport
|
||||
this._element.style.display = 'none';
|
||||
} else {
|
||||
this._element.style.top = `${line * renderService.dimensions.actualCellHeight}px`;
|
||||
this._element.style.display = this._bufferService.buffer === this._bufferService.buffers.alt ? 'none' : 'block';
|
||||
}
|
||||
}
|
||||
|
||||
public override dispose(): void {
|
||||
if (this.isDisposed) {
|
||||
return;
|
||||
}
|
||||
if (this._element && this._container.contains(this._element)) {
|
||||
this._container.removeChild(this._element);
|
||||
}
|
||||
this.isDisposed = true;
|
||||
this._onDispose.fire();
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ export class MouseService implements IMouseService {
|
||||
|
||||
public getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined {
|
||||
return getCoords(
|
||||
window,
|
||||
event,
|
||||
element,
|
||||
colCount,
|
||||
|
||||
@@ -9,8 +9,8 @@ import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { ScreenDprMonitor } from 'browser/ScreenDprMonitor';
|
||||
import { addDisposableDomListener } from 'browser/Lifecycle';
|
||||
import { IColorSet, IRenderDebouncer } from 'browser/Types';
|
||||
import { IOptionsService, IBufferService } from 'common/services/Services';
|
||||
import { IColorSet, IRenderDebouncer, IRenderDebouncerWithCallback } from 'browser/Types';
|
||||
import { IOptionsService, IBufferService, IDecorationService } from 'common/services/Services';
|
||||
import { ICharSizeService, IRenderService } from 'browser/services/Services';
|
||||
|
||||
interface ISelectionState {
|
||||
@@ -22,7 +22,7 @@ interface ISelectionState {
|
||||
export class RenderService extends Disposable implements IRenderService {
|
||||
public serviceBrand: undefined;
|
||||
|
||||
private _renderDebouncer: IRenderDebouncer;
|
||||
private _renderDebouncer: IRenderDebouncerWithCallback;
|
||||
private _screenDprMonitor: ScreenDprMonitor;
|
||||
|
||||
private _isPaused: boolean = false;
|
||||
@@ -39,8 +39,10 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
|
||||
private _onDimensionsChange = new EventEmitter<IRenderDimensions>();
|
||||
public get onDimensionsChange(): IEvent<IRenderDimensions> { return this._onDimensionsChange.event; }
|
||||
private _onRenderedViewportChange = new EventEmitter<{ start: number, end: number }>();
|
||||
public get onRenderedViewportChange(): IEvent<{ start: number, end: number }> { return this._onRenderedViewportChange.event; }
|
||||
private _onRender = new EventEmitter<{ start: number, end: number }>();
|
||||
public get onRenderedBufferChange(): IEvent<{ start: number, end: number }> { return this._onRender.event; }
|
||||
public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; }
|
||||
private _onRefreshRequest = new EventEmitter<{ start: number, end: number }>();
|
||||
public get onRefreshRequest(): IEvent<{ start: number, end: number }> { return this._onRefreshRequest.event; }
|
||||
|
||||
@@ -52,6 +54,7 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
screenElement: HTMLElement,
|
||||
@IOptionsService optionsService: IOptionsService,
|
||||
@ICharSizeService private readonly _charSizeService: ICharSizeService,
|
||||
@IDecorationService decorationService: IDecorationService,
|
||||
@IBufferService bufferService: IBufferService
|
||||
) {
|
||||
super();
|
||||
@@ -67,9 +70,15 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
|
||||
this.register(bufferService.onResize(() => this._fullRefresh()));
|
||||
this.register(bufferService.buffers.onBufferActivate(() => this._renderer?.clear()));
|
||||
this.register(optionsService.onOptionChange(() => this._renderer.onOptionsChanged()));
|
||||
this.register(optionsService.onOptionChange(() => this._handleOptionsChanged()));
|
||||
this.register(this._charSizeService.onCharSizeChange(() => this.onCharSizeChanged()));
|
||||
|
||||
// Do a full refresh whenever any decoration is added or removed. This may not actually result
|
||||
// in changes but since decorations should be used sparingly or added/removed all in the same
|
||||
// frame this should have minimal performance impact.
|
||||
this.register(decorationService.onDecorationRegistered(() => this._fullRefresh()));
|
||||
this.register(decorationService.onDecorationRemoved(() => this._fullRefresh()));
|
||||
|
||||
// No need to register this as renderer is explicitly disposed in RenderService.dispose
|
||||
this._renderer.onRequestRedraw(e => this.refreshRows(e.start, e.end, true));
|
||||
|
||||
@@ -122,8 +131,9 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
|
||||
// Fire render event only if it was not a redraw
|
||||
if (!this._isNextRenderRedrawOnly) {
|
||||
this._onRender.fire({ start, end });
|
||||
this._onRenderedViewportChange.fire({ start, end });
|
||||
}
|
||||
this._onRender.fire({ start, end });
|
||||
this._isNextRenderRedrawOnly = true;
|
||||
}
|
||||
|
||||
@@ -132,7 +142,7 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
this._fireOnCanvasResize();
|
||||
}
|
||||
|
||||
public changeOptions(): void {
|
||||
private _handleOptionsChanged(): void {
|
||||
this._renderer.onOptionsChanged();
|
||||
this.refreshRows(0, this._rowCount - 1);
|
||||
this._fireOnCanvasResize();
|
||||
@@ -161,6 +171,10 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
this._fullRefresh();
|
||||
}
|
||||
|
||||
public addRefreshCallback(callback: FrameRequestCallback): number {
|
||||
return this._renderDebouncer.addRefreshCallback(callback);
|
||||
}
|
||||
|
||||
private _fullRefresh(): void {
|
||||
if (this._isPaused) {
|
||||
this._needsFullRefresh = true;
|
||||
|
||||
@@ -340,6 +340,9 @@ describe('SelectionService', () => {
|
||||
buffer.lines.set(0, stringToRow('foo bar'));
|
||||
selectionService.selectLineAt(0);
|
||||
assert.equal(selectionService.selectionText, 'foo bar', 'The selected text is correct');
|
||||
assert.deepEqual(selectionService.model.selectionStart, [0, 0]);
|
||||
assert.deepEqual(selectionService.model.selectionEnd, undefined);
|
||||
assert.deepEqual(selectionService.model.selectionStartLength, 20);
|
||||
assert.deepEqual(selectionService.model.finalSelectionStart, [0, 0]);
|
||||
assert.deepEqual(selectionService.model.finalSelectionEnd, [bufferService.cols, 0], 'The actual selection spans the entire column');
|
||||
});
|
||||
@@ -350,6 +353,9 @@ describe('SelectionService', () => {
|
||||
buffer.lines.set(1, line2);
|
||||
selectionService.selectLineAt(0);
|
||||
assert.equal(selectionService.selectionText, 'foobar', 'The selected text is correct');
|
||||
assert.deepEqual(selectionService.model.selectionStart, [0, 0]);
|
||||
assert.deepEqual(selectionService.model.selectionEnd, undefined);
|
||||
assert.deepEqual(selectionService.model.selectionStartLength, 40);
|
||||
assert.deepEqual(selectionService.model.finalSelectionStart, [0, 0]);
|
||||
assert.deepEqual(selectionService.model.finalSelectionEnd, [bufferService.cols, 1], 'The actual selection spans the entire column');
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ import { SelectionModel } from 'browser/selection/SelectionModel';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
import { IMouseService, ISelectionService, IRenderService } from 'browser/services/Services';
|
||||
import { ILinkifier2 } from 'browser/Types';
|
||||
import { IBufferRange, ILinkifier2 } from 'browser/Types';
|
||||
import { IBufferService, IOptionsService, ICoreService } from 'common/services/Services';
|
||||
import { getCoordsRelativeToElement } from 'browser/input/Mouse';
|
||||
import { moveToCellSequence } from 'browser/input/MoveToCell';
|
||||
@@ -207,8 +207,12 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
return '';
|
||||
}
|
||||
|
||||
// For column selection it's not enough to rely on final selection's swapping of reversed
|
||||
// values, it also needs the x coordinates to swap independently of the y coordinate is needed
|
||||
const startCol = start[0] < end[0] ? start[0] : end[0];
|
||||
const endCol = start[0] < end[0] ? end[0] : start[0];
|
||||
for (let i = start[1]; i <= end[1]; i++) {
|
||||
const lineText = buffer.translateBufferLineToString(i, true, start[0], end[0]);
|
||||
const lineText = buffer.translateBufferLineToString(i, true, startCol, endCol);
|
||||
result.push(lineText);
|
||||
}
|
||||
} else {
|
||||
@@ -308,6 +312,15 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
return this._areCoordsInSelection(coords, start, end);
|
||||
}
|
||||
|
||||
public isCellInSelection(x: number, y: number): boolean {
|
||||
const start = this._model.finalSelectionStart;
|
||||
const end = this._model.finalSelectionEnd;
|
||||
if (!start || !end) {
|
||||
return false;
|
||||
}
|
||||
return this._areCoordsInSelection([x, y], start, end);
|
||||
}
|
||||
|
||||
protected _areCoordsInSelection(coords: [number, number], start: [number, number], end: [number, number]): boolean {
|
||||
return (coords[1] > start[1] && coords[1] < end[1]) ||
|
||||
(start[1] === end[1] && coords[1] === start[1] && coords[0] >= start[0] && coords[0] < end[0]) ||
|
||||
@@ -393,7 +406,7 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
* @param event The mouse event.
|
||||
*/
|
||||
private _getMouseEventScrollAmount(event: MouseEvent): number {
|
||||
let offset = getCoordsRelativeToElement(event, this._screenElement)[1];
|
||||
let offset = getCoordsRelativeToElement(window, event, this._screenElement)[1];
|
||||
const terminalHeight = this._renderService.dimensions.canvasHeight;
|
||||
if (offset >= 0 && offset <= terminalHeight) {
|
||||
return 0;
|
||||
@@ -771,6 +784,7 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
this._model.selectionStart = [col, row];
|
||||
this._model.selectionStartLength = length;
|
||||
this.refresh();
|
||||
this._fireEventIfSelectionChanged();
|
||||
}
|
||||
|
||||
public rightClickSelect(ev: MouseEvent): void {
|
||||
@@ -1002,8 +1016,12 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
*/
|
||||
protected _selectLineAt(line: number): void {
|
||||
const wrappedRange = this._bufferService.buffer.getWrappedRangeForLine(line);
|
||||
const range: IBufferRange = {
|
||||
start: { x: 0, y: wrappedRange.first },
|
||||
end: { x: this._bufferService.cols - 1, y: wrappedRange.last }
|
||||
};
|
||||
this._model.selectionStart = [0, wrappedRange.first];
|
||||
this._model.selectionEnd = [this._bufferService.cols, wrappedRange.last];
|
||||
this._model.selectionStartLength = 0;
|
||||
this._model.selectionEnd = undefined;
|
||||
this._model.selectionStartLength = getRangeLength(range, this._bufferService.cols);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { IEvent } from 'common/EventEmitter';
|
||||
import { IRenderDimensions, IRenderer } from 'browser/renderer/Types';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { IColorSet, IRenderDebouncer } from 'browser/Types';
|
||||
import { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types';
|
||||
import { createDecorator } from 'common/services/ServiceRegistry';
|
||||
import { IDisposable } from 'common/Types';
|
||||
@@ -49,15 +49,20 @@ export interface IRenderService extends IDisposable {
|
||||
* Fires when buffer changes are rendered. This does not fire when only cursor
|
||||
* or selections are rendered.
|
||||
*/
|
||||
onRenderedBufferChange: IEvent<{ start: number, end: number }>;
|
||||
onRenderedViewportChange: IEvent<{ start: number, end: number }>;
|
||||
/**
|
||||
* Fires on render
|
||||
*/
|
||||
onRender: IEvent<{ start: number, end: number }>;
|
||||
onRefreshRequest: IEvent<{ start: number, end: number }>;
|
||||
|
||||
dimensions: IRenderDimensions;
|
||||
|
||||
addRefreshCallback(callback: FrameRequestCallback): number;
|
||||
|
||||
refreshRows(start: number, end: number): void;
|
||||
clearTextureAtlas(): void;
|
||||
resize(cols: number, rows: number): void;
|
||||
changeOptions(): void;
|
||||
setRenderer(renderer: IRenderer): void;
|
||||
setColors(colors: IColorSet): void;
|
||||
onDevicePixelRatioChange(): void;
|
||||
@@ -97,6 +102,7 @@ export interface ISelectionService {
|
||||
shouldForceSelection(event: MouseEvent): boolean;
|
||||
refresh(isLinuxMouseSelection?: boolean): void;
|
||||
onMouseDown(event: MouseEvent): void;
|
||||
isCellInSelection(x: number, y: number): boolean;
|
||||
}
|
||||
|
||||
export const ISoundService = createDecorator<ISoundService>('SoundService');
|
||||
@@ -115,11 +121,3 @@ export interface ICharacterJoinerService {
|
||||
deregister(joinerId: number): boolean;
|
||||
getJoinedCharacters(row: number): [number, number][];
|
||||
}
|
||||
|
||||
|
||||
export const IDecorationService = createDecorator<IDecorationService>('DecorationService');
|
||||
export interface IDecorationService extends IDisposable {
|
||||
registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined;
|
||||
refresh(): void;
|
||||
attachToDom(screenElement: HTMLElement, renderService: IRenderService, bufferService: IBufferService): void;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { channels, color, css, rgb, rgba, toPaddedHex, contrastRatio } from 'browser/Color';
|
||||
import { channels, color, css, rgb, rgba, toPaddedHex, contrastRatio } from 'common/Color';
|
||||
|
||||
describe('Color', () => {
|
||||
|
||||
@@ -153,6 +153,44 @@ describe('Color', () => {
|
||||
|
||||
describe('css', () => {
|
||||
describe('toColor', () => {
|
||||
it('should convert the #rgb format to an IColor', () => {
|
||||
assert.deepEqual(css.toColor('#000'), { css: '#000000', rgba: 0x000000FF });
|
||||
assert.deepEqual(css.toColor('#111'), { css: '#111111', rgba: 0x111111FF });
|
||||
assert.deepEqual(css.toColor('#222'), { css: '#222222', rgba: 0x222222FF });
|
||||
assert.deepEqual(css.toColor('#333'), { css: '#333333', rgba: 0x333333FF });
|
||||
assert.deepEqual(css.toColor('#444'), { css: '#444444', rgba: 0x444444FF });
|
||||
assert.deepEqual(css.toColor('#555'), { css: '#555555', rgba: 0x555555FF });
|
||||
assert.deepEqual(css.toColor('#666'), { css: '#666666', rgba: 0x666666FF });
|
||||
assert.deepEqual(css.toColor('#777'), { css: '#777777', rgba: 0x777777FF });
|
||||
assert.deepEqual(css.toColor('#888'), { css: '#888888', rgba: 0x888888FF });
|
||||
assert.deepEqual(css.toColor('#999'), { css: '#999999', rgba: 0x999999FF });
|
||||
assert.deepEqual(css.toColor('#aaa'), { css: '#aaaaaa', rgba: 0xaaaaaaFF });
|
||||
assert.deepEqual(css.toColor('#bbb'), { css: '#bbbbbb', rgba: 0xbbbbbbFF });
|
||||
assert.deepEqual(css.toColor('#ccc'), { css: '#cccccc', rgba: 0xccccccFF });
|
||||
assert.deepEqual(css.toColor('#ddd'), { css: '#dddddd', rgba: 0xddddddFF });
|
||||
assert.deepEqual(css.toColor('#eee'), { css: '#eeeeee', rgba: 0xeeeeeeFF });
|
||||
assert.deepEqual(css.toColor('#fff'), { css: '#ffffff', rgba: 0xffffffFF });
|
||||
assert.deepEqual(css.toColor('#fff'), { css: '#ffffff', rgba: 0xffffffFF });
|
||||
});
|
||||
it('should convert the #rgb format to an IColor', () => {
|
||||
assert.deepEqual(css.toColor('#0000'), { css: '#00000000', rgba: 0x00000000 });
|
||||
assert.deepEqual(css.toColor('#1111'), { css: '#11111111', rgba: 0x11111111 });
|
||||
assert.deepEqual(css.toColor('#2222'), { css: '#22222222', rgba: 0x22222222 });
|
||||
assert.deepEqual(css.toColor('#3333'), { css: '#33333333', rgba: 0x33333333 });
|
||||
assert.deepEqual(css.toColor('#4444'), { css: '#44444444', rgba: 0x44444444 });
|
||||
assert.deepEqual(css.toColor('#5555'), { css: '#55555555', rgba: 0x55555555 });
|
||||
assert.deepEqual(css.toColor('#6666'), { css: '#66666666', rgba: 0x66666666 });
|
||||
assert.deepEqual(css.toColor('#7777'), { css: '#77777777', rgba: 0x77777777 });
|
||||
assert.deepEqual(css.toColor('#8888'), { css: '#88888888', rgba: 0x88888888 });
|
||||
assert.deepEqual(css.toColor('#9999'), { css: '#99999999', rgba: 0x99999999 });
|
||||
assert.deepEqual(css.toColor('#aaaa'), { css: '#aaaaaaaa', rgba: 0xaaaaaaaa });
|
||||
assert.deepEqual(css.toColor('#bbbb'), { css: '#bbbbbbbb', rgba: 0xbbbbbbbb });
|
||||
assert.deepEqual(css.toColor('#cccc'), { css: '#cccccccc', rgba: 0xcccccccc });
|
||||
assert.deepEqual(css.toColor('#dddd'), { css: '#dddddddd', rgba: 0xdddddddd });
|
||||
assert.deepEqual(css.toColor('#eeee'), { css: '#eeeeeeee', rgba: 0xeeeeeeee });
|
||||
assert.deepEqual(css.toColor('#ffff'), { css: '#ffffffff', rgba: 0xffffffff });
|
||||
assert.deepEqual(css.toColor('#ffff'), { css: '#ffffffff', rgba: 0xffffffff });
|
||||
});
|
||||
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 });
|
||||
@@ -191,6 +229,20 @@ describe('Color', () => {
|
||||
assert.deepEqual(css.toColor('#f0f0f0f0'), { css: '#f0f0f0f0', rgba: 0xf0f0f0f0 });
|
||||
assert.deepEqual(css.toColor('#ffffffff'), { css: '#ffffffff', rgba: 0xffffffff });
|
||||
});
|
||||
it('should convert the rgb() format to an IColor', () => {
|
||||
assert.deepEqual(css.toColor('rgb(0, 0, 0)'), { css: '#000000ff', rgba: 0x000000ff });
|
||||
assert.deepEqual(css.toColor('rgb(80, 0, 0)'), { css: '#500000ff', rgba: 0x500000ff });
|
||||
assert.deepEqual(css.toColor('rgb(0, 80, 0)'), { css: '#005000ff', rgba: 0x005000ff });
|
||||
assert.deepEqual(css.toColor('rgb(0, 0, 80)'), { css: '#000050ff', rgba: 0x000050ff });
|
||||
assert.deepEqual(css.toColor('rgb(255, 255, 255)'), { css: '#ffffffff', rgba: 0xffffffff });
|
||||
});
|
||||
it('should convert the rgba() format to an IColor', () => {
|
||||
assert.deepEqual(css.toColor('rgba(0, 0, 0, 0)'), { css: '#00000000', rgba: 0x00000000 });
|
||||
assert.deepEqual(css.toColor('rgba(80, 0, 0, 0.5)'), { css: '#50000080', rgba: 0x50000080 });
|
||||
assert.deepEqual(css.toColor('rgba(0, 80, 0, 0.5)'), { css: '#00500080', rgba: 0x00500080 });
|
||||
assert.deepEqual(css.toColor('rgba(0, 0, 80, 0.5)'), { css: '#00005080', rgba: 0x00005080 });
|
||||
assert.deepEqual(css.toColor('rgba(255, 255, 255, 1)'), { css: '#ffffffff', rgba: 0xffffffff });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IColor } from 'browser/Types';
|
||||
import { IColorRGB } from 'common/Types';
|
||||
import { IColor, IColorRGB } from 'common/Types';
|
||||
|
||||
/**
|
||||
* Helper functions where the source type is "channels" (individual color channels as numbers).
|
||||
@@ -95,17 +94,40 @@ export namespace color {
|
||||
*/
|
||||
export namespace css {
|
||||
export function toColor(css: string): IColor {
|
||||
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
|
||||
};
|
||||
if (css.match(/#[0-9a-f]{3,8}/i)) {
|
||||
switch (css.length) {
|
||||
case 4: { // #rgb
|
||||
const r = parseInt(css.slice(1, 2).repeat(2), 16);
|
||||
const g = parseInt(css.slice(2, 3).repeat(2), 16);
|
||||
const b = parseInt(css.slice(3, 4).repeat(2), 16);
|
||||
return rgba.toColor(r, g, b);
|
||||
}
|
||||
case 5: { // #rgba
|
||||
const r = parseInt(css.slice(1, 2).repeat(2), 16);
|
||||
const g = parseInt(css.slice(2, 3).repeat(2), 16);
|
||||
const b = parseInt(css.slice(3, 4).repeat(2), 16);
|
||||
const a = parseInt(css.slice(4, 5).repeat(2), 16);
|
||||
return rgba.toColor(r, g, b, a);
|
||||
}
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
const rgbaMatch = css.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/);
|
||||
if (rgbaMatch) { // rgb() or rgba()
|
||||
const r = parseInt(rgbaMatch[1]);
|
||||
const g = parseInt(rgbaMatch[2]);
|
||||
const b = parseInt(rgbaMatch[3]);
|
||||
const a = Math.round((rgbaMatch[5] === undefined ? 1 : parseFloat(rgbaMatch[5])) * 0xFF);
|
||||
return rgba.toColor(r, g, b, a);
|
||||
}
|
||||
throw new Error('css.toColor: Unsupported css format');
|
||||
}
|
||||
@@ -151,15 +173,42 @@ export namespace rgb {
|
||||
* Helper functions where the source type is "rgba" (number: 0xrrggbbaa).
|
||||
*/
|
||||
export namespace rgba {
|
||||
/**
|
||||
* Given a foreground color and a background color, either increase or reduce the luminance of the
|
||||
* foreground color until the specified contrast ratio is met. If pure white or black is hit
|
||||
* without the contrast ratio being met, go the other direction using the background color as the
|
||||
* foreground color and take either the first or second result depending on which has the higher
|
||||
* contrast ratio.
|
||||
*
|
||||
* `undefined` will be returned if the contrast ratio is already met.
|
||||
*
|
||||
* @param bgRgba The background color in rgba format.
|
||||
* @param fgRgba The foreground color in rgba format.
|
||||
* @param ratio The contrast ratio to achieve.
|
||||
*/
|
||||
export function ensureContrastRatio(bgRgba: number, fgRgba: number, ratio: number): number | undefined {
|
||||
const bgL = rgb.relativeLuminance(bgRgba >> 8);
|
||||
const fgL = rgb.relativeLuminance(fgRgba >> 8);
|
||||
const cr = contrastRatio(bgL, fgL);
|
||||
if (cr < ratio) {
|
||||
if (fgL < bgL) {
|
||||
return reduceLuminance(bgRgba, fgRgba, ratio);
|
||||
const resultA = reduceLuminance(bgRgba, fgRgba, ratio);
|
||||
const resultARatio = contrastRatio(bgL, rgb.relativeLuminance(resultA >> 8));
|
||||
if (resultARatio < ratio) {
|
||||
const resultB = increaseLuminance(bgRgba, bgRgba, ratio);
|
||||
const resultBRatio = contrastRatio(bgL, rgb.relativeLuminance(resultB >> 8));
|
||||
return resultARatio > resultBRatio ? resultA : resultB;
|
||||
}
|
||||
return resultA;
|
||||
}
|
||||
return increaseLuminance(bgRgba, fgRgba, ratio);
|
||||
const resultA = increaseLuminance(bgRgba, fgRgba, ratio);
|
||||
const resultARatio = contrastRatio(bgL, rgb.relativeLuminance(resultA >> 8));
|
||||
if (resultARatio < ratio) {
|
||||
const resultB = reduceLuminance(bgRgba, bgRgba, ratio);
|
||||
const resultBRatio = contrastRatio(bgL, rgb.relativeLuminance(resultB >> 8));
|
||||
return resultARatio > resultBRatio ? resultA : resultB;
|
||||
}
|
||||
return resultA;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -173,13 +222,13 @@ export namespace rgba {
|
||||
let fgR = (fgRgba >> 24) & 0xFF;
|
||||
let fgG = (fgRgba >> 16) & 0xFF;
|
||||
let fgB = (fgRgba >> 8) & 0xFF;
|
||||
let cr = contrastRatio(rgb.relativeLuminance2(fgR, fgB, fgG), rgb.relativeLuminance2(bgR, bgG, bgB));
|
||||
let cr = contrastRatio(rgb.relativeLuminance2(fgR, fgG, fgB), rgb.relativeLuminance2(bgR, bgG, bgB));
|
||||
while (cr < ratio && (fgR > 0 || fgG > 0 || fgB > 0)) {
|
||||
// Reduce by 10% until the ratio is hit
|
||||
fgR -= Math.max(0, Math.ceil(fgR * 0.1));
|
||||
fgG -= Math.max(0, Math.ceil(fgG * 0.1));
|
||||
fgB -= Math.max(0, Math.ceil(fgB * 0.1));
|
||||
cr = contrastRatio(rgb.relativeLuminance2(fgR, fgB, fgG), rgb.relativeLuminance2(bgR, bgG, bgB));
|
||||
cr = contrastRatio(rgb.relativeLuminance2(fgR, fgG, fgB), rgb.relativeLuminance2(bgR, bgG, bgB));
|
||||
}
|
||||
return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0;
|
||||
}
|
||||
@@ -193,13 +242,13 @@ export namespace rgba {
|
||||
let fgR = (fgRgba >> 24) & 0xFF;
|
||||
let fgG = (fgRgba >> 16) & 0xFF;
|
||||
let fgB = (fgRgba >> 8) & 0xFF;
|
||||
let cr = contrastRatio(rgb.relativeLuminance2(fgR, fgB, fgG), rgb.relativeLuminance2(bgR, bgG, bgB));
|
||||
let cr = contrastRatio(rgb.relativeLuminance2(fgR, fgG, fgB), rgb.relativeLuminance2(bgR, bgG, bgB));
|
||||
while (cr < ratio && (fgR < 0xFF || fgG < 0xFF || fgB < 0xFF)) {
|
||||
// Increase by 10% until the ratio is hit
|
||||
fgR = Math.min(0xFF, fgR + Math.ceil((255 - fgR) * 0.1));
|
||||
fgG = Math.min(0xFF, fgG + Math.ceil((255 - fgG) * 0.1));
|
||||
fgB = Math.min(0xFF, fgB + Math.ceil((255 - fgB) * 0.1));
|
||||
cr = contrastRatio(rgb.relativeLuminance2(fgR, fgB, fgG), rgb.relativeLuminance2(bgR, bgG, bgB));
|
||||
cr = contrastRatio(rgb.relativeLuminance2(fgR, fgG, fgB), rgb.relativeLuminance2(bgR, bgG, bgB));
|
||||
}
|
||||
return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0;
|
||||
}
|
||||
@@ -209,10 +258,10 @@ export namespace rgba {
|
||||
return [(value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF];
|
||||
}
|
||||
|
||||
export function toColor(r: number, g: number, b: number): IColor {
|
||||
export function toColor(r: number, g: number, b: number, a?: number): IColor {
|
||||
return {
|
||||
css: channels.toCss(r, g, b),
|
||||
rgba: channels.toRgba(r, g, b)
|
||||
css: channels.toCss(r, g, b, a),
|
||||
rgba: channels.toRgba(r, g, b, a)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,8 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
|
||||
private _onResize = new EventEmitter<{ cols: number, rows: number }>();
|
||||
public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; }
|
||||
protected _onScroll = new EventEmitter<IScrollEvent, void>();
|
||||
public get onWriteParsed(): IEvent<void> { return this._onWriteParsed.event; }
|
||||
protected _onWriteParsed = new EventEmitter<void>();
|
||||
/**
|
||||
* Internally we track the source of the scroll but this is meaningless outside the library so
|
||||
* it's filtered out.
|
||||
@@ -138,6 +140,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
|
||||
|
||||
// Setup WriteBuffer
|
||||
this._writeBuffer = new WriteBuffer((data, promiseResult) => this._inputHandler.parse(data, promiseResult));
|
||||
this.register(forwardEvent(this._writeBuffer.onWriteParsed, this._onWriteParsed));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { SortedList } from 'common/SortedList';
|
||||
|
||||
const deepStrictEqual = assert.deepStrictEqual;
|
||||
|
||||
describe('SortedList', () => {
|
||||
let list: SortedList<number>;
|
||||
function assertList(expected: number[]): void {
|
||||
deepStrictEqual(Array.from(list.values()), expected);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
list = new SortedList<number>(e => e);
|
||||
});
|
||||
|
||||
describe('insert', () => {
|
||||
it('should maintain sorted values', () => {
|
||||
list.insert(10);
|
||||
assertList([10]);
|
||||
list.insert(8);
|
||||
assertList([8, 10]);
|
||||
list.insert(15);
|
||||
assertList([8, 10, 15]);
|
||||
list.insert(2);
|
||||
assertList([2, 8, 10, 15]);
|
||||
list.insert(1);
|
||||
assertList([1, 2, 8, 10, 15]);
|
||||
list.insert(6);
|
||||
assertList([1, 2, 6, 8, 10, 15]);
|
||||
});
|
||||
it('should allow duplicates of the same key', () => {
|
||||
list.insert(5);
|
||||
assertList([5]);
|
||||
list.insert(5);
|
||||
assertList([5, 5]);
|
||||
list.insert(8);
|
||||
assertList([5, 5, 8]);
|
||||
list.insert(5);
|
||||
assertList([5, 5, 5, 8]);
|
||||
list.insert(8);
|
||||
assertList([5, 5, 5, 8, 8]);
|
||||
list.insert(6);
|
||||
assertList([5, 5, 5, 6, 8, 8]);
|
||||
});
|
||||
});
|
||||
it('delete', () => {
|
||||
list.insert(1);
|
||||
list.insert(2);
|
||||
list.insert(4);
|
||||
list.insert(3);
|
||||
list.insert(5);
|
||||
assertList([1, 2, 3, 4, 5]);
|
||||
list.delete(1);
|
||||
assertList([2, 3, 4, 5]);
|
||||
list.delete(3);
|
||||
assertList([2, 4, 5]);
|
||||
list.delete(4);
|
||||
assertList([2, 5]);
|
||||
list.delete(5);
|
||||
assertList([2]);
|
||||
list.delete(2);
|
||||
assertList([]);
|
||||
});
|
||||
it('getKeyIterator', () => {
|
||||
list.insert(5);
|
||||
list.insert(5);
|
||||
list.insert(8);
|
||||
list.insert(5);
|
||||
list.insert(8);
|
||||
list.insert(6);
|
||||
assertList([5, 5, 5, 6, 8, 8]);
|
||||
deepStrictEqual(Array.from(list.getKeyIterator(1)), []);
|
||||
deepStrictEqual(Array.from(list.getKeyIterator(5)), [5, 5, 5]);
|
||||
deepStrictEqual(Array.from(list.getKeyIterator(6)), [6]);
|
||||
deepStrictEqual(Array.from(list.getKeyIterator(8)), [8, 8]);
|
||||
deepStrictEqual(Array.from(list.getKeyIterator(9)), []);
|
||||
});
|
||||
it('clear', () => {
|
||||
list.insert(1);
|
||||
list.insert(2);
|
||||
list.insert(4);
|
||||
list.insert(3);
|
||||
list.insert(5);
|
||||
list.clear();
|
||||
assertList([]);
|
||||
});
|
||||
it('custom key', () => {
|
||||
const customList = new SortedList<{ key: number }>(e => e.key);
|
||||
customList.insert({ key: 5 });
|
||||
customList.insert({ key: 2 });
|
||||
customList.insert({ key: 10 });
|
||||
customList.insert({ key: 5 });
|
||||
customList.insert({ key: 6 });
|
||||
deepStrictEqual(Array.from(customList.values()), [
|
||||
{ key: 2 },
|
||||
{ key: 5 },
|
||||
{ key: 5 },
|
||||
{ key: 6 },
|
||||
{ key: 10 }
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Copyright (c) 2022 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* A generic list that is maintained in sorted order and allows values with duplicate keys. This
|
||||
* list is based on binary search and as such locating a key will take O(log n) amortized, this
|
||||
* includes the by key iterator.
|
||||
*/
|
||||
export class SortedList<T> {
|
||||
private readonly _array: T[] = [];
|
||||
|
||||
constructor(
|
||||
private readonly _getKey: (value: T) => number
|
||||
) {
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this._array.length = 0;
|
||||
}
|
||||
|
||||
public insert(value: T): void {
|
||||
if (this._array.length === 0) {
|
||||
this._array.push(value);
|
||||
return;
|
||||
}
|
||||
const i = this._search(this._getKey(value), 0, this._array.length - 1);
|
||||
this._array.splice(i, 0, value);
|
||||
}
|
||||
|
||||
public delete(value: T): boolean {
|
||||
if (this._array.length === 0) {
|
||||
return false;
|
||||
}
|
||||
const key = this._getKey(value);
|
||||
let i = this._search(key, 0, this._array.length - 1);
|
||||
if (this._getKey(this._array[i]) !== key) {
|
||||
return false;
|
||||
}
|
||||
do {
|
||||
if (this._array[i] === value) {
|
||||
this._array.splice(i, 1);
|
||||
return true;
|
||||
}
|
||||
} while (++i < this._array.length && this._getKey(this._array[i]) === key);
|
||||
return false;
|
||||
}
|
||||
|
||||
public *getKeyIterator(key: number): IterableIterator<T> {
|
||||
if (this._array.length === 0) {
|
||||
return;
|
||||
}
|
||||
let i = this._search(key, 0, this._array.length - 1);
|
||||
if (i < 0 || i >= this._array.length) {
|
||||
return;
|
||||
}
|
||||
if (this._getKey(this._array[i]) !== key) {
|
||||
return;
|
||||
}
|
||||
do {
|
||||
yield this._array[i];
|
||||
} while (++i < this._array.length && this._getKey(this._array[i]) === key);
|
||||
}
|
||||
|
||||
public values(): IterableIterator<T> {
|
||||
return this._array.values();
|
||||
}
|
||||
|
||||
private _search(key: number, min: number, max: number): number {
|
||||
if (max < min) {
|
||||
return min;
|
||||
}
|
||||
let mid = Math.floor((min + max) / 2);
|
||||
if (this._getKey(this._array[mid]) > key) {
|
||||
return this._search(key, min, mid - 1);
|
||||
}
|
||||
if (this._getKey(this._array[mid]) < key) {
|
||||
return this._search(key, mid + 1, max);
|
||||
}
|
||||
// Value found! Since keys can be duplicates, move the result index back to the lowest index
|
||||
// that matches the key.
|
||||
while (mid > 0 && this._getKey(this._array[mid - 1]) === key) {
|
||||
mid--;
|
||||
}
|
||||
return mid;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum } from 'common/services/Services';
|
||||
import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum, IDecorationService, IInternalDecoration } from 'common/services/Services';
|
||||
import { IEvent, EventEmitter } from 'common/EventEmitter';
|
||||
import { clone } from 'common/Clone';
|
||||
import { DEFAULT_OPTIONS } from 'common/services/OptionsService';
|
||||
@@ -11,6 +11,7 @@ import { IBufferSet, IBuffer } from 'common/buffer/Types';
|
||||
import { BufferSet } from 'common/buffer/BufferSet';
|
||||
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType, ICharset, IModes, IAttributeData } from 'common/Types';
|
||||
import { UnicodeV6 } from 'common/input/UnicodeV6';
|
||||
import { IDecorationOptions, IDecoration } from 'xterm';
|
||||
|
||||
export class MockBufferService implements IBufferService {
|
||||
public serviceBrand: any;
|
||||
@@ -158,3 +159,15 @@ export class MockUnicodeService implements IUnicodeService {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
export class MockDecorationService implements IDecorationService {
|
||||
public serviceBrand: any;
|
||||
public get decorations(): IterableIterator<IInternalDecoration> { return [].values(); }
|
||||
public onDecorationRegistered = new EventEmitter<IInternalDecoration>().event;
|
||||
public onDecorationRemoved = new EventEmitter<IInternalDecoration>().event;
|
||||
public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { return undefined; }
|
||||
public reset(): void { }
|
||||
public *getDecorationsAtLine(line: number): IterableIterator<IInternalDecoration> { }
|
||||
public *getDecorationsAtCell(x: number, line: number): IterableIterator<IInternalDecoration> { }
|
||||
public dispose(): void { }
|
||||
}
|
||||
|
||||
Vendored
+6
@@ -50,6 +50,7 @@ export interface IKeyboardEvent {
|
||||
keyCode: number;
|
||||
key: string;
|
||||
type: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
export interface IScrollEvent {
|
||||
@@ -102,6 +103,11 @@ export interface ICharset {
|
||||
}
|
||||
|
||||
export type CharData = [number, string, number, number];
|
||||
|
||||
export interface IColor {
|
||||
css: string;
|
||||
rgba: number; // 32-bit int with rgba in each byte
|
||||
}
|
||||
export type IColorRGB = [number, number, number];
|
||||
|
||||
export interface IExtendedAttrs {
|
||||
|
||||
+22
-13
@@ -585,20 +585,29 @@ export class Buffer implements IBuffer {
|
||||
return x >= this._cols ? this._cols - 1 : x < 0 ? 0 : x;
|
||||
}
|
||||
|
||||
public clearMarkers(y?: number): void {
|
||||
/**
|
||||
* Clears markers on single line.
|
||||
* @param y The line to clear.
|
||||
*/
|
||||
public clearMarkers(y: number): void {
|
||||
this._isClearing = true;
|
||||
if (y !== undefined) {
|
||||
for (let i = 0; i < this.markers.length; i++) {
|
||||
if (this.markers[i].line === y) {
|
||||
this.markers[i].dispose();
|
||||
this.markers.splice(i--, 1);
|
||||
}
|
||||
for (let i = 0; i < this.markers.length; i++) {
|
||||
if (this.markers[i].line === y) {
|
||||
this.markers[i].dispose();
|
||||
this.markers.splice(i--, 1);
|
||||
}
|
||||
} else {
|
||||
for (const marker of this.markers) {
|
||||
marker.dispose();
|
||||
}
|
||||
this.markers = [];
|
||||
}
|
||||
this._isClearing = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears markers on all lines
|
||||
*/
|
||||
public clearAllMarkers(): void {
|
||||
this._isClearing = true;
|
||||
for (let i = 0; i < this.markers.length; i++) {
|
||||
this.markers[i].dispose();
|
||||
this.markers.splice(i--, 1);
|
||||
}
|
||||
this._isClearing = false;
|
||||
}
|
||||
@@ -659,7 +668,7 @@ export class Buffer implements IBuffer {
|
||||
export class BufferStringIterator implements IBufferStringIterator {
|
||||
private _current: number;
|
||||
|
||||
constructor (
|
||||
constructor(
|
||||
private _buffer: IBuffer,
|
||||
private _trimRight: boolean,
|
||||
private _startIndex: number = 0,
|
||||
|
||||
Vendored
+3
-2
@@ -10,7 +10,7 @@ import { IEvent } from 'common/EventEmitter';
|
||||
export type BufferIndex = [number, number];
|
||||
|
||||
export interface IBufferStringIteratorResult {
|
||||
range: {first: number, last: number};
|
||||
range: { first: number, last: number };
|
||||
content: string;
|
||||
}
|
||||
|
||||
@@ -45,7 +45,8 @@ export interface IBuffer {
|
||||
getNullCell(attr?: IAttributeData): ICellData;
|
||||
getWhitespaceCell(attr?: IAttributeData): ICellData;
|
||||
addMarker(y: number): IMarker;
|
||||
clearMarkers(y?: number): void;
|
||||
clearMarkers(y: number): void;
|
||||
clearAllMarkers(): void;
|
||||
}
|
||||
|
||||
export interface IBufferSet extends IDisposable {
|
||||
|
||||
@@ -148,3 +148,6 @@ export namespace C1 {
|
||||
/** Application Program Command */
|
||||
export const APC = '\x9f';
|
||||
}
|
||||
export namespace C1_ESCAPED {
|
||||
export const ST = `${C0.ESC}\\`;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ function testEvaluateKeyboardEvent(partialEvent: {
|
||||
shiftKey?: boolean;
|
||||
metaKey?: boolean;
|
||||
keyCode?: number;
|
||||
code?: string;
|
||||
key?: string;
|
||||
type?: string;
|
||||
}, partialOptions: {
|
||||
@@ -26,6 +27,7 @@ function testEvaluateKeyboardEvent(partialEvent: {
|
||||
shiftKey: partialEvent.shiftKey || false,
|
||||
metaKey: partialEvent.metaKey || false,
|
||||
keyCode: partialEvent.keyCode !== undefined ? partialEvent.keyCode : 0,
|
||||
code: partialEvent.code || '',
|
||||
key: partialEvent.key || '',
|
||||
type: partialEvent.type || ''
|
||||
};
|
||||
@@ -313,5 +315,9 @@ describe('Keyboard', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 49, key: '!' }).key, '!');
|
||||
});
|
||||
|
||||
it('should return proper sequence for ctrl+@', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, shiftKey: true, keyCode: 50, key: '@' }).key, '\x00');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -230,6 +230,8 @@ export function evaluateKeyboardEvent(
|
||||
// page up
|
||||
if (ev.shiftKey) {
|
||||
result.type = KeyboardResultType.PAGE_UP;
|
||||
} else if (ev.ctrlKey) {
|
||||
result.key = C0.ESC + '[5;' + (modifiers + 1) + '~';
|
||||
} else {
|
||||
result.key = C0.ESC + '[5~';
|
||||
}
|
||||
@@ -238,6 +240,8 @@ export function evaluateKeyboardEvent(
|
||||
// page down
|
||||
if (ev.shiftKey) {
|
||||
result.type = KeyboardResultType.PAGE_DOWN;
|
||||
} else if (ev.ctrlKey) {
|
||||
result.key = C0.ESC + '[6;' + (modifiers + 1) + '~';
|
||||
} else {
|
||||
result.key = C0.ESC + '[6~';
|
||||
}
|
||||
@@ -354,7 +358,23 @@ export function evaluateKeyboardEvent(
|
||||
result.key = C0.ESC + key;
|
||||
} else if (ev.keyCode >= 65 && ev.keyCode <= 90) {
|
||||
const keyCode = ev.ctrlKey ? ev.keyCode - 64 : ev.keyCode + 32;
|
||||
result.key = C0.ESC + String.fromCharCode(keyCode);
|
||||
let keyString = String.fromCharCode(keyCode);
|
||||
if (ev.shiftKey) {
|
||||
keyString = keyString.toUpperCase();
|
||||
}
|
||||
result.key = C0.ESC + keyString;
|
||||
} else if (ev.key === 'Dead' && ev.code.startsWith('Key')) {
|
||||
// Reference: https://github.com/xtermjs/xterm.js/issues/3725
|
||||
// Alt will produce a "dead key" (initate composition) with some
|
||||
// of the letters in US layout (e.g. N/E/U).
|
||||
// It's safe to match against Key* since no other `code` values begin with "Key".
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code/code_values#code_values_on_mac
|
||||
let keyString = ev.code.slice(3, 4);
|
||||
if (!ev.shiftKey) {
|
||||
keyString = keyString.toLowerCase();
|
||||
}
|
||||
result.key = C0.ESC + keyString;
|
||||
result.cancel = true;
|
||||
}
|
||||
} else if (isMac && !ev.altKey && !ev.ctrlKey && !ev.shiftKey && ev.metaKey) {
|
||||
if (ev.keyCode === 65) { // cmd + a
|
||||
@@ -367,6 +387,9 @@ export function evaluateKeyboardEvent(
|
||||
if (ev.key === '_') { // ^_
|
||||
result.key = C0.US;
|
||||
}
|
||||
if (ev.key === '@') { // ^ + shift + 2 = ^ + @
|
||||
result.key = C0.NUL;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
|
||||
declare const setTimeout: (handler: () => void, timeout?: number) => void;
|
||||
|
||||
/**
|
||||
@@ -44,6 +46,8 @@ export class WriteBuffer {
|
||||
private _bufferOffset = 0;
|
||||
private _isSyncWriting = false;
|
||||
private _syncCalls = 0;
|
||||
public get onWriteParsed(): IEvent<void> { return this._onWriteParsed.event; }
|
||||
private _onWriteParsed = new EventEmitter<void>();
|
||||
|
||||
constructor(private _action: (data: string | Uint8Array, promiseResult?: boolean) => void | Promise<boolean>) { }
|
||||
|
||||
@@ -220,5 +224,6 @@ export class WriteBuffer {
|
||||
this._pendingData = 0;
|
||||
this._bufferOffset = 0;
|
||||
}
|
||||
this._onWriteParsed.fire();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
|
||||
// 'rgb:' rule - matching: r/g/b | rr/gg/bb | rrr/ggg/bbb | rrrr/gggg/bbbb (hex digits)
|
||||
const RGB_REX = /^([\da-f]{1})\/([\da-f]{1})\/([\da-f]{1})$|^([\da-f]{2})\/([\da-f]{2})\/([\da-f]{2})$|^([\da-f]{3})\/([\da-f]{3})\/([\da-f]{3})$|^([\da-f]{4})\/([\da-f]{4})\/([\da-f]{4})$/;
|
||||
const RGB_REX = /^([\da-f])\/([\da-f])\/([\da-f])$|^([\da-f]{2})\/([\da-f]{2})\/([\da-f]{2})$|^([\da-f]{3})\/([\da-f]{3})\/([\da-f]{3})$|^([\da-f]{4})\/([\da-f]{4})\/([\da-f]{4})$/;
|
||||
// '#...' rule - matching any hex digits
|
||||
const HASH_REX = /^[\da-f]+$/;
|
||||
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* Copyright (c) 2022 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { css } from 'common/Color';
|
||||
import { EventEmitter } from 'common/EventEmitter';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { IDecorationService, IInternalDecoration } from 'common/services/Services';
|
||||
import { SortedList } from 'common/SortedList';
|
||||
import { IColor } from 'common/Types';
|
||||
import { IDecorationOptions, IDecoration, IMarker, IEvent } from 'xterm';
|
||||
|
||||
export class DecorationService extends Disposable implements IDecorationService {
|
||||
public serviceBrand: any;
|
||||
|
||||
/**
|
||||
* A list of all decorations, sorted by the marker's line value. This relies on the fact that
|
||||
* while marker line values do change, they should all change by the same amount so this should
|
||||
* never become out of order.
|
||||
*/
|
||||
private readonly _decorations: SortedList<IInternalDecoration> = new SortedList(e => e.marker.line);
|
||||
|
||||
private _onDecorationRegistered = this.register(new EventEmitter<IInternalDecoration>());
|
||||
public get onDecorationRegistered(): IEvent<IInternalDecoration> { return this._onDecorationRegistered.event; }
|
||||
private _onDecorationRemoved = this.register(new EventEmitter<IInternalDecoration>());
|
||||
public get onDecorationRemoved(): IEvent<IInternalDecoration> { return this._onDecorationRemoved.event; }
|
||||
|
||||
public get decorations(): IterableIterator<IInternalDecoration> { return this._decorations.values(); }
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public registerDecoration(options: IDecorationOptions): IDecoration | undefined {
|
||||
if (options.marker.isDisposed) {
|
||||
return undefined;
|
||||
}
|
||||
const decoration = new Decoration(options);
|
||||
if (decoration) {
|
||||
const markerDispose = decoration.marker.onDispose(() => decoration.dispose());
|
||||
decoration.onDispose(() => {
|
||||
if (decoration) {
|
||||
if (this._decorations.delete(decoration)) {
|
||||
this._onDecorationRemoved.fire(decoration);
|
||||
}
|
||||
markerDispose.dispose();
|
||||
}
|
||||
});
|
||||
this._decorations.insert(decoration);
|
||||
this._onDecorationRegistered.fire(decoration);
|
||||
}
|
||||
return decoration;
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
for (const d of this._decorations.values()) {
|
||||
d.dispose();
|
||||
}
|
||||
this._decorations.clear();
|
||||
}
|
||||
|
||||
public *getDecorationsAtLine(line: number): IterableIterator<IInternalDecoration> {
|
||||
return this._decorations.getKeyIterator(line);
|
||||
}
|
||||
|
||||
public *getDecorationsAtCell(x: number, line: number, layer?: 'bottom' | 'top'): IterableIterator<IInternalDecoration> {
|
||||
let xmin = 0;
|
||||
let xmax = 0;
|
||||
for (const d of this._decorations.getKeyIterator(line)) {
|
||||
xmin = d.options.x ?? 0;
|
||||
xmax = xmin + (d.options.width ?? 1);
|
||||
if (x >= xmin && x < xmax && (!layer || (d.options.layer ?? 'bottom') === layer)) {
|
||||
yield d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
for (const d of this._decorations.values()) {
|
||||
this._onDecorationRemoved.fire(d);
|
||||
}
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
|
||||
class Decoration extends Disposable implements IInternalDecoration {
|
||||
public readonly marker: IMarker;
|
||||
public element: HTMLElement | undefined;
|
||||
public isDisposed: boolean = false;
|
||||
|
||||
public readonly onRenderEmitter = this.register(new EventEmitter<HTMLElement>());
|
||||
public readonly onRender = this.onRenderEmitter.event;
|
||||
private _onDispose = this.register(new EventEmitter<void>());
|
||||
public readonly onDispose = this._onDispose.event;
|
||||
|
||||
private _cachedBg: IColor | undefined | null = null;
|
||||
public get backgroundColorRGB(): IColor | undefined {
|
||||
if (this._cachedBg === null) {
|
||||
if (this.options.backgroundColor) {
|
||||
this._cachedBg = css.toColor(this.options.backgroundColor);
|
||||
} else {
|
||||
this._cachedBg = undefined;
|
||||
}
|
||||
}
|
||||
return this._cachedBg;
|
||||
}
|
||||
|
||||
private _cachedFg: IColor | undefined | null = null;
|
||||
public get foregroundColorRGB(): IColor | undefined {
|
||||
if (this._cachedFg === null) {
|
||||
if (this.options.foregroundColor) {
|
||||
this._cachedFg = css.toColor(this.options.foregroundColor);
|
||||
} else {
|
||||
this._cachedFg = undefined;
|
||||
}
|
||||
}
|
||||
return this._cachedFg;
|
||||
}
|
||||
|
||||
constructor(
|
||||
public readonly options: IDecorationOptions
|
||||
) {
|
||||
super();
|
||||
this.marker = options.marker;
|
||||
if (this.options.overviewRulerOptions && !this.options.overviewRulerOptions.position) {
|
||||
this.options.overviewRulerOptions.position = 'full';
|
||||
}
|
||||
}
|
||||
|
||||
public override dispose(): void {
|
||||
if (this._isDisposed) {
|
||||
return;
|
||||
}
|
||||
this._isDisposed = true;
|
||||
this._onDispose.fire();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,8 @@ export const DEFAULT_OPTIONS: Readonly<ITerminalOptions> = {
|
||||
altClickMovesCursor: true,
|
||||
convertEol: false,
|
||||
termName: 'xterm',
|
||||
cancelEvents: false
|
||||
cancelEvents: false,
|
||||
overviewRulerWidth: undefined
|
||||
};
|
||||
|
||||
const FONT_WEIGHT_OPTIONS: Extract<FontWeight, string>[] = ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'];
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IEvent } from 'common/EventEmitter';
|
||||
import { IEvent, IEventEmitter } from 'common/EventEmitter';
|
||||
import { IBuffer, IBufferSet } from 'common/buffer/Types';
|
||||
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData, ScrollSource } from 'common/Types';
|
||||
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData, ScrollSource, IDisposable, IColorRGB, IColor } from 'common/Types';
|
||||
import { createDecorator } from 'common/services/ServiceRegistry';
|
||||
import { IDecorationOptions, IDecoration } from 'xterm';
|
||||
|
||||
export const IBufferService = createDecorator<IBufferService>('BufferService');
|
||||
export interface IBufferService {
|
||||
@@ -245,6 +246,7 @@ export interface ITerminalOptions {
|
||||
windowsMode: boolean;
|
||||
windowOptions: IWindowOptions;
|
||||
wordSeparator: string;
|
||||
overviewRulerWidth?: number;
|
||||
|
||||
[key: string]: any;
|
||||
cancelEvents: boolean;
|
||||
@@ -257,6 +259,7 @@ export interface ITheme {
|
||||
cursor?: string;
|
||||
cursorAccent?: string;
|
||||
selection?: string;
|
||||
selectionForeground?: string;
|
||||
black?: string;
|
||||
red?: string;
|
||||
green?: string;
|
||||
@@ -298,3 +301,23 @@ export interface IUnicodeVersionProvider {
|
||||
readonly version: string;
|
||||
wcwidth(ucs: number): 0 | 1 | 2;
|
||||
}
|
||||
|
||||
export const IDecorationService = createDecorator<IDecorationService>('DecorationService');
|
||||
export interface IDecorationService extends IDisposable {
|
||||
serviceBrand: undefined;
|
||||
readonly decorations: IterableIterator<IInternalDecoration>;
|
||||
readonly onDecorationRegistered: IEvent<IInternalDecoration>;
|
||||
readonly onDecorationRemoved: IEvent<IInternalDecoration>;
|
||||
registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined;
|
||||
reset(): void;
|
||||
/** Iterates over the decorations at a line (in no particular order). */
|
||||
getDecorationsAtLine(line: number): IterableIterator<IInternalDecoration>;
|
||||
/** Iterates over the decorations at a cell (in no particular order). */
|
||||
getDecorationsAtCell(x: number, line: number, layer?: 'bottom' | 'top'): IterableIterator<IInternalDecoration>;
|
||||
}
|
||||
export interface IInternalDecoration extends IDecoration {
|
||||
readonly options: IDecorationOptions;
|
||||
readonly backgroundColorRGB: IColor | undefined;
|
||||
readonly foregroundColorRGB: IColor | undefined;
|
||||
readonly onRenderEmitter: IEventEmitter<HTMLElement>;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"composite": true,
|
||||
"strict": true,
|
||||
"declarationMap": true,
|
||||
"experimentalDecorators": true
|
||||
"experimentalDecorators": true,
|
||||
"downlevelIteration": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -362,7 +362,7 @@ describe('InputHandler Integration Tests', function(): void {
|
||||
await pollFor(page, async () => await page.evaluate(`(() => _stack)()`), []);
|
||||
});
|
||||
it('14 - GetWinSizePixels', async function(): Promise<any> {
|
||||
await page.evaluate(`window.term.setOption('windowOptions', { getWinSizePixels: true }); `);
|
||||
await page.evaluate(`window.term.options.windowOptions = { getWinSizePixels: true }; `);
|
||||
await page.evaluate(`(() => {
|
||||
window._stack = [];
|
||||
const _h = window.term.onData(data => window._stack.push(data));
|
||||
@@ -373,7 +373,7 @@ describe('InputHandler Integration Tests', function(): void {
|
||||
await pollFor(page, async () => await page.evaluate(`(() => _stack)()`), [`\x1b[4;${d.height};${d.width}t`]);
|
||||
});
|
||||
it('16 - GetCellSizePixels', async function(): Promise<any> {
|
||||
await page.evaluate(`window.term.setOption('windowOptions', { getCellSizePixels: true }); `);
|
||||
await page.evaluate(`window.term.options.windowOptions = { getCellSizePixels: true }; `);
|
||||
await page.evaluate(`(() => {
|
||||
window._stack = [];
|
||||
const _h = window.term.onData(data => window._stack.push(data));
|
||||
@@ -399,13 +399,13 @@ describe('InputHandler Integration Tests', function(): void {
|
||||
});
|
||||
it('query single color', async () => {
|
||||
await writeSync(page, '\x1b]4;0;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x07']);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x1b\\']);
|
||||
await writeSync(page, '\x1b]4;77;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x07', '\x1b]4;77;rgb:5f5f/d7d7/5f5f\x07']);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x1b\\', '\x1b]4;77;rgb:5f5f/d7d7/5f5f\x1b\\']);
|
||||
});
|
||||
it('query multiple colors', async () => {
|
||||
await writeSync(page, '\x1b]4;0;?;77;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x07', '\x1b]4;77;rgb:5f5f/d7d7/5f5f\x07']);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x1b\\', '\x1b]4;77;rgb:5f5f/d7d7/5f5f\x1b\\']);
|
||||
});
|
||||
it('set & query single color', async () => {
|
||||
await writeSync(page, '\x1b]4;0;?\x07');
|
||||
@@ -413,10 +413,10 @@ describe('InputHandler Integration Tests', function(): void {
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), restore);
|
||||
// set new color & query
|
||||
await writeSync(page, '\x1b]4;0;rgb:01/02/03\x07\x1b]4;0;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], '\x1b]4;0;rgb:0101/0202/0303\x07']);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], '\x1b]4;0;rgb:0101/0202/0303\x1b\\']);
|
||||
// restore should set old color
|
||||
await writeSync(page, restore[0] + '\x1b]4;0;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], '\x1b]4;0;rgb:0101/0202/0303\x07', restore[0]]);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], '\x1b]4;0;rgb:0101/0202/0303\x1b\\', restore[0]]);
|
||||
});
|
||||
it('query & set colors mixed', async () => {
|
||||
await writeSync(page, '\x1b]4;0;?;77;?\x07');
|
||||
@@ -424,11 +424,11 @@ describe('InputHandler Integration Tests', function(): void {
|
||||
await page.evaluate('window._recordedData.length = 0;');
|
||||
// mixed call - change 0, query 43, change 77
|
||||
await writeSync(page, '\x1b]4;0;rgb:01/02/03;43;?;77;#aabbcc\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;43;rgb:0000/d7d7/afaf\x07']);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;43;rgb:0000/d7d7/afaf\x1b\\']);
|
||||
await page.evaluate('window._recordedData.length = 0;');
|
||||
// query new values for 0 + 77
|
||||
await writeSync(page, '\x1b]4;0;?;77;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:0101/0202/0303\x07', '\x1b]4;77;rgb:aaaa/bbbb/cccc\x07']);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:0101/0202/0303\x1b\\', '\x1b]4;77;rgb:aaaa/bbbb/cccc\x1b\\']);
|
||||
await page.evaluate('window._recordedData.length = 0;');
|
||||
// restore old values for 0 + 77
|
||||
await writeSync(page, restore[0] + restore[1] + '\x1b]4;0;?;77;?\x07');
|
||||
@@ -451,10 +451,10 @@ describe('InputHandler Integration Tests', function(): void {
|
||||
await writeSync(page, `\x1b]4;${i};?\x07`);
|
||||
const restore: string[] = await page.evaluate('window._recordedData');
|
||||
await writeSync(page, `\x1b]4;${i};rgb:01/02/03\x07\x1b]4;${i};?\x07`);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], `\x1b]4;${i};rgb:0101/0202/0303\x07`]);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], `\x1b]4;${i};rgb:0101/0202/0303\x1b\\`]);
|
||||
// restore slot color
|
||||
await writeSync(page, `\x1b]104;${i}\x07\x1b]4;${i};?\x07`);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], `\x1b]4;${i};rgb:0101/0202/0303\x07`, restore[0]]);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], `\x1b]4;${i};rgb:0101/0202/0303\x1b\\`, restore[0]]);
|
||||
await page.evaluate('window._recordedData.length = 0;');
|
||||
}
|
||||
});
|
||||
@@ -491,62 +491,62 @@ describe('InputHandler Integration Tests', function(): void {
|
||||
});
|
||||
it('query FG color', async () => {
|
||||
await writeSync(page, '\x1b]10;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x07']);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x1b\\']);
|
||||
});
|
||||
it('query BG color', async () => {
|
||||
await writeSync(page, '\x1b]11;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:0000/0000/0000\x07']);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:0000/0000/0000\x1b\\']);
|
||||
});
|
||||
it('query FG & BG color in one call', async () => {
|
||||
await writeSync(page, '\x1b]10;?;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x07', '\x1b]11;rgb:0000/0000/0000\x07']);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x1b\\', '\x1b]11;rgb:0000/0000/0000\x1b\\']);
|
||||
});
|
||||
it('set & query FG', async () => {
|
||||
await writeSync(page, '\x1b]10;rgb:1/2/3\x07\x1b]10;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x07']);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x1b\\']);
|
||||
await writeSync(page, '\x1b]10;#ffffff\x07\x1b]10;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x07', '\x1b]10;rgb:ffff/ffff/ffff\x07']);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x1b\\', '\x1b]10;rgb:ffff/ffff/ffff\x1b\\']);
|
||||
});
|
||||
it('set & query BG', async () => {
|
||||
await writeSync(page, '\x1b]11;rgb:1/2/3\x07\x1b]11;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x07']);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x1b\\']);
|
||||
await writeSync(page, '\x1b]11;#000000\x07\x1b]11;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x07', '\x1b]11;rgb:0000/0000/0000\x07']);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x1b\\', '\x1b]11;rgb:0000/0000/0000\x1b\\']);
|
||||
});
|
||||
it('set & query cursor color', async () => {
|
||||
await writeSync(page, '\x1b]12;rgb:1/2/3\x07\x1b]12;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x07']);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x1b\\']);
|
||||
await writeSync(page, '\x1b]12;#ffffff\x07\x1b]12;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x07', '\x1b]12;rgb:ffff/ffff/ffff\x07']);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x1b\\', '\x1b]12;rgb:ffff/ffff/ffff\x1b\\']);
|
||||
});
|
||||
it('set & query FG & BG color in one call', async () => {
|
||||
await writeSync(page, '\x1b]10;#123456;rgb:aa/bb/cc\x07\x1b]10;?;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1212/3434/5656\x07', '\x1b]11;rgb:aaaa/bbbb/cccc\x07']);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1212/3434/5656\x1b\\', '\x1b]11;rgb:aaaa/bbbb/cccc\x1b\\']);
|
||||
await writeSync(page, '\x1b]10;#ffffff;#000000\x07');
|
||||
});
|
||||
it('OSC 110: restore FG color', async () => {
|
||||
await writeSync(page, '\x1b]10;rgb:1/2/3\x07\x1b]10;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x07']);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x1b\\']);
|
||||
await page.evaluate('window._recordedData.length = 0;');
|
||||
// restore
|
||||
await writeSync(page, '\x1b]110\x07\x1b]10;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x07']);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x1b\\']);
|
||||
});
|
||||
it('OSC 111: restore BG color', async () => {
|
||||
await writeSync(page, '\x1b]11;rgb:1/2/3\x07\x1b]11;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x07']);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x1b\\']);
|
||||
await page.evaluate('window._recordedData.length = 0;');
|
||||
// restore
|
||||
await writeSync(page, '\x1b]111\x07\x1b]11;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:0000/0000/0000\x07']);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:0000/0000/0000\x1b\\']);
|
||||
});
|
||||
it('OSC 112: restore cursor color', async () => {
|
||||
await writeSync(page, '\x1b]12;rgb:1/2/3\x07\x1b]12;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x07']);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x1b\\']);
|
||||
await page.evaluate('window._recordedData.length = 0;');
|
||||
// restore
|
||||
await writeSync(page, '\x1b]112\x07\x1b]12;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:ffff/ffff/ffff\x07']);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:ffff/ffff/ffff\x1b\\']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -229,7 +229,7 @@ describe('Mouse Tracking Tests', async () => {
|
||||
window.calls = [];
|
||||
window.term.onData(e => calls.push( Array.from(e).map(el => el.charCodeAt(0)) ));
|
||||
window.term.onBinary(e => calls.push( Array.from(e).map(el => el.charCodeAt(0)) ));
|
||||
window.term.setOption('fontSize', ${fontSize});
|
||||
window.term.options.fontSize = ${fontSize};
|
||||
window.term.resize(${cols}, ${rows});
|
||||
`);
|
||||
});
|
||||
|
||||
+73
-51
@@ -574,7 +574,7 @@ describe('API Integration Tests', function(): void {
|
||||
await page.evaluate(`window.term.scrollLines(10)`);
|
||||
await page.evaluate(`window.term.addMarker(3)`);
|
||||
await page.evaluate(`window.term.addMarker(4)`);
|
||||
await page.evaluate(`
|
||||
await page.evaluate(`
|
||||
for (let i = 0; i < window.term.markers.length; ++i) {
|
||||
const marker = window.term.markers[i];
|
||||
marker.onDispose(() => window.disposeStack.push(marker));
|
||||
@@ -732,54 +732,69 @@ describe('API Integration Tests', function(): void {
|
||||
});
|
||||
|
||||
describe('registerDecoration', () => {
|
||||
it('should register decorations and render them', async () => {
|
||||
await openTerminal(page);
|
||||
await writeSync(page, '\\n\\n\\n\\n');
|
||||
await writeSync(page, '\\n\\n\\n\\n');
|
||||
await writeSync(page, '\\n\\n\\n\\n');
|
||||
await page.evaluate(`window.marker1 = window.term.addMarker(1)`);
|
||||
await page.evaluate(`window.marker2 = window.term.addMarker(2)`);
|
||||
await page.evaluate(`window.term.registerDecoration({ marker: window.marker1 })`);
|
||||
await page.evaluate(`window.term.registerDecoration({ marker: window.marker2 })`);
|
||||
await page.evaluate(`window.term.resize(10, 5)`);
|
||||
assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 2);
|
||||
describe('bufferDecorations', () => {
|
||||
it('should register decorations and render them when terminal open is called', async () => {
|
||||
await page.evaluate(`window.term = new Terminal({})`);
|
||||
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
|
||||
await page.waitForSelector('.xterm-text-layer');
|
||||
await page.evaluate(`window.marker1 = window.term.addMarker(1)`);
|
||||
await page.evaluate(`window.marker2 = window.term.addMarker(2)`);
|
||||
await page.evaluate(`window.term.registerDecoration({ marker: window.marker1 })`);
|
||||
await page.evaluate(`window.term.registerDecoration({ marker: window.marker2 })`);
|
||||
await openTerminal(page);
|
||||
await pollFor(page, `document.querySelectorAll('.xterm-screen .xterm-decoration').length`, 2);
|
||||
});
|
||||
it('should return undefined when the marker has already been disposed of', async () => {
|
||||
await openTerminal(page);
|
||||
await page.evaluate(`window.marker = window.term.addMarker(1)`);
|
||||
await page.evaluate(`window.marker.dispose()`);
|
||||
await pollFor(page, `window.decoration = window.term.registerDecoration({ marker: window.marker });`, undefined);
|
||||
});
|
||||
it('should throw when a negative x offset is provided', async () => {
|
||||
await openTerminal(page);
|
||||
await page.evaluate(`window.marker = window.term.addMarker(1)`);
|
||||
await page.evaluate(`
|
||||
try {
|
||||
window.decoration = window.term.registerDecoration({ marker: window.marker, x: -2 });
|
||||
} catch (e) {
|
||||
window.throwMessage = e.message;
|
||||
}
|
||||
`);
|
||||
await pollFor(page, 'window.throwMessage', 'This API only accepts positive integers');
|
||||
});
|
||||
});
|
||||
it('on resize should dispose of the old decoration and create a new one', async () => {
|
||||
await openTerminal(page);
|
||||
await writeSync(page, '\\n\\n\\n\\n');
|
||||
await writeSync(page, '\\n\\n\\n\\n');
|
||||
await page.evaluate(`window.marker = window.term.addMarker(1)`);
|
||||
await page.evaluate(`window.decoration = window.term.registerDecoration({ marker: window.marker })`);
|
||||
await page.evaluate(`window.term.resize(10, 5)`);
|
||||
assert.equal(await page.evaluate(`document.querySelectorAll('.xterm-screen .xterm-decoration').length`), 1);
|
||||
});
|
||||
it('should return undefined when the marker has already been disposed of', async () => {
|
||||
await openTerminal(page);
|
||||
await writeSync(page, '\\n\\n\\n\\n');
|
||||
await writeSync(page, '\\n\\n\\n\\n');
|
||||
await page.evaluate(`window.marker = window.term.addMarker(1)`);
|
||||
await page.evaluate(`window.marker.dispose()`);
|
||||
assert.equal(await page.evaluate(`window.decoration = window.term.registerDecoration({ marker: window.marker });`), undefined);
|
||||
});
|
||||
it('should throw when a negative x offset is provided', async () => {
|
||||
await openTerminal(page);
|
||||
await writeSync(page, '\\n\\n\\n\\n');
|
||||
await writeSync(page, '\\n\\n\\n\\n');
|
||||
await page.evaluate(`window.marker = window.term.addMarker(1)`);
|
||||
await page.evaluate(`
|
||||
try {
|
||||
window.decoration = window.term.registerDecoration({ marker: window.marker, x: -2 });
|
||||
} catch (e) {
|
||||
window.throwMessage = e.message;
|
||||
}
|
||||
`);
|
||||
await pollFor(page, 'window.throwMessage', 'This API only accepts positive integers');
|
||||
describe('overviewRulerDecorations', () => {
|
||||
it('should not add an overview ruler when width is not set', async () => {
|
||||
await page.evaluate(`window.term = new Terminal({})`);
|
||||
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
|
||||
await page.waitForSelector('.xterm-text-layer');
|
||||
await page.evaluate(`window.marker1 = window.term.addMarker(1)`);
|
||||
await page.evaluate(`window.marker2 = window.term.addMarker(2)`);
|
||||
await page.evaluate(`window.term.registerDecoration({ marker: window.marker1, overviewRulerOptions: { color: 'red', position: 'full' } })`);
|
||||
await page.evaluate(`window.term.registerDecoration({ marker: window.marker2, overviewRulerOptions: { color: 'blue', position: 'full' } })`);
|
||||
await openTerminal(page);
|
||||
await pollFor(page, `document.querySelectorAll('.xterm-decoration-overview-ruler').length`, 0);
|
||||
});
|
||||
it('should add an overview ruler when width is set', async () => {
|
||||
await page.evaluate(`window.term = new Terminal({ overviewRulerWidth: 15 })`);
|
||||
await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
|
||||
await page.waitForSelector('.xterm-text-layer');
|
||||
await page.evaluate(`window.marker1 = window.term.addMarker(1)`);
|
||||
await page.evaluate(`window.marker2 = window.term.addMarker(2)`);
|
||||
await page.evaluate(`window.term.registerDecoration({ marker: window.marker1, overviewRulerOptions: { color: 'red', position: 'full' } })`);
|
||||
await page.evaluate(`window.term.registerDecoration({ marker: window.marker2, overviewRulerOptions: { color: 'blue', position: 'full' } })`);
|
||||
await openTerminal(page);
|
||||
await pollFor(page, `document.querySelectorAll('.xterm-decoration-overview-ruler').length`, 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('registerLinkProvider', () => {
|
||||
it('should fire provideLinks when hovering cells', async () => {
|
||||
await openTerminal(page, { rendererType: 'dom' });
|
||||
// Focus the terminal as the cursor will show and trigger a rerender, which can clear the
|
||||
// active link
|
||||
await page.evaluate('window.term.focus()');
|
||||
await page.evaluate(`
|
||||
window.calls = [];
|
||||
window.disposable = window.term.registerLinkProvider({
|
||||
@@ -799,6 +814,9 @@ describe('API Integration Tests', function(): void {
|
||||
|
||||
it('should fire hover and leave events on the link', async () => {
|
||||
await openTerminal(page, { rendererType: 'dom' });
|
||||
// Focus the terminal as the cursor will show and trigger a rerender, which can clear the
|
||||
// active link
|
||||
await page.evaluate('window.term.focus()');
|
||||
await writeSync(page, 'foo bar baz');
|
||||
// Wait for renderer to catch up as links are cleared on render
|
||||
await pollFor(page, `document.querySelector('.xterm-rows').textContent`, 'foo bar baz ');
|
||||
@@ -834,6 +852,9 @@ describe('API Integration Tests', function(): void {
|
||||
|
||||
it('should work fine when hover and leave callbacks are not provided', async () => {
|
||||
await openTerminal(page, { rendererType: 'dom' });
|
||||
// Focus the terminal as the cursor will show and trigger a rerender, which can clear the
|
||||
// active link
|
||||
await page.evaluate('window.term.focus()');
|
||||
await writeSync(page, 'foo bar baz');
|
||||
// Wait for renderer to catch up as links are cleared on render
|
||||
await pollFor(page, `document.querySelector('.xterm-rows').textContent`, 'foo bar baz ');
|
||||
@@ -874,18 +895,12 @@ describe('API Integration Tests', function(): void {
|
||||
|
||||
it('should fire activate events when clicking the link', async () => {
|
||||
await openTerminal(page, { rendererType: 'dom' });
|
||||
// Focus the terminal as the cursor will show and trigger a rerender, which can clear the
|
||||
// active link
|
||||
await page.evaluate('window.term.focus()');
|
||||
await writeSync(page, 'a b c');
|
||||
|
||||
// Wait for renderer to catch up as links are cleared on render
|
||||
await pollFor(page, `document.querySelector('.xterm-rows').textContent`, 'a b c ');
|
||||
|
||||
// Focus terminal to avoid a render event clearing the active link
|
||||
const dims = await getDimensions();
|
||||
await moveMouseCell(page, dims, 5, 5);
|
||||
await page.mouse.down();
|
||||
await page.mouse.up();
|
||||
await timeout(200); // Not sure how to avoid this timeout, checking for xterm-focus doesn't help
|
||||
|
||||
await page.evaluate(`
|
||||
window.calls = [];
|
||||
window.disposable = window.term.registerLinkProvider({
|
||||
@@ -901,6 +916,7 @@ describe('API Integration Tests', function(): void {
|
||||
}
|
||||
});
|
||||
`);
|
||||
const dims = await getDimensions();
|
||||
await moveMouseCell(page, dims, 3, 1);
|
||||
await pollFor(page, `window.calls`, ['provide 1', 'hover 1']);
|
||||
await page.mouse.down();
|
||||
@@ -921,6 +937,9 @@ describe('API Integration Tests', function(): void {
|
||||
|
||||
it('should work when multiple links are provided on the same line', async () => {
|
||||
await openTerminal(page, { rendererType: 'dom' });
|
||||
// Focus the terminal as the cursor will show and trigger a rerender, which can clear the
|
||||
// active link
|
||||
await page.evaluate('window.term.focus()');
|
||||
await writeSync(page, 'foo bar baz');
|
||||
// Wait for renderer to catch up as links are cleared on render
|
||||
await pollFor(page, `document.querySelector('.xterm-rows').textContent`, 'foo bar baz ');
|
||||
@@ -967,6 +986,9 @@ describe('API Integration Tests', function(): void {
|
||||
|
||||
it('should dispose links when hovering away', async () => {
|
||||
await openTerminal(page, { rendererType: 'dom' });
|
||||
// Focus the terminal as the cursor will show and trigger a rerender, which can clear the
|
||||
// active link
|
||||
await page.evaluate('window.term.focus()');
|
||||
await writeSync(page, 'foo bar baz');
|
||||
// Wait for renderer to catch up as links are cleared on render
|
||||
await pollFor(page, `document.querySelector('.xterm-rows').textContent`, 'foo bar baz ');
|
||||
|
||||
Vendored
+80
-19
@@ -266,6 +266,12 @@ declare module 'xterm' {
|
||||
* All features are disabled by default for security reasons.
|
||||
*/
|
||||
windowOptions?: IWindowOptions;
|
||||
|
||||
/**
|
||||
* The width, in pixels, of the canvas for the overview ruler. The overview
|
||||
* ruler will be hidden when not set.
|
||||
*/
|
||||
overviewRulerWidth?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -282,6 +288,8 @@ declare module 'xterm' {
|
||||
cursorAccent?: string;
|
||||
/** The selection background color (can be transparent) */
|
||||
selection?: string;
|
||||
/** The selection foreground color */
|
||||
selectionForeground?: string;
|
||||
/** ANSI black (eg. `\x1b[30m`) */
|
||||
black?: string;
|
||||
/** ANSI red (eg. `\x1b[31m`) */
|
||||
@@ -394,7 +402,7 @@ declare module 'xterm' {
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a disposable with an
|
||||
* Represents a disposable that tracks is disposed state.
|
||||
* @param onDispose event listener and
|
||||
* @param isDisposed property.
|
||||
*/
|
||||
@@ -427,49 +435,91 @@ declare module 'xterm' {
|
||||
readonly onRender: IEvent<HTMLElement>;
|
||||
|
||||
/**
|
||||
* The HTMLElement that gets created after the
|
||||
* first _onRender call, or undefined if accessed before
|
||||
* The element that the decoration is rendered to. This will be undefined
|
||||
* until it is rendered for the first time by {@link IDecoration.onRender}.
|
||||
* that.
|
||||
*/
|
||||
readonly element: HTMLElement | undefined;
|
||||
element: HTMLElement | undefined;
|
||||
|
||||
/**
|
||||
* The options for the overview ruler that can be updated.
|
||||
* This will only take effect when {@link IDecorationOptions.overviewRulerOptions}
|
||||
* were provided initially.
|
||||
*/
|
||||
options: Pick<IDecorationOptions, 'overviewRulerOptions'>;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Options provided when registering a decoration
|
||||
* containing a @param marker, @param anchor,
|
||||
* @param x offset from the anchor, @param width in cells
|
||||
* and @param height in cells.
|
||||
* Overview ruler decoration options
|
||||
*/
|
||||
interface IDecorationOverviewRulerOptions {
|
||||
color: string;
|
||||
position?: 'left' | 'center' | 'right' | 'full';
|
||||
}
|
||||
|
||||
/*
|
||||
* Options that define the presentation of the decoration.
|
||||
*/
|
||||
export interface IDecorationOptions {
|
||||
/**
|
||||
* The line in the terminal where
|
||||
* the decoration will be displayed
|
||||
*/
|
||||
marker: IMarker;
|
||||
readonly marker: IMarker;
|
||||
|
||||
/*
|
||||
* Where the decoration will be anchored -
|
||||
* defaults to the left edge
|
||||
*/
|
||||
anchor?: 'right' | 'left';
|
||||
readonly anchor?: 'right' | 'left';
|
||||
|
||||
/**
|
||||
* The x position offset relative to the anchor
|
||||
*/
|
||||
x?: number;
|
||||
*/
|
||||
readonly x?: number;
|
||||
|
||||
|
||||
/**
|
||||
* The width of the decoration in cells, which defaults to
|
||||
* cell width
|
||||
* The width of the decoration in cells, defaults to 1.
|
||||
*/
|
||||
width?: number;
|
||||
readonly width?: number;
|
||||
|
||||
/**
|
||||
* The height of the decoration in cells, which defaults to
|
||||
* cell height
|
||||
* The height of the decoration in cells, defaults to 1.
|
||||
*/
|
||||
height?: number;
|
||||
readonly height?: number;
|
||||
|
||||
/**
|
||||
* The background color of the cell(s). When 2 decorations both set the foreground color the
|
||||
* last registered decoration will be used. Only the `#RRGGBB` format is supported.
|
||||
*/
|
||||
readonly backgroundColor?: string;
|
||||
|
||||
/**
|
||||
* The foreground color of the cell(s). When 2 decorations both set the foreground color the
|
||||
* last registered decoration will be used. Only the `#RRGGBB` format is supported.
|
||||
*/
|
||||
readonly foregroundColor?: string;
|
||||
|
||||
/**
|
||||
* What layer to render the decoration at when {@link backgroundColor} or
|
||||
* {@link foregroundColor} are used. `'bottom'` will render under the selection, `'top`' will
|
||||
* render above the selection\*.
|
||||
*
|
||||
* *\* The selection will render on top regardless of layer on the canvas renderer due to how
|
||||
* it renders selection separately.*
|
||||
*/
|
||||
readonly layer?: 'bottom' | 'top';
|
||||
|
||||
/**
|
||||
* When defined, renders the decoration in the overview ruler to the right
|
||||
* of the terminal. {@link ITerminalOptions.overviewRulerWidth} must be set
|
||||
* in order to see the overview ruler.
|
||||
* @param color The color of the decoration.
|
||||
* @param position The position of the decoration.
|
||||
*/
|
||||
overviewRulerOptions?: IDecorationOverviewRulerOptions
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -791,6 +841,17 @@ declare module 'xterm' {
|
||||
*/
|
||||
onRender: IEvent<{ start: number, end: number }>;
|
||||
|
||||
/**
|
||||
* Adds an event listener for when data has been parsed by the terminal,
|
||||
* after {@link write} is called. This event is useful to listen for any
|
||||
* changes in the buffer.
|
||||
*
|
||||
* This fires at most once per frame, after data parsing completes. Note
|
||||
* that this can fire when there are still writes pending if there is a lot
|
||||
* of data.
|
||||
*/
|
||||
onWriteParsed: IEvent<void>;
|
||||
|
||||
/**
|
||||
* Adds an event listener for when the terminal is resized. The event value
|
||||
* contains the new size.
|
||||
@@ -939,7 +1000,7 @@ declare module 'xterm' {
|
||||
|
||||
/**
|
||||
* (EXPERIMENTAL) Adds a decoration to the terminal using
|
||||
* @param decorationOptions, which takes a marker and an optional anchor,
|
||||
* @param decorationOptions, which takes a marker and an optional anchor,
|
||||
* width, height, and x offset from the anchor. Returns the decoration or
|
||||
* undefined if the alt buffer is active or the marker has already been disposed of.
|
||||
* @throws when options include a negative x offset.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3":
|
||||
"@babel/code-frame@^7.8.3":
|
||||
version "7.8.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e"
|
||||
integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==
|
||||
@@ -144,6 +144,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8"
|
||||
integrity sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==
|
||||
|
||||
"@babel/runtime@^7.15.4":
|
||||
version "7.17.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.9.tgz#d19fbf802d01a8cb6cf053a64e472d42c434ba72"
|
||||
integrity sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/template@^7.7.4", "@babel/template@^7.8.3", "@babel/template@^7.8.6":
|
||||
version "7.8.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b"
|
||||
@@ -355,9 +362,9 @@
|
||||
integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==
|
||||
|
||||
"@types/mocha@^8.2.1":
|
||||
version "8.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.2.2.tgz#91daa226eb8c2ff261e6a8cbf8c7304641e095e0"
|
||||
integrity sha512-Lwh0lzzqT5Pqh6z61P3c3P5nm6fzQK/MMHl9UKeneAeInVflBSz1O2EkX6gM6xfJd7FBXBY5purtLx7fUiZ7Hw==
|
||||
version "8.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.2.3.tgz#bbeb55fbc73f28ea6de601fbfa4613f58d785323"
|
||||
integrity sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw==
|
||||
|
||||
"@types/mocha@^9.0.0":
|
||||
version "9.0.0"
|
||||
@@ -375,9 +382,9 @@
|
||||
integrity sha512-hkzMMD3xu6BrJpGVLeQ3htQQNAcOrJjX7WFmtK8zWQpz2UJf13LCFF2ALA7c9OVdvc2vQJeDdjfR35M0sBCxvw==
|
||||
|
||||
"@types/node@^12.12.37":
|
||||
version "12.20.12"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.12.tgz#fd9c1c2cfab536a2383ed1ef70f94adea743a226"
|
||||
integrity sha512-KQZ1al2hKOONAs2MFv+yTQP1LkDWMrRJ9YCVRalXltOfXsBmH5IownLxQaiq0lnAHwAViLnh2aTYqrPcRGEbgg==
|
||||
version "12.20.50"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.50.tgz#14ba5198f1754ffd0472a2f84ab433b45ee0b65e"
|
||||
integrity sha512-+9axpWx2b2JCVovr7Ilgt96uc6C1zBKOQMpGtRbWT9IoR/8ue32GGMfGA4woP8QyP2gBs6GQWEVM3tCybGCxDA==
|
||||
|
||||
"@types/node@^14.14.44":
|
||||
version "14.14.44"
|
||||
@@ -390,9 +397,9 @@
|
||||
integrity sha512-+hQX+WyJAOne7Fh3zF5CxPemILIbuhNcqHHodzK9caYOLnC8pD5efmPleRnw0z++LfKUC/sVNMwk0Gap+B0baA==
|
||||
|
||||
"@types/puppeteer@^5.4.3":
|
||||
version "5.4.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-5.4.3.tgz#cdca84aa7751d77448d8a477dbfa0af1f11485f2"
|
||||
integrity sha512-3nE8YgR9DIsgttLW+eJf6mnXxq8Ge+27m5SU3knWmrlfl6+KOG0Bf9f7Ua7K+C4BnaTMAh3/UpySqdAYvrsvjg==
|
||||
version "5.4.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-5.4.6.tgz#afc438e41dcbc27ca1ba0235ea464a372db2b21c"
|
||||
integrity sha512-98Kghehs7+/GD9b56qryhqdqVCXUTbetTv3PlvDnmFRTHQH0j9DIp1f7rkAW3BAj4U3yoeSEQnKgdW8bDq0Y0Q==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
@@ -422,13 +429,6 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/yauzl@^2.9.1":
|
||||
version "2.9.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.2.tgz#c48e5d56aff1444409e39fa164b0b4d4552a7b7a"
|
||||
integrity sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@typescript-eslint/eslint-plugin@^5.3.0":
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.3.0.tgz#a55ae72d28ffeb6badd817fe4566c9cced1f5e29"
|
||||
@@ -824,7 +824,7 @@ acorn@^8.4.1, acorn@^8.5.0:
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2"
|
||||
integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==
|
||||
|
||||
agent-base@6, agent-base@^6.0.2:
|
||||
agent-base@6:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
|
||||
integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
|
||||
@@ -869,20 +869,10 @@ ansi-colors@4.1.1, ansi-colors@^4.1.1:
|
||||
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
|
||||
integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
|
||||
|
||||
ansi-regex@^2.0.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
|
||||
integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8=
|
||||
|
||||
ansi-regex@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
|
||||
integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
|
||||
|
||||
ansi-regex@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
|
||||
integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
|
||||
ansi-regex@^5.0.0, ansi-regex@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
|
||||
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
|
||||
|
||||
ansi-styles@^3.2.1:
|
||||
version "3.2.1"
|
||||
@@ -899,14 +889,6 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
|
||||
"@types/color-name" "^1.1.1"
|
||||
color-convert "^2.0.1"
|
||||
|
||||
anymatch@~3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142"
|
||||
integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==
|
||||
dependencies:
|
||||
normalize-path "^3.0.0"
|
||||
picomatch "^2.0.4"
|
||||
|
||||
anymatch@~3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
|
||||
@@ -1038,21 +1020,11 @@ browserslist@^4.14.5:
|
||||
escalade "^3.1.1"
|
||||
node-releases "^1.1.71"
|
||||
|
||||
buffer-crc32@~0.2.3:
|
||||
version "0.2.13"
|
||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||
integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
|
||||
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
|
||||
|
||||
builtin-modules@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
|
||||
integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=
|
||||
|
||||
bytes@3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
|
||||
@@ -1108,7 +1080,7 @@ chai@^4.3.4:
|
||||
pathval "^1.1.1"
|
||||
type-detect "^4.0.5"
|
||||
|
||||
chalk@^2.0.0, chalk@^2.3.0:
|
||||
chalk@^2.0.0:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
|
||||
@@ -1138,21 +1110,6 @@ check-error@^1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
|
||||
integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=
|
||||
|
||||
chokidar@3.5.1:
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a"
|
||||
integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==
|
||||
dependencies:
|
||||
anymatch "~3.1.1"
|
||||
braces "~3.0.2"
|
||||
glob-parent "~5.1.0"
|
||||
is-binary-path "~2.1.0"
|
||||
is-glob "~4.0.1"
|
||||
normalize-path "~3.0.0"
|
||||
readdirp "~3.5.0"
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.1"
|
||||
|
||||
chokidar@3.5.3:
|
||||
version "3.5.3"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
|
||||
@@ -1179,9 +1136,9 @@ clean-stack@^2.0.0:
|
||||
integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
|
||||
|
||||
cli-table@^0.3.6:
|
||||
version "0.3.6"
|
||||
resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.6.tgz#e9d6aa859c7fe636981fd3787378c2a20bce92fc"
|
||||
integrity sha512-ZkNZbnZjKERTY5NwC2SeMeLeifSPq/pubeRoTpdr3WchLlnZg6hEgvHkK5zL7KNFdd9PmHN8lxrENUwI3cE8vQ==
|
||||
version "0.3.11"
|
||||
resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.11.tgz#ac69cdecbe81dccdba4889b9a18b7da312a9d3ee"
|
||||
integrity sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==
|
||||
dependencies:
|
||||
colors "1.0.3"
|
||||
|
||||
@@ -1257,11 +1214,11 @@ colors@1.0.3:
|
||||
integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=
|
||||
|
||||
columnify@^1.5.4:
|
||||
version "1.5.4"
|
||||
resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.5.4.tgz#4737ddf1c7b69a8a7c340570782e947eec8e78bb"
|
||||
integrity sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.6.0.tgz#6989531713c9008bb29735e61e37acf5bd553cf3"
|
||||
integrity sha512-lomjuFZKfM6MSAnV9aCZC9sc0qGbmZdfygNv+nCpqVkSKdCxCklLtd16O0EILGkImHw9ZpHkAnHaB+8Zxq5W6Q==
|
||||
dependencies:
|
||||
strip-ansi "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
wcwidth "^1.0.0"
|
||||
|
||||
combined-stream@^1.0.8:
|
||||
@@ -1271,7 +1228,7 @@ combined-stream@^1.0.8:
|
||||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
commander@^2.12.1, commander@^2.20.0:
|
||||
commander@^2.20.0:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
@@ -1286,20 +1243,15 @@ commander@^7.0.0:
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
|
||||
integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
|
||||
|
||||
commander@^8.2.0:
|
||||
version "8.3.0"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
|
||||
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
|
||||
|
||||
commondir@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
|
||||
integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
|
||||
|
||||
complex.js@^2.0.11:
|
||||
version "2.0.12"
|
||||
resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.0.12.tgz#fa4df97d8928e5f7b6a86b35bdeecc3a3eda8a22"
|
||||
integrity sha512-oQX99fwL6LrTVg82gDY1dIWXy6qZRnRL35N+YhIX0N7tSwsa0KFy6IEMHTNuCW4mP7FS7MEqZ/2I/afzYwPldw==
|
||||
complex.js@^2.0.15:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.1.1.tgz#0675dac8e464ec431fb2ab7d30f41d889fb25c31"
|
||||
integrity sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg==
|
||||
|
||||
concat-map@0.0.1:
|
||||
version "0.0.1"
|
||||
@@ -1391,13 +1343,6 @@ debug@4, debug@^4.1.0, debug@^4.1.1:
|
||||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
debug@4.3.1:
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
|
||||
integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
|
||||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
debug@4.3.3:
|
||||
version "4.3.3"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
|
||||
@@ -1405,7 +1350,7 @@ debug@4.3.3:
|
||||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
debug@^4.3.1, debug@^4.3.2:
|
||||
debug@^4.3.2:
|
||||
version "4.3.2"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
|
||||
integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
|
||||
@@ -1422,12 +1367,7 @@ decamelize@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837"
|
||||
integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==
|
||||
|
||||
decimal.js@^10.0.0, decimal.js@^10.2.1:
|
||||
version "10.2.1"
|
||||
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.1.tgz#238ae7b0f0c793d3e3cea410108b35a2c01426a3"
|
||||
integrity sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==
|
||||
|
||||
decimal.js@^10.3.1:
|
||||
decimal.js@^10.0.0, decimal.js@^10.3.1:
|
||||
version "10.3.1"
|
||||
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783"
|
||||
integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==
|
||||
@@ -1506,11 +1446,6 @@ diff@5.0.0:
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b"
|
||||
integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==
|
||||
|
||||
diff@^4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
|
||||
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
|
||||
|
||||
dir-glob@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
|
||||
@@ -1552,13 +1487,6 @@ encodeurl@~1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
||||
integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
|
||||
|
||||
end-of-stream@^1.1.0:
|
||||
version "1.4.4"
|
||||
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0"
|
||||
integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==
|
||||
dependencies:
|
||||
once "^1.4.0"
|
||||
|
||||
enhanced-resolve@^5.0.0, enhanced-resolve@^5.8.3:
|
||||
version "5.8.3"
|
||||
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz#6d552d465cce0423f5b3d718511ea53826a7b2f0"
|
||||
@@ -1672,11 +1600,6 @@ escape-string-regexp@^1.0.5:
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
||||
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
|
||||
|
||||
escape-string-regexp@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344"
|
||||
integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==
|
||||
|
||||
escodegen@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd"
|
||||
@@ -1882,17 +1805,6 @@ express@^4.17.1:
|
||||
utils-merge "1.0.1"
|
||||
vary "~1.1.2"
|
||||
|
||||
extract-zip@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a"
|
||||
integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==
|
||||
dependencies:
|
||||
debug "^4.1.1"
|
||||
get-stream "^5.1.0"
|
||||
yauzl "^2.10.0"
|
||||
optionalDependencies:
|
||||
"@types/yauzl" "^2.9.1"
|
||||
|
||||
fast-deep-equal@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4"
|
||||
@@ -1936,13 +1848,6 @@ fastq@^1.6.0:
|
||||
dependencies:
|
||||
reusify "^1.0.4"
|
||||
|
||||
fd-slicer@~1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
|
||||
integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=
|
||||
dependencies:
|
||||
pend "~1.2.0"
|
||||
|
||||
file-entry-cache@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
|
||||
@@ -2040,10 +1945,10 @@ forwarded@~0.1.2:
|
||||
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
|
||||
integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=
|
||||
|
||||
fraction.js@^4.0.13:
|
||||
version "4.0.13"
|
||||
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.0.13.tgz#3c1c315fa16b35c85fffa95725a36fa729c69dfe"
|
||||
integrity sha512-E1fz2Xs9ltlUp+qbiyx9wmt2n9dRzPsS11Jtdb8D2o+cC7wr9xkkKsVKJuBX0ST+LVS+LhLO+SbLJNtfWcJvXA==
|
||||
fraction.js@^4.1.1:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
|
||||
integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==
|
||||
|
||||
fresh@0.5.2:
|
||||
version "0.5.2"
|
||||
@@ -2060,7 +1965,7 @@ fs.realpath@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
|
||||
|
||||
fsevents@~2.3.1, fsevents@~2.3.2:
|
||||
fsevents@~2.3.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
|
||||
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
|
||||
@@ -2104,19 +2009,12 @@ get-package-type@^0.1.0:
|
||||
resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
|
||||
integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==
|
||||
|
||||
get-stream@^5.1.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3"
|
||||
integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==
|
||||
dependencies:
|
||||
pump "^3.0.0"
|
||||
|
||||
get-stream@^6.0.0:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
|
||||
integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
|
||||
|
||||
glob-parent@^5.1.2, glob-parent@~5.1.0, glob-parent@~5.1.2:
|
||||
glob-parent@^5.1.2, glob-parent@~5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
||||
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
|
||||
@@ -2135,18 +2033,6 @@ glob-to-regexp@^0.4.1:
|
||||
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
|
||||
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
|
||||
|
||||
glob@7.1.6, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
|
||||
version "7.1.6"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
|
||||
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
|
||||
dependencies:
|
||||
fs.realpath "^1.0.0"
|
||||
inflight "^1.0.4"
|
||||
inherits "2"
|
||||
minimatch "^3.0.4"
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
glob@7.2.0, glob@^7.2.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
|
||||
@@ -2159,10 +2045,10 @@ glob@7.2.0, glob@^7.2.0:
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
glob@^7.1.1:
|
||||
version "7.1.7"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
|
||||
integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==
|
||||
glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
|
||||
version "7.1.6"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
|
||||
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
|
||||
dependencies:
|
||||
fs.realpath "^1.0.0"
|
||||
inflight "^1.0.4"
|
||||
@@ -2379,11 +2265,6 @@ interpret@^2.2.0:
|
||||
resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9"
|
||||
integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==
|
||||
|
||||
ip@^1.1.5:
|
||||
version "1.1.5"
|
||||
resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a"
|
||||
integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=
|
||||
|
||||
ipaddr.js@1.9.1:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
|
||||
@@ -2437,11 +2318,6 @@ is-extglob@^2.1.1:
|
||||
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
|
||||
integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
|
||||
|
||||
is-fullwidth-code-point@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
|
||||
integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
|
||||
|
||||
is-fullwidth-code-point@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
|
||||
@@ -2666,23 +2542,11 @@ jest-worker@^27.0.6:
|
||||
merge-stream "^2.0.0"
|
||||
supports-color "^8.0.0"
|
||||
|
||||
jpeg-js@^0.4.2:
|
||||
version "0.4.3"
|
||||
resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.3.tgz#6158e09f1983ad773813704be80680550eff977b"
|
||||
integrity sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q==
|
||||
|
||||
js-tokens@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
|
||||
|
||||
js-yaml@4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.0.0.tgz#f426bc0ff4b4051926cd588c71113183409a121f"
|
||||
integrity sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==
|
||||
dependencies:
|
||||
argparse "^2.0.1"
|
||||
|
||||
js-yaml@4.1.0, js-yaml@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
|
||||
@@ -2813,13 +2677,6 @@ lodash@^4.17.13:
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
||||
log-symbols@4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920"
|
||||
integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==
|
||||
dependencies:
|
||||
chalk "^4.0.0"
|
||||
|
||||
log-symbols@4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503"
|
||||
@@ -2843,14 +2700,15 @@ make-dir@^3.0.0, make-dir@^3.0.2:
|
||||
semver "^6.0.0"
|
||||
|
||||
mathjs@^9.3.0:
|
||||
version "9.3.2"
|
||||
resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-9.3.2.tgz#6523dd5c963d200ff1cea0ff7963b10521b82185"
|
||||
integrity sha512-0YKSKAeN9OkbIQrxfxnBT4kk/KlH71piWOsvVvAasyRIj/Xd/zlpc5VP/aFxwr+llOq2F3f6booPEu2fWv3yjQ==
|
||||
version "9.5.2"
|
||||
resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-9.5.2.tgz#e0f3279320dc6f49e45d99c4fcdd8b52231f0462"
|
||||
integrity sha512-c0erTq0GP503/Ch2OtDOAn50GIOsuxTMjmE00NI/vKJFSWrDaQHRjx6ai+16xYv70yBSnnpUgHZGNf9FR9IwmA==
|
||||
dependencies:
|
||||
complex.js "^2.0.11"
|
||||
decimal.js "^10.2.1"
|
||||
"@babel/runtime" "^7.15.4"
|
||||
complex.js "^2.0.15"
|
||||
decimal.js "^10.3.1"
|
||||
escape-latex "^1.2.0"
|
||||
fraction.js "^4.0.13"
|
||||
fraction.js "^4.1.1"
|
||||
javascript-natural-sort "^0.7.1"
|
||||
seedrandom "^3.0.5"
|
||||
tiny-emitter "^2.1.0"
|
||||
@@ -2930,11 +2788,6 @@ mime@1.6.0:
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
|
||||
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
|
||||
|
||||
mime@^2.4.6:
|
||||
version "2.5.2"
|
||||
resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe"
|
||||
integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==
|
||||
|
||||
mimic-fn@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
||||
@@ -2948,47 +2801,9 @@ minimatch@3.0.4, minimatch@^3.0.4:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
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.3:
|
||||
version "0.5.5"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
|
||||
integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
|
||||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
|
||||
mocha@^8.3.2:
|
||||
version "8.4.0"
|
||||
resolved "https://registry.yarnpkg.com/mocha/-/mocha-8.4.0.tgz#677be88bf15980a3cae03a73e10a0fc3997f0cff"
|
||||
integrity sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==
|
||||
dependencies:
|
||||
"@ungap/promise-all-settled" "1.1.2"
|
||||
ansi-colors "4.1.1"
|
||||
browser-stdout "1.3.1"
|
||||
chokidar "3.5.1"
|
||||
debug "4.3.1"
|
||||
diff "5.0.0"
|
||||
escape-string-regexp "4.0.0"
|
||||
find-up "5.0.0"
|
||||
glob "7.1.6"
|
||||
growl "1.10.5"
|
||||
he "1.2.0"
|
||||
js-yaml "4.0.0"
|
||||
log-symbols "4.0.0"
|
||||
minimatch "3.0.4"
|
||||
ms "2.1.3"
|
||||
nanoid "3.1.20"
|
||||
serialize-javascript "5.0.1"
|
||||
strip-json-comments "3.1.1"
|
||||
supports-color "8.1.1"
|
||||
which "2.0.2"
|
||||
wide-align "1.1.3"
|
||||
workerpool "6.1.0"
|
||||
yargs "16.2.0"
|
||||
yargs-parser "20.2.4"
|
||||
yargs-unparser "2.0.0"
|
||||
version "1.2.6"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
||||
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
||||
|
||||
mocha@^9.2.0:
|
||||
version "9.2.0"
|
||||
@@ -3050,11 +2865,6 @@ nan@^2.14.0:
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19"
|
||||
integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==
|
||||
|
||||
nanoid@3.1.20:
|
||||
version "3.1.20"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788"
|
||||
integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==
|
||||
|
||||
nanoid@3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c"
|
||||
@@ -3179,7 +2989,7 @@ on-finished@~2.3.0:
|
||||
dependencies:
|
||||
ee-first "1.1.1"
|
||||
|
||||
once@^1.3.0, once@^1.3.1, once@^1.4.0:
|
||||
once@^1.3.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
|
||||
@@ -3326,11 +3136,6 @@ pathval@^1.1.1:
|
||||
resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d"
|
||||
integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==
|
||||
|
||||
pend@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||
integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
|
||||
|
||||
picomatch@^2.0.4, picomatch@^2.2.1:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
|
||||
@@ -3348,39 +3153,17 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0:
|
||||
dependencies:
|
||||
find-up "^4.0.0"
|
||||
|
||||
playwright-core@=1.16.2:
|
||||
version "1.16.2"
|
||||
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.16.2.tgz#13c4c352a41e431ba167dbadb80e8c628e1e1b79"
|
||||
integrity sha512-8WkoP5OfZAYrRxtW/PCVACn9bNgqrTxVVPlc+MoxvJ48knNsZ+skrPjfno/XF3SgTUY9DyYX0g5fVOB7lkPtGg==
|
||||
dependencies:
|
||||
commander "^8.2.0"
|
||||
debug "^4.1.1"
|
||||
extract-zip "^2.0.1"
|
||||
https-proxy-agent "^5.0.0"
|
||||
jpeg-js "^0.4.2"
|
||||
mime "^2.4.6"
|
||||
pngjs "^5.0.0"
|
||||
progress "^2.0.3"
|
||||
proper-lockfile "^4.1.1"
|
||||
proxy-from-env "^1.1.0"
|
||||
rimraf "^3.0.2"
|
||||
socks-proxy-agent "^6.1.0"
|
||||
stack-utils "^2.0.3"
|
||||
ws "^7.4.6"
|
||||
yauzl "^2.10.0"
|
||||
yazl "^2.5.1"
|
||||
playwright-core@1.22.1:
|
||||
version "1.22.1"
|
||||
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.22.1.tgz#59ddf903546171fdfd9c3dc189630c883619667c"
|
||||
integrity sha512-H+ZUVYnceWNXrRf3oxTEKAr81QzFsCKu5Fp//fEjQvqgKkfA1iX3E9DBrPJpPNOrgVzcE+IqeI0fDmYJe6Ynnw==
|
||||
|
||||
playwright@^1.16.2:
|
||||
version "1.16.2"
|
||||
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.16.2.tgz#92e1c48a74cab778970facca4094ce79fb57cae4"
|
||||
integrity sha512-lqndwy5rDIp3tOLex5lnLSrltomqxgVkRkRi547rXoTl2qy7PY2rtHbUouQl7KY2XoVSeSeECYVq/bLJ+bbJNg==
|
||||
playwright@^1.22.1:
|
||||
version "1.22.1"
|
||||
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.22.1.tgz#a41bf55065953a6b31c151433e0f00c9e23c54dd"
|
||||
integrity sha512-pn4dvphQdL3zTgI+xfqkTL3HK1xbtQz+zjJxzyhwKLDxMNuAH7/NCm75auWbYLyssdnvApznujwrQuJvqXTKUw==
|
||||
dependencies:
|
||||
playwright-core "=1.16.2"
|
||||
|
||||
pngjs@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb"
|
||||
integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==
|
||||
playwright-core "1.22.1"
|
||||
|
||||
prelude-ls@^1.2.1:
|
||||
version "1.2.1"
|
||||
@@ -3399,20 +3182,11 @@ process-on-spawn@^1.0.0:
|
||||
dependencies:
|
||||
fromentries "^1.2.0"
|
||||
|
||||
progress@^2.0.0, progress@^2.0.3:
|
||||
progress@^2.0.0:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
|
||||
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
|
||||
|
||||
proper-lockfile@^4.1.1:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f"
|
||||
integrity sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==
|
||||
dependencies:
|
||||
graceful-fs "^4.2.4"
|
||||
retry "^0.12.0"
|
||||
signal-exit "^3.0.2"
|
||||
|
||||
proxy-addr@~2.0.5:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf"
|
||||
@@ -3421,24 +3195,11 @@ proxy-addr@~2.0.5:
|
||||
forwarded "~0.1.2"
|
||||
ipaddr.js "1.9.1"
|
||||
|
||||
proxy-from-env@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
|
||||
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
|
||||
|
||||
psl@^1.1.33:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
|
||||
integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==
|
||||
|
||||
pump@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
|
||||
integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
|
||||
dependencies:
|
||||
end-of-stream "^1.1.0"
|
||||
once "^1.3.1"
|
||||
|
||||
punycode@^2.1.0, punycode@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||
@@ -3476,13 +3237,6 @@ raw-body@2.4.0:
|
||||
iconv-lite "0.4.24"
|
||||
unpipe "1.0.0"
|
||||
|
||||
readdirp@~3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e"
|
||||
integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==
|
||||
dependencies:
|
||||
picomatch "^2.2.1"
|
||||
|
||||
readdirp@~3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
|
||||
@@ -3497,6 +3251,11 @@ rechoir@^0.7.0:
|
||||
dependencies:
|
||||
resolve "^1.9.0"
|
||||
|
||||
regenerator-runtime@^0.13.4:
|
||||
version "0.13.9"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
|
||||
integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
|
||||
|
||||
regexp.prototype.flags@^1.3.0:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26"
|
||||
@@ -3559,11 +3318,6 @@ resolve@^1.9.0:
|
||||
is-core-module "^2.2.0"
|
||||
path-parse "^1.0.6"
|
||||
|
||||
retry@^0.12.0:
|
||||
version "0.12.0"
|
||||
resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
|
||||
integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=
|
||||
|
||||
reusify@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
|
||||
@@ -3628,7 +3382,7 @@ seedrandom@^3.0.5:
|
||||
resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7"
|
||||
integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==
|
||||
|
||||
semver@^5.3.0, semver@^5.4.1:
|
||||
semver@^5.4.1:
|
||||
version "5.7.1"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
||||
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
|
||||
@@ -3664,13 +3418,6 @@ send@0.17.1:
|
||||
range-parser "~1.2.1"
|
||||
statuses "~1.5.0"
|
||||
|
||||
serialize-javascript@5.0.1, serialize-javascript@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4"
|
||||
integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==
|
||||
dependencies:
|
||||
randombytes "^2.1.0"
|
||||
|
||||
serialize-javascript@6.0.0, serialize-javascript@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8"
|
||||
@@ -3678,6 +3425,13 @@ serialize-javascript@6.0.0, serialize-javascript@^6.0.0:
|
||||
dependencies:
|
||||
randombytes "^2.1.0"
|
||||
|
||||
serialize-javascript@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4"
|
||||
integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==
|
||||
dependencies:
|
||||
randombytes "^2.1.0"
|
||||
|
||||
serve-static@1.14.1:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
|
||||
@@ -3741,28 +3495,6 @@ slash@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
|
||||
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
|
||||
|
||||
smart-buffer@^4.1.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae"
|
||||
integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==
|
||||
|
||||
socks-proxy-agent@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.1.0.tgz#869cf2d7bd10fea96c7ad3111e81726855e285c3"
|
||||
integrity sha512-57e7lwCN4Tzt3mXz25VxOErJKXlPfXmkMLnk310v/jwW20jWRVcgsOit+xNkN3eIEdB47GwnfAEBLacZ/wVIKg==
|
||||
dependencies:
|
||||
agent-base "^6.0.2"
|
||||
debug "^4.3.1"
|
||||
socks "^2.6.1"
|
||||
|
||||
socks@^2.6.1:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/socks/-/socks-2.6.1.tgz#989e6534a07cf337deb1b1c94aaa44296520d30e"
|
||||
integrity sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==
|
||||
dependencies:
|
||||
ip "^1.1.5"
|
||||
smart-buffer "^4.1.0"
|
||||
|
||||
source-list-map@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
|
||||
@@ -3830,26 +3562,11 @@ sprintf-js@~1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
|
||||
|
||||
stack-utils@^2.0.3:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5"
|
||||
integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==
|
||||
dependencies:
|
||||
escape-string-regexp "^2.0.0"
|
||||
|
||||
"statuses@>= 1.5.0 < 2", statuses@~1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
|
||||
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
|
||||
|
||||
"string-width@^1.0.2 || 2":
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
|
||||
integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
|
||||
dependencies:
|
||||
is-fullwidth-code-point "^2.0.0"
|
||||
strip-ansi "^4.0.0"
|
||||
|
||||
string-width@^4.1.0, string-width@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5"
|
||||
@@ -3875,20 +3592,6 @@ string.prototype.trimstart@^1.0.4:
|
||||
call-bind "^1.0.2"
|
||||
define-properties "^1.1.3"
|
||||
|
||||
strip-ansi@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
|
||||
integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=
|
||||
dependencies:
|
||||
ansi-regex "^2.0.0"
|
||||
|
||||
strip-ansi@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
|
||||
integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8=
|
||||
dependencies:
|
||||
ansi-regex "^3.0.0"
|
||||
|
||||
strip-ansi@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532"
|
||||
@@ -3896,6 +3599,13 @@ strip-ansi@^6.0.0:
|
||||
dependencies:
|
||||
ansi-regex "^5.0.0"
|
||||
|
||||
strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-bom@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878"
|
||||
@@ -4046,42 +3756,11 @@ ts-loader@^9.1.2:
|
||||
micromatch "^4.0.0"
|
||||
semver "^7.3.4"
|
||||
|
||||
tslib@^1.13.0:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tslib@^1.8.1:
|
||||
version "1.11.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35"
|
||||
integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==
|
||||
|
||||
tslint@^6.1.3:
|
||||
version "6.1.3"
|
||||
resolved "https://registry.yarnpkg.com/tslint/-/tslint-6.1.3.tgz#5c23b2eccc32487d5523bd3a470e9aa31789d904"
|
||||
integrity sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.0.0"
|
||||
builtin-modules "^1.1.1"
|
||||
chalk "^2.3.0"
|
||||
commander "^2.12.1"
|
||||
diff "^4.0.1"
|
||||
glob "^7.1.1"
|
||||
js-yaml "^3.13.1"
|
||||
minimatch "^3.0.4"
|
||||
mkdirp "^0.5.3"
|
||||
resolve "^1.3.2"
|
||||
semver "^5.3.0"
|
||||
tslib "^1.13.0"
|
||||
tsutils "^2.29.0"
|
||||
|
||||
tsutils@^2.29.0:
|
||||
version "2.29.0"
|
||||
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99"
|
||||
integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==
|
||||
dependencies:
|
||||
tslib "^1.8.1"
|
||||
|
||||
tsutils@^3.21.0:
|
||||
version "3.21.0"
|
||||
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
|
||||
@@ -4127,9 +3806,9 @@ type-is@~1.6.17, type-is@~1.6.18:
|
||||
mime-types "~2.1.24"
|
||||
|
||||
typed-function@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-2.0.0.tgz#15ab3825845138a8b1113bd89e60cd6a435739e8"
|
||||
integrity sha512-Hhy1Iwo/e4AtLZNK10ewVVcP2UEs408DS35ubP825w/YgSBK1KVLwALvvIG4yX75QJrxjCpcWkzkVRB0BwwYlA==
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-2.1.0.tgz#ded6f8a442ba8749ff3fe75bc41419c8d46ccc3f"
|
||||
integrity sha512-bctQIOqx2iVbWGDGPWwIm18QScpu2XRmkC19D8rQGFsjKSgteq/o1hTZvIG/wuDq8fanpBDrLkLq+aEN/6y5XQ==
|
||||
|
||||
typedarray-to-buffer@^3.1.5:
|
||||
version "3.1.5"
|
||||
@@ -4139,9 +3818,9 @@ typedarray-to-buffer@^3.1.5:
|
||||
is-typedarray "^1.0.0"
|
||||
|
||||
typescript@^4.2.3:
|
||||
version "4.2.4"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961"
|
||||
integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==
|
||||
version "4.6.4"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9"
|
||||
integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==
|
||||
|
||||
typescript@^4.4.4:
|
||||
version "4.4.4"
|
||||
@@ -4406,13 +4085,6 @@ which@2.0.2, which@^2.0.1:
|
||||
dependencies:
|
||||
isexe "^2.0.0"
|
||||
|
||||
wide-align@1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"
|
||||
integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==
|
||||
dependencies:
|
||||
string-width "^1.0.2 || 2"
|
||||
|
||||
wildcard@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec"
|
||||
@@ -4423,11 +4095,6 @@ word-wrap@^1.2.3, word-wrap@~1.2.3:
|
||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
|
||||
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
|
||||
|
||||
workerpool@6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.0.tgz#a8e038b4c94569596852de7a8ea4228eefdeb37b"
|
||||
integrity sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==
|
||||
|
||||
workerpool@6.2.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.0.tgz#827d93c9ba23ee2019c3ffaff5c27fccea289e8b"
|
||||
@@ -4486,10 +4153,10 @@ xmlchars@^2.2.0:
|
||||
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
|
||||
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
|
||||
|
||||
xterm-benchmark@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/xterm-benchmark/-/xterm-benchmark-0.3.0.tgz#8702ae41672ff1e656423336f4e54699a3ab74e8"
|
||||
integrity sha512-JTC1NjaqAWRHA3vPwbAocOJvz42ysYelrq7z2PS9egly8Hi5W7zfJIZlpkzEsZ8dXo0fcEm/6azmoYFy+TMjig==
|
||||
xterm-benchmark@^0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/xterm-benchmark/-/xterm-benchmark-0.3.1.tgz#dcaaf808e40605c7c27a83b5a5b81f9c45045e24"
|
||||
integrity sha512-JjsCrSxkYKWf5CmBt2BeXm83KQdStyoGWREWQ0jSFF5N8CYVbdKQoWgs56mmy6qWD5GDKxO+V89Cvnbc8YUFjw==
|
||||
dependencies:
|
||||
"@types/app-root-path" "^1.2.4"
|
||||
"@types/cli-table" "^0.3.0"
|
||||
@@ -4502,8 +4169,6 @@ xterm-benchmark@^0.3.0:
|
||||
columnify "^1.5.4"
|
||||
commander "^6.2.1"
|
||||
mathjs "^9.3.0"
|
||||
mocha "^8.3.2"
|
||||
tslint "^6.1.3"
|
||||
typescript "^4.2.3"
|
||||
|
||||
y18n@^4.0.0:
|
||||
@@ -4579,21 +4244,6 @@ yargs@^15.0.2:
|
||||
y18n "^4.0.0"
|
||||
yargs-parser "^18.1.1"
|
||||
|
||||
yauzl@^2.10.0:
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
|
||||
integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=
|
||||
dependencies:
|
||||
buffer-crc32 "~0.2.3"
|
||||
fd-slicer "~1.1.0"
|
||||
|
||||
yazl@^2.5.1:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.5.1.tgz#a3d65d3dd659a5b0937850e8609f22fffa2b5c35"
|
||||
integrity sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==
|
||||
dependencies:
|
||||
buffer-crc32 "~0.2.3"
|
||||
|
||||
yocto-queue@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
||||
|
||||
Reference in New Issue
Block a user