diff --git a/extensions/css/client/src/colorDecorators.ts b/extensions/css/client/src/colorDecorators.ts index e68dad254ab..3d7828f2939 100644 --- a/extensions/css/client/src/colorDecorators.ts +++ b/extensions/css/client/src/colorDecorators.ts @@ -5,7 +5,7 @@ 'use strict'; import * as parse from 'parse-color'; -import { Range, TextDocument, DocumentColorProvider, Color, ColorRange } from 'vscode'; +import { workspace, Range, TextDocument, DocumentColorProvider, Color, ColorRange, Event, EventEmitter } from 'vscode'; const CSSColorFormats = { Hex: '#{red:X}{green:X}{blue:X}', @@ -20,12 +20,37 @@ const CSSColorFormats = { }; export class ColorProvider implements DocumentColorProvider { - constructor(private decoratorProvider: (uri: string) => Thenable, private supportedLanguages: { [id: string]: boolean }, private isDecoratorEnabled: (languageId: string) => boolean) { } + private onDidChangeColorsEmitter = new EventEmitter(); + private decoratorEnablement = {}; + + constructor(private decoratorProvider: (uri: string) => Thenable, private supportedLanguages: { [id: string]: boolean }, isDecoratorEnabled: (languageId: string) => boolean) { + for (let languageId in supportedLanguages) { + this.decoratorEnablement[languageId] = isDecoratorEnabled(languageId); + } + + workspace.onDidChangeConfiguration(_ => { + let hasChanges = false; + for (let languageId in supportedLanguages) { + let prev = this.decoratorEnablement[languageId]; + let curr = isDecoratorEnabled(languageId); + if (prev !== curr) { + this.decoratorEnablement[languageId] = curr; + hasChanges = true; + } + } + if (hasChanges) { + this.onDidChangeColorsEmitter.fire(); + } + }); + } + + public get onDidChangeColors(): Event { + return this.onDidChangeColorsEmitter.event; + } async provideDocumentColors(document: TextDocument): Promise { - let renderDecorator = false; - if (document && this.isDecoratorEnabled(document.languageId)) { - renderDecorator = true; + if (!this.supportedLanguages[document.languageId] || !this.decoratorEnablement[document.languageId]) { + return []; } const ranges = await this.decoratorProvider(document.uri.toString()); @@ -43,7 +68,7 @@ export class ColorProvider implements DocumentColorProvider { } } if (color) { - result.push(new ColorRange(range, color, [CSSColorFormats.Hex, CSSColorFormats.RGB, CSSColorFormats.HSL], renderDecorator)); + result.push(new ColorRange(range, color, [CSSColorFormats.Hex, CSSColorFormats.RGB, CSSColorFormats.HSL])); } } return result; diff --git a/extensions/json/client/src/colorDecorators.ts b/extensions/json/client/src/colorDecorators.ts index f9e94069dc2..ddf591add67 100644 --- a/extensions/json/client/src/colorDecorators.ts +++ b/extensions/json/client/src/colorDecorators.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { Range, TextDocument, DocumentColorProvider, Color, ColorRange } from 'vscode'; +import { workspace, Range, TextDocument, DocumentColorProvider, Color, ColorRange, Event, EventEmitter } from 'vscode'; const ColorFormat_HEX = { opaque: '"#{red:X}{green:X}{blue:X}"', @@ -12,12 +12,38 @@ const ColorFormat_HEX = { }; export class ColorProvider implements DocumentColorProvider { - constructor(private decoratorProvider: (uri: string) => Thenable, private supportedLanguages: { [id: string]: boolean }, private isDecoratorEnabled: (languageId: string) => boolean) { } + private onDidChangeColorsEmitter = new EventEmitter(); + private decoratorEnablement = {}; + + constructor(private decoratorProvider: (uri: string) => Thenable, private supportedLanguages: { [id: string]: boolean }, isDecoratorEnabled: (languageId: string) => boolean) { + for (let languageId in supportedLanguages) { + this.decoratorEnablement[languageId] = isDecoratorEnabled(languageId); + } + + workspace.onDidChangeConfiguration(_ => { + let hasChanges = false; + for (let languageId in supportedLanguages) { + let prev = this.decoratorEnablement[languageId]; + let curr = isDecoratorEnabled(languageId); + if (prev !== curr) { + this.decoratorEnablement[languageId] = curr; + hasChanges = true; + } + } + if (hasChanges) { + this.onDidChangeColorsEmitter.fire(); + } + }); + } + + + public get onDidChangeColors(): Event { + return this.onDidChangeColorsEmitter.event; + } async provideDocumentColors(document: TextDocument): Promise { - let renderDecorator = false; - if (document && this.isDecoratorEnabled(document.languageId)) { - renderDecorator = true; + if (!this.supportedLanguages[document.languageId] || !this.decoratorEnablement[document.languageId]) { + return []; } const ranges = await this.decoratorProvider(document.uri.toString()); @@ -26,7 +52,7 @@ export class ColorProvider implements DocumentColorProvider { let color = parseColorFromRange(document, range); if (color) { let r = new Range(range.start.line, range.start.character, range.end.line, range.end.character); - result.push(new ColorRange(r, color, [ColorFormat_HEX], renderDecorator)); + result.push(new ColorRange(r, color, [ColorFormat_HEX])); } } return result; diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 02af3f2c6af..b65ae364849 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -709,11 +709,6 @@ export interface IColorRange { * The available formats for this specific color. */ formatters: IColorFormatter[]; - - /** - * Controls whether the color decorator is rendered. - */ - renderDecorator: boolean; } /** @@ -721,6 +716,7 @@ export interface IColorRange { * @internal */ export interface ColorRangeProvider { + onDidChange?: Event; /** * Provides the color ranges for a specific model. diff --git a/src/vs/editor/contrib/colorPicker/browser/colorController.ts b/src/vs/editor/contrib/colorPicker/browser/colorController.ts index facb2f800df..7f673d8fdd8 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorController.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorController.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ICommonCodeEditor, IEditorContribution } from 'vs/editor/common/editorCommon'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions'; @@ -28,18 +29,21 @@ export class ColorController extends Disposable implements IEditorContribution { constructor( private _editor: ICodeEditor, - @ICodeEditorService private _codeEditorService: ICodeEditorService + @ICodeEditorService private _codeEditorService: ICodeEditorService, + @IConfigurationService private _configurationService: IConfigurationService ) { super(); this._decorations = []; this._decorationsTypes = {}; + this._register(ColorProviderRegistry.onDidChange((e) => this.triggerUpdateDecorations())); this._register(_editor.onDidChangeModel((e) => this.triggerUpdateDecorations())); + this._register(_editor.onDidChangeModelLanguage((e) => this.triggerUpdateDecorations())); this._register(_editor.onDidChangeModelContent((e) => { setTimeout(() => this.triggerUpdateDecorations(), 0); })); - this._register(_editor.onDidChangeModelLanguage((e) => this.triggerUpdateDecorations())); - this._register(_editor.onDidChangeConfiguration((e) => this.triggerUpdateDecorations())); - this._register(ColorProviderRegistry.onDidChange((e) => this.triggerUpdateDecorations())); + this._register(_configurationService.onDidUpdateConfiguration((e) => { + setTimeout(() => this.triggerUpdateDecorations(), 0); + })); this.triggerUpdateDecorations(); } @@ -49,9 +53,6 @@ export class ColorController extends Disposable implements IEditorContribution { let newDecorationsTypes: { [key: string]: boolean } = {}; for (let i = 0; i < colorInfos.length && decorations.length < MAX_DECORATORS; i++) { - if (!colorInfos[i].renderDecorator) { - continue; - } const { red, green, blue, alpha } = colorInfos[i].color; const rgba = new RGBA(Math.round(red * 255), Math.round(green * 255), Math.round(blue * 255), alpha); let subKey = hash(rgba).toString(16); diff --git a/src/vs/editor/contrib/colorPicker/browser/colorDetector.ts b/src/vs/editor/contrib/colorPicker/browser/colorDetector.ts index 37f549337aa..0895cac562d 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorDetector.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorDetector.ts @@ -3,15 +3,20 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { RGBA } from 'vs/base/common/color'; +import { hash } from 'vs/base/common/hash'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { TPromise } from 'vs/base/common/winjs.base'; import { ICommonCodeEditor, IEditorContribution } from 'vs/editor/common/editorCommon'; import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { ColorProviderRegistry, IColorRange } from 'vs/editor/common/modes'; -import { TPromise } from 'vs/base/common/winjs.base'; -import { getColors } from 'vs/editor/contrib/colorPicker/common/color'; import { Range } from 'vs/editor/common/core/range'; import { Position } from 'vs/editor/common/core/position'; +import { ColorProviderRegistry, IColorRange } from 'vs/editor/common/modes'; +import { ICodeEditorService } from 'vs/editor/common/services/codeEditorService'; +import { getColors } from 'vs/editor/contrib/colorPicker/common/color'; + +const MAX_DECORATORS = 500; @editorContribution export class ColorDetector implements IEditorContribution { @@ -20,22 +25,27 @@ export class ColorDetector implements IEditorContribution { static RECOMPUTE_TIME = 1000; // ms - private listenersToRemove: IDisposable[] = []; + private globalToDispose: IDisposable[] = []; + private localToDispose: IDisposable[] = []; private computePromise: TPromise; private timeoutPromise: TPromise; private decorationsIds: string[] = []; private colorRanges = new Map(); - constructor(private editor: ICodeEditor) { - this.listenersToRemove.push(editor.onDidChangeModelContent((e) => this.onChange())); - this.listenersToRemove.push(editor.onDidChangeModel((e) => this.onModelChanged())); - this.listenersToRemove.push(editor.onDidChangeModelLanguage((e) => this.onModelModeChanged())); - this.listenersToRemove.push(ColorProviderRegistry.onDidChange((e) => this.onModelModeChanged())); + private _colorDecorators: string[] = []; + private _decorationsTypes: { [key: string]: boolean } = {}; + + constructor(private editor: ICodeEditor, + @ICodeEditorService private _codeEditorService: ICodeEditorService, + ) { + this.globalToDispose.push(editor.onDidChangeModel((e) => this.onModelChanged())); + this.globalToDispose.push(editor.onDidChangeModelLanguage((e) => this.onModelChanged())); + this.globalToDispose.push(ColorProviderRegistry.onDidChange((e) => this.onModelChanged())); this.timeoutPromise = null; this.computePromise = null; - this.beginCompute(); + this.onModelChanged(); } getId(): string { @@ -47,41 +57,54 @@ export class ColorDetector implements IEditorContribution { } dispose(): void { - this.listenersToRemove = dispose(this.listenersToRemove); this.stop(); + this.globalToDispose = dispose(this.globalToDispose); } private onModelChanged(): void { this.stop(); - this.beginCompute(); - } - - private onModelModeChanged(): void { - this.stop(); - this.beginCompute(); - } - - private onChange(): void { - if (!this.timeoutPromise) { - this.timeoutPromise = TPromise.timeout(ColorDetector.RECOMPUTE_TIME); - this.timeoutPromise.then(() => { - this.timeoutPromise = null; - this.beginCompute(); - }); + const model = this.editor.getModel(); + if (!model) { + return; } + + if (!ColorProviderRegistry.has(model)) { + return; + } + + for (const provider of ColorProviderRegistry.all(model)) { + if (typeof provider.onDidChange === 'function') { + let registration = provider.onDidChange(() => { + if (this.timeoutPromise) { + this.timeoutPromise.cancel(); + this.timeoutPromise = null; + } + if (this.computePromise) { + this.computePromise.cancel(); + this.computePromise = null; + } + this.beginCompute(); + }); + this.localToDispose.push(registration); + } + } + + this.localToDispose.push(this.editor.onDidChangeModelContent((e) => { + if (!this.timeoutPromise) { + this.timeoutPromise = TPromise.timeout(ColorDetector.RECOMPUTE_TIME); + this.timeoutPromise.then(() => { + this.timeoutPromise = null; + this.beginCompute(); + }); + } + })); + this.beginCompute(); } private beginCompute(): void { - if (!this.editor.getModel()) { - return; - } - - if (!ColorProviderRegistry.has(this.editor.getModel())) { - return; - } - this.computePromise = getColors(this.editor.getModel()).then(colorInfos => { this.updateDecorations(colorInfos); + this.updateColorDecorators(colorInfos); this.computePromise = null; }); } @@ -95,6 +118,7 @@ export class ColorDetector implements IEditorContribution { this.computePromise.cancel(); this.computePromise = null; } + this.localToDispose = dispose(this.localToDispose); } private updateDecorations(colorInfos: IColorRange[]): void { @@ -111,8 +135,7 @@ export class ColorDetector implements IEditorContribution { const colorRanges = colorInfos.map(c => ({ range: c.range, color: c.color, - formatters: c.formatters, - renderDecorator: c.renderDecorator + formatters: c.formatters })); this.decorationsIds = this.editor.deltaDecorations(this.decorationsIds, decorations); @@ -121,6 +144,56 @@ export class ColorDetector implements IEditorContribution { this.decorationsIds.forEach((id, i) => this.colorRanges.set(id, colorRanges[i])); } + private updateColorDecorators(colorInfos: IColorRange[]): void { + let decorations = []; + let newDecorationsTypes: { [key: string]: boolean } = {}; + + for (let i = 0; i < colorInfos.length && decorations.length < MAX_DECORATORS; i++) { + const { red, green, blue, alpha } = colorInfos[i].color; + const rgba = new RGBA(Math.round(red * 255), Math.round(green * 255), Math.round(blue * 255), alpha); + let subKey = hash(rgba).toString(16); + let color = `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})`; + let key = 'colorBox-' + subKey; + + if (!this._decorationsTypes[key] && !newDecorationsTypes[key]) { + this._codeEditorService.registerDecorationType(key, { + before: { + contentText: ' ', + border: 'solid 0.1em #000', + margin: '0.1em 0.2em 0 0.2em', + width: '0.8em', + height: '0.8em', + backgroundColor: color + }, + dark: { + before: { + border: 'solid 0.1em #eee' + } + } + }); + } + + newDecorationsTypes[key] = true; + decorations.push({ + range: { + startLineNumber: colorInfos[i].range.startLineNumber, + startColumn: colorInfos[i].range.startColumn, + endLineNumber: colorInfos[i].range.endLineNumber, + endColumn: colorInfos[i].range.endColumn + }, + options: this._codeEditorService.resolveDecorationOptions(key, true) + }); + } + + for (let subType in this._decorationsTypes) { + if (!newDecorationsTypes[subType]) { + this._codeEditorService.removeDecorationType(subType); + } + } + + this._colorDecorators = this.editor.deltaDecorations(this._colorDecorators, decorations); + } + getColorRange(position: Position): IColorRange | null { const decorations = this.editor.getModel() .getDecorationsInRange(Range.fromPositions(position, position)) diff --git a/src/vs/editor/editor.all.ts b/src/vs/editor/editor.all.ts index 4625116cf98..bba9229491d 100644 --- a/src/vs/editor/editor.all.ts +++ b/src/vs/editor/editor.all.ts @@ -41,5 +41,4 @@ import 'vs/editor/contrib/suggest/browser/suggestController'; import 'vs/editor/contrib/toggleTabFocusMode/common/toggleTabFocusMode'; import 'vs/editor/contrib/wordHighlighter/common/wordHighlighter'; import 'vs/editor/contrib/wordOperations/common/wordOperations'; -import 'vs/editor/contrib/colorPicker/browser/colorController'; import 'vs/editor/contrib/colorPicker/browser/colorDetector'; \ No newline at end of file diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index edf188cbf75..6bd713ede6c 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -179,20 +179,14 @@ declare module 'vscode' { */ availableFormats: ColorFormat[]; - /** - * Controls whether the color decorator is rendered. - */ - renderDecorator: boolean; - /** * Creates a new color range. * * @param range The range the color appears in. Must not be empty. * @param color The value of the color. * @param format The format in which this color is currently formatted. - * @param renderDecorator Controls whether the color decorator is rendered. */ - constructor(range: Range, color: Color, availableFormats: ColorFormat[], renderDecorator: boolean); + constructor(range: Range, color: Color, availableFormats: ColorFormat[]); } /** @@ -200,6 +194,10 @@ declare module 'vscode' { * picking and modifying colors in the editor. */ export interface DocumentColorProvider { + /** + * An optional event to signal that the Colors from this provider have changed. + */ + onDidChangeColors?: Event; /** * Provide colors for the given document. diff --git a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts index d28eb322823..954d2a2e034 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts @@ -276,10 +276,9 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- colors - $registerDocumentColorProvider(handle: number, selector: vscode.DocumentSelector): TPromise { + $registerDocumentColorProvider(handle: number, selector: vscode.DocumentSelector, eventHandle: number): TPromise { const proxy = this._proxy; - - this._registrations[handle] = modes.ColorProviderRegistry.register(selector, { + const provider = { provideColorRanges: (model, token) => { return wireCancellationToken(token, proxy.$provideDocumentColors(handle, model.uri)) .then(documentColors => { @@ -303,17 +302,32 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha return { color, formatters, - range: documentColor.range, - renderDecorator: documentColor.renderDecorator + range: documentColor.range }; }); }); } - }); + }; + + if (typeof eventHandle === 'number') { + const emitter = new Emitter(); + this._registrations[eventHandle] = emitter; + provider.onDidChange = emitter.event; + } + + this._registrations[handle] = modes.ColorProviderRegistry.register(selector, provider); return TPromise.as(null); } + $emitColorsEvent(eventHandle: number, event?: any): TPromise { + const obj = this._registrations[eventHandle]; + if (obj instanceof Emitter) { + obj.fire(event); + } + return undefined; + } + $registerColorFormats(formats: IRawColorFormatMap): TPromise { formats.forEach(f => this._formatters.set(f[0], new ColorFormatter(f[1]))); return TPromise.as(null); diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index dc68ebb8bed..eb47237c104 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -226,7 +226,8 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerSignatureHelpProvider(handle: number, selector: vscode.DocumentSelector, triggerCharacter: string[]): TPromise; $registerDocumentLinkProvider(handle: number, selector: vscode.DocumentSelector): TPromise; $registerColorFormats(formats: IRawColorFormatMap): TPromise; - $registerDocumentColorProvider(handle: number, selector: vscode.DocumentSelector): TPromise; + $registerDocumentColorProvider(handle: number, selector: vscode.DocumentSelector, eventHandle: number): TPromise; + $emitColorsEvent(eventHandle: number, event?: any): TPromise; $setLanguageConfiguration(handle: number, languageId: string, configuration: vscode.LanguageConfiguration): TPromise; } @@ -474,7 +475,6 @@ export interface IRawColorInfo { color: [number, number, number, number]; availableFormats: (number | [number, number])[]; range: IRange; - renderDecorator: boolean; } export type IRawColorFormatMap = [number, string][]; diff --git a/src/vs/workbench/api/node/extHostLanguageFeatures.ts b/src/vs/workbench/api/node/extHostLanguageFeatures.ts index 0c40410d01d..d628b7ded6c 100644 --- a/src/vs/workbench/api/node/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/node/extHostLanguageFeatures.ts @@ -734,8 +734,7 @@ class ColorProviderAdapter { return { color: [ci.color.red, ci.color.green, ci.color.blue, ci.color.alpha] as [number, number, number, number], availableFormats: availableFormats, - range: TypeConverters.fromRange(ci.range), - renderDecorator: ci.renderDecorator + range: TypeConverters.fromRange(ci.range) }; }); @@ -1040,9 +1039,16 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { registerColorProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentColorProvider): vscode.Disposable { const handle = this._nextHandle(); + const eventHandle = typeof provider.onDidChangeColors === 'function' ? this._nextHandle() : undefined; this._adapter.set(handle, new ColorProviderAdapter(this._proxy, this._documents, this._colorFormatCache, provider)); - this._proxy.$registerDocumentColorProvider(handle, selector); - return this._createDisposable(handle); + this._proxy.$registerDocumentColorProvider(handle, selector, eventHandle); + let result = this._createDisposable(handle); + + if (eventHandle !== undefined) { + const subscription = provider.onDidChangeColors(_ => this._proxy.$emitColorsEvent(eventHandle)); + result = Disposable.from(result, subscription); + } + return result; } $provideDocumentColors(handle: number, resource: URI): TPromise { diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index e5f82229efb..980d89b0c0e 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -1056,9 +1056,7 @@ export class ColorRange { availableFormats: IColorFormat[]; - renderDecorator: boolean; - - constructor(range: Range, color: Color, availableFormats: IColorFormat[], renderDecorator: boolean) { + constructor(range: Range, color: Color, availableFormats: IColorFormat[]) { if (color && !(color instanceof Color)) { throw illegalArgument('color'); } @@ -1071,7 +1069,6 @@ export class ColorRange { this.range = range; this.color = color; this.availableFormats = availableFormats; - this.renderDecorator = renderDecorator; } }