chore(vscode): update to 1.56.0

This commit is contained in:
Akash Satheesan
2021-04-30 20:25:17 +05:30
1749 changed files with 88014 additions and 43316 deletions

View File

@@ -5,8 +5,8 @@
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Event } from 'vs/base/common/event';
import { IProcessEnvironment } from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
import { IProcessEnvironment, OperatingSystem } from 'vs/base/common/platform';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IGetTerminalLayoutInfoArgs, IProcessDetails, IPtyHostProcessReplayEvent, ISetTerminalLayoutInfoArgs } from 'vs/platform/terminal/common/terminalProcess';
export enum WindowsShellType {
@@ -44,6 +44,7 @@ export interface IPtyHostAttachTarget {
workspaceId: string;
workspaceName: string;
isOrphan: boolean;
icon: string | undefined;
}
export type ITerminalsLayoutInfo = IRawTerminalsLayoutInfo<IPtyHostAttachTarget | null>;
@@ -93,9 +94,12 @@ export interface IOffProcessTerminalService {
onPtyHostRestart: Event<void>;
attachToProcess(id: number): Promise<ITerminalChildProcess | undefined>;
listProcesses(reduceGraceTime?: boolean): Promise<IProcessDetails[]>;
listProcesses(): Promise<IProcessDetails[]>;
getDefaultSystemShell(osOverride?: OperatingSystem): Promise<string>;
getShellEnvironment(): Promise<IProcessEnvironment>;
setTerminalLayoutInfo(layoutInfo?: ITerminalsLayoutInfoById): Promise<void>;
getTerminalLayoutInfo(): Promise<ITerminalsLayoutInfo | undefined>;
reduceConnectionGraceTime(): Promise<void>;
}
export const ILocalTerminalService = createDecorator<ILocalTerminalService>('localTerminalService');
@@ -111,7 +115,6 @@ export interface IPtyService {
readonly onPtyHostStart?: Event<void>;
readonly onPtyHostUnresponsive?: Event<void>;
readonly onPtyHostResponsive?: Event<void>;
readonly onProcessData: Event<{ id: number, event: IProcessDataEvent | string }>;
readonly onProcessExit: Event<{ id: number, event: number | undefined }>;
readonly onProcessReady: Event<{ id: number, event: { pid: number, cwd: string } }>;
@@ -142,10 +145,8 @@ export interface IPtyService {
/**
* Lists all orphaned processes, ie. those without a connected frontend.
* @param reduceGraceTime Whether to reduce the reconnection grace time for all orphaned
* terminals.
*/
listProcesses(reduceGraceTime: boolean): Promise<IProcessDetails[]>;
listProcesses(): Promise<IProcessDetails[]>;
start(id: number): Promise<ITerminalLaunchError | undefined>;
shutdown(id: number, immediate: boolean): Promise<void>;
@@ -155,11 +156,15 @@ export interface IPtyService {
getCwd(id: number): Promise<string>;
getLatency(id: number): Promise<number>;
acknowledgeDataEvent(id: number, charCount: number): Promise<void>;
processBinary(id: number, data: string): Promise<void>;
/** Confirm the process is _not_ an orphan. */
orphanQuestionReply(id: number): Promise<void>;
getDefaultSystemShell(osOverride?: OperatingSystem): Promise<string>;
getShellEnvironment(): Promise<IProcessEnvironment>;
setTerminalLayoutInfo(args: ISetTerminalLayoutInfoArgs): Promise<void>;
getTerminalLayoutInfo(args: IGetTerminalLayoutInfoArgs): Promise<ITerminalsLayoutInfo | undefined>;
reduceConnectionGraceTime(): Promise<void>;
}
export enum HeartbeatConstants {
@@ -196,6 +201,11 @@ export interface IShellLaunchConfig {
*/
name?: string;
/**
* An string to follow the name of the terminal with, indicating a special kind of terminal
*/
description?: string;
/**
* The shell executable (bash, cmd, etc.).
*/
@@ -238,9 +248,9 @@ export interface IShellLaunchConfig {
initialText?: string;
/**
* Whether an extension is controlling the terminal via a `vscode.Pseudoterminal`.
* Custom PTY/pseudoterminal process to use.
*/
isExtensionCustomPtyTerminal?: boolean;
customPtyImplementation?: (terminalId: number, cols: number, rows: number) => ITerminalChildProcess;
/**
* A UUID generated by the extension host process for terminals created on the extension host process.
@@ -250,7 +260,7 @@ export interface IShellLaunchConfig {
/**
* This is a terminal that attaches to an already running terminal.
*/
attachPersistentProcess?: { id: number; pid: number; title: string; cwd: string; };
attachPersistentProcess?: { id: number; pid: number; title: string; cwd: string; icon?: string; };
/**
* Whether the terminal process environment should be exactly as provided in
@@ -280,10 +290,25 @@ export interface IShellLaunchConfig {
* Whether this terminal was created by an extension.
*/
isExtensionOwnedTerminal?: boolean;
/**
* The codicon ID to use for this terminal. If not specified it will use the default fallback
* icon.
*/
icon?: string;
}
export interface IShellLaunchConfigDto {
name?: string;
executable?: string;
args?: string[] | string;
cwd?: string | UriComponents;
env?: ITerminalEnvironment;
hideFromUser?: boolean;
}
export interface ITerminalEnvironment {
[key: string]: string | null;
[key: string]: string | null | undefined;
}
export interface ITerminalLaunchError {
@@ -337,6 +362,7 @@ export interface ITerminalChildProcess {
*/
shutdown(immediate: boolean): void;
input(data: string): void;
processBinary(data: string): Promise<void>;
resize(cols: number, rows: number): void;
/**
@@ -388,7 +414,11 @@ export const enum FlowControlConstants {
export interface IProcessDataEvent {
data: string;
sync: boolean;
trackCommit: boolean;
/**
* When trackCommit is set, this will be set to a promise that resolves when the data is parsed.
*/
writePromise?: Promise<void>;
}
export interface ITerminalDimensions {
@@ -409,3 +439,5 @@ export interface ITerminalDimensionsOverride extends Readonly<ITerminalDimension
*/
forceExactSize?: boolean;
}
export type SafeConfigProvider = <T>(key: string) => T | undefined;

View File

@@ -7,15 +7,6 @@ import { UriComponents } from 'vs/base/common/uri';
import { IRawTerminalTabLayoutInfo, ITerminalEnvironment, ITerminalTabLayoutInfoById } from 'vs/platform/terminal/common/terminal';
import { ISerializableEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariable';
export interface IShellLaunchConfigDto {
name?: string;
executable?: string;
args?: string[] | string;
cwd?: string | UriComponents;
env?: { [key: string]: string | null; };
hideFromUser?: boolean;
}
export interface ISingleTerminalConfiguration<T> {
userValue: T | undefined;
value: T | undefined;
@@ -65,6 +56,7 @@ export interface IProcessDetails {
workspaceId: string;
workspaceName: string;
isOrphan: boolean;
icon: string | undefined;
}
export type ITerminalTabLayoutInfoDto = IRawTerminalTabLayoutInfo<IProcessDetails>;

View File

@@ -9,10 +9,11 @@ import { IPtyService, IProcessDataEvent, IShellLaunchConfig, ITerminalDimensions
import { Client } from 'vs/base/parts/ipc/node/ipc.cp';
import { FileAccess } from 'vs/base/common/network';
import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
import { IProcessEnvironment } from 'vs/base/common/platform';
import { IProcessEnvironment, OperatingSystem } from 'vs/base/common/platform';
import { Emitter } from 'vs/base/common/event';
import { LogLevelChannelClient } from 'vs/platform/log/common/logIpc';
import { IGetTerminalLayoutInfoArgs, IProcessDetails, IPtyHostProcessReplayEvent, ISetTerminalLayoutInfoArgs } from 'vs/platform/terminal/common/terminalProcess';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
enum Constants {
MaxRestarts = 5
@@ -36,6 +37,7 @@ export class PtyHostService extends Disposable implements IPtyService {
private _proxy: IPtyService;
private _restartCount = 0;
private _isResponsive = true;
private _isDisposed = false;
private _heartbeatFirstTimeout?: NodeJS.Timeout;
@@ -49,7 +51,6 @@ export class PtyHostService extends Disposable implements IPtyService {
readonly onPtyHostUnresponsive = this._onPtyHostUnresponsive.event;
private readonly _onPtyHostResponsive = this._register(new Emitter<void>());
readonly onPtyHostResponsive = this._onPtyHostResponsive.event;
private readonly _onProcessData = this._register(new Emitter<{ id: number, event: IProcessDataEvent | string }>());
readonly onProcessData = this._onProcessData.event;
private readonly _onProcessExit = this._register(new Emitter<{ id: number, event: number | undefined }>());
@@ -70,7 +71,8 @@ export class PtyHostService extends Disposable implements IPtyService {
readonly onProcessOrphanQuestion = this._onProcessOrphanQuestion.event;
constructor(
@ILogService private readonly _logService: ILogService
@ILogService private readonly _logService: ILogService,
@ITelemetryService private readonly _telemetryService: ITelemetryService
) {
super();
@@ -102,6 +104,10 @@ export class PtyHostService extends Disposable implements IPtyService {
// Handle exit
this._register(client.onDidProcessExit(e => {
/* __GDPR__
"ptyHost/exit" : {}
*/
this._telemetryService.publicLog('ptyHost/exit');
this._onPtyHostExit.fire(e.code);
if (!this._isDisposed) {
if (this._restartCount <= Constants.MaxRestarts) {
@@ -135,7 +141,7 @@ export class PtyHostService extends Disposable implements IPtyService {
return [client, proxy];
}
dispose() {
override dispose() {
this._isDisposed = true;
super.dispose();
}
@@ -153,10 +159,12 @@ export class PtyHostService extends Disposable implements IPtyService {
detachFromProcess(id: number): Promise<void> {
return this._proxy.detachFromProcess(id);
}
listProcesses(reduceGraceTime: boolean): Promise<IProcessDetails[]> {
return this._proxy.listProcesses(reduceGraceTime);
listProcesses(): Promise<IProcessDetails[]> {
return this._proxy.listProcesses();
}
reduceConnectionGraceTime(): Promise<void> {
return this._proxy.reduceConnectionGraceTime();
}
start(id: number): Promise<ITerminalLaunchError | undefined> {
return this._proxy.start(id);
}
@@ -166,6 +174,9 @@ export class PtyHostService extends Disposable implements IPtyService {
input(id: number, data: string): Promise<void> {
return this._proxy.input(id, data);
}
processBinary(id: number, data: string): Promise<void> {
return this._proxy.processBinary(id, data);
}
resize(id: number, cols: number, rows: number): Promise<void> {
return this._proxy.resize(id, cols, rows);
}
@@ -185,6 +196,13 @@ export class PtyHostService extends Disposable implements IPtyService {
return this._proxy.orphanQuestionReply(id);
}
getDefaultSystemShell(osOverride?: OperatingSystem): Promise<string> {
return this._proxy.getDefaultSystemShell(osOverride);
}
getShellEnvironment(): Promise<IProcessEnvironment> {
return this._proxy.getShellEnvironment();
}
setTerminalLayoutInfo(args: ISetTerminalLayoutInfoArgs): Promise<void> {
return this._proxy.setTerminalLayoutInfo(args);
}
@@ -193,6 +211,11 @@ export class PtyHostService extends Disposable implements IPtyService {
}
async restartPtyHost(): Promise<void> {
/* __GDPR__
"ptyHost/restart" : {}
*/
this._telemetryService.publicLog('ptyHost/restart');
this._isResponsive = true;
this._disposePtyHost();
[this._client, this._proxy] = this._startPtyHost();
}
@@ -207,6 +230,13 @@ export class PtyHostService extends Disposable implements IPtyService {
private _handleHeartbeat() {
this._clearHeartbeatTimeouts();
this._heartbeatFirstTimeout = setTimeout(() => this._handleHeartbeatFirstTimeout(), HeartbeatConstants.BeatInterval * HeartbeatConstants.FirstWaitMultiplier);
if (!this._isResponsive) {
/* __GDPR__
"ptyHost/responsive" : {}
*/
this._telemetryService.publicLog('ptyHost/responsive');
this._isResponsive = true;
}
this._onPtyHostResponsive.fire();
}
@@ -219,12 +249,23 @@ export class PtyHostService extends Disposable implements IPtyService {
private _handleHeartbeatSecondTimeout() {
this._logService.error(`No ptyHost heartbeat after ${(HeartbeatConstants.BeatInterval * HeartbeatConstants.FirstWaitMultiplier + HeartbeatConstants.BeatInterval * HeartbeatConstants.FirstWaitMultiplier) / 1000} seconds`);
this._heartbeatSecondTimeout = undefined;
if (this._isResponsive) {
/* __GDPR__
"ptyHost/responsive" : {}
*/
this._telemetryService.publicLog('ptyHost/unresponsive');
this._isResponsive = false;
}
this._onPtyHostUnresponsive.fire();
}
private _handleUnresponsiveCreateProcess() {
this._clearHeartbeatTimeouts();
this._logService.error(`No ptyHost response to createProcess after ${HeartbeatConstants.CreateProcessTimeout / 1000} seconds`);
/* __GDPR__
"ptyHost/responsive" : {}
*/
this._telemetryService.publicLog('ptyHost/responsive');
this._onPtyHostUnresponsive.fire();
}

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { IProcessEnvironment } from 'vs/base/common/platform';
import { IProcessEnvironment, OperatingSystem, OS } from 'vs/base/common/platform';
import { IPtyService, IProcessDataEvent, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, LocalReconnectConstants, ITerminalsLayoutInfo, IRawTerminalInstanceLayoutInfo, ITerminalTabLayoutInfoById, ITerminalInstanceLayoutInfoById, TerminalShellType } from 'vs/platform/terminal/common/terminal';
import { AutoOpenBarrier, Queue, RunOnceScheduler } from 'vs/base/common/async';
import { Emitter } from 'vs/base/common/event';
@@ -13,6 +13,7 @@ import { TerminalProcess } from 'vs/platform/terminal/node/terminalProcess';
import { ISetTerminalLayoutInfoArgs, ITerminalTabLayoutInfoDto, IProcessDetails, IGetTerminalLayoutInfoArgs, IPtyHostProcessReplayEvent } from 'vs/platform/terminal/common/terminalProcess';
import { ILogService } from 'vs/platform/log/common/log';
import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBuffering';
import { getSystemShell } from 'vs/base/node/shell';
type WorkspaceId = string;
@@ -90,7 +91,7 @@ export class PtyService extends Disposable implements IPtyService {
if (process.onProcessResolvedShellLaunchConfig) {
process.onProcessResolvedShellLaunchConfig(event => this._onProcessResolvedShellLaunchConfig.fire({ id, event }));
}
const persistentProcess = new PersistentTerminalProcess(id, process, workspaceId, workspaceName, shouldPersist, cols, rows, this._logService);
const persistentProcess = new PersistentTerminalProcess(id, process, workspaceId, workspaceName, shouldPersist, cols, rows, this._logService, shellLaunchConfig.icon);
process.onProcessExit(() => {
persistentProcess.dispose();
this._ptys.delete(id);
@@ -117,13 +118,13 @@ export class PtyService extends Disposable implements IPtyService {
this._throwIfNoPty(id).detach();
}
async listProcesses(reduceGraceTime: boolean): Promise<IProcessDetails[]> {
if (reduceGraceTime) {
for (const pty of this._ptys.values()) {
pty.reduceGraceTime();
}
async reduceConnectionGraceTime(): Promise<void> {
for (const pty of this._ptys.values()) {
pty.reduceGraceTime();
}
}
async listProcesses(): Promise<IProcessDetails[]> {
const persistentProcesses = Array.from(this._ptys.entries()).filter(([_, pty]) => pty.shouldPersistTerminal);
this._logService.info(`Listing ${persistentProcesses.length} persistent terminals, ${this._ptys.size} total terminals`);
@@ -136,11 +137,15 @@ export class PtyService extends Disposable implements IPtyService {
return this._throwIfNoPty(id).start();
}
async shutdown(id: number, immediate: boolean): Promise<void> {
return this._throwIfNoPty(id).shutdown(immediate);
// Don't throw if the pty is already shutdown
return this._ptys.get(id)?.shutdown(immediate);
}
async input(id: number, data: string): Promise<void> {
return this._throwIfNoPty(id).input(data);
}
async processBinary(id: number, data: string): Promise<void> {
return this._throwIfNoPty(id).writeBinary(data);
}
async resize(id: number, cols: number, rows: number): Promise<void> {
return this._throwIfNoPty(id).resize(cols, rows);
}
@@ -160,6 +165,14 @@ export class PtyService extends Disposable implements IPtyService {
return this._throwIfNoPty(id).orphanQuestionReply();
}
async getDefaultSystemShell(osOverride: OperatingSystem = OS): Promise<string> {
return getSystemShell(osOverride, process.env);
}
async getShellEnvironment(): Promise<IProcessEnvironment> {
return { ...process.env };
}
async setTerminalLayoutInfo(args: ISetTerminalLayoutInfoArgs): Promise<void> {
this._workspaceLayoutInfos.set(args.workspaceId, args);
}
@@ -213,7 +226,8 @@ export class PtyService extends Disposable implements IPtyService {
workspaceId: persistentProcess.workspaceId,
workspaceName: persistentProcess.workspaceName,
cwd,
isOrphan
isOrphan,
icon: persistentProcess.icon
};
}
@@ -251,7 +265,7 @@ export class PersistentTerminalProcess extends Disposable {
readonly onProcessShellTypeChanged = this._onProcessShellTypeChanged.event;
private readonly _onProcessOverrideDimensions = this._register(new Emitter<ITerminalDimensionsOverride | undefined>());
readonly onProcessOverrideDimensions = this._onProcessOverrideDimensions.event;
private readonly _onProcessData = this._register(new Emitter<IProcessDataEvent>());
private readonly _onProcessData = this._register(new Emitter<string>());
readonly onProcessData = this._onProcessData.event;
private readonly _onProcessOrphanQuestion = this._register(new Emitter<void>());
readonly onProcessOrphanQuestion = this._onProcessOrphanQuestion.event;
@@ -263,6 +277,7 @@ export class PersistentTerminalProcess extends Disposable {
get pid(): number { return this._pid; }
get title(): string { return this._terminalProcess.currentTitle; }
get icon(): string | undefined { return this._icon; }
constructor(
private _persistentProcessId: number,
@@ -271,7 +286,8 @@ export class PersistentTerminalProcess extends Disposable {
public readonly workspaceName: string,
public readonly shouldPersistTerminal: boolean,
cols: number, rows: number,
private readonly _logService: ILogService
private readonly _logService: ILogService,
private readonly _icon?: string
) {
super();
this._recorder = new TerminalRecorder(cols, rows);
@@ -295,12 +311,12 @@ export class PersistentTerminalProcess extends Disposable {
this._register(this._terminalProcess.onProcessShellTypeChanged(e => this._onProcessShellTypeChanged.fire(e)));
// Data buffering to reduce the amount of messages going to the renderer
this._bufferer = new TerminalDataBufferer((_, data) => this._onProcessData.fire({ data: data, sync: true }));
this._bufferer = new TerminalDataBufferer((_, data) => this._onProcessData.fire(data));
this._register(this._bufferer.startBuffering(this._persistentProcessId, this._terminalProcess.onProcessData));
this._register(this._terminalProcess.onProcessExit(() => this._bufferer.stopBuffering(this._persistentProcessId)));
// Data recording for reconnect
this._register(this.onProcessData(e => this._recorder.recordData(e.data)));
this._register(this.onProcessData(e => this._recorder.recordData(e)));
}
attach(): void {
@@ -340,6 +356,9 @@ export class PersistentTerminalProcess extends Disposable {
}
return this._terminalProcess.input(data);
}
writeBinary(data: string): Promise<void> {
return this._terminalProcess.processBinary(data);
}
resize(cols: number, rows: number): void {
if (this._inReplay) {
return;

View File

@@ -6,7 +6,7 @@
import * as os from 'os';
import * as path from 'vs/base/common/path';
import * as process from 'vs/base/common/process';
import { exists } from 'vs/base/node/pfs';
import * as pfs from 'vs/base/node/pfs';
import { isString } from 'vs/base/common/types';
import { getCaseInsensitive } from 'vs/base/common/objects';
import { IProcessEnvironment, isWindows } from 'vs/base/common/platform';
@@ -20,7 +20,7 @@ export function getWindowsBuildNumber(): number {
return buildNumber;
}
export async function findExecutable(command: string, cwd?: string, paths?: string[], env: IProcessEnvironment = process.env as IProcessEnvironment): Promise<string | undefined> {
export async function findExecutable(command: string, cwd?: string, paths?: string[], env: IProcessEnvironment = process.env as IProcessEnvironment, exists: (path: string) => Promise<boolean> = pfs.exists): Promise<string | undefined> {
// If we have an absolute path then we take it.
if (path.isAbsolute(command)) {
return await exists(command) ? command : undefined;

View File

@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import * as path from 'vs/base/common/path';
import * as platform from 'vs/base/common/platform';
import type * as pty from 'node-pty';
import * as fs from 'fs';
import * as os from 'os';
@@ -17,6 +16,8 @@ import { findExecutable, getWindowsBuildNumber } from 'vs/platform/terminal/node
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { WindowsShellHelper } from 'vs/platform/terminal/node/windowsShellHelper';
import { IProcessEnvironment, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
import { timeout } from 'vs/base/common/async';
// Writing large amounts of data can be corrupted for some reason, after looking into this is
// appears to be a race condition around writing to the FD which may be based on how powerful the
@@ -44,9 +45,33 @@ const enum ShutdownConstants {
MaximumShutdownTime = 5000
}
const enum Constants {
/**
* The minimum duration between kill and spawn calls on Windows/conpty as a mitigation for a
* hang issue. See:
* - https://github.com/microsoft/vscode/issues/71966
* - https://github.com/microsoft/vscode/issues/117956
* - https://github.com/microsoft/vscode/issues/121336
*/
KillSpawnThrottleInterval = 250,
/**
* The amount of time to wait when a call is throttles beyond the exact amount, this is used to
* try prevent early timeouts causing a kill/spawn call to happen at double the regular
* interval.
*/
KillSpawnSpacingDuration = 50,
}
interface IWriteObject {
data: string,
isBinary: boolean
}
export class TerminalProcess extends Disposable implements ITerminalChildProcess {
readonly shouldPersist = false;
private static _lastKillOrStart = 0;
private _exitCode: number | undefined;
private _exitMessage: string | undefined;
private _closeTimeout: any;
@@ -62,7 +87,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
private _isDisposed: boolean = false;
private _windowsShellHelper: WindowsShellHelper | undefined;
private _titleInterval: NodeJS.Timer | null = null;
private _writeQueue: string[] = [];
private _writeQueue: IWriteObject[] = [];
private _writeTimeout: NodeJS.Timeout | undefined;
private _delayedResizer: DelayedResizer | undefined;
private readonly _initialCwd: string;
@@ -93,17 +118,17 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
cwd: string,
cols: number,
rows: number,
env: platform.IProcessEnvironment,
env: IProcessEnvironment,
/**
* environment used for `findExecutable`
*/
private readonly _executableEnv: platform.IProcessEnvironment,
private readonly _executableEnv: IProcessEnvironment,
windowsEnableConpty: boolean,
@ILogService private readonly _logService: ILogService
) {
super();
let name: string;
if (platform.isWindows) {
if (isWindows) {
name = path.basename(this._shellLaunchConfig.executable || '');
} else {
// Using 'xterm-256color' here helps ensure that the majority of Linux distributions will use a
@@ -115,7 +140,8 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
this._ptyOptions = {
name,
cwd,
env,
// TODO: When node-pty is updated this cast can be removed
env: env as { [key: string]: string; },
cols,
rows,
useConpty,
@@ -123,7 +149,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
conptyInheritCursor: useConpty && !!_shellLaunchConfig.initialText
};
// Delay resizes to avoid conpty not respecting very early resize calls
if (platform.isWindows) {
if (isWindows) {
if (useConpty && cols === 0 && rows === 0 && this._shellLaunchConfig.executable?.endsWith('Git\\bin\\bash.exe')) {
this._delayedResizer = new DelayedResizer();
this._register(this._delayedResizer.onTrigger(dimensions => {
@@ -194,6 +220,9 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
if (!executable) {
return { message: localize('launchFail.executableDoesNotExist', "Path to shell executable \"{0}\" does not exist", slc.executable) };
}
// Set the executable explicitly here so that node-pty doesn't need to search the
// $PATH too.
slc.executable = executable;
}
}
return undefined;
@@ -201,6 +230,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
private async setupPtyProcess(shellLaunchConfig: IShellLaunchConfig, options: pty.IPtyForkOptions): Promise<void> {
const args = shellLaunchConfig.args || [];
await this._throttleKillSpawn();
this._logService.trace('IPty#spawn', shellLaunchConfig.executable, args, options);
const ptyProcess = (await import('node-pty')).spawn(shellLaunchConfig.executable!, args, options);
this._ptyProcess = ptyProcess;
@@ -220,7 +250,6 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
// Refire the data event
this._onProcessData.fire(data);
if (this._closeTimeout) {
clearTimeout(this._closeTimeout);
this._queueProcessExit();
}
this._windowsShellHelper?.checkShell();
@@ -233,7 +262,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
this._sendProcessId(ptyProcess.pid);
}
public dispose(): void {
public override dispose(): void {
this._isDisposed = true;
if (this._titleInterval) {
clearInterval(this._titleInterval);
@@ -246,7 +275,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
// Send initial timeout async to give event listeners a chance to init
setTimeout(() => this._sendProcessTitle(ptyProcess), 0);
// Setup polling for non-Windows, for Windows `process` doesn't change
if (!platform.isWindows) {
if (!isWindows) {
this._titleInterval = setInterval(() => {
if (this._currentTitle !== ptyProcess.process) {
this._sendProcessTitle(ptyProcess);
@@ -278,6 +307,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
// point but we want to make sure
try {
if (this._ptyProcess) {
await this._throttleKillSpawn();
this._logService.trace('IPty#kill');
this._ptyProcess.kill();
}
@@ -288,6 +318,19 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
this.dispose();
}
private async _throttleKillSpawn(): Promise<void> {
// Only throttle on Windows/conpty
if (!isWindows || !('useConpty' in this._ptyOptions) || !this._ptyOptions.useConpty) {
return;
}
// Use a loop to ensure multiple calls in a single interval space out
while (Date.now() - TerminalProcess._lastKillOrStart < Constants.KillSpawnThrottleInterval) {
this._logService.trace('Throttling kill/spawn call');
await timeout(Constants.KillSpawnThrottleInterval - (Date.now() - TerminalProcess._lastKillOrStart) + Constants.KillSpawnSpacingDuration);
}
TerminalProcess._lastKillOrStart = Date.now();
}
private _sendProcessId(pid: number) {
this._onProcessReady.fire({ pid, cwd: this._initialCwd });
}
@@ -301,7 +344,10 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
}
public shutdown(immediate: boolean): void {
if (immediate) {
// don't force immediate disposal of the terminal processes on Windows as an additional
// mitigation for https://github.com/microsoft/vscode/issues/71966 which causes the pty host
// to become unresponsive, disconnecting all terminals across all windows.
if (immediate && !isWindows) {
this._kill();
} else {
if (!this._closeTimeout && !this._isDisposed) {
@@ -317,16 +363,24 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
}
}
public input(data: string): void {
public input(data: string, isBinary?: boolean): void {
if (this._isDisposed || !this._ptyProcess) {
return;
}
for (let i = 0; i <= Math.floor(data.length / WRITE_MAX_CHUNK_SIZE); i++) {
this._writeQueue.push(data.substr(i * WRITE_MAX_CHUNK_SIZE, WRITE_MAX_CHUNK_SIZE));
const obj = {
isBinary: isBinary || false,
data: data.substr(i * WRITE_MAX_CHUNK_SIZE, WRITE_MAX_CHUNK_SIZE)
};
this._writeQueue.push(obj);
}
this._startWrite();
}
public async processBinary(data: string): Promise<void> {
this.input(data, true);
}
private _startWrite(): void {
// Don't write if it's already queued of is there is nothing to write
if (this._writeTimeout !== undefined || this._writeQueue.length === 0) {
@@ -349,9 +403,12 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
}
private _doWrite(): void {
const data = this._writeQueue.shift()!;
this._logService.trace('IPty#write', `${data.length} characters`);
this._ptyProcess!.write(data);
const object = this._writeQueue.shift()!;
if (object.isBinary) {
this._ptyProcess!.write(Buffer.from(object.data, 'binary') as any);
} else {
this._ptyProcess!.write(object.data);
}
}
public resize(cols: number, rows: number): void {
@@ -412,7 +469,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
}
public getCwd(): Promise<string> {
if (platform.isMacintosh) {
if (isMacintosh) {
// Disable cwd lookup on macOS Big Sur due to spawn blocking thread (darwin v20 is macOS
// Big Sur) https://github.com/Microsoft/vscode/issues/105446
const osRelease = os.release().split('.');
@@ -435,7 +492,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
}
}
if (platform.isLinux) {
if (isLinux) {
return new Promise<string>(resolve => {
if (!this._ptyProcess) {
resolve(this._initialCwd);
@@ -484,7 +541,7 @@ class DelayedResizer extends Disposable {
});
}
dispose(): void {
override dispose(): void {
super.dispose();
clearTimeout(this._timeout);
}

View File

@@ -3,13 +3,13 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as platform from 'vs/base/common/platform';
import { Emitter, Event } from 'vs/base/common/event';
import type * as WindowsProcessTreeType from 'windows-process-tree';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { TerminalShellType, WindowsShellType } from 'vs/platform/terminal/common/terminal';
import { debounce } from 'vs/base/common/decorators';
import { timeout } from 'vs/base/common/async';
import { isWindows, platform } from 'vs/base/common/platform';
export interface IWindowsShellHelper extends IDisposable {
readonly onShellNameChanged: Event<string>;
@@ -51,8 +51,8 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe
) {
super();
if (!platform.isWindows) {
throw new Error(`WindowsShellHelper cannot be instantiated on ${platform.platform}`);
if (!isWindows) {
throw new Error(`WindowsShellHelper cannot be instantiated on ${platform}`);
}
this._isDisposed = false;
@@ -69,7 +69,7 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe
@debounce(500)
async checkShell(): Promise<void> {
if (platform.isWindows) {
if (isWindows) {
// Wait to give the shell some time to actually launch a process, this
// could lead to a race condition but it would be recovered from when
// data stops and should cover the majority of cases
@@ -112,7 +112,7 @@ export class WindowsShellHelper extends Disposable implements IWindowsShellHelpe
return this.traverseTree(tree.children[favouriteChild]);
}
public dispose(): void {
public override dispose(): void {
this._isDisposed = true;
super.dispose();
}