未验证 提交 8a1632a5 编写于 作者: D Daniel Imms 提交者: GitHub

Merge pull request #90101 from microsoft/tyriar/75793

Improve promise usage in terminal
......@@ -309,15 +309,16 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
return true;
}
private _onRequestAvailableShells(request: IAvailableShellsRequest): void {
private async _onRequestAvailableShells(req: IAvailableShellsRequest): Promise<void> {
if (this._isPrimaryExtHost()) {
this._proxy.$requestAvailableShells().then(e => request(e));
req.callback(await this._proxy.$getAvailableShells());
}
}
private _onRequestDefaultShellAndArgs(request: IDefaultShellAndArgsRequest): void {
private async _onRequestDefaultShellAndArgs(req: IDefaultShellAndArgsRequest): Promise<void> {
if (this._isPrimaryExtHost()) {
this._proxy.$requestDefaultShellAndArgs(request.useAutomationShell).then(e => request.callback(e.shell, e.args));
const res = await this._proxy.$getDefaultShellAndArgs(req.useAutomationShell);
req.callback(res.shell, res.args);
}
}
......
......@@ -1288,8 +1288,8 @@ export interface ExtHostTerminalServiceShape {
$acceptProcessRequestCwd(id: number): void;
$acceptProcessRequestLatency(id: number): number;
$acceptWorkspacePermissionsChanged(isAllowed: boolean): void;
$requestAvailableShells(): Promise<IShellDefinitionDto[]>;
$requestDefaultShellAndArgs(useAutomationShell: boolean): Promise<IShellAndArgsDto>;
$getAvailableShells(): Promise<IShellDefinitionDto[]>;
$getDefaultShellAndArgs(useAutomationShell: boolean): Promise<IShellAndArgsDto>;
}
export interface ExtHostSCMShape {
......
......@@ -325,8 +325,8 @@ export abstract class BaseExtHostTerminalService implements IExtHostTerminalServ
public abstract getDefaultShell(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string;
public abstract getDefaultShellArgs(useAutomationShell: boolean, configProvider: ExtHostConfigProvider): string[] | string;
public abstract $spawnExtHostProcess(id: number, shellLaunchConfigDto: IShellLaunchConfigDto, activeWorkspaceRootUriComponents: UriComponents, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise<void>;
public abstract $requestAvailableShells(): Promise<IShellDefinitionDto[]>;
public abstract $requestDefaultShellAndArgs(useAutomationShell: boolean): Promise<IShellAndArgsDto>;
public abstract $getAvailableShells(): Promise<IShellDefinitionDto[]>;
public abstract $getDefaultShellAndArgs(useAutomationShell: boolean): Promise<IShellAndArgsDto>;
public abstract $acceptWorkspacePermissionsChanged(isAllowed: boolean): void;
public createExtensionTerminal(options: vscode.ExtensionTerminalOptions): vscode.Terminal {
......@@ -606,11 +606,11 @@ export class WorkerExtHostTerminalService extends BaseExtHostTerminalService {
throw new Error('Not implemented');
}
public $requestAvailableShells(): Promise<IShellDefinitionDto[]> {
public $getAvailableShells(): Promise<IShellDefinitionDto[]> {
throw new Error('Not implemented');
}
public async $requestDefaultShellAndArgs(useAutomationShell: boolean): Promise<IShellAndArgsDto> {
public async $getDefaultShellAndArgs(useAutomationShell: boolean): Promise<IShellAndArgsDto> {
throw new Error('Not implemented');
}
......
......@@ -179,7 +179,7 @@ export class ExtHostTask extends ExtHostTaskBase {
}
public $getDefaultShellAndArgs(): Promise<{ shell: string, args: string[] | string | undefined }> {
return this._terminalService.$requestDefaultShellAndArgs(true);
return this._terminalService.$getDefaultShellAndArgs(true);
}
public async $jsonTasksSupported(): Promise<boolean> {
......
......@@ -200,16 +200,16 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
this._setupExtHostProcessListeners(id, new TerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, enableConpty, this._logService));
}
public $requestAvailableShells(): Promise<IShellDefinitionDto[]> {
public $getAvailableShells(): Promise<IShellDefinitionDto[]> {
return detectAvailableShells();
}
public async $requestDefaultShellAndArgs(useAutomationShell: boolean): Promise<IShellAndArgsDto> {
public async $getDefaultShellAndArgs(useAutomationShell: boolean): Promise<IShellAndArgsDto> {
const configProvider = await this._extHostConfiguration.getConfigProvider();
return Promise.resolve({
return {
shell: this.getDefaultShell(useAutomationShell, configProvider),
args: this.getDefaultShellArgs(useAutomationShell, configProvider)
});
};
}
public $acceptWorkspacePermissionsChanged(isAllowed: boolean): void {
......
......@@ -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. */
......
......@@ -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);
}
}
......
......@@ -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);
}
}
......@@ -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');
}
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());
});
}
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;
}
if (!xterm.element || !xterm.textarea) {
throw new Error('xterm elements not set after open');
}
// 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;
}
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;
}
// If tab focus mode is on, tab is not passed to the terminal
if (TabFocus.getTabFocusMode() && event.keyCode === 9) {
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;
}
// 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;
}
// If tab focus mode is on, tab is not passed to the terminal
if (TabFocus.getTabFocusMode() && event.keyCode === 9) {
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();
});
}));
// 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;
}
// 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.
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);
}));
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);
}
listener.dispose();
});
}));
const neverMeasureRenderTime = this._storageService.getBoolean(NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, StorageScope.GLOBAL, false);
if (!neverMeasureRenderTime && this._configHelper.config.rendererType === 'auto') {
this._measureRenderTime();
// 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));
}
......@@ -872,8 +873,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 +885,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 +893,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 +1334,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 +1384,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) {
......
......@@ -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 {
......
......@@ -296,9 +296,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);
}
}
}));
......
......@@ -360,7 +360,7 @@ export interface IStartExtensionTerminalRequest {
}
export interface IAvailableShellsRequest {
(shells: IShellDefinition[]): void;
callback: (shells: IShellDefinition[]) => void;
}
export interface IDefaultShellAndArgsRequest {
......
......@@ -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);
});
......
......@@ -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;
}
// 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();
});
// Fire a new check for the shell when any key is pressed.
this._xterm.onKey(() => this._onCheckShell.fire(undefined));
// 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 {
......
......@@ -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[]> {
......
......@@ -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;
}
// 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
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();
}
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) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册