mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-31 01:00:29 -04:00
Merge from vscode 4d91d96e5e121b38d33508cdef17868bab255eae
This commit is contained in:
committed by
AzureDataStudio
parent
a971aee5bd
commit
5e7071e466
@@ -11,22 +11,27 @@ import * as fs from 'fs';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { getWindowsBuildNumber } from 'vs/workbench/contrib/terminal/node/terminal';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IShellLaunchConfig, ITerminalChildProcess, SHELL_PATH_INVALID_EXIT_CODE, SHELL_PATH_DIRECTORY_EXIT_CODE, SHELL_CWD_INVALID_EXIT_CODE } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { IShellLaunchConfig, ITerminalChildProcess, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { exec } from 'child_process';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { stat } from 'vs/base/node/pfs';
|
||||
import { findExecutable } from 'vs/workbench/contrib/terminal/node/terminalEnvironment';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export class TerminalProcess extends Disposable implements ITerminalChildProcess {
|
||||
private _exitCode: number | undefined;
|
||||
private _exitMessage: string | undefined;
|
||||
private _closeTimeout: any;
|
||||
private _ptyProcess: pty.IPty | undefined;
|
||||
private _currentTitle: string = '';
|
||||
private _processStartupComplete: Promise<void> | undefined;
|
||||
private _isDisposed: boolean = false;
|
||||
private _titleInterval: NodeJS.Timer | null = null;
|
||||
private _initialCwd: string;
|
||||
private readonly _initialCwd: string;
|
||||
private readonly _ptyOptions: pty.IPtyForkOptions | pty.IWindowsPtyForkOptions;
|
||||
|
||||
public get exitMessage(): string | undefined { return this._exitMessage; }
|
||||
|
||||
private readonly _onProcessData = this._register(new Emitter<string>());
|
||||
public get onProcessData(): Event<string> { return this._onProcessData.event; }
|
||||
@@ -38,7 +43,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
public get onProcessTitleChanged(): Event<string> { return this._onProcessTitleChanged.event; }
|
||||
|
||||
constructor(
|
||||
shellLaunchConfig: IShellLaunchConfig,
|
||||
private readonly _shellLaunchConfig: IShellLaunchConfig,
|
||||
cwd: string,
|
||||
cols: number,
|
||||
rows: number,
|
||||
@@ -47,73 +52,80 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
@ILogService private readonly _logService: ILogService
|
||||
) {
|
||||
super();
|
||||
let shellName: string;
|
||||
let name: string;
|
||||
if (os.platform() === 'win32') {
|
||||
shellName = path.basename(shellLaunchConfig.executable || '');
|
||||
name = path.basename(this._shellLaunchConfig.executable || '');
|
||||
} else {
|
||||
// Using 'xterm-256color' here helps ensure that the majority of Linux distributions will use a
|
||||
// color prompt as defined in the default ~/.bashrc file.
|
||||
shellName = 'xterm-256color';
|
||||
name = 'xterm-256color';
|
||||
}
|
||||
|
||||
this._initialCwd = cwd;
|
||||
|
||||
const useConpty = windowsEnableConpty && process.platform === 'win32' && getWindowsBuildNumber() >= 18309;
|
||||
const options: pty.IPtyForkOptions | pty.IWindowsPtyForkOptions = {
|
||||
name: shellName,
|
||||
this._ptyOptions = {
|
||||
name,
|
||||
cwd,
|
||||
env,
|
||||
cols,
|
||||
rows,
|
||||
useConpty,
|
||||
// This option will force conpty to not redraw the whole viewport on launch
|
||||
conptyInheritCursor: useConpty && !!shellLaunchConfig.initialText
|
||||
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);
|
||||
}
|
||||
return undefined;
|
||||
}, async err => {
|
||||
if (err && err.code === 'ENOENT') {
|
||||
// So we can include in the error message the specified CWD
|
||||
shellLaunchConfig.cwd = cwd;
|
||||
return Promise.reject(SHELL_CWD_INVALID_EXIT_CODE);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const executableVerification = stat(shellLaunchConfig.executable!).then(async stat => {
|
||||
if (!stat.isFile() && !stat.isSymbolicLink()) {
|
||||
return Promise.reject(stat.isDirectory() ? SHELL_PATH_DIRECTORY_EXIT_CODE : SHELL_PATH_INVALID_EXIT_CODE);
|
||||
}
|
||||
return undefined;
|
||||
}, async (err) => {
|
||||
if (err && err.code === 'ENOENT') {
|
||||
let cwd = shellLaunchConfig.cwd instanceof URI ? shellLaunchConfig.cwd.path : shellLaunchConfig.cwd!;
|
||||
// Try to get path
|
||||
const envPaths: string[] | undefined = (shellLaunchConfig.env && shellLaunchConfig.env.PATH) ? shellLaunchConfig.env.PATH.split(path.delimiter) : undefined;
|
||||
const executable = await findExecutable(shellLaunchConfig.executable!, cwd, envPaths);
|
||||
if (!executable) {
|
||||
return Promise.reject(SHELL_PATH_INVALID_EXIT_CODE);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
Promise.all([cwdVerification, executableVerification]).then(() => {
|
||||
this.setupPtyProcess(shellLaunchConfig, options);
|
||||
}).catch((exitCode: number) => {
|
||||
return this._launchFailed(exitCode);
|
||||
});
|
||||
}
|
||||
|
||||
private _launchFailed(exitCode: number): void {
|
||||
this._exitCode = exitCode;
|
||||
this._queueProcessExit();
|
||||
this._processStartupComplete = Promise.resolve(undefined);
|
||||
public async start(): Promise<ITerminalLaunchError | undefined> {
|
||||
const results = await Promise.all([this._validateCwd(), this._validateExecutable()]);
|
||||
const firstError = results.find(r => r !== undefined);
|
||||
if (firstError) {
|
||||
return firstError;
|
||||
}
|
||||
|
||||
try {
|
||||
this.setupPtyProcess(this._shellLaunchConfig, this._ptyOptions);
|
||||
return undefined;
|
||||
} catch (err) {
|
||||
this._logService.trace('IPty#spawn native exception', err);
|
||||
return { message: `A native exception occurred during launch (${err.message})` };
|
||||
}
|
||||
}
|
||||
|
||||
private async _validateCwd(): Promise<undefined | ITerminalLaunchError> {
|
||||
try {
|
||||
const result = await stat(this._initialCwd);
|
||||
if (!result.isDirectory()) {
|
||||
return { message: localize('launchFail.cwdNotDirectory', "Starting directory (cwd) \"{0}\" is not a directory", this._initialCwd.toString()) };
|
||||
}
|
||||
} catch (err) {
|
||||
if (err?.code === 'ENOENT') {
|
||||
return { message: localize('launchFail.cwdDoesNotExist', "Starting directory (cwd) \"{0}\" does not exist", this._initialCwd.toString()) };
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async _validateExecutable(): Promise<undefined | ITerminalLaunchError> {
|
||||
const slc = this._shellLaunchConfig;
|
||||
if (!slc.executable) {
|
||||
throw new Error('IShellLaunchConfig.executable not set');
|
||||
}
|
||||
try {
|
||||
const result = await stat(slc.executable);
|
||||
if (!result.isFile() && !result.isSymbolicLink()) {
|
||||
return { message: localize('launchFail.executableIsNotFileOrSymlink', "Shell path \"{0}\" is not a file of a symlink", slc.executable) };
|
||||
}
|
||||
} catch (err) {
|
||||
if (err?.code === 'ENOENT') {
|
||||
// The executable isn't an absolute path, try find it on the PATH or CWD
|
||||
let cwd = slc.cwd instanceof URI ? slc.cwd.path : slc.cwd!;
|
||||
const envPaths: string[] | undefined = (slc.env && slc.env.PATH) ? slc.env.PATH.split(path.delimiter) : undefined;
|
||||
const executable = await findExecutable(slc.executable!, cwd, envPaths);
|
||||
if (!executable) {
|
||||
return { message: localize('launchFail.executableDoesNotExist', "Shell path \"{0}\" does not exist", slc.executable) };
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private setupPtyProcess(shellLaunchConfig: IShellLaunchConfig, options: pty.IPtyForkOptions): void {
|
||||
@@ -124,22 +136,19 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
this._processStartupComplete = new Promise<void>(c => {
|
||||
this.onProcessReady(() => c());
|
||||
});
|
||||
ptyProcess.on('data', data => {
|
||||
ptyProcess.onData(data => {
|
||||
this._onProcessData.fire(data);
|
||||
if (this._closeTimeout) {
|
||||
clearTimeout(this._closeTimeout);
|
||||
this._queueProcessExit();
|
||||
}
|
||||
});
|
||||
ptyProcess.on('exit', code => {
|
||||
this._exitCode = code;
|
||||
ptyProcess.onExit(e => {
|
||||
this._exitCode = e.exitCode;
|
||||
this._queueProcessExit();
|
||||
});
|
||||
this._setupTitlePolling(ptyProcess);
|
||||
// TODO: We should no longer need to delay this since pty.spawn is sync
|
||||
setTimeout(() => {
|
||||
this._sendProcessId(ptyProcess);
|
||||
}, 500);
|
||||
this._sendProcessId(ptyProcess.pid);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
@@ -200,8 +209,8 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
private _sendProcessId(ptyProcess: pty.IPty) {
|
||||
this._onProcessReady.fire({ pid: ptyProcess.pid, cwd: this._initialCwd });
|
||||
private _sendProcessId(pid: number) {
|
||||
this._onProcessReady.fire({ pid, cwd: this._initialCwd });
|
||||
}
|
||||
|
||||
private _sendProcessTitle(ptyProcess: pty.IPty): void {
|
||||
|
||||
Reference in New Issue
Block a user