mirror of
https://github.com/microsoft/vscode.git
synced 2026-09-21 03:36:14 +01:00
Merge pull request #30732 from Microsoft/amqi/update-window-shellname
Rename terminal instance on enter in Windows
This commit is contained in:
@@ -361,5 +361,5 @@ export interface ITerminalInstance {
|
||||
/**
|
||||
* Sets the title of the terminal instance.
|
||||
*/
|
||||
setTitle(title: string): void;
|
||||
setTitle(title: string, eventFromProcess: boolean): void;
|
||||
}
|
||||
|
||||
@@ -622,7 +622,7 @@ export class RenameTerminalAction extends Action {
|
||||
prompt: nls.localize('workbench.action.terminal.rename.prompt', "Enter terminal name"),
|
||||
}).then(name => {
|
||||
if (name) {
|
||||
terminalInstance.setTitle(name);
|
||||
terminalInstance.setTitle(name, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import * as platform from 'vs/base/common/platform';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import Uri from 'vs/base/common/uri';
|
||||
import { WindowsShellHelper } from 'vs/workbench/parts/terminal/electron-browser/windowsShellHelper';
|
||||
import XTermTerminal = require('xterm');
|
||||
import { Dimension } from 'vs/base/browser/builder';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
@@ -84,6 +85,7 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
private _messageTitleListener: (message: { type: string, content: string }) => void;
|
||||
private _preLaunchInputQueue: string;
|
||||
private _initialCwd: string;
|
||||
private _windowsShellHelper: WindowsShellHelper;
|
||||
|
||||
private _widgetManager: TerminalWidgetManager;
|
||||
private _linkHandler: TerminalLinkHandler;
|
||||
@@ -138,6 +140,12 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
this._createProcess(this._shellLaunchConfig);
|
||||
this._createXterm();
|
||||
|
||||
if (platform.isWindows) {
|
||||
this._processReady.then(() => {
|
||||
this._windowsShellHelper = new WindowsShellHelper(this._processId, this._shellLaunchConfig.executable);
|
||||
});
|
||||
}
|
||||
|
||||
// Only attach xterm.js to the DOM if the terminal panel has been opened before.
|
||||
if (_container) {
|
||||
this.attachToElement(_container);
|
||||
@@ -264,6 +272,13 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
if (TabFocus.getTabFocusMode() && event.keyCode === 9) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Windows does not get a process title event from terminalProcess so we check the name on enter
|
||||
// messageTitleListener is falsy when the API/user renames the terminal so we don't override it
|
||||
if (platform.isWindows && event.keyCode === 13 /* ENTER */ && this._messageTitleListener) {
|
||||
this._windowsShellHelper.getShellName().then(title => this.setTitle(title, true));
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
this._instanceDisposables.push(dom.addDisposableListener(this._xterm.element, 'mouseup', (event: KeyboardEvent) => {
|
||||
@@ -519,17 +534,17 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
const platformKey = platform.isWindows ? 'windows' : platform.isMacintosh ? 'osx' : 'linux';
|
||||
const envFromConfig = { ...process.env, ...this._configHelper.config.env[platformKey] };
|
||||
const env = TerminalInstance.createTerminalEnv(envFromConfig, shell, this._initialCwd, locale, this._cols, this._rows);
|
||||
this._title = shell.name || '';
|
||||
this._process = cp.fork(Uri.parse(require.toUrl('bootstrap')).fsPath, ['--type=terminal'], {
|
||||
env,
|
||||
cwd: Uri.parse(path.dirname(require.toUrl('../node/terminalProcess'))).fsPath
|
||||
});
|
||||
if (!shell.name) {
|
||||
if (shell.name) {
|
||||
this.setTitle(shell.name, false);
|
||||
} else {
|
||||
// Only listen for process title changes when a name is not provided
|
||||
this._messageTitleListener = (message) => {
|
||||
if (message.type === 'title') {
|
||||
this._title = message.content ? message.content : '';
|
||||
this._onTitleChanged.fire(this._title);
|
||||
this.setTitle(message.content ? message.content : '', true);
|
||||
}
|
||||
};
|
||||
this._process.on('message', this._messageTitleListener);
|
||||
@@ -652,7 +667,7 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
const oldTitle = this._title;
|
||||
this._createProcess(shell);
|
||||
if (oldTitle !== this._title) {
|
||||
this._onTitleChanged.fire(this._title);
|
||||
this.setTitle(this._title, true);
|
||||
}
|
||||
this._process.on('message', (message) => this._sendPtyDataToXterm(message));
|
||||
|
||||
@@ -829,19 +844,25 @@ export class TerminalInstance implements ITerminalInstance {
|
||||
this._terminalProcessFactory = factory;
|
||||
}
|
||||
|
||||
public setTitle(title: string): void {
|
||||
public setTitle(title: string, eventFromProcess: boolean): void {
|
||||
if (eventFromProcess) {
|
||||
if (platform.isWindows) {
|
||||
// Remove the .exe extension
|
||||
title = path.basename(title.split('.exe')[0]);
|
||||
}
|
||||
} else {
|
||||
// If the title has not been set by the API or the rename command, unregister the handler that
|
||||
// automatically updates the terminal name
|
||||
if (this._process && this._messageTitleListener) {
|
||||
this._process.removeListener('message', this._messageTitleListener);
|
||||
this._messageTitleListener = null;
|
||||
}
|
||||
}
|
||||
const didTitleChange = title !== this._title;
|
||||
this._title = title;
|
||||
if (didTitleChange) {
|
||||
this._onTitleChanged.fire(title);
|
||||
}
|
||||
|
||||
// If the title was not set by the API, unregister the handler that
|
||||
// automatically updates the terminal name
|
||||
if (this._process && this._messageTitleListener) {
|
||||
this._process.removeListener('message', this._messageTitleListener);
|
||||
this._messageTitleListener = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -866,4 +887,4 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
||||
if (scrollbarSliderActiveBackgroundColor) {
|
||||
collector.addRule(`.monaco-workbench .panel.integrated-terminal .xterm .xterm-viewport::-webkit-scrollbar-thumb:active { background-color: ${scrollbarSliderActiveBackgroundColor}; }`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as cp from 'child_process';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import * as path from 'path';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Emitter, debounceEvent } from 'vs/base/common/event';
|
||||
|
||||
const SHELL_EXECUTABLES = ['cmd.exe', 'powershell.exe', 'bash.exe'];
|
||||
|
||||
export class WindowsShellHelper {
|
||||
private _childProcessIdStack: number[];
|
||||
private _onCheckWindowsShell: Emitter<string>;
|
||||
private _rootShellExecutable: string;
|
||||
private _rootProcessId: number;
|
||||
|
||||
public constructor(rootProcessId: number, rootShellExecutable: string) {
|
||||
this._childProcessIdStack = [];
|
||||
this._rootShellExecutable = rootShellExecutable;
|
||||
this._rootProcessId = rootProcessId;
|
||||
|
||||
if (!platform.isWindows) {
|
||||
throw new Error(`WindowsShellHelper cannot be instantiated on ${platform.platform}`);
|
||||
}
|
||||
|
||||
this._onCheckWindowsShell = new Emitter<string>();
|
||||
debounceEvent(this._onCheckWindowsShell.event, (l, e) => e, 100, true)(() => {
|
||||
this.getShellName();
|
||||
});
|
||||
}
|
||||
|
||||
private getChildProcessDetails(pid: number): TPromise<{ executable: string, pid: number }[]> {
|
||||
return new TPromise((resolve, reject) => {
|
||||
cp.execFile('wmic.exe', ['process', 'where', `parentProcessId=${pid}`, 'get', 'ExecutablePath,ProcessId'], (err, stdout, stderr) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else if (stderr.length > 0) {
|
||||
resolve([]); // No processes found
|
||||
} else {
|
||||
const childProcessLines = stdout.split('\n').slice(1).filter(str => !/^\s*$/.test(str));
|
||||
const childProcessDetails = childProcessLines.map(str => {
|
||||
const s = str.split(' ');
|
||||
return { executable: s[0], pid: Number(s[1]) };
|
||||
});
|
||||
resolve(childProcessDetails);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private refreshShellProcessTree(pid: number, parent: string): TPromise<string> {
|
||||
return this.getChildProcessDetails(pid).then(result => {
|
||||
// When we didn't find any child processes of the process
|
||||
if (result.length === 0) {
|
||||
// Case where we found a child process already and are checking further down the pid tree
|
||||
// We have reached the end here so we know that parent is the deepest first child of the tree
|
||||
if (parent) {
|
||||
return TPromise.as(parent);
|
||||
}
|
||||
// Case where we haven't found a child and only the root shell is left
|
||||
if (this._childProcessIdStack.length === 1) {
|
||||
return TPromise.as(this._rootShellExecutable);
|
||||
}
|
||||
// Otherwise, we go up the tree to find the next valid deepest child of the root
|
||||
this._childProcessIdStack.pop();
|
||||
return this.refreshShellProcessTree(this._childProcessIdStack[this._childProcessIdStack.length - 1], null);
|
||||
}
|
||||
// We only go one level deep when checking for children of processes other then shells
|
||||
if (SHELL_EXECUTABLES.indexOf(path.basename(result[0].executable)) === -1) {
|
||||
return TPromise.as(result[0].executable);
|
||||
}
|
||||
// Save the pid in the stack and keep looking for children of that child
|
||||
this._childProcessIdStack.push(result[0].pid);
|
||||
return this.refreshShellProcessTree(result[0].pid, result[0].executable);
|
||||
}, error => { return error; });
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the innermost shell executable running in the terminal
|
||||
*/
|
||||
public getShellName(): TPromise<string> {
|
||||
if (this._childProcessIdStack.length === 0) {
|
||||
this._childProcessIdStack.push(this._rootProcessId);
|
||||
}
|
||||
return new TPromise<string>((resolve) => {
|
||||
this.refreshShellProcessTree(this._childProcessIdStack[this._childProcessIdStack.length - 1], null).then(result => {
|
||||
resolve(result);
|
||||
}, error => { return error; });
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user