Merge from vscode 1ec43773e37997841c5af42b33ddb180e9735bf2

This commit is contained in:
ADS Merger
2020-03-29 01:29:32 +00:00
parent 586ec50916
commit a64304602e
316 changed files with 6524 additions and 11687 deletions

View File

@@ -155,3 +155,8 @@
.xterm.xterm-cursor-pointer {
cursor: pointer!important;
}
/* Rotate icon when terminal is in the sidebar */
.monaco-workbench .part.sidebar .title-actions .terminal-action.codicon-split-horizontal {
transform: rotate(-90deg);
}

View File

@@ -15,15 +15,12 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as ActionBarExtensions, IActionBarRegistry, Scope } from 'vs/workbench/browser/actions';
import * as panel from 'vs/workbench/browser/panel';
import { getQuickNavigateHandler } from 'vs/workbench/browser/parts/quickopen/quickopen';
import { Extensions as QuickOpenExtensions, IQuickOpenRegistry, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen';
import { getQuickNavigateHandler } from 'vs/workbench/browser/quickaccess';
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
import { Extensions as ViewContainerExtensions, IViewContainersRegistry, ViewContainerLocation, IViewsRegistry } from 'vs/workbench/common/views';
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, SplitInActiveWorkspaceTerminalAction, SplitTerminalAction, TerminalPasteAction, TERMINAL_PICKER_PREFIX, ToggleCaseSensitiveCommand, ToggleEscapeSequenceLoggingAction, ToggleRegexCommand, ToggleTerminalAction, ToggleWholeWordCommand, NavigationModeFocusPreviousTerminalAction, NavigationModeFocusNextTerminalAction, NavigationModeExitTerminalAction, ManageWorkspaceShellPermissionsTerminalCommand, CreateNewWithCwdTerminalAction, RenameWithArgTerminalAction, SendSequenceTerminalAction } from 'vs/workbench/contrib/terminal/browser/terminalActions';
import { ClearSelectionTerminalAction, ClearTerminalAction, CopyTerminalSelectionAction, CreateNewInActiveWorkspaceTerminalAction, CreateNewTerminalAction, DeleteToLineStartTerminalAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, FindNext, FindPrevious, FocusActiveTerminalAction, FocusNextPaneTerminalAction, FocusNextTerminalAction, FocusPreviousPaneTerminalAction, FocusPreviousTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, KillTerminalAction, MoveToLineEndTerminalAction, MoveToLineStartTerminalAction, QuickAccessTerminalAction, RenameTerminalAction, ResizePaneDownTerminalAction, ResizePaneLeftTerminalAction, ResizePaneRightTerminalAction, ResizePaneUpTerminalAction, RunActiveFileInTerminalAction, RunSelectedTextInTerminalAction, ScrollDownPageTerminalAction, ScrollDownTerminalAction, ScrollToBottomTerminalAction, ScrollToNextCommandAction, ScrollToPreviousCommandAction, ScrollToTopTerminalAction, ScrollUpPageTerminalAction, ScrollUpTerminalAction, SelectAllTerminalAction, SelectDefaultShellWindowsTerminalAction, SelectToNextCommandAction, SelectToNextLineAction, SelectToPreviousCommandAction, SelectToPreviousLineAction, SplitInActiveWorkspaceTerminalAction, SplitTerminalAction, TerminalPasteAction, ToggleCaseSensitiveCommand, ToggleEscapeSequenceLoggingAction, ToggleRegexCommand, ToggleTerminalAction, ToggleWholeWordCommand, NavigationModeFocusPreviousTerminalAction, NavigationModeFocusNextTerminalAction, NavigationModeExitTerminalAction, ManageWorkspaceShellPermissionsTerminalCommand, CreateNewWithCwdTerminalAction, RenameWithArgTerminalAction, SendSequenceTerminalAction } from 'vs/workbench/contrib/terminal/browser/terminalActions';
import { TerminalViewPane } from 'vs/workbench/contrib/terminal/browser/terminalView';
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_VIEW_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 } from 'vs/workbench/contrib/terminal/browser/terminalCommands';
@@ -48,20 +45,7 @@ if (platform.isWeb) {
registerShellConfiguration();
}
const quickOpenRegistry = (Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen));
const inTerminalsPicker = 'inTerminalPicker';
quickOpenRegistry.registerQuickOpenHandler(
QuickOpenHandlerDescriptor.create(
TerminalPickerHandler,
TerminalPickerHandler.ID,
TERMINAL_PICKER_PREFIX,
inTerminalsPicker,
nls.localize('quickOpen.terminal', "Show All Opened Terminals")
)
);
const quickAccessRegistry = (Registry.as<IQuickAccessRegistry>(QuickAccessExtensions.Quickaccess));
quickAccessRegistry.registerQuickAccessProvider({
@@ -72,13 +56,13 @@ quickAccessRegistry.registerQuickAccessProvider({
helpEntries: [{ description: nls.localize('tasksQuickAccessHelp', "Show All Opened Terminals"), needsEditor: false }]
});
const quickOpenNavigateNextInTerminalPickerId = 'workbench.action.quickOpenNavigateNextInTerminalPicker';
const quickAccessNavigateNextInTerminalPickerId = 'workbench.action.quickOpenNavigateNextInTerminalPicker';
CommandsRegistry.registerCommand(
{ id: quickOpenNavigateNextInTerminalPickerId, handler: getQuickNavigateHandler(quickOpenNavigateNextInTerminalPickerId, true) });
{ id: quickAccessNavigateNextInTerminalPickerId, handler: getQuickNavigateHandler(quickAccessNavigateNextInTerminalPickerId, true) });
const quickOpenNavigatePreviousInTerminalPickerId = 'workbench.action.quickOpenNavigatePreviousInTerminalPicker';
const quickAccessNavigatePreviousInTerminalPickerId = 'workbench.action.quickOpenNavigatePreviousInTerminalPicker';
CommandsRegistry.registerCommand(
{ id: quickOpenNavigatePreviousInTerminalPickerId, handler: getQuickNavigateHandler(quickOpenNavigatePreviousInTerminalPickerId, false) });
{ id: quickAccessNavigatePreviousInTerminalPickerId, handler: getQuickNavigateHandler(quickAccessNavigatePreviousInTerminalPickerId, false) });
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
@@ -373,9 +357,7 @@ configurationRegistry.registerConfiguration({
});
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickOpenTermAction, QuickOpenTermAction.ID, QuickOpenTermAction.LABEL), 'Terminal: Switch Active Terminal', nls.localize('terminal', "Terminal"));
const actionBarRegistry = Registry.as<IActionBarRegistry>(ActionBarExtensions.Actionbar);
actionBarRegistry.registerActionBarContributor(Scope.VIEWER, QuickOpenActionTermContributor);
registry.registerWorkbenchAction(SyncActionDescriptor.create(QuickAccessTerminalAction, QuickAccessTerminalAction.ID, QuickAccessTerminalAction.LABEL), 'Terminal: Switch Active Terminal', nls.localize('terminal', "Terminal"));
const VIEW_CONTAINER = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({
id: TERMINAL_VIEW_ID,

View File

@@ -14,17 +14,13 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { IQuickInputService, IPickOptions, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { ActionBarContributor } from 'vs/workbench/browser/actions';
import { TerminalEntry } from 'vs/workbench/contrib/terminal/browser/terminalQuickOpen';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
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 { timeout } from 'vs/base/common/async';
import { FindReplaceState } from 'vs/editor/contrib/find/findState';
import { ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
@@ -35,8 +31,7 @@ import { isWindows } from 'vs/base/common/platform';
import { withNullAsUndefined } from 'vs/base/common/types';
import { ITerminalInstance, ITerminalService, Direction } from 'vs/workbench/contrib/terminal/browser/terminal';
import { Action2 } from 'vs/platform/actions/common/actions';
export const TERMINAL_PICKER_PREFIX = 'term ';
import { TerminalQuickAccessProvider } from 'vs/workbench/contrib/terminal/browser/terminalsQuickAccess';
async function getCwdForSplit(configHelper: ITerminalConfigHelper, instance: ITerminalInstance, folders?: IWorkspaceFolder[], commandService?: ICommandService): Promise<string | URI | undefined> {
switch (configHelper.config.splitCwd) {
@@ -118,29 +113,6 @@ export class KillTerminalAction extends Action {
}
}
export class QuickKillTerminalAction extends Action {
public static readonly ID = TERMINAL_COMMAND_ID.QUICK_KILL;
public static readonly LABEL = nls.localize('workbench.action.terminal.quickKill', "Kill Terminal Instance");
constructor(
id: string, label: string,
private terminalEntry: TerminalEntry,
@IQuickOpenService private readonly quickOpenService: IQuickOpenService
) {
super(id, label, 'terminal-action kill');
}
public async run(event?: any): Promise<any> {
const instance = this.terminalEntry.instance;
if (instance) {
instance.dispose(true);
}
await timeout(50);
return this.quickOpenService.show(TERMINAL_PICKER_PREFIX);
}
}
/**
* Copies the terminal selection. Note that since the command palette takes focus from the terminal,
* this cannot be triggered through the command palette.
@@ -1022,15 +994,14 @@ export class RenameTerminalAction extends Action {
constructor(
id: string, label: string,
@IQuickOpenService protected quickOpenService: IQuickOpenService,
@IQuickInputService protected quickInputService: IQuickInputService,
@ITerminalService protected terminalService: ITerminalService
) {
super(id, label);
}
public async run(entry?: TerminalEntry): Promise<any> {
const terminalInstance = entry ? entry.instance : this.terminalService.getActiveInstance();
public async run(): Promise<any> {
const terminalInstance = this.terminalService.getActiveInstance();
if (!terminalInstance) {
return Promise.resolve(undefined);
}
@@ -1103,64 +1074,21 @@ export class HideTerminalFindWidgetAction extends Action {
}
}
export class QuickOpenActionTermContributor extends ActionBarContributor {
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService
) {
super();
}
public getActions(context: any): ReadonlyArray<IAction> {
const actions: Action[] = [];
if (context.element instanceof TerminalEntry) {
actions.push(this.instantiationService.createInstance(RenameTerminalQuickOpenAction, RenameTerminalQuickOpenAction.ID, RenameTerminalQuickOpenAction.LABEL, context.element));
actions.push(this.instantiationService.createInstance(QuickKillTerminalAction, QuickKillTerminalAction.ID, QuickKillTerminalAction.LABEL, context.element));
}
return actions;
}
public hasActions(context: any): boolean {
return true;
}
}
export class QuickOpenTermAction extends Action {
export class QuickAccessTerminalAction extends Action {
public static readonly ID = TERMINAL_COMMAND_ID.QUICK_OPEN_TERM;
public static readonly LABEL = nls.localize('quickOpenTerm', "Switch Active Terminal");
public static readonly LABEL = nls.localize('quickAccessTerminal', "Switch Active Terminal");
constructor(
id: string,
label: string,
@IQuickOpenService private readonly quickOpenService: IQuickOpenService
@IQuickInputService private readonly quickInputService: IQuickInputService
) {
super(id, label);
}
public run(): Promise<void> {
return this.quickOpenService.show(TERMINAL_PICKER_PREFIX);
}
}
export class RenameTerminalQuickOpenAction extends RenameTerminalAction {
constructor(
id: string, label: string,
private terminal: TerminalEntry,
@IQuickOpenService quickOpenService: IQuickOpenService,
@IQuickInputService quickInputService: IQuickInputService,
@ITerminalService terminalService: ITerminalService
) {
super(id, label, quickOpenService, quickInputService, terminalService);
this.class = 'codicon codicon-gear';
}
public async run(): Promise<any> {
await super.run(this.terminal);
// This timeout is needed to make sure the previous quickOpen has time to close before we show the next one
await timeout(50);
await this.quickOpenService.show(TERMINAL_PICKER_PREFIX);
async run(): Promise<void> {
this.quickInputService.quickAccess.show(TerminalQuickAccessProvider.PREFIX);
}
}

View File

@@ -23,7 +23,7 @@ import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notif
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry';
import { ICssStyleCollector, IColorTheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { PANEL_BACKGROUND } from 'vs/workbench/common/theme';
import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/terminalWidgetManager';
import { IShellLaunchConfig, ITerminalDimensions, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_VIEW_ID, IWindowsShellHelper, SHELL_PATH_INVALID_EXIT_CODE, SHELL_PATH_DIRECTORY_EXIT_CODE, SHELL_CWD_INVALID_EXIT_CODE, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, INavigationMode, TitleEventSource, TERMINAL_COMMAND_ID, LEGACY_CONSOLE_MODE_EXIT_CODE } 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';
@@ -40,7 +40,7 @@ import { NavigationModeAddon } from 'vs/workbench/contrib/terminal/browser/addon
import { XTermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IViewsService } from 'vs/workbench/common/views';
import { IViewsService, IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views';
// How long in milliseconds should an average frame take to render for a notification to appear
// which suggests the fallback DOM-based renderer
@@ -292,6 +292,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
@ILogService private readonly _logService: ILogService,
@IStorageService private readonly _storageService: IStorageService,
@IAccessibilityService private readonly _accessibilityService: IAccessibilityService,
@IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService,
@IOpenerService private readonly _openerService: IOpenerService
) {
super();
@@ -534,6 +535,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._commandTrackerAddon = new CommandTrackerAddon();
this._xterm.loadAddon(this._commandTrackerAddon);
this._register(this._themeService.onDidColorThemeChange(theme => this._updateTheme(xterm, theme)));
this._register(this._viewDescriptorService.onDidChangeLocation(({ views }) => {
if (views.some(v => v.id === TERMINAL_VIEW_ID)) {
this._updateTheme(xterm);
}
}));
return xterm;
}
@@ -1453,8 +1459,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
theme = this._themeService.getColorTheme();
}
const location = this._viewDescriptorService.getViewLocation(TERMINAL_VIEW_ID)!;
const foregroundColor = theme.getColor(TERMINAL_FOREGROUND_COLOR);
const backgroundColor = theme.getColor(TERMINAL_BACKGROUND_COLOR) || theme.getColor(PANEL_BACKGROUND);
const backgroundColor = theme.getColor(TERMINAL_BACKGROUND_COLOR) || (location === ViewContainerLocation.Sidebar ? theme.getColor(SIDE_BAR_BACKGROUND) : theme.getColor(PANEL_BACKGROUND));
const cursorColor = theme.getColor(TERMINAL_CURSOR_FOREGROUND_COLOR) || foregroundColor;
const cursorAccentColor = theme.getColor(TERMINAL_CURSOR_BACKGROUND_COLOR) || backgroundColor;
const selectionColor = theme.getColor(TERMINAL_SELECTION_BACKGROUND_COLOR);

View File

@@ -23,6 +23,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { Disposable } from 'vs/base/common/lifecycle';
import { withNullAsUndefined } from 'vs/base/common/types';
import { IEnvironmentVariableService, IMergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
/** The amount of time to consider terminal errors to be related to the launch */
const LAUNCHING_DURATION = 500;
@@ -59,6 +60,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
private _latency: number = -1;
private _latencyLastMeasured: number = 0;
private _initialCwd: string | undefined;
private _extEnvironmentVariableCollection: IMergedEnvironmentVariableCollection | undefined;
private readonly _onProcessReady = this._register(new Emitter<void>());
public get onProcessReady(): Event<void> { return this._onProcessReady.event; }
@@ -87,7 +89,8 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
@IProductService private readonly _productService: IProductService,
@ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService,
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
@IEnvironmentVariableService private readonly _environmentVariableService: IEnvironmentVariableService
) {
super();
this.ptyProcessReady = new Promise<void>(c => {
@@ -230,6 +233,11 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
const baseEnv = this._configHelper.config.inheritEnv ? processEnv : await this._terminalInstanceService.getMainProcessParentEnv();
const env = terminalEnvironment.createTerminalEnvironment(shellLaunchConfig, lastActiveWorkspace, envFromConfigValue, this._configurationResolverService, isWorkspaceShellAllowed, this._productService.version, this._configHelper.config.detectLocale, baseEnv);
// Fetch any extension environment additions and apply them
this._extEnvironmentVariableCollection = this._environmentVariableService.mergedCollection;
this._register(this._environmentVariableService.onDidChangeCollections(newCollection => this._onEnvironmentVariableCollectionChange(newCollection)));
this._extEnvironmentVariableCollection.applyToProcessEnvironment(env);
const useConpty = this._configHelper.config.windowsEnableConpty && !isScreenReaderModeEnabled;
return this._terminalInstanceService.createTerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, useConpty);
}
@@ -304,4 +312,37 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
this._onProcessExit.fire(exitCode);
}
private _onEnvironmentVariableCollectionChange(newCollection: IMergedEnvironmentVariableCollection): void {
// TODO: React to changes in environment variable collections
// const newAdditions = this._extEnvironmentVariableCollection!.getNewAdditions(newCollection);
// if (newAdditions === undefined) {
// return;
// }
// const promptChoices: IPromptChoice[] = [
// {
// label: nls.localize('apply', "Apply"),
// run: () => {
// let text = '';
// newAdditions.forEach((mutator, variable) => {
// // TODO: Support other common shells
// // TODO: Escape the new values properly
// switch (mutator.type) {
// case EnvironmentVariableMutatorType.Append:
// text += `export ${variable}="$${variable}${mutator.value}"\n`;
// break;
// case EnvironmentVariableMutatorType.Prepend:
// text += `export ${variable}="${mutator.value}$${variable}"\n`;
// break;
// case EnvironmentVariableMutatorType.Replace:
// text += `export ${variable}="${mutator.value}"\n`;
// break;
// }
// });
// this.write(text);
// }
// } as IPromptChoice
// ];
// this._notificationService.prompt(Severity.Info, nls.localize('environmentchange', "An extension wants to change the terminal environment, do you want to send commands to set the variables in the terminal? Note if you have an application open in the terminal this may not work."), promptChoices);
}
}

View File

@@ -1,137 +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 { 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/browser/terminal';
import { ContributableActionProvider } from 'vs/workbench/browser/actions';
import { stripWildcards } from 'vs/base/common/strings';
import { matchesFuzzy } from 'vs/base/common/filters';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { CancellationToken } from 'vs/base/common/cancellation';
export class TerminalEntry extends QuickOpenEntry {
constructor(
public instance: ITerminalInstance,
private label: string,
private terminalService: ITerminalService
) {
super();
}
public getLabel(): string {
return this.label;
}
public getAriaLabel(): string {
return nls.localize('termEntryAriaLabel', "{0}, terminal picker", this.getLabel());
}
public run(mode: Mode, context: IEntryRunContext): boolean {
if (mode === Mode.OPEN) {
setTimeout(() => {
this.terminalService.setActiveInstance(this.instance);
this.terminalService.showPanel(true);
}, 0);
return true;
}
return super.run(mode, context);
}
}
export class CreateTerminal extends QuickOpenEntry {
constructor(
private label: string,
private commandService: ICommandService
) {
super();
}
public getLabel(): string {
return this.label;
}
public getAriaLabel(): string {
return nls.localize('termCreateEntryAriaLabel', "{0}, create new terminal", this.getLabel());
}
public run(mode: Mode, context: IEntryRunContext): boolean {
if (mode === Mode.OPEN) {
setTimeout(() => this.commandService.executeCommand('workbench.action.terminal.new'), 0);
return true;
}
return super.run(mode, context);
}
}
export class TerminalPickerHandler extends QuickOpenHandler {
public static readonly ID = 'workbench.picker.terminals';
constructor(
@ITerminalService private readonly terminalService: ITerminalService,
@ICommandService private readonly commandService: ICommandService,
) {
super();
}
public getResults(searchValue: string, token: CancellationToken): Promise<QuickOpenModel> {
searchValue = searchValue.trim();
const normalizedSearchValueLowercase = stripWildcards(searchValue).toLowerCase();
const terminalEntries: QuickOpenEntry[] = this.getTerminals();
terminalEntries.push(new CreateTerminal('$(plus) ' + nls.localize("workbench.action.terminal.newplus", "Create New Integrated Terminal"), this.commandService));
const entries = terminalEntries.filter(e => {
if (!searchValue) {
return true;
}
const label = e.getLabel();
if (!label) {
return false;
}
const highlights = matchesFuzzy(normalizedSearchValueLowercase, label, true);
if (!highlights) {
return false;
}
e.setHighlights(highlights);
return true;
});
return Promise.resolve(new QuickOpenModel(entries, new ContributableActionProvider()));
}
private getTerminals(): TerminalEntry[] {
return this.terminalService.terminalTabs.reduce((terminals: TerminalEntry[], tab, tabIndex) => {
const terminalsInTab = tab.terminalInstances.map((terminal, terminalIndex) => {
const label = `${tabIndex + 1}.${terminalIndex + 1}: ${terminal.title}`;
return new TerminalEntry(terminal, label, this.terminalService);
});
return [...terminals, ...terminalsInTab];
}, []);
}
public getAutoFocus(searchValue: string, context: { model: IModel<QuickOpenEntry>, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus {
return {
autoFocusFirstEntry: !!searchValue || !!context.quickNavigateConfiguration
};
}
public getEmptyLabel(searchString: string): string {
if (searchString.length > 0) {
return nls.localize('noTerminalsMatching', "No terminals matching");
}
return nls.localize('noTerminalsFound', "No terminals open");
}
}

View File

@@ -111,7 +111,7 @@ export class TerminalService implements ITerminalService {
this._activeTabIndex = 0;
this._isShuttingDown = false;
this._findState = new FindReplaceState();
lifecycleService.onBeforeShutdown(async event => event.veto(await this._onBeforeShutdown()));
lifecycleService.onBeforeShutdown(async event => event.veto(this._onBeforeShutdown()));
lifecycleService.onShutdown(() => this._onShutdown());
if (this._terminalNativeService) {
this._terminalNativeService.onOpenFileRequest(e => this._onOpenFileRequest(e));
@@ -466,7 +466,7 @@ export class TerminalService implements ITerminalService {
public async showPanel(focus?: boolean): Promise<void> {
const pane = this._viewsService.getActiveViewWithId(TERMINAL_VIEW_ID) as TerminalViewPane;
if (!pane) {
await this._panelService.openPanel(TERMINAL_VIEW_ID, focus);
await this._viewsService.openView(TERMINAL_VIEW_ID, focus);
}
if (focus) {
// Do the focus call asynchronously as going through the

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as aria from 'vs/base/browser/ui/aria/aria';
import * as nls from 'vs/nls';
import { IShellLaunchConfig, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal';
import { IShellLaunchConfig, ITerminalConfigHelper, TERMINAL_VIEW_ID } 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';
@@ -12,6 +12,7 @@ import { SplitView, Orientation, IView, Sizing } from 'vs/base/browser/ui/splitv
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';
import { ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views';
const SPLIT_PANE_MIN_SIZE = 120;
@@ -215,6 +216,7 @@ export class TerminalTab extends Disposable implements ITerminalTab {
private _splitPaneContainer: SplitPaneContainer | undefined;
private _tabElement: HTMLElement | undefined;
private _panelPosition: Position = Position.BOTTOM;
private _terminalLocation: ViewContainerLocation = ViewContainerLocation.Panel;
private _activeInstanceIndex: number;
@@ -232,6 +234,7 @@ export class TerminalTab extends Disposable implements ITerminalTab {
shellLaunchConfigOrInstance: IShellLaunchConfig | ITerminalInstance,
@ITerminalService private readonly _terminalService: ITerminalService,
@IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService,
@IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService,
@IInstantiationService private readonly _instantiationService: IInstantiationService
) {
super();
@@ -340,12 +343,18 @@ export class TerminalTab extends Disposable implements ITerminalTab {
public attachToElement(element: HTMLElement): void {
this._container = element;
this._tabElement = document.createElement('div');
this._tabElement.classList.add('terminal-tab');
// If we already have a tab element, we can reparent it
if (!this._tabElement) {
this._tabElement = document.createElement('div');
this._tabElement.classList.add('terminal-tab');
}
this._container.appendChild(this._tabElement);
if (!this._splitPaneContainer) {
this._panelPosition = this._layoutService.getPanelPosition();
const orientation = this._panelPosition === Position.BOTTOM ? Orientation.HORIZONTAL : Orientation.VERTICAL;
this._terminalLocation = this._viewDescriptorService.getViewLocation(TERMINAL_VIEW_ID)!;
const orientation = this._terminalLocation === ViewContainerLocation.Panel && this._panelPosition === Position.BOTTOM ? Orientation.HORIZONTAL : Orientation.VERTICAL;
const newLocal = this._instantiationService.createInstance(SplitPaneContainer, this._tabElement, orientation);
this._splitPaneContainer = newLocal;
this.terminalInstances.forEach(instance => this._splitPaneContainer!.split(instance));
@@ -394,11 +403,14 @@ export class TerminalTab extends Disposable implements ITerminalTab {
if (this._splitPaneContainer) {
// Check if the panel position changed and rotate panes if so
const newPanelPosition = this._layoutService.getPanelPosition();
const panelPositionChanged = newPanelPosition !== this._panelPosition;
if (panelPositionChanged) {
const newOrientation = newPanelPosition === Position.BOTTOM ? Orientation.HORIZONTAL : Orientation.VERTICAL;
const newTerminalLocation = this._viewDescriptorService.getViewLocation(TERMINAL_VIEW_ID)!;
const terminalPositionChanged = newPanelPosition !== this._panelPosition || newTerminalLocation !== this._terminalLocation;
if (terminalPositionChanged) {
const newOrientation = newTerminalLocation === ViewContainerLocation.Panel && newPanelPosition === Position.BOTTOM ? Orientation.HORIZONTAL : Orientation.VERTICAL;
this._splitPaneContainer.setOrientation(newOrientation);
this._panelPosition = newPanelPosition;
this._terminalLocation = newTerminalLocation;
}
this._splitPaneContainer.layout(width, height);

View File

@@ -29,6 +29,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IViewDescriptorService } from 'vs/workbench/common/views';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
const FIND_FOCUS_CLASS = 'find-focused';
@@ -326,8 +327,11 @@ export class TerminalViewPane extends ViewPane {
}
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
const backgroundColor = theme.getColor(TERMINAL_BACKGROUND_COLOR);
collector.addRule(`.monaco-workbench .pane-body.integrated-terminal .terminal-outer-container { background-color: ${backgroundColor ? backgroundColor.toString() : ''}; }`);
const panelBackgroundColor = theme.getColor(TERMINAL_BACKGROUND_COLOR) || theme.getColor(PANEL_BACKGROUND);
collector.addRule(`.monaco-workbench .part.panel .pane-body.integrated-terminal .terminal-outer-container { background-color: ${panelBackgroundColor ? panelBackgroundColor.toString() : ''}; }`);
const sidebarBackgroundColor = theme.getColor(TERMINAL_BACKGROUND_COLOR) || theme.getColor(SIDE_BAR_BACKGROUND);
collector.addRule(`.monaco-workbench .part.sidebar .pane-body.integrated-terminal .terminal-outer-container { background-color: ${sidebarBackgroundColor ? sidebarBackgroundColor.toString() : ''}; }`);
const borderColor = theme.getColor(TERMINAL_BORDER_COLOR);
if (borderColor) {

View File

@@ -0,0 +1,10 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IEnvironmentVariableService } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { EnvironmentVariableService } from 'vs/workbench/contrib/terminal/common/environmentVariableService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
registerSingleton(IEnvironmentVariableService, EnvironmentVariableService, true);

View File

@@ -0,0 +1,99 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Event } from 'vs/base/common/event';
import { IProcessEnvironment } from 'vs/base/common/platform';
export const IEnvironmentVariableService = createDecorator<IEnvironmentVariableService>('environmentVariableService');
export enum EnvironmentVariableMutatorType {
Replace = 1,
Append = 2,
Prepend = 3
}
export interface IEnvironmentVariableMutator {
readonly value: string;
readonly type: EnvironmentVariableMutatorType;
}
export interface IExtensionOwnedEnvironmentVariableMutator extends IEnvironmentVariableMutator {
readonly extensionIdentifier: string;
}
export interface IEnvironmentVariableCollection {
readonly map: ReadonlyMap<string, IEnvironmentVariableMutator>;
}
export interface IEnvironmentVariableCollectionWithPersistence extends IEnvironmentVariableCollection {
readonly persistent: boolean;
}
export interface IMergedEnvironmentVariableCollectionDiff {
added: ReadonlyMap<string, IExtensionOwnedEnvironmentVariableMutator[]>;
changed: ReadonlyMap<string, IExtensionOwnedEnvironmentVariableMutator[]>;
removed: ReadonlyMap<string, IExtensionOwnedEnvironmentVariableMutator[]>;
}
/**
* Represents an environment variable collection that results from merging several collections
* together.
*/
export interface IMergedEnvironmentVariableCollection {
readonly map: ReadonlyMap<string, IExtensionOwnedEnvironmentVariableMutator[]>;
/**
* Applies this collection to a process environment.
*/
applyToProcessEnvironment(env: IProcessEnvironment): void;
/**
* Generates a diff of this connection against another.
*/
diff(other: IMergedEnvironmentVariableCollection): IMergedEnvironmentVariableCollectionDiff;
}
/**
* Tracks and persists environment variable collections as defined by extensions.
*/
export interface IEnvironmentVariableService {
_serviceBrand: undefined;
/**
* Gets a single collection constructed by merging all environment variable collections into
* one.
*/
readonly collections: ReadonlyMap<string, IEnvironmentVariableCollection>;
/**
* Gets a single collection constructed by merging all environment variable collections into
* one.
*/
readonly mergedCollection: IMergedEnvironmentVariableCollection;
/**
* An event that is fired when an extension's environment variable collection changes, the event
* provides the new merged collection.
*/
onDidChangeCollections: Event<IMergedEnvironmentVariableCollection>;
/**
* Sets an extension's environment variable collection.
*/
set(extensionIdentifier: string, collection: IEnvironmentVariableCollection): void;
/**
* Deletes an extension's environment variable collection.
*/
delete(extensionIdentifier: string): void;
}
/**
* First: Variable
* Second: Value
* Third: Type
*/
export type ISerializableEnvironmentVariableCollection = [string, IEnvironmentVariableMutator][];

View File

@@ -0,0 +1,146 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IEnvironmentVariableCollection, EnvironmentVariableMutatorType, IMergedEnvironmentVariableCollection, IMergedEnvironmentVariableCollectionDiff, IExtensionOwnedEnvironmentVariableMutator } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { IProcessEnvironment } from 'vs/base/common/platform';
export class MergedEnvironmentVariableCollection implements IMergedEnvironmentVariableCollection {
readonly map: Map<string, IExtensionOwnedEnvironmentVariableMutator[]> = new Map();
constructor(collections: Map<string, IEnvironmentVariableCollection>) {
collections.forEach((collection, extensionIdentifier) => {
const it = collection.map.entries();
let next = it.next();
while (!next.done) {
const variable = next.value[0];
let entry = this.map.get(variable);
if (!entry) {
entry = [];
this.map.set(variable, entry);
}
// If the first item in the entry is replace ignore any other entries as they would
// just get replaced by this one.
if (entry.length > 0 && entry[0].type === EnvironmentVariableMutatorType.Replace) {
next = it.next();
continue;
}
// Mutators get applied in the reverse order than they are created
const mutator = next.value[1];
entry.unshift({
extensionIdentifier,
value: mutator.value,
type: mutator.type
});
next = it.next();
}
});
}
applyToProcessEnvironment(env: IProcessEnvironment): void {
this.map.forEach((mutators, variable) => {
mutators.forEach(mutator => {
switch (mutator.type) {
case EnvironmentVariableMutatorType.Append:
env[variable] = (env[variable] || '') + mutator.value;
break;
case EnvironmentVariableMutatorType.Prepend:
env[variable] = mutator.value + (env[variable] || '');
break;
case EnvironmentVariableMutatorType.Replace:
env[variable] = mutator.value;
break;
}
});
});
}
diff(other: IMergedEnvironmentVariableCollection): IMergedEnvironmentVariableCollectionDiff {
const added: Map<string, IExtensionOwnedEnvironmentVariableMutator[]> = new Map();
const changed: Map<string, IExtensionOwnedEnvironmentVariableMutator[]> = new Map();
const removed: Map<string, IExtensionOwnedEnvironmentVariableMutator[]> = new Map();
// Find added
other.map.forEach((otherMutators, variable) => {
const currentMutators = this.map.get(variable);
const result = getMissingMutatorsFromArray(otherMutators, currentMutators);
if (result) {
added.set(variable, result);
}
});
// Find removed
this.map.forEach((currentMutators, variable) => {
const otherMutators = other.map.get(variable);
const result = getMissingMutatorsFromArray(currentMutators, otherMutators);
if (result) {
removed.set(variable, result);
}
});
// Find changed
this.map.forEach((currentMutators, variable) => {
const otherMutators = other.map.get(variable);
const result = getChangedMutatorsFromArray(currentMutators, otherMutators);
if (result) {
changed.set(variable, result);
}
});
return { added, changed, removed };
}
}
function getMissingMutatorsFromArray(
current: IExtensionOwnedEnvironmentVariableMutator[],
other: IExtensionOwnedEnvironmentVariableMutator[] | undefined
): IExtensionOwnedEnvironmentVariableMutator[] | undefined {
// If it doesn't exist, all are removed
if (!other) {
return current;
}
// Create a map to help
const otherMutatorExtensions = new Set<string>();
other.forEach(m => otherMutatorExtensions.add(m.extensionIdentifier));
// Find entries removed from other
const result: IExtensionOwnedEnvironmentVariableMutator[] = [];
current.forEach(mutator => {
if (!otherMutatorExtensions.has(mutator.extensionIdentifier)) {
result.push(mutator);
}
});
return result.length === 0 ? undefined : result;
}
function getChangedMutatorsFromArray(
current: IExtensionOwnedEnvironmentVariableMutator[],
other: IExtensionOwnedEnvironmentVariableMutator[] | undefined
): IExtensionOwnedEnvironmentVariableMutator[] | undefined {
// If it doesn't exist, none are changed (they are removed)
if (!other) {
return undefined;
}
// Create a map to help
const otherMutatorExtensions = new Map<string, IExtensionOwnedEnvironmentVariableMutator>();
other.forEach(m => otherMutatorExtensions.set(m.extensionIdentifier, m));
// Find entries that exist in both but are not equal
const result: IExtensionOwnedEnvironmentVariableMutator[] = [];
current.forEach(mutator => {
const otherMutator = otherMutatorExtensions.get(mutator.extensionIdentifier);
if (otherMutator && (mutator.type !== otherMutator.type || mutator.value !== otherMutator.value)) {
// Return the new result, not the old one
result.push(otherMutator);
}
});
return result.length === 0 ? undefined : result;
}

View File

@@ -0,0 +1,120 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IEnvironmentVariableService, IMergedEnvironmentVariableCollection, ISerializableEnvironmentVariableCollection, IEnvironmentVariableCollectionWithPersistence } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { Event, Emitter } from 'vs/base/common/event';
import { debounce, throttle } from 'vs/base/common/decorators';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { MergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableCollection';
import { deserializeEnvironmentVariableCollection, serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared';
const ENVIRONMENT_VARIABLE_COLLECTIONS_KEY = 'terminal.integrated.environmentVariableCollections';
interface ISerializableExtensionEnvironmentVariableCollection {
extensionIdentifier: string,
collection: ISerializableEnvironmentVariableCollection
}
/**
* Tracks and persists environment variable collections as defined by extensions.
*/
export class EnvironmentVariableService implements IEnvironmentVariableService {
_serviceBrand: undefined;
collections: Map<string, IEnvironmentVariableCollectionWithPersistence> = new Map();
mergedCollection: IMergedEnvironmentVariableCollection;
private readonly _onDidChangeCollections = new Emitter<IMergedEnvironmentVariableCollection>();
get onDidChangeCollections(): Event<IMergedEnvironmentVariableCollection> { return this._onDidChangeCollections.event; }
constructor(
@IExtensionService private readonly _extensionService: IExtensionService,
@IStorageService private readonly _storageService: IStorageService
) {
const serializedPersistedCollections = this._storageService.get(ENVIRONMENT_VARIABLE_COLLECTIONS_KEY, StorageScope.WORKSPACE);
if (serializedPersistedCollections) {
const collectionsJson: ISerializableExtensionEnvironmentVariableCollection[] = JSON.parse(serializedPersistedCollections);
collectionsJson.forEach(c => this.collections.set(c.extensionIdentifier, {
persistent: true,
map: deserializeEnvironmentVariableCollection(c.collection)
}));
// Asynchronously invalidate collections where extensions have been uninstalled, this is
// async to avoid making all functions on the service synchronous and because extensions
// being uninstalled is rare.
this._invalidateExtensionCollections();
}
this.mergedCollection = this._resolveMergedCollection();
// Listen for uninstalled/disabled extensions
this._extensionService.onDidChangeExtensions(() => this._invalidateExtensionCollections());
}
set(extensionIdentifier: string, collection: IEnvironmentVariableCollectionWithPersistence): void {
this.collections.set(extensionIdentifier, collection);
this._updateCollections();
}
delete(extensionIdentifier: string): void {
this.collections.delete(extensionIdentifier);
this._updateCollections();
}
private _updateCollections(): void {
this._persistCollectionsEventually();
this.mergedCollection = this._resolveMergedCollection();
this._notifyCollectionUpdatesEventually();
}
@throttle(1000)
private _persistCollectionsEventually(): void {
this._persistCollections();
}
protected _persistCollections(): void {
const collectionsJson: ISerializableExtensionEnvironmentVariableCollection[] = [];
this.collections.forEach((collection, extensionIdentifier) => {
if (collection.persistent) {
collectionsJson.push({
extensionIdentifier,
collection: serializeEnvironmentVariableCollection(this.collections.get(extensionIdentifier)!.map)
});
}
});
const stringifiedJson = JSON.stringify(collectionsJson);
this._storageService.store(ENVIRONMENT_VARIABLE_COLLECTIONS_KEY, stringifiedJson, StorageScope.WORKSPACE);
}
@debounce(1000)
private _notifyCollectionUpdatesEventually(): void {
this._notifyCollectionUpdates();
}
protected _notifyCollectionUpdates(): void {
this._onDidChangeCollections.fire(this.mergedCollection);
}
private _resolveMergedCollection(): IMergedEnvironmentVariableCollection {
return new MergedEnvironmentVariableCollection(this.collections);
}
private async _invalidateExtensionCollections(): Promise<void> {
await this._extensionService.whenInstalledExtensionsRegistered();
const registeredExtensions = await this._extensionService.getExtensions();
let changes = false;
this.collections.forEach((_, extensionIdentifier) => {
const isExtensionRegistered = registeredExtensions.some(r => r.identifier.value === extensionIdentifier);
if (!isExtensionRegistered) {
this.collections.delete(extensionIdentifier);
changes = true;
}
});
if (changes) {
this._updateCollections();
}
}
}

View File

@@ -0,0 +1,18 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IEnvironmentVariableMutator, ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable';
// This file is shared between the renderer and extension host
export function serializeEnvironmentVariableCollection(collection: ReadonlyMap<string, IEnvironmentVariableMutator>): ISerializableEnvironmentVariableCollection {
return [...collection.entries()];
}
export function deserializeEnvironmentVariableCollection(
serializedCollection: ISerializableEnvironmentVariableCollection
): Map<string, IEnvironmentVariableMutator> {
return new Map<string, IEnvironmentVariableMutator>(serializedCollection);
}

View File

@@ -6,7 +6,7 @@
import * as nls from 'vs/nls';
import { registerColor, ColorIdentifier, ColorDefaults } from 'vs/platform/theme/common/colorRegistry';
import { PANEL_BORDER, PANEL_BACKGROUND } from 'vs/workbench/common/theme';
import { PANEL_BORDER } from 'vs/workbench/common/theme';
/**
* The color identifiers for the terminal's ansi colors. The index in the array corresponds to the index
@@ -14,11 +14,7 @@ import { PANEL_BORDER, PANEL_BACKGROUND } from 'vs/workbench/common/theme';
*/
export const ansiColorIdentifiers: ColorIdentifier[] = [];
export const TERMINAL_BACKGROUND_COLOR = registerColor('terminal.background', {
dark: PANEL_BACKGROUND,
light: PANEL_BACKGROUND,
hc: PANEL_BACKGROUND
}, nls.localize('terminal.background', 'The background color of the terminal, this allows coloring the terminal differently to the panel.'));
export const TERMINAL_BACKGROUND_COLOR = registerColor('terminal.background', null, nls.localize('terminal.background', 'The background color of the terminal, this allows coloring the terminal differently to the panel.'));
export const TERMINAL_FOREGROUND_COLOR = registerColor('terminal.foreground', {
light: '#333333',
dark: '#CCCCCC',

View File

@@ -0,0 +1,321 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { deepStrictEqual, strictEqual } from 'assert';
import { EnvironmentVariableMutatorType } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { IProcessEnvironment } from 'vs/base/common/platform';
import { MergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableCollection';
import { deserializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared';
suite('EnvironmentVariable - MergedEnvironmentVariableCollection', () => {
suite('ctor', () => {
test('Should keep entries that come after a Prepend or Append type mutators', () => {
const merged = new MergedEnvironmentVariableCollection(new Map([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a1', type: EnvironmentVariableMutatorType.Prepend }]
])
}],
['ext2', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a2', type: EnvironmentVariableMutatorType.Append }]
])
}],
['ext3', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a3', type: EnvironmentVariableMutatorType.Prepend }]
])
}],
['ext4', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a4', type: EnvironmentVariableMutatorType.Append }]
])
}]
]));
deepStrictEqual([...merged.map.entries()], [
['A', [
{ extensionIdentifier: 'ext4', type: EnvironmentVariableMutatorType.Append, value: 'a4' },
{ extensionIdentifier: 'ext3', type: EnvironmentVariableMutatorType.Prepend, value: 'a3' },
{ extensionIdentifier: 'ext2', type: EnvironmentVariableMutatorType.Append, value: 'a2' },
{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Prepend, value: 'a1' }
]]
]);
});
test('Should remove entries that come after a Replace type mutator', () => {
const merged = new MergedEnvironmentVariableCollection(new Map([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a1', type: EnvironmentVariableMutatorType.Prepend }]
])
}],
['ext2', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a2', type: EnvironmentVariableMutatorType.Append }]
])
}],
['ext3', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a3', type: EnvironmentVariableMutatorType.Replace }]
])
}],
['ext4', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a4', type: EnvironmentVariableMutatorType.Append }]
])
}]
]));
deepStrictEqual([...merged.map.entries()], [
['A', [
{ extensionIdentifier: 'ext3', type: EnvironmentVariableMutatorType.Replace, value: 'a3' },
{ extensionIdentifier: 'ext2', type: EnvironmentVariableMutatorType.Append, value: 'a2' },
{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Prepend, value: 'a1' }
]]
], 'The ext4 entry should be removed as it comes after a Replace');
});
});
suite('applyToProcessEnvironment', () => {
test('should apply the collection to an environment', () => {
const merged = new MergedEnvironmentVariableCollection(new Map([
['ext', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }],
['B', { value: 'b', type: EnvironmentVariableMutatorType.Append }],
['C', { value: 'c', type: EnvironmentVariableMutatorType.Prepend }]
])
}]
]));
const env: IProcessEnvironment = {
A: 'foo',
B: 'bar',
C: 'baz'
};
merged.applyToProcessEnvironment(env);
deepStrictEqual(env, {
A: 'a',
B: 'barb',
C: 'cbaz'
});
});
test('should apply the collection to environment entries with no values', () => {
const merged = new MergedEnvironmentVariableCollection(new Map([
['ext', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }],
['B', { value: 'b', type: EnvironmentVariableMutatorType.Append }],
['C', { value: 'c', type: EnvironmentVariableMutatorType.Prepend }]
])
}]
]));
const env: IProcessEnvironment = {};
merged.applyToProcessEnvironment(env);
deepStrictEqual(env, {
A: 'a',
B: 'b',
C: 'c'
});
});
});
suite('diff', () => {
test('should generate added diffs from when the first entry is added', () => {
const merged1 = new MergedEnvironmentVariableCollection(new Map([]));
const merged2 = new MergedEnvironmentVariableCollection(new Map([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }]
])
}]
]));
const diff = merged1.diff(merged2);
strictEqual(diff.changed.size, 0);
strictEqual(diff.removed.size, 0);
const entries = [...diff.added.entries()];
deepStrictEqual(entries, [
['A', [{ extensionIdentifier: 'ext1', value: 'a', type: EnvironmentVariableMutatorType.Replace }]]
]);
});
test('should generate added diffs from the same extension', () => {
const merged1 = new MergedEnvironmentVariableCollection(new Map([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }]
])
}]
]));
const merged2 = new MergedEnvironmentVariableCollection(new Map([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }],
['B', { value: 'b', type: EnvironmentVariableMutatorType.Append }]
])
}]
]));
const diff = merged1.diff(merged2);
strictEqual(diff.changed.size, 0);
strictEqual(diff.removed.size, 0);
const entries = [...diff.added.entries()];
deepStrictEqual(entries, [
['B', [{ extensionIdentifier: 'ext1', value: 'b', type: EnvironmentVariableMutatorType.Append }]]
]);
});
test('should generate added diffs from a different extension', () => {
const merged1 = new MergedEnvironmentVariableCollection(new Map([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a1', type: EnvironmentVariableMutatorType.Prepend }]
])
}]
]));
const merged2 = new MergedEnvironmentVariableCollection(new Map([
['ext2', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a2', type: EnvironmentVariableMutatorType.Append }]
])
}],
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a1', type: EnvironmentVariableMutatorType.Prepend }]
])
}]
]));
const diff = merged1.diff(merged2);
strictEqual(diff.changed.size, 0);
strictEqual(diff.removed.size, 0);
deepStrictEqual([...diff.added.entries()], [
['A', [{ extensionIdentifier: 'ext2', value: 'a2', type: EnvironmentVariableMutatorType.Append }]]
]);
const merged3 = new MergedEnvironmentVariableCollection(new Map([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a1', type: EnvironmentVariableMutatorType.Prepend }]
])
}],
// This entry should get removed
['ext2', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a2', type: EnvironmentVariableMutatorType.Append }]
])
}]
]));
const diff2 = merged1.diff(merged3);
strictEqual(diff2.changed.size, 0);
strictEqual(diff2.removed.size, 0);
deepStrictEqual([...diff.added.entries()], [...diff2.added.entries()], 'Swapping the order of the entries in the other collection should yield the same result');
});
test('should remove entries in the diff that come after a Replce', () => {
const merged1 = new MergedEnvironmentVariableCollection(new Map([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a1', type: EnvironmentVariableMutatorType.Replace }]
])
}]
]));
const merged4 = new MergedEnvironmentVariableCollection(new Map([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a1', type: EnvironmentVariableMutatorType.Replace }]
])
}],
// This entry should get removed as it comes after a replace
['ext2', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a2', type: EnvironmentVariableMutatorType.Append }]
])
}]
]));
const diff = merged1.diff(merged4);
strictEqual(diff.changed.size, 0);
strictEqual(diff.removed.size, 0);
deepStrictEqual([...diff.added.entries()], [], 'Replace should ignore any entries after it');
});
test('should generate removed diffs', () => {
const merged1 = new MergedEnvironmentVariableCollection(new Map([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }],
['B', { value: 'b', type: EnvironmentVariableMutatorType.Replace }]
])
}]
]));
const merged2 = new MergedEnvironmentVariableCollection(new Map([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }]
])
}]
]));
const diff = merged1.diff(merged2);
strictEqual(diff.changed.size, 0);
strictEqual(diff.added.size, 0);
deepStrictEqual([...diff.removed.entries()], [
['B', [{ extensionIdentifier: 'ext1', value: 'b', type: EnvironmentVariableMutatorType.Replace }]]
]);
});
test('should generate changed diffs', () => {
const merged1 = new MergedEnvironmentVariableCollection(new Map([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a1', type: EnvironmentVariableMutatorType.Replace }],
['B', { value: 'b', type: EnvironmentVariableMutatorType.Replace }]
])
}]
]));
const merged2 = new MergedEnvironmentVariableCollection(new Map([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a2', type: EnvironmentVariableMutatorType.Replace }],
['B', { value: 'b', type: EnvironmentVariableMutatorType.Append }]
])
}]
]));
const diff = merged1.diff(merged2);
strictEqual(diff.added.size, 0);
strictEqual(diff.removed.size, 0);
deepStrictEqual([...diff.changed.entries()], [
['A', [{ extensionIdentifier: 'ext1', value: 'a2', type: EnvironmentVariableMutatorType.Replace }]],
['B', [{ extensionIdentifier: 'ext1', value: 'b', type: EnvironmentVariableMutatorType.Append }]]
]);
});
test('should generate diffs with added, changed and removed', () => {
const merged1 = new MergedEnvironmentVariableCollection(new Map([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a1', type: EnvironmentVariableMutatorType.Replace }],
['B', { value: 'b', type: EnvironmentVariableMutatorType.Prepend }]
])
}]
]));
const merged2 = new MergedEnvironmentVariableCollection(new Map([
['ext1', {
map: deserializeEnvironmentVariableCollection([
['A', { value: 'a2', type: EnvironmentVariableMutatorType.Replace }],
['C', { value: 'c', type: EnvironmentVariableMutatorType.Append }]
])
}]
]));
const diff = merged1.diff(merged2);
deepStrictEqual([...diff.added.entries()], [
['C', [{ extensionIdentifier: 'ext1', value: 'c', type: EnvironmentVariableMutatorType.Append }]],
]);
deepStrictEqual([...diff.removed.entries()], [
['B', [{ extensionIdentifier: 'ext1', value: 'b', type: EnvironmentVariableMutatorType.Prepend }]]
]);
deepStrictEqual([...diff.changed.entries()], [
['A', [{ extensionIdentifier: 'ext1', value: 'a2', type: EnvironmentVariableMutatorType.Replace }]]
]);
});
});
});

