diff --git a/extensions/git/resources/icons/dark/status-added.svg b/extensions/git/resources/icons/dark/status-added.svg new file mode 100644 index 00000000000..cdc40f45f16 --- /dev/null +++ b/extensions/git/resources/icons/dark/status-added.svg @@ -0,0 +1,6 @@ + + + + A + + \ No newline at end of file diff --git a/extensions/git/resources/icons/dark/status-conflict.svg b/extensions/git/resources/icons/dark/status-conflict.svg new file mode 100644 index 00000000000..53b243c8b9f --- /dev/null +++ b/extensions/git/resources/icons/dark/status-conflict.svg @@ -0,0 +1,6 @@ + + + + C + + \ No newline at end of file diff --git a/extensions/git/resources/icons/dark/status-copied.svg b/extensions/git/resources/icons/dark/status-copied.svg new file mode 100644 index 00000000000..7bd78c9427e --- /dev/null +++ b/extensions/git/resources/icons/dark/status-copied.svg @@ -0,0 +1,6 @@ + + + + C + + \ No newline at end of file diff --git a/extensions/git/resources/icons/dark/status-deleted.svg b/extensions/git/resources/icons/dark/status-deleted.svg new file mode 100644 index 00000000000..e7596e2e2ad --- /dev/null +++ b/extensions/git/resources/icons/dark/status-deleted.svg @@ -0,0 +1,6 @@ + + + + D + + \ No newline at end of file diff --git a/extensions/git/resources/icons/dark/status-ignored.svg b/extensions/git/resources/icons/dark/status-ignored.svg new file mode 100644 index 00000000000..85abc367a29 --- /dev/null +++ b/extensions/git/resources/icons/dark/status-ignored.svg @@ -0,0 +1,6 @@ + + + + I + + \ No newline at end of file diff --git a/extensions/git/resources/icons/dark/status-modified.svg b/extensions/git/resources/icons/dark/status-modified.svg new file mode 100644 index 00000000000..d0de37d3468 --- /dev/null +++ b/extensions/git/resources/icons/dark/status-modified.svg @@ -0,0 +1,6 @@ + + + + M + + \ No newline at end of file diff --git a/extensions/git/resources/icons/dark/status-renamed.svg b/extensions/git/resources/icons/dark/status-renamed.svg new file mode 100644 index 00000000000..878067dd4b1 --- /dev/null +++ b/extensions/git/resources/icons/dark/status-renamed.svg @@ -0,0 +1,6 @@ + + + + R + + \ No newline at end of file diff --git a/extensions/git/resources/icons/dark/status-untracked.svg b/extensions/git/resources/icons/dark/status-untracked.svg new file mode 100644 index 00000000000..c6a48e14f08 --- /dev/null +++ b/extensions/git/resources/icons/dark/status-untracked.svg @@ -0,0 +1,6 @@ + + + + U + + \ No newline at end of file diff --git a/extensions/git/src/scmProvider.ts b/extensions/git/src/scmProvider.ts index 57d74e5d3f3..f397acf5cc9 100644 --- a/extensions/git/src/scmProvider.ts +++ b/extensions/git/src/scmProvider.ts @@ -9,29 +9,12 @@ import { Uri, Disposable, SCMProvider, SCMResource, SCMResourceDecorations, SCMR import { Model } from './model'; import * as path from 'path'; -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`)); +function getIconUri(iconName: string, theme: string): Uri { + return Uri.file(path.join(iconsRootPath, theme, `${iconName}.svg`)); } -const Icons = { - Modified: getIconUri('status-modified', Theme.Light), - Added: getIconUri('status-added', Theme.Light), - Deleted: getIconUri('status-deleted', Theme.Light), - Renamed: getIconUri('status-renamed', Theme.Light), - Copied: getIconUri('status-copied', Theme.Light), - Untracked: getIconUri('status-untracked', Theme.Light), - Ignored: getIconUri('status-ignored', Theme.Light), - Conflict: getIconUri('status-conflict', Theme.Light), -}; - enum Status { INDEX_MODIFIED, INDEX_ADDED, @@ -57,24 +40,47 @@ class Resource implements SCMResource { get uri(): Uri { return this._uri; } - private get iconPath(): Uri | undefined { + private static Icons = { + light: { + Modified: getIconUri('status-modified', 'light'), + Added: getIconUri('status-added', 'light'), + Deleted: getIconUri('status-deleted', 'light'), + Renamed: getIconUri('status-renamed', 'light'), + Copied: getIconUri('status-copied', 'light'), + Untracked: getIconUri('status-untracked', 'light'), + Ignored: getIconUri('status-ignored', 'light'), + Conflict: getIconUri('status-conflict', 'light'), + }, + dark: { + Modified: getIconUri('status-modified', 'dark'), + Added: getIconUri('status-added', 'dark'), + Deleted: getIconUri('status-deleted', 'dark'), + Renamed: getIconUri('status-renamed', 'dark'), + Copied: getIconUri('status-copied', 'dark'), + Untracked: getIconUri('status-untracked', 'dark'), + Ignored: getIconUri('status-ignored', 'dark'), + Conflict: getIconUri('status-conflict', 'dark') + } + }; + + private getIconPath(theme: string): Uri | undefined { switch (this.type) { - case Status.INDEX_MODIFIED: return Icons.Modified; - case Status.MODIFIED: return Icons.Modified; - case Status.INDEX_ADDED: return Icons.Added; - case Status.INDEX_DELETED: return Icons.Deleted; - case Status.DELETED: return Icons.Deleted; - case Status.INDEX_RENAMED: return Icons.Renamed; - case Status.INDEX_COPIED: return Icons.Copied; - case Status.UNTRACKED: return Icons.Untracked; - case Status.IGNORED: return Icons.Ignored; - case Status.BOTH_DELETED: return Icons.Conflict; - case Status.ADDED_BY_US: return Icons.Conflict; - case Status.DELETED_BY_THEM: return Icons.Conflict; - case Status.ADDED_BY_THEM: return Icons.Conflict; - case Status.DELETED_BY_US: return Icons.Conflict; - case Status.BOTH_ADDED: return Icons.Conflict; - case Status.BOTH_MODIFIED: return Icons.Conflict; + case Status.INDEX_MODIFIED: return Resource.Icons[theme].Modified; + case Status.MODIFIED: return Resource.Icons[theme].Modified; + case Status.INDEX_ADDED: return Resource.Icons[theme].Added; + case Status.INDEX_DELETED: return Resource.Icons[theme].Deleted; + case Status.DELETED: return Resource.Icons[theme].Deleted; + case Status.INDEX_RENAMED: return Resource.Icons[theme].Renamed; + case Status.INDEX_COPIED: return Resource.Icons[theme].Copied; + case Status.UNTRACKED: return Resource.Icons[theme].Untracked; + case Status.IGNORED: return Resource.Icons[theme].Ignored; + case Status.BOTH_DELETED: return Resource.Icons[theme].Conflict; + case Status.ADDED_BY_US: return Resource.Icons[theme].Conflict; + case Status.DELETED_BY_THEM: return Resource.Icons[theme].Conflict; + case Status.ADDED_BY_THEM: return Resource.Icons[theme].Conflict; + case Status.DELETED_BY_US: return Resource.Icons[theme].Conflict; + case Status.BOTH_ADDED: return Resource.Icons[theme].Conflict; + case Status.BOTH_MODIFIED: return Resource.Icons[theme].Conflict; default: return void 0; } } @@ -92,10 +98,10 @@ class Resource implements SCMResource { } get decorations(): SCMResourceDecorations { - return { - iconPath: this.iconPath, - strikeThrough: this.strikeThrough - }; + const light = { iconPath: this.getIconPath('light') }; + const dark = { iconPath: this.getIconPath('dark') }; + + return { strikeThrough: this.strikeThrough, light, dark }; } constructor(private _uri: Uri, private type: any) { diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index a8f760686f1..4fdb07da97d 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -86,9 +86,14 @@ declare module 'vscode' { getClickCommand?(node: T): string; } - export interface SCMResourceDecorations { + export interface SCMResourceThemableDecorations { readonly iconPath?: string | Uri; + } + + export interface SCMResourceDecorations extends SCMResourceThemableDecorations { readonly strikeThrough?: boolean; + readonly light?: SCMResourceThemableDecorations; + readonly dark?: SCMResourceThemableDecorations; } export interface SCMResource { diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 1f341bd54a6..27b345d4ba0 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -236,7 +236,11 @@ export interface SCMProviderFeatures { supportsOriginalResource: boolean; } -export type SCMRawResource = [string /*uri*/, string /*decoration icon*/, boolean /*strike through*/]; +export type SCMRawResource = [ + string /*uri*/, + string[] /*icons: light, dark*/, + 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 2ac1569e733..63199d452a3 100644 --- a/src/vs/workbench/api/node/extHostSCM.ts +++ b/src/vs/workbench/api/node/extHostSCM.ts @@ -13,6 +13,16 @@ import { Disposable } from 'vs/workbench/api/node/extHostTypes'; import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceGroup } from './extHost.protocol'; import * as vscode from 'vscode'; +function getIconPath(decorations: vscode.SCMResourceThemableDecorations) { + if (!decorations) { + return void 0; + } else if (typeof decorations.iconPath === 'string') { + return URI.file(decorations.iconPath).toString(); + } else if (decorations.iconPath) { + return `${decorations.iconPath}`; + } +} + export class ExtHostSCM { private _proxy: MainThreadSCMShape; @@ -49,20 +59,22 @@ export class ExtHostSCM { const rawResourceGroups = resourceGroups.map(g => { const rawResources = g.resources.map(r => { const uri = r.uri.toString(); - let strikeThrough = false; - let decorationIcon: string | undefined; + const iconPath = getIconPath(r.decorations); + const lightIconPath = r.decorations && getIconPath(r.decorations.light) || iconPath; + const darkIconPath = r.decorations && getIconPath(r.decorations.dark) || iconPath; + const icons: string[] = []; - 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; + if (lightIconPath || darkIconPath) { + icons.push(lightIconPath); } - return [uri, decorationIcon, strikeThrough] as SCMRawResource; + if (darkIconPath !== lightIconPath) { + icons.push(darkIconPath); + } + + const strikeThrough = r.decorations && !!r.decorations.strikeThrough; + + return [uri, icons, 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 90eca27f859..ed4dfee5044 100644 --- a/src/vs/workbench/api/node/mainThreadSCM.ts +++ b/src/vs/workbench/api/node/mainThreadSCM.ts @@ -87,9 +87,14 @@ class MainThreadSCMProvider implements ISCMProvider { const [id, label, rawResources] = rawGroup; const resources = rawResources.map(rawResource => { - const [uri, decorationIcon, strikeThrough] = rawResource; + const [uri, icons, strikeThrough] = rawResource; + + const icon = icons[0]; + const iconDark = icons[1] || icon; + const decorations = { - icon: decorationIcon && URI.parse(decorationIcon), + icon: icon && URI.parse(icon), + iconDark: iconDark && URI.parse(iconDark), strikeThrough }; diff --git a/src/vs/workbench/parts/scm/browser/scmViewlet.ts b/src/vs/workbench/parts/scm/browser/scmViewlet.ts index d97cfce39da..c477a81b9c4 100644 --- a/src/vs/workbench/parts/scm/browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/browser/scmViewlet.ts @@ -35,6 +35,8 @@ import { IAction, IActionItem } from 'vs/base/common/actions'; import { createActionItem } from 'vs/platform/actions/browser/menuItemActionItem'; import { SCMMenus } from './scmMenus'; import { ActionBar, IActionItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IThemeService } from 'vs/workbench/services/themes/common/themeService'; +import { isDarkTheme } from 'vs/platform/theme/common/themes'; interface SearchInputEvent extends Event { target: HTMLInputElement; @@ -95,6 +97,7 @@ class ResourceRenderer implements IRenderer { constructor( private scmMenus: SCMMenus, private actionItemProvider: IActionItemProvider, + @IThemeService private themeService: IThemeService, @IInstantiationService private instantiationService: IInstantiationService ) { @@ -117,8 +120,11 @@ class ResourceRenderer implements IRenderer { 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}')`; + const theme = this.themeService.getColorTheme(); + const icon = isDarkTheme(theme) ? resource.decorations.iconDark : resource.decorations.icon; + + if (icon) { + template.decorationIcon.style.backgroundImage = `url('${icon}')`; } else { template.decorationIcon.style.backgroundImage = ''; } @@ -160,6 +166,7 @@ export class SCMViewlet extends Viewlet { @IKeybindingService private keybindingService: IKeybindingService, @IMessageService private messageService: IMessageService, @IContextMenuService private contextMenuService: IContextMenuService, + @IThemeService private themeService: IThemeService, @IMenuService private menuService: IMenuService ) { super(VIEWLET_ID, telemetryService); @@ -223,6 +230,7 @@ export class SCMViewlet extends Viewlet { this.setActiveProvider(this.scmService.activeProvider); this.scmService.onDidChangeProvider(this.setActiveProvider, this, this.disposables); + this.themeService.onDidColorThemeChange(this.update, this, this.disposables); return TPromise.as(null); } diff --git a/src/vs/workbench/services/scm/common/scm.ts b/src/vs/workbench/services/scm/common/scm.ts index 68f7810e682..d60e80ae5c5 100644 --- a/src/vs/workbench/services/scm/common/scm.ts +++ b/src/vs/workbench/services/scm/common/scm.ts @@ -18,7 +18,8 @@ export interface IBaselineResourceProvider { export const ISCMService = createDecorator('scm'); export interface ISCMResourceDecorations { - icon: URI; + icon?: URI; + iconDark?: URI; strikeThrough?: boolean; }