1
0
Martin Aeschlimann 9 жил өмнө
parent
commit
50e0c8d70c

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+/node_modules/
+/out/
+/release/

+ 8 - 0
.npmignore

@@ -0,0 +1,8 @@
+/.vscode/
+/lib/
+/out/
+/src/
+/test/
+/release/dev/
+/gulpfile.js
+/.npmignore

+ 9 - 0
.vscode/settings.json

@@ -0,0 +1,9 @@
+// Place your settings in this file to overwrite default and user settings.
+{
+	"files.trimTrailingWhitespace": true,
+	"search.exclude": {
+		"**/node_modules": true,
+		"**/release": true,
+		"**/out": true
+	}
+}

+ 21 - 0
LICENSE.md

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) Microsoft Corporation
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 11 - 2
README.md

@@ -1,2 +1,11 @@
-# monaco-json
-JSON language support for the Monaco editor
+# Monaco JSON
+
+JSON language plugin for the Monaco Editor.
+
+
+## Installing
+
+This npm module is bundled and distributed in the [monaco-editor](https://www.npmjs.com/package/monaco-editor) npm module.
+
+## License
+[MIT](https://github.com/Microsoft/monaco-json/blob/master/LICENSE.md)

+ 225 - 0
gulpfile.js

@@ -0,0 +1,225 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+var gulp = require('gulp');
+var tsb = require('gulp-tsb');
+var assign = require('object-assign');
+var fs = require('fs');
+var path = require('path');
+var merge = require('merge-stream');
+var rjs = require('gulp-requirejs');
+var uglify = require('gulp-uglify');
+var rimraf = require('rimraf');
+var es = require('event-stream');
+
+gulp.task('clean-release', function(cb) { rimraf('release', { maxBusyTries: 1 }, cb); });
+gulp.task('release', ['clean-release','compile'], function() {
+
+	var sha1 = getGitVersion(__dirname);
+	var semver = require('./package.json').version;
+	var headerVersion = semver + '(' + sha1 + ')';
+
+	var BUNDLED_FILE_HEADER = [
+		'/*!-----------------------------------------------------------------------------',
+		' * Copyright (c) Microsoft Corporation. All rights reserved.',
+		' * monaco-json version: ' + headerVersion,
+		' * Released under the MIT license',
+		' * https://github.com/Microsoft/monaco-json/blob/master/LICENSE.md',
+		' *-----------------------------------------------------------------------------*/',
+		''
+	].join('\n');
+
+	function bundleOne(moduleId, exclude) {
+		return rjs({
+			baseUrl: '/out/',
+			name: 'vs/language/json/' + moduleId,
+			out: moduleId + '.js',
+			exclude: exclude,
+			paths: {
+				'vs/language/json': __dirname + '/out'
+			},
+			packages: [{
+				name: 'vscode-json-languageservice',
+				location: __dirname + '/node_modules/vscode-json-languageservice/lib',
+				main: 'jsonLanguageService'
+			}, {
+				name: 'vscode-languageserver-types',
+				location: __dirname + '/node_modules/vscode-languageserver-types/lib',
+				main: 'main'
+			}, {
+				name: 'jsonc-parser',
+				location: __dirname + '/node_modules/jsonc-parser/lib',
+				main: 'main'
+			}, {
+				name: 'vscode-nls',
+				location: __dirname + '/out/fillers',
+				main: 'vscode-nls'
+			}]
+		})
+	}
+
+	return merge(
+		merge(
+			bundleOne('monaco.contribution', ['vs/language/json/jsonMode']),
+			bundleOne('jsonMode'),
+			bundleOne('jsonWorker')
+		)
+		.pipe(es.through(function(data) {
+			data.contents = new Buffer(
+				BUNDLED_FILE_HEADER
+				+ data.contents.toString()
+			);
+			this.emit('data', data);
+		}))
+		.pipe(gulp.dest('./release/dev'))
+		.pipe(uglify({
+			preserveComments: 'some'
+		}))
+		.pipe(gulp.dest('./release/min')),
+		gulp.src('src/monaco.d.ts').pipe(gulp.dest('./release/min'))
+	);
+});
+
+
+var compilation = tsb.create(assign({ verbose: true }, require('./src/tsconfig.json').compilerOptions));
+
+var tsSources = 'src/**/*.ts';
+
+function compileTask() {
+	return merge(
+		gulp.src(tsSources).pipe(compilation())
+	)
+	.pipe(gulp.dest('out'));
+}
+
+gulp.task('clean-out', function(cb) { rimraf('out', { maxBusyTries: 1 }, cb); });
+gulp.task('compile', ['clean-out'], compileTask);
+gulp.task('compile-without-clean', compileTask);
+gulp.task('watch', ['compile'], function() {
+	gulp.watch(tsSources, ['compile-without-clean']);
+});
+
+
+/**
+ * Escape text such that it can be used in a javascript string enclosed by double quotes (")
+ */
+function escapeText(text) {
+	// http://www.javascriptkit.com/jsref/escapesequence.shtml
+	// \b	Backspace.
+	// \f	Form feed.
+	// \n	Newline.
+	// \O	Nul character.
+	// \r	Carriage return.
+	// \t	Horizontal tab.
+	// \v	Vertical tab.
+	// \'	Single quote or apostrophe.
+	// \"	Double quote.
+	// \\	Backslash.
+	// \ddd	The Latin-1 character specified by the three octal digits between 0 and 377. ie, copyright symbol is \251.
+	// \xdd	The Latin-1 character specified by the two hexadecimal digits dd between 00 and FF.  ie, copyright symbol is \xA9.
+	// \udddd	The Unicode character specified by the four hexadecimal digits dddd. ie, copyright symbol is \u00A9.
+	var _backspace = '\b'.charCodeAt(0);
+	var _formFeed = '\f'.charCodeAt(0);
+	var _newLine = '\n'.charCodeAt(0);
+	var _nullChar = 0;
+	var _carriageReturn = '\r'.charCodeAt(0);
+	var _tab = '\t'.charCodeAt(0);
+	var _verticalTab = '\v'.charCodeAt(0);
+	var _backslash = '\\'.charCodeAt(0);
+	var _doubleQuote = '"'.charCodeAt(0);
+
+	var startPos = 0, chrCode, replaceWith = null, resultPieces = [];
+
+	for (var i = 0, len = text.length; i < len; i++) {
+		chrCode = text.charCodeAt(i);
+		switch (chrCode) {
+			case _backspace:
+				replaceWith = '\\b';
+				break;
+			case _formFeed:
+				replaceWith = '\\f';
+				break;
+			case _newLine:
+				replaceWith = '\\n';
+				break;
+			case _nullChar:
+				replaceWith = '\\0';
+				break;
+			case _carriageReturn:
+				replaceWith = '\\r';
+				break;
+			case _tab:
+				replaceWith = '\\t';
+				break;
+			case _verticalTab:
+				replaceWith = '\\v';
+				break;
+			case _backslash:
+				replaceWith = '\\\\';
+				break;
+			case _doubleQuote:
+				replaceWith = '\\"';
+				break;
+		}
+		if (replaceWith !== null) {
+			resultPieces.push(text.substring(startPos, i));
+			resultPieces.push(replaceWith);
+			startPos = i + 1;
+			replaceWith = null;
+		}
+	}
+	resultPieces.push(text.substring(startPos, len));
+	return resultPieces.join('');
+}
+
+function getGitVersion(repo) {
+	var git = path.join(repo, '.git');
+	var headPath = path.join(git, 'HEAD');
+	var head;
+
+	try {
+		head = fs.readFileSync(headPath, 'utf8').trim();
+	} catch (e) {
+		return void 0;
+	}
+
+	if (/^[0-9a-f]{40}$/i.test(head)) {
+		return head;
+	}
+
+	var refMatch = /^ref: (.*)$/.exec(head);
+
+	if (!refMatch) {
+		return void 0;
+	}
+
+	var ref = refMatch[1];
+	var refPath = path.join(git, ref);
+
+	try {
+		return fs.readFileSync(refPath, 'utf8').trim();
+	} catch (e) {
+		// noop
+	}
+
+	var packedRefsPath = path.join(git, 'packed-refs');
+	var refsRaw;
+
+	try {
+		refsRaw = fs.readFileSync(packedRefsPath, 'utf8').trim();
+	} catch (e) {
+		return void 0;
+	}
+
+	var refsRegex = /^([0-9a-f]{40})\s+(.+)$/gm;
+	var refsMatch;
+	var refs = {};
+
+	while (refsMatch = refsRegex.exec(refsRaw)) {
+		refs[refsMatch[2]] = refsMatch[1];
+	}
+
+	return refs[ref];
+}

+ 35 - 0
package.json

@@ -0,0 +1,35 @@
+{
+  "name": "monaco-json",
+  "version": "1.0.0",
+  "description": "JSON plugin for the Monaco Editor",
+  "scripts": {
+    "compile": "gulp compile",
+    "watch": "gulp watch",
+    "prepublish": "gulp release"
+  },
+  "author": "Microsoft Corporation",
+  "license": "MIT",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/Microsoft/monaco-json"
+  },
+  "bugs": {
+    "url": "https://github.com/Microsoft/monaco-json/issues"
+  },
+  "dependencies": {
+    "vscode-json-languageservice": "^1.0.0",
+    "vscode-languageserver-types": "^1.0.1"
+  },
+  "devDependencies": {
+    "event-stream": "^3.3.2",
+    "gulp": "^3.9.1",
+    "gulp-requirejs": "^0.1.3",
+    "gulp-tsb": "^1.10.4",
+    "gulp-uglify": "^1.5.3",
+    "merge-stream": "^1.0.0",
+    "monaco-editor-core": "0.5.1",
+    "object-assign": "^4.1.0",
+    "rimraf": "^2.5.2",
+    "typescript": "1.8.10"
+  }
+}

+ 34 - 0
src/fillers/vscode-nls.ts

@@ -0,0 +1,34 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import {LocalizeInfo, LocalizeFunc, Options, LoadFunc} from 'vscode-nls';
+
+export {LocalizeInfo, LocalizeFunc, Options, LoadFunc};
+
+function format(message: string, args: any[]): string {
+	let result:string;
+
+	if (args.length === 0) {
+		result = message;
+	} else {
+		result = message.replace(/\{(\d+)\}/g, (match, rest) => {
+			let index = rest[0];
+			return typeof args[index] !== 'undefined' ? args[index] : match;
+		});
+	}
+	return result;
+}
+
+function localize(key: string | LocalizeInfo, message: string, ...args: any[]): string {
+	return format(message, args);
+}
+
+export function loadMessageBundle(file?: string): LocalizeFunc {
+    return localize;
+}
+
+export function config(opt?: Options | string): LoadFunc {
+    return loadMessageBundle;
+}

+ 60 - 0
src/jsonMode.ts

@@ -0,0 +1,60 @@
+/*---------------------------------------------------------------------------------------------
+ *  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 {WorkerManager} from './workerManager';
+import {JSONWorker} from './jsonWorker';
+import {LanguageServiceDefaultsImpl} from './monaco.contribution';
+import * as languageFeatures from './languageFeatures';
+import {createTokenizationSupport} from './tokenization';
+
+import Promise = monaco.Promise;
+import Uri = monaco.Uri;
+import IDisposable = monaco.IDisposable;
+
+export function setupMode(defaults: LanguageServiceDefaultsImpl): void {
+
+	let disposables: IDisposable[] = [];
+
+	const client = new WorkerManager(defaults);
+	disposables.push(client);
+
+	const worker: languageFeatures.WorkerAccessor = (...uris: Uri[]): Promise<JSONWorker> => {
+		return client.getLanguageServiceWorker(...uris);
+	};
+
+	let languageId = defaults.languageId;
+
+	disposables.push(monaco.languages.registerCompletionItemProvider(languageId, new languageFeatures.CompletionAdapter(worker)));
+	disposables.push(monaco.languages.registerHoverProvider(languageId, new languageFeatures.HoverAdapter(worker)));
+	disposables.push(monaco.languages.registerDocumentSymbolProvider(languageId, new languageFeatures.DocumentSymbolAdapter(worker)));
+	disposables.push(monaco.languages.registerDocumentFormattingEditProvider(languageId, new languageFeatures.DocumentFormattingEditProvider(worker)));
+	disposables.push(monaco.languages.registerDocumentRangeFormattingEditProvider(languageId, new languageFeatures.DocumentRangeFormattingEditProvider(worker)));
+	disposables.push(new languageFeatures.DiagnostcsAdapter(languageId, worker));
+	disposables.push(monaco.languages.setTokensProvider(languageId, createTokenizationSupport(true)));
+	disposables.push(monaco.languages.setLanguageConfiguration(languageId, richEditConfiguration));
+}
+
+
+const richEditConfiguration: monaco.languages.LanguageConfiguration = {
+	wordPattern: /(-?\d*\.\d\w*)|([^\[\{\]\}\:\"\,\s]+)/g,
+
+	comments: {
+		lineComment: '//',
+		blockComment: ['/*', '*/']
+	},
+
+	brackets: [
+		['{', '}'],
+		['[', ']']
+	],
+
+	autoClosingPairs: [
+		{ open: '{', close: '}', notIn: ['string'] },
+		{ open: '[', close: ']', notIn: ['string'] },
+		{ open: '"', close: '"', notIn: ['string'] }
+	]
+};
+

