Skip to content

Commit

Permalink
Codicon color and URI support to TerminalOptions
Browse files Browse the repository at this point in the history
Partial close of 12074

- This commit adds URI and { light, dark } support for terminal icons.
- This commit adds ColorTheme support for ThemeIcons
- Support for `ExtensionTerminalOptions#iconPath`

Signed-Off-By: FernandoAscencio <fernando.ascencio.cama@ericsson.com>
  • Loading branch information
FernandoAscencio committed Aug 30, 2023
1 parent 5d7a655 commit ba3dda2
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 18 deletions.
11 changes: 11 additions & 0 deletions packages/core/src/browser/style/tabs.css
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,17 @@
padding-right: 8px;
}

.p-TabBar.theia-app-centers .p-TabBar-tabIcon[class*="plugin-icon-"],
.p-TabBar-tab.p-mod-drag-image .p-TabBar-tabIcon[class*="plugin-icon-"] {
background: none;
height: var(--theia-icon-size);
}

.p-TabBar.theia-app-centers .p-TabBar-tabIcon[class*="plugin-icon-"]::before,
.p-TabBar-tab.p-mod-drag-image .p-TabBar-tabIcon[class*="plugin-icon-"]::before {
display: inline-block;
}

/* codicons */
.p-TabBar.theia-app-centers .p-TabBar-tabIcon.codicon,
.p-TabBar-tab.p-mod-drag-image .p-TabBar-tabIcon.codicon {
Expand Down
5 changes: 4 additions & 1 deletion packages/plugin-ext/src/main/browser/terminal-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { getIconClass } from '../../plugin/terminal-ext';
import { PluginTerminalRegistry } from './plugin-terminal-registry';
import { CancellationToken } from '@theia/core';
import { HostedPluginSupport } from '../../hosted/browser/hosted-plugin';
import { PluginSharedStyle } from './plugin-shared-style';

/**
* Plugin api service allows working with terminal emulator.
Expand All @@ -41,6 +42,7 @@ export class TerminalServiceMainImpl implements TerminalServiceMain, TerminalLin
private readonly hostedPluginSupport: HostedPluginSupport;
private readonly shell: ApplicationShell;
private readonly extProxy: TerminalServiceExt;
private readonly sharedStyle: PluginSharedStyle;
private readonly shellTerminalServer: ShellTerminalServerProxy;
private readonly terminalLinkProviders: string[] = [];

Expand All @@ -50,6 +52,7 @@ export class TerminalServiceMainImpl implements TerminalServiceMain, TerminalLin
this.terminals = container.get(TerminalService);
this.pluginTerminalRegistry = container.get(PluginTerminalRegistry);
this.hostedPluginSupport = container.get(HostedPluginSupport);
this.sharedStyle = container.get(PluginSharedStyle);
this.shell = container.get(ApplicationShell);
this.shellTerminalServer = container.get(ShellTerminalServerProxy);
this.extProxy = rpc.getProxy(MAIN_RPC_CONTEXT.TERMINAL_EXT);
Expand Down Expand Up @@ -140,7 +143,7 @@ export class TerminalServiceMainImpl implements TerminalServiceMain, TerminalLin
const terminal = await this.terminals.newTerminal({
id,
title: options.name,
iconClass: getIconClass(options),
iconClass: getIconClass(options, this.sharedStyle, this.toDispose),
shellPath: options.shellPath,
shellArgs: options.shellArgs,
cwd: options.cwd ? new URI(options.cwd) : undefined,
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin-ext/src/plugin/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,7 @@ export function createAPIFactory(
createTerminal(nameOrOptions: theia.TerminalOptions | theia.ExtensionTerminalOptions | theia.ExtensionTerminalOptions | (string | undefined),
shellPath?: string,
shellArgs?: string[] | string): theia.Terminal {
return terminalExt.createTerminal(nameOrOptions, shellPath, shellArgs);
return terminalExt.createTerminal(plugin, nameOrOptions, shellPath, shellArgs);
},
onDidChangeTerminalState,
onDidCloseTerminal,
Expand Down
51 changes: 43 additions & 8 deletions packages/plugin-ext/src/plugin/terminal-ext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// *****************************************************************************
import { UUID } from '@theia/core/shared/@phosphor/coreutils';
import { Terminal, TerminalOptions, PseudoTerminalOptions, ExtensionTerminalOptions, TerminalState } from '@theia/plugin';
import { TerminalServiceExt, TerminalServiceMain, PLUGIN_RPC_CONTEXT } from '../common/plugin-api-rpc';
import { TerminalServiceExt, TerminalServiceMain, PLUGIN_RPC_CONTEXT, Plugin } from '../common/plugin-api-rpc';
import { RPCProtocol } from '../common/rpc-protocol';
import { Event, Emitter } from '@theia/core/lib/common/event';
import { Deferred } from '@theia/core/lib/common/promise-util';
Expand All @@ -24,21 +24,42 @@ import * as Converter from './type-converters';
import { Disposable, EnvironmentVariableMutatorType, TerminalExitReason, ThemeIcon } from './types-impl';
import { SerializableEnvironmentVariableCollection } from '@theia/terminal/lib/common/base-terminal-protocol';
import { ProvidedTerminalLink } from '../common/plugin-api-rpc-model';
import { ThemeIcon as MonacoThemeIcon } from '@theia/monaco-editor-core/esm/vs/platform/theme/common/themeService';
import { ThemeIcon as MonacoThemeIcon, ThemeColor as MonacoThemeColor } from '@theia/monaco-editor-core/esm/vs/platform/theme/common/themeService';
import { PluginSharedStyle } from '../main/browser/plugin-shared-style';
import { PluginIconPath } from './plugin-icon-path';
import { IconUrl } from '../common';
import { DisposableCollection } from '@theia/core';

export function getIconUris(iconPath: theia.TerminalOptions['iconPath']): { id: string } | undefined {
export function getIconUris(iconPath: theia.TerminalOptions['iconPath']): MonacoThemeIcon | IconUrl | undefined {
if (ThemeIcon.is(iconPath)) {
return { id: iconPath.id };
} else if (typeof iconPath === 'object' && 'light' in iconPath) {
return { light: iconPath.light.toString(), dark: iconPath.dark.toString() };
} else if (typeof iconPath === 'string') {
return iconPath;
}
return undefined;
}

export function getIconClass(options: theia.TerminalOptions | theia.ExtensionTerminalOptions): string | undefined {
const iconClass = getIconUris(options.iconPath);
if (iconClass) {
return MonacoThemeIcon.asClassName(iconClass);
export function getIconClass(
options: theia.TerminalOptions | theia.ExtensionTerminalOptions,
sharedStyle: PluginSharedStyle, disposables: DisposableCollection
): string | { icon: string, color: string } | undefined {
const iconUriOrCodicon = getIconUris(options.iconPath);
const iconColor = MonacoThemeColor.isThemeColor(options.color) ? options.color : undefined;
let iconClass;
if (iconUriOrCodicon) {
if (MonacoThemeIcon.isThemeIcon(iconUriOrCodicon)) {
iconClass = MonacoThemeIcon.asClassName(iconUriOrCodicon);
} else {
const iconReference = sharedStyle.toIconClass(iconUriOrCodicon);
disposables.push(iconReference);
iconClass = iconReference.object.iconClass;
}
} else {
iconClass = (MonacoThemeIcon.asClassName({ id: 'terminal' }));
}
return undefined;
return iconColor ? { icon: iconClass, color: iconColor.id } : iconClass;
}

/**
Expand Down Expand Up @@ -79,6 +100,7 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
}

createTerminal(
extension: Plugin,
nameOrOptions: TerminalOptions | PseudoTerminalOptions | ExtensionTerminalOptions | (string | undefined),
shellPath?: string, shellArgs?: string[] | string
): Terminal {
Expand Down Expand Up @@ -117,6 +139,19 @@ export class TerminalServiceExtImpl implements TerminalServiceExt {
}
}

if (typeof nameOrOptions === 'object' && 'iconPath' in nameOrOptions) {
const iconPath = nameOrOptions.iconPath;
if (ThemeIcon.is(iconPath)) {
options.iconPath = iconPath;
} else if (typeof iconPath === 'string' || (typeof iconPath === 'object' && ('light' in iconPath || 'path' in iconPath))) {
options.iconPath = PluginIconPath.toUrl(iconPath, extension);
}
}

if (typeof nameOrOptions === 'object' && 'color' in nameOrOptions) {
options.color = nameOrOptions.color;
}

this.proxy.$createTerminal(id, options, parentId, !!pseudoTerminal);

let creationOptions: theia.TerminalOptions | theia.ExtensionTerminalOptions = options;
Expand Down
4 changes: 2 additions & 2 deletions packages/plugin/src/theia.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3140,7 +3140,7 @@ export module '@theia/plugin' {
/**
* The icon path or {@link ThemeIcon} for the terminal.
*/
iconPath?: ThemeIcon;
iconPath?: string | ThemeIcon | Uri | { light: Uri | string, dark: Uri | string };

/**
* The icon {@link ThemeColor} for the terminal.
Expand Down Expand Up @@ -3261,7 +3261,7 @@ export module '@theia/plugin' {
/**
* The icon path or {@link ThemeIcon} for the terminal.
*/
iconPath?: ThemeIcon;
iconPath?: string | ThemeIcon | Uri | { light: Uri | string, dark: Uri | string };

/**
* The icon {@link ThemeColor} for the terminal.
Expand Down
4 changes: 2 additions & 2 deletions packages/terminal/src/browser/base/terminal-widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,9 @@ export interface TerminalWidgetOptions {
readonly title?: string;

/**
* icon class
* icon class with or without color modifier
*/
readonly iconClass?: string;
readonly iconClass?: string | {icon: string, color: string};

/**
* Path to the executable shell. For example: `/bin/bash`, `bash`, `sh`.
Expand Down
40 changes: 36 additions & 4 deletions packages/terminal/src/browser/terminal-widget-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { FitAddon } from 'xterm-addon-fit';
import { inject, injectable, named, postConstruct } from '@theia/core/shared/inversify';
import { ContributionProvider, Disposable, Event, Emitter, ILogger, DisposableCollection, Channel, OS } from '@theia/core';
import {
Widget, Message, WebSocketConnectionProvider, StatefulWidget, isFirefox, MessageLoop, KeyCode, codicon, ExtractableWidget, ContextMenuRenderer
Widget, Message, WebSocketConnectionProvider, StatefulWidget, isFirefox, MessageLoop, KeyCode, ExtractableWidget, ContextMenuRenderer
} from '@theia/core/lib/browser';
import { isOSX } from '@theia/core/lib/common';
import { WorkspaceService } from '@theia/workspace/lib/browser';
Expand Down Expand Up @@ -138,9 +138,19 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget
this.setTitle(this.options.title || TerminalWidgetImpl.LABEL);

if (this.options.iconClass) {
this.title.iconClass = this.options.iconClass;
} else {
this.title.iconClass = codicon('terminal');
const iconClass = this.options.iconClass;
if (typeof iconClass === 'object' && 'color' in iconClass) {
const iconClasses: string[] = [];
iconClasses.push(iconClass.icon);
// TODO: Build different handling for URI icons.
if (this.isTerminalAnsiColor(iconClass.color)) {
const color = this.getCodiconColor(iconClass.color);
iconClasses.push(color ? color : '');
}
this.title.iconClass = iconClasses.join(' ');
} else {
this.title.iconClass = iconClass;
}
}

if (this.options.kind) {
Expand Down Expand Up @@ -322,6 +332,28 @@ export class TerminalWidgetImpl extends TerminalWidget implements StatefulWidget
return value === 'line' ? 'bar' : value;
}

private isTerminalAnsiColor(color: string): boolean {
const colorId = color.substring(13);
const colorName = colorId.charAt(0).toLowerCase() + colorId.slice(1);
return colorName in this.themeService.theme;
}

/**
* Returns the object class required to paint a codicon to a theme appropiate color.
* Does not give URI icons color.
*
* @param colorId the ThemeColor id
* @returns the corresponding ansi class for the color.
*/
private getCodiconColor(colorId: string): string | undefined {
if (colorId?.startsWith('terminal.ansi')) {
const colorClass = colorId.split('.')[1].split(/(?=[A-Z])/).join('-').toLocaleLowerCase();
return colorClass + '-fg';
}
// TODO: implement for other cases in color.
return undefined;
}

/**
* Returns given renderer type if it is valid and supported or default renderer otherwise.
*
Expand Down

0 comments on commit ba3dda2

Please sign in to comment.