mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-30 16:50:30 -04:00
Merge from vscode 1fbacccbc900bb59ba8a8f26a4128d48a1c97842
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
||||
@@ -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;
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user