[json] color decorators

This commit is contained in:
Martin Aeschlimann 2017-03-27 14:26:56 +02:00
parent dc009ee150
commit 7d63b291cf
9 changed files with 202 additions and 10 deletions

View file

@ -0,0 +1,156 @@
/*---------------------------------------------------------------------------------------------
* 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 { window, workspace, DecorationOptions, DecorationRenderOptions, Disposable, Range, TextDocument } from 'vscode';
const MAX_DECORATORS = 500;
let decorationType: DecorationRenderOptions = {
before: {
contentText: ' ',
border: 'solid 0.1em #000',
margin: '0.1em 0.2em 0 0.2em',
width: '0.8em',
height: '0.8em'
},
dark: {
before: {
border: 'solid 0.1em #eee'
}
}
};
export function activateColorDecorations(decoratorProvider: (uri: string) => Thenable<Range[]>, supportedLanguages: { [id: string]: boolean }, isDecoratorEnabled: (languageId: string) => boolean): Disposable {
let disposables: Disposable[] = [];
let colorsDecorationType = window.createTextEditorDecorationType(decorationType);
disposables.push(colorsDecorationType);
let decoratorEnablement = {};
for (let languageId in supportedLanguages) {
decoratorEnablement[languageId] = isDecoratorEnabled(languageId);
}
let pendingUpdateRequests: { [key: string]: NodeJS.Timer; } = {};
window.onDidChangeVisibleTextEditors(editors => {
for (let editor of editors) {
triggerUpdateDecorations(editor.document);
}
}, null, disposables);
workspace.onDidChangeTextDocument(event => triggerUpdateDecorations(event.document), null, disposables);
// track open and close for document languageId changes
workspace.onDidCloseTextDocument(event => triggerUpdateDecorations(event, true));
workspace.onDidOpenTextDocument(event => triggerUpdateDecorations(event));
workspace.onDidChangeConfiguration(_ => {
let hasChanges = false;
for (let languageId in supportedLanguages) {
let prev = decoratorEnablement[languageId];
let curr = isDecoratorEnabled(languageId);
if (prev !== curr) {
decoratorEnablement[languageId] = curr;
hasChanges = true;
}
}
if (hasChanges) {
updateAllVisibleEditors(true);
}
}, null, disposables);
updateAllVisibleEditors(false);
function updateAllVisibleEditors(settingsChanges: boolean) {
window.visibleTextEditors.forEach(editor => {
if (editor.document) {
triggerUpdateDecorations(editor.document, settingsChanges);
}
});
}
function triggerUpdateDecorations(document: TextDocument, settingsChanges = false) {
let triggerUpdate = supportedLanguages[document.languageId] && (decoratorEnablement[document.languageId] || settingsChanges);
if (triggerUpdate) {
let documentUriStr = document.uri.toString();
let timeout = pendingUpdateRequests[documentUriStr];
if (typeof timeout !== 'undefined') {
clearTimeout(timeout);
}
pendingUpdateRequests[documentUriStr] = setTimeout(() => {
// check if the document is in use by an active editor
for (let editor of window.visibleTextEditors) {
if (editor.document && documentUriStr === editor.document.uri.toString()) {
if (decoratorEnablement[editor.document.languageId]) {
updateDecorationForEditor(documentUriStr, editor.document.version);
break;
} else {
editor.setDecorations(colorsDecorationType, []);
}
}
}
delete pendingUpdateRequests[documentUriStr];
}, 500);
}
}
function updateDecorationForEditor(contentUri: string, documentVersion: number) {
decoratorProvider(contentUri).then(ranges => {
for (let editor of window.visibleTextEditors) {
let document = editor.document;
if (document && document.version === documentVersion && contentUri === document.uri.toString()) {
let decorations = [];
for (let i = 0; i < ranges.length && decorations.length < MAX_DECORATORS; i++) {
let range = ranges[i];
let text = document.getText(range);
let value = <string>JSON.parse(text);
let color = hex2rgba(value);
if (color) {
decorations.push(<DecorationOptions>{
range: range,
renderOptions: {
before: {
backgroundColor: color
}
}
});
}
}
editor.setDecorations(colorsDecorationType, decorations);
}
}
});
}
return Disposable.from(...disposables);
}
const CharCode_Hash = 35;
function hex2rgba(hex: string): string {
if (!hex) {
return null;
}
if (hex.length === 7 && hex.charCodeAt(0) === CharCode_Hash) {
// #RRGGBB format
return hex;
}
if (hex.length === 9 && hex.charCodeAt(0) === CharCode_Hash) {
// #RRGGBBAA format
var val = parseInt(hex.substr(1), 16);
var r = (val >> 24) & 255;
var g = (val >> 16) & 255;
var b = (val >> 8) & 255;
var a = val & 255;
return `rgba(${r}, ${g}, ${b}, ${+(a / 255).toFixed(2)})`;
}
return null;
}

View file

@ -6,9 +6,10 @@
import * as path from 'path';
import { workspace, languages, ExtensionContext, extensions, Uri } from 'vscode';
import { workspace, languages, ExtensionContext, extensions, Uri, Range } from 'vscode';
import { LanguageClient, LanguageClientOptions, RequestType, ServerOptions, TransportKind, NotificationType } from 'vscode-languageclient';
import TelemetryReporter from 'vscode-extension-telemetry';
import { activateColorDecorations } from "./colorDecorators";
import * as nls from 'vscode-nls';
let localize = nls.loadMessageBundle();
@ -17,6 +18,10 @@ namespace VSCodeContentRequest {
export const type: RequestType<string, string, any, any> = new RequestType('vscode/content');
}
namespace ColorSymbolRequest {
export const type: RequestType<string, Range[], any, any> = new RequestType('json/colorSymbols');
}
export interface ISchemaAssociations {
[pattern: string]: string[];
}
@ -81,6 +86,15 @@ export function activate(context: ExtensionContext) {
});
client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociation(context));
let colorRequestor = (uri: string) => {
return client.sendRequest(ColorSymbolRequest.type, uri).then(ranges => ranges.map(client.protocol2CodeConverter.asRange));
};
let isDecoratorEnabled = (languageId: string) => {
return workspace.getConfiguration().get<boolean>(languageId + '.colorDecorators.enable');
};
disposable = activateColorDecorations(colorRequestor, { json: true }, isDecoratorEnabled);
context.subscriptions.push(disposable);
});
// Push the disposable to the context's subscriptions so that the

View file

@ -106,7 +106,12 @@
"verbose"
],
"default": "off",
"description": "Traces the communication between VS Code and the JSON language server."
"description": "%json.tracing.desc%"
},
"json.colorDecorators.enable": {
"type": "boolean",
"default": true,
"description": "%json.colorDecorators.enable.desc%"
}
}
},

View file

@ -4,5 +4,7 @@
"json.schemas.fileMatch.desc": "An array of file patterns to match against when resolving JSON files to schemas.",
"json.schemas.fileMatch.item.desc": "A file pattern that can contain '*' to match against when resolving JSON files to schemas.",
"json.schemas.schema.desc": "The schema definition for the given URL. The schema only needs to be provided to avoid accesses to the schema URL.",
"json.format.enable.desc": "Enable/disable default JSON formatter (requires restart)"
"json.format.enable.desc": "Enable/disable default JSON formatter (requires restart)",
"json.tracing.desc": "Traces the communication between VS Code and the JSON language server.",
"json.colorDecorators.enable.desc": "Enables or disables color decorators"
}

View file

@ -43,9 +43,9 @@
"resolved": "https://registry.npmjs.org/request-light/-/request-light-0.2.0.tgz"
},
"vscode-json-languageservice": {
"version": "2.0.4",
"version": "2.0.5",
"from": "vscode-json-languageservice@next",
"resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-2.0.4.tgz"
"resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-2.0.5.tgz"
},
"vscode-jsonrpc": {
"version": "3.1.0-alpha.1",

View file

@ -10,7 +10,7 @@
"dependencies": {
"jsonc-parser": "^0.4.0",
"request-light": "^0.2.0",
"vscode-json-languageservice": "^2.0.4",
"vscode-json-languageservice": "^2.0.5",
"vscode-languageserver": "^3.1.0-alpha.1",
"vscode-nls": "^2.0.2"
},

View file

@ -7,7 +7,7 @@
import {
createConnection, IConnection,
TextDocuments, TextDocument, InitializeParams, InitializeResult, NotificationType, RequestType,
DocumentRangeFormattingRequest, Disposable
DocumentRangeFormattingRequest, Disposable, Range
} from 'vscode-languageserver';
import { xhr, XHRResponse, configure as configureHttpRequests, getErrorStatusDescription } from 'request-light';
@ -34,6 +34,10 @@ namespace VSCodeContentRequest {
export const type: RequestType<string, string, any, any> = new RequestType('vscode/content');
}
namespace ColorSymbolRequest {
export const type: RequestType<string, Range[], any, any> = new RequestType('json/colorSymbols');
}
// Create a connection for the server
let connection: IConnection = createConnection();
@ -304,5 +308,14 @@ connection.onDocumentRangeFormatting(formatParams => {
return languageService.format(document, formatParams.range, formatParams.options);
});
connection.onRequest(ColorSymbolRequest.type, uri => {
let document = documents.get(uri);
if (document) {
let jsonDocument = getJSONDocument(document);
return languageService.findColorSymbols(document, jsonDocument);
}
return [];
});
// Listen on the connection
connection.listen();

View file

@ -80,7 +80,7 @@ class ColorRegistry implements IColorRegistry {
public registerColor(id: string, defaults: ColorDefaults, description: string): ColorIdentifier {
let colorContribution = { id, description, defaults };
this.colorsById[id] = colorContribution;
this.colorSchema.properties[id] = { type: 'string', description };
this.colorSchema.properties[id] = { type: 'string', description, format: 'color' };
return id;
}

View file

@ -151,10 +151,12 @@ export const tokenColorsSchema = {
type: 'object',
properties: {
foreground: {
type: 'string'
type: 'string',
format: 'color'
},
background: {
type: 'string'
type: 'string',
format: 'color'
},
fontStyle: {
type: 'string',