diff --git a/extensions/git/src/decorationProvider.ts b/extensions/git/src/decorationProvider.ts new file mode 100644 index 00000000000..ed4bad8aef0 --- /dev/null +++ b/extensions/git/src/decorationProvider.ts @@ -0,0 +1,96 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { window, Uri, Disposable, Event, EventEmitter, DecorationData, DecorationProvider } from 'vscode'; +import { Repository, GitResourceGroup } from './repository'; +import { Model } from './model'; + +class GitDecorationProvider implements DecorationProvider { + + private readonly _onDidChangeDecorations = new EventEmitter(); + readonly onDidChangeDecorations: Event = this._onDidChangeDecorations.event; + + private disposables: Disposable[] = []; + private decorations = new Map(); + + constructor(private repository: Repository) { + this.disposables.push( + window.registerDecorationProvider(this, repository.root), + repository.onDidRunOperation(this.onDidRunOperation, this) + ); + } + + private onDidRunOperation(): void { + let newDecorations = new Map(); + this.collectDecorationData(this.repository.indexGroup, newDecorations); + this.collectDecorationData(this.repository.workingTreeGroup, newDecorations); + + let uris: Uri[] = []; + newDecorations.forEach((value, uriString) => { + if (this.decorations.has(uriString)) { + this.decorations.delete(uriString); + } else { + uris.push(Uri.parse(uriString)); + } + }); + this.decorations.forEach((value, uriString) => { + uris.push(Uri.parse(uriString)); + }); + this.decorations = newDecorations; + this._onDidChangeDecorations.fire(uris); + } + + private collectDecorationData(group: GitResourceGroup, bucket: Map): void { + group.resourceStates.forEach(r => { + if (r.resourceDecoration) { + bucket.set(r.original.toString(), r.resourceDecoration); + } + }); + } + + provideDecoration(uri: Uri): DecorationData | undefined { + return this.decorations.get(uri.toString()); + } + + dispose(): void { + this.disposables.forEach(d => d.dispose()); + } +} + + +export class GitDecorations { + + private disposables: Disposable[] = []; + private providers = new Map(); + + constructor(private model: Model) { + this.disposables.push( + model.onDidOpenRepository(this.onDidOpenRepository, this), + model.onDidCloseRepository(this.onDidCloseRepository, this) + ); + model.repositories.forEach(this.onDidOpenRepository, this); + } + + private onDidOpenRepository(repository: Repository): void { + const provider = new GitDecorationProvider(repository); + this.providers.set(repository, provider); + } + + private onDidCloseRepository(repository: Repository): void { + const provider = this.providers.get(repository); + if (provider) { + provider.dispose(); + this.providers.delete(repository); + } + } + + dispose(): void { + this.disposables.forEach(d => d.dispose()); + this.providers.forEach(value => value.dispose); + this.providers.clear(); + } +} diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index acc30eeb81d..b090495be71 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -12,6 +12,7 @@ import { findGit, Git, IGit } from './git'; import { Model } from './model'; import { CommandCenter } from './commands'; import { GitContentProvider } from './contentProvider'; +import { GitDecorations } from './decorationProvider'; import { Askpass } from './askpass'; import { toDisposable } from './util'; import TelemetryReporter from 'vscode-extension-telemetry'; @@ -54,6 +55,7 @@ async function init(context: ExtensionContext, disposables: Disposable[]): Promi disposables.push( new CommandCenter(git, model, outputChannel, telemetryReporter), new GitContentProvider(model), + new GitDecorations(model) ); await checkGitVersion(info); @@ -93,4 +95,4 @@ async function checkGitVersion(info: IGit): Promise { } else if (choice === neverShowAgain) { await config.update('ignoreLegacyWarning', true, true); } -} \ No newline at end of file +} diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 6c9f781c58f..319edc3a07e 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -5,7 +5,7 @@ 'use strict'; -import { Uri, Command, EventEmitter, Event, scm, SourceControl, SourceControlInputBox, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, Disposable, ProgressLocation, window, workspace, WorkspaceEdit, ThemeColor } from 'vscode'; +import { Uri, Command, EventEmitter, Event, scm, SourceControl, SourceControlInputBox, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, Disposable, ProgressLocation, window, workspace, WorkspaceEdit, ThemeColor, DecorationData } from 'vscode'; import { Repository as BaseRepository, Ref, Branch, Remote, Commit, GitErrorCodes, Stash, RefType } from './git'; import { anyEvent, filterEvent, eventToPromise, dispose, find } from './util'; import { memoize, throttle, debounce } from './decorators'; @@ -170,27 +170,29 @@ export class Resource implements SourceControlResourceState { // return this.resourceUri.fsPath.substr(0, workspaceRootPath.length) !== workspaceRootPath; } - private get color(): ThemeColor | undefined { - switch (this.type) { - case Status.INDEX_MODIFIED: - case Status.MODIFIED: - return new ThemeColor('git.color.modified'); - case Status.UNTRACKED: - return new ThemeColor('git.color.untracked'); - default: - return undefined; - } - } - get decorations(): SourceControlResourceDecorations { const light = { iconPath: this.getIconPath('light') }; const dark = { iconPath: this.getIconPath('dark') }; const tooltip = this.tooltip; const strikeThrough = this.strikeThrough; const faded = this.faded; - const color = this.color; - return { strikeThrough, faded, tooltip, light, dark, color }; + return { strikeThrough, faded, tooltip, light, dark }; + } + + get resourceDecoration(): DecorationData | undefined { + const title = this.tooltip; + switch (this.type) { + case Status.IGNORED: + return { priority: 3, title, opacity: 0.75 }; + case Status.UNTRACKED: + return { priority: 1, title, abbreviation: localize('untracked, short', "U"), bubble: true, color: new ThemeColor('git.color.untracked') }; + case Status.INDEX_MODIFIED: + case Status.MODIFIED: + return { priority: 2, title, abbreviation: localize('modified, short', "M"), bubble: true, color: new ThemeColor('git.color.modified') }; + default: + return undefined; + } } constructor( diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 891b0b2289f..09c84d07fc1 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -5795,12 +5795,6 @@ declare module 'vscode' { */ readonly tooltip?: string; - /** - * A color for a specific - * [source control resource state](#SourceControlResourceState). - */ - readonly color?: ThemeColor; - /** * The light theme decorations. */ diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index e8485b673fc..d48b0f9020a 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -175,6 +175,7 @@ declare module 'vscode' { export interface DecorationData { priority?: number; title?: string; + bubble?: boolean; abbreviation?: string; color?: ThemeColor; opacity?: number; diff --git a/src/vs/workbench/api/electron-browser/mainThreadDecorations.ts b/src/vs/workbench/api/electron-browser/mainThreadDecorations.ts index 1dd8523ff44..b2ae08b829c 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDecorations.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDecorations.ts @@ -36,9 +36,13 @@ export class MainThreadDecorations implements MainThreadDecorationsShape { onDidChange: emitter.event, provideDecorations: (uri) => { return this._proxy.$providerDecorations(handle, uri).then(data => { - const [weight, title, letter, opacity, themeColor] = data; + if (!data) { + return undefined; + } + const [weight, bubble, title, letter, opacity, themeColor] = data; return { weight: weight || 0, + bubble: bubble || false, title, letter, opacity, diff --git a/src/vs/workbench/api/electron-browser/mainThreadSCM.ts b/src/vs/workbench/api/electron-browser/mainThreadSCM.ts index 69b253930b7..64253cfa2d4 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadSCM.ts @@ -182,7 +182,7 @@ class MainThreadSCMProvider implements ISCMProvider { for (const [start, deleteCount, rawResources] of groupSlices) { const resources = rawResources.map(rawResource => { - const [handle, sourceUri, icons, tooltip, strikeThrough, faded, color] = rawResource; + const [handle, sourceUri, icons, tooltip, strikeThrough, faded] = rawResource; const icon = icons[0]; const iconDark = icons[1] || icon; const decorations = { @@ -190,8 +190,7 @@ class MainThreadSCMProvider implements ISCMProvider { iconDark: iconDark && URI.parse(iconDark), tooltip, strikeThrough, - faded, - color: color && color.id + faded }; return new MainThreadSCMResource( diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index eee71c3f13d..5de663decdf 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -362,8 +362,7 @@ export type SCMRawResource = [ string[] /*icons: light, dark*/, string /*tooltip*/, boolean /*strike through*/, - boolean /*faded*/, - { id: string } /*ThemeColor*/ + boolean /*faded*/ ]; export type SCMRawResourceSplice = [ @@ -603,7 +602,7 @@ export interface ExtHostDebugServiceShape { } -export type DecorationData = [number, string, string, number, ThemeColor]; +export type DecorationData = [number, boolean, string, string, number, ThemeColor]; export interface ExtHostDecorationsShape { $providerDecorations(handle: number, uri: URI): TPromise; diff --git a/src/vs/workbench/api/node/extHostDecorations.ts b/src/vs/workbench/api/node/extHostDecorations.ts index 05b93b96f33..ad428987404 100644 --- a/src/vs/workbench/api/node/extHostDecorations.ts +++ b/src/vs/workbench/api/node/extHostDecorations.ts @@ -41,7 +41,7 @@ export class ExtHostDecorations implements ExtHostDecorationsShape { $providerDecorations(handle: number, uri: URI): TPromise { const provider = this._provider.get(handle); return asWinJsPromise(token => provider.provideDecoration(uri, token)).then(data => { - return [data.priority, data.title, data.abbreviation, data.opacity, data.color]; + return data && [data.priority, data.bubble, data.title, data.abbreviation, data.opacity, data.color]; }); } } diff --git a/src/vs/workbench/api/node/extHostSCM.ts b/src/vs/workbench/api/node/extHostSCM.ts index 9fafcbf368d..310ea752f91 100644 --- a/src/vs/workbench/api/node/extHostSCM.ts +++ b/src/vs/workbench/api/node/extHostSCM.ts @@ -243,9 +243,8 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG const tooltip = (r.decorations && r.decorations.tooltip) || ''; const strikeThrough = r.decorations && !!r.decorations.strikeThrough; const faded = r.decorations && !!r.decorations.faded; - const color = r.decorations && r.decorations.color; - return [handle, sourceUri, icons, tooltip, strikeThrough, faded, color] as SCMRawResource; + return [handle, sourceUri, icons, tooltip, strikeThrough, faded] as SCMRawResource; }); handlesToDelete.push(...this._handlesSnapshot.splice(start, deleteCount, ...handles)); diff --git a/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts b/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts index 9405fdb123c..b5465c83f69 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts @@ -17,9 +17,7 @@ import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { StatusUpdater, StatusBarController } from './scmActivity'; -import { FileDecorations } from './scmFileDecorations'; import { SCMViewlet } from 'vs/workbench/parts/scm/electron-browser/scmViewlet'; -import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; class OpenSCMViewletAction extends ToggleViewletAction { @@ -51,9 +49,6 @@ Registry.as(WorkbenchExtensions.Workbench) Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(StatusBarController); -Registry.as(WorkbenchExtensions.Workbench) - .registerWorkbenchContribution(FileDecorations); - // Register Action to Open Viewlet Registry.as(WorkbenchActionExtensions.WorkbenchActions).registerWorkbenchAction( new SyncActionDescriptor(OpenSCMViewletAction, VIEWLET_ID, localize('toggleSCMViewlet', "Show SCM"), { @@ -65,17 +60,3 @@ Registry.as(WorkbenchActionExtensions.WorkbenchActions 'View: Show SCM', localize('view', "View") ); - - -Registry.as(Extensions.Configuration).registerConfiguration({ - 'id': 'scm', - 'order': 101, - 'type': 'object', - 'properties': { - 'scm.fileDecorations.enabled': { - 'description': localize('scm.fileDecorations.enabled', "Show source control status on files and folders"), - 'type': 'boolean', - 'default': true - } - } -}); diff --git a/src/vs/workbench/parts/scm/electron-browser/scmFileDecorations.ts b/src/vs/workbench/parts/scm/electron-browser/scmFileDecorations.ts deleted file mode 100644 index 23e0d15fdae..00000000000 --- a/src/vs/workbench/parts/scm/electron-browser/scmFileDecorations.ts +++ /dev/null @@ -1,136 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IDecorationsService, IDecorationsProvider, IDecorationData } from 'vs/workbench/services/decorations/browser/decorations'; -import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle'; -import { ISCMService, ISCMRepository, ISCMProvider, ISCMResource } from 'vs/workbench/services/scm/common/scm'; -import URI from 'vs/base/common/uri'; -import Event, { Emitter } from 'vs/base/common/event'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { localize } from 'vs/nls'; - -class SCMDecorationsProvider implements IDecorationsProvider { - - private readonly _disposable: IDisposable; - private readonly _onDidChange = new Emitter(); - private _data = new Map(); - - readonly label: string; - readonly onDidChange: Event = this._onDidChange.event; - - constructor( - private readonly _provider: ISCMProvider, - private readonly _config: ISCMConfiguration - ) { - this.label = this._provider.label; - this._disposable = this._provider.onDidChangeResources(this._updateGroups, this); - this._updateGroups(); - } - - dispose(): void { - this._disposable.dispose(); - } - - private _updateGroups(): void { - const uris: URI[] = []; - const newData = new Map(); - for (const group of this._provider.resources) { - for (const resource of group.resourceCollection.resources) { - newData.set(resource.sourceUri.toString(), resource); - - if (!this._data.has(resource.sourceUri.toString())) { - uris.push(resource.sourceUri); // added - } - } - } - - this._data.forEach((value, key) => { - if (!newData.has(key)) { - uris.push(value.sourceUri); // removed - } - }); - - this._data = newData; - this._onDidChange.fire(uris); - } - - provideDecorations(uri: URI): IDecorationData { - const resource = this._data.get(uri.toString()); - if (!resource || !resource.decorations.color || !resource.decorations.tooltip) { - return undefined; - } - return { - bubble: true, - weight: 255 - resource.decorations.tooltip.charAt(0).toLowerCase().charCodeAt(0), - title: localize('tooltip', "{0}, {1}", resource.decorations.tooltip, this._provider.label), - color: resource.decorations.color, - letter: resource.decorations.tooltip.charAt(0) - }; - } -} - -interface ISCMConfiguration { - fileDecorations: { - enabled: boolean; - }; -} - -export class FileDecorations implements IWorkbenchContribution { - - private _providers = new Map(); - private _configListener: IDisposable; - private _repoListeners: IDisposable[]; - - constructor( - @IDecorationsService private _decorationsService: IDecorationsService, - @IConfigurationService private _configurationService: IConfigurationService, - @ISCMService private _scmService: ISCMService, - ) { - this._configListener = this._configurationService.onDidChangeConfiguration(e => e.affectsConfiguration('scm.fileDecorations.enabled') && this._update()); - this._update(); - } - - getId(): string { - throw new Error('smc.SCMFileDecorations'); - } - - dispose(): void { - this._providers.forEach(value => dispose(value)); - dispose(this._repoListeners); - dispose(this._configListener, this._configListener); - } - - private _update(): void { - const config = this._configurationService.getConfiguration('scm'); - if (config.fileDecorations.enabled) { - this._scmService.repositories.forEach(this._onDidAddRepository, this); - this._repoListeners = [ - this._scmService.onDidAddRepository(this._onDidAddRepository, this), - this._scmService.onDidRemoveRepository(this._onDidRemoveRepository, this) - ]; - } else { - this._repoListeners = dispose(this._repoListeners); - this._providers.forEach(value => dispose(value)); - this._providers.clear(); - } - } - - private _onDidAddRepository(repo: ISCMRepository): void { - const provider = new SCMDecorationsProvider(repo.provider, this._configurationService.getConfiguration('scm')); - const registration = this._decorationsService.registerDecorationsProvider(provider); - this._providers.set(repo, combinedDisposable([registration, provider])); - } - - private _onDidRemoveRepository(repo: ISCMRepository): void { - let listener = this._providers.get(repo); - if (listener) { - this._providers.delete(repo); - listener.dispose(); - } - } -} diff --git a/src/vs/workbench/services/scm/common/scm.ts b/src/vs/workbench/services/scm/common/scm.ts index e5dda8e6e2b..b659bfeedc4 100644 --- a/src/vs/workbench/services/scm/common/scm.ts +++ b/src/vs/workbench/services/scm/common/scm.ts @@ -11,7 +11,6 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import Event from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Command } from 'vs/editor/common/modes'; -import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; export interface IBaselineResourceProvider { getBaselineResource(resource: URI): TPromise; @@ -25,7 +24,6 @@ export interface ISCMResourceDecorations { tooltip?: string; strikeThrough?: boolean; faded?: boolean; - color?: ColorIdentifier; } export interface ISCMResourceSplice {