disable decorations (#154430)

This commit is contained in:
Megan Rogge 2022-07-13 10:17:01 -07:00 committed by GitHub
parent a03ebcaa0d
commit ff31a8c6fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 169 additions and 55 deletions

View file

@ -14,6 +14,10 @@
position: relative;
}
.terminal-command-decoration.hide {
visibility: hidden;
}
.monaco-workbench .pane-body.integrated-terminal .terminal-outer-container,
.monaco-workbench .pane-body.integrated-terminal .terminal-groups-container,
.monaco-workbench .pane-body.integrated-terminal .terminal-group,

View file

@ -46,6 +46,7 @@ import { getTerminalActionBarArgs } from 'vs/workbench/contrib/terminal/browser/
import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey';
import { getShellIntegrationTooltip } from 'vs/workbench/contrib/terminal/browser/terminalTooltip';
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities';
export class TerminalViewPane extends ViewPane {
private _actions: IAction[] | undefined;
@ -65,7 +66,7 @@ export class TerminalViewPane extends ViewPane {
@IKeybindingService keybindingService: IKeybindingService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
@IConfigurationService configurationService: IConfigurationService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@IContextMenuService private readonly _contextMenuService: IContextMenuService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@ITerminalService private readonly _terminalService: ITerminalService,
@ -81,7 +82,7 @@ export class TerminalViewPane extends ViewPane {
@ITerminalProfileResolverService private readonly _terminalProfileResolverService: ITerminalProfileResolverService,
@IThemeService private readonly _themeService: IThemeService
) {
super(options, keybindingService, _contextMenuService, configurationService, _contextKeyService, viewDescriptorService, _instantiationService, openerService, themeService, telemetryService);
super(options, keybindingService, _contextMenuService, _configurationService, _contextKeyService, viewDescriptorService, _instantiationService, openerService, themeService, telemetryService);
this._register(this._terminalService.onDidRegisterProcessSupport(() => {
if (this._actions) {
for (const action of this._actions) {
@ -111,20 +112,34 @@ export class TerminalViewPane extends ViewPane {
this._terminalTabbedView?.rerenderTabs();
}
}));
configurationService.onDidChangeConfiguration(e => {
if ((e.affectsConfiguration(TerminalSettingId.ShellIntegrationDecorationsEnabled) && !configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationsEnabled)) ||
(e.affectsConfiguration(TerminalSettingId.ShellIntegrationEnabled) && !configurationService.getValue(TerminalSettingId.ShellIntegrationEnabled))) {
this._parentDomElement?.classList.remove('shell-integration');
} else if (configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationsEnabled) && configurationService.getValue(TerminalSettingId.ShellIntegrationEnabled)) {
this._parentDomElement?.classList.add('shell-integration');
_configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(TerminalSettingId.ShellIntegrationDecorationsEnabled) || e.affectsConfiguration(TerminalSettingId.ShellIntegrationEnabled)) {
this._updateForShellIntegration();
}
});
this._register(this._terminalService.onDidCreateInstance((i) => {
i.capabilities.onDidAddCapability(c => {
if (c === TerminalCapability.CommandDetection && !this._gutterDecorationsEnabled()) {
this._parentDomElement?.classList.add('shell-integration');
}
});
}));
this._updateForShellIntegration();
}
if (configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationsEnabled) && configurationService.getValue(TerminalSettingId.ShellIntegrationEnabled)) {
private _updateForShellIntegration() {
if (this._gutterDecorationsEnabled()) {
this._parentDomElement?.classList.add('shell-integration');
} else {
this._parentDomElement?.classList.remove('shell-integration');
}
}
private _gutterDecorationsEnabled(): boolean {
const decorationsEnabled = this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationsEnabled);
return (decorationsEnabled === 'both' || decorationsEnabled === 'gutter') && this._configurationService.getValue(TerminalSettingId.ShellIntegrationEnabled);
}
override renderBody(container: HTMLElement): void {
super.renderBody(container);

View file

@ -28,13 +28,14 @@ import { IGenericMarkProperties } from 'vs/platform/terminal/common/terminalProc
const enum DecorationSelector {
CommandDecoration = 'terminal-command-decoration',
Hide = 'hide',
ErrorColor = 'error',
DefaultColor = 'default-color',
Default = 'default',
Codicon = 'codicon',
XtermDecoration = 'xterm-decoration',
OverviewRuler = 'xterm-decoration-overview-ruler',
GenericMarkerIcon = 'codicon-circle-small-filled'
GenericMarkerIcon = 'codicon-circle-small-filled',
OverviewRuler = '.xterm-decoration-overview-ruler'
}
const enum DecorationStyles {
@ -51,6 +52,8 @@ export class DecorationAddon extends Disposable implements ITerminalAddon {
private _contextMenuVisible: boolean = false;
private _decorations: Map<number, IDisposableDecoration> = new Map();
private _placeholderDecoration: IDecoration | undefined;
private _showGutterDecorations?: boolean;
private _showOverviewRulerDecorations?: boolean;
private readonly _onDidRequestRunCommand = this._register(new Emitter<{ command: ITerminalCommand; copyAsHtml?: boolean }>());
readonly onDidRequestRunCommand = this._onDidRequestRunCommand.event;
@ -79,9 +82,67 @@ export class DecorationAddon extends Disposable implements ITerminalAddon {
this.refreshLayouts();
} else if (e.affectsConfiguration('workbench.colorCustomizations')) {
this._refreshStyles(true);
} else if (e.affectsConfiguration(TerminalSettingId.ShellIntegrationDecorationsEnabled)) {
if (this._commandDetectionListeners) {
dispose(this._commandDetectionListeners);
this._commandDetectionListeners = undefined;
}
this._updateDecorationVisibility();
}
});
this._themeService.onDidColorThemeChange(() => this._refreshStyles(true));
this._updateDecorationVisibility();
this._register(this._capabilities.onDidAddCapability(c => {
if (c === TerminalCapability.CommandDetection) {
this._addCommandDetectionListeners();
}
}));
this._register(this._capabilities.onDidRemoveCapability(c => {
if (c === TerminalCapability.CommandDetection) {
if (this._commandDetectionListeners) {
dispose(this._commandDetectionListeners);
this._commandDetectionListeners = undefined;
}
}
}));
}
private _updateDecorationVisibility(): void {
const showDecorations = this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationsEnabled);
this._showGutterDecorations = (showDecorations === 'both' || showDecorations === 'gutter');
this._showOverviewRulerDecorations = (showDecorations === 'both' || showDecorations === 'overviewRuler');
this._disposeAllDecorations();
if (this._showGutterDecorations || this._showOverviewRulerDecorations) {
this._attachToCommandCapability();
this._updateGutterDecorationVisibility();
}
const currentCommand = this._capabilities.get(TerminalCapability.CommandDetection)?.executingCommandObject;
if (currentCommand) {
this.registerCommandDecoration(currentCommand, true);
}
}
private _disposeAllDecorations(): void {
this._placeholderDecoration?.dispose();
for (const value of this._decorations.values()) {
value.decoration.dispose();
dispose(value.disposables);
}
}
private _updateGutterDecorationVisibility(): void {
const commandDecorationElements = document.querySelectorAll(DecorationSelector.CommandDecoration);
for (const commandDecorationElement of commandDecorationElements) {
this._updateCommandDecorationVisibility(commandDecorationElement);
}
}
private _updateCommandDecorationVisibility(commandDecorationElement: Element): void {
if (this._showGutterDecorations) {
commandDecorationElement.classList.remove(DecorationSelector.Hide);
} else {
commandDecorationElement.classList.add(DecorationSelector.Hide);
}
}
public refreshLayouts(): void {
@ -128,31 +189,14 @@ export class DecorationAddon extends Disposable implements ITerminalAddon {
public clearDecorations(): void {
this._placeholderDecoration?.marker.dispose();
this._clearPlaceholder();
for (const value of this._decorations.values()) {
value.decoration.dispose();
dispose(value.disposables);
}
this._disposeAllDecorations();
this._decorations.clear();
}
private _attachToCommandCapability(): void {
if (this._capabilities.has(TerminalCapability.CommandDetection)) {
this._addCommandDetectionListeners();
} else {
this._register(this._capabilities.onDidAddCapability(c => {
if (c === TerminalCapability.CommandDetection) {
this._addCommandDetectionListeners();
}
}));
}
this._register(this._capabilities.onDidRemoveCapability(c => {
if (c === TerminalCapability.CommandDetection) {
if (this._commandDetectionListeners) {
dispose(this._commandDetectionListeners);
this._commandDetectionListeners = undefined;
}
}
}));
}
private _addCommandDetectionListeners(): void {
@ -204,13 +248,12 @@ export class DecorationAddon extends Disposable implements ITerminalAddon {
}
registerCommandDecoration(command: ITerminalCommand, beforeCommandExecution?: boolean): IDecoration | undefined {
if (!this._terminal || (beforeCommandExecution && command.genericMarkProperties)) {
if (!this._terminal || (beforeCommandExecution && command.genericMarkProperties) || (!this._showGutterDecorations && !this._showOverviewRulerDecorations)) {
return undefined;
}
if (!command.marker) {
throw new Error(`cannot add a decoration for a command ${JSON.stringify(command)} with no marker`);
}
this._clearPlaceholder();
let color = command.exitCode === undefined ? defaultColor : command.exitCode ? errorColor : successColor;
if (color && typeof color !== 'string') {
@ -220,9 +263,9 @@ export class DecorationAddon extends Disposable implements ITerminalAddon {
}
const decoration = this._terminal.registerDecoration({
marker: command.marker,
overviewRulerOptions: beforeCommandExecution
overviewRulerOptions: this._showOverviewRulerDecorations ? (beforeCommandExecution
? { color, position: 'left' }
: { color, position: command.exitCode ? 'right' : 'left' }
: { color, position: command.exitCode ? 'right' : 'left' }) : undefined
});
if (!decoration) {
return undefined;
@ -287,20 +330,25 @@ export class DecorationAddon extends Disposable implements ITerminalAddon {
element.classList.remove(classes);
}
element.classList.add(DecorationSelector.CommandDecoration, DecorationSelector.Codicon, DecorationSelector.XtermDecoration);
if (genericMarkProperties) {
element.classList.add(DecorationSelector.DefaultColor, DecorationSelector.GenericMarkerIcon);
if (!genericMarkProperties.hoverMessage) {
//disable the mouse pointer
element.classList.add(DecorationSelector.Default);
}
} else if (exitCode === undefined) {
element.classList.add(DecorationSelector.DefaultColor, DecorationSelector.Default);
element.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationIcon)}`);
} else if (exitCode) {
element.classList.add(DecorationSelector.ErrorColor);
element.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationIconError)}`);
} else {
element.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationIconSuccess)}`);
// command decoration
this._updateCommandDecorationVisibility(element);
if (exitCode === undefined) {
element.classList.add(DecorationSelector.DefaultColor, DecorationSelector.Default);
element.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationIcon)}`);
} else if (exitCode) {
element.classList.add(DecorationSelector.ErrorColor);
element.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationIconError)}`);
} else {
element.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationIconSuccess)}`);
}
}
}

