Compare commits
52
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a91c0643f | ||
|
|
6daadc4c55 | ||
|
|
c05d0f650c | ||
|
|
d5b5f1a9a4 | ||
|
|
dfcd674f8c | ||
|
|
a7e0a9c534 | ||
|
|
5839accbf1 | ||
|
|
01c9a98e8e | ||
|
|
bcfed71e1d | ||
|
|
046d4b8314 | ||
|
|
a9da61e605 | ||
|
|
24c7be210c | ||
|
|
9c815250b6 | ||
|
|
377f88796f | ||
|
|
ed6c849501 | ||
|
|
5a21948b4a | ||
|
|
b24bda92ad | ||
|
|
9895691e0e | ||
|
|
77870b06b9 | ||
|
|
41dc93a008 | ||
|
|
ba9f2e0f11 | ||
|
|
b3fb8db6b3 | ||
|
|
91946dcd9b | ||
|
|
41c0e26292 | ||
|
|
fe89817667 | ||
|
|
7ee7aa2719 | ||
|
|
b05c26c6fd | ||
|
|
0adbedf98d | ||
|
|
89ead0b4b7 | ||
|
|
349510065b | ||
|
|
fa8a0e7f24 | ||
|
|
0c4e9c6b27 | ||
|
|
de2b62eeac | ||
|
|
65b2af10f8 | ||
|
|
1a20fbb25f | ||
|
|
d51ba2578b | ||
|
|
56734874ac | ||
|
|
72f1d1048a | ||
|
|
d17df65fad | ||
|
|
c2504fa669 | ||
|
|
5a2bf6d039 | ||
|
|
568e4c6935 | ||
|
|
8b1c5c4ffb | ||
|
|
d34bb5ed79 | ||
|
|
ace4c3663f | ||
|
|
75075ebb70 | ||
|
|
7b8ad8c0b4 | ||
|
|
f28c21eb77 | ||
|
|
3142460db5 | ||
|
|
b97b1b6093 | ||
|
|
303db85763 | ||
|
|
49ec7199e9 |
@@ -214,6 +214,7 @@ Xterm.js is used in several world-class applications to provide great terminal e
|
||||
- [**web3os**](https://web3os.sh): A decentralized operating system for the next web
|
||||
- [**Cratecode**](https://cratecode.com): Learn to program for free through interactive online lessons. Cratecode uses xterm.js to give users access to their own Linux environment.
|
||||
- [**Super Terminal**](https://github.com/bugwheels94/super-terminal): It is a http based terminal for developers who dont like repetition and save time.
|
||||
- [**graSSHopper**](https://grasshopper.coding.kiwi): A simple SSH client with file explorer, history and many more features.
|
||||
- [And much more...](https://github.com/xtermjs/xterm.js/network/dependents?package_id=UGFja2FnZS0xNjYzMjc4OQ%3D%3D)
|
||||
|
||||
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it on our list. Note: Please add any new contributions to the end of the list only.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm-addon-canvas",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { removeElementFromParent } from 'browser/Dom';
|
||||
import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache';
|
||||
import { TEXT_BASELINE } from 'browser/renderer/shared/Constants';
|
||||
import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs';
|
||||
@@ -69,10 +68,12 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer
|
||||
this.register(this._themeService.onChangeColors(e => {
|
||||
this._refreshCharAtlas(e);
|
||||
this.reset();
|
||||
// Trigger selection changed as it's handled separately to regular rendering
|
||||
this.handleSelectionChanged(this._selectionModel.selectionStart, this._selectionModel.selectionEnd, this._selectionModel.columnSelectMode);
|
||||
}));
|
||||
|
||||
this.register(toDisposable(() => {
|
||||
removeElementFromParent(this._canvas);
|
||||
this._canvas.remove();
|
||||
this._charAtlas?.dispose();
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
|
||||
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, ISelectionService, IThemeService } from 'browser/services/Services';
|
||||
import { IColorSet, ITerminal } from 'browser/Types';
|
||||
import { CanvasRenderer } from './CanvasRenderer';
|
||||
import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
|
||||
@@ -7,7 +7,7 @@ import { removeTerminalFromCache } from 'browser/renderer/shared/CharAtlasCache'
|
||||
import { observeDevicePixelDimensions } from 'browser/renderer/shared/DevicePixelObserver';
|
||||
import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';
|
||||
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
|
||||
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
||||
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, ISelectionService, IThemeService } from 'browser/services/Services';
|
||||
import { ILinkifier2 } from 'browser/Types';
|
||||
import { EventEmitter, forwardEvent } from 'common/EventEmitter';
|
||||
import { Disposable, toDisposable } from 'common/Lifecycle';
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import { IRenderDimensions } from 'browser/renderer/shared/Types';
|
||||
import { BaseRenderLayer } from './BaseRenderLayer';
|
||||
import { IColorSet, ReadonlyColorSet } from 'browser/Types';
|
||||
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
||||
import { Terminal } from 'xterm';
|
||||
@@ -48,6 +47,7 @@ export class SelectionRenderLayer extends BaseRenderLayer {
|
||||
// On resize use the base render layer's cached selection values since resize clears _state
|
||||
// inside reset.
|
||||
if (this._selectionModel.selectionStart && this._selectionModel.selectionEnd) {
|
||||
this._clearState();
|
||||
this._redrawSelection(this._selectionModel.selectionStart, this._selectionModel.selectionEnd, this._selectionModel.columnSelectMode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm-addon-fit",
|
||||
"version": "0.6.0",
|
||||
"version": "0.7.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm-addon-search",
|
||||
"version": "0.10.0",
|
||||
"version": "0.11.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xterm-addon-unicode11",
|
||||
"version": "0.4.0",
|
||||
"version": "0.5.0",
|
||||
"author": {
|
||||
"name": "The xterm.js authors",
|
||||
"url": "https://xtermjs.org/"
|
||||
|
||||
@@ -23,4 +23,5 @@
|
||||
"peerDependencies": {
|
||||
"xterm": "^5.0.0"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,4 +24,5 @@
|
||||
"peerDependencies": {
|
||||
"xterm": "^5.0.0"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -40,8 +40,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
|
||||
private _canvas: HTMLCanvasElement;
|
||||
private _gl: IWebGL2RenderingContext;
|
||||
private _rectangleRenderer: RectangleRenderer;
|
||||
private _glyphRenderer: GlyphRenderer;
|
||||
private _rectangleRenderer?: RectangleRenderer;
|
||||
private _glyphRenderer?: GlyphRenderer;
|
||||
|
||||
public readonly dimensions: IRenderDimensions;
|
||||
|
||||
@@ -181,10 +181,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
this._core.screenElement!.style.width = `${this.dimensions.css.canvas.width}px`;
|
||||
this._core.screenElement!.style.height = `${this.dimensions.css.canvas.height}px`;
|
||||
|
||||
this._rectangleRenderer.setDimensions(this.dimensions);
|
||||
this._rectangleRenderer.handleResize();
|
||||
this._glyphRenderer.setDimensions(this.dimensions);
|
||||
this._glyphRenderer.handleResize();
|
||||
this._rectangleRenderer?.setDimensions(this.dimensions);
|
||||
this._rectangleRenderer?.handleResize();
|
||||
this._glyphRenderer?.setDimensions(this.dimensions);
|
||||
this._glyphRenderer?.handleResize();
|
||||
|
||||
this._refreshCharAtlas();
|
||||
|
||||
@@ -279,7 +279,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
}
|
||||
this._charAtlas = atlas;
|
||||
this._charAtlas.warmUp();
|
||||
this._glyphRenderer.setAtlas(this._charAtlas);
|
||||
this._glyphRenderer?.setAtlas(this._charAtlas);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -290,7 +290,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
private _clearModel(clearGlyphRenderer: boolean): void {
|
||||
this._model.clear();
|
||||
if (clearGlyphRenderer) {
|
||||
this._glyphRenderer.clear();
|
||||
this._glyphRenderer?.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,6 +331,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
l.handleGridChanged(this._terminal, start, end);
|
||||
}
|
||||
|
||||
if (!this._glyphRenderer || !this._rectangleRenderer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Tell renderer the frame is beginning
|
||||
if (this._glyphRenderer.beginFrame()) {
|
||||
this._clearModel(true);
|
||||
@@ -340,8 +344,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
this._updateModel(start, end);
|
||||
|
||||
// Render
|
||||
this._rectangleRenderer.render();
|
||||
this._glyphRenderer.render(this._model);
|
||||
this._rectangleRenderer?.render();
|
||||
this._glyphRenderer?.render(this._model);
|
||||
}
|
||||
|
||||
private _updateModel(start: number, end: number): void {
|
||||
@@ -352,7 +356,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
let lastBg: number;
|
||||
let y: number;
|
||||
let row: number;
|
||||
let line: IBufferLine | undefined;
|
||||
let line: IBufferLine;
|
||||
let joinedRanges: [number, number][];
|
||||
let isJoined: boolean;
|
||||
let lastCharX: number;
|
||||
@@ -365,10 +369,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
|
||||
for (y = start; y <= end; y++) {
|
||||
row = y + terminal.buffer.ydisp;
|
||||
line = terminal.buffer.lines.get(row);
|
||||
if (!line) {
|
||||
break;
|
||||
}
|
||||
line = terminal.buffer.lines.get(row)!;
|
||||
this._model.lineLengths[y] = 0;
|
||||
joinedRanges = this._characterJoinerService.getJoinedCharacters(row);
|
||||
for (x = 0; x < terminal.cols; x++) {
|
||||
@@ -432,7 +433,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg;
|
||||
this._model.cells[i + RENDER_MODEL_EXT_OFFSET] = this._cellColorResolver.result.ext;
|
||||
|
||||
this._glyphRenderer.updateCell(x, y, code, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, chars, lastBg);
|
||||
this._glyphRenderer!.updateCell(x, y, code, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, chars, lastBg);
|
||||
|
||||
if (isJoined) {
|
||||
// Restore work cell
|
||||
@@ -441,7 +442,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
// Null out non-first cells
|
||||
for (x++; x < lastCharX; x++) {
|
||||
j = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
|
||||
this._glyphRenderer.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0);
|
||||
this._glyphRenderer!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0);
|
||||
this._model.cells[j] = NULL_CELL_CODE;
|
||||
this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._cellColorResolver.result.bg;
|
||||
this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg;
|
||||
@@ -450,7 +451,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
}
|
||||
}
|
||||
}
|
||||
this._rectangleRenderer.updateBackgrounds(this._model);
|
||||
this._rectangleRenderer!.updateBackgrounds(this._model);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+2
-2
@@ -82,7 +82,7 @@ jobs:
|
||||
|
||||
- job: Linux_IntegrationTests
|
||||
pool:
|
||||
vmImage: 'ubuntu-18.04'
|
||||
vmImage: 'ubuntu-20.04'
|
||||
steps:
|
||||
- script: |
|
||||
# source: https://github.com/microsoft/playwright/issues/1041
|
||||
@@ -151,7 +151,7 @@ jobs:
|
||||
- Windows_IntegrationTests
|
||||
condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['FORCE_RELEASE'], 'true')))
|
||||
pool:
|
||||
vmImage: 'ubuntu-18.04'
|
||||
vmImage: 'ubuntu-20.04'
|
||||
steps:
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
|
||||
@@ -146,10 +146,30 @@
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.xterm .xterm-accessibility-buffer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
padding: .5em;
|
||||
background: #000;
|
||||
color: #fff;
|
||||
opacity: 0;
|
||||
overflow: scroll;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.xterm .xterm-accessibility-buffer:focus {
|
||||
opacity: 1;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.xterm .live-region {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
|
||||
@@ -4,35 +4,24 @@
|
||||
*/
|
||||
|
||||
import * as Strings from 'browser/LocalizableStrings';
|
||||
import { ITerminal, IRenderDebouncer } from 'browser/Types';
|
||||
import { IBuffer } from 'common/buffer/Types';
|
||||
import { ITerminal, IRenderDebouncer, ReadonlyColorSet } from 'browser/Types';
|
||||
import { isMac } from 'common/Platform';
|
||||
import { TimeBasedDebouncer } from 'browser/TimeBasedDebouncer';
|
||||
import { addDisposableDomListener } from 'browser/Lifecycle';
|
||||
import { Disposable, toDisposable } from 'common/Lifecycle';
|
||||
import { ScreenDprMonitor } from 'browser/ScreenDprMonitor';
|
||||
import { IRenderService } from 'browser/services/Services';
|
||||
import { removeElementFromParent } from 'browser/Dom';
|
||||
import { IRenderService, IThemeService } from 'browser/services/Services';
|
||||
import { IOptionsService } from 'common/services/Services';
|
||||
import { ITerminalOptions } from 'xterm';
|
||||
|
||||
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 _accessibilityContainer: HTMLElement;
|
||||
private _accessiblityBuffer: HTMLElement;
|
||||
|
||||
private _liveRegion: HTMLElement;
|
||||
private _liveRegionLineCount: number = 0;
|
||||
|
||||
private _renderRowsDebouncer: IRenderDebouncer;
|
||||
private _screenDprMonitor: ScreenDprMonitor;
|
||||
|
||||
private _topBoundaryFocusListener: (e: FocusEvent) => void;
|
||||
private _bottomBoundaryFocusListener: (e: FocusEvent) => void;
|
||||
private _liveRegionDebouncer: IRenderDebouncer;
|
||||
|
||||
/**
|
||||
* This queue has a character pushed to it for keys that are pressed, if the
|
||||
@@ -47,47 +36,48 @@ export class AccessibilityManager extends Disposable {
|
||||
|
||||
private _charsToAnnounce: string = '';
|
||||
|
||||
private _isAccessibilityBufferActive: boolean = false;
|
||||
public get isAccessibilityBufferActive(): boolean { return this._isAccessibilityBufferActive; }
|
||||
|
||||
constructor(
|
||||
private readonly _terminal: ITerminal,
|
||||
private readonly _renderService: IRenderService
|
||||
@IOptionsService optionsService: IOptionsService,
|
||||
@IRenderService private readonly _renderService: IRenderService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super();
|
||||
this._accessibilityTreeRoot = document.createElement('div');
|
||||
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._handleBoundaryFocus(e, BoundaryPosition.TOP);
|
||||
this._bottomBoundaryFocusListener = e => this._handleBoundaryFocus(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._accessibilityContainer = document.createElement('div');
|
||||
this._accessibilityContainer.classList.add('xterm-accessibility');
|
||||
|
||||
this._liveRegion = document.createElement('div');
|
||||
this._liveRegion.classList.add('live-region');
|
||||
this._liveRegion.setAttribute('aria-live', 'assertive');
|
||||
this._accessibilityTreeRoot.appendChild(this._liveRegion);
|
||||
this._accessibilityContainer.appendChild(this._liveRegion);
|
||||
this._liveRegionDebouncer = this.register(new TimeBasedDebouncer(this._announceCharacters.bind(this)));
|
||||
|
||||
if (!this._terminal.element) {
|
||||
throw new Error('Cannot enable accessibility before Terminal.open');
|
||||
}
|
||||
this._terminal.element.insertAdjacentElement('afterbegin', this._accessibilityTreeRoot);
|
||||
this._terminal.element.insertAdjacentElement('afterbegin', this._accessibilityContainer);
|
||||
|
||||
this.register(this._renderRowsDebouncer);
|
||||
this.register(this._terminal.onResize(e => this._handleResize(e.rows)));
|
||||
this._accessiblityBuffer = document.createElement('div');
|
||||
this._accessiblityBuffer.setAttribute('role', 'document');
|
||||
this._accessiblityBuffer.ariaRoleDescription = Strings.accessibilityBuffer;
|
||||
this._accessiblityBuffer.tabIndex = 0;
|
||||
this._accessibilityContainer.appendChild(this._accessiblityBuffer);
|
||||
this._accessiblityBuffer.classList.add('xterm-accessibility-buffer');
|
||||
this.register(addDisposableDomListener(this._accessiblityBuffer, 'keydown', (ev: KeyboardEvent) => {
|
||||
if (ev.key === 'Tab') {
|
||||
this._isAccessibilityBufferActive = false;
|
||||
}}
|
||||
));
|
||||
this.register(addDisposableDomListener(this._accessiblityBuffer, 'focus',() => this._refreshAccessibilityBuffer()));
|
||||
this.register(addDisposableDomListener(this._accessiblityBuffer, 'focusout',() => {
|
||||
this._isAccessibilityBufferActive = false;
|
||||
}));
|
||||
|
||||
|
||||
this.register(this._liveRegionDebouncer);
|
||||
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
|
||||
@@ -96,108 +86,18 @@ export class AccessibilityManager extends Disposable {
|
||||
this.register(this._terminal.onA11yTab(spaceCount => this._handleTab(spaceCount)));
|
||||
this.register(this._terminal.onKey(e => this._handleKey(e.key)));
|
||||
this.register(this._terminal.onBlur(() => this._clearLiveRegion()));
|
||||
this.register(this._renderService.onDimensionsChange(() => this._refreshRowsDimensions()));
|
||||
|
||||
this._screenDprMonitor = new ScreenDprMonitor(window);
|
||||
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()));
|
||||
this._handleColorChange(themeService.colors);
|
||||
this.register(themeService.onChangeColors(e => this._handleColorChange(e)));
|
||||
this._handleFontOptionChange(optionsService.options);
|
||||
this.register(optionsService.onMultipleOptionChange(['fontSize', 'fontFamily', 'letterSpacing', 'lineHeight'], () => this._handleFontOptionChange(optionsService.options)));
|
||||
|
||||
this.register(toDisposable(() => {
|
||||
removeElementFromParent(this._accessibilityTreeRoot);
|
||||
this._rowElements.length = 0;
|
||||
this._accessiblityBuffer.remove();
|
||||
this._accessibilityContainer.remove();
|
||||
}));
|
||||
}
|
||||
|
||||
private _handleBoundaryFocus(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 _handleResize(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 _handleTab(spaceCount: number): void {
|
||||
for (let i = 0; i < spaceCount; i++) {
|
||||
this._handleChar(' ');
|
||||
@@ -227,7 +127,7 @@ export class AccessibilityManager extends Disposable {
|
||||
if (isMac) {
|
||||
if (this._liveRegion.textContent && this._liveRegion.textContent.length > 0 && !this._liveRegion.parentNode) {
|
||||
setTimeout(() => {
|
||||
this._accessibilityTreeRoot.appendChild(this._liveRegion);
|
||||
this._accessibilityContainer.appendChild(this._liveRegion);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
@@ -240,7 +140,7 @@ export class AccessibilityManager extends Disposable {
|
||||
|
||||
// Only detach/attach on mac as otherwise messages can go unaccounced
|
||||
if (isMac) {
|
||||
removeElementFromParent(this._liveRegion);
|
||||
this._liveRegion.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,40 +153,7 @@ export class AccessibilityManager extends Disposable {
|
||||
}
|
||||
|
||||
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.css.cell.height) {
|
||||
return;
|
||||
}
|
||||
this._accessibilityTreeRoot.style.width = `${this._renderService.dimensions.css.canvas.width}px`;
|
||||
if (this._rowElements.length !== this._terminal.rows) {
|
||||
this._handleResize(this._terminal.rows);
|
||||
}
|
||||
for (let i = 0; i < this._terminal.rows; i++) {
|
||||
this._refreshRowDimensions(this._rowElements[i]);
|
||||
}
|
||||
this._liveRegionDebouncer.refresh(start, end, this._terminal.rows);
|
||||
}
|
||||
|
||||
private _refreshRowDimensions(element: HTMLElement): void {
|
||||
@@ -300,4 +167,32 @@ export class AccessibilityManager extends Disposable {
|
||||
this._liveRegion.textContent += this._charsToAnnounce;
|
||||
this._charsToAnnounce = '';
|
||||
}
|
||||
|
||||
|
||||
private _refreshAccessibilityBuffer(): void {
|
||||
if (!this._terminal.viewport) {
|
||||
return;
|
||||
}
|
||||
this._isAccessibilityBufferActive = true;
|
||||
const { bufferElements } = this._terminal.viewport.getBufferElements(0);
|
||||
for (const element of bufferElements) {
|
||||
if (element.textContent) {
|
||||
element.textContent = element.textContent.replace(new RegExp(' ', 'g'), '\xA0');
|
||||
}
|
||||
}
|
||||
this._accessiblityBuffer.replaceChildren(...bufferElements);
|
||||
this._accessiblityBuffer.scrollTop = this._accessiblityBuffer.scrollHeight;
|
||||
}
|
||||
|
||||
private _handleColorChange(colorSet: ReadonlyColorSet): void {
|
||||
this._accessiblityBuffer.style.backgroundColor = colorSet.background.css;
|
||||
this._accessiblityBuffer.style.color = colorSet.foreground.css;
|
||||
}
|
||||
|
||||
private _handleFontOptionChange(options: Required<ITerminalOptions>): void {
|
||||
this._accessiblityBuffer.style.fontFamily = options.fontFamily;
|
||||
this._accessiblityBuffer.style.fontSize = `${options.fontSize}px`;
|
||||
this._accessiblityBuffer.style.lineHeight = `${options.lineHeight * this._renderService.dimensions.css.cell.height}px`;
|
||||
this._accessiblityBuffer.style.letterSpacing = `${options.letterSpacing}px`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2020 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import jsdom = require('jsdom');
|
||||
import { removeElementFromParent } from 'browser/Dom';
|
||||
import { strictEqual, doesNotThrow } from 'assert';
|
||||
|
||||
describe('Dom', () => {
|
||||
const dom = new jsdom.JSDOM();
|
||||
const document = dom.window.document;
|
||||
|
||||
describe('removeElementFromParent', () => {
|
||||
it('should remove single child', () => {
|
||||
const e = document.createElement('div');
|
||||
document.body.appendChild(e);
|
||||
strictEqual(e.parentElement, document.body);
|
||||
removeElementFromParent(e);
|
||||
strictEqual(e.parentElement, null);
|
||||
});
|
||||
it('should remove multiple elements', () => {
|
||||
const e1 = document.createElement('div');
|
||||
const e2 = document.createElement('div');
|
||||
document.body.appendChild(e1);
|
||||
document.body.appendChild(e2);
|
||||
strictEqual(e1.parentElement, document.body);
|
||||
strictEqual(e2.parentElement, document.body);
|
||||
removeElementFromParent(e1, e2);
|
||||
strictEqual(e1.parentElement, null);
|
||||
strictEqual(e2.parentElement, null);
|
||||
});
|
||||
it('should not throw on undefined', () => {
|
||||
const e = document.createElement('div');
|
||||
document.body.appendChild(e);
|
||||
strictEqual(e.parentElement, document.body);
|
||||
doesNotThrow(() => removeElementFromParent(undefined, e));
|
||||
strictEqual(e.parentElement, null);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,10 +0,0 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
|
||||
private _linkCacheDisposables: IDisposable[] = [];
|
||||
private _lastBufferCell: IBufferCellPosition | undefined;
|
||||
private _isMouseOut: boolean = true;
|
||||
private _wasResized: boolean = false;
|
||||
private _activeProviderReplies: Map<Number, ILinkWithState[] | undefined> | undefined;
|
||||
private _activeLine: number = -1;
|
||||
|
||||
@@ -39,6 +40,11 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
|
||||
this.register(toDisposable(() => {
|
||||
this._lastMouseEvent = undefined;
|
||||
}));
|
||||
// Listen to resize to catch the case where it's resized and the cursor is out of the viewport.
|
||||
this.register(this._bufferService.onResize(() => {
|
||||
this._clearCurrentLink();
|
||||
this._wasResized = true;
|
||||
}));
|
||||
}
|
||||
|
||||
public registerLinkProvider(linkProvider: ILinkProvider): IDisposable {
|
||||
@@ -105,9 +111,10 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
|
||||
private _handleHover(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) {
|
||||
if (this._activeLine !== position.y || this._wasResized) {
|
||||
this._clearCurrentLink();
|
||||
this._askForLink(position, false);
|
||||
this._wasResized = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -309,19 +316,27 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
|
||||
}
|
||||
});
|
||||
|
||||
// Add listener for rerendering
|
||||
// Listen to viewport changes to re-render the link under the cursor (only when the line the
|
||||
// link is on changes)
|
||||
if (this._renderService) {
|
||||
this._linkCacheDisposables.push(this._renderService.onRenderedViewportChange(e => {
|
||||
// Sanity check, this shouldn't happen in practice as this listener would be disposed
|
||||
if (!this._currentLink) {
|
||||
return;
|
||||
}
|
||||
// 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;
|
||||
const oldEvent = this._currentLink ? this._lastMouseEvent : undefined;
|
||||
this._clearCurrentLink(start, e.end + 1 + this._bufferService.buffer.ydisp);
|
||||
if (oldEvent && this._element) {
|
||||
// re-eval previously active link after changes
|
||||
const position = this._positionFromMouseEvent(oldEvent, this._element, this._mouseService!);
|
||||
if (position) {
|
||||
this._askForLink(position, false);
|
||||
const end = this._bufferService.buffer.ydisp + 1 + e.end;
|
||||
// Only clear the link if the viewport change happened on this line
|
||||
if (this._currentLink.link.range.start.y >= start && this._currentLink.link.range.end.y <= end) {
|
||||
this._clearCurrentLink(start, end);
|
||||
if (this._lastMouseEvent && this._element) {
|
||||
// re-eval previously active link after changes
|
||||
const position = this._positionFromMouseEvent(this._lastMouseEvent, this._element, this._mouseService!);
|
||||
if (position) {
|
||||
this._askForLink(position, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -10,3 +10,6 @@ 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';
|
||||
|
||||
// eslint-disable-next-line prefer-const
|
||||
export let accessibilityBuffer = 'Accessibility buffer';
|
||||
|
||||
@@ -1041,241 +1041,6 @@ describe('Terminal', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Buffer.stringIndexToBufferIndex', () => {
|
||||
let terminal: TestTerminal;
|
||||
|
||||
beforeEach(() => {
|
||||
terminal = new TestTerminal({ rows: 5, cols: 10, scrollback: 5 });
|
||||
});
|
||||
|
||||
it('multiline ascii', async () => {
|
||||
const input = 'This is ASCII text spanning multiple lines.';
|
||||
await terminal.writeP(input);
|
||||
const s = terminal.buffer.iterator(true).next().content;
|
||||
assert.equal(input, s);
|
||||
for (let i = 0; i < input.length; ++i) {
|
||||
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
|
||||
assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex);
|
||||
}
|
||||
});
|
||||
|
||||
it('combining e\u0301 in a sentence', async () => {
|
||||
const input = 'Sitting in the cafe\u0301 drinking coffee.';
|
||||
await terminal.writeP(input);
|
||||
const s = terminal.buffer.iterator(true).next().content;
|
||||
assert.equal(input, s);
|
||||
for (let i = 0; i < 19; ++i) {
|
||||
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
|
||||
assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex);
|
||||
}
|
||||
// string index 18 & 19 point to combining char e\u0301 ---> same buffer Index
|
||||
assert.deepEqual(
|
||||
terminal.buffer.stringIndexToBufferIndex(0, 18),
|
||||
terminal.buffer.stringIndexToBufferIndex(0, 19));
|
||||
// after the combining char every string index has an offset of -1
|
||||
for (let i = 19; i < input.length; ++i) {
|
||||
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
|
||||
assert.deepEqual([((i - 1) / terminal.cols) | 0, (i - 1) % terminal.cols], bufferIndex);
|
||||
}
|
||||
});
|
||||
|
||||
it('multiline combining e\u0301', async () => {
|
||||
const input = 'e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301e\u0301';
|
||||
await terminal.writeP(input);
|
||||
const s = terminal.buffer.iterator(true).next().content;
|
||||
assert.equal(input, s);
|
||||
// every buffer cell index contains 2 string indices
|
||||
for (let i = 0; i < input.length; ++i) {
|
||||
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
|
||||
assert.deepEqual([((i >> 1) / terminal.cols) | 0, (i >> 1) % terminal.cols], bufferIndex);
|
||||
}
|
||||
});
|
||||
|
||||
it('surrogate char in a sentence', async () => {
|
||||
const input = 'The 𝄞 is a clef widely used in modern notation.';
|
||||
await terminal.writeP(input);
|
||||
const s = terminal.buffer.iterator(true).next().content;
|
||||
assert.equal(input, s);
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
|
||||
assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex);
|
||||
}
|
||||
// string index 4 & 5 point to surrogate char 𝄞 ---> same buffer Index
|
||||
assert.deepEqual(
|
||||
terminal.buffer.stringIndexToBufferIndex(0, 4),
|
||||
terminal.buffer.stringIndexToBufferIndex(0, 5));
|
||||
// after the combining char every string index has an offset of -1
|
||||
for (let i = 5; i < input.length; ++i) {
|
||||
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
|
||||
assert.deepEqual([((i - 1) / terminal.cols) | 0, (i - 1) % terminal.cols], bufferIndex);
|
||||
}
|
||||
});
|
||||
|
||||
it('multiline surrogate char', async () => {
|
||||
const input = '𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞𝄞';
|
||||
await terminal.writeP(input);
|
||||
const s = terminal.buffer.iterator(true).next().content;
|
||||
assert.equal(input, s);
|
||||
// every buffer cell index contains 2 string indices
|
||||
for (let i = 0; i < input.length; ++i) {
|
||||
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
|
||||
assert.deepEqual([((i >> 1) / terminal.cols) | 0, (i >> 1) % terminal.cols], bufferIndex);
|
||||
}
|
||||
});
|
||||
|
||||
it('surrogate char with combining', async () => {
|
||||
// eye of Ra with acute accent - string length of 3
|
||||
const input = '𓂀\u0301 - the eye hiroglyph with an acute accent.';
|
||||
await terminal.writeP(input);
|
||||
const s = terminal.buffer.iterator(true).next().content;
|
||||
assert.equal(input, s);
|
||||
// index 0..2 should map to 0
|
||||
assert.deepEqual([0, 0], terminal.buffer.stringIndexToBufferIndex(0, 1));
|
||||
assert.deepEqual([0, 0], terminal.buffer.stringIndexToBufferIndex(0, 2));
|
||||
for (let i = 2; i < input.length; ++i) {
|
||||
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
|
||||
assert.deepEqual([((i - 2) / terminal.cols) | 0, (i - 2) % terminal.cols], bufferIndex);
|
||||
}
|
||||
});
|
||||
|
||||
it('multiline surrogate with combining', async () => {
|
||||
const input = '𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301𓂀\u0301';
|
||||
await terminal.writeP(input);
|
||||
const s = terminal.buffer.iterator(true).next().content;
|
||||
assert.equal(input, s);
|
||||
// every buffer cell index contains 3 string indices
|
||||
for (let i = 0; i < input.length; ++i) {
|
||||
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
|
||||
assert.deepEqual([(((i / 3) | 0) / terminal.cols) | 0, ((i / 3) | 0) % terminal.cols], bufferIndex);
|
||||
}
|
||||
});
|
||||
|
||||
it('fullwidth chars', async () => {
|
||||
const input = 'These 123 are some fat numbers.';
|
||||
await terminal.writeP(input);
|
||||
const s = terminal.buffer.iterator(true).next().content;
|
||||
assert.equal(input, s);
|
||||
for (let i = 0; i < 6; ++i) {
|
||||
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
|
||||
assert.deepEqual([(i / terminal.cols) | 0, i % terminal.cols], bufferIndex);
|
||||
}
|
||||
// string index 6, 7, 8 take 2 cells
|
||||
assert.deepEqual([0, 8], terminal.buffer.stringIndexToBufferIndex(0, 7));
|
||||
assert.deepEqual([1, 0], terminal.buffer.stringIndexToBufferIndex(0, 8));
|
||||
// rest of the string has offset of +3
|
||||
for (let i = 9; i < input.length; ++i) {
|
||||
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
|
||||
assert.deepEqual([((i + 3) / terminal.cols) | 0, (i + 3) % terminal.cols], bufferIndex);
|
||||
}
|
||||
});
|
||||
|
||||
it('multiline fullwidth chars', async () => {
|
||||
const input = '12345678901234567890';
|
||||
await terminal.writeP(input);
|
||||
const s = terminal.buffer.iterator(true).next().content;
|
||||
assert.equal(input, s);
|
||||
for (let i = 9; i < input.length; ++i) {
|
||||
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i);
|
||||
assert.deepEqual([((i << 1) / terminal.cols) | 0, (i << 1) % terminal.cols], bufferIndex);
|
||||
}
|
||||
});
|
||||
|
||||
it('fullwidth combining with emoji - match emoji cell', async () => {
|
||||
const input = 'Lots of ¥\u0301 make me 😃.';
|
||||
await terminal.writeP(input);
|
||||
const s = terminal.buffer.iterator(true).next().content;
|
||||
assert.equal(input, s);
|
||||
const stringIndex = s.match(/😃/)!.index!;
|
||||
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, stringIndex);
|
||||
assert(terminal.buffer.lines.get(bufferIndex[0])!.loadCell(bufferIndex[1], new CellData()).getChars(), '😃');
|
||||
});
|
||||
|
||||
it('multiline fullwidth chars with offset 1 (currently tests for broken behavior)', async () => {
|
||||
const input = 'a12345678901234567890';
|
||||
// the 'a' at the beginning moves all fullwidth chars one to the right
|
||||
// now the end of the line contains a dangling empty cell since
|
||||
// the next fullwidth char has to wrap early
|
||||
// the dangling last cell is wrongly added in the string
|
||||
// --> fixable after resolving #1685
|
||||
await terminal.writeP(input);
|
||||
const s = terminal.buffer.iterator(true).next().content;
|
||||
assert.equal(input, s);
|
||||
for (let i = 10; i < input.length; ++i) {
|
||||
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i, true);
|
||||
const j = (i - 0) << 1;
|
||||
assert.deepEqual([(j / terminal.cols) | 0, j % terminal.cols], bufferIndex);
|
||||
}
|
||||
});
|
||||
|
||||
it('test fully wrapped buffer up to last char', async () => {
|
||||
const input = Array(6).join('1234567890');
|
||||
await terminal.writeP(input);
|
||||
const s = terminal.buffer.iterator(true).next().content;
|
||||
assert.equal(input, s);
|
||||
for (let i = 0; i < input.length; ++i) {
|
||||
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i, true);
|
||||
assert.equal(input[i], terminal.buffer.lines.get(bufferIndex[0])!.loadCell(bufferIndex[1], new CellData()).getChars());
|
||||
}
|
||||
});
|
||||
|
||||
it('test fully wrapped buffer up to last char with full width odd', async () => {
|
||||
const input = 'a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301'
|
||||
+ 'a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301a¥\u0301';
|
||||
await terminal.writeP(input);
|
||||
const s = terminal.buffer.iterator(true).next().content;
|
||||
assert.equal(input, s);
|
||||
for (let i = 0; i < input.length; ++i) {
|
||||
const bufferIndex = terminal.buffer.stringIndexToBufferIndex(0, i, true);
|
||||
assert.equal(
|
||||
(!(i % 3))
|
||||
? input[i]
|
||||
: (i % 3 === 1)
|
||||
? input.slice(i, i + 2)
|
||||
: input.slice(i - 1, i + 1),
|
||||
terminal.buffer.lines.get(bufferIndex[0])!.loadCell(bufferIndex[1], new CellData()).getChars());
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle \t in lines correctly', async () => {
|
||||
const input = '\thttps://google.de';
|
||||
await terminal.writeP(input);
|
||||
const s = terminal.buffer.iterator(true).next().content;
|
||||
assert.equal(s, Array(terminal.optionsService.options.tabStopWidth + 1).join(' ') + 'https://google.de');
|
||||
});
|
||||
});
|
||||
|
||||
describe('BufferStringIterator', function (): void {
|
||||
it('iterator does not overflow buffer limits', async () => {
|
||||
const terminal = new TestTerminal({ rows: 5, cols: 10, scrollback: 5 });
|
||||
const data = [
|
||||
'aaaaaaaaaa',
|
||||
'aaaaaaaaa\n',
|
||||
'aaaaaaaaaa',
|
||||
'aaaaaaaaa\n',
|
||||
'aaaaaaaaaa',
|
||||
'aaaaaaaaaa',
|
||||
'aaaaaaaaaa',
|
||||
'aaaaaaaaa\n',
|
||||
'aaaaaaaaaa',
|
||||
'aaaaaaaaaa'
|
||||
];
|
||||
await terminal.writeP(data.join(''));
|
||||
// brute force test with insane values
|
||||
assert.doesNotThrow(() => {
|
||||
for (let overscan = 0; overscan < 20; ++overscan) {
|
||||
for (let start = -10; start < 20; ++start) {
|
||||
for (let end = -10; end < 20; ++end) {
|
||||
const it = terminal.buffer.iterator(false, start, end, overscan, overscan);
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Windows Mode', () => {
|
||||
it('should mark lines as wrapped when the line ends in a non-null character after a LF', async () => {
|
||||
const data = [
|
||||
|
||||
@@ -267,7 +267,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
private _handleScreenReaderModeOptionChange(value: boolean): void {
|
||||
if (value) {
|
||||
if (!this._accessibilityManager && this._renderService) {
|
||||
this._accessibilityManager = new AccessibilityManager(this, this._renderService);
|
||||
this._accessibilityManager = this._instantiationService.createInstance(AccessibilityManager, this);
|
||||
}
|
||||
} else {
|
||||
this._accessibilityManager?.dispose();
|
||||
@@ -419,7 +419,6 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
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
|
||||
@@ -553,7 +552,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
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);
|
||||
this._accessibilityManager = this._instantiationService.createInstance(AccessibilityManager, this);
|
||||
}
|
||||
this.register(this.optionsService.onSpecificOptionChange('screenReaderMode', e => this._handleScreenReaderModeOptionChange(e)));
|
||||
|
||||
@@ -768,6 +767,9 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
*/
|
||||
this.register(addDisposableDomListener(el, 'mousedown', (ev: MouseEvent) => {
|
||||
ev.preventDefault();
|
||||
if (this._accessibilityManager?.isAccessibilityBufferActive) {
|
||||
return;
|
||||
}
|
||||
this.focus();
|
||||
|
||||
// Don't send the mouse button to the pty if mouse events are disabled or
|
||||
|
||||
@@ -8,7 +8,7 @@ import { IEvent, EventEmitter } from 'common/EventEmitter';
|
||||
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IMouseService, IRenderService, ISelectionService, IThemeService } from 'browser/services/Services';
|
||||
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
|
||||
import { IColorSet, ITerminal, ILinkifier2, IBrowser, IViewport, ICompositionHelper, CharacterJoinerHandler, IBufferRange, ReadonlyColorSet } from 'browser/Types';
|
||||
import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types';
|
||||
import { IBuffer, IBufferSet } from 'common/buffer/Types';
|
||||
import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, ITerminalOptions, ColorIndex } from 'common/Types';
|
||||
import { Buffer } from 'common/buffer/Buffer';
|
||||
import * as Browser from 'common/Platform';
|
||||
@@ -140,6 +140,9 @@ export class MockTerminal implements ITerminal {
|
||||
public write(data: string): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public getBufferElements(startLine: number, endLine?: number | undefined): { bufferElements: HTMLElement[], cursorElement?: HTMLElement | undefined } {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public bracketedPasteMode!: boolean;
|
||||
public renderer!: IRenderer;
|
||||
public linkifier2!: ILinkifier2;
|
||||
@@ -240,12 +243,6 @@ export class MockBuffer implements IBuffer {
|
||||
public getBlankLine(attr: IAttributeData, isWrapped?: boolean): IBufferLine {
|
||||
return Buffer.prototype.getBlankLine.apply(this, arguments as any);
|
||||
}
|
||||
public stringIndexToBufferIndex(lineIndex: number, stringIndex: number): number[] {
|
||||
return Buffer.prototype.stringIndexToBufferIndex.apply(this, arguments as any);
|
||||
}
|
||||
public iterator(trimRight: boolean, startIndex?: number, endIndex?: number): IBufferStringIterator {
|
||||
return Buffer.prototype.iterator.apply(this, arguments as any);
|
||||
}
|
||||
public getNullCell(attr?: IAttributeData): ICellData {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
@@ -316,6 +313,9 @@ export class MockViewport implements IViewport {
|
||||
public getLinesScrolled(ev: WheelEvent): number {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public getBufferElements(startLine: number, endLine?: number | undefined): { bufferElements: HTMLElement[], cursorElement?: HTMLElement | undefined } {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
export class MockCompositionHelper implements ICompositionHelper {
|
||||
|
||||
Vendored
+1
@@ -144,6 +144,7 @@ export interface IViewport extends IDisposable {
|
||||
scrollBarWidth: number;
|
||||
syncScrollArea(immediate?: boolean): void;
|
||||
getLinesScrolled(ev: WheelEvent): number;
|
||||
getBufferElements(startLine: number, endLine?: number): { bufferElements: HTMLElement[], cursorElement?: HTMLElement };
|
||||
handleWheel(ev: WheelEvent): boolean;
|
||||
handleTouchStart(ev: TouchEvent): void;
|
||||
handleTouchMove(ev: TouchEvent): boolean;
|
||||
|
||||
@@ -279,6 +279,33 @@ export class Viewport extends Disposable implements IViewport {
|
||||
return amount;
|
||||
}
|
||||
|
||||
|
||||
public getBufferElements(startLine: number, endLine?: number): { bufferElements: HTMLElement[], cursorElement?: HTMLElement } {
|
||||
let currentLine: string = '';
|
||||
let cursorElement: HTMLElement | undefined;
|
||||
const bufferElements: HTMLElement[] = [];
|
||||
const end = endLine ?? this._bufferService.buffer.lines.length;
|
||||
const lines = this._bufferService.buffer.lines;
|
||||
for (let i = startLine; i < end; i++) {
|
||||
const line = lines.get(i);
|
||||
if (!line) {
|
||||
continue;
|
||||
}
|
||||
const isWrapped = lines.get(i + 1)?.isWrapped;
|
||||
currentLine += line.translateToString(!isWrapped);
|
||||
if (!isWrapped || i === lines.length - 1) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = currentLine;
|
||||
bufferElements.push(div);
|
||||
if (currentLine.length > 0) {
|
||||
cursorElement = div;
|
||||
}
|
||||
currentLine = '';
|
||||
}
|
||||
}
|
||||
return { bufferElements, cursorElement };
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of pixels scrolled by the mouse event taking into account what type of delta
|
||||
* is being used.
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { removeElementFromParent } from 'browser/Dom';
|
||||
import { BOLD_CLASS, CURSOR_BLINK_CLASS, CURSOR_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory, ITALIC_CLASS } from 'browser/renderer/dom/DomRendererRowFactory';
|
||||
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants';
|
||||
import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';
|
||||
@@ -87,7 +86,10 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
|
||||
// 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);
|
||||
this._rowContainer.remove();
|
||||
this._selectionContainer.remove();
|
||||
this._themeStyleElement.remove();
|
||||
this._dimensionsStyleElement.remove();
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -587,7 +587,7 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
);
|
||||
break;
|
||||
case UnderlineStyle.DOTTED:
|
||||
this._tmpCtx.setLineDash([this._config.devicePixelRatio * 2, this._config.devicePixelRatio]);
|
||||
this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]);
|
||||
this._tmpCtx.moveTo(xChLeft, yTop);
|
||||
this._tmpCtx.lineTo(xChRight, yTop);
|
||||
break;
|
||||
@@ -627,7 +627,7 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
// outline around the whole glyph, as well as additional pixels in the glyph at the top
|
||||
// which would increase GPU memory demands
|
||||
const clipRegion = new Path2D();
|
||||
clipRegion.rect(xLeft, yTop - Math.ceil(lineWidth / 2), this._config.deviceCellWidth, yBot - yTop + Math.ceil(lineWidth / 2));
|
||||
clipRegion.rect(xLeft, yTop - Math.ceil(lineWidth / 2), this._config.deviceCellWidth * chWidth, yBot - yTop + Math.ceil(lineWidth / 2));
|
||||
this._tmpCtx.clip(clipRegion);
|
||||
this._tmpCtx.lineWidth = this._config.devicePixelRatio * 3;
|
||||
this._tmpCtx.strokeStyle = backgroundColor.css;
|
||||
|
||||
@@ -152,6 +152,14 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
if (!this._renderer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Since this is debounced, a resize event could have happened between the time a refresh was
|
||||
// requested and when this triggers. Clamp the values of start and end to ensure they're valid
|
||||
// given the current viewport state.
|
||||
start = Math.min(start, this._rowCount - 1);
|
||||
end = Math.min(end, this._rowCount - 1);
|
||||
|
||||
// Render
|
||||
this._renderer.renderRows(start, end);
|
||||
|
||||
// Update selection if needed
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { ICircularList } from 'common/Types';
|
||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
import { EventEmitter } from 'common/EventEmitter';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
|
||||
export interface IInsertEvent {
|
||||
|
||||
+2
-100
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { CircularList, IInsertEvent } from 'common/CircularList';
|
||||
import { IBuffer, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult } from 'common/buffer/Types';
|
||||
import { IBuffer } 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';
|
||||
@@ -14,7 +14,7 @@ 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';
|
||||
import { DebouncedIdleTask, IdleTaskQueue } from 'common/TaskQueue';
|
||||
import { IdleTaskQueue } from 'common/TaskQueue';
|
||||
|
||||
export const MAX_BUFFER_SIZE = 4294967295; // 2^32 - 1
|
||||
|
||||
@@ -510,43 +510,6 @@ export class Buffer implements IBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
// 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 trimRight Whether to trim whitespace to the right.
|
||||
*/
|
||||
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
|
||||
@@ -684,65 +647,4 @@ export class Buffer implements IBuffer {
|
||||
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 };
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
-12
@@ -9,16 +9,6 @@ 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;
|
||||
@@ -40,8 +30,6 @@ export interface IBuffer {
|
||||
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;
|
||||
|
||||
@@ -132,17 +132,9 @@ 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;
|
||||
type GetLeadingNonServiceArgs<TArgs extends any[]> = TArgs extends [] ? []
|
||||
: TArgs extends [...infer TFirst, infer TLast] ? TLast extends IBrandedService ? GetLeadingNonServiceArgs<TFirst> : TArgs
|
||||
: never;
|
||||
|
||||
export const IInstantiationService = createDecorator<IInstantiationService>('InstantiationService');
|
||||
export interface IInstantiationService {
|
||||
|
||||
Vendored
+6
-1
@@ -498,6 +498,11 @@ declare module 'xterm' {
|
||||
* being printed to the terminal when `screenReaderMode` is enabled.
|
||||
*/
|
||||
tooMuchOutput: string;
|
||||
|
||||
/**
|
||||
* The aria label for the accessibility buffer
|
||||
*/
|
||||
accessibilityBuffer: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1179,7 +1184,7 @@ declare module 'xterm' {
|
||||
* @param range The buffer range of the link.
|
||||
*/
|
||||
leave?(event: MouseEvent, text: string, range: IBufferRange): void;
|
||||
|
||||
|
||||
/**
|
||||
* Whether to receive non-HTTP URLs from LinkProvider. When false, any usage of non-HTTP URLs
|
||||
* will be ignored. Enabling this option without proper protection in `activate` function
|
||||
|
||||
@@ -2650,11 +2650,9 @@ json-stable-stringify-without-jsonify@^1.0.1:
|
||||
integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
|
||||
|
||||
json5@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.2.tgz#43ef1f0af9835dd624751a6b7fa48874fb2d608e"
|
||||
integrity sha512-MoUOQ4WdiN3yxhm7NEVJSJrieAo5hNSLQ5sj05OTRHPL9HOBy8u4Bu88jsC1jvqAdN+E1bJmsUcZH+1HQxliqQ==
|
||||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
|
||||
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
|
||||
|
||||
kind-of@^6.0.2:
|
||||
version "6.0.3"
|
||||
@@ -2853,11 +2851,6 @@ minimatch@^3.0.4:
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimist@^1.2.5:
|
||||
version "1.2.6"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
||||
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
||||
|
||||
mocha@^10.1.0:
|
||||
version "10.1.0"
|
||||
resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.1.0.tgz#dbf1114b7c3f9d0ca5de3133906aea3dfc89ef7a"
|
||||
|
||||
Reference in New Issue
Block a user