Skip to content

Commit

Permalink
plugin: ThemeIcon Support in SCM
Browse files Browse the repository at this point in the history
Finishes Estelle's changes to support Themables.

Signed-off-by: FernandoAscencio <[email protected]>
Co-Authored-By: Estelle
  • Loading branch information
FernandoAscencio committed May 31, 2023
1 parent e5fa9d8 commit 8836ce4
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 33 deletions.
2 changes: 1 addition & 1 deletion packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -964,7 +964,7 @@ export interface SourceControlGroupFeatures {
export interface ScmRawResource {
handle: number,
sourceUri: UriComponents,
icons: UriComponents[],
icons: (IconUrl | ThemeIcon | undefined)[], /* icons: light, dark */
tooltip: string,
strikeThrough: boolean,
faded: boolean,
Expand Down
31 changes: 25 additions & 6 deletions packages/plugin-ext/src/main/browser/scm-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ import { URI as vscodeURI } from '@theia/core/shared/vscode-uri';
import { Splice } from '../../common/arrays';
import { UriComponents } from '../../common/uri-components';
import { ColorRegistry } from '@theia/core/lib/browser/color-registry';
import { PluginSharedStyle } from './plugin-shared-style';
import { ThemeIcon } from '@theia/monaco-editor-core/esm/vs/platform/theme/common/themeService';
import { IconUrl } from '../../common';

export class PluginScmResourceGroup implements ScmResourceGroup {

Expand Down Expand Up @@ -147,10 +150,12 @@ export class PluginScmProvider implements ScmProvider {
constructor(
private readonly proxy: ScmExt,
private readonly colors: ColorRegistry,
private readonly sharedStyle: PluginSharedStyle,
private readonly _handle: number,
private readonly _contextValue: string,
private readonly _label: string,
private readonly _rootUri: vscodeURI | undefined
private readonly _rootUri: vscodeURI | undefined,
private disposables: DisposableCollection
) { }

updateSourceControl(features: SourceControlProviderFeatures): void {
Expand Down Expand Up @@ -222,13 +227,13 @@ export class PluginScmProvider implements ScmProvider {
const { start, deleteCount, rawResources } = groupSlice;
const resources = rawResources.map(rawResource => {
const { handle, sourceUri, icons, tooltip, strikeThrough, faded, contextValue, command } = rawResource;
const icon = icons[0];
const iconDark = icons[1] || icon;
const icon = this.toIconClass(icons[0]);
const iconDark = this.toIconClass(icons[1]) || icon;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const colorVariable = (rawResource as any).colorId && this.colors.toCssVariableName((rawResource as any).colorId);
const decorations = {
icon: icon ? vscodeURI.revive(icon) : undefined,
iconDark: iconDark ? vscodeURI.revive(iconDark) : undefined,
icon,
iconDark,
tooltip,
strikeThrough,
// TODO remove the letter and colorId fields when the FileDecorationProvider is applied, see https://github.com/eclipse-theia/theia/pull/8911
Expand Down Expand Up @@ -258,6 +263,18 @@ export class PluginScmProvider implements ScmProvider {
this.onDidChangeResourcesEmitter.fire();
}

private toIconClass(icon: IconUrl | ThemeIcon | undefined): string | undefined {
if (!icon) {
return undefined;
}
if (ThemeIcon.isThemeIcon(icon)) {
return ThemeIcon.asClassName(icon);
}
const reference = this.sharedStyle.toIconClass(icon);
this.disposables.push(reference);
return reference.object.iconClass;
}

unregisterGroup(handle: number): void {
const group = this.groupsByHandle[handle];

Expand All @@ -280,11 +297,13 @@ export class ScmMainImpl implements ScmMain {
private repositoryDisposables = new Map<number, DisposableCollection>();
private readonly disposables = new DisposableCollection();
private readonly colors: ColorRegistry;
private readonly sharedStyle: PluginSharedStyle;

constructor(rpc: RPCProtocol, container: interfaces.Container) {
this.proxy = rpc.getProxy(MAIN_RPC_CONTEXT.SCM_EXT);
this.scmService = container.get(ScmService);
this.colors = container.get(ColorRegistry);
this.sharedStyle = container.get(PluginSharedStyle);
}

dispose(): void {
Expand All @@ -298,7 +317,7 @@ export class ScmMainImpl implements ScmMain {
}

async $registerSourceControl(handle: number, id: string, label: string, rootUri: UriComponents | undefined): Promise<void> {
const provider = new PluginScmProvider(this.proxy, this.colors, handle, id, label, rootUri ? vscodeURI.revive(rootUri) : undefined);
const provider = new PluginScmProvider(this.proxy, this.colors, this.sharedStyle, handle, id, label, rootUri ? vscodeURI.revive(rootUri) : undefined, this.disposables);
const repository = this.scmService.registerScmProvider(provider, {
input: {
validator: async value => {
Expand Down
49 changes: 29 additions & 20 deletions packages/plugin-ext/src/plugin/scm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,26 @@ import { Splice } from '../common/arrays';
import { UriComponents } from '../common/uri-components';
import { Command } from '../common/plugin-api-rpc-model';
import { RPCProtocol } from '../common/rpc-protocol';
import { URI } from './types-impl';
import { URI, ThemeIcon } from './types-impl';
import { ScmCommandArg } from '../common/plugin-api-rpc';
import { sep } from '@theia/core/lib/common/paths';
import { PluginIconPath } from './plugin-icon-path';
type ProviderHandle = number;
type GroupHandle = number;
type ResourceStateHandle = number;

function getIconResource(decorations?: theia.SourceControlResourceThemableDecorations): theia.Uri | undefined {
if (!decorations) {
function getIconResource(decorations?: theia.SourceControlResourceThemableDecorations): UriComponents | ThemeIcon | undefined {
if (!decorations || !decorations.iconPath) {
return undefined;
} else if (typeof decorations.iconPath === 'string') {
return URI.file(decorations.iconPath);
} else {
} else if (URI.isUri(decorations.iconPath)) {
return decorations.iconPath;
} else if (ThemeIcon.is(decorations.iconPath)) {
return decorations.iconPath;
} else {
console.warn(`Unexpected Value ${decorations.iconPath} in Source Control Resource Themable Decoration. URI, ThemeIcon or string expected.`);
return undefined;
}
}

Expand Down Expand Up @@ -111,8 +117,8 @@ function compareResourceThemableDecorations(a: theia.SourceControlResourceThemab
return 1;
}

const aPath = typeof a.iconPath === 'string' ? a.iconPath : a.iconPath.fsPath;
const bPath = typeof b.iconPath === 'string' ? b.iconPath : b.iconPath.fsPath;
const aPath = typeof a.iconPath === 'string' ? a.iconPath : URI.isUri(a.iconPath) ? a.iconPath.fsPath : (a.iconPath as ThemeIcon).id;
const bPath = typeof b.iconPath === 'string' ? b.iconPath : URI.isUri(b.iconPath) ? b.iconPath.fsPath : (b.iconPath as ThemeIcon).id;
return comparePaths(aPath, bPath);
}

Expand Down Expand Up @@ -415,6 +421,7 @@ class ScmResourceGroupImpl implements theia.SourceControlResourceGroup {
private proxy: ScmMain,
private commands: CommandRegistryImpl,
private sourceControlHandle: number,
private plugin: Plugin,
private _id: string,
private _label: string,
) { }
Expand Down Expand Up @@ -443,10 +450,11 @@ class ScmResourceGroupImpl implements theia.SourceControlResourceGroup {
this.resourceStatesMap.set(handle, r);

const sourceUri = r.resourceUri;
const iconUri = getIconResource(r.decorations);
const lightIconUri = r.decorations && getIconResource(r.decorations.light) || iconUri;
const darkIconUri = r.decorations && getIconResource(r.decorations.dark) || iconUri;
const icons: UriComponents[] = [];

const icon = getIconResource(r.decorations);
const lightIcon = r.decorations && getIconResource(r.decorations.light) || icon;
const darkIcon = r.decorations && getIconResource(r.decorations.dark) || icon;
const icons = [this.getThemableIcon(lightIcon), this.getThemableIcon(darkIcon)];
let command: Command | undefined;

if (r.command) {
Expand All @@ -459,14 +467,6 @@ class ScmResourceGroupImpl implements theia.SourceControlResourceGroup {
}
}

if (lightIconUri) {
icons.push(lightIconUri);
}

if (darkIconUri && (darkIconUri.toString() !== lightIconUri?.toString())) {
icons.push(darkIconUri);
}

const tooltip = (r.decorations && r.decorations.tooltip) || '';
const strikeThrough = r.decorations && !!r.decorations.strikeThrough;
const faded = r.decorations && !!r.decorations.faded;
Expand Down Expand Up @@ -511,6 +511,15 @@ class ScmResourceGroupImpl implements theia.SourceControlResourceGroup {
return rawResourceSplices;
}

private getThemableIcon(icon: UriComponents | ThemeIcon | undefined): string | ThemeIcon | undefined {
if (!icon) {
return undefined;
} else if (ThemeIcon.is(icon)) {
return icon;
}
return PluginIconPath.asString(URI.revive(icon), this.plugin);
}

dispose(): void {
this._disposed = true;
this.onDidDisposeEmitter.fire();
Expand Down Expand Up @@ -626,7 +635,7 @@ class SourceControlImpl implements theia.SourceControl {
private handle: number = SourceControlImpl.handlePool++;

constructor(
plugin: Plugin,
private plugin: Plugin,
private proxy: ScmMain,
private commands: CommandRegistryImpl,
private _id: string,
Expand All @@ -641,7 +650,7 @@ class SourceControlImpl implements theia.SourceControl {
private updatedResourceGroups = new Set<ScmResourceGroupImpl>();

createResourceGroup(id: string, label: string): ScmResourceGroupImpl {
const group = new ScmResourceGroupImpl(this.proxy, this.commands, this.handle, id, label);
const group = new ScmResourceGroupImpl(this.proxy, this.commands, this.handle, this.plugin, id, label);
const disposable = group.onDidDispose(() => this.createdResourceGroups.delete(group));
this.createdResourceGroups.set(group, disposable);
this.eventuallyAddResourceGroups();
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin/src/theia.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11379,7 +11379,7 @@ export module '@theia/plugin' {
* The icon path for a specific
* {@link SourceControlResourceState source control resource state}.
*/
readonly iconPath?: string | Uri;
readonly iconPath?: string | Uri | ThemeIcon;
}

/**
Expand Down
2 changes: 2 additions & 0 deletions packages/scm/src/browser/scm-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ export interface ScmResource {
}

export interface ScmResourceDecorations {
icon?: string;
iconDark?: string;
tooltip?: string;
source?: string;
letter?: string;
Expand Down
30 changes: 25 additions & 5 deletions packages/scm/src/browser/scm-tree-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
/* eslint-disable no-null/no-null, @typescript-eslint/no-explicit-any */

import * as React from '@theia/core/shared/react';
import { injectable, inject } from '@theia/core/shared/inversify';
import { injectable, inject, postConstruct } from '@theia/core/shared/inversify';
import URI from '@theia/core/lib/common/uri';
import { isOSX } from '@theia/core/lib/common/os';
import { DisposableCollection, Disposable } from '@theia/core/lib/common/disposable';
Expand All @@ -32,6 +32,7 @@ import { IconThemeService } from '@theia/core/lib/browser/icon-theme-service';
import { ColorRegistry } from '@theia/core/lib/browser/color-registry';
import { Decoration, DecorationsService } from '@theia/core/lib/browser/decorations-service';
import { FileStat } from '@theia/filesystem/lib/common/files';
import { ThemeService } from '@theia/core/lib/browser/theming';

@injectable()
export class ScmTreeWidget extends TreeWidget {
Expand All @@ -55,6 +56,7 @@ export class ScmTreeWidget extends TreeWidget {
@inject(IconThemeService) protected readonly iconThemeService: IconThemeService;
@inject(DecorationsService) protected readonly decorationsService: DecorationsService;
@inject(ColorRegistry) protected readonly colors: ColorRegistry;
@inject(ThemeService) protected readonly themeService: ThemeService;

// TODO: Make TreeWidget generic to better type those fields.
override readonly model: ScmTreeModel;
Expand All @@ -69,6 +71,12 @@ export class ScmTreeWidget extends TreeWidget {
this.addClass('groups-outer-container');
}

@postConstruct()
protected override init(): void {
super.init();
this.toDispose.push(this.themeService.onDidColorThemeChange(() => this.update()));
}

set viewMode(id: 'tree' | 'list') {
// Close the search box because the structure of the tree will change dramatically
// and the search results will be out of date.
Expand Down Expand Up @@ -153,6 +161,7 @@ export class ScmTreeWidget extends TreeWidget {
sourceUri: node.sourceUri,
decoration: this.decorationsService.getDecoration(new URI(node.sourceUri), true)[0],
colors: this.colors,
isLightTheme: this.isCurrentThemeLight(),
renderExpansionToggle: () => this.renderExpansionToggle(node, props),
}}
/>;
Expand Down Expand Up @@ -436,6 +445,11 @@ export class ScmTreeWidget extends TreeWidget {
return super.getPaddingLeft(node, props);
}

protected isCurrentThemeLight(): boolean {
const type = this.themeService.getCurrentTheme().type;
return type.toLocaleLowerCase().includes('light');
}

protected override needsExpansionTogglePadding(node: TreeNode): boolean {
const theme = this.iconThemeService.getDefinition(this.iconThemeService.current);
if (theme && (theme.hidesExplorerArrows || (theme.hasFileIcons && !theme.hasFolderIcons))) {
Expand Down Expand Up @@ -528,12 +542,16 @@ export class ScmResourceComponent extends ScmElement<ScmResourceComponent.Props>

override render(): JSX.Element | undefined {
const { hover } = this.state;
const { model, treeNode, colors, parentPath, sourceUri, decoration, labelProvider, commandExecutor, menus, contextKeys, caption } = this.props;
const { model, treeNode, colors, parentPath, sourceUri, decoration, labelProvider, commandExecutor, menus, contextKeys, caption, isLightTheme } = this.props;
const resourceUri = new URI(sourceUri);

const decorationIcon = treeNode.decorations;
const themedIcon = isLightTheme ? decorationIcon?.icon : decorationIcon?.iconDark;
const classNames: string[] = themedIcon ? ['decoration-icon', themedIcon] : ['decoration-icon', 'status'];

const icon = labelProvider.getIcon(resourceUri);
const color = decoration && decoration.colorId ? `var(${colors.toCssVariableName(decoration.colorId)})` : '';
const letter = decoration && decoration.letter || '';
const color = decoration && decoration.colorId && !themedIcon ? `var(${colors.toCssVariableName(decoration.colorId)})` : '';
const letter = decoration && decoration.letter && !themedIcon ? decoration.letter : '';
const tooltip = decoration && decoration.tooltip || '';
const textDecoration = treeNode.decorations?.strikeThrough === true ? 'line-through' : 'normal';
const relativePath = parentPath.relative(resourceUri.parent);
Expand Down Expand Up @@ -567,7 +585,7 @@ export class ScmResourceComponent extends ScmElement<ScmResourceComponent.Props>
model,
treeNode
}}>
<div title={tooltip} className='status' style={{ color }}>
<div title={tooltip} className={classNames.join(' ')} style={{ color }}>
{letter}
</div>
</ScmInlineActions>
Expand Down Expand Up @@ -630,13 +648,15 @@ export class ScmResourceComponent extends ScmElement<ScmResourceComponent.Props>
}
};
}

export namespace ScmResourceComponent {
export interface Props extends ScmElement.Props {
treeNode: ScmFileChangeNode;
parentPath: URI;
sourceUri: string;
decoration: Decoration | undefined;
colors: ColorRegistry;
isLightTheme: boolean
}
}

Expand Down
4 changes: 4 additions & 0 deletions packages/scm/src/browser/style/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,10 @@
font-size: var(--theia-ui-font-size0);
}

.theia-scm .decoration-icon {
margin: 2px 0px;
}

.scm-change-count {
float: right;
}
Expand Down

0 comments on commit 8836ce4

Please sign in to comment.