diff --git a/extensions/git/src/scmProvider.ts b/extensions/git/src/scmProvider.ts index ede244543f8..e2d650111f4 100644 --- a/extensions/git/src/scmProvider.ts +++ b/extensions/git/src/scmProvider.ts @@ -5,17 +5,65 @@ 'use strict'; -import { Uri, Disposable, SCMProvider, SCMResource, SCMResourceGroup, EventEmitter, Event } from 'vscode'; +import { Uri, Disposable, SCMProvider, SCMResource, SCMResourceDecorations, SCMResourceGroup, EventEmitter, Event } from 'vscode'; import { Model } from './model'; import * as path from 'path'; -const Status: any = {}; +enum Theme { + Light, + Dark +} + +const iconsRootPath = path.join(path.dirname(__dirname), 'resources', 'icons'); + +function getIconUri(iconName: string, theme: Theme): Uri { + const themeName = theme === Theme.Light ? 'light' : 'dark'; + return Uri.file(path.join(iconsRootPath, themeName, `${iconName}.svg`)); +} + +enum Status { + INDEX_MODIFIED, + INDEX_ADDED, + INDEX_DELETED, + INDEX_RENAMED, + INDEX_COPIED, + + MODIFIED, + DELETED, + UNTRACKED, + IGNORED, + + ADDED_BY_US, + ADDED_BY_THEM, + DELETED_BY_US, + DELETED_BY_THEM, + BOTH_ADDED, + BOTH_DELETED, + BOTH_MODIFIED +} class Resource implements SCMResource { get uri(): Uri { return this._uri; } - constructor(private _uri: Uri, type: any) { + get decorations(): SCMResourceDecorations { + let iconPath: Uri | undefined; + let strikeThrough = false; + + switch (this.type) { + case Status.MODIFIED: + iconPath = getIconUri('refresh', Theme.Light); + break; + case Status.DELETED: + iconPath = getIconUri('refresh', Theme.Light); + strikeThrough = true; + break; + } + + return { iconPath, strikeThrough }; + } + + constructor(private _uri: Uri, private type: any) { } } diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 3d181534ed0..a8f760686f1 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -86,8 +86,14 @@ declare module 'vscode' { getClickCommand?(node: T): string; } + export interface SCMResourceDecorations { + readonly iconPath?: string | Uri; + readonly strikeThrough?: boolean; + } + export interface SCMResource { readonly uri: Uri; + readonly decorations?: SCMResourceDecorations; } export interface SCMResourceGroup { diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 3d999cba4d7..1f341bd54a6 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -236,7 +236,7 @@ export interface SCMProviderFeatures { supportsOriginalResource: boolean; } -export type SCMRawResource = [string /*uri*/]; +export type SCMRawResource = [string /*uri*/, string /*decoration icon*/, boolean /*strike through*/]; export type SCMRawResourceGroup = [string /*id*/, string /*label*/, SCMRawResource[]]; export abstract class MainThreadSCMShape { diff --git a/src/vs/workbench/api/node/extHostSCM.ts b/src/vs/workbench/api/node/extHostSCM.ts index 9499f83e8ef..2ac1569e733 100644 --- a/src/vs/workbench/api/node/extHostSCM.ts +++ b/src/vs/workbench/api/node/extHostSCM.ts @@ -47,7 +47,23 @@ export class ExtHostSCM { const onDidChange = debounceEvent(provider.onDidChange, (l, e) => e, 200); const onDidChangeListener = onDidChange(resourceGroups => { const rawResourceGroups = resourceGroups.map(g => { - const rawResources = g.resources.map(r => [r.uri.toString()] as SCMRawResource); + const rawResources = g.resources.map(r => { + const uri = r.uri.toString(); + let strikeThrough = false; + let decorationIcon: string | undefined; + + if (r.decorations) { + if (typeof r.decorations.iconPath === 'string') { + decorationIcon = URI.file(r.decorations.iconPath).toString(); + } else if (r.decorations.iconPath) { + decorationIcon = `${r.decorations.iconPath}`; + } + + strikeThrough = !!r.decorations.strikeThrough; + } + + return [uri, decorationIcon, strikeThrough] as SCMRawResource; + }); return [g.id, g.label, rawResources] as SCMRawResourceGroup; }); diff --git a/src/vs/workbench/api/node/mainThreadSCM.ts b/src/vs/workbench/api/node/mainThreadSCM.ts index b37fd4d27d5..90eca27f859 100644 --- a/src/vs/workbench/api/node/mainThreadSCM.ts +++ b/src/vs/workbench/api/node/mainThreadSCM.ts @@ -87,9 +87,17 @@ class MainThreadSCMProvider implements ISCMProvider { const [id, label, rawResources] = rawGroup; const resources = rawResources.map(rawResource => { - const [uri] = rawResource; + const [uri, decorationIcon, strikeThrough] = rawResource; + const decorations = { + icon: decorationIcon && URI.parse(decorationIcon), + strikeThrough + }; - return { uri: URI.parse(uri), resourceGroupId: id }; + return { + resourceGroupId: id, + uri: URI.parse(uri), + decorations + }; }); return { id, label, resources }; diff --git a/src/vs/workbench/parts/scm/browser/media/scmViewlet.css b/src/vs/workbench/parts/scm/browser/media/scmViewlet.css index 1cb6e1c130b..8f932d82168 100644 --- a/src/vs/workbench/parts/scm/browser/media/scmViewlet.css +++ b/src/vs/workbench/parts/scm/browser/media/scmViewlet.css @@ -59,6 +59,17 @@ text-overflow: ellipsis; } +.scm-viewlet .monaco-list-row > .resource > .name.strike-through > .monaco-icon-label > .label-name { + text-decoration: line-through; +} + +.scm-viewlet .monaco-list-row > .resource > .decoration-icon { + width: 16px; + height: 100%; + background-repeat: no-repeat; + background-position: 50% 50%; +} + .scm-viewlet .monaco-list-row > .resource > .actions .action-label, .scm-viewlet .monaco-list-row > .resource-group > .actions .action-label { width: 16px; diff --git a/src/vs/workbench/parts/scm/browser/scmViewlet.ts b/src/vs/workbench/parts/scm/browser/scmViewlet.ts index bb02b070d7e..0d55a0bdddd 100644 --- a/src/vs/workbench/parts/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/browser/scmViewlet.ts @@ -81,7 +81,9 @@ class ResourceGroupRenderer implements IRenderer { const element = append(container, $('.resource')); const name = append(element, $('.name')); const fileLabel = this.instantiationService.createInstance(FileLabel, name, void 0); + const decorationIcon = append(element, $('.decoration-icon')); const actionsContainer = append(element, $('.actions')); const actionBar = new ActionBar(actionsContainer, { actionItemProvider: this.actionItemProvider }); - return { fileLabel, actionBar }; + return { name, fileLabel, decorationIcon, actionBar }; } renderElement(resource: ISCMResource, index: number, template: ResourceTemplate): void { template.fileLabel.setFile(resource.uri); template.actionBar.clear(); template.actionBar.push(this.scmMenus.getResourceActions(resource.resourceGroupId)); + toggleClass(template.name, 'strike-through', resource.decorations.strikeThrough); + + if (resource.decorations.icon) { + template.decorationIcon.style.backgroundImage = `url('${resource.decorations.icon}')`; + } else { + delete template.decorationIcon.style.backgroundImage; + } } disposeTemplate(template: ResourceTemplate): void { diff --git a/src/vs/workbench/services/scm/common/scm.ts b/src/vs/workbench/services/scm/common/scm.ts index 6223681243e..68f7810e682 100644 --- a/src/vs/workbench/services/scm/common/scm.ts +++ b/src/vs/workbench/services/scm/common/scm.ts @@ -17,9 +17,15 @@ export interface IBaselineResourceProvider { export const ISCMService = createDecorator('scm'); +export interface ISCMResourceDecorations { + icon: URI; + strikeThrough?: boolean; +} + export interface ISCMResource { - readonly uri: URI; readonly resourceGroupId: string; + readonly uri: URI; + readonly decorations: ISCMResourceDecorations; } export interface ISCMResourceGroup {