mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-31 17:20:28 -04:00
Merge from vscode 2c306f762bf9c3db82dc06c7afaa56ef46d72f79 (#14050)
* Merge from vscode 2c306f762bf9c3db82dc06c7afaa56ef46d72f79 * Fix breaks * Extension management fixes * Fix breaks in windows bundling * Fix/skip failing tests * Update distro * Add clear to nuget.config * Add hygiene task * Bump distro * Fix hygiene issue * Add build to hygiene exclusion * Update distro * Update hygiene * Hygiene exclusions * Update tsconfig * Bump distro for server breaks * Update build config * Update darwin path * Add done calls to notebook tests * Skip failing tests * Disable smoke tests
This commit is contained in:
@@ -7,6 +7,8 @@ import { IEnvironmentVariableInfo, IMergedEnvironmentVariableCollection, IMerged
|
||||
import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
|
||||
export class EnvironmentVariableInfoStale implements IEnvironmentVariableInfo {
|
||||
readonly requiresAction = true;
|
||||
@@ -53,8 +55,8 @@ export class EnvironmentVariableInfoStale implements IEnvironmentVariableInfo {
|
||||
return info;
|
||||
}
|
||||
|
||||
getIcon(): string {
|
||||
return 'warning';
|
||||
getIcon(): ThemeIcon {
|
||||
return Codicon.warning;
|
||||
}
|
||||
|
||||
getActions(): { label: string, iconClass?: string, run: () => void, commandId: string }[] {
|
||||
@@ -83,8 +85,8 @@ export class EnvironmentVariableInfoChangesActive implements IEnvironmentVariabl
|
||||
return message + '\n\n```\n' + changes.join('\n') + '\n```';
|
||||
}
|
||||
|
||||
getIcon(): string {
|
||||
return 'info';
|
||||
getIcon(): ThemeIcon {
|
||||
return Codicon.info;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import type { Terminal, IViewportRange, ILinkProvider } from 'xterm';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { posix, win32 } from 'vs/base/common/path';
|
||||
import { ITerminalExternalLinkProvider, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { OperatingSystem, isMacintosh, OS } from 'vs/base/common/platform';
|
||||
import { OperatingSystem, isMacintosh, OS, isWindows } from 'vs/base/common/platform';
|
||||
import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { TerminalProtocolLinkProvider } from 'vs/workbench/contrib/terminal/browser/links/terminalProtocolLinkProvider';
|
||||
import { TerminalValidatedLocalLinkProvider, lineAndColumnClause, unixLocalLinkClause, winLocalLinkClause, winDrivePrefix, winLineAndColumnMatchIndex, unixLineAndColumnMatchIndex, lineAndColumnClauseGroupCount } from 'vs/workbench/contrib/terminal/browser/links/terminalValidatedLocalLinkProvider';
|
||||
@@ -34,6 +34,7 @@ export type XtermLinkMatcherValidationCallback = (uri: string, callback: (isVali
|
||||
interface IPath {
|
||||
join(...paths: string[]): string;
|
||||
normalize(path: string): string;
|
||||
sep: '\\' | '/';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -191,8 +192,10 @@ export class TerminalLinkManager extends DisposableStore {
|
||||
// Check if it's a file:/// link, hand off to local link handler so to open an editor and
|
||||
// respect line/col attachment
|
||||
const uri = URI.parse(link);
|
||||
if (uri.scheme === 'file') {
|
||||
this._handleLocalLink(uri.fsPath);
|
||||
if (uri.scheme === Schemas.file) {
|
||||
// Just using fsPath here is unsafe: https://github.com/microsoft/vscode/issues/109076
|
||||
const fsPath = uri.fsPath;
|
||||
this._handleLocalLink(((this.osPath.sep === posix.sep) && isWindows) ? fsPath.replace(/\\/g, posix.sep) : fsPath);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { TerminalLink, OPEN_FILE_LABEL } from 'vs/workbench/contrib/terminal/bro
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { TerminalBaseLinkProvider } from 'vs/workbench/contrib/terminal/browser/links/terminalBaseLinkProvider';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
export class TerminalProtocolLinkProvider extends TerminalBaseLinkProvider {
|
||||
private _linkComputerTarget: ILinkComputerTarget | undefined;
|
||||
@@ -51,7 +52,7 @@ export class TerminalProtocolLinkProvider extends TerminalBaseLinkProvider {
|
||||
const uri = link.url
|
||||
? (typeof link.url === 'string' ? URI.parse(link.url) : link.url)
|
||||
: undefined;
|
||||
const label = (uri?.scheme === 'file') ? OPEN_FILE_LABEL : undefined;
|
||||
const label = (uri?.scheme === Schemas.file) ? OPEN_FILE_LABEL : undefined;
|
||||
return this._instantiationService.createInstance(TerminalLink, this._xterm, range, link.url?.toString() || '', this._xterm.buffer.active.viewportY, this._activateCallback, this._tooltipCallback, true, label);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -49,6 +49,8 @@ export const unixLineAndColumnMatchIndex = 11;
|
||||
// Each line and column clause have 6 groups (ie no. of expressions in round brackets)
|
||||
export const lineAndColumnClauseGroupCount = 6;
|
||||
|
||||
const MAX_LENGTH = 2000;
|
||||
|
||||
export class TerminalValidatedLocalLinkProvider extends TerminalBaseLinkProvider {
|
||||
constructor(
|
||||
private readonly _xterm: Terminal,
|
||||
@@ -85,6 +87,9 @@ export class TerminalValidatedLocalLinkProvider extends TerminalBaseLinkProvider
|
||||
}
|
||||
|
||||
const text = getXtermLineContent(this._xterm.buffer.active, startLine, endLine, this._xterm.cols);
|
||||
if (text.length > MAX_LENGTH) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// clone regex to do a global search on text
|
||||
const rex = new RegExp(this._localLinkRegex, 'g');
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
transition: background-color 800ms linear;
|
||||
}
|
||||
|
||||
.monaco-workbench .pane-body.integrated-terminal .xterm-viewport {
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.monaco-workbench .pane-body.integrated-terminal .xterm-viewport::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
@@ -196,7 +196,3 @@
|
||||
padding: 0 22px 0 6px;
|
||||
}
|
||||
|
||||
/* HACK: Can remove when fixed upstream https://github.com/xtermjs/xterm.js/issues/3058 */
|
||||
.xterm-helper-textarea {
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
@@ -62,10 +62,10 @@
|
||||
}
|
||||
|
||||
.xterm .xterm-helper-textarea {
|
||||
/*
|
||||
* HACK: to fix IE's blinking cursor
|
||||
* Move textarea out of the screen to the far left, so that the cursor is not visible.
|
||||
*/
|
||||
padding: 0;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
/* Move textarea out of the screen to the far left, so that the cursor is not visible */
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
left: -9999em;
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { Barrier } from 'vs/base/common/async';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { revive } from 'vs/base/common/marshalling';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IRemoteTerminalService, ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { IRemoteTerminalProcessExecCommandEvent, IShellLaunchConfigDto, RemoteTerminalChannelClient, REMOTE_TERMINAL_CHANNEL_NAME } from 'vs/workbench/contrib/terminal/common/remoteTerminalChannel';
|
||||
import { IProcessDataEvent, IRemoteTerminalAttachTarget, IShellLaunchConfig, ITerminalChildProcess, ITerminalConfigHelper, ITerminalDimensionsOverride, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
|
||||
export class RemoteTerminalService extends Disposable implements IRemoteTerminalService {
|
||||
public _serviceBrand: undefined;
|
||||
|
||||
private readonly _remoteTerminalChannel: RemoteTerminalChannelClient | null;
|
||||
private _hasConnectedToRemote = false;
|
||||
|
||||
constructor(
|
||||
@ITerminalInstanceService readonly terminalInstanceService: ITerminalInstanceService,
|
||||
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
) {
|
||||
super();
|
||||
const connection = this._remoteAgentService.getConnection();
|
||||
if (connection) {
|
||||
this._remoteTerminalChannel = this._instantiationService.createInstance(RemoteTerminalChannelClient, connection.remoteAuthority, connection.getChannel(REMOTE_TERMINAL_CHANNEL_NAME));
|
||||
} else {
|
||||
this._remoteTerminalChannel = null;
|
||||
}
|
||||
}
|
||||
|
||||
public async createRemoteTerminalProcess(terminalId: number, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI | undefined, cols: number, rows: number, configHelper: ITerminalConfigHelper,): Promise<ITerminalChildProcess> {
|
||||
if (!this._remoteTerminalChannel) {
|
||||
throw new Error(`Cannot create remote terminal when there is no remote!`);
|
||||
}
|
||||
|
||||
let isPreconnectionTerminal = false;
|
||||
if (!this._hasConnectedToRemote) {
|
||||
isPreconnectionTerminal = true;
|
||||
this._remoteAgentService.getEnvironment().then(() => {
|
||||
this._hasConnectedToRemote = true;
|
||||
});
|
||||
}
|
||||
|
||||
return new RemoteTerminalProcess(terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, configHelper, isPreconnectionTerminal, this._remoteTerminalChannel, this._remoteAgentService, this._logService, this._commandService);
|
||||
}
|
||||
|
||||
public async listTerminals(isInitialization = false): Promise<IRemoteTerminalAttachTarget[]> {
|
||||
const terms = this._remoteTerminalChannel ? await this._remoteTerminalChannel.listTerminals(isInitialization) : [];
|
||||
return terms.map(termDto => {
|
||||
return <IRemoteTerminalAttachTarget>{
|
||||
id: termDto.id,
|
||||
pid: termDto.pid,
|
||||
title: termDto.title,
|
||||
cwd: termDto.cwd,
|
||||
workspaceId: termDto.workspaceId,
|
||||
workspaceName: termDto.workspaceName
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class RemoteTerminalProcess extends Disposable implements ITerminalChildProcess {
|
||||
|
||||
public readonly _onProcessData = this._register(new Emitter<IProcessDataEvent>());
|
||||
public readonly onProcessData: Event<IProcessDataEvent> = this._onProcessData.event;
|
||||
private readonly _onProcessExit = this._register(new Emitter<number | undefined>());
|
||||
public readonly onProcessExit: Event<number | undefined> = this._onProcessExit.event;
|
||||
public readonly _onProcessReady = this._register(new Emitter<{ pid: number, cwd: string }>());
|
||||
public get onProcessReady(): Event<{ pid: number, cwd: string }> { return this._onProcessReady.event; }
|
||||
private readonly _onProcessTitleChanged = this._register(new Emitter<string>());
|
||||
public readonly onProcessTitleChanged: Event<string> = this._onProcessTitleChanged.event;
|
||||
private readonly _onProcessOverrideDimensions = this._register(new Emitter<ITerminalDimensionsOverride | undefined>());
|
||||
public readonly onProcessOverrideDimensions: Event<ITerminalDimensionsOverride | undefined> = this._onProcessOverrideDimensions.event;
|
||||
private readonly _onProcessResolvedShellLaunchConfig = this._register(new Emitter<IShellLaunchConfig>());
|
||||
public get onProcessResolvedShellLaunchConfig(): Event<IShellLaunchConfig> { return this._onProcessResolvedShellLaunchConfig.event; }
|
||||
|
||||
private _startBarrier: Barrier;
|
||||
private _remoteTerminalId: number;
|
||||
|
||||
private _inReplay = false;
|
||||
|
||||
constructor(
|
||||
private readonly _terminalId: number,
|
||||
private readonly _shellLaunchConfig: IShellLaunchConfig,
|
||||
private readonly _activeWorkspaceRootUri: URI | undefined,
|
||||
private readonly _cols: number,
|
||||
private readonly _rows: number,
|
||||
private readonly _configHelper: ITerminalConfigHelper,
|
||||
private readonly _isPreconnectionTerminal: boolean,
|
||||
private readonly _remoteTerminalChannel: RemoteTerminalChannelClient,
|
||||
private readonly _remoteAgentService: IRemoteAgentService,
|
||||
private readonly _logService: ILogService,
|
||||
private readonly _commandService: ICommandService,
|
||||
) {
|
||||
super();
|
||||
this._startBarrier = new Barrier();
|
||||
this._remoteTerminalId = 0;
|
||||
|
||||
if (this._isPreconnectionTerminal) {
|
||||
// Add a loading title only if this terminal is
|
||||
// instantiated before a connection is up and running
|
||||
setTimeout(() => this._onProcessTitleChanged.fire(nls.localize('terminal.integrated.starting', "Starting...")), 0);
|
||||
}
|
||||
}
|
||||
|
||||
public async start(): Promise<ITerminalLaunchError | undefined> {
|
||||
// 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');
|
||||
}
|
||||
|
||||
if (!this._shellLaunchConfig.remoteAttach) {
|
||||
const isWorkspaceShellAllowed = this._configHelper.checkWorkspaceShellPermissions(env.os);
|
||||
|
||||
const shellLaunchConfigDto: IShellLaunchConfigDto = {
|
||||
name: this._shellLaunchConfig.name,
|
||||
executable: this._shellLaunchConfig.executable,
|
||||
args: this._shellLaunchConfig.args,
|
||||
cwd: this._shellLaunchConfig.cwd,
|
||||
env: this._shellLaunchConfig.env
|
||||
};
|
||||
|
||||
this._logService.trace('Spawning remote agent process', { terminalId: this._terminalId, shellLaunchConfigDto });
|
||||
|
||||
const result = await this._remoteTerminalChannel.createTerminalProcess(
|
||||
shellLaunchConfigDto,
|
||||
this._activeWorkspaceRootUri,
|
||||
!this._shellLaunchConfig.isFeatureTerminal && this._configHelper.config.enablePersistentSessions,
|
||||
this._cols,
|
||||
this._rows,
|
||||
isWorkspaceShellAllowed,
|
||||
);
|
||||
|
||||
this._remoteTerminalId = result.terminalId;
|
||||
this.setupTerminalEventListener();
|
||||
this._onProcessResolvedShellLaunchConfig.fire(reviveIShellLaunchConfig(result.resolvedShellLaunchConfig));
|
||||
|
||||
const startResult = await this._remoteTerminalChannel.startTerminalProcess(this._remoteTerminalId);
|
||||
|
||||
if (typeof startResult !== 'undefined') {
|
||||
// An error occurred
|
||||
return startResult;
|
||||
}
|
||||
} else {
|
||||
this._remoteTerminalId = this._shellLaunchConfig.remoteAttach.id;
|
||||
this._onProcessReady.fire({ pid: this._shellLaunchConfig.remoteAttach.pid, cwd: this._shellLaunchConfig.remoteAttach.cwd });
|
||||
this.setupTerminalEventListener();
|
||||
|
||||
setTimeout(() => {
|
||||
this._onProcessTitleChanged.fire(this._shellLaunchConfig.remoteAttach!.title);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
this._startBarrier.open();
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public shutdown(immediate: boolean): void {
|
||||
this._startBarrier.wait().then(_ => {
|
||||
this._remoteTerminalChannel.shutdownTerminalProcess(this._remoteTerminalId, immediate);
|
||||
});
|
||||
}
|
||||
|
||||
public input(data: string): void {
|
||||
if (this._inReplay) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._startBarrier.wait().then(_ => {
|
||||
this._remoteTerminalChannel.sendInputToTerminalProcess(this._remoteTerminalId, data);
|
||||
});
|
||||
}
|
||||
|
||||
private setupTerminalEventListener(): void {
|
||||
this._register(this._remoteTerminalChannel.onTerminalProcessEvent(this._remoteTerminalId)(event => {
|
||||
switch (event.type) {
|
||||
case 'ready':
|
||||
return this._onProcessReady.fire({ pid: event.pid, cwd: event.cwd });
|
||||
case 'titleChanged':
|
||||
return this._onProcessTitleChanged.fire(event.title);
|
||||
case 'data':
|
||||
return this._onProcessData.fire({ data: event.data, sync: false });
|
||||
case 'replay': {
|
||||
try {
|
||||
this._inReplay = true;
|
||||
|
||||
for (const e of event.events) {
|
||||
if (e.cols !== 0 || e.rows !== 0) {
|
||||
// never override with 0x0 as that is a marker for an unknown initial size
|
||||
this._onProcessOverrideDimensions.fire({ cols: e.cols, rows: e.rows, forceExactSize: true });
|
||||
}
|
||||
this._onProcessData.fire({ data: e.data, sync: true });
|
||||
}
|
||||
} finally {
|
||||
this._inReplay = false;
|
||||
}
|
||||
|
||||
// remove size override
|
||||
this._onProcessOverrideDimensions.fire(undefined);
|
||||
|
||||
return;
|
||||
}
|
||||
case 'exit':
|
||||
return this._onProcessExit.fire(event.exitCode);
|
||||
case 'execCommand':
|
||||
return this._execCommand(event);
|
||||
case 'orphan?': {
|
||||
this._remoteTerminalChannel.orphanQuestionReply(this._remoteTerminalId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public resize(cols: number, rows: number): void {
|
||||
if (this._inReplay) {
|
||||
return;
|
||||
}
|
||||
this._startBarrier.wait().then(_ => {
|
||||
|
||||
this._remoteTerminalChannel.resizeTerminalProcess(this._remoteTerminalId, cols, rows);
|
||||
});
|
||||
}
|
||||
|
||||
public async getInitialCwd(): Promise<string> {
|
||||
await this._startBarrier.wait();
|
||||
return this._remoteTerminalChannel.getTerminalInitialCwd(this._remoteTerminalId);
|
||||
}
|
||||
|
||||
public async getCwd(): Promise<string> {
|
||||
await this._startBarrier.wait();
|
||||
return this._remoteTerminalChannel.getTerminalCwd(this._remoteTerminalId);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO@roblourens I don't think this does anything useful in the EH and the value isn't used
|
||||
*/
|
||||
public async getLatency(): Promise<number> {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private async _execCommand(event: IRemoteTerminalProcessExecCommandEvent): Promise<void> {
|
||||
const reqId = event.reqId;
|
||||
const commandArgs = event.commandArgs.map(arg => revive(arg));
|
||||
try {
|
||||
const result = await this._commandService.executeCommand(event.commandId, ...commandArgs);
|
||||
this._remoteTerminalChannel.sendCommandResultToTerminalProcess(this._remoteTerminalId, reqId, false, result);
|
||||
} catch (err) {
|
||||
this._remoteTerminalChannel.sendCommandResultToTerminalProcess(this._remoteTerminalId, reqId, true, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function reviveIShellLaunchConfig(dto: IShellLaunchConfigDto): IShellLaunchConfig {
|
||||
return {
|
||||
name: dto.name,
|
||||
executable: dto.executable,
|
||||
args: dto.args,
|
||||
cwd: (
|
||||
(typeof dto.cwd === 'string' || typeof dto.cwd === 'undefined')
|
||||
? dto.cwd
|
||||
: URI.revive(dto.cwd)
|
||||
),
|
||||
env: dto.env,
|
||||
hideFromUser: dto.hideFromUser
|
||||
};
|
||||
}
|
||||
|
||||
registerSingleton(IRemoteTerminalService, RemoteTerminalService);
|
||||
@@ -17,7 +17,7 @@ import { KeybindingWeight, KeybindingsRegistry, IKeybindings } from 'vs/platform
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import * as panel from 'vs/workbench/browser/panel';
|
||||
import { getQuickNavigateHandler } from 'vs/workbench/browser/quickaccess';
|
||||
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
|
||||
import { CATEGORIES, Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
|
||||
import { Extensions as ViewContainerExtensions, IViewContainersRegistry, ViewContainerLocation, IViewsRegistry } from 'vs/workbench/common/views';
|
||||
import { registerTerminalActions, ClearTerminalAction, CopyTerminalSelectionAction, CreateNewTerminalAction, KillTerminalAction, SelectAllTerminalAction, SelectDefaultShellWindowsTerminalAction, SplitInActiveWorkspaceTerminalAction, SplitTerminalAction, TerminalPasteAction, ToggleTerminalAction, terminalSendSequenceCommand } from 'vs/workbench/contrib/terminal/browser/terminalActions';
|
||||
import { TerminalViewPane } from 'vs/workbench/contrib/terminal/browser/terminalView';
|
||||
@@ -36,6 +36,7 @@ import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/pl
|
||||
import { TerminalQuickAccessProvider } from 'vs/workbench/contrib/terminal/browser/terminalQuickAccess';
|
||||
import { terminalConfiguration } from 'vs/workbench/contrib/terminal/common/terminalConfiguration';
|
||||
import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { terminalViewIcon } from 'vs/workbench/contrib/terminal/browser/terminalIcons';
|
||||
|
||||
// Register services
|
||||
registerSingleton(ITerminalService, TerminalService, true);
|
||||
@@ -63,7 +64,7 @@ configurationRegistry.registerConfiguration(terminalConfiguration);
|
||||
const VIEW_CONTAINER = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({
|
||||
id: TERMINAL_VIEW_ID,
|
||||
name: nls.localize('terminal', "Terminal"),
|
||||
icon: 'codicon-terminal',
|
||||
icon: terminalViewIcon,
|
||||
ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [TERMINAL_VIEW_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]),
|
||||
storageId: TERMINAL_VIEW_ID,
|
||||
focusCommand: { id: TERMINAL_COMMAND_ID.FOCUS },
|
||||
@@ -74,7 +75,7 @@ Registry.as<panel.PanelRegistry>(panel.Extensions.Panels).setDefaultPanelId(TERM
|
||||
Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry).registerViews([{
|
||||
id: TERMINAL_VIEW_ID,
|
||||
name: nls.localize('terminal', "Terminal"),
|
||||
containerIcon: 'codicon-terminal',
|
||||
containerIcon: terminalViewIcon,
|
||||
canToggleVisibility: false,
|
||||
canMoveView: true,
|
||||
ctorDescriptor: new SyncDescriptor(TerminalViewPane)
|
||||
@@ -101,7 +102,7 @@ actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(SelectAllTermin
|
||||
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleTerminalAction, {
|
||||
primary: KeyMod.CtrlCmd | KeyCode.US_BACKTICK,
|
||||
mac: { primary: KeyMod.WinCtrl | KeyCode.US_BACKTICK }
|
||||
}), 'View: Toggle Integrated Terminal', nls.localize('viewCategory', "View"));
|
||||
}), 'View: Toggle Integrated Terminal', CATEGORIES.View.value);
|
||||
// Weight is higher than work workbench contributions so the keybinding remains
|
||||
// highest priority when chords are registered afterwards
|
||||
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(ClearTerminalAction, {
|
||||
@@ -145,21 +146,22 @@ function registerSendSequenceKeybinding(text: string, rule: { when?: ContextKeyE
|
||||
const CTRL_LETTER_OFFSET = 64;
|
||||
|
||||
if (BrowserFeatures.clipboard.readText) {
|
||||
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(TerminalPasteAction, {
|
||||
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(TerminalPasteAction, platform.isMacintosh && platform.isWeb ? undefined : {
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KEY_V,
|
||||
win: { primary: KeyMod.CtrlCmd | KeyCode.KEY_V, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_V] },
|
||||
linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_V }
|
||||
}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Paste into Active Terminal', category, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED);
|
||||
// An extra Windows-only ctrl+v keybinding is used for pwsh that sends ctrl+v directly to the
|
||||
// shell, this gets handled by PSReadLine which properly handles multi-line pastes. This is
|
||||
// disabled in accessibility mode as PowerShell does not run PSReadLine when it detects a screen
|
||||
// reader.
|
||||
if (platform.isWindows) {
|
||||
registerSendSequenceKeybinding(String.fromCharCode('V'.charCodeAt(0) - CTRL_LETTER_OFFSET), { // ctrl+v
|
||||
when: ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, ContextKeyExpr.equals(KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE_KEY, WindowsShellType.PowerShell), CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()),
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KEY_V
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// An extra Windows-only ctrl+v keybinding is used for pwsh that sends ctrl+v directly to the
|
||||
// shell, this gets handled by PSReadLine which properly handles multi-line pastes. This is
|
||||
// disabled in accessibility mode as PowerShell does not run PSReadLine when it detects a screen
|
||||
// reader. This works even when clipboard.readText is not supported.
|
||||
if (platform.isWindows) {
|
||||
registerSendSequenceKeybinding(String.fromCharCode('V'.charCodeAt(0) - CTRL_LETTER_OFFSET), { // ctrl+v
|
||||
when: ContextKeyExpr.and(KEYBINDING_CONTEXT_TERMINAL_FOCUS, ContextKeyExpr.equals(KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE_KEY, WindowsShellType.PowerShell), CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()),
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KEY_V
|
||||
});
|
||||
}
|
||||
|
||||
// Delete word left: ctrl+w
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { Terminal as XTermTerminal } from 'xterm';
|
||||
import type { SearchAddon as XTermSearchAddon } from 'xterm-addon-search';
|
||||
import type { Unicode11Addon as XTermUnicode11Addon } from 'xterm-addon-unicode11';
|
||||
import type { WebglAddon as XTermWebglAddon } from 'xterm-addon-webgl';
|
||||
import { IWindowsShellHelper, ITerminalConfigHelper, ITerminalChildProcess, IShellLaunchConfig, IDefaultShellAndArgsRequest, ISpawnExtHostProcessRequest, IStartExtensionTerminalRequest, IAvailableShellsRequest, ITerminalProcessExtHostProxy, ICommandTracker, INavigationMode, TitleEventSource, ITerminalDimensions, ITerminalLaunchError, ITerminalNativeWindowsDelegate, LinuxDistro } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { IWindowsShellHelper, ITerminalConfigHelper, ITerminalChildProcess, IShellLaunchConfig, IDefaultShellAndArgsRequest, ISpawnExtHostProcessRequest, IStartExtensionTerminalRequest, IAvailableShellsRequest, ITerminalProcessExtHostProxy, ICommandTracker, INavigationMode, TitleEventSource, ITerminalDimensions, ITerminalLaunchError, ITerminalNativeWindowsDelegate, LinuxDistro, IRemoteTerminalAttachTarget } 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';
|
||||
@@ -17,6 +17,7 @@ import { URI } from 'vs/base/common/uri';
|
||||
|
||||
export const ITerminalService = createDecorator<ITerminalService>('terminalService');
|
||||
export const ITerminalInstanceService = createDecorator<ITerminalInstanceService>('terminalInstanceService');
|
||||
export const IRemoteTerminalService = createDecorator<IRemoteTerminalService>('remoteTerminalService');
|
||||
|
||||
/**
|
||||
* A service used by TerminalInstance (and components owned by it) that allows it to break its
|
||||
@@ -69,6 +70,11 @@ export interface ITerminalTab {
|
||||
split(shellLaunchConfig: IShellLaunchConfig): ITerminalInstance;
|
||||
}
|
||||
|
||||
export const enum TerminalConnectionState {
|
||||
Connecting,
|
||||
Connected
|
||||
}
|
||||
|
||||
export interface ITerminalService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
@@ -77,7 +83,9 @@ export interface ITerminalService {
|
||||
terminalInstances: ITerminalInstance[];
|
||||
terminalTabs: ITerminalTab[];
|
||||
isProcessSupportRegistered: boolean;
|
||||
readonly connectionState: TerminalConnectionState;
|
||||
|
||||
initializeTerminals(): Promise<void>;
|
||||
onActiveTabChanged: Event<void>;
|
||||
onTabDisposed: Event<ITerminalTab>;
|
||||
onInstanceCreated: Event<ITerminalInstance>;
|
||||
@@ -88,10 +96,11 @@ export interface ITerminalService {
|
||||
onInstanceRequestSpawnExtHostProcess: Event<ISpawnExtHostProcessRequest>;
|
||||
onInstanceRequestStartExtensionTerminal: Event<IStartExtensionTerminalRequest>;
|
||||
onInstancesChanged: Event<void>;
|
||||
onInstanceTitleChanged: Event<ITerminalInstance>;
|
||||
onInstanceTitleChanged: Event<ITerminalInstance | undefined>;
|
||||
onActiveInstanceChanged: Event<ITerminalInstance | undefined>;
|
||||
onRequestAvailableShells: Event<IAvailableShellsRequest>;
|
||||
onDidRegisterProcessSupport: Event<void>;
|
||||
onDidChangeConnectionState: Event<void>;
|
||||
|
||||
/**
|
||||
* Creates a terminal.
|
||||
@@ -172,6 +181,16 @@ export interface ITerminalService {
|
||||
extHostReady(remoteAuthority: string): 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>;
|
||||
isAttachedToTerminal(remoteTerm: IRemoteTerminalAttachTarget): boolean;
|
||||
}
|
||||
|
||||
export interface IRemoteTerminalService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
dispose(): void;
|
||||
|
||||
listTerminals(isInitialization?: boolean): Promise<IRemoteTerminalAttachTarget[]>;
|
||||
createRemoteTerminalProcess(terminalId: number, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI | undefined, cols: number, rows: number, configHelper: ITerminalConfigHelper,): Promise<ITerminalChildProcess>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -403,6 +422,11 @@ export interface ITerminalInstance {
|
||||
*/
|
||||
notifyFindWidgetFocusChanged(isFocused: boolean): void;
|
||||
|
||||
/**
|
||||
* Notifies the terminal to refresh its focus state based on the active document elemnet in DOM
|
||||
*/
|
||||
refreshFocusState(): void;
|
||||
|
||||
/**
|
||||
* Focuses the terminal instance if it's able to (xterm.js instance exists).
|
||||
*
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { EndOfLinePreference } from 'vs/editor/common/model';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { TERMINAL_VIEW_ID, ITerminalConfigHelper, TitleEventSource, TERMINAL_COMMAND_ID, KEYBINDING_CONTEXT_TERMINAL_FIND_FOCUSED, TERMINAL_ACTION_CATEGORY, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, KEYBINDING_CONTEXT_TERMINAL_FIND_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { TERMINAL_VIEW_ID, ITerminalConfigHelper, TitleEventSource, TERMINAL_COMMAND_ID, KEYBINDING_CONTEXT_TERMINAL_FIND_FOCUSED, TERMINAL_ACTION_CATEGORY, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, KEYBINDING_CONTEXT_TERMINAL_FIND_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, IRemoteTerminalAttachTarget } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { attachSelectBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { IQuickInputService, IPickOptions, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
@@ -24,13 +24,12 @@ import { Schemas } from 'vs/base/common/network';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { ITerminalInstance, ITerminalService, Direction } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { ITerminalInstance, ITerminalService, Direction, IRemoteTerminalService, TerminalConnectionState } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { Action2, registerAction2, ILocalizedString } from 'vs/platform/actions/common/actions';
|
||||
import { TerminalQuickAccessProvider } from 'vs/workbench/contrib/terminal/browser/terminalQuickAccess';
|
||||
import { ToggleViewAction } from 'vs/workbench/browser/actions/layoutActions';
|
||||
import { IViewsService, IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { addClass } from 'vs/base/browser/dom';
|
||||
import { selectBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
@@ -40,6 +39,11 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { ITerminalContributionService } from 'vs/workbench/contrib/terminal/common/terminalExtensionPoints';
|
||||
import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
|
||||
import { FindInFilesCommand, IFindInFilesArgs } from 'vs/workbench/contrib/search/browser/searchActions';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { RemoteNameContext } from 'vs/workbench/browser/contextkeys';
|
||||
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
|
||||
import { killTerminalIcon, newTerminalIcon } from 'vs/workbench/contrib/terminal/browser/terminalIcons';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
|
||||
async function getCwdForSplit(configHelper: ITerminalConfigHelper, instance: ITerminalInstance, folders?: IWorkspaceFolder[], commandService?: ICommandService): Promise<string | URI | undefined> {
|
||||
switch (configHelper.config.splitCwd) {
|
||||
@@ -79,23 +83,9 @@ export class ToggleTerminalAction extends ToggleViewAction {
|
||||
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@ITerminalService private readonly terminalService: ITerminalService
|
||||
) {
|
||||
super(id, label, TERMINAL_VIEW_ID, viewsService, viewDescriptorService, contextKeyService, layoutService);
|
||||
}
|
||||
|
||||
async run() {
|
||||
if (this.terminalService.isProcessSupportRegistered && this.terminalService.terminalInstances.length === 0) {
|
||||
// If there is not yet an instance attempt to create it here so that we can suggest a
|
||||
// new shell on Windows (and not do so when the panel is restored on reload).
|
||||
const newTerminalInstance = this.terminalService.createTerminal(undefined);
|
||||
const toDispose = newTerminalInstance.onProcessIdReady(() => {
|
||||
newTerminalInstance.focus();
|
||||
toDispose.dispose();
|
||||
});
|
||||
}
|
||||
return super.run();
|
||||
}
|
||||
}
|
||||
|
||||
export class KillTerminalAction extends Action {
|
||||
@@ -108,7 +98,7 @@ export class KillTerminalAction extends Action {
|
||||
id: string, label: string,
|
||||
@ITerminalService private readonly _terminalService: ITerminalService
|
||||
) {
|
||||
super(id, label, 'terminal-action codicon-trash');
|
||||
super(id, label, 'terminal-action ' + ThemeIcon.asClassName(killTerminalIcon));
|
||||
}
|
||||
|
||||
async run() {
|
||||
@@ -188,7 +178,7 @@ export class CreateNewTerminalAction extends Action {
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService
|
||||
) {
|
||||
super(id, label, 'terminal-action codicon-add');
|
||||
super(id, label, 'terminal-action ' + ThemeIcon.asClassName(newTerminalIcon));
|
||||
}
|
||||
|
||||
async run(event?: any) {
|
||||
@@ -229,8 +219,8 @@ export class SplitTerminalAction extends Action {
|
||||
public static readonly ID = TERMINAL_COMMAND_ID.SPLIT;
|
||||
public static readonly LABEL = localize('workbench.action.terminal.split', "Split Terminal");
|
||||
public static readonly SHORT_LABEL = localize('workbench.action.terminal.split.short', "Split");
|
||||
public static readonly HORIZONTAL_CLASS = 'terminal-action codicon-split-horizontal';
|
||||
public static readonly VERTICAL_CLASS = 'terminal-action codicon-split-vertical';
|
||||
public static readonly HORIZONTAL_CLASS = 'terminal-action ' + Codicon.splitHorizontal.classNames;
|
||||
public static readonly VERTICAL_CLASS = 'terminal-action ' + Codicon.splitVertical.classNames;
|
||||
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@@ -308,6 +298,23 @@ export class SelectDefaultShellWindowsTerminalAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
export class ConfigureTerminalSettingsAction extends Action {
|
||||
|
||||
public static readonly ID = TERMINAL_COMMAND_ID.CONFIGURE_TERMINAL_SETTINGS;
|
||||
public static readonly LABEL = localize('workbench.action.terminal.openSettings', "Configure Terminal Settings");
|
||||
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IPreferencesService private readonly _preferencesService: IPreferencesService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
async run() {
|
||||
this._preferencesService.openSettings(false, '@feature:terminal');
|
||||
}
|
||||
}
|
||||
|
||||
const terminalIndexRe = /^([0-9]+): /;
|
||||
|
||||
export class SwitchTerminalAction extends Action {
|
||||
@@ -320,6 +327,7 @@ export class SwitchTerminalAction extends Action {
|
||||
@ITerminalService private readonly _terminalService: ITerminalService,
|
||||
@ITerminalContributionService private readonly _contributions: ITerminalContributionService,
|
||||
@ICommandService private readonly _commands: ICommandService,
|
||||
@IPreferencesService private readonly preferencesService: IPreferencesService
|
||||
) {
|
||||
super(id, label, 'terminal-action switch-terminal');
|
||||
}
|
||||
@@ -336,7 +344,11 @@ export class SwitchTerminalAction extends Action {
|
||||
this._terminalService.refreshActiveTab();
|
||||
return this._terminalService.selectDefaultShell();
|
||||
}
|
||||
|
||||
if (item === ConfigureTerminalSettingsAction.LABEL) {
|
||||
const settingsAction = new ConfigureTerminalSettingsAction(ConfigureTerminalSettingsAction.ID, ConfigureTerminalSettingsAction.LABEL, this.preferencesService);
|
||||
settingsAction.run();
|
||||
this._terminalService.refreshActiveTab();
|
||||
}
|
||||
const indexMatches = terminalIndexRe.exec(item);
|
||||
if (indexMatches) {
|
||||
this._terminalService.setActiveTabByIndex(Number(indexMatches[1]) - 1);
|
||||
@@ -370,12 +382,13 @@ export class SwitchTerminalActionViewItem extends SelectActionViewItem {
|
||||
this._register(_terminalService.onActiveTabChanged(this._updateItems, this));
|
||||
this._register(_terminalService.onInstanceTitleChanged(this._updateItems, this));
|
||||
this._register(_terminalService.onTabDisposed(this._updateItems, this));
|
||||
this._register(_terminalService.onDidChangeConnectionState(this._updateItems, this));
|
||||
this._register(attachSelectBoxStyler(this.selectBox, this._themeService));
|
||||
}
|
||||
|
||||
render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
addClass(container, 'switch-terminal');
|
||||
container.classList.add('switch-terminal');
|
||||
this._register(attachStylerCallback(this._themeService, { selectBorder }, colors => {
|
||||
container.style.borderColor = colors.selectBorder ? `${colors.selectBorder}` : '';
|
||||
}));
|
||||
@@ -387,7 +400,10 @@ export class SwitchTerminalActionViewItem extends SelectActionViewItem {
|
||||
}
|
||||
|
||||
function getTerminalSelectOpenItems(terminalService: ITerminalService, contributions: ITerminalContributionService): ISelectOptionItem[] {
|
||||
const items = terminalService.getTabLabels().map(label => <ISelectOptionItem>{ text: label });
|
||||
const items = terminalService.connectionState === TerminalConnectionState.Connected ?
|
||||
terminalService.getTabLabels().map(label => <ISelectOptionItem>{ text: label }) :
|
||||
[{ text: localize('terminalConnectingLabel', "Starting...") }];
|
||||
|
||||
items.push({ text: SwitchTerminalActionViewItem.SEPARATOR, isDisabled: true });
|
||||
|
||||
for (const contributed of contributions.terminalTypes) {
|
||||
@@ -395,6 +411,7 @@ function getTerminalSelectOpenItems(terminalService: ITerminalService, contribut
|
||||
}
|
||||
|
||||
items.push({ text: SelectDefaultShellWindowsTerminalAction.LABEL });
|
||||
items.push({ text: ConfigureTerminalSettingsAction.LABEL });
|
||||
return items;
|
||||
}
|
||||
|
||||
@@ -692,7 +709,7 @@ export function registerTerminalActions() {
|
||||
}
|
||||
|
||||
const uri = editor.getModel().uri;
|
||||
if (uri.scheme !== 'file') {
|
||||
if (uri.scheme !== Schemas.file) {
|
||||
notificationService.warn(localize('workbench.action.terminal.runActiveFile.noFile', 'Only files on disk can be run in the terminal'));
|
||||
return;
|
||||
}
|
||||
@@ -983,6 +1000,43 @@ export function registerTerminalActions() {
|
||||
accessor.get(ITerminalService).hideFindWidget();
|
||||
}
|
||||
});
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: TERMINAL_COMMAND_ID.ATTACH_TO_REMOTE_TERMINAL,
|
||||
title: { value: localize('workbench.action.terminal.attachToRemote', "Attach to Session"), original: 'Attach to Session' },
|
||||
f1: true,
|
||||
category,
|
||||
keybinding: {
|
||||
when: RemoteNameContext.notEqualsTo(''),
|
||||
weight: KeybindingWeight.WorkbenchContrib
|
||||
}
|
||||
});
|
||||
}
|
||||
async run(accessor: ServicesAccessor) {
|
||||
const quickInputService = accessor.get(IQuickInputService);
|
||||
const remoteTerminalService = accessor.get(IRemoteTerminalService);
|
||||
const terminalService = accessor.get(ITerminalService);
|
||||
const labelService = accessor.get(ILabelService);
|
||||
const remoteTerms = await remoteTerminalService.listTerminals();
|
||||
const unattachedTerms = remoteTerms.filter(term => !terminalService.isAttachedToTerminal(term));
|
||||
const items = unattachedTerms.map(term => {
|
||||
const cwdLabel = labelService.getUriLabel(URI.file(term.cwd));
|
||||
return {
|
||||
label: term.title,
|
||||
detail: term.workspaceName ? `${term.workspaceName} ⸱ ${cwdLabel}` : cwdLabel,
|
||||
description: term.pid ? String(term.pid) : '',
|
||||
term
|
||||
};
|
||||
});
|
||||
const selected = await quickInputService.pick<IRemoteTerminalPick>(items, { canPickMany: false });
|
||||
if (selected) {
|
||||
const instance = terminalService.createTerminal({ remoteAttach: selected.term });
|
||||
terminalService.setActiveInstance(instance);
|
||||
terminalService.showPanel(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
@@ -1402,3 +1456,7 @@ export function registerTerminalActions() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
interface IRemoteTerminalPick extends IQuickPickItem {
|
||||
term: IRemoteTerminalAttachTarget;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { ITerminalConfiguration, ITerminalFont, IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, TERMINAL_CONFIG_SECTION, DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, MINIMUM_LETTER_SPACING, LinuxDistro, IShellLaunchConfig, MINIMUM_FONT_WEIGHT, MAXIMUM_FONT_WEIGHT, DEFAULT_FONT_WEIGHT, DEFAULT_BOLD_FONT_WEIGHT, FontWeight } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { INotificationService, NeverShowAgainScope } from 'vs/platform/notification/common/notification';
|
||||
@@ -15,13 +15,11 @@ import { IBrowserTerminalConfigHelper } from 'vs/workbench/contrib/terminal/brow
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { basename } from 'vs/base/common/path';
|
||||
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { InstallRecommendedExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { XTermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private';
|
||||
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
|
||||
const MINIMUM_FONT_SIZE = 6;
|
||||
const MAXIMUM_FONT_SIZE = 25;
|
||||
@@ -41,6 +39,9 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper {
|
||||
private readonly _onWorkspacePermissionsChanged = new Emitter<boolean>();
|
||||
public get onWorkspacePermissionsChanged(): Event<boolean> { return this._onWorkspacePermissionsChanged.event; }
|
||||
|
||||
private readonly _onConfigChanged = new Emitter<void>();
|
||||
public get onConfigChanged(): Event<void> { return this._onConfigChanged.event; }
|
||||
|
||||
public constructor(
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@IExtensionManagementService private readonly _extensionManagementService: IExtensionManagementService,
|
||||
@@ -49,7 +50,6 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper {
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService
|
||||
) {
|
||||
this._updateConfig();
|
||||
this._configurationService.onDidChangeConfiguration(e => {
|
||||
@@ -57,9 +57,6 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper {
|
||||
this._updateConfig();
|
||||
}
|
||||
});
|
||||
|
||||
// opt-in to syncing
|
||||
storageKeysSyncRegistryService.registerStorageKey({ key: 'terminalConfigHelper/launchRecommendationsIgnore', version: 1 });
|
||||
}
|
||||
|
||||
public setLinuxDistro(linuxDistro: LinuxDistro) {
|
||||
@@ -72,6 +69,7 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper {
|
||||
configValues.fontWeightBold = this._normalizeFontWeight(configValues.fontWeightBold, DEFAULT_BOLD_FONT_WEIGHT);
|
||||
|
||||
this.config = configValues;
|
||||
this._onConfigChanged.fire();
|
||||
}
|
||||
|
||||
public configFontIsMonospace(): boolean {
|
||||
@@ -208,7 +206,7 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper {
|
||||
|
||||
public setWorkspaceShellAllowed(isAllowed: boolean): void {
|
||||
this._onWorkspacePermissionsChanged.fire(isAllowed);
|
||||
this._storageService.store(IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, isAllowed, StorageScope.WORKSPACE);
|
||||
this._storageService.store(IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, isAllowed, StorageScope.WORKSPACE, StorageTarget.MACHINE);
|
||||
}
|
||||
|
||||
public isWorkspaceShellAllowed(defaultValue: boolean | undefined = undefined): boolean | undefined {
|
||||
@@ -341,7 +339,7 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper {
|
||||
}
|
||||
|
||||
private async isExtensionInstalled(id: string): Promise<boolean> {
|
||||
const extensions = await this._extensionManagementService.getInstalled(ExtensionType.User);
|
||||
const extensions = await this._extensionManagementService.getInstalled();
|
||||
return extensions.some(e => e.identifier.id === id);
|
||||
}
|
||||
|
||||
|
||||
14
src/vs/workbench/contrib/terminal/browser/terminalIcons.ts
Normal file
14
src/vs/workbench/contrib/terminal/browser/terminalIcons.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { localize } from 'vs/nls';
|
||||
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
|
||||
|
||||
export const terminalViewIcon = registerIcon('terminal-view-icon', Codicon.terminal, localize('terminalViewIcon', 'View icon of the terminal view.'));
|
||||
|
||||
export const renameTerminalIcon = registerIcon('terminal-rename', Codicon.gear, localize('renameTerminalIcon', 'Icon for rename in the terminal quick menu.'));
|
||||
export const killTerminalIcon = registerIcon('terminal-kill', Codicon.trash, localize('killTerminalIcon', 'Icon for killing a terminal instance.'));
|
||||
export const newTerminalIcon = registerIcon('terminal-new', Codicon.add, localize('newTerminalIcon', 'Icon for creating a new terminal instance.'));
|
||||
@@ -20,12 +20,12 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
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, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, INavigationMode, TitleEventSource, DEFAULT_COMMANDS_TO_SKIP_SHELL, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { IShellLaunchConfig, 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, IProcessDataEvent, ITerminalDimensionsOverride, SHOW_TERMINAL_CONFIG_PROMPT } 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';
|
||||
@@ -44,6 +44,9 @@ import { IViewsService, IViewDescriptorService, ViewContainerLocation } from 'vs
|
||||
import { EnvironmentVariableInfoWidget } from 'vs/workbench/contrib/terminal/browser/widgets/environmentVariableInfoWidget';
|
||||
import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable';
|
||||
import { TerminalLaunchHelpAction } from 'vs/workbench/contrib/terminal/browser/terminalActions';
|
||||
import { TypeAheadAddon } from 'vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon';
|
||||
import { BrowserFeatures } from 'vs/base/browser/canIUse';
|
||||
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
|
||||
|
||||
// How long in milliseconds should an average frame take to render for a notification to appear
|
||||
// which suggests the fallback DOM-based renderer
|
||||
@@ -93,7 +96,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
private _terminalA11yTreeFocusContextKey: IContextKey<boolean>;
|
||||
private _cols: number = 0;
|
||||
private _rows: number = 0;
|
||||
private _dimensionsOverride: ITerminalDimensions | undefined;
|
||||
private _dimensionsOverride: ITerminalDimensionsOverride | undefined;
|
||||
private _windowsShellHelper: IWindowsShellHelper | undefined;
|
||||
private _xtermReadyPromise: Promise<XTermTerminal>;
|
||||
private _titleReadyPromise: Promise<string>;
|
||||
@@ -116,12 +119,18 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
public get id(): number { return this._id; }
|
||||
public get cols(): number {
|
||||
if (this._dimensionsOverride && this._dimensionsOverride.cols) {
|
||||
if (this._dimensionsOverride.forceExactSize) {
|
||||
return this._dimensionsOverride.cols;
|
||||
}
|
||||
return Math.min(Math.max(this._dimensionsOverride.cols, 2), this._cols);
|
||||
}
|
||||
return this._cols;
|
||||
}
|
||||
public get rows(): number {
|
||||
if (this._dimensionsOverride && this._dimensionsOverride.rows) {
|
||||
if (this._dimensionsOverride.forceExactSize) {
|
||||
return this._dimensionsOverride.rows;
|
||||
}
|
||||
return Math.min(Math.max(this._dimensionsOverride.rows, 2), this._rows);
|
||||
}
|
||||
return this._rows;
|
||||
@@ -179,6 +188,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
|
||||
@IKeybindingService private readonly _keybindingService: IKeybindingService,
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@IPreferencesService private readonly _preferencesService: IPreferencesService,
|
||||
@IViewsService private readonly _viewsService: IViewsService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IClipboardService private readonly _clipboardService: IClipboardService,
|
||||
@@ -414,7 +424,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
this._xterm.onKey(e => this._onKey(e.key, e.domEvent));
|
||||
this._xterm.onSelectionChange(async () => this._onSelectionChange());
|
||||
|
||||
this._processManager.onProcessData(data => this._onProcessData(data));
|
||||
this._processManager.onProcessData(e => this._onProcessData(e));
|
||||
this._xterm.onData(data => this._processManager.write(data));
|
||||
this.processReady.then(async () => {
|
||||
if (this._linkManager) {
|
||||
@@ -448,6 +458,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
}
|
||||
}));
|
||||
|
||||
const typeaheadAddon = this._register(this._instantiationService.createInstance(TypeAheadAddon, this._processManager, this._configHelper));
|
||||
this._xterm.loadAddon(typeaheadAddon);
|
||||
|
||||
return xterm;
|
||||
}
|
||||
|
||||
@@ -488,7 +501,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
|
||||
this._container = container;
|
||||
this._wrapperElement = document.createElement('div');
|
||||
dom.addClass(this._wrapperElement, 'terminal-wrapper');
|
||||
this._wrapperElement.classList.add('terminal-wrapper');
|
||||
this._xtermElement = document.createElement('div');
|
||||
|
||||
// Attach the xterm object to the DOM, exposing it to the smoke tests
|
||||
@@ -531,9 +544,29 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip processing by xterm.js of keyboard events that resolve to commands described
|
||||
// within commandsToSkipShell
|
||||
if (resolveResult && this._skipTerminalCommands.some(k => k === resolveResult.commandId)) {
|
||||
// for keyboard events that resolve to commands described
|
||||
// within commandsToSkipShell, either alert or skip processing by xterm.js
|
||||
if (resolveResult && this._skipTerminalCommands.some(k => k === resolveResult.commandId) && !this._configHelper.config.sendKeybindingsToShell) {
|
||||
// don't alert when terminal is opened or closed
|
||||
if (this._storageService.getBoolean(SHOW_TERMINAL_CONFIG_PROMPT, StorageScope.GLOBAL, true) &&
|
||||
resolveResult.commandId !== 'workbench.action.terminal.toggleTerminal' &&
|
||||
resolveResult.commandId !== 'workbench.action.terminal.new' &&
|
||||
resolveResult.commandId !== 'workbench.action.togglePanel' &&
|
||||
resolveResult.commandId !== 'workbench.action.terminal.focus') {
|
||||
this._notificationService.prompt(
|
||||
Severity.Info,
|
||||
nls.localize('configure terminal settings', "Some keybindings are dispatched to the workbench by default."),
|
||||
[
|
||||
{
|
||||
label: nls.localize('configureTerminalSettings', "Configure Terminal Settings"),
|
||||
run: () => {
|
||||
this._preferencesService.openSettings(false, '@id:terminal.integrated.commandsToSkipShell,terminal.integrated.sendKeybindingsToShell,terminal.integrated.allowChords');
|
||||
}
|
||||
} as IPromptChoice
|
||||
]
|
||||
);
|
||||
this._storageService.store(SHOW_TERMINAL_CONFIG_PROMPT, false, StorageScope.GLOBAL, StorageTarget.USER);
|
||||
}
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
@@ -554,6 +587,12 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fallback to force ctrl+v to paste on browsers that do not support
|
||||
// navigator.clipboard.readText
|
||||
if (!BrowserFeatures.clipboard.readText && event.key === 'v' && event.ctrlKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
this._register(dom.addDisposableListener(xterm.element, 'mousedown', () => {
|
||||
@@ -646,7 +685,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
{
|
||||
label: nls.localize('dontShowAgain', "Don't Show Again"),
|
||||
isSecondary: true,
|
||||
run: () => this._storageService.store(NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, true, StorageScope.GLOBAL)
|
||||
run: () => this._storageService.store(NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, true, StorageScope.GLOBAL, StorageTarget.MACHINE)
|
||||
} as IPromptChoice
|
||||
];
|
||||
this._notificationService.prompt(
|
||||
@@ -718,6 +757,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
this._terminalFocusContextKey.set(terminalFocused);
|
||||
}
|
||||
|
||||
public refreshFocusState() {
|
||||
this.notifyFindWidgetFocusChanged(false);
|
||||
}
|
||||
|
||||
public dispose(immediate?: boolean): void {
|
||||
this._logService.trace(`terminalInstance#dispose (id: ${this.id})`);
|
||||
|
||||
@@ -730,7 +773,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
dispose(this._widgetManager);
|
||||
|
||||
if (this._xterm && this._xterm.element) {
|
||||
this._hadFocusOnExit = dom.hasClass(this._xterm.element, 'focus');
|
||||
this._hadFocusOnExit = this._xterm.element.classList.contains('focus');
|
||||
}
|
||||
if (this._wrapperElement) {
|
||||
if (this._wrapperElement.xterm) {
|
||||
@@ -813,7 +856,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
public setVisible(visible: boolean): void {
|
||||
this._isVisible = visible;
|
||||
if (this._wrapperElement) {
|
||||
dom.toggleClass(this._wrapperElement, 'active', visible);
|
||||
this._wrapperElement.classList.toggle('active', visible);
|
||||
}
|
||||
if (visible && this._xterm && this._xtermCore) {
|
||||
// Trigger a manual scroll event which will sync the viewport and scroll bar. This is
|
||||
@@ -878,11 +921,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
this._processManager = this._instantiationService.createInstance(TerminalProcessManager, this._id, this._configHelper);
|
||||
this._processManager.onProcessReady(() => this._onProcessIdReady.fire(this));
|
||||
this._processManager.onProcessExit(exitCode => this._onProcessExit(exitCode));
|
||||
this._processManager.onProcessData(data => {
|
||||
this._initialDataEvents?.push(data);
|
||||
this._onData.fire(data);
|
||||
this._processManager.onProcessData(ev => {
|
||||
this._initialDataEvents?.push(ev.data);
|
||||
this._onData.fire(ev.data);
|
||||
});
|
||||
this._processManager.onProcessOverrideDimensions(e => this.setDimensions(e));
|
||||
this._processManager.onProcessOverrideDimensions(e => this.setDimensions(e, true));
|
||||
this._processManager.onProcessResolvedShellLaunchConfig(e => this._setResolvedShellLaunchConfig(e));
|
||||
this._processManager.onEnvironmentVariableInfoChanged(e => this._onEnvironmentVariableInfoChanged(e));
|
||||
|
||||
@@ -955,9 +998,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
}
|
||||
}
|
||||
|
||||
private _onProcessData(data: string): void {
|
||||
const messageId = ++this._latestXtermWriteData;
|
||||
this._xterm?.write(data, () => this._latestXtermParseData = messageId);
|
||||
private _onProcessData(ev: IProcessDataEvent): void {
|
||||
if (ev.sync) {
|
||||
this._xtermCore?.writeSync(ev.data);
|
||||
} else {
|
||||
const messageId = ++this._latestXtermWriteData;
|
||||
this._xterm?.write(ev.data, () => this._latestXtermParseData = messageId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1345,6 +1392,10 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
|
||||
@debounce(50)
|
||||
private async _resize(): Promise<void> {
|
||||
this._resizeNow(false);
|
||||
}
|
||||
|
||||
private async _resizeNow(immediate: boolean): Promise<void> {
|
||||
let cols = this.cols;
|
||||
let rows = this.rows;
|
||||
|
||||
@@ -1386,7 +1437,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
// maximize on Windows/Linux would fire an event saying that the terminal was not
|
||||
// visible.
|
||||
if (this._xterm.getOption('rendererType') === 'canvas') {
|
||||
this._xtermCore._renderService._onIntersectionChange({ intersectionRatio: 1 });
|
||||
this._xtermCore._renderService?._onIntersectionChange({ intersectionRatio: 1 });
|
||||
// HACK: Force a refresh of the screen to ensure links are refresh corrected.
|
||||
// This can probably be removed when the above hack is fixed in Chromium.
|
||||
this._xterm.refresh(0, this._xterm.rows - 1);
|
||||
@@ -1394,8 +1445,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
}
|
||||
}
|
||||
|
||||
await this._processManager.ptyProcessReady;
|
||||
this._processManager.setDimensions(cols, rows);
|
||||
if (immediate) {
|
||||
// do not await, call setDimensions synchronously
|
||||
this._processManager.setDimensions(cols, rows);
|
||||
} else {
|
||||
await this._processManager.ptyProcessReady;
|
||||
this._processManager.setDimensions(cols, rows);
|
||||
}
|
||||
}
|
||||
|
||||
public setShellType(shellType: TerminalShellType) {
|
||||
@@ -1438,9 +1494,18 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
|
||||
return this._titleReadyPromise;
|
||||
}
|
||||
|
||||
public setDimensions(dimensions: ITerminalDimensions | undefined): void {
|
||||
public setDimensions(dimensions: ITerminalDimensionsOverride | undefined, immediate: boolean = false): void {
|
||||
if (this._dimensionsOverride && this._dimensionsOverride.forceExactSize && !dimensions && this._rows === 0 && this._cols === 0) {
|
||||
// this terminal never had a real size => keep the last dimensions override exact size
|
||||
this._cols = this._dimensionsOverride.cols;
|
||||
this._rows = this._dimensionsOverride.rows;
|
||||
}
|
||||
this._dimensionsOverride = dimensions;
|
||||
this._resize();
|
||||
if (immediate) {
|
||||
this._resizeNow(true);
|
||||
} else {
|
||||
this._resize();
|
||||
}
|
||||
}
|
||||
|
||||
private _setResolvedShellLaunchConfig(shellLaunchConfig: IShellLaunchConfig): void {
|
||||
@@ -1562,13 +1627,17 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
|
||||
.monaco-workbench .pane-body.integrated-terminal .find-focused .xterm .xterm-viewport,
|
||||
.monaco-workbench .pane-body.integrated-terminal .xterm.focus .xterm-viewport,
|
||||
.monaco-workbench .pane-body.integrated-terminal .xterm:focus .xterm-viewport,
|
||||
.monaco-workbench .pane-body.integrated-terminal .xterm:hover .xterm-viewport { background-color: ${scrollbarSliderBackgroundColor} !important; }`
|
||||
);
|
||||
.monaco-workbench .pane-body.integrated-terminal .xterm:hover .xterm-viewport { background-color: ${scrollbarSliderBackgroundColor} !important; }
|
||||
.monaco-workbench .pane-body.integrated-terminal .xterm-viewport { scrollbar-color: ${scrollbarSliderBackgroundColor} transparent; }
|
||||
`);
|
||||
}
|
||||
|
||||
const scrollbarSliderHoverBackgroundColor = theme.getColor(scrollbarSliderHoverBackground);
|
||||
if (scrollbarSliderHoverBackgroundColor) {
|
||||
collector.addRule(`.monaco-workbench .pane-body.integrated-terminal .xterm .xterm-viewport::-webkit-scrollbar-thumb:hover { background-color: ${scrollbarSliderHoverBackgroundColor}; }`);
|
||||
collector.addRule(`
|
||||
.monaco-workbench .pane-body.integrated-terminal .xterm .xterm-viewport::-webkit-scrollbar-thumb:hover { background-color: ${scrollbarSliderHoverBackgroundColor}; }
|
||||
.monaco-workbench .pane-body.integrated-terminal .xterm-viewport:hover { scrollbar-color: ${scrollbarSliderHoverBackgroundColor} transparent; }
|
||||
`);
|
||||
}
|
||||
|
||||
const scrollbarSliderActiveBackgroundColor = theme.getColor(scrollbarSliderActiveBackground);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ITerminalProcessExtHostProxy, IShellLaunchConfig, ITerminalChildProcess, ITerminalConfigHelper, ITerminalDimensions, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ITerminalProcessExtHostProxy, IShellLaunchConfig, ITerminalChildProcess, ITerminalConfigHelper, ITerminalDimensions, ITerminalLaunchError, ITerminalDimensionsOverride } 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';
|
||||
@@ -23,8 +23,8 @@ export class TerminalProcessExtHostProxy extends Disposable implements ITerminal
|
||||
public get onProcessReady(): Event<{ pid: number, cwd: string }> { return this._onProcessReady.event; }
|
||||
private readonly _onProcessTitleChanged = this._register(new Emitter<string>());
|
||||
public readonly onProcessTitleChanged: Event<string> = this._onProcessTitleChanged.event;
|
||||
private readonly _onProcessOverrideDimensions = this._register(new Emitter<ITerminalDimensions | undefined>());
|
||||
public get onProcessOverrideDimensions(): Event<ITerminalDimensions | undefined> { return this._onProcessOverrideDimensions.event; }
|
||||
private readonly _onProcessOverrideDimensions = this._register(new Emitter<ITerminalDimensionsOverride | undefined>());
|
||||
public get onProcessOverrideDimensions(): Event<ITerminalDimensionsOverride | undefined> { return this._onProcessOverrideDimensions.event; }
|
||||
private readonly _onProcessResolvedShellLaunchConfig = this._register(new Emitter<IShellLaunchConfig>());
|
||||
public get onProcessResolvedShellLaunchConfig(): Event<IShellLaunchConfig> { return this._onProcessResolvedShellLaunchConfig.event; }
|
||||
|
||||
|
||||
@@ -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, ITerminalLaunchError } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ProcessState, ITerminalProcessManager, IShellLaunchConfig, ITerminalConfigHelper, ITerminalChildProcess, IBeforeProcessDataEvent, ITerminalEnvironment, ITerminalLaunchError, IProcessDataEvent, ITerminalDimensionsOverride } 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';
|
||||
@@ -18,7 +18,7 @@ import { Schemas } from 'vs/base/common/network';
|
||||
import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { IRemoteTerminalService, ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
@@ -69,14 +69,14 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
|
||||
public get onProcessReady(): Event<void> { return this._onProcessReady.event; }
|
||||
private readonly _onBeforeProcessData = this._register(new Emitter<IBeforeProcessDataEvent>());
|
||||
public get onBeforeProcessData(): Event<IBeforeProcessDataEvent> { return this._onBeforeProcessData.event; }
|
||||
private readonly _onProcessData = this._register(new Emitter<string>());
|
||||
public get onProcessData(): Event<string> { return this._onProcessData.event; }
|
||||
private readonly _onProcessData = this._register(new Emitter<IProcessDataEvent>());
|
||||
public get onProcessData(): Event<IProcessDataEvent> { return this._onProcessData.event; }
|
||||
private readonly _onProcessTitle = this._register(new Emitter<string>());
|
||||
public get onProcessTitle(): Event<string> { return this._onProcessTitle.event; }
|
||||
private readonly _onProcessExit = this._register(new Emitter<number | undefined>());
|
||||
public get onProcessExit(): Event<number | undefined> { return this._onProcessExit.event; }
|
||||
private readonly _onProcessOverrideDimensions = this._register(new Emitter<ITerminalDimensions | undefined>());
|
||||
public get onProcessOverrideDimensions(): Event<ITerminalDimensions | undefined> { return this._onProcessOverrideDimensions.event; }
|
||||
private readonly _onProcessOverrideDimensions = this._register(new Emitter<ITerminalDimensionsOverride | undefined>());
|
||||
public get onProcessOverrideDimensions(): Event<ITerminalDimensionsOverride | undefined> { return this._onProcessOverrideDimensions.event; }
|
||||
private readonly _onProcessOverrideShellLaunchConfig = this._register(new Emitter<IShellLaunchConfig>());
|
||||
public get onProcessResolvedShellLaunchConfig(): Event<IShellLaunchConfig> { return this._onProcessOverrideShellLaunchConfig.event; }
|
||||
private readonly _onEnvironmentVariableInfoChange = this._register(new Emitter<IEnvironmentVariableInfo>());
|
||||
@@ -98,7 +98,8 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
|
||||
@ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService,
|
||||
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
|
||||
@IPathService private readonly _pathService: IPathService,
|
||||
@IEnvironmentVariableService private readonly _environmentVariableService: IEnvironmentVariableService
|
||||
@IEnvironmentVariableService private readonly _environmentVariableService: IEnvironmentVariableService,
|
||||
@IRemoteTerminalService private readonly _remoteTerminalService: IRemoteTerminalService,
|
||||
) {
|
||||
super();
|
||||
this.ptyProcessReady = new Promise<void>(c => {
|
||||
@@ -136,7 +137,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
|
||||
if (shellLaunchConfig.cwd && typeof shellLaunchConfig.cwd === 'object') {
|
||||
this.remoteAuthority = getRemoteAuthority(shellLaunchConfig.cwd);
|
||||
} else {
|
||||
this.remoteAuthority = this._environmentService.configuration.remoteAuthority;
|
||||
this.remoteAuthority = this._environmentService.remoteAuthority;
|
||||
}
|
||||
const hasRemoteAuthority = !!this.remoteAuthority;
|
||||
let launchRemotely = hasRemoteAuthority || forceExtHostProcess;
|
||||
@@ -157,7 +158,12 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
|
||||
}
|
||||
|
||||
const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot();
|
||||
this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, this._configHelper);
|
||||
const enableRemoteAgentTerminals = this._configHelper.config.serverSpawn;
|
||||
if (enableRemoteAgentTerminals) {
|
||||
this._process = await this._remoteTerminalService.createRemoteTerminalProcess(this._terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, this._configHelper);
|
||||
} else {
|
||||
this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, this._configHelper);
|
||||
}
|
||||
} else {
|
||||
this._process = await this._launchProcess(shellLaunchConfig, cols, rows, this.userHome, isScreenReaderModeEnabled);
|
||||
}
|
||||
@@ -165,11 +171,13 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
|
||||
|
||||
this.processState = ProcessState.LAUNCHING;
|
||||
|
||||
this._process.onProcessData(data => {
|
||||
this._process.onProcessData(ev => {
|
||||
const data = (typeof ev === 'string' ? ev : ev.data);
|
||||
const sync = (typeof ev === 'string' ? false : ev.sync);
|
||||
const beforeProcessDataEvent: IBeforeProcessDataEvent = { data };
|
||||
this._onBeforeProcessData.fire(beforeProcessDataEvent);
|
||||
if (beforeProcessDataEvent.data && beforeProcessDataEvent.data.length > 0) {
|
||||
this._onProcessData.fire(beforeProcessDataEvent.data);
|
||||
this._onProcessData.fire({ data: beforeProcessDataEvent.data, sync });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -240,8 +248,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
|
||||
const initialCwd = terminalEnvironment.getCwd(
|
||||
shellLaunchConfig,
|
||||
userHome,
|
||||
lastActiveWorkspace,
|
||||
this._configurationResolverService,
|
||||
terminalEnvironment.createVariableResolver(lastActiveWorkspace, this._configurationResolverService),
|
||||
activeWorkspaceRootUri,
|
||||
this._configHelper.config.cwd,
|
||||
this._logService
|
||||
@@ -250,7 +257,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
|
||||
const isWorkspaceShellAllowed = this._configHelper.checkWorkspaceShellPermissions();
|
||||
this._configHelper.showRecommendations(shellLaunchConfig);
|
||||
const baseEnv = this._configHelper.config.inheritEnv ? processEnv : await this._terminalInstanceService.getMainProcessParentEnv();
|
||||
const env = terminalEnvironment.createTerminalEnvironment(shellLaunchConfig, lastActiveWorkspace, envFromConfigValue, this._configurationResolverService, isWorkspaceShellAllowed, this._productService.version, this._configHelper.config.detectLocale, baseEnv);
|
||||
const env = terminalEnvironment.createTerminalEnvironment(shellLaunchConfig, envFromConfigValue, terminalEnvironment.createVariableResolver(lastActiveWorkspace, this._configurationResolverService), isWorkspaceShellAllowed, this._productService.version, this._configHelper.config.detectLocale, baseEnv);
|
||||
|
||||
// Fetch any extension environment additions and apply them
|
||||
if (!shellLaunchConfig.strictEnv) {
|
||||
|
||||
@@ -10,6 +10,8 @@ import { matchesFuzzy } from 'vs/base/common/filters';
|
||||
import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { killTerminalIcon, renameTerminalIcon } from 'vs/workbench/contrib/terminal/browser/terminalIcons';
|
||||
|
||||
export class TerminalQuickAccessProvider extends PickerQuickAccessProvider<IPickerQuickAccessItem> {
|
||||
|
||||
@@ -39,11 +41,11 @@ export class TerminalQuickAccessProvider extends PickerQuickAccessProvider<IPick
|
||||
highlights: { label: highlights },
|
||||
buttons: [
|
||||
{
|
||||
iconClass: 'codicon-gear',
|
||||
iconClass: ThemeIcon.asClassName(renameTerminalIcon),
|
||||
tooltip: localize('renameTerminal', "Rename Terminal")
|
||||
},
|
||||
{
|
||||
iconClass: 'codicon-trash',
|
||||
iconClass: ThemeIcon.asClassName(killTerminalIcon),
|
||||
tooltip: localize('killTerminal', "Kill Terminal Instance")
|
||||
}
|
||||
],
|
||||
|
||||
@@ -3,32 +3,34 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { TERMINAL_VIEW_ID, IShellLaunchConfig, ITerminalConfigHelper, ISpawnExtHostProcessRequest, IStartExtensionTerminalRequest, IAvailableShellsRequest, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, ITerminalProcessExtHostProxy, IShellDefinition, LinuxDistro, KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE, ITerminalLaunchError, ITerminalNativeWindowsDelegate } 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';
|
||||
import { TerminalViewPane } from 'vs/workbench/contrib/terminal/browser/terminalView';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { TerminalTab } from 'vs/workbench/contrib/terminal/browser/terminalTab';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminalInstance';
|
||||
import { ITerminalService, ITerminalInstance, ITerminalTab, TerminalShellType, WindowsShellType, ITerminalExternalLinkProvider } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
|
||||
import { IQuickInputService, IQuickPickItem, IPickOptions } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { basename } from 'vs/base/common/path';
|
||||
import { isMacintosh, isWeb, isWindows, OperatingSystem } from 'vs/base/common/platform';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { FindReplaceState } from 'vs/editor/contrib/find/findState';
|
||||
import * as nls from 'vs/nls';
|
||||
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IPickOptions, IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views';
|
||||
import { TerminalConnectionState, IRemoteTerminalService, ITerminalExternalLinkProvider, ITerminalInstance, ITerminalService, ITerminalTab, TerminalShellType, WindowsShellType } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
|
||||
import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminalInstance';
|
||||
import { TerminalTab } from 'vs/workbench/contrib/terminal/browser/terminalTab';
|
||||
import { TerminalViewPane } from 'vs/workbench/contrib/terminal/browser/terminalView';
|
||||
import { IAvailableShellsRequest, IRemoteTerminalAttachTarget, IShellDefinition, IShellLaunchConfig, ISpawnExtHostProcessRequest, IStartExtensionTerminalRequest, ITerminalConfigHelper, ITerminalLaunchError, ITerminalNativeWindowsDelegate, ITerminalProcessExtHostProxy, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, KEYBINDING_CONTEXT_TERMINAL_SHELL_TYPE, LinuxDistro, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { escapeNonWindowsPath } from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
|
||||
import { isWindows, isMacintosh, OperatingSystem, isWeb } from 'vs/base/common/platform';
|
||||
import { basename } from 'vs/base/common/path';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { IViewsService, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
|
||||
interface IExtHostReadyEntry {
|
||||
promise: Promise<void>;
|
||||
@@ -62,6 +64,8 @@ export class TerminalService implements ITerminalService {
|
||||
private _configHelper: TerminalConfigHelper;
|
||||
private _terminalContainer: HTMLElement | undefined;
|
||||
private _nativeWindowsDelegate: ITerminalNativeWindowsDelegate | undefined;
|
||||
private _remoteTerminalsInitialized: Promise<void> | undefined;
|
||||
private _connectionState: TerminalConnectionState;
|
||||
|
||||
public get configHelper(): ITerminalConfigHelper { return this._configHelper; }
|
||||
|
||||
@@ -85,8 +89,8 @@ export class TerminalService implements ITerminalService {
|
||||
public get onInstanceMaximumDimensionsChanged(): Event<ITerminalInstance> { return this._onInstanceMaximumDimensionsChanged.event; }
|
||||
private readonly _onInstancesChanged = new Emitter<void>();
|
||||
public get onInstancesChanged(): Event<void> { return this._onInstancesChanged.event; }
|
||||
private readonly _onInstanceTitleChanged = new Emitter<ITerminalInstance>();
|
||||
public get onInstanceTitleChanged(): Event<ITerminalInstance> { return this._onInstanceTitleChanged.event; }
|
||||
private readonly _onInstanceTitleChanged = new Emitter<ITerminalInstance | undefined>();
|
||||
public get onInstanceTitleChanged(): Event<ITerminalInstance | undefined> { return this._onInstanceTitleChanged.event; }
|
||||
private readonly _onActiveInstanceChanged = new Emitter<ITerminalInstance | undefined>();
|
||||
public get onActiveInstanceChanged(): Event<ITerminalInstance | undefined> { return this._onActiveInstanceChanged.event; }
|
||||
private readonly _onTabDisposed = new Emitter<ITerminalTab>();
|
||||
@@ -95,6 +99,9 @@ export class TerminalService implements ITerminalService {
|
||||
public get onRequestAvailableShells(): Event<IAvailableShellsRequest> { return this._onRequestAvailableShells.event; }
|
||||
private readonly _onDidRegisterProcessSupport = new Emitter<void>();
|
||||
public get onDidRegisterProcessSupport(): Event<void> { return this._onDidRegisterProcessSupport.event; }
|
||||
private readonly _onDidChangeConnectionState = new Emitter<void>();
|
||||
public get onDidChangeConnectionState(): Event<void> { return this._onDidChangeConnectionState.event; }
|
||||
public get connectionState(): TerminalConnectionState { return this._connectionState; }
|
||||
|
||||
constructor(
|
||||
@IContextKeyService private _contextKeyService: IContextKeyService,
|
||||
@@ -108,7 +115,10 @@ export class TerminalService implements ITerminalService {
|
||||
@IConfigurationService private _configurationService: IConfigurationService,
|
||||
@IViewsService private _viewsService: IViewsService,
|
||||
@IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService,
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService
|
||||
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
|
||||
@IRemoteTerminalService private readonly _remoteTerminalService: IRemoteTerminalService,
|
||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService
|
||||
) {
|
||||
this._activeTabIndex = 0;
|
||||
this._isShuttingDown = false;
|
||||
@@ -129,6 +139,42 @@ export class TerminalService implements ITerminalService {
|
||||
this._handleInstanceContextKeys();
|
||||
this._processSupportContextKey = KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED.bindTo(this._contextKeyService);
|
||||
this._processSupportContextKey.set(!isWeb || this._remoteAgentService.getConnection() !== null);
|
||||
|
||||
const enableTerminalReconnection = this.configHelper.config.enablePersistentSessions;
|
||||
const serverSpawn = this.configHelper.config.serverSpawn;
|
||||
if (!!this._environmentService.remoteAuthority && enableTerminalReconnection && serverSpawn) {
|
||||
this._remoteTerminalsInitialized = this._reconnectToRemoteTerminals();
|
||||
this._connectionState = TerminalConnectionState.Connecting;
|
||||
} else {
|
||||
this._connectionState = TerminalConnectionState.Connected;
|
||||
}
|
||||
}
|
||||
|
||||
private async _reconnectToRemoteTerminals(): Promise<void> {
|
||||
const remoteTerms = await this._remoteTerminalService.listTerminals(true);
|
||||
const workspace = this._workspaceContextService.getWorkspace();
|
||||
const unattachedWorkspaceRemoteTerms = remoteTerms
|
||||
.filter(term => term.workspaceId === workspace.id)
|
||||
.filter(term => !this.isAttachedToTerminal(term));
|
||||
|
||||
/* __GDPR__
|
||||
"terminalReconnection" : {
|
||||
"count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
|
||||
}
|
||||
*/
|
||||
const data = {
|
||||
count: unattachedWorkspaceRemoteTerms.length
|
||||
};
|
||||
this._telemetryService.publicLog('terminalReconnection', data);
|
||||
if (unattachedWorkspaceRemoteTerms.length > 0) {
|
||||
// Reattach to all remote terminals
|
||||
for (let term of unattachedWorkspaceRemoteTerms) {
|
||||
this.createTerminal({ remoteAttach: term });
|
||||
}
|
||||
}
|
||||
|
||||
this._connectionState = TerminalConnectionState.Connected;
|
||||
this._onDidChangeConnectionState.fire();
|
||||
}
|
||||
|
||||
public setNativeWindowsDelegate(delegate: ITerminalNativeWindowsDelegate): void {
|
||||
@@ -339,6 +385,23 @@ export class TerminalService implements ITerminalService {
|
||||
}
|
||||
}
|
||||
|
||||
public isAttachedToTerminal(remoteTerm: IRemoteTerminalAttachTarget): boolean {
|
||||
return this.terminalInstances.some(term => term.processId === remoteTerm.pid);
|
||||
}
|
||||
|
||||
public async initializeTerminals(): Promise<void> {
|
||||
if (this._remoteTerminalsInitialized) {
|
||||
await this._remoteTerminalsInitialized;
|
||||
|
||||
if (!this.terminalTabs.length) {
|
||||
this.createTerminal(undefined);
|
||||
}
|
||||
} else if (!this._environmentService.remoteAuthority && this.terminalTabs.length === 0) {
|
||||
// Local, just create a terminal
|
||||
this.createTerminal();
|
||||
}
|
||||
}
|
||||
|
||||
private _getInstanceFromGlobalInstanceIndex(index: number): { tab: ITerminalTab, tabIndex: number, instance: ITerminalInstance, localInstanceIndex: number } | null {
|
||||
let currentTabIndex = 0;
|
||||
while (index >= 0 && currentTabIndex < this._terminalTabs.length) {
|
||||
@@ -620,9 +683,12 @@ export class TerminalService implements ITerminalService {
|
||||
this._initInstanceListeners(instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
const terminalTab = this._instantiationService.createInstance(TerminalTab, this._terminalContainer, shell);
|
||||
this._terminalTabs.push(terminalTab);
|
||||
|
||||
const instance = terminalTab.terminalInstances[0];
|
||||
|
||||
terminalTab.addDisposable(terminalTab.onDisposed(this._onTabDisposed.fire, this._onTabDisposed));
|
||||
terminalTab.addDisposable(terminalTab.onInstancesChanged(this._onInstancesChanged.fire, this._onInstancesChanged));
|
||||
this._initInstanceListeners(instance);
|
||||
|
||||
@@ -56,7 +56,7 @@ class SplitPaneContainer extends Disposable {
|
||||
(this.orientation === Orientation.VERTICAL && direction === Direction.Right)) {
|
||||
amount *= -1;
|
||||
}
|
||||
this._layoutService.resizePart(Parts.PANEL_PART, amount);
|
||||
this._layoutService.resizePart(Parts.PANEL_PART, amount, amount);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -228,7 +228,7 @@ export class TerminalTab extends Disposable implements ITerminalTab {
|
||||
|
||||
constructor(
|
||||
private _container: HTMLElement | undefined,
|
||||
shellLaunchConfigOrInstance: IShellLaunchConfig | ITerminalInstance,
|
||||
shellLaunchConfigOrInstance: IShellLaunchConfig | ITerminalInstance | undefined,
|
||||
@ITerminalService private readonly _terminalService: ITerminalService,
|
||||
@IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService,
|
||||
@IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService,
|
||||
@@ -236,6 +236,18 @@ export class TerminalTab extends Disposable implements ITerminalTab {
|
||||
) {
|
||||
super();
|
||||
|
||||
if (shellLaunchConfigOrInstance) {
|
||||
this.addInstance(shellLaunchConfigOrInstance);
|
||||
}
|
||||
|
||||
this._activeInstanceIndex = 0;
|
||||
|
||||
if (this._container) {
|
||||
this.attachToElement(this._container);
|
||||
}
|
||||
}
|
||||
|
||||
public addInstance(shellLaunchConfigOrInstance: IShellLaunchConfig | ITerminalInstance): void {
|
||||
let instance: ITerminalInstance;
|
||||
if ('id' in shellLaunchConfigOrInstance) {
|
||||
instance = shellLaunchConfigOrInstance;
|
||||
@@ -244,11 +256,12 @@ export class TerminalTab extends Disposable implements ITerminalTab {
|
||||
}
|
||||
this._terminalInstances.push(instance);
|
||||
this._initInstanceListeners(instance);
|
||||
this._activeInstanceIndex = 0;
|
||||
|
||||
if (this._container) {
|
||||
this.attachToElement(this._container);
|
||||
if (this._splitPaneContainer) {
|
||||
this._splitPaneContainer!.split(instance);
|
||||
}
|
||||
|
||||
this._onInstancesChanged.fire();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
|
||||
1454
src/vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon.ts
Normal file
1454
src/vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,7 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { TERMINAL_BACKGROUND_COLOR, TERMINAL_BORDER_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry';
|
||||
import { DataTransfers } from 'vs/base/browser/dnd';
|
||||
import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { ITerminalService, TerminalConnectionState } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { BrowserFeatures } from 'vs/base/browser/canIUse';
|
||||
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
@@ -41,6 +41,7 @@ export class TerminalViewPane extends ViewPane {
|
||||
private _terminalContainer: HTMLElement | undefined;
|
||||
private _findWidget: TerminalFindWidget | undefined;
|
||||
private _splitTerminalAction: IAction | undefined;
|
||||
private _terminalsInitialized = false;
|
||||
private _bodyDimensions: { width: number, height: number } = { width: 0, height: 0 };
|
||||
|
||||
constructor(
|
||||
@@ -75,11 +76,11 @@ export class TerminalViewPane extends ViewPane {
|
||||
}
|
||||
|
||||
this._parentDomElement = container;
|
||||
dom.addClass(this._parentDomElement, 'integrated-terminal');
|
||||
this._parentDomElement.classList.add('integrated-terminal');
|
||||
this._fontStyleElement = document.createElement('style');
|
||||
|
||||
this._terminalContainer = document.createElement('div');
|
||||
dom.addClass(this._terminalContainer, 'terminal-outer-container');
|
||||
this._terminalContainer.classList.add('terminal-outer-container');
|
||||
|
||||
this._findWidget = this._instantiationService.createInstance(TerminalFindWidget, this._terminalService.getFindState());
|
||||
this._findWidget.focusTracker.onDidFocus(() => this._terminalContainer!.classList.add(FIND_FOCUS_CLASS));
|
||||
@@ -109,18 +110,28 @@ export class TerminalViewPane extends ViewPane {
|
||||
|
||||
this._register(this.onDidChangeBodyVisibility(visible => {
|
||||
if (visible) {
|
||||
const hadTerminals = this._terminalService.terminalInstances.length > 0;
|
||||
if (!hadTerminals) {
|
||||
this._terminalService.createTerminal();
|
||||
const hadTerminals = !!this._terminalService.terminalTabs.length;
|
||||
if (this._terminalsInitialized) {
|
||||
if (!hadTerminals) {
|
||||
this._terminalService.createTerminal();
|
||||
}
|
||||
} else {
|
||||
this._terminalsInitialized = true;
|
||||
this._terminalService.initializeTerminals();
|
||||
}
|
||||
|
||||
this._updateTheme();
|
||||
if (hadTerminals) {
|
||||
this._terminalService.getActiveTab()?.setVisible(visible);
|
||||
} else {
|
||||
// TODO@Tyriar - this call seems unnecessary
|
||||
this.layoutBody(this._bodyDimensions.height, this._bodyDimensions.width);
|
||||
}
|
||||
} else {
|
||||
this._terminalService.getActiveTab()?.setVisible(false);
|
||||
this._terminalService.terminalInstances.forEach(instance => {
|
||||
instance.notifyFindWidgetFocusChanged(false);
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -204,8 +215,26 @@ export class TerminalViewPane extends ViewPane {
|
||||
return super.getActionViewItem(action);
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this._terminalService.getActiveInstance()?.focusWhenReady(true);
|
||||
public focus() {
|
||||
if (this._terminalService.connectionState === TerminalConnectionState.Connecting) {
|
||||
// If the terminal is waiting to reconnect to remote terminals, then there is no TerminalInstance yet that can
|
||||
// be focused. So wait for connection to finish, then focus.
|
||||
const activeElement = document.activeElement;
|
||||
this._register(this._terminalService.onDidChangeConnectionState(() => {
|
||||
// Only focus the terminal if the activeElement has not changed since focus() was called
|
||||
// TODO hack
|
||||
if (document.activeElement === activeElement) {
|
||||
this._focus();
|
||||
}
|
||||
}));
|
||||
|
||||
return;
|
||||
}
|
||||
this._focus();
|
||||
}
|
||||
|
||||
private _focus() {
|
||||
this._terminalService.getActiveInstance()?.focusWhenReady();
|
||||
}
|
||||
|
||||
public focusFindWidget() {
|
||||
|
||||
@@ -12,6 +12,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IHoverService, IHoverOptions } from 'vs/workbench/services/hover/browser/hover';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
export class EnvironmentVariableInfoWidget extends Widget implements ITerminalWidget {
|
||||
readonly id = 'env-var-info';
|
||||
@@ -34,7 +35,7 @@ export class EnvironmentVariableInfoWidget extends Widget implements ITerminalWi
|
||||
attach(container: HTMLElement): void {
|
||||
this._container = container;
|
||||
this._domNode = document.createElement('div');
|
||||
this._domNode.classList.add('terminal-env-var-info', 'codicon', `codicon-${this._info.getIcon()}`);
|
||||
this._domNode.classList.add('terminal-env-var-info', ...ThemeIcon.asClassNameArray(this._info.getIcon()));
|
||||
if (this.requiresAction) {
|
||||
this._domNode.classList.add('requires-action');
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IBufferCell } from 'xterm';
|
||||
|
||||
export type XTermAttributes = Omit<IBufferCell, 'getWidth' | 'getChars' | 'getCode'> & { clone?(): XTermAttributes };
|
||||
|
||||
export interface XTermCore {
|
||||
_onScroll: IEventEmitter<number>;
|
||||
_onKey: IEventEmitter<{ key: string }>;
|
||||
@@ -16,6 +20,10 @@ export interface XTermCore {
|
||||
triggerDataEvent(data: string, wasUserInput?: boolean): void;
|
||||
};
|
||||
|
||||
_inputHandler: {
|
||||
_curAttrData: XTermAttributes;
|
||||
};
|
||||
|
||||
_renderService: {
|
||||
dimensions: {
|
||||
actualCellWidth: number;
|
||||
@@ -26,6 +34,8 @@ export interface XTermCore {
|
||||
};
|
||||
_onIntersectionChange: any;
|
||||
};
|
||||
|
||||
writeSync(data: string | Uint8Array): void;
|
||||
}
|
||||
|
||||
export interface IEventEmitter<T> {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IProcessEnvironment } from 'vs/base/common/platform';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
export const IEnvironmentVariableService = createDecorator<IEnvironmentVariableService>('environmentVariableService');
|
||||
|
||||
@@ -98,6 +99,6 @@ export type ISerializableEnvironmentVariableCollection = [string, IEnvironmentVa
|
||||
export interface IEnvironmentVariableInfo {
|
||||
readonly requiresAction: boolean;
|
||||
getInfo(): string;
|
||||
getIcon(): string;
|
||||
getIcon(): ThemeIcon;
|
||||
getActions?(): { label: string, iconClass?: string, run: () => void, commandId: string }[];
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { IEnvironmentVariableService, IMergedEnvironmentVariableCollection, ISerializableEnvironmentVariableCollection, IEnvironmentVariableCollectionWithPersistence } from 'vs/workbench/contrib/terminal/common/environmentVariable';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { debounce, throttle } from 'vs/base/common/decorators';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { MergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableCollection';
|
||||
import { deserializeEnvironmentVariableCollection, serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared';
|
||||
@@ -85,7 +85,7 @@ export class EnvironmentVariableService implements IEnvironmentVariableService {
|
||||
}
|
||||
});
|
||||
const stringifiedJson = JSON.stringify(collectionsJson);
|
||||
this._storageService.store(ENVIRONMENT_VARIABLE_COLLECTIONS_KEY, stringifiedJson, StorageScope.WORKSPACE);
|
||||
this._storageService.store(ENVIRONMENT_VARIABLE_COLLECTIONS_KEY, stringifiedJson, StorageScope.WORKSPACE, StorageTarget.MACHINE);
|
||||
}
|
||||
|
||||
@debounce(1000)
|
||||
|
||||
@@ -0,0 +1,362 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { IChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IEnvironmentVariableService, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
|
||||
import { serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared';
|
||||
import { ITerminalConfiguration, ITerminalEnvironment, ITerminalLaunchError, TERMINAL_CONFIG_SECTION } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
|
||||
import { SideBySideEditor, EditorResourceAccessor } from 'vs/workbench/common/editor';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
|
||||
export const REMOTE_TERMINAL_CHANNEL_NAME = 'remoteterminal';
|
||||
|
||||
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;
|
||||
defaultValue: T | undefined;
|
||||
}
|
||||
|
||||
export interface ICompleteTerminalConfiguration {
|
||||
'terminal.integrated.automationShell.windows': ISingleTerminalConfiguration<string | string[]>;
|
||||
'terminal.integrated.automationShell.osx': ISingleTerminalConfiguration<string | string[]>;
|
||||
'terminal.integrated.automationShell.linux': ISingleTerminalConfiguration<string | string[]>;
|
||||
'terminal.integrated.shell.windows': ISingleTerminalConfiguration<string | string[]>;
|
||||
'terminal.integrated.shell.osx': ISingleTerminalConfiguration<string | string[]>;
|
||||
'terminal.integrated.shell.linux': ISingleTerminalConfiguration<string | string[]>;
|
||||
'terminal.integrated.shellArgs.windows': ISingleTerminalConfiguration<string | string[]>;
|
||||
'terminal.integrated.shellArgs.osx': ISingleTerminalConfiguration<string | string[]>;
|
||||
'terminal.integrated.shellArgs.linux': ISingleTerminalConfiguration<string | string[]>;
|
||||
'terminal.integrated.env.windows': ISingleTerminalConfiguration<ITerminalEnvironment>;
|
||||
'terminal.integrated.env.osx': ISingleTerminalConfiguration<ITerminalEnvironment>;
|
||||
'terminal.integrated.env.linux': ISingleTerminalConfiguration<ITerminalEnvironment>;
|
||||
'terminal.integrated.inheritEnv': boolean;
|
||||
'terminal.integrated.cwd': string;
|
||||
'terminal.integrated.detectLocale': 'auto' | 'off' | 'on';
|
||||
}
|
||||
|
||||
export type ITerminalEnvironmentVariableCollections = [string, ISerializableEnvironmentVariableCollection][];
|
||||
|
||||
export interface IWorkspaceFolderData {
|
||||
uri: UriComponents;
|
||||
name: string;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export interface ICreateTerminalProcessArguments {
|
||||
configuration: ICompleteTerminalConfiguration;
|
||||
resolvedVariables: { [name: string]: string; };
|
||||
envVariableCollections: ITerminalEnvironmentVariableCollections;
|
||||
shellLaunchConfig: IShellLaunchConfigDto;
|
||||
workspaceId: string;
|
||||
workspaceName: string;
|
||||
workspaceFolders: IWorkspaceFolderData[];
|
||||
activeWorkspaceFolder: IWorkspaceFolderData | null;
|
||||
activeFileResource: UriComponents | undefined;
|
||||
shouldPersistTerminal: boolean;
|
||||
cols: number;
|
||||
rows: number;
|
||||
isWorkspaceShellAllowed: boolean;
|
||||
resolverEnv: { [key: string]: string | null; } | undefined
|
||||
}
|
||||
|
||||
export interface ICreateTerminalProcessResult {
|
||||
terminalId: number;
|
||||
resolvedShellLaunchConfig: IShellLaunchConfigDto;
|
||||
}
|
||||
|
||||
export interface IStartTerminalProcessArguments {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface ISendInputToTerminalProcessArguments {
|
||||
id: number;
|
||||
data: string;
|
||||
}
|
||||
|
||||
export interface IShutdownTerminalProcessArguments {
|
||||
id: number;
|
||||
immediate: boolean;
|
||||
}
|
||||
|
||||
export interface IResizeTerminalProcessArguments {
|
||||
id: number;
|
||||
cols: number;
|
||||
rows: number;
|
||||
}
|
||||
|
||||
export interface IGetTerminalInitialCwdArguments {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface IGetTerminalCwdArguments {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface ISendCommandResultToTerminalProcessArguments {
|
||||
id: number;
|
||||
reqId: number;
|
||||
isError: boolean;
|
||||
payload: any;
|
||||
}
|
||||
|
||||
export interface IOrphanQuestionReplyArgs {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface IListTerminalsArgs {
|
||||
isInitialization: boolean;
|
||||
}
|
||||
|
||||
export interface IRemoteTerminalDescriptionDto {
|
||||
id: number;
|
||||
pid: number;
|
||||
title: string;
|
||||
cwd: string;
|
||||
workspaceId: string;
|
||||
workspaceName: string;
|
||||
}
|
||||
|
||||
export interface ITriggerTerminalDataReplayArguments {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface IRemoteTerminalProcessReadyEvent {
|
||||
type: 'ready';
|
||||
pid: number;
|
||||
cwd: string;
|
||||
}
|
||||
export interface IRemoteTerminalProcessTitleChangedEvent {
|
||||
type: 'titleChanged';
|
||||
title: string;
|
||||
}
|
||||
export interface IRemoteTerminalProcessDataEvent {
|
||||
type: 'data';
|
||||
data: string;
|
||||
}
|
||||
export interface ReplayEntry { cols: number; rows: number; data: string; }
|
||||
export interface IRemoteTerminalProcessReplayEvent {
|
||||
type: 'replay';
|
||||
events: ReplayEntry[];
|
||||
}
|
||||
export interface IRemoteTerminalProcessExitEvent {
|
||||
type: 'exit';
|
||||
exitCode: number | undefined;
|
||||
}
|
||||
export interface IRemoteTerminalProcessExecCommandEvent {
|
||||
type: 'execCommand';
|
||||
reqId: number;
|
||||
commandId: string;
|
||||
commandArgs: any[];
|
||||
}
|
||||
export interface IRemoteTerminalProcessOrphanQuestionEvent {
|
||||
type: 'orphan?';
|
||||
}
|
||||
export type IRemoteTerminalProcessEvent = (
|
||||
IRemoteTerminalProcessReadyEvent
|
||||
| IRemoteTerminalProcessTitleChangedEvent
|
||||
| IRemoteTerminalProcessDataEvent
|
||||
| IRemoteTerminalProcessReplayEvent
|
||||
| IRemoteTerminalProcessExitEvent
|
||||
| IRemoteTerminalProcessExecCommandEvent
|
||||
| IRemoteTerminalProcessOrphanQuestionEvent
|
||||
);
|
||||
|
||||
export interface IOnTerminalProcessEventArguments {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export class RemoteTerminalChannelClient {
|
||||
|
||||
constructor(
|
||||
private readonly _remoteAuthority: string,
|
||||
private readonly _channel: IChannel,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,
|
||||
@IConfigurationResolverService private readonly _resolverService: IConfigurationResolverService,
|
||||
@IEnvironmentVariableService private readonly _environmentVariableService: IEnvironmentVariableService,
|
||||
@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@ILabelService private readonly _labelService: ILabelService,
|
||||
) { }
|
||||
|
||||
private _readSingleTerminalConfiguration<T>(key: string): ISingleTerminalConfiguration<T> {
|
||||
const result = this._configurationService.inspect<T>(key);
|
||||
return {
|
||||
userValue: result.userValue,
|
||||
value: result.value,
|
||||
defaultValue: result.defaultValue,
|
||||
};
|
||||
}
|
||||
|
||||
public async createTerminalProcess(shellLaunchConfig: IShellLaunchConfigDto, activeWorkspaceRootUri: URI | undefined, shouldPersistTerminal: boolean, cols: number, rows: number, isWorkspaceShellAllowed: boolean): Promise<ICreateTerminalProcessResult> {
|
||||
const terminalConfig = this._configurationService.getValue<ITerminalConfiguration>(TERMINAL_CONFIG_SECTION);
|
||||
const configuration: ICompleteTerminalConfiguration = {
|
||||
'terminal.integrated.automationShell.windows': this._readSingleTerminalConfiguration('terminal.integrated.automationShell.windows'),
|
||||
'terminal.integrated.automationShell.osx': this._readSingleTerminalConfiguration('terminal.integrated.automationShell.osx'),
|
||||
'terminal.integrated.automationShell.linux': this._readSingleTerminalConfiguration('terminal.integrated.automationShell.linux'),
|
||||
'terminal.integrated.shell.windows': this._readSingleTerminalConfiguration('terminal.integrated.shell.windows'),
|
||||
'terminal.integrated.shell.osx': this._readSingleTerminalConfiguration('terminal.integrated.shell.osx'),
|
||||
'terminal.integrated.shell.linux': this._readSingleTerminalConfiguration('terminal.integrated.shell.linux'),
|
||||
'terminal.integrated.shellArgs.windows': this._readSingleTerminalConfiguration('terminal.integrated.shellArgs.windows'),
|
||||
'terminal.integrated.shellArgs.osx': this._readSingleTerminalConfiguration('terminal.integrated.shellArgs.osx'),
|
||||
'terminal.integrated.shellArgs.linux': this._readSingleTerminalConfiguration('terminal.integrated.shellArgs.linux'),
|
||||
'terminal.integrated.env.windows': this._readSingleTerminalConfiguration('terminal.integrated.env.windows'),
|
||||
'terminal.integrated.env.osx': this._readSingleTerminalConfiguration('terminal.integrated.env.osx'),
|
||||
'terminal.integrated.env.linux': this._readSingleTerminalConfiguration('terminal.integrated.env.linux'),
|
||||
'terminal.integrated.inheritEnv': terminalConfig.inheritEnv,
|
||||
'terminal.integrated.cwd': terminalConfig.cwd,
|
||||
'terminal.integrated.detectLocale': terminalConfig.detectLocale,
|
||||
};
|
||||
|
||||
// We will use the resolver service to resolve all the variables in the config / launch config
|
||||
// But then we will keep only some variables, since the rest need to be resolved on the remote side
|
||||
const resolvedVariables = Object.create(null);
|
||||
const lastActiveWorkspace = activeWorkspaceRootUri ? withNullAsUndefined(this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri)) : undefined;
|
||||
let allResolvedVariables: Map<string, string> | undefined = undefined;
|
||||
try {
|
||||
allResolvedVariables = await this._resolverService.resolveWithInteraction(lastActiveWorkspace, {
|
||||
shellLaunchConfig,
|
||||
configuration
|
||||
});
|
||||
} catch (err) {
|
||||
this._logService.error(err);
|
||||
}
|
||||
if (allResolvedVariables) {
|
||||
for (const [name, value] of allResolvedVariables.entries()) {
|
||||
if (/^config:/.test(name) || name === 'selectedText' || name === 'lineNumber') {
|
||||
resolvedVariables[name] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const envVariableCollections: ITerminalEnvironmentVariableCollections = [];
|
||||
for (const [k, v] of this._environmentVariableService.collections.entries()) {
|
||||
envVariableCollections.push([k, serializeEnvironmentVariableCollection(v.map)]);
|
||||
}
|
||||
|
||||
const resolverResult = await this._remoteAuthorityResolverService.resolveAuthority(this._remoteAuthority);
|
||||
const resolverEnv = resolverResult.options && resolverResult.options.extensionHostEnv;
|
||||
|
||||
const workspace = this._workspaceContextService.getWorkspace();
|
||||
const workspaceFolders = workspace.folders;
|
||||
const activeWorkspaceFolder = activeWorkspaceRootUri ? this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) : null;
|
||||
|
||||
const activeFileResource = EditorResourceAccessor.getOriginalUri(this._editorService.activeEditor, {
|
||||
supportSideBySide: SideBySideEditor.PRIMARY,
|
||||
filterByScheme: [Schemas.file, Schemas.userData, Schemas.vscodeRemote]
|
||||
});
|
||||
|
||||
const args: ICreateTerminalProcessArguments = {
|
||||
configuration,
|
||||
resolvedVariables,
|
||||
envVariableCollections,
|
||||
shellLaunchConfig,
|
||||
workspaceId: workspace.id,
|
||||
workspaceName: this._labelService.getWorkspaceLabel(workspace),
|
||||
workspaceFolders,
|
||||
activeWorkspaceFolder,
|
||||
activeFileResource,
|
||||
shouldPersistTerminal,
|
||||
cols,
|
||||
rows,
|
||||
isWorkspaceShellAllowed,
|
||||
resolverEnv
|
||||
};
|
||||
return await this._channel.call<ICreateTerminalProcessResult>('$createTerminalProcess', args);
|
||||
}
|
||||
|
||||
public async startTerminalProcess(terminalId: number): Promise<ITerminalLaunchError | void> {
|
||||
const args: IStartTerminalProcessArguments = {
|
||||
id: terminalId
|
||||
};
|
||||
return this._channel.call<ITerminalLaunchError | void>('$startTerminalProcess', args);
|
||||
}
|
||||
|
||||
public onTerminalProcessEvent(terminalId: number): Event<IRemoteTerminalProcessEvent> {
|
||||
const args: IOnTerminalProcessEventArguments = {
|
||||
id: terminalId
|
||||
};
|
||||
return this._channel.listen<IRemoteTerminalProcessEvent>('$onTerminalProcessEvent', args);
|
||||
}
|
||||
|
||||
public sendInputToTerminalProcess(id: number, data: string): Promise<void> {
|
||||
const args: ISendInputToTerminalProcessArguments = {
|
||||
id, data
|
||||
};
|
||||
return this._channel.call<void>('$sendInputToTerminalProcess', args);
|
||||
}
|
||||
|
||||
public shutdownTerminalProcess(id: number, immediate: boolean): Promise<void> {
|
||||
const args: IShutdownTerminalProcessArguments = {
|
||||
id, immediate
|
||||
};
|
||||
return this._channel.call<void>('$shutdownTerminalProcess', args);
|
||||
}
|
||||
|
||||
public resizeTerminalProcess(id: number, cols: number, rows: number): Promise<void> {
|
||||
const args: IResizeTerminalProcessArguments = {
|
||||
id, cols, rows
|
||||
};
|
||||
return this._channel.call<void>('$resizeTerminalProcess', args);
|
||||
}
|
||||
|
||||
public getTerminalInitialCwd(id: number): Promise<string> {
|
||||
const args: IGetTerminalInitialCwdArguments = {
|
||||
id
|
||||
};
|
||||
return this._channel.call<string>('$getTerminalInitialCwd', args);
|
||||
}
|
||||
|
||||
public getTerminalCwd(id: number): Promise<string> {
|
||||
const args: IGetTerminalCwdArguments = {
|
||||
id
|
||||
};
|
||||
return this._channel.call<string>('$getTerminalCwd', args);
|
||||
}
|
||||
|
||||
public sendCommandResultToTerminalProcess(id: number, reqId: number, isError: boolean, payload: any): Promise<void> {
|
||||
const args: ISendCommandResultToTerminalProcessArguments = {
|
||||
id,
|
||||
reqId,
|
||||
isError,
|
||||
payload
|
||||
};
|
||||
return this._channel.call<void>('$sendCommandResultToTerminalProcess', args);
|
||||
}
|
||||
|
||||
public orphanQuestionReply(id: number): Promise<void> {
|
||||
const args: IOrphanQuestionReplyArgs = {
|
||||
id
|
||||
};
|
||||
return this._channel.call<void>('$orphanQuestionReply', args);
|
||||
}
|
||||
|
||||
public listTerminals(isInitialization: boolean): Promise<IRemoteTerminalDescriptionDto[]> {
|
||||
const args: IListTerminalsArgs = {
|
||||
isInitialization
|
||||
};
|
||||
return this._channel.call<IRemoteTerminalDescriptionDto[]>('$listTerminals', args);
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@ export const KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED = new RawContextKey<b
|
||||
|
||||
export const IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY = 'terminal.integrated.isWorkspaceShellAllowed';
|
||||
export const NEVER_MEASURE_RENDER_TIME_STORAGE_KEY = 'terminal.integrated.neverMeasureRenderTime';
|
||||
|
||||
export const SHOW_TERMINAL_CONFIG_PROMPT = 'terminal.integrated.showTerminalConfigPrompt';
|
||||
// The creation of extension host terminals is delayed by this value (milliseconds). The purpose of
|
||||
// this delay is to allow the terminal instance to initialize correctly and have its ID set before
|
||||
// trying to create the corressponding object on the ext host.
|
||||
@@ -108,6 +108,7 @@ export interface ITerminalConfiguration {
|
||||
fontWeightBold: FontWeight;
|
||||
minimumContrastRatio: number;
|
||||
mouseWheelScrollSensitivity: number;
|
||||
sendKeybindingsToShell: boolean;
|
||||
// fontLigatures: boolean;
|
||||
fontSize: number;
|
||||
letterSpacing: number;
|
||||
@@ -135,8 +136,15 @@ export interface ITerminalConfiguration {
|
||||
enableFileLinks: boolean;
|
||||
unicodeVersion: '6' | '11';
|
||||
experimentalLinkProvider: boolean;
|
||||
localEchoLatencyThreshold: number;
|
||||
localEchoExcludePrograms: ReadonlyArray<string>;
|
||||
localEchoStyle: 'bold' | 'dim' | 'italic' | 'underlined' | 'inverted' | string;
|
||||
serverSpawn: boolean;
|
||||
enablePersistentSessions: boolean;
|
||||
}
|
||||
|
||||
export const DEFAULT_LOCAL_ECHO_EXCLUDE: ReadonlyArray<string> = ['vim', 'vi', 'nano', 'tmux'];
|
||||
|
||||
export interface ITerminalConfigHelper {
|
||||
config: ITerminalConfiguration;
|
||||
|
||||
@@ -163,6 +171,15 @@ export interface ITerminalEnvironment {
|
||||
[key: string]: string | null;
|
||||
}
|
||||
|
||||
export interface IRemoteTerminalAttachTarget {
|
||||
id: number;
|
||||
pid: number;
|
||||
title: string;
|
||||
cwd: string;
|
||||
workspaceId: string;
|
||||
workspaceName: string;
|
||||
}
|
||||
|
||||
export interface IShellLaunchConfig {
|
||||
/**
|
||||
* The name of the terminal, if this is not set the name of the process will be used.
|
||||
@@ -215,6 +232,11 @@ export interface IShellLaunchConfig {
|
||||
*/
|
||||
isExtensionTerminal?: boolean;
|
||||
|
||||
/**
|
||||
* This is a terminal that attaches to an already running remote terminal.
|
||||
*/
|
||||
remoteAttach?: { id: number; pid: number; title: string; cwd: string; };
|
||||
|
||||
/**
|
||||
* Whether the terminal process environment should be exactly as provided in
|
||||
* `TerminalOptions.env`. When this is false (default), the environment will be based on the
|
||||
@@ -232,6 +254,12 @@ export interface IShellLaunchConfig {
|
||||
* as normal.
|
||||
*/
|
||||
hideFromUser?: boolean;
|
||||
|
||||
/**
|
||||
* Whether this terminal is not a terminal that the user directly created and uses, but rather
|
||||
* a terminal used to drive some VS Code feature.
|
||||
*/
|
||||
isFeatureTerminal?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -266,6 +294,13 @@ export interface ITerminalDimensions {
|
||||
readonly rows: number;
|
||||
}
|
||||
|
||||
export interface ITerminalDimensionsOverride extends ITerminalDimensions {
|
||||
/**
|
||||
* indicate that xterm must receive these exact dimensions, even if they overflow the ui!
|
||||
*/
|
||||
forceExactSize?: boolean;
|
||||
}
|
||||
|
||||
export interface ICommandTracker {
|
||||
scrollToPreviousCommand(): void;
|
||||
scrollToNextCommand(): void;
|
||||
@@ -289,6 +324,11 @@ export interface IBeforeProcessDataEvent {
|
||||
data: string;
|
||||
}
|
||||
|
||||
export interface IProcessDataEvent {
|
||||
data: string;
|
||||
sync: boolean;
|
||||
}
|
||||
|
||||
export interface ITerminalProcessManager extends IDisposable {
|
||||
readonly processState: ProcessState;
|
||||
readonly ptyProcessReady: Promise<void>;
|
||||
@@ -300,10 +340,10 @@ export interface ITerminalProcessManager extends IDisposable {
|
||||
|
||||
readonly onProcessReady: Event<void>;
|
||||
readonly onBeforeProcessData: Event<IBeforeProcessDataEvent>;
|
||||
readonly onProcessData: Event<string>;
|
||||
readonly onProcessData: Event<IProcessDataEvent>;
|
||||
readonly onProcessTitle: Event<string>;
|
||||
readonly onProcessExit: Event<number | undefined>;
|
||||
readonly onProcessOverrideDimensions: Event<ITerminalDimensions | undefined>;
|
||||
readonly onProcessOverrideDimensions: Event<ITerminalDimensionsOverride | undefined>;
|
||||
readonly onProcessResolvedShellLaunchConfig: Event<IShellLaunchConfig>;
|
||||
readonly onEnvironmentVariableInfoChanged: Event<IEnvironmentVariableInfo>;
|
||||
|
||||
@@ -414,11 +454,11 @@ export interface ITerminalLaunchError {
|
||||
* child_process.ChildProcess node.js interface.
|
||||
*/
|
||||
export interface ITerminalChildProcess {
|
||||
onProcessData: Event<string>;
|
||||
onProcessData: Event<IProcessDataEvent | string>;
|
||||
onProcessExit: Event<number | undefined>;
|
||||
onProcessReady: Event<{ pid: number, cwd: string }>;
|
||||
onProcessTitleChanged: Event<string>;
|
||||
onProcessOverrideDimensions?: Event<ITerminalDimensions | undefined>;
|
||||
onProcessOverrideDimensions?: Event<ITerminalDimensionsOverride | undefined>;
|
||||
onProcessResolvedShellLaunchConfig?: Event<IShellLaunchConfig>;
|
||||
|
||||
/**
|
||||
@@ -450,6 +490,7 @@ export const enum TERMINAL_COMMAND_ID {
|
||||
TOGGLE = 'workbench.action.terminal.toggleTerminal',
|
||||
KILL = 'workbench.action.terminal.kill',
|
||||
QUICK_KILL = 'workbench.action.terminal.quickKill',
|
||||
CONFIGURE_TERMINAL_SETTINGS = 'workbench.action.terminal.openSettings',
|
||||
COPY_SELECTION = 'workbench.action.terminal.copySelection',
|
||||
SELECT_ALL = 'workbench.action.terminal.selectAll',
|
||||
DELETE_WORD_LEFT = 'workbench.action.terminal.deleteWordLeft',
|
||||
@@ -507,7 +548,8 @@ export const enum TERMINAL_COMMAND_ID {
|
||||
NAVIGATION_MODE_FOCUS_NEXT = 'workbench.action.terminal.navigationModeFocusNext',
|
||||
NAVIGATION_MODE_FOCUS_PREVIOUS = 'workbench.action.terminal.navigationModeFocusPrevious',
|
||||
SHOW_ENVIRONMENT_INFORMATION = 'workbench.action.terminal.showEnvironmentInformation',
|
||||
SEARCH_WORKSPACE = 'workbench.action.terminal.searchWorkspace'
|
||||
SEARCH_WORKSPACE = 'workbench.action.terminal.searchWorkspace',
|
||||
ATTACH_TO_REMOTE_TERMINAL = 'workbench.action.terminal.attachToSession'
|
||||
}
|
||||
|
||||
export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { localize } from 'vs/nls';
|
||||
import { EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions';
|
||||
import { DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, DEFAULT_COMMANDS_TO_SKIP_SHELL, SUGGESTIONS_FONT_WEIGHT, MINIMUM_FONT_WEIGHT, MAXIMUM_FONT_WEIGHT } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, DEFAULT_COMMANDS_TO_SKIP_SHELL, SUGGESTIONS_FONT_WEIGHT, MINIMUM_FONT_WEIGHT, MAXIMUM_FONT_WEIGHT, DEFAULT_LOCAL_ECHO_EXCLUDE } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { isMacintosh, isWindows, Platform } from 'vs/base/common/platform';
|
||||
|
||||
export const terminalConfiguration: IConfigurationNode = {
|
||||
@@ -15,6 +15,11 @@ export const terminalConfiguration: IConfigurationNode = {
|
||||
title: localize('terminalIntegratedConfigurationTitle', "Integrated Terminal"),
|
||||
type: 'object',
|
||||
properties: {
|
||||
'terminal.integrated.sendKeybindingsToShell': {
|
||||
markdownDescription: localize('terminal.integrated.sendKeybindingsToShell', "Dispatches most keybindings to the terminal instead of the workbench, overriding `#terminal.integrated.commandsToSkipShell#`, which can be used alternatively for fine tuning."),
|
||||
type: 'boolean',
|
||||
default: false
|
||||
},
|
||||
'terminal.integrated.automationShell.linux': {
|
||||
markdownDescription: localize({
|
||||
key: 'terminal.integrated.automationShell.linux',
|
||||
@@ -211,7 +216,7 @@ export const terminalConfiguration: IConfigurationNode = {
|
||||
localize('terminal.integrated.rendererType.auto', "Let VS Code guess which renderer to use."),
|
||||
localize('terminal.integrated.rendererType.canvas', "Use the standard GPU/canvas-based renderer."),
|
||||
localize('terminal.integrated.rendererType.dom', "Use the fallback DOM-based renderer."),
|
||||
localize('terminal.integrated.rendererType.experimentalWebgl', "Use the experimental webgl-based renderer. Note that this has some [known issues](https://github.com/xtermjs/xterm.js/issues?q=is%3Aopen+is%3Aissue+label%3Aarea%2Faddon%2Fwebgl) and this will only be enabled for new terminals (not hot swappable like the other renderers).")
|
||||
localize('terminal.integrated.rendererType.experimentalWebgl', "Use the experimental webgl-based renderer. Note that this has some [known issues](https://github.com/xtermjs/xterm.js/issues?q=is%3Aopen+is%3Aissue+label%3Aarea%2Faddon%2Fwebgl).")
|
||||
],
|
||||
default: 'auto',
|
||||
description: localize('terminal.integrated.rendererType', "Controls how the terminal is rendered.")
|
||||
@@ -351,6 +356,47 @@ export const terminalConfiguration: IConfigurationNode = {
|
||||
description: localize('terminal.integrated.experimentalLinkProvider', "An experimental setting that aims to improve link detection in the terminal by improving when links are detected and by enabling shared link detection with the editor. Currently this only supports web links."),
|
||||
type: 'boolean',
|
||||
default: true
|
||||
},
|
||||
'terminal.integrated.localEchoLatencyThreshold': {
|
||||
description: localize('terminal.integrated.localEchoLatencyThreshold', "Experimental: length of network delay, in milliseconds, where local edits will be echoed on the terminal without waiting for server acknowledgement. If '0', local echo will always be on, and if '-1' it will be disabled."),
|
||||
type: 'integer',
|
||||
minimum: -1,
|
||||
default: 30,
|
||||
},
|
||||
'terminal.integrated.localEchoExcludePrograms': {
|
||||
description: localize('terminal.integrated.localEchoExcludePrograms', "Experimental: local echo will be disabled when any of these program names are found in the terminal title."),
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
uniqueItems: true
|
||||
},
|
||||
default: DEFAULT_LOCAL_ECHO_EXCLUDE,
|
||||
},
|
||||
'terminal.integrated.localEchoStyle': {
|
||||
description: localize('terminal.integrated.localEchoStyle', "Experimental: terminal style of locally echoed text; either a font style or an RGB color."),
|
||||
default: 'dim',
|
||||
oneOf: [
|
||||
{
|
||||
type: 'string',
|
||||
default: 'dim',
|
||||
enum: ['bold', 'dim', 'italic', 'underlined', 'inverted'],
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
format: 'color-hex',
|
||||
default: '#ff0000',
|
||||
}
|
||||
]
|
||||
},
|
||||
'terminal.integrated.serverSpawn': {
|
||||
description: localize('terminal.integrated.serverSpawn', "Experimental: spawn remote terminals from the remote agent process instead of the remote extension host"),
|
||||
type: 'boolean',
|
||||
default: true
|
||||
},
|
||||
'terminal.integrated.enablePersistentSessions': {
|
||||
description: localize('terminal.integrated.enablePersistentSessions', "Experimental: persist terminal sessions for the workspace across window reloads. Currently only supported in VS Code Remote workspaces."),
|
||||
type: 'boolean',
|
||||
default: true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IProcessDataEvent } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
|
||||
interface TerminalDataBuffer extends IDisposable {
|
||||
data: string[];
|
||||
@@ -23,19 +24,20 @@ export class TerminalDataBufferer implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
startBuffering(id: number, event: Event<string>, throttleBy: number = 5): IDisposable {
|
||||
startBuffering(id: number, event: Event<string | IProcessDataEvent>, throttleBy: number = 5): IDisposable {
|
||||
let disposable: IDisposable;
|
||||
disposable = event((e: string) => {
|
||||
disposable = event((e: string | IProcessDataEvent) => {
|
||||
const data = (typeof e === 'string' ? e : e.data);
|
||||
let buffer = this._terminalBufferMap.get(id);
|
||||
if (buffer) {
|
||||
buffer.data.push(e);
|
||||
buffer.data.push(data);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const timeoutId = setTimeout(() => this._flushBuffer(id), throttleBy);
|
||||
buffer = {
|
||||
data: [e],
|
||||
data: [data],
|
||||
timeoutId: timeoutId,
|
||||
dispose: () => {
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
@@ -74,12 +74,12 @@ function mergeNonNullKeys(env: platform.IProcessEnvironment, other: ITerminalEnv
|
||||
}
|
||||
}
|
||||
|
||||
function resolveConfigurationVariables(configurationResolverService: IConfigurationResolverService, env: ITerminalEnvironment, lastActiveWorkspaceRoot: IWorkspaceFolder | undefined): ITerminalEnvironment {
|
||||
function resolveConfigurationVariables(variableResolver: VariableResolver, env: ITerminalEnvironment): ITerminalEnvironment {
|
||||
Object.keys(env).forEach((key) => {
|
||||
const value = env[key];
|
||||
if (typeof value === 'string') {
|
||||
try {
|
||||
env[key] = configurationResolverService.resolve(lastActiveWorkspaceRoot, value);
|
||||
env[key] = variableResolver(value);
|
||||
} catch (e) {
|
||||
env[key] = value;
|
||||
}
|
||||
@@ -93,7 +93,8 @@ export function shouldSetLangEnvVariable(env: platform.IProcessEnvironment, dete
|
||||
return true;
|
||||
}
|
||||
if (detectLocale === 'auto') {
|
||||
return !env['LANG'] || (env['LANG'].search(/\.UTF\-8$/) === -1 && env['LANG'].search(/\.utf8$/) === -1);
|
||||
const lang = env['LANG'];
|
||||
return !lang || (lang.search(/\.UTF\-8$/) === -1 && lang.search(/\.utf8$/) === -1 && lang.search(/\.euc.+/) === -1);
|
||||
}
|
||||
return false; // 'off'
|
||||
}
|
||||
@@ -177,23 +178,22 @@ export function getLangEnvVariable(locale?: string): string {
|
||||
export function getCwd(
|
||||
shell: IShellLaunchConfig,
|
||||
userHome: string | undefined,
|
||||
lastActiveWorkspace: IWorkspaceFolder | undefined,
|
||||
configurationResolverService: IConfigurationResolverService | undefined,
|
||||
variableResolver: VariableResolver | undefined,
|
||||
root: Uri | undefined,
|
||||
customCwd: string | undefined,
|
||||
logService?: ILogService
|
||||
): string {
|
||||
if (shell.cwd) {
|
||||
const unresolved = (typeof shell.cwd === 'object') ? shell.cwd.fsPath : shell.cwd;
|
||||
const resolved = _resolveCwd(unresolved, lastActiveWorkspace, configurationResolverService);
|
||||
const resolved = _resolveCwd(unresolved, variableResolver);
|
||||
return _sanitizeCwd(resolved || unresolved);
|
||||
}
|
||||
|
||||
let cwd: string | undefined;
|
||||
|
||||
if (!shell.ignoreConfigurationCwd && customCwd) {
|
||||
if (configurationResolverService) {
|
||||
customCwd = _resolveCwd(customCwd, lastActiveWorkspace, configurationResolverService, logService);
|
||||
if (variableResolver) {
|
||||
customCwd = _resolveCwd(customCwd, variableResolver, logService);
|
||||
}
|
||||
if (customCwd) {
|
||||
if (path.isAbsolute(customCwd)) {
|
||||
@@ -212,10 +212,10 @@ export function getCwd(
|
||||
return _sanitizeCwd(cwd);
|
||||
}
|
||||
|
||||
function _resolveCwd(cwd: string, lastActiveWorkspace: IWorkspaceFolder | undefined, configurationResolverService: IConfigurationResolverService | undefined, logService?: ILogService): string | undefined {
|
||||
if (configurationResolverService) {
|
||||
function _resolveCwd(cwd: string, variableResolver: VariableResolver | undefined, logService?: ILogService): string | undefined {
|
||||
if (variableResolver) {
|
||||
try {
|
||||
return configurationResolverService.resolve(lastActiveWorkspace, cwd);
|
||||
return variableResolver(cwd);
|
||||
} catch (e) {
|
||||
logService?.error('Could not resolve terminal cwd', e);
|
||||
return undefined;
|
||||
@@ -245,14 +245,37 @@ export function escapeNonWindowsPath(path: string): string {
|
||||
return newPath;
|
||||
}
|
||||
|
||||
export type TerminalShellSetting = (
|
||||
`terminal.integrated.automationShell.windows`
|
||||
| `terminal.integrated.automationShell.osx`
|
||||
| `terminal.integrated.automationShell.linux`
|
||||
| `terminal.integrated.shell.windows`
|
||||
| `terminal.integrated.shell.osx`
|
||||
| `terminal.integrated.shell.linux`
|
||||
);
|
||||
|
||||
export type TerminalShellArgsSetting = (
|
||||
`terminal.integrated.shellArgs.windows`
|
||||
| `terminal.integrated.shellArgs.osx`
|
||||
| `terminal.integrated.shellArgs.linux`
|
||||
);
|
||||
|
||||
export type VariableResolver = (str: string) => string;
|
||||
|
||||
export function createVariableResolver(lastActiveWorkspace: IWorkspaceFolder | undefined, configurationResolverService: IConfigurationResolverService | undefined): VariableResolver | undefined {
|
||||
if (!configurationResolverService) {
|
||||
return undefined;
|
||||
}
|
||||
return (str) => configurationResolverService.resolve(lastActiveWorkspace, str);
|
||||
}
|
||||
|
||||
export function getDefaultShell(
|
||||
fetchSetting: (key: string) => { userValue?: string | string[], value?: string | string[], defaultValue?: string | string[] },
|
||||
fetchSetting: (key: TerminalShellSetting) => { userValue?: string | string[], value?: string | string[], defaultValue?: string | string[] },
|
||||
isWorkspaceShellAllowed: boolean,
|
||||
defaultShell: string,
|
||||
isWoW64: boolean,
|
||||
windir: string | undefined,
|
||||
lastActiveWorkspace: IWorkspaceFolder | undefined,
|
||||
configurationResolverService: IConfigurationResolverService | undefined,
|
||||
variableResolver: VariableResolver | undefined,
|
||||
logService: ILogService,
|
||||
useAutomationShell: boolean,
|
||||
platformOverride: platform.Platform = platform.platform
|
||||
@@ -282,9 +305,9 @@ export function getDefaultShell(
|
||||
executable = executable.replace(/\//g, '\\');
|
||||
}
|
||||
|
||||
if (configurationResolverService) {
|
||||
if (variableResolver) {
|
||||
try {
|
||||
executable = configurationResolverService.resolve(lastActiveWorkspace, executable);
|
||||
executable = variableResolver(executable);
|
||||
} catch (e) {
|
||||
logService.error(`Could not resolve shell`, e);
|
||||
}
|
||||
@@ -294,11 +317,10 @@ export function getDefaultShell(
|
||||
}
|
||||
|
||||
export function getDefaultShellArgs(
|
||||
fetchSetting: (key: string) => { userValue?: string | string[], value?: string | string[], defaultValue?: string | string[] },
|
||||
fetchSetting: (key: TerminalShellSetting | TerminalShellArgsSetting) => { userValue?: string | string[], value?: string | string[], defaultValue?: string | string[] },
|
||||
isWorkspaceShellAllowed: boolean,
|
||||
useAutomationShell: boolean,
|
||||
lastActiveWorkspace: IWorkspaceFolder | undefined,
|
||||
configurationResolverService: IConfigurationResolverService | undefined,
|
||||
variableResolver: VariableResolver | undefined,
|
||||
logService: ILogService,
|
||||
platformOverride: platform.Platform = platform.platform,
|
||||
): string | string[] {
|
||||
@@ -309,19 +331,19 @@ export function getDefaultShellArgs(
|
||||
}
|
||||
|
||||
const platformKey = platformOverride === platform.Platform.Windows ? 'windows' : platformOverride === platform.Platform.Mac ? 'osx' : 'linux';
|
||||
const shellArgsConfigValue = fetchSetting(`terminal.integrated.shellArgs.${platformKey}`);
|
||||
const shellArgsConfigValue = fetchSetting(<TerminalShellArgsSetting>`terminal.integrated.shellArgs.${platformKey}`);
|
||||
let args = ((isWorkspaceShellAllowed ? shellArgsConfigValue.value : shellArgsConfigValue.userValue) || shellArgsConfigValue.defaultValue);
|
||||
if (!args) {
|
||||
return [];
|
||||
}
|
||||
if (typeof args === 'string' && platformOverride === platform.Platform.Windows) {
|
||||
return configurationResolverService ? configurationResolverService.resolve(lastActiveWorkspace, args) : args;
|
||||
return variableResolver ? variableResolver(args) : args;
|
||||
}
|
||||
if (configurationResolverService) {
|
||||
if (variableResolver) {
|
||||
const resolvedArgs: string[] = [];
|
||||
for (const arg of args) {
|
||||
try {
|
||||
resolvedArgs.push(configurationResolverService.resolve(lastActiveWorkspace, arg));
|
||||
resolvedArgs.push(variableResolver(arg));
|
||||
} catch (e) {
|
||||
logService.error(`Could not resolve terminal.integrated.shellArgs.${platformKey}`, e);
|
||||
resolvedArgs.push(arg);
|
||||
@@ -333,22 +355,21 @@ export function getDefaultShellArgs(
|
||||
}
|
||||
|
||||
function getShellSetting(
|
||||
fetchSetting: (key: string) => { userValue?: string | string[], value?: string | string[], defaultValue?: string | string[] },
|
||||
fetchSetting: (key: TerminalShellSetting) => { userValue?: string | string[], value?: string | string[], defaultValue?: string | string[] },
|
||||
isWorkspaceShellAllowed: boolean,
|
||||
type: 'automationShell' | 'shell',
|
||||
platformOverride: platform.Platform = platform.platform,
|
||||
): string | null {
|
||||
const platformKey = platformOverride === platform.Platform.Windows ? 'windows' : platformOverride === platform.Platform.Mac ? 'osx' : 'linux';
|
||||
const shellConfigValue = fetchSetting(`terminal.integrated.${type}.${platformKey}`);
|
||||
const shellConfigValue = fetchSetting(<TerminalShellSetting>`terminal.integrated.${type}.${platformKey}`);
|
||||
const executable = (isWorkspaceShellAllowed ? <string>shellConfigValue.value : <string>shellConfigValue.userValue) || (<string | null>shellConfigValue.defaultValue);
|
||||
return executable;
|
||||
}
|
||||
|
||||
export function createTerminalEnvironment(
|
||||
shellLaunchConfig: IShellLaunchConfig,
|
||||
lastActiveWorkspace: IWorkspaceFolder | undefined,
|
||||
envFromConfig: { userValue?: ITerminalEnvironment, value?: ITerminalEnvironment, defaultValue?: ITerminalEnvironment },
|
||||
configurationResolverService: IConfigurationResolverService | undefined,
|
||||
variableResolver: VariableResolver | undefined,
|
||||
isWorkspaceShellAllowed: boolean,
|
||||
version: string | undefined,
|
||||
detectLocale: 'auto' | 'off' | 'on',
|
||||
@@ -368,12 +389,12 @@ export function createTerminalEnvironment(
|
||||
const allowedEnvFromConfig = { ...(isWorkspaceShellAllowed ? envFromConfig.value : envFromConfig.userValue) };
|
||||
|
||||
// Resolve env vars from config and shell
|
||||
if (configurationResolverService) {
|
||||
if (variableResolver) {
|
||||
if (allowedEnvFromConfig) {
|
||||
resolveConfigurationVariables(configurationResolverService, allowedEnvFromConfig, lastActiveWorkspace);
|
||||
resolveConfigurationVariables(variableResolver, allowedEnvFromConfig);
|
||||
}
|
||||
if (shellLaunchConfig.env) {
|
||||
resolveConfigurationVariables(configurationResolverService, shellLaunchConfig.env, lastActiveWorkspace);
|
||||
resolveConfigurationVariables(variableResolver, shellLaunchConfig.env);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import { TerminalNativeContribution } from 'vs/workbench/contrib/terminal/electr
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { getTerminalShellConfiguration } from 'vs/workbench/contrib/terminal/common/terminalConfiguration';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
|
||||
// This file contains additional desktop-only contributions on top of those in browser/
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import type { SearchAddon as XTermSearchAddon } from 'xterm-addon-search';
|
||||
import type { Unicode11Addon as XTermUnicode11Addon } from 'xterm-addon-unicode11';
|
||||
import type { WebglAddon as XTermWebglAddon } from 'xterm-addon-webgl';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { getDefaultShell, getDefaultShellArgs } from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
|
||||
import { createVariableResolver, getDefaultShell, getDefaultShellArgs } from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
|
||||
import { StorageScope, IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { getMainProcessParentEnv } from 'vs/workbench/contrib/terminal/node/terminalEnvironment';
|
||||
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
|
||||
@@ -75,7 +75,7 @@ export class TerminalInstanceService implements ITerminalInstanceService {
|
||||
}
|
||||
|
||||
public createTerminalProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean): ITerminalChildProcess {
|
||||
return this._instantiationService.createInstance(TerminalProcess, shellLaunchConfig, cwd, cols, rows, env, windowsEnableConpty);
|
||||
return this._instantiationService.createInstance(TerminalProcess, shellLaunchConfig, cwd, cols, rows, env, process.env as IProcessEnvironment, windowsEnableConpty);
|
||||
}
|
||||
|
||||
private _isWorkspaceShellAllowed(): boolean {
|
||||
@@ -93,8 +93,7 @@ export class TerminalInstanceService implements ITerminalInstanceService {
|
||||
getSystemShell(platformOverride),
|
||||
process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'),
|
||||
process.env.windir,
|
||||
lastActiveWorkspace,
|
||||
this._configurationResolverService,
|
||||
createVariableResolver(lastActiveWorkspace, this._configurationResolverService),
|
||||
this._logService,
|
||||
useAutomationShell,
|
||||
platformOverride
|
||||
@@ -103,8 +102,7 @@ export class TerminalInstanceService implements ITerminalInstanceService {
|
||||
(key) => this._configurationService.inspect(key),
|
||||
isWorkspaceShellAllowed,
|
||||
useAutomationShell,
|
||||
lastActiveWorkspace,
|
||||
this._configurationResolverService,
|
||||
createVariableResolver(lastActiveWorkspace, this._configurationResolverService),
|
||||
this._logService,
|
||||
platformOverride
|
||||
);
|
||||
|
||||
@@ -13,7 +13,7 @@ import { execFile } from 'child_process';
|
||||
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 { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
@@ -26,12 +26,12 @@ export class TerminalNativeContribution extends Disposable implements IWorkbench
|
||||
@ITerminalService private readonly _terminalService: ITerminalService,
|
||||
@IInstantiationService readonly instantiationService: IInstantiationService,
|
||||
@IRemoteAgentService readonly remoteAgentService: IRemoteAgentService,
|
||||
@IElectronService readonly electronService: IElectronService
|
||||
@INativeHostService readonly nativeHostService: INativeHostService
|
||||
) {
|
||||
super();
|
||||
|
||||
ipcRenderer.on('vscode:openFiles', (_: unknown, request: INativeOpenFileRequest) => this._onOpenFileRequest(request));
|
||||
this._register(electronService.onOSResume(() => this._onOsResume()));
|
||||
this._register(nativeHostService.onDidResumeOS(() => this._onOsResume()));
|
||||
|
||||
this._terminalService.setLinuxDistro(linuxDistro);
|
||||
this._terminalService.setNativeWindowsDelegate({
|
||||
|
||||
@@ -16,27 +16,27 @@ import { normalize, basename } from 'vs/base/common/path';
|
||||
* shell that the terminal uses by default.
|
||||
* @param p The platform to detect the shell of.
|
||||
*/
|
||||
export function getSystemShell(p: platform.Platform): string {
|
||||
export function getSystemShell(p: platform.Platform, environment: platform.IProcessEnvironment = process.env as platform.IProcessEnvironment): string {
|
||||
if (p === platform.Platform.Windows) {
|
||||
if (platform.isWindows) {
|
||||
return getSystemShellWindows();
|
||||
return getSystemShellWindows(environment);
|
||||
}
|
||||
// Don't detect Windows shell when not on Windows
|
||||
return processes.getWindowsShell();
|
||||
return processes.getWindowsShell(environment);
|
||||
}
|
||||
// Only use $SHELL for the current OS
|
||||
if (platform.isLinux && p === platform.Platform.Mac || platform.isMacintosh && p === platform.Platform.Linux) {
|
||||
return '/bin/bash';
|
||||
}
|
||||
return getSystemShellUnixLike();
|
||||
return getSystemShellUnixLike(environment);
|
||||
}
|
||||
|
||||
let _TERMINAL_DEFAULT_SHELL_UNIX_LIKE: string | null = null;
|
||||
function getSystemShellUnixLike(): string {
|
||||
function getSystemShellUnixLike(environment: platform.IProcessEnvironment): string {
|
||||
if (!_TERMINAL_DEFAULT_SHELL_UNIX_LIKE) {
|
||||
let unixLikeTerminal = 'sh';
|
||||
if (!platform.isWindows && process.env.SHELL) {
|
||||
unixLikeTerminal = process.env.SHELL;
|
||||
if (!platform.isWindows && environment.SHELL) {
|
||||
unixLikeTerminal = environment.SHELL;
|
||||
// Some systems have $SHELL set to /bin/false which breaks the terminal
|
||||
if (unixLikeTerminal === '/bin/false') {
|
||||
unixLikeTerminal = '/bin/bash';
|
||||
@@ -51,12 +51,12 @@ function getSystemShellUnixLike(): string {
|
||||
}
|
||||
|
||||
let _TERMINAL_DEFAULT_SHELL_WINDOWS: string | null = null;
|
||||
function getSystemShellWindows(): string {
|
||||
function getSystemShellWindows(environment: platform.IProcessEnvironment): string {
|
||||
if (!_TERMINAL_DEFAULT_SHELL_WINDOWS) {
|
||||
const isAtLeastWindows10 = platform.isWindows && parseFloat(os.release()) >= 10;
|
||||
const is32ProcessOn64Windows = process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432');
|
||||
const powerShellPath = `${process.env.windir}\\${is32ProcessOn64Windows ? 'Sysnative' : 'System32'}\\WindowsPowerShell\\v1.0\\powershell.exe`;
|
||||
_TERMINAL_DEFAULT_SHELL_WINDOWS = isAtLeastWindows10 ? powerShellPath : processes.getWindowsShell();
|
||||
const is32ProcessOn64Windows = environment.hasOwnProperty('PROCESSOR_ARCHITEW6432');
|
||||
const powerShellPath = `${environment.windir}\\${is32ProcessOn64Windows ? 'Sysnative' : 'System32'}\\WindowsPowerShell\\v1.0\\powershell.exe`;
|
||||
_TERMINAL_DEFAULT_SHELL_WINDOWS = isAtLeastWindows10 ? powerShellPath : processes.getWindowsShell(environment);
|
||||
}
|
||||
return _TERMINAL_DEFAULT_SHELL_WINDOWS;
|
||||
}
|
||||
@@ -152,7 +152,7 @@ async function validateShellPaths(label: string, potentialPaths: string[]): Prom
|
||||
}
|
||||
try {
|
||||
const result = await stat(normalize(current));
|
||||
if (result.isFile || result.isSymbolicLink) {
|
||||
if (result.isFile() || result.isSymbolicLink()) {
|
||||
return {
|
||||
label,
|
||||
path: current
|
||||
|
||||
@@ -7,10 +7,11 @@ import { IProcessEnvironment, isLinux, isMacintosh, isWindows } from 'vs/base/co
|
||||
import { readFile, exists } from 'vs/base/node/pfs';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import { isString } from 'vs/base/common/types';
|
||||
import { getCaseInsensitive } from 'vs/base/common/objects';
|
||||
|
||||
let mainProcessParentEnv: IProcessEnvironment | undefined;
|
||||
|
||||
export async function getMainProcessParentEnv(): Promise<IProcessEnvironment> {
|
||||
export async function getMainProcessParentEnv(baseEnvironment: IProcessEnvironment = process.env as IProcessEnvironment): Promise<IProcessEnvironment> {
|
||||
if (mainProcessParentEnv) {
|
||||
return mainProcessParentEnv;
|
||||
}
|
||||
@@ -65,21 +66,21 @@ export async function getMainProcessParentEnv(): Promise<IProcessEnvironment> {
|
||||
'TMPDIR'
|
||||
];
|
||||
rootEnvVars.forEach(k => {
|
||||
if (process.env[k]) {
|
||||
mainProcessParentEnv![k] = process.env[k]!;
|
||||
if (baseEnvironment[k]) {
|
||||
mainProcessParentEnv![k] = baseEnvironment[k]!;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Windows should return a fresh environment block, might need native code?
|
||||
if (isWindows) {
|
||||
mainProcessParentEnv = process.env as IProcessEnvironment;
|
||||
mainProcessParentEnv = baseEnvironment;
|
||||
}
|
||||
|
||||
return mainProcessParentEnv!;
|
||||
}
|
||||
|
||||
export async function findExecutable(command: string, cwd?: string, paths?: string[]): Promise<string | undefined> {
|
||||
export async function findExecutable(command: string, cwd?: string, paths?: string[], env: IProcessEnvironment = process.env as IProcessEnvironment): Promise<string | undefined> {
|
||||
// If we have an absolute path then we take it.
|
||||
if (path.isAbsolute(command)) {
|
||||
return await exists(command) ? command : undefined;
|
||||
@@ -94,8 +95,9 @@ export async function findExecutable(command: string, cwd?: string, paths?: stri
|
||||
const fullPath = path.join(cwd, command);
|
||||
return await exists(fullPath) ? fullPath : undefined;
|
||||
}
|
||||
if (paths === undefined && isString(process.env.PATH)) {
|
||||
paths = process.env.PATH.split(path.delimiter);
|
||||
const envPath = getCaseInsensitive(env, 'PATH');
|
||||
if (paths === undefined && isString(envPath)) {
|
||||
paths = envPath.split(path.delimiter);
|
||||
}
|
||||
// No PATH environment. Make path absolute to the cwd.
|
||||
if (paths === undefined || paths.length === 0) {
|
||||
|
||||
@@ -57,6 +57,10 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
cols: number,
|
||||
rows: number,
|
||||
env: platform.IProcessEnvironment,
|
||||
/**
|
||||
* environment used for `findExecutable`
|
||||
*/
|
||||
private readonly _executableEnv: platform.IProcessEnvironment,
|
||||
windowsEnableConpty: boolean,
|
||||
@ILogService private readonly _logService: ILogService
|
||||
) {
|
||||
@@ -139,7 +143,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess
|
||||
// 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);
|
||||
const executable = await findExecutable(slc.executable!, cwd, envPaths, this._executableEnv);
|
||||
if (!executable) {
|
||||
return { message: localize('launchFail.executableDoesNotExist', "Path to shell executable \"{0}\" does not exist", slc.executable) };
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/term
|
||||
import { EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions';
|
||||
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
||||
import { LinuxDistro } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
|
||||
suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} skip suite
|
||||
let fixture: HTMLElement;
|
||||
@@ -30,7 +29,7 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
const configurationService = new TestConfigurationService();
|
||||
configurationService.setUserConfiguration('editor', { fontFamily: 'foo' });
|
||||
configurationService.setUserConfiguration('terminal', { integrated: { fontFamily: null } });
|
||||
const configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
const configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!);
|
||||
configHelper.setLinuxDistro(LinuxDistro.Fedora);
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.getFont().fontFamily, '\'DejaVu Sans Mono\', monospace', 'Fedora should have its font overridden when terminal.integrated.fontFamily not set');
|
||||
@@ -40,7 +39,7 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
const configurationService = new TestConfigurationService();
|
||||
configurationService.setUserConfiguration('editor', { fontFamily: 'foo' });
|
||||
configurationService.setUserConfiguration('terminal', { integrated: { fontFamily: null } });
|
||||
const configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
const configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!);
|
||||
configHelper.setLinuxDistro(LinuxDistro.Ubuntu);
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.getFont().fontFamily, '\'Ubuntu Mono\', monospace', 'Ubuntu should have its font overridden when terminal.integrated.fontFamily not set');
|
||||
@@ -50,7 +49,7 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
const configurationService = new TestConfigurationService();
|
||||
configurationService.setUserConfiguration('editor', { fontFamily: 'foo' });
|
||||
configurationService.setUserConfiguration('terminal', { integrated: { fontFamily: null } });
|
||||
const configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
const configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!);
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.getFont().fontFamily, 'foo', 'editor.fontFamily should be the fallback when terminal.integrated.fontFamily not set');
|
||||
});
|
||||
@@ -68,7 +67,7 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
fontSize: 10
|
||||
}
|
||||
});
|
||||
let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!);
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.getFont().fontSize, 10, 'terminal.integrated.fontSize should be selected over editor.fontSize');
|
||||
|
||||
@@ -81,12 +80,12 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
fontSize: 0
|
||||
}
|
||||
});
|
||||
configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!);
|
||||
configHelper.setLinuxDistro(LinuxDistro.Ubuntu);
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.getFont().fontSize, 8, 'The minimum terminal font size (with adjustment) should be used when terminal.integrated.fontSize less than it');
|
||||
|
||||
configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!);
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.getFont().fontSize, 6, 'The minimum terminal font size should be used when terminal.integrated.fontSize less than it');
|
||||
|
||||
@@ -99,7 +98,7 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
fontSize: 1500
|
||||
}
|
||||
});
|
||||
configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!);
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.getFont().fontSize, 25, 'The maximum terminal font size should be used when terminal.integrated.fontSize more than it');
|
||||
|
||||
@@ -112,12 +111,12 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
fontSize: null
|
||||
}
|
||||
});
|
||||
configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!);
|
||||
configHelper.setLinuxDistro(LinuxDistro.Ubuntu);
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.getFont().fontSize, EDITOR_FONT_DEFAULTS.fontSize + 2, 'The default editor font size (with adjustment) should be used when terminal.integrated.fontSize is not set');
|
||||
|
||||
configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!);
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.getFont().fontSize, EDITOR_FONT_DEFAULTS.fontSize, 'The default editor font size should be used when terminal.integrated.fontSize is not set');
|
||||
});
|
||||
@@ -135,7 +134,7 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
lineHeight: 2
|
||||
}
|
||||
});
|
||||
let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!);
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.getFont().lineHeight, 2, 'terminal.integrated.lineHeight should be selected over editor.lineHeight');
|
||||
|
||||
@@ -149,7 +148,7 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
lineHeight: 0
|
||||
}
|
||||
});
|
||||
configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!);
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.getFont().lineHeight, 1, 'editor.lineHeight should be 1 when terminal.integrated.lineHeight not set');
|
||||
});
|
||||
@@ -162,7 +161,7 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
}
|
||||
});
|
||||
|
||||
let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!);
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.configFontIsMonospace(), true, 'monospace is monospaced');
|
||||
});
|
||||
@@ -174,7 +173,7 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
fontFamily: 'sans-serif'
|
||||
}
|
||||
});
|
||||
let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!);
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.configFontIsMonospace(), false, 'sans-serif is not monospaced');
|
||||
});
|
||||
@@ -186,7 +185,7 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
fontFamily: 'serif'
|
||||
}
|
||||
});
|
||||
let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!);
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.configFontIsMonospace(), false, 'serif is not monospaced');
|
||||
});
|
||||
@@ -202,7 +201,7 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
}
|
||||
});
|
||||
|
||||
let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!);
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.configFontIsMonospace(), true, 'monospace is monospaced');
|
||||
});
|
||||
@@ -218,7 +217,7 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
}
|
||||
});
|
||||
|
||||
let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!);
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.configFontIsMonospace(), false, 'sans-serif is not monospaced');
|
||||
});
|
||||
@@ -234,7 +233,7 @@ suite.skip('Workbench - TerminalConfigHelper', () => { // {{SQL CARBON EDIT}} sk
|
||||
}
|
||||
});
|
||||
|
||||
let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!, new StorageKeysSyncRegistryService());
|
||||
let configHelper = new TerminalConfigHelper(configurationService, null!, null!, null!, null!, null!, null!);
|
||||
configHelper.panelContainer = fixture;
|
||||
assert.equal(configHelper.configFontIsMonospace(), false, 'serif is not monospaced');
|
||||
});
|
||||
|
||||
@@ -0,0 +1,415 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { Terminal } from 'xterm';
|
||||
import { SinonStub, stub, useFakeTimers } from 'sinon';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { CharPredictState, IPrediction, PredictionStats, TypeAheadAddon } from 'vs/workbench/contrib/terminal/browser/terminalTypeAheadAddon';
|
||||
import { DEFAULT_LOCAL_ECHO_EXCLUDE, IBeforeProcessDataEvent, ITerminalConfiguration, ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
const CSI = `\x1b[`;
|
||||
|
||||
suite('Workbench - Terminal Typeahead', () => {
|
||||
suite('PredictionStats', () => {
|
||||
let stats: PredictionStats;
|
||||
const add = new Emitter<IPrediction>();
|
||||
const succeed = new Emitter<IPrediction>();
|
||||
const fail = new Emitter<IPrediction>();
|
||||
|
||||
setup(() => {
|
||||
stats = new PredictionStats({
|
||||
onPredictionAdded: add.event,
|
||||
onPredictionSucceeded: succeed.event,
|
||||
onPredictionFailed: fail.event,
|
||||
} as any);
|
||||
});
|
||||
|
||||
test('creates sane data', () => {
|
||||
const stubs = createPredictionStubs(5);
|
||||
const clock = useFakeTimers();
|
||||
try {
|
||||
for (const s of stubs) { add.fire(s); }
|
||||
|
||||
for (let i = 0; i < stubs.length; i++) {
|
||||
clock.tick(100);
|
||||
(i % 2 ? fail : succeed).fire(stubs[i]);
|
||||
}
|
||||
|
||||
assert.strictEqual(stats.accuracy, 3 / 5);
|
||||
assert.strictEqual(stats.sampleSize, 5);
|
||||
assert.deepStrictEqual(stats.latency, {
|
||||
count: 3,
|
||||
min: 100,
|
||||
max: 500,
|
||||
median: 300
|
||||
});
|
||||
} finally {
|
||||
clock.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('circular buffer', () => {
|
||||
const bufferSize = 24;
|
||||
const stubs = createPredictionStubs(bufferSize * 2);
|
||||
|
||||
for (const s of stubs.slice(0, bufferSize)) { add.fire(s); succeed.fire(s); }
|
||||
assert.strictEqual(stats.accuracy, 1);
|
||||
|
||||
for (const s of stubs.slice(bufferSize, bufferSize * 3 / 2)) { add.fire(s); fail.fire(s); }
|
||||
assert.strictEqual(stats.accuracy, 0.5);
|
||||
|
||||
for (const s of stubs.slice(bufferSize * 3 / 2)) { add.fire(s); fail.fire(s); }
|
||||
assert.strictEqual(stats.accuracy, 0);
|
||||
});
|
||||
});
|
||||
|
||||
suite('timeline', () => {
|
||||
const onBeforeProcessData = new Emitter<IBeforeProcessDataEvent>();
|
||||
const onConfigChanged = new Emitter<void>();
|
||||
let publicLog: SinonStub;
|
||||
let config: ITerminalConfiguration;
|
||||
let addon: TestTypeAheadAddon;
|
||||
|
||||
const predictedHelloo = [
|
||||
`${CSI}?25l`, // hide cursor
|
||||
`${CSI}2;7H`, // move cursor
|
||||
'o', // new character
|
||||
`${CSI}2;8H`, // place cursor back at end of line
|
||||
`${CSI}?25h`, // show cursor
|
||||
].join('');
|
||||
|
||||
const expectProcessed = (input: string, output: string) => {
|
||||
const evt = { data: input };
|
||||
onBeforeProcessData.fire(evt);
|
||||
assert.strictEqual(JSON.stringify(evt.data), JSON.stringify(output));
|
||||
};
|
||||
|
||||
setup(() => {
|
||||
config = upcastPartial<ITerminalConfiguration>({
|
||||
localEchoStyle: 'italic',
|
||||
localEchoLatencyThreshold: 0,
|
||||
localEchoExcludePrograms: DEFAULT_LOCAL_ECHO_EXCLUDE,
|
||||
});
|
||||
publicLog = stub();
|
||||
addon = new TestTypeAheadAddon(
|
||||
upcastPartial<ITerminalProcessManager>({ onBeforeProcessData: onBeforeProcessData.event }),
|
||||
upcastPartial<TerminalConfigHelper>({ config, onConfigChanged: onConfigChanged.event }),
|
||||
upcastPartial<ITelemetryService>({ publicLog })
|
||||
);
|
||||
addon.unlockMakingPredictions();
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
addon.dispose();
|
||||
});
|
||||
|
||||
test('predicts a single character', () => {
|
||||
const t = createMockTerminal({ lines: ['hello|'] });
|
||||
addon.activate(t.terminal);
|
||||
t.onData('o');
|
||||
t.expectWritten(`${CSI}3mo${CSI}23m`);
|
||||
});
|
||||
|
||||
test('validates character prediction', () => {
|
||||
const t = createMockTerminal({ lines: ['hello|'] });
|
||||
addon.activate(t.terminal);
|
||||
t.onData('o');
|
||||
expectProcessed('o', predictedHelloo);
|
||||
assert.strictEqual(addon.stats?.accuracy, 1);
|
||||
});
|
||||
|
||||
test('rolls back character prediction', () => {
|
||||
const t = createMockTerminal({ lines: ['hello|'] });
|
||||
addon.activate(t.terminal);
|
||||
t.onData('o');
|
||||
|
||||
expectProcessed('q', [
|
||||
`${CSI}?25l`, // hide cursor
|
||||
`${CSI}2;7H`, // move cursor cursor
|
||||
`${CSI}X`, // delete character
|
||||
`${CSI}0m`, // reset style
|
||||
'q', // new character
|
||||
`${CSI}?25h`, // show cursor
|
||||
].join(''));
|
||||
assert.strictEqual(addon.stats?.accuracy, 0);
|
||||
});
|
||||
|
||||
test('restores cursor graphics mode', () => {
|
||||
const t = createMockTerminal({
|
||||
lines: ['hello|'],
|
||||
cursorAttrs: { isAttributeDefault: false, isBold: true, isFgPalette: true, getFgColor: 1 },
|
||||
});
|
||||
addon.activate(t.terminal);
|
||||
t.onData('o');
|
||||
|
||||
expectProcessed('q', [
|
||||
`${CSI}?25l`, // hide cursor
|
||||
`${CSI}2;7H`, // move cursor cursor
|
||||
`${CSI}X`, // delete character
|
||||
`${CSI}1;38;5;1m`, // reset style
|
||||
'q', // new character
|
||||
`${CSI}?25h`, // show cursor
|
||||
].join(''));
|
||||
assert.strictEqual(addon.stats?.accuracy, 0);
|
||||
});
|
||||
|
||||
test('validates against and applies graphics mode on predicted', () => {
|
||||
const t = createMockTerminal({ lines: ['hello|'] });
|
||||
addon.activate(t.terminal);
|
||||
t.onData('o');
|
||||
expectProcessed(`${CSI}4mo`, [
|
||||
`${CSI}?25l`, // hide cursor
|
||||
`${CSI}2;7H`, // move cursor
|
||||
`${CSI}4m`, // new PTY's style
|
||||
'o', // new character
|
||||
`${CSI}2;8H`, // place cursor back at end of line
|
||||
`${CSI}?25h`, // show cursor
|
||||
].join(''));
|
||||
assert.strictEqual(addon.stats?.accuracy, 1);
|
||||
});
|
||||
|
||||
test('ignores cursor hides or shows', () => {
|
||||
const t = createMockTerminal({ lines: ['hello|'] });
|
||||
addon.activate(t.terminal);
|
||||
t.onData('o');
|
||||
expectProcessed(`${CSI}?25lo${CSI}?25h`, [
|
||||
`${CSI}?25l`, // hide cursor from PTY
|
||||
`${CSI}?25l`, // hide cursor
|
||||
`${CSI}2;7H`, // move cursor
|
||||
'o', // new character
|
||||
`${CSI}?25h`, // show cursor from PTY
|
||||
`${CSI}2;8H`, // place cursor back at end of line
|
||||
`${CSI}?25h`, // show cursor
|
||||
].join(''));
|
||||
assert.strictEqual(addon.stats?.accuracy, 1);
|
||||
});
|
||||
|
||||
test('matches backspace at EOL (bash style)', () => {
|
||||
const t = createMockTerminal({ lines: ['hello|'] });
|
||||
addon.activate(t.terminal);
|
||||
t.onData('\x7F');
|
||||
expectProcessed(`\b${CSI}K`, `\b${CSI}K`);
|
||||
assert.strictEqual(addon.stats?.accuracy, 1);
|
||||
});
|
||||
|
||||
test('matches backspace at EOL (zsh style)', () => {
|
||||
const t = createMockTerminal({ lines: ['hello|'] });
|
||||
addon.activate(t.terminal);
|
||||
t.onData('\x7F');
|
||||
expectProcessed('\b \b', '\b \b');
|
||||
assert.strictEqual(addon.stats?.accuracy, 1);
|
||||
});
|
||||
|
||||
test('gradually matches backspace', () => {
|
||||
const t = createMockTerminal({ lines: ['hello|'] });
|
||||
addon.activate(t.terminal);
|
||||
t.onData('\x7F');
|
||||
expectProcessed('\b', '');
|
||||
expectProcessed(' \b', '\b \b');
|
||||
assert.strictEqual(addon.stats?.accuracy, 1);
|
||||
});
|
||||
|
||||
test('restores old character after invalid backspace', () => {
|
||||
const t = createMockTerminal({ lines: ['hel|lo'] });
|
||||
addon.activate(t.terminal);
|
||||
addon.unlockLeftNavigating();
|
||||
t.onData('\x7F');
|
||||
t.expectWritten(`${CSI}2;4H${CSI}X`);
|
||||
expectProcessed('x', `${CSI}?25l${CSI}0ml${CSI}2;5H${CSI}0mx${CSI}?25h`);
|
||||
assert.strictEqual(addon.stats?.accuracy, 0);
|
||||
});
|
||||
|
||||
test('waits for validation before deleting to left of cursor', () => {
|
||||
const t = createMockTerminal({ lines: ['hello|'] });
|
||||
addon.activate(t.terminal);
|
||||
|
||||
// initially should not backspace (until the server confirms it)
|
||||
t.onData('\x7F');
|
||||
t.expectWritten('');
|
||||
expectProcessed('\b \b', '\b \b');
|
||||
t.cursor.x--;
|
||||
|
||||
// enter input on the column...
|
||||
t.onData('o');
|
||||
onBeforeProcessData.fire({ data: 'o' });
|
||||
t.cursor.x++;
|
||||
t.clearWritten();
|
||||
|
||||
// now that the column is 'unlocked', we should be able to predict backspace on it
|
||||
t.onData('\x7F');
|
||||
t.expectWritten(`${CSI}2;6H${CSI}X`);
|
||||
});
|
||||
|
||||
test('waits for first valid prediction on a line', () => {
|
||||
const t = createMockTerminal({ lines: ['hello|'] });
|
||||
addon.lockMakingPredictions();
|
||||
addon.activate(t.terminal);
|
||||
|
||||
t.onData('o');
|
||||
t.expectWritten('');
|
||||
expectProcessed('o', 'o');
|
||||
|
||||
t.onData('o');
|
||||
t.expectWritten(`${CSI}3mo${CSI}23m`);
|
||||
});
|
||||
|
||||
test('disables on title change', () => {
|
||||
const t = createMockTerminal({ lines: ['hello|'] });
|
||||
addon.activate(t.terminal);
|
||||
|
||||
addon.reevaluateNow();
|
||||
assert.strictEqual(addon.isShowing, true, 'expected to show initially');
|
||||
|
||||
t.onTitleChange.fire('foo - VIM.exe');
|
||||
addon.reevaluateNow();
|
||||
assert.strictEqual(addon.isShowing, false, 'expected to hide when vim is open');
|
||||
|
||||
t.onTitleChange.fire('foo - git.exe');
|
||||
addon.reevaluateNow();
|
||||
assert.strictEqual(addon.isShowing, true, 'expected to show again after vim closed');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class TestTypeAheadAddon extends TypeAheadAddon {
|
||||
public unlockMakingPredictions() {
|
||||
this.lastRow = { y: 1, startingX: 100, charState: CharPredictState.Validated };
|
||||
}
|
||||
|
||||
public lockMakingPredictions() {
|
||||
this.lastRow = undefined;
|
||||
}
|
||||
|
||||
public unlockLeftNavigating() {
|
||||
this.lastRow = { y: 1, startingX: 1, charState: CharPredictState.Validated };
|
||||
}
|
||||
|
||||
public reevaluateNow() {
|
||||
this.reevaluatePredictorStateNow(this.stats!, this.timeline!);
|
||||
}
|
||||
|
||||
public get isShowing() {
|
||||
return !!this.timeline?.isShowingPredictions;
|
||||
}
|
||||
}
|
||||
|
||||
function upcastPartial<T>(v: Partial<T>): T {
|
||||
return v as T;
|
||||
}
|
||||
|
||||
function createPredictionStubs(n: number) {
|
||||
return new Array(n).fill(0).map(stubPrediction);
|
||||
}
|
||||
|
||||
function stubPrediction(): IPrediction {
|
||||
return {
|
||||
apply: () => '',
|
||||
rollback: () => '',
|
||||
matches: () => 0,
|
||||
rollForwards: () => '',
|
||||
};
|
||||
}
|
||||
|
||||
function createMockTerminal({ lines, cursorAttrs }: {
|
||||
lines: string[],
|
||||
cursorAttrs?: any,
|
||||
}) {
|
||||
const written: string[] = [];
|
||||
const cursor = { y: 1, x: 1 };
|
||||
const onTitleChange = new Emitter<string>();
|
||||
const onData = new Emitter<string>();
|
||||
const csiEmitter = new Emitter<number[]>();
|
||||
|
||||
for (let y = 0; y < lines.length; y++) {
|
||||
const line = lines[y];
|
||||
if (line.includes('|')) {
|
||||
cursor.y = y + 1;
|
||||
cursor.x = line.indexOf('|') + 1;
|
||||
lines[y] = line.replace('|', '');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
written,
|
||||
cursor,
|
||||
expectWritten: (s: string) => {
|
||||
assert.strictEqual(JSON.stringify(written.join('')), JSON.stringify(s));
|
||||
written.splice(0, written.length);
|
||||
},
|
||||
clearWritten: () => written.splice(0, written.length),
|
||||
onData: (s: string) => onData.fire(s),
|
||||
csiEmitter,
|
||||
onTitleChange,
|
||||
terminal: {
|
||||
cols: 80,
|
||||
rows: 5,
|
||||
onResize: new Emitter<void>().event,
|
||||
onData: onData.event,
|
||||
onTitleChange: onTitleChange.event,
|
||||
parser: {
|
||||
registerCsiHandler(_: unknown, callback: () => void) {
|
||||
csiEmitter.event(callback);
|
||||
},
|
||||
},
|
||||
write(line: string) {
|
||||
written.push(line);
|
||||
},
|
||||
_core: {
|
||||
_inputHandler: {
|
||||
_curAttrData: mockCell('', cursorAttrs)
|
||||
},
|
||||
writeSync() {
|
||||
|
||||
}
|
||||
},
|
||||
buffer: {
|
||||
active: {
|
||||
type: 'normal',
|
||||
baseY: 0,
|
||||
get cursorY() { return cursor.y; },
|
||||
get cursorX() { return cursor.x; },
|
||||
getLine(y: number) {
|
||||
const s = lines[y - 1] || '';
|
||||
return {
|
||||
length: s.length,
|
||||
getCell: (x: number) => mockCell(s[x - 1] || ''),
|
||||
translateToString: (trim: boolean, start = 0, end = s.length) => {
|
||||
const out = s.slice(start, end);
|
||||
return trim ? out.trimRight() : out;
|
||||
},
|
||||
};
|
||||
},
|
||||
}
|
||||
}
|
||||
} as unknown as Terminal
|
||||
};
|
||||
}
|
||||
|
||||
function mockCell(char: string, attrs: { [key: string]: unknown } = {}) {
|
||||
return new Proxy({}, {
|
||||
get(_, prop) {
|
||||
if (typeof prop === 'string' && attrs.hasOwnProperty(prop)) {
|
||||
return () => attrs[prop];
|
||||
}
|
||||
|
||||
switch (prop) {
|
||||
case 'getWidth':
|
||||
return () => 1;
|
||||
case 'getChars':
|
||||
return () => char;
|
||||
case 'getCode':
|
||||
return () => char.charCodeAt(0) || 0;
|
||||
case 'isAttributeDefault':
|
||||
return () => true;
|
||||
default:
|
||||
return String(prop).startsWith('is') ? (() => false) : (() => 0);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -187,31 +187,31 @@ suite('Workbench - TerminalEnvironment', () => {
|
||||
}
|
||||
|
||||
test('should default to userHome for an empty workspace', () => {
|
||||
assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, undefined, undefined), '/userHome/');
|
||||
assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, undefined), '/userHome/');
|
||||
});
|
||||
|
||||
test('should use to the workspace if it exists', () => {
|
||||
assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, Uri.file('/foo'), undefined), '/foo');
|
||||
assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, Uri.file('/foo'), undefined), '/foo');
|
||||
});
|
||||
|
||||
test('should use an absolute custom cwd as is', () => {
|
||||
assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, undefined, '/foo'), '/foo');
|
||||
assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, '/foo'), '/foo');
|
||||
});
|
||||
|
||||
test('should normalize a relative custom cwd against the workspace path', () => {
|
||||
assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, Uri.file('/bar'), 'foo'), '/bar/foo');
|
||||
assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, Uri.file('/bar'), './foo'), '/bar/foo');
|
||||
assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, Uri.file('/bar'), '../foo'), '/foo');
|
||||
assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, Uri.file('/bar'), 'foo'), '/bar/foo');
|
||||
assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, Uri.file('/bar'), './foo'), '/bar/foo');
|
||||
assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, Uri.file('/bar'), '../foo'), '/foo');
|
||||
});
|
||||
|
||||
test('should fall back for relative a custom cwd that doesn\'t have a workspace', () => {
|
||||
assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, undefined, 'foo'), '/userHome/');
|
||||
assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, undefined, './foo'), '/userHome/');
|
||||
assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, undefined, '../foo'), '/userHome/');
|
||||
assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, 'foo'), '/userHome/');
|
||||
assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, './foo'), '/userHome/');
|
||||
assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, '../foo'), '/userHome/');
|
||||
});
|
||||
|
||||
test('should ignore custom cwd when told to ignore', () => {
|
||||
assertPathsMatch(getCwd({ executable: undefined, args: [], ignoreConfigurationCwd: true }, '/userHome/', undefined, undefined, Uri.file('/bar'), '/foo'), '/bar');
|
||||
assertPathsMatch(getCwd({ executable: undefined, args: [], ignoreConfigurationCwd: true }, '/userHome/', undefined, Uri.file('/bar'), '/foo'), '/bar');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -221,7 +221,7 @@ suite('Workbench - TerminalEnvironment', () => {
|
||||
return ({
|
||||
'terminal.integrated.shell.windows': { userValue: 'C:\\Windows\\Sysnative\\cmd.exe', value: undefined, defaultValue: undefined }
|
||||
} as any)[key];
|
||||
}, false, 'DEFAULT', false, 'C:\\Windows', undefined, undefined, {} as any, false, platform.Platform.Windows);
|
||||
}, false, 'DEFAULT', false, 'C:\\Windows', undefined, {} as any, false, platform.Platform.Windows);
|
||||
assert.equal(shell, 'C:\\Windows\\System32\\cmd.exe');
|
||||
});
|
||||
|
||||
@@ -230,7 +230,7 @@ suite('Workbench - TerminalEnvironment', () => {
|
||||
return ({
|
||||
'terminal.integrated.shell.windows': { userValue: 'C:\\Windows\\Sysnative\\cmd.exe', value: undefined, defaultValue: undefined }
|
||||
} as any)[key];
|
||||
}, false, 'DEFAULT', true, 'C:\\Windows', undefined, undefined, {} as any, false, platform.Platform.Windows);
|
||||
}, false, 'DEFAULT', true, 'C:\\Windows', undefined, {} as any, false, platform.Platform.Windows);
|
||||
assert.equal(shell, 'C:\\Windows\\Sysnative\\cmd.exe');
|
||||
});
|
||||
|
||||
@@ -240,21 +240,21 @@ suite('Workbench - TerminalEnvironment', () => {
|
||||
'terminal.integrated.shell.windows': { userValue: 'shell', value: undefined, defaultValue: undefined },
|
||||
'terminal.integrated.automationShell.windows': { userValue: undefined, value: undefined, defaultValue: undefined }
|
||||
} as any)[key];
|
||||
}, false, 'DEFAULT', false, 'C:\\Windows', undefined, undefined, {} as any, false, platform.Platform.Windows);
|
||||
}, false, 'DEFAULT', false, 'C:\\Windows', undefined, {} as any, false, platform.Platform.Windows);
|
||||
assert.equal(shell1, 'shell', 'automationShell was false');
|
||||
const shell2 = getDefaultShell(key => {
|
||||
return ({
|
||||
'terminal.integrated.shell.windows': { userValue: 'shell', value: undefined, defaultValue: undefined },
|
||||
'terminal.integrated.automationShell.windows': { userValue: undefined, value: undefined, defaultValue: undefined }
|
||||
} as any)[key];
|
||||
}, false, 'DEFAULT', false, 'C:\\Windows', undefined, undefined, {} as any, true, platform.Platform.Windows);
|
||||
}, false, 'DEFAULT', false, 'C:\\Windows', undefined, {} as any, true, platform.Platform.Windows);
|
||||
assert.equal(shell2, 'shell', 'automationShell was true');
|
||||
const shell3 = getDefaultShell(key => {
|
||||
return ({
|
||||
'terminal.integrated.shell.windows': { userValue: 'shell', value: undefined, defaultValue: undefined },
|
||||
'terminal.integrated.automationShell.windows': { userValue: 'automationShell', value: undefined, defaultValue: undefined }
|
||||
} as any)[key];
|
||||
}, false, 'DEFAULT', false, 'C:\\Windows', undefined, undefined, {} as any, true, platform.Platform.Windows);
|
||||
}, false, 'DEFAULT', false, 'C:\\Windows', undefined, {} as any, true, platform.Platform.Windows);
|
||||
assert.equal(shell3, 'automationShell', 'automationShell was true and specified in settings');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user