chore: align with pengx17/master branch

This commit is contained in:
Peng Xiao 2018-10-25 09:57:08 +08:00
parent 63bca4922f
commit a0db642717
40 changed files with 1499 additions and 2926 deletions

View file

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

View file

@ -7,21 +7,37 @@ YAML language plugin for the Monaco Editor. It provides the following features w
* Formatting * Formatting
* Document Symbols * Document Symbols
* Syntax highlighting * Syntax highlighting
* Automatically load remote schema files
Schemas can be provided by configuration. See [here](https://github.com/Microsoft/monaco-json/blob/master/src/monaco.d.ts) Schemas can also be provided by configuration. See [here](https://github.com/Microsoft/monaco-json/blob/master/src/monaco.d.ts)
for the API that the JSON plugin offers to configure the JSON language support. for the API that the JSON plugin offers to configure the JSON language support.
## Installing ## Installing
TODO: Document exact distribution method `yarn add monaco-yaml`
See `test/index.html` as an example. Currently only load with vs loader is supported. (AMD)
Load with ESM is added, but not yet tested.
## Development ## Development
* `git clone https://github.com/kpdecker/monaco-yaml` * `git clone https://github.com/kpdecker/monaco-yaml`
* `cd monaco-yaml` * `cd monaco-yaml`
* `yarn` * `yarn`
* `npm run watch` * `yarn watch`
* open `$/monaco-yaml/test/index.html` in your favorite browser. * open `$/monaco-yaml/test/index.html` in your favorite browser.
A running example:
![demo-image](test-demo.png)
## Credits
- https://github.com/redhat-developer/yaml-language-server
### Maintain
Manually clone dependencies list below and update the project files accordingly:
- `src/languageservice`: https://github.com/redhat-developer/yaml-language-server
- `cp yaml-language-server/src/languageservice monaco-yaml/src/languageservice`
- Modify the import paths, go to the test page and see if it still works
- `src/yaml-ast-parser`: https://github.com/mulesoft-labs/yaml-ast-parser/tree/master/src
## License ## License
[MIT](https://github.com/kpdecker/monaco-yaml/blob/master/LICENSE.md) [MIT](https://github.com/kpdecker/monaco-yaml/blob/master/LICENSE.md)

View file

@ -1,190 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 getDependencyLocation(name, libLocation, container) {
var location = __dirname + '/node_modules/' + name + '/' + libLocation;
if (!fs.existsSync(location)) {
var oldLocation = __dirname + '/node_modules/' + container + '/node_modules/' + name + '/' + libLocation;
if (!fs.existsSync(oldLocation)) {
console.error('Unable to find ' + name + ' node module at ' + location + ' or ' + oldLocation);
return;
}
return oldLocation;
}
return location;
}
var jsoncLocation = getDependencyLocation('jsonc-parser', 'lib', 'vscode-json-languageservice');
var uriLocation = getDependencyLocation('vscode-uri', 'lib', 'vscode-json-languageservice');
function bundleOne(moduleId, exclude) {
return rjs({
baseUrl: '/out/',
name: 'vs/languages/yaml/' + moduleId,
out: moduleId + '.js',
exclude: exclude,
paths: {
'vs/languages/yaml': __dirname + '/out'
},
packages: [{
name: 'yaml-ast-parser',
location: __dirname + '/out/yaml-ast-parser',
main: 'index'
}, {
name: 'js-yaml',
location: __dirname + '/node_modules/js-yaml/dist',
main: 'js-yaml'
}, {
name: 'vscode-json-languageservice',
location: __dirname + '/node_modules/vscode-json-languageservice',
main: 'jsonLanguageService'
}, {
name: 'vscode-languageserver-types',
location: __dirname + '/node_modules/vscode-languageserver-types/lib',
main: 'main'
}, {
name: 'vscode-uri',
location: uriLocation,
main: 'index'
}, {
name: 'jsonc-parser',
location: jsoncLocation,
main: 'main'
}, {
name: 'vscode-uri',
location: uriLocation,
main: 'index'
}, {
name: 'vscode-nls',
location: __dirname + '/out/fillers',
main: 'vscode-nls'
}, {
name: 'os',
location: __dirname + '/out/fillers',
main: 'os'
}]
})
}
return merge(
merge(
bundleOne('monaco.contribution', ['vs/languages/yaml/yamlMode']),
bundleOne('yamlMode'),
bundleOne('yamlWorker')
)
.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']);
});
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];
}

View file

@ -1,33 +1,39 @@
{ {
"name": "monaco-yaml", "name": "monaco-yaml",
"version": "1.0.0", "version": "1.1.0",
"description": "YAML plugin for the Monaco Editor", "description": "YAML plugin for the Monaco Editor",
"scripts": { "scripts": {
"compile": "gulp compile", "compile": "rimraf ./out && tsc -p ./src/tsconfig.json && tsc -p ./src/tsconfig.esm.json",
"watch": "gulp watch", "watch": "tsc -p ./src --watch",
"prepublish": "gulp release" "prepublish": "rimraf ./release && yarn run compile && node ./scripts/release.js && node ./scripts/bundle && mcopy ./src/monaco.d.ts ./release/monaco.d.ts"
}, },
"author": "Kevin Decker <kpdecker@gmail.com> (http://incaseofstairs.com)", "author": "Kevin Decker <kpdecker@gmail.com> (http://incaseofstairs.com)",
"maintainers": [
"kpdecker",
"pengx17"
],
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/kpdecker/monaco-yaml" "url": "https://github.com/kpdecker/monaco-yaml"
}, },
"bugs": {
"url": "https://github.com/kpdecker/monaco-yaml/issues"
},
"devDependencies": { "devDependencies": {
"event-stream": "^3.3.2", "@types/chai": "^4.1.4",
"gulp": "^3.9.1", "@types/mocha": "^5.2.5",
"gulp-requirejs": "^0.1.3", "@types/node": "^10.9.3",
"gulp-tsb": "^2.0.0", "js-yaml": "^3.12.0",
"gulp-uglify": "^1.5.3", "jsonc-parser": "^2.0.2",
"js-yaml": "^3.10.0", "monaco-editor-core": "0.14.6",
"jsonc-parser": "1.0.0", "monaco-languages": "1.5.1",
"merge-stream": "^1.0.0", "monaco-plugin-helpers": "^1.0.2",
"monaco-editor-core": "^0.10.1", "requirejs": "^2.3.5",
"monaco-languages": "^0.9.0", "rimraf": "^2.6.2",
"object-assign": "^4.1.0", "typescript": "^3.0.3",
"rimraf": "^2.5.2", "uglify-es": "^3.3.9",
"typescript": "^2.7.1", "vscode-json-languageservice": "^3.1.6",
"vscode-json-languageservice": "^3.0.5", "vscode-languageserver-types": "3.12.0"
"vscode-languageserver-types": "^3.5.0"
} }
} }

82
scripts/bundle.js Normal file
View file

@ -0,0 +1,82 @@
const requirejs = require('requirejs');
const path = require('path');
const fs = require('fs');
const UglifyES = require("uglify-es");
const helpers = require('monaco-plugin-helpers');
const REPO_ROOT = path.resolve(__dirname, '..');
const sha1 = helpers.getGitVersion(REPO_ROOT);
const semver = require('../package.json').version;
const headerVersion = semver + '(' + sha1 + ')';
const BUNDLED_FILE_HEADER = [
'/*!-----------------------------------------------------------------------------',
' * Copyright (c) Microsoft Corporation. All rights reserved.',
' * monaco-yaml version: ' + headerVersion,
' * Released under the MIT license',
' * https://github.com/kpdecker/monaco-yaml/blob/master/LICENSE.md',
' *-----------------------------------------------------------------------------*/',
''
].join('\n');
bundleOne('monaco.contribution');
bundleOne('yamlMode');
bundleOne('yamlWorker');
function bundleOne(moduleId, exclude) {
requirejs.optimize({
baseUrl: 'out/amd/',
name: 'vs/language/yaml/' + moduleId,
out: 'release/dev/' + moduleId + '.js',
exclude: exclude,
paths: {
'vs/language/yaml': REPO_ROOT + '/out/amd'
},
optimize: 'none',
packages: [
{
name: 'js-yaml',
location: path.join(REPO_ROOT, 'node_modules/js-yaml/dist'),
main: 'js-yaml'
},
// The following is required by YAML language service
{
name: 'jsonc-parser',
location: path.join(REPO_ROOT, 'node_modules/jsonc-parser/lib/umd'),
main: 'main'
}, {
name: 'vscode-json-languageservice/lib',
location: path.join(REPO_ROOT, 'node_modules/vscode-json-languageservice/lib/umd')
},
{
name: 'vscode-languageserver-types',
location: path.join(REPO_ROOT, 'node_modules/vscode-languageserver-types/lib/umd'),
main: 'main'
}, {
name: 'vscode-uri',
location: path.join(REPO_ROOT, 'node_modules/vscode-uri/lib/umd'),
main: 'index'
}, {
name: 'vscode-nls',
location: path.join(REPO_ROOT, '/out/amd/fillers'),
main: 'vscode-nls'
}]
}, function () {
const devFilePath = path.join(REPO_ROOT, 'release/dev/' + moduleId + '.js');
const minFilePath = path.join(REPO_ROOT, 'release/min/' + moduleId + '.js');
const fileContents = fs.readFileSync(devFilePath).toString();
console.log();
console.log(`Minifying ${devFilePath}...`);
const result = UglifyES.minify(fileContents, {
output: {
comments: 'some'
}
});
console.log(`Done.`);
try { fs.mkdirSync(path.join(REPO_ROOT, 'release/min')) } catch (err) { }
fs.writeFileSync(minFilePath, BUNDLED_FILE_HEADER + result.code);
})
}

28
scripts/release.js Normal file
View file

@ -0,0 +1,28 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const path = require("path");
const helpers = require("monaco-plugin-helpers");
const REPO_ROOT = path.join(__dirname, "../");
helpers.packageESM({
repoRoot: REPO_ROOT,
esmSource: "out/esm",
esmDestination: "release/esm",
entryPoints: ["monaco.contribution.js", "yamlMode.js", "yaml.worker.js"],
resolveAlias: {
"vscode-nls": path.join(REPO_ROOT, "out/esm/fillers/vscode-nls.js")
},
resolveSkip: ["monaco-editor-core", "js-yaml"],
destinationFolderSimplification: {
node_modules: "_deps",
"jsonc-parser/lib/esm": "jsonc-parser",
"vscode-languageserver-types/lib/esm": "vscode-languageserver-types",
"vscode-uri/lib/esm": "vscode-uri",
"vscode-json-languageservice/lib/esm": "vscode-json-languageservice",
// "js-yaml/dist": "js-yaml"
}
});

View file