+ 114 - 0
src/jsonWorker.ts

@@ -0,0 +1,114 @@
+/*---------------------------------------------------------------------------------------------
+ *  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 Promise = monaco.Promise;
+import Thenable = monaco.Thenable;
+import IWorkerContext = monaco.worker.IWorkerContext;
+
+import * as jsonService from 'vscode-json-languageservice';
+import * as ls from 'vscode-languageserver-types';
+
+
+class PromiseAdapter<T> implements jsonService.Thenable<T> {
+	private wrapped: monaco.Promise<T>;
+
+	constructor(executor: (resolve: (value?: T | jsonService.Thenable<T>) => void, reject: (reason?: any) => void) => void) {
+		this.wrapped = new monaco.Promise<T>(executor);
+	}
+	public then<TResult>(onfulfilled?: (value: T) => TResult | jsonService.Thenable<TResult>, onrejected?: (reason: any) => void): jsonService.Thenable<TResult> {
+		return this.wrapped.then(onfulfilled, onrejected);
+	}
+	public getWrapped(): monaco.Promise<T> {
+		return this.wrapped;
+	}
+	public cancel() : void {
+		this.wrapped.cancel();
+	}
+
+	public static resolve<T>(v: T) : jsonService.Thenable<T> {
+		return monaco.Promise.as(v);
+	}
+	public static reject<T>(v: T) : jsonService.Thenable<T> {
+		return monaco.Promise.wrapError(v);
+	}
+	public static all<T>(values: jsonService.Thenable<T>[]): jsonService.Thenable<T[]> {
+		return monaco.Promise.join(values);
+	}
+}
+
+function toMonacoPromise<R>(thenable: jsonService.Thenable<R>) : Thenable<R> {
+	if (thenable instanceof PromiseAdapter) {
+		return thenable.getWrapped();
+	}
+	return thenable;
+}
+
+export class JSONWorker {
+
+	private _ctx:IWorkerContext;
+	private _languageService: jsonService.LanguageService;
+	private _languageSettings: jsonService.LanguageSettings;
+	private _languageId: string;
+
+	constructor(ctx:IWorkerContext, createData: ICreateData) {
+		this._ctx = ctx;
+		this._languageSettings = createData.languageSettings;
+		this._languageId = createData.languageId;
+		this._languageService = jsonService.getLanguageService({ promiseConstructor: PromiseAdapter });
+		this._languageService.configure(this._languageSettings);
+	}
+
+    doValidation(uri: string): Thenable<ls.Diagnostic[]> {
+		let document = this._getTextDocument(uri);
+		let jsonDocument = this._languageService.parseJSONDocument(document);
+		return this._languageService.doValidation(document, jsonDocument);
+	}
+    doComplete(uri: string, position: ls.Position): Thenable<ls.CompletionList> {
+		let document = this._getTextDocument(uri);
+		let jsonDocument = this._languageService.parseJSONDocument(document);
+		return this._languageService.doComplete(document, position, jsonDocument);
+	}
+    doResolve(item: ls.CompletionItem): Thenable<ls.CompletionItem> {
+		return this._languageService.doResolve(item);
+	}
+    doHover(uri: string, position: ls.Position): Thenable<ls.Hover> {
+		let document = this._getTextDocument(uri);
+		let jsonDocument = this._languageService.parseJSONDocument(document);
+		return this._languageService.doHover(document, position, jsonDocument);
+	}
+    format(uri: string, range: ls.Range, options: ls.FormattingOptions): Thenable<ls.TextEdit[]> {
+		let document = this._getTextDocument(uri);
+		let textEdits = this._languageService.format(document, range, options);
+		return Promise.as(textEdits);
+	}
+    resetSchema(uri: string): Thenable<boolean> {
+		return Promise.as(this._languageService.resetSchema(uri));
+	}
+    findDocumentSymbols(uri: string): Promise<ls.SymbolInformation[]> {
+		let document = this._getTextDocument(uri);
+		let jsonDocument = this._languageService.parseJSONDocument(document);
+		let symbols = this._languageService.findDocumentSymbols(document, jsonDocument);
+		return Promise.as(symbols);
+	}
+	private _getTextDocument(uri: string): ls.TextDocument {
+		let models = this._ctx.getMirrorModels();
+		for (let model of models) {
+			if (model.uri.toString() === uri) {
+				return ls.TextDocument.create(uri, this._languageId, model.version, model.getValue());
+			}
+		}
+		return null;
+	}
+}
+
+export interface ICreateData {
+	languageId: string;
+	languageSettings: jsonService.LanguageSettings;
+}
+
+export function create(ctx:IWorkerContext, createData: ICreateData): JSONWorker {
+	return new JSONWorker(ctx, createData);
+}

+ 451 - 0
src/languageFeatures.ts

@@ -0,0 +1,451 @@
+/*---------------------------------------------------------------------------------------------
+ *  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 {LanguageServiceDefaultsImpl} from './monaco.contribution';
+import {JSONWorker} from './jsonWorker';
+
+import * as ls from 'vscode-languageserver-types';
+
+import Uri = monaco.Uri;
+import Position = monaco.Position;
+import Range = monaco.Range;
+import Thenable = monaco.Thenable;
+import Promise = monaco.Promise;
+import CancellationToken = monaco.CancellationToken;
+import IDisposable = monaco.IDisposable;
+
+
+export interface WorkerAccessor {
+	(...more: Uri[]): Thenable<JSONWorker>
+}
+
+// --- diagnostics --- ---
+
+export class DiagnostcsAdapter {
+
+	private _disposables: IDisposable[] = [];
+	private _listener: { [uri: string]: IDisposable } = Object.create(null);
+
+	constructor(private _languageId: string, private _worker: WorkerAccessor) {
+		const onModelAdd = (model: monaco.editor.IModel): void => {
+			let modeId = model.getModeId();
+			if (modeId !== this._languageId) {
+				return;
+			}
+
+			let handle: number;
+			this._listener[model.uri.toString()] = model.onDidChangeContent(() => {
+				clearTimeout(handle);
+				handle = setTimeout(() => this._doValidate(model.uri, modeId), 500);
+			});
+
+			this._doValidate(model.uri, modeId);
+		};
+
+		const onModelRemoved = (model: monaco.editor.IModel): void => {
+			delete this._listener[model.uri.toString()];
+		};
+
+		this._disposables.push(monaco.editor.onDidCreateModel(onModelAdd));
+		this._disposables.push(monaco.editor.onWillDisposeModel(model => {
+			onModelRemoved(model);
+			this._resetSchema(model.uri);
+		}));
+		this._disposables.push(monaco.editor.onDidChangeModelLanguage(event => {
+			onModelRemoved(event.model);
+			onModelAdd(event.model);
+			this._resetSchema(event.model.uri);
+		}));
+
+		this._disposables.push({
+			dispose: () => {
+				for (let key in this._listener) {
+					this._listener[key].dispose();
+				}
+			}
+		});
+
+		monaco.editor.getModels().forEach(onModelAdd);
+	}
+
+	public dispose(): void {
+		this._disposables.forEach(d => d && d.dispose());
+		this._disposables = [];
+	}
+
+	private _resetSchema(resource: Uri): void {
+		this._worker().then(worker => {
+			worker.resetSchema(resource.toString());
+		});
+	}
+
+	private _doValidate(resource: Uri, languageId: string): void {
+		this._worker(resource).then(worker => {
+			return worker.doValidation(resource.toString()).then(diagnostics => {
+				const markers = diagnostics.map(d => toDiagnostics(resource, d));
+				monaco.editor.setModelMarkers(monaco.editor.getModel(resource), languageId, markers);
+			});
+		}).then(undefined, err => {
+			console.error(err);
+		});
+	}
+}
+
+
+function toSeverity(lsSeverity: number): monaco.Severity {
+	switch (lsSeverity) {
+		case ls.DiagnosticSeverity.Error: return monaco.Severity.Error;
+		case ls.DiagnosticSeverity.Warning: return monaco.Severity.Warning;
+		case ls.DiagnosticSeverity.Information:
+		case ls.DiagnosticSeverity.Hint:
+		default:
+			return monaco.Severity.Info;
+	}
+}
+
+function toDiagnostics(resource: Uri, diag: ls.Diagnostic): monaco.editor.IMarkerData {
+	let code = typeof diag.code === 'number' ? String(diag.code) : <string>diag.code;
+
+	return {
+		severity: toSeverity(diag.severity),
+		startLineNumber: diag.range.start.line + 1,
+		startColumn: diag.range.start.character + 1,
+		endLineNumber: diag.range.end.line + 1,
+		endColumn: diag.range.end.character + 1,
+		message: diag.message,
+		code: code,
+		source: diag.source
+	};
+}
+
+// --- completion ------
+
+function fromPosition(position: Position): ls.Position {
+	if (!position) {
+		return void 0;
+	}
+	return { character: position.column - 1, line: position.lineNumber - 1 };
+}
+
+function fromRange(range: Range): ls.Range {
+	if (!range) {
+		return void 0;
+	}
+	return { start: fromPosition(range.getStartPosition()), end: fromPosition(range.getEndPosition()) };
+}
+
+function toRange(range: ls.Range): Range {
+	if (!range) {
+		return void 0;
+	}
+	return new Range(range.start.line + 1, range.start.character + 1, range.end.line + 1, range.end.character + 1);
+}
+
+function toCompletionItemKind(kind: number): monaco.languages.CompletionItemKind {
+	let mItemKind = monaco.languages.CompletionItemKind;
+
+	switch (kind) {
+		case ls.CompletionItemKind.Text: return mItemKind.Text;
+		case ls.CompletionItemKind.Method: return mItemKind.Method;
+		case ls.CompletionItemKind.Function: return mItemKind.Function;
+		case ls.CompletionItemKind.Constructor: return mItemKind.Constructor;
+		case ls.CompletionItemKind.Field: return mItemKind.Field;
+		case ls.CompletionItemKind.Variable: return mItemKind.Variable;
+		case ls.CompletionItemKind.Class: return mItemKind.Class;
+		case ls.CompletionItemKind.Interface: return mItemKind.Interface;
+		case ls.CompletionItemKind.Module: return mItemKind.Module;
+		case ls.CompletionItemKind.Property: return mItemKind.Property;
+		case ls.CompletionItemKind.Unit: return mItemKind.Unit;
+		case ls.CompletionItemKind.Value: return mItemKind.Value;
+		case ls.CompletionItemKind.Enum: return mItemKind.Enum;
+		case ls.CompletionItemKind.Keyword: return mItemKind.Keyword;
+		case ls.CompletionItemKind.Snippet: return mItemKind.Snippet;
+		case ls.CompletionItemKind.Color: return mItemKind.Color;
+		case ls.CompletionItemKind.File: return mItemKind.File;
+		case ls.CompletionItemKind.Reference: return mItemKind.Reference;
+	}
+	return mItemKind.Property;
+}
+
+function fromCompletionItemKind(kind: monaco.languages.CompletionItemKind): ls.CompletionItemKind {
+	let mItemKind = monaco.languages.CompletionItemKind;
+
+	switch (kind) {
+		case mItemKind.Text: return ls.CompletionItemKind.Text;
+		case mItemKind.Method: return ls.CompletionItemKind.Method;
+		case mItemKind.Function: return ls.CompletionItemKind.Function;
+		case mItemKind.Constructor: return ls.CompletionItemKind.Constructor;
+		case mItemKind.Field: return ls.CompletionItemKind.Field;
+		case mItemKind.Variable: return ls.CompletionItemKind.Variable;
+		case mItemKind.Class: return ls.CompletionItemKind.Class;
+		case mItemKind.Interface: return ls.CompletionItemKind.Interface;
+		case mItemKind.Module: return ls.CompletionItemKind.Module;
+		case mItemKind.Property: return ls.CompletionItemKind.Property;
+		case mItemKind.Unit: return ls.CompletionItemKind.Unit;
+		case mItemKind.Value: return ls.CompletionItemKind.Value;
+		case mItemKind.Enum: return ls.CompletionItemKind.Enum;
+		case mItemKind.Keyword: return ls.CompletionItemKind.Keyword;
+		case mItemKind.Snippet: return ls.CompletionItemKind.Snippet;
+		case mItemKind.Color: return ls.CompletionItemKind.Color;
+		case mItemKind.File: return ls.CompletionItemKind.File;
+		case mItemKind.Reference: return ls.CompletionItemKind.Reference;
+	}
+	return ls.CompletionItemKind.Property;
+}
+
+function toTextEdit(textEdit: ls.TextEdit): monaco.editor.ISingleEditOperation {
+	if (!textEdit) {
+		return void 0;
+	}
+	return {
+		range: toRange(textEdit.range),
+		text: textEdit.newText
+	}
+}
+
+function fromTextEdit(editOp: monaco.editor.ISingleEditOperation): ls.TextEdit {
+	if (!editOp) {
+		return void 0;
+	}
+	return {
+		range: fromRange(Range.lift(editOp.range)),
+		newText: editOp.text
+	}
+}
+
+interface DataCompletionItem extends monaco.languages.CompletionItem {
+	data?: any;
+}
+
+function toCompletionItem(entry: ls.CompletionItem): DataCompletionItem {
+	return {
+		label: entry.label,
+		insertText: entry.insertText,
+		sortText: entry.sortText,
+		filterText: entry.filterText,
+		documentation: entry.documentation,
+		detail: entry.detail,
+		kind: toCompletionItemKind(entry.kind),
+		textEdit: toTextEdit(entry.textEdit),
+		data: entry.data
+	};
+}
+
+function fromCompletionItem(entry: DataCompletionItem): ls.CompletionItem {
+	return {
+		label: entry.label,
+		insertText: entry.insertText,
+		sortText: entry.sortText,
+		filterText: entry.filterText,
+		documentation: entry.documentation,
+		detail: entry.detail,
+		kind: fromCompletionItemKind(entry.kind),
+		textEdit: fromTextEdit(entry.textEdit),
+		data: entry.data
+	};
+}
+
+
+export class CompletionAdapter implements monaco.languages.CompletionItemProvider {
+
+	constructor(private _worker: WorkerAccessor) {
+	}
+
+	public get triggerCharacters(): string[] {
+		return [' ', ':'];
+	}
+
+	provideCompletionItems(model: monaco.editor.IReadOnlyModel, position: Position, token: CancellationToken): Thenable<monaco.languages.CompletionList> {
+		const wordInfo = model.getWordUntilPosition(position);
+		const resource = model.uri;
+
+		return wireCancellationToken(token, this._worker(resource).then(worker => {
+			return worker.doComplete(resource.toString(), fromPosition(position)).then(info => {
+				if (!info) {
+					return;
+				}
+				let items: monaco.languages.CompletionItem[] = info.items.map(entry => {
+					return {
+						label: entry.label,
+						insertText: entry.insertText,
+						sortText: entry.sortText,
+						filterText: entry.filterText,
+						documentation: entry.documentation,
+						detail: entry.detail,
+						kind: toCompletionItemKind(entry.kind),
+						textEdit: toTextEdit(entry.textEdit)
+					};
+				});
+
+				return <monaco.languages.CompletionList>{
+					isIncomplete: info.isIncomplete,
+					items: items
+				};
+			});
+		}));
+	}
+
+	resolveCompletionItem(item: monaco.languages.CompletionItem, token: CancellationToken): Thenable<monaco.languages.CompletionItem> {
+		return wireCancellationToken(token, this._worker().then(worker => {
+			let lsItem = fromCompletionItem(item);
+			return worker.doResolve(lsItem).then(result => {
+				return toCompletionItem(result);
+			});
+		}));
+	}
+}
+
+function toMarkedStringArray(contents: ls.MarkedString | ls.MarkedString[]): monaco.MarkedString[] {
+	if (!contents) {
+		return void 0;
+	}
+	if (Array.isArray(contents)) {
+		return (<ls.MarkedString[]>contents);
+	}
+	return [<ls.MarkedString>contents];
+}
+
+
+// --- hover ------
+
+export class HoverAdapter implements monaco.languages.HoverProvider {
+
+	constructor(private _worker: WorkerAccessor) {
+	}
+
+	provideHover(model: monaco.editor.IReadOnlyModel, position: Position, token: CancellationToken): Thenable<monaco.languages.Hover> {
+		let resource = model.uri;
+
+		return wireCancellationToken(token, this._worker(resource).then(worker => {
+			return worker.doHover(resource.toString(), fromPosition(position));
+		}).then(info => {
+			if (!info) {
+				return;
+			}
+			return <monaco.languages.Hover>{
+				range: toRange(info.range),
+				contents: toMarkedStringArray(info.contents)
+			};
+		}));
+	}
+}
+
+// --- definition ------
+
+function toLocation(location: ls.Location): monaco.languages.Location {
+	return {
+		uri: Uri.parse(location.uri),
+		range: toRange(location.range)
+	};
+}
+
+
+// --- document symbols ------
+
+function toSymbolKind(kind: ls.SymbolKind): monaco.languages.SymbolKind {
+	let mKind = monaco.languages.SymbolKind;
+
+	switch (kind) {
+		case ls.SymbolKind.File: return mKind.Array;
+		case ls.SymbolKind.Module: return mKind.Module;
+		case ls.SymbolKind.Namespace: return mKind.Namespace;
+		case ls.SymbolKind.Package: return mKind.Package;
+		case ls.SymbolKind.Class: return mKind.Class;
+		case ls.SymbolKind.Method: return mKind.Method;
+		case ls.SymbolKind.Property: return mKind.Property;
+		case ls.SymbolKind.Field: return mKind.Field;
+		case ls.SymbolKind.Constructor: return mKind.Constructor;
+		case ls.SymbolKind.Enum: return mKind.Enum;
+		case ls.SymbolKind.Interface: return mKind.Interface;
+		case ls.SymbolKind.Function: return mKind.Function;
+		case ls.SymbolKind.Variable: return mKind.Variable;
+		case ls.SymbolKind.Constant: return mKind.Constant;
+		case ls.SymbolKind.String: return mKind.String;
+		case ls.SymbolKind.Number: return mKind.Number;
+		case ls.SymbolKind.Boolean: return mKind.Boolean;
+		case ls.SymbolKind.Array: return mKind.Array;
+	}
+	return mKind.Function;
+}
+
+
+export class DocumentSymbolAdapter implements monaco.languages.DocumentSymbolProvider {
+
+	constructor(private _worker: WorkerAccessor) {
+	}
+
+	public provideDocumentSymbols(model: monaco.editor.IReadOnlyModel, token: CancellationToken): Thenable<monaco.languages.SymbolInformation[]> {
+		const resource = model.uri;
+
+		return wireCancellationToken(token, this._worker(resource).then(worker => worker.findDocumentSymbols(resource.toString())).then(items => {
+			if (!items) {
+				return;
+			}
+			return items.map(item => ({
+				name: item.name,
+				containerName: item.containerName,
+				kind: toSymbolKind(item.kind),
+				location: toLocation(item.location)
+			}));
+		}));
+	}
+}
+
+
+function fromFormattingOptions(options: monaco.languages.FormattingOptions): ls.FormattingOptions {
+	return {
+		tabSize: options.tabSize,
+        insertSpaces: options.insertSpaces
+	};
+}
+
+export class DocumentFormattingEditProvider implements monaco.languages.DocumentFormattingEditProvider {
+
+	constructor(private _worker: WorkerAccessor) {
+	}
+
+	public provideDocumentFormattingEdits(model: monaco.editor.IReadOnlyModel, options: monaco.languages.FormattingOptions, token: CancellationToken): Thenable<monaco.editor.ISingleEditOperation[]> {
+		const resource = model.uri;
+
+		return wireCancellationToken(token, this._worker(resource).then(worker => {
+			return worker.format(resource.toString(), null, fromFormattingOptions(options)).then(edits => {
+				if (!edits || edits.length === 0) {
+					return;
+				}
+				return edits.map(toTextEdit);
+			});
+		}));
+	}
+}
+
+export class DocumentRangeFormattingEditProvider implements monaco.languages.DocumentRangeFormattingEditProvider {
+
+	constructor(private _worker: WorkerAccessor) {
+	}
+
+	public provideDocumentRangeFormattingEdits(model: monaco.editor.IReadOnlyModel, range: Range, options: monaco.languages.FormattingOptions, token: CancellationToken): Thenable<monaco.editor.ISingleEditOperation[]> {
+		const resource = model.uri;
+
+		return wireCancellationToken(token, this._worker(resource).then(worker => {
+			return worker.format(resource.toString(), fromRange(range), fromFormattingOptions(options)).then(edits => {
+				if (!edits || edits.length === 0) {
+					return;
+				}
+				return edits.map(toTextEdit);
+			});
+		}));
+	}
+}
+
+/**
+ * Hook a cancellation token to a WinJS Promise
+ */
+function wireCancellationToken<T>(token: CancellationToken, promise: Thenable<T>): Thenable<T> {
+	if ((<Promise<T>>promise).cancel) {
+		token.onCancellationRequested(() => (<Promise<T>>promise).cancel());
+	}
+	return promise;
+}