View File

@@ -0,0 +1,116 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { deepStrictEqual } from 'assert';
import { TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
import { EnvironmentVariableService } from 'vs/workbench/contrib/terminal/common/environmentVariableService';
import { EnvironmentVariableMutatorType, IEnvironmentVariableMutator } from 'vs/workbench/contrib/terminal/common/environmentVariable';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { Emitter } from 'vs/base/common/event';
import { IProcessEnvironment } from 'vs/base/common/platform';
class TestEnvironmentVariableService extends EnvironmentVariableService {
persistCollections(): void { this._persistCollections(); }
notifyCollectionUpdates(): void { this._notifyCollectionUpdates(); }
}
suite('EnvironmentVariable - EnvironmentVariableService', () => {
let instantiationService: TestInstantiationService;
let environmentVariableService: TestEnvironmentVariableService;
let storageService: TestStorageService;
let changeExtensionsEvent: Emitter<void>;
setup(() => {
changeExtensionsEvent = new Emitter<void>();
instantiationService = new TestInstantiationService();
instantiationService.stub(IExtensionService, TestExtensionService);
storageService = new TestStorageService();
instantiationService.stub(IStorageService, storageService);
instantiationService.stub(IExtensionService, TestExtensionService);
instantiationService.stub(IExtensionService, 'onDidChangeExtensions', changeExtensionsEvent.event);
instantiationService.stub(IExtensionService, 'getExtensions', [
{ identifier: { value: 'ext1' } },
{ identifier: { value: 'ext2' } },
{ identifier: { value: 'ext3' } }
]);
environmentVariableService = instantiationService.createInstance(TestEnvironmentVariableService);
});
test('should persist collections to the storage service and be able to restore from them', () => {
const collection = new Map<string, IEnvironmentVariableMutator>();
collection.set('A', { value: 'a', type: EnvironmentVariableMutatorType.Replace });
collection.set('B', { value: 'b', type: EnvironmentVariableMutatorType.Append });
collection.set('C', { value: 'c', type: EnvironmentVariableMutatorType.Prepend });
environmentVariableService.set('ext1', { map: collection, persistent: true });
deepStrictEqual([...environmentVariableService.mergedCollection.map.entries()], [
['A', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Replace, value: 'a' }]],
['B', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Append, value: 'b' }]],
['C', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Prepend, value: 'c' }]]
]);
// Persist with old service, create a new service with the same storage service to verify restore
environmentVariableService.persistCollections();
const service2: TestEnvironmentVariableService = instantiationService.createInstance(TestEnvironmentVariableService);
deepStrictEqual([...service2.mergedCollection.map.entries()], [
['A', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Replace, value: 'a' }]],
['B', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Append, value: 'b' }]],
['C', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Prepend, value: 'c' }]]
]);
});
suite('mergedCollection', () => {
test('should overwrite any other variable with the first extension that replaces', () => {
const collection1 = new Map<string, IEnvironmentVariableMutator>();
const collection2 = new Map<string, IEnvironmentVariableMutator>();
const collection3 = new Map<string, IEnvironmentVariableMutator>();
collection1.set('A', { value: 'a1', type: EnvironmentVariableMutatorType.Append });
collection1.set('B', { value: 'b1', type: EnvironmentVariableMutatorType.Replace });
collection2.set('A', { value: 'a2', type: EnvironmentVariableMutatorType.Replace });
collection2.set('B', { value: 'b2', type: EnvironmentVariableMutatorType.Append });
collection3.set('A', { value: 'a3', type: EnvironmentVariableMutatorType.Prepend });
collection3.set('B', { value: 'b3', type: EnvironmentVariableMutatorType.Replace });
environmentVariableService.set('ext1', { map: collection1, persistent: true });
environmentVariableService.set('ext2', { map: collection2, persistent: true });
environmentVariableService.set('ext3', { map: collection3, persistent: true });
deepStrictEqual([...environmentVariableService.mergedCollection.map.entries()], [
['A', [
{ extensionIdentifier: 'ext2', type: EnvironmentVariableMutatorType.Replace, value: 'a2' },
{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Append, value: 'a1' }
]],
['B', [{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Replace, value: 'b1' }]]
]);
});
test('should correctly apply the environment values from multiple extension contributions in the correct order', () => {
const collection1 = new Map<string, IEnvironmentVariableMutator>();
const collection2 = new Map<string, IEnvironmentVariableMutator>();
const collection3 = new Map<string, IEnvironmentVariableMutator>();
collection1.set('A', { value: ':a1', type: EnvironmentVariableMutatorType.Append });
collection2.set('A', { value: 'a2:', type: EnvironmentVariableMutatorType.Prepend });
collection3.set('A', { value: 'a3', type: EnvironmentVariableMutatorType.Replace });
environmentVariableService.set('ext1', { map: collection1, persistent: true });
environmentVariableService.set('ext2', { map: collection2, persistent: true });
environmentVariableService.set('ext3', { map: collection3, persistent: true });
// The entries should be ordered in the order they are applied
deepStrictEqual([...environmentVariableService.mergedCollection.map.entries()], [
['A', [
{ extensionIdentifier: 'ext3', type: EnvironmentVariableMutatorType.Replace, value: 'a3' },
{ extensionIdentifier: 'ext2', type: EnvironmentVariableMutatorType.Prepend, value: 'a2:' },
{ extensionIdentifier: 'ext1', type: EnvironmentVariableMutatorType.Append, value: ':a1' }
]]
]);
// Verify the entries get applied to the environment as expected
const env: IProcessEnvironment = { A: 'foo' };
environmentVariableService.mergedCollection.applyToProcessEnvironment(env);
deepStrictEqual(env, { A: 'a2:a3:a1' });
});
});
});

