Skip to content

Commit 9937bcf

Browse files
committed
Color Decorator sits together with Color Picker.
1 parent f4ccc31 commit 9937bcf

11 files changed

Lines changed: 217 additions & 82 deletions

File tree

extensions/css/client/src/colorDecorators.ts

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
'use strict';
66

77
import * as parse from 'parse-color';
8-
import { Range, TextDocument, DocumentColorProvider, Color, ColorRange } from 'vscode';
8+
import { workspace, Range, TextDocument, DocumentColorProvider, Color, ColorRange, Event, EventEmitter } from 'vscode';
99

1010
const CSSColorFormats = {
1111
Hex: '#{red:X}{green:X}{blue:X}',
@@ -20,12 +20,37 @@ const CSSColorFormats = {
2020
};
2121

2222
export class ColorProvider implements DocumentColorProvider {
23-
constructor(private decoratorProvider: (uri: string) => Thenable<Range[]>, private supportedLanguages: { [id: string]: boolean }, private isDecoratorEnabled: (languageId: string) => boolean) { }
23+
private onDidChangeColorsEmitter = new EventEmitter<void>();
24+
private decoratorEnablement = {};
25+
26+
constructor(private decoratorProvider: (uri: string) => Thenable<Range[]>, private supportedLanguages: { [id: string]: boolean }, isDecoratorEnabled: (languageId: string) => boolean) {
27+
for (let languageId in supportedLanguages) {
28+
this.decoratorEnablement[languageId] = isDecoratorEnabled(languageId);
29+
}
30+
31+
workspace.onDidChangeConfiguration(_ => {
32+
let hasChanges = false;
33+
for (let languageId in supportedLanguages) {
34+
let prev = this.decoratorEnablement[languageId];
35+
let curr = isDecoratorEnabled(languageId);
36+
if (prev !== curr) {
37+
this.decoratorEnablement[languageId] = curr;
38+
hasChanges = true;
39+
}
40+
}
41+
if (hasChanges) {
42+
this.onDidChangeColorsEmitter.fire();
43+
}
44+
});
45+
}
46+
47+
public get onDidChangeColors(): Event<void> {
48+
return this.onDidChangeColorsEmitter.event;
49+
}
2450

2551
async provideDocumentColors(document: TextDocument): Promise<ColorRange[]> {
26-
let renderDecorator = false;
27-
if (document && this.isDecoratorEnabled(document.languageId)) {
28-
renderDecorator = true;
52+
if (!this.supportedLanguages[document.languageId] || !this.decoratorEnablement[document.languageId]) {
53+
return [];
2954
}
3055

3156
const ranges = await this.decoratorProvider(document.uri.toString());
@@ -43,7 +68,7 @@ export class ColorProvider implements DocumentColorProvider {
4368
}
4469
}
4570
if (color) {
46-
result.push(new ColorRange(range, color, [CSSColorFormats.Hex, CSSColorFormats.RGB, CSSColorFormats.HSL], renderDecorator));
71+
result.push(new ColorRange(range, color, [CSSColorFormats.Hex, CSSColorFormats.RGB, CSSColorFormats.HSL]));
4772
}
4873
}
4974
return result;

extensions/json/client/src/colorDecorators.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,46 @@
44
*--------------------------------------------------------------------------------------------*/
55
'use strict';
66

7-
import { Range, TextDocument, DocumentColorProvider, Color, ColorRange } from 'vscode';
7+
import { workspace, Range, TextDocument, DocumentColorProvider, Color, ColorRange, Event, EventEmitter } from 'vscode';
88

99
const ColorFormat_HEX = {
1010
opaque: '"#{red:X}{green:X}{blue:X}"',
1111
transparent: '"#{red:X}{green:X}{blue:X}{alpha:X}"'
1212
};
1313

1414
export class ColorProvider implements DocumentColorProvider {
15-
constructor(private decoratorProvider: (uri: string) => Thenable<Range[]>, private supportedLanguages: { [id: string]: boolean }, private isDecoratorEnabled: (languageId: string) => boolean) { }
15+
private onDidChangeColorsEmitter = new EventEmitter<void>();
16+
private decoratorEnablement = {};
17+
18+
constructor(private decoratorProvider: (uri: string) => Thenable<Range[]>, private supportedLanguages: { [id: string]: boolean }, isDecoratorEnabled: (languageId: string) => boolean) {
19+
for (let languageId in supportedLanguages) {
20+
this.decoratorEnablement[languageId] = isDecoratorEnabled(languageId);
21+
}
22+
23+
workspace.onDidChangeConfiguration(_ => {
24+
let hasChanges = false;
25+
for (let languageId in supportedLanguages) {
26+
let prev = this.decoratorEnablement[languageId];
27+
let curr = isDecoratorEnabled(languageId);
28+
if (prev !== curr) {
29+
this.decoratorEnablement[languageId] = curr;
30+
hasChanges = true;
31+
}
32+
}
33+
if (hasChanges) {
34+
this.onDidChangeColorsEmitter.fire();
35+
}
36+
});
37+
}
38+
39+
40+
public get onDidChangeColors(): Event<void> {
41+
return this.onDidChangeColorsEmitter.event;
42+
}
1643

1744
async provideDocumentColors(document: TextDocument): Promise<ColorRange[]> {
18-
let renderDecorator = false;
19-
if (document && this.isDecoratorEnabled(document.languageId)) {
20-
renderDecorator = true;
45+
if (!this.supportedLanguages[document.languageId] || !this.decoratorEnablement[document.languageId]) {
46+
return [];
2147
}
2248

2349
const ranges = await this.decoratorProvider(document.uri.toString());
@@ -26,7 +52,7 @@ export class ColorProvider implements DocumentColorProvider {
2652
let color = parseColorFromRange(document, range);
2753
if (color) {
2854
let r = new Range(range.start.line, range.start.character, range.end.line, range.end.character);
29-
result.push(new ColorRange(r, color, [ColorFormat_HEX], renderDecorator));
55+
result.push(new ColorRange(r, color, [ColorFormat_HEX]));
3056
}
3157
}
3258
return result;

src/vs/editor/common/modes.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -709,18 +709,14 @@ export interface IColorRange {
709709
* The available formats for this specific color.
710710
*/
711711
formatters: IColorFormatter[];
712-
713-
/**
714-
* Controls whether the color decorator is rendered.
715-
*/
716-
renderDecorator: boolean;
717712
}
718713

719714
/**
720715
* A provider of colors for editor models.
721716
* @internal
722717
*/
723718
export interface ColorRangeProvider {
719+
onDidChange?: Event<this>;
724720

725721
/**
726722
* Provides the color ranges for a specific model.

src/vs/editor/contrib/colorPicker/browser/colorController.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55
import { Disposable } from 'vs/base/common/lifecycle';
6+
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
67
import { ICommonCodeEditor, IEditorContribution } from 'vs/editor/common/editorCommon';
78
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
89
import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions';
@@ -28,18 +29,21 @@ export class ColorController extends Disposable implements IEditorContribution {
2829

2930
constructor(
3031
private _editor: ICodeEditor,
31-
@ICodeEditorService private _codeEditorService: ICodeEditorService
32+
@ICodeEditorService private _codeEditorService: ICodeEditorService,
33+
@IConfigurationService private _configurationService: IConfigurationService
3234
) {
3335
super();
3436
this._decorations = [];
3537
this._decorationsTypes = {};
38+
this._register(ColorProviderRegistry.onDidChange((e) => this.triggerUpdateDecorations()));
3639
this._register(_editor.onDidChangeModel((e) => this.triggerUpdateDecorations()));
40+
this._register(_editor.onDidChangeModelLanguage((e) => this.triggerUpdateDecorations()));
3741
this._register(_editor.onDidChangeModelContent((e) => {
3842
setTimeout(() => this.triggerUpdateDecorations(), 0);
3943
}));
40-
this._register(_editor.onDidChangeModelLanguage((e) => this.triggerUpdateDecorations()));
41-
this._register(_editor.onDidChangeConfiguration((e) => this.triggerUpdateDecorations()));
42-
this._register(ColorProviderRegistry.onDidChange((e) => this.triggerUpdateDecorations()));
44+
this._register(_configurationService.onDidUpdateConfiguration((e) => {
45+
setTimeout(() => this.triggerUpdateDecorations(), 0);
46+
}));
4347
this.triggerUpdateDecorations();
4448
}
4549

@@ -49,9 +53,6 @@ export class ColorController extends Disposable implements IEditorContribution {
4953
let newDecorationsTypes: { [key: string]: boolean } = {};
5054

5155
for (let i = 0; i < colorInfos.length && decorations.length < MAX_DECORATORS; i++) {
52-
if (!colorInfos[i].renderDecorator) {
53-
continue;
54-
}
5556
const { red, green, blue, alpha } = colorInfos[i].color;
5657
const rgba = new RGBA(Math.round(red * 255), Math.round(green * 255), Math.round(blue * 255), alpha);
5758
let subKey = hash(rgba).toString(16);

src/vs/editor/contrib/colorPicker/browser/colorDetector.ts

Lines changed: 107 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,20 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { RGBA } from 'vs/base/common/color';
7+
import { hash } from 'vs/base/common/hash';
8+
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
9+
import { TPromise } from 'vs/base/common/winjs.base';
610
import { ICommonCodeEditor, IEditorContribution } from 'vs/editor/common/editorCommon';
711
import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions';
812
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
9-
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
10-
import { ColorProviderRegistry, IColorRange } from 'vs/editor/common/modes';
11-
import { TPromise } from 'vs/base/common/winjs.base';
12-
import { getColors } from 'vs/editor/contrib/colorPicker/common/color';
1313
import { Range } from 'vs/editor/common/core/range';
1414
import { Position } from 'vs/editor/common/core/position';
15+
import { ColorProviderRegistry, IColorRange } from 'vs/editor/common/modes';
16+
import { ICodeEditorService } from 'vs/editor/common/services/codeEditorService';
17+
import { getColors } from 'vs/editor/contrib/colorPicker/common/color';
18+
19+
const MAX_DECORATORS = 500;
1520

1621
@editorContribution
1722
export class ColorDetector implements IEditorContribution {
@@ -20,22 +25,27 @@ export class ColorDetector implements IEditorContribution {
2025

2126
static RECOMPUTE_TIME = 1000; // ms
2227

23-
private listenersToRemove: IDisposable[] = [];
28+
private globalToDispose: IDisposable[] = [];
29+
private localToDispose: IDisposable[] = [];
2430
private computePromise: TPromise<void>;
2531
private timeoutPromise: TPromise<void>;
2632

2733
private decorationsIds: string[] = [];
2834
private colorRanges = new Map<string, IColorRange>();
2935

30-
constructor(private editor: ICodeEditor) {
31-
this.listenersToRemove.push(editor.onDidChangeModelContent((e) => this.onChange()));
32-
this.listenersToRemove.push(editor.onDidChangeModel((e) => this.onModelChanged()));
33-
this.listenersToRemove.push(editor.onDidChangeModelLanguage((e) => this.onModelModeChanged()));
34-
this.listenersToRemove.push(ColorProviderRegistry.onDidChange((e) => this.onModelModeChanged()));
36+
private _colorDecorators: string[] = [];
37+
private _decorationsTypes: { [key: string]: boolean } = {};
38+
39+
constructor(private editor: ICodeEditor,
40+
@ICodeEditorService private _codeEditorService: ICodeEditorService,
41+
) {
42+
this.globalToDispose.push(editor.onDidChangeModel((e) => this.onModelChanged()));
43+
this.globalToDispose.push(editor.onDidChangeModelLanguage((e) => this.onModelChanged()));
44+
this.globalToDispose.push(ColorProviderRegistry.onDidChange((e) => this.onModelChanged()));
3545

3646
this.timeoutPromise = null;
3747
this.computePromise = null;
38-
this.beginCompute();
48+
this.onModelChanged();
3949
}
4050

4151
getId(): string {
@@ -47,41 +57,54 @@ export class ColorDetector implements IEditorContribution {
4757
}
4858

4959
dispose(): void {
50-
this.listenersToRemove = dispose(this.listenersToRemove);
5160
this.stop();
61+
this.globalToDispose = dispose(this.globalToDispose);
5262
}
5363

5464
private onModelChanged(): void {
5565
this.stop();
56-
this.beginCompute();
57-
}
58-
59-
private onModelModeChanged(): void {
60-
this.stop();
61-
this.beginCompute();
62-
}
63-
64-
private onChange(): void {
65-
if (!this.timeoutPromise) {
66-
this.timeoutPromise = TPromise.timeout(ColorDetector.RECOMPUTE_TIME);
67-
this.timeoutPromise.then(() => {
68-
this.timeoutPromise = null;
69-
this.beginCompute();
70-
});
66+
const model = this.editor.getModel();
67+
if (!model) {
68+
return;
7169
}
72-
}
7370

74-
private beginCompute(): void {
75-
if (!this.editor.getModel()) {
71+
if (!ColorProviderRegistry.has(model)) {
7672
return;
7773
}
7874

79-
if (!ColorProviderRegistry.has(this.editor.getModel())) {
80-
return;
75+
for (const provider of ColorProviderRegistry.all(model)) {
76+
if (typeof provider.onDidChange === 'function') {
77+
let registration = provider.onDidChange(() => {
78+
if (this.timeoutPromise) {
79+
this.timeoutPromise.cancel();
80+
this.timeoutPromise = null;
81+
}
82+
if (this.computePromise) {
83+
this.computePromise.cancel();
84+
this.computePromise = null;
85+
}
86+
this.beginCompute();
87+
});
88+
this.localToDispose.push(registration);
89+
}
8190
}
8291

92+
this.localToDispose.push(this.editor.onDidChangeModelContent((e) => {
93+
if (!this.timeoutPromise) {
94+
this.timeoutPromise = TPromise.timeout(ColorDetector.RECOMPUTE_TIME);
95+
this.timeoutPromise.then(() => {
96+
this.timeoutPromise = null;
97+
this.beginCompute();
98+
});
99+
}
100+
}));
101+
this.beginCompute();
102+
}
103+
104+
private beginCompute(): void {
83105
this.computePromise = getColors(this.editor.getModel()).then(colorInfos => {
84106
this.updateDecorations(colorInfos);
107+
this.updateColorDecorators(colorInfos);
85108
this.computePromise = null;
86109
});
87110
}
@@ -95,6 +118,7 @@ export class ColorDetector implements IEditorContribution {
95118
this.computePromise.cancel();
96119
this.computePromise = null;
97120
}
121+
this.localToDispose = dispose(this.localToDispose);
98122
}
99123

100124
private updateDecorations(colorInfos: IColorRange[]): void {
@@ -111,8 +135,7 @@ export class ColorDetector implements IEditorContribution {
111135
const colorRanges = colorInfos.map(c => ({
112136
range: c.range,
113137
color: c.color,
114-
formatters: c.formatters,
115-
renderDecorator: c.renderDecorator
138+
formatters: c.formatters
116139
}));
117140

118141
this.decorationsIds = this.editor.deltaDecorations(this.decorationsIds, decorations);
@@ -121,6 +144,56 @@ export class ColorDetector implements IEditorContribution {
121144
this.decorationsIds.forEach((id, i) => this.colorRanges.set(id, colorRanges[i]));
122145
}
123146

147+
private updateColorDecorators(colorInfos: IColorRange[]): void {
148+
let decorations = [];
149+
let newDecorationsTypes: { [key: string]: boolean } = {};
150+
151+
for (let i = 0; i < colorInfos.length && decorations.length < MAX_DECORATORS; i++) {
152+
const { red, green, blue, alpha } = colorInfos[i].color;
153+
const rgba = new RGBA(Math.round(red * 255), Math.round(green * 255), Math.round(blue * 255), alpha);
154+
let subKey = hash(rgba).toString(16);
155+
let color = `rgba(${rgba.r}, ${rgba.g}, ${rgba.b}, ${rgba.a})`;
156+
let key = 'colorBox-' + subKey;
157+
158+
if (!this._decorationsTypes[key] && !newDecorationsTypes[key]) {
159+
this._codeEditorService.registerDecorationType(key, {
160+
before: {
161+
contentText: ' ',
162+
border: 'solid 0.1em #000',
163+
margin: '0.1em 0.2em 0 0.2em',
164+
width: '0.8em',
165+
height: '0.8em',
166+
backgroundColor: color
167+
},
168+
dark: {
169+
before: {
170+
border: 'solid 0.1em #eee'
171+
}
172+
}
173+
});
174+
}
175+
176+
newDecorationsTypes[key] = true;
177+
decorations.push({
178+
range: {
179+
startLineNumber: colorInfos[i].range.startLineNumber,
180+
startColumn: colorInfos[i].range.startColumn,
181+
endLineNumber: colorInfos[i].range.endLineNumber,
182+
endColumn: colorInfos[i].range.endColumn
183+
},
184+
options: this._codeEditorService.resolveDecorationOptions(key, true)
185+
});
186+
}
187+
188+
for (let subType in this._decorationsTypes) {
189+
if (!newDecorationsTypes[subType]) {
190+
this._codeEditorService.removeDecorationType(subType);
191+
}
192+
}
193+
194+
this._colorDecorators = this.editor.deltaDecorations(this._colorDecorators, decorations);
195+
}
196+
124197
getColorRange(position: Position): IColorRange | null {
125198
const decorations = this.editor.getModel()
126199
.getDecorationsInRange(Range.fromPositions(position, position))

0 commit comments

Comments
 (0)