Merge remote-tracking branch 'upstream/master' into pr/tisilent/4837

This commit is contained in:
Daniel Imms
2023-11-02 09:30:17 -07:00
278 changed files with 848 additions and 565 deletions
+9 -16
View File
@@ -7,10 +7,9 @@ import * as Strings from 'browser/LocalizableStrings';
import { ITerminal, IRenderDebouncer } from 'browser/Types';
import { TimeBasedDebouncer } from 'browser/TimeBasedDebouncer';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { ScreenDprMonitor } from 'browser/ScreenDprMonitor';
import { IRenderService } from 'browser/services/Services';
import { addDisposableDomListener } from 'browser/Lifecycle';
import { ICoreBrowserService, IRenderService } from 'browser/services/Services';
import { IBuffer } from 'common/buffer/Types';
import { IInstantiationService } from 'common/services/Services';
const MAX_ROWS_TO_READ = 20;
@@ -29,8 +28,6 @@ export class AccessibilityManager extends Disposable {
private _liveRegionLineCount: number = 0;
private _liveRegionDebouncer: IRenderDebouncer;
private _screenDprMonitor: ScreenDprMonitor;
private _topBoundaryFocusListener: (e: FocusEvent) => void;
private _bottomBoundaryFocusListener: (e: FocusEvent) => void;
@@ -49,13 +46,15 @@ export class AccessibilityManager extends Disposable {
constructor(
private readonly _terminal: ITerminal,
@IInstantiationService instantiationService: IInstantiationService,
@ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService,
@IRenderService private readonly _renderService: IRenderService
) {
super();
this._accessibilityContainer = document.createElement('div');
this._accessibilityContainer = this._coreBrowserService.mainDocument.createElement('div');
this._accessibilityContainer.classList.add('xterm-accessibility');
this._rowContainer = document.createElement('div');
this._rowContainer = this._coreBrowserService.mainDocument.createElement('div');
this._rowContainer.setAttribute('role', 'list');
this._rowContainer.classList.add('xterm-accessibility-tree');
this._rowElements = [];
@@ -72,7 +71,7 @@ export class AccessibilityManager extends Disposable {
this._refreshRowsDimensions();
this._accessibilityContainer.appendChild(this._rowContainer);
this._liveRegion = document.createElement('div');
this._liveRegion = this._coreBrowserService.mainDocument.createElement('div');
this._liveRegion.classList.add('live-region');
this._liveRegion.setAttribute('aria-live', 'assertive');
this._accessibilityContainer.appendChild(this._liveRegion);
@@ -93,13 +92,7 @@ export class AccessibilityManager extends Disposable {
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.register(this._coreBrowserService.onDprChange(() => this._refreshRowsDimensions()));
this._refreshRows();
this.register(toDisposable(() => {
@@ -261,7 +254,7 @@ export class AccessibilityManager extends Disposable {
}
private _createAccessibilityTreeNode(): HTMLElement {
const element = document.createElement('div');
const element = this._coreBrowserService.mainDocument.createElement('div');
element.setAttribute('role', 'listitem');
element.tabIndex = -1;
this._refreshRowDimensions(element);
-72
View File
@@ -1,72 +0,0 @@
/**
* Copyright (c) 2017 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { Disposable, toDisposable } 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;
private _outerListener: ((this: MediaQueryList, ev: MediaQueryListEvent) => any) | undefined;
private _listener: ScreenDprListener | undefined;
private _resolutionMediaMatchList: MediaQueryList | undefined;
constructor(private _parentWindow: Window) {
super();
this._currentDevicePixelRatio = this._parentWindow.devicePixelRatio;
this.register(toDisposable(() => {
this.clearListener();
}));
}
public setListener(listener: ScreenDprListener): void {
if (this._listener) {
this.clearListener();
}
this._listener = listener;
this._outerListener = () => {
if (!this._listener) {
return;
}
this._listener(this._parentWindow.devicePixelRatio, this._currentDevicePixelRatio);
this._updateDpr();
};
this._updateDpr();
}
private _updateDpr(): void {
if (!this._outerListener) {
return;
}
// Clear listeners for old DPR
this._resolutionMediaMatchList?.removeListener(this._outerListener);
// Add listeners for new DPR
this._currentDevicePixelRatio = this._parentWindow.devicePixelRatio;
this._resolutionMediaMatchList = this._parentWindow.matchMedia(`screen and (resolution: ${this._parentWindow.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;
}
}
+27 -13
View File
@@ -54,13 +54,10 @@ import { evaluateKeyboardEvent } from 'common/input/Keyboard';
import { toRgbString } from 'common/input/XParseColor';
import { DecorationService } from 'common/services/DecorationService';
import { IDecorationService } from 'common/services/Services';
import { IDecoration, IDecorationOptions, IDisposable, ILinkProvider, IMarker } from 'xterm';
import { IDecoration, IDecorationOptions, IDisposable, ILinkProvider, IMarker } from '@xterm/xterm';
import { WindowsOptionsReportType } from '../common/InputHandler';
import { AccessibilityManager } from './AccessibilityManager';
// 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;
@@ -397,7 +394,19 @@ export class Terminal extends CoreTerminal implements ITerminal {
this._logService.debug('Terminal.open was called on an element that was not attached to the DOM');
}
this._document = parent.ownerDocument!;
// If the terminal is already opened
if (this.element?.ownerDocument.defaultView && this._coreBrowserService) {
// Adjust the window if needed
if (this.element.ownerDocument.defaultView !== this._coreBrowserService.window) {
this._coreBrowserService.window = this.element.ownerDocument.defaultView;
}
return;
}
this._document = parent.ownerDocument;
if (this.options.documentOverride && this.options.documentOverride instanceof Document) {
this._document = this.optionsService.rawOptions.documentOverride as Document;
}
// Create main element container
this.element = this._document.createElement('div');
@@ -408,25 +417,25 @@ export class Terminal extends CoreTerminal implements ITerminal {
// 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');
const fragment = this._document.createDocumentFragment();
this._viewportElement = this._document.createElement('div');
this._viewportElement.classList.add('xterm-viewport');
fragment.appendChild(this._viewportElement);
this._viewportScrollArea = document.createElement('div');
this._viewportScrollArea = this._document.createElement('div');
this._viewportScrollArea.classList.add('xterm-scroll-area');
this._viewportElement.appendChild(this._viewportScrollArea);
this.screenElement = document.createElement('div');
this.screenElement = this._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 = this._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 = this._document.createElement('textarea');
this.textarea.classList.add('xterm-helper-textarea');
this.textarea.setAttribute('aria-label', Strings.promptLabel);
if (!Browser.isChromeOS) {
@@ -441,7 +450,12 @@ export class Terminal extends CoreTerminal implements ITerminal {
// Register the core browser service before the generic textarea handlers are registered so it
// handles them first. Otherwise the renderers may use the wrong focus state.
this._coreBrowserService = this._instantiationService.createInstance(CoreBrowserService, this.textarea, this._document.defaultView ?? window);
this._coreBrowserService = this.register(this._instantiationService.createInstance(CoreBrowserService,
this.textarea,
parent.ownerDocument.defaultView ?? window,
// Force unsafe null in node.js environment for tests
this._document ?? (typeof window !== 'undefined') ? window.document : null as any
));
this._instantiationService.setService(ICoreBrowserService, this._coreBrowserService);
this.register(addDisposableDomListener(this.textarea, 'focus', (ev: KeyboardEvent) => this._handleTextAreaFocus(ev)));
@@ -463,7 +477,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
this.register(this._renderService.onRenderedViewportChange(e => this._onRender.fire(e)));
this.onResize(e => this._renderService!.resize(e.cols, e.rows));
this._compositionView = document.createElement('div');
this._compositionView = this._document.createElement('div');
this._compositionView.classList.add('composition-view');
this._compositionHelper = this._instantiationService.createInstance(CompositionHelper, this.textarea, this._compositionView);
this._helperContainer.appendChild(this._compositionView);
+1 -1
View File
@@ -9,7 +9,7 @@ import * as os from 'os';
import * as fs from 'fs';
import * as pty from 'node-pty';
import { Terminal } from 'browser/Terminal';
import { IDisposable } from 'xterm';
import { IDisposable } from '@xterm/xterm';
// all test files expect terminal in 80x25
const COLS = 80;
+6 -1
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { IDisposable, IMarker, ILinkProvider, IDecorationOptions, IDecoration } from 'xterm';
import { IDisposable, IMarker, ILinkProvider, IDecorationOptions, IDecoration } from '@xterm/xterm';
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';
@@ -350,11 +350,16 @@ export class MockCompositionHelper implements ICompositionHelper {
}
export class MockCoreBrowserService implements ICoreBrowserService {
public onDprChange = new EventEmitter<number>().event;
public onWindowChange = new EventEmitter<Window & typeof globalThis, void>().event;
public serviceBrand: undefined;
public isFocused: boolean = true;
public get window(): Window & typeof globalThis {
throw Error('Window object not available in tests');
}
public get mainDocument(): Document {
throw Error('Document object not available in tests');
}
public dpr: number = 1;
}
+1 -1
View File
@@ -6,7 +6,7 @@
import { IEvent } from 'common/EventEmitter';
import { CharData, IColor, ICoreTerminal, ITerminalOptions } from 'common/Types';
import { IBuffer } from 'common/buffer/Types';
import { IDisposable, Terminal as ITerminalApi } from 'xterm';
import { IDisposable, Terminal as ITerminalApi } from '@xterm/xterm';
import { IMouseService, IRenderService } from './services/Services';
/**
@@ -3,8 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { addDisposableDomListener } from 'browser/Lifecycle';
import { IRenderService } from 'browser/services/Services';
import { ICoreBrowserService, IRenderService } from 'browser/services/Services';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { IBufferService, IDecorationService, IInternalDecoration } from 'common/services/Services';
@@ -19,6 +18,7 @@ export class BufferDecorationRenderer extends Disposable {
constructor(
private readonly _screenElement: HTMLElement,
@IBufferService private readonly _bufferService: IBufferService,
@ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService,
@IDecorationService private readonly _decorationService: IDecorationService,
@IRenderService private readonly _renderService: IRenderService
) {
@@ -33,7 +33,7 @@ export class BufferDecorationRenderer extends Disposable {
this._dimensionsChanged = true;
this._queueRefresh();
}));
this.register(addDisposableDomListener(window, 'resize', () => this._queueRefresh()));
this.register(this._coreBrowserService.onDprChange(() => this._queueRefresh()));
this.register(this._bufferService.buffers.onBufferActivate(() => {
this._altBufferIsActive = this._bufferService.buffer === this._bufferService.buffers.alt;
}));
@@ -70,7 +70,7 @@ export class BufferDecorationRenderer extends Disposable {
}
private _createElement(decoration: IInternalDecoration): HTMLElement {
const element = document.createElement('div');
const element = this._coreBrowserService.mainDocument.createElement('div');
element.classList.add('xterm-decoration');
element.classList.toggle('xterm-decoration-top-layer', decoration?.options?.layer === 'top');
element.style.width = `${Math.round((decoration.options.width || 1) * this._renderService.dimensions.css.cell.width)}px`;
@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import { ColorZoneStore, IColorZone, IColorZoneStore } from 'browser/decorations/ColorZoneStore';
import { addDisposableDomListener } from 'browser/Lifecycle';
import { ICoreBrowserService, IRenderService } from 'browser/services/Services';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
@@ -52,10 +51,10 @@ export class OverviewRulerRenderer extends Disposable {
@IDecorationService private readonly _decorationService: IDecorationService,
@IRenderService private readonly _renderService: IRenderService,
@IOptionsService private readonly _optionsService: IOptionsService,
@ICoreBrowserService private readonly _coreBrowseService: ICoreBrowserService
@ICoreBrowserService private readonly _coreBrowserService: ICoreBrowserService
) {
super();
this._canvas = document.createElement('canvas');
this._canvas = this._coreBrowserService.mainDocument.createElement('canvas');
this._canvas.classList.add('xterm-decoration-overview-ruler');
this._refreshCanvasDimensions();
this._viewportElement.parentElement?.insertBefore(this._canvas, this._viewportElement);
@@ -112,7 +111,7 @@ export class OverviewRulerRenderer extends Disposable {
// overview ruler width changed
this.register(this._optionsService.onSpecificOptionChange('overviewRulerWidth', () => this._queueRefresh(true)));
// device pixel ratio changed
this.register(addDisposableDomListener(this._coreBrowseService.window, 'resize', () => this._queueRefresh(true)));
this.register(this._coreBrowserService.onDprChange(() => this._queueRefresh(true)));
// set the canvas dimensions
this._queueRefresh(true);
}
@@ -135,11 +134,11 @@ export class OverviewRulerRenderer extends Disposable {
}
private _refreshDrawHeightConstants(): void {
drawHeight.full = Math.round(2 * this._coreBrowseService.dpr);
drawHeight.full = Math.round(2 * this._coreBrowserService.dpr);
// Calculate actual pixels per line
const pixelsPerLine = this._canvas.height / this._bufferService.buffer.lines.length;
// Clamp actual pixels within a range
const nonFullHeight = Math.round(Math.max(Math.min(pixelsPerLine, 12), 6) * this._coreBrowseService.dpr);
const nonFullHeight = Math.round(Math.max(Math.min(pixelsPerLine, 12), 6) * this._coreBrowserService.dpr);
drawHeight.left = nonFullHeight;
drawHeight.center = nonFullHeight;
drawHeight.right = nonFullHeight;
@@ -157,9 +156,9 @@ export class OverviewRulerRenderer extends Disposable {
private _refreshCanvasDimensions(): void {
this._canvas.style.width = `${this._width}px`;
this._canvas.width = Math.round(this._width * this._coreBrowseService.dpr);
this._canvas.width = Math.round(this._width * this._coreBrowserService.dpr);
this._canvas.style.height = `${this._screenElement.clientHeight}px`;
this._canvas.height = Math.round(this._screenElement.clientHeight * this._coreBrowseService.dpr);
this._canvas.height = Math.round(this._screenElement.clientHeight * this._coreBrowserService.dpr);
this._refreshDrawConstants();
this._refreshColorZonePadding();
}
@@ -211,7 +210,7 @@ export class OverviewRulerRenderer extends Disposable {
if (this._animationFrame !== undefined) {
return;
}
this._animationFrame = this._coreBrowseService.window.requestAnimationFrame(() => {
this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => {
this._refreshDecorations();
this._animationFrame = undefined;
});
+1 -1
View File
@@ -13,7 +13,7 @@ import { AddonManager } from 'common/public/AddonManager';
import { BufferNamespaceApi } from 'common/public/BufferNamespaceApi';
import { ParserApi } from 'common/public/ParserApi';
import { UnicodeApi } from 'common/public/UnicodeApi';
import { IBufferNamespace as IBufferNamespaceApi, IDecoration, IDecorationOptions, IDisposable, ILinkProvider, ILocalizableStrings, IMarker, IModes, IParser, ITerminalAddon, Terminal as ITerminalApi, ITerminalInitOnlyOptions, IUnicodeHandling } from 'xterm';
import { IBufferNamespace as IBufferNamespaceApi, IDecoration, IDecorationOptions, IDisposable, ILinkProvider, ILocalizableStrings, IMarker, IModes, IParser, ITerminalAddon, Terminal as ITerminalApi, ITerminalInitOnlyOptions, IUnicodeHandling } from '@xterm/xterm';
/**
* The set of options that only have an effect when set in the Terminal constructor.
@@ -4,7 +4,7 @@ import { ReadonlyColorSet } from 'browser/Types';
import { Attributes, BgFlags, FgFlags } from 'common/buffer/Constants';
import { IDecorationService } from 'common/services/Services';
import { ICellData } from 'common/Types';
import { Terminal } from 'xterm';
import { Terminal } from '@xterm/xterm';
// Work variables to avoid garbage collection
let $fg = 0;
@@ -4,7 +4,7 @@
*/
import { TextureAtlas } from 'browser/renderer/shared/TextureAtlas';
import { ITerminalOptions, Terminal } from 'xterm';
import { ITerminalOptions, Terminal } from '@xterm/xterm';
import { ITerminal, ReadonlyColorSet } from 'browser/Types';
import { ICharAtlasConfig, ITextureAtlas } from 'browser/renderer/shared/Types';
import { generateConfig, configEquals } from 'browser/renderer/shared/CharAtlasUtils';
@@ -5,7 +5,7 @@
import { ICharAtlasConfig } from './Types';
import { Attributes } from 'common/buffer/Constants';
import { ITerminalOptions } from 'xterm';
import { ITerminalOptions } from '@xterm/xterm';
import { IColorSet, ReadonlyColorSet } from 'browser/Types';
import { NULL_COLOR } from 'common/Color';
+1 -1
View File
@@ -474,7 +474,7 @@ function drawPatternChar(
if (!pattern) {
const width = charDefinition[0].length;
const height = charDefinition.length;
const tmpCanvas = document.createElement('canvas');
const tmpCanvas = ctx.canvas.ownerDocument.createElement('canvas');
tmpCanvas.width = width;
tmpCanvas.height = height;
const tmpCtx = throwIfFalsy(tmpCanvas.getContext('2d'));
@@ -5,7 +5,7 @@
import { ITerminal } from 'browser/Types';
import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
import { Terminal } from 'xterm';
import { Terminal } from '@xterm/xterm';
class SelectionRenderModel implements ISelectionRenderModel {
public hasSelection!: boolean;
+1 -1
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { FontWeight, Terminal } from 'xterm';
import { FontWeight, Terminal } from '@xterm/xterm';
import { IColorSet, ITerminal } from 'browser/Types';
import { IDisposable } from 'common/Types';
import { IEvent } from 'common/EventEmitter';
+106 -2
View File
@@ -3,22 +3,49 @@
* @license MIT
*/
import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
import { ICoreBrowserService } from './Services';
import { EventEmitter, forwardEvent } from 'common/EventEmitter';
import { addDisposableDomListener } from 'browser/Lifecycle';
export class CoreBrowserService implements ICoreBrowserService {
export class CoreBrowserService extends Disposable implements ICoreBrowserService {
public serviceBrand: undefined;
private _isFocused = false;
private _cachedIsFocused: boolean | undefined = undefined;
private _screenDprMonitor = new ScreenDprMonitor(this._window);
private readonly _onDprChange = this.register(new EventEmitter<number>());
public readonly onDprChange = this._onDprChange.event;
private readonly _onWindowChange = this.register(new EventEmitter<Window & typeof globalThis>());
public readonly onWindowChange = this._onWindowChange.event;
constructor(
private _textarea: HTMLTextAreaElement,
public readonly window: Window & typeof globalThis
private _window: Window & typeof globalThis,
public readonly mainDocument: Document
) {
super();
// Monitor device pixel ratio
this.register(this.onWindowChange(w => this._screenDprMonitor.setWindow(w)));
this.register(forwardEvent(this._screenDprMonitor.onDprChange, this._onDprChange));
this._textarea.addEventListener('focus', () => this._isFocused = true);
this._textarea.addEventListener('blur', () => this._isFocused = false);
}
public get window(): Window & typeof globalThis {
return this._window;
}
public set window(value: Window & typeof globalThis) {
if (this._window !== value) {
this._window = value;
this._onWindowChange.fire(this._window);
}
}
public get dpr(): number {
return this.window.devicePixelRatio;
}
@@ -31,3 +58,80 @@ export class CoreBrowserService implements ICoreBrowserService {
return this._cachedIsFocused;
}
}
/**
* 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.
*/
class ScreenDprMonitor extends Disposable {
private _currentDevicePixelRatio: number;
private _outerListener: ((this: MediaQueryList, ev: MediaQueryListEvent) => any) | undefined;
private _resolutionMediaMatchList: MediaQueryList | undefined;
private _windowResizeListener = this.register(new MutableDisposable());
private readonly _onDprChange = this.register(new EventEmitter<number>());
public readonly onDprChange = this._onDprChange.event;
constructor(private _parentWindow: Window) {
super();
// Initialize listener and dpr value
this._outerListener = () => this._setDprAndFireIfDiffers();
this._currentDevicePixelRatio = this._parentWindow.devicePixelRatio;
this._updateDpr();
// Monitor active window resize
this._setWindowResizeListener();
// Setup additional disposables
this.register(toDisposable(() => this.clearListener()));
}
public setWindow(parentWindow: Window): void {
this._parentWindow = parentWindow;
this._setWindowResizeListener();
this._setDprAndFireIfDiffers();
}
private _setWindowResizeListener(): void {
this._windowResizeListener.value = addDisposableDomListener(this._parentWindow, 'resize', () => this._setDprAndFireIfDiffers());
}
private _setDprAndFireIfDiffers(): void {
if (this._parentWindow.devicePixelRatio !== this._currentDevicePixelRatio) {
this._onDprChange.fire(this._parentWindow.devicePixelRatio);
}
this._updateDpr();
}
private _updateDpr(): void {
if (!this._outerListener) {
return;
}
// Clear listeners for old DPR
this._resolutionMediaMatchList?.removeListener(this._outerListener);
// Add listeners for new DPR
this._currentDevicePixelRatio = this._parentWindow.devicePixelRatio;
this._resolutionMediaMatchList = this._parentWindow.matchMedia(`screen and (resolution: ${this._parentWindow.devicePixelRatio}dppx)`);
this._resolutionMediaMatchList.addListener(this._outerListener);
}
public clearListener(): void {
if (!this._resolutionMediaMatchList || !this._outerListener) {
return;
}
this._resolutionMediaMatchList.removeListener(this._outerListener);
this._resolutionMediaMatchList = undefined;
this._outerListener = undefined;
}
}
+3 -11
View File
@@ -3,16 +3,14 @@
* @license MIT
*/
import { addDisposableDomListener } from 'browser/Lifecycle';
import { RenderDebouncer } from 'browser/RenderDebouncer';
import { ScreenDprMonitor } from 'browser/ScreenDprMonitor';
import { IRenderDebouncerWithCallback } from 'browser/Types';
import { IRenderDimensions, IRenderer } from 'browser/renderer/shared/Types';
import { ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
import { EventEmitter } from 'common/EventEmitter';
import { Disposable, MutableDisposable } from 'common/Lifecycle';
import { DebouncedIdleTask } from 'common/TaskQueue';
import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services';
import { IBufferService, IDecorationService, IInstantiationService, IOptionsService } from 'common/services/Services';
interface ISelectionState {
start: [number, number] | undefined;
@@ -25,7 +23,6 @@ export class RenderService extends Disposable implements IRenderService {
private _renderer: MutableDisposable<IRenderer> = this.register(new MutableDisposable());
private _renderDebouncer: IRenderDebouncerWithCallback;
private _screenDprMonitor: ScreenDprMonitor;
private _pausedResizeTask = new DebouncedIdleTask();
private _isPaused: boolean = false;
@@ -59,6 +56,7 @@ export class RenderService extends Disposable implements IRenderService {
@IDecorationService decorationService: IDecorationService,
@IBufferService bufferService: IBufferService,
@ICoreBrowserService coreBrowserService: ICoreBrowserService,
@IInstantiationService instantiationService: IInstantiationService,
@IThemeService themeService: IThemeService
) {
super();
@@ -66,9 +64,7 @@ export class RenderService extends Disposable implements IRenderService {
this._renderDebouncer = new RenderDebouncer(coreBrowserService.window, (start, end) => this._renderRows(start, end));
this.register(this._renderDebouncer);
this._screenDprMonitor = new ScreenDprMonitor(coreBrowserService.window);
this._screenDprMonitor.setListener(() => this.handleDevicePixelRatioChange());
this.register(this._screenDprMonitor);
this.register(coreBrowserService.onDprChange(() => this.handleDevicePixelRatioChange()));
this.register(bufferService.onResize(() => this._fullRefresh()));
this.register(bufferService.buffers.onBufferActivate(() => this._renderer.value?.clear()));
@@ -104,10 +100,6 @@ export class RenderService extends Disposable implements IRenderService {
'cursorStyle'
], () => this.refreshRows(bufferService.buffer.y, bufferService.buffer.y, true)));
// dprchange should handle this case, we need this as well for browsers that don't support the
// matchMedia query.
this.register(addDisposableDomListener(coreBrowserService.window, 'resize', () => this.handleDevicePixelRatioChange()));
this.register(themeService.onChangeColors(() => this._fullRefresh()));
// Detect whether IntersectionObserver is detected and enable renderer pause
+13 -4
View File
@@ -28,12 +28,21 @@ export interface ICoreBrowserService {
serviceBrand: undefined;
readonly isFocused: boolean;
readonly onDprChange: IEvent<number>;
readonly onWindowChange: IEvent<Window & typeof globalThis>;
/**
* Parent window that the terminal is rendered into. DOM and rendering APIs
* (e.g. requestAnimationFrame) should be invoked in the context of this
* window.
* Gets or sets the parent window that the terminal is rendered into. DOM and rendering APIs (e.g.
* requestAnimationFrame) should be invoked in the context of this window. This should be set when
* the window hosting the xterm.js instance changes.
*/
readonly window: Window & typeof globalThis;
window: Window & typeof globalThis;
/**
* The document of the primary window to be used to create elements when working with multiple
* windows. This is defined by the documentOverride setting.
*/
readonly mainDocument: Document;
/**
* Helper for getting the devicePixelRatio of the parent window.
*/
+1
View File
@@ -113,6 +113,7 @@ export namespace css {
let $ctx: CanvasRenderingContext2D | undefined;
let $litmusColor: CanvasGradient | undefined;
if (!isNode) {
// This is guaranteed to run in the first window, so document should be correct
const canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = 1;
+5
View File
@@ -71,3 +71,8 @@ export class EventEmitter<T, U = void> implements IEventEmitter<T, U> {
export function forwardEvent<T>(from: IEvent<T>, to: IEventEmitter<T>): IDisposable {
return from(e => to.fire(e));
}
export function runAndSubscribe<T>(event: IEvent<T>, handler: (e: T | undefined) => any): IDisposable {
handler(undefined);
return event(e => handler(e));
}
+1 -1
View File
@@ -49,7 +49,7 @@ const GLEVEL: { [key: string]: number } = { '(': 0, ')': 1, '*': 2, '+': 3, '-':
/**
* Document xterm VT features here that are currently unsupported
*/
// @vt: #E[Supported via xterm-addon-image.] DCS SIXEL "SIXEL Graphics" "DCS Ps ; Ps ; Ps ; q Pt ST" "Draw SIXEL image."
// @vt: #E[Supported via @xterm/addon-image.] DCS SIXEL "SIXEL Graphics" "DCS Ps ; Ps ; Ps ; q Pt ST" "Draw SIXEL image."
// @vt: #N DCS DECUDK "User Defined Keys" "DCS Ps ; Ps \| Pt ST" "Definitions for user-defined keys."
// @vt: #N DCS XTGETTCAP "Request Terminfo String" "DCS + q Pt ST" "Request Terminfo String."
// @vt: #N DCS XTSETTCAP "Set Terminfo Data" "DCS + p Pt ST" "Set Terminfo Data."
+2 -1
View File
@@ -12,8 +12,9 @@ interface INavigator {
// 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;
declare const process: unknown;
export const isNode = (typeof navigator === 'undefined') ? true : false;
export const isNode = (typeof process !== 'undefined') ? true : false;
const userAgent = (isNode) ? 'node' : navigator.userAgent;
const platform = (isNode) ? 'node' : navigator.platform;
+1 -1
View File
@@ -12,7 +12,7 @@ import { IBufferSet, IBuffer } from 'common/buffer/Types';
import { BufferSet } from 'common/buffer/BufferSet';
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType, ICharset, IModes, IAttributeData, IOscLinkData, IDisposable } from 'common/Types';
import { UnicodeV6 } from 'common/input/UnicodeV6';
import { IDecorationOptions, IDecoration } from 'xterm';
import { IDecorationOptions, IDecoration } from '@xterm/xterm';
export class MockBufferService implements IBufferService {
public serviceBrand: any;
+1 -1
View File
@@ -9,7 +9,7 @@ import { Attributes, UnderlineStyle } from 'common/buffer/Constants'; // eslint-
import { IBufferSet } from 'common/buffer/Types';
import { IParams } from 'common/parser/Types';
import { ICoreMouseService, ICoreService, IOptionsService, IUnicodeService } from 'common/services/Services';
import { IFunctionIdentifier, ITerminalOptions as IPublicTerminalOptions } from 'xterm';
import { IFunctionIdentifier, ITerminalOptions as IPublicTerminalOptions } from '@xterm/xterm';
export interface ICoreTerminal {
coreMouseService: ICoreMouseService;
+1 -1
View File
@@ -5,7 +5,7 @@
import { assert } from 'chai';
import { getRangeLength } from 'common/buffer/BufferRange';
import { IBufferRange } from 'xterm';
import { IBufferRange } from '@xterm/xterm';
describe('BufferRange', () => {
describe('getRangeLength', () => {
+1 -1
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { IBufferRange } from 'xterm';
import { IBufferRange } from '@xterm/xterm';
export function getRangeLength(range: IBufferRange, bufferCols: number): number {
if (range.start.y > range.end.y) {
+15
View File
@@ -106,6 +106,21 @@ describe('Keyboard', () => {
it('should return \\x1b[5B for ctrl+down', () => {
assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 40 }).key, '\x1b[1;5B'); // CSI 5 B
});
it('should return \\x08 for ctrl+backspace', () => {
assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, keyCode: 8 }).key, '\x08');
});
it('should return \\x1b\\x7f for alt+backspace', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 8 }).key, '\x1b\x7f');
});
it('should return \\x1b\\x08 for ctrl+alt+backspace', () => {
assert.equal(testEvaluateKeyboardEvent({ ctrlKey: true, altKey: true, keyCode: 8 }).key, '\x1b\x08');
});
it('should return \\x1b[3;2~ for shift+delete', () => {
assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 46 }).key, '\x1b[3;2~');
});
it('should return \\x1b[3;3~ for alt+delete', () => {
assert.equal(testEvaluateKeyboardEvent({ altKey: true, keyCode: 46 }).key, '\x1b[3;3~');
});
describe('On non-macOS platforms', () => {
// Evalueate alt + arrow key movement, which is a feature of terminal emulators but not VT100
+2 -3
View File
@@ -83,11 +83,10 @@ export function evaluateKeyboardEvent(
break;
case 8:
// backspace
result.key = ev.ctrlKey ? '\b' : C0.DEL; // ^H or ^?
if (ev.altKey) {
result.key = C0.ESC + C0.DEL; // \e ^?
break;
result.key = C0.ESC + result.key;
}
result.key = C0.DEL; // ^?
break;
case 9:
// tab
+1 -1
View File
@@ -5,7 +5,7 @@
import { assert } from 'chai';
import { AddonManager, ILoadedAddon } from './AddonManager';
import { ITerminalAddon } from 'xterm';
import { ITerminalAddon } from '@xterm/xterm';
class TestAddonManager extends AddonManager {
public get addons(): ILoadedAddon[] {
+1 -1
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { ITerminalAddon, IDisposable, Terminal } from 'xterm';
import { ITerminalAddon, IDisposable, Terminal } from '@xterm/xterm';
export interface ILoadedAddon {
instance: ITerminalAddon;
+1 -1
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi } from 'xterm';
import { IBuffer as IBufferApi, IBufferLine as IBufferLineApi, IBufferCell as IBufferCellApi } from '@xterm/xterm';
import { IBuffer } from 'common/buffer/Types';
import { BufferLineApiView } from 'common/public/BufferLineApiView';
import { CellData } from 'common/buffer/CellData';
+1 -1
View File
@@ -5,7 +5,7 @@
import { CellData } from 'common/buffer/CellData';
import { IBufferLine, ICellData } from 'common/Types';
import { IBufferCell as IBufferCellApi, IBufferLine as IBufferLineApi } from 'xterm';
import { IBufferCell as IBufferCellApi, IBufferLine as IBufferLineApi } from '@xterm/xterm';
export class BufferLineApiView implements IBufferLineApi {
constructor(private _line: IBufferLine) { }
+1 -1
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { IBuffer as IBufferApi, IBufferNamespace as IBufferNamespaceApi } from 'xterm';
import { IBuffer as IBufferApi, IBufferNamespace as IBufferNamespaceApi } from '@xterm/xterm';
import { BufferApiView } from 'common/public/BufferApiView';
import { EventEmitter } from 'common/EventEmitter';
import { ICoreTerminal } from 'common/Types';
+1 -1
View File
@@ -4,7 +4,7 @@
*/
import { IParams } from 'common/parser/Types';
import { IDisposable, IFunctionIdentifier, IParser } from 'xterm';
import { IDisposable, IFunctionIdentifier, IParser } from '@xterm/xterm';
import { ICoreTerminal } from 'common/Types';
export class ParserApi implements IParser {
+1 -1
View File
@@ -4,7 +4,7 @@
*/
import { ICoreTerminal } from 'common/Types';
import { IUnicodeHandling, IUnicodeVersionProvider } from 'xterm';
import { IUnicodeHandling, IUnicodeVersionProvider } from '@xterm/xterm';
export class UnicodeApi implements IUnicodeHandling {
constructor(private _core: ICoreTerminal) { }
+1 -1
View File
@@ -9,7 +9,7 @@ import { Disposable, toDisposable } from 'common/Lifecycle';
import { IDecorationService, IInternalDecoration } from 'common/services/Services';
import { SortedList } from 'common/SortedList';
import { IColor } from 'common/Types';
import { IDecoration, IDecorationOptions, IMarker } from 'xterm';
import { IDecoration, IDecorationOptions, IMarker } from '@xterm/xterm';
// Work variables to avoid garbage collection
let $xmin = 0;
+1
View File
@@ -18,6 +18,7 @@ export const DEFAULT_OPTIONS: Readonly<Required<ITerminalOptions>> = {
cursorInactiveStyle: 'outline',
customGlyphs: true,
drawBoldTextInBrightColors: true,
documentOverride: null,
fastScrollModifier: 'alt',
fastScrollSensitivity: 5,
fontFamily: 'courier-new, courier, monospace',
+2 -1
View File
@@ -7,7 +7,7 @@ import { IEvent, IEventEmitter } from 'common/EventEmitter';
import { IBuffer, IBufferSet } from 'common/buffer/Types';
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset, IWindowOptions, IModes, IAttributeData, ScrollSource, IDisposable, IColor, CursorStyle, CursorInactiveStyle, IOscLinkData } from 'common/Types';
import { createDecorator } from 'common/services/ServiceRegistry';
import { IDecorationOptions, IDecoration, ILinkHandler, IWindowsPty, ILogger } from 'xterm';
import { IDecorationOptions, IDecoration, ILinkHandler, IWindowsPty, ILogger } from '@xterm/xterm';
export const IBufferService = createDecorator<IBufferService>('BufferService');
export interface IBufferService {
@@ -217,6 +217,7 @@ export interface ITerminalOptions {
cursorInactiveStyle?: CursorInactiveStyle;
customGlyphs?: boolean;
disableStdin?: boolean;
documentOverride?: any | null;
drawBoldTextInBrightColors?: boolean;
fastScrollModifier?: 'none' | 'alt' | 'ctrl' | 'shift';
fastScrollSensitivity?: number;
+1 -1
View File
@@ -5,7 +5,7 @@
import { deepStrictEqual, strictEqual, throws } from 'assert';
import { Terminal } from 'headless/public/Terminal';
import { ITerminalOptions } from 'xterm-headless';
import { ITerminalOptions } from '@xterm/headless';
let term: Terminal;
+1 -1
View File
@@ -7,7 +7,7 @@ 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, ITerminalInitOnlyOptions, IUnicodeHandling, Terminal as ITerminalApi } from 'xterm-headless';
import { IBufferNamespace as IBufferNamespaceApi, IMarker, IModes, IParser, ITerminalAddon, ITerminalInitOnlyOptions, 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';
+1 -1
View File
@@ -17,7 +17,7 @@
},
"include": [
"./**/*",
"../../typings/xterm.d.ts", // common/Types.d.ts imports from 'xterm'
"../../typings/xterm.d.ts", // common/Types.d.ts imports from '@xterm/xterm'
"../../typings/xterm-headless.d.ts"
],
"references": [