Merge pull request #147463 from amanasifkhalid/terminal-tab-defaults

Close #134566: Added settings for terminal tab default color/icon
This commit is contained in:
Daniel Imms 2022-07-07 05:12:46 -07:00 committed by GitHub
commit 72208d7bbb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 86 additions and 36 deletions

View file

@ -40,6 +40,8 @@ export const enum TerminalSettingId {
DefaultProfileMacOs = 'terminal.integrated.defaultProfile.osx',
DefaultProfileWindows = 'terminal.integrated.defaultProfile.windows',
UseWslProfiles = 'terminal.integrated.useWslProfiles',
TabsDefaultIconColor = 'terminal.integrated.tabs.defaultIconColor',
TabsDefaultIconId = 'terminal.integrated.tabs.defaultIconId',
TabsEnabled = 'terminal.integrated.tabs.enabled',
TabsEnableAnimation = 'terminal.integrated.tabs.enableAnimation',
TabsHideCondition = 'terminal.integrated.tabs.hideCondition',

View file

@ -12,6 +12,27 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { IExtensionTerminalProfile, ITerminalProfile, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
import { createProfileSchemaEnums } from 'vs/platform/terminal/common/terminalProfiles';
export const terminalColorSchema: IJSONSchema = {
type: ['string', 'null'],
enum: [
'terminal.ansiBlack',
'terminal.ansiRed',
'terminal.ansiGreen',
'terminal.ansiYellow',
'terminal.ansiBlue',
'terminal.ansiMagenta',
'terminal.ansiCyan',
'terminal.ansiWhite'
],
default: null
};
export const terminalIconSchema: IJSONSchema = {
type: 'string',
enum: Array.from(Codicon.getAll(), icon => icon.id),
markdownEnumDescriptions: Array.from(Codicon.getAll(), icon => `$(${icon.id})`),
};
const terminalProfileBaseProperties: IJSONSchemaMap = {
args: {
description: localize('terminalProfile.args', 'An optional set of arguments to run the shell executable with.'),
@ -26,24 +47,11 @@ const terminalProfileBaseProperties: IJSONSchemaMap = {
},
icon: {
description: localize('terminalProfile.icon', 'A codicon ID to associate with this terminal.'),
type: 'string',
enum: Array.from(Codicon.getAll(), icon => icon.id),
markdownEnumDescriptions: Array.from(Codicon.getAll(), icon => `$(${icon.id})`),
...terminalIconSchema
},
color: {
description: localize('terminalProfile.color', 'A theme color ID to associate with this terminal.'),
type: ['string', 'null'],
enum: [
'terminal.ansiBlack',
'terminal.ansiRed',
'terminal.ansiGreen',
'terminal.ansiYellow',
'terminal.ansiBlue',
'terminal.ansiMagenta',
'terminal.ansiCyan',
'terminal.ansiWhite'
],
default: null
...terminalColorSchema
},
env: {
markdownDescription: localize('terminalProfile.env', "An object with environment variables that will be added to the terminal profile process. Set to `null` to delete environment variables from the base environment."),

View file

@ -1723,6 +1723,7 @@ export function registerTerminalActions() {
const themeService = accessor.get(IThemeService);
const groupService = accessor.get(ITerminalGroupService);
const notificationService = accessor.get(INotificationService);
const picks: ITerminalQuickPickItem[] = [];
if (groupService.instances.length <= 1) {
notificationService.warn(localize('workbench.action.terminal.join.insufficientTerminals', 'Insufficient terminals for the join action'));
@ -1732,7 +1733,7 @@ export function registerTerminalActions() {
for (const terminal of otherInstances) {
const group = groupService.getGroupForInstance(terminal);
if (group?.terminalInstances.length === 1) {
const iconId = getIconId(terminal);
const iconId = getIconId(accessor, terminal);
const label = `$(${iconId}): ${terminal.title}`;
const iconClasses: string[] = [];
const colorClass = getColorClass(terminal);

View file

@ -3,14 +3,15 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Codicon } from 'vs/base/common/codicons';
import { hash } from 'vs/base/common/hash';
import { URI } from 'vs/base/common/uri';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionTerminalProfile, ITerminalProfile } from 'vs/platform/terminal/common/terminal';
import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry';
import { ColorScheme } from 'vs/platform/theme/common/theme';
import { IColorTheme, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
import { ITerminalProfileResolverService } from 'vs/workbench/contrib/terminal/common/terminal';
import { ansiColorMap } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry';
@ -116,9 +117,9 @@ export function getUriClasses(terminal: ITerminalInstance | IExtensionTerminalPr
return iconClasses;
}
export function getIconId(terminal: ITerminalInstance | IExtensionTerminalProfile | ITerminalProfile): string {
export function getIconId(accessor: ServicesAccessor, terminal: ITerminalInstance | IExtensionTerminalProfile | ITerminalProfile): string {
if (!terminal.icon || (terminal.icon instanceof Object && !('id' in terminal.icon))) {
return Codicon.terminal.id;
return accessor.get(ITerminalProfileResolverService).getDefaultIcon().id;
}
return typeof terminal.icon === 'string' ? terminal.icon : terminal.icon.id;
}

View file

@ -86,6 +86,7 @@ import type { IMarker, ITerminalAddon, Terminal as XTermTerminal } from 'xterm';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IGenericMarkProperties } from 'vs/platform/terminal/common/terminalProcess';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry';
const enum Constants {
/**
@ -548,7 +549,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
private _getIcon(): TerminalIcon | undefined {
if (!this._icon) {
this._icon = this._processManager.processState >= ProcessState.Launching ? Codicon.terminal : undefined;
this._icon = this._processManager.processState >= ProcessState.Launching
? getIconRegistry().getIcon(this._configurationService.getValue(TerminalSettingId.TabsDefaultIconId))
: undefined;
}
return this._icon;
}

View file

@ -11,7 +11,7 @@ import { getUriClasses, getColorClass, getColorStyleElement } from 'vs/workbench
import { configureTerminalProfileIcon } from 'vs/workbench/contrib/terminal/browser/terminalIcons';
import * as nls from 'vs/nls';
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal';
import { ITerminalProfileResolverService, ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal';
import { IQuickPickTerminalObject, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
import { IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQuickAccess';
import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry';
@ -22,6 +22,7 @@ type DefaultProfileName = string;
export class TerminalProfileQuickpick {
constructor(
@ITerminalProfileService private readonly _terminalProfileService: ITerminalProfileService,
@ITerminalProfileResolverService private readonly _terminalProfileResolverService: ITerminalProfileResolverService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@IQuickInputService private readonly _quickInputService: IQuickInputService,
@IThemeService private readonly _themeService: IThemeService
@ -155,7 +156,7 @@ export class TerminalProfileQuickpick {
}
}
if (!icon || !getIconRegistry().getIcon(icon.id)) {
icon = Codicon.terminal;
icon = this._terminalProfileResolverService.getDefaultIcon();
}
const uriClasses = getUriClasses(contributed, this._themeService.getColorTheme().type, true);
const colorClass = getColorClass(contributed);

View file

@ -16,6 +16,7 @@ import { IShellLaunchConfig, ITerminalProfile, ITerminalProfileObject, TerminalI
import { IShellLaunchConfigResolveOptions, ITerminalProfileResolverService, ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal';
import * as path from 'vs/base/common/path';
import { Codicon } from 'vs/base/common/codicons';
import { getIconRegistry, IIconRegistry } from 'vs/platform/theme/common/iconRegistry';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { debounce } from 'vs/base/common/decorators';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
@ -51,6 +52,8 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro
private _primaryBackendOs: OperatingSystem | undefined;
private readonly _iconRegistry: IIconRegistry = getIconRegistry();
private _defaultProfileName: string | undefined;
get defaultProfileName(): string | undefined { return this._defaultProfileName; }
@ -94,11 +97,11 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro
resolveIcon(shellLaunchConfig: IShellLaunchConfig, os: OperatingSystem): void {
if (shellLaunchConfig.icon) {
shellLaunchConfig.icon = this._getCustomIcon(shellLaunchConfig.icon) || Codicon.terminal;
shellLaunchConfig.icon = this._getCustomIcon(shellLaunchConfig.icon) || this.getDefaultIcon();
return;
}
if (shellLaunchConfig.customPtyImplementation) {
shellLaunchConfig.icon = Codicon.terminal;
shellLaunchConfig.icon = this.getDefaultIcon();
return;
}
if (shellLaunchConfig.executable) {
@ -108,6 +111,13 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro
if (defaultProfile) {
shellLaunchConfig.icon = defaultProfile.icon;
}
if (!shellLaunchConfig.icon) {
shellLaunchConfig.icon = this.getDefaultIcon();
}
}
getDefaultIcon(): TerminalIcon & ThemeIcon {
return this._iconRegistry.getIcon(this._configurationService.getValue(TerminalSettingId.TabsDefaultIconId)) || Codicon.terminal;
}
async resolveShellLaunchConfig(shellLaunchConfig: IShellLaunchConfig, options: IShellLaunchConfigResolveOptions): Promise<void> {
@ -135,7 +145,9 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro
// Verify the icon is valid, and fallback correctly to the generic terminal id if there is
// an issue
shellLaunchConfig.icon = this._getCustomIcon(shellLaunchConfig.icon) || this._getCustomIcon(resolvedProfile.icon) || Codicon.terminal;
shellLaunchConfig.icon = this._getCustomIcon(shellLaunchConfig.icon)
|| this._getCustomIcon(resolvedProfile.icon)
|| this.getDefaultIcon();
// Override the name if specified
if (resolvedProfile.overrideName) {
@ -143,7 +155,9 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro
}
// Apply the color
shellLaunchConfig.color = shellLaunchConfig.color || resolvedProfile.color;
shellLaunchConfig.color = shellLaunchConfig.color
|| resolvedProfile.color
|| this._configurationService.getValue(TerminalSettingId.TabsDefaultIconColor);
// Resolve useShellEnvironment based on the setting if it's not set
if (shellLaunchConfig.useShellEnvironment === undefined) {
@ -232,6 +246,7 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro
if (defaultProfileName && typeof defaultProfileName === 'string') {
return this._terminalProfileService.availableProfiles.find(e => e.profileName === defaultProfileName);
}
return undefined;
}

View file

@ -16,6 +16,7 @@ import { getColorClass, getIconId, getUriClasses } from 'vs/workbench/contrib/te
import { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings';
import { TerminalLocation } from 'vs/platform/terminal/common/terminal';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
let terminalPicks: Array<IPickerQuickAccessItem | IQuickPickSeparator> = [];
export class TerminalQuickAccessProvider extends PickerQuickAccessProvider<IPickerQuickAccessItem> {
@ -27,7 +28,8 @@ export class TerminalQuickAccessProvider extends PickerQuickAccessProvider<IPick
@ITerminalEditorService private readonly _terminalEditorService: ITerminalEditorService,
@ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService,
@ICommandService private readonly _commandService: ICommandService,
@IThemeService private readonly _themeService: IThemeService
@IThemeService private readonly _themeService: IThemeService,
@IInstantiationService private readonly _instantiationService: IInstantiationService
) {
super(TerminalQuickAccessProvider.PREFIX, { canAcceptInBackground: true });
}
@ -80,7 +82,7 @@ export class TerminalQuickAccessProvider extends PickerQuickAccessProvider<IPick
}
private _createPick(terminal: ITerminalInstance, terminalIndex: number, filter: string, groupInfo?: { groupIndex: number; groupSize: number }): IPickerQuickAccessItem | undefined {
const iconId = getIconId(terminal);
const iconId = this._instantiationService.invokeFunction(getIconId, terminal);
const index = groupInfo
? (groupInfo.groupSize > 1
? `${groupInfo.groupIndex + 1}.${terminalIndex + 1}`

View file

@ -309,7 +309,7 @@ class TerminalTabsRenderer implements IListRenderer<ITerminalInstance, ITerminal
}
const shellIntegrationString = getShellIntegrationTooltip(instance, true, this._configurationService);
const iconId = getIconId(instance);
const iconId = this._instantiationService.invokeFunction(getIconId, instance);
const hasActionbar = !this.shouldHideActionBar();
let label: string = '';
if (!hasText) {

View file

@ -45,6 +45,7 @@ import { withNullAsUndefined } from 'vs/base/common/types';
import { getTerminalActionBarArgs } from 'vs/workbench/contrib/terminal/browser/terminalMenus';
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';
export class TerminalViewPane extends ViewPane {
private _actions: IAction[] | undefined;
@ -374,12 +375,13 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem {
@ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService,
@IContextMenuService private readonly _contextMenuService: IContextMenuService,
@ICommandService private readonly _commandService: ICommandService,
@IConfigurationService configurationService: IConfigurationService
@IConfigurationService configurationService: IConfigurationService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
) {
super(new MenuItemAction(
{
id: action.id,
title: getSingleTabLabel(_terminalGroupService.activeInstance, _terminalService.configHelper.config.tabs.separator),
title: _instantiationService.invokeFunction(getSingleTabLabel, _terminalGroupService.activeInstance, _terminalService.configHelper.config.tabs.separator),
tooltip: getSingleTabTooltip(_terminalGroupService.activeInstance, _terminalService.configHelper.config.tabs.separator, configurationService)
},
{
@ -473,7 +475,7 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem {
}
}
label.style.color = colorStyle;
dom.reset(label, ...renderLabelWithIcons(getSingleTabLabel(instance, this._terminalService.configHelper.config.tabs.separator, ThemeIcon.isThemeIcon(this._commandAction.item.icon) ? this._commandAction.item.icon : undefined)));
dom.reset(label, ...renderLabelWithIcons(this._instantiationService.invokeFunction(getSingleTabLabel, instance, this._terminalService.configHelper.config.tabs.separator, ThemeIcon.isThemeIcon(this._commandAction.item.icon) ? this._commandAction.item.icon : undefined)));
if (this._altCommand) {
label.classList.remove(this._altCommand);
@ -515,13 +517,13 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem {
}
}
function getSingleTabLabel(instance: ITerminalInstance | undefined, separator: string, icon?: ThemeIcon) {
function getSingleTabLabel(accessor: ServicesAccessor, instance: ITerminalInstance | undefined, separator: string, icon?: ThemeIcon) {
// Don't even show the icon if there is no title as the icon would shift around when the title
// is added
if (!instance || !instance.title) {
return '';
}
const iconClass = ThemeIcon.isThemeIcon(instance.icon) ? instance.icon?.id : Codicon.terminal.id;
const iconClass = ThemeIcon.isThemeIcon(instance.icon) ? instance.icon.id : accessor.get(ITerminalProfileResolverService).getDefaultIcon();
const label = `$(${icon?.id || iconClass}) ${getSingleTabTitle(instance, separator)}`;
const primaryStatus = instance.statusList.primary;

View file

@ -15,6 +15,7 @@ import { URI } from 'vs/base/common/uri';
import { IGenericMarkProperties, IProcessDetails, ISerializedCommandDetectionCapability } from 'vs/platform/terminal/common/terminalProcess';
import { Registry } from 'vs/platform/registry/common/platform';
import { ITerminalCapabilityStore, IXtermMarker } from 'vs/platform/terminal/common/capabilities/capabilities';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
export const TERMINAL_VIEW_ID = 'terminal';
@ -54,6 +55,7 @@ export interface ITerminalProfileResolverService {
getDefaultProfile(options: IShellLaunchConfigResolveOptions): Promise<ITerminalProfile>;
getDefaultShell(options: IShellLaunchConfigResolveOptions): Promise<string>;
getDefaultShellArgs(options: IShellLaunchConfigResolveOptions): Promise<string | string[]>;
getDefaultIcon(): TerminalIcon & ThemeIcon;
getEnvironment(remoteAuthority: string | undefined): Promise<IProcessEnvironment>;
createProfileFromShellAndShellArgs(shell?: unknown, shellArgs?: unknown): Promise<ITerminalProfile | string>;
}

View file

@ -9,6 +9,8 @@ import { DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, DEFAU
import { TerminalLocationString, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
import { isMacintosh, isWindows } from 'vs/base/common/platform';
import { Registry } from 'vs/platform/registry/common/platform';
import { Codicon } from 'vs/base/common/codicons';
import { terminalColorSchema, terminalIconSchema } from 'vs/platform/terminal/common/terminalPlatformConfiguration';
const terminalDescriptors = '\n- ' + [
'`\${cwd}`: ' + localize("cwd", "the terminal's current working directory"),
@ -38,6 +40,15 @@ const terminalConfiguration: IConfigurationNode = {
type: 'boolean',
default: false
},
[TerminalSettingId.TabsDefaultIconColor]: {
description: localize('terminal.integrated.tabs.defaultIconColor', "A theme color ID to associate with terminals by default."),
...terminalColorSchema
},
[TerminalSettingId.TabsDefaultIconId]: {
description: localize('terminal.integrated.tabs.defaultIconId', "A codicon ID to associate with terminals by default."),
...terminalIconSchema,
default: Codicon.terminal.id,
},
[TerminalSettingId.TabsEnabled]: {
description: localize('terminal.integrated.tabs.enabled', 'Controls whether terminal tabs display as a list to the side of the terminal. When this is disabled a dropdown will display instead.'),
type: 'boolean',

View file

@ -35,7 +35,7 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/
import { MenuBarVisibility, IWindowOpenable, IOpenWindowOptions, IOpenEmptyWindowOptions } from 'vs/platform/window/common/window';
import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
import { ITextResourceConfigurationService, ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration';
import { IPosition, Position as EditorPosition } from 'vs/editor/common/core/position';
@ -122,7 +122,7 @@ import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEd
import { IEnterWorkspaceResult, IRecent, IRecentlyOpened, IWorkspaceFolderCreationData, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
import { IWorkspaceTrustManagementService, IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust';
import { TestWorkspaceTrustManagementService, TestWorkspaceTrustRequestService } from 'vs/workbench/services/workspaces/test/common/testWorkspaceTrustService';
import { IExtensionTerminalProfile, IShellLaunchConfig, ITerminalProfile, TerminalLocation, TerminalShellType } from 'vs/platform/terminal/common/terminal';
import { IExtensionTerminalProfile, IShellLaunchConfig, ITerminalProfile, TerminalIcon, TerminalLocation, TerminalShellType } from 'vs/platform/terminal/common/terminal';
import { ICreateTerminalOptions, IDeserializedTerminalEditorInput, ITerminalEditorService, ITerminalGroup, ITerminalGroupService, ITerminalInstance, ITerminalInstanceService, TerminalEditorLocation } from 'vs/workbench/contrib/terminal/browser/terminal';
import { assertIsDefined, isArray } from 'vs/base/common/types';
import { IRegisterContributedProfileArgs, IShellLaunchConfigResolveOptions, ITerminalBackend, ITerminalProfileProvider, ITerminalProfileResolverService, ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal';
@ -165,6 +165,7 @@ import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/co
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
import { EnablementState, IExtensionManagementServer, IScannedExtension, IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService, ScanOptions } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { InstallVSIXOptions, ILocalExtension, IGalleryExtension, InstallOptions, IExtensionIdentifier, UninstallOptions, IExtensionsControlManifest, IGalleryMetadata, IExtensionManagementParticipant } from 'vs/platform/extensionManagement/common/extensionManagement';
import { Codicon } from 'vs/base/common/codicons';
export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput {
return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined, undefined, undefined, undefined, undefined);
@ -1863,6 +1864,7 @@ export class TestTerminalProfileResolverService implements ITerminalProfileResol
async getDefaultProfile(options: IShellLaunchConfigResolveOptions): Promise<ITerminalProfile> { return { path: '/default', profileName: 'Default', isDefault: true }; }
async getDefaultShell(options: IShellLaunchConfigResolveOptions): Promise<string> { return '/default'; }
async getDefaultShellArgs(options: IShellLaunchConfigResolveOptions): Promise<string | string[]> { return []; }
getDefaultIcon(): TerminalIcon & ThemeIcon { return Codicon.terminal; }
async getEnvironment(): Promise<IProcessEnvironment> { return env; }
getSafeConfigValue(key: string, os: OperatingSystem): unknown | undefined { return undefined; }
getSafeConfigValueFullKey(key: string): unknown | undefined { return undefined; }