From 3479d81d77b15527021e31e4e12df11e83aa38a1 Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 8 Sep 2017 01:15:52 -0700 Subject: [PATCH 1/3] Fix #32235. Functional style formatter. Color range is all float number and remove fromHex, fromHSL methods. --- extensions/css/client/src/cssMain.ts | 45 +++-- .../css/client/src/typings/color-convert.d.ts | 11 ++ extensions/css/npm-shrinkwrap.json | 5 + extensions/css/package.json | 1 + extensions/html/client/src/htmlMain.ts | 45 +++-- .../client/src/typings/color-convert.d.ts | 11 ++ extensions/html/npm-shrinkwrap.json | 5 + extensions/html/package.json | 1 + extensions/json/client/src/jsonMain.ts | 20 ++- src/vs/editor/common/modes.ts | 22 ++- .../colorPicker/browser/colorDetector.ts | 46 +++--- .../colorPicker/browser/colorPickerModel.ts | 47 ++---- .../colorPicker/browser/colorPickerWidget.ts | 7 +- .../contrib/colorPicker/common/color.ts | 26 ++- .../colorPicker/common/colorFormatter.ts | 154 ++++-------------- .../test/common/colorFormatter.test.ts | 69 +------- .../hover/browser/modesContentHover.ts | 37 ++--- .../standalone/browser/standaloneLanguages.ts | 1 + src/vs/monaco.d.ts | 17 +- src/vs/vscode.proposed.d.ts | 78 ++------- .../mainThreadLanguageFeatures.ts | 22 +-- src/vs/workbench/api/node/extHost.api.impl.ts | 1 + src/vs/workbench/api/node/extHost.protocol.ts | 8 +- .../api/node/extHostLanguageFeatures.ts | 35 +--- src/vs/workbench/api/node/extHostTypes.ts | 29 +--- 25 files changed, 277 insertions(+), 466 deletions(-) create mode 100644 extensions/css/client/src/typings/color-convert.d.ts create mode 100644 extensions/html/client/src/typings/color-convert.d.ts diff --git a/extensions/css/client/src/cssMain.ts b/extensions/css/client/src/cssMain.ts index 677ba2cd541f9..95f0da71a2df5 100644 --- a/extensions/css/client/src/cssMain.ts +++ b/extensions/css/client/src/cssMain.ts @@ -6,27 +6,16 @@ import * as path from 'path'; -import { languages, window, commands, ExtensionContext, TextDocument, ColorRange, Color } from 'vscode'; +import { languages, window, commands, ExtensionContext, TextDocument, ColorRange, ColorFormat, Color } from 'vscode'; import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind, TextEdit } from 'vscode-languageclient'; import { ConfigurationFeature } from 'vscode-languageclient/lib/proposed'; import { DocumentColorRequest } from 'vscode-languageserver-protocol/lib/protocol.colorProvider.proposed'; import * as nls from 'vscode-nls'; +import * as convert from 'color-convert'; let localize = nls.loadMessageBundle(); -const CSSColorFormats = { - Hex: '#{red:X}{green:X}{blue:X}', - RGB: { - opaque: 'rgb({red:d[0-255]}, {green:d[0-255]}, {blue:d[0-255]})', - transparent: 'rgba({red:d[0-255]}, {green:d[0-255]}, {blue:d[0-255]}, {alpha})' - }, - HSL: { - opaque: 'hsl({hue:d[0-360]}, {saturation:d[0-100]}%, {luminance:d[0-100]}%)', - transparent: 'hsla({hue:d[0-360]}, {saturation:d[0-100]}%, {luminance:d[0-100]}%, {alpha})' - } -}; - // this method is called when vs code is activated export function activate(context: ExtensionContext) { @@ -63,6 +52,11 @@ export function activate(context: ExtensionContext) { // client can be deactivated on extension deactivation context.subscriptions.push(disposable); + var _toTwoDigitHex = function (n: number): string { + const r = n.toString(16); + return r.length !== 2 ? '0' + r : r; + }; + client.onReady().then(_ => { // register color provider context.subscriptions.push(languages.registerColorProvider(documentSelector, { @@ -72,9 +66,32 @@ export function activate(context: ExtensionContext) { return symbols.map(symbol => { let range = client.protocol2CodeConverter.asRange(symbol.range); let color = new Color(symbol.color.red * 255, symbol.color.green * 255, symbol.color.blue * 255, symbol.color.alpha); - return new ColorRange(range, color, [CSSColorFormats.Hex, CSSColorFormats.RGB, CSSColorFormats.HSL]); + return new ColorRange(range, color); }); }); + }, + resolveColor(color: Color, colorFormat: ColorFormat): Thenable | string { + switch (colorFormat) { + case ColorFormat.RGB: + if (color.alpha === 1) { + return `rgb(${Math.round(color.red * 255)}, ${Math.round(color.green * 255)}, ${Math.round(color.blue * 255)})`; + } else { + return `rgb(${Math.round(color.red * 255)}, ${Math.round(color.green * 255)}, ${Math.round(color.blue * 255)}, ${color.alpha})`; + } + case ColorFormat.HEX: + if (color.alpha === 1) { + return `#${_toTwoDigitHex(Math.round(color.red * 255))}${_toTwoDigitHex(Math.round(color.green * 255))}${_toTwoDigitHex(Math.round(color.blue * 255))}`; + } else { + return `#${_toTwoDigitHex(Math.round(color.red * 255))}${_toTwoDigitHex(Math.round(color.green * 255))}${_toTwoDigitHex(Math.round(color.blue * 255))}${_toTwoDigitHex(Math.round(color.alpha * 255))}`; + } + case ColorFormat.HSL: + const hsl = convert.rgb.hsl(Math.round(color.red * 255), Math.round(color.green * 255), Math.round(color.blue * 255)); + if (color.alpha === 1) { + return `hsl(${hsl[0]}, ${hsl[1]}%, ${hsl[2]}%)`; + } else { + return `hsla(${hsl[0]}, ${hsl[1]}%, ${hsl[2]}%, ${color.alpha})`; + } + } } })); }); diff --git a/extensions/css/client/src/typings/color-convert.d.ts b/extensions/css/client/src/typings/color-convert.d.ts new file mode 100644 index 0000000000000..a88de674d2dbc --- /dev/null +++ b/extensions/css/client/src/typings/color-convert.d.ts @@ -0,0 +1,11 @@ +declare module "color-convert" { + module convert { + module rgb { + function hex(r: number, g: number, b: number); + function hsl(r: number, g: number, b: number); + function hvs(r: number, g: number, b: number); + } + } + + export = convert; +} \ No newline at end of file diff --git a/extensions/css/npm-shrinkwrap.json b/extensions/css/npm-shrinkwrap.json index d645e03b53fc2..6ae15a5c6707f 100644 --- a/extensions/css/npm-shrinkwrap.json +++ b/extensions/css/npm-shrinkwrap.json @@ -2,6 +2,11 @@ "name": "css", "version": "0.1.0", "dependencies": { + "color-convert": { + "version": "0.5.3", + "from": "color-convert@>=0.5.0 <0.6.0", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz" + }, "vscode-jsonrpc": { "version": "3.3.1", "from": "vscode-jsonrpc@>=3.3.0 <4.0.0", diff --git a/extensions/css/package.json b/extensions/css/package.json index 686dd900e0be0..6ace6ad649a4f 100644 --- a/extensions/css/package.json +++ b/extensions/css/package.json @@ -737,6 +737,7 @@ } }, "dependencies": { + "color-convert": "^0.5.3", "vscode-languageclient": "3.4.0-next.17", "vscode-languageserver-protocol": "^3.1.1", "vscode-nls": "^2.0.2" diff --git a/extensions/html/client/src/htmlMain.ts b/extensions/html/client/src/htmlMain.ts index 44138ce758aec..defc1982ce012 100644 --- a/extensions/html/client/src/htmlMain.ts +++ b/extensions/html/client/src/htmlMain.ts @@ -6,11 +6,12 @@ import * as path from 'path'; -import { languages, ExtensionContext, IndentAction, Position, TextDocument, Color, ColorRange } from 'vscode'; +import { languages, ExtensionContext, IndentAction, Position, TextDocument, Color, ColorRange, ColorFormat } from 'vscode'; import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind, RequestType, TextDocumentPositionParams } from 'vscode-languageclient'; import { EMPTY_ELEMENTS } from './htmlEmptyTagsShared'; import { activateTagClosing } from './tagClosing'; import TelemetryReporter from 'vscode-extension-telemetry'; +import * as convert from 'color-convert'; import { ConfigurationFeature } from 'vscode-languageclient/lib/proposed'; import { DocumentColorRequest } from 'vscode-languageserver-protocol/lib/protocol.colorProvider.proposed'; @@ -28,18 +29,6 @@ interface IPackageInfo { aiKey: string; } -const CSSColorFormats = { - Hex: '#{red:X}{green:X}{blue:X}', - RGB: { - opaque: 'rgb({red:d[0-255]}, {green:d[0-255]}, {blue:d[0-255]})', - transparent: 'rgba({red:d[0-255]}, {green:d[0-255]}, {blue:d[0-255]}, {alpha})' - }, - HSL: { - opaque: 'hsl({hue:d[0-360]}, {saturation:d[0-100]}%, {luminance:d[0-100]}%)', - transparent: 'hsla({hue:d[0-360]}, {saturation:d[0-100]}%, {luminance:d[0-100]}%, {alpha})' - } -}; - export function activate(context: ExtensionContext) { let toDispose = context.subscriptions; @@ -79,6 +68,11 @@ export function activate(context: ExtensionContext) { let client = new LanguageClient('html', localize('htmlserver.name', 'HTML Language Server'), serverOptions, clientOptions); client.registerFeature(new ConfigurationFeature(client)); + var _toTwoDigitHex = function (n: number): string { + const r = n.toString(16); + return r.length !== 2 ? '0' + r : r; + }; + let disposable = client.start(); toDispose.push(disposable); client.onReady().then(() => { @@ -89,9 +83,32 @@ export function activate(context: ExtensionContext) { return symbols.map(symbol => { let range = client.protocol2CodeConverter.asRange(symbol.range); let color = new Color(symbol.color.red * 255, symbol.color.green * 255, symbol.color.blue * 255, symbol.color.alpha); - return new ColorRange(range, color, [CSSColorFormats.Hex, CSSColorFormats.RGB, CSSColorFormats.HSL]); + return new ColorRange(range, color); }); }); + }, + resolveColor(color: Color, colorFormat: ColorFormat): Thenable | string { + switch (colorFormat) { + case ColorFormat.RGB: + if (color.alpha === 1) { + return `rgb(${Math.round(color.red * 255)}, ${Math.round(color.green * 255)}, ${Math.round(color.blue * 255)})`; + } else { + return `rgb(${Math.round(color.red * 255)}, ${Math.round(color.green * 255)}, ${Math.round(color.blue * 255)}, ${color.alpha})`; + } + case ColorFormat.HEX: + if (color.alpha === 1) { + return `#${_toTwoDigitHex(Math.round(color.red * 255))}${_toTwoDigitHex(Math.round(color.green * 255))}${_toTwoDigitHex(Math.round(color.blue * 255))}`; + } else { + return `#${_toTwoDigitHex(Math.round(color.red * 255))}${_toTwoDigitHex(Math.round(color.green * 255))}${_toTwoDigitHex(Math.round(color.blue * 255))}${_toTwoDigitHex(Math.round(color.alpha * 255))}`; + } + case ColorFormat.HSL: + const hsl = convert.rgb.hsl(Math.round(color.red * 255), Math.round(color.green * 255), Math.round(color.blue * 255)); + if (color.alpha === 1) { + return `hsl(${hsl[0]}, ${hsl[1]}%, ${hsl[2]}%)`; + } else { + return `hsla(${hsl[0]}, ${hsl[1]}%, ${hsl[2]}%, ${color.alpha})`; + } + } } }); toDispose.push(disposable); diff --git a/extensions/html/client/src/typings/color-convert.d.ts b/extensions/html/client/src/typings/color-convert.d.ts new file mode 100644 index 0000000000000..a88de674d2dbc --- /dev/null +++ b/extensions/html/client/src/typings/color-convert.d.ts @@ -0,0 +1,11 @@ +declare module "color-convert" { + module convert { + module rgb { + function hex(r: number, g: number, b: number); + function hsl(r: number, g: number, b: number); + function hvs(r: number, g: number, b: number); + } + } + + export = convert; +} \ No newline at end of file diff --git a/extensions/html/npm-shrinkwrap.json b/extensions/html/npm-shrinkwrap.json index f8f32b0eef75c..4f5dd2f368608 100644 --- a/extensions/html/npm-shrinkwrap.json +++ b/extensions/html/npm-shrinkwrap.json @@ -2,6 +2,11 @@ "name": "html", "version": "0.1.0", "dependencies": { + "color-convert": { + "version": "0.5.3", + "from": "color-convert@>=0.5.0 <0.6.0", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz" + }, "applicationinsights": { "version": "0.18.0", "from": "applicationinsights@0.18.0", diff --git a/extensions/html/package.json b/extensions/html/package.json index 3f4871ce158ea..a5ebee09dd3e1 100644 --- a/extensions/html/package.json +++ b/extensions/html/package.json @@ -215,6 +215,7 @@ } }, "dependencies": { + "color-convert": "^0.5.3", "vscode-extension-telemetry": "0.0.8", "vscode-languageclient": "3.4.0-next.17", "vscode-languageserver-protocol": "^3.1.1", diff --git a/extensions/json/client/src/jsonMain.ts b/extensions/json/client/src/jsonMain.ts index afbf9c71d6433..ed6ffe7e55cb8 100644 --- a/extensions/json/client/src/jsonMain.ts +++ b/extensions/json/client/src/jsonMain.ts @@ -6,7 +6,7 @@ import * as path from 'path'; -import { workspace, languages, ExtensionContext, extensions, Uri, TextDocument, ColorRange, Color } from 'vscode'; +import { workspace, languages, ExtensionContext, extensions, Uri, TextDocument, ColorRange, Color, ColorFormat } from 'vscode'; import { LanguageClient, LanguageClientOptions, RequestType, ServerOptions, TransportKind, NotificationType, DidChangeConfigurationNotification } from 'vscode-languageclient'; import TelemetryReporter from 'vscode-extension-telemetry'; import { ConfigurationFeature } from 'vscode-languageclient/lib/proposed'; @@ -56,11 +56,6 @@ interface JSONSchemaSettings { schema?: any; } -const ColorFormat_HEX = { - opaque: '"#{red:X}{green:X}{blue:X}"', - transparent: '"#{red:X}{green:X}{blue:X}{alpha:X}"' -}; - export function activate(context: ExtensionContext) { let packageInfo = getPackageInfo(context); @@ -121,6 +116,10 @@ export function activate(context: ExtensionContext) { client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociation(context)); + var _toTwoDigitHex = function (n: number): string { + const r = n.toString(16); + return r.length !== 2 ? '0' + r : r; + }; // register color provider context.subscriptions.push(languages.registerColorProvider(documentSelector, { provideDocumentColors(document: TextDocument): Thenable { @@ -129,9 +128,16 @@ export function activate(context: ExtensionContext) { return symbols.map(symbol => { let range = client.protocol2CodeConverter.asRange(symbol.range); let color = new Color(symbol.color.red * 255, symbol.color.green * 255, symbol.color.blue * 255, symbol.color.alpha); - return new ColorRange(range, color, [ColorFormat_HEX]); + return new ColorRange(range, color); }); }); + }, + resolveColor(color: Color, colorFormat: ColorFormat): Thenable | string { + if (color.alpha === 1) { + return `#${_toTwoDigitHex(Math.round(color.red * 255))}${_toTwoDigitHex(Math.round(color.green * 255))}${_toTwoDigitHex(Math.round(color.blue * 255))}`; + } else { + return `#${_toTwoDigitHex(Math.round(color.red * 255))}${_toTwoDigitHex(Math.round(color.green * 255))}${_toTwoDigitHex(Math.round(color.blue * 255))}${_toTwoDigitHex(Math.round(color.alpha * 255))}`; + } } })); }); diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 4796243507d0a..25ba36a4099c8 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -679,12 +679,23 @@ export interface IColor { readonly alpha: number; } +/** + * Represents a color format + */ +export enum ColorFormat { + RGB = 0, + HEX = 1, + HSL = 2 +} + /** * A color formatter. + * @internal */ export interface IColorFormatter { readonly supportsTransparency: boolean; - format(color: IColor): string; + readonly colorFormat: ColorFormat; + format(color: Color): string; } /** @@ -701,11 +712,6 @@ export interface IColorRange { * The color represented in this range. */ color: IColor; - - /** - * The available formats for this specific color. - */ - formatters: IColorFormatter[]; } /** @@ -716,6 +722,10 @@ export interface DocumentColorProvider { * Provides the color ranges for a specific model. */ provideColorRanges(model: editorCommon.IReadOnlyModel, token: CancellationToken): IColorRange[] | Thenable; + /** + * Provide the string representation for a color. + */ + resolveColor(color: IColor, colorFormat: ColorFormat, token: CancellationToken): string | Thenable; } export interface IResourceEdit { diff --git a/src/vs/editor/contrib/colorPicker/browser/colorDetector.ts b/src/vs/editor/contrib/colorPicker/browser/colorDetector.ts index 95551219ade20..2a9e69e2ec7f7 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorDetector.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorDetector.ts @@ -12,9 +12,9 @@ import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; 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 { ColorProviderRegistry } from 'vs/editor/common/modes'; import { ICodeEditorService } from 'vs/editor/common/services/codeEditorService'; -import { getColors } from 'vs/editor/contrib/colorPicker/common/color'; +import { getColors, IColorData } from 'vs/editor/contrib/colorPicker/common/color'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; const MAX_DECORATORS = 500; @@ -32,7 +32,7 @@ export class ColorDetector implements IEditorContribution { private _timeoutPromise: TPromise; private _decorationsIds: string[] = []; - private _colorRanges = new Map(); + private _colorDatas = new Map(); private _colorDecoratorIds: string[] = []; private _decorationsTypes: { [key: string]: boolean } = {}; @@ -146,35 +146,29 @@ export class ColorDetector implements IEditorContribution { this._localToDispose = dispose(this._localToDispose); } - private updateDecorations(colorInfos: IColorRange[]): void { - const decorations = colorInfos.map(c => ({ + private updateDecorations(colorDatas: IColorData[]): void { + const decorations = colorDatas.map(c => ({ range: { - startLineNumber: c.range.startLineNumber, - startColumn: c.range.startColumn, - endLineNumber: c.range.endLineNumber, - endColumn: c.range.endColumn + startLineNumber: c.colorRange.range.startLineNumber, + startColumn: c.colorRange.range.startColumn, + endLineNumber: c.colorRange.range.endLineNumber, + endColumn: c.colorRange.range.endColumn }, options: {} })); - const colorRanges = colorInfos.map(c => ({ - range: c.range, - color: c.color, - formatters: c.formatters - })); - this._decorationsIds = this._editor.deltaDecorations(this._decorationsIds, decorations); - this._colorRanges = new Map(); - this._decorationsIds.forEach((id, i) => this._colorRanges.set(id, colorRanges[i])); + this._colorDatas = new Map(); + this._decorationsIds.forEach((id, i) => this._colorDatas.set(id, colorDatas[i])); } - private updateColorDecorators(colorInfos: IColorRange[]): void { + private updateColorDecorators(colorInfos: IColorData[]): 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 { red, green, blue, alpha } = colorInfos[i].colorRange.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})`; @@ -201,10 +195,10 @@ export class ColorDetector implements IEditorContribution { 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 + startLineNumber: colorInfos[i].colorRange.range.startLineNumber, + startColumn: colorInfos[i].colorRange.range.startColumn, + endLineNumber: colorInfos[i].colorRange.range.endLineNumber, + endColumn: colorInfos[i].colorRange.range.endColumn }, options: this._codeEditorService.resolveDecorationOptions(key, true) }); @@ -228,15 +222,15 @@ export class ColorDetector implements IEditorContribution { } } - getColorRange(position: Position): IColorRange | null { + getColorData(position: Position): IColorData | null { const decorations = this._editor.getModel() .getDecorationsInRange(Range.fromPositions(position, position)) - .filter(d => this._colorRanges.has(d.id)); + .filter(d => this._colorDatas.has(d.id)); if (decorations.length === 0) { return null; } - return this._colorRanges.get(decorations[0].id); + return this._colorDatas.get(decorations[0].id); } } diff --git a/src/vs/editor/contrib/colorPicker/browser/colorPickerModel.ts b/src/vs/editor/contrib/colorPicker/browser/colorPickerModel.ts index af4d256bbd3fc..2a41cf6c0a872 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorPickerModel.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorPickerModel.ts @@ -6,10 +6,7 @@ import Event, { Emitter } from 'vs/base/common/event'; import { Color } from 'vs/base/common/color'; import { IColorFormatter } from 'vs/editor/common/modes'; - -function canFormat(formatter: IColorFormatter, color: Color): boolean { - return color.isOpaque() || formatter.supportsTransparency; -} +import { HexFormatter, HSLFormatter, RGBFormatter } from '../common/colorFormatter'; export class ColorPickerModel { @@ -26,7 +23,6 @@ export class ColorPickerModel { } this._color = color; - this._checkFormat(); this._onDidChangeColor.fire(color); } @@ -43,46 +39,23 @@ export class ColorPickerModel { private _onDidChangeFormatter = new Emitter(); readonly onDidChangeFormatter: Event = this._onDidChangeFormatter.event; - constructor(color: Color, availableFormatters: IColorFormatter[], private formatterIndex: number) { - if (availableFormatters.length === 0) { - throw new Error('Color picker needs formats'); - } - - if (formatterIndex < 0 || formatterIndex >= availableFormatters.length) { - throw new Error('Formatter index out of bounds'); - } - + constructor(color: Color, private formatterIndex: number) { this.originalColor = color; - this.formatters = availableFormatters; this._color = color; + this.formatters = [ + new RGBFormatter(), + new HexFormatter(), + new HSLFormatter() + ]; } selectNextColorFormat(): void { - const oldFomatterIndex = this.formatterIndex; - this._checkFormat((this.formatterIndex + 1) % this.formatters.length); - if (oldFomatterIndex !== this.formatterIndex) { - this.flushColor(); - } + this.formatterIndex = (this.formatterIndex + 1) % this.formatters.length; + this.flushColor(); + this._onDidChangeFormatter.fire(this.formatter); } flushColor(): void { this._onColorFlushed.fire(this._color); } - - private _checkFormat(start = this.formatterIndex): void { - let isNewFormat = this.formatterIndex !== start; - this.formatterIndex = start; - - while (!canFormat(this.formatter, this._color)) { - this.formatterIndex = (this.formatterIndex + 1) % this.formatters.length; - - if (this.formatterIndex === start) { - return; - } - } - - if (isNewFormat) { - this._onDidChangeFormatter.fire(this.formatter); - } - } } diff --git a/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts b/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts index 9e416c5cac35f..51801ba8175c9 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorPickerWidget.ts @@ -55,12 +55,7 @@ export class ColorPickerHeader extends Disposable { } private onDidChangeFormatter(): void { - this.pickedColorNode.textContent = this.model.formatter.format({ - red: this.model.color.rgba.r / 255, - green: this.model.color.rgba.g / 255, - blue: this.model.color.rgba.b / 255, - alpha: this.model.color.rgba.a - }); + this.pickedColorNode.textContent = this.model.formatter.format(this.model.color); } } diff --git a/src/vs/editor/contrib/colorPicker/common/color.ts b/src/vs/editor/contrib/colorPicker/common/color.ts index 666c21bacf9d7..836ab29ebc7dc 100644 --- a/src/vs/editor/contrib/colorPicker/common/color.ts +++ b/src/vs/editor/contrib/colorPicker/common/color.ts @@ -4,15 +4,29 @@ *--------------------------------------------------------------------------------------------*/ import { TPromise } from 'vs/base/common/winjs.base'; -import { ColorProviderRegistry, IColorRange } from 'vs/editor/common/modes'; +import { ColorProviderRegistry, DocumentColorProvider, IColorRange, IColor, ColorFormat } from 'vs/editor/common/modes'; import { asWinJsPromise } from 'vs/base/common/async'; import { IReadOnlyModel } from 'vs/editor/common/editorCommon'; -import { flatten } from 'vs/base/common/arrays'; -export function getColors(model: IReadOnlyModel): TPromise { +export interface IColorData { + colorRange: IColorRange; + provider: DocumentColorProvider; +} + +export function getColors(model: IReadOnlyModel): TPromise { + const colors: IColorData[] = []; const providers = ColorProviderRegistry.ordered(model).reverse(); - const promises = providers.map(p => asWinJsPromise(token => p.provideColorRanges(model, token))); + const promises = providers.map(provider => asWinJsPromise(token => provider.provideColorRanges(model, token)).then(result => { + if (Array.isArray(result)) { + for (let colorRange of result) { + colors.push({ colorRange, provider }); + } + } + })); + + return TPromise.join(promises).then(() => colors); +} - return TPromise.join(promises) - .then(ranges => flatten(ranges.filter(r => Array.isArray(r)))); +export function resolveColor(color: IColor, colorFormat: ColorFormat, provider: DocumentColorProvider): TPromise { + return asWinJsPromise(token => provider.resolveColor(color, colorFormat, token)); } diff --git a/src/vs/editor/contrib/colorPicker/common/colorFormatter.ts b/src/vs/editor/contrib/colorPicker/common/colorFormatter.ts index b65a85c540370..4dfd4f78e0fca 100644 --- a/src/vs/editor/contrib/colorPicker/common/colorFormatter.ts +++ b/src/vs/editor/contrib/colorPicker/common/colorFormatter.ts @@ -3,142 +3,56 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IColorFormatter, IColor } from 'vs/editor/common/modes'; -import { Color, RGBA } from 'vs/base/common/color'; - -function roundFloat(number: number, decimalPoints: number): number { - const decimal = Math.pow(10, decimalPoints); - return Math.round(number * decimal) / decimal; -} - -interface Node { - (color: Color): string; -} - -function createLiteralNode(value: string): Node { - return () => value; -} +import { IColorFormatter, ColorFormat } from 'vs/editor/common/modes'; +import { Color } from 'vs/base/common/color'; function normalize(value: number, min: number, max: number): number { return value * (max - min) + min; } -function getPropertyValue(color: Color, variable: string): number | undefined { - switch (variable) { - case 'red': - return color.rgba.r / 255; - case 'green': - return color.rgba.g / 255; - case 'blue': - return color.rgba.b / 255; - case 'alpha': - return color.rgba.a; - case 'hue': - return color.hsla.h / 360; - case 'saturation': - return color.hsla.s; - case 'luminance': - return color.hsla.l; - default: - return undefined; - } -} - -function createPropertyNode(variable: string, fractionDigits: number, type: string, min: number | undefined, max: number | undefined): Node { - return color => { - let value = getPropertyValue(color, variable); - - if (value === undefined) { - return ''; - } - - if (type === 'd') { - min = typeof min === 'number' ? min : 0; - max = typeof max === 'number' ? max : 255; - - return (normalize(value, min, max).toFixed(0)).toString(); - } else if (type === 'x' || type === 'X') { - min = typeof min === 'number' ? min : 0; - max = typeof max === 'number' ? max : 255; - - let result = normalize(value, min, max).toString(16); - - if (type === 'X') { - result = result.toUpperCase(); - } - - return result.length < 2 ? `0${result}` : result; +export class RGBFormatter implements IColorFormatter { + readonly supportsTransparency: boolean = true; + readonly colorFormat: ColorFormat = ColorFormat.RGB; + + format(color: Color): string { + const rgb = color.rgba; + if (rgb.a === 1) { + return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`; + } else { + return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b}, ${rgb.a})`; } - - min = typeof min === 'number' ? min : 0; - max = typeof max === 'number' ? max : 1; - return roundFloat(normalize(value, min, max), 2).toString(); - }; + } } -export class ColorFormatter implements IColorFormatter { - +export class HexFormatter implements IColorFormatter { readonly supportsTransparency: boolean = false; - private tree: Node[] = []; - - // Group 0: variable - // Group 1: decimal digits - // Group 2: floating/integer/hex - // Group 3: range begin - // Group 4: range end - private static PATTERN = /{(\w+)(?::(\d*)(\w)+(?:\[(\d+)-(\d+)\])?)?}/g; - - constructor(format: string) { - let match = ColorFormatter.PATTERN.exec(format); - let startIndex = 0; - - // if no match -> erroor throw new Error(`${format} is not consistent with color format syntax.`); - while (match !== null) { - const index = match.index; - - if (startIndex < index) { - this.tree.push(createLiteralNode(format.substring(startIndex, index))); - } - - // add more parser catches - const variable = match[1]; - if (!variable) { - throw new Error(`${variable} is not defined.`); - } - - this.supportsTransparency = this.supportsTransparency || (variable === 'alpha'); - - const decimals = match[2] && parseInt(match[2]); - const type = match[3]; - const startRange = match[4] && parseInt(match[4]); - const endRange = match[5] && parseInt(match[5]); + readonly colorFormat: ColorFormat = ColorFormat.HEX; - this.tree.push(createPropertyNode(variable, decimals, type, startRange, endRange)); - - startIndex = index + match[0].length; - match = ColorFormatter.PATTERN.exec(format); - } - - this.tree.push(createLiteralNode(format.substring(startIndex, format.length))); + _toTwoDigitHex(n: number): string { + const r = n.toString(16); + return r.length !== 2 ? '0' + r : r; } - format(color: IColor): string { - const richColor = new Color(new RGBA(Math.round(color.red * 255), Math.round(color.green * 255), Math.round(color.blue * 255), color.alpha)); - return this.tree.map(node => node(richColor)).join(''); + format(color: Color): string { + const rgb = color.rgba; + if (rgb.a === 1) { + return `#${this._toTwoDigitHex(rgb.r).toUpperCase()}${this._toTwoDigitHex(rgb.g).toUpperCase()}${this._toTwoDigitHex(rgb.b).toUpperCase()}`; + } else { + return `#${this._toTwoDigitHex(rgb.r).toUpperCase()}${this._toTwoDigitHex(rgb.g).toUpperCase()}${this._toTwoDigitHex(rgb.b).toUpperCase()}${this._toTwoDigitHex(Math.round(rgb.a * 255)).toUpperCase()}`; + } } } -export class CombinedColorFormatter implements IColorFormatter { - +export class HSLFormatter implements IColorFormatter { readonly supportsTransparency: boolean = true; - - constructor(private opaqueFormatter: IColorFormatter, private transparentFormatter: IColorFormatter) { - if (!transparentFormatter.supportsTransparency) { - throw new Error('Invalid transparent formatter'); + readonly colorFormat: ColorFormat = ColorFormat.HSL; + + format(color: Color): string { + const hsla = color.hsla; + if (hsla.a === 1) { + return `hsl(${hsla.h}, ${normalize(hsla.s, 0, 100).toFixed(0)}%, ${normalize(hsla.l, 0, 100).toFixed(0)}%)`; + } else { + return `hsla(${hsla.h}, ${normalize(hsla.s, 0, 100).toFixed(0)}%, ${normalize(hsla.l, 0, 100).toFixed(0)}%, ${hsla.a})`; } } - - format(color: IColor): string { - return color.alpha === 1 ? this.opaqueFormatter.format(color) : this.transparentFormatter.format(color); - } } \ No newline at end of file diff --git a/src/vs/editor/contrib/colorPicker/test/common/colorFormatter.test.ts b/src/vs/editor/contrib/colorPicker/test/common/colorFormatter.test.ts index 29d7c964393a7..11e0ce417f825 100644 --- a/src/vs/editor/contrib/colorPicker/test/common/colorFormatter.test.ts +++ b/src/vs/editor/contrib/colorPicker/test/common/colorFormatter.test.ts @@ -6,78 +6,27 @@ import * as assert from 'assert'; import { Color, RGBA, HSLA } from 'vs/base/common/color'; -import { IColor } from 'vs/editor/common/modes'; -import { ColorFormatter } from 'vs/editor/contrib/colorPicker/common/colorFormatter'; +import { RGBFormatter, HexFormatter, HSLFormatter } from 'vs/editor/contrib/colorPicker/common/colorFormatter'; -function convert2IColor(color: Color): IColor { - return { - red: color.rgba.r / 255, - green: color.rgba.g / 255, - blue: color.rgba.b / 255, - alpha: color.rgba.a - }; -} suite('ColorFormatter', () => { - test('empty formatter', () => { - const formatter = new ColorFormatter(''); - assert.equal(formatter.supportsTransparency, false); - - assert.equal(formatter.format(convert2IColor(Color.white)), ''); - assert.equal(formatter.format(convert2IColor(Color.transparent)), ''); - }); - - test('no placeholder', () => { - const formatter = new ColorFormatter('hello'); - assert.equal(formatter.supportsTransparency, false); - - assert.equal(formatter.format(convert2IColor(Color.white)), 'hello'); - assert.equal(formatter.format(convert2IColor(Color.transparent)), 'hello'); - }); - - test('supportsTransparency', () => { - const formatter = new ColorFormatter('hello'); - assert.equal(formatter.supportsTransparency, false); - - const transparentFormatter = new ColorFormatter('{alpha}'); - assert.equal(transparentFormatter.supportsTransparency, true); - }); - - test('default number format is float', () => { - const formatter = new ColorFormatter('{red}'); - assert.equal(formatter.format(convert2IColor(Color.red)), '1'); - }); - - test('default decimal range is [0-255]', () => { - const formatter = new ColorFormatter('{red:d}'); - assert.equal(formatter.format(convert2IColor(Color.red)), '255'); - }); - - test('default hex range is [0-FF]', () => { - const formatter = new ColorFormatter('{red:X}'); - assert.equal(formatter.format(convert2IColor(Color.red)), 'FF'); - }); - test('documentation', () => { const color = new Color(new RGBA(255, 127, 0)); - const rgb = new ColorFormatter('rgb({red:d[0-255]}, {green:d[0-255]}, {blue:d[0-255]})'); - assert.equal(rgb.format(convert2IColor(color)), 'rgb(255, 127, 0)'); - - const rgba = new ColorFormatter('rgba({red:d[0-255]}, {green:d[0-255]}, {blue:d[0-255]}, {alpha})'); - assert.equal(rgba.format(convert2IColor(color)), 'rgba(255, 127, 0, 1)'); + const rgb = new RGBFormatter(); + assert.equal(rgb.format(color), 'rgb(255, 127, 0)'); - const hex = new ColorFormatter('#{red:X}{green:X}{blue:X}'); - assert.equal(hex.format(convert2IColor(color)), '#FF7F00'); + const hex = new HexFormatter(); + assert.equal(hex.format(color), '#FF7F00'); - const hsla = new ColorFormatter('hsla({hue:d[0-360]}, {saturation:d[0-100]}%, {luminance:d[0-100]}%, {alpha})'); - assert.equal(hsla.format(convert2IColor(color)), 'hsla(30, 100%, 50%, 1)'); + const hsl = new HSLFormatter(); + assert.equal(hsl.format(color), 'hsl(30, 100%, 50%)'); }); test('bug#32323', () => { const color = new Color(new HSLA(121, 0.45, 0.29, 0.61)); const rgba = color.rgba; const color2 = new Color(new RGBA(rgba.r, rgba.g, rgba.b, rgba.a)); - const hsla = new ColorFormatter('hsla({hue:d[0-360]}, {saturation:d[0-100]}%, {luminance:d[0-100]}%, {alpha})'); - assert.equal(hsla.format(convert2IColor(color2)), 'hsla(121, 45%, 29%, 0.61)'); + const hsla = new HSLFormatter(); + assert.equal(hsla.format(color2), 'hsla(121, 45%, 29%, 0.61)'); }); }); \ No newline at end of file diff --git a/src/vs/editor/contrib/hover/browser/modesContentHover.ts b/src/vs/editor/contrib/hover/browser/modesContentHover.ts index 9a790a075503a..718e4f41f2080 100644 --- a/src/vs/editor/contrib/hover/browser/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/browser/modesContentHover.ts @@ -9,7 +9,7 @@ import * as dom from 'vs/base/browser/dom'; import { TPromise } from 'vs/base/common/winjs.base'; import { IRange, Range } from 'vs/editor/common/core/range'; import { Position } from 'vs/editor/common/core/position'; -import { HoverProviderRegistry, Hover, IColor, IColorFormatter } from 'vs/editor/common/modes'; +import { HoverProviderRegistry, Hover, IColor, DocumentColorProvider } from 'vs/editor/common/modes'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { getHover } from '../common/hover'; import { HoverOperation, IHoverComputer } from './hoverOperation'; @@ -22,6 +22,7 @@ import { ColorPickerWidget } from 'vs/editor/contrib/colorPicker/browser/colorPi import { ColorDetector } from 'vs/editor/contrib/colorPicker/browser/colorDetector'; import { Color, RGBA } from 'vs/base/common/color'; import { IDisposable, empty as EmptyDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle'; +import { resolveColor } from 'vs/editor/contrib/colorPicker/common/color'; const $ = dom.$; class ColorHover { @@ -29,7 +30,7 @@ class ColorHover { constructor( public readonly range: IRange, public readonly color: IColor, - public readonly formatters: IColorFormatter[] + public readonly provider: DocumentColorProvider ) { } } @@ -90,13 +91,13 @@ class ModesContentComputer implements IHoverComputer { } const range = new Range(this._range.startLineNumber, startColumn, this._range.startLineNumber, endColumn); - const colorRange = colorDetector.getColorRange(d.range.getStartPosition()); + const colorData = colorDetector.getColorData(d.range.getStartPosition()); - if (!didFindColor && colorRange) { + if (!didFindColor && colorData) { didFindColor = true; - const { color, formatters } = colorRange; - return new ColorHover(d.range, color, formatters); + const { color } = colorData.colorRange; + return new ColorHover(d.range, color, colorData.provider); } else { if (isEmptyMarkdownString(d.options.hoverMessage)) { return null; @@ -313,34 +314,24 @@ export class ModesContentHoverWidget extends ContentHoverWidget { const rgba = new RGBA(red * 255, green * 255, blue * 255, alpha); const color = new Color(rgba); - const formatters = [...msg.formatters]; - const text = this._editor.getModel().getValueInRange(msg.range); - - let formatterIndex = 0; - - for (let i = 0; i < formatters.length; i++) { - if (text === formatters[i].format(msg.color)) { - formatterIndex = i; - break; - } - } - - const model = new ColorPickerModel(color, formatters, formatterIndex); + const model = new ColorPickerModel(color, 0); const widget = new ColorPickerWidget(fragment, model, this._editor.getConfiguration().pixelRatio); const editorModel = this._editor.getModel(); let range = new Range(msg.range.startLineNumber, msg.range.startColumn, msg.range.endLineNumber, msg.range.endColumn); const updateEditorModel = () => { - const text = model.formatter.format({ + const color = { red: model.color.rgba.r / 255, green: model.color.rgba.g / 255, blue: model.color.rgba.b / 255, alpha: model.color.rgba.a + }; + resolveColor(color, model.formatter.colorFormat, msg.provider).then(text => { + editorModel.pushEditOperations([], [{ identifier: null, range, text, forceMoveMarkers: false }], () => []); + this._editor.pushUndoStop(); + range = range.setEndPosition(range.endLineNumber, range.startColumn + text.length); }); - editorModel.pushEditOperations([], [{ identifier: null, range, text, forceMoveMarkers: false }], () => []); - this._editor.pushUndoStop(); - range = range.setEndPosition(range.endLineNumber, range.startColumn + text.length); }; const colorListener = model.onColorFlushed(updateEditorModel); diff --git a/src/vs/editor/standalone/browser/standaloneLanguages.ts b/src/vs/editor/standalone/browser/standaloneLanguages.ts index 894e651ba1932..8dfdbf9ca77a6 100644 --- a/src/vs/editor/standalone/browser/standaloneLanguages.ts +++ b/src/vs/editor/standalone/browser/standaloneLanguages.ts @@ -739,5 +739,6 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages { CompletionItemKind: CompletionItemKind, SymbolKind: modes.SymbolKind, IndentAction: IndentAction, + ColorFormat: modes.ColorFormat }; } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 86652dcf0173a..467ac20a37455 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -4807,11 +4807,12 @@ declare module monaco.languages { } /** - * A color formatter. + * Represents a color format */ - export interface IColorFormatter { - readonly supportsTransparency: boolean; - format(color: IColor): string; + export enum ColorFormat { + RGB = 0, + HEX = 1, + HSL = 2, } /** @@ -4826,10 +4827,6 @@ declare module monaco.languages { * The color represented in this range. */ color: IColor; - /** - * The available formats for this specific color. - */ - formatters: IColorFormatter[]; } /** @@ -4840,6 +4837,10 @@ declare module monaco.languages { * Provides the color ranges for a specific model. */ provideColorRanges(model: editor.IReadOnlyModel, token: CancellationToken): IColorRange[] | Thenable; + /** + * Provide the string representation for a color. + */ + resolveColor(color: IColor, colorFormat: ColorFormat, token: CancellationToken): string | Thenable; } export interface IResourceEdit { diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 3adf13e7ded47..32a7f37b42646 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -102,71 +102,16 @@ declare module 'vscode' { readonly alpha: number; constructor(red: number, green: number, blue: number, alpha: number); - - /** - * Creates a color from the HSLA space. - * - * @param hue The hue component in the range [0-1]. - * @param saturation The saturation component in the range [0-1]. - * @param luminance The luminance component in the range [0-1]. - * @param alpha The alpha component in the range [0-1]. - */ - static fromHSLA(hue: number, saturation: number, luminance: number, alpha: number): Color; - - /** - * Creates a color by from a hex string. Supported formats are: #RRGGBB, #RRGGBBAA, #RGB, #RGBA. - * null is returned if the string does not match one of the supported formats. - * @param hex a string to parse - */ - static fromHex(hex: string): Color | null; } /** - * A color format is either a single format or a combination of two - * formats: an opaque one and a transparent one. The format itself - * is a string representation of how the color can be formatted. It - * supports the use of placeholders, similar to how snippets work. - * Each placeholder, surrounded by curly braces `{}`, requires a - * variable name and can optionally specify a number format and range - * for that variable's value. - * - * Supported variables: - * - `red` - * - `green` - * - `blue` - * - `hue` - * - `saturation` - * - `luminance` - * - `alpha` - * - * Supported number formats: - * - `f`, float with 2 decimal points. This is the default format. Default range is `[0-1]`. - * - `Xf`, float with `X` decimal points. Default range is `[0-1]`. - * - `d`, decimal. Default range is `[0-255]`. - * - `x`, `X`, hexadecimal. Default range is `[00-FF]`. - * - * The default number format is float. The default number range is `[0-1]`. - * - * As an example, take the color `Color(1, 0.5, 0, 1)`. Here's how - * different formats would format it: - * - * - CSS RGB - * - Format: `rgb({red:d[0-255]}, {green:d[0-255]}, {blue:d[0-255]})` - * - Output: `rgb(255, 127, 0)` - * - * - CSS RGBA - * - Format: `rgba({red:d[0-255]}, {green:d[0-255]}, {blue:d[0-255]}, {alpha})` - * - Output: `rgba(255, 127, 0, 1)` - * - * - CSS Hexadecimal - * - Format: `#{red:X}{green:X}{blue:X}` - * - Output: `#FF7F00` - * - * - CSS HSLA - * - Format: `hsla({hue:d[0-360]}, {saturation:d[0-100]}%, {luminance:d[0-100]}%, {alpha})` - * - Output: `hsla(30, 100%, 50%, 1)` + * Represents a color format */ - export type ColorFormat = string | { opaque: string, transparent: string }; + export enum ColorFormat { + RGB = 0, + HEX = 1, + HSL = 2 + } /** * Represents a color range from a document. @@ -183,11 +128,6 @@ declare module 'vscode' { */ color: Color; - /** - * The other formats this color range supports the color to be formatted in. - */ - availableFormats: ColorFormat[]; - /** * Creates a new color range. * @@ -195,7 +135,7 @@ declare module 'vscode' { * @param color The value of the color. * @param format The format in which this color is currently formatted. */ - constructor(range: Range, color: Color, availableFormats: ColorFormat[]); + constructor(range: Range, color: Color); } /** @@ -212,6 +152,10 @@ declare module 'vscode' { * can be signaled by returning `undefined`, `null`, or an empty array. */ provideDocumentColors(document: TextDocument, token: CancellationToken): ProviderResult; + /** + * Provide the string representation for a color. + */ + resolveColor(color: Color, colorFormat: ColorFormat): ProviderResult; } export namespace languages { diff --git a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts index 8ae09ea6f350b..44979079968ea 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts @@ -15,12 +15,11 @@ import { wireCancellationToken } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Position as EditorPosition } from 'vs/editor/common/core/position'; import { Range as EditorRange } from 'vs/editor/common/core/range'; -import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, IRawColorFormatMap, MainContext, IExtHostContext } from '../node/extHost.protocol'; +import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext } from '../node/extHost.protocol'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { LanguageConfiguration } from 'vs/editor/common/modes/languageConfiguration'; import { IHeapService } from './mainThreadHeapService'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { ColorFormatter, CombinedColorFormatter } from 'vs/editor/contrib/colorPicker/common/colorFormatter'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; @extHostNamedCustomer(MainContext.MainThreadLanguageFeatures) @@ -30,7 +29,6 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha private _heapService: IHeapService; private _modeService: IModeService; private _registrations: { [handle: number]: IDisposable; } = Object.create(null); - private _formatters: Map; constructor( extHostContext: IExtHostContext, @@ -40,7 +38,6 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha this._proxy = extHostContext.get(ExtHostContext.ExtHostLanguageFeatures); this._heapService = heapService; this._modeService = modeService; - this._formatters = new Map(); } dispose(): void { @@ -293,14 +290,6 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha return wireCancellationToken(token, proxy.$provideDocumentColors(handle, model.uri)) .then(documentColors => { return documentColors.map(documentColor => { - const formatters = documentColor.availableFormats.map(f => { - if (typeof f === 'number') { - return this._formatters.get(f); - } else { - return new CombinedColorFormatter(this._formatters.get(f[0]), this._formatters.get(f[1])); - } - }); - const [red, green, blue, alpha] = documentColor.color; const color = { red: red / 255.0, @@ -311,22 +300,19 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha return { color, - formatters, range: documentColor.range }; }); }); + }, + resolveColor: (color, format, token) => { + return wireCancellationToken(token, proxy.$resolveColor(handle, color, format)); } }); return TPromise.as(null); } - $registerColorFormats(formats: IRawColorFormatMap): TPromise { - formats.forEach(f => this._formatters.set(f[0], new ColorFormatter(f[1]))); - return TPromise.as(null); - } - // --- configuration $setLanguageConfiguration(handle: number, languageId: string, _configuration: vscode.LanguageConfiguration): TPromise { diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index f31a2199462e3..b1829b9387fc1 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -551,6 +551,7 @@ export function createApiFactory( CancellationTokenSource: CancellationTokenSource, CodeLens: extHostTypes.CodeLens, Color: extHostTypes.Color, + ColorFormat: extHostTypes.ColorFormat, ColorRange: extHostTypes.ColorRange, EndOfLine: extHostTypes.EndOfLine, CompletionItem: extHostTypes.CompletionItem, diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 5f6c645118434..d2963b04958a7 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -232,7 +232,6 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerSuggestSupport(handle: number, selector: vscode.DocumentSelector, triggerCharacters: string[], supportsResolveDetails: boolean): TPromise; $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; $setLanguageConfiguration(handle: number, languageId: string, configuration: vscode.LanguageConfiguration): TPromise; } @@ -501,13 +500,9 @@ export interface ExtHostHeapServiceShape { } export interface IRawColorInfo { color: [number, number, number, number]; - availableFormats: (number | [number, number])[]; range: IRange; } -export type IRawColorFormatMap = [number, string][]; - - export interface IExtHostSuggestion extends modes.ISuggestion { _id: number; _parentId: number; @@ -541,8 +536,9 @@ export interface ExtHostLanguageFeaturesShape { $releaseCompletionItems(handle: number, id: number): void; $provideSignatureHelp(handle: number, resource: URI, position: IPosition): TPromise; $provideDocumentLinks(handle: number, resource: URI): TPromise; - $provideDocumentColors(handle: number, resource: URI): TPromise; $resolveDocumentLink(handle: number, link: modes.ILink): TPromise; + $provideDocumentColors(handle: number, resource: URI): TPromise; + $resolveColor(handle: number, color: modes.IColor, colorFormat: modes.ColorFormat): TPromise; } export interface ExtHostQuickOpenShape { diff --git a/src/vs/workbench/api/node/extHostLanguageFeatures.ts b/src/vs/workbench/api/node/extHostLanguageFeatures.ts index b877218b528b8..fd77b9e39b2ff 100644 --- a/src/vs/workbench/api/node/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/node/extHostLanguageFeatures.ts @@ -18,7 +18,7 @@ import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHos import { ExtHostDiagnostics } from 'vs/workbench/api/node/extHostDiagnostics'; import { IWorkspaceSymbolProvider } from 'vs/workbench/parts/search/common/search'; import { asWinJsPromise } from 'vs/base/common/async'; -import { MainContext, MainThreadTelemetryShape, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IRawColorFormatMap, IMainContext, IExtHostSuggestResult, IExtHostSuggestion } from './extHost.protocol'; +import { MainContext, MainThreadTelemetryShape, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IMainContext, IExtHostSuggestResult, IExtHostSuggestion } from './extHost.protocol'; import { regExpLeadsToEndlessLoop } from 'vs/base/common/strings'; import { IPosition } from 'vs/editor/common/core/position'; import { IRange } from 'vs/editor/common/core/range'; @@ -704,8 +704,6 @@ class LinkProviderAdapter { class ColorProviderAdapter { - private static _colorFormatHandlePool: number = 0; - constructor( private _proxy: MainThreadLanguageFeaturesShape, private _documents: ExtHostDocuments, @@ -720,39 +718,20 @@ class ColorProviderAdapter { return []; } - const newRawColorFormats: IRawColorFormatMap = []; - const getFormatId = (format: string) => { - let id = this._colorFormatCache.get(format); - - if (typeof id !== 'number') { - id = ColorProviderAdapter._colorFormatHandlePool++; - this._colorFormatCache.set(format, id); - newRawColorFormats.push([id, format]); - } - - return id; - }; - const colorInfos: IRawColorInfo[] = colors.map(ci => { - const availableFormats = ci.availableFormats.map(format => { - if (typeof format === 'string') { - return getFormatId(format); - } else { - return [getFormatId(format.opaque), getFormatId(format.transparent)] as [number, number]; - } - }); - 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) }; }); - this._proxy.$registerColorFormats(newRawColorFormats); return colorInfos; }); } + + resolveColor(color: modes.IColor, colorFormat: modes.ColorFormat): TPromise { + return asWinJsPromise(token => this._provider.resolveColor(color, colorFormat)); + } } type Adapter = OutlineAdapter | CodeLensAdapter | DefinitionAdapter | HoverAdapter @@ -1063,6 +1042,10 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._withAdapter(handle, ColorProviderAdapter, adapter => adapter.provideColors(resource)); } + $resolveColor(handle: number, color: modes.IColor, colorFormat: modes.ColorFormat): TPromise { + return this._withAdapter(handle, ColorProviderAdapter, adapter => adapter.resolveColor(color, colorFormat)); + } + // --- configuration setLanguageConfiguration(languageId: string, configuration: vscode.LanguageConfiguration): vscode.Disposable { diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index 21db20fa779c6..e1b60f1ba51c0 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -7,7 +7,6 @@ import * as crypto from 'crypto'; import URI from 'vs/base/common/uri'; -import { Color as BaseColor, HSLA } from 'vs/base/common/color'; import { illegalArgument } from 'vs/base/common/errors'; import * as vscode from 'vscode'; import { isMarkdownString } from 'vs/base/common/htmlContent'; @@ -1052,20 +1051,6 @@ export class Color { this.blue = blue; this.alpha = alpha; } - - static fromHSLA(hue: number, saturation: number, luminance: number, alpha: number): Color { - const color = new BaseColor(new HSLA(hue, saturation, luminance, alpha)).rgba; - return new Color(color.r, color.g, color.b, color.a); - } - - static fromHex(hex: string): Color | null { - let baseColor = BaseColor.Format.CSS.parseHex(hex); - if (baseColor) { - const rgba = baseColor.rgba; - return new Color(rgba.r, rgba.g, rgba.b, rgba.a); - } - return null; - } } export type IColorFormat = string | { opaque: string, transparent: string }; @@ -1075,24 +1060,24 @@ export class ColorRange { color: Color; - availableFormats: IColorFormat[]; - - constructor(range: Range, color: Color, availableFormats: IColorFormat[]) { + constructor(range: Range, color: Color) { if (color && !(color instanceof Color)) { throw illegalArgument('color'); } - if (availableFormats && !Array.isArray(availableFormats)) { - throw illegalArgument('availableFormats'); - } if (!Range.isRange(range) || range.isEmpty) { throw illegalArgument('range'); } this.range = range; this.color = color; - this.availableFormats = availableFormats; } } +export enum ColorFormat { + RGB = 0, + HEX = 1, + HSL = 2 +} + export enum TaskRevealKind { Always = 1, From f0ecb26ace6e0e6c66e93b5c24de4656fd9a6a4f Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 8 Sep 2017 11:19:11 -0700 Subject: [PATCH 2/3] Guess color format. --- .../contrib/colorPicker/browser/colorPickerModel.ts | 9 +++++++++ src/vs/editor/contrib/hover/browser/modesContentHover.ts | 2 ++ 2 files changed, 11 insertions(+) diff --git a/src/vs/editor/contrib/colorPicker/browser/colorPickerModel.ts b/src/vs/editor/contrib/colorPicker/browser/colorPickerModel.ts index 2a41cf6c0a872..50f63a9529f47 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorPickerModel.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorPickerModel.ts @@ -55,6 +55,15 @@ export class ColorPickerModel { this._onDidChangeFormatter.fire(this.formatter); } + guessColorFormat(color: Color, originalText: string): void { + for (let i = 0; i < this.formatters.length; i++) { + if (originalText === this.formatters[i].format(color)) { + this.formatterIndex = i; + break; + } + } + } + flushColor(): void { this._onColorFlushed.fire(this._color); } diff --git a/src/vs/editor/contrib/hover/browser/modesContentHover.ts b/src/vs/editor/contrib/hover/browser/modesContentHover.ts index 718e4f41f2080..e957b545fb6c9 100644 --- a/src/vs/editor/contrib/hover/browser/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/browser/modesContentHover.ts @@ -315,6 +315,8 @@ export class ModesContentHoverWidget extends ContentHoverWidget { const color = new Color(rgba); const model = new ColorPickerModel(color, 0); + const originalText = this._editor.getModel().getValueInRange(msg.range); + model.guessColorFormat(color, originalText); const widget = new ColorPickerWidget(fragment, model, this._editor.getConfiguration().pixelRatio); const editorModel = this._editor.getModel(); From 3753e0e1d2429703baebbccd51fb19cf9ba6cef3 Mon Sep 17 00:00:00 2001 From: rebornix Date: Fri, 8 Sep 2017 14:11:44 -0700 Subject: [PATCH 3/3] Align signatures with other providers. --- extensions/css/client/src/cssMain.ts | 4 ++-- extensions/html/client/src/htmlMain.ts | 4 ++-- extensions/json/client/src/jsonMain.ts | 2 +- src/vs/editor/contrib/colorPicker/common/colorFormatter.ts | 2 +- src/vs/vscode.proposed.d.ts | 2 +- .../api/electron-browser/mainThreadLanguageFeatures.ts | 2 +- src/vs/workbench/api/node/extHost.protocol.ts | 2 +- src/vs/workbench/api/node/extHostLanguageFeatures.ts | 4 ++-- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/extensions/css/client/src/cssMain.ts b/extensions/css/client/src/cssMain.ts index 95f0da71a2df5..cb9310fe1cae6 100644 --- a/extensions/css/client/src/cssMain.ts +++ b/extensions/css/client/src/cssMain.ts @@ -70,13 +70,13 @@ export function activate(context: ExtensionContext) { }); }); }, - resolveColor(color: Color, colorFormat: ColorFormat): Thenable | string { + resolveDocumentColor(color: Color, colorFormat: ColorFormat): Thenable | string { switch (colorFormat) { case ColorFormat.RGB: if (color.alpha === 1) { return `rgb(${Math.round(color.red * 255)}, ${Math.round(color.green * 255)}, ${Math.round(color.blue * 255)})`; } else { - return `rgb(${Math.round(color.red * 255)}, ${Math.round(color.green * 255)}, ${Math.round(color.blue * 255)}, ${color.alpha})`; + return `rgba(${Math.round(color.red * 255)}, ${Math.round(color.green * 255)}, ${Math.round(color.blue * 255)}, ${color.alpha})`; } case ColorFormat.HEX: if (color.alpha === 1) { diff --git a/extensions/html/client/src/htmlMain.ts b/extensions/html/client/src/htmlMain.ts index defc1982ce012..659d60794eab2 100644 --- a/extensions/html/client/src/htmlMain.ts +++ b/extensions/html/client/src/htmlMain.ts @@ -87,13 +87,13 @@ export function activate(context: ExtensionContext) { }); }); }, - resolveColor(color: Color, colorFormat: ColorFormat): Thenable | string { + resolveDocumentColor(color: Color, colorFormat: ColorFormat): Thenable | string { switch (colorFormat) { case ColorFormat.RGB: if (color.alpha === 1) { return `rgb(${Math.round(color.red * 255)}, ${Math.round(color.green * 255)}, ${Math.round(color.blue * 255)})`; } else { - return `rgb(${Math.round(color.red * 255)}, ${Math.round(color.green * 255)}, ${Math.round(color.blue * 255)}, ${color.alpha})`; + return `rgba(${Math.round(color.red * 255)}, ${Math.round(color.green * 255)}, ${Math.round(color.blue * 255)}, ${color.alpha})`; } case ColorFormat.HEX: if (color.alpha === 1) { diff --git a/extensions/json/client/src/jsonMain.ts b/extensions/json/client/src/jsonMain.ts index ed6ffe7e55cb8..026beb78a74fd 100644 --- a/extensions/json/client/src/jsonMain.ts +++ b/extensions/json/client/src/jsonMain.ts @@ -132,7 +132,7 @@ export function activate(context: ExtensionContext) { }); }); }, - resolveColor(color: Color, colorFormat: ColorFormat): Thenable | string { + resolveDocumentColor(color: Color, colorFormat: ColorFormat): Thenable | string { if (color.alpha === 1) { return `#${_toTwoDigitHex(Math.round(color.red * 255))}${_toTwoDigitHex(Math.round(color.green * 255))}${_toTwoDigitHex(Math.round(color.blue * 255))}`; } else { diff --git a/src/vs/editor/contrib/colorPicker/common/colorFormatter.ts b/src/vs/editor/contrib/colorPicker/common/colorFormatter.ts index 4dfd4f78e0fca..4bf43664aee06 100644 --- a/src/vs/editor/contrib/colorPicker/common/colorFormatter.ts +++ b/src/vs/editor/contrib/colorPicker/common/colorFormatter.ts @@ -19,7 +19,7 @@ export class RGBFormatter implements IColorFormatter { if (rgb.a === 1) { return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`; } else { - return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b}, ${rgb.a})`; + return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${rgb.a})`; } } } diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 32a7f37b42646..575bde2236bf2 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -155,7 +155,7 @@ declare module 'vscode' { /** * Provide the string representation for a color. */ - resolveColor(color: Color, colorFormat: ColorFormat): ProviderResult; + resolveDocumentColor(color: Color, colorFormat: ColorFormat): ProviderResult; } export namespace languages { diff --git a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts index 44979079968ea..7015375493233 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts @@ -306,7 +306,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha }); }, resolveColor: (color, format, token) => { - return wireCancellationToken(token, proxy.$resolveColor(handle, color, format)); + return wireCancellationToken(token, proxy.$resolveDocumentColor(handle, color, format)); } }); diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index d2963b04958a7..0f5f2955d8804 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -538,7 +538,7 @@ export interface ExtHostLanguageFeaturesShape { $provideDocumentLinks(handle: number, resource: URI): TPromise; $resolveDocumentLink(handle: number, link: modes.ILink): TPromise; $provideDocumentColors(handle: number, resource: URI): TPromise; - $resolveColor(handle: number, color: modes.IColor, colorFormat: modes.ColorFormat): TPromise; + $resolveDocumentColor(handle: number, color: modes.IColor, colorFormat: modes.ColorFormat): TPromise; } export interface ExtHostQuickOpenShape { diff --git a/src/vs/workbench/api/node/extHostLanguageFeatures.ts b/src/vs/workbench/api/node/extHostLanguageFeatures.ts index fd77b9e39b2ff..2ea04f708b3b2 100644 --- a/src/vs/workbench/api/node/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/node/extHostLanguageFeatures.ts @@ -730,7 +730,7 @@ class ColorProviderAdapter { } resolveColor(color: modes.IColor, colorFormat: modes.ColorFormat): TPromise { - return asWinJsPromise(token => this._provider.resolveColor(color, colorFormat)); + return asWinJsPromise(token => this._provider.resolveDocumentColor(color, colorFormat)); } } @@ -1042,7 +1042,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._withAdapter(handle, ColorProviderAdapter, adapter => adapter.provideColors(resource)); } - $resolveColor(handle: number, color: modes.IColor, colorFormat: modes.ColorFormat): TPromise { + $resolveDocumentColor(handle: number, color: modes.IColor, colorFormat: modes.ColorFormat): TPromise { return this._withAdapter(handle, ColorProviderAdapter, adapter => adapter.resolveColor(color, colorFormat)); }