@xterm/xterm 5.4.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +235 -0
- package/css/xterm.css +209 -0
- package/lib/xterm.js +2 -0
- package/lib/xterm.js.map +1 -0
- package/package.json +101 -0
- package/src/browser/AccessibilityManager.ts +278 -0
- package/src/browser/Clipboard.ts +93 -0
- package/src/browser/ColorContrastCache.ts +34 -0
- package/src/browser/Lifecycle.ts +33 -0
- package/src/browser/Linkifier2.ts +416 -0
- package/src/browser/LocalizableStrings.ts +12 -0
- package/src/browser/OscLinkProvider.ts +128 -0
- package/src/browser/RenderDebouncer.ts +83 -0
- package/src/browser/Terminal.ts +1317 -0
- package/src/browser/TimeBasedDebouncer.ts +86 -0
- package/src/browser/Types.d.ts +181 -0
- package/src/browser/Viewport.ts +401 -0
- package/src/browser/decorations/BufferDecorationRenderer.ts +134 -0
- package/src/browser/decorations/ColorZoneStore.ts +117 -0
- package/src/browser/decorations/OverviewRulerRenderer.ts +218 -0
- package/src/browser/input/CompositionHelper.ts +246 -0
- package/src/browser/input/Mouse.ts +54 -0
- package/src/browser/input/MoveToCell.ts +249 -0
- package/src/browser/public/Terminal.ts +260 -0
- package/src/browser/renderer/dom/DomRenderer.ts +509 -0
- package/src/browser/renderer/dom/DomRendererRowFactory.ts +526 -0
- package/src/browser/renderer/dom/WidthCache.ts +160 -0
- package/src/browser/renderer/shared/CellColorResolver.ts +137 -0
- package/src/browser/renderer/shared/CharAtlasCache.ts +96 -0
- package/src/browser/renderer/shared/CharAtlasUtils.ts +75 -0
- package/src/browser/renderer/shared/Constants.ts +14 -0
- package/src/browser/renderer/shared/CursorBlinkStateManager.ts +146 -0
- package/src/browser/renderer/shared/CustomGlyphs.ts +687 -0
- package/src/browser/renderer/shared/DevicePixelObserver.ts +41 -0
- package/src/browser/renderer/shared/README.md +1 -0
- package/src/browser/renderer/shared/RendererUtils.ts +58 -0
- package/src/browser/renderer/shared/SelectionRenderModel.ts +91 -0
- package/src/browser/renderer/shared/TextureAtlas.ts +1082 -0
- package/src/browser/renderer/shared/Types.d.ts +173 -0
- package/src/browser/selection/SelectionModel.ts +144 -0
- package/src/browser/selection/Types.d.ts +15 -0
- package/src/browser/services/CharSizeService.ts +102 -0
- package/src/browser/services/CharacterJoinerService.ts +339 -0
- package/src/browser/services/CoreBrowserService.ts +137 -0
- package/src/browser/services/MouseService.ts +46 -0
- package/src/browser/services/RenderService.ts +279 -0
- package/src/browser/services/SelectionService.ts +1031 -0
- package/src/browser/services/Services.ts +147 -0
- package/src/browser/services/ThemeService.ts +237 -0
- package/src/common/CircularList.ts +241 -0
- package/src/common/Clone.ts +23 -0
- package/src/common/Color.ts +357 -0
- package/src/common/CoreTerminal.ts +284 -0
- package/src/common/EventEmitter.ts +78 -0
- package/src/common/InputHandler.ts +3461 -0
- package/src/common/Lifecycle.ts +108 -0
- package/src/common/MultiKeyMap.ts +42 -0
- package/src/common/Platform.ts +44 -0
- package/src/common/SortedList.ts +118 -0
- package/src/common/TaskQueue.ts +166 -0
- package/src/common/TypedArrayUtils.ts +17 -0
- package/src/common/Types.d.ts +553 -0
- package/src/common/WindowsMode.ts +27 -0
- package/src/common/buffer/AttributeData.ts +196 -0
- package/src/common/buffer/Buffer.ts +654 -0
- package/src/common/buffer/BufferLine.ts +524 -0
- package/src/common/buffer/BufferRange.ts +13 -0
- package/src/common/buffer/BufferReflow.ts +223 -0
- package/src/common/buffer/BufferSet.ts +134 -0
- package/src/common/buffer/CellData.ts +94 -0
- package/src/common/buffer/Constants.ts +149 -0
- package/src/common/buffer/Marker.ts +43 -0
- package/src/common/buffer/Types.d.ts +52 -0
- package/src/common/data/Charsets.ts +256 -0
- package/src/common/data/EscapeSequences.ts +153 -0
- package/src/common/input/Keyboard.ts +398 -0
- package/src/common/input/TextDecoder.ts +346 -0
- package/src/common/input/UnicodeV6.ts +145 -0
- package/src/common/input/WriteBuffer.ts +246 -0
- package/src/common/input/XParseColor.ts +80 -0
- package/src/common/parser/Constants.ts +58 -0
- package/src/common/parser/DcsParser.ts +192 -0
- package/src/common/parser/EscapeSequenceParser.ts +792 -0
- package/src/common/parser/OscParser.ts +238 -0
- package/src/common/parser/Params.ts +229 -0
- package/src/common/parser/Types.d.ts +275 -0
- package/src/common/public/AddonManager.ts +53 -0
- package/src/common/public/BufferApiView.ts +35 -0
- package/src/common/public/BufferLineApiView.ts +29 -0
- package/src/common/public/BufferNamespaceApi.ts +36 -0
- package/src/common/public/ParserApi.ts +37 -0
- package/src/common/public/UnicodeApi.ts +27 -0
- package/src/common/services/BufferService.ts +151 -0
- package/src/common/services/CharsetService.ts +34 -0
- package/src/common/services/CoreMouseService.ts +318 -0
- package/src/common/services/CoreService.ts +87 -0
- package/src/common/services/DecorationService.ts +140 -0
- package/src/common/services/InstantiationService.ts +85 -0
- package/src/common/services/LogService.ts +124 -0
- package/src/common/services/OptionsService.ts +202 -0
- package/src/common/services/OscLinkService.ts +115 -0
- package/src/common/services/ServiceRegistry.ts +49 -0
- package/src/common/services/Services.ts +373 -0
- package/src/common/services/UnicodeService.ts +111 -0
- package/src/headless/Terminal.ts +136 -0
- package/src/headless/public/Terminal.ts +195 -0
- package/typings/xterm.d.ts +1857 -0
package/package.json
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xterm/xterm",
|
|
3
|
+
"description": "Full xterm terminal, in your browser",
|
|
4
|
+
"version": "5.4.0-beta.1",
|
|
5
|
+
"main": "lib/xterm.js",
|
|
6
|
+
"style": "css/xterm.css",
|
|
7
|
+
"types": "typings/xterm.d.ts",
|
|
8
|
+
"repository": "https://github.com/xtermjs/xterm.js",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"keywords": [
|
|
11
|
+
"cli",
|
|
12
|
+
"command-line",
|
|
13
|
+
"console",
|
|
14
|
+
"pty",
|
|
15
|
+
"shell",
|
|
16
|
+
"ssh",
|
|
17
|
+
"styles",
|
|
18
|
+
"terminal-emulator",
|
|
19
|
+
"terminal",
|
|
20
|
+
"tty",
|
|
21
|
+
"vt100",
|
|
22
|
+
"webgl",
|
|
23
|
+
"xterm"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"prepackage": "npm run build",
|
|
27
|
+
"package": "webpack",
|
|
28
|
+
"package-headless": "webpack --config ./webpack.config.headless.js",
|
|
29
|
+
"postpackage-headless": "node ./bin/package_headless.js",
|
|
30
|
+
"start": "node demo/start",
|
|
31
|
+
"start-server-only": "node demo/start-server-only",
|
|
32
|
+
"build-demo": "webpack --config ./demo/webpack.config.js",
|
|
33
|
+
"lint": "eslint -c .eslintrc.json --max-warnings 0 --ext .ts src/ addons/",
|
|
34
|
+
"lint-api": "eslint --no-eslintrc -c .eslintrc.json.typings --max-warnings 0 --no-ignore --ext .d.ts typings/",
|
|
35
|
+
"test": "npm run test-unit",
|
|
36
|
+
"posttest": "npm run lint",
|
|
37
|
+
"test-api": "npm run test-api-chromium",
|
|
38
|
+
"test-api-chromium": "node ./bin/test_api.js --browser=chromium --timeout=20000",
|
|
39
|
+
"test-api-firefox": "node ./bin/test_api.js --browser=firefox --timeout=20000",
|
|
40
|
+
"test-api-webkit": "node ./bin/test_api.js --browser=webkit --timeout=20000",
|
|
41
|
+
"test-playwright": "node ./bin/test_playwright.js --workers=75%",
|
|
42
|
+
"test-playwright-chromium": "node ./bin/test_playwright.js --workers=75% \"--project=Chrome Stable\"",
|
|
43
|
+
"test-playwright-firefox": "node ./bin/test_playwright.js --workers=75% \"--project=Firefox Stable\"",
|
|
44
|
+
"test-playwright-webkit": "node ./bin/test_playwright.js --workers=75% \"--project=WebKit\"",
|
|
45
|
+
"test-playwright-debug": "node ./bin/test_playwright.js --workers=1 --headed --timeout=30000",
|
|
46
|
+
"test-unit": "node ./bin/test.js",
|
|
47
|
+
"test-unit-coverage": "node ./bin/test.js --coverage",
|
|
48
|
+
"test-unit-dev": "cross-env NODE_PATH='./out' mocha",
|
|
49
|
+
"build": "tsc -b ./tsconfig.all.json",
|
|
50
|
+
"install-addons": "node ./bin/install-addons.js",
|
|
51
|
+
"presetup": "npm run install-addons",
|
|
52
|
+
"setup": "npm run build",
|
|
53
|
+
"prepublishOnly": "npm run package",
|
|
54
|
+
"watch": "tsc -b -w ./tsconfig.all.json --preserveWatchOutput",
|
|
55
|
+
"benchmark": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json",
|
|
56
|
+
"benchmark-baseline": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --baseline out-test/benchmark/test/benchmark/*benchmark.js",
|
|
57
|
+
"benchmark-eval": "NODE_PATH=./out xterm-benchmark -r 5 -c test/benchmark/benchmark.json --eval out-test/benchmark/test/benchmark/*benchmark.js",
|
|
58
|
+
"clean": "rm -rf lib out addons/*/lib addons/*/out",
|
|
59
|
+
"vtfeatures": "node bin/extract_vtfeatures.js src/**/*.ts src/*.ts"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@lunapaint/png-codec": "^0.2.0",
|
|
63
|
+
"@playwright/test": "^1.37.1",
|
|
64
|
+
"@types/chai": "^4.2.22",
|
|
65
|
+
"@types/debug": "^4.1.7",
|
|
66
|
+
"@types/deep-equal": "^1.0.1",
|
|
67
|
+
"@types/express": "4",
|
|
68
|
+
"@types/express-ws": "^3.0.1",
|
|
69
|
+
"@types/glob": "^7.2.0",
|
|
70
|
+
"@types/jsdom": "^16.2.13",
|
|
71
|
+
"@types/mocha": "^9.0.0",
|
|
72
|
+
"@types/node": "^18.16.0",
|
|
73
|
+
"@types/utf8": "^3.0.0",
|
|
74
|
+
"@types/webpack": "^5.28.0",
|
|
75
|
+
"@types/ws": "^8.2.0",
|
|
76
|
+
"@typescript-eslint/eslint-plugin": "^6.2.00",
|
|
77
|
+
"@typescript-eslint/parser": "^6.2.00",
|
|
78
|
+
"chai": "^4.3.4",
|
|
79
|
+
"cross-env": "^7.0.3",
|
|
80
|
+
"deep-equal": "^2.0.5",
|
|
81
|
+
"eslint": "^8.45.0",
|
|
82
|
+
"eslint-plugin-jsdoc": "^39.3.6",
|
|
83
|
+
"express": "^4.17.1",
|
|
84
|
+
"express-ws": "^5.0.2",
|
|
85
|
+
"glob": "^7.2.0",
|
|
86
|
+
"jsdom": "^18.0.1",
|
|
87
|
+
"mocha": "^10.1.0",
|
|
88
|
+
"mustache": "^4.2.0",
|
|
89
|
+
"node-pty": "1.1.0-beta5",
|
|
90
|
+
"nyc": "^15.1.0",
|
|
91
|
+
"source-map-loader": "^3.0.0",
|
|
92
|
+
"source-map-support": "^0.5.20",
|
|
93
|
+
"ts-loader": "^9.3.1",
|
|
94
|
+
"typescript": "^5.1.6",
|
|
95
|
+
"utf8": "^3.0.0",
|
|
96
|
+
"webpack": "^5.61.0",
|
|
97
|
+
"webpack-cli": "^4.9.1",
|
|
98
|
+
"ws": "^8.2.3",
|
|
99
|
+
"xterm-benchmark": "^0.3.1"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
|
3
|
+
* @license MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as Strings from 'browser/LocalizableStrings';
|
|
7
|
+
import { ITerminal, IRenderDebouncer } from 'browser/Types';
|
|
8
|
+
import { TimeBasedDebouncer } from 'browser/TimeBasedDebouncer';
|
|
9
|
+
import { Disposable, toDisposable } from 'common/Lifecycle';
|
|
10
|
+
import { ICoreBrowserService, IRenderService } from 'browser/services/Services';
|
|
11
|
+
import { IBuffer } from 'common/buffer/Types';
|
|
12
|
+
import { IInstantiationService } from 'common/services/Services';
|
|
13
|
+
|
|
14
|
+
const MAX_ROWS_TO_READ = 20;
|
|
15
|
+
|
|
16
|
+
const enum BoundaryPosition {
|
|
17
|
+
TOP,
|
|
18
|
+
BOTTOM
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class AccessibilityManager extends Disposable {
|
|
22
|
+
private _accessibilityContainer: HTMLElement;
|
|
23
|
+
|
|
24
|
+
private _rowContainer: HTMLElement;
|
|
25
|
+
private _rowElements: HTMLElement[];
|
|
26
|
+
|
|
27
|
+
private _liveRegion: HTMLElement;
|
|
28
|
+
private _liveRegionLineCount: number = 0;
|
|
29
|
+
private _liveRegionDebouncer: IRenderDebouncer;
|
|
30
|
+
|
|
31
|
+
private _topBoundaryFocusListener: (e: FocusEvent) => void;
|
|
32
|
+
private _bottomBoundaryFocusListener: (e: FocusEvent) => void;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* This queue has a character pushed to it for keys that are pressed, if the
|
|
36
|
+
* next character added to the terminal is equal to the key char then it is
|
|
37
|
+
* not announced (added to live region) because it has already been announced
|
|
38
|
+
* by the textarea event (which cannot be canceled). There are some race
|
|
39
|
+
* condition cases if there is typing while data is streaming, but this covers
|
|
40
|
+
* the main case of typing into the prompt and inputting the answer to a
|
|
41
|
+
* question (Y/N, etc.).
|
|
42
|
+
*/
|
|
43
|
+
private _charsToConsume: string[] = [];
|
|
44
|
+
|
|
45
|
+
private _charsToAnnounce: string = '';
|
|
46
|
+
|
|
47
|
+
constructor(
|
|
48
|
+
private readonly _terminal: ITerminal,
|
|
49
|
+
@IInstantiationService instantiationService: IInstantiationService,
|
|
50
|
+
@ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService,
|
|
51
|
+
@IRenderService private readonly _renderService: IRenderService
|
|
52
|
+
) {
|
|
53
|
+
super();
|
|
54
|
+
this._accessibilityContainer = this._coreBrowserService.mainDocument.createElement('div');
|
|
55
|
+
this._accessibilityContainer.classList.add('xterm-accessibility');
|
|
56
|
+
|
|
57
|
+
this._rowContainer = this._coreBrowserService.mainDocument.createElement('div');
|
|
58
|
+
this._rowContainer.setAttribute('role', 'list');
|
|
59
|
+
this._rowContainer.classList.add('xterm-accessibility-tree');
|
|
60
|
+
this._rowElements = [];
|
|
61
|
+
for (let i = 0; i < this._terminal.rows; i++) {
|
|
62
|
+
this._rowElements[i] = this._createAccessibilityTreeNode();
|
|
63
|
+
this._rowContainer.appendChild(this._rowElements[i]);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this._topBoundaryFocusListener = e => this._handleBoundaryFocus(e, BoundaryPosition.TOP);
|
|
67
|
+
this._bottomBoundaryFocusListener = e => this._handleBoundaryFocus(e, BoundaryPosition.BOTTOM);
|
|
68
|
+
this._rowElements[0].addEventListener('focus', this._topBoundaryFocusListener);
|
|
69
|
+
this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener);
|
|
70
|
+
|
|
71
|
+
this._refreshRowsDimensions();
|
|
72
|
+
this._accessibilityContainer.appendChild(this._rowContainer);
|
|
73
|
+
|
|
74
|
+
this._liveRegion = this._coreBrowserService.mainDocument.createElement('div');
|
|
75
|
+
this._liveRegion.classList.add('live-region');
|
|
76
|
+
this._liveRegion.setAttribute('aria-live', 'assertive');
|
|
77
|
+
this._accessibilityContainer.appendChild(this._liveRegion);
|
|
78
|
+
this._liveRegionDebouncer = this.register(new TimeBasedDebouncer(this._renderRows.bind(this)));
|
|
79
|
+
|
|
80
|
+
if (!this._terminal.element) {
|
|
81
|
+
throw new Error('Cannot enable accessibility before Terminal.open');
|
|
82
|
+
}
|
|
83
|
+
this._terminal.element.insertAdjacentElement('afterbegin', this._accessibilityContainer);
|
|
84
|
+
|
|
85
|
+
this.register(this._terminal.onResize(e => this._handleResize(e.rows)));
|
|
86
|
+
this.register(this._terminal.onRender(e => this._refreshRows(e.start, e.end)));
|
|
87
|
+
this.register(this._terminal.onScroll(() => this._refreshRows()));
|
|
88
|
+
// Line feed is an issue as the prompt won't be read out after a command is run
|
|
89
|
+
this.register(this._terminal.onA11yChar(char => this._handleChar(char)));
|
|
90
|
+
this.register(this._terminal.onLineFeed(() => this._handleChar('\n')));
|
|
91
|
+
this.register(this._terminal.onA11yTab(spaceCount => this._handleTab(spaceCount)));
|
|
92
|
+
this.register(this._terminal.onKey(e => this._handleKey(e.key)));
|
|
93
|
+
this.register(this._terminal.onBlur(() => this._clearLiveRegion()));
|
|
94
|
+
this.register(this._renderService.onDimensionsChange(() => this._refreshRowsDimensions()));
|
|
95
|
+
this.register(this._coreBrowserService.onDprChange(() => this._refreshRowsDimensions()));
|
|
96
|
+
|
|
97
|
+
this._refreshRows();
|
|
98
|
+
this.register(toDisposable(() => {
|
|
99
|
+
this._accessibilityContainer.remove();
|
|
100
|
+
this._rowElements.length = 0;
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private _handleTab(spaceCount: number): void {
|
|
105
|
+
for (let i = 0; i < spaceCount; i++) {
|
|
106
|
+
this._handleChar(' ');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private _handleChar(char: string): void {
|
|
111
|
+
if (this._liveRegionLineCount < MAX_ROWS_TO_READ + 1) {
|
|
112
|
+
if (this._charsToConsume.length > 0) {
|
|
113
|
+
// Have the screen reader ignore the char if it was just input
|
|
114
|
+
const shiftedChar = this._charsToConsume.shift();
|
|
115
|
+
if (shiftedChar !== char) {
|
|
116
|
+
this._charsToAnnounce += char;
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
this._charsToAnnounce += char;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (char === '\n') {
|
|
123
|
+
this._liveRegionLineCount++;
|
|
124
|
+
if (this._liveRegionLineCount === MAX_ROWS_TO_READ + 1) {
|
|
125
|
+
this._liveRegion.textContent += Strings.tooMuchOutput;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private _clearLiveRegion(): void {
|
|
132
|
+
this._liveRegion.textContent = '';
|
|
133
|
+
this._liveRegionLineCount = 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private _handleKey(keyChar: string): void {
|
|
137
|
+
this._clearLiveRegion();
|
|
138
|
+
// Only add the char if there is no control character.
|
|
139
|
+
if (!/\p{Control}/u.test(keyChar)) {
|
|
140
|
+
this._charsToConsume.push(keyChar);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private _refreshRows(start?: number, end?: number): void {
|
|
145
|
+
this._liveRegionDebouncer.refresh(start, end, this._terminal.rows);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private _renderRows(start: number, end: number): void {
|
|
149
|
+
const buffer: IBuffer = this._terminal.buffer;
|
|
150
|
+
const setSize = buffer.lines.length.toString();
|
|
151
|
+
for (let i = start; i <= end; i++) {
|
|
152
|
+
const lineData = buffer.translateBufferLineToString(buffer.ydisp + i, true);
|
|
153
|
+
const posInSet = (buffer.ydisp + i + 1).toString();
|
|
154
|
+
const element = this._rowElements[i];
|
|
155
|
+
if (element) {
|
|
156
|
+
if (lineData.length === 0) {
|
|
157
|
+
element.innerText = '\u00a0';
|
|
158
|
+
} else {
|
|
159
|
+
element.textContent = lineData;
|
|
160
|
+
}
|
|
161
|
+
element.setAttribute('aria-posinset', posInSet);
|
|
162
|
+
element.setAttribute('aria-setsize', setSize);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
this._announceCharacters();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private _announceCharacters(): void {
|
|
169
|
+
if (this._charsToAnnounce.length === 0) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
this._liveRegion.textContent += this._charsToAnnounce;
|
|
173
|
+
this._charsToAnnounce = '';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private _handleBoundaryFocus(e: FocusEvent, position: BoundaryPosition): void {
|
|
177
|
+
const boundaryElement = e.target as HTMLElement;
|
|
178
|
+
const beforeBoundaryElement = this._rowElements[position === BoundaryPosition.TOP ? 1 : this._rowElements.length - 2];
|
|
179
|
+
|
|
180
|
+
// Don't scroll if the buffer top has reached the end in that direction
|
|
181
|
+
const posInSet = boundaryElement.getAttribute('aria-posinset');
|
|
182
|
+
const lastRowPos = position === BoundaryPosition.TOP ? '1' : `${this._terminal.buffer.lines.length}`;
|
|
183
|
+
if (posInSet === lastRowPos) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Don't scroll when the last focused item was not the second row (focus is going the other
|
|
188
|
+
// direction)
|
|
189
|
+
if (e.relatedTarget !== beforeBoundaryElement) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Remove old boundary element from array
|
|
194
|
+
let topBoundaryElement: HTMLElement;
|
|
195
|
+
let bottomBoundaryElement: HTMLElement;
|
|
196
|
+
if (position === BoundaryPosition.TOP) {
|
|
197
|
+
topBoundaryElement = boundaryElement;
|
|
198
|
+
bottomBoundaryElement = this._rowElements.pop()!;
|
|
199
|
+
this._rowContainer.removeChild(bottomBoundaryElement);
|
|
200
|
+
} else {
|
|
201
|
+
topBoundaryElement = this._rowElements.shift()!;
|
|
202
|
+
bottomBoundaryElement = boundaryElement;
|
|
203
|
+
this._rowContainer.removeChild(topBoundaryElement);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Remove listeners from old boundary elements
|
|
207
|
+
topBoundaryElement.removeEventListener('focus', this._topBoundaryFocusListener);
|
|
208
|
+
bottomBoundaryElement.removeEventListener('focus', this._bottomBoundaryFocusListener);
|
|
209
|
+
|
|
210
|
+
// Add new element to array/DOM
|
|
211
|
+
if (position === BoundaryPosition.TOP) {
|
|
212
|
+
const newElement = this._createAccessibilityTreeNode();
|
|
213
|
+
this._rowElements.unshift(newElement);
|
|
214
|
+
this._rowContainer.insertAdjacentElement('afterbegin', newElement);
|
|
215
|
+
} else {
|
|
216
|
+
const newElement = this._createAccessibilityTreeNode();
|
|
217
|
+
this._rowElements.push(newElement);
|
|
218
|
+
this._rowContainer.appendChild(newElement);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Add listeners to new boundary elements
|
|
222
|
+
this._rowElements[0].addEventListener('focus', this._topBoundaryFocusListener);
|
|
223
|
+
this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener);
|
|
224
|
+
|
|
225
|
+
// Scroll up
|
|
226
|
+
this._terminal.scrollLines(position === BoundaryPosition.TOP ? -1 : 1);
|
|
227
|
+
|
|
228
|
+
// Focus new boundary before element
|
|
229
|
+
this._rowElements[position === BoundaryPosition.TOP ? 1 : this._rowElements.length - 2].focus();
|
|
230
|
+
|
|
231
|
+
// Prevent the standard behavior
|
|
232
|
+
e.preventDefault();
|
|
233
|
+
e.stopImmediatePropagation();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private _handleResize(rows: number): void {
|
|
237
|
+
// Remove bottom boundary listener
|
|
238
|
+
this._rowElements[this._rowElements.length - 1].removeEventListener('focus', this._bottomBoundaryFocusListener);
|
|
239
|
+
|
|
240
|
+
// Grow rows as required
|
|
241
|
+
for (let i = this._rowContainer.children.length; i < this._terminal.rows; i++) {
|
|
242
|
+
this._rowElements[i] = this._createAccessibilityTreeNode();
|
|
243
|
+
this._rowContainer.appendChild(this._rowElements[i]);
|
|
244
|
+
}
|
|
245
|
+
// Shrink rows as required
|
|
246
|
+
while (this._rowElements.length > rows) {
|
|
247
|
+
this._rowContainer.removeChild(this._rowElements.pop()!);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Add bottom boundary listener
|
|
251
|
+
this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener);
|
|
252
|
+
|
|
253
|
+
this._refreshRowsDimensions();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private _createAccessibilityTreeNode(): HTMLElement {
|
|
257
|
+
const element = this._coreBrowserService.mainDocument.createElement('div');
|
|
258
|
+
element.setAttribute('role', 'listitem');
|
|
259
|
+
element.tabIndex = -1;
|
|
260
|
+
this._refreshRowDimensions(element);
|
|
261
|
+
return element;
|
|
262
|
+
}
|
|
263
|
+
private _refreshRowsDimensions(): void {
|
|
264
|
+
if (!this._renderService.dimensions.css.cell.height) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
this._accessibilityContainer.style.width = `${this._renderService.dimensions.css.canvas.width}px`;
|
|
268
|
+
if (this._rowElements.length !== this._terminal.rows) {
|
|
269
|
+
this._handleResize(this._terminal.rows);
|
|
270
|
+
}
|
|
271
|
+
for (let i = 0; i < this._terminal.rows; i++) {
|
|
272
|
+
this._refreshRowDimensions(this._rowElements[i]);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
private _refreshRowDimensions(element: HTMLElement): void {
|
|
276
|
+
element.style.height = `${this._renderService.dimensions.css.cell.height}px`;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2016 The xterm.js authors. All rights reserved.
|
|
3
|
+
* @license MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ISelectionService } from 'browser/services/Services';
|
|
7
|
+
import { ICoreService, IOptionsService } from 'common/services/Services';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Prepares text to be pasted into the terminal by normalizing the line endings
|
|
11
|
+
* @param text The pasted text that needs processing before inserting into the terminal
|
|
12
|
+
*/
|
|
13
|
+
export function prepareTextForTerminal(text: string): string {
|
|
14
|
+
return text.replace(/\r?\n/g, '\r');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Bracket text for paste, if necessary, as per https://cirw.in/blog/bracketed-paste
|
|
19
|
+
* @param text The pasted text to bracket
|
|
20
|
+
*/
|
|
21
|
+
export function bracketTextForPaste(text: string, bracketedPasteMode: boolean): string {
|
|
22
|
+
if (bracketedPasteMode) {
|
|
23
|
+
return '\x1b[200~' + text + '\x1b[201~';
|
|
24
|
+
}
|
|
25
|
+
return text;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Binds copy functionality to the given terminal.
|
|
30
|
+
* @param ev The original copy event to be handled
|
|
31
|
+
*/
|
|
32
|
+
export function copyHandler(ev: ClipboardEvent, selectionService: ISelectionService): void {
|
|
33
|
+
if (ev.clipboardData) {
|
|
34
|
+
ev.clipboardData.setData('text/plain', selectionService.selectionText);
|
|
35
|
+
}
|
|
36
|
+
// Prevent or the original text will be copied.
|
|
37
|
+
ev.preventDefault();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Redirect the clipboard's data to the terminal's input handler.
|
|
42
|
+
*/
|
|
43
|
+
export function handlePasteEvent(ev: ClipboardEvent, textarea: HTMLTextAreaElement, coreService: ICoreService, optionsService: IOptionsService): void {
|
|
44
|
+
ev.stopPropagation();
|
|
45
|
+
if (ev.clipboardData) {
|
|
46
|
+
const text = ev.clipboardData.getData('text/plain');
|
|
47
|
+
paste(text, textarea, coreService, optionsService);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function paste(text: string, textarea: HTMLTextAreaElement, coreService: ICoreService, optionsService: IOptionsService): void {
|
|
52
|
+
text = prepareTextForTerminal(text);
|
|
53
|
+
text = bracketTextForPaste(text, coreService.decPrivateModes.bracketedPasteMode && optionsService.rawOptions.ignoreBracketedPasteMode !== true);
|
|
54
|
+
coreService.triggerDataEvent(text, true);
|
|
55
|
+
textarea.value = '';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Moves the textarea under the mouse cursor and focuses it.
|
|
60
|
+
* @param ev The original right click event to be handled.
|
|
61
|
+
* @param textarea The terminal's textarea.
|
|
62
|
+
*/
|
|
63
|
+
export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextAreaElement, screenElement: HTMLElement): void {
|
|
64
|
+
|
|
65
|
+
// Calculate textarea position relative to the screen element
|
|
66
|
+
const pos = screenElement.getBoundingClientRect();
|
|
67
|
+
const left = ev.clientX - pos.left - 10;
|
|
68
|
+
const top = ev.clientY - pos.top - 10;
|
|
69
|
+
|
|
70
|
+
// Bring textarea at the cursor position
|
|
71
|
+
textarea.style.width = '20px';
|
|
72
|
+
textarea.style.height = '20px';
|
|
73
|
+
textarea.style.left = `${left}px`;
|
|
74
|
+
textarea.style.top = `${top}px`;
|
|
75
|
+
textarea.style.zIndex = '1000';
|
|
76
|
+
|
|
77
|
+
textarea.focus();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Bind to right-click event and allow right-click copy and paste.
|
|
82
|
+
*/
|
|
83
|
+
export function rightClickHandler(ev: MouseEvent, textarea: HTMLTextAreaElement, screenElement: HTMLElement, selectionService: ISelectionService, shouldSelectWord: boolean): void {
|
|
84
|
+
moveTextAreaUnderMouseCursor(ev, textarea, screenElement);
|
|
85
|
+
|
|
86
|
+
if (shouldSelectWord) {
|
|
87
|
+
selectionService.rightClickSelect(ev);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Get textarea ready to copy from the context menu
|
|
91
|
+
textarea.value = selectionService.selectionText;
|
|
92
|
+
textarea.select();
|
|
93
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
|
|
3
|
+
* @license MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { IColorContrastCache } from 'browser/Types';
|
|
7
|
+
import { IColor } from 'common/Types';
|
|
8
|
+
import { TwoKeyMap } from 'common/MultiKeyMap';
|
|
9
|
+
|
|
10
|
+
export class ColorContrastCache implements IColorContrastCache {
|
|
11
|
+
private _color: TwoKeyMap</* bg */number, /* fg */number, IColor | null> = new TwoKeyMap();
|
|
12
|
+
private _css: TwoKeyMap</* bg */number, /* fg */number, string | null> = new TwoKeyMap();
|
|
13
|
+
|
|
14
|
+
public setCss(bg: number, fg: number, value: string | null): void {
|
|
15
|
+
this._css.set(bg, fg, value);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public getCss(bg: number, fg: number): string | null | undefined {
|
|
19
|
+
return this._css.get(bg, fg);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public setColor(bg: number, fg: number, value: IColor | null): void {
|
|
23
|
+
this._color.set(bg, fg, value);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public getColor(bg: number, fg: number): IColor | null | undefined {
|
|
27
|
+
return this._color.get(bg, fg);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public clear(): void {
|
|
31
|
+
this._color.clear();
|
|
32
|
+
this._css.clear();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
|
3
|
+
* @license MIT
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { IDisposable } from 'common/Types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Adds a disposable listener to a node in the DOM, returning the disposable.
|
|
10
|
+
* @param node The node to add a listener to.
|
|
11
|
+
* @param type The event type.
|
|
12
|
+
* @param handler The handler for the listener.
|
|
13
|
+
* @param options The boolean or options object to pass on to the event
|
|
14
|
+
* listener.
|
|
15
|
+
*/
|
|
16
|
+
export function addDisposableDomListener(
|
|
17
|
+
node: Element | Window | Document,
|
|
18
|
+
type: string,
|
|
19
|
+
handler: (e: any) => void,
|
|
20
|
+
options?: boolean | AddEventListenerOptions
|
|
21
|
+
): IDisposable {
|
|
22
|
+
node.addEventListener(type, handler, options);
|
|
23
|
+
let disposed = false;
|
|
24
|
+
return {
|
|
25
|
+
dispose: () => {
|
|
26
|
+
if (disposed) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
disposed = true;
|
|
30
|
+
node.removeEventListener(type, handler, options);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|