@ -12,6 +12,7 @@ import * as ls from 'vscode-languageserver-types';
import Uri = monaco.Uri; import Uri = monaco.Uri;
import Position = monaco.Position; import Position = monaco.Position;
import Range = monaco.Range; import Range = monaco.Range;
import IRange = monaco.IRange;
import Thenable = monaco.Thenable; import Thenable = monaco.Thenable;
import Promise = monaco.Promise; import Promise = monaco.Promise;
import CancellationToken = monaco.CancellationToken; import CancellationToken = monaco.CancellationToken;
@ -24,19 +25,19 @@ export interface WorkerAccessor {
// --- diagnostics --- --- // --- diagnostics --- ---
export class DiagnostcsAdapter { export class DiagnosticsAdapter {
private _disposables: IDisposable[] = []; private _disposables: IDisposable[] = [];
private _listener: { [uri: string]: IDisposable } = Object.create(null); private _listener: { [uri: string]: IDisposable } = Object.create(null);
constructor(private _languageId: string, private _worker: WorkerAccessor) { constructor(private _languageId: string, private _worker: WorkerAccessor, defaults: LanguageServiceDefaultsImpl) {
const onModelAdd = (model: monaco.editor.IModel): void => { const onModelAdd = (model: monaco.editor.IModel): void => {
let modeId = model.getModeId(); let modeId = model.getModeId();
if (modeId !== this._languageId) { if (modeId !== this._languageId) {
return; return;
} }
let handle: number; let handle: NodeJS.Timer;
this._listener[model.uri.toString()] = model.onDidChangeContent(() => { this._listener[model.uri.toString()] = model.onDidChangeContent(() => {
clearTimeout(handle); clearTimeout(handle);
handle = setTimeout(() => this._doValidate(model.uri, modeId), 500); handle = setTimeout(() => this._doValidate(model.uri, modeId), 500);
@ -66,8 +67,18 @@ export class DiagnostcsAdapter {
this._resetSchema(event.model.uri); this._resetSchema(event.model.uri);
})); }));
this._disposables.push(defaults.onDidChange(_ => {
monaco.editor.getModels().forEach(model => {
if (model.getModeId() === this._languageId) {
onModelRemoved(model);
onModelAdd(model);
}
});
}));
this._disposables.push({ this._disposables.push({
dispose: () => { dispose: () => {
monaco.editor.getModels().forEach(onModelRemoved);
for (let key in this._listener) { for (let key in this._listener) {
this._listener[key].dispose(); this._listener[key].dispose();
} }
@ -104,14 +115,14 @@ export class DiagnostcsAdapter {
} }
function toSeverity(lsSeverity: number): monaco.Severity { function toSeverity(lsSeverity: number): monaco.MarkerSeverity {
switch (lsSeverity) { switch (lsSeverity) {
case ls.DiagnosticSeverity.Error: return monaco.Severity.Error; case ls.DiagnosticSeverity.Error: return monaco.MarkerSeverity.Error;
case ls.DiagnosticSeverity.Warning: return monaco.Severity.Warning; case ls.DiagnosticSeverity.Warning: return monaco.MarkerSeverity.Warning;
case ls.DiagnosticSeverity.Information: case ls.DiagnosticSeverity.Information: return monaco.MarkerSeverity.Info;
case ls.DiagnosticSeverity.Hint: case ls.DiagnosticSeverity.Hint: return monaco.MarkerSeverity.Hint;
default: default:
return monaco.Severity.Info; return monaco.MarkerSeverity.Info;
} }
} }
@ -139,13 +150,12 @@ function fromPosition(position: Position): ls.Position {
return { character: position.column - 1, line: position.lineNumber - 1 }; return { character: position.column - 1, line: position.lineNumber - 1 };
} }
function fromRange(range: Range): ls.Range { function fromRange(range: IRange): ls.Range {
if (!range) { if (!range) {
return void 0; return void 0;
} }
return { start: fromPosition(range.getStartPosition()), end: fromPosition(range.getEndPosition()) }; return { start: { line: range.startLineNumber - 1, character: range.startColumn - 1 }, end: { line: range.endLineNumber - 1, character: range.endColumn - 1 } };
} }
function toRange(range: ls.Range): Range { function toRange(range: ls.Range): Range {
if (!range) { if (!range) {
return void 0; return void 0;
@ -233,12 +243,19 @@ function toCompletionItem(entry: ls.CompletionItem): DataCompletionItem {
}; };
} }
function fromMarkdownString(entry: string | monaco.IMarkdownString): ls.MarkupContent {
return {
kind: (typeof entry === 'string' ? ls.MarkupKind.PlainText : ls.MarkupKind.Markdown),
value: (typeof entry === 'string' ? entry : entry.value)
}
}
function fromCompletionItem(entry: DataCompletionItem): ls.CompletionItem { function fromCompletionItem(entry: DataCompletionItem): ls.CompletionItem {
let item: ls.CompletionItem = { let item: ls.CompletionItem = {
label: entry.label, label: entry.label,
sortText: entry.sortText, sortText: entry.sortText,
filterText: entry.filterText, filterText: entry.filterText,
documentation: entry.documentation, documentation: fromMarkdownString(entry.documentation),
detail: entry.detail, detail: entry.detail,
kind: fromCompletionItemKind(entry.kind), kind: fromCompletionItemKind(entry.kind),
data: entry.data data: entry.data
@ -303,14 +320,38 @@ export class CompletionAdapter implements monaco.languages.CompletionItemProvide
} }
} }
function toMarkedStringArray(contents: ls.MarkedString | ls.MarkedString[]): monaco.MarkedString[] { function isMarkupContent(thing: any): thing is ls.MarkupContent {
return thing && typeof thing === 'object' && typeof (<ls.MarkupContent>thing).kind === 'string';
}
function toMarkdownString(entry: ls.MarkupContent | ls.MarkedString): monaco.IMarkdownString {
if (typeof entry === 'string') {
return {
value: entry
};
}
if (isMarkupContent(entry)) {
if (entry.kind === 'plaintext') {
return {
value: entry.value.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&')
};
}
return {
value: entry.value
};
}
return { value: '```' + entry.language + '\n' + entry.value + '\n```\n' };
}
function toMarkedStringArray(contents: ls.MarkupContent | ls.MarkedString | ls.MarkedString[]): monaco.IMarkdownString[] {
if (!contents) { if (!contents) {
return void 0; return void 0;
} }
if (Array.isArray(contents)) { if (Array.isArray(contents)) {
return (<ls.MarkedString[]>contents); return contents.map(toMarkdownString);
} }
return [<ls.MarkedString>contents]; return [toMarkdownString(contents)];
} }
@ -382,7 +423,7 @@ export class DocumentSymbolAdapter implements monaco.languages.DocumentSymbolPro
constructor(private _worker: WorkerAccessor) { constructor(private _worker: WorkerAccessor) {
} }
public provideDocumentSymbols(model: monaco.editor.IReadOnlyModel, token: CancellationToken): Thenable<monaco.languages.SymbolInformation[]> { public provideDocumentSymbols(model: monaco.editor.IReadOnlyModel, token: CancellationToken): Thenable<monaco.languages.DocumentSymbol[]> {
const resource = model.uri; const resource = model.uri;
return wireCancellationToken(token, this._worker(resource).then(worker => worker.findDocumentSymbols(resource.toString())).then(items => { return wireCancellationToken(token, this._worker(resource).then(worker => worker.findDocumentSymbols(resource.toString())).then(items => {
@ -391,9 +432,11 @@ export class DocumentSymbolAdapter implements monaco.languages.DocumentSymbolPro
} }
return items.map(item => ({ return items.map(item => ({
name: item.name, name: item.name,
detail: '',
containerName: item.containerName, containerName: item.containerName,
kind: toSymbolKind(item.kind), kind: toSymbolKind(item.kind),
location: toLocation(item.location) range: toRange(item.location.range),
selectionRange: toRange(item.location.range)
})); }));
})); }));
} }

View file

@ -1,4 +1,5 @@
/*--------------------------------------------------------------------------------------------- /*---------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Copyright (c) Microsoft Corporation. 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. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/

View file

@ -1,4 +1,5 @@
/*--------------------------------------------------------------------------------------------- /*---------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Copyright (c) Microsoft Corporation. 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. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
@ -43,6 +44,7 @@ export interface JSONSchema {
patternErrorMessage?: string; // VSCode extension patternErrorMessage?: string; // VSCode extension
deprecationMessage?: string; // VSCode extension deprecationMessage?: string; // VSCode extension
enumDescriptions?: string[]; // VSCode extension enumDescriptions?: string[]; // VSCode extension
schemaSequence?: JSONSchema[]; // extension for multiple schemas related to multiple documents in single yaml file
"x-kubernetes-group-version-kind"?; //Kubernetes extension "x-kubernetes-group-version-kind"?; //Kubernetes extension
} }

View file

@ -1,10 +1,11 @@
/*--------------------------------------------------------------------------------------------- /*---------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Copyright (c) Microsoft Corporation. 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. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
import Json = require('jsonc-parser'); import * as Json from 'jsonc-parser';
import { JSONSchema } from '../jsonSchema'; import { JSONSchema } from '../jsonSchema';
import * as objects from '../utils/objects'; import * as objects from '../utils/objects';
@ -118,7 +119,7 @@ export class ASTNode {
collector.push(item); collector.push(item);
} }
} }
return node; return node;
}; };
let foundNode = findNode(this); let foundNode = findNode(this);
return collector.length; return collector.length;
@ -157,7 +158,7 @@ export class ASTNode {
if (!matchingSchemas.include(this)) { if (!matchingSchemas.include(this)) {
return; return;
} }
if (Array.isArray(schema.type)) { if (Array.isArray(schema.type)) {
if ((<string[]>schema.type).indexOf(this.type) === -1) { if ((<string[]>schema.type).indexOf(this.type) === -1) {
validationResult.problems.push({ validationResult.problems.push({
@ -539,7 +540,7 @@ export class StringASTNode extends ASTNode {
}); });
} }
} }
} }
} }
@ -649,7 +650,7 @@ export class ObjectASTNode extends ASTNode {
let seenKeys: { [key: string]: ASTNode } = Object.create(null); let seenKeys: { [key: string]: ASTNode } = Object.create(null);
let unprocessedProperties: string[] = []; let unprocessedProperties: string[] = [];
this.properties.forEach((node) => { this.properties.forEach((node) => {
let key = node.key.value; let key = node.key.value;
//Replace the merge key with the actual values of what the node value points to in seen keys //Replace the merge key with the actual values of what the node value points to in seen keys
@ -682,7 +683,7 @@ export class ObjectASTNode extends ASTNode {
seenKeys[key] = node.value; seenKeys[key] = node.value;
unprocessedProperties.push(key); unprocessedProperties.push(key);
} }
}); });
if (Array.isArray(schema.required)) { if (Array.isArray(schema.required)) {
@ -770,7 +771,7 @@ export class ObjectASTNode extends ASTNode {
} }
}); });
} }
} }
if (schema.maxProperties) { if (schema.maxProperties) {
if (this.properties.length > schema.maxProperties) { if (this.properties.length > schema.maxProperties) {

View file

@ -1,6 +1,10 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Copyright (c) Adam Voss. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
import { JSONSchema } from 'vscode-json-languageservice/lib/jsonSchema';
import { ASTNode, ErrorCode, BooleanASTNode, NullASTNode, ArrayASTNode, NumberASTNode, ObjectASTNode, PropertyASTNode, StringASTNode, IApplicableSchema, JSONDocument } from './jsonParser'; import { ASTNode, ErrorCode, BooleanASTNode, NullASTNode, ArrayASTNode, NumberASTNode, ObjectASTNode, PropertyASTNode, StringASTNode, IApplicableSchema, JSONDocument } from './jsonParser';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
@ -8,8 +12,10 @@ const localize = nls.loadMessageBundle();
import * as Yaml from '../../yaml-ast-parser/index' import * as Yaml from '../../yaml-ast-parser/index'
import { Kind } from '../../yaml-ast-parser/index' import { Kind } from '../../yaml-ast-parser/index'
import { Schema, Type } from 'js-yaml';
import { getLineStartPositions, getPosition } from '../utils/documentPositionCalculator' import { getLineStartPositions, getPosition } from '../utils/documentPositionCalculator'
import YAMLException from '../../yaml-ast-parser/exception';
export class SingleYAMLDocument extends JSONDocument { export class SingleYAMLDocument extends JSONDocument {
private lines; private lines;
@ -153,7 +159,7 @@ function recursivelyBuildAst(parent: ASTNode, node: Yaml.YAMLNode): ASTNode {
switch (type) { switch (type) {
case Yaml.ScalarType.null: { case Yaml.ScalarType.null: {
return new NullASTNode(parent, name, instance.startPosition, instance.endPosition); return new StringASTNode(parent, name, false, instance.startPosition, instance.endPosition);
} }
case Yaml.ScalarType.bool: { case Yaml.ScalarType.bool: {
return new BooleanASTNode(parent, name, Yaml.parseYamlBoolean(value), node.startPosition, node.endPosition) return new BooleanASTNode(parent, name, Yaml.parseYamlBoolean(value), node.startPosition, node.endPosition)
@ -193,11 +199,11 @@ function recursivelyBuildAst(parent: ASTNode, node: Yaml.YAMLNode): ASTNode {
} }
} }
function convertError(e: Yaml.YAMLException) { function convertError(e: YAMLException) {
return { message: `${e.reason}`, location: { start: e.mark.position, end: e.mark.position + e.mark.column, code: ErrorCode.Undefined } } return { message: `${e.reason}`, location: { start: e.mark.position, end: e.mark.position + e.mark.column, code: ErrorCode.Undefined } }
} }
function createJSONDocument(yamlDoc: Yaml.YAMLNode, startPositions: number[]) { function createJSONDocument(yamlDoc: Yaml.YAMLNode, startPositions: number[], text: string) {
let _doc = new SingleYAMLDocument(startPositions); let _doc = new SingleYAMLDocument(startPositions);
_doc.root = recursivelyBuildAst(null, yamlDoc) _doc.root = recursivelyBuildAst(null, yamlDoc)
@ -208,8 +214,18 @@ function createJSONDocument(yamlDoc: Yaml.YAMLNode, startPositions: number[]) {
const duplicateKeyReason = 'duplicate key' const duplicateKeyReason = 'duplicate key'
//Patch ontop of yaml-ast-parser to disable duplicate key message on merge key
let isDuplicateAndNotMergeKey = function (error: YAMLException, yamlText: string) {
let errorConverted = convertError(error);
let errorStart = errorConverted.location.start;
let errorEnd = errorConverted.location.end;
if (error.reason === duplicateKeyReason && yamlText.substring(errorStart, errorEnd).startsWith("<<")) {
return false;
}
return true;
};
const errors = yamlDoc.errors.filter(e => e.reason !== duplicateKeyReason && !e.isWarning).map(e => convertError(e)) const errors = yamlDoc.errors.filter(e => e.reason !== duplicateKeyReason && !e.isWarning).map(e => convertError(e))
const warnings = yamlDoc.errors.filter(e => e.reason === duplicateKeyReason || e.isWarning).map(e => convertError(e)) const warnings = yamlDoc.errors.filter(e => (e.reason === duplicateKeyReason && isDuplicateAndNotMergeKey(e, text)) || e.isWarning).map(e => convertError(e))
errors.forEach(e => _doc.errors.push(e)); errors.forEach(e => _doc.errors.push(e));
warnings.forEach(e => _doc.warnings.push(e)); warnings.forEach(e => _doc.warnings.push(e));
@ -230,13 +246,29 @@ export class YAMLDocument {
} }
export function parse(text: string): YAMLDocument { export function parse(text: string, customTags = []): YAMLDocument {
const startPositions = getLineStartPositions(text) const startPositions = getLineStartPositions(text)
// This is documented to return a YAMLNode even though the // This is documented to return a YAMLNode even though the
// typing only returns a YAMLDocument // typing only returns a YAMLDocument
const yamlDocs = [] const yamlDocs = []
Yaml.loadAll(text, doc => yamlDocs.push(doc), {})
return new YAMLDocument(yamlDocs.map(doc => createJSONDocument(doc, startPositions))); let schemaWithAdditionalTags = Schema.create(customTags.map((tag) => {
} const typeInfo = tag.split(' ');
return new Type(typeInfo[0], { kind: typeInfo[1] || 'scalar' });
}));
//We need compiledTypeMap to be available from schemaWithAdditionalTags before we add the new custom properties
customTags.map((tag) => {
const typeInfo = tag.split(' ');
schemaWithAdditionalTags.compiledTypeMap[typeInfo[0]] = new Type(typeInfo[0], { kind: typeInfo[1] || 'scalar' });
});
let additionalOptions: Yaml.LoadOptions = {
schema: schemaWithAdditionalTags
}
Yaml.loadAll(text, doc => yamlDocs.push(doc), additionalOptions);
return new YAMLDocument(yamlDocs.map(doc => createJSONDocument(doc, startPositions, text)));
}

View file

@ -1,11 +1,11 @@
/*--------------------------------------------------------------------------------------------- /*---------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Copyright (c) Microsoft Corporation. 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. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
import Parser = require('../parser/jsonParser'); import * as Parser from '../parser/jsonParser';
import Strings = require('../utils/strings');
import { SymbolInformation, SymbolKind, TextDocument, Range, Location } from 'vscode-languageserver-types'; import { SymbolInformation, SymbolKind, TextDocument, Range, Location } from 'vscode-languageserver-types';
import { Thenable } from "../yamlLanguageService"; import { Thenable } from "../yamlLanguageService";
@ -18,7 +18,7 @@ export class YAMLDocumentSymbols {
if(!doc || doc["documents"].length === 0){ if(!doc || doc["documents"].length === 0){
return null; return null;
} }
let collectOutlineEntries = (result: SymbolInformation[], node: Parser.ASTNode, containerName: string): SymbolInformation[] => { let collectOutlineEntries = (result: SymbolInformation[], node: Parser.ASTNode, containerName: string): SymbolInformation[] => {
if (node.type === 'array') { if (node.type === 'array') {
(<Parser.ArrayASTNode>node).items.forEach((node: Parser.ASTNode) => { (<Parser.ArrayASTNode>node).items.forEach((node: Parser.ASTNode) => {
@ -26,7 +26,7 @@ export class YAMLDocumentSymbols {
}); });
} else if (node.type === 'object') { } else if (node.type === 'object') {
let objectNode = <Parser.ObjectASTNode>node; let objectNode = <Parser.ObjectASTNode>node;
objectNode.properties.forEach((property: Parser.PropertyASTNode) => { objectNode.properties.forEach((property: Parser.PropertyASTNode) => {
let location = Location.create(document.uri, Range.create(document.positionAt(property.start), document.positionAt(property.end))); let location = Location.create(document.uri, Range.create(document.positionAt(property.start), document.positionAt(property.end)));
let valueNode = property.value; let valueNode = property.value;
@ -48,7 +48,7 @@ export class YAMLDocumentSymbols {
results = results.concat(result); results = results.concat(result);
} }
} }
return results; return results;
} }

View file

@ -1,20 +1,42 @@
/*--------------------------------------------------------------------------------------------- /*---------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Copyright (c) Microsoft Corporation. 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. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
import Json = require('jsonc-parser'); import * as Json from 'jsonc-parser';
import {JSONSchema, JSONSchemaMap} from '../jsonSchema'; import {JSONSchema, JSONSchemaMap} from '../jsonSchema';
import URI from 'vscode-uri'; import URI from 'vscode-uri';
import Strings = require('../utils/strings'); import * as Strings from '../utils/strings';
import Parser = require('../parser/jsonParser');
import {SchemaRequestService, WorkspaceContextService, PromiseConstructor, Thenable} from '../yamlLanguageService'; import {SchemaRequestService, WorkspaceContextService, PromiseConstructor, Thenable} from '../yamlLanguageService';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
/**
* getParseErrorMessage has been removed from jsonc-parser since 1.0.0
*
* see https://github.com/Microsoft/node-jsonc-parser/blob/42ec16f9c91582d4267a0c48199cdac283c90fc9/CHANGELOG.md
* 1.0.0
* remove nls dependency (remove getParseErrorMessage)
*/
function getParseErrorMessage(errorCode: Json.ParseErrorCode): string {
switch (errorCode) {
case Json.ParseErrorCode.InvalidSymbol: return localize('error.invalidSymbol', 'Invalid symbol');
case Json.ParseErrorCode.InvalidNumberFormat: return localize('error.invalidNumberFormat', 'Invalid number format');
case Json.ParseErrorCode.PropertyNameExpected: return localize('error.propertyNameExpected', 'Property name expected');
case Json.ParseErrorCode.ValueExpected: return localize('error.valueExpected', 'Value expected');
case Json.ParseErrorCode.ColonExpected: return localize('error.colonExpected', 'Colon expected');
case Json.ParseErrorCode.CommaExpected: return localize('error.commaExpected', 'Comma expected');
case Json.ParseErrorCode.CloseBraceExpected: return localize('error.closeBraceExpected', 'Closing brace expected');
case Json.ParseErrorCode.CloseBracketExpected: return localize('error.closeBracketExpected', 'Closing bracket expected');
case Json.ParseErrorCode.EndOfFileExpected: return localize('error.endOfFileExpected', 'End of file expected');
default: return '';
}
}
export interface IJSONSchemaService { export interface IJSONSchemaService {
/** /**
@ -82,7 +104,7 @@ export class FilePatternAssociation {
constructor(pattern: string) { constructor(pattern: string) {
this.combinedSchemaId = 'schemaservice://combinedSchema/' + encodeURIComponent(pattern); this.combinedSchemaId = 'schemaservice://combinedSchema/' + encodeURIComponent(pattern);
try { try {
this.patternRegExp = new RegExp(Strings.convertSimple2RegExpPattern(pattern) + '$'); this.patternRegExp = Strings.convertSimple2RegExp(pattern);
} catch (e) { } catch (e) {
// invalid pattern // invalid pattern
this.patternRegExp = null; this.patternRegExp = null;
@ -368,7 +390,7 @@ export class JSONSchemaService implements IJSONSchemaService {
let schemaContent: JSONSchema = {}; let schemaContent: JSONSchema = {};
let jsonErrors = []; let jsonErrors = [];
schemaContent = Json.parse(content, jsonErrors); schemaContent = Json.parse(content, jsonErrors);
let errors = jsonErrors.length ? [localize('json.schema.invalidFormat', 'Unable to parse content from \'{0}\': {1}.', toDisplayString(url), Json.getParseErrorMessage(jsonErrors[0]))] : []; let errors = jsonErrors.length ? [localize('json.schema.invalidFormat', 'Unable to parse content from \'{0}\': {1}.', toDisplayString(url), getParseErrorMessage(jsonErrors[0]))] : [];
return new UnresolvedSchema(schemaContent, errors); return new UnresolvedSchema(schemaContent, errors);
}, },
(error: any) => { (error: any) => {
@ -479,7 +501,7 @@ export class JSONSchemaService implements IJSONSchemaService {
} }
collectEntries(next.items, next.additionalProperties, next.not); collectEntries(next.items, next.additionalProperties, next.not);
collectMapEntries(next.definitions, next.properties, next.patternProperties, <JSONSchemaMap>next.dependencies); collectMapEntries(next.definitions, next.properties, next.patternProperties, <JSONSchemaMap>next.dependencies);
collectArrayEntries(next.anyOf, next.allOf, next.oneOf, <JSONSchema[]>next.items); collectArrayEntries(next.anyOf, next.allOf, next.oneOf, <JSONSchema[]>next.items, next.schemaSequence);
} }
return this.promise.all(openPromises); return this.promise.all(openPromises);
}; };
@ -496,7 +518,7 @@ export class JSONSchemaService implements IJSONSchemaService {
return entry.getCombinedSchema(this).getResolvedSchema(); return entry.getCombinedSchema(this).getResolvedSchema();
} }
} }
return null; return this.promise.resolve(null);
}; };
if (this.customSchemaProvider) { if (this.customSchemaProvider) {
return this.customSchemaProvider(resource).then(schemaUri => { return this.customSchemaProvider(resource).then(schemaUri => {

View file

@ -6,14 +6,14 @@
'use strict'; 'use strict';
import Parser = require('../parser/jsonParser'); import * as Parser from '../parser/jsonParser';
import Json = require('jsonc-parser'); import * as Json from 'jsonc-parser';
import SchemaService = require('./jsonSchemaService'); import * as SchemaService from './jsonSchemaService';
import { JSONSchema } from '../jsonSchema'; import { JSONSchema } from '../jsonSchema';
import { JSONWorkerContribution, CompletionsCollector } from '../jsonContributions'; import { JSONWorkerContribution, CompletionsCollector } from '../jsonContributions';
import { PromiseConstructor, Thenable } from 'vscode-json-languageservice'; import { PromiseConstructor, Thenable } from 'vscode-json-languageservice';
import { CompletionItem, CompletionItemKind, CompletionList, TextDocument, Position, Range, TextEdit } from 'vscode-languageserver-types'; import { CompletionItem, CompletionItemKind, CompletionList, TextDocument, Position, Range, TextEdit, InsertTextFormat } from 'vscode-languageserver-types';
import * as nls from 'vscode-nls'; import * as nls from 'vscode-nls';
import { matchOffsetToDocument } from '../utils/arrUtils'; import { matchOffsetToDocument } from '../utils/arrUtils';
@ -25,11 +25,17 @@ export class YAMLCompletion {
private schemaService: SchemaService.IJSONSchemaService; private schemaService: SchemaService.IJSONSchemaService;
private contributions: JSONWorkerContribution[]; private contributions: JSONWorkerContribution[];
private promise: PromiseConstructor; private promise: PromiseConstructor;
private customTags: Array<String>;
constructor(schemaService: SchemaService.IJSONSchemaService, contributions: JSONWorkerContribution[] = [], promiseConstructor?: PromiseConstructor) { constructor(schemaService: SchemaService.IJSONSchemaService, contributions: JSONWorkerContribution[] = [], promiseConstructor?: PromiseConstructor) {
this.schemaService = schemaService; this.schemaService = schemaService;
this.contributions = contributions; this.contributions = contributions;
this.promise = promiseConstructor || Promise; this.promise = promiseConstructor || Promise;
this.customTags = [];
}
public configure(customTags: Array<String>){
this.customTags = customTags;
} }
public doResolve(item: CompletionItem): Thenable<CompletionItem> { public doResolve(item: CompletionItem): Thenable<CompletionItem> {
@ -44,7 +50,7 @@ export class YAMLCompletion {
return this.promise.resolve(item); return this.promise.resolve(item);
} }
public doComplete(document: TextDocument, position: Position, doc: Parser.JSONDocument): Thenable<CompletionList> { public doComplete(document: TextDocument, position: Position, doc): Thenable<CompletionList> {
let result: CompletionList = { let result: CompletionList = {
items: [], items: [],
@ -52,14 +58,15 @@ export class YAMLCompletion {
}; };
let offset = document.offsetAt(position); let offset = document.offsetAt(position);
if (document.getText()[offset] === ":") { if(document.getText()[offset] === ":"){
return null; return Promise.resolve(result);
} }
let currentDoc = matchOffsetToDocument(offset, doc); let currentDoc = matchOffsetToDocument(offset, doc);
if (currentDoc === null) { if(currentDoc === null){
return null; return Promise.resolve(result);
} }
const currentDocIndex = doc.documents.indexOf(currentDoc);
let node = currentDoc.getNodeFromOffsetEndInclusive(offset); let node = currentDoc.getNodeFromOffsetEndInclusive(offset);
if (this.isInComment(document, node ? node.start : 0, offset)) { if (this.isInComment(document, node ? node.start : 0, offset)) {
return Promise.resolve(result); return Promise.resolve(result);
@ -67,12 +74,32 @@ export class YAMLCompletion {
let currentWord = this.getCurrentWord(document, offset); let currentWord = this.getCurrentWord(document, offset);
let overwriteRange = null;
if(node && node.type === 'null'){
let nodeStartPos = document.positionAt(node.start);
nodeStartPos.character += 1;
let nodeEndPos = document.positionAt(node.end);
nodeEndPos.character += 1;
overwriteRange = Range.create(nodeStartPos, nodeEndPos);
}else if (node && (node.type === 'string' || node.type === 'number' || node.type === 'boolean')) {
overwriteRange = Range.create(document.positionAt(node.start), document.positionAt(node.end));
} else {
let overwriteStart = offset - currentWord.length;
if (overwriteStart > 0 && document.getText()[overwriteStart - 1] === '"') {
overwriteStart--;
}
overwriteRange = Range.create(document.positionAt(overwriteStart), position);
}
let proposed: { [key: string]: CompletionItem } = {}; let proposed: { [key: string]: CompletionItem } = {};
let collector: CompletionsCollector = { let collector: CompletionsCollector = {
add: (suggestion: CompletionItem) => { add: (suggestion: CompletionItem) => {
let existing = proposed[suggestion.label]; let existing = proposed[suggestion.label];
if (!existing) { if (!existing) {
proposed[suggestion.label] = suggestion; proposed[suggestion.label] = suggestion;
if (overwriteRange) {
suggestion.textEdit = TextEdit.replace(overwriteRange, suggestion.insertText);
}
result.items.push(suggestion); result.items.push(suggestion);
} else if (!existing.documentation) { } else if (!existing.documentation) {
existing.documentation = suggestion.documentation; existing.documentation = suggestion.documentation;
@ -94,8 +121,12 @@ export class YAMLCompletion {
return this.schemaService.getSchemaForResource(document.uri).then((schema) => { return this.schemaService.getSchemaForResource(document.uri).then((schema) => {
if (!schema) { if(!schema){
return null; return Promise.resolve(result);
}
let newSchema = schema;
if (schema.schema && schema.schema.schemaSequence && schema.schema.schemaSequence[currentDocIndex]) {
newSchema = new SchemaService.ResolvedSchema(schema.schema.schemaSequence[currentDocIndex]);
} }
let collectionPromises: Thenable<any>[] = []; let collectionPromises: Thenable<any>[] = [];
@ -129,9 +160,14 @@ export class YAMLCompletion {
} }
}); });
if (schema) { let separatorAfter = '';
if (addValue) {
separatorAfter = this.evaluateSeparatorAfter(document, document.offsetAt(overwriteRange.end));
}
if (newSchema) {
// property proposals with schema // property proposals with schema
this.getPropertyCompletions(schema, currentDoc, node, addValue, collector); this.getPropertyCompletions(newSchema, currentDoc, node, addValue, collector, separatorAfter);
} }
let location = node.getPath(); let location = node.getPath();
@ -144,19 +180,24 @@ export class YAMLCompletion {
if ((!schema && currentWord.length > 0 && document.getText().charAt(offset - currentWord.length - 1) !== '"')) { if ((!schema && currentWord.length > 0 && document.getText().charAt(offset - currentWord.length - 1) !== '"')) {
collector.add({ collector.add({
kind: CompletionItemKind.Property, kind: CompletionItemKind.Property,
label: this.getLabelForValue(currentWord) label: this.getLabelForValue(currentWord),
insertText: this.getInsertTextForProperty(currentWord, null, false, separatorAfter),
insertTextFormat: InsertTextFormat.Snippet,
documentation: ''
}); });
} }
} }
// proposals for values // proposals for values
let types: { [type: string]: boolean } = {}; if (newSchema) {
if (schema) { this.getValueCompletions(newSchema, currentDoc, node, offset, document, collector);
this.getValueCompletions(schema, currentDoc, node, offset, document, collector, types);
} }
if (this.contributions.length > 0) { if (this.contributions.length > 0) {
this.getContributedValueCompletions(currentDoc, node, offset, document, collector, collectionPromises); this.getContributedValueCompletions(currentDoc, node, offset, document, collector, collectionPromises);
} }
if (this.customTags.length > 0) {
this.getCustomTagValueCompletions(collector);
}
return this.promise.all(collectionPromises).then(() => { return this.promise.all(collectionPromises).then(() => {
return result; return result;
@ -164,7 +205,7 @@ export class YAMLCompletion {
}); });
} }
private getPropertyCompletions(schema: SchemaService.ResolvedSchema, doc, node: Parser.ASTNode, addValue: boolean, collector: CompletionsCollector): void { private getPropertyCompletions(schema: SchemaService.ResolvedSchema, doc, node: Parser.ASTNode, addValue: boolean, collector: CompletionsCollector, separatorAfter: string): void {
let matchingSchemas = doc.getMatchingSchemas(schema.schema); let matchingSchemas = doc.getMatchingSchemas(schema.schema);
matchingSchemas.forEach((s) => { matchingSchemas.forEach((s) => {
if (s.node === node && !s.inverted) { if (s.node === node && !s.inverted) {
@ -176,18 +217,26 @@ export class YAMLCompletion {
collector.add({ collector.add({
kind: CompletionItemKind.Property, kind: CompletionItemKind.Property,
label: key, label: key,
insertText: `${key}:`, insertText: this.getInsertTextForProperty(key, propertySchema, addValue, separatorAfter),
filterText: this.getFilterTextForValue(key), insertTextFormat: InsertTextFormat.Snippet,
documentation: propertySchema.description || '' documentation: propertySchema.description || ''
}); });
} }
}); });
} }
// Error fix
// If this is a array of string/boolean/number
// test:
// - item1
// it will treated as a property key since `:` has been appended
if (node.type === 'object' && node.parent && node.parent.type === 'array' && s.schema.type !== 'object') {
this.addSchemaValueCompletions(s.schema, collector, separatorAfter)
}
} }
}); });
} }
private getValueCompletions(schema: SchemaService.ResolvedSchema, doc, node: Parser.ASTNode, offset: number, document: TextDocument, collector: CompletionsCollector, types: { [type: string]: boolean }): void { private getValueCompletions(schema: SchemaService.ResolvedSchema, doc, node: Parser.ASTNode, offset: number, document: TextDocument, collector: CompletionsCollector): void {
let offsetForSeparator = offset; let offsetForSeparator = offset;
let parentKey: string = null; let parentKey: string = null;
let valueNode: Parser.ASTNode = null; let valueNode: Parser.ASTNode = null;
@ -198,17 +247,17 @@ export class YAMLCompletion {
node = node.parent; node = node.parent;
} }
if (node && node.type === 'null') { if(node && node.type === 'null'){
let nodeParent = node.parent; let nodeParent = node.parent;
/* /*
* This is going to be an object for some reason and we need to find the property * This is going to be an object for some reason and we need to find the property
* Its an issue with the null node * Its an issue with the null node
*/ */
if (nodeParent && nodeParent.type === "object") { if(nodeParent && nodeParent.type === "object"){
for (let prop in nodeParent["properties"]) { for(let prop in nodeParent["properties"]){
let currNode = nodeParent["properties"][prop]; let currNode = nodeParent["properties"][prop];
if (currNode.key && currNode.key.location === node.location) { if(currNode.key && currNode.key.location === node.location){
node = currNode; node = currNode;
} }
} }
@ -216,7 +265,7 @@ export class YAMLCompletion {
} }
if (!node) { if (!node) {
this.addSchemaValueCompletions(schema.schema, collector, types); this.addSchemaValueCompletions(schema.schema, collector, "");
return; return;
} }
@ -230,6 +279,7 @@ export class YAMLCompletion {
node = node.parent; node = node.parent;
} }
let separatorAfter = this.evaluateSeparatorAfter(document, offsetForSeparator);
if (node && (parentKey !== null || node.type === 'array')) { if (node && (parentKey !== null || node.type === 'array')) {
let matchingSchemas = doc.getMatchingSchemas(schema.schema); let matchingSchemas = doc.getMatchingSchemas(schema.schema);
matchingSchemas.forEach(s => { matchingSchemas.forEach(s => {
@ -238,30 +288,30 @@ export class YAMLCompletion {
if (Array.isArray(s.schema.items)) { if (Array.isArray(s.schema.items)) {
let index = this.findItemAtOffset(node, document, offset); let index = this.findItemAtOffset(node, document, offset);
if (index < s.schema.items.length) { if (index < s.schema.items.length) {
this.addSchemaValueCompletions(s.schema.items[index], collector, types); this.addSchemaValueCompletions(s.schema.items[index], collector, separatorAfter, true);
} }
} else { } else if (s.schema.items.type === 'object') {
this.addSchemaValueCompletions(s.schema.items, collector, types); collector.add({
kind: this.getSuggestionKind(s.schema.items.type),
label: `- (array item)`,
documentation: `Create an item of an array${s.schema.description === undefined ? '' : '(' + s.schema.description + ')'}`,
insertText: `- ${this.getInsertTextForObject(s.schema.items, separatorAfter).insertText.trimLeft()}`,
insertTextFormat: InsertTextFormat.Snippet,
});
}
else {
this.addSchemaValueCompletions(s.schema.items, collector, separatorAfter, true);
} }
} }
if (s.schema.properties) { if (s.schema.properties) {
let propertySchema = s.schema.properties[parentKey]; let propertySchema = s.schema.properties[parentKey];
if (propertySchema) { if (propertySchema) {
this.addSchemaValueCompletions(propertySchema, collector, types); this.addSchemaValueCompletions(propertySchema, collector, separatorAfter, false);
} }
} }
} }
}); });
} }
if (node) {
if (types['boolean']) {
this.addBooleanValueCompletion(true, collector);
this.addBooleanValueCompletion(false, collector);
}
if (types['null']) {
this.addNullValueCompletion(collector);
}
}
} }
private getContributedValueCompletions(doc: Parser.JSONDocument, node: Parser.ASTNode, offset: number, document: TextDocument, collector: CompletionsCollector, collectionPromises: Thenable<any>[]) { private getContributedValueCompletions(doc: Parser.JSONDocument, node: Parser.ASTNode, offset: number, document: TextDocument, collector: CompletionsCollector, collectionPromises: Thenable<any>[]) {
@ -293,22 +343,43 @@ export class YAMLCompletion {
} }
} }
private addSchemaValueCompletions(schema: JSONSchema, collector: CompletionsCollector, types: { [type: string]: boolean }): void { private getCustomTagValueCompletions(collector: CompletionsCollector) {
this.addDefaultValueCompletions(schema, collector); this.customTags.forEach((customTagItem) => {
this.addEnumValueCompletions(schema, collector); let tagItemSplit = customTagItem.split(" ");
this.collectTypes(schema, types); if(tagItemSplit && tagItemSplit[0]){
if (Array.isArray(schema.allOf)) { this.addCustomTagValueCompletion(collector, " ", tagItemSplit[0]);
schema.allOf.forEach(s => this.addSchemaValueCompletions(s, collector, types)); }
});
}
private addSchemaValueCompletions(schema: JSONSchema, collector: CompletionsCollector, separatorAfter: string, forArrayItem = false): void {
let types: { [type: string]: boolean } = {};
this.addSchemaValueCompletionsCore(schema, collector, types, separatorAfter, forArrayItem);
if (types['boolean']) {
this.addBooleanValueCompletion(true, collector, separatorAfter);
this.addBooleanValueCompletion(false, collector, separatorAfter);
} }
if (Array.isArray(schema.anyOf)) { if (types['null']) {
schema.anyOf.forEach(s => this.addSchemaValueCompletions(s, collector, types)); this.addNullValueCompletion(collector, separatorAfter);
}
if (Array.isArray(schema.oneOf)) {
schema.oneOf.forEach(s => this.addSchemaValueCompletions(s, collector, types));
} }
} }
private addDefaultValueCompletions(schema: JSONSchema, collector: CompletionsCollector, arrayDepth = 0): void { private addSchemaValueCompletionsCore(schema: JSONSchema, collector: CompletionsCollector, types: { [type: string]: boolean }, separatorAfter: string, forArrayItem = false): void {
this.addDefaultValueCompletions(schema, collector, separatorAfter, 0, forArrayItem);
this.addEnumValueCompletions(schema, collector, separatorAfter, forArrayItem);
this.collectTypes(schema, types);
if (Array.isArray(schema.allOf)) {
schema.allOf.forEach(s => this.addSchemaValueCompletionsCore(s, collector, types, separatorAfter, forArrayItem));
}
if (Array.isArray(schema.anyOf)) {
schema.anyOf.forEach(s => this.addSchemaValueCompletionsCore(s, collector, types, separatorAfter, forArrayItem));
}
if (Array.isArray(schema.oneOf)) {
schema.oneOf.forEach(s => this.addSchemaValueCompletionsCore(s, collector, types, separatorAfter, forArrayItem));
}
}
private addDefaultValueCompletions(schema: JSONSchema, collector: CompletionsCollector, separatorAfter: string, arrayDepth = 0, forArrayItem = false): void {
let hasProposals = false; let hasProposals = false;
if (schema.default) { if (schema.default) {
let type = schema.type; let type = schema.type;
@ -319,17 +390,19 @@ export class YAMLCompletion {
} }
collector.add({ collector.add({
kind: this.getSuggestionKind(type), kind: this.getSuggestionKind(type),
label: this.getLabelForValue(value), label: forArrayItem ? `- ${this.getLabelForValue(value)}` : this.getLabelForValue(value),
insertText: forArrayItem ? `- ${this.getInsertTextForValue(value, separatorAfter)}` : this.getInsertTextForValue(value, separatorAfter),
insertTextFormat: InsertTextFormat.Snippet,
detail: localize('json.suggest.default', 'Default value'), detail: localize('json.suggest.default', 'Default value'),
}); });
hasProposals = true; hasProposals = true;
} }
if (!hasProposals && schema.items && !Array.isArray(schema.items)) { if (!hasProposals && schema.items && !Array.isArray(schema.items)) {
this.addDefaultValueCompletions(schema.items, collector, arrayDepth + 1); this.addDefaultValueCompletions(schema.items, collector, separatorAfter, arrayDepth + 1);
} }
} }
private addEnumValueCompletions(schema: JSONSchema, collector: CompletionsCollector): void { private addEnumValueCompletions(schema: JSONSchema, collector: CompletionsCollector, separatorAfter: string, forArrayItem = false): void {
if (Array.isArray(schema.enum)) { if (Array.isArray(schema.enum)) {
for (let i = 0, length = schema.enum.length; i < length; i++) { for (let i = 0, length = schema.enum.length; i < length; i++) {
let enm = schema.enum[i]; let enm = schema.enum[i];
@ -339,7 +412,9 @@ export class YAMLCompletion {
} }
collector.add({ collector.add({
kind: this.getSuggestionKind(schema.type), kind: this.getSuggestionKind(schema.type),
label: this.getLabelForValue(enm), label: forArrayItem ? `- ${this.getLabelForValue(enm)}` : this.getLabelForValue(enm),
insertText: forArrayItem ? `- ${this.getInsertTextForValue(enm, separatorAfter)}` : this.getInsertTextForValue(enm, separatorAfter),
insertTextFormat: InsertTextFormat.Snippet,
documentation documentation
}); });
} }
@ -355,18 +430,32 @@ export class YAMLCompletion {
} }
} }
private addBooleanValueCompletion(value: boolean, collector: CompletionsCollector): void { private addBooleanValueCompletion(value: boolean, collector: CompletionsCollector, separatorAfter: string): void {
collector.add({ collector.add({
kind: this.getSuggestionKind('boolean'), kind: this.getSuggestionKind('boolean'),
label: value ? 'true' : 'false', label: value ? 'true' : 'false',
insertText: this.getInsertTextForValue(value, separatorAfter),
insertTextFormat: InsertTextFormat.Snippet,
documentation: '' documentation: ''
}); });
} }
private addNullValueCompletion(collector: CompletionsCollector): void { private addNullValueCompletion(collector: CompletionsCollector, separatorAfter: string): void {
collector.add({ collector.add({
kind: this.getSuggestionKind('null'), kind: this.getSuggestionKind('null'),
label: 'null', label: 'null',
insertText: 'null' + separatorAfter,
insertTextFormat: InsertTextFormat.Snippet,
documentation: ''
});
}
private addCustomTagValueCompletion(collector: CompletionsCollector, separatorAfter: string, label: string): void {
collector.add({
kind: this.getSuggestionKind('string'),
label: label,
insertText: label + separatorAfter,
insertTextFormat: InsertTextFormat.Snippet,
documentation: '' documentation: ''
}); });
} }
@ -379,10 +468,6 @@ export class YAMLCompletion {
return label; return label;
} }
private getFilterTextForValue(value): string {
return JSON.stringify(value);
}
private getSuggestionKind(type: any): CompletionItemKind { private getSuggestionKind(type: any): CompletionItemKind {
if (Array.isArray(type)) { if (Array.isArray(type)) {
let array = <any[]>type; let array = <any[]>type;
@ -436,4 +521,178 @@ export class YAMLCompletion {
} }
return (token === Json.SyntaxKind.LineCommentTrivia || token === Json.SyntaxKind.BlockCommentTrivia) && scanner.getTokenOffset() <= offset; return (token === Json.SyntaxKind.LineCommentTrivia || token === Json.SyntaxKind.BlockCommentTrivia) && scanner.getTokenOffset() <= offset;
} }
private getInsertTextForPlainText(text: string): string {
return text.replace(/[\\\$\}]/g, '\\$&'); // escape $, \ and }
}
private getInsertTextForValue(value: any, separatorAfter: string): string {
var text = value;
if (text === '{}') {
return '{\n\t$1\n}' + separatorAfter;
} else if (text === '[]') {
return '[\n\t$1\n]' + separatorAfter;
}
return this.getInsertTextForPlainText(text + separatorAfter);
}
private getInsertTextForObject(schema: JSONSchema, separatorAfter: string, indent = '\t', insertIndex = 1) {
let insertText = "";
if (!schema.properties) {
insertText = `${indent}\$${insertIndex++}\n`;
return { insertText, insertIndex };
}
Object.keys(schema.properties).forEach((key: string) => {
let propertySchema = schema.properties[key];
let type = Array.isArray(propertySchema.type) ? propertySchema.type[0] : propertySchema.type;
if (!type) {
if (propertySchema.properties) {
type = 'object';
}
if (propertySchema.items) {
type = 'array';
}
}
if (schema.required && schema.required.indexOf(key) > -1) {
switch (type) {
case 'boolean':
case 'string':
case 'number':
case 'integer':
insertText += `${indent}${key}: \$${insertIndex++}\n`
break;
case 'array':
let arrayInsertResult = this.getInsertTextForArray(propertySchema.items, separatorAfter, `${indent}\t`, insertIndex++);
insertIndex = arrayInsertResult.insertIndex;
insertText += `${indent}${key}:\n${indent}\t- ${arrayInsertResult.insertText}\n`;
break;
case 'object':
let objectInsertResult = this.getInsertTextForObject(propertySchema, separatorAfter, `${indent}\t`, insertIndex++);
insertIndex = objectInsertResult.insertIndex;
insertText += `${indent}${key}:\n${objectInsertResult.insertText}\n`;
break;
}
} else if (propertySchema.default !== undefined) {
switch (type) {
case 'boolean':
case 'string':
case 'number':
case 'integer':
insertText += `${indent}${key}: \${${insertIndex++}:${propertySchema.default}}\n`
break;
case 'array':
case 'object':
// TODO: support default value for array object
break;
}
}
});
if (insertText.trim().length === 0) {
insertText = `${indent}\$${insertIndex++}\n`;
}
insertText = insertText.trimRight() + separatorAfter;
return { insertText, insertIndex };
}
private getInsertTextForArray(schema: JSONSchema, separatorAfter: string, indent = '\t', insertIndex = 1) {
let insertText = '';
if (!schema) {
insertText = `\$${insertIndex++}`;
}
let type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
if (!type) {
if (schema.properties) {
type = 'object';
}
if (schema.items) {
type = 'array';
}
}
switch (schema.type) {
case 'boolean':
insertText = `\${${insertIndex++}:false}`;
break;
case 'number':
case 'integer':
insertText = `\${${insertIndex++}:0}`;
break;
case 'string':
insertText = `\${${insertIndex++}:null}`;
break;
case 'object':
let objectInsertResult = this.getInsertTextForObject(schema, separatorAfter, `${indent}\t`, insertIndex++);
insertText = objectInsertResult.insertText.trimLeft();
insertIndex = objectInsertResult.insertIndex;
break;
}
return { insertText, insertIndex };
}
private getInsertTextForProperty(key: string, propertySchema: JSONSchema, addValue: boolean, separatorAfter: string): string {
let propertyText = this.getInsertTextForValue(key, '');
// if (!addValue) {
// return propertyText;
// }
let resultText = propertyText + ':';
let value;
if (propertySchema) {
if (propertySchema.default !== undefined) {
value = ` \${1:${propertySchema.default}}`
}
else if (propertySchema.properties) {
return `${resultText}\n${this.getInsertTextForObject(propertySchema, separatorAfter).insertText}`;
}
else if (propertySchema.items) {
return `${resultText}\n\t- ${this.getInsertTextForArray(propertySchema.items, separatorAfter).insertText}`;
}
else {
var type = Array.isArray(propertySchema.type) ? propertySchema.type[0] : propertySchema.type;
switch (type) {
case 'boolean':
value = ' $1';
break;
case 'string':
value = ' $1';
break;
case 'object':
value = '\n\t';
break;
case 'array':
value = '\n\t- ';
break;
case 'number':
case 'integer':
value = ' ${1:0}';
break;
case 'null':
value = ' ${1:null}';
break;
default:
return propertyText;
}
}
}
if (!value) {
value = '$1';
}
return resultText + value + separatorAfter;
}
private evaluateSeparatorAfter(document: TextDocument, offset: number) {
let scanner = Json.createScanner(document.getText(), true);
scanner.setPosition(offset);
let token = scanner.scan();
switch (token) {
case Json.SyntaxKind.CommaToken:
case Json.SyntaxKind.CloseBraceToken:
case Json.SyntaxKind.CloseBracketToken:
case Json.SyntaxKind.EOF:
return '';
default:
return '';
}
}
} }

View file

@ -0,0 +1,47 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Copyright (c) Adam Voss. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as jsyaml from 'js-yaml';
import * as Yaml from '../../yaml-ast-parser/index'
import { EOL } from '../../fillers/os';
import { TextDocument, Range, Position, FormattingOptions, TextEdit } from 'vscode-languageserver-types';
export function format(document: TextDocument, options: FormattingOptions, customTags: Array<String>): TextEdit[] {
const text = document.getText();
let schemaWithAdditionalTags = jsyaml.Schema.create(customTags.map((tag) => {
const typeInfo = tag.split(' ');
return new jsyaml.Type(typeInfo[0], { kind: typeInfo[1] || 'scalar' });
}));
//We need compiledTypeMap to be available from schemaWithAdditionalTags before we add the new custom properties
customTags.map((tag) => {
const typeInfo = tag.split(' ');
schemaWithAdditionalTags.compiledTypeMap[typeInfo[0]] = new jsyaml.Type(typeInfo[0], { kind: typeInfo[1] || 'scalar' });
});
let additionalOptions: Yaml.LoadOptions = {
schema: schemaWithAdditionalTags
}
const documents = []
jsyaml.loadAll(text, doc => documents.push(doc), additionalOptions)
const dumpOptions = { indent: options.tabSize, noCompatMode: true };
let newText;
if (documents.length == 1) {
const yaml = documents[0]
newText = jsyaml.safeDump(yaml, dumpOptions)
}
else {
const formatted = documents.map(d => jsyaml.safeDump(d, dumpOptions))
newText = '%YAML 1.2' + EOL + '---' + EOL + formatted.join('...' + EOL + '---' + EOL) + '...' + EOL
}
return [TextEdit.replace(Range.create(Position.create(0, 0), document.positionAt(text.length)), newText)]
}

View file

@ -1,12 +1,13 @@
/*--------------------------------------------------------------------------------------------- /*---------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Copyright (c) Microsoft Corporation. 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. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
import Parser = require('../parser/jsonParser'); import * as Parser from '../parser/jsonParser';
import SchemaService = require('./jsonSchemaService'); import * as SchemaService from './jsonSchemaService';
import {JSONWorkerContribution} from '../jsonContributions'; import {JSONWorkerContribution} from '../jsonContributions';
import {PromiseConstructor, Thenable} from 'vscode-json-languageservice'; import {PromiseConstructor, Thenable} from 'vscode-json-languageservice';
@ -25,13 +26,18 @@ export class YAMLHover {
this.promise = promiseConstructor || Promise; this.promise = promiseConstructor || Promise;
} }
public doHover(document: TextDocument, position: Position, doc: Parser.JSONDocument): Thenable<Hover> { public doHover(document: TextDocument, position: Position, doc): Thenable<Hover> {
if(!document){
this.promise.resolve(void 0);
}
let offset = document.offsetAt(position); let offset = document.offsetAt(position);
let currentDoc = matchOffsetToDocument(offset, doc); let currentDoc = matchOffsetToDocument(offset, doc);
if(currentDoc === null){ if(currentDoc === null){
return null; return this.promise.resolve(void 0);
} }
const currentDocIndex = doc.documents.indexOf(currentDoc);
let node = currentDoc.getNodeFromOffset(offset); let node = currentDoc.getNodeFromOffset(offset);
if (!node || (node.type === 'object' || node.type === 'array') && offset > node.start + 1 && offset < node.end - 1) { if (!node || (node.type === 'object' || node.type === 'array') && offset > node.start + 1 && offset < node.end - 1) {
return this.promise.resolve(void 0); return this.promise.resolve(void 0);
@ -46,7 +52,7 @@ export class YAMLHover {
node = propertyNode.value; node = propertyNode.value;
if (!node) { if (!node) {
return this.promise.resolve(void 0); return this.promise.resolve(void 0);
} }
} }
} }
@ -71,8 +77,11 @@ export class YAMLHover {
return this.schemaService.getSchemaForResource(document.uri).then((schema) => { return this.schemaService.getSchemaForResource(document.uri).then((schema) => {
if (schema) { if (schema) {
let newSchema = schema;
let matchingSchemas = currentDoc.getMatchingSchemas(schema.schema, node.start); if (schema.schema && schema.schema.schemaSequence && schema.schema.schemaSequence[currentDocIndex]) {
newSchema = new SchemaService.ResolvedSchema(schema.schema.schemaSequence[currentDocIndex]);
}
let matchingSchemas = currentDoc.getMatchingSchemas(newSchema.schema, node.start);
let title: string = null; let title: string = null;
let markdownDescription: string = null; let markdownDescription: string = null;

View file

@ -1,15 +1,17 @@
/*--------------------------------------------------------------------------------------------- /*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Red Hat, Inc. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information. * 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 { JSONSchemaService } from './jsonSchemaService'; import { JSONSchemaService, ResolvedSchema } from './jsonSchemaService';
import { JSONDocument, ObjectASTNode, IProblem, ProblemSeverity } from '../parser/jsonParser'; import { JSONDocument, ObjectASTNode, IProblem, ProblemSeverity } from '../parser/jsonParser';
import { TextDocument, Diagnostic, DiagnosticSeverity } from 'vscode-languageserver-types'; import { TextDocument, Diagnostic, DiagnosticSeverity } from 'vscode-languageserver-types';
import { LanguageSettings, PromiseConstructor, Thenable } from '../yamlLanguageService'; import { PromiseConstructor, Thenable, LanguageSettings} from '../yamlLanguageService';
export class YAMLValidation { export class YAMLValidation {
private jsonSchemaService: JSONSchemaService; private jsonSchemaService: JSONSchemaService;
private promise: PromiseConstructor; private promise: PromiseConstructor;
private comments: boolean; private comments: boolean;
@ -21,35 +23,59 @@ export class YAMLValidation {
this.validationEnabled = true; this.validationEnabled = true;
} }
public configure(shouldValidate: LanguageSettings) { public configure(shouldValidate: LanguageSettings){
if (shouldValidate) { if(shouldValidate){
this.validationEnabled = shouldValidate.validate; this.validationEnabled = shouldValidate.validate;
} }
} }
public doValidation(textDocument, yamlDocument) { public doValidation(textDocument, yamlDocument) {
if (!this.validationEnabled) { if(!this.validationEnabled){
return this.promise.resolve([]); return this.promise.resolve([]);
} }
return this.jsonSchemaService.getSchemaForResource(textDocument.uri).then(function (schema) { return this.jsonSchemaService.getSchemaForResource(textDocument.uri).then(function (schema) {
var diagnostics = [];
var added = {};
let newSchema = schema;
if (schema) { if (schema) {
let documentIndex = 0;
for (let currentYAMLDoc in yamlDocument.documents) { for(let currentYAMLDoc in yamlDocument.documents){
let currentDoc = yamlDocument.documents[currentYAMLDoc]; let currentDoc = yamlDocument.documents[currentYAMLDoc];
let diagnostics = currentDoc.getValidationProblems(schema.schema); if (schema.schema && schema.schema.schemaSequence && schema.schema.schemaSequence[documentIndex]) {
for (let diag in diagnostics) { newSchema = new ResolvedSchema(schema.schema.schemaSequence[documentIndex]);
}
let diagnostics = currentDoc.getValidationProblems(newSchema.schema);
for(let diag in diagnostics){
let curDiagnostic = diagnostics[diag]; let curDiagnostic = diagnostics[diag];
currentDoc.errors.push({ location: { start: curDiagnostic.location.start, end: curDiagnostic.location.end }, message: curDiagnostic.message }) currentDoc.errors.push({ location: { start: curDiagnostic.location.start, end: curDiagnostic.location.end }, message: curDiagnostic.message })
} }
documentIndex++;
} }
}
if(newSchema && newSchema.errors.length > 0){
for(let curDiagnostic of newSchema.errors){
diagnostics.push({
severity: DiagnosticSeverity.Error,
range: {
start: {
line: 0,
character: 0
},
end: {
line: 0,
character: 1
}
},
message: curDiagnostic
});
}
} }
var diagnostics = []; for(let currentYAMLDoc in yamlDocument.documents){
var added = {};
for (let currentYAMLDoc in yamlDocument.documents) {
let currentDoc = yamlDocument.documents[currentYAMLDoc]; let currentDoc = yamlDocument.documents[currentYAMLDoc];
currentDoc.errors.concat(currentDoc.warnings).forEach(function (error, idx) { currentDoc.errors.concat(currentDoc.warnings).forEach(function (error, idx) {
// remove duplicated messages // remove duplicated messages

View file

@ -0,0 +1,75 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SingleYAMLDocument } from "../parser/yamlParser";
export function removeDuplicates(arr, prop) {
var new_arr = [];
var lookup = {};
for (var i in arr) {
lookup[arr[i][prop]] = arr[i];
}
for (i in lookup) {
new_arr.push(lookup[i]);
}
return new_arr;
}
export function getLineOffsets(textDocString: String): number[] {
let lineOffsets: number[] = [];
let text = textDocString;
let isLineStart = true;
for (let i = 0; i < text.length; i++) {
if (isLineStart) {
lineOffsets.push(i);
isLineStart = false;
}
let ch = text.charAt(i);
isLineStart = (ch === '\r' || ch === '\n');
if (ch === '\r' && i + 1 < text.length && text.charAt(i + 1) === '\n') {
i++;
}
}
if (isLineStart && text.length > 0) {
lineOffsets.push(text.length);
}
return lineOffsets;
}
export function removeDuplicatesObj(objArray){
let nonDuplicateSet = new Set();
let nonDuplicateArr = [];
for(let obj in objArray){
let currObj = objArray[obj];
let stringifiedObj = JSON.stringify(currObj);
if(!nonDuplicateSet.has(stringifiedObj)){
nonDuplicateArr.push(currObj);
nonDuplicateSet.add(stringifiedObj);
}
}
return nonDuplicateArr;
}
export function matchOffsetToDocument(offset: number, jsonDocuments): SingleYAMLDocument {
for(let jsonDoc in jsonDocuments.documents){
let currJsonDoc = jsonDocuments.documents[jsonDoc];
if(currJsonDoc.root && currJsonDoc.root.end >= offset && currJsonDoc.root.start <= offset){
return currJsonDoc;
}
}
return null;
}

View file

@ -1,3 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. 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" "use strict"
export function insertionPointReturnValue(pt: number) { export function insertionPointReturnValue(pt: number) {

View file

@ -2,12 +2,11 @@
* Copyright (c) Red Hat, Inc. All rights reserved. * Copyright (c) Red Hat, Inc. All rights reserved.
* 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.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { DiagnosticSeverity } from "vscode-languageserver/lib/main";
export class ErrorHandler { export class ErrorHandler {
private errorResultsList; private errorResultsList;
private textDocument; private textDocument;
constructor(textDocument){ constructor(textDocument){
this.errorResultsList = []; this.errorResultsList = [];
this.textDocument = textDocument; this.textDocument = textDocument;
@ -22,7 +21,7 @@ export class ErrorHandler {
}, },
message: errorMessage message: errorMessage
}); });
} }
public getErrorResultsList(){ public getErrorResultsList(){

View file

@ -32,6 +32,16 @@ export function endsWith(haystack: string, needle: string): boolean {
} }
} }
export function convertSimple2RegExpPattern(pattern: string): string { export function convertSimple2RegExp(pattern: string): RegExp {
return pattern.replace(/[\-\\\{\}\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&').replace(/[\*]/g, '.*'); var match = pattern.match(new RegExp('^/(.*?)/([gimy]*)$'));
return match ? convertRegexString2RegExp(match[1], match[2])
: convertGlobalPattern2RegExp(pattern)
}
function convertGlobalPattern2RegExp(pattern: string): RegExp {
return new RegExp(pattern.replace(/[\-\\\{\}\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&').replace(/[\*]/g, '.*') + '$');
}
function convertRegexString2RegExp(pattern: string, flag: string): RegExp {
return new RegExp(pattern, flag);
} }

View file

@ -0,0 +1,155 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. 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.
*--------------------------------------------------------------------------------------------*/
import { JSONSchemaService } from "./services/jsonSchemaService";
import {
TextDocument,
Position,
CompletionList,
FormattingOptions,
Diagnostic
} from "vscode-languageserver-types";
import { JSONSchema } from "./jsonSchema";
import { YAMLDocumentSymbols } from "./services/documentSymbols";
import { YAMLCompletion } from "./services/yamlCompletion";
import { JSONDocument } from "vscode-json-languageservice";
import { YAMLHover } from "./services/yamlHover";
import { YAMLValidation } from "./services/yamlValidation";
import { format } from "./services/yamlFormatter";
import { parse as parseYAML } from "./parser/yamlParser";
export interface LanguageSettings {
validate?: boolean; //Setting for whether we want to validate the schema
isKubernetes?: boolean; //If true then its validating against kubernetes
schemas?: any[]; //List of schemas,
customTags?: Array<String>; //Array of Custom Tags
}
export type YAMLDocument = { documents: JSONDocument[] };
export interface PromiseConstructor {
/**
* Creates a new Promise.
* @param executor A callback used to initialize the promise. This callback is passed two arguments:
* a resolve callback used resolve the promise with a value or the result of another promise,
* and a reject callback used to reject the promise with a provided reason or error.
*/
new <T>(
executor: (
resolve: (value?: T | Thenable<T>) => void,
reject: (reason?: any) => void
) => void
): Thenable<T>;
/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T>(values: Array<T | Thenable<T>>): Thenable<T[]>;
/**
* Creates a new rejected promise for the provided reason.
* @param reason The reason the promise was rejected.
* @returns A new rejected Promise.
*/
reject<T>(reason: any): Thenable<T>;
/**
* Creates a new resolved promise for the provided value.
* @param value A promise.
* @returns A promise whose internal state matches the provided promise.
*/
resolve<T>(value: T | Thenable<T>): Thenable<T>;
}
export interface Thenable<R> {
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of which ever callback is executed.
*/
then<TResult>(
onfulfilled?: (value: R) => TResult | Thenable<TResult>,
onrejected?: (reason: any) => TResult | Thenable<TResult>
): Thenable<TResult>;
then<TResult>(
onfulfilled?: (value: R) => TResult | Thenable<TResult>,
onrejected?: (reason: any) => void
): Thenable<TResult>;
}
export interface WorkspaceContextService {
resolveRelativePath(relativePath: string, resource: string): string;
}
/**
* The schema request service is used to fetch schemas. The result should the schema file comment, or,
* in case of an error, a displayable error string
*/
export interface SchemaRequestService {
(uri: string): Thenable<string>;
}
export interface SchemaConfiguration {
/**
* 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.
* If no schema is provided, the schema will be fetched with the schema request service (if available).
*/
schema?: JSONSchema;
}
export interface LanguageService {
configure(settings): void;
doComplete(document: TextDocument, position: Position, doc): Thenable<CompletionList>;
doValidation(document: TextDocument, yamlDocument): Thenable<Diagnostic[]>;
doHover(document: TextDocument, position: Position, doc);
findDocumentSymbols(document: TextDocument, doc);
doResolve(completionItem);
resetSchema(uri: string): boolean;
doFormat(document: TextDocument, options: FormattingOptions, customTags: Array<String>);
parseYAMLDocument(document: TextDocument): YAMLDocument;
}
export function getLanguageService(schemaRequestService, workspaceContext, contributions, customSchemaProvider, promiseConstructor?): LanguageService {
let promise = promiseConstructor || Promise;
let schemaService = new JSONSchemaService(schemaRequestService, workspaceContext, customSchemaProvider);
let completer = new YAMLCompletion(schemaService, contributions, promise);
let hover = new YAMLHover(schemaService, contributions, promise);
let yamlDocumentSymbols = new YAMLDocumentSymbols();
let yamlValidation = new YAMLValidation(schemaService, promise);
return {
configure: (settings) => {
schemaService.clearExternalSchemas();
if (settings.schemas) {
settings.schemas.forEach(settings => {
schemaService.registerExternalSchema(settings.uri, settings.fileMatch, settings.schema);
});
}
yamlValidation.configure(settings);
let customTagsSetting = settings && settings["customTags"] ? settings["customTags"] : [];
completer.configure(customTagsSetting);
},
doComplete: completer.doComplete.bind(completer),
doResolve: completer.doResolve.bind(completer),
doValidation: yamlValidation.doValidation.bind(yamlValidation),
doHover: hover.doHover.bind(hover),
findDocumentSymbols: yamlDocumentSymbols.findDocumentSymbols.bind(yamlDocumentSymbols),
resetSchema: (uri: string) => schemaService.onResourceChange(uri),
doFormat: format,
parseYAMLDocument: (document: TextDocument) => parseYAML(document.getText())
}
}

View file

@ -8,11 +8,10 @@ import * as mode from './yamlMode';
import Emitter = monaco.Emitter; import Emitter = monaco.Emitter;
import IEvent = monaco.IEvent; import IEvent = monaco.IEvent;
import IDisposable = monaco.IDisposable;
declare var require: <T>(moduleId: [string], callback: (module: T) => void) => void; declare var require: <T>(moduleId: [string], callback: (module: T) => void) => void;
// --- JSON configuration and defaults --------- // --- YAML configuration and defaults ---------
export class LanguageServiceDefaultsImpl implements monaco.languages.yaml.LanguageServiceDefaults { export class LanguageServiceDefaultsImpl implements monaco.languages.yaml.LanguageServiceDefaults {
@ -62,7 +61,7 @@ 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/languages/yaml/yamlMode'], callback); require<typeof mode>(['vs/language/yaml/yamlMode'], callback);
} }
monaco.languages.register({ monaco.languages.register({

43
src/monaco.d.ts vendored
View file

@ -5,34 +5,35 @@
declare module monaco.languages.yaml { 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.
*/
readonly validate?: boolean;
/**
* A list of known schemas and/or associations of schemas to file names.
*/
readonly schemas?: {
/** /**
* If set, the validator will be enabled and perform syntax validation as well as schema based validation. * The URI of the schema, which is also the identifier of the schema.
*/ */
readonly validate?: boolean; readonly uri: string;
/** /**
* A list of known schemas and/or associations of schemas to file names. * A list of file names that are associated to the schema. The '*' wildcard can be used. For example '*.schema.json', 'package.json'
*/ */
readonly schemas?: { readonly fileMatch?: string[];
/** /**
* The URI of the schema, which is also the identifier of the schema. * The schema for the given URI.
*/ */
readonly uri: string; readonly schema?: any;
/** }[];
* A list of file names that are associated to the schema. The '*' wildcard can be used. For example '*.schema.json', 'package.json'
*/
readonly fileMatch?: string[];
/**
* The schema for the given URI.
*/
readonly schema?: any;
}[];
} }
export interface LanguageServiceDefaults { export interface LanguageServiceDefaults {
readonly onDidChange: IEvent<LanguageServiceDefaults>; readonly onDidChange: IEvent<LanguageServiceDefaults>;
readonly diagnosticsOptions: DiagnosticsOptions; readonly diagnosticsOptions: DiagnosticsOptions;
setDiagnosticsOptions(options: DiagnosticsOptions): void; setDiagnosticsOptions(options: DiagnosticsOptions): void;
} }
export var yamlDefaults: LanguageServiceDefaults; export var yamlDefaults: LanguageServiceDefaults;
} }

16
src/tsconfig.esm.json Normal file
View file

@ -0,0 +1,16 @@
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "node",
"outDir": "../out/esm",
"target": "es5",
"lib": [
"dom",
"es5",
"es2015.collection",
"es2015.promise",
"es2016",
"es2017.string"
]
}
}

View file

@ -2,7 +2,17 @@
"compilerOptions": { "compilerOptions": {
"module": "umd", "module": "umd",
"moduleResolution": "node", "moduleResolution": "node",
"outDir": "../out", "outDir": "../out/amd",
"target": "es5" "forceConsistentCasingInFileNames": true,
"target": "es5",
"lib": [
"dom",
"es5",
"es2015.collection",
"es2015.promise",
"es2016",
"es2017.string"
],
"downlevelIteration": true
} }
} }

