提交 d069e922 编写于 作者: R rebornix

Re #34366. Extensions define color formats.

上级 ff8c917a
......@@ -6,7 +6,7 @@
import * as path from 'path';
import { languages, window, commands, ExtensionContext, TextDocument, ColorRange, ColorFormat, Color } from 'vscode';
import { languages, window, commands, ExtensionContext, TextDocument, ColorInformation, ColorPresentation, Color, TextEdit as CodeTextEdit } from 'vscode';
import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind, TextEdit } from 'vscode-languageclient';
import { ConfigurationFeature } from 'vscode-languageclient/lib/proposed';
......@@ -60,38 +60,45 @@ export function activate(context: ExtensionContext) {
client.onReady().then(_ => {
// register color provider
context.subscriptions.push(languages.registerColorProvider(documentSelector, {
provideDocumentColors(document: TextDocument): Thenable<ColorRange[]> {
provideDocumentColors(document: TextDocument): Thenable<ColorInformation[]> {
let params = client.code2ProtocolConverter.asDocumentSymbolParams(document);
return client.sendRequest(DocumentColorRequest.type, params).then(symbols => {
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);
return new ColorInformation(range, color);
});
});
},
resolveDocumentColor(color: Color, colorFormat: ColorFormat): Thenable<string> | 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 `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) {
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})`;
}
provideColorPresentations(colorInfo: ColorInformation): ColorPresentation[] | Thenable<ColorPresentation[]> {
let result: ColorPresentation[] = [];
let color = colorInfo.color;
let label;
if (color.alpha === 1) {
label = `rgb(${Math.round(color.red * 255)}, ${Math.round(color.green * 255)}, ${Math.round(color.blue * 255)})`;
} else {
label = `rgba(${Math.round(color.red * 255)}, ${Math.round(color.green * 255)}, ${Math.round(color.blue * 255)}, ${color.alpha})`;
}
result.push({ label: label, textEdit: new CodeTextEdit(colorInfo.range, label) });
if (color.alpha === 1) {
label = `#${_toTwoDigitHex(Math.round(color.red * 255))}${_toTwoDigitHex(Math.round(color.green * 255))}${_toTwoDigitHex(Math.round(color.blue * 255))}`;
} else {
label = `#${_toTwoDigitHex(Math.round(color.red * 255))}${_toTwoDigitHex(Math.round(color.green * 255))}${_toTwoDigitHex(Math.round(color.blue * 255))}${_toTwoDigitHex(Math.round(color.alpha * 255))}`;
}
result.push({ label: label, textEdit: new CodeTextEdit(colorInfo.range, label) });
const hsl = convert.rgb.hsl(Math.round(color.red * 255), Math.round(color.green * 255), Math.round(color.blue * 255));
if (color.alpha === 1) {
label = `hsl(${hsl[0]}, ${hsl[1]}%, ${hsl[2]}%)`;
} else {
label = `hsla(${hsl[0]}, ${hsl[1]}%, ${hsl[2]}%, ${color.alpha})`;
}
result.push({ label: label, textEdit: new CodeTextEdit(colorInfo.range, label) });
return result;
}
}));
});
......
......@@ -6,7 +6,7 @@
import * as path from 'path';
import { languages, ExtensionContext, IndentAction, Position, TextDocument, Color, ColorRange, ColorFormat } from 'vscode';
import { languages, ExtensionContext, IndentAction, Position, TextDocument, Color, ColorInformation, ColorPresentation, TextEdit } from 'vscode';
import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind, RequestType, TextDocumentPositionParams } from 'vscode-languageclient';
import { EMPTY_ELEMENTS } from './htmlEmptyTagsShared';
import { activateTagClosing } from './tagClosing';
......@@ -77,38 +77,45 @@ export function activate(context: ExtensionContext) {
toDispose.push(disposable);
client.onReady().then(() => {
disposable = languages.registerColorProvider(documentSelector, {
provideDocumentColors(document: TextDocument): Thenable<ColorRange[]> {
provideDocumentColors(document: TextDocument): Thenable<ColorInformation[]> {
let params = client.code2ProtocolConverter.asDocumentSymbolParams(document);
return client.sendRequest(DocumentColorRequest.type, params).then(symbols => {
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);
return new ColorInformation(range, color);
});
});
},
resolveDocumentColor(color: Color, colorFormat: ColorFormat): Thenable<string> | 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 `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) {
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})`;
}
provideColorPresentations(colorInfo: ColorInformation): ColorPresentation[] | Thenable<ColorPresentation[]> {
let result: ColorPresentation[] = [];
let color = colorInfo.color;
let label;
if (color.alpha === 1) {
label = `rgb(${Math.round(color.red * 255)}, ${Math.round(color.green * 255)}, ${Math.round(color.blue * 255)})`;
} else {
label = `rgba(${Math.round(color.red * 255)}, ${Math.round(color.green * 255)}, ${Math.round(color.blue * 255)}, ${color.alpha})`;
}
result.push({ label: label, textEdit: new TextEdit(colorInfo.range, label) });
if (color.alpha === 1) {
label = `#${_toTwoDigitHex(Math.round(color.red * 255))}${_toTwoDigitHex(Math.round(color.green * 255))}${_toTwoDigitHex(Math.round(color.blue * 255))}`;
} else {
label = `#${_toTwoDigitHex(Math.round(color.red * 255))}${_toTwoDigitHex(Math.round(color.green * 255))}${_toTwoDigitHex(Math.round(color.blue * 255))}${_toTwoDigitHex(Math.round(color.alpha * 255))}`;
}
result.push({ label: label, textEdit: new TextEdit(colorInfo.range, label) });
const hsl = convert.rgb.hsl(Math.round(color.red * 255), Math.round(color.green * 255), Math.round(color.blue * 255));
if (color.alpha === 1) {
label = `hsl(${hsl[0]}, ${hsl[1]}%, ${hsl[2]}%)`;
} else {
label = `hsla(${hsl[0]}, ${hsl[1]}%, ${hsl[2]}%, ${color.alpha})`;
}
result.push({ label: label, textEdit: new TextEdit(colorInfo.range, label) });
return result;
}
});
toDispose.push(disposable);
......
......@@ -6,7 +6,7 @@
import * as path from 'path';
import { workspace, languages, ExtensionContext, extensions, Uri, TextDocument, ColorRange, Color, ColorFormat } from 'vscode';
import { workspace, languages, ExtensionContext, extensions, Uri, TextDocument, ColorInformation, Color, ColorPresentation, TextEdit } 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';
......@@ -122,22 +122,29 @@ export function activate(context: ExtensionContext) {
};
// register color provider
context.subscriptions.push(languages.registerColorProvider(documentSelector, {
provideDocumentColors(document: TextDocument): Thenable<ColorRange[]> {
provideDocumentColors(document: TextDocument): Thenable<ColorInformation[]> {
let params = client.code2ProtocolConverter.asDocumentSymbolParams(document);
return client.sendRequest(DocumentColorRequest.type, params).then(symbols => {
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);
return new ColorInformation(range, color);
});
});
},
resolveDocumentColor(color: Color, colorFormat: ColorFormat): Thenable<string> | string {
provideColorPresentations(colorInfo: ColorInformation): ColorPresentation[] | Thenable<ColorPresentation[]> {
let result: ColorPresentation[] = [];
let color = colorInfo.color;
let label;
if (color.alpha === 1) {
return `#${_toTwoDigitHex(Math.round(color.red * 255))}${_toTwoDigitHex(Math.round(color.green * 255))}${_toTwoDigitHex(Math.round(color.blue * 255))}`;
label = `#${_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))}`;
label = `#${_toTwoDigitHex(Math.round(color.red * 255))}${_toTwoDigitHex(Math.round(color.green * 255))}${_toTwoDigitHex(Math.round(color.blue * 255))}${_toTwoDigitHex(Math.round(color.alpha * 255))}`;
}
result.push({ label: label, textEdit: new TextEdit(colorInfo.range, label) });
return result;
}
}));
});
......
......@@ -680,28 +680,31 @@ export interface IColor {
}
/**
* Represents a color format
* String representations for a color
*/
export enum ColorFormat {
RGB = 0,
HEX = 1,
HSL = 2
}
/**
* A color formatter.
* @internal
*/
export interface IColorFormatter {
readonly supportsTransparency: boolean;
readonly colorFormat: ColorFormat;
format(color: Color): string;
export interface IColorPresentation {
/**
* The label of this color presentation. It will be shown on the color
* picker header. By default this is also the text that is inserted when selecting
* this color presentation.
*/
label: string;
/**
* An [edit](#TextEdit) which is applied to a document when selecting
* this presentation for the color.
*/
textEdit?: TextEdit;
/**
* An optional array of additional [text edits](#TextEdit) that are applied when
* selecting this color presentation.
*/
additionalTextEdits?: TextEdit[];
}
/**
* A color range is a range in a text model which represents a color.
*/
export interface IColorRange {
export interface IColorInformation {
/**
* The range within the model.
......@@ -721,11 +724,11 @@ export interface DocumentColorProvider {
/**
* Provides the color ranges for a specific model.
*/
provideColorRanges(model: editorCommon.IReadOnlyModel, token: CancellationToken): IColorRange[] | Thenable<IColorRange[]>;
provideDocumentColors(model: editorCommon.IReadOnlyModel, token: CancellationToken): IColorInformation[] | Thenable<IColorInformation[]>;
/**
* Provide the string representation for a color.
* Provide the string representations for a color.
*/
resolveColor(color: IColor, colorFormat: ColorFormat, token: CancellationToken): string | Thenable<string>;
provideColorPresentations(colorInfo: IColorInformation, token: CancellationToken): IColorPresentation[] | Thenable<IColorPresentation[]>;
}
export interface IResourceEdit {
......
......@@ -149,10 +149,10 @@ export class ColorDetector implements IEditorContribution {
private updateDecorations(colorDatas: IColorData[]): void {
const decorations = colorDatas.map(c => ({
range: {
startLineNumber: c.colorRange.range.startLineNumber,
startColumn: c.colorRange.range.startColumn,
endLineNumber: c.colorRange.range.endLineNumber,
endColumn: c.colorRange.range.endColumn
startLineNumber: c.colorInfo.range.startLineNumber,
startColumn: c.colorInfo.range.startColumn,
endLineNumber: c.colorInfo.range.endLineNumber,
endColumn: c.colorInfo.range.endColumn
},
options: {}
}));
......@@ -163,12 +163,12 @@ export class ColorDetector implements IEditorContribution {
this._decorationsIds.forEach((id, i) => this._colorDatas.set(id, colorDatas[i]));
}
private updateColorDecorators(colorInfos: IColorData[]): void {
private updateColorDecorators(colorData: 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].colorRange.color;
for (let i = 0; i < colorData.length && decorations.length < MAX_DECORATORS; i++) {
const { red, green, blue, alpha } = colorData[i].colorInfo.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})`;
......@@ -195,10 +195,10 @@ export class ColorDetector implements IEditorContribution {
newDecorationsTypes[key] = true;
decorations.push({
range: {
startLineNumber: colorInfos[i].colorRange.range.startLineNumber,
startColumn: colorInfos[i].colorRange.range.startColumn,
endLineNumber: colorInfos[i].colorRange.range.endLineNumber,
endColumn: colorInfos[i].colorRange.range.endColumn
startLineNumber: colorData[i].colorInfo.range.startLineNumber,
startColumn: colorData[i].colorInfo.range.startColumn,
endLineNumber: colorData[i].colorInfo.range.endLineNumber,
endColumn: colorData[i].colorInfo.range.endColumn
},
options: this._codeEditorService.resolveDecorationOptions(key, true)
});
......
......@@ -5,8 +5,7 @@
import Event, { Emitter } from 'vs/base/common/event';
import { Color } from 'vs/base/common/color';
import { IColorFormatter } from 'vs/editor/common/modes';
import { HexFormatter, HSLFormatter, RGBFormatter } from '../common/colorFormatter';
import { IColorPresentation } from 'vs/editor/common/modes';
export class ColorPickerModel {
......@@ -26,9 +25,21 @@ export class ColorPickerModel {
this._onDidChangeColor.fire(color);
}
get formatter(): IColorFormatter { return this.formatters[this.formatterIndex]; }
get presentation(): IColorPresentation { return this.colorPresentations[this.presentationIndex]; }
readonly formatters: IColorFormatter[];
private _colorPresentations: IColorPresentation[];
get colorPresentations(): IColorPresentation[] {
return this._colorPresentations;
}
set colorPresentations(colorPresentations: IColorPresentation[]) {
this._colorPresentations = colorPresentations;
if (this.presentationIndex > colorPresentations.length - 1) {
this.presentationIndex = 0;
}
this._onDidChangePresentation.fire(this.presentation);
}
private _onColorFlushed = new Emitter<Color>();
readonly onColorFlushed: Event<Color> = this._onColorFlushed.event;
......@@ -36,29 +47,26 @@ export class ColorPickerModel {
private _onDidChangeColor = new Emitter<Color>();
readonly onDidChangeColor: Event<Color> = this._onDidChangeColor.event;
private _onDidChangeFormatter = new Emitter<IColorFormatter>();
readonly onDidChangeFormatter: Event<IColorFormatter> = this._onDidChangeFormatter.event;
private _onDidChangePresentation = new Emitter<IColorPresentation>();
readonly onDidChangePresentation: Event<IColorPresentation> = this._onDidChangePresentation.event;
constructor(color: Color, private formatterIndex: number) {
constructor(color: Color, availableColorPresentations: IColorPresentation[], private presentationIndex: number) {
this.originalColor = color;
this._color = color;
this.formatters = [
new RGBFormatter(),
new HexFormatter(),
new HSLFormatter()
];
this._colorPresentations = availableColorPresentations;
}
selectNextColorFormat(): void {
this.formatterIndex = (this.formatterIndex + 1) % this.formatters.length;
selectNextColorPresentation(): void {
this.presentationIndex = (this.presentationIndex + 1) % this.colorPresentations.length;
this.flushColor();
this._onDidChangeFormatter.fire(this.formatter);
this._onDidChangePresentation.fire(this.presentation);
}
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;
guessColorPresentation(color: Color, originalText: string): void {
for (let i = 0; i < this.colorPresentations.length; i++) {
if (originalText === this.colorPresentations[i].label) {
this.presentationIndex = i;
this._onDidChangePresentation.fire(this.presentation);
break;
}
}
......
......@@ -38,24 +38,25 @@ export class ColorPickerHeader extends Disposable {
this.backgroundColor = theme.getColor(editorHoverBackground) || Color.white;
}));
this._register(dom.addDisposableListener(this.pickedColorNode, dom.EventType.CLICK, () => this.model.selectNextColorFormat()));
this._register(dom.addDisposableListener(this.pickedColorNode, dom.EventType.CLICK, () => this.model.selectNextColorPresentation()));
this._register(dom.addDisposableListener(colorBox, dom.EventType.CLICK, () => {
this.model.color = this.model.originalColor;
this.model.flushColor();
}));
this._register(model.onDidChangeColor(this.onDidChangeColor, this));
this._register(model.onDidChangeFormatter(this.onDidChangeFormatter, this));
this.onDidChangeColor(this.model.color);
this._register(model.onDidChangePresentation(this.onDidChangePresentation, this));
this.pickedColorNode.style.backgroundColor = Color.Format.CSS.format(model.color);
dom.toggleClass(this.pickedColorNode, 'light', model.color.rgba.a < 0.5 ? this.backgroundColor.isLighter() : model.color.isLighter());
}
private onDidChangeColor(color: Color): void {
this.pickedColorNode.style.backgroundColor = Color.Format.CSS.format(color);
dom.toggleClass(this.pickedColorNode, 'light', color.rgba.a < 0.5 ? this.backgroundColor.isLighter() : color.isLighter());
this.onDidChangeFormatter();
this.onDidChangePresentation();
}
private onDidChangeFormatter(): void {
this.pickedColorNode.textContent = this.model.formatter.format(this.model.color);
private onDidChangePresentation(): void {
this.pickedColorNode.textContent = this.model.presentation.label;
}
}
......
......@@ -4,22 +4,22 @@
*--------------------------------------------------------------------------------------------*/
import { TPromise } from 'vs/base/common/winjs.base';
import { ColorProviderRegistry, DocumentColorProvider, IColorRange, IColor, ColorFormat } from 'vs/editor/common/modes';
import { ColorProviderRegistry, DocumentColorProvider, IColorInformation, IColorPresentation } from 'vs/editor/common/modes';
import { asWinJsPromise } from 'vs/base/common/async';
import { IReadOnlyModel } from 'vs/editor/common/editorCommon';
export interface IColorData {
colorRange: IColorRange;
colorInfo: IColorInformation;
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 => {
const promises = providers.map(provider => asWinJsPromise(token => provider.provideDocumentColors(model, token)).then(result => {
if (Array.isArray(result)) {
for (let colorRange of result) {
colors.push({ colorRange, provider });
for (let colorInfo of result) {
colors.push({ colorInfo, provider });
}
}
}));
......@@ -27,6 +27,6 @@ export function getColors(model: IReadOnlyModel): TPromise<IColorData[]> {
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));
export function getColorPresentations(colorInfo: IColorInformation, provider: DocumentColorProvider): TPromise<IColorPresentation[]> {
return asWinJsPromise(token => provider.provideColorPresentations(colorInfo, token));
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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;
}
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 `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${rgb.a})`;
}
}
}
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})`;
}
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import { Color, RGBA, HSLA } from 'vs/base/common/color';
import { RGBFormatter, HexFormatter, HSLFormatter } from 'vs/editor/contrib/colorPicker/common/colorFormatter';
suite('ColorFormatter', () => {
test('documentation', () => {
const color = new Color(new RGBA(255, 127, 0));
const rgb = new RGBFormatter();
assert.equal(rgb.format(color), 'rgb(255, 127, 0)');
const hex = new HexFormatter();
assert.equal(hex.format(color), '#FF7F00');
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 HSLFormatter();
assert.equal(hsla.format(color2), 'hsla(121, 45%, 29%, 0.61)');
});
});
\ No newline at end of file
......@@ -22,7 +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';
import { getColorPresentations } from 'vs/editor/contrib/colorPicker/common/color';
const $ = dom.$;
class ColorHover {
......@@ -96,7 +96,7 @@ class ModesContentComputer implements IHoverComputer<HoverPart[]> {
if (!didFindColor && colorData) {
didFindColor = true;
const { color } = colorData.colorRange;
const { color } = colorData.colorInfo;
return new ColorHover(d.range, color, colorData.provider);
} else {
if (isEmptyMarkdownString(d.options.hoverMessage)) {
......@@ -294,6 +294,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
highlightRange = messages[0].range,
fragment = document.createDocumentFragment();
let containColorPicker = false;
messages.forEach((msg) => {
if (!msg.range) {
return;
......@@ -310,46 +311,88 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
fragment.appendChild($('div.hover-row', null, renderedContents));
});
} else {
containColorPicker = true;
const { red, green, blue, alpha } = msg.color;
const rgba = new RGBA(red * 255, green * 255, blue * 255, alpha);
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();
let range = new Range(msg.range.startLineNumber, msg.range.startColumn, msg.range.endLineNumber, msg.range.endColumn);
let colorInfo = { range: msg.range, color: msg.color };
const updateEditorModel = () => {
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 }], () => []);
// create blank olor picker model and widget first to ensure it's positioned correctly.
const model = new ColorPickerModel(color, [], 0);
const widget = new ColorPickerWidget(fragment, model, this._editor.getConfiguration().pixelRatio);
getColorPresentations(colorInfo, msg.provider).then(colorPresentations => {
model.colorPresentations = colorPresentations;
const originalText = this._editor.getModel().getValueInRange(msg.range);
model.guessColorPresentation(color, originalText);
const updateEditorModel = () => {
let textEdits;
let newRange;
if (model.presentation.textEdit) {
textEdits = [model.presentation.textEdit];
console.log('insert text');
newRange = new Range(
model.presentation.textEdit.range.startLineNumber,
model.presentation.textEdit.range.startColumn,
model.presentation.textEdit.range.endLineNumber,
model.presentation.textEdit.range.endColumn
);
newRange = newRange.setEndPosition(newRange.endLineNumber, newRange.startColumn + model.presentation.textEdit.text.length);
} else {
textEdits = [{ identifier: null, range, text: model.presentation.label, forceMoveMarkers: false }];
newRange = range.setEndPosition(range.endLineNumber, range.startColumn + model.presentation.label.length);
}
editorModel.pushEditOperations([], textEdits, () => []);
if (model.presentation.additionalTextEdits) {
textEdits = [...model.presentation.additionalTextEdits];
editorModel.pushEditOperations([], textEdits, () => []);
this.hide();
}
this._editor.pushUndoStop();
range = range.setEndPosition(range.endLineNumber, range.startColumn + text.length);
range = newRange;
};
const updateColorPresentations = (color: Color) => {
return getColorPresentations({
range: range,
color: {
red: color.rgba.r / 255,
green: color.rgba.g / 255,
blue: color.rgba.b / 255,
alpha: color.rgba.a
}
}, msg.provider).then((colorPresentations) => {
model.colorPresentations = colorPresentations;
});
};
const colorListener = model.onColorFlushed((color: Color) => {
updateColorPresentations(color).then(updateEditorModel);
});
};
const colorChangeListener = model.onDidChangeColor(updateColorPresentations);
const colorListener = model.onColorFlushed(updateEditorModel);
this._colorPicker = widget;
this.showAt(new Position(renderRange.startLineNumber, renderColumn), this._shouldFocus);
this.updateContents(fragment);
this._colorPicker.layout();
this._colorPicker = widget;
this.renderDisposable = combinedDisposable([colorListener, widget]);
this.renderDisposable = combinedDisposable([colorListener, colorChangeListener, widget]);
});
}
});
// show
this.showAt(new Position(renderRange.startLineNumber, renderColumn), this._shouldFocus);
this.updateContents(fragment);
if (this._colorPicker) {
this._colorPicker.layout();
if (!containColorPicker) {
this.showAt(new Position(renderRange.startLineNumber, renderColumn), this._shouldFocus);
this.updateContents(fragment);
}
this._isChangingDecorations = true;
......
......@@ -738,7 +738,6 @@ export function createMonacoLanguagesAPI(): typeof monaco.languages {
DocumentHighlightKind: modes.DocumentHighlightKind,
CompletionItemKind: CompletionItemKind,
SymbolKind: modes.SymbolKind,
IndentAction: IndentAction,
ColorFormat: modes.ColorFormat
IndentAction: IndentAction
};
}
......@@ -4823,18 +4823,31 @@ declare module monaco.languages {
}
/**
* Represents a color format
* String representations for a color
*/
export enum ColorFormat {
RGB = 0,
HEX = 1,
HSL = 2,
export interface IColorPresentation {
/**
* The label of this color presentation. It will be shown on the color
* picker header. By default this is also the text that is inserted when selecting
* this color presentation.
*/
label: string;
/**
* An [edit](#TextEdit) which is applied to a document when selecting
* this presentation for the color.
*/
textEdit?: TextEdit;
/**
* An optional array of additional [text edits](#TextEdit) that are applied when
* selecting this color presentation.
*/
additionalTextEdits?: TextEdit[];
}
/**
* A color range is a range in a text model which represents a color.
*/
export interface IColorRange {
export interface IColorInformation {
/**
* The range within the model.
*/
......@@ -4852,11 +4865,11 @@ declare module monaco.languages {
/**
* Provides the color ranges for a specific model.
*/
provideColorRanges(model: editor.IReadOnlyModel, token: CancellationToken): IColorRange[] | Thenable<IColorRange[]>;
provideDocumentColors(model: editor.IReadOnlyModel, token: CancellationToken): IColorInformation[] | Thenable<IColorInformation[]>;
/**
* Provide the string representation for a color.
* Provide the string representations for a color.
*/
resolveColor(color: IColor, colorFormat: ColorFormat, token: CancellationToken): string | Thenable<string>;
provideColorPresentations(colorInfo: IColorInformation, token: CancellationToken): IColorPresentation[] | Thenable<IColorPresentation[]>;
}
export interface IResourceEdit {
......
......@@ -104,19 +104,10 @@ declare module 'vscode' {
constructor(red: number, green: number, blue: number, alpha: number);
}
/**
* Represents a color format
*/
export enum ColorFormat {
RGB = 0,
HEX = 1,
HSL = 2
}
/**
* Represents a color range from a document.
*/
export class ColorRange {
export class ColorInformation {
/**
* The range in the document where this color appers.
......@@ -138,6 +129,26 @@ declare module 'vscode' {
constructor(range: Range, color: Color);
}
export class ColorPresentation {
/**
* The label of this color presentation. It will be shown on the color
* picker header. By default this is also the text that is inserted when selecting
* this color presentation.
*/
label: string;
/**
* An [edit](#TextEdit) which is applied to a document when selecting
* this presentation for the color. When `falsy` the [label](#ColorPresentation.label)
* is used.
*/
textEdit?: TextEdit;
/**
* An optional array of additional [text edits](#TextEdit) that are applied when
* selecting this color presentation. Edits must not overlap with the main [edit](#ColorPresentation.textEdit) nor with themselves.
*/
additionalTextEdits?: TextEdit[];
}
/**
* The document color provider defines the contract between extensions and feature of
* picking and modifying colors in the editor.
......@@ -148,14 +159,14 @@ declare module 'vscode' {
*
* @param document The document in which the command was invoked.
* @param token A cancellation token.
* @return An array of [color ranges](#ColorRange) or a thenable that resolves to such. The lack of a result
* @return An array of [color informations](#ColorInformation) or a thenable that resolves to such. The lack of a result
* can be signaled by returning `undefined`, `null`, or an empty array.
*/
provideDocumentColors(document: TextDocument, token: CancellationToken): ProviderResult<ColorRange[]>;
provideDocumentColors(document: TextDocument, token: CancellationToken): ProviderResult<ColorInformation[]>;
/**
* Provide the string representation for a color.
* Provide representations for a color.
*/
resolveDocumentColor(color: Color, colorFormat: ColorFormat): ProviderResult<string>;
provideColorPresentations(colorInfo: ColorInformation, token: CancellationToken): ProviderResult<ColorPresentation[]>;
}
export namespace languages {
......
......@@ -286,7 +286,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
$registerDocumentColorProvider(handle: number, selector: vscode.DocumentSelector): TPromise<any> {
const proxy = this._proxy;
this._registrations[handle] = modes.ColorProviderRegistry.register(selector, <modes.DocumentColorProvider>{
provideColorRanges: (model, token) => {
provideDocumentColors: (model, token) => {
return wireCancellationToken(token, proxy.$provideDocumentColors(handle, model.uri))
.then(documentColors => {
return documentColors.map(documentColor => {
......@@ -305,8 +305,12 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
});
});
},
resolveColor: (color, format, token) => {
return wireCancellationToken(token, proxy.$resolveDocumentColor(handle, color, format));
provideColorPresentations: (colorInfo, token) => {
return wireCancellationToken(token, proxy.$provideColorPresentations(handle, {
color: [colorInfo.color.red, colorInfo.color.green, colorInfo.color.blue, colorInfo.color.alpha],
range: colorInfo.range
}));
}
});
......
......@@ -551,8 +551,8 @@ export function createApiFactory(
CancellationTokenSource: CancellationTokenSource,
CodeLens: extHostTypes.CodeLens,
Color: extHostTypes.Color,
ColorFormat: extHostTypes.ColorFormat,
ColorRange: extHostTypes.ColorRange,
ColorPresentation: extHostTypes.ColorPresentation,
ColorInformation: extHostTypes.ColorInformation,
EndOfLine: extHostTypes.EndOfLine,
CompletionItem: extHostTypes.CompletionItem,
CompletionItemKind: extHostTypes.CompletionItemKind,
......
......@@ -548,7 +548,7 @@ export interface ExtHostLanguageFeaturesShape {
$provideDocumentLinks(handle: number, resource: URI): TPromise<modes.ILink[]>;
$resolveDocumentLink(handle: number, link: modes.ILink): TPromise<modes.ILink>;
$provideDocumentColors(handle: number, resource: URI): TPromise<IRawColorInfo[]>;
$resolveDocumentColor(handle: number, color: modes.IColor, colorFormat: modes.ColorFormat): TPromise<string>;
$provideColorPresentations(handle: number, colorInfo: IRawColorInfo): TPromise<modes.IColorPresentation[]>;
}
export interface ExtHostQuickOpenShape {
......
......@@ -729,8 +729,19 @@ class ColorProviderAdapter {
});
}
resolveColor(color: modes.IColor, colorFormat: modes.ColorFormat): TPromise<string> {
return asWinJsPromise(token => this._provider.resolveDocumentColor(color, colorFormat));
provideColorPresentations(rawColorInfo: IRawColorInfo): TPromise<modes.IColorPresentation[]> {
let colorInfo: vscode.ColorInformation = {
range: TypeConverters.toRange(rawColorInfo.range),
color: {
red: rawColorInfo.color[0],
green: rawColorInfo.color[1],
blue: rawColorInfo.color[2],
alpha: rawColorInfo.color[3]
}
};
return asWinJsPromise(token => this._provider.provideColorPresentations(colorInfo, token)).then(value => {
return value.map(v => TypeConverters.ColorPresentation.from(v));
});
}
}
......@@ -1042,8 +1053,8 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
return this._withAdapter(handle, ColorProviderAdapter, adapter => adapter.provideColors(resource));
}
$resolveDocumentColor(handle: number, color: modes.IColor, colorFormat: modes.ColorFormat): TPromise<string> {
return this._withAdapter(handle, ColorProviderAdapter, adapter => adapter.resolveColor(color, colorFormat));
$provideColorPresentations(handle: number, colorInfo: IRawColorInfo): TPromise<modes.IColorPresentation[]> {
return this._withAdapter(handle, ColorProviderAdapter, adapter => adapter.provideColorPresentations(colorInfo));
}
// --- configuration
......
......@@ -448,6 +448,24 @@ export namespace DocumentLink {
}
}
export namespace ColorPresentation {
export function to(colorPresentation: modes.IColorPresentation): vscode.ColorPresentation {
return {
label: colorPresentation.label,
textEdit: colorPresentation.textEdit ? TextEdit.to(colorPresentation.textEdit) : undefined,
additionalTextEdits: colorPresentation.additionalTextEdits ? colorPresentation.additionalTextEdits.map(value => TextEdit.to(value)) : undefined
};
}
export function from(colorPresentation: vscode.ColorPresentation): modes.IColorPresentation {
return {
label: colorPresentation.label,
textEdit: colorPresentation.textEdit ? TextEdit.from(colorPresentation.textEdit) : undefined,
additionalTextEdits: colorPresentation.additionalTextEdits ? colorPresentation.additionalTextEdits.map(value => TextEdit.from(value)) : undefined
};
}
}
export namespace TextDocumentSaveReason {
export function to(reason: SaveReason): vscode.TextDocumentSaveReason {
......
......@@ -1055,7 +1055,7 @@ export class Color {
export type IColorFormat = string | { opaque: string, transparent: string };
export class ColorRange {
export class ColorInformation {
range: Range;
color: Color;
......@@ -1072,6 +1072,12 @@ export class ColorRange {
}
}
export class ColorPresentation {
label: string;
textEdit?: TextEdit;
additionalTextEdits?: TextEdit[];
}
export enum ColorFormat {
RGB = 0,
HEX = 1,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册