+ 77 - 0
src/monaco.contribution.ts

@@ -0,0 +1,77 @@
+/*---------------------------------------------------------------------------------------------
+ *  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 mode from './jsonMode';
+
+import Emitter = monaco.Emitter;
+import IEvent = monaco.IEvent;
+import IDisposable = monaco.IDisposable;
+
+declare var require: <T>(moduleId: [string], callback: (module: T) => void) => void;
+
+// --- JSON configuration and defaults ---------
+
+export class LanguageServiceDefaultsImpl implements monaco.languages.json.LanguageServiceDefaults {
+
+	private _onDidChange = new Emitter<monaco.languages.json.LanguageServiceDefaults>();
+	private _diagnosticsOptions: monaco.languages.json.DiagnosticsOptions;
+	private _languageId: string;
+
+	constructor(languageId: string, diagnosticsOptions: monaco.languages.json.DiagnosticsOptions) {
+		this._languageId = languageId;
+		this.setDiagnosticsOptions(diagnosticsOptions);
+	}
+
+	get onDidChange(): IEvent<monaco.languages.json.LanguageServiceDefaults> {
+		return this._onDidChange.event;
+	}
+
+	get languageId(): string {
+		return this._languageId;
+	}
+
+	get diagnosticsOptions(): monaco.languages.json.DiagnosticsOptions {
+		return this._diagnosticsOptions;
+	}
+
+	setDiagnosticsOptions(options: monaco.languages.json.DiagnosticsOptions): void {
+		this._diagnosticsOptions = options || Object.create(null);
+		this._onDidChange.fire(this);
+	}
+}
+
+const diagnosticDefault: monaco.languages.json.DiagnosticsOptions = {
+	validate: true,
+	allowComments: true,
+	schemas: []
+}
+
+const jsonDefaults = new LanguageServiceDefaultsImpl('json', diagnosticDefault);
+
+
+// Export API
+function createAPI(): typeof monaco.languages.json {
+	return {
+		jsonDefaults: jsonDefaults,
+	}
+}
+monaco.languages.json = createAPI();
+
+// --- Registration to monaco editor ---
+
+function withMode(callback: (module: typeof mode) => void): void {
+	require<typeof mode>(['vs/language/json/jsonMode'], callback);
+}
+
+monaco.languages.register({
+	id: 'json',
+	extensions: ['.json', '.bowerrc', '.jshintrc', '.jscsrc', '.eslintrc', '.babelrc'],
+	aliases: ['JSON', 'json'],
+	mimetypes: ['application/json'],
+});
+monaco.languages.onLanguage('json', () => {
+	withMode(mode => mode.setupMode(jsonDefaults));
+});

+ 42 - 0
src/monaco.d.ts

@@ -0,0 +1,42 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+declare module monaco.languages.json {
+    export interface DiagnosticsOptions {
+        /**
+         * If set, the validator will return syntax errors.
+         */
+        validate?: boolean;
+        /**
+         * If set, comments are toleranted. If not set, a syntax error is emmited for comments.
+         */
+        allowComments?: boolean;
+        /**
+         * A list of known schemas and/or associations of schemas to file names.
+         */
+        schemas?: {
+            /**
+             * The URI of the schema, which is also the identifier of the schema.
+             */
+            uri: string;
+            /**
+             * A list of file names that are associated to the schema. The '*' wildcard can be used. For example '*.schema.json', 'package.json'
+             */
+            fileMatch?: string[];
+            /**
+             * The schema for the given URI.
+             */
+            schema?: any;
+        }[];
+    }
+
+    export interface LanguageServiceDefaults {
+        onDidChange: IEvent<LanguageServiceDefaults>;
+        diagnosticsOptions: DiagnosticsOptions;
+        setDiagnosticsOptions(options: DiagnosticsOptions): void;
+    }
+
+    export var jsonDefaults: LanguageServiceDefaults;
+}

+ 180 - 0
src/tokenization.ts

@@ -0,0 +1,180 @@
+/*---------------------------------------------------------------------------------------------
+ *  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 json = require('jsonc-parser');
+
+export function createTokenizationSupport(supportComments: boolean): monaco.languages.TokensProvider {
+    return {
+        getInitialState: () => new JSONState(null, null, false),
+        tokenize: (line, state, offsetDelta?, stopAtOffset?) => tokenize(supportComments, line, <JSONState>state, offsetDelta, stopAtOffset)
+    };
+}
+
+export const TOKEN_DELIM_OBJECT = 'punctuation.bracket.json';
+export const TOKEN_DELIM_ARRAY = 'punctuation.array.json';
+export const TOKEN_DELIM_COLON = 'punctuation.colon.json';
+export const TOKEN_DELIM_COMMA = 'punctuation.comma.json';
+export const TOKEN_VALUE_BOOLEAN = 'support.property-value.keyword.json';
+export const TOKEN_VALUE_NULL = 'support.property-value.constant.other.json';
+export const TOKEN_VALUE_STRING = 'support.property-value.string.value.json';
+export const TOKEN_VALUE_NUMBER = 'support.property-value.constant.numeric.json';
+export const TOKEN_PROPERTY_NAME = 'support.type.property-name.json';
+export const TOKEN_COMMENT_BLOCK = 'comment.block.json';
+export const TOKEN_COMMENT_LINE = 'comment.line.json';
+
+class JSONState implements monaco.languages.IState {
+
+    private _state: monaco.languages.IState;
+
+    public scanError: json.ScanError;
+    public lastWasColon: boolean;
+
+    constructor(state: monaco.languages.IState, scanError: json.ScanError, lastWasColon: boolean) {
+        this._state = state;
+        this.scanError = scanError;
+        this.lastWasColon = lastWasColon;
+    }
+
+    public clone(): JSONState {
+        return new JSONState(this._state, this.scanError, this.lastWasColon);
+    }
+
+    public equals(other: monaco.languages.IState): boolean {
+        if (other === this) {
+            return true;
+        }
+        if (!other || !(other instanceof JSONState)) {
+            return false;
+        }
+        return this.scanError === (<JSONState>other).scanError &&
+            this.lastWasColon === (<JSONState>other).lastWasColon;
+    }
+
+    public getStateData(): monaco.languages.IState {
+        return this._state;
+    }
+
+    public setStateData(state: monaco.languages.IState): void {
+        this._state = state;
+    }
+}
+
+function tokenize(comments: boolean, line: string, state: JSONState, offsetDelta: number = 0, stopAtOffset?: number): monaco.languages.ILineTokens {
+
+    // handle multiline strings and block comments
+    var numberOfInsertedCharacters = 0,
+        adjustOffset = false;
+
+    switch (state.scanError) {
+        case json.ScanError.UnexpectedEndOfString:
+            line = '"' + line;
+            numberOfInsertedCharacters = 1;
+            break;
+        case json.ScanError.UnexpectedEndOfComment:
+            line = '/*' + line;
+            numberOfInsertedCharacters = 2;
+            break;
+    }
+
+    var scanner = json.createScanner(line),
+        kind: json.SyntaxKind,
+        ret: monaco.languages.ILineTokens,
+        lastWasColon = state.lastWasColon;
+
+    ret = {
+        tokens: <monaco.languages.IToken[]>[],
+        endState: state.clone()
+    };
+
+    while (true) {
+
+        var offset = offsetDelta + scanner.getPosition(),
+            type = '';
+
+        kind = scanner.scan();
+        if (kind === json.SyntaxKind.EOF) {
+            break;
+        }
+
+        // Check that the scanner has advanced
+        if (offset === offsetDelta + scanner.getPosition()) {
+            throw new Error('Scanner did not advance, next 3 characters are: ' + line.substr(scanner.getPosition(), 3));
+        }
+
+        // In case we inserted /* or " character, we need to
+        // adjust the offset of all tokens (except the first)
+        if (adjustOffset) {
+            offset -= numberOfInsertedCharacters;
+        }
+        adjustOffset = numberOfInsertedCharacters > 0;
+
+
+        // brackets and type
+        switch (kind) {
+            case json.SyntaxKind.OpenBraceToken:
+                type = TOKEN_DELIM_OBJECT;
+                lastWasColon = false;
+                break;
+            case json.SyntaxKind.CloseBraceToken:
+                type = TOKEN_DELIM_OBJECT;
+                lastWasColon = false;
+                break;
+            case json.SyntaxKind.OpenBracketToken:
+                type = TOKEN_DELIM_ARRAY;
+                lastWasColon = false;
+                break;
+            case json.SyntaxKind.CloseBracketToken:
+                type = TOKEN_DELIM_ARRAY;
+                lastWasColon = false;
+                break;
+            case json.SyntaxKind.ColonToken:
+                type = TOKEN_DELIM_COLON;
+                lastWasColon = true;
+                break;
+            case json.SyntaxKind.CommaToken:
+                type = TOKEN_DELIM_COMMA;
+                lastWasColon = false;
+                break;
+            case json.SyntaxKind.TrueKeyword:
+            case json.SyntaxKind.FalseKeyword:
+                type = TOKEN_VALUE_BOOLEAN;
+                lastWasColon = false;
+                break;
+            case json.SyntaxKind.NullKeyword:
+                type = TOKEN_VALUE_NULL;
+                lastWasColon = false;
+                break;
+            case json.SyntaxKind.StringLiteral:
+                type = lastWasColon ? TOKEN_VALUE_STRING : TOKEN_PROPERTY_NAME;
+                lastWasColon = false;
+                break;
+            case json.SyntaxKind.NumericLiteral:
+                type = TOKEN_VALUE_NUMBER;
+                lastWasColon = false;
+                break;
+        }
+
+        // comments, iff enabled
+        if (comments) {
+            switch (kind) {
+                case json.SyntaxKind.LineCommentTrivia:
+                    type = TOKEN_COMMENT_LINE;
+                    break;
+                case json.SyntaxKind.BlockCommentTrivia:
+                    type = TOKEN_COMMENT_BLOCK;
+                    break;
+            }
+        }
+
+        ret.endState = new JSONState(state.getStateData(), scanner.getTokenError(), lastWasColon);
+        ret.tokens.push({
+            startIndex: offset,
+            scopes: type
+        });
+    }
+
+    return ret;
+}

+ 8 - 0
src/tsconfig.json

@@ -0,0 +1,8 @@
+{
+  "compilerOptions": {
+    "module": "umd",
+    "moduleResolution": "node",
+    "outDir": "../out",
+    "target": "es5"
+  }
+}

+ 5 - 0
src/typings/refs.d.ts

@@ -0,0 +1,5 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+/// <reference path='../../node_modules/monaco-editor-core/monaco.d.ts'/>

+ 104 - 0
src/workerManager.ts

@@ -0,0 +1,104 @@
+/*---------------------------------------------------------------------------------------------
+ *  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 {LanguageServiceDefaultsImpl} from './monaco.contribution';
+import {JSONWorker} from './jsonWorker';
+
+import Promise = monaco.Promise;
+import IDisposable = monaco.IDisposable;
+import Uri = monaco.Uri;
+
+const STOP_WHEN_IDLE_FOR = 2 * 60 * 1000; // 2min
+
+export class WorkerManager {
+
+	private _defaults: LanguageServiceDefaultsImpl;
+	private _idleCheckInterval: number;
+	private _lastUsedTime: number;
+	private _configChangeListener: IDisposable;
+
+	private _worker: monaco.editor.MonacoWebWorker<JSONWorker>;
+	private _client: Promise<JSONWorker>;
+
+	constructor(defaults: LanguageServiceDefaultsImpl) {
+		this._defaults = defaults;
+		this._worker = null;
+		this._idleCheckInterval = setInterval(() => this._checkIfIdle(), 30 * 1000);
+		this._lastUsedTime = 0;
+		this._configChangeListener = this._defaults.onDidChange(() => this._stopWorker());
+	}
+
+	private _stopWorker(): void {
+		if (this._worker) {
+			this._worker.dispose();
+			this._worker = null;
+		}
+		this._client = null;
+	}
+
+	dispose(): void {
+		clearInterval(this._idleCheckInterval);
+		this._configChangeListener.dispose();
+		this._stopWorker();
+	}
+
+	private _checkIfIdle(): void {
+		if (!this._worker) {
+			return;
+		}
+		let timePassedSinceLastUsed = Date.now() - this._lastUsedTime;
+		if (timePassedSinceLastUsed > STOP_WHEN_IDLE_FOR) {
+			this._stopWorker();
+		}
+	}
+
+	private _getClient(): Promise<JSONWorker> {
+		this._lastUsedTime = Date.now();
+
+		if (!this._client) {
+			this._worker = monaco.editor.createWebWorker<JSONWorker>({
+
+				// module that exports the create() method and returns a `JSONWorker` instance
+				moduleId: 'vs/language/json/jsonWorker',
+
+				// passed in to the create() method
+				createData: {
+					languageSettings: this._defaults.diagnosticsOptions,
+					languageId: this._defaults.languageId
+				}
+			});
+
+			this._client = this._worker.getProxy();
+		}
+
+		return this._client;
+	}
+
+	getLanguageServiceWorker(...resources: Uri[]): Promise<JSONWorker> {
+		let _client: JSONWorker;
+		return toShallowCancelPromise(
+			this._getClient().then((client) => {
+				_client = client
+			}).then(_ => {
+				return this._worker.withSyncedResources(resources)
+			}).then(_ => _client)
+		);
+	}
+}
+
+function toShallowCancelPromise<T>(p: Promise<T>): Promise<T> {
+	let completeCallback: (value: T) => void;
+	let errorCallback: (err: any) => void;
+
+	let r = new Promise<T>((c, e) => {
+		completeCallback = c;
+		errorCallback = e;
+	}, () => { });
+
+	p.then(completeCallback, errorCallback);
+
+	return r;
+}