Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can a language in Visual Studio Code be extended?

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?

like image 975
Matt Miller Avatar asked Feb 10 '18 16:02

Matt Miller


People also ask

Can VS Code run all languages?

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.

Can Visual Studio run multiple languages?

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.


1 Answers

With some help from the vscode team, the code below got things working.

Syntax highlighting within a JSON string literal

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();
    }
}
like image 166
Matt Miller Avatar answered Oct 18 '22 01:10

Matt Miller