mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
@@ -23,6 +23,8 @@
|
||||
"addons/addon-image/src/tsconfig.json",
|
||||
"addons/addon-image/test/tsconfig.json",
|
||||
"addons/addon-ligatures/src/tsconfig.json",
|
||||
"addons/addon-progress/src/tsconfig.json",
|
||||
"addons/addon-progress/test/tsconfig.json",
|
||||
"addons/addon-search/src/tsconfig.json",
|
||||
"addons/addon-search/test/tsconfig.json",
|
||||
"addons/addon-serialize/src/tsconfig.json",
|
||||
|
||||
@@ -44,6 +44,9 @@ jobs:
|
||||
./addons/addon-ligatures/lib/* \
|
||||
./addons/addon-ligatures/out/* \
|
||||
./addons/addon-ligatures/out-*/* \
|
||||
./addons/addon-progress/lib/* \
|
||||
./addons/addon-progress/out/* \
|
||||
./addons/addon-progress/out-*/* \
|
||||
./addons/addon-search/lib/* \
|
||||
./addons/addon-search/out/* \
|
||||
./addons/addon-search/out-*/* \
|
||||
@@ -212,6 +215,8 @@ jobs:
|
||||
run: yarn test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-fit
|
||||
- name: Integration tests (addon-image)
|
||||
run: yarn test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-image
|
||||
- name: Integration tests (addon-progress)
|
||||
run: yarn test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-progress
|
||||
- name: Integration tests (addon-search)
|
||||
run: yarn test-integration-${{ matrix.browser }} --workers=50% --forbid-only --suite=addon-search
|
||||
- name: Integration tests (addon-serialize)
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
lib
|
||||
node_modules
|
||||
@@ -0,0 +1,32 @@
|
||||
# Blacklist - exclude everything except npm defaults such as LICENSE, etc
|
||||
*
|
||||
!*/
|
||||
|
||||
# Whitelist - lib/
|
||||
!lib/**/*.d.ts
|
||||
|
||||
!lib/**/*.js
|
||||
!lib/**/*.js.map
|
||||
|
||||
!lib/**/*.mjs
|
||||
!lib/**/*.mjs.map
|
||||
|
||||
!lib/**/*.css
|
||||
|
||||
# Whitelist - src/
|
||||
!src/**/*.ts
|
||||
!src/**/*.d.ts
|
||||
|
||||
!src/**/*.js
|
||||
!src/**/*.js.map
|
||||
|
||||
!src/**/*.css
|
||||
|
||||
# Blacklist - src/ test files
|
||||
src/**/*.test.ts
|
||||
src/**/*.test.d.ts
|
||||
src/**/*.test.js
|
||||
src/**/*.test.js.map
|
||||
|
||||
# Whitelist - typings/
|
||||
!typings/*.d.ts
|
||||
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2024, The xterm.js authors (https://github.com/xtermjs/xterm.js)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -0,0 +1,69 @@
|
||||
## @xterm/addon-progress
|
||||
|
||||
An xterm.js addon providing an interface for ConEmu's progress sequence.
|
||||
See https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC for sequence details.
|
||||
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
npm install --save @xterm/addon-progress
|
||||
```
|
||||
|
||||
|
||||
### Usage
|
||||
|
||||
```ts
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { ProgressAddon, IProgressState } from '@xterm/addon-progress';
|
||||
|
||||
const terminal = new Terminal();
|
||||
const progressAddon = new ProgressAddon();
|
||||
terminal.loadAddon(progressAddon);
|
||||
progressAddon.onChange({state, value}: IProgressState) => {
|
||||
// state: 0-4 integer (see below for meaning)
|
||||
// value: 0-100 integer (percent value)
|
||||
|
||||
// do your visualisation based on state/value here
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-progress/typings/addon-progress.d.ts) for more advanced usage.
|
||||
|
||||
### Sequence
|
||||
|
||||
The sequence to set progress information has the following format:
|
||||
|
||||
```plain
|
||||
ESC ] 9 ; 4 ; <state> ; <progress value> BEL
|
||||
```
|
||||
|
||||
where state is a decimal number in 0 to 4 and progress value is a decimal number in 0 to 100.
|
||||
The states have the following meaning:
|
||||
|
||||
- 0: Remove any progress indication. Also resets progress value to 0. A given progress value will be ignored.
|
||||
- 1: Normal state to set a progress value. The value should be in 0..100, greater values are clamped to 100.
|
||||
If the value is omitted, it will be set to 0.
|
||||
- 2: Error state with an optional progress value. An omitted value will be set to 0,
|
||||
which has a special meaning using the last active value.
|
||||
- 3: Actual progress is "indeterminate", any progress value will be ignored. Meant to be used to indicate
|
||||
a running task without progress information (e.g. by a spinner). A previously set progress value
|
||||
by any other state sequence will be left untouched.
|
||||
- 4: Pause or warning state with an optional progress value. An omitted value will be set to 0,
|
||||
which has a special meaning using the last active value.
|
||||
|
||||
The addon resolves most of those semantic nuances and will provide these ready-to-go values:
|
||||
- For the remove state (0) any progress value wont be parsed, thus is even allowed to contain garbage.
|
||||
It will always emit `{state: 0, value: 0}`.
|
||||
- For the set state (1) an omitted value will be set to 0 emitting `{state: 1, value: 0}`.
|
||||
If a value was given, it must be decimal digits only, any characters outside will mark the whole sequence
|
||||
as faulty (no sloppy integer parsing). The value will be clamped to max 100 giving
|
||||
`{state: 1, value: parsedAndClampedValue}`.
|
||||
- For the error and pause state (2 & 4) an omitted or zero value will emit `{state: 2|4, value: lastValue}`.
|
||||
If a value was given, it must be decimal digits only, any characters outside will mark the whole sequence
|
||||
as faulty (no sloppy integer parsing). The value will be clamped to max 100 giving
|
||||
`{state: 2|4, value: parsedAndClampedValue}`.
|
||||
- For the indeterminate state (3) a value notion will be ignored.
|
||||
It still emits the value as `{state: 3, value: lastValue}`. Keep in mind not use that value while
|
||||
that state is active, as a task might have entered that state without a proper reset at the beginning.
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "@xterm/addon-progress",
|
||||
"version": "0.1.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
},
|
||||
"main": "lib/addon-progress.js",
|
||||
"module": "lib/addon-progress.mjs",
|
||||
"types": "typings/addon-progress.d.ts",
|
||||
"repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-progress",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"terminal",
|
||||
"xterm",
|
||||
"xterm.js"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "../../node_modules/.bin/tsc -p .",
|
||||
"prepackage": "npm run build",
|
||||
"package": "../../node_modules/.bin/webpack",
|
||||
"prepublishOnly": "npm run package",
|
||||
"start": "node ../../demo/start"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@xterm/xterm": "^5.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Copyright (c) 2024 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import type { Terminal, ITerminalAddon, IDisposable } from '@xterm/xterm';
|
||||
import type { ProgressAddon as IProgressApi, IProgressState } from '@xterm/addon-progress';
|
||||
import type { Emitter, Event } from 'vs/base/common/event';
|
||||
|
||||
|
||||
const enum ProgressType {
|
||||
REMOVE = 0,
|
||||
SET = 1,
|
||||
ERROR = 2,
|
||||
INDETERMINATE = 3,
|
||||
PAUSE = 4
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Strict integer parsing, only decimal digits allowed.
|
||||
*/
|
||||
function toInt(s: string): number {
|
||||
let v = 0;
|
||||
for (let i = 0; i < s.length; ++i) {
|
||||
const c = s.charCodeAt(i);
|
||||
if (c < 0x30 || 0x39 < c) {
|
||||
return -1;
|
||||
}
|
||||
v = v * 10 + c - 48;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
export class ProgressAddon implements ITerminalAddon, IProgressApi {
|
||||
private _seqHandler: IDisposable | undefined;
|
||||
private _st: ProgressType = ProgressType.REMOVE;
|
||||
private _pr = 0;
|
||||
private _onChange: Emitter<IProgressState> | undefined;
|
||||
public onChange: Event<IProgressState> | undefined;
|
||||
|
||||
public dispose(): void {
|
||||
this._seqHandler?.dispose();
|
||||
this._onChange?.dispose();
|
||||
}
|
||||
|
||||
public activate(terminal: Terminal): void {
|
||||
this._seqHandler = terminal.parser.registerOscHandler(9, data => {
|
||||
if (!data.startsWith('4;')) {
|
||||
return false;
|
||||
}
|
||||
const parts = data.split(';');
|
||||
|
||||
if (parts.length > 3) {
|
||||
return true; // faulty sequence, just exit
|
||||
}
|
||||
if (parts.length === 2) {
|
||||
parts.push('');
|
||||
}
|
||||
const st = toInt(parts[1]);
|
||||
const pr = toInt(parts[2]);
|
||||
|
||||
switch (st) {
|
||||
case ProgressType.REMOVE:
|
||||
this.progress = { state: st, value: 0 };
|
||||
break;
|
||||
case ProgressType.SET:
|
||||
if (pr < 0) return true; // faulty sequence, just exit
|
||||
this.progress = { state: st, value: pr };
|
||||
break;
|
||||
case ProgressType.ERROR:
|
||||
case ProgressType.PAUSE:
|
||||
if (pr < 0) return true; // faulty sequence, just exit
|
||||
this.progress = { state: st, value: pr || this._pr };
|
||||
break;
|
||||
case ProgressType.INDETERMINATE:
|
||||
this.progress = { state: st, value: this._pr };
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
// FIXME: borrow emitter ctor from xterm, to be changed once #5283 is resolved
|
||||
this._onChange = new (terminal as any)._core._onData.constructor();
|
||||
this.onChange = this._onChange!.event;
|
||||
}
|
||||
|
||||
public get progress(): IProgressState {
|
||||
return { state: this._st, value: this._pr };
|
||||
}
|
||||
|
||||
public set progress(progress: IProgressState) {
|
||||
if (progress.state < 0 || progress.state > 4) {
|
||||
console.warn(`progress state out of bounds, not applied`);
|
||||
return;
|
||||
}
|
||||
this._st = progress.state;
|
||||
this._pr = Math.min(Math.max(progress.value, 0), 100);
|
||||
this._onChange?.fire({ state: this._st, value: this._pr });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2021",
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2015"
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../out",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/mocha",
|
||||
"../../../src/vs/typings/thenable"
|
||||
],
|
||||
"paths": {
|
||||
"browser/*": [
|
||||
"../../../src/browser/*"
|
||||
],
|
||||
"vs/*": [
|
||||
"../../../src/vs/*"
|
||||
],
|
||||
"@xterm/addon-progress": [
|
||||
"../typings/addon-progress.d.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../src/browser"
|
||||
},
|
||||
{
|
||||
"path": "../../../src/vs"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Copyright (c) 2024 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import test from '@playwright/test';
|
||||
import { deepStrictEqual } from 'assert';
|
||||
import { ITestContext, createTestContext, openTerminal } from '../../../test/playwright/TestUtils';
|
||||
|
||||
|
||||
let ctx: ITestContext;
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
ctx = await createTestContext(browser);
|
||||
ctx.page.setViewportSize({ width: 1024, height: 768 });
|
||||
await openTerminal(ctx);
|
||||
});
|
||||
test.afterAll(async () => await ctx.page.close());
|
||||
|
||||
|
||||
test.describe('ProgressAddon', () => {
|
||||
test.beforeEach(async function(): Promise<any> {
|
||||
await ctx.page.evaluate(`
|
||||
window.progressStack = [];
|
||||
window.term.reset();
|
||||
window.progressAddon?.dispose();
|
||||
window.progressAddon = new ProgressAddon();
|
||||
window.term.loadAddon(window.progressAddon);
|
||||
window.progressAddon.onChange(progress => window.progressStack.push(progress));
|
||||
`);
|
||||
});
|
||||
|
||||
test('initial values should be 0;0', async () => {
|
||||
deepStrictEqual(await ctx.page.evaluate('window.progressAddon.progress'), {state: 0, value: 0});
|
||||
});
|
||||
test('state 0: remove', async () => {
|
||||
// no value
|
||||
await ctx.proxy.write('\x1b]9;4;0\x1b\\');
|
||||
deepStrictEqual(await ctx.page.evaluate('window.progressStack'), [{state: 0, value: 0}]);
|
||||
// value ignored
|
||||
await ctx.proxy.write('\x1b]9;4;0;12\x1b\\');
|
||||
deepStrictEqual(await ctx.page.evaluate('window.progressStack'), [{state: 0, value: 0}, {state: 0, value: 0}]);
|
||||
});
|
||||
test('state 1: set', async () => {
|
||||
// set 10%
|
||||
await ctx.proxy.write('\x1b]9;4;1;10\x1b\\');
|
||||
deepStrictEqual(await ctx.page.evaluate('window.progressStack'), [{state: 1, value: 10}]);
|
||||
// set 50%
|
||||
await ctx.proxy.write('\x1b]9;4;1;50\x1b\\');
|
||||
deepStrictEqual(await ctx.page.evaluate('window.progressStack'), [{state: 1, value: 10}, {state: 1, value: 50}]);
|
||||
// set 23%
|
||||
await ctx.proxy.write('\x1b]9;4;1;23\x1b\\');
|
||||
deepStrictEqual(await ctx.page.evaluate('window.progressStack'), [{state: 1, value: 10}, {state: 1, value: 50}, {state: 1, value: 23}]);
|
||||
});
|
||||
test('state 1: set - special sequence handling', async () => {
|
||||
// missing progress value defaults to 0
|
||||
await ctx.proxy.write('\x1b]9;4;1\x1b\\');
|
||||
deepStrictEqual(await ctx.page.evaluate('window.progressStack'), [{state: 1, value: 0}]);
|
||||
// malformed progress value get ignored
|
||||
await ctx.proxy.write('\x1b]9;4;1;12x\x1b\\');
|
||||
deepStrictEqual(await ctx.page.evaluate('window.progressStack'), [{state: 1, value: 0}]);
|
||||
// out of bounds gets clamped to 100
|
||||
await ctx.proxy.write('\x1b]9;4;1;123\x1b\\');
|
||||
deepStrictEqual(await ctx.page.evaluate('window.progressStack'), [{state: 1, value: 0}, {state: 1, value: 100}]);
|
||||
});
|
||||
test('state 2: error - preserve previous value on empty/0', async () => {
|
||||
// set value to 12
|
||||
await ctx.proxy.write('\x1b]9;4;1;12\x1b\\');
|
||||
// omitted/empty/0 value emits previous value
|
||||
await ctx.proxy.write('\x1b]9;4;2\x1b\\');
|
||||
await ctx.proxy.write('\x1b]9;4;2;\x1b\\');
|
||||
await ctx.proxy.write('\x1b]9;4;2;0\x1b\\');
|
||||
deepStrictEqual(
|
||||
await ctx.page.evaluate('window.progressStack'),
|
||||
[{state: 1, value: 12}, {state: 2, value: 12}, {state: 2, value: 12}, {state: 2, value: 12}]
|
||||
);
|
||||
});
|
||||
test('state 2: error - with new value', async () => {
|
||||
// set value to 12
|
||||
await ctx.proxy.write('\x1b]9;4;1;12\x1b\\');
|
||||
// new value updates clamped
|
||||
await ctx.proxy.write('\x1b]9;4;2;25\x1b\\');
|
||||
await ctx.proxy.write('\x1b]9;4;2;123\x1b\\');
|
||||
deepStrictEqual(
|
||||
await ctx.page.evaluate('window.progressStack'),
|
||||
[{state: 1, value: 12}, {state: 2, value: 25}, {state: 2, value: 100}]
|
||||
);
|
||||
});
|
||||
test('state 3: indeterminate - keeps value untouched', async () => {
|
||||
// set value to 12
|
||||
await ctx.proxy.write('\x1b]9;4;1;12\x1b\\');
|
||||
// new value updates clamped
|
||||
await ctx.proxy.write('\x1b]9;4;3\x1b\\');
|
||||
await ctx.proxy.write('\x1b]9;4;3;123\x1b\\');
|
||||
deepStrictEqual(
|
||||
await ctx.page.evaluate('window.progressStack'),
|
||||
[{state: 1, value: 12}, {state: 3, value: 12}, {state: 3, value: 12}]
|
||||
);
|
||||
});
|
||||
test('state 4: pause - preserve previous value on empty/0', async () => {
|
||||
// set value to 12
|
||||
await ctx.proxy.write('\x1b]9;4;1;12\x1b\\');
|
||||
// omitted/empty/0 value emits previous value
|
||||
await ctx.proxy.write('\x1b]9;4;4\x1b\\');
|
||||
await ctx.proxy.write('\x1b]9;4;4;\x1b\\');
|
||||
await ctx.proxy.write('\x1b]9;4;4;0\x1b\\');
|
||||
deepStrictEqual(
|
||||
await ctx.page.evaluate('window.progressStack'),
|
||||
[{state: 1, value: 12}, {state: 4, value: 12}, {state: 4, value: 12}, {state: 4, value: 12}]
|
||||
);
|
||||
});
|
||||
test('state 4: pause - with new value', async () => {
|
||||
// set value to 12
|
||||
await ctx.proxy.write('\x1b]9;4;1;12\x1b\\');
|
||||
// new value updates clamped
|
||||
await ctx.proxy.write('\x1b]9;4;4;25\x1b\\');
|
||||
await ctx.proxy.write('\x1b]9;4;4;123\x1b\\');
|
||||
deepStrictEqual(
|
||||
await ctx.page.evaluate('window.progressStack'),
|
||||
[{state: 1, value: 12}, {state: 4, value: 25}, {state: 4, value: 100}]
|
||||
);
|
||||
});
|
||||
test('invalid sequences should not emit anything', async () => {
|
||||
// illegal state
|
||||
await ctx.proxy.write('\x1b]9;4;5;12\x1b\\');
|
||||
// illegal chars in value
|
||||
await ctx.proxy.write('\x1b]9;4;1; 123xxxx\x1b\\');
|
||||
deepStrictEqual(await ctx.page.evaluate('window.progressStack'), []);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { PlaywrightTestConfig } from '@playwright/test';
|
||||
|
||||
const config: PlaywrightTestConfig = {
|
||||
testDir: '.',
|
||||
timeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
name: 'ChromeStable',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'FirefoxStable',
|
||||
use: {
|
||||
browserName: 'firefox'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'WebKit',
|
||||
use: {
|
||||
browserName: 'webkit'
|
||||
}
|
||||
}
|
||||
],
|
||||
reporter: 'list',
|
||||
webServer: {
|
||||
command: 'npm run start',
|
||||
port: 3000,
|
||||
timeout: 120000,
|
||||
reuseExistingServer: !process.env.CI
|
||||
}
|
||||
};
|
||||
export default config;
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "ESNext",
|
||||
"lib": [
|
||||
"es2021",
|
||||
],
|
||||
"rootDir": ".",
|
||||
"outDir": "../out-test",
|
||||
"sourceMap": true,
|
||||
"removeComments": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"common/*": [
|
||||
"../../../src/common/*"
|
||||
],
|
||||
"browser/*": [
|
||||
"../../../src/browser/*"
|
||||
]
|
||||
},
|
||||
"strict": true,
|
||||
"types": [
|
||||
"../../../node_modules/@types/node"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../../typings/xterm.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../src/common"
|
||||
},
|
||||
{
|
||||
"path": "../../../src/browser"
|
||||
},
|
||||
{
|
||||
"path": "../../../test/playwright"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{ "path": "./src" },
|
||||
{ "path": "./test" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2024 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal, ITerminalAddon, IDisposable } from '@xterm/xterm';
|
||||
import type { Event } from 'vs/base/common/event';
|
||||
|
||||
|
||||
declare module '@xterm/addon-progress' {
|
||||
/**
|
||||
* An xterm.js addon that provides an interface for ConEmu's progress
|
||||
* sequence.
|
||||
*/
|
||||
export class ProgressAddon implements ITerminalAddon, IDisposable {
|
||||
|
||||
/**
|
||||
* Creates a new progress addon
|
||||
*/
|
||||
constructor();
|
||||
|
||||
/**
|
||||
* Activates the addon
|
||||
* @param terminal The terminal the addon is being loaded in.
|
||||
*/
|
||||
public activate(terminal: Terminal): void;
|
||||
|
||||
/**
|
||||
* Disposes the addon.
|
||||
*/
|
||||
public dispose(): void;
|
||||
|
||||
/**
|
||||
* An event that fires when the tracked progress changes.
|
||||
*/
|
||||
public readonly onChange: Event<IProgressState> | undefined;
|
||||
|
||||
/**
|
||||
* Gets or sets the current progress tracked by the addon.
|
||||
* This can also be used to reset a stuck progress indicator
|
||||
* back to initial with `{state: 0, value: 0}`
|
||||
* or to restore an indicator.
|
||||
*/
|
||||
public progress: IProgressState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Progress state tracked by the addon.
|
||||
*/
|
||||
export interface IProgressState {
|
||||
state: 0 | 1 | 2 | 3 | 4;
|
||||
value: number;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2024 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const addonName = 'ProgressAddon';
|
||||
const mainFile = 'addon-progress.js';
|
||||
|
||||
module.exports = {
|
||||
entry: `./out/${addonName}.js`,
|
||||
devtool: 'source-map',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: ["source-map-loader"],
|
||||
enforce: "pre",
|
||||
exclude: /node_modules/
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
modules: ['./node_modules'],
|
||||
extensions: [ '.js' ],
|
||||
alias: {
|
||||
common: path.resolve('../../out/common'),
|
||||
browser: path.resolve('../../out/browser'),
|
||||
vs: path.resolve('../../out/vs')
|
||||
}
|
||||
},
|
||||
output: {
|
||||
filename: mainFile,
|
||||
path: path.resolve('./lib'),
|
||||
library: addonName,
|
||||
libraryTarget: 'umd',
|
||||
// Force usage of globalThis instead of global / self. (This is cross-env compatible)
|
||||
globalObject: 'globalThis',
|
||||
},
|
||||
mode: 'production'
|
||||
};
|
||||
@@ -136,6 +136,7 @@ if (config.addon) {
|
||||
"@xterm/addon-clipboard": "./addons/addon-clipboard/lib/addon-clipboard.mjs",
|
||||
"@xterm/addon-fit": "./addons/addon-fit/lib/addon-fit.mjs",
|
||||
"@xterm/addon-image": "./addons/addon-image/lib/addon-image.mjs",
|
||||
"@xterm/addon-progress": "./addons/addon-progress/lib/addon-progress.mjs",
|
||||
"@xterm/addon-search": "./addons/addon-search/lib/addon-search.mjs",
|
||||
"@xterm/addon-serialize": "./addons/addon-serialize/lib/addon-serialize.mjs",
|
||||
"@xterm/addon-web-links": "./addons/addon-web-links/lib/addon-web-links.mjs",
|
||||
|
||||
@@ -46,6 +46,7 @@ const addonPackageDirs = [
|
||||
path.resolve(__dirname, '../addons/addon-fit'),
|
||||
path.resolve(__dirname, '../addons/addon-image'),
|
||||
path.resolve(__dirname, '../addons/addon-ligatures'),
|
||||
path.resolve(__dirname, '../addons/addon-progress'),
|
||||
path.resolve(__dirname, '../addons/addon-search'),
|
||||
path.resolve(__dirname, '../addons/addon-serialize'),
|
||||
path.resolve(__dirname, '../addons/addon-unicode11'),
|
||||
|
||||
@@ -25,6 +25,7 @@ const addons = [
|
||||
'clipboard',
|
||||
'fit',
|
||||
'image',
|
||||
'progress',
|
||||
'search',
|
||||
'serialize',
|
||||
'unicode-graphemes',
|
||||
|
||||
+65
-15
@@ -21,6 +21,7 @@ import { AttachAddon } from '@xterm/addon-attach';
|
||||
import { ClipboardAddon } from '@xterm/addon-clipboard';
|
||||
import { FitAddon } from '@xterm/addon-fit';
|
||||
import { LigaturesAddon } from '@xterm/addon-ligatures';
|
||||
import { ProgressAddon, IProgressState } from '@xterm/addon-progress';
|
||||
import { SearchAddon, ISearchOptions } from '@xterm/addon-search';
|
||||
import { SerializeAddon } from '@xterm/addon-serialize';
|
||||
import { WebLinksAddon } from '@xterm/addon-web-links';
|
||||
@@ -35,6 +36,7 @@ export interface IWindowWithTerminal extends Window {
|
||||
ClipboardAddon?: typeof ClipboardAddon; // eslint-disable-line @typescript-eslint/naming-convention
|
||||
FitAddon?: typeof FitAddon; // eslint-disable-line @typescript-eslint/naming-convention
|
||||
ImageAddon?: typeof ImageAddon; // eslint-disable-line @typescript-eslint/naming-convention
|
||||
ProgressAddon?: typeof ProgressAddon; // eslint-disable-line @typescript-eslint/naming-convention
|
||||
SearchAddon?: typeof SearchAddon; // eslint-disable-line @typescript-eslint/naming-convention
|
||||
SerializeAddon?: typeof SerializeAddon; // eslint-disable-line @typescript-eslint/naming-convention
|
||||
WebLinksAddon?: typeof WebLinksAddon; // eslint-disable-line @typescript-eslint/naming-convention
|
||||
@@ -52,7 +54,7 @@ let socket;
|
||||
let pid;
|
||||
let autoResize: boolean = true;
|
||||
|
||||
type AddonType = 'attach' | 'clipboard' | 'fit' | 'image' | 'search' | 'serialize' | 'unicode11' | 'unicodeGraphemes' | 'webLinks' | 'webgl' | 'ligatures';
|
||||
type AddonType = 'attach' | 'clipboard' | 'fit' | 'image' | 'progress' | 'search' | 'serialize' | 'unicode11' | 'unicodeGraphemes' | 'webLinks' | 'webgl' | 'ligatures';
|
||||
|
||||
interface IDemoAddon<T extends AddonType> {
|
||||
name: T;
|
||||
@@ -63,13 +65,14 @@ interface IDemoAddon<T extends AddonType> {
|
||||
T extends 'fit' ? typeof FitAddon :
|
||||
T extends 'image' ? typeof ImageAddonType :
|
||||
T extends 'ligatures' ? typeof LigaturesAddon :
|
||||
T extends 'search' ? typeof SearchAddon :
|
||||
T extends 'serialize' ? typeof SerializeAddon :
|
||||
T extends 'webLinks' ? typeof WebLinksAddon :
|
||||
T extends 'unicode11' ? typeof Unicode11Addon :
|
||||
T extends 'unicodeGraphemes' ? typeof UnicodeGraphemesAddon :
|
||||
T extends 'webgl' ? typeof WebglAddon :
|
||||
never
|
||||
T extends 'progress' ? typeof ProgressAddon :
|
||||
T extends 'search' ? typeof SearchAddon :
|
||||
T extends 'serialize' ? typeof SerializeAddon :
|
||||
T extends 'webLinks' ? typeof WebLinksAddon :
|
||||
T extends 'unicode11' ? typeof Unicode11Addon :
|
||||
T extends 'unicodeGraphemes' ? typeof UnicodeGraphemesAddon :
|
||||
T extends 'webgl' ? typeof WebglAddon :
|
||||
never
|
||||
);
|
||||
instance?: (
|
||||
T extends 'attach' ? AttachAddon :
|
||||
@@ -77,13 +80,14 @@ interface IDemoAddon<T extends AddonType> {
|
||||
T extends 'fit' ? FitAddon :
|
||||
T extends 'image' ? ImageAddonType :
|
||||
T extends 'ligatures' ? LigaturesAddon :
|
||||
T extends 'search' ? SearchAddon :
|
||||
T extends 'serialize' ? SerializeAddon :
|
||||
T extends 'webLinks' ? WebLinksAddon :
|
||||
T extends 'unicode11' ? Unicode11Addon :
|
||||
T extends 'unicodeGraphemes' ? UnicodeGraphemesAddon :
|
||||
T extends 'webgl' ? WebglAddon :
|
||||
never
|
||||
T extends 'progress' ? ProgressAddon :
|
||||
T extends 'search' ? SearchAddon :
|
||||
T extends 'serialize' ? SerializeAddon :
|
||||
T extends 'webLinks' ? WebLinksAddon :
|
||||
T extends 'unicode11' ? Unicode11Addon :
|
||||
T extends 'unicodeGraphemes' ? UnicodeGraphemesAddon :
|
||||
T extends 'webgl' ? WebglAddon :
|
||||
never
|
||||
);
|
||||
}
|
||||
|
||||
@@ -92,6 +96,7 @@ const addons: { [T in AddonType]: IDemoAddon<T> } = {
|
||||
clipboard: { name: 'clipboard', ctor: ClipboardAddon, canChange: true },
|
||||
fit: { name: 'fit', ctor: FitAddon, canChange: false },
|
||||
image: { name: 'image', ctor: ImageAddon, canChange: true },
|
||||
progress: { name: 'progress', ctor: ProgressAddon, canChange: true },
|
||||
search: { name: 'search', ctor: SearchAddon, canChange: true },
|
||||
serialize: { name: 'serialize', ctor: SerializeAddon, canChange: true },
|
||||
webLinks: { name: 'webLinks', ctor: WebLinksAddon, canChange: true },
|
||||
@@ -213,6 +218,7 @@ if (document.location.pathname === '/test') {
|
||||
window.ClipboardAddon = ClipboardAddon;
|
||||
window.FitAddon = FitAddon;
|
||||
window.ImageAddon = ImageAddon;
|
||||
window.ProgressAddon = ProgressAddon;
|
||||
window.SearchAddon = SearchAddon;
|
||||
window.SerializeAddon = SerializeAddon;
|
||||
window.Unicode11Addon = Unicode11Addon;
|
||||
@@ -246,6 +252,7 @@ if (document.location.pathname === '/test') {
|
||||
addVtButtons();
|
||||
initImageAddonExposed();
|
||||
testEvents();
|
||||
progressButtons();
|
||||
}
|
||||
|
||||
function createTerminal(): void {
|
||||
@@ -272,6 +279,7 @@ function createTerminal(): void {
|
||||
addons.serialize.instance = new SerializeAddon();
|
||||
addons.fit.instance = new FitAddon();
|
||||
addons.image.instance = new ImageAddon();
|
||||
addons.progress.instance = new ProgressAddon();
|
||||
addons.unicodeGraphemes.instance = new UnicodeGraphemesAddon();
|
||||
addons.clipboard.instance = new ClipboardAddon();
|
||||
try { // try to start with webgl renderer (might throw on older safari/webkit)
|
||||
@@ -282,6 +290,7 @@ function createTerminal(): void {
|
||||
addons.webLinks.instance = new WebLinksAddon();
|
||||
typedTerm.loadAddon(addons.fit.instance);
|
||||
typedTerm.loadAddon(addons.image.instance);
|
||||
typedTerm.loadAddon(addons.progress.instance);
|
||||
typedTerm.loadAddon(addons.search.instance);
|
||||
typedTerm.loadAddon(addons.serialize.instance);
|
||||
typedTerm.loadAddon(addons.unicodeGraphemes.instance);
|
||||
@@ -1458,3 +1467,44 @@ function testEvents(): void {
|
||||
document.getElementById('event-focus').addEventListener('click', ()=> term.focus());
|
||||
document.getElementById('event-blur').addEventListener('click', ()=> term.blur());
|
||||
}
|
||||
|
||||
|
||||
function progressButtons(): void {
|
||||
const STATES = { 0: 'remove', 1: 'set', 2: 'error', 3: 'indeterminate', 4: 'pause' };
|
||||
const COLORS = { 0: '', 1: 'green', 2: 'red', 3: '', 4: 'yellow' };
|
||||
|
||||
function progressHandler({state, value}: IProgressState) {
|
||||
// Simulate windows taskbar hack by windows terminal:
|
||||
// Since the taskbar has no means to indicate error/pause state other than by coloring
|
||||
// the current progress, we move 0 to 10% and distribute higher values in the remaining 90 %
|
||||
// NOTE: This is most likely not what you want to do for other progress indicators,
|
||||
// that have a proper visual state for error/paused.
|
||||
value = Math.min(10 + value * 0.9, 100);
|
||||
document.getElementById('progress-percent').style.width = `${value}%`;
|
||||
document.getElementById('progress-percent').style.backgroundColor = COLORS[state];
|
||||
document.getElementById('progress-state').innerText = `State: ${STATES[state]}`;
|
||||
|
||||
document.getElementById('progress-percent').style.display = state === 3 ? 'none' : 'block';
|
||||
document.getElementById('progress-indeterminate').style.display = state === 3 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
const progressAddon = addons.progress.instance;
|
||||
progressAddon.onChange(progressHandler);
|
||||
|
||||
// apply initial state once to make it visible on page load
|
||||
const initialProgress = progressAddon.progress;
|
||||
progressHandler(initialProgress);
|
||||
|
||||
document.getElementById('progress-run').addEventListener('click', async () => {
|
||||
term.write('\x1b]9;4;0\x1b\\');
|
||||
for (let i = 0; i <= 100; i += 5) {
|
||||
term.write(`\x1b]9;4;1;${i}\x1b\\`);
|
||||
await new Promise(res => setTimeout(res, 200));
|
||||
}
|
||||
});
|
||||
document.getElementById('progress-0').addEventListener('click', () => term.write('\x1b]9;4;0\x1b\\'));
|
||||
document.getElementById('progress-1').addEventListener('click', () => term.write('\x1b]9;4;1;20\x1b\\'));
|
||||
document.getElementById('progress-2').addEventListener('click', () => term.write('\x1b]9;4;2\x1b\\'));
|
||||
document.getElementById('progress-3').addEventListener('click', () => term.write('\x1b]9;4;3\x1b\\'));
|
||||
document.getElementById('progress-4').addEventListener('click', () => term.write('\x1b]9;4;4\x1b\\'));
|
||||
}
|
||||
|
||||
@@ -120,6 +120,47 @@
|
||||
<dt>Events Test</dt>
|
||||
<dd><button id="event-focus">focus</button></dd>
|
||||
<dd><button id="event-blur">blur</button></dd>
|
||||
|
||||
<dt>Progress Addon</dt>
|
||||
<dd><button id="progress-run">full set run</button></dd>
|
||||
<dd><button id="progress-0">state 0: remove</button></dd>
|
||||
<dd><button id="progress-1">state 1: set 20%</button></dd>
|
||||
<dd><button id="progress-2">state 2: error</button></dd>
|
||||
<dd><button id="progress-3">state 3: indeterminate</button></dd>
|
||||
<dd><button id="progress-4">state 4: pause</button></dd>
|
||||
<style>
|
||||
#progress-progress {
|
||||
border: 1px solid black;
|
||||
height: 10px;
|
||||
}
|
||||
#progress-percent {
|
||||
height: 100%;
|
||||
}
|
||||
#progress-indeterminate {
|
||||
display: none;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
#progress-indeterminate:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0px;
|
||||
width: 50px;
|
||||
height: 10px;
|
||||
background: blue;
|
||||
animation: ballbns 1s ease-in-out infinite alternate;
|
||||
}
|
||||
@keyframes ballbns {
|
||||
0% { left: 0; transform: translateX(0%); }
|
||||
100% { left: 100%; transform: translateX(-100%); }
|
||||
}
|
||||
</style>
|
||||
<dd><div id="progress-progress">
|
||||
<div id="progress-percent"></div>
|
||||
<div id="progress-indeterminate"></div>
|
||||
</div></dd>
|
||||
<dd><div id="progress-state">State:</div></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user