View file

@ -117,17 +117,17 @@ const terminalConfiguration: IConfigurationNode = {
[TerminalSettingId.ShellIntegrationDecorationIconSuccess]: {
type: 'string',
default: 'primitive-dot',
markdownDescription: localize('terminal.integrated.shellIntegration.decorationIconSuccess', "Controls the icon that will be used for each command in terminals with shell integration enabled that do not have an associated exit code. Set to {0} to hide the icon or disable decorations with {1}.", '`\'\'`', '`#terminal.integrated.shellIntegration.decorationsEnabled#`')
markdownDescription: localize('terminal.integrated.shellIntegration.decorationIconSuccess', "Controls the icon that will be used for each command in terminals with shell integration enabled that do not have an associated exit code. Set to {0} to hide the icon or disable decorations with {1}.", '`\"\"`', '`#terminal.integrated.shellIntegration.decorationsEnabled#`')
},
[TerminalSettingId.ShellIntegrationDecorationIconError]: {
type: 'string',
default: 'error-small',
markdownDescription: localize('terminal.integrated.shellIntegration.decorationIconError', "Controls the icon that will be used for each command in terminals with shell integration enabled that do have an associated exit code. Set to {0} to hide the icon or disable decorations with {1}.", '`\'\'`', '`#terminal.integrated.shellIntegration.decorationsEnabled#`')
markdownDescription: localize('terminal.integrated.shellIntegration.decorationIconError', "Controls the icon that will be used for each command in terminals with shell integration enabled that do have an associated exit code. Set to {0} to hide the icon or disable decorations with {1}.", '`\"\"`', '`#terminal.integrated.shellIntegration.decorationsEnabled#`')
},
[TerminalSettingId.ShellIntegrationDecorationIcon]: {
type: 'string',
default: 'circle-outline',
markdownDescription: localize('terminal.integrated.shellIntegration.decorationIcon', "Controls the icon that will be used for skipped/empty commands. Set to {0} to hide the icon or disable decorations with {1}.", '`\'\'`', '`#terminal.integrated.shellIntegration.decorationsEnabled#`')
markdownDescription: localize('terminal.integrated.shellIntegration.decorationIcon', "Controls the icon that will be used for skipped/empty commands. Set to {0} to hide the icon or disable decorations with {1}.", '`\"\"`', '`#terminal.integrated.shellIntegration.decorationsEnabled#`')
},
[TerminalSettingId.TabsFocusMode]: {
type: 'string',
@ -546,15 +546,22 @@ const terminalConfiguration: IConfigurationNode = {
},
[TerminalSettingId.ShellIntegrationEnabled]: {
restricted: true,
markdownDescription: localize('terminal.integrated.shellIntegration.enabled', "Enable features like enhanced command tracking and current working directory detection. \n\nShell integration works by injecting the shell with a startup script. The script gives VS Code insight into what is happening within the terminal.\n\nSupported shells:\n\n- Linux/macOS: bash, pwsh, zsh\n - Windows: pwsh\n\nThis setting applies only when terminals are created, so you will need to restart your terminals for it to take effect.\n\n Note that the script injection may not work if you have custom arguments defined in the terminal profile, a [complex bash `PROMPT_COMMAND`](https://code.visualstudio.com/docs/editor/integrated-terminal#_complex-bash-promptcommand), or other unsupported setup."),
markdownDescription: localize('terminal.integrated.shellIntegration.enabled', "Determines whether or not shell integration is auto-injected to support features like enhanced command tracking and current working directory detection. \n\nShell integration works by injecting the shell with a startup script. The script gives VS Code insight into what is happening within the terminal.\n\nSupported shells:\n\n- Linux/macOS: bash, pwsh, zsh\n - Windows: pwsh\n\nThis setting applies only when terminals are created, so you will need to restart your terminals for it to take effect.\n\n Note that the script injection may not work if you have custom arguments defined in the terminal profile, a [complex bash `PROMPT_COMMAND`](https://code.visualstudio.com/docs/editor/integrated-terminal#_complex-bash-promptcommand), or other unsupported setup. To disable decorations, see {0}", '`#terminal.integrated.shellIntegrations.decorationsEnabled#`'),
type: 'boolean',
default: true
},
[TerminalSettingId.ShellIntegrationDecorationsEnabled]: {
restricted: true,
markdownDescription: localize('terminal.integrated.shellIntegration.decorationsEnabled', "When shell integration is enabled, adds a decoration for each command."),
type: 'boolean',
default: true
type: 'string',
enum: ['both', 'gutter', 'overviewRuler', 'never'],
enumDescriptions: [
localize('terminal.integrated.shellIntegration.decorationsEnabled.both', "Show decorations in the gutter (left) and overview ruler (right)"),
localize('terminal.integrated.shellIntegration.decorationsEnabled.gutter', "Show gutter decorations to the left of the terminal"),
localize('terminal.integrated.shellIntegration.decorationsEnabled.overviewRuler', "Show overview ruler decorations to the right of the terminal"),
localize('terminal.integrated.shellIntegration.decorationsEnabled.never', "Do not show decorations"),
],
default: 'both'
},
[TerminalSettingId.ShellIntegrationCommandHistory]: {
restricted: true,

View file

@ -37,7 +37,14 @@ suite('DecorationAddon', () => {
const instantiationService = new TestInstantiationService();
const configurationService = new TestConfigurationService({
workbench: {
hover: { delay: 5 }
hover: { delay: 5 },
},
terminal: {
integrated: {
shellIntegration: {
decorationsEnabled: 'both'
}
}
}
});
instantiationService.stub(IThemeService, new TestThemeService());

View file

@ -25,7 +25,8 @@ export enum Selector {
Tabs = '.tabs-list .monaco-list-row',
SplitButton = '.editor .codicon-split-horizontal',
XtermSplitIndex0 = '#terminal .terminal-groups-container .split-view-view:nth-child(1) .terminal-wrapper',
XtermSplitIndex1 = '#terminal .terminal-groups-container .split-view-view:nth-child(2) .terminal-wrapper'
XtermSplitIndex1 = '#terminal .terminal-groups-container .split-view-view:nth-child(2) .terminal-wrapper',
Hide = '.hide'
}
/**
@ -226,14 +227,18 @@ export class Terminal {
await this.code.waitForElement(Selector.TerminalView, result => result === undefined);
}
async assertCommandDecorations(expectedCounts?: ICommandDecorationCounts, customConfig?: { updatedIcon: string; count: number }): Promise<void> {
async assertCommandDecorations(expectedCounts?: ICommandDecorationCounts, customIcon?: { updatedIcon: string; count: number }, showDecorations?: 'both' | 'gutter' | 'overviewRuler' | 'never'): Promise<void> {
if (expectedCounts) {
await this.code.waitForElements(Selector.CommandDecorationPlaceholder, true, decorations => decorations && decorations.length === expectedCounts.placeholder);
await this.code.waitForElements(Selector.CommandDecorationSuccess, true, decorations => decorations && decorations.length === expectedCounts.success);
await this.code.waitForElements(Selector.CommandDecorationError, true, decorations => decorations && decorations.length === expectedCounts.error);
const placeholderSelector = showDecorations === 'overviewRuler' ? `${Selector.CommandDecorationPlaceholder}${Selector.Hide}` : Selector.CommandDecorationPlaceholder;
await this.code.waitForElements(placeholderSelector, true, decorations => decorations && decorations.length === expectedCounts.placeholder);
const successSelector = showDecorations === 'overviewRuler' ? `${Selector.CommandDecorationSuccess}${Selector.Hide}` : Selector.CommandDecorationSuccess;
await this.code.waitForElements(successSelector, true, decorations => decorations && decorations.length === expectedCounts.success);
const errorSelector = showDecorations === 'overviewRuler' ? `${Selector.CommandDecorationError}${Selector.Hide}` : Selector.CommandDecorationError;
await this.code.waitForElements(errorSelector, true, decorations => decorations && decorations.length === expectedCounts.error);
}
if (customConfig) {
await this.code.waitForElements(`.terminal-command-decoration.codicon-${customConfig.updatedIcon}`, true, decorations => decorations && decorations.length === customConfig.count);
if (customIcon) {
await this.code.waitForElements(`.terminal-command-decoration.codicon-${customIcon.updatedIcon}`, true, decorations => decorations && decorations.length === customIcon.count);
}
}

View file

@ -36,7 +36,6 @@ export function setup() {
});
describe('Decorations', function () {
describe('Should show default icons', function () {
it('Placeholder', async () => {
await createShellIntegrationProfile();
await terminal.assertCommandDecorations({ placeholder: 1, success: 0, error: 0 });
@ -62,8 +61,37 @@ export function setup() {
await settingsEditor.addUserSetting('terminal.integrated.shellIntegration.decorationIconSuccess', '"zap"');
await settingsEditor.addUserSetting('terminal.integrated.shellIntegration.decorationIconError', '"zap"');
await terminal.assertCommandDecorations(undefined, { updatedIcon: "zap", count: 3 });
});
});
describe('terminal.integrated.shellIntegration.decorationsEnabled should determine gutter and overview ruler decoration visibility', function () {
beforeEach(async () => {
await settingsEditor.clearUserSettings();
await setTerminalTestSettings(app, [['terminal.integrated.shellIntegration.enabled', 'true']]);
await createShellIntegrationProfile();
await terminal.assertCommandDecorations({ placeholder: 1, success: 0, error: 0 });
await terminal.runCommandInTerminal(`echo "foo"`);
await terminal.runCommandInTerminal(`bar`);
await terminal.assertCommandDecorations({ placeholder: 1, success: 1, error: 1 });
});
afterEach(async () => {
await app.workbench.terminal.runCommand(TerminalCommandId.KillAll);
});
it('never', async () => {
await settingsEditor.addUserSetting('terminal.integrated.shellIntegration.decorationsEnabled', '"never"');
await terminal.assertCommandDecorations({ placeholder: 0, success: 0, error: 0 }, undefined, 'never');
});
it('both', async () => {
await settingsEditor.addUserSetting('terminal.integrated.shellIntegration.decorationsEnabled', '"both"');
await terminal.assertCommandDecorations({ placeholder: 1, success: 1, error: 1 }, undefined, 'both');
});
it('gutter', async () => {
await settingsEditor.addUserSetting('terminal.integrated.shellIntegration.decorationsEnabled', '"gutter"');
await terminal.assertCommandDecorations({ placeholder: 1, success: 1, error: 1 }, undefined, 'gutter');
});
it('overviewRuler', async () => {
await settingsEditor.addUserSetting('terminal.integrated.shellIntegration.decorationsEnabled', '"overviewRuler"');
await terminal.assertCommandDecorations({ placeholder: 1, success: 1, error: 1 }, undefined, 'overviewRuler');
});
});
});
});