提交 9937bcf6 编写于 作者: R rebornix 提交者: Peng Lyu

Color Decorator sits together with Color Picker.

上级 f4ccc317
......@@ -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<Range[]>, private supportedLanguages: { [id: string]: boolean }, private isDecoratorEnabled: (languageId: string) => boolean) { }
private onDidChangeColorsEmitter = new EventEmitter<void>();
private decoratorEnablement = {};
constructor(private decoratorProvider: (uri: string) => Thenable<Range[]>, 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<void> {
return this.onDidChangeColorsEmitter.event;
}
async provideDocumentColors(document: TextDocument): Promise<ColorRange[]> {
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;
......
......@@ -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<Range[]>, private supportedLanguages: { [id: string]: boolean }, private isDecoratorEnabled: (languageId: string) => boolean) { }
private onDidChangeColorsEmitter = new EventEmitter<void>();
private decoratorEnablement = {};
constructor(private decoratorProvider: (uri: string) => Thenable<Range[]>, 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<void> {
return this.onDidChangeColorsEmitter.event;
}
async provideDocumentColors(document: TextDocument): Promise<ColorRange[]> {
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;
......
......@@ -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<this>;
/**
* Provides the color ranges for a specific model.
......
......@@ -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);
......
......@@ -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<void>;
private timeoutPromise: TPromise<void>;
private decorationsIds: string[] = [];
private colorRanges = new Map<string, IColorRange>();
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;
}
}
private beginCompute(): void {
if (!this.editor.getModel()) {
if (!ColorProviderRegistry.has(model)) {
return;
}
if (!ColorProviderRegistry.has(this.editor.getModel())) {
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 {
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))
......
......@@ -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
......@@ -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<void>;
/**
* Provide colors for the given document.
......
......@@ -276,10 +276,9 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
// --- colors
$registerDocumentColorProvider(handle: number, selector: vscode.DocumentSelector): TPromise<any> {
$registerDocumentColorProvider(handle: number, selector: vscode.DocumentSelector, eventHandle: number): TPromise<any> {
const proxy = this._proxy;
this._registrations[handle] = modes.ColorProviderRegistry.register(selector, <modes.ColorRangeProvider>{
const provider = <modes.ColorRangeProvider>{
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<modes.ColorRangeProvider>();
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<any> {
const obj = this._registrations[eventHandle];
if (obj instanceof Emitter) {
obj.fire(event);
}
return undefined;
}
$registerColorFormats(formats: IRawColorFormatMap): TPromise<any> {
formats.forEach(f => this._formatters.set(f[0], new ColorFormatter(f[1])));
return TPromise.as(null);
......
......@@ -226,7 +226,8 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable {
$registerSignatureHelpProvider(handle: number, selector: vscode.DocumentSelector, triggerCharacter: string[]): TPromise<any>;
$registerDocumentLinkProvider(handle: number, selector: vscode.DocumentSelector): TPromise<any>;
$registerColorFormats(formats: IRawColorFormatMap): TPromise<any>;
$registerDocumentColorProvider(handle: number, selector: vscode.DocumentSelector): TPromise<any>;
$registerDocumentColorProvider(handle: number, selector: vscode.DocumentSelector, eventHandle: number): TPromise<any>;
$emitColorsEvent(eventHandle: number, event?: any): TPromise<any>;
$setLanguageConfiguration(handle: number, languageId: string, configuration: vscode.LanguageConfiguration): TPromise<any>;
}
......@@ -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][];
......
......@@ -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<IRawColorInfo[]> {
......
......@@ -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;
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册