mirror of
https://github.com/danbulant/monaco-yaml
synced 2026-07-09 21:11:01 +00:00
commit
d1b457c5dc
10 changed files with 259 additions and 450 deletions
|
|
@ -20,10 +20,9 @@ Load with ESM is added, but not yet tested.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
* `git clone https://github.com/kpdecker/monaco-yaml`
|
* `git clone https://github.com/pengx17/monaco-yaml`
|
||||||
* `cd monaco-yaml`
|
* `cd monaco-yaml`
|
||||||
* `yarn`
|
* `yarn`
|
||||||
* `yarn watch`
|
|
||||||
* open `$/monaco-yaml/test/index.html` in your favorite browser.
|
* open `$/monaco-yaml/test/index.html` in your favorite browser.
|
||||||
|
|
||||||
A running example:
|
A running example:
|
||||||
|
|
@ -40,4 +39,4 @@ Manually clone dependencies list below and update the project files accordingly:
|
||||||
- `src/yaml-ast-parser`: https://github.com/mulesoft-labs/yaml-ast-parser/tree/master/src
|
- `src/yaml-ast-parser`: https://github.com/mulesoft-labs/yaml-ast-parser/tree/master/src
|
||||||
|
|
||||||
## License
|
## License
|
||||||
[MIT](https://github.com/kpdecker/monaco-yaml/blob/master/LICENSE.md)
|
[MIT](https://github.com/pengx17/monaco-yaml/blob/master/LICENSE.md)
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,10 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/kpdecker/monaco-yaml"
|
"url": "https://github.com/pengx17/monaco-yaml"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/kpdecker/monaco-yaml/issues"
|
"url": "https://github.com/pengx17/monaco-yaml/issues"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chai": "^4.1.4",
|
"@types/chai": "^4.1.4",
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
"@types/node": "^10.9.3",
|
"@types/node": "^10.9.3",
|
||||||
"js-yaml": "^3.12.0",
|
"js-yaml": "^3.12.0",
|
||||||
"jsonc-parser": "^2.0.2",
|
"jsonc-parser": "^2.0.2",
|
||||||
"monaco-editor-core": "0.15.0",
|
"monaco-editor-core": "0.15.5",
|
||||||
"monaco-languages": "1.6.0",
|
"monaco-languages": "1.6.0",
|
||||||
"monaco-plugin-helpers": "^1.0.2",
|
"monaco-plugin-helpers": "^1.0.2",
|
||||||
"requirejs": "^2.3.5",
|
"requirejs": "^2.3.5",
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ export abstract class ASTNodeImpl {
|
||||||
|
|
||||||
constructor(parent: ASTNode, offset: number, length?: number) {
|
constructor(parent: ASTNode, offset: number, length?: number) {
|
||||||
this.offset = offset;
|
this.offset = offset;
|
||||||
this.length = length;
|
this.length = length || 0;
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -290,6 +290,30 @@ export function contains(node: ASTNode, offset: number, includeRightBound = fals
|
||||||
return offset >= node.offset && offset < (node.offset + node.length) || includeRightBound && offset === (node.offset + node.length);
|
return offset >= node.offset && offset < (node.offset + node.length) || includeRightBound && offset === (node.offset + node.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// export function contains(node: ASTNode, offset: number, includeRightBound = false): boolean {
|
||||||
|
// let flag = offset >= node.offset && offset <= (node.offset + node.length);
|
||||||
|
// if (!flag && includeRightBound) {
|
||||||
|
// if (node.parent && node.parent.children && )
|
||||||
|
// const nextSibling = node.parent
|
||||||
|
// }
|
||||||
|
// return flag;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export function findNodeAtOffset(node: ASTNode, offset: number, includeRightBound = false): ASTNode | undefined {
|
||||||
|
// if (contains(node, offset, includeRightBound)) {
|
||||||
|
// const children = node.children;
|
||||||
|
// if (Array.isArray(children)) {
|
||||||
|
// for (var i = 0; i < children.length && children[i].offset <= offset; i++) {
|
||||||
|
// const item = findNodeAtOffset(children[i], offset, includeRightBound);
|
||||||
|
// if (item) {
|
||||||
|
// return item;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return node;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
export class JSONDocument {
|
export class JSONDocument {
|
||||||
|
|
||||||
constructor(public root: ASTNode, public readonly syntaxErrors: Diagnostic[] = [], public readonly comments: Range[] = []) {
|
constructor(public root: ASTNode, public readonly syntaxErrors: Diagnostic[] = [], public readonly comments: Range[] = []) {
|
||||||
|
|
@ -298,6 +322,7 @@ export class JSONDocument {
|
||||||
public getNodeFromOffset(offset: number, includeRightBound = false): ASTNode | undefined {
|
public getNodeFromOffset(offset: number, includeRightBound = false): ASTNode | undefined {
|
||||||
if (this.root) {
|
if (this.root) {
|
||||||
return <ASTNode>Json.findNodeAtOffset(this.root, offset, includeRightBound);
|
return <ASTNode>Json.findNodeAtOffset(this.root, offset, includeRightBound);
|
||||||
|
//return findNodeAtOffset(this.root, offset, includeRightBound);
|
||||||
}
|
}
|
||||||
return void 0;
|
return void 0;
|
||||||
}
|
}
|
||||||
|
|
@ -987,307 +1012,3 @@ function validate(node: ASTNode, schema: JSONSchema, validationResult: Validatio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function parse(textDocument: TextDocument, config?: JSONDocumentConfig): JSONDocument {
|
|
||||||
|
|
||||||
let problems: Diagnostic[] = [];
|
|
||||||
let lastProblemOffset = -1;
|
|
||||||
let text = textDocument.getText();
|
|
||||||
let scanner = Json.createScanner(text, false);
|
|
||||||
|
|
||||||
let commentRanges: Range[] = config && config.collectComments ? [] : void 0;
|
|
||||||
|
|
||||||
function _scanNext(): Json.SyntaxKind {
|
|
||||||
while (true) {
|
|
||||||
let token = scanner.scan();
|
|
||||||
_checkScanError();
|
|
||||||
switch (token) {
|
|
||||||
case Json.SyntaxKind.LineCommentTrivia:
|
|
||||||
case Json.SyntaxKind.BlockCommentTrivia:
|
|
||||||
if (Array.isArray(commentRanges)) {
|
|
||||||
commentRanges.push(Range.create(textDocument.positionAt(scanner.getTokenOffset()), textDocument.positionAt(scanner.getTokenOffset() + scanner.getTokenLength())));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Json.SyntaxKind.Trivia:
|
|
||||||
case Json.SyntaxKind.LineBreakTrivia:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _accept(token: Json.SyntaxKind): boolean {
|
|
||||||
if (scanner.getToken() === token) {
|
|
||||||
_scanNext();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _errorAtRange<T extends ASTNode>(message: string, code: ErrorCode, startOffset: number, endOffset: number, severity: DiagnosticSeverity = DiagnosticSeverity.Error): void {
|
|
||||||
|
|
||||||
if (problems.length === 0 || startOffset !== lastProblemOffset) {
|
|
||||||
let range = Range.create(textDocument.positionAt(startOffset), textDocument.positionAt(endOffset));
|
|
||||||
problems.push(Diagnostic.create(range, message, severity, code, textDocument.languageId));
|
|
||||||
lastProblemOffset = startOffset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _error<T extends ASTNodeImpl>(message: string, code: ErrorCode, node: T = null, skipUntilAfter: Json.SyntaxKind[] = [], skipUntil: Json.SyntaxKind[] = []): T {
|
|
||||||
let start = scanner.getTokenOffset();
|
|
||||||
let end = scanner.getTokenOffset() + scanner.getTokenLength();
|
|
||||||
if (start === end && start > 0) {
|
|
||||||
start--;
|
|
||||||
while (start > 0 && /\s/.test(text.charAt(start))) {
|
|
||||||
start--;
|
|
||||||
}
|
|
||||||
end = start + 1;
|
|
||||||
}
|
|
||||||
_errorAtRange(message, code, start, end);
|
|
||||||
|
|
||||||
if (node) {
|
|
||||||
_finalize(node, false);
|
|
||||||
}
|
|
||||||
if (skipUntilAfter.length + skipUntil.length > 0) {
|
|
||||||
let token = scanner.getToken();
|
|
||||||
while (token !== Json.SyntaxKind.EOF) {
|
|
||||||
if (skipUntilAfter.indexOf(token) !== -1) {
|
|
||||||
_scanNext();
|
|
||||||
break;
|
|
||||||
} else if (skipUntil.indexOf(token) !== -1) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
token = _scanNext();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _checkScanError(): boolean {
|
|
||||||
switch (scanner.getTokenError()) {
|
|
||||||
case Json.ScanError.InvalidUnicode:
|
|
||||||
_error(localize('InvalidUnicode', 'Invalid unicode sequence in string.'), ErrorCode.InvalidUnicode);
|
|
||||||
return true;
|
|
||||||
case Json.ScanError.InvalidEscapeCharacter:
|
|
||||||
_error(localize('InvalidEscapeCharacter', 'Invalid escape character in string.'), ErrorCode.InvalidEscapeCharacter);
|
|
||||||
return true;
|
|
||||||
case Json.ScanError.UnexpectedEndOfNumber:
|
|
||||||
_error(localize('UnexpectedEndOfNumber', 'Unexpected end of number.'), ErrorCode.UnexpectedEndOfNumber);
|
|
||||||
return true;
|
|
||||||
case Json.ScanError.UnexpectedEndOfComment:
|
|
||||||
_error(localize('UnexpectedEndOfComment', 'Unexpected end of comment.'), ErrorCode.UnexpectedEndOfComment);
|
|
||||||
return true;
|
|
||||||
case Json.ScanError.UnexpectedEndOfString:
|
|
||||||
_error(localize('UnexpectedEndOfString', 'Unexpected end of string.'), ErrorCode.UnexpectedEndOfString);
|
|
||||||
return true;
|
|
||||||
case Json.ScanError.InvalidCharacter:
|
|
||||||
_error(localize('InvalidCharacter', 'Invalid characters in string. Control characters must be escaped.'), ErrorCode.InvalidCharacter);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _finalize<T extends ASTNodeImpl>(node: T, scanNext: boolean): T {
|
|
||||||
node.length = scanner.getTokenOffset() + scanner.getTokenLength() - node.offset;
|
|
||||||
|
|
||||||
if (scanNext) {
|
|
||||||
_scanNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _parseArray(parent: ASTNode): ArrayASTNode {
|
|
||||||
if (scanner.getToken() !== Json.SyntaxKind.OpenBracketToken) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
let node = new ArrayASTNodeImpl(parent, scanner.getTokenOffset());
|
|
||||||
_scanNext(); // consume OpenBracketToken
|
|
||||||
|
|
||||||
let count = 0;
|
|
||||||
let needsComma = false;
|
|
||||||
while (scanner.getToken() !== Json.SyntaxKind.CloseBracketToken && scanner.getToken() !== Json.SyntaxKind.EOF) {
|
|
||||||
if (scanner.getToken() === Json.SyntaxKind.CommaToken) {
|
|
||||||
if (!needsComma) {
|
|
||||||
_error(localize('ValueExpected', 'Value expected'), ErrorCode.ValueExpected);
|
|
||||||
}
|
|
||||||
let commaOffset = scanner.getTokenOffset();
|
|
||||||
_scanNext(); // consume comma
|
|
||||||
if (scanner.getToken() === Json.SyntaxKind.CloseBracketToken) {
|
|
||||||
if (needsComma) {
|
|
||||||
_errorAtRange(localize('TrailingComma', 'Trailing comma'), ErrorCode.TrailingComma, commaOffset, commaOffset + 1);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else if (needsComma) {
|
|
||||||
_error(localize('ExpectedComma', 'Expected comma'), ErrorCode.CommaExpected);
|
|
||||||
}
|
|
||||||
let item = _parseValue(node, count++);
|
|
||||||
if (!item) {
|
|
||||||
_error(localize('PropertyExpected', 'Value expected'), ErrorCode.ValueExpected, null, [], [Json.SyntaxKind.CloseBracketToken, Json.SyntaxKind.CommaToken]);
|
|
||||||
} else {
|
|
||||||
node.items.push(item);
|
|
||||||
}
|
|
||||||
needsComma = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scanner.getToken() !== Json.SyntaxKind.CloseBracketToken) {
|
|
||||||
return _error(localize('ExpectedCloseBracket', 'Expected comma or closing bracket'), ErrorCode.CommaOrCloseBacketExpected, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _finalize(node, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _parseProperty(parent: ObjectASTNode, keysSeen: { [key: string]: (PropertyASTNode | boolean) }): PropertyASTNode {
|
|
||||||
|
|
||||||
let node = new PropertyASTNodeImpl(parent, scanner.getTokenOffset());
|
|
||||||
let key = _parseString(node);
|
|
||||||
if (!key) {
|
|
||||||
if (scanner.getToken() === Json.SyntaxKind.Unknown) {
|
|
||||||
// give a more helpful error message
|
|
||||||
_error(localize('DoubleQuotesExpected', 'Property keys must be doublequoted'), ErrorCode.Undefined);
|
|
||||||
let keyNode = new StringASTNodeImpl(node, scanner.getTokenOffset(), scanner.getTokenLength());
|
|
||||||
keyNode.value = scanner.getTokenValue();
|
|
||||||
key = keyNode;
|
|
||||||
_scanNext(); // consume Unknown
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
node.keyNode = key;
|
|
||||||
|
|
||||||
let seen = keysSeen[key.value];
|
|
||||||
if (seen) {
|
|
||||||
_errorAtRange(localize('DuplicateKeyWarning', "Duplicate object key"), ErrorCode.DuplicateKey, node.keyNode.offset, node.keyNode.offset + node.keyNode.length, DiagnosticSeverity.Warning);
|
|
||||||
if (typeof seen === 'object') {
|
|
||||||
_errorAtRange(localize('DuplicateKeyWarning', "Duplicate object key"), ErrorCode.DuplicateKey, seen.keyNode.offset, seen.keyNode.offset + seen.keyNode.length, DiagnosticSeverity.Warning);
|
|
||||||
}
|
|
||||||
keysSeen[key.value] = true; // if the same key is duplicate again, avoid duplicate error reporting
|
|
||||||
} else {
|
|
||||||
keysSeen[key.value] = node;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scanner.getToken() === Json.SyntaxKind.ColonToken) {
|
|
||||||
node.colonOffset = scanner.getTokenOffset();
|
|
||||||
_scanNext(); // consume ColonToken
|
|
||||||
} else {
|
|
||||||
_error(localize('ColonExpected', 'Colon expected'), ErrorCode.ColonExpected);
|
|
||||||
if (scanner.getToken() === Json.SyntaxKind.StringLiteral && textDocument.positionAt(key.offset + key.length).line < textDocument.positionAt(scanner.getTokenOffset()).line) {
|
|
||||||
node.length = key.length;
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let value = _parseValue(node, key.value);
|
|
||||||
if (!value) {
|
|
||||||
return _error(localize('ValueExpected', 'Value expected'), ErrorCode.ValueExpected, node, [], [Json.SyntaxKind.CloseBraceToken, Json.SyntaxKind.CommaToken]);
|
|
||||||
}
|
|
||||||
node.valueNode = value;
|
|
||||||
node.length = value.offset + value.length - node.offset;
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _parseObject(parent: ASTNode): ObjectASTNode {
|
|
||||||
if (scanner.getToken() !== Json.SyntaxKind.OpenBraceToken) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
let node = new ObjectASTNodeImpl(parent, scanner.getTokenOffset());
|
|
||||||
let keysSeen: any = Object.create(null);
|
|
||||||
_scanNext(); // consume OpenBraceToken
|
|
||||||
let needsComma = false;
|
|
||||||
|
|
||||||
while (scanner.getToken() !== Json.SyntaxKind.CloseBraceToken && scanner.getToken() !== Json.SyntaxKind.EOF) {
|
|
||||||
if (scanner.getToken() === Json.SyntaxKind.CommaToken) {
|
|
||||||
if (!needsComma) {
|
|
||||||
_error(localize('PropertyExpected', 'Property expected'), ErrorCode.PropertyExpected);
|
|
||||||
}
|
|
||||||
let commaOffset = scanner.getTokenOffset();
|
|
||||||
_scanNext(); // consume comma
|
|
||||||
if (scanner.getToken() === Json.SyntaxKind.CloseBraceToken) {
|
|
||||||
if (needsComma) {
|
|
||||||
_errorAtRange(localize('TrailingComma', 'Trailing comma'), ErrorCode.TrailingComma, commaOffset, commaOffset + 1);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
} else if (needsComma) {
|
|
||||||
_error(localize('ExpectedComma', 'Expected comma'), ErrorCode.CommaExpected);
|
|
||||||
}
|
|
||||||
let property = _parseProperty(node, keysSeen);
|
|
||||||
if (!property) {
|
|
||||||
_error(localize('PropertyExpected', 'Property expected'), ErrorCode.PropertyExpected, null, [], [Json.SyntaxKind.CloseBraceToken, Json.SyntaxKind.CommaToken]);
|
|
||||||
} else {
|
|
||||||
node.properties.push(property);
|
|
||||||
}
|
|
||||||
needsComma = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scanner.getToken() !== Json.SyntaxKind.CloseBraceToken) {
|
|
||||||
return _error(localize('ExpectedCloseBrace', 'Expected comma or closing brace'), ErrorCode.CommaOrCloseBraceExpected, node);
|
|
||||||
}
|
|
||||||
return _finalize(node, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _parseString(parent: ASTNode): StringASTNode {
|
|
||||||
if (scanner.getToken() !== Json.SyntaxKind.StringLiteral) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let node = new StringASTNodeImpl(parent, scanner.getTokenOffset());
|
|
||||||
node.value = scanner.getTokenValue();
|
|
||||||
|
|
||||||
return _finalize(node, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _parseNumber(parent: ASTNode): NumberASTNode {
|
|
||||||
if (scanner.getToken() !== Json.SyntaxKind.NumericLiteral) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let node = new NumberASTNodeImpl(parent, scanner.getTokenOffset());
|
|
||||||
if (scanner.getTokenError() === Json.ScanError.None) {
|
|
||||||
let tokenValue = scanner.getTokenValue();
|
|
||||||
try {
|
|
||||||
let numberValue = JSON.parse(tokenValue);
|
|
||||||
if (!isNumber(numberValue)) {
|
|
||||||
return _error(localize('InvalidNumberFormat', 'Invalid number format.'), ErrorCode.Undefined, node);
|
|
||||||
}
|
|
||||||
node.value = numberValue;
|
|
||||||
} catch (e) {
|
|
||||||
return _error(localize('InvalidNumberFormat', 'Invalid number format.'), ErrorCode.Undefined, node);
|
|
||||||
}
|
|
||||||
node.isInteger = tokenValue.indexOf('.') === -1;
|
|
||||||
}
|
|
||||||
return _finalize(node, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _parseLiteral(parent: ASTNode): ASTNode {
|
|
||||||
let node: ASTNodeImpl;
|
|
||||||
switch (scanner.getToken()) {
|
|
||||||
case Json.SyntaxKind.NullKeyword:
|
|
||||||
return _finalize(new NullASTNodeImpl(parent, scanner.getTokenOffset()), true);
|
|
||||||
case Json.SyntaxKind.TrueKeyword:
|
|
||||||
return _finalize(new BooleanASTNodeImpl(parent, true, scanner.getTokenOffset()), true);
|
|
||||||
case Json.SyntaxKind.FalseKeyword:
|
|
||||||
return _finalize(new BooleanASTNodeImpl(parent, false, scanner.getTokenOffset()), true);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _parseValue(parent: ASTNode, name: Json.Segment): ASTNode {
|
|
||||||
return _parseArray(parent) || _parseObject(parent) || _parseString(parent) || _parseNumber(parent) || _parseLiteral(parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
let _root = null;
|
|
||||||
let token = _scanNext();
|
|
||||||
if (token !== Json.SyntaxKind.EOF) {
|
|
||||||
_root = _parseValue(null, null);
|
|
||||||
if (!_root) {
|
|
||||||
_error(localize('Invalid symbol', 'Expected a JSON object, array or literal.'), ErrorCode.Undefined);
|
|
||||||
} else if (scanner.getToken() !== Json.SyntaxKind.EOF) {
|
|
||||||
_error(localize('End of file expected', 'End of file expected.'), ErrorCode.Undefined);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new JSONDocument(_root, problems, commentRanges);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,14 @@ import * as nls from 'vscode-nls';
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
import * as Yaml from '../../yaml-ast-parser/index'
|
import * as Yaml from '../../yaml-ast-parser/index'
|
||||||
import { Schema, Type } from 'js-yaml';
|
|
||||||
|
|
||||||
import { getLineStartPositions } from '../utils/documentPositionCalculator'
|
import { getLineStartPositions } from '../utils/documentPositionCalculator'
|
||||||
import { parseYamlBoolean } from './scalar-type';
|
import { parseYamlBoolean } from './scalar-type';
|
||||||
import { ObjectASTNodeImpl, StringASTNodeImpl, PropertyASTNodeImpl, NullASTNodeImpl, ArrayASTNodeImpl, BooleanASTNodeImpl, NumberASTNodeImpl } from './jsonParser';
|
import { ObjectASTNodeImpl, StringASTNodeImpl, PropertyASTNodeImpl, NullASTNodeImpl, ArrayASTNodeImpl, BooleanASTNodeImpl, NumberASTNodeImpl } from './jsonParser';
|
||||||
import { ASTNode, PropertyASTNode, ErrorCode } from '../jsonLanguageTypes';
|
import { ASTNode, PropertyASTNode, ErrorCode } from '../jsonLanguageTypes';
|
||||||
import { SingleYAMLDocument, YAMLDocument } from '../yamlLanguageTypes';
|
import { SingleYAMLDocument, YAMLDocument } from '../yamlLanguageTypes';
|
||||||
|
import { Schema } from '../../yaml-ast-parser/schema';
|
||||||
|
import { Type } from '../../yaml-ast-parser/type';
|
||||||
|
|
||||||
function recursivelyBuildAst(parent: ASTNode, node: Yaml.YAMLNode): ASTNode {
|
function recursivelyBuildAst(parent: ASTNode, node: Yaml.YAMLNode): ASTNode {
|
||||||
|
|
||||||
|
|
@ -47,7 +48,8 @@ function recursivelyBuildAst(parent: ASTNode, node: Yaml.YAMLNode): ASTNode {
|
||||||
const keyNode = new StringASTNodeImpl(result, key.startPosition, key.endPosition - key.startPosition);
|
const keyNode = new StringASTNodeImpl(result, key.startPosition, key.endPosition - key.startPosition);
|
||||||
keyNode.value = key.value;
|
keyNode.value = key.value;
|
||||||
|
|
||||||
const valueNode = (instance.value) ? recursivelyBuildAst(result, instance.value) : new NullASTNodeImpl(parent, instance.startPosition)
|
// TODO: calculate the correct NULL range.
|
||||||
|
const valueNode = (instance.value) ? recursivelyBuildAst(result, instance.value) : new NullASTNodeImpl(parent, instance.endPosition);
|
||||||
|
|
||||||
result.keyNode = keyNode;
|
result.keyNode = keyNode;
|
||||||
result.valueNode = valueNode;
|
result.valueNode = valueNode;
|
||||||
|
|
@ -131,7 +133,7 @@ function recursivelyBuildAst(parent: ASTNode, node: Yaml.YAMLNode): ASTNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertError(e: Yaml.Error) {
|
function convertError(e: Yaml.Error) {
|
||||||
return { message: `${e.reason}`, location: { start: e.mark.position, end: e.mark.position + e.mark.column, code: ErrorCode.Undefined } }
|
return { message: `${e.reason}`, location: { start: e.mark.position - e.mark.column, end: e.mark.position, code: ErrorCode.Undefined } }
|
||||||
}
|
}
|
||||||
|
|
||||||
function createJSONDocument(yamlDoc: Yaml.YAMLNode, startPositions: number[], text: string) {
|
function createJSONDocument(yamlDoc: Yaml.YAMLNode, startPositions: number[], text: string) {
|
||||||
|
|
@ -169,7 +171,7 @@ export function parse(text: string, customTags = []): YAMLDocument {
|
||||||
const startPositions = getLineStartPositions(text)
|
const startPositions = getLineStartPositions(text)
|
||||||
// This is documented to return a YAMLNode even though the
|
// This is documented to return a YAMLNode even though the
|
||||||
// typing only returns a YAMLDocument
|
// typing only returns a YAMLDocument
|
||||||
const yamlDocs = []
|
const yamlDocs: Yaml.YAMLNode[] = []
|
||||||
|
|
||||||
let schemaWithAdditionalTags = Schema.create(customTags.map((tag) => {
|
let schemaWithAdditionalTags = Schema.create(customTags.map((tag) => {
|
||||||
const typeInfo = tag.split(' ');
|
const typeInfo = tag.split(' ');
|
||||||
|
|
|
||||||
|
|
@ -58,11 +58,13 @@ export class YAMLCompletion {
|
||||||
|
|
||||||
let currentDoc = matchOffsetToDocument(offset, doc);
|
let currentDoc = matchOffsetToDocument(offset, doc);
|
||||||
|
|
||||||
|
const currentDocIndex = doc.documents.indexOf(currentDoc);
|
||||||
|
|
||||||
if (currentDoc === null) {
|
if (currentDoc === null) {
|
||||||
return Promise.resolve(result);
|
return Promise.resolve(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
let node = currentDoc.getNodeFromOffset(offset, true);
|
let node = currentDoc.getNodeFromOffsetEndInclusive(offset);
|
||||||
if (this.isInComment(document, node ? node.offset : 0, offset)) {
|
if (this.isInComment(document, node ? node.offset : 0, offset)) {
|
||||||
return Promise.resolve(result);
|
return Promise.resolve(result);
|
||||||
}
|
}
|
||||||
|
|
@ -131,12 +133,13 @@ export class YAMLCompletion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Support multiple doc per YAML
|
||||||
|
if (schema && schema.schema && schema.schema.schemaSequence && schema.schema.schemaSequence[currentDocIndex]) {
|
||||||
|
schema = new SchemaService.ResolvedSchema(schema.schema.schemaSequence[currentDocIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
// proposals for properties
|
// proposals for properties
|
||||||
if (node && node.type === 'object') {
|
if (node && node.type === 'object') {
|
||||||
// don't suggest keys when the cursor is just before the opening curly brace
|
|
||||||
if (node.offset === offset) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
// don't suggest properties that are already present
|
// don't suggest properties that are already present
|
||||||
let properties = node.properties;
|
let properties = node.properties;
|
||||||
properties.forEach(p => {
|
properties.forEach(p => {
|
||||||
|
|
@ -362,7 +365,6 @@ export class YAMLCompletion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private getValueCompletions(schema: SchemaService.ResolvedSchema, doc: Parser.JSONDocument, node: ASTNode, offset: number, document: TextDocument, collector: CompletionsCollector, types: { [type: string]: boolean }): void {
|
private getValueCompletions(schema: SchemaService.ResolvedSchema, doc: Parser.JSONDocument, node: ASTNode, offset: number, document: TextDocument, collector: CompletionsCollector, types: { [type: string]: boolean }): void {
|
||||||
let offsetForSeparator = offset;
|
let offsetForSeparator = offset;
|
||||||
let parentKey: string = null;
|
let parentKey: string = null;
|
||||||
|
|
@ -679,11 +681,11 @@ export class YAMLCompletion {
|
||||||
}
|
}
|
||||||
|
|
||||||
private getInsertTextForValue(value: any, separatorAfter: string): string {
|
private getInsertTextForValue(value: any, separatorAfter: string): string {
|
||||||
var text = JSON.stringify(value, null, '\t');
|
const text = value;
|
||||||
if (text === '{}') {
|
if (text === '{}') {
|
||||||
return '{$1}' + separatorAfter;
|
return '{\n\t$1\n}' + separatorAfter;
|
||||||
} else if (text === '[]') {
|
} else if (text === '[]') {
|
||||||
return '[$1]' + separatorAfter;
|
return '[\n\t$1\n]' + separatorAfter;
|
||||||
}
|
}
|
||||||
return this.getInsertTextForPlainText(text + separatorAfter);
|
return this.getInsertTextForPlainText(text + separatorAfter);
|
||||||
}
|
}
|
||||||
|
|
@ -807,13 +809,13 @@ export class YAMLCompletion {
|
||||||
value = ' $1';
|
value = ' $1';
|
||||||
break;
|
break;
|
||||||
case 'string':
|
case 'string':
|
||||||
value = '"$1"';
|
value = ' $1';
|
||||||
break;
|
break;
|
||||||
case 'object':
|
case 'object':
|
||||||
value = '{$1}';
|
value = '\n\t';
|
||||||
break;
|
break;
|
||||||
case 'array':
|
case 'array':
|
||||||
value = '[$1]';
|
value = '\n\t- ';
|
||||||
break;
|
break;
|
||||||
case 'number':
|
case 'number':
|
||||||
case 'integer':
|
case 'integer':
|
||||||
|
|
@ -834,8 +836,8 @@ export class YAMLCompletion {
|
||||||
}
|
}
|
||||||
|
|
||||||
private getCurrentWord(document: TextDocument, offset: number) {
|
private getCurrentWord(document: TextDocument, offset: number) {
|
||||||
var i = offset - 1;
|
let i = offset - 1;
|
||||||
var text = document.getText();
|
const text = document.getText();
|
||||||
while (i >= 0 && ' \t\n\r\v":{[,]}'.indexOf(text.charAt(i)) === -1) {
|
while (i >= 0 && ' \t\n\r\v":{[,]}'.indexOf(text.charAt(i)) === -1) {
|
||||||
i--;
|
i--;
|
||||||
}
|
}
|
||||||
|
|
@ -853,7 +855,7 @@ export class YAMLCompletion {
|
||||||
case Json.SyntaxKind.EOF:
|
case Json.SyntaxKind.EOF:
|
||||||
return '';
|
return '';
|
||||||
default:
|
default:
|
||||||
return ',';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,37 @@ export class SingleYAMLDocument extends JSONDocument {
|
||||||
return matchingSchemas;
|
return matchingSchemas;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getNodeFromOffset(offset: number, includeRightBound = false): ASTNode {
|
public getNodeFromOffset(offset: number): ASTNode {
|
||||||
return super.getNodeFromOffset(offset, includeRightBound);
|
return super.getNodeFromOffset(offset, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getNodeFromOffsetEndInclusive(offset: number): ASTNode {
|
||||||
|
let collector: ASTNode[] = [];
|
||||||
|
let findNode = (node: ASTNode): ASTNode => {
|
||||||
|
if (offset >= node.offset && offset <= node.offset + node.length) {
|
||||||
|
let children = node.children;
|
||||||
|
for (let i = 0; i < children.length && children[i].offset <= offset; i++) {
|
||||||
|
let item = findNode(children[i]);
|
||||||
|
if (item) {
|
||||||
|
collector.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
let foundNode = findNode(this.root);
|
||||||
|
let currMinDist = Number.MAX_VALUE;
|
||||||
|
let currMinNode = null;
|
||||||
|
for (let possibleNode in collector) {
|
||||||
|
let currNode = collector[possibleNode];
|
||||||
|
let minDist = (currNode.offset + currNode.length - offset) + (offset - currNode.offset);
|
||||||
|
if (minDist < currMinDist) {
|
||||||
|
currMinNode = currNode;
|
||||||
|
currMinDist = minDist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return currMinNode || foundNode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,18 +75,19 @@ export class Schema {
|
||||||
}
|
}
|
||||||
|
|
||||||
static DEFAULT = null;
|
static DEFAULT = null;
|
||||||
static create = function createSchema() {
|
static create = function createSchema(...args: [Schema | Schema[], Type[]] | [Type[]]) {
|
||||||
var schemas, types;
|
let schemas: Schema | Schema[];
|
||||||
|
let types: Type[];
|
||||||
|
|
||||||
switch (arguments.length) {
|
switch (args.length) {
|
||||||
case 1:
|
case 1:
|
||||||
schemas = Schema.DEFAULT;
|
schemas = Schema.DEFAULT;
|
||||||
types = arguments[0];
|
types = args[0];
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
schemas = arguments[0];
|
schemas = args[0];
|
||||||
types = arguments[1];
|
types = args[1];
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -26,15 +26,16 @@ export function setupMode(defaults: LanguageServiceDefaultsImpl): void {
|
||||||
|
|
||||||
let languageId = defaults.languageId;
|
let languageId = defaults.languageId;
|
||||||
|
|
||||||
// TODO:
|
disposables.push(monaco.languages.registerCompletionItemProvider(languageId, new languageFeatures.CompletionAdapter(worker)));
|
||||||
// disposables.push(monaco.languages.registerCompletionItemProvider(languageId, new languageFeatures.CompletionAdapter(worker)));
|
|
||||||
disposables.push(monaco.languages.registerHoverProvider(languageId, new languageFeatures.HoverAdapter(worker)));
|
disposables.push(monaco.languages.registerHoverProvider(languageId, new languageFeatures.HoverAdapter(worker)));
|
||||||
disposables.push(monaco.languages.registerDocumentSymbolProvider(languageId, new languageFeatures.DocumentSymbolAdapter(worker)));
|
disposables.push(monaco.languages.registerDocumentSymbolProvider(languageId, new languageFeatures.DocumentSymbolAdapter(worker)));
|
||||||
disposables.push(monaco.languages.registerColorProvider(languageId, new languageFeatures.DocumentColorAdapter(worker)));
|
|
||||||
disposables.push(monaco.languages.registerDocumentFormattingEditProvider(languageId, new languageFeatures.DocumentFormattingEditProvider(worker)));
|
disposables.push(monaco.languages.registerDocumentFormattingEditProvider(languageId, new languageFeatures.DocumentFormattingEditProvider(worker)));
|
||||||
disposables.push(monaco.languages.registerDocumentRangeFormattingEditProvider(languageId, new languageFeatures.DocumentRangeFormattingEditProvider(worker)));
|
disposables.push(monaco.languages.registerDocumentRangeFormattingEditProvider(languageId, new languageFeatures.DocumentRangeFormattingEditProvider(worker)));
|
||||||
disposables.push(new languageFeatures.DiagnosticsAdapter(languageId, worker, defaults));
|
disposables.push(new languageFeatures.DiagnosticsAdapter(languageId, worker, defaults));
|
||||||
disposables.push(monaco.languages.setLanguageConfiguration(languageId, richEditConfiguration));
|
disposables.push(monaco.languages.setLanguageConfiguration(languageId, richEditConfiguration));
|
||||||
|
|
||||||
|
// Color adapter should be necessary most of the time:
|
||||||
|
// disposables.push(monaco.languages.registerColorProvider(languageId, new languageFeatures.DocumentColorAdapter(worker)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
130
test/index.html
130
test/index.html
|
|
@ -2,21 +2,22 @@
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta http-equiv="X-UA-Compatible"
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
content="IE=edge" />
|
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
||||||
<meta http-equiv="Content-Type"
|
<link rel="stylesheet" data-name="vs/editor/editor.main" href="../node_modules/monaco-editor-core/dev/vs/editor/editor.main.css">
|
||||||
content="text/html;charset=utf-8" />
|
|
||||||
<link rel="stylesheet"
|
|
||||||
data-name="vs/editor/editor.main"
|
|
||||||
href="../node_modules/monaco-editor-core/dev/vs/editor/editor.main.css">
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<h2>Monaco Editor YAML test page</h2>
|
<h2>Monaco Editor YAML test page</h2>
|
||||||
<code id="path"></code>
|
<code id="path"></code>
|
||||||
<div id="container"
|
<div id="container" style="width:800px;height:600px;border:1px solid grey"></div>
|
||||||
style="width:800px;height:600px;border:1px solid grey"></div>
|
|
||||||
|
<style>
|
||||||
|
.x-highlight-range {
|
||||||
|
background-color: lightblue;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Loading basic-languages to get the YAML language definition
|
// Loading basic-languages to get the YAML language definition
|
||||||
|
|
@ -46,41 +47,94 @@
|
||||||
'vs/basic-languages/monaco.contribution',
|
'vs/basic-languages/monaco.contribution',
|
||||||
'vs/language/yaml/monaco.contribution'
|
'vs/language/yaml/monaco.contribution'
|
||||||
], function () {
|
], function () {
|
||||||
const yaml = `apiVersion: apps/v1
|
const yaml = `p1: `;
|
||||||
kind: Deployment
|
const modelUri = monaco.Uri.parse("a://b/foo.json");
|
||||||
metadata:
|
const editor = monaco.editor.create(document.getElementById('container'), {
|
||||||
name: nginx-deployment
|
language: 'yaml',
|
||||||
namespace: default
|
showFoldingControls: 'always',
|
||||||
labels:
|
model: monaco.editor.createModel(yaml, "yaml", modelUri),
|
||||||
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({
|
monaco.languages.yaml.yamlDefaults.setDiagnosticsOptions({
|
||||||
enableSchemaRequest: true,
|
enableSchemaRequest: true,
|
||||||
validate: true,
|
validate: true,
|
||||||
schemas: [
|
schemas: [{
|
||||||
{
|
uri: "http://myserver/foo-schema.json", // id of the first schema
|
||||||
uri: 'https://raw.githubusercontent.com/garethr/kubernetes-json-schema/master/master/deployment.json',
|
fileMatch: [modelUri.toString()], // associate with our model
|
||||||
fileMatch: ['*'],
|
schema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
p1: {
|
||||||
|
enum: ["v1", "v2"]
|
||||||
},
|
},
|
||||||
],
|
p2: {
|
||||||
|
$ref: "http://myserver/bar-schema.json" // reference the second schema
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
uri: "http://myserver/bar-schema.json", // id of the first schema
|
||||||
|
schema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
q1: {
|
||||||
|
enum: ["x1", "x2"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
require(["vs/editor/contrib/quickOpen/quickOpen"], async quickOpen => {
|
||||||
|
const NEVER_CANCEL_TOKEN = {
|
||||||
|
isCancellationRequested: false,
|
||||||
|
onCancellationRequested: () => Event.NONE,
|
||||||
|
};
|
||||||
|
|
||||||
|
let oldDecorations = [];
|
||||||
|
|
||||||
|
async function _getSymbolForPosition(model, position) {
|
||||||
|
const symbols = await quickOpen.getDocumentSymbols(
|
||||||
|
model,
|
||||||
|
false,
|
||||||
|
NEVER_CANCEL_TOKEN,
|
||||||
|
);
|
||||||
|
|
||||||
|
function _recur(symbol) {
|
||||||
|
let target = symbol;
|
||||||
|
if (symbol && symbol.children && symbol.children.length) {
|
||||||
|
target = _recur(symbol.children.find(child => child.range.containsPosition(position))) || symbol;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _recur({ children: symbols });
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.onDidChangeCursorSelection(async ({ selection }) => {
|
||||||
|
const model = editor.getModel();
|
||||||
|
const position = selection.getPosition();
|
||||||
|
const symbol = await _getSymbolForPosition(model, position);
|
||||||
|
|
||||||
|
console.log(`${symbol.name}: ${symbol.range}`);
|
||||||
|
if (symbol && symbol.range) {
|
||||||
|
const decoration = {
|
||||||
|
range: symbol.range,
|
||||||
|
options: {
|
||||||
|
isWholeLine: false,
|
||||||
|
className: 'x-highlight-range',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
oldDecorations = editor.deltaDecorations(
|
||||||
|
oldDecorations,
|
||||||
|
decoration ? [decoration] : [],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -140,10 +140,10 @@ minimatch@^3.0.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion "^1.1.7"
|
brace-expansion "^1.1.7"
|
||||||
|
|
||||||
monaco-editor-core@0.15.0:
|
monaco-editor-core@0.15.5:
|
||||||
version "0.15.0"
|
version "0.15.5"
|
||||||
resolved "https://registry.yarnpkg.com/monaco-editor-core/-/monaco-editor-core-0.15.0.tgz#1ad5f130ed8efae9d9ed85d8a58a55067e14d983"
|
resolved "https://registry.yarnpkg.com/monaco-editor-core/-/monaco-editor-core-0.15.5.tgz#145f1953a8e319282d92502252d68ef3486b6875"
|
||||||
integrity sha512-s1zuo+p6Gl6IC4WJP6HBkr4pWULm+HdFfacB8vOFPQLLi2oJseO20UuSkxYuZTUJQIjvhNrQfLNAmPKLZaf7tg==
|
integrity sha512-kM3KHRjj16cFdK5Z0EppKUu793JVMpsEesBSWlqdgrxcmjyDMXV6xK0oatPcAYp3eOfbbyjPhruxDXj85FKyIg==
|
||||||
|
|
||||||
monaco-languages@1.6.0:
|
monaco-languages@1.6.0:
|
||||||
version "1.6.0"
|
version "1.6.0"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue