Fix #32235. Functional style formatter. Color range is all float number and remove fromHex, fromHSL methods.

This commit is contained in:
rebornix
2017-09-08 01:15:52 -07:00
parent b9c9c90c93
commit 3479d81d77
25 changed files with 286 additions and 475 deletions

View File

@@ -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<IColorRange[]>;
/**
* Provide the string representation for a color.
*/
resolveColor(color: IColor, colorFormat: ColorFormat, token: CancellationToken): string | Thenable<string>;
}
export interface IResourceEdit {

View File

@@ -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<void>;
private _decorationsIds: string[] = [];
private _colorRanges = new Map<string, IColorRange>();
private _colorDatas = new Map<string, IColorData>();
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<string, IColorRange>();
this._decorationsIds.forEach((id, i) => this._colorRanges.set(id, colorRanges[i]));
this._colorDatas = new Map<string, IColorData>();
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);
}
}

View File

@@ -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<IColorFormatter>();
readonly onDidChangeFormatter: Event<IColorFormatter> = 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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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<IColorRange[]> {
const providers = ColorProviderRegistry.ordered(model).reverse();
const promises = providers.map(p => asWinJsPromise(token => p.provideColorRanges(model, token)));
return TPromise.join(promises)
.then(ranges => flatten(ranges.filter(r => Array.isArray(r))));
export interface IColorData {
colorRange: IColorRange;
provider: DocumentColorProvider;
}
export function getColors(model: IReadOnlyModel): TPromise<IColorData[]> {
const colors: IColorData[] = [];
const providers = ColorProviderRegistry.ordered(model).reverse();
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);
}
export function resolveColor(color: IColor, colorFormat: ColorFormat, provider: DocumentColorProvider): TPromise<string> {
return asWinJsPromise(token => provider.resolveColor(color, colorFormat, token));
}

View File

@@ -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;
}
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 {
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]);
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)));
}
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('');
}
}
export class CombinedColorFormatter implements IColorFormatter {
export class RGBFormatter implements IColorFormatter {
readonly supportsTransparency: boolean = true;
readonly colorFormat: ColorFormat = ColorFormat.RGB;
constructor(private opaqueFormatter: IColorFormatter, private transparentFormatter: IColorFormatter) {
if (!transparentFormatter.supportsTransparency) {
throw new Error('Invalid transparent formatter');
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})`;
}
}
}
format(color: IColor): string {
return color.alpha === 1 ? this.opaqueFormatter.format(color) : this.transparentFormatter.format(color);
export class HexFormatter implements IColorFormatter {
readonly supportsTransparency: boolean = false;
readonly colorFormat: ColorFormat = ColorFormat.HEX;
_toTwoDigitHex(n: number): string {
const r = n.toString(16);
return r.length !== 2 ? '0' + r : r;
}
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 HSLFormatter implements IColorFormatter {
readonly supportsTransparency: boolean = true;
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})`;
}
}
}

View File

@@ -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 rgb = new RGBFormatter();
assert.equal(rgb.format(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 hex = new HexFormatter();
assert.equal(hex.format(color), '#FF7F00');
const hex = new ColorFormatter('#{red:X}{green:X}{blue:X}');
assert.equal(hex.format(convert2IColor(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)');
});
});

View File

@@ -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<HoverPart[]> {
}
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);

View File

@@ -739,5 +739,6 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages {
CompletionItemKind: CompletionItemKind,
SymbolKind: modes.SymbolKind,
IndentAction: IndentAction,
ColorFormat: modes.ColorFormat
};
}

17
src/vs/monaco.d.ts vendored
View File

@@ -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<IColorRange[]>;
/**
* Provide the string representation for a color.
*/
resolveColor(color: IColor, colorFormat: ColorFormat, token: CancellationToken): string | Thenable<string>;
}
export interface IResourceEdit {

View File

@@ -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.
* <code>null</code> 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<ColorRange[]>;
/**
* Provide the string representation for a color.
*/
resolveColor(color: Color, colorFormat: ColorFormat): ProviderResult<string>;
}
export namespace languages {

View File

@@ -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<number, ColorFormatter>;
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<number, ColorFormatter>();
}
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<any> {
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<any> {

View File

@@ -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,

View File

@@ -232,7 +232,6 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable {
$registerSuggestSupport(handle: number, selector: vscode.DocumentSelector, triggerCharacters: string[], supportsResolveDetails: boolean): TPromise<any>;
$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>;
$setLanguageConfiguration(handle: number, languageId: string, configuration: vscode.LanguageConfiguration): TPromise<any>;
}
@@ -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<modes.SignatureHelp>;
$provideDocumentLinks(handle: number, resource: URI): TPromise<modes.ILink[]>;
$provideDocumentColors(handle: number, resource: URI): TPromise<IRawColorInfo[]>;
$resolveDocumentLink(handle: number, link: modes.ILink): TPromise<modes.ILink>;
$provideDocumentColors(handle: number, resource: URI): TPromise<IRawColorInfo[]>;
$resolveColor(handle: number, color: modes.IColor, colorFormat: modes.ColorFormat): TPromise<string>;
}
export interface ExtHostQuickOpenShape {

View File

@@ -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<string> {
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<string> {
return this._withAdapter(handle, ColorProviderAdapter, adapter => adapter.resolveColor(color, colorFormat));
}
// --- configuration
setLanguageConfiguration(languageId: string, configuration: vscode.LanguageConfiguration): vscode.Disposable {

View File

@@ -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,