aboutsummaryrefslogtreecommitdiffstats
path: root/node_modules/xterm/src
diff options
context:
space:
mode:
authorAnthony Schneider <tonyschneider3@gmail.com>2022-02-11 19:40:35 -0600
committerAnthony Schneider <tonyschneider3@gmail.com>2022-02-11 19:40:35 -0600
commitb52feccdcc58c1f4583c8542632d6c026335dea7 (patch)
tree5e242dd13ed4bbfff85a07109ef826f80874e2a6 /node_modules/xterm/src
parent94862321e2e4a58e3209c037e8061f0435b3aa82 (diff)
Changed javascript to be in its own file. Began (messy) setup for terminal.
Diffstat (limited to 'node_modules/xterm/src')
-rw-r--r--node_modules/xterm/src/browser/AccessibilityManager.ts302
-rw-r--r--node_modules/xterm/src/browser/Clipboard.ts99
-rw-r--r--node_modules/xterm/src/browser/Color.ts236
-rw-r--r--node_modules/xterm/src/browser/ColorContrastCache.ts38
-rw-r--r--node_modules/xterm/src/browser/ColorManager.ts258
-rw-r--r--node_modules/xterm/src/browser/Dom.ts10
-rw-r--r--node_modules/xterm/src/browser/Lifecycle.ts30
-rw-r--r--node_modules/xterm/src/browser/Linkifier.ts356
-rw-r--r--node_modules/xterm/src/browser/Linkifier2.ts392
-rw-r--r--node_modules/xterm/src/browser/LocalizableStrings.ts10
-rw-r--r--node_modules/xterm/src/browser/MouseZoneManager.ts236
-rw-r--r--node_modules/xterm/src/browser/RenderDebouncer.ts63
-rw-r--r--node_modules/xterm/src/browser/ScreenDprMonitor.ts69
-rw-r--r--node_modules/xterm/src/browser/Terminal.ts1399
-rw-r--r--node_modules/xterm/src/browser/TimeBasedDebouncer.ts86
-rw-r--r--node_modules/xterm/src/browser/Types.d.ts315
-rw-r--r--node_modules/xterm/src/browser/Viewport.ts294
-rw-r--r--node_modules/xterm/src/browser/input/CompositionHelper.ts237
-rw-r--r--node_modules/xterm/src/browser/input/Mouse.ts58
-rw-r--r--node_modules/xterm/src/browser/input/MoveToCell.ts249
-rw-r--r--node_modules/xterm/src/browser/public/Terminal.ts284
-rw-r--r--node_modules/xterm/src/browser/renderer/BaseRenderLayer.ts513
-rw-r--r--node_modules/xterm/src/browser/renderer/CursorRenderLayer.ts377
-rw-r--r--node_modules/xterm/src/browser/renderer/CustomGlyphs.ts563
-rw-r--r--node_modules/xterm/src/browser/renderer/GridCache.ts33
-rw-r--r--node_modules/xterm/src/browser/renderer/LinkRenderLayer.ts83
-rw-r--r--node_modules/xterm/src/browser/renderer/Renderer.ts215
-rw-r--r--node_modules/xterm/src/browser/renderer/RendererUtils.ts11
-rw-r--r--node_modules/xterm/src/browser/renderer/SelectionRenderLayer.ts128
-rw-r--r--node_modules/xterm/src/browser/renderer/TextRenderLayer.ts330
-rw-r--r--node_modules/xterm/src/browser/renderer/Types.d.ts109
-rw-r--r--node_modules/xterm/src/browser/renderer/atlas/BaseCharAtlas.ts58
-rw-r--r--node_modules/xterm/src/browser/renderer/atlas/CharAtlasCache.ts95
-rw-r--r--node_modules/xterm/src/browser/renderer/atlas/CharAtlasUtils.ts54
-rw-r--r--node_modules/xterm/src/browser/renderer/atlas/Constants.ts15
-rw-r--r--node_modules/xterm/src/browser/renderer/atlas/DynamicCharAtlas.ts404
-rw-r--r--node_modules/xterm/src/browser/renderer/atlas/LRUMap.ts136
-rw-r--r--node_modules/xterm/src/browser/renderer/atlas/Types.d.ts29
-rw-r--r--node_modules/xterm/src/browser/renderer/dom/DomRenderer.ts400
-rw-r--r--node_modules/xterm/src/browser/renderer/dom/DomRendererRowFactory.ts259
-rw-r--r--node_modules/xterm/src/browser/selection/SelectionModel.ts139
-rw-r--r--node_modules/xterm/src/browser/selection/Types.d.ts15
-rw-r--r--node_modules/xterm/src/browser/services/CharSizeService.ts87
-rw-r--r--node_modules/xterm/src/browser/services/CharacterJoinerService.ts339
-rw-r--r--node_modules/xterm/src/browser/services/CoreBrowserService.ts20
-rw-r--r--node_modules/xterm/src/browser/services/MouseService.ts35
-rw-r--r--node_modules/xterm/src/browser/services/RenderService.ts222
-rw-r--r--node_modules/xterm/src/browser/services/SelectionService.ts1009
-rw-r--r--node_modules/xterm/src/browser/services/Services.ts115
-rw-r--r--node_modules/xterm/src/browser/services/SoundService.ts63
-rw-r--r--node_modules/xterm/src/common/CircularList.ts239
-rw-r--r--node_modules/xterm/src/common/Clone.ts23
-rw-r--r--node_modules/xterm/src/common/CoreTerminal.ts297
-rw-r--r--node_modules/xterm/src/common/EventEmitter.ts69
-rw-r--r--node_modules/xterm/src/common/InputHandler.ts3229
-rw-r--r--node_modules/xterm/src/common/Lifecycle.ts68
-rw-r--r--node_modules/xterm/src/common/Platform.ts31
-rw-r--r--node_modules/xterm/src/common/TypedArrayUtils.ts50
-rw-r--r--node_modules/xterm/src/common/Types.d.ts483
-rw-r--r--node_modules/xterm/src/common/WindowsMode.ts27
-rw-r--r--node_modules/xterm/src/common/buffer/AttributeData.ts148
-rw-r--r--node_modules/xterm/src/common/buffer/Buffer.ts681
-rw-r--r--node_modules/xterm/src/common/buffer/BufferLine.ts441
-rw-r--r--node_modules/xterm/src/common/buffer/BufferRange.ts13
-rw-r--r--node_modules/xterm/src/common/buffer/BufferReflow.ts220
-rw-r--r--node_modules/xterm/src/common/buffer/BufferSet.ts131
-rw-r--r--node_modules/xterm/src/common/buffer/CellData.ts94
-rw-r--r--node_modules/xterm/src/common/buffer/Constants.ts139
-rw-r--r--node_modules/xterm/src/common/buffer/Marker.ts37
-rw-r--r--node_modules/xterm/src/common/buffer/Types.d.ts62
-rw-r--r--node_modules/xterm/src/common/data/Charsets.ts256
-rw-r--r--node_modules/xterm/src/common/data/EscapeSequences.ts150
-rw-r--r--node_modules/xterm/src/common/input/Keyboard.ts375
-rw-r--r--node_modules/xterm/src/common/input/TextDecoder.ts346
-rw-r--r--node_modules/xterm/src/common/input/UnicodeV6.ts133
-rw-r--r--node_modules/xterm/src/common/input/WriteBuffer.ts224
-rw-r--r--node_modules/xterm/src/common/input/XParseColor.ts80
-rw-r--r--node_modules/xterm/src/common/parser/Constants.ts58
-rw-r--r--node_modules/xterm/src/common/parser/DcsParser.ts192
-rw-r--r--node_modules/xterm/src/common/parser/EscapeSequenceParser.ts796
-rw-r--r--node_modules/xterm/src/common/parser/OscParser.ts238
-rw-r--r--node_modules/xterm/src/common/parser/Params.ts229
-rw-r--r--node_modules/xterm/src/common/parser/Types.d.ts274
-rw-r--r--node_modules/xterm/src/common/public/AddonManager.ts56
-rw-r--r--node_modules/xterm/src/common/public/BufferApiView.ts35
-rw-r--r--node_modules/xterm/src/common/public/BufferLineApiView.ts29
-rw-r--r--node_modules/xterm/src/common/public/BufferNamespaceApi.ts33
-rw-r--r--node_modules/xterm/src/common/public/ParserApi.ts37
-rw-r--r--node_modules/xterm/src/common/public/UnicodeApi.ts27
-rw-r--r--node_modules/xterm/src/common/services/BufferService.ts185
-rw-r--r--node_modules/xterm/src/common/services/CharsetService.ts34
-rw-r--r--node_modules/xterm/src/common/services/CoreMouseService.ts309
-rw-r--r--node_modules/xterm/src/common/services/CoreService.ts92
-rw-r--r--node_modules/xterm/src/common/services/DirtyRowService.ts53
-rw-r--r--node_modules/xterm/src/common/services/InstantiationService.ts83
-rw-r--r--node_modules/xterm/src/common/services/LogService.ts88
-rw-r--r--node_modules/xterm/src/common/services/OptionsService.ts177
-rw-r--r--node_modules/xterm/src/common/services/ServiceRegistry.ts49
-rw-r--r--node_modules/xterm/src/common/services/Services.ts300
-rw-r--r--node_modules/xterm/src/common/services/UnicodeService.ts82
-rw-r--r--node_modules/xterm/src/headless/Terminal.ts170
-rw-r--r--node_modules/xterm/src/headless/Types.d.ts31
-rw-r--r--node_modules/xterm/src/headless/public/Terminal.ts216
103 files changed, 22796 insertions, 0 deletions
diff --git a/node_modules/xterm/src/browser/AccessibilityManager.ts b/node_modules/xterm/src/browser/AccessibilityManager.ts
new file mode 100644
index 0000000..eda29c0
--- /dev/null
+++ b/node_modules/xterm/src/browser/AccessibilityManager.ts
@@ -0,0 +1,302 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import * as Strings from 'browser/LocalizableStrings';
+import { ITerminal, IRenderDebouncer } from 'browser/Types';
+import { IBuffer } from 'common/buffer/Types';
+import { isMac } from 'common/Platform';
+import { TimeBasedDebouncer } from 'browser/TimeBasedDebouncer';
+import { addDisposableDomListener } from 'browser/Lifecycle';
+import { Disposable } from 'common/Lifecycle';
+import { ScreenDprMonitor } from 'browser/ScreenDprMonitor';
+import { IRenderService } from 'browser/services/Services';
+import { removeElementFromParent } from 'browser/Dom';
+
+const MAX_ROWS_TO_READ = 20;
+
+const enum BoundaryPosition {
+ TOP,
+ BOTTOM
+}
+
+export class AccessibilityManager extends Disposable {
+ private _accessibilityTreeRoot: HTMLElement;
+ private _rowContainer: HTMLElement;
+ private _rowElements: HTMLElement[];
+ private _liveRegion: HTMLElement;
+ private _liveRegionLineCount: number = 0;
+
+ private _renderRowsDebouncer: IRenderDebouncer;
+ private _screenDprMonitor: ScreenDprMonitor;
+
+ private _topBoundaryFocusListener: (e: FocusEvent) => void;
+ private _bottomBoundaryFocusListener: (e: FocusEvent) => void;
+
+ /**
+ * This queue has a character pushed to it for keys that are pressed, if the
+ * next character added to the terminal is equal to the key char then it is
+ * not announced (added to live region) because it has already been announced
+ * by the textarea event (which cannot be canceled). There are some race
+ * condition cases if there is typing while data is streaming, but this covers
+ * the main case of typing into the prompt and inputting the answer to a
+ * question (Y/N, etc.).
+ */
+ private _charsToConsume: string[] = [];
+
+ private _charsToAnnounce: string = '';
+
+ constructor(
+ private readonly _terminal: ITerminal,
+ private readonly _renderService: IRenderService
+ ) {
+ super();
+ this._accessibilityTreeRoot = document.createElement('div');
+ this._accessibilityTreeRoot.setAttribute('role', 'document');
+ this._accessibilityTreeRoot.classList.add('xterm-accessibility');
+ this._accessibilityTreeRoot.tabIndex = 0;
+
+ this._rowContainer = document.createElement('div');
+ this._rowContainer.setAttribute('role', 'list');
+ this._rowContainer.classList.add('xterm-accessibility-tree');
+ this._rowElements = [];
+ for (let i = 0; i < this._terminal.rows; i++) {
+ this._rowElements[i] = this._createAccessibilityTreeNode();
+ this._rowContainer.appendChild(this._rowElements[i]);
+ }
+
+ this._topBoundaryFocusListener = e => this._onBoundaryFocus(e, BoundaryPosition.TOP);
+ this._bottomBoundaryFocusListener = e => this._onBoundaryFocus(e, BoundaryPosition.BOTTOM);
+ this._rowElements[0].addEventListener('focus', this._topBoundaryFocusListener);
+ this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener);
+
+ this._refreshRowsDimensions();
+ this._accessibilityTreeRoot.appendChild(this._rowContainer);
+
+ this._renderRowsDebouncer = new TimeBasedDebouncer(this._renderRows.bind(this));
+ this._refreshRows();
+
+ this._liveRegion = document.createElement('div');
+ this._liveRegion.classList.add('live-region');
+ this._liveRegion.setAttribute('aria-live', 'assertive');
+ this._accessibilityTreeRoot.appendChild(this._liveRegion);
+
+ if (!this._terminal.element) {
+ throw new Error('Cannot enable accessibility before Terminal.open');
+ }
+ this._terminal.element.insertAdjacentElement('afterbegin', this._accessibilityTreeRoot);
+
+ this.register(this._renderRowsDebouncer);
+ this.register(this._terminal.onResize(e => this._onResize(e.rows)));
+ this.register(this._terminal.onRender(e => this._refreshRows(e.start, e.end)));
+ this.register(this._terminal.onScroll(() => this._refreshRows()));
+ // Line feed is an issue as the prompt won't be read out after a command is run
+ this.register(this._terminal.onA11yChar(char => this._onChar(char)));
+ this.register(this._terminal.onLineFeed(() => this._onChar('\n')));
+ this.register(this._terminal.onA11yTab(spaceCount => this._onTab(spaceCount)));
+ this.register(this._terminal.onKey(e => this._onKey(e.key)));
+ this.register(this._terminal.onBlur(() => this._clearLiveRegion()));
+ this.register(this._renderService.onDimensionsChange(() => this._refreshRowsDimensions()));
+
+ this._screenDprMonitor = new ScreenDprMonitor();
+ this.register(this._screenDprMonitor);
+ this._screenDprMonitor.setListener(() => this._refreshRowsDimensions());
+ // This shouldn't be needed on modern browsers but is present in case the
+ // media query that drives the ScreenDprMonitor isn't supported
+ this.register(addDisposableDomListener(window, 'resize', () => this._refreshRowsDimensions()));
+ }
+
+ public dispose(): void {
+ super.dispose();
+ removeElementFromParent(this._accessibilityTreeRoot);
+ this._rowElements.length = 0;
+ }
+
+ private _onBoundaryFocus(e: FocusEvent, position: BoundaryPosition): void {
+ const boundaryElement = e.target as HTMLElement;
+ const beforeBoundaryElement = this._rowElements[position === BoundaryPosition.TOP ? 1 : this._rowElements.length - 2];
+
+ // Don't scroll if the buffer top has reached the end in that direction
+ const posInSet = boundaryElement.getAttribute('aria-posinset');
+ const lastRowPos = position === BoundaryPosition.TOP ? '1' : `${this._terminal.buffer.lines.length}`;
+ if (posInSet === lastRowPos) {
+ return;
+ }
+
+ // Don't scroll when the last focused item was not the second row (focus is going the other
+ // direction)
+ if (e.relatedTarget !== beforeBoundaryElement) {
+ return;
+ }
+
+ // Remove old boundary element from array
+ let topBoundaryElement: HTMLElement;
+ let bottomBoundaryElement: HTMLElement;
+ if (position === BoundaryPosition.TOP) {
+ topBoundaryElement = boundaryElement;
+ bottomBoundaryElement = this._rowElements.pop()!;
+ this._rowContainer.removeChild(bottomBoundaryElement);
+ } else {
+ topBoundaryElement = this._rowElements.shift()!;
+ bottomBoundaryElement = boundaryElement;
+ this._rowContainer.removeChild(topBoundaryElement);
+ }
+
+ // Remove listeners from old boundary elements
+ topBoundaryElement.removeEventListener('focus', this._topBoundaryFocusListener);
+ bottomBoundaryElement.removeEventListener('focus', this._bottomBoundaryFocusListener);
+
+ // Add new element to array/DOM
+ if (position === BoundaryPosition.TOP) {
+ const newElement = this._createAccessibilityTreeNode();
+ this._rowElements.unshift(newElement);
+ this._rowContainer.insertAdjacentElement('afterbegin', newElement);
+ } else {
+ const newElement = this._createAccessibilityTreeNode();
+ this._rowElements.push(newElement);
+ this._rowContainer.appendChild(newElement);
+ }
+
+ // Add listeners to new boundary elements
+ this._rowElements[0].addEventListener('focus', this._topBoundaryFocusListener);
+ this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener);
+
+ // Scroll up
+ this._terminal.scrollLines(position === BoundaryPosition.TOP ? -1 : 1);
+
+ // Focus new boundary before element
+ this._rowElements[position === BoundaryPosition.TOP ? 1 : this._rowElements.length - 2].focus();
+
+ // Prevent the standard behavior
+ e.preventDefault();
+ e.stopImmediatePropagation();
+ }
+
+ private _onResize(rows: number): void {
+ // Remove bottom boundary listener
+ this._rowElements[this._rowElements.length - 1].removeEventListener('focus', this._bottomBoundaryFocusListener);
+
+ // Grow rows as required
+ for (let i = this._rowContainer.children.length; i < this._terminal.rows; i++) {
+ this._rowElements[i] = this._createAccessibilityTreeNode();
+ this._rowContainer.appendChild(this._rowElements[i]);
+ }
+ // Shrink rows as required
+ while (this._rowElements.length > rows) {
+ this._rowContainer.removeChild(this._rowElements.pop()!);
+ }
+
+ // Add bottom boundary listener
+ this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener);
+
+ this._refreshRowsDimensions();
+ }
+
+ private _createAccessibilityTreeNode(): HTMLElement {
+ const element = document.createElement('div');
+ element.setAttribute('role', 'listitem');
+ element.tabIndex = -1;
+ this._refreshRowDimensions(element);
+ return element;
+ }
+
+ private _onTab(spaceCount: number): void {
+ for (let i = 0; i < spaceCount; i++) {
+ this._onChar(' ');
+ }
+ }
+
+ private _onChar(char: string): void {
+ if (this._liveRegionLineCount < MAX_ROWS_TO_READ + 1) {
+ if (this._charsToConsume.length > 0) {
+ // Have the screen reader ignore the char if it was just input
+ const shiftedChar = this._charsToConsume.shift();
+ if (shiftedChar !== char) {
+ this._charsToAnnounce += char;
+ }
+ } else {
+ this._charsToAnnounce += char;
+ }
+
+ if (char === '\n') {
+ this._liveRegionLineCount++;
+ if (this._liveRegionLineCount === MAX_ROWS_TO_READ + 1) {
+ this._liveRegion.textContent += Strings.tooMuchOutput;
+ }
+ }
+
+ // Only detach/attach on mac as otherwise messages can go unaccounced
+ if (isMac) {
+ if (this._liveRegion.textContent && this._liveRegion.textContent.length > 0 && !this._liveRegion.parentNode) {
+ setTimeout(() => {
+ this._accessibilityTreeRoot.appendChild(this._liveRegion);
+ }, 0);
+ }
+ }
+ }
+ }
+
+ private _clearLiveRegion(): void {
+ this._liveRegion.textContent = '';
+ this._liveRegionLineCount = 0;
+
+ // Only detach/attach on mac as otherwise messages can go unaccounced
+ if (isMac) {
+ removeElementFromParent(this._liveRegion);
+ }
+ }
+
+ private _onKey(keyChar: string): void {
+ this._clearLiveRegion();
+ this._charsToConsume.push(keyChar);
+ }
+
+ private _refreshRows(start?: number, end?: number): void {
+ this._renderRowsDebouncer.refresh(start, end, this._terminal.rows);
+ }
+
+ private _renderRows(start: number, end: number): void {
+ const buffer: IBuffer = this._terminal.buffer;
+ const setSize = buffer.lines.length.toString();
+ for (let i = start; i <= end; i++) {
+ const lineData = buffer.translateBufferLineToString(buffer.ydisp + i, true);
+ const posInSet = (buffer.ydisp + i + 1).toString();
+ const element = this._rowElements[i];
+ if (element) {
+ if (lineData.length === 0) {
+ element.innerText = '\u00a0';
+ } else {
+ element.textContent = lineData;
+ }
+ element.setAttribute('aria-posinset', posInSet);
+ element.setAttribute('aria-setsize', setSize);
+ }
+ }
+ this._announceCharacters();
+ }
+
+ private _refreshRowsDimensions(): void {
+ if (!this._renderService.dimensions.actualCellHeight) {
+ return;
+ }
+ if (this._rowElements.length !== this._terminal.rows) {
+ this._onResize(this._terminal.rows);
+ }
+ for (let i = 0; i < this._terminal.rows; i++) {
+ this._refreshRowDimensions(this._rowElements[i]);
+ }
+ }
+
+ private _refreshRowDimensions(element: HTMLElement): void {
+ element.style.height = `${this._renderService.dimensions.actualCellHeight}px`;
+ }
+
+ private _announceCharacters(): void {
+ if (this._charsToAnnounce.length === 0) {
+ return;
+ }
+ this._liveRegion.textContent += this._charsToAnnounce;
+ this._charsToAnnounce = '';
+ }
+}
diff --git a/node_modules/xterm/src/browser/Clipboard.ts b/node_modules/xterm/src/browser/Clipboard.ts
new file mode 100644
index 0000000..29e865c
--- /dev/null
+++ b/node_modules/xterm/src/browser/Clipboard.ts
@@ -0,0 +1,99 @@
+/**
+ * Copyright (c) 2016 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { ISelectionService } from 'browser/services/Services';
+import { ICoreService } from 'common/services/Services';
+
+/**
+ * Prepares text to be pasted into the terminal by normalizing the line endings
+ * @param text The pasted text that needs processing before inserting into the terminal
+ */
+export function prepareTextForTerminal(text: string): string {
+ return text.replace(/\r?\n/g, '\r');
+}
+
+/**
+ * Bracket text for paste, if necessary, as per https://cirw.in/blog/bracketed-paste
+ * @param text The pasted text to bracket
+ */
+export function bracketTextForPaste(text: string, bracketedPasteMode: boolean): string {
+ if (bracketedPasteMode) {
+ return '\x1b[200~' + text + '\x1b[201~';
+ }
+ return text;
+}
+
+/**
+ * Binds copy functionality to the given terminal.
+ * @param ev The original copy event to be handled
+ */
+export function copyHandler(ev: ClipboardEvent, selectionService: ISelectionService): void {
+ if (ev.clipboardData) {
+ ev.clipboardData.setData('text/plain', selectionService.selectionText);
+ }
+ // Prevent or the original text will be copied.
+ ev.preventDefault();
+}
+
+/**
+ * Redirect the clipboard's data to the terminal's input handler.
+ * @param ev The original paste event to be handled
+ * @param term The terminal on which to apply the handled paste event
+ */
+export function handlePasteEvent(ev: ClipboardEvent, textarea: HTMLTextAreaElement, coreService: ICoreService): void {
+ ev.stopPropagation();
+ if (ev.clipboardData) {
+ const text = ev.clipboardData.getData('text/plain');
+ paste(text, textarea, coreService);
+ }
+}
+
+export function paste(text: string, textarea: HTMLTextAreaElement, coreService: ICoreService): void {
+ text = prepareTextForTerminal(text);
+ text = bracketTextForPaste(text, coreService.decPrivateModes.bracketedPasteMode);
+ coreService.triggerDataEvent(text, true);
+ textarea.value = '';
+}
+
+/**
+ * Moves the textarea under the mouse cursor and focuses it.
+ * @param ev The original right click event to be handled.
+ * @param textarea The terminal's textarea.
+ */
+export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextAreaElement, screenElement: HTMLElement): void {
+
+ // Calculate textarea position relative to the screen element
+ const pos = screenElement.getBoundingClientRect();
+ const left = ev.clientX - pos.left - 10;
+ const top = ev.clientY - pos.top - 10;
+
+ // Bring textarea at the cursor position
+ textarea.style.width = '20px';
+ textarea.style.height = '20px';
+ textarea.style.left = `${left}px`;
+ textarea.style.top = `${top}px`;
+ textarea.style.zIndex = '1000';
+
+ textarea.focus();
+}
+
+/**
+ * Bind to right-click event and allow right-click copy and paste.
+ * @param ev The original right click event to be handled.
+ * @param textarea The terminal's textarea.
+ * @param selectionService The terminal's selection manager.
+ * @param shouldSelectWord If true and there is no selection the current word will be selected
+ */
+export function rightClickHandler(ev: MouseEvent, textarea: HTMLTextAreaElement, screenElement: HTMLElement, selectionService: ISelectionService, shouldSelectWord: boolean): void {
+ moveTextAreaUnderMouseCursor(ev, textarea, screenElement);
+
+ if (shouldSelectWord) {
+ selectionService.rightClickSelect(ev);
+ }
+
+ // Get textarea ready to copy from the context menu
+ textarea.value = selectionService.selectionText;
+ textarea.select();
+}
diff --git a/node_modules/xterm/src/browser/Color.ts b/node_modules/xterm/src/browser/Color.ts
new file mode 100644
index 0000000..32e311d
--- /dev/null
+++ b/node_modules/xterm/src/browser/Color.ts
@@ -0,0 +1,236 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IColor } from 'browser/Types';
+import { IColorRGB } from 'common/Types';
+
+/**
+ * Helper functions where the source type is "channels" (individual color channels as numbers).
+ */
+export namespace channels {
+ export function toCss(r: number, g: number, b: number, a?: number): string {
+ if (a !== undefined) {
+ return `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}${toPaddedHex(a)}`;
+ }
+ return `#${toPaddedHex(r)}${toPaddedHex(g)}${toPaddedHex(b)}`;
+ }
+
+ export function toRgba(r: number, g: number, b: number, a: number = 0xFF): number {
+ // Note: The aggregated number is RGBA32 (BE), thus needs to be converted to ABGR32
+ // on LE systems, before it can be used for direct 32-bit buffer writes.
+ // >>> 0 forces an unsigned int
+ return (r << 24 | g << 16 | b << 8 | a) >>> 0;
+ }
+}
+
+/**
+ * Helper functions where the source type is `IColor`.
+ */
+export namespace color {
+ export function blend(bg: IColor, fg: IColor): IColor {
+ const a = (fg.rgba & 0xFF) / 255;
+ if (a === 1) {
+ return {
+ css: fg.css,
+ rgba: fg.rgba
+ };
+ }
+ const fgR = (fg.rgba >> 24) & 0xFF;
+ const fgG = (fg.rgba >> 16) & 0xFF;
+ const fgB = (fg.rgba >> 8) & 0xFF;
+ const bgR = (bg.rgba >> 24) & 0xFF;
+ const bgG = (bg.rgba >> 16) & 0xFF;
+ const bgB = (bg.rgba >> 8) & 0xFF;
+ const r = bgR + Math.round((fgR - bgR) * a);
+ const g = bgG + Math.round((fgG - bgG) * a);
+ const b = bgB + Math.round((fgB - bgB) * a);
+ const css = channels.toCss(r, g, b);
+ const rgba = channels.toRgba(r, g, b);
+ return { css, rgba };
+ }
+
+ export function isOpaque(color: IColor): boolean {
+ return (color.rgba & 0xFF) === 0xFF;
+ }
+
+ export function ensureContrastRatio(bg: IColor, fg: IColor, ratio: number): IColor | undefined {
+ const result = rgba.ensureContrastRatio(bg.rgba, fg.rgba, ratio);
+ if (!result) {
+ return undefined;
+ }
+ return rgba.toColor(
+ (result >> 24 & 0xFF),
+ (result >> 16 & 0xFF),
+ (result >> 8 & 0xFF)
+ );
+ }
+
+ export function opaque(color: IColor): IColor {
+ const rgbaColor = (color.rgba | 0xFF) >>> 0;
+ const [r, g, b] = rgba.toChannels(rgbaColor);
+ return {
+ css: channels.toCss(r, g, b),
+ rgba: rgbaColor
+ };
+ }
+
+ export function opacity(color: IColor, opacity: number): IColor {
+ const a = Math.round(opacity * 0xFF);
+ const [r, g, b] = rgba.toChannels(color.rgba);
+ return {
+ css: channels.toCss(r, g, b, a),
+ rgba: channels.toRgba(r, g, b, a)
+ };
+ }
+
+ export function toColorRGB(color: IColor): IColorRGB {
+ return [(color.rgba >> 24) & 0xFF, (color.rgba >> 16) & 0xFF, (color.rgba >> 8) & 0xFF];
+ }
+}
+
+/**
+ * Helper functions where the source type is "css" (string: '#rgb', '#rgba', '#rrggbb', '#rrggbbaa').
+ */
+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
+ };
+ }
+ throw new Error('css.toColor: Unsupported css format');
+ }
+}
+
+/**
+ * Helper functions where the source type is "rgb" (number: 0xrrggbb).
+ */
+export namespace rgb {
+ /**
+ * Gets the relative luminance of an RGB color, this is useful in determining the contrast ratio
+ * between two colors.
+ * @param rgb The color to use.
+ * @see https://www.w3.org/TR/WCAG20/#relativeluminancedef
+ */
+ export function relativeLuminance(rgb: number): number {
+ return relativeLuminance2(
+ (rgb >> 16) & 0xFF,
+ (rgb >> 8 ) & 0xFF,
+ (rgb ) & 0xFF);
+ }
+
+ /**
+ * Gets the relative luminance of an RGB color, this is useful in determining the contrast ratio
+ * between two colors.
+ * @param r The red channel (0x00 to 0xFF).
+ * @param g The green channel (0x00 to 0xFF).
+ * @param b The blue channel (0x00 to 0xFF).
+ * @see https://www.w3.org/TR/WCAG20/#relativeluminancedef
+ */
+ export function relativeLuminance2(r: number, g: number, b: number): number {
+ const rs = r / 255;
+ const gs = g / 255;
+ const bs = b / 255;
+ const rr = rs <= 0.03928 ? rs / 12.92 : Math.pow((rs + 0.055) / 1.055, 2.4);
+ const rg = gs <= 0.03928 ? gs / 12.92 : Math.pow((gs + 0.055) / 1.055, 2.4);
+ const rb = bs <= 0.03928 ? bs / 12.92 : Math.pow((bs + 0.055) / 1.055, 2.4);
+ return rr * 0.2126 + rg * 0.7152 + rb * 0.0722;
+ }
+}
+
+/**
+ * Helper functions where the source type is "rgba" (number: 0xrrggbbaa).
+ */
+export namespace rgba {
+ 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);
+ }
+ return increaseLuminance(bgRgba, fgRgba, ratio);
+ }
+ return undefined;
+ }
+
+ export function reduceLuminance(bgRgba: number, fgRgba: number, ratio: number): number {
+ // This is a naive but fast approach to reducing luminance as converting to
+ // HSL and back is expensive
+ const bgR = (bgRgba >> 24) & 0xFF;
+ const bgG = (bgRgba >> 16) & 0xFF;
+ const bgB = (bgRgba >> 8) & 0xFF;
+ 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));
+ 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));
+ }
+ return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0;
+ }
+
+ export function increaseLuminance(bgRgba: number, fgRgba: number, ratio: number): number {
+ // This is a naive but fast approach to increasing luminance as converting to
+ // HSL and back is expensive
+ const bgR = (bgRgba >> 24) & 0xFF;
+ const bgG = (bgRgba >> 16) & 0xFF;
+ const bgB = (bgRgba >> 8) & 0xFF;
+ 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));
+ 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));
+ }
+ return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0;
+ }
+
+ // FIXME: Move this to channels NS?
+ export function toChannels(value: number): [number, number, number, number] {
+ return [(value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF];
+ }
+
+ export function toColor(r: number, g: number, b: number): IColor {
+ return {
+ css: channels.toCss(r, g, b),
+ rgba: channels.toRgba(r, g, b)
+ };
+ }
+}
+
+export function toPaddedHex(c: number): string {
+ const s = c.toString(16);
+ return s.length < 2 ? '0' + s : s;
+}
+
+/**
+ * Gets the contrast ratio between two relative luminance values.
+ * @param l1 The first relative luminance.
+ * @param l2 The first relative luminance.
+ * @see https://www.w3.org/TR/WCAG20/#contrast-ratiodef
+ */
+export function contrastRatio(l1: number, l2: number): number {
+ if (l1 < l2) {
+ return (l2 + 0.05) / (l1 + 0.05);
+ }
+ return (l1 + 0.05) / (l2 + 0.05);
+}
diff --git a/node_modules/xterm/src/browser/ColorContrastCache.ts b/node_modules/xterm/src/browser/ColorContrastCache.ts
new file mode 100644
index 0000000..b96b66c
--- /dev/null
+++ b/node_modules/xterm/src/browser/ColorContrastCache.ts
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IColor, IColorContrastCache } from 'browser/Types';
+
+export class ColorContrastCache implements IColorContrastCache {
+ private _color: { [bg: number]: { [fg: number]: IColor | null | undefined } | undefined } = {};
+ private _rgba: { [bg: number]: { [fg: number]: string | null | undefined } | undefined } = {};
+
+ public clear(): void {
+ this._color = {};
+ this._rgba = {};
+ }
+
+ public setCss(bg: number, fg: number, value: string | null): void {
+ if (!this._rgba[bg]) {
+ this._rgba[bg] = {};
+ }
+ this._rgba[bg]![fg] = value;
+ }
+
+ public getCss(bg: number, fg: number): string | null | undefined {
+ return this._rgba[bg] ? this._rgba[bg]![fg] : undefined;
+ }
+
+ public setColor(bg: number, fg: number, value: IColor | null): void {
+ if (!this._color[bg]) {
+ this._color[bg] = {};
+ }
+ this._color[bg]![fg] = value;
+ }
+
+ public getColor(bg: number, fg: number): IColor | null | undefined {
+ return this._color[bg] ? this._color[bg]![fg] : undefined;
+ }
+}
diff --git a/node_modules/xterm/src/browser/ColorManager.ts b/node_modules/xterm/src/browser/ColorManager.ts
new file mode 100644
index 0000000..b4b57c6
--- /dev/null
+++ b/node_modules/xterm/src/browser/ColorManager.ts
@@ -0,0 +1,258 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IColorManager, IColor, IColorSet, IColorContrastCache } from 'browser/Types';
+import { ITheme } from 'common/services/Services';
+import { channels, color, css } from 'browser/Color';
+import { ColorContrastCache } from 'browser/ColorContrastCache';
+import { ColorIndex } from 'common/Types';
+
+
+interface IRestoreColorSet {
+ foreground: IColor;
+ background: IColor;
+ cursor: IColor;
+ ansi: IColor[];
+}
+
+
+const DEFAULT_FOREGROUND = css.toColor('#ffffff');
+const DEFAULT_BACKGROUND = css.toColor('#000000');
+const DEFAULT_CURSOR = css.toColor('#ffffff');
+const DEFAULT_CURSOR_ACCENT = css.toColor('#000000');
+const DEFAULT_SELECTION = {
+ css: 'rgba(255, 255, 255, 0.3)',
+ rgba: 0xFFFFFF4D
+};
+
+// An IIFE to generate DEFAULT_ANSI_COLORS.
+export const DEFAULT_ANSI_COLORS = Object.freeze((() => {
+ const colors = [
+ // dark:
+ css.toColor('#2e3436'),
+ css.toColor('#cc0000'),
+ css.toColor('#4e9a06'),
+ css.toColor('#c4a000'),
+ css.toColor('#3465a4'),
+ css.toColor('#75507b'),
+ css.toColor('#06989a'),
+ css.toColor('#d3d7cf'),
+ // bright:
+ css.toColor('#555753'),
+ css.toColor('#ef2929'),
+ css.toColor('#8ae234'),
+ css.toColor('#fce94f'),
+ css.toColor('#729fcf'),
+ css.toColor('#ad7fa8'),
+ css.toColor('#34e2e2'),
+ css.toColor('#eeeeec')
+ ];
+
+ // Fill in the remaining 240 ANSI colors.
+ // Generate colors (16-231)
+ const v = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff];
+ for (let i = 0; i < 216; i++) {
+ const r = v[(i / 36) % 6 | 0];
+ const g = v[(i / 6) % 6 | 0];
+ const b = v[i % 6];
+ colors.push({
+ css: channels.toCss(r, g, b),
+ rgba: channels.toRgba(r, g, b)
+ });
+ }
+
+ // Generate greys (232-255)
+ for (let i = 0; i < 24; i++) {
+ const c = 8 + i * 10;
+ colors.push({
+ css: channels.toCss(c, c, c),
+ rgba: channels.toRgba(c, c, c)
+ });
+ }
+
+ return colors;
+})());
+
+/**
+ * Manages the source of truth for a terminal's colors.
+ */
+export class ColorManager implements IColorManager {
+ public colors: IColorSet;
+ private _ctx: CanvasRenderingContext2D;
+ private _litmusColor: CanvasGradient;
+ private _contrastCache: IColorContrastCache;
+ private _restoreColors!: IRestoreColorSet;
+
+ constructor(document: Document, public allowTransparency: boolean) {
+ const canvas = document.createElement('canvas');
+ canvas.width = 1;
+ canvas.height = 1;
+ const ctx = canvas.getContext('2d');
+ if (!ctx) {
+ throw new Error('Could not get rendering context');
+ }
+ this._ctx = ctx;
+ this._ctx.globalCompositeOperation = 'copy';
+ this._litmusColor = this._ctx.createLinearGradient(0, 0, 1, 1);
+ this._contrastCache = new ColorContrastCache();
+ this.colors = {
+ foreground: DEFAULT_FOREGROUND,
+ background: DEFAULT_BACKGROUND,
+ cursor: DEFAULT_CURSOR,
+ cursorAccent: DEFAULT_CURSOR_ACCENT,
+ selectionTransparent: DEFAULT_SELECTION,
+ selectionOpaque: color.blend(DEFAULT_BACKGROUND, DEFAULT_SELECTION),
+ ansi: DEFAULT_ANSI_COLORS.slice(),
+ contrastCache: this._contrastCache
+ };
+ this._updateRestoreColors();
+ }
+
+ public onOptionsChange(key: string): void {
+ if (key === 'minimumContrastRatio') {
+ this._contrastCache.clear();
+ }
+ }
+
+ /**
+ * Sets the terminal's theme.
+ * @param theme The theme to use. If a partial theme is provided then default
+ * colors will be used where colors are not defined.
+ */
+ public setTheme(theme: ITheme = {}): void {
+ this.colors.foreground = this._parseColor(theme.foreground, DEFAULT_FOREGROUND);
+ this.colors.background = this._parseColor(theme.background, DEFAULT_BACKGROUND);
+ this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR, true);
+ this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT, true);
+ this.colors.selectionTransparent = this._parseColor(theme.selection, DEFAULT_SELECTION, true);
+ this.colors.selectionOpaque = color.blend(this.colors.background, this.colors.selectionTransparent);
+ /**
+ * If selection color is opaque, blend it with background with 0.3 opacity
+ * Issue #2737
+ */
+ if (color.isOpaque(this.colors.selectionTransparent)) {
+ const opacity = 0.3;
+ this.colors.selectionTransparent = color.opacity(this.colors.selectionTransparent, opacity);
+ }
+ this.colors.ansi[0] = this._parseColor(theme.black, DEFAULT_ANSI_COLORS[0]);
+ this.colors.ansi[1] = this._parseColor(theme.red, DEFAULT_ANSI_COLORS[1]);
+ this.colors.ansi[2] = this._parseColor(theme.green, DEFAULT_ANSI_COLORS[2]);
+ this.colors.ansi[3] = this._parseColor(theme.yellow, DEFAULT_ANSI_COLORS[3]);
+ this.colors.ansi[4] = this._parseColor(theme.blue, DEFAULT_ANSI_COLORS[4]);
+ this.colors.ansi[5] = this._parseColor(theme.magenta, DEFAULT_ANSI_COLORS[5]);
+ this.colors.ansi[6] = this._parseColor(theme.cyan, DEFAULT_ANSI_COLORS[6]);
+ this.colors.ansi[7] = this._parseColor(theme.white, DEFAULT_ANSI_COLORS[7]);
+ this.colors.ansi[8] = this._parseColor(theme.brightBlack, DEFAULT_ANSI_COLORS[8]);
+ this.colors.ansi[9] = this._parseColor(theme.brightRed, DEFAULT_ANSI_COLORS[9]);
+ this.colors.ansi[10] = this._parseColor(theme.brightGreen, DEFAULT_ANSI_COLORS[10]);
+ this.colors.ansi[11] = this._parseColor(theme.brightYellow, DEFAULT_ANSI_COLORS[11]);
+ this.colors.ansi[12] = this._parseColor(theme.brightBlue, DEFAULT_ANSI_COLORS[12]);
+ this.colors.ansi[13] = this._parseColor(theme.brightMagenta, DEFAULT_ANSI_COLORS[13]);
+ this.colors.ansi[14] = this._parseColor(theme.brightCyan, DEFAULT_ANSI_COLORS[14]);
+ this.colors.ansi[15] = this._parseColor(theme.brightWhite, DEFAULT_ANSI_COLORS[15]);
+ // Clear our the cache
+ this._contrastCache.clear();
+ this._updateRestoreColors();
+ }
+
+ public restoreColor(slot?: ColorIndex): void {
+ // unset slot restores all ansi colors
+ if (slot === undefined) {
+ for (let i = 0; i < this._restoreColors.ansi.length; ++i) {
+ this.colors.ansi[i] = this._restoreColors.ansi[i];
+ }
+ return;
+ }
+ switch (slot) {
+ case ColorIndex.FOREGROUND:
+ this.colors.foreground = this._restoreColors.foreground;
+ break;
+ case ColorIndex.BACKGROUND:
+ this.colors.background = this._restoreColors.background;
+ break;
+ case ColorIndex.CURSOR:
+ this.colors.cursor = this._restoreColors.cursor;
+ break;
+ default:
+ this.colors.ansi[slot] = this._restoreColors.ansi[slot];
+ }
+ }
+
+ private _updateRestoreColors(): void {
+ this._restoreColors = {
+ foreground: this.colors.foreground,
+ background: this.colors.background,
+ cursor: this.colors.cursor,
+ ansi: [...this.colors.ansi]
+ };
+ }
+
+ private _parseColor(
+ css: string | undefined,
+ fallback: IColor,
+ allowTransparency: boolean = this.allowTransparency
+ ): IColor {
+ if (css === undefined) {
+ return fallback;
+ }
+
+ // If parsing the value results in failure, then it must be ignored, and the attribute must
+ // retain its previous value.
+ // -- https://html.spec.whatwg.org/multipage/canvas.html#fill-and-stroke-styles
+ this._ctx.fillStyle = this._litmusColor;
+ this._ctx.fillStyle = css;
+ if (typeof this._ctx.fillStyle !== 'string') {
+ console.warn(`Color: ${css} is invalid using fallback ${fallback.css}`);
+ return fallback;
+ }
+
+ this._ctx.fillRect(0, 0, 1, 1);
+ const data = this._ctx.getImageData(0, 0, 1, 1).data;
+
+ // Check if the printed color was transparent
+ if (data[3] !== 0xFF) {
+ if (!allowTransparency) {
+ // Ideally we'd just ignore the alpha channel, but...
+ //
+ // Browsers may not give back exactly the same RGB values we put in, because most/all
+ // convert the color to a pre-multiplied representation. getImageData converts that back to
+ // a un-premultipled representation, but the precision loss may make the RGB channels unuable
+ // on their own.
+ //
+ // E.g. In Chrome #12345610 turns into #10305010, and in the extreme case, 0xFFFFFF00 turns
+ // into 0x00000000.
+ //
+ // "Note: Due to the lossy nature of converting to and from premultiplied alpha color values,
+ // pixels that have just been set using putImageData() might be returned to an equivalent
+ // getImageData() as different values."
+ // -- https://html.spec.whatwg.org/multipage/canvas.html#pixel-manipulation
+ //
+ // So let's just use the fallback color in this case instead.
+ console.warn(
+ `Color: ${css} is using transparency, but allowTransparency is false. ` +
+ `Using fallback ${fallback.css}.`
+ );
+ return fallback;
+ }
+
+ // https://html.spec.whatwg.org/multipage/canvas.html#serialisation-of-a-color
+ // the color value has alpha less than 1.0, and the string is the color value in the CSS rgba()
+ const [r, g, b, a] = this._ctx.fillStyle.substring(5, this._ctx.fillStyle.length - 1).split(',').map(component => Number(component));
+ const alpha = Math.round(a * 255);
+ const rgba: number = channels.toRgba(r, g, b, alpha);
+ return {
+ rgba,
+ css
+ };
+ }
+
+ return {
+ // https://html.spec.whatwg.org/multipage/canvas.html#serialisation-of-a-color
+ // if it has alpha equal to 1.0, then the string is a lowercase six-digit hex value, prefixed with a "#" character
+ css: this._ctx.fillStyle,
+ rgba: channels.toRgba(data[0], data[1], data[2], data[3])
+ };
+ }
+}
diff --git a/node_modules/xterm/src/browser/Dom.ts b/node_modules/xterm/src/browser/Dom.ts
new file mode 100644
index 0000000..c558a8b
--- /dev/null
+++ b/node_modules/xterm/src/browser/Dom.ts
@@ -0,0 +1,10 @@
+/**
+ * Copyright (c) 2020 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+export function removeElementFromParent(...elements: (HTMLElement | undefined)[]): void {
+ for (const e of elements) {
+ e?.parentElement?.removeChild(e);
+ }
+}
diff --git a/node_modules/xterm/src/browser/Lifecycle.ts b/node_modules/xterm/src/browser/Lifecycle.ts
new file mode 100644
index 0000000..6e84179
--- /dev/null
+++ b/node_modules/xterm/src/browser/Lifecycle.ts
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IDisposable } from 'common/Types';
+
+/**
+ * Adds a disposable listener to a node in the DOM, returning the disposable.
+ * @param type The event type.
+ * @param handler The handler for the listener.
+ */
+export function addDisposableDomListener(
+ node: Element | Window | Document,
+ type: string,
+ handler: (e: any) => void,
+ options?: boolean | AddEventListenerOptions
+): IDisposable {
+ node.addEventListener(type, handler, options);
+ let disposed = false;
+ return {
+ dispose: () => {
+ if (disposed) {
+ return;
+ }
+ disposed = true;
+ node.removeEventListener(type, handler, options);
+ }
+ };
+}
diff --git a/node_modules/xterm/src/browser/Linkifier.ts b/node_modules/xterm/src/browser/Linkifier.ts
new file mode 100644
index 0000000..b17d66a
--- /dev/null
+++ b/node_modules/xterm/src/browser/Linkifier.ts
@@ -0,0 +1,356 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { ILinkifierEvent, ILinkMatcher, LinkMatcherHandler, ILinkMatcherOptions, ILinkifier, IMouseZoneManager, IMouseZone, IRegisteredLinkMatcher } from 'browser/Types';
+import { IBufferStringIteratorResult } from 'common/buffer/Types';
+import { EventEmitter, IEvent } from 'common/EventEmitter';
+import { ILogService, IBufferService, IOptionsService, IUnicodeService } from 'common/services/Services';
+
+/**
+ * Limit of the unwrapping line expansion (overscan) at the top and bottom
+ * of the actual viewport in ASCII characters.
+ * A limit of 2000 should match most sane urls.
+ */
+const OVERSCAN_CHAR_LIMIT = 2000;
+
+/**
+ * The Linkifier applies links to rows shortly after they have been refreshed.
+ */
+export class Linkifier implements ILinkifier {
+ /**
+ * The time to wait after a row is changed before it is linkified. This prevents
+ * the costly operation of searching every row multiple times, potentially a
+ * huge amount of times.
+ */
+ protected static _timeBeforeLatency = 200;
+
+ protected _linkMatchers: IRegisteredLinkMatcher[] = [];
+
+ private _mouseZoneManager: IMouseZoneManager | undefined;
+ private _element: HTMLElement | undefined;
+
+ private _rowsTimeoutId: number | undefined;
+ private _nextLinkMatcherId = 0;
+ private _rowsToLinkify: { start: number | undefined, end: number | undefined };
+
+ private _onShowLinkUnderline = new EventEmitter<ILinkifierEvent>();
+ public get onShowLinkUnderline(): IEvent<ILinkifierEvent> { return this._onShowLinkUnderline.event; }
+ private _onHideLinkUnderline = new EventEmitter<ILinkifierEvent>();
+ public get onHideLinkUnderline(): IEvent<ILinkifierEvent> { return this._onHideLinkUnderline.event; }
+ private _onLinkTooltip = new EventEmitter<ILinkifierEvent>();
+ public get onLinkTooltip(): IEvent<ILinkifierEvent> { return this._onLinkTooltip.event; }
+
+ constructor(
+ @IBufferService protected readonly _bufferService: IBufferService,
+ @ILogService private readonly _logService: ILogService,
+ @IUnicodeService private readonly _unicodeService: IUnicodeService
+ ) {
+ this._rowsToLinkify = {
+ start: undefined,
+ end: undefined
+ };
+ }
+
+ /**
+ * Attaches the linkifier to the DOM, enabling linkification.
+ * @param mouseZoneManager The mouse zone manager to register link zones with.
+ */
+ public attachToDom(element: HTMLElement, mouseZoneManager: IMouseZoneManager): void {
+ this._element = element;
+ this._mouseZoneManager = mouseZoneManager;
+ }
+
+ /**
+ * Queue linkification on a set of rows.
+ * @param start The row to linkify from (inclusive).
+ * @param end The row to linkify to (inclusive).
+ */
+ public linkifyRows(start: number, end: number): void {
+ // Don't attempt linkify if not yet attached to DOM
+ if (!this._mouseZoneManager) {
+ return;
+ }
+
+ // Increase range to linkify
+ if (this._rowsToLinkify.start === undefined || this._rowsToLinkify.end === undefined) {
+ this._rowsToLinkify.start = start;
+ this._rowsToLinkify.end = end;
+ } else {
+ this._rowsToLinkify.start = Math.min(this._rowsToLinkify.start, start);
+ this._rowsToLinkify.end = Math.max(this._rowsToLinkify.end, end);
+ }
+
+ // Clear out any existing links on this row range
+ this._mouseZoneManager.clearAll(start, end);
+
+ // Restart timer
+ if (this._rowsTimeoutId) {
+ clearTimeout(this._rowsTimeoutId);
+ }
+
+ // Cannot use window.setTimeout since tests need to run in node
+ this._rowsTimeoutId = setTimeout(() => this._linkifyRows(), Linkifier._timeBeforeLatency) as any as number;
+ }
+
+ /**
+ * Linkifies the rows requested.
+ */
+ private _linkifyRows(): void {
+ this._rowsTimeoutId = undefined;
+ const buffer = this._bufferService.buffer;
+
+ if (this._rowsToLinkify.start === undefined || this._rowsToLinkify.end === undefined) {
+ this._logService.debug('_rowToLinkify was unset before _linkifyRows was called');
+ return;
+ }
+
+ // Ensure the start row exists
+ const absoluteRowIndexStart = buffer.ydisp + this._rowsToLinkify.start;
+ if (absoluteRowIndexStart >= buffer.lines.length) {
+ return;
+ }
+
+ // Invalidate bad end row values (if a resize happened)
+ const absoluteRowIndexEnd = buffer.ydisp + Math.min(this._rowsToLinkify.end, this._bufferService.rows) + 1;
+
+ // Iterate over the range of unwrapped content strings within start..end
+ // (excluding).
+ // _doLinkifyRow gets full unwrapped lines with the start row as buffer offset
+ // for every matcher.
+ // The unwrapping is needed to also match content that got wrapped across
+ // several buffer lines. To avoid a worst case scenario where the whole buffer
+ // contains just a single unwrapped string we limit this line expansion beyond
+ // the viewport to +OVERSCAN_CHAR_LIMIT chars (overscan) at top and bottom.
+ // This comes with the tradeoff that matches longer than OVERSCAN_CHAR_LIMIT
+ // chars will not match anymore at the viewport borders.
+ const overscanLineLimit = Math.ceil(OVERSCAN_CHAR_LIMIT / this._bufferService.cols);
+ const iterator = this._bufferService.buffer.iterator(
+ false, absoluteRowIndexStart, absoluteRowIndexEnd, overscanLineLimit, overscanLineLimit);
+ while (iterator.hasNext()) {
+ const lineData: IBufferStringIteratorResult = iterator.next();
+ for (let i = 0; i < this._linkMatchers.length; i++) {
+ this._doLinkifyRow(lineData.range.first, lineData.content, this._linkMatchers[i]);
+ }
+ }
+
+ this._rowsToLinkify.start = undefined;
+ this._rowsToLinkify.end = undefined;
+ }
+
+ /**
+ * Registers a link matcher, allowing custom link patterns to be matched and
+ * handled.
+ * @param regex The regular expression to search for. Specifically, this
+ * searches the textContent of the rows. You will want to use \s to match a
+ * space ' ' character for example.
+ * @param handler The callback when the link is called.
+ * @param options Options for the link matcher.
+ * @return The ID of the new matcher, this can be used to deregister.
+ */
+ public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options: ILinkMatcherOptions = {}): number {
+ if (!handler) {
+ throw new Error('handler must be defined');
+ }
+ const matcher: IRegisteredLinkMatcher = {
+ id: this._nextLinkMatcherId++,
+ regex,
+ handler,
+ matchIndex: options.matchIndex,
+ validationCallback: options.validationCallback,
+ hoverTooltipCallback: options.tooltipCallback,
+ hoverLeaveCallback: options.leaveCallback,
+ willLinkActivate: options.willLinkActivate,
+ priority: options.priority || 0
+ };
+ this._addLinkMatcherToList(matcher);
+ return matcher.id;
+ }
+
+ /**
+ * Inserts a link matcher to the list in the correct position based on the
+ * priority of each link matcher. New link matchers of equal priority are
+ * considered after older link matchers.
+ * @param matcher The link matcher to be added.
+ */
+ private _addLinkMatcherToList(matcher: IRegisteredLinkMatcher): void {
+ if (this._linkMatchers.length === 0) {
+ this._linkMatchers.push(matcher);
+ return;
+ }
+
+ for (let i = this._linkMatchers.length - 1; i >= 0; i--) {
+ if (matcher.priority <= this._linkMatchers[i].priority) {
+ this._linkMatchers.splice(i + 1, 0, matcher);
+ return;
+ }
+ }
+
+ this._linkMatchers.splice(0, 0, matcher);
+ }
+
+ /**
+ * Deregisters a link matcher if it has been registered.
+ * @param matcherId The link matcher's ID (returned after register)
+ * @return Whether a link matcher was found and deregistered.
+ */
+ public deregisterLinkMatcher(matcherId: number): boolean {
+ for (let i = 0; i < this._linkMatchers.length; i++) {
+ if (this._linkMatchers[i].id === matcherId) {
+ this._linkMatchers.splice(i, 1);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Linkifies a row given a specific handler.
+ * @param rowIndex The row index to linkify (absolute index).
+ * @param text string content of the unwrapped row.
+ * @param matcher The link matcher for this line.
+ */
+ private _doLinkifyRow(rowIndex: number, text: string, matcher: ILinkMatcher): void {
+ // clone regex to do a global search on text
+ const rex = new RegExp(matcher.regex.source, (matcher.regex.flags || '') + 'g');
+ let match;
+ let stringIndex = -1;
+ while ((match = rex.exec(text)) !== null) {
+ const uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex];
+ if (!uri) {
+ // something matched but does not comply with the given matchIndex
+ // since this is most likely a bug the regex itself we simply do nothing here
+ this._logService.debug('match found without corresponding matchIndex', match, matcher);
+ break;
+ }
+
+ // Get index, match.index is for the outer match which includes negated chars
+ // therefore we cannot use match.index directly, instead we search the position
+ // of the match group in text again
+ // also correct regex and string search offsets for the next loop run
+ stringIndex = text.indexOf(uri, stringIndex + 1);
+ rex.lastIndex = stringIndex + uri.length;
+ if (stringIndex < 0) {
+ // invalid stringIndex (should not have happened)
+ break;
+ }
+
+ // get the buffer index as [absolute row, col] for the match
+ const bufferIndex = this._bufferService.buffer.stringIndexToBufferIndex(rowIndex, stringIndex);
+ if (bufferIndex[0] < 0) {
+ // invalid bufferIndex (should not have happened)
+ break;
+ }
+
+ const line = this._bufferService.buffer.lines.get(bufferIndex[0]);
+ if (!line) {
+ break;
+ }
+
+ const attr = line.getFg(bufferIndex[1]);
+ const fg = attr ? (attr >> 9) & 0x1ff : undefined;
+
+ if (matcher.validationCallback) {
+ matcher.validationCallback(uri, isValid => {
+ // Discard link if the line has already changed
+ if (this._rowsTimeoutId) {
+ return;
+ }
+ if (isValid) {
+ this._addLink(bufferIndex[1], bufferIndex[0] - this._bufferService.buffer.ydisp, uri, matcher, fg);
+ }
+ });
+ } else {
+ this._addLink(bufferIndex[1], bufferIndex[0] - this._bufferService.buffer.ydisp, uri, matcher, fg);
+ }
+ }
+ }
+
+ /**
+ * Registers a link to the mouse zone manager.
+ * @param x The column the link starts.
+ * @param y The row the link is on.
+ * @param uri The URI of the link.
+ * @param matcher The link matcher for the link.
+ * @param fg The link color for hover event.
+ */
+ private _addLink(x: number, y: number, uri: string, matcher: ILinkMatcher, fg: number | undefined): void {
+ if (!this._mouseZoneManager || !this._element) {
+ return;
+ }
+ // FIXME: get cell length from buffer to avoid mismatch after Unicode version change
+ const width = this._unicodeService.getStringCellWidth(uri);
+ const x1 = x % this._bufferService.cols;
+ const y1 = y + Math.floor(x / this._bufferService.cols);
+ let x2 = (x1 + width) % this._bufferService.cols;
+ let y2 = y1 + Math.floor((x1 + width) / this._bufferService.cols);
+ if (x2 === 0) {
+ x2 = this._bufferService.cols;
+ y2--;
+ }
+
+ this._mouseZoneManager.add(new MouseZone(
+ x1 + 1,
+ y1 + 1,
+ x2 + 1,
+ y2 + 1,
+ e => {
+ if (matcher.handler) {
+ return matcher.handler(e, uri);
+ }
+ const newWindow = window.open();
+ if (newWindow) {
+ newWindow.opener = null;
+ newWindow.location.href = uri;
+ } else {
+ console.warn('Opening link blocked as opener could not be cleared');
+ }
+ },
+ () => {
+ this._onShowLinkUnderline.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg));
+ this._element!.classList.add('xterm-cursor-pointer');
+ },
+ e => {
+ this._onLinkTooltip.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg));
+ if (matcher.hoverTooltipCallback) {
+ // Note that IViewportRange use 1-based coordinates to align with escape sequences such
+ // as CUP which use 1,1 as the default for row/col
+ matcher.hoverTooltipCallback(e, uri, { start: { x: x1, y: y1 }, end: { x: x2, y: y2 } });
+ }
+ },
+ () => {
+ this._onHideLinkUnderline.fire(this._createLinkHoverEvent(x1, y1, x2, y2, fg));
+ this._element!.classList.remove('xterm-cursor-pointer');
+ if (matcher.hoverLeaveCallback) {
+ matcher.hoverLeaveCallback();
+ }
+ },
+ e => {
+ if (matcher.willLinkActivate) {
+ return matcher.willLinkActivate(e, uri);
+ }
+ return true;
+ }
+ ));
+ }
+
+ private _createLinkHoverEvent(x1: number, y1: number, x2: number, y2: number, fg: number | undefined): ILinkifierEvent {
+ return { x1, y1, x2, y2, cols: this._bufferService.cols, fg };
+ }
+}
+
+export class MouseZone implements IMouseZone {
+ constructor(
+ public x1: number,
+ public y1: number,
+ public x2: number,
+ public y2: number,
+ public clickCallback: (e: MouseEvent) => any,
+ public hoverCallback: (e: MouseEvent) => any,
+ public tooltipCallback: (e: MouseEvent) => any,
+ public leaveCallback: () => void,
+ public willLinkActivate: (e: MouseEvent) => boolean
+ ) {
+ }
+}
diff --git a/node_modules/xterm/src/browser/Linkifier2.ts b/node_modules/xterm/src/browser/Linkifier2.ts
new file mode 100644
index 0000000..8954293
--- /dev/null
+++ b/node_modules/xterm/src/browser/Linkifier2.ts
@@ -0,0 +1,392 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { ILinkifier2, ILinkProvider, IBufferCellPosition, ILink, ILinkifierEvent, ILinkDecorations, ILinkWithState } from 'browser/Types';
+import { IDisposable } from 'common/Types';
+import { IMouseService, IRenderService } from './services/Services';
+import { IBufferService } from 'common/services/Services';
+import { EventEmitter, IEvent } from 'common/EventEmitter';
+import { Disposable, getDisposeArrayDisposable, disposeArray } from 'common/Lifecycle';
+import { addDisposableDomListener } from 'browser/Lifecycle';
+
+export class Linkifier2 extends Disposable implements ILinkifier2 {
+ private _element: HTMLElement | undefined;
+ private _mouseService: IMouseService | undefined;
+ private _renderService: IRenderService | undefined;
+ private _linkProviders: ILinkProvider[] = [];
+ public get currentLink(): ILinkWithState | undefined { return this._currentLink; }
+ protected _currentLink: ILinkWithState | undefined;
+ private _lastMouseEvent: MouseEvent | undefined;
+ private _linkCacheDisposables: IDisposable[] = [];
+ private _lastBufferCell: IBufferCellPosition | undefined;
+ private _isMouseOut: boolean = true;
+ private _activeProviderReplies: Map<Number, ILinkWithState[] | undefined> | undefined;
+ private _activeLine: number = -1;
+
+ private _onShowLinkUnderline = this.register(new EventEmitter<ILinkifierEvent>());
+ public get onShowLinkUnderline(): IEvent<ILinkifierEvent> { return this._onShowLinkUnderline.event; }
+ private _onHideLinkUnderline = this.register(new EventEmitter<ILinkifierEvent>());
+ public get onHideLinkUnderline(): IEvent<ILinkifierEvent> { return this._onHideLinkUnderline.event; }
+
+ constructor(
+ @IBufferService private readonly _bufferService: IBufferService
+ ) {
+ super();
+ this.register(getDisposeArrayDisposable(this._linkCacheDisposables));
+ }
+
+ public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
+ this._linkProviders.push(linkProvider);
+ return {
+ dispose: () => {
+ // Remove the link provider from the list
+ const providerIndex = this._linkProviders.indexOf(linkProvider);
+
+ if (providerIndex !== -1) {
+ this._linkProviders.splice(providerIndex, 1);
+ }
+ }
+ };
+ }
+
+ public attachToDom(element: HTMLElement, mouseService: IMouseService, renderService: IRenderService): void {
+ this._element = element;
+ this._mouseService = mouseService;
+ this._renderService = renderService;
+
+ this.register(addDisposableDomListener(this._element, 'mouseleave', () => {
+ this._isMouseOut = true;
+ this._clearCurrentLink();
+ }));
+ this.register(addDisposableDomListener(this._element, 'mousemove', this._onMouseMove.bind(this)));
+ this.register(addDisposableDomListener(this._element, 'click', this._onClick.bind(this)));
+ }
+
+ private _onMouseMove(event: MouseEvent): void {
+ this._lastMouseEvent = event;
+
+ if (!this._element || !this._mouseService) {
+ return;
+ }
+
+ const position = this._positionFromMouseEvent(event, this._element, this._mouseService);
+ if (!position) {
+ return;
+ }
+ this._isMouseOut = false;
+
+ // Ignore the event if it's an embedder created hover widget
+ const composedPath = event.composedPath() as HTMLElement[];
+ for (let i = 0; i < composedPath.length; i++) {
+ const target = composedPath[i];
+ // Hit Terminal.element, break and continue
+ if (target.classList.contains('xterm')) {
+ break;
+ }
+ // It's a hover, don't respect hover event
+ if (target.classList.contains('xterm-hover')) {
+ return;
+ }
+ }
+
+ if (!this._lastBufferCell || (position.x !== this._lastBufferCell.x || position.y !== this._lastBufferCell.y)) {
+ this._onHover(position);
+ this._lastBufferCell = position;
+ }
+ }
+
+ private _onHover(position: IBufferCellPosition): void {
+ // TODO: This currently does not cache link provider results across wrapped lines, activeLine should be something like `activeRange: {startY, endY}`
+ // Check if we need to clear the link
+ if (this._activeLine !== position.y) {
+ this._clearCurrentLink();
+ this._askForLink(position, false);
+ return;
+ }
+
+ // Check the if the link is in the mouse position
+ const isCurrentLinkInPosition = this._currentLink && this._linkAtPosition(this._currentLink.link, position);
+ if (!isCurrentLinkInPosition) {
+ this._clearCurrentLink();
+ this._askForLink(position, true);
+ }
+ }
+
+ private _askForLink(position: IBufferCellPosition, useLineCache: boolean): void {
+ if (!this._activeProviderReplies || !useLineCache) {
+ this._activeProviderReplies?.forEach(reply => {
+ reply?.forEach(linkWithState => {
+ if (linkWithState.link.dispose) {
+ linkWithState.link.dispose();
+ }
+ });
+ });
+ this._activeProviderReplies = new Map();
+ this._activeLine = position.y;
+ }
+ let linkProvided = false;
+
+ // There is no link cached, so ask for one
+ this._linkProviders.forEach((linkProvider, i) => {
+ if (useLineCache) {
+ const existingReply = this._activeProviderReplies?.get(i);
+ // If there isn't a reply, the provider hasn't responded yet.
+
+ // TODO: If there isn't a reply yet it means that the provider is still resolving. Ensuring
+ // provideLinks isn't triggered again saves ILink.hover firing twice though. This probably
+ // needs promises to get fixed
+ if (existingReply) {
+ linkProvided = this._checkLinkProviderResult(i, position, linkProvided);
+ }
+ } else {
+ linkProvider.provideLinks(position.y, (links: ILink[] | undefined) => {
+ if (this._isMouseOut) {
+ return;
+ }
+ const linksWithState: ILinkWithState[] | undefined = links?.map(link => ({ link }));
+ this._activeProviderReplies?.set(i, linksWithState);
+ linkProvided = this._checkLinkProviderResult(i, position, linkProvided);
+
+ // If all providers have responded, remove lower priority links that intersect ranges of
+ // higher priority links
+ if (this._activeProviderReplies?.size === this._linkProviders.length) {
+ this._removeIntersectingLinks(position.y, this._activeProviderReplies);
+ }
+ });
+ }
+ });
+ }
+
+ private _removeIntersectingLinks(y: number, replies: Map<Number, ILinkWithState[] | undefined>): void {
+ const occupiedCells = new Set<number>();
+ for (let i = 0; i < replies.size; i++) {
+ const providerReply = replies.get(i);
+ if (!providerReply) {
+ continue;
+ }
+ for (let i = 0; i < providerReply.length; i++) {
+ const linkWithState = providerReply[i];
+ const startX = linkWithState.link.range.start.y < y ? 0 : linkWithState.link.range.start.x;
+ const endX = linkWithState.link.range.end.y > y ? this._bufferService.cols : linkWithState.link.range.end.x;
+ for (let x = startX; x <= endX; x++) {
+ if (occupiedCells.has(x)) {
+ providerReply.splice(i--, 1);
+ break;
+ }
+ occupiedCells.add(x);
+ }
+ }
+ }
+ }
+
+ private _checkLinkProviderResult(index: number, position: IBufferCellPosition, linkProvided: boolean): boolean {
+ if (!this._activeProviderReplies) {
+ return linkProvided;
+ }
+
+ const links = this._activeProviderReplies.get(index);
+
+ // Check if every provider before this one has come back undefined
+ let hasLinkBefore = false;
+ for (let j = 0; j < index; j++) {
+ if (!this._activeProviderReplies.has(j) || this._activeProviderReplies.get(j)) {
+ hasLinkBefore = true;
+ }
+ }
+
+ // If all providers with higher priority came back undefined, then this provider's link for
+ // the position should be used
+ if (!hasLinkBefore && links) {
+ const linkAtPosition = links.find(link => this._linkAtPosition(link.link, position));
+ if (linkAtPosition) {
+ linkProvided = true;
+ this._handleNewLink(linkAtPosition);
+ }
+ }
+
+ // Check if all the providers have responded
+ if (this._activeProviderReplies.size === this._linkProviders.length && !linkProvided) {
+ // Respect the order of the link providers
+ for (let j = 0; j < this._activeProviderReplies.size; j++) {
+ const currentLink = this._activeProviderReplies.get(j)?.find(link => this._linkAtPosition(link.link, position));
+ if (currentLink) {
+ linkProvided = true;
+ this._handleNewLink(currentLink);
+ break;
+ }
+ }
+ }
+
+ return linkProvided;
+ }
+
+ private _onClick(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)) {
+ this._currentLink.link.activate(event, this._currentLink.link.text);
+ }
+ }
+
+ private _clearCurrentLink(startRow?: number, endRow?: number): void {
+ if (!this._element || !this._currentLink || !this._lastMouseEvent) {
+ return;
+ }
+
+ // If we have a start and end row, check that the link is within it
+ if (!startRow || !endRow || (this._currentLink.link.range.start.y >= startRow && this._currentLink.link.range.end.y <= endRow)) {
+ this._linkLeave(this._element, this._currentLink.link, this._lastMouseEvent);
+ this._currentLink = undefined;
+ disposeArray(this._linkCacheDisposables);
+ }
+ }
+
+ private _handleNewLink(linkWithState: ILinkWithState): void {
+ if (!this._element || !this._lastMouseEvent || !this._mouseService) {
+ return;
+ }
+
+ const position = this._positionFromMouseEvent(this._lastMouseEvent, this._element, this._mouseService);
+
+ if (!position) {
+ return;
+ }
+
+ // Trigger hover if the we have a link at the position
+ if (this._linkAtPosition(linkWithState.link, position)) {
+ this._currentLink = linkWithState;
+ this._currentLink.state = {
+ decorations: {
+ underline: linkWithState.link.decorations === undefined ? true : linkWithState.link.decorations.underline,
+ pointerCursor: linkWithState.link.decorations === undefined ? true : linkWithState.link.decorations.pointerCursor
+ },
+ isHovered: true
+ };
+ this._linkHover(this._element, linkWithState.link, this._lastMouseEvent);
+
+ // Add listener for tracking decorations changes
+ linkWithState.link.decorations = {} as ILinkDecorations;
+ Object.defineProperties(linkWithState.link.decorations, {
+ pointerCursor: {
+ get: () => this._currentLink?.state?.decorations.pointerCursor,
+ set: v => {
+ if (this._currentLink?.state && this._currentLink.state.decorations.pointerCursor !== v) {
+ this._currentLink.state.decorations.pointerCursor = v;
+ if (this._currentLink.state.isHovered) {
+ this._element?.classList.toggle('xterm-cursor-pointer', v);
+ }
+ }
+ }
+ },
+ underline: {
+ get: () => this._currentLink?.state?.decorations.underline,
+ set: v => {
+ if (this._currentLink?.state && this._currentLink?.state?.decorations.underline !== v) {
+ this._currentLink.state.decorations.underline = v;
+ if (this._currentLink.state.isHovered) {
+ this._fireUnderlineEvent(linkWithState.link, v);
+ }
+ }
+ }
+ }
+ });
+
+ // Add listener for rerendering
+ if (this._renderService) {
+ this._linkCacheDisposables.push(this._renderService.onRenderedBufferChange(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;
+ this._clearCurrentLink(start, e.end + 1 + this._bufferService.buffer.ydisp);
+ }));
+ }
+ }
+ }
+
+ protected _linkHover(element: HTMLElement, link: ILink, event: MouseEvent): void {
+ if (this._currentLink?.state) {
+ this._currentLink.state.isHovered = true;
+ if (this._currentLink.state.decorations.underline) {
+ this._fireUnderlineEvent(link, true);
+ }
+ if (this._currentLink.state.decorations.pointerCursor) {
+ element.classList.add('xterm-cursor-pointer');
+ }
+ }
+
+ if (link.hover) {
+ link.hover(event, link.text);
+ }
+ }
+
+ private _fireUnderlineEvent(link: ILink, showEvent: boolean): void {
+ const range = link.range;
+ const scrollOffset = this._bufferService.buffer.ydisp;
+ const event = this._createLinkUnderlineEvent(range.start.x - 1, range.start.y - scrollOffset - 1, range.end.x, range.end.y - scrollOffset - 1, undefined);
+ const emitter = showEvent ? this._onShowLinkUnderline : this._onHideLinkUnderline;
+ emitter.fire(event);
+ }
+
+ protected _linkLeave(element: HTMLElement, link: ILink, event: MouseEvent): void {
+ if (this._currentLink?.state) {
+ this._currentLink.state.isHovered = false;
+ if (this._currentLink.state.decorations.underline) {
+ this._fireUnderlineEvent(link, false);
+ }
+ if (this._currentLink.state.decorations.pointerCursor) {
+ element.classList.remove('xterm-cursor-pointer');
+ }
+ }
+
+ if (link.leave) {
+ link.leave(event, link.text);
+ }
+ }
+
+ /**
+ * Check if the buffer position is within the link
+ * @param link
+ * @param position
+ */
+ private _linkAtPosition(link: ILink, position: IBufferCellPosition): boolean {
+ const sameLine = link.range.start.y === link.range.end.y;
+ const wrappedFromLeft = link.range.start.y < position.y;
+ const wrappedToRight = link.range.end.y > position.y;
+
+ // If the start and end have the same y, then the position must be between start and end x
+ // If not, then handle each case seperately, depending on which way it wraps
+ return ((sameLine && link.range.start.x <= position.x && link.range.end.x >= position.x) ||
+ (wrappedFromLeft && link.range.end.x >= position.x) ||
+ (wrappedToRight && link.range.start.x <= position.x) ||
+ (wrappedFromLeft && wrappedToRight)) &&
+ link.range.start.y <= position.y &&
+ link.range.end.y >= position.y;
+ }
+
+ /**
+ * Get the buffer position from a mouse event
+ * @param event
+ */
+ private _positionFromMouseEvent(event: MouseEvent, element: HTMLElement, mouseService: IMouseService): IBufferCellPosition | undefined {
+ const coords = mouseService.getCoords(event, element, this._bufferService.cols, this._bufferService.rows);
+ if (!coords) {
+ return;
+ }
+
+ return { x: coords[0], y: coords[1] + this._bufferService.buffer.ydisp };
+ }
+
+ private _createLinkUnderlineEvent(x1: number, y1: number, x2: number, y2: number, fg: number | undefined): ILinkifierEvent {
+ return { x1, y1, x2, y2, cols: this._bufferService.cols, fg };
+ }
+}
diff --git a/node_modules/xterm/src/browser/LocalizableStrings.ts b/node_modules/xterm/src/browser/LocalizableStrings.ts
new file mode 100644
index 0000000..c0a904c
--- /dev/null
+++ b/node_modules/xterm/src/browser/LocalizableStrings.ts
@@ -0,0 +1,10 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+// eslint-disable-next-line prefer-const
+export let promptLabel = 'Terminal input';
+
+// eslint-disable-next-line prefer-const
+export let tooMuchOutput = 'Too much output to announce, navigate to rows manually to read';
diff --git a/node_modules/xterm/src/browser/MouseZoneManager.ts b/node_modules/xterm/src/browser/MouseZoneManager.ts
new file mode 100644
index 0000000..71ffe7c
--- /dev/null
+++ b/node_modules/xterm/src/browser/MouseZoneManager.ts
@@ -0,0 +1,236 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { Disposable } from 'common/Lifecycle';
+import { addDisposableDomListener } from 'browser/Lifecycle';
+import { IMouseService, ISelectionService } from 'browser/services/Services';
+import { IMouseZoneManager, IMouseZone } from 'browser/Types';
+import { IBufferService, IOptionsService } from 'common/services/Services';
+
+/**
+ * The MouseZoneManager allows components to register zones within the terminal
+ * that trigger hover and click callbacks.
+ *
+ * This class was intentionally made not so robust initially as the only case it
+ * needed to support was single-line links which never overlap. Improvements can
+ * be made in the future.
+ */
+export class MouseZoneManager extends Disposable implements IMouseZoneManager {
+ private _zones: IMouseZone[] = [];
+
+ private _areZonesActive: boolean = false;
+ private _mouseMoveListener: (e: MouseEvent) => any;
+ private _mouseLeaveListener: (e: MouseEvent) => any;
+ private _clickListener: (e: MouseEvent) => any;
+
+ private _tooltipTimeout: number | undefined;
+ private _currentZone: IMouseZone | undefined;
+ private _lastHoverCoords: [number | undefined, number | undefined] = [undefined, undefined];
+ private _initialSelectionLength: number = 0;
+
+ constructor(
+ private readonly _element: HTMLElement,
+ private readonly _screenElement: HTMLElement,
+ @IBufferService private readonly _bufferService: IBufferService,
+ @IMouseService private readonly _mouseService: IMouseService,
+ @ISelectionService private readonly _selectionService: ISelectionService,
+ @IOptionsService private readonly _optionsService: IOptionsService
+ ) {
+ super();
+
+ this.register(addDisposableDomListener(this._element, 'mousedown', e => this._onMouseDown(e)));
+
+ // These events are expensive, only listen to it when mouse zones are active
+ this._mouseMoveListener = e => this._onMouseMove(e);
+ this._mouseLeaveListener = e => this._onMouseLeave(e);
+ this._clickListener = e => this._onClick(e);
+ }
+
+ public dispose(): void {
+ super.dispose();
+ this._deactivate();
+ }
+
+ public add(zone: IMouseZone): void {
+ this._zones.push(zone);
+ if (this._zones.length === 1) {
+ this._activate();
+ }
+ }
+
+ public clearAll(start?: number, end?: number): void {
+ // Exit if there's nothing to clear
+ if (this._zones.length === 0) {
+ return;
+ }
+
+ // Clear all if start/end weren't set
+ if (!start || !end) {
+ start = 0;
+ end = this._bufferService.rows - 1;
+ }
+
+ // Iterate through zones and clear them out if they're within the range
+ for (let i = 0; i < this._zones.length; i++) {
+ const zone = this._zones[i];
+ if ((zone.y1 > start && zone.y1 <= end + 1) ||
+ (zone.y2 > start && zone.y2 <= end + 1) ||
+ (zone.y1 < start && zone.y2 > end + 1)) {
+ if (this._currentZone && this._currentZone === zone) {
+ this._currentZone.leaveCallback();
+ this._currentZone = undefined;
+ }
+ this._zones.splice(i--, 1);
+ }
+ }
+
+ // Deactivate the mouse zone manager if all the zones have been removed
+ if (this._zones.length === 0) {
+ this._deactivate();
+ }
+ }
+
+ private _activate(): void {
+ if (!this._areZonesActive) {
+ this._areZonesActive = true;
+ this._element.addEventListener('mousemove', this._mouseMoveListener);
+ this._element.addEventListener('mouseleave', this._mouseLeaveListener);
+ this._element.addEventListener('click', this._clickListener);
+ }
+ }
+
+ private _deactivate(): void {
+ if (this._areZonesActive) {
+ this._areZonesActive = false;
+ this._element.removeEventListener('mousemove', this._mouseMoveListener);
+ this._element.removeEventListener('mouseleave', this._mouseLeaveListener);
+ this._element.removeEventListener('click', this._clickListener);
+ }
+ }
+
+ private _onMouseMove(e: MouseEvent): void {
+ // TODO: Ideally this would only clear the hover state when the mouse moves
+ // outside of the mouse zone
+ if (this._lastHoverCoords[0] !== e.pageX || this._lastHoverCoords[1] !== e.pageY) {
+ this._onHover(e);
+ // Record the current coordinates
+ this._lastHoverCoords = [e.pageX, e.pageY];
+ }
+ }
+
+ private _onHover(e: MouseEvent): void {
+ const zone = this._findZoneEventAt(e);
+
+ // Do nothing if the zone is the same
+ if (zone === this._currentZone) {
+ return;
+ }
+
+ // Fire the hover end callback and cancel any existing timer if a new zone
+ // is being hovered
+ if (this._currentZone) {
+ this._currentZone.leaveCallback();
+ this._currentZone = undefined;
+ if (this._tooltipTimeout) {
+ clearTimeout(this._tooltipTimeout);
+ }
+ }
+
+ // Exit if there is not zone
+ if (!zone) {
+ return;
+ }
+ this._currentZone = zone;
+
+ // Trigger the hover callback
+ if (zone.hoverCallback) {
+ zone.hoverCallback(e);
+ }
+
+ // Restart the tooltip timeout
+ this._tooltipTimeout = window.setTimeout(() => this._onTooltip(e), this._optionsService.rawOptions.linkTooltipHoverDuration);
+ }
+
+ private _onTooltip(e: MouseEvent): void {
+ this._tooltipTimeout = undefined;
+ const zone = this._findZoneEventAt(e);
+ zone?.tooltipCallback(e);
+ }
+
+ private _onMouseDown(e: MouseEvent): void {
+ // Store current terminal selection length, to check if we're performing
+ // a selection operation
+ this._initialSelectionLength = this._getSelectionLength();
+
+ // Ignore the event if there are no zones active
+ if (!this._areZonesActive) {
+ return;
+ }
+
+ // Find the active zone, prevent event propagation if found to prevent other
+ // components from handling the mouse event.
+ const zone = this._findZoneEventAt(e);
+ if (zone?.willLinkActivate(e)) {
+ e.preventDefault();
+ e.stopImmediatePropagation();
+ }
+ }
+
+ private _onMouseLeave(e: MouseEvent): void {
+ // Fire the hover end callback and cancel any existing timer if the mouse
+ // leaves the terminal element
+ if (this._currentZone) {
+ this._currentZone.leaveCallback();
+ this._currentZone = undefined;
+ if (this._tooltipTimeout) {
+ clearTimeout(this._tooltipTimeout);
+ }
+ }
+ }
+
+ private _onClick(e: MouseEvent): void {
+ // Find the active zone and click it if found and no selection was
+ // being performed
+ const zone = this._findZoneEventAt(e);
+ const currentSelectionLength = this._getSelectionLength();
+
+ if (zone && currentSelectionLength === this._initialSelectionLength) {
+ zone.clickCallback(e);
+ e.preventDefault();
+ e.stopImmediatePropagation();
+ }
+ }
+
+ private _getSelectionLength(): number {
+ const selectionText = this._selectionService.selectionText;
+ return selectionText ? selectionText.length : 0;
+ }
+
+ private _findZoneEventAt(e: MouseEvent): IMouseZone | undefined {
+ const coords = this._mouseService.getCoords(e, this._screenElement, this._bufferService.cols, this._bufferService.rows);
+ if (!coords) {
+ return undefined;
+ }
+ const x = coords[0];
+ const y = coords[1];
+ for (let i = 0; i < this._zones.length; i++) {
+ const zone = this._zones[i];
+ if (zone.y1 === zone.y2) {
+ // Single line link
+ if (y === zone.y1 && x >= zone.x1 && x < zone.x2) {
+ return zone;
+ }
+ } else {
+ // Multi-line link
+ if ((y === zone.y1 && x >= zone.x1) ||
+ (y === zone.y2 && x < zone.x2) ||
+ (y > zone.y1 && y < zone.y2)) {
+ return zone;
+ }
+ }
+ }
+ return undefined;
+ }
+}
diff --git a/node_modules/xterm/src/browser/RenderDebouncer.ts b/node_modules/xterm/src/browser/RenderDebouncer.ts
new file mode 100644
index 0000000..0252107
--- /dev/null
+++ b/node_modules/xterm/src/browser/RenderDebouncer.ts
@@ -0,0 +1,63 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IRenderDebouncer } from 'browser/Types';
+
+/**
+ * Debounces calls to render terminal rows using animation frames.
+ */
+export class RenderDebouncer implements IRenderDebouncer {
+ private _rowStart: number | undefined;
+ private _rowEnd: number | undefined;
+ private _rowCount: number | undefined;
+ private _animationFrame: number | undefined;
+
+ constructor(
+ private _renderCallback: (start: number, end: number) => void
+ ) {
+ }
+
+ public dispose(): void {
+ if (this._animationFrame) {
+ window.cancelAnimationFrame(this._animationFrame);
+ this._animationFrame = undefined;
+ }
+ }
+
+ 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
+ rowStart = rowStart !== undefined ? rowStart : 0;
+ rowEnd = rowEnd !== undefined ? rowEnd : this._rowCount - 1;
+ // Set the properties to the updated values
+ this._rowStart = this._rowStart !== undefined ? Math.min(this._rowStart, rowStart) : rowStart;
+ this._rowEnd = this._rowEnd !== undefined ? Math.max(this._rowEnd, rowEnd) : rowEnd;
+
+ if (this._animationFrame) {
+ return;
+ }
+
+ this._animationFrame = window.requestAnimationFrame(() => this._innerRefresh());
+ }
+
+ private _innerRefresh(): void {
+ // Make sure values are set
+ if (this._rowStart === undefined || this._rowEnd === undefined || this._rowCount === undefined) {
+ return;
+ }
+
+ // Clamp values
+ const start = Math.max(this._rowStart, 0);
+ const end = Math.min(this._rowEnd, this._rowCount - 1);
+
+ // 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);
+ }
+}
diff --git a/node_modules/xterm/src/browser/ScreenDprMonitor.ts b/node_modules/xterm/src/browser/ScreenDprMonitor.ts
new file mode 100644
index 0000000..27ae231
--- /dev/null
+++ b/node_modules/xterm/src/browser/ScreenDprMonitor.ts
@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { Disposable } from 'common/Lifecycle';
+
+export type ScreenDprListener = (newDevicePixelRatio?: number, oldDevicePixelRatio?: number) => void;
+
+/**
+ * The screen device pixel ratio monitor allows listening for when the
+ * window.devicePixelRatio value changes. This is done not with polling but with
+ * the use of window.matchMedia to watch media queries. When the event fires,
+ * the listener will be reattached using a different media query to ensure that
+ * any further changes will register.
+ *
+ * The listener should fire on both window zoom changes and switching to a
+ * monitor with a different DPI.
+ */
+export class ScreenDprMonitor extends Disposable {
+ private _currentDevicePixelRatio: number = window.devicePixelRatio;
+ private _outerListener: ((this: MediaQueryList, ev: MediaQueryListEvent) => any) | undefined;
+ private _listener: ScreenDprListener | undefined;
+ private _resolutionMediaMatchList: MediaQueryList | undefined;
+
+ public setListener(listener: ScreenDprListener): void {
+ if (this._listener) {
+ this.clearListener();
+ }
+ this._listener = listener;
+ this._outerListener = () => {
+ if (!this._listener) {
+ return;
+ }
+ this._listener(window.devicePixelRatio, this._currentDevicePixelRatio);
+ this._updateDpr();
+ };
+ this._updateDpr();
+ }
+
+ public dispose(): void {
+ super.dispose();
+ this.clearListener();
+ }
+
+ private _updateDpr(): void {
+ if (!this._outerListener) {
+ return;
+ }
+
+ // Clear listeners for old DPR
+ this._resolutionMediaMatchList?.removeListener(this._outerListener);
+
+ // Add listeners for new DPR
+ this._currentDevicePixelRatio = window.devicePixelRatio;
+ this._resolutionMediaMatchList = window.matchMedia(`screen and (resolution: ${window.devicePixelRatio}dppx)`);
+ this._resolutionMediaMatchList.addListener(this._outerListener);
+ }
+
+ public clearListener(): void {
+ if (!this._resolutionMediaMatchList || !this._listener || !this._outerListener) {
+ return;
+ }
+ this._resolutionMediaMatchList.removeListener(this._outerListener);
+ this._resolutionMediaMatchList = undefined;
+ this._listener = undefined;
+ this._outerListener = undefined;
+ }
+}
diff --git a/node_modules/xterm/src/browser/Terminal.ts b/node_modules/xterm/src/browser/Terminal.ts
new file mode 100644
index 0000000..fe5c7f7
--- /dev/null
+++ b/node_modules/xterm/src/browser/Terminal.ts
@@ -0,0 +1,1399 @@
+/**
+ * Copyright (c) 2014 The xterm.js authors. All rights reserved.
+ * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
+ * @license MIT
+ *
+ * Originally forked from (with the author's permission):
+ * Fabrice Bellard's javascript vt100 for jslinux:
+ * http://bellard.org/jslinux/
+ * Copyright (c) 2011 Fabrice Bellard
+ * The original design remains. The terminal itself
+ * has been extended to include xterm CSI codes, among
+ * other features.
+ *
+ * Terminal Emulation References:
+ * http://vt100.net/
+ * http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt
+ * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
+ * http://invisible-island.net/vttest/
+ * http://www.inwap.com/pdp10/ansicode.txt
+ * http://linux.die.net/man/4/console_codes
+ * http://linux.die.net/man/7/urxvt
+ */
+
+import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler, ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport, ILinkifier2, CharacterJoinerHandler } from 'browser/Types';
+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 { WindowsOptionsReportType } from '../common/InputHandler';
+import { Renderer } from 'browser/renderer/Renderer';
+import { Linkifier } from 'browser/Linkifier';
+import { SelectionService } from 'browser/services/SelectionService';
+import * as Browser from 'common/Platform';
+import { addDisposableDomListener } from 'browser/Lifecycle';
+import * as Strings from 'browser/LocalizableStrings';
+import { SoundService } from 'browser/services/SoundService';
+import { MouseZoneManager } from 'browser/MouseZoneManager';
+import { AccessibilityManager } from './AccessibilityManager';
+import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider } from 'xterm';
+import { DomRenderer } from 'browser/renderer/dom/DomRenderer';
+import { KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IColorEvent, ColorIndex, ColorRequestType } from 'common/Types';
+import { evaluateKeyboardEvent } from 'common/input/Keyboard';
+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 } 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 { CharacterJoinerService } from 'browser/services/CharacterJoinerService';
+import { toRgbString } from 'common/input/XParseColor';
+
+// Let it work inside Node.js for automated testing purposes.
+const document: Document = (typeof window !== 'undefined') ? window.document : null as any;
+
+export class Terminal extends CoreTerminal implements ITerminal {
+ public textarea: HTMLTextAreaElement | undefined;
+ public element: HTMLElement | undefined;
+ public screenElement: HTMLElement | undefined;
+
+ private _document: Document | undefined;
+ private _viewportScrollArea: HTMLElement | undefined;
+ private _viewportElement: HTMLElement | undefined;
+ private _helperContainer: HTMLElement | undefined;
+ private _compositionView: HTMLElement | undefined;
+
+ // private _visualBellTimer: number;
+
+ public browser: IBrowser = Browser as any;
+
+ private _customKeyEventHandler: CustomKeyEventHandler | undefined;
+
+ // browser services
+ private _charSizeService: ICharSizeService | undefined;
+ private _mouseService: IMouseService | undefined;
+ private _renderService: IRenderService | undefined;
+ private _characterJoinerService: ICharacterJoinerService | undefined;
+ private _selectionService: ISelectionService | undefined;
+ private _soundService: ISoundService | undefined;
+
+ /**
+ * Records whether the keydown event has already been handled and triggered a data event, if so
+ * the keypress event should not trigger a data event but should still print to the textarea so
+ * screen readers will announce it.
+ */
+ private _keyDownHandled: 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
+ * screen readers will announce it.
+ */
+ private _keyPressHandled: boolean = false;
+
+ /**
+ * Records whether there has been a keydown event for a dead key without a corresponding keydown
+ * event for the composed/alternative character. If we cancel the keydown event for the dead key,
+ * no events will be emitted for the final character.
+ */
+ private _unprocessedDeadKey: boolean = false;
+
+ public linkifier: ILinkifier;
+ public linkifier2: ILinkifier2;
+ public viewport: IViewport | undefined;
+ private _compositionHelper: ICompositionHelper | undefined;
+ private _mouseZoneManager: IMouseZoneManager | undefined;
+ private _accessibilityManager: AccessibilityManager | undefined;
+ private _colorManager: ColorManager | undefined;
+ private _theme: ITheme | undefined;
+
+ private _onCursorMove = new EventEmitter<void>();
+ public get onCursorMove(): IEvent<void> { return this._onCursorMove.event; }
+ private _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>();
+ public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._onKey.event; }
+ private _onRender = new EventEmitter<{ start: number, end: number }>();
+ public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; }
+ private _onSelectionChange = new EventEmitter<void>();
+ public get onSelectionChange(): IEvent<void> { return this._onSelectionChange.event; }
+ private _onTitleChange = new EventEmitter<string>();
+ public get onTitleChange(): IEvent<string> { return this._onTitleChange.event; }
+ private _onBell = new EventEmitter<void>();
+ public get onBell(): IEvent<void> { return this._onBell.event; }
+
+ private _onFocus = new EventEmitter<void>();
+ public get onFocus(): IEvent<void> { return this._onFocus.event; }
+ private _onBlur = new EventEmitter<void>();
+ public get onBlur(): IEvent<void> { return this._onBlur.event; }
+ private _onA11yCharEmitter = new EventEmitter<string>();
+ public get onA11yChar(): IEvent<string> { return this._onA11yCharEmitter.event; }
+ private _onA11yTabEmitter = new EventEmitter<number>();
+ public get onA11yTab(): IEvent<number> { return this._onA11yTabEmitter.event; }
+
+ /**
+ * Creates a new `Terminal` object.
+ *
+ * @param options An object containing a set of options, the available options are:
+ * - `cursorBlink` (boolean): Whether the terminal cursor blinks
+ * - `cols` (number): The number of columns of the terminal (horizontal size)
+ * - `rows` (number): The number of rows of the terminal (vertical size)
+ *
+ * @public
+ * @class Xterm Xterm
+ * @alias module:xterm/src/xterm
+ */
+ constructor(
+ options: Partial<ITerminalOptions> = {}
+ ) {
+ super(options);
+
+ this._setup();
+
+ this.linkifier = this._instantiationService.createInstance(Linkifier);
+ this.linkifier2 = this.register(this._instantiationService.createInstance(Linkifier2));
+
+ // Setup InputHandler listeners
+ this.register(this._inputHandler.onRequestBell(() => this.bell()));
+ this.register(this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end)));
+ this.register(this._inputHandler.onRequestSendFocus(() => this._reportFocus()));
+ this.register(this._inputHandler.onRequestReset(() => this.reset()));
+ this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type)));
+ this.register(this._inputHandler.onColor((event) => this._handleColorEvent(event)));
+ this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove));
+ this.register(forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange));
+ this.register(forwardEvent(this._inputHandler.onA11yChar, this._onA11yCharEmitter));
+ this.register(forwardEvent(this._inputHandler.onA11yTab, this._onA11yTabEmitter));
+
+ // Setup listeners
+ this.register(this._bufferService.onResize(e => this._afterResize(e.cols, e.rows)));
+ }
+
+ /**
+ * Handle color event from inputhandler for OSC 4|104 | 10|110 | 11|111 | 12|112.
+ * An event from OSC 4|104 may contain multiple set or report requests, and multiple
+ * or none restore requests (resetting all),
+ * while an event from OSC 10|110 | 11|111 | 12|112 always contains a single request.
+ */
+ private _handleColorEvent(event: IColorEvent): void {
+ if (!this._colorManager) return;
+ for (const req of event) {
+ let acc: 'foreground' | 'background' | 'cursor' | 'ansi' | undefined = undefined;
+ let ident = '';
+ switch (req.index) {
+ case ColorIndex.FOREGROUND: // OSC 10 | 110
+ acc = 'foreground';
+ ident = '10';
+ break;
+ case ColorIndex.BACKGROUND: // OSC 11 | 111
+ acc = 'background';
+ ident = '11';
+ break;
+ case ColorIndex.CURSOR: // OSC 12 | 112
+ acc = 'cursor';
+ ident = '12';
+ break;
+ default: // OSC 4 | 104
+ // we can skip the [0..255] range check here (already done in inputhandler)
+ acc = 'ansi';
+ ident = '4;' + req.index;
+ }
+ if (acc) {
+ switch (req.type) {
+ case ColorRequestType.REPORT:
+ 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}`);
+ break;
+ case ColorRequestType.SET:
+ if (acc === 'ansi') this._colorManager.colors.ansi[req.index] = rgba.toColor(...req.color);
+ else this._colorManager.colors[acc] = rgba.toColor(...req.color);
+ break;
+ case ColorRequestType.RESTORE:
+ this._colorManager.restoreColor(req.index);
+ break;
+ }
+ }
+ }
+ this._renderService?.setColors(this._colorManager.colors);
+ this.viewport?.onThemeChange(this._colorManager.colors);
+ }
+
+ public dispose(): void {
+ if (this._isDisposed) {
+ return;
+ }
+ super.dispose();
+ this._renderService?.dispose();
+ this._customKeyEventHandler = undefined;
+ this.write = () => { };
+ this.element?.parentNode?.removeChild(this.element);
+ }
+
+ protected _setup(): void {
+ super._setup();
+
+ this._customKeyEventHandler = undefined;
+ }
+
+ /**
+ * Convenience property to active buffer.
+ */
+ public get buffer(): IBuffer {
+ return this.buffers.active;
+ }
+
+ /**
+ * Focus the terminal. Delegates focus handling to the terminal's DOM element.
+ */
+ public focus(): void {
+ if (this.textarea) {
+ this.textarea.focus({ preventScroll: true });
+ }
+ }
+
+ protected _updateOptions(key: string): void {
+ super._updateOptions(key);
+
+ // TODO: These listeners should be owned by individual components
+ switch (key) {
+ case 'fontFamily':
+ case 'fontSize':
+ // When the font changes the size of the cells may change which requires a renderer clear
+ this._renderService?.clear();
+ this._charSizeService?.measure();
+ break;
+ case 'cursorBlink':
+ case 'cursorStyle':
+ // The DOM renderer needs a row refresh to update the cursor styles
+ this.refresh(this.buffer.y, this.buffer.y);
+ break;
+ case 'customGlyphs':
+ case 'drawBoldTextInBrightColors':
+ case 'letterSpacing':
+ case 'lineHeight':
+ case 'fontWeight':
+ case 'fontWeightBold':
+ case 'minimumContrastRatio':
+ // When the font changes the size of the cells may change which requires a renderer clear
+ if (this._renderService) {
+ this._renderService.clear();
+ this._renderService.onResize(this.cols, this.rows);
+ this.refresh(0, this.rows - 1);
+ }
+ break;
+ case 'rendererType':
+ if (this._renderService) {
+ this._renderService.setRenderer(this._createRenderer());
+ this._renderService.onResize(this.cols, this.rows);
+ }
+ break;
+ case 'scrollback':
+ this.viewport?.syncScrollArea();
+ break;
+ case 'screenReaderMode':
+ if (this.optionsService.rawOptions.screenReaderMode) {
+ if (!this._accessibilityManager && this._renderService) {
+ this._accessibilityManager = new AccessibilityManager(this, this._renderService);
+ }
+ } else {
+ this._accessibilityManager?.dispose();
+ this._accessibilityManager = undefined;
+ }
+ break;
+ case 'tabStopWidth': this.buffers.setupTabStops(); break;
+ case 'theme':
+ this._setTheme(this.optionsService.rawOptions.theme);
+ break;
+ }
+ }
+
+ /**
+ * Binds the desired focus behavior on a given terminal object.
+ */
+ private _onTextAreaFocus(ev: KeyboardEvent): void {
+ if (this.coreService.decPrivateModes.sendFocus) {
+ this.coreService.triggerDataEvent(C0.ESC + '[I');
+ }
+ this.updateCursorStyle(ev);
+ this.element!.classList.add('focus');
+ this._showCursor();
+ this._onFocus.fire();
+ }
+
+ /**
+ * Blur the terminal, calling the blur function on the terminal's underlying
+ * textarea.
+ */
+ public blur(): void {
+ return this.textarea?.blur();
+ }
+
+ /**
+ * Binds the desired blur behavior on a given terminal object.
+ */
+ private _onTextAreaBlur(): void {
+ // Text can safely be removed on blur. Doing it earlier could interfere with
+ // screen readers reading it out.
+ this.textarea!.value = '';
+ this.refresh(this.buffer.y, this.buffer.y);
+ if (this.coreService.decPrivateModes.sendFocus) {
+ this.coreService.triggerDataEvent(C0.ESC + '[O');
+ }
+ this.element!.classList.remove('focus');
+ this._onBlur.fire();
+ }
+
+ private _syncTextArea(): void {
+ if (!this.textarea || !this.buffer.isCursorInViewport || this._compositionHelper!.isComposing || !this._renderService) {
+ return;
+ }
+ const cursorY = this.buffer.ybase + this.buffer.y;
+ const bufferLine = this.buffer.lines.get(cursorY);
+ if (!bufferLine) {
+ return;
+ }
+ const cursorX = Math.min(this.buffer.x, this.cols - 1);
+ const cellHeight = this._renderService.dimensions.actualCellHeight;
+ const width = bufferLine.getWidth(cursorX);
+ const cellWidth = this._renderService.dimensions.actualCellWidth * width;
+ const cursorTop = this.buffer.y * this._renderService.dimensions.actualCellHeight;
+ const cursorLeft = cursorX * this._renderService.dimensions.actualCellWidth;
+
+ // Sync the textarea to the exact position of the composition view so the IME knows where the
+ // text is.
+ this.textarea.style.left = cursorLeft + 'px';
+ this.textarea.style.top = cursorTop + 'px';
+ this.textarea.style.width = cellWidth + 'px';
+ this.textarea.style.height = cellHeight + 'px';
+ this.textarea.style.lineHeight = cellHeight + 'px';
+ this.textarea.style.zIndex = '-5';
+ }
+
+ /**
+ * Initialize default behavior
+ */
+ private _initGlobal(): void {
+ this._bindKeys();
+
+ // Bind clipboard functionality
+ this.register(addDisposableDomListener(this.element!, 'copy', (event: ClipboardEvent) => {
+ // If mouse events are active it means the selection manager is disabled and
+ // copy should be handled by the host program.
+ if (!this.hasSelection()) {
+ return;
+ }
+ copyHandler(event, this._selectionService!);
+ }));
+ const pasteHandlerWrapper = (event: ClipboardEvent): void => handlePasteEvent(event, this.textarea!, this.coreService);
+ this.register(addDisposableDomListener(this.textarea!, 'paste', pasteHandlerWrapper));
+ this.register(addDisposableDomListener(this.element!, 'paste', pasteHandlerWrapper));
+
+ // Handle right click context menus
+ if (Browser.isFirefox) {
+ // Firefox doesn't appear to fire the contextmenu event on right click
+ this.register(addDisposableDomListener(this.element!, 'mousedown', (event: MouseEvent) => {
+ if (event.button === 2) {
+ rightClickHandler(event, this.textarea!, this.screenElement!, this._selectionService!, this.options.rightClickSelectsWord);
+ }
+ }));
+ } else {
+ this.register(addDisposableDomListener(this.element!, 'contextmenu', (event: MouseEvent) => {
+ rightClickHandler(event, this.textarea!, this.screenElement!, this._selectionService!, this.options.rightClickSelectsWord);
+ }));
+ }
+
+ // Move the textarea under the cursor when middle clicking on Linux to ensure
+ // middle click to paste selection works. This only appears to work in Chrome
+ // at the time is writing.
+ if (Browser.isLinux) {
+ // Use auxclick event over mousedown the latter doesn't seem to work. Note
+ // that the regular click event doesn't fire for the middle mouse button.
+ this.register(addDisposableDomListener(this.element!, 'auxclick', (event: MouseEvent) => {
+ if (event.button === 1) {
+ moveTextAreaUnderMouseCursor(event, this.textarea!, this.screenElement!);
+ }
+ }));
+ }
+ }
+
+ /**
+ * Apply key handling to the terminal
+ */
+ private _bindKeys(): void {
+ this.register(addDisposableDomListener(this.textarea!, 'keyup', (ev: KeyboardEvent) => this._keyUp(ev), true));
+ this.register(addDisposableDomListener(this.textarea!, 'keydown', (ev: KeyboardEvent) => this._keyDown(ev), true));
+ this.register(addDisposableDomListener(this.textarea!, 'keypress', (ev: KeyboardEvent) => this._keyPress(ev), true));
+ this.register(addDisposableDomListener(this.textarea!, 'compositionstart', () => this._compositionHelper!.compositionstart()));
+ this.register(addDisposableDomListener(this.textarea!, 'compositionupdate', (e: CompositionEvent) => this._compositionHelper!.compositionupdate(e)));
+ this.register(addDisposableDomListener(this.textarea!, 'compositionend', () => this._compositionHelper!.compositionend()));
+ this.register(addDisposableDomListener(this.textarea!, 'input', (ev: InputEvent) => this._inputEvent(ev), true));
+ this.register(this.onRender(() => this._compositionHelper!.updateCompositionElements()));
+ this.register(this.onRender(e => this._queueLinkification(e.start, e.end)));
+ }
+
+ /**
+ * Opens the terminal within an element.
+ *
+ * @param parent The element to create the terminal within.
+ */
+ public open(parent: HTMLElement): void {
+ if (!parent) {
+ throw new Error('Terminal requires a parent element.');
+ }
+
+ if (!parent.isConnected) {
+ this._logService.debug('Terminal.open was called on an element that was not attached to the DOM');
+ }
+
+ this._document = parent.ownerDocument!;
+
+ // Create main element container
+ this.element = this._document.createElement('div');
+ this.element.dir = 'ltr'; // xterm.css assumes LTR
+ this.element.classList.add('terminal');
+ this.element.classList.add('xterm');
+ this.element.setAttribute('tabindex', '0');
+ parent.appendChild(this.element);
+
+ // Performance: Use a document fragment to build the terminal
+ // viewport and helper elements detached from the DOM
+ const fragment = document.createDocumentFragment();
+ 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);
+
+ this.screenElement = document.createElement('div');
+ this.screenElement.classList.add('xterm-screen');
+ // Create the container that will hold helpers like the textarea for
+ // capturing DOM Events. Then produce the helpers.
+ this._helperContainer = document.createElement('div');
+ this._helperContainer.classList.add('xterm-helpers');
+ this.screenElement.appendChild(this._helperContainer);
+ fragment.appendChild(this.screenElement);
+
+ this.textarea = document.createElement('textarea');
+ this.textarea.classList.add('xterm-helper-textarea');
+ this.textarea.setAttribute('aria-label', Strings.promptLabel);
+ this.textarea.setAttribute('aria-multiline', 'false');
+ this.textarea.setAttribute('autocorrect', 'off');
+ this.textarea.setAttribute('autocapitalize', 'off');
+ this.textarea.setAttribute('spellcheck', 'false');
+ this.textarea.tabIndex = 0;
+ this.register(addDisposableDomListener(this.textarea, 'focus', (ev: KeyboardEvent) => this._onTextAreaFocus(ev)));
+ this.register(addDisposableDomListener(this.textarea, 'blur', () => this._onTextAreaBlur()));
+ this._helperContainer.appendChild(this.textarea);
+
+ const coreBrowserService = this._instantiationService.createInstance(CoreBrowserService, this.textarea);
+ this._instantiationService.setService(ICoreBrowserService, coreBrowserService);
+
+ this._charSizeService = this._instantiationService.createInstance(CharSizeService, this._document, this._helperContainer);
+ this._instantiationService.setService(ICharSizeService, this._charSizeService);
+
+ this._theme = this.options.theme || this._theme;
+ this._colorManager = new ColorManager(document, this.options.allowTransparency);
+ this.register(this.optionsService.onOptionChange(e => this._colorManager!.onOptionsChange(e)));
+ this._colorManager.setTheme(this._theme);
+
+ this._characterJoinerService = this._instantiationService.createInstance(CharacterJoinerService);
+ this._instantiationService.setService(ICharacterJoinerService, this._characterJoinerService);
+
+ 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.onResize(e => this._renderService!.resize(e.cols, e.rows));
+
+ this._compositionView = document.createElement('div');
+ this._compositionView.classList.add('composition-view');
+ this._compositionHelper = this._instantiationService.createInstance(CompositionHelper, this.textarea, this._compositionView);
+ this._helperContainer.appendChild(this._compositionView);
+
+ // Performance: Add viewport and helper elements from the fragment
+ this.element.appendChild(fragment);
+
+ this._soundService = this._instantiationService.createInstance(SoundService);
+ this._instantiationService.setService(ISoundService, this._soundService);
+ this._mouseService = this._instantiationService.createInstance(MouseService);
+ this._instantiationService.setService(IMouseService, this._mouseService);
+
+ this.viewport = this._instantiationService.createInstance(Viewport,
+ (amount: number) => this.scrollLines(amount, true, ScrollSource.VIEWPORT),
+ this._viewportElement,
+ this._viewportScrollArea,
+ this.element
+ );
+ this.viewport.onThemeChange(this._colorManager.colors);
+ this.register(this._inputHandler.onRequestSyncScrollBar(() => this.viewport!.syncScrollArea()));
+ this.register(this.viewport);
+
+ this.register(this.onCursorMove(() => {
+ this._renderService!.onCursorMove();
+ this._syncTextArea();
+ }));
+ this.register(this.onResize(() => this._renderService!.onResize(this.cols, this.rows)));
+ this.register(this.onBlur(() => this._renderService!.onBlur()));
+ this.register(this.onFocus(() => this._renderService!.onFocus()));
+ this.register(this._renderService.onDimensionsChange(() => this.viewport!.syncScrollArea()));
+
+ this._selectionService = this.register(this._instantiationService.createInstance(SelectionService,
+ this.element,
+ this.screenElement,
+ this.linkifier2
+ ));
+ this._instantiationService.setService(ISelectionService, this._selectionService);
+ this.register(this._selectionService.onRequestScrollLines(e => this.scrollLines(e.amount, e.suppressScrollEvent)));
+ this.register(this._selectionService.onSelectionChange(() => this._onSelectionChange.fire()));
+ this.register(this._selectionService.onRequestRedraw(e => this._renderService!.onSelectionChanged(e.start, e.end, e.columnSelectMode)));
+ this.register(this._selectionService.onLinuxMouseSelection(text => {
+ // If there's a new selection, put it into the textarea, focus and select it
+ // in order to register it as a selection on the OS. This event is fired
+ // only on Linux to enable middle click to paste selection.
+ this.textarea!.value = text;
+ this.textarea!.focus();
+ this.textarea!.select();
+ }));
+ this.register(this._onScroll.event(ev => {
+ this.viewport!.syncScrollArea();
+ this._selectionService!.refresh();
+ }));
+ this.register(addDisposableDomListener(this._viewportElement, 'scroll', () => this._selectionService!.refresh()));
+
+ this._mouseZoneManager = this._instantiationService.createInstance(MouseZoneManager, this.element, this.screenElement);
+ this.register(this._mouseZoneManager);
+ this.register(this.onScroll(() => this._mouseZoneManager!.clearAll()));
+ this.linkifier.attachToDom(this.element, this._mouseZoneManager);
+ this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService);
+
+ // This event listener must be registered aftre MouseZoneManager is created
+ this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e)));
+
+ // apply mouse event classes set by escape codes before terminal was attached
+ if (this.coreMouseService.areMouseEventsActive) {
+ this._selectionService.disable();
+ this.element.classList.add('enable-mouse-events');
+ } else {
+ this._selectionService.enable();
+ }
+
+ if (this.options.screenReaderMode) {
+ // Note that this must be done *after* the renderer is created in order to
+ // ensure the correct order of the dprchange event
+ this._accessibilityManager = new AccessibilityManager(this, this._renderService);
+ }
+
+ // Measure the character size
+ this._charSizeService.measure();
+
+ // Setup loop that draws to screen
+ this.refresh(0, this.rows - 1);
+
+ // Initialize global actions that need to be taken on the document.
+ this._initGlobal();
+
+ // Listen for mouse events and translate
+ // them into terminal mouse protocols.
+ this.bindMouse();
+ }
+
+ private _createRenderer(): IRenderer {
+ switch (this.options.rendererType) {
+ case 'canvas': return this._instantiationService.createInstance(Renderer, this._colorManager!.colors, this.screenElement!, this.linkifier, this.linkifier2);
+ case 'dom': return this._instantiationService.createInstance(DomRenderer, this._colorManager!.colors, this.element!, this.screenElement!, this._viewportElement!, this.linkifier, this.linkifier2);
+ default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`);
+ }
+ }
+
+ /**
+ * Sets the theme on the renderer. The renderer must have been initialized.
+ * @param theme The theme to set.
+ */
+ private _setTheme(theme: ITheme): void {
+ this._theme = theme;
+ this._colorManager?.setTheme(theme);
+ this._renderService?.setColors(this._colorManager!.colors);
+ this.viewport?.onThemeChange(this._colorManager!.colors);
+ }
+
+ /**
+ * Bind certain mouse events to the terminal.
+ * By default only 3 button + wheel up/down is ativated. For higher buttons
+ * no mouse report will be created. Typically the standard actions will be active.
+ *
+ * There are several reasons not to enable support for higher buttons/wheel:
+ * - Button 4 and 5 are typically used for history back and forward navigation,
+ * there is no straight forward way to supress/intercept those standard actions.
+ * - Support for higher buttons does not work in some platform/browser combinations.
+ * - Left/right wheel was not tested.
+ * - Emulators vary in mouse button support, typically only 3 buttons and
+ * wheel up/down work reliable.
+ *
+ * TODO: Move mouse event code into its own file.
+ */
+ public bindMouse(): void {
+ const self = this;
+ const el = this.element!;
+
+ // send event to CoreMouseService
+ function sendEvent(ev: MouseEvent | WheelEvent): boolean {
+ // get mouse coordinates
+ const pos = self._mouseService!.getRawByteCoords(ev, self.screenElement!, self.cols, self.rows);
+ if (!pos) {
+ return false;
+ }
+
+ let but: CoreMouseButton;
+ let action: CoreMouseAction | undefined;
+ switch ((ev as any).overrideType || ev.type) {
+ case 'mousemove':
+ action = CoreMouseAction.MOVE;
+ if (ev.buttons === undefined) {
+ // buttons is not supported on macOS, try to get a value from button instead
+ but = CoreMouseButton.NONE;
+ if (ev.button !== undefined) {
+ but = ev.button < 3 ? ev.button : CoreMouseButton.NONE;
+ }
+ } else {
+ // according to MDN buttons only reports up to button 5 (AUX2)
+ but = ev.buttons & 1 ? CoreMouseButton.LEFT :
+ ev.buttons & 4 ? CoreMouseButton.MIDDLE :
+ ev.buttons & 2 ? CoreMouseButton.RIGHT :
+ CoreMouseButton.NONE; // fallback to NONE
+ }
+ break;
+ case 'mouseup':
+ action = CoreMouseAction.UP;
+ but = ev.button < 3 ? ev.button : CoreMouseButton.NONE;
+ break;
+ case 'mousedown':
+ action = CoreMouseAction.DOWN;
+ 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;
+ }
+ but = CoreMouseButton.WHEEL;
+ break;
+ default:
+ // dont handle other event types by accident
+ return false;
+ }
+
+ // exit if we cannot determine valid button/action values
+ // do nothing for higher buttons than wheel
+ if (action === undefined || but === undefined || but > CoreMouseButton.WHEEL) {
+ return false;
+ }
+
+ return self.coreMouseService.triggerMouseEvent({
+ col: pos.x - 33, // FIXME: why -33 here?
+ row: pos.y - 33,
+ button: but,
+ action,
+ ctrl: ev.ctrlKey,
+ alt: ev.altKey,
+ shift: ev.shiftKey
+ });
+ }
+
+ /**
+ * Event listener state handling.
+ * We listen to the onProtocolChange event of CoreMouseService and put
+ * requested listeners in `requestedEvents`. With this the listeners
+ * have all bits to do the event listener juggling.
+ * Note: 'mousedown' currently is "always on" and not managed
+ * by onProtocolChange.
+ */
+ const requestedEvents: { [key: string]: ((ev: Event) => void) | null } = {
+ mouseup: null,
+ wheel: null,
+ mousedrag: null,
+ mousemove: null
+ };
+ const eventListeners: { [key: string]: (ev: any) => void | boolean } = {
+ mouseup: (ev: MouseEvent) => {
+ sendEvent(ev);
+ if (!ev.buttons) {
+ // if no other button is held remove global handlers
+ this._document!.removeEventListener('mouseup', requestedEvents.mouseup!);
+ if (requestedEvents.mousedrag) {
+ this._document!.removeEventListener('mousemove', requestedEvents.mousedrag);
+ }
+ }
+ return this.cancel(ev);
+ },
+ wheel: (ev: WheelEvent) => {
+ sendEvent(ev);
+ return this.cancel(ev, true);
+ },
+ mousedrag: (ev: MouseEvent) => {
+ // deal only with move while a button is held
+ if (ev.buttons) {
+ sendEvent(ev);
+ }
+ },
+ mousemove: (ev: MouseEvent) => {
+ // deal only with move without any button
+ if (!ev.buttons) {
+ sendEvent(ev);
+ }
+ }
+ };
+ this.register(this.coreMouseService.onProtocolChange(events => {
+ // apply global changes on events
+ if (events) {
+ if (this.optionsService.rawOptions.logLevel === 'debug') {
+ this._logService.debug('Binding to mouse events:', this.coreMouseService.explainEvents(events));
+ }
+ this.element!.classList.add('enable-mouse-events');
+ this._selectionService!.disable();
+ } else {
+ this._logService.debug('Unbinding from mouse events.');
+ this.element!.classList.remove('enable-mouse-events');
+ this._selectionService!.enable();
+ }
+
+ // add/remove handlers from requestedEvents
+
+ if (!(events & CoreMouseEventType.MOVE)) {
+ el.removeEventListener('mousemove', requestedEvents.mousemove!);
+ requestedEvents.mousemove = null;
+ } else if (!requestedEvents.mousemove) {
+ el.addEventListener('mousemove', eventListeners.mousemove);
+ requestedEvents.mousemove = eventListeners.mousemove;
+ }
+
+ if (!(events & CoreMouseEventType.WHEEL)) {
+ el.removeEventListener('wheel', requestedEvents.wheel!);
+ requestedEvents.wheel = null;
+ } else if (!requestedEvents.wheel) {
+ el.addEventListener('wheel', eventListeners.wheel, { passive: false });
+ requestedEvents.wheel = eventListeners.wheel;
+ }
+
+ if (!(events & CoreMouseEventType.UP)) {
+ this._document!.removeEventListener('mouseup', requestedEvents.mouseup!);
+ requestedEvents.mouseup = null;
+ } else if (!requestedEvents.mouseup) {
+ requestedEvents.mouseup = eventListeners.mouseup;
+ }
+
+ if (!(events & CoreMouseEventType.DRAG)) {
+ this._document!.removeEventListener('mousemove', requestedEvents.mousedrag!);
+ requestedEvents.mousedrag = null;
+ } else if (!requestedEvents.mousedrag) {
+ requestedEvents.mousedrag = eventListeners.mousedrag;
+ }
+ }));
+ // force initial onProtocolChange so we dont miss early mouse requests
+ this.coreMouseService.activeProtocol = this.coreMouseService.activeProtocol;
+
+ /**
+ * "Always on" event listeners.
+ */
+ this.register(addDisposableDomListener(el, 'mousedown', (ev: MouseEvent) => {
+ ev.preventDefault();
+ this.focus();
+
+ // Don't send the mouse button to the pty if mouse events are disabled or
+ // if the selection manager is having selection forced (ie. a modifier is
+ // held).
+ if (!this.coreMouseService.areMouseEventsActive || this._selectionService!.shouldForceSelection(ev)) {
+ return;
+ }
+
+ sendEvent(ev);
+
+ // Register additional global handlers which should keep reporting outside
+ // of the terminal element.
+ // Note: Other emulators also do this for 'mousedown' while a button
+ // is held, we currently limit 'mousedown' to the terminal only.
+ if (requestedEvents.mouseup) {
+ this._document!.addEventListener('mouseup', requestedEvents.mouseup);
+ }
+ if (requestedEvents.mousedrag) {
+ this._document!.addEventListener('mousemove', requestedEvents.mousedrag);
+ }
+
+ return this.cancel(ev);
+ }));
+
+ this.register(addDisposableDomListener(el, 'wheel', (ev: WheelEvent) => {
+ // do nothing, if app side handles wheel itself
+ if (requestedEvents.wheel) return;
+
+ if (!this.buffer.hasScrollback) {
+ // Convert wheel events into up/down events when the buffer does not have scrollback, this
+ // enables scrolling in apps hosted in the alt buffer such as vim or tmux.
+ const amount = this.viewport!.getLinesScrolled(ev);
+
+ // Do nothing if there's no vertical scroll
+ if (amount === 0) {
+ return;
+ }
+
+ // Construct and send sequences
+ const sequence = C0.ESC + (this.coreService.decPrivateModes.applicationCursorKeys ? 'O' : '[') + (ev.deltaY < 0 ? 'A' : 'B');
+ let data = '';
+ for (let i = 0; i < Math.abs(amount); i++) {
+ data += sequence;
+ }
+ this.coreService.triggerDataEvent(data, true);
+ return this.cancel(ev, true);
+ }
+
+ // normal viewport scrolling
+ // conditionally stop event, if the viewport still had rows to scroll within
+ if (this.viewport!.onWheel(ev)) {
+ return this.cancel(ev);
+ }
+ }, { passive: false }));
+
+ this.register(addDisposableDomListener(el, 'touchstart', (ev: TouchEvent) => {
+ if (this.coreMouseService.areMouseEventsActive) return;
+ this.viewport!.onTouchStart(ev);
+ return this.cancel(ev);
+ }, { passive: true }));
+
+ this.register(addDisposableDomListener(el, 'touchmove', (ev: TouchEvent) => {
+ if (this.coreMouseService.areMouseEventsActive) return;
+ if (!this.viewport!.onTouchMove(ev)) {
+ return this.cancel(ev);
+ }
+ }, { passive: false }));
+ }
+
+
+ /**
+ * Tells the renderer to refresh terminal content between two rows (inclusive) at the next
+ * opportunity.
+ * @param start The row to start from (between 0 and this.rows - 1).
+ * @param end The row to end at (between start and this.rows - 1).
+ */
+ public refresh(start: number, end: number): void {
+ this._renderService?.refreshRows(start, end);
+ }
+
+ /**
+ * Queues linkification for the specified rows.
+ * @param start The row to start from (between 0 and this.rows - 1).
+ * @param end The row to end at (between start and this.rows - 1).
+ */
+ private _queueLinkification(start: number, end: number): void {
+ this.linkifier?.linkifyRows(start, end);
+ }
+
+ /**
+ * Change the cursor style for different selection modes
+ */
+ public updateCursorStyle(ev: KeyboardEvent): void {
+ if (this._selectionService?.shouldColumnSelect(ev)) {
+ this.element!.classList.add('column-select');
+ } else {
+ this.element!.classList.remove('column-select');
+ }
+ }
+
+ /**
+ * Display the cursor element
+ */
+ private _showCursor(): void {
+ if (!this.coreService.isCursorInitialized) {
+ this.coreService.isCursorInitialized = true;
+ this.refresh(this.buffer.y, this.buffer.y);
+ }
+ }
+
+ public scrollLines(disp: number, suppressScrollEvent?: boolean, source = ScrollSource.TERMINAL): void {
+ super.scrollLines(disp, suppressScrollEvent, source);
+ this.refresh(0, this.rows - 1);
+ }
+
+ public paste(data: string): void {
+ paste(data, this.textarea!, this.coreService);
+ }
+
+ /**
+ * Attaches a custom key event handler which is run before keys are processed,
+ * giving consumers of xterm.js ultimate control as to what keys should be
+ * processed by the terminal and what keys should not.
+ * @param customKeyEventHandler The custom KeyboardEvent handler to attach.
+ * This is a function that takes a KeyboardEvent, allowing consumers to stop
+ * propagation and/or prevent the default action. The function returns whether
+ * the event should be processed by xterm.js.
+ */
+ public attachCustomKeyEventHandler(customKeyEventHandler: CustomKeyEventHandler): void {
+ this._customKeyEventHandler = customKeyEventHandler;
+ }
+
+ /**
+ * Registers a link matcher, allowing custom link patterns to be matched and
+ * handled.
+ * @param regex The regular expression to search for, specifically
+ * this searches the textContent of the rows. You will want to use \s to match
+ * a space ' ' character for example.
+ * @param handler The callback when the link is called.
+ * @param options Options for the link matcher.
+ * @return The ID of the new matcher, this can be used to deregister.
+ */
+ public registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number {
+ const matcherId = this.linkifier.registerLinkMatcher(regex, handler, options);
+ this.refresh(0, this.rows - 1);
+ return matcherId;
+ }
+
+ /**
+ * Deregisters a link matcher if it has been registered.
+ * @param matcherId The link matcher's ID (returned after register)
+ */
+ public deregisterLinkMatcher(matcherId: number): void {
+ if (this.linkifier.deregisterLinkMatcher(matcherId)) {
+ this.refresh(0, this.rows - 1);
+ }
+ }
+
+ public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
+ return this.linkifier2.registerLinkProvider(linkProvider);
+ }
+
+ public registerCharacterJoiner(handler: CharacterJoinerHandler): number {
+ if (!this._characterJoinerService) {
+ throw new Error('Terminal must be opened first');
+ }
+ const joinerId = this._characterJoinerService.register(handler);
+ this.refresh(0, this.rows - 1);
+ return joinerId;
+ }
+
+ public deregisterCharacterJoiner(joinerId: number): void {
+ if (!this._characterJoinerService) {
+ throw new Error('Terminal must be opened first');
+ }
+ if (this._characterJoinerService.deregister(joinerId)) {
+ this.refresh(0, this.rows - 1);
+ }
+ }
+
+ public get markers(): IMarker[] {
+ return this.buffer.markers;
+ }
+
+ public addMarker(cursorYOffset: number): IMarker | undefined {
+ // Disallow markers on the alt buffer
+ if (this.buffer !== this.buffers.normal) {
+ return;
+ }
+
+ return this.buffer.addMarker(this.buffer.ybase + this.buffer.y + cursorYOffset);
+ }
+
+ /**
+ * Gets whether the terminal has an active selection.
+ */
+ public hasSelection(): boolean {
+ return this._selectionService ? this._selectionService.hasSelection : false;
+ }
+
+ /**
+ * Selects text within the terminal.
+ * @param column The column the selection starts at..
+ * @param row The row the selection starts at.
+ * @param length The length of the selection.
+ */
+ public select(column: number, row: number, length: number): void {
+ this._selectionService!.setSelection(column, row, length);
+ }
+
+ /**
+ * Gets the terminal's current selection, this is useful for implementing copy
+ * behavior outside of xterm.js.
+ */
+ public getSelection(): string {
+ return this._selectionService ? this._selectionService.selectionText : '';
+ }
+
+ public getSelectionPosition(): ISelectionPosition | undefined {
+ if (!this._selectionService || !this._selectionService.hasSelection) {
+ return undefined;
+ }
+
+ return {
+ startColumn: this._selectionService.selectionStart![0],
+ startRow: this._selectionService.selectionStart![1],
+ endColumn: this._selectionService.selectionEnd![0],
+ endRow: this._selectionService.selectionEnd![1]
+ };
+ }
+
+ /**
+ * Clears the current terminal selection.
+ */
+ public clearSelection(): void {
+ this._selectionService?.clearSelection();
+ }
+
+ /**
+ * Selects all text within the terminal.
+ */
+ public selectAll(): void {
+ this._selectionService?.selectAll();
+ }
+
+ public selectLines(start: number, end: number): void {
+ this._selectionService?.selectLines(start, end);
+ }
+
+ /**
+ * Handle a keydown event
+ * Key Resources:
+ * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent
+ * @param ev The keydown event to be handled.
+ */
+ protected _keyDown(event: KeyboardEvent): boolean | undefined {
+ this._keyDownHandled = false;
+
+ if (this._customKeyEventHandler && this._customKeyEventHandler(event) === false) {
+ return false;
+ }
+
+ if (!this._compositionHelper!.keydown(event)) {
+ if (this.buffer.ybase !== this.buffer.ydisp) {
+ this._bufferService.scrollToBottom();
+ }
+ return false;
+ }
+
+ if (event.key === 'Dead' || event.key === 'AltGraph') {
+ this._unprocessedDeadKey = true;
+ }
+
+ const result = evaluateKeyboardEvent(event, this.coreService.decPrivateModes.applicationCursorKeys, this.browser.isMac, this.options.macOptionIsMeta);
+
+ this.updateCursorStyle(event);
+
+ if (result.type === KeyboardResultType.PAGE_DOWN || result.type === KeyboardResultType.PAGE_UP) {
+ const scrollCount = this.rows - 1;
+ this.scrollLines(result.type === KeyboardResultType.PAGE_UP ? -scrollCount : scrollCount);
+ return this.cancel(event, true);
+ }
+
+ if (result.type === KeyboardResultType.SELECT_ALL) {
+ this.selectAll();
+ }
+
+ if (this._isThirdLevelShift(this.browser, event)) {
+ return true;
+ }
+
+ if (result.cancel) {
+ // The event is canceled at the end already, is this necessary?
+ this.cancel(event, true);
+ }
+
+ if (!result.key) {
+ return true;
+ }
+
+ if (this._unprocessedDeadKey) {
+ this._unprocessedDeadKey = false;
+ return true;
+ }
+
+ // If ctrl+c or enter is being sent, clear out the textarea. This is done so that screen readers
+ // will announce deleted characters. This will not work 100% of the time but it should cover
+ // most scenarios.
+ if (result.key === C0.ETX || result.key === C0.CR) {
+ this.textarea!.value = '';
+ }
+
+ this._onKey.fire({ key: result.key, domEvent: event });
+ this._showCursor();
+ this.coreService.triggerDataEvent(result.key, true);
+
+ // Cancel events when not in screen reader mode so events don't get bubbled up and handled by
+ // other listeners. When screen reader mode is enabled, this could cause issues if the event
+ // is handled at a higher level, this is a compromise in order to echo keys to the screen
+ // reader.
+ if (!this.optionsService.rawOptions.screenReaderMode) {
+ return this.cancel(event, true);
+ }
+
+ this._keyDownHandled = true;
+ }
+
+ private _isThirdLevelShift(browser: IBrowser, ev: KeyboardEvent): boolean {
+ const thirdLevelKey =
+ (browser.isMac && !this.options.macOptionIsMeta && ev.altKey && !ev.ctrlKey && !ev.metaKey) ||
+ (browser.isWindows && ev.altKey && ev.ctrlKey && !ev.metaKey) ||
+ (browser.isWindows && ev.getModifierState('AltGraph'));
+
+ if (ev.type === 'keypress') {
+ return thirdLevelKey;
+ }
+
+ // Don't invoke for arrows, pageDown, home, backspace, etc. (on non-keypress events)
+ return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47);
+ }
+
+ protected _keyUp(ev: KeyboardEvent): void {
+ if (this._customKeyEventHandler && this._customKeyEventHandler(ev) === false) {
+ return;
+ }
+
+ if (!wasModifierKeyOnlyEvent(ev)) {
+ this.focus();
+ }
+
+ this.updateCursorStyle(ev);
+ this._keyPressHandled = false;
+ }
+
+ /**
+ * Handle a keypress event.
+ * Key Resources:
+ * - https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent
+ * @param ev The keypress event to be handled.
+ */
+ protected _keyPress(ev: KeyboardEvent): boolean {
+ let key;
+
+ this._keyPressHandled = false;
+
+ if (this._keyDownHandled) {
+ return false;
+ }
+
+ if (this._customKeyEventHandler && this._customKeyEventHandler(ev) === false) {
+ return false;
+ }
+
+ this.cancel(ev);
+
+ if (ev.charCode) {
+ key = ev.charCode;
+ } else if (ev.which === null || ev.which === undefined) {
+ key = ev.keyCode;
+ } else if (ev.which !== 0 && ev.charCode !== 0) {
+ key = ev.which;
+ } else {
+ return false;
+ }
+
+ if (!key || (
+ (ev.altKey || ev.ctrlKey || ev.metaKey) && !this._isThirdLevelShift(this.browser, ev)
+ )) {
+ return false;
+ }
+
+ key = String.fromCharCode(key);
+
+ this._onKey.fire({ key, domEvent: ev });
+ this._showCursor();
+ this.coreService.triggerDataEvent(key, true);
+
+ this._keyPressHandled = true;
+
+ // The key was handled so clear the dead key state, otherwise certain keystrokes like arrow
+ // keys could be ignored
+ this._unprocessedDeadKey = false;
+
+ return true;
+ }
+
+ /**
+ * Handle an input event.
+ * Key Resources:
+ * - https://developer.mozilla.org/en-US/docs/Web/API/InputEvent
+ * @param ev The input event to be handled.
+ */
+ 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) {
+ if (this._keyPressHandled) {
+ return false;
+ }
+
+ // The key was handled so clear the dead key state, otherwise certain keystrokes like arrow
+ // keys could be ignored
+ this._unprocessedDeadKey = false;
+
+ const text = ev.data;
+ this.coreService.triggerDataEvent(text, true);
+
+ this.cancel(ev);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Ring the bell.
+ * Note: We could do sweet things with webaudio here
+ */
+ public bell(): void {
+ if (this._soundBell()) {
+ this._soundService?.playBellSound();
+ }
+
+ this._onBell.fire();
+
+ // if (this._visualBell()) {
+ // this.element.classList.add('visual-bell-active');
+ // clearTimeout(this._visualBellTimer);
+ // this._visualBellTimer = window.setTimeout(() => {
+ // this.element.classList.remove('visual-bell-active');
+ // }, 200);
+ // }
+ }
+
+ /**
+ * Resizes the terminal.
+ *
+ * @param x The number of columns to resize to.
+ * @param y The number of rows to resize to.
+ */
+ public resize(x: number, y: number): void {
+ if (x === this.cols && y === this.rows) {
+ // Check if we still need to measure the char size (fixes #785).
+ if (this._charSizeService && !this._charSizeService.hasValidSize) {
+ this._charSizeService.measure();
+ }
+ return;
+ }
+
+ super.resize(x, y);
+ }
+
+ private _afterResize(x: number, y: number): void {
+ this._charSizeService?.measure();
+
+ // Sync the scroll area to make sure scroll events don't fire and scroll the viewport to an
+ // invalid location
+ this.viewport?.syncScrollArea(true);
+ }
+
+ /**
+ * Clear the entire buffer, making the prompt line the new first line.
+ */
+ public clear(): void {
+ if (this.buffer.ybase === 0 && this.buffer.y === 0) {
+ // Don't clear if it's already clear
+ return;
+ }
+ this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y)!);
+ this.buffer.lines.length = 1;
+ this.buffer.ydisp = 0;
+ this.buffer.ybase = 0;
+ this.buffer.y = 0;
+ for (let i = 1; i < this.rows; i++) {
+ this.buffer.lines.push(this.buffer.getBlankLine(DEFAULT_ATTR_DATA));
+ }
+ this.refresh(0, this.rows - 1);
+ this._onScroll.fire({ position: this.buffer.ydisp, source: ScrollSource.TERMINAL });
+ }
+
+ /**
+ * Reset terminal.
+ * Note: Calling this directly from JS is synchronous but does not clear
+ * input buffers and does not reset the parser, thus the terminal will
+ * continue to apply pending input data.
+ * If you need in band reset (synchronous with input data) consider
+ * using DECSTR (soft reset, CSI ! p) or RIS instead (hard reset, ESC c).
+ */
+ public reset(): void {
+ /**
+ * Since _setup handles a full terminal creation, we have to carry forward
+ * a few things that should not reset.
+ */
+ this.options.rows = this.rows;
+ this.options.cols = this.cols;
+ const customKeyEventHandler = this._customKeyEventHandler;
+
+ this._setup();
+ super.reset();
+ this._selectionService?.reset();
+
+ // reattach
+ this._customKeyEventHandler = customKeyEventHandler;
+
+ // do a full screen refresh
+ this.refresh(0, this.rows - 1);
+ this.viewport?.syncScrollArea();
+ }
+
+ public clearTextureAtlas(): void {
+ this._renderService?.clearTextureAtlas();
+ }
+
+ private _reportFocus(): void {
+ if (this.element?.classList.contains('focus')) {
+ this.coreService.triggerDataEvent(C0.ESC + '[I');
+ } else {
+ this.coreService.triggerDataEvent(C0.ESC + '[O');
+ }
+ }
+
+ private _reportWindowsOptions(type: WindowsOptionsReportType): void {
+ if (!this._renderService) {
+ return;
+ }
+
+ switch (type) {
+ case WindowsOptionsReportType.GET_WIN_SIZE_PIXELS:
+ const canvasWidth = this._renderService.dimensions.scaledCanvasWidth.toFixed(0);
+ const canvasHeight = this._renderService.dimensions.scaledCanvasHeight.toFixed(0);
+ this.coreService.triggerDataEvent(`${C0.ESC}[4;${canvasHeight};${canvasWidth}t`);
+ break;
+ case WindowsOptionsReportType.GET_CELL_SIZE_PIXELS:
+ const cellWidth = this._renderService.dimensions.scaledCellWidth.toFixed(0);
+ const cellHeight = this._renderService.dimensions.scaledCellHeight.toFixed(0);
+ this.coreService.triggerDataEvent(`${C0.ESC}[6;${cellHeight};${cellWidth}t`);
+ break;
+ }
+ }
+
+ // TODO: Remove cancel function and cancelEvents option
+ public cancel(ev: Event, force?: boolean): boolean | undefined {
+ if (!this.options.cancelEvents && !force) {
+ return;
+ }
+ ev.preventDefault();
+ ev.stopPropagation();
+ return false;
+ }
+
+ private _visualBell(): boolean {
+ return false;
+ // return this.options.bellStyle === 'visual' ||
+ // this.options.bellStyle === 'both';
+ }
+
+ private _soundBell(): boolean {
+ return this.options.bellStyle === 'sound';
+ // return this.options.bellStyle === 'sound' ||
+ // this.options.bellStyle === 'both';
+ }
+}
+
+/**
+ * Helpers
+ */
+
+function wasModifierKeyOnlyEvent(ev: KeyboardEvent): boolean {
+ return ev.keyCode === 16 || // Shift
+ ev.keyCode === 17 || // Ctrl
+ ev.keyCode === 18; // Alt
+}
diff --git a/node_modules/xterm/src/browser/TimeBasedDebouncer.ts b/node_modules/xterm/src/browser/TimeBasedDebouncer.ts
new file mode 100644
index 0000000..707e25c
--- /dev/null
+++ b/node_modules/xterm/src/browser/TimeBasedDebouncer.ts
@@ -0,0 +1,86 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+const RENDER_DEBOUNCE_THRESHOLD_MS = 1000; // 1 Second
+
+import { IRenderDebouncer } from 'browser/Types';
+
+/**
+ * Debounces calls to update screen readers to update at most once configurable interval of time.
+ */
+export class TimeBasedDebouncer implements IRenderDebouncer {
+ private _rowStart: number | undefined;
+ private _rowEnd: number | undefined;
+ private _rowCount: number | undefined;
+
+ // The last moment that the Terminal was refreshed at
+ private _lastRefreshMs = 0;
+ // Whether a trailing refresh should be triggered due to a refresh request that was throttled
+ private _additionalRefreshRequested = false;
+
+ private _refreshTimeoutID: number | undefined;
+
+ constructor(
+ private _renderCallback: (start: number, end: number) => void,
+ private readonly _debounceThresholdMS = RENDER_DEBOUNCE_THRESHOLD_MS
+ ) {
+ }
+
+ public dispose(): void {
+ if (this._refreshTimeoutID) {
+ clearTimeout(this._refreshTimeoutID);
+ }
+ }
+
+ 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
+ rowStart = rowStart !== undefined ? rowStart : 0;
+ rowEnd = rowEnd !== undefined ? rowEnd : this._rowCount - 1;
+ // Set the properties to the updated values
+ this._rowStart = this._rowStart !== undefined ? Math.min(this._rowStart, rowStart) : rowStart;
+ this._rowEnd = this._rowEnd !== undefined ? Math.max(this._rowEnd, rowEnd) : rowEnd;
+
+ // Only refresh if the time since last refresh is above a threshold, otherwise wait for
+ // enough time to pass before refreshing again.
+ const refreshRequestTime: number = Date.now();
+ if (refreshRequestTime - this._lastRefreshMs >= this._debounceThresholdMS) {
+ // Enough time has lapsed since the last refresh; refresh immediately
+ this._lastRefreshMs = refreshRequestTime;
+ this._innerRefresh();
+ } else if (!this._additionalRefreshRequested) {
+ // This is the first additional request throttled; set up trailing refresh
+ const elapsed = refreshRequestTime - this._lastRefreshMs;
+ const waitPeriodBeforeTrailingRefresh = this._debounceThresholdMS - elapsed;
+ this._additionalRefreshRequested = true;
+
+ this._refreshTimeoutID = window.setTimeout(() => {
+ this._lastRefreshMs = Date.now();
+ this._innerRefresh();
+ this._additionalRefreshRequested = false;
+ this._refreshTimeoutID = undefined; // No longer need to clear the timeout
+ }, waitPeriodBeforeTrailingRefresh);
+ }
+ }
+
+ private _innerRefresh(): void {
+ // Make sure values are set
+ if (this._rowStart === undefined || this._rowEnd === undefined || this._rowCount === undefined) {
+ return;
+ }
+
+ // Clamp values
+ const start = Math.max(this._rowStart, 0);
+ const end = Math.min(this._rowEnd, this._rowCount - 1);
+
+ // Reset debouncer (this happens before render callback as the render could trigger it again)
+ this._rowStart = undefined;
+ this._rowEnd = undefined;
+
+ // Run render callback
+ this._renderCallback(start, end);
+ }
+}
+
diff --git a/node_modules/xterm/src/browser/Types.d.ts b/node_modules/xterm/src/browser/Types.d.ts
new file mode 100644
index 0000000..a616584
--- /dev/null
+++ b/node_modules/xterm/src/browser/Types.d.ts
@@ -0,0 +1,315 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IDisposable, IMarker, ISelectionPosition } from 'xterm';
+import { IEvent } from 'common/EventEmitter';
+import { ICoreTerminal, CharData, ITerminalOptions } from 'common/Types';
+import { IMouseService, IRenderService } from './services/Services';
+import { IBuffer, IBufferSet } from 'common/buffer/Types';
+import { IFunctionIdentifier, IParams } from 'common/parser/Types';
+
+export interface ITerminal extends IPublicTerminal, ICoreTerminal {
+ element: HTMLElement | undefined;
+ screenElement: HTMLElement | undefined;
+ browser: IBrowser;
+ buffer: IBuffer;
+ viewport: IViewport | undefined;
+ options: ITerminalOptions;
+ linkifier: ILinkifier;
+ linkifier2: ILinkifier2;
+
+ onBlur: IEvent<void>;
+ onFocus: IEvent<void>;
+ onA11yChar: IEvent<string>;
+ onA11yTab: IEvent<number>;
+
+ cancel(ev: Event, force?: boolean): boolean | void;
+}
+
+// Portions of the public API that are required by the internal Terminal
+export interface IPublicTerminal extends IDisposable {
+ textarea: HTMLTextAreaElement | undefined;
+ rows: number;
+ cols: number;
+ buffer: IBuffer;
+ markers: IMarker[];
+ onCursorMove: IEvent<void>;
+ onData: IEvent<string>;
+ onBinary: IEvent<string>;
+ onKey: IEvent<{ key: string, domEvent: KeyboardEvent }>;
+ onLineFeed: IEvent<void>;
+ onScroll: IEvent<number>;
+ onSelectionChange: IEvent<void>;
+ onRender: IEvent<{ start: number, end: number }>;
+ onResize: IEvent<{ cols: number, rows: number }>;
+ onTitleChange: IEvent<string>;
+ onBell: IEvent<void>;
+ blur(): void;
+ focus(): void;
+ resize(columns: number, rows: number): void;
+ open(parent: HTMLElement): void;
+ attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void;
+ registerCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean | Promise<boolean>): IDisposable;
+ registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean | Promise<boolean>): IDisposable;
+ registerEscHandler(id: IFunctionIdentifier, callback: () => boolean | Promise<boolean>): IDisposable;
+ registerOscHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable;
+ registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number;
+ deregisterLinkMatcher(matcherId: number): void;
+ registerLinkProvider(linkProvider: ILinkProvider): IDisposable;
+ registerCharacterJoiner(handler: (text: string) => [number, number][]): number;
+ deregisterCharacterJoiner(joinerId: number): void;
+ addMarker(cursorYOffset: number): IMarker | undefined;
+ hasSelection(): boolean;
+ getSelection(): string;
+ getSelectionPosition(): ISelectionPosition | undefined;
+ clearSelection(): void;
+ select(column: number, row: number, length: number): void;
+ selectAll(): void;
+ selectLines(start: number, end: number): void;
+ dispose(): void;
+ scrollLines(amount: number): void;
+ scrollPages(pageCount: number): void;
+ scrollToTop(): void;
+ scrollToBottom(): void;
+ scrollToLine(line: number): void;
+ clear(): void;
+ write(data: string | Uint8Array, callback?: () => void): void;
+ paste(data: string): void;
+ refresh(start: number, end: number): void;
+ clearTextureAtlas(): void;
+ reset(): void;
+}
+
+export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean;
+
+export type LineData = CharData[];
+
+export interface ICompositionHelper {
+ readonly isComposing: boolean;
+ compositionstart(): void;
+ compositionupdate(ev: CompositionEvent): void;
+ compositionend(): void;
+ updateCompositionElements(dontRecurse?: boolean): void;
+ keydown(ev: KeyboardEvent): boolean;
+}
+
+export interface IBrowser {
+ isNode: boolean;
+ userAgent: string;
+ platform: string;
+ isFirefox: boolean;
+ isMac: boolean;
+ isIpad: boolean;
+ isIphone: boolean;
+ isWindows: boolean;
+}
+
+export interface IColorManager {
+ colors: IColorSet;
+ 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;
+ cursor: IColor;
+ cursorAccent: IColor;
+ selectionTransparent: IColor;
+ /** The selection blended on top of background. */
+ selectionOpaque: IColor;
+ ansi: IColor[];
+ contrastCache: IColorContrastCache;
+}
+
+export interface IColorContrastCache {
+ clear(): void;
+ setCss(bg: number, fg: number, value: string | null): void;
+ getCss(bg: number, fg: number): string | null | undefined;
+ setColor(bg: number, fg: number, value: IColor | null): void;
+ getColor(bg: number, fg: number): IColor | null | undefined;
+}
+
+export interface IPartialColorSet {
+ foreground: IColor;
+ background: IColor;
+ cursor?: IColor;
+ cursorAccent?: IColor;
+ selection?: IColor;
+ ansi: IColor[];
+}
+
+export interface IViewport extends IDisposable {
+ scrollBarWidth: number;
+ syncScrollArea(immediate?: boolean): void;
+ getLinesScrolled(ev: WheelEvent): number;
+ onWheel(ev: WheelEvent): boolean;
+ onTouchStart(ev: TouchEvent): void;
+ onTouchMove(ev: TouchEvent): boolean;
+ onThemeChange(colors: IColorSet): void;
+}
+
+export interface IViewportRange {
+ start: IViewportRangePosition;
+ end: IViewportRangePosition;
+}
+
+export interface IViewportRangePosition {
+ x: number;
+ y: number;
+}
+
+export type LinkMatcherHandler = (event: MouseEvent, uri: string) => void;
+export type LinkMatcherHoverTooltipCallback = (event: MouseEvent, uri: string, position: IViewportRange) => void;
+export type LinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void;
+
+export interface ILinkMatcher {
+ id: number;
+ regex: RegExp;
+ handler: LinkMatcherHandler;
+ hoverTooltipCallback?: LinkMatcherHoverTooltipCallback;
+ hoverLeaveCallback?: () => void;
+ matchIndex?: number;
+ validationCallback?: LinkMatcherValidationCallback;
+ priority?: number;
+ willLinkActivate?: (event: MouseEvent, uri: string) => boolean;
+}
+
+export interface IRegisteredLinkMatcher extends ILinkMatcher {
+ priority: number;
+}
+
+export interface ILinkifierEvent {
+ x1: number;
+ y1: number;
+ x2: number;
+ y2: number;
+ cols: number;
+ fg: number | undefined;
+}
+
+export interface ILinkifier {
+ onShowLinkUnderline: IEvent<ILinkifierEvent>;
+ onHideLinkUnderline: IEvent<ILinkifierEvent>;
+ onLinkTooltip: IEvent<ILinkifierEvent>;
+
+ attachToDom(element: HTMLElement, mouseZoneManager: IMouseZoneManager): void;
+ linkifyRows(start: number, end: number): void;
+ registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number;
+ deregisterLinkMatcher(matcherId: number): boolean;
+}
+
+interface ILinkState {
+ decorations: ILinkDecorations;
+ isHovered: boolean;
+}
+export interface ILinkWithState {
+ link: ILink;
+ state?: ILinkState;
+}
+
+export interface ILinkifier2 {
+ onShowLinkUnderline: IEvent<ILinkifierEvent>;
+ onHideLinkUnderline: IEvent<ILinkifierEvent>;
+ readonly currentLink: ILinkWithState | undefined;
+
+ attachToDom(element: HTMLElement, mouseService: IMouseService, renderService: IRenderService): void;
+ registerLinkProvider(linkProvider: ILinkProvider): IDisposable;
+}
+
+export interface ILinkMatcherOptions {
+ /**
+ * The index of the link from the regex.match(text) call. This defaults to 0
+ * (for regular expressions without capture groups).
+ */
+ matchIndex?: number;
+ /**
+ * A callback that validates an individual link, returning true if valid and
+ * false if invalid.
+ */
+ validationCallback?: LinkMatcherValidationCallback;
+ /**
+ * A callback that fires when the mouse hovers over a link.
+ */
+ tooltipCallback?: LinkMatcherHoverTooltipCallback;
+ /**
+ * A callback that fires when the mouse leaves a link that was hovered.
+ */
+ leaveCallback?: () => void;
+ /**
+ * The priority of the link matcher, this defines the order in which the link
+ * matcher is evaluated relative to others, from highest to lowest. The
+ * default value is 0.
+ */
+ priority?: number;
+ /**
+ * A callback that fires when the mousedown and click events occur that
+ * determines whether a link will be activated upon click. This enables
+ * only activating a link when a certain modifier is held down, if not the
+ * mouse event will continue propagation (eg. double click to select word).
+ */
+ willLinkActivate?: (event: MouseEvent, uri: string) => boolean;
+}
+
+export interface IMouseZoneManager extends IDisposable {
+ add(zone: IMouseZone): void;
+ clearAll(start?: number, end?: number): void;
+}
+
+export interface IMouseZone {
+ x1: number;
+ x2: number;
+ y1: number;
+ y2: number;
+ clickCallback: (e: MouseEvent) => any;
+ hoverCallback: (e: MouseEvent) => any | undefined;
+ tooltipCallback: (e: MouseEvent) => any | undefined;
+ leaveCallback: () => any | undefined;
+ willLinkActivate: (e: MouseEvent) => boolean;
+}
+
+interface ILinkProvider {
+ provideLinks(y: number, callback: (links: ILink[] | undefined) => void): void;
+}
+
+interface ILink {
+ range: IBufferRange;
+ text: string;
+ decorations?: ILinkDecorations;
+ activate(event: MouseEvent, text: string): void;
+ hover?(event: MouseEvent, text: string): void;
+ leave?(event: MouseEvent, text: string): void;
+ dispose?(): void;
+}
+
+interface ILinkDecorations {
+ pointerCursor: boolean;
+ underline: boolean;
+}
+
+interface IBufferRange {
+ start: IBufferCellPosition;
+ end: IBufferCellPosition;
+}
+
+interface IBufferCellPosition {
+ x: number;
+ y: number;
+}
+
+export type CharacterJoinerHandler = (text: string) => [number, number][];
+
+export interface ICharacterJoiner {
+ id: number;
+ handler: CharacterJoinerHandler;
+}
+
+export interface IRenderDebouncer extends IDisposable {
+ refresh(rowStart: number | undefined, rowEnd: number | undefined, rowCount: number): void;
+}
diff --git a/node_modules/xterm/src/browser/Viewport.ts b/node_modules/xterm/src/browser/Viewport.ts
new file mode 100644
index 0000000..14fab89
--- /dev/null
+++ b/node_modules/xterm/src/browser/Viewport.ts
@@ -0,0 +1,294 @@
+/**
+ * Copyright (c) 2016 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { Disposable } from 'common/Lifecycle';
+import { addDisposableDomListener } from 'browser/Lifecycle';
+import { IColorSet, IViewport } from 'browser/Types';
+import { ICharSizeService, IRenderService } from 'browser/services/Services';
+import { IBufferService, IOptionsService } from 'common/services/Services';
+import { IBuffer } from 'common/buffer/Types';
+import { IRenderDimensions } from 'browser/renderer/Types';
+
+const FALLBACK_SCROLL_BAR_WIDTH = 15;
+
+/**
+ * Represents the viewport of a terminal, the visible area within the larger buffer of output.
+ * Logic for the virtual scroll bar is included in this object.
+ */
+export class Viewport extends Disposable implements IViewport {
+ public scrollBarWidth: number = 0;
+ private _currentRowHeight: number = 0;
+ private _currentScaledCellHeight: number = 0;
+ private _lastRecordedBufferLength: number = 0;
+ private _lastRecordedViewportHeight: number = 0;
+ private _lastRecordedBufferHeight: number = 0;
+ private _lastTouchY: number = 0;
+ private _lastScrollTop: number = 0;
+ private _lastHadScrollBar: boolean = false;
+ private _activeBuffer: IBuffer;
+ private _renderDimensions: IRenderDimensions;
+
+ // Stores a partial line amount when scrolling, this is used to keep track of how much of a line
+ // is scrolled so we can "scroll" over partial lines and feel natural on touchpads. This is a
+ // quick fix and could have a more robust solution in place that reset the value when needed.
+ private _wheelPartialScroll: number = 0;
+
+ private _refreshAnimationFrame: number | null = null;
+ private _ignoreNextScrollEvent: boolean = false;
+
+ constructor(
+ private readonly _scrollLines: (amount: number) => void,
+ private readonly _viewportElement: HTMLElement,
+ private readonly _scrollArea: HTMLElement,
+ private readonly _element: HTMLElement,
+ @IBufferService private readonly _bufferService: IBufferService,
+ @IOptionsService private readonly _optionsService: IOptionsService,
+ @ICharSizeService private readonly _charSizeService: ICharSizeService,
+ @IRenderService private readonly _renderService: IRenderService
+ ) {
+ super();
+
+ // Measure the width of the scrollbar. If it is 0 we can assume it's an OSX overlay scrollbar.
+ // 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
+ this._activeBuffer = this._bufferService.buffer;
+ this.register(this._bufferService.buffers.onBufferActivate(e => this._activeBuffer = e.activeBuffer));
+ this._renderDimensions = this._renderService.dimensions;
+ this.register(this._renderService.onDimensionsChange(e => this._renderDimensions = e));
+
+ // Perform this async to ensure the ICharSizeService is ready.
+ setTimeout(() => this.syncScrollArea(), 0);
+ }
+
+ public onThemeChange(colors: IColorSet): void {
+ this._viewportElement.style.backgroundColor = colors.background.css;
+ }
+
+ /**
+ * Refreshes row height, setting line-height, viewport height and scroll area height if
+ * necessary.
+ */
+ private _refresh(immediate: boolean): void {
+ if (immediate) {
+ this._innerRefresh();
+ if (this._refreshAnimationFrame !== null) {
+ cancelAnimationFrame(this._refreshAnimationFrame);
+ }
+ return;
+ }
+ if (this._refreshAnimationFrame === null) {
+ this._refreshAnimationFrame = requestAnimationFrame(() => this._innerRefresh());
+ }
+ }
+
+ private _innerRefresh(): void {
+ if (this._charSizeService.height > 0) {
+ this._currentRowHeight = this._renderService.dimensions.scaledCellHeight / window.devicePixelRatio;
+ this._currentScaledCellHeight = this._renderService.dimensions.scaledCellHeight;
+ this._lastRecordedViewportHeight = this._viewportElement.offsetHeight;
+ const newBufferHeight = Math.round(this._currentRowHeight * this._lastRecordedBufferLength) + (this._lastRecordedViewportHeight - this._renderService.dimensions.canvasHeight);
+ if (this._lastRecordedBufferHeight !== newBufferHeight) {
+ this._lastRecordedBufferHeight = newBufferHeight;
+ this._scrollArea.style.height = this._lastRecordedBufferHeight + 'px';
+ }
+ }
+
+ // Sync scrollTop
+ const scrollTop = this._bufferService.buffer.ydisp * this._currentRowHeight;
+ if (this._viewportElement.scrollTop !== scrollTop) {
+ // Ignore the next scroll event which will be triggered by setting the scrollTop as we do not
+ // want this event to scroll the terminal
+ this._ignoreNextScrollEvent = true;
+ 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;
+ }
+
+ /**
+ * Updates dimensions and synchronizes the scroll area if necessary.
+ */
+ public syncScrollArea(immediate: boolean = false): void {
+ // If buffer height changed
+ if (this._lastRecordedBufferLength !== this._bufferService.buffer.lines.length) {
+ this._lastRecordedBufferLength = this._bufferService.buffer.lines.length;
+ this._refresh(immediate);
+ return;
+ }
+
+ // If viewport height changed
+ if (this._lastRecordedViewportHeight !== this._renderService.dimensions.canvasHeight) {
+ this._refresh(immediate);
+ return;
+ }
+
+ // If the buffer position doesn't match last scroll top
+ if (this._lastScrollTop !== this._activeBuffer.ydisp * this._currentRowHeight) {
+ this._refresh(immediate);
+ return;
+ }
+
+ // If row height changed
+ if (this._renderDimensions.scaledCellHeight !== this._currentScaledCellHeight) {
+ this._refresh(immediate);
+ return;
+ }
+
+ // If the scroll bar visibility changed
+ if (this._lastHadScrollBar !== (this._optionsService.rawOptions.scrollback > 0)) {
+ this._refresh(immediate);
+ }
+ }
+
+ /**
+ * Handles scroll events on the viewport, calculating the new viewport and requesting the
+ * terminal to scroll to it.
+ * @param ev The scroll event.
+ */
+ private _onScroll(ev: Event): void {
+ // Record current scroll top position
+ this._lastScrollTop = this._viewportElement.scrollTop;
+
+ // Don't attempt to scroll if the element is not visible, otherwise scrollTop will be corrupt
+ // which causes the terminal to scroll the buffer to the top
+ if (!this._viewportElement.offsetParent) {
+ return;
+ }
+
+ // Ignore the event if it was flagged to ignore (when the source of the event is from Viewport)
+ if (this._ignoreNextScrollEvent) {
+ this._ignoreNextScrollEvent = false;
+ // Still trigger the scroll so lines get refreshed
+ this._scrollLines(0);
+ return;
+ }
+
+ const newRow = Math.round(this._lastScrollTop / this._currentRowHeight);
+ const diff = newRow - this._bufferService.buffer.ydisp;
+ this._scrollLines(diff);
+ }
+
+ /**
+ * Handles bubbling of scroll event in case the viewport has reached top or bottom
+ * @param ev The scroll event.
+ * @param amount The amount scrolled
+ */
+ private _bubbleScroll(ev: Event, amount: number): boolean {
+ const scrollPosFromTop = this._viewportElement.scrollTop + this._lastRecordedViewportHeight;
+ if ((amount < 0 && this._viewportElement.scrollTop !== 0) ||
+ (amount > 0 && scrollPosFromTop < this._lastRecordedBufferHeight)) {
+ if (ev.cancelable) {
+ ev.preventDefault();
+ }
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Handles mouse wheel events by adjusting the viewport's scrollTop and delegating the actual
+ * scrolling to `onScroll`, this event needs to be attached manually by the consumer of
+ * `Viewport`.
+ * @param ev The mouse wheel event.
+ */
+ public onWheel(ev: WheelEvent): boolean {
+ const amount = this._getPixelsScrolled(ev);
+ if (amount === 0) {
+ return false;
+ }
+ this._viewportElement.scrollTop += amount;
+ return this._bubbleScroll(ev, amount);
+ }
+
+ private _getPixelsScrolled(ev: WheelEvent): number {
+ // Do nothing if it's not a vertical scroll event
+ if (ev.deltaY === 0 || ev.shiftKey) {
+ return 0;
+ }
+
+ // Fallback to WheelEvent.DOM_DELTA_PIXEL
+ let amount = this._applyScrollModifier(ev.deltaY, ev);
+ if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) {
+ amount *= this._currentRowHeight;
+ } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
+ amount *= this._currentRowHeight * this._bufferService.rows;
+ }
+ return amount;
+ }
+
+ /**
+ * Gets the number of pixels scrolled by the mouse event taking into account what type of delta
+ * is being used.
+ * @param ev The mouse wheel event.
+ */
+ public getLinesScrolled(ev: WheelEvent): number {
+ // Do nothing if it's not a vertical scroll event
+ if (ev.deltaY === 0 || ev.shiftKey) {
+ return 0;
+ }
+
+ // Fallback to WheelEvent.DOM_DELTA_LINE
+ let amount = this._applyScrollModifier(ev.deltaY, ev);
+ if (ev.deltaMode === WheelEvent.DOM_DELTA_PIXEL) {
+ amount /= this._currentRowHeight + 0.0; // Prevent integer division
+ this._wheelPartialScroll += amount;
+ amount = Math.floor(Math.abs(this._wheelPartialScroll)) * (this._wheelPartialScroll > 0 ? 1 : -1);
+ this._wheelPartialScroll %= 1;
+ } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
+ amount *= this._bufferService.rows;
+ }
+ return amount;
+ }
+
+ private _applyScrollModifier(amount: number, ev: WheelEvent): number {
+ const modifier = this._optionsService.rawOptions.fastScrollModifier;
+ // Multiply the scroll speed when the modifier is down
+ if ((modifier === 'alt' && ev.altKey) ||
+ (modifier === 'ctrl' && ev.ctrlKey) ||
+ (modifier === 'shift' && ev.shiftKey)) {
+ return amount * this._optionsService.rawOptions.fastScrollSensitivity * this._optionsService.rawOptions.scrollSensitivity;
+ }
+
+ return amount * this._optionsService.rawOptions.scrollSensitivity;
+ }
+
+ /**
+ * Handles the touchstart event, recording the touch occurred.
+ * @param ev The touch event.
+ */
+ public onTouchStart(ev: TouchEvent): void {
+ this._lastTouchY = ev.touches[0].pageY;
+ }
+
+ /**
+ * Handles the touchmove event, scrolling the viewport if the position shifted.
+ * @param ev The touch event.
+ */
+ public onTouchMove(ev: TouchEvent): boolean {
+ const deltaY = this._lastTouchY - ev.touches[0].pageY;
+ this._lastTouchY = ev.touches[0].pageY;
+ if (deltaY === 0) {
+ return false;
+ }
+ this._viewportElement.scrollTop += deltaY;
+ return this._bubbleScroll(ev, deltaY);
+ }
+}
diff --git a/node_modules/xterm/src/browser/input/CompositionHelper.ts b/node_modules/xterm/src/browser/input/CompositionHelper.ts
new file mode 100644
index 0000000..61051b5
--- /dev/null
+++ b/node_modules/xterm/src/browser/input/CompositionHelper.ts
@@ -0,0 +1,237 @@
+/**
+ * Copyright (c) 2016 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IRenderService } from 'browser/services/Services';
+import { IBufferService, ICoreService, IOptionsService } from 'common/services/Services';
+
+interface IPosition {
+ start: number;
+ end: number;
+}
+
+/**
+ * Encapsulates the logic for handling compositionstart, compositionupdate and compositionend
+ * events, displaying the in-progress composition to the UI and forwarding the final composition
+ * to the handler.
+ */
+export class CompositionHelper {
+ /**
+ * Whether input composition is currently happening, eg. via a mobile keyboard, speech input or
+ * IME. This variable determines whether the compositionText should be displayed on the UI.
+ */
+ private _isComposing: boolean;
+ public get isComposing(): boolean { return this._isComposing; }
+
+ /**
+ * The position within the input textarea's value of the current composition.
+ */
+ private _compositionPosition: IPosition;
+
+ /**
+ * Whether a composition is in the process of being sent, setting this to false will cancel any
+ * in-progress composition.
+ */
+ private _isSendingComposition: boolean;
+
+ /**
+ * Data already sent due to keydown event.
+ */
+ private _dataAlreadySent: string;
+
+ constructor(
+ private readonly _textarea: HTMLTextAreaElement,
+ private readonly _compositionView: HTMLElement,
+ @IBufferService private readonly _bufferService: IBufferService,
+ @IOptionsService private readonly _optionsService: IOptionsService,
+ @ICoreService private readonly _coreService: ICoreService,
+ @IRenderService private readonly _renderService: IRenderService
+ ) {
+ this._isComposing = false;
+ this._isSendingComposition = false;
+ this._compositionPosition = { start: 0, end: 0 };
+ this._dataAlreadySent = '';
+ }
+
+ /**
+ * Handles the compositionstart event, activating the composition view.
+ */
+ public compositionstart(): void {
+ this._isComposing = true;
+ this._compositionPosition.start = this._textarea.value.length;
+ this._compositionView.textContent = '';
+ this._dataAlreadySent = '';
+ this._compositionView.classList.add('active');
+ }
+
+ /**
+ * Handles the compositionupdate event, updating the composition view.
+ * @param ev The event.
+ */
+ public compositionupdate(ev: Pick<CompositionEvent, 'data'>): void {
+ this._compositionView.textContent = ev.data;
+ this.updateCompositionElements();
+ setTimeout(() => {
+ this._compositionPosition.end = this._textarea.value.length;
+ }, 0);
+ }
+
+ /**
+ * Handles the compositionend event, hiding the composition view and sending the composition to
+ * the handler.
+ */
+ public compositionend(): void {
+ this._finalizeComposition(true);
+ }
+
+ /**
+ * Handles the keydown event, routing any necessary events to the CompositionHelper functions.
+ * @param ev The keydown event.
+ * @return Whether the Terminal should continue processing the keydown event.
+ */
+ public keydown(ev: KeyboardEvent): boolean {
+ if (this._isComposing || this._isSendingComposition) {
+ if (ev.keyCode === 229) {
+ // Continue composing if the keyCode is the "composition character"
+ return false;
+ }
+ if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) {
+ // Continue composing if the keyCode is a modifier key
+ return false;
+ }
+ // Finish composition immediately. This is mainly here for the case where enter is
+ // pressed and the handler needs to be triggered before the command is executed.
+ this._finalizeComposition(false);
+ }
+
+ if (ev.keyCode === 229) {
+ // If the "composition character" is used but gets to this point it means a non-composition
+ // character (eg. numbers and punctuation) was pressed when the IME was active.
+ this._handleAnyTextareaChanges();
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Finalizes the composition, resuming regular input actions. This is called when a composition
+ * is ending.
+ * @param waitForPropagation Whether to wait for events to propagate before sending
+ * the input. This should be false if a non-composition keystroke is entered before the
+ * compositionend event is triggered, such as enter, so that the composition is sent before
+ * the command is executed.
+ */
+ private _finalizeComposition(waitForPropagation: boolean): void {
+ this._compositionView.classList.remove('active');
+ this._isComposing = false;
+
+ if (!waitForPropagation) {
+ // Cancel any delayed composition send requests and send the input immediately.
+ this._isSendingComposition = false;
+ const input = this._textarea.value.substring(this._compositionPosition.start, this._compositionPosition.end);
+ this._coreService.triggerDataEvent(input, true);
+ } else {
+ // Make a deep copy of the composition position here as a new compositionstart event may
+ // fire before the setTimeout executes.
+ const currentCompositionPosition = {
+ start: this._compositionPosition.start,
+ end: this._compositionPosition.end
+ };
+
+ // Since composition* events happen before the changes take place in the textarea on most
+ // browsers, use a setTimeout with 0ms time to allow the native compositionend event to
+ // complete. This ensures the correct character is retrieved.
+ // This solution was used because:
+ // - The compositionend event's data property is unreliable, at least on Chromium
+ // - The last compositionupdate event's data property does not always accurately describe
+ // the character, a counter example being Korean where an ending consonsant can move to
+ // the following character if the following input is a vowel.
+ this._isSendingComposition = true;
+ setTimeout(() => {
+ // Ensure that the input has not already been sent
+ if (this._isSendingComposition) {
+ this._isSendingComposition = false;
+ let input;
+ // Add length of data already sent due to keydown event,
+ // otherwise input characters can be duplicated. (Issue #3191)
+ currentCompositionPosition.start += this._dataAlreadySent.length;
+ if (this._isComposing) {
+ // Use the end position to get the string if a new composition has started.
+ input = this._textarea.value.substring(currentCompositionPosition.start, currentCompositionPosition.end);
+ } else {
+ // Don't use the end position here in order to pick up any characters after the
+ // composition has finished, for example when typing a non-composition character
+ // (eg. 2) after a composition character.
+ input = this._textarea.value.substring(currentCompositionPosition.start);
+ }
+ if (input.length > 0) {
+ this._coreService.triggerDataEvent(input, true);
+ }
+ }
+ }, 0);
+ }
+ }
+
+ /**
+ * Apply any changes made to the textarea after the current event chain is allowed to complete.
+ * This should be called when not currently composing but a keydown event with the "composition
+ * character" (229) is triggered, in order to allow non-composition text to be entered when an
+ * IME is active.
+ */
+ private _handleAnyTextareaChanges(): void {
+ const oldValue = this._textarea.value;
+ setTimeout(() => {
+ // Ignore if a composition has started since the timeout
+ if (!this._isComposing) {
+ const newValue = this._textarea.value;
+ const diff = newValue.replace(oldValue, '');
+ if (diff.length > 0) {
+ this._dataAlreadySent = diff;
+ this._coreService.triggerDataEvent(diff, true);
+ }
+ }
+ }, 0);
+ }
+
+ /**
+ * Positions the composition view on top of the cursor and the textarea just below it (so the
+ * IME helper dialog is positioned correctly).
+ * @param dontRecurse Whether to use setTimeout to recursively trigger another update, this is
+ * necessary as the IME events across browsers are not consistently triggered.
+ */
+ public updateCompositionElements(dontRecurse?: boolean): void {
+ if (!this._isComposing) {
+ return;
+ }
+
+ if (this._bufferService.buffer.isCursorInViewport) {
+ const cursorX = Math.min(this._bufferService.buffer.x, this._bufferService.cols - 1);
+
+ const cellHeight = this._renderService.dimensions.actualCellHeight;
+ const cursorTop = this._bufferService.buffer.y * this._renderService.dimensions.actualCellHeight;
+ const cursorLeft = cursorX * this._renderService.dimensions.actualCellWidth;
+
+ this._compositionView.style.left = cursorLeft + 'px';
+ this._compositionView.style.top = cursorTop + 'px';
+ this._compositionView.style.height = cellHeight + 'px';
+ this._compositionView.style.lineHeight = cellHeight + 'px';
+ this._compositionView.style.fontFamily = this._optionsService.rawOptions.fontFamily;
+ this._compositionView.style.fontSize = this._optionsService.rawOptions.fontSize + 'px';
+ // Sync the textarea to the exact position of the composition view so the IME knows where the
+ // text is.
+ const compositionViewBounds = this._compositionView.getBoundingClientRect();
+ this._textarea.style.left = cursorLeft + 'px';
+ this._textarea.style.top = cursorTop + 'px';
+ // Ensure the text area is at least 1x1, otherwise certain IMEs may break
+ this._textarea.style.width = Math.max(compositionViewBounds.width, 1) + 'px';
+ this._textarea.style.height = Math.max(compositionViewBounds.height, 1) + 'px';
+ this._textarea.style.lineHeight = compositionViewBounds.height + 'px';
+ }
+
+ if (!dontRecurse) {
+ setTimeout(() => this.updateCompositionElements(true), 0);
+ }
+ }
+}
diff --git a/node_modules/xterm/src/browser/input/Mouse.ts b/node_modules/xterm/src/browser/input/Mouse.ts
new file mode 100644
index 0000000..2986fb3
--- /dev/null
+++ b/node_modules/xterm/src/browser/input/Mouse.ts
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+export function getCoordsRelativeToElement(event: {clientX: number, clientY: number}, element: HTMLElement): [number, number] {
+ const rect = element.getBoundingClientRect();
+ return [event.clientX - rect.left, event.clientY - rect.top];
+}
+
+/**
+ * Gets coordinates within the terminal for a particular mouse event. The result
+ * is returned as an array in the form [x, y] instead of an object as it's a
+ * little faster and this function is used in some low level code.
+ * @param event The mouse event.
+ * @param element The terminal's container element.
+ * @param colCount The number of columns in the terminal.
+ * @param rowCount The number of rows n the terminal.
+ * @param isSelection Whether the request is for the selection or not. This will
+ * 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 {
+ // Coordinates cannot be measured if there are no valid
+ if (!hasValidCharSize) {
+ return undefined;
+ }
+
+ const coords = getCoordsRelativeToElement(event, element);
+ if (!coords) {
+ return undefined;
+ }
+
+ coords[0] = Math.ceil((coords[0] + (isSelection ? actualCellWidth / 2 : 0)) / actualCellWidth);
+ coords[1] = Math.ceil(coords[1] / actualCellHeight);
+
+ // Ensure coordinates are within the terminal viewport. Note that selections
+ // need an addition point of precision to cover the end point (as characters
+ // cover half of one char and half of the next).
+ coords[0] = Math.min(Math.max(coords[0], 1), colCount + (isSelection ? 1 : 0));
+ coords[1] = Math.min(Math.max(coords[1], 1), rowCount);
+
+ return coords;
+}
+
+/**
+ * Gets coordinates within the terminal for a particular mouse event, wrapping
+ * them to the bounds of the terminal and adding 32 to both the x and y values
+ * as expected by xterm.
+ */
+export function getRawByteCoords(coords: [number, number] | undefined): { x: number, y: number } | undefined {
+ if (!coords) {
+ return undefined;
+ }
+
+ // xterm sends raw bytes and starts at 32 (SP) for each.
+ return { x: coords[0] + 32, y: coords[1] + 32 };
+}
diff --git a/node_modules/xterm/src/browser/input/MoveToCell.ts b/node_modules/xterm/src/browser/input/MoveToCell.ts
new file mode 100644
index 0000000..82e767c
--- /dev/null
+++ b/node_modules/xterm/src/browser/input/MoveToCell.ts
@@ -0,0 +1,249 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { C0 } from 'common/data/EscapeSequences';
+import { IBufferService } from 'common/services/Services';
+
+const enum Direction {
+ UP = 'A',
+ DOWN = 'B',
+ RIGHT = 'C',
+ LEFT = 'D'
+}
+
+/**
+ * Concatenates all the arrow sequences together.
+ * Resets the starting row to an unwrapped row, moves to the requested row,
+ * then moves to requested col.
+ */
+export function moveToCellSequence(targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string {
+ const startX = bufferService.buffer.x;
+ const startY = bufferService.buffer.y;
+
+ // The alt buffer should try to navigate between rows
+ if (!bufferService.buffer.hasScrollback) {
+ return resetStartingRow(startX, startY, targetX, targetY, bufferService, applicationCursor) +
+ moveToRequestedRow(startY, targetY, bufferService, applicationCursor) +
+ moveToRequestedCol(startX, startY, targetX, targetY, bufferService, applicationCursor);
+ }
+
+ // Only move horizontally for the normal buffer
+ let direction;
+ if (startY === targetY) {
+ direction = startX > targetX ? Direction.LEFT : Direction.RIGHT;
+ return repeat(Math.abs(startX - targetX), sequence(direction, applicationCursor));
+ }
+ direction = startY > targetY ? Direction.LEFT : Direction.RIGHT;
+ const rowDifference = Math.abs(startY - targetY);
+ const cellsToMove = colsFromRowEnd(startY > targetY ? targetX : startX, bufferService) +
+ (rowDifference - 1) * bufferService.cols + 1 /* wrap around 1 row */ +
+ colsFromRowBeginning(startY > targetY ? startX : targetX, bufferService);
+ return repeat(cellsToMove, sequence(direction, applicationCursor));
+}
+
+/**
+ * Find the number of cols from a row beginning to a col.
+ */
+function colsFromRowBeginning(currX: number, bufferService: IBufferService): number {
+ return currX - 1;
+}
+
+/**
+ * Find the number of cols from a col to row end.
+ */
+function colsFromRowEnd(currX: number, bufferService: IBufferService): number {
+ return bufferService.cols - currX;
+}
+
+/**
+ * If the initial position of the cursor is on a row that is wrapped, move the
+ * cursor up to the first row that is not wrapped to have accurate vertical
+ * positioning.
+ */
+function resetStartingRow(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string {
+ if (moveToRequestedRow(startY, targetY, bufferService, applicationCursor).length === 0) {
+ return '';
+ }
+ return repeat(bufferLine(
+ startX, startY, startX,
+ startY - wrappedRowsForRow(bufferService, startY), false, bufferService
+ ).length, sequence(Direction.LEFT, applicationCursor));
+}
+
+/**
+ * Using the reset starting and ending row, move to the requested row,
+ * ignoring wrapped rows
+ */
+function moveToRequestedRow(startY: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string {
+ const startRow = startY - wrappedRowsForRow(bufferService, startY);
+ const endRow = targetY - wrappedRowsForRow(bufferService, targetY);
+
+ const rowsToMove = Math.abs(startRow - endRow) - wrappedRowsCount(startY, targetY, bufferService);
+
+ return repeat(rowsToMove, sequence(verticalDirection(startY, targetY), applicationCursor));
+}
+
+/**
+ * Move to the requested col on the ending row
+ */
+function moveToRequestedCol(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): string {
+ let startRow;
+ if (moveToRequestedRow(startY, targetY, bufferService, applicationCursor).length > 0) {
+ startRow = targetY - wrappedRowsForRow(bufferService, targetY);
+ } else {
+ startRow = startY;
+ }
+
+ const endRow = targetY;
+ const direction = horizontalDirection(startX, startY, targetX, targetY, bufferService, applicationCursor);
+
+ return repeat(bufferLine(
+ startX, startRow, targetX, endRow,
+ direction === Direction.RIGHT, bufferService
+ ).length, sequence(direction, applicationCursor));
+}
+
+/**
+ * Utility functions
+ */
+
+/**
+ * Calculates the number of wrapped rows between the unwrapped starting and
+ * ending rows. These rows need to ignored since the cursor skips over them.
+ */
+function wrappedRowsCount(startY: number, targetY: number, bufferService: IBufferService): number {
+ let wrappedRows = 0;
+ const startRow = startY - wrappedRowsForRow(bufferService, startY);
+ const endRow = targetY - wrappedRowsForRow(bufferService, targetY);
+
+ for (let i = 0; i < Math.abs(startRow - endRow); i++) {
+ const direction = verticalDirection(startY, targetY) === Direction.UP ? -1 : 1;
+ const line = bufferService.buffer.lines.get(startRow + (direction * i));
+ if (line?.isWrapped) {
+ wrappedRows++;
+ }
+ }
+
+ return wrappedRows;
+}
+
+/**
+ * Calculates the number of wrapped rows that make up a given row.
+ * @param currentRow The row to determine how many wrapped rows make it up
+ */
+function wrappedRowsForRow(bufferService: IBufferService, currentRow: number): number {
+ let rowCount = 0;
+ let line = bufferService.buffer.lines.get(currentRow);
+ let lineWraps = line?.isWrapped;
+
+ while (lineWraps && currentRow >= 0 && currentRow < bufferService.rows) {
+ rowCount++;
+ line = bufferService.buffer.lines.get(--currentRow);
+ lineWraps = line?.isWrapped;
+ }
+
+ return rowCount;
+}
+
+/**
+ * Direction determiners
+ */
+
+/**
+ * Determines if the right or left arrow is needed
+ */
+function horizontalDirection(startX: number, startY: number, targetX: number, targetY: number, bufferService: IBufferService, applicationCursor: boolean): Direction {
+ let startRow;
+ if (moveToRequestedRow(targetX, targetY, bufferService, applicationCursor).length > 0) {
+ startRow = targetY - wrappedRowsForRow(bufferService, targetY);
+ } else {
+ startRow = startY;
+ }
+
+ if ((startX < targetX &&
+ startRow <= targetY) || // down/right or same y/right
+ (startX >= targetX &&
+ startRow < targetY)) { // down/left or same y/left
+ return Direction.RIGHT;
+ }
+ return Direction.LEFT;
+}
+
+/**
+ * Determines if the up or down arrow is needed
+ */
+function verticalDirection(startY: number, targetY: number): Direction {
+ return startY > targetY ? Direction.UP : Direction.DOWN;
+}
+
+/**
+ * Constructs the string of chars in the buffer from a starting row and col
+ * to an ending row and col
+ * @param startCol The starting column position
+ * @param startRow The starting row position
+ * @param endCol The ending column position
+ * @param endRow The ending row position
+ * @param forward Direction to move
+ */
+function bufferLine(
+ startCol: number,
+ startRow: number,
+ endCol: number,
+ endRow: number,
+ forward: boolean,
+ bufferService: IBufferService
+): string {
+ let currentCol = startCol;
+ let currentRow = startRow;
+ let bufferStr = '';
+
+ while (currentCol !== endCol || currentRow !== endRow) {
+ currentCol += forward ? 1 : -1;
+
+ if (forward && currentCol > bufferService.cols - 1) {
+ bufferStr += bufferService.buffer.translateBufferLineToString(
+ currentRow, false, startCol, currentCol
+ );
+ currentCol = 0;
+ startCol = 0;
+ currentRow++;
+ } else if (!forward && currentCol < 0) {
+ bufferStr += bufferService.buffer.translateBufferLineToString(
+ currentRow, false, 0, startCol + 1
+ );
+ currentCol = bufferService.cols - 1;
+ startCol = currentCol;
+ currentRow--;
+ }
+ }
+
+ return bufferStr + bufferService.buffer.translateBufferLineToString(
+ currentRow, false, startCol, currentCol
+ );
+}
+
+/**
+ * Constructs the escape sequence for clicking an arrow
+ * @param direction The direction to move
+ */
+function sequence(direction: Direction, applicationCursor: boolean): string {
+ const mod = applicationCursor ? 'O' : '[';
+ return C0.ESC + mod + direction;
+}
+
+/**
+ * Returns a string repeated a given number of times
+ * Polyfill from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat
+ * @param count The number of times to repeat the string
+ * @param string The string that is to be repeated
+ */
+function repeat(count: number, str: string): string {
+ count = Math.floor(count);
+ let rpt = '';
+ for (let i = 0; i < count; i++) {
+ rpt += str;
+ }
+ return rpt;
+}
diff --git a/node_modules/xterm/src/browser/public/Terminal.ts b/node_modules/xterm/src/browser/public/Terminal.ts
new file mode 100644
index 0000000..117805f
--- /dev/null
+++ b/node_modules/xterm/src/browser/public/Terminal.ts
@@ -0,0 +1,284 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { Terminal as ITerminalApi, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBufferNamespace as IBufferNamespaceApi, IParser, ILinkProvider, IUnicodeHandling, FontWeight, IModes } from 'xterm';
+import { ITerminal } from 'browser/Types';
+import { Terminal as TerminalCore } from 'browser/Terminal';
+import * as Strings from 'browser/LocalizableStrings';
+import { IEvent } from 'common/EventEmitter';
+import { ParserApi } from 'common/public/ParserApi';
+import { UnicodeApi } from 'common/public/UnicodeApi';
+import { AddonManager } from 'common/public/AddonManager';
+import { BufferNamespaceApi } from 'common/public/BufferNamespaceApi';
+import { ITerminalOptions } from 'common/Types';
+
+/**
+ * The set of options that only have an effect when set in the Terminal constructor.
+ */
+const CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows'];
+
+export class Terminal implements ITerminalApi {
+ private _core: ITerminal;
+ private _addonManager: AddonManager;
+ private _parser: IParser | undefined;
+ private _buffer: BufferNamespaceApi | undefined;
+ private _publicOptions: ITerminalOptions;
+
+ constructor(options?: ITerminalOptions) {
+ this._core = new TerminalCore(options);
+ this._addonManager = new AddonManager();
+
+ this._publicOptions = { ... this._core.options };
+ const getter = (propName: string): any => {
+ return this._core.options[propName];
+ };
+ const setter = (propName: string, value: any): void => {
+ this._checkReadonlyOptions(propName);
+ this._core.options[propName] = value;
+ };
+
+ for (const propName in this._core.options) {
+ const desc = {
+ get: getter.bind(this, propName),
+ set: setter.bind(this, propName)
+ };
+ Object.defineProperty(this._publicOptions, propName, desc);
+ }
+ }
+
+ private _checkReadonlyOptions(propName: string): void {
+ // Throw an error if any constructor only option is modified
+ // from terminal.options
+ // Modifications from anywhere else are allowed
+ if (CONSTRUCTOR_ONLY_OPTIONS.includes(propName)) {
+ throw new Error(`Option "${propName}" can only be set in the constructor`);
+ }
+ }
+
+ private _checkProposedApi(): void {
+ if (!this._core.optionsService.rawOptions.allowProposedApi) {
+ throw new Error('You must set the allowProposedApi option to true to use proposed API');
+ }
+ }
+
+ public get onBell(): IEvent<void> { return this._core.onBell; }
+ public get onBinary(): IEvent<string> { return this._core.onBinary; }
+ public get onCursorMove(): IEvent<void> { return this._core.onCursorMove; }
+ public get onData(): IEvent<string> { return this._core.onData; }
+ public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._core.onKey; }
+ public get onLineFeed(): IEvent<void> { return this._core.onLineFeed; }
+ public get onRender(): IEvent<{ start: number, end: number }> { return this._core.onRender; }
+ public get onResize(): IEvent<{ cols: number, rows: number }> { return this._core.onResize; }
+ 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 element(): HTMLElement | undefined { return this._core.element; }
+ public get parser(): IParser {
+ this._checkProposedApi();
+ if (!this._parser) {
+ this._parser = new ParserApi(this._core);
+ }
+ return this._parser;
+ }
+ public get unicode(): IUnicodeHandling {
+ this._checkProposedApi();
+ return new UnicodeApi(this._core);
+ }
+ public get textarea(): HTMLTextAreaElement | undefined { return this._core.textarea; }
+ public get rows(): number { return this._core.rows; }
+ public get cols(): number { return this._core.cols; }
+ public get buffer(): IBufferNamespaceApi {
+ this._checkProposedApi();
+ if (!this._buffer) {
+ this._buffer = new BufferNamespaceApi(this._core);
+ }
+ return this._buffer;
+ }
+ public get markers(): ReadonlyArray<IMarker> {
+ this._checkProposedApi();
+ return this._core.markers;
+ }
+ public get modes(): IModes {
+ const m = this._core.coreService.decPrivateModes;
+ let mouseTrackingMode: 'none' | 'x10' | 'vt200' | 'drag' | 'any' = 'none';
+ switch (this._core.coreMouseService.activeProtocol) {
+ case 'X10': mouseTrackingMode = 'x10'; break;
+ case 'VT200': mouseTrackingMode = 'vt200'; break;
+ case 'DRAG': mouseTrackingMode = 'drag'; break;
+ case 'ANY': mouseTrackingMode = 'any'; break;
+ }
+ return {
+ applicationCursorKeysMode: m.applicationCursorKeys,
+ applicationKeypadMode: m.applicationKeypad,
+ bracketedPasteMode: m.bracketedPasteMode,
+ insertMode: this._core.coreService.modes.insertMode,
+ mouseTrackingMode: mouseTrackingMode,
+ originMode: m.origin,
+ reverseWraparoundMode: m.reverseWraparound,
+ sendFocusMode: m.sendFocus,
+ wraparoundMode: m.wraparound
+ };
+ }
+ public get options(): ITerminalOptions {
+ return this._publicOptions;
+ }
+ public set options(options: ITerminalOptions) {
+ for (const propName in options) {
+ this._publicOptions[propName] = options[propName];
+ }
+ }
+ public blur(): void {
+ this._core.blur();
+ }
+ public focus(): void {
+ this._core.focus();
+ }
+ public resize(columns: number, rows: number): void {
+ this._verifyIntegers(columns, rows);
+ this._core.resize(columns, rows);
+ }
+ public open(parent: HTMLElement): void {
+ this._core.open(parent);
+ }
+ public attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {
+ this._core.attachCustomKeyEventHandler(customKeyEventHandler);
+ }
+ public registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number {
+ this._checkProposedApi();
+ return this._core.registerLinkMatcher(regex, handler, options);
+ }
+ public deregisterLinkMatcher(matcherId: number): void {
+ this._checkProposedApi();
+ this._core.deregisterLinkMatcher(matcherId);
+ }
+ public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
+ this._checkProposedApi();
+ return this._core.registerLinkProvider(linkProvider);
+ }
+ public registerCharacterJoiner(handler: (text: string) => [number, number][]): number {
+ this._checkProposedApi();
+ return this._core.registerCharacterJoiner(handler);
+ }
+ public deregisterCharacterJoiner(joinerId: number): void {
+ this._checkProposedApi();
+ this._core.deregisterCharacterJoiner(joinerId);
+ }
+ public registerMarker(cursorYOffset: number): IMarker | undefined {
+ this._checkProposedApi();
+ this._verifyIntegers(cursorYOffset);
+ return this._core.addMarker(cursorYOffset);
+ }
+ public addMarker(cursorYOffset: number): IMarker | undefined {
+ return this.registerMarker(cursorYOffset);
+ }
+ public hasSelection(): boolean {
+ return this._core.hasSelection();
+ }
+ public select(column: number, row: number, length: number): void {
+ this._verifyIntegers(column, row, length);
+ this._core.select(column, row, length);
+ }
+ public getSelection(): string {
+ return this._core.getSelection();
+ }
+ public getSelectionPosition(): ISelectionPosition | undefined {
+ return this._core.getSelectionPosition();
+ }
+ public clearSelection(): void {
+ this._core.clearSelection();
+ }
+ public selectAll(): void {
+ this._core.selectAll();
+ }
+ public selectLines(start: number, end: number): void {
+ this._verifyIntegers(start, end);
+ this._core.selectLines(start, end);
+ }
+ public dispose(): void {
+ this._addonManager.dispose();
+ this._core.dispose();
+ }
+ public scrollLines(amount: number): void {
+ this._verifyIntegers(amount);
+ this._core.scrollLines(amount);
+ }
+ public scrollPages(pageCount: number): void {
+ this._verifyIntegers(pageCount);
+ this._core.scrollPages(pageCount);
+ }
+ public scrollToTop(): void {
+ this._core.scrollToTop();
+ }
+ public scrollToBottom(): void {
+ this._core.scrollToBottom();
+ }
+ public scrollToLine(line: number): void {
+ this._verifyIntegers(line);
+ this._core.scrollToLine(line);
+ }
+ public clear(): void {
+ this._core.clear();
+ }
+ public write(data: string | Uint8Array, callback?: () => void): void {
+ this._core.write(data, callback);
+ }
+ public writeUtf8(data: Uint8Array, callback?: () => void): void {
+ this._core.write(data, callback);
+ }
+ public writeln(data: string | Uint8Array, callback?: () => void): void {
+ this._core.write(data);
+ this._core.write('\r\n', callback);
+ }
+ public paste(data: string): void {
+ this._core.paste(data);
+ }
+ public getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string;
+ public getOption(key: 'allowTransparency' | 'altClickMovesCursor' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'visualBell'): boolean;
+ public getOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number;
+ public getOption(key: 'fontWeight' | 'fontWeightBold'): FontWeight;
+ public getOption(key: string): any;
+ public getOption(key: any): any {
+ return this._core.optionsService.getOption(key);
+ }
+ public setOption(key: 'bellSound' | 'fontFamily' | 'termName' | 'wordSeparator', value: string): void;
+ public setOption(key: 'fontWeight' | 'fontWeightBold', value: 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number): void;
+ public setOption(key: 'logLevel', value: 'debug' | 'info' | 'warn' | 'error' | 'off'): void;
+ public setOption(key: 'bellStyle', value: 'none' | 'visual' | 'sound' | 'both'): void;
+ public setOption(key: 'cursorStyle', value: 'block' | 'underline' | 'bar'): void;
+ public setOption(key: 'allowTransparency' | 'altClickMovesCursor' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'visualBell', value: boolean): void;
+ public setOption(key: 'fontSize' | 'letterSpacing' | 'lineHeight' | 'tabStopWidth' | 'scrollback', value: number): void;
+ public setOption(key: 'theme', value: ITheme): void;
+ public setOption(key: 'cols' | 'rows', value: number): void;
+ public setOption(key: string, value: any): void;
+ public setOption(key: any, value: any): void {
+ this._checkReadonlyOptions(key);
+ this._core.optionsService.setOption(key, value);
+ }
+ public refresh(start: number, end: number): void {
+ this._verifyIntegers(start, end);
+ this._core.refresh(start, end);
+ }
+ public reset(): void {
+ this._core.reset();
+ }
+ public clearTextureAtlas(): void {
+ this._core.clearTextureAtlas();
+ }
+ public loadAddon(addon: ITerminalAddon): void {
+ return this._addonManager.loadAddon(this, addon);
+ }
+ public static get strings(): ILocalizableStrings {
+ return Strings;
+ }
+
+ private _verifyIntegers(...values: number[]): void {
+ for (const value of values) {
+ if (value === Infinity || isNaN(value) || value % 1 !== 0) {
+ throw new Error('This API only accepts integers');
+ }
+ }
+ }
+}
diff --git a/node_modules/xterm/src/browser/renderer/BaseRenderLayer.ts b/node_modules/xterm/src/browser/renderer/BaseRenderLayer.ts
new file mode 100644
index 0000000..629e943
--- /dev/null
+++ b/node_modules/xterm/src/browser/renderer/BaseRenderLayer.ts
@@ -0,0 +1,513 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IRenderDimensions, IRenderLayer } from 'browser/renderer/Types';
+import { ICellData } 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 { CellData } from 'common/buffer/CellData';
+import { IBufferService, IOptionsService } from 'common/services/Services';
+import { throwIfFalsy } from 'browser/renderer/RendererUtils';
+import { channels, color, rgba } from 'browser/Color';
+import { removeElementFromParent } from 'browser/Dom';
+import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs';
+
+export abstract class BaseRenderLayer implements IRenderLayer {
+ private _canvas: HTMLCanvasElement;
+ protected _ctx!: CanvasRenderingContext2D;
+ private _scaledCharWidth: number = 0;
+ private _scaledCharHeight: number = 0;
+ private _scaledCellWidth: number = 0;
+ private _scaledCellHeight: number = 0;
+ private _scaledCharLeft: number = 0;
+ private _scaledCharTop: number = 0;
+
+ protected _charAtlas: BaseCharAtlas | undefined;
+
+ /**
+ * An object that's reused when drawing glyphs in order to reduce GC.
+ */
+ private _currentGlyphIdentifier: IGlyphIdentifier = {
+ chars: '',
+ code: 0,
+ bg: 0,
+ fg: 0,
+ bold: false,
+ dim: false,
+ italic: false
+ };
+
+ constructor(
+ private _container: HTMLElement,
+ id: string,
+ zIndex: number,
+ private _alpha: boolean,
+ protected _colors: IColorSet,
+ private _rendererId: number,
+ protected readonly _bufferService: IBufferService,
+ protected readonly _optionsService: IOptionsService
+ ) {
+ this._canvas = document.createElement('canvas');
+ this._canvas.classList.add(`xterm-${id}-layer`);
+ this._canvas.style.zIndex = zIndex.toString();
+ this._initCanvas();
+ this._container.appendChild(this._canvas);
+ }
+
+ public dispose(): void {
+ removeElementFromParent(this._canvas);
+ this._charAtlas?.dispose();
+ }
+
+ private _initCanvas(): void {
+ this._ctx = throwIfFalsy(this._canvas.getContext('2d', { alpha: this._alpha }));
+ // Draw the background if this is an opaque layer
+ if (!this._alpha) {
+ this._clearAll();
+ }
+ }
+
+ public onOptionsChanged(): void {}
+ public onBlur(): void {}
+ 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 setColors(colorSet: IColorSet): void {
+ this._refreshCharAtlas(colorSet);
+ }
+
+ protected _setTransparency(alpha: boolean): void {
+ // Do nothing when alpha doesn't change
+ if (alpha === this._alpha) {
+ return;
+ }
+
+ // Create new canvas and replace old one
+ const oldCanvas = this._canvas;
+ this._alpha = alpha;
+ // Cloning preserves properties
+ this._canvas = this._canvas.cloneNode() as HTMLCanvasElement;
+ this._initCanvas();
+ this._container.replaceChild(this._canvas, oldCanvas);
+
+ // Regenerate char atlas and force a full redraw
+ this._refreshCharAtlas(this._colors);
+ this.onGridChanged(0, this._bufferService.rows - 1);
+ }
+
+ /**
+ * Refreshes the char atlas, aquiring a new one if necessary.
+ * @param colorSet The color set to use for the char atlas.
+ */
+ private _refreshCharAtlas(colorSet: IColorSet): void {
+ if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) {
+ return;
+ }
+ this._charAtlas = acquireCharAtlas(this._optionsService.rawOptions, this._rendererId, colorSet, this._scaledCharWidth, this._scaledCharHeight);
+ this._charAtlas.warmUp();
+ }
+
+ public resize(dim: IRenderDimensions): void {
+ this._scaledCellWidth = dim.scaledCellWidth;
+ this._scaledCellHeight = dim.scaledCellHeight;
+ this._scaledCharWidth = dim.scaledCharWidth;
+ this._scaledCharHeight = dim.scaledCharHeight;
+ this._scaledCharLeft = dim.scaledCharLeft;
+ this._scaledCharTop = dim.scaledCharTop;
+ this._canvas.width = dim.scaledCanvasWidth;
+ this._canvas.height = dim.scaledCanvasHeight;
+ this._canvas.style.width = `${dim.canvasWidth}px`;
+ this._canvas.style.height = `${dim.canvasHeight}px`;
+
+ // Draw the background if this is an opaque layer
+ if (!this._alpha) {
+ this._clearAll();
+ }
+
+ this._refreshCharAtlas(this._colors);
+ }
+
+ public abstract reset(): void;
+
+ public clearTextureAtlas(): void {
+ this._charAtlas?.clear();
+ }
+
+ /**
+ * Fills 1+ cells completely. This uses the existing fillStyle on the context.
+ * @param x The column to start at.
+ * @param y The row to start at
+ * @param width The number of columns to fill.
+ * @param height The number of rows to fill.
+ */
+ protected _fillCells(x: number, y: number, width: number, height: number): void {
+ this._ctx.fillRect(
+ x * this._scaledCellWidth,
+ y * this._scaledCellHeight,
+ width * this._scaledCellWidth,
+ height * this._scaledCellHeight);
+ }
+
+ /**
+ * Fills a 1px line (2px on HDPI) at the middle of the cell. This uses the
+ * existing fillStyle on the context.
+ * @param x The column to fill.
+ * @param y The row to fill.
+ */
+ protected _fillMiddleLineAtCells(x: number, y: number, width: number = 1): void {
+ const cellOffset = Math.ceil(this._scaledCellHeight * 0.5);
+ this._ctx.fillRect(
+ x * this._scaledCellWidth,
+ (y + 1) * this._scaledCellHeight - cellOffset - window.devicePixelRatio,
+ width * this._scaledCellWidth,
+ window.devicePixelRatio);
+ }
+
+ /**
+ * Fills a 1px line (2px on HDPI) at the bottom of the cell. This uses the
+ * existing fillStyle on the context.
+ * @param x The column to fill.
+ * @param y The row to fill.
+ */
+ protected _fillBottomLineAtCells(x: number, y: number, width: number = 1): void {
+ this._ctx.fillRect(
+ x * this._scaledCellWidth,
+ (y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */,
+ width * this._scaledCellWidth,
+ window.devicePixelRatio);
+ }
+
+ /**
+ * Fills a 1px line (2px on HDPI) at the left of the cell. This uses the
+ * existing fillStyle on the context.
+ * @param x The column to fill.
+ * @param y The row to fill.
+ */
+ protected _fillLeftLineAtCell(x: number, y: number, width: number): void {
+ this._ctx.fillRect(
+ x * this._scaledCellWidth,
+ y * this._scaledCellHeight,
+ window.devicePixelRatio * width,
+ this._scaledCellHeight);
+ }
+
+ /**
+ * Strokes a 1px rectangle (2px on HDPI) around a cell. This uses the existing
+ * strokeStyle on the context.
+ * @param x The column to fill.
+ * @param y The row to fill.
+ */
+ protected _strokeRectAtCell(x: number, y: number, width: number, height: number): void {
+ this._ctx.lineWidth = window.devicePixelRatio;
+ this._ctx.strokeRect(
+ x * this._scaledCellWidth + window.devicePixelRatio / 2,
+ y * this._scaledCellHeight + (window.devicePixelRatio / 2),
+ width * this._scaledCellWidth - window.devicePixelRatio,
+ (height * this._scaledCellHeight) - window.devicePixelRatio);
+ }
+
+ /**
+ * Clears the entire canvas.
+ */
+ protected _clearAll(): void {
+ if (this._alpha) {
+ this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
+ } else {
+ this._ctx.fillStyle = this._colors.background.css;
+ this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height);
+ }
+ }
+
+ /**
+ * Clears 1+ cells completely.
+ * @param x The column to start at.
+ * @param y The row to start at.
+ * @param width The number of columns to clear.
+ * @param height The number of rows to clear.
+ */
+ protected _clearCells(x: number, y: number, width: number, height: number): void {
+ if (this._alpha) {
+ this._ctx.clearRect(
+ x * this._scaledCellWidth,
+ y * this._scaledCellHeight,
+ width * this._scaledCellWidth,
+ height * this._scaledCellHeight);
+ } else {
+ this._ctx.fillStyle = this._colors.background.css;
+ this._ctx.fillRect(
+ x * this._scaledCellWidth,
+ y * this._scaledCellHeight,
+ width * this._scaledCellWidth,
+ height * this._scaledCellHeight);
+ }
+ }
+
+ /**
+ * Draws a truecolor character at the cell. The character will be clipped to
+ * ensure that it fits with the cell, including the cell to the right if it's
+ * a wide character. This uses the existing fillStyle on the context.
+ * @param cell The cell data for the character to draw.
+ * @param x The column to draw at.
+ * @param y The row to draw at.
+ * @param color The color of the character.
+ */
+ protected _fillCharTrueColor(cell: CellData, x: number, y: number): void {
+ this._ctx.font = this._getFont(false, false);
+ this._ctx.textBaseline = TEXT_BASELINE;
+ this._clipRow(y);
+
+ // Draw custom characters if applicable
+ let drawSuccess = false;
+ if (this._optionsService.rawOptions.customGlyphs !== false) {
+ drawSuccess = tryDrawCustomChar(this._ctx, cell.getChars(), x * this._scaledCellWidth, y * this._scaledCellHeight, this._scaledCellWidth, this._scaledCellHeight);
+ }
+
+ // Draw the character
+ if (!drawSuccess) {
+ this._ctx.fillText(
+ cell.getChars(),
+ x * this._scaledCellWidth + this._scaledCharLeft,
+ y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight);
+ }
+ }
+
+ /**
+ * Draws one or more characters at a cell. If possible this will draw using
+ * the character atlas to reduce draw time.
+ * @param chars The character or characters.
+ * @param code The character code.
+ * @param width The width of the characters.
+ * @param x The column to draw at.
+ * @param y The row to draw at.
+ * @param fg The foreground color, in the format stored within the attributes.
+ * @param bg The background color, in the format stored within the attributes.
+ * This is used to validate whether a cached image can be used.
+ * @param bold Whether the text is bold.
+ */
+ protected _drawChars(cell: ICellData, x: number, y: number): void {
+ const contrastColor = this._getContrastColor(cell);
+
+ // skip cache right away if we draw in RGB
+ // Note: to avoid bad runtime JoinedCellData will be skipped
+ // in the cache handler itself (atlasDidDraw == false) and
+ // fall through to uncached later down below
+ if (contrastColor || cell.isFgRGB() || cell.isBgRGB()) {
+ this._drawUncachedChars(cell, x, y, contrastColor);
+ return;
+ }
+
+ let fg;
+ let bg;
+ if (cell.isInverse()) {
+ fg = (cell.isBgDefault()) ? INVERTED_DEFAULT_COLOR : cell.getBgColor();
+ bg = (cell.isFgDefault()) ? INVERTED_DEFAULT_COLOR : cell.getFgColor();
+ } else {
+ bg = (cell.isBgDefault()) ? DEFAULT_COLOR : cell.getBgColor();
+ fg = (cell.isFgDefault()) ? DEFAULT_COLOR : cell.getFgColor();
+ }
+
+ const drawInBrightColor = this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && fg < 8;
+
+ fg += drawInBrightColor ? 8 : 0;
+ this._currentGlyphIdentifier.chars = cell.getChars() || WHITESPACE_CELL_CHAR;
+ this._currentGlyphIdentifier.code = cell.getCode() || WHITESPACE_CELL_CODE;
+ this._currentGlyphIdentifier.bg = bg;
+ this._currentGlyphIdentifier.fg = fg;
+ 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);
+
+ if (!atlasDidDraw) {
+ this._drawUncachedChars(cell, x, y);
+ }
+ }
+
+ /**
+ * Draws one or more characters at one or more cells. The character(s) will be
+ * clipped to ensure that they fit with the cell(s), including the cell to the
+ * right if the last character is a wide character.
+ * @param chars The character.
+ * @param width The width of the character.
+ * @param fg The foreground color, in the format stored within the attributes.
+ * @param x The column to draw at.
+ * @param y The row to draw at.
+ */
+ private _drawUncachedChars(cell: ICellData, x: number, y: number, fgOverride?: IColor): void {
+ this._ctx.save();
+ this._ctx.font = this._getFont(!!cell.isBold(), !!cell.isItalic());
+ this._ctx.textBaseline = TEXT_BASELINE;
+
+ if (cell.isInverse()) {
+ if (fgOverride) {
+ this._ctx.fillStyle = fgOverride.css;
+ } else if (cell.isBgDefault()) {
+ this._ctx.fillStyle = color.opaque(this._colors.background).css;
+ } else if (cell.isBgRGB()) {
+ this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`;
+ } else {
+ let bg = cell.getBgColor();
+ if (this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && bg < 8) {
+ bg += 8;
+ }
+ this._ctx.fillStyle = this._colors.ansi[bg].css;
+ }
+ } else {
+ if (fgOverride) {
+ this._ctx.fillStyle = fgOverride.css;
+ } else if (cell.isFgDefault()) {
+ this._ctx.fillStyle = this._colors.foreground.css;
+ } else if (cell.isFgRGB()) {
+ this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`;
+ } else {
+ let fg = cell.getFgColor();
+ if (this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && fg < 8) {
+ fg += 8;
+ }
+ this._ctx.fillStyle = this._colors.ansi[fg].css;
+ }
+ }
+
+ this._clipRow(y);
+
+ // Apply alpha to dim the character
+ if (cell.isDim()) {
+ this._ctx.globalAlpha = DIM_OPACITY;
+ }
+
+ // Draw custom characters if applicable
+ let drawSuccess = false;
+ if (this._optionsService.rawOptions.customGlyphs !== false) {
+ drawSuccess = tryDrawCustomChar(this._ctx, cell.getChars(), x * this._scaledCellWidth, y * this._scaledCellHeight, this._scaledCellWidth, this._scaledCellHeight);
+ }
+
+ // Draw the character
+ if (!drawSuccess) {
+ this._ctx.fillText(
+ cell.getChars(),
+ x * this._scaledCellWidth + this._scaledCharLeft,
+ y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight);
+ }
+
+ this._ctx.restore();
+ }
+
+
+ /**
+ * Clips a row to ensure no pixels will be drawn outside the cells in the row.
+ * @param y The row to clip.
+ */
+ private _clipRow(y: number): void {
+ this._ctx.beginPath();
+ this._ctx.rect(
+ 0,
+ y * this._scaledCellHeight,
+ this._bufferService.cols * this._scaledCellWidth,
+ this._scaledCellHeight);
+ this._ctx.clip();
+ }
+
+ /**
+ * Gets the current font.
+ * @param isBold If we should use the bold fontWeight.
+ */
+ protected _getFont(isBold: boolean, isItalic: boolean): string {
+ const fontWeight = isBold ? this._optionsService.rawOptions.fontWeightBold : this._optionsService.rawOptions.fontWeight;
+ const fontStyle = isItalic ? 'italic' : '';
+
+ 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) {
+ return undefined;
+ }
+
+ // Try get from cache first
+ const adjustedColor = this._colors.contrastCache.getColor(cell.bg, cell.fg);
+ if (adjustedColor !== undefined) {
+ return adjustedColor || undefined;
+ }
+
+ let fgColor = cell.getFgColor();
+ let fgColorMode = cell.getFgColorMode();
+ let bgColor = cell.getBgColor();
+ let bgColorMode = cell.getBgColorMode();
+ const isInverse = !!cell.isInverse();
+ const isBold = !!cell.isInverse();
+ if (isInverse) {
+ const temp = fgColor;
+ fgColor = bgColor;
+ bgColor = temp;
+ const temp2 = fgColorMode;
+ fgColorMode = bgColorMode;
+ bgColorMode = temp2;
+ }
+
+ const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, isInverse);
+ const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, isInverse, isBold);
+ const result = rgba.ensureContrastRatio(bgRgba, fgRgba, this._optionsService.rawOptions.minimumContrastRatio);
+
+ if (!result) {
+ this._colors.contrastCache.setColor(cell.bg, cell.fg, null);
+ return undefined;
+ }
+
+ const color: IColor = {
+ css: channels.toCss(
+ (result >> 24) & 0xFF,
+ (result >> 16) & 0xFF,
+ (result >> 8) & 0xFF
+ ),
+ rgba: result
+ };
+ this._colors.contrastCache.setColor(cell.bg, cell.fg, color);
+
+ return color;
+ }
+
+ private _resolveBackgroundRgba(bgColorMode: number, bgColor: number, inverse: boolean): number {
+ switch (bgColorMode) {
+ case Attributes.CM_P16:
+ case Attributes.CM_P256:
+ return this._colors.ansi[bgColor].rgba;
+ case Attributes.CM_RGB:
+ return bgColor << 8;
+ case Attributes.CM_DEFAULT:
+ default:
+ if (inverse) {
+ return this._colors.foreground.rgba;
+ }
+ return this._colors.background.rgba;
+ }
+ }
+
+ private _resolveForegroundRgba(fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): number {
+ switch (fgColorMode) {
+ case Attributes.CM_P16:
+ case Attributes.CM_P256:
+ if (this._optionsService.rawOptions.drawBoldTextInBrightColors && bold && fgColor < 8) {
+ fgColor += 8;
+ }
+ return this._colors.ansi[fgColor].rgba;
+ case Attributes.CM_RGB:
+ return fgColor << 8;
+ case Attributes.CM_DEFAULT:
+ default:
+ if (inverse) {
+ return this._colors.background.rgba;
+ }
+ return this._colors.foreground.rgba;
+ }
+ }
+}
+
diff --git a/node_modules/xterm/src/browser/renderer/CursorRenderLayer.ts b/node_modules/xterm/src/browser/renderer/CursorRenderLayer.ts
new file mode 100644
index 0000000..ea419cb
--- /dev/null
+++ b/node_modules/xterm/src/browser/renderer/CursorRenderLayer.ts
@@ -0,0 +1,377 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types';
+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 { IEventEmitter } from 'common/EventEmitter';
+import { ICoreBrowserService } from 'browser/services/Services';
+
+interface ICursorState {
+ x: number;
+ y: number;
+ isFocused: boolean;
+ style: string;
+ width: number;
+}
+
+/**
+ * The time between cursor blinks.
+ */
+const BLINK_INTERVAL = 600;
+
+export class CursorRenderLayer extends BaseRenderLayer {
+ private _state: ICursorState;
+ private _cursorRenderers: {[key: string]: (x: number, y: number, cell: ICellData) => void};
+ private _cursorBlinkStateManager: CursorBlinkStateManager | undefined;
+ private _cell: ICellData = new CellData();
+
+ constructor(
+ container: HTMLElement,
+ zIndex: number,
+ colors: IColorSet,
+ rendererId: number,
+ private _onRequestRedraw: IEventEmitter<IRequestRedrawEvent>,
+ @IBufferService bufferService: IBufferService,
+ @IOptionsService optionsService: IOptionsService,
+ @ICoreService private readonly _coreService: ICoreService,
+ @ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService
+ ) {
+ super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService);
+ this._state = {
+ x: 0,
+ y: 0,
+ isFocused: false,
+ style: '',
+ width: 0
+ };
+ this._cursorRenderers = {
+ 'bar': this._renderBarCursor.bind(this),
+ 'block': this._renderBlockCursor.bind(this),
+ 'underline': this._renderUnderlineCursor.bind(this)
+ };
+ }
+
+ public dispose(): void {
+ if (this._cursorBlinkStateManager) {
+ this._cursorBlinkStateManager.dispose();
+ this._cursorBlinkStateManager = undefined;
+ }
+ super.dispose();
+ }
+
+ public resize(dim: IRenderDimensions): void {
+ super.resize(dim);
+ // Resizing the canvas discards the contents of the canvas so clear state
+ this._state = {
+ x: 0,
+ y: 0,
+ isFocused: false,
+ style: '',
+ width: 0
+ };
+ }
+
+ public reset(): void {
+ this._clearCursor();
+ this._cursorBlinkStateManager?.restartBlinkAnimation();
+ this.onOptionsChanged();
+ }
+
+ public onBlur(): void {
+ this._cursorBlinkStateManager?.pause();
+ this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
+ }
+
+ public onFocus(): void {
+ this._cursorBlinkStateManager?.resume();
+ this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
+ }
+
+ public onOptionsChanged(): void {
+ if (this._optionsService.rawOptions.cursorBlink) {
+ if (!this._cursorBlinkStateManager) {
+ this._cursorBlinkStateManager = new CursorBlinkStateManager(this._coreBrowserService.isFocused, () => {
+ this._render(true);
+ });
+ }
+ } else {
+ this._cursorBlinkStateManager?.dispose();
+ this._cursorBlinkStateManager = undefined;
+ }
+ // Request a refresh from the terminal as management of rendering is being
+ // moved back to the terminal
+ this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
+ }
+
+ public onCursorMove(): void {
+ this._cursorBlinkStateManager?.restartBlinkAnimation();
+ }
+
+ public onGridChanged(startRow: number, endRow: number): void {
+ if (!this._cursorBlinkStateManager || this._cursorBlinkStateManager.isPaused) {
+ this._render(false);
+ } else {
+ this._cursorBlinkStateManager.restartBlinkAnimation();
+ }
+ }
+
+ private _render(triggeredByAnimationFrame: boolean): void {
+ // Don't draw the cursor if it's hidden
+ if (!this._coreService.isCursorInitialized || this._coreService.isCursorHidden) {
+ this._clearCursor();
+ return;
+ }
+
+ const cursorY = this._bufferService.buffer.ybase + this._bufferService.buffer.y;
+ const viewportRelativeCursorY = cursorY - this._bufferService.buffer.ydisp;
+
+ // Don't draw the cursor if it's off-screen
+ if (viewportRelativeCursorY < 0 || viewportRelativeCursorY >= this._bufferService.rows) {
+ this._clearCursor();
+ return;
+ }
+
+ // in case cursor.x == cols adjust visual cursor to cols - 1
+ const cursorX = Math.min(this._bufferService.buffer.x, this._bufferService.cols - 1);
+ this._bufferService.buffer.lines.get(cursorY)!.loadCell(cursorX, this._cell);
+ if (this._cell.content === undefined) {
+ return;
+ }
+
+ if (!this._coreBrowserService.isFocused) {
+ this._clearCursor();
+ this._ctx.save();
+ this._ctx.fillStyle = this._colors.cursor.css;
+ const cursorStyle = this._optionsService.rawOptions.cursorStyle;
+ if (cursorStyle && cursorStyle !== 'block') {
+ this._cursorRenderers[cursorStyle](cursorX, viewportRelativeCursorY, this._cell);
+ } else {
+ this._renderBlurCursor(cursorX, viewportRelativeCursorY, this._cell);
+ }
+ this._ctx.restore();
+ this._state.x = cursorX;
+ this._state.y = viewportRelativeCursorY;
+ this._state.isFocused = false;
+ this._state.style = cursorStyle;
+ this._state.width = this._cell.getWidth();
+ return;
+ }
+
+ // Don't draw the cursor if it's blinking
+ if (this._cursorBlinkStateManager && !this._cursorBlinkStateManager.isCursorVisible) {
+ this._clearCursor();
+ return;
+ }
+
+ if (this._state) {
+ // The cursor is already in the correct spot, don't redraw
+ if (this._state.x === cursorX &&
+ this._state.y === viewportRelativeCursorY &&
+ this._state.isFocused === this._coreBrowserService.isFocused &&
+ this._state.style === this._optionsService.rawOptions.cursorStyle &&
+ this._state.width === this._cell.getWidth()) {
+ return;
+ }
+ this._clearCursor();
+ }
+
+ this._ctx.save();
+ this._cursorRenderers[this._optionsService.rawOptions.cursorStyle || 'block'](cursorX, viewportRelativeCursorY, this._cell);
+ this._ctx.restore();
+
+ this._state.x = cursorX;
+ this._state.y = viewportRelativeCursorY;
+ this._state.isFocused = false;
+ this._state.style = this._optionsService.rawOptions.cursorStyle;
+ this._state.width = this._cell.getWidth();
+ }
+
+ private _clearCursor(): void {
+ if (this._state) {
+ // Avoid potential rounding errors when device pixel ratio is less than 1
+ if (window.devicePixelRatio < 1) {
+ this._clearAll();
+ } else {
+ this._clearCells(this._state.x, this._state.y, this._state.width, 1);
+ }
+ this._state = {
+ x: 0,
+ y: 0,
+ isFocused: false,
+ style: '',
+ width: 0
+ };
+ }
+ }
+
+ private _renderBarCursor(x: number, y: number, cell: ICellData): void {
+ this._ctx.save();
+ this._ctx.fillStyle = this._colors.cursor.css;
+ this._fillLeftLineAtCell(x, y, this._optionsService.rawOptions.cursorWidth);
+ this._ctx.restore();
+ }
+
+ private _renderBlockCursor(x: number, y: number, cell: ICellData): void {
+ this._ctx.save();
+ this._ctx.fillStyle = this._colors.cursor.css;
+ this._fillCells(x, y, cell.getWidth(), 1);
+ this._ctx.fillStyle = this._colors.cursorAccent.css;
+ this._fillCharTrueColor(cell, x, y);
+ this._ctx.restore();
+ }
+
+ private _renderUnderlineCursor(x: number, y: number, cell: ICellData): void {
+ this._ctx.save();
+ this._ctx.fillStyle = this._colors.cursor.css;
+ this._fillBottomLineAtCells(x, y);
+ this._ctx.restore();
+ }
+
+ private _renderBlurCursor(x: number, y: number, cell: ICellData): void {
+ this._ctx.save();
+ this._ctx.strokeStyle = this._colors.cursor.css;
+ this._strokeRectAtCell(x, y, cell.getWidth(), 1);
+ this._ctx.restore();
+ }
+}
+
+class CursorBlinkStateManager {
+ public isCursorVisible: boolean;
+
+ private _animationFrame: number | undefined;
+ private _blinkStartTimeout: number | undefined;
+ private _blinkInterval: number | undefined;
+
+ /**
+ * The time at which the animation frame was restarted, this is used on the
+ * next render to restart the timers so they don't need to restart the timers
+ * multiple times over a short period.
+ */
+ private _animationTimeRestarted: number | undefined;
+
+ constructor(
+ isFocused: boolean,
+ private _renderCallback: () => void
+ ) {
+ this.isCursorVisible = true;
+ if (isFocused) {
+ this._restartInterval();
+ }
+ }
+
+ public get isPaused(): boolean { return !(this._blinkStartTimeout || this._blinkInterval); }
+
+ public dispose(): void {
+ if (this._blinkInterval) {
+ window.clearInterval(this._blinkInterval);
+ this._blinkInterval = undefined;
+ }
+ if (this._blinkStartTimeout) {
+ window.clearTimeout(this._blinkStartTimeout);
+ this._blinkStartTimeout = undefined;
+ }
+ if (this._animationFrame) {
+ window.cancelAnimationFrame(this._animationFrame);
+ this._animationFrame = undefined;
+ }
+ }
+
+ public restartBlinkAnimation(): void {
+ if (this.isPaused) {
+ return;
+ }
+ // Save a timestamp so that the restart can be done on the next interval
+ this._animationTimeRestarted = Date.now();
+ // Force a cursor render to ensure it's visible and in the correct position
+ this.isCursorVisible = true;
+ if (!this._animationFrame) {
+ this._animationFrame = window.requestAnimationFrame(() => {
+ this._renderCallback();
+ this._animationFrame = undefined;
+ });
+ }
+ }
+
+ private _restartInterval(timeToStart: number = BLINK_INTERVAL): void {
+ // Clear any existing interval
+ if (this._blinkInterval) {
+ window.clearInterval(this._blinkInterval);
+ this._blinkInterval = undefined;
+ }
+
+ // Setup the initial timeout which will hide the cursor, this is done before
+ // the regular interval is setup in order to support restarting the blink
+ // animation in a lightweight way (without thrashing clearInterval and
+ // setInterval).
+ this._blinkStartTimeout = window.setTimeout(() => {
+ // Check if another animation restart was requested while this was being
+ // started
+ if (this._animationTimeRestarted) {
+ const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted);
+ this._animationTimeRestarted = undefined;
+ if (time > 0) {
+ this._restartInterval(time);
+ return;
+ }
+ }
+
+ // Hide the cursor
+ this.isCursorVisible = false;
+ this._animationFrame = window.requestAnimationFrame(() => {
+ this._renderCallback();
+ this._animationFrame = undefined;
+ });
+
+ // Setup the blink interval
+ this._blinkInterval = window.setInterval(() => {
+ // Adjust the animation time if it was restarted
+ if (this._animationTimeRestarted) {
+ // calc time diff
+ // Make restart interval do a setTimeout initially?
+ const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted);
+ this._animationTimeRestarted = undefined;
+ this._restartInterval(time);
+ return;
+ }
+
+ // Invert visibility and render
+ this.isCursorVisible = !this.isCursorVisible;
+ this._animationFrame = window.requestAnimationFrame(() => {
+ this._renderCallback();
+ this._animationFrame = undefined;
+ });
+ }, BLINK_INTERVAL);
+ }, timeToStart);
+ }
+
+ public pause(): void {
+ this.isCursorVisible = true;
+ if (this._blinkInterval) {
+ window.clearInterval(this._blinkInterval);
+ this._blinkInterval = undefined;
+ }
+ if (this._blinkStartTimeout) {
+ window.clearTimeout(this._blinkStartTimeout);
+ this._blinkStartTimeout = undefined;
+ }
+ if (this._animationFrame) {
+ window.cancelAnimationFrame(this._animationFrame);
+ this._animationFrame = undefined;
+ }
+ }
+
+ public resume(): void {
+ // Clear out any existing timers just in case
+ this.pause();
+
+ this._animationTimeRestarted = undefined;
+ this._restartInterval();
+ this.restartBlinkAnimation();
+ }
+}
diff --git a/node_modules/xterm/src/browser/renderer/CustomGlyphs.ts b/node_modules/xterm/src/browser/renderer/CustomGlyphs.ts
new file mode 100644
index 0000000..7756279
--- /dev/null
+++ b/node_modules/xterm/src/browser/renderer/CustomGlyphs.ts
@@ -0,0 +1,563 @@
+/**
+ * Copyright (c) 2021 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { throwIfFalsy } from 'browser/renderer/RendererUtils';
+
+interface IBlockVector {
+ x: number;
+ y: number;
+ w: number;
+ h: number;
+}
+
+export const blockElementDefinitions: { [index: string]: IBlockVector[] | undefined } = {
+ // Block elements (0x2580-0x2590)
+ '▀': [{ x: 0, y: 0, w: 8, h: 4 }], // UPPER HALF BLOCK
+ '▁': [{ x: 0, y: 7, w: 8, h: 1 }], // LOWER ONE EIGHTH BLOCK
+ '▂': [{ x: 0, y: 6, w: 8, h: 2 }], // LOWER ONE QUARTER BLOCK
+ '▃': [{ x: 0, y: 5, w: 8, h: 3 }], // LOWER THREE EIGHTHS BLOCK
+ '▄': [{ x: 0, y: 4, w: 8, h: 4 }], // LOWER HALF BLOCK
+ '▅': [{ x: 0, y: 3, w: 8, h: 5 }], // LOWER FIVE EIGHTHS BLOCK
+ '▆': [{ x: 0, y: 2, w: 8, h: 6 }], // LOWER THREE QUARTERS BLOCK
+ '▇': [{ x: 0, y: 1, w: 8, h: 7 }], // LOWER SEVEN EIGHTHS BLOCK
+ '█': [{ x: 0, y: 0, w: 8, h: 8 }], // FULL BLOCK
+ '▉': [{ x: 0, y: 0, w: 7, h: 8 }], // LEFT SEVEN EIGHTHS BLOCK
+ '▊': [{ x: 0, y: 0, w: 6, h: 8 }], // LEFT THREE QUARTERS BLOCK
+ '▋': [{ x: 0, y: 0, w: 5, h: 8 }], // LEFT FIVE EIGHTHS BLOCK
+ '▌': [{ x: 0, y: 0, w: 4, h: 8 }], // LEFT HALF BLOCK
+ '▍': [{ x: 0, y: 0, w: 3, h: 8 }], // LEFT THREE EIGHTHS BLOCK
+ '▎': [{ x: 0, y: 0, w: 2, h: 8 }], // LEFT ONE QUARTER BLOCK
+ '▏': [{ x: 0, y: 0, w: 1, h: 8 }], // LEFT ONE EIGHTH BLOCK
+ '▐': [{ x: 4, y: 0, w: 4, h: 8 }], // RIGHT HALF BLOCK
+
+ // Block elements (0x2594-0x2595)
+ '▔': [{ x: 0, y: 0, w: 9, h: 1 }], // UPPER ONE EIGHTH BLOCK
+ '▕': [{ x: 7, y: 0, w: 1, h: 8 }], // RIGHT ONE EIGHTH BLOCK
+
+ // Terminal graphic characters (0x2596-0x259F)
+ '▖': [{ x: 0, y: 4, w: 4, h: 4 }], // QUADRANT LOWER LEFT
+ '▗': [{ x: 4, y: 4, w: 4, h: 4 }], // QUADRANT LOWER RIGHT
+ '▘': [{ x: 0, y: 0, w: 4, h: 4 }], // QUADRANT UPPER LEFT
+ '▙': [{ x: 0, y: 0, w: 4, h: 8 }, { x: 0, y: 4, w: 8, h: 4 }], // QUADRANT UPPER LEFT AND LOWER LEFT AND LOWER RIGHT
+ '▚': [{ x: 0, y: 0, w: 4, h: 4 }, { x: 4, y: 4, w: 4, h: 4 }], // QUADRANT UPPER LEFT AND LOWER RIGHT
+ '▛': [{ x: 0, y: 0, w: 4, h: 8 }, { x: 0, y: 0, w: 4, h: 8 }], // QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER LEFT
+ '▜': [{ x: 0, y: 0, w: 8, h: 4 }, { x: 4, y: 0, w: 4, h: 8 }], // QUADRANT UPPER LEFT AND UPPER RIGHT AND LOWER RIGHT
+ '▝': [{ x: 4, y: 0, w: 4, h: 4 }], // QUADRANT UPPER RIGHT
+ '▞': [{ x: 4, y: 0, w: 4, h: 4 }, { x: 0, y: 4, w: 4, h: 4 }], // QUADRANT UPPER RIGHT AND LOWER LEFT
+ '▟': [{ x: 4, y: 0, w: 4, h: 8 }, { x: 0, y: 4, w: 8, h: 4 }], // QUADRANT UPPER RIGHT AND LOWER LEFT AND LOWER RIGHT
+
+ // VERTICAL ONE EIGHTH BLOCK-2 through VERTICAL ONE EIGHTH BLOCK-7
+ '\u{1FB70}': [{ x: 1, y: 0, w: 1, h: 8 }],
+ '\u{1FB71}': [{ x: 2, y: 0, w: 1, h: 8 }],
+ '\u{1FB72}': [{ x: 3, y: 0, w: 1, h: 8 }],
+ '\u{1FB73}': [{ x: 4, y: 0, w: 1, h: 8 }],
+ '\u{1FB74}': [{ x: 5, y: 0, w: 1, h: 8 }],
+ '\u{1FB75}': [{ x: 6, y: 0, w: 1, h: 8 }],
+
+ // HORIZONTAL ONE EIGHTH BLOCK-2 through HORIZONTAL ONE EIGHTH BLOCK-7
+ '\u{1FB76}': [{ x: 0, y: 1, w: 8, h: 1 }],
+ '\u{1FB77}': [{ x: 0, y: 2, w: 8, h: 1 }],
+ '\u{1FB78}': [{ x: 0, y: 3, w: 8, h: 1 }],
+ '\u{1FB79}': [{ x: 0, y: 4, w: 8, h: 1 }],
+ '\u{1FB7A}': [{ x: 0, y: 5, w: 8, h: 1 }],
+ '\u{1FB7B}': [{ x: 0, y: 6, w: 8, h: 1 }],
+
+ // LEFT AND LOWER ONE EIGHTH BLOCK
+ '\u{1FB7C}': [{ x: 0, y: 0, w: 1, h: 8 }, { x: 0, y: 7, w: 8, h: 1 }],
+ // LEFT AND UPPER ONE EIGHTH BLOCK
+ '\u{1FB7D}': [{ x: 0, y: 0, w: 1, h: 8 }, { x: 0, y: 0, w: 8, h: 1 }],
+ // RIGHT AND UPPER ONE EIGHTH BLOCK
+ '\u{1FB7E}': [{ x: 7, y: 0, w: 1, h: 8 }, { x: 0, y: 0, w: 8, h: 1 }],
+ // RIGHT AND LOWER ONE EIGHTH BLOCK
+ '\u{1FB7F}': [{ x: 7, y: 0, w: 1, h: 8 }, { x: 0, y: 7, w: 8, h: 1 }],
+ // UPPER AND LOWER ONE EIGHTH BLOCK
+ '\u{1FB80}': [{ x: 0, y: 0, w: 8, h: 1 }, { x: 0, y: 7, w: 8, h: 1 }],
+ // HORIZONTAL ONE EIGHTH BLOCK-1358
+ '\u{1FB81}': [{ x: 0, y: 0, w: 8, h: 1 }, { x: 0, y: 2, w: 8, h: 1 }, { x: 0, y: 4, w: 8, h: 1 }, { x: 0, y: 7, w: 8, h: 1 }],
+
+ // UPPER ONE QUARTER BLOCK
+ '\u{1FB82}': [{ x: 0, y: 0, w: 8, h: 2 }],
+ // UPPER THREE EIGHTHS BLOCK
+ '\u{1FB83}': [{ x: 0, y: 0, w: 8, h: 3 }],
+ // UPPER FIVE EIGHTHS BLOCK
+ '\u{1FB84}': [{ x: 0, y: 0, w: 8, h: 5 }],
+ // UPPER THREE QUARTERS BLOCK
+ '\u{1FB85}': [{ x: 0, y: 0, w: 8, h: 6 }],
+ // UPPER SEVEN EIGHTHS BLOCK
+ '\u{1FB86}': [{ x: 0, y: 0, w: 8, h: 7 }],
+
+ // RIGHT ONE QUARTER BLOCK
+ '\u{1FB87}': [{ x: 6, y: 0, w: 2, h: 8 }],
+ // RIGHT THREE EIGHTHS B0OCK
+ '\u{1FB88}': [{ x: 5, y: 0, w: 3, h: 8 }],
+ // RIGHT FIVE EIGHTHS BL0CK
+ '\u{1FB89}': [{ x: 3, y: 0, w: 5, h: 8 }],
+ // RIGHT THREE QUARTERS 0LOCK
+ '\u{1FB8A}': [{ x: 2, y: 0, w: 6, h: 8 }],
+ // RIGHT SEVEN EIGHTHS B0OCK
+ '\u{1FB8B}': [{ x: 1, y: 0, w: 7, h: 8 }],
+
+ // CHECKER BOARD FILL
+ '\u{1FB95}': [
+ { x: 0, y: 0, w: 2, h: 2 }, { x: 4, y: 0, w: 2, h: 2 },
+ { x: 2, y: 2, w: 2, h: 2 }, { x: 6, y: 2, w: 2, h: 2 },
+ { x: 0, y: 4, w: 2, h: 2 }, { x: 4, y: 4, w: 2, h: 2 },
+ { x: 2, y: 6, w: 2, h: 2 }, { x: 6, y: 6, w: 2, h: 2 }
+ ],
+ // INVERSE CHECKER BOARD FILL
+ '\u{1FB96}': [
+ { x: 2, y: 0, w: 2, h: 2 }, { x: 6, y: 0, w: 2, h: 2 },
+ { x: 0, y: 2, w: 2, h: 2 }, { x: 4, y: 2, w: 2, h: 2 },
+ { x: 2, y: 4, w: 2, h: 2 }, { x: 6, y: 4, w: 2, h: 2 },
+ { x: 0, y: 6, w: 2, h: 2 }, { x: 4, y: 6, w: 2, h: 2 }
+ ],
+ // HEAVY HORIZONTAL FILL (upper middle and lower one quarter block)
+ '\u{1FB97}': [{ x: 0, y: 2, w: 8, h: 2 }, { x: 0, y: 6, w: 8, h: 2 }]
+};
+
+type PatternDefinition = number[][];
+
+/**
+ * Defines the repeating pattern used by special characters, the pattern is made up of a 2d array of
+ * pixel values to be filled (1) or not filled (0).
+ */
+const patternCharacterDefinitions: { [key: string]: PatternDefinition | undefined } = {
+ // Shade characters (0x2591-0x2593)
+ '░': [ // LIGHT SHADE (25%)
+ [1, 0, 0, 0],
+ [0, 0, 0, 0],
+ [0, 0, 1, 0],
+ [0, 0, 0, 0]
+ ],
+ '▒': [ // MEDIUM SHADE (50%)
+ [1, 0],
+ [0, 0],
+ [0, 1],
+ [0, 0]
+ ],
+ '▓': [ // DARK SHADE (75%)
+ [0, 1],
+ [1, 1],
+ [1, 0],
+ [1, 1]
+ ]
+};
+
+const enum Shapes {
+ /** │ */ TOP_TO_BOTTOM = 'M.5,0 L.5,1',
+ /** ─ */ LEFT_TO_RIGHT = 'M0,.5 L1,.5',
+
+ /** └ */ TOP_TO_RIGHT = 'M.5,0 L.5,.5 L1,.5',
+ /** ┘ */ TOP_TO_LEFT = 'M.5,0 L.5,.5 L0,.5',
+ /** ┐ */ LEFT_TO_BOTTOM = 'M0,.5 L.5,.5 L.5,1',
+ /** ┌ */ RIGHT_TO_BOTTOM = 'M0.5,1 L.5,.5 L1,.5',
+
+ /** ╵ */ MIDDLE_TO_TOP = 'M.5,.5 L.5,0',
+ /** ╴ */ MIDDLE_TO_LEFT = 'M.5,.5 L0,.5',
+ /** ╶ */ MIDDLE_TO_RIGHT = 'M.5,.5 L1,.5',
+ /** ╷ */ MIDDLE_TO_BOTTOM = 'M.5,.5 L.5,1',
+
+ /** ┴ */ T_TOP = 'M0,.5 L1,.5 M.5,.5 L.5,0',
+ /** ┤ */ T_LEFT = 'M.5,0 L.5,1 M.5,.5 L0,.5',
+ /** ├ */ T_RIGHT = 'M.5,0 L.5,1 M.5,.5 L1,.5',
+ /** ┬ */ T_BOTTOM = 'M0,.5 L1,.5 M.5,.5 L.5,1',
+
+ /** ┼ */ CROSS = 'M0,.5 L1,.5 M.5,0 L.5,1',
+
+ /** ╌ */ TWO_DASHES_HORIZONTAL = 'M.1,.5 L.4,.5 M.6,.5 L.9,.5', // .2 empty, .3 filled
+ /** ┄ */ THREE_DASHES_HORIZONTAL = 'M.0667,.5 L.2667,.5 M.4,.5 L.6,.5 M.7333,.5 L.9333,.5', // .1333 empty, .2 filled
+ /** ┉ */ FOUR_DASHES_HORIZONTAL = 'M.05,.5 L.2,.5 M.3,.5 L.45,.5 M.55,.5 L.7,.5 M.8,.5 L.95,.5', // .1 empty, .15 filled
+ /** ╎ */ TWO_DASHES_VERTICAL = 'M.5,.1 L.5,.4 M.5,.6 L.5,.9',
+ /** ┆ */ THREE_DASHES_VERTICAL = 'M.5,.0667 L.5,.2667 M.5,.4 L.5,.6 M.5,.7333 L.5,.9333',
+ /** ┊ */ FOUR_DASHES_VERTICAL = 'M.5,.05 L.5,.2 M.5,.3 L.5,.45 L.5,.55 M.5,.7 L.5,.95',
+}
+
+const enum Style {
+ NORMAL = 1,
+ BOLD = 3
+}
+
+/**
+ * This contains the definitions of all box drawing characters in the format of SVG paths (ie. the
+ * svg d attribute).
+ */
+export const boxDrawingDefinitions: { [character: string]: { [fontWeight: number]: string | ((xp: number, yp: number) => string) } | undefined } = {
+ // Uniform normal and bold
+ '─': { [Style.NORMAL]: Shapes.LEFT_TO_RIGHT },
+ '━': { [Style.BOLD]: Shapes.LEFT_TO_RIGHT },
+ '│': { [Style.NORMAL]: Shapes.TOP_TO_BOTTOM },
+ '┃': { [Style.BOLD]: Shapes.TOP_TO_BOTTOM },
+ '┌': { [Style.NORMAL]: Shapes.RIGHT_TO_BOTTOM },
+ '┏': { [Style.BOLD]: Shapes.RIGHT_TO_BOTTOM },
+ '┐': { [Style.NORMAL]: Shapes.LEFT_TO_BOTTOM },
+ '┓': { [Style.BOLD]: Shapes.LEFT_TO_BOTTOM },
+ '└': { [Style.NORMAL]: Shapes.TOP_TO_RIGHT },
+ '┗': { [Style.BOLD]: Shapes.TOP_TO_RIGHT },
+ '┘': { [Style.NORMAL]: Shapes.TOP_TO_LEFT },
+ '┛': { [Style.BOLD]: Shapes.TOP_TO_LEFT },
+ '├': { [Style.NORMAL]: Shapes.T_RIGHT },
+ '┣': { [Style.BOLD]: Shapes.T_RIGHT },
+ '┤': { [Style.NORMAL]: Shapes.T_LEFT },
+ '┫': { [Style.BOLD]: Shapes.T_LEFT },
+ '┬': { [Style.NORMAL]: Shapes.T_BOTTOM },
+ '┳': { [Style.BOLD]: Shapes.T_BOTTOM },
+ '┴': { [Style.NORMAL]: Shapes.T_TOP },
+ '┻': { [Style.BOLD]: Shapes.T_TOP },
+ '┼': { [Style.NORMAL]: Shapes.CROSS },
+ '╋': { [Style.BOLD]: Shapes.CROSS },
+ '╴': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT },
+ '╸': { [Style.BOLD]: Shapes.MIDDLE_TO_LEFT },
+ '╵': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP },
+ '╹': { [Style.BOLD]: Shapes.MIDDLE_TO_TOP },
+ '╶': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT },
+ '╺': { [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT },
+ '╷': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM },
+ '╻': { [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM },
+
+ // Double border
+ '═': { [Style.NORMAL]: (xp, yp) => `M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L1,${.5 + yp}` },
+ '║': { [Style.NORMAL]: (xp, yp) => `M${.5 - xp},0 L${.5 - xp},1 M${.5 + xp},0 L${.5 + xp},1` },
+ '╒': { [Style.NORMAL]: (xp, yp) => `M.5,1 L.5,${.5 - yp} L1,${.5 - yp} M.5,${.5 + yp} L1,${.5 + yp}` },
+ '╓': { [Style.NORMAL]: (xp, yp) => `M${.5 - xp},1 L${.5 - xp},.5 L1,.5 M${.5 + xp},.5 L${.5 + xp},1` },
+ '╔': { [Style.NORMAL]: (xp, yp) => `M1,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},1 M1,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},1` },
+ '╕': { [Style.NORMAL]: (xp, yp) => `M0,${.5 - yp} L.5,${.5 - yp} L.5,1 M0,${.5 + yp} L.5,${.5 + yp}` },
+ '╖': { [Style.NORMAL]: (xp, yp) => `M${.5 + xp},1 L${.5 + xp},.5 L0,.5 M${.5 - xp},.5 L${.5 - xp},1` },
+ '╗': { [Style.NORMAL]: (xp, yp) => `M0,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},1 M0,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},1` },
+ '╘': { [Style.NORMAL]: (xp, yp) => `M.5,0 L.5,${.5 + yp} L1,${.5 + yp} M.5,${.5 - yp} L1,${.5 - yp}` },
+ '╙': { [Style.NORMAL]: (xp, yp) => `M1,.5 L${.5 - xp},.5 L${.5 - xp},0 M${.5 + xp},.5 L${.5 + xp},0` },
+ '╚': { [Style.NORMAL]: (xp, yp) => `M1,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},0 M1,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},0` },
+ '╛': { [Style.NORMAL]: (xp, yp) => `M0,${.5 + yp} L.5,${.5 + yp} L.5,0 M0,${.5 - yp} L.5,${.5 - yp}` },
+ '╜': { [Style.NORMAL]: (xp, yp) => `M0,.5 L${.5 + xp},.5 L${.5 + xp},0 M${.5 - xp},.5 L${.5 - xp},0` },
+ '╝': { [Style.NORMAL]: (xp, yp) => `M0,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},0 M0,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},0` },
+ '╞': { [Style.NORMAL]: (xp, yp) => `${Shapes.TOP_TO_BOTTOM} M.5,${.5 - yp} L1,${.5 - yp} M.5,${.5 + yp} L1,${.5 + yp}` },
+ '╟': { [Style.NORMAL]: (xp, yp) => `M${.5 - xp},0 L${.5 - xp},1 M${.5 + xp},0 L${.5 + xp},1 M${.5 + xp},.5 L1,.5` },
+ '╠': { [Style.NORMAL]: (xp, yp) => `M${.5 - xp},0 L${.5 - xp},1 M1,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},1 M1,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},0` },
+ '╡': { [Style.NORMAL]: (xp, yp) => `${Shapes.TOP_TO_BOTTOM} M0,${.5 - yp} L.5,${.5 - yp} M0,${.5 + yp} L.5,${.5 + yp}` },
+ '╢': { [Style.NORMAL]: (xp, yp) => `M0,.5 L${.5 - xp},.5 M${.5 - xp},0 L${.5 - xp},1 M${.5 + xp},0 L${.5 + xp},1` },
+ '╣': { [Style.NORMAL]: (xp, yp) => `M${.5 + xp},0 L${.5 + xp},1 M0,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},1 M0,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},0` },
+ '╤': { [Style.NORMAL]: (xp, yp) => `M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L1,${.5 + yp} M.5,${.5 + yp} L.5,1` },
+ '╥': { [Style.NORMAL]: (xp, yp) => `${Shapes.LEFT_TO_RIGHT} M${.5 - xp},.5 L${.5 - xp},1 M${.5 + xp},.5 L${.5 + xp},1` },
+ '╦': { [Style.NORMAL]: (xp, yp) => `M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},1 M1,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},1` },
+ '╧': { [Style.NORMAL]: (xp, yp) => `M.5,0 L.5,${.5 - yp} M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L1,${.5 + yp}` },
+ '╨': { [Style.NORMAL]: (xp, yp) => `${Shapes.LEFT_TO_RIGHT} M${.5 - xp},.5 L${.5 - xp},0 M${.5 + xp},.5 L${.5 + xp},0` },
+ '╩': { [Style.NORMAL]: (xp, yp) => `M0,${.5 + yp} L1,${.5 + yp} M0,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},0 M1,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},0` },
+ '╪': { [Style.NORMAL]: (xp, yp) => `${Shapes.TOP_TO_BOTTOM} M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L1,${.5 + yp}` },
+ '╫': { [Style.NORMAL]: (xp, yp) => `${Shapes.LEFT_TO_RIGHT} M${.5 - xp},0 L${.5 - xp},1 M${.5 + xp},0 L${.5 + xp},1` },
+ '╬': { [Style.NORMAL]: (xp, yp) => `M0,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},1 M1,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},1 M0,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},0 M1,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},0` },
+
+ // Diagonal
+ '╱': { [Style.NORMAL]: 'M1,0 L0,1' },
+ '╲': { [Style.NORMAL]: 'M0,0 L1,1' },
+ '╳': { [Style.NORMAL]: 'M1,0 L0,1 M0,0 L1,1' },
+
+ // Mixed weight
+ '╼': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT },
+ '╽': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM },
+ '╾': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT },
+ '╿': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_TOP },
+ '┍': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT },
+ '┎': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM },
+ '┑': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT },
+ '┒': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM },
+ '┕': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT },
+ '┖': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_TOP },
+ '┙': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT },
+ '┚': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.MIDDLE_TO_TOP },
+ '┝': { [Style.NORMAL]: Shapes.TOP_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT },
+ '┞': { [Style.NORMAL]: Shapes.RIGHT_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_TOP },
+ '┟': { [Style.NORMAL]: Shapes.TOP_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM },
+ '┠': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.TOP_TO_BOTTOM },
+ '┡': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.TOP_TO_RIGHT },
+ '┢': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.RIGHT_TO_BOTTOM },
+ '┥': { [Style.NORMAL]: Shapes.TOP_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT },
+ '┦': { [Style.NORMAL]: Shapes.LEFT_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_TOP },
+ '┧': { [Style.NORMAL]: Shapes.TOP_TO_LEFT, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM },
+ '┨': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.TOP_TO_BOTTOM },
+ '┩': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.TOP_TO_LEFT },
+ '┪': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.LEFT_TO_BOTTOM },
+ '┭': { [Style.NORMAL]: Shapes.RIGHT_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT },
+ '┮': { [Style.NORMAL]: Shapes.LEFT_TO_BOTTOM, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT },
+ '┯': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: Shapes.LEFT_TO_RIGHT },
+ '┰': { [Style.NORMAL]: Shapes.LEFT_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM },
+ '┱': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.LEFT_TO_BOTTOM },
+ '┲': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.RIGHT_TO_BOTTOM },
+ '┵': { [Style.NORMAL]: Shapes.TOP_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT },
+ '┶': { [Style.NORMAL]: Shapes.TOP_TO_LEFT, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT },
+ '┷': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: Shapes.LEFT_TO_RIGHT },
+ '┸': { [Style.NORMAL]: Shapes.LEFT_TO_RIGHT, [Style.BOLD]: Shapes.MIDDLE_TO_TOP },
+ '┹': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: Shapes.TOP_TO_LEFT },
+ '┺': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: Shapes.TOP_TO_RIGHT },
+ '┽': { [Style.NORMAL]: `${Shapes.TOP_TO_BOTTOM} ${Shapes.MIDDLE_TO_RIGHT}`, [Style.BOLD]: Shapes.MIDDLE_TO_LEFT },
+ '┾': { [Style.NORMAL]: `${Shapes.TOP_TO_BOTTOM} ${Shapes.MIDDLE_TO_LEFT}`, [Style.BOLD]: Shapes.MIDDLE_TO_RIGHT },
+ '┿': { [Style.NORMAL]: Shapes.TOP_TO_BOTTOM, [Style.BOLD]: Shapes.LEFT_TO_RIGHT },
+ '╀': { [Style.NORMAL]: `${Shapes.LEFT_TO_RIGHT} ${Shapes.MIDDLE_TO_BOTTOM}`, [Style.BOLD]: Shapes.MIDDLE_TO_TOP },
+ '╁': { [Style.NORMAL]: `${Shapes.MIDDLE_TO_TOP} ${Shapes.LEFT_TO_RIGHT}`, [Style.BOLD]: Shapes.MIDDLE_TO_BOTTOM },
+ '╂': { [Style.NORMAL]: Shapes.LEFT_TO_RIGHT, [Style.BOLD]: Shapes.TOP_TO_BOTTOM },
+ '╃': { [Style.NORMAL]: Shapes.RIGHT_TO_BOTTOM, [Style.BOLD]: Shapes.TOP_TO_LEFT },
+ '╄': { [Style.NORMAL]: Shapes.LEFT_TO_BOTTOM, [Style.BOLD]: Shapes.TOP_TO_RIGHT },
+ '╅': { [Style.NORMAL]: Shapes.TOP_TO_RIGHT, [Style.BOLD]: Shapes.LEFT_TO_BOTTOM },
+ '╆': { [Style.NORMAL]: Shapes.TOP_TO_LEFT, [Style.BOLD]: Shapes.RIGHT_TO_BOTTOM },
+ '╇': { [Style.NORMAL]: Shapes.MIDDLE_TO_BOTTOM, [Style.BOLD]: `${Shapes.MIDDLE_TO_TOP} ${Shapes.LEFT_TO_RIGHT}` },
+ '╈': { [Style.NORMAL]: Shapes.MIDDLE_TO_TOP, [Style.BOLD]: `${Shapes.LEFT_TO_RIGHT} ${Shapes.MIDDLE_TO_BOTTOM}` },
+ '╉': { [Style.NORMAL]: Shapes.MIDDLE_TO_RIGHT, [Style.BOLD]: `${Shapes.TOP_TO_BOTTOM} ${Shapes.MIDDLE_TO_LEFT}` },
+ '╊': { [Style.NORMAL]: Shapes.MIDDLE_TO_LEFT, [Style.BOLD]: `${Shapes.TOP_TO_BOTTOM} ${Shapes.MIDDLE_TO_RIGHT}` },
+
+ // Dashed
+ '╌': { [Style.NORMAL]: Shapes.TWO_DASHES_HORIZONTAL },
+ '╍': { [Style.BOLD]: Shapes.TWO_DASHES_HORIZONTAL },
+ '┄': { [Style.NORMAL]: Shapes.THREE_DASHES_HORIZONTAL },
+ '┅': { [Style.BOLD]: Shapes.THREE_DASHES_HORIZONTAL },
+ '┈': { [Style.NORMAL]: Shapes.FOUR_DASHES_HORIZONTAL },
+ '┉': { [Style.BOLD]: Shapes.FOUR_DASHES_HORIZONTAL },
+ '╎': { [Style.NORMAL]: Shapes.TWO_DASHES_VERTICAL },
+ '╏': { [Style.BOLD]: Shapes.TWO_DASHES_VERTICAL },
+ '┆': { [Style.NORMAL]: Shapes.THREE_DASHES_VERTICAL },
+ '┇': { [Style.BOLD]: Shapes.THREE_DASHES_VERTICAL },
+ '┊': { [Style.NORMAL]: Shapes.FOUR_DASHES_VERTICAL },
+ '┋': { [Style.BOLD]: Shapes.FOUR_DASHES_VERTICAL },
+
+ // Curved
+ '╭': { [Style.NORMAL]: 'C.5,1,.5,.5,1,.5' },
+ '╮': { [Style.NORMAL]: 'C.5,1,.5,.5,0,.5' },
+ '╯': { [Style.NORMAL]: 'C.5,0,.5,.5,0,.5' },
+ '╰': { [Style.NORMAL]: 'C.5,0,.5,.5,1,.5' }
+};
+
+/**
+ * Try drawing a custom block element or box drawing character, returning whether it was
+ * successfully drawn.
+ */
+export function tryDrawCustomChar(
+ ctx: CanvasRenderingContext2D,
+ c: string,
+ xOffset: number,
+ yOffset: number,
+ scaledCellWidth: number,
+ scaledCellHeight: number
+): boolean {
+ const blockElementDefinition = blockElementDefinitions[c];
+ if (blockElementDefinition) {
+ drawBlockElementChar(ctx, blockElementDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight);
+ return true;
+ }
+
+ const patternDefinition = patternCharacterDefinitions[c];
+ if (patternDefinition) {
+ drawPatternChar(ctx, patternDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight);
+ return true;
+ }
+
+ const boxDrawingDefinition = boxDrawingDefinitions[c];
+ if (boxDrawingDefinition) {
+ drawBoxDrawingChar(ctx, boxDrawingDefinition, xOffset, yOffset, scaledCellWidth, scaledCellHeight);
+ return true;
+ }
+
+ return false;
+}
+
+function drawBlockElementChar(
+ ctx: CanvasRenderingContext2D,
+ charDefinition: IBlockVector[],
+ xOffset: number,
+ yOffset: number,
+ scaledCellWidth: number,
+ scaledCellHeight: number
+): void {
+ for (let i = 0; i < charDefinition.length; i++) {
+ const box = charDefinition[i];
+ const xEighth = scaledCellWidth / 8;
+ const yEighth = scaledCellHeight / 8;
+ ctx.fillRect(
+ xOffset + box.x * xEighth,
+ yOffset + box.y * yEighth,
+ box.w * xEighth,
+ box.h * yEighth
+ );
+ }
+}
+
+const cachedPatterns: Map<PatternDefinition, Map</* fillStyle */string, CanvasPattern>> = new Map();
+
+function drawPatternChar(
+ ctx: CanvasRenderingContext2D,
+ charDefinition: number[][],
+ xOffset: number,
+ yOffset: number,
+ scaledCellWidth: number,
+ scaledCellHeight: number
+): void {
+ let patternSet = cachedPatterns.get(charDefinition);
+ if (!patternSet) {
+ patternSet = new Map();
+ cachedPatterns.set(charDefinition, patternSet);
+ }
+ const fillStyle = ctx.fillStyle;
+ if (typeof fillStyle !== 'string') {
+ throw new Error(`Unexpected fillStyle type "${fillStyle}"`);
+ }
+ let pattern = patternSet.get(fillStyle);
+ if (!pattern) {
+ const width = charDefinition[0].length;
+ const height = charDefinition.length;
+ const tmpCanvas = document.createElement('canvas');
+ tmpCanvas.width = width;
+ tmpCanvas.height = height;
+ const tmpCtx = throwIfFalsy(tmpCanvas.getContext('2d'));
+ const imageData = new ImageData(width, height);
+
+ // Extract rgba from fillStyle
+ let r: number;
+ let g: number;
+ 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;
+ } else if (fillStyle.startsWith('rgba')) {
+ ([r, g, b, a] = fillStyle.substring(5, fillStyle.length - 1).split(',').map(e => parseFloat(e)));
+ } else {
+ throw new Error(`Unexpected fillStyle color format "${fillStyle}" when drawing pattern glyph`);
+ }
+
+ for (let y = 0; y < height; y++) {
+ for (let x = 0; x < width; x++) {
+ imageData.data[(y * width + x) * 4 ] = r;
+ imageData.data[(y * width + x) * 4 + 1] = g;
+ imageData.data[(y * width + x) * 4 + 2] = b;
+ imageData.data[(y * width + x) * 4 + 3] = charDefinition[y][x] * (a * 255);
+ }
+ }
+ tmpCtx.putImageData(imageData, 0, 0);
+ pattern = throwIfFalsy(ctx.createPattern(tmpCanvas, null));
+ patternSet.set(fillStyle, pattern);
+ }
+ ctx.fillStyle = pattern;
+ ctx.fillRect(xOffset, yOffset, scaledCellWidth, scaledCellHeight);
+}
+
+/**
+ * Draws the following box drawing characters by mapping a subset of SVG d attribute instructions to
+ * canvas draw calls.
+ *
+ * Box styles: ┎┰┒┍┯┑╓╥╖╒╤╕ ┏┳┓┌┲┓┌┬┐┏┱┐
+ * ┌─┬─┐ ┏━┳━┓ ╔═╦═╗ ┠╂┨┝┿┥╟╫╢╞╪╡ ┡╇┩├╊┫┢╈┪┣╉┤
+ * │ │ │ ┃ ┃ ┃ ║ ║ ║ ┖┸┚┕┷┙╙╨╜╘╧╛ └┴┘└┺┛┗┻┛┗┹┘
+ * ├─┼─┤ ┣━╋━┫ ╠═╬═╣ ┏┱┐┌┲┓┌┬┐┌┬┐ ┏┳┓┌┮┓┌┬┐┏┭┐
+ * │ │ │ ┃ ┃ ┃ ║ ║ ║ ┡╃┤├╄┩├╆┪┢╅┤ ┞╀┦├┾┫┟╁┧┣┽┤
+ * └─┴─┘ ┗━┻━┛ ╚═╩═╝ └┴┘└┴┘└┺┛┗┹┘ └┴┘└┶┛┗┻┛┗┵┘
+ *
+ * Other:
+ * ╭─╮ ╲ ╱ ╷╻╎╏┆┇┊┋ ╺╾╴ ╌╌╌ ┄┄┄ ┈┈┈
+ * │ │ ╳ ╽╿╎╏┆┇┊┋ ╶╼╸ ╍╍╍ ┅┅┅ ┉┉┉
+ * ╰─╯ ╱ ╲ ╹╵╎╏┆┇┊┋
+ *
+ * All box drawing characters:
+ * ─ ━ │ ┃ ┄ ┅ ┆ ┇ ┈ ┉ ┊ ┋ ┌ ┍ ┎ ┏
+ * ┐ ┑ ┒ ┓ └ ┕ ┖ ┗ ┘ ┙ ┚ ┛ ├ ┝ ┞ ┟
+ * ┠ ┡ ┢ ┣ ┤ ┥ ┦ ┧ ┨ ┩ ┪ ┫ ┬ ┭ ┮ ┯
+ * ┰ ┱ ┲ ┳ ┴ ┵ ┶ ┷ ┸ ┹ ┺ ┻ ┼ ┽ ┾ ┿
+ * ╀ ╁ ╂ ╃ ╄ ╅ ╆ ╇ ╈ ╉ ╊ ╋ ╌ ╍ ╎ ╏
+ * ═ ║ ╒ ╓ ╔ ╕ ╖ ╗ ╘ ╙ ╚ ╛ ╜ ╝ ╞ ╟
+ * ╠ ╡ ╢ ╣ ╤ ╥ ╦ ╧ ╨ ╩ ╪ ╫ ╬ ╭ ╮ ╯
+ * ╰ ╱ ╲ ╳ ╴ ╵ ╶ ╷ ╸ ╹ ╺ ╻ ╼ ╽ ╾ ╿
+ *
+ * ---
+ *
+ * Box drawing alignment tests: █
+ * ▉
+ * ╔══╦══╗ ┌──┬──┐ ╭──┬──╮ ╭──┬──╮ ┏━━┳━━┓ ┎┒┏┑ ╷ ╻ ┏┯┓ ┌┰┐ ▊ ╱╲╱╲╳╳╳
+ * ║┌─╨─┐║ │╔═╧═╗│ │╒═╪═╕│ │╓─╁─╖│ ┃┌─╂─┐┃ ┗╃╄┙ ╶┼╴╺╋╸┠┼┨ ┝╋┥ ▋ ╲╱╲╱╳╳╳
+ * ║│╲ ╱│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╿ │┃ ┍╅╆┓ ╵ ╹ ┗┷┛ └┸┘ ▌ ╱╲╱╲╳╳╳
+ * ╠╡ ╳ ╞╣ ├╢ ╟┤ ├┼─┼─┼┤ ├╫─╂─╫┤ ┣┿╾┼╼┿┫ ┕┛┖┚ ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳
+ * ║│╱ ╲│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▎
+ * ║└─╥─┘║ │╚═╤═╝│ │╘═╪═╛│ │╙─╀─╜│ ┃└─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▏
+ * ╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█
+ *
+ * Source: https://www.w3.org/2001/06/utf-8-test/UTF-8-demo.html
+ */
+function drawBoxDrawingChar(
+ ctx: CanvasRenderingContext2D,
+ charDefinition: { [fontWeight: number]: string | ((xp: number, yp: number) => string) },
+ xOffset: number,
+ yOffset: number,
+ scaledCellWidth: number,
+ scaledCellHeight: number
+): void {
+ ctx.strokeStyle = ctx.fillStyle;
+ for (const [fontWeight, instructions] of Object.entries(charDefinition)) {
+ ctx.beginPath();
+ ctx.lineWidth = window.devicePixelRatio * Number.parseInt(fontWeight);
+ let actualInstructions: string;
+ if (typeof instructions === 'function') {
+ const xp = .15;
+ const yp = .15 / scaledCellHeight * scaledCellWidth;
+ actualInstructions = instructions(xp, yp);
+ } else {
+ actualInstructions = instructions;
+ }
+ for (const instruction of actualInstructions.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));
+ }
+ ctx.stroke();
+ ctx.closePath();
+ }
+}
+
+function clamp(value: number, max: number, min: number = 0): number {
+ return Math.max(Math.min(value, max), min);
+}
+
+const svgToCanvasInstructionMap: { [index: string]: any } = {
+ 'C': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.bezierCurveTo(args[0], args[1], args[2], args[3], args[4], args[5]),
+ 'L': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.lineTo(args[0], args[1]),
+ 'M': (ctx: CanvasRenderingContext2D, args: number[]) => ctx.moveTo(args[0], args[1])
+};
+
+function translateArgs(args: string[], cellWidth: number, cellHeight: number, xOffset: number, yOffset: number): number[] {
+ const result = args.map(e => parseFloat(e) || parseInt(e));
+
+ if (result.length < 2) {
+ throw new Error('Too few arguments for instruction');
+ }
+
+ for (let x = 0; x < result.length; x += 2) {
+ // Translate from 0-1 to 0-cellWidth
+ result[x] *= cellWidth;
+ // 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;
+ }
+
+ for (let y = 1; y < result.length; y += 2) {
+ // Translate from 0-1 to 0-cellHeight
+ result[y] *= cellHeight;
+ // Ensure coordinate doesn't escape cell bounds and round to the nearest 0.5 to ensure a crisp
+ // line at 100% devicePixelRatio
+ if (result[y] !== 0) {
+ result[y] = clamp(Math.round(result[y] + 0.5) - 0.5, cellHeight, 0);
+ }
+ // Apply the cell's offset (ie. x*cellHeight)
+ result[y] += yOffset;
+ }
+
+ return result;
+}
diff --git a/node_modules/xterm/src/browser/renderer/GridCache.ts b/node_modules/xterm/src/browser/renderer/GridCache.ts
new file mode 100644
index 0000000..b48798d
--- /dev/null
+++ b/node_modules/xterm/src/browser/renderer/GridCache.ts
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+export class GridCache<T> {
+ public cache: (T | undefined)[][];
+
+ public constructor() {
+ this.cache = [];
+ }
+
+ public resize(width: number, height: number): void {
+ for (let x = 0; x < width; x++) {
+ if (this.cache.length <= x) {
+ this.cache.push([]);
+ }
+ for (let y = this.cache[x].length; y < height; y++) {
+ this.cache[x].push(undefined);
+ }
+ this.cache[x].length = height;
+ }
+ this.cache.length = width;
+ }
+
+ public clear(): void {
+ for (let x = 0; x < this.cache.length; x++) {
+ for (let y = 0; y < this.cache[x].length; y++) {
+ this.cache[x][y] = undefined;
+ }
+ }
+ }
+}
diff --git a/node_modules/xterm/src/browser/renderer/LinkRenderLayer.ts b/node_modules/xterm/src/browser/renderer/LinkRenderLayer.ts
new file mode 100644
index 0000000..2492f92
--- /dev/null
+++ b/node_modules/xterm/src/browser/renderer/LinkRenderLayer.ts
@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IRenderDimensions } from 'browser/renderer/Types';
+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';
+
+export class LinkRenderLayer extends BaseRenderLayer {
+ private _state: ILinkifierEvent | undefined;
+
+ constructor(
+ container: HTMLElement,
+ zIndex: number,
+ colors: IColorSet,
+ rendererId: number,
+ linkifier: ILinkifier,
+ linkifier2: ILinkifier2,
+ @IBufferService bufferService: IBufferService,
+ @IOptionsService optionsService: IOptionsService
+ ) {
+ super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService);
+ linkifier.onShowLinkUnderline(e => this._onShowLinkUnderline(e));
+ linkifier.onHideLinkUnderline(e => this._onHideLinkUnderline(e));
+
+ linkifier2.onShowLinkUnderline(e => this._onShowLinkUnderline(e));
+ linkifier2.onHideLinkUnderline(e => this._onHideLinkUnderline(e));
+ }
+
+ public resize(dim: IRenderDimensions): void {
+ super.resize(dim);
+ // Resizing the canvas discards the contents of the canvas so clear state
+ this._state = undefined;
+ }
+
+ public reset(): void {
+ this._clearCurrentLink();
+ }
+
+ private _clearCurrentLink(): void {
+ if (this._state) {
+ this._clearCells(this._state.x1, this._state.y1, this._state.cols - this._state.x1, 1);
+ const middleRowCount = this._state.y2 - this._state.y1 - 1;
+ if (middleRowCount > 0) {
+ this._clearCells(0, this._state.y1 + 1, this._state.cols, middleRowCount);
+ }
+ this._clearCells(0, this._state.y2, this._state.x2, 1);
+ this._state = undefined;
+ }
+ }
+
+ private _onShowLinkUnderline(e: ILinkifierEvent): void {
+ if (e.fg === INVERTED_DEFAULT_COLOR) {
+ this._ctx.fillStyle = this._colors.background.css;
+ } else if (e.fg && is256Color(e.fg)) {
+ // 256 color support
+ this._ctx.fillStyle = this._colors.ansi[e.fg].css;
+ } else {
+ this._ctx.fillStyle = this._colors.foreground.css;
+ }
+
+ if (e.y1 === e.y2) {
+ // Single line link
+ this._fillBottomLineAtCells(e.x1, e.y1, e.x2 - e.x1);
+ } else {
+ // Multi-line link
+ this._fillBottomLineAtCells(e.x1, e.y1, e.cols - e.x1);
+ for (let y = e.y1 + 1; y < e.y2; y++) {
+ this._fillBottomLineAtCells(0, y, e.cols);
+ }
+ this._fillBottomLineAtCells(0, e.y2, e.x2);
+ }
+ this._state = e;
+ }
+
+ private _onHideLinkUnderline(e: ILinkifierEvent): void {
+ this._clearCurrentLink();
+ }
+}
diff --git a/node_modules/xterm/src/browser/renderer/Renderer.ts b/node_modules/xterm/src/browser/renderer/Renderer.ts
new file mode 100644
index 0000000..7a64257
--- /dev/null
+++ b/node_modules/xterm/src/browser/renderer/Renderer.ts
@@ -0,0 +1,215 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { TextRenderLayer } from 'browser/renderer/TextRenderLayer';
+import { SelectionRenderLayer } from 'browser/renderer/SelectionRenderLayer';
+import { CursorRenderLayer } from 'browser/renderer/CursorRenderLayer';
+import { IRenderLayer, IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types';
+import { LinkRenderLayer } from 'browser/renderer/LinkRenderLayer';
+import { Disposable } from 'common/Lifecycle';
+import { IColorSet, ILinkifier, ILinkifier2 } from 'browser/Types';
+import { ICharSizeService, ICoreBrowserService } from 'browser/services/Services';
+import { IBufferService, IOptionsService, ICoreService, IInstantiationService } from 'common/services/Services';
+import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache';
+import { EventEmitter, IEvent } from 'common/EventEmitter';
+
+let nextRendererId = 1;
+
+export class Renderer extends Disposable implements IRenderer {
+ private _id = nextRendererId++;
+
+ private _renderLayers: IRenderLayer[];
+ private _devicePixelRatio: number;
+
+ public dimensions: IRenderDimensions;
+
+ private _onRequestRedraw = new EventEmitter<IRequestRedrawEvent>();
+ public get onRequestRedraw(): IEvent<IRequestRedrawEvent> { return this._onRequestRedraw.event; }
+
+ constructor(
+ private _colors: IColorSet,
+ private readonly _screenElement: HTMLElement,
+ linkifier: ILinkifier,
+ linkifier2: ILinkifier2,
+ @IInstantiationService instantiationService: IInstantiationService,
+ @IBufferService private readonly _bufferService: IBufferService,
+ @ICharSizeService private readonly _charSizeService: ICharSizeService,
+ @IOptionsService private readonly _optionsService: IOptionsService
+ ) {
+ super();
+ const allowTransparency = this._optionsService.rawOptions.allowTransparency;
+ this._renderLayers = [
+ instantiationService.createInstance(TextRenderLayer, this._screenElement, 0, this._colors, allowTransparency, this._id),
+ instantiationService.createInstance(SelectionRenderLayer, this._screenElement, 1, this._colors, this._id),
+ instantiationService.createInstance(LinkRenderLayer, this._screenElement, 2, this._colors, this._id, linkifier, linkifier2),
+ instantiationService.createInstance(CursorRenderLayer, this._screenElement, 3, this._colors, this._id, this._onRequestRedraw)
+ ];
+ this.dimensions = {
+ scaledCharWidth: 0,
+ scaledCharHeight: 0,
+ scaledCellWidth: 0,
+ scaledCellHeight: 0,
+ scaledCharLeft: 0,
+ scaledCharTop: 0,
+ scaledCanvasWidth: 0,
+ scaledCanvasHeight: 0,
+ canvasWidth: 0,
+ canvasHeight: 0,
+ actualCellWidth: 0,
+ actualCellHeight: 0
+ };
+ this._devicePixelRatio = window.devicePixelRatio;
+ this._updateDimensions();
+ this.onOptionsChanged();
+ }
+
+ public dispose(): void {
+ for (const l of this._renderLayers) {
+ l.dispose();
+ }
+ super.dispose();
+ removeTerminalFromCache(this._id);
+ }
+
+ public onDevicePixelRatioChange(): void {
+ // If the device pixel ratio changed, the char atlas needs to be regenerated
+ // and the terminal needs to refreshed
+ if (this._devicePixelRatio !== window.devicePixelRatio) {
+ this._devicePixelRatio = window.devicePixelRatio;
+ this.onResize(this._bufferService.cols, this._bufferService.rows);
+ }
+ }
+
+ public setColors(colors: IColorSet): void {
+ this._colors = colors;
+ // Clear layers and force a full render
+ for (const l of this._renderLayers) {
+ l.setColors(this._colors);
+ l.reset();
+ }
+ }
+
+ public onResize(cols: number, rows: number): void {
+ // Update character and canvas dimensions
+ this._updateDimensions();
+
+ // Resize all render layers
+ for (const l of this._renderLayers) {
+ l.resize(this.dimensions);
+ }
+
+ // Resize the screen
+ this._screenElement.style.width = `${this.dimensions.canvasWidth}px`;
+ this._screenElement.style.height = `${this.dimensions.canvasHeight}px`;
+ }
+
+ public onCharSizeChanged(): void {
+ this.onResize(this._bufferService.cols, this._bufferService.rows);
+ }
+
+ public onBlur(): void {
+ this._runOperation(l => l.onBlur());
+ }
+
+ public onFocus(): void {
+ this._runOperation(l => l.onFocus());
+ }
+
+ public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void {
+ this._runOperation(l => l.onSelectionChanged(start, end, columnSelectMode));
+ }
+
+ public onCursorMove(): void {
+ this._runOperation(l => l.onCursorMove());
+ }
+
+ public onOptionsChanged(): void {
+ this._runOperation(l => l.onOptionsChanged());
+ }
+
+ public clear(): void {
+ this._runOperation(l => l.reset());
+ }
+
+ private _runOperation(operation: (layer: IRenderLayer) => void): void {
+ for (const l of this._renderLayers) {
+ operation(l);
+ }
+ }
+
+ /**
+ * Performs the refresh loop callback, calling refresh only if a refresh is
+ * necessary before queueing up the next one.
+ */
+ public renderRows(start: number, end: number): void {
+ for (const l of this._renderLayers) {
+ l.onGridChanged(start, end);
+ }
+ }
+
+ public clearTextureAtlas(): void {
+ for (const layer of this._renderLayers) {
+ layer.clearTextureAtlas();
+ }
+ }
+
+ /**
+ * Recalculates the character and canvas dimensions.
+ */
+ private _updateDimensions(): void {
+ if (!this._charSizeService.hasValidSize) {
+ return;
+ }
+
+ // Calculate the scaled character width. Width is floored as it must be
+ // drawn to an integer grid in order for the CharAtlas "stamps" to not be
+ // blurry. When text is drawn to the grid not using the CharAtlas, it is
+ // clipped to ensure there is no overlap with the next cell.
+ this.dimensions.scaledCharWidth = Math.floor(this._charSizeService.width * window.devicePixelRatio);
+
+ // Calculate the scaled character height. Height is ceiled in case
+ // devicePixelRatio is a floating point number in order to ensure there is
+ // enough space to draw the character to the cell.
+ this.dimensions.scaledCharHeight = Math.ceil(this._charSizeService.height * window.devicePixelRatio);
+
+ // Calculate the scaled cell height, if lineHeight is not 1 then the value
+ // 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._optionsService.rawOptions.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._optionsService.rawOptions.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._optionsService.rawOptions.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._optionsService.rawOptions.letterSpacing / 2);
+
+ // Recalculate the canvas dimensions; scaled* define the actual number of
+ // pixel in the canvas
+ this.dimensions.scaledCanvasHeight = this._bufferService.rows * this.dimensions.scaledCellHeight;
+ this.dimensions.scaledCanvasWidth = this._bufferService.cols * this.dimensions.scaledCellWidth;
+
+ // The the size of the canvas on the page. It's very important that this
+ // rounds to nearest integer and not ceils as browsers often set
+ // window.devicePixelRatio as something like 1.100000023841858, when it's
+ // actually 1.1. Ceiling causes blurriness as the backing canvas image is 1
+ // pixel too large for the canvas element size.
+ this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / window.devicePixelRatio);
+ this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / window.devicePixelRatio);
+
+ // Get the _actual_ dimensions of an individual cell. This needs to be
+ // derived from the canvasWidth/Height calculated above which takes into
+ // account window.devicePixelRatio. ICharSizeService.width/height by itself
+ // is insufficient when the page is not at 100% zoom level as it's measured
+ // in CSS pixels, but the actual char size on the canvas can differ.
+ this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._bufferService.rows;
+ this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._bufferService.cols;
+ }
+}
diff --git a/node_modules/xterm/src/browser/renderer/RendererUtils.ts b/node_modules/xterm/src/browser/renderer/RendererUtils.ts
new file mode 100644
index 0000000..48fd26a
--- /dev/null
+++ b/node_modules/xterm/src/browser/renderer/RendererUtils.ts
@@ -0,0 +1,11 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+export function throwIfFalsy<T>(value: T | undefined | null): T {
+ if (!value) {
+ throw new Error('value must not be falsy');
+ }
+ return value;
+}
diff --git a/node_modules/xterm/src/browser/renderer/SelectionRenderLayer.ts b/node_modules/xterm/src/browser/renderer/SelectionRenderLayer.ts
new file mode 100644
index 0000000..9054e3c
--- /dev/null
+++ b/node_modules/xterm/src/browser/renderer/SelectionRenderLayer.ts
@@ -0,0 +1,128 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IRenderDimensions } from 'browser/renderer/Types';
+import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer';
+import { IColorSet } from 'browser/Types';
+import { IBufferService, IOptionsService } from 'common/services/Services';
+
+interface ISelectionState {
+ start?: [number, number];
+ end?: [number, number];
+ columnSelectMode?: boolean;
+ ydisp?: number;
+}
+
+export class SelectionRenderLayer extends BaseRenderLayer {
+ private _state!: ISelectionState;
+
+ constructor(
+ container: HTMLElement,
+ zIndex: number,
+ colors: IColorSet,
+ rendererId: number,
+ @IBufferService bufferService: IBufferService,
+ @IOptionsService optionsService: IOptionsService
+ ) {
+ super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService);
+ this._clearState();
+ }
+
+ private _clearState(): void {
+ this._state = {
+ start: undefined,
+ end: undefined,
+ columnSelectMode: undefined,
+ ydisp: undefined
+ };
+ }
+
+ public resize(dim: IRenderDimensions): void {
+ super.resize(dim);
+ // Resizing the canvas discards the contents of the canvas so clear state
+ this._clearState();
+ }
+
+ public reset(): void {
+ if (this._state.start && this._state.end) {
+ this._clearState();
+ this._clearAll();
+ }
+ }
+
+ public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
+ // Selection has not changed
+ if (!this._didStateChange(start, end, columnSelectMode, this._bufferService.buffer.ydisp)) {
+ return;
+ }
+
+ // Remove all selections
+ this._clearAll();
+
+ // Selection does not exist
+ if (!start || !end) {
+ this._clearState();
+ return;
+ }
+
+ // Translate from buffer position to viewport position
+ const viewportStartRow = start[1] - this._bufferService.buffer.ydisp;
+ const viewportEndRow = end[1] - this._bufferService.buffer.ydisp;
+ const viewportCappedStartRow = Math.max(viewportStartRow, 0);
+ const viewportCappedEndRow = Math.min(viewportEndRow, this._bufferService.rows - 1);
+
+ // No need to draw the selection
+ if (viewportCappedStartRow >= this._bufferService.rows || viewportCappedEndRow < 0) {
+ this._state.ydisp = this._bufferService.buffer.ydisp;
+ return;
+ }
+
+ this._ctx.fillStyle = this._colors.selectionTransparent.css;
+
+ if (columnSelectMode) {
+ const startCol = start[0];
+ const width = end[0] - startCol;
+ const height = viewportCappedEndRow - viewportCappedStartRow + 1;
+ this._fillCells(startCol, viewportCappedStartRow, width, height);
+ } else {
+ // Draw first row
+ const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
+ const startRowEndCol = viewportCappedStartRow === viewportEndRow ? end[0] : this._bufferService.cols;
+ this._fillCells(startCol, viewportCappedStartRow, startRowEndCol - startCol, 1);
+
+ // Draw middle rows
+ const middleRowsCount = Math.max(viewportCappedEndRow - viewportCappedStartRow - 1, 0);
+ this._fillCells(0, viewportCappedStartRow + 1, this._bufferService.cols, middleRowsCount);
+
+ // Draw final row
+ if (viewportCappedStartRow !== viewportCappedEndRow) {
+ // Only draw viewportEndRow if it's not the same as viewportStartRow
+ const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;
+ this._fillCells(0, viewportCappedEndRow, endCol, 1);
+ }
+ }
+
+ // Save state for next render
+ this._state.start = [start[0], start[1]];
+ this._state.end = [end[0], end[1]];
+ this._state.columnSelectMode = columnSelectMode;
+ this._state.ydisp = this._bufferService.buffer.ydisp;
+ }
+
+ private _didStateChange(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean, ydisp: number): boolean {
+ return !this._areCoordinatesEqual(start, this._state.start) ||
+ !this._areCoordinatesEqual(end, this._state.end) ||
+ columnSelectMode !== this._state.columnSelectMode ||
+ ydisp !== this._state.ydisp;
+ }
+
+ private _areCoordinatesEqual(coord1: [number, number] | undefined, coord2: [number, number] | undefined): boolean {
+ if (!coord1 || !coord2) {
+ return false;
+ }
+
+ return coord1[0] === coord2[0] && coord1[1] === coord2[1];
+ }
+}
diff --git a/node_modules/xterm/src/browser/renderer/TextRenderLayer.ts b/node_modules/xterm/src/browser/renderer/TextRenderLayer.ts
new file mode 100644
index 0000000..33d942f
--- /dev/null
+++ b/node_modules/xterm/src/browser/renderer/TextRenderLayer.ts
@@ -0,0 +1,330 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IRenderDimensions } from 'browser/renderer/Types';
+import { CharData, ICellData } from 'common/Types';
+import { GridCache } from 'browser/renderer/GridCache';
+import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer';
+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 { ICharacterJoinerService } from 'browser/services/Services';
+import { JoinedCellData } from 'browser/services/CharacterJoinerService';
+
+/**
+ * This CharData looks like a null character, which will forc a clear and render
+ * when the character changes (a regular space ' ' character may not as it's
+ * drawn state is a cleared cell).
+ */
+// const OVERLAP_OWNED_CHAR_DATA: CharData = [null, '', 0, -1];
+
+export class TextRenderLayer extends BaseRenderLayer {
+ private _state: GridCache<CharData>;
+ private _characterWidth: number = 0;
+ private _characterFont: string = '';
+ private _characterOverlapCache: { [key: string]: boolean } = {};
+ private _workCell = new CellData();
+
+ constructor(
+ container: HTMLElement,
+ zIndex: number,
+ colors: IColorSet,
+ alpha: boolean,
+ rendererId: number,
+ @IBufferService bufferService: IBufferService,
+ @IOptionsService optionsService: IOptionsService,
+ @ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService
+ ) {
+ super(container, 'text', zIndex, alpha, colors, rendererId, bufferService, optionsService);
+ this._state = new GridCache<CharData>();
+ }
+
+ public resize(dim: IRenderDimensions): void {
+ super.resize(dim);
+
+ // Clear the character width cache if the font or width has changed
+ const terminalFont = this._getFont(false, false);
+ if (this._characterWidth !== dim.scaledCharWidth || this._characterFont !== terminalFont) {
+ this._characterWidth = dim.scaledCharWidth;
+ this._characterFont = terminalFont;
+ this._characterOverlapCache = {};
+ }
+ // Resizing the canvas discards the contents of the canvas so clear state
+ this._state.clear();
+ this._state.resize(this._bufferService.cols, this._bufferService.rows);
+ }
+
+ public reset(): void {
+ this._state.clear();
+ this._clearAll();
+ }
+
+ private _forEachCell(
+ firstRow: number,
+ lastRow: number,
+ callback: (
+ cell: ICellData,
+ x: number,
+ y: number
+ ) => void
+ ): void {
+ for (let y = firstRow; y <= lastRow; y++) {
+ const row = y + this._bufferService.buffer.ydisp;
+ const line = this._bufferService.buffer.lines.get(row);
+ const joinedRanges = this._characterJoinerService.getJoinedCharacters(row);
+ for (let x = 0; x < this._bufferService.cols; x++) {
+ line!.loadCell(x, this._workCell);
+ let cell = this._workCell;
+
+ // If true, indicates that the current character(s) to draw were joined.
+ let isJoined = false;
+ let lastCharX = x;
+
+ // The character to the left is a wide character, drawing is owned by
+ // the char at x-1
+ if (cell.getWidth() === 0) {
+ continue;
+ }
+
+ // Process any joined character ranges as needed. Because of how the
+ // ranges are produced, we know that they are valid for the characters
+ // and attributes of our input.
+ if (joinedRanges.length > 0 && x === joinedRanges[0][0]) {
+ isJoined = true;
+ const range = joinedRanges.shift()!;
+
+ // We already know the exact start and end column of the joined range,
+ // so we get the string and width representing it directly
+ cell = new JoinedCellData(
+ this._workCell,
+ line!.translateToString(true, range[0], range[1]),
+ range[1] - range[0]
+ );
+
+ // Skip over the cells occupied by this range in the loop
+ lastCharX = range[1] - 1;
+ }
+
+ // If the character is an overlapping char and the character to the
+ // right is a space, take ownership of the cell to the right. We skip
+ // this check for joined characters because their rendering likely won't
+ // yield the same result as rendering the last character individually.
+ if (!isJoined && this._isOverlapping(cell)) {
+ // If the character is overlapping, we want to force a re-render on every
+ // frame. This is specifically to work around the case where two
+ // overlaping chars `a` and `b` are adjacent, the cursor is moved to b and a
+ // space is added. Without this, the first half of `b` would never
+ // get removed, and `a` would not re-render because it thinks it's
+ // already in the correct state.
+ // this._state.cache[x][y] = OVERLAP_OWNED_CHAR_DATA;
+ if (lastCharX < line!.length - 1 && line!.getCodePoint(lastCharX + 1) === NULL_CELL_CODE) {
+ // patch width to 2
+ cell.content &= ~Content.WIDTH_MASK;
+ cell.content |= 2 << Content.WIDTH_SHIFT;
+ // this._clearChar(x + 1, y);
+ // The overlapping char's char data will force a clear and render when the
+ // overlapping char is no longer to the left of the character and also when
+ // the space changes to another character.
+ // this._state.cache[x + 1][y] = OVERLAP_OWNED_CHAR_DATA;
+ }
+ }
+
+ callback(
+ cell,
+ x,
+ y
+ );
+
+ x = lastCharX;
+ }
+ }
+ }
+
+ /**
+ * Draws the background for a specified range of columns. Tries to batch adjacent cells of the
+ * same color together to reduce draw calls.
+ */
+ private _drawBackground(firstRow: number, lastRow: number): void {
+ const ctx = this._ctx;
+ const cols = this._bufferService.cols;
+ let startX: number = 0;
+ let startY: number = 0;
+ let prevFillStyle: string | null = null;
+
+ ctx.save();
+
+ this._forEachCell(firstRow, lastRow, (cell, x, y) => {
+ // libvte and xterm both draw the background (but not foreground) of invisible characters,
+ // so we should too.
+ let nextFillStyle = null; // null represents default background color
+
+ if (cell.isInverse()) {
+ if (cell.isFgDefault()) {
+ nextFillStyle = this._colors.foreground.css;
+ } else if (cell.isFgRGB()) {
+ nextFillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`;
+ } else {
+ nextFillStyle = this._colors.ansi[cell.getFgColor()].css;
+ }
+ } else if (cell.isBgRGB()) {
+ nextFillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`;
+ } else if (cell.isBgPalette()) {
+ nextFillStyle = this._colors.ansi[cell.getBgColor()].css;
+ }
+
+ if (prevFillStyle === null) {
+ // This is either the first iteration, or the default background was set. Either way, we
+ // don't need to draw anything.
+ startX = x;
+ startY = y;
+ }
+
+ if (y !== startY) {
+ // our row changed, draw the previous row
+ ctx.fillStyle = prevFillStyle || '';
+ this._fillCells(startX, startY, cols - startX, 1);
+ startX = x;
+ startY = y;
+ } else if (prevFillStyle !== nextFillStyle) {
+ // our color changed, draw the previous characters in this row
+ ctx.fillStyle = prevFillStyle || '';
+ this._fillCells(startX, startY, x - startX, 1);
+ startX = x;
+ startY = y;
+ }
+
+ prevFillStyle = nextFillStyle;
+ });
+
+ // flush the last color we encountered
+ if (prevFillStyle !== null) {
+ ctx.fillStyle = prevFillStyle;
+ this._fillCells(startX, startY, cols - startX, 1);
+ }
+
+ ctx.restore();
+ }
+
+ private _drawForeground(firstRow: number, lastRow: number): void {
+ this._forEachCell(firstRow, lastRow, (cell, x, y) => {
+ if (cell.isInvisible()) {
+ return;
+ }
+ this._drawChars(cell, x, y);
+ if (cell.isUnderline() || cell.isStrikethrough()) {
+ this._ctx.save();
+
+ if (cell.isInverse()) {
+ if (cell.isBgDefault()) {
+ this._ctx.fillStyle = this._colors.background.css;
+ } else if (cell.isBgRGB()) {
+ this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`;
+ } else {
+ let bg = cell.getBgColor();
+ if (this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && bg < 8) {
+ bg += 8;
+ }
+ this._ctx.fillStyle = this._colors.ansi[bg].css;
+ }
+ } else {
+ if (cell.isFgDefault()) {
+ this._ctx.fillStyle = this._colors.foreground.css;
+ } else if (cell.isFgRGB()) {
+ this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`;
+ } else {
+ let fg = cell.getFgColor();
+ if (this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && fg < 8) {
+ fg += 8;
+ }
+ this._ctx.fillStyle = this._colors.ansi[fg].css;
+ }
+ }
+
+ if (cell.isStrikethrough()) {
+ this._fillMiddleLineAtCells(x, y, cell.getWidth());
+ }
+ if (cell.isUnderline()) {
+ this._fillBottomLineAtCells(x, y, cell.getWidth());
+ }
+ this._ctx.restore();
+ }
+ });
+ }
+
+ public onGridChanged(firstRow: number, lastRow: number): void {
+ // Resize has not been called yet
+ if (this._state.cache.length === 0) {
+ return;
+ }
+
+ if (this._charAtlas) {
+ this._charAtlas.beginFrame();
+ }
+
+ this._clearCells(0, firstRow, this._bufferService.cols, lastRow - firstRow + 1);
+ this._drawBackground(firstRow, lastRow);
+ this._drawForeground(firstRow, lastRow);
+ }
+
+ public onOptionsChanged(): void {
+ this._setTransparency(this._optionsService.rawOptions.allowTransparency);
+ }
+
+ /**
+ * Whether a character is overlapping to the next cell.
+ */
+ private _isOverlapping(cell: ICellData): boolean {
+ // Only single cell characters can be overlapping, rendering issues can
+ // occur without this check
+ if (cell.getWidth() !== 1) {
+ return false;
+ }
+
+ // We assume that any ascii character will not overlap
+ if (cell.getCode() < 256) {
+ return false;
+ }
+
+ const chars = cell.getChars();
+
+ // Deliver from cache if available
+ if (this._characterOverlapCache.hasOwnProperty(chars)) {
+ return this._characterOverlapCache[chars];
+ }
+
+ // Setup the font
+ this._ctx.save();
+ this._ctx.font = this._characterFont;
+
+ // Measure the width of the character, but Math.floor it
+ // because that is what the renderer does when it calculates
+ // the character dimensions we are comparing against
+ const overlaps = Math.floor(this._ctx.measureText(chars).width) > this._characterWidth;
+
+ // Restore the original context
+ this._ctx.restore();
+
+ // Cache and return
+ this._characterOverlapCache[chars] = overlaps;
+ return overlaps;
+ }
+
+ /**
+ * Clear the charcater at the cell specified.
+ * @param x The column of the char.
+ * @param y The row of the char.
+ */
+ // private _clearChar(x: number, y: number): void {
+ // let colsToClear = 1;
+ // // Clear the adjacent character if it was wide
+ // const state = this._state.cache[x][y];
+ // if (state && state[CHAR_DATA_WIDTH_INDEX] === 2) {
+ // colsToClear = 2;
+ // }
+ // this.clearCells(x, y, colsToClear, 1);
+ // }
+}
diff --git a/node_modules/xterm/src/browser/renderer/Types.d.ts b/node_modules/xterm/src/browser/renderer/Types.d.ts
new file mode 100644
index 0000000..6818a92
--- /dev/null
+++ b/node_modules/xterm/src/browser/renderer/Types.d.ts
@@ -0,0 +1,109 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IDisposable } from 'common/Types';
+import { IColorSet } from 'browser/Types';
+import { IEvent } from 'common/EventEmitter';
+
+export interface IRenderDimensions {
+ scaledCharWidth: number;
+ scaledCharHeight: number;
+ scaledCellWidth: number;
+ scaledCellHeight: number;
+ scaledCharLeft: number;
+ scaledCharTop: number;
+ scaledCanvasWidth: number;
+ scaledCanvasHeight: number;
+ canvasWidth: number;
+ canvasHeight: number;
+ actualCellWidth: number;
+ actualCellHeight: number;
+}
+
+export interface IRequestRedrawEvent {
+ start: number;
+ end: number;
+}
+
+/**
+ * Note that IRenderer implementations should emit the refresh event after
+ * rendering rows to the screen.
+ */
+export interface IRenderer extends IDisposable {
+ readonly dimensions: IRenderDimensions;
+
+ /**
+ * Fires when the renderer is requesting to be redrawn on the next animation
+ * frame but is _not_ a result of content changing (eg. selection changes).
+ */
+ readonly onRequestRedraw: IEvent<IRequestRedrawEvent>;
+
+ dispose(): void;
+ setColors(colors: IColorSet): void;
+ onDevicePixelRatioChange(): void;
+ onResize(cols: number, rows: number): void;
+ onCharSizeChanged(): void;
+ onBlur(): void;
+ onFocus(): void;
+ onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void;
+ onCursorMove(): void;
+ onOptionsChanged(): void;
+ clear(): void;
+ renderRows(start: number, end: number): void;
+ clearTextureAtlas?(): void;
+}
+
+export interface IRenderLayer extends IDisposable {
+ /**
+ * Called when the terminal loses focus.
+ */
+ onBlur(): void;
+
+ /**
+ * * Called when the terminal gets focus.
+ */
+ onFocus(): void;
+
+ /**
+ * Called when the cursor is moved.
+ */
+ onCursorMove(): void;
+
+ /**
+ * Called when options change.
+ */
+ onOptionsChanged(): void;
+
+ /**
+ * Called when the theme changes.
+ */
+ setColors(colorSet: IColorSet): void;
+
+ /**
+ * Called when the data in the grid has changed (or needs to be rendered
+ * again).
+ */
+ onGridChanged(startRow: number, endRow: number): void;
+
+ /**
+ * Calls when the selection changes.
+ */
+ onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void;
+
+ /**
+ * Resize the render layer.
+ */
+ resize(dim: IRenderDimensions): void;
+
+ /**
+ * Clear the state of the render layer.
+ */
+ reset(): void;
+
+ /**
+ * Clears the texture atlas.
+ */
+ clearTextureAtlas(): void;
+}
diff --git a/node_modules/xterm/src/browser/renderer/atlas/BaseCharAtlas.ts b/node_modules/xterm/src/browser/renderer/atlas/BaseCharAtlas.ts
new file mode 100644
index 0000000..83c30d2
--- /dev/null
+++ b/node_modules/xterm/src/browser/renderer/atlas/BaseCharAtlas.ts
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IGlyphIdentifier } from 'browser/renderer/atlas/Types';
+import { IDisposable } from 'common/Types';
+
+export abstract class BaseCharAtlas implements IDisposable {
+ private _didWarmUp: boolean = false;
+
+ public dispose(): void { }
+
+ /**
+ * Perform any work needed to warm the cache before it can be used. May be called multiple times.
+ * Implement _doWarmUp instead if you only want to get called once.
+ */
+ public warmUp(): void {
+ if (!this._didWarmUp) {
+ this._doWarmUp();
+ this._didWarmUp = true;
+ }
+ }
+
+ /**
+ * Perform any work needed to warm the cache before it can be used. Used by the default
+ * implementation of warmUp(), and will only be called once.
+ */
+ private _doWarmUp(): void { }
+
+ public clear(): void { }
+
+ /**
+ * Called when we start drawing a new frame.
+ *
+ * TODO: We rely on this getting called by TextRenderLayer. This should really be called by
+ * Renderer instead, but we need to make Renderer the source-of-truth for the char atlas, instead
+ * of BaseRenderLayer.
+ */
+ public beginFrame(): void { }
+
+ /**
+ * May be called before warmUp finishes, however it is okay for the implementation to
+ * do nothing and return false in that case.
+ *
+ * @param ctx Where to draw the character onto.
+ * @param glyph Information about what to draw
+ * @param x The position on the context to start drawing at
+ * @param y The position on the context to start drawing at
+ * @returns The success state. True if we drew the character.
+ */
+ public abstract draw(
+ ctx: CanvasRenderingContext2D,
+ glyph: IGlyphIdentifier,
+ x: number,
+ y: number
+ ): boolean;
+}
diff --git a/node_modules/xterm/src/browser/renderer/atlas/CharAtlasCache.ts b/node_modules/xterm/src/browser/renderer/atlas/CharAtlasCache.ts
new file mode 100644
index 0000000..257835b
--- /dev/null
+++ b/node_modules/xterm/src/browser/renderer/atlas/CharAtlasCache.ts
@@ -0,0 +1,95 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { generateConfig, configEquals } from 'browser/renderer/atlas/CharAtlasUtils';
+import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas';
+import { DynamicCharAtlas } from 'browser/renderer/atlas/DynamicCharAtlas';
+import { ICharAtlasConfig } from 'browser/renderer/atlas/Types';
+import { IColorSet } from 'browser/Types';
+import { ITerminalOptions } from 'common/services/Services';
+
+interface ICharAtlasCacheEntry {
+ atlas: BaseCharAtlas;
+ config: ICharAtlasConfig;
+ // N.B. This implementation potentially holds onto copies of the terminal forever, so
+ // this may cause memory leaks.
+ ownedBy: number[];
+}
+
+const charAtlasCache: ICharAtlasCacheEntry[] = [];
+
+/**
+ * Acquires a char atlas, either generating a new one or returning an existing
+ * one that is in use by another terminal.
+ */
+export function acquireCharAtlas(
+ options: ITerminalOptions,
+ rendererId: number,
+ colors: IColorSet,
+ scaledCharWidth: number,
+ scaledCharHeight: number
+): BaseCharAtlas {
+ const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, options, colors);
+
+ // Check to see if the renderer already owns this config
+ for (let i = 0; i < charAtlasCache.length; i++) {
+ const entry = charAtlasCache[i];
+ const ownedByIndex = entry.ownedBy.indexOf(rendererId);
+ if (ownedByIndex >= 0) {
+ if (configEquals(entry.config, newConfig)) {
+ return entry.atlas;
+ }
+ // The configs differ, release the renderer from the entry
+ if (entry.ownedBy.length === 1) {
+ entry.atlas.dispose();
+ charAtlasCache.splice(i, 1);
+ } else {
+ entry.ownedBy.splice(ownedByIndex, 1);
+ }
+ break;
+ }
+ }
+
+ // Try match a char atlas from the cache
+ for (let i = 0; i < charAtlasCache.length; i++) {
+ const entry = charAtlasCache[i];
+ if (configEquals(entry.config, newConfig)) {
+ // Add the renderer to the cache entry and return
+ entry.ownedBy.push(rendererId);
+ return entry.atlas;
+ }
+ }
+
+ const newEntry: ICharAtlasCacheEntry = {
+ atlas: new DynamicCharAtlas(
+ document,
+ newConfig
+ ),
+ config: newConfig,
+ ownedBy: [rendererId]
+ };
+ charAtlasCache.push(newEntry);
+ return newEntry.atlas;
+}
+
+/**
+ * Removes a terminal reference from the cache, allowing its memory to be freed.
+ */
+export function removeTerminalFromCache(rendererId: number): void {
+ for (let i = 0; i < charAtlasCache.length; i++) {
+ const index = charAtlasCache[i].ownedBy.indexOf(rendererId);
+ if (index !== -1) {
+ if (charAtlasCache[i].ownedBy.length === 1) {
+ // Remove the cache entry if it's the only renderer
+ charAtlasCache[i].atlas.dispose();
+ charAtlasCache.splice(i, 1);
+ } else {
+ // Remove the reference from the cache entry
+ charAtlasCache[i].ownedBy.splice(index, 1);
+ }
+ break;
+ }
+ }
+}
diff --git a/node_modules/xterm/src/browser/renderer/atlas/CharAtlasUtils.ts b/node_modules/xterm/src/browser/renderer/atlas/CharAtlasUtils.ts
new file mode 100644
index 0000000..be92727
--- /dev/null
+++ b/node_modules/xterm/src/browser/renderer/atlas/CharAtlasUtils.ts
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { ICharAtlasConfig } from 'browser/renderer/atlas/Types';
+import { DEFAULT_COLOR } from 'common/buffer/Constants';
+import { IColorSet, IPartialColorSet } from 'browser/Types';
+import { ITerminalOptions } from 'common/services/Services';
+
+export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, options: ITerminalOptions, colors: IColorSet): ICharAtlasConfig {
+ // null out some fields that don't matter
+ const clonedColors: IPartialColorSet = {
+ foreground: colors.foreground,
+ background: colors.background,
+ cursor: undefined,
+ cursorAccent: undefined,
+ selection: undefined,
+ ansi: [...colors.ansi]
+ };
+ return {
+ devicePixelRatio: window.devicePixelRatio,
+ scaledCharWidth,
+ scaledCharHeight,
+ fontFamily: options.fontFamily,
+ fontSize: options.fontSize,
+ fontWeight: options.fontWeight,
+ fontWeightBold: options.fontWeightBold,
+ allowTransparency: options.allowTransparency,
+ colors: clonedColors
+ };
+}
+
+export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean {
+ for (let i = 0; i < a.colors.ansi.length; i++) {
+ if (a.colors.ansi[i].rgba !== b.colors.ansi[i].rgba) {
+ return false;
+ }
+ }
+ return a.devicePixelRatio === b.devicePixelRatio &&
+ a.fontFamily === b.fontFamily &&
+ a.fontSize === b.fontSize &&
+ a.fontWeight === b.fontWeight &&
+ a.fontWeightBold === b.fontWeightBold &&
+ a.allowTransparency === b.allowTransparency &&
+ a.scaledCharWidth === b.scaledCharWidth &&
+ a.scaledCharHeight === b.scaledCharHeight &&
+ a.colors.foreground === b.colors.foreground &&
+ a.colors.background === b.colors.background;
+}
+
+export function is256Color(colorCode: number): boolean {
+ return colorCode < DEFAULT_COLOR;
+}
diff --git a/node_modules/xterm/src/browser/renderer/atlas/Constants.ts b/node_modules/xterm/src/browser/renderer/atlas/Constants.ts
new file mode 100644
index 0000000..c1701e9
--- /dev/null
+++ b/node_modules/xterm/src/browser/renderer/atlas/Constants.ts
@@ -0,0 +1,15 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { isFirefox, isLegacyEdge } from 'common/Platform';
+
+export const INVERTED_DEFAULT_COLOR = 257;
+export const DIM_OPACITY = 0.5;
+// The text baseline is set conditionally by browser. Using 'ideographic' for Firefox or Legacy Edge would
+// result in truncated text (Issue 3353). Using 'bottom' for Chrome would result in slightly
+// unaligned Powerline fonts (PR 3356#issuecomment-850928179).
+export const TEXT_BASELINE: CanvasTextBaseline = isFirefox || isLegacyEdge ? 'bottom' : 'ideographic';
+
+export const CHAR_ATLAS_CELL_SPACING = 1;
diff --git a/node_modules/xterm/src/browser/renderer/atlas/DynamicCharAtlas.ts b/node_modules/xterm/src/browser/renderer/atlas/DynamicCharAtlas.ts
new file mode 100644
index 0000000..118dbcd
--- /dev/null
+++ b/node_modules/xterm/src/browser/renderer/atlas/DynamicCharAtlas.ts
@@ -0,0 +1,404 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/atlas/Constants';
+import { IGlyphIdentifier, ICharAtlasConfig } from 'browser/renderer/atlas/Types';
+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 { throwIfFalsy } from 'browser/renderer/RendererUtils';
+import { color } from 'browser/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.
+const TEXTURE_WIDTH = 1024;
+const TEXTURE_HEIGHT = 1024;
+
+const TRANSPARENT_COLOR = {
+ css: 'rgba(0, 0, 0, 0)',
+ rgba: 0
+};
+
+// Drawing to the cache is expensive: If we have to draw more than this number of glyphs to the
+// cache in a single frame, give up on trying to cache anything else, and try to finish the current
+// frame ASAP.
+//
+// This helps to limit the amount of damage a program can do when it would otherwise thrash the
+// cache.
+const FRAME_CACHE_DRAW_LIMIT = 100;
+
+/**
+ * The number of milliseconds to wait before generating the ImageBitmap, this is to debounce/batch
+ * the operation as window.createImageBitmap is asynchronous.
+ */
+const GLYPH_BITMAP_COMMIT_DELAY = 100;
+
+interface IGlyphCacheValue {
+ index: number;
+ isEmpty: boolean;
+ inBitmap: boolean;
+}
+
+export function getGlyphCacheKey(glyph: IGlyphIdentifier): number {
+ // Note that this only returns a valid key when code < 256
+ // Layout:
+ // 0b00000000000000000000000000000001: italic (1)
+ // 0b00000000000000000000000000000010: dim (1)
+ // 0b00000000000000000000000000000100: bold (1)
+ // 0b00000000000000000000111111111000: fg (9)
+ // 0b00000000000111111111000000000000: bg (9)
+ // 0b00011111111000000000000000000000: code (8)
+ // 0b11100000000000000000000000000000: unused (3)
+ return glyph.code << 21 | glyph.bg << 12 | glyph.fg << 3 | (glyph.bold ? 0 : 4) + (glyph.dim ? 0 : 2) + (glyph.italic ? 0 : 1);
+}
+
+export class DynamicCharAtlas extends BaseCharAtlas {
+ // An ordered map that we're using to keep track of where each glyph is in the atlas texture.
+ // It's ordered so that we can determine when to remove the old entries.
+ private _cacheMap: LRUMap<IGlyphCacheValue>;
+
+ // The texture that the atlas is drawn to
+ private _cacheCanvas: HTMLCanvasElement;
+ private _cacheCtx: CanvasRenderingContext2D;
+
+ // A temporary context that glyphs are drawn to before being transfered to the atlas.
+ private _tmpCtx: CanvasRenderingContext2D;
+
+ // The number of characters stored in the atlas by width/height
+ private _width: number;
+ private _height: number;
+
+ private _drawToCacheCount: number = 0;
+
+ // An array of glyph keys that are waiting on the bitmap to be generated.
+ private _glyphsWaitingOnBitmap: IGlyphCacheValue[] = [];
+
+ // The timeout that is used to batch bitmap generation so it's not requested for every new glyph.
+ private _bitmapCommitTimeout: number | null = null;
+
+ // The bitmap to draw from, this is much faster on other browsers than others.
+ private _bitmap: ImageBitmap | null = null;
+
+ constructor(document: Document, private _config: ICharAtlasConfig) {
+ super();
+ this._cacheCanvas = document.createElement('canvas');
+ this._cacheCanvas.width = TEXTURE_WIDTH;
+ this._cacheCanvas.height = TEXTURE_HEIGHT;
+ // The canvas needs alpha because we use clearColor to convert the background color to alpha.
+ // It might also contain some characters with transparent backgrounds if allowTransparency is
+ // set.
+ this._cacheCtx = throwIfFalsy(this._cacheCanvas.getContext('2d', { alpha: true }));
+
+ const tmpCanvas = document.createElement('canvas');
+ tmpCanvas.width = this._config.scaledCharWidth;
+ tmpCanvas.height = this._config.scaledCharHeight;
+ this._tmpCtx = throwIfFalsy(tmpCanvas.getContext('2d', { alpha: this._config.allowTransparency }));
+
+ this._width = Math.floor(TEXTURE_WIDTH / this._config.scaledCharWidth);
+ this._height = Math.floor(TEXTURE_HEIGHT / this._config.scaledCharHeight);
+ const capacity = this._width * this._height;
+ this._cacheMap = new LRUMap(capacity);
+ this._cacheMap.prealloc(capacity);
+
+ // This is useful for debugging
+ // document.body.appendChild(this._cacheCanvas);
+ }
+
+ public dispose(): void {
+ if (this._bitmapCommitTimeout !== null) {
+ window.clearTimeout(this._bitmapCommitTimeout);
+ this._bitmapCommitTimeout = null;
+ }
+ }
+
+ public beginFrame(): void {
+ this._drawToCacheCount = 0;
+ }
+
+ public clear(): void {
+ if (this._cacheMap.size > 0) {
+ const capacity = this._width * this._height;
+ this._cacheMap = new LRUMap(capacity);
+ this._cacheMap.prealloc(capacity);
+ }
+ this._cacheCtx.clearRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);
+ this._tmpCtx.clearRect(0, 0, this._config.scaledCharWidth, this._config.scaledCharHeight);
+ }
+
+ public draw(
+ ctx: CanvasRenderingContext2D,
+ glyph: IGlyphIdentifier,
+ x: number,
+ y: number
+ ): boolean {
+ // Space is always an empty cell, special case this as it's so common
+ if (glyph.code === 32) {
+ return true;
+ }
+
+ // Exit early for uncachable glyphs
+ if (!this._canCache(glyph)) {
+ return false;
+ }
+
+ const glyphKey = getGlyphCacheKey(glyph);
+ const cacheValue = this._cacheMap.get(glyphKey);
+ if (cacheValue !== null && cacheValue !== undefined) {
+ this._drawFromCache(ctx, cacheValue, x, y);
+ return true;
+ }
+ if (this._drawToCacheCount < FRAME_CACHE_DRAW_LIMIT) {
+ let index;
+ if (this._cacheMap.size < this._cacheMap.capacity) {
+ index = this._cacheMap.size;
+ } else {
+ // we're out of space, so our call to set will delete this item
+ index = this._cacheMap.peek()!.index;
+ }
+ const cacheValue = this._drawToCache(glyph, index);
+ this._cacheMap.set(glyphKey, cacheValue);
+ this._drawFromCache(ctx, cacheValue, x, y);
+ return true;
+ }
+ return false;
+ }
+
+ private _canCache(glyph: IGlyphIdentifier): boolean {
+ // Only cache ascii and extended characters for now, to be safe. In the future, we could do
+ // something more complicated to determine the expected width of a character.
+ //
+ // If we switch the renderer over to webgl at some point, we may be able to use blending modes
+ // to draw overlapping glyphs from the atlas:
+ // https://github.com/servo/webrender/issues/464#issuecomment-255632875
+ // https://webglfundamentals.org/webgl/lessons/webgl-text-texture.html
+ return glyph.code < 256;
+ }
+
+ private _toCoordinateX(index: number): number {
+ return (index % this._width) * this._config.scaledCharWidth;
+ }
+
+ private _toCoordinateY(index: number): number {
+ return Math.floor(index / this._width) * this._config.scaledCharHeight;
+ }
+
+ private _drawFromCache(
+ ctx: CanvasRenderingContext2D,
+ cacheValue: IGlyphCacheValue,
+ x: number,
+ y: number
+ ): void {
+ // We don't actually need to do anything if this is whitespace.
+ if (cacheValue.isEmpty) {
+ return;
+ }
+ const cacheX = this._toCoordinateX(cacheValue.index);
+ const cacheY = this._toCoordinateY(cacheValue.index);
+ ctx.drawImage(
+ cacheValue.inBitmap ? this._bitmap! : this._cacheCanvas,
+ cacheX,
+ cacheY,
+ this._config.scaledCharWidth,
+ this._config.scaledCharHeight,
+ x,
+ y,
+ this._config.scaledCharWidth,
+ this._config.scaledCharHeight
+ );
+ }
+
+ private _getColorFromAnsiIndex(idx: number): IColor {
+ if (idx < this._config.colors.ansi.length) {
+ return this._config.colors.ansi[idx];
+ }
+ return DEFAULT_ANSI_COLORS[idx];
+ }
+
+ private _getBackgroundColor(glyph: IGlyphIdentifier): IColor {
+ if (this._config.allowTransparency) {
+ // The background color might have some transparency, so we need to render it as fully
+ // transparent in the atlas. Otherwise we'd end up drawing the transparent background twice
+ // around the anti-aliased edges of the glyph, and it would look too dark.
+ return TRANSPARENT_COLOR;
+ }
+ if (glyph.bg === INVERTED_DEFAULT_COLOR) {
+ return this._config.colors.foreground;
+ }
+ if (glyph.bg < 256) {
+ return this._getColorFromAnsiIndex(glyph.bg);
+ }
+ return this._config.colors.background;
+ }
+
+ private _getForegroundColor(glyph: IGlyphIdentifier): IColor {
+ if (glyph.fg === INVERTED_DEFAULT_COLOR) {
+ return color.opaque(this._config.colors.background);
+ }
+ if (glyph.fg < 256) {
+ // 256 color support
+ return this._getColorFromAnsiIndex(glyph.fg);
+ }
+ return this._config.colors.foreground;
+ }
+
+ // TODO: We do this (or something similar) in multiple places. We should split this off
+ // into a shared function.
+ private _drawToCache(glyph: IGlyphIdentifier, index: number): IGlyphCacheValue {
+ this._drawToCacheCount++;
+
+ this._tmpCtx.save();
+
+ // draw the background
+ const backgroundColor = this._getBackgroundColor(glyph);
+ // Use a 'copy' composite operation to clear any existing glyph out of _tmpCtxWithAlpha, regardless of
+ // transparency in backgroundColor
+ this._tmpCtx.globalCompositeOperation = 'copy';
+ this._tmpCtx.fillStyle = backgroundColor.css;
+ this._tmpCtx.fillRect(0, 0, this._config.scaledCharWidth, this._config.scaledCharHeight);
+ this._tmpCtx.globalCompositeOperation = 'source-over';
+
+ // draw the foreground/glyph
+ const fontWeight = glyph.bold ? this._config.fontWeightBold : this._config.fontWeight;
+ const fontStyle = glyph.italic ? 'italic' : '';
+ this._tmpCtx.font =
+ `${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`;
+ this._tmpCtx.textBaseline = TEXT_BASELINE;
+
+ this._tmpCtx.fillStyle = this._getForegroundColor(glyph).css;
+
+ // Apply alpha to dim the character
+ if (glyph.dim) {
+ this._tmpCtx.globalAlpha = DIM_OPACITY;
+ }
+ // Draw the character
+ this._tmpCtx.fillText(glyph.chars, 0, this._config.scaledCharHeight);
+
+ // clear the background from the character to avoid issues with drawing over the previous
+ // character if it extends past it's bounds
+ let imageData = this._tmpCtx.getImageData(
+ 0, 0, this._config.scaledCharWidth, this._config.scaledCharHeight
+ );
+ let isEmpty = false;
+ if (!this._config.allowTransparency) {
+ isEmpty = clearColor(imageData, backgroundColor);
+ }
+
+ // If this charcater is underscore and empty, shift it up until it is visible, try for a maximum
+ // of 5 pixels.
+ if (isEmpty && glyph.chars === '_' && !this._config.allowTransparency) {
+ for (let offset = 1; offset <= 5; offset++) {
+ // Draw the character
+ this._tmpCtx.fillText(glyph.chars, 0, this._config.scaledCharHeight - offset);
+
+ // clear the background from the character to avoid issues with drawing over the previous
+ // character if it extends past it's bounds
+ imageData = this._tmpCtx.getImageData(
+ 0, 0, this._config.scaledCharWidth, this._config.scaledCharHeight
+ );
+ isEmpty = clearColor(imageData, backgroundColor);
+ if (!isEmpty) {
+ break;
+ }
+ }
+ }
+
+ this._tmpCtx.restore();
+
+ // copy the data from imageData to _cacheCanvas
+ const x = this._toCoordinateX(index);
+ const y = this._toCoordinateY(index);
+ // putImageData doesn't do any blending, so it will overwrite any existing cache entry for us
+ this._cacheCtx.putImageData(imageData, x, y);
+
+ // Add the glyph and queue it to the bitmap (if the browser supports it)
+ const cacheValue = {
+ index,
+ isEmpty,
+ inBitmap: false
+ };
+ this._addGlyphToBitmap(cacheValue);
+
+ return cacheValue;
+ }
+
+ private _addGlyphToBitmap(cacheValue: IGlyphCacheValue): void {
+ // Support is patchy for createImageBitmap at the moment, pass a canvas back
+ // if support is lacking as drawImage works there too. Firefox is also
+ // included here as ImageBitmap appears both buggy and has horrible
+ // performance (tested on v55).
+ if (!('createImageBitmap' in window) || isFirefox || isSafari) {
+ return;
+ }
+
+ // Add the glyph to the queue
+ this._glyphsWaitingOnBitmap.push(cacheValue);
+
+ // Check if bitmap generation timeout already exists
+ if (this._bitmapCommitTimeout !== null) {
+ return;
+ }
+
+ this._bitmapCommitTimeout = window.setTimeout(() => this._generateBitmap(), GLYPH_BITMAP_COMMIT_DELAY);
+ }
+
+ private _generateBitmap(): void {
+ const glyphsMovingToBitmap = this._glyphsWaitingOnBitmap;
+ this._glyphsWaitingOnBitmap = [];
+ window.createImageBitmap(this._cacheCanvas).then(bitmap => {
+ // Set bitmap
+ this._bitmap = bitmap;
+
+ // Mark all new glyphs as in bitmap, excluding glyphs that came in after
+ // the bitmap was requested
+ for (let i = 0; i < glyphsMovingToBitmap.length; i++) {
+ const value = glyphsMovingToBitmap[i];
+ // It doesn't matter if the value was already evicted, it will be
+ // released from memory after this block if so.
+ value.inBitmap = true;
+ }
+ });
+ this._bitmapCommitTimeout = null;
+ }
+}
+
+// This is used for debugging the renderer, just swap out `new DynamicCharAtlas` with
+// `new NoneCharAtlas`.
+export class NoneCharAtlas extends BaseCharAtlas {
+ constructor(document: Document, config: ICharAtlasConfig) {
+ super();
+ }
+
+ public draw(
+ ctx: CanvasRenderingContext2D,
+ glyph: IGlyphIdentifier,
+ x: number,
+ y: number
+ ): boolean {
+ return false;
+ }
+}
+
+/**
+ * Makes a partiicular rgb color in an ImageData completely transparent.
+ * @returns True if the result is "empty", meaning all pixels are fully transparent.
+ */
+function clearColor(imageData: ImageData, color: IColor): boolean {
+ let isEmpty = true;
+ const r = color.rgba >>> 24;
+ const g = color.rgba >>> 16 & 0xFF;
+ const b = color.rgba >>> 8 & 0xFF;
+ for (let offset = 0; offset < imageData.data.length; offset += 4) {
+ if (imageData.data[offset] === r &&
+ imageData.data[offset + 1] === g &&
+ imageData.data[offset + 2] === b) {
+ imageData.data[offset + 3] = 0;
+ } else {
+ isEmpty = false;
+ }
+ }
+ return isEmpty;
+}
diff --git a/node_modules/xterm/src/browser/renderer/atlas/LRUMap.ts b/node_modules/xterm/src/browser/renderer/atlas/LRUMap.ts
new file mode 100644
index 0000000..f70962f
--- /dev/null
+++ b/node_modules/xterm/src/browser/renderer/atlas/LRUMap.ts
@@ -0,0 +1,136 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+interface ILinkedListNode<T> {
+ prev: ILinkedListNode<T> | null;
+ next: ILinkedListNode<T> | null;
+ key: number | null;
+ value: T | null;
+}
+
+export class LRUMap<T> {
+ private _map: { [key: number]: ILinkedListNode<T> } = {};
+ private _head: ILinkedListNode<T> | null = null;
+ private _tail: ILinkedListNode<T> | null = null;
+ private _nodePool: ILinkedListNode<T>[] = [];
+ public size: number = 0;
+
+ constructor(public capacity: number) { }
+
+ private _unlinkNode(node: ILinkedListNode<T>): void {
+ const prev = node.prev;
+ const next = node.next;
+ if (node === this._head) {
+ this._head = next;
+ }
+ if (node === this._tail) {
+ this._tail = prev;
+ }
+ if (prev !== null) {
+ prev.next = next;
+ }
+ if (next !== null) {
+ next.prev = prev;
+ }
+ }
+
+ private _appendNode(node: ILinkedListNode<T>): void {
+ const tail = this._tail;
+ if (tail !== null) {
+ tail.next = node;
+ }
+ node.prev = tail;
+ node.next = null;
+ this._tail = node;
+ if (this._head === null) {
+ this._head = node;
+ }
+ }
+
+ /**
+ * Preallocate a bunch of linked-list nodes. Allocating these nodes ahead of time means that
+ * they're more likely to live next to each other in memory, which seems to improve performance.
+ *
+ * Each empty object only consumes about 60 bytes of memory, so this is pretty cheap, even for
+ * large maps.
+ */
+ public prealloc(count: number): void {
+ const nodePool = this._nodePool;
+ for (let i = 0; i < count; i++) {
+ nodePool.push({
+ prev: null,
+ next: null,
+ key: null,
+ value: null
+ });
+ }
+ }
+
+ public get(key: number): T | null {
+ // This is unsafe: We're assuming our keyspace doesn't overlap with Object.prototype. However,
+ // it's faster than calling hasOwnProperty, and in our case, it would never overlap.
+ const node = this._map[key];
+ if (node !== undefined) {
+ this._unlinkNode(node);
+ this._appendNode(node);
+ return node.value;
+ }
+ return null;
+ }
+
+ /**
+ * Gets a value from a key without marking it as the most recently used item.
+ */
+ public peekValue(key: number): T | null {
+ const node = this._map[key];
+ if (node !== undefined) {
+ return node.value;
+ }
+ return null;
+ }
+
+ public peek(): T | null {
+ const head = this._head;
+ return head === null ? null : head.value;
+ }
+
+ public set(key: number, value: T): void {
+ // This is unsafe: See note above.
+ let node = this._map[key];
+ if (node !== undefined) {
+ // already exists, we just need to mutate it and move it to the end of the list
+ node = this._map[key];
+ this._unlinkNode(node);
+ node.value = value;
+ } else if (this.size >= this.capacity) {
+ // we're out of space: recycle the head node, move it to the tail
+ node = this._head!;
+ this._unlinkNode(node);
+ delete this._map[node.key!];
+ node.key = key;
+ node.value = value;
+ this._map[key] = node;
+ } else {
+ // make a new element
+ const nodePool = this._nodePool;
+ if (nodePool.length > 0) {
+ // use a preallocated node if we can
+ node = nodePool.pop()!;
+ node.key = key;
+ node.value = value;
+ } else {
+ node = {
+ prev: null,
+ next: null,
+ key,
+ value
+ };
+ }
+ this._map[key] = node;
+ this.size++;
+ }
+ this._appendNode(node);
+ }
+}
diff --git a/node_modules/xterm/src/browser/renderer/atlas/Types.d.ts b/node_modules/xterm/src/browser/renderer/atlas/Types.d.ts
new file mode 100644
index 0000000..d8bc54c
--- /dev/null
+++ b/node_modules/xterm/src/browser/renderer/atlas/Types.d.ts
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { FontWeight } from 'common/services/Services';
+import { IPartialColorSet } from 'browser/Types';
+
+export interface IGlyphIdentifier {
+ chars: string;
+ code: number;
+ bg: number;
+ fg: number;
+ bold: boolean;
+ dim: boolean;
+ italic: boolean;
+}
+
+export interface ICharAtlasConfig {
+ devicePixelRatio: number;
+ fontSize: number;
+ fontFamily: string;
+ fontWeight: FontWeight;
+ fontWeightBold: FontWeight;
+ scaledCharWidth: number;
+ scaledCharHeight: number;
+ allowTransparency: boolean;
+ colors: IPartialColorSet;
+}
diff --git a/node_modules/xterm/src/browser/renderer/dom/DomRenderer.ts b/node_modules/xterm/src/browser/renderer/dom/DomRenderer.ts
new file mode 100644
index 0000000..ee28339
--- /dev/null
+++ b/node_modules/xterm/src/browser/renderer/dom/DomRenderer.ts
@@ -0,0 +1,400 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types';
+import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from 'browser/renderer/dom/DomRendererRowFactory';
+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 { EventEmitter, IEvent } from 'common/EventEmitter';
+import { color } from 'browser/Color';
+import { removeElementFromParent } from 'browser/Dom';
+
+const TERMINAL_CLASS_PREFIX = 'xterm-dom-renderer-owner-';
+const ROW_CONTAINER_CLASS = 'xterm-rows';
+const FG_CLASS_PREFIX = 'xterm-fg-';
+const BG_CLASS_PREFIX = 'xterm-bg-';
+const FOCUS_CLASS = 'xterm-focus';
+const SELECTION_CLASS = 'xterm-selection';
+
+let nextTerminalId = 1;
+
+/**
+ * A fallback renderer for when canvas is slow. This is not meant to be
+ * particularly fast or feature complete, more just stable and usable for when
+ * canvas is not an option.
+ */
+export class DomRenderer extends Disposable implements IRenderer {
+ private _rowFactory: DomRendererRowFactory;
+ private _terminalClass: number = nextTerminalId++;
+
+ private _themeStyleElement!: HTMLStyleElement;
+ private _dimensionsStyleElement!: HTMLStyleElement;
+ private _rowContainer: HTMLElement;
+ private _rowElements: HTMLElement[] = [];
+ private _selectionContainer: HTMLElement;
+
+ public dimensions: IRenderDimensions;
+
+ public get onRequestRedraw(): IEvent<IRequestRedrawEvent> { return new EventEmitter<IRequestRedrawEvent>().event; }
+
+ constructor(
+ private _colors: IColorSet,
+ private readonly _element: HTMLElement,
+ private readonly _screenElement: HTMLElement,
+ private readonly _viewportElement: HTMLElement,
+ private readonly _linkifier: ILinkifier,
+ private readonly _linkifier2: ILinkifier2,
+ @IInstantiationService instantiationService: IInstantiationService,
+ @ICharSizeService private readonly _charSizeService: ICharSizeService,
+ @IOptionsService private readonly _optionsService: IOptionsService,
+ @IBufferService private readonly _bufferService: IBufferService
+ ) {
+ super();
+ this._rowContainer = document.createElement('div');
+ this._rowContainer.classList.add(ROW_CONTAINER_CLASS);
+ this._rowContainer.style.lineHeight = 'normal';
+ this._rowContainer.setAttribute('aria-hidden', 'true');
+ this._refreshRowElements(this._bufferService.cols, this._bufferService.rows);
+ this._selectionContainer = document.createElement('div');
+ this._selectionContainer.classList.add(SELECTION_CLASS);
+ this._selectionContainer.setAttribute('aria-hidden', 'true');
+
+ this.dimensions = {
+ scaledCharWidth: 0,
+ scaledCharHeight: 0,
+ scaledCellWidth: 0,
+ scaledCellHeight: 0,
+ scaledCharLeft: 0,
+ scaledCharTop: 0,
+ scaledCanvasWidth: 0,
+ scaledCanvasHeight: 0,
+ canvasWidth: 0,
+ canvasHeight: 0,
+ actualCellWidth: 0,
+ actualCellHeight: 0
+ };
+ this._updateDimensions();
+ this._injectCss();
+
+ this._rowFactory = instantiationService.createInstance(DomRendererRowFactory, document, this._colors);
+
+ this._element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass);
+ 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._linkifier2.onShowLinkUnderline(e => this._onLinkHover(e));
+ this._linkifier2.onHideLinkUnderline(e => this._onLinkLeave(e));
+ }
+
+ public dispose(): void {
+ this._element.classList.remove(TERMINAL_CLASS_PREFIX + this._terminalClass);
+
+ // Outside influences such as React unmounts may manipulate the DOM before our disposal.
+ // https://github.com/xtermjs/xterm.js/issues/2960
+ removeElementFromParent(this._rowContainer, this._selectionContainer, this._themeStyleElement, this._dimensionsStyleElement);
+
+ super.dispose();
+ }
+
+ private _updateDimensions(): void {
+ this.dimensions.scaledCharWidth = this._charSizeService.width * window.devicePixelRatio;
+ this.dimensions.scaledCharHeight = Math.ceil(this._charSizeService.height * window.devicePixelRatio);
+ this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._optionsService.rawOptions.letterSpacing);
+ this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._optionsService.rawOptions.lineHeight);
+ this.dimensions.scaledCharLeft = 0;
+ this.dimensions.scaledCharTop = 0;
+ this.dimensions.scaledCanvasWidth = this.dimensions.scaledCellWidth * this._bufferService.cols;
+ this.dimensions.scaledCanvasHeight = this.dimensions.scaledCellHeight * this._bufferService.rows;
+ this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / window.devicePixelRatio);
+ this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / window.devicePixelRatio);
+ this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._bufferService.cols;
+ this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._bufferService.rows;
+
+ for (const element of this._rowElements) {
+ element.style.width = `${this.dimensions.canvasWidth}px`;
+ element.style.height = `${this.dimensions.actualCellHeight}px`;
+ element.style.lineHeight = `${this.dimensions.actualCellHeight}px`;
+ // Make sure rows don't overflow onto following row
+ element.style.overflow = 'hidden';
+ }
+
+ if (!this._dimensionsStyleElement) {
+ this._dimensionsStyleElement = document.createElement('style');
+ this._screenElement.appendChild(this._dimensionsStyleElement);
+ }
+
+ const styles =
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS} span {` +
+ ` display: inline-block;` +
+ ` height: 100%;` +
+ ` vertical-align: top;` +
+ ` width: ${this.dimensions.actualCellWidth}px` +
+ `}`;
+
+ this._dimensionsStyleElement.textContent = styles;
+
+ this._selectionContainer.style.height = this._viewportElement.style.height;
+ this._screenElement.style.width = `${this.dimensions.canvasWidth}px`;
+ this._screenElement.style.height = `${this.dimensions.canvasHeight}px`;
+ }
+
+ public setColors(colors: IColorSet): void {
+ this._colors = colors;
+ this._injectCss();
+ }
+
+ private _injectCss(): void {
+ if (!this._themeStyleElement) {
+ this._themeStyleElement = document.createElement('style');
+ this._screenElement.appendChild(this._themeStyleElement);
+ }
+
+ // Base CSS
+ let styles =
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS} {` +
+ ` color: ${this._colors.foreground.css};` +
+ ` font-family: ${this._optionsService.rawOptions.fontFamily};` +
+ ` font-size: ${this._optionsService.rawOptions.fontSize}px;` +
+ `}`;
+ // Text styles
+ styles +=
+ `${this._terminalSelector} span:not(.${BOLD_CLASS}) {` +
+ ` font-weight: ${this._optionsService.rawOptions.fontWeight};` +
+ `}` +
+ `${this._terminalSelector} span.${BOLD_CLASS} {` +
+ ` font-weight: ${this._optionsService.rawOptions.fontWeightBold};` +
+ `}` +
+ `${this._terminalSelector} span.${ITALIC_CLASS} {` +
+ ` font-style: italic;` +
+ `}`;
+ // Blink animation
+ styles +=
+ `@keyframes blink_box_shadow` + `_` + this._terminalClass + ` {` +
+ ` 50% {` +
+ ` box-shadow: none;` +
+ ` }` +
+ `}`;
+ styles +=
+ `@keyframes blink_block` + `_` + this._terminalClass + ` {` +
+ ` 0% {` +
+ ` background-color: ${this._colors.cursor.css};` +
+ ` color: ${this._colors.cursorAccent.css};` +
+ ` }` +
+ ` 50% {` +
+ ` background-color: ${this._colors.cursorAccent.css};` +
+ ` color: ${this._colors.cursor.css};` +
+ ` }` +
+ `}`;
+ // Cursor
+ styles +=
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS}:not(.${FOCUS_CLASS}) .${CURSOR_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` +
+ ` outline: 1px solid ${this._colors.cursor.css};` +
+ ` outline-offset: -1px;` +
+ `}` +
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS}:not(.${CURSOR_STYLE_BLOCK_CLASS}) {` +
+ ` animation: blink_box_shadow` + `_` + this._terminalClass + ` 1s step-end infinite;` +
+ `}` +
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_BLINK_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` +
+ ` animation: blink_block` + `_` + this._terminalClass + ` 1s step-end infinite;` +
+ `}` +
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BLOCK_CLASS} {` +
+ ` background-color: ${this._colors.cursor.css};` +
+ ` color: ${this._colors.cursorAccent.css};` +
+ `}` +
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_BAR_CLASS} {` +
+ ` box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${this._colors.cursor.css} inset;` +
+ `}` +
+ `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${CURSOR_CLASS}.${CURSOR_STYLE_UNDERLINE_CLASS} {` +
+ ` box-shadow: 0 -1px 0 ${this._colors.cursor.css} inset;` +
+ `}`;
+ // Selection
+ styles +=
+ `${this._terminalSelector} .${SELECTION_CLASS} {` +
+ ` position: absolute;` +
+ ` top: 0;` +
+ ` left: 0;` +
+ ` z-index: 1;` +
+ ` pointer-events: none;` +
+ `}` +
+ `${this._terminalSelector} .${SELECTION_CLASS} div {` +
+ ` position: absolute;` +
+ ` background-color: ${this._colors.selectionTransparent.css};` +
+ `}`;
+ // Colors
+ this._colors.ansi.forEach((c, i) => {
+ styles +=
+ `${this._terminalSelector} .${FG_CLASS_PREFIX}${i} { color: ${c.css}; }` +
+ `${this._terminalSelector} .${BG_CLASS_PREFIX}${i} { background-color: ${c.css}; }`;
+ });
+ styles +=
+ `${this._terminalSelector} .${FG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { color: ${color.opaque(this._colors.background).css}; }` +
+ `${this._terminalSelector} .${BG_CLASS_PREFIX}${INVERTED_DEFAULT_COLOR} { background-color: ${this._colors.foreground.css}; }`;
+
+ this._themeStyleElement.textContent = styles;
+ }
+
+ public onDevicePixelRatioChange(): void {
+ this._updateDimensions();
+ }
+
+ private _refreshRowElements(cols: number, rows: number): void {
+ // Add missing elements
+ for (let i = this._rowElements.length; i <= rows; i++) {
+ const row = document.createElement('div');
+ this._rowContainer.appendChild(row);
+ this._rowElements.push(row);
+ }
+ // Remove excess elements
+ while (this._rowElements.length > rows) {
+ this._rowContainer.removeChild(this._rowElements.pop()!);
+ }
+ }
+
+ public onResize(cols: number, rows: number): void {
+ this._refreshRowElements(cols, rows);
+ this._updateDimensions();
+ }
+
+ public onCharSizeChanged(): void {
+ this._updateDimensions();
+ }
+
+ public onBlur(): void {
+ this._rowContainer.classList.remove(FOCUS_CLASS);
+ }
+
+ public onFocus(): void {
+ this._rowContainer.classList.add(FOCUS_CLASS);
+ }
+
+ public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
+ // Remove all selections
+ while (this._selectionContainer.children.length) {
+ this._selectionContainer.removeChild(this._selectionContainer.children[0]);
+ }
+
+ // Selection does not exist
+ if (!start || !end) {
+ return;
+ }
+
+ // Translate from buffer position to viewport position
+ const viewportStartRow = start[1] - this._bufferService.buffer.ydisp;
+ const viewportEndRow = end[1] - this._bufferService.buffer.ydisp;
+ const viewportCappedStartRow = Math.max(viewportStartRow, 0);
+ const viewportCappedEndRow = Math.min(viewportEndRow, this._bufferService.rows - 1);
+
+ // No need to draw the selection
+ if (viewportCappedStartRow >= this._bufferService.rows || viewportCappedEndRow < 0) {
+ return;
+ }
+
+ // Create the selections
+ const documentFragment = document.createDocumentFragment();
+
+ if (columnSelectMode) {
+ documentFragment.appendChild(
+ this._createSelectionElement(viewportCappedStartRow, start[0], end[0], viewportCappedEndRow - viewportCappedStartRow + 1)
+ );
+ } else {
+ // Draw first row
+ const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0;
+ const endCol = viewportCappedStartRow === viewportEndRow ? end[0] : this._bufferService.cols;
+ documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow, startCol, endCol));
+ // Draw middle rows
+ const middleRowsCount = viewportCappedEndRow - viewportCappedStartRow - 1;
+ documentFragment.appendChild(this._createSelectionElement(viewportCappedStartRow + 1, 0, this._bufferService.cols, middleRowsCount));
+ // Draw final row
+ if (viewportCappedStartRow !== viewportCappedEndRow) {
+ // Only draw viewportEndRow if it's not the same as viewporttartRow
+ const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._bufferService.cols;
+ documentFragment.appendChild(this._createSelectionElement(viewportCappedEndRow, 0, endCol));
+ }
+ }
+ this._selectionContainer.appendChild(documentFragment);
+ }
+
+ /**
+ * Creates a selection element at the specified position.
+ * @param row The row of the selection.
+ * @param colStart The start column.
+ * @param colEnd The end columns.
+ */
+ private _createSelectionElement(row: number, colStart: number, colEnd: number, rowCount: number = 1): HTMLElement {
+ const element = document.createElement('div');
+ element.style.height = `${rowCount * this.dimensions.actualCellHeight}px`;
+ element.style.top = `${row * this.dimensions.actualCellHeight}px`;
+ element.style.left = `${colStart * this.dimensions.actualCellWidth}px`;
+ element.style.width = `${this.dimensions.actualCellWidth * (colEnd - colStart)}px`;
+ return element;
+ }
+
+ public onCursorMove(): void {
+ // No-op, the cursor is drawn when rows are drawn
+ }
+
+ public onOptionsChanged(): void {
+ // Force a refresh
+ this._updateDimensions();
+ this._injectCss();
+ }
+
+ public clear(): void {
+ for (const e of this._rowElements) {
+ e.innerText = '';
+ }
+ }
+
+ public renderRows(start: number, end: number): void {
+ const cursorAbsoluteY = this._bufferService.buffer.ybase + this._bufferService.buffer.y;
+ const cursorX = Math.min(this._bufferService.buffer.x, this._bufferService.cols - 1);
+ const cursorBlink = this._optionsService.rawOptions.cursorBlink;
+
+ 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;
+ rowElement.appendChild(this._rowFactory.createRow(lineData!, row, row === cursorAbsoluteY, cursorStyle, cursorX, cursorBlink, this.dimensions.actualCellWidth, this._bufferService.cols));
+ }
+ }
+
+ private get _terminalSelector(): string {
+ return `.${TERMINAL_CLASS_PREFIX}${this._terminalClass}`;
+ }
+
+ private _onLinkHover(e: ILinkifierEvent): void {
+ this._setCellUnderline(e.x1, e.x2, e.y1, e.y2, e.cols, true);
+ }
+
+ private _onLinkLeave(e: ILinkifierEvent): void {
+ this._setCellUnderline(e.x1, e.x2, e.y1, e.y2, e.cols, false);
+ }
+
+ private _setCellUnderline(x: number, x2: number, y: number, y2: number, cols: number, enabled: boolean): void {
+ while (x !== x2 || y !== y2) {
+ const row = this._rowElements[y];
+ if (!row) {
+ return;
+ }
+ const span = row.children[x] as HTMLElement;
+ if (span) {
+ span.style.textDecoration = enabled ? 'underline' : 'none';
+ }
+ if (++x >= cols) {
+ x = 0;
+ y++;
+ }
+ }
+ }
+}
diff --git a/node_modules/xterm/src/browser/renderer/dom/DomRendererRowFactory.ts b/node_modules/xterm/src/browser/renderer/dom/DomRendererRowFactory.ts
new file mode 100644
index 0000000..fda800a
--- /dev/null
+++ b/node_modules/xterm/src/browser/renderer/dom/DomRendererRowFactory.ts
@@ -0,0 +1,259 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IBufferLine } 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 { JoinedCellData } from 'browser/services/CharacterJoinerService';
+
+export const BOLD_CLASS = 'xterm-bold';
+export const DIM_CLASS = 'xterm-dim';
+export const ITALIC_CLASS = 'xterm-italic';
+export const UNDERLINE_CLASS = 'xterm-underline';
+export const STRIKETHROUGH_CLASS = 'xterm-strikethrough';
+export const CURSOR_CLASS = 'xterm-cursor';
+export const CURSOR_BLINK_CLASS = 'xterm-cursor-blink';
+export const CURSOR_STYLE_BLOCK_CLASS = 'xterm-cursor-block';
+export const CURSOR_STYLE_BAR_CLASS = 'xterm-cursor-bar';
+export const CURSOR_STYLE_UNDERLINE_CLASS = 'xterm-cursor-underline';
+
+export class DomRendererRowFactory {
+ private _workCell: CellData = new CellData();
+
+ constructor(
+ private readonly _document: Document,
+ private _colors: IColorSet,
+ @ICharacterJoinerService private readonly _characterJoinerService: ICharacterJoinerService,
+ @IOptionsService private readonly _optionsService: IOptionsService,
+ @ICoreService private readonly _coreService: ICoreService
+ ) {
+ }
+
+ public setColors(colors: IColorSet): void {
+ this._colors = colors;
+ }
+
+ 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();
+
+ const joinedRanges = this._characterJoinerService.getJoinedCharacters(row);
+ // Find the line length first, this prevents the need to output a bunch of
+ // empty cells at the end. This cannot easily be integrated into the main
+ // loop below because of the colCount feature (which can be removed after we
+ // properly support reflow and disallow data to go beyond the right-side of
+ // the viewport).
+ let lineLength = 0;
+ for (let x = Math.min(lineData.length, cols) - 1; x >= 0; x--) {
+ if (lineData.loadCell(x, this._workCell).getCode() !== NULL_CELL_CODE || (isCursorRow && x === cursorX)) {
+ lineLength = x + 1;
+ break;
+ }
+ }
+
+ for (let x = 0; x < lineLength; x++) {
+ lineData.loadCell(x, this._workCell);
+ let width = this._workCell.getWidth();
+
+ // The character to the left is a wide character, drawing is owned by the char at x-1
+ if (width === 0) {
+ continue;
+ }
+
+ // If true, indicates that the current character(s) to draw were joined.
+ let isJoined = false;
+ let lastCharX = x;
+
+ // Process any joined character ranges as needed. Because of how the
+ // ranges are produced, we know that they are valid for the characters
+ // and attributes of our input.
+ let cell = this._workCell;
+ if (joinedRanges.length > 0 && x === joinedRanges[0][0]) {
+ isJoined = true;
+ const range = joinedRanges.shift()!;
+
+ // We already know the exact start and end column of the joined range,
+ // so we get the string and width representing it directly
+ cell = new JoinedCellData(
+ this._workCell,
+ lineData.translateToString(true, range[0], range[1]),
+ range[1] - range[0]
+ );
+
+ // Skip over the cells occupied by this range in the loop
+ lastCharX = range[1] - 1;
+
+ // Recalculate width
+ width = cell.getWidth();
+ }
+
+ const charElement = this._document.createElement('span');
+ if (width > 1) {
+ charElement.style.width = `${cellWidth * width}px`;
+ }
+
+ if (isJoined) {
+ // Ligatures in the DOM renderer must use display inline, as they may not show with
+ // inline-block if they are outside the bounds of the element
+ charElement.style.display = 'inline';
+
+ // The DOM renderer colors the background of the cursor but for ligatures all cells are
+ // joined. The workaround here is to show a cursor around the whole ligature so it shows up,
+ // the cursor looks the same when on any character of the ligature though
+ if (cursorX >= x && cursorX <= lastCharX) {
+ cursorX = x;
+ }
+ }
+
+ if (!this._coreService.isCursorHidden && isCursorRow && x === cursorX) {
+ charElement.classList.add(CURSOR_CLASS);
+
+ if (cursorBlink) {
+ charElement.classList.add(CURSOR_BLINK_CLASS);
+ }
+
+ switch (cursorStyle) {
+ case 'bar':
+ charElement.classList.add(CURSOR_STYLE_BAR_CLASS);
+ break;
+ case 'underline':
+ charElement.classList.add(CURSOR_STYLE_UNDERLINE_CLASS);
+ break;
+ default:
+ charElement.classList.add(CURSOR_STYLE_BLOCK_CLASS);
+ break;
+ }
+ }
+
+ if (cell.isBold()) {
+ charElement.classList.add(BOLD_CLASS);
+ }
+
+ if (cell.isItalic()) {
+ charElement.classList.add(ITALIC_CLASS);
+ }
+
+ if (cell.isDim()) {
+ charElement.classList.add(DIM_CLASS);
+ }
+
+ if (cell.isUnderline()) {
+ charElement.classList.add(UNDERLINE_CLASS);
+ }
+
+ if (cell.isInvisible()) {
+ charElement.textContent = WHITESPACE_CELL_CHAR;
+ } else {
+ charElement.textContent = cell.getChars() || WHITESPACE_CELL_CHAR;
+ }
+
+ if (cell.isStrikethrough()) {
+ charElement.classList.add(STRIKETHROUGH_CLASS);
+ }
+
+ let fg = cell.getFgColor();
+ let fgColorMode = cell.getFgColorMode();
+ let bg = cell.getBgColor();
+ let bgColorMode = cell.getBgColorMode();
+ const isInverse = !!cell.isInverse();
+ if (isInverse) {
+ const temp = fg;
+ fg = bg;
+ bg = temp;
+ const temp2 = fgColorMode;
+ fgColorMode = bgColorMode;
+ bgColorMode = temp2;
+ }
+
+ // Foreground
+ switch (fgColorMode) {
+ case Attributes.CM_P16:
+ case Attributes.CM_P256:
+ if (cell.isBold() && fg < 8 && this._optionsService.rawOptions.drawBoldTextInBrightColors) {
+ fg += 8;
+ }
+ if (!this._applyMinimumContrast(charElement, this._colors.background, this._colors.ansi[fg])) {
+ charElement.classList.add(`xterm-fg-${fg}`);
+ }
+ break;
+ case Attributes.CM_RGB:
+ const color = rgba.toColor(
+ (fg >> 16) & 0xFF,
+ (fg >> 8) & 0xFF,
+ (fg ) & 0xFF
+ );
+ if (!this._applyMinimumContrast(charElement, this._colors.background, color)) {
+ 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 (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;
+ }
+ return fragment;
+ }
+
+ private _applyMinimumContrast(element: HTMLElement, bg: IColor, fg: IColor): boolean {
+ if (this._optionsService.rawOptions.minimumContrastRatio === 1) {
+ return false;
+ }
+
+ // Try get from cache first
+ let adjustedColor = this._colors.contrastCache.getColor(this._workCell.bg, this._workCell.fg);
+
+ // 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);
+ }
+
+ if (adjustedColor) {
+ this._addStyle(element, `color:${adjustedColor.css}`);
+ return true;
+ }
+
+ return false;
+ }
+
+ private _addStyle(element: HTMLElement, style: string): void {
+ element.setAttribute('style', `${element.getAttribute('style') || ''}${style};`);
+ }
+}
+
+function padStart(text: string, padChar: string, length: number): string {
+ while (text.length < length) {
+ text = padChar + text;
+ }
+ return text;
+}
diff --git a/node_modules/xterm/src/browser/selection/SelectionModel.ts b/node_modules/xterm/src/browser/selection/SelectionModel.ts
new file mode 100644
index 0000000..1d84446
--- /dev/null
+++ b/node_modules/xterm/src/browser/selection/SelectionModel.ts
@@ -0,0 +1,139 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IBufferService } from 'common/services/Services';
+
+/**
+ * Represents a selection within the buffer. This model only cares about column
+ * and row coordinates, not wide characters.
+ */
+export class SelectionModel {
+ /**
+ * Whether select all is currently active.
+ */
+ public isSelectAllActive: boolean = false;
+
+ /**
+ * The minimal length of the selection from the start position. When double
+ * clicking on a word, the word will be selected which makes the selection
+ * start at the start of the word and makes this variable the length.
+ */
+ public selectionStartLength: number = 0;
+
+ /**
+ * The [x, y] position the selection starts at.
+ */
+ public selectionStart: [number, number] | undefined;
+
+ /**
+ * The [x, y] position the selection ends at.
+ */
+ public selectionEnd: [number, number] | undefined;
+
+ constructor(
+ private _bufferService: IBufferService
+ ) {
+ }
+
+ /**
+ * Clears the current selection.
+ */
+ public clearSelection(): void {
+ this.selectionStart = undefined;
+ this.selectionEnd = undefined;
+ this.isSelectAllActive = false;
+ this.selectionStartLength = 0;
+ }
+
+ /**
+ * The final selection start, taking into consideration select all.
+ */
+ public get finalSelectionStart(): [number, number] | undefined {
+ if (this.isSelectAllActive) {
+ return [0, 0];
+ }
+
+ if (!this.selectionEnd || !this.selectionStart) {
+ return this.selectionStart;
+ }
+
+ return this.areSelectionValuesReversed() ? this.selectionEnd : this.selectionStart;
+ }
+
+ /**
+ * The final selection end, taking into consideration select all, double click
+ * word selection and triple click line selection.
+ */
+ public get finalSelectionEnd(): [number, number] | undefined {
+ if (this.isSelectAllActive) {
+ return [this._bufferService.cols, this._bufferService.buffer.ybase + this._bufferService.rows - 1];
+ }
+
+ if (!this.selectionStart) {
+ return undefined;
+ }
+
+ // Use the selection start + length if the end doesn't exist or they're reversed
+ if (!this.selectionEnd || this.areSelectionValuesReversed()) {
+ const startPlusLength = this.selectionStart[0] + this.selectionStartLength;
+ if (startPlusLength > this._bufferService.cols) {
+ // Ensure the trailing EOL isn't included when the selection ends on the right edge
+ if (startPlusLength % this._bufferService.cols === 0) {
+ return [this._bufferService.cols, this.selectionStart[1] + Math.floor(startPlusLength / this._bufferService.cols) - 1];
+ }
+ return [startPlusLength % this._bufferService.cols, this.selectionStart[1] + Math.floor(startPlusLength / this._bufferService.cols)];
+ }
+ return [startPlusLength, this.selectionStart[1]];
+ }
+
+ // Ensure the the word/line is selected after a double/triple click
+ 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]];
+ }
+ }
+ return this.selectionEnd;
+ }
+
+ /**
+ * Returns whether the selection start and end are reversed.
+ */
+ public areSelectionValuesReversed(): boolean {
+ const start = this.selectionStart;
+ const end = this.selectionEnd;
+ if (!start || !end) {
+ return false;
+ }
+ return start[1] > end[1] || (start[1] === end[1] && start[0] > end[0]);
+ }
+
+ /**
+ * Handle the buffer being trimmed, adjust the selection position.
+ * @param amount The amount the buffer is being trimmed.
+ * @return Whether a refresh is necessary.
+ */
+ public onTrim(amount: number): boolean {
+ // Adjust the selection position based on the trimmed amount.
+ if (this.selectionStart) {
+ this.selectionStart[1] -= amount;
+ }
+ if (this.selectionEnd) {
+ this.selectionEnd[1] -= amount;
+ }
+
+ // The selection has moved off the buffer, clear it.
+ if (this.selectionEnd && this.selectionEnd[1] < 0) {
+ this.clearSelection();
+ return true;
+ }
+
+ // If the selection start is trimmed, ensure the start column is 0.
+ if (this.selectionStart && this.selectionStart[1] < 0) {
+ this.selectionStart[1] = 0;
+ }
+ return false;
+ }
+}
diff --git a/node_modules/xterm/src/browser/selection/Types.d.ts b/node_modules/xterm/src/browser/selection/Types.d.ts
new file mode 100644
index 0000000..8adfc17
--- /dev/null
+++ b/node_modules/xterm/src/browser/selection/Types.d.ts
@@ -0,0 +1,15 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+export interface ISelectionRedrawRequestEvent {
+ start: [number, number] | undefined;
+ end: [number, number] | undefined;
+ columnSelectMode: boolean;
+}
+
+export interface ISelectionRequestScrollLinesEvent {
+ amount: number;
+ suppressScrollEvent: boolean;
+}
diff --git a/node_modules/xterm/src/browser/services/CharSizeService.ts b/node_modules/xterm/src/browser/services/CharSizeService.ts
new file mode 100644
index 0000000..b04e157
--- /dev/null
+++ b/node_modules/xterm/src/browser/services/CharSizeService.ts
@@ -0,0 +1,87 @@
+/**
+ * Copyright (c) 2016 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IOptionsService } from 'common/services/Services';
+import { IEvent, EventEmitter } from 'common/EventEmitter';
+import { ICharSizeService } from 'browser/services/Services';
+
+export class CharSizeService implements ICharSizeService {
+ public serviceBrand: undefined;
+
+ public width: number = 0;
+ public height: number = 0;
+ private _measureStrategy: IMeasureStrategy;
+
+ public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; }
+
+ private _onCharSizeChange = new EventEmitter<void>();
+ public get onCharSizeChange(): IEvent<void> { return this._onCharSizeChange.event; }
+
+ constructor(
+ document: Document,
+ parentElement: HTMLElement,
+ @IOptionsService private readonly _optionsService: IOptionsService
+ ) {
+ this._measureStrategy = new DomMeasureStrategy(document, parentElement, this._optionsService);
+ }
+
+ public measure(): void {
+ const result = this._measureStrategy.measure();
+ if (result.width !== this.width || result.height !== this.height) {
+ this.width = result.width;
+ this.height = result.height;
+ this._onCharSizeChange.fire();
+ }
+ }
+}
+
+interface IMeasureStrategy {
+ measure(): IReadonlyMeasureResult;
+}
+
+interface IReadonlyMeasureResult {
+ readonly width: number;
+ readonly height: number;
+}
+
+interface IMeasureResult {
+ width: number;
+ height: number;
+}
+
+// TODO: For supporting browsers we should also provide a CanvasCharDimensionsProvider that uses ctx.measureText
+class DomMeasureStrategy implements IMeasureStrategy {
+ private _result: IMeasureResult = { width: 0, height: 0 };
+ private _measureElement: HTMLElement;
+
+ constructor(
+ private _document: Document,
+ private _parentElement: HTMLElement,
+ private _optionsService: IOptionsService
+ ) {
+ this._measureElement = this._document.createElement('span');
+ this._measureElement.classList.add('xterm-char-measure-element');
+ this._measureElement.textContent = 'W';
+ this._measureElement.setAttribute('aria-hidden', 'true');
+ this._parentElement.appendChild(this._measureElement);
+ }
+
+ public measure(): IReadonlyMeasureResult {
+ this._measureElement.style.fontFamily = this._optionsService.rawOptions.fontFamily;
+ this._measureElement.style.fontSize = `${this._optionsService.rawOptions.fontSize}px`;
+
+ // Note that this triggers a synchronous layout
+ const geometry = this._measureElement.getBoundingClientRect();
+
+ // If values are 0 then the element is likely currently display:none, in which case we should
+ // retain the previous value.
+ if (geometry.width !== 0 && geometry.height !== 0) {
+ this._result.width = geometry.width;
+ this._result.height = Math.ceil(geometry.height);
+ }
+
+ return this._result;
+ }
+}
diff --git a/node_modules/xterm/src/browser/services/CharacterJoinerService.ts b/node_modules/xterm/src/browser/services/CharacterJoinerService.ts
new file mode 100644
index 0000000..ca4f198
--- /dev/null
+++ b/node_modules/xterm/src/browser/services/CharacterJoinerService.ts
@@ -0,0 +1,339 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IBufferLine, ICellData, CharData } from 'common/Types';
+import { ICharacterJoiner } from 'browser/Types';
+import { AttributeData } from 'common/buffer/AttributeData';
+import { WHITESPACE_CELL_CHAR, Content } from 'common/buffer/Constants';
+import { CellData } from 'common/buffer/CellData';
+import { IBufferService } from 'common/services/Services';
+import { ICharacterJoinerService } from 'browser/services/Services';
+
+export class JoinedCellData extends AttributeData implements ICellData {
+ private _width: number;
+ // .content carries no meaning for joined CellData, simply nullify it
+ // thus we have to overload all other .content accessors
+ public content: number = 0;
+ public fg: number;
+ public bg: number;
+ public combinedData: string = '';
+
+ constructor(firstCell: ICellData, chars: string, width: number) {
+ super();
+ this.fg = firstCell.fg;
+ this.bg = firstCell.bg;
+ this.combinedData = chars;
+ this._width = width;
+ }
+
+ public isCombined(): number {
+ // always mark joined cell data as combined
+ return Content.IS_COMBINED_MASK;
+ }
+
+ public getWidth(): number {
+ return this._width;
+ }
+
+ public getChars(): string {
+ return this.combinedData;
+ }
+
+ public getCode(): number {
+ // code always gets the highest possible fake codepoint (read as -1)
+ // this is needed as code is used by caches as identifier
+ return 0x1FFFFF;
+ }
+
+ public setFromCharData(value: CharData): void {
+ throw new Error('not implemented');
+ }
+
+ public getAsCharData(): CharData {
+ return [this.fg, this.getChars(), this.getWidth(), this.getCode()];
+ }
+}
+
+export class CharacterJoinerService implements ICharacterJoinerService {
+ public serviceBrand: undefined;
+
+ private _characterJoiners: ICharacterJoiner[] = [];
+ private _nextCharacterJoinerId: number = 0;
+ private _workCell: CellData = new CellData();
+
+ constructor(
+ @IBufferService private _bufferService: IBufferService
+ ) { }
+
+ public register(handler: (text: string) => [number, number][]): number {
+ const joiner: ICharacterJoiner = {
+ id: this._nextCharacterJoinerId++,
+ handler
+ };
+
+ this._characterJoiners.push(joiner);
+ return joiner.id;
+ }
+
+ public deregister(joinerId: number): boolean {
+ for (let i = 0; i < this._characterJoiners.length; i++) {
+ if (this._characterJoiners[i].id === joinerId) {
+ this._characterJoiners.splice(i, 1);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public getJoinedCharacters(row: number): [number, number][] {
+ if (this._characterJoiners.length === 0) {
+ return [];
+ }
+
+ const line = this._bufferService.buffer.lines.get(row);
+ if (!line || line.length === 0) {
+ return [];
+ }
+
+ const ranges: [number, number][] = [];
+ const lineStr = line.translateToString(true);
+
+ // Because some cells can be represented by multiple javascript characters,
+ // we track the cell and the string indexes separately. This allows us to
+ // translate the string ranges we get from the joiners back into cell ranges
+ // for use when rendering
+ let rangeStartColumn = 0;
+ let currentStringIndex = 0;
+ let rangeStartStringIndex = 0;
+ let rangeAttrFG = line.getFg(0);
+ let rangeAttrBG = line.getBg(0);
+
+ for (let x = 0; x < line.getTrimmedLength(); x++) {
+ line.loadCell(x, this._workCell);
+
+ if (this._workCell.getWidth() === 0) {
+ // If this character is of width 0, skip it.
+ continue;
+ }
+
+ // End of range
+ if (this._workCell.fg !== rangeAttrFG || this._workCell.bg !== rangeAttrBG) {
+ // If we ended up with a sequence of more than one character,
+ // look for ranges to join.
+ if (x - rangeStartColumn > 1) {
+ const joinedRanges = this._getJoinedRanges(
+ lineStr,
+ rangeStartStringIndex,
+ currentStringIndex,
+ line,
+ rangeStartColumn
+ );
+ for (let i = 0; i < joinedRanges.length; i++) {
+ ranges.push(joinedRanges[i]);
+ }
+ }
+
+ // Reset our markers for a new range.
+ rangeStartColumn = x;
+ rangeStartStringIndex = currentStringIndex;
+ rangeAttrFG = this._workCell.fg;
+ rangeAttrBG = this._workCell.bg;
+ }
+
+ currentStringIndex += this._workCell.getChars().length || WHITESPACE_CELL_CHAR.length;
+ }
+
+ // Process any trailing ranges.
+ if (this._bufferService.cols - rangeStartColumn > 1) {
+ const joinedRanges = this._getJoinedRanges(
+ lineStr,
+ rangeStartStringIndex,
+ currentStringIndex,
+ line,
+ rangeStartColumn
+ );
+ for (let i = 0; i < joinedRanges.length; i++) {
+ ranges.push(joinedRanges[i]);
+ }
+ }
+
+ return ranges;
+ }
+
+ /**
+ * Given a segment of a line of text, find all ranges of text that should be
+ * joined in a single rendering unit. Ranges are internally converted to
+ * column ranges, rather than string ranges.
+ * @param line String representation of the full line of text
+ * @param startIndex Start position of the range to search in the string (inclusive)
+ * @param endIndex End position of the range to search in the string (exclusive)
+ */
+ private _getJoinedRanges(line: string, startIndex: number, endIndex: number, lineData: IBufferLine, startCol: number): [number, number][] {
+ const text = line.substring(startIndex, endIndex);
+ // At this point we already know that there is at least one joiner so
+ // we can just pull its value and assign it directly rather than
+ // merging it into an empty array, which incurs unnecessary writes.
+ let allJoinedRanges: [number, number][] = [];
+ try {
+ allJoinedRanges = this._characterJoiners[0].handler(text);
+ } catch (error) {
+ console.error(error);
+ }
+ for (let i = 1; i < this._characterJoiners.length; i++) {
+ // We merge any overlapping ranges across the different joiners
+ try {
+ const joinerRanges = this._characterJoiners[i].handler(text);
+ for (let j = 0; j < joinerRanges.length; j++) {
+ CharacterJoinerService._mergeRanges(allJoinedRanges, joinerRanges[j]);
+ }
+ } catch (error) {
+ console.error(error);
+ }
+ }
+ this._stringRangesToCellRanges(allJoinedRanges, lineData, startCol);
+ return allJoinedRanges;
+ }
+
+ /**
+ * Modifies the provided ranges in-place to adjust for variations between
+ * string length and cell width so that the range represents a cell range,
+ * rather than the string range the joiner provides.
+ * @param ranges String ranges containing start (inclusive) and end (exclusive) index
+ * @param line Cell data for the relevant line in the terminal
+ * @param startCol Offset within the line to start from
+ */
+ private _stringRangesToCellRanges(ranges: [number, number][], line: IBufferLine, startCol: number): void {
+ let currentRangeIndex = 0;
+ let currentRangeStarted = false;
+ let currentStringIndex = 0;
+ let currentRange = ranges[currentRangeIndex];
+
+ // If we got through all of the ranges, stop searching
+ if (!currentRange) {
+ return;
+ }
+
+ for (let x = startCol; x < this._bufferService.cols; x++) {
+ const width = line.getWidth(x);
+ const length = line.getString(x).length || WHITESPACE_CELL_CHAR.length;
+
+ // We skip zero-width characters when creating the string to join the text
+ // so we do the same here
+ if (width === 0) {
+ continue;
+ }
+
+ // Adjust the start of the range
+ if (!currentRangeStarted && currentRange[0] <= currentStringIndex) {
+ currentRange[0] = x;
+ currentRangeStarted = true;
+ }
+
+ // Adjust the end of the range
+ if (currentRange[1] <= currentStringIndex) {
+ currentRange[1] = x;
+
+ // We're finished with this range, so we move to the next one
+ currentRange = ranges[++currentRangeIndex];
+
+ // If there are no more ranges left, stop searching
+ if (!currentRange) {
+ break;
+ }
+
+ // Ranges can be on adjacent characters. Because the end index of the
+ // ranges are exclusive, this means that the index for the start of a
+ // range can be the same as the end index of the previous range. To
+ // account for the start of the next range, we check here just in case.
+ if (currentRange[0] <= currentStringIndex) {
+ currentRange[0] = x;
+ currentRangeStarted = true;
+ } else {
+ currentRangeStarted = false;
+ }
+ }
+
+ // Adjust the string index based on the character length to line up with
+ // the column adjustment
+ currentStringIndex += length;
+ }
+
+ // If there is still a range left at the end, it must extend all the way to
+ // the end of the line.
+ if (currentRange) {
+ currentRange[1] = this._bufferService.cols;
+ }
+ }
+
+ /**
+ * Merges the range defined by the provided start and end into the list of
+ * existing ranges. The merge is done in place on the existing range for
+ * performance and is also returned.
+ * @param ranges Existing range list
+ * @param newRange Tuple of two numbers representing the new range to merge in.
+ * @returns The ranges input with the new range merged in place
+ */
+ private static _mergeRanges(ranges: [number, number][], newRange: [number, number]): [number, number][] {
+ let inRange = false;
+ for (let i = 0; i < ranges.length; i++) {
+ const range = ranges[i];
+ if (!inRange) {
+ if (newRange[1] <= range[0]) {
+ // Case 1: New range is before the search range
+ ranges.splice(i, 0, newRange);
+ return ranges;
+ }
+
+ if (newRange[1] <= range[1]) {
+ // Case 2: New range is either wholly contained within the
+ // search range or overlaps with the front of it
+ range[0] = Math.min(newRange[0], range[0]);
+ return ranges;
+ }
+
+ if (newRange[0] < range[1]) {
+ // Case 3: New range either wholly contains the search range
+ // or overlaps with the end of it
+ range[0] = Math.min(newRange[0], range[0]);
+ inRange = true;
+ }
+
+ // Case 4: New range starts after the search range
+ continue;
+ } else {
+ if (newRange[1] <= range[0]) {
+ // Case 5: New range extends from previous range but doesn't
+ // reach the current one
+ ranges[i - 1][1] = newRange[1];
+ return ranges;
+ }
+
+ if (newRange[1] <= range[1]) {
+ // Case 6: New range extends from prvious range into the
+ // current range
+ ranges[i - 1][1] = Math.max(newRange[1], range[1]);
+ ranges.splice(i, 1);
+ return ranges;
+ }
+
+ // Case 7: New range extends from previous range past the
+ // end of the current range
+ ranges.splice(i, 1);
+ i--;
+ }
+ }
+
+ if (inRange) {
+ // Case 8: New range extends past the last existing range
+ ranges[ranges.length - 1][1] = newRange[1];
+ } else {
+ // Case 9: New range starts after the last existing range
+ ranges.push(newRange);
+ }
+
+ return ranges;
+ }
+}
diff --git a/node_modules/xterm/src/browser/services/CoreBrowserService.ts b/node_modules/xterm/src/browser/services/CoreBrowserService.ts
new file mode 100644
index 0000000..4eabc89
--- /dev/null
+++ b/node_modules/xterm/src/browser/services/CoreBrowserService.ts
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { ICoreBrowserService } from './Services';
+
+export class CoreBrowserService implements ICoreBrowserService {
+ public serviceBrand: undefined;
+
+ constructor(
+ private _textarea: HTMLTextAreaElement
+ ) {
+ }
+
+ public get isFocused(): boolean {
+ const docOrShadowRoot = this._textarea.getRootNode ? this._textarea.getRootNode() as Document | ShadowRoot : document;
+ return docOrShadowRoot.activeElement === this._textarea && document.hasFocus();
+ }
+}
diff --git a/node_modules/xterm/src/browser/services/MouseService.ts b/node_modules/xterm/src/browser/services/MouseService.ts
new file mode 100644
index 0000000..348ba64
--- /dev/null
+++ b/node_modules/xterm/src/browser/services/MouseService.ts
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { ICharSizeService, IRenderService, IMouseService } from './Services';
+import { getCoords, getRawByteCoords } from 'browser/input/Mouse';
+
+export class MouseService implements IMouseService {
+ public serviceBrand: undefined;
+
+ constructor(
+ @IRenderService private readonly _renderService: IRenderService,
+ @ICharSizeService private readonly _charSizeService: ICharSizeService
+ ) {
+ }
+
+ public getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined {
+ return getCoords(
+ event,
+ element,
+ colCount,
+ rowCount,
+ this._charSizeService.hasValidSize,
+ this._renderService.dimensions.actualCellWidth,
+ this._renderService.dimensions.actualCellHeight,
+ isSelection
+ );
+ }
+
+ public getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number, y: number } | undefined {
+ const coords = this.getCoords(event, element, colCount, rowCount);
+ return getRawByteCoords(coords);
+ }
+}
diff --git a/node_modules/xterm/src/browser/services/RenderService.ts b/node_modules/xterm/src/browser/services/RenderService.ts
new file mode 100644
index 0000000..b8283e0
--- /dev/null
+++ b/node_modules/xterm/src/browser/services/RenderService.ts
@@ -0,0 +1,222 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IRenderer, IRenderDimensions } from 'browser/renderer/Types';
+import { RenderDebouncer } from 'browser/RenderDebouncer';
+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 { ICharSizeService, IRenderService } from 'browser/services/Services';
+
+interface ISelectionState {
+ start: [number, number] | undefined;
+ end: [number, number] | undefined;
+ columnSelectMode: boolean;
+}
+
+export class RenderService extends Disposable implements IRenderService {
+ public serviceBrand: undefined;
+
+ private _renderDebouncer: IRenderDebouncer;
+ private _screenDprMonitor: ScreenDprMonitor;
+
+ private _isPaused: boolean = false;
+ private _needsFullRefresh: boolean = false;
+ private _isNextRenderRedrawOnly: boolean = true;
+ private _needsSelectionRefresh: boolean = false;
+ private _canvasWidth: number = 0;
+ private _canvasHeight: number = 0;
+ private _selectionState: ISelectionState = {
+ start: undefined,
+ end: undefined,
+ columnSelectMode: false
+ };
+
+ private _onDimensionsChange = new EventEmitter<IRenderDimensions>();
+ public get onDimensionsChange(): IEvent<IRenderDimensions> { return this._onDimensionsChange.event; }
+ private _onRender = new EventEmitter<{ start: number, end: number }>();
+ public get onRenderedBufferChange(): 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; }
+
+ public get dimensions(): IRenderDimensions { return this._renderer.dimensions; }
+
+ constructor(
+ private _renderer: IRenderer,
+ private _rowCount: number,
+ screenElement: HTMLElement,
+ @IOptionsService optionsService: IOptionsService,
+ @ICharSizeService private readonly _charSizeService: ICharSizeService,
+ @IBufferService bufferService: IBufferService
+ ) {
+ super();
+
+ this.register({ dispose: () => this._renderer.dispose() });
+
+ this._renderDebouncer = new RenderDebouncer((start, end) => this._renderRows(start, end));
+ this.register(this._renderDebouncer);
+
+ this._screenDprMonitor = new ScreenDprMonitor();
+ this._screenDprMonitor.setListener(() => this.onDevicePixelRatioChange());
+ this.register(this._screenDprMonitor);
+
+ this.register(bufferService.onResize(e => this._fullRefresh()));
+ this.register(optionsService.onOptionChange(() => this._renderer.onOptionsChanged()));
+ this.register(this._charSizeService.onCharSizeChange(() => this.onCharSizeChanged()));
+
+ // No need to register this as renderer is explicitly disposed in RenderService.dispose
+ this._renderer.onRequestRedraw(e => this.refreshRows(e.start, e.end, true));
+
+ // dprchange should handle this case, we need this as well for browsers that don't support the
+ // matchMedia query.
+ this.register(addDisposableDomListener(window, 'resize', () => this.onDevicePixelRatioChange()));
+
+ // Detect whether IntersectionObserver is detected and enable renderer pause
+ // and resume based on terminal visibility if so
+ if ('IntersectionObserver' in window) {
+ const observer = new IntersectionObserver(e => this._onIntersectionChange(e[e.length - 1]), { threshold: 0 });
+ observer.observe(screenElement);
+ this.register({ dispose: () => observer.disconnect() });
+ }
+ }
+
+ private _onIntersectionChange(entry: IntersectionObserverEntry): void {
+ this._isPaused = entry.isIntersecting === undefined ? (entry.intersectionRatio === 0) : !entry.isIntersecting;
+
+ // Terminal was hidden on open
+ if (!this._isPaused && !this._charSizeService.hasValidSize) {
+ this._charSizeService.measure();
+ }
+
+ if (!this._isPaused && this._needsFullRefresh) {
+ this.refreshRows(0, this._rowCount - 1);
+ this._needsFullRefresh = false;
+ }
+ }
+
+ public refreshRows(start: number, end: number, isRedrawOnly: boolean = false): void {
+ if (this._isPaused) {
+ this._needsFullRefresh = true;
+ return;
+ }
+ if (!isRedrawOnly) {
+ this._isNextRenderRedrawOnly = false;
+ }
+ this._renderDebouncer.refresh(start, end, this._rowCount);
+ }
+
+ private _renderRows(start: number, end: number): void {
+ this._renderer.renderRows(start, end);
+
+ // Update selection if needed
+ if (this._needsSelectionRefresh) {
+ this._renderer.onSelectionChanged(this._selectionState.start, this._selectionState.end, this._selectionState.columnSelectMode);
+ this._needsSelectionRefresh = false;
+ }
+
+ // Fire render event only if it was not a redraw
+ if (!this._isNextRenderRedrawOnly) {
+ this._onRender.fire({ start, end });
+ }
+ this._isNextRenderRedrawOnly = true;
+ }
+
+ public resize(cols: number, rows: number): void {
+ this._rowCount = rows;
+ this._fireOnCanvasResize();
+ }
+
+ public changeOptions(): void {
+ this._renderer.onOptionsChanged();
+ this.refreshRows(0, this._rowCount - 1);
+ this._fireOnCanvasResize();
+ }
+
+ private _fireOnCanvasResize(): void {
+ // Don't fire the event if the dimensions haven't changed
+ if (this._renderer.dimensions.canvasWidth === this._canvasWidth && this._renderer.dimensions.canvasHeight === this._canvasHeight) {
+ return;
+ }
+ this._onDimensionsChange.fire(this._renderer.dimensions);
+ }
+
+ public dispose(): void {
+ super.dispose();
+ }
+
+ public setRenderer(renderer: IRenderer): void {
+ // TODO: RenderService should be the only one to dispose the renderer
+ this._renderer.dispose();
+ this._renderer = renderer;
+ this._renderer.onRequestRedraw(e => this.refreshRows(e.start, e.end, true));
+
+ // Force a refresh
+ this._needsSelectionRefresh = true;
+ this._fullRefresh();
+ }
+
+ private _fullRefresh(): void {
+ if (this._isPaused) {
+ this._needsFullRefresh = true;
+ } else {
+ this.refreshRows(0, this._rowCount - 1);
+ }
+ }
+
+ public clearTextureAtlas(): void {
+ this._renderer?.clearTextureAtlas?.();
+ this._fullRefresh();
+ }
+
+ public setColors(colors: IColorSet): void {
+ this._renderer.setColors(colors);
+ this._fullRefresh();
+ }
+
+ public onDevicePixelRatioChange(): void {
+ // Force char size measurement as DomMeasureStrategy(getBoundingClientRect) is not stable
+ // when devicePixelRatio changes
+ this._charSizeService.measure();
+
+ this._renderer.onDevicePixelRatioChange();
+ this.refreshRows(0, this._rowCount - 1);
+ }
+
+ public onResize(cols: number, rows: number): void {
+ this._renderer.onResize(cols, rows);
+ this._fullRefresh();
+ }
+
+ // TODO: Is this useful when we have onResize?
+ public onCharSizeChanged(): void {
+ this._renderer.onCharSizeChanged();
+ }
+
+ public onBlur(): void {
+ this._renderer.onBlur();
+ }
+
+ public onFocus(): void {
+ this._renderer.onFocus();
+ }
+
+ public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
+ this._selectionState.start = start;
+ this._selectionState.end = end;
+ this._selectionState.columnSelectMode = columnSelectMode;
+ this._renderer.onSelectionChanged(start, end, columnSelectMode);
+ }
+
+ public onCursorMove(): void {
+ this._renderer.onCursorMove();
+ }
+
+ public clear(): void {
+ this._renderer.clear();
+ }
+}
diff --git a/node_modules/xterm/src/browser/services/SelectionService.ts b/node_modules/xterm/src/browser/services/SelectionService.ts
new file mode 100644
index 0000000..1ea2395
--- /dev/null
+++ b/node_modules/xterm/src/browser/services/SelectionService.ts
@@ -0,0 +1,1009 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { ISelectionRedrawRequestEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types';
+import { IBuffer } from 'common/buffer/Types';
+import { IBufferLine, IDisposable } from 'common/Types';
+import * as Browser from 'common/Platform';
+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 { IBufferService, IOptionsService, ICoreService } from 'common/services/Services';
+import { getCoordsRelativeToElement } from 'browser/input/Mouse';
+import { moveToCellSequence } from 'browser/input/MoveToCell';
+import { Disposable } from 'common/Lifecycle';
+import { getRangeLength } from 'common/buffer/BufferRange';
+
+/**
+ * The number of pixels the mouse needs to be above or below the viewport in
+ * order to scroll at the maximum speed.
+ */
+const DRAG_SCROLL_MAX_THRESHOLD = 50;
+
+/**
+ * The maximum scrolling speed
+ */
+const DRAG_SCROLL_MAX_SPEED = 15;
+
+/**
+ * The number of milliseconds between drag scroll updates.
+ */
+const DRAG_SCROLL_INTERVAL = 50;
+
+/**
+ * The maximum amount of time that can have elapsed for an alt click to move the
+ * cursor.
+ */
+const ALT_CLICK_MOVE_CURSOR_TIME = 500;
+
+const NON_BREAKING_SPACE_CHAR = String.fromCharCode(160);
+const ALL_NON_BREAKING_SPACE_REGEX = new RegExp(NON_BREAKING_SPACE_CHAR, 'g');
+
+/**
+ * Represents a position of a word on a line.
+ */
+interface IWordPosition {
+ start: number;
+ length: number;
+}
+
+/**
+ * A selection mode, this drives how the selection behaves on mouse move.
+ */
+export const enum SelectionMode {
+ NORMAL,
+ WORD,
+ LINE,
+ COLUMN
+}
+
+/**
+ * A class that manages the selection of the terminal. With help from
+ * SelectionModel, SelectionService handles with all logic associated with
+ * dealing with the selection, including handling mouse interaction, wide
+ * characters and fetching the actual text within the selection. Rendering is
+ * not handled by the SelectionService but the onRedrawRequest event is fired
+ * when the selection is ready to be redrawn (on an animation frame).
+ */
+export class SelectionService extends Disposable implements ISelectionService {
+ public serviceBrand: undefined;
+
+ protected _model: SelectionModel;
+
+ /**
+ * The amount to scroll every drag scroll update (depends on how far the mouse
+ * drag is above or below the terminal).
+ */
+ private _dragScrollAmount: number = 0;
+
+ /**
+ * The current selection mode.
+ */
+ protected _activeSelectionMode: SelectionMode;
+
+ /**
+ * A setInterval timer that is active while the mouse is down whose callback
+ * scrolls the viewport when necessary.
+ */
+ private _dragScrollIntervalTimer: number | undefined;
+
+ /**
+ * The animation frame ID used for refreshing the selection.
+ */
+ private _refreshAnimationFrame: number | undefined;
+
+ /**
+ * Whether selection is enabled.
+ */
+ private _enabled = true;
+
+ private _mouseMoveListener: EventListener;
+ private _mouseUpListener: EventListener;
+ private _trimListener: IDisposable;
+ private _workCell: CellData = new CellData();
+
+ private _mouseDownTimeStamp: number = 0;
+ private _oldHasSelection: boolean = false;
+ private _oldSelectionStart: [number, number] | undefined = undefined;
+ private _oldSelectionEnd: [number, number] | undefined = undefined;
+
+ private _onLinuxMouseSelection = this.register(new EventEmitter<string>());
+ public get onLinuxMouseSelection(): IEvent<string> { return this._onLinuxMouseSelection.event; }
+ private _onRedrawRequest = this.register(new EventEmitter<ISelectionRedrawRequestEvent>());
+ public get onRequestRedraw(): IEvent<ISelectionRedrawRequestEvent> { return this._onRedrawRequest.event; }
+ private _onSelectionChange = this.register(new EventEmitter<void>());
+ public get onSelectionChange(): IEvent<void> { return this._onSelectionChange.event; }
+ private _onRequestScrollLines = this.register(new EventEmitter<ISelectionRequestScrollLinesEvent>());
+ public get onRequestScrollLines(): IEvent<ISelectionRequestScrollLinesEvent> { return this._onRequestScrollLines.event; }
+
+ constructor(
+ private readonly _element: HTMLElement,
+ private readonly _screenElement: HTMLElement,
+ private readonly _linkifier: ILinkifier2,
+ @IBufferService private readonly _bufferService: IBufferService,
+ @ICoreService private readonly _coreService: ICoreService,
+ @IMouseService private readonly _mouseService: IMouseService,
+ @IOptionsService private readonly _optionsService: IOptionsService,
+ @IRenderService private readonly _renderService: IRenderService
+ ) {
+ super();
+
+ // Init listeners
+ this._mouseMoveListener = event => this._onMouseMove(event as MouseEvent);
+ this._mouseUpListener = event => this._onMouseUp(event as MouseEvent);
+ this._coreService.onUserInput(() => {
+ if (this.hasSelection) {
+ this.clearSelection();
+ }
+ });
+ this._trimListener = this._bufferService.buffer.lines.onTrim(amount => this._onTrim(amount));
+ this.register(this._bufferService.buffers.onBufferActivate(e => this._onBufferActivate(e)));
+
+ this.enable();
+
+ this._model = new SelectionModel(this._bufferService);
+ this._activeSelectionMode = SelectionMode.NORMAL;
+ }
+
+ public dispose(): void {
+ this._removeMouseDownListeners();
+ }
+
+ public reset(): void {
+ this.clearSelection();
+ }
+
+ /**
+ * Disables the selection manager. This is useful for when terminal mouse
+ * are enabled.
+ */
+ public disable(): void {
+ this.clearSelection();
+ this._enabled = false;
+ }
+
+ /**
+ * Enable the selection manager.
+ */
+ public enable(): void {
+ this._enabled = true;
+ }
+
+ public get selectionStart(): [number, number] | undefined { return this._model.finalSelectionStart; }
+ public get selectionEnd(): [number, number] | undefined { return this._model.finalSelectionEnd; }
+
+ /**
+ * Gets whether there is an active text selection.
+ */
+ public get hasSelection(): boolean {
+ const start = this._model.finalSelectionStart;
+ const end = this._model.finalSelectionEnd;
+ if (!start || !end) {
+ return false;
+ }
+ return start[0] !== end[0] || start[1] !== end[1];
+ }
+
+ /**
+ * Gets the text currently selected.
+ */
+ public get selectionText(): string {
+ const start = this._model.finalSelectionStart;
+ const end = this._model.finalSelectionEnd;
+ if (!start || !end) {
+ return '';
+ }
+
+ const buffer = this._bufferService.buffer;
+ const result: string[] = [];
+
+ if (this._activeSelectionMode === SelectionMode.COLUMN) {
+ // Ignore zero width selections
+ if (start[0] === end[0]) {
+ return '';
+ }
+
+ for (let i = start[1]; i <= end[1]; i++) {
+ const lineText = buffer.translateBufferLineToString(i, true, start[0], end[0]);
+ result.push(lineText);
+ }
+ } else {
+ // Get first row
+ const startRowEndCol = start[1] === end[1] ? end[0] : undefined;
+ result.push(buffer.translateBufferLineToString(start[1], true, start[0], startRowEndCol));
+
+ // Get middle rows
+ for (let i = start[1] + 1; i <= end[1] - 1; i++) {
+ const bufferLine = buffer.lines.get(i);
+ const lineText = buffer.translateBufferLineToString(i, true);
+ if (bufferLine?.isWrapped) {
+ result[result.length - 1] += lineText;
+ } else {
+ result.push(lineText);
+ }
+ }
+
+ // Get final row
+ if (start[1] !== end[1]) {
+ const bufferLine = buffer.lines.get(end[1]);
+ const lineText = buffer.translateBufferLineToString(end[1], true, 0, end[0]);
+ if (bufferLine && bufferLine!.isWrapped) {
+ result[result.length - 1] += lineText;
+ } else {
+ result.push(lineText);
+ }
+ }
+ }
+
+ // Format string by replacing non-breaking space chars with regular spaces
+ // and joining the array into a multi-line string.
+ const formattedResult = result.map(line => {
+ return line.replace(ALL_NON_BREAKING_SPACE_REGEX, ' ');
+ }).join(Browser.isWindows ? '\r\n' : '\n');
+
+ return formattedResult;
+ }
+
+ /**
+ * Clears the current terminal selection.
+ */
+ public clearSelection(): void {
+ this._model.clearSelection();
+ this._removeMouseDownListeners();
+ this.refresh();
+ this._onSelectionChange.fire();
+ }
+
+ /**
+ * Queues a refresh, redrawing the selection on the next opportunity.
+ * @param isLinuxMouseSelection Whether the selection should be registered as a new
+ * selection on Linux.
+ */
+ public refresh(isLinuxMouseSelection?: boolean): void {
+ // Queue the refresh for the renderer
+ if (!this._refreshAnimationFrame) {
+ this._refreshAnimationFrame = window.requestAnimationFrame(() => this._refresh());
+ }
+
+ // If the platform is Linux and the refresh call comes from a mouse event,
+ // we need to update the selection for middle click to paste selection.
+ if (Browser.isLinux && isLinuxMouseSelection) {
+ const selectionText = this.selectionText;
+ if (selectionText.length) {
+ this._onLinuxMouseSelection.fire(this.selectionText);
+ }
+ }
+ }
+
+ /**
+ * Fires the refresh event, causing consumers to pick it up and redraw the
+ * selection state.
+ */
+ private _refresh(): void {
+ this._refreshAnimationFrame = undefined;
+ this._onRedrawRequest.fire({
+ start: this._model.finalSelectionStart,
+ end: this._model.finalSelectionEnd,
+ columnSelectMode: this._activeSelectionMode === SelectionMode.COLUMN
+ });
+ }
+
+ /**
+ * Checks if the current click was inside the current selection
+ * @param event The mouse event
+ */
+ private _isClickInSelection(event: MouseEvent): boolean {
+ const coords = this._getMouseBufferCoords(event);
+ const start = this._model.finalSelectionStart;
+ const end = this._model.finalSelectionEnd;
+
+ if (!start || !end || !coords) {
+ return false;
+ }
+
+ return this._areCoordsInSelection(coords, 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]) ||
+ (start[1] < end[1] && coords[1] === end[1] && coords[0] < end[0]) ||
+ (start[1] < end[1] && coords[1] === start[1] && coords[0] >= start[0]);
+ }
+
+ /**
+ * Selects word at the current mouse event coordinates.
+ * @param event The mouse event.
+ */
+ private _selectWordAtCursor(event: MouseEvent, allowWhitespaceOnlySelection: boolean): boolean {
+ // Check if there is a link under the cursor first and select that if so
+ const range = this._linkifier.currentLink?.link?.range;
+ if (range) {
+ this._model.selectionStart = [range.start.x - 1, range.start.y - 1];
+ this._model.selectionStartLength = getRangeLength(range, this._bufferService.cols);
+ this._model.selectionEnd = undefined;
+ return true;
+ }
+
+ const coords = this._getMouseBufferCoords(event);
+ if (coords) {
+ this._selectWordAt(coords, allowWhitespaceOnlySelection);
+ this._model.selectionEnd = undefined;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Selects all text within the terminal.
+ */
+ public selectAll(): void {
+ this._model.isSelectAllActive = true;
+ this.refresh();
+ this._onSelectionChange.fire();
+ }
+
+ public selectLines(start: number, end: number): void {
+ this._model.clearSelection();
+ start = Math.max(start, 0);
+ end = Math.min(end, this._bufferService.buffer.lines.length - 1);
+ this._model.selectionStart = [0, start];
+ this._model.selectionEnd = [this._bufferService.cols, end];
+ this.refresh();
+ this._onSelectionChange.fire();
+ }
+
+ /**
+ * Handle the buffer being trimmed, adjust the selection position.
+ * @param amount The amount the buffer is being trimmed.
+ */
+ private _onTrim(amount: number): void {
+ const needsRefresh = this._model.onTrim(amount);
+ if (needsRefresh) {
+ this.refresh();
+ }
+ }
+
+ /**
+ * Gets the 0-based [x, y] buffer coordinates of the current mouse event.
+ * @param event The mouse event.
+ */
+ private _getMouseBufferCoords(event: MouseEvent): [number, number] | undefined {
+ const coords = this._mouseService.getCoords(event, this._screenElement, this._bufferService.cols, this._bufferService.rows, true);
+ if (!coords) {
+ return undefined;
+ }
+
+ // Convert to 0-based
+ coords[0]--;
+ coords[1]--;
+
+ // Convert viewport coords to buffer coords
+ coords[1] += this._bufferService.buffer.ydisp;
+ return coords;
+ }
+
+ /**
+ * Gets the amount the viewport should be scrolled based on how far out of the
+ * terminal the mouse is.
+ * @param event The mouse event.
+ */
+ private _getMouseEventScrollAmount(event: MouseEvent): number {
+ let offset = getCoordsRelativeToElement(event, this._screenElement)[1];
+ const terminalHeight = this._renderService.dimensions.canvasHeight;
+ if (offset >= 0 && offset <= terminalHeight) {
+ return 0;
+ }
+ if (offset > terminalHeight) {
+ offset -= terminalHeight;
+ }
+
+ offset = Math.min(Math.max(offset, -DRAG_SCROLL_MAX_THRESHOLD), DRAG_SCROLL_MAX_THRESHOLD);
+ offset /= DRAG_SCROLL_MAX_THRESHOLD;
+ return (offset / Math.abs(offset)) + Math.round(offset * (DRAG_SCROLL_MAX_SPEED - 1));
+ }
+
+ /**
+ * Returns whether the selection manager should force selection, regardless of
+ * whether the terminal is in mouse events mode.
+ * @param event The mouse event.
+ */
+ public shouldForceSelection(event: MouseEvent): boolean {
+ if (Browser.isMac) {
+ return event.altKey && this._optionsService.rawOptions.macOptionClickForcesSelection;
+ }
+
+ return event.shiftKey;
+ }
+
+ /**
+ * Handles te mousedown event, setting up for a new selection.
+ * @param event The mousedown event.
+ */
+ public onMouseDown(event: MouseEvent): void {
+ this._mouseDownTimeStamp = event.timeStamp;
+ // If we have selection, we want the context menu on right click even if the
+ // terminal is in mouse mode.
+ if (event.button === 2 && this.hasSelection) {
+ return;
+ }
+
+ // Only action the primary button
+ if (event.button !== 0) {
+ return;
+ }
+
+ // Allow selection when using a specific modifier key, even when disabled
+ if (!this._enabled) {
+ if (!this.shouldForceSelection(event)) {
+ return;
+ }
+
+ // Don't send the mouse down event to the current process, we want to select
+ event.stopPropagation();
+ }
+
+ // Tell the browser not to start a regular selection
+ event.preventDefault();
+
+ // Reset drag scroll state
+ this._dragScrollAmount = 0;
+
+ if (this._enabled && event.shiftKey) {
+ this._onIncrementalClick(event);
+ } else {
+ if (event.detail === 1) {
+ this._onSingleClick(event);
+ } else if (event.detail === 2) {
+ this._onDoubleClick(event);
+ } else if (event.detail === 3) {
+ this._onTripleClick(event);
+ }
+ }
+
+ this._addMouseDownListeners();
+ this.refresh(true);
+ }
+
+ /**
+ * Adds listeners when mousedown is triggered.
+ */
+ private _addMouseDownListeners(): void {
+ // Listen on the document so that dragging outside of viewport works
+ if (this._screenElement.ownerDocument) {
+ this._screenElement.ownerDocument.addEventListener('mousemove', this._mouseMoveListener);
+ this._screenElement.ownerDocument.addEventListener('mouseup', this._mouseUpListener);
+ }
+ this._dragScrollIntervalTimer = window.setInterval(() => this._dragScroll(), DRAG_SCROLL_INTERVAL);
+ }
+
+ /**
+ * Removes the listeners that are registered when mousedown is triggered.
+ */
+ private _removeMouseDownListeners(): void {
+ if (this._screenElement.ownerDocument) {
+ this._screenElement.ownerDocument.removeEventListener('mousemove', this._mouseMoveListener);
+ this._screenElement.ownerDocument.removeEventListener('mouseup', this._mouseUpListener);
+ }
+ clearInterval(this._dragScrollIntervalTimer);
+ this._dragScrollIntervalTimer = undefined;
+ }
+
+ /**
+ * Performs an incremental click, setting the selection end position to the mouse
+ * position.
+ * @param event The mouse event.
+ */
+ private _onIncrementalClick(event: MouseEvent): void {
+ if (this._model.selectionStart) {
+ this._model.selectionEnd = this._getMouseBufferCoords(event);
+ }
+ }
+
+ /**
+ * Performs a single click, resetting relevant state and setting the selection
+ * start position.
+ * @param event The mouse event.
+ */
+ private _onSingleClick(event: MouseEvent): void {
+ this._model.selectionStartLength = 0;
+ this._model.isSelectAllActive = false;
+ this._activeSelectionMode = this.shouldColumnSelect(event) ? SelectionMode.COLUMN : SelectionMode.NORMAL;
+
+ // Initialize the new selection
+ this._model.selectionStart = this._getMouseBufferCoords(event);
+ if (!this._model.selectionStart) {
+ return;
+ }
+ this._model.selectionEnd = undefined;
+
+ // Ensure the line exists
+ const line = this._bufferService.buffer.lines.get(this._model.selectionStart[1]);
+ if (!line) {
+ return;
+ }
+
+ // Return early if the click event is not in the buffer (eg. in scroll bar)
+ if (line.length === this._model.selectionStart[0]) {
+ return;
+ }
+
+ // If the mouse is over the second half of a wide character, adjust the
+ // selection to cover the whole character
+ if (line.hasWidth(this._model.selectionStart[0]) === 0) {
+ this._model.selectionStart[0]++;
+ }
+ }
+
+ /**
+ * Performs a double click, selecting the current word.
+ * @param event The mouse event.
+ */
+ private _onDoubleClick(event: MouseEvent): void {
+ if (this._selectWordAtCursor(event, true)) {
+ this._activeSelectionMode = SelectionMode.WORD;
+ }
+ }
+
+ /**
+ * Performs a triple click, selecting the current line and activating line
+ * select mode.
+ * @param event The mouse event.
+ */
+ private _onTripleClick(event: MouseEvent): void {
+ const coords = this._getMouseBufferCoords(event);
+ if (coords) {
+ this._activeSelectionMode = SelectionMode.LINE;
+ this._selectLineAt(coords[1]);
+ }
+ }
+
+ /**
+ * Returns whether the selection manager should operate in column select mode
+ * @param event the mouse or keyboard event
+ */
+ public shouldColumnSelect(event: KeyboardEvent | MouseEvent): boolean {
+ return event.altKey && !(Browser.isMac && this._optionsService.rawOptions.macOptionClickForcesSelection);
+ }
+
+ /**
+ * Handles the mousemove event when the mouse button is down, recording the
+ * end of the selection and refreshing the selection.
+ * @param event The mousemove event.
+ */
+ private _onMouseMove(event: MouseEvent): void {
+ // If the mousemove listener is active it means that a selection is
+ // currently being made, we should stop propagation to prevent mouse events
+ // to be sent to the pty.
+ event.stopImmediatePropagation();
+
+ // Do nothing if there is no selection start, this can happen if the first
+ // click in the terminal is an incremental click
+ if (!this._model.selectionStart) {
+ return;
+ }
+
+ // Record the previous position so we know whether to redraw the selection
+ // at the end.
+ const previousSelectionEnd = this._model.selectionEnd ? [this._model.selectionEnd[0], this._model.selectionEnd[1]] : null;
+
+ // Set the initial selection end based on the mouse coordinates
+ this._model.selectionEnd = this._getMouseBufferCoords(event);
+ if (!this._model.selectionEnd) {
+ this.refresh(true);
+ return;
+ }
+
+ // Select the entire line if line select mode is active.
+ if (this._activeSelectionMode === SelectionMode.LINE) {
+ if (this._model.selectionEnd[1] < this._model.selectionStart[1]) {
+ this._model.selectionEnd[0] = 0;
+ } else {
+ this._model.selectionEnd[0] = this._bufferService.cols;
+ }
+ } else if (this._activeSelectionMode === SelectionMode.WORD) {
+ this._selectToWordAt(this._model.selectionEnd);
+ }
+
+ // Determine the amount of scrolling that will happen.
+ this._dragScrollAmount = this._getMouseEventScrollAmount(event);
+
+ // If the cursor was above or below the viewport, make sure it's at the
+ // start or end of the viewport respectively. This should only happen when
+ // NOT in column select mode.
+ if (this._activeSelectionMode !== SelectionMode.COLUMN) {
+ if (this._dragScrollAmount > 0) {
+ this._model.selectionEnd[0] = this._bufferService.cols;
+ } else if (this._dragScrollAmount < 0) {
+ this._model.selectionEnd[0] = 0;
+ }
+ }
+
+ // If the character is a wide character include the cell to the right in the
+ // selection. Note that selections at the very end of the line will never
+ // have a character.
+ const buffer = this._bufferService.buffer;
+ if (this._model.selectionEnd[1] < buffer.lines.length) {
+ const line = buffer.lines.get(this._model.selectionEnd[1]);
+ if (line && line.hasWidth(this._model.selectionEnd[0]) === 0) {
+ this._model.selectionEnd[0]++;
+ }
+ }
+
+ // Only draw here if the selection changes.
+ if (!previousSelectionEnd ||
+ previousSelectionEnd[0] !== this._model.selectionEnd[0] ||
+ previousSelectionEnd[1] !== this._model.selectionEnd[1]) {
+ this.refresh(true);
+ }
+ }
+
+ /**
+ * The callback that occurs every DRAG_SCROLL_INTERVAL ms that does the
+ * scrolling of the viewport.
+ */
+ private _dragScroll(): void {
+ if (!this._model.selectionEnd || !this._model.selectionStart) {
+ return;
+ }
+ if (this._dragScrollAmount) {
+ this._onRequestScrollLines.fire({ amount: this._dragScrollAmount, suppressScrollEvent: false });
+ // Re-evaluate selection
+ // If the cursor was above or below the viewport, make sure it's at the
+ // start or end of the viewport respectively. This should only happen when
+ // NOT in column select mode.
+ const buffer = this._bufferService.buffer;
+ if (this._dragScrollAmount > 0) {
+ if (this._activeSelectionMode !== SelectionMode.COLUMN) {
+ this._model.selectionEnd[0] = this._bufferService.cols;
+ }
+ this._model.selectionEnd[1] = Math.min(buffer.ydisp + this._bufferService.rows, buffer.lines.length - 1);
+ } else {
+ if (this._activeSelectionMode !== SelectionMode.COLUMN) {
+ this._model.selectionEnd[0] = 0;
+ }
+ this._model.selectionEnd[1] = buffer.ydisp;
+ }
+ this.refresh();
+ }
+ }
+
+ /**
+ * Handles the mouseup event, removing the mousedown listeners.
+ * @param event The mouseup event.
+ */
+ private _onMouseUp(event: MouseEvent): void {
+ const timeElapsed = event.timeStamp - this._mouseDownTimeStamp;
+
+ this._removeMouseDownListeners();
+
+ if (this.selectionText.length <= 1 && timeElapsed < ALT_CLICK_MOVE_CURSOR_TIME && event.altKey && this._optionsService.getOption('altClickMovesCursor')) {
+ if (this._bufferService.buffer.ybase === this._bufferService.buffer.ydisp) {
+ const coordinates = this._mouseService.getCoords(
+ event,
+ this._element,
+ this._bufferService.cols,
+ this._bufferService.rows,
+ false
+ );
+ if (coordinates && coordinates[0] !== undefined && coordinates[1] !== undefined) {
+ const sequence = moveToCellSequence(coordinates[0] - 1, coordinates[1] - 1, this._bufferService, this._coreService.decPrivateModes.applicationCursorKeys);
+ this._coreService.triggerDataEvent(sequence, true);
+ }
+ }
+ } else {
+ this._fireEventIfSelectionChanged();
+ }
+ }
+
+ private _fireEventIfSelectionChanged(): void {
+ const start = this._model.finalSelectionStart;
+ const end = this._model.finalSelectionEnd;
+ const hasSelection = !!start && !!end && (start[0] !== end[0] || start[1] !== end[1]);
+
+ if (!hasSelection) {
+ if (this._oldHasSelection) {
+ this._fireOnSelectionChange(start, end, hasSelection);
+ }
+ return;
+ }
+
+ // Sanity check, these should not be undefined as there is a selection
+ if (!start || !end) {
+ return;
+ }
+
+ if (!this._oldSelectionStart || !this._oldSelectionEnd || (
+ start[0] !== this._oldSelectionStart[0] || start[1] !== this._oldSelectionStart[1] ||
+ end[0] !== this._oldSelectionEnd[0] || end[1] !== this._oldSelectionEnd[1])) {
+
+ this._fireOnSelectionChange(start, end, hasSelection);
+ }
+ }
+
+ private _fireOnSelectionChange(start: [number, number] | undefined, end: [number, number] | undefined, hasSelection: boolean): void {
+ this._oldSelectionStart = start;
+ this._oldSelectionEnd = end;
+ this._oldHasSelection = hasSelection;
+ this._onSelectionChange.fire();
+ }
+
+ private _onBufferActivate(e: {activeBuffer: IBuffer, inactiveBuffer: IBuffer}): void {
+ this.clearSelection();
+ // Only adjust the selection on trim, shiftElements is rarely used (only in
+ // reverseIndex) and delete in a splice is only ever used when the same
+ // number of elements was just added. Given this is could actually be
+ // beneficial to leave the selection as is for these cases.
+ this._trimListener.dispose();
+ this._trimListener = e.activeBuffer.lines.onTrim(amount => this._onTrim(amount));
+ }
+
+ /**
+ * Converts a viewport column to the character index on the buffer line, the
+ * latter takes into account wide characters.
+ * @param coords The coordinates to find the 2 index for.
+ */
+ private _convertViewportColToCharacterIndex(bufferLine: IBufferLine, coords: [number, number]): number {
+ let charIndex = coords[0];
+ for (let i = 0; coords[0] >= i; i++) {
+ const length = bufferLine.loadCell(i, this._workCell).getChars().length;
+ if (this._workCell.getWidth() === 0) {
+ // Wide characters aren't included in the line string so decrement the
+ // index so the index is back on the wide character.
+ charIndex--;
+ } else if (length > 1 && coords[0] !== i) {
+ // Emojis take up multiple characters, so adjust accordingly. For these
+ // we don't want ot include the character at the column as we're
+ // returning the start index in the string, not the end index.
+ charIndex += length - 1;
+ }
+ }
+ return charIndex;
+ }
+
+ public setSelection(col: number, row: number, length: number): void {
+ this._model.clearSelection();
+ this._removeMouseDownListeners();
+ this._model.selectionStart = [col, row];
+ this._model.selectionStartLength = length;
+ this.refresh();
+ }
+
+ public rightClickSelect(ev: MouseEvent): void {
+ if (!this._isClickInSelection(ev)) {
+ if (this._selectWordAtCursor(ev, false)) {
+ this.refresh(true);
+ }
+ this._fireEventIfSelectionChanged();
+ }
+ }
+
+ /**
+ * Gets positional information for the word at the coordinated specified.
+ * @param coords The coordinates to get the word at.
+ */
+ private _getWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean, followWrappedLinesAbove: boolean = true, followWrappedLinesBelow: boolean = true): IWordPosition | undefined {
+ // Ensure coords are within viewport (eg. not within scroll bar)
+ if (coords[0] >= this._bufferService.cols) {
+ return undefined;
+ }
+
+ const buffer = this._bufferService.buffer;
+ const bufferLine = buffer.lines.get(coords[1]);
+ if (!bufferLine) {
+ return undefined;
+ }
+
+ const line = buffer.translateBufferLineToString(coords[1], false);
+
+ // Get actual index, taking into consideration wide characters
+ let startIndex = this._convertViewportColToCharacterIndex(bufferLine, coords);
+ let endIndex = startIndex;
+
+ // Record offset to be used later
+ const charOffset = coords[0] - startIndex;
+ let leftWideCharCount = 0;
+ let rightWideCharCount = 0;
+ let leftLongCharOffset = 0;
+ let rightLongCharOffset = 0;
+
+ if (line.charAt(startIndex) === ' ') {
+ // Expand until non-whitespace is hit
+ while (startIndex > 0 && line.charAt(startIndex - 1) === ' ') {
+ startIndex--;
+ }
+ while (endIndex < line.length && line.charAt(endIndex + 1) === ' ') {
+ endIndex++;
+ }
+ } else {
+ // Expand until whitespace is hit. This algorithm works by scanning left
+ // and right from the starting position, keeping both the index format
+ // (line) and the column format (bufferLine) in sync. When a wide
+ // character is hit, it is recorded and the column index is adjusted.
+ let startCol = coords[0];
+ let endCol = coords[0];
+
+ // Consider the initial position, skip it and increment the wide char
+ // variable
+ if (bufferLine.getWidth(startCol) === 0) {
+ leftWideCharCount++;
+ startCol--;
+ }
+ if (bufferLine.getWidth(endCol) === 2) {
+ rightWideCharCount++;
+ endCol++;
+ }
+
+ // Adjust the end index for characters whose length are > 1 (emojis)
+ const length = bufferLine.getString(endCol).length;
+ if (length > 1) {
+ rightLongCharOffset += length - 1;
+ endIndex += length - 1;
+ }
+
+ // Expand the string in both directions until a space is hit
+ while (startCol > 0 && startIndex > 0 && !this._isCharWordSeparator(bufferLine.loadCell(startCol - 1, this._workCell))) {
+ bufferLine.loadCell(startCol - 1, this._workCell);
+ const length = this._workCell.getChars().length;
+ if (this._workCell.getWidth() === 0) {
+ // If the next character is a wide char, record it and skip the column
+ leftWideCharCount++;
+ startCol--;
+ } else if (length > 1) {
+ // If the next character's string is longer than 1 char (eg. emoji),
+ // adjust the index
+ leftLongCharOffset += length - 1;
+ startIndex -= length - 1;
+ }
+ startIndex--;
+ startCol--;
+ }
+ while (endCol < bufferLine.length && endIndex + 1 < line.length && !this._isCharWordSeparator(bufferLine.loadCell(endCol + 1, this._workCell))) {
+ bufferLine.loadCell(endCol + 1, this._workCell);
+ const length = this._workCell.getChars().length;
+ if (this._workCell.getWidth() === 2) {
+ // If the next character is a wide char, record it and skip the column
+ rightWideCharCount++;
+ endCol++;
+ } else if (length > 1) {
+ // If the next character's string is longer than 1 char (eg. emoji),
+ // adjust the index
+ rightLongCharOffset += length - 1;
+ endIndex += length - 1;
+ }
+ endIndex++;
+ endCol++;
+ }
+ }
+
+ // Incremenet the end index so it is at the start of the next character
+ endIndex++;
+
+ // Calculate the start _column_, converting the the string indexes back to
+ // column coordinates.
+ let start =
+ startIndex // The index of the selection's start char in the line string
+ + charOffset // The difference between the initial char's column and index
+ - leftWideCharCount // The number of wide chars left of the initial char
+ + leftLongCharOffset; // The number of additional chars left of the initial char added by columns with strings longer than 1 (emojis)
+
+ // Calculate the length in _columns_, converting the the string indexes back
+ // to column coordinates.
+ let length = Math.min(this._bufferService.cols, // Disallow lengths larger than the terminal cols
+ endIndex // The index of the selection's end char in the line string
+ - startIndex // The index of the selection's start char in the line string
+ + leftWideCharCount // The number of wide chars left of the initial char
+ + rightWideCharCount // The number of wide chars right of the initial char (inclusive)
+ - leftLongCharOffset // The number of additional chars left of the initial char added by columns with strings longer than 1 (emojis)
+ - rightLongCharOffset); // The number of additional chars right of the initial char (inclusive) added by columns with strings longer than 1 (emojis)
+
+ if (!allowWhitespaceOnlySelection && line.slice(startIndex, endIndex).trim() === '') {
+ return undefined;
+ }
+
+ // Recurse upwards if the line is wrapped and the word wraps to the above line
+ if (followWrappedLinesAbove) {
+ if (start === 0 && bufferLine.getCodePoint(0) !== 32 /* ' ' */) {
+ const previousBufferLine = buffer.lines.get(coords[1] - 1);
+ if (previousBufferLine && bufferLine.isWrapped && previousBufferLine.getCodePoint(this._bufferService.cols - 1) !== 32 /* ' ' */) {
+ const previousLineWordPosition = this._getWordAt([this._bufferService.cols - 1, coords[1] - 1], false, true, false);
+ if (previousLineWordPosition) {
+ const offset = this._bufferService.cols - previousLineWordPosition.start;
+ start -= offset;
+ length += offset;
+ }
+ }
+ }
+ }
+
+ // Recurse downwards if the line is wrapped and the word wraps to the next line
+ if (followWrappedLinesBelow) {
+ if (start + length === this._bufferService.cols && bufferLine.getCodePoint(this._bufferService.cols - 1) !== 32 /* ' ' */) {
+ const nextBufferLine = buffer.lines.get(coords[1] + 1);
+ if (nextBufferLine?.isWrapped && nextBufferLine.getCodePoint(0) !== 32 /* ' ' */) {
+ const nextLineWordPosition = this._getWordAt([0, coords[1] + 1], false, false, true);
+ if (nextLineWordPosition) {
+ length += nextLineWordPosition.length;
+ }
+ }
+ }
+ }
+
+ return { start, length };
+ }
+
+ /**
+ * Selects the word at the coordinates specified.
+ * @param coords The coordinates to get the word at.
+ * @param allowWhitespaceOnlySelection If whitespace should be selected
+ */
+ protected _selectWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean): void {
+ const wordPosition = this._getWordAt(coords, allowWhitespaceOnlySelection);
+ if (wordPosition) {
+ // Adjust negative start value
+ while (wordPosition.start < 0) {
+ wordPosition.start += this._bufferService.cols;
+ coords[1]--;
+ }
+ this._model.selectionStart = [wordPosition.start, coords[1]];
+ this._model.selectionStartLength = wordPosition.length;
+ }
+ }
+
+ /**
+ * Sets the selection end to the word at the coordinated specified.
+ * @param coords The coordinates to get the word at.
+ */
+ private _selectToWordAt(coords: [number, number]): void {
+ const wordPosition = this._getWordAt(coords, true);
+ if (wordPosition) {
+ let endRow = coords[1];
+
+ // Adjust negative start value
+ while (wordPosition.start < 0) {
+ wordPosition.start += this._bufferService.cols;
+ endRow--;
+ }
+
+ // Adjust wrapped length value, this only needs to happen when values are reversed as in that
+ // case we're interested in the start of the word, not the end
+ if (!this._model.areSelectionValuesReversed()) {
+ while (wordPosition.start + wordPosition.length > this._bufferService.cols) {
+ wordPosition.length -= this._bufferService.cols;
+ endRow++;
+ }
+ }
+
+ this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : wordPosition.start + wordPosition.length, endRow];
+ }
+ }
+
+ /**
+ * Gets whether the character is considered a word separator by the select
+ * word logic.
+ * @param char The character to check.
+ */
+ private _isCharWordSeparator(cell: CellData): boolean {
+ // Zero width characters are never separators as they are always to the
+ // right of wide characters
+ if (cell.getWidth() === 0) {
+ return false;
+ }
+ return this._optionsService.rawOptions.wordSeparator.indexOf(cell.getChars()) >= 0;
+ }
+
+ /**
+ * Selects the line specified.
+ * @param line The line index.
+ */
+ protected _selectLineAt(line: number): void {
+ const wrappedRange = this._bufferService.buffer.getWrappedRangeForLine(line);
+ this._model.selectionStart = [0, wrappedRange.first];
+ this._model.selectionEnd = [this._bufferService.cols, wrappedRange.last];
+ this._model.selectionStartLength = 0;
+ }
+}
diff --git a/node_modules/xterm/src/browser/services/Services.ts b/node_modules/xterm/src/browser/services/Services.ts
new file mode 100644
index 0000000..4928fa2
--- /dev/null
+++ b/node_modules/xterm/src/browser/services/Services.ts
@@ -0,0 +1,115 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IEvent } from 'common/EventEmitter';
+import { IRenderDimensions, IRenderer } from 'browser/renderer/Types';
+import { IColorSet } from 'browser/Types';
+import { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types';
+import { createDecorator } from 'common/services/ServiceRegistry';
+import { IDisposable } from 'common/Types';
+
+export const ICharSizeService = createDecorator<ICharSizeService>('CharSizeService');
+export interface ICharSizeService {
+ serviceBrand: undefined;
+
+ readonly width: number;
+ readonly height: number;
+ readonly hasValidSize: boolean;
+
+ readonly onCharSizeChange: IEvent<void>;
+
+ measure(): void;
+}
+
+export const ICoreBrowserService = createDecorator<ICoreBrowserService>('CoreBrowserService');
+export interface ICoreBrowserService {
+ serviceBrand: undefined;
+
+ readonly isFocused: boolean;
+}
+
+export const IMouseService = createDecorator<IMouseService>('MouseService');
+export interface IMouseService {
+ serviceBrand: undefined;
+
+ getCoords(event: {clientX: number, clientY: number}, element: HTMLElement, colCount: number, rowCount: number, isSelection?: boolean): [number, number] | undefined;
+ getRawByteCoords(event: MouseEvent, element: HTMLElement, colCount: number, rowCount: number): { x: number, y: number } | undefined;
+}
+
+export const IRenderService = createDecorator<IRenderService>('RenderService');
+export interface IRenderService extends IDisposable {
+ serviceBrand: undefined;
+
+ onDimensionsChange: IEvent<IRenderDimensions>;
+ /**
+ * Fires when buffer changes are rendered. This does not fire when only cursor
+ * or selections are rendered.
+ */
+ onRenderedBufferChange: IEvent<{ start: number, end: number }>;
+ onRefreshRequest: IEvent<{ start: number, end: number }>;
+
+ dimensions: IRenderDimensions;
+
+ 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;
+ onResize(cols: number, rows: number): void;
+ // TODO: Is this useful when we have onResize?
+ onCharSizeChanged(): void;
+ onBlur(): void;
+ onFocus(): void;
+ onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void;
+ onCursorMove(): void;
+ clear(): void;
+}
+
+export const ISelectionService = createDecorator<ISelectionService>('SelectionService');
+export interface ISelectionService {
+ serviceBrand: undefined;
+
+ readonly selectionText: string;
+ readonly hasSelection: boolean;
+ readonly selectionStart: [number, number] | undefined;
+ readonly selectionEnd: [number, number] | undefined;
+
+ readonly onLinuxMouseSelection: IEvent<string>;
+ readonly onRequestRedraw: IEvent<ISelectionRequestRedrawEvent>;
+ readonly onRequestScrollLines: IEvent<ISelectionRequestScrollLinesEvent>;
+ readonly onSelectionChange: IEvent<void>;
+
+ disable(): void;
+ enable(): void;
+ reset(): void;
+ setSelection(row: number, col: number, length: number): void;
+ selectAll(): void;
+ selectLines(start: number, end: number): void;
+ clearSelection(): void;
+ rightClickSelect(event: MouseEvent): void;
+ shouldColumnSelect(event: KeyboardEvent | MouseEvent): boolean;
+ shouldForceSelection(event: MouseEvent): boolean;
+ refresh(isLinuxMouseSelection?: boolean): void;
+ onMouseDown(event: MouseEvent): void;
+}
+
+export const ISoundService = createDecorator<ISoundService>('SoundService');
+export interface ISoundService {
+ serviceBrand: undefined;
+
+ playBellSound(): void;
+}
+
+
+export const ICharacterJoinerService = createDecorator<ICharacterJoinerService>('CharacterJoinerService');
+export interface ICharacterJoinerService {
+ serviceBrand: undefined;
+
+ register(handler: (text: string) => [number, number][]): number;
+ deregister(joinerId: number): boolean;
+ getJoinedCharacters(row: number): [number, number][];
+}
diff --git a/node_modules/xterm/src/browser/services/SoundService.ts b/node_modules/xterm/src/browser/services/SoundService.ts
new file mode 100644
index 0000000..a3b6800
--- /dev/null
+++ b/node_modules/xterm/src/browser/services/SoundService.ts
@@ -0,0 +1,63 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IOptionsService } from 'common/services/Services';
+import { ISoundService } from 'browser/services/Services';
+
+export class SoundService implements ISoundService {
+ public serviceBrand: undefined;
+
+ private static _audioContext: AudioContext;
+
+ public static get audioContext(): AudioContext | null {
+ if (!SoundService._audioContext) {
+ const audioContextCtor: typeof AudioContext = (window as any).AudioContext || (window as any).webkitAudioContext;
+ if (!audioContextCtor) {
+ console.warn('Web Audio API is not supported by this browser. Consider upgrading to the latest version');
+ return null;
+ }
+ SoundService._audioContext = new audioContextCtor();
+ }
+ return SoundService._audioContext;
+ }
+
+ constructor(
+ @IOptionsService private _optionsService: IOptionsService
+ ) {
+ }
+
+ public playBellSound(): void {
+ const ctx = SoundService.audioContext;
+ if (!ctx) {
+ return;
+ }
+ const bellAudioSource = ctx.createBufferSource();
+ ctx.decodeAudioData(this._base64ToArrayBuffer(this._removeMimeType(this._optionsService.rawOptions.bellSound)), (buffer) => {
+ bellAudioSource.buffer = buffer;
+ bellAudioSource.connect(ctx.destination);
+ bellAudioSource.start(0);
+ });
+ }
+
+ private _base64ToArrayBuffer(base64: string): ArrayBuffer {
+ const binaryString = window.atob(base64);
+ const len = binaryString.length;
+ const bytes = new Uint8Array(len);
+
+ for (let i = 0; i < len; i++) {
+ bytes[i] = binaryString.charCodeAt(i);
+ }
+
+ return bytes.buffer;
+ }
+
+ private _removeMimeType(dataURI: string): string {
+ // Split the input to get the mime-type and the data itself
+ const splitUri = dataURI.split(',');
+
+ // Return only the data
+ return splitUri[1];
+ }
+}
diff --git a/node_modules/xterm/src/common/CircularList.ts b/node_modules/xterm/src/common/CircularList.ts
new file mode 100644
index 0000000..4d2c04e
--- /dev/null
+++ b/node_modules/xterm/src/common/CircularList.ts
@@ -0,0 +1,239 @@
+/**
+ * Copyright (c) 2016 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { ICircularList } from 'common/Types';
+import { EventEmitter, IEvent } from 'common/EventEmitter';
+
+export interface IInsertEvent {
+ index: number;
+ amount: number;
+}
+
+export interface IDeleteEvent {
+ index: number;
+ amount: number;
+}
+
+/**
+ * Represents a circular list; a list with a maximum size that wraps around when push is called,
+ * overriding values at the start of the list.
+ */
+export class CircularList<T> implements ICircularList<T> {
+ protected _array: (T | undefined)[];
+ private _startIndex: number;
+ private _length: number;
+
+ public onDeleteEmitter = new EventEmitter<IDeleteEvent>();
+ public get onDelete(): IEvent<IDeleteEvent> { return this.onDeleteEmitter.event; }
+ public onInsertEmitter = new EventEmitter<IInsertEvent>();
+ public get onInsert(): IEvent<IInsertEvent> { return this.onInsertEmitter.event; }
+ public onTrimEmitter = new EventEmitter<number>();
+ public get onTrim(): IEvent<number> { return this.onTrimEmitter.event; }
+
+ constructor(
+ private _maxLength: number
+ ) {
+ this._array = new Array<T>(this._maxLength);
+ this._startIndex = 0;
+ this._length = 0;
+ }
+
+ public get maxLength(): number {
+ return this._maxLength;
+ }
+
+ public set maxLength(newMaxLength: number) {
+ // There was no change in maxLength, return early.
+ if (this._maxLength === newMaxLength) {
+ return;
+ }
+
+ // Reconstruct array, starting at index 0. Only transfer values from the
+ // indexes 0 to length.
+ const newArray = new Array<T | undefined>(newMaxLength);
+ for (let i = 0; i < Math.min(newMaxLength, this.length); i++) {
+ newArray[i] = this._array[this._getCyclicIndex(i)];
+ }
+ this._array = newArray;
+ this._maxLength = newMaxLength;
+ this._startIndex = 0;
+ }
+
+ public get length(): number {
+ return this._length;
+ }
+
+ public set length(newLength: number) {
+ if (newLength > this._length) {
+ for (let i = this._length; i < newLength; i++) {
+ this._array[i] = undefined;
+ }
+ }
+ this._length = newLength;
+ }
+
+ /**
+ * Gets the value at an index.
+ *
+ * Note that for performance reasons there is no bounds checking here, the index reference is
+ * circular so this should always return a value and never throw.
+ * @param index The index of the value to get.
+ * @return The value corresponding to the index.
+ */
+ public get(index: number): T | undefined {
+ return this._array[this._getCyclicIndex(index)];
+ }
+
+ /**
+ * Sets the value at an index.
+ *
+ * Note that for performance reasons there is no bounds checking here, the index reference is
+ * circular so this should always return a value and never throw.
+ * @param index The index to set.
+ * @param value The value to set.
+ */
+ public set(index: number, value: T | undefined): void {
+ this._array[this._getCyclicIndex(index)] = value;
+ }
+
+ /**
+ * Pushes a new value onto the list, wrapping around to the start of the array, overriding index 0
+ * if the maximum length is reached.
+ * @param value The value to push onto the list.
+ */
+ public push(value: T): void {
+ this._array[this._getCyclicIndex(this._length)] = value;
+ if (this._length === this._maxLength) {
+ this._startIndex = ++this._startIndex % this._maxLength;
+ this.onTrimEmitter.fire(1);
+ } else {
+ this._length++;
+ }
+ }
+
+ /**
+ * Advance ringbuffer index and return current element for recycling.
+ * Note: The buffer must be full for this method to work.
+ * @throws When the buffer is not full.
+ */
+ public recycle(): T {
+ if (this._length !== this._maxLength) {
+ throw new Error('Can only recycle when the buffer is full');
+ }
+ this._startIndex = ++this._startIndex % this._maxLength;
+ this.onTrimEmitter.fire(1);
+ return this._array[this._getCyclicIndex(this._length - 1)]!;
+ }
+
+ /**
+ * Ringbuffer is at max length.
+ */
+ public get isFull(): boolean {
+ return this._length === this._maxLength;
+ }
+
+ /**
+ * Removes and returns the last value on the list.
+ * @return The popped value.
+ */
+ public pop(): T | undefined {
+ return this._array[this._getCyclicIndex(this._length-- - 1)];
+ }
+
+ /**
+ * Deletes and/or inserts items at a particular index (in that order). Unlike
+ * Array.prototype.splice, this operation does not return the deleted items as a new array in
+ * order to save creating a new array. Note that this operation may shift all values in the list
+ * in the worst case.
+ * @param start The index to delete and/or insert.
+ * @param deleteCount The number of elements to delete.
+ * @param items The items to insert.
+ */
+ public splice(start: number, deleteCount: number, ...items: T[]): void {
+ // Delete items
+ if (deleteCount) {
+ for (let i = start; i < this._length - deleteCount; i++) {
+ this._array[this._getCyclicIndex(i)] = this._array[this._getCyclicIndex(i + deleteCount)];
+ }
+ this._length -= deleteCount;
+ this.onDeleteEmitter.fire({ index: start, amount: deleteCount });
+ }
+
+ // Add items
+ for (let i = this._length - 1; i >= start; i--) {
+ this._array[this._getCyclicIndex(i + items.length)] = this._array[this._getCyclicIndex(i)];
+ }
+ for (let i = 0; i < items.length; i++) {
+ this._array[this._getCyclicIndex(start + i)] = items[i];
+ }
+ if (items.length) {
+ this.onInsertEmitter.fire({ index: start, amount: items.length });
+ }
+
+ // Adjust length as needed
+ if (this._length + items.length > this._maxLength) {
+ const countToTrim = (this._length + items.length) - this._maxLength;
+ this._startIndex += countToTrim;
+ this._length = this._maxLength;
+ this.onTrimEmitter.fire(countToTrim);
+ } else {
+ this._length += items.length;
+ }
+ }
+
+ /**
+ * Trims a number of items from the start of the list.
+ * @param count The number of items to remove.
+ */
+ public trimStart(count: number): void {
+ if (count > this._length) {
+ count = this._length;
+ }
+ this._startIndex += count;
+ this._length -= count;
+ this.onTrimEmitter.fire(count);
+ }
+
+ public shiftElements(start: number, count: number, offset: number): void {
+ if (count <= 0) {
+ return;
+ }
+ if (start < 0 || start >= this._length) {
+ throw new Error('start argument out of range');
+ }
+ if (start + offset < 0) {
+ throw new Error('Cannot shift elements in list beyond index 0');
+ }
+
+ if (offset > 0) {
+ for (let i = count - 1; i >= 0; i--) {
+ this.set(start + i + offset, this.get(start + i));
+ }
+ const expandListBy = (start + count + offset) - this._length;
+ if (expandListBy > 0) {
+ this._length += expandListBy;
+ while (this._length > this._maxLength) {
+ this._length--;
+ this._startIndex++;
+ this.onTrimEmitter.fire(1);
+ }
+ }
+ } else {
+ for (let i = 0; i < count; i++) {
+ this.set(start + i + offset, this.get(start + i));
+ }
+ }
+ }
+
+ /**
+ * Gets the cyclic index for the specified regular index. The cyclic index can then be used on the
+ * backing array to get the element associated with the regular index.
+ * @param index The regular index.
+ * @returns The cyclic index.
+ */
+ private _getCyclicIndex(index: number): number {
+ return (this._startIndex + index) % this._maxLength;
+ }
+}
diff --git a/node_modules/xterm/src/common/Clone.ts b/node_modules/xterm/src/common/Clone.ts
new file mode 100644
index 0000000..37821fe
--- /dev/null
+++ b/node_modules/xterm/src/common/Clone.ts
@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) 2016 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+/*
+ * A simple utility for cloning values
+ */
+export function clone<T>(val: T, depth: number = 5): T {
+ if (typeof val !== 'object') {
+ return val;
+ }
+
+ // If we're cloning an array, use an array as the base, otherwise use an object
+ const clonedObject: any = Array.isArray(val) ? [] : {};
+
+ for (const key in val) {
+ // Recursively clone eack item unless we're at the maximum depth
+ clonedObject[key] = depth <= 1 ? val[key] : (val[key] && clone(val[key], depth - 1));
+ }
+
+ return clonedObject as T;
+}
diff --git a/node_modules/xterm/src/common/CoreTerminal.ts b/node_modules/xterm/src/common/CoreTerminal.ts
new file mode 100644
index 0000000..12b374c
--- /dev/null
+++ b/node_modules/xterm/src/common/CoreTerminal.ts
@@ -0,0 +1,297 @@
+/**
+ * Copyright (c) 2014-2020 The xterm.js authors. All rights reserved.
+ * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
+ * @license MIT
+ *
+ * Originally forked from (with the author's permission):
+ * Fabrice Bellard's javascript vt100 for jslinux:
+ * http://bellard.org/jslinux/
+ * Copyright (c) 2011 Fabrice Bellard
+ * The original design remains. The terminal itself
+ * has been extended to include xterm CSI codes, among
+ * other features.
+ *
+ * Terminal Emulation References:
+ * http://vt100.net/
+ * http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt
+ * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
+ * http://invisible-island.net/vttest/
+ * http://www.inwap.com/pdp10/ansicode.txt
+ * http://linux.die.net/man/4/console_codes
+ * http://linux.die.net/man/7/urxvt
+ */
+
+import { Disposable } from 'common/Lifecycle';
+import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, IDirtyRowService, LogLevelEnum, ITerminalOptions } from 'common/services/Services';
+import { InstantiationService } from 'common/services/InstantiationService';
+import { LogService } from 'common/services/LogService';
+import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService';
+import { OptionsService } from 'common/services/OptionsService';
+import { IDisposable, IBufferLine, IAttributeData, ICoreTerminal, IKeyboardEvent, IScrollEvent, ScrollSource, ITerminalOptions as IPublicTerminalOptions } from 'common/Types';
+import { CoreService } from 'common/services/CoreService';
+import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter';
+import { CoreMouseService } from 'common/services/CoreMouseService';
+import { DirtyRowService } from 'common/services/DirtyRowService';
+import { UnicodeService } from 'common/services/UnicodeService';
+import { CharsetService } from 'common/services/CharsetService';
+import { updateWindowsModeWrappedState } from 'common/WindowsMode';
+import { IFunctionIdentifier, IParams } from 'common/parser/Types';
+import { IBufferSet } from 'common/buffer/Types';
+import { InputHandler } from 'common/InputHandler';
+import { WriteBuffer } from 'common/input/WriteBuffer';
+
+// Only trigger this warning a single time per session
+let hasWriteSyncWarnHappened = false;
+
+export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
+ protected readonly _instantiationService: IInstantiationService;
+ protected readonly _bufferService: IBufferService;
+ protected readonly _logService: ILogService;
+ protected readonly _charsetService: ICharsetService;
+ protected readonly _dirtyRowService: IDirtyRowService;
+
+ public readonly coreMouseService: ICoreMouseService;
+ public readonly coreService: ICoreService;
+ public readonly unicodeService: IUnicodeService;
+ public readonly optionsService: IOptionsService;
+
+ protected _inputHandler: InputHandler;
+ private _writeBuffer: WriteBuffer;
+ private _windowsMode: IDisposable | undefined;
+
+ private _onBinary = new EventEmitter<string>();
+ public get onBinary(): IEvent<string> { return this._onBinary.event; }
+ private _onData = new EventEmitter<string>();
+ public get onData(): IEvent<string> { return this._onData.event; }
+ protected _onLineFeed = new EventEmitter<void>();
+ public get onLineFeed(): IEvent<void> { return this._onLineFeed.event; }
+ 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>();
+ /**
+ * Internally we track the source of the scroll but this is meaningless outside the library so
+ * it's filtered out.
+ */
+ protected _onScrollApi?: EventEmitter<number, void>;
+ public get onScroll(): IEvent<number, void> {
+ if (!this._onScrollApi) {
+ this._onScrollApi = new EventEmitter<number, void>();
+ this.register(this._onScroll.event(ev => {
+ this._onScrollApi?.fire(ev.position);
+ }));
+ }
+ return this._onScrollApi.event;
+ }
+
+ public get cols(): number { return this._bufferService.cols; }
+ public get rows(): number { return this._bufferService.rows; }
+ public get buffers(): IBufferSet { return this._bufferService.buffers; }
+ public get options(): ITerminalOptions { return this.optionsService.options; }
+ public set options(options: ITerminalOptions) {
+ for (const key in options) {
+ this.optionsService.options[key] = options[key];
+ }
+ }
+
+ constructor(
+ options: Partial<ITerminalOptions>
+ ) {
+ super();
+
+ // Setup and initialize services
+ this._instantiationService = new InstantiationService();
+ this.optionsService = new OptionsService(options);
+ this._instantiationService.setService(IOptionsService, this.optionsService);
+ this._bufferService = this.register(this._instantiationService.createInstance(BufferService));
+ this._instantiationService.setService(IBufferService, this._bufferService);
+ this._logService = this._instantiationService.createInstance(LogService);
+ this._instantiationService.setService(ILogService, this._logService);
+ this.coreService = this.register(this._instantiationService.createInstance(CoreService, () => this.scrollToBottom()));
+ this._instantiationService.setService(ICoreService, this.coreService);
+ this.coreMouseService = this._instantiationService.createInstance(CoreMouseService);
+ this._instantiationService.setService(ICoreMouseService, this.coreMouseService);
+ this._dirtyRowService = this._instantiationService.createInstance(DirtyRowService);
+ this._instantiationService.setService(IDirtyRowService, this._dirtyRowService);
+ this.unicodeService = this._instantiationService.createInstance(UnicodeService);
+ this._instantiationService.setService(IUnicodeService, this.unicodeService);
+ this._charsetService = this._instantiationService.createInstance(CharsetService);
+ this._instantiationService.setService(ICharsetService, this._charsetService);
+
+ // Register input handler and handle/forward events
+ this._inputHandler = new InputHandler(this._bufferService, this._charsetService, this.coreService, this._dirtyRowService, this._logService, this.optionsService, this.coreMouseService, this.unicodeService);
+ this.register(forwardEvent(this._inputHandler.onLineFeed, this._onLineFeed));
+ this.register(this._inputHandler);
+
+ // Setup listeners
+ this.register(forwardEvent(this._bufferService.onResize, this._onResize));
+ this.register(forwardEvent(this.coreService.onData, this._onData));
+ this.register(forwardEvent(this.coreService.onBinary, this._onBinary));
+ this.register(this.optionsService.onOptionChange(key => this._updateOptions(key)));
+ this.register(this._bufferService.onScroll(event => {
+ this._onScroll.fire({ position: this._bufferService.buffer.ydisp, source: ScrollSource.TERMINAL });
+ this._dirtyRowService.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom);
+ }));
+ this.register(this._inputHandler.onScroll(event => {
+ this._onScroll.fire({ position: this._bufferService.buffer.ydisp, source: ScrollSource.TERMINAL });
+ this._dirtyRowService.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom);
+ }));
+
+ // Setup WriteBuffer
+ this._writeBuffer = new WriteBuffer((data, promiseResult) => this._inputHandler.parse(data, promiseResult));
+ }
+
+ public dispose(): void {
+ if (this._isDisposed) {
+ return;
+ }
+ super.dispose();
+ this._windowsMode?.dispose();
+ this._windowsMode = undefined;
+ }
+
+ public write(data: string | Uint8Array, callback?: () => void): void {
+ this._writeBuffer.write(data, callback);
+ }
+
+ /**
+ * Write data to terminal synchonously.
+ *
+ * This method is unreliable with async parser handlers, thus should not
+ * be used anymore. If you need blocking semantics on data input consider
+ * `write` with a callback instead.
+ *
+ * @deprecated Unreliable, will be removed soon.
+ */
+ public writeSync(data: string | Uint8Array, maxSubsequentCalls?: number): void {
+ if (this._logService.logLevel <= LogLevelEnum.WARN && !hasWriteSyncWarnHappened) {
+ this._logService.warn('writeSync is unreliable and will be removed soon.');
+ hasWriteSyncWarnHappened = true;
+ }
+ this._writeBuffer.writeSync(data, maxSubsequentCalls);
+ }
+
+ public resize(x: number, y: number): void {
+ if (isNaN(x) || isNaN(y)) {
+ return;
+ }
+
+ x = Math.max(x, MINIMUM_COLS);
+ y = Math.max(y, MINIMUM_ROWS);
+
+ this._bufferService.resize(x, y);
+ }
+
+ /**
+ * Scroll the terminal down 1 row, creating a blank line.
+ * @param isWrapped Whether the new line is wrapped from the previous line.
+ */
+ public scroll(eraseAttr: IAttributeData, isWrapped: boolean = false): void {
+ this._bufferService.scroll(eraseAttr, isWrapped);
+ }
+
+ /**
+ * Scroll the display of the terminal
+ * @param disp The number of lines to scroll down (negative scroll up).
+ * @param suppressScrollEvent Don't emit the scroll event as scrollLines. This is used
+ * to avoid unwanted events being handled by the viewport when the event was triggered from the
+ * viewport originally.
+ */
+ public scrollLines(disp: number, suppressScrollEvent?: boolean, source?: ScrollSource): void {
+ this._bufferService.scrollLines(disp, suppressScrollEvent, source);
+ }
+
+ /**
+ * Scroll the display of the terminal by a number of pages.
+ * @param pageCount The number of pages to scroll (negative scrolls up).
+ */
+ public scrollPages(pageCount: number): void {
+ this._bufferService.scrollPages(pageCount);
+ }
+
+ /**
+ * Scrolls the display of the terminal to the top.
+ */
+ public scrollToTop(): void {
+ this._bufferService.scrollToTop();
+ }
+
+ /**
+ * Scrolls the display of the terminal to the bottom.
+ */
+ public scrollToBottom(): void {
+ this._bufferService.scrollToBottom();
+ }
+
+ public scrollToLine(line: number): void {
+ this._bufferService.scrollToLine(line);
+ }
+
+ /** Add handler for ESC escape sequence. See xterm.d.ts for details. */
+ public registerEscHandler(id: IFunctionIdentifier, callback: () => boolean | Promise<boolean>): IDisposable {
+ return this._inputHandler.registerEscHandler(id, callback);
+ }
+
+ /** Add handler for DCS escape sequence. See xterm.d.ts for details. */
+ public registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean | Promise<boolean>): IDisposable {
+ return this._inputHandler.registerDcsHandler(id, callback);
+ }
+
+ /** Add handler for CSI escape sequence. See xterm.d.ts for details. */
+ public registerCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean | Promise<boolean>): IDisposable {
+ return this._inputHandler.registerCsiHandler(id, callback);
+ }
+
+ /** Add handler for OSC escape sequence. See xterm.d.ts for details. */
+ public registerOscHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable {
+ return this._inputHandler.registerOscHandler(ident, callback);
+ }
+
+ protected _setup(): void {
+ if (this.optionsService.rawOptions.windowsMode) {
+ this._enableWindowsMode();
+ }
+ }
+
+ public reset(): void {
+ this._inputHandler.reset();
+ this._bufferService.reset();
+ this._charsetService.reset();
+ this.coreService.reset();
+ this.coreMouseService.reset();
+ }
+
+ protected _updateOptions(key: string): void {
+ // TODO: These listeners should be owned by individual components
+ switch (key) {
+ case 'scrollback':
+ this.buffers.resize(this.cols, this.rows);
+ break;
+ case 'windowsMode':
+ if (this.optionsService.rawOptions.windowsMode) {
+ this._enableWindowsMode();
+ } else {
+ this._windowsMode?.dispose();
+ this._windowsMode = undefined;
+ }
+ break;
+ }
+ }
+
+ protected _enableWindowsMode(): void {
+ if (!this._windowsMode) {
+ const disposables: IDisposable[] = [];
+ disposables.push(this.onLineFeed(updateWindowsModeWrappedState.bind(null, this._bufferService)));
+ disposables.push(this.registerCsiHandler({ final: 'H' }, () => {
+ updateWindowsModeWrappedState(this._bufferService);
+ return false;
+ }));
+ this._windowsMode = {
+ dispose: () => {
+ for (const d of disposables) {
+ d.dispose();
+ }
+ }
+ };
+ }
+ }
+}
diff --git a/node_modules/xterm/src/common/EventEmitter.ts b/node_modules/xterm/src/common/EventEmitter.ts
new file mode 100644
index 0000000..4684809
--- /dev/null
+++ b/node_modules/xterm/src/common/EventEmitter.ts
@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IDisposable } from 'common/Types';
+
+interface IListener<T, U = void> {
+ (arg1: T, arg2: U): void;
+}
+
+export interface IEvent<T, U = void> {
+ (listener: (arg1: T, arg2: U) => any): IDisposable;
+}
+
+export interface IEventEmitter<T, U = void> {
+ event: IEvent<T, U>;
+ fire(arg1: T, arg2: U): void;
+ dispose(): void;
+}
+
+export class EventEmitter<T, U = void> implements IEventEmitter<T, U> {
+ private _listeners: IListener<T, U>[] = [];
+ private _event?: IEvent<T, U>;
+ private _disposed: boolean = false;
+
+ public get event(): IEvent<T, U> {
+ if (!this._event) {
+ this._event = (listener: (arg1: T, arg2: U) => any) => {
+ this._listeners.push(listener);
+ const disposable = {
+ dispose: () => {
+ if (!this._disposed) {
+ for (let i = 0; i < this._listeners.length; i++) {
+ if (this._listeners[i] === listener) {
+ this._listeners.splice(i, 1);
+ return;
+ }
+ }
+ }
+ }
+ };
+ return disposable;
+ };
+ }
+ return this._event;
+ }
+
+ public fire(arg1: T, arg2: U): void {
+ const queue: IListener<T, U>[] = [];
+ for (let i = 0; i < this._listeners.length; i++) {
+ queue.push(this._listeners[i]);
+ }
+ for (let i = 0; i < queue.length; i++) {
+ queue[i].call(undefined, arg1, arg2);
+ }
+ }
+
+ public dispose(): void {
+ if (this._listeners) {
+ this._listeners.length = 0;
+ }
+ this._disposed = true;
+ }
+}
+
+export function forwardEvent<T>(from: IEvent<T>, to: IEventEmitter<T>): IDisposable {
+ return from(e => to.fire(e));
+}
diff --git a/node_modules/xterm/src/common/InputHandler.ts b/node_modules/xterm/src/common/InputHandler.ts
new file mode 100644
index 0000000..68660f8
--- /dev/null
+++ b/node_modules/xterm/src/common/InputHandler.ts
@@ -0,0 +1,3229 @@
+/**
+ * Copyright (c) 2014 The xterm.js authors. All rights reserved.
+ * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
+ * @license MIT
+ */
+
+import { IInputHandler, IAttributeData, IDisposable, IWindowOptions, IColorEvent, IParseStack, ColorIndex, ColorRequestType } from 'common/Types';
+import { C0, C1 } from 'common/data/EscapeSequences';
+import { CHARSETS, DEFAULT_CHARSET } from 'common/data/Charsets';
+import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser';
+import { Disposable } from 'common/Lifecycle';
+import { concat } from 'common/TypedArrayUtils';
+import { StringToUtf32, stringFromCodePoint, utf32ToString, Utf8ToUtf32 } from 'common/input/TextDecoder';
+import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
+import { EventEmitter, IEvent } from 'common/EventEmitter';
+import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IFunctionIdentifier } from 'common/parser/Types';
+import { NULL_CELL_CODE, NULL_CELL_WIDTH, Attributes, FgFlags, BgFlags, Content, UnderlineStyle } from 'common/buffer/Constants';
+import { CellData } from 'common/buffer/CellData';
+import { AttributeData } from 'common/buffer/AttributeData';
+import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, LogLevelEnum } from 'common/services/Services';
+import { OscHandler } from 'common/parser/OscParser';
+import { DcsHandler } from 'common/parser/DcsParser';
+import { IBuffer } from 'common/buffer/Types';
+import { parseColor } from 'common/input/XParseColor';
+
+/**
+ * Map collect to glevel. Used in `selectCharset`.
+ */
+const GLEVEL: { [key: string]: number } = { '(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2 };
+
+/**
+ * VT commands done by the parser - FIXME: move this to the parser?
+ */
+// @vt: #Y ESC CSI "Control Sequence Introducer" "ESC [" "Start of a CSI sequence."
+// @vt: #Y ESC OSC "Operating System Command" "ESC ]" "Start of an OSC sequence."
+// @vt: #Y ESC DCS "Device Control String" "ESC P" "Start of a DCS sequence."
+// @vt: #Y ESC ST "String Terminator" "ESC \" "Terminator used for string type sequences."
+// @vt: #Y ESC PM "Privacy Message" "ESC ^" "Start of a privacy message."
+// @vt: #Y ESC APC "Application Program Command" "ESC _" "Start of an APC sequence."
+// @vt: #Y C1 CSI "Control Sequence Introducer" "\x9B" "Start of a CSI sequence."
+// @vt: #Y C1 OSC "Operating System Command" "\x9D" "Start of an OSC sequence."
+// @vt: #Y C1 DCS "Device Control String" "\x90" "Start of a DCS sequence."
+// @vt: #Y C1 ST "String Terminator" "\x9C" "Terminator used for string type sequences."
+// @vt: #Y C1 PM "Privacy Message" "\x9E" "Start of a privacy message."
+// @vt: #Y C1 APC "Application Program Command" "\x9F" "Start of an APC sequence."
+// @vt: #Y C0 NUL "Null" "\0, \x00" "NUL is ignored."
+// @vt: #Y C0 ESC "Escape" "\e, \x1B" "Start of a sequence. Cancels any other sequence."
+
+/**
+ * Document common VT features here that are currently unsupported
+ */
+// @vt: #N DCS SIXEL "SIXEL Graphics" "DCS Ps ; Ps ; Ps ; q Pt ST" "Draw SIXEL image starting at cursor position."
+// @vt: #N OSC 1 "Set Icon Name" "OSC 1 ; Pt BEL" "Set icon name."
+
+/**
+ * Max length of the UTF32 input buffer. Real memory consumption is 4 times higher.
+ */
+const MAX_PARSEBUFFER_LENGTH = 131072;
+
+/**
+ * Limit length of title and icon name stacks.
+ */
+const STACK_LIMIT = 10;
+
+// map params to window option
+function paramToWindowOption(n: number, opts: IWindowOptions): boolean {
+ if (n > 24) {
+ return opts.setWinLines || false;
+ }
+ switch (n) {
+ case 1: return !!opts.restoreWin;
+ case 2: return !!opts.minimizeWin;
+ case 3: return !!opts.setWinPosition;
+ case 4: return !!opts.setWinSizePixels;
+ case 5: return !!opts.raiseWin;
+ case 6: return !!opts.lowerWin;
+ case 7: return !!opts.refreshWin;
+ case 8: return !!opts.setWinSizeChars;
+ case 9: return !!opts.maximizeWin;
+ case 10: return !!opts.fullscreenWin;
+ case 11: return !!opts.getWinState;
+ case 13: return !!opts.getWinPosition;
+ case 14: return !!opts.getWinSizePixels;
+ case 15: return !!opts.getScreenSizePixels;
+ case 16: return !!opts.getCellSizePixels;
+ case 18: return !!opts.getWinSizeChars;
+ case 19: return !!opts.getScreenSizeChars;
+ case 20: return !!opts.getIconTitle;
+ case 21: return !!opts.getWinTitle;
+ case 22: return !!opts.pushTitle;
+ case 23: return !!opts.popTitle;
+ case 24: return !!opts.setWinLines;
+ }
+ return false;
+}
+
+export enum WindowsOptionsReportType {
+ GET_WIN_SIZE_PIXELS = 0,
+ GET_CELL_SIZE_PIXELS = 1
+}
+
+// create a warning log if an async handler takes longer than the limit (in ms)
+const SLOW_ASYNC_LIMIT = 5000;
+
+/**
+ * DCS subparser implementations
+ */
+
+/**
+ * DCS $ q Pt ST
+ * DECRQSS (https://vt100.net/docs/vt510-rm/DECRQSS.html)
+ * Request Status String (DECRQSS), VT420 and up.
+ * Response: DECRPSS (https://vt100.net/docs/vt510-rm/DECRPSS.html)
+ *
+ * @vt: #P[See limited support below.] DCS DECRQSS "Request Selection or Setting" "DCS $ q Pt ST" "Request several terminal settings."
+ * Response is in the form `ESC P 1 $ r Pt ST` for valid requests, where `Pt` contains the corresponding CSI string,
+ * `ESC P 0 ST` for invalid requests.
+ *
+ * Supported requests and responses:
+ *
+ * | Type | Request | Response (`Pt`) |
+ * | -------------------------------- | ----------------- | ----------------------------------------------------- |
+ * | Graphic Rendition (SGR) | `DCS $ q m ST` | always reporting `0m` (currently broken) |
+ * | Top and Bottom Margins (DECSTBM) | `DCS $ q r ST` | `Ps ; Ps r` |
+ * | Cursor Style (DECSCUSR) | `DCS $ q SP q ST` | `Ps SP q` |
+ * | Protection Attribute (DECSCA) | `DCS $ q " q ST` | always reporting `0 " q` (DECSCA is unsupported) |
+ * | Conformance Level (DECSCL) | `DCS $ q " p ST` | always reporting `61 ; 1 " p` (DECSCL is unsupported) |
+ *
+ *
+ * TODO:
+ * - fix SGR report
+ * - either implement DECSCA or remove the report
+ * - either check which conformance is better suited or remove the report completely
+ * --> we are currently a mixture of all up to VT400 but dont follow anyone strictly
+ */
+class DECRQSS implements IDcsHandler {
+ private _data: Uint32Array = new Uint32Array(0);
+
+ constructor(
+ private _bufferService: IBufferService,
+ private _coreService: ICoreService,
+ private _logService: ILogService,
+ private _optionsService: IOptionsService
+ ) { }
+
+ public hook(params: IParams): void {
+ this._data = new Uint32Array(0);
+ }
+
+ public put(data: Uint32Array, start: number, end: number): void {
+ this._data = concat(this._data, data.subarray(start, end));
+ }
+
+ public unhook(success: boolean): boolean {
+ if (!success) {
+ this._data = new Uint32Array(0);
+ return true;
+ }
+ const data = utf32ToString(this._data);
+ this._data = new Uint32Array(0);
+ switch (data) {
+ // valid: DCS 1 $ r Pt ST (xterm)
+ case '"q': // DECSCA
+ this._coreService.triggerDataEvent(`${C0.ESC}P1$r0"q${C0.ESC}\\`);
+ break;
+ case '"p': // DECSCL
+ this._coreService.triggerDataEvent(`${C0.ESC}P1$r61;1"p${C0.ESC}\\`);
+ break;
+ case 'r': // DECSTBM
+ const pt = '' + (this._bufferService.buffer.scrollTop + 1) +
+ ';' + (this._bufferService.buffer.scrollBottom + 1) + 'r';
+ this._coreService.triggerDataEvent(`${C0.ESC}P1$r${pt}${C0.ESC}\\`);
+ break;
+ case 'm': // SGR
+ // TODO: report real settings instead of 0m
+ this._coreService.triggerDataEvent(`${C0.ESC}P1$r0m${C0.ESC}\\`);
+ break;
+ case ' q': // DECSCUSR
+ const STYLES: { [key: string]: number } = { 'block': 2, 'underline': 4, 'bar': 6 };
+ let style = STYLES[this._optionsService.rawOptions.cursorStyle];
+ style -= this._optionsService.rawOptions.cursorBlink ? 1 : 0;
+ this._coreService.triggerDataEvent(`${C0.ESC}P1$r${style} q${C0.ESC}\\`);
+ break;
+ default:
+ // invalid: DCS 0 $ r Pt ST (xterm)
+ this._logService.debug('Unknown DCS $q %s', data);
+ this._coreService.triggerDataEvent(`${C0.ESC}P0$r${C0.ESC}\\`);
+ }
+ return true;
+ }
+}
+
+/**
+ * DCS Ps; Ps| Pt ST
+ * DECUDK (https://vt100.net/docs/vt510-rm/DECUDK.html)
+ * not supported
+ *
+ * @vt: #N DCS DECUDK "User Defined Keys" "DCS Ps ; Ps | Pt ST" "Definitions for user-defined keys."
+ */
+
+/**
+ * DCS + q Pt ST (xterm)
+ * Request Terminfo String
+ * not implemented
+ *
+ * @vt: #N DCS XTGETTCAP "Request Terminfo String" "DCS + q Pt ST" "Request Terminfo String."
+ */
+
+/**
+ * DCS + p Pt ST (xterm)
+ * Set Terminfo Data
+ * not supported
+ *
+ * @vt: #N DCS XTSETTCAP "Set Terminfo Data" "DCS + p Pt ST" "Set Terminfo Data."
+ */
+
+
+
+/**
+ * The terminal's standard implementation of IInputHandler, this handles all
+ * input from the Parser.
+ *
+ * Refer to http://invisible-island.net/xterm/ctlseqs/ctlseqs.html to understand
+ * each function's header comment.
+ */
+export class InputHandler extends Disposable implements IInputHandler {
+ private _parseBuffer: Uint32Array = new Uint32Array(4096);
+ private _stringDecoder: StringToUtf32 = new StringToUtf32();
+ private _utf8Decoder: Utf8ToUtf32 = new Utf8ToUtf32();
+ private _workCell: CellData = new CellData();
+ private _windowTitle = '';
+ private _iconName = '';
+ protected _windowTitleStack: string[] = [];
+ protected _iconNameStack: string[] = [];
+
+ private _curAttrData: IAttributeData = DEFAULT_ATTR_DATA.clone();
+ private _eraseAttrDataInternal: IAttributeData = DEFAULT_ATTR_DATA.clone();
+
+ private _activeBuffer: IBuffer;
+
+ private _onRequestBell = new EventEmitter<void>();
+ public get onRequestBell(): IEvent<void> { return this._onRequestBell.event; }
+ private _onRequestRefreshRows = new EventEmitter<number, number>();
+ public get onRequestRefreshRows(): IEvent<number, number> { return this._onRequestRefreshRows.event; }
+ private _onRequestReset = new EventEmitter<void>();
+ public get onRequestReset(): IEvent<void> { return this._onRequestReset.event; }
+ private _onRequestSendFocus = new EventEmitter<void>();
+ public get onRequestSendFocus(): IEvent<void> { return this._onRequestSendFocus.event; }
+ private _onRequestSyncScrollBar = new EventEmitter<void>();
+ public get onRequestSyncScrollBar(): IEvent<void> { return this._onRequestSyncScrollBar.event; }
+ private _onRequestWindowsOptionsReport = new EventEmitter<WindowsOptionsReportType>();
+ public get onRequestWindowsOptionsReport(): IEvent<WindowsOptionsReportType> { return this._onRequestWindowsOptionsReport.event; }
+
+ private _onA11yChar = new EventEmitter<string>();
+ public get onA11yChar(): IEvent<string> { return this._onA11yChar.event; }
+ private _onA11yTab = new EventEmitter<number>();
+ public get onA11yTab(): IEvent<number> { return this._onA11yTab.event; }
+ private _onCursorMove = new EventEmitter<void>();
+ public get onCursorMove(): IEvent<void> { return this._onCursorMove.event; }
+ private _onLineFeed = new EventEmitter<void>();
+ public get onLineFeed(): IEvent<void> { return this._onLineFeed.event; }
+ private _onScroll = new EventEmitter<number>();
+ public get onScroll(): IEvent<number> { return this._onScroll.event; }
+ private _onTitleChange = new EventEmitter<string>();
+ public get onTitleChange(): IEvent<string> { return this._onTitleChange.event; }
+ private _onColor = new EventEmitter<IColorEvent>();
+ public get onColor(): IEvent<IColorEvent> { return this._onColor.event; }
+
+ private _parseStack: IParseStack = {
+ paused: false,
+ cursorStartX: 0,
+ cursorStartY: 0,
+ decodedLength: 0,
+ position: 0
+ };
+
+ constructor(
+ private readonly _bufferService: IBufferService,
+ private readonly _charsetService: ICharsetService,
+ private readonly _coreService: ICoreService,
+ private readonly _dirtyRowService: IDirtyRowService,
+ private readonly _logService: ILogService,
+ private readonly _optionsService: IOptionsService,
+ private readonly _coreMouseService: ICoreMouseService,
+ private readonly _unicodeService: IUnicodeService,
+ private readonly _parser: IEscapeSequenceParser = new EscapeSequenceParser()
+ ) {
+ super();
+ this.register(this._parser);
+
+ // Track properties used in performance critical code manually to avoid using slow getters
+ this._activeBuffer = this._bufferService.buffer;
+ this.register(this._bufferService.buffers.onBufferActivate(e => this._activeBuffer = e.activeBuffer));
+
+ /**
+ * custom fallback handlers
+ */
+ this._parser.setCsiHandlerFallback((ident, params) => {
+ this._logService.debug('Unknown CSI code: ', { identifier: this._parser.identToString(ident), params: params.toArray() });
+ });
+ this._parser.setEscHandlerFallback(ident => {
+ this._logService.debug('Unknown ESC code: ', { identifier: this._parser.identToString(ident) });
+ });
+ this._parser.setExecuteHandlerFallback(code => {
+ this._logService.debug('Unknown EXECUTE code: ', { code });
+ });
+ this._parser.setOscHandlerFallback((identifier, action, data) => {
+ this._logService.debug('Unknown OSC code: ', { identifier, action, data });
+ });
+ this._parser.setDcsHandlerFallback((ident, action, payload) => {
+ if (action === 'HOOK') {
+ payload = payload.toArray();
+ }
+ this._logService.debug('Unknown DCS code: ', { identifier: this._parser.identToString(ident), action, payload });
+ });
+
+ /**
+ * print handler
+ */
+ this._parser.setPrintHandler((data, start, end) => this.print(data, start, end));
+
+ /**
+ * CSI handler
+ */
+ this._parser.registerCsiHandler({ final: '@' }, params => this.insertChars(params));
+ this._parser.registerCsiHandler({ intermediates: ' ', final: '@' }, params => this.scrollLeft(params));
+ this._parser.registerCsiHandler({ final: 'A' }, params => this.cursorUp(params));
+ this._parser.registerCsiHandler({ intermediates: ' ', final: 'A' }, params => this.scrollRight(params));
+ this._parser.registerCsiHandler({ final: 'B' }, params => this.cursorDown(params));
+ this._parser.registerCsiHandler({ final: 'C' }, params => this.cursorForward(params));
+ this._parser.registerCsiHandler({ final: 'D' }, params => this.cursorBackward(params));
+ this._parser.registerCsiHandler({ final: 'E' }, params => this.cursorNextLine(params));
+ this._parser.registerCsiHandler({ final: 'F' }, params => this.cursorPrecedingLine(params));
+ this._parser.registerCsiHandler({ final: 'G' }, params => this.cursorCharAbsolute(params));
+ this._parser.registerCsiHandler({ final: 'H' }, params => this.cursorPosition(params));
+ this._parser.registerCsiHandler({ final: 'I' }, params => this.cursorForwardTab(params));
+ this._parser.registerCsiHandler({ final: 'J' }, params => this.eraseInDisplay(params));
+ this._parser.registerCsiHandler({ prefix: '?', final: 'J' }, params => this.eraseInDisplay(params));
+ this._parser.registerCsiHandler({ final: 'K' }, params => this.eraseInLine(params));
+ this._parser.registerCsiHandler({ prefix: '?', final: 'K' }, params => this.eraseInLine(params));
+ this._parser.registerCsiHandler({ final: 'L' }, params => this.insertLines(params));
+ this._parser.registerCsiHandler({ final: 'M' }, params => this.deleteLines(params));
+ this._parser.registerCsiHandler({ final: 'P' }, params => this.deleteChars(params));
+ this._parser.registerCsiHandler({ final: 'S' }, params => this.scrollUp(params));
+ this._parser.registerCsiHandler({ final: 'T' }, params => this.scrollDown(params));
+ this._parser.registerCsiHandler({ final: 'X' }, params => this.eraseChars(params));
+ this._parser.registerCsiHandler({ final: 'Z' }, params => this.cursorBackwardTab(params));
+ this._parser.registerCsiHandler({ final: '`' }, params => this.charPosAbsolute(params));
+ this._parser.registerCsiHandler({ final: 'a' }, params => this.hPositionRelative(params));
+ this._parser.registerCsiHandler({ final: 'b' }, params => this.repeatPrecedingCharacter(params));
+ this._parser.registerCsiHandler({ final: 'c' }, params => this.sendDeviceAttributesPrimary(params));
+ this._parser.registerCsiHandler({ prefix: '>', final: 'c' }, params => this.sendDeviceAttributesSecondary(params));
+ this._parser.registerCsiHandler({ final: 'd' }, params => this.linePosAbsolute(params));
+ this._parser.registerCsiHandler({ final: 'e' }, params => this.vPositionRelative(params));
+ this._parser.registerCsiHandler({ final: 'f' }, params => this.hVPosition(params));
+ this._parser.registerCsiHandler({ final: 'g' }, params => this.tabClear(params));
+ this._parser.registerCsiHandler({ final: 'h' }, params => this.setMode(params));
+ this._parser.registerCsiHandler({ prefix: '?', final: 'h' }, params => this.setModePrivate(params));
+ this._parser.registerCsiHandler({ final: 'l' }, params => this.resetMode(params));
+ this._parser.registerCsiHandler({ prefix: '?', final: 'l' }, params => this.resetModePrivate(params));
+ this._parser.registerCsiHandler({ final: 'm' }, params => this.charAttributes(params));
+ this._parser.registerCsiHandler({ final: 'n' }, params => this.deviceStatus(params));
+ this._parser.registerCsiHandler({ prefix: '?', final: 'n' }, params => this.deviceStatusPrivate(params));
+ this._parser.registerCsiHandler({ intermediates: '!', final: 'p' }, params => this.softReset(params));
+ this._parser.registerCsiHandler({ intermediates: ' ', final: 'q' }, params => this.setCursorStyle(params));
+ this._parser.registerCsiHandler({ final: 'r' }, params => this.setScrollRegion(params));
+ this._parser.registerCsiHandler({ final: 's' }, params => this.saveCursor(params));
+ this._parser.registerCsiHandler({ final: 't' }, params => this.windowOptions(params));
+ this._parser.registerCsiHandler({ final: 'u' }, params => this.restoreCursor(params));
+ this._parser.registerCsiHandler({ intermediates: '\'', final: '}' }, params => this.insertColumns(params));
+ this._parser.registerCsiHandler({ intermediates: '\'', final: '~' }, params => this.deleteColumns(params));
+
+ /**
+ * execute handler
+ */
+ this._parser.setExecuteHandler(C0.BEL, () => this.bell());
+ this._parser.setExecuteHandler(C0.LF, () => this.lineFeed());
+ this._parser.setExecuteHandler(C0.VT, () => this.lineFeed());
+ this._parser.setExecuteHandler(C0.FF, () => this.lineFeed());
+ this._parser.setExecuteHandler(C0.CR, () => this.carriageReturn());
+ this._parser.setExecuteHandler(C0.BS, () => this.backspace());
+ this._parser.setExecuteHandler(C0.HT, () => this.tab());
+ this._parser.setExecuteHandler(C0.SO, () => this.shiftOut());
+ this._parser.setExecuteHandler(C0.SI, () => this.shiftIn());
+ // FIXME: What do to with missing? Old code just added those to print.
+
+ this._parser.setExecuteHandler(C1.IND, () => this.index());
+ this._parser.setExecuteHandler(C1.NEL, () => this.nextLine());
+ this._parser.setExecuteHandler(C1.HTS, () => this.tabSet());
+
+ /**
+ * OSC handler
+ */
+ // 0 - icon name + title
+ this._parser.registerOscHandler(0, new OscHandler(data => { this.setTitle(data); this.setIconName(data); return true; }));
+ // 1 - icon name
+ this._parser.registerOscHandler(1, new OscHandler(data => this.setIconName(data)));
+ // 2 - title
+ this._parser.registerOscHandler(2, new OscHandler(data => this.setTitle(data)));
+ // 3 - set property X in the form "prop=value"
+ // 4 - Change Color Number
+ this._parser.registerOscHandler(4, new OscHandler(data => this.setOrReportIndexedColor(data)));
+ // 5 - Change Special Color Number
+ // 6 - Enable/disable Special Color Number c
+ // 7 - current directory? (not in xterm spec, see https://gitlab.com/gnachman/iterm2/issues/3939)
+ // 10 - Change VT100 text foreground color to Pt.
+ this._parser.registerOscHandler(10, new OscHandler(data => this.setOrReportFgColor(data)));
+ // 11 - Change VT100 text background color to Pt.
+ this._parser.registerOscHandler(11, new OscHandler(data => this.setOrReportBgColor(data)));
+ // 12 - Change text cursor color to Pt.
+ this._parser.registerOscHandler(12, new OscHandler(data => this.setOrReportCursorColor(data)));
+ // 13 - Change mouse foreground color to Pt.
+ // 14 - Change mouse background color to Pt.
+ // 15 - Change Tektronix foreground color to Pt.
+ // 16 - Change Tektronix background color to Pt.
+ // 17 - Change highlight background color to Pt.
+ // 18 - Change Tektronix cursor color to Pt.
+ // 19 - Change highlight foreground color to Pt.
+ // 46 - Change Log File to Pt.
+ // 50 - Set Font to Pt.
+ // 51 - reserved for Emacs shell.
+ // 52 - Manipulate Selection Data.
+ // 104 ; c - Reset Color Number c.
+ this._parser.registerOscHandler(104, new OscHandler(data => this.restoreIndexedColor(data)));
+ // 105 ; c - Reset Special Color Number c.
+ // 106 ; c; f - Enable/disable Special Color Number c.
+ // 110 - Reset VT100 text foreground color.
+ this._parser.registerOscHandler(110, new OscHandler(data => this.restoreFgColor(data)));
+ // 111 - Reset VT100 text background color.
+ this._parser.registerOscHandler(111, new OscHandler(data => this.restoreBgColor(data)));
+ // 112 - Reset text cursor color.
+ this._parser.registerOscHandler(112, new OscHandler(data => this.restoreCursorColor(data)));
+ // 113 - Reset mouse foreground color.
+ // 114 - Reset mouse background color.
+ // 115 - Reset Tektronix foreground color.
+ // 116 - Reset Tektronix background color.
+ // 117 - Reset highlight color.
+ // 118 - Reset Tektronix cursor color.
+ // 119 - Reset highlight foreground color.
+
+ /**
+ * ESC handlers
+ */
+ this._parser.registerEscHandler({ final: '7' }, () => this.saveCursor());
+ this._parser.registerEscHandler({ final: '8' }, () => this.restoreCursor());
+ this._parser.registerEscHandler({ final: 'D' }, () => this.index());
+ this._parser.registerEscHandler({ final: 'E' }, () => this.nextLine());
+ this._parser.registerEscHandler({ final: 'H' }, () => this.tabSet());
+ this._parser.registerEscHandler({ final: 'M' }, () => this.reverseIndex());
+ this._parser.registerEscHandler({ final: '=' }, () => this.keypadApplicationMode());
+ this._parser.registerEscHandler({ final: '>' }, () => this.keypadNumericMode());
+ this._parser.registerEscHandler({ final: 'c' }, () => this.fullReset());
+ this._parser.registerEscHandler({ final: 'n' }, () => this.setgLevel(2));
+ this._parser.registerEscHandler({ final: 'o' }, () => this.setgLevel(3));
+ this._parser.registerEscHandler({ final: '|' }, () => this.setgLevel(3));
+ this._parser.registerEscHandler({ final: '}' }, () => this.setgLevel(2));
+ this._parser.registerEscHandler({ final: '~' }, () => this.setgLevel(1));
+ this._parser.registerEscHandler({ intermediates: '%', final: '@' }, () => this.selectDefaultCharset());
+ this._parser.registerEscHandler({ intermediates: '%', final: 'G' }, () => this.selectDefaultCharset());
+ for (const flag in CHARSETS) {
+ this._parser.registerEscHandler({ intermediates: '(', final: flag }, () => this.selectCharset('(' + flag));
+ this._parser.registerEscHandler({ intermediates: ')', final: flag }, () => this.selectCharset(')' + flag));
+ this._parser.registerEscHandler({ intermediates: '*', final: flag }, () => this.selectCharset('*' + flag));
+ this._parser.registerEscHandler({ intermediates: '+', final: flag }, () => this.selectCharset('+' + flag));
+ this._parser.registerEscHandler({ intermediates: '-', final: flag }, () => this.selectCharset('-' + flag));
+ this._parser.registerEscHandler({ intermediates: '.', final: flag }, () => this.selectCharset('.' + flag));
+ this._parser.registerEscHandler({ intermediates: '/', final: flag }, () => this.selectCharset('/' + flag)); // TODO: supported?
+ }
+ this._parser.registerEscHandler({ intermediates: '#', final: '8' }, () => this.screenAlignmentPattern());
+
+ /**
+ * error handler
+ */
+ this._parser.setErrorHandler((state: IParsingState) => {
+ this._logService.error('Parsing error: ', state);
+ return state;
+ });
+
+ /**
+ * DCS handler
+ */
+ this._parser.registerDcsHandler({ intermediates: '$', final: 'q' }, new DECRQSS(this._bufferService, this._coreService, this._logService, this._optionsService));
+ }
+
+ public dispose(): void {
+ super.dispose();
+ }
+
+ /**
+ * Async parse support.
+ */
+ private _preserveStack(cursorStartX: number, cursorStartY: number, decodedLength: number, position: number): void {
+ this._parseStack.paused = true;
+ this._parseStack.cursorStartX = cursorStartX;
+ this._parseStack.cursorStartY = cursorStartY;
+ this._parseStack.decodedLength = decodedLength;
+ this._parseStack.position = position;
+ }
+
+ private _logSlowResolvingAsync(p: Promise<boolean>): void {
+ // log a limited warning about an async handler taking too long
+ if (this._logService.logLevel <= LogLevelEnum.WARN) {
+ Promise.race([p, new Promise((res, rej) => setTimeout(() => rej('#SLOW_TIMEOUT'), SLOW_ASYNC_LIMIT))])
+ .catch(err => {
+ if (err !== '#SLOW_TIMEOUT') {
+ throw err;
+ }
+ console.warn(`async parser handler taking longer than ${SLOW_ASYNC_LIMIT} ms`);
+ });
+ }
+ }
+
+ /**
+ * Parse call with async handler support.
+ *
+ * Whether the stack state got preserved for the next call, is indicated by the return value:
+ * - undefined (void):
+ * all handlers were sync, no stack save, continue normally with next chunk
+ * - Promise\<boolean\>:
+ * execution stopped at async handler, stack saved, continue with
+ * same chunk and the promise resolve value as `promiseResult` until the method returns `undefined`
+ *
+ * Note: This method should only be called by `Terminal.write` to ensure correct execution order and
+ * proper continuation of async parser handlers.
+ */
+ public parse(data: string | Uint8Array, promiseResult?: boolean): void | Promise<boolean> {
+ let result: void | Promise<boolean>;
+ let cursorStartX = this._activeBuffer.x;
+ let cursorStartY = this._activeBuffer.y;
+ let start = 0;
+ const wasPaused = this._parseStack.paused;
+
+ if (wasPaused) {
+ // assumption: _parseBuffer never mutates between async calls
+ if (result = this._parser.parse(this._parseBuffer, this._parseStack.decodedLength, promiseResult)) {
+ this._logSlowResolvingAsync(result);
+ return result;
+ }
+ cursorStartX = this._parseStack.cursorStartX;
+ cursorStartY = this._parseStack.cursorStartY;
+ this._parseStack.paused = false;
+ if (data.length > MAX_PARSEBUFFER_LENGTH) {
+ start = this._parseStack.position + MAX_PARSEBUFFER_LENGTH;
+ }
+ }
+
+ // Log debug data, the log level gate is to prevent extra work in this hot path
+ if (this._logService.logLevel <= LogLevelEnum.DEBUG) {
+ this._logService.debug(`parsing data${typeof data === 'string' ? ` "${data}"` : ''}`, typeof data === 'string'
+ ? data.split('').map(e => e.charCodeAt(0))
+ : data
+ );
+ }
+
+ // resize input buffer if needed
+ if (this._parseBuffer.length < data.length) {
+ if (this._parseBuffer.length < MAX_PARSEBUFFER_LENGTH) {
+ this._parseBuffer = new Uint32Array(Math.min(data.length, MAX_PARSEBUFFER_LENGTH));
+ }
+ }
+
+ // Clear the dirty row service so we know which lines changed as a result of parsing
+ // Important: do not clear between async calls, otherwise we lost pending update information.
+ if (!wasPaused) {
+ this._dirtyRowService.clearRange();
+ }
+
+ // process big data in smaller chunks
+ if (data.length > MAX_PARSEBUFFER_LENGTH) {
+ for (let i = start; i < data.length; i += MAX_PARSEBUFFER_LENGTH) {
+ const end = i + MAX_PARSEBUFFER_LENGTH < data.length ? i + MAX_PARSEBUFFER_LENGTH : data.length;
+ const len = (typeof data === 'string')
+ ? this._stringDecoder.decode(data.substring(i, end), this._parseBuffer)
+ : this._utf8Decoder.decode(data.subarray(i, end), this._parseBuffer);
+ if (result = this._parser.parse(this._parseBuffer, len)) {
+ this._preserveStack(cursorStartX, cursorStartY, len, i);
+ this._logSlowResolvingAsync(result);
+ return result;
+ }
+ }
+ } else {
+ if (!wasPaused) {
+ const len = (typeof data === 'string')
+ ? this._stringDecoder.decode(data, this._parseBuffer)
+ : this._utf8Decoder.decode(data, this._parseBuffer);
+ if (result = this._parser.parse(this._parseBuffer, len)) {
+ this._preserveStack(cursorStartX, cursorStartY, len, 0);
+ this._logSlowResolvingAsync(result);
+ return result;
+ }
+ }
+ }
+
+ if (this._activeBuffer.x !== cursorStartX || this._activeBuffer.y !== cursorStartY) {
+ this._onCursorMove.fire();
+ }
+
+ // Refresh any dirty rows accumulated as part of parsing
+ this._onRequestRefreshRows.fire(this._dirtyRowService.start, this._dirtyRowService.end);
+ }
+
+ public print(data: Uint32Array, start: number, end: number): void {
+ let code: number;
+ let chWidth: number;
+ const charset = this._charsetService.charset;
+ const screenReaderMode = this._optionsService.rawOptions.screenReaderMode;
+ const cols = this._bufferService.cols;
+ const wraparoundMode = this._coreService.decPrivateModes.wraparound;
+ const insertMode = this._coreService.modes.insertMode;
+ const curAttr = this._curAttrData;
+ let bufferRow = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!;
+
+ this._dirtyRowService.markDirty(this._activeBuffer.y);
+
+ // handle wide chars: reset start_cell-1 if we would overwrite the second cell of a wide char
+ if (this._activeBuffer.x && end - start > 0 && bufferRow.getWidth(this._activeBuffer.x - 1) === 2) {
+ bufferRow.setCellFromCodePoint(this._activeBuffer.x - 1, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended);
+ }
+
+ for (let pos = start; pos < end; ++pos) {
+ code = data[pos];
+
+ // calculate print space
+ // expensive call, therefore we save width in line buffer
+ chWidth = this._unicodeService.wcwidth(code);
+
+ // get charset replacement character
+ // charset is only defined for ASCII, therefore we only
+ // search for an replacement char if code < 127
+ if (code < 127 && charset) {
+ const ch = charset[String.fromCharCode(code)];
+ if (ch) {
+ code = ch.charCodeAt(0);
+ }
+ }
+
+ if (screenReaderMode) {
+ this._onA11yChar.fire(stringFromCodePoint(code));
+ }
+
+ // insert combining char at last cursor position
+ // this._activeBuffer.x should never be 0 for a combining char
+ // since they always follow a cell consuming char
+ // therefore we can test for this._activeBuffer.x to avoid overflow left
+ if (!chWidth && this._activeBuffer.x) {
+ if (!bufferRow.getWidth(this._activeBuffer.x - 1)) {
+ // found empty cell after fullwidth, need to go 2 cells back
+ // it is save to step 2 cells back here
+ // since an empty cell is only set by fullwidth chars
+ bufferRow.addCodepointToCell(this._activeBuffer.x - 2, code);
+ } else {
+ bufferRow.addCodepointToCell(this._activeBuffer.x - 1, code);
+ }
+ continue;
+ }
+
+ // goto next line if ch would overflow
+ // NOTE: To avoid costly width checks here,
+ // the terminal does not allow a cols < 2.
+ if (this._activeBuffer.x + chWidth - 1 >= cols) {
+ // autowrap - DECAWM
+ // automatically wraps to the beginning of the next line
+ if (wraparoundMode) {
+ // clear left over cells to the right
+ while (this._activeBuffer.x < cols) {
+ bufferRow.setCellFromCodePoint(this._activeBuffer.x++, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended);
+ }
+ this._activeBuffer.x = 0;
+ this._activeBuffer.y++;
+ if (this._activeBuffer.y === this._activeBuffer.scrollBottom + 1) {
+ this._activeBuffer.y--;
+ this._bufferService.scroll(this._eraseAttrData(), true);
+ } else {
+ if (this._activeBuffer.y >= this._bufferService.rows) {
+ this._activeBuffer.y = this._bufferService.rows - 1;
+ }
+ // The line already exists (eg. the initial viewport), mark it as a
+ // wrapped line
+ this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!.isWrapped = true;
+ }
+ // row changed, get it again
+ bufferRow = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!;
+ } else {
+ this._activeBuffer.x = cols - 1;
+ if (chWidth === 2) {
+ // FIXME: check for xterm behavior
+ // What to do here? We got a wide char that does not fit into last cell
+ continue;
+ }
+ }
+ }
+
+ // insert mode: move characters to right
+ if (insertMode) {
+ // right shift cells according to the width
+ bufferRow.insertCells(this._activeBuffer.x, chWidth, this._activeBuffer.getNullCell(curAttr), curAttr);
+ // test last cell - since the last cell has only room for
+ // a halfwidth char any fullwidth shifted there is lost
+ // and will be set to empty cell
+ if (bufferRow.getWidth(cols - 1) === 2) {
+ bufferRow.setCellFromCodePoint(cols - 1, NULL_CELL_CODE, NULL_CELL_WIDTH, curAttr.fg, curAttr.bg, curAttr.extended);
+ }
+ }
+
+ // write current char to buffer and advance cursor
+ bufferRow.setCellFromCodePoint(this._activeBuffer.x++, code, chWidth, curAttr.fg, curAttr.bg, curAttr.extended);
+
+ // fullwidth char - also set next cell to placeholder stub and advance cursor
+ // for graphemes bigger than fullwidth we can simply loop to zero
+ // we already made sure above, that this._activeBuffer.x + chWidth will not overflow right
+ if (chWidth > 0) {
+ while (--chWidth) {
+ // other than a regular empty cell a cell following a wide char has no width
+ bufferRow.setCellFromCodePoint(this._activeBuffer.x++, 0, 0, curAttr.fg, curAttr.bg, curAttr.extended);
+ }
+ }
+ }
+ // store last char in Parser.precedingCodepoint for REP to work correctly
+ // This needs to check whether:
+ // - fullwidth + surrogates: reset
+ // - combining: only base char gets carried on (bug in xterm?)
+ if (end - start > 0) {
+ bufferRow.loadCell(this._activeBuffer.x - 1, this._workCell);
+ if (this._workCell.getWidth() === 2 || this._workCell.getCode() > 0xFFFF) {
+ this._parser.precedingCodepoint = 0;
+ } else if (this._workCell.isCombined()) {
+ this._parser.precedingCodepoint = this._workCell.getChars().charCodeAt(0);
+ } else {
+ this._parser.precedingCodepoint = this._workCell.content;
+ }
+ }
+
+ // handle wide chars: reset cell to the right if it is second cell of a wide char
+ if (this._activeBuffer.x < cols && end - start > 0 && bufferRow.getWidth(this._activeBuffer.x) === 0 && !bufferRow.hasContent(this._activeBuffer.x)) {
+ bufferRow.setCellFromCodePoint(this._activeBuffer.x, 0, 1, curAttr.fg, curAttr.bg, curAttr.extended);
+ }
+
+ this._dirtyRowService.markDirty(this._activeBuffer.y);
+ }
+
+ /**
+ * Forward registerCsiHandler from parser.
+ */
+ public registerCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean | Promise<boolean>): IDisposable {
+ if (id.final === 't' && !id.prefix && !id.intermediates) {
+ // security: always check whether window option is allowed
+ return this._parser.registerCsiHandler(id, params => {
+ if (!paramToWindowOption(params.params[0], this._optionsService.rawOptions.windowOptions)) {
+ return true;
+ }
+ return callback(params);
+ });
+ }
+ return this._parser.registerCsiHandler(id, callback);
+ }
+
+ /**
+ * Forward registerDcsHandler from parser.
+ */
+ public registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean | Promise<boolean>): IDisposable {
+ return this._parser.registerDcsHandler(id, new DcsHandler(callback));
+ }
+
+ /**
+ * Forward registerEscHandler from parser.
+ */
+ public registerEscHandler(id: IFunctionIdentifier, callback: () => boolean | Promise<boolean>): IDisposable {
+ return this._parser.registerEscHandler(id, callback);
+ }
+
+ /**
+ * Forward registerOscHandler from parser.
+ */
+ public registerOscHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable {
+ return this._parser.registerOscHandler(ident, new OscHandler(callback));
+ }
+
+ /**
+ * BEL
+ * Bell (Ctrl-G).
+ *
+ * @vt: #Y C0 BEL "Bell" "\a, \x07" "Ring the bell."
+ * The behavior of the bell is further customizable with `ITerminalOptions.bellStyle`
+ * and `ITerminalOptions.bellSound`.
+ */
+ public bell(): boolean {
+ this._onRequestBell.fire();
+ return true;
+ }
+
+ /**
+ * LF
+ * Line Feed or New Line (NL). (LF is Ctrl-J).
+ *
+ * @vt: #Y C0 LF "Line Feed" "\n, \x0A" "Move the cursor one row down, scrolling if needed."
+ * Scrolling is restricted to scroll margins and will only happen on the bottom line.
+ *
+ * @vt: #Y C0 VT "Vertical Tabulation" "\v, \x0B" "Treated as LF."
+ * @vt: #Y C0 FF "Form Feed" "\f, \x0C" "Treated as LF."
+ */
+ public lineFeed(): boolean {
+ this._dirtyRowService.markDirty(this._activeBuffer.y);
+ if (this._optionsService.rawOptions.convertEol) {
+ this._activeBuffer.x = 0;
+ }
+ this._activeBuffer.y++;
+ if (this._activeBuffer.y === this._activeBuffer.scrollBottom + 1) {
+ this._activeBuffer.y--;
+ this._bufferService.scroll(this._eraseAttrData());
+ } else if (this._activeBuffer.y >= this._bufferService.rows) {
+ this._activeBuffer.y = this._bufferService.rows - 1;
+ }
+ // If the end of the line is hit, prevent this action from wrapping around to the next line.
+ if (this._activeBuffer.x >= this._bufferService.cols) {
+ this._activeBuffer.x--;
+ }
+ this._dirtyRowService.markDirty(this._activeBuffer.y);
+
+ this._onLineFeed.fire();
+ return true;
+ }
+
+ /**
+ * CR
+ * Carriage Return (Ctrl-M).
+ *
+ * @vt: #Y C0 CR "Carriage Return" "\r, \x0D" "Move the cursor to the beginning of the row."
+ */
+ public carriageReturn(): boolean {
+ this._activeBuffer.x = 0;
+ return true;
+ }
+
+ /**
+ * BS
+ * Backspace (Ctrl-H).
+ *
+ * @vt: #Y C0 BS "Backspace" "\b, \x08" "Move the cursor one position to the left."
+ * By default it is not possible to move the cursor past the leftmost position.
+ * If `reverse wrap-around` (`CSI ? 45 h`) is set, a previous soft line wrap (DECAWM)
+ * can be undone with BS within the scroll margins. In that case the cursor will wrap back
+ * to the end of the previous row. Note that it is not possible to peek back into the scrollbuffer
+ * with the cursor, thus at the home position (top-leftmost cell) this has no effect.
+ */
+ public backspace(): boolean {
+ // reverse wrap-around is disabled
+ if (!this._coreService.decPrivateModes.reverseWraparound) {
+ this._restrictCursor();
+ if (this._activeBuffer.x > 0) {
+ this._activeBuffer.x--;
+ }
+ return true;
+ }
+
+ // reverse wrap-around is enabled
+ // other than for normal operation mode, reverse wrap-around allows the cursor
+ // to be at x=cols to be able to address the last cell of a row by BS
+ this._restrictCursor(this._bufferService.cols);
+
+ if (this._activeBuffer.x > 0) {
+ this._activeBuffer.x--;
+ } else {
+ /**
+ * reverse wrap-around handling:
+ * Our implementation deviates from xterm on purpose. Details:
+ * - only previous soft NLs can be reversed (isWrapped=true)
+ * - only works within scrollborders (top/bottom, left/right not yet supported)
+ * - cannot peek into scrollbuffer
+ * - any cursor movement sequence keeps working as expected
+ */
+ if (this._activeBuffer.x === 0
+ && this._activeBuffer.y > this._activeBuffer.scrollTop
+ && this._activeBuffer.y <= this._activeBuffer.scrollBottom
+ && this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)?.isWrapped) {
+ this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!.isWrapped = false;
+ this._activeBuffer.y--;
+ this._activeBuffer.x = this._bufferService.cols - 1;
+ // find last taken cell - last cell can have 3 different states:
+ // - hasContent(true) + hasWidth(1): narrow char - we are done
+ // - hasWidth(0): second part of wide char - we are done
+ // - hasContent(false) + hasWidth(1): empty cell due to early wrapping wide char, go one cell further back
+ const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!;
+ if (line.hasWidth(this._activeBuffer.x) && !line.hasContent(this._activeBuffer.x)) {
+ this._activeBuffer.x--;
+ // We do this only once, since width=1 + hasContent=false currently happens only once before
+ // early wrapping of a wide char.
+ // This needs to be fixed once we support graphemes taking more than 2 cells.
+ }
+ }
+ }
+ this._restrictCursor();
+ return true;
+ }
+
+ /**
+ * TAB
+ * Horizontal Tab (HT) (Ctrl-I).
+ *
+ * @vt: #Y C0 HT "Horizontal Tabulation" "\t, \x09" "Move the cursor to the next character tab stop."
+ */
+ public tab(): boolean {
+ if (this._activeBuffer.x >= this._bufferService.cols) {
+ return true;
+ }
+ const originalX = this._activeBuffer.x;
+ this._activeBuffer.x = this._activeBuffer.nextStop();
+ if (this._optionsService.rawOptions.screenReaderMode) {
+ this._onA11yTab.fire(this._activeBuffer.x - originalX);
+ }
+ return true;
+ }
+
+ /**
+ * SO
+ * Shift Out (Ctrl-N) -> Switch to Alternate Character Set. This invokes the
+ * G1 character set.
+ *
+ * @vt: #P[Only limited ISO-2022 charset support.] C0 SO "Shift Out" "\x0E" "Switch to an alternative character set."
+ */
+ public shiftOut(): boolean {
+ this._charsetService.setgLevel(1);
+ return true;
+ }
+
+ /**
+ * SI
+ * Shift In (Ctrl-O) -> Switch to Standard Character Set. This invokes the G0
+ * character set (the default).
+ *
+ * @vt: #Y C0 SI "Shift In" "\x0F" "Return to regular character set after Shift Out."
+ */
+ public shiftIn(): boolean {
+ this._charsetService.setgLevel(0);
+ return true;
+ }
+
+ /**
+ * Restrict cursor to viewport size / scroll margin (origin mode).
+ */
+ private _restrictCursor(maxCol: number = this._bufferService.cols - 1): void {
+ this._activeBuffer.x = Math.min(maxCol, Math.max(0, this._activeBuffer.x));
+ this._activeBuffer.y = this._coreService.decPrivateModes.origin
+ ? Math.min(this._activeBuffer.scrollBottom, Math.max(this._activeBuffer.scrollTop, this._activeBuffer.y))
+ : Math.min(this._bufferService.rows - 1, Math.max(0, this._activeBuffer.y));
+ this._dirtyRowService.markDirty(this._activeBuffer.y);
+ }
+
+ /**
+ * Set absolute cursor position.
+ */
+ private _setCursor(x: number, y: number): void {
+ this._dirtyRowService.markDirty(this._activeBuffer.y);
+ if (this._coreService.decPrivateModes.origin) {
+ this._activeBuffer.x = x;
+ this._activeBuffer.y = this._activeBuffer.scrollTop + y;
+ } else {
+ this._activeBuffer.x = x;
+ this._activeBuffer.y = y;
+ }
+ this._restrictCursor();
+ this._dirtyRowService.markDirty(this._activeBuffer.y);
+ }
+
+ /**
+ * Set relative cursor position.
+ */
+ private _moveCursor(x: number, y: number): void {
+ // for relative changes we have to make sure we are within 0 .. cols/rows - 1
+ // before calculating the new position
+ this._restrictCursor();
+ this._setCursor(this._activeBuffer.x + x, this._activeBuffer.y + y);
+ }
+
+ /**
+ * CSI Ps A
+ * Cursor Up Ps Times (default = 1) (CUU).
+ *
+ * @vt: #Y CSI CUU "Cursor Up" "CSI Ps A" "Move cursor `Ps` times up (default=1)."
+ * If the cursor would pass the top scroll margin, it will stop there.
+ */
+ public cursorUp(params: IParams): boolean {
+ // stop at scrollTop
+ const diffToTop = this._activeBuffer.y - this._activeBuffer.scrollTop;
+ if (diffToTop >= 0) {
+ this._moveCursor(0, -Math.min(diffToTop, params.params[0] || 1));
+ } else {
+ this._moveCursor(0, -(params.params[0] || 1));
+ }
+ return true;
+ }
+
+ /**
+ * CSI Ps B
+ * Cursor Down Ps Times (default = 1) (CUD).
+ *
+ * @vt: #Y CSI CUD "Cursor Down" "CSI Ps B" "Move cursor `Ps` times down (default=1)."
+ * If the cursor would pass the bottom scroll margin, it will stop there.
+ */
+ public cursorDown(params: IParams): boolean {
+ // stop at scrollBottom
+ const diffToBottom = this._activeBuffer.scrollBottom - this._activeBuffer.y;
+ if (diffToBottom >= 0) {
+ this._moveCursor(0, Math.min(diffToBottom, params.params[0] || 1));
+ } else {
+ this._moveCursor(0, params.params[0] || 1);
+ }
+ return true;
+ }
+
+ /**
+ * CSI Ps C
+ * Cursor Forward Ps Times (default = 1) (CUF).
+ *
+ * @vt: #Y CSI CUF "Cursor Forward" "CSI Ps C" "Move cursor `Ps` times forward (default=1)."
+ */
+ public cursorForward(params: IParams): boolean {
+ this._moveCursor(params.params[0] || 1, 0);
+ return true;
+ }
+
+ /**
+ * CSI Ps D
+ * Cursor Backward Ps Times (default = 1) (CUB).
+ *
+ * @vt: #Y CSI CUB "Cursor Backward" "CSI Ps D" "Move cursor `Ps` times backward (default=1)."
+ */
+ public cursorBackward(params: IParams): boolean {
+ this._moveCursor(-(params.params[0] || 1), 0);
+ return true;
+ }
+
+ /**
+ * CSI Ps E
+ * Cursor Next Line Ps Times (default = 1) (CNL).
+ * Other than cursorDown (CUD) also set the cursor to first column.
+ *
+ * @vt: #Y CSI CNL "Cursor Next Line" "CSI Ps E" "Move cursor `Ps` times down (default=1) and to the first column."
+ * Same as CUD, additionally places the cursor at the first column.
+ */
+ public cursorNextLine(params: IParams): boolean {
+ this.cursorDown(params);
+ this._activeBuffer.x = 0;
+ return true;
+ }
+
+ /**
+ * CSI Ps F
+ * Cursor Previous Line Ps Times (default = 1) (CPL).
+ * Other than cursorUp (CUU) also set the cursor to first column.
+ *
+ * @vt: #Y CSI CPL "Cursor Backward" "CSI Ps F" "Move cursor `Ps` times up (default=1) and to the first column."
+ * Same as CUU, additionally places the cursor at the first column.
+ */
+ public cursorPrecedingLine(params: IParams): boolean {
+ this.cursorUp(params);
+ this._activeBuffer.x = 0;
+ return true;
+ }
+
+ /**
+ * CSI Ps G
+ * Cursor Character Absolute [column] (default = [row,1]) (CHA).
+ *
+ * @vt: #Y CSI CHA "Cursor Horizontal Absolute" "CSI Ps G" "Move cursor to `Ps`-th column of the active row (default=1)."
+ */
+ public cursorCharAbsolute(params: IParams): boolean {
+ this._setCursor((params.params[0] || 1) - 1, this._activeBuffer.y);
+ return true;
+ }
+
+ /**
+ * CSI Ps ; Ps H
+ * Cursor Position [row;column] (default = [1,1]) (CUP).
+ *
+ * @vt: #Y CSI CUP "Cursor Position" "CSI Ps ; Ps H" "Set cursor to position [`Ps`, `Ps`] (default = [1, 1])."
+ * If ORIGIN mode is set, places the cursor to the absolute position within the scroll margins.
+ * If ORIGIN mode is not set, places the cursor to the absolute position within the viewport.
+ * Note that the coordinates are 1-based, thus the top left position starts at `1 ; 1`.
+ */
+ public cursorPosition(params: IParams): boolean {
+ this._setCursor(
+ // col
+ (params.length >= 2) ? (params.params[1] || 1) - 1 : 0,
+ // row
+ (params.params[0] || 1) - 1
+ );
+ return true;
+ }
+
+ /**
+ * CSI Pm ` Character Position Absolute
+ * [column] (default = [row,1]) (HPA).
+ * Currently same functionality as CHA.
+ *
+ * @vt: #Y CSI HPA "Horizontal Position Absolute" "CSI Ps ` " "Same as CHA."
+ */
+ public charPosAbsolute(params: IParams): boolean {
+ this._setCursor((params.params[0] || 1) - 1, this._activeBuffer.y);
+ return true;
+ }
+
+ /**
+ * CSI Pm a Character Position Relative
+ * [columns] (default = [row,col+1]) (HPR)
+ *
+ * @vt: #Y CSI HPR "Horizontal Position Relative" "CSI Ps a" "Same as CUF."
+ */
+ public hPositionRelative(params: IParams): boolean {
+ this._moveCursor(params.params[0] || 1, 0);
+ return true;
+ }
+
+ /**
+ * CSI Pm d Vertical Position Absolute (VPA)
+ * [row] (default = [1,column])
+ *
+ * @vt: #Y CSI VPA "Vertical Position Absolute" "CSI Ps d" "Move cursor to `Ps`-th row (default=1)."
+ */
+ public linePosAbsolute(params: IParams): boolean {
+ this._setCursor(this._activeBuffer.x, (params.params[0] || 1) - 1);
+ return true;
+ }
+
+ /**
+ * CSI Pm e Vertical Position Relative (VPR)
+ * [rows] (default = [row+1,column])
+ * reuse CSI Ps B ?
+ *
+ * @vt: #Y CSI VPR "Vertical Position Relative" "CSI Ps e" "Move cursor `Ps` times down (default=1)."
+ */
+ public vPositionRelative(params: IParams): boolean {
+ this._moveCursor(0, params.params[0] || 1);
+ return true;
+ }
+
+ /**
+ * CSI Ps ; Ps f
+ * Horizontal and Vertical Position [row;column] (default =
+ * [1,1]) (HVP).
+ * Same as CUP.
+ *
+ * @vt: #Y CSI HVP "Horizontal and Vertical Position" "CSI Ps ; Ps f" "Same as CUP."
+ */
+ public hVPosition(params: IParams): boolean {
+ this.cursorPosition(params);
+ return true;
+ }
+
+ /**
+ * CSI Ps g Tab Clear (TBC).
+ * Ps = 0 -> Clear Current Column (default).
+ * Ps = 3 -> Clear All.
+ * Potentially:
+ * Ps = 2 -> Clear Stops on Line.
+ * http://vt100.net/annarbor/aaa-ug/section6.html
+ *
+ * @vt: #Y CSI TBC "Tab Clear" "CSI Ps g" "Clear tab stops at current position (0) or all (3) (default=0)."
+ * Clearing tabstops off the active row (Ps = 2, VT100) is currently not supported.
+ */
+ public tabClear(params: IParams): boolean {
+ const param = params.params[0];
+ if (param === 0) {
+ delete this._activeBuffer.tabs[this._activeBuffer.x];
+ } else if (param === 3) {
+ this._activeBuffer.tabs = {};
+ }
+ return true;
+ }
+
+ /**
+ * CSI Ps I
+ * Cursor Forward Tabulation Ps tab stops (default = 1) (CHT).
+ *
+ * @vt: #Y CSI CHT "Cursor Horizontal Tabulation" "CSI Ps I" "Move cursor `Ps` times tabs forward (default=1)."
+ */
+ public cursorForwardTab(params: IParams): boolean {
+ if (this._activeBuffer.x >= this._bufferService.cols) {
+ return true;
+ }
+ let param = params.params[0] || 1;
+ while (param--) {
+ this._activeBuffer.x = this._activeBuffer.nextStop();
+ }
+ return true;
+ }
+
+ /**
+ * CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT).
+ *
+ * @vt: #Y CSI CBT "Cursor Backward Tabulation" "CSI Ps Z" "Move cursor `Ps` tabs backward (default=1)."
+ */
+ public cursorBackwardTab(params: IParams): boolean {
+ if (this._activeBuffer.x >= this._bufferService.cols) {
+ return true;
+ }
+ let param = params.params[0] || 1;
+
+ while (param--) {
+ this._activeBuffer.x = this._activeBuffer.prevStop();
+ }
+ return true;
+ }
+
+
+ /**
+ * Helper method to erase cells in a terminal row.
+ * The cell gets replaced with the eraseChar of the terminal.
+ * @param y row index
+ * @param start first cell index to be erased
+ * @param end end - 1 is last erased cell
+ * @param cleanWrap clear the isWrapped flag
+ */
+ private _eraseInBufferLine(y: number, start: number, end: number, clearWrap: boolean = false): void {
+ const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;
+ line.replaceCells(
+ start,
+ end,
+ this._activeBuffer.getNullCell(this._eraseAttrData()),
+ this._eraseAttrData()
+ );
+ if (clearWrap) {
+ line.isWrapped = false;
+ }
+ }
+
+ /**
+ * Helper method to reset cells in a terminal row.
+ * The cell gets replaced with the eraseChar of the terminal and the isWrapped property is set to false.
+ * @param y row index
+ */
+ private _resetBufferLine(y: number): void {
+ const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;
+ line.fill(this._activeBuffer.getNullCell(this._eraseAttrData()));
+ line.isWrapped = false;
+ }
+
+ /**
+ * CSI Ps J Erase in Display (ED).
+ * Ps = 0 -> Erase Below (default).
+ * Ps = 1 -> Erase Above.
+ * Ps = 2 -> Erase All.
+ * Ps = 3 -> Erase Saved Lines (xterm).
+ * CSI ? Ps J
+ * Erase in Display (DECSED).
+ * Ps = 0 -> Selective Erase Below (default).
+ * Ps = 1 -> Selective Erase Above.
+ * Ps = 2 -> Selective Erase All.
+ *
+ * @vt: #Y CSI ED "Erase In Display" "CSI Ps J" "Erase various parts of the viewport."
+ * Supported param values:
+ *
+ * | Ps | Effect |
+ * | -- | ------------------------------------------------------------ |
+ * | 0 | Erase from the cursor through the end of the viewport. |
+ * | 1 | Erase from the beginning of the viewport through the cursor. |
+ * | 2 | Erase complete viewport. |
+ * | 3 | Erase scrollback. |
+ *
+ * @vt: #P[Protection attributes are not supported.] CSI DECSED "Selective Erase In Display" "CSI ? Ps J" "Currently the same as ED."
+ */
+ public eraseInDisplay(params: IParams): boolean {
+ this._restrictCursor(this._bufferService.cols);
+ let j;
+ switch (params.params[0]) {
+ case 0:
+ j = this._activeBuffer.y;
+ this._dirtyRowService.markDirty(j);
+ this._eraseInBufferLine(j++, this._activeBuffer.x, this._bufferService.cols, this._activeBuffer.x === 0);
+ for (; j < this._bufferService.rows; j++) {
+ this._resetBufferLine(j);
+ }
+ this._dirtyRowService.markDirty(j);
+ break;
+ case 1:
+ j = this._activeBuffer.y;
+ this._dirtyRowService.markDirty(j);
+ // Deleted front part of line and everything before. This line will no longer be wrapped.
+ this._eraseInBufferLine(j, 0, this._activeBuffer.x + 1, true);
+ if (this._activeBuffer.x + 1 >= this._bufferService.cols) {
+ // Deleted entire previous line. This next line can no longer be wrapped.
+ this._activeBuffer.lines.get(j + 1)!.isWrapped = false;
+ }
+ while (j--) {
+ this._resetBufferLine(j);
+ }
+ this._dirtyRowService.markDirty(0);
+ break;
+ case 2:
+ j = this._bufferService.rows;
+ this._dirtyRowService.markDirty(j - 1);
+ while (j--) {
+ this._resetBufferLine(j);
+ }
+ this._dirtyRowService.markDirty(0);
+ break;
+ case 3:
+ // Clear scrollback (everything not in viewport)
+ const scrollBackSize = this._activeBuffer.lines.length - this._bufferService.rows;
+ if (scrollBackSize > 0) {
+ this._activeBuffer.lines.trimStart(scrollBackSize);
+ this._activeBuffer.ybase = Math.max(this._activeBuffer.ybase - scrollBackSize, 0);
+ this._activeBuffer.ydisp = Math.max(this._activeBuffer.ydisp - scrollBackSize, 0);
+ // Force a scroll event to refresh viewport
+ this._onScroll.fire(0);
+ }
+ break;
+ }
+ return true;
+ }
+
+ /**
+ * CSI Ps K Erase in Line (EL).
+ * Ps = 0 -> Erase to Right (default).
+ * Ps = 1 -> Erase to Left.
+ * Ps = 2 -> Erase All.
+ * CSI ? Ps K
+ * Erase in Line (DECSEL).
+ * Ps = 0 -> Selective Erase to Right (default).
+ * Ps = 1 -> Selective Erase to Left.
+ * Ps = 2 -> Selective Erase All.
+ *
+ * @vt: #Y CSI EL "Erase In Line" "CSI Ps K" "Erase various parts of the active row."
+ * Supported param values:
+ *
+ * | Ps | Effect |
+ * | -- | -------------------------------------------------------- |
+ * | 0 | Erase from the cursor through the end of the row. |
+ * | 1 | Erase from the beginning of the line through the cursor. |
+ * | 2 | Erase complete line. |
+ *
+ * @vt: #P[Protection attributes are not supported.] CSI DECSEL "Selective Erase In Line" "CSI ? Ps K" "Currently the same as EL."
+ */
+ public eraseInLine(params: IParams): boolean {
+ this._restrictCursor(this._bufferService.cols);
+ switch (params.params[0]) {
+ case 0:
+ this._eraseInBufferLine(this._activeBuffer.y, this._activeBuffer.x, this._bufferService.cols, this._activeBuffer.x === 0);
+ break;
+ case 1:
+ this._eraseInBufferLine(this._activeBuffer.y, 0, this._activeBuffer.x + 1, false);
+ break;
+ case 2:
+ this._eraseInBufferLine(this._activeBuffer.y, 0, this._bufferService.cols, true);
+ break;
+ }
+ this._dirtyRowService.markDirty(this._activeBuffer.y);
+ return true;
+ }
+
+ /**
+ * CSI Ps L
+ * Insert Ps Line(s) (default = 1) (IL).
+ *
+ * @vt: #Y CSI IL "Insert Line" "CSI Ps L" "Insert `Ps` blank lines at active row (default=1)."
+ * For every inserted line at the scroll top one line at the scroll bottom gets removed.
+ * The cursor is set to the first column.
+ * IL has no effect if the cursor is outside the scroll margins.
+ */
+ public insertLines(params: IParams): boolean {
+ this._restrictCursor();
+ let param = params.params[0] || 1;
+
+ if (this._activeBuffer.y > this._activeBuffer.scrollBottom || this._activeBuffer.y < this._activeBuffer.scrollTop) {
+ return true;
+ }
+
+ const row: number = this._activeBuffer.ybase + this._activeBuffer.y;
+
+ const scrollBottomRowsOffset = this._bufferService.rows - 1 - this._activeBuffer.scrollBottom;
+ const scrollBottomAbsolute = this._bufferService.rows - 1 + this._activeBuffer.ybase - scrollBottomRowsOffset + 1;
+ while (param--) {
+ // test: echo -e '\e[44m\e[1L\e[0m'
+ // blankLine(true) - xterm/linux behavior
+ this._activeBuffer.lines.splice(scrollBottomAbsolute - 1, 1);
+ this._activeBuffer.lines.splice(row, 0, this._activeBuffer.getBlankLine(this._eraseAttrData()));
+ }
+
+ this._dirtyRowService.markRangeDirty(this._activeBuffer.y, this._activeBuffer.scrollBottom);
+ this._activeBuffer.x = 0; // see https://vt100.net/docs/vt220-rm/chapter4.html - vt220 only?
+ return true;
+ }
+
+ /**
+ * CSI Ps M
+ * Delete Ps Line(s) (default = 1) (DL).
+ *
+ * @vt: #Y CSI DL "Delete Line" "CSI Ps M" "Delete `Ps` lines at active row (default=1)."
+ * For every deleted line at the scroll top one blank line at the scroll bottom gets appended.
+ * The cursor is set to the first column.
+ * DL has no effect if the cursor is outside the scroll margins.
+ */
+ public deleteLines(params: IParams): boolean {
+ this._restrictCursor();
+ let param = params.params[0] || 1;
+
+ if (this._activeBuffer.y > this._activeBuffer.scrollBottom || this._activeBuffer.y < this._activeBuffer.scrollTop) {
+ return true;
+ }
+
+ const row: number = this._activeBuffer.ybase + this._activeBuffer.y;
+
+ let j: number;
+ j = this._bufferService.rows - 1 - this._activeBuffer.scrollBottom;
+ j = this._bufferService.rows - 1 + this._activeBuffer.ybase - j;
+ while (param--) {
+ // test: echo -e '\e[44m\e[1M\e[0m'
+ // blankLine(true) - xterm/linux behavior
+ this._activeBuffer.lines.splice(row, 1);
+ this._activeBuffer.lines.splice(j, 0, this._activeBuffer.getBlankLine(this._eraseAttrData()));
+ }
+
+ this._dirtyRowService.markRangeDirty(this._activeBuffer.y, this._activeBuffer.scrollBottom);
+ this._activeBuffer.x = 0; // see https://vt100.net/docs/vt220-rm/chapter4.html - vt220 only?
+ return true;
+ }
+
+ /**
+ * CSI Ps @
+ * Insert Ps (Blank) Character(s) (default = 1) (ICH).
+ *
+ * @vt: #Y CSI ICH "Insert Characters" "CSI Ps @" "Insert `Ps` (blank) characters (default = 1)."
+ * The ICH sequence inserts `Ps` blank characters. The cursor remains at the beginning of the blank characters.
+ * Text between the cursor and right margin moves to the right. Characters moved past the right margin are lost.
+ *
+ *
+ * FIXME: check against xterm - should not work outside of scroll margins (see VT520 manual)
+ */
+ public insertChars(params: IParams): boolean {
+ this._restrictCursor();
+ const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y);
+ if (line) {
+ line.insertCells(
+ this._activeBuffer.x,
+ params.params[0] || 1,
+ this._activeBuffer.getNullCell(this._eraseAttrData()),
+ this._eraseAttrData()
+ );
+ this._dirtyRowService.markDirty(this._activeBuffer.y);
+ }
+ return true;
+ }
+
+ /**
+ * CSI Ps P
+ * Delete Ps Character(s) (default = 1) (DCH).
+ *
+ * @vt: #Y CSI DCH "Delete Character" "CSI Ps P" "Delete `Ps` characters (default=1)."
+ * As characters are deleted, the remaining characters between the cursor and right margin move to the left.
+ * Character attributes move with the characters. The terminal adds blank characters at the right margin.
+ *
+ *
+ * FIXME: check against xterm - should not work outside of scroll margins (see VT520 manual)
+ */
+ public deleteChars(params: IParams): boolean {
+ this._restrictCursor();
+ const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y);
+ if (line) {
+ line.deleteCells(
+ this._activeBuffer.x,
+ params.params[0] || 1,
+ this._activeBuffer.getNullCell(this._eraseAttrData()),
+ this._eraseAttrData()
+ );
+ this._dirtyRowService.markDirty(this._activeBuffer.y);
+ }
+ return true;
+ }
+
+ /**
+ * CSI Ps S Scroll up Ps lines (default = 1) (SU).
+ *
+ * @vt: #Y CSI SU "Scroll Up" "CSI Ps S" "Scroll `Ps` lines up (default=1)."
+ *
+ *
+ * FIXME: scrolled out lines at top = 1 should add to scrollback (xterm)
+ */
+ public scrollUp(params: IParams): boolean {
+ let param = params.params[0] || 1;
+
+ while (param--) {
+ this._activeBuffer.lines.splice(this._activeBuffer.ybase + this._activeBuffer.scrollTop, 1);
+ this._activeBuffer.lines.splice(this._activeBuffer.ybase + this._activeBuffer.scrollBottom, 0, this._activeBuffer.getBlankLine(this._eraseAttrData()));
+ }
+ this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
+ return true;
+ }
+
+ /**
+ * CSI Ps T Scroll down Ps lines (default = 1) (SD).
+ *
+ * @vt: #Y CSI SD "Scroll Down" "CSI Ps T" "Scroll `Ps` lines down (default=1)."
+ */
+ public scrollDown(params: IParams): boolean {
+ let param = params.params[0] || 1;
+
+ while (param--) {
+ this._activeBuffer.lines.splice(this._activeBuffer.ybase + this._activeBuffer.scrollBottom, 1);
+ this._activeBuffer.lines.splice(this._activeBuffer.ybase + this._activeBuffer.scrollTop, 0, this._activeBuffer.getBlankLine(DEFAULT_ATTR_DATA));
+ }
+ this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
+ return true;
+ }
+
+ /**
+ * CSI Ps SP @ Scroll left Ps columns (default = 1) (SL) ECMA-48
+ *
+ * Notation: (Pn)
+ * Representation: CSI Pn 02/00 04/00
+ * Parameter default value: Pn = 1
+ * SL causes the data in the presentation component to be moved by n character positions
+ * if the line orientation is horizontal, or by n line positions if the line orientation
+ * is vertical, such that the data appear to move to the left; where n equals the value of Pn.
+ * The active presentation position is not affected by this control function.
+ *
+ * Supported:
+ * - always left shift (no line orientation setting respected)
+ *
+ * @vt: #Y CSI SL "Scroll Left" "CSI Ps SP @" "Scroll viewport `Ps` times to the left."
+ * SL moves the content of all lines within the scroll margins `Ps` times to the left.
+ * SL has no effect outside of the scroll margins.
+ */
+ public scrollLeft(params: IParams): boolean {
+ if (this._activeBuffer.y > this._activeBuffer.scrollBottom || this._activeBuffer.y < this._activeBuffer.scrollTop) {
+ return true;
+ }
+ const param = params.params[0] || 1;
+ for (let y = this._activeBuffer.scrollTop; y <= this._activeBuffer.scrollBottom; ++y) {
+ const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;
+ line.deleteCells(0, param, this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData());
+ line.isWrapped = false;
+ }
+ this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
+ return true;
+ }
+
+ /**
+ * CSI Ps SP A Scroll right Ps columns (default = 1) (SR) ECMA-48
+ *
+ * Notation: (Pn)
+ * Representation: CSI Pn 02/00 04/01
+ * Parameter default value: Pn = 1
+ * SR causes the data in the presentation component to be moved by n character positions
+ * if the line orientation is horizontal, or by n line positions if the line orientation
+ * is vertical, such that the data appear to move to the right; where n equals the value of Pn.
+ * The active presentation position is not affected by this control function.
+ *
+ * Supported:
+ * - always right shift (no line orientation setting respected)
+ *
+ * @vt: #Y CSI SR "Scroll Right" "CSI Ps SP A" "Scroll viewport `Ps` times to the right."
+ * SL moves the content of all lines within the scroll margins `Ps` times to the right.
+ * Content at the right margin is lost.
+ * SL has no effect outside of the scroll margins.
+ */
+ public scrollRight(params: IParams): boolean {
+ if (this._activeBuffer.y > this._activeBuffer.scrollBottom || this._activeBuffer.y < this._activeBuffer.scrollTop) {
+ return true;
+ }
+ const param = params.params[0] || 1;
+ for (let y = this._activeBuffer.scrollTop; y <= this._activeBuffer.scrollBottom; ++y) {
+ const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;
+ line.insertCells(0, param, this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData());
+ line.isWrapped = false;
+ }
+ this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
+ return true;
+ }
+
+ /**
+ * CSI Pm ' }
+ * Insert Ps Column(s) (default = 1) (DECIC), VT420 and up.
+ *
+ * @vt: #Y CSI DECIC "Insert Columns" "CSI Ps ' }" "Insert `Ps` columns at cursor position."
+ * DECIC inserts `Ps` times blank columns at the cursor position for all lines with the scroll margins,
+ * moving content to the right. Content at the right margin is lost.
+ * DECIC has no effect outside the scrolling margins.
+ */
+ public insertColumns(params: IParams): boolean {
+ if (this._activeBuffer.y > this._activeBuffer.scrollBottom || this._activeBuffer.y < this._activeBuffer.scrollTop) {
+ return true;
+ }
+ const param = params.params[0] || 1;
+ for (let y = this._activeBuffer.scrollTop; y <= this._activeBuffer.scrollBottom; ++y) {
+ const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;
+ line.insertCells(this._activeBuffer.x, param, this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData());
+ line.isWrapped = false;
+ }
+ this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
+ return true;
+ }
+
+ /**
+ * CSI Pm ' ~
+ * Delete Ps Column(s) (default = 1) (DECDC), VT420 and up.
+ *
+ * @vt: #Y CSI DECDC "Delete Columns" "CSI Ps ' ~" "Delete `Ps` columns at cursor position."
+ * DECDC deletes `Ps` times columns at the cursor position for all lines with the scroll margins,
+ * moving content to the left. Blank columns are added at the right margin.
+ * DECDC has no effect outside the scrolling margins.
+ */
+ public deleteColumns(params: IParams): boolean {
+ if (this._activeBuffer.y > this._activeBuffer.scrollBottom || this._activeBuffer.y < this._activeBuffer.scrollTop) {
+ return true;
+ }
+ const param = params.params[0] || 1;
+ for (let y = this._activeBuffer.scrollTop; y <= this._activeBuffer.scrollBottom; ++y) {
+ const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;
+ line.deleteCells(this._activeBuffer.x, param, this._activeBuffer.getNullCell(this._eraseAttrData()), this._eraseAttrData());
+ line.isWrapped = false;
+ }
+ this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
+ return true;
+ }
+
+ /**
+ * CSI Ps X
+ * Erase Ps Character(s) (default = 1) (ECH).
+ *
+ * @vt: #Y CSI ECH "Erase Character" "CSI Ps X" "Erase `Ps` characters from current cursor position to the right (default=1)."
+ * ED erases `Ps` characters from current cursor position to the right.
+ * ED works inside or outside the scrolling margins.
+ */
+ public eraseChars(params: IParams): boolean {
+ this._restrictCursor();
+ const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y);
+ if (line) {
+ line.replaceCells(
+ this._activeBuffer.x,
+ this._activeBuffer.x + (params.params[0] || 1),
+ this._activeBuffer.getNullCell(this._eraseAttrData()),
+ this._eraseAttrData()
+ );
+ this._dirtyRowService.markDirty(this._activeBuffer.y);
+ }
+ return true;
+ }
+
+ /**
+ * CSI Ps b Repeat the preceding graphic character Ps times (REP).
+ * From ECMA 48 (@see http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf)
+ * Notation: (Pn)
+ * Representation: CSI Pn 06/02
+ * Parameter default value: Pn = 1
+ * REP is used to indicate that the preceding character in the data stream,
+ * if it is a graphic character (represented by one or more bit combinations) including SPACE,
+ * is to be repeated n times, where n equals the value of Pn.
+ * If the character preceding REP is a control function or part of a control function,
+ * the effect of REP is not defined by this Standard.
+ *
+ * Since we propagate the terminal as xterm-256color we have to follow xterm's behavior:
+ * - fullwidth + surrogate chars are ignored
+ * - for combining chars only the base char gets repeated
+ * - text attrs are applied normally
+ * - wrap around is respected
+ * - any valid sequence resets the carried forward char
+ *
+ * Note: To get reset on a valid sequence working correctly without much runtime penalty,
+ * the preceding codepoint is stored on the parser in `this.print` and reset during `parser.parse`.
+ *
+ * @vt: #Y CSI REP "Repeat Preceding Character" "CSI Ps b" "Repeat preceding character `Ps` times (default=1)."
+ * REP repeats the previous character `Ps` times advancing the cursor, also wrapping if DECAWM is set.
+ * REP has no effect if the sequence does not follow a printable ASCII character
+ * (NOOP for any other sequence in between or NON ASCII characters).
+ */
+ public repeatPrecedingCharacter(params: IParams): boolean {
+ if (!this._parser.precedingCodepoint) {
+ return true;
+ }
+ // call print to insert the chars and handle correct wrapping
+ const length = params.params[0] || 1;
+ const data = new Uint32Array(length);
+ for (let i = 0; i < length; ++i) {
+ data[i] = this._parser.precedingCodepoint;
+ }
+ this.print(data, 0, data.length);
+ return true;
+ }
+
+ /**
+ * CSI Ps c Send Device Attributes (Primary DA).
+ * Ps = 0 or omitted -> request attributes from terminal. The
+ * response depends on the decTerminalID resource setting.
+ * -> CSI ? 1 ; 2 c (``VT100 with Advanced Video Option'')
+ * -> CSI ? 1 ; 0 c (``VT101 with No Options'')
+ * -> CSI ? 6 c (``VT102'')
+ * -> CSI ? 6 0 ; 1 ; 2 ; 6 ; 8 ; 9 ; 1 5 ; c (``VT220'')
+ * The VT100-style response parameters do not mean anything by
+ * themselves. VT220 parameters do, telling the host what fea-
+ * tures the terminal supports:
+ * Ps = 1 -> 132-columns.
+ * Ps = 2 -> Printer.
+ * Ps = 6 -> Selective erase.
+ * Ps = 8 -> User-defined keys.
+ * Ps = 9 -> National replacement character sets.
+ * Ps = 1 5 -> Technical characters.
+ * Ps = 2 2 -> ANSI color, e.g., VT525.
+ * Ps = 2 9 -> ANSI text locator (i.e., DEC Locator mode).
+ *
+ * @vt: #Y CSI DA1 "Primary Device Attributes" "CSI c" "Send primary device attributes."
+ *
+ *
+ * TODO: fix and cleanup response
+ */
+ public sendDeviceAttributesPrimary(params: IParams): boolean {
+ if (params.params[0] > 0) {
+ return true;
+ }
+ if (this._is('xterm') || this._is('rxvt-unicode') || this._is('screen')) {
+ this._coreService.triggerDataEvent(C0.ESC + '[?1;2c');
+ } else if (this._is('linux')) {
+ this._coreService.triggerDataEvent(C0.ESC + '[?6c');
+ }
+ return true;
+ }
+
+ /**
+ * CSI > Ps c
+ * Send Device Attributes (Secondary DA).
+ * Ps = 0 or omitted -> request the terminal's identification
+ * code. The response depends on the decTerminalID resource set-
+ * ting. It should apply only to VT220 and up, but xterm extends
+ * this to VT100.
+ * -> CSI > Pp ; Pv ; Pc c
+ * where Pp denotes the terminal type
+ * Pp = 0 -> ``VT100''.
+ * Pp = 1 -> ``VT220''.
+ * and Pv is the firmware version (for xterm, this was originally
+ * the XFree86 patch number, starting with 95). In a DEC termi-
+ * nal, Pc indicates the ROM cartridge registration number and is
+ * always zero.
+ * More information:
+ * xterm/charproc.c - line 2012, for more information.
+ * vim responds with ^[[?0c or ^[[?1c after the terminal's response (?)
+ *
+ * @vt: #Y CSI DA2 "Secondary Device Attributes" "CSI > c" "Send primary device attributes."
+ *
+ *
+ * TODO: fix and cleanup response
+ */
+ public sendDeviceAttributesSecondary(params: IParams): boolean {
+ if (params.params[0] > 0) {
+ return true;
+ }
+ // xterm and urxvt
+ // seem to spit this
+ // out around ~370 times (?).
+ if (this._is('xterm')) {
+ this._coreService.triggerDataEvent(C0.ESC + '[>0;276;0c');
+ } else if (this._is('rxvt-unicode')) {
+ this._coreService.triggerDataEvent(C0.ESC + '[>85;95;0c');
+ } else if (this._is('linux')) {
+ // not supported by linux console.
+ // linux console echoes parameters.
+ this._coreService.triggerDataEvent(params.params[0] + 'c');
+ } else if (this._is('screen')) {
+ this._coreService.triggerDataEvent(C0.ESC + '[>83;40003;0c');
+ }
+ return true;
+ }
+
+ /**
+ * Evaluate if the current terminal is the given argument.
+ * @param term The terminal name to evaluate
+ */
+ private _is(term: string): boolean {
+ return (this._optionsService.rawOptions.termName + '').indexOf(term) === 0;
+ }
+
+ /**
+ * CSI Pm h Set Mode (SM).
+ * Ps = 2 -> Keyboard Action Mode (AM).
+ * Ps = 4 -> Insert Mode (IRM).
+ * Ps = 1 2 -> Send/receive (SRM).
+ * Ps = 2 0 -> Automatic Newline (LNM).
+ *
+ * @vt: #P[Only IRM is supported.] CSI SM "Set Mode" "CSI Pm h" "Set various terminal modes."
+ * Supported param values by SM:
+ *
+ * | Param | Action | Support |
+ * | ----- | -------------------------------------- | ------- |
+ * | 2 | Keyboard Action Mode (KAM). Always on. | #N |
+ * | 4 | Insert Mode (IRM). | #Y |
+ * | 12 | Send/receive (SRM). Always off. | #N |
+ * | 20 | Automatic Newline (LNM). Always off. | #N |
+ */
+ public setMode(params: IParams): boolean {
+ for (let i = 0; i < params.length; i++) {
+ switch (params.params[i]) {
+ case 4:
+ this._coreService.modes.insertMode = true;
+ break;
+ case 20:
+ // this._t.convertEol = true;
+ break;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * CSI ? Pm h
+ * DEC Private Mode Set (DECSET).
+ * Ps = 1 -> Application Cursor Keys (DECCKM).
+ * Ps = 2 -> Designate USASCII for character sets G0-G3
+ * (DECANM), and set VT100 mode.
+ * Ps = 3 -> 132 Column Mode (DECCOLM).
+ * Ps = 4 -> Smooth (Slow) Scroll (DECSCLM).
+ * Ps = 5 -> Reverse Video (DECSCNM).
+ * Ps = 6 -> Origin Mode (DECOM).
+ * Ps = 7 -> Wraparound Mode (DECAWM).
+ * Ps = 8 -> Auto-repeat Keys (DECARM).
+ * Ps = 9 -> Send Mouse X & Y on button press. See the sec-
+ * tion Mouse Tracking.
+ * Ps = 1 0 -> Show toolbar (rxvt).
+ * Ps = 1 2 -> Start Blinking Cursor (att610).
+ * Ps = 1 8 -> Print form feed (DECPFF).
+ * Ps = 1 9 -> Set print extent to full screen (DECPEX).
+ * Ps = 2 5 -> Show Cursor (DECTCEM).
+ * Ps = 3 0 -> Show scrollbar (rxvt).
+ * Ps = 3 5 -> Enable font-shifting functions (rxvt).
+ * Ps = 3 8 -> Enter Tektronix Mode (DECTEK).
+ * Ps = 4 0 -> Allow 80 -> 132 Mode.
+ * Ps = 4 1 -> more(1) fix (see curses resource).
+ * Ps = 4 2 -> Enable Nation Replacement Character sets (DECN-
+ * RCM).
+ * Ps = 4 4 -> Turn On Margin Bell.
+ * Ps = 4 5 -> Reverse-wraparound Mode.
+ * Ps = 4 6 -> Start Logging. This is normally disabled by a
+ * compile-time option.
+ * Ps = 4 7 -> Use Alternate Screen Buffer. (This may be dis-
+ * abled by the titeInhibit resource).
+ * Ps = 6 6 -> Application keypad (DECNKM).
+ * Ps = 6 7 -> Backarrow key sends backspace (DECBKM).
+ * Ps = 1 0 0 0 -> Send Mouse X & Y on button press and
+ * release. See the section Mouse Tracking.
+ * Ps = 1 0 0 1 -> Use Hilite Mouse Tracking.
+ * Ps = 1 0 0 2 -> Use Cell Motion Mouse Tracking.
+ * Ps = 1 0 0 3 -> Use All Motion Mouse Tracking.
+ * Ps = 1 0 0 4 -> Send FocusIn/FocusOut events.
+ * Ps = 1 0 0 5 -> Enable Extended Mouse Mode.
+ * Ps = 1 0 1 0 -> Scroll to bottom on tty output (rxvt).
+ * Ps = 1 0 1 1 -> Scroll to bottom on key press (rxvt).
+ * Ps = 1 0 3 4 -> Interpret "meta" key, sets eighth bit.
+ * (enables the eightBitInput resource).
+ * Ps = 1 0 3 5 -> Enable special modifiers for Alt and Num-
+ * Lock keys. (This enables the numLock resource).
+ * Ps = 1 0 3 6 -> Send ESC when Meta modifies a key. (This
+ * enables the metaSendsEscape resource).
+ * Ps = 1 0 3 7 -> Send DEL from the editing-keypad Delete
+ * key.
+ * Ps = 1 0 3 9 -> Send ESC when Alt modifies a key. (This
+ * enables the altSendsEscape resource).
+ * Ps = 1 0 4 0 -> Keep selection even if not highlighted.
+ * (This enables the keepSelection resource).
+ * Ps = 1 0 4 1 -> Use the CLIPBOARD selection. (This enables
+ * the selectToClipboard resource).
+ * Ps = 1 0 4 2 -> Enable Urgency window manager hint when
+ * Control-G is received. (This enables the bellIsUrgent
+ * resource).
+ * Ps = 1 0 4 3 -> Enable raising of the window when Control-G
+ * is received. (enables the popOnBell resource).
+ * Ps = 1 0 4 7 -> Use Alternate Screen Buffer. (This may be
+ * disabled by the titeInhibit resource).
+ * Ps = 1 0 4 8 -> Save cursor as in DECSC. (This may be dis-
+ * abled by the titeInhibit resource).
+ * Ps = 1 0 4 9 -> Save cursor as in DECSC and use Alternate
+ * Screen Buffer, clearing it first. (This may be disabled by
+ * the titeInhibit resource). This combines the effects of the 1
+ * 0 4 7 and 1 0 4 8 modes. Use this with terminfo-based
+ * applications rather than the 4 7 mode.
+ * Ps = 1 0 5 0 -> Set terminfo/termcap function-key mode.
+ * Ps = 1 0 5 1 -> Set Sun function-key mode.
+ * Ps = 1 0 5 2 -> Set HP function-key mode.
+ * Ps = 1 0 5 3 -> Set SCO function-key mode.
+ * Ps = 1 0 6 0 -> Set legacy keyboard emulation (X11R6).
+ * Ps = 1 0 6 1 -> Set VT220 keyboard emulation.
+ * Ps = 2 0 0 4 -> Set bracketed paste mode.
+ * Modes:
+ * http: *vt100.net/docs/vt220-rm/chapter4.html
+ *
+ * @vt: #P[See below for supported modes.] CSI DECSET "DEC Private Set Mode" "CSI ? Pm h" "Set various terminal attributes."
+ * Supported param values by DECSET:
+ *
+ * | param | Action | Support |
+ * | ----- | ------------------------------------------------------- | --------|
+ * | 1 | Application Cursor Keys (DECCKM). | #Y |
+ * | 2 | Designate US-ASCII for character sets G0-G3 (DECANM). | #Y |
+ * | 3 | 132 Column Mode (DECCOLM). | #Y |
+ * | 6 | Origin Mode (DECOM). | #Y |
+ * | 7 | Auto-wrap Mode (DECAWM). | #Y |
+ * | 8 | Auto-repeat Keys (DECARM). Always on. | #N |
+ * | 9 | X10 xterm mouse protocol. | #Y |
+ * | 12 | Start Blinking Cursor. | #Y |
+ * | 25 | Show Cursor (DECTCEM). | #Y |
+ * | 45 | Reverse wrap-around. | #Y |
+ * | 47 | Use Alternate Screen Buffer. | #Y |
+ * | 66 | Application keypad (DECNKM). | #Y |
+ * | 1000 | X11 xterm mouse protocol. | #Y |
+ * | 1002 | Use Cell Motion Mouse Tracking. | #Y |
+ * | 1003 | Use All Motion Mouse Tracking. | #Y |
+ * | 1004 | Send FocusIn/FocusOut events | #Y |
+ * | 1005 | Enable UTF-8 Mouse Mode. | #N |
+ * | 1006 | Enable SGR Mouse Mode. | #Y |
+ * | 1015 | Enable urxvt Mouse Mode. | #N |
+ * | 1047 | Use Alternate Screen Buffer. | #Y |
+ * | 1048 | Save cursor as in DECSC. | #Y |
+ * | 1049 | Save cursor and switch to alternate buffer clearing it. | #P[Does not clear the alternate buffer.] |
+ * | 2004 | Set bracketed paste mode. | #Y |
+ *
+ *
+ * FIXME: implement DECSCNM, 1049 should clear altbuffer
+ */
+ public setModePrivate(params: IParams): boolean {
+ for (let i = 0; i < params.length; i++) {
+ switch (params.params[i]) {
+ case 1:
+ this._coreService.decPrivateModes.applicationCursorKeys = true;
+ break;
+ case 2:
+ this._charsetService.setgCharset(0, DEFAULT_CHARSET);
+ this._charsetService.setgCharset(1, DEFAULT_CHARSET);
+ this._charsetService.setgCharset(2, DEFAULT_CHARSET);
+ this._charsetService.setgCharset(3, DEFAULT_CHARSET);
+ // set VT100 mode here
+ break;
+ case 3:
+ /**
+ * DECCOLM - 132 column mode.
+ * This is only active if 'SetWinLines' (24) is enabled
+ * through `options.windowsOptions`.
+ */
+ if (this._optionsService.rawOptions.windowOptions.setWinLines) {
+ this._bufferService.resize(132, this._bufferService.rows);
+ this._onRequestReset.fire();
+ }
+ break;
+ case 6:
+ this._coreService.decPrivateModes.origin = true;
+ this._setCursor(0, 0);
+ break;
+ case 7:
+ this._coreService.decPrivateModes.wraparound = true;
+ break;
+ case 12:
+ // this.cursorBlink = true;
+ break;
+ case 45:
+ this._coreService.decPrivateModes.reverseWraparound = true;
+ break;
+ case 66:
+ this._logService.debug('Serial port requested application keypad.');
+ this._coreService.decPrivateModes.applicationKeypad = true;
+ this._onRequestSyncScrollBar.fire();
+ break;
+ case 9: // X10 Mouse
+ // no release, no motion, no wheel, no modifiers.
+ this._coreMouseService.activeProtocol = 'X10';
+ break;
+ case 1000: // vt200 mouse
+ // no motion.
+ this._coreMouseService.activeProtocol = 'VT200';
+ break;
+ case 1002: // button event mouse
+ this._coreMouseService.activeProtocol = 'DRAG';
+ break;
+ case 1003: // any event mouse
+ // any event - sends motion events,
+ // even if there is no button held down.
+ this._coreMouseService.activeProtocol = 'ANY';
+ break;
+ case 1004: // send focusin/focusout events
+ // focusin: ^[[I
+ // focusout: ^[[O
+ this._coreService.decPrivateModes.sendFocus = true;
+ this._onRequestSendFocus.fire();
+ break;
+ case 1005: // utf8 ext mode mouse - removed in #2507
+ this._logService.debug('DECSET 1005 not supported (see #2507)');
+ break;
+ case 1006: // sgr ext mode mouse
+ this._coreMouseService.activeEncoding = 'SGR';
+ break;
+ case 1015: // urxvt ext mode mouse - removed in #2507
+ this._logService.debug('DECSET 1015 not supported (see #2507)');
+ break;
+ case 25: // show cursor
+ this._coreService.isCursorHidden = false;
+ break;
+ case 1048: // alt screen cursor
+ this.saveCursor();
+ break;
+ case 1049: // alt screen buffer cursor
+ this.saveCursor();
+ // FALL-THROUGH
+ case 47: // alt screen buffer
+ case 1047: // alt screen buffer
+ this._bufferService.buffers.activateAltBuffer(this._eraseAttrData());
+ this._coreService.isCursorInitialized = true;
+ this._onRequestRefreshRows.fire(0, this._bufferService.rows - 1);
+ this._onRequestSyncScrollBar.fire();
+ break;
+ case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste)
+ this._coreService.decPrivateModes.bracketedPasteMode = true;
+ break;
+ }
+ }
+ return true;
+ }
+
+
+ /**
+ * CSI Pm l Reset Mode (RM).
+ * Ps = 2 -> Keyboard Action Mode (AM).
+ * Ps = 4 -> Replace Mode (IRM).
+ * Ps = 1 2 -> Send/receive (SRM).
+ * Ps = 2 0 -> Normal Linefeed (LNM).
+ *
+ * @vt: #P[Only IRM is supported.] CSI RM "Reset Mode" "CSI Pm l" "Set various terminal attributes."
+ * Supported param values by RM:
+ *
+ * | Param | Action | Support |
+ * | ----- | -------------------------------------- | ------- |
+ * | 2 | Keyboard Action Mode (KAM). Always on. | #N |
+ * | 4 | Replace Mode (IRM). (default) | #Y |
+ * | 12 | Send/receive (SRM). Always off. | #N |
+ * | 20 | Normal Linefeed (LNM). Always off. | #N |
+ *
+ *
+ * FIXME: why is LNM commented out?
+ */
+ public resetMode(params: IParams): boolean {
+ for (let i = 0; i < params.length; i++) {
+ switch (params.params[i]) {
+ case 4:
+ this._coreService.modes.insertMode = false;
+ break;
+ case 20:
+ // this._t.convertEol = false;
+ break;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * CSI ? Pm l
+ * DEC Private Mode Reset (DECRST).
+ * Ps = 1 -> Normal Cursor Keys (DECCKM).
+ * Ps = 2 -> Designate VT52 mode (DECANM).
+ * Ps = 3 -> 80 Column Mode (DECCOLM).
+ * Ps = 4 -> Jump (Fast) Scroll (DECSCLM).
+ * Ps = 5 -> Normal Video (DECSCNM).
+ * Ps = 6 -> Normal Cursor Mode (DECOM).
+ * Ps = 7 -> No Wraparound Mode (DECAWM).
+ * Ps = 8 -> No Auto-repeat Keys (DECARM).
+ * Ps = 9 -> Don't send Mouse X & Y on button press.
+ * Ps = 1 0 -> Hide toolbar (rxvt).
+ * Ps = 1 2 -> Stop Blinking Cursor (att610).
+ * Ps = 1 8 -> Don't print form feed (DECPFF).
+ * Ps = 1 9 -> Limit print to scrolling region (DECPEX).
+ * Ps = 2 5 -> Hide Cursor (DECTCEM).
+ * Ps = 3 0 -> Don't show scrollbar (rxvt).
+ * Ps = 3 5 -> Disable font-shifting functions (rxvt).
+ * Ps = 4 0 -> Disallow 80 -> 132 Mode.
+ * Ps = 4 1 -> No more(1) fix (see curses resource).
+ * Ps = 4 2 -> Disable Nation Replacement Character sets (DEC-
+ * NRCM).
+ * Ps = 4 4 -> Turn Off Margin Bell.
+ * Ps = 4 5 -> No Reverse-wraparound Mode.
+ * Ps = 4 6 -> Stop Logging. (This is normally disabled by a
+ * compile-time option).
+ * Ps = 4 7 -> Use Normal Screen Buffer.
+ * Ps = 6 6 -> Numeric keypad (DECNKM).
+ * Ps = 6 7 -> Backarrow key sends delete (DECBKM).
+ * Ps = 1 0 0 0 -> Don't send Mouse X & Y on button press and
+ * release. See the section Mouse Tracking.
+ * Ps = 1 0 0 1 -> Don't use Hilite Mouse Tracking.
+ * Ps = 1 0 0 2 -> Don't use Cell Motion Mouse Tracking.
+ * Ps = 1 0 0 3 -> Don't use All Motion Mouse Tracking.
+ * Ps = 1 0 0 4 -> Don't send FocusIn/FocusOut events.
+ * Ps = 1 0 0 5 -> Disable Extended Mouse Mode.
+ * Ps = 1 0 1 0 -> Don't scroll to bottom on tty output
+ * (rxvt).
+ * Ps = 1 0 1 1 -> Don't scroll to bottom on key press (rxvt).
+ * Ps = 1 0 3 4 -> Don't interpret "meta" key. (This disables
+ * the eightBitInput resource).
+ * Ps = 1 0 3 5 -> Disable special modifiers for Alt and Num-
+ * Lock keys. (This disables the numLock resource).
+ * Ps = 1 0 3 6 -> Don't send ESC when Meta modifies a key.
+ * (This disables the metaSendsEscape resource).
+ * Ps = 1 0 3 7 -> Send VT220 Remove from the editing-keypad
+ * Delete key.
+ * Ps = 1 0 3 9 -> Don't send ESC when Alt modifies a key.
+ * (This disables the altSendsEscape resource).
+ * Ps = 1 0 4 0 -> Do not keep selection when not highlighted.
+ * (This disables the keepSelection resource).
+ * Ps = 1 0 4 1 -> Use the PRIMARY selection. (This disables
+ * the selectToClipboard resource).
+ * Ps = 1 0 4 2 -> Disable Urgency window manager hint when
+ * Control-G is received. (This disables the bellIsUrgent
+ * resource).
+ * Ps = 1 0 4 3 -> Disable raising of the window when Control-
+ * G is received. (This disables the popOnBell resource).
+ * Ps = 1 0 4 7 -> Use Normal Screen Buffer, clearing screen
+ * first if in the Alternate Screen. (This may be disabled by
+ * the titeInhibit resource).
+ * Ps = 1 0 4 8 -> Restore cursor as in DECRC. (This may be
+ * disabled by the titeInhibit resource).
+ * Ps = 1 0 4 9 -> Use Normal Screen Buffer and restore cursor
+ * as in DECRC. (This may be disabled by the titeInhibit
+ * resource). This combines the effects of the 1 0 4 7 and 1 0
+ * 4 8 modes. Use this with terminfo-based applications rather
+ * than the 4 7 mode.
+ * Ps = 1 0 5 0 -> Reset terminfo/termcap function-key mode.
+ * Ps = 1 0 5 1 -> Reset Sun function-key mode.
+ * Ps = 1 0 5 2 -> Reset HP function-key mode.
+ * Ps = 1 0 5 3 -> Reset SCO function-key mode.
+ * Ps = 1 0 6 0 -> Reset legacy keyboard emulation (X11R6).
+ * Ps = 1 0 6 1 -> Reset keyboard emulation to Sun/PC style.
+ * Ps = 2 0 0 4 -> Reset bracketed paste mode.
+ *
+ * @vt: #P[See below for supported modes.] CSI DECRST "DEC Private Reset Mode" "CSI ? Pm l" "Reset various terminal attributes."
+ * Supported param values by DECRST:
+ *
+ * | param | Action | Support |
+ * | ----- | ------------------------------------------------------- | ------- |
+ * | 1 | Normal Cursor Keys (DECCKM). | #Y |
+ * | 2 | Designate VT52 mode (DECANM). | #N |
+ * | 3 | 80 Column Mode (DECCOLM). | #B[Switches to old column width instead of 80.] |
+ * | 6 | Normal Cursor Mode (DECOM). | #Y |
+ * | 7 | No Wraparound Mode (DECAWM). | #Y |
+ * | 8 | No Auto-repeat Keys (DECARM). | #N |
+ * | 9 | Don't send Mouse X & Y on button press. | #Y |
+ * | 12 | Stop Blinking Cursor. | #Y |
+ * | 25 | Hide Cursor (DECTCEM). | #Y |
+ * | 45 | No reverse wrap-around. | #Y |
+ * | 47 | Use Normal Screen Buffer. | #Y |
+ * | 66 | Numeric keypad (DECNKM). | #Y |
+ * | 1000 | Don't send Mouse reports. | #Y |
+ * | 1002 | Don't use Cell Motion Mouse Tracking. | #Y |
+ * | 1003 | Don't use All Motion Mouse Tracking. | #Y |
+ * | 1004 | Don't send FocusIn/FocusOut events. | #Y |
+ * | 1005 | Disable UTF-8 Mouse Mode. | #N |
+ * | 1006 | Disable SGR Mouse Mode. | #Y |
+ * | 1015 | Disable urxvt Mouse Mode. | #N |
+ * | 1047 | Use Normal Screen Buffer (clearing screen if in alt). | #Y |
+ * | 1048 | Restore cursor as in DECRC. | #Y |
+ * | 1049 | Use Normal Screen Buffer and restore cursor. | #Y |
+ * | 2004 | Reset bracketed paste mode. | #Y |
+ *
+ *
+ * FIXME: DECCOLM is currently broken (already fixed in window options PR)
+ */
+ public resetModePrivate(params: IParams): boolean {
+ for (let i = 0; i < params.length; i++) {
+ switch (params.params[i]) {
+ case 1:
+ this._coreService.decPrivateModes.applicationCursorKeys = false;
+ break;
+ case 3:
+ /**
+ * DECCOLM - 80 column mode.
+ * This is only active if 'SetWinLines' (24) is enabled
+ * through `options.windowsOptions`.
+ */
+ if (this._optionsService.rawOptions.windowOptions.setWinLines) {
+ this._bufferService.resize(80, this._bufferService.rows);
+ this._onRequestReset.fire();
+ }
+ break;
+ case 6:
+ this._coreService.decPrivateModes.origin = false;
+ this._setCursor(0, 0);
+ break;
+ case 7:
+ this._coreService.decPrivateModes.wraparound = false;
+ break;
+ case 12:
+ // this.cursorBlink = false;
+ break;
+ case 45:
+ this._coreService.decPrivateModes.reverseWraparound = false;
+ break;
+ case 66:
+ this._logService.debug('Switching back to normal keypad.');
+ this._coreService.decPrivateModes.applicationKeypad = false;
+ this._onRequestSyncScrollBar.fire();
+ break;
+ case 9: // X10 Mouse
+ case 1000: // vt200 mouse
+ case 1002: // button event mouse
+ case 1003: // any event mouse
+ this._coreMouseService.activeProtocol = 'NONE';
+ break;
+ case 1004: // send focusin/focusout events
+ this._coreService.decPrivateModes.sendFocus = false;
+ break;
+ case 1005: // utf8 ext mode mouse - removed in #2507
+ this._logService.debug('DECRST 1005 not supported (see #2507)');
+ break;
+ case 1006: // sgr ext mode mouse
+ this._coreMouseService.activeEncoding = 'DEFAULT';
+ break;
+ case 1015: // urxvt ext mode mouse - removed in #2507
+ this._logService.debug('DECRST 1015 not supported (see #2507)');
+ break;
+ case 25: // hide cursor
+ this._coreService.isCursorHidden = true;
+ break;
+ case 1048: // alt screen cursor
+ this.restoreCursor();
+ break;
+ case 1049: // alt screen buffer cursor
+ // FALL-THROUGH
+ case 47: // normal screen buffer
+ case 1047: // normal screen buffer - clearing it first
+ // Ensure the selection manager has the correct buffer
+ this._bufferService.buffers.activateNormalBuffer();
+ if (params.params[i] === 1049) {
+ this.restoreCursor();
+ }
+ this._coreService.isCursorInitialized = true;
+ this._onRequestRefreshRows.fire(0, this._bufferService.rows - 1);
+ this._onRequestSyncScrollBar.fire();
+ break;
+ case 2004: // bracketed paste mode (https://cirw.in/blog/bracketed-paste)
+ this._coreService.decPrivateModes.bracketedPasteMode = false;
+ break;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Helper to write color information packed with color mode.
+ */
+ private _updateAttrColor(color: number, mode: number, c1: number, c2: number, c3: number): number {
+ if (mode === 2) {
+ color |= Attributes.CM_RGB;
+ color &= ~Attributes.RGB_MASK;
+ color |= AttributeData.fromColorRGB([c1, c2, c3]);
+ } else if (mode === 5) {
+ color &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);
+ color |= Attributes.CM_P256 | (c1 & 0xff);
+ }
+ return color;
+ }
+
+ /**
+ * Helper to extract and apply color params/subparams.
+ * Returns advance for params index.
+ */
+ private _extractColor(params: IParams, pos: number, attr: IAttributeData): number {
+ // normalize params
+ // meaning: [target, CM, ign, val, val, val]
+ // RGB : [ 38/48, 2, ign, r, g, b]
+ // P256 : [ 38/48, 5, ign, v, ign, ign]
+ const accu = [0, 0, -1, 0, 0, 0];
+
+ // alignment placeholder for non color space sequences
+ let cSpace = 0;
+
+ // return advance we took in params
+ let advance = 0;
+
+ do {
+ accu[advance + cSpace] = params.params[pos + advance];
+ if (params.hasSubParams(pos + advance)) {
+ const subparams = params.getSubParams(pos + advance)!;
+ let i = 0;
+ do {
+ if (accu[1] === 5) {
+ cSpace = 1;
+ }
+ accu[advance + i + 1 + cSpace] = subparams[i];
+ } while (++i < subparams.length && i + advance + 1 + cSpace < accu.length);
+ break;
+ }
+ // exit early if can decide color mode with semicolons
+ if ((accu[1] === 5 && advance + cSpace >= 2)
+ || (accu[1] === 2 && advance + cSpace >= 5)) {
+ break;
+ }
+ // offset colorSpace slot for semicolon mode
+ if (accu[1]) {
+ cSpace = 1;
+ }
+ } while (++advance + pos < params.length && advance + cSpace < accu.length);
+
+ // set default values to 0
+ for (let i = 2; i < accu.length; ++i) {
+ if (accu[i] === -1) {
+ accu[i] = 0;
+ }
+ }
+
+ // apply colors
+ switch (accu[0]) {
+ case 38:
+ attr.fg = this._updateAttrColor(attr.fg, accu[1], accu[3], accu[4], accu[5]);
+ break;
+ case 48:
+ attr.bg = this._updateAttrColor(attr.bg, accu[1], accu[3], accu[4], accu[5]);
+ break;
+ case 58:
+ attr.extended = attr.extended.clone();
+ attr.extended.underlineColor = this._updateAttrColor(attr.extended.underlineColor, accu[1], accu[3], accu[4], accu[5]);
+ }
+
+ return advance;
+ }
+
+ /**
+ * SGR 4 subparams:
+ * 4:0 - equal to SGR 24 (turn off all underline)
+ * 4:1 - equal to SGR 4 (single underline)
+ * 4:2 - equal to SGR 21 (double underline)
+ * 4:3 - curly underline
+ * 4:4 - dotted underline
+ * 4:5 - dashed underline
+ */
+ private _processUnderline(style: number, attr: IAttributeData): void {
+ // treat extended attrs as immutable, thus always clone from old one
+ // this is needed since the buffer only holds references to it
+ attr.extended = attr.extended.clone();
+
+ // default to 1 == single underline
+ if (!~style || style > 5) {
+ style = 1;
+ }
+ attr.extended.underlineStyle = style;
+ attr.fg |= FgFlags.UNDERLINE;
+
+ // 0 deactivates underline
+ if (style === 0) {
+ attr.fg &= ~FgFlags.UNDERLINE;
+ }
+
+ // update HAS_EXTENDED in BG
+ attr.updateExtended();
+ }
+
+ /**
+ * CSI Pm m Character Attributes (SGR).
+ *
+ * @vt: #P[See below for supported attributes.] CSI SGR "Select Graphic Rendition" "CSI Pm m" "Set/Reset various text attributes."
+ * SGR selects one or more character attributes at the same time. Multiple params (up to 32)
+ * are applied in order from left to right. The changed attributes are applied to all new
+ * characters received. If you move characters in the viewport by scrolling or any other means,
+ * then the attributes move with the characters.
+ *
+ * Supported param values by SGR:
+ *
+ * | Param | Meaning | Support |
+ * | --------- | -------------------------------------------------------- | ------- |
+ * | 0 | Normal (default). Resets any other preceding SGR. | #Y |
+ * | 1 | Bold. (also see `options.drawBoldTextInBrightColors`) | #Y |
+ * | 2 | Faint, decreased intensity. | #Y |
+ * | 3 | Italic. | #Y |
+ * | 4 | Underlined (see below for style support). | #Y |
+ * | 5 | Slowly blinking. | #N |
+ * | 6 | Rapidly blinking. | #N |
+ * | 7 | Inverse. Flips foreground and background color. | #Y |
+ * | 8 | Invisible (hidden). | #Y |
+ * | 9 | Crossed-out characters (strikethrough). | #Y |
+ * | 21 | Doubly underlined. | #P[Currently outputs a single underline.] |
+ * | 22 | Normal (neither bold nor faint). | #Y |
+ * | 23 | No italic. | #Y |
+ * | 24 | Not underlined. | #Y |
+ * | 25 | Steady (not blinking). | #Y |
+ * | 27 | Positive (not inverse). | #Y |
+ * | 28 | Visible (not hidden). | #Y |
+ * | 29 | Not Crossed-out (strikethrough). | #Y |
+ * | 30 | Foreground color: Black. | #Y |
+ * | 31 | Foreground color: Red. | #Y |
+ * | 32 | Foreground color: Green. | #Y |
+ * | 33 | Foreground color: Yellow. | #Y |
+ * | 34 | Foreground color: Blue. | #Y |
+ * | 35 | Foreground color: Magenta. | #Y |
+ * | 36 | Foreground color: Cyan. | #Y |
+ * | 37 | Foreground color: White. | #Y |
+ * | 38 | Foreground color: Extended color. | #P[Support for RGB and indexed colors, see below.] |
+ * | 39 | Foreground color: Default (original). | #Y |
+ * | 40 | Background color: Black. | #Y |
+ * | 41 | Background color: Red. | #Y |
+ * | 42 | Background color: Green. | #Y |
+ * | 43 | Background color: Yellow. | #Y |
+ * | 44 | Background color: Blue. | #Y |
+ * | 45 | Background color: Magenta. | #Y |
+ * | 46 | Background color: Cyan. | #Y |
+ * | 47 | Background color: White. | #Y |
+ * | 48 | Background color: Extended color. | #P[Support for RGB and indexed colors, see below.] |
+ * | 49 | Background color: Default (original). | #Y |
+ * | 90 - 97 | Bright foreground color (analogous to 30 - 37). | #Y |
+ * | 100 - 107 | Bright background color (analogous to 40 - 47). | #Y |
+ *
+ * Underline supports subparams to denote the style in the form `4 : x`:
+ *
+ * | x | Meaning | Support |
+ * | ------ | ------------------------------------------------------------- | ------- |
+ * | 0 | No underline. Same as `SGR 24 m`. | #Y |
+ * | 1 | Single underline. Same as `SGR 4 m`. | #Y |
+ * | 2 | Double underline. | #P[Currently outputs a single underline.] |
+ * | 3 | Curly underline. | #P[Currently outputs a single underline.] |
+ * | 4 | Dotted underline. | #P[Currently outputs a single underline.] |
+ * | 5 | Dashed underline. | #P[Currently outputs a single underline.] |
+ * | other | Single underline. Same as `SGR 4 m`. | #Y |
+ *
+ * Extended colors are supported for foreground (Ps=38) and background (Ps=48) as follows:
+ *
+ * | Ps + 1 | Meaning | Support |
+ * | ------ | ------------------------------------------------------------- | ------- |
+ * | 0 | Implementation defined. | #N |
+ * | 1 | Transparent. | #N |
+ * | 2 | RGB color as `Ps ; 2 ; R ; G ; B` or `Ps : 2 : : R : G : B`. | #Y |
+ * | 3 | CMY color. | #N |
+ * | 4 | CMYK color. | #N |
+ * | 5 | Indexed (256 colors) as `Ps ; 5 ; INDEX` or `Ps : 5 : INDEX`. | #Y |
+ *
+ *
+ * FIXME: blinking is implemented in attrs, but not working in renderers?
+ * FIXME: remove dead branch for p=100
+ */
+ public charAttributes(params: IParams): boolean {
+ // Optimize a single SGR0.
+ if (params.length === 1 && params.params[0] === 0) {
+ this._curAttrData.fg = DEFAULT_ATTR_DATA.fg;
+ this._curAttrData.bg = DEFAULT_ATTR_DATA.bg;
+ return true;
+ }
+
+ const l = params.length;
+ let p;
+ const attr = this._curAttrData;
+
+ for (let i = 0; i < l; i++) {
+ p = params.params[i];
+ if (p >= 30 && p <= 37) {
+ // fg color 8
+ attr.fg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);
+ attr.fg |= Attributes.CM_P16 | (p - 30);
+ } else if (p >= 40 && p <= 47) {
+ // bg color 8
+ attr.bg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);
+ attr.bg |= Attributes.CM_P16 | (p - 40);
+ } else if (p >= 90 && p <= 97) {
+ // fg color 16
+ attr.fg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);
+ attr.fg |= Attributes.CM_P16 | (p - 90) | 8;
+ } else if (p >= 100 && p <= 107) {
+ // bg color 16
+ attr.bg &= ~(Attributes.CM_MASK | Attributes.PCOLOR_MASK);
+ attr.bg |= Attributes.CM_P16 | (p - 100) | 8;
+ } else if (p === 0) {
+ // default
+ attr.fg = DEFAULT_ATTR_DATA.fg;
+ attr.bg = DEFAULT_ATTR_DATA.bg;
+ } else if (p === 1) {
+ // bold text
+ attr.fg |= FgFlags.BOLD;
+ } else if (p === 3) {
+ // italic text
+ attr.bg |= BgFlags.ITALIC;
+ } else if (p === 4) {
+ // underlined text
+ attr.fg |= FgFlags.UNDERLINE;
+ this._processUnderline(params.hasSubParams(i) ? params.getSubParams(i)![0] : UnderlineStyle.SINGLE, attr);
+ } else if (p === 5) {
+ // blink
+ attr.fg |= FgFlags.BLINK;
+ } else if (p === 7) {
+ // inverse and positive
+ // test with: echo -e '\e[31m\e[42mhello\e[7mworld\e[27mhi\e[m'
+ attr.fg |= FgFlags.INVERSE;
+ } else if (p === 8) {
+ // invisible
+ attr.fg |= FgFlags.INVISIBLE;
+ } else if (p === 9) {
+ // strikethrough
+ attr.fg |= FgFlags.STRIKETHROUGH;
+ } else if (p === 2) {
+ // dimmed text
+ attr.bg |= BgFlags.DIM;
+ } else if (p === 21) {
+ // double underline
+ this._processUnderline(UnderlineStyle.DOUBLE, attr);
+ } else if (p === 22) {
+ // not bold nor faint
+ attr.fg &= ~FgFlags.BOLD;
+ attr.bg &= ~BgFlags.DIM;
+ } else if (p === 23) {
+ // not italic
+ attr.bg &= ~BgFlags.ITALIC;
+ } else if (p === 24) {
+ // not underlined
+ attr.fg &= ~FgFlags.UNDERLINE;
+ } else if (p === 25) {
+ // not blink
+ attr.fg &= ~FgFlags.BLINK;
+ } else if (p === 27) {
+ // not inverse
+ attr.fg &= ~FgFlags.INVERSE;
+ } else if (p === 28) {
+ // not invisible
+ attr.fg &= ~FgFlags.INVISIBLE;
+ } else if (p === 29) {
+ // not strikethrough
+ attr.fg &= ~FgFlags.STRIKETHROUGH;
+ } else if (p === 39) {
+ // reset fg
+ attr.fg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK);
+ attr.fg |= DEFAULT_ATTR_DATA.fg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK);
+ } else if (p === 49) {
+ // reset bg
+ attr.bg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK);
+ attr.bg |= DEFAULT_ATTR_DATA.bg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK);
+ } else if (p === 38 || p === 48 || p === 58) {
+ // fg color 256 and RGB
+ i += this._extractColor(params, i, attr);
+ } else if (p === 59) {
+ attr.extended = attr.extended.clone();
+ attr.extended.underlineColor = -1;
+ attr.updateExtended();
+ } else if (p === 100) { // FIXME: dead branch, p=100 already handled above!
+ // reset fg/bg
+ attr.fg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK);
+ attr.fg |= DEFAULT_ATTR_DATA.fg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK);
+ attr.bg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK);
+ attr.bg |= DEFAULT_ATTR_DATA.bg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK);
+ } else {
+ this._logService.debug('Unknown SGR attribute: %d.', p);
+ }
+ }
+ return true;
+ }
+
+ /**
+ * CSI Ps n Device Status Report (DSR).
+ * Ps = 5 -> Status Report. Result (``OK'') is
+ * CSI 0 n
+ * Ps = 6 -> Report Cursor Position (CPR) [row;column].
+ * Result is
+ * CSI r ; c R
+ * CSI ? Ps n
+ * Device Status Report (DSR, DEC-specific).
+ * Ps = 6 -> Report Cursor Position (CPR) [row;column] as CSI
+ * ? r ; c R (assumes page is zero).
+ * Ps = 1 5 -> Report Printer status as CSI ? 1 0 n (ready).
+ * or CSI ? 1 1 n (not ready).
+ * Ps = 2 5 -> Report UDK status as CSI ? 2 0 n (unlocked)
+ * or CSI ? 2 1 n (locked).
+ * Ps = 2 6 -> Report Keyboard status as
+ * CSI ? 2 7 ; 1 ; 0 ; 0 n (North American).
+ * The last two parameters apply to VT400 & up, and denote key-
+ * board ready and LK01 respectively.
+ * Ps = 5 3 -> Report Locator status as
+ * CSI ? 5 3 n Locator available, if compiled-in, or
+ * CSI ? 5 0 n No Locator, if not.
+ *
+ * @vt: #Y CSI DSR "Device Status Report" "CSI Ps n" "Request cursor position (CPR) with `Ps` = 6."
+ */
+ public deviceStatus(params: IParams): boolean {
+ switch (params.params[0]) {
+ case 5:
+ // status report
+ this._coreService.triggerDataEvent(`${C0.ESC}[0n`);
+ break;
+ case 6:
+ // cursor position
+ const y = this._activeBuffer.y + 1;
+ const x = this._activeBuffer.x + 1;
+ this._coreService.triggerDataEvent(`${C0.ESC}[${y};${x}R`);
+ break;
+ }
+ return true;
+ }
+
+ // @vt: #P[Only CPR is supported.] CSI DECDSR "DEC Device Status Report" "CSI ? Ps n" "Only CPR is supported (same as DSR)."
+ public deviceStatusPrivate(params: IParams): boolean {
+ // modern xterm doesnt seem to
+ // respond to any of these except ?6, 6, and 5
+ switch (params.params[0]) {
+ case 6:
+ // cursor position
+ const y = this._activeBuffer.y + 1;
+ const x = this._activeBuffer.x + 1;
+ this._coreService.triggerDataEvent(`${C0.ESC}[?${y};${x}R`);
+ break;
+ case 15:
+ // no printer
+ // this.handler(C0.ESC + '[?11n');
+ break;
+ case 25:
+ // dont support user defined keys
+ // this.handler(C0.ESC + '[?21n');
+ break;
+ case 26:
+ // north american keyboard
+ // this.handler(C0.ESC + '[?27;1;0;0n');
+ break;
+ case 53:
+ // no dec locator/mouse
+ // this.handler(C0.ESC + '[?50n');
+ break;
+ }
+ return true;
+ }
+
+ /**
+ * CSI ! p Soft terminal reset (DECSTR).
+ * http://vt100.net/docs/vt220-rm/table4-10.html
+ *
+ * @vt: #Y CSI DECSTR "Soft Terminal Reset" "CSI ! p" "Reset several terminal attributes to initial state."
+ * There are two terminal reset sequences - RIS and DECSTR. While RIS performs almost a full terminal bootstrap,
+ * DECSTR only resets certain attributes. For most needs DECSTR should be sufficient.
+ *
+ * The following terminal attributes are reset to default values:
+ * - IRM is reset (dafault = false)
+ * - scroll margins are reset (default = viewport size)
+ * - erase attributes are reset to default
+ * - charsets are reset
+ * - DECSC data is reset to initial values
+ * - DECOM is reset to absolute mode
+ *
+ *
+ * FIXME: there are several more attributes missing (see VT520 manual)
+ */
+ public softReset(params: IParams): boolean {
+ this._coreService.isCursorHidden = false;
+ this._onRequestSyncScrollBar.fire();
+ this._activeBuffer.scrollTop = 0;
+ this._activeBuffer.scrollBottom = this._bufferService.rows - 1;
+ this._curAttrData = DEFAULT_ATTR_DATA.clone();
+ this._coreService.reset();
+ this._charsetService.reset();
+
+ // reset DECSC data
+ this._activeBuffer.savedX = 0;
+ this._activeBuffer.savedY = this._activeBuffer.ybase;
+ this._activeBuffer.savedCurAttrData.fg = this._curAttrData.fg;
+ this._activeBuffer.savedCurAttrData.bg = this._curAttrData.bg;
+ this._activeBuffer.savedCharset = this._charsetService.charset;
+
+ // reset DECOM
+ this._coreService.decPrivateModes.origin = false;
+ return true;
+ }
+
+ /**
+ * CSI Ps SP q Set cursor style (DECSCUSR, VT520).
+ * Ps = 0 -> blinking block.
+ * Ps = 1 -> blinking block (default).
+ * Ps = 2 -> steady block.
+ * Ps = 3 -> blinking underline.
+ * Ps = 4 -> steady underline.
+ * Ps = 5 -> blinking bar (xterm).
+ * Ps = 6 -> steady bar (xterm).
+ *
+ * @vt: #Y CSI DECSCUSR "Set Cursor Style" "CSI Ps SP q" "Set cursor style."
+ * Supported cursor styles:
+ * - empty, 0 or 1: steady block
+ * - 2: blink block
+ * - 3: steady underline
+ * - 4: blink underline
+ * - 5: steady bar
+ * - 6: blink bar
+ */
+ public setCursorStyle(params: IParams): boolean {
+ const param = params.params[0] || 1;
+ switch (param) {
+ case 1:
+ case 2:
+ this._optionsService.options.cursorStyle = 'block';
+ break;
+ case 3:
+ case 4:
+ this._optionsService.options.cursorStyle = 'underline';
+ break;
+ case 5:
+ case 6:
+ this._optionsService.options.cursorStyle = 'bar';
+ break;
+ }
+ const isBlinking = param % 2 === 1;
+ this._optionsService.options.cursorBlink = isBlinking;
+ return true;
+ }
+
+ /**
+ * CSI Ps ; Ps r
+ * Set Scrolling Region [top;bottom] (default = full size of win-
+ * dow) (DECSTBM).
+ *
+ * @vt: #Y CSI DECSTBM "Set Top and Bottom Margin" "CSI Ps ; Ps r" "Set top and bottom margins of the viewport [top;bottom] (default = viewport size)."
+ */
+ public setScrollRegion(params: IParams): boolean {
+ const top = params.params[0] || 1;
+ let bottom: number;
+
+ if (params.length < 2 || (bottom = params.params[1]) > this._bufferService.rows || bottom === 0) {
+ bottom = this._bufferService.rows;
+ }
+
+ if (bottom > top) {
+ this._activeBuffer.scrollTop = top - 1;
+ this._activeBuffer.scrollBottom = bottom - 1;
+ this._setCursor(0, 0);
+ }
+ return true;
+ }
+
+ /**
+ * CSI Ps ; Ps ; Ps t - Various window manipulations and reports (xterm)
+ *
+ * Note: Only those listed below are supported. All others are left to integrators and
+ * need special treatment based on the embedding environment.
+ *
+ * Ps = 1 4 supported
+ * Report xterm text area size in pixels.
+ * Result is CSI 4 ; height ; width t
+ * Ps = 14 ; 2 not implemented
+ * Ps = 16 supported
+ * Report xterm character cell size in pixels.
+ * Result is CSI 6 ; height ; width t
+ * Ps = 18 supported
+ * Report the size of the text area in characters.
+ * Result is CSI 8 ; height ; width t
+ * Ps = 20 supported
+ * Report xterm window's icon label.
+ * Result is OSC L label ST
+ * Ps = 21 supported
+ * Report xterm window's title.
+ * Result is OSC l label ST
+ * Ps = 22 ; 0 -> Save xterm icon and window title on stack. supported
+ * Ps = 22 ; 1 -> Save xterm icon title on stack. supported
+ * Ps = 22 ; 2 -> Save xterm window title on stack. supported
+ * Ps = 23 ; 0 -> Restore xterm icon and window title from stack. supported
+ * Ps = 23 ; 1 -> Restore xterm icon title from stack. supported
+ * Ps = 23 ; 2 -> Restore xterm window title from stack. supported
+ * Ps >= 24 not implemented
+ */
+ public windowOptions(params: IParams): boolean {
+ if (!paramToWindowOption(params.params[0], this._optionsService.rawOptions.windowOptions)) {
+ return true;
+ }
+ const second = (params.length > 1) ? params.params[1] : 0;
+ switch (params.params[0]) {
+ case 14: // GetWinSizePixels, returns CSI 4 ; height ; width t
+ if (second !== 2) {
+ this._onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_WIN_SIZE_PIXELS);
+ }
+ break;
+ case 16: // GetCellSizePixels, returns CSI 6 ; height ; width t
+ this._onRequestWindowsOptionsReport.fire(WindowsOptionsReportType.GET_CELL_SIZE_PIXELS);
+ break;
+ case 18: // GetWinSizeChars, returns CSI 8 ; height ; width t
+ if (this._bufferService) {
+ this._coreService.triggerDataEvent(`${C0.ESC}[8;${this._bufferService.rows};${this._bufferService.cols}t`);
+ }
+ break;
+ case 22: // PushTitle
+ if (second === 0 || second === 2) {
+ this._windowTitleStack.push(this._windowTitle);
+ if (this._windowTitleStack.length > STACK_LIMIT) {
+ this._windowTitleStack.shift();
+ }
+ }
+ if (second === 0 || second === 1) {
+ this._iconNameStack.push(this._iconName);
+ if (this._iconNameStack.length > STACK_LIMIT) {
+ this._iconNameStack.shift();
+ }
+ }
+ break;
+ case 23: // PopTitle
+ if (second === 0 || second === 2) {
+ if (this._windowTitleStack.length) {
+ this.setTitle(this._windowTitleStack.pop()!);
+ }
+ }
+ if (second === 0 || second === 1) {
+ if (this._iconNameStack.length) {
+ this.setIconName(this._iconNameStack.pop()!);
+ }
+ }
+ break;
+ }
+ return true;
+ }
+
+
+ /**
+ * CSI s
+ * ESC 7
+ * Save cursor (ANSI.SYS).
+ *
+ * @vt: #P[TODO...] CSI SCOSC "Save Cursor" "CSI s" "Save cursor position, charmap and text attributes."
+ * @vt: #Y ESC SC "Save Cursor" "ESC 7" "Save cursor position, charmap and text attributes."
+ */
+ public saveCursor(params?: IParams): boolean {
+ this._activeBuffer.savedX = this._activeBuffer.x;
+ this._activeBuffer.savedY = this._activeBuffer.ybase + this._activeBuffer.y;
+ this._activeBuffer.savedCurAttrData.fg = this._curAttrData.fg;
+ this._activeBuffer.savedCurAttrData.bg = this._curAttrData.bg;
+ this._activeBuffer.savedCharset = this._charsetService.charset;
+ return true;
+ }
+
+
+ /**
+ * CSI u
+ * ESC 8
+ * Restore cursor (ANSI.SYS).
+ *
+ * @vt: #P[TODO...] CSI SCORC "Restore Cursor" "CSI u" "Restore cursor position, charmap and text attributes."
+ * @vt: #Y ESC RC "Restore Cursor" "ESC 8" "Restore cursor position, charmap and text attributes."
+ */
+ public restoreCursor(params?: IParams): boolean {
+ this._activeBuffer.x = this._activeBuffer.savedX || 0;
+ this._activeBuffer.y = Math.max(this._activeBuffer.savedY - this._activeBuffer.ybase, 0);
+ this._curAttrData.fg = this._activeBuffer.savedCurAttrData.fg;
+ this._curAttrData.bg = this._activeBuffer.savedCurAttrData.bg;
+ this._charsetService.charset = (this as any)._savedCharset;
+ if (this._activeBuffer.savedCharset) {
+ this._charsetService.charset = this._activeBuffer.savedCharset;
+ }
+ this._restrictCursor();
+ return true;
+ }
+
+
+ /**
+ * OSC 2; <data> ST (set window title)
+ * Proxy to set window title.
+ *
+ * @vt: #P[Icon name is not exposed.] OSC 0 "Set Windows Title and Icon Name" "OSC 0 ; Pt BEL" "Set window title and icon name."
+ * Icon name is not supported. For Window Title see below.
+ *
+ * @vt: #Y OSC 2 "Set Windows Title" "OSC 2 ; Pt BEL" "Set window title."
+ * xterm.js does not manipulate the title directly, instead exposes changes via the event `Terminal.onTitleChange`.
+ */
+ public setTitle(data: string): boolean {
+ this._windowTitle = data;
+ this._onTitleChange.fire(data);
+ return true;
+ }
+
+ /**
+ * OSC 1; <data> ST
+ * Note: Icon name is not exposed.
+ */
+ public setIconName(data: string): boolean {
+ this._iconName = data;
+ return true;
+ }
+
+ /**
+ * OSC 4; <num> ; <text> ST (set ANSI color <num> to <text>)
+ *
+ * @vt: #Y OSC 4 "Set ANSI color" "OSC 4 ; c ; spec BEL" "Change color number `c` to the color specified by `spec`."
+ * `c` is the color index between 0 and 255. The color format of `spec` is derived from `XParseColor` (see OSC 10 for supported formats).
+ * There may be multipe `c ; spec` pairs present in the same instruction.
+ * If `spec` contains `?` the terminal returns a sequence with the currently set color.
+ */
+ public setOrReportIndexedColor(data: string): boolean {
+ const event: IColorEvent = [];
+ const slots = data.split(';');
+ while (slots.length > 1) {
+ const idx = slots.shift() as string;
+ const spec = slots.shift() as string;
+ if (/^\d+$/.exec(idx)) {
+ const index = parseInt(idx);
+ if (0 <= index && index < 256) {
+ if (spec === '?') {
+ event.push({ type: ColorRequestType.REPORT, index });
+ } else {
+ const color = parseColor(spec);
+ if (color) {
+ event.push({ type: ColorRequestType.SET, index, color });
+ }
+ }
+ }
+ }
+ }
+ if (event.length) {
+ this._onColor.fire(event);
+ }
+ return true;
+ }
+
+ // special colors - OSC 10 | 11 | 12
+ private _specialColors = [ColorIndex.FOREGROUND, ColorIndex.BACKGROUND, ColorIndex.CURSOR];
+
+ /**
+ * Apply colors requests for special colors in OSC 10 | 11 | 12.
+ * Since these commands are stacking from multiple parameters,
+ * we handle them in a loop with an entry offset to `_specialColors`.
+ */
+ private _setOrReportSpecialColor(data: string, offset: number): boolean {
+ const slots = data.split(';');
+ for (let i = 0; i < slots.length; ++i, ++offset) {
+ if (offset >= this._specialColors.length) break;
+ if (slots[i] === '?') {
+ this._onColor.fire([{ type: ColorRequestType.REPORT, index: this._specialColors[offset] }]);
+ } else {
+ const color = parseColor(slots[i]);
+ if (color) {
+ this._onColor.fire([{ type: ColorRequestType.SET, index: this._specialColors[offset], color }]);
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * OSC 10 ; <xcolor name>|<?> ST - set or query default foreground color
+ *
+ * @vt: #Y OSC 10 "Set or query default foreground color" "OSC 10 ; Pt BEL" "Set or query default foreground color."
+ * To set the color, the following color specification formats are supported:
+ * - `rgb:<red>/<green>/<blue>` for `<red>, <green>, <blue>` in `h | hh | hhh | hhhh`, where
+ * `h` is a single hexadecimal digit (case insignificant). The different widths scale
+ * from 4 bit (`h`) to 16 bit (`hhhh`) and get converted to 8 bit (`hh`).
+ * - `#RGB` - 4 bits per channel, expanded to `#R0G0B0`
+ * - `#RRGGBB` - 8 bits per channel
+ * - `#RRRGGGBBB` - 12 bits per channel, truncated to `#RRGGBB`
+ * - `#RRRRGGGGBBBB` - 16 bits per channel, truncated to `#RRGGBB`
+ *
+ * **Note:** X11 named colors are currently unsupported.
+ *
+ * If `Pt` contains `?` instead of a color specification, the terminal
+ * returns a sequence with the current default foreground color
+ * (use that sequence to restore the color after changes).
+ *
+ * **Note:** Other than xterm, xterm.js does not support OSC 12 - 19.
+ * Therefore stacking multiple `Pt` separated by `;` only works for the first two entries.
+ */
+ public setOrReportFgColor(data: string): boolean {
+ return this._setOrReportSpecialColor(data, 0);
+ }
+
+ /**
+ * OSC 11 ; <xcolor name>|<?> ST - set or query default background color
+ *
+ * @vt: #Y OSC 11 "Set or query default background color" "OSC 11 ; Pt BEL" "Same as OSC 10, but for default background."
+ */
+ public setOrReportBgColor(data: string): boolean {
+ return this._setOrReportSpecialColor(data, 1);
+ }
+
+ /**
+ * OSC 12 ; <xcolor name>|<?> ST - set or query default cursor color
+ *
+ * @vt: #Y OSC 12 "Set or query default cursor color" "OSC 12 ; Pt BEL" "Same as OSC 10, but for default cursor color."
+ */
+ public setOrReportCursorColor(data: string): boolean {
+ return this._setOrReportSpecialColor(data, 2);
+ }
+
+ /**
+ * OSC 104 ; <num> ST - restore ANSI color <num>
+ *
+ * @vt: #Y OSC 104 "Reset ANSI color" "OSC 104 ; c BEL" "Reset color number `c` to themed color."
+ * `c` is the color index between 0 and 255. This function restores the default color for `c` as
+ * specified by the loaded theme. Any number of `c` parameters may be given.
+ * If no parameters are given, the entire indexed color table will be reset.
+ */
+ public restoreIndexedColor(data: string): boolean {
+ if (!data) {
+ this._onColor.fire([{ type: ColorRequestType.RESTORE }]);
+ return true;
+ }
+ const event: IColorEvent = [];
+ const slots = data.split(';');
+ for (let i = 0; i < slots.length; ++i) {
+ if (/^\d+$/.exec(slots[i])) {
+ const index = parseInt(slots[i]);
+ if (0 <= index && index < 256) {
+ event.push({ type: ColorRequestType.RESTORE, index });
+ }
+ }
+ }
+ if (event.length) {
+ this._onColor.fire(event);
+ }
+ return true;
+ }
+
+ /**
+ * OSC 110 ST - restore default foreground color
+ *
+ * @vt: #Y OSC 110 "Restore default foreground color" "OSC 110 BEL" "Restore default foreground to themed color."
+ */
+ public restoreFgColor(data: string): boolean {
+ this._onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.FOREGROUND }]);
+ return true;
+ }
+
+ /**
+ * OSC 111 ST - restore default background color
+ *
+ * @vt: #Y OSC 111 "Restore default background color" "OSC 111 BEL" "Restore default background to themed color."
+ */
+ public restoreBgColor(data: string): boolean {
+ this._onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.BACKGROUND }]);
+ return true;
+ }
+
+ /**
+ * OSC 112 ST - restore default cursor color
+ *
+ * @vt: #Y OSC 112 "Restore default cursor color" "OSC 112 BEL" "Restore default cursor to themed color."
+ */
+ public restoreCursorColor(data: string): boolean {
+ this._onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.CURSOR }]);
+ return true;
+ }
+
+ /**
+ * ESC E
+ * C1.NEL
+ * DEC mnemonic: NEL (https://vt100.net/docs/vt510-rm/NEL)
+ * Moves cursor to first position on next line.
+ *
+ * @vt: #Y C1 NEL "Next Line" "\x85" "Move the cursor to the beginning of the next row."
+ * @vt: #Y ESC NEL "Next Line" "ESC E" "Move the cursor to the beginning of the next row."
+ */
+ public nextLine(): boolean {
+ this._activeBuffer.x = 0;
+ this.index();
+ return true;
+ }
+
+ /**
+ * ESC =
+ * DEC mnemonic: DECKPAM (https://vt100.net/docs/vt510-rm/DECKPAM.html)
+ * Enables the numeric keypad to send application sequences to the host.
+ */
+ public keypadApplicationMode(): boolean {
+ this._logService.debug('Serial port requested application keypad.');
+ this._coreService.decPrivateModes.applicationKeypad = true;
+ this._onRequestSyncScrollBar.fire();
+ return true;
+ }
+
+ /**
+ * ESC >
+ * DEC mnemonic: DECKPNM (https://vt100.net/docs/vt510-rm/DECKPNM.html)
+ * Enables the keypad to send numeric characters to the host.
+ */
+ public keypadNumericMode(): boolean {
+ this._logService.debug('Switching back to normal keypad.');
+ this._coreService.decPrivateModes.applicationKeypad = false;
+ this._onRequestSyncScrollBar.fire();
+ return true;
+ }
+
+ /**
+ * ESC % @
+ * ESC % G
+ * Select default character set. UTF-8 is not supported (string are unicode anyways)
+ * therefore ESC % G does the same.
+ */
+ public selectDefaultCharset(): boolean {
+ this._charsetService.setgLevel(0);
+ this._charsetService.setgCharset(0, DEFAULT_CHARSET); // US (default)
+ return true;
+ }
+
+ /**
+ * ESC ( C
+ * Designate G0 Character Set, VT100, ISO 2022.
+ * ESC ) C
+ * Designate G1 Character Set (ISO 2022, VT100).
+ * ESC * C
+ * Designate G2 Character Set (ISO 2022, VT220).
+ * ESC + C
+ * Designate G3 Character Set (ISO 2022, VT220).
+ * ESC - C
+ * Designate G1 Character Set (VT300).
+ * ESC . C
+ * Designate G2 Character Set (VT300).
+ * ESC / C
+ * Designate G3 Character Set (VT300). C = A -> ISO Latin-1 Supplemental. - Supported?
+ */
+ public selectCharset(collectAndFlag: string): boolean {
+ if (collectAndFlag.length !== 2) {
+ this.selectDefaultCharset();
+ return true;
+ }
+ if (collectAndFlag[0] === '/') {
+ return true; // TODO: Is this supported?
+ }
+ this._charsetService.setgCharset(GLEVEL[collectAndFlag[0]], CHARSETS[collectAndFlag[1]] || DEFAULT_CHARSET);
+ return true;
+ }
+
+ /**
+ * ESC D
+ * C1.IND
+ * DEC mnemonic: IND (https://vt100.net/docs/vt510-rm/IND.html)
+ * Moves the cursor down one line in the same column.
+ *
+ * @vt: #Y C1 IND "Index" "\x84" "Move the cursor one line down scrolling if needed."
+ * @vt: #Y ESC IND "Index" "ESC D" "Move the cursor one line down scrolling if needed."
+ */
+ public index(): boolean {
+ this._restrictCursor();
+ this._activeBuffer.y++;
+ if (this._activeBuffer.y === this._activeBuffer.scrollBottom + 1) {
+ this._activeBuffer.y--;
+ this._bufferService.scroll(this._eraseAttrData());
+ } else if (this._activeBuffer.y >= this._bufferService.rows) {
+ this._activeBuffer.y = this._bufferService.rows - 1;
+ }
+ this._restrictCursor();
+ return true;
+ }
+
+ /**
+ * ESC H
+ * C1.HTS
+ * DEC mnemonic: HTS (https://vt100.net/docs/vt510-rm/HTS.html)
+ * Sets a horizontal tab stop at the column position indicated by
+ * the value of the active column when the terminal receives an HTS.
+ *
+ * @vt: #Y C1 HTS "Horizontal Tabulation Set" "\x88" "Places a tab stop at the current cursor position."
+ * @vt: #Y ESC HTS "Horizontal Tabulation Set" "ESC H" "Places a tab stop at the current cursor position."
+ */
+ public tabSet(): boolean {
+ this._activeBuffer.tabs[this._activeBuffer.x] = true;
+ return true;
+ }
+
+ /**
+ * ESC M
+ * C1.RI
+ * DEC mnemonic: HTS
+ * Moves the cursor up one line in the same column. If the cursor is at the top margin,
+ * the page scrolls down.
+ *
+ * @vt: #Y ESC IR "Reverse Index" "ESC M" "Move the cursor one line up scrolling if needed."
+ */
+ public reverseIndex(): boolean {
+ this._restrictCursor();
+ if (this._activeBuffer.y === this._activeBuffer.scrollTop) {
+ // possibly move the code below to term.reverseScroll();
+ // test: echo -ne '\e[1;1H\e[44m\eM\e[0m'
+ // blankLine(true) is xterm/linux behavior
+ const scrollRegionHeight = this._activeBuffer.scrollBottom - this._activeBuffer.scrollTop;
+ this._activeBuffer.lines.shiftElements(this._activeBuffer.ybase + this._activeBuffer.y, scrollRegionHeight, 1);
+ this._activeBuffer.lines.set(this._activeBuffer.ybase + this._activeBuffer.y, this._activeBuffer.getBlankLine(this._eraseAttrData()));
+ this._dirtyRowService.markRangeDirty(this._activeBuffer.scrollTop, this._activeBuffer.scrollBottom);
+ } else {
+ this._activeBuffer.y--;
+ this._restrictCursor(); // quickfix to not run out of bounds
+ }
+ return true;
+ }
+
+ /**
+ * ESC c
+ * DEC mnemonic: RIS (https://vt100.net/docs/vt510-rm/RIS.html)
+ * Reset to initial state.
+ */
+ public fullReset(): boolean {
+ this._parser.reset();
+ this._onRequestReset.fire();
+ return true;
+ }
+
+ public reset(): void {
+ this._curAttrData = DEFAULT_ATTR_DATA.clone();
+ this._eraseAttrDataInternal = DEFAULT_ATTR_DATA.clone();
+ }
+
+ /**
+ * back_color_erase feature for xterm.
+ */
+ private _eraseAttrData(): IAttributeData {
+ this._eraseAttrDataInternal.bg &= ~(Attributes.CM_MASK | 0xFFFFFF);
+ this._eraseAttrDataInternal.bg |= this._curAttrData.bg & ~0xFC000000;
+ return this._eraseAttrDataInternal;
+ }
+
+ /**
+ * ESC n
+ * ESC o
+ * ESC |
+ * ESC }
+ * ESC ~
+ * DEC mnemonic: LS (https://vt100.net/docs/vt510-rm/LS.html)
+ * When you use a locking shift, the character set remains in GL or GR until
+ * you use another locking shift. (partly supported)
+ */
+ public setgLevel(level: number): boolean {
+ this._charsetService.setgLevel(level);
+ return true;
+ }
+
+ /**
+ * ESC # 8
+ * DEC mnemonic: DECALN (https://vt100.net/docs/vt510-rm/DECALN.html)
+ * This control function fills the complete screen area with
+ * a test pattern (E) used for adjusting screen alignment.
+ *
+ * @vt: #Y ESC DECALN "Screen Alignment Pattern" "ESC # 8" "Fill viewport with a test pattern (E)."
+ */
+ public screenAlignmentPattern(): boolean {
+ // prepare cell data
+ const cell = new CellData();
+ cell.content = 1 << Content.WIDTH_SHIFT | 'E'.charCodeAt(0);
+ cell.fg = this._curAttrData.fg;
+ cell.bg = this._curAttrData.bg;
+
+
+ this._setCursor(0, 0);
+ for (let yOffset = 0; yOffset < this._bufferService.rows; ++yOffset) {
+ const row = this._activeBuffer.ybase + this._activeBuffer.y + yOffset;
+ const line = this._activeBuffer.lines.get(row);
+ if (line) {
+ line.fill(cell);
+ line.isWrapped = false;
+ }
+ }
+ this._dirtyRowService.markAllDirty();
+ this._setCursor(0, 0);
+ return true;
+ }
+}
diff --git a/node_modules/xterm/src/common/Lifecycle.ts b/node_modules/xterm/src/common/Lifecycle.ts
new file mode 100644
index 0000000..56bcfdc
--- /dev/null
+++ b/node_modules/xterm/src/common/Lifecycle.ts
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IDisposable } from 'common/Types';
+
+/**
+ * A base class that can be extended to provide convenience methods for managing the lifecycle of an
+ * object and its components.
+ */
+export abstract class Disposable implements IDisposable {
+ protected _disposables: IDisposable[] = [];
+ protected _isDisposed: boolean = false;
+
+ constructor() {
+ }
+
+ /**
+ * Disposes the object, triggering the `dispose` method on all registered IDisposables.
+ */
+ public dispose(): void {
+ this._isDisposed = true;
+ for (const d of this._disposables) {
+ d.dispose();
+ }
+ this._disposables.length = 0;
+ }
+
+ /**
+ * Registers a disposable object.
+ * @param d The disposable to register.
+ * @returns The disposable.
+ */
+ public register<T extends IDisposable>(d: T): T {
+ this._disposables.push(d);
+ return d;
+ }
+
+ /**
+ * Unregisters a disposable object if it has been registered, if not do
+ * nothing.
+ * @param d The disposable to unregister.
+ */
+ public unregister<T extends IDisposable>(d: T): void {
+ const index = this._disposables.indexOf(d);
+ if (index !== -1) {
+ this._disposables.splice(index, 1);
+ }
+ }
+}
+
+/**
+ * Dispose of all disposables in an array and set its length to 0.
+ */
+export function disposeArray(disposables: IDisposable[]): void {
+ for (const d of disposables) {
+ d.dispose();
+ }
+ disposables.length = 0;
+}
+
+/**
+ * Creates a disposable that will dispose of an array of disposables when disposed.
+ */
+export function getDisposeArrayDisposable(array: IDisposable[]): IDisposable {
+ return { dispose: () => disposeArray(array) };
+}
diff --git a/node_modules/xterm/src/common/Platform.ts b/node_modules/xterm/src/common/Platform.ts
new file mode 100644
index 0000000..7b823b1
--- /dev/null
+++ b/node_modules/xterm/src/common/Platform.ts
@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2016 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+interface INavigator {
+ userAgent: string;
+ language: string;
+ platform: string;
+}
+
+// We're declaring a navigator global here as we expect it in all runtimes (node and browser), but
+// we want this module to live in common.
+declare const navigator: INavigator;
+
+const isNode = (typeof navigator === 'undefined') ? true : false;
+const userAgent = (isNode) ? 'node' : navigator.userAgent;
+const platform = (isNode) ? 'node' : navigator.platform;
+
+export const isFirefox = userAgent.includes('Firefox');
+export const isLegacyEdge = userAgent.includes('Edge');
+export const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent);
+
+// Find the users platform. We use this to interpret the meta key
+// and ISO third level shifts.
+// http://stackoverflow.com/q/19877924/577598
+export const isMac = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'].includes(platform);
+export const isIpad = platform === 'iPad';
+export const isIphone = platform === 'iPhone';
+export const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].includes(platform);
+export const isLinux = platform.indexOf('Linux') >= 0;
diff --git a/node_modules/xterm/src/common/TypedArrayUtils.ts b/node_modules/xterm/src/common/TypedArrayUtils.ts
new file mode 100644
index 0000000..158c717
--- /dev/null
+++ b/node_modules/xterm/src/common/TypedArrayUtils.ts
@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+export type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array | Float32Array | Float64Array;
+
+
+/**
+ * polyfill for TypedArray.fill
+ * This is needed to support .fill in all safari versions and IE 11.
+ */
+export function fill<T extends TypedArray>(array: T, value: number, start?: number, end?: number): T {
+ // all modern engines that support .fill
+ if (array.fill) {
+ return array.fill(value, start, end) as T;
+ }
+ return fillFallback(array, value, start, end);
+}
+
+export function fillFallback<T extends TypedArray>(array: T, value: number, start: number = 0, end: number = array.length): T {
+ // safari and IE 11
+ // since IE 11 does not support Array.prototype.fill either
+ // we cannot use the suggested polyfill from MDN
+ // instead we simply fall back to looping
+ if (start >= array.length) {
+ return array;
+ }
+ start = (array.length + start) % array.length;
+ if (end >= array.length) {
+ end = array.length;
+ } else {
+ end = (array.length + end) % array.length;
+ }
+ for (let i = start; i < end; ++i) {
+ array[i] = value;
+ }
+ return array;
+}
+
+/**
+ * Concat two typed arrays `a` and `b`.
+ * Returns a new typed array.
+ */
+export function concat<T extends TypedArray>(a: T, b: T): T {
+ const result = new (a.constructor as any)(a.length + b.length);
+ result.set(a);
+ result.set(b, a.length);
+ return result;
+}
diff --git a/node_modules/xterm/src/common/Types.d.ts b/node_modules/xterm/src/common/Types.d.ts
new file mode 100644
index 0000000..fee426e
--- /dev/null
+++ b/node_modules/xterm/src/common/Types.d.ts
@@ -0,0 +1,483 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IFunctionIdentifier, ITerminalOptions as IPublicTerminalOptions } from 'xterm';
+import { IEvent, IEventEmitter } from 'common/EventEmitter';
+import { IDeleteEvent, IInsertEvent } from 'common/CircularList';
+import { IParams } from 'common/parser/Types';
+import { ICoreMouseService, ICoreService, IOptionsService, IUnicodeService } from 'common/services/Services';
+import { IBufferSet } from 'common/buffer/Types';
+
+export interface ICoreTerminal {
+ coreMouseService: ICoreMouseService;
+ coreService: ICoreService;
+ optionsService: IOptionsService;
+ unicodeService: IUnicodeService;
+ buffers: IBufferSet;
+ options: ITerminalOptions;
+ registerCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean | Promise<boolean>): IDisposable;
+ registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean | Promise<boolean>): IDisposable;
+ registerEscHandler(id: IFunctionIdentifier, callback: () => boolean | Promise<boolean>): IDisposable;
+ registerOscHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable;
+}
+
+export interface IDisposable {
+ dispose(): void;
+}
+
+// TODO: The options that are not in the public API should be reviewed
+export interface ITerminalOptions extends IPublicTerminalOptions {
+ [key: string]: any;
+ cancelEvents?: boolean;
+ convertEol?: boolean;
+ termName?: string;
+}
+
+export type XtermListener = (...args: any[]) => void;
+
+/**
+ * A keyboard event interface which does not depend on the DOM, KeyboardEvent implicitly extends
+ * this event.
+ */
+export interface IKeyboardEvent {
+ altKey: boolean;
+ ctrlKey: boolean;
+ shiftKey: boolean;
+ metaKey: boolean;
+ /** @deprecated See KeyboardEvent.keyCode */
+ keyCode: number;
+ key: string;
+ type: string;
+}
+
+export interface IScrollEvent {
+ position: number;
+ source: ScrollSource;
+}
+
+export const enum ScrollSource {
+ TERMINAL,
+ VIEWPORT,
+}
+
+export interface ICircularList<T> {
+ length: number;
+ maxLength: number;
+ isFull: boolean;
+
+ onDeleteEmitter: IEventEmitter<IDeleteEvent>;
+ onDelete: IEvent<IDeleteEvent>;
+ onInsertEmitter: IEventEmitter<IInsertEvent>;
+ onInsert: IEvent<IInsertEvent>;
+ onTrimEmitter: IEventEmitter<number>;
+ onTrim: IEvent<number>;
+
+ get(index: number): T | undefined;
+ set(index: number, value: T): void;
+ push(value: T): void;
+ recycle(): T;
+ pop(): T | undefined;
+ splice(start: number, deleteCount: number, ...items: T[]): void;
+ trimStart(count: number): void;
+ shiftElements(start: number, count: number, offset: number): void;
+}
+
+export const enum KeyboardResultType {
+ SEND_KEY,
+ SELECT_ALL,
+ PAGE_UP,
+ PAGE_DOWN
+}
+
+export interface IKeyboardResult {
+ type: KeyboardResultType;
+ cancel: boolean;
+ key: string | undefined;
+}
+
+export interface ICharset {
+ [key: string]: string | undefined;
+}
+
+export type CharData = [number, string, number, number];
+export type IColorRGB = [number, number, number];
+
+export interface IExtendedAttrs {
+ underlineStyle: number;
+ underlineColor: number;
+ clone(): IExtendedAttrs;
+ isEmpty(): boolean;
+}
+
+/** Attribute data */
+export interface IAttributeData {
+ fg: number;
+ bg: number;
+ extended: IExtendedAttrs;
+
+ clone(): IAttributeData;
+
+ // flags
+ isInverse(): number;
+ isBold(): number;
+ isUnderline(): number;
+ isBlink(): number;
+ isInvisible(): number;
+ isItalic(): number;
+ isDim(): number;
+ isStrikethrough(): number;
+
+ // color modes
+ getFgColorMode(): number;
+ getBgColorMode(): number;
+ isFgRGB(): boolean;
+ isBgRGB(): boolean;
+ isFgPalette(): boolean;
+ isBgPalette(): boolean;
+ isFgDefault(): boolean;
+ isBgDefault(): boolean;
+ isAttributeDefault(): boolean;
+
+ // colors
+ getFgColor(): number;
+ getBgColor(): number;
+
+ // extended attrs
+ hasExtendedAttrs(): number;
+ updateExtended(): void;
+ getUnderlineColor(): number;
+ getUnderlineColorMode(): number;
+ isUnderlineColorRGB(): boolean;
+ isUnderlineColorPalette(): boolean;
+ isUnderlineColorDefault(): boolean;
+ getUnderlineStyle(): number;
+}
+
+/** Cell data */
+export interface ICellData extends IAttributeData {
+ content: number;
+ combinedData: string;
+ isCombined(): number;
+ getWidth(): number;
+ getChars(): string;
+ getCode(): number;
+ setFromCharData(value: CharData): void;
+ getAsCharData(): CharData;
+}
+
+/**
+ * Interface for a line in the terminal buffer.
+ */
+export interface IBufferLine {
+ length: number;
+ isWrapped: boolean;
+ get(index: number): CharData;
+ set(index: number, value: CharData): void;
+ loadCell(index: number, cell: ICellData): ICellData;
+ setCell(index: number, cell: ICellData): void;
+ setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number, eAttrs: IExtendedAttrs): void;
+ addCodepointToCell(index: number, codePoint: number): void;
+ insertCells(pos: number, n: number, ch: ICellData, eraseAttr?: IAttributeData): void;
+ deleteCells(pos: number, n: number, fill: ICellData, eraseAttr?: IAttributeData): void;
+ replaceCells(start: number, end: number, fill: ICellData, eraseAttr?: IAttributeData): void;
+ resize(cols: number, fill: ICellData): void;
+ fill(fillCellData: ICellData): void;
+ copyFrom(line: IBufferLine): void;
+ clone(): IBufferLine;
+ getTrimmedLength(): number;
+ translateToString(trimRight?: boolean, startCol?: number, endCol?: number): string;
+
+ /* direct access to cell attrs */
+ getWidth(index: number): number;
+ hasWidth(index: number): number;
+ getFg(index: number): number;
+ getBg(index: number): number;
+ hasContent(index: number): number;
+ getCodePoint(index: number): number;
+ isCombined(index: number): number;
+ getString(index: number): string;
+}
+
+export interface IMarker extends IDisposable {
+ readonly id: number;
+ readonly isDisposed: boolean;
+ readonly line: number;
+ onDispose: IEvent<void>;
+}
+export interface IModes {
+ insertMode: boolean;
+}
+
+export interface IDecPrivateModes {
+ applicationCursorKeys: boolean;
+ applicationKeypad: boolean;
+ bracketedPasteMode: boolean;
+ origin: boolean;
+ reverseWraparound: boolean;
+ sendFocus: boolean;
+ wraparound: boolean; // defaults: xterm - true, vt100 - false
+}
+
+export interface IRowRange {
+ start: number;
+ end: number;
+}
+
+/**
+ * Interface for mouse events in the core.
+ */
+export const enum CoreMouseButton {
+ LEFT = 0,
+ MIDDLE = 1,
+ RIGHT = 2,
+ NONE = 3,
+ WHEEL = 4,
+ // additional buttons 1..8
+ // untested!
+ AUX1 = 8,
+ AUX2 = 9,
+ AUX3 = 10,
+ AUX4 = 11,
+ AUX5 = 12,
+ AUX6 = 13,
+ AUX7 = 14,
+ AUX8 = 15
+}
+
+export const enum CoreMouseAction {
+ UP = 0, // buttons, wheel
+ DOWN = 1, // buttons, wheel
+ LEFT = 2, // wheel only
+ RIGHT = 3, // wheel only
+ MOVE = 32 // buttons only
+}
+
+export interface ICoreMouseEvent {
+ /** column (zero based). */
+ col: number;
+ /** row (zero based). */
+ row: number;
+ /**
+ * Button the action occured. Due to restrictions of the tracking protocols
+ * it is not possible to report multiple buttons at once.
+ * Wheel is treated as a button.
+ * There are invalid combinations of buttons and actions possible
+ * (like move + wheel), those are silently ignored by the CoreMouseService.
+ */
+ button: CoreMouseButton;
+ action: CoreMouseAction;
+ /**
+ * Modifier states.
+ * Protocols will add/ignore those based on specific restrictions.
+ */
+ ctrl?: boolean;
+ alt?: boolean;
+ shift?: boolean;
+}
+
+/**
+ * CoreMouseEventType
+ * To be reported to the browser component which events a mouse
+ * protocol wants to be catched and forwarded as an ICoreMouseEvent
+ * to CoreMouseService.
+ */
+export const enum CoreMouseEventType {
+ NONE = 0,
+ /** any mousedown event */
+ DOWN = 1,
+ /** any mouseup event */
+ UP = 2,
+ /** any mousemove event while a button is held */
+ DRAG = 4,
+ /** any mousemove event without a button */
+ MOVE = 8,
+ /** any wheel event */
+ WHEEL = 16
+}
+
+/**
+ * Mouse protocol interface.
+ * A mouse protocol can be registered and activated at the CoreMouseService.
+ * `events` should contain a list of needed events as a hint for the browser component
+ * to install/remove the appropriate event handlers.
+ * `restrict` applies further protocol specific restrictions like not allowed
+ * modifiers or filtering invalid event types.
+ */
+export interface ICoreMouseProtocol {
+ events: CoreMouseEventType;
+ restrict: (e: ICoreMouseEvent) => boolean;
+}
+
+/**
+ * CoreMouseEncoding
+ * The tracking encoding can be registered and activated at the CoreMouseService.
+ * If a ICoreMouseEvent passes all procotol restrictions it will be encoded
+ * with the active encoding and sent out.
+ * Note: Returning an empty string will supress sending a mouse report,
+ * which can be used to skip creating falsey reports in limited encodings
+ * (DEFAULT only supports up to 223 1-based as coord value).
+ */
+export type CoreMouseEncoding = (event: ICoreMouseEvent) => string;
+
+/**
+ * windowOptions
+ */
+export interface IWindowOptions {
+ restoreWin?: boolean;
+ minimizeWin?: boolean;
+ setWinPosition?: boolean;
+ setWinSizePixels?: boolean;
+ raiseWin?: boolean;
+ lowerWin?: boolean;
+ refreshWin?: boolean;
+ setWinSizeChars?: boolean;
+ maximizeWin?: boolean;
+ fullscreenWin?: boolean;
+ getWinState?: boolean;
+ getWinPosition?: boolean;
+ getWinSizePixels?: boolean;
+ getScreenSizePixels?: boolean;
+ getCellSizePixels?: boolean;
+ getWinSizeChars?: boolean;
+ getScreenSizeChars?: boolean;
+ getIconTitle?: boolean;
+ getWinTitle?: boolean;
+ pushTitle?: boolean;
+ popTitle?: boolean;
+ setWinLines?: boolean;
+}
+
+// color events from common, used for OSC 4/10/11/12 and 104/110/111/112
+export const enum ColorRequestType {
+ REPORT = 0,
+ SET = 1,
+ RESTORE = 2
+}
+export const enum ColorIndex {
+ FOREGROUND = 256,
+ BACKGROUND = 257,
+ CURSOR = 258
+}
+export interface IColorReportRequest {
+ type: ColorRequestType.REPORT;
+ index: ColorIndex;
+}
+export interface IColorSetRequest {
+ type: ColorRequestType.SET;
+ index: ColorIndex;
+ color: IColorRGB;
+}
+export interface IColorRestoreRequest {
+ type: ColorRequestType.RESTORE;
+ index?: ColorIndex;
+}
+export type IColorEvent = (IColorReportRequest | IColorSetRequest | IColorRestoreRequest)[];
+
+
+/**
+ * Calls the parser and handles actions generated by the parser.
+ */
+export interface IInputHandler {
+ onTitleChange: IEvent<string>;
+
+ parse(data: string | Uint8Array, promiseResult?: boolean): void | Promise<boolean>;
+ print(data: Uint32Array, start: number, end: number): void;
+ registerCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean | Promise<boolean>): IDisposable;
+ registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean | Promise<boolean>): IDisposable;
+ registerEscHandler(id: IFunctionIdentifier, callback: () => boolean | Promise<boolean>): IDisposable;
+ registerOscHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable;
+
+ /** C0 BEL */ bell(): boolean;
+ /** C0 LF */ lineFeed(): boolean;
+ /** C0 CR */ carriageReturn(): boolean;
+ /** C0 BS */ backspace(): boolean;
+ /** C0 HT */ tab(): boolean;
+ /** C0 SO */ shiftOut(): boolean;
+ /** C0 SI */ shiftIn(): boolean;
+
+ /** CSI @ */ insertChars(params: IParams): boolean;
+ /** CSI SP @ */ scrollLeft(params: IParams): boolean;
+ /** CSI A */ cursorUp(params: IParams): boolean;
+ /** CSI SP A */ scrollRight(params: IParams): boolean;
+ /** CSI B */ cursorDown(params: IParams): boolean;
+ /** CSI C */ cursorForward(params: IParams): boolean;
+ /** CSI D */ cursorBackward(params: IParams): boolean;
+ /** CSI E */ cursorNextLine(params: IParams): boolean;
+ /** CSI F */ cursorPrecedingLine(params: IParams): boolean;
+ /** CSI G */ cursorCharAbsolute(params: IParams): boolean;
+ /** CSI H */ cursorPosition(params: IParams): boolean;
+ /** CSI I */ cursorForwardTab(params: IParams): boolean;
+ /** CSI J */ eraseInDisplay(params: IParams): boolean;
+ /** CSI K */ eraseInLine(params: IParams): boolean;
+ /** CSI L */ insertLines(params: IParams): boolean;
+ /** CSI M */ deleteLines(params: IParams): boolean;
+ /** CSI P */ deleteChars(params: IParams): boolean;
+ /** CSI S */ scrollUp(params: IParams): boolean;
+ /** CSI T */ scrollDown(params: IParams, collect?: string): boolean;
+ /** CSI X */ eraseChars(params: IParams): boolean;
+ /** CSI Z */ cursorBackwardTab(params: IParams): boolean;
+ /** CSI ` */ charPosAbsolute(params: IParams): boolean;
+ /** CSI a */ hPositionRelative(params: IParams): boolean;
+ /** CSI b */ repeatPrecedingCharacter(params: IParams): boolean;
+ /** CSI c */ sendDeviceAttributesPrimary(params: IParams): boolean;
+ /** CSI > c */ sendDeviceAttributesSecondary(params: IParams): boolean;
+ /** CSI d */ linePosAbsolute(params: IParams): boolean;
+ /** CSI e */ vPositionRelative(params: IParams): boolean;
+ /** CSI f */ hVPosition(params: IParams): boolean;
+ /** CSI g */ tabClear(params: IParams): boolean;
+ /** CSI h */ setMode(params: IParams, collect?: string): boolean;
+ /** CSI l */ resetMode(params: IParams, collect?: string): boolean;
+ /** CSI m */ charAttributes(params: IParams): boolean;
+ /** CSI n */ deviceStatus(params: IParams, collect?: string): boolean;
+ /** CSI p */ softReset(params: IParams, collect?: string): boolean;
+ /** CSI q */ setCursorStyle(params: IParams, collect?: string): boolean;
+ /** CSI r */ setScrollRegion(params: IParams, collect?: string): boolean;
+ /** CSI s */ saveCursor(params: IParams): boolean;
+ /** CSI u */ restoreCursor(params: IParams): boolean;
+ /** CSI ' } */ insertColumns(params: IParams): boolean;
+ /** CSI ' ~ */ deleteColumns(params: IParams): boolean;
+
+ /** OSC 0
+ OSC 2 */ setTitle(data: string): boolean;
+ /** OSC 4 */ setOrReportIndexedColor(data: string): boolean;
+ /** OSC 10 */ setOrReportFgColor(data: string): boolean;
+ /** OSC 11 */ setOrReportBgColor(data: string): boolean;
+ /** OSC 12 */ setOrReportCursorColor(data: string): boolean;
+ /** OSC 104 */ restoreIndexedColor(data: string): boolean;
+ /** OSC 110 */ restoreFgColor(data: string): boolean;
+ /** OSC 111 */ restoreBgColor(data: string): boolean;
+ /** OSC 112 */ restoreCursorColor(data: string): boolean;
+
+ /** ESC E */ nextLine(): boolean;
+ /** ESC = */ keypadApplicationMode(): boolean;
+ /** ESC > */ keypadNumericMode(): boolean;
+ /** ESC % G
+ ESC % @ */ selectDefaultCharset(): boolean;
+ /** ESC ( C
+ ESC ) C
+ ESC * C
+ ESC + C
+ ESC - C
+ ESC . C
+ ESC / C */ selectCharset(collectAndFlag: string): boolean;
+ /** ESC D */ index(): boolean;
+ /** ESC H */ tabSet(): boolean;
+ /** ESC M */ reverseIndex(): boolean;
+ /** ESC c */ fullReset(): boolean;
+ /** ESC n
+ ESC o
+ ESC |
+ ESC }
+ ESC ~ */ setgLevel(level: number): boolean;
+ /** ESC # 8 */ screenAlignmentPattern(): boolean;
+}
+
+interface IParseStack {
+ paused: boolean;
+ cursorStartX: number;
+ cursorStartY: number;
+ decodedLength: number;
+ position: number;
+}
diff --git a/node_modules/xterm/src/common/WindowsMode.ts b/node_modules/xterm/src/common/WindowsMode.ts
new file mode 100644
index 0000000..7cff094
--- /dev/null
+++ b/node_modules/xterm/src/common/WindowsMode.ts
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CODE } from 'common/buffer/Constants';
+import { IBufferService } from 'common/services/Services';
+
+export function updateWindowsModeWrappedState(bufferService: IBufferService): void {
+ // Winpty does not support wraparound mode which means that lines will never
+ // be marked as wrapped. This causes issues for things like copying a line
+ // retaining the wrapped new line characters or if consumers are listening
+ // in on the data stream.
+ //
+ // The workaround for this is to listen to every incoming line feed and mark
+ // the line as wrapped if the last character in the previous line is not a
+ // space. This is certainly not without its problems, but generally on
+ // Windows when text reaches the end of the terminal it's likely going to be
+ // wrapped.
+ const line = bufferService.buffer.lines.get(bufferService.buffer.ybase + bufferService.buffer.y - 1);
+ const lastChar = line?.get(bufferService.cols - 1);
+
+ const nextLine = bufferService.buffer.lines.get(bufferService.buffer.ybase + bufferService.buffer.y);
+ if (nextLine && lastChar) {
+ nextLine.isWrapped = (lastChar[CHAR_DATA_CODE_INDEX] !== NULL_CELL_CODE && lastChar[CHAR_DATA_CODE_INDEX] !== WHITESPACE_CELL_CODE);
+ }
+}
diff --git a/node_modules/xterm/src/common/buffer/AttributeData.ts b/node_modules/xterm/src/common/buffer/AttributeData.ts
new file mode 100644
index 0000000..43d378e
--- /dev/null
+++ b/node_modules/xterm/src/common/buffer/AttributeData.ts
@@ -0,0 +1,148 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IAttributeData, IColorRGB, IExtendedAttrs } from 'common/Types';
+import { Attributes, FgFlags, BgFlags, UnderlineStyle } from 'common/buffer/Constants';
+
+export class AttributeData implements IAttributeData {
+ public static toColorRGB(value: number): IColorRGB {
+ return [
+ value >>> Attributes.RED_SHIFT & 255,
+ value >>> Attributes.GREEN_SHIFT & 255,
+ value & 255
+ ];
+ }
+
+ public static fromColorRGB(value: IColorRGB): number {
+ return (value[0] & 255) << Attributes.RED_SHIFT | (value[1] & 255) << Attributes.GREEN_SHIFT | value[2] & 255;
+ }
+
+ public clone(): IAttributeData {
+ const newObj = new AttributeData();
+ newObj.fg = this.fg;
+ newObj.bg = this.bg;
+ newObj.extended = this.extended.clone();
+ return newObj;
+ }
+
+ // data
+ public fg = 0;
+ public bg = 0;
+ public extended = new ExtendedAttrs();
+
+ // flags
+ public isInverse(): number { return this.fg & FgFlags.INVERSE; }
+ public isBold(): number { return this.fg & FgFlags.BOLD; }
+ public isUnderline(): number { return this.fg & FgFlags.UNDERLINE; }
+ public isBlink(): number { return this.fg & FgFlags.BLINK; }
+ public isInvisible(): number { return this.fg & FgFlags.INVISIBLE; }
+ public isItalic(): number { return this.bg & BgFlags.ITALIC; }
+ public isDim(): number { return this.bg & BgFlags.DIM; }
+ public isStrikethrough(): number { return this.fg & FgFlags.STRIKETHROUGH; }
+
+ // color modes
+ public getFgColorMode(): number { return this.fg & Attributes.CM_MASK; }
+ public getBgColorMode(): number { return this.bg & Attributes.CM_MASK; }
+ public isFgRGB(): boolean { return (this.fg & Attributes.CM_MASK) === Attributes.CM_RGB; }
+ public isBgRGB(): boolean { return (this.bg & Attributes.CM_MASK) === Attributes.CM_RGB; }
+ public isFgPalette(): boolean { return (this.fg & Attributes.CM_MASK) === Attributes.CM_P16 || (this.fg & Attributes.CM_MASK) === Attributes.CM_P256; }
+ public isBgPalette(): boolean { return (this.bg & Attributes.CM_MASK) === Attributes.CM_P16 || (this.bg & Attributes.CM_MASK) === Attributes.CM_P256; }
+ public isFgDefault(): boolean { return (this.fg & Attributes.CM_MASK) === 0; }
+ public isBgDefault(): boolean { return (this.bg & Attributes.CM_MASK) === 0; }
+ public isAttributeDefault(): boolean { return this.fg === 0 && this.bg === 0; }
+
+ // colors
+ public getFgColor(): number {
+ switch (this.fg & Attributes.CM_MASK) {
+ case Attributes.CM_P16:
+ case Attributes.CM_P256: return this.fg & Attributes.PCOLOR_MASK;
+ case Attributes.CM_RGB: return this.fg & Attributes.RGB_MASK;
+ default: return -1; // CM_DEFAULT defaults to -1
+ }
+ }
+ public getBgColor(): number {
+ switch (this.bg & Attributes.CM_MASK) {
+ case Attributes.CM_P16:
+ case Attributes.CM_P256: return this.bg & Attributes.PCOLOR_MASK;
+ case Attributes.CM_RGB: return this.bg & Attributes.RGB_MASK;
+ default: return -1; // CM_DEFAULT defaults to -1
+ }
+ }
+
+ // extended attrs
+ public hasExtendedAttrs(): number {
+ return this.bg & BgFlags.HAS_EXTENDED;
+ }
+ public updateExtended(): void {
+ if (this.extended.isEmpty()) {
+ this.bg &= ~BgFlags.HAS_EXTENDED;
+ } else {
+ this.bg |= BgFlags.HAS_EXTENDED;
+ }
+ }
+ public getUnderlineColor(): number {
+ if ((this.bg & BgFlags.HAS_EXTENDED) && ~this.extended.underlineColor) {
+ switch (this.extended.underlineColor & Attributes.CM_MASK) {
+ case Attributes.CM_P16:
+ case Attributes.CM_P256: return this.extended.underlineColor & Attributes.PCOLOR_MASK;
+ case Attributes.CM_RGB: return this.extended.underlineColor & Attributes.RGB_MASK;
+ default: return this.getFgColor();
+ }
+ }
+ return this.getFgColor();
+ }
+ public getUnderlineColorMode(): number {
+ return (this.bg & BgFlags.HAS_EXTENDED) && ~this.extended.underlineColor
+ ? this.extended.underlineColor & Attributes.CM_MASK
+ : this.getFgColorMode();
+ }
+ public isUnderlineColorRGB(): boolean {
+ return (this.bg & BgFlags.HAS_EXTENDED) && ~this.extended.underlineColor
+ ? (this.extended.underlineColor & Attributes.CM_MASK) === Attributes.CM_RGB
+ : this.isFgRGB();
+ }
+ public isUnderlineColorPalette(): boolean {
+ return (this.bg & BgFlags.HAS_EXTENDED) && ~this.extended.underlineColor
+ ? (this.extended.underlineColor & Attributes.CM_MASK) === Attributes.CM_P16
+ || (this.extended.underlineColor & Attributes.CM_MASK) === Attributes.CM_P256
+ : this.isFgPalette();
+ }
+ public isUnderlineColorDefault(): boolean {
+ return (this.bg & BgFlags.HAS_EXTENDED) && ~this.extended.underlineColor
+ ? (this.extended.underlineColor & Attributes.CM_MASK) === 0
+ : this.isFgDefault();
+ }
+ public getUnderlineStyle(): UnderlineStyle {
+ return this.fg & FgFlags.UNDERLINE
+ ? (this.bg & BgFlags.HAS_EXTENDED ? this.extended.underlineStyle : UnderlineStyle.SINGLE)
+ : UnderlineStyle.NONE;
+ }
+}
+
+
+/**
+ * Extended attributes for a cell.
+ * Holds information about different underline styles and color.
+ */
+export class ExtendedAttrs implements IExtendedAttrs {
+ constructor(
+ // underline style, NONE is empty
+ public underlineStyle: UnderlineStyle = UnderlineStyle.NONE,
+ // underline color, -1 is empty (same as FG)
+ public underlineColor: number = -1
+ ) {}
+
+ public clone(): IExtendedAttrs {
+ return new ExtendedAttrs(this.underlineStyle, this.underlineColor);
+ }
+
+ /**
+ * Convenient method to indicate whether the object holds no additional information,
+ * that needs to be persistant in the buffer.
+ */
+ public isEmpty(): boolean {
+ return this.underlineStyle === UnderlineStyle.NONE;
+ }
+}
diff --git a/node_modules/xterm/src/common/buffer/Buffer.ts b/node_modules/xterm/src/common/buffer/Buffer.ts
new file mode 100644
index 0000000..02ce7c8
--- /dev/null
+++ b/node_modules/xterm/src/common/buffer/Buffer.ts
@@ -0,0 +1,681 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { CircularList, IInsertEvent } from 'common/CircularList';
+import { IBuffer, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult } from 'common/buffer/Types';
+import { IBufferLine, ICellData, IAttributeData, ICharset } from 'common/Types';
+import { BufferLine, DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
+import { CellData } from 'common/buffer/CellData';
+import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_CHAR_INDEX } from 'common/buffer/Constants';
+import { reflowLargerApplyNewLayout, reflowLargerCreateNewLayout, reflowLargerGetLinesToRemove, reflowSmallerGetNewLineLengths, getWrappedLineTrimmedLength } from 'common/buffer/BufferReflow';
+import { Marker } from 'common/buffer/Marker';
+import { IOptionsService, IBufferService } from 'common/services/Services';
+import { DEFAULT_CHARSET } from 'common/data/Charsets';
+import { ExtendedAttrs } from 'common/buffer/AttributeData';
+
+export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1
+
+/**
+ * This class represents a terminal buffer (an internal state of the terminal), where the
+ * following information is stored (in high-level):
+ * - text content of this particular buffer
+ * - cursor position
+ * - scroll position
+ */
+export class Buffer implements IBuffer {
+ public lines: CircularList<IBufferLine>;
+ public ydisp: number = 0;
+ public ybase: number = 0;
+ public y: number = 0;
+ public x: number = 0;
+ public scrollBottom: number;
+ public scrollTop: number;
+ // TODO: Type me
+ public tabs: any;
+ public savedY: number = 0;
+ public savedX: number = 0;
+ public savedCurAttrData = DEFAULT_ATTR_DATA.clone();
+ public savedCharset: ICharset | undefined = DEFAULT_CHARSET;
+ public markers: Marker[] = [];
+ private _nullCell: ICellData = CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
+ private _whitespaceCell: ICellData = CellData.fromCharData([0, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_WIDTH, WHITESPACE_CELL_CODE]);
+ private _cols: number;
+ private _rows: number;
+
+ constructor(
+ private _hasScrollback: boolean,
+ private _optionsService: IOptionsService,
+ private _bufferService: IBufferService
+ ) {
+ this._cols = this._bufferService.cols;
+ this._rows = this._bufferService.rows;
+ this.lines = new CircularList<IBufferLine>(this._getCorrectBufferLength(this._rows));
+ this.scrollTop = 0;
+ this.scrollBottom = this._rows - 1;
+ this.setupTabStops();
+ }
+
+ public getNullCell(attr?: IAttributeData): ICellData {
+ if (attr) {
+ this._nullCell.fg = attr.fg;
+ this._nullCell.bg = attr.bg;
+ this._nullCell.extended = attr.extended;
+ } else {
+ this._nullCell.fg = 0;
+ this._nullCell.bg = 0;
+ this._nullCell.extended = new ExtendedAttrs();
+ }
+ return this._nullCell;
+ }
+
+ public getWhitespaceCell(attr?: IAttributeData): ICellData {
+ if (attr) {
+ this._whitespaceCell.fg = attr.fg;
+ this._whitespaceCell.bg = attr.bg;
+ this._whitespaceCell.extended = attr.extended;
+ } else {
+ this._whitespaceCell.fg = 0;
+ this._whitespaceCell.bg = 0;
+ this._whitespaceCell.extended = new ExtendedAttrs();
+ }
+ return this._whitespaceCell;
+ }
+
+ public getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine {
+ return new BufferLine(this._bufferService.cols, this.getNullCell(attr), isWrapped);
+ }
+
+ public get hasScrollback(): boolean {
+ return this._hasScrollback && this.lines.maxLength > this._rows;
+ }
+
+ public get isCursorInViewport(): boolean {
+ const absoluteY = this.ybase + this.y;
+ const relativeY = absoluteY - this.ydisp;
+ return (relativeY >= 0 && relativeY < this._rows);
+ }
+
+ /**
+ * Gets the correct buffer length based on the rows provided, the terminal's
+ * scrollback and whether this buffer is flagged to have scrollback or not.
+ * @param rows The terminal rows to use in the calculation.
+ */
+ private _getCorrectBufferLength(rows: number): number {
+ if (!this._hasScrollback) {
+ return rows;
+ }
+
+ const correctBufferLength = rows + this._optionsService.rawOptions.scrollback;
+
+ return correctBufferLength > MAX_BUFFER_SIZE ? MAX_BUFFER_SIZE : correctBufferLength;
+ }
+
+ /**
+ * Fills the buffer's viewport with blank lines.
+ */
+ public fillViewportRows(fillAttr?: IAttributeData): void {
+ if (this.lines.length === 0) {
+ if (fillAttr === undefined) {
+ fillAttr = DEFAULT_ATTR_DATA;
+ }
+ let i = this._rows;
+ while (i--) {
+ this.lines.push(this.getBlankLine(fillAttr));
+ }
+ }
+ }
+
+ /**
+ * Clears the buffer to it's initial state, discarding all previous data.
+ */
+ public clear(): void {
+ this.ydisp = 0;
+ this.ybase = 0;
+ this.y = 0;
+ this.x = 0;
+ this.lines = new CircularList<IBufferLine>(this._getCorrectBufferLength(this._rows));
+ this.scrollTop = 0;
+ this.scrollBottom = this._rows - 1;
+ this.setupTabStops();
+ }
+
+ /**
+ * Resizes the buffer, adjusting its data accordingly.
+ * @param newCols The new number of columns.
+ * @param newRows The new number of rows.
+ */
+ public resize(newCols: number, newRows: number): void {
+ // store reference to null cell with default attrs
+ const nullCell = this.getNullCell(DEFAULT_ATTR_DATA);
+
+ // Increase max length if needed before adjustments to allow space to fill
+ // as required.
+ const newMaxLength = this._getCorrectBufferLength(newRows);
+ if (newMaxLength > this.lines.maxLength) {
+ this.lines.maxLength = newMaxLength;
+ }
+
+ // The following adjustments should only happen if the buffer has been
+ // initialized/filled.
+ if (this.lines.length > 0) {
+ // Deal with columns increasing (reducing needs to happen after reflow)
+ if (this._cols < newCols) {
+ for (let i = 0; i < this.lines.length; i++) {
+ this.lines.get(i)!.resize(newCols, nullCell);
+ }
+ }
+
+ // Resize rows in both directions as needed
+ let addToY = 0;
+ if (this._rows < newRows) {
+ for (let y = this._rows; y < newRows; y++) {
+ if (this.lines.length < newRows + this.ybase) {
+ if (this._optionsService.rawOptions.windowsMode) {
+ // Just add the new missing rows on Windows as conpty reprints the screen with it's
+ // view of the world. Once a line enters scrollback for conpty it remains there
+ this.lines.push(new BufferLine(newCols, nullCell));
+ } else {
+ if (this.ybase > 0 && this.lines.length <= this.ybase + this.y + addToY + 1) {
+ // There is room above the buffer and there are no empty elements below the line,
+ // scroll up
+ this.ybase--;
+ addToY++;
+ if (this.ydisp > 0) {
+ // Viewport is at the top of the buffer, must increase downwards
+ this.ydisp--;
+ }
+ } else {
+ // Add a blank line if there is no buffer left at the top to scroll to, or if there
+ // are blank lines after the cursor
+ this.lines.push(new BufferLine(newCols, nullCell));
+ }
+ }
+ }
+ }
+ } else { // (this._rows >= newRows)
+ for (let y = this._rows; y > newRows; y--) {
+ if (this.lines.length > newRows + this.ybase) {
+ if (this.lines.length > this.ybase + this.y + 1) {
+ // The line is a blank line below the cursor, remove it
+ this.lines.pop();
+ } else {
+ // The line is the cursor, scroll down
+ this.ybase++;
+ this.ydisp++;
+ }
+ }
+ }
+ }
+
+ // Reduce max length if needed after adjustments, this is done after as it
+ // would otherwise cut data from the bottom of the buffer.
+ if (newMaxLength < this.lines.maxLength) {
+ // Trim from the top of the buffer and adjust ybase and ydisp.
+ const amountToTrim = this.lines.length - newMaxLength;
+ if (amountToTrim > 0) {
+ this.lines.trimStart(amountToTrim);
+ this.ybase = Math.max(this.ybase - amountToTrim, 0);
+ this.ydisp = Math.max(this.ydisp - amountToTrim, 0);
+ this.savedY = Math.max(this.savedY - amountToTrim, 0);
+ }
+ this.lines.maxLength = newMaxLength;
+ }
+
+ // Make sure that the cursor stays on screen
+ this.x = Math.min(this.x, newCols - 1);
+ this.y = Math.min(this.y, newRows - 1);
+ if (addToY) {
+ this.y += addToY;
+ }
+ this.savedX = Math.min(this.savedX, newCols - 1);
+
+ this.scrollTop = 0;
+ }
+
+ this.scrollBottom = newRows - 1;
+
+ if (this._isReflowEnabled) {
+ this._reflow(newCols, newRows);
+
+ // Trim the end of the line off if cols shrunk
+ if (this._cols > newCols) {
+ for (let i = 0; i < this.lines.length; i++) {
+ this.lines.get(i)!.resize(newCols, nullCell);
+ }
+ }
+ }
+
+ this._cols = newCols;
+ this._rows = newRows;
+ }
+
+ private get _isReflowEnabled(): boolean {
+ return this._hasScrollback && !this._optionsService.rawOptions.windowsMode;
+ }
+
+ private _reflow(newCols: number, newRows: number): void {
+ if (this._cols === newCols) {
+ return;
+ }
+
+ // Iterate through rows, ignore the last one as it cannot be wrapped
+ if (newCols > this._cols) {
+ this._reflowLarger(newCols, newRows);
+ } else {
+ this._reflowSmaller(newCols, newRows);
+ }
+ }
+
+ private _reflowLarger(newCols: number, newRows: number): void {
+ const toRemove: number[] = reflowLargerGetLinesToRemove(this.lines, this._cols, newCols, this.ybase + this.y, this.getNullCell(DEFAULT_ATTR_DATA));
+ if (toRemove.length > 0) {
+ const newLayoutResult = reflowLargerCreateNewLayout(this.lines, toRemove);
+ reflowLargerApplyNewLayout(this.lines, newLayoutResult.layout);
+ this._reflowLargerAdjustViewport(newCols, newRows, newLayoutResult.countRemoved);
+ }
+ }
+
+ private _reflowLargerAdjustViewport(newCols: number, newRows: number, countRemoved: number): void {
+ const nullCell = this.getNullCell(DEFAULT_ATTR_DATA);
+ // Adjust viewport based on number of items removed
+ let viewportAdjustments = countRemoved;
+ while (viewportAdjustments-- > 0) {
+ if (this.ybase === 0) {
+ if (this.y > 0) {
+ this.y--;
+ }
+ if (this.lines.length < newRows) {
+ // Add an extra row at the bottom of the viewport
+ this.lines.push(new BufferLine(newCols, nullCell));
+ }
+ } else {
+ if (this.ydisp === this.ybase) {
+ this.ydisp--;
+ }
+ this.ybase--;
+ }
+ }
+ this.savedY = Math.max(this.savedY - countRemoved, 0);
+ }
+
+ private _reflowSmaller(newCols: number, newRows: number): void {
+ const nullCell = this.getNullCell(DEFAULT_ATTR_DATA);
+ // Gather all BufferLines that need to be inserted into the Buffer here so that they can be
+ // batched up and only committed once
+ const toInsert = [];
+ let countToInsert = 0;
+ // Go backwards as many lines may be trimmed and this will avoid considering them
+ for (let y = this.lines.length - 1; y >= 0; y--) {
+ // Check whether this line is a problem
+ let nextLine = this.lines.get(y) as BufferLine;
+ if (!nextLine || !nextLine.isWrapped && nextLine.getTrimmedLength() <= newCols) {
+ continue;
+ }
+
+ // Gather wrapped lines and adjust y to be the starting line
+ const wrappedLines: BufferLine[] = [nextLine];
+ while (nextLine.isWrapped && y > 0) {
+ nextLine = this.lines.get(--y) as BufferLine;
+ wrappedLines.unshift(nextLine);
+ }
+
+ // If these lines contain the cursor don't touch them, the program will handle fixing up
+ // wrapped lines with the cursor
+ const absoluteY = this.ybase + this.y;
+ if (absoluteY >= y && absoluteY < y + wrappedLines.length) {
+ continue;
+ }
+
+ const lastLineLength = wrappedLines[wrappedLines.length - 1].getTrimmedLength();
+ const destLineLengths = reflowSmallerGetNewLineLengths(wrappedLines, this._cols, newCols);
+ const linesToAdd = destLineLengths.length - wrappedLines.length;
+ let trimmedLines: number;
+ if (this.ybase === 0 && this.y !== this.lines.length - 1) {
+ // If the top section of the buffer is not yet filled
+ trimmedLines = Math.max(0, this.y - this.lines.maxLength + linesToAdd);
+ } else {
+ trimmedLines = Math.max(0, this.lines.length - this.lines.maxLength + linesToAdd);
+ }
+
+ // Add the new lines
+ const newLines: BufferLine[] = [];
+ for (let i = 0; i < linesToAdd; i++) {
+ const newLine = this.getBlankLine(DEFAULT_ATTR_DATA, true) as BufferLine;
+ newLines.push(newLine);
+ }
+ if (newLines.length > 0) {
+ toInsert.push({
+ // countToInsert here gets the actual index, taking into account other inserted items.
+ // using this we can iterate through the list forwards
+ start: y + wrappedLines.length + countToInsert,
+ newLines
+ });
+ countToInsert += newLines.length;
+ }
+ wrappedLines.push(...newLines);
+
+ // Copy buffer data to new locations, this needs to happen backwards to do in-place
+ let destLineIndex = destLineLengths.length - 1; // Math.floor(cellsNeeded / newCols);
+ let destCol = destLineLengths[destLineIndex]; // cellsNeeded % newCols;
+ if (destCol === 0) {
+ destLineIndex--;
+ destCol = destLineLengths[destLineIndex];
+ }
+ let srcLineIndex = wrappedLines.length - linesToAdd - 1;
+ let srcCol = lastLineLength;
+ while (srcLineIndex >= 0) {
+ const cellsToCopy = Math.min(srcCol, destCol);
+ if (wrappedLines[destLineIndex] === undefined) {
+ // Sanity check that the line exists, this has been known to fail for an unknown reason
+ // which would stop the reflow from happening if an exception would throw.
+ break;
+ }
+ wrappedLines[destLineIndex].copyCellsFrom(wrappedLines[srcLineIndex], srcCol - cellsToCopy, destCol - cellsToCopy, cellsToCopy, true);
+ destCol -= cellsToCopy;
+ if (destCol === 0) {
+ destLineIndex--;
+ destCol = destLineLengths[destLineIndex];
+ }
+ srcCol -= cellsToCopy;
+ if (srcCol === 0) {
+ srcLineIndex--;
+ const wrappedLinesIndex = Math.max(srcLineIndex, 0);
+ srcCol = getWrappedLineTrimmedLength(wrappedLines, wrappedLinesIndex, this._cols);
+ }
+ }
+
+ // Null out the end of the line ends if a wide character wrapped to the following line
+ for (let i = 0; i < wrappedLines.length; i++) {
+ if (destLineLengths[i] < newCols) {
+ wrappedLines[i].setCell(destLineLengths[i], nullCell);
+ }
+ }
+
+ // Adjust viewport as needed
+ let viewportAdjustments = linesToAdd - trimmedLines;
+ while (viewportAdjustments-- > 0) {
+ if (this.ybase === 0) {
+ if (this.y < newRows - 1) {
+ this.y++;
+ this.lines.pop();
+ } else {
+ this.ybase++;
+ this.ydisp++;
+ }
+ } else {
+ // Ensure ybase does not exceed its maximum value
+ if (this.ybase < Math.min(this.lines.maxLength, this.lines.length + countToInsert) - newRows) {
+ if (this.ybase === this.ydisp) {
+ this.ydisp++;
+ }
+ this.ybase++;
+ }
+ }
+ }
+ this.savedY = Math.min(this.savedY + linesToAdd, this.ybase + newRows - 1);
+ }
+
+ // Rearrange lines in the buffer if there are any insertions, this is done at the end rather
+ // than earlier so that it's a single O(n) pass through the buffer, instead of O(n^2) from many
+ // costly calls to CircularList.splice.
+ if (toInsert.length > 0) {
+ // Record buffer insert events and then play them back backwards so that the indexes are
+ // correct
+ const insertEvents: IInsertEvent[] = [];
+
+ // Record original lines so they don't get overridden when we rearrange the list
+ const originalLines: BufferLine[] = [];
+ for (let i = 0; i < this.lines.length; i++) {
+ originalLines.push(this.lines.get(i) as BufferLine);
+ }
+ const originalLinesLength = this.lines.length;
+
+ let originalLineIndex = originalLinesLength - 1;
+ let nextToInsertIndex = 0;
+ let nextToInsert = toInsert[nextToInsertIndex];
+ this.lines.length = Math.min(this.lines.maxLength, this.lines.length + countToInsert);
+ let countInsertedSoFar = 0;
+ for (let i = Math.min(this.lines.maxLength - 1, originalLinesLength + countToInsert - 1); i >= 0; i--) {
+ if (nextToInsert && nextToInsert.start > originalLineIndex + countInsertedSoFar) {
+ // Insert extra lines here, adjusting i as needed
+ for (let nextI = nextToInsert.newLines.length - 1; nextI >= 0; nextI--) {
+ this.lines.set(i--, nextToInsert.newLines[nextI]);
+ }
+ i++;
+
+ // Create insert events for later
+ insertEvents.push({
+ index: originalLineIndex + 1,
+ amount: nextToInsert.newLines.length
+ });
+
+ countInsertedSoFar += nextToInsert.newLines.length;
+ nextToInsert = toInsert[++nextToInsertIndex];
+ } else {
+ this.lines.set(i, originalLines[originalLineIndex--]);
+ }
+ }
+
+ // Update markers
+ let insertCountEmitted = 0;
+ for (let i = insertEvents.length - 1; i >= 0; i--) {
+ insertEvents[i].index += insertCountEmitted;
+ this.lines.onInsertEmitter.fire(insertEvents[i]);
+ insertCountEmitted += insertEvents[i].amount;
+ }
+ const amountToTrim = Math.max(0, originalLinesLength + countToInsert - this.lines.maxLength);
+ if (amountToTrim > 0) {
+ this.lines.onTrimEmitter.fire(amountToTrim);
+ }
+ }
+ }
+
+ // private _reflowSmallerGetLinesNeeded()
+
+ /**
+ * Translates a string index back to a BufferIndex.
+ * To get the correct buffer position the string must start at `startCol` 0
+ * (default in translateBufferLineToString).
+ * The method also works on wrapped line strings given rows were not trimmed.
+ * The method operates on the CharData string length, there are no
+ * additional content or boundary checks. Therefore the string and the buffer
+ * should not be altered in between.
+ * TODO: respect trim flag after fixing #1685
+ * @param lineIndex line index the string was retrieved from
+ * @param stringIndex index within the string
+ * @param startCol column offset the string was retrieved from
+ */
+ public stringIndexToBufferIndex(lineIndex: number, stringIndex: number, trimRight: boolean = false): BufferIndex {
+ while (stringIndex) {
+ const line = this.lines.get(lineIndex);
+ if (!line) {
+ return [-1, -1];
+ }
+ const length = (trimRight) ? line.getTrimmedLength() : line.length;
+ for (let i = 0; i < length; ++i) {
+ if (line.get(i)[CHAR_DATA_WIDTH_INDEX]) {
+ // empty cells report a string length of 0, but get replaced
+ // with a whitespace in translateToString, thus replace with 1
+ stringIndex -= line.get(i)[CHAR_DATA_CHAR_INDEX].length || 1;
+ }
+ if (stringIndex < 0) {
+ return [lineIndex, i];
+ }
+ }
+ lineIndex++;
+ }
+ return [lineIndex, 0];
+ }
+
+ /**
+ * Translates a buffer line to a string, with optional start and end columns.
+ * Wide characters will count as two columns in the resulting string. This
+ * function is useful for getting the actual text underneath the raw selection
+ * position.
+ * @param line The line being translated.
+ * @param trimRight Whether to trim whitespace to the right.
+ * @param startCol The column to start at.
+ * @param endCol The column to end at.
+ */
+ public translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol: number = 0, endCol?: number): string {
+ const line = this.lines.get(lineIndex);
+ if (!line) {
+ return '';
+ }
+ return line.translateToString(trimRight, startCol, endCol);
+ }
+
+ public getWrappedRangeForLine(y: number): { first: number, last: number } {
+ let first = y;
+ let last = y;
+ // Scan upwards for wrapped lines
+ while (first > 0 && this.lines.get(first)!.isWrapped) {
+ first--;
+ }
+ // Scan downwards for wrapped lines
+ while (last + 1 < this.lines.length && this.lines.get(last + 1)!.isWrapped) {
+ last++;
+ }
+ return { first, last };
+ }
+
+ /**
+ * Setup the tab stops.
+ * @param i The index to start setting up tab stops from.
+ */
+ public setupTabStops(i?: number): void {
+ if (i !== null && i !== undefined) {
+ if (!this.tabs[i]) {
+ i = this.prevStop(i);
+ }
+ } else {
+ this.tabs = {};
+ i = 0;
+ }
+
+ for (; i < this._cols; i += this._optionsService.rawOptions.tabStopWidth) {
+ this.tabs[i] = true;
+ }
+ }
+
+ /**
+ * Move the cursor to the previous tab stop from the given position (default is current).
+ * @param x The position to move the cursor to the previous tab stop.
+ */
+ public prevStop(x?: number): number {
+ if (x === null || x === undefined) {
+ x = this.x;
+ }
+ while (!this.tabs[--x] && x > 0);
+ return x >= this._cols ? this._cols - 1 : x < 0 ? 0 : x;
+ }
+
+ /**
+ * Move the cursor one tab stop forward from the given position (default is current).
+ * @param x The position to move the cursor one tab stop forward.
+ */
+ public nextStop(x?: number): number {
+ if (x === null || x === undefined) {
+ x = this.x;
+ }
+ while (!this.tabs[++x] && x < this._cols);
+ return x >= this._cols ? this._cols - 1 : x < 0 ? 0 : x;
+ }
+
+ public addMarker(y: number): Marker {
+ const marker = new Marker(y);
+ this.markers.push(marker);
+ marker.register(this.lines.onTrim(amount => {
+ marker.line -= amount;
+ // The marker should be disposed when the line is trimmed from the buffer
+ if (marker.line < 0) {
+ marker.dispose();
+ }
+ }));
+ marker.register(this.lines.onInsert(event => {
+ if (marker.line >= event.index) {
+ marker.line += event.amount;
+ }
+ }));
+ marker.register(this.lines.onDelete(event => {
+ // Delete the marker if it's within the range
+ if (marker.line >= event.index && marker.line < event.index + event.amount) {
+ marker.dispose();
+ }
+
+ // Shift the marker if it's after the deleted range
+ if (marker.line > event.index) {
+ marker.line -= event.amount;
+ }
+ }));
+ marker.register(marker.onDispose(() => this._removeMarker(marker)));
+ return marker;
+ }
+
+ private _removeMarker(marker: Marker): void {
+ this.markers.splice(this.markers.indexOf(marker), 1);
+ }
+
+ public iterator(trimRight: boolean, startIndex?: number, endIndex?: number, startOverscan?: number, endOverscan?: number): IBufferStringIterator {
+ return new BufferStringIterator(this, trimRight, startIndex, endIndex, startOverscan, endOverscan);
+ }
+}
+
+/**
+ * Iterator to get unwrapped content strings from the buffer.
+ * The iterator returns at least the string data between the borders
+ * `startIndex` and `endIndex` (exclusive) and will expand the lines
+ * by `startOverscan` to the top and by `endOverscan` to the bottom,
+ * if no new line was found in between.
+ * It will never read/return string data beyond `startIndex - startOverscan`
+ * or `endIndex + endOverscan`. Therefore the first and last line might be truncated.
+ * It is possible to always get the full string for the first and last line as well
+ * by setting the overscan values to the actual buffer length. This not recommended
+ * since it might return the whole buffer within a single string in a worst case scenario.
+ */
+export class BufferStringIterator implements IBufferStringIterator {
+ private _current: number;
+
+ constructor (
+ private _buffer: IBuffer,
+ private _trimRight: boolean,
+ private _startIndex: number = 0,
+ private _endIndex: number = _buffer.lines.length,
+ private _startOverscan: number = 0,
+ private _endOverscan: number = 0
+ ) {
+ if (this._startIndex < 0) {
+ this._startIndex = 0;
+ }
+ if (this._endIndex > this._buffer.lines.length) {
+ this._endIndex = this._buffer.lines.length;
+ }
+ this._current = this._startIndex;
+ }
+
+ public hasNext(): boolean {
+ return this._current < this._endIndex;
+ }
+
+ public next(): IBufferStringIteratorResult {
+ const range = this._buffer.getWrappedRangeForLine(this._current);
+ // limit search window to overscan value at both borders
+ if (range.first < this._startIndex - this._startOverscan) {
+ range.first = this._startIndex - this._startOverscan;
+ }
+ if (range.last > this._endIndex + this._endOverscan) {
+ range.last = this._endIndex + this._endOverscan;
+ }
+ // limit to current buffer length
+ range.first = Math.max(range.first, 0);
+ range.last = Math.min(range.last, this._buffer.lines.length);
+ let content = '';
+ for (let i = range.first; i <= range.last; ++i) {
+ content += this._buffer.translateBufferLineToString(i, this._trimRight);
+ }
+ this._current = range.last + 1;
+ return { range, content };
+ }
+}
diff --git a/node_modules/xterm/src/common/buffer/BufferLine.ts b/node_modules/xterm/src/common/buffer/BufferLine.ts
new file mode 100644
index 0000000..f0bf4fc
--- /dev/null
+++ b/node_modules/xterm/src/common/buffer/BufferLine.ts
@@ -0,0 +1,441 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { CharData, IBufferLine, ICellData, IAttributeData, IExtendedAttrs } from 'common/Types';
+import { stringFromCodePoint } from 'common/input/TextDecoder';
+import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Content, BgFlags } from 'common/buffer/Constants';
+import { CellData } from 'common/buffer/CellData';
+import { AttributeData, ExtendedAttrs } from 'common/buffer/AttributeData';
+
+/**
+ * buffer memory layout:
+ *
+ * | uint32_t | uint32_t | uint32_t |
+ * | `content` | `FG` | `BG` |
+ * | wcwidth(2) comb(1) codepoint(21) | flags(8) R(8) G(8) B(8) | flags(8) R(8) G(8) B(8) |
+ */
+
+
+/** typed array slots taken by one cell */
+const CELL_SIZE = 3;
+
+/**
+ * Cell member indices.
+ *
+ * Direct access:
+ * `content = data[column * CELL_SIZE + Cell.CONTENT];`
+ * `fg = data[column * CELL_SIZE + Cell.FG];`
+ * `bg = data[column * CELL_SIZE + Cell.BG];`
+ */
+const enum Cell {
+ CONTENT = 0,
+ FG = 1, // currently simply holds all known attrs
+ BG = 2 // currently unused
+}
+
+export const DEFAULT_ATTR_DATA = Object.freeze(new AttributeData());
+
+/**
+ * Typed array based bufferline implementation.
+ *
+ * There are 2 ways to insert data into the cell buffer:
+ * - `setCellFromCodepoint` + `addCodepointToCell`
+ * Use these for data that is already UTF32.
+ * Used during normal input in `InputHandler` for faster buffer access.
+ * - `setCell`
+ * This method takes a CellData object and stores the data in the buffer.
+ * Use `CellData.fromCharData` to create the CellData object (e.g. from JS string).
+ *
+ * To retrieve data from the buffer use either one of the primitive methods
+ * (if only one particular value is needed) or `loadCell`. For `loadCell` in a loop
+ * memory allocs / GC pressure can be greatly reduced by reusing the CellData object.
+ */
+export class BufferLine implements IBufferLine {
+ protected _data: Uint32Array;
+ protected _combined: {[index: number]: string} = {};
+ protected _extendedAttrs: {[index: number]: ExtendedAttrs} = {};
+ public length: number;
+
+ constructor(cols: number, fillCellData?: ICellData, public isWrapped: boolean = false) {
+ this._data = new Uint32Array(cols * CELL_SIZE);
+ const cell = fillCellData || CellData.fromCharData([0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE]);
+ for (let i = 0; i < cols; ++i) {
+ this.setCell(i, cell);
+ }
+ this.length = cols;
+ }
+
+ /**
+ * Get cell data CharData.
+ * @deprecated
+ */
+ public get(index: number): CharData {
+ const content = this._data[index * CELL_SIZE + Cell.CONTENT];
+ const cp = content & Content.CODEPOINT_MASK;
+ return [
+ this._data[index * CELL_SIZE + Cell.FG],
+ (content & Content.IS_COMBINED_MASK)
+ ? this._combined[index]
+ : (cp) ? stringFromCodePoint(cp) : '',
+ content >> Content.WIDTH_SHIFT,
+ (content & Content.IS_COMBINED_MASK)
+ ? this._combined[index].charCodeAt(this._combined[index].length - 1)
+ : cp
+ ];
+ }
+
+ /**
+ * Set cell data from CharData.
+ * @deprecated
+ */
+ public set(index: number, value: CharData): void {
+ this._data[index * CELL_SIZE + Cell.FG] = value[CHAR_DATA_ATTR_INDEX];
+ if (value[CHAR_DATA_CHAR_INDEX].length > 1) {
+ this._combined[index] = value[1];
+ this._data[index * CELL_SIZE + Cell.CONTENT] = index | Content.IS_COMBINED_MASK | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
+ } else {
+ this._data[index * CELL_SIZE + Cell.CONTENT] = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
+ }
+ }
+
+ /**
+ * primitive getters
+ * use these when only one value is needed, otherwise use `loadCell`
+ */
+ public getWidth(index: number): number {
+ return this._data[index * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT;
+ }
+
+ /** Test whether content has width. */
+ public hasWidth(index: number): number {
+ return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.WIDTH_MASK;
+ }
+
+ /** Get FG cell component. */
+ public getFg(index: number): number {
+ return this._data[index * CELL_SIZE + Cell.FG];
+ }
+
+ /** Get BG cell component. */
+ public getBg(index: number): number {
+ return this._data[index * CELL_SIZE + Cell.BG];
+ }
+
+ /**
+ * Test whether contains any chars.
+ * Basically an empty has no content, but other cells might differ in FG/BG
+ * from real empty cells.
+ * */
+ public hasContent(index: number): number {
+ return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK;
+ }
+
+ /**
+ * Get codepoint of the cell.
+ * To be in line with `code` in CharData this either returns
+ * a single UTF32 codepoint or the last codepoint of a combined string.
+ */
+ public getCodePoint(index: number): number {
+ const content = this._data[index * CELL_SIZE + Cell.CONTENT];
+ if (content & Content.IS_COMBINED_MASK) {
+ return this._combined[index].charCodeAt(this._combined[index].length - 1);
+ }
+ return content & Content.CODEPOINT_MASK;
+ }
+
+ /** Test whether the cell contains a combined string. */
+ public isCombined(index: number): number {
+ return this._data[index * CELL_SIZE + Cell.CONTENT] & Content.IS_COMBINED_MASK;
+ }
+
+ /** Returns the string content of the cell. */
+ public getString(index: number): string {
+ const content = this._data[index * CELL_SIZE + Cell.CONTENT];
+ if (content & Content.IS_COMBINED_MASK) {
+ return this._combined[index];
+ }
+ if (content & Content.CODEPOINT_MASK) {
+ return stringFromCodePoint(content & Content.CODEPOINT_MASK);
+ }
+ // return empty string for empty cells
+ return '';
+ }
+
+ /**
+ * Load data at `index` into `cell`. This is used to access cells in a way that's more friendly
+ * to GC as it significantly reduced the amount of new objects/references needed.
+ */
+ public loadCell(index: number, cell: ICellData): ICellData {
+ const startIndex = index * CELL_SIZE;
+ cell.content = this._data[startIndex + Cell.CONTENT];
+ cell.fg = this._data[startIndex + Cell.FG];
+ cell.bg = this._data[startIndex + Cell.BG];
+ if (cell.content & Content.IS_COMBINED_MASK) {
+ cell.combinedData = this._combined[index];
+ }
+ if (cell.bg & BgFlags.HAS_EXTENDED) {
+ cell.extended = this._extendedAttrs[index];
+ }
+ return cell;
+ }
+
+ /**
+ * Set data at `index` to `cell`.
+ */
+ public setCell(index: number, cell: ICellData): void {
+ if (cell.content & Content.IS_COMBINED_MASK) {
+ this._combined[index] = cell.combinedData;
+ }
+ if (cell.bg & BgFlags.HAS_EXTENDED) {
+ this._extendedAttrs[index] = cell.extended;
+ }
+ this._data[index * CELL_SIZE + Cell.CONTENT] = cell.content;
+ this._data[index * CELL_SIZE + Cell.FG] = cell.fg;
+ this._data[index * CELL_SIZE + Cell.BG] = cell.bg;
+ }
+
+ /**
+ * Set cell data from input handler.
+ * Since the input handler see the incoming chars as UTF32 codepoints,
+ * it gets an optimized access method.
+ */
+ public setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number, eAttrs: IExtendedAttrs): void {
+ if (bg & BgFlags.HAS_EXTENDED) {
+ this._extendedAttrs[index] = eAttrs;
+ }
+ this._data[index * CELL_SIZE + Cell.CONTENT] = codePoint | (width << Content.WIDTH_SHIFT);
+ this._data[index * CELL_SIZE + Cell.FG] = fg;
+ this._data[index * CELL_SIZE + Cell.BG] = bg;
+ }
+
+ /**
+ * Add a codepoint to a cell from input handler.
+ * During input stage combining chars with a width of 0 follow and stack
+ * onto a leading char. Since we already set the attrs
+ * by the previous `setDataFromCodePoint` call, we can omit it here.
+ */
+ public addCodepointToCell(index: number, codePoint: number): void {
+ let content = this._data[index * CELL_SIZE + Cell.CONTENT];
+ if (content & Content.IS_COMBINED_MASK) {
+ // we already have a combined string, simply add
+ this._combined[index] += stringFromCodePoint(codePoint);
+ } else {
+ if (content & Content.CODEPOINT_MASK) {
+ // normal case for combining chars:
+ // - move current leading char + new one into combined string
+ // - set combined flag
+ this._combined[index] = stringFromCodePoint(content & Content.CODEPOINT_MASK) + stringFromCodePoint(codePoint);
+ content &= ~Content.CODEPOINT_MASK; // set codepoint in buffer to 0
+ content |= Content.IS_COMBINED_MASK;
+ } else {
+ // should not happen - we actually have no data in the cell yet
+ // simply set the data in the cell buffer with a width of 1
+ content = codePoint | (1 << Content.WIDTH_SHIFT);
+ }
+ this._data[index * CELL_SIZE + Cell.CONTENT] = content;
+ }
+ }
+
+ public insertCells(pos: number, n: number, fillCellData: ICellData, eraseAttr?: IAttributeData): void {
+ pos %= this.length;
+
+ // handle fullwidth at pos: reset cell one to the left if pos is second cell of a wide char
+ if (pos && this.getWidth(pos - 1) === 2) {
+ this.setCellFromCodePoint(pos - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs());
+ }
+
+ if (n < this.length - pos) {
+ const cell = new CellData();
+ for (let i = this.length - pos - n - 1; i >= 0; --i) {
+ this.setCell(pos + n + i, this.loadCell(pos + i, cell));
+ }
+ for (let i = 0; i < n; ++i) {
+ this.setCell(pos + i, fillCellData);
+ }
+ } else {
+ for (let i = pos; i < this.length; ++i) {
+ this.setCell(i, fillCellData);
+ }
+ }
+
+ // handle fullwidth at line end: reset last cell if it is first cell of a wide char
+ if (this.getWidth(this.length - 1) === 2) {
+ this.setCellFromCodePoint(this.length - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs());
+ }
+ }
+
+ public deleteCells(pos: number, n: number, fillCellData: ICellData, eraseAttr?: IAttributeData): void {
+ pos %= this.length;
+ if (n < this.length - pos) {
+ const cell = new CellData();
+ for (let i = 0; i < this.length - pos - n; ++i) {
+ this.setCell(pos + i, this.loadCell(pos + n + i, cell));
+ }
+ for (let i = this.length - n; i < this.length; ++i) {
+ this.setCell(i, fillCellData);
+ }
+ } else {
+ for (let i = pos; i < this.length; ++i) {
+ this.setCell(i, fillCellData);
+ }
+ }
+
+ // handle fullwidth at pos:
+ // - reset pos-1 if wide char
+ // - reset pos if width==0 (previous second cell of a wide char)
+ if (pos && this.getWidth(pos - 1) === 2) {
+ this.setCellFromCodePoint(pos - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs());
+ }
+ if (this.getWidth(pos) === 0 && !this.hasContent(pos)) {
+ this.setCellFromCodePoint(pos, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs());
+ }
+ }
+
+ public replaceCells(start: number, end: number, fillCellData: ICellData, eraseAttr?: IAttributeData): void {
+ // handle fullwidth at start: reset cell one to the left if start is second cell of a wide char
+ if (start && this.getWidth(start - 1) === 2) {
+ this.setCellFromCodePoint(start - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs());
+ }
+ // handle fullwidth at last cell + 1: reset to empty cell if it is second part of a wide char
+ if (end < this.length && this.getWidth(end - 1) === 2) {
+ this.setCellFromCodePoint(end, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0, eraseAttr?.extended || new ExtendedAttrs());
+ }
+
+ while (start < end && start < this.length) {
+ this.setCell(start++, fillCellData);
+ }
+ }
+
+ public resize(cols: number, fillCellData: ICellData): void {
+ if (cols === this.length) {
+ return;
+ }
+ if (cols > this.length) {
+ const data = new Uint32Array(cols * CELL_SIZE);
+ if (this.length) {
+ if (cols * CELL_SIZE < this._data.length) {
+ data.set(this._data.subarray(0, cols * CELL_SIZE));
+ } else {
+ data.set(this._data);
+ }
+ }
+ this._data = data;
+ for (let i = this.length; i < cols; ++i) {
+ this.setCell(i, fillCellData);
+ }
+ } else {
+ if (cols) {
+ const data = new Uint32Array(cols * CELL_SIZE);
+ data.set(this._data.subarray(0, cols * CELL_SIZE));
+ this._data = data;
+ // Remove any cut off combined data, FIXME: repeat this for extended attrs
+ const keys = Object.keys(this._combined);
+ for (let i = 0; i < keys.length; i++) {
+ const key = parseInt(keys[i], 10);
+ if (key >= cols) {
+ delete this._combined[key];
+ }
+ }
+ } else {
+ this._data = new Uint32Array(0);
+ this._combined = {};
+ }
+ }
+ this.length = cols;
+ }
+
+ /** fill a line with fillCharData */
+ public fill(fillCellData: ICellData): void {
+ this._combined = {};
+ this._extendedAttrs = {};
+ for (let i = 0; i < this.length; ++i) {
+ this.setCell(i, fillCellData);
+ }
+ }
+
+ /** alter to a full copy of line */
+ public copyFrom(line: BufferLine): void {
+ if (this.length !== line.length) {
+ this._data = new Uint32Array(line._data);
+ } else {
+ // use high speed copy if lengths are equal
+ this._data.set(line._data);
+ }
+ this.length = line.length;
+ this._combined = {};
+ for (const el in line._combined) {
+ this._combined[el] = line._combined[el];
+ }
+ this._extendedAttrs = {};
+ for (const el in line._extendedAttrs) {
+ this._extendedAttrs[el] = line._extendedAttrs[el];
+ }
+ this.isWrapped = line.isWrapped;
+ }
+
+ /** create a new clone */
+ public clone(): IBufferLine {
+ const newLine = new BufferLine(0);
+ newLine._data = new Uint32Array(this._data);
+ newLine.length = this.length;
+ for (const el in this._combined) {
+ newLine._combined[el] = this._combined[el];
+ }
+ for (const el in this._extendedAttrs) {
+ newLine._extendedAttrs[el] = this._extendedAttrs[el];
+ }
+ newLine.isWrapped = this.isWrapped;
+ return newLine;
+ }
+
+ public getTrimmedLength(): number {
+ for (let i = this.length - 1; i >= 0; --i) {
+ if ((this._data[i * CELL_SIZE + Cell.CONTENT] & Content.HAS_CONTENT_MASK)) {
+ return i + (this._data[i * CELL_SIZE + Cell.CONTENT] >> Content.WIDTH_SHIFT);
+ }
+ }
+ return 0;
+ }
+
+ public copyCellsFrom(src: BufferLine, srcCol: number, destCol: number, length: number, applyInReverse: boolean): void {
+ const srcData = src._data;
+ if (applyInReverse) {
+ for (let cell = length - 1; cell >= 0; cell--) {
+ for (let i = 0; i < CELL_SIZE; i++) {
+ this._data[(destCol + cell) * CELL_SIZE + i] = srcData[(srcCol + cell) * CELL_SIZE + i];
+ }
+ }
+ } else {
+ for (let cell = 0; cell < length; cell++) {
+ for (let i = 0; i < CELL_SIZE; i++) {
+ this._data[(destCol + cell) * CELL_SIZE + i] = srcData[(srcCol + cell) * CELL_SIZE + i];
+ }
+ }
+ }
+
+ // Move any combined data over as needed, FIXME: repeat for extended attrs
+ const srcCombinedKeys = Object.keys(src._combined);
+ for (let i = 0; i < srcCombinedKeys.length; i++) {
+ const key = parseInt(srcCombinedKeys[i], 10);
+ if (key >= srcCol) {
+ this._combined[key - srcCol + destCol] = src._combined[key];
+ }
+ }
+ }
+
+ public translateToString(trimRight: boolean = false, startCol: number = 0, endCol: number = this.length): string {
+ if (trimRight) {
+ endCol = Math.min(endCol, this.getTrimmedLength());
+ }
+ let result = '';
+ while (startCol < endCol) {
+ const content = this._data[startCol * CELL_SIZE + Cell.CONTENT];
+ const cp = content & Content.CODEPOINT_MASK;
+ result += (content & Content.IS_COMBINED_MASK) ? this._combined[startCol] : (cp) ? stringFromCodePoint(cp) : WHITESPACE_CELL_CHAR;
+ startCol += (content >> Content.WIDTH_SHIFT) || 1; // always advance by 1
+ }
+ return result;
+ }
+}
diff --git a/node_modules/xterm/src/common/buffer/BufferRange.ts b/node_modules/xterm/src/common/buffer/BufferRange.ts
new file mode 100644
index 0000000..a49cf48
--- /dev/null
+++ b/node_modules/xterm/src/common/buffer/BufferRange.ts
@@ -0,0 +1,13 @@
+/**
+ * Copyright (c) 2021 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IBufferRange } from 'xterm';
+
+export function getRangeLength(range: IBufferRange, bufferCols: number): number {
+ if (range.start.y > range.end.y) {
+ throw new Error(`Buffer range end (${range.end.x}, ${range.end.y}) cannot be before start (${range.start.x}, ${range.start.y})`);
+ }
+ return bufferCols * (range.end.y - range.start.y) + (range.end.x - range.start.x + 1);
+}
diff --git a/node_modules/xterm/src/common/buffer/BufferReflow.ts b/node_modules/xterm/src/common/buffer/BufferReflow.ts
new file mode 100644
index 0000000..ece9a96
--- /dev/null
+++ b/node_modules/xterm/src/common/buffer/BufferReflow.ts
@@ -0,0 +1,220 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { BufferLine } from 'common/buffer/BufferLine';
+import { CircularList } from 'common/CircularList';
+import { IBufferLine, ICellData } from 'common/Types';
+
+export interface INewLayoutResult {
+ layout: number[];
+ countRemoved: number;
+}
+
+/**
+ * Evaluates and returns indexes to be removed after a reflow larger occurs. Lines will be removed
+ * when a wrapped line unwraps.
+ * @param lines The buffer lines.
+ * @param newCols The columns after resize.
+ */
+export function reflowLargerGetLinesToRemove(lines: CircularList<IBufferLine>, oldCols: number, newCols: number, bufferAbsoluteY: number, nullCell: ICellData): number[] {
+ // Gather all BufferLines that need to be removed from the Buffer here so that they can be
+ // batched up and only committed once
+ const toRemove: number[] = [];
+
+ for (let y = 0; y < lines.length - 1; y++) {
+ // Check if this row is wrapped
+ let i = y;
+ let nextLine = lines.get(++i) as BufferLine;
+ if (!nextLine.isWrapped) {
+ continue;
+ }
+
+ // Check how many lines it's wrapped for
+ const wrappedLines: BufferLine[] = [lines.get(y) as BufferLine];
+ while (i < lines.length && nextLine.isWrapped) {
+ wrappedLines.push(nextLine);
+ nextLine = lines.get(++i) as BufferLine;
+ }
+
+ // If these lines contain the cursor don't touch them, the program will handle fixing up wrapped
+ // lines with the cursor
+ if (bufferAbsoluteY >= y && bufferAbsoluteY < i) {
+ y += wrappedLines.length - 1;
+ continue;
+ }
+
+ // Copy buffer data to new locations
+ let destLineIndex = 0;
+ let destCol = getWrappedLineTrimmedLength(wrappedLines, destLineIndex, oldCols);
+ let srcLineIndex = 1;
+ let srcCol = 0;
+ while (srcLineIndex < wrappedLines.length) {
+ const srcTrimmedTineLength = getWrappedLineTrimmedLength(wrappedLines, srcLineIndex, oldCols);
+ const srcRemainingCells = srcTrimmedTineLength - srcCol;
+ const destRemainingCells = newCols - destCol;
+ const cellsToCopy = Math.min(srcRemainingCells, destRemainingCells);
+
+ wrappedLines[destLineIndex].copyCellsFrom(wrappedLines[srcLineIndex], srcCol, destCol, cellsToCopy, false);
+
+ destCol += cellsToCopy;
+ if (destCol === newCols) {
+ destLineIndex++;
+ destCol = 0;
+ }
+ srcCol += cellsToCopy;
+ if (srcCol === srcTrimmedTineLength) {
+ srcLineIndex++;
+ srcCol = 0;
+ }
+
+ // Make sure the last cell isn't wide, if it is copy it to the current dest
+ if (destCol === 0 && destLineIndex !== 0) {
+ if (wrappedLines[destLineIndex - 1].getWidth(newCols - 1) === 2) {
+ wrappedLines[destLineIndex].copyCellsFrom(wrappedLines[destLineIndex - 1], newCols - 1, destCol++, 1, false);
+ // Null out the end of the last row
+ wrappedLines[destLineIndex - 1].setCell(newCols - 1, nullCell);
+ }
+ }
+ }
+
+ // Clear out remaining cells or fragments could remain;
+ wrappedLines[destLineIndex].replaceCells(destCol, newCols, nullCell);
+
+ // Work backwards and remove any rows at the end that only contain null cells
+ let countToRemove = 0;
+ for (let i = wrappedLines.length - 1; i > 0; i--) {
+ if (i > destLineIndex || wrappedLines[i].getTrimmedLength() === 0) {
+ countToRemove++;
+ } else {
+ break;
+ }
+ }
+
+ if (countToRemove > 0) {
+ toRemove.push(y + wrappedLines.length - countToRemove); // index
+ toRemove.push(countToRemove);
+ }
+
+ y += wrappedLines.length - 1;
+ }
+ return toRemove;
+}
+
+/**
+ * Creates and return the new layout for lines given an array of indexes to be removed.
+ * @param lines The buffer lines.
+ * @param toRemove The indexes to remove.
+ */
+export function reflowLargerCreateNewLayout(lines: CircularList<IBufferLine>, toRemove: number[]): INewLayoutResult {
+ const layout: number[] = [];
+ // First iterate through the list and get the actual indexes to use for rows
+ let nextToRemoveIndex = 0;
+ let nextToRemoveStart = toRemove[nextToRemoveIndex];
+ let countRemovedSoFar = 0;
+ for (let i = 0; i < lines.length; i++) {
+ if (nextToRemoveStart === i) {
+ const countToRemove = toRemove[++nextToRemoveIndex];
+
+ // Tell markers that there was a deletion
+ lines.onDeleteEmitter.fire({
+ index: i - countRemovedSoFar,
+ amount: countToRemove
+ });
+
+ i += countToRemove - 1;
+ countRemovedSoFar += countToRemove;
+ nextToRemoveStart = toRemove[++nextToRemoveIndex];
+ } else {
+ layout.push(i);
+ }
+ }
+ return {
+ layout,
+ countRemoved: countRemovedSoFar
+ };
+}
+
+/**
+ * Applies a new layout to the buffer. This essentially does the same as many splice calls but it's
+ * done all at once in a single iteration through the list since splice is very expensive.
+ * @param lines The buffer lines.
+ * @param newLayout The new layout to apply.
+ */
+export function reflowLargerApplyNewLayout(lines: CircularList<IBufferLine>, newLayout: number[]): void {
+ // Record original lines so they don't get overridden when we rearrange the list
+ const newLayoutLines: BufferLine[] = [];
+ for (let i = 0; i < newLayout.length; i++) {
+ newLayoutLines.push(lines.get(newLayout[i]) as BufferLine);
+ }
+
+ // Rearrange the list
+ for (let i = 0; i < newLayoutLines.length; i++) {
+ lines.set(i, newLayoutLines[i]);
+ }
+ lines.length = newLayout.length;
+}
+
+/**
+ * Gets the new line lengths for a given wrapped line. The purpose of this function it to pre-
+ * compute the wrapping points since wide characters may need to be wrapped onto the following line.
+ * This function will return an array of numbers of where each line wraps to, the resulting array
+ * will only contain the values `newCols` (when the line does not end with a wide character) and
+ * `newCols - 1` (when the line does end with a wide character), except for the last value which
+ * will contain the remaining items to fill the line.
+ *
+ * Calling this with a `newCols` value of `1` will lock up.
+ *
+ * @param wrappedLines The wrapped lines to evaluate.
+ * @param oldCols The columns before resize.
+ * @param newCols The columns after resize.
+ */
+export function reflowSmallerGetNewLineLengths(wrappedLines: BufferLine[], oldCols: number, newCols: number): number[] {
+ const newLineLengths: number[] = [];
+ const cellsNeeded = wrappedLines.map((l, i) => getWrappedLineTrimmedLength(wrappedLines, i, oldCols)).reduce((p, c) => p + c);
+
+ // Use srcCol and srcLine to find the new wrapping point, use that to get the cellsAvailable and
+ // linesNeeded
+ let srcCol = 0;
+ let srcLine = 0;
+ let cellsAvailable = 0;
+ while (cellsAvailable < cellsNeeded) {
+ if (cellsNeeded - cellsAvailable < newCols) {
+ // Add the final line and exit the loop
+ newLineLengths.push(cellsNeeded - cellsAvailable);
+ break;
+ }
+ srcCol += newCols;
+ const oldTrimmedLength = getWrappedLineTrimmedLength(wrappedLines, srcLine, oldCols);
+ if (srcCol > oldTrimmedLength) {
+ srcCol -= oldTrimmedLength;
+ srcLine++;
+ }
+ const endsWithWide = wrappedLines[srcLine].getWidth(srcCol - 1) === 2;
+ if (endsWithWide) {
+ srcCol--;
+ }
+ const lineLength = endsWithWide ? newCols - 1 : newCols;
+ newLineLengths.push(lineLength);
+ cellsAvailable += lineLength;
+ }
+
+ return newLineLengths;
+}
+
+export function getWrappedLineTrimmedLength(lines: BufferLine[], i: number, cols: number): number {
+ // If this is the last row in the wrapped line, get the actual trimmed length
+ if (i === lines.length - 1) {
+ return lines[i].getTrimmedLength();
+ }
+ // Detect whether the following line starts with a wide character and the end of the current line
+ // is null, if so then we can be pretty sure the null character should be excluded from the line
+ // length]
+ const endsInNull = !(lines[i].hasContent(cols - 1)) && lines[i].getWidth(cols - 1) === 1;
+ const followingLineStartsWithWide = lines[i + 1].getWidth(0) === 2;
+ if (endsInNull && followingLineStartsWithWide) {
+ return cols - 1;
+ }
+ return cols;
+}
diff --git a/node_modules/xterm/src/common/buffer/BufferSet.ts b/node_modules/xterm/src/common/buffer/BufferSet.ts
new file mode 100644
index 0000000..de220e8
--- /dev/null
+++ b/node_modules/xterm/src/common/buffer/BufferSet.ts
@@ -0,0 +1,131 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IBuffer, IBufferSet } from 'common/buffer/Types';
+import { IAttributeData } from 'common/Types';
+import { Buffer } from 'common/buffer/Buffer';
+import { EventEmitter, IEvent } from 'common/EventEmitter';
+import { IOptionsService, IBufferService } from 'common/services/Services';
+import { Disposable } from 'common/Lifecycle';
+
+/**
+ * The BufferSet represents the set of two buffers used by xterm terminals (normal and alt) and
+ * provides also utilities for working with them.
+ */
+export class BufferSet extends Disposable implements IBufferSet {
+ private _normal!: Buffer;
+ private _alt!: Buffer;
+ private _activeBuffer!: Buffer;
+
+ private _onBufferActivate = this.register(new EventEmitter<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>());
+ public get onBufferActivate(): IEvent<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}> { return this._onBufferActivate.event; }
+
+ /**
+ * Create a new BufferSet for the given terminal.
+ * @param _terminal - The terminal the BufferSet will belong to
+ */
+ constructor(
+ private readonly _optionsService: IOptionsService,
+ private readonly _bufferService: IBufferService
+ ) {
+ super();
+ this.reset();
+ }
+
+ public reset(): void {
+ this._normal = new Buffer(true, this._optionsService, this._bufferService);
+ this._normal.fillViewportRows();
+
+ // The alt buffer should never have scrollback.
+ // See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
+ this._alt = new Buffer(false, this._optionsService, this._bufferService);
+ this._activeBuffer = this._normal;
+ this._onBufferActivate.fire({
+ activeBuffer: this._normal,
+ inactiveBuffer: this._alt
+ });
+
+ this.setupTabStops();
+ }
+
+ /**
+ * Returns the alt Buffer of the BufferSet
+ */
+ public get alt(): Buffer {
+ return this._alt;
+ }
+
+ /**
+ * Returns the normal Buffer of the BufferSet
+ */
+ public get active(): Buffer {
+ return this._activeBuffer;
+ }
+
+ /**
+ * Returns the currently active Buffer of the BufferSet
+ */
+ public get normal(): Buffer {
+ return this._normal;
+ }
+
+ /**
+ * Sets the normal Buffer of the BufferSet as its currently active Buffer
+ */
+ public activateNormalBuffer(): void {
+ if (this._activeBuffer === this._normal) {
+ return;
+ }
+ this._normal.x = this._alt.x;
+ this._normal.y = this._alt.y;
+ // The alt buffer should always be cleared when we switch to the normal
+ // buffer. This frees up memory since the alt buffer should always be new
+ // when activated.
+ this._alt.clear();
+ this._activeBuffer = this._normal;
+ this._onBufferActivate.fire({
+ activeBuffer: this._normal,
+ inactiveBuffer: this._alt
+ });
+ }
+
+ /**
+ * Sets the alt Buffer of the BufferSet as its currently active Buffer
+ */
+ public activateAltBuffer(fillAttr?: IAttributeData): void {
+ if (this._activeBuffer === this._alt) {
+ return;
+ }
+ // Since the alt buffer is always cleared when the normal buffer is
+ // activated, we want to fill it when switching to it.
+ this._alt.fillViewportRows(fillAttr);
+ this._alt.x = this._normal.x;
+ this._alt.y = this._normal.y;
+ this._activeBuffer = this._alt;
+ this._onBufferActivate.fire({
+ activeBuffer: this._alt,
+ inactiveBuffer: this._normal
+ });
+ }
+
+ /**
+ * Resizes both normal and alt buffers, adjusting their data accordingly.
+ * @param newCols The new number of columns.
+ * @param newRows The new number of rows.
+ */
+ public resize(newCols: number, newRows: number): void {
+ this._normal.resize(newCols, newRows);
+ this._alt.resize(newCols, newRows);
+ }
+
+ /**
+ * Setup the tab stops.
+ * @param i The index to start setting up tab stops from.
+ */
+ public setupTabStops(i?: number): void {
+ this._normal.setupTabStops(i);
+ this._alt.setupTabStops(i);
+ }
+}
diff --git a/node_modules/xterm/src/common/buffer/CellData.ts b/node_modules/xterm/src/common/buffer/CellData.ts
new file mode 100644
index 0000000..a87b579
--- /dev/null
+++ b/node_modules/xterm/src/common/buffer/CellData.ts
@@ -0,0 +1,94 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { CharData, ICellData, IExtendedAttrs } from 'common/Types';
+import { stringFromCodePoint } from 'common/input/TextDecoder';
+import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, Content } from 'common/buffer/Constants';
+import { AttributeData, ExtendedAttrs } from 'common/buffer/AttributeData';
+
+/**
+ * CellData - represents a single Cell in the terminal buffer.
+ */
+export class CellData extends AttributeData implements ICellData {
+ /** Helper to create CellData from CharData. */
+ public static fromCharData(value: CharData): CellData {
+ const obj = new CellData();
+ obj.setFromCharData(value);
+ return obj;
+ }
+ /** Primitives from terminal buffer. */
+ public content = 0;
+ public fg = 0;
+ public bg = 0;
+ public extended: IExtendedAttrs = new ExtendedAttrs();
+ public combinedData = '';
+ /** Whether cell contains a combined string. */
+ public isCombined(): number {
+ return this.content & Content.IS_COMBINED_MASK;
+ }
+ /** Width of the cell. */
+ public getWidth(): number {
+ return this.content >> Content.WIDTH_SHIFT;
+ }
+ /** JS string of the content. */
+ public getChars(): string {
+ if (this.content & Content.IS_COMBINED_MASK) {
+ return this.combinedData;
+ }
+ if (this.content & Content.CODEPOINT_MASK) {
+ return stringFromCodePoint(this.content & Content.CODEPOINT_MASK);
+ }
+ return '';
+ }
+ /**
+ * Codepoint of cell
+ * Note this returns the UTF32 codepoint of single chars,
+ * if content is a combined string it returns the codepoint
+ * of the last char in string to be in line with code in CharData.
+ * */
+ public getCode(): number {
+ return (this.isCombined())
+ ? this.combinedData.charCodeAt(this.combinedData.length - 1)
+ : this.content & Content.CODEPOINT_MASK;
+ }
+ /** Set data from CharData */
+ public setFromCharData(value: CharData): void {
+ this.fg = value[CHAR_DATA_ATTR_INDEX];
+ this.bg = 0;
+ let combined = false;
+ // surrogates and combined strings need special treatment
+ if (value[CHAR_DATA_CHAR_INDEX].length > 2) {
+ combined = true;
+ }
+ else if (value[CHAR_DATA_CHAR_INDEX].length === 2) {
+ const code = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0);
+ // if the 2-char string is a surrogate create single codepoint
+ // everything else is combined
+ if (0xD800 <= code && code <= 0xDBFF) {
+ const second = value[CHAR_DATA_CHAR_INDEX].charCodeAt(1);
+ if (0xDC00 <= second && second <= 0xDFFF) {
+ this.content = ((code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
+ }
+ else {
+ combined = true;
+ }
+ }
+ else {
+ combined = true;
+ }
+ }
+ else {
+ this.content = value[CHAR_DATA_CHAR_INDEX].charCodeAt(0) | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
+ }
+ if (combined) {
+ this.combinedData = value[CHAR_DATA_CHAR_INDEX];
+ this.content = Content.IS_COMBINED_MASK | (value[CHAR_DATA_WIDTH_INDEX] << Content.WIDTH_SHIFT);
+ }
+ }
+ /** Get data as CharData. */
+ public getAsCharData(): CharData {
+ return [this.fg, this.getChars(), this.getWidth(), this.getCode()];
+ }
+}
diff --git a/node_modules/xterm/src/common/buffer/Constants.ts b/node_modules/xterm/src/common/buffer/Constants.ts
new file mode 100644
index 0000000..a2c1b88
--- /dev/null
+++ b/node_modules/xterm/src/common/buffer/Constants.ts
@@ -0,0 +1,139 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+export const DEFAULT_COLOR = 256;
+export const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0);
+
+export const CHAR_DATA_ATTR_INDEX = 0;
+export const CHAR_DATA_CHAR_INDEX = 1;
+export const CHAR_DATA_WIDTH_INDEX = 2;
+export const CHAR_DATA_CODE_INDEX = 3;
+
+/**
+ * Null cell - a real empty cell (containing nothing).
+ * Note that code should always be 0 for a null cell as
+ * several test condition of the buffer line rely on this.
+ */
+export const NULL_CELL_CHAR = '';
+export const NULL_CELL_WIDTH = 1;
+export const NULL_CELL_CODE = 0;
+
+/**
+ * Whitespace cell.
+ * This is meant as a replacement for empty cells when needed
+ * during rendering lines to preserve correct aligment.
+ */
+export const WHITESPACE_CELL_CHAR = ' ';
+export const WHITESPACE_CELL_WIDTH = 1;
+export const WHITESPACE_CELL_CODE = 32;
+
+/**
+ * Bitmasks for accessing data in `content`.
+ */
+export const enum Content {
+ /**
+ * bit 1..21 codepoint, max allowed in UTF32 is 0x10FFFF (21 bits taken)
+ * read: `codepoint = content & Content.codepointMask;`
+ * write: `content |= codepoint & Content.codepointMask;`
+ * shortcut if precondition `codepoint <= 0x10FFFF` is met:
+ * `content |= codepoint;`
+ */
+ CODEPOINT_MASK = 0x1FFFFF,
+
+ /**
+ * bit 22 flag indication whether a cell contains combined content
+ * read: `isCombined = content & Content.isCombined;`
+ * set: `content |= Content.isCombined;`
+ * clear: `content &= ~Content.isCombined;`
+ */
+ IS_COMBINED_MASK = 0x200000, // 1 << 21
+
+ /**
+ * bit 1..22 mask to check whether a cell contains any string data
+ * we need to check for codepoint and isCombined bits to see
+ * whether a cell contains anything
+ * read: `isEmpty = !(content & Content.hasContent)`
+ */
+ HAS_CONTENT_MASK = 0x3FFFFF,
+
+ /**
+ * bit 23..24 wcwidth value of cell, takes 2 bits (ranges from 0..2)
+ * read: `width = (content & Content.widthMask) >> Content.widthShift;`
+ * `hasWidth = content & Content.widthMask;`
+ * as long as wcwidth is highest value in `content`:
+ * `width = content >> Content.widthShift;`
+ * write: `content |= (width << Content.widthShift) & Content.widthMask;`
+ * shortcut if precondition `0 <= width <= 3` is met:
+ * `content |= width << Content.widthShift;`
+ */
+ WIDTH_MASK = 0xC00000, // 3 << 22
+ WIDTH_SHIFT = 22
+}
+
+export const enum Attributes {
+ /**
+ * bit 1..8 blue in RGB, color in P256 and P16
+ */
+ BLUE_MASK = 0xFF,
+ BLUE_SHIFT = 0,
+ PCOLOR_MASK = 0xFF,
+ PCOLOR_SHIFT = 0,
+
+ /**
+ * bit 9..16 green in RGB
+ */
+ GREEN_MASK = 0xFF00,
+ GREEN_SHIFT = 8,
+
+ /**
+ * bit 17..24 red in RGB
+ */
+ RED_MASK = 0xFF0000,
+ RED_SHIFT = 16,
+
+ /**
+ * bit 25..26 color mode: DEFAULT (0) | P16 (1) | P256 (2) | RGB (3)
+ */
+ CM_MASK = 0x3000000,
+ CM_DEFAULT = 0,
+ CM_P16 = 0x1000000,
+ CM_P256 = 0x2000000,
+ CM_RGB = 0x3000000,
+
+ /**
+ * bit 1..24 RGB room
+ */
+ RGB_MASK = 0xFFFFFF
+}
+
+export const enum FgFlags {
+ /**
+ * bit 27..32
+ */
+ INVERSE = 0x4000000,
+ BOLD = 0x8000000,
+ UNDERLINE = 0x10000000,
+ BLINK = 0x20000000,
+ INVISIBLE = 0x40000000,
+ STRIKETHROUGH = 0x80000000,
+}
+
+export const enum BgFlags {
+ /**
+ * bit 27..32 (upper 3 unused)
+ */
+ ITALIC = 0x4000000,
+ DIM = 0x8000000,
+ HAS_EXTENDED = 0x10000000
+}
+
+export const enum UnderlineStyle {
+ NONE = 0,
+ SINGLE = 1,
+ DOUBLE = 2,
+ CURLY = 3,
+ DOTTED = 4,
+ DASHED = 5
+}
diff --git a/node_modules/xterm/src/common/buffer/Marker.ts b/node_modules/xterm/src/common/buffer/Marker.ts
new file mode 100644
index 0000000..72c4085
--- /dev/null
+++ b/node_modules/xterm/src/common/buffer/Marker.ts
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { EventEmitter, IEvent } from 'common/EventEmitter';
+import { Disposable } from 'common/Lifecycle';
+import { IMarker } from 'common/Types';
+
+export class Marker extends Disposable implements IMarker {
+ private static _nextId = 1;
+
+ private _id: number = Marker._nextId++;
+ public isDisposed: boolean = false;
+
+ public get id(): number { return this._id; }
+
+ private _onDispose = new EventEmitter<void>();
+ public get onDispose(): IEvent<void> { return this._onDispose.event; }
+
+ constructor(
+ public line: number
+ ) {
+ super();
+ }
+
+ public dispose(): void {
+ if (this.isDisposed) {
+ return;
+ }
+ this.isDisposed = true;
+ this.line = -1;
+ // Emit before super.dispose such that dispose listeners get a change to react
+ this._onDispose.fire();
+ super.dispose();
+ }
+}
diff --git a/node_modules/xterm/src/common/buffer/Types.d.ts b/node_modules/xterm/src/common/buffer/Types.d.ts
new file mode 100644
index 0000000..cbf40a0
--- /dev/null
+++ b/node_modules/xterm/src/common/buffer/Types.d.ts
@@ -0,0 +1,62 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IAttributeData, ICircularList, IBufferLine, ICellData, IMarker, ICharset, IDisposable } from 'common/Types';
+import { IEvent } from 'common/EventEmitter';
+
+// BufferIndex denotes a position in the buffer: [rowIndex, colIndex]
+export type BufferIndex = [number, number];
+
+export interface IBufferStringIteratorResult {
+ range: {first: number, last: number};
+ content: string;
+}
+
+export interface IBufferStringIterator {
+ hasNext(): boolean;
+ next(): IBufferStringIteratorResult;
+}
+
+export interface IBuffer {
+ readonly lines: ICircularList<IBufferLine>;
+ ydisp: number;
+ ybase: number;
+ y: number;
+ x: number;
+ tabs: any;
+ scrollBottom: number;
+ scrollTop: number;
+ hasScrollback: boolean;
+ savedY: number;
+ savedX: number;
+ savedCharset: ICharset | undefined;
+ savedCurAttrData: IAttributeData;
+ isCursorInViewport: boolean;
+ markers: IMarker[];
+ translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string;
+ getWrappedRangeForLine(y: number): { first: number, last: number };
+ nextStop(x?: number): number;
+ prevStop(x?: number): number;
+ getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine;
+ stringIndexToBufferIndex(lineIndex: number, stringIndex: number, trimRight?: boolean): number[];
+ iterator(trimRight: boolean, startIndex?: number, endIndex?: number, startOverscan?: number, endOverscan?: number): IBufferStringIterator;
+ getNullCell(attr?: IAttributeData): ICellData;
+ getWhitespaceCell(attr?: IAttributeData): ICellData;
+ addMarker(y: number): IMarker;
+}
+
+export interface IBufferSet extends IDisposable {
+ alt: IBuffer;
+ normal: IBuffer;
+ active: IBuffer;
+
+ onBufferActivate: IEvent<{ activeBuffer: IBuffer, inactiveBuffer: IBuffer }>;
+
+ activateNormalBuffer(): void;
+ activateAltBuffer(fillAttr?: IAttributeData): void;
+ reset(): void;
+ resize(newCols: number, newRows: number): void;
+ setupTabStops(i?: number): void;
+}
diff --git a/node_modules/xterm/src/common/data/Charsets.ts b/node_modules/xterm/src/common/data/Charsets.ts
new file mode 100644
index 0000000..c72d5a2
--- /dev/null
+++ b/node_modules/xterm/src/common/data/Charsets.ts
@@ -0,0 +1,256 @@
+/**
+ * Copyright (c) 2016 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { ICharset } from 'common/Types';
+
+/**
+ * The character sets supported by the terminal. These enable several languages
+ * to be represented within the terminal with only 8-bit encoding. See ISO 2022
+ * for a discussion on character sets. Only VT100 character sets are supported.
+ */
+export const CHARSETS: { [key: string]: ICharset | undefined } = {};
+
+/**
+ * The default character set, US.
+ */
+export const DEFAULT_CHARSET: ICharset | undefined = CHARSETS['B'];
+
+/**
+ * DEC Special Character and Line Drawing Set.
+ * Reference: http://vt100.net/docs/vt102-ug/table5-13.html
+ * A lot of curses apps use this if they see TERM=xterm.
+ * testing: echo -e '\e(0a\e(B'
+ * The xterm output sometimes seems to conflict with the
+ * reference above. xterm seems in line with the reference
+ * when running vttest however.
+ * The table below now uses xterm's output from vttest.
+ */
+CHARSETS['0'] = {
+ '`': '\u25c6', // '◆'
+ 'a': '\u2592', // '▒'
+ 'b': '\u2409', // '␉' (HT)
+ 'c': '\u240c', // '␌' (FF)
+ 'd': '\u240d', // '␍' (CR)
+ 'e': '\u240a', // '␊' (LF)
+ 'f': '\u00b0', // '°'
+ 'g': '\u00b1', // '±'
+ 'h': '\u2424', // '␤' (NL)
+ 'i': '\u240b', // '␋' (VT)
+ 'j': '\u2518', // '┘'
+ 'k': '\u2510', // '┐'
+ 'l': '\u250c', // '┌'
+ 'm': '\u2514', // '└'
+ 'n': '\u253c', // '┼'
+ 'o': '\u23ba', // '⎺'
+ 'p': '\u23bb', // '⎻'
+ 'q': '\u2500', // '─'
+ 'r': '\u23bc', // '⎼'
+ 's': '\u23bd', // '⎽'
+ 't': '\u251c', // '├'
+ 'u': '\u2524', // '┤'
+ 'v': '\u2534', // '┴'
+ 'w': '\u252c', // '┬'
+ 'x': '\u2502', // '│'
+ 'y': '\u2264', // '≤'
+ 'z': '\u2265', // '≥'
+ '{': '\u03c0', // 'π'
+ '|': '\u2260', // '≠'
+ '}': '\u00a3', // '£'
+ '~': '\u00b7' // '·'
+};
+
+/**
+ * British character set
+ * ESC (A
+ * Reference: http://vt100.net/docs/vt220-rm/table2-5.html
+ */
+CHARSETS['A'] = {
+ '#': '£'
+};
+
+/**
+ * United States character set
+ * ESC (B
+ */
+CHARSETS['B'] = undefined;
+
+/**
+ * Dutch character set
+ * ESC (4
+ * Reference: http://vt100.net/docs/vt220-rm/table2-6.html
+ */
+CHARSETS['4'] = {
+ '#': '£',
+ '@': '¾',
+ '[': 'ij',
+ '\\': '½',
+ ']': '|',
+ '{': '¨',
+ '|': 'f',
+ '}': '¼',
+ '~': '´'
+};
+
+/**
+ * Finnish character set
+ * ESC (C or ESC (5
+ * Reference: http://vt100.net/docs/vt220-rm/table2-7.html
+ */
+CHARSETS['C'] =
+CHARSETS['5'] = {
+ '[': 'Ä',
+ '\\': 'Ö',
+ ']': 'Å',
+ '^': 'Ü',
+ '`': 'é',
+ '{': 'ä',
+ '|': 'ö',
+ '}': 'å',
+ '~': 'ü'
+};
+
+/**
+ * French character set
+ * ESC (R
+ * Reference: http://vt100.net/docs/vt220-rm/table2-8.html
+ */
+CHARSETS['R'] = {
+ '#': '£',
+ '@': 'à',
+ '[': '°',
+ '\\': 'ç',
+ ']': '§',
+ '{': 'é',
+ '|': 'ù',
+ '}': 'è',
+ '~': '¨'
+};
+
+/**
+ * French Canadian character set
+ * ESC (Q
+ * Reference: http://vt100.net/docs/vt220-rm/table2-9.html
+ */
+CHARSETS['Q'] = {
+ '@': 'à',
+ '[': 'â',
+ '\\': 'ç',
+ ']': 'ê',
+ '^': 'î',
+ '`': 'ô',
+ '{': 'é',
+ '|': 'ù',
+ '}': 'è',
+ '~': 'û'
+};
+
+/**
+ * German character set
+ * ESC (K
+ * Reference: http://vt100.net/docs/vt220-rm/table2-10.html
+ */
+CHARSETS['K'] = {
+ '@': '§',
+ '[': 'Ä',
+ '\\': 'Ö',
+ ']': 'Ü',
+ '{': 'ä',
+ '|': 'ö',
+ '}': 'ü',
+ '~': 'ß'
+};
+
+/**
+ * Italian character set
+ * ESC (Y
+ * Reference: http://vt100.net/docs/vt220-rm/table2-11.html
+ */
+CHARSETS['Y'] = {
+ '#': '£',
+ '@': '§',
+ '[': '°',
+ '\\': 'ç',
+ ']': 'é',
+ '`': 'ù',
+ '{': 'à',
+ '|': 'ò',
+ '}': 'è',
+ '~': 'ì'
+};
+
+/**
+ * Norwegian/Danish character set
+ * ESC (E or ESC (6
+ * Reference: http://vt100.net/docs/vt220-rm/table2-12.html
+ */
+CHARSETS['E'] =
+CHARSETS['6'] = {
+ '@': 'Ä',
+ '[': 'Æ',
+ '\\': 'Ø',
+ ']': 'Å',
+ '^': 'Ü',
+ '`': 'ä',
+ '{': 'æ',
+ '|': 'ø',
+ '}': 'å',
+ '~': 'ü'
+};
+
+/**
+ * Spanish character set
+ * ESC (Z
+ * Reference: http://vt100.net/docs/vt220-rm/table2-13.html
+ */
+CHARSETS['Z'] = {
+ '#': '£',
+ '@': '§',
+ '[': '¡',
+ '\\': 'Ñ',
+ ']': '¿',
+ '{': '°',
+ '|': 'ñ',
+ '}': 'ç'
+};
+
+/**
+ * Swedish character set
+ * ESC (H or ESC (7
+ * Reference: http://vt100.net/docs/vt220-rm/table2-14.html
+ */
+CHARSETS['H'] =
+CHARSETS['7'] = {
+ '@': 'É',
+ '[': 'Ä',
+ '\\': 'Ö',
+ ']': 'Å',
+ '^': 'Ü',
+ '`': 'é',
+ '{': 'ä',
+ '|': 'ö',
+ '}': 'å',
+ '~': 'ü'
+};
+
+/**
+ * Swiss character set
+ * ESC (=
+ * Reference: http://vt100.net/docs/vt220-rm/table2-15.html
+ */
+CHARSETS['='] = {
+ '#': 'ù',
+ '@': 'à',
+ '[': 'é',
+ '\\': 'ç',
+ ']': 'ê',
+ '^': 'î',
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ '_': 'è',
+ '`': 'ô',
+ '{': 'ä',
+ '|': 'ö',
+ '}': 'ü',
+ '~': 'û'
+};
diff --git a/node_modules/xterm/src/common/data/EscapeSequences.ts b/node_modules/xterm/src/common/data/EscapeSequences.ts
new file mode 100644
index 0000000..e35f01d
--- /dev/null
+++ b/node_modules/xterm/src/common/data/EscapeSequences.ts
@@ -0,0 +1,150 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+/**
+ * C0 control codes
+ * See = https://en.wikipedia.org/wiki/C0_and_C1_control_codes
+ */
+export namespace C0 {
+ /** Null (Caret = ^@, C = \0) */
+ export const NUL = '\x00';
+ /** Start of Heading (Caret = ^A) */
+ export const SOH = '\x01';
+ /** Start of Text (Caret = ^B) */
+ export const STX = '\x02';
+ /** End of Text (Caret = ^C) */
+ export const ETX = '\x03';
+ /** End of Transmission (Caret = ^D) */
+ export const EOT = '\x04';
+ /** Enquiry (Caret = ^E) */
+ export const ENQ = '\x05';
+ /** Acknowledge (Caret = ^F) */
+ export const ACK = '\x06';
+ /** Bell (Caret = ^G, C = \a) */
+ export const BEL = '\x07';
+ /** Backspace (Caret = ^H, C = \b) */
+ export const BS = '\x08';
+ /** Character Tabulation, Horizontal Tabulation (Caret = ^I, C = \t) */
+ export const HT = '\x09';
+ /** Line Feed (Caret = ^J, C = \n) */
+ export const LF = '\x0a';
+ /** Line Tabulation, Vertical Tabulation (Caret = ^K, C = \v) */
+ export const VT = '\x0b';
+ /** Form Feed (Caret = ^L, C = \f) */
+ export const FF = '\x0c';
+ /** Carriage Return (Caret = ^M, C = \r) */
+ export const CR = '\x0d';
+ /** Shift Out (Caret = ^N) */
+ export const SO = '\x0e';
+ /** Shift In (Caret = ^O) */
+ export const SI = '\x0f';
+ /** Data Link Escape (Caret = ^P) */
+ export const DLE = '\x10';
+ /** Device Control One (XON) (Caret = ^Q) */
+ export const DC1 = '\x11';
+ /** Device Control Two (Caret = ^R) */
+ export const DC2 = '\x12';
+ /** Device Control Three (XOFF) (Caret = ^S) */
+ export const DC3 = '\x13';
+ /** Device Control Four (Caret = ^T) */
+ export const DC4 = '\x14';
+ /** Negative Acknowledge (Caret = ^U) */
+ export const NAK = '\x15';
+ /** Synchronous Idle (Caret = ^V) */
+ export const SYN = '\x16';
+ /** End of Transmission Block (Caret = ^W) */
+ export const ETB = '\x17';
+ /** Cancel (Caret = ^X) */
+ export const CAN = '\x18';
+ /** End of Medium (Caret = ^Y) */
+ export const EM = '\x19';
+ /** Substitute (Caret = ^Z) */
+ export const SUB = '\x1a';
+ /** Escape (Caret = ^[, C = \e) */
+ export const ESC = '\x1b';
+ /** File Separator (Caret = ^\) */
+ export const FS = '\x1c';
+ /** Group Separator (Caret = ^]) */
+ export const GS = '\x1d';
+ /** Record Separator (Caret = ^^) */
+ export const RS = '\x1e';
+ /** Unit Separator (Caret = ^_) */
+ export const US = '\x1f';
+ /** Space */
+ export const SP = '\x20';
+ /** Delete (Caret = ^?) */
+ export const DEL = '\x7f';
+}
+
+/**
+ * C1 control codes
+ * See = https://en.wikipedia.org/wiki/C0_and_C1_control_codes
+ */
+export namespace C1 {
+ /** padding character */
+ export const PAD = '\x80';
+ /** High Octet Preset */
+ export const HOP = '\x81';
+ /** Break Permitted Here */
+ export const BPH = '\x82';
+ /** No Break Here */
+ export const NBH = '\x83';
+ /** Index */
+ export const IND = '\x84';
+ /** Next Line */
+ export const NEL = '\x85';
+ /** Start of Selected Area */
+ export const SSA = '\x86';
+ /** End of Selected Area */
+ export const ESA = '\x87';
+ /** Horizontal Tabulation Set */
+ export const HTS = '\x88';
+ /** Horizontal Tabulation With Justification */
+ export const HTJ = '\x89';
+ /** Vertical Tabulation Set */
+ export const VTS = '\x8a';
+ /** Partial Line Down */
+ export const PLD = '\x8b';
+ /** Partial Line Up */
+ export const PLU = '\x8c';
+ /** Reverse Index */
+ export const RI = '\x8d';
+ /** Single-Shift 2 */
+ export const SS2 = '\x8e';
+ /** Single-Shift 3 */
+ export const SS3 = '\x8f';
+ /** Device Control String */
+ export const DCS = '\x90';
+ /** Private Use 1 */
+ export const PU1 = '\x91';
+ /** Private Use 2 */
+ export const PU2 = '\x92';
+ /** Set Transmit State */
+ export const STS = '\x93';
+ /** Destructive backspace, intended to eliminate ambiguity about meaning of BS. */
+ export const CCH = '\x94';
+ /** Message Waiting */
+ export const MW = '\x95';
+ /** Start of Protected Area */
+ export const SPA = '\x96';
+ /** End of Protected Area */
+ export const EPA = '\x97';
+ /** Start of String */
+ export const SOS = '\x98';
+ /** Single Graphic Character Introducer */
+ export const SGCI = '\x99';
+ /** Single Character Introducer */
+ export const SCI = '\x9a';
+ /** Control Sequence Introducer */
+ export const CSI = '\x9b';
+ /** String Terminator */
+ export const ST = '\x9c';
+ /** Operating System Command */
+ export const OSC = '\x9d';
+ /** Privacy Message */
+ export const PM = '\x9e';
+ /** Application Program Command */
+ export const APC = '\x9f';
+}
diff --git a/node_modules/xterm/src/common/input/Keyboard.ts b/node_modules/xterm/src/common/input/Keyboard.ts
new file mode 100644
index 0000000..b4b3dce
--- /dev/null
+++ b/node_modules/xterm/src/common/input/Keyboard.ts
@@ -0,0 +1,375 @@
+/**
+ * Copyright (c) 2014 The xterm.js authors. All rights reserved.
+ * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
+ * @license MIT
+ */
+
+import { IKeyboardEvent, IKeyboardResult, KeyboardResultType } from 'common/Types';
+import { C0 } from 'common/data/EscapeSequences';
+
+// reg + shift key mappings for digits and special chars
+const KEYCODE_KEY_MAPPINGS: { [key: number]: [string, string]} = {
+ // digits 0-9
+ 48: ['0', ')'],
+ 49: ['1', '!'],
+ 50: ['2', '@'],
+ 51: ['3', '#'],
+ 52: ['4', '$'],
+ 53: ['5', '%'],
+ 54: ['6', '^'],
+ 55: ['7', '&'],
+ 56: ['8', '*'],
+ 57: ['9', '('],
+
+ // special chars
+ 186: [';', ':'],
+ 187: ['=', '+'],
+ 188: [',', '<'],
+ 189: ['-', '_'],
+ 190: ['.', '>'],
+ 191: ['/', '?'],
+ 192: ['`', '~'],
+ 219: ['[', '{'],
+ 220: ['\\', '|'],
+ 221: [']', '}'],
+ 222: ['\'', '"']
+};
+
+export function evaluateKeyboardEvent(
+ ev: IKeyboardEvent,
+ applicationCursorMode: boolean,
+ isMac: boolean,
+ macOptionIsMeta: boolean
+): IKeyboardResult {
+ const result: IKeyboardResult = {
+ type: KeyboardResultType.SEND_KEY,
+ // Whether to cancel event propagation (NOTE: this may not be needed since the event is
+ // canceled at the end of keyDown
+ cancel: false,
+ // The new key even to emit
+ key: undefined
+ };
+ const modifiers = (ev.shiftKey ? 1 : 0) | (ev.altKey ? 2 : 0) | (ev.ctrlKey ? 4 : 0) | (ev.metaKey ? 8 : 0);
+ switch (ev.keyCode) {
+ case 0:
+ if (ev.key === 'UIKeyInputUpArrow') {
+ if (applicationCursorMode) {
+ result.key = C0.ESC + 'OA';
+ } else {
+ result.key = C0.ESC + '[A';
+ }
+ }
+ else if (ev.key === 'UIKeyInputLeftArrow') {
+ if (applicationCursorMode) {
+ result.key = C0.ESC + 'OD';
+ } else {
+ result.key = C0.ESC + '[D';
+ }
+ }
+ else if (ev.key === 'UIKeyInputRightArrow') {
+ if (applicationCursorMode) {
+ result.key = C0.ESC + 'OC';
+ } else {
+ result.key = C0.ESC + '[C';
+ }
+ }
+ else if (ev.key === 'UIKeyInputDownArrow') {
+ if (applicationCursorMode) {
+ result.key = C0.ESC + 'OB';
+ } else {
+ result.key = C0.ESC + '[B';
+ }
+ }
+ break;
+ case 8:
+ // backspace
+ if (ev.shiftKey) {
+ result.key = C0.BS; // ^H
+ break;
+ } else if (ev.altKey) {
+ result.key = C0.ESC + C0.DEL; // \e ^?
+ break;
+ }
+ result.key = C0.DEL; // ^?
+ break;
+ case 9:
+ // tab
+ if (ev.shiftKey) {
+ result.key = C0.ESC + '[Z';
+ break;
+ }
+ result.key = C0.HT;
+ result.cancel = true;
+ break;
+ case 13:
+ // return/enter
+ result.key = ev.altKey ? C0.ESC + C0.CR : C0.CR;
+ result.cancel = true;
+ break;
+ case 27:
+ // escape
+ result.key = C0.ESC;
+ if (ev.altKey) {
+ result.key = C0.ESC + C0.ESC;
+ }
+ result.cancel = true;
+ break;
+ case 37:
+ // left-arrow
+ if (ev.metaKey) {
+ break;
+ }
+ if (modifiers) {
+ result.key = C0.ESC + '[1;' + (modifiers + 1) + 'D';
+ // HACK: Make Alt + left-arrow behave like Ctrl + left-arrow: move one word backwards
+ // http://unix.stackexchange.com/a/108106
+ // macOS uses different escape sequences than linux
+ if (result.key === C0.ESC + '[1;3D') {
+ result.key = C0.ESC + (isMac ? 'b' : '[1;5D');
+ }
+ } else if (applicationCursorMode) {
+ result.key = C0.ESC + 'OD';
+ } else {
+ result.key = C0.ESC + '[D';
+ }
+ break;
+ case 39:
+ // right-arrow
+ if (ev.metaKey) {
+ break;
+ }
+ if (modifiers) {
+ result.key = C0.ESC + '[1;' + (modifiers + 1) + 'C';
+ // HACK: Make Alt + right-arrow behave like Ctrl + right-arrow: move one word forward
+ // http://unix.stackexchange.com/a/108106
+ // macOS uses different escape sequences than linux
+ if (result.key === C0.ESC + '[1;3C') {
+ result.key = C0.ESC + (isMac ? 'f' : '[1;5C');
+ }
+ } else if (applicationCursorMode) {
+ result.key = C0.ESC + 'OC';
+ } else {
+ result.key = C0.ESC + '[C';
+ }
+ break;
+ case 38:
+ // up-arrow
+ if (ev.metaKey) {
+ break;
+ }
+ if (modifiers) {
+ result.key = C0.ESC + '[1;' + (modifiers + 1) + 'A';
+ // HACK: Make Alt + up-arrow behave like Ctrl + up-arrow
+ // http://unix.stackexchange.com/a/108106
+ // macOS uses different escape sequences than linux
+ if (!isMac && result.key === C0.ESC + '[1;3A') {
+ result.key = C0.ESC + '[1;5A';
+ }
+ } else if (applicationCursorMode) {
+ result.key = C0.ESC + 'OA';
+ } else {
+ result.key = C0.ESC + '[A';
+ }
+ break;
+ case 40:
+ // down-arrow
+ if (ev.metaKey) {
+ break;
+ }
+ if (modifiers) {
+ result.key = C0.ESC + '[1;' + (modifiers + 1) + 'B';
+ // HACK: Make Alt + down-arrow behave like Ctrl + down-arrow
+ // http://unix.stackexchange.com/a/108106
+ // macOS uses different escape sequences than linux
+ if (!isMac && result.key === C0.ESC + '[1;3B') {
+ result.key = C0.ESC + '[1;5B';
+ }
+ } else if (applicationCursorMode) {
+ result.key = C0.ESC + 'OB';
+ } else {
+ result.key = C0.ESC + '[B';
+ }
+ break;
+ case 45:
+ // insert
+ if (!ev.shiftKey && !ev.ctrlKey) {
+ // <Ctrl> or <Shift> + <Insert> are used to
+ // copy-paste on some systems.
+ result.key = C0.ESC + '[2~';
+ }
+ break;
+ case 46:
+ // delete
+ if (modifiers) {
+ result.key = C0.ESC + '[3;' + (modifiers + 1) + '~';
+ } else {
+ result.key = C0.ESC + '[3~';
+ }
+ break;
+ case 36:
+ // home
+ if (modifiers) {
+ result.key = C0.ESC + '[1;' + (modifiers + 1) + 'H';
+ } else if (applicationCursorMode) {
+ result.key = C0.ESC + 'OH';
+ } else {
+ result.key = C0.ESC + '[H';
+ }
+ break;
+ case 35:
+ // end
+ if (modifiers) {
+ result.key = C0.ESC + '[1;' + (modifiers + 1) + 'F';
+ } else if (applicationCursorMode) {
+ result.key = C0.ESC + 'OF';
+ } else {
+ result.key = C0.ESC + '[F';
+ }
+ break;
+ case 33:
+ // page up
+ if (ev.shiftKey) {
+ result.type = KeyboardResultType.PAGE_UP;
+ } else {
+ result.key = C0.ESC + '[5~';
+ }
+ break;
+ case 34:
+ // page down
+ if (ev.shiftKey) {
+ result.type = KeyboardResultType.PAGE_DOWN;
+ } else {
+ result.key = C0.ESC + '[6~';
+ }
+ break;
+ case 112:
+ // F1-F12
+ if (modifiers) {
+ result.key = C0.ESC + '[1;' + (modifiers + 1) + 'P';
+ } else {
+ result.key = C0.ESC + 'OP';
+ }
+ break;
+ case 113:
+ if (modifiers) {
+ result.key = C0.ESC + '[1;' + (modifiers + 1) + 'Q';
+ } else {
+ result.key = C0.ESC + 'OQ';
+ }
+ break;
+ case 114:
+ if (modifiers) {
+ result.key = C0.ESC + '[1;' + (modifiers + 1) + 'R';
+ } else {
+ result.key = C0.ESC + 'OR';
+ }
+ break;
+ case 115:
+ if (modifiers) {
+ result.key = C0.ESC + '[1;' + (modifiers + 1) + 'S';
+ } else {
+ result.key = C0.ESC + 'OS';
+ }
+ break;
+ case 116:
+ if (modifiers) {
+ result.key = C0.ESC + '[15;' + (modifiers + 1) + '~';
+ } else {
+ result.key = C0.ESC + '[15~';
+ }
+ break;
+ case 117:
+ if (modifiers) {
+ result.key = C0.ESC + '[17;' + (modifiers + 1) + '~';
+ } else {
+ result.key = C0.ESC + '[17~';
+ }
+ break;
+ case 118:
+ if (modifiers) {
+ result.key = C0.ESC + '[18;' + (modifiers + 1) + '~';
+ } else {
+ result.key = C0.ESC + '[18~';
+ }
+ break;
+ case 119:
+ if (modifiers) {
+ result.key = C0.ESC + '[19;' + (modifiers + 1) + '~';
+ } else {
+ result.key = C0.ESC + '[19~';
+ }
+ break;
+ case 120:
+ if (modifiers) {
+ result.key = C0.ESC + '[20;' + (modifiers + 1) + '~';
+ } else {
+ result.key = C0.ESC + '[20~';
+ }
+ break;
+ case 121:
+ if (modifiers) {
+ result.key = C0.ESC + '[21;' + (modifiers + 1) + '~';
+ } else {
+ result.key = C0.ESC + '[21~';
+ }
+ break;
+ case 122:
+ if (modifiers) {
+ result.key = C0.ESC + '[23;' + (modifiers + 1) + '~';
+ } else {
+ result.key = C0.ESC + '[23~';
+ }
+ break;
+ case 123:
+ if (modifiers) {
+ result.key = C0.ESC + '[24;' + (modifiers + 1) + '~';
+ } else {
+ result.key = C0.ESC + '[24~';
+ }
+ break;
+ default:
+ // a-z and space
+ if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {
+ if (ev.keyCode >= 65 && ev.keyCode <= 90) {
+ result.key = String.fromCharCode(ev.keyCode - 64);
+ } else if (ev.keyCode === 32) {
+ result.key = C0.NUL;
+ } else if (ev.keyCode >= 51 && ev.keyCode <= 55) {
+ // escape, file sep, group sep, record sep, unit sep
+ result.key = String.fromCharCode(ev.keyCode - 51 + 27);
+ } else if (ev.keyCode === 56) {
+ result.key = C0.DEL;
+ } else if (ev.keyCode === 219) {
+ result.key = C0.ESC;
+ } else if (ev.keyCode === 220) {
+ result.key = C0.FS;
+ } else if (ev.keyCode === 221) {
+ result.key = C0.GS;
+ }
+ } else if ((!isMac || macOptionIsMeta) && ev.altKey && !ev.metaKey) {
+ // On macOS this is a third level shift when !macOptionIsMeta. Use <Esc> instead.
+ const keyMapping = KEYCODE_KEY_MAPPINGS[ev.keyCode];
+ const key = keyMapping?.[!ev.shiftKey ? 0 : 1];
+ if (key) {
+ 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);
+ }
+ } else if (isMac && !ev.altKey && !ev.ctrlKey && !ev.shiftKey && ev.metaKey) {
+ if (ev.keyCode === 65) { // cmd + a
+ result.type = KeyboardResultType.SELECT_ALL;
+ }
+ } else if (ev.key && !ev.ctrlKey && !ev.altKey && !ev.metaKey && ev.keyCode >= 48 && ev.key.length === 1) {
+ // Include only keys that that result in a _single_ character; don't include num lock, volume up, etc.
+ result.key = ev.key;
+ } else if (ev.key && ev.ctrlKey) {
+ if (ev.key === '_') { // ^_
+ result.key = C0.US;
+ }
+ }
+ break;
+ }
+
+ return result;
+}
diff --git a/node_modules/xterm/src/common/input/TextDecoder.ts b/node_modules/xterm/src/common/input/TextDecoder.ts
new file mode 100644
index 0000000..715e919
--- /dev/null
+++ b/node_modules/xterm/src/common/input/TextDecoder.ts
@@ -0,0 +1,346 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+/**
+ * Polyfill - Convert UTF32 codepoint into JS string.
+ * Note: The built-in String.fromCodePoint happens to be much slower
+ * due to additional sanity checks. We can avoid them since
+ * we always operate on legal UTF32 (granted by the input decoders)
+ * and use this faster version instead.
+ */
+export function stringFromCodePoint(codePoint: number): string {
+ if (codePoint > 0xFFFF) {
+ codePoint -= 0x10000;
+ return String.fromCharCode((codePoint >> 10) + 0xD800) + String.fromCharCode((codePoint % 0x400) + 0xDC00);
+ }
+ return String.fromCharCode(codePoint);
+}
+
+/**
+ * Convert UTF32 char codes into JS string.
+ * Basically the same as `stringFromCodePoint` but for multiple codepoints
+ * in a loop (which is a lot faster).
+ */
+export function utf32ToString(data: Uint32Array, start: number = 0, end: number = data.length): string {
+ let result = '';
+ for (let i = start; i < end; ++i) {
+ let codepoint = data[i];
+ if (codepoint > 0xFFFF) {
+ // JS strings are encoded as UTF16, thus a non BMP codepoint gets converted into a surrogate pair
+ // conversion rules:
+ // - subtract 0x10000 from code point, leaving a 20 bit number
+ // - add high 10 bits to 0xD800 --> first surrogate
+ // - add low 10 bits to 0xDC00 --> second surrogate
+ codepoint -= 0x10000;
+ result += String.fromCharCode((codepoint >> 10) + 0xD800) + String.fromCharCode((codepoint % 0x400) + 0xDC00);
+ } else {
+ result += String.fromCharCode(codepoint);
+ }
+ }
+ return result;
+}
+
+/**
+ * StringToUtf32 - decodes UTF16 sequences into UTF32 codepoints.
+ * To keep the decoder in line with JS strings it handles single surrogates as UCS2.
+ */
+export class StringToUtf32 {
+ private _interim: number = 0;
+
+ /**
+ * Clears interim and resets decoder to clean state.
+ */
+ public clear(): void {
+ this._interim = 0;
+ }
+
+ /**
+ * Decode JS string to UTF32 codepoints.
+ * The methods assumes stream input and will store partly transmitted
+ * surrogate pairs and decode them with the next data chunk.
+ * Note: The method does no bound checks for target, therefore make sure
+ * the provided input data does not exceed the size of `target`.
+ * Returns the number of written codepoints in `target`.
+ */
+ public decode(input: string, target: Uint32Array): number {
+ const length = input.length;
+
+ if (!length) {
+ return 0;
+ }
+
+ let size = 0;
+ let startPos = 0;
+
+ // handle leftover surrogate high
+ if (this._interim) {
+ const second = input.charCodeAt(startPos++);
+ if (0xDC00 <= second && second <= 0xDFFF) {
+ target[size++] = (this._interim - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
+ } else {
+ // illegal codepoint (USC2 handling)
+ target[size++] = this._interim;
+ target[size++] = second;
+ }
+ this._interim = 0;
+ }
+
+ for (let i = startPos; i < length; ++i) {
+ const code = input.charCodeAt(i);
+ // surrogate pair first
+ if (0xD800 <= code && code <= 0xDBFF) {
+ if (++i >= length) {
+ this._interim = code;
+ return size;
+ }
+ const second = input.charCodeAt(i);
+ if (0xDC00 <= second && second <= 0xDFFF) {
+ target[size++] = (code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
+ } else {
+ // illegal codepoint (USC2 handling)
+ target[size++] = code;
+ target[size++] = second;
+ }
+ continue;
+ }
+ if (code === 0xFEFF) {
+ // BOM
+ continue;
+ }
+ target[size++] = code;
+ }
+ return size;
+ }
+}
+
+/**
+ * Utf8Decoder - decodes UTF8 byte sequences into UTF32 codepoints.
+ */
+export class Utf8ToUtf32 {
+ public interim: Uint8Array = new Uint8Array(3);
+
+ /**
+ * Clears interim bytes and resets decoder to clean state.
+ */
+ public clear(): void {
+ this.interim.fill(0);
+ }
+
+ /**
+ * Decodes UTF8 byte sequences in `input` to UTF32 codepoints in `target`.
+ * The methods assumes stream input and will store partly transmitted bytes
+ * and decode them with the next data chunk.
+ * Note: The method does no bound checks for target, therefore make sure
+ * the provided data chunk does not exceed the size of `target`.
+ * Returns the number of written codepoints in `target`.
+ */
+ public decode(input: Uint8Array, target: Uint32Array): number {
+ const length = input.length;
+
+ if (!length) {
+ return 0;
+ }
+
+ let size = 0;
+ let byte1: number;
+ let byte2: number;
+ let byte3: number;
+ let byte4: number;
+ let codepoint = 0;
+ let startPos = 0;
+
+ // handle leftover bytes
+ if (this.interim[0]) {
+ let discardInterim = false;
+ let cp = this.interim[0];
+ cp &= ((((cp & 0xE0) === 0xC0)) ? 0x1F : (((cp & 0xF0) === 0xE0)) ? 0x0F : 0x07);
+ let pos = 0;
+ let tmp: number;
+ while ((tmp = this.interim[++pos] & 0x3F) && pos < 4) {
+ cp <<= 6;
+ cp |= tmp;
+ }
+ // missing bytes - read ahead from input
+ const type = (((this.interim[0] & 0xE0) === 0xC0)) ? 2 : (((this.interim[0] & 0xF0) === 0xE0)) ? 3 : 4;
+ const missing = type - pos;
+ while (startPos < missing) {
+ if (startPos >= length) {
+ return 0;
+ }
+ tmp = input[startPos++];
+ if ((tmp & 0xC0) !== 0x80) {
+ // wrong continuation, discard interim bytes completely
+ startPos--;
+ discardInterim = true;
+ break;
+ } else {
+ // need to save so we can continue short inputs in next call
+ this.interim[pos++] = tmp;
+ cp <<= 6;
+ cp |= tmp & 0x3F;
+ }
+ }
+ if (!discardInterim) {
+ // final test is type dependent
+ if (type === 2) {
+ if (cp < 0x80) {
+ // wrong starter byte
+ startPos--;
+ } else {
+ target[size++] = cp;
+ }
+ } else if (type === 3) {
+ if (cp < 0x0800 || (cp >= 0xD800 && cp <= 0xDFFF) || cp === 0xFEFF) {
+ // illegal codepoint or BOM
+ } else {
+ target[size++] = cp;
+ }
+ } else {
+ if (cp < 0x010000 || cp > 0x10FFFF) {
+ // illegal codepoint
+ } else {
+ target[size++] = cp;
+ }
+ }
+ }
+ this.interim.fill(0);
+ }
+
+ // loop through input
+ const fourStop = length - 4;
+ let i = startPos;
+ while (i < length) {
+ /**
+ * ASCII shortcut with loop unrolled to 4 consecutive ASCII chars.
+ * This is a compromise between speed gain for ASCII
+ * and penalty for non ASCII:
+ * For best ASCII performance the char should be stored directly into target,
+ * but even a single attempt to write to target and compare afterwards
+ * penalizes non ASCII really bad (-50%), thus we load the char into byteX first,
+ * which reduces ASCII performance by ~15%.
+ * This trial for ASCII reduces non ASCII performance by ~10% which seems acceptible
+ * compared to the gains.
+ * Note that this optimization only takes place for 4 consecutive ASCII chars,
+ * for any shorter it bails out. Worst case - all 4 bytes being read but
+ * thrown away due to the last being a non ASCII char (-10% performance).
+ */
+ while (i < fourStop
+ && !((byte1 = input[i]) & 0x80)
+ && !((byte2 = input[i + 1]) & 0x80)
+ && !((byte3 = input[i + 2]) & 0x80)
+ && !((byte4 = input[i + 3]) & 0x80))
+ {
+ target[size++] = byte1;
+ target[size++] = byte2;
+ target[size++] = byte3;
+ target[size++] = byte4;
+ i += 4;
+ }
+
+ // reread byte1
+ byte1 = input[i++];
+
+ // 1 byte
+ if (byte1 < 0x80) {
+ target[size++] = byte1;
+
+ // 2 bytes
+ } else if ((byte1 & 0xE0) === 0xC0) {
+ if (i >= length) {
+ this.interim[0] = byte1;
+ return size;
+ }
+ byte2 = input[i++];
+ if ((byte2 & 0xC0) !== 0x80) {
+ // wrong continuation
+ i--;
+ continue;
+ }
+ codepoint = (byte1 & 0x1F) << 6 | (byte2 & 0x3F);
+ if (codepoint < 0x80) {
+ // wrong starter byte
+ i--;
+ continue;
+ }
+ target[size++] = codepoint;
+
+ // 3 bytes
+ } else if ((byte1 & 0xF0) === 0xE0) {
+ if (i >= length) {
+ this.interim[0] = byte1;
+ return size;
+ }
+ byte2 = input[i++];
+ if ((byte2 & 0xC0) !== 0x80) {
+ // wrong continuation
+ i--;
+ continue;
+ }
+ if (i >= length) {
+ this.interim[0] = byte1;
+ this.interim[1] = byte2;
+ return size;
+ }
+ byte3 = input[i++];
+ if ((byte3 & 0xC0) !== 0x80) {
+ // wrong continuation
+ i--;
+ continue;
+ }
+ codepoint = (byte1 & 0x0F) << 12 | (byte2 & 0x3F) << 6 | (byte3 & 0x3F);
+ if (codepoint < 0x0800 || (codepoint >= 0xD800 && codepoint <= 0xDFFF) || codepoint === 0xFEFF) {
+ // illegal codepoint or BOM, no i-- here
+ continue;
+ }
+ target[size++] = codepoint;
+
+ // 4 bytes
+ } else if ((byte1 & 0xF8) === 0xF0) {
+ if (i >= length) {
+ this.interim[0] = byte1;
+ return size;
+ }
+ byte2 = input[i++];
+ if ((byte2 & 0xC0) !== 0x80) {
+ // wrong continuation
+ i--;
+ continue;
+ }
+ if (i >= length) {
+ this.interim[0] = byte1;
+ this.interim[1] = byte2;
+ return size;
+ }
+ byte3 = input[i++];
+ if ((byte3 & 0xC0) !== 0x80) {
+ // wrong continuation
+ i--;
+ continue;
+ }
+ if (i >= length) {
+ this.interim[0] = byte1;
+ this.interim[1] = byte2;
+ this.interim[2] = byte3;
+ return size;
+ }
+ byte4 = input[i++];
+ if ((byte4 & 0xC0) !== 0x80) {
+ // wrong continuation
+ i--;
+ continue;
+ }
+ codepoint = (byte1 & 0x07) << 18 | (byte2 & 0x3F) << 12 | (byte3 & 0x3F) << 6 | (byte4 & 0x3F);
+ if (codepoint < 0x010000 || codepoint > 0x10FFFF) {
+ // illegal codepoint, no i-- here
+ continue;
+ }
+ target[size++] = codepoint;
+ } else {
+ // illegal byte, just skip
+ }
+ }
+ return size;
+ }
+}
diff --git a/node_modules/xterm/src/common/input/UnicodeV6.ts b/node_modules/xterm/src/common/input/UnicodeV6.ts
new file mode 100644
index 0000000..b308203
--- /dev/null
+++ b/node_modules/xterm/src/common/input/UnicodeV6.ts
@@ -0,0 +1,133 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+import { IUnicodeVersionProvider } from 'common/services/Services';
+import { fill } from 'common/TypedArrayUtils';
+
+type CharWidth = 0 | 1 | 2;
+
+const BMP_COMBINING = [
+ [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489],
+ [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2],
+ [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603],
+ [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670],
+ [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED],
+ [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A],
+ [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902],
+ [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D],
+ [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981],
+ [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD],
+ [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C],
+ [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D],
+ [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC],
+ [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD],
+ [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C],
+ [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D],
+ [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0],
+ [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48],
+ [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC],
+ [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD],
+ [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D],
+ [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6],
+ [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E],
+ [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC],
+ [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35],
+ [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E],
+ [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97],
+ [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030],
+ [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039],
+ [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F],
+ [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753],
+ [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD],
+ [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD],
+ [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922],
+ [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B],
+ [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34],
+ [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42],
+ [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF],
+ [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063],
+ [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F],
+ [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B],
+ [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F],
+ [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB]
+];
+const HIGH_COMBINING = [
+ [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F],
+ [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169],
+ [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD],
+ [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F],
+ [0xE0100, 0xE01EF]
+];
+
+// BMP lookup table, lazy initialized during first addon loading
+let table: Uint8Array;
+
+function bisearch(ucs: number, data: number[][]): boolean {
+ let min = 0;
+ let max = data.length - 1;
+ let mid;
+ if (ucs < data[0][0] || ucs > data[max][1]) {
+ return false;
+ }
+ while (max >= min) {
+ mid = (min + max) >> 1;
+ if (ucs > data[mid][1]) {
+ min = mid + 1;
+ } else if (ucs < data[mid][0]) {
+ max = mid - 1;
+ } else {
+ return true;
+ }
+ }
+ return false;
+}
+
+export class UnicodeV6 implements IUnicodeVersionProvider {
+ public readonly version = '6';
+
+ constructor() {
+ // init lookup table once
+ if (!table) {
+ table = new Uint8Array(65536);
+ fill(table, 1);
+ table[0] = 0;
+ // control chars
+ fill(table, 0, 1, 32);
+ fill(table, 0, 0x7f, 0xa0);
+
+ // apply wide char rules first
+ // wide chars
+ fill(table, 2, 0x1100, 0x1160);
+ table[0x2329] = 2;
+ table[0x232a] = 2;
+ fill(table, 2, 0x2e80, 0xa4d0);
+ table[0x303f] = 1; // wrongly in last line
+
+ fill(table, 2, 0xac00, 0xd7a4);
+ fill(table, 2, 0xf900, 0xfb00);
+ fill(table, 2, 0xfe10, 0xfe1a);
+ fill(table, 2, 0xfe30, 0xfe70);
+ fill(table, 2, 0xff00, 0xff61);
+ fill(table, 2, 0xffe0, 0xffe7);
+
+ // apply combining last to ensure we overwrite
+ // wrongly wide set chars:
+ // the original algo evals combining first and falls
+ // through to wide check so we simply do here the opposite
+ // combining 0
+ for (let r = 0; r < BMP_COMBINING.length; ++r) {
+ fill(table, 0, BMP_COMBINING[r][0], BMP_COMBINING[r][1] + 1);
+ }
+ }
+ }
+
+ public wcwidth(num: number): CharWidth {
+ if (num < 32) return 0;
+ if (num < 127) return 1;
+ if (num < 65536) return table[num] as CharWidth;
+ if (bisearch(num, HIGH_COMBINING)) return 0;
+ if ((num >= 0x20000 && num <= 0x2fffd) || (num >= 0x30000 && num <= 0x3fffd)) return 2;
+ return 1;
+ }
+}
diff --git a/node_modules/xterm/src/common/input/WriteBuffer.ts b/node_modules/xterm/src/common/input/WriteBuffer.ts
new file mode 100644
index 0000000..cc84c9a
--- /dev/null
+++ b/node_modules/xterm/src/common/input/WriteBuffer.ts
@@ -0,0 +1,224 @@
+
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+declare const setTimeout: (handler: () => void, timeout?: number) => void;
+
+/**
+ * Safety watermark to avoid memory exhaustion and browser engine crash on fast data input.
+ * Enable flow control to avoid this limit and make sure that your backend correctly
+ * propagates this to the underlying pty. (see docs for further instructions)
+ * Since this limit is meant as a safety parachute to prevent browser crashs,
+ * it is set to a very high number. Typically xterm.js gets unresponsive with
+ * a 100 times lower number (>500 kB).
+ */
+const DISCARD_WATERMARK = 50000000; // ~50 MB
+
+/**
+ * The max number of ms to spend on writes before allowing the renderer to
+ * catch up with a 0ms setTimeout. A value of < 33 to keep us close to
+ * 30fps, and a value of < 16 to try to run at 60fps. Of course, the real FPS
+ * depends on the time it takes for the renderer to draw the frame.
+ */
+const WRITE_TIMEOUT_MS = 12;
+
+/**
+ * Threshold of max held chunks in the write buffer, that were already processed.
+ * This is a tradeoff between extensive write buffer shifts (bad runtime) and high
+ * memory consumption by data thats not used anymore.
+ */
+const WRITE_BUFFER_LENGTH_THRESHOLD = 50;
+
+// queueMicrotask polyfill for nodejs < v11
+const qmt: (cb: () => void) => void = (typeof queueMicrotask === 'undefined')
+ ? (cb: () => void) => { Promise.resolve().then(cb); }
+ : queueMicrotask;
+
+
+export class WriteBuffer {
+ private _writeBuffer: (string | Uint8Array)[] = [];
+ private _callbacks: ((() => void) | undefined)[] = [];
+ private _pendingData = 0;
+ private _bufferOffset = 0;
+ private _isSyncWriting = false;
+ private _syncCalls = 0;
+
+ constructor(private _action: (data: string | Uint8Array, promiseResult?: boolean) => void | Promise<boolean>) { }
+
+ /**
+ * @deprecated Unreliable, to be removed soon.
+ */
+ public writeSync(data: string | Uint8Array, maxSubsequentCalls?: number): void {
+ // stop writeSync recursions with maxSubsequentCalls argument
+ // This is dangerous to use as it will lose the current data chunk
+ // and return immediately.
+ if (maxSubsequentCalls !== undefined && this._syncCalls > maxSubsequentCalls) {
+ // comment next line if a whole loop block should only contain x `writeSync` calls
+ // (total flat vs. deep nested limit)
+ this._syncCalls = 0;
+ return;
+ }
+ // append chunk to buffer
+ this._pendingData += data.length;
+ this._writeBuffer.push(data);
+ this._callbacks.push(undefined);
+
+ // increase recursion counter
+ this._syncCalls++;
+ // exit early if another writeSync loop is active
+ if (this._isSyncWriting) {
+ return;
+ }
+ this._isSyncWriting = true;
+
+ // force sync processing on pending data chunks to avoid in-band data scrambling
+ // does the same as innerWrite but without event loop
+ // we have to do it here as single loop steps to not corrupt loop subject
+ // by another writeSync call triggered from _action
+ let chunk: string | Uint8Array | undefined;
+ while (chunk = this._writeBuffer.shift()) {
+ this._action(chunk);
+ const cb = this._callbacks.shift();
+ if (cb) cb();
+ }
+ // reset to avoid reprocessing of chunks with scheduled innerWrite call
+ // stopping scheduled innerWrite by offset > length condition
+ this._pendingData = 0;
+ this._bufferOffset = 0x7FFFFFFF;
+
+ // allow another writeSync to loop
+ this._isSyncWriting = false;
+ this._syncCalls = 0;
+ }
+
+ public write(data: string | Uint8Array, callback?: () => void): void {
+ if (this._pendingData > DISCARD_WATERMARK) {
+ throw new Error('write data discarded, use flow control to avoid losing data');
+ }
+
+ // schedule chunk processing for next event loop run
+ if (!this._writeBuffer.length) {
+ this._bufferOffset = 0;
+ setTimeout(() => this._innerWrite());
+ }
+
+ this._pendingData += data.length;
+ this._writeBuffer.push(data);
+ this._callbacks.push(callback);
+ }
+
+ /**
+ * Inner write call, that enters the sliced chunk processing by timing.
+ *
+ * `lastTime` indicates, when the last _innerWrite call had started.
+ * It is used to aggregate async handler execution under a timeout constraint
+ * effectively lowering the redrawing needs, schematically:
+ *
+ * macroTask _innerWrite:
+ * if (Date.now() - (lastTime | 0) < WRITE_TIMEOUT_MS):
+ * schedule microTask _innerWrite(lastTime)
+ * else:
+ * schedule macroTask _innerWrite(0)
+ *
+ * overall execution order on task queues:
+ *
+ * macrotasks: [...] --> _innerWrite(0) --> [...] --> screenUpdate --> [...]
+ * m t: |
+ * i a: [...]
+ * c s: |
+ * r k: while < timeout:
+ * o s: _innerWrite(timeout)
+ *
+ * `promiseResult` depicts the promise resolve value of an async handler.
+ * This value gets carried forward through all saved stack states of the
+ * paused parser for proper continuation.
+ *
+ * Note, for pure sync code `lastTime` and `promiseResult` have no meaning.
+ */
+ protected _innerWrite(lastTime: number = 0, promiseResult: boolean = true): void {
+ const startTime = lastTime || Date.now();
+ while (this._writeBuffer.length > this._bufferOffset) {
+ const data = this._writeBuffer[this._bufferOffset];
+ const result = this._action(data, promiseResult);
+ if (result) {
+ /**
+ * If we get a promise as return value, we re-schedule the continuation
+ * as thenable on the promise and exit right away.
+ *
+ * The exit here means, that we block input processing at the current active chunk,
+ * the exact execution position within the chunk is preserved by the saved
+ * stack content in InputHandler and EscapeSequenceParser.
+ *
+ * Resuming happens automatically from that saved stack state.
+ * Also the resolved promise value is passed along the callstack to
+ * `EscapeSequenceParser.parse` to correctly resume the stopped handler loop.
+ *
+ * Exceptions on async handlers will be logged to console async, but do not interrupt
+ * the input processing (continues with next handler at the current input position).
+ */
+
+ /**
+ * If a promise takes long to resolve, we should schedule continuation behind setTimeout.
+ * This might already be too late, if our .then enters really late (executor + prev thens took very long).
+ * This cannot be solved here for the handler itself (it is the handlers responsibility to slice hard work),
+ * but we can at least schedule a screen update as we gain control.
+ */
+ const continuation: (r: boolean) => void = (r: boolean) => Date.now() - startTime >= WRITE_TIMEOUT_MS
+ ? setTimeout(() => this._innerWrite(0, r))
+ : this._innerWrite(startTime, r);
+
+ /**
+ * Optimization considerations:
+ * The continuation above favors FPS over throughput by eval'ing `startTime` on resolve.
+ * This might schedule too many screen updates with bad throughput drops (in case a slow
+ * resolving handler sliced its work properly behind setTimeout calls). We cannot spot
+ * this condition here, also the renderer has no way to spot nonsense updates either.
+ * FIXME: A proper fix for this would track the FPS at the renderer entry level separately.
+ *
+ * If favoring of FPS shows bad throughtput impact, use the following instead. It favors
+ * throughput by eval'ing `startTime` upfront pulling at least one more chunk into the
+ * current microtask queue (executed before setTimeout).
+ */
+ // const continuation: (r: boolean) => void = Date.now() - startTime >= WRITE_TIMEOUT_MS
+ // ? r => setTimeout(() => this._innerWrite(0, r))
+ // : r => this._innerWrite(startTime, r);
+
+ // Handle exceptions synchronously to current band position, idea:
+ // 1. spawn a single microtask which we allow to throw hard
+ // 2. spawn a promise immediately resolving to `true`
+ // (executed on the same queue, thus properly aligned before continuation happens)
+ result.catch(err => {
+ qmt(() => {throw err;});
+ return Promise.resolve(false);
+ }).then(continuation);
+ return;
+ }
+
+ const cb = this._callbacks[this._bufferOffset];
+ if (cb) cb();
+ this._bufferOffset++;
+ this._pendingData -= data.length;
+
+ if (Date.now() - startTime >= WRITE_TIMEOUT_MS) {
+ break;
+ }
+ }
+ if (this._writeBuffer.length > this._bufferOffset) {
+ // Allow renderer to catch up before processing the next batch
+ // trim already processed chunks if we are above threshold
+ if (this._bufferOffset > WRITE_BUFFER_LENGTH_THRESHOLD) {
+ this._writeBuffer = this._writeBuffer.slice(this._bufferOffset);
+ this._callbacks = this._callbacks.slice(this._bufferOffset);
+ this._bufferOffset = 0;
+ }
+ setTimeout(() => this._innerWrite());
+ } else {
+ this._writeBuffer.length = 0;
+ this._callbacks.length = 0;
+ this._pendingData = 0;
+ this._bufferOffset = 0;
+ }
+ }
+}
diff --git a/node_modules/xterm/src/common/input/XParseColor.ts b/node_modules/xterm/src/common/input/XParseColor.ts
new file mode 100644
index 0000000..8c023a3
--- /dev/null
+++ b/node_modules/xterm/src/common/input/XParseColor.ts
@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2021 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+
+// '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})$/;
+// '#...' rule - matching any hex digits
+const HASH_REX = /^[\da-f]+$/;
+
+/**
+ * Parse color spec to RGB values (8 bit per channel).
+ * See `man xparsecolor` for details about certain format specifications.
+ *
+ * Supported formats:
+ * - rgb:<red>/<green>/<blue> with <red>, <green>, <blue> in h | hh | hhh | hhhh
+ * - #RGB, #RRGGBB, #RRRGGGBBB, #RRRRGGGGBBBB
+ *
+ * All other formats like rgbi: or device-independent string specifications
+ * with float numbering are not supported.
+ */
+export function parseColor(data: string): [number, number, number] | undefined {
+ if (!data) return;
+ // also handle uppercases
+ let low = data.toLowerCase();
+ if (low.indexOf('rgb:') === 0) {
+ // 'rgb:' specifier
+ low = low.slice(4);
+ const m = RGB_REX.exec(low);
+ if (m) {
+ const base = m[1] ? 15 : m[4] ? 255 : m[7] ? 4095 : 65535;
+ return [
+ Math.round(parseInt(m[1] || m[4] || m[7] || m[10], 16) / base * 255),
+ Math.round(parseInt(m[2] || m[5] || m[8] || m[11], 16) / base * 255),
+ Math.round(parseInt(m[3] || m[6] || m[9] || m[12], 16) / base * 255)
+ ];
+ }
+ } else if (low.indexOf('#') === 0) {
+ // '#' specifier
+ low = low.slice(1);
+ if (HASH_REX.exec(low) && [3, 6, 9, 12].includes(low.length)) {
+ const adv = low.length / 3;
+ const result: [number, number, number] = [0, 0, 0];
+ for (let i = 0; i < 3; ++i) {
+ const c = parseInt(low.slice(adv * i, adv * i + adv), 16);
+ result[i] = adv === 1 ? c << 4 : adv === 2 ? c : adv === 3 ? c >> 4 : c >> 8;
+ }
+ return result;
+ }
+ }
+
+ // Named colors are currently not supported due to the large addition to the xterm.js bundle size
+ // they would add. In order to support named colors, we would need some way of optionally loading
+ // additional payloads so startup/download time is not bloated (see #3530).
+}
+
+// pad hex output to requested bit width
+function pad(n: number, bits: number): string {
+ const s = n.toString(16);
+ const s2 = s.length < 2 ? '0' + s : s;
+ switch (bits) {
+ case 4:
+ return s[0];
+ case 8:
+ return s2;
+ case 12:
+ return (s2 + s2).slice(0, 3);
+ default:
+ return s2 + s2;
+ }
+}
+
+/**
+ * Convert a given color to rgb:../../.. string of `bits` depth.
+ */
+export function toRgbString(color: [number, number, number], bits: number = 16): string {
+ const [r, g, b] = color;
+ return `rgb:${pad(r, bits)}/${pad(g, bits)}/${pad(b, bits)}`;
+}
diff --git a/node_modules/xterm/src/common/parser/Constants.ts b/node_modules/xterm/src/common/parser/Constants.ts
new file mode 100644
index 0000000..85156c3
--- /dev/null
+++ b/node_modules/xterm/src/common/parser/Constants.ts
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+/**
+ * Internal states of EscapeSequenceParser.
+ */
+export const enum ParserState {
+ GROUND = 0,
+ ESCAPE = 1,
+ ESCAPE_INTERMEDIATE = 2,
+ CSI_ENTRY = 3,
+ CSI_PARAM = 4,
+ CSI_INTERMEDIATE = 5,
+ CSI_IGNORE = 6,
+ SOS_PM_APC_STRING = 7,
+ OSC_STRING = 8,
+ DCS_ENTRY = 9,
+ DCS_PARAM = 10,
+ DCS_IGNORE = 11,
+ DCS_INTERMEDIATE = 12,
+ DCS_PASSTHROUGH = 13
+}
+
+/**
+* Internal actions of EscapeSequenceParser.
+*/
+export const enum ParserAction {
+ IGNORE = 0,
+ ERROR = 1,
+ PRINT = 2,
+ EXECUTE = 3,
+ OSC_START = 4,
+ OSC_PUT = 5,
+ OSC_END = 6,
+ CSI_DISPATCH = 7,
+ PARAM = 8,
+ COLLECT = 9,
+ ESC_DISPATCH = 10,
+ CLEAR = 11,
+ DCS_HOOK = 12,
+ DCS_PUT = 13,
+ DCS_UNHOOK = 14
+}
+
+/**
+ * Internal states of OscParser.
+ */
+export const enum OscState {
+ START = 0,
+ ID = 1,
+ PAYLOAD = 2,
+ ABORT = 3
+}
+
+// payload limit for OSC and DCS
+export const PAYLOAD_LIMIT = 10000000;
diff --git a/node_modules/xterm/src/common/parser/DcsParser.ts b/node_modules/xterm/src/common/parser/DcsParser.ts
new file mode 100644
index 0000000..b66524b
--- /dev/null
+++ b/node_modules/xterm/src/common/parser/DcsParser.ts
@@ -0,0 +1,192 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IDisposable } from 'common/Types';
+import { IDcsHandler, IParams, IHandlerCollection, IDcsParser, DcsFallbackHandlerType, ISubParserStackState } from 'common/parser/Types';
+import { utf32ToString } from 'common/input/TextDecoder';
+import { Params } from 'common/parser/Params';
+import { PAYLOAD_LIMIT } from 'common/parser/Constants';
+
+const EMPTY_HANDLERS: IDcsHandler[] = [];
+
+export class DcsParser implements IDcsParser {
+ private _handlers: IHandlerCollection<IDcsHandler> = Object.create(null);
+ private _active: IDcsHandler[] = EMPTY_HANDLERS;
+ private _ident: number = 0;
+ private _handlerFb: DcsFallbackHandlerType = () => { };
+ private _stack: ISubParserStackState = {
+ paused: false,
+ loopPosition: 0,
+ fallThrough: false
+ };
+
+ public dispose(): void {
+ this._handlers = Object.create(null);
+ this._handlerFb = () => { };
+ this._active = EMPTY_HANDLERS;
+ }
+
+ public registerHandler(ident: number, handler: IDcsHandler): IDisposable {
+ if (this._handlers[ident] === undefined) {
+ this._handlers[ident] = [];
+ }
+ const handlerList = this._handlers[ident];
+ handlerList.push(handler);
+ return {
+ dispose: () => {
+ const handlerIndex = handlerList.indexOf(handler);
+ if (handlerIndex !== -1) {
+ handlerList.splice(handlerIndex, 1);
+ }
+ }
+ };
+ }
+
+ public clearHandler(ident: number): void {
+ if (this._handlers[ident]) delete this._handlers[ident];
+ }
+
+ public setHandlerFallback(handler: DcsFallbackHandlerType): void {
+ this._handlerFb = handler;
+ }
+
+ public reset(): void {
+ // force cleanup leftover handlers
+ if (this._active.length) {
+ for (let j = this._stack.paused ? this._stack.loopPosition - 1 : this._active.length - 1; j >= 0; --j) {
+ this._active[j].unhook(false);
+ }
+ }
+ this._stack.paused = false;
+ this._active = EMPTY_HANDLERS;
+ this._ident = 0;
+ }
+
+ public hook(ident: number, params: IParams): void {
+ // always reset leftover handlers
+ this.reset();
+ this._ident = ident;
+ this._active = this._handlers[ident] || EMPTY_HANDLERS;
+ if (!this._active.length) {
+ this._handlerFb(this._ident, 'HOOK', params);
+ } else {
+ for (let j = this._active.length - 1; j >= 0; j--) {
+ this._active[j].hook(params);
+ }
+ }
+ }
+
+ public put(data: Uint32Array, start: number, end: number): void {
+ if (!this._active.length) {
+ this._handlerFb(this._ident, 'PUT', utf32ToString(data, start, end));
+ } else {
+ for (let j = this._active.length - 1; j >= 0; j--) {
+ this._active[j].put(data, start, end);
+ }
+ }
+ }
+
+ public unhook(success: boolean, promiseResult: boolean = true): void | Promise<boolean> {
+ if (!this._active.length) {
+ this._handlerFb(this._ident, 'UNHOOK', success);
+ } else {
+ let handlerResult: boolean | Promise<boolean> = false;
+ let j = this._active.length - 1;
+ let fallThrough = false;
+ if (this._stack.paused) {
+ j = this._stack.loopPosition - 1;
+ handlerResult = promiseResult;
+ fallThrough = this._stack.fallThrough;
+ this._stack.paused = false;
+ }
+ if (!fallThrough && handlerResult === false) {
+ for (; j >= 0; j--) {
+ handlerResult = this._active[j].unhook(success);
+ if (handlerResult === true) {
+ break;
+ } else if (handlerResult instanceof Promise) {
+ this._stack.paused = true;
+ this._stack.loopPosition = j;
+ this._stack.fallThrough = false;
+ return handlerResult;
+ }
+ }
+ j--;
+ }
+ // cleanup left over handlers (fallThrough for async)
+ for (; j >= 0; j--) {
+ handlerResult = this._active[j].unhook(false);
+ if (handlerResult instanceof Promise) {
+ this._stack.paused = true;
+ this._stack.loopPosition = j;
+ this._stack.fallThrough = true;
+ return handlerResult;
+ }
+ }
+ }
+ this._active = EMPTY_HANDLERS;
+ this._ident = 0;
+ }
+}
+
+// predefine empty params as [0] (ZDM)
+const EMPTY_PARAMS = new Params();
+EMPTY_PARAMS.addParam(0);
+
+/**
+ * Convenient class to create a DCS handler from a single callback function.
+ * Note: The payload is currently limited to 50 MB (hardcoded).
+ */
+export class DcsHandler implements IDcsHandler {
+ private _data = '';
+ private _params: IParams = EMPTY_PARAMS;
+ private _hitLimit: boolean = false;
+
+ constructor(private _handler: (data: string, params: IParams) => boolean | Promise<boolean>) { }
+
+ public hook(params: IParams): void {
+ // since we need to preserve params until `unhook`, we have to clone it
+ // (only borrowed from parser and spans multiple parser states)
+ // perf optimization:
+ // clone only, if we have non empty params, otherwise stick with default
+ this._params = (params.length > 1 || params.params[0]) ? params.clone() : EMPTY_PARAMS;
+ this._data = '';
+ this._hitLimit = false;
+ }
+
+ public put(data: Uint32Array, start: number, end: number): void {
+ if (this._hitLimit) {
+ return;
+ }
+ this._data += utf32ToString(data, start, end);
+ if (this._data.length > PAYLOAD_LIMIT) {
+ this._data = '';
+ this._hitLimit = true;
+ }
+ }
+
+ public unhook(success: boolean): boolean | Promise<boolean> {
+ let ret: boolean | Promise<boolean> = false;
+ if (this._hitLimit) {
+ ret = false;
+ } else if (success) {
+ ret = this._handler(this._data, this._params);
+ if (ret instanceof Promise) {
+ // need to hold data and params until `ret` got resolved
+ // dont care for errors, data will be freed anyway on next start
+ return ret.then(res => {
+ this._params = EMPTY_PARAMS;
+ this._data = '';
+ this._hitLimit = false;
+ return res;
+ });
+ }
+ }
+ this._params = EMPTY_PARAMS;
+ this._data = '';
+ this._hitLimit = false;
+ return ret;
+ }
+}
diff --git a/node_modules/xterm/src/common/parser/EscapeSequenceParser.ts b/node_modules/xterm/src/common/parser/EscapeSequenceParser.ts
new file mode 100644
index 0000000..f20a7e9
--- /dev/null
+++ b/node_modules/xterm/src/common/parser/EscapeSequenceParser.ts
@@ -0,0 +1,796 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IParsingState, IDcsHandler, IEscapeSequenceParser, IParams, IOscHandler, IHandlerCollection, CsiHandlerType, OscFallbackHandlerType, IOscParser, EscHandlerType, IDcsParser, DcsFallbackHandlerType, IFunctionIdentifier, ExecuteFallbackHandlerType, CsiFallbackHandlerType, EscFallbackHandlerType, PrintHandlerType, PrintFallbackHandlerType, ExecuteHandlerType, IParserStackState, ParserStackType, ResumableHandlersType } from 'common/parser/Types';
+import { ParserState, ParserAction } from 'common/parser/Constants';
+import { Disposable } from 'common/Lifecycle';
+import { IDisposable } from 'common/Types';
+import { fill } from 'common/TypedArrayUtils';
+import { Params } from 'common/parser/Params';
+import { OscParser } from 'common/parser/OscParser';
+import { DcsParser } from 'common/parser/DcsParser';
+
+/**
+ * Table values are generated like this:
+ * index: currentState << TableValue.INDEX_STATE_SHIFT | charCode
+ * value: action << TableValue.TRANSITION_ACTION_SHIFT | nextState
+ */
+const enum TableAccess {
+ TRANSITION_ACTION_SHIFT = 4,
+ TRANSITION_STATE_MASK = 15,
+ INDEX_STATE_SHIFT = 8
+}
+
+/**
+ * Transition table for EscapeSequenceParser.
+ */
+export class TransitionTable {
+ public table: Uint8Array;
+
+ constructor(length: number) {
+ this.table = new Uint8Array(length);
+ }
+
+ /**
+ * Set default transition.
+ * @param action default action
+ * @param next default next state
+ */
+ public setDefault(action: ParserAction, next: ParserState): void {
+ fill(this.table, action << TableAccess.TRANSITION_ACTION_SHIFT | next);
+ }
+
+ /**
+ * Add a transition to the transition table.
+ * @param code input character code
+ * @param state current parser state
+ * @param action parser action to be done
+ * @param next next parser state
+ */
+ public add(code: number, state: ParserState, action: ParserAction, next: ParserState): void {
+ this.table[state << TableAccess.INDEX_STATE_SHIFT | code] = action << TableAccess.TRANSITION_ACTION_SHIFT | next;
+ }
+
+ /**
+ * Add transitions for multiple input character codes.
+ * @param codes input character code array
+ * @param state current parser state
+ * @param action parser action to be done
+ * @param next next parser state
+ */
+ public addMany(codes: number[], state: ParserState, action: ParserAction, next: ParserState): void {
+ for (let i = 0; i < codes.length; i++) {
+ this.table[state << TableAccess.INDEX_STATE_SHIFT | codes[i]] = action << TableAccess.TRANSITION_ACTION_SHIFT | next;
+ }
+ }
+}
+
+
+// Pseudo-character placeholder for printable non-ascii characters (unicode).
+const NON_ASCII_PRINTABLE = 0xA0;
+
+
+/**
+ * VT500 compatible transition table.
+ * Taken from https://vt100.net/emu/dec_ansi_parser.
+ */
+export const VT500_TRANSITION_TABLE = (function (): TransitionTable {
+ const table: TransitionTable = new TransitionTable(4095);
+
+ // range macro for byte
+ const BYTE_VALUES = 256;
+ const blueprint = Array.apply(null, Array(BYTE_VALUES)).map((unused: any, i: number) => i);
+ const r = (start: number, end: number): number[] => blueprint.slice(start, end);
+
+ // Default definitions.
+ const PRINTABLES = r(0x20, 0x7f); // 0x20 (SP) included, 0x7F (DEL) excluded
+ const EXECUTABLES = r(0x00, 0x18);
+ EXECUTABLES.push(0x19);
+ EXECUTABLES.push.apply(EXECUTABLES, r(0x1c, 0x20));
+
+ const states: number[] = r(ParserState.GROUND, ParserState.DCS_PASSTHROUGH + 1);
+ let state: any;
+
+ // set default transition
+ table.setDefault(ParserAction.ERROR, ParserState.GROUND);
+ // printables
+ table.addMany(PRINTABLES, ParserState.GROUND, ParserAction.PRINT, ParserState.GROUND);
+ // global anywhere rules
+ for (state in states) {
+ table.addMany([0x18, 0x1a, 0x99, 0x9a], state, ParserAction.EXECUTE, ParserState.GROUND);
+ table.addMany(r(0x80, 0x90), state, ParserAction.EXECUTE, ParserState.GROUND);
+ table.addMany(r(0x90, 0x98), state, ParserAction.EXECUTE, ParserState.GROUND);
+ table.add(0x9c, state, ParserAction.IGNORE, ParserState.GROUND); // ST as terminator
+ table.add(0x1b, state, ParserAction.CLEAR, ParserState.ESCAPE); // ESC
+ table.add(0x9d, state, ParserAction.OSC_START, ParserState.OSC_STRING); // OSC
+ table.addMany([0x98, 0x9e, 0x9f], state, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);
+ table.add(0x9b, state, ParserAction.CLEAR, ParserState.CSI_ENTRY); // CSI
+ table.add(0x90, state, ParserAction.CLEAR, ParserState.DCS_ENTRY); // DCS
+ }
+ // rules for executables and 7f
+ table.addMany(EXECUTABLES, ParserState.GROUND, ParserAction.EXECUTE, ParserState.GROUND);
+ table.addMany(EXECUTABLES, ParserState.ESCAPE, ParserAction.EXECUTE, ParserState.ESCAPE);
+ table.add(0x7f, ParserState.ESCAPE, ParserAction.IGNORE, ParserState.ESCAPE);
+ table.addMany(EXECUTABLES, ParserState.OSC_STRING, ParserAction.IGNORE, ParserState.OSC_STRING);
+ table.addMany(EXECUTABLES, ParserState.CSI_ENTRY, ParserAction.EXECUTE, ParserState.CSI_ENTRY);
+ table.add(0x7f, ParserState.CSI_ENTRY, ParserAction.IGNORE, ParserState.CSI_ENTRY);
+ table.addMany(EXECUTABLES, ParserState.CSI_PARAM, ParserAction.EXECUTE, ParserState.CSI_PARAM);
+ table.add(0x7f, ParserState.CSI_PARAM, ParserAction.IGNORE, ParserState.CSI_PARAM);
+ table.addMany(EXECUTABLES, ParserState.CSI_IGNORE, ParserAction.EXECUTE, ParserState.CSI_IGNORE);
+ table.addMany(EXECUTABLES, ParserState.CSI_INTERMEDIATE, ParserAction.EXECUTE, ParserState.CSI_INTERMEDIATE);
+ table.add(0x7f, ParserState.CSI_INTERMEDIATE, ParserAction.IGNORE, ParserState.CSI_INTERMEDIATE);
+ table.addMany(EXECUTABLES, ParserState.ESCAPE_INTERMEDIATE, ParserAction.EXECUTE, ParserState.ESCAPE_INTERMEDIATE);
+ table.add(0x7f, ParserState.ESCAPE_INTERMEDIATE, ParserAction.IGNORE, ParserState.ESCAPE_INTERMEDIATE);
+ // osc
+ table.add(0x5d, ParserState.ESCAPE, ParserAction.OSC_START, ParserState.OSC_STRING);
+ table.addMany(PRINTABLES, ParserState.OSC_STRING, ParserAction.OSC_PUT, ParserState.OSC_STRING);
+ table.add(0x7f, ParserState.OSC_STRING, ParserAction.OSC_PUT, ParserState.OSC_STRING);
+ table.addMany([0x9c, 0x1b, 0x18, 0x1a, 0x07], ParserState.OSC_STRING, ParserAction.OSC_END, ParserState.GROUND);
+ table.addMany(r(0x1c, 0x20), ParserState.OSC_STRING, ParserAction.IGNORE, ParserState.OSC_STRING);
+ // sos/pm/apc does nothing
+ table.addMany([0x58, 0x5e, 0x5f], ParserState.ESCAPE, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);
+ table.addMany(PRINTABLES, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);
+ table.addMany(EXECUTABLES, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);
+ table.add(0x9c, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.GROUND);
+ table.add(0x7f, ParserState.SOS_PM_APC_STRING, ParserAction.IGNORE, ParserState.SOS_PM_APC_STRING);
+ // csi entries
+ table.add(0x5b, ParserState.ESCAPE, ParserAction.CLEAR, ParserState.CSI_ENTRY);
+ table.addMany(r(0x40, 0x7f), ParserState.CSI_ENTRY, ParserAction.CSI_DISPATCH, ParserState.GROUND);
+ table.addMany(r(0x30, 0x3c), ParserState.CSI_ENTRY, ParserAction.PARAM, ParserState.CSI_PARAM);
+ table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.CSI_ENTRY, ParserAction.COLLECT, ParserState.CSI_PARAM);
+ table.addMany(r(0x30, 0x3c), ParserState.CSI_PARAM, ParserAction.PARAM, ParserState.CSI_PARAM);
+ table.addMany(r(0x40, 0x7f), ParserState.CSI_PARAM, ParserAction.CSI_DISPATCH, ParserState.GROUND);
+ table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.CSI_PARAM, ParserAction.IGNORE, ParserState.CSI_IGNORE);
+ table.addMany(r(0x20, 0x40), ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.CSI_IGNORE);
+ table.add(0x7f, ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.CSI_IGNORE);
+ table.addMany(r(0x40, 0x7f), ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.GROUND);
+ table.addMany(r(0x20, 0x30), ParserState.CSI_ENTRY, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE);
+ table.addMany(r(0x20, 0x30), ParserState.CSI_INTERMEDIATE, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE);
+ table.addMany(r(0x30, 0x40), ParserState.CSI_INTERMEDIATE, ParserAction.IGNORE, ParserState.CSI_IGNORE);
+ table.addMany(r(0x40, 0x7f), ParserState.CSI_INTERMEDIATE, ParserAction.CSI_DISPATCH, ParserState.GROUND);
+ table.addMany(r(0x20, 0x30), ParserState.CSI_PARAM, ParserAction.COLLECT, ParserState.CSI_INTERMEDIATE);
+ // esc_intermediate
+ table.addMany(r(0x20, 0x30), ParserState.ESCAPE, ParserAction.COLLECT, ParserState.ESCAPE_INTERMEDIATE);
+ table.addMany(r(0x20, 0x30), ParserState.ESCAPE_INTERMEDIATE, ParserAction.COLLECT, ParserState.ESCAPE_INTERMEDIATE);
+ table.addMany(r(0x30, 0x7f), ParserState.ESCAPE_INTERMEDIATE, ParserAction.ESC_DISPATCH, ParserState.GROUND);
+ table.addMany(r(0x30, 0x50), ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND);
+ table.addMany(r(0x51, 0x58), ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND);
+ table.addMany([0x59, 0x5a, 0x5c], ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND);
+ table.addMany(r(0x60, 0x7f), ParserState.ESCAPE, ParserAction.ESC_DISPATCH, ParserState.GROUND);
+ // dcs entry
+ table.add(0x50, ParserState.ESCAPE, ParserAction.CLEAR, ParserState.DCS_ENTRY);
+ table.addMany(EXECUTABLES, ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY);
+ table.add(0x7f, ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY);
+ table.addMany(r(0x1c, 0x20), ParserState.DCS_ENTRY, ParserAction.IGNORE, ParserState.DCS_ENTRY);
+ table.addMany(r(0x20, 0x30), ParserState.DCS_ENTRY, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE);
+ table.addMany(r(0x30, 0x3c), ParserState.DCS_ENTRY, ParserAction.PARAM, ParserState.DCS_PARAM);
+ table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.DCS_ENTRY, ParserAction.COLLECT, ParserState.DCS_PARAM);
+ table.addMany(EXECUTABLES, ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE);
+ table.addMany(r(0x20, 0x80), ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE);
+ table.addMany(r(0x1c, 0x20), ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE);
+ table.addMany(EXECUTABLES, ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM);
+ table.add(0x7f, ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM);
+ table.addMany(r(0x1c, 0x20), ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_PARAM);
+ table.addMany(r(0x30, 0x3c), ParserState.DCS_PARAM, ParserAction.PARAM, ParserState.DCS_PARAM);
+ table.addMany([0x3c, 0x3d, 0x3e, 0x3f], ParserState.DCS_PARAM, ParserAction.IGNORE, ParserState.DCS_IGNORE);
+ table.addMany(r(0x20, 0x30), ParserState.DCS_PARAM, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE);
+ table.addMany(EXECUTABLES, ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE);
+ table.add(0x7f, ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE);
+ table.addMany(r(0x1c, 0x20), ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_INTERMEDIATE);
+ table.addMany(r(0x20, 0x30), ParserState.DCS_INTERMEDIATE, ParserAction.COLLECT, ParserState.DCS_INTERMEDIATE);
+ table.addMany(r(0x30, 0x40), ParserState.DCS_INTERMEDIATE, ParserAction.IGNORE, ParserState.DCS_IGNORE);
+ table.addMany(r(0x40, 0x7f), ParserState.DCS_INTERMEDIATE, ParserAction.DCS_HOOK, ParserState.DCS_PASSTHROUGH);
+ table.addMany(r(0x40, 0x7f), ParserState.DCS_PARAM, ParserAction.DCS_HOOK, ParserState.DCS_PASSTHROUGH);
+ table.addMany(r(0x40, 0x7f), ParserState.DCS_ENTRY, ParserAction.DCS_HOOK, ParserState.DCS_PASSTHROUGH);
+ table.addMany(EXECUTABLES, ParserState.DCS_PASSTHROUGH, ParserAction.DCS_PUT, ParserState.DCS_PASSTHROUGH);
+ table.addMany(PRINTABLES, ParserState.DCS_PASSTHROUGH, ParserAction.DCS_PUT, ParserState.DCS_PASSTHROUGH);
+ table.add(0x7f, ParserState.DCS_PASSTHROUGH, ParserAction.IGNORE, ParserState.DCS_PASSTHROUGH);
+ table.addMany([0x1b, 0x9c, 0x18, 0x1a], ParserState.DCS_PASSTHROUGH, ParserAction.DCS_UNHOOK, ParserState.GROUND);
+ // special handling of unicode chars
+ table.add(NON_ASCII_PRINTABLE, ParserState.GROUND, ParserAction.PRINT, ParserState.GROUND);
+ table.add(NON_ASCII_PRINTABLE, ParserState.OSC_STRING, ParserAction.OSC_PUT, ParserState.OSC_STRING);
+ table.add(NON_ASCII_PRINTABLE, ParserState.CSI_IGNORE, ParserAction.IGNORE, ParserState.CSI_IGNORE);
+ table.add(NON_ASCII_PRINTABLE, ParserState.DCS_IGNORE, ParserAction.IGNORE, ParserState.DCS_IGNORE);
+ table.add(NON_ASCII_PRINTABLE, ParserState.DCS_PASSTHROUGH, ParserAction.DCS_PUT, ParserState.DCS_PASSTHROUGH);
+ return table;
+})();
+
+
+/**
+ * EscapeSequenceParser.
+ * This class implements the ANSI/DEC compatible parser described by
+ * Paul Williams (https://vt100.net/emu/dec_ansi_parser).
+ *
+ * To implement custom ANSI compliant escape sequences it is not needed to
+ * alter this parser, instead consider registering a custom handler.
+ * For non ANSI compliant sequences change the transition table with
+ * the optional `transitions` constructor argument and
+ * reimplement the `parse` method.
+ *
+ * This parser is currently hardcoded to operate in ZDM (Zero Default Mode)
+ * as suggested by the original parser, thus empty parameters are set to 0.
+ * This this is not in line with the latest ECMA-48 specification
+ * (ZDM was part of the early specs and got completely removed later on).
+ *
+ * Other than the original parser from vt100.net this parser supports
+ * sub parameters in digital parameters separated by colons. Empty sub parameters
+ * are set to -1 (no ZDM for sub parameters).
+ *
+ * About prefix and intermediate bytes:
+ * This parser follows the assumptions of the vt100.net parser with these restrictions:
+ * - only one prefix byte is allowed as first parameter byte, byte range 0x3c .. 0x3f
+ * - max. two intermediates are respected, byte range 0x20 .. 0x2f
+ * Note that this is not in line with ECMA-48 which does not limit either of those.
+ * Furthermore ECMA-48 allows the prefix byte range at any param byte position. Currently
+ * there are no known sequences that follow the broader definition of the specification.
+ *
+ * TODO: implement error recovery hook via error handler return values
+ */
+export class EscapeSequenceParser extends Disposable implements IEscapeSequenceParser {
+ public initialState: number;
+ public currentState: number;
+ public precedingCodepoint: number;
+
+ // buffers over several parse calls
+ protected _params: Params;
+ protected _collect: number;
+
+ // handler lookup containers
+ protected _printHandler: PrintHandlerType;
+ protected _executeHandlers: { [flag: number]: ExecuteHandlerType };
+ protected _csiHandlers: IHandlerCollection<CsiHandlerType>;
+ protected _escHandlers: IHandlerCollection<EscHandlerType>;
+ protected _oscParser: IOscParser;
+ protected _dcsParser: IDcsParser;
+ protected _errorHandler: (state: IParsingState) => IParsingState;
+
+ // fallback handlers
+ protected _printHandlerFb: PrintFallbackHandlerType;
+ protected _executeHandlerFb: ExecuteFallbackHandlerType;
+ protected _csiHandlerFb: CsiFallbackHandlerType;
+ protected _escHandlerFb: EscFallbackHandlerType;
+ protected _errorHandlerFb: (state: IParsingState) => IParsingState;
+
+ // parser stack save for async handler support
+ protected _parseStack: IParserStackState = {
+ state: ParserStackType.NONE,
+ handlers: [],
+ handlerPos: 0,
+ transition: 0,
+ chunkPos: 0
+ };
+
+ constructor(
+ protected readonly _transitions: TransitionTable = VT500_TRANSITION_TABLE
+ ) {
+ super();
+
+ this.initialState = ParserState.GROUND;
+ this.currentState = this.initialState;
+ this._params = new Params(); // defaults to 32 storable params/subparams
+ this._params.addParam(0); // ZDM
+ this._collect = 0;
+ this.precedingCodepoint = 0;
+
+ // set default fallback handlers and handler lookup containers
+ this._printHandlerFb = (data, start, end): void => { };
+ this._executeHandlerFb = (code: number): void => { };
+ this._csiHandlerFb = (ident: number, params: IParams): void => { };
+ this._escHandlerFb = (ident: number): void => { };
+ this._errorHandlerFb = (state: IParsingState): IParsingState => state;
+ this._printHandler = this._printHandlerFb;
+ this._executeHandlers = Object.create(null);
+ this._csiHandlers = Object.create(null);
+ this._escHandlers = Object.create(null);
+ this._oscParser = new OscParser();
+ this._dcsParser = new DcsParser();
+ this._errorHandler = this._errorHandlerFb;
+
+ // swallow 7bit ST (ESC+\)
+ this.registerEscHandler({ final: '\\' }, () => true);
+ }
+
+ protected _identifier(id: IFunctionIdentifier, finalRange: number[] = [0x40, 0x7e]): number {
+ let res = 0;
+ if (id.prefix) {
+ if (id.prefix.length > 1) {
+ throw new Error('only one byte as prefix supported');
+ }
+ res = id.prefix.charCodeAt(0);
+ if (res && 0x3c > res || res > 0x3f) {
+ throw new Error('prefix must be in range 0x3c .. 0x3f');
+ }
+ }
+ if (id.intermediates) {
+ if (id.intermediates.length > 2) {
+ throw new Error('only two bytes as intermediates are supported');
+ }
+ for (let i = 0; i < id.intermediates.length; ++i) {
+ const intermediate = id.intermediates.charCodeAt(i);
+ if (0x20 > intermediate || intermediate > 0x2f) {
+ throw new Error('intermediate must be in range 0x20 .. 0x2f');
+ }
+ res <<= 8;
+ res |= intermediate;
+ }
+ }
+ if (id.final.length !== 1) {
+ throw new Error('final must be a single byte');
+ }
+ const finalCode = id.final.charCodeAt(0);
+ if (finalRange[0] > finalCode || finalCode > finalRange[1]) {
+ throw new Error(`final must be in range ${finalRange[0]} .. ${finalRange[1]}`);
+ }
+ res <<= 8;
+ res |= finalCode;
+
+ return res;
+ }
+
+ public identToString(ident: number): string {
+ const res: string[] = [];
+ while (ident) {
+ res.push(String.fromCharCode(ident & 0xFF));
+ ident >>= 8;
+ }
+ return res.reverse().join('');
+ }
+
+ public dispose(): void {
+ this._csiHandlers = Object.create(null);
+ this._executeHandlers = Object.create(null);
+ this._escHandlers = Object.create(null);
+ this._oscParser.dispose();
+ this._dcsParser.dispose();
+ }
+
+ public setPrintHandler(handler: PrintHandlerType): void {
+ this._printHandler = handler;
+ }
+ public clearPrintHandler(): void {
+ this._printHandler = this._printHandlerFb;
+ }
+
+ public registerEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): IDisposable {
+ const ident = this._identifier(id, [0x30, 0x7e]);
+ if (this._escHandlers[ident] === undefined) {
+ this._escHandlers[ident] = [];
+ }
+ const handlerList = this._escHandlers[ident];
+ handlerList.push(handler);
+ return {
+ dispose: () => {
+ const handlerIndex = handlerList.indexOf(handler);
+ if (handlerIndex !== -1) {
+ handlerList.splice(handlerIndex, 1);
+ }
+ }
+ };
+ }
+ public clearEscHandler(id: IFunctionIdentifier): void {
+ if (this._escHandlers[this._identifier(id, [0x30, 0x7e])]) delete this._escHandlers[this._identifier(id, [0x30, 0x7e])];
+ }
+ public setEscHandlerFallback(handler: EscFallbackHandlerType): void {
+ this._escHandlerFb = handler;
+ }
+
+ public setExecuteHandler(flag: string, handler: ExecuteHandlerType): void {
+ this._executeHandlers[flag.charCodeAt(0)] = handler;
+ }
+ public clearExecuteHandler(flag: string): void {
+ if (this._executeHandlers[flag.charCodeAt(0)]) delete this._executeHandlers[flag.charCodeAt(0)];
+ }
+ public setExecuteHandlerFallback(handler: ExecuteFallbackHandlerType): void {
+ this._executeHandlerFb = handler;
+ }
+
+ public registerCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): IDisposable {
+ const ident = this._identifier(id);
+ if (this._csiHandlers[ident] === undefined) {
+ this._csiHandlers[ident] = [];
+ }
+ const handlerList = this._csiHandlers[ident];
+ handlerList.push(handler);
+ return {
+ dispose: () => {
+ const handlerIndex = handlerList.indexOf(handler);
+ if (handlerIndex !== -1) {
+ handlerList.splice(handlerIndex, 1);
+ }
+ }
+ };
+ }
+ public clearCsiHandler(id: IFunctionIdentifier): void {
+ if (this._csiHandlers[this._identifier(id)]) delete this._csiHandlers[this._identifier(id)];
+ }
+ public setCsiHandlerFallback(callback: (ident: number, params: IParams) => void): void {
+ this._csiHandlerFb = callback;
+ }
+
+ public registerDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): IDisposable {
+ return this._dcsParser.registerHandler(this._identifier(id), handler);
+ }
+ public clearDcsHandler(id: IFunctionIdentifier): void {
+ this._dcsParser.clearHandler(this._identifier(id));
+ }
+ public setDcsHandlerFallback(handler: DcsFallbackHandlerType): void {
+ this._dcsParser.setHandlerFallback(handler);
+ }
+
+ public registerOscHandler(ident: number, handler: IOscHandler): IDisposable {
+ return this._oscParser.registerHandler(ident, handler);
+ }
+ public clearOscHandler(ident: number): void {
+ this._oscParser.clearHandler(ident);
+ }
+ public setOscHandlerFallback(handler: OscFallbackHandlerType): void {
+ this._oscParser.setHandlerFallback(handler);
+ }
+
+ public setErrorHandler(callback: (state: IParsingState) => IParsingState): void {
+ this._errorHandler = callback;
+ }
+ public clearErrorHandler(): void {
+ this._errorHandler = this._errorHandlerFb;
+ }
+
+ /**
+ * Reset parser to initial values.
+ *
+ * This can also be used to lift the improper continuation error condition
+ * when dealing with async handlers. Use this only as a last resort to silence
+ * that error when the terminal has no pending data to be processed. Note that
+ * the interrupted async handler might continue its work in the future messing
+ * up the terminal state even further.
+ */
+ public reset(): void {
+ this.currentState = this.initialState;
+ this._oscParser.reset();
+ this._dcsParser.reset();
+ this._params.reset();
+ this._params.addParam(0); // ZDM
+ this._collect = 0;
+ this.precedingCodepoint = 0;
+ // abort pending continuation from async handler
+ // Here the RESET type indicates, that the next parse call will
+ // ignore any saved stack, instead continues sync with next codepoint from GROUND
+ if (this._parseStack.state !== ParserStackType.NONE) {
+ this._parseStack.state = ParserStackType.RESET;
+ this._parseStack.handlers = []; // also release handlers ref
+ }
+ }
+
+ /**
+ * Async parse support.
+ */
+ protected _preserveStack(
+ state: ParserStackType,
+ handlers: ResumableHandlersType,
+ handlerPos: number,
+ transition: number,
+ chunkPos: number
+ ): void {
+ this._parseStack.state = state;
+ this._parseStack.handlers = handlers;
+ this._parseStack.handlerPos = handlerPos;
+ this._parseStack.transition = transition;
+ this._parseStack.chunkPos = chunkPos;
+ }
+
+ /**
+ * Parse UTF32 codepoints in `data` up to `length`.
+ *
+ * Note: For several actions with high data load the parsing is optimized
+ * by using local read ahead loops with hardcoded conditions to
+ * avoid costly table lookups. Make sure that any change of table values
+ * will be reflected in the loop conditions as well and vice versa.
+ * Affected states/actions:
+ * - GROUND:PRINT
+ * - CSI_PARAM:PARAM
+ * - DCS_PARAM:PARAM
+ * - OSC_STRING:OSC_PUT
+ * - DCS_PASSTHROUGH:DCS_PUT
+ *
+ * Note on asynchronous handler support:
+ * Any handler returning a promise will be treated as asynchronous.
+ * To keep the in-band blocking working for async handlers, `parse` pauses execution,
+ * creates a stack save and returns the promise to the caller.
+ * For proper continuation of the paused state it is important
+ * to await the promise resolving. On resolve the parse must be repeated
+ * with the same chunk of data and the resolved value in `promiseResult`
+ * until no promise is returned.
+ *
+ * Important: With only sync handlers defined, parsing is completely synchronous as well.
+ * As soon as an async handler is involved, synchronous parsing is not possible anymore.
+ *
+ * Boilerplate for proper parsing of multiple chunks with async handlers:
+ *
+ * ```typescript
+ * async function parseMultipleChunks(chunks: Uint32Array[]): Promise<void> {
+ * for (const chunk of chunks) {
+ * let result: void | Promise<boolean>;
+ * let prev: boolean | undefined;
+ * while (result = parser.parse(chunk, chunk.length, prev)) {
+ * prev = await result;
+ * }
+ * }
+ * // finished parsing all chunks...
+ * }
+ * ```
+ */
+ public parse(data: Uint32Array, length: number, promiseResult?: boolean): void | Promise<boolean> {
+ let code = 0;
+ let transition = 0;
+ let start = 0;
+ let handlerResult: void | boolean | Promise<boolean>;
+
+ // resume from async handler
+ if (this._parseStack.state) {
+ // allow sync parser reset even in continuation mode
+ // Note: can be used to recover parser from improper continuation error below
+ if (this._parseStack.state === ParserStackType.RESET) {
+ this._parseStack.state = ParserStackType.NONE;
+ start = this._parseStack.chunkPos + 1; // continue with next codepoint in GROUND
+ } else {
+ if (promiseResult === undefined || this._parseStack.state === ParserStackType.FAIL) {
+ /**
+ * Reject further parsing on improper continuation after pausing.
+ * This is a really bad condition with screwed up execution order and prolly messed up
+ * terminal state, therefore we exit hard with an exception and reject any further parsing.
+ *
+ * Note: With `Terminal.write` usage this exception should never occur, as the top level
+ * calls are guaranteed to handle async conditions properly. If you ever encounter this
+ * exception in your terminal integration it indicates, that you injected data chunks to
+ * `InputHandler.parse` or `EscapeSequenceParser.parse` synchronously without waiting for
+ * continuation of a running async handler.
+ *
+ * It is possible to get rid of this error by calling `reset`. But dont rely on that,
+ * as the pending async handler still might mess up the terminal later. Instead fix the faulty
+ * async handling, so this error will not be thrown anymore.
+ */
+ this._parseStack.state = ParserStackType.FAIL;
+ throw new Error('improper continuation due to previous async handler, giving up parsing');
+ }
+
+ // we have to resume the old handler loop if:
+ // - return value of the promise was `false`
+ // - handlers are not exhausted yet
+ const handlers = this._parseStack.handlers;
+ let handlerPos = this._parseStack.handlerPos - 1;
+ switch (this._parseStack.state) {
+ case ParserStackType.CSI:
+ if (promiseResult === false && handlerPos > -1) {
+ for (; handlerPos >= 0; handlerPos--) {
+ handlerResult = (handlers as CsiHandlerType[])[handlerPos](this._params);
+ if (handlerResult === true) {
+ break;
+ } else if (handlerResult instanceof Promise) {
+ this._parseStack.handlerPos = handlerPos;
+ return handlerResult;
+ }
+ }
+ }
+ this._parseStack.handlers = [];
+ break;
+ case ParserStackType.ESC:
+ if (promiseResult === false && handlerPos > -1) {
+ for (; handlerPos >= 0; handlerPos--) {
+ handlerResult = (handlers as EscHandlerType[])[handlerPos]();
+ if (handlerResult === true) {
+ break;
+ } else if (handlerResult instanceof Promise) {
+ this._parseStack.handlerPos = handlerPos;
+ return handlerResult;
+ }
+ }
+ }
+ this._parseStack.handlers = [];
+ break;
+ case ParserStackType.DCS:
+ code = data[this._parseStack.chunkPos];
+ handlerResult = this._dcsParser.unhook(code !== 0x18 && code !== 0x1a, promiseResult);
+ if (handlerResult) {
+ return handlerResult;
+ }
+ if (code === 0x1b) this._parseStack.transition |= ParserState.ESCAPE;
+ this._params.reset();
+ this._params.addParam(0); // ZDM
+ this._collect = 0;
+ break;
+ case ParserStackType.OSC:
+ code = data[this._parseStack.chunkPos];
+ handlerResult = this._oscParser.end(code !== 0x18 && code !== 0x1a, promiseResult);
+ if (handlerResult) {
+ return handlerResult;
+ }
+ if (code === 0x1b) this._parseStack.transition |= ParserState.ESCAPE;
+ this._params.reset();
+ this._params.addParam(0); // ZDM
+ this._collect = 0;
+ break;
+ }
+ // cleanup before continuing with the main sync loop
+ this._parseStack.state = ParserStackType.NONE;
+ start = this._parseStack.chunkPos + 1;
+ this.precedingCodepoint = 0;
+ this.currentState = this._parseStack.transition & TableAccess.TRANSITION_STATE_MASK;
+ }
+ }
+
+ // continue with main sync loop
+
+ // process input string
+ for (let i = start; i < length; ++i) {
+ code = data[i];
+
+ // normal transition & action lookup
+ transition = this._transitions.table[this.currentState << TableAccess.INDEX_STATE_SHIFT | (code < 0xa0 ? code : NON_ASCII_PRINTABLE)];
+ switch (transition >> TableAccess.TRANSITION_ACTION_SHIFT) {
+ case ParserAction.PRINT:
+ // read ahead with loop unrolling
+ // Note: 0x20 (SP) is included, 0x7F (DEL) is excluded
+ for (let j = i + 1; ; ++j) {
+ if (j >= length || (code = data[j]) < 0x20 || (code > 0x7e && code < NON_ASCII_PRINTABLE)) {
+ this._printHandler(data, i, j);
+ i = j - 1;
+ break;
+ }
+ if (++j >= length || (code = data[j]) < 0x20 || (code > 0x7e && code < NON_ASCII_PRINTABLE)) {
+ this._printHandler(data, i, j);
+ i = j - 1;
+ break;
+ }
+ if (++j >= length || (code = data[j]) < 0x20 || (code > 0x7e && code < NON_ASCII_PRINTABLE)) {
+ this._printHandler(data, i, j);
+ i = j - 1;
+ break;
+ }
+ if (++j >= length || (code = data[j]) < 0x20 || (code > 0x7e && code < NON_ASCII_PRINTABLE)) {
+ this._printHandler(data, i, j);
+ i = j - 1;
+ break;
+ }
+ }
+ break;
+ case ParserAction.EXECUTE:
+ if (this._executeHandlers[code]) this._executeHandlers[code]();
+ else this._executeHandlerFb(code);
+ this.precedingCodepoint = 0;
+ break;
+ case ParserAction.IGNORE:
+ break;
+ case ParserAction.ERROR:
+ const inject: IParsingState = this._errorHandler(
+ {
+ position: i,
+ code,
+ currentState: this.currentState,
+ collect: this._collect,
+ params: this._params,
+ abort: false
+ });
+ if (inject.abort) return;
+ // inject values: currently not implemented
+ break;
+ case ParserAction.CSI_DISPATCH:
+ // Trigger CSI Handler
+ const handlers = this._csiHandlers[this._collect << 8 | code];
+ let j = handlers ? handlers.length - 1 : -1;
+ for (; j >= 0; j--) {
+ // true means success and to stop bubbling
+ // a promise indicates an async handler that needs to finish before progressing
+ handlerResult = handlers[j](this._params);
+ if (handlerResult === true) {
+ break;
+ } else if (handlerResult instanceof Promise) {
+ this._preserveStack(ParserStackType.CSI, handlers, j, transition, i);
+ return handlerResult;
+ }
+ }
+ if (j < 0) {
+ this._csiHandlerFb(this._collect << 8 | code, this._params);
+ }
+ this.precedingCodepoint = 0;
+ break;
+ case ParserAction.PARAM:
+ // inner loop: digits (0x30 - 0x39) and ; (0x3b) and : (0x3a)
+ do {
+ switch (code) {
+ case 0x3b:
+ this._params.addParam(0); // ZDM
+ break;
+ case 0x3a:
+ this._params.addSubParam(-1);
+ break;
+ default: // 0x30 - 0x39
+ this._params.addDigit(code - 48);
+ }
+ } while (++i < length && (code = data[i]) > 0x2f && code < 0x3c);
+ i--;
+ break;
+ case ParserAction.COLLECT:
+ this._collect <<= 8;
+ this._collect |= code;
+ break;
+ case ParserAction.ESC_DISPATCH:
+ const handlersEsc = this._escHandlers[this._collect << 8 | code];
+ let jj = handlersEsc ? handlersEsc.length - 1 : -1;
+ for (; jj >= 0; jj--) {
+ // true means success and to stop bubbling
+ // a promise indicates an async handler that needs to finish before progressing
+ handlerResult = handlersEsc[jj]();
+ if (handlerResult === true) {
+ break;
+ } else if (handlerResult instanceof Promise) {
+ this._preserveStack(ParserStackType.ESC, handlersEsc, jj, transition, i);
+ return handlerResult;
+ }
+ }
+ if (jj < 0) {
+ this._escHandlerFb(this._collect << 8 | code);
+ }
+ this.precedingCodepoint = 0;
+ break;
+ case ParserAction.CLEAR:
+ this._params.reset();
+ this._params.addParam(0); // ZDM
+ this._collect = 0;
+ break;
+ case ParserAction.DCS_HOOK:
+ this._dcsParser.hook(this._collect << 8 | code, this._params);
+ break;
+ case ParserAction.DCS_PUT:
+ // inner loop - exit DCS_PUT: 0x18, 0x1a, 0x1b, 0x7f, 0x80 - 0x9f
+ // unhook triggered by: 0x1b, 0x9c (success) and 0x18, 0x1a (abort)
+ for (let j = i + 1; ; ++j) {
+ if (j >= length || (code = data[j]) === 0x18 || code === 0x1a || code === 0x1b || (code > 0x7f && code < NON_ASCII_PRINTABLE)) {
+ this._dcsParser.put(data, i, j);
+ i = j - 1;
+ break;
+ }
+ }
+ break;
+ case ParserAction.DCS_UNHOOK:
+ handlerResult = this._dcsParser.unhook(code !== 0x18 && code !== 0x1a);
+ if (handlerResult) {
+ this._preserveStack(ParserStackType.DCS, [], 0, transition, i);
+ return handlerResult;
+ }
+ if (code === 0x1b) transition |= ParserState.ESCAPE;
+ this._params.reset();
+ this._params.addParam(0); // ZDM
+ this._collect = 0;
+ this.precedingCodepoint = 0;
+ break;
+ case ParserAction.OSC_START:
+ this._oscParser.start();
+ break;
+ case ParserAction.OSC_PUT:
+ // inner loop: 0x20 (SP) included, 0x7F (DEL) included
+ for (let j = i + 1; ; j++) {
+ if (j >= length || (code = data[j]) < 0x20 || (code > 0x7f && code < NON_ASCII_PRINTABLE)) {
+ this._oscParser.put(data, i, j);
+ i = j - 1;
+ break;
+ }
+ }
+ break;
+ case ParserAction.OSC_END:
+ handlerResult = this._oscParser.end(code !== 0x18 && code !== 0x1a);
+ if (handlerResult) {
+ this._preserveStack(ParserStackType.OSC, [], 0, transition, i);
+ return handlerResult;
+ }
+ if (code === 0x1b) transition |= ParserState.ESCAPE;
+ this._params.reset();
+ this._params.addParam(0); // ZDM
+ this._collect = 0;
+ this.precedingCodepoint = 0;
+ break;
+ }
+ this.currentState = transition & TableAccess.TRANSITION_STATE_MASK;
+ }
+ }
+}
diff --git a/node_modules/xterm/src/common/parser/OscParser.ts b/node_modules/xterm/src/common/parser/OscParser.ts
new file mode 100644
index 0000000..32710ae
--- /dev/null
+++ b/node_modules/xterm/src/common/parser/OscParser.ts
@@ -0,0 +1,238 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IOscHandler, IHandlerCollection, OscFallbackHandlerType, IOscParser, ISubParserStackState } from 'common/parser/Types';
+import { OscState, PAYLOAD_LIMIT } from 'common/parser/Constants';
+import { utf32ToString } from 'common/input/TextDecoder';
+import { IDisposable } from 'common/Types';
+
+const EMPTY_HANDLERS: IOscHandler[] = [];
+
+export class OscParser implements IOscParser {
+ private _state = OscState.START;
+ private _active = EMPTY_HANDLERS;
+ private _id = -1;
+ private _handlers: IHandlerCollection<IOscHandler> = Object.create(null);
+ private _handlerFb: OscFallbackHandlerType = () => { };
+ private _stack: ISubParserStackState = {
+ paused: false,
+ loopPosition: 0,
+ fallThrough: false
+ };
+
+ public registerHandler(ident: number, handler: IOscHandler): IDisposable {
+ if (this._handlers[ident] === undefined) {
+ this._handlers[ident] = [];
+ }
+ const handlerList = this._handlers[ident];
+ handlerList.push(handler);
+ return {
+ dispose: () => {
+ const handlerIndex = handlerList.indexOf(handler);
+ if (handlerIndex !== -1) {
+ handlerList.splice(handlerIndex, 1);
+ }
+ }
+ };
+ }
+ public clearHandler(ident: number): void {
+ if (this._handlers[ident]) delete this._handlers[ident];
+ }
+ public setHandlerFallback(handler: OscFallbackHandlerType): void {
+ this._handlerFb = handler;
+ }
+
+ public dispose(): void {
+ this._handlers = Object.create(null);
+ this._handlerFb = () => { };
+ this._active = EMPTY_HANDLERS;
+ }
+
+ public reset(): void {
+ // force cleanup handlers if payload was already sent
+ if (this._state === OscState.PAYLOAD) {
+ for (let j = this._stack.paused ? this._stack.loopPosition - 1 : this._active.length - 1; j >= 0; --j) {
+ this._active[j].end(false);
+ }
+ }
+ this._stack.paused = false;
+ this._active = EMPTY_HANDLERS;
+ this._id = -1;
+ this._state = OscState.START;
+ }
+
+ private _start(): void {
+ this._active = this._handlers[this._id] || EMPTY_HANDLERS;
+ if (!this._active.length) {
+ this._handlerFb(this._id, 'START');
+ } else {
+ for (let j = this._active.length - 1; j >= 0; j--) {
+ this._active[j].start();
+ }
+ }
+ }
+
+ private _put(data: Uint32Array, start: number, end: number): void {
+ if (!this._active.length) {
+ this._handlerFb(this._id, 'PUT', utf32ToString(data, start, end));
+ } else {
+ for (let j = this._active.length - 1; j >= 0; j--) {
+ this._active[j].put(data, start, end);
+ }
+ }
+ }
+
+ public start(): void {
+ // always reset leftover handlers
+ this.reset();
+ this._state = OscState.ID;
+ }
+
+ /**
+ * Put data to current OSC command.
+ * Expects the identifier of the OSC command in the form
+ * OSC id ; payload ST/BEL
+ * Payload chunks are not further processed and get
+ * directly passed to the handlers.
+ */
+ public put(data: Uint32Array, start: number, end: number): void {
+ if (this._state === OscState.ABORT) {
+ return;
+ }
+ if (this._state === OscState.ID) {
+ while (start < end) {
+ const code = data[start++];
+ if (code === 0x3b) {
+ this._state = OscState.PAYLOAD;
+ this._start();
+ break;
+ }
+ if (code < 0x30 || 0x39 < code) {
+ this._state = OscState.ABORT;
+ return;
+ }
+ if (this._id === -1) {
+ this._id = 0;
+ }
+ this._id = this._id * 10 + code - 48;
+ }
+ }
+ if (this._state === OscState.PAYLOAD && end - start > 0) {
+ this._put(data, start, end);
+ }
+ }
+
+ /**
+ * Indicates end of an OSC command.
+ * Whether the OSC got aborted or finished normally
+ * is indicated by `success`.
+ */
+ public end(success: boolean, promiseResult: boolean = true): void | Promise<boolean> {
+ if (this._state === OscState.START) {
+ return;
+ }
+ // do nothing if command was faulty
+ if (this._state !== OscState.ABORT) {
+ // if we are still in ID state and get an early end
+ // means that the command has no payload thus we still have
+ // to announce START and send END right after
+ if (this._state === OscState.ID) {
+ this._start();
+ }
+
+ if (!this._active.length) {
+ this._handlerFb(this._id, 'END', success);
+ } else {
+ let handlerResult: boolean | Promise<boolean> = false;
+ let j = this._active.length - 1;
+ let fallThrough = false;
+ if (this._stack.paused) {
+ j = this._stack.loopPosition - 1;
+ handlerResult = promiseResult;
+ fallThrough = this._stack.fallThrough;
+ this._stack.paused = false;
+ }
+ if (!fallThrough && handlerResult === false) {
+ for (; j >= 0; j--) {
+ handlerResult = this._active[j].end(success);
+ if (handlerResult === true) {
+ break;
+ } else if (handlerResult instanceof Promise) {
+ this._stack.paused = true;
+ this._stack.loopPosition = j;
+ this._stack.fallThrough = false;
+ return handlerResult;
+ }
+ }
+ j--;
+ }
+ // cleanup left over handlers
+ // we always have to call .end for proper cleanup,
+ // here we use `success` to indicate whether a handler should execute
+ for (; j >= 0; j--) {
+ handlerResult = this._active[j].end(false);
+ if (handlerResult instanceof Promise) {
+ this._stack.paused = true;
+ this._stack.loopPosition = j;
+ this._stack.fallThrough = true;
+ return handlerResult;
+ }
+ }
+ }
+
+ }
+ this._active = EMPTY_HANDLERS;
+ this._id = -1;
+ this._state = OscState.START;
+ }
+}
+
+/**
+ * Convenient class to allow attaching string based handler functions
+ * as OSC handlers.
+ */
+export class OscHandler implements IOscHandler {
+ private _data = '';
+ private _hitLimit: boolean = false;
+
+ constructor(private _handler: (data: string) => boolean | Promise<boolean>) { }
+
+ public start(): void {
+ this._data = '';
+ this._hitLimit = false;
+ }
+
+ public put(data: Uint32Array, start: number, end: number): void {
+ if (this._hitLimit) {
+ return;
+ }
+ this._data += utf32ToString(data, start, end);
+ if (this._data.length > PAYLOAD_LIMIT) {
+ this._data = '';
+ this._hitLimit = true;
+ }
+ }
+
+ public end(success: boolean): boolean | Promise<boolean> {
+ let ret: boolean | Promise<boolean> = false;
+ if (this._hitLimit) {
+ ret = false;
+ } else if (success) {
+ ret = this._handler(this._data);
+ if (ret instanceof Promise) {
+ // need to hold data until `ret` got resolved
+ // dont care for errors, data will be freed anyway on next start
+ return ret.then(res => {
+ this._data = '';
+ this._hitLimit = false;
+ return res;
+ });
+ }
+ }
+ this._data = '';
+ this._hitLimit = false;
+ return ret;
+ }
+}
diff --git a/node_modules/xterm/src/common/parser/Params.ts b/node_modules/xterm/src/common/parser/Params.ts
new file mode 100644
index 0000000..7071453
--- /dev/null
+++ b/node_modules/xterm/src/common/parser/Params.ts
@@ -0,0 +1,229 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+import { IParams, ParamsArray } from 'common/parser/Types';
+
+// max value supported for a single param/subparam (clamped to positive int32 range)
+const MAX_VALUE = 0x7FFFFFFF;
+// max allowed subparams for a single sequence (hardcoded limitation)
+const MAX_SUBPARAMS = 256;
+
+/**
+ * Params storage class.
+ * This type is used by the parser to accumulate sequence parameters and sub parameters
+ * and transmit them to the input handler actions.
+ *
+ * NOTES:
+ * - params object for action handlers is borrowed, use `.toArray` or `.clone` to get a copy
+ * - never read beyond `params.length - 1` (likely to contain arbitrary data)
+ * - `.getSubParams` returns a borrowed typed array, use `.getSubParamsAll` for cloned sub params
+ * - hardcoded limitations:
+ * - max. value for a single (sub) param is 2^31 - 1 (greater values are clamped to that)
+ * - max. 256 sub params possible
+ * - negative values are not allowed beside -1 (placeholder for default value)
+ *
+ * About ZDM (Zero Default Mode):
+ * ZDM is not orchestrated by this class. If the parser is in ZDM,
+ * it should add 0 for empty params, otherwise -1. This does not apply
+ * to subparams, empty subparams should always be added with -1.
+ */
+export class Params implements IParams {
+ // params store and length
+ public params: Int32Array;
+ public length: number;
+
+ // sub params store and length
+ protected _subParams: Int32Array;
+ protected _subParamsLength: number;
+
+ // sub params offsets from param: param idx --> [start, end] offset
+ private _subParamsIdx: Uint16Array;
+ private _rejectDigits: boolean;
+ private _rejectSubDigits: boolean;
+ private _digitIsSub: boolean;
+
+ /**
+ * Create a `Params` type from JS array representation.
+ */
+ public static fromArray(values: ParamsArray): Params {
+ const params = new Params();
+ if (!values.length) {
+ return params;
+ }
+ // skip leading sub params
+ for (let i = (Array.isArray(values[0])) ? 1 : 0; i < values.length; ++i) {
+ const value = values[i];
+ if (Array.isArray(value)) {
+ for (let k = 0; k < value.length; ++k) {
+ params.addSubParam(value[k]);
+ }
+ } else {
+ params.addParam(value);
+ }
+ }
+ return params;
+ }
+
+ /**
+ * @param maxLength max length of storable parameters
+ * @param maxSubParamsLength max length of storable sub parameters
+ */
+ constructor(public maxLength: number = 32, public maxSubParamsLength: number = 32) {
+ if (maxSubParamsLength > MAX_SUBPARAMS) {
+ throw new Error('maxSubParamsLength must not be greater than 256');
+ }
+ this.params = new Int32Array(maxLength);
+ this.length = 0;
+ this._subParams = new Int32Array(maxSubParamsLength);
+ this._subParamsLength = 0;
+ this._subParamsIdx = new Uint16Array(maxLength);
+ this._rejectDigits = false;
+ this._rejectSubDigits = false;
+ this._digitIsSub = false;
+ }
+
+ /**
+ * Clone object.
+ */
+ public clone(): Params {
+ const newParams = new Params(this.maxLength, this.maxSubParamsLength);
+ newParams.params.set(this.params);
+ newParams.length = this.length;
+ newParams._subParams.set(this._subParams);
+ newParams._subParamsLength = this._subParamsLength;
+ newParams._subParamsIdx.set(this._subParamsIdx);
+ newParams._rejectDigits = this._rejectDigits;
+ newParams._rejectSubDigits = this._rejectSubDigits;
+ newParams._digitIsSub = this._digitIsSub;
+ return newParams;
+ }
+
+ /**
+ * Get a JS array representation of the current parameters and sub parameters.
+ * The array is structured as follows:
+ * sequence: "1;2:3:4;5::6"
+ * array : [1, 2, [3, 4], 5, [-1, 6]]
+ */
+ public toArray(): ParamsArray {
+ const res: ParamsArray = [];
+ for (let i = 0; i < this.length; ++i) {
+ res.push(this.params[i]);
+ const start = this._subParamsIdx[i] >> 8;
+ const end = this._subParamsIdx[i] & 0xFF;
+ if (end - start > 0) {
+ res.push(Array.prototype.slice.call(this._subParams, start, end));
+ }
+ }
+ return res;
+ }
+
+ /**
+ * Reset to initial empty state.
+ */
+ public reset(): void {
+ this.length = 0;
+ this._subParamsLength = 0;
+ this._rejectDigits = false;
+ this._rejectSubDigits = false;
+ this._digitIsSub = false;
+ }
+
+ /**
+ * Add a parameter value.
+ * `Params` only stores up to `maxLength` parameters, any later
+ * parameter will be ignored.
+ * Note: VT devices only stored up to 16 values, xterm seems to
+ * store up to 30.
+ */
+ public addParam(value: number): void {
+ this._digitIsSub = false;
+ if (this.length >= this.maxLength) {
+ this._rejectDigits = true;
+ return;
+ }
+ if (value < -1) {
+ throw new Error('values lesser than -1 are not allowed');
+ }
+ this._subParamsIdx[this.length] = this._subParamsLength << 8 | this._subParamsLength;
+ this.params[this.length++] = value > MAX_VALUE ? MAX_VALUE : value;
+ }
+
+ /**
+ * Add a sub parameter value.
+ * The sub parameter is automatically associated with the last parameter value.
+ * Thus it is not possible to add a subparameter without any parameter added yet.
+ * `Params` only stores up to `subParamsLength` sub parameters, any later
+ * sub parameter will be ignored.
+ */
+ public addSubParam(value: number): void {
+ this._digitIsSub = true;
+ if (!this.length) {
+ return;
+ }
+ if (this._rejectDigits || this._subParamsLength >= this.maxSubParamsLength) {
+ this._rejectSubDigits = true;
+ return;
+ }
+ if (value < -1) {
+ throw new Error('values lesser than -1 are not allowed');
+ }
+ this._subParams[this._subParamsLength++] = value > MAX_VALUE ? MAX_VALUE : value;
+ this._subParamsIdx[this.length - 1]++;
+ }
+
+ /**
+ * Whether parameter at index `idx` has sub parameters.
+ */
+ public hasSubParams(idx: number): boolean {
+ return ((this._subParamsIdx[idx] & 0xFF) - (this._subParamsIdx[idx] >> 8) > 0);
+ }
+
+ /**
+ * Return sub parameters for parameter at index `idx`.
+ * Note: The values are borrowed, thus you need to copy
+ * the values if you need to hold them in nonlocal scope.
+ */
+ public getSubParams(idx: number): Int32Array | null {
+ const start = this._subParamsIdx[idx] >> 8;
+ const end = this._subParamsIdx[idx] & 0xFF;
+ if (end - start > 0) {
+ return this._subParams.subarray(start, end);
+ }
+ return null;
+ }
+
+ /**
+ * Return all sub parameters as {idx: subparams} mapping.
+ * Note: The values are not borrowed.
+ */
+ public getSubParamsAll(): {[idx: number]: Int32Array} {
+ const result: {[idx: number]: Int32Array} = {};
+ for (let i = 0; i < this.length; ++i) {
+ const start = this._subParamsIdx[i] >> 8;
+ const end = this._subParamsIdx[i] & 0xFF;
+ if (end - start > 0) {
+ result[i] = this._subParams.slice(start, end);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Add a single digit value to current parameter.
+ * This is used by the parser to account digits on a char by char basis.
+ */
+ public addDigit(value: number): void {
+ let length;
+ if (this._rejectDigits
+ || !(length = this._digitIsSub ? this._subParamsLength : this.length)
+ || (this._digitIsSub && this._rejectSubDigits)
+ ) {
+ return;
+ }
+
+ const store = this._digitIsSub ? this._subParams : this.params;
+ const cur = store[length - 1];
+ store[length - 1] = ~cur ? Math.min(cur * 10 + value, MAX_VALUE) : value;
+ }
+}
diff --git a/node_modules/xterm/src/common/parser/Types.d.ts b/node_modules/xterm/src/common/parser/Types.d.ts
new file mode 100644
index 0000000..3a621ea
--- /dev/null
+++ b/node_modules/xterm/src/common/parser/Types.d.ts
@@ -0,0 +1,274 @@
+/**
+ * Copyright (c) 2017 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IDisposable } from 'common/Types';
+import { ParserState } from 'common/parser/Constants';
+
+
+/** sequence params serialized to js arrays */
+export type ParamsArray = (number | number[])[];
+
+/** Params constructor type. */
+export interface IParamsConstructor {
+ new(maxLength: number, maxSubParamsLength: number): IParams;
+
+ /** create params from ParamsArray */
+ fromArray(values: ParamsArray): IParams;
+}
+
+/** Interface of Params storage class. */
+export interface IParams {
+ /** from ctor */
+ maxLength: number;
+ maxSubParamsLength: number;
+
+ /** param values and its length */
+ params: Int32Array;
+ length: number;
+
+ /** methods */
+ clone(): IParams;
+ toArray(): ParamsArray;
+ reset(): void;
+ addParam(value: number): void;
+ addSubParam(value: number): void;
+ hasSubParams(idx: number): boolean;
+ getSubParams(idx: number): Int32Array | null;
+ getSubParamsAll(): {[idx: number]: Int32Array};
+}
+
+/**
+ * Internal state of EscapeSequenceParser.
+ * Used as argument of the error handler to allow
+ * introspection at runtime on parse errors.
+ * Return it with altered values to recover from
+ * faulty states (not yet supported).
+ * Set `abort` to `true` to abort the current parsing.
+ */
+export interface IParsingState {
+ // position in parse string
+ position: number;
+ // actual character code
+ code: number;
+ // current parser state
+ currentState: ParserState;
+ // collect buffer with intermediate characters
+ collect: number;
+ // params buffer
+ params: IParams;
+ // should abort (default: false)
+ abort: boolean;
+}
+
+/**
+ * Command handler interfaces.
+ */
+
+/**
+ * CSI handler types.
+ * Note: `params` is borrowed.
+ */
+export type CsiHandlerType = (params: IParams) => boolean | Promise<boolean>;
+export type CsiFallbackHandlerType = (ident: number, params: IParams) => void;
+
+/**
+ * DCS handler types.
+ */
+export interface IDcsHandler {
+ /**
+ * Called when a DCS command starts.
+ * Prepare needed data structures here.
+ * Note: `params` is borrowed.
+ */
+ hook(params: IParams): void;
+ /**
+ * Incoming payload chunk.
+ * Note: `params` is borrowed.
+ */
+ put(data: Uint32Array, start: number, end: number): void;
+ /**
+ * End of DCS command. `success` indicates whether the
+ * command finished normally or got aborted, thus final
+ * execution of the command should depend on `success`.
+ * To save memory also cleanup data structures here.
+ */
+ unhook(success: boolean): boolean | Promise<boolean>;
+}
+export type DcsFallbackHandlerType = (ident: number, action: 'HOOK' | 'PUT' | 'UNHOOK', payload?: any) => void;
+
+/**
+ * ESC handler types.
+ */
+export type EscHandlerType = () => boolean | Promise<boolean>;
+export type EscFallbackHandlerType = (identifier: number) => void;
+
+/**
+ * EXECUTE handler types.
+ */
+export type ExecuteHandlerType = () => boolean;
+export type ExecuteFallbackHandlerType = (ident: number) => void;
+
+/**
+ * OSC handler types.
+ */
+export interface IOscHandler {
+ /**
+ * Announces start of this OSC command.
+ * Prepare needed data structures here.
+ */
+ start(): void;
+ /**
+ * Incoming data chunk.
+ * Note: Data is borrowed.
+ */
+ put(data: Uint32Array, start: number, end: number): void;
+ /**
+ * End of OSC command. `success` indicates whether the
+ * command finished normally or got aborted, thus final
+ * execution of the command should depend on `success`.
+ * To save memory also cleanup data structures here.
+ */
+ end(success: boolean): boolean | Promise<boolean>;
+}
+export type OscFallbackHandlerType = (ident: number, action: 'START' | 'PUT' | 'END', payload?: any) => void;
+
+/**
+ * PRINT handler types.
+ */
+export type PrintHandlerType = (data: Uint32Array, start: number, end: number) => void;
+export type PrintFallbackHandlerType = PrintHandlerType;
+
+
+/**
+* EscapeSequenceParser interface.
+*/
+export interface IEscapeSequenceParser extends IDisposable {
+ /**
+ * Preceding codepoint to get REP working correctly.
+ * This must be set by the print handler as last action.
+ * It gets reset by the parser for any valid sequence beside REP itself.
+ */
+ precedingCodepoint: number;
+
+ /**
+ * Reset the parser to its initial state (handlers are kept).
+ */
+ reset(): void;
+
+ /**
+ * Parse UTF32 codepoints in `data` up to `length`.
+ * @param data The data to parse.
+ */
+ parse(data: Uint32Array, length: number, promiseResult?: boolean): void | Promise<boolean>;
+
+ /**
+ * Get string from numercial function identifier `ident`.
+ * Useful in fallback handlers which expose the low level
+ * numcerical function identifier for debugging purposes.
+ * Note: A full back translation to `IFunctionIdentifier`
+ * is not implemented.
+ */
+ identToString(ident: number): string;
+
+ setPrintHandler(handler: PrintHandlerType): void;
+ clearPrintHandler(): void;
+
+ registerEscHandler(id: IFunctionIdentifier, handler: EscHandlerType): IDisposable;
+ clearEscHandler(id: IFunctionIdentifier): void;
+ setEscHandlerFallback(handler: EscFallbackHandlerType): void;
+
+ setExecuteHandler(flag: string, handler: ExecuteHandlerType): void;
+ clearExecuteHandler(flag: string): void;
+ setExecuteHandlerFallback(handler: ExecuteFallbackHandlerType): void;
+
+ registerCsiHandler(id: IFunctionIdentifier, handler: CsiHandlerType): IDisposable;
+ clearCsiHandler(id: IFunctionIdentifier): void;
+ setCsiHandlerFallback(callback: CsiFallbackHandlerType): void;
+
+ registerDcsHandler(id: IFunctionIdentifier, handler: IDcsHandler): IDisposable;
+ clearDcsHandler(id: IFunctionIdentifier): void;
+ setDcsHandlerFallback(handler: DcsFallbackHandlerType): void;
+
+ registerOscHandler(ident: number, handler: IOscHandler): IDisposable;
+ clearOscHandler(ident: number): void;
+ setOscHandlerFallback(handler: OscFallbackHandlerType): void;
+
+ setErrorHandler(handler: (state: IParsingState) => IParsingState): void;
+ clearErrorHandler(): void;
+}
+
+/**
+ * Subparser interfaces.
+ * The subparsers are instantiated in `EscapeSequenceParser` and
+ * called during `EscapeSequenceParser.parse`.
+ */
+export interface ISubParser<T, U> extends IDisposable {
+ reset(): void;
+ registerHandler(ident: number, handler: T): IDisposable;
+ clearHandler(ident: number): void;
+ setHandlerFallback(handler: U): void;
+ put(data: Uint32Array, start: number, end: number): void;
+}
+
+export interface IOscParser extends ISubParser<IOscHandler, OscFallbackHandlerType> {
+ start(): void;
+ end(success: boolean, promiseResult?: boolean): void | Promise<boolean>;
+}
+
+export interface IDcsParser extends ISubParser<IDcsHandler, DcsFallbackHandlerType> {
+ hook(ident: number, params: IParams): void;
+ unhook(success: boolean, promiseResult?: boolean): void | Promise<boolean>;
+}
+
+/**
+ * Interface to denote a specific ESC, CSI or DCS handler slot.
+ * The values are used to create an integer respresentation during handler
+ * regristation before passed to the subparsers as `ident`.
+ * The integer translation is made to allow a faster handler access
+ * in `EscapeSequenceParser.parse`.
+ */
+export interface IFunctionIdentifier {
+ prefix?: string;
+ intermediates?: string;
+ final: string;
+}
+
+export interface IHandlerCollection<T> {
+ [key: string]: T[];
+}
+
+/**
+ * Types for async parser support.
+ */
+
+// type of saved stack state in parser
+export const enum ParserStackType {
+ NONE = 0,
+ FAIL,
+ RESET,
+ CSI,
+ ESC,
+ OSC,
+ DCS
+}
+
+// aggregate of resumable handler lists
+export type ResumableHandlersType = CsiHandlerType[] | EscHandlerType[];
+
+// saved stack state of the parser
+export interface IParserStackState {
+ state: ParserStackType;
+ handlers: ResumableHandlersType;
+ handlerPos: number;
+ transition: number;
+ chunkPos: number;
+}
+
+// saved stack state of subparser (OSC and DCS)
+export interface ISubParserStackState {
+ paused: boolean;
+ loopPosition: number;
+ fallThrough: boolean;
+}
diff --git a/node_modules/xterm/src/common/public/AddonManager.ts b/node_modules/xterm/src/common/public/AddonManager.ts
new file mode 100644
index 0000000..06c7812
--- /dev/null
+++ b/node_modules/xterm/src/common/public/AddonManager.ts
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { ITerminalAddon, IDisposable, Terminal } from 'xterm';
+
+export interface ILoadedAddon {
+ instance: ITerminalAddon;
+ dispose: () => void;
+ isDisposed: boolean;
+}
+
+export class AddonManager implements IDisposable {
+ protected _addons: ILoadedAddon[] = [];
+
+ constructor() {
+ }
+
+ public dispose(): void {
+ for (let i = this._addons.length - 1; i >= 0; i--) {
+ this._addons[i].instance.dispose();
+ }
+ }
+
+ public loadAddon(terminal: Terminal, instance: ITerminalAddon): void {
+ const loadedAddon: ILoadedAddon = {
+ instance,
+ dispose: instance.dispose,
+ isDisposed: false
+ };
+ this._addons.push(loadedAddon);
+ instance.dispose = () => this._wrappedAddonDispose(loadedAddon);
+ instance.activate(terminal as any);
+ }
+
+ private _wrappedAddonDispose(loadedAddon: ILoadedAddon): void {
+ if (loadedAddon.isDisposed) {
+ // Do nothing if already disposed
+ return;
+ }
+ let index = -1;
+ for (let i = 0; i < this._addons.length; i++) {
+ if (this._addons[i] === loadedAddon) {
+ index = i;
+ break;
+ }
+ }
+ if (index === -1) {
+ throw new Error('Could not dispose an addon that has not been loaded');
+ }
+ loadedAddon.isDisposed = true;
+ loadedAddon.dispose.apply(loadedAddon.instance);
+ this._addons.splice(index, 1);
+ }
+}
diff --git a/node_modules/xterm/src/common/public/BufferApiView.ts b/node_modules/xterm/src/common/public/BufferApiView.ts
new file mode 100644
index 0000000..ca9ef2d
--- /dev/null
+++ b/node_modules/xterm/src/common/public/BufferApiView.ts
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2021 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi } from 'xterm';
+import { IBuffer } from 'common/buffer/Types';
+import { BufferLineApiView } from 'common/public/BufferLineApiView';
+import { CellData } from 'common/buffer/CellData';
+
+export class BufferApiView implements IBufferApi {
+ constructor(
+ private _buffer: IBuffer,
+ public readonly type: 'normal' | 'alternate'
+ ) { }
+
+ public init(buffer: IBuffer): BufferApiView {
+ this._buffer = buffer;
+ return this;
+ }
+
+ public get cursorY(): number { return this._buffer.y; }
+ public get cursorX(): number { return this._buffer.x; }
+ public get viewportY(): number { return this._buffer.ydisp; }
+ public get baseY(): number { return this._buffer.ybase; }
+ public get length(): number { return this._buffer.lines.length; }
+ public getLine(y: number): IBufferLineApi | undefined {
+ const line = this._buffer.lines.get(y);
+ if (!line) {
+ return undefined;
+ }
+ return new BufferLineApiView(line);
+ }
+ public getNullCell(): IBufferCellApi { return new CellData(); }
+}
diff --git a/node_modules/xterm/src/common/public/BufferLineApiView.ts b/node_modules/xterm/src/common/public/BufferLineApiView.ts
new file mode 100644
index 0000000..6037501
--- /dev/null
+++ b/node_modules/xterm/src/common/public/BufferLineApiView.ts
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2021 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { CellData } from 'common/buffer/CellData';
+import { IBufferLine, ICellData } from 'common/Types';
+import { IBufferCell as IBufferCellApi, IBufferLine as IBufferLineApi } from 'xterm';
+
+export class BufferLineApiView implements IBufferLineApi {
+ constructor(private _line: IBufferLine) { }
+
+ public get isWrapped(): boolean { return this._line.isWrapped; }
+ public get length(): number { return this._line.length; }
+ public getCell(x: number, cell?: IBufferCellApi): IBufferCellApi | undefined {
+ if (x < 0 || x >= this._line.length) {
+ return undefined;
+ }
+
+ if (cell) {
+ this._line.loadCell(x, cell as ICellData);
+ return cell;
+ }
+ return this._line.loadCell(x, new CellData());
+ }
+ public translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string {
+ return this._line.translateToString(trimRight, startColumn, endColumn);
+ }
+}
diff --git a/node_modules/xterm/src/common/public/BufferNamespaceApi.ts b/node_modules/xterm/src/common/public/BufferNamespaceApi.ts
new file mode 100644
index 0000000..d86f6bf
--- /dev/null
+++ b/node_modules/xterm/src/common/public/BufferNamespaceApi.ts
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2021 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IBuffer as IBufferApi, IBufferNamespace as IBufferNamespaceApi } from 'xterm';
+import { BufferApiView } from 'common/public/BufferApiView';
+import { IEvent, EventEmitter } from 'common/EventEmitter';
+import { ICoreTerminal } from 'common/Types';
+
+export class BufferNamespaceApi implements IBufferNamespaceApi {
+ private _normal: BufferApiView;
+ private _alternate: BufferApiView;
+ private _onBufferChange = new EventEmitter<IBufferApi>();
+ public get onBufferChange(): IEvent<IBufferApi> { return this._onBufferChange.event; }
+
+ constructor(private _core: ICoreTerminal) {
+ this._normal = new BufferApiView(this._core.buffers.normal, 'normal');
+ this._alternate = new BufferApiView(this._core.buffers.alt, 'alternate');
+ this._core.buffers.onBufferActivate(() => this._onBufferChange.fire(this.active));
+ }
+ public get active(): IBufferApi {
+ if (this._core.buffers.active === this._core.buffers.normal) { return this.normal; }
+ if (this._core.buffers.active === this._core.buffers.alt) { return this.alternate; }
+ throw new Error('Active buffer is neither normal nor alternate');
+ }
+ public get normal(): IBufferApi {
+ return this._normal.init(this._core.buffers.normal);
+ }
+ public get alternate(): IBufferApi {
+ return this._alternate.init(this._core.buffers.alt);
+ }
+}
diff --git a/node_modules/xterm/src/common/public/ParserApi.ts b/node_modules/xterm/src/common/public/ParserApi.ts
new file mode 100644
index 0000000..67df4be
--- /dev/null
+++ b/node_modules/xterm/src/common/public/ParserApi.ts
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2021 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IParams } from 'common/parser/Types';
+import { IDisposable, IFunctionIdentifier, IParser } from 'xterm';
+import { ICoreTerminal } from 'common/Types';
+
+export class ParserApi implements IParser {
+ constructor(private _core: ICoreTerminal) { }
+
+ public registerCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean | Promise<boolean>): IDisposable {
+ return this._core.registerCsiHandler(id, (params: IParams) => callback(params.toArray()));
+ }
+ public addCsiHandler(id: IFunctionIdentifier, callback: (params: (number | number[])[]) => boolean | Promise<boolean>): IDisposable {
+ return this.registerCsiHandler(id, callback);
+ }
+ public registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean | Promise<boolean>): IDisposable {
+ return this._core.registerDcsHandler(id, (data: string, params: IParams) => callback(data, params.toArray()));
+ }
+ public addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: (number | number[])[]) => boolean | Promise<boolean>): IDisposable {
+ return this.registerDcsHandler(id, callback);
+ }
+ public registerEscHandler(id: IFunctionIdentifier, handler: () => boolean | Promise<boolean>): IDisposable {
+ return this._core.registerEscHandler(id, handler);
+ }
+ public addEscHandler(id: IFunctionIdentifier, handler: () => boolean | Promise<boolean>): IDisposable {
+ return this.registerEscHandler(id, handler);
+ }
+ public registerOscHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable {
+ return this._core.registerOscHandler(ident, callback);
+ }
+ public addOscHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable {
+ return this.registerOscHandler(ident, callback);
+ }
+}
diff --git a/node_modules/xterm/src/common/public/UnicodeApi.ts b/node_modules/xterm/src/common/public/UnicodeApi.ts
new file mode 100644
index 0000000..8a669a0
--- /dev/null
+++ b/node_modules/xterm/src/common/public/UnicodeApi.ts
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2021 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { ICoreTerminal } from 'common/Types';
+import { IUnicodeHandling, IUnicodeVersionProvider } from 'xterm';
+
+export class UnicodeApi implements IUnicodeHandling {
+ constructor(private _core: ICoreTerminal) { }
+
+ public register(provider: IUnicodeVersionProvider): void {
+ this._core.unicodeService.register(provider);
+ }
+
+ public get versions(): string[] {
+ return this._core.unicodeService.versions;
+ }
+
+ public get activeVersion(): string {
+ return this._core.unicodeService.activeVersion;
+ }
+
+ public set activeVersion(version: string) {
+ this._core.unicodeService.activeVersion = version;
+ }
+}
diff --git a/node_modules/xterm/src/common/services/BufferService.ts b/node_modules/xterm/src/common/services/BufferService.ts
new file mode 100644
index 0000000..bba60dd
--- /dev/null
+++ b/node_modules/xterm/src/common/services/BufferService.ts
@@ -0,0 +1,185 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IBufferService, IOptionsService } from 'common/services/Services';
+import { BufferSet } from 'common/buffer/BufferSet';
+import { IBufferSet, IBuffer } from 'common/buffer/Types';
+import { EventEmitter, IEvent } from 'common/EventEmitter';
+import { Disposable } from 'common/Lifecycle';
+import { IAttributeData, IBufferLine, ScrollSource } from 'common/Types';
+
+export const MINIMUM_COLS = 2; // Less than 2 can mess with wide chars
+export const MINIMUM_ROWS = 1;
+
+export class BufferService extends Disposable implements IBufferService {
+ public serviceBrand: any;
+
+ public cols: number;
+ public rows: number;
+ public buffers: IBufferSet;
+ /** Whether the user is scrolling (locks the scroll position) */
+ public isUserScrolling: boolean = false;
+
+ private _onResize = new EventEmitter<{ cols: number, rows: number }>();
+ public get onResize(): IEvent<{ cols: number, rows: number }> { return this._onResize.event; }
+ private _onScroll = new EventEmitter<number>();
+ public get onScroll(): IEvent<number> { return this._onScroll.event; }
+
+ public get buffer(): IBuffer { return this.buffers.active; }
+
+ /** An IBufferline to clone/copy from for new blank lines */
+ private _cachedBlankLine: IBufferLine | undefined;
+
+ constructor(
+ @IOptionsService private _optionsService: IOptionsService
+ ) {
+ super();
+ this.cols = Math.max(_optionsService.rawOptions.cols || 0, MINIMUM_COLS);
+ this.rows = Math.max(_optionsService.rawOptions.rows || 0, MINIMUM_ROWS);
+ this.buffers = new BufferSet(_optionsService, this);
+ }
+
+ public dispose(): void {
+ super.dispose();
+ this.buffers.dispose();
+ }
+
+ public resize(cols: number, rows: number): void {
+ this.cols = cols;
+ this.rows = rows;
+ this.buffers.resize(cols, rows);
+ this.buffers.setupTabStops(this.cols);
+ this._onResize.fire({ cols, rows });
+ }
+
+ public reset(): void {
+ this.buffers.reset();
+ this.isUserScrolling = false;
+ }
+
+ /**
+ * Scroll the terminal down 1 row, creating a blank line.
+ * @param isWrapped Whether the new line is wrapped from the previous line.
+ */
+ public scroll(eraseAttr: IAttributeData, isWrapped: boolean = false): void {
+ const buffer = this.buffer;
+
+ let newLine: IBufferLine | undefined;
+ newLine = this._cachedBlankLine;
+ if (!newLine || newLine.length !== this.cols || newLine.getFg(0) !== eraseAttr.fg || newLine.getBg(0) !== eraseAttr.bg) {
+ newLine = buffer.getBlankLine(eraseAttr, isWrapped);
+ this._cachedBlankLine = newLine;
+ }
+ newLine.isWrapped = isWrapped;
+
+ const topRow = buffer.ybase + buffer.scrollTop;
+ const bottomRow = buffer.ybase + buffer.scrollBottom;
+
+ if (buffer.scrollTop === 0) {
+ // Determine whether the buffer is going to be trimmed after insertion.
+ const willBufferBeTrimmed = buffer.lines.isFull;
+
+ // Insert the line using the fastest method
+ if (bottomRow === buffer.lines.length - 1) {
+ if (willBufferBeTrimmed) {
+ buffer.lines.recycle().copyFrom(newLine);
+ } else {
+ buffer.lines.push(newLine.clone());
+ }
+ } else {
+ buffer.lines.splice(bottomRow + 1, 0, newLine.clone());
+ }
+
+ // Only adjust ybase and ydisp when the buffer is not trimmed
+ if (!willBufferBeTrimmed) {
+ buffer.ybase++;
+ // Only scroll the ydisp with ybase if the user has not scrolled up
+ if (!this.isUserScrolling) {
+ buffer.ydisp++;
+ }
+ } else {
+ // When the buffer is full and the user has scrolled up, keep the text
+ // stable unless ydisp is right at the top
+ if (this.isUserScrolling) {
+ buffer.ydisp = Math.max(buffer.ydisp - 1, 0);
+ }
+ }
+ } else {
+ // scrollTop is non-zero which means no line will be going to the
+ // scrollback, instead we can just shift them in-place.
+ const scrollRegionHeight = bottomRow - topRow + 1 /* as it's zero-based */;
+ buffer.lines.shiftElements(topRow + 1, scrollRegionHeight - 1, -1);
+ buffer.lines.set(bottomRow, newLine.clone());
+ }
+
+ // Move the viewport to the bottom of the buffer unless the user is
+ // scrolling.
+ if (!this.isUserScrolling) {
+ buffer.ydisp = buffer.ybase;
+ }
+
+ this._onScroll.fire(buffer.ydisp);
+ }
+
+ /**
+ * Scroll the display of the terminal
+ * @param disp The number of lines to scroll down (negative scroll up).
+ * @param suppressScrollEvent Don't emit the scroll event as scrollLines. This is used
+ * to avoid unwanted events being handled by the viewport when the event was triggered from the
+ * viewport originally.
+ */
+ public scrollLines(disp: number, suppressScrollEvent?: boolean, source?: ScrollSource): void {
+ const buffer = this.buffer;
+ if (disp < 0) {
+ if (buffer.ydisp === 0) {
+ return;
+ }
+ this.isUserScrolling = true;
+ } else if (disp + buffer.ydisp >= buffer.ybase) {
+ this.isUserScrolling = false;
+ }
+
+ const oldYdisp = buffer.ydisp;
+ buffer.ydisp = Math.max(Math.min(buffer.ydisp + disp, buffer.ybase), 0);
+
+ // No change occurred, don't trigger scroll/refresh
+ if (oldYdisp === buffer.ydisp) {
+ return;
+ }
+
+ if (!suppressScrollEvent) {
+ this._onScroll.fire(buffer.ydisp);
+ }
+ }
+
+ /**
+ * Scroll the display of the terminal by a number of pages.
+ * @param pageCount The number of pages to scroll (negative scrolls up).
+ */
+ public scrollPages(pageCount: number): void {
+ this.scrollLines(pageCount * (this.rows - 1));
+ }
+
+ /**
+ * Scrolls the display of the terminal to the top.
+ */
+ public scrollToTop(): void {
+ this.scrollLines(-this.buffer.ydisp);
+ }
+
+ /**
+ * Scrolls the display of the terminal to the bottom.
+ */
+ public scrollToBottom(): void {
+ this.scrollLines(this.buffer.ybase - this.buffer.ydisp);
+ }
+
+ public scrollToLine(line: number): void {
+ const scrollAmount = line - this.buffer.ydisp;
+ if (scrollAmount !== 0) {
+ this.scrollLines(scrollAmount);
+ }
+ }
+}
diff --git a/node_modules/xterm/src/common/services/CharsetService.ts b/node_modules/xterm/src/common/services/CharsetService.ts
new file mode 100644
index 0000000..c538106
--- /dev/null
+++ b/node_modules/xterm/src/common/services/CharsetService.ts
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { ICharsetService } from 'common/services/Services';
+import { ICharset } from 'common/Types';
+
+export class CharsetService implements ICharsetService {
+ public serviceBrand: any;
+
+ public charset: ICharset | undefined;
+ public glevel: number = 0;
+
+ private _charsets: (ICharset | undefined)[] = [];
+
+ public reset(): void {
+ this.charset = undefined;
+ this._charsets = [];
+ this.glevel = 0;
+ }
+
+ public setgLevel(g: number): void {
+ this.glevel = g;
+ this.charset = this._charsets[g];
+ }
+
+ public setgCharset(g: number, charset: ICharset | undefined): void {
+ this._charsets[g] = charset;
+ if (this.glevel === g) {
+ this.charset = charset;
+ }
+ }
+}
diff --git a/node_modules/xterm/src/common/services/CoreMouseService.ts b/node_modules/xterm/src/common/services/CoreMouseService.ts
new file mode 100644
index 0000000..0b0dc36
--- /dev/null
+++ b/node_modules/xterm/src/common/services/CoreMouseService.ts
@@ -0,0 +1,309 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+import { IBufferService, ICoreService, ICoreMouseService } from 'common/services/Services';
+import { EventEmitter, IEvent } from 'common/EventEmitter';
+import { ICoreMouseProtocol, ICoreMouseEvent, CoreMouseEncoding, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types';
+
+/**
+ * Supported default protocols.
+ */
+const DEFAULT_PROTOCOLS: {[key: string]: ICoreMouseProtocol} = {
+ /**
+ * NONE
+ * Events: none
+ * Modifiers: none
+ */
+ NONE: {
+ events: CoreMouseEventType.NONE,
+ restrict: () => false
+ },
+ /**
+ * X10
+ * Events: mousedown
+ * Modifiers: none
+ */
+ X10: {
+ events: CoreMouseEventType.DOWN,
+ restrict: (e: ICoreMouseEvent) => {
+ // no wheel, no move, no up
+ if (e.button === CoreMouseButton.WHEEL || e.action !== CoreMouseAction.DOWN) {
+ return false;
+ }
+ // no modifiers
+ e.ctrl = false;
+ e.alt = false;
+ e.shift = false;
+ return true;
+ }
+ },
+ /**
+ * VT200
+ * Events: mousedown / mouseup / wheel
+ * Modifiers: all
+ */
+ VT200: {
+ events: CoreMouseEventType.DOWN | CoreMouseEventType.UP | CoreMouseEventType.WHEEL,
+ restrict: (e: ICoreMouseEvent) => {
+ // no move
+ if (e.action === CoreMouseAction.MOVE) {
+ return false;
+ }
+ return true;
+ }
+ },
+ /**
+ * DRAG
+ * Events: mousedown / mouseup / wheel / mousedrag
+ * Modifiers: all
+ */
+ DRAG: {
+ events: CoreMouseEventType.DOWN | CoreMouseEventType.UP | CoreMouseEventType.WHEEL | CoreMouseEventType.DRAG,
+ restrict: (e: ICoreMouseEvent) => {
+ // no move without button
+ if (e.action === CoreMouseAction.MOVE && e.button === CoreMouseButton.NONE) {
+ return false;
+ }
+ return true;
+ }
+ },
+ /**
+ * ANY
+ * Events: all mouse related events
+ * Modifiers: all
+ */
+ ANY: {
+ events:
+ CoreMouseEventType.DOWN | CoreMouseEventType.UP | CoreMouseEventType.WHEEL
+ | CoreMouseEventType.DRAG | CoreMouseEventType.MOVE,
+ restrict: (e: ICoreMouseEvent) => true
+ }
+};
+
+const enum Modifiers {
+ SHIFT = 4,
+ ALT = 8,
+ CTRL = 16
+}
+
+// helper for default encoders to generate the event code.
+function eventCode(e: ICoreMouseEvent, isSGR: boolean): number {
+ let code = (e.ctrl ? Modifiers.CTRL : 0) | (e.shift ? Modifiers.SHIFT : 0) | (e.alt ? Modifiers.ALT : 0);
+ if (e.button === CoreMouseButton.WHEEL) {
+ code |= 64;
+ code |= e.action;
+ } else {
+ code |= e.button & 3;
+ if (e.button & 4) {
+ code |= 64;
+ }
+ if (e.button & 8) {
+ code |= 128;
+ }
+ if (e.action === CoreMouseAction.MOVE) {
+ code |= CoreMouseAction.MOVE;
+ } else if (e.action === CoreMouseAction.UP && !isSGR) {
+ // special case - only SGR can report button on release
+ // all others have to go with NONE
+ code |= CoreMouseButton.NONE;
+ }
+ }
+ return code;
+}
+
+const S = String.fromCharCode;
+
+/**
+ * Supported default encodings.
+ */
+const DEFAULT_ENCODINGS: {[key: string]: CoreMouseEncoding} = {
+ /**
+ * DEFAULT - CSI M Pb Px Py
+ * Single byte encoding for coords and event code.
+ * Can encode values up to 223 (1-based).
+ */
+ DEFAULT: (e: ICoreMouseEvent) => {
+ const params = [eventCode(e, false) + 32, e.col + 32, e.row + 32];
+ // supress mouse report if we exceed addressible range
+ // Note this is handled differently by emulators
+ // - xterm: sends 0;0 coords instead
+ // - vte, konsole: no report
+ if (params[0] > 255 || params[1] > 255 || params[2] > 255) {
+ return '';
+ }
+ return `\x1b[M${S(params[0])}${S(params[1])}${S(params[2])}`;
+ },
+ /**
+ * SGR - CSI < Pb ; Px ; Py M|m
+ * No encoding limitation.
+ * Can report button on release and works with a well formed sequence.
+ */
+ SGR: (e: ICoreMouseEvent) => {
+ const final = (e.action === CoreMouseAction.UP && e.button !== CoreMouseButton.WHEEL) ? 'm' : 'M';
+ return `\x1b[<${eventCode(e, true)};${e.col};${e.row}${final}`;
+ }
+};
+
+/**
+ * CoreMouseService
+ *
+ * Provides mouse tracking reports with different protocols and encodings.
+ * - protocols: NONE (default), X10, VT200, DRAG, ANY
+ * - encodings: DEFAULT, SGR (UTF8, URXVT removed in #2507)
+ *
+ * Custom protocols/encodings can be added by `addProtocol` / `addEncoding`.
+ * To activate a protocol/encoding, set `activeProtocol` / `activeEncoding`.
+ * Switching a protocol will send a notification event `onProtocolChange`
+ * with a list of needed events to track.
+ *
+ * The service handles the mouse tracking state and decides whether to send
+ * a tracking report to the backend based on protocol and encoding limitations.
+ * To send a mouse event call `triggerMouseEvent`.
+ */
+export class CoreMouseService implements ICoreMouseService {
+ private _protocols: {[name: string]: ICoreMouseProtocol} = {};
+ private _encodings: {[name: string]: CoreMouseEncoding} = {};
+ private _activeProtocol: string = '';
+ private _activeEncoding: string = '';
+ private _onProtocolChange = new EventEmitter<CoreMouseEventType>();
+ private _lastEvent: ICoreMouseEvent | null = null;
+
+ constructor(
+ @IBufferService private readonly _bufferService: IBufferService,
+ @ICoreService private readonly _coreService: ICoreService
+ ) {
+ // register default protocols and encodings
+ for (const name of Object.keys(DEFAULT_PROTOCOLS)) this.addProtocol(name, DEFAULT_PROTOCOLS[name]);
+ for (const name of Object.keys(DEFAULT_ENCODINGS)) this.addEncoding(name, DEFAULT_ENCODINGS[name]);
+ // call reset to set defaults
+ this.reset();
+ }
+
+ public addProtocol(name: string, protocol: ICoreMouseProtocol): void {
+ this._protocols[name] = protocol;
+ }
+
+ public addEncoding(name: string, encoding: CoreMouseEncoding): void {
+ this._encodings[name] = encoding;
+ }
+
+ public get activeProtocol(): string {
+ return this._activeProtocol;
+ }
+
+ public get areMouseEventsActive(): boolean {
+ return this._protocols[this._activeProtocol].events !== 0;
+ }
+
+ public set activeProtocol(name: string) {
+ if (!this._protocols[name]) {
+ throw new Error(`unknown protocol "${name}"`);
+ }
+ this._activeProtocol = name;
+ this._onProtocolChange.fire(this._protocols[name].events);
+ }
+
+ public get activeEncoding(): string {
+ return this._activeEncoding;
+ }
+
+ public set activeEncoding(name: string) {
+ if (!this._encodings[name]) {
+ throw new Error(`unknown encoding "${name}"`);
+ }
+ this._activeEncoding = name;
+ }
+
+ public reset(): void {
+ this.activeProtocol = 'NONE';
+ this.activeEncoding = 'DEFAULT';
+ this._lastEvent = null;
+ }
+
+ /**
+ * Event to announce changes in mouse tracking.
+ */
+ public get onProtocolChange(): IEvent<CoreMouseEventType> {
+ return this._onProtocolChange.event;
+ }
+
+ /**
+ * Triggers a mouse event to be sent.
+ *
+ * Returns true if the event passed all protocol restrictions and a report
+ * was sent, otherwise false. The return value may be used to decide whether
+ * the default event action in the bowser component should be omitted.
+ *
+ * Note: The method will change values of the given event object
+ * to fullfill protocol and encoding restrictions.
+ */
+ public triggerMouseEvent(e: ICoreMouseEvent): boolean {
+ // range check for col/row
+ if (e.col < 0 || e.col >= this._bufferService.cols
+ || e.row < 0 || e.row >= this._bufferService.rows) {
+ return false;
+ }
+
+ // filter nonsense combinations of button + action
+ if (e.button === CoreMouseButton.WHEEL && e.action === CoreMouseAction.MOVE) {
+ return false;
+ }
+ if (e.button === CoreMouseButton.NONE && e.action !== CoreMouseAction.MOVE) {
+ return false;
+ }
+ if (e.button !== CoreMouseButton.WHEEL && (e.action === CoreMouseAction.LEFT || e.action === CoreMouseAction.RIGHT)) {
+ return false;
+ }
+
+ // report 1-based coords
+ e.col++;
+ e.row++;
+
+ // debounce move at grid level
+ if (e.action === CoreMouseAction.MOVE && this._lastEvent && this._compareEvents(this._lastEvent, e)) {
+ return false;
+ }
+
+ // apply protocol restrictions
+ if (!this._protocols[this._activeProtocol].restrict(e)) {
+ return false;
+ }
+
+ // encode report and send
+ const report = this._encodings[this._activeEncoding](e);
+ if (report) {
+ // always send DEFAULT as binary data
+ if (this._activeEncoding === 'DEFAULT') {
+ this._coreService.triggerBinaryEvent(report);
+ } else {
+ this._coreService.triggerDataEvent(report, true);
+ }
+ }
+
+ this._lastEvent = e;
+
+ return true;
+ }
+
+ public explainEvents(events: CoreMouseEventType): {[event: string]: boolean} {
+ return {
+ down: !!(events & CoreMouseEventType.DOWN),
+ up: !!(events & CoreMouseEventType.UP),
+ drag: !!(events & CoreMouseEventType.DRAG),
+ move: !!(events & CoreMouseEventType.MOVE),
+ wheel: !!(events & CoreMouseEventType.WHEEL)
+ };
+ }
+
+ private _compareEvents(e1: ICoreMouseEvent, e2: ICoreMouseEvent): boolean {
+ if (e1.col !== e2.col) return false;
+ if (e1.row !== e2.row) return false;
+ if (e1.button !== e2.button) return false;
+ if (e1.action !== e2.action) return false;
+ if (e1.ctrl !== e2.ctrl) return false;
+ if (e1.alt !== e2.alt) return false;
+ if (e1.shift !== e2.shift) return false;
+ return true;
+ }
+}
diff --git a/node_modules/xterm/src/common/services/CoreService.ts b/node_modules/xterm/src/common/services/CoreService.ts
new file mode 100644
index 0000000..20a3460
--- /dev/null
+++ b/node_modules/xterm/src/common/services/CoreService.ts
@@ -0,0 +1,92 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { ICoreService, ILogService, IOptionsService, IBufferService } from 'common/services/Services';
+import { EventEmitter, IEvent } from 'common/EventEmitter';
+import { IDecPrivateModes, IModes } from 'common/Types';
+import { clone } from 'common/Clone';
+import { Disposable } from 'common/Lifecycle';
+
+const DEFAULT_MODES: IModes = Object.freeze({
+ insertMode: false
+});
+
+const DEFAULT_DEC_PRIVATE_MODES: IDecPrivateModes = Object.freeze({
+ applicationCursorKeys: false,
+ applicationKeypad: false,
+ bracketedPasteMode: false,
+ origin: false,
+ reverseWraparound: false,
+ sendFocus: false,
+ wraparound: true // defaults: xterm - true, vt100 - false
+});
+
+export class CoreService extends Disposable implements ICoreService {
+ public serviceBrand: any;
+
+ public isCursorInitialized: boolean = false;
+ public isCursorHidden: boolean = false;
+ public modes: IModes;
+ public decPrivateModes: IDecPrivateModes;
+
+ // Circular dependency, this must be unset or memory will leak after Terminal.dispose
+ private _scrollToBottom: (() => void) | undefined;
+
+ private _onData = this.register(new EventEmitter<string>());
+ public get onData(): IEvent<string> { return this._onData.event; }
+ private _onUserInput = this.register(new EventEmitter<void>());
+ public get onUserInput(): IEvent<void> { return this._onUserInput.event; }
+ private _onBinary = this.register(new EventEmitter<string>());
+ public get onBinary(): IEvent<string> { return this._onBinary.event; }
+
+ constructor(
+ // TODO: Move this into a service
+ scrollToBottom: () => void,
+ @IBufferService private readonly _bufferService: IBufferService,
+ @ILogService private readonly _logService: ILogService,
+ @IOptionsService private readonly _optionsService: IOptionsService
+ ) {
+ super();
+ this._scrollToBottom = scrollToBottom;
+ this.register({ dispose: () => this._scrollToBottom = undefined });
+ this.modes = clone(DEFAULT_MODES);
+ this.decPrivateModes = clone(DEFAULT_DEC_PRIVATE_MODES);
+ }
+
+ public reset(): void {
+ this.modes = clone(DEFAULT_MODES);
+ this.decPrivateModes = clone(DEFAULT_DEC_PRIVATE_MODES);
+ }
+
+ public triggerDataEvent(data: string, wasUserInput: boolean = false): void {
+ // Prevents all events to pty process if stdin is disabled
+ if (this._optionsService.rawOptions.disableStdin) {
+ return;
+ }
+
+ // Input is being sent to the terminal, the terminal should focus the prompt.
+ const buffer = this._bufferService.buffer;
+ if (buffer.ybase !== buffer.ydisp) {
+ this._scrollToBottom!();
+ }
+
+ // Fire onUserInput so listeners can react as well (eg. clear selection)
+ if (wasUserInput) {
+ this._onUserInput.fire();
+ }
+
+ // Fire onData API
+ this._logService.debug(`sending data "${data}"`, () => data.split('').map(e => e.charCodeAt(0)));
+ this._onData.fire(data);
+ }
+
+ public triggerBinaryEvent(data: string): void {
+ if (this._optionsService.rawOptions.disableStdin) {
+ return;
+ }
+ this._logService.debug(`sending binary "${data}"`, () => data.split('').map(e => e.charCodeAt(0)));
+ this._onBinary.fire(data);
+ }
+}
diff --git a/node_modules/xterm/src/common/services/DirtyRowService.ts b/node_modules/xterm/src/common/services/DirtyRowService.ts
new file mode 100644
index 0000000..1c43b67
--- /dev/null
+++ b/node_modules/xterm/src/common/services/DirtyRowService.ts
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IBufferService, IDirtyRowService } from 'common/services/Services';
+
+export class DirtyRowService implements IDirtyRowService {
+ public serviceBrand: any;
+
+ private _start!: number;
+ private _end!: number;
+
+ public get start(): number { return this._start; }
+ public get end(): number { return this._end; }
+
+ constructor(
+ @IBufferService private readonly _bufferService: IBufferService
+ ) {
+ this.clearRange();
+ }
+
+ public clearRange(): void {
+ this._start = this._bufferService.buffer.y;
+ this._end = this._bufferService.buffer.y;
+ }
+
+ public markDirty(y: number): void {
+ if (y < this._start) {
+ this._start = y;
+ } else if (y > this._end) {
+ this._end = y;
+ }
+ }
+
+ public markRangeDirty(y1: number, y2: number): void {
+ if (y1 > y2) {
+ const temp = y1;
+ y1 = y2;
+ y2 = temp;
+ }
+ if (y1 < this._start) {
+ this._start = y1;
+ }
+ if (y2 > this._end) {
+ this._end = y2;
+ }
+ }
+
+ public markAllDirty(): void {
+ this.markRangeDirty(0, this._bufferService.rows - 1);
+ }
+}
diff --git a/node_modules/xterm/src/common/services/InstantiationService.ts b/node_modules/xterm/src/common/services/InstantiationService.ts
new file mode 100644
index 0000000..8280948
--- /dev/null
+++ b/node_modules/xterm/src/common/services/InstantiationService.ts
@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ *
+ * This was heavily inspired from microsoft/vscode's dependency injection system (MIT).
+ */
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { IInstantiationService, IServiceIdentifier } from 'common/services/Services';
+import { getServiceDependencies } from 'common/services/ServiceRegistry';
+
+export class ServiceCollection {
+
+ private _entries = new Map<IServiceIdentifier<any>, any>();
+
+ constructor(...entries: [IServiceIdentifier<any>, any][]) {
+ for (const [id, service] of entries) {
+ this.set(id, service);
+ }
+ }
+
+ public set<T>(id: IServiceIdentifier<T>, instance: T): T {
+ const result = this._entries.get(id);
+ this._entries.set(id, instance);
+ return result;
+ }
+
+ public forEach(callback: (id: IServiceIdentifier<any>, instance: any) => any): void {
+ this._entries.forEach((value, key) => callback(key, value));
+ }
+
+ public has(id: IServiceIdentifier<any>): boolean {
+ return this._entries.has(id);
+ }
+
+ public get<T>(id: IServiceIdentifier<T>): T | undefined {
+ return this._entries.get(id);
+ }
+}
+
+export class InstantiationService implements IInstantiationService {
+ public serviceBrand: undefined;
+
+ private readonly _services: ServiceCollection = new ServiceCollection();
+
+ constructor() {
+ this._services.set(IInstantiationService, this);
+ }
+
+ public setService<T>(id: IServiceIdentifier<T>, instance: T): void {
+ this._services.set(id, instance);
+ }
+
+ public getService<T>(id: IServiceIdentifier<T>): T | undefined {
+ return this._services.get(id);
+ }
+
+ public createInstance<T>(ctor: any, ...args: any[]): T {
+ const serviceDependencies = getServiceDependencies(ctor).sort((a, b) => a.index - b.index);
+
+ const serviceArgs: any[] = [];
+ for (const dependency of serviceDependencies) {
+ const service = this._services.get(dependency.id);
+ if (!service) {
+ throw new Error(`[createInstance] ${ctor.name} depends on UNKNOWN service ${dependency.id}.`);
+ }
+ serviceArgs.push(service);
+ }
+
+ const firstServiceArgPos = serviceDependencies.length > 0 ? serviceDependencies[0].index : args.length;
+
+ // check for argument mismatches, adjust static args if needed
+ if (args.length !== firstServiceArgPos) {
+ throw new Error(`[createInstance] First service dependency of ${ctor.name} at position ${firstServiceArgPos + 1} conflicts with ${args.length} static arguments`);
+ }
+
+ // now create the instance
+ return new ctor(...[...args, ...serviceArgs]);
+ }
+}
diff --git a/node_modules/xterm/src/common/services/LogService.ts b/node_modules/xterm/src/common/services/LogService.ts
new file mode 100644
index 0000000..d356656
--- /dev/null
+++ b/node_modules/xterm/src/common/services/LogService.ts
@@ -0,0 +1,88 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { ILogService, IOptionsService, LogLevelEnum } from 'common/services/Services';
+
+type LogType = (message?: any, ...optionalParams: any[]) => void;
+
+interface IConsole {
+ log: LogType;
+ error: LogType;
+ info: LogType;
+ trace: LogType;
+ warn: LogType;
+}
+
+// console is available on both node.js and browser contexts but the common
+// module doesn't depend on them so we need to explicitly declare it.
+declare const console: IConsole;
+
+const optionsKeyToLogLevel: { [key: string]: LogLevelEnum } = {
+ debug: LogLevelEnum.DEBUG,
+ info: LogLevelEnum.INFO,
+ warn: LogLevelEnum.WARN,
+ error: LogLevelEnum.ERROR,
+ off: LogLevelEnum.OFF
+};
+
+const LOG_PREFIX = 'xterm.js: ';
+
+export class LogService implements ILogService {
+ public serviceBrand: any;
+
+ public logLevel: LogLevelEnum = LogLevelEnum.OFF;
+
+ constructor(
+ @IOptionsService private readonly _optionsService: IOptionsService
+ ) {
+ this._updateLogLevel();
+ this._optionsService.onOptionChange(key => {
+ if (key === 'logLevel') {
+ this._updateLogLevel();
+ }
+ });
+ }
+
+ private _updateLogLevel(): void {
+ this.logLevel = optionsKeyToLogLevel[this._optionsService.rawOptions.logLevel];
+ }
+
+ private _evalLazyOptionalParams(optionalParams: any[]): void {
+ for (let i = 0; i < optionalParams.length; i++) {
+ if (typeof optionalParams[i] === 'function') {
+ optionalParams[i] = optionalParams[i]();
+ }
+ }
+ }
+
+ private _log(type: LogType, message: string, optionalParams: any[]): void {
+ this._evalLazyOptionalParams(optionalParams);
+ type.call(console, LOG_PREFIX + message, ...optionalParams);
+ }
+
+ public debug(message: string, ...optionalParams: any[]): void {
+ if (this.logLevel <= LogLevelEnum.DEBUG) {
+ this._log(console.log, message, optionalParams);
+ }
+ }
+
+ public info(message: string, ...optionalParams: any[]): void {
+ if (this.logLevel <= LogLevelEnum.INFO) {
+ this._log(console.info, message, optionalParams);
+ }
+ }
+
+ public warn(message: string, ...optionalParams: any[]): void {
+ if (this.logLevel <= LogLevelEnum.WARN) {
+ this._log(console.warn, message, optionalParams);
+ }
+ }
+
+ public error(message: string, ...optionalParams: any[]): void {
+ if (this.logLevel <= LogLevelEnum.ERROR) {
+ this._log(console.error, message, optionalParams);
+ }
+ }
+}
diff --git a/node_modules/xterm/src/common/services/OptionsService.ts b/node_modules/xterm/src/common/services/OptionsService.ts
new file mode 100644
index 0000000..43fe998
--- /dev/null
+++ b/node_modules/xterm/src/common/services/OptionsService.ts
@@ -0,0 +1,177 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IOptionsService, ITerminalOptions, FontWeight } from 'common/services/Services';
+import { EventEmitter, IEvent } from 'common/EventEmitter';
+import { isMac } from 'common/Platform';
+
+// Source: https://freesound.org/people/altemark/sounds/45759/
+// This sound is released under the Creative Commons Attribution 3.0 Unported
+// (CC BY 3.0) license. It was created by 'altemark'. No modifications have been
+// made, apart from the conversion to base64.
+export const DEFAULT_BELL_SOUND = 'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjMyLjEwNAAAAAAAAAAAAAAA//tQxAADB8AhSmxhIIEVCSiJrDCQBTcu3UrAIwUdkRgQbFAZC1CQEwTJ9mjRvBA4UOLD8nKVOWfh+UlK3z/177OXrfOdKl7pyn3Xf//WreyTRUoAWgBgkOAGbZHBgG1OF6zM82DWbZaUmMBptgQhGjsyYqc9ae9XFz280948NMBWInljyzsNRFLPWdnZGWrddDsjK1unuSrVN9jJsK8KuQtQCtMBjCEtImISdNKJOopIpBFpNSMbIHCSRpRR5iakjTiyzLhchUUBwCgyKiweBv/7UsQbg8isVNoMPMjAAAA0gAAABEVFGmgqK////9bP/6XCykxBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq';
+
+export const DEFAULT_OPTIONS: Readonly<ITerminalOptions> = {
+ cols: 80,
+ rows: 24,
+ cursorBlink: false,
+ cursorStyle: 'block',
+ cursorWidth: 1,
+ customGlyphs: true,
+ bellSound: DEFAULT_BELL_SOUND,
+ bellStyle: 'none',
+ drawBoldTextInBrightColors: true,
+ fastScrollModifier: 'alt',
+ fastScrollSensitivity: 5,
+ fontFamily: 'courier-new, courier, monospace',
+ fontSize: 15,
+ fontWeight: 'normal',
+ fontWeightBold: 'bold',
+ lineHeight: 1.0,
+ linkTooltipHoverDuration: 500,
+ letterSpacing: 0,
+ logLevel: 'info',
+ scrollback: 1000,
+ scrollSensitivity: 1,
+ screenReaderMode: false,
+ macOptionIsMeta: false,
+ macOptionClickForcesSelection: false,
+ minimumContrastRatio: 1,
+ disableStdin: false,
+ allowProposedApi: true,
+ allowTransparency: false,
+ tabStopWidth: 8,
+ theme: {},
+ rightClickSelectsWord: isMac,
+ rendererType: 'canvas',
+ windowOptions: {},
+ windowsMode: false,
+ wordSeparator: ' ()[]{}\',"`',
+ altClickMovesCursor: true,
+ convertEol: false,
+ termName: 'xterm',
+ cancelEvents: false
+};
+
+const FONT_WEIGHT_OPTIONS: Extract<FontWeight, string>[] = ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'];
+
+export class OptionsService implements IOptionsService {
+ public serviceBrand: any;
+
+ public readonly rawOptions: ITerminalOptions;
+ public options: ITerminalOptions;
+
+ private _onOptionChange = new EventEmitter<string>();
+ public get onOptionChange(): IEvent<string> { return this._onOptionChange.event; }
+
+ constructor(options: Partial<ITerminalOptions>) {
+ // set the default value of each option
+ const defaultOptions = { ...DEFAULT_OPTIONS };
+ for (const key in options) {
+ if (key in defaultOptions) {
+ try {
+ const newValue = options[key];
+ defaultOptions[key] = this._sanitizeAndValidateOption(key, newValue);
+ } catch (e) {
+ console.error(e);
+ }
+ }
+ }
+
+ // set up getters and setters for each option
+ this.rawOptions = defaultOptions;
+ this.options = { ... defaultOptions };
+ this._setupOptions();
+ }
+
+ private _setupOptions(): void {
+ const getter = (propName: string): any => {
+ if (!(propName in DEFAULT_OPTIONS)) {
+ throw new Error(`No option with key "${propName}"`);
+ }
+ return this.rawOptions[propName];
+ };
+
+ const setter = (propName: string, value: any): void => {
+ if (!(propName in DEFAULT_OPTIONS)) {
+ throw new Error(`No option with key "${propName}"`);
+ }
+
+ value = this._sanitizeAndValidateOption(propName, value);
+ // Don't fire an option change event if they didn't change
+ if (this.rawOptions[propName] !== value) {
+ this.rawOptions[propName] = value;
+ this._onOptionChange.fire(propName);
+ }
+ };
+
+ for (const propName in this.rawOptions) {
+ const desc = {
+ get: getter.bind(this, propName),
+ set: setter.bind(this, propName)
+ };
+ Object.defineProperty(this.options, propName, desc);
+ }
+ }
+
+ public setOption(key: string, value: any): void {
+ this.options[key] = value;
+ }
+
+ private _sanitizeAndValidateOption(key: string, value: any): any {
+ switch (key) {
+ case 'bellStyle':
+ case 'cursorStyle':
+ case 'rendererType':
+ case 'wordSeparator':
+ if (!value) {
+ value = DEFAULT_OPTIONS[key];
+ }
+ break;
+ case 'fontWeight':
+ case 'fontWeightBold':
+ if (typeof value === 'number' && 1 <= value && value <= 1000) {
+ // already valid numeric value
+ break;
+ }
+ value = FONT_WEIGHT_OPTIONS.includes(value) ? value : DEFAULT_OPTIONS[key];
+ break;
+ case 'cursorWidth':
+ value = Math.floor(value);
+ // Fall through for bounds check
+ case 'lineHeight':
+ case 'tabStopWidth':
+ if (value < 1) {
+ throw new Error(`${key} cannot be less than 1, value: ${value}`);
+ }
+ break;
+ case 'minimumContrastRatio':
+ value = Math.max(1, Math.min(21, Math.round(value * 10) / 10));
+ break;
+ case 'scrollback':
+ value = Math.min(value, 4294967295);
+ if (value < 0) {
+ throw new Error(`${key} cannot be less than 0, value: ${value}`);
+ }
+ break;
+ case 'fastScrollSensitivity':
+ case 'scrollSensitivity':
+ if (value <= 0) {
+ throw new Error(`${key} cannot be less than or equal to 0, value: ${value}`);
+ }
+ case 'rows':
+ case 'cols':
+ if (!value && value !== 0) {
+ throw new Error(`${key} must be numeric, value: ${value}`);
+ }
+ break;
+ }
+ return value;
+ }
+
+ public getOption(key: string): any {
+ return this.options[key];
+ }
+}
diff --git a/node_modules/xterm/src/common/services/ServiceRegistry.ts b/node_modules/xterm/src/common/services/ServiceRegistry.ts
new file mode 100644
index 0000000..6510fb8
--- /dev/null
+++ b/node_modules/xterm/src/common/services/ServiceRegistry.ts
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ *
+ * This was heavily inspired from microsoft/vscode's dependency injection system (MIT).
+ */
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { IServiceIdentifier } from 'common/services/Services';
+
+const DI_TARGET = 'di$target';
+const DI_DEPENDENCIES = 'di$dependencies';
+
+export const serviceRegistry: Map<string, IServiceIdentifier<any>> = new Map();
+
+export function getServiceDependencies(ctor: any): { id: IServiceIdentifier<any>, index: number, optional: boolean }[] {
+ return ctor[DI_DEPENDENCIES] || [];
+}
+
+export function createDecorator<T>(id: string): IServiceIdentifier<T> {
+ if (serviceRegistry.has(id)) {
+ return serviceRegistry.get(id)!;
+ }
+
+ const decorator: any = function (target: Function, key: string, index: number): any {
+ if (arguments.length !== 3) {
+ throw new Error('@IServiceName-decorator can only be used to decorate a parameter');
+ }
+
+ storeServiceDependency(decorator, target, index);
+ };
+
+ decorator.toString = () => id;
+
+ serviceRegistry.set(id, decorator);
+ return decorator;
+}
+
+function storeServiceDependency(id: Function, target: Function, index: number): void {
+ if ((target as any)[DI_TARGET] === target) {
+ (target as any)[DI_DEPENDENCIES].push({ id, index });
+ } else {
+ (target as any)[DI_DEPENDENCIES] = [{ id, index }];
+ (target as any)[DI_TARGET] = target;
+ }
+}
diff --git a/node_modules/xterm/src/common/services/Services.ts b/node_modules/xterm/src/common/services/Services.ts
new file mode 100644
index 0000000..90dca98
--- /dev/null
+++ b/node_modules/xterm/src/common/services/Services.ts
@@ -0,0 +1,300 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IEvent } 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 { createDecorator } from 'common/services/ServiceRegistry';
+
+export const IBufferService = createDecorator<IBufferService>('BufferService');
+export interface IBufferService {
+ serviceBrand: undefined;
+
+ readonly cols: number;
+ readonly rows: number;
+ readonly buffer: IBuffer;
+ readonly buffers: IBufferSet;
+ isUserScrolling: boolean;
+ onResize: IEvent<{ cols: number, rows: number }>;
+ onScroll: IEvent<number>;
+ scroll(eraseAttr: IAttributeData, isWrapped?: boolean): void;
+ scrollToBottom(): void;
+ scrollToTop(): void;
+ scrollToLine(line: number): void;
+ scrollLines(disp: number, suppressScrollEvent?: boolean, source?: ScrollSource): void;
+ scrollPages(pageCount: number): void;
+ resize(cols: number, rows: number): void;
+ reset(): void;
+}
+
+export const ICoreMouseService = createDecorator<ICoreMouseService>('CoreMouseService');
+export interface ICoreMouseService {
+ activeProtocol: string;
+ activeEncoding: string;
+ areMouseEventsActive: boolean;
+ addProtocol(name: string, protocol: ICoreMouseProtocol): void;
+ addEncoding(name: string, encoding: CoreMouseEncoding): void;
+ reset(): void;
+
+ /**
+ * Triggers a mouse event to be sent.
+ *
+ * Returns true if the event passed all protocol restrictions and a report
+ * was sent, otherwise false. The return value may be used to decide whether
+ * the default event action in the bowser component should be omitted.
+ *
+ * Note: The method will change values of the given event object
+ * to fullfill protocol and encoding restrictions.
+ */
+ triggerMouseEvent(event: ICoreMouseEvent): boolean;
+
+ /**
+ * Event to announce changes in mouse tracking.
+ */
+ onProtocolChange: IEvent<CoreMouseEventType>;
+
+ /**
+ * Human readable version of mouse events.
+ */
+ explainEvents(events: CoreMouseEventType): { [event: string]: boolean };
+}
+
+export const ICoreService = createDecorator<ICoreService>('CoreService');
+export interface ICoreService {
+ serviceBrand: undefined;
+
+ /**
+ * Initially the cursor will not be visible until the first time the terminal
+ * is focused.
+ */
+ isCursorInitialized: boolean;
+ isCursorHidden: boolean;
+
+ readonly modes: IModes;
+ readonly decPrivateModes: IDecPrivateModes;
+
+ readonly onData: IEvent<string>;
+ readonly onUserInput: IEvent<void>;
+ readonly onBinary: IEvent<string>;
+
+ reset(): void;
+
+ /**
+ * Triggers the onData event in the public API.
+ * @param data The data that is being emitted.
+ * @param wasFromUser Whether the data originated from the user (as opposed to
+ * resulting from parsing incoming data). When true this will also:
+ * - Scroll to the bottom of the buffer.s
+ * - Fire the `onUserInput` event (so selection can be cleared).
+ */
+ triggerDataEvent(data: string, wasUserInput?: boolean): void;
+
+ /**
+ * Triggers the onBinary event in the public API.
+ * @param data The data that is being emitted.
+ */
+ triggerBinaryEvent(data: string): void;
+}
+
+export const ICharsetService = createDecorator<ICharsetService>('CharsetService');
+export interface ICharsetService {
+ serviceBrand: undefined;
+
+ charset: ICharset | undefined;
+ readonly glevel: number;
+
+ reset(): void;
+
+ /**
+ * Set the G level of the terminal.
+ * @param g
+ */
+ setgLevel(g: number): void;
+
+ /**
+ * Set the charset for the given G level of the terminal.
+ * @param g
+ * @param charset
+ */
+ setgCharset(g: number, charset: ICharset | undefined): void;
+}
+
+export const IDirtyRowService = createDecorator<IDirtyRowService>('DirtyRowService');
+export interface IDirtyRowService {
+ serviceBrand: undefined;
+
+ readonly start: number;
+ readonly end: number;
+
+ clearRange(): void;
+ markDirty(y: number): void;
+ markRangeDirty(y1: number, y2: number): void;
+ markAllDirty(): void;
+}
+
+export interface IServiceIdentifier<T> {
+ (...args: any[]): void;
+ type: T;
+}
+
+export interface IBrandedService {
+ serviceBrand: undefined;
+}
+
+type GetLeadingNonServiceArgs<Args> =
+ Args extends [...IBrandedService[]] ? []
+ : Args extends [infer A1, ...IBrandedService[]] ? [A1]
+ : Args extends [infer A1, infer A2, ...IBrandedService[]] ? [A1, A2]
+ : Args extends [infer A1, infer A2, infer A3, ...IBrandedService[]] ? [A1, A2, A3]
+ : Args extends [infer A1, infer A2, infer A3, infer A4, ...IBrandedService[]] ? [A1, A2, A3, A4]
+ : Args extends [infer A1, infer A2, infer A3, infer A4, infer A5, ...IBrandedService[]] ? [A1, A2, A3, A4, A5]
+ : Args extends [infer A1, infer A2, infer A3, infer A4, infer A5, infer A6, ...IBrandedService[]] ? [A1, A2, A3, A4, A5, A6]
+ : Args extends [infer A1, infer A2, infer A3, infer A4, infer A5, infer A6, infer A7, ...IBrandedService[]] ? [A1, A2, A3, A4, A5, A6, A7]
+ : Args extends [infer A1, infer A2, infer A3, infer A4, infer A5, infer A6, infer A7, infer A8, ...IBrandedService[]] ? [A1, A2, A3, A4, A5, A6, A7, A8]
+ : never;
+
+export const IInstantiationService = createDecorator<IInstantiationService>('InstantiationService');
+export interface IInstantiationService {
+ serviceBrand: undefined;
+
+ setService<T>(id: IServiceIdentifier<T>, instance: T): void;
+ getService<T>(id: IServiceIdentifier<T>): T | undefined;
+ createInstance<Ctor extends new (...args: any[]) => any, R extends InstanceType<Ctor>>(t: Ctor, ...args: GetLeadingNonServiceArgs<ConstructorParameters<Ctor>>): R;
+}
+
+export enum LogLevelEnum {
+ DEBUG = 0,
+ INFO = 1,
+ WARN = 2,
+ ERROR = 3,
+ OFF = 4
+}
+
+export const ILogService = createDecorator<ILogService>('LogService');
+export interface ILogService {
+ serviceBrand: undefined;
+
+ logLevel: LogLevelEnum;
+
+ debug(message: any, ...optionalParams: any[]): void;
+ info(message: any, ...optionalParams: any[]): void;
+ warn(message: any, ...optionalParams: any[]): void;
+ error(message: any, ...optionalParams: any[]): void;
+}
+
+export const IOptionsService = createDecorator<IOptionsService>('OptionsService');
+export interface IOptionsService {
+ serviceBrand: undefined;
+
+ /**
+ * Read only access to the raw options object, this is an internal-only fast path for accessing
+ * single options without any validation as we trust TypeScript to enforce correct usage
+ * internally.
+ */
+ readonly rawOptions: Readonly<ITerminalOptions>;
+ readonly options: ITerminalOptions;
+
+ readonly onOptionChange: IEvent<string>;
+
+ setOption<T>(key: string, value: T): void;
+ getOption<T>(key: string): T | undefined;
+}
+
+export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number;
+export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'off';
+
+export type RendererType = 'dom' | 'canvas';
+
+export interface ITerminalOptions {
+ allowProposedApi: boolean;
+ allowTransparency: boolean;
+ altClickMovesCursor: boolean;
+ bellSound: string;
+ bellStyle: 'none' | 'sound' /* | 'visual' | 'both' */;
+ cols: number;
+ convertEol: boolean;
+ cursorBlink: boolean;
+ cursorStyle: 'block' | 'underline' | 'bar';
+ cursorWidth: number;
+ customGlyphs: boolean;
+ disableStdin: boolean;
+ drawBoldTextInBrightColors: boolean;
+ fastScrollModifier: 'alt' | 'ctrl' | 'shift' | undefined;
+ fastScrollSensitivity: number;
+ fontSize: number;
+ fontFamily: string;
+ fontWeight: FontWeight;
+ fontWeightBold: FontWeight;
+ letterSpacing: number;
+ lineHeight: number;
+ linkTooltipHoverDuration: number;
+ logLevel: LogLevel;
+ macOptionIsMeta: boolean;
+ macOptionClickForcesSelection: boolean;
+ minimumContrastRatio: number;
+ rendererType: RendererType;
+ rightClickSelectsWord: boolean;
+ rows: number;
+ screenReaderMode: boolean;
+ scrollback: number;
+ scrollSensitivity: number;
+ tabStopWidth: number;
+ theme: ITheme;
+ windowsMode: boolean;
+ windowOptions: IWindowOptions;
+ wordSeparator: string;
+
+ [key: string]: any;
+ cancelEvents: boolean;
+ termName: string;
+}
+
+export interface ITheme {
+ foreground?: string;
+ background?: string;
+ cursor?: string;
+ cursorAccent?: string;
+ selection?: string;
+ black?: string;
+ red?: string;
+ green?: string;
+ yellow?: string;
+ blue?: string;
+ magenta?: string;
+ cyan?: string;
+ white?: string;
+ brightBlack?: string;
+ brightRed?: string;
+ brightGreen?: string;
+ brightYellow?: string;
+ brightBlue?: string;
+ brightMagenta?: string;
+ brightCyan?: string;
+ brightWhite?: string;
+}
+
+export const IUnicodeService = createDecorator<IUnicodeService>('UnicodeService');
+export interface IUnicodeService {
+ serviceBrand: undefined;
+ /** Register an Unicode version provider. */
+ register(provider: IUnicodeVersionProvider): void;
+ /** Registered Unicode versions. */
+ readonly versions: string[];
+ /** Currently active version. */
+ activeVersion: string;
+ /** Event triggered, when activate version changed. */
+ readonly onChange: IEvent<string>;
+
+ /**
+ * Unicode version dependent
+ */
+ wcwidth(codepoint: number): number;
+ getStringCellWidth(s: string): number;
+}
+
+export interface IUnicodeVersionProvider {
+ readonly version: string;
+ wcwidth(ucs: number): 0 | 1 | 2;
+}
diff --git a/node_modules/xterm/src/common/services/UnicodeService.ts b/node_modules/xterm/src/common/services/UnicodeService.ts
new file mode 100644
index 0000000..e96b757
--- /dev/null
+++ b/node_modules/xterm/src/common/services/UnicodeService.ts
@@ -0,0 +1,82 @@
+/**
+ * Copyright (c) 2019 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+import { IUnicodeService, IUnicodeVersionProvider } from 'common/services/Services';
+import { EventEmitter, IEvent } from 'common/EventEmitter';
+import { UnicodeV6 } from 'common/input/UnicodeV6';
+
+
+export class UnicodeService implements IUnicodeService {
+ public serviceBrand: any;
+
+ private _providers: {[key: string]: IUnicodeVersionProvider} = Object.create(null);
+ private _active: string = '';
+ private _activeProvider: IUnicodeVersionProvider;
+ private _onChange = new EventEmitter<string>();
+ public get onChange(): IEvent<string> { return this._onChange.event; }
+
+ constructor() {
+ const defaultProvider = new UnicodeV6();
+ this.register(defaultProvider);
+ this._active = defaultProvider.version;
+ this._activeProvider = defaultProvider;
+ }
+
+ public get versions(): string[] {
+ return Object.keys(this._providers);
+ }
+
+ public get activeVersion(): string {
+ return this._active;
+ }
+
+ public set activeVersion(version: string) {
+ if (!this._providers[version]) {
+ throw new Error(`unknown Unicode version "${version}"`);
+ }
+ this._active = version;
+ this._activeProvider = this._providers[version];
+ this._onChange.fire(version);
+ }
+
+ public register(provider: IUnicodeVersionProvider): void {
+ this._providers[provider.version] = provider;
+ }
+
+ /**
+ * Unicode version dependent interface.
+ */
+ public wcwidth(num: number): number {
+ return this._activeProvider.wcwidth(num);
+ }
+
+ public getStringCellWidth(s: string): number {
+ let result = 0;
+ const length = s.length;
+ for (let i = 0; i < length; ++i) {
+ let code = s.charCodeAt(i);
+ // surrogate pair first
+ if (0xD800 <= code && code <= 0xDBFF) {
+ if (++i >= length) {
+ // this should not happen with strings retrieved from
+ // Buffer.translateToString as it converts from UTF-32
+ // and therefore always should contain the second part
+ // for any other string we still have to handle it somehow:
+ // simply treat the lonely surrogate first as a single char (UCS-2 behavior)
+ return result + this.wcwidth(code);
+ }
+ const second = s.charCodeAt(i);
+ // convert surrogate pair to high codepoint only for valid second part (UTF-16)
+ // otherwise treat them independently (UCS-2 behavior)
+ if (0xDC00 <= second && second <= 0xDFFF) {
+ code = (code - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
+ } else {
+ result += this.wcwidth(second);
+ }
+ }
+ result += this.wcwidth(code);
+ }
+ return result;
+ }
+}
diff --git a/node_modules/xterm/src/headless/Terminal.ts b/node_modules/xterm/src/headless/Terminal.ts
new file mode 100644
index 0000000..7f138ce
--- /dev/null
+++ b/node_modules/xterm/src/headless/Terminal.ts
@@ -0,0 +1,170 @@
+/**
+ * Copyright (c) 2014 The xterm.js authors. All rights reserved.
+ * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
+ * @license MIT
+ *
+ * Originally forked from (with the author's permission):
+ * Fabrice Bellard's javascript vt100 for jslinux:
+ * http://bellard.org/jslinux/
+ * Copyright (c) 2011 Fabrice Bellard
+ * The original design remains. The terminal itself
+ * has been extended to include xterm CSI codes, among
+ * other features.
+ *
+ * Terminal Emulation References:
+ * http://vt100.net/
+ * http://invisible-island.net/xterm/ctlseqs/ctlseqs.txt
+ * http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
+ * http://invisible-island.net/vttest/
+ * http://www.inwap.com/pdp10/ansicode.txt
+ * http://linux.die.net/man/4/console_codes
+ * http://linux.die.net/man/7/urxvt
+ */
+
+import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
+import { IBuffer } from 'common/buffer/Types';
+import { CoreTerminal } from 'common/CoreTerminal';
+import { EventEmitter, forwardEvent, IEvent } from 'common/EventEmitter';
+import { ITerminalOptions as IInitializedTerminalOptions } from 'common/services/Services';
+import { IMarker, ITerminalOptions, ScrollSource } from 'common/Types';
+
+export class Terminal extends CoreTerminal {
+ // TODO: We should remove options once components adopt optionsService
+ public get options(): IInitializedTerminalOptions { return this.optionsService.options; }
+
+ private _onBell = new EventEmitter<void>();
+ public get onBell(): IEvent<void> { return this._onBell.event; }
+ private _onCursorMove = new EventEmitter<void>();
+ public get onCursorMove(): IEvent<void> { return this._onCursorMove.event; }
+ private _onTitleChange = new EventEmitter<string>();
+ public get onTitleChange(): IEvent<string> { return this._onTitleChange.event; }
+
+ private _onA11yCharEmitter = new EventEmitter<string>();
+ public get onA11yChar(): IEvent<string> { return this._onA11yCharEmitter.event; }
+ private _onA11yTabEmitter = new EventEmitter<number>();
+ public get onA11yTab(): IEvent<number> { return this._onA11yTabEmitter.event; }
+
+ /**
+ * Creates a new `Terminal` object.
+ *
+ * @param options An object containing a set of options, the available options are:
+ * - `cursorBlink` (boolean): Whether the terminal cursor blinks
+ * - `cols` (number): The number of columns of the terminal (horizontal size)
+ * - `rows` (number): The number of rows of the terminal (vertical size)
+ *
+ * @public
+ * @class Xterm Xterm
+ * @alias module:xterm/src/xterm
+ */
+ constructor(
+ options: ITerminalOptions = {}
+ ) {
+ super(options);
+
+ this._setup();
+
+ // Setup InputHandler listeners
+ this.register(this._inputHandler.onRequestBell(() => this.bell()));
+ this.register(this._inputHandler.onRequestReset(() => this.reset()));
+ this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove));
+ this.register(forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange));
+ this.register(forwardEvent(this._inputHandler.onA11yChar, this._onA11yCharEmitter));
+ this.register(forwardEvent(this._inputHandler.onA11yTab, this._onA11yTabEmitter));
+ }
+
+ public dispose(): void {
+ if (this._isDisposed) {
+ return;
+ }
+ super.dispose();
+ this.write = () => { };
+ }
+
+ /**
+ * Convenience property to active buffer.
+ */
+ public get buffer(): IBuffer {
+ return this.buffers.active;
+ }
+
+ protected _updateOptions(key: string): void {
+ super._updateOptions(key);
+
+ // TODO: These listeners should be owned by individual components
+ switch (key) {
+ case 'tabStopWidth': this.buffers.setupTabStops(); break;
+ }
+ }
+
+ // TODO: Support paste here?
+
+ public get markers(): IMarker[] {
+ return this.buffer.markers;
+ }
+
+ public addMarker(cursorYOffset: number): IMarker | undefined {
+ // Disallow markers on the alt buffer
+ if (this.buffer !== this.buffers.normal) {
+ return;
+ }
+
+ return this.buffer.addMarker(this.buffer.ybase + this.buffer.y + cursorYOffset);
+ }
+
+ public bell(): void {
+ this._onBell.fire();
+ }
+
+ /**
+ * Resizes the terminal.
+ *
+ * @param x The number of columns to resize to.
+ * @param y The number of rows to resize to.
+ */
+ public resize(x: number, y: number): void {
+ if (x === this.cols && y === this.rows) {
+ return;
+ }
+
+ super.resize(x, y);
+ }
+
+ /**
+ * Clear the entire buffer, making the prompt line the new first line.
+ */
+ public clear(): void {
+ if (this.buffer.ybase === 0 && this.buffer.y === 0) {
+ // Don't clear if it's already clear
+ return;
+ }
+ this.buffer.lines.set(0, this.buffer.lines.get(this.buffer.ybase + this.buffer.y)!);
+ this.buffer.lines.length = 1;
+ this.buffer.ydisp = 0;
+ this.buffer.ybase = 0;
+ this.buffer.y = 0;
+ for (let i = 1; i < this.rows; i++) {
+ this.buffer.lines.push(this.buffer.getBlankLine(DEFAULT_ATTR_DATA));
+ }
+ this._onScroll.fire({ position: this.buffer.ydisp, source: ScrollSource.TERMINAL });
+ }
+
+ /**
+ * Reset terminal.
+ * Note: Calling this directly from JS is synchronous but does not clear
+ * input buffers and does not reset the parser, thus the terminal will
+ * continue to apply pending input data.
+ * If you need in band reset (synchronous with input data) consider
+ * using DECSTR (soft reset, CSI ! p) or RIS instead (hard reset, ESC c).
+ */
+ public reset(): void {
+ /**
+ * Since _setup handles a full terminal creation, we have to carry forward
+ * a few things that should not reset.
+ */
+ this.options.rows = this.rows;
+ this.options.cols = this.cols;
+
+ this._setup();
+ super.reset();
+ }
+}
diff --git a/node_modules/xterm/src/headless/Types.d.ts b/node_modules/xterm/src/headless/Types.d.ts
new file mode 100644
index 0000000..868e5c1
--- /dev/null
+++ b/node_modules/xterm/src/headless/Types.d.ts
@@ -0,0 +1,31 @@
+import { IBuffer, IBufferSet } from 'common/buffer/Types';
+import { IEvent } from 'common/EventEmitter';
+import { IFunctionIdentifier, IParams } from 'common/parser/Types';
+import { ICoreTerminal, IDisposable, IMarker, ITerminalOptions } from 'common/Types';
+
+export interface ITerminal extends ICoreTerminal {
+ rows: number;
+ cols: number;
+ buffer: IBuffer;
+ buffers: IBufferSet;
+ markers: IMarker[];
+ // TODO: We should remove options once components adopt optionsService
+ options: ITerminalOptions;
+
+ onCursorMove: IEvent<void>;
+ onData: IEvent<string>;
+ onBinary: IEvent<string>;
+ onLineFeed: IEvent<void>;
+ onResize: IEvent<{ cols: number, rows: number }>;
+ onTitleChange: IEvent<string>;
+ resize(columns: number, rows: number): void;
+ addCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean): IDisposable;
+ addDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean): IDisposable;
+ addEscHandler(id: IFunctionIdentifier, callback: () => boolean): IDisposable;
+ addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;
+ addMarker(cursorYOffset: number): IMarker | undefined;
+ dispose(): void;
+ clear(): void;
+ write(data: string | Uint8Array, callback?: () => void): void;
+ reset(): void;
+}
diff --git a/node_modules/xterm/src/headless/public/Terminal.ts b/node_modules/xterm/src/headless/public/Terminal.ts
new file mode 100644
index 0000000..9aa171a
--- /dev/null
+++ b/node_modules/xterm/src/headless/public/Terminal.ts
@@ -0,0 +1,216 @@
+/**
+ * Copyright (c) 2018 The xterm.js authors. All rights reserved.
+ * @license MIT
+ */
+
+import { IEvent } from 'common/EventEmitter';
+import { BufferNamespaceApi } from 'common/public/BufferNamespaceApi';
+import { ParserApi } from 'common/public/ParserApi';
+import { UnicodeApi } from 'common/public/UnicodeApi';
+import { IBufferNamespace as IBufferNamespaceApi, IMarker, IModes, IParser, ITerminalAddon, IUnicodeHandling, Terminal as ITerminalApi } from 'xterm-headless';
+import { Terminal as TerminalCore } from 'headless/Terminal';
+import { AddonManager } from 'common/public/AddonManager';
+import { ITerminalOptions } from 'common/Types';
+
+/**
+ * The set of options that only have an effect when set in the Terminal constructor.
+ */
+const CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows'];
+
+export class Terminal implements ITerminalApi {
+ private _core: TerminalCore;
+ private _addonManager: AddonManager;
+ private _parser: IParser | undefined;
+ private _buffer: BufferNamespaceApi | undefined;
+ private _publicOptions: ITerminalOptions;
+
+ constructor(options?: ITerminalOptions) {
+ this._core = new TerminalCore(options);
+ this._addonManager = new AddonManager();
+
+ this._publicOptions = { ... this._core.options };
+ const getter = (propName: string): any => {
+ return this._core.options[propName];
+ };
+ const setter = (propName: string, value: any): void => {
+ this._checkReadonlyOptions(propName);
+ this._core.options[propName] = value;
+ };
+
+ for (const propName in this._core.options) {
+ Object.defineProperty(this._publicOptions, propName, {
+ get: () => {
+ return this._core.options[propName];
+ },
+ set: (value: any) => {
+ this._checkReadonlyOptions(propName);
+ this._core.options[propName] = value;
+ }
+ });
+ const desc = {
+ get: getter.bind(this, propName),
+ set: setter.bind(this, propName)
+ };
+ Object.defineProperty(this._publicOptions, propName, desc);
+ }
+ }
+
+ private _checkReadonlyOptions(propName: string): void {
+ // Throw an error if any constructor only option is modified
+ // from terminal.options
+ // Modifications from anywhere else are allowed
+ if (CONSTRUCTOR_ONLY_OPTIONS.includes(propName)) {
+ throw new Error(`Option "${propName}" can only be set in the constructor`);
+ }
+ }
+
+ private _checkProposedApi(): void {
+ if (!this._core.optionsService.options.allowProposedApi) {
+ throw new Error('You must set the allowProposedApi option to true to use proposed API');
+ }
+ }
+
+ public get onBell(): IEvent<void> { return this._core.onBell; }
+ public get onBinary(): IEvent<string> { return this._core.onBinary; }
+ public get onCursorMove(): IEvent<void> { return this._core.onCursorMove; }
+ public get onData(): IEvent<string> { return this._core.onData; }
+ public get onLineFeed(): IEvent<void> { return this._core.onLineFeed; }
+ public get onResize(): IEvent<{ cols: number, rows: number }> { return this._core.onResize; }
+ public get onScroll(): IEvent<number> { return this._core.onScroll; }
+ public get onTitleChange(): IEvent<string> { return this._core.onTitleChange; }
+
+ public get parser(): IParser {
+ this._checkProposedApi();
+ if (!this._parser) {
+ this._parser = new ParserApi(this._core);
+ }
+ return this._parser;
+ }
+ public get unicode(): IUnicodeHandling {
+ this._checkProposedApi();
+ return new UnicodeApi(this._core);
+ }
+ public get rows(): number { return this._core.rows; }
+ public get cols(): number { return this._core.cols; }
+ public get buffer(): IBufferNamespaceApi {
+ this._checkProposedApi();
+ if (!this._buffer) {
+ this._buffer = new BufferNamespaceApi(this._core);
+ }
+ return this._buffer;
+ }
+ public get markers(): ReadonlyArray<IMarker> {
+ this._checkProposedApi();
+ return this._core.markers;
+ }
+ public get modes(): IModes {
+ const m = this._core.coreService.decPrivateModes;
+ let mouseTrackingMode: 'none' | 'x10' | 'vt200' | 'drag' | 'any' = 'none';
+ switch (this._core.coreMouseService.activeProtocol) {
+ case 'X10': mouseTrackingMode = 'x10'; break;
+ case 'VT200': mouseTrackingMode = 'vt200'; break;
+ case 'DRAG': mouseTrackingMode = 'drag'; break;
+ case 'ANY': mouseTrackingMode = 'any'; break;
+ }
+ return {
+ applicationCursorKeysMode: m.applicationCursorKeys,
+ applicationKeypadMode: m.applicationKeypad,
+ bracketedPasteMode: m.bracketedPasteMode,
+ insertMode: this._core.coreService.modes.insertMode,
+ mouseTrackingMode: mouseTrackingMode,
+ originMode: m.origin,
+ reverseWraparoundMode: m.reverseWraparound,
+ sendFocusMode: m.sendFocus,
+ wraparoundMode: m.wraparound
+ };
+ }
+ public get options(): ITerminalOptions {
+ return this._publicOptions;
+ }
+ public set options(options: ITerminalOptions) {
+ for (const propName in options) {
+ this._publicOptions[propName] = options[propName];
+ }
+ }
+ public resize(columns: number, rows: number): void {
+ this._verifyIntegers(columns, rows);
+ this._core.resize(columns, rows);
+ }
+ public registerMarker(cursorYOffset: number): IMarker | undefined {
+ this._checkProposedApi();
+ this._verifyIntegers(cursorYOffset);
+ return this._core.addMarker(cursorYOffset);
+ }
+ public addMarker(cursorYOffset: number): IMarker | undefined {
+ return this.registerMarker(cursorYOffset);
+ }
+ public dispose(): void {
+ this._addonManager.dispose();
+ this._core.dispose();
+ }
+ public scrollLines(amount: number): void {
+ this._verifyIntegers(amount);
+ this._core.scrollLines(amount);
+ }
+ public scrollPages(pageCount: number): void {
+ this._verifyIntegers(pageCount);
+ this._core.scrollPages(pageCount);
+ }
+ public scrollToTop(): void {
+ this._core.scrollToTop();
+ }
+ public scrollToBottom(): void {
+ this._core.scrollToBottom();
+ }
+ public scrollToLine(line: number): void {
+ this._verifyIntegers(line);
+ this._core.scrollToLine(line);
+ }
+ public clear(): void {
+ this._core.clear();
+ }
+ public write(data: string | Uint8Array, callback?: () => void): void {
+ this._core.write(data, callback);
+ }
+ public writeUtf8(data: Uint8Array, callback?: () => void): void {
+ this._core.write(data, callback);
+ }
+ public writeln(data: string | Uint8Array, callback?: () => void): void {
+ this._core.write(data);
+ this._core.write('\r\n', callback);
+ }
+ public getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string;
+ public getOption(key: 'allowTransparency' | 'altClickMovesCursor' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'visualBell'): boolean;
+ public getOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number;
+ public getOption(key: string): any;
+ public getOption(key: any): any {
+ return this._core.optionsService.getOption(key);
+ }
+ public setOption(key: 'bellSound' | 'fontFamily' | 'termName' | 'wordSeparator', value: string): void;
+ public setOption(key: 'fontWeight' | 'fontWeightBold', value: 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number): void;
+ public setOption(key: 'logLevel', value: 'debug' | 'info' | 'warn' | 'error' | 'off'): void;
+ public setOption(key: 'bellStyle', value: 'none' | 'visual' | 'sound' | 'both'): void;
+ public setOption(key: 'cursorStyle', value: 'block' | 'underline' | 'bar'): void;
+ public setOption(key: 'allowTransparency' | 'altClickMovesCursor' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'visualBell', value: boolean): void;
+ public setOption(key: 'fontSize' | 'letterSpacing' | 'lineHeight' | 'tabStopWidth' | 'scrollback', value: number): void;
+ public setOption(key: 'cols' | 'rows', value: number): void;
+ public setOption(key: string, value: any): void;
+ public setOption(key: any, value: any): void {
+ this._core.optionsService.setOption(key, value);
+ }
+ public reset(): void {
+ this._core.reset();
+ }
+ public loadAddon(addon: ITerminalAddon): void {
+ // TODO: This could cause issues if the addon calls renderer apis
+ return this._addonManager.loadAddon(this as any, addon);
+ }
+
+ private _verifyIntegers(...values: number[]): void {
+ for (const value of values) {
+ if (value === Infinity || isNaN(value) || value % 1 !== 0) {
+ throw new Error('This API only accepts integers');
+ }
+ }
+ }
+}