Scenario
I have JSON files that describe a series of tasks to be carried out, where each task can refer to other tasks and objects in the JSON file.
{
"tasks": [
{ "id": "first", "action": "doSomething()", "result": {} },
{ "id": "second", "action": "doSomething(${id:first.result})", "result": {} },
]
}
I'd like to have both JSON schema validation and custom language text effects like keyword coloring and even "Go to definition" support within strings in the JSON.
What I can do
I can create an extension that specifies a JSON schema for a file extension "*.foo.json". This gives schema validation and code completion in the editor if vscode recognizes the file as a JSON file.
I can also create a new "foo" language in the extension for "*.foo.json" files that has custom keyword coloring within the JSON strings. I do this by creating a TextMate (*.tmLanguage.json) file copied from the JSON.tmLanguage.json, and then modifying the "stringcontent" definition.
Problem
The problem is that the schema validation and hints only work if I have "JSON" chosen in the status bar as the file type, and the custom text coloring only works if I have "foo" chosen in the status bar as the file type.
Is there any way to have both at once? Can I somehow extend the JSON language handling within vscode?
In Visual Studio Code, we have support for almost every major programming language. Several ship in the box, for example, JavaScript, TypeScript, CSS, and HTML but more rich language extensions can be found in the VS Code Marketplace.
1) You cannot have a Visual Studio project that uses multiple languages (unless you count ASM in C/C++). However, a single Visual Studio solution can have multiple projects where each project uses a different language.
With some help from the vscode team, the code below got things working.
package.json
...
"activationEvents": [
"onLanguage:json",
"onLanguage:jsonc"
],
"main": "./src/extension",
"dependencies": {
"jsonc": "^0.1.0",
"jsonc-parser": "^1.0.0",
"vscode-nls": "^3.2.1"
},
...
src/extension.js
'use strict';
const path = require( 'path' );
const vscode = require( 'vscode' );
const { getLocation, visit, parse, ParseError, ParseErrorCode } = require( 'jsonc-parser' );
module.exports = {
activate
};
let pendingFooJsonDecoration;
const decoration = vscode.window.createTextEditorDecorationType( {
color: '#04f1f9' // something like cyan
} );
// wire up *.foo.json decorations
function activate ( context /* vscode.ExtensionContext */) {
// decorate when changing the active editor editor
context.subscriptions.push( vscode.window.onDidChangeActiveTextEditor( editor => updateFooJsonDecorations( editor ), null, context.subscriptions ) );
// decorate when the document changes
context.subscriptions.push( vscode.workspace.onDidChangeTextDocument( event => {
if ( vscode.window.activeTextEditor && event.document === vscode.window.activeTextEditor.document ) {
if ( pendingFooJsonDecoration ) {
clearTimeout( pendingFooJsonDecoration );
}
pendingFooJsonDecoration = setTimeout( () => updateFooJsonDecorations( vscode.window.activeTextEditor ), 1000);
}
}, null, context.subscriptions ) );
// decorate the active editor now
updateFooJsonDecorations( vscode.window.activeTextEditor );
// decorate when then cursor moves
context.subscriptions.push( new EditorEventHandler() );
}
const substitutionRegex = /\$\{[\w\:\.]+\}/g;
function updateFooJsonDecorations ( editor /* vscode.TextEditor */ ) {
if ( !editor || !path.basename( editor.document.fileName ).endsWith( '.foo.json' ) ) {
return;
}
const ranges /* vscode.Range[] */ = [];
visit( editor.document.getText(), {
onLiteralValue: ( value, offset, length ) => {
const matches = [];
let match;
while ( ( match = substitutionRegex.exec( value ) ) !== null) {
matches.push( match );
const start = offset + match.index + 1;
const end = match.index + 1 + offset + match[ 0 ].length;
ranges.push( new vscode.Range( editor.document.positionAt( start ), editor.document.positionAt( end ) ) );
}
}
});
editor.setDecorations( decoration, ranges );
}
class EditorEventHandler {
constructor () {
let subscriptions /*: Disposable[] */ = [];
vscode.window.onDidChangeTextEditorSelection( ( e /* TextEditorSelectionChangeEvent */ ) => {
if ( e.textEditor === vscode.window.activeTextEditor) {
updateFooJsonDecorations( e.textEditor );
}
}, this, subscriptions );
this._disposable = vscode.Disposable.from( ...subscriptions );
}
dispose () {
this._disposable.dispose();
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With