Merge from vscode 64980ea1f3f532c82bb6c28d27bba9ef2c5b4463 (#7206)

* Merge from vscode 64980ea1f3f532c82bb6c28d27bba9ef2c5b4463

* fix config changes

* fix strictnull checks
This commit is contained in:
Anthony Dresser
2019-09-15 22:38:26 -07:00
committed by GitHub
parent fa6c52699e
commit ea0f9e6ce9
1226 changed files with 21541 additions and 17633 deletions

View File

@@ -72,8 +72,6 @@
}
.monaco-workbench .panel.integrated-terminal .split-view-view {
/* Make relative as terminal absolute positioning needs it deeper in the tree */
position: relative;
box-sizing: border-box;
}

View File

@@ -23,9 +23,9 @@ import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/wor
import { ClearSelectionTerminalAction, ClearTerminalAction, CopyTerminalSelectionAction, CreateNewInActiveWorkspaceTerminalAction, CreateNewTerminalAction, DeleteToLineStartTerminalAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, FindNext, FindPrevious, FocusActiveTerminalAction, FocusNextPaneTerminalAction, FocusNextTerminalAction, FocusPreviousPaneTerminalAction, FocusPreviousTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, KillTerminalAction, MoveToLineEndTerminalAction, MoveToLineStartTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, RenameTerminalAction, ResizePaneDownTerminalAction, ResizePaneLeftTerminalAction, ResizePaneRightTerminalAction, ResizePaneUpTerminalAction, RunActiveFileInTerminalAction, RunSelectedTextInTerminalAction, ScrollDownPageTerminalAction, ScrollDownTerminalAction, ScrollToBottomTerminalAction, ScrollToNextCommandAction, ScrollToPreviousCommandAction, ScrollToTopTerminalAction, ScrollUpPageTerminalAction, ScrollUpTerminalAction, SelectAllTerminalAction, SelectDefaultShellWindowsTerminalAction, SelectToNextCommandAction, SelectToNextLineAction, SelectToPreviousCommandAction, SelectToPreviousLineAction, SendSequenceTerminalCommand, SplitInActiveWorkspaceTerminalAction, SplitTerminalAction, TerminalPasteAction, TERMINAL_PICKER_PREFIX, ToggleCaseSensitiveCommand, ToggleEscapeSequenceLoggingAction, ToggleRegexCommand, ToggleTerminalAction, ToggleWholeWordCommand, NavigationModeFocusPreviousTerminalAction, NavigationModeFocusNextTerminalAction, NavigationModeExitTerminalAction, ManageWorkspaceShellPermissionsTerminalCommand } from 'vs/workbench/contrib/terminal/browser/terminalActions';
import { TerminalPanel } from 'vs/workbench/contrib/terminal/browser/terminalPanel';
import { TerminalPickerHandler } from 'vs/workbench/contrib/terminal/browser/terminalQuickOpen';
import { KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, ITerminalService, TERMINAL_ACTION_CATEGORY, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS } from 'vs/workbench/contrib/terminal/common/terminal';
import { KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, TERMINAL_ACTION_CATEGORY, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal';
import { registerColors } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry';
import { setupTerminalCommands, TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminalCommands';
import { setupTerminalCommands } from 'vs/workbench/contrib/terminal/browser/terminalCommands';
import { setupTerminalMenu } from 'vs/workbench/contrib/terminal/common/terminalMenu';
import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry';
import { EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions';
@@ -34,6 +34,7 @@ import { TerminalService } from 'vs/workbench/contrib/terminal/browser/terminalS
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { registerShellConfiguration } from 'vs/workbench/contrib/terminal/common/terminalShellConfig';
import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility';
import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
registerSingleton(ITerminalService, TerminalService, true);
@@ -194,10 +195,16 @@ configurationRegistry.registerConfiguration({
type: 'number',
default: 1000
},
'terminal.integrated.setLocaleVariables': {
markdownDescription: nls.localize('terminal.integrated.setLocaleVariables', "Controls whether locale variables are set at startup of the terminal."),
type: 'boolean',
default: true
'terminal.integrated.detectLocale': {
markdownDescription: nls.localize('terminal.integrated.detectLocale', "Controls whether to detect and set the `$LANG` environment variable to a UTF-8 compliant option since VS Code's terminal only supports UTF-8 encoded data coming from the shell."),
type: 'string',
enum: ['auto', 'off', 'on'],
enumDescriptions: [
nls.localize('terminal.integrated.detectLocale.auto', "Set the `$LANG` environment variable if the existing variable does not exist or it does not end in `'.UTF-8'`."),
nls.localize('terminal.integrated.detectLocale.off', "Do not set the `$LANG` environment variable."),
nls.localize('terminal.integrated.detectLocale.on', "Always set the `$LANG` environment variable.")
],
default: 'auto'
},
'terminal.integrated.rendererType': {
type: 'string',

View File

@@ -6,11 +6,16 @@
import { Terminal as XTermTerminal } from 'xterm';
import { WebLinksAddon as XTermWebLinksAddon } from 'xterm-addon-web-links';
import { SearchAddon as XTermSearchAddon } from 'xterm-addon-search';
import { ITerminalInstance, IWindowsShellHelper, ITerminalConfigHelper, ITerminalChildProcess, IShellLaunchConfig, IDefaultShellAndArgsRequest } from 'vs/workbench/contrib/terminal/common/terminal';
import { IWindowsShellHelper, ITerminalConfigHelper, ITerminalChildProcess, IShellLaunchConfig, IDefaultShellAndArgsRequest, ISpawnExtHostProcessRequest, IStartExtensionTerminalRequest, IAvailableShellsRequest, ITerminalProcessExtHostProxy, ICommandTracker, INavigationMode, TitleEventSource, ITerminalDimensions } 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';
import { IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IDisposable } from 'vs/base/common/lifecycle';
import { FindReplaceState } from 'vs/editor/contrib/find/findState';
import { URI } from 'vs/base/common/uri';
export const ITerminalService = createDecorator<ITerminalService>('terminalService');
export const ITerminalInstanceService = createDecorator<ITerminalInstanceService>('terminalInstanceService');
/**
@@ -19,7 +24,7 @@ export const ITerminalInstanceService = createDecorator<ITerminalInstanceService
* dependency on ITerminalService.
*/
export interface ITerminalInstanceService {
_serviceBrand: any;
_serviceBrand: undefined;
// These events are optional as the requests they make are only needed on the browser side
onRequestDefaultShellAndArgs?: Event<IDefaultShellAndArgsRequest>;
@@ -37,3 +42,404 @@ export interface ITerminalInstanceService {
export interface IBrowserTerminalConfigHelper extends ITerminalConfigHelper {
panelContainer: HTMLElement | undefined;
}
export const enum Direction {
Left = 0,
Right = 1,
Up = 2,
Down = 3
}
export interface ITerminalTab {
activeInstance: ITerminalInstance | null;
terminalInstances: ITerminalInstance[];
title: string;
onDisposed: Event<ITerminalTab>;
onInstancesChanged: Event<void>;
focusPreviousPane(): void;
focusNextPane(): void;
resizePane(direction: Direction): void;
setActiveInstanceByIndex(index: number): void;
attachToElement(element: HTMLElement): void;
setVisible(visible: boolean): void;
layout(width: number, height: number): void;
addDisposable(disposable: IDisposable): void;
split(terminalFocusContextKey: IContextKey<boolean>, configHelper: ITerminalConfigHelper, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance | undefined;
}
export interface ITerminalService {
_serviceBrand: undefined;
activeTabIndex: number;
configHelper: ITerminalConfigHelper;
terminalInstances: ITerminalInstance[];
terminalTabs: ITerminalTab[];
onActiveTabChanged: Event<void>;
onTabDisposed: Event<ITerminalTab>;
onInstanceCreated: Event<ITerminalInstance>;
onInstanceDisposed: Event<ITerminalInstance>;
onInstanceProcessIdReady: Event<ITerminalInstance>;
onInstanceDimensionsChanged: Event<ITerminalInstance>;
onInstanceMaximumDimensionsChanged: Event<ITerminalInstance>;
onInstanceRequestSpawnExtHostProcess: Event<ISpawnExtHostProcessRequest>;
onInstanceRequestStartExtensionTerminal: Event<IStartExtensionTerminalRequest>;
onInstancesChanged: Event<void>;
onInstanceTitleChanged: Event<ITerminalInstance>;
onActiveInstanceChanged: Event<ITerminalInstance | undefined>;
onRequestAvailableShells: Event<IAvailableShellsRequest>;
/**
* Creates a terminal.
* @param shell The shell launch configuration to use.
*/
createTerminal(shell?: IShellLaunchConfig): ITerminalInstance;
/**
* Creates a raw terminal instance, this should not be used outside of the terminal part.
*/
createInstance(container: HTMLElement | undefined, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance;
getInstanceFromId(terminalId: number): ITerminalInstance | undefined;
getInstanceFromIndex(terminalIndex: number): ITerminalInstance;
getTabLabels(): string[];
getActiveInstance(): ITerminalInstance | null;
setActiveInstance(terminalInstance: ITerminalInstance): void;
setActiveInstanceByIndex(terminalIndex: number): void;
getActiveOrCreateInstance(): ITerminalInstance;
splitInstance(instance: ITerminalInstance, shell?: IShellLaunchConfig): ITerminalInstance | null;
getActiveTab(): ITerminalTab | null;
setActiveTabToNext(): void;
setActiveTabToPrevious(): void;
setActiveTabByIndex(tabIndex: number): void;
/**
* Fire the onActiveTabChanged event, this will trigger the terminal dropdown to be updated,
* among other things.
*/
refreshActiveTab(): void;
showPanel(focus?: boolean): Promise<void>;
hidePanel(): void;
focusFindWidget(): Promise<void>;
hideFindWidget(): void;
getFindState(): FindReplaceState;
findNext(): void;
findPrevious(): void;
selectDefaultWindowsShell(): Promise<void>;
setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void;
manageWorkspaceShellPermissions(): void;
/**
* Takes a path and returns the properly escaped path to send to the terminal.
* On Windows, this included trying to prepare the path for WSL if needed.
*
* @param executable The executable off the shellLaunchConfig
* @param title The terminal's title
* @param path The path to be escaped and formatted.
* @returns An escaped version of the path to be execuded in the terminal.
*/
preparePathForTerminalAsync(path: string, executable: string | undefined, title: string): Promise<string>;
extHostReady(remoteAuthority: string): void;
requestSpawnExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number, isWorkspaceShellAllowed: boolean): void;
requestStartExtensionTerminal(proxy: ITerminalProcessExtHostProxy, cols: number, rows: number): void;
}
export interface ISearchOptions {
/**
* Whether the find should be done as a regex.
*/
regex?: boolean;
/**
* Whether only whole words should match.
*/
wholeWord?: boolean;
/**
* Whether find should pay attention to case.
*/
caseSensitive?: boolean;
/**
* Whether the search should start at the current search position (not the next row)
*/
incremental?: boolean;
}
export interface ITerminalInstance {
/**
* The ID of the terminal instance, this is an arbitrary number only used to identify the
* terminal instance.
*/
readonly id: number;
readonly cols: number;
readonly rows: number;
readonly maxCols: number;
readonly maxRows: number;
/**
* The process ID of the shell process, this is undefined when there is no process associated
* with this terminal.
*/
processId: number | undefined;
/**
* An event that fires when the terminal instance's title changes.
*/
onTitleChanged: Event<ITerminalInstance>;
/**
* An event that fires when the terminal instance is disposed.
*/
onDisposed: Event<ITerminalInstance>;
onFocused: Event<ITerminalInstance>;
onProcessIdReady: Event<ITerminalInstance>;
onRequestExtHostProcess: Event<ITerminalInstance>;
onDimensionsChanged: Event<void>;
onMaximumDimensionsChanged: Event<void>;
onFocus: Event<ITerminalInstance>;
/**
* Attach a listener to the raw data stream coming from the pty, including ANSI escape
* sequences.
*/
onData: Event<string>;
/**
* Attach a listener to listen for new lines added to this terminal instance.
*
* @param listener The listener function which takes new line strings added to the terminal,
* excluding ANSI escape sequences. The line event will fire when an LF character is added to
* the terminal (ie. the line is not wrapped). Note that this means that the line data will
* not fire for the last line, until either the line is ended with a LF character of the process
* is exited. The lineData string will contain the fully wrapped line, not containing any LF/CR
* characters.
*/
onLineData: Event<string>;
/**
* Attach a listener that fires when the terminal's pty process exits. The number in the event
* is the processes' exit code, an exit code of null means the process was killed as a result of
* the ITerminalInstance being disposed.
*/
onExit: Event<number>;
processReady: Promise<void>;
/**
* The title of the terminal. This is either title or the process currently running or an
* explicit name given to the terminal instance through the extension API.
*/
readonly title: string;
/**
* The focus state of the terminal before exiting.
*/
readonly hadFocusOnExit: boolean;
/**
* False when the title is set by an API or the user. We check this to make sure we
* do not override the title when the process title changes in the terminal.
*/
isTitleSetByProcess: boolean;
/**
* The shell launch config used to launch the shell.
*/
readonly shellLaunchConfig: IShellLaunchConfig;
/**
* Whether to disable layout for the terminal. This is useful when the size of the terminal is
* being manipulating (e.g. adding a split pane) and we want the terminal to ignore particular
* resize events.
*/
disableLayout: boolean;
/**
* An object that tracks when commands are run and enables navigating and selecting between
* them.
*/
readonly commandTracker: ICommandTracker | undefined;
readonly navigationMode: INavigationMode | undefined;
/**
* Dispose the terminal instance, removing it from the panel/service and freeing up resources.
*
* @param immediate Whether the kill should be immediate or not. Immediate should only be used
* when VS Code is shutting down or in cases where the terminal dispose was user initiated.
* The immediate===false exists to cover an edge case where the final output of the terminal can
* get cut off. If immediate kill any terminal processes immediately.
*/
dispose(immediate?: boolean): void;
/**
* Forces the terminal to redraw its viewport.
*/
forceRedraw(): void;
/**
* Registers a link matcher, allowing custom link patterns to be matched and handled.
* @param regex The regular expression the search for, specifically this searches the
* textContent of the rows. You will want to use \s to match a space ' ' character for example.
* @param handler The callback when the link is called.
* @param matchIndex The index of the link from the regex.match(html) call. This defaults to 0
* (for regular expressions without capture groups).
* @param validationCallback A callback which can be used to validate the link after it has been
* added to the DOM.
* @return The ID of the new matcher, this can be used to deregister.
*/
registerLinkMatcher(regex: RegExp, handler: (url: string) => void, matchIndex?: number, validationCallback?: (uri: string, callback: (isValid: boolean) => void) => void): number;
/**
* Deregisters a link matcher if it has been registered.
* @param matcherId The link matcher's ID (returned after register)
* @return Whether a link matcher was found and deregistered.
*/
deregisterLinkMatcher(matcherId: number): void;
/**
* Check if anything is selected in terminal.
*/
hasSelection(): boolean;
/**
* Copies the terminal selection to the clipboard.
*/
copySelection(): Promise<void>;
/**
* Current selection in the terminal.
*/
readonly selection: string | undefined;
/**
* Clear current selection.
*/
clearSelection(): void;
/**
* Select all text in the terminal.
*/
selectAll(): void;
/**
* Find the next instance of the term
*/
findNext(term: string, searchOptions: ISearchOptions): boolean;
/**
* Find the previous instance of the term
*/
findPrevious(term: string, searchOptions: ISearchOptions): boolean;
/**
* Notifies the terminal that the find widget's focus state has been changed.
*/
notifyFindWidgetFocusChanged(isFocused: boolean): void;
/**
* Focuses the terminal instance if it's able to (xterm.js instance exists).
*
* @param focus Force focus even if there is a selection.
*/
focus(force?: boolean): void;
/**
* Focuses the terminal instance when it's ready (the xterm.js instance is created). Use this
* when the terminal is being shown.
*
* @param focus Force focus even if there is a selection.
*/
focusWhenReady(force?: boolean): Promise<void>;
/**
* Focuses and pastes the contents of the clipboard into the terminal instance.
*/
paste(): Promise<void>;
/**
* Send text to the terminal instance. The text is written to the stdin of the underlying pty
* process (shell) of the terminal instance.
*
* @param text The text to send.
* @param addNewLine Whether to add a new line to the text being sent, this is normally
* required to run a command in the terminal. The character(s) added are \n or \r\n
* depending on the platform. This defaults to `true`.
*/
sendText(text: string, addNewLine: boolean): void;
/**
* Write text directly to the terminal, skipping the process if it exists.
* @param text The text to write.
*/
write(text: string): void;
/** Scroll the terminal buffer down 1 line. */
scrollDownLine(): void;
/** Scroll the terminal buffer down 1 page. */
scrollDownPage(): void;
/** Scroll the terminal buffer to the bottom. */
scrollToBottom(): void;
/** Scroll the terminal buffer up 1 line. */
scrollUpLine(): void;
/** Scroll the terminal buffer up 1 page. */
scrollUpPage(): void;
/** Scroll the terminal buffer to the top. */
scrollToTop(): void;
/**
* Clears the terminal buffer, leaving only the prompt line.
*/
clear(): void;
/**
* Attaches the terminal instance to an element on the DOM, before this is called the terminal
* instance process may run in the background but cannot be displayed on the UI.
*
* @param container The element to attach the terminal instance to.
*/
attachToElement(container: HTMLElement): void;
/**
* Configure the dimensions of the terminal instance.
*
* @param dimension The dimensions of the container.
*/
layout(dimension: { width: number, height: number }): void;
/**
* Sets whether the terminal instance's element is visible in the DOM.
*
* @param visible Whether the element is visible.
*/
setVisible(visible: boolean): void;
/**
* Immediately kills the terminal's current pty process and launches a new one to replace it.
*
* @param shell The new launch configuration.
*/
reuseTerminal(shell: IShellLaunchConfig): void;
/**
* Sets the title of the terminal instance.
*/
setTitle(title: string, eventSource: TitleEventSource): void;
waitForTitle(): Promise<string>;
setDimensions(dimensions: ITerminalDimensions): void;
addDisposable(disposable: IDisposable): void;
toggleEscapeSequenceLogging(): void;
getInitialCwd(): Promise<string>;
getCwd(): Promise<string>;
}

View File

@@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
import { Action, IAction } from 'vs/base/common/actions';
import { EndOfLinePreference } from 'vs/editor/common/model';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { ITerminalService, TERMINAL_PANEL_ID, ITerminalInstance, Direction, ITerminalConfigHelper, TitleEventSource } from 'vs/workbench/contrib/terminal/common/terminal';
import { TERMINAL_PANEL_ID, ITerminalConfigHelper, TitleEventSource, TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal';
import { SelectActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { TogglePanelAction } from 'vs/workbench/browser/panel';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
@@ -24,7 +24,6 @@ import { ICommandService } from 'vs/platform/commands/common/commands';
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminalCommands';
import { Command } from 'vs/editor/browser/editorExtensions';
import { timeout } from 'vs/base/common/async';
import { FindReplaceState } from 'vs/editor/contrib/find/findState';
@@ -35,6 +34,7 @@ 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';
export const TERMINAL_PICKER_PREFIX = 'term ';
@@ -231,8 +231,8 @@ export class DeleteWordRightTerminalAction extends BaseSendTextTerminalAction {
label: string,
@ITerminalService terminalService: ITerminalService
) {
// Send alt+D
super(id, label, '\x1bD', terminalService);
// Send alt+d
super(id, label, '\x1bd', terminalService);
}
}
@@ -549,7 +549,7 @@ export class FocusActiveTerminalAction extends Action {
}
public run(event?: any): Promise<any> {
const instance = this.terminalService.getActiveOrCreateInstance(true);
const instance = this.terminalService.getActiveOrCreateInstance();
if (!instance) {
return Promise.resolve(undefined);
}

View File

@@ -0,0 +1,30 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
export function setupTerminalCommands(): void {
registerOpenTerminalAtIndexCommands();
}
function registerOpenTerminalAtIndexCommands(): void {
for (let i = 0; i < 9; i++) {
const terminalIndex = i;
const visibleIndex = i + 1;
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: `workbench.action.terminal.focusAtIndex${visibleIndex}`,
weight: KeybindingWeight.WorkbenchContrib,
when: undefined,
primary: 0,
handler: accessor => {
const terminalService = accessor.get(ITerminalService);
terminalService.setActiveInstanceByIndex(terminalIndex);
return terminalService.showPanel(true);
}
});
}
}

View File

@@ -10,7 +10,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IStorageService, StorageScope } 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 } from 'vs/workbench/contrib/terminal/common/terminal';
import Severity from 'vs/base/common/severity';
import { Terminal as XTermTerminal } from 'xterm';
import { INotificationService, NeverShowAgainScope } from 'vs/platform/notification/common/notification';
import { IBrowserTerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminal';
import { Emitter, Event } from 'vs/base/common/event';
@@ -21,6 +20,7 @@ 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/product';
import { XTermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private';
const MINIMUM_FONT_SIZE = 6;
const MAXIMUM_FONT_SIZE = 25;
@@ -129,7 +129,7 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper {
* Gets the font information based on the terminal.integrated.fontFamily
* terminal.integrated.fontSize, terminal.integrated.lineHeight configuration properties
*/
public getFont(xterm?: XTermTerminal, excludeDimensions?: boolean): ITerminalFont {
public getFont(xtermCore?: XTermCore, excludeDimensions?: boolean): ITerminalFont {
const editorConfig = this._configurationService.getValue<IEditorOptions>('editor');
let fontFamily = this.config.fontFamily || editorConfig.fontFamily || EDITOR_FONT_DEFAULTS.fontFamily;
@@ -161,15 +161,15 @@ export class TerminalConfigHelper implements IBrowserTerminalConfigHelper {
}
// Get the character dimensions from xterm if it's available
if (xterm) {
if (xterm._core._charSizeService && xterm._core._charSizeService.width && xterm._core._charSizeService.height) {
if (xtermCore) {
if (xtermCore._charSizeService && xtermCore._charSizeService.width && xtermCore._charSizeService.height) {
return {
fontFamily,
fontSize,
letterSpacing,
lineHeight,
charHeight: xterm._core._charSizeService.height,
charWidth: xterm._core._charSizeService.width
charHeight: xtermCore._charSizeService.height,
charWidth: xtermCore._charSizeService.width
};
}
}

View File

@@ -5,9 +5,10 @@
import { SimpleFindWidget } from 'vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { ITerminalService, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED } from 'vs/workbench/contrib/terminal/common/terminal';
import { KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED } from 'vs/workbench/contrib/terminal/common/terminal';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { FindReplaceState } from 'vs/editor/contrib/find/findState';
import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
export class TerminalFindWidget extends SimpleFindWidget {
protected _findInputFocused: IContextKey<boolean>;

View File

@@ -25,19 +25,19 @@ import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderB
import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { PANEL_BACKGROUND } from 'vs/workbench/common/theme';
import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/terminalWidgetManager';
import { IShellLaunchConfig, ITerminalDimensions, ITerminalInstance, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_PANEL_ID, IWindowsShellHelper, SHELL_PATH_INVALID_EXIT_CODE, SHELL_PATH_DIRECTORY_EXIT_CODE, SHELL_CWD_INVALID_EXIT_CODE, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, INavigationMode, TitleEventSource } from 'vs/workbench/contrib/terminal/common/terminal';
import { IShellLaunchConfig, ITerminalDimensions, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_PANEL_ID, IWindowsShellHelper, SHELL_PATH_INVALID_EXIT_CODE, SHELL_PATH_DIRECTORY_EXIT_CODE, SHELL_CWD_INVALID_EXIT_CODE, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, INavigationMode, TitleEventSource, TERMINAL_COMMAND_ID } 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 { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminalCommands';
import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper';
import { TerminalLinkHandler } from 'vs/workbench/contrib/terminal/browser/terminalLinkHandler';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { ITerminalInstanceService, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/terminalProcessManager';
import { Terminal as XTermTerminal, IBuffer, ITerminalAddon } from 'xterm';
import { SearchAddon, ISearchOptions } from 'xterm-addon-search';
import { CommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon';
import { NavigationModeAddon } from 'vs/workbench/contrib/terminal/browser/addons/navigationModeAddon';
import { XTermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private';
// How long in milliseconds should an average frame take to render for a notification to appear
// which suggests the fallback DOM-based renderer
@@ -184,6 +184,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
private _title: string = '';
private _wrapperElement: (HTMLElement & { xterm?: XTermTerminal }) | undefined;
private _xterm: XTermTerminal | undefined;
private _xtermCore: XTermCore | undefined;
private _xtermSearch: SearchAddon | undefined;
private _xtermElement: HTMLDivElement | undefined;
private _terminalHasTextContextKey: IContextKey<boolean>;
@@ -351,7 +352,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
return null;
}
const font = this._configHelper.getFont(this._xterm);
const font = this._configHelper.getFont(this._xtermCore);
if (!font.charWidth || !font.charHeight) {
this._setLastKnownColsAndRows();
return null;
@@ -399,7 +400,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
private _getDimension(width: number, height: number): ICanvasDimensions | undefined {
// The font needs to have been initialized
const font = this._configHelper.getFont(this._xterm);
const font = this._configHelper.getFont(this._xtermCore);
if (!font || !font.charWidth || !font.charHeight) {
return undefined;
}
@@ -412,8 +413,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
// needs to happen otherwise its scrollTop value is invalid when the panel is toggled as
// it gets removed and then added back to the DOM (resetting scrollTop to 0).
// Upstream issue: https://github.com/sourcelair/xterm.js/issues/291
if (this._xterm) {
this._xterm._core._onScroll.fire(this._xterm.buffer.viewportY);
if (this._xterm && this._xtermCore) {
this._xtermCore._onScroll.fire(this._xterm.buffer.viewportY);
}
}
@@ -472,6 +473,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
rendererType: config.rendererType === 'auto' ? 'canvas' : config.rendererType
});
this._xterm = xterm;
this._xtermCore = (xterm as any)._core as XTermCore;
this.updateAccessibilitySupport();
this._terminalInstanceService.getXtermSearchConstructor().then(Addon => {
this._xtermSearch = new Addon();
@@ -674,9 +676,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
private async _measureRenderTime(): Promise<void> {
const xterm = await this._xtermReadyPromise;
await this._xtermReadyPromise;
const frameTimes: number[] = [];
const textRenderLayer = xterm._core._renderService._renderer._renderLayers[0];
const textRenderLayer = this._xtermCore!._renderService._renderer._renderLayers[0];
const originalOnGridChanged = textRenderLayer.onGridChanged;
const evaluateCanvasRenderer = () => {
@@ -893,12 +895,12 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
if (this._wrapperElement) {
dom.toggleClass(this._wrapperElement, 'active', visible);
}
if (visible && this._xterm) {
if (visible && this._xterm && this._xtermCore) {
// Trigger a manual scroll event which will sync the viewport and scroll bar. This is
// necessary if the number of rows in the terminal has decreased while it was in the
// background since scrollTop changes take no effect but the terminal's position does
// change since the number of visible rows decreases.
this._xterm._core._onScroll.fire(this._xterm.buffer.viewportY);
this._xtermCore._onScroll.fire(this._xterm.buffer.viewportY);
if (this._container && this._container.parentElement) {
// Force a layout when the instance becomes invisible. This is particularly important
// for ensuring that terminals that are created in the background by an extension will
@@ -1322,11 +1324,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
let cols = this.cols;
let rows = this.rows;
if (this._xterm) {
if (this._xterm && this._xtermCore) {
// Only apply these settings when the terminal is visible so that
// the characters are measured correctly.
if (this._isVisible) {
const font = this._configHelper.getFont(this._xterm);
const font = this._configHelper.getFont(this._xtermCore);
const config = this._configHelper.config;
this._safeSetOption('letterSpacing', font.letterSpacing);
this._safeSetOption('lineHeight', font.lineHeight);
@@ -1354,7 +1356,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._xterm._core._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);

View File

@@ -17,7 +17,7 @@ let WebLinksAddon: typeof XTermWebLinksAddon;
let SearchAddon: typeof XTermSearchAddon;
export class TerminalInstanceService implements ITerminalInstanceService {
public _serviceBrand: any;
public _serviceBrand: undefined;
private readonly _onRequestDefaultShellAndArgs = new Emitter<IDefaultShellAndArgsRequest>();
public get onRequestDefaultShellAndArgs(): Event<IDefaultShellAndArgsRequest> { return this._onRequestDefaultShellAndArgs.event; }

View File

@@ -9,7 +9,7 @@ import { Emitter, Event } from 'vs/base/common/event';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
export class TerminalNativeService implements ITerminalNativeService {
public _serviceBrand: any;
public _serviceBrand: undefined;
public get linuxDistro(): LinuxDistro { return LinuxDistro.Unknown; }

View File

@@ -12,7 +12,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ITerminalService, TERMINAL_PANEL_ID } from 'vs/workbench/contrib/terminal/common/terminal';
import { TERMINAL_PANEL_ID } from 'vs/workbench/contrib/terminal/common/terminal';
import { IThemeService, ITheme, registerThemingParticipant, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
import { TerminalFindWidget } from 'vs/workbench/contrib/terminal/browser/terminalFindWidget';
import { editorHoverBackground, editorHoverBorder, editorForeground } from 'vs/platform/theme/common/colorRegistry';
@@ -24,6 +24,7 @@ import { TERMINAL_BACKGROUND_COLOR, TERMINAL_BORDER_COLOR } from 'vs/workbench/c
import { DataTransfers } from 'vs/base/browser/dnd';
import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
const FIND_FOCUS_CLASS = 'find-focused';

View File

@@ -4,11 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import { Event, Emitter } from 'vs/base/common/event';
import { ITerminalService, ITerminalProcessExtHostProxy, IShellLaunchConfig, ITerminalChildProcess, ITerminalConfigHelper, ITerminalDimensions } from 'vs/workbench/contrib/terminal/common/terminal';
import { ITerminalProcessExtHostProxy, IShellLaunchConfig, ITerminalChildProcess, ITerminalConfigHelper, ITerminalDimensions } from 'vs/workbench/contrib/terminal/common/terminal';
import { Disposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import * as nls from 'vs/nls';
import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
let hasReceivedResponse: boolean = false;

View File

@@ -10,7 +10,7 @@ import { ProcessState, ITerminalProcessManager, IShellLaunchConfig, ITerminalCon
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';
import { TerminalProcessExtHostProxy } from 'vs/workbench/contrib/terminal/common/terminalProcessExtHostProxy';
import { TerminalProcessExtHostProxy } from 'vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
@@ -227,7 +227,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.setLocaleVariables, baseEnv);
const env = terminalEnvironment.createTerminalEnvironment(shellLaunchConfig, lastActiveWorkspace, envFromConfigValue, this._configurationResolverService, isWorkspaceShellAllowed, this._productService.version, this._configHelper.config.detectLocale, baseEnv);
const useConpty = this._configHelper.config.windowsEnableConpty && !isScreenReaderModeEnabled;
return this._terminalInstanceService.createTerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, useConpty);

View File

@@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
import { Mode, IEntryRunContext, IAutoFocus, IQuickNavigateConfiguration, IModel } from 'vs/base/parts/quickopen/common/quickOpen';
import { QuickOpenModel, QuickOpenEntry } from 'vs/base/parts/quickopen/browser/quickOpenModel';
import { QuickOpenHandler } from 'vs/workbench/browser/quickopen';
import { ITerminalService, ITerminalInstance } from 'vs/workbench/contrib/terminal/common/terminal';
import { ITerminalService, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
import { ContributableActionProvider } from 'vs/workbench/browser/actions';
import { stripWildcards } from 'vs/base/common/strings';
import { matchesFuzzy } from 'vs/base/common/filters';
@@ -134,4 +134,4 @@ export class TerminalPickerHandler extends QuickOpenHandler {
}
return nls.localize('noTerminalsFound', "No terminals open");
}
}
}

View File

@@ -3,13 +3,12 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ITerminalService, TERMINAL_PANEL_ID, ITerminalInstance, IShellLaunchConfig, ITerminalConfigHelper, ITerminalNativeService } from 'vs/workbench/contrib/terminal/common/terminal';
import { TerminalService as CommonTerminalService } from 'vs/workbench/contrib/terminal/common/terminalService';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import * as nls from 'vs/nls';
import { TERMINAL_PANEL_ID, IShellLaunchConfig, ITerminalConfigHelper, ITerminalNativeService, ISpawnExtHostProcessRequest, IStartExtensionTerminalRequest, IAvailableShellsRequest, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, ITerminalProcessExtHostProxy, IShellDefinition } from 'vs/workbench/contrib/terminal/common/terminal';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { TerminalPanel } from 'vs/workbench/contrib/terminal/browser/terminalPanel';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { INotificationService } from 'vs/platform/notification/common/notification';
@@ -18,38 +17,578 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminalInstance';
import { IBrowserTerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminal';
import { IBrowserTerminalConfigHelper, ITerminalService, ITerminalInstance, ITerminalTab } 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 } from 'vs/platform/quickinput/common/quickInput';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
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 { URI } from 'vs/base/common/uri';
import { FindReplaceState } from 'vs/editor/contrib/find/findState';
import { escapeNonWindowsPath } from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
import { isWindows, isMacintosh, OperatingSystem } from 'vs/base/common/platform';
import { basename } from 'vs/base/common/path';
import { IOpenFileRequest } from 'vs/platform/windows/common/windows';
interface IExtHostReadyEntry {
promise: Promise<void>;
resolve: () => void;
}
export class TerminalService implements ITerminalService {
public _serviceBrand: undefined;
protected _isShuttingDown: boolean;
protected _terminalFocusContextKey: IContextKey<boolean>;
protected _findWidgetVisible: IContextKey<boolean>;
protected _terminalTabs: ITerminalTab[] = [];
protected _backgroundedTerminalInstances: ITerminalInstance[] = [];
protected get _terminalInstances(): ITerminalInstance[] {
return this._terminalTabs.reduce((p, c) => p.concat(c.terminalInstances), <ITerminalInstance[]>[]);
}
private _findState: FindReplaceState;
private _extHostsReady: { [authority: string]: IExtHostReadyEntry | undefined } = {};
private _activeTabIndex: number;
public get activeTabIndex(): number { return this._activeTabIndex; }
public get terminalInstances(): ITerminalInstance[] { return this._terminalInstances; }
public get terminalTabs(): ITerminalTab[] { return this._terminalTabs; }
export class TerminalService extends CommonTerminalService implements ITerminalService {
private _configHelper: IBrowserTerminalConfigHelper;
private _terminalContainer: HTMLElement | undefined;
public get configHelper(): ITerminalConfigHelper { return this._configHelper; }
protected readonly _onActiveTabChanged = new Emitter<void>();
public get onActiveTabChanged(): Event<void> { return this._onActiveTabChanged.event; }
protected readonly _onInstanceCreated = new Emitter<ITerminalInstance>();
public get onInstanceCreated(): Event<ITerminalInstance> { return this._onInstanceCreated.event; }
protected readonly _onInstanceDisposed = new Emitter<ITerminalInstance>();
public get onInstanceDisposed(): Event<ITerminalInstance> { return this._onInstanceDisposed.event; }
protected readonly _onInstanceProcessIdReady = new Emitter<ITerminalInstance>();
public get onInstanceProcessIdReady(): Event<ITerminalInstance> { return this._onInstanceProcessIdReady.event; }
protected readonly _onInstanceRequestSpawnExtHostProcess = new Emitter<ISpawnExtHostProcessRequest>();
public get onInstanceRequestSpawnExtHostProcess(): Event<ISpawnExtHostProcessRequest> { return this._onInstanceRequestSpawnExtHostProcess.event; }
protected readonly _onInstanceRequestStartExtensionTerminal = new Emitter<IStartExtensionTerminalRequest>();
public get onInstanceRequestStartExtensionTerminal(): Event<IStartExtensionTerminalRequest> { return this._onInstanceRequestStartExtensionTerminal.event; }
protected readonly _onInstanceDimensionsChanged = new Emitter<ITerminalInstance>();
public get onInstanceDimensionsChanged(): Event<ITerminalInstance> { return this._onInstanceDimensionsChanged.event; }
protected readonly _onInstanceMaximumDimensionsChanged = new Emitter<ITerminalInstance>();
public get onInstanceMaximumDimensionsChanged(): Event<ITerminalInstance> { return this._onInstanceMaximumDimensionsChanged.event; }
protected readonly _onInstancesChanged = new Emitter<void>();
public get onInstancesChanged(): Event<void> { return this._onInstancesChanged.event; }
protected readonly _onInstanceTitleChanged = new Emitter<ITerminalInstance>();
public get onInstanceTitleChanged(): Event<ITerminalInstance> { return this._onInstanceTitleChanged.event; }
protected readonly _onActiveInstanceChanged = new Emitter<ITerminalInstance | undefined>();
public get onActiveInstanceChanged(): Event<ITerminalInstance | undefined> { return this._onActiveInstanceChanged.event; }
protected readonly _onTabDisposed = new Emitter<ITerminalTab>();
public get onTabDisposed(): Event<ITerminalTab> { return this._onTabDisposed.event; }
protected readonly _onRequestAvailableShells = new Emitter<IAvailableShellsRequest>();
public get onRequestAvailableShells(): Event<IAvailableShellsRequest> { return this._onRequestAvailableShells.event; }
constructor(
@IContextKeyService contextKeyService: IContextKeyService,
@IPanelService panelService: IPanelService,
@IContextKeyService private _contextKeyService: IContextKeyService,
@IPanelService private _panelService: IPanelService,
@IWorkbenchLayoutService private _layoutService: IWorkbenchLayoutService,
@ILifecycleService lifecycleService: ILifecycleService,
@IStorageService storageService: IStorageService,
@INotificationService notificationService: INotificationService,
@IDialogService dialogService: IDialogService,
@IInstantiationService protected readonly _instantiationService: IInstantiationService,
@IExtensionService extensionService: IExtensionService,
@IFileService fileService: IFileService,
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@ITerminalNativeService readonly terminalNativeService: ITerminalNativeService,
@IQuickInputService readonly quickInputService: IQuickInputService,
@IConfigurationService readonly configurationService: IConfigurationService
@INotificationService private _notificationService: INotificationService,
@IDialogService private _dialogService: IDialogService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IExtensionService private _extensionService: IExtensionService,
@IFileService private _fileService: IFileService,
@IRemoteAgentService private _remoteAgentService: IRemoteAgentService,
@ITerminalNativeService private _terminalNativeService: ITerminalNativeService,
@IQuickInputService private _quickInputService: IQuickInputService,
@IConfigurationService private _configurationService: IConfigurationService
) {
super(contextKeyService, panelService, lifecycleService, storageService, notificationService, dialogService, extensionService, fileService, remoteAgentService, terminalNativeService, quickInputService, configurationService);
this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper, this.terminalNativeService.linuxDistro);
this._activeTabIndex = 0;
this._isShuttingDown = false;
this._findState = new FindReplaceState();
lifecycleService.onBeforeShutdown(event => event.veto(this._onBeforeShutdown()));
lifecycleService.onShutdown(() => this._onShutdown());
this._terminalNativeService.onOpenFileRequest(e => this._onOpenFileRequest(e));
this._terminalNativeService.onOsResume(() => this._onOsResume());
this._terminalFocusContextKey = KEYBINDING_CONTEXT_TERMINAL_FOCUS.bindTo(this._contextKeyService);
this._findWidgetVisible = KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE.bindTo(this._contextKeyService);
this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper, this._terminalNativeService.linuxDistro);
this.onTabDisposed(tab => this._removeTab(tab));
this.onActiveTabChanged(() => {
const instance = this.getActiveInstance();
this._onActiveInstanceChanged.fire(instance ? instance : undefined);
});
this._handleContextKeys();
}
private _handleContextKeys(): void {
const terminalIsOpenContext = KEYBINDING_CONTEXT_TERMINAL_IS_OPEN.bindTo(this._contextKeyService);
const updateTerminalContextKeys = () => {
terminalIsOpenContext.set(this.terminalInstances.length > 0);
};
this.onInstancesChanged(() => updateTerminalContextKeys());
}
public getActiveOrCreateInstance(): ITerminalInstance {
const activeInstance = this.getActiveInstance();
return activeInstance ? activeInstance : this.createTerminal(undefined);
}
public requestSpawnExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number, isWorkspaceShellAllowed: boolean): void {
this._extensionService.whenInstalledExtensionsRegistered().then(async () => {
// Wait for the remoteAuthority to be ready (and listening for events) before firing
// the event to spawn the ext host process
const conn = this._remoteAgentService.getConnection();
const remoteAuthority = conn ? conn.remoteAuthority : 'null';
await this._whenExtHostReady(remoteAuthority);
this._onInstanceRequestSpawnExtHostProcess.fire({ proxy, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, isWorkspaceShellAllowed });
});
}
public requestStartExtensionTerminal(proxy: ITerminalProcessExtHostProxy, cols: number, rows: number): void {
this._onInstanceRequestStartExtensionTerminal.fire({ proxy, cols, rows });
}
public async extHostReady(remoteAuthority: string): Promise<void> {
this._createExtHostReadyEntry(remoteAuthority);
this._extHostsReady[remoteAuthority]!.resolve();
}
private async _whenExtHostReady(remoteAuthority: string): Promise<void> {
this._createExtHostReadyEntry(remoteAuthority);
return this._extHostsReady[remoteAuthority]!.promise;
}
private _createExtHostReadyEntry(remoteAuthority: string): void {
if (this._extHostsReady[remoteAuthority]) {
return;
}
let resolve!: () => void;
const promise = new Promise<void>(r => resolve = r);
this._extHostsReady[remoteAuthority] = { promise, resolve };
}
private _onBeforeShutdown(): boolean | Promise<boolean> {
if (this.terminalInstances.length === 0) {
// No terminal instances, don't veto
return false;
}
if (this.configHelper.config.confirmOnExit) {
// veto if configured to show confirmation and the user choosed not to exit
return this._showTerminalCloseConfirmation().then(veto => {
if (!veto) {
this._isShuttingDown = true;
}
return veto;
});
}
this._isShuttingDown = true;
return false;
}
private _onShutdown(): void {
// Dispose of all instances
this.terminalInstances.forEach(instance => instance.dispose(true));
}
private _onOpenFileRequest(request: IOpenFileRequest): void {
// if the request to open files is coming in from the integrated terminal (identified though
// the termProgram variable) and we are instructed to wait for editors close, wait for the
// marker file to get deleted and then focus back to the integrated terminal.
if (request.termProgram === 'vscode' && request.filesToWait) {
const waitMarkerFileUri = URI.revive(request.filesToWait.waitMarkerFileUri);
this._terminalNativeService.whenFileDeleted(waitMarkerFileUri).then(() => {
if (this.terminalInstances.length > 0) {
const terminal = this.getActiveInstance();
if (terminal) {
terminal.focus();
}
}
});
}
}
private _onOsResume(): void {
const activeTab = this.getActiveTab();
if (!activeTab) {
return;
}
activeTab.terminalInstances.forEach(instance => instance.forceRedraw());
}
public getTabLabels(): string[] {
return this._terminalTabs.filter(tab => tab.terminalInstances.length > 0).map((tab, index) => `${index + 1}: ${tab.title ? tab.title : ''}`);
}
public getFindState(): FindReplaceState {
return this._findState;
}
private _removeTab(tab: ITerminalTab): void {
// Get the index of the tab and remove it from the list
const index = this._terminalTabs.indexOf(tab);
const wasActiveTab = tab === this.getActiveTab();
if (index !== -1) {
this._terminalTabs.splice(index, 1);
}
// Adjust focus if the tab was active
if (wasActiveTab && this._terminalTabs.length > 0) {
// TODO: Only focus the new tab if the removed tab had focus?
// const hasFocusOnExit = tab.activeInstance.hadFocusOnExit;
const newIndex = index < this._terminalTabs.length ? index : this._terminalTabs.length - 1;
this.setActiveTabByIndex(newIndex);
const activeInstance = this.getActiveInstance();
if (activeInstance) {
activeInstance.focus(true);
}
}
// Hide the panel if there are no more instances, provided that VS Code is not shutting
// down. When shutting down the panel is locked in place so that it is restored upon next
// launch.
if (this._terminalTabs.length === 0 && !this._isShuttingDown) {
this.hidePanel();
this._onActiveInstanceChanged.fire(undefined);
}
// Fire events
this._onInstancesChanged.fire();
if (wasActiveTab) {
this._onActiveTabChanged.fire();
}
}
public refreshActiveTab(): void {
// Fire active instances changed
this._onActiveTabChanged.fire();
}
public getActiveTab(): ITerminalTab | null {
if (this._activeTabIndex < 0 || this._activeTabIndex >= this._terminalTabs.length) {
return null;
}
return this._terminalTabs[this._activeTabIndex];
}
public getActiveInstance(): ITerminalInstance | null {
const tab = this.getActiveTab();
if (!tab) {
return null;
}
return tab.activeInstance;
}
public getInstanceFromId(terminalId: number): ITerminalInstance | undefined {
let bgIndex = -1;
this._backgroundedTerminalInstances.forEach((terminalInstance, i) => {
if (terminalInstance.id === terminalId) {
bgIndex = i;
}
});
if (bgIndex !== -1) {
return this._backgroundedTerminalInstances[bgIndex];
}
try {
return this.terminalInstances[this._getIndexFromId(terminalId)];
} catch {
return undefined;
}
}
public getInstanceFromIndex(terminalIndex: number): ITerminalInstance {
return this.terminalInstances[terminalIndex];
}
public setActiveInstance(terminalInstance: ITerminalInstance): void {
// If this was a hideFromUser terminal created by the API this was triggered by show,
// in which case we need to create the terminal tab
if (terminalInstance.shellLaunchConfig.hideFromUser) {
this._showBackgroundTerminal(terminalInstance);
}
this.setActiveInstanceByIndex(this._getIndexFromId(terminalInstance.id));
}
public setActiveTabByIndex(tabIndex: number): void {
if (tabIndex >= this._terminalTabs.length) {
return;
}
const didTabChange = this._activeTabIndex !== tabIndex;
this._activeTabIndex = tabIndex;
this._terminalTabs.forEach((t, i) => t.setVisible(i === this._activeTabIndex));
if (didTabChange) {
this._onActiveTabChanged.fire();
}
}
private _getInstanceFromGlobalInstanceIndex(index: number): { tab: ITerminalTab, tabIndex: number, instance: ITerminalInstance, localInstanceIndex: number } | null {
let currentTabIndex = 0;
while (index >= 0 && currentTabIndex < this._terminalTabs.length) {
const tab = this._terminalTabs[currentTabIndex];
const count = tab.terminalInstances.length;
if (index < count) {
return {
tab,
tabIndex: currentTabIndex,
instance: tab.terminalInstances[index],
localInstanceIndex: index
};
}
index -= count;
currentTabIndex++;
}
return null;
}
public setActiveInstanceByIndex(terminalIndex: number): void {
const query = this._getInstanceFromGlobalInstanceIndex(terminalIndex);
if (!query) {
return;
}
query.tab.setActiveInstanceByIndex(query.localInstanceIndex);
const didTabChange = this._activeTabIndex !== query.tabIndex;
this._activeTabIndex = query.tabIndex;
this._terminalTabs.forEach((t, i) => t.setVisible(i === query.tabIndex));
// Only fire the event if there was a change
if (didTabChange) {
this._onActiveTabChanged.fire();
}
}
public setActiveTabToNext(): void {
if (this._terminalTabs.length <= 1) {
return;
}
let newIndex = this._activeTabIndex + 1;
if (newIndex >= this._terminalTabs.length) {
newIndex = 0;
}
this.setActiveTabByIndex(newIndex);
}
public setActiveTabToPrevious(): void {
if (this._terminalTabs.length <= 1) {
return;
}
let newIndex = this._activeTabIndex - 1;
if (newIndex < 0) {
newIndex = this._terminalTabs.length - 1;
}
this.setActiveTabByIndex(newIndex);
}
public splitInstance(instanceToSplit: ITerminalInstance, shellLaunchConfig: IShellLaunchConfig = {}): ITerminalInstance | null {
const tab = this._getTabForInstance(instanceToSplit);
if (!tab) {
return null;
}
const instance = tab.split(this._terminalFocusContextKey, this.configHelper, shellLaunchConfig);
if (!instance) {
this._showNotEnoughSpaceToast();
return null;
}
this._initInstanceListeners(instance);
this._onInstancesChanged.fire();
this._terminalTabs.forEach((t, i) => t.setVisible(i === this._activeTabIndex));
return instance;
}
protected _initInstanceListeners(instance: ITerminalInstance): void {
instance.addDisposable(instance.onDisposed(this._onInstanceDisposed.fire, this._onInstanceDisposed));
instance.addDisposable(instance.onTitleChanged(this._onInstanceTitleChanged.fire, this._onInstanceTitleChanged));
instance.addDisposable(instance.onProcessIdReady(this._onInstanceProcessIdReady.fire, this._onInstanceProcessIdReady));
instance.addDisposable(instance.onDimensionsChanged(() => this._onInstanceDimensionsChanged.fire(instance)));
instance.addDisposable(instance.onMaximumDimensionsChanged(() => this._onInstanceMaximumDimensionsChanged.fire(instance)));
instance.addDisposable(instance.onFocus(this._onActiveInstanceChanged.fire, this._onActiveInstanceChanged));
}
private _getTabForInstance(instance: ITerminalInstance): ITerminalTab | null {
for (const tab of this._terminalTabs) {
if (tab.terminalInstances.indexOf(instance) !== -1) {
return tab;
}
}
return null;
}
public showPanel(focus?: boolean): Promise<void> {
return new Promise<void>((complete) => {
const panel = this._panelService.getActivePanel();
if (!panel || panel.getId() !== TERMINAL_PANEL_ID) {
this._panelService.openPanel(TERMINAL_PANEL_ID, focus);
if (focus) {
// Do the focus call asynchronously as going through the
// command palette will force editor focus
setTimeout(() => {
const instance = this.getActiveInstance();
if (instance) {
instance.focusWhenReady(true).then(() => complete(undefined));
} else {
complete(undefined);
}
}, 0);
} else {
complete(undefined);
}
} else {
if (focus) {
// Do the focus call asynchronously as going through the
// command palette will force editor focus
setTimeout(() => {
const instance = this.getActiveInstance();
if (instance) {
instance.focusWhenReady(true).then(() => complete(undefined));
} else {
complete(undefined);
}
}, 0);
} else {
complete(undefined);
}
}
return undefined;
});
}
private _getIndexFromId(terminalId: number): number {
let terminalIndex = -1;
this.terminalInstances.forEach((terminalInstance, i) => {
if (terminalInstance.id === terminalId) {
terminalIndex = i;
}
});
if (terminalIndex === -1) {
throw new Error(`Terminal with ID ${terminalId} does not exist (has it already been disposed?)`);
}
return terminalIndex;
}
public async manageWorkspaceShellPermissions(): Promise<void> {
const allowItem: IQuickPickItem = { label: nls.localize('workbench.action.terminal.allowWorkspaceShell', "Allow Workspace Shell Configuration") };
const disallowItem: IQuickPickItem = { label: nls.localize('workbench.action.terminal.disallowWorkspaceShell', "Disallow Workspace Shell Configuration") };
const value = await this._quickInputService.pick([allowItem, disallowItem], { canPickMany: false });
if (!value) {
return;
}
this.configHelper.setWorkspaceShellAllowed(value === allowItem);
}
protected async _showTerminalCloseConfirmation(): Promise<boolean> {
let message: string;
if (this.terminalInstances.length === 1) {
message = nls.localize('terminalService.terminalCloseConfirmationSingular', "There is an active terminal session, do you want to kill it?");
} else {
message = nls.localize('terminalService.terminalCloseConfirmationPlural', "There are {0} active terminal sessions, do you want to kill them?", this.terminalInstances.length);
}
const res = await this._dialogService.confirm({
message,
type: 'warning',
});
return !res.confirmed;
}
protected _showNotEnoughSpaceToast(): void {
this._notificationService.info(nls.localize('terminal.minWidth', "Not enough space to split terminal."));
}
protected _validateShellPaths(label: string, potentialPaths: string[]): Promise<[string, string] | null> {
if (potentialPaths.length === 0) {
return Promise.resolve(null);
}
const current = potentialPaths.shift();
if (current! === '') {
return this._validateShellPaths(label, potentialPaths);
}
return this._fileService.exists(URI.file(current!)).then(exists => {
if (!exists) {
return this._validateShellPaths(label, potentialPaths);
}
return [label, current] as [string, string];
});
}
public preparePathForTerminalAsync(originalPath: string, executable: string, title: string): Promise<string> {
return new Promise<string>(c => {
if (!executable) {
c(originalPath);
return;
}
const hasSpace = originalPath.indexOf(' ') !== -1;
const pathBasename = basename(executable, '.exe');
const isPowerShell = pathBasename === 'pwsh' ||
title === 'pwsh' ||
pathBasename === 'powershell' ||
title === 'powershell';
if (isPowerShell && (hasSpace || originalPath.indexOf('\'') !== -1)) {
c(`& '${originalPath.replace(/'/g, '\'\'')}'`);
return;
}
if (isWindows) {
// 17063 is the build number where wsl path was introduced.
// Update Windows uriPath to be executed in WSL.
const lowerExecutable = executable.toLowerCase();
if (this._terminalNativeService.getWindowsBuildNumber() >= 17063 &&
(lowerExecutable.indexOf('wsl') !== -1 || (lowerExecutable.indexOf('bash.exe') !== -1 && lowerExecutable.toLowerCase().indexOf('git') === -1))) {
c(this._terminalNativeService.getWslPath(originalPath));
return;
} else if (hasSpace) {
c('"' + originalPath + '"');
} else {
c(originalPath);
}
return;
}
c(escapeNonWindowsPath(originalPath));
});
}
public selectDefaultWindowsShell(): Promise<void> {
return this._detectWindowsShells().then(shells => {
const options: IPickOptions<IQuickPickItem> = {
placeHolder: nls.localize('terminal.integrated.chooseWindowsShell', "Select your preferred terminal shell, you can change this later in your settings")
};
const quickPickItems = shells.map((s): IQuickPickItem => {
return { label: s.label, description: s.path };
});
return this._quickInputService.pick(quickPickItems, options).then(async value => {
if (!value) {
return undefined;
}
const shell = value.description;
const env = await this._remoteAgentService.getEnvironment();
let platformKey: string;
if (env) {
platformKey = env.os === OperatingSystem.Windows ? 'windows' : (env.os === OperatingSystem.Macintosh ? 'osx' : 'linux');
} else {
platformKey = isWindows ? 'windows' : (isMacintosh ? 'osx' : 'linux');
}
await this._configurationService.updateValue(`terminal.integrated.shell.${platformKey}`, shell, ConfigurationTarget.USER).then(() => shell);
return Promise.resolve();
});
});
}
private _detectWindowsShells(): Promise<IShellDefinition[]> {
return new Promise(r => this._onRequestAvailableShells.fire(r));
}
public createInstance(container: HTMLElement | undefined, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance {
const instance = this._instantiationService.createInstance(TerminalInstance, this._terminalFocusContextKey, this._configHelper, container, shellLaunchConfig);
this._onInstanceCreated.fire(instance);

View File

@@ -4,13 +4,14 @@
*--------------------------------------------------------------------------------------------*/
import * as aria from 'vs/base/browser/ui/aria/aria';
import * as nls from 'vs/nls';
import { ITerminalInstance, IShellLaunchConfig, ITerminalTab, Direction, ITerminalService, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal';
import { IShellLaunchConfig, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal';
import { IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { Event, Emitter } from 'vs/base/common/event';
import { IDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { SplitView, Orientation, IView, Sizing } from 'vs/base/browser/ui/splitview/splitview';
import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITerminalInstance, Direction, ITerminalTab, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
const SPLIT_PANE_MIN_SIZE = 120;
const TERMINAL_MIN_USEFUL_SIZE = 250;

View File

@@ -0,0 +1,34 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export interface XTermCore {
_onScroll: IEventEmitter<number>;
_onKey: IEventEmitter<{ key: string }>;
_charSizeService: {
width: number;
height: number;
};
_coreService: {
triggerDataEvent(data: string, wasUserInput?: boolean): void;
};
_renderService: {
_renderer: {
_renderLayers: any[];
};
_onIntersectionChange: any;
};
// TODO: Remove below once a synchronous write API is added
// The below are only used in tests
writeBuffer: string[];
_innerWrite(): void;
}
export interface IEventEmitter<T> {
fire(e: T): void;
}

View File

@@ -6,10 +6,9 @@
import * as nls from 'vs/nls';
import { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { RawContextKey, ContextKeyExpr, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { URI } from 'vs/base/common/uri';
import { FindReplaceState } from 'vs/editor/contrib/find/findState';
import { OperatingSystem } from 'vs/base/common/platform';
import { IOpenFileRequest } from 'vs/platform/windows/common/windows';
@@ -48,7 +47,6 @@ export const NEVER_MEASURE_RENDER_TIME_STORAGE_KEY = 'terminal.integrated.neverM
// trying to create the corressponding object on the ext host.
export const EXT_HOST_CREATION_DELAY = 100;
export const ITerminalService = createDecorator<ITerminalService>('terminalService');
export const ITerminalNativeService = createDecorator<ITerminalNativeService>('terminalNativeService');
export const TerminalCursorStyle = {
@@ -100,7 +98,7 @@ export interface ITerminalConfiguration {
fontSize: number;
letterSpacing: number;
lineHeight: number;
setLocaleVariables: boolean;
detectLocale: 'auto' | 'off' | 'on';
scrollback: number;
commandsToSkipShell: string[];
cwd: string;
@@ -217,94 +215,11 @@ export interface IShellLaunchConfig {
hideFromUser?: boolean;
}
export interface ITerminalService {
_serviceBrand: any;
activeTabIndex: number;
configHelper: ITerminalConfigHelper;
terminalInstances: ITerminalInstance[];
terminalTabs: ITerminalTab[];
onActiveTabChanged: Event<void>;
onTabDisposed: Event<ITerminalTab>;
onInstanceCreated: Event<ITerminalInstance>;
onInstanceDisposed: Event<ITerminalInstance>;
onInstanceProcessIdReady: Event<ITerminalInstance>;
onInstanceDimensionsChanged: Event<ITerminalInstance>;
onInstanceMaximumDimensionsChanged: Event<ITerminalInstance>;
onInstanceRequestSpawnExtHostProcess: Event<ISpawnExtHostProcessRequest>;
onInstanceRequestStartExtensionTerminal: Event<IStartExtensionTerminalRequest>;
onInstancesChanged: Event<void>;
onInstanceTitleChanged: Event<ITerminalInstance>;
onActiveInstanceChanged: Event<ITerminalInstance | undefined>;
onRequestAvailableShells: Event<IAvailableShellsRequest>;
/**
* Creates a terminal.
* @param shell The shell launch configuration to use.
*/
createTerminal(shell?: IShellLaunchConfig): ITerminalInstance;
/**
* Creates a raw terminal instance, this should not be used outside of the terminal part.
*/
// tslint:disable-next-line: no-dom-globals
createInstance(container: HTMLElement | undefined, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance;
getInstanceFromId(terminalId: number): ITerminalInstance | undefined;
getInstanceFromIndex(terminalIndex: number): ITerminalInstance;
getTabLabels(): string[];
getActiveInstance(): ITerminalInstance | null;
setActiveInstance(terminalInstance: ITerminalInstance): void;
setActiveInstanceByIndex(terminalIndex: number): void;
getActiveOrCreateInstance(wasNewTerminalAction?: boolean): ITerminalInstance;
splitInstance(instance: ITerminalInstance, shell?: IShellLaunchConfig): ITerminalInstance | null;
getActiveTab(): ITerminalTab | null;
setActiveTabToNext(): void;
setActiveTabToPrevious(): void;
setActiveTabByIndex(tabIndex: number): void;
/**
* Fire the onActiveTabChanged event, this will trigger the terminal dropdown to be updated,
* among other things.
*/
refreshActiveTab(): void;
showPanel(focus?: boolean): Promise<void>;
hidePanel(): void;
focusFindWidget(): Promise<void>;
hideFindWidget(): void;
getFindState(): FindReplaceState;
findNext(): void;
findPrevious(): void;
selectDefaultWindowsShell(): Promise<void>;
// tslint:disable-next-line: no-dom-globals
setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void;
manageWorkspaceShellPermissions(): void;
/**
* Takes a path and returns the properly escaped path to send to the terminal.
* On Windows, this included trying to prepare the path for WSL if needed.
*
* @param executable The executable off the shellLaunchConfig
* @param title The terminal's title
* @param path The path to be escaped and formatted.
* @returns An escaped version of the path to be execuded in the terminal.
*/
preparePathForTerminalAsync(path: string, executable: string | undefined, title: string): Promise<string>;
extHostReady(remoteAuthority: string): void;
requestSpawnExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number, isWorkspaceShellAllowed: boolean): void;
requestStartExtensionTerminal(proxy: ITerminalProcessExtHostProxy, cols: number, rows: number): void;
}
/**
* Provides access to native or electron APIs to other terminal services.
*/
export interface ITerminalNativeService {
_serviceBrand: any;
_serviceBrand: undefined;
readonly linuxDistro: LinuxDistro;
@@ -321,32 +236,6 @@ export interface IShellDefinition {
path: string;
}
export const enum Direction {
Left = 0,
Right = 1,
Up = 2,
Down = 3
}
export interface ITerminalTab {
activeInstance: ITerminalInstance | null;
terminalInstances: ITerminalInstance[];
title: string;
onDisposed: Event<ITerminalTab>;
onInstancesChanged: Event<void>;
focusPreviousPane(): void;
focusNextPane(): void;
resizePane(direction: Direction): void;
setActiveInstanceByIndex(index: number): void;
// tslint:disable-next-line: no-dom-globals
attachToElement(element: HTMLElement): void;
setVisible(visible: boolean): void;
layout(width: number, height: number): void;
addDisposable(disposable: IDisposable): void;
split(terminalFocusContextKey: IContextKey<boolean>, configHelper: ITerminalConfigHelper, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance | undefined;
}
export interface ITerminalDimensions {
/**
* The columns of the terminal.
@@ -359,302 +248,6 @@ export interface ITerminalDimensions {
readonly rows: number;
}
interface ISearchOptions {
/**
* Whether the find should be done as a regex.
*/
regex?: boolean;
/**
* Whether only whole words should match.
*/
wholeWord?: boolean;
/**
* Whether find should pay attention to case.
*/
caseSensitive?: boolean;
/**
* Whether the search should start at the current search position (not the next row)
*/
incremental?: boolean;
}
export interface ITerminalInstance {
/**
* The ID of the terminal instance, this is an arbitrary number only used to identify the
* terminal instance.
*/
readonly id: number;
readonly cols: number;
readonly rows: number;
readonly maxCols: number;
readonly maxRows: number;
/**
* The process ID of the shell process, this is undefined when there is no process associated
* with this terminal.
*/
processId: number | undefined;
/**
* An event that fires when the terminal instance's title changes.
*/
onTitleChanged: Event<ITerminalInstance>;
/**
* An event that fires when the terminal instance is disposed.
*/
onDisposed: Event<ITerminalInstance>;
onFocused: Event<ITerminalInstance>;
onProcessIdReady: Event<ITerminalInstance>;
onRequestExtHostProcess: Event<ITerminalInstance>;
onDimensionsChanged: Event<void>;
onMaximumDimensionsChanged: Event<void>;
onFocus: Event<ITerminalInstance>;
/**
* Attach a listener to the raw data stream coming from the pty, including ANSI escape
* sequences.
*/
onData: Event<string>;
/**
* Attach a listener to listen for new lines added to this terminal instance.
*
* @param listener The listener function which takes new line strings added to the terminal,
* excluding ANSI escape sequences. The line event will fire when an LF character is added to
* the terminal (ie. the line is not wrapped). Note that this means that the line data will
* not fire for the last line, until either the line is ended with a LF character of the process
* is exited. The lineData string will contain the fully wrapped line, not containing any LF/CR
* characters.
*/
onLineData: Event<string>;
/**
* Attach a listener that fires when the terminal's pty process exits. The number in the event
* is the processes' exit code, an exit code of null means the process was killed as a result of
* the ITerminalInstance being disposed.
*/
onExit: Event<number>;
processReady: Promise<void>;
/**
* The title of the terminal. This is either title or the process currently running or an
* explicit name given to the terminal instance through the extension API.
*/
readonly title: string;
/**
* The focus state of the terminal before exiting.
*/
readonly hadFocusOnExit: boolean;
/**
* False when the title is set by an API or the user. We check this to make sure we
* do not override the title when the process title changes in the terminal.
*/
isTitleSetByProcess: boolean;
/**
* The shell launch config used to launch the shell.
*/
readonly shellLaunchConfig: IShellLaunchConfig;
/**
* Whether to disable layout for the terminal. This is useful when the size of the terminal is
* being manipulating (e.g. adding a split pane) and we want the terminal to ignore particular
* resize events.
*/
disableLayout: boolean;
/**
* An object that tracks when commands are run and enables navigating and selecting between
* them.
*/
readonly commandTracker: ICommandTracker | undefined;
readonly navigationMode: INavigationMode | undefined;
/**
* Dispose the terminal instance, removing it from the panel/service and freeing up resources.
*
* @param immediate Whether the kill should be immediate or not. Immediate should only be used
* when VS Code is shutting down or in cases where the terminal dispose was user initiated.
* The immediate===false exists to cover an edge case where the final output of the terminal can
* get cut off. If immediate kill any terminal processes immediately.
*/
dispose(immediate?: boolean): void;
/**
* Forces the terminal to redraw its viewport.
*/
forceRedraw(): void;
/**
* Registers a link matcher, allowing custom link patterns to be matched and handled.
* @param regex The regular expression the search for, specifically this searches the
* textContent of the rows. You will want to use \s to match a space ' ' character for example.
* @param handler The callback when the link is called.
* @param matchIndex The index of the link from the regex.match(html) call. This defaults to 0
* (for regular expressions without capture groups).
* @param validationCallback A callback which can be used to validate the link after it has been
* added to the DOM.
* @return The ID of the new matcher, this can be used to deregister.
*/
registerLinkMatcher(regex: RegExp, handler: (url: string) => void, matchIndex?: number, validationCallback?: (uri: string, callback: (isValid: boolean) => void) => void): number;
/**
* Deregisters a link matcher if it has been registered.
* @param matcherId The link matcher's ID (returned after register)
* @return Whether a link matcher was found and deregistered.
*/
deregisterLinkMatcher(matcherId: number): void;
/**
* Check if anything is selected in terminal.
*/
hasSelection(): boolean;
/**
* Copies the terminal selection to the clipboard.
*/
copySelection(): Promise<void>;
/**
* Current selection in the terminal.
*/
readonly selection: string | undefined;
/**
* Clear current selection.
*/
clearSelection(): void;
/**
* Select all text in the terminal.
*/
selectAll(): void;
/**
* Find the next instance of the term
*/
findNext(term: string, searchOptions: ISearchOptions): boolean;
/**
* Find the previous instance of the term
*/
findPrevious(term: string, searchOptions: ISearchOptions): boolean;
/**
* Notifies the terminal that the find widget's focus state has been changed.
*/
notifyFindWidgetFocusChanged(isFocused: boolean): void;
/**
* Focuses the terminal instance if it's able to (xterm.js instance exists).
*
* @param focus Force focus even if there is a selection.
*/
focus(force?: boolean): void;
/**
* Focuses the terminal instance when it's ready (the xterm.js instance is created). Use this
* when the terminal is being shown.
*
* @param focus Force focus even if there is a selection.
*/
focusWhenReady(force?: boolean): Promise<void>;
/**
* Focuses and pastes the contents of the clipboard into the terminal instance.
*/
paste(): Promise<void>;
/**
* Send text to the terminal instance. The text is written to the stdin of the underlying pty
* process (shell) of the terminal instance.
*
* @param text The text to send.
* @param addNewLine Whether to add a new line to the text being sent, this is normally
* required to run a command in the terminal. The character(s) added are \n or \r\n
* depending on the platform. This defaults to `true`.
*/
sendText(text: string, addNewLine: boolean): void;
/**
* Write text directly to the terminal, skipping the process if it exists.
* @param text The text to write.
*/
write(text: string): void;
/** Scroll the terminal buffer down 1 line. */
scrollDownLine(): void;
/** Scroll the terminal buffer down 1 page. */
scrollDownPage(): void;
/** Scroll the terminal buffer to the bottom. */
scrollToBottom(): void;
/** Scroll the terminal buffer up 1 line. */
scrollUpLine(): void;
/** Scroll the terminal buffer up 1 page. */
scrollUpPage(): void;
/** Scroll the terminal buffer to the top. */
scrollToTop(): void;
/**
* Clears the terminal buffer, leaving only the prompt line.
*/
clear(): void;
/**
* Attaches the terminal instance to an element on the DOM, before this is called the terminal
* instance process may run in the background but cannot be displayed on the UI.
*
* @param container The element to attach the terminal instance to.
*/
// tslint:disable-next-line: no-dom-globals
attachToElement(container: HTMLElement): void;
/**
* Configure the dimensions of the terminal instance.
*
* @param dimension The dimensions of the container.
*/
layout(dimension: { width: number, height: number }): void;
/**
* Sets whether the terminal instance's element is visible in the DOM.
*
* @param visible Whether the element is visible.
*/
setVisible(visible: boolean): void;
/**
* Immediately kills the terminal's current pty process and launches a new one to replace it.
*
* @param shell The new launch configuration.
*/
reuseTerminal(shell: IShellLaunchConfig): void;
/**
* Sets the title of the terminal instance.
*/
setTitle(title: string, eventSource: TitleEventSource): void;
waitForTitle(): Promise<string>;
setDimensions(dimensions: ITerminalDimensions): void;
addDisposable(disposable: IDisposable): void;
toggleEscapeSequenceLogging(): void;
getInitialCwd(): Promise<string>;
getCwd(): Promise<string>;
}
export interface ICommandTracker {
scrollToPreviousCommand(): void;
scrollToNextCommand(): void;
@@ -813,3 +406,69 @@ export interface ITerminalChildProcess {
getCwd(): Promise<string>;
getLatency(): Promise<number>;
}
export const enum TERMINAL_COMMAND_ID {
FIND_NEXT = 'workbench.action.terminal.findNext',
FIND_NEXT_TERMINAL_FOCUS = 'workbench.action.terminal.findNextTerminalFocus',
FIND_PREVIOUS = 'workbench.action.terminal.findPrevious',
FIND_PREVIOUS_TERMINAL_FOCUS = 'workbench.action.terminal.findPreviousTerminalFocus',
TOGGLE = 'workbench.action.terminal.toggleTerminal',
KILL = 'workbench.action.terminal.kill',
QUICK_KILL = 'workbench.action.terminal.quickKill',
COPY_SELECTION = 'workbench.action.terminal.copySelection',
SELECT_ALL = 'workbench.action.terminal.selectAll',
DELETE_WORD_LEFT = 'workbench.action.terminal.deleteWordLeft',
DELETE_WORD_RIGHT = 'workbench.action.terminal.deleteWordRight',
DELETE_TO_LINE_START = 'workbench.action.terminal.deleteToLineStart',
MOVE_TO_LINE_START = 'workbench.action.terminal.moveToLineStart',
MOVE_TO_LINE_END = 'workbench.action.terminal.moveToLineEnd',
NEW = 'workbench.action.terminal.new',
NEW_LOCAL = 'workbench.action.terminal.newLocal',
NEW_IN_ACTIVE_WORKSPACE = 'workbench.action.terminal.newInActiveWorkspace',
SPLIT = 'workbench.action.terminal.split',
SPLIT_IN_ACTIVE_WORKSPACE = 'workbench.action.terminal.splitInActiveWorkspace',
FOCUS_PREVIOUS_PANE = 'workbench.action.terminal.focusPreviousPane',
FOCUS_NEXT_PANE = 'workbench.action.terminal.focusNextPane',
RESIZE_PANE_LEFT = 'workbench.action.terminal.resizePaneLeft',
RESIZE_PANE_RIGHT = 'workbench.action.terminal.resizePaneRight',
RESIZE_PANE_UP = 'workbench.action.terminal.resizePaneUp',
RESIZE_PANE_DOWN = 'workbench.action.terminal.resizePaneDown',
FOCUS = 'workbench.action.terminal.focus',
FOCUS_NEXT = 'workbench.action.terminal.focusNext',
FOCUS_PREVIOUS = 'workbench.action.terminal.focusPrevious',
PASTE = 'workbench.action.terminal.paste',
SELECT_DEFAULT_SHELL = 'workbench.action.terminal.selectDefaultShell',
RUN_SELECTED_TEXT = 'workbench.action.terminal.runSelectedText',
RUN_ACTIVE_FILE = 'workbench.action.terminal.runActiveFile',
SWITCH_TERMINAL = 'workbench.action.terminal.switchTerminal',
SCROLL_DOWN_LINE = 'workbench.action.terminal.scrollDown',
SCROLL_DOWN_PAGE = 'workbench.action.terminal.scrollDownPage',
SCROLL_TO_BOTTOM = 'workbench.action.terminal.scrollToBottom',
SCROLL_UP_LINE = 'workbench.action.terminal.scrollUp',
SCROLL_UP_PAGE = 'workbench.action.terminal.scrollUpPage',
SCROLL_TO_TOP = 'workbench.action.terminal.scrollToTop',
CLEAR = 'workbench.action.terminal.clear',
CLEAR_SELECTION = 'workbench.action.terminal.clearSelection',
MANAGE_WORKSPACE_SHELL_PERMISSIONS = 'workbench.action.terminal.manageWorkspaceShellPermissions',
RENAME = 'workbench.action.terminal.rename',
FIND_WIDGET_FOCUS = 'workbench.action.terminal.focusFindWidget',
FIND_WIDGET_HIDE = 'workbench.action.terminal.hideFindWidget',
QUICK_OPEN_TERM = 'workbench.action.quickOpenTerm',
SCROLL_TO_PREVIOUS_COMMAND = 'workbench.action.terminal.scrollToPreviousCommand',
SCROLL_TO_NEXT_COMMAND = 'workbench.action.terminal.scrollToNextCommand',
SELECT_TO_PREVIOUS_COMMAND = 'workbench.action.terminal.selectToPreviousCommand',
SELECT_TO_NEXT_COMMAND = 'workbench.action.terminal.selectToNextCommand',
SELECT_TO_PREVIOUS_LINE = 'workbench.action.terminal.selectToPreviousLine',
SELECT_TO_NEXT_LINE = 'workbench.action.terminal.selectToNextLine',
TOGGLE_ESCAPE_SEQUENCE_LOGGING = 'toggleEscapeSequenceLogging',
SEND_SEQUENCE = 'workbench.action.terminal.sendSequence',
TOGGLE_FIND_REGEX = 'workbench.action.terminal.toggleFindRegex',
TOGGLE_FIND_WHOLE_WORD = 'workbench.action.terminal.toggleFindWholeWord',
TOGGLE_FIND_CASE_SENSITIVE = 'workbench.action.terminal.toggleFindCaseSensitive',
TOGGLE_FIND_REGEX_TERMINAL_FOCUS = 'workbench.action.terminal.toggleFindRegexTerminalFocus',
TOGGLE_FIND_WHOLE_WORD_TERMINAL_FOCUS = 'workbench.action.terminal.toggleFindWholeWordTerminalFocus',
TOGGLE_FIND_CASE_SENSITIVE_TERMINAL_FOCUS = 'workbench.action.terminal.toggleFindCaseSensitiveTerminalFocus',
NAVIGATION_MODE_EXIT = 'workbench.action.terminal.navigationModeExit',
NAVIGATION_MODE_FOCUS_NEXT = 'workbench.action.terminal.navigationModeFocusNext',
NAVIGATION_MODE_FOCUS_PREVIOUS = 'workbench.action.terminal.navigationModeFocusPrevious'
}

View File

@@ -1,96 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ITerminalService } from 'vs/workbench/contrib/terminal/common/terminal';
export const enum TERMINAL_COMMAND_ID {
FIND_NEXT = 'workbench.action.terminal.findNext',
FIND_NEXT_TERMINAL_FOCUS = 'workbench.action.terminal.findNextTerminalFocus',
FIND_PREVIOUS = 'workbench.action.terminal.findPrevious',
FIND_PREVIOUS_TERMINAL_FOCUS = 'workbench.action.terminal.findPreviousTerminalFocus',
TOGGLE = 'workbench.action.terminal.toggleTerminal',
KILL = 'workbench.action.terminal.kill',
QUICK_KILL = 'workbench.action.terminal.quickKill',
COPY_SELECTION = 'workbench.action.terminal.copySelection',
SELECT_ALL = 'workbench.action.terminal.selectAll',
DELETE_WORD_LEFT = 'workbench.action.terminal.deleteWordLeft',
DELETE_WORD_RIGHT = 'workbench.action.terminal.deleteWordRight',
DELETE_TO_LINE_START = 'workbench.action.terminal.deleteToLineStart',
MOVE_TO_LINE_START = 'workbench.action.terminal.moveToLineStart',
MOVE_TO_LINE_END = 'workbench.action.terminal.moveToLineEnd',
NEW = 'workbench.action.terminal.new',
NEW_LOCAL = 'workbench.action.terminal.newLocal',
NEW_IN_ACTIVE_WORKSPACE = 'workbench.action.terminal.newInActiveWorkspace',
SPLIT = 'workbench.action.terminal.split',
SPLIT_IN_ACTIVE_WORKSPACE = 'workbench.action.terminal.splitInActiveWorkspace',
FOCUS_PREVIOUS_PANE = 'workbench.action.terminal.focusPreviousPane',
FOCUS_NEXT_PANE = 'workbench.action.terminal.focusNextPane',
RESIZE_PANE_LEFT = 'workbench.action.terminal.resizePaneLeft',
RESIZE_PANE_RIGHT = 'workbench.action.terminal.resizePaneRight',
RESIZE_PANE_UP = 'workbench.action.terminal.resizePaneUp',
RESIZE_PANE_DOWN = 'workbench.action.terminal.resizePaneDown',
FOCUS = 'workbench.action.terminal.focus',
FOCUS_NEXT = 'workbench.action.terminal.focusNext',
FOCUS_PREVIOUS = 'workbench.action.terminal.focusPrevious',
PASTE = 'workbench.action.terminal.paste',
SELECT_DEFAULT_SHELL = 'workbench.action.terminal.selectDefaultShell',
RUN_SELECTED_TEXT = 'workbench.action.terminal.runSelectedText',
RUN_ACTIVE_FILE = 'workbench.action.terminal.runActiveFile',
SWITCH_TERMINAL = 'workbench.action.terminal.switchTerminal',
SCROLL_DOWN_LINE = 'workbench.action.terminal.scrollDown',
SCROLL_DOWN_PAGE = 'workbench.action.terminal.scrollDownPage',
SCROLL_TO_BOTTOM = 'workbench.action.terminal.scrollToBottom',
SCROLL_UP_LINE = 'workbench.action.terminal.scrollUp',
SCROLL_UP_PAGE = 'workbench.action.terminal.scrollUpPage',
SCROLL_TO_TOP = 'workbench.action.terminal.scrollToTop',
CLEAR = 'workbench.action.terminal.clear',
CLEAR_SELECTION = 'workbench.action.terminal.clearSelection',
MANAGE_WORKSPACE_SHELL_PERMISSIONS = 'workbench.action.terminal.manageWorkspaceShellPermissions',
RENAME = 'workbench.action.terminal.rename',
FIND_WIDGET_FOCUS = 'workbench.action.terminal.focusFindWidget',
FIND_WIDGET_HIDE = 'workbench.action.terminal.hideFindWidget',
QUICK_OPEN_TERM = 'workbench.action.quickOpenTerm',
SCROLL_TO_PREVIOUS_COMMAND = 'workbench.action.terminal.scrollToPreviousCommand',
SCROLL_TO_NEXT_COMMAND = 'workbench.action.terminal.scrollToNextCommand',
SELECT_TO_PREVIOUS_COMMAND = 'workbench.action.terminal.selectToPreviousCommand',
SELECT_TO_NEXT_COMMAND = 'workbench.action.terminal.selectToNextCommand',
SELECT_TO_PREVIOUS_LINE = 'workbench.action.terminal.selectToPreviousLine',
SELECT_TO_NEXT_LINE = 'workbench.action.terminal.selectToNextLine',
TOGGLE_ESCAPE_SEQUENCE_LOGGING = 'toggleEscapeSequenceLogging',
SEND_SEQUENCE = 'workbench.action.terminal.sendSequence',
TOGGLE_FIND_REGEX = 'workbench.action.terminal.toggleFindRegex',
TOGGLE_FIND_WHOLE_WORD = 'workbench.action.terminal.toggleFindWholeWord',
TOGGLE_FIND_CASE_SENSITIVE = 'workbench.action.terminal.toggleFindCaseSensitive',
TOGGLE_FIND_REGEX_TERMINAL_FOCUS = 'workbench.action.terminal.toggleFindRegexTerminalFocus',
TOGGLE_FIND_WHOLE_WORD_TERMINAL_FOCUS = 'workbench.action.terminal.toggleFindWholeWordTerminalFocus',
TOGGLE_FIND_CASE_SENSITIVE_TERMINAL_FOCUS = 'workbench.action.terminal.toggleFindCaseSensitiveTerminalFocus',
NAVIGATION_MODE_EXIT = 'workbench.action.terminal.navigationModeExit',
NAVIGATION_MODE_FOCUS_NEXT = 'workbench.action.terminal.navigationModeFocusNext',
NAVIGATION_MODE_FOCUS_PREVIOUS = 'workbench.action.terminal.navigationModeFocusPrevious'
}
export function setupTerminalCommands(): void {
registerOpenTerminalAtIndexCommands();
}
function registerOpenTerminalAtIndexCommands(): void {
for (let i = 0; i < 9; i++) {
const terminalIndex = i;
const visibleIndex = i + 1;
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: `workbench.action.terminal.focusAtIndex${visibleIndex}`,
weight: KeybindingWeight.WorkbenchContrib,
when: undefined,
primary: 0,
handler: accessor => {
const terminalService = accessor.get(ITerminalService);
terminalService.setActiveInstanceByIndex(terminalIndex);
return terminalService.showPanel(true);
}
});
}
}

View File

@@ -51,13 +51,13 @@ function _mergeEnvironmentValue(env: ITerminalEnvironment, key: string, value: s
}
}
export function addTerminalEnvironmentKeys(env: platform.IProcessEnvironment, version: string | undefined, locale: string | undefined, setLocaleVariables: boolean): void {
export function addTerminalEnvironmentKeys(env: platform.IProcessEnvironment, version: string | undefined, locale: string | undefined, detectLocale: 'auto' | 'off' | 'on'): void {
env['TERM_PROGRAM'] = 'vscode';
if (version) {
env['TERM_PROGRAM_VERSION'] = version;
}
if (setLocaleVariables) {
env['LANG'] = _getLangEnvVariable(locale);
if (shouldSetLangEnvVariable(env, detectLocale)) {
env['LANG'] = getLangEnvVariable(locale);
}
env['COLORTERM'] = 'truecolor';
}
@@ -88,37 +88,87 @@ function resolveConfigurationVariables(configurationResolverService: IConfigurat
return env;
}
function _getLangEnvVariable(locale?: string) {
export function shouldSetLangEnvVariable(env: platform.IProcessEnvironment, detectLocale: 'auto' | 'off' | 'on'): boolean {
if (detectLocale === 'on') {
return true;
}
if (detectLocale === 'auto') {
return !env['LANG'] || env['LANG'].search(/\.UTF\-8$/) === -1;
}
return false; // 'off'
}
export function getLangEnvVariable(locale?: string): string {
const parts = locale ? locale.split('-') : [];
const n = parts.length;
if (n === 0) {
// Fallback to en_US to prevent possible encoding issues.
// Fallback to en_US if the locale is unknown
return 'en_US.UTF-8';
}
if (n === 1) {
// app.getLocale can return just a language without a variant, fill in the variant for
// supported languages as many shells expect a 2-part locale.
// The local may only contain the language, not the variant, if this is the case guess the
// variant such that it can be used as a valid $LANG variable. The language variant chosen
// is the original and/or most prominent with help from
// https://stackoverflow.com/a/2502675/1156119
// The list of locales was generated by running `locale -a` on macOS
const languageVariants: { [key: string]: string } = {
af: 'ZA',
am: 'ET',
be: 'BY',
bg: 'BG',
ca: 'ES',
cs: 'CZ',
da: 'DK',
// de: 'AT',
// de: 'CH',
de: 'DE',
el: 'GR',
// en: 'AU',
// en: 'CA',
// en: 'GB',
// en: 'IE',
// en: 'NZ',
en: 'US',
es: 'ES',
et: 'EE',
eu: 'ES',
fi: 'FI',
// fr: 'BE',
// fr: 'CA',
// fr: 'CH',
fr: 'FR',
he: 'IL',
hr: 'HR',
hu: 'HU',
hy: 'AM',
is: 'IS',
// it: 'CH',
it: 'IT',
ja: 'JP',
kk: 'KZ',
ko: 'KR',
lt: 'LT',
// nl: 'BE',
nl: 'NL',
no: 'NO',
pl: 'PL',
pt: 'BR',
// pt: 'PT',
ro: 'RO',
ru: 'RU',
sk: 'SK',
zh: 'CN'
sl: 'SI',
sr: 'YU',
sv: 'SE',
tr: 'TR',
uk: 'UA',
zh: 'CN',
};
if (parts[0] in languageVariants) {
parts.push(languageVariants[parts[0]]);
}
} else {
// Ensure the variant is uppercase
// Ensure the variant is uppercase to be a valid $LANG
parts[1] = parts[1].toUpperCase();
}
return parts.join('_') + '.UTF-8';
@@ -294,7 +344,7 @@ export function createTerminalEnvironment(
configurationResolverService: IConfigurationResolverService | undefined,
isWorkspaceShellAllowed: boolean,
version: string | undefined,
setLocaleVariables: boolean,
detectLocale: 'auto' | 'off' | 'on',
baseEnv: platform.IProcessEnvironment
): platform.IProcessEnvironment {
// Create a terminal environment based on settings, launch config and permissions
@@ -329,7 +379,7 @@ export function createTerminalEnvironment(
mergeEnvironments(env, shellLaunchConfig.env);
// Adding other env keys necessary to create the process
addTerminalEnvironmentKeys(env, version, platform.locale, setLocaleVariables);
addTerminalEnvironmentKeys(env, version, platform.locale, detectLocale);
}
return env;
}

View File

@@ -5,7 +5,7 @@
import * as nls from 'vs/nls';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminalCommands';
import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
export function setupTerminalMenu() {

View File

@@ -1,595 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { Event, Emitter } from 'vs/base/common/event';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalConfigHelper, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, TERMINAL_PANEL_ID, ITerminalTab, ITerminalProcessExtHostProxy, ISpawnExtHostProcessRequest, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN, ITerminalNativeService, IShellDefinition, IAvailableShellsRequest, IStartExtensionTerminalRequest } from 'vs/workbench/contrib/terminal/common/terminal';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { URI } from 'vs/base/common/uri';
import { FindReplaceState } from 'vs/editor/contrib/find/findState';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IFileService } from 'vs/platform/files/common/files';
import { escapeNonWindowsPath } from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
import { isWindows, isMacintosh, OperatingSystem } from 'vs/base/common/platform';
import { basename } from 'vs/base/common/path';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IOpenFileRequest } from 'vs/platform/windows/common/windows';
import { IPickOptions, IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
interface IExtHostReadyEntry {
promise: Promise<void>;
resolve: () => void;
}
export abstract class TerminalService implements ITerminalService {
public _serviceBrand: any;
protected _isShuttingDown: boolean;
protected _terminalFocusContextKey: IContextKey<boolean>;
protected _findWidgetVisible: IContextKey<boolean>;
protected _terminalTabs: ITerminalTab[] = [];
protected _backgroundedTerminalInstances: ITerminalInstance[] = [];
protected get _terminalInstances(): ITerminalInstance[] {
return this._terminalTabs.reduce((p, c) => p.concat(c.terminalInstances), <ITerminalInstance[]>[]);
}
private _findState: FindReplaceState;
private _extHostsReady: { [authority: string]: IExtHostReadyEntry | undefined } = {};
private _activeTabIndex: number;
public get activeTabIndex(): number { return this._activeTabIndex; }
public get terminalInstances(): ITerminalInstance[] { return this._terminalInstances; }
public get terminalTabs(): ITerminalTab[] { return this._terminalTabs; }
protected readonly _onActiveTabChanged = new Emitter<void>();
public get onActiveTabChanged(): Event<void> { return this._onActiveTabChanged.event; }
protected readonly _onInstanceCreated = new Emitter<ITerminalInstance>();
public get onInstanceCreated(): Event<ITerminalInstance> { return this._onInstanceCreated.event; }
protected readonly _onInstanceDisposed = new Emitter<ITerminalInstance>();
public get onInstanceDisposed(): Event<ITerminalInstance> { return this._onInstanceDisposed.event; }
protected readonly _onInstanceProcessIdReady = new Emitter<ITerminalInstance>();
public get onInstanceProcessIdReady(): Event<ITerminalInstance> { return this._onInstanceProcessIdReady.event; }
protected readonly _onInstanceRequestSpawnExtHostProcess = new Emitter<ISpawnExtHostProcessRequest>();
public get onInstanceRequestSpawnExtHostProcess(): Event<ISpawnExtHostProcessRequest> { return this._onInstanceRequestSpawnExtHostProcess.event; }
protected readonly _onInstanceRequestStartExtensionTerminal = new Emitter<IStartExtensionTerminalRequest>();
public get onInstanceRequestStartExtensionTerminal(): Event<IStartExtensionTerminalRequest> { return this._onInstanceRequestStartExtensionTerminal.event; }
protected readonly _onInstanceDimensionsChanged = new Emitter<ITerminalInstance>();
public get onInstanceDimensionsChanged(): Event<ITerminalInstance> { return this._onInstanceDimensionsChanged.event; }
protected readonly _onInstanceMaximumDimensionsChanged = new Emitter<ITerminalInstance>();
public get onInstanceMaximumDimensionsChanged(): Event<ITerminalInstance> { return this._onInstanceMaximumDimensionsChanged.event; }
protected readonly _onInstancesChanged = new Emitter<void>();
public get onInstancesChanged(): Event<void> { return this._onInstancesChanged.event; }
protected readonly _onInstanceTitleChanged = new Emitter<ITerminalInstance>();
public get onInstanceTitleChanged(): Event<ITerminalInstance> { return this._onInstanceTitleChanged.event; }
protected readonly _onActiveInstanceChanged = new Emitter<ITerminalInstance | undefined>();
public get onActiveInstanceChanged(): Event<ITerminalInstance | undefined> { return this._onActiveInstanceChanged.event; }
protected readonly _onTabDisposed = new Emitter<ITerminalTab>();
public get onTabDisposed(): Event<ITerminalTab> { return this._onTabDisposed.event; }
protected readonly _onRequestAvailableShells = new Emitter<IAvailableShellsRequest>();
public get onRequestAvailableShells(): Event<IAvailableShellsRequest> { return this._onRequestAvailableShells.event; }
public abstract get configHelper(): ITerminalConfigHelper;
constructor(
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IPanelService protected readonly _panelService: IPanelService,
@ILifecycleService readonly lifecycleService: ILifecycleService,
@IStorageService protected readonly _storageService: IStorageService,
@INotificationService protected readonly _notificationService: INotificationService,
@IDialogService private readonly _dialogService: IDialogService,
@IExtensionService private readonly _extensionService: IExtensionService,
@IFileService protected readonly _fileService: IFileService,
@IRemoteAgentService readonly _remoteAgentService: IRemoteAgentService,
@ITerminalNativeService private readonly _terminalNativeService: ITerminalNativeService,
@IQuickInputService private readonly _quickInputService: IQuickInputService,
@IConfigurationService private readonly _configurationService: IConfigurationService
) {
this._activeTabIndex = 0;
this._isShuttingDown = false;
this._findState = new FindReplaceState();
lifecycleService.onBeforeShutdown(event => event.veto(this._onBeforeShutdown()));
lifecycleService.onShutdown(() => this._onShutdown());
this._terminalNativeService.onOpenFileRequest(e => this._onOpenFileRequest(e));
this._terminalNativeService.onOsResume(() => this._onOsResume());
this._terminalFocusContextKey = KEYBINDING_CONTEXT_TERMINAL_FOCUS.bindTo(this._contextKeyService);
this._findWidgetVisible = KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE.bindTo(this._contextKeyService);
this.onTabDisposed(tab => this._removeTab(tab));
this.onActiveTabChanged(() => {
const instance = this.getActiveInstance();
this._onActiveInstanceChanged.fire(instance ? instance : undefined);
});
this._handleContextKeys();
}
private _handleContextKeys(): void {
const terminalIsOpenContext = KEYBINDING_CONTEXT_TERMINAL_IS_OPEN.bindTo(this._contextKeyService);
const updateTerminalContextKeys = () => {
terminalIsOpenContext.set(this.terminalInstances.length > 0);
};
this.onInstancesChanged(() => updateTerminalContextKeys());
}
protected abstract _showBackgroundTerminal(instance: ITerminalInstance): void;
public abstract createTerminal(shell?: IShellLaunchConfig, wasNewTerminalAction?: boolean): ITerminalInstance;
// tslint:disable-next-line: no-dom-globals
public abstract createInstance(container: HTMLElement, shellLaunchConfig: IShellLaunchConfig): ITerminalInstance;
// tslint:disable-next-line: no-dom-globals
public abstract setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void;
public getActiveOrCreateInstance(wasNewTerminalAction?: boolean): ITerminalInstance {
const activeInstance = this.getActiveInstance();
return activeInstance ? activeInstance : this.createTerminal(undefined, wasNewTerminalAction);
}
public requestSpawnExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number, isWorkspaceShellAllowed: boolean): void {
this._extensionService.whenInstalledExtensionsRegistered().then(async () => {
// Wait for the remoteAuthority to be ready (and listening for events) before firing
// the event to spawn the ext host process
const conn = this._remoteAgentService.getConnection();
const remoteAuthority = conn ? conn.remoteAuthority : 'null';
await this._whenExtHostReady(remoteAuthority);
this._onInstanceRequestSpawnExtHostProcess.fire({ proxy, shellLaunchConfig, activeWorkspaceRootUri, cols, rows, isWorkspaceShellAllowed });
});
}
public requestStartExtensionTerminal(proxy: ITerminalProcessExtHostProxy, cols: number, rows: number): void {
this._onInstanceRequestStartExtensionTerminal.fire({ proxy, cols, rows });
}
public async extHostReady(remoteAuthority: string): Promise<void> {
this._createExtHostReadyEntry(remoteAuthority);
this._extHostsReady[remoteAuthority]!.resolve();
}
private async _whenExtHostReady(remoteAuthority: string): Promise<void> {
this._createExtHostReadyEntry(remoteAuthority);
return this._extHostsReady[remoteAuthority]!.promise;
}
private _createExtHostReadyEntry(remoteAuthority: string): void {
if (this._extHostsReady[remoteAuthority]) {
return;
}
let resolve!: () => void;
const promise = new Promise<void>(r => resolve = r);
this._extHostsReady[remoteAuthority] = { promise, resolve };
}
private _onBeforeShutdown(): boolean | Promise<boolean> {
if (this.terminalInstances.length === 0) {
// No terminal instances, don't veto
return false;
}
if (this.configHelper.config.confirmOnExit) {
// veto if configured to show confirmation and the user choosed not to exit
return this._showTerminalCloseConfirmation().then(veto => {
if (!veto) {
this._isShuttingDown = true;
}
return veto;
});
}
this._isShuttingDown = true;
return false;
}
private _onShutdown(): void {
// Dispose of all instances
this.terminalInstances.forEach(instance => instance.dispose(true));
}
private _onOpenFileRequest(request: IOpenFileRequest): void {
// if the request to open files is coming in from the integrated terminal (identified though
// the termProgram variable) and we are instructed to wait for editors close, wait for the
// marker file to get deleted and then focus back to the integrated terminal.
if (request.termProgram === 'vscode' && request.filesToWait) {
const waitMarkerFileUri = URI.revive(request.filesToWait.waitMarkerFileUri);
this._terminalNativeService.whenFileDeleted(waitMarkerFileUri).then(() => {
if (this.terminalInstances.length > 0) {
const terminal = this.getActiveInstance();
if (terminal) {
terminal.focus();
}
}
});
}
}
private _onOsResume(): void {
const activeTab = this.getActiveTab();
if (!activeTab) {
return;
}
activeTab.terminalInstances.forEach(instance => instance.forceRedraw());
}
public getTabLabels(): string[] {
return this._terminalTabs.filter(tab => tab.terminalInstances.length > 0).map((tab, index) => `${index + 1}: ${tab.title ? tab.title : ''}`);
}
public getFindState(): FindReplaceState {
return this._findState;
}
private _removeTab(tab: ITerminalTab): void {
// Get the index of the tab and remove it from the list
const index = this._terminalTabs.indexOf(tab);
const wasActiveTab = tab === this.getActiveTab();
if (index !== -1) {
this._terminalTabs.splice(index, 1);
}
// Adjust focus if the tab was active
if (wasActiveTab && this._terminalTabs.length > 0) {
// TODO: Only focus the new tab if the removed tab had focus?
// const hasFocusOnExit = tab.activeInstance.hadFocusOnExit;
const newIndex = index < this._terminalTabs.length ? index : this._terminalTabs.length - 1;
this.setActiveTabByIndex(newIndex);
const activeInstance = this.getActiveInstance();
if (activeInstance) {
activeInstance.focus(true);
}
}
// Hide the panel if there are no more instances, provided that VS Code is not shutting
// down. When shutting down the panel is locked in place so that it is restored upon next
// launch.
if (this._terminalTabs.length === 0 && !this._isShuttingDown) {
this.hidePanel();
this._onActiveInstanceChanged.fire(undefined);
}
// Fire events
this._onInstancesChanged.fire();
if (wasActiveTab) {
this._onActiveTabChanged.fire();
}
}
public refreshActiveTab(): void {
// Fire active instances changed
this._onActiveTabChanged.fire();
}
public getActiveTab(): ITerminalTab | null {
if (this._activeTabIndex < 0 || this._activeTabIndex >= this._terminalTabs.length) {
return null;
}
return this._terminalTabs[this._activeTabIndex];
}
public getActiveInstance(): ITerminalInstance | null {
const tab = this.getActiveTab();
if (!tab) {
return null;
}
return tab.activeInstance;
}
public getInstanceFromId(terminalId: number): ITerminalInstance | undefined {
let bgIndex = -1;
this._backgroundedTerminalInstances.forEach((terminalInstance, i) => {
if (terminalInstance.id === terminalId) {
bgIndex = i;
}
});
if (bgIndex !== -1) {
return this._backgroundedTerminalInstances[bgIndex];
}
try {
return this.terminalInstances[this._getIndexFromId(terminalId)];
} catch {
return undefined;
}
}
public getInstanceFromIndex(terminalIndex: number): ITerminalInstance {
return this.terminalInstances[terminalIndex];
}
public setActiveInstance(terminalInstance: ITerminalInstance): void {
// If this was a hideFromUser terminal created by the API this was triggered by show,
// in which case we need to create the terminal tab
if (terminalInstance.shellLaunchConfig.hideFromUser) {
this._showBackgroundTerminal(terminalInstance);
}
this.setActiveInstanceByIndex(this._getIndexFromId(terminalInstance.id));
}
public setActiveTabByIndex(tabIndex: number): void {
if (tabIndex >= this._terminalTabs.length) {
return;
}
const didTabChange = this._activeTabIndex !== tabIndex;
this._activeTabIndex = tabIndex;
this._terminalTabs.forEach((t, i) => t.setVisible(i === this._activeTabIndex));
if (didTabChange) {
this._onActiveTabChanged.fire();
}
}
private _getInstanceFromGlobalInstanceIndex(index: number): { tab: ITerminalTab, tabIndex: number, instance: ITerminalInstance, localInstanceIndex: number } | null {
let currentTabIndex = 0;
while (index >= 0 && currentTabIndex < this._terminalTabs.length) {
const tab = this._terminalTabs[currentTabIndex];
const count = tab.terminalInstances.length;
if (index < count) {
return {
tab,
tabIndex: currentTabIndex,
instance: tab.terminalInstances[index],
localInstanceIndex: index
};
}
index -= count;
currentTabIndex++;
}
return null;
}
public setActiveInstanceByIndex(terminalIndex: number): void {
const query = this._getInstanceFromGlobalInstanceIndex(terminalIndex);
if (!query) {
return;
}
query.tab.setActiveInstanceByIndex(query.localInstanceIndex);
const didTabChange = this._activeTabIndex !== query.tabIndex;
this._activeTabIndex = query.tabIndex;
this._terminalTabs.forEach((t, i) => t.setVisible(i === query.tabIndex));
// Only fire the event if there was a change
if (didTabChange) {
this._onActiveTabChanged.fire();
}
}
public setActiveTabToNext(): void {
if (this._terminalTabs.length <= 1) {
return;
}
let newIndex = this._activeTabIndex + 1;
if (newIndex >= this._terminalTabs.length) {
newIndex = 0;
}
this.setActiveTabByIndex(newIndex);
}
public setActiveTabToPrevious(): void {
if (this._terminalTabs.length <= 1) {
return;
}
let newIndex = this._activeTabIndex - 1;
if (newIndex < 0) {
newIndex = this._terminalTabs.length - 1;
}
this.setActiveTabByIndex(newIndex);
}
public splitInstance(instanceToSplit: ITerminalInstance, shellLaunchConfig: IShellLaunchConfig = {}): ITerminalInstance | null {
const tab = this._getTabForInstance(instanceToSplit);
if (!tab) {
return null;
}
const instance = tab.split(this._terminalFocusContextKey, this.configHelper, shellLaunchConfig);
if (!instance) {
this._showNotEnoughSpaceToast();
return null;
}
this._initInstanceListeners(instance);
this._onInstancesChanged.fire();
this._terminalTabs.forEach((t, i) => t.setVisible(i === this._activeTabIndex));
return instance;
}
protected _initInstanceListeners(instance: ITerminalInstance): void {
instance.addDisposable(instance.onDisposed(this._onInstanceDisposed.fire, this._onInstanceDisposed));
instance.addDisposable(instance.onTitleChanged(this._onInstanceTitleChanged.fire, this._onInstanceTitleChanged));
instance.addDisposable(instance.onProcessIdReady(this._onInstanceProcessIdReady.fire, this._onInstanceProcessIdReady));
instance.addDisposable(instance.onDimensionsChanged(() => this._onInstanceDimensionsChanged.fire(instance)));
instance.addDisposable(instance.onMaximumDimensionsChanged(() => this._onInstanceMaximumDimensionsChanged.fire(instance)));
instance.addDisposable(instance.onFocus(this._onActiveInstanceChanged.fire, this._onActiveInstanceChanged));
}
private _getTabForInstance(instance: ITerminalInstance): ITerminalTab | null {
for (const tab of this._terminalTabs) {
if (tab.terminalInstances.indexOf(instance) !== -1) {
return tab;
}
}
return null;
}
public showPanel(focus?: boolean): Promise<void> {
return new Promise<void>((complete) => {
const panel = this._panelService.getActivePanel();
if (!panel || panel.getId() !== TERMINAL_PANEL_ID) {
this._panelService.openPanel(TERMINAL_PANEL_ID, focus);
if (focus) {
// Do the focus call asynchronously as going through the
// command palette will force editor focus
setTimeout(() => {
const instance = this.getActiveInstance();
if (instance) {
instance.focusWhenReady(true).then(() => complete(undefined));
} else {
complete(undefined);
}
}, 0);
} else {
complete(undefined);
}
} else {
if (focus) {
// Do the focus call asynchronously as going through the
// command palette will force editor focus
setTimeout(() => {
const instance = this.getActiveInstance();
if (instance) {
instance.focusWhenReady(true).then(() => complete(undefined));
} else {
complete(undefined);
}
}, 0);
} else {
complete(undefined);
}
}
return undefined;
});
}
public abstract hidePanel(): void;
public abstract focusFindWidget(): Promise<void>;
public abstract hideFindWidget(): void;
public abstract findNext(): void;
public abstract findPrevious(): void;
private _getIndexFromId(terminalId: number): number {
let terminalIndex = -1;
this.terminalInstances.forEach((terminalInstance, i) => {
if (terminalInstance.id === terminalId) {
terminalIndex = i;
}
});
if (terminalIndex === -1) {
throw new Error(`Terminal with ID ${terminalId} does not exist (has it already been disposed?)`);
}
return terminalIndex;
}
public async manageWorkspaceShellPermissions(): Promise<void> {
const allowItem: IQuickPickItem = { label: nls.localize('workbench.action.terminal.allowWorkspaceShell', "Allow Workspace Shell Configuration") };
const disallowItem: IQuickPickItem = { label: nls.localize('workbench.action.terminal.disallowWorkspaceShell', "Disallow Workspace Shell Configuration") };
const value = await this._quickInputService.pick([allowItem, disallowItem], { canPickMany: false });
if (!value) {
return;
}
this.configHelper.setWorkspaceShellAllowed(value === allowItem);
}
protected async _showTerminalCloseConfirmation(): Promise<boolean> {
let message: string;
if (this.terminalInstances.length === 1) {
message = nls.localize('terminalService.terminalCloseConfirmationSingular', "There is an active terminal session, do you want to kill it?");
} else {
message = nls.localize('terminalService.terminalCloseConfirmationPlural', "There are {0} active terminal sessions, do you want to kill them?", this.terminalInstances.length);
}
const res = await this._dialogService.confirm({
message,
type: 'warning',
});
return !res.confirmed;
}
protected _showNotEnoughSpaceToast(): void {
this._notificationService.info(nls.localize('terminal.minWidth', "Not enough space to split terminal."));
}
protected _validateShellPaths(label: string, potentialPaths: string[]): Promise<[string, string] | null> {
if (potentialPaths.length === 0) {
return Promise.resolve(null);
}
const current = potentialPaths.shift();
if (current! === '') {
return this._validateShellPaths(label, potentialPaths);
}
return this._fileService.exists(URI.file(current!)).then(exists => {
if (!exists) {
return this._validateShellPaths(label, potentialPaths);
}
return [label, current] as [string, string];
});
}
public preparePathForTerminalAsync(originalPath: string, executable: string, title: string): Promise<string> {
return new Promise<string>(c => {
if (!executable) {
c(originalPath);
return;
}
const hasSpace = originalPath.indexOf(' ') !== -1;
const pathBasename = basename(executable, '.exe');
const isPowerShell = pathBasename === 'pwsh' ||
title === 'pwsh' ||
pathBasename === 'powershell' ||
title === 'powershell';
if (isPowerShell && (hasSpace || originalPath.indexOf('\'') !== -1)) {
c(`& '${originalPath.replace(/'/g, '\'\'')}'`);
return;
}
if (isWindows) {
// 17063 is the build number where wsl path was introduced.
// Update Windows uriPath to be executed in WSL.
const lowerExecutable = executable.toLowerCase();
if (this._terminalNativeService.getWindowsBuildNumber() >= 17063 &&
(lowerExecutable.indexOf('wsl') !== -1 || (lowerExecutable.indexOf('bash.exe') !== -1 && lowerExecutable.toLowerCase().indexOf('git') === -1))) {
c(this._terminalNativeService.getWslPath(originalPath));
return;
} else if (hasSpace) {
c('"' + originalPath + '"');
} else {
c(originalPath);
}
return;
}
c(escapeNonWindowsPath(originalPath));
});
}
public selectDefaultWindowsShell(): Promise<void> {
return this._detectWindowsShells().then(shells => {
const options: IPickOptions<IQuickPickItem> = {
placeHolder: nls.localize('terminal.integrated.chooseWindowsShell', "Select your preferred terminal shell, you can change this later in your settings")
};
const quickPickItems = shells.map(s => {
return { label: s.label, description: s.path };
});
return this._quickInputService.pick(quickPickItems, options).then(async value => {
if (!value) {
return undefined;
}
const shell = value.description;
const env = await this._remoteAgentService.getEnvironment();
let platformKey: string;
if (env) {
platformKey = env.os === OperatingSystem.Windows ? 'windows' : (env.os === OperatingSystem.Macintosh ? 'osx' : 'linux');
} else {
platformKey = isWindows ? 'windows' : (isMacintosh ? 'osx' : 'linux');
}
await this._configurationService.updateValue(`terminal.integrated.shell.${platformKey}`, shell, ConfigurationTarget.USER).then(() => shell);
return Promise.resolve();
});
});
}
private _detectWindowsShells(): Promise<IShellDefinition[]> {
return new Promise(r => this._onRequestAvailableShells.fire(r));
}
}

View File

@@ -3,9 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { ITerminalInstance, IWindowsShellHelper, IShellLaunchConfig, ITerminalChildProcess, IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY } from 'vs/workbench/contrib/terminal/common/terminal';
import { WindowsShellHelper } from 'vs/workbench/contrib/terminal/node/windowsShellHelper';
import { ITerminalInstanceService, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
import { IWindowsShellHelper, IShellLaunchConfig, ITerminalChildProcess, IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY } from 'vs/workbench/contrib/terminal/common/terminal';
import { WindowsShellHelper } from 'vs/workbench/contrib/terminal/electron-browser/windowsShellHelper';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IProcessEnvironment, platform, Platform } from 'vs/base/common/platform';
import { TerminalProcess } from 'vs/workbench/contrib/terminal/node/terminalProcess';
@@ -27,7 +27,7 @@ let WebLinksAddon: typeof XTermWebLinksAddon;
let SearchAddon: typeof XTermSearchAddon;
export class TerminalInstanceService implements ITerminalInstanceService {
public _serviceBrand: any;
public _serviceBrand: undefined;
constructor(
@IInstantiationService private readonly _instantiationService: IInstantiationService,

View File

@@ -13,11 +13,11 @@ import { escapeNonWindowsPath } from 'vs/workbench/contrib/terminal/common/termi
import { execFile } from 'child_process';
import { Emitter, Event } from 'vs/base/common/event';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { registerRemoteContributions } from 'vs/workbench/contrib/terminal/node/terminalRemote';
import { registerRemoteContributions } from 'vs/workbench/contrib/terminal/electron-browser/terminalRemote';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
export class TerminalNativeService implements ITerminalNativeService {
public _serviceBrand: any;
public _serviceBrand: undefined;
public get linuxDistro(): LinuxDistro { return linuxDistro; }
@@ -78,4 +78,4 @@ export class TerminalNativeService implements ITerminalNativeService {
public getWindowsBuildNumber(): number {
return getWindowsBuildNumber();
}
}
}

View File

@@ -7,11 +7,11 @@ import * as nls from 'vs/nls';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { TERMINAL_ACTION_CATEGORY, ITerminalService, TitleEventSource } from 'vs/workbench/contrib/terminal/common/terminal';
import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminalCommands';
import { TERMINAL_ACTION_CATEGORY, TitleEventSource, TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal';
import { Action } from 'vs/base/common/actions';
import { URI } from 'vs/base/common/uri';
import { homedir } from 'os';
import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
export function registerRemoteContributions() {
const actionRegistry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);

View File

@@ -5,10 +5,11 @@
import * as platform from 'vs/base/common/platform';
import { Emitter, Event } from 'vs/base/common/event';
import { ITerminalInstance, IWindowsShellHelper, TitleEventSource } from 'vs/workbench/contrib/terminal/common/terminal';
import { IWindowsShellHelper, TitleEventSource } from 'vs/workbench/contrib/terminal/common/terminal';
import { Terminal as XTermTerminal } from 'xterm';
import * as WindowsProcessTreeType from 'windows-process-tree';
import { Disposable } from 'vs/base/common/lifecycle';
import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
const SHELL_EXECUTABLES = [
'cmd.exe',

View File

@@ -4,17 +4,13 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { Terminal, TerminalCore } from 'xterm';
import { Terminal } from 'xterm';
import { CommandTrackerAddon } from 'vs/workbench/contrib/terminal/browser/addons/commandTrackerAddon';
import { isWindows } from 'vs/base/common/platform';
interface TestTerminalCore extends TerminalCore {
writeBuffer: string[];
_innerWrite(): void;
}
import { XTermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private';
interface TestTerminal extends Terminal {
_core: TestTerminalCore;
_core: XTermCore;
}
function syncWrite(term: TestTerminal, data: string): void {

View File

@@ -35,7 +35,7 @@ class MockTerminalInstanceService implements ITerminalInstanceService {
getDefaultShellAndArgs(): Promise<{ shell: string; args: string | string[] | undefined; }> {
throw new Error('Method not implemented.');
}
_serviceBrand: any;
_serviceBrand: undefined;
getXtermConstructor(): Promise<any> {
throw new Error('Method not implemented.');
}

View File

@@ -5,30 +5,110 @@
import * as assert from 'assert';
import * as platform from 'vs/base/common/platform';
import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
import { URI as Uri } from 'vs/base/common/uri';
import { IStringDictionary } from 'vs/base/common/collections';
import { addTerminalEnvironmentKeys, mergeEnvironments, getCwd, getDefaultShell, getLangEnvVariable, shouldSetLangEnvVariable } from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
suite('Workbench - TerminalEnvironment', () => {
test('addTerminalEnvironmentKeys', () => {
const env: { [key: string]: any } = { FOO: 'bar' };
const locale = 'en-au';
terminalEnvironment.addTerminalEnvironmentKeys(env, '1.2.3', locale, true);
assert.equal(env['TERM_PROGRAM'], 'vscode');
assert.equal(env['TERM_PROGRAM_VERSION'], '1.2.3');
assert.equal(env['LANG'], 'en_AU.UTF-8', 'LANG is equal to the requested locale with UTF-8');
suite('addTerminalEnvironmentKeys', () => {
test('should set expected variables', () => {
const env: { [key: string]: any } = {};
addTerminalEnvironmentKeys(env, '1.2.3', 'en', 'on');
assert.equal(env['TERM_PROGRAM'], 'vscode');
assert.equal(env['TERM_PROGRAM_VERSION'], '1.2.3');
assert.equal(env['COLORTERM'], 'truecolor');
assert.equal(env['LANG'], 'en_US.UTF-8');
});
test('should use language variant for LANG that is provided in locale', () => {
const env: { [key: string]: any } = {};
addTerminalEnvironmentKeys(env, '1.2.3', 'en-au', 'on');
assert.equal(env['LANG'], 'en_AU.UTF-8', 'LANG is equal to the requested locale with UTF-8');
});
test('should fallback to en_US when no locale is provided', () => {
const env2: { [key: string]: any } = { FOO: 'bar' };
addTerminalEnvironmentKeys(env2, '1.2.3', undefined, 'on');
assert.equal(env2['LANG'], 'en_US.UTF-8', 'LANG is equal to en_US.UTF-8 as fallback.'); // More info on issue #14586
});
test('should fallback to en_US when an invalid locale is provided', () => {
const env3 = { LANG: 'replace' };
addTerminalEnvironmentKeys(env3, '1.2.3', undefined, 'on');
assert.equal(env3['LANG'], 'en_US.UTF-8', 'LANG is set to the fallback LANG');
});
test('should override existing LANG', () => {
const env4 = { LANG: 'en_AU.UTF-8' };
addTerminalEnvironmentKeys(env4, '1.2.3', undefined, 'on');
assert.equal(env4['LANG'], 'en_US.UTF-8', 'LANG is equal to the parent environment\'s LANG');
});
});
const env2: { [key: string]: any } = { FOO: 'bar' };
terminalEnvironment.addTerminalEnvironmentKeys(env2, '1.2.3', undefined, true);
assert.equal(env2['LANG'], 'en_US.UTF-8', 'LANG is equal to en_US.UTF-8 as fallback.'); // More info on issue #14586
suite('shouldSetLangEnvVariable', () => {
test('auto', () => {
assert.equal(shouldSetLangEnvVariable({}, 'auto'), true);
assert.equal(shouldSetLangEnvVariable({ LANG: 'en-US' }, 'auto'), true);
assert.equal(shouldSetLangEnvVariable({ LANG: 'en-US.UTF-8' }, 'auto'), false);
});
test('off', () => {
assert.equal(shouldSetLangEnvVariable({}, 'off'), false);
assert.equal(shouldSetLangEnvVariable({ LANG: 'en-US' }, 'off'), false);
assert.equal(shouldSetLangEnvVariable({ LANG: 'en-US.UTF-8' }, 'off'), false);
});
test('on', () => {
assert.equal(shouldSetLangEnvVariable({}, 'on'), true);
assert.equal(shouldSetLangEnvVariable({ LANG: 'en-US' }, 'on'), true);
assert.equal(shouldSetLangEnvVariable({ LANG: 'en-US.UTF-8' }, 'on'), true);
});
});
const env3 = { LANG: 'replace' };
terminalEnvironment.addTerminalEnvironmentKeys(env3, '1.2.3', undefined, true);
assert.equal(env3['LANG'], 'en_US.UTF-8', 'LANG is set to the fallback LANG');
const env4 = { LANG: 'en_US.UTF-8' };
terminalEnvironment.addTerminalEnvironmentKeys(env3, '1.2.3', undefined, true);
assert.equal(env4['LANG'], 'en_US.UTF-8', 'LANG is equal to the parent environment\'s LANG');
suite('getLangEnvVariable', () => {
test('should fallback to en_US when no locale is provided', () => {
assert.equal(getLangEnvVariable(undefined), 'en_US.UTF-8');
assert.equal(getLangEnvVariable(''), 'en_US.UTF-8');
});
test('should fallback to default language variants when variant isn\'t provided', () => {
assert.equal(getLangEnvVariable('af'), 'af_ZA.UTF-8');
assert.equal(getLangEnvVariable('am'), 'am_ET.UTF-8');
assert.equal(getLangEnvVariable('be'), 'be_BY.UTF-8');
assert.equal(getLangEnvVariable('bg'), 'bg_BG.UTF-8');
assert.equal(getLangEnvVariable('ca'), 'ca_ES.UTF-8');
assert.equal(getLangEnvVariable('cs'), 'cs_CZ.UTF-8');
assert.equal(getLangEnvVariable('da'), 'da_DK.UTF-8');
assert.equal(getLangEnvVariable('de'), 'de_DE.UTF-8');
assert.equal(getLangEnvVariable('el'), 'el_GR.UTF-8');
assert.equal(getLangEnvVariable('en'), 'en_US.UTF-8');
assert.equal(getLangEnvVariable('es'), 'es_ES.UTF-8');
assert.equal(getLangEnvVariable('et'), 'et_EE.UTF-8');
assert.equal(getLangEnvVariable('eu'), 'eu_ES.UTF-8');
assert.equal(getLangEnvVariable('fi'), 'fi_FI.UTF-8');
assert.equal(getLangEnvVariable('fr'), 'fr_FR.UTF-8');
assert.equal(getLangEnvVariable('he'), 'he_IL.UTF-8');
assert.equal(getLangEnvVariable('hr'), 'hr_HR.UTF-8');
assert.equal(getLangEnvVariable('hu'), 'hu_HU.UTF-8');
assert.equal(getLangEnvVariable('hy'), 'hy_AM.UTF-8');
assert.equal(getLangEnvVariable('is'), 'is_IS.UTF-8');
assert.equal(getLangEnvVariable('it'), 'it_IT.UTF-8');
assert.equal(getLangEnvVariable('ja'), 'ja_JP.UTF-8');
assert.equal(getLangEnvVariable('kk'), 'kk_KZ.UTF-8');
assert.equal(getLangEnvVariable('ko'), 'ko_KR.UTF-8');
assert.equal(getLangEnvVariable('lt'), 'lt_LT.UTF-8');
assert.equal(getLangEnvVariable('nl'), 'nl_NL.UTF-8');
assert.equal(getLangEnvVariable('no'), 'no_NO.UTF-8');
assert.equal(getLangEnvVariable('pl'), 'pl_PL.UTF-8');
assert.equal(getLangEnvVariable('pt'), 'pt_BR.UTF-8');
assert.equal(getLangEnvVariable('ro'), 'ro_RO.UTF-8');
assert.equal(getLangEnvVariable('ru'), 'ru_RU.UTF-8');
assert.equal(getLangEnvVariable('sk'), 'sk_SK.UTF-8');
assert.equal(getLangEnvVariable('sl'), 'sl_SI.UTF-8');
assert.equal(getLangEnvVariable('sr'), 'sr_YU.UTF-8');
assert.equal(getLangEnvVariable('sv'), 'sv_SE.UTF-8');
assert.equal(getLangEnvVariable('tr'), 'tr_TR.UTF-8');
assert.equal(getLangEnvVariable('uk'), 'uk_UA.UTF-8');
assert.equal(getLangEnvVariable('zh'), 'zh_CN.UTF-8');
});
test('should set language variant based on full locale', () => {
assert.equal(getLangEnvVariable('en-AU'), 'en_AU.UTF-8');
assert.equal(getLangEnvVariable('en-au'), 'en_AU.UTF-8');
assert.equal(getLangEnvVariable('fa-ke'), 'fa_KE.UTF-8');
});
});
suite('mergeEnvironments', () => {
@@ -39,7 +119,7 @@ suite('Workbench - TerminalEnvironment', () => {
const other = {
c: 'd'
};
terminalEnvironment.mergeEnvironments(parent, other);
mergeEnvironments(parent, other);
assert.deepEqual(parent, {
a: 'b',
c: 'd'
@@ -56,7 +136,7 @@ suite('Workbench - TerminalEnvironment', () => {
const other = {
A: 'c'
};
terminalEnvironment.mergeEnvironments(parent, other);
mergeEnvironments(parent, other);
assert.deepEqual(parent, {
a: 'c'
});
@@ -70,7 +150,7 @@ suite('Workbench - TerminalEnvironment', () => {
const other: IStringDictionary<string | null> = {
a: null
};
terminalEnvironment.mergeEnvironments(parent, other);
mergeEnvironments(parent, other);
assert.deepEqual(parent, {
c: 'd'
});
@@ -87,7 +167,7 @@ suite('Workbench - TerminalEnvironment', () => {
const other: IStringDictionary<string | null> = {
A: null
};
terminalEnvironment.mergeEnvironments(parent, other);
mergeEnvironments(parent, other);
assert.deepEqual(parent, {
c: 'd'
});
@@ -101,37 +181,37 @@ suite('Workbench - TerminalEnvironment', () => {
}
test('should default to userHome for an empty workspace', () => {
assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, undefined, undefined), '/userHome/');
assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, undefined, undefined), '/userHome/');
});
test('should use to the workspace if it exists', () => {
assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, Uri.file('/foo'), undefined), '/foo');
assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, Uri.file('/foo'), undefined), '/foo');
});
test('should use an absolute custom cwd as is', () => {
assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, undefined, '/foo'), '/foo');
assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, undefined, '/foo'), '/foo');
});
test('should normalize a relative custom cwd against the workspace path', () => {
assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, Uri.file('/bar'), 'foo'), '/bar/foo');
assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, Uri.file('/bar'), './foo'), '/bar/foo');
assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, Uri.file('/bar'), '../foo'), '/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'), '/bar/foo');
assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, Uri.file('/bar'), '../foo'), '/foo');
});
test('should fall back for relative a custom cwd that doesn\'t have a workspace', () => {
assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, undefined, 'foo'), '/userHome/');
assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, undefined, './foo'), '/userHome/');
assertPathsMatch(terminalEnvironment.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, undefined, '../foo'), '/userHome/');
});
test('should ignore custom cwd when told to ignore', () => {
assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [], ignoreConfigurationCwd: true }, '/userHome/', undefined, undefined, Uri.file('/bar'), '/foo'), '/bar');
assertPathsMatch(getCwd({ executable: undefined, args: [], ignoreConfigurationCwd: true }, '/userHome/', undefined, undefined, Uri.file('/bar'), '/foo'), '/bar');
});
});
suite('getDefaultShell', () => {
test('should change Sysnative to System32 in non-WoW64 systems', () => {
const shell = terminalEnvironment.getDefaultShell(key => {
const shell = getDefaultShell(key => {
return ({
'terminal.integrated.shell.windows': { user: 'C:\\Windows\\Sysnative\\cmd.exe', value: undefined, default: undefined }
} as any)[key];
@@ -140,7 +220,7 @@ suite('Workbench - TerminalEnvironment', () => {
});
test('should not change Sysnative to System32 in WoW64 systems', () => {
const shell = terminalEnvironment.getDefaultShell(key => {
const shell = getDefaultShell(key => {
return ({
'terminal.integrated.shell.windows': { user: 'C:\\Windows\\Sysnative\\cmd.exe', value: undefined, default: undefined }
} as any)[key];
@@ -149,21 +229,21 @@ suite('Workbench - TerminalEnvironment', () => {
});
test('should use automationShell when specified', () => {
const shell1 = terminalEnvironment.getDefaultShell(key => {
const shell1 = getDefaultShell(key => {
return ({
'terminal.integrated.shell.windows': { user: 'shell', value: undefined, default: undefined },
'terminal.integrated.automationShell.windows': { user: undefined, value: undefined, default: undefined }
} as any)[key];
}, false, 'DEFAULT', false, 'C:\\Windows', undefined, undefined, {} as any, false, platform.Platform.Windows);
assert.equal(shell1, 'shell', 'automationShell was false');
const shell2 = terminalEnvironment.getDefaultShell(key => {
const shell2 = getDefaultShell(key => {
return ({
'terminal.integrated.shell.windows': { user: 'shell', value: undefined, default: undefined },
'terminal.integrated.automationShell.windows': { user: undefined, value: undefined, default: undefined }
} as any)[key];
}, false, 'DEFAULT', false, 'C:\\Windows', undefined, undefined, {} as any, true, platform.Platform.Windows);
assert.equal(shell2, 'shell', 'automationShell was true');
const shell3 = terminalEnvironment.getDefaultShell(key => {
const shell3 = getDefaultShell(key => {
return ({
'terminal.integrated.shell.windows': { user: 'shell', value: undefined, default: undefined },
'terminal.integrated.automationShell.windows': { user: 'automationShell', value: undefined, default: undefined }