Merge from vscode 1fbacccbc900bb59ba8a8f26a4128d48a1c97842

This commit is contained in:
ADS Merger
2020-02-13 02:56:02 +00:00
parent 9af1f3b0eb
commit 73ea8b79b2
229 changed files with 3192 additions and 2103 deletions

View File

@@ -332,11 +332,6 @@ configurationRegistry.registerConfiguration({
type: 'boolean',
default: true
},
'terminal.integrated.experimentalRefreshOnResume': {
description: nls.localize('terminal.integrated.experimentalRefreshOnResume', "An experimental setting that will refresh the terminal renderer when the system is resumed."),
type: 'boolean',
default: false
},
'terminal.integrated.experimentalUseTitleEvent': {
description: nls.localize('terminal.integrated.experimentalUseTitleEvent', "An experimental setting that will use the terminal title event for the dropdown title. This setting will only apply to new terminals."),
type: 'boolean',

View File

@@ -392,12 +392,6 @@ export interface ITerminalInstance {
*/
sendText(text: string, addNewLine: boolean): void;
/**
* Write text directly to the terminal, skipping the process if it exists.
* @param text The text to write.
*/
write(text: string): void;
/** Scroll the terminal buffer down 1 line. */
scrollDownLine(): void;
/** Scroll the terminal buffer down 1 page. */

View File

@@ -38,28 +38,26 @@ import { Action2 } from 'vs/platform/actions/common/actions';
export const TERMINAL_PICKER_PREFIX = 'term ';
function getCwdForSplit(configHelper: ITerminalConfigHelper, instance: ITerminalInstance, folders?: IWorkspaceFolder[], commandService?: ICommandService): Promise<string | URI> {
async function getCwdForSplit(configHelper: ITerminalConfigHelper, instance: ITerminalInstance, folders?: IWorkspaceFolder[], commandService?: ICommandService): Promise<string | URI | undefined> {
switch (configHelper.config.splitCwd) {
case 'workspaceRoot':
let pathPromise: Promise<string | URI> = Promise.resolve('');
if (folders !== undefined && commandService !== undefined) {
if (folders.length === 1) {
pathPromise = Promise.resolve(folders[0].uri);
return folders[0].uri;
} else if (folders.length > 1) {
// Only choose a path when there's more than 1 folder
const options: IPickOptions<IQuickPickItem> = {
placeHolder: nls.localize('workbench.action.terminal.newWorkspacePlaceholder', "Select current working directory for new terminal")
};
pathPromise = commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID, [options]).then(workspace => {
if (!workspace) {
// Don't split the instance if the workspace picker was canceled
return undefined;
}
return Promise.resolve(workspace.uri);
});
const workspace = await commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID, [options]);
if (!workspace) {
// Don't split the instance if the workspace picker was canceled
return undefined;
}
return Promise.resolve(workspace.uri);
}
}
return pathPromise;
return '';
case 'initial':
return instance.getInitialCwd();
case 'inherited':
@@ -133,12 +131,13 @@ export class QuickKillTerminalAction extends Action {
super(id, label, 'terminal-action kill');
}
public run(event?: any): Promise<any> {
public async run(event?: any): Promise<any> {
const instance = this.terminalEntry.instance;
if (instance) {
instance.dispose(true);
}
return Promise.resolve(timeout(50)).then(result => this.quickOpenService.show(TERMINAL_PICKER_PREFIX, undefined));
await timeout(50);
return this.quickOpenService.show(TERMINAL_PICKER_PREFIX, undefined);
}
}
@@ -329,43 +328,35 @@ export class CreateNewTerminalAction extends Action {
super(id, label, 'terminal-action codicon-add');
}
public run(event?: any): Promise<any> {
public async run(event?: any): Promise<any> {
const folders = this.workspaceContextService.getWorkspace().folders;
if (event instanceof MouseEvent && (event.altKey || event.ctrlKey)) {
const activeInstance = this.terminalService.getActiveInstance();
if (activeInstance) {
return getCwdForSplit(this.terminalService.configHelper, activeInstance).then(cwd => {
this.terminalService.splitInstance(activeInstance, { cwd });
return Promise.resolve(null);
});
const cwd = await getCwdForSplit(this.terminalService.configHelper, activeInstance);
this.terminalService.splitInstance(activeInstance, { cwd });
return undefined;
}
}
let instancePromise: Promise<ITerminalInstance | null>;
let instance: ITerminalInstance | undefined;
if (folders.length <= 1) {
// Allow terminal service to handle the path when there is only a
// single root
instancePromise = Promise.resolve(this.terminalService.createTerminal(undefined));
instance = this.terminalService.createTerminal(undefined);
} else {
const options: IPickOptions<IQuickPickItem> = {
placeHolder: nls.localize('workbench.action.terminal.newWorkspacePlaceholder', "Select current working directory for new terminal")
};
instancePromise = this.commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID, [options]).then(workspace => {
if (!workspace) {
// Don't create the instance if the workspace picker was canceled
return null;
}
return this.terminalService.createTerminal({ cwd: workspace.uri });
});
}
return instancePromise.then(instance => {
if (!instance) {
return Promise.resolve(undefined);
const workspace = await this.commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID, [options]);
if (!workspace) {
// Don't create the instance if the workspace picker was canceled
return undefined;
}
this.terminalService.setActiveInstance(instance);
return this.terminalService.showPanel(true);
});
instance = this.terminalService.createTerminal({ cwd: workspace.uri });
}
this.terminalService.setActiveInstance(instance);
return this.terminalService.showPanel(true);
}
}
@@ -405,19 +396,17 @@ export class SplitTerminalAction extends Action {
super(id, label, 'terminal-action codicon-split-horizontal');
}
public run(event?: any): Promise<any> {
public async run(event?: any): Promise<any> {
const instance = this._terminalService.getActiveInstance();
if (!instance) {
return Promise.resolve(undefined);
}
return getCwdForSplit(this._terminalService.configHelper, instance, this.workspaceContextService.getWorkspace().folders, this.commandService).then(cwd => {
if (cwd || (cwd === '')) {
this._terminalService.splitInstance(instance, { cwd });
return this._terminalService.showPanel(true);
} else {
return undefined;
}
});
const cwd = await getCwdForSplit(this._terminalService.configHelper, instance, this.workspaceContextService.getWorkspace().folders, this.commandService);
if (cwd === undefined) {
return undefined;
}
this._terminalService.splitInstance(instance, { cwd });
return this._terminalService.showPanel(true);
}
}
@@ -432,15 +421,14 @@ export class SplitInActiveWorkspaceTerminalAction extends Action {
super(id, label);
}
public run(event?: any): Promise<any> {
public async run(event?: any): Promise<any> {
const instance = this._terminalService.getActiveInstance();
if (!instance) {
return Promise.resolve(undefined);
}
return getCwdForSplit(this._terminalService.configHelper, instance).then(cwd => {
this._terminalService.splitInstance(instance, { cwd });
return this._terminalService.showPanel(true);
});
const cwd = await getCwdForSplit(this._terminalService.configHelper, instance);
this._terminalService.splitInstance(instance, { cwd });
return this._terminalService.showPanel(true);
}
}
@@ -697,7 +685,7 @@ export class RunActiveFileInTerminalAction extends Action {
super(id, label);
}
public run(event?: any): Promise<any> {
public async run(event?: any): Promise<any> {
const instance = this.terminalService.getActiveOrCreateInstance();
if (!instance) {
return Promise.resolve(undefined);
@@ -712,10 +700,9 @@ export class RunActiveFileInTerminalAction extends Action {
return Promise.resolve(undefined);
}
return this.terminalService.preparePathForTerminalAsync(uri.fsPath, instance.shellLaunchConfig.executable, instance.title, instance.shellType).then(path => {
instance.sendText(path, true);
return this.terminalService.showPanel();
});
const path = await this.terminalService.preparePathForTerminalAsync(uri.fsPath, instance.shellLaunchConfig.executable, instance.title, instance.shellType);
instance.sendText(path, true);
return this.terminalService.showPanel();
}
}
@@ -1040,19 +1027,18 @@ export class RenameTerminalAction extends Action {
super(id, label);
}
public run(entry?: TerminalEntry): Promise<any> {
public async run(entry?: TerminalEntry): Promise<any> {
const terminalInstance = entry ? entry.instance : this.terminalService.getActiveInstance();
if (!terminalInstance) {
return Promise.resolve(undefined);
}
return this.quickInputService.input({
const name = await this.quickInputService.input({
value: terminalInstance.title,
prompt: nls.localize('workbench.action.terminal.rename.prompt', "Enter terminal name"),
}).then(name => {
if (name) {
terminalInstance.setTitle(name, TitleEventSource.Api);
}
});
if (name) {
terminalInstance.setTitle(name, TitleEventSource.Api);
}
}
}
export class RenameWithArgTerminalAction extends Action2 {
@@ -1168,12 +1154,11 @@ export class RenameTerminalQuickOpenAction extends RenameTerminalAction {
this.class = 'codicon codicon-gear';
}
public run(): Promise<any> {
super.run(this.terminal)
// This timeout is needed to make sure the previous quickOpen has time to close before we show the next one
.then(() => timeout(50))
.then(result => this.quickOpenService.show(TERMINAL_PICKER_PREFIX, undefined));
return Promise.resolve(null);
public async run(): Promise<any> {
await super.run(this.terminal);
// This timeout is needed to make sure the previous quickOpen has time to close before we show the next one
await timeout(50);
await this.quickOpenService.show(TERMINAL_PICKER_PREFIX, undefined);
}
}

View File

@@ -312,9 +312,8 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper {
}
}
private isExtensionInstalled(id: string): Promise<boolean> {
return this._extensionManagementService.getInstalled(ExtensionType.User).then(extensions => {
return extensions.some(e => e.identifier.id === id);
});
private async isExtensionInstalled(id: string): Promise<boolean> {
const extensions = await this._extensionManagementService.getInstalled(ExtensionType.User);
return extensions.some(e => e.identifier.id === id);
}
}

View File

@@ -560,136 +560,136 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._container.appendChild(this._wrapperElement);
}
public _attachToElement(container: HTMLElement): void {
this._xtermReadyPromise.then(xterm => {
if (this._wrapperElement) {
throw new Error('The terminal instance has already been attached to a container');
}
public async _attachToElement(container: HTMLElement): Promise<void> {
const xterm = await this._xtermReadyPromise;
this._container = container;
this._wrapperElement = document.createElement('div');
dom.addClass(this._wrapperElement, 'terminal-wrapper');
this._xtermElement = document.createElement('div');
if (this._wrapperElement) {
throw new Error('The terminal instance has already been attached to a container');
}
// Attach the xterm object to the DOM, exposing it to the smoke tests
this._wrapperElement.xterm = this._xterm;
this._container = container;
this._wrapperElement = document.createElement('div');
dom.addClass(this._wrapperElement, 'terminal-wrapper');
this._xtermElement = document.createElement('div');
this._wrapperElement.appendChild(this._xtermElement);
this._container.appendChild(this._wrapperElement);
xterm.open(this._xtermElement);
if (this._configHelper.config.rendererType === 'experimentalWebgl') {
this._terminalInstanceService.getXtermWebglConstructor().then(Addon => {
xterm.loadAddon(new Addon());
});
}
// Attach the xterm object to the DOM, exposing it to the smoke tests
this._wrapperElement.xterm = this._xterm;
if (!xterm.element || !xterm.textarea) {
throw new Error('xterm elements not set after open');
}
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;
}
// Skip processing by xterm.js of keyboard events that resolve to commands described
// within commandsToSkipShell
const standardKeyboardEvent = new StandardKeyboardEvent(event);
const resolveResult = this._keybindingService.softDispatch(standardKeyboardEvent, standardKeyboardEvent.target);
// Respect chords if the allowChords setting is set and it's not Escape. Escape is
// handled specially for Zen Mode's Escape, Escape chord, plus it's important in
// terminals generally
const allowChords = resolveResult && resolveResult.enterChord && this._configHelper.config.allowChords && event.key !== 'Escape';
if (allowChords || resolveResult && this._skipTerminalCommands.some(k => k === resolveResult.commandId)) {
event.preventDefault();
return false;
}
// If tab focus mode is on, tab is not passed to the terminal
if (TabFocus.getTabFocusMode() && event.keyCode === 9) {
return false;
}
// Always have alt+F4 skip the terminal on Windows and allow it to be handled by the
// system
if (platform.isWindows && event.altKey && event.key === 'F4' && !event.ctrlKey) {
return false;
}
return true;
this._wrapperElement.appendChild(this._xtermElement);
this._container.appendChild(this._wrapperElement);
xterm.open(this._xtermElement);
if (this._configHelper.config.rendererType === 'experimentalWebgl') {
this._terminalInstanceService.getXtermWebglConstructor().then(Addon => {
xterm.loadAddon(new Addon());
});
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', () => {
// Delay with a setTimeout to allow the mouseup to propagate through the DOM
// before evaluating the new selection state.
setTimeout(() => this._refreshSelectionContextKey(), 0);
listener.dispose();
});
}));
}
// xterm.js currently drops selection on keyup as we need to handle this case.
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);
}));
if (!xterm.element || !xterm.textarea) {
throw new Error('xterm elements not set after open');
}
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._register(dom.addDisposableListener(focusTrap, 'focus', () => {
let currentElement = focusTrap;
while (!dom.hasClass(currentElement, 'part')) {
currentElement = currentElement.parentElement!;
}
const hidePanelElement = <HTMLElement>currentElement.querySelector('.hide-panel-action');
hidePanelElement.focus();
}));
xtermHelper.insertBefore(focusTrap, xterm.textarea);
this._register(dom.addDisposableListener(xterm.textarea, 'focus', () => {
this._terminalFocusContextKey.set(true);
this._onFocused.fire(this);
}));
this._register(dom.addDisposableListener(xterm.textarea, 'blur', () => {
this._terminalFocusContextKey.reset();
this._refreshSelectionContextKey();
}));
this._register(dom.addDisposableListener(xterm.element, 'focus', () => {
this._terminalFocusContextKey.set(true);
}));
this._register(dom.addDisposableListener(xterm.element, 'blur', () => {
this._terminalFocusContextKey.reset();
this._refreshSelectionContextKey();
}));
const widgetManager = new TerminalWidgetManager(this._wrapperElement, this._openerService);
this._widgetManager = widgetManager;
this._processManager.onProcessReady(() => this._linkHandler?.setWidgetManager(widgetManager));
const computedStyle = window.getComputedStyle(this._container);
const width = parseInt(computedStyle.getPropertyValue('width').replace('px', ''), 10);
const height = parseInt(computedStyle.getPropertyValue('height').replace('px', ''), 10);
this.layout(new dom.Dimension(width, height));
this.setVisible(this._isVisible);
this.updateConfig();
// If IShellLaunchConfig.waitOnExit was true and the process finished before the terminal
// panel was initialized.
if (xterm.getOption('disableStdin')) {
this._attachPressAnyKeyToCloseListener(xterm);
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;
}
const neverMeasureRenderTime = this._storageService.getBoolean(NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, StorageScope.GLOBAL, false);
if (!neverMeasureRenderTime && this._configHelper.config.rendererType === 'auto') {
this._measureRenderTime();
// Skip processing by xterm.js of keyboard events that resolve to commands described
// within commandsToSkipShell
const standardKeyboardEvent = new StandardKeyboardEvent(event);
const resolveResult = this._keybindingService.softDispatch(standardKeyboardEvent, standardKeyboardEvent.target);
// Respect chords if the allowChords setting is set and it's not Escape. Escape is
// handled specially for Zen Mode's Escape, Escape chord, plus it's important in
// terminals generally
const allowChords = resolveResult && resolveResult.enterChord && this._configHelper.config.allowChords && event.key !== 'Escape';
if (allowChords || resolveResult && this._skipTerminalCommands.some(k => k === resolveResult.commandId)) {
event.preventDefault();
return false;
}
// If tab focus mode is on, tab is not passed to the terminal
if (TabFocus.getTabFocusMode() && event.keyCode === 9) {
return false;
}
// Always have alt+F4 skip the terminal on Windows and allow it to be handled by the
// system
if (platform.isWindows && event.altKey && event.key === 'F4' && !event.ctrlKey) {
return false;
}
return true;
});
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', () => {
// Delay with a setTimeout to allow the mouseup to propagate through the DOM
// before evaluating the new selection state.
setTimeout(() => this._refreshSelectionContextKey(), 0);
listener.dispose();
});
}));
// xterm.js currently drops selection on keyup as we need to handle this case.
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>xterm.element.querySelector('.xterm-helpers');
const focusTrap: HTMLElement = document.createElement('div');
focusTrap.setAttribute('tabindex', '0');
dom.addClass(focusTrap, 'focus-trap');
this._register(dom.addDisposableListener(focusTrap, 'focus', () => {
let currentElement = focusTrap;
while (!dom.hasClass(currentElement, 'part')) {
currentElement = currentElement.parentElement!;
}
const hidePanelElement = <HTMLElement>currentElement.querySelector('.hide-panel-action');
hidePanelElement.focus();
}));
xtermHelper.insertBefore(focusTrap, xterm.textarea);
this._register(dom.addDisposableListener(xterm.textarea, 'focus', () => {
this._terminalFocusContextKey.set(true);
this._onFocused.fire(this);
}));
this._register(dom.addDisposableListener(xterm.textarea, 'blur', () => {
this._terminalFocusContextKey.reset();
this._refreshSelectionContextKey();
}));
this._register(dom.addDisposableListener(xterm.element, 'focus', () => {
this._terminalFocusContextKey.set(true);
}));
this._register(dom.addDisposableListener(xterm.element, 'blur', () => {
this._terminalFocusContextKey.reset();
this._refreshSelectionContextKey();
}));
const widgetManager = new TerminalWidgetManager(this._wrapperElement, this._openerService);
this._widgetManager = widgetManager;
this._processManager.onProcessReady(() => this._linkHandler?.setWidgetManager(widgetManager));
const computedStyle = window.getComputedStyle(this._container);
const width = parseInt(computedStyle.getPropertyValue('width').replace('px', ''), 10);
const height = parseInt(computedStyle.getPropertyValue('height').replace('px', ''), 10);
this.layout(new dom.Dimension(width, height));
this.setVisible(this._isVisible);
this.updateConfig();
// If IShellLaunchConfig.waitOnExit was true and the process finished before the terminal
// panel was initialized.
if (xterm.getOption('disableStdin')) {
this._attachPressAnyKeyToCloseListener(xterm);
}
const neverMeasureRenderTime = this._storageService.getBoolean(NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, StorageScope.GLOBAL, false);
if (!neverMeasureRenderTime && this._configHelper.config.rendererType === 'auto') {
this._measureRenderTime();
}
}
private async _measureRenderTime(): Promise<void> {
@@ -744,6 +744,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
public deregisterLinkMatcher(linkMatcherId: number): void {
// TODO: Move this into TerminalLinkHandler to avoid the promise check
this._xtermReadyPromise.then(xterm => xterm.deregisterLinkMatcher(linkMatcherId));
}
@@ -846,15 +847,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
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
const xterm = this._xterm;
setTimeout(() => xterm.setOption('rendererType', 'canvas'), 0);
}
}
this._xterm.refresh(0, this._xterm.rows - 1);
}
@@ -872,8 +864,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
}
public focusWhenReady(force?: boolean): Promise<void> {
return this._xtermReadyPromise.then(() => this.focus(force));
public async focusWhenReady(force?: boolean): Promise<void> {
await this._xtermReadyPromise;
this.focus(force);
}
public async paste(): Promise<void> {
@@ -883,17 +876,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this.focus();
this._xterm.paste(await this._clipboardService.readText());
}
public write(text: string): void {
this._xtermReadyPromise.then(() => {
if (!this._xterm) {
return;
}
this._xterm.write(text);
});
}
public sendText(text: string, addNewLine: boolean): void {
public async sendText(text: string, addNewLine: boolean): Promise<void> {
// Normalize line endings to 'enter' press.
text = text.replace(TerminalInstance.EOL_REGEX, '\r');
if (addNewLine && text.substr(text.length - 1) !== '\r') {
@@ -901,7 +884,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
// Send it to the process
this._processManager.ptyProcessReady.then(() => this._processManager.write(text));
await this._processManager.ptyProcessReady;
this._processManager.write(text);
}
public setVisible(visible: boolean): void {
@@ -1341,7 +1325,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
@debounce(50)
private _resize(): void {
private async _resize(): Promise<void> {
let cols = this.cols;
let rows = this.rows;
@@ -1391,7 +1375,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
}
this._processManager.ptyProcessReady.then(() => this._processManager.setDimensions(cols, rows));
await this._processManager.ptyProcessReady;
this._processManager.setDimensions(cols, rows);
}
public setShellType(shellType: TerminalShellType) {

View File

@@ -14,7 +14,6 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { TerminalTab } from 'vs/workbench/contrib/terminal/browser/terminalTab';
import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminalInstance';
import { ITerminalService, ITerminalInstance, ITerminalTab, TerminalShellType, WindowsShellType } from 'vs/workbench/contrib/terminal/browser/terminal';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
@@ -29,6 +28,7 @@ import { isWindows, isMacintosh, OperatingSystem } from 'vs/base/common/platform
import { basename } from 'vs/base/common/path';
import { IOpenFileRequest } from 'vs/platform/windows/common/windows';
import { find } from 'vs/base/common/arrays';
import { timeout } from 'vs/base/common/async';
import { IViewsService } from 'vs/workbench/common/views';
interface IExtHostReadyEntry {
@@ -97,7 +97,6 @@ export class TerminalService implements ITerminalService {
@IDialogService private _dialogService: IDialogService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IExtensionService private _extensionService: IExtensionService,
@IFileService private _fileService: IFileService,
@IRemoteAgentService private _remoteAgentService: IRemoteAgentService,
@IQuickInputService private _quickInputService: IQuickInputService,
@IConfigurationService private _configurationService: IConfigurationService,
@@ -110,7 +109,7 @@ export class TerminalService implements ITerminalService {
this._activeTabIndex = 0;
this._isShuttingDown = false;
this._findState = new FindReplaceState();
lifecycleService.onBeforeShutdown(event => event.veto(this._onBeforeShutdown()));
lifecycleService.onBeforeShutdown(async event => event.veto(await this._onBeforeShutdown()));
lifecycleService.onShutdown(() => this._onShutdown());
if (this._terminalNativeService) {
this._terminalNativeService.onOpenFileRequest(e => this._onOpenFileRequest(e));
@@ -143,15 +142,14 @@ export class TerminalService implements ITerminalService {
return activeInstance ? activeInstance : this.createTerminal(undefined);
}
public requestSpawnExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI | undefined, cols: number, rows: number, isWorkspaceShellAllowed: boolean): void {
this._extensionService.whenInstalledExtensionsRegistered().then(async () => {
// Wait for the remoteAuthority to be ready (and listening for events) before firing
// the event to spawn the ext host process
const conn = this._remoteAgentService.getConnection();
const remoteAuthority = conn ? conn.remoteAuthority : 'null';
await this._whenExtHostReady(remoteAuthority);
this._onInstanceRequestSpawnExtHostProcess.fire({ proxy, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, isWorkspaceShellAllowed });
});
public async requestSpawnExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI | undefined, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise<void> {
await this._extensionService.whenInstalledExtensionsRegistered();
// Wait for the remoteAuthority to be ready (and listening for events) before firing
// the event to spawn the ext host process
const conn = this._remoteAgentService.getConnection();
const remoteAuthority = conn ? conn.remoteAuthority : 'null';
await this._whenExtHostReady(remoteAuthority);
this._onInstanceRequestSpawnExtHostProcess.fire({ proxy, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, isWorkspaceShellAllowed });
}
public requestStartExtensionTerminal(proxy: ITerminalProcessExtHostProxy, cols: number, rows: number): void {
@@ -178,7 +176,7 @@ export class TerminalService implements ITerminalService {
this._extHostsReady[remoteAuthority] = { promise, resolve };
}
private _onBeforeShutdown(): boolean | Promise<boolean> {
private async _onBeforeShutdown(): Promise<boolean> {
if (this.terminalInstances.length === 0) {
// No terminal instances, don't veto
return false;
@@ -186,12 +184,11 @@ export class TerminalService implements ITerminalService {
if (this.configHelper.config.confirmOnExit) {
// veto if configured to show confirmation and the user choosed not to exit
return this._showTerminalCloseConfirmation().then(veto => {
if (!veto) {
this._isShuttingDown = true;
}
return veto;
});
const veto = await this._showTerminalCloseConfirmation();
if (!veto) {
this._isShuttingDown = true;
}
return veto;
}
this._isShuttingDown = true;
@@ -204,20 +201,19 @@ export class TerminalService implements ITerminalService {
this.terminalInstances.forEach(instance => instance.dispose(true));
}
private _onOpenFileRequest(request: IOpenFileRequest): void {
private async _onOpenFileRequest(request: IOpenFileRequest): Promise<void> {
// if the request to open files is coming in from the integrated terminal (identified though
// the termProgram variable) and we are instructed to wait for editors close, wait for the
// marker file to get deleted and then focus back to the integrated terminal.
if (request.termProgram === 'vscode' && request.filesToWait) {
if (request.termProgram === 'vscode' && request.filesToWait && this._terminalNativeService) {
const waitMarkerFileUri = URI.revive(request.filesToWait.waitMarkerFileUri);
this._terminalNativeService?.whenFileDeleted(waitMarkerFileUri).then(() => {
if (this.terminalInstances.length > 0) {
const terminal = this.getActiveInstance();
if (terminal) {
terminal.focus();
}
await this._terminalNativeService.whenFileDeleted(waitMarkerFileUri);
if (this.terminalInstances.length > 0) {
const terminal = this.getActiveInstance();
if (terminal) {
terminal.focus();
}
});
}
}
}
@@ -421,43 +417,20 @@ export class TerminalService implements ITerminalService {
return find(this._terminalTabs, tab => tab.terminalInstances.indexOf(instance) !== -1);
}
public showPanel(focus?: boolean): Promise<void> {
return new Promise<void>(async (complete) => {
const pane = this._viewsService.getActiveViewWithId(TERMINAL_VIEW_ID) as TerminalViewPane;
if (!pane) {
await this._panelService.openPanel(TERMINAL_VIEW_ID, focus);
if (focus) {
// Do the focus call asynchronously as going through the
// command palette will force editor focus
setTimeout(() => {
const instance = this.getActiveInstance();
if (instance) {
instance.focusWhenReady(true).then(() => complete(undefined));
} else {
complete(undefined);
}
}, 0);
} else {
complete(undefined);
}
} else {
if (focus) {
// Do the focus call asynchronously as going through the
// command palette will force editor focus
setTimeout(() => {
const instance = this.getActiveInstance();
if (instance) {
instance.focusWhenReady(true).then(() => complete(undefined));
} else {
complete(undefined);
}
}, 0);
} else {
complete(undefined);
}
public async showPanel(focus?: boolean): Promise<void> {
const pane = this._viewsService.getActiveViewWithId(TERMINAL_VIEW_ID) as TerminalViewPane;
if (!pane) {
await this._panelService.openPanel(TERMINAL_VIEW_ID, focus);
}
if (focus) {
// Do the focus call asynchronously as going through the
// command palette will force editor focus
await timeout(0);
const instance = this.getActiveInstance();
if (instance) {
await instance.focusWhenReady(true);
}
return undefined;
});
}
}
private _getIndexFromId(terminalId: number): number {
@@ -497,22 +470,6 @@ export class TerminalService implements ITerminalService {
return !res.confirmed;
}
protected _validateShellPaths(label: string, potentialPaths: string[]): Promise<[string, string] | null> {
if (potentialPaths.length === 0) {
return Promise.resolve(null);
}
const current = potentialPaths.shift();
if (current! === '') {
return this._validateShellPaths(label, potentialPaths);
}
return this._fileService.exists(URI.file(current!)).then(exists => {
if (!exists) {
return this._validateShellPaths(label, potentialPaths);
}
return [label, current] as [string, string];
});
}
public preparePathForTerminalAsync(originalPath: string, executable: string, title: string, shellType: TerminalShellType): Promise<string> {
return new Promise<string>(c => {
if (!executable) {
@@ -575,34 +532,31 @@ export class TerminalService implements ITerminalService {
});
}
public selectDefaultWindowsShell(): Promise<void> {
return this._detectWindowsShells().then(shells => {
const options: IPickOptions<IQuickPickItem> = {
placeHolder: nls.localize('terminal.integrated.chooseWindowsShell', "Select your preferred terminal shell, you can change this later in your settings")
};
const quickPickItems = shells.map((s): IQuickPickItem => {
return { label: s.label, description: s.path };
});
return this._quickInputService.pick(quickPickItems, options).then(async value => {
if (!value) {
return undefined;
}
const shell = value.description;
const env = await this._remoteAgentService.getEnvironment();
let platformKey: string;
if (env) {
platformKey = env.os === OperatingSystem.Windows ? 'windows' : (env.os === OperatingSystem.Macintosh ? 'osx' : 'linux');
} else {
platformKey = isWindows ? 'windows' : (isMacintosh ? 'osx' : 'linux');
}
await this._configurationService.updateValue(`terminal.integrated.shell.${platformKey}`, shell, ConfigurationTarget.USER).then(() => shell);
return Promise.resolve();
});
public async selectDefaultWindowsShell(): Promise<void> {
const shells = await this._detectWindowsShells();
const options: IPickOptions<IQuickPickItem> = {
placeHolder: nls.localize('terminal.integrated.chooseWindowsShell', "Select your preferred terminal shell, you can change this later in your settings")
};
const quickPickItems = shells.map((s): IQuickPickItem => {
return { label: s.label, description: s.path };
});
const value = await this._quickInputService.pick(quickPickItems, options);
if (!value) {
return undefined;
}
const shell = value.description;
const env = await this._remoteAgentService.getEnvironment();
let platformKey: string;
if (env) {
platformKey = env.os === OperatingSystem.Windows ? 'windows' : (env.os === OperatingSystem.Macintosh ? 'osx' : 'linux');
} else {
platformKey = isWindows ? 'windows' : (isMacintosh ? 'osx' : 'linux');
}
await this._configurationService.updateValue(`terminal.integrated.shell.${platformKey}`, shell, ConfigurationTarget.USER);
}
private _detectWindowsShells(): Promise<IShellDefinition[]> {
return new Promise(r => this._onRequestAvailableShells.fire(r));
return new Promise(r => this._onRequestAvailableShells.fire({ callback: r }));
}
@@ -655,12 +609,11 @@ export class TerminalService implements ITerminalService {
this._onInstancesChanged.fire();
}
public focusFindWidget(): Promise<void> {
return this.showPanel(false).then(() => {
const pane = this._viewsService.getActiveViewWithId(TERMINAL_VIEW_ID) as TerminalViewPane;
pane.focusFindWidget();
this._findWidgetVisible.set(true);
});
public async focusFindWidget(): Promise<void> {
await this.showPanel(false);
const pane = this._viewsService.getActiveViewWithId(TERMINAL_VIEW_ID) as TerminalViewPane;
pane.focusFindWidget();
this._findWidgetVisible.set(true);
}
public hideFindWidget(): void {

View File

@@ -28,6 +28,7 @@ import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/vie
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IViewDescriptorService } from 'vs/workbench/common/views';
import { IOpenerService } from 'vs/platform/opener/common/opener';
const FIND_FOCUS_CLASS = 'find-focused';
@@ -53,9 +54,10 @@ export class TerminalViewPane extends ViewPane {
@IThemeService protected readonly themeService: IThemeService,
@ITelemetryService telemetryService: ITelemetryService,
@INotificationService private readonly _notificationService: INotificationService,
@IStorageService storageService: IStorageService
@IStorageService storageService: IStorageService,
@IOpenerService openerService: IOpenerService,
) {
super(options, keybindingService, _contextMenuService, configurationService, contextKeyService, viewDescriptorService, _instantiationService);
super(options, keybindingService, _contextMenuService, configurationService, contextKeyService, viewDescriptorService, _instantiationService, openerService, themeService);
}
protected renderBody(container: HTMLElement): void {
@@ -296,9 +298,8 @@ export class TerminalViewPane extends ViewPane {
const terminal = this._terminalService.getActiveInstance();
if (terminal) {
return this._terminalService.preparePathForTerminalAsync(path, terminal.shellLaunchConfig.executable, terminal.title, terminal.shellType).then(preparedPath => {
terminal.sendText(preparedPath, false);
});
const preparedPath = await this._terminalService.preparePathForTerminalAsync(path, terminal.shellLaunchConfig.executable, terminal.title, terminal.shellType);
terminal.sendText(preparedPath, false);
}
}
}));

View File

@@ -119,7 +119,6 @@ export interface ITerminalConfiguration {
showExitAlert: boolean;
splitCwd: 'workspaceRoot' | 'initial' | 'inherited';
windowsEnableConpty: boolean;
experimentalRefreshOnResume: boolean;
experimentalUseTitleEvent: boolean;
enableFileLinks: boolean;
unicodeVersion: '6' | '11';
@@ -360,7 +359,7 @@ export interface IStartExtensionTerminalRequest {
}
export interface IAvailableShellsRequest {
(shells: IShellDefinition[]): void;
callback: (shells: IShellDefinition[]) => void;
}
export interface IDefaultShellAndArgsRequest {

View File

@@ -44,17 +44,16 @@ export class TerminalNativeService implements ITerminalNativeService {
// Complete when wait marker file is deleted
return new Promise<void>(resolve => {
let running = false;
const interval = setInterval(() => {
const interval = setInterval(async () => {
if (!running) {
running = true;
this._fileService.exists(path).then(exists => {
running = false;
const exists = await this._fileService.exists(path);
running = false;
if (!exists) {
clearInterval(interval);
resolve(undefined);
}
});
if (!exists) {
clearInterval(interval);
resolve(undefined);
}
}
}, 1000);
});

View File

@@ -10,6 +10,7 @@ import { Terminal as XTermTerminal } from 'xterm';
import * as WindowsProcessTreeType from 'windows-process-tree';
import { Disposable } from 'vs/base/common/lifecycle';
import { ITerminalInstance, TerminalShellType, WindowsShellType } from 'vs/workbench/contrib/terminal/browser/terminal';
import { timeout } from 'vs/base/common/async';
const SHELL_EXECUTABLES = [
'cmd.exe',
@@ -46,36 +47,40 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe
this._isDisposed = false;
(import('windows-process-tree')).then(mod => {
if (this._isDisposed) {
return;
}
this._startMonitoringShell();
}
windowsProcessTree = mod;
// The debounce is necessary to prevent multiple processes from spawning when
// the enter key or output is spammed
Event.debounce(this._onCheckShell.event, (l, e) => e, 150, true)(() => {
setTimeout(() => {
this.checkShell();
}, 50);
});
private async _startMonitoringShell(): Promise<void> {
if (!windowsProcessTree) {
windowsProcessTree = await import('windows-process-tree');
}
// We want to fire a new check for the shell on a linefeed, but only
// when parsing has finished which is indicated by the cursormove event.
// If this is done on every linefeed, parsing ends up taking
// significantly longer due to resetting timers. Note that this is
// private API.
this._xterm.onLineFeed(() => this._newLineFeed = true);
this._xterm.onCursorMove(() => {
if (this._newLineFeed) {
this._onCheckShell.fire(undefined);
this._newLineFeed = false;
}
});
if (this._isDisposed) {
return;
}
// Fire a new check for the shell when any key is pressed.
this._xterm.onKey(() => this._onCheckShell.fire(undefined));
// The debounce is necessary to prevent multiple processes from spawning when
// the enter key or output is spammed
Event.debounce(this._onCheckShell.event, (l, e) => e, 150, true)(async () => {
await timeout(50);
this.checkShell();
});
// We want to fire a new check for the shell on a linefeed, but only
// when parsing has finished which is indicated by the cursormove event.
// If this is done on every linefeed, parsing ends up taking
// significantly longer due to resetting timers. Note that this is
// private API.
this._xterm.onLineFeed(() => this._newLineFeed = true);
this._xterm.onCursorMove(() => {
if (this._newLineFeed) {
this._onCheckShell.fire(undefined);
this._newLineFeed = false;
}
});
// Fire a new check for the shell when any key is pressed.
this._xterm.onKey(() => this._onCheckShell.fire(undefined));
}
private checkShell(): void {

View File

@@ -64,18 +64,17 @@ function getSystemShellWindows(): string {
let detectedDistro = LinuxDistro.Unknown;
if (platform.isLinux) {
const file = '/etc/os-release';
fileExists(file).then(exists => {
fileExists(file).then(async exists => {
if (!exists) {
return;
}
readFile(file).then(b => {
const contents = b.toString();
if (/NAME="?Fedora"?/.test(contents)) {
detectedDistro = LinuxDistro.Fedora;
} else if (/NAME="?Ubuntu"?/.test(contents)) {
detectedDistro = LinuxDistro.Ubuntu;
}
});
const buffer = await readFile(file);
const contents = buffer.toString();
if (/NAME="?Fedora"?/.test(contents)) {
detectedDistro = LinuxDistro.Fedora;
} else if (/NAME="?Ubuntu"?/.test(contents)) {
detectedDistro = LinuxDistro.Ubuntu;
}
});
}
@@ -128,8 +127,8 @@ async function detectAvailableWindowsShells(): Promise<IShellDefinition[]> {
};
const promises: PromiseLike<IShellDefinition | undefined>[] = [];
Object.keys(expectedLocations).forEach(key => promises.push(validateShellPaths(key, expectedLocations[key])));
return Promise.all(promises).then(coalesce);
const shells = await Promise.all(promises);
return coalesce(shells);
}
async function detectAvailableUnixShells(): Promise<IShellDefinition[]> {

View File

@@ -70,6 +70,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
conptyInheritCursor: useConpty && !!shellLaunchConfig.initialText
};
// TODO: Pull verification out into its own function
const cwdVerification = stat(cwd).then(async stat => {
if (!stat.isDirectory()) {
return Promise.reject(SHELL_CWD_INVALID_EXIT_CODE);
@@ -178,26 +179,25 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
this._closeTimeout = setTimeout(() => this._kill(), 250);
}
private _kill(): void {
private async _kill(): Promise<void> {
// Wait to kill to process until the start up code has run. This prevents us from firing a process exit before a
// process start.
this._processStartupComplete!.then(() => {
if (this._isDisposed) {
return;
await this._processStartupComplete;
if (this._isDisposed) {
return;
}
// Attempt to kill the pty, it may have already been killed at this
// point but we want to make sure
try {
if (this._ptyProcess) {
this._logService.trace('IPty#kill');
this._ptyProcess.kill();
}
// Attempt to kill the pty, it may have already been killed at this
// point but we want to make sure
try {
if (this._ptyProcess) {
this._logService.trace('IPty#kill');
this._ptyProcess.kill();
}
} catch (ex) {
// Swallow, the pty has already been killed
}
this._onProcessExit.fire(this._exitCode || 0);
this.dispose();
});
} catch (ex) {
// Swallow, the pty has already been killed
}
this._onProcessExit.fire(this._exitCode || 0);
this.dispose();
}
private _sendProcessId(ptyProcess: pty.IPty) {