View file

@ -16,7 +16,7 @@ const STOP_WHEN_IDLE_FOR = 2 * 60 * 1000; // 2min
export class WorkerManager { export class WorkerManager {
private _defaults: LanguageServiceDefaultsImpl; private _defaults: LanguageServiceDefaultsImpl;
private _idleCheckInterval: number; private _idleCheckInterval: NodeJS.Timer;
private _lastUsedTime: number; private _lastUsedTime: number;
private _configChangeListener: IDisposable; private _configChangeListener: IDisposable;
@ -62,7 +62,7 @@ export class WorkerManager {
this._worker = monaco.editor.createWebWorker<YAMLWorker>({ this._worker = monaco.editor.createWebWorker<YAMLWorker>({
// module that exports the create() method and returns a `YAMLWorker` instance // module that exports the create() method and returns a `YAMLWorker` instance
moduleId: 'vs/languages/yaml/yamlWorker', moduleId: 'vs/language/yaml/yamlWorker',
label: this._defaults.languageId, label: this._defaults.languageId,

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
import YAMLException = require('./exception'); import YAMLException from './exception';
var TYPE_CONSTRUCTOR_OPTIONS = [ var TYPE_CONSTRUCTOR_OPTIONS = [
'kind', 'kind',

View file

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2017 Red Hat Inc. and others.
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.

View file

@ -1,26 +0,0 @@
'use strict';
import * as jsyaml from 'js-yaml'
import { EOL } from 'os';
import { TextDocument, Range, Position, FormattingOptions, TextEdit } from 'vscode-languageserver-types';
export function format(document: TextDocument, options: FormattingOptions): TextEdit[] {
const text = document.getText()
const documents = []
jsyaml.loadAll(text, doc => documents.push(doc))
const dumpOptions = { indent: options.tabSize, noCompatMode: true };
let newText;
if (documents.length == 1) {
const yaml = documents[0]
newText = jsyaml.safeDump(yaml, dumpOptions)
}
else {
const formatted = documents.map(d => jsyaml.safeDump(d, dumpOptions))
newText = '%YAML 1.2' + EOL + '---' + EOL + formatted.join('...' + EOL + '---' + EOL) + '...' + EOL
}
return [TextEdit.replace(Range.create(Position.create(0, 0), document.positionAt(text.length)), newText)]
}

View file

@ -1,72 +0,0 @@
import { YAMLDocument } from "../yamlLanguageService";
import { SingleYAMLDocument } from "../parser/yamlParser";
export function removeDuplicates(arr, prop) {
var new_arr = [];
var lookup = {};
for (var i in arr) {
lookup[arr[i][prop]] = arr[i];
}
for (i in lookup) {
new_arr.push(lookup[i]);
}
return new_arr;
}
export function getLineOffsets(textDocString: String): number[] {
let lineOffsets: number[] = [];
let text = textDocString;
let isLineStart = true;
for (let i = 0; i < text.length; i++) {
if (isLineStart) {
lineOffsets.push(i);
isLineStart = false;
}
let ch = text.charAt(i);
isLineStart = (ch === '\r' || ch === '\n');
if (ch === '\r' && i + 1 < text.length && text.charAt(i + 1) === '\n') {
i++;
}
}
if (isLineStart && text.length > 0) {
lineOffsets.push(text.length);
}
return lineOffsets;
}
export function removeDuplicatesObj(objArray) {
let nonDuplicateSet = new Set();
let nonDuplicateArr = [];
for (let obj in objArray) {
let currObj = objArray[obj];
let stringifiedObj = JSON.stringify(currObj);
if (!nonDuplicateSet.has(stringifiedObj)) {
nonDuplicateArr.push(currObj);
nonDuplicateSet.add(stringifiedObj);
}
}
return nonDuplicateArr;
}
export function matchOffsetToDocument(offset: number, jsonDocuments): SingleYAMLDocument {
for (let jsonDoc in jsonDocuments.documents) {
let currJsonDoc = jsonDocuments.documents[jsonDoc];
if (currJsonDoc.root && currJsonDoc.root.end >= offset && currJsonDoc.root.start <= offset) {
return currJsonDoc;
}
}
return null;
}

View file

@ -1,173 +0,0 @@
import {
TextDocument, Position, CompletionItem, CompletionList, Hover, Range, SymbolInformation, Diagnostic,
TextEdit, FormattingOptions, MarkedString
} from 'vscode-languageserver-types';
import { JSONSchemaService } from './services/jsonSchemaService'
import { JSONSchema } from './jsonSchema';
import { parse as parseYAML } from "./parser/yamlParser";
import { YAMLDocumentSymbols } from './services/documentSymbols';
import { YAMLCompletion } from "./services/yamlCompletion";
import { YAMLHover } from "./services/yamlHover";
import { YAMLValidation } from "./services/yamlValidation";
import { format as formatYAML } from './services/yamlFormatter';
export type JSONDocument = {}
export type YAMLDocument = { documents: JSONDocument[] }
export interface LanguageService {
configure(settings: LanguageSettings): void;
doValidation(document: TextDocument, yamlDocument: YAMLDocument): Thenable<Diagnostic[]>;
parseYAMLDocument(document: TextDocument): YAMLDocument;
resetSchema(uri: string): boolean;
doResolve(item: CompletionItem): Thenable<CompletionItem>;
doComplete(document: TextDocument, position: Position, doc: YAMLDocument): Thenable<CompletionList>;
findDocumentSymbols(document: TextDocument, doc: YAMLDocument): SymbolInformation[];
doHover(document: TextDocument, position: Position, doc: YAMLDocument): Thenable<Hover>;
format(document: TextDocument, options: FormattingOptions): TextEdit[];
}
export interface LanguageSettings {
/**
* If set, the validator will return syntax errors.
*/
validate?: boolean;
/**
* A list of known schemas and/or associations of schemas to file names.
*/
schemas?: SchemaConfiguration[];
}
export interface SchemaConfiguration {
/**
* 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.
* If no schema is provided, the schema will be fetched with the schema request service (if available).
*/
schema?: JSONSchema;
}
export interface LanguageSettings {
validate?: boolean; //Setting for whether we want to validate the schema
schemas?: any[]; //List of schemas
}
export interface PromiseConstructor {
/**
* Creates a new Promise.
* @param executor A callback used to initialize the promise. This callback is passed two arguments:
* a resolve callback used resolve the spromise with a value or the result of another promise,
* and a reject callback used to reject the promise with a provided reason or error.
*/
new <T>(executor: (resolve: (value?: T | Thenable<T>) => void, reject: (reason?: any) => void) => void): Thenable<T>;
/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T>(values: Array<T | Thenable<T>>): Thenable<T[]>;
/**
* Creates a new rejected promise for the provided reason.
* @param reason The reason the promise was rejected.
* @returns A new rejected Promise.
*/
reject<T>(reason: any): Thenable<T>;
/**
* Creates a new resolved promise for the provided value.
* @param value A promise.
* @returns A promise whose internal state matches the provided promise.
*/
resolve<T>(value: T | Thenable<T>): Thenable<T>;
}
export interface Thenable<R> {
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of which ever callback is executed.
*/
then<TResult>(onfulfilled?: (value: R) => TResult | Thenable<TResult>, onrejected?: (reason: any) => TResult | Thenable<TResult>): Thenable<TResult>;
then<TResult>(onfulfilled?: (value: R) => TResult | Thenable<TResult>, onrejected?: (reason: any) => void): Thenable<TResult>;
}
export interface WorkspaceContextService {
resolveRelativePath(relativePath: string, resource: string): string;
}
/**
* The schema request service is used to fetch schemas. The result should the schema file comment, or,
* in case of an error, a displayable error string
*/
export interface SchemaRequestService {
(uri: string): Thenable<string>;
}
export interface SchemaConfiguration {
/**
* 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.
* If no schema is provided, the schema will be fetched with the schema request service (if available).
*/
schema?: JSONSchema;
}
export interface LanguageService {
configure(settings): void;
parseYAMLDocument(document: TextDocument): YAMLDocument,
doComplete(document: TextDocument, position: Position, doc): Thenable<CompletionList>;
doValidation(document: TextDocument, yamlDocument): Thenable<Diagnostic[]>;
doHover(document: TextDocument, position: Position, doc);
findDocumentSymbols(document: TextDocument, doc);
doResolve(completionItem);
resetSchema(uri: string): boolean;
}
export function getLanguageService(schemaRequestService, workspaceContext, contributions, customSchemaProvider, promiseConstructor?): LanguageService {
let promise = promiseConstructor || Promise;
let schemaService = new JSONSchemaService(schemaRequestService, workspaceContext, customSchemaProvider);
let completer = new YAMLCompletion(schemaService, contributions, promise);
let hover = new YAMLHover(schemaService, contributions, promise);
let yamlDocumentSymbols = new YAMLDocumentSymbols();
let yamlValidation = new YAMLValidation(schemaService, promise);
return {
configure: (settings) => {
schemaService.clearExternalSchemas();
if (settings.schemas) {
settings.schemas.forEach(settings => {
schemaService.registerExternalSchema(settings.uri, settings.fileMatch, settings.schema);
});
}
yamlValidation.configure(settings);
},
parseYAMLDocument: (document: TextDocument) => parseYAML(document.getText()),
doComplete: completer.doComplete.bind(completer),
doResolve: completer.doResolve.bind(completer),
doValidation: yamlValidation.doValidation.bind(yamlValidation),
doHover: hover.doHover.bind(hover),
findDocumentSymbols: yamlDocumentSymbols.findDocumentSymbols.bind(yamlDocumentSymbols),
resetSchema: (uri: string) => schemaService.onResourceChange(uri),
format: formatYAML
}
}

15
src/yaml.worker.ts Normal file
View file

@ -0,0 +1,15 @@
/*---------------------------------------------------------------------------------------------
* 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 worker from 'monaco-editor-core/esm/vs/editor/editor.worker';
import { YAMLWorker } from './yamlWorker';
self.onmessage = () => {
// ignore the first message
worker.initialize((ctx, createData) => {
return new YAMLWorker(ctx, createData)
});
};

View file

@ -1,71 +1,71 @@
/*--------------------------------------------------------------------------------------------- /*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. 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. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
import { WorkerManager } from './workerManager'; import { WorkerManager } from './workerManager';
import { YAMLWorker } from './yamlWorker'; 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 Promise = monaco.Promise; import Promise = monaco.Promise;
import Uri = monaco.Uri; import Uri = monaco.Uri;
import IDisposable = monaco.IDisposable; import IDisposable = monaco.IDisposable;
export function setupMode(defaults: LanguageServiceDefaultsImpl): void { export function setupMode(defaults: LanguageServiceDefaultsImpl): void {
let disposables: IDisposable[] = []; let disposables: IDisposable[] = [];
const client = new WorkerManager(defaults); const client = new WorkerManager(defaults);
disposables.push(client); disposables.push(client);
const worker: languageFeatures.WorkerAccessor = (...uris: Uri[]): Promise<YAMLWorker> => { const worker: languageFeatures.WorkerAccessor = (...uris: Uri[]): Promise<YAMLWorker> => {
return client.getLanguageServiceWorker(...uris); return client.getLanguageServiceWorker(...uris);
}; };
let languageId = defaults.languageId; let languageId = defaults.languageId;
disposables.push(monaco.languages.registerCompletionItemProvider(languageId, new languageFeatures.CompletionAdapter(worker))); disposables.push(monaco.languages.registerCompletionItemProvider(languageId, new languageFeatures.CompletionAdapter(worker)));
disposables.push(monaco.languages.registerHoverProvider(languageId, new languageFeatures.HoverAdapter(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.registerDocumentSymbolProvider(languageId, new languageFeatures.DocumentSymbolAdapter(worker)));
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.DiagnosticsAdapter(languageId, worker, defaults));
// 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 = {
comments: { comments: {
lineComment: '#' lineComment: '#'
}, },
brackets: [ brackets: [
['{', '}'], ['{', '}'],
['[', ']'], ['[', ']'],
['(', ')'] ['(', ')']
], ],
autoClosingPairs: [ autoClosingPairs: [
{ open: '{', close: '}' }, { open: '{', close: '}' },
{ open: '[', close: ']' }, { open: '[', close: ']' },
{ open: '(', close: ')' }, { open: '(', close: ')' },
{ open: '"', close: '"' }, { open: '"', close: '"' },
{ open: '\'', close: '\'' }, { open: '\'', close: '\'' },
], ],
surroundingPairs: [ surroundingPairs: [
{ open: '{', close: '}' }, { open: '{', close: '}' },
{ open: '[', close: ']' }, { open: '[', close: ']' },
{ open: '(', close: ')' }, { open: '(', close: ')' },
{ open: '"', close: '"' }, { open: '"', close: '"' },
{ open: '\'', close: '\'' }, { open: '\'', close: '\'' },
], ],
onEnterRules: [ onEnterRules: [
{ {
beforeText: /:\s*$/, beforeText: /:\s*$/,
action: { indentAction: monaco.languages.IndentAction.Indent } action: { indentAction: monaco.languages.IndentAction.Indent }
} }
], ],
}; };

View file

@ -1,146 +1,214 @@
/*--------------------------------------------------------------------------------------------- /*---------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved. * Copyright (c) Red Hat, Inc. All rights reserved.
* Copyright (c) Adam Voss. All rights reserved. * Copyright (c) Adam Voss. All rights reserved.
* Copyright (c) Microsoft Corporation. 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. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
import Promise = monaco.Promise; import Promise = monaco.Promise;
import Thenable = monaco.Thenable; import Thenable = monaco.Thenable;
import IWorkerContext = monaco.worker.IWorkerContext; import IWorkerContext = monaco.worker.IWorkerContext;
import * as yamlService from './yaml-languageservice/yamlLanguageService'; import * as ls from 'vscode-languageserver-types';
import * as ls from 'vscode-languageserver-types'; import * as yamlService from './languageservice/yamlLanguageService';
import { getLineOffsets } from './yaml-languageservice/utils/arrUtils'; import { SchemaRequestService } from './languageservice/yamlLanguageService';
import { parse as parseYAML } from "./yaml-languageservice/parser/yamlParser";
class PromiseAdapter<T> implements yamlService.Thenable<T> {
export class YAMLWorker { private wrapped: monaco.Promise<T>;
private _ctx: IWorkerContext; constructor(executor: (resolve: (value?: T | yamlService.Thenable<T>) => void, reject: (reason?: any) => void) => void) {
private _languageService: yamlService.LanguageService; this.wrapped = new monaco.Promise<T>(executor);
private _languageSettings: yamlService.LanguageSettings; }
private _languageId: string; public then<TResult>(onfulfilled?: (value: T) => TResult | yamlService.Thenable<TResult>, onrejected?: (reason: any) => void): yamlService.Thenable<TResult> {
let thenable : yamlService.Thenable<T> = this.wrapped;
constructor(ctx: IWorkerContext, createData: ICreateData) { return thenable.then(onfulfilled, onrejected);
this._ctx = ctx; }
this._languageSettings = createData.languageSettings; public getWrapped(): monaco.Thenable<T> {
this._languageId = createData.languageId; return this.wrapped;
this._languageService = yamlService.getLanguageService(); }
this._languageService.configure(this._languageSettings); public cancel(): void {
} this.wrapped.cancel();
}
doValidation(uri: string): Thenable<ls.Diagnostic[]> { public static resolve<T>(v: T | Thenable<T>): yamlService.Thenable<T> {
let document = this._getTextDocument(uri); return <monaco.Thenable<T>> monaco.Promise.as(v);
if (document) { }
let jsonDocument = this._languageService.parseYAMLDocument(document); public static reject<T>(v: T): yamlService.Thenable<T> {
return this._languageService.doValidation(document, jsonDocument); return monaco.Promise.wrapError(<any>v);
} }
return Promise.as([]); public static all<T>(values: yamlService.Thenable<T>[]): yamlService.Thenable<T[]> {
} return monaco.Promise.join(values);
doComplete(uri: string, position: ls.Position): Thenable<ls.CompletionList> { }
let document = this._getTextDocument(uri); }
let completionFix = completionHelper(document, position);
let newText = completionFix.newText; // Currently we only support loading schemas via xhr:
let jsonDocument = parseYAML(newText); const ajax = (url: string) =>
return this._languageService.doComplete(document, position, jsonDocument); new Promise((resolve, reject) => {
} const request = new XMLHttpRequest();
doResolve(item: ls.CompletionItem): Thenable<ls.CompletionItem> { request.onreadystatechange = () => {
return this._languageService.doResolve(item); if (request.readyState === XMLHttpRequest.DONE) {
} const response = request.responseText;
doHover(uri: string, position: ls.Position): Thenable<ls.Hover> { if (request.status < 400) {
let document = this._getTextDocument(uri); resolve(response);
let jsonDocument = this._languageService.parseYAMLDocument(document); } else {
return this._languageService.doHover(document, position, jsonDocument); reject(response);
} }
format(uri: string, range: ls.Range, options: ls.FormattingOptions): Thenable<ls.TextEdit[]> { }
let document = this._getTextDocument(uri); };
let textEdits = this._languageService.format(document, options); request.onerror = reject;
return Promise.as(textEdits); request.open('GET', url);
} request.send();
resetSchema(uri: string): Thenable<boolean> { });
return Promise.as(this._languageService.resetSchema(uri));
} export class YAMLWorker {
findDocumentSymbols(uri: string): Promise<ls.SymbolInformation[]> {
let document = this._getTextDocument(uri); private _ctx: IWorkerContext;
let jsonDocument = this._languageService.parseYAMLDocument(document); private _languageService: yamlService.LanguageService;
let symbols = this._languageService.findDocumentSymbols(document, jsonDocument); private _languageSettings: yamlService.LanguageSettings;
return Promise.as(symbols); private _languageId: string;
}
private _getTextDocument(uri: string): ls.TextDocument { constructor(ctx: IWorkerContext, createData: ICreateData) {
let models = this._ctx.getMirrorModels(); this._ctx = ctx;
for (let model of models) { this._languageSettings = createData.languageSettings;
if (model.uri.toString() === uri) { this._languageId = createData.languageId;
return ls.TextDocument.create(uri, this._languageId, model.version, model.getValue()); this._languageService = yamlService.getLanguageService(ajax, null, [], null, PromiseAdapter);
} this._languageService.configure(this._languageSettings);
} }
return null;
} doValidation(uri: string): Thenable<ls.Diagnostic[]> {
} let document = this._getTextDocument(uri);
if (document) {
export interface ICreateData { let yamlDocument = this._languageService.parseYAMLDocument(document);
languageId: string; return this._languageService.doValidation(document, yamlDocument);
languageSettings: yamlService.LanguageSettings; }
} return Promise.as([]);
}
export function create(ctx: IWorkerContext, createData: ICreateData): YAMLWorker {
return new YAMLWorker(ctx, createData); doComplete(uri: string, position: ls.Position): Thenable<ls.CompletionList> {
} let document = this._getTextDocument(uri);
let completionFix = completionHelper(document, position);
let yamlDocument = this._languageService.parseYAMLDocument(document);
// https://github.com/redhat-developer/yaml-language-server/blob/5e069c0e9d7004d57f1fa6e93df670d4895883d1/src/server.ts#L453 return this._languageService.doComplete(document, position, yamlDocument);
function completionHelper(document: ls.TextDocument, textDocumentPosition: ls.Position) { }
doResolve(item: ls.CompletionItem): Thenable<ls.CompletionItem> {
//Get the string we are looking at via a substring return this._languageService.doResolve(item);
let linePos = textDocumentPosition.line; }
let position = textDocumentPosition; doHover(uri: string, position: ls.Position): Thenable<ls.Hover> {
let lineOffset = getLineOffsets(document.getText()); let document = this._getTextDocument(uri);
let start = lineOffset[linePos]; //Start of where the autocompletion is happening let yamlDocument = this._languageService.parseYAMLDocument(document)
let end = 0; //End of where the autocompletion is happening return this._languageService.doHover(document, position, yamlDocument);
if (lineOffset[linePos + 1]) { }
end = lineOffset[linePos + 1]; format(uri: string, range: ls.Range, options: ls.FormattingOptions): Thenable<ls.TextEdit[]> {
} else { let document = this._getTextDocument(uri);
end = document.getText().length; let textEdits = this._languageService.doFormat(document, options, []);
} return Promise.as(textEdits);
let textLine = document.getText().substring(start, end); }
resetSchema(uri: string): Thenable<boolean> {
//Check if the string we are looking at is a node return Promise.as(this._languageService.resetSchema(uri));
if (textLine.indexOf(":") === -1) { }
//We need to add the ":" to load the nodes findDocumentSymbols(uri: string): Thenable<ls.SymbolInformation[]> {
let newText = ""; let document = this._getTextDocument(uri);
let yamlDocument = this._languageService.parseYAMLDocument(document);
//This is for the empty line case let symbols = this._languageService.findDocumentSymbols(document, yamlDocument);
let trimmedText = textLine.trim(); return Promise.as(symbols);
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. private _getTextDocument(uri: string): ls.TextDocument {
if (lineOffset[linePos + 1]) { let models = this._ctx.getMirrorModels();
newText = document.getText().substring(0, start + (textLine.length - 1)) + "holder:\r\n" + document.getText().substr(end + 2); for (let model of models) {
} else { if (model.uri.toString() === uri) {
newText = document.getText().substring(0, start + (textLine.length)) + "holder:\r\n" + document.getText().substr(end + 2); return ls.TextDocument.create(uri, this._languageId, model.version, model.getValue());
} }
//For when missing semi colon case }
} else { return null;
//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 { export interface ICreateData {
newText = document.getText().substring(0, start + (textLine.length)) + ":\r\n" + document.getText().substr(end + 2); languageId: string;
} languageSettings: yamlService.LanguageSettings;
} schemaRequestService?: SchemaRequestService;
}
return {
"newText": newText, export function create(ctx: IWorkerContext, createData: ICreateData): YAMLWorker {
"newPosition": textDocumentPosition return new YAMLWorker(ctx, createData);
} }
} else { export function getLineOffsets(textDocString: String): number[] {
//All the nodes are loaded let lineOffsets: number[] = [];
position.character = position.character - 1; let text = textDocString;
return { let isLineStart = true;
"newText": document.getText(), for (let i = 0; i < text.length; i++) {
"newPosition": position if (isLineStart) {
} lineOffsets.push(i);
} isLineStart = false;
}
} let ch = text.charAt(i);
isLineStart = (ch === '\r' || ch === '\n');
if (ch === '\r' && i + 1 < text.length && text.charAt(i + 1) === '\n') {
i++;
}
}
if (isLineStart && text.length > 0) {
lineOffsets.push(text.length);
}
return lineOffsets;
}
// 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
}
}
}

BIN
test-demo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

View file

@ -1,63 +1,110 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible"
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> content="IE=edge" />
<link rel="stylesheet" data-name="vs/editor/editor.main" href="../node_modules/monaco-editor-core/dev/vs/editor/editor.main.css"> <meta http-equiv="Content-Type"
content="text/html;charset=utf-8" />
<link rel="stylesheet"
data-name="vs/editor/editor.main"
href="../node_modules/monaco-editor-core/dev/vs/editor/editor.main.css">
</head> </head>
<body> <body>
<h2>Monaco Editor JSON test page</h2> <h2>Monaco Editor YAML test page</h2>
<div id="container" style="width:800px;height:600px;border:1px solid grey"></div> <code id="path"></code>
<div id="container"
style="width:800px;height:600px;border:1px solid grey"></div>
<script> <script>
// Loading basic-languages to get the json language definition // Loading basic-languages to get the YAML language definition
var require = { var paths = {
paths: { 'vs/basic-languages': '../node_modules/monaco-languages/release/dev',
'vs/basic-languages': '../node_modules/monaco-languages/release', 'vs/language/yaml': '../release/dev',
'vs/languages/yaml': '../release/dev',
'vs': '../node_modules/monaco-editor-core/dev/vs' 'vs': '../node_modules/monaco-editor-core/dev/vs'
} }
}; if (document.location.protocol === 'http:') {
</script> // Add support for running local http server
<script src="../node_modules/monaco-editor-core/dev/vs/loader.js"></script> let testIndex = document.location.pathname.indexOf('/test/');
<script src="../node_modules/monaco-editor-core/dev/vs/editor/editor.main.nls.js"></script> if (testIndex !== -1) {
<script src="../node_modules/monaco-editor-core/dev/vs/editor/editor.main.js"></script> let prefix = document.location.pathname.substr(0, testIndex);
paths['vs/language/yaml'] = prefix + '/release/dev';
}
}
var require = {
paths: paths
};
</script>
<script src="../node_modules/monaco-editor-core/dev/vs/loader.js"></script>
<script src="../node_modules/monaco-editor-core/dev/vs/editor/editor.main.nls.js"></script>
<script src="../node_modules/monaco-editor-core/dev/vs/editor/editor.main.js"></script>
<script> <script>
require([ require([
'vs/basic-languages/src/monaco.contribution', 'vs/basic-languages/monaco.contribution',
'vs/languages/yaml/monaco.contribution' 'vs/language/yaml/monaco.contribution'
], function() { ], function () {
monaco.languages.yaml.yamlDefaults.setDiagnosticsOptions({ const yaml = `apiVersion: apps/v1
validate: true, kind: Deployment
schemas: [{ metadata:
fileMatch: ['*'], name: nginx-deployment
schema: { namespace: default
title: 'Person', labels:
type: 'object', app: nginx
properties: { spec:
name: { type: 'string' }, replicas: 1
age: { selector:
description: 'Age in years', matchLabels:
type: 'integer', apps.deployment: nginx
minimum: 0 template:
} metadata:
labels:
apps.deployment: nginx
spec:
containers:
- name: nginx
image: nginx:alpine`;
var editor = monaco.editor.create(document.getElementById('container'), {
value: yaml,
language: 'yaml'
});
monaco.languages.yaml.yamlDefaults.setDiagnosticsOptions({
validate: true,
schemas: [
{
uri: 'https://raw.githubusercontent.com/garethr/kubernetes-json-schema/master/master/deployment.json',
fileMatch: ['*'],
}, },
required: ['name'] ],
} });
}]
});
var editor = monaco.editor.create(document.getElementById('container'), { // See: https://github.com/Microsoft/vscode/blob/master/src/vs/editor/contrib/quickOpen/quickOpen.ts
value: [ require(['vs/editor/contrib/quickOpen/quickOpen'], quickOpen => {
'name: Apple',
'age: 18.0' // Breadcrumbs emulation:
].join('\n'), editor.onDidChangeCursorSelection(({ selection }) => {
language: 'yaml' quickOpen.getDocumentSymbols(editor.getModel()).then(symbols => {
symbols = symbols.filter(symbol => symbol.range.containsPosition(selection.getPosition()));
symbols = symbols.map(symbol => {
if (symbol.kind === 17) {
return `[]${symbol.name}`;
} else if (symbol.kind === 18 || symbol.kind === 1) {
return `{}${symbol.name}`;
} else {
return symbol.name;
}
});
document.querySelector('#path').innerHTML = symbols.join(' > ');
})
})
})
}); });
}); </script>
</script>
</body> </body>
</html> </html>

2060
yarn.lock

File diff suppressed because it is too large Load diff