View File

@@ -0,0 +1,38 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { deepStrictEqual } from 'assert';
import { deserializeEnvironmentVariableCollection, serializeEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableShared';
import { EnvironmentVariableMutatorType, IEnvironmentVariableMutator } from 'vs/workbench/contrib/terminal/common/environmentVariable';
suite('EnvironmentVariable - deserializeEnvironmentVariableCollection', () => {
test('should construct correctly with 3 arguments', () => {
const c = deserializeEnvironmentVariableCollection([
['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }],
['B', { value: 'b', type: EnvironmentVariableMutatorType.Append }],
['C', { value: 'c', type: EnvironmentVariableMutatorType.Prepend }]
]);
const keys = [...c.keys()];
deepStrictEqual(keys, ['A', 'B', 'C']);
deepStrictEqual(c.get('A'), { value: 'a', type: EnvironmentVariableMutatorType.Replace });
deepStrictEqual(c.get('B'), { value: 'b', type: EnvironmentVariableMutatorType.Append });
deepStrictEqual(c.get('C'), { value: 'c', type: EnvironmentVariableMutatorType.Prepend });
});
});
suite('EnvironmentVariable - serializeEnvironmentVariableCollection', () => {
test('should correctly serialize the object', () => {
const collection = new Map<string, IEnvironmentVariableMutator>();
deepStrictEqual(serializeEnvironmentVariableCollection(collection), []);
collection.set('A', { value: 'a', type: EnvironmentVariableMutatorType.Replace });
collection.set('B', { value: 'b', type: EnvironmentVariableMutatorType.Append });
collection.set('C', { value: 'c', type: EnvironmentVariableMutatorType.Prepend });
deepStrictEqual(serializeEnvironmentVariableCollection(collection), [
['A', { value: 'a', type: EnvironmentVariableMutatorType.Replace }],
['B', { value: 'b', type: EnvironmentVariableMutatorType.Append }],
['C', { value: 'c', type: EnvironmentVariableMutatorType.Prepend }]
]);
});
});