[json] color decorators
This commit is contained in:
parent
dc009ee150
commit
7d63b291cf
9 changed files with 202 additions and 10 deletions
156
extensions/json/client/src/colorDecorators.ts
Normal file
156
extensions/json/client/src/colorDecorators.ts
Normal 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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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%"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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"
|
||||
}
|
4
extensions/json/server/npm-shrinkwrap.json
generated
4
extensions/json/server/npm-shrinkwrap.json
generated
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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();
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
Loading…
Reference in a new issue