mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-22 12:50:29 -04:00
Merge from vscode 52dcb723a39ae75bee1bd56b3312d7fcdc87aeed (#6719)
This commit is contained in:
@@ -9,7 +9,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { debounce } from 'vs/base/common/decorators';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import * as lifecycle from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { TabFocus } from 'vs/editor/common/config/commonEditorConfig';
|
||||
import * as nls from 'vs/nls';
|
||||
@@ -30,13 +30,13 @@ import { ansiColorIdentifiers, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_BACKGR
|
||||
import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminalCommands';
|
||||
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
|
||||
import { TerminalLinkHandler } from 'vs/workbench/contrib/terminal/browser/terminalLinkHandler';
|
||||
import { TerminalCommandTracker } from 'vs/workbench/contrib/terminal/browser/terminalCommandTracker';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/terminalProcessManager';
|
||||
import { Terminal as XTermTerminal, IBuffer, ITerminalAddon } from 'xterm';
|
||||
import { SearchAddon, ISearchOptions } from 'xterm-addon-search';
|
||||
import { CommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon';
|
||||
import { NavigationModeAddon } from 'vs/workbench/contrib/terminal/browser/addons/navigationModeAddon';
|
||||
|
||||
// How long in milliseconds should an average frame take to render for a notification to appear
|
||||
@@ -165,15 +165,15 @@ interface IGridDimensions {
|
||||
rows: number;
|
||||
}
|
||||
|
||||
export class TerminalInstance implements ITerminalInstance {
|
||||
export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
private static readonly EOL_REGEX = /\r?\n/g;
|
||||
|
||||
private static _lastKnownCanvasDimensions: ICanvasDimensions | undefined;
|
||||
private static _lastKnownGridDimensions: IGridDimensions | undefined;
|
||||
private static _idCounter = 1;
|
||||
|
||||
private _processManager: ITerminalProcessManager | undefined;
|
||||
private _pressAnyKeyToCloseListener: lifecycle.IDisposable | undefined;
|
||||
private _processManager!: ITerminalProcessManager;
|
||||
private _pressAnyKeyToCloseListener: IDisposable | undefined;
|
||||
|
||||
private _id: number;
|
||||
private _isExiting: boolean;
|
||||
@@ -181,27 +181,26 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
private _isVisible: boolean;
|
||||
private _isDisposed: boolean;
|
||||
private _skipTerminalCommands: string[];
|
||||
private _title: string;
|
||||
private _wrapperElement: HTMLDivElement;
|
||||
private _xterm: XTermTerminal;
|
||||
private _title: string = '';
|
||||
private _wrapperElement: (HTMLElement & { xterm?: XTermTerminal }) | undefined;
|
||||
private _xterm: XTermTerminal | undefined;
|
||||
private _xtermSearch: SearchAddon | undefined;
|
||||
private _xtermElement: HTMLDivElement;
|
||||
private _xtermElement: HTMLDivElement | undefined;
|
||||
private _terminalHasTextContextKey: IContextKey<boolean>;
|
||||
private _terminalA11yTreeFocusContextKey: IContextKey<boolean>;
|
||||
private _cols: number;
|
||||
private _rows: number;
|
||||
private _cols: number = 0;
|
||||
private _rows: number = 0;
|
||||
private _dimensionsOverride: ITerminalDimensions | undefined;
|
||||
private _windowsShellHelper: IWindowsShellHelper | undefined;
|
||||
private _xtermReadyPromise: Promise<void>;
|
||||
private _xtermReadyPromise: Promise<XTermTerminal>;
|
||||
private _titleReadyPromise: Promise<string>;
|
||||
private _titleReadyComplete: (title: string) => any;
|
||||
private _titleReadyComplete: ((title: string) => any) | undefined;
|
||||
|
||||
private readonly _disposables = new lifecycle.DisposableStore();
|
||||
private _messageTitleDisposable: lifecycle.IDisposable | undefined;
|
||||
private _messageTitleDisposable: IDisposable | undefined;
|
||||
|
||||
private _widgetManager: TerminalWidgetManager;
|
||||
private _linkHandler: TerminalLinkHandler;
|
||||
private _commandTracker: TerminalCommandTracker;
|
||||
private _widgetManager: TerminalWidgetManager | undefined;
|
||||
private _linkHandler: TerminalLinkHandler | undefined;
|
||||
private _commandTrackerAddon: CommandTrackerAddon | undefined;
|
||||
private _navigationModeAddon: INavigationMode & ITerminalAddon | undefined;
|
||||
|
||||
public disableLayout: boolean;
|
||||
@@ -221,15 +220,15 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
public get maxCols(): number { return this._cols; }
|
||||
public get maxRows(): number { return this._rows; }
|
||||
// TODO: Ideally processId would be merged into processReady
|
||||
public get processId(): number | undefined { return this._processManager ? this._processManager.shellProcessId : undefined; }
|
||||
public get processId(): number | undefined { return this._processManager.shellProcessId; }
|
||||
// TODO: How does this work with detached processes?
|
||||
// TODO: Should this be an event as it can fire twice?
|
||||
public get processReady(): Promise<void> { return this._processManager ? this._processManager.ptyProcessReady : Promise.resolve(undefined); }
|
||||
public get processReady(): Promise<void> { return this._processManager.ptyProcessReady; }
|
||||
public get title(): string { return this._title; }
|
||||
public get hadFocusOnExit(): boolean { return this._hadFocusOnExit; }
|
||||
public get isTitleSetByProcess(): boolean { return !!this._messageTitleDisposable; }
|
||||
public get shellLaunchConfig(): IShellLaunchConfig { return this._shellLaunchConfig; }
|
||||
public get commandTracker(): TerminalCommandTracker { return this._commandTracker; }
|
||||
public get commandTracker(): CommandTrackerAddon | undefined { return this._commandTrackerAddon; }
|
||||
public get navigationMode(): INavigationMode | undefined { return this._navigationModeAddon; }
|
||||
|
||||
private readonly _onExit = new Emitter<number>();
|
||||
@@ -246,8 +245,6 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
public get onData(): Event<string> { return this._onData.event; }
|
||||
private readonly _onLineData = new Emitter<string>();
|
||||
public get onLineData(): Event<string> { return this._onLineData.event; }
|
||||
private readonly _onRendererInput = new Emitter<string>();
|
||||
public get onRendererInput(): Event<string> { return this._onRendererInput.event; }
|
||||
private readonly _onRequestExtHostProcess = new Emitter<ITerminalInstance>();
|
||||
public get onRequestExtHostProcess(): Event<ITerminalInstance> { return this._onRequestExtHostProcess.event; }
|
||||
private readonly _onDimensionsChanged = new Emitter<void>();
|
||||
@@ -275,6 +272,8 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
@IStorageService private readonly _storageService: IStorageService,
|
||||
@IAccessibilityService private readonly _accessibilityService: IAccessibilityService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._skipTerminalCommands = [];
|
||||
this._isExiting = false;
|
||||
this._hadFocusOnExit = false;
|
||||
@@ -293,11 +292,7 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
this._logService.trace(`terminalInstance#ctor (id: ${this.id})`, this._shellLaunchConfig);
|
||||
|
||||
this._initDimensions();
|
||||
if (!this.shellLaunchConfig.isRendererOnly) {
|
||||
this._createProcess();
|
||||
} else {
|
||||
this.setTitle(this._shellLaunchConfig.name, false);
|
||||
}
|
||||
this._createProcess();
|
||||
|
||||
this._xtermReadyPromise = this._createXterm();
|
||||
this._xtermReadyPromise.then(() => {
|
||||
@@ -321,8 +316,8 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
}));
|
||||
}
|
||||
|
||||
public addDisposable(disposable: lifecycle.IDisposable): void {
|
||||
this._disposables.add(disposable);
|
||||
public addDisposable(disposable: IDisposable): void {
|
||||
this._register(disposable);
|
||||
}
|
||||
|
||||
private _initDimensions(): void {
|
||||
@@ -455,11 +450,11 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
/**
|
||||
* Create xterm.js instance and attach data listeners.
|
||||
*/
|
||||
protected async _createXterm(): Promise<void> {
|
||||
protected async _createXterm(): Promise<XTermTerminal> {
|
||||
const Terminal = await this._getXtermConstructor();
|
||||
const font = this._configHelper.getFont(undefined, true);
|
||||
const config = this._configHelper.config;
|
||||
this._xterm = new Terminal({
|
||||
const xterm = new Terminal({
|
||||
scrollback: config.scrollback,
|
||||
theme: this._getXtermTheme(),
|
||||
drawBoldTextInBrightColors: config.drawBoldTextInBrightColors,
|
||||
@@ -476,53 +471,48 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
// TODO: Guess whether to use canvas or dom better
|
||||
rendererType: config.rendererType === 'auto' ? 'canvas' : config.rendererType
|
||||
});
|
||||
this._xterm = xterm;
|
||||
this.updateAccessibilitySupport();
|
||||
this._terminalInstanceService.getXtermSearchConstructor().then(Addon => {
|
||||
this._xtermSearch = new Addon();
|
||||
this._xterm.loadAddon(this._xtermSearch);
|
||||
xterm.loadAddon(this._xtermSearch);
|
||||
});
|
||||
if (this._shellLaunchConfig.initialText) {
|
||||
this._xterm.writeln(this._shellLaunchConfig.initialText);
|
||||
}
|
||||
this._xterm.onLineFeed(() => this._onLineFeed());
|
||||
this._xterm.onKey(e => this._onKey(e.key, e.domEvent));
|
||||
this._xterm.onSelectionChange(async () => this._onSelectionChange());
|
||||
|
||||
if (this._processManager) {
|
||||
this._processManager.onProcessData(data => this._onProcessData(data));
|
||||
this._xterm.onData(data => this._processManager!.write(data));
|
||||
// TODO: How does the cwd work on detached processes?
|
||||
this.processReady.then(async () => {
|
||||
this._linkHandler.processCwd = await this._processManager!.getInitialCwd();
|
||||
});
|
||||
// Init winpty compat and link handler after process creation as they rely on the
|
||||
// underlying process OS
|
||||
this._processManager.onProcessReady(() => {
|
||||
if (!this._processManager) {
|
||||
return;
|
||||
}
|
||||
if (this._processManager.os === platform.OperatingSystem.Windows) {
|
||||
this._xterm.setOption('windowsMode', true);
|
||||
// Force line data to be sent when the cursor is moved, the main purpose for
|
||||
// this is because ConPTY will often not do a line feed but instead move the
|
||||
// cursor, in which case we still want to send the current line's data to tasks.
|
||||
this._xterm.addCsiHandler('H', () => {
|
||||
this._onCursorMove();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, this._processManager);
|
||||
});
|
||||
} else if (this.shellLaunchConfig.isRendererOnly) {
|
||||
this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, undefined);
|
||||
}
|
||||
this._processManager.onProcessData(data => this._onProcessData(data));
|
||||
this._xterm.onData(data => this._processManager.write(data));
|
||||
// TODO: How does the cwd work on detached processes?
|
||||
this.processReady.then(async () => {
|
||||
if (this._linkHandler) {
|
||||
this._linkHandler.processCwd = await this._processManager.getInitialCwd();
|
||||
}
|
||||
});
|
||||
// Init winpty compat and link handler after process creation as they rely on the
|
||||
// underlying process OS
|
||||
this._processManager.onProcessReady(() => {
|
||||
if (this._processManager.os === platform.OperatingSystem.Windows) {
|
||||
xterm.setOption('windowsMode', true);
|
||||
// Force line data to be sent when the cursor is moved, the main purpose for
|
||||
// this is because ConPTY will often not do a line feed but instead move the
|
||||
// cursor, in which case we still want to send the current line's data to tasks.
|
||||
xterm.addCsiHandler('H', () => {
|
||||
this._onCursorMove();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, this._processManager, this._configHelper);
|
||||
});
|
||||
|
||||
// Register listener to trigger the onInput ext API if the terminal is a renderer only
|
||||
if (this._shellLaunchConfig.isRendererOnly) {
|
||||
this._xterm.onData(data => this._sendRendererInput(data));
|
||||
}
|
||||
this._commandTrackerAddon = new CommandTrackerAddon();
|
||||
this._xterm.loadAddon(this._commandTrackerAddon);
|
||||
this._register(this._themeService.onThemeChange(theme => this._updateTheme(xterm, theme)));
|
||||
|
||||
this._commandTracker = new TerminalCommandTracker(this._xterm);
|
||||
this._disposables.add(this._themeService.onThemeChange(theme => this._updateTheme(theme)));
|
||||
return xterm;
|
||||
}
|
||||
|
||||
private _isScreenReaderOptimized(): boolean {
|
||||
@@ -562,7 +552,7 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
}
|
||||
|
||||
public _attachToElement(container: HTMLElement): void {
|
||||
this._xtermReadyPromise.then(() => {
|
||||
this._xtermReadyPromise.then(xterm => {
|
||||
if (this._wrapperElement) {
|
||||
throw new Error('The terminal instance has already been attached to a container');
|
||||
}
|
||||
@@ -573,11 +563,11 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
this._xtermElement = document.createElement('div');
|
||||
|
||||
// Attach the xterm object to the DOM, exposing it to the smoke tests
|
||||
(<any>this._wrapperElement).xterm = this._xterm;
|
||||
this._wrapperElement.xterm = this._xterm;
|
||||
|
||||
this._xterm.open(this._xtermElement);
|
||||
this._xterm.textarea.addEventListener('focus', () => this._onFocus.fire(this));
|
||||
this._xterm.attachCustomKeyEventHandler((event: KeyboardEvent): boolean => {
|
||||
xterm.open(this._xtermElement);
|
||||
xterm.textarea.addEventListener('focus', () => this._onFocus.fire(this));
|
||||
xterm.attachCustomKeyEventHandler((event: KeyboardEvent): boolean => {
|
||||
// Disable all input if the terminal is exiting
|
||||
if (this._isExiting) {
|
||||
return false;
|
||||
@@ -604,7 +594,7 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
|
||||
return true;
|
||||
});
|
||||
this._disposables.add(dom.addDisposableListener(this._xterm.element, 'mousedown', () => {
|
||||
this._register(dom.addDisposableListener(xterm.element, 'mousedown', () => {
|
||||
// We need to listen to the mouseup event on the document since the user may release
|
||||
// the mouse button anywhere outside of _xterm.element.
|
||||
const listener = dom.addDisposableListener(document, 'mouseup', () => {
|
||||
@@ -616,17 +606,17 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
}));
|
||||
|
||||
// xterm.js currently drops selection on keyup as we need to handle this case.
|
||||
this._disposables.add(dom.addDisposableListener(this._xterm.element, 'keyup', () => {
|
||||
this._register(dom.addDisposableListener(xterm.element, 'keyup', () => {
|
||||
// Wait until keyup has propagated through the DOM before evaluating
|
||||
// the new selection state.
|
||||
setTimeout(() => this._refreshSelectionContextKey(), 0);
|
||||
}));
|
||||
|
||||
const xtermHelper: HTMLElement = <HTMLElement>this._xterm.element.querySelector('.xterm-helpers');
|
||||
const xtermHelper: HTMLElement = <HTMLElement>xterm.element.querySelector('.xterm-helpers');
|
||||
const focusTrap: HTMLElement = document.createElement('div');
|
||||
focusTrap.setAttribute('tabindex', '0');
|
||||
dom.addClass(focusTrap, 'focus-trap');
|
||||
this._disposables.add(dom.addDisposableListener(focusTrap, 'focus', () => {
|
||||
this._register(dom.addDisposableListener(focusTrap, 'focus', () => {
|
||||
let currentElement = focusTrap;
|
||||
while (!dom.hasClass(currentElement, 'part')) {
|
||||
currentElement = currentElement.parentElement!;
|
||||
@@ -634,20 +624,20 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
const hidePanelElement = <HTMLElement>currentElement.querySelector('.hide-panel-action');
|
||||
hidePanelElement.focus();
|
||||
}));
|
||||
xtermHelper.insertBefore(focusTrap, this._xterm.textarea);
|
||||
xtermHelper.insertBefore(focusTrap, xterm.textarea);
|
||||
|
||||
this._disposables.add(dom.addDisposableListener(this._xterm.textarea, 'focus', () => {
|
||||
this._register(dom.addDisposableListener(xterm.textarea, 'focus', () => {
|
||||
this._terminalFocusContextKey.set(true);
|
||||
this._onFocused.fire(this);
|
||||
}));
|
||||
this._disposables.add(dom.addDisposableListener(this._xterm.textarea, 'blur', () => {
|
||||
this._register(dom.addDisposableListener(xterm.textarea, 'blur', () => {
|
||||
this._terminalFocusContextKey.reset();
|
||||
this._refreshSelectionContextKey();
|
||||
}));
|
||||
this._disposables.add(dom.addDisposableListener(this._xterm.element, 'focus', () => {
|
||||
this._register(dom.addDisposableListener(xterm.element, 'focus', () => {
|
||||
this._terminalFocusContextKey.set(true);
|
||||
}));
|
||||
this._disposables.add(dom.addDisposableListener(this._xterm.element, 'blur', () => {
|
||||
this._register(dom.addDisposableListener(xterm.element, 'blur', () => {
|
||||
this._terminalFocusContextKey.reset();
|
||||
this._refreshSelectionContextKey();
|
||||
}));
|
||||
@@ -655,13 +645,13 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
this._wrapperElement.appendChild(this._xtermElement);
|
||||
this._container.appendChild(this._wrapperElement);
|
||||
|
||||
if (this._processManager) {
|
||||
this._widgetManager = new TerminalWidgetManager(this._wrapperElement);
|
||||
this._processManager.onProcessReady(() => this._linkHandler.setWidgetManager(this._widgetManager));
|
||||
} else if (this._shellLaunchConfig.isRendererOnly) {
|
||||
this._widgetManager = new TerminalWidgetManager(this._wrapperElement);
|
||||
this._linkHandler.setWidgetManager(this._widgetManager);
|
||||
}
|
||||
const widgetManager = new TerminalWidgetManager(this._wrapperElement);
|
||||
this._widgetManager = widgetManager;
|
||||
this._processManager.onProcessReady(() => {
|
||||
if (this._linkHandler) {
|
||||
this._linkHandler.setWidgetManager(widgetManager);
|
||||
}
|
||||
});
|
||||
|
||||
const computedStyle = window.getComputedStyle(this._container);
|
||||
const width = parseInt(computedStyle.getPropertyValue('width').replace('px', ''), 10);
|
||||
@@ -672,8 +662,8 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
|
||||
// If IShellLaunchConfig.waitOnExit was true and the process finished before the terminal
|
||||
// panel was initialized.
|
||||
if (this._xterm.getOption('disableStdin')) {
|
||||
this._attachPressAnyKeyToCloseListener();
|
||||
if (xterm.getOption('disableStdin')) {
|
||||
this._attachPressAnyKeyToCloseListener(xterm);
|
||||
}
|
||||
|
||||
const neverMeasureRenderTime = this._storageService.getBoolean(NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, StorageScope.GLOBAL, false);
|
||||
@@ -683,9 +673,10 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
});
|
||||
}
|
||||
|
||||
private _measureRenderTime(): void {
|
||||
private async _measureRenderTime(): Promise<void> {
|
||||
const xterm = await this._xtermReadyPromise;
|
||||
const frameTimes: number[] = [];
|
||||
const textRenderLayer = this._xterm._core._renderService._renderer._renderLayers[0];
|
||||
const textRenderLayer = xterm._core._renderService._renderer._renderLayers[0];
|
||||
const originalOnGridChanged = textRenderLayer.onGridChanged;
|
||||
|
||||
const evaluateCanvasRenderer = () => {
|
||||
@@ -730,34 +721,41 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
}
|
||||
|
||||
public registerLinkMatcher(regex: RegExp, handler: (url: string) => void, matchIndex?: number, validationCallback?: (uri: string, callback: (isValid: boolean) => void) => void): number {
|
||||
return this._linkHandler.registerCustomLinkHandler(regex, handler, matchIndex, validationCallback);
|
||||
return this._linkHandler!.registerCustomLinkHandler(regex, handler, matchIndex, validationCallback);
|
||||
}
|
||||
|
||||
public deregisterLinkMatcher(linkMatcherId: number): void {
|
||||
this._xterm.deregisterLinkMatcher(linkMatcherId);
|
||||
this._xtermReadyPromise.then(xterm => xterm.deregisterLinkMatcher(linkMatcherId));
|
||||
}
|
||||
|
||||
public hasSelection(): boolean {
|
||||
return this._xterm && this._xterm.hasSelection();
|
||||
return this._xterm ? this._xterm.hasSelection() : false;
|
||||
}
|
||||
|
||||
public async copySelection(): Promise<void> {
|
||||
const xterm = await this._xtermReadyPromise;
|
||||
if (this.hasSelection()) {
|
||||
await this._clipboardService.writeText(this._xterm.getSelection());
|
||||
await this._clipboardService.writeText(xterm.getSelection());
|
||||
} else {
|
||||
this._notificationService.warn(nls.localize('terminal.integrated.copySelection.noSelection', 'The terminal has no selection to copy'));
|
||||
}
|
||||
}
|
||||
|
||||
public get selection(): string | undefined {
|
||||
return this.hasSelection() ? this._xterm.getSelection() : undefined;
|
||||
return this._xterm && this.hasSelection() ? this._xterm.getSelection() : undefined;
|
||||
}
|
||||
|
||||
public clearSelection(): void {
|
||||
if (!this._xterm) {
|
||||
return;
|
||||
}
|
||||
this._xterm.clearSelection();
|
||||
}
|
||||
|
||||
public selectAll(): void {
|
||||
if (!this._xterm) {
|
||||
return;
|
||||
}
|
||||
// Focus here to ensure the terminal context key is set
|
||||
this._xterm.focus();
|
||||
this._xterm.selectAll();
|
||||
@@ -778,6 +776,9 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
}
|
||||
|
||||
public notifyFindWidgetFocusChanged(isFocused: boolean): void {
|
||||
if (!this._xterm) {
|
||||
return;
|
||||
}
|
||||
const terminalFocused = !isFocused && (document.activeElement === this._xterm.textarea || document.activeElement === this._xterm.element);
|
||||
this._terminalFocusContextKey.set(terminalFocused);
|
||||
}
|
||||
@@ -785,18 +786,18 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
public dispose(immediate?: boolean): void {
|
||||
this._logService.trace(`terminalInstance#dispose (id: ${this.id})`);
|
||||
|
||||
lifecycle.dispose(this._windowsShellHelper);
|
||||
dispose(this._windowsShellHelper);
|
||||
this._windowsShellHelper = undefined;
|
||||
this._linkHandler = lifecycle.dispose(this._linkHandler);
|
||||
this._commandTracker = lifecycle.dispose(this._commandTracker);
|
||||
this._widgetManager = lifecycle.dispose(this._widgetManager);
|
||||
this._linkHandler = dispose(this._linkHandler);
|
||||
this._commandTrackerAddon = dispose(this._commandTrackerAddon);
|
||||
this._widgetManager = dispose(this._widgetManager);
|
||||
|
||||
if (this._xterm && this._xterm.element) {
|
||||
this._hadFocusOnExit = dom.hasClass(this._xterm.element, 'focus');
|
||||
}
|
||||
if (this._wrapperElement) {
|
||||
if ((<any>this._wrapperElement).xterm) {
|
||||
(<any>this._wrapperElement).xterm = null;
|
||||
if (this._wrapperElement.xterm) {
|
||||
this._wrapperElement.xterm = undefined;
|
||||
}
|
||||
if (this._wrapperElement.parentElement) {
|
||||
this._container.removeChild(this._wrapperElement);
|
||||
@@ -813,39 +814,29 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
this._pressAnyKeyToCloseListener = undefined;
|
||||
}
|
||||
|
||||
if (this._processManager) {
|
||||
this._processManager.dispose(immediate);
|
||||
} else {
|
||||
// In cases where there is no associated process (for example executing an extension callback task)
|
||||
// consumers still expect on onExit event to be fired. An example of this is terminating the extension callback
|
||||
// task.
|
||||
this._onExit.fire(0);
|
||||
}
|
||||
this._processManager.dispose(immediate);
|
||||
// Process manager dispose/shutdown doesn't fire process exit, trigger with undefined if it
|
||||
// hasn't happened yet
|
||||
this._onProcessExit(undefined);
|
||||
|
||||
if (!this._isDisposed) {
|
||||
this._isDisposed = true;
|
||||
this._onDisposed.fire(this);
|
||||
}
|
||||
this._disposables.dispose();
|
||||
}
|
||||
|
||||
public rendererExit(exitCode: number): void {
|
||||
// The use of this API is for cases where there is no backing process behind a terminal
|
||||
// instance (e.g. a custom execution task).
|
||||
if (!this.shellLaunchConfig.isRendererOnly) {
|
||||
throw new Error('rendererExit is only expected to be called on a renderer only terminal');
|
||||
}
|
||||
|
||||
return this._onProcessExit(exitCode);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public forceRedraw(): void {
|
||||
if (!this._xterm) {
|
||||
return;
|
||||
}
|
||||
if (this._configHelper.config.experimentalRefreshOnResume) {
|
||||
if (this._xterm.getOption('rendererType') !== 'dom') {
|
||||
this._xterm.setOption('rendererType', 'dom');
|
||||
// Do this asynchronously to clear our the texture atlas as all terminals will not
|
||||
// be using canvas
|
||||
setTimeout(() => this._xterm.setOption('rendererType', 'canvas'), 0);
|
||||
const xterm = this._xterm;
|
||||
setTimeout(() => xterm.setOption('rendererType', 'canvas'), 0);
|
||||
}
|
||||
}
|
||||
this._xterm.refresh(0, this._xterm.rows - 1);
|
||||
@@ -870,6 +861,9 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
}
|
||||
|
||||
public async paste(): Promise<void> {
|
||||
if (!this._xterm) {
|
||||
return;
|
||||
}
|
||||
this.focus();
|
||||
this._xterm._core._coreService.triggerDataEvent(await this._clipboardService.readText(), true);
|
||||
}
|
||||
@@ -880,10 +874,6 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
return;
|
||||
}
|
||||
this._xterm.write(text);
|
||||
if (this._shellLaunchConfig.isRendererOnly) {
|
||||
// Fire onData API in the extension host
|
||||
this._onData.fire(text);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -894,17 +884,8 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
text += '\r';
|
||||
}
|
||||
|
||||
if (this._shellLaunchConfig.isRendererOnly) {
|
||||
// If the terminal is a renderer only, fire the onInput ext API
|
||||
this._sendRendererInput(text);
|
||||
} else {
|
||||
// If the terminal has a process, send it to the process
|
||||
if (this._processManager) {
|
||||
this._processManager.ptyProcessReady.then(() => {
|
||||
this._processManager!.write(text);
|
||||
});
|
||||
}
|
||||
}
|
||||
// Send it to the process
|
||||
this._processManager.ptyProcessReady.then(() => this._processManager.write(text));
|
||||
}
|
||||
|
||||
public setVisible(visible: boolean): void {
|
||||
@@ -936,31 +917,45 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
}
|
||||
|
||||
public scrollDownLine(): void {
|
||||
this._xterm.scrollLines(1);
|
||||
if (this._xterm) {
|
||||
this._xterm.scrollLines(1);
|
||||
}
|
||||
}
|
||||
|
||||
public scrollDownPage(): void {
|
||||
this._xterm.scrollPages(1);
|
||||
if (this._xterm) {
|
||||
this._xterm.scrollPages(1);
|
||||
}
|
||||
}
|
||||
|
||||
public scrollToBottom(): void {
|
||||
this._xterm.scrollToBottom();
|
||||
if (this._xterm) {
|
||||
this._xterm.scrollToBottom();
|
||||
}
|
||||
}
|
||||
|
||||
public scrollUpLine(): void {
|
||||
this._xterm.scrollLines(-1);
|
||||
if (this._xterm) {
|
||||
this._xterm.scrollLines(-1);
|
||||
}
|
||||
}
|
||||
|
||||
public scrollUpPage(): void {
|
||||
this._xterm.scrollPages(-1);
|
||||
if (this._xterm) {
|
||||
this._xterm.scrollPages(-1);
|
||||
}
|
||||
}
|
||||
|
||||
public scrollToTop(): void {
|
||||
this._xterm.scrollToTop();
|
||||
if (this._xterm) {
|
||||
this._xterm.scrollToTop();
|
||||
}
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this._xterm.clear();
|
||||
if (this._xterm) {
|
||||
this._xterm.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private _refreshSelectionContextKey() {
|
||||
@@ -987,12 +982,12 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
|
||||
if (platform.isWindows) {
|
||||
this._processManager.ptyProcessReady.then(() => {
|
||||
if (this._processManager!.remoteAuthority) {
|
||||
if (this._processManager.remoteAuthority) {
|
||||
return;
|
||||
}
|
||||
this._xtermReadyPromise.then(() => {
|
||||
if (!this._isDisposed) {
|
||||
this._windowsShellHelper = this._terminalInstanceService.createWindowsShellHelper(this._processManager!.shellProcessId, this, this._xterm);
|
||||
this._xtermReadyPromise.then(xterm => {
|
||||
if (!this._isDisposed && this._processManager && this._processManager.shellProcessId) {
|
||||
this._windowsShellHelper = this._terminalInstanceService.createWindowsShellHelper(this._processManager.shellProcessId, this, xterm);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1001,7 +996,7 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
// Create the process asynchronously to allow the terminal's container
|
||||
// to be created so dimensions are accurate
|
||||
setTimeout(() => {
|
||||
this._processManager!.createProcess(this._shellLaunchConfig, this._cols, this._rows, this._isScreenReaderOptimized());
|
||||
this._processManager.createProcess(this._shellLaunchConfig, this._cols, this._rows, this._isScreenReaderOptimized());
|
||||
}, 0);
|
||||
}
|
||||
|
||||
@@ -1021,13 +1016,13 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
* through user action.
|
||||
*/
|
||||
private _onProcessExit(exitCode?: number): void {
|
||||
this._logService.debug(`Terminal process exit (id: ${this.id}) with code ${exitCode}`);
|
||||
|
||||
// Prevent dispose functions being triggered multiple times
|
||||
if (this._isExiting) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._logService.debug(`Terminal process exit (id: ${this.id}) with code ${exitCode}`);
|
||||
|
||||
this._isExiting = true;
|
||||
let exitCodeMessage: string | undefined;
|
||||
|
||||
@@ -1039,7 +1034,7 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
exitCodeMessage = nls.localize('terminal.integrated.exitedWithInvalidPathDirectory', 'The terminal shell path "{0}" is a directory', this._shellLaunchConfig.executable);
|
||||
} else if (exitCode === SHELL_CWD_INVALID_EXIT_CODE && this._shellLaunchConfig.cwd) {
|
||||
exitCodeMessage = nls.localize('terminal.integrated.exitedWithInvalidCWD', 'The terminal shell CWD "{0}" does not exist', this._shellLaunchConfig.cwd.toString());
|
||||
} else if (this._processManager && this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) {
|
||||
} else if (this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) {
|
||||
let args = '';
|
||||
if (typeof this._shellLaunchConfig.args === 'string') {
|
||||
args = ` ${this._shellLaunchConfig.args}`;
|
||||
@@ -1061,29 +1056,31 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
}
|
||||
}
|
||||
|
||||
this._logService.debug(`Terminal process exit (id: ${this.id})${this._processManager ? ' state ' + this._processManager.processState : ''}`);
|
||||
this._logService.debug(`Terminal process exit (id: ${this.id}) state ${this._processManager.processState}`);
|
||||
|
||||
// Only trigger wait on exit when the exit was *not* triggered by the
|
||||
// user (via the `workbench.action.terminal.kill` command).
|
||||
if (this._shellLaunchConfig.waitOnExit && (!this._processManager || this._processManager.processState !== ProcessState.KILLED_BY_USER)) {
|
||||
if (exitCodeMessage) {
|
||||
this._xterm.writeln(exitCodeMessage);
|
||||
}
|
||||
if (typeof this._shellLaunchConfig.waitOnExit === 'string') {
|
||||
let message = this._shellLaunchConfig.waitOnExit;
|
||||
// Bold the message and add an extra new line to make it stand out from the rest of the output
|
||||
message = `\r\n\x1b[1m${message}\x1b[0m`;
|
||||
this._xterm.writeln(message);
|
||||
}
|
||||
// Disable all input if the terminal is exiting and listen for next keypress
|
||||
this._xterm.setOption('disableStdin', true);
|
||||
if (this._xterm.textarea) {
|
||||
this._attachPressAnyKeyToCloseListener();
|
||||
}
|
||||
if (this._shellLaunchConfig.waitOnExit && this._processManager.processState !== ProcessState.KILLED_BY_USER) {
|
||||
this._xtermReadyPromise.then(xterm => {
|
||||
if (exitCodeMessage) {
|
||||
xterm.writeln(exitCodeMessage);
|
||||
}
|
||||
if (typeof this._shellLaunchConfig.waitOnExit === 'string') {
|
||||
let message = this._shellLaunchConfig.waitOnExit;
|
||||
// Bold the message and add an extra new line to make it stand out from the rest of the output
|
||||
message = `\r\n\x1b[1m${message}\x1b[0m`;
|
||||
xterm.writeln(message);
|
||||
}
|
||||
// Disable all input if the terminal is exiting and listen for next keypress
|
||||
xterm.setOption('disableStdin', true);
|
||||
if (xterm.textarea) {
|
||||
this._attachPressAnyKeyToCloseListener(xterm);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.dispose();
|
||||
if (exitCodeMessage) {
|
||||
if (this._processManager && this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) {
|
||||
if (this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) {
|
||||
this._notificationService.error(exitCodeMessage);
|
||||
} else {
|
||||
if (this._configHelper.config.showExitAlert) {
|
||||
@@ -1098,9 +1095,9 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
this._onExit.fire(exitCode || 0);
|
||||
}
|
||||
|
||||
private _attachPressAnyKeyToCloseListener() {
|
||||
private _attachPressAnyKeyToCloseListener(xterm: XTermTerminal) {
|
||||
if (!this._pressAnyKeyToCloseListener) {
|
||||
this._pressAnyKeyToCloseListener = dom.addDisposableListener(this._xterm.textarea, 'keypress', (event: KeyboardEvent) => {
|
||||
this._pressAnyKeyToCloseListener = dom.addDisposableListener(xterm.textarea, 'keypress', (event: KeyboardEvent) => {
|
||||
if (this._pressAnyKeyToCloseListener) {
|
||||
this._pressAnyKeyToCloseListener.dispose();
|
||||
this._pressAnyKeyToCloseListener = undefined;
|
||||
@@ -1119,59 +1116,47 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
}
|
||||
|
||||
// Kill and clear up the process, making the process manager ready for a new process
|
||||
if (this._processManager) {
|
||||
this._processManager.dispose();
|
||||
this._processManager = undefined;
|
||||
this._processManager.dispose();
|
||||
|
||||
if (this._xterm) {
|
||||
// Ensure new processes' output starts at start of new line
|
||||
this._xterm.write('\n\x1b[G');
|
||||
|
||||
// Print initialText if specified
|
||||
if (shell.initialText) {
|
||||
this._xterm.writeln(shell.initialText);
|
||||
}
|
||||
|
||||
// Clean up waitOnExit state
|
||||
if (this._isExiting && this._shellLaunchConfig.waitOnExit) {
|
||||
this._xterm.setOption('disableStdin', false);
|
||||
this._isExiting = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure new processes' output starts at start of new line
|
||||
this._xterm.write('\n\x1b[G');
|
||||
|
||||
// Print initialText if specified
|
||||
if (shell.initialText) {
|
||||
this._xterm.writeln(shell.initialText);
|
||||
}
|
||||
|
||||
const oldTitle = this._title;
|
||||
// Clean up waitOnExit state
|
||||
if (this._isExiting && this._shellLaunchConfig.waitOnExit) {
|
||||
this._xterm.setOption('disableStdin', false);
|
||||
this._isExiting = false;
|
||||
}
|
||||
// HACK: Force initialText to be non-falsy for reused terminals such that the
|
||||
// conptyInheritCursor flag is passed to the node-pty, this flag can cause a Window to hang
|
||||
// in Windows 10 1903 so we only want to use it when something is definitely written to the
|
||||
// terminal.
|
||||
shell.initialText = ' ';
|
||||
|
||||
// Set the new shell launch config
|
||||
this._shellLaunchConfig = shell; // Must be done before calling _createProcess()
|
||||
|
||||
// Launch the process unless this is only a renderer.
|
||||
// In the renderer only cases, we still need to set the title correctly.
|
||||
if (!this._shellLaunchConfig.isRendererOnly) {
|
||||
this._createProcess();
|
||||
} else if (this._shellLaunchConfig.name) {
|
||||
this.setTitle(this._shellLaunchConfig.name, false);
|
||||
}
|
||||
const oldTitle = this._title;
|
||||
this._createProcess();
|
||||
|
||||
if (oldTitle !== this._title) {
|
||||
this.setTitle(this._title, true);
|
||||
}
|
||||
|
||||
if (this._processManager) {
|
||||
// The "!" operator is required here because _processManager is set to undefiend earlier
|
||||
// and TS does not know that createProcess sets it.
|
||||
this._processManager!.onProcessData(data => this._onProcessData(data));
|
||||
}
|
||||
}
|
||||
|
||||
private _sendRendererInput(input: string): void {
|
||||
if (this._processManager) {
|
||||
throw new Error('onRendererInput attempted to be used on a regular terminal');
|
||||
}
|
||||
|
||||
// For terminal renderers onData fires on keystrokes and when sendText is called.
|
||||
this._onRendererInput.fire(input);
|
||||
this._processManager.onProcessData(data => this._onProcessData(data));
|
||||
}
|
||||
|
||||
private _onLineFeed(): void {
|
||||
const buffer = this._xterm.buffer;
|
||||
const buffer = this._xterm!.buffer;
|
||||
const newLine = buffer.getLine(buffer.baseY + buffer.cursorY);
|
||||
if (newLine && !newLine.isWrapped) {
|
||||
this._sendLineData(buffer, buffer.baseY + buffer.cursorY - 1);
|
||||
@@ -1179,7 +1164,7 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
}
|
||||
|
||||
private _onCursorMove(): void {
|
||||
const buffer = this._xterm.buffer;
|
||||
const buffer = this._xterm!.buffer;
|
||||
this._sendLineData(buffer, buffer.baseY + buffer.cursorY);
|
||||
}
|
||||
|
||||
@@ -1207,11 +1192,19 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
}
|
||||
}
|
||||
|
||||
private async _onSelectionChange(): Promise<void> {
|
||||
if (this._configurationService.getValue('terminal.integrated.copyOnSelection')) {
|
||||
if (this.hasSelection()) {
|
||||
await this.copySelection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@debounce(2000)
|
||||
private async _updateProcessCwd(): Promise<string> {
|
||||
// reset cwd if it has changed, so file based url paths can be resolved
|
||||
const cwd = await this.getCwd();
|
||||
if (cwd) {
|
||||
if (cwd && this._linkHandler) {
|
||||
this._linkHandler.processCwd = cwd;
|
||||
}
|
||||
return cwd;
|
||||
@@ -1234,14 +1227,14 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
const isEnabled = this._isScreenReaderOptimized();
|
||||
if (isEnabled) {
|
||||
this._navigationModeAddon = new NavigationModeAddon(this._terminalA11yTreeFocusContextKey);
|
||||
this._xterm.loadAddon(this._navigationModeAddon);
|
||||
this._xterm!.loadAddon(this._navigationModeAddon);
|
||||
} else {
|
||||
if (this._navigationModeAddon) {
|
||||
this._navigationModeAddon.dispose();
|
||||
this._navigationModeAddon = undefined;
|
||||
}
|
||||
}
|
||||
this._xterm.setOption('screenReaderMode', isEnabled);
|
||||
this._xterm!.setOption('screenReaderMode', isEnabled);
|
||||
}
|
||||
|
||||
private _setCursorBlink(blink: boolean): void {
|
||||
@@ -1352,9 +1345,7 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
}
|
||||
}
|
||||
|
||||
if (this._processManager) {
|
||||
this._processManager.ptyProcessReady.then(() => this._processManager!.setDimensions(cols, rows));
|
||||
}
|
||||
this._processManager.ptyProcessReady.then(() => this._processManager.setDimensions(cols, rows));
|
||||
}
|
||||
|
||||
public setTitle(title: string | undefined, eventFromProcess: boolean): void {
|
||||
@@ -1370,19 +1361,17 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
} else {
|
||||
// If the title has not been set by the API or the rename command, unregister the handler that
|
||||
// automatically updates the terminal name
|
||||
if (this._messageTitleDisposable) {
|
||||
lifecycle.dispose(this._messageTitleDisposable);
|
||||
lifecycle.dispose(this._windowsShellHelper);
|
||||
this._messageTitleDisposable = undefined;
|
||||
this._windowsShellHelper = undefined;
|
||||
}
|
||||
dispose(this._messageTitleDisposable);
|
||||
this._messageTitleDisposable = undefined;
|
||||
dispose(this._windowsShellHelper);
|
||||
this._windowsShellHelper = undefined;
|
||||
}
|
||||
const didTitleChange = title !== this._title;
|
||||
const oldTitle = this._title;
|
||||
this._title = title;
|
||||
if (didTitleChange) {
|
||||
if (!oldTitle) {
|
||||
if (this._titleReadyComplete) {
|
||||
this._titleReadyComplete(title);
|
||||
this._titleReadyComplete = undefined;
|
||||
}
|
||||
this._onTitleChanged.fire(this);
|
||||
}
|
||||
@@ -1440,25 +1429,21 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
};
|
||||
}
|
||||
|
||||
private _updateTheme(theme?: ITheme): void {
|
||||
this._xterm.setOption('theme', this._getXtermTheme(theme));
|
||||
private _updateTheme(xterm: XTermTerminal, theme?: ITheme): void {
|
||||
xterm.setOption('theme', this._getXtermTheme(theme));
|
||||
}
|
||||
|
||||
public toggleEscapeSequenceLogging(): void {
|
||||
this._xterm.setOption('logLevel', 'debug');
|
||||
public async toggleEscapeSequenceLogging(): Promise<void> {
|
||||
const xterm = await this._xtermReadyPromise;
|
||||
const isDebug = xterm.getOption('logLevel') === 'debug';
|
||||
xterm.setOption('logLevel', isDebug ? 'info' : 'debug');
|
||||
}
|
||||
|
||||
public getInitialCwd(): Promise<string> {
|
||||
if (!this._processManager) {
|
||||
return Promise.resolve('');
|
||||
}
|
||||
return this._processManager.getInitialCwd();
|
||||
}
|
||||
|
||||
public getCwd(): Promise<string> {
|
||||
if (!this._processManager) {
|
||||
return Promise.resolve('');
|
||||
}
|
||||
return this._processManager.getCwd();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user