mirror of
https://github.com/danbulant/monaco-yaml
synced 2026-06-17 05:21:05 +00:00
chore: align with pengx17/master branch
This commit is contained in:
parent
63bca4922f
commit
a0db642717
40 changed files with 1499 additions and 2926 deletions
|
|
@ -1,8 +1,7 @@
|
|||
/.vscode/
|
||||
/lib/
|
||||
/out/
|
||||
/scripts/
|
||||
/src/
|
||||
/test/
|
||||
/release/dev/
|
||||
/gulpfile.js
|
||||
/.gitignore
|
||||
/.npmignore
|
||||
|
|
|
|||
22
README.md
22
README.md
|
|
@ -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:
|
||||

|
||||
|
||||
## 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)
|
||||
|
|
|
|||
190
gulpfile.js
190
gulpfile.js
|
|
@ -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];
|
||||
}
|
||||
44
package.json
44
package.json
|
|
@ -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
82
scripts/bundle.js
Normal 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
28
scripts/release.js
Normal 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"
|
||||
}
|
||||
});
|
||||
|
|
@ -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)
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
@ -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) {
|
||||
|
|
@ -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)));
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
@ -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 => {
|
||||
|
|
@ -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 '';
|
||||
}
|
||||
}
|
||||
}
|
||||
47
src/languageservice/services/yamlFormatter.ts
Normal file
47
src/languageservice/services/yamlFormatter.ts
Normal 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)]
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
75
src/languageservice/utils/arrUtils.ts
Normal file
75
src/languageservice/utils/arrUtils.ts
Normal 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;
|
||||
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
@ -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(){
|
||||
|
|
@ -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);
|
||||
}
|
||||
155
src/languageservice/yamlLanguageService.ts
Normal file
155
src/languageservice/yamlLanguageService.ts
Normal 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())
|
||||
}
|
||||
}
|
||||
|
|
@ -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
43
src/monaco.d.ts
vendored
|
|
@ -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
16
src/tsconfig.esm.json
Normal 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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
import YAMLException = require('./exception');
|
||||
import YAMLException from './exception';
|
||||
|
||||
var TYPE_CONSTRUCTOR_OPTIONS = [
|
||||
'kind',
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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)]
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
@ -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
15
src/yaml.worker.ts
Normal 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)
|
||||
});
|
||||
};
|
||||
142
src/yamlMode.ts
142
src/yamlMode.ts
|
|
@ -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 }
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
BIN
test-demo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 154 KiB |
141
test/index.html
141
test/index.html
|
|
@ -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>
|
||||
Loading…
Reference in a new issue