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/
/lib/
/out/
/scripts/
/src/
/test/
/release/dev/
/gulpfile.js
/.gitignore
/.npmignore

View file

@ -7,21 +7,37 @@ YAML language plugin for the Monaco Editor. It provides the following features w
* Formatting
* Document Symbols
* 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.
## 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
* `git clone https://github.com/kpdecker/monaco-yaml`
* `cd monaco-yaml`
* `yarn`
* `npm run watch`
* `yarn watch`
* 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
[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",
"version": "1.0.0",
"version": "1.1.0",
"description": "YAML plugin for the Monaco Editor",
"scripts": {
"compile": "gulp compile",
"watch": "gulp watch",
"prepublish": "gulp release"
"compile": "rimraf ./out && tsc -p ./src/tsconfig.json && tsc -p ./src/tsconfig.esm.json",
"watch": "tsc -p ./src --watch",
"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)",
"maintainers": [
"kpdecker",
"pengx17"
],
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/kpdecker/monaco-yaml"
},
"bugs": {
"url": "https://github.com/kpdecker/monaco-yaml/issues"
},
"devDependencies": {
"event-stream": "^3.3.2",
"gulp": "^3.9.1",
"gulp-requirejs": "^0.1.3",
"gulp-tsb": "^2.0.0",
"gulp-uglify": "^1.5.3",
"js-yaml": "^3.10.0",
"jsonc-parser": "1.0.0",
"merge-stream": "^1.0.0",
"monaco-editor-core": "^0.10.1",
"monaco-languages": "^0.9.0",
"object-assign": "^4.1.0",
"rimraf": "^2.5.2",
"typescript": "^2.7.1",
"vscode-json-languageservice": "^3.0.5",
"vscode-languageserver-types": "^3.5.0"
"@types/chai": "^4.1.4",
"@types/mocha": "^5.2.5",
"@types/node": "^10.9.3",
"js-yaml": "^3.12.0",
"jsonc-parser": "^2.0.2",
"monaco-editor-core": "0.14.6",
"monaco-languages": "1.5.1",
"monaco-plugin-helpers": "^1.0.2",
"requirejs": "^2.3.5",
"rimraf": "^2.6.2",
"typescript": "^3.0.3",
"uglify-es": "^3.3.9",
"vscode-json-languageservice": "^3.1.6",
"vscode-languageserver-types": "3.12.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 Position = monaco.Position;
import Range = monaco.Range;
import IRange = monaco.IRange;
import Thenable = monaco.Thenable;
import Promise = monaco.Promise;
import CancellationToken = monaco.CancellationToken;
@ -24,19 +25,19 @@ export interface WorkerAccessor {
// --- diagnostics --- ---
export class DiagnostcsAdapter {
export class DiagnosticsAdapter {
private _disposables: IDisposable[] = [];
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 => {
let modeId = model.getModeId();
if (modeId !== this._languageId) {
return;
}
let handle: number;
let handle: NodeJS.Timer;
this._listener[model.uri.toString()] = model.onDidChangeContent(() => {
clearTimeout(handle);
handle = setTimeout(() => this._doValidate(model.uri, modeId), 500);
@ -66,8 +67,18 @@ export class DiagnostcsAdapter {
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({
dispose: () => {
monaco.editor.getModels().forEach(onModelRemoved);
for (let key in this._listener) {
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) {
case ls.DiagnosticSeverity.Error: return monaco.Severity.Error;
case ls.DiagnosticSeverity.Warning: return monaco.Severity.Warning;
case ls.DiagnosticSeverity.Information:
case ls.DiagnosticSeverity.Hint:
case ls.DiagnosticSeverity.Error: return monaco.MarkerSeverity.Error;
case ls.DiagnosticSeverity.Warning: return monaco.MarkerSeverity.Warning;
case ls.DiagnosticSeverity.Information: return monaco.MarkerSeverity.Info;
case ls.DiagnosticSeverity.Hint: return monaco.MarkerSeverity.Hint;
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 };
}
function fromRange(range: Range): ls.Range {
function fromRange(range: IRange): ls.Range {
if (!range) {
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 {
if (!range) {
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 {
let item: ls.CompletionItem = {
label: entry.label,
sortText: entry.sortText,
filterText: entry.filterText,
documentation: entry.documentation,
documentation: fromMarkdownString(entry.documentation),
detail: entry.detail,
kind: fromCompletionItemKind(entry.kind),
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) {
return void 0;
}
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) {
}
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;
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 => ({
name: item.name,
detail: '',
containerName: item.containerName,
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.
* 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.
* 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
deprecationMessage?: 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
}

View file

@ -1,10 +1,11 @@
/*---------------------------------------------------------------------------------------------
* 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';
import Json = require('jsonc-parser');
import * as Json from 'jsonc-parser';
import { JSONSchema } from '../jsonSchema';
import * as objects from '../utils/objects';
@ -118,7 +119,7 @@ export class ASTNode {
collector.push(item);
}
}
return node;
return node;
};
let foundNode = findNode(this);
return collector.length;
@ -157,7 +158,7 @@ export class ASTNode {
if (!matchingSchemas.include(this)) {
return;
}
if (Array.isArray(schema.type)) {
if ((<string[]>schema.type).indexOf(this.type) === -1) {
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 unprocessedProperties: string[] = [];
this.properties.forEach((node) => {
let key = node.key.value;
//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;
unprocessedProperties.push(key);
}
});
if (Array.isArray(schema.required)) {
@ -770,7 +771,7 @@ export class ObjectASTNode extends ASTNode {
}
});
}
}
}
if (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';
import { JSONSchema } from 'vscode-json-languageservice/lib/jsonSchema';
import { ASTNode, ErrorCode, BooleanASTNode, NullASTNode, ArrayASTNode, NumberASTNode, ObjectASTNode, PropertyASTNode, StringASTNode, IApplicableSchema, JSONDocument } from './jsonParser';
import * as nls from 'vscode-nls';
@ -8,8 +12,10 @@ const localize = nls.loadMessageBundle();
import * as Yaml 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 YAMLException from '../../yaml-ast-parser/exception';
export class SingleYAMLDocument extends JSONDocument {
private lines;
@ -153,7 +159,7 @@ function recursivelyBuildAst(parent: ASTNode, node: Yaml.YAMLNode): ASTNode {
switch (type) {
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: {
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 } }
}
function createJSONDocument(yamlDoc: Yaml.YAMLNode, startPositions: number[]) {
function createJSONDocument(yamlDoc: Yaml.YAMLNode, startPositions: number[], text: string) {
let _doc = new SingleYAMLDocument(startPositions);
_doc.root = recursivelyBuildAst(null, yamlDoc)
@ -208,8 +214,18 @@ function createJSONDocument(yamlDoc: Yaml.YAMLNode, startPositions: number[]) {
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 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));
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)
// This is documented to return a YAMLNode even though the
// typing only returns a YAMLDocument
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.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import Parser = require('../parser/jsonParser');
import Strings = require('../utils/strings');
import * as Parser from '../parser/jsonParser';
import { SymbolInformation, SymbolKind, TextDocument, Range, Location } from 'vscode-languageserver-types';
import { Thenable } from "../yamlLanguageService";
@ -18,7 +18,7 @@ export class YAMLDocumentSymbols {
if(!doc || doc["documents"].length === 0){
return null;
}
let collectOutlineEntries = (result: SymbolInformation[], node: Parser.ASTNode, containerName: string): SymbolInformation[] => {
if (node.type === 'array') {
(<Parser.ArrayASTNode>node).items.forEach((node: Parser.ASTNode) => {
@ -26,7 +26,7 @@ export class YAMLDocumentSymbols {
});
} else if (node.type === 'object') {
let objectNode = <Parser.ObjectASTNode>node;
objectNode.properties.forEach((property: Parser.PropertyASTNode) => {
let location = Location.create(document.uri, Range.create(document.positionAt(property.start), document.positionAt(property.end)));
let valueNode = property.value;
@ -48,7 +48,7 @@ export class YAMLDocumentSymbols {
results = results.concat(result);
}
}
return results;
}

View file

@ -1,20 +1,42 @@
/*---------------------------------------------------------------------------------------------
* 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';
import Json = require('jsonc-parser');
import * as Json from 'jsonc-parser';
import {JSONSchema, JSONSchemaMap} from '../jsonSchema';
import URI from 'vscode-uri';
import Strings = require('../utils/strings');
import Parser = require('../parser/jsonParser');
import * as Strings from '../utils/strings';
import {SchemaRequestService, WorkspaceContextService, PromiseConstructor, Thenable} from '../yamlLanguageService';
import * as nls from 'vscode-nls';
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 {
/**
@ -82,7 +104,7 @@ export class FilePatternAssociation {
constructor(pattern: string) {
this.combinedSchemaId = 'schemaservice://combinedSchema/' + encodeURIComponent(pattern);
try {
this.patternRegExp = new RegExp(Strings.convertSimple2RegExpPattern(pattern) + '$');
this.patternRegExp = Strings.convertSimple2RegExp(pattern);
} catch (e) {
// invalid pattern
this.patternRegExp = null;
@ -368,7 +390,7 @@ export class JSONSchemaService implements IJSONSchemaService {
let schemaContent: JSONSchema = {};
let 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);
},
(error: any) => {
@ -479,7 +501,7 @@ export class JSONSchemaService implements IJSONSchemaService {
}
collectEntries(next.items, next.additionalProperties, next.not);
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);
};
@ -496,7 +518,7 @@ export class JSONSchemaService implements IJSONSchemaService {
return entry.getCombinedSchema(this).getResolvedSchema();
}
}
return null;
return this.promise.resolve(null);
};
if (this.customSchemaProvider) {
return this.customSchemaProvider(resource).then(schemaUri => {

View file

@ -6,14 +6,14 @@
'use strict';
import Parser = require('../parser/jsonParser');
import Json = require('jsonc-parser');
import SchemaService = require('./jsonSchemaService');
import * as Parser from '../parser/jsonParser';
import * as Json from 'jsonc-parser';
import * as SchemaService from './jsonSchemaService';
import { JSONSchema } from '../jsonSchema';
import { JSONWorkerContribution, CompletionsCollector } from '../jsonContributions';
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 { matchOffsetToDocument } from '../utils/arrUtils';
@ -25,11 +25,17 @@ export class YAMLCompletion {
private schemaService: SchemaService.IJSONSchemaService;
private contributions: JSONWorkerContribution[];
private promise: PromiseConstructor;
private customTags: Array<String>;
constructor(schemaService: SchemaService.IJSONSchemaService, contributions: JSONWorkerContribution[] = [], promiseConstructor?: PromiseConstructor) {
this.schemaService = schemaService;
this.contributions = contributions;
this.promise = promiseConstructor || Promise;
this.customTags = [];
}
public configure(customTags: Array<String>){
this.customTags = customTags;
}
public doResolve(item: CompletionItem): Thenable<CompletionItem> {
@ -44,7 +50,7 @@ export class YAMLCompletion {
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 = {
items: [],
@ -52,14 +58,15 @@ export class YAMLCompletion {
};
let offset = document.offsetAt(position);
if (document.getText()[offset] === ":") {
return null;
if(document.getText()[offset] === ":"){
return Promise.resolve(result);
}
let currentDoc = matchOffsetToDocument(offset, doc);
if (currentDoc === null) {
return null;
if(currentDoc === null){
return Promise.resolve(result);
}
const currentDocIndex = doc.documents.indexOf(currentDoc);
let node = currentDoc.getNodeFromOffsetEndInclusive(offset);
if (this.isInComment(document, node ? node.start : 0, offset)) {
return Promise.resolve(result);
@ -67,12 +74,32 @@ export class YAMLCompletion {
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 collector: CompletionsCollector = {
add: (suggestion: CompletionItem) => {
let existing = proposed[suggestion.label];
if (!existing) {
proposed[suggestion.label] = suggestion;
if (overwriteRange) {
suggestion.textEdit = TextEdit.replace(overwriteRange, suggestion.insertText);
}
result.items.push(suggestion);
} else if (!existing.documentation) {
existing.documentation = suggestion.documentation;
@ -94,8 +121,12 @@ export class YAMLCompletion {
return this.schemaService.getSchemaForResource(document.uri).then((schema) => {
if (!schema) {
return null;
if(!schema){
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>[] = [];
@ -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
this.getPropertyCompletions(schema, currentDoc, node, addValue, collector);
this.getPropertyCompletions(newSchema, currentDoc, node, addValue, collector, separatorAfter);
}
let location = node.getPath();
@ -144,19 +180,24 @@ export class YAMLCompletion {
if ((!schema && currentWord.length > 0 && document.getText().charAt(offset - currentWord.length - 1) !== '"')) {
collector.add({
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
let types: { [type: string]: boolean } = {};
if (schema) {
this.getValueCompletions(schema, currentDoc, node, offset, document, collector, types);
if (newSchema) {
this.getValueCompletions(newSchema, currentDoc, node, offset, document, collector);
}
if (this.contributions.length > 0) {
this.getContributedValueCompletions(currentDoc, node, offset, document, collector, collectionPromises);
}
if (this.customTags.length > 0) {
this.getCustomTagValueCompletions(collector);
}
return this.promise.all(collectionPromises).then(() => {
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);
matchingSchemas.forEach((s) => {
if (s.node === node && !s.inverted) {
@ -176,18 +217,26 @@ export class YAMLCompletion {
collector.add({
kind: CompletionItemKind.Property,
label: key,
insertText: `${key}:`,
filterText: this.getFilterTextForValue(key),
insertText: this.getInsertTextForProperty(key, propertySchema, addValue, separatorAfter),
insertTextFormat: InsertTextFormat.Snippet,
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 parentKey: string = null;
let valueNode: Parser.ASTNode = null;
@ -198,17 +247,17 @@ export class YAMLCompletion {
node = node.parent;
}
if (node && node.type === 'null') {
if(node && node.type === 'null'){
let nodeParent = node.parent;
/*
* This is going to be an object for some reason and we need to find the property
* Its an issue with the null node
*/
if (nodeParent && nodeParent.type === "object") {
for (let prop in nodeParent["properties"]) {
if(nodeParent && nodeParent.type === "object"){
for(let prop in nodeParent["properties"]){
let currNode = nodeParent["properties"][prop];
if (currNode.key && currNode.key.location === node.location) {
if(currNode.key && currNode.key.location === node.location){
node = currNode;
}
}
@ -216,7 +265,7 @@ export class YAMLCompletion {
}
if (!node) {
this.addSchemaValueCompletions(schema.schema, collector, types);
this.addSchemaValueCompletions(schema.schema, collector, "");
return;
}
@ -230,6 +279,7 @@ export class YAMLCompletion {
node = node.parent;
}
let separatorAfter = this.evaluateSeparatorAfter(document, offsetForSeparator);
if (node && (parentKey !== null || node.type === 'array')) {
let matchingSchemas = doc.getMatchingSchemas(schema.schema);
matchingSchemas.forEach(s => {
@ -238,30 +288,30 @@ export class YAMLCompletion {
if (Array.isArray(s.schema.items)) {
let index = this.findItemAtOffset(node, document, offset);
if (index < s.schema.items.length) {
this.addSchemaValueCompletions(s.schema.items[index], collector, types);
this.addSchemaValueCompletions(s.schema.items[index], collector, separatorAfter, true);
}
} else {
this.addSchemaValueCompletions(s.schema.items, collector, types);
} else if (s.schema.items.type === 'object') {
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) {
let propertySchema = s.schema.properties[parentKey];
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>[]) {
@ -293,22 +343,43 @@ export class YAMLCompletion {
}
}
private addSchemaValueCompletions(schema: JSONSchema, collector: CompletionsCollector, types: { [type: string]: boolean }): void {
this.addDefaultValueCompletions(schema, collector);
this.addEnumValueCompletions(schema, collector);
this.collectTypes(schema, types);
if (Array.isArray(schema.allOf)) {
schema.allOf.forEach(s => this.addSchemaValueCompletions(s, collector, types));
private getCustomTagValueCompletions(collector: CompletionsCollector) {
this.customTags.forEach((customTagItem) => {
let tagItemSplit = customTagItem.split(" ");
if(tagItemSplit && tagItemSplit[0]){
this.addCustomTagValueCompletion(collector, " ", tagItemSplit[0]);
}
});
}
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)) {
schema.anyOf.forEach(s => this.addSchemaValueCompletions(s, collector, types));
}
if (Array.isArray(schema.oneOf)) {
schema.oneOf.forEach(s => this.addSchemaValueCompletions(s, collector, types));
if (types['null']) {
this.addNullValueCompletion(collector, separatorAfter);
}
}
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;
if (schema.default) {
let type = schema.type;
@ -319,17 +390,19 @@ export class YAMLCompletion {
}
collector.add({
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'),
});
hasProposals = true;
}
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)) {
for (let i = 0, length = schema.enum.length; i < length; i++) {
let enm = schema.enum[i];
@ -339,7 +412,9 @@ export class YAMLCompletion {
}
collector.add({
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
});
}
@ -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({
kind: this.getSuggestionKind('boolean'),
label: value ? 'true' : 'false',
insertText: this.getInsertTextForValue(value, separatorAfter),
insertTextFormat: InsertTextFormat.Snippet,
documentation: ''
});
}
private addNullValueCompletion(collector: CompletionsCollector): void {
private addNullValueCompletion(collector: CompletionsCollector, separatorAfter: string): void {
collector.add({
kind: this.getSuggestionKind('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: ''
});
}
@ -379,10 +468,6 @@ export class YAMLCompletion {
return label;
}
private getFilterTextForValue(value): string {
return JSON.stringify(value);
}
private getSuggestionKind(type: any): CompletionItemKind {
if (Array.isArray(type)) {
let array = <any[]>type;
@ -436,4 +521,178 @@ export class YAMLCompletion {
}
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.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import Parser = require('../parser/jsonParser');
import SchemaService = require('./jsonSchemaService');
import * as Parser from '../parser/jsonParser';
import * as SchemaService from './jsonSchemaService';
import {JSONWorkerContribution} from '../jsonContributions';
import {PromiseConstructor, Thenable} from 'vscode-json-languageservice';
@ -25,13 +26,18 @@ export class YAMLHover {
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 currentDoc = matchOffsetToDocument(offset, doc);
if(currentDoc === null){
return null;
return this.promise.resolve(void 0);
}
const currentDocIndex = doc.documents.indexOf(currentDoc);
let node = currentDoc.getNodeFromOffset(offset);
if (!node || (node.type === 'object' || node.type === 'array') && offset > node.start + 1 && offset < node.end - 1) {
return this.promise.resolve(void 0);
@ -46,7 +52,7 @@ export class YAMLHover {
node = propertyNode.value;
if (!node) {
return this.promise.resolve(void 0);
}
}
}
}
@ -71,8 +77,11 @@ export class YAMLHover {
return this.schemaService.getSchemaForResource(document.uri).then((schema) => {
if (schema) {
let matchingSchemas = currentDoc.getMatchingSchemas(schema.schema, node.start);
let newSchema = schema;
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 markdownDescription: string = null;

View file

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

View file

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

View file

@ -32,6 +32,16 @@ export function endsWith(haystack: string, needle: string): boolean {
}
}
export function convertSimple2RegExpPattern(pattern: string): string {
return pattern.replace(/[\-\\\{\}\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&').replace(/[\*]/g, '.*');
export function convertSimple2RegExp(pattern: string): RegExp {
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 IEvent = monaco.IEvent;
import IDisposable = monaco.IDisposable;
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 {
@ -62,7 +61,7 @@ monaco.languages.yaml = createAPI();
// --- Registration to monaco editor ---
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({

43
src/monaco.d.ts vendored
View file

@ -5,34 +5,35 @@
declare module monaco.languages.yaml {
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?: {
/**
* The URI of the schema, which is also the identifier of the schema.
*/
readonly uri: string;
/**
* 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;
}[];
readonly fileMatch?: string[];
/**
* The schema for the given URI.
*/
readonly schema?: any;
}[];
}
export interface LanguageServiceDefaults {
readonly onDidChange: IEvent<LanguageServiceDefaults>;
readonly diagnosticsOptions: DiagnosticsOptions;
setDiagnosticsOptions(options: DiagnosticsOptions): void;
readonly onDidChange: IEvent<LanguageServiceDefaults>;
readonly diagnosticsOptions: DiagnosticsOptions;
setDiagnosticsOptions(options: DiagnosticsOptions): void;
}
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": {
"module": "umd",
"moduleResolution": "node",
"outDir": "../out",
"target": "es5"
"outDir": "../out/amd",
"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 {
private _defaults: LanguageServiceDefaultsImpl;
private _idleCheckInterval: number;
private _idleCheckInterval: NodeJS.Timer;
private _lastUsedTime: number;
private _configChangeListener: IDisposable;
@ -62,7 +62,7 @@ export class WorkerManager {
this._worker = monaco.editor.createWebWorker<YAMLWorker>({
// 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,

View file

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

View file

@ -1,146 +1,214 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Copyright (c) Adam Voss. All rights reserved.
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import Promise = monaco.Promise;
import Thenable = monaco.Thenable;
import IWorkerContext = monaco.worker.IWorkerContext;
import * as yamlService from './yaml-languageservice/yamlLanguageService';
import * as ls from 'vscode-languageserver-types';
import { getLineOffsets } from './yaml-languageservice/utils/arrUtils';
import { parse as parseYAML } from "./yaml-languageservice/parser/yamlParser";
export class YAMLWorker {
private _ctx: IWorkerContext;
private _languageService: yamlService.LanguageService;
private _languageSettings: yamlService.LanguageSettings;
private _languageId: string;
constructor(ctx: IWorkerContext, createData: ICreateData) {
this._ctx = ctx;
this._languageSettings = createData.languageSettings;
this._languageId = createData.languageId;
this._languageService = yamlService.getLanguageService();
this._languageService.configure(this._languageSettings);
}
doValidation(uri: string): Thenable<ls.Diagnostic[]> {
let document = this._getTextDocument(uri);
if (document) {
let jsonDocument = this._languageService.parseYAMLDocument(document);
return this._languageService.doValidation(document, jsonDocument);
}
return Promise.as([]);
}
doComplete(uri: string, position: ls.Position): Thenable<ls.CompletionList> {
let document = this._getTextDocument(uri);
let completionFix = completionHelper(document, position);
let newText = completionFix.newText;
let jsonDocument = parseYAML(newText);
return this._languageService.doComplete(document, position, jsonDocument);
}
doResolve(item: ls.CompletionItem): Thenable<ls.CompletionItem> {
return this._languageService.doResolve(item);
}
doHover(uri: string, position: ls.Position): Thenable<ls.Hover> {
let document = this._getTextDocument(uri);
let jsonDocument = this._languageService.parseYAMLDocument(document);
return this._languageService.doHover(document, position, jsonDocument);
}
format(uri: string, range: ls.Range, options: ls.FormattingOptions): Thenable<ls.TextEdit[]> {
let document = this._getTextDocument(uri);
let textEdits = this._languageService.format(document, options);
return Promise.as(textEdits);
}
resetSchema(uri: string): Thenable<boolean> {
return Promise.as(this._languageService.resetSchema(uri));
}
findDocumentSymbols(uri: string): Promise<ls.SymbolInformation[]> {
let document = this._getTextDocument(uri);
let jsonDocument = this._languageService.parseYAMLDocument(document);
let symbols = this._languageService.findDocumentSymbols(document, jsonDocument);
return Promise.as(symbols);
}
private _getTextDocument(uri: string): ls.TextDocument {
let models = this._ctx.getMirrorModels();
for (let model of models) {
if (model.uri.toString() === uri) {
return ls.TextDocument.create(uri, this._languageId, model.version, model.getValue());
}
}
return null;
}
}
export interface ICreateData {
languageId: string;
languageSettings: yamlService.LanguageSettings;
}
export function create(ctx: IWorkerContext, createData: ICreateData): YAMLWorker {
return new YAMLWorker(ctx, createData);
}
// https://github.com/redhat-developer/yaml-language-server/blob/5e069c0e9d7004d57f1fa6e93df670d4895883d1/src/server.ts#L453
function completionHelper(document: ls.TextDocument, textDocumentPosition: ls.Position) {
//Get the string we are looking at via a substring
let linePos = textDocumentPosition.line;
let position = textDocumentPosition;
let lineOffset = getLineOffsets(document.getText());
let start = lineOffset[linePos]; //Start of where the autocompletion is happening
let end = 0; //End of where the autocompletion is happening
if (lineOffset[linePos + 1]) {
end = lineOffset[linePos + 1];
} else {
end = document.getText().length;
}
let textLine = document.getText().substring(start, end);
//Check if the string we are looking at is a node
if (textLine.indexOf(":") === -1) {
//We need to add the ":" to load the nodes
let newText = "";
//This is for the empty line case
let trimmedText = textLine.trim();
if (trimmedText.length === 0 || (trimmedText.length === 1 && trimmedText[0] === '-')) {
//Add a temp node that is in the document but we don't use at all.
if (lineOffset[linePos + 1]) {
newText = document.getText().substring(0, start + (textLine.length - 1)) + "holder:\r\n" + document.getText().substr(end + 2);
} else {
newText = document.getText().substring(0, start + (textLine.length)) + "holder:\r\n" + document.getText().substr(end + 2);
}
//For when missing semi colon case
} else {
//Add a semicolon to the end of the current line so we can validate the node
if (lineOffset[linePos + 1]) {
newText = document.getText().substring(0, start + (textLine.length - 1)) + ":\r\n" + document.getText().substr(end + 2);
} else {
newText = document.getText().substring(0, start + (textLine.length)) + ":\r\n" + document.getText().substr(end + 2);
}
}
return {
"newText": newText,
"newPosition": textDocumentPosition
}
} else {
//All the nodes are loaded
position.character = position.character - 1;
return {
"newText": document.getText(),
"newPosition": position
}
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Copyright (c) Adam Voss. All rights reserved.
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import Promise = monaco.Promise;
import Thenable = monaco.Thenable;
import IWorkerContext = monaco.worker.IWorkerContext;
import * as ls from 'vscode-languageserver-types';
import * as yamlService from './languageservice/yamlLanguageService';
import { SchemaRequestService } from './languageservice/yamlLanguageService';
class PromiseAdapter<T> implements yamlService.Thenable<T> {
private wrapped: monaco.Promise<T>;
constructor(executor: (resolve: (value?: T | yamlService.Thenable<T>) => void, reject: (reason?: any) => void) => void) {
this.wrapped = new monaco.Promise<T>(executor);
}
public then<TResult>(onfulfilled?: (value: T) => TResult | yamlService.Thenable<TResult>, onrejected?: (reason: any) => void): yamlService.Thenable<TResult> {
let thenable : yamlService.Thenable<T> = this.wrapped;
return thenable.then(onfulfilled, onrejected);
}
public getWrapped(): monaco.Thenable<T> {
return this.wrapped;
}
public cancel(): void {
this.wrapped.cancel();
}
public static resolve<T>(v: T | Thenable<T>): yamlService.Thenable<T> {
return <monaco.Thenable<T>> monaco.Promise.as(v);
}
public static reject<T>(v: T): yamlService.Thenable<T> {
return monaco.Promise.wrapError(<any>v);
}
public static all<T>(values: yamlService.Thenable<T>[]): yamlService.Thenable<T[]> {
return monaco.Promise.join(values);
}
}
// Currently we only support loading schemas via xhr:
const ajax = (url: string) =>
new Promise((resolve, reject) => {
const request = new XMLHttpRequest();
request.onreadystatechange = () => {
if (request.readyState === XMLHttpRequest.DONE) {
const response = request.responseText;
if (request.status < 400) {
resolve(response);
} else {
reject(response);
}
}
};
request.onerror = reject;
request.open('GET', url);
request.send();
});
export class YAMLWorker {
private _ctx: IWorkerContext;
private _languageService: yamlService.LanguageService;
private _languageSettings: yamlService.LanguageSettings;
private _languageId: string;
constructor(ctx: IWorkerContext, createData: ICreateData) {
this._ctx = ctx;
this._languageSettings = createData.languageSettings;
this._languageId = createData.languageId;
this._languageService = yamlService.getLanguageService(ajax, null, [], null, PromiseAdapter);
this._languageService.configure(this._languageSettings);
}
doValidation(uri: string): Thenable<ls.Diagnostic[]> {
let document = this._getTextDocument(uri);
if (document) {
let yamlDocument = this._languageService.parseYAMLDocument(document);
return this._languageService.doValidation(document, yamlDocument);
}
return Promise.as([]);
}
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);
return this._languageService.doComplete(document, position, yamlDocument);
}
doResolve(item: ls.CompletionItem): Thenable<ls.CompletionItem> {
return this._languageService.doResolve(item);
}
doHover(uri: string, position: ls.Position): Thenable<ls.Hover> {
let document = this._getTextDocument(uri);
let yamlDocument = this._languageService.parseYAMLDocument(document)
return this._languageService.doHover(document, position, yamlDocument);
}
format(uri: string, range: ls.Range, options: ls.FormattingOptions): Thenable<ls.TextEdit[]> {
let document = this._getTextDocument(uri);
let textEdits = this._languageService.doFormat(document, options, []);
return Promise.as(textEdits);
}
resetSchema(uri: string): Thenable<boolean> {
return Promise.as(this._languageService.resetSchema(uri));
}
findDocumentSymbols(uri: string): Thenable<ls.SymbolInformation[]> {
let document = this._getTextDocument(uri);
let yamlDocument = this._languageService.parseYAMLDocument(document);
let symbols = this._languageService.findDocumentSymbols(document, yamlDocument);
return Promise.as(symbols);
}
private _getTextDocument(uri: string): ls.TextDocument {
let models = this._ctx.getMirrorModels();
for (let model of models) {
if (model.uri.toString() === uri) {
return ls.TextDocument.create(uri, this._languageId, model.version, model.getValue());
}
}
return null;
}
}
export interface ICreateData {
languageId: string;
languageSettings: yamlService.LanguageSettings;
schemaRequestService?: SchemaRequestService;
}
export function create(ctx: IWorkerContext, createData: ICreateData): YAMLWorker {
return new YAMLWorker(ctx, createData);
}
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;
}
// 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>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<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">
<meta http-equiv="X-UA-Compatible"
content="IE=edge" />
<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>
<body>
<h2>Monaco Editor JSON test page</h2>
<div id="container" style="width:800px;height:600px;border:1px solid grey"></div>
<h2>Monaco Editor YAML test page</h2>
<code id="path"></code>
<div id="container"
style="width:800px;height:600px;border:1px solid grey"></div>
<script>
// Loading basic-languages to get the json language definition
var require = {
paths: {
'vs/basic-languages': '../node_modules/monaco-languages/release',
'vs/languages/yaml': '../release/dev',
<script>
// Loading basic-languages to get the YAML language definition
var paths = {
'vs/basic-languages': '../node_modules/monaco-languages/release/dev',
'vs/language/yaml': '../release/dev',
'vs': '../node_modules/monaco-editor-core/dev/vs'
}
};
</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>
if (document.location.protocol === 'http:') {
// Add support for running local http server
let testIndex = document.location.pathname.indexOf('/test/');
if (testIndex !== -1) {
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>
require([
'vs/basic-languages/src/monaco.contribution',
'vs/languages/yaml/monaco.contribution'
], function() {
monaco.languages.yaml.yamlDefaults.setDiagnosticsOptions({
validate: true,
schemas: [{
fileMatch: ['*'],
schema: {
title: 'Person',
type: 'object',
properties: {
name: { type: 'string' },
age: {
description: 'Age in years',
type: 'integer',
minimum: 0
}
<script>
require([
'vs/basic-languages/monaco.contribution',
'vs/language/yaml/monaco.contribution'
], function () {
const yaml = `apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: default
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
apps.deployment: nginx
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'), {
value: [
'name: Apple',
'age: 18.0'
].join('\n'),
language: 'yaml'
// See: https://github.com/Microsoft/vscode/blob/master/src/vs/editor/contrib/quickOpen/quickOpen.ts
require(['vs/editor/contrib/quickOpen/quickOpen'], quickOpen => {
// Breadcrumbs emulation:
editor.onDidChangeCursorSelection(({ selection }) => {
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>
</html>

2060
yarn.lock

File diff suppressed because it is too large Load diff