Convert json implementation to yaml libs

This commit is contained in:
Kevin Decker 2018-02-25 23:35:44 -06:00
parent 8ba88be05d
commit ad350d08b2
11 changed files with 255 additions and 227 deletions

View file

@ -52,21 +52,13 @@ gulp.task('release', ['clean-release','compile'], function() {
return rjs({ return rjs({
baseUrl: '/out/', baseUrl: '/out/',
name: 'vs/language/json/' + moduleId, name: 'hl/yaml/' + moduleId,
out: moduleId + '.js', out: moduleId + '.js',
exclude: exclude, exclude: exclude,
paths: { paths: {
'vs/language/json': __dirname + '/out' 'hl/yaml': __dirname + '/out'
}, },
packages: [{ packages: [{
name: 'vscode-yaml-languageservice',
location: __dirname + '/out/vscode-yaml-languageservice/',
main: 'yamlLanguageService'
}, {
name: 'yaml-language-server',
location: __dirname + '/out/yaml-language-server',
main: 'yamlLanguageService'
}, {
name: 'yaml-ast-parser', name: 'yaml-ast-parser',
location: __dirname + '/out/yaml-ast-parser', location: __dirname + '/out/yaml-ast-parser',
main: 'index' main: 'index'
@ -108,9 +100,9 @@ gulp.task('release', ['clean-release','compile'], function() {
return merge( return merge(
merge( merge(
bundleOne('monaco.contribution', ['vs/language/json/jsonMode']), bundleOne('monaco.contribution', ['hl/yaml/yamlMode']),
bundleOne('jsonMode'), bundleOne('yamlMode'),
bundleOne('jsonWorker') bundleOne('yamlWorker')
) )
.pipe(es.through(function (data) { .pipe(es.through(function (data) {
data.contents = new Buffer( data.contents = new Buffer(

View file

@ -1,5 +1,5 @@
{ {
"name": "monaco-json", "name": "monaco-yaml",
"version": "1.3.2", "version": "1.3.2",
"description": "JSON plugin for the Monaco Editor", "description": "JSON plugin for the Monaco Editor",
"scripts": { "scripts": {

View file

@ -1,116 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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> {
let thenable : monaco.Thenable<T> = this.wrapped;
return thenable.then(onfulfilled, onrejected);
}
public getWrapped(): monaco.Thenable<T> {
return this.wrapped;
}
public cancel(): void {
this.wrapped.cancel();
}
public static resolve<T>(v: T | Thenable<T>): jsonService.Thenable<T> {
return <monaco.Thenable<T>> 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);
if (document) {
let jsonDocument = this._languageService.parseJSONDocument(document);
return this._languageService.doValidation(document, jsonDocument);
}
return Promise.as([]);
}
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);
}

View file

@ -5,7 +5,7 @@
'use strict'; 'use strict';
import { LanguageServiceDefaultsImpl } from './monaco.contribution'; import { LanguageServiceDefaultsImpl } from './monaco.contribution';
import {JSONWorker} from './jsonWorker'; import { YAMLWorker } from './yamlWorker';
import * as ls from 'vscode-languageserver-types'; import * as ls from 'vscode-languageserver-types';
@ -19,7 +19,7 @@ import IDisposable = monaco.IDisposable;
export interface WorkerAccessor { export interface WorkerAccessor {
(...more: Uri[]): Thenable<JSONWorker> (...more: Uri[]): Thenable<YAMLWorker>
} }
// --- diagnostics --- --- // --- diagnostics --- ---

View file

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

8
src/monaco.d.ts vendored
View file

@ -3,16 +3,12 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
declare module monaco.languages.json { declare module monaco.languages.yaml {
export interface DiagnosticsOptions { export interface DiagnosticsOptions {
/** /**
* If set, the validator will be enabled and perform syntax validation as well as schema based validation. * If set, the validator will be enabled and perform syntax validation as well as schema based validation.
*/ */
readonly validate?: boolean; readonly validate?: boolean;
/**
* If set, comments are tolerated. If set to false, syntax errors will be emmited for comments.
*/
readonly allowComments?: boolean;
/** /**
* A list of known schemas and/or associations of schemas to file names. * A list of known schemas and/or associations of schemas to file names.
*/ */
@ -38,5 +34,5 @@ declare module monaco.languages.json {
setDiagnosticsOptions(options: DiagnosticsOptions): void; setDiagnosticsOptions(options: DiagnosticsOptions): void;
} }
export var jsonDefaults: LanguageServiceDefaults; export var yamlDefaults: LanguageServiceDefaults;
} }

View file

@ -5,7 +5,7 @@
'use strict'; 'use strict';
import { LanguageServiceDefaultsImpl } from './monaco.contribution'; import { LanguageServiceDefaultsImpl } from './monaco.contribution';
import {JSONWorker} from './jsonWorker'; import { YAMLWorker } from './yamlWorker';
import Promise = monaco.Promise; import Promise = monaco.Promise;
import IDisposable = monaco.IDisposable; import IDisposable = monaco.IDisposable;
@ -20,8 +20,8 @@ export class WorkerManager {
private _lastUsedTime: number; private _lastUsedTime: number;
private _configChangeListener: IDisposable; private _configChangeListener: IDisposable;
private _worker: monaco.editor.MonacoWebWorker<JSONWorker>; private _worker: monaco.editor.MonacoWebWorker<YAMLWorker>;
private _client: Promise<JSONWorker>; private _client: Promise<YAMLWorker>;
constructor(defaults: LanguageServiceDefaultsImpl) { constructor(defaults: LanguageServiceDefaultsImpl) {
this._defaults = defaults; this._defaults = defaults;
@ -55,14 +55,14 @@ export class WorkerManager {
} }
} }
private _getClient(): Promise<JSONWorker> { private _getClient(): Promise<YAMLWorker> {
this._lastUsedTime = Date.now(); this._lastUsedTime = Date.now();
if (!this._client) { if (!this._client) {
this._worker = monaco.editor.createWebWorker<JSONWorker>({ this._worker = monaco.editor.createWebWorker<YAMLWorker>({
// module that exports the create() method and returns a `JSONWorker` instance // module that exports the create() method and returns a `YAMLWorker` instance
moduleId: 'vs/language/json/jsonWorker', moduleId: 'hl/yaml/yamlWorker',
label: this._defaults.languageId, label: this._defaults.languageId,
@ -79,8 +79,8 @@ export class WorkerManager {
return this._client; return this._client;
} }
getLanguageServiceWorker(...resources: Uri[]): Promise<JSONWorker> { getLanguageServiceWorker(...resources: Uri[]): Promise<YAMLWorker> {
let _client: JSONWorker; let _client: YAMLWorker;
return toShallowCancelPromise( return toShallowCancelPromise(
this._getClient().then((client) => { this._getClient().then((client) => {
_client = client _client = client

View file

@ -176,6 +176,7 @@ export class YAMLCompletion {
collector.add({ collector.add({
kind: CompletionItemKind.Property, kind: CompletionItemKind.Property,
label: key, label: key,
insertText: `${key}:`,
filterText: this.getFilterTextForValue(key), filterText: this.getFilterTextForValue(key),
documentation: propertySchema.description || '' documentation: propertySchema.description || ''
}); });

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
import jsyaml = require('js-yaml') import * as jsyaml from 'js-yaml'
import { EOL } from 'os'; import { EOL } from 'os';
import { TextDocument, Range, Position, FormattingOptions, TextEdit } from 'vscode-languageserver-types'; import { TextDocument, Range, Position, FormattingOptions, TextEdit } from 'vscode-languageserver-types';

View file

@ -5,10 +5,10 @@
'use strict'; 'use strict';
import { WorkerManager } from './workerManager'; import { WorkerManager } from './workerManager';
import {JSONWorker} from './jsonWorker'; import { YAMLWorker } from './yamlWorker';
import { LanguageServiceDefaultsImpl } from './monaco.contribution'; import { LanguageServiceDefaultsImpl } from './monaco.contribution';
import * as languageFeatures from './languageFeatures'; import * as languageFeatures from './languageFeatures';
import {createTokenizationSupport} from './tokenization'; // import { createTokenizationSupport } from './tokenization';
import Promise = monaco.Promise; import Promise = monaco.Promise;
import Uri = monaco.Uri; import Uri = monaco.Uri;
@ -21,7 +21,7 @@ export function setupMode(defaults: LanguageServiceDefaultsImpl): void {
const client = new WorkerManager(defaults); const client = new WorkerManager(defaults);
disposables.push(client); disposables.push(client);
const worker: languageFeatures.WorkerAccessor = (...uris: Uri[]): Promise<JSONWorker> => { const worker: languageFeatures.WorkerAccessor = (...uris: Uri[]): Promise<YAMLWorker> => {
return client.getLanguageServiceWorker(...uris); return client.getLanguageServiceWorker(...uris);
}; };
@ -33,28 +33,40 @@ export function setupMode(defaults: LanguageServiceDefaultsImpl): void {
disposables.push(monaco.languages.registerDocumentFormattingEditProvider(languageId, new languageFeatures.DocumentFormattingEditProvider(worker))); disposables.push(monaco.languages.registerDocumentFormattingEditProvider(languageId, new languageFeatures.DocumentFormattingEditProvider(worker)));
disposables.push(monaco.languages.registerDocumentRangeFormattingEditProvider(languageId, new languageFeatures.DocumentRangeFormattingEditProvider(worker))); disposables.push(monaco.languages.registerDocumentRangeFormattingEditProvider(languageId, new languageFeatures.DocumentRangeFormattingEditProvider(worker)));
disposables.push(new languageFeatures.DiagnostcsAdapter(languageId, worker)); disposables.push(new languageFeatures.DiagnostcsAdapter(languageId, worker));
disposables.push(monaco.languages.setTokensProvider(languageId, createTokenizationSupport(true))); // disposables.push(monaco.languages.setTokensProvider(languageId, createTokenizationSupport(true)));
disposables.push(monaco.languages.setLanguageConfiguration(languageId, richEditConfiguration)); disposables.push(monaco.languages.setLanguageConfiguration(languageId, richEditConfiguration));
} }
const richEditConfiguration: monaco.languages.LanguageConfiguration = { const richEditConfiguration: monaco.languages.LanguageConfiguration = {
wordPattern: /(-?\d*\.\d\w*)|([^\[\{\]\}\:\"\,\s]+)/g,
comments: { comments: {
lineComment: '//', lineComment: '#'
blockComment: ['/*', '*/']
}, },
brackets: [ brackets: [
['{', '}'], ['{', '}'],
['[', ']'] ['[', ']'],
['(', ')']
],
autoClosingPairs: [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '"', close: '"' },
{ open: '\'', close: '\'' },
],
surroundingPairs: [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '"', close: '"' },
{ open: '\'', close: '\'' },
], ],
autoClosingPairs: [ onEnterRules: [
{ open: '{', close: '}', notIn: ['string'] }, {
{ open: '[', close: ']', notIn: ['string'] }, beforeText: /:\s*$/,
{ open: '"', close: '"', notIn: ['string'] } action: { indentAction: monaco.languages.IndentAction.Indent }
] }
],
}; };

146
src/yamlWorker.ts Normal file
View file

@ -0,0 +1,146 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Copyright (c) Adam Voss. All rights reserved.
* 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 yamlService from './yaml-languageservice/yamlLanguageService';
import * as ls from 'vscode-languageserver-types';
import { getLineOffsets } from './yaml-languageservice/utils/arrUtils';
import { parse as parseYAML } from "./yaml-languageservice/parser/yamlParser";
export class YAMLWorker {
private _ctx: IWorkerContext;
private _languageService: yamlService.LanguageService;
private _languageSettings: yamlService.LanguageSettings;
private _languageId: string;
constructor(ctx: IWorkerContext, createData: ICreateData) {
this._ctx = ctx;
this._languageSettings = createData.languageSettings;
this._languageId = createData.languageId;
this._languageService = yamlService.getLanguageService();
this._languageService.configure(this._languageSettings);
}
doValidation(uri: string): Thenable<ls.Diagnostic[]> {
let document = this._getTextDocument(uri);
if (document) {
let jsonDocument = this._languageService.parseYAMLDocument(document);
return this._languageService.doValidation(document, jsonDocument);
}
return Promise.as([]);
}
doComplete(uri: string, position: ls.Position): Thenable<ls.CompletionList> {
let document = this._getTextDocument(uri);
let completionFix = completionHelper(document, position);
let newText = completionFix.newText;
let jsonDocument = parseYAML(newText);
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.parseYAMLDocument(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, 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.parseYAMLDocument(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: yamlService.LanguageSettings;
}
export function create(ctx: IWorkerContext, createData: ICreateData): YAMLWorker {
return new YAMLWorker(ctx, createData);
}
// https://github.com/redhat-developer/yaml-language-server/blob/5e069c0e9d7004d57f1fa6e93df670d4895883d1/src/server.ts#L453
function completionHelper(document: ls.TextDocument, textDocumentPosition: ls.Position) {
//Get the string we are looking at via a substring
let linePos = textDocumentPosition.line;
let position = textDocumentPosition;
let lineOffset = getLineOffsets(document.getText());
let start = lineOffset[linePos]; //Start of where the autocompletion is happening
let end = 0; //End of where the autocompletion is happening
if (lineOffset[linePos + 1]) {
end = lineOffset[linePos + 1];
} else {
end = document.getText().length;
}
let textLine = document.getText().substring(start, end);
//Check if the string we are looking at is a node
if (textLine.indexOf(":") === -1) {
//We need to add the ":" to load the nodes
let newText = "";
//This is for the empty line case
let trimmedText = textLine.trim();
if (trimmedText.length === 0 || (trimmedText.length === 1 && trimmedText[0] === '-')) {
//Add a temp node that is in the document but we don't use at all.
if (lineOffset[linePos + 1]) {
newText = document.getText().substring(0, start + (textLine.length - 1)) + "holder:\r\n" + document.getText().substr(end + 2);
} else {
newText = document.getText().substring(0, start + (textLine.length)) + "holder:\r\n" + document.getText().substr(end + 2);
}
//For when missing semi colon case
} else {
//Add a semicolon to the end of the current line so we can validate the node
if (lineOffset[linePos + 1]) {
newText = document.getText().substring(0, start + (textLine.length - 1)) + ":\r\n" + document.getText().substr(end + 2);
} else {
newText = document.getText().substring(0, start + (textLine.length)) + ":\r\n" + document.getText().substr(end + 2);
}
}
return {
"newText": newText,
"newPosition": textDocumentPosition
}
} else {
//All the nodes are loaded
position.character = position.character - 1;
return {
"newText": document.getText(),
"newPosition": position
}
}
}