mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-30 16:50:30 -04:00
Merge from vscode 4d91d96e5e121b38d33508cdef17868bab255eae
This commit is contained in:
committed by
AzureDataStudio
parent
a971aee5bd
commit
5e7071e466
@@ -68,20 +68,24 @@ export class TerminalLink extends DisposableStore implements ILink {
|
||||
}
|
||||
}));
|
||||
|
||||
const timeout = this._configurationService.getValue<number>('editor.hover.delay');
|
||||
this._tooltipScheduler = new RunOnceScheduler(() => {
|
||||
this._tooltipCallback(
|
||||
this,
|
||||
convertBufferRangeToViewport(this.range, this._viewportY),
|
||||
this._isHighConfidenceLink ? () => this._enableDecorations() : undefined,
|
||||
this._isHighConfidenceLink ? () => this._disableDecorations() : undefined
|
||||
);
|
||||
// Clear out scheduler until next hover event
|
||||
this._tooltipScheduler?.dispose();
|
||||
this._tooltipScheduler = undefined;
|
||||
}, timeout);
|
||||
this.add(this._tooltipScheduler);
|
||||
this._tooltipScheduler.schedule();
|
||||
// Only show the tooltip and highlight for high confidence links (not word/search workspace
|
||||
// links). Feedback was that this makes using the terminal overly noisy.
|
||||
if (this._isHighConfidenceLink) {
|
||||
const timeout = this._configurationService.getValue<number>('editor.hover.delay');
|
||||
this._tooltipScheduler = new RunOnceScheduler(() => {
|
||||
this._tooltipCallback(
|
||||
this,
|
||||
convertBufferRangeToViewport(this.range, this._viewportY),
|
||||
this._isHighConfidenceLink ? () => this._enableDecorations() : undefined,
|
||||
this._isHighConfidenceLink ? () => this._disableDecorations() : undefined
|
||||
);
|
||||
// Clear out scheduler until next hover event
|
||||
this._tooltipScheduler?.dispose();
|
||||
this._tooltipScheduler = undefined;
|
||||
}, timeout);
|
||||
this.add(this._tooltipScheduler);
|
||||
this._tooltipScheduler.schedule();
|
||||
}
|
||||
|
||||
const origin = { x: event.pageX, y: event.pageY };
|
||||
this._hoverListeners = new DisposableStore();
|
||||
|
||||
@@ -12,29 +12,6 @@
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.monaco-workbench .terminal-hover-widget {
|
||||
position: fixed;
|
||||
font-size: 14px;
|
||||
line-height: 19px;
|
||||
animation: fadein 100ms linear;
|
||||
/* Must be higher than sash's z-index and terminal canvases */
|
||||
z-index: 40;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.monaco-workbench .terminal-hover-widget a {
|
||||
color: #3794ff;
|
||||
}
|
||||
|
||||
.monaco-workbench .terminal-hover-widget.right-aligned .hover-row.status-bar .actions {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.monaco-workbench .terminal-hover-widget.right-aligned .hover-row.status-bar .actions .action-container {
|
||||
margin-right: 0;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.monaco-workbench .terminal-overlay-widget {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
@@ -58,15 +35,16 @@
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.monaco-workbench .terminal-env-var-info:hover,
|
||||
.monaco-workbench .terminal-env-var-info.requires-action {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.monaco-workbench .pane-body.integrated-terminal .monaco-split-view2.horizontal .split-view-view:last-child .terminal-env-var-info {
|
||||
/* Adjust for reduced margin in splits */
|
||||
right: -8px;
|
||||
}
|
||||
|
||||
.monaco-workbench .terminal-env-var-info:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.monaco-workbench .terminal-env-var-info.codicon {
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Terminal as XTermTerminal } from 'xterm';
|
||||
import { SearchAddon as XTermSearchAddon } from 'xterm-addon-search';
|
||||
import { Unicode11Addon as XTermUnicode11Addon } from 'xterm-addon-unicode11';
|
||||
import { WebglAddon as XTermWebglAddon } from 'xterm-addon-webgl';
|
||||
import { IWindowsShellHelper, ITerminalConfigHelper, ITerminalChildProcess, IShellLaunchConfig, IDefaultShellAndArgsRequest, ISpawnExtHostProcessRequest, IStartExtensionTerminalRequest, IAvailableShellsRequest, ITerminalProcessExtHostProxy, ICommandTracker, INavigationMode, TitleEventSource, ITerminalDimensions } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { IWindowsShellHelper, ITerminalConfigHelper, ITerminalChildProcess, IShellLaunchConfig, IDefaultShellAndArgsRequest, ISpawnExtHostProcessRequest, IStartExtensionTerminalRequest, IAvailableShellsRequest, ITerminalProcessExtHostProxy, ICommandTracker, INavigationMode, TitleEventSource, ITerminalDimensions, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IProcessEnvironment, Platform } from 'vs/base/common/platform';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
@@ -24,7 +24,7 @@ export const ITerminalInstanceService = createDecorator<ITerminalInstanceService
|
||||
* dependency on ITerminalService.
|
||||
*/
|
||||
export interface ITerminalInstanceService {
|
||||
_serviceBrand: undefined;
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
// These events are optional as the requests they make are only needed on the browser side
|
||||
onRequestDefaultShellAndArgs?: Event<IDefaultShellAndArgsRequest>;
|
||||
@@ -70,7 +70,7 @@ export interface ITerminalTab {
|
||||
}
|
||||
|
||||
export interface ITerminalService {
|
||||
_serviceBrand: undefined;
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
activeTabIndex: number;
|
||||
configHelper: ITerminalConfigHelper;
|
||||
@@ -161,8 +161,8 @@ export interface ITerminalService {
|
||||
preparePathForTerminalAsync(path: string, executable: string | undefined, title: string, shellType: TerminalShellType): Promise<string>;
|
||||
|
||||
extHostReady(remoteAuthority: string): void;
|
||||
requestSpawnExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI | undefined, cols: number, rows: number, isWorkspaceShellAllowed: boolean): void;
|
||||
requestStartExtensionTerminal(proxy: ITerminalProcessExtHostProxy, cols: number, rows: number): void;
|
||||
requestSpawnExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI | undefined, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise<ITerminalLaunchError | undefined>;
|
||||
requestStartExtensionTerminal(proxy: ITerminalProcessExtHostProxy, cols: number, rows: number): Promise<ITerminalLaunchError | undefined>;
|
||||
}
|
||||
|
||||
export interface ISearchOptions {
|
||||
|
||||
@@ -37,6 +37,7 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { localize } from 'vs/nls';
|
||||
import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
|
||||
async function getCwdForSplit(configHelper: ITerminalConfigHelper, instance: ITerminalInstance, folders?: IWorkspaceFolder[], commandService?: ICommandService): Promise<string | URI | undefined> {
|
||||
switch (configHelper.config.splitCwd) {
|
||||
@@ -392,6 +393,19 @@ export class ClearTerminalAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
export class TerminalLaunchTroubleshootAction extends Action {
|
||||
|
||||
constructor(
|
||||
@IOpenerService private readonly _openerService: IOpenerService
|
||||
) {
|
||||
super('workbench.action.terminal.launchHelp', localize('terminalLaunchTroubleshoot', "Troubleshoot"));
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
this._openerService.open('https://aka.ms/vscode-troubleshoot-terminal-launch');
|
||||
}
|
||||
}
|
||||
|
||||
export function registerTerminalActions() {
|
||||
const category: ILocalizedString = { value: TERMINAL_ACTION_CATEGORY, original: 'Terminal' };
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderB
|
||||
import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager';
|
||||
import { IShellLaunchConfig, ITerminalDimensions, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_VIEW_ID, IWindowsShellHelper, SHELL_PATH_INVALID_EXIT_CODE, SHELL_PATH_DIRECTORY_EXIT_CODE, SHELL_CWD_INVALID_EXIT_CODE, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, INavigationMode, TitleEventSource, LEGACY_CONSOLE_MODE_EXIT_CODE, DEFAULT_COMMANDS_TO_SKIP_SHELL } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { IShellLaunchConfig, ITerminalDimensions, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_VIEW_ID, IWindowsShellHelper, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, INavigationMode, TitleEventSource, DEFAULT_COMMANDS_TO_SKIP_SHELL, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ansiColorIdentifiers, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_FOREGROUND_COLOR, TERMINAL_SELECTION_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry';
|
||||
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
|
||||
import { TerminalLinkManager } from 'vs/workbench/contrib/terminal/browser/links/terminalLinkManager';
|
||||
@@ -42,6 +42,7 @@ import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { IViewsService, IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views';
|
||||
import { EnvironmentVariableInfoWidget } from 'vs/workbench/contrib/terminal/browser/widgets/environmentVariableInfoWidget';
|
||||
import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable';
|
||||
import { TerminalLaunchTroubleshootAction } from 'vs/workbench/contrib/terminal/browser/terminalActions';
|
||||
|
||||
// How long in milliseconds should an average frame take to render for a notification to appear
|
||||
// which suggests the fallback DOM-based renderer
|
||||
@@ -103,6 +104,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
private _commandTrackerAddon: CommandTrackerAddon | undefined;
|
||||
private _navigationModeAddon: INavigationMode & ITerminalAddon | undefined;
|
||||
|
||||
private _timeoutDimension: dom.Dimension | undefined;
|
||||
|
||||
public disableLayout: boolean;
|
||||
public get id(): number { return this._id; }
|
||||
public get cols(): number {
|
||||
@@ -483,8 +486,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
throw new Error('xterm elements not set after open');
|
||||
}
|
||||
|
||||
// Check if custom Terminal title exists and set same
|
||||
if (this._title.length > 0) {
|
||||
xterm.textarea.setAttribute('aria-label', nls.localize('terminalTextBoxAriaLabelNumberAndTitle', "Terminal {0}, {1}", this._id, this._title));
|
||||
} else {
|
||||
xterm.textarea.setAttribute('aria-label', nls.localize('terminalTextBoxAriaLabel', "Terminal {0}", this._id));
|
||||
}
|
||||
|
||||
xterm.textarea.setAttribute('aria-label', nls.localize('terminalTextBoxAriaLabel', "Terminal {0}", this._id));
|
||||
xterm.textarea.addEventListener('focus', () => this._onFocus.fire(this));
|
||||
xterm.attachCustomKeyEventHandler((event: KeyboardEvent): boolean => {
|
||||
// Disable all input if the terminal is exiting
|
||||
@@ -821,7 +829,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
// HACK: Trigger another async layout to ensure xterm's CharMeasure is ready to use,
|
||||
// this hack can be removed when https://github.com/xtermjs/xterm.js/issues/702 is
|
||||
// supported.
|
||||
setTimeout(() => this.layout(new dom.Dimension(width, height)), 0);
|
||||
this._timeoutDimension = new dom.Dimension(width, height);
|
||||
setTimeout(() => this.layout(this._timeoutDimension!), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -897,7 +906,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
this._windowsShellHelper = this._terminalInstanceService.createWindowsShellHelper(this._processManager.shellProcessId, xterm);
|
||||
this._windowsShellHelper.onShellNameChange(title => {
|
||||
this.setShellType(this.getShellType(title));
|
||||
if (this.isTitleSetByProcess) {
|
||||
if (this.isTitleSetByProcess && !this._configHelper.config.experimentalUseTitleEvent) {
|
||||
this.setTitle(title, TitleEventSource.Process);
|
||||
}
|
||||
});
|
||||
@@ -906,11 +915,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
});
|
||||
}
|
||||
|
||||
// Create the process asynchronously to allow the terminal's container
|
||||
// to be created so dimensions are accurate
|
||||
setTimeout(() => {
|
||||
this._processManager.createProcess(this._shellLaunchConfig, this._cols, this._rows, this._accessibilityService.isScreenReaderOptimized());
|
||||
}, 0);
|
||||
// Create the process asynchronously to allow the terminal's container to be created so
|
||||
// dimensions are accurate
|
||||
this._processManager.createProcess(this._shellLaunchConfig, this._cols, this._rows, this._accessibilityService.isScreenReaderOptimized()).then(error => {
|
||||
if (error) {
|
||||
this._onProcessExit(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getShellType(executable: string): TerminalShellType {
|
||||
@@ -945,48 +956,54 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
* @param exitCode The exit code of the process, this is undefined when the terminal was exited
|
||||
* through user action.
|
||||
*/
|
||||
private _onProcessExit(exitCode?: number): void {
|
||||
private _onProcessExit(exitCodeOrError?: number | ITerminalLaunchError): void {
|
||||
// Prevent dispose functions being triggered multiple times
|
||||
if (this._isExiting) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._logService.debug(`Terminal process exit (id: ${this.id}) with code ${exitCode}`);
|
||||
this._logService.debug(`Terminal process exit (id: ${this.id}) with code ${this._exitCode}`);
|
||||
|
||||
this._exitCode = exitCode;
|
||||
this._isExiting = true;
|
||||
let exitCodeMessage: string | undefined;
|
||||
|
||||
// Create exit code message
|
||||
if (exitCode) {
|
||||
if (exitCode === SHELL_PATH_INVALID_EXIT_CODE) {
|
||||
exitCodeMessage = nls.localize('terminal.integrated.exitedWithInvalidPath', 'The terminal shell path "{0}" does not exist', this._shellLaunchConfig.executable);
|
||||
} else if (exitCode === SHELL_PATH_DIRECTORY_EXIT_CODE) {
|
||||
exitCodeMessage = nls.localize('terminal.integrated.exitedWithInvalidPathDirectory', 'The terminal shell path "{0}" is a directory', this._shellLaunchConfig.executable);
|
||||
} else if (exitCode === SHELL_CWD_INVALID_EXIT_CODE && this._shellLaunchConfig.cwd) {
|
||||
exitCodeMessage = nls.localize('terminal.integrated.exitedWithInvalidCWD', 'The terminal shell CWD "{0}" does not exist', this._shellLaunchConfig.cwd.toString());
|
||||
} else if (exitCode === LEGACY_CONSOLE_MODE_EXIT_CODE) {
|
||||
exitCodeMessage = nls.localize('terminal.integrated.legacyConsoleModeError', 'The terminal failed to launch properly because your system has legacy console mode enabled, uncheck "Use legacy console" cmd.exe\'s properties to fix this.');
|
||||
} else if (this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) {
|
||||
let args = '';
|
||||
if (typeof this._shellLaunchConfig.args === 'string') {
|
||||
args = ` ${this._shellLaunchConfig.args}`;
|
||||
} else if (this._shellLaunchConfig.args && this._shellLaunchConfig.args.length) {
|
||||
args = ' ' + this._shellLaunchConfig.args.map(a => {
|
||||
if (typeof a === 'string' && a.indexOf(' ') !== -1) {
|
||||
return `'${a}'`;
|
||||
}
|
||||
return a;
|
||||
}).join(' ');
|
||||
switch (typeof exitCodeOrError) {
|
||||
case 'number':
|
||||
// Only show the error if the exit code is non-zero
|
||||
this._exitCode = exitCodeOrError;
|
||||
if (this._exitCode === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
let commandLine: string | undefined = undefined;
|
||||
if (this._shellLaunchConfig.executable) {
|
||||
exitCodeMessage = nls.localize('terminal.integrated.launchFailed', 'The terminal process command \'{0}{1}\' failed to launch (exit code: {2})', this._shellLaunchConfig.executable, args, exitCode);
|
||||
} else {
|
||||
exitCodeMessage = nls.localize('terminal.integrated.launchFailedExtHost', 'The terminal process failed to launch (exit code: {0})', exitCode);
|
||||
commandLine = this._shellLaunchConfig.executable;
|
||||
if (typeof this._shellLaunchConfig.args === 'string') {
|
||||
commandLine += ` ${this._shellLaunchConfig.args}`;
|
||||
} else if (this._shellLaunchConfig.args && this._shellLaunchConfig.args.length) {
|
||||
commandLine += this._shellLaunchConfig.args.map(a => ` '${a}'`).join();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
exitCodeMessage = nls.localize('terminal.integrated.exitedWithCode', 'The terminal process terminated with exit code: {0}', exitCode);
|
||||
}
|
||||
|
||||
if (this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) {
|
||||
if (commandLine) {
|
||||
exitCodeMessage = nls.localize('launchFailed.exitCodeAndCommandLine', "The terminal process \"{0}\" failed to launch (exit code: {1})", commandLine, this._exitCode);
|
||||
break;
|
||||
}
|
||||
exitCodeMessage = nls.localize('launchFailed.exitCodeOnly', "The terminal process failed to launch (exit code: {0})", this._exitCode);
|
||||
break;
|
||||
}
|
||||
if (commandLine) {
|
||||
exitCodeMessage = nls.localize('terminated.exitCodeAndCommandLine', "The terminal process \"{0}\" terminated with exit code: {1}", commandLine, this._exitCode);
|
||||
break;
|
||||
}
|
||||
exitCodeMessage = nls.localize('terminated.exitCodeOnly', "The terminal process terminated with exit code: {0}", this._exitCode);
|
||||
break;
|
||||
case 'object':
|
||||
this._exitCode = exitCodeOrError.code;
|
||||
exitCodeMessage = nls.localize('launchFailed.errorMessage', "The terminal process failed to launch: {0}", exitCodeOrError.message);
|
||||
break;
|
||||
}
|
||||
|
||||
this._logService.debug(`Terminal process exit (id: ${this.id}) state ${this._processManager.processState}`);
|
||||
@@ -1013,19 +1030,23 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
} else {
|
||||
this.dispose();
|
||||
if (exitCodeMessage) {
|
||||
if (this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) {
|
||||
this._notificationService.error(exitCodeMessage);
|
||||
const failedDuringLaunch = this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH;
|
||||
if (failedDuringLaunch || this._configHelper.config.showExitAlert) {
|
||||
// Always show launch failures
|
||||
this._notificationService.notify({
|
||||
message: exitCodeMessage,
|
||||
severity: Severity.Error,
|
||||
actions: { primary: [this._instantiationService.createInstance(TerminalLaunchTroubleshootAction)] }
|
||||
});
|
||||
} else {
|
||||
if (this._configHelper.config.showExitAlert) {
|
||||
this._notificationService.error(exitCodeMessage);
|
||||
} else {
|
||||
console.warn(exitCodeMessage);
|
||||
}
|
||||
// Log to help surface the error in case users report issues with showExitAlert
|
||||
// disabled
|
||||
this._logService.warn(exitCodeMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._onExit.fire(exitCode);
|
||||
this._onExit.fire(this._exitCode);
|
||||
}
|
||||
|
||||
private _attachPressAnyKeyToCloseListener(xterm: XTermTerminal) {
|
||||
@@ -1269,6 +1290,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
return;
|
||||
}
|
||||
|
||||
this._timeoutDimension = new dom.Dimension(dimension.width, dimension.height);
|
||||
|
||||
if (this._xterm && this._xterm.element) {
|
||||
this._xterm.element.style.width = terminalWidth + 'px';
|
||||
}
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ITerminalProcessExtHostProxy, IShellLaunchConfig, ITerminalChildProcess, ITerminalConfigHelper, ITerminalDimensions } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ITerminalProcessExtHostProxy, IShellLaunchConfig, ITerminalChildProcess, ITerminalConfigHelper, ITerminalDimensions, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import * as nls from 'vs/nls';
|
||||
import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
|
||||
let hasReceivedResponse: boolean = false;
|
||||
let hasReceivedResponseFromRemoteExtHost: boolean = false;
|
||||
|
||||
export class TerminalProcessExtHostProxy extends Disposable implements ITerminalChildProcess, ITerminalProcessExtHostProxy {
|
||||
|
||||
@@ -28,6 +28,8 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal
|
||||
private readonly _onProcessResolvedShellLaunchConfig = this._register(new Emitter<IShellLaunchConfig>());
|
||||
public get onProcessResolvedShellLaunchConfig(): Event<IShellLaunchConfig> { return this._onProcessResolvedShellLaunchConfig.event; }
|
||||
|
||||
private readonly _onStart = this._register(new Emitter<void>());
|
||||
public readonly onStart: Event<void> = this._onStart.event;
|
||||
private readonly _onInput = this._register(new Emitter<string>());
|
||||
public readonly onInput: Event<string> = this._onInput.event;
|
||||
private readonly _onResize: Emitter<{ cols: number, rows: number }> = this._register(new Emitter<{ cols: number, rows: number }>());
|
||||
@@ -47,31 +49,15 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal
|
||||
|
||||
constructor(
|
||||
public terminalId: number,
|
||||
shellLaunchConfig: IShellLaunchConfig,
|
||||
activeWorkspaceRootUri: URI | undefined,
|
||||
cols: number,
|
||||
rows: number,
|
||||
configHelper: ITerminalConfigHelper,
|
||||
private _shellLaunchConfig: IShellLaunchConfig,
|
||||
private _activeWorkspaceRootUri: URI | undefined,
|
||||
private _cols: number,
|
||||
private _rows: number,
|
||||
private _configHelper: ITerminalConfigHelper,
|
||||
@ITerminalService private readonly _terminalService: ITerminalService,
|
||||
@IRemoteAgentService readonly remoteAgentService: IRemoteAgentService
|
||||
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService
|
||||
) {
|
||||
super();
|
||||
|
||||
// Request a process if needed, if this is a virtual process this step can be skipped as
|
||||
// there is no real "process" and we know it's ready on the ext host already.
|
||||
if (shellLaunchConfig.isExtensionTerminal) {
|
||||
this._terminalService.requestStartExtensionTerminal(this, cols, rows);
|
||||
} else {
|
||||
remoteAgentService.getEnvironment().then(env => {
|
||||
if (!env) {
|
||||
throw new Error('Could not fetch environment');
|
||||
}
|
||||
this._terminalService.requestSpawnExtHostProcess(this, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, configHelper.checkWorkspaceShellPermissions(env.os));
|
||||
});
|
||||
if (!hasReceivedResponse) {
|
||||
setTimeout(() => this._onProcessTitleChanged.fire(nls.localize('terminal.integrated.starting', "Starting...")), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public emitData(data: string): void {
|
||||
@@ -79,7 +65,7 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal
|
||||
}
|
||||
|
||||
public emitTitle(title: string): void {
|
||||
hasReceivedResponse = true;
|
||||
hasReceivedResponseFromRemoteExtHost = true;
|
||||
this._onProcessTitleChanged.fire(title);
|
||||
}
|
||||
|
||||
@@ -118,6 +104,29 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal
|
||||
}
|
||||
}
|
||||
|
||||
public async start(): Promise<ITerminalLaunchError | undefined> {
|
||||
// Request a process if needed, if this is a virtual process this step can be skipped as
|
||||
// there is no real "process" and we know it's ready on the ext host already.
|
||||
if (this._shellLaunchConfig.isExtensionTerminal) {
|
||||
return this._terminalService.requestStartExtensionTerminal(this, this._cols, this._rows);
|
||||
}
|
||||
|
||||
// Add a loading title if the extension host has not started yet as there could be a
|
||||
// decent wait for the user
|
||||
if (!hasReceivedResponseFromRemoteExtHost) {
|
||||
setTimeout(() => this._onProcessTitleChanged.fire(nls.localize('terminal.integrated.starting', "Starting...")), 0);
|
||||
}
|
||||
|
||||
// Fetch the environment to check shell permissions
|
||||
const env = await this._remoteAgentService.getEnvironment();
|
||||
if (!env) {
|
||||
// Extension host processes are only allowed in remote extension hosts currently
|
||||
throw new Error('Could not fetch remote environment');
|
||||
}
|
||||
|
||||
return this._terminalService.requestSpawnExtHostProcess(this, this._shellLaunchConfig, this._activeWorkspaceRootUri, this._cols, this._rows, this._configHelper.checkWorkspaceShellPermissions(env.os));
|
||||
}
|
||||
|
||||
public shutdown(immediate: boolean): void {
|
||||
this._onShutdown.fire(immediate);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
|
||||
import { env as processEnv } from 'vs/base/common/process';
|
||||
import { ProcessState, ITerminalProcessManager, IShellLaunchConfig, ITerminalConfigHelper, ITerminalChildProcess, IBeforeProcessDataEvent, ITerminalEnvironment, ITerminalDimensions } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ProcessState, ITerminalProcessManager, IShellLaunchConfig, ITerminalConfigHelper, ITerminalChildProcess, IBeforeProcessDataEvent, ITerminalEnvironment, ITerminalDimensions, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IHistoryService } from 'vs/workbench/services/history/common/history';
|
||||
@@ -45,8 +45,8 @@ enum ProcessType {
|
||||
*
|
||||
* Internal definitions:
|
||||
* - Process: The process launched with the terminalProcess.ts file, or the pty as a whole
|
||||
* - Pty Process: The pseudoterminal master process (or the winpty agent process)
|
||||
* - Shell Process: The pseudoterminal slave process (ie. the shell)
|
||||
* - Pty Process: The pseudoterminal parent process (or the conpty/winpty agent process)
|
||||
* - Shell Process: The pseudoterminal child process (ie. the shell)
|
||||
*/
|
||||
export class TerminalProcessManager extends Disposable implements ITerminalProcessManager {
|
||||
public processState: ProcessState = ProcessState.UNINITIALIZED;
|
||||
@@ -127,7 +127,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
|
||||
cols: number,
|
||||
rows: number,
|
||||
isScreenReaderModeEnabled: boolean
|
||||
): Promise<void> {
|
||||
): Promise<ITerminalLaunchError | undefined> {
|
||||
if (shellLaunchConfig.isExtensionTerminal) {
|
||||
this._processType = ProcessType.ExtensionTerminal;
|
||||
this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, undefined, cols, rows, this._configHelper);
|
||||
@@ -162,6 +162,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
|
||||
this._process = await this._launchProcess(shellLaunchConfig, cols, rows, this.userHome, isScreenReaderModeEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
this.processState = ProcessState.LAUNCHING;
|
||||
|
||||
this._process.onProcessData(data => {
|
||||
@@ -198,6 +199,13 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
|
||||
this.processState = ProcessState.RUNNING;
|
||||
}
|
||||
}, LAUNCHING_DURATION);
|
||||
|
||||
const error = await this._process.start();
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async _launchProcess(
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { TERMINAL_VIEW_ID, IShellLaunchConfig, ITerminalConfigHelper, ITerminalNativeService, ISpawnExtHostProcessRequest, IStartExtensionTerminalRequest, IAvailableShellsRequest, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, ITerminalProcessExtHostProxy, IShellDefinition, LinuxDistro, KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { TERMINAL_VIEW_ID, IShellLaunchConfig, ITerminalConfigHelper, ITerminalNativeService, ISpawnExtHostProcessRequest, IStartExtensionTerminalRequest, IAvailableShellsRequest, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, ITerminalProcessExtHostProxy, IShellDefinition, LinuxDistro, KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
@@ -147,18 +147,23 @@ export class TerminalService implements ITerminalService {
|
||||
return activeInstance ? activeInstance : this.createTerminal(undefined);
|
||||
}
|
||||
|
||||
public async requestSpawnExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI | undefined, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise<void> {
|
||||
public async requestSpawnExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI | undefined, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise<ITerminalLaunchError | undefined> {
|
||||
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 });
|
||||
return new Promise<ITerminalLaunchError | undefined>(callback => {
|
||||
this._onInstanceRequestSpawnExtHostProcess.fire({ proxy, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, isWorkspaceShellAllowed, callback });
|
||||
});
|
||||
}
|
||||
|
||||
public requestStartExtensionTerminal(proxy: ITerminalProcessExtHostProxy, cols: number, rows: number): void {
|
||||
this._onInstanceRequestStartExtensionTerminal.fire({ proxy, cols, rows });
|
||||
public requestStartExtensionTerminal(proxy: ITerminalProcessExtHostProxy, cols: number, rows: number): Promise<ITerminalLaunchError | undefined> {
|
||||
// The initial request came from the extension host, no need to wait for it
|
||||
return new Promise<ITerminalLaunchError | undefined>(callback => {
|
||||
this._onInstanceRequestStartExtensionTerminal.fire({ proxy, cols, rows, callback });
|
||||
});
|
||||
}
|
||||
|
||||
public async extHostReady(remoteAuthority: string): Promise<void> {
|
||||
|
||||
@@ -6,28 +6,27 @@
|
||||
import { Widget } from 'vs/base/browser/ui/widget';
|
||||
import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable';
|
||||
import { MarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { ITerminalWidget, IHoverTarget, IHoverAnchor, HorizontalAnchorSide, VerticalAnchorSide } from 'vs/workbench/contrib/terminal/browser/widgets/widgets';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { HoverWidget } from 'vs/workbench/contrib/terminal/browser/widgets/hoverWidget';
|
||||
import { ITerminalWidget } from 'vs/workbench/contrib/terminal/browser/widgets/widgets';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IHoverService, IHoverOptions } from 'vs/workbench/contrib/hover/browser/hover';
|
||||
|
||||
export class EnvironmentVariableInfoWidget extends Widget implements ITerminalWidget {
|
||||
readonly id = 'env-var-info';
|
||||
|
||||
private _domNode: HTMLElement | undefined;
|
||||
private _container: HTMLElement | undefined;
|
||||
private _hoverWidget: HoverWidget | undefined;
|
||||
private _mouseMoveListener: IDisposable | undefined;
|
||||
private _hoverOptions: IHoverOptions | undefined;
|
||||
|
||||
get requiresAction() { return this._info.requiresAction; }
|
||||
|
||||
constructor(
|
||||
private _info: IEnvironmentVariableInfo,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@IHoverService private readonly _hoverService: IHoverService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -36,9 +35,11 @@ export class EnvironmentVariableInfoWidget extends Widget implements ITerminalWi
|
||||
this._container = container;
|
||||
this._domNode = document.createElement('div');
|
||||
this._domNode.classList.add('terminal-env-var-info', 'codicon', `codicon-${this._info.getIcon()}`);
|
||||
if (this.requiresAction) {
|
||||
this._domNode.classList.add('requires-action');
|
||||
}
|
||||
container.appendChild(this._domNode);
|
||||
|
||||
|
||||
const timeout = this._configurationService.getValue<number>('editor.hover.delay');
|
||||
const scheduler: RunOnceScheduler = new RunOnceScheduler(() => this._showHover(), timeout);
|
||||
this._register(scheduler);
|
||||
@@ -71,42 +72,21 @@ export class EnvironmentVariableInfoWidget extends Widget implements ITerminalWi
|
||||
}
|
||||
|
||||
focus() {
|
||||
this._showHover();
|
||||
this._hoverWidget?.focus();
|
||||
this._showHover(true);
|
||||
}
|
||||
|
||||
private _showHover() {
|
||||
if (!this._domNode || !this._container || this._hoverWidget) {
|
||||
private _showHover(focus?: boolean) {
|
||||
if (!this._domNode || !this._container) {
|
||||
return;
|
||||
}
|
||||
const target = new ElementHoverTarget(this._domNode);
|
||||
const actions = this._info.getActions ? this._info.getActions() : undefined;
|
||||
this._hoverWidget = this._instantiationService.createInstance(HoverWidget, this._container, target, new MarkdownString(this._info.getInfo()), () => { }, actions);
|
||||
this._register(this._hoverWidget);
|
||||
this._register(this._hoverWidget.onDispose(() => this._hoverWidget = undefined));
|
||||
}
|
||||
}
|
||||
|
||||
class ElementHoverTarget implements IHoverTarget {
|
||||
readonly targetElements: readonly HTMLElement[];
|
||||
|
||||
constructor(
|
||||
private _element: HTMLElement
|
||||
) {
|
||||
this.targetElements = [this._element];
|
||||
}
|
||||
|
||||
get anchor(): IHoverAnchor {
|
||||
const position = dom.getDomNodePagePosition(this._element);
|
||||
return {
|
||||
x: position.left,
|
||||
horizontalAnchorSide: HorizontalAnchorSide.Left,
|
||||
y: document.documentElement.clientHeight - position.top - 1,
|
||||
verticalAnchorSide: VerticalAnchorSide.Bottom,
|
||||
fallbackY: position.top + position.height
|
||||
};
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
if (!this._hoverOptions) {
|
||||
const actions = this._info.getActions ? this._info.getActions() : undefined;
|
||||
this._hoverOptions = {
|
||||
target: this._domNode,
|
||||
text: new MarkdownString(this._info.getInfo()),
|
||||
actions
|
||||
};
|
||||
}
|
||||
this._hoverService.showHover(this._hoverOptions, focus);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,246 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { renderMarkdown } from 'vs/base/browser/markdownRenderer';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { editorHoverHighlight, editorHoverBackground, editorHoverBorder, textLinkForeground, editorHoverForeground, editorHoverStatusBarBackground, textCodeBlockBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IHoverTarget, HorizontalAnchorSide, VerticalAnchorSide } from 'vs/workbench/contrib/terminal/browser/widgets/widgets';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { HoverWidget as BaseHoverWidget, renderHoverAction } from 'vs/base/browser/ui/hover/hoverWidget';
|
||||
import { Widget } from 'vs/base/browser/ui/widget';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
export class HoverWidget extends Widget {
|
||||
private readonly _messageListeners = new DisposableStore();
|
||||
private readonly _mouseTracker: CompositeMouseTracker;
|
||||
|
||||
private readonly _hover: BaseHoverWidget;
|
||||
|
||||
private _isDisposed: boolean = false;
|
||||
|
||||
get isDisposed(): boolean { return this._isDisposed; }
|
||||
get domNode(): HTMLElement { return this._hover.containerDomNode; }
|
||||
|
||||
private readonly _onDispose = new Emitter<void>();
|
||||
get onDispose(): Event<void> { return this._onDispose.event; }
|
||||
|
||||
constructor(
|
||||
private _container: HTMLElement,
|
||||
private _target: IHoverTarget,
|
||||
private _text: IMarkdownString,
|
||||
private _linkHandler: (url: string) => void,
|
||||
private _actions: { label: string, iconClass?: string, run: (target: HTMLElement) => void, commandId: string }[] | undefined,
|
||||
@IKeybindingService private readonly _keybindingService: IKeybindingService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._hover = this._register(new BaseHoverWidget());
|
||||
this._hover.containerDomNode.classList.add('terminal-hover-widget', 'fadeIn', 'xterm-hover');
|
||||
|
||||
// Don't allow mousedown out of the widget, otherwise preventDefault will call and text will
|
||||
// not be selected.
|
||||
this.onmousedown(this._hover.containerDomNode, e => e.stopPropagation());
|
||||
|
||||
// Hide hover on escape
|
||||
this.onkeydown(this._hover.containerDomNode, e => {
|
||||
if (e.equals(KeyCode.Escape)) {
|
||||
this.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
const rowElement = $('div.hover-row.markdown-hover');
|
||||
const contentsElement = $('div.hover-contents');
|
||||
const markdownElement = renderMarkdown(this._text, {
|
||||
actionHandler: {
|
||||
callback: (content) => this._linkHandler(content),
|
||||
disposeables: this._messageListeners
|
||||
},
|
||||
codeBlockRenderer: async (_, value) => {
|
||||
const fontFamily = this._configurationService.getValue<IEditorOptions>('editor').fontFamily || EDITOR_FONT_DEFAULTS.fontFamily;
|
||||
return `<span style="font-family: ${fontFamily}; white-space: nowrap">${value.replace(/\n/g, '<br>')}</span>`;
|
||||
},
|
||||
codeBlockRenderCallback: () => {
|
||||
contentsElement.classList.add('code-hover-contents');
|
||||
this.layout();
|
||||
}
|
||||
});
|
||||
contentsElement.appendChild(markdownElement);
|
||||
rowElement.appendChild(contentsElement);
|
||||
this._hover.contentsDomNode.appendChild(rowElement);
|
||||
|
||||
if (this._actions && this._actions.length > 0) {
|
||||
const statusBarElement = $('div.hover-row.status-bar');
|
||||
const actionsElement = $('div.actions');
|
||||
this._actions.forEach(action => {
|
||||
const keybinding = this._keybindingService.lookupKeybinding(action.commandId);
|
||||
const keybindingLabel = keybinding ? keybinding.getLabel() : null;
|
||||
renderHoverAction(actionsElement, action, keybindingLabel);
|
||||
});
|
||||
statusBarElement.appendChild(actionsElement);
|
||||
this._hover.containerDomNode.appendChild(statusBarElement);
|
||||
}
|
||||
|
||||
this._mouseTracker = new CompositeMouseTracker([this._hover.containerDomNode, ..._target.targetElements]);
|
||||
this._register(this._mouseTracker.onMouseOut(() => this.dispose()));
|
||||
this._register(this._mouseTracker);
|
||||
|
||||
this._container.appendChild(this._hover.containerDomNode);
|
||||
|
||||
this.layout();
|
||||
}
|
||||
|
||||
public layout(): void {
|
||||
const anchor = this._target.anchor;
|
||||
|
||||
this._hover.containerDomNode.classList.remove('right-aligned');
|
||||
this._hover.contentsDomNode.style.maxHeight = '';
|
||||
if (anchor.horizontalAnchorSide === HorizontalAnchorSide.Left) {
|
||||
if (anchor.x + this._hover.containerDomNode.clientWidth > document.documentElement.clientWidth) {
|
||||
// Shift the hover to the left when part of it would get cut off
|
||||
const width = Math.round(this._hover.containerDomNode.clientWidth);
|
||||
this._hover.containerDomNode.style.width = `${width - 1}px`;
|
||||
this._hover.containerDomNode.style.maxWidth = '';
|
||||
const left = document.documentElement.clientWidth - width - 1;
|
||||
this._hover.containerDomNode.style.left = `${left}px`;
|
||||
// Right align if the right edge is closer to the anchor than the left edge
|
||||
if (left + width / 2 < anchor.x) {
|
||||
this._hover.containerDomNode.classList.add('right-aligned');
|
||||
}
|
||||
} else {
|
||||
this._hover.containerDomNode.style.width = '';
|
||||
this._hover.containerDomNode.style.maxWidth = `${document.documentElement.clientWidth - anchor.x - 1}px`;
|
||||
this._hover.containerDomNode.style.left = `${anchor.x}px`;
|
||||
}
|
||||
} else {
|
||||
this._hover.containerDomNode.style.right = `${anchor.x}px`;
|
||||
}
|
||||
// Use fallback y value if there is not enough vertical space
|
||||
if (anchor.verticalAnchorSide === VerticalAnchorSide.Bottom) {
|
||||
if (anchor.y + this._hover.containerDomNode.clientHeight > document.documentElement.clientHeight) {
|
||||
this._hover.containerDomNode.style.top = `${anchor.fallbackY}px`;
|
||||
this._hover.contentsDomNode.style.maxHeight = `${document.documentElement.clientHeight - anchor.fallbackY}px`;
|
||||
} else {
|
||||
this._hover.containerDomNode.style.bottom = `${anchor.y}px`;
|
||||
this._hover.containerDomNode.style.maxHeight = '';
|
||||
}
|
||||
} else {
|
||||
if (anchor.y + this._hover.containerDomNode.clientHeight > document.documentElement.clientHeight) {
|
||||
this._hover.containerDomNode.style.bottom = `${anchor.fallbackY}px`;
|
||||
} else {
|
||||
this._hover.containerDomNode.style.top = `${anchor.y}px`;
|
||||
}
|
||||
}
|
||||
this._hover.onContentsChanged();
|
||||
}
|
||||
|
||||
public focus() {
|
||||
this._hover.containerDomNode.focus();
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (!this._isDisposed) {
|
||||
this._onDispose.fire();
|
||||
this._hover.containerDomNode.parentElement?.removeChild(this.domNode);
|
||||
this._messageListeners.dispose();
|
||||
this._target.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
this._isDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
class CompositeMouseTracker extends Widget {
|
||||
private _isMouseIn: boolean = false;
|
||||
private _mouseTimeout: number | undefined;
|
||||
|
||||
private readonly _onMouseOut = new Emitter<void>();
|
||||
get onMouseOut(): Event<void> { return this._onMouseOut.event; }
|
||||
|
||||
constructor(
|
||||
private _elements: HTMLElement[]
|
||||
) {
|
||||
super();
|
||||
this._elements.forEach(n => this.onmouseover(n, () => this._onTargetMouseOver()));
|
||||
this._elements.forEach(n => this.onnonbubblingmouseout(n, () => this._onTargetMouseOut()));
|
||||
}
|
||||
|
||||
private _onTargetMouseOver(): void {
|
||||
this._isMouseIn = true;
|
||||
this._clearEvaluateMouseStateTimeout();
|
||||
}
|
||||
|
||||
private _onTargetMouseOut(): void {
|
||||
this._isMouseIn = false;
|
||||
this._evaluateMouseState();
|
||||
}
|
||||
|
||||
private _evaluateMouseState(): void {
|
||||
this._clearEvaluateMouseStateTimeout();
|
||||
// Evaluate whether the mouse is still outside asynchronously such that other mouse targets
|
||||
// have the opportunity to first their mouse in event.
|
||||
this._mouseTimeout = window.setTimeout(() => this._fireIfMouseOutside(), 0);
|
||||
}
|
||||
|
||||
private _clearEvaluateMouseStateTimeout(): void {
|
||||
if (this._mouseTimeout) {
|
||||
clearTimeout(this._mouseTimeout);
|
||||
this._mouseTimeout = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private _fireIfMouseOutside(): void {
|
||||
if (!this._isMouseIn) {
|
||||
this._onMouseOut.fire();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const editorHoverHighlightColor = theme.getColor(editorHoverHighlight);
|
||||
if (editorHoverHighlightColor) {
|
||||
collector.addRule(`.integrated-terminal .hoverHighlight { background-color: ${editorHoverHighlightColor}; }`);
|
||||
}
|
||||
const hoverBackground = theme.getColor(editorHoverBackground);
|
||||
if (hoverBackground) {
|
||||
collector.addRule(`.integrated-terminal .monaco-hover { background-color: ${hoverBackground}; }`);
|
||||
}
|
||||
const hoverBorder = theme.getColor(editorHoverBorder);
|
||||
if (hoverBorder) {
|
||||
collector.addRule(`.integrated-terminal .monaco-hover { border: 1px solid ${hoverBorder}; }`);
|
||||
collector.addRule(`.integrated-terminal .monaco-hover .hover-row:not(:first-child):not(:empty) { border-top: 1px solid ${hoverBorder.transparent(0.5)}; }`);
|
||||
collector.addRule(`.integrated-terminal .monaco-hover hr { border-top: 1px solid ${hoverBorder.transparent(0.5)}; }`);
|
||||
collector.addRule(`.integrated-terminal .monaco-hover hr { border-bottom: 0px solid ${hoverBorder.transparent(0.5)}; }`);
|
||||
}
|
||||
const link = theme.getColor(textLinkForeground);
|
||||
if (link) {
|
||||
collector.addRule(`.integrated-terminal .monaco-hover a { color: ${link}; }`);
|
||||
}
|
||||
const hoverForeground = theme.getColor(editorHoverForeground);
|
||||
if (hoverForeground) {
|
||||
collector.addRule(`.integrated-terminal .monaco-hover { color: ${hoverForeground}; }`);
|
||||
}
|
||||
const actionsBackground = theme.getColor(editorHoverStatusBarBackground);
|
||||
if (actionsBackground) {
|
||||
collector.addRule(`.integrated-terminal .monaco-hover .hover-row .actions { background-color: ${actionsBackground}; }`);
|
||||
}
|
||||
const codeBackground = theme.getColor(textCodeBlockBackground);
|
||||
if (codeBackground) {
|
||||
collector.addRule(`.integrated-terminal .monaco-hover code { background-color: ${codeBackground}; }`);
|
||||
}
|
||||
});
|
||||
@@ -6,11 +6,12 @@
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { Widget } from 'vs/base/browser/ui/widget';
|
||||
import { ITerminalWidget, IHoverAnchor, IHoverTarget, HorizontalAnchorSide, VerticalAnchorSide } from 'vs/workbench/contrib/terminal/browser/widgets/widgets';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { HoverWidget } from 'vs/workbench/contrib/terminal/browser/widgets/hoverWidget';
|
||||
import { ITerminalWidget } from 'vs/workbench/contrib/terminal/browser/widgets/widgets';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { IViewportRange } from 'xterm';
|
||||
import { IHoverTarget, IHoverService } from 'vs/workbench/contrib/hover/browser/hover';
|
||||
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { editorHoverHighlight } from 'vs/platform/theme/common/colorRegistry';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
@@ -28,8 +29,8 @@ export class TerminalHover extends Disposable implements ITerminalWidget {
|
||||
constructor(
|
||||
private readonly _targetOptions: ILinkHoverTargetOptions,
|
||||
private readonly _text: IMarkdownString,
|
||||
private readonly _linkHandler: (url: string) => void,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService
|
||||
private readonly _linkHandler: (url: string) => any,
|
||||
@IHoverService private readonly _hoverService: IHoverService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -40,92 +41,91 @@ export class TerminalHover extends Disposable implements ITerminalWidget {
|
||||
|
||||
attach(container: HTMLElement): void {
|
||||
const target = new CellHoverTarget(container, this._targetOptions);
|
||||
this._register(this._instantiationService.createInstance(HoverWidget, container, target, this._text, this._linkHandler, []));
|
||||
this._hoverService.showHover({
|
||||
target,
|
||||
text: this._text,
|
||||
linkHandler: this._linkHandler,
|
||||
// .xterm-hover lets xterm know that the hover is part of a link
|
||||
additionalClasses: ['xterm-hover']
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class CellHoverTarget extends Widget implements IHoverTarget {
|
||||
private _domNode: HTMLElement;
|
||||
private _isDisposed: boolean = false;
|
||||
private _domNode: HTMLElement | undefined;
|
||||
private readonly _targetElements: HTMLElement[] = [];
|
||||
|
||||
readonly targetElements: readonly HTMLElement[];
|
||||
get targetElements(): readonly HTMLElement[] { return this._targetElements; }
|
||||
|
||||
constructor(
|
||||
private readonly _container: HTMLElement,
|
||||
o: ILinkHoverTargetOptions
|
||||
container: HTMLElement,
|
||||
private readonly _options: ILinkHoverTargetOptions
|
||||
) {
|
||||
super();
|
||||
|
||||
this._domNode = $('div.terminal-hover-targets');
|
||||
const targets: HTMLElement[] = [];
|
||||
const rowCount = o.viewportRange.end.y - o.viewportRange.start.y + 1;
|
||||
this._domNode = $('div.terminal-hover-targets.xterm-hover');
|
||||
const rowCount = this._options.viewportRange.end.y - this._options.viewportRange.start.y + 1;
|
||||
|
||||
// Add top target row
|
||||
const width = (o.viewportRange.end.y > o.viewportRange.start.y ? o.terminalDimensions.width - o.viewportRange.start.x : o.viewportRange.end.x - o.viewportRange.start.x + 1) * o.cellDimensions.width;
|
||||
const width = (this._options.viewportRange.end.y > this._options.viewportRange.start.y ? this._options.terminalDimensions.width - this._options.viewportRange.start.x : this._options.viewportRange.end.x - this._options.viewportRange.start.x + 1) * this._options.cellDimensions.width;
|
||||
const topTarget = $('div.terminal-hover-target.hoverHighlight');
|
||||
topTarget.style.left = `${o.viewportRange.start.x * o.cellDimensions.width}px`;
|
||||
topTarget.style.bottom = `${(o.terminalDimensions.height - o.viewportRange.start.y - 1) * o.cellDimensions.height}px`;
|
||||
topTarget.style.left = `${this._options.viewportRange.start.x * this._options.cellDimensions.width}px`;
|
||||
topTarget.style.bottom = `${(this._options.terminalDimensions.height - this._options.viewportRange.start.y - 1) * this._options.cellDimensions.height}px`;
|
||||
topTarget.style.width = `${width}px`;
|
||||
topTarget.style.height = `${o.cellDimensions.height}px`;
|
||||
targets.push(this._domNode.appendChild(topTarget));
|
||||
topTarget.style.height = `${this._options.cellDimensions.height}px`;
|
||||
this._targetElements.push(this._domNode.appendChild(topTarget));
|
||||
|
||||
// Add middle target rows
|
||||
if (rowCount > 2) {
|
||||
const middleTarget = $('div.terminal-hover-target.hoverHighlight');
|
||||
middleTarget.style.left = `0px`;
|
||||
middleTarget.style.bottom = `${(o.terminalDimensions.height - o.viewportRange.start.y - 1 - (rowCount - 2)) * o.cellDimensions.height}px`;
|
||||
middleTarget.style.width = `${o.terminalDimensions.width * o.cellDimensions.width}px`;
|
||||
middleTarget.style.height = `${(rowCount - 2) * o.cellDimensions.height}px`;
|
||||
targets.push(this._domNode.appendChild(middleTarget));
|
||||
middleTarget.style.bottom = `${(this._options.terminalDimensions.height - this._options.viewportRange.start.y - 1 - (rowCount - 2)) * this._options.cellDimensions.height}px`;
|
||||
middleTarget.style.width = `${this._options.terminalDimensions.width * this._options.cellDimensions.width}px`;
|
||||
middleTarget.style.height = `${(rowCount - 2) * this._options.cellDimensions.height}px`;
|
||||
this._targetElements.push(this._domNode.appendChild(middleTarget));
|
||||
}
|
||||
|
||||
// Add bottom target row
|
||||
if (rowCount > 1) {
|
||||
const bottomTarget = $('div.terminal-hover-target.hoverHighlight');
|
||||
bottomTarget.style.left = `0px`;
|
||||
bottomTarget.style.bottom = `${(o.terminalDimensions.height - o.viewportRange.end.y - 1) * o.cellDimensions.height}px`;
|
||||
bottomTarget.style.width = `${(o.viewportRange.end.x + 1) * o.cellDimensions.width}px`;
|
||||
bottomTarget.style.height = `${o.cellDimensions.height}px`;
|
||||
targets.push(this._domNode.appendChild(bottomTarget));
|
||||
bottomTarget.style.bottom = `${(this._options.terminalDimensions.height - this._options.viewportRange.end.y - 1) * this._options.cellDimensions.height}px`;
|
||||
bottomTarget.style.width = `${(this._options.viewportRange.end.x + 1) * this._options.cellDimensions.width}px`;
|
||||
bottomTarget.style.height = `${this._options.cellDimensions.height}px`;
|
||||
this._targetElements.push(this._domNode.appendChild(bottomTarget));
|
||||
}
|
||||
|
||||
this.targetElements = targets;
|
||||
|
||||
if (o.modifierDownCallback && o.modifierUpCallback) {
|
||||
if (this._options.modifierDownCallback && this._options.modifierUpCallback) {
|
||||
let down = false;
|
||||
this._register(dom.addDisposableListener(document, 'keydown', e => {
|
||||
if (e.ctrlKey && !down) {
|
||||
down = true;
|
||||
o.modifierDownCallback!();
|
||||
this._options.modifierDownCallback!();
|
||||
}
|
||||
}));
|
||||
this._register(dom.addDisposableListener(document, 'keyup', e => {
|
||||
if (!e.ctrlKey) {
|
||||
down = false;
|
||||
o.modifierUpCallback!();
|
||||
this._options.modifierUpCallback!();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
this._container.appendChild(this._domNode);
|
||||
container.appendChild(this._domNode);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
if (!this._isDisposed) {
|
||||
this._container.removeChild(this._domNode);
|
||||
}
|
||||
this._isDisposed = true;
|
||||
this._domNode?.parentElement?.removeChild(this._domNode);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
get anchor(): IHoverAnchor {
|
||||
const firstPosition = dom.getDomNodePagePosition(this.targetElements[0]);
|
||||
return {
|
||||
x: firstPosition.left,
|
||||
horizontalAnchorSide: HorizontalAnchorSide.Left,
|
||||
y: document.documentElement.clientHeight - firstPosition.top - 1,
|
||||
verticalAnchorSide: VerticalAnchorSide.Bottom,
|
||||
fallbackY: firstPosition.top + firstPosition.height - 1
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
let editorHoverHighlightColor = theme.getColor(editorHoverHighlight);
|
||||
if (editorHoverHighlightColor) {
|
||||
if (editorHoverHighlightColor.isOpaque()) {
|
||||
editorHoverHighlightColor = editorHoverHighlightColor.transparent(0.5);
|
||||
}
|
||||
collector.addRule(`.integrated-terminal .hoverHighlight { background-color: ${editorHoverHighlightColor}; }`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -12,32 +12,3 @@ export interface ITerminalWidget extends IDisposable {
|
||||
id: string;
|
||||
attach(container: HTMLElement): void;
|
||||
}
|
||||
|
||||
export enum HorizontalAnchorSide {
|
||||
Left,
|
||||
Right
|
||||
}
|
||||
|
||||
export enum VerticalAnchorSide {
|
||||
Top,
|
||||
Bottom
|
||||
}
|
||||
|
||||
export interface IHoverAnchor {
|
||||
x: number;
|
||||
y: number;
|
||||
horizontalAnchorSide: HorizontalAnchorSide;
|
||||
verticalAnchorSide: VerticalAnchorSide;
|
||||
/**
|
||||
* Fallback Y value to try with opposite VerticalAlignment if the hover does not fit vertically.
|
||||
*/
|
||||
fallbackY: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* A target for a hover which can know about domain-specific locations.
|
||||
*/
|
||||
export interface IHoverTarget extends IDisposable {
|
||||
readonly targetElements: readonly HTMLElement[];
|
||||
readonly anchor: IHoverAnchor;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ export interface IMergedEnvironmentVariableCollection {
|
||||
* Tracks and persists environment variable collections as defined by extensions.
|
||||
*/
|
||||
export interface IEnvironmentVariableService {
|
||||
_serviceBrand: undefined;
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
/**
|
||||
* Gets a single collection constructed by merging all environment variable collections into
|
||||
|
||||
@@ -22,7 +22,7 @@ interface ISerializableExtensionEnvironmentVariableCollection {
|
||||
* Tracks and persists environment variable collections as defined by extensions.
|
||||
*/
|
||||
export class EnvironmentVariableService implements IEnvironmentVariableService {
|
||||
_serviceBrand: undefined;
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
collections: Map<string, IEnvironmentVariableCollectionWithPersistence> = new Map();
|
||||
mergedCollection: IMergedEnvironmentVariableCollection;
|
||||
|
||||
@@ -70,10 +70,6 @@ export const TERMINAL_ACTION_CATEGORY = nls.localize('terminalCategory', "Termin
|
||||
export const DEFAULT_LETTER_SPACING = 0;
|
||||
export const MINIMUM_LETTER_SPACING = -5;
|
||||
export const DEFAULT_LINE_HEIGHT = 1;
|
||||
export const SHELL_PATH_INVALID_EXIT_CODE = -1;
|
||||
export const SHELL_PATH_DIRECTORY_EXIT_CODE = -2;
|
||||
export const SHELL_CWD_INVALID_EXIT_CODE = -3;
|
||||
export const LEGACY_CONSOLE_MODE_EXIT_CODE = 3221225786; // microsoft/vscode#73790
|
||||
|
||||
export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
|
||||
|
||||
@@ -237,7 +233,7 @@ export interface IShellLaunchConfig {
|
||||
* Provides access to native or electron APIs to other terminal services.
|
||||
*/
|
||||
export interface ITerminalNativeService {
|
||||
_serviceBrand: undefined;
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
readonly linuxDistro: LinuxDistro;
|
||||
|
||||
@@ -308,7 +304,7 @@ export interface ITerminalProcessManager extends IDisposable {
|
||||
readonly onEnvironmentVariableInfoChanged: Event<IEnvironmentVariableInfo>;
|
||||
|
||||
dispose(immediate?: boolean): void;
|
||||
createProcess(shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number, isScreenReaderModeEnabled: boolean): Promise<void>;
|
||||
createProcess(shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number, isScreenReaderModeEnabled: boolean): Promise<ITerminalLaunchError | undefined>;
|
||||
write(data: string): void;
|
||||
setDimensions(cols: number, rows: number): void;
|
||||
|
||||
@@ -364,12 +360,14 @@ export interface ISpawnExtHostProcessRequest {
|
||||
cols: number;
|
||||
rows: number;
|
||||
isWorkspaceShellAllowed: boolean;
|
||||
callback: (error: ITerminalLaunchError | undefined) => void;
|
||||
}
|
||||
|
||||
export interface IStartExtensionTerminalRequest {
|
||||
proxy: ITerminalProcessExtHostProxy;
|
||||
cols: number;
|
||||
rows: number;
|
||||
callback: (error: ITerminalLaunchError | undefined) => void;
|
||||
}
|
||||
|
||||
export interface IAvailableShellsRequest {
|
||||
@@ -402,6 +400,11 @@ export interface IWindowsShellHelper extends IDisposable {
|
||||
getShellName(): Promise<string>;
|
||||
}
|
||||
|
||||
export interface ITerminalLaunchError {
|
||||
message: string;
|
||||
code?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface representing a raw terminal child process, this contains a subset of the
|
||||
* child_process.ChildProcess node.js interface.
|
||||
@@ -414,6 +417,14 @@ export interface ITerminalChildProcess {
|
||||
onProcessOverrideDimensions?: Event<ITerminalDimensions | undefined>;
|
||||
onProcessResolvedShellLaunchConfig?: Event<IShellLaunchConfig>;
|
||||
|
||||
/**
|
||||
* Starts the process.
|
||||
*
|
||||
* @returns undefined when the process was successfully started, otherwise an object containing
|
||||
* information on what went wrong.
|
||||
*/
|
||||
start(): Promise<ITerminalLaunchError | undefined>;
|
||||
|
||||
/**
|
||||
* Shutdown the terminal process.
|
||||
*
|
||||
|
||||
@@ -15,24 +15,29 @@ import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { registerRemoteContributions } from 'vs/workbench/contrib/terminal/electron-browser/terminalRemote';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export class TerminalNativeService implements ITerminalNativeService {
|
||||
export class TerminalNativeService extends Disposable implements ITerminalNativeService {
|
||||
public _serviceBrand: undefined;
|
||||
|
||||
public get linuxDistro(): LinuxDistro { return linuxDistro; }
|
||||
|
||||
private readonly _onOpenFileRequest = new Emitter<IOpenFileRequest>();
|
||||
private readonly _onOpenFileRequest = this._register(new Emitter<IOpenFileRequest>());
|
||||
public get onOpenFileRequest(): Event<IOpenFileRequest> { return this._onOpenFileRequest.event; }
|
||||
private readonly _onOsResume = new Emitter<void>();
|
||||
private readonly _onOsResume = this._register(new Emitter<void>());
|
||||
public get onOsResume(): Event<void> { return this._onOsResume.event; }
|
||||
|
||||
constructor(
|
||||
@IFileService private readonly _fileService: IFileService,
|
||||
@IInstantiationService readonly instantiationService: IInstantiationService,
|
||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService
|
||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
||||
@IElectronService electronService: IElectronService
|
||||
) {
|
||||
super();
|
||||
|
||||
ipcRenderer.on('vscode:openFiles', (event: unknown, request: IOpenFileRequest) => this._onOpenFileRequest.fire(request));
|
||||
ipcRenderer.on('vscode:osResume', () => this._onOsResume.fire());
|
||||
this._register(electronService.onOSResume(() => this._onOsResume.fire()));
|
||||
|
||||
const connection = remoteAgentService.getConnection();
|
||||
if (connection && connection.remoteAuthority) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -37,7 +37,7 @@ class MockTerminalInstanceService implements ITerminalInstanceService {
|
||||
getDefaultShellAndArgs(): Promise<{ shell: string; args: string | string[] | undefined; }> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
_serviceBrand: undefined;
|
||||
declare readonly _serviceBrand: undefined;
|
||||
getXtermConstructor(): Promise